Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/cd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,5 @@ jobs:
- name: Generate artifact attestation for sdist and wheel(s)
uses: actions/attest@59d89421af93a897026c735860bf21b6eb4f7b26 # v4.1.0
with:
subject-path: "dist/*"
subject-path: dist/*
- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
4 changes: 1 addition & 3 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,7 @@
nox.options.default_venv_backend = "uv"


# TODO(denialhaag): Add 3.14 when all dependencies support it
# https://github.com/munich-quantum-toolkit/qecc/issues/464
PYTHON_ALL_VERSIONS = ["3.10", "3.11", "3.12", "3.13"]
PYTHON_ALL_VERSIONS = ["3.10", "3.11", "3.12", "3.13", "3.14"]

if os.environ.get("CI", None):
nox.options.error_on_missing_interpreters = True
Expand Down
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)",
"Typing :: Typed",
]
Expand All @@ -51,15 +52,20 @@ dependencies = [
"numpy>=1.24.1",
"numpy>=1.26; python_version >= '3.12'",
"numpy>=2.1; python_version >= '3.13'",
"numpy>=2.3.2; python_version >= '3.14'",
"numba>=0.57",
"numba>=0.59; python_version >= '3.12'",
"numba>=0.61; python_version >= '3.13'",
"numba>=0.63.1; python_version >= '3.14'",
"scipy>=1.15.2",
"scipy>=1.16.1; python_version >= '3.14'",
"z3-solver>=4.15.3",
"multiprocess>=0.70.17",
"multiprocess>=0.70.19; python_version >= '3.14'",
"ldpc>=2.3.10",
Comment thread
denialhaag marked this conversation as resolved.
"stim>=1.14.0",
"pymatching>=2.2.2",
"pymatching>=2.4.0; python_version >= '3.14'",
"bposd>=1.6",
"qecsim>=1.0b9",
"sinter>=1.14.0",
Expand Down
2 changes: 1 addition & 1 deletion src/mqt/qecc/cc_decoder/stim_interface/color_code_stim.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ def gen_stim_circuit_memory_experiment(
"""Generate a stim circuit for a memory experiment on the 2D color code."""
data_qubits = range(len(pcm[0]))
circuit = stim.Circuit()
circuit.append("R", data_qubits) # ty: ignore[no-matching-overload]
circuit.append("R", data_qubits)

# initialization
circuit = add_checks_one_round(pcm, circuit, False, 0)
Expand Down
36 changes: 18 additions & 18 deletions src/mqt/qecc/circuit_synthesis/cat_states.py
Original file line number Diff line number Diff line change
Expand Up @@ -73,9 +73,9 @@ def cat_state_line(w: int) -> stim.Circuit:
noisy stim circuit preparing the cat state
"""
circ = stim.Circuit()
circ.append("H", [0]) # ty: ignore[no-matching-overload]
circ.append("H", [0])
for i in reversed(range(1, w)):
circ.append("CX", [0, i]) # ty: ignore[no-matching-overload]
circ.append("CX", [0, i])
return circ


Expand Down Expand Up @@ -153,7 +153,7 @@ def sample_cat_state(
circ = self._get_noisy_circ(p)
# Final, *noise-free* measurement of data qubits
circ.append("TICK") # ty: ignore[invalid-argument-type]
circ.append("MR", list(range(self.w1))) # ty: ignore[no-matching-overload]
circ.append("MR", list(range(self.w1)))

if batch_size is None:
batch_size = n_samples
Expand Down Expand Up @@ -290,8 +290,8 @@ def _add_qubit_initializations(circ: stim.Circuit) -> stim.Circuit:
hadamard_qubits.update(t.value for t in gate.targets_copy())

with_inits = with_inits[::-1]
with_inits.append("RX", list(hadamard_qubits - is_initialized)) # ty: ignore[no-matching-overload]
with_inits.append("R", [q for q in range(circ.num_qubits) if q not in hadamard_qubits and q not in is_initialized]) # ty: ignore[no-matching-overload]
with_inits.append("RX", list(hadamard_qubits - is_initialized))
with_inits.append("R", [q for q in range(circ.num_qubits) if q not in hadamard_qubits and q not in is_initialized])
return with_inits[::-1]


Expand Down Expand Up @@ -417,14 +417,14 @@ def fault_gens_from_circuit(circ: stim.Circuit) -> list[int]:

def _ft_w_4_cat_state() -> tuple[stim.Circuit, list[tuple[list[int], list[int]]]]:
circ = stim.Circuit()
circ.append("RX", [4]) # ty: ignore[no-matching-overload]
circ.append("R", [0, 1, 2, 3]) # ty: ignore[no-matching-overload]
circ.append("CX", [4, 0]) # ty: ignore[no-matching-overload]
circ.append("CX", [0, 1]) # ty: ignore[no-matching-overload]
circ.append("CX", [1, 2]) # ty: ignore[no-matching-overload]
circ.append("CX", [2, 3]) # ty: ignore[no-matching-overload]
circ.append("CX", [3, 4]) # ty: ignore[no-matching-overload]
circ.append("MR", [4]) # ty: ignore[no-matching-overload]
circ.append("RX", [4])
circ.append("R", [0, 1, 2, 3])
circ.append("CX", [4, 0])
circ.append("CX", [0, 1])
circ.append("CX", [1, 2])
circ.append("CX", [2, 3])
circ.append("CX", [3, 4])
circ.append("MR", [4])
return circ, [([4], [0, 1, 2, 3])]


Expand Down Expand Up @@ -490,10 +490,10 @@ def _recurse(w1: int, w2: int) -> tuple[stim.Circuit, list[tuple[list[int], list
new_measurements = []
for i in range(n_meas):
anc = circ.num_qubits
circ.append("R", [anc]) # ty: ignore[no-matching-overload]
circ.append("CX", [i, anc]) # ty: ignore[no-matching-overload]
circ.append("CX", [i + w1, anc]) # ty: ignore[no-matching-overload]
circ.append("MR", [anc]) # ty: ignore[no-matching-overload]
circ.append("R", [anc])
circ.append("CX", [i, anc])
circ.append("CX", [i + w1, anc])
circ.append("MR", [anc])
new_measurements.append(anc)

data_to_flip = list(range(w1)) if w1 < w2 else list(range(w1, w1 + w2))
Expand Down Expand Up @@ -629,7 +629,7 @@ def simulate_recursive_cat_construction(
circ_run += circ_base
circ_run.append("TICK") # ty: ignore[invalid-argument-type]
# measure data at the end
circ_run.append("MR", list(range(w))) # ty: ignore[no-matching-overload]
circ_run.append("MR", list(range(w)))
data_cols_start = len(meas_index_of_qubit) # data bits are at the end

circ_noisy = circ_run
Expand Down
17 changes: 9 additions & 8 deletions src/mqt/qecc/circuit_synthesis/circuit_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def relabel_qubits(circ: Circuit, qubit_mapping: dict[int, int] | int) -> Circui
relabelled_qubits = [qubit_mapping[q.value] for q in op.targets_copy()]
else:
relabelled_qubits = [q.value + qubit_mapping for q in op.targets_copy()]
new_circ.append(op.name, relabelled_qubits) # ty: ignore[no-matching-overload]
new_circ.append(op.name, relabelled_qubits)
return new_circ


Expand All @@ -67,10 +67,10 @@ def qiskit_to_stim_circuit(qc: QuantumCircuit) -> Circuit:
op = gate.operation.name
qubit = qc.find_bit(gate.qubits[0])[0]
if op in single_qubit_gate_map:
stim_circuit.append(single_qubit_gate_map[op], [qubit]) # ty: ignore[no-matching-overload]
stim_circuit.append(single_qubit_gate_map[op], [qubit])
elif op == "cx":
target = qc.find_bit(gate.qubits[1])[0]
stim_circuit.append("CX", [qubit, target]) # ty: ignore[no-matching-overload]
stim_circuit.append("CX", [qubit, target])
elif op == "barrier":
stim_circuit.append("TICK") # ty: ignore[invalid-argument-type]
else:
Expand Down Expand Up @@ -149,8 +149,9 @@ def collect_circuit_layers(circ: Circuit, scheduling_method: str = "asap") -> li
assert isinstance(instr, CircuitInstruction)
for grp in instr.target_groups():
qubits = [q.qubit_value for q in grp]
circ_copy.append(instr.name, qubits) # ty: ignore[no-matching-overload]
circ_copy.append("TICK", []) # ty: ignore[no-matching-overload]
assert all(qubit is not None for qubit in qubits)
circ_copy.append(instr.name, cast("list[int]", qubits))
circ_copy.append("TICK", [])

if scheduling_method == "alap":
circ_copy = circ_copy[::-1] # Reverse the circuit for ALAP scheduling
Expand Down Expand Up @@ -181,7 +182,7 @@ def collect_circuit_layers(circ: Circuit, scheduling_method: str = "asap") -> li

# Check if any qubit from this instruction is already used in the layer
if not any(qubit_layer_used[q] for q in qubits):
layer.append(instr.name, qubits) # ty: ignore[no-matching-overload]
layer.append(instr.name, qubits)
instr_to_delete.append(idx) # Mark this instruction for removal

# Mark the qubits used in this instruction
Expand Down Expand Up @@ -229,7 +230,7 @@ def remove_single_qubit_gates(circ: Circuit) -> Circuit:
assert isinstance(op, CircuitInstruction)
if all(len(grp) == 1 for grp in op.target_groups()):
continue
new_circ.append(op.name, _get_qubit_values(op)) # ty: ignore[no-matching-overload]
new_circ.append(op.name, _get_qubit_values(op))
return new_circ


Expand All @@ -246,7 +247,7 @@ def remove_swap_gates(circ: Circuit) -> Circuit:
for op in circ:
if op.name == "SWAP":
continue
new_circ.append(op.name, _get_qubit_values(op)) # ty: ignore[no-matching-overload]
new_circ.append(op.name, _get_qubit_values(op))
return new_circ


Expand Down
6 changes: 3 additions & 3 deletions src/mqt/qecc/circuit_synthesis/circuits.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ def to_stim_circuit(self, with_resets: bool = True) -> stim.Circuit:
result = stim.Circuit()

for qubit, basis in self._initializations.items():
result.append("R" + basis, [qubit]) # ty: ignore[no-matching-overload]
result.append("R" + basis, [qubit])

result += self._circ

Expand Down Expand Up @@ -387,10 +387,10 @@ def to_stim_circuit(self, with_resets: bool = True) -> stim.Circuit:

if with_resets:
for qubit, basis in self._initializations.items():
stim_circuit.append("R" + basis, [qubit]) # ty: ignore[no-matching-overload]
stim_circuit.append("R" + basis, [qubit])

if self.cnots:
stim_circuit.append("CX", [qubit for pair in self.cnots for qubit in pair]) # ty: ignore[no-matching-overload]
stim_circuit.append("CX", [qubit for pair in self.cnots for qubit in pair])

return stim_circuit

Expand Down
30 changes: 15 additions & 15 deletions src/mqt/qecc/circuit_synthesis/encoding.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,27 +456,27 @@ def cz_var(depth: int, q1: int, q2: int) -> z3.BoolRef:
# Single-qubit gates.
for q in range(n):
if model_bool(model, h_gate[depth][q]):
reduction.append("H", [q]) # ty: ignore[no-matching-overload]
reduction.append("H", [q])
elif model_bool(model, s_gate[depth][q]):
reduction.append("S", [q]) # ty: ignore[no-matching-overload]
reduction.append("S", [q])
elif model_bool(model, sqrt_x_gate[depth][q]):
reduction.append("SQRT_X", [q]) # ty: ignore[no-matching-overload]
reduction.append("SQRT_X", [q])

# Two-qubit gates.
for (control, target), gate in cx_gate[depth].items():
if model_bool(model, gate):
reduction.append("CX", [control, target]) # ty: ignore[no-matching-overload]
reduction.append("CX", [control, target])

for (q1, q2), gate in cz_gate[depth].items():
if model_bool(model, gate):
reduction.append("CZ", [q1, q2]) # ty: ignore[no-matching-overload]
reduction.append("CZ", [q1, q2])

# Normalize terminal X-pivot ancillas to Z-pivot ancillas.
# In the final encoder this becomes initial H preparation of those ancillas.
x_pivot_ancillas: list[int] = [q for q in range(n) if model_bool(model, x_pivot[q])]

if x_pivot_ancillas:
reduction.append("H", x_pivot_ancillas) # ty: ignore[no-matching-overload]
reduction.append("H", x_pivot_ancillas)

# Extract which physical qubits carry the input logical qubits.
encoding_qubits: list[int] = []
Expand All @@ -496,7 +496,7 @@ def cz_var(depth: int, q1: int, q2: int) -> z3.BoolRef:
encoder_circuit = stim.Circuit()

if ancilla_qubits:
encoder_circuit.append("RZ", ancilla_qubits) # ty: ignore[no-matching-overload]
encoder_circuit.append("RZ", ancilla_qubits)

encoder_circuit += reduction.inverse()

Expand Down Expand Up @@ -533,11 +533,11 @@ def cz_var(depth: int, q1: int, q2: int) -> z3.BoolRef:

for q, (xv, zv) in enumerate(zip(x_correction, z_correction, strict=False)):
if xv == 1 and zv == 1:
encoder_circuit.append("Y", [q]) # ty: ignore[no-matching-overload]
encoder_circuit.append("Y", [q])
elif xv == 1:
encoder_circuit.append("X", [q]) # ty: ignore[no-matching-overload]
encoder_circuit.append("X", [q])
elif zv == 1:
encoder_circuit.append("Z", [q]) # ty: ignore[no-matching-overload]
encoder_circuit.append("Z", [q])

return CliffordIsometry.from_stim_circuit(encoder_circuit)

Expand Down Expand Up @@ -744,7 +744,7 @@ def gottesman_encoding_circuit(tableau: StabilizerTableau | Sequence[str]) -> Cl
z_part[row] = t

if x_part[row][column] == 0:
circ.append("H", [column]) # ty: ignore[no-matching-overload]
circ.append("H", [column])
t = x_part[:, column].copy()
x_part[:, column] = z_part[:, column]
z_part[:, column] = t
Expand All @@ -753,26 +753,26 @@ def gottesman_encoding_circuit(tableau: StabilizerTableau | Sequence[str]) -> Cl
for q in np.where(x_part[row])[0]:
if q == column:
continue
circ.append("CX", [column, q]) # ty: ignore[no-matching-overload]
circ.append("CX", [column, q])
x_part[:, q] ^= x_part[:, column]
z_part[:, column] ^= z_part[:, q]

if z_part[row][column] == 1:
circ.append("S", [column]) # ty: ignore[no-matching-overload]
circ.append("S", [column])
z_part[:, column] ^= x_part[:, column]

for q in np.where(z_part[row])[0]:
if q == column:
continue
circ.append("CZ", [column, q]) # ty: ignore[no-matching-overload]
circ.append("CZ", [column, q])
z_part[:, q] ^= x_part[:, column]
z_part[:, column] ^= x_part[:, q]

# reduce stabilizers below row
x_part[:, column] = 0
x_part[row, column] = 1

circ.append("H", initialized) # ty: ignore[no-matching-overload]
circ.append("H", initialized)
circ = circ.inverse()

signs = [s.sign for s in circ.to_tableau().to_stabilizers()]
Expand Down
2 changes: 1 addition & 1 deletion src/mqt/qecc/circuit_synthesis/noise.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,7 @@ def apply(self, circ: Circuit) -> Circuit:
assert isinstance(op, CircuitInstruction)
for grp in op.target_groups():
layer_circ = Circuit()
layer_circ.append(op.name, grp) # ty: ignore[no-matching-overload]
layer_circ.append(op.name, grp)
layers.append(layer_circ)

if self.resets_alap:
Expand Down
Loading
Loading