Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
2037c83
Add quditgates.py and quditcircuit.py.
WeiguoMa Aug 28, 2025
3c062ab
add quditgates to __init__.py
WeiguoMa Aug 28, 2025
30c7025
Add functions from tc.Circuit
WeiguoMa Aug 29, 2025
da94086
Add tests for QuditCircuit
WeiguoMa Aug 29, 2025
6755d9f
Reported a potential bug.
WeiguoMa Aug 29, 2025
2b600b8
Add doc.
WeiguoMa Aug 29, 2025
4b96665
black
WeiguoMa Aug 29, 2025
dbbb685
Add QuditCircuit interface to __init__.py
WeiguoMa Aug 31, 2025
12f66c8
remove a useless pack
WeiguoMa Sep 1, 2025
4eb9dc2
remove useless pack.
WeiguoMa Sep 1, 2025
bde4f24
fixed a possible bug
WeiguoMa Sep 1, 2025
0212135
formatted codes.
WeiguoMa Sep 1, 2025
c910791
remove redundant code.
WeiguoMa Sep 1, 2025
255824a
add test_large_scale_sample
WeiguoMa Sep 2, 2025
edb97b1
Adjust the code framework
WeiguoMa Sep 3, 2025
b0b30d5
new docstring
WeiguoMa Sep 3, 2025
7585731
Add tests for quditgates
WeiguoMa Sep 3, 2025
0e6c053
formatted codes
WeiguoMa Sep 3, 2025
96a55b2
formatted codes
WeiguoMa Sep 3, 2025
9dbabaa
black
WeiguoMa Sep 3, 2025
35288d2
reform doc
WeiguoMa Sep 3, 2025
a3f2131
use np.eye
WeiguoMa Sep 3, 2025
5278f50
use np.diag
WeiguoMa Sep 3, 2025
62750f1
fixed an early notation
WeiguoMa Sep 3, 2025
3a265cc
use np.diag
WeiguoMa Sep 3, 2025
d88b9a9
remove useless line
WeiguoMa Sep 3, 2025
26dd999
change atol from 1e-4 to 1e-5
WeiguoMa Sep 3, 2025
60ce1e7
bug fixed
WeiguoMa Sep 3, 2025
8793b21
use highp
WeiguoMa Sep 3, 2025
2135ecf
remove `count_vector` from sample
WeiguoMa Sep 3, 2025
3e29f11
remove sympy
WeiguoMa Sep 3, 2025
8c86e1c
format codes according to pylint
WeiguoMa Sep 3, 2025
83378fb
change u8 __doc__
WeiguoMa Sep 3, 2025
d379cff
remove 'count_vector'
WeiguoMa Sep 3, 2025
995a3c8
remove y gate
WeiguoMa Sep 3, 2025
10cc46c
remove a y-related test info
WeiguoMa Sep 3, 2025
a64a3b9
remove y
WeiguoMa Sep 3, 2025
b6a8c10
rzz_gate from global to local (for consistence)
WeiguoMa Sep 4, 2025
92d98a3
np -> backend to ensure the auto-differential
WeiguoMa Sep 5, 2025
ee8b617
fixed rzz_gate
WeiguoMa Sep 5, 2025
9cceefa
fixed a docsing bug
WeiguoMa Sep 5, 2025
56902a5
Add expectation_before(), amplitude_before() functions
WeiguoMa Sep 5, 2025
d396ba2
add Gate
WeiguoMa Sep 5, 2025
8b224f0
Provide a corresponding uppercase entry for the doors.
WeiguoMa Sep 5, 2025
0944fb5
np -> backend, and this should solve auto-differential.
WeiguoMa Sep 5, 2025
3dd499b
Fixed a bug that could lead to backend pollution.
WeiguoMa Sep 5, 2025
5c3a082
add test_quditvqe.py for testing vqe, jit and vmap in QUDIT Interface.
WeiguoMa Sep 5, 2025
cdb1ee6
add tests for codecov test.
WeiguoMa Sep 5, 2025
90646ee
black .
WeiguoMa Sep 5, 2025
0e253e1
extend check_rotation()
WeiguoMa Sep 5, 2025
5a9a4ea
remove qudit gates lru_cache
WeiguoMa Sep 5, 2025
507c3ab
optimized codes
WeiguoMa Sep 5, 2025
ee11fd2
remove un-used import.
WeiguoMa Sep 5, 2025
9849ee0
fixed a bug of qudit swap
WeiguoMa Sep 5, 2025
a5246ae
add backend as para for tests/
WeiguoMa Sep 5, 2025
7a8759e
add ref to u8 gate and fixed logical bug of this
WeiguoMa Sep 5, 2025
80b04af
move jit, autograd, vmap tests to test_quditcircuit.py
WeiguoMa Sep 5, 2025
8ff6981
Add an example for qudit vqe.
WeiguoMa Sep 5, 2025
768b003
format
WeiguoMa Sep 5, 2025
68bb073
remove quditvqe test.
WeiguoMa Sep 6, 2025
8aa2695
change function name
WeiguoMa Sep 6, 2025
53669a2
add test for len(nodes) in amplitude_before_test
WeiguoMa Sep 6, 2025
52f9804
optimized examples/vqe_qudit_example.py according to comments.
WeiguoMa Sep 6, 2025
d4d3d8d
refresh the whole file, making no complicated conversion
WeiguoMa Sep 9, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
171 changes: 171 additions & 0 deletions examples/vqe_qudit_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
"""
VQE on QuditCircuits.

This example shows how to run a simple VQE on a qudit system using
`tensorcircuit.QuditCircuit`. We build a compact ansatz using single-qudit
rotations in selected two-level subspaces and RXX-type entanglers, then
optimize the energy of a Hermitian "clock–shift" Hamiltonian:

H(d) = - J * (X_c \otimes X_c) - h * (Z_c \otimes I + I \otimes Z_c)

where, for local dimension `d`,
- Z_c = (Z + Z^\dagger)/2 is the Hermitian "clock" observable with Z = diag(1, \omega, \omega^2, ..., \omega^{d-1})
- X_c = (S + S^\dagger)/2 is the Hermitian "shift" observable with S the cyclic shift
- \omega = exp(2\pi i/d)

The code defaults to a 2-qutrit (d=3) problem but can be changed via CLI flags.
"""

# import os, sys
#
# base_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
# if base_dir not in sys.path:
# sys.path.insert(0, base_dir)

import time
import argparse
import tensorcircuit as tc

tc.set_backend("jax")
tc.set_dtype("complex128")


def vqe_forward(param, *, nqudits: int, d: int, nlayers: int, J: float, h: float):
"""Build a QuditCircuit ansatz and compute ⟨H⟩.

Ansatz:
[ for L in 1...nlayers ]
- On each site q:
RX(q; θ_Lq^(01)) ∘ RY(q; θ_Lq^(12)) ∘ RZ(q; φ_Lq^(0))
(subspace indices shown as superscripts)
- Entangle neighboring pairs with RXX on subspaces (0,1)
"""
if d < 3:
raise ValueError("This example assblumes d >= 3 (qutrit or higher).")

S = tc.quditgates._x_matrix_func(d)
Z = tc.quditgates._z_matrix_func(d)
Sdag = tc.backend.adjoint(S)
Zdag = tc.backend.adjoint(Z)

c = tc.QuditCircuit(nqudits, dim=d)

pairs = [(i, i + 1) for i in range(nqudits - 1)]

it = iter(param)

for _ in range(nlayers):
for q in range(nqudits):
c.rx(q, theta=next(it), j=0, k=1)
c.ry(q, theta=next(it), j=1, k=2)
c.rz(q, theta=next(it), j=0)

for i, j in pairs:
c.rxx(i, j, theta=next(it), j1=0, k1=1, j2=0, k2=1)

# H = -J * 1/2 (S_i S_j^\dagger + S_i^\dagger S_j) - h * 1/2 (Z + Z^\dagger)
energy = 0.0
for i, j in pairs:
e_ij = 0.5 * (
c.expectation((S, [i]), (Sdag, [j])) + c.expectation((Sdag, [i]), (S, [j]))
)
energy += -J * tc.backend.real(e_ij)
for q in range(nqudits):
zq = 0.5 * (c.expectation((Z, [q])) + c.expectation((Zdag, [q])))
energy += -h * tc.backend.real(zq)
return tc.backend.real(energy)


def build_param_shape(nqudits: int, d: int, nlayers: int):
# Per layer per qudit: RX^(01), RY^(12) (or dummy), RZ^(0) = 3 params
# Per layer entanglers: len(pairs) parameters
pairs = nqudits - 1
per_layer = 3 * nqudits + pairs
return (nlayers * per_layer,)


def main():
parser = argparse.ArgumentParser(
description="VQE on QuditCircuit (clock–shift model)"
)
parser.add_argument(
"--d", type=int, default=3, help="Local dimension per site (>=3)"
)
parser.add_argument("--nqudits", type=int, default=2, help="Number of sites")
parser.add_argument("--nlayers", type=int, default=3, help="Ansatz depth (layers)")
parser.add_argument(
"--J", type=float, default=1.0, help="Coupling strength for XcXc term"
)
parser.add_argument(
"--h", type=float, default=0.6, help="Field strength for Zc terms"
)
parser.add_argument("--steps", type=int, default=200, help="Optimization steps")
parser.add_argument("--lr", type=float, default=0.05, help="Learning rate")
args = parser.parse_args()

assert args.d >= 3, "d must be >= 3"

shape = build_param_shape(args.nqudits, args.d, args.nlayers)
param = tc.backend.random_uniform(shape, boundaries=(-0.1, 0.1), seed=42)

try:
import optax

optimizer = optax.adam(args.lr)
vgf = tc.backend.jit(
tc.backend.value_and_grad(
lambda p: vqe_forward(
p,
nqudits=args.nqudits,
d=args.d,
nlayers=args.nlayers,
J=args.J,
h=args.h,
)
)
)
opt_state = optimizer.init(param)

@tc.backend.jit
def train_step(p, opt_state):
loss, grads = vgf(p)
updates, opt_state = optimizer.update(grads, opt_state, p)
p = optax.apply_updates(p, updates)
return p, opt_state, loss

print("Starting VQE optimization (optax/adam)...")
loss = None
for i in range(args.steps):
t0 = time.time()
param, opt_state, loss = train_step(param, opt_state)
# ensure sync for accurate timing
_ = float(loss)
if i % 20 == 0:
dt = time.time() - t0
print(f"Step {i:4d} loss={loss:.6f} dt/step={dt:.4f}s")
print("Final loss:", float(loss) if loss is not None else "n/a")

except ModuleNotFoundError:
print("Optax not available; using naive gradient descent.")
value_and_grad = tc.backend.value_and_grad(
lambda p: vqe_forward(
p,
nqudits=args.nqudits,
d=args.d,
nlayers=args.nlayers,
J=args.J,
h=args.h,
)
)
lr = args.lr
loss = None
for i in range(args.steps):
loss, grads = value_and_grad(param)
param = param - lr * grads
if i % 20 == 0:
print(f"Step {i:4d} loss={float(loss):.6f}")
print("Final loss:", float(loss) if loss is not None else "n/a")


if __name__ == "__main__":
main()
2 changes: 2 additions & 0 deletions tensorcircuit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,10 @@
runtime_contractor,
) # prerun of set hooks
from . import gates
from . import quditgates
from . import basecircuit
from .gates import Gate
from .quditcircuit import QuditCircuit
from .circuit import Circuit, expectation
from .mpscircuit import MPSCircuit
from .densitymatrix import DMCircuit as DMCircuit_reference
Expand Down
24 changes: 4 additions & 20 deletions tensorcircuit/basecircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,28 +480,12 @@ def measure_jit(

def amplitude_before(self, l: Union[str, Tensor]) -> List[Gate]:
r"""
Returns the tensornetwor nodes for the amplitude of the circuit given a computational-basis label ``l``.
For a state simulator, it computes :math:`\langle l \vert \psi\rangle`;
for a density-matrix simulator, it computes :math:`\mathrm{Tr}(\rho \vert l\rangle\langle l\vert)`.
Returns the tensornetwor nodes for the amplitude of the circuit given the bitstring l.
For state simulator, it computes :math:`\langle l\vert \psi\rangle`,
for density matrix simulator, it computes :math:`Tr(\rho \vert l\rangle \langle 1\vert)`
Note how these two are different up to a square operation.

:Example:

>>> c = tc.Circuit(2)
>>> c.X(0)
>>> c.amplitude("10") # d=2, per-qubit digits
array(1.+0.j, dtype=complex64)
>>> c.CNOT(0, 1)
>>> c.amplitude("11")
array(1.+0.j, dtype=complex64)

For qudits (d>2, d<=36):
>>> c = tc.Circuit(3, dim=12)
>>> c.amplitude("0A2") # base-12 string, A stands for 10

:param l: Basis label.
- If a string: it must be a base-d string of length ``nqubits``, using 0–9A–Z (A=10,…,Z=35) when ``d<=36``.
- If a tensor/array/list: it should contain per-site integers in ``[0, d-1]`` with length ``nqubits``.
:param l: The bitstring of 0 and 1s.
:type l: Union[str, Tensor]
:return: The tensornetwork nodes for the amplitude of the circuit.
:rtype: List[Gate]
Expand Down
Loading