Skip to content
Merged
Show file tree
Hide file tree
Changes from 53 commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
935001a
Fixed a potential issue in the qudit system that could have caused la…
WeiguoMa Aug 27, 2025
db7b202
Add variable _ALPHBET for representing the states in qudit systems.
WeiguoMa Aug 27, 2025
81cca4f
Optimized functions in counts, make them avaliable for qudit systems.…
WeiguoMa Aug 27, 2025
52fbf66
Removed hard codes in quantum.py (corresponding to qubit system.), al…
WeiguoMa Aug 27, 2025
d92d6e1
black formatted file.
WeiguoMa Aug 27, 2025
01d2538
Expanded the any_gate function, which is main entry function for the …
WeiguoMa Aug 27, 2025
2a9d8ce
Add a parameter _d for abstractcircuit, which is 2 in Default.
WeiguoMa Aug 27, 2025
9baccfb
Removed hard code in circuit.py (correspond to only qubit system), Ci…
WeiguoMa Aug 27, 2025
e5ff8ca
decode the labels for new representation of states, 0-9A-Z.
WeiguoMa Aug 27, 2025
5b7ffe4
change site label, when dim>2, it changes from `qb-` to `qd-`
WeiguoMa Aug 27, 2025
10627e6
black .
WeiguoMa Aug 27, 2025
2091530
Adjust system to qudit systems.
WeiguoMa Aug 27, 2025
db7b548
Adjust system to qudit systems. Passes all tests in test_mpscircuit.py
WeiguoMa Aug 27, 2025
c18944f
black .
WeiguoMa Aug 27, 2025
c512a59
Considering the existence of resetting the circuit in the line (such …
WeiguoMa Aug 28, 2025
0f319e9
Fixed a potential error when the backend is TensorFlow.
WeiguoMa Aug 28, 2025
2a3e1aa
removed unnecessary codes.
WeiguoMa Aug 29, 2025
aecf4b5
Merge branch 'tensorcircuit:master' into qudit_infras
WeiguoMa Aug 29, 2025
91aea86
fix #41
refraction-ray Aug 30, 2025
48fb567
fix bug
WeiguoMa Aug 31, 2025
d9cbed2
add floor_divide() to all the backends.
WeiguoMa Aug 31, 2025
3d05a5f
add test for floor_divide().
WeiguoMa Aug 31, 2025
c7227e2
remove redundant codes
WeiguoMa Aug 31, 2025
9e88765
make codes consistent
WeiguoMa Aug 31, 2025
855b28c
fixed a potential bug.
WeiguoMa Aug 31, 2025
44f6e0c
fixed a potential bug.
WeiguoMa Aug 31, 2025
cb6dcf6
add tests for d>2 case.
WeiguoMa Aug 31, 2025
8cbf98e
add test for d>2 cases.
WeiguoMa Aug 31, 2025
8b7b227
use backend
WeiguoMa Sep 1, 2025
a7e1577
d -> dim
WeiguoMa Sep 1, 2025
70737e9
Merge branch 'master' into qudit_infras
WeiguoMa Sep 1, 2025
5b42282
black .
WeiguoMa Sep 1, 2025
28af7a2
a bug fixed
WeiguoMa Sep 1, 2025
75b6c1d
I found that the correlation in qudit systems is ill-defined, so does…
WeiguoMa Sep 1, 2025
3832c02
I changed this into a more random method.
WeiguoMa Sep 1, 2025
32893c3
black .
WeiguoMa Sep 1, 2025
3802c7d
add test for conversions.
WeiguoMa Sep 1, 2025
8b41e59
formatted code
WeiguoMa Sep 1, 2025
7624596
remove redundant code.
WeiguoMa Sep 1, 2025
5c24c4e
set parameter `dim` be the last args
WeiguoMa Sep 2, 2025
53ae6a3
bug fixed
WeiguoMa Sep 2, 2025
ffb2006
change strategy
WeiguoMa Sep 2, 2025
f079f24
optimized
WeiguoMa Sep 2, 2025
e3f88dd
fixed a possible bug
WeiguoMa Sep 2, 2025
810d624
adjust n_max_d for dim-dimensional qudit circuits.
WeiguoMa Sep 2, 2025
801fb3a
fix argsort and reshape according to comments.
WeiguoMa Sep 2, 2025
7999217
use _infer_num_sites() instead of risky calculation.
WeiguoMa Sep 2, 2025
d6f0eb4
move _decode_basis_label() from basecircuit.py to quantum.py
WeiguoMa Sep 2, 2025
ca78678
use _infer
WeiguoMa Sep 2, 2025
bf9e6fc
add import
WeiguoMa Sep 2, 2025
4e2e27d
tiny changes based on comments.
WeiguoMa Sep 2, 2025
793876b
use backend.probability_sample()
WeiguoMa Sep 2, 2025
1c5e67e
add onehot_d_tensor in quantum.py and apply.
WeiguoMa Sep 2, 2025
78120ad
remove parse_key
WeiguoMa Sep 2, 2025
d84bd72
use onehot_d_tensor
WeiguoMa Sep 2, 2025
a26be1f
import onhot_d_tensor
WeiguoMa Sep 2, 2025
f82b382
removed a useless import
WeiguoMa Sep 2, 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
1 change: 1 addition & 0 deletions tensorcircuit/abstractcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@

class AbstractCircuit:
_nqubits: int
_d: int = 2
_qir: List[Dict[str, Any]]
_extra_qir: List[Dict[str, Any]]
inputs: Tensor
Expand Down
199 changes: 126 additions & 73 deletions tensorcircuit/basecircuit.py

Large diffs are not rendered by default.

108 changes: 56 additions & 52 deletions tensorcircuit/circuit.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""
Quantum circuit: the state simulator
Quantum circuit: the state simulator.
Supports qubit (dim=2) and qudit (3 <= dim <= 36) systems.
For string-encoded samples/counts, digits use 0–9A–Z where A=10, …, Z=35.
"""

# pylint: disable=invalid-name
Expand All @@ -13,8 +15,8 @@

from . import gates
from . import channels
from .cons import backend, contractor, dtypestr, npdtype
from .quantum import QuOperator, identity
from .cons import backend, contractor, dtypestr, npdtype, _ALPHABET
from .quantum import QuOperator, identity, _infer_num_sites
from .simplify import _full_light_cone_cancel
from .basecircuit import BaseCircuit

Expand All @@ -23,7 +25,7 @@


class Circuit(BaseCircuit):
"""
r"""
``Circuit`` class.
Simple usage demo below.

Expand All @@ -45,21 +47,26 @@ def __init__(
inputs: Optional[Tensor] = None,
mps_inputs: Optional[QuOperator] = None,
split: Optional[Dict[str, Any]] = None,
dim: Optional[int] = None,
) -> None:
"""
r"""
Circuit object based on state simulator.
Do not use this class with d!=2 directly, use tc.QuditCircuit instead for qudit systems.

:param nqubits: The number of qubits in the circuit.
:type nqubits: int
:param dim: The local Hilbert space dimension per site. Qudit is supported for 2 <= d <= 36.
:type dim: If None, the dimension of the circuit will be `2`, which is a qubit system.
:param inputs: If not None, the initial state of the circuit is taken as ``inputs``
instead of :math:`\\vert 0\\rangle^n` qubits, defaults to None.
instead of :math:`\vert 0 \rangle^n` qubits, defaults to None.
:type inputs: Optional[Tensor], optional
:param mps_inputs: QuVector for a MPS like initial wavefunction.
:type mps_inputs: Optional[QuOperator]
:param split: dict if two qubit gate is ready for split, including parameters for at least one of
``max_singular_values`` and ``max_truncation_err``.
:type split: Optional[Dict[str, Any]]
"""
self._d = 2 if dim is None else dim
self.inputs = inputs
self.mps_inputs = mps_inputs
self.split = split
Expand All @@ -70,18 +77,19 @@ def __init__(
"inputs": inputs,
"mps_inputs": mps_inputs,
"split": split,
"dim": dim,
}
if (inputs is None) and (mps_inputs is None):
nodes = self.all_zero_nodes(nqubits)
nodes = self.all_zero_nodes(nqubits, dim=self._d)
self._front = [n.get_edge(0) for n in nodes]
elif inputs is not None: # provide input function
inputs = backend.convert_to_tensor(inputs)
inputs = backend.cast(inputs, dtype=dtypestr)
inputs = backend.reshape(inputs, [-1])
N = inputs.shape[0]
n = int(np.log(N) / np.log(2))
n = _infer_num_sites(N, dim=self._d)
assert n == nqubits or n == 2 * nqubits
inputs = backend.reshape(inputs, [2 for _ in range(n)])
inputs = backend.reshape(inputs, [self._d for _ in range(n)])
inputs = Gate(inputs)
nodes = [inputs]
self._front = [inputs.get_edge(i) for i in range(n)]
Expand Down Expand Up @@ -178,27 +186,14 @@ def mid_measurement(self, index: int, keep: int = 0) -> Tensor:

:param index: The index of qubit that the Z direction postselection applied on.
:type index: int
:param keep: 0 for spin up, 1 for spin down, defaults to be 0.
:param keep: the post-selected digit in {0, ..., d-1}, defaults to be 0.
:type keep: int, optional
"""
# normalization not guaranteed
# assert keep in [0, 1]
if keep < 0.5:
gate = np.array(
[
[1.0],
[0.0],
],
dtype=npdtype,
)
else:
gate = np.array(
[
[0.0],
[1.0],
],
dtype=npdtype,
)
gate = np.array(
[[0.0] if _idx != keep else [1.0] for _idx in range(self._d)],
dtype=npdtype,
)

mg1 = tn.Node(gate)
mg2 = tn.Node(gate)
Expand Down Expand Up @@ -479,7 +474,7 @@ def step_function(x: Tensor) -> Tensor:
if get_gate_from_index is None:
raise ValueError("no `get_gate_from_index` implementation is provided")
g = get_gate_from_index(r, kraus)
g = backend.reshape(g, [2 for _ in range(sites * 2)])
g = backend.reshape(g, [self._d for _ in range(sites * 2)])
self.any(*index, unitary=g, name=name) # type: ignore
return r

Expand Down Expand Up @@ -680,7 +675,7 @@ def _meta_apply_channels(cls) -> None:
Apply %s quantum channel on the circuit.
See :py:meth:`tensorcircuit.channels.%schannel`

:param index: Qubit number that the gate applies on.
:param index: Site index that the gate applies on.
:type index: int.
:param status: uniform external random number between 0 and 1
:type status: Tensor
Expand Down Expand Up @@ -737,8 +732,8 @@ def get_quoperator(self) -> QuOperator:
:return: ``QuOperator`` object for the circuit unitary (open indices for the input state)
:rtype: QuOperator
"""
mps = identity([2 for _ in range(self._nqubits)])
c = Circuit(self._nqubits)
mps = identity([self._d for _ in range(self._nqubits)])
c = Circuit(self._nqubits, dim=self._d)
ns, es = self._copy()
c._nodes = ns
c._front = es
Expand All @@ -758,8 +753,8 @@ def matrix(self) -> Tensor:
:return: The circuit unitary matrix
:rtype: Tensor
"""
mps = identity([2 for _ in range(self._nqubits)])
c = Circuit(self._nqubits)
mps = identity([self._d for _ in range(self._nqubits)])
c = Circuit(self._nqubits, dim=self._d)
ns, es = self._copy()
c._nodes = ns
c._front = es
Expand All @@ -772,6 +767,9 @@ def measure_reference(
"""
Take measurement on the given quantum lines by ``index``.

Return format:
- For d <= 36, the sample is a base-d string using 0–9A–Z (A=10,…).

:Example:

>>> c = tc.Circuit(3)
Expand Down Expand Up @@ -800,10 +798,9 @@ def measure_reference(
if i != j:
e ^ edge2[i]
for i in range(len(sample)):
if sample[i] == "0":
m = np.array([1, 0], dtype=npdtype)
else:
m = np.array([0, 1], dtype=npdtype)
m = np.array([0 for _ in range(self._d)], dtype=npdtype)
m[int(sample[i])] = 1

nodes1.append(tn.Node(m))
nodes1[-1].get_edge(0) ^ edge1[index[i]]
nodes2.append(tn.Node(m))
Expand All @@ -814,15 +811,13 @@ def measure_reference(
/ p
* contractor(nodes1, output_edge_order=[edge1[j], edge2[j]]).tensor
)
pu = rho[0, 0]
r = backend.random_uniform([])
r = backend.real(backend.cast(r, dtypestr))
if r < backend.real(pu):
sample += "0"
p = p * pu
else:
sample += "1"
p = p * (1 - pu)
probs = backend.real(backend.diagonal(rho))
probs /= backend.sum(probs)
outcome = backend.implicit_randc(self._d, shape=1, p=probs)

sample += _ALPHABET[outcome]
p *= float(probs[outcome])

if with_prob:
return sample, p
else:
Expand All @@ -842,6 +837,10 @@ def expectation(
) -> Tensor:
"""
Compute the expectation of corresponding operators.
For qudit (d > 2),
ensure that operator tensor shapes are consistent with d (each site contributes two axes of size d).

Noise shorthand (via noise_conf) is qubit-only; for d>2, use explicit operators.

:Example:

Expand Down Expand Up @@ -883,8 +882,6 @@ def expectation(
:return: Tensor with one element
:rtype: Tensor
"""
from .noisemodel import expectation_noisfy

if noise_conf is None:
# if not reuse:
# nodes1, edge1 = self._copy()
Expand All @@ -899,6 +896,8 @@ def expectation(
nodes1 = _full_light_cone_cancel(nodes1)
return contractor(nodes1).tensor
else:
from .noisemodel import expectation_noisfy

return expectation_noisfy(
self,
*ops,
Expand All @@ -919,9 +918,11 @@ def expectation(
bra: Optional[Tensor] = None,
conj: bool = True,
normalization: bool = False,
dim: Optional[int] = None,
) -> Tensor:
"""
Compute :math:`\\langle bra\\vert ops \\vert ket\\rangle`.
For qudit systems (d>2), ops must be reshaped with per-site axes of length d.

Example 1 (:math:`bra` is same as :math:`ket`)

Expand Down Expand Up @@ -966,6 +967,8 @@ def expectation(
:type ket: Tensor
:param bra: :math:`bra`, defaults to None, which is the same as ``ket``.
:type bra: Optional[Tensor], optional
:param dim: dimension of the circuit (defaults to 2)
:type dim: int, optional
:param conj: :math:`bra` changes to the adjoint matrix of :math:`bra`, defaults to True.
:type conj: bool, optional
:param normalization: Normalize the :math:`ket` and :math:`bra`, defaults to False.
Expand All @@ -974,6 +977,7 @@ def expectation(
:return: The result of :math:`\\langle bra\\vert ops \\vert ket\\rangle`.
:rtype: Tensor
"""
dim = 2 if dim is None else dim
if bra is None:
bra = ket
if isinstance(ket, QuOperator):
Expand All @@ -987,7 +991,7 @@ def expectation(
for op, index in ops:
if not isinstance(op, tn.Node):
# op is only a matrix
op = backend.reshape2(op)
op = backend.reshaped(op, dim)
op = gates.Gate(op)
if isinstance(index, int):
index = [index]
Expand All @@ -1011,8 +1015,8 @@ def expectation(
if conj is True:
bra = backend.conj(bra)
ket = backend.reshape(ket, [-1])
ket = backend.reshape2(ket)
bra = backend.reshape2(bra)
ket = backend.reshaped(ket, dim)
bra = backend.reshaped(bra, dim)
n = len(backend.shape_tuple(ket))
ket = Gate(ket)
bra = Gate(bra)
Expand All @@ -1024,7 +1028,7 @@ def expectation(
for op, index in ops:
if not isinstance(op, tn.Node):
# op is only a matrix
op = backend.reshape2(op)
op = backend.reshaped(op, dim)
op = gates.Gate(op)
if isinstance(index, int):
index = [index]
Expand Down
1 change: 1 addition & 0 deletions tensorcircuit/cons.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ def sorted_edges(edges: Iterator[tn.Edge]) -> List[tn.Edge]:
npdtype = np.complex64
backend: NumpyBackend = get_backend("numpy")
contractor = tn.contractors.auto
_ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
# these above lines are just for mypy, it is not very good at evaluating runtime object


Expand Down
21 changes: 13 additions & 8 deletions tensorcircuit/densitymatrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from .circuit import Circuit
from .cons import backend, contractor, dtypestr
from .basecircuit import BaseCircuit
from .quantum import QuOperator
from .quantum import QuOperator, _infer_num_sites

Gate = gates.Gate
Tensor = Any
Expand All @@ -35,9 +35,11 @@ def __init__(
dminputs: Optional[Tensor] = None,
mpo_dminputs: Optional[QuOperator] = None,
split: Optional[Dict[str, Any]] = None,
dim: Optional[int] = None,
) -> None:
"""
The density matrix simulator based on tensornetwork engine.
Do not use this class with d!=2 directly

:param nqubits: Number of qubits
:type nqubits: int
Expand All @@ -55,6 +57,7 @@ def __init__(
``max_singular_values`` and ``max_truncation_err``.
:type split: Optional[Dict[str, Any]]
"""
self._d = 2 if dim is None else dim
if not empty:
if (
(inputs is None)
Expand All @@ -73,9 +76,9 @@ def __init__(
inputs = backend.cast(inputs, dtype=dtypestr)
inputs = backend.reshape(inputs, [-1])
N = inputs.shape[0]
n = int(np.log(N) / np.log(2))
n = _infer_num_sites(N, self._d)
assert n == nqubits
inputs = backend.reshape(inputs, [2 for _ in range(n)])
inputs = backend.reshape(inputs, [self._d for _ in range(n)])
inputs_gate = Gate(inputs)
self._nodes = [inputs_gate]
self.coloring_nodes(self._nodes)
Expand All @@ -94,7 +97,9 @@ def __init__(
elif dminputs is not None:
dminputs = backend.convert_to_tensor(dminputs)
dminputs = backend.cast(dminputs, dtype=dtypestr)
dminputs = backend.reshape(dminputs, [2 for _ in range(2 * nqubits)])
dminputs = backend.reshape(
dminputs, [self._d for _ in range(2 * nqubits)]
)
dminputs_gate = Gate(dminputs)
nodes = [dminputs_gate]
self._front = [dminputs_gate.get_edge(i) for i in range(2 * nqubits)]
Expand Down Expand Up @@ -217,7 +222,7 @@ def apply_general_kraus(
dd = dmc.densitymatrix()
circuits.append(dd)
tensor = reduce(add, circuits)
tensor = backend.reshape(tensor, [2 for _ in range(2 * self._nqubits)])
tensor = backend.reshaped(tensor, d=self._d)
self._nodes = [Gate(tensor)]
dangling = [e for e in self._nodes[0]]
self._front = dangling
Expand Down Expand Up @@ -255,7 +260,7 @@ def densitymatrix(self, check: bool = False, reuse: bool = True) -> Tensor:
t = contractor(nodes, output_edge_order=d_edges)
else:
t = nodes[0]
dm = backend.reshape(t.tensor, shape=[2**self._nqubits, 2**self._nqubits])
dm = backend.reshapem(t.tensor)
if check:
self.check_density_matrix(dm)
return dm
Expand All @@ -274,7 +279,7 @@ def wavefunction(self) -> Tensor:
dm = self.densitymatrix()
e, v = backend.eigh(dm)
np.testing.assert_allclose(
e[:-1], backend.zeros([2**self._nqubits - 1]), atol=1e-5
e[:-1], backend.zeros([self._d**self._nqubits - 1]), atol=1e-5
)
return v[:, -1]

Expand Down Expand Up @@ -375,7 +380,7 @@ def apply_general_kraus(
# index = [index[0] for _ in range(len(kraus))]
super_op = kraus_to_super_gate(kraus)
nlegs = 4 * len(index)
super_op = backend.reshape(super_op, [2 for _ in range(nlegs)])
super_op = backend.reshape(super_op, [self._d for _ in range(nlegs)])
super_op = Gate(super_op)
o2i = int(nlegs / 2)
r2l = int(nlegs / 4)
Expand Down
Loading