From 4200ef1d0e7cc4d20aac02e2a2136c7b563e31b5 Mon Sep 17 00:00:00 2001 From: "LAPTOP-N4TQ80AC\\khusa" Date: Sat, 9 May 2026 11:24:20 +0200 Subject: [PATCH 01/72] Added main.py --- main.py | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 main.py diff --git a/main.py b/main.py new file mode 100644 index 000000000..9ddee3c66 --- /dev/null +++ b/main.py @@ -0,0 +1,9 @@ + +import src.mqt.bench.benchmark_generation as benchmark_generation + +if __name__ == "__main__": + for alg in ["ghz", "bv", "graphstate"]: # add QFT + for code in ["shor", "stean"]: + for qubits in range(2, 3): + qc = benchmark_generation.get_benchmark(benchmark=alg, level= benchmark_generation.BenchmarkLevel.ALG, circuit_size=qubits) + print(qc) From 5a700a012b7d6609d59e6e56c09d4abb59b90311 Mon Sep 17 00:00:00 2001 From: Saleh Alsherif Date: Sat, 9 May 2026 12:08:58 +0200 Subject: [PATCH 02/72] added code --- main.py | 4 ++-- src/mqt/bench/benchmark_generation.py | 5 +++++ 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 9ddee3c66..cf8aa9634 100644 --- a/main.py +++ b/main.py @@ -4,6 +4,6 @@ if __name__ == "__main__": for alg in ["ghz", "bv", "graphstate"]: # add QFT for code in ["shor", "stean"]: - for qubits in range(2, 3): - qc = benchmark_generation.get_benchmark(benchmark=alg, level= benchmark_generation.BenchmarkLevel.ALG, circuit_size=qubits) + for qubits in range(3, 5): + qc = benchmark_generation.get_benchmark(code=code, benchmark=alg, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=qubits) print(qc) diff --git a/src/mqt/bench/benchmark_generation.py b/src/mqt/bench/benchmark_generation.py index e982a7956..e464e7f1c 100644 --- a/src/mqt/bench/benchmark_generation.py +++ b/src/mqt/bench/benchmark_generation.py @@ -480,6 +480,7 @@ def get_benchmark( circuit_size: int, target: Target | None = None, opt_level: int = 2, + code: str = "", *, generate_mirror_circuit: bool = False, random_parameters: bool = True, @@ -494,6 +495,7 @@ def get_benchmark( circuit_size: None, target: Target | None = None, opt_level: int = 2, + code: str = "", *, generate_mirror_circuit: bool = False, random_parameters: bool = True, @@ -508,6 +510,7 @@ def get_benchmark( circuit_size: int | None = None, target: Target | None = None, opt_level: int = 2, + code: str = "", *, generate_mirror_circuit: bool = False, random_parameters: bool = True, @@ -521,6 +524,7 @@ def get_benchmark( circuit_size: int | None = None, target: Target | None = None, opt_level: int = 2, + code: str = "", # noqa: ARG001 *, generate_mirror_circuit: bool = False, random_parameters: bool = True, @@ -535,6 +539,7 @@ def get_benchmark( target: `~qiskit.transpiler.target.Target` for the benchmark generation (only used for "nativegates" and "mapped" level) opt_level: Optimization level to be used by the transpiler. + code: Error correction code to be used (currently unused). generate_mirror_circuit: If True, generates the mirror version (U @ U.inverse()) of the benchmark. random_parameters: If True, assigns random parameters to the circuit's parameters if they exist. kwargs: Additional keyword arguments passed to the circuit creation. From 38aa1f8795205a7cafa7d545314197f9ebd5a713 Mon Sep 17 00:00:00 2001 From: "LAPTOP-N4TQ80AC\\khusa" Date: Sat, 9 May 2026 12:55:08 +0200 Subject: [PATCH 03/72] added entrying points --- .gitignore | 1 + src/mqt/bench/benchmark_generation.py | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index d664737b1..671e137ea 100644 --- a/.gitignore +++ b/.gitignore @@ -301,3 +301,4 @@ pyrightconfig.json # setuptools_scm src/**/_version.py +.idea/ diff --git a/src/mqt/bench/benchmark_generation.py b/src/mqt/bench/benchmark_generation.py index e464e7f1c..ecb94e798 100644 --- a/src/mqt/bench/benchmark_generation.py +++ b/src/mqt/bench/benchmark_generation.py @@ -200,6 +200,7 @@ def get_benchmark_alg( def get_benchmark_alg( benchmark: str | QuantumCircuit, circuit_size: int | None = None, + code:str = "", *, generate_mirror_circuit: bool = False, random_parameters: bool = True, @@ -210,6 +211,7 @@ def get_benchmark_alg( Arguments: benchmark: QuantumCircuit or name of the benchmark to be generated circuit_size: Input for the benchmark creation, in most cases this is equal to the qubit number + code: generate_mirror_circuit: If True, generates the mirror version (U @ U.inverse()) of the benchmark. random_parameters: If True, assigns random parameters to the circuit's parameters if they exist. kwargs: Additional keyword arguments passed to the circuit creation. @@ -218,8 +220,15 @@ def get_benchmark_alg( Qiskit::QuantumCircuit representing the raw benchmark circuit without any hardware-specific compilation or mapping. """ qc = _get_circuit(benchmark, circuit_size, random_parameters, **kwargs) + # Todo: Make it combined with error code if generate_mirror_circuit: return _create_mirror_circuit(qc, inplace=True) + + #if code == "shor": + # return generate_shor(qc) + #elif code == "stean": + # return generate_stean(qc) + return qc @@ -524,7 +533,7 @@ def get_benchmark( circuit_size: int | None = None, target: Target | None = None, opt_level: int = 2, - code: str = "", # noqa: ARG001 + code: str = "", *, generate_mirror_circuit: bool = False, random_parameters: bool = True, @@ -553,6 +562,7 @@ def get_benchmark( circuit_size=circuit_size, generate_mirror_circuit=generate_mirror_circuit, random_parameters=random_parameters, + code=code, **kwargs, ) From df2affd18a8b1aeb3af402f3fd0a5373c18b4008 Mon Sep 17 00:00:00 2001 From: Saleh Alsherif Date: Wed, 13 May 2026 11:47:05 +0200 Subject: [PATCH 04/72] changed the name of the argument to encoding to match the project description --- main.py | 4 ++-- src/mqt/bench/benchmark_generation.py | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/main.py b/main.py index cf8aa9634..b59887d57 100644 --- a/main.py +++ b/main.py @@ -1,9 +1,9 @@ -import src.mqt.bench.benchmark_generation as benchmark_generation +import mqt.bench.benchmark_generation as benchmark_generation if __name__ == "__main__": for alg in ["ghz", "bv", "graphstate"]: # add QFT for code in ["shor", "stean"]: for qubits in range(3, 5): - qc = benchmark_generation.get_benchmark(code=code, benchmark=alg, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=qubits) + qc = benchmark_generation.get_benchmark(benchmark=alg, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=qubits, encoding=code) print(qc) diff --git a/src/mqt/bench/benchmark_generation.py b/src/mqt/bench/benchmark_generation.py index ecb94e798..5929ca7e1 100644 --- a/src/mqt/bench/benchmark_generation.py +++ b/src/mqt/bench/benchmark_generation.py @@ -200,7 +200,7 @@ def get_benchmark_alg( def get_benchmark_alg( benchmark: str | QuantumCircuit, circuit_size: int | None = None, - code:str = "", + encoding:str = "", *, generate_mirror_circuit: bool = False, random_parameters: bool = True, @@ -224,9 +224,9 @@ def get_benchmark_alg( if generate_mirror_circuit: return _create_mirror_circuit(qc, inplace=True) - #if code == "shor": + #if encoding == "shor": # return generate_shor(qc) - #elif code == "stean": + #elif encoding == "stean": # return generate_stean(qc) return qc @@ -489,7 +489,7 @@ def get_benchmark( circuit_size: int, target: Target | None = None, opt_level: int = 2, - code: str = "", + encoding: str = "", *, generate_mirror_circuit: bool = False, random_parameters: bool = True, @@ -504,7 +504,7 @@ def get_benchmark( circuit_size: None, target: Target | None = None, opt_level: int = 2, - code: str = "", + encoding: str = "", *, generate_mirror_circuit: bool = False, random_parameters: bool = True, @@ -519,7 +519,7 @@ def get_benchmark( circuit_size: int | None = None, target: Target | None = None, opt_level: int = 2, - code: str = "", + encoding: str = "", *, generate_mirror_circuit: bool = False, random_parameters: bool = True, @@ -533,7 +533,7 @@ def get_benchmark( circuit_size: int | None = None, target: Target | None = None, opt_level: int = 2, - code: str = "", + encoding: str = "", *, generate_mirror_circuit: bool = False, random_parameters: bool = True, @@ -562,7 +562,7 @@ def get_benchmark( circuit_size=circuit_size, generate_mirror_circuit=generate_mirror_circuit, random_parameters=random_parameters, - code=code, + encoding=code, **kwargs, ) From e3295e6be27bf3817cc72148f49b061cc2d8a4ef Mon Sep 17 00:00:00 2001 From: salehalsherif Date: Sat, 16 May 2026 12:27:58 +0200 Subject: [PATCH 05/72] Shorcode Transpiler excluding S gates --- .../bench/error_correction/shor_transpiler.py | 301 ++++++++++++++++++ tests/test_shor_transpiler.py | 58 ++++ 2 files changed, 359 insertions(+) create mode 100644 src/mqt/bench/error_correction/shor_transpiler.py create mode 100644 tests/test_shor_transpiler.py diff --git a/src/mqt/bench/error_correction/shor_transpiler.py b/src/mqt/bench/error_correction/shor_transpiler.py new file mode 100644 index 000000000..bcf2ba419 --- /dev/null +++ b/src/mqt/bench/error_correction/shor_transpiler.py @@ -0,0 +1,301 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Shor Transpiler for converting standard circuits into fault-tolerant circuits using the 9-qubit Shor code.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister +from qiskit.circuit import AncillaRegister + +# ruff: noqa: PLC2701 +# these functions are reused from the benchmark and they should be extendable i.e. they shouldn't be private +from mqt.bench.benchmarks.shors_nine_qubit_code import ( + _apply_nine_qubit_shors_code_bit_flip_correction, + _apply_nine_qubit_shors_code_phase_flip_correction, + _get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit, + _get_three_qubit_bit_flip_encoding_decoding_circuit, + _get_three_qubit_bit_flip_syndrome_extraction_circuit, + _get_three_qubit_phase_flip_encoding_circuit, +) + +if TYPE_CHECKING: + from qiskit.circuit import CircuitInstruction + + +class ShorTranspiler: + """A high-level transpiler that encodes a QuantumCircuit using Shor's 9-qubit error correction code.""" + + def __init__(self, original_circuit: QuantumCircuit) -> None: + """Initialize the transpiler with the original QuantumCircuit.""" + self.original_qc = original_circuit + self.num_logical_qubits = original_circuit.num_qubits + self.physical_data_registers: list[QuantumRegister] = [] + self.bit_flip_syndromes: list[AncillaRegister] = [] + self.phase_flip_syndromes: list[AncillaRegister] = [] + self.bit_flip_syndrome_measurements: list[ClassicalRegister] = [] + self.phase_flip_syndrome_measurements: list[ClassicalRegister] = [] + self.transpiled_qc = QuantumCircuit() + + def transpile(self) -> QuantumCircuit: + """Transpile the original circuit to a fault-tolerant circuit using Shor's code.""" + self.encode_qubits() + self.replace_gates() + return self.transpiled_qc + + def encode_qubits(self) -> None: + """Replace each logical qubit with a 9-qubit physical register and apply Shor encoding.""" + all_registers = [] + for logical_qubit_index in range(self.num_logical_qubits): + physical_data_register = QuantumRegister(9, f"q{logical_qubit_index}") + bit_flip_syndrome_register = AncillaRegister(6, f"bs{logical_qubit_index}") + phase_flip_syndrome_register = AncillaRegister(2, f"ps{logical_qubit_index}") + bit_flip_measurement_register = ClassicalRegister(6, f"bsm{logical_qubit_index}") + phase_flip_measurement_register = ClassicalRegister(2, f"psm{logical_qubit_index}") + + self.physical_data_registers.append(physical_data_register) + self.bit_flip_syndromes.append(bit_flip_syndrome_register) + self.phase_flip_syndromes.append(phase_flip_syndrome_register) + self.bit_flip_syndrome_measurements.append(bit_flip_measurement_register) + self.phase_flip_syndrome_measurements.append(phase_flip_measurement_register) + + all_registers.extend([ + physical_data_register, + bit_flip_syndrome_register, + phase_flip_syndrome_register, + bit_flip_measurement_register, + phase_flip_measurement_register, + ]) + + self.transpiled_qc = QuantumCircuit(*all_registers) + self.transpiled_qc.name = f"{self.original_qc.name}_shor_encoded" + + # Apply encoding for each logical qubit + for logical_qubit_index in range(self.num_logical_qubits): + physical_data_register = self.physical_data_registers[logical_qubit_index] + + # Phase flip encoding on the first qubit of each block + self.transpiled_qc.compose( + _get_three_qubit_phase_flip_encoding_circuit(), + qubits=[physical_data_register[0], physical_data_register[3], physical_data_register[6]], + inplace=True, + ) + + # Bit flip encoding on each block + self.transpiled_qc.compose( + _get_three_qubit_bit_flip_encoding_decoding_circuit(), + qubits=physical_data_register[:3], + inplace=True, + ) + self.transpiled_qc.compose( + _get_three_qubit_bit_flip_encoding_decoding_circuit(), + qubits=physical_data_register[3:6], + inplace=True, + ) + self.transpiled_qc.compose( + _get_three_qubit_bit_flip_encoding_decoding_circuit(), + qubits=physical_data_register[6:9], + inplace=True, + ) + self.transpiled_qc.barrier() + + def replace_gates(self) -> None: + """Scan original circuit and replace gates with logical equivalents.""" + gate_handlers = { + "barrier": self._handle_barrier, + "measure": self._handle_measure, + "h": self._handle_h, + "x": self._handle_x, + "z": self._handle_z, + "s": self._handle_s, + "cx": self._handle_cx, + "cz": self._handle_cz, + } + + for instruction in self.original_qc.data: + gate_name = instruction.operation.name + if gate_name in gate_handlers: + gate_handlers[gate_name](instruction) + else: + msg = f"Gate {gate_name} is not supported by ShorTranspiler." + raise NotImplementedError(msg) + + def _handle_barrier(self, instruction: CircuitInstruction) -> None: + """Handle barrier instruction.""" + logical_instruction_qubits = instruction.qubits + involved_physical_data_registers = [ + self.physical_data_registers[self.original_qc.qubits.index(logical_qubit)] + for logical_qubit in logical_instruction_qubits + ] + flattened_physical_qubits = [ + physical_qubit + for physical_data_register in involved_physical_data_registers + for physical_qubit in physical_data_register + ] + if flattened_physical_qubits: + self.transpiled_qc.barrier(flattened_physical_qubits) + else: + self.transpiled_qc.barrier() + + def _handle_measure(self, instruction: CircuitInstruction) -> None: + """Handle measure instruction.""" + logical_instruction_qubits = instruction.qubits + logical_instruction_clbits = instruction.clbits + logical_qubit_index = self.original_qc.qubits.index(logical_instruction_qubits[0]) + logical_classical_bit_index = self.original_qc.clbits.index(logical_instruction_clbits[0]) + measurement_register_name = f"meas_{logical_qubit_index}_{logical_classical_bit_index}" + physical_measurement_register = ClassicalRegister(9, measurement_register_name) + self.transpiled_qc.add_register(physical_measurement_register) + + physical_data_register = self.physical_data_registers[logical_qubit_index] + + # --- Editable and Well-Commented Majority Vote Section --- + # NOTE: In a physical quantum computer, you would measure the 9 physical qubits + # into 9 classical bits. Classical post-processing would then compute the + # majority vote across the 3 bit-flip blocks and then across the phase-flip blocks. + # Here, we perform the 9 physical measurements. + self.transpiled_qc.measure(physical_data_register, physical_measurement_register) + + # A classical post-processing function for majority vote could look like: + # + # def classical_majority_vote(bitstring: str) -> str: + # '''Computes the logical bit value from the 9 physical bit measurements.''' + # # bitstring is 9 bits: b8 b7 b6 b5 b4 b3 b2 b1 b0 + # blocks = [bitstring[6:9], bitstring[3:6], bitstring[0:3]] + # block_votes = [1 if block.count('1') > 1 else 0 for block in blocks] + # logical_bit = 1 if sum(block_votes) > 1 else 0 + # return str(logical_bit) + # --------------------------------------------------------- + + def _handle_h(self, instruction: CircuitInstruction) -> None: + """Handle Hadamard instruction.""" + logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) + physical_data_register = self.physical_data_registers[logical_qubit_index] + for physical_qubit_index in range(9): + self.transpiled_qc.h(physical_data_register[physical_qubit_index]) + # The Hadamard gate is not completely transversal for Shor's code. + # It needs to be followed by a swap that transposes the 9 qubits. + self.transpiled_qc.swap(physical_data_register[1], physical_data_register[3]) + self.transpiled_qc.swap(physical_data_register[2], physical_data_register[6]) + self.transpiled_qc.swap(physical_data_register[5], physical_data_register[7]) + self.insert_syndromes(logical_qubit_index) + + def _handle_x(self, instruction: CircuitInstruction) -> None: + """Handle X instruction.""" + logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) + physical_data_register = self.physical_data_registers[logical_qubit_index] + # Explanation: In Shor's 9-qubit code, the logical states |0>_L and |1>_L are eigenstates of + # transversal X and Z with eigenvalues swapped relative to single physical qubits. + # Applying a physical Z to all 9 qubits transforms |0>_L to |1>_L (and vice versa), which acts + # as a logical X gate. Therefore, to implement logical X, we apply physical Z transversally. + for physical_qubit_index in range(9): + self.transpiled_qc.z(physical_data_register[physical_qubit_index]) + self.insert_syndromes(logical_qubit_index) + + def _handle_z(self, instruction: CircuitInstruction) -> None: + """Handle Z instruction.""" + logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) + physical_data_register = self.physical_data_registers[logical_qubit_index] + # Explanation: Conversely, applying a physical X to all 9 qubits imparts a -1 phase to |1>_L + # while leaving |0>_L unchanged. This acts as a logical Z gate. Therefore, to implement + # logical Z, we apply physical X transversally. + for physical_qubit_index in range(9): + self.transpiled_qc.x(physical_data_register[physical_qubit_index]) + self.insert_syndromes(logical_qubit_index) + + def _handle_s(self, instruction: CircuitInstruction) -> None: + """Handle S instruction.""" + # Explanation: A purely transversal physical S gate destroys the superposition in Shor's 9-qubit code + # and is not fault-tolerant. Universal fault-tolerance requires non-transversal techniques such as + # magic state injection to properly implement the logical phase gate (S). + msg = "Logical S gate is not fault-tolerant transversally in Shor's code and requires magic state injection." + raise NotImplementedError(msg) + + def _handle_cx(self, instruction: CircuitInstruction) -> None: + """Handle CX instruction.""" + control_logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) + target_logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[1]) + control_physical_data_register = self.physical_data_registers[control_logical_qubit_index] + target_physical_data_register = self.physical_data_registers[target_logical_qubit_index] + for physical_qubit_index in range(9): + self.transpiled_qc.cx( + control_physical_data_register[physical_qubit_index], + target_physical_data_register[physical_qubit_index], + ) + self.insert_syndromes(control_logical_qubit_index) + self.insert_syndromes(target_logical_qubit_index) +# it should use the hadamards with cnots + def _handle_cz(self, instruction: CircuitInstruction) -> None: + """Handle CZ instruction.""" + control_logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) + target_logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[1]) + control_physical_data_register = self.physical_data_registers[control_logical_qubit_index] + target_physical_data_register = self.physical_data_registers[target_logical_qubit_index] + for physical_qubit_index in range(9): + self.transpiled_qc.cz( + control_physical_data_register[physical_qubit_index], + target_physical_data_register[physical_qubit_index], + ) + self.insert_syndromes(control_logical_qubit_index) + self.insert_syndromes(target_logical_qubit_index) +#TODO: Review and verify it works + def insert_syndromes(self, logical_qubit_index: int) -> None: + """Automate the insertion of the measurement and correction cycles.""" + physical_data_register = self.physical_data_registers[logical_qubit_index] + bit_flip_syndrome_register = self.bit_flip_syndromes[logical_qubit_index] + phase_flip_syndrome_register = self.phase_flip_syndromes[logical_qubit_index] + bit_flip_measurement_register = self.bit_flip_syndrome_measurements[logical_qubit_index] + phase_flip_measurement_register = self.phase_flip_syndrome_measurements[logical_qubit_index] + + self.transpiled_qc.barrier() + + # Bit-flip syndrome extraction + self.transpiled_qc.compose( + _get_three_qubit_bit_flip_syndrome_extraction_circuit(), + qubits=physical_data_register[:3] + bit_flip_syndrome_register[:2], + inplace=True, + ) + self.transpiled_qc.compose( + _get_three_qubit_bit_flip_syndrome_extraction_circuit(), + qubits=physical_data_register[3:6] + bit_flip_syndrome_register[2:4], + inplace=True, + ) + self.transpiled_qc.compose( + _get_three_qubit_bit_flip_syndrome_extraction_circuit(), + qubits=physical_data_register[6:9] + bit_flip_syndrome_register[4:6], + inplace=True, + ) + + self.transpiled_qc.barrier() + + # Phase-flip syndrome extraction + self.transpiled_qc.compose( + _get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit(), + qubits=physical_data_register[:] + phase_flip_syndrome_register[:], + inplace=True, + ) + + self.transpiled_qc.barrier() + + # Error correction + _apply_nine_qubit_shors_code_bit_flip_correction( + self.transpiled_qc, + physical_data_register, + bit_flip_syndrome_register, + bit_flip_measurement_register, + ) + self.transpiled_qc.barrier() + _apply_nine_qubit_shors_code_phase_flip_correction( + self.transpiled_qc, + physical_data_register, + phase_flip_syndrome_register, + phase_flip_measurement_register, + ) + self.transpiled_qc.barrier() diff --git a/tests/test_shor_transpiler.py b/tests/test_shor_transpiler.py new file mode 100644 index 000000000..dc33856dc --- /dev/null +++ b/tests/test_shor_transpiler.py @@ -0,0 +1,58 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Tests for Shor Transpiler.""" + +from __future__ import annotations + +import pytest +from qiskit import QuantumCircuit + +from mqt.bench.error_correction.shor_transpiler import ShorTranspiler + +# this needs mpre tests +def test_shor_transpiler() -> None: + """Test that ShorTranspiler successfully transpiles a basic circuit.""" + qc = QuantumCircuit(2, 1) + qc.x(0) + qc.z(1) + qc.measure(1, 0) + + print("\n--- Logical Circuit ---") + print(qc.draw(fold=-1)) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + print("\n--- Transpiled Circuit ---") + print(transpiled_qc) + + # 2 original qubits * 9 data qubits = 18 data qubits + # 2 original qubits * 8 ancilla qubits = 16 ancilla qubits + # Total qubits = 34 + assert transpiled_qc.num_qubits == 34 + + # 2 original qubits * 8 classical bits = 16 syndrome bits + # 1 measurement * 9 bits = 9 measurement bits + # Total clbits = 25 + assert transpiled_qc.num_clbits == 25 + ##wrong expectations in the test it should check only non error correction gate mapping + ops = [inst.operation.name for inst in transpiled_qc.data] + assert "h" in ops + assert "cx" in ops + assert "measure" in ops + + +def test_shor_transpiler_unsupported_gate() -> None: + """Test that unsupported gates raise NotImplementedError.""" + qc = QuantumCircuit(1) + qc.t(0) + + transpiler = ShorTranspiler(qc) + with pytest.raises(NotImplementedError, match=r"Gate t is not supported by ShorTranspiler\."): + transpiler.transpile() From 6e08c7e8cd72c03ddf1e617e8f3cf1aab06ea772 Mon Sep 17 00:00:00 2001 From: "LAPTOP-N4TQ80AC\\khusa" Date: Fri, 22 May 2026 19:48:21 +0200 Subject: [PATCH 06/72] =?UTF-8?q?=E2=9C=A8=20Adding=20Steane=20for=20Cliff?= =?UTF-8?q?ord=20set?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../error_correction/steane_transpiler.py | 278 ++++++++++++++++++ tests/test_steane_transpiler.py | 66 +++++ 2 files changed, 344 insertions(+) create mode 100644 src/mqt/bench/error_correction/steane_transpiler.py create mode 100644 tests/test_steane_transpiler.py diff --git a/src/mqt/bench/error_correction/steane_transpiler.py b/src/mqt/bench/error_correction/steane_transpiler.py new file mode 100644 index 000000000..ef08a9b22 --- /dev/null +++ b/src/mqt/bench/error_correction/steane_transpiler.py @@ -0,0 +1,278 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Steane Transpiler for converting standard circuits into fault-tolerant circuits using the 7-qubit Steane code.""" + +from __future__ import annotations + +from typing import TYPE_CHECKING + +from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister +from qiskit.circuit import AncillaRegister + +# ruff: noqa: PLC2701 +# these functions are reused from the benchmark and they should be extendable i.e. they shouldn't be private +# TODO ask Patrick how to solve this issue, e.g. need to move functionality from benchmarks into dedicated helper class +from mqt.bench.benchmarks.seven_qubit_steane_code import ( + _get_seven_qubit_steane_code_encoding_circuit, + _get_seven_qubit_steane_code_decoding_circuit, + _get_seven_qubit_steane_code_syndrome_extraction_circuit, + _apply_seven_qubit_steane_code_correction +) + +from mqt.bench.benchmarks.shors_nine_qubit_code import ( + _apply_nine_qubit_shors_code_bit_flip_correction, + _apply_nine_qubit_shors_code_phase_flip_correction, + _get_three_qubit_bit_flip_encoding_decoding_circuit, + _get_three_qubit_bit_flip_syndrome_extraction_circuit, + _get_three_qubit_phase_flip_encoding_circuit, + _get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit +) + +if TYPE_CHECKING: + from qiskit.circuit import CircuitInstruction + + +class SteaneTranspiler: + """A high-level transpiler that encodes a QuantumCircuit using Steane's 7-qubit error correction code.""" + + def __init__(self, original_circuit: QuantumCircuit) -> None: + """Initialize the transpiler with the original QuantumCircuit.""" + self.original_qc = original_circuit + self.num_logical_qubits = original_circuit.num_qubits + self.physical_data_registers: list[QuantumRegister] = [] + self.bit_flip_syndromes: list[AncillaRegister] = [] + self.phase_flip_syndromes: list[AncillaRegister] = [] + self.bit_flip_syndrome_measurements: list[ClassicalRegister] = [] + self.phase_flip_syndrome_measurements: list[ClassicalRegister] = [] + self.logical_qubit_measurements: list[ClassicalRegister] = [] + self.transpiled_qc = QuantumCircuit() + + def transpile(self) -> QuantumCircuit: + """Transpile the original circuit to a fault-tolerant circuit using Steane's code.""" + self.encode_qubits() + self.replace_gates() + return self.transpiled_qc + + def encode_qubits(self) -> None: + """Replace each logical qubit with a 7-qubit physical register and apply Steane encoding.""" + all_registers = [] + for logical_qubit_index in range(self.num_logical_qubits): + #use another name as logical_qubit + physical_data_register = QuantumRegister(7, f"q{logical_qubit_index}") + bit_flip_syndrome_register = AncillaRegister(3, f"bs{logical_qubit_index}") + phase_flip_syndrome_register = AncillaRegister(3, f"ps{logical_qubit_index}") + bit_flip_measurement_register = ClassicalRegister(3, f"bsm{logical_qubit_index}") + phase_flip_measurement_register = ClassicalRegister(3, f"psm{logical_qubit_index}") + logical_qubit_measurement_register = ClassicalRegister(1, f"m{logical_qubit_index}") + # t Gate? + + self.physical_data_registers.append(physical_data_register) + self.bit_flip_syndromes.append(bit_flip_syndrome_register) + self.phase_flip_syndromes.append(phase_flip_syndrome_register) + self.bit_flip_syndrome_measurements.append(bit_flip_measurement_register) + self.phase_flip_syndrome_measurements.append(phase_flip_measurement_register) + self.logical_qubit_measurements.append(logical_qubit_measurement_register) + + all_registers.extend([ + physical_data_register, + bit_flip_syndrome_register, + phase_flip_syndrome_register, + bit_flip_measurement_register, + phase_flip_measurement_register, + logical_qubit_measurement_register + ]) + + self.transpiled_qc = QuantumCircuit(*all_registers) + self.transpiled_qc.name = f"{self.original_qc.name}_steane_encoded" + + # Apply encoding for each logical qubit + for logical_qubit_index in range(self.num_logical_qubits): + physical_data_register = self.physical_data_registers[logical_qubit_index] + + # Phase flip encoding on the first qubit of each block + self.transpiled_qc.compose( + _get_seven_qubit_steane_code_encoding_circuit(), + qubits=physical_data_register[:], + inplace=True, + ) + self.transpiled_qc.barrier(label="Encoding") + + def replace_gates(self) -> None: + """Scan original circuit and replace gates with logical equivalents.""" + gate_handlers = { + "barrier": self._handle_barrier, + "measure": self._handle_measure, + "h": self._handle_h, + "x": self._handle_x, + "z": self._handle_z, + "s": self._handle_s, + "cx": self._handle_cx, + "cz": self._handle_cz, + "t": self._handle_t + } + + for instruction in self.original_qc.data: + gate_name = instruction.operation.name + if gate_name in gate_handlers: + gate_handlers[gate_name](instruction) + else: + msg = f"Gate {gate_name} is not supported by ShorTranspiler." + raise NotImplementedError(msg) + + def _handle_barrier(self, instruction: CircuitInstruction) -> None: + """Handle barrier instruction.""" + logical_instruction_qubits = instruction.qubits + involved_physical_data_registers = [ + self.physical_data_registers[self.original_qc.qubits.index(logical_qubit)] + for logical_qubit in logical_instruction_qubits + ] + flattened_physical_qubits = [ + physical_qubit + for physical_data_register in involved_physical_data_registers + for physical_qubit in physical_data_register + ] + if flattened_physical_qubits: + self.transpiled_qc.barrier(flattened_physical_qubits) + else: + self.transpiled_qc.barrier() + + def _handle_measure(self, instruction: CircuitInstruction) -> None: + """Handle measure instruction.""" + # TODO: improve measurement, so that one could measure several qubits + + logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) + logical_classical_bit_index = self.original_qc.clbits.index(instruction.clbits[0]) + + self.transpiled_qc.compose(_get_seven_qubit_steane_code_encoding_circuit(), + qubits=self.physical_data_registers[logical_qubit_index], + inplace=True + ) + + self.transpiled_qc.measure(self.physical_data_registers[logical_qubit_index][0], self.logical_qubit_measurements[logical_classical_bit_index]) + + self.transpiled_qc.barrier(label=f"Measurement {logical_qubit_index}") + + + def _handle_h(self, instruction: CircuitInstruction) -> None: + """Handle Hadamard instruction.""" + logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) + physical_data_register = self.physical_data_registers[logical_qubit_index] + for physical_qubit_index in range(7): + self.transpiled_qc.h(physical_data_register[physical_qubit_index]) + + self.transpiled_qc.barrier(label=f"H {logical_qubit_index}") + self.insert_syndromes(logical_qubit_index) + + + def _handle_x(self, instruction: CircuitInstruction) -> None: + """Handle X instruction.""" + logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) + physical_data_register = self.physical_data_registers[logical_qubit_index] + for physical_qubit_index in range(7): + self.transpiled_qc.x(physical_data_register[physical_qubit_index]) + + self.transpiled_qc.barrier(label=f"X {logical_qubit_index}") + self.insert_syndromes(logical_qubit_index) + + def _handle_z(self, instruction: CircuitInstruction) -> None: + """Handle Z instruction.""" + logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) + physical_data_register = self.physical_data_registers[logical_qubit_index] + for physical_qubit_index in range(7): + self.transpiled_qc.z(physical_data_register[physical_qubit_index]) + + self.transpiled_qc.barrier(label=f"Z {logical_qubit_index}") + self.insert_syndromes(logical_qubit_index) + + def _handle_s(self, instruction: CircuitInstruction) -> None: + """Handle S instruction.""" + #S Made cia SDG + logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) + physical_data_register = self.physical_data_registers[logical_qubit_index] + for physical_qubit_index in range(7): + self.transpiled_qc.sdg(physical_data_register[physical_qubit_index]) + + self.transpiled_qc.barrier(label=f"S {logical_qubit_index}") + self.insert_syndromes(logical_qubit_index) + + def _handle_t(self, instruction: CircuitInstruction) -> None: + """Handle T instruction.""" + # Explanation: A purely transversal physical S gate destroys the superposition in Shor's 9-qubit code + # and is not fault-tolerant. Universal fault-tolerance requires non-transversal techniques such as + # magic state injection to properly implement the logical phase gate (S). + msg = "Logical S gate is not fault-tolerant transversally in Shor's code and requires magic state injection." + raise NotImplementedError(msg) + + def _handle_cx(self, instruction: CircuitInstruction) -> None: + """Handle CX instruction.""" + control_logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) + target_logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[1]) + control_physical_data_register = self.physical_data_registers[control_logical_qubit_index] + target_physical_data_register = self.physical_data_registers[target_logical_qubit_index] + for physical_qubit_index in range(7): + self.transpiled_qc.cx( + control_physical_data_register[physical_qubit_index], + target_physical_data_register[physical_qubit_index], + ) + + self.transpiled_qc.barrier(label=f"CX {control_logical_qubit_index} {target_logical_qubit_index}") + + self.insert_syndromes(control_logical_qubit_index) + self.insert_syndromes(target_logical_qubit_index) + + # it should use the hadamards with cnots + def _handle_cz(self, instruction: CircuitInstruction) -> None: + """Handle CZ instruction.""" + control_logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) + target_logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[1]) + control_physical_data_register = self.physical_data_registers[control_logical_qubit_index] + target_physical_data_register = self.physical_data_registers[target_logical_qubit_index] + for physical_qubit_index in range(7): + self.transpiled_qc.cz( + control_physical_data_register[physical_qubit_index], + target_physical_data_register[physical_qubit_index], + ) + + self.transpiled_qc.barrier(label=f"CZ {control_logical_qubit_index} {target_logical_qubit_index}") + + self.insert_syndromes(control_logical_qubit_index) + self.insert_syndromes(target_logical_qubit_index) + + # TODO: Review and verify it works + def insert_syndromes(self, logical_qubit_index: int) -> None: + """Automate the insertion of the measurement and correction cycles.""" + physical_data_register = self.physical_data_registers[logical_qubit_index] + bit_flip_syndrome_register = self.bit_flip_syndromes[logical_qubit_index] + phase_flip_syndrome_register = self.phase_flip_syndromes[logical_qubit_index] + bit_flip_measurement_register = self.bit_flip_syndrome_measurements[logical_qubit_index] + phase_flip_measurement_register = self.phase_flip_syndrome_measurements[logical_qubit_index] + + self.transpiled_qc.barrier(label="Syndrom Start") + + # Syndrome extraction + self.transpiled_qc.compose( + _get_seven_qubit_steane_code_syndrome_extraction_circuit(), + qubits=physical_data_register[:] + bit_flip_syndrome_register[:] + phase_flip_syndrome_register[:], + inplace=True, + + ) + + self.transpiled_qc.barrier() + + + # Error correction + _apply_seven_qubit_steane_code_correction( + self.transpiled_qc, + physical_data_register, + bit_flip_syndrome_register, + phase_flip_syndrome_register, + bit_flip_measurement_register, + phase_flip_measurement_register + ) + self.transpiled_qc.barrier(label="Correction End") diff --git a/tests/test_steane_transpiler.py b/tests/test_steane_transpiler.py new file mode 100644 index 000000000..8ea108146 --- /dev/null +++ b/tests/test_steane_transpiler.py @@ -0,0 +1,66 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Tests for Shor Transpiler.""" + +from __future__ import annotations + +#import pytest +from qiskit import QuantumCircuit +import matplotlib.pyplot as plt + +from mqt.bench.error_correction.steane_transpiler import SteaneTranspiler + +# this needs mpre tests +def test_shor_transpiler() -> None: + """Test that ShorTranspiler successfully transpiles a basic circuit.""" + qc = QuantumCircuit(2, 1) + qc.h(0) + qc.x(1) + qc.cx(0, 1) + qc.cz(0, 1) + qc.measure(0, 0) + + print("\n--- Logical Circuit ---") + print(qc.draw(fold=-1)) + + transpiler = SteaneTranspiler(qc) + transpiled_qc = transpiler.transpile() + transpiled_qc.draw("mpl", fold=-1) + #plt.show() + plt.savefig("circuit.png", dpi=300, bbox_inches="tight") + + #print("\n--- Transpiled Circuit ---") + #print(transpiled_qc) + + + + # 2 original qubits * 9 data qubits = 18 data qubits + # 2 original qubits * 8 ancilla qubits = 16 ancilla qubits + # Total qubits = 34 + #assert transpiled_qc.num_qubits == 34 + + # 2 original qubits * 8 classical bits = 16 syndrome bits + # 1 measurement * 9 bits = 9 measurement bits + # Total clbits = 25 + #assert transpiled_qc.num_clbits == 25 + ##wrong expectations in the test it should check only non error correction gate mapping + ops = [inst.operation.name for inst in transpiled_qc.data] + #assert "h" in ops + #assert "cx" in ops + #assert "measure" in ops + + +def test_shor_transpiler_unsupported_gate() -> None: + """Test that unsupported gates raise NotImplementedError.""" + qc = QuantumCircuit(1) + qc.t(0) + + transpiler = SteaneTranspiler(qc) + with pytest.raises(NotImplementedError, match=r"Gate t is not supported by ShorTranspiler\."): + transpiler.transpile() From ac7b9ea6b23aae669ecbd75745a0e7ce6771f1e1 Mon Sep 17 00:00:00 2001 From: "LAPTOP-N4TQ80AC\\khusa" Date: Sat, 23 May 2026 10:58:02 +0200 Subject: [PATCH 07/72] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactoring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../error_correction/steane_transpiler.py | 89 +++++++++---------- tests/test_steane_transpiler.py | 16 +--- 2 files changed, 43 insertions(+), 62 deletions(-) diff --git a/src/mqt/bench/error_correction/steane_transpiler.py b/src/mqt/bench/error_correction/steane_transpiler.py index ef08a9b22..c9540fff3 100644 --- a/src/mqt/bench/error_correction/steane_transpiler.py +++ b/src/mqt/bench/error_correction/steane_transpiler.py @@ -13,7 +13,7 @@ from typing import TYPE_CHECKING from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister -from qiskit.circuit import AncillaRegister +from qiskit.circuit import AncillaRegister, barrier # ruff: noqa: PLC2701 # these functions are reused from the benchmark and they should be extendable i.e. they shouldn't be private @@ -127,55 +127,48 @@ def replace_gates(self) -> None: def _handle_barrier(self, instruction: CircuitInstruction) -> None: """Handle barrier instruction.""" - logical_instruction_qubits = instruction.qubits - involved_physical_data_registers = [ - self.physical_data_registers[self.original_qc.qubits.index(logical_qubit)] - for logical_qubit in logical_instruction_qubits - ] - flattened_physical_qubits = [ - physical_qubit - for physical_data_register in involved_physical_data_registers - for physical_qubit in physical_data_register - ] - if flattened_physical_qubits: - self.transpiled_qc.barrier(flattened_physical_qubits) - else: - self.transpiled_qc.barrier() + barrier_register = [] + for i in range(len(instruction.qubits)): + physical_data_register = self.physical_data_registers[i] + bit_flip_syndromes_register = self.bit_flip_syndromes[i] + phase_flip_syndromes_register = self.phase_flip_syndromes[i] + barrier_register.extend([physical_data_register, bit_flip_syndromes_register, phase_flip_syndromes_register]) + self.transpiled_qc.barrier( *barrier_register,label=f"Barrier") def _handle_measure(self, instruction: CircuitInstruction) -> None: """Handle measure instruction.""" - # TODO: improve measurement, so that one could measure several qubits + # TODO: consider measure_all(), because of new meas register everything goes wrong - logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) - logical_classical_bit_index = self.original_qc.clbits.index(instruction.clbits[0]) + for i in range(len(instruction.qubits)): + logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[i]) + logical_classical_bit_index = self.original_qc.clbits.index(instruction.clbits[i]) - self.transpiled_qc.compose(_get_seven_qubit_steane_code_encoding_circuit(), - qubits=self.physical_data_registers[logical_qubit_index], - inplace=True - ) + self.transpiled_qc.compose(_get_seven_qubit_steane_code_decoding_circuit(), + qubits=self.physical_data_registers[logical_qubit_index], + inplace=True + ) - self.transpiled_qc.measure(self.physical_data_registers[logical_qubit_index][0], self.logical_qubit_measurements[logical_classical_bit_index]) + self.transpiled_qc.measure(self.physical_data_registers[logical_qubit_index][0], + self.logical_qubit_measurements[logical_classical_bit_index]) self.transpiled_qc.barrier(label=f"Measurement {logical_qubit_index}") - def _handle_h(self, instruction: CircuitInstruction) -> None: """Handle Hadamard instruction.""" logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) physical_data_register = self.physical_data_registers[logical_qubit_index] - for physical_qubit_index in range(7): - self.transpiled_qc.h(physical_data_register[physical_qubit_index]) + + self.transpiled_qc.h(physical_data_register) self.transpiled_qc.barrier(label=f"H {logical_qubit_index}") self.insert_syndromes(logical_qubit_index) - def _handle_x(self, instruction: CircuitInstruction) -> None: """Handle X instruction.""" logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) physical_data_register = self.physical_data_registers[logical_qubit_index] - for physical_qubit_index in range(7): - self.transpiled_qc.x(physical_data_register[physical_qubit_index]) + + self.transpiled_qc.x(physical_data_register) self.transpiled_qc.barrier(label=f"X {logical_qubit_index}") self.insert_syndromes(logical_qubit_index) @@ -184,8 +177,8 @@ def _handle_z(self, instruction: CircuitInstruction) -> None: """Handle Z instruction.""" logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) physical_data_register = self.physical_data_registers[logical_qubit_index] - for physical_qubit_index in range(7): - self.transpiled_qc.z(physical_data_register[physical_qubit_index]) + + self.transpiled_qc.z(physical_data_register) self.transpiled_qc.barrier(label=f"Z {logical_qubit_index}") self.insert_syndromes(logical_qubit_index) @@ -195,19 +188,17 @@ def _handle_s(self, instruction: CircuitInstruction) -> None: #S Made cia SDG logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) physical_data_register = self.physical_data_registers[logical_qubit_index] - for physical_qubit_index in range(7): - self.transpiled_qc.sdg(physical_data_register[physical_qubit_index]) + + self.transpiled_qc.sdg(physical_data_register) self.transpiled_qc.barrier(label=f"S {logical_qubit_index}") self.insert_syndromes(logical_qubit_index) def _handle_t(self, instruction: CircuitInstruction) -> None: """Handle T instruction.""" - # Explanation: A purely transversal physical S gate destroys the superposition in Shor's 9-qubit code - # and is not fault-tolerant. Universal fault-tolerance requires non-transversal techniques such as - # magic state injection to properly implement the logical phase gate (S). - msg = "Logical S gate is not fault-tolerant transversally in Shor's code and requires magic state injection." - raise NotImplementedError(msg) + logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) + physical_data_register = self.physical_data_registers[logical_qubit_index] + def _handle_cx(self, instruction: CircuitInstruction) -> None: """Handle CX instruction.""" @@ -215,29 +206,29 @@ def _handle_cx(self, instruction: CircuitInstruction) -> None: target_logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[1]) control_physical_data_register = self.physical_data_registers[control_logical_qubit_index] target_physical_data_register = self.physical_data_registers[target_logical_qubit_index] - for physical_qubit_index in range(7): - self.transpiled_qc.cx( - control_physical_data_register[physical_qubit_index], - target_physical_data_register[physical_qubit_index], - ) + + self.transpiled_qc.cx( + control_physical_data_register, + target_physical_data_register, + ) self.transpiled_qc.barrier(label=f"CX {control_logical_qubit_index} {target_logical_qubit_index}") self.insert_syndromes(control_logical_qubit_index) self.insert_syndromes(target_logical_qubit_index) - # it should use the hadamards with cnots + # it сould use the hadamards with cnots def _handle_cz(self, instruction: CircuitInstruction) -> None: """Handle CZ instruction.""" control_logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) target_logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[1]) control_physical_data_register = self.physical_data_registers[control_logical_qubit_index] target_physical_data_register = self.physical_data_registers[target_logical_qubit_index] - for physical_qubit_index in range(7): - self.transpiled_qc.cz( - control_physical_data_register[physical_qubit_index], - target_physical_data_register[physical_qubit_index], - ) + + self.transpiled_qc.cz( + control_physical_data_register, + target_physical_data_register, + ) self.transpiled_qc.barrier(label=f"CZ {control_logical_qubit_index} {target_logical_qubit_index}") diff --git a/tests/test_steane_transpiler.py b/tests/test_steane_transpiler.py index 8ea108146..fa3228907 100644 --- a/tests/test_steane_transpiler.py +++ b/tests/test_steane_transpiler.py @@ -11,7 +11,7 @@ from __future__ import annotations #import pytest -from qiskit import QuantumCircuit +from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister import matplotlib.pyplot as plt from mqt.bench.error_correction.steane_transpiler import SteaneTranspiler @@ -19,12 +19,10 @@ # this needs mpre tests def test_shor_transpiler() -> None: """Test that ShorTranspiler successfully transpiles a basic circuit.""" - qc = QuantumCircuit(2, 1) + qc = QuantumCircuit(QuantumRegister(1), QuantumRegister(1), ClassicalRegister(2)) qc.h(0) - qc.x(1) qc.cx(0, 1) - qc.cz(0, 1) - qc.measure(0, 0) + qc.measure([0,1],[0,1]) print("\n--- Logical Circuit ---") print(qc.draw(fold=-1)) @@ -56,11 +54,3 @@ def test_shor_transpiler() -> None: #assert "measure" in ops -def test_shor_transpiler_unsupported_gate() -> None: - """Test that unsupported gates raise NotImplementedError.""" - qc = QuantumCircuit(1) - qc.t(0) - - transpiler = SteaneTranspiler(qc) - with pytest.raises(NotImplementedError, match=r"Gate t is not supported by ShorTranspiler\."): - transpiler.transpile() From b0e5c8ce4710916ec6f02bcd11f25e0aacf97f78 Mon Sep 17 00:00:00 2001 From: salehalsherif Date: Sat, 23 May 2026 13:03:13 +0200 Subject: [PATCH 08/72] Shor_Code_Transpiler completed with tests anf T gate to be tested --- .gitignore | 2 + main.py | 7 + .../bench/error_correction/shor_transpiler.py | 449 +++++++++++------- tests/test_shor_equivalence.py | 145 ++++++ tests/test_shor_transpiler.py | 250 +++++++++- 5 files changed, 668 insertions(+), 185 deletions(-) create mode 100644 tests/test_shor_equivalence.py diff --git a/.gitignore b/.gitignore index 671e137ea..e8e131fbf 100644 --- a/.gitignore +++ b/.gitignore @@ -302,3 +302,5 @@ pyrightconfig.json # setuptools_scm src/**/_version.py .idea/ +tests/circuit_drawings + diff --git a/main.py b/main.py index b59887d57..027ed8f7c 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,10 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License import mqt.bench.benchmark_generation as benchmark_generation diff --git a/src/mqt/bench/error_correction/shor_transpiler.py b/src/mqt/bench/error_correction/shor_transpiler.py index bcf2ba419..216de13a0 100644 --- a/src/mqt/bench/error_correction/shor_transpiler.py +++ b/src/mqt/bench/error_correction/shor_transpiler.py @@ -10,12 +10,15 @@ from __future__ import annotations -from typing import TYPE_CHECKING +import numpy as np +from dataclasses import dataclass +from typing import TYPE_CHECKING, Callable from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister from qiskit.circuit import AncillaRegister # ruff: noqa: PLC2701 +#ignore the below comment # these functions are reused from the benchmark and they should be extendable i.e. they shouldn't be private from mqt.bench.benchmarks.shors_nine_qubit_code import ( _apply_nine_qubit_shors_code_bit_flip_correction, @@ -29,20 +32,50 @@ if TYPE_CHECKING: from qiskit.circuit import CircuitInstruction +# Constants for the Shor 9-qubit code structure +SHOR_TOTAL_QUBITS = 9 +SHOR_BLOCK_SIZE = 3 +SHOR_NUM_BLOCKS = 3 +SHOR_PHASE_FLIP_TARGETS = [0, 3, 6] + + +@dataclass +class ShorLogicalQubit: + """Encapsulates the physical registers representing a single Shor logical qubit.""" + data: QuantumRegister + bit_flip_syndrome: AncillaRegister | None = None + phase_flip_syndrome: AncillaRegister | None = None + bit_flip_measure: ClassicalRegister | None = None + phase_flip_measure: ClassicalRegister | None = None + + def get_all_registers(self) -> list: + """Return all active registers for this logical qubit.""" + regs = [self.data] + if self.bit_flip_syndrome: + regs.extend([ + self.bit_flip_syndrome, + self.phase_flip_syndrome, + self.bit_flip_measure, + self.phase_flip_measure, + ]) + return regs + class ShorTranspiler: """A high-level transpiler that encodes a QuantumCircuit using Shor's 9-qubit error correction code.""" - def __init__(self, original_circuit: QuantumCircuit) -> None: + def __init__(self, original_circuit: QuantumCircuit, add_syndromes: bool = True) -> None: """Initialize the transpiler with the original QuantumCircuit.""" self.original_qc = original_circuit self.num_logical_qubits = original_circuit.num_qubits - self.physical_data_registers: list[QuantumRegister] = [] - self.bit_flip_syndromes: list[AncillaRegister] = [] - self.phase_flip_syndromes: list[AncillaRegister] = [] - self.bit_flip_syndrome_measurements: list[ClassicalRegister] = [] - self.phase_flip_syndrome_measurements: list[ClassicalRegister] = [] + self.add_syndromes = add_syndromes + self.logical_qubits: list[ShorLogicalQubit] = [] + self.s_gate_count = 0 + self.t_gate_count = 0 self.transpiled_qc = QuantumCircuit() + + # We need this for backwards compatibility with the testing suite + self.physical_data_registers: list[QuantumRegister] = [] def transpile(self) -> QuantumCircuit: """Transpile the original circuit to a fault-tolerant circuit using Shor's code.""" @@ -53,86 +86,99 @@ def transpile(self) -> QuantumCircuit: def encode_qubits(self) -> None: """Replace each logical qubit with a 9-qubit physical register and apply Shor encoding.""" all_registers = [] - for logical_qubit_index in range(self.num_logical_qubits): - physical_data_register = QuantumRegister(9, f"q{logical_qubit_index}") - bit_flip_syndrome_register = AncillaRegister(6, f"bs{logical_qubit_index}") - phase_flip_syndrome_register = AncillaRegister(2, f"ps{logical_qubit_index}") - bit_flip_measurement_register = ClassicalRegister(6, f"bsm{logical_qubit_index}") - phase_flip_measurement_register = ClassicalRegister(2, f"psm{logical_qubit_index}") - - self.physical_data_registers.append(physical_data_register) - self.bit_flip_syndromes.append(bit_flip_syndrome_register) - self.phase_flip_syndromes.append(phase_flip_syndrome_register) - self.bit_flip_syndrome_measurements.append(bit_flip_measurement_register) - self.phase_flip_syndrome_measurements.append(phase_flip_measurement_register) - - all_registers.extend([ - physical_data_register, - bit_flip_syndrome_register, - phase_flip_syndrome_register, - bit_flip_measurement_register, - phase_flip_measurement_register, - ]) + for i in range(self.num_logical_qubits): + data_reg = QuantumRegister(SHOR_TOTAL_QUBITS, f"q{i}") + self.physical_data_registers.append(data_reg) + + if self.add_syndromes: + logical_qubit = ShorLogicalQubit( + data=data_reg, + bit_flip_syndrome=AncillaRegister(6, f"bs{i}"), + phase_flip_syndrome=AncillaRegister(2, f"ps{i}"), + bit_flip_measure=ClassicalRegister(6, f"bsm{i}"), + phase_flip_measure=ClassicalRegister(2, f"psm{i}") + ) + else: + logical_qubit = ShorLogicalQubit(data=data_reg) + + self.logical_qubits.append(logical_qubit) + all_registers.extend(logical_qubit.get_all_registers()) self.transpiled_qc = QuantumCircuit(*all_registers) self.transpiled_qc.name = f"{self.original_qc.name}_shor_encoded" # Apply encoding for each logical qubit - for logical_qubit_index in range(self.num_logical_qubits): - physical_data_register = self.physical_data_registers[logical_qubit_index] - - # Phase flip encoding on the first qubit of each block - self.transpiled_qc.compose( - _get_three_qubit_phase_flip_encoding_circuit(), - qubits=[physical_data_register[0], physical_data_register[3], physical_data_register[6]], - inplace=True, - ) + for logical_qubit in self.logical_qubits: + self._apply_shor_encoding(self.transpiled_qc, logical_qubit.data) + self.transpiled_qc.barrier() + + def decode_qubits(self) -> None: + """Apply Shor 9-qubit decoding to each logical qubit.""" + for logical_qubit in self.logical_qubits: + self._apply_shor_decoding(self.transpiled_qc, logical_qubit.data) + self.transpiled_qc.barrier() + + @staticmethod + def _apply_shor_encoding(qc: QuantumCircuit, physical_data_register: QuantumRegister) -> None: + """Apply Shor 9-qubit encoding to a physical data register.""" + # Phase flip encoding on the first qubit of each block + qc.compose( + _get_three_qubit_phase_flip_encoding_circuit(), + qubits=[physical_data_register[i] for i in SHOR_PHASE_FLIP_TARGETS], + inplace=True, + ) - # Bit flip encoding on each block - self.transpiled_qc.compose( + # Bit flip encoding on each block + for i in range(SHOR_NUM_BLOCKS): + qc.compose( _get_three_qubit_bit_flip_encoding_decoding_circuit(), - qubits=physical_data_register[:3], + qubits=physical_data_register[i * SHOR_BLOCK_SIZE : (i + 1) * SHOR_BLOCK_SIZE], inplace=True, ) - self.transpiled_qc.compose( - _get_three_qubit_bit_flip_encoding_decoding_circuit(), - qubits=physical_data_register[3:6], - inplace=True, - ) - self.transpiled_qc.compose( - _get_three_qubit_bit_flip_encoding_decoding_circuit(), - qubits=physical_data_register[6:9], + + @staticmethod + def _apply_shor_decoding(qc: QuantumCircuit, physical_data_register: QuantumRegister) -> None: + """Apply Shor 9-qubit decoding to a physical data register.""" + for i in range(SHOR_NUM_BLOCKS): + qc.compose( + _get_three_qubit_bit_flip_encoding_decoding_circuit().inverse(), + qubits=physical_data_register[i * SHOR_BLOCK_SIZE : (i + 1) * SHOR_BLOCK_SIZE], inplace=True, ) - self.transpiled_qc.barrier() + qc.compose( + _get_three_qubit_phase_flip_encoding_circuit().inverse(), + qubits=[physical_data_register[i] for i in SHOR_PHASE_FLIP_TARGETS], + inplace=True, + ) def replace_gates(self) -> None: """Scan original circuit and replace gates with logical equivalents.""" - gate_handlers = { - "barrier": self._handle_barrier, - "measure": self._handle_measure, - "h": self._handle_h, - "x": self._handle_x, - "z": self._handle_z, - "s": self._handle_s, - "cx": self._handle_cx, - "cz": self._handle_cz, - } - for instruction in self.original_qc.data: gate_name = instruction.operation.name - if gate_name in gate_handlers: - gate_handlers[gate_name](instruction) - else: + handler_name = f"_logical_{gate_name}" + + if not hasattr(self, handler_name): msg = f"Gate {gate_name} is not supported by ShorTranspiler." raise NotImplementedError(msg) + + handler = getattr(self, handler_name) + logical_qubit_indices = [self.original_qc.qubits.index(q) for q in instruction.qubits] + logical_clbit_indices = [self.original_qc.clbits.index(c) for c in instruction.clbits] + + if gate_name == "barrier": + handler(logical_qubit_indices) + elif gate_name == "measure": + handler(logical_qubit_indices[0], logical_clbit_indices[0]) + elif gate_name in ["cx", "cz"]: + handler(logical_qubit_indices[0], logical_qubit_indices[1]) + else: + handler(logical_qubit_indices[0]) - def _handle_barrier(self, instruction: CircuitInstruction) -> None: - """Handle barrier instruction.""" - logical_instruction_qubits = instruction.qubits + def _logical_barrier(self, logical_qubit_indices: list[int]) -> None: + """Apply logical barrier across the specified physical qubits.""" involved_physical_data_registers = [ - self.physical_data_registers[self.original_qc.qubits.index(logical_qubit)] - for logical_qubit in logical_instruction_qubits + self.logical_qubits[idx].data + for idx in logical_qubit_indices ] flattened_physical_qubits = [ physical_qubit @@ -144,41 +190,27 @@ def _handle_barrier(self, instruction: CircuitInstruction) -> None: else: self.transpiled_qc.barrier() - def _handle_measure(self, instruction: CircuitInstruction) -> None: - """Handle measure instruction.""" - logical_instruction_qubits = instruction.qubits - logical_instruction_clbits = instruction.clbits - logical_qubit_index = self.original_qc.qubits.index(logical_instruction_qubits[0]) - logical_classical_bit_index = self.original_qc.clbits.index(logical_instruction_clbits[0]) + def _logical_measure(self, logical_qubit_index: int, logical_classical_bit_index: int) -> None: + """Apply logical measurement mapping to 9 physical measurements. + + Classical post-processing would compute the majority vote across the 3 bit-flip + blocks and then across the phase-flip blocks to extract the logical value. + """ measurement_register_name = f"meas_{logical_qubit_index}_{logical_classical_bit_index}" - physical_measurement_register = ClassicalRegister(9, measurement_register_name) + physical_measurement_register = ClassicalRegister(SHOR_TOTAL_QUBITS, measurement_register_name) self.transpiled_qc.add_register(physical_measurement_register) - physical_data_register = self.physical_data_registers[logical_qubit_index] - - # --- Editable and Well-Commented Majority Vote Section --- - # NOTE: In a physical quantum computer, you would measure the 9 physical qubits - # into 9 classical bits. Classical post-processing would then compute the - # majority vote across the 3 bit-flip blocks and then across the phase-flip blocks. - # Here, we perform the 9 physical measurements. + physical_data_register = self.logical_qubits[logical_qubit_index].data self.transpiled_qc.measure(physical_data_register, physical_measurement_register) + + def _logical_h(self, logical_qubit_index: int) -> None: + """Apply logical Hadamard. - # A classical post-processing function for majority vote could look like: - # - # def classical_majority_vote(bitstring: str) -> str: - # '''Computes the logical bit value from the 9 physical bit measurements.''' - # # bitstring is 9 bits: b8 b7 b6 b5 b4 b3 b2 b1 b0 - # blocks = [bitstring[6:9], bitstring[3:6], bitstring[0:3]] - # block_votes = [1 if block.count('1') > 1 else 0 for block in blocks] - # logical_bit = 1 if sum(block_votes) > 1 else 0 - # return str(logical_bit) - # --------------------------------------------------------- - - def _handle_h(self, instruction: CircuitInstruction) -> None: - """Handle Hadamard instruction.""" - logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) - physical_data_register = self.physical_data_registers[logical_qubit_index] - for physical_qubit_index in range(9): + The Hadamard gate is not completely transversal for Shor's code. It requires + applying physical H gates followed by SWAPs that transpose the 9-qubit blocks. + """ + physical_data_register = self.logical_qubits[logical_qubit_index].data + for physical_qubit_index in range(SHOR_TOTAL_QUBITS): self.transpiled_qc.h(physical_data_register[physical_qubit_index]) # The Hadamard gate is not completely transversal for Shor's code. # It needs to be followed by a swap that transposes the 9 qubits. @@ -187,115 +219,166 @@ def _handle_h(self, instruction: CircuitInstruction) -> None: self.transpiled_qc.swap(physical_data_register[5], physical_data_register[7]) self.insert_syndromes(logical_qubit_index) - def _handle_x(self, instruction: CircuitInstruction) -> None: - """Handle X instruction.""" - logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) - physical_data_register = self.physical_data_registers[logical_qubit_index] - # Explanation: In Shor's 9-qubit code, the logical states |0>_L and |1>_L are eigenstates of - # transversal X and Z with eigenvalues swapped relative to single physical qubits. - # Applying a physical Z to all 9 qubits transforms |0>_L to |1>_L (and vice versa), which acts - # as a logical X gate. Therefore, to implement logical X, we apply physical Z transversally. - for physical_qubit_index in range(9): - self.transpiled_qc.z(physical_data_register[physical_qubit_index]) + def _logical_x(self, logical_qubit_index: int) -> None: + """Apply Transversal logical X. + + In Shor's code, a logical X acts like a global physical Z across the three + blocks. Since Z on one qubit of a block flips the entire block's phase, + applying one Z per block (Z_0 Z_3 Z_6) transversally achieves logical X. + """ + physical_data_register = self.logical_qubits[logical_qubit_index].data + for q in (physical_data_register[i] for i in SHOR_PHASE_FLIP_TARGETS): + self.transpiled_qc.z(q) self.insert_syndromes(logical_qubit_index) - def _handle_z(self, instruction: CircuitInstruction) -> None: - """Handle Z instruction.""" - logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) - physical_data_register = self.physical_data_registers[logical_qubit_index] - # Explanation: Conversely, applying a physical X to all 9 qubits imparts a -1 phase to |1>_L - # while leaving |0>_L unchanged. This acts as a logical Z gate. Therefore, to implement - # logical Z, we apply physical X transversally. - for physical_qubit_index in range(9): - self.transpiled_qc.x(physical_data_register[physical_qubit_index]) + def _logical_z(self, logical_qubit_index: int) -> None: + """Apply Transversal logical Z. + + Applying X to the three qubits of a single block (e.g. X_0 X_1 X_2) maps + |000> to |111>, effectively giving diag(+1,-1) on the logical subspace. + """ + physical_data_register = self.logical_qubits[logical_qubit_index].data + for q in (physical_data_register[0], physical_data_register[1], physical_data_register[2]): + self.transpiled_qc.x(q) + self.insert_syndromes(logical_qubit_index) + + def _apply_teleportation_gadget(self, logical_qubit_index: int, phase: float, ancilla_name: str, measure_name: str, correction_callback: Callable) -> None: + """Apply a magic state gate teleportation gadget (used for non-transversal S and T gates).""" + ancilla_register = QuantumRegister(SHOR_TOTAL_QUBITS, ancilla_name) + creg = ClassicalRegister(1, measure_name) + self.transpiled_qc.add_register(ancilla_register) + self.transpiled_qc.add_register(creg) + + physical_data_register = self.logical_qubits[logical_qubit_index].data + + # Prepare magic state: H -> P(phase) -> Encode + self._prepare_magic(self.transpiled_qc, ancilla_register, phase) + + # Transversal logical CNOT + self._apply_logical_cx(physical_data_register,ancilla_register) + + # Decode and measure ancilla in logical Z basis + self._apply_shor_decoding(self.transpiled_qc, ancilla_register) + self.transpiled_qc.measure(ancilla_register[0], creg[0]) + + # Apply conditional correction based on the measurement outcome + with self.transpiled_qc.if_test((creg[0], 1)): + correction_callback() + self.insert_syndromes(logical_qubit_index) - def _handle_s(self, instruction: CircuitInstruction) -> None: - """Handle S instruction.""" - # Explanation: A purely transversal physical S gate destroys the superposition in Shor's 9-qubit code - # and is not fault-tolerant. Universal fault-tolerance requires non-transversal techniques such as - # magic state injection to properly implement the logical phase gate (S). - msg = "Logical S gate is not fault-tolerant transversally in Shor's code and requires magic state injection." - raise NotImplementedError(msg) - - def _handle_cx(self, instruction: CircuitInstruction) -> None: - """Handle CX instruction.""" - control_logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) - target_logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[1]) - control_physical_data_register = self.physical_data_registers[control_logical_qubit_index] - target_physical_data_register = self.physical_data_registers[target_logical_qubit_index] - for physical_qubit_index in range(9): + def _logical_s(self, logical_qubit_index: int) -> None: + """Apply logical S via |Y>-state teleportation. Correction: logical Z.""" + self.s_gate_count += 1 + def z_correction() -> None: + self._logical_z(logical_qubit_index) + + self._apply_teleportation_gadget( + logical_qubit_index=logical_qubit_index, + phase=np.pi / 2, + ancilla_name=f"ms{self.s_gate_count - 1}", + measure_name=f"tmeas{self.s_gate_count - 1}", + correction_callback=z_correction + ) + + def _logical_t(self, logical_qubit_index: int) -> None: + """Apply logical T via |A>-state teleportation. Correction: logical S.""" + self.t_gate_count += 1 + + def s_correction() -> None: + self._logical_s(logical_qubit_index) + + self._apply_teleportation_gadget( + logical_qubit_index=logical_qubit_index, + phase=np.pi / 4, + ancilla_name=f"anc_t_{self.t_gate_count}", + measure_name=f"creg_t_{self.t_gate_count}", + correction_callback=s_correction + ) + + @staticmethod + def _prepare_magic(qc: QuantumCircuit, physical_ancilla_register: QuantumRegister, phase: float) -> None: + """Encode a magic state (|0> + e^{i*phase}|1>)/sqrt2 into a physical register.""" + qc.h(physical_ancilla_register[0]) + qc.p(phase, physical_ancilla_register[0]) + ShorTranspiler._apply_shor_encoding(qc, physical_ancilla_register) + + def _apply_logical_cx(self, control_register: QuantumRegister, target_register: QuantumRegister) -> None: + """Apply transversal logical CX between two physical registers.""" + for physical_qubit_index in range(SHOR_TOTAL_QUBITS): self.transpiled_qc.cx( - control_physical_data_register[physical_qubit_index], - target_physical_data_register[physical_qubit_index], - ) - self.insert_syndromes(control_logical_qubit_index) - self.insert_syndromes(target_logical_qubit_index) -# it should use the hadamards with cnots - def _handle_cz(self, instruction: CircuitInstruction) -> None: - """Handle CZ instruction.""" - control_logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) - target_logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[1]) - control_physical_data_register = self.physical_data_registers[control_logical_qubit_index] - target_physical_data_register = self.physical_data_registers[target_logical_qubit_index] - for physical_qubit_index in range(9): - self.transpiled_qc.cz( - control_physical_data_register[physical_qubit_index], - target_physical_data_register[physical_qubit_index], + target_register[physical_qubit_index], + control_register[physical_qubit_index] ) + + def _logical_cx(self, control_logical_qubit_index: int, target_logical_qubit_index: int) -> None: + """Apply transversal logical CX. + + Because the Shor logical operators X_L and Z_L have interchanged physical basis mapping + compared to typical codes, the physical CX role is inverted: control and target are + swapped at the physical level to construct a logical CX. + """ + control_physical_data_register = self.logical_qubits[control_logical_qubit_index].data + target_physical_data_register = self.logical_qubits[target_logical_qubit_index].data + self._apply_logical_cx(control_physical_data_register, target_physical_data_register) + self.insert_syndromes(control_logical_qubit_index) self.insert_syndromes(target_logical_qubit_index) -#TODO: Review and verify it works - def insert_syndromes(self, logical_qubit_index: int) -> None: - """Automate the insertion of the measurement and correction cycles.""" - physical_data_register = self.physical_data_registers[logical_qubit_index] - bit_flip_syndrome_register = self.bit_flip_syndromes[logical_qubit_index] - phase_flip_syndrome_register = self.phase_flip_syndromes[logical_qubit_index] - bit_flip_measurement_register = self.bit_flip_syndrome_measurements[logical_qubit_index] - phase_flip_measurement_register = self.phase_flip_syndrome_measurements[logical_qubit_index] + def _logical_cz(self, control_logical_qubit_index: int, target_logical_qubit_index: int) -> None: + """Apply logical CZ (implemented as H-CX-H).""" + self._logical_h(target_logical_qubit_index) + self.transpiled_qc.barrier() + self._logical_cx(control_logical_qubit_index,target_logical_qubit_index) self.transpiled_qc.barrier() + self._logical_h(target_logical_qubit_index) - # Bit-flip syndrome extraction - self.transpiled_qc.compose( - _get_three_qubit_bit_flip_syndrome_extraction_circuit(), - qubits=physical_data_register[:3] + bit_flip_syndrome_register[:2], - inplace=True, - ) - self.transpiled_qc.compose( - _get_three_qubit_bit_flip_syndrome_extraction_circuit(), - qubits=physical_data_register[3:6] + bit_flip_syndrome_register[2:4], - inplace=True, - ) - self.transpiled_qc.compose( - _get_three_qubit_bit_flip_syndrome_extraction_circuit(), - qubits=physical_data_register[6:9] + bit_flip_syndrome_register[4:6], - inplace=True, - ) + def insert_syndromes(self, logical_qubit_index: int) -> None: + """Automate the insertion of bit-flip and phase-flip error correction cycles.""" + if not self.add_syndromes: + return + + qubit = self.logical_qubits[logical_qubit_index] + self.transpiled_qc.barrier() + self._extract_bit_flip_syndromes(qubit) + self.transpiled_qc.barrier() + + self._extract_phase_flip_syndromes(qubit) + self.transpiled_qc.barrier() + + self._apply_error_corrections(qubit) self.transpiled_qc.barrier() - # Phase-flip syndrome extraction + def _extract_bit_flip_syndromes(self, qubit: ShorLogicalQubit) -> None: + """Extract bit-flip syndromes for the three blocks.""" + for i in range(SHOR_NUM_BLOCKS): + self.transpiled_qc.compose( + _get_three_qubit_bit_flip_syndrome_extraction_circuit(), + qubits=qubit.data[i * SHOR_BLOCK_SIZE : (i + 1) * SHOR_BLOCK_SIZE] + qubit.bit_flip_syndrome[i * 2 : (i + 1) * 2], + inplace=True, + ) + + def _extract_phase_flip_syndromes(self, qubit: ShorLogicalQubit) -> None: + """Extract phase-flip syndromes across the blocks.""" self.transpiled_qc.compose( _get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit(), - qubits=physical_data_register[:] + phase_flip_syndrome_register[:], + qubits=qubit.data[:] + qubit.phase_flip_syndrome[:], inplace=True, ) - self.transpiled_qc.barrier() - - # Error correction + def _apply_error_corrections(self, qubit: ShorLogicalQubit) -> None: + """Apply bit-flip and phase-flip error corrections based on syndromes.""" _apply_nine_qubit_shors_code_bit_flip_correction( self.transpiled_qc, - physical_data_register, - bit_flip_syndrome_register, - bit_flip_measurement_register, + qubit.data, + qubit.bit_flip_syndrome, + qubit.bit_flip_measure, ) self.transpiled_qc.barrier() _apply_nine_qubit_shors_code_phase_flip_correction( self.transpiled_qc, - physical_data_register, - phase_flip_syndrome_register, - phase_flip_measurement_register, + qubit.data, + qubit.phase_flip_syndrome, + qubit.phase_flip_measure, ) - self.transpiled_qc.barrier() diff --git a/tests/test_shor_equivalence.py b/tests/test_shor_equivalence.py new file mode 100644 index 000000000..478306aae --- /dev/null +++ b/tests/test_shor_equivalence.py @@ -0,0 +1,145 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Equivalence tests for Shor Transpiler gates.""" + +from __future__ import annotations + +import numpy as np +import pytest +from pathlib import Path +from qiskit import QuantumCircuit +from qiskit.quantum_info import state_fidelity +from qiskit_aer import AerSimulator + +from mqt.bench.error_correction.shor_transpiler import ShorTranspiler + + +def verify_gate_equivalence(gate_name: str, num_qubits: int) -> None: + """Verify that a transpiled gate is mathematically equivalent to the logical gate. + + Args: + gate_name: The name of the gate to test ('h', 'x', 'z', 's', 't', 'cx', 'cz'). + num_qubits: The number of qubits the gate acts on. + """ + # Create the logical circuit and initialize it in a non-trivial state (|+> state) + qc_logical = QuantumCircuit(num_qubits) + # Apply the gate + if gate_name == "h": + qc_logical.h(0) + elif gate_name == "x": + qc_logical.x(0) + elif gate_name == "z": + qc_logical.z(0) + elif gate_name == "s": + qc_logical.s(0) + elif gate_name == "t": + qc_logical.t(0) + elif gate_name == "cx": + qc_logical.cx(0, 1) + elif gate_name == "cz": + qc_logical.cz(0, 1) + else: + msg = f"Unknown gate {gate_name}" + raise ValueError(msg) + + # Get the expected density matrix + qc_logical_sim = qc_logical.copy() + qc_logical_sim.save_density_matrix() + sim = AerSimulator(method="statevector") + result_logical = sim.run(qc_logical_sim).result() + expected_rho = result_logical.data()["density_matrix"] + + # Transpile the circuit + # We set add_syndromes=False to prevent statevector simulation from blowing up in memory + transpiler = ShorTranspiler(qc_logical, add_syndromes=False) + transpiled_qc = transpiler.transpile() + + drawing = transpiled_qc.draw(fold=-1) + print(f"\n--- Transpiled Circuit for {gate_name.upper()} ---") + print(drawing) + + # Save to file to ensure it can be viewed regardless of pytest-xdist capturing stdout + output_dir = Path("tests/circuit_drawings") + output_dir.mkdir(parents=True, exist_ok=True) + with open(output_dir / f"{gate_name}_transpiled.txt", "w", encoding="utf-8") as f: + f.write(f"number of qubits {num_qubits}\n") + f.write(f"--- Transpiled Circuit for {gate_name.upper()} ---\n\n") + f.write(str(drawing) + "\n") + # Apply decoding so the logical state collapses back to the first physical qubit of each block + transpiler.decode_qubits() + + + # Extract the density matrix of the physical qubits holding the logical state + # These are the 0-th qubits of each physical data register + logical_qubits_physical = [transpiler.physical_data_registers[i][0] for i in range(num_qubits)] + + transpiled_qc.save_density_matrix(logical_qubits_physical, label="rho_transpiled") + + result_transpiled = sim.run(transpiled_qc).result() + assert result_transpiled.success, f"Simulation failed: {result_transpiled.status}" + + actual_rho = result_transpiled.data()["rho_transpiled"] + + # Compare the density matrices + fidelity = state_fidelity(expected_rho, actual_rho) + + # Convert to statevector for visual inspection (since the states are pure) + expected_sv = expected_rho.to_statevector() + actual_sv = actual_rho.to_statevector() + + # Save the resulting density matrices and state vectors to the text file for visual inspection + with open(output_dir / f"{gate_name}_transpiled.txt", "a", encoding="utf-8") as f: + f.write("\n\n=== LOGICAL EXPECTED DENSITY MATRIX ===\n") + f.write(str(np.round(expected_rho.data, 3)) + "\n") + f.write("\n=== ACTUAL TRANSPILED DENSITY MATRIX (AFTER DECODING) ===\n") + f.write(str(np.round(actual_rho.data, 3)) + "\n") + + f.write("\n\n=== LOGICAL EXPECTED STATE VECTOR ===\n") + f.write(str(np.round(expected_sv.data, 3)) + "\n") + f.write("\n=== ACTUAL TRANSPILED STATE VECTOR (AFTER DECODING) ===\n") + f.write(str(np.round(actual_sv.data, 3)) + "\n") + + f.write(f"\nSTATE FIDELITY: {fidelity:.6f}\n") + + assert fidelity > 0.999, f"Fidelity too low: {fidelity}" + + +def test_h_equivalence() -> None: + """Test equivalence for logical H gate.""" + verify_gate_equivalence("h", 1) + + +def test_x_equivalence() -> None: + """Test equivalence for logical X gate.""" + verify_gate_equivalence("x", 1) + + +def test_z_equivalence() -> None: + """Test equivalence for logical Z gate.""" + verify_gate_equivalence("z", 1) + + +def test_s_equivalence() -> None: + """Test equivalence for logical S gate.""" + verify_gate_equivalence("s", 1) + + +@pytest.mark.skip(reason="Slow test, takes ~1-2 mins due to 27 qubit simulation") +def test_t_equivalence() -> None: + """Test equivalence for logical T gate.""" + verify_gate_equivalence("t", 1) + + +def test_cx_equivalence() -> None: + """Test equivalence for logical CX gate.""" + verify_gate_equivalence("cx", 2) + +def test_cz_equivalence() -> None: + """Test equivalence for logical CZ gate.""" + verify_gate_equivalence("cz", 2) diff --git a/tests/test_shor_transpiler.py b/tests/test_shor_transpiler.py index dc33856dc..482c3f686 100644 --- a/tests/test_shor_transpiler.py +++ b/tests/test_shor_transpiler.py @@ -51,8 +51,254 @@ def test_shor_transpiler() -> None: def test_shor_transpiler_unsupported_gate() -> None: """Test that unsupported gates raise NotImplementedError.""" qc = QuantumCircuit(1) - qc.t(0) + qc.rx(0, 0) transpiler = ShorTranspiler(qc) - with pytest.raises(NotImplementedError, match=r"Gate t is not supported by ShorTranspiler\."): + with pytest.raises(NotImplementedError, match=r"Gate rx is not supported by ShorTranspiler\."): transpiler.transpile() + + +def test_shor_transpiler_s_gate_structure() -> None: + """Test that the S gate teleportation circuit has the correct structure. + + Verifies that: + - A magic state ancilla register (9 qubits) is allocated. + - The teleportation measurement register (1 classical bit) is allocated. + - The circuit contains the expected gates (h, s, cx, measure). + """ + qc = QuantumCircuit(1) + qc.s(0) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + # Original: 9 data + 6 bit-flip ancilla + 2 phase-flip ancilla = 17 + # S gate adds: 9 magic data = 9 + # Total = 26 + assert transpiled_qc.num_qubits == 26 + + # Original: 6 bf meas + 2 pf meas = 8 + # S gate adds: 1 teleport meas = 1 + # Total = 9 + assert transpiled_qc.num_clbits == 9 + + ops = [inst.operation.name for inst in transpiled_qc.data] + + # Magic state prep uses h and p on the ancilla qubit + assert "p" in ops + assert "h" in ops + + # Teleportation uses cx, measure + assert "cx" in ops + assert "measure" in ops + + # Conditional correction uses if_else + assert "if_else" in ops + + # Verify the magic state register exists + reg_names = [reg.name for reg in transpiled_qc.qregs] + assert "ms0" in reg_names + + # Verify teleportation measurement register exists + creg_names = [reg.name for reg in transpiled_qc.cregs] + assert "tmeas0" in creg_names + + +def test_shor_transpiler_s_gate_followed_by_other_gates() -> None: + """Test that gates applied after the S gate target the correct register. + + After S gate teleportation, subsequent gates should operate on the original + data register since the teleportation gadget doesn't swap the pointers. + """ + qc = QuantumCircuit(1, 1) + qc.s(0) + qc.z(0) + qc.measure(0, 0) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + # Circuit should compile and run without errors + assert transpiled_qc.num_qubits > 0 + assert transpiled_qc.num_clbits > 0 + + ops = [inst.operation.name for inst in transpiled_qc.data] + assert "p" in ops # Magic state prep (phase) + assert "x" in ops # Logical Z uses physical X + assert "measure" in ops + + +def test_shor_transpiler_multiple_s_gates() -> None: + """Test that multiple S gates each allocate independent ancilla blocks. + + Two consecutive S gates should produce two independent magic state + ancilla blocks (ms0 and ms1). + """ + qc = QuantumCircuit(1) + qc.s(0) + qc.s(0) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + reg_names = [reg.name for reg in transpiled_qc.qregs] + assert "ms0" in reg_names + assert "ms1" in reg_names + + +def test_shor_transpiler_t_gate_structure() -> None: + """Test that the T gate teleportation circuit has the correct structure. + + Verifies that: + - A magic state ancilla register for T (9 qubits) is allocated. + - The teleportation measurement register for T (1 classical bit) is allocated. + - Because of the S correction, another S ancilla and measurement are also allocated conditionally. + """ + qc = QuantumCircuit(1) + qc.t(0) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + # Original: 17 qubits + # T gate adds: 9 magic data for T + 9 magic data for S = 18 + # Total = 35 + assert transpiled_qc.num_qubits == 35 + + # Original: 8 clbits + # T gate adds: 1 for T + 1 for S = 2 + # Total = 10 + assert transpiled_qc.num_clbits == 10 + + ops = [inst.operation.name for inst in transpiled_qc.data] + + # Magic state prep uses p and h + assert "p" in ops + assert "h" in ops + + # Verify the T-magic state register exists + reg_names = [reg.name for reg in transpiled_qc.qregs] + assert "anc_t_1" in reg_names + assert "ms0" in reg_names # from the S correction + + +def test_shor_transpiler_barrier() -> None: + """Test logical barrier translates to physical barrier on all involved qubits.""" + qc = QuantumCircuit(2) + qc.barrier(0) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + ops = [inst.operation.name for inst in transpiled_qc.data] + assert "barrier" in ops + + +def test_shor_transpiler_measure() -> None: + """Test logical measure maps to 9 physical measurements.""" + qc = QuantumCircuit(1, 1) + qc.measure(0, 0) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + measure_count = sum(1 for inst in transpiled_qc.data if inst.operation.name == "measure") + # At least 9 physical measurements for the single logical measurement + assert measure_count >= 9 + + +def test_shor_transpiler_h_gate() -> None: + """Test logical H gate.""" + qc = QuantumCircuit(1) + qc.h(0) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + ops = [inst.operation.name for inst in transpiled_qc.data] + assert "h" in ops + assert "swap" in ops + + +def test_shor_transpiler_x_gate() -> None: + """Test logical X gate uses Z transversally.""" + qc = QuantumCircuit(1) + qc.x(0) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + ops = [inst.operation.name for inst in transpiled_qc.data] + # Shor code logical X = Z_0 Z_3 Z_6 + assert "z" in ops + + +def test_shor_transpiler_z_gate() -> None: + """Test logical Z gate uses X transversally.""" + qc = QuantumCircuit(1) + qc.z(0) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + ops = [inst.operation.name for inst in transpiled_qc.data] + # Shor code logical Z = X_0 X_1 X_2 + assert "x" in ops + + +def test_shor_transpiler_cx_gate() -> None: + """Test logical CX gate.""" + qc = QuantumCircuit(2) + qc.cx(0, 1) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + ops = [inst.operation.name for inst in transpiled_qc.data] + assert "cx" in ops + + +def test_shor_transpiler_cz_gate() -> None: + """Test logical CZ gate.""" + qc = QuantumCircuit(2) + qc.cz(0, 1) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + ops = [inst.operation.name for inst in transpiled_qc.data] + # CZ is implemented via H, CX, H + assert "h" in ops + assert "cx" in ops + assert "swap" in ops + + +def test_shor_transpiler_encode_decode() -> None: + """Test static encoding and decoding methods directly.""" + from qiskit import QuantumRegister + qc = QuantumCircuit() + reg = QuantumRegister(9, "q") + qc.add_register(reg) + + ShorTranspiler._apply_shor_encoding(qc, reg) + ops_enc = [inst.operation.name for inst in qc.data] + assert len(ops_enc) > 0 + + ShorTranspiler._apply_shor_decoding(qc, reg) + ops_dec = [inst.operation.name for inst in qc.data] + assert len(ops_dec) > len(ops_enc) + + +def test_shor_transpiler_prepare_magic() -> None: + """Test _prepare_magic directly.""" + from qiskit import QuantumRegister + import numpy as np + + qc = QuantumCircuit() + anc = QuantumRegister(9, "anc") + qc.add_register(anc) + + ShorTranspiler._prepare_magic(qc, anc, np.pi/2) + ops = [inst.operation.name for inst in qc.data] + assert "h" in ops + assert "p" in ops From 3e211cb8cc570f702abbcd3b6568d644f36cb9e0 Mon Sep 17 00:00:00 2001 From: "LAPTOP-N4TQ80AC\\khusa" Date: Sat, 23 May 2026 13:45:49 +0200 Subject: [PATCH 09/72] change to statevector --- tests/test_shor_equivalence.py | 34 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 22 deletions(-) diff --git a/tests/test_shor_equivalence.py b/tests/test_shor_equivalence.py index 478306aae..10e22a2bb 100644 --- a/tests/test_shor_equivalence.py +++ b/tests/test_shor_equivalence.py @@ -14,7 +14,7 @@ import pytest from pathlib import Path from qiskit import QuantumCircuit -from qiskit.quantum_info import state_fidelity +from qiskit.quantum_info import state_fidelity, Statevector from qiskit_aer import AerSimulator from mqt.bench.error_correction.shor_transpiler import ShorTranspiler @@ -50,10 +50,8 @@ def verify_gate_equivalence(gate_name: str, num_qubits: int) -> None: # Get the expected density matrix qc_logical_sim = qc_logical.copy() - qc_logical_sim.save_density_matrix() - sim = AerSimulator(method="statevector") - result_logical = sim.run(qc_logical_sim).result() - expected_rho = result_logical.data()["density_matrix"] + # expected logical state + expected_sv_init = Statevector.from_instruction(qc_logical) # Transpile the circuit # We set add_syndromes=False to prevent statevector simulation from blowing up in memory @@ -79,35 +77,27 @@ def verify_gate_equivalence(gate_name: str, num_qubits: int) -> None: # These are the 0-th qubits of each physical data register logical_qubits_physical = [transpiler.physical_data_registers[i][0] for i in range(num_qubits)] - transpiled_qc.save_density_matrix(logical_qubits_physical, label="rho_transpiled") + expected_sv_transpiled = Statevector.from_instruction(transpiled_qc) - result_transpiled = sim.run(transpiled_qc).result() - assert result_transpiled.success, f"Simulation failed: {result_transpiled.status}" - - actual_rho = result_transpiled.data()["rho_transpiled"] # Compare the density matrices - fidelity = state_fidelity(expected_rho, actual_rho) + #fidelity = state_fidelity(expected_rho, actual_rho) # Convert to statevector for visual inspection (since the states are pure) - expected_sv = expected_rho.to_statevector() - actual_sv = actual_rho.to_statevector() + #expected_sv = expected_rho.to_statevector() + #actual_sv = actual_rho.to_statevector() # Save the resulting density matrices and state vectors to the text file for visual inspection with open(output_dir / f"{gate_name}_transpiled.txt", "a", encoding="utf-8") as f: - f.write("\n\n=== LOGICAL EXPECTED DENSITY MATRIX ===\n") - f.write(str(np.round(expected_rho.data, 3)) + "\n") - f.write("\n=== ACTUAL TRANSPILED DENSITY MATRIX (AFTER DECODING) ===\n") - f.write(str(np.round(actual_rho.data, 3)) + "\n") f.write("\n\n=== LOGICAL EXPECTED STATE VECTOR ===\n") - f.write(str(np.round(expected_sv.data, 3)) + "\n") + f.write(str(np.round(expected_sv_init, 3)) + "\n") f.write("\n=== ACTUAL TRANSPILED STATE VECTOR (AFTER DECODING) ===\n") - f.write(str(np.round(actual_sv.data, 3)) + "\n") + f.write(str(np.round(expected_sv_transpiled, 3)) + "\n") - f.write(f"\nSTATE FIDELITY: {fidelity:.6f}\n") + #f.write(f"\nSTATE FIDELITY: {fidelity:.6f}\n") - assert fidelity > 0.999, f"Fidelity too low: {fidelity}" + #assert fidelity > 0.999, f"Fidelity too low: {fidelity}" def test_h_equivalence() -> None: @@ -130,7 +120,7 @@ def test_s_equivalence() -> None: verify_gate_equivalence("s", 1) -@pytest.mark.skip(reason="Slow test, takes ~1-2 mins due to 27 qubit simulation") +#@pytest.mark.skip(reason="Slow test, takes ~1-2 mins due to 27 qubit simulation") def test_t_equivalence() -> None: """Test equivalence for logical T gate.""" verify_gate_equivalence("t", 1) From 1fa57c3d6dc8a411bec04bba8d1daf126f9188f8 Mon Sep 17 00:00:00 2001 From: salehalsherif Date: Sat, 23 May 2026 14:02:29 +0200 Subject: [PATCH 10/72] used reduced density matrix --- scratch.py | 22 ++++++++++++++ tests/test_shor_equivalence.py | 53 ++++++++++++++++++++++++---------- 2 files changed, 59 insertions(+), 16 deletions(-) create mode 100644 scratch.py diff --git a/scratch.py b/scratch.py new file mode 100644 index 000000000..7bfa6994f --- /dev/null +++ b/scratch.py @@ -0,0 +1,22 @@ +import qiskit +from qiskit import QuantumCircuit +from qiskit_aer import AerSimulator +from qiskit.quantum_info import partial_trace + +qc = QuantumCircuit(2, 1) +qc.h(0) +qc.cx(0, 1) +qc.measure(0, 0) +with qc.if_test((qc.clbits[0], 1)): + qc.x(1) +qc.save_statevector() + +sim = AerSimulator(method="statevector") +result = sim.run(qc).result() +sv = result.get_statevector() +print("Statevector:") +print(sv) + +rho = partial_trace(sv, [0]) +print("Partial trace (rho):") +print(rho) diff --git a/tests/test_shor_equivalence.py b/tests/test_shor_equivalence.py index 10e22a2bb..052d25937 100644 --- a/tests/test_shor_equivalence.py +++ b/tests/test_shor_equivalence.py @@ -72,32 +72,53 @@ def verify_gate_equivalence(gate_name: str, num_qubits: int) -> None: # Apply decoding so the logical state collapses back to the first physical qubit of each block transpiler.decode_qubits() + transpiled_qc.save_statevector() + + sim = AerSimulator(method="statevector") + result_transpiled = sim.run(transpiled_qc).result() + assert result_transpiled.success, f"Simulation failed: {result_transpiled.status}" + actual_sv = result_transpiled.get_statevector() # Extract the density matrix of the physical qubits holding the logical state # These are the 0-th qubits of each physical data register - logical_qubits_physical = [transpiler.physical_data_registers[i][0] for i in range(num_qubits)] + logical_qubits_physical = [ + transpiled_qc.find_bit(transpiler.physical_data_registers[i][0]).index + for i in range(num_qubits) + ] + + from qiskit.quantum_info import partial_trace + all_qubits = list(range(transpiled_qc.num_qubits)) + trace_qubits = [q for q in all_qubits if q not in logical_qubits_physical] + + actual_rho = partial_trace(actual_sv, trace_qubits) - expected_sv_transpiled = Statevector.from_instruction(transpiled_qc) + from qiskit.quantum_info import DensityMatrix + expected_rho_init = DensityMatrix(expected_sv_init) + try: + actual_sv_reduced = actual_rho.to_statevector() + except Exception: + actual_sv_reduced = None # Compare the density matrices - #fidelity = state_fidelity(expected_rho, actual_rho) - - # Convert to statevector for visual inspection (since the states are pure) - #expected_sv = expected_rho.to_statevector() - #actual_sv = actual_rho.to_statevector() + fidelity = state_fidelity(expected_sv_init, actual_rho) # Save the resulting density matrices and state vectors to the text file for visual inspection with open(output_dir / f"{gate_name}_transpiled.txt", "a", encoding="utf-8") as f: - - f.write("\n\n=== LOGICAL EXPECTED STATE VECTOR ===\n") - f.write(str(np.round(expected_sv_init, 3)) + "\n") - f.write("\n=== ACTUAL TRANSPILED STATE VECTOR (AFTER DECODING) ===\n") - f.write(str(np.round(expected_sv_transpiled, 3)) + "\n") - - #f.write(f"\nSTATE FIDELITY: {fidelity:.6f}\n") + f.write("\n\n=== LOGICAL EXPECTED DENSITY MATRIX ===\n") + f.write(str(np.round(expected_rho_init.data, 3)) + "\n") + f.write("\n=== ACTUAL TRANSPILED DENSITY MATRIX (REDUCED) ===\n") + f.write(str(np.round(actual_rho.data, 3)) + "\n") + + if actual_sv_reduced is not None: + f.write("\n\n=== LOGICAL EXPECTED STATE VECTOR ===\n") + f.write(str(np.round(expected_sv_init.data, 3)) + "\n") + f.write("\n=== ACTUAL TRANSPILED STATE VECTOR (REDUCED) ===\n") + f.write(str(np.round(actual_sv_reduced.data, 3)) + "\n") + + f.write(f"\nSTATE FIDELITY: {fidelity:.6f}\n") - #assert fidelity > 0.999, f"Fidelity too low: {fidelity}" + assert fidelity > 0.999, f"Fidelity too low: {fidelity}" def test_h_equivalence() -> None: @@ -120,7 +141,7 @@ def test_s_equivalence() -> None: verify_gate_equivalence("s", 1) -#@pytest.mark.skip(reason="Slow test, takes ~1-2 mins due to 27 qubit simulation") +@pytest.mark.skip(reason="Slow test, takes ~1-2 mins due to 27 qubit simulation") def test_t_equivalence() -> None: """Test equivalence for logical T gate.""" verify_gate_equivalence("t", 1) From 5cf108d428cc3241d1304c21db3735f5ea022e0e Mon Sep 17 00:00:00 2001 From: "LAPTOP-N4TQ80AC\\khusa" Date: Wed, 27 May 2026 13:04:27 +0200 Subject: [PATCH 11/72] =?UTF-8?q?=E2=9C=A8=20Implementing=20T=20Gate=20as?= =?UTF-8?q?=20in=20exercise=20decription?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../error_correction/steane_transpiler.py | 48 +++++++++++++++---- tests/test_steane_transpiler.py | 10 ++-- 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/src/mqt/bench/error_correction/steane_transpiler.py b/src/mqt/bench/error_correction/steane_transpiler.py index c9540fff3..5a9c07a4a 100644 --- a/src/mqt/bench/error_correction/steane_transpiler.py +++ b/src/mqt/bench/error_correction/steane_transpiler.py @@ -25,15 +25,6 @@ _apply_seven_qubit_steane_code_correction ) -from mqt.bench.benchmarks.shors_nine_qubit_code import ( - _apply_nine_qubit_shors_code_bit_flip_correction, - _apply_nine_qubit_shors_code_phase_flip_correction, - _get_three_qubit_bit_flip_encoding_decoding_circuit, - _get_three_qubit_bit_flip_syndrome_extraction_circuit, - _get_three_qubit_phase_flip_encoding_circuit, - _get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit -) - if TYPE_CHECKING: from qiskit.circuit import CircuitInstruction @@ -199,6 +190,45 @@ def _handle_t(self, instruction: CircuitInstruction) -> None: logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) physical_data_register = self.physical_data_registers[logical_qubit_index] + t_ancilla_register = QuantumRegister(7) + t_test_register = ClassicalRegister(1) + + self.transpiled_qc.add_register(t_ancilla_register) + self.transpiled_qc.add_register(t_test_register) + + # make ket 0 L + self.transpiled_qc.compose( + _get_seven_qubit_steane_code_encoding_circuit(), + qubits=t_ancilla_register[:], + inplace=True, + ) + + # make ket + L (Applying H L) + self.transpiled_qc.h(t_ancilla_register) + + # apply physical t gates + self.transpiled_qc.t(t_ancilla_register) + + # logical cnot from data to ancilla + self.transpiled_qc.cx(physical_data_register, t_ancilla_register) + + # made logical measurement + self.transpiled_qc.compose(_get_seven_qubit_steane_code_decoding_circuit(), + qubits=t_ancilla_register, + inplace=True + ) + self.transpiled_qc.measure(t_ancilla_register[0], + t_test_register[0]) + + # Think about whther need to add error correction after these logical gates + + # apply if_test + with self.transpiled_qc.if_test((t_test_register[0], 1)): + self.transpiled_qc.sdg(physical_data_register) + + self.transpiled_qc.barrier(label=f"T {logical_qubit_index}") + self.insert_syndromes(logical_qubit_index) + def _handle_cx(self, instruction: CircuitInstruction) -> None: """Handle CX instruction.""" diff --git a/tests/test_steane_transpiler.py b/tests/test_steane_transpiler.py index fa3228907..399814605 100644 --- a/tests/test_steane_transpiler.py +++ b/tests/test_steane_transpiler.py @@ -16,13 +16,11 @@ from mqt.bench.error_correction.steane_transpiler import SteaneTranspiler -# this needs mpre tests -def test_shor_transpiler() -> None: +def test_steane_transpiler() -> None: """Test that ShorTranspiler successfully transpiles a basic circuit.""" - qc = QuantumCircuit(QuantumRegister(1), QuantumRegister(1), ClassicalRegister(2)) - qc.h(0) - qc.cx(0, 1) - qc.measure([0,1],[0,1]) + qc = QuantumCircuit(QuantumRegister(1), ClassicalRegister(1)) + qc.t(0) + #qc.measure(0,0) print("\n--- Logical Circuit ---") print(qc.draw(fold=-1)) From e9cc1594c1fe8fab5f15b548f184b3823282abff Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Fri, 29 May 2026 16:33:34 +0200 Subject: [PATCH 12/72] updated encoding parameter to make main.py run small formatting changes --- src/mqt/bench/benchmark_generation.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/mqt/bench/benchmark_generation.py b/src/mqt/bench/benchmark_generation.py index 5929ca7e1..72f7a9a1d 100644 --- a/src/mqt/bench/benchmark_generation.py +++ b/src/mqt/bench/benchmark_generation.py @@ -200,7 +200,7 @@ def get_benchmark_alg( def get_benchmark_alg( benchmark: str | QuantumCircuit, circuit_size: int | None = None, - encoding:str = "", + encoding: str = "", *, generate_mirror_circuit: bool = False, random_parameters: bool = True, @@ -224,9 +224,9 @@ def get_benchmark_alg( if generate_mirror_circuit: return _create_mirror_circuit(qc, inplace=True) - #if encoding == "shor": + # if encoding == "shor": # return generate_shor(qc) - #elif encoding == "stean": + # elif encoding == "stean": # return generate_stean(qc) return qc @@ -562,7 +562,7 @@ def get_benchmark( circuit_size=circuit_size, generate_mirror_circuit=generate_mirror_circuit, random_parameters=random_parameters, - encoding=code, + encoding=encoding, **kwargs, ) From fba7a1ff6b0e5f8f8db1093e8942b8b67511d93f Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Fri, 29 May 2026 16:34:19 +0200 Subject: [PATCH 13/72] added various testing utilities --- main.py | 198 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 194 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index b59887d57..a2307e65b 100644 --- a/main.py +++ b/main.py @@ -1,9 +1,199 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License import mqt.bench.benchmark_generation as benchmark_generation +from qiskit_aer import AerSimulator # update uv requirements? +import qiskit as qk + + +# uv requirements to be added: mqt.qcec, qiskit_aer + + +def errorcode_testing(alg: str = 'ghz', code: str = 'shor', qubits: int = 3): + assert qubits >= 3 + + base_circuit = benchmark_generation.get_benchmark( + benchmark=alg, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=qubits, encoding=code + ) + error_circuit = base_circuit.copy(name='error_circuit') + error_circuit = insert_error_gate(error_circuit) + uncorrected_circuit = benchmark_generation.get_benchmark( + benchmark=alg, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=qubits + ) + + + ### Equivalence checking + equivalent = check_equivalence(base_circuit, error_circuit) + #assert equivalent, 'Insertion of an error (flipped qubit) has lead to a new, no longer equivalent circuit' + print(f'Circuits are equivalent: {equivalent}') + + + + ### Simulated probabilistic similarity Base vs. Error-Inserted + error_fidelity = compare_distributions(base_circuit, error_circuit) + threshold = 0.95 # arbitrary guess + #assert fidelity > threshold, f'Simulated Hellinger Fidelity between base and error circuit is too low. Measured: {fidelity}, >Expected: {threshold}' + print(f'Hellinger Fidelity with error: {error_fidelity}') + + ### Simulated probabilistic similarity Uncorrected vs. Error-Inserted + # TODO: put in error corrected circuit + #### Example for condensing qubits + example = {'00000001111111': 3, '10101011111111': 1, '11111111010101': 2, '10101011111110' : 7} + print(condense_counts(example,'stean')) + + """ + uncorrected_fidelity = compare_distributions(uncorrected_circuit, error_circuit, code='shor') + threshold = threshold # arbitrary guess + #assert fidelity > threshold, f'Simulated Hellinger Fidelity between uncorrected and error circuit is too low. Measured: {uncorrected_fidelity}, Expected: >{threshold}' + print(f'Hellinger Fidelity with error: {uncorrected_fidelity}') + """ + + +def run_circuit(qc: qk.QuantumCircuit, shots: int = 1024): + """ + Simulates the circuit using AerSimulator + + Returns: + job.result() + + transpiled circuit qc + """ + simulator = AerSimulator() + transpiled_circuit = qk.transpile(qc, simulator) + job = simulator.run(transpiled_circuit, shots=shots) + + return job.result(), transpiled_circuit + +def insert_error_gate(qc: qk.QuantumCircuit) -> qk.QuantumCircuit: + """ + Flips bit 0 at the beginning of the circuit + """ + from qiskit.circuit import CircuitInstruction + from qiskit.circuit.library import XGate + + gate = XGate() + qubits = [qc.qubits[0]] + insert_index = 0 + + qc.data.insert(insert_index, CircuitInstruction(gate, qubits)) + + return qc + +def check_equivalence(qc1: qk.QuantumCircuit, qc2: qk.QuantumCircuit) -> bool: + """ + Uses MQT QCEC to verify if qc1 and qc2 are equivalent + """ + import mqt.qcec + from mqt.qcec.pyqcec import EquivalenceCriterion as EC + + verification_results = mqt.qcec.verify(qc1, qc2) + accepted_equivalencies = [ + EC.equivalent, + EC.equivalent_up_to_global_phase, + EC.probably_equivalent + ] + + equivalent = verification_results.equivalence in accepted_equivalencies + return equivalent + +def compare_distributions(base: qk.QuantumCircuit, error: qk.QuantumCircuit, shots:int = 1024, code: str = 'None') -> float: + """ + Simulates 2 circuits and computes the Hellinger Fidelity between their count distributions + 1 = the same, 0 = no overlap + + If code is set to either 'stean' or 'shor' circuit error's result will be interpreted logically + """ + from qiskit.quantum_info import hellinger_fidelity + + result1, base = run_circuit(base, shots) + result2, error = run_circuit(error, shots) + counts1 = result1.get_counts(base) + counts2 = result2.get_counts(error) + # to be removed due to decoding + #if code in ['stean', 'shor']: + # counts2 = condense_counts(counts2, code) + + fidelity = hellinger_fidelity(counts1, counts2) + return fidelity + +def parse_qubits(physical_qubits: str, code: str): + """ + Takes in a measurement in physical qubits and returns the corresponding logical measurement. + + Returns: + Logical Measurement if possible, 'x' otherwise + """ + + from textwrap import wrap + + logical_qubits = '' + logical_0 = [] + logical_1 = [] + length = 0 + if code == 'stean': + length = 7 + logical_0 = ['0000000', '1010101', '0110011', '1100110', + '0001111', '1011010', '0111100', '1101001'] + logical_1 = ['1111111', '0101010', '1001100', '0011001', + '1110000', '0100101', '1000011', '0010110'] + elif code == 'shor': + length = 9 + logical_0 = [] + logical_1 = [] + else: + raise Exception('Wrong error correction code provided to qubit condensing') + assert len(physical_qubits)%length == 0, f'Result contains wrong number of physical qubits. \nExpected: Multiple of {length}\nReceived: {len(physical_qubits)}' + + qubits = wrap(physical_qubits, length) + for qubit in qubits: + if qubit in logical_0: + logical_qubits += '0' + elif qubit in logical_1: + logical_qubits += '1' + else: + return 'x' + + return logical_qubits + +def condense_counts(counts: dict[str, int], code: str) -> dict[str, int]: + """ + Kinda unnecessariy considering decoding... + + Takes in a result dict of physical measurements and returns logical measurements according to code. + Incoherent results will be grouped under 'x' + + Supports codes 'shor' and 'stean' + + Example: Code 'stean' + + Input: {'00000001111111': 3, '10101011111111': 1, '11111111010101': 2, '10101011111110' : 7} + Output: {'01': 4, '10': 2, 'x': 7} + """ + assert code in ['shor', 'stean'], f'Unsupported error code in condense_counts() {code}' + logical_counts = {} + for physical_measurement, count in counts.items(): + logical_measurement = parse_qubits(physical_measurement, code) + logical_counts[logical_measurement] = logical_counts.get(logical_measurement, 0) + count + + return logical_counts + + if __name__ == "__main__": - for alg in ["ghz", "bv", "graphstate"]: # add QFT + for alg in ["ghz", "bv", "graphstate"]: # add QFT for code in ["shor", "stean"]: - for qubits in range(3, 5): - qc = benchmark_generation.get_benchmark(benchmark=alg, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=qubits, encoding=code) - print(qc) + # for qubits in range(3, 5): + qubits = 3 + #print(code) + qc = benchmark_generation.get_benchmark( + benchmark=alg, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=qubits, encoding=code + ) + #print(qc) + #print(" _________ ") + + errorcode_testing() From 0dec02a5c7c4730f35ff3fb0dba8bcbb46c2b798 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Fri, 29 May 2026 17:55:30 +0200 Subject: [PATCH 14/72] Added test outputs to gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 671e137ea..b1c42d28e 100644 --- a/.gitignore +++ b/.gitignore @@ -302,3 +302,6 @@ pyrightconfig.json # setuptools_scm src/**/_version.py .idea/ + +# Test Output +tests/circuit_drawings/* From 8e189f1ad8f0c262335c2d4f1be3d9ac90b17de6 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Fri, 29 May 2026 22:05:25 +0200 Subject: [PATCH 15/72] added barrier to decoding --- src/mqt/bench/error_correction/shor_transpiler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mqt/bench/error_correction/shor_transpiler.py b/src/mqt/bench/error_correction/shor_transpiler.py index 216de13a0..368f57316 100644 --- a/src/mqt/bench/error_correction/shor_transpiler.py +++ b/src/mqt/bench/error_correction/shor_transpiler.py @@ -114,6 +114,7 @@ def encode_qubits(self) -> None: def decode_qubits(self) -> None: """Apply Shor 9-qubit decoding to each logical qubit.""" + self.transpiled_qc.barrier() for logical_qubit in self.logical_qubits: self._apply_shor_decoding(self.transpiled_qc, logical_qubit.data) self.transpiled_qc.barrier() From e041878be43fa101e870e52e8aaec505dd83af4b Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Fri, 29 May 2026 22:06:44 +0200 Subject: [PATCH 16/72] first attempts at unification --- main.py | 41 +- tests/test_error_correction.py | 758 +++++++++++++++++++++++++++++++++ 2 files changed, 797 insertions(+), 2 deletions(-) create mode 100644 tests/test_error_correction.py diff --git a/main.py b/main.py index a2307e65b..2a67c20a7 100644 --- a/main.py +++ b/main.py @@ -10,6 +10,9 @@ from qiskit_aer import AerSimulator # update uv requirements? import qiskit as qk +from mqt.bench.error_correction.shor_transpiler import ShorTranspiler +from tests.test_error_correction import insert_error + # uv requirements to be added: mqt.qcec, qiskit_aer @@ -91,7 +94,8 @@ def check_equivalence(qc1: qk.QuantumCircuit, qc2: qk.QuantumCircuit) -> bool: import mqt.qcec from mqt.qcec.pyqcec import EquivalenceCriterion as EC - verification_results = mqt.qcec.verify(qc1, qc2) + verification_results = mqt.qcec.verify(qc1, qc2, + transform_dynamic_circuit=True) accepted_equivalencies = [ EC.equivalent, EC.equivalent_up_to_global_phase, @@ -196,4 +200,37 @@ def condense_counts(counts: dict[str, int], code: str) -> dict[str, int]: #print(qc) #print(" _________ ") - errorcode_testing() + #errorcode_testing() + + circuit_size = 3 + algorithm = 'ghz' + code = 'shor' + # Initialize circuits + logical_circuit = qk.QuantumCircuit(1) + logical_circuit.h(0) + #logical_circuit = benchmark_generation.get_benchmark( + # benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding=code + # ) + error_corrected_circuit = logical_circuit.copy() + transpiler = ShorTranspiler(error_corrected_circuit, add_syndromes=True) + transpiler.transpile() + error_corrected_circuit = transpiler.transpiled_qc + transpiler.decode_qubits() + error_corrected_circuit = transpiler.transpiled_qc + error_induced_circuit = error_corrected_circuit.copy() + error_induced_circuit = insert_error(error_induced_circuit) + + + print(" __________________________________________________________________________________________ ") + print('Logical Circuit:') + print(logical_circuit) + print(" __________________________________________________________________________________________ ") + print('Error corrected Circuit:') + print(error_corrected_circuit) + print(" __________________________________________________________________________________________ ") + print('Error Induced Circuit') + print(error_induced_circuit) + print(" __________________________________________________________________________________________ ") + + #print(check_equivalence(logical_circuit, error_corrected_circuit)) + #print(check_equivalence(error_corrected_circuit, error_induced_circuit)) diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py new file mode 100644 index 000000000..eb3606698 --- /dev/null +++ b/tests/test_error_correction.py @@ -0,0 +1,758 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + + + + + + +# TODO: +# uv requirements to be added: mqt.qcec, qiskit_aer + + + + +# What do we want to test: +# 1 function for each, split steane and shor for hardcoded sanity, combine for equvalencies +# transpilers work as intended (simply sanity checks) ✅ +# transpilers produce correct logical circuits (equivalency & simulation) +# produce circuits lead to correct results (equivalency (possible?) & simulations) +# works for all 4 given algorithms (maybe incorporate into correctness and error correction) + +# Have the ability to save the created circuits (utility function) +## save to file vs print vs logging? + +from __future__ import annotations + +import numpy as np +import pytest +from pathlib import Path +from qiskit import QuantumCircuit +from qiskit.quantum_info import state_fidelity, Statevector + +from mqt.bench.error_correction.shor_transpiler import ShorTranspiler +import mqt.bench.benchmark_generation as benchmark_generation +from qiskit_aer import AerSimulator # update uv requirements? +import qiskit as qk +from qiskit.circuit import CircuitInstruction, Gate +from qiskit.circuit.library import XGate + + + +def test_shor_transpiler_structure(): + """ + Ensures the ShorTranspiler translates a basic circuit into a reasonable structure. + Doesn't check for equivalence or correctness. + """ + qc = QuantumCircuit(2, 1) + qc.x(0) + qc.z(1) + qc.measure(1, 0) + + print("\n--- Logical Circuit ---") + print(qc.draw(fold=-1)) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + print("\n--- Transpiled Circuit ---") + print(transpiled_qc) + + # 2 original qubits * 9 data qubits = 18 data qubits + # 2 original qubits * 8 ancilla qubits = 16 ancilla qubits + # Total qubits = 34 + assert transpiled_qc.num_qubits == 34 + + # 2 original qubits * 8 classical bits = 16 syndrome bits + # 1 measurement * 9 bits = 9 measurement bits + # Total clbits = 25 + assert transpiled_qc.num_clbits == 25 + ##wrong expectations in the test it should check only non error correction gate mapping + ops = [inst.operation.name for inst in transpiled_qc.data] + assert "h" in ops + assert "cx" in ops + assert "measure" in ops + +@pytest.mark.skip() +def test_steane_transpiler_structure(): + assert False + +@pytest.mark.parametrize("code", ['shor']) #["steane", "shor"]) # double parametrize leads to crossproduct +@pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate",]) # "qft"]) +def test_errorcorrection_transpiler_equivalence(code: str, algorithm: str): + """ + Ensures the transpiler creates error-corrected circuits which produce the same result as the orinigal logical circuit. + Afterwards an error is introduced and the test checks, whether it is corrected. + Iterates over a number of example algorithms. + """ + circuit_size = 3 + # Initialize circuits + logical_circuit = benchmark_generation.get_benchmark( + benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding=code + ) + error_corrected_circuit = logical_circuit.copy() + transpiler = ShorTranspiler(error_corrected_circuit, add_syndromes=False) + transpiler.transpile() + transpiler.decode_qubits() + error_corrected_circuit = transpiler.transpiled_qc + error_induced_circuit = error_corrected_circuit.copy() + error_induced_circuit = insert_error(error_induced_circuit) + + # run each circuit + # compare results + + + +def insert_error(qc: QuantumCircuit, gate: Gate = XGate(), index: int | None = None) -> QuantumCircuit: + """ + Adds the specified gate at the beginning of the circuit + Flips the first qubit right after the first barrier by default + """ + assert qc.num_qubits >= gate.num_qubits, f'Quantum Circuit has not enough qubits to accomodate gate {gate.name}' + assert index is None or index >= 0, f'Index must be >= 0, Index provided: {index}' + + # Finds the first barrier + if index is None: + for i, instruction in enumerate(qc.data): + if instruction.operation.name == "barrier": + index = i + 1 + break + + # Insert the error gate + qubits = qc.qubits[:gate.num_qubits] + qc.data.insert(index, CircuitInstruction(gate, qubits)) + + return qc + + + +############################################################################################################################################ +############################################################################################################################################ +############################################################################################################################################ +############################################################################################################################################ +############################################################################################################################################ + + + +def errorcode_testing(alg: str = 'ghz', code: str = 'shor', qubits: int = 3): + assert qubits >= 3 + + base_circuit = benchmark_generation.get_benchmark( + benchmark=alg, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=qubits, encoding=code + ) + error_circuit = base_circuit.copy(name='error_circuit') + error_circuit = insert_error_gate(error_circuit) + uncorrected_circuit = benchmark_generation.get_benchmark( + benchmark=alg, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=qubits + ) + + + ### Equivalence checking + equivalent = check_equivalence(base_circuit, error_circuit) + #assert equivalent, 'Insertion of an error (flipped qubit) has lead to a new, no longer equivalent circuit' + print(f'Circuits are equivalent: {equivalent}') + + + + ### Simulated probabilistic similarity Base vs. Error-Inserted + error_fidelity = compare_distributions(base_circuit, error_circuit) + threshold = 0.95 # arbitrary guess + #assert fidelity > threshold, f'Simulated Hellinger Fidelity between base and error circuit is too low. Measured: {fidelity}, >Expected: {threshold}' + print(f'Hellinger Fidelity with error: {error_fidelity}') + + ### Simulated probabilistic similarity Uncorrected vs. Error-Inserted + # TODO: put in error corrected circuit + #### Example for condensing qubits + example = {'00000001111111': 3, '10101011111111': 1, '11111111010101': 2, '10101011111110' : 7} + print(condense_counts(example,'stean')) + + """ + uncorrected_fidelity = compare_distributions(uncorrected_circuit, error_circuit, code='shor') + threshold = threshold # arbitrary guess + #assert fidelity > threshold, f'Simulated Hellinger Fidelity between uncorrected and error circuit is too low. Measured: {uncorrected_fidelity}, Expected: >{threshold}' + print(f'Hellinger Fidelity with error: {uncorrected_fidelity}') + """ + + +def run_circuit(qc: qk.QuantumCircuit, shots: int = 1024): + """ + Simulates the circuit using AerSimulator + + Returns: + job.result() + + transpiled circuit qc + """ + simulator = AerSimulator() + transpiled_circuit = qk.transpile(qc, simulator) + job = simulator.run(transpiled_circuit, shots=shots) + + return job.result(), transpiled_circuit + +def insert_error_gate(qc: qk.QuantumCircuit, gate: Gate = XGate(), index: int | None = None) -> qk.QuantumCircuit: + """ + Adds the specified gate at the beginning of the circuit + Flips the first qubit after the first barrier by default + """ + assert qc.num_qubits >= gate.num_qubits, f'Quantum Circuit has not enough qubits to accomodate gate {gate.name}' + assert index >= 0, f'Index must be >= 0, Index provided: {index}' + + qubits = qc.qubits[:gate.num_qubits] + qc.data.insert(index, CircuitInstruction(gate, qubits)) + + return qc + +def check_equivalence(qc1: qk.QuantumCircuit, qc2: qk.QuantumCircuit) -> bool: + """ + Uses MQT QCEC to verify if qc1 and qc2 are equivalent + """ + import mqt.qcec + from mqt.qcec.pyqcec import EquivalenceCriterion as EC + + verification_results = mqt.qcec.verify(qc1, qc2) + accepted_equivalencies = [ + EC.equivalent, + EC.equivalent_up_to_global_phase, + EC.probably_equivalent + ] + + equivalent = verification_results.equivalence in accepted_equivalencies + return equivalent + +def compare_distributions(base: qk.QuantumCircuit, error: qk.QuantumCircuit, shots:int = 1024, code: str = 'None') -> float: + """ + Simulates 2 circuits and computes the Hellinger Fidelity between their count distributions + 1 = the same, 0 = no overlap + + If code is set to either 'stean' or 'shor' circuit error's result will be interpreted logically + """ + from qiskit.quantum_info import hellinger_fidelity + + result1, base = run_circuit(base, shots) + result2, error = run_circuit(error, shots) + counts1 = result1.get_counts(base) + counts2 = result2.get_counts(error) + # to be removed due to decoding + #if code in ['stean', 'shor']: + # counts2 = condense_counts(counts2, code) + + fidelity = hellinger_fidelity(counts1, counts2) + return fidelity + +def parse_qubits(physical_qubits: str, code: str): + """ + Takes in a measurement in physical qubits and returns the corresponding logical measurement. + + Returns: + Logical Measurement if possible, 'x' otherwise + """ + + from textwrap import wrap + + logical_qubits = '' + logical_0 = [] + logical_1 = [] + length = 0 + if code == 'stean': + length = 7 + logical_0 = ['0000000', '1010101', '0110011', '1100110', + '0001111', '1011010', '0111100', '1101001'] + logical_1 = ['1111111', '0101010', '1001100', '0011001', + '1110000', '0100101', '1000011', '0010110'] + elif code == 'shor': + length = 9 + logical_0 = [] + logical_1 = [] + else: + raise Exception('Wrong error correction code provided to qubit condensing') + assert len(physical_qubits)%length == 0, f'Result contains wrong number of physical qubits. \nExpected: Multiple of {length}\nReceived: {len(physical_qubits)}' + + qubits = wrap(physical_qubits, length) + for qubit in qubits: + if qubit in logical_0: + logical_qubits += '0' + elif qubit in logical_1: + logical_qubits += '1' + else: + return 'x' + + return logical_qubits + +def condense_counts(counts: dict[str, int], code: str) -> dict[str, int]: + """ + Kinda unnecessariy considering decoding... + + Takes in a result dict of physical measurements and returns logical measurements according to code. + Incoherent results will be grouped under 'x' + + Supports codes 'shor' and 'stean' + + Example: Code 'stean' + + Input: {'00000001111111': 3, '10101011111111': 1, '11111111010101': 2, '10101011111110' : 7} + Output: {'01': 4, '10': 2, 'x': 7} + """ + assert code in ['shor', 'stean'], f'Unsupported error code in condense_counts() {code}' + logical_counts = {} + for physical_measurement, count in counts.items(): + logical_measurement = parse_qubits(physical_measurement, code) + logical_counts[logical_measurement] = logical_counts.get(logical_measurement, 0) + count + + return logical_counts + + +############################################################################################################################################ +############################################################################################################################################ +############################################################################################################################################ +############################################################################################################################################ +############################################################################################################################################ + + +"""Equivalence tests for Shor Transpiler gates.""" + + + +def verify_gate_equivalence(gate_name: str, num_qubits: int) -> None: + """Verify that a transpiled gate is mathematically equivalent to the logical gate. + + Args: + gate_name: The name of the gate to test ('h', 'x', 'z', 's', 't', 'cx', 'cz'). + num_qubits: The number of qubits the gate acts on. + """ + # Create the logical circuit and initialize it in a non-trivial state (|+> state) + qc_logical = QuantumCircuit(num_qubits) + # Apply the gate + if gate_name == "h": + qc_logical.h(0) + elif gate_name == "x": + qc_logical.x(0) + elif gate_name == "z": + qc_logical.z(0) + elif gate_name == "s": + qc_logical.s(0) + elif gate_name == "t": + qc_logical.t(0) + elif gate_name == "cx": + qc_logical.cx(0, 1) + elif gate_name == "cz": + qc_logical.cz(0, 1) + else: + msg = f"Unknown gate {gate_name}" + raise ValueError(msg) + + # Get the expected density matrix + qc_logical_sim = qc_logical.copy() + # expected logical state + expected_sv_init = Statevector.from_instruction(qc_logical) + + # Transpile the circuit + # We set add_syndromes=False to prevent statevector simulation from blowing up in memory + transpiler = ShorTranspiler(qc_logical, add_syndromes=False) + transpiled_qc = transpiler.transpile() + + drawing = transpiled_qc.draw(fold=-1) + print(f"\n--- Transpiled Circuit for {gate_name.upper()} ---") + print(drawing) + + # Save to file to ensure it can be viewed regardless of pytest-xdist capturing stdout + output_dir = Path("tests/circuit_drawings") + output_dir.mkdir(parents=True, exist_ok=True) + with open(output_dir / f"{gate_name}_transpiled.txt", "w", encoding="utf-8") as f: + f.write(f"number of qubits {num_qubits}\n") + f.write(f"--- Transpiled Circuit for {gate_name.upper()} ---\n\n") + f.write(str(drawing) + "\n") + # Apply decoding so the logical state collapses back to the first physical qubit of each block + transpiler.decode_qubits() + + transpiled_qc.save_statevector() + + sim = AerSimulator(method="statevector") + result_transpiled = sim.run(transpiled_qc).result() + assert result_transpiled.success, f"Simulation failed: {result_transpiled.status}" + actual_sv = result_transpiled.get_statevector() + + # Extract the density matrix of the physical qubits holding the logical state + # These are the 0-th qubits of each physical data register + logical_qubits_physical = [ + transpiled_qc.find_bit(transpiler.physical_data_registers[i][0]).index + for i in range(num_qubits) + ] + + from qiskit.quantum_info import partial_trace + all_qubits = list(range(transpiled_qc.num_qubits)) + trace_qubits = [q for q in all_qubits if q not in logical_qubits_physical] + + actual_rho = partial_trace(actual_sv, trace_qubits) + + from qiskit.quantum_info import DensityMatrix + expected_rho_init = DensityMatrix(expected_sv_init) + + try: + actual_sv_reduced = actual_rho.to_statevector() + except Exception: + actual_sv_reduced = None + + # Compare the density matrices + fidelity = state_fidelity(expected_sv_init, actual_rho) + + # Save the resulting density matrices and state vectors to the text file for visual inspection + with open(output_dir / f"{gate_name}_transpiled.txt", "a", encoding="utf-8") as f: + f.write("\n\n=== LOGICAL EXPECTED DENSITY MATRIX ===\n") + f.write(str(np.round(expected_rho_init.data, 3)) + "\n") + f.write("\n=== ACTUAL TRANSPILED DENSITY MATRIX (REDUCED) ===\n") + f.write(str(np.round(actual_rho.data, 3)) + "\n") + + if actual_sv_reduced is not None: + f.write("\n\n=== LOGICAL EXPECTED STATE VECTOR ===\n") + f.write(str(np.round(expected_sv_init.data, 3)) + "\n") + f.write("\n=== ACTUAL TRANSPILED STATE VECTOR (REDUCED) ===\n") + f.write(str(np.round(actual_sv_reduced.data, 3)) + "\n") + + f.write(f"\nSTATE FIDELITY: {fidelity:.6f}\n") + + assert fidelity > 0.999, f"Fidelity too low: {fidelity}" + + +def test_h_equivalence() -> None: + """Test equivalence for logical H gate.""" + verify_gate_equivalence("h", 1) + + +def test_x_equivalence() -> None: + """Test equivalence for logical X gate.""" + verify_gate_equivalence("x", 1) + + +def test_z_equivalence() -> None: + """Test equivalence for logical Z gate.""" + verify_gate_equivalence("z", 1) + + +def test_s_equivalence() -> None: + """Test equivalence for logical S gate.""" + verify_gate_equivalence("s", 1) + + +@pytest.mark.skip(reason="Slow test, takes >10 mins due to 27 qubit simulation") +def test_t_equivalence() -> None: + """Test equivalence for logical T gate.""" + verify_gate_equivalence("t", 1) + + +def test_cx_equivalence() -> None: + """Test equivalence for logical CX gate.""" + verify_gate_equivalence("cx", 2) + +def test_cz_equivalence() -> None: + """Test equivalence for logical CZ gate.""" + verify_gate_equivalence("cz", 2) + + +############################################################################################################################################ +############################################################################################################################################ +############################################################################################################################################ +############################################################################################################################################ +############################################################################################################################################ + + +"""Tests for Shor Transpiler.""" + +#from __future__ import annotations + +import pytest +from qiskit import QuantumCircuit + +from mqt.bench.error_correction.shor_transpiler import ShorTranspiler + +# this needs mpre tests +def test_shor_transpiler() -> None: + """Test that ShorTranspiler successfully transpiles a basic circuit.""" + qc = QuantumCircuit(2, 1) + qc.x(0) + qc.z(1) + qc.measure(1, 0) + + print("\n--- Logical Circuit ---") + print(qc.draw(fold=-1)) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + print("\n--- Transpiled Circuit ---") + print(transpiled_qc) + + # 2 original qubits * 9 data qubits = 18 data qubits + # 2 original qubits * 8 ancilla qubits = 16 ancilla qubits + # Total qubits = 34 + assert transpiled_qc.num_qubits == 34 + + # 2 original qubits * 8 classical bits = 16 syndrome bits + # 1 measurement * 9 bits = 9 measurement bits + # Total clbits = 25 + assert transpiled_qc.num_clbits == 25 + ##wrong expectations in the test it should check only non error correction gate mapping + ops = [inst.operation.name for inst in transpiled_qc.data] + assert "h" in ops + assert "cx" in ops + assert "measure" in ops + + +def test_shor_transpiler_unsupported_gate() -> None: + """Test that unsupported gates raise NotImplementedError.""" + qc = QuantumCircuit(1) + qc.rx(0, 0) + + transpiler = ShorTranspiler(qc) + with pytest.raises(NotImplementedError, match=r"Gate rx is not supported by ShorTranspiler\."): + transpiler.transpile() + + +def test_shor_transpiler_s_gate_structure() -> None: + """Test that the S gate teleportation circuit has the correct structure. + + Verifies that: + - A magic state ancilla register (9 qubits) is allocated. + - The teleportation measurement register (1 classical bit) is allocated. + - The circuit contains the expected gates (h, s, cx, measure). + """ + qc = QuantumCircuit(1) + qc.s(0) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + # Original: 9 data + 6 bit-flip ancilla + 2 phase-flip ancilla = 17 + # S gate adds: 9 magic data = 9 + # Total = 26 + assert transpiled_qc.num_qubits == 26 + + # Original: 6 bf meas + 2 pf meas = 8 + # S gate adds: 1 teleport meas = 1 + # Total = 9 + assert transpiled_qc.num_clbits == 9 + + ops = [inst.operation.name for inst in transpiled_qc.data] + + # Magic state prep uses h and p on the ancilla qubit + assert "p" in ops + assert "h" in ops + + # Teleportation uses cx, measure + assert "cx" in ops + assert "measure" in ops + + # Conditional correction uses if_else + assert "if_else" in ops + + # Verify the magic state register exists + reg_names = [reg.name for reg in transpiled_qc.qregs] + assert "ms0" in reg_names + + # Verify teleportation measurement register exists + creg_names = [reg.name for reg in transpiled_qc.cregs] + assert "tmeas0" in creg_names + + +def test_shor_transpiler_s_gate_followed_by_other_gates() -> None: + """Test that gates applied after the S gate target the correct register. + + After S gate teleportation, subsequent gates should operate on the original + data register since the teleportation gadget doesn't swap the pointers. + """ + qc = QuantumCircuit(1, 1) + qc.s(0) + qc.z(0) + qc.measure(0, 0) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + # Circuit should compile and run without errors + assert transpiled_qc.num_qubits > 0 + assert transpiled_qc.num_clbits > 0 + + ops = [inst.operation.name for inst in transpiled_qc.data] + assert "p" in ops # Magic state prep (phase) + assert "x" in ops # Logical Z uses physical X + assert "measure" in ops + + +def test_shor_transpiler_multiple_s_gates() -> None: + """Test that multiple S gates each allocate independent ancilla blocks. + + Two consecutive S gates should produce two independent magic state + ancilla blocks (ms0 and ms1). + """ + qc = QuantumCircuit(1) + qc.s(0) + qc.s(0) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + reg_names = [reg.name for reg in transpiled_qc.qregs] + assert "ms0" in reg_names + assert "ms1" in reg_names + + +def test_shor_transpiler_t_gate_structure() -> None: + """Test that the T gate teleportation circuit has the correct structure. + + Verifies that: + - A magic state ancilla register for T (9 qubits) is allocated. + - The teleportation measurement register for T (1 classical bit) is allocated. + - Because of the S correction, another S ancilla and measurement are also allocated conditionally. + """ + qc = QuantumCircuit(1) + qc.t(0) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + # Original: 17 qubits + # T gate adds: 9 magic data for T + 9 magic data for S = 18 + # Total = 35 + assert transpiled_qc.num_qubits == 35 + + # Original: 8 clbits + # T gate adds: 1 for T + 1 for S = 2 + # Total = 10 + assert transpiled_qc.num_clbits == 10 + + ops = [inst.operation.name for inst in transpiled_qc.data] + + # Magic state prep uses p and h + assert "p" in ops + assert "h" in ops + + # Verify the T-magic state register exists + reg_names = [reg.name for reg in transpiled_qc.qregs] + assert "anc_t_1" in reg_names + assert "ms0" in reg_names # from the S correction + + +def test_shor_transpiler_barrier() -> None: + """Test logical barrier translates to physical barrier on all involved qubits.""" + qc = QuantumCircuit(2) + qc.barrier(0) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + ops = [inst.operation.name for inst in transpiled_qc.data] + assert "barrier" in ops + + +def test_shor_transpiler_measure() -> None: + """Test logical measure maps to 9 physical measurements.""" + qc = QuantumCircuit(1, 1) + qc.measure(0, 0) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + measure_count = sum(1 for inst in transpiled_qc.data if inst.operation.name == "measure") + # At least 9 physical measurements for the single logical measurement + assert measure_count >= 9 + + +def test_shor_transpiler_h_gate() -> None: + """Test logical H gate.""" + qc = QuantumCircuit(1) + qc.h(0) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + ops = [inst.operation.name for inst in transpiled_qc.data] + assert "h" in ops + assert "swap" in ops + + +def test_shor_transpiler_x_gate() -> None: + """Test logical X gate uses Z transversally.""" + qc = QuantumCircuit(1) + qc.x(0) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + ops = [inst.operation.name for inst in transpiled_qc.data] + # Shor code logical X = Z_0 Z_3 Z_6 + assert "z" in ops + + +def test_shor_transpiler_z_gate() -> None: + """Test logical Z gate uses X transversally.""" + qc = QuantumCircuit(1) + qc.z(0) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + ops = [inst.operation.name for inst in transpiled_qc.data] + # Shor code logical Z = X_0 X_1 X_2 + assert "x" in ops + + +def test_shor_transpiler_cx_gate() -> None: + """Test logical CX gate.""" + qc = QuantumCircuit(2) + qc.cx(0, 1) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + ops = [inst.operation.name for inst in transpiled_qc.data] + assert "cx" in ops + + +def test_shor_transpiler_cz_gate() -> None: + """Test logical CZ gate.""" + qc = QuantumCircuit(2) + qc.cz(0, 1) + + transpiler = ShorTranspiler(qc) + transpiled_qc = transpiler.transpile() + + ops = [inst.operation.name for inst in transpiled_qc.data] + # CZ is implemented via H, CX, H + assert "h" in ops + assert "cx" in ops + assert "swap" in ops + + +def test_shor_transpiler_encode_decode() -> None: + """Test static encoding and decoding methods directly.""" + from qiskit import QuantumRegister + qc = QuantumCircuit() + reg = QuantumRegister(9, "q") + qc.add_register(reg) + + ShorTranspiler._apply_shor_encoding(qc, reg) + ops_enc = [inst.operation.name for inst in qc.data] + assert len(ops_enc) > 0 + + ShorTranspiler._apply_shor_decoding(qc, reg) + ops_dec = [inst.operation.name for inst in qc.data] + assert len(ops_dec) > len(ops_enc) + + +def test_shor_transpiler_prepare_magic() -> None: + """Test _prepare_magic directly.""" + from qiskit import QuantumRegister + import numpy as np + + qc = QuantumCircuit() + anc = QuantumRegister(9, "anc") + qc.add_register(anc) + + ShorTranspiler._prepare_magic(qc, anc, np.pi/2) + ops = [inst.operation.name for inst in qc.data] + assert "h" in ops + assert "p" in ops From 0410fed5dd54b46d1964ce221b4f6113c6dd4c6d Mon Sep 17 00:00:00 2001 From: "LAPTOP-N4TQ80AC\\khusa" Date: Sat, 30 May 2026 10:43:17 +0200 Subject: [PATCH 17/72] =?UTF-8?q?=E2=9C=A8=20Add=20syndromes=20control=20f?= =?UTF-8?q?lag?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../error_correction/steane_transpiler.py | 29 ++++++++++++------- tests/test_steane_transpiler.py | 6 ++-- 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/mqt/bench/error_correction/steane_transpiler.py b/src/mqt/bench/error_correction/steane_transpiler.py index 5a9c07a4a..d3be31162 100644 --- a/src/mqt/bench/error_correction/steane_transpiler.py +++ b/src/mqt/bench/error_correction/steane_transpiler.py @@ -32,7 +32,7 @@ class SteaneTranspiler: """A high-level transpiler that encodes a QuantumCircuit using Steane's 7-qubit error correction code.""" - def __init__(self, original_circuit: QuantumCircuit) -> None: + def __init__(self, original_circuit: QuantumCircuit, add_syndromes = True) -> None: """Initialize the transpiler with the original QuantumCircuit.""" self.original_qc = original_circuit self.num_logical_qubits = original_circuit.num_qubits @@ -42,8 +42,10 @@ def __init__(self, original_circuit: QuantumCircuit) -> None: self.bit_flip_syndrome_measurements: list[ClassicalRegister] = [] self.phase_flip_syndrome_measurements: list[ClassicalRegister] = [] self.logical_qubit_measurements: list[ClassicalRegister] = [] + self.add_syndromes = add_syndromes self.transpiled_qc = QuantumCircuit() + def transpile(self) -> QuantumCircuit: """Transpile the original circuit to a fault-tolerant circuit using Steane's code.""" self.encode_qubits() @@ -152,7 +154,8 @@ def _handle_h(self, instruction: CircuitInstruction) -> None: self.transpiled_qc.h(physical_data_register) self.transpiled_qc.barrier(label=f"H {logical_qubit_index}") - self.insert_syndromes(logical_qubit_index) + if self.add_syndromes: + self.insert_syndromes(logical_qubit_index) def _handle_x(self, instruction: CircuitInstruction) -> None: """Handle X instruction.""" @@ -162,7 +165,8 @@ def _handle_x(self, instruction: CircuitInstruction) -> None: self.transpiled_qc.x(physical_data_register) self.transpiled_qc.barrier(label=f"X {logical_qubit_index}") - self.insert_syndromes(logical_qubit_index) + if self.add_syndromes: + self.insert_syndromes(logical_qubit_index) def _handle_z(self, instruction: CircuitInstruction) -> None: """Handle Z instruction.""" @@ -172,7 +176,8 @@ def _handle_z(self, instruction: CircuitInstruction) -> None: self.transpiled_qc.z(physical_data_register) self.transpiled_qc.barrier(label=f"Z {logical_qubit_index}") - self.insert_syndromes(logical_qubit_index) + if self.add_syndromes: + self.insert_syndromes(logical_qubit_index) def _handle_s(self, instruction: CircuitInstruction) -> None: """Handle S instruction.""" @@ -183,7 +188,8 @@ def _handle_s(self, instruction: CircuitInstruction) -> None: self.transpiled_qc.sdg(physical_data_register) self.transpiled_qc.barrier(label=f"S {logical_qubit_index}") - self.insert_syndromes(logical_qubit_index) + if self.add_syndromes: + self.insert_syndromes(logical_qubit_index) def _handle_t(self, instruction: CircuitInstruction) -> None: """Handle T instruction.""" @@ -227,7 +233,8 @@ def _handle_t(self, instruction: CircuitInstruction) -> None: self.transpiled_qc.sdg(physical_data_register) self.transpiled_qc.barrier(label=f"T {logical_qubit_index}") - self.insert_syndromes(logical_qubit_index) + if self.add_syndromes: + self.insert_syndromes(logical_qubit_index) def _handle_cx(self, instruction: CircuitInstruction) -> None: @@ -244,8 +251,9 @@ def _handle_cx(self, instruction: CircuitInstruction) -> None: self.transpiled_qc.barrier(label=f"CX {control_logical_qubit_index} {target_logical_qubit_index}") - self.insert_syndromes(control_logical_qubit_index) - self.insert_syndromes(target_logical_qubit_index) + if self.add_syndromes: + self.insert_syndromes(control_logical_qubit_index) + self.insert_syndromes(target_logical_qubit_index) # it сould use the hadamards with cnots def _handle_cz(self, instruction: CircuitInstruction) -> None: @@ -262,8 +270,9 @@ def _handle_cz(self, instruction: CircuitInstruction) -> None: self.transpiled_qc.barrier(label=f"CZ {control_logical_qubit_index} {target_logical_qubit_index}") - self.insert_syndromes(control_logical_qubit_index) - self.insert_syndromes(target_logical_qubit_index) + if self.add_syndromes: + self.insert_syndromes(control_logical_qubit_index) + self.insert_syndromes(target_logical_qubit_index) # TODO: Review and verify it works def insert_syndromes(self, logical_qubit_index: int) -> None: diff --git a/tests/test_steane_transpiler.py b/tests/test_steane_transpiler.py index 399814605..6ca267b3f 100644 --- a/tests/test_steane_transpiler.py +++ b/tests/test_steane_transpiler.py @@ -18,14 +18,14 @@ def test_steane_transpiler() -> None: """Test that ShorTranspiler successfully transpiles a basic circuit.""" - qc = QuantumCircuit(QuantumRegister(1), ClassicalRegister(1)) - qc.t(0) + qc = QuantumCircuit(QuantumRegister(2), ClassicalRegister(1)) + qc.cx(0,1) #qc.measure(0,0) print("\n--- Logical Circuit ---") print(qc.draw(fold=-1)) - transpiler = SteaneTranspiler(qc) + transpiler = SteaneTranspiler(qc, False) transpiled_qc = transpiler.transpile() transpiled_qc.draw("mpl", fold=-1) #plt.show() From 574db9a5e20477b3dbf8480afd74e463690ecb9e Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 30 May 2026 12:38:08 +0200 Subject: [PATCH 18/72] More Ways to test :) --- main.py | 181 +++++++++++++++++---------------- tests/test_error_correction.py | 107 ++++++++----------- 2 files changed, 141 insertions(+), 147 deletions(-) diff --git a/main.py b/main.py index 2a67c20a7..85f36a89f 100644 --- a/main.py +++ b/main.py @@ -9,9 +9,13 @@ import mqt.bench.benchmark_generation as benchmark_generation from qiskit_aer import AerSimulator # update uv requirements? import qiskit as qk +from qiskit import QuantumCircuit +from qiskit.result.result import Result from mqt.bench.error_correction.shor_transpiler import ShorTranspiler from tests.test_error_correction import insert_error +from qiskit.circuit import CircuitInstruction, Gate +from qiskit.circuit.library import XGate, HGate # uv requirements to be added: mqt.qcec, qiskit_aer @@ -38,16 +42,16 @@ def errorcode_testing(alg: str = 'ghz', code: str = 'shor', qubits: int = 3): ### Simulated probabilistic similarity Base vs. Error-Inserted - error_fidelity = compare_distributions(base_circuit, error_circuit) - threshold = 0.95 # arbitrary guess + #error_fidelity = compare_distributions(base_circuit, error_circuit) + #threshold = 0.95 # arbitrary guess #assert fidelity > threshold, f'Simulated Hellinger Fidelity between base and error circuit is too low. Measured: {fidelity}, >Expected: {threshold}' - print(f'Hellinger Fidelity with error: {error_fidelity}') + #print(f'Hellinger Fidelity with error: {error_fidelity}') ### Simulated probabilistic similarity Uncorrected vs. Error-Inserted # TODO: put in error corrected circuit #### Example for condensing qubits - example = {'00000001111111': 3, '10101011111111': 1, '11111111010101': 2, '10101011111110' : 7} - print(condense_counts(example,'stean')) + #example = {'00000001111111': 3, '10101011111111': 1, '11111111010101': 2, '10101011111110' : 7} + #print(condense_counts(example,'stean')) """ uncorrected_fidelity = compare_distributions(uncorrected_circuit, error_circuit, code='shor') @@ -57,9 +61,11 @@ def errorcode_testing(alg: str = 'ghz', code: str = 'shor', qubits: int = 3): """ -def run_circuit(qc: qk.QuantumCircuit, shots: int = 1024): +def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[Result, QuantumCircuit]: """ - Simulates the circuit using AerSimulator + Simulates the circuit using AerSimulator. + + Adds measurements to all qubits. Returns: job.result() @@ -67,23 +73,30 @@ def run_circuit(qc: qk.QuantumCircuit, shots: int = 1024): transpiled circuit qc """ simulator = AerSimulator() + qc.measure_all() transpiled_circuit = qk.transpile(qc, simulator) job = simulator.run(transpiled_circuit, shots=shots) return job.result(), transpiled_circuit -def insert_error_gate(qc: qk.QuantumCircuit) -> qk.QuantumCircuit: +def insert_error(qc: QuantumCircuit, gate: Gate = XGate(), index: int | None = None) -> QuantumCircuit: """ - Flips bit 0 at the beginning of the circuit + Adds the specified gate at the beginning of the circuit + Flips the first qubit right after the first barrier by default """ - from qiskit.circuit import CircuitInstruction - from qiskit.circuit.library import XGate - - gate = XGate() - qubits = [qc.qubits[0]] - insert_index = 0 + assert qc.num_qubits >= gate.num_qubits, f'Quantum Circuit has not enough qubits to accomodate gate {gate.name}' + assert index is None or index >= 0, f'Index must be >= 0, Index provided: {index}' + + # Finds the first barrier + if index is None: + for i, instruction in enumerate(qc.data): + if instruction.operation.name == "barrier": + index = i + 1 + break - qc.data.insert(insert_index, CircuitInstruction(gate, qubits)) + # Insert the error gate + qubits = qc.qubits[:gate.num_qubits] + qc.data.insert(index, CircuitInstruction(gate, qubits)) return qc @@ -94,8 +107,7 @@ def check_equivalence(qc1: qk.QuantumCircuit, qc2: qk.QuantumCircuit) -> bool: import mqt.qcec from mqt.qcec.pyqcec import EquivalenceCriterion as EC - verification_results = mqt.qcec.verify(qc1, qc2, - transform_dynamic_circuit=True) + verification_results = mqt.qcec.verify(qc1, qc2) accepted_equivalencies = [ EC.equivalent, EC.equivalent_up_to_global_phase, @@ -105,7 +117,7 @@ def check_equivalence(qc1: qk.QuantumCircuit, qc2: qk.QuantumCircuit) -> bool: equivalent = verification_results.equivalence in accepted_equivalencies return equivalent -def compare_distributions(base: qk.QuantumCircuit, error: qk.QuantumCircuit, shots:int = 1024, code: str = 'None') -> float: +def compare_distributions(qc1: QuantumCircuit, qc2: QuantumCircuit, counts1: dict, counts2: dict, code1: str = 'None', code2: str = 'None') -> float: """ Simulates 2 circuits and computes the Hellinger Fidelity between their count distributions 1 = the same, 0 = no overlap @@ -113,77 +125,57 @@ def compare_distributions(base: qk.QuantumCircuit, error: qk.QuantumCircuit, sho If code is set to either 'stean' or 'shor' circuit error's result will be interpreted logically """ from qiskit.quantum_info import hellinger_fidelity - - result1, base = run_circuit(base, shots) - result2, error = run_circuit(error, shots) - counts1 = result1.get_counts(base) - counts2 = result2.get_counts(error) # to be removed due to decoding - #if code in ['stean', 'shor']: - # counts2 = condense_counts(counts2, code) + if code1 in ['stean', 'shor']: + counts1 = condense_counts(qc1, counts1, code1) + if code2 in ['stean', 'shor']: + counts2 = condense_counts(qc2, counts2, code2) fidelity = hellinger_fidelity(counts1, counts2) return fidelity -def parse_qubits(physical_qubits: str, code: str): +def parse_qubits(qc: qk.QuantumCircuit, physical_qubits: str): """ Takes in a measurement in physical qubits and returns the corresponding logical measurement. - Returns: - Logical Measurement if possible, 'x' otherwise + Underlying circuit must use registers named 'qx' (x in int) for each logical qubit, with results in qx[0] """ - - from textwrap import wrap - - logical_qubits = '' - logical_0 = [] - logical_1 = [] - length = 0 - if code == 'stean': - length = 7 - logical_0 = ['0000000', '1010101', '0110011', '1100110', - '0001111', '1011010', '0111100', '1101001'] - logical_1 = ['1111111', '0101010', '1001100', '0011001', - '1110000', '0100101', '1000011', '0010110'] - elif code == 'shor': - length = 9 - logical_0 = [] - logical_1 = [] - else: - raise Exception('Wrong error correction code provided to qubit condensing') - assert len(physical_qubits)%length == 0, f'Result contains wrong number of physical qubits. \nExpected: Multiple of {length}\nReceived: {len(physical_qubits)}' - - qubits = wrap(physical_qubits, length) - for qubit in qubits: - if qubit in logical_0: - logical_qubits += '0' - elif qubit in logical_1: - logical_qubits += '1' - else: - return 'x' - + # indices + import re + def is_q_integer(s: str) -> bool: + """ checks if s is of form 'qx' where x in int (e.g. 'q1', 'q23') """ + return bool(re.fullmatch(r'q\d+', s)) + + data_indices = [] + for register in qc.qregs: + print(register) + if is_q_integer(register.name): + data_indices.append(qc.find_bit(register[-1]).index) # qiskit is little-endian + + print(data_indices) + + # condesning + logical_qubits = "" + for index in data_indices: + logical_qubits += physical_qubits[index] + + print(logical_qubits) + return logical_qubits -def condense_counts(counts: dict[str, int], code: str) -> dict[str, int]: +def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int], code: str) -> dict[str, int]: """ - Kinda unnecessariy considering decoding... - - Takes in a result dict of physical measurements and returns logical measurements according to code. - Incoherent results will be grouped under 'x' + Takes in a result dict of a decoded physical measurement and returns logical measurements according to code. Supports codes 'shor' and 'stean' - - Example: Code 'stean' - - Input: {'00000001111111': 3, '10101011111111': 1, '11111111010101': 2, '10101011111110' : 7} - Output: {'01': 4, '10': 2, 'x': 7} """ - assert code in ['shor', 'stean'], f'Unsupported error code in condense_counts() {code}' + assert code in ['shor', 'stean'], f'Unsupported error code in condense_counts(): {code}' logical_counts = {} for physical_measurement, count in counts.items(): - logical_measurement = parse_qubits(physical_measurement, code) + logical_measurement = parse_qubits(qc, physical_measurement) logical_counts[logical_measurement] = logical_counts.get(logical_measurement, 0) + count + print(logical_counts) return logical_counts @@ -202,35 +194,54 @@ def condense_counts(counts: dict[str, int], code: str) -> dict[str, int]: #errorcode_testing() - circuit_size = 3 + circuit_size = 1 algorithm = 'ghz' code = 'shor' # Initialize circuits - logical_circuit = qk.QuantumCircuit(1) + logical_circuit = qk.QuantumCircuit(circuit_size) logical_circuit.h(0) + #logical_circuit = benchmark_generation.get_benchmark( # benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding=code # ) error_corrected_circuit = logical_circuit.copy() transpiler = ShorTranspiler(error_corrected_circuit, add_syndromes=True) transpiler.transpile() - error_corrected_circuit = transpiler.transpiled_qc transpiler.decode_qubits() error_corrected_circuit = transpiler.transpiled_qc error_induced_circuit = error_corrected_circuit.copy() - error_induced_circuit = insert_error(error_induced_circuit) + #error_induced_circuit = insert_error(error_induced_circuit) + error_induced_circuit = insert_error(error_induced_circuit,gate=HGate()) + + + - print(" __________________________________________________________________________________________ ") - print('Logical Circuit:') - print(logical_circuit) - print(" __________________________________________________________________________________________ ") - print('Error corrected Circuit:') - print(error_corrected_circuit) - print(" __________________________________________________________________________________________ ") - print('Error Induced Circuit') - print(error_induced_circuit) - print(" __________________________________________________________________________________________ ") + #print(" __________________________________________________________________________________________ ") + #print('Logical Circuit:') + #print(logical_circuit) + #print(" __________________________________________________________________________________________ ") + #print('Error corrected Circuit:') + #print(error_corrected_circuit) + #print(" __________________________________________________________________________________________ ") + #print('Error Induced Circuit') + #print(error_induced_circuit) + #print(" __________________________________________________________________________________________ ") #print(check_equivalence(logical_circuit, error_corrected_circuit)) #print(check_equivalence(error_corrected_circuit, error_induced_circuit)) + + logical_result, logical_circuit = run_circuit(logical_circuit) + corrected_result, error_corrected_circuit = run_circuit(error_corrected_circuit) + induced_result, error_induced_circuit = run_circuit(error_induced_circuit) + + logical_counts = logical_result.get_counts(logical_circuit) + corrected_counts = corrected_result.get_counts(error_corrected_circuit) + induced_counts = induced_result.get_counts(error_induced_circuit) + + print(logical_counts) + print(corrected_counts) + print(induced_counts) + + print(compare_distributions(logical_circuit, error_corrected_circuit, logical_counts, corrected_counts, 'none', 'shor')) + print(compare_distributions(error_corrected_circuit, error_induced_circuit, corrected_counts, induced_counts,'shor', 'shor')) \ No newline at end of file diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index eb3606698..155095c07 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -43,6 +43,8 @@ from qiskit.circuit.library import XGate +# for: [steane, shor] +# for: [x,h,z] def test_shor_transpiler_structure(): """ @@ -52,16 +54,14 @@ def test_shor_transpiler_structure(): qc = QuantumCircuit(2, 1) qc.x(0) qc.z(1) - qc.measure(1, 0) - - print("\n--- Logical Circuit ---") - print(qc.draw(fold=-1)) transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() + transpiler.transpile() + transpiler.decode_qubits() + transpiled_qc = transpiler.transpiled_qc + + - print("\n--- Transpiled Circuit ---") - print(transpiled_qc) # 2 original qubits * 9 data qubits = 18 data qubits # 2 original qubits * 8 ancilla qubits = 16 ancilla qubits @@ -104,6 +104,10 @@ def test_errorcorrection_transpiler_equivalence(code: str, algorithm: str): error_induced_circuit = insert_error(error_induced_circuit) # run each circuit + logical_counts, logical_circuit = run_circuit(logical_circuit) + corrected_counts, error_corrected_circuit = run_circuit(error_corrected_circuit) + induced_counts, error_induced_circuit = run_circuit(error_induced_circuit) + # compare results @@ -169,7 +173,7 @@ def errorcode_testing(alg: str = 'ghz', code: str = 'shor', qubits: int = 3): # TODO: put in error corrected circuit #### Example for condensing qubits example = {'00000001111111': 3, '10101011111111': 1, '11111111010101': 2, '10101011111110' : 7} - print(condense_counts(example,'stean')) + #print(condense_counts(example,'stean')) """ uncorrected_fidelity = compare_distributions(uncorrected_circuit, error_circuit, code='shor') @@ -179,9 +183,11 @@ def errorcode_testing(alg: str = 'ghz', code: str = 'shor', qubits: int = 3): """ -def run_circuit(qc: qk.QuantumCircuit, shots: int = 1024): +def run_circuit(qc: qk.QuantumCircuit, shots: int = 1024) -> tuple[dict, QuantumCircuit]: """ - Simulates the circuit using AerSimulator + Simulates the circuit using AerSimulator. + + Adds measurements to all qubits. Returns: job.result() @@ -189,6 +195,7 @@ def run_circuit(qc: qk.QuantumCircuit, shots: int = 1024): transpiled circuit qc """ simulator = AerSimulator() + qc.measure_all() transpiled_circuit = qk.transpile(qc, simulator) job = simulator.run(transpiled_circuit, shots=shots) @@ -224,7 +231,7 @@ def check_equivalence(qc1: qk.QuantumCircuit, qc2: qk.QuantumCircuit) -> bool: equivalent = verification_results.equivalence in accepted_equivalencies return equivalent -def compare_distributions(base: qk.QuantumCircuit, error: qk.QuantumCircuit, shots:int = 1024, code: str = 'None') -> float: +def compare_distributions(qc1: QuantumCircuit, qc2: QuantumCircuit, counts1: dict, counts2: dict, shots:int = 1024, code1: str = 'None', code2: str = 'None') -> float: """ Simulates 2 circuits and computes the Hellinger Fidelity between their count distributions 1 = the same, 0 = no overlap @@ -233,79 +240,55 @@ def compare_distributions(base: qk.QuantumCircuit, error: qk.QuantumCircuit, sho """ from qiskit.quantum_info import hellinger_fidelity - result1, base = run_circuit(base, shots) - result2, error = run_circuit(error, shots) - counts1 = result1.get_counts(base) - counts2 = result2.get_counts(error) # to be removed due to decoding - #if code in ['stean', 'shor']: - # counts2 = condense_counts(counts2, code) + if code1 in ['stean', 'shor']: + counts1 = condense_counts(qc1, counts1, code1) + if code2 in ['stean', 'shor']: + counts2 = condense_counts(qc2, counts2, code2) fidelity = hellinger_fidelity(counts1, counts2) return fidelity -def parse_qubits(physical_qubits: str, code: str): +def parse_qubits(qc: qk.QuantumCircuit, physical_qubits: str): """ Takes in a measurement in physical qubits and returns the corresponding logical measurement. - Returns: - Logical Measurement if possible, 'x' otherwise + Underlying circuit must use registers named 'qx' (x in int) for each logical qubit, with results in qx[0] """ - - from textwrap import wrap - - logical_qubits = '' - logical_0 = [] - logical_1 = [] - length = 0 - if code == 'stean': - length = 7 - logical_0 = ['0000000', '1010101', '0110011', '1100110', - '0001111', '1011010', '0111100', '1101001'] - logical_1 = ['1111111', '0101010', '1001100', '0011001', - '1110000', '0100101', '1000011', '0010110'] - elif code == 'shor': - length = 9 - logical_0 = [] - logical_1 = [] - else: - raise Exception('Wrong error correction code provided to qubit condensing') - assert len(physical_qubits)%length == 0, f'Result contains wrong number of physical qubits. \nExpected: Multiple of {length}\nReceived: {len(physical_qubits)}' - - qubits = wrap(physical_qubits, length) - for qubit in qubits: - if qubit in logical_0: - logical_qubits += '0' - elif qubit in logical_1: - logical_qubits += '1' - else: - return 'x' - + # indices + import re + def is_q_integer(s: str) -> bool: + """ checks if s is of form 'qx' where x in int (e.g. 'q1', 'q23') """ + return bool(re.fullmatch(r'q\d+', s)) + + data_indices = [] + for register in qc.qregs: + if is_q_integer(register.name): + data_indices.append(qc.find_bit(register[0]).index) + + # condesning + logical_qubits = "" + for index in data_indices: + logical_qubits += physical_qubits[index] + return logical_qubits -def condense_counts(counts: dict[str, int], code: str) -> dict[str, int]: +def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int], code: str) -> dict[str, int]: """ - Kinda unnecessariy considering decoding... - - Takes in a result dict of physical measurements and returns logical measurements according to code. - Incoherent results will be grouped under 'x' + Takes in a result dict of a decoded physical measurement and returns logical measurements according to code. Supports codes 'shor' and 'stean' - - Example: Code 'stean' - - Input: {'00000001111111': 3, '10101011111111': 1, '11111111010101': 2, '10101011111110' : 7} - Output: {'01': 4, '10': 2, 'x': 7} """ - assert code in ['shor', 'stean'], f'Unsupported error code in condense_counts() {code}' + assert code in ['shor', 'stean'], f'Unsupported error code in condense_counts(): {code}' logical_counts = {} for physical_measurement, count in counts.items(): - logical_measurement = parse_qubits(physical_measurement, code) + logical_measurement = parse_qubits(qc, physical_measurement) logical_counts[logical_measurement] = logical_counts.get(logical_measurement, 0) + count return logical_counts + ############################################################################################################################################ ############################################################################################################################################ ############################################################################################################################################ From 569bc68bbedc786e9d3dd6478ebb0ce609556189 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 30 May 2026 13:26:45 +0200 Subject: [PATCH 19/72] More changes --- main.py | 84 ++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/main.py b/main.py index 85f36a89f..4822df26e 100644 --- a/main.py +++ b/main.py @@ -13,6 +13,7 @@ from qiskit.result.result import Result from mqt.bench.error_correction.shor_transpiler import ShorTranspiler +from mqt.bench.error_correction.steane_transpiler import SteaneTranspiler from tests.test_error_correction import insert_error from qiskit.circuit import CircuitInstruction, Gate from qiskit.circuit.library import XGate, HGate @@ -73,7 +74,7 @@ def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[Result, QuantumC transpiled circuit qc """ simulator = AerSimulator() - qc.measure_all() + #qc.measure_all() transpiled_circuit = qk.transpile(qc, simulator) job = simulator.run(transpiled_circuit, shots=shots) @@ -122,44 +123,67 @@ def compare_distributions(qc1: QuantumCircuit, qc2: QuantumCircuit, counts1: dic Simulates 2 circuits and computes the Hellinger Fidelity between their count distributions 1 = the same, 0 = no overlap - If code is set to either 'stean' or 'shor' circuit error's result will be interpreted logically + If code is set to either 'steane' or 'shor' circuit error's result will be interpreted logically """ from qiskit.quantum_info import hellinger_fidelity # to be removed due to decoding - if code1 in ['stean', 'shor']: + + + + print(counts1) + if code1 in ['steane', 'shor']: counts1 = condense_counts(qc1, counts1, code1) - if code2 in ['stean', 'shor']: + print(counts1) + #print(qc2) + print(counts2) + if code2 in ['steane', 'shor']: counts2 = condense_counts(qc2, counts2, code2) + print(counts2) fidelity = hellinger_fidelity(counts1, counts2) return fidelity -def parse_qubits(qc: qk.QuantumCircuit, physical_qubits: str): +def parse_qubits(qc: qk.QuantumCircuit, physical_qubits: str, code: str): """ Takes in a measurement in physical qubits and returns the corresponding logical measurement. Underlying circuit must use registers named 'qx' (x in int) for each logical qubit, with results in qx[0] """ # indices - import re - def is_q_integer(s: str) -> bool: - """ checks if s is of form 'qx' where x in int (e.g. 'q1', 'q23') """ - return bool(re.fullmatch(r'q\d+', s)) - + #import re + #def is_q_integer(s: str) -> bool: + # """ checks if s is of form 'qx' where x in int (e.g. 'q1', 'q23') """ + # return bool(re.fullmatch(r'q\d+', s)) + # data_indices = [] - for register in qc.qregs: - print(register) - if is_q_integer(register.name): - data_indices.append(qc.find_bit(register[-1]).index) # qiskit is little-endian + #for register in qc.qregs: + # #print(register) + # if is_q_integer(register.name): + # data_indices.append(qc.find_bit(register[0]).index) # qiskit is little-endian + # + + #length = 0 + #if code == 'steane': + length = 7 + # remove blanks from classical registers + physical_qubits = physical_qubits.replace(' ', '') + print(physical_qubits) + + for i in range(len(physical_qubits)): + if i % length == 0: + data_indices.append(i) print(data_indices) + + + # condesning logical_qubits = "" for index in data_indices: logical_qubits += physical_qubits[index] - print(logical_qubits) + #print(logical_qubits) return logical_qubits @@ -167,15 +191,15 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int], code: str) -> """ Takes in a result dict of a decoded physical measurement and returns logical measurements according to code. - Supports codes 'shor' and 'stean' + Supports codes 'shor' and 'steane' """ - assert code in ['shor', 'stean'], f'Unsupported error code in condense_counts(): {code}' + assert code in ['shor', 'steane'], f'Unsupported error code in condense_counts(): {code}' logical_counts = {} for physical_measurement, count in counts.items(): - logical_measurement = parse_qubits(qc, physical_measurement) + logical_measurement = parse_qubits(qc, physical_measurement, code) logical_counts[logical_measurement] = logical_counts.get(logical_measurement, 0) + count - print(logical_counts) + #print(logical_counts) return logical_counts @@ -198,16 +222,22 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int], code: str) -> algorithm = 'ghz' code = 'shor' # Initialize circuits - logical_circuit = qk.QuantumCircuit(circuit_size) - logical_circuit.h(0) + logical_circuit = QuantumCircuit(circuit_size, circuit_size) + logical_circuit.t(0) + logical_circuit.measure(0,0) + #logical_circuit.measure([0,1], [0,1]) #logical_circuit = benchmark_generation.get_benchmark( # benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding=code # ) error_corrected_circuit = logical_circuit.copy() - transpiler = ShorTranspiler(error_corrected_circuit, add_syndromes=True) + print(error_corrected_circuit.qregs) + #transpiler = ShorTranspiler(error_corrected_circuit, add_syndromes=True) + transpiler = SteaneTranspiler(logical_circuit, add_syndromes=False) transpiler.transpile() - transpiler.decode_qubits() + print(error_corrected_circuit.qregs) + + #transpiler.decode_qubits() error_corrected_circuit = transpiler.transpiled_qc error_induced_circuit = error_corrected_circuit.copy() #error_induced_circuit = insert_error(error_induced_circuit) @@ -239,9 +269,9 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int], code: str) -> corrected_counts = corrected_result.get_counts(error_corrected_circuit) induced_counts = induced_result.get_counts(error_induced_circuit) - print(logical_counts) - print(corrected_counts) - print(induced_counts) + #print(logical_counts) + #print(corrected_counts) + #print(induced_counts) - print(compare_distributions(logical_circuit, error_corrected_circuit, logical_counts, corrected_counts, 'none', 'shor')) + print(compare_distributions(logical_circuit, error_corrected_circuit, logical_counts, corrected_counts, 'none', 'steane')) print(compare_distributions(error_corrected_circuit, error_induced_circuit, corrected_counts, induced_counts,'shor', 'shor')) \ No newline at end of file From ba98eaf3dbecc76cd69b8eb60465f575cac54347 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 30 May 2026 14:38:51 +0200 Subject: [PATCH 20/72] improved circuit simulation and data processing --- main.py | 136 +++++++++++++++++++++++++------------------------------- 1 file changed, 60 insertions(+), 76 deletions(-) diff --git a/main.py b/main.py index 4822df26e..ce231e5ef 100644 --- a/main.py +++ b/main.py @@ -11,6 +11,7 @@ import qiskit as qk from qiskit import QuantumCircuit from qiskit.result.result import Result +from qiskit_aer.primitives import SamplerV2 from mqt.bench.error_correction.shor_transpiler import ShorTranspiler from mqt.bench.error_correction.steane_transpiler import SteaneTranspiler @@ -62,23 +63,28 @@ def errorcode_testing(alg: str = 'ghz', code: str = 'shor', qubits: int = 3): """ -def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[Result, QuantumCircuit]: +def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[dict, QuantumCircuit]: """ Simulates the circuit using AerSimulator. - Adds measurements to all qubits. + Adds measurements to all qubits, adds new classical registers for each. + Reads out ONLY those measurements and returns their counts Returns: - job.result() + counts of all quantum registers - transpiled circuit qc + qc with measure_all() """ - simulator = AerSimulator() - #qc.measure_all() - transpiled_circuit = qk.transpile(qc, simulator) - job = simulator.run(transpiled_circuit, shots=shots) - - return job.result(), transpiled_circuit + sampler = SamplerV2() + qc.measure_all() + job = sampler.run([qc], shots=shots) + result = job.result() + + # Grabbing only the desired outcomes + pub_result = result[0] + meas_bit_counts = pub_result.data.meas.get_counts() + + return meas_bit_counts, qc def insert_error(qc: QuantumCircuit, gate: Gate = XGate(), index: int | None = None) -> QuantumCircuit: """ @@ -126,20 +132,12 @@ def compare_distributions(qc1: QuantumCircuit, qc2: QuantumCircuit, counts1: dic If code is set to either 'steane' or 'shor' circuit error's result will be interpreted logically """ from qiskit.quantum_info import hellinger_fidelity - # to be removed due to decoding - - - print(counts1) if code1 in ['steane', 'shor']: counts1 = condense_counts(qc1, counts1, code1) - print(counts1) - #print(qc2) - print(counts2) if code2 in ['steane', 'shor']: counts2 = condense_counts(qc2, counts2, code2) - print(counts2) - + fidelity = hellinger_fidelity(counts1, counts2) return fidelity @@ -149,42 +147,31 @@ def parse_qubits(qc: qk.QuantumCircuit, physical_qubits: str, code: str): Underlying circuit must use registers named 'qx' (x in int) for each logical qubit, with results in qx[0] """ - # indices - #import re - #def is_q_integer(s: str) -> bool: - # """ checks if s is of form 'qx' where x in int (e.g. 'q1', 'q23') """ - # return bool(re.fullmatch(r'q\d+', s)) - # - data_indices = [] - #for register in qc.qregs: - # #print(register) - # if is_q_integer(register.name): - # data_indices.append(qc.find_bit(register[0]).index) # qiskit is little-endian - # - - #length = 0 - #if code == 'steane': - length = 7 - - # remove blanks from classical registers + # remove blanks caused by classical registers physical_qubits = physical_qubits.replace(' ', '') - print(physical_qubits) - - for i in range(len(physical_qubits)): - if i % length == 0: - data_indices.append(i) - print(data_indices) - + #print(physical_qubits) + # indices + import re + def is_q_integer(s: str) -> bool: + """ checks if s is of form 'qx' where x in int (e.g. 'q1', 'q23') """ + return bool(re.fullmatch(r'q\d+', s)) - - # condesning + data_indices = [] + if code == 'shor': + for register in qc.qregs: + if is_q_integer(register.name): + data_indices.append(qc.find_bit(register[-1]).index) # qiskit is little-endian + elif code == 'steane': + for i in range(len(physical_qubits)): + if i % 7 == 0: + data_indices.append(i) + + # condensing logical_qubits = "" for index in data_indices: logical_qubits += physical_qubits[index] - #print(logical_qubits) - return logical_qubits def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int], code: str) -> dict[str, int]: @@ -199,7 +186,6 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int], code: str) -> logical_measurement = parse_qubits(qc, physical_measurement, code) logical_counts[logical_measurement] = logical_counts.get(logical_measurement, 0) + count - #print(logical_counts) return logical_counts @@ -222,26 +208,31 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int], code: str) -> algorithm = 'ghz' code = 'shor' # Initialize circuits - logical_circuit = QuantumCircuit(circuit_size, circuit_size) - logical_circuit.t(0) - logical_circuit.measure(0,0) - #logical_circuit.measure([0,1], [0,1]) + t_circuit = QuantumCircuit(1) + t_circuit.t(0) - #logical_circuit = benchmark_generation.get_benchmark( - # benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding=code - # ) + xcx_circuit = QuantumCircuit(2) + xcx_circuit.x(0) + xcx_circuit.cx(0,1) + + logical_circuit = xcx_circuit + + logical_circuit = benchmark_generation.get_benchmark( + benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding=code + ) error_corrected_circuit = logical_circuit.copy() - print(error_corrected_circuit.qregs) - #transpiler = ShorTranspiler(error_corrected_circuit, add_syndromes=True) - transpiler = SteaneTranspiler(logical_circuit, add_syndromes=False) + code = 'shor' + shor_transpiler = ShorTranspiler(error_corrected_circuit, add_syndromes=False) + steane_transpiler = SteaneTranspiler(logical_circuit, add_syndromes=False) + if code == 'shor': + transpiler = shor_transpiler + transpiler = shor_transpiler transpiler.transpile() - print(error_corrected_circuit.qregs) - - #transpiler.decode_qubits() + transpiler.decode_qubits() error_corrected_circuit = transpiler.transpiled_qc + error_induced_circuit = error_corrected_circuit.copy() - #error_induced_circuit = insert_error(error_induced_circuit) - error_induced_circuit = insert_error(error_induced_circuit,gate=HGate()) + error_induced_circuit = insert_error(error_induced_circuit ,gate=HGate()) @@ -261,17 +252,10 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int], code: str) -> #print(check_equivalence(logical_circuit, error_corrected_circuit)) #print(check_equivalence(error_corrected_circuit, error_induced_circuit)) - logical_result, logical_circuit = run_circuit(logical_circuit) - corrected_result, error_corrected_circuit = run_circuit(error_corrected_circuit) - induced_result, error_induced_circuit = run_circuit(error_induced_circuit) - - logical_counts = logical_result.get_counts(logical_circuit) - corrected_counts = corrected_result.get_counts(error_corrected_circuit) - induced_counts = induced_result.get_counts(error_induced_circuit) + logical_counts, logical_circuit = run_circuit(logical_circuit) + corrected_counts, error_corrected_circuit = run_circuit(error_corrected_circuit) + induced_counts, error_induced_circuit = run_circuit(error_induced_circuit) - #print(logical_counts) - #print(corrected_counts) - #print(induced_counts) - print(compare_distributions(logical_circuit, error_corrected_circuit, logical_counts, corrected_counts, 'none', 'steane')) - print(compare_distributions(error_corrected_circuit, error_induced_circuit, corrected_counts, induced_counts,'shor', 'shor')) \ No newline at end of file + print(compare_distributions(logical_circuit, error_corrected_circuit, logical_counts, corrected_counts, 'none', code)) + print(compare_distributions(error_corrected_circuit, error_induced_circuit, corrected_counts, induced_counts, code, code)) \ No newline at end of file From bd58ad50cf3304c4fd26ccbb170971a750ce112f Mon Sep 17 00:00:00 2001 From: "LAPTOP-N4TQ80AC\\khusa" Date: Sat, 30 May 2026 14:45:31 +0200 Subject: [PATCH 21/72] =?UTF-8?q?=E2=9C=A8=20Adding=20Decode=20All?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 45 ++++++++++++++----- .../error_correction/steane_transpiler.py | 17 ++++++- 2 files changed, 48 insertions(+), 14 deletions(-) diff --git a/main.py b/main.py index 4822df26e..0c41d3d2b 100644 --- a/main.py +++ b/main.py @@ -74,11 +74,11 @@ def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[Result, QuantumC transpiled circuit qc """ simulator = AerSimulator() - #qc.measure_all() - transpiled_circuit = qk.transpile(qc, simulator) - job = simulator.run(transpiled_circuit, shots=shots) + qc.measure_all() + #transpiled_circuit = qk.transpile(qc, simulator) + job = simulator.run(qc, shots=shots) - return job.result(), transpiled_circuit + return job.result(), qc def insert_error(qc: QuantumCircuit, gate: Gate = XGate(), index: int | None = None) -> QuantumCircuit: """ @@ -187,6 +187,21 @@ def parse_qubits(qc: qk.QuantumCircuit, physical_qubits: str, code: str): return logical_qubits + +def get_logical_classical_indices(qc, name): + logical_cregs = sorted( + [cr for cr in qc.cregs if cr.name.startswith(name)], + key=lambda cr: int(cr.name.replace(name, "")) + ) + + indices = [] + + for cr in logical_cregs: + # assuming each logical register has size 1 + indices.append(qc.find_bit(cr[0]).index) + + return indices + def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int], code: str) -> dict[str, int]: """ Takes in a result dict of a decoded physical measurement and returns logical measurements according to code. @@ -194,12 +209,17 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int], code: str) -> Supports codes 'shor' and 'steane' """ assert code in ['shor', 'steane'], f'Unsupported error code in condense_counts(): {code}' - logical_counts = {} - for physical_measurement, count in counts.items(): - logical_measurement = parse_qubits(qc, physical_measurement, code) - logical_counts[logical_measurement] = logical_counts.get(logical_measurement, 0) + count + #logical_counts = {} + #for physical_measurement, count in counts.items(): + # logical_measurement = parse_qubits(qc, physical_measurement, code) + # logical_counts[logical_measurement] = logical_counts.get(logical_measurement, 0) + count #print(logical_counts) + + indices = get_logical_classical_indices(qc, "logical_meas") + from qiskit.result import marginal_counts + logical_counts = marginal_counts(counts, indices=indices) + return logical_counts @@ -218,13 +238,13 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int], code: str) -> #errorcode_testing() - circuit_size = 1 + circuit_size = 2 algorithm = 'ghz' code = 'shor' # Initialize circuits logical_circuit = QuantumCircuit(circuit_size, circuit_size) - logical_circuit.t(0) - logical_circuit.measure(0,0) + logical_circuit.x(0) + #logical_circuit.measure(0,0) #logical_circuit.measure([0,1], [0,1]) #logical_circuit = benchmark_generation.get_benchmark( @@ -235,6 +255,7 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int], code: str) -> #transpiler = ShorTranspiler(error_corrected_circuit, add_syndromes=True) transpiler = SteaneTranspiler(logical_circuit, add_syndromes=False) transpiler.transpile() + transpiler.decode_qubits() print(error_corrected_circuit.qregs) #transpiler.decode_qubits() @@ -274,4 +295,4 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int], code: str) -> #print(induced_counts) print(compare_distributions(logical_circuit, error_corrected_circuit, logical_counts, corrected_counts, 'none', 'steane')) - print(compare_distributions(error_corrected_circuit, error_induced_circuit, corrected_counts, induced_counts,'shor', 'shor')) \ No newline at end of file + print(compare_distributions(error_corrected_circuit, error_induced_circuit, corrected_counts, induced_counts,'steane', 'steane')) \ No newline at end of file diff --git a/src/mqt/bench/error_correction/steane_transpiler.py b/src/mqt/bench/error_correction/steane_transpiler.py index d3be31162..c2da25ec6 100644 --- a/src/mqt/bench/error_correction/steane_transpiler.py +++ b/src/mqt/bench/error_correction/steane_transpiler.py @@ -43,6 +43,7 @@ def __init__(self, original_circuit: QuantumCircuit, add_syndromes = True) -> No self.phase_flip_syndrome_measurements: list[ClassicalRegister] = [] self.logical_qubit_measurements: list[ClassicalRegister] = [] self.add_syndromes = add_syndromes + self.t_gate_count = 0 self.transpiled_qc = QuantumCircuit() @@ -62,7 +63,7 @@ def encode_qubits(self) -> None: phase_flip_syndrome_register = AncillaRegister(3, f"ps{logical_qubit_index}") bit_flip_measurement_register = ClassicalRegister(3, f"bsm{logical_qubit_index}") phase_flip_measurement_register = ClassicalRegister(3, f"psm{logical_qubit_index}") - logical_qubit_measurement_register = ClassicalRegister(1, f"m{logical_qubit_index}") + logical_qubit_measurement_register = ClassicalRegister(1, f"logical_meas{logical_qubit_index}") # t Gate? self.physical_data_registers.append(physical_data_register) @@ -96,6 +97,17 @@ def encode_qubits(self) -> None: ) self.transpiled_qc.barrier(label="Encoding") + def decode_qubits(self) -> None: + """Apply Steane 7-qubit decoding to each logical qubit.""" + self.transpiled_qc.barrier() + for logical_qubit_index in range(self.num_logical_qubits): + physical_data_register = self.physical_data_registers[logical_qubit_index] + self.transpiled_qc.compose(_get_seven_qubit_steane_code_decoding_circuit(), + qubits=physical_data_register[:], + inplace=True + ) + self.transpiled_qc.barrier() + def replace_gates(self) -> None: """Scan original circuit and replace gates with logical equivalents.""" gate_handlers = { @@ -196,7 +208,8 @@ def _handle_t(self, instruction: CircuitInstruction) -> None: logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) physical_data_register = self.physical_data_registers[logical_qubit_index] - t_ancilla_register = QuantumRegister(7) + t_ancilla_register = QuantumRegister(7, f"t{self.t_gate_count}") + self.t_gate_count += 1 t_test_register = ClassicalRegister(1) self.transpiled_qc.add_register(t_ancilla_register) From f3fbac18892974d0c148e0c7de192e627150aea1 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 30 May 2026 15:01:48 +0200 Subject: [PATCH 22/72] small modifications to structure --- tests/test_error_correction.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index 155095c07..90c15334e 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -82,9 +82,15 @@ def test_shor_transpiler_structure(): def test_steane_transpiler_structure(): assert False + +def test_errorcorrection_transpiler_equivalence(): + assert True + + + @pytest.mark.parametrize("code", ['shor']) #["steane", "shor"]) # double parametrize leads to crossproduct @pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate",]) # "qft"]) -def test_errorcorrection_transpiler_equivalence(code: str, algorithm: str): +def test_errorcorrection_transpiler_correctness(code: str, algorithm: str): """ Ensures the transpiler creates error-corrected circuits which produce the same result as the orinigal logical circuit. Afterwards an error is introduced and the test checks, whether it is corrected. From 997d7d15f2e11d20fced8135112ad2fb2c89d603 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 30 May 2026 16:31:48 +0200 Subject: [PATCH 23/72] streamlined testing utilities --- main.py | 98 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/main.py b/main.py index d5cdd6bc9..d41041f23 100644 --- a/main.py +++ b/main.py @@ -17,7 +17,8 @@ from mqt.bench.error_correction.steane_transpiler import SteaneTranspiler from tests.test_error_correction import insert_error from qiskit.circuit import CircuitInstruction, Gate -from qiskit.circuit.library import XGate, HGate +from qiskit.circuit.library import XGate, HGate, ZGate +from qiskit.quantum_info import hellinger_fidelity # uv requirements to be added: mqt.qcec, qiskit_aer @@ -83,6 +84,9 @@ def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[dict, QuantumCir # Grabbing only the desired outcomes pub_result = result[0] meas_bit_counts = pub_result.data.meas.get_counts() + # outputs reversed bitstrings, we just reverse them right back, + # so their indices align with the qubit indices + meas_bit_counts = {k[::-1]: v for k, v in meas_bit_counts.items()} return meas_bit_counts, qc @@ -120,7 +124,6 @@ def check_equivalence(qc1: qk.QuantumCircuit, qc2: qk.QuantumCircuit) -> bool: EC.equivalent_up_to_global_phase, EC.probably_equivalent ] - equivalent = verification_results.equivalence in accepted_equivalencies return equivalent @@ -131,17 +134,21 @@ def compare_distributions(qc1: QuantumCircuit, qc2: QuantumCircuit, counts1: dic If code is set to either 'steane' or 'shor' circuit error's result will be interpreted logically """ - from qiskit.quantum_info import hellinger_fidelity - + + #print(counts1) if code1 in ['steane', 'shor']: - counts1 = condense_counts(qc1, counts1, code1) + counts1 = condense_counts(qc1, counts1) + #print(counts1) + + #print(counts2) if code2 in ['steane', 'shor']: - counts2 = condense_counts(qc2, counts2, code2) + counts2 = condense_counts(qc2, counts2) + #print(counts2) fidelity = hellinger_fidelity(counts1, counts2) return fidelity -def parse_qubits(qc: qk.QuantumCircuit, physical_qubits: str, code: str): +def parse_qubits(qc: qk.QuantumCircuit, physical_qubits: str): """ Takes in a measurement in physical qubits and returns the corresponding logical measurement. @@ -149,7 +156,6 @@ def parse_qubits(qc: qk.QuantumCircuit, physical_qubits: str, code: str): """ # remove blanks caused by classical registers physical_qubits = physical_qubits.replace(' ', '') - #print(physical_qubits) # indices import re @@ -158,15 +164,10 @@ def is_q_integer(s: str) -> bool: return bool(re.fullmatch(r'q\d+', s)) data_indices = [] - if code == 'shor': - for register in qc.qregs: - if is_q_integer(register.name): - data_indices.append(qc.find_bit(register[-1]).index) # qiskit is little-endian - elif code == 'steane': - for i in range(len(physical_qubits)): - if i % 7 == 0: - data_indices.append(i) - + for register in qc.qregs: + if is_q_integer(register.name): + data_indices.append(qc.find_bit(register[0]).index) + # condensing logical_qubits = "" for index in data_indices: @@ -175,21 +176,21 @@ def is_q_integer(s: str) -> bool: return logical_qubits -def get_logical_classical_indices(qc, name): - logical_cregs = sorted( - [cr for cr in qc.cregs if cr.name.startswith(name)], - key=lambda cr: int(cr.name.replace(name, "")) - ) - - indices = [] - - for cr in logical_cregs: - # assuming each logical register has size 1 - indices.append(qc.find_bit(cr[0]).index) - - return indices +#def get_logical_classical_indices(qc, name): +# logical_cregs = sorted( +# [cr for cr in qc.cregs if cr.name.startswith(name)], +# key=lambda cr: int(cr.name.replace(name, "")) +# ) +# +# indices = [] +# +# for cr in logical_cregs: +# # assuming each logical register has size 1 +# indices.append(qc.find_bit(cr[0]).index) +# +# return indices -def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int], code: str) -> dict[str, int]: +def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, int]: """ Takes in a result dict of a decoded physical measurement and returns logical measurements according to code. @@ -198,13 +199,8 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int], code: str) -> assert code in ['shor', 'steane'], f'Unsupported error code in condense_counts(): {code}' logical_counts = {} for physical_measurement, count in counts.items(): - logical_measurement = parse_qubits(qc, physical_measurement, code) + logical_measurement = parse_qubits(qc, physical_measurement) logical_counts[logical_measurement] = logical_counts.get(logical_measurement, 0) + count - - - #indices = get_logical_classical_indices(qc, "logical_meas") - #from qiskit.result import marginal_counts - #logical_counts = marginal_counts(counts, indices=indices) return logical_counts @@ -222,42 +218,46 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int], code: str) -> #print(qc) #print(" _________ ") - #errorcode_testing() - circuit_size = 2 algorithm = 'ghz' code = 'shor' # Initialize circuits t_circuit = QuantumCircuit(1) + t_circuit.h(0) t_circuit.t(0) + t_circuit.h(0) xcx_circuit = QuantumCircuit(2) xcx_circuit.x(0) xcx_circuit.cx(0,1) - logical_circuit = xcx_circuit + h_circuit = QuantumCircuit(1) + h_circuit.h(0) + h_circuit.h(0) + + logical_circuit = t_circuit - logical_circuit = benchmark_generation.get_benchmark( - benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding=code - ) + #logical_circuit = benchmark_generation.get_benchmark( + # benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding=code + # ) error_corrected_circuit = logical_circuit.copy() - code = 'shor' + code = 'steane' shor_transpiler = ShorTranspiler(error_corrected_circuit, add_syndromes=False) steane_transpiler = SteaneTranspiler(logical_circuit, add_syndromes=False) if code == 'shor': transpiler = shor_transpiler - transpiler = shor_transpiler + else: + transpiler = steane_transpiler transpiler.transpile() transpiler.decode_qubits() error_corrected_circuit = transpiler.transpiled_qc error_induced_circuit = error_corrected_circuit.copy() - error_induced_circuit = insert_error(error_induced_circuit ,gate=HGate()) - + # this is for inserting phase flip in steane after the first Hadamard + #error_induced_circuit = insert_error(error_induced_circuit ,gate=ZGate(), index=16) + error_induced_circuit = insert_error(error_induced_circuit ,gate=XGate()) - - #print(" __________________________________________________________________________________________ ") #print('Logical Circuit:') #print(logical_circuit) From 1e3b44ceb3ebb3f3261f1c93c886994596c3911b Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 30 May 2026 16:42:30 +0200 Subject: [PATCH 24/72] more changes --- main.py | 15 ++- tests/test_error_correction.py | 167 +++++++++++++++++---------------- 2 files changed, 95 insertions(+), 87 deletions(-) diff --git a/main.py b/main.py index d41041f23..96d313c8b 100644 --- a/main.py +++ b/main.py @@ -192,11 +192,10 @@ def is_q_integer(s: str) -> bool: def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, int]: """ - Takes in a result dict of a decoded physical measurement and returns logical measurements according to code. - - Supports codes 'shor' and 'steane' + Takes in a result dict of a decoded physical measurement and returns logical measurements + Requires decode to place the result in the first qubit of each register named 'qx', with x an integer (e.g. 'q2') """ - assert code in ['shor', 'steane'], f'Unsupported error code in condense_counts(): {code}' + #assert code in ['shor', 'steane'], f'Unsupported error code in condense_counts(): {code}' logical_counts = {} for physical_measurement, count in counts.items(): logical_measurement = parse_qubits(qc, physical_measurement) @@ -228,14 +227,14 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, i t_circuit.h(0) xcx_circuit = QuantumCircuit(2) - xcx_circuit.x(0) + #xcx_circuit.x(0) xcx_circuit.cx(0,1) h_circuit = QuantumCircuit(1) h_circuit.h(0) - h_circuit.h(0) + #h_circuit.h(0) - logical_circuit = t_circuit + logical_circuit = xcx_circuit #logical_circuit = benchmark_generation.get_benchmark( # benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding=code @@ -269,7 +268,7 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, i #print(error_induced_circuit) #print(" __________________________________________________________________________________________ ") - #print(check_equivalence(logical_circuit, error_corrected_circuit)) + print(check_equivalence(logical_circuit, error_corrected_circuit)) #print(check_equivalence(error_corrected_circuit, error_induced_circuit)) logical_counts, logical_circuit = run_circuit(logical_circuit) diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index 90c15334e..19e6c000f 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -33,14 +33,16 @@ import pytest from pathlib import Path from qiskit import QuantumCircuit -from qiskit.quantum_info import state_fidelity, Statevector +from qiskit.quantum_info import hellinger_fidelity, state_fidelity, Statevector from mqt.bench.error_correction.shor_transpiler import ShorTranspiler import mqt.bench.benchmark_generation as benchmark_generation from qiskit_aer import AerSimulator # update uv requirements? import qiskit as qk from qiskit.circuit import CircuitInstruction, Gate -from qiskit.circuit.library import XGate +from qiskit.circuit.library import CXGate, HGate, SGate, XGate, ZGate +from mqt.bench.error_correction.steane_transpiler import SteaneTranspiler +from qiskit_aer.primitives import SamplerV2 # for: [steane, shor] @@ -82,13 +84,26 @@ def test_shor_transpiler_structure(): def test_steane_transpiler_structure(): assert False +@pytest.mark.parametrize("code", ['shor', "steane"]) +@pytest.mark.parametrize("gate", [XGate(), ZGate(), HGate(), SGate(), CXGate()]) +def test_errorcorrection_transpiler_gate_equivalence(code:str, gate: Gate): + num_qubits = gate.num_qubits + logical_circuit = QuantumCircuit(num_qubits) + logical_circuit.append(gate, qargs=list(range(num_qubits))) + + error_corrected_circuit = logical_circuit.copy() + if code == 'shor': + transpiler = ShorTranspiler(error_corrected_circuit, add_syndromes=False) + else: + transpiler = SteaneTranspiler(logical_circuit, add_syndromes=False) + transpiler.transpile() + transpiler.decode_qubits() + error_corrected_circuit = transpiler.transpiled_qc -def test_errorcorrection_transpiler_equivalence(): - assert True - + assert check_equivalence(logical_circuit, error_corrected_circuit), f'Transpiler {code} does convert Gate {gate.name} to its logical equivalent' -@pytest.mark.parametrize("code", ['shor']) #["steane", "shor"]) # double parametrize leads to crossproduct +@pytest.mark.parametrize("code", ["shor", "steane"]) # double parametrize leads to crossproduct @pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate",]) # "qft"]) def test_errorcorrection_transpiler_correctness(code: str, algorithm: str): """ @@ -149,72 +164,48 @@ def insert_error(qc: QuantumCircuit, gate: Gate = XGate(), index: int | None = N -def errorcode_testing(alg: str = 'ghz', code: str = 'shor', qubits: int = 3): - assert qubits >= 3 - - base_circuit = benchmark_generation.get_benchmark( - benchmark=alg, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=qubits, encoding=code - ) - error_circuit = base_circuit.copy(name='error_circuit') - error_circuit = insert_error_gate(error_circuit) - uncorrected_circuit = benchmark_generation.get_benchmark( - benchmark=alg, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=qubits - ) - - - ### Equivalence checking - equivalent = check_equivalence(base_circuit, error_circuit) - #assert equivalent, 'Insertion of an error (flipped qubit) has lead to a new, no longer equivalent circuit' - print(f'Circuits are equivalent: {equivalent}') - - - - ### Simulated probabilistic similarity Base vs. Error-Inserted - error_fidelity = compare_distributions(base_circuit, error_circuit) - threshold = 0.95 # arbitrary guess - #assert fidelity > threshold, f'Simulated Hellinger Fidelity between base and error circuit is too low. Measured: {fidelity}, >Expected: {threshold}' - print(f'Hellinger Fidelity with error: {error_fidelity}') - - ### Simulated probabilistic similarity Uncorrected vs. Error-Inserted - # TODO: put in error corrected circuit - #### Example for condensing qubits - example = {'00000001111111': 3, '10101011111111': 1, '11111111010101': 2, '10101011111110' : 7} - #print(condense_counts(example,'stean')) - - """ - uncorrected_fidelity = compare_distributions(uncorrected_circuit, error_circuit, code='shor') - threshold = threshold # arbitrary guess - #assert fidelity > threshold, f'Simulated Hellinger Fidelity between uncorrected and error circuit is too low. Measured: {uncorrected_fidelity}, Expected: >{threshold}' - print(f'Hellinger Fidelity with error: {uncorrected_fidelity}') - """ - - -def run_circuit(qc: qk.QuantumCircuit, shots: int = 1024) -> tuple[dict, QuantumCircuit]: +def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[dict, QuantumCircuit]: """ Simulates the circuit using AerSimulator. - Adds measurements to all qubits. + Adds measurements to all qubits, adds new classical registers for each. + Reads out ONLY those measurements and returns their counts Returns: - job.result() + counts of all quantum registers - transpiled circuit qc + qc with measure_all() """ - simulator = AerSimulator() + sampler = SamplerV2() qc.measure_all() - transpiled_circuit = qk.transpile(qc, simulator) - job = simulator.run(transpiled_circuit, shots=shots) - - return job.result(), transpiled_circuit + job = sampler.run([qc], shots=shots) + result = job.result() + + # Grabbing only the desired outcomes + pub_result = result[0] + meas_bit_counts = pub_result.data.meas.get_counts() + # outputs reversed bitstrings, we just reverse them right back, + # so their indices align with the qubit indices + meas_bit_counts = {k[::-1]: v for k, v in meas_bit_counts.items()} -def insert_error_gate(qc: qk.QuantumCircuit, gate: Gate = XGate(), index: int | None = None) -> qk.QuantumCircuit: + return meas_bit_counts, qc + +def insert_error(qc: QuantumCircuit, gate: Gate = XGate(), index: int | None = None) -> QuantumCircuit: """ Adds the specified gate at the beginning of the circuit - Flips the first qubit after the first barrier by default + Flips the first qubit right after the first barrier by default """ assert qc.num_qubits >= gate.num_qubits, f'Quantum Circuit has not enough qubits to accomodate gate {gate.name}' - assert index >= 0, f'Index must be >= 0, Index provided: {index}' + assert index is None or index >= 0, f'Index must be >= 0, Index provided: {index}' + + # Finds the first barrier + if index is None: + for i, instruction in enumerate(qc.data): + if instruction.operation.name == "barrier": + index = i + 1 + break + # Insert the error gate qubits = qc.qubits[:gate.num_qubits] qc.data.insert(index, CircuitInstruction(gate, qubits)) @@ -233,25 +224,27 @@ def check_equivalence(qc1: qk.QuantumCircuit, qc2: qk.QuantumCircuit) -> bool: EC.equivalent_up_to_global_phase, EC.probably_equivalent ] - equivalent = verification_results.equivalence in accepted_equivalencies return equivalent -def compare_distributions(qc1: QuantumCircuit, qc2: QuantumCircuit, counts1: dict, counts2: dict, shots:int = 1024, code1: str = 'None', code2: str = 'None') -> float: +def compare_distributions(qc1: QuantumCircuit, qc2: QuantumCircuit, counts1: dict, counts2: dict, code1: str = 'None', code2: str = 'None') -> float: """ Simulates 2 circuits and computes the Hellinger Fidelity between their count distributions 1 = the same, 0 = no overlap - If code is set to either 'stean' or 'shor' circuit error's result will be interpreted logically + If code is set to either 'steane' or 'shor' circuit error's result will be interpreted logically """ - from qiskit.quantum_info import hellinger_fidelity + + #print(counts1) + if code1 in ['steane', 'shor']: + counts1 = condense_counts(qc1, counts1) + #print(counts1) + + #print(counts2) + if code2 in ['steane', 'shor']: + counts2 = condense_counts(qc2, counts2) + #print(counts2) - # to be removed due to decoding - if code1 in ['stean', 'shor']: - counts1 = condense_counts(qc1, counts1, code1) - if code2 in ['stean', 'shor']: - counts2 = condense_counts(qc2, counts2, code2) - fidelity = hellinger_fidelity(counts1, counts2) return fidelity @@ -261,38 +254,54 @@ def parse_qubits(qc: qk.QuantumCircuit, physical_qubits: str): Underlying circuit must use registers named 'qx' (x in int) for each logical qubit, with results in qx[0] """ + # remove blanks caused by classical registers + physical_qubits = physical_qubits.replace(' ', '') + # indices import re def is_q_integer(s: str) -> bool: """ checks if s is of form 'qx' where x in int (e.g. 'q1', 'q23') """ return bool(re.fullmatch(r'q\d+', s)) - + data_indices = [] for register in qc.qregs: if is_q_integer(register.name): - data_indices.append(qc.find_bit(register[0]).index) - - # condesning + data_indices.append(qc.find_bit(register[0]).index) + + # condensing logical_qubits = "" for index in data_indices: logical_qubits += physical_qubits[index] return logical_qubits -def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int], code: str) -> dict[str, int]: - """ - Takes in a result dict of a decoded physical measurement and returns logical measurements according to code. - Supports codes 'shor' and 'stean' +#def get_logical_classical_indices(qc, name): +# logical_cregs = sorted( +# [cr for cr in qc.cregs if cr.name.startswith(name)], +# key=lambda cr: int(cr.name.replace(name, "")) +# ) +# +# indices = [] +# +# for cr in logical_cregs: +# # assuming each logical register has size 1 +# indices.append(qc.find_bit(cr[0]).index) +# +# return indices + +def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, int]: + """ + Takes in a result dict of a decoded physical measurement and returns logical measurements + Requires decode to place the result in the first qubit of each register named 'qx', with x an integer (e.g. 'q2') """ - assert code in ['shor', 'stean'], f'Unsupported error code in condense_counts(): {code}' + #assert code in ['shor', 'steane'], f'Unsupported error code in condense_counts(): {code}' logical_counts = {} for physical_measurement, count in counts.items(): logical_measurement = parse_qubits(qc, physical_measurement) logical_counts[logical_measurement] = logical_counts.get(logical_measurement, 0) + count - - return logical_counts + return logical_counts ############################################################################################################################################ From 4a26c64ece44a06c9cbcabd1e8e51ae8e50ba4eb Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 30 May 2026 17:32:47 +0200 Subject: [PATCH 25/72] Improved testing methodologies --- main.py | 4 +- tests/test_error_correction.py | 541 +++------------------------------ 2 files changed, 48 insertions(+), 497 deletions(-) diff --git a/main.py b/main.py index 96d313c8b..ab979d1d9 100644 --- a/main.py +++ b/main.py @@ -231,10 +231,10 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, i xcx_circuit.cx(0,1) h_circuit = QuantumCircuit(1) - h_circuit.h(0) + h_circuit.z(0) #h_circuit.h(0) - logical_circuit = xcx_circuit + logical_circuit = h_circuit #logical_circuit = benchmark_generation.get_benchmark( # benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding=code diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index 19e6c000f..505f60b6b 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -45,48 +45,13 @@ from qiskit_aer.primitives import SamplerV2 -# for: [steane, shor] -# for: [x,h,z] -def test_shor_transpiler_structure(): - """ - Ensures the ShorTranspiler translates a basic circuit into a reasonable structure. - Doesn't check for equivalence or correctness. - """ - qc = QuantumCircuit(2, 1) - qc.x(0) - qc.z(1) - - transpiler = ShorTranspiler(qc) - transpiler.transpile() - transpiler.decode_qubits() - transpiled_qc = transpiler.transpiled_qc - - - - - # 2 original qubits * 9 data qubits = 18 data qubits - # 2 original qubits * 8 ancilla qubits = 16 ancilla qubits - # Total qubits = 34 - assert transpiled_qc.num_qubits == 34 - - # 2 original qubits * 8 classical bits = 16 syndrome bits - # 1 measurement * 9 bits = 9 measurement bits - # Total clbits = 25 - assert transpiled_qc.num_clbits == 25 - ##wrong expectations in the test it should check only non error correction gate mapping - ops = [inst.operation.name for inst in transpiled_qc.data] - assert "h" in ops - assert "cx" in ops - assert "measure" in ops - -@pytest.mark.skip() -def test_steane_transpiler_structure(): - assert False - -@pytest.mark.parametrize("code", ['shor', "steane"]) -@pytest.mark.parametrize("gate", [XGate(), ZGate(), HGate(), SGate(), CXGate()]) +@pytest.mark.parametrize("code", ["steane", "shor"]) +@pytest.mark.parametrize("gate", [XGate(), ZGate(), HGate(), SGate()]) def test_errorcorrection_transpiler_gate_equivalence(code:str, gate: Gate): + if gate.name == 's' and code == "shor": + # this SGate entails non-unitary elements and can therefore not be evaluated properly + return num_qubits = gate.num_qubits logical_circuit = QuantumCircuit(num_qubits) logical_circuit.append(gate, qargs=list(range(num_qubits))) @@ -95,7 +60,7 @@ def test_errorcorrection_transpiler_gate_equivalence(code:str, gate: Gate): if code == 'shor': transpiler = ShorTranspiler(error_corrected_circuit, add_syndromes=False) else: - transpiler = SteaneTranspiler(logical_circuit, add_syndromes=False) + transpiler = SteaneTranspiler(error_corrected_circuit, add_syndromes=False) transpiler.transpile() transpiler.decode_qubits() error_corrected_circuit = transpiler.transpiled_qc @@ -103,8 +68,42 @@ def test_errorcorrection_transpiler_gate_equivalence(code:str, gate: Gate): assert check_equivalence(logical_circuit, error_corrected_circuit), f'Transpiler {code} does convert Gate {gate.name} to its logical equivalent' +@pytest.mark.parametrize("code", ["steane", "shor"]) +@pytest.mark.parametrize("gate", [XGate(), ZGate(), HGate(), SGate()]) +def test_errorcorrection_transpiler_gate_correctness(code: str, gate: Gate): + num_qubits = gate.num_qubits + logical_circuit = QuantumCircuit(num_qubits) + logical_circuit.append(gate, qargs=list(range(num_qubits))) + error_corrected_circuit = logical_circuit.copy() + if code == "shor": + transpiler = ShorTranspiler(error_corrected_circuit, add_syndromes=True) + else: + transpiler = SteaneTranspiler(logical_circuit, add_syndromes=True) + transpiler.transpile() + transpiler.decode_qubits() + error_corrected_circuit = transpiler.transpiled_qc + + error_induced_circuit = error_corrected_circuit.copy() + # this is for inserting phase flip in steane after the first Hadamard + #error_induced_circuit = insert_error(error_induced_circuit ,gate=ZGate(), index=16) + error_induced_circuit = insert_error(error_induced_circuit ,gate=XGate()) + + + logical_counts, logical_circuit = run_circuit(logical_circuit) + corrected_counts, error_corrected_circuit = run_circuit(error_corrected_circuit) + induced_counts, error_induced_circuit = run_circuit(error_induced_circuit) + + logical_corrected_fidelity = compare_distributions(logical_circuit, error_corrected_circuit, logical_counts, corrected_counts, "none", code) + corrected_induced_fidelity = compare_distributions(error_corrected_circuit, error_induced_circuit, corrected_counts, induced_counts, code, code) + + assert logical_corrected_fidelity >= 0.99, f"Error corrected circuit created by {code} transpiler for Gate {gate.name} does match its logical circuit well enough." + assert corrected_induced_fidelity >= 0.99, f"Error corrected circuit created by {code} transpiler for Gate {gate.name} does not correct the bitflip well enough." + + + @pytest.mark.parametrize("code", ["shor", "steane"]) # double parametrize leads to crossproduct @pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate",]) # "qft"]) +@pytest.mark.skip def test_errorcorrection_transpiler_correctness(code: str, algorithm: str): """ Ensures the transpiler creates error-corrected circuits which produce the same result as the orinigal logical circuit. @@ -138,8 +137,8 @@ def insert_error(qc: QuantumCircuit, gate: Gate = XGate(), index: int | None = N Adds the specified gate at the beginning of the circuit Flips the first qubit right after the first barrier by default """ - assert qc.num_qubits >= gate.num_qubits, f'Quantum Circuit has not enough qubits to accomodate gate {gate.name}' - assert index is None or index >= 0, f'Index must be >= 0, Index provided: {index}' + assert qc.num_qubits >= gate.num_qubits, f"Quantum Circuit has not enough qubits to accomodate gate {gate.name}" + assert index is None or index >= 0, f"Index must be >= 0, Index provided: {index}" # Finds the first barrier if index is None: @@ -150,7 +149,10 @@ def insert_error(qc: QuantumCircuit, gate: Gate = XGate(), index: int | None = N # Insert the error gate qubits = qc.qubits[:gate.num_qubits] - qc.data.insert(index, CircuitInstruction(gate, qubits)) + if index is not None: + qc.data.insert(index, CircuitInstruction(gate, qubits)) + else: + raise Exception("Please provide either an index or a circuit with a barrier to insert an error into") return qc @@ -303,454 +305,3 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, i return logical_counts - -############################################################################################################################################ -############################################################################################################################################ -############################################################################################################################################ -############################################################################################################################################ -############################################################################################################################################ - - -"""Equivalence tests for Shor Transpiler gates.""" - - - -def verify_gate_equivalence(gate_name: str, num_qubits: int) -> None: - """Verify that a transpiled gate is mathematically equivalent to the logical gate. - - Args: - gate_name: The name of the gate to test ('h', 'x', 'z', 's', 't', 'cx', 'cz'). - num_qubits: The number of qubits the gate acts on. - """ - # Create the logical circuit and initialize it in a non-trivial state (|+> state) - qc_logical = QuantumCircuit(num_qubits) - # Apply the gate - if gate_name == "h": - qc_logical.h(0) - elif gate_name == "x": - qc_logical.x(0) - elif gate_name == "z": - qc_logical.z(0) - elif gate_name == "s": - qc_logical.s(0) - elif gate_name == "t": - qc_logical.t(0) - elif gate_name == "cx": - qc_logical.cx(0, 1) - elif gate_name == "cz": - qc_logical.cz(0, 1) - else: - msg = f"Unknown gate {gate_name}" - raise ValueError(msg) - - # Get the expected density matrix - qc_logical_sim = qc_logical.copy() - # expected logical state - expected_sv_init = Statevector.from_instruction(qc_logical) - - # Transpile the circuit - # We set add_syndromes=False to prevent statevector simulation from blowing up in memory - transpiler = ShorTranspiler(qc_logical, add_syndromes=False) - transpiled_qc = transpiler.transpile() - - drawing = transpiled_qc.draw(fold=-1) - print(f"\n--- Transpiled Circuit for {gate_name.upper()} ---") - print(drawing) - - # Save to file to ensure it can be viewed regardless of pytest-xdist capturing stdout - output_dir = Path("tests/circuit_drawings") - output_dir.mkdir(parents=True, exist_ok=True) - with open(output_dir / f"{gate_name}_transpiled.txt", "w", encoding="utf-8") as f: - f.write(f"number of qubits {num_qubits}\n") - f.write(f"--- Transpiled Circuit for {gate_name.upper()} ---\n\n") - f.write(str(drawing) + "\n") - # Apply decoding so the logical state collapses back to the first physical qubit of each block - transpiler.decode_qubits() - - transpiled_qc.save_statevector() - - sim = AerSimulator(method="statevector") - result_transpiled = sim.run(transpiled_qc).result() - assert result_transpiled.success, f"Simulation failed: {result_transpiled.status}" - actual_sv = result_transpiled.get_statevector() - - # Extract the density matrix of the physical qubits holding the logical state - # These are the 0-th qubits of each physical data register - logical_qubits_physical = [ - transpiled_qc.find_bit(transpiler.physical_data_registers[i][0]).index - for i in range(num_qubits) - ] - - from qiskit.quantum_info import partial_trace - all_qubits = list(range(transpiled_qc.num_qubits)) - trace_qubits = [q for q in all_qubits if q not in logical_qubits_physical] - - actual_rho = partial_trace(actual_sv, trace_qubits) - - from qiskit.quantum_info import DensityMatrix - expected_rho_init = DensityMatrix(expected_sv_init) - - try: - actual_sv_reduced = actual_rho.to_statevector() - except Exception: - actual_sv_reduced = None - - # Compare the density matrices - fidelity = state_fidelity(expected_sv_init, actual_rho) - - # Save the resulting density matrices and state vectors to the text file for visual inspection - with open(output_dir / f"{gate_name}_transpiled.txt", "a", encoding="utf-8") as f: - f.write("\n\n=== LOGICAL EXPECTED DENSITY MATRIX ===\n") - f.write(str(np.round(expected_rho_init.data, 3)) + "\n") - f.write("\n=== ACTUAL TRANSPILED DENSITY MATRIX (REDUCED) ===\n") - f.write(str(np.round(actual_rho.data, 3)) + "\n") - - if actual_sv_reduced is not None: - f.write("\n\n=== LOGICAL EXPECTED STATE VECTOR ===\n") - f.write(str(np.round(expected_sv_init.data, 3)) + "\n") - f.write("\n=== ACTUAL TRANSPILED STATE VECTOR (REDUCED) ===\n") - f.write(str(np.round(actual_sv_reduced.data, 3)) + "\n") - - f.write(f"\nSTATE FIDELITY: {fidelity:.6f}\n") - - assert fidelity > 0.999, f"Fidelity too low: {fidelity}" - - -def test_h_equivalence() -> None: - """Test equivalence for logical H gate.""" - verify_gate_equivalence("h", 1) - - -def test_x_equivalence() -> None: - """Test equivalence for logical X gate.""" - verify_gate_equivalence("x", 1) - - -def test_z_equivalence() -> None: - """Test equivalence for logical Z gate.""" - verify_gate_equivalence("z", 1) - - -def test_s_equivalence() -> None: - """Test equivalence for logical S gate.""" - verify_gate_equivalence("s", 1) - - -@pytest.mark.skip(reason="Slow test, takes >10 mins due to 27 qubit simulation") -def test_t_equivalence() -> None: - """Test equivalence for logical T gate.""" - verify_gate_equivalence("t", 1) - - -def test_cx_equivalence() -> None: - """Test equivalence for logical CX gate.""" - verify_gate_equivalence("cx", 2) - -def test_cz_equivalence() -> None: - """Test equivalence for logical CZ gate.""" - verify_gate_equivalence("cz", 2) - - -############################################################################################################################################ -############################################################################################################################################ -############################################################################################################################################ -############################################################################################################################################ -############################################################################################################################################ - - -"""Tests for Shor Transpiler.""" - -#from __future__ import annotations - -import pytest -from qiskit import QuantumCircuit - -from mqt.bench.error_correction.shor_transpiler import ShorTranspiler - -# this needs mpre tests -def test_shor_transpiler() -> None: - """Test that ShorTranspiler successfully transpiles a basic circuit.""" - qc = QuantumCircuit(2, 1) - qc.x(0) - qc.z(1) - qc.measure(1, 0) - - print("\n--- Logical Circuit ---") - print(qc.draw(fold=-1)) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - print("\n--- Transpiled Circuit ---") - print(transpiled_qc) - - # 2 original qubits * 9 data qubits = 18 data qubits - # 2 original qubits * 8 ancilla qubits = 16 ancilla qubits - # Total qubits = 34 - assert transpiled_qc.num_qubits == 34 - - # 2 original qubits * 8 classical bits = 16 syndrome bits - # 1 measurement * 9 bits = 9 measurement bits - # Total clbits = 25 - assert transpiled_qc.num_clbits == 25 - ##wrong expectations in the test it should check only non error correction gate mapping - ops = [inst.operation.name for inst in transpiled_qc.data] - assert "h" in ops - assert "cx" in ops - assert "measure" in ops - - -def test_shor_transpiler_unsupported_gate() -> None: - """Test that unsupported gates raise NotImplementedError.""" - qc = QuantumCircuit(1) - qc.rx(0, 0) - - transpiler = ShorTranspiler(qc) - with pytest.raises(NotImplementedError, match=r"Gate rx is not supported by ShorTranspiler\."): - transpiler.transpile() - - -def test_shor_transpiler_s_gate_structure() -> None: - """Test that the S gate teleportation circuit has the correct structure. - - Verifies that: - - A magic state ancilla register (9 qubits) is allocated. - - The teleportation measurement register (1 classical bit) is allocated. - - The circuit contains the expected gates (h, s, cx, measure). - """ - qc = QuantumCircuit(1) - qc.s(0) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - # Original: 9 data + 6 bit-flip ancilla + 2 phase-flip ancilla = 17 - # S gate adds: 9 magic data = 9 - # Total = 26 - assert transpiled_qc.num_qubits == 26 - - # Original: 6 bf meas + 2 pf meas = 8 - # S gate adds: 1 teleport meas = 1 - # Total = 9 - assert transpiled_qc.num_clbits == 9 - - ops = [inst.operation.name for inst in transpiled_qc.data] - - # Magic state prep uses h and p on the ancilla qubit - assert "p" in ops - assert "h" in ops - - # Teleportation uses cx, measure - assert "cx" in ops - assert "measure" in ops - - # Conditional correction uses if_else - assert "if_else" in ops - - # Verify the magic state register exists - reg_names = [reg.name for reg in transpiled_qc.qregs] - assert "ms0" in reg_names - - # Verify teleportation measurement register exists - creg_names = [reg.name for reg in transpiled_qc.cregs] - assert "tmeas0" in creg_names - - -def test_shor_transpiler_s_gate_followed_by_other_gates() -> None: - """Test that gates applied after the S gate target the correct register. - - After S gate teleportation, subsequent gates should operate on the original - data register since the teleportation gadget doesn't swap the pointers. - """ - qc = QuantumCircuit(1, 1) - qc.s(0) - qc.z(0) - qc.measure(0, 0) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - # Circuit should compile and run without errors - assert transpiled_qc.num_qubits > 0 - assert transpiled_qc.num_clbits > 0 - - ops = [inst.operation.name for inst in transpiled_qc.data] - assert "p" in ops # Magic state prep (phase) - assert "x" in ops # Logical Z uses physical X - assert "measure" in ops - - -def test_shor_transpiler_multiple_s_gates() -> None: - """Test that multiple S gates each allocate independent ancilla blocks. - - Two consecutive S gates should produce two independent magic state - ancilla blocks (ms0 and ms1). - """ - qc = QuantumCircuit(1) - qc.s(0) - qc.s(0) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - reg_names = [reg.name for reg in transpiled_qc.qregs] - assert "ms0" in reg_names - assert "ms1" in reg_names - - -def test_shor_transpiler_t_gate_structure() -> None: - """Test that the T gate teleportation circuit has the correct structure. - - Verifies that: - - A magic state ancilla register for T (9 qubits) is allocated. - - The teleportation measurement register for T (1 classical bit) is allocated. - - Because of the S correction, another S ancilla and measurement are also allocated conditionally. - """ - qc = QuantumCircuit(1) - qc.t(0) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - # Original: 17 qubits - # T gate adds: 9 magic data for T + 9 magic data for S = 18 - # Total = 35 - assert transpiled_qc.num_qubits == 35 - - # Original: 8 clbits - # T gate adds: 1 for T + 1 for S = 2 - # Total = 10 - assert transpiled_qc.num_clbits == 10 - - ops = [inst.operation.name for inst in transpiled_qc.data] - - # Magic state prep uses p and h - assert "p" in ops - assert "h" in ops - - # Verify the T-magic state register exists - reg_names = [reg.name for reg in transpiled_qc.qregs] - assert "anc_t_1" in reg_names - assert "ms0" in reg_names # from the S correction - - -def test_shor_transpiler_barrier() -> None: - """Test logical barrier translates to physical barrier on all involved qubits.""" - qc = QuantumCircuit(2) - qc.barrier(0) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - ops = [inst.operation.name for inst in transpiled_qc.data] - assert "barrier" in ops - - -def test_shor_transpiler_measure() -> None: - """Test logical measure maps to 9 physical measurements.""" - qc = QuantumCircuit(1, 1) - qc.measure(0, 0) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - measure_count = sum(1 for inst in transpiled_qc.data if inst.operation.name == "measure") - # At least 9 physical measurements for the single logical measurement - assert measure_count >= 9 - - -def test_shor_transpiler_h_gate() -> None: - """Test logical H gate.""" - qc = QuantumCircuit(1) - qc.h(0) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - ops = [inst.operation.name for inst in transpiled_qc.data] - assert "h" in ops - assert "swap" in ops - - -def test_shor_transpiler_x_gate() -> None: - """Test logical X gate uses Z transversally.""" - qc = QuantumCircuit(1) - qc.x(0) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - ops = [inst.operation.name for inst in transpiled_qc.data] - # Shor code logical X = Z_0 Z_3 Z_6 - assert "z" in ops - - -def test_shor_transpiler_z_gate() -> None: - """Test logical Z gate uses X transversally.""" - qc = QuantumCircuit(1) - qc.z(0) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - ops = [inst.operation.name for inst in transpiled_qc.data] - # Shor code logical Z = X_0 X_1 X_2 - assert "x" in ops - - -def test_shor_transpiler_cx_gate() -> None: - """Test logical CX gate.""" - qc = QuantumCircuit(2) - qc.cx(0, 1) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - ops = [inst.operation.name for inst in transpiled_qc.data] - assert "cx" in ops - - -def test_shor_transpiler_cz_gate() -> None: - """Test logical CZ gate.""" - qc = QuantumCircuit(2) - qc.cz(0, 1) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - ops = [inst.operation.name for inst in transpiled_qc.data] - # CZ is implemented via H, CX, H - assert "h" in ops - assert "cx" in ops - assert "swap" in ops - - -def test_shor_transpiler_encode_decode() -> None: - """Test static encoding and decoding methods directly.""" - from qiskit import QuantumRegister - qc = QuantumCircuit() - reg = QuantumRegister(9, "q") - qc.add_register(reg) - - ShorTranspiler._apply_shor_encoding(qc, reg) - ops_enc = [inst.operation.name for inst in qc.data] - assert len(ops_enc) > 0 - - ShorTranspiler._apply_shor_decoding(qc, reg) - ops_dec = [inst.operation.name for inst in qc.data] - assert len(ops_dec) > len(ops_enc) - - -def test_shor_transpiler_prepare_magic() -> None: - """Test _prepare_magic directly.""" - from qiskit import QuantumRegister - import numpy as np - - qc = QuantumCircuit() - anc = QuantumRegister(9, "anc") - qc.add_register(anc) - - ShorTranspiler._prepare_magic(qc, anc, np.pi/2) - ops = [inst.operation.name for inst in qc.data] - assert "h" in ops - assert "p" in ops From 8bf47d6221c3f84b6418fd4207d77158a8047b3e Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sun, 31 May 2026 17:04:22 +0200 Subject: [PATCH 26/72] + Enabled Algorithmic Equality testing + Added logging functionality --- tests/test_error_correction.py | 120 +++++++++++++-------------------- 1 file changed, 46 insertions(+), 74 deletions(-) diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index 505f60b6b..75f4434d9 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -43,6 +43,7 @@ from qiskit.circuit.library import CXGate, HGate, SGate, XGate, ZGate from mqt.bench.error_correction.steane_transpiler import SteaneTranspiler from qiskit_aer.primitives import SamplerV2 +from pathlib import Path @@ -52,6 +53,7 @@ def test_errorcorrection_transpiler_gate_equivalence(code:str, gate: Gate): if gate.name == 's' and code == "shor": # this SGate entails non-unitary elements and can therefore not be evaluated properly return + num_qubits = gate.num_qubits logical_circuit = QuantumCircuit(num_qubits) logical_circuit.append(gate, qargs=list(range(num_qubits))) @@ -65,12 +67,16 @@ def test_errorcorrection_transpiler_gate_equivalence(code:str, gate: Gate): transpiler.decode_qubits() error_corrected_circuit = transpiler.transpiled_qc - assert check_equivalence(logical_circuit, error_corrected_circuit), f'Transpiler {code} does convert Gate {gate.name} to its logical equivalent' + assert check_equivalence(logical_circuit, error_corrected_circuit), f'Transpiler {code} does not convert Gate {gate.name} to its logical equivalent' @pytest.mark.parametrize("code", ["steane", "shor"]) @pytest.mark.parametrize("gate", [XGate(), ZGate(), HGate(), SGate()]) def test_errorcorrection_transpiler_gate_correctness(code: str, gate: Gate): + if gate.name == 's' and code == "shor": + # this takes a little longer.... + return + num_qubits = gate.num_qubits logical_circuit = QuantumCircuit(num_qubits) logical_circuit.append(gate, qargs=list(range(num_qubits))) @@ -96,14 +102,13 @@ def test_errorcorrection_transpiler_gate_correctness(code: str, gate: Gate): logical_corrected_fidelity = compare_distributions(logical_circuit, error_corrected_circuit, logical_counts, corrected_counts, "none", code) corrected_induced_fidelity = compare_distributions(error_corrected_circuit, error_induced_circuit, corrected_counts, induced_counts, code, code) - assert logical_corrected_fidelity >= 0.99, f"Error corrected circuit created by {code} transpiler for Gate {gate.name} does match its logical circuit well enough." + assert logical_corrected_fidelity >= 0.99, f"Error corrected circuit created by {code} transpiler for Gate {gate.name} does not match its logical circuit well enough." assert corrected_induced_fidelity >= 0.99, f"Error corrected circuit created by {code} transpiler for Gate {gate.name} does not correct the bitflip well enough." @pytest.mark.parametrize("code", ["shor", "steane"]) # double parametrize leads to crossproduct -@pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate",]) # "qft"]) -@pytest.mark.skip +@pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate"]) #, "qft"]) def test_errorcorrection_transpiler_correctness(code: str, algorithm: str): """ Ensures the transpiler creates error-corrected circuits which produce the same result as the orinigal logical circuit. @@ -116,19 +121,28 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str): benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding=code ) error_corrected_circuit = logical_circuit.copy() - transpiler = ShorTranspiler(error_corrected_circuit, add_syndromes=False) + if code == "shor": + transpiler = ShorTranspiler(error_corrected_circuit, add_syndromes=True) + else: + transpiler = SteaneTranspiler(logical_circuit, add_syndromes=True) transpiler.transpile() transpiler.decode_qubits() error_corrected_circuit = transpiler.transpiled_qc + error_induced_circuit = error_corrected_circuit.copy() - error_induced_circuit = insert_error(error_induced_circuit) + # this is for inserting phase flip in steane after the first Hadamard + #error_induced_circuit = insert_error(error_induced_circuit ,gate=ZGate(), index=16) + error_induced_circuit = insert_error(error_induced_circuit ,gate=XGate()) - # run each circuit logical_counts, logical_circuit = run_circuit(logical_circuit) corrected_counts, error_corrected_circuit = run_circuit(error_corrected_circuit) induced_counts, error_induced_circuit = run_circuit(error_induced_circuit) - # compare results + logical_corrected_fidelity = compare_distributions(logical_circuit, error_corrected_circuit, logical_counts, corrected_counts, "none", code) + corrected_induced_fidelity = compare_distributions(error_corrected_circuit, error_induced_circuit, corrected_counts, induced_counts, code, code) + + assert logical_corrected_fidelity >= 0.99, f"Error corrected circuit created by {code} transpiler for algorithm {algorithm} does not match its logical circuit well enough." + assert corrected_induced_fidelity >= 0.99, f"Error corrected circuit created by {code} transpiler for Algorithm {algorithm} does not correct the bitflip well enough." @@ -157,14 +171,21 @@ def insert_error(qc: QuantumCircuit, gate: Gate = XGate(), index: int | None = N return qc +def check_equivalence(qc1: qk.QuantumCircuit, qc2: qk.QuantumCircuit) -> bool: + """ + Uses MQT QCEC to verify if qc1 and qc2 are equivalent + """ + import mqt.qcec + from mqt.qcec.pyqcec import EquivalenceCriterion as EC -############################################################################################################################################ -############################################################################################################################################ -############################################################################################################################################ -############################################################################################################################################ -############################################################################################################################################ - - + verification_results = mqt.qcec.verify(qc1, qc2) + accepted_equivalencies = [ + EC.equivalent, + EC.equivalent_up_to_global_phase, + EC.probably_equivalent + ] + equivalent = verification_results.equivalence in accepted_equivalencies + return equivalent def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[dict, QuantumCircuit]: """ @@ -192,43 +213,6 @@ def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[dict, QuantumCir return meas_bit_counts, qc -def insert_error(qc: QuantumCircuit, gate: Gate = XGate(), index: int | None = None) -> QuantumCircuit: - """ - Adds the specified gate at the beginning of the circuit - Flips the first qubit right after the first barrier by default - """ - assert qc.num_qubits >= gate.num_qubits, f'Quantum Circuit has not enough qubits to accomodate gate {gate.name}' - assert index is None or index >= 0, f'Index must be >= 0, Index provided: {index}' - - # Finds the first barrier - if index is None: - for i, instruction in enumerate(qc.data): - if instruction.operation.name == "barrier": - index = i + 1 - break - - # Insert the error gate - qubits = qc.qubits[:gate.num_qubits] - qc.data.insert(index, CircuitInstruction(gate, qubits)) - - return qc - -def check_equivalence(qc1: qk.QuantumCircuit, qc2: qk.QuantumCircuit) -> bool: - """ - Uses MQT QCEC to verify if qc1 and qc2 are equivalent - """ - import mqt.qcec - from mqt.qcec.pyqcec import EquivalenceCriterion as EC - - verification_results = mqt.qcec.verify(qc1, qc2) - accepted_equivalencies = [ - EC.equivalent, - EC.equivalent_up_to_global_phase, - EC.probably_equivalent - ] - equivalent = verification_results.equivalence in accepted_equivalencies - return equivalent - def compare_distributions(qc1: QuantumCircuit, qc2: QuantumCircuit, counts1: dict, counts2: dict, code1: str = 'None', code2: str = 'None') -> float: """ Simulates 2 circuits and computes the Hellinger Fidelity between their count distributions @@ -236,17 +220,11 @@ def compare_distributions(qc1: QuantumCircuit, qc2: QuantumCircuit, counts1: dic If code is set to either 'steane' or 'shor' circuit error's result will be interpreted logically """ - - #print(counts1) if code1 in ['steane', 'shor']: counts1 = condense_counts(qc1, counts1) - #print(counts1) - - #print(counts2) if code2 in ['steane', 'shor']: counts2 = condense_counts(qc2, counts2) - #print(counts2) - + fidelity = hellinger_fidelity(counts1, counts2) return fidelity @@ -277,21 +255,6 @@ def is_q_integer(s: str) -> bool: return logical_qubits - -#def get_logical_classical_indices(qc, name): -# logical_cregs = sorted( -# [cr for cr in qc.cregs if cr.name.startswith(name)], -# key=lambda cr: int(cr.name.replace(name, "")) -# ) -# -# indices = [] -# -# for cr in logical_cregs: -# # assuming each logical register has size 1 -# indices.append(qc.find_bit(cr[0]).index) -# -# return indices - def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, int]: """ Takes in a result dict of a decoded physical measurement and returns logical measurements @@ -305,3 +268,12 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, i return logical_counts +def log_circuits(circuits: dict[str, QuantumCircuit]) -> None: + log_dir = Path(__file__).parent / "circuit_drawings" + log_dir.mkdir(exist_ok=True) + + for name, circuit in circuits.items(): + with open(log_dir / f"{name}_transpiled.txt", "w", encoding="utf-8") as f: + f.write(f"number of qubits {circuit.num_qubits}\n") + f.write(f"--- Transpiled Circuit for {name.upper()} ---\n\n") + f.write(str(circuit.draw(fold=-1)) + "\n") From 3832fe801ce13193e0d85c7b1136e293cb85e82f Mon Sep 17 00:00:00 2001 From: salehalsherif Date: Sat, 6 Jun 2026 10:43:27 +0200 Subject: [PATCH 27/72] Refactored to OpenClose Principle --- src/mqt/bench/benchmark_generation.py | 11 +- .../bench/benchmarks/shors_nine_qubit_code.py | 212 +++--------------- .../shor_circuit_components.py | 168 ++++++++++++++ .../bench/error_correction/shor_transpiler.py | 102 +++++---- tests/test_shor_equivalence.py | 16 +- tests/test_shor_transpiler.py | 9 +- 6 files changed, 263 insertions(+), 255 deletions(-) create mode 100644 src/mqt/bench/error_correction/shor_circuit_components.py diff --git a/src/mqt/bench/benchmark_generation.py b/src/mqt/bench/benchmark_generation.py index 5929ca7e1..b35de8caf 100644 --- a/src/mqt/bench/benchmark_generation.py +++ b/src/mqt/bench/benchmark_generation.py @@ -13,7 +13,6 @@ import sys from enum import Enum, auto from typing import TYPE_CHECKING, overload - import numpy as np from qiskit import generate_preset_pass_manager from qiskit.circuit import ClassicalRegister, QuantumCircuit, SessionEquivalenceLibrary @@ -32,10 +31,8 @@ if TYPE_CHECKING: # pragma: no cover from qiskit.transpiler import Target - from .configuration_options import ConfigurationOptions - class BenchmarkLevel(Enum): """Enum representing different levels.""" @@ -200,7 +197,7 @@ def get_benchmark_alg( def get_benchmark_alg( benchmark: str | QuantumCircuit, circuit_size: int | None = None, - encoding:str = "", + encoding: str = "", # noqa: ARG001 *, generate_mirror_circuit: bool = False, random_parameters: bool = True, @@ -211,7 +208,7 @@ def get_benchmark_alg( Arguments: benchmark: QuantumCircuit or name of the benchmark to be generated circuit_size: Input for the benchmark creation, in most cases this is equal to the qubit number - code: + encoding: Error correction code to be used (currently unused). generate_mirror_circuit: If True, generates the mirror version (U @ U.inverse()) of the benchmark. random_parameters: If True, assigns random parameters to the circuit's parameters if they exist. kwargs: Additional keyword arguments passed to the circuit creation. @@ -548,7 +545,7 @@ def get_benchmark( target: `~qiskit.transpiler.target.Target` for the benchmark generation (only used for "nativegates" and "mapped" level) opt_level: Optimization level to be used by the transpiler. - code: Error correction code to be used (currently unused). + encoding: Error correction code to be used (currently unused). generate_mirror_circuit: If True, generates the mirror version (U @ U.inverse()) of the benchmark. random_parameters: If True, assigns random parameters to the circuit's parameters if they exist. kwargs: Additional keyword arguments passed to the circuit creation. @@ -562,7 +559,7 @@ def get_benchmark( circuit_size=circuit_size, generate_mirror_circuit=generate_mirror_circuit, random_parameters=random_parameters, - encoding=code, + encoding=encoding, **kwargs, ) diff --git a/src/mqt/bench/benchmarks/shors_nine_qubit_code.py b/src/mqt/bench/benchmarks/shors_nine_qubit_code.py index 5ea409ce7..04f7e59f6 100644 --- a/src/mqt/bench/benchmarks/shors_nine_qubit_code.py +++ b/src/mqt/bench/benchmarks/shors_nine_qubit_code.py @@ -9,171 +9,20 @@ """Shor's 9 Qubit Code benchmark definition.""" from __future__ import annotations - from qiskit import ClassicalRegister from qiskit.circuit import AncillaRegister, QuantumCircuit, QuantumRegister -from ._registry import register_benchmark - - -def _get_three_qubit_bit_flip_encoding_decoding_circuit() -> QuantumCircuit: - """Create 3-qubit bit-flip encoding/decoding circuit. - - Encodes |0> → |000> and |1> → |111>. Self-inverse, so used for both encoding and decoding. - - Returns: - QuantumCircuit: 3-qubit circuit (qubit 0 is the input/output qubit). - """ - out = QuantumCircuit(3) - out.cx(0, 1) - out.cx(0, 2) - return out - - -def _get_three_qubit_phase_flip_encoding_circuit() -> QuantumCircuit: - """Create 3-qubit phase-flip encoding circuit. - - Encodes |0> → |+++> and |1> → |---> - - Returns: - QuantumCircuit: 3-qubit encoding circuit (qubit 0 is the input qubit). - """ - out = QuantumCircuit(3) - out.cx(0, 1) - out.cx(0, 2) - out.h(0) - out.h(1) - out.h(2) - return out - - -def _get_three_qubit_phase_flip_decoding_circuit() -> QuantumCircuit: - """Create 3-qubit phase-flip decoding circuit. - - Reverses the phase-flip encoding. - - Returns: - QuantumCircuit: 3-qubit decoding circuit (qubit 0 is the output qubit). - """ - out = QuantumCircuit(3) - out.h(0) - out.h(1) - out.h(2) - out.cx(0, 1) - out.cx(0, 2) - return out - - -def _get_three_qubit_bit_flip_syndrome_extraction_circuit() -> QuantumCircuit: - """Create circuit to extract bit-flip syndrome from a 3-qubit block. - - Uses 2 ancilla qubits to measure parity and identify which qubit (if any) flipped. - Syndrome mapping: 01 → qubit 0, 10 → qubit 1, 11 → qubit 2, 00 → no error. - - Returns: - QuantumCircuit: 5-qubit circuit (qubits 0-2 are data, qubits 3-4 are syndrome ancillas). - """ - out = QuantumCircuit(5) - out.cx(0, 3) - out.cx(1, 4) - out.cx(2, 3) - out.cx(2, 4) - return out - - -def _get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit() -> QuantumCircuit: - """Create circuit to extract phase-flip syndrome across the three 3-qubit blocks. - - Detects which block (if any) experienced a phase flip using 2 ancilla qubits. - Syndrome mapping: 01 → block 1 (qubits 0-2), 10 → block 2 (qubits 3-5), - 11 → block 3 (qubits 6-8), 00 → no error. - - Returns: - QuantumCircuit: 11-qubit circuit (qubits 0-8 are data, qubits 9-10 are syndrome ancillas). - """ - logical_qubit, phase_flip_syndrome = QuantumRegister(9), AncillaRegister(2) - out = QuantumCircuit(logical_qubit, phase_flip_syndrome) - # The order on the CNOT gates below is reversed when compared to what one might expect - # with the control being the ancilla, and the target being one of the component qubits of the logical qubit - # This is because we put Hadamards at the starts and ends of the ancilla bits, in order to check the phase - # of the logical qubits as opposed to the amplitude. - # But this also effectively swaps the order of the control and target, so we swap them back to normal - out.h(phase_flip_syndrome[0]) - out.h(phase_flip_syndrome[1]) - # Syndrome 01 (block 1) - for i in range(3): - out.cx(phase_flip_syndrome[0], logical_qubit[i]) - # Syndrome 10 (block 2) - for i in range(3, 6): - out.cx(phase_flip_syndrome[1], logical_qubit[i]) - # Syndrome 11 (block 3) - for i in range(6, 9): - out.cx(phase_flip_syndrome[0], logical_qubit[i]) - out.cx(phase_flip_syndrome[1], logical_qubit[i]) - out.h(phase_flip_syndrome[0]) - out.h(phase_flip_syndrome[1]) - return out - - -def _apply_nine_qubit_shors_code_bit_flip_correction( - qc: QuantumCircuit, - logical_qubit: QuantumRegister, - bit_flip_syndrome: AncillaRegister, - bit_flip_syndrome_measurement: ClassicalRegister, -) -> None: - """Apply bit-flip correction based on syndrome measurement. - - Measures the 6 syndrome qubits and conditionally applies X gates to correct - bit-flip errors on any of the 9 data qubits. - - Arguments: - qc: The quantum circuit to modify. - logical_qubit: Register containing the 9 data qubits. - bit_flip_syndrome: Ancilla register containing the 6 syndrome qubits. - bit_flip_syndrome_measurement: Classical register for syndrome measurement results. - """ - qc.measure(bit_flip_syndrome, bit_flip_syndrome_measurement) - # Note that Qiskit uses little-endian bit order - for index, syndrome in enumerate([ - 0b000001, - 0b000010, - 0b000011, - 0b000100, - 0b001000, - 0b001100, - 0b010000, - 0b100000, - 0b110000, - ]): - with qc.if_test((bit_flip_syndrome_measurement, syndrome)): - qc.x(logical_qubit[index]) - - -def _apply_nine_qubit_shors_code_phase_flip_correction( - qc: QuantumCircuit, - logical_qubit: QuantumRegister, - phase_flip_syndrome: AncillaRegister, - phase_flip_syndrome_measurement: ClassicalRegister, -) -> None: - """Apply phase-flip correction based on syndrome measurement. - - Measures the 2 syndrome qubits and conditionally applies Z gates to correct - phase-flip errors on the first qubit of the affected block. - - Arguments: - qc: The quantum circuit to modify. - logical_qubit: Register containing the 9 data qubits. - phase_flip_syndrome: Ancilla register containing the 2 syndrome qubits. - phase_flip_syndrome_measurement: Classical register for syndrome measurement results. - """ - qc.measure(phase_flip_syndrome, phase_flip_syndrome_measurement) - with qc.if_test((phase_flip_syndrome_measurement, 0b01)): - qc.z(logical_qubit[0]) - with qc.if_test((phase_flip_syndrome_measurement, 0b10)): - qc.z(logical_qubit[3]) - with qc.if_test((phase_flip_syndrome_measurement, 0b11)): - qc.z(logical_qubit[6]) +from mqt.bench.error_correction.shor_circuit_components import ( + apply_nine_qubit_shors_code_bit_flip_correction, + apply_nine_qubit_shors_code_phase_flip_correction, + get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit, + get_three_qubit_bit_flip_encoding_decoding_circuit, + get_three_qubit_bit_flip_syndrome_extraction_circuit, + get_three_qubit_phase_flip_encoding_circuit, + get_three_qubit_phase_flip_decoding_circuit, +) +from ._registry import register_benchmark def _create_single_logical_qubit_circuit(index: int) -> QuantumCircuit: """Create a complete Shor code circuit for one logical qubit. @@ -204,53 +53,53 @@ def _create_single_logical_qubit_circuit(index: int) -> QuantumCircuit: # == Encoding == # Apply phase flip encoding on the first qubit of each bit-flip block qc.compose( - _get_three_qubit_phase_flip_encoding_circuit(), + get_three_qubit_phase_flip_encoding_circuit(), qubits=[logical_qubit[0], logical_qubit[3], logical_qubit[6]], inplace=True, ) # Apply bit flip encoding on each block - qc.compose(_get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[:3], inplace=True) - qc.compose(_get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[3:6], inplace=True) - qc.compose(_get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[6:9], inplace=True) + qc.compose(get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[:3], inplace=True) + qc.compose(get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[3:6], inplace=True) + qc.compose(get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[6:9], inplace=True) qc.barrier() # == Syndrome extraction == qc.compose( - _get_three_qubit_bit_flip_syndrome_extraction_circuit(), + get_three_qubit_bit_flip_syndrome_extraction_circuit(), qubits=logical_qubit[:3] + bit_flip_syndrome[:2], inplace=True, ) qc.compose( - _get_three_qubit_bit_flip_syndrome_extraction_circuit(), + get_three_qubit_bit_flip_syndrome_extraction_circuit(), qubits=logical_qubit[3:6] + bit_flip_syndrome[2:4], inplace=True, ) qc.compose( - _get_three_qubit_bit_flip_syndrome_extraction_circuit(), + get_three_qubit_bit_flip_syndrome_extraction_circuit(), qubits=logical_qubit[6:9] + bit_flip_syndrome[4:6], inplace=True, ) qc.barrier() qc.compose( - _get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit(), + get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit(), qubits=logical_qubit[:] + phase_flip_syndrome[:], inplace=True, ) qc.barrier() # == Error correction == - _apply_nine_qubit_shors_code_bit_flip_correction( + apply_nine_qubit_shors_code_bit_flip_correction( qc, logical_qubit, bit_flip_syndrome, bit_flip_syndrome_measurement ) qc.barrier() - _apply_nine_qubit_shors_code_phase_flip_correction( + apply_nine_qubit_shors_code_phase_flip_correction( qc, logical_qubit, phase_flip_syndrome, phase_flip_syndrome_measurement ) qc.barrier() # == Decoding == - qc.compose(_get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[:3], inplace=True) - qc.compose(_get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[3:6], inplace=True) - qc.compose(_get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[6:9], inplace=True) + qc.compose(get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[:3], inplace=True) + qc.compose(get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[3:6], inplace=True) + qc.compose(get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=logical_qubit[6:9], inplace=True) qc.compose( - _get_three_qubit_phase_flip_decoding_circuit(), + get_three_qubit_phase_flip_decoding_circuit(), qubits=[logical_qubit[0], logical_qubit[3], logical_qubit[6]], inplace=True, ) @@ -258,7 +107,6 @@ def _create_single_logical_qubit_circuit(index: int) -> QuantumCircuit: qc.measure(logical_qubit[0], logical_qubit_measurement) return qc - @register_benchmark("shors_nine_qubit_code", description="Shor's 9 Qubit Code") def create_circuit(num_qubits: int) -> QuantumCircuit: """Returns a quantum circuit implementing Shor's 9 Qubit Code. @@ -279,17 +127,17 @@ def create_circuit(num_qubits: int) -> QuantumCircuit: Syndrome Extraction: - Bit-flip syndrome: For each block, 2 ancilla qubits measure the parity of - qubit pairs to detect which qubit (if any) experienced a bit flip. - Syndrome 01 → qubit 0, syndrome 10 → qubit 1, syndrome 11 → qubit 2. + qubit pairs to detect which qubit (if any) experienced a bit flip. + Syndrome 01 → qubit 0, syndrome 10 → qubit 1, syndrome 11 → qubit 2. - Phase-flip syndrome: 2 ancilla qubits detect phase differences between - the three blocks. Syndrome 01 → block 1 (qubits 0-2), syndrome 10 → block 2 - (qubits 3-5), syndrome 11 → block 3 (qubits 6-8). + the three blocks. Syndrome 01 → block 1 (qubits 0-2), syndrome 10 → block 2 + (qubits 3-5), syndrome 11 → block 3 (qubits 6-8). Error Correction: - Bit-flip correction: Based on the 6-bit syndrome measurement, X gates are - conditionally applied to correct bit flips on any of the 9 data qubits. + conditionally applied to correct bit flips on any of the 9 data qubits. - Phase-flip correction: Based on the 2-bit syndrome measurement, Z gates are - conditionally applied to the first qubit of the affected block. + conditionally applied to the first qubit of the affected block. Circuit Structure (per logical qubit): - 17 qubits: diff --git a/src/mqt/bench/error_correction/shor_circuit_components.py b/src/mqt/bench/error_correction/shor_circuit_components.py new file mode 100644 index 000000000..46edc09a6 --- /dev/null +++ b/src/mqt/bench/error_correction/shor_circuit_components.py @@ -0,0 +1,168 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Shor's 9-qubit code circuit components.""" + +from __future__ import annotations + + +from qiskit.circuit import AncillaRegister, QuantumCircuit, QuantumRegister, ClassicalRegister + +def get_three_qubit_phase_flip_decoding_circuit() -> QuantumCircuit: + """Create 3-qubit phase-flip decoding circuit. + + Reverses the phase-flip encoding. + + Returns: + QuantumCircuit: 3-qubit decoding circuit (qubit 0 is the output qubit). + """ + out = QuantumCircuit(3) + out.h(0) + out.h(1) + out.h(2) + out.cx(0, 1) + out.cx(0, 2) + return out + +def get_three_qubit_bit_flip_encoding_decoding_circuit() -> QuantumCircuit: + """Create 3-qubit bit-flip encoding/decoding circuit. + + Encodes |0> → |000> and |1> → |111>. Self-inverse, so used for both encoding and decoding. + + Returns: + QuantumCircuit: 3-qubit circuit (qubit 0 is the input/output qubit). + """ + out = QuantumCircuit(3) + out.cx(0, 1) + out.cx(0, 2) + return out + +def get_three_qubit_phase_flip_encoding_circuit() -> QuantumCircuit: + """Create 3-qubit phase-flip encoding circuit. + + Encodes |0> → |+++> and |1> → |---> + + Returns: + QuantumCircuit: 3-qubit encoding circuit (qubit 0 is the input qubit). + """ + out = QuantumCircuit(3) + out.cx(0, 1) + out.cx(0, 2) + out.h(0) + out.h(1) + out.h(2) + return out + +def get_three_qubit_bit_flip_syndrome_extraction_circuit() -> QuantumCircuit: + """Create circuit to extract bit-flip syndrome from a 3-qubit block. + + Uses 2 ancilla qubits to measure parity and identify which qubit (if any) flipped. + Syndrome mapping: 01 → qubit 0, 10 → qubit 1, 11 → qubit 2, 00 → no error. + + Returns: + QuantumCircuit: 5-qubit circuit (qubits 0-2 are data, qubits 3-4 are syndrome ancillas). + """ + out = QuantumCircuit(5) + out.cx(0, 3) + out.cx(1, 4) + out.cx(2, 3) + out.cx(2, 4) + return out + +def get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit() -> QuantumCircuit: + """Create circuit to extract phase-flip syndrome across the three 3-qubit blocks. + + Detects which block (if any) experienced a phase flip using 2 ancilla qubits. + Syndrome mapping: 01 → block 1 (qubits 0-2), 10 → block 2 (qubits 3-5), + 11 → block 3 (qubits 6-8), 00 → no error. + + Returns: + QuantumCircuit: 11-qubit circuit (qubits 0-8 are data, qubits 9-10 are syndrome ancillas). + """ + logical_qubit, phase_flip_syndrome = QuantumRegister(9), AncillaRegister(2) + out = QuantumCircuit(logical_qubit, phase_flip_syndrome) + # The order on the CNOT gates below is reversed when compared to what one might expect + # with the control being the ancilla, and the target being one of the component qubits of the logical qubit + # This is because we put Hadamards at the starts and ends of the ancilla bits, in order to check the phase + # of the logical qubits as opposed to the amplitude. + # But this also effectively swaps the order of the control and target, so we swap them back to normal + out.h(phase_flip_syndrome[0]) + out.h(phase_flip_syndrome[1]) + # Syndrome 01 (block 1) + for i in range(3): + out.cx(phase_flip_syndrome[0], logical_qubit[i]) + # Syndrome 10 (block 2) + for i in range(3, 6): + out.cx(phase_flip_syndrome[1], logical_qubit[i]) + # Syndrome 11 (block 3) + for i in range(6, 9): + out.cx(phase_flip_syndrome[0], logical_qubit[i]) + out.cx(phase_flip_syndrome[1], logical_qubit[i]) + out.h(phase_flip_syndrome[0]) + out.h(phase_flip_syndrome[1]) + return out + +def apply_nine_qubit_shors_code_bit_flip_correction( + qc: QuantumCircuit, + logical_qubit: QuantumRegister, + bit_flip_syndrome: AncillaRegister, + bit_flip_syndrome_measurement: ClassicalRegister, +) -> None: + """Apply bit-flip correction based on syndrome measurement. + + Measures the 6 syndrome qubits and conditionally applies X gates to correct + bit-flip errors on any of the 9 data qubits. + + Arguments: + qc: The quantum circuit to modify. + logical_qubit: Register containing the 9 data qubits. + bit_flip_syndrome: Ancilla register containing the 6 syndrome qubits. + bit_flip_syndrome_measurement: Classical register for syndrome measurement results. + """ + qc.measure(bit_flip_syndrome, bit_flip_syndrome_measurement) + # Note that Qiskit uses little-endian bit order + for index, syndrome in enumerate([ + 0b000001, + 0b000010, + 0b000011, + 0b000100, + 0b001000, + 0b001100, + 0b010000, + 0b100000, + 0b110000, + ]): + with qc.if_test((bit_flip_syndrome_measurement, syndrome)): + qc.x(logical_qubit[index]) + + +def apply_nine_qubit_shors_code_phase_flip_correction( + qc: QuantumCircuit, + logical_qubit: QuantumRegister, + phase_flip_syndrome: AncillaRegister, + phase_flip_syndrome_measurement: ClassicalRegister, +) -> None: + """Apply phase-flip correction based on syndrome measurement. + + Measures the 2 syndrome qubits and conditionally applies Z gates to correct + phase-flip errors on the first qubit of the affected block. + + Arguments: + qc: The quantum circuit to modify. + logical_qubit: Register containing the 9 data qubits. + phase_flip_syndrome: Ancilla register containing the 2 syndrome qubits. + phase_flip_syndrome_measurement: Classical register for syndrome measurement results. + """ + qc.measure(phase_flip_syndrome, phase_flip_syndrome_measurement) + with qc.if_test((phase_flip_syndrome_measurement, 0b01)): + qc.z(logical_qubit[0]) + with qc.if_test((phase_flip_syndrome_measurement, 0b10)): + qc.z(logical_qubit[3]) + with qc.if_test((phase_flip_syndrome_measurement, 0b11)): + qc.z(logical_qubit[6]) + diff --git a/src/mqt/bench/error_correction/shor_transpiler.py b/src/mqt/bench/error_correction/shor_transpiler.py index 216de13a0..4002a5a53 100644 --- a/src/mqt/bench/error_correction/shor_transpiler.py +++ b/src/mqt/bench/error_correction/shor_transpiler.py @@ -10,27 +10,25 @@ from __future__ import annotations -import numpy as np from dataclasses import dataclass -from typing import TYPE_CHECKING, Callable +import numpy as np from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister from qiskit.circuit import AncillaRegister -# ruff: noqa: PLC2701 #ignore the below comment # these functions are reused from the benchmark and they should be extendable i.e. they shouldn't be private -from mqt.bench.benchmarks.shors_nine_qubit_code import ( - _apply_nine_qubit_shors_code_bit_flip_correction, - _apply_nine_qubit_shors_code_phase_flip_correction, - _get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit, - _get_three_qubit_bit_flip_encoding_decoding_circuit, - _get_three_qubit_bit_flip_syndrome_extraction_circuit, - _get_three_qubit_phase_flip_encoding_circuit, +from mqt.bench.error_correction.shor_circuit_components import ( + apply_nine_qubit_shors_code_bit_flip_correction, + apply_nine_qubit_shors_code_phase_flip_correction, + get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit, + get_three_qubit_bit_flip_encoding_decoding_circuit, + get_three_qubit_bit_flip_syndrome_extraction_circuit, + get_three_qubit_phase_flip_encoding_circuit, ) -if TYPE_CHECKING: - from qiskit.circuit import CircuitInstruction + +from collections.abc import Callable # Constants for the Shor 9-qubit code structure SHOR_TOTAL_QUBITS = 9 @@ -73,7 +71,7 @@ def __init__(self, original_circuit: QuantumCircuit, add_syndromes: bool = True) self.s_gate_count = 0 self.t_gate_count = 0 self.transpiled_qc = QuantumCircuit() - + # We need this for backwards compatibility with the testing suite self.physical_data_registers: list[QuantumRegister] = [] @@ -89,7 +87,7 @@ def encode_qubits(self) -> None: for i in range(self.num_logical_qubits): data_reg = QuantumRegister(SHOR_TOTAL_QUBITS, f"q{i}") self.physical_data_registers.append(data_reg) - + if self.add_syndromes: logical_qubit = ShorLogicalQubit( data=data_reg, @@ -100,7 +98,7 @@ def encode_qubits(self) -> None: ) else: logical_qubit = ShorLogicalQubit(data=data_reg) - + self.logical_qubits.append(logical_qubit) all_registers.extend(logical_qubit.get_all_registers()) @@ -111,19 +109,19 @@ def encode_qubits(self) -> None: for logical_qubit in self.logical_qubits: self._apply_shor_encoding(self.transpiled_qc, logical_qubit.data) self.transpiled_qc.barrier() - + def decode_qubits(self) -> None: """Apply Shor 9-qubit decoding to each logical qubit.""" for logical_qubit in self.logical_qubits: self._apply_shor_decoding(self.transpiled_qc, logical_qubit.data) self.transpiled_qc.barrier() - + @staticmethod def _apply_shor_encoding(qc: QuantumCircuit, physical_data_register: QuantumRegister) -> None: """Apply Shor 9-qubit encoding to a physical data register.""" # Phase flip encoding on the first qubit of each block qc.compose( - _get_three_qubit_phase_flip_encoding_circuit(), + get_three_qubit_phase_flip_encoding_circuit(), qubits=[physical_data_register[i] for i in SHOR_PHASE_FLIP_TARGETS], inplace=True, ) @@ -131,7 +129,7 @@ def _apply_shor_encoding(qc: QuantumCircuit, physical_data_register: QuantumRegi # Bit flip encoding on each block for i in range(SHOR_NUM_BLOCKS): qc.compose( - _get_three_qubit_bit_flip_encoding_decoding_circuit(), + get_three_qubit_bit_flip_encoding_decoding_circuit(), qubits=physical_data_register[i * SHOR_BLOCK_SIZE : (i + 1) * SHOR_BLOCK_SIZE], inplace=True, ) @@ -141,12 +139,12 @@ def _apply_shor_decoding(qc: QuantumCircuit, physical_data_register: QuantumRegi """Apply Shor 9-qubit decoding to a physical data register.""" for i in range(SHOR_NUM_BLOCKS): qc.compose( - _get_three_qubit_bit_flip_encoding_decoding_circuit().inverse(), + get_three_qubit_bit_flip_encoding_decoding_circuit().inverse(), qubits=physical_data_register[i * SHOR_BLOCK_SIZE : (i + 1) * SHOR_BLOCK_SIZE], inplace=True, ) qc.compose( - _get_three_qubit_phase_flip_encoding_circuit().inverse(), + get_three_qubit_phase_flip_encoding_circuit().inverse(), qubits=[physical_data_register[i] for i in SHOR_PHASE_FLIP_TARGETS], inplace=True, ) @@ -156,15 +154,15 @@ def replace_gates(self) -> None: for instruction in self.original_qc.data: gate_name = instruction.operation.name handler_name = f"_logical_{gate_name}" - + if not hasattr(self, handler_name): msg = f"Gate {gate_name} is not supported by ShorTranspiler." raise NotImplementedError(msg) - + handler = getattr(self, handler_name) logical_qubit_indices = [self.original_qc.qubits.index(q) for q in instruction.qubits] logical_clbit_indices = [self.original_qc.clbits.index(c) for c in instruction.clbits] - + if gate_name == "barrier": handler(logical_qubit_indices) elif gate_name == "measure": @@ -192,7 +190,7 @@ def _logical_barrier(self, logical_qubit_indices: list[int]) -> None: def _logical_measure(self, logical_qubit_index: int, logical_classical_bit_index: int) -> None: """Apply logical measurement mapping to 9 physical measurements. - + Classical post-processing would compute the majority vote across the 3 bit-flip blocks and then across the phase-flip blocks to extract the logical value. """ @@ -205,8 +203,8 @@ def _logical_measure(self, logical_qubit_index: int, logical_classical_bit_index def _logical_h(self, logical_qubit_index: int) -> None: """Apply logical Hadamard. - - The Hadamard gate is not completely transversal for Shor's code. It requires + + The Hadamard gate is not completely transversal for Shor's code. It requires applying physical H gates followed by SWAPs that transpose the 9-qubit blocks. """ physical_data_register = self.logical_qubits[logical_qubit_index].data @@ -221,9 +219,9 @@ def _logical_h(self, logical_qubit_index: int) -> None: def _logical_x(self, logical_qubit_index: int) -> None: """Apply Transversal logical X. - - In Shor's code, a logical X acts like a global physical Z across the three - blocks. Since Z on one qubit of a block flips the entire block's phase, + + In Shor's code, a logical X acts like a global physical Z across the three + blocks. Since Z on one qubit of a block flips the entire block's phase, applying one Z per block (Z_0 Z_3 Z_6) transversally achieves logical X. """ physical_data_register = self.logical_qubits[logical_qubit_index].data @@ -233,8 +231,8 @@ def _logical_x(self, logical_qubit_index: int) -> None: def _logical_z(self, logical_qubit_index: int) -> None: """Apply Transversal logical Z. - - Applying X to the three qubits of a single block (e.g. X_0 X_1 X_2) maps + + Applying X to the three qubits of a single block (e.g. X_0 X_1 X_2) maps |000> to |111>, effectively giving diag(+1,-1) on the logical subspace. """ physical_data_register = self.logical_qubits[logical_qubit_index].data @@ -264,7 +262,7 @@ def _apply_teleportation_gadget(self, logical_qubit_index: int, phase: float, an # Apply conditional correction based on the measurement outcome with self.transpiled_qc.if_test((creg[0], 1)): correction_callback() - + self.insert_syndromes(logical_qubit_index) def _logical_s(self, logical_qubit_index: int) -> None: @@ -272,11 +270,11 @@ def _logical_s(self, logical_qubit_index: int) -> None: self.s_gate_count += 1 def z_correction() -> None: self._logical_z(logical_qubit_index) - + self._apply_teleportation_gadget( - logical_qubit_index=logical_qubit_index, - phase=np.pi / 2, - ancilla_name=f"ms{self.s_gate_count - 1}", + logical_qubit_index=logical_qubit_index, + phase=np.pi / 2, + ancilla_name=f"ms{self.s_gate_count - 1}", measure_name=f"tmeas{self.s_gate_count - 1}", correction_callback=z_correction ) @@ -284,14 +282,14 @@ def z_correction() -> None: def _logical_t(self, logical_qubit_index: int) -> None: """Apply logical T via |A>-state teleportation. Correction: logical S.""" self.t_gate_count += 1 - + def s_correction() -> None: self._logical_s(logical_qubit_index) - + self._apply_teleportation_gadget( - logical_qubit_index=logical_qubit_index, - phase=np.pi / 4, - ancilla_name=f"anc_t_{self.t_gate_count}", + logical_qubit_index=logical_qubit_index, + phase=np.pi / 4, + ancilla_name=f"anc_t_{self.t_gate_count}", measure_name=f"creg_t_{self.t_gate_count}", correction_callback=s_correction ) @@ -313,15 +311,15 @@ def _apply_logical_cx(self, control_register: QuantumRegister, target_register: def _logical_cx(self, control_logical_qubit_index: int, target_logical_qubit_index: int) -> None: """Apply transversal logical CX. - + Because the Shor logical operators X_L and Z_L have interchanged physical basis mapping - compared to typical codes, the physical CX role is inverted: control and target are + compared to typical codes, the physical CX role is inverted: control and target are swapped at the physical level to construct a logical CX. """ control_physical_data_register = self.logical_qubits[control_logical_qubit_index].data target_physical_data_register = self.logical_qubits[target_logical_qubit_index].data self._apply_logical_cx(control_physical_data_register, target_physical_data_register) - + self.insert_syndromes(control_logical_qubit_index) self.insert_syndromes(target_logical_qubit_index) @@ -337,16 +335,16 @@ def insert_syndromes(self, logical_qubit_index: int) -> None: """Automate the insertion of bit-flip and phase-flip error correction cycles.""" if not self.add_syndromes: return - + qubit = self.logical_qubits[logical_qubit_index] self.transpiled_qc.barrier() self._extract_bit_flip_syndromes(qubit) self.transpiled_qc.barrier() - + self._extract_phase_flip_syndromes(qubit) self.transpiled_qc.barrier() - + self._apply_error_corrections(qubit) self.transpiled_qc.barrier() @@ -354,7 +352,7 @@ def _extract_bit_flip_syndromes(self, qubit: ShorLogicalQubit) -> None: """Extract bit-flip syndromes for the three blocks.""" for i in range(SHOR_NUM_BLOCKS): self.transpiled_qc.compose( - _get_three_qubit_bit_flip_syndrome_extraction_circuit(), + get_three_qubit_bit_flip_syndrome_extraction_circuit(), qubits=qubit.data[i * SHOR_BLOCK_SIZE : (i + 1) * SHOR_BLOCK_SIZE] + qubit.bit_flip_syndrome[i * 2 : (i + 1) * 2], inplace=True, ) @@ -362,21 +360,21 @@ def _extract_bit_flip_syndromes(self, qubit: ShorLogicalQubit) -> None: def _extract_phase_flip_syndromes(self, qubit: ShorLogicalQubit) -> None: """Extract phase-flip syndromes across the blocks.""" self.transpiled_qc.compose( - _get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit(), + get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit(), qubits=qubit.data[:] + qubit.phase_flip_syndrome[:], inplace=True, ) def _apply_error_corrections(self, qubit: ShorLogicalQubit) -> None: """Apply bit-flip and phase-flip error corrections based on syndromes.""" - _apply_nine_qubit_shors_code_bit_flip_correction( + apply_nine_qubit_shors_code_bit_flip_correction( self.transpiled_qc, qubit.data, qubit.bit_flip_syndrome, qubit.bit_flip_measure, ) self.transpiled_qc.barrier() - _apply_nine_qubit_shors_code_phase_flip_correction( + apply_nine_qubit_shors_code_phase_flip_correction( self.transpiled_qc, qubit.data, qubit.phase_flip_syndrome, diff --git a/tests/test_shor_equivalence.py b/tests/test_shor_equivalence.py index 052d25937..433a974d1 100644 --- a/tests/test_shor_equivalence.py +++ b/tests/test_shor_equivalence.py @@ -10,11 +10,13 @@ from __future__ import annotations +from pathlib import Path + import numpy as np import pytest -from pathlib import Path from qiskit import QuantumCircuit -from qiskit.quantum_info import state_fidelity, Statevector +from qiskit.exceptions import QiskitError +from qiskit.quantum_info import DensityMatrix, Statevector, partial_trace, state_fidelity from qiskit_aer import AerSimulator from mqt.bench.error_correction.shor_transpiler import ShorTranspiler @@ -49,7 +51,7 @@ def verify_gate_equivalence(gate_name: str, num_qubits: int) -> None: raise ValueError(msg) # Get the expected density matrix - qc_logical_sim = qc_logical.copy() + qc_logical.copy() # expected logical state expected_sv_init = Statevector.from_instruction(qc_logical) @@ -65,7 +67,7 @@ def verify_gate_equivalence(gate_name: str, num_qubits: int) -> None: # Save to file to ensure it can be viewed regardless of pytest-xdist capturing stdout output_dir = Path("tests/circuit_drawings") output_dir.mkdir(parents=True, exist_ok=True) - with open(output_dir / f"{gate_name}_transpiled.txt", "w", encoding="utf-8") as f: + with Path(output_dir / f"{gate_name}_transpiled.txt").open("w", encoding="utf-8") as f: f.write(f"number of qubits {num_qubits}\n") f.write(f"--- Transpiled Circuit for {gate_name.upper()} ---\n\n") f.write(str(drawing) + "\n") @@ -86,25 +88,23 @@ def verify_gate_equivalence(gate_name: str, num_qubits: int) -> None: for i in range(num_qubits) ] - from qiskit.quantum_info import partial_trace all_qubits = list(range(transpiled_qc.num_qubits)) trace_qubits = [q for q in all_qubits if q not in logical_qubits_physical] actual_rho = partial_trace(actual_sv, trace_qubits) - from qiskit.quantum_info import DensityMatrix expected_rho_init = DensityMatrix(expected_sv_init) try: actual_sv_reduced = actual_rho.to_statevector() - except Exception: + except QiskitError: actual_sv_reduced = None # Compare the density matrices fidelity = state_fidelity(expected_sv_init, actual_rho) # Save the resulting density matrices and state vectors to the text file for visual inspection - with open(output_dir / f"{gate_name}_transpiled.txt", "a", encoding="utf-8") as f: + with Path(output_dir / f"{gate_name}_transpiled.txt").open("a", encoding="utf-8") as f: f.write("\n\n=== LOGICAL EXPECTED DENSITY MATRIX ===\n") f.write(str(np.round(expected_rho_init.data, 3)) + "\n") f.write("\n=== ACTUAL TRANSPILED DENSITY MATRIX (REDUCED) ===\n") diff --git a/tests/test_shor_transpiler.py b/tests/test_shor_transpiler.py index 482c3f686..2c3c41c13 100644 --- a/tests/test_shor_transpiler.py +++ b/tests/test_shor_transpiler.py @@ -10,11 +10,12 @@ from __future__ import annotations +import numpy as np +from qiskit import QuantumCircuit, QuantumRegister import pytest -from qiskit import QuantumCircuit - from mqt.bench.error_correction.shor_transpiler import ShorTranspiler + # this needs mpre tests def test_shor_transpiler() -> None: """Test that ShorTranspiler successfully transpiles a basic circuit.""" @@ -275,7 +276,6 @@ def test_shor_transpiler_cz_gate() -> None: def test_shor_transpiler_encode_decode() -> None: """Test static encoding and decoding methods directly.""" - from qiskit import QuantumRegister qc = QuantumCircuit() reg = QuantumRegister(9, "q") qc.add_register(reg) @@ -291,9 +291,6 @@ def test_shor_transpiler_encode_decode() -> None: def test_shor_transpiler_prepare_magic() -> None: """Test _prepare_magic directly.""" - from qiskit import QuantumRegister - import numpy as np - qc = QuantumCircuit() anc = QuantumRegister(9, "anc") qc.add_register(anc) From 49bd55ae6966e271786d4598f8775202890547d1 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 6 Jun 2026 11:03:42 +0200 Subject: [PATCH 28/72] + logging --- tests/test_error_correction.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index 75f4434d9..75e5d1f57 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -92,7 +92,7 @@ def test_errorcorrection_transpiler_gate_correctness(code: str, gate: Gate): error_induced_circuit = error_corrected_circuit.copy() # this is for inserting phase flip in steane after the first Hadamard #error_induced_circuit = insert_error(error_induced_circuit ,gate=ZGate(), index=16) - error_induced_circuit = insert_error(error_induced_circuit ,gate=XGate()) + error_induced_circuit = insert_error(error_induced_circuit, gate=XGate()) logical_counts, logical_circuit = run_circuit(logical_circuit) @@ -141,7 +141,11 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str): logical_corrected_fidelity = compare_distributions(logical_circuit, error_corrected_circuit, logical_counts, corrected_counts, "none", code) corrected_induced_fidelity = compare_distributions(error_corrected_circuit, error_induced_circuit, corrected_counts, induced_counts, code, code) - assert logical_corrected_fidelity >= 0.99, f"Error corrected circuit created by {code} transpiler for algorithm {algorithm} does not match its logical circuit well enough." + log_circuits({f'log_{code}_{algorithm}':logical_circuit, + f'corrected_{code}_{algorithm}':error_corrected_circuit, + f'induced_{code}_{algorithm}':error_induced_circuit,}) + + assert logical_corrected_fidelity >= 0.99, f"Error corrected circuit created by {code} transpiler for Algorithm {algorithm} does not match its logical circuit well enough." assert corrected_induced_fidelity >= 0.99, f"Error corrected circuit created by {code} transpiler for Algorithm {algorithm} does not correct the bitflip well enough." From a65972d366f7ed07d0a547950cd1a1b716880f6d Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 6 Jun 2026 12:33:55 +0200 Subject: [PATCH 29/72] + cx & cz gate testing --- tests/test_error_correction.py | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index 75e5d1f57..ef11db910 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -40,7 +40,7 @@ from qiskit_aer import AerSimulator # update uv requirements? import qiskit as qk from qiskit.circuit import CircuitInstruction, Gate -from qiskit.circuit.library import CXGate, HGate, SGate, XGate, ZGate +from qiskit.circuit.library import CXGate, HGate, SGate, XGate, ZGate, CZGate from mqt.bench.error_correction.steane_transpiler import SteaneTranspiler from qiskit_aer.primitives import SamplerV2 from pathlib import Path @@ -48,7 +48,7 @@ @pytest.mark.parametrize("code", ["steane", "shor"]) -@pytest.mark.parametrize("gate", [XGate(), ZGate(), HGate(), SGate()]) +@pytest.mark.parametrize("gate", [XGate(), ZGate(), HGate(), SGate(), CXGate(), CZGate()]) def test_errorcorrection_transpiler_gate_equivalence(code:str, gate: Gate): if gate.name == 's' and code == "shor": # this SGate entails non-unitary elements and can therefore not be evaluated properly @@ -71,7 +71,7 @@ def test_errorcorrection_transpiler_gate_equivalence(code:str, gate: Gate): @pytest.mark.parametrize("code", ["steane", "shor"]) -@pytest.mark.parametrize("gate", [XGate(), ZGate(), HGate(), SGate()]) +@pytest.mark.parametrize("gate", [XGate(), ZGate(), HGate(), SGate(), CXGate(), CZGate()]) def test_errorcorrection_transpiler_gate_correctness(code: str, gate: Gate): if gate.name == 's' and code == "shor": # this takes a little longer.... @@ -141,9 +141,15 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str): logical_corrected_fidelity = compare_distributions(logical_circuit, error_corrected_circuit, logical_counts, corrected_counts, "none", code) corrected_induced_fidelity = compare_distributions(error_corrected_circuit, error_induced_circuit, corrected_counts, induced_counts, code, code) - log_circuits({f'log_{code}_{algorithm}':logical_circuit, - f'corrected_{code}_{algorithm}':error_corrected_circuit, - f'induced_{code}_{algorithm}':error_induced_circuit,}) + #log_circuits({f'log_{code}_{algorithm}':logical_circuit, + # f'corrected_{code}_{algorithm}':error_corrected_circuit, + # f'induced_{code}_{algorithm}':error_induced_circuit,}) + + print(corrected_counts) + print('condensed:', condense_counts(error_corrected_circuit, corrected_counts)) + print('Logical', logical_counts) + + assert logical_corrected_fidelity >= 0.99, f"Error corrected circuit created by {code} transpiler for Algorithm {algorithm} does not match its logical circuit well enough." assert corrected_induced_fidelity >= 0.99, f"Error corrected circuit created by {code} transpiler for Algorithm {algorithm} does not correct the bitflip well enough." @@ -281,3 +287,10 @@ def log_circuits(circuits: dict[str, QuantumCircuit]) -> None: f.write(f"number of qubits {circuit.num_qubits}\n") f.write(f"--- Transpiled Circuit for {name.upper()} ---\n\n") f.write(str(circuit.draw(fold=-1)) + "\n") + + + import matplotlib.pyplot as plt + + fig = circuit.draw(output="mpl", fold=-1) + fig.savefig(log_dir / f"{name}_transpiled.png", dpi=150, bbox_inches="tight") + plt.close(fig) From 6f765f0b58f4e3640809cc0a4e1426dbcf080be1 Mon Sep 17 00:00:00 2001 From: "LAPTOP-N4TQ80AC\\khusa" Date: Sat, 6 Jun 2026 12:33:59 +0200 Subject: [PATCH 30/72] =?UTF-8?q?=F0=9F=9A=A7=20Working=20with=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../bench/error_correction/shor_transpiler.py | 3 + .../error_correction/steane_transpiler.py | 9 ++- tests/test_error_correction.py | 59 ++++++++++++++++--- 3 files changed, 62 insertions(+), 9 deletions(-) diff --git a/src/mqt/bench/error_correction/shor_transpiler.py b/src/mqt/bench/error_correction/shor_transpiler.py index 368f57316..1bc6d8397 100644 --- a/src/mqt/bench/error_correction/shor_transpiler.py +++ b/src/mqt/bench/error_correction/shor_transpiler.py @@ -383,3 +383,6 @@ def _apply_error_corrections(self, qubit: ShorLogicalQubit) -> None: qubit.phase_flip_syndrome, qubit.phase_flip_measure, ) + + self.transpiled_qc.reset(qubit.bit_flip_syndrome) + self.transpiled_qc.reset(qubit.phase_flip_syndrome) diff --git a/src/mqt/bench/error_correction/steane_transpiler.py b/src/mqt/bench/error_correction/steane_transpiler.py index c2da25ec6..73f9c5264 100644 --- a/src/mqt/bench/error_correction/steane_transpiler.py +++ b/src/mqt/bench/error_correction/steane_transpiler.py @@ -127,7 +127,7 @@ def replace_gates(self) -> None: if gate_name in gate_handlers: gate_handlers[gate_name](instruction) else: - msg = f"Gate {gate_name} is not supported by ShorTranspiler." + msg = f"Gate {gate_name} is not supported by SteaneTranspiler." raise NotImplementedError(msg) def _handle_barrier(self, instruction: CircuitInstruction) -> None: @@ -208,7 +208,7 @@ def _handle_t(self, instruction: CircuitInstruction) -> None: logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) physical_data_register = self.physical_data_registers[logical_qubit_index] - t_ancilla_register = QuantumRegister(7, f"t{self.t_gate_count}") + t_ancilla_register = AncillaRegister(7, f"t{self.t_gate_count}") self.t_gate_count += 1 t_test_register = ClassicalRegister(1) @@ -298,6 +298,10 @@ def insert_syndromes(self, logical_qubit_index: int) -> None: self.transpiled_qc.barrier(label="Syndrom Start") + # clean ancillas + self.transpiled_qc.reset(bit_flip_syndrome_register) + self.transpiled_qc.reset(phase_flip_syndrome_register) + # Syndrome extraction self.transpiled_qc.compose( _get_seven_qubit_steane_code_syndrome_extraction_circuit(), @@ -318,4 +322,5 @@ def insert_syndromes(self, logical_qubit_index: int) -> None: bit_flip_measurement_register, phase_flip_measurement_register ) + self.transpiled_qc.barrier(label="Correction End") diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index 75f4434d9..c5bd4e6e8 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -32,7 +32,7 @@ import numpy as np import pytest from pathlib import Path -from qiskit import QuantumCircuit +from qiskit import QuantumCircuit, transpile from qiskit.quantum_info import hellinger_fidelity, state_fidelity, Statevector from mqt.bench.error_correction.shor_transpiler import ShorTranspiler @@ -92,7 +92,7 @@ def test_errorcorrection_transpiler_gate_correctness(code: str, gate: Gate): error_induced_circuit = error_corrected_circuit.copy() # this is for inserting phase flip in steane after the first Hadamard #error_induced_circuit = insert_error(error_induced_circuit ,gate=ZGate(), index=16) - error_induced_circuit = insert_error(error_induced_circuit ,gate=XGate()) + error_induced_circuit = insert_error(error_induced_circuit ,gate=gate) logical_counts, logical_circuit = run_circuit(logical_circuit) @@ -107,19 +107,34 @@ def test_errorcorrection_transpiler_gate_correctness(code: str, gate: Gate): -@pytest.mark.parametrize("code", ["shor", "steane"]) # double parametrize leads to crossproduct -@pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate"]) #, "qft"]) +@pytest.mark.parametrize("code", ["steane"]) # "shor", double parametrize leads to crossproduct +@pytest.mark.parametrize("algorithm", ["ghz"]) #, ", "bv", "graphstate"qft"]) def test_errorcorrection_transpiler_correctness(code: str, algorithm: str): """ Ensures the transpiler creates error-corrected circuits which produce the same result as the orinigal logical circuit. Afterwards an error is introduced and the test checks, whether it is corrected. Iterates over a number of example algorithms. """ + if algorithm == 'qft' and code == "shor": + # this takes a little longer.... + return circuit_size = 3 # Initialize circuits logical_circuit = benchmark_generation.get_benchmark( benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding=code ) + for i in logical_circuit.qubits: + logical_circuit.h(i) + + if algorithm == 'qft': + basis = ["h", "s", "t", "x", "z", "cx", "cz"] + + logical_circuit = transpile( + logical_circuit, + basis_gates=basis + ) + print(logical_circuit.decompose().count_ops()) + error_corrected_circuit = logical_circuit.copy() if code == "shor": transpiler = ShorTranspiler(error_corrected_circuit, add_syndromes=True) @@ -132,7 +147,13 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str): error_induced_circuit = error_corrected_circuit.copy() # this is for inserting phase flip in steane after the first Hadamard #error_induced_circuit = insert_error(error_induced_circuit ,gate=ZGate(), index=16) - error_induced_circuit = insert_error(error_induced_circuit ,gate=XGate()) + #error_induced_circuit = insert_error(error_induced_circuit ,gate=XGate()) + error_induced_circuit = insert_error_after_barrier( + error_corrected_circuit, + barrier_label="Encoding", + gate=XGate(), + qubit_index=0, + ) logical_counts, logical_circuit = run_circuit(logical_circuit) corrected_counts, error_corrected_circuit = run_circuit(error_corrected_circuit) @@ -144,7 +165,26 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str): assert logical_corrected_fidelity >= 0.99, f"Error corrected circuit created by {code} transpiler for algorithm {algorithm} does not match its logical circuit well enough." assert corrected_induced_fidelity >= 0.99, f"Error corrected circuit created by {code} transpiler for Algorithm {algorithm} does not correct the bitflip well enough." - +def insert_error_after_barrier( + qc: QuantumCircuit, + barrier_label: str, + gate: Gate = XGate(), + qubit_index: int = 0, +) -> QuantumCircuit: + qc = qc.copy() + + for i, instruction in enumerate(qc.data): + if ( + instruction.operation.name == "barrier" + and instruction.operation.label == barrier_label + ): + qc.data.insert( + i + 1, + CircuitInstruction(gate, [qc.qubits[qubit_index]]), + ) + return qc + + raise ValueError(f"Barrier with label {barrier_label!r} not found") def insert_error(qc: QuantumCircuit, gate: Gate = XGate(), index: int | None = None) -> QuantumCircuit: """ @@ -187,7 +227,7 @@ def check_equivalence(qc1: qk.QuantumCircuit, qc2: qk.QuantumCircuit) -> bool: equivalent = verification_results.equivalence in accepted_equivalencies return equivalent -def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[dict, QuantumCircuit]: +def run_circuit(qc: QuantumCircuit, shots: int = 10240) -> tuple[dict, QuantumCircuit]: """ Simulates the circuit using AerSimulator. @@ -277,3 +317,8 @@ def log_circuits(circuits: dict[str, QuantumCircuit]) -> None: f.write(f"number of qubits {circuit.num_qubits}\n") f.write(f"--- Transpiled Circuit for {name.upper()} ---\n\n") f.write(str(circuit.draw(fold=-1)) + "\n") + + + +if __name__ == "__main__": + test_errorcorrection_transpiler_correctness("bv", "steane") \ No newline at end of file From 02d1ac24bae612764acf930ce367cb73db8d2a1e Mon Sep 17 00:00:00 2001 From: "LAPTOP-N4TQ80AC\\khusa" Date: Sat, 6 Jun 2026 13:05:06 +0200 Subject: [PATCH 31/72] Addind Steane transpiler --- .../bench/error_correction/shor_transpiler.py | 9 ++++-- tests/test_error_correction.py | 31 +++++++++++++++---- 2 files changed, 31 insertions(+), 9 deletions(-) diff --git a/src/mqt/bench/error_correction/shor_transpiler.py b/src/mqt/bench/error_correction/shor_transpiler.py index ade334706..ec7540ad0 100644 --- a/src/mqt/bench/error_correction/shor_transpiler.py +++ b/src/mqt/bench/error_correction/shor_transpiler.py @@ -108,7 +108,7 @@ def encode_qubits(self) -> None: # Apply encoding for each logical qubit for logical_qubit in self.logical_qubits: self._apply_shor_encoding(self.transpiled_qc, logical_qubit.data) - self.transpiled_qc.barrier() + self.transpiled_qc.barrier(label="Encoding") def decode_qubits(self) -> None: """Apply Shor 9-qubit decoding to each logical qubit.""" @@ -337,6 +337,7 @@ def insert_syndromes(self, logical_qubit_index: int) -> None: if not self.add_syndromes: return + qubit = self.logical_qubits[logical_qubit_index] self.transpiled_qc.barrier() @@ -351,6 +352,7 @@ def insert_syndromes(self, logical_qubit_index: int) -> None: def _extract_bit_flip_syndromes(self, qubit: ShorLogicalQubit) -> None: """Extract bit-flip syndromes for the three blocks.""" + self.transpiled_qc.reset(qubit.bit_flip_syndrome) for i in range(SHOR_NUM_BLOCKS): self.transpiled_qc.compose( get_three_qubit_bit_flip_syndrome_extraction_circuit(), @@ -360,6 +362,7 @@ def _extract_bit_flip_syndromes(self, qubit: ShorLogicalQubit) -> None: def _extract_phase_flip_syndromes(self, qubit: ShorLogicalQubit) -> None: """Extract phase-flip syndromes across the blocks.""" + self.transpiled_qc.reset(qubit.phase_flip_syndrome) self.transpiled_qc.compose( get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit(), qubits=qubit.data[:] + qubit.phase_flip_syndrome[:], @@ -382,5 +385,5 @@ def _apply_error_corrections(self, qubit: ShorLogicalQubit) -> None: qubit.phase_flip_measure, ) - self.transpiled_qc.reset(qubit.bit_flip_syndrome) - self.transpiled_qc.reset(qubit.phase_flip_syndrome) + + diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index 915bf6a72..bfde15b42 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -106,10 +106,28 @@ def test_errorcorrection_transpiler_gate_correctness(code: str, gate: Gate): assert corrected_induced_fidelity >= 0.99, f"Error corrected circuit created by {code} transpiler for Gate {gate.name} does not correct the bitflip well enough." +def add_h_before_measurements(qc: QuantumCircuit) -> QuantumCircuit: + new_qc = QuantumCircuit(*qc.qregs, *qc.cregs, name=qc.name) -@pytest.mark.parametrize("code", ["shor", "steane"]) # double parametrize leads to crossproduct -@pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate"]) #, "qft"]) -def test_errorcorrection_transpiler_correctness(code: str, algorithm: str): + for instruction in qc.data: + op = instruction.operation + qargs = instruction.qubits + cargs = instruction.clbits + + if op.name == "measure": + # Add H to the qubit that is about to be measured + new_qc.h(qargs[0]) + + # Add the original instruction + new_qc.append(op, qargs, cargs) + + return new_qc + +@pytest.mark.parametrize("code", ["shor"]) # "shor", double parametrize leads to crossproduct +@pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate"]) #, , "bv", "graphstate""qft"]) +@pytest.mark.parametrize("Error", [XGate(), ZGate()]) #, , "bv", "graphstate""qft"]) +@pytest.mark.parametrize("MeasureBaseX", [True, False]) #, , "bv", "graphstate""qft"]) +def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error, MeasureBaseX:bool): """ Ensures the transpiler creates error-corrected circuits which produce the same result as the orinigal logical circuit. Afterwards an error is introduced and the test checks, whether it is corrected. @@ -123,8 +141,9 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str): logical_circuit = benchmark_generation.get_benchmark( benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding=code ) - for i in logical_circuit.qubits: - logical_circuit.h(i) + + if MeasureBaseX: + logical_circuit = add_h_before_measurements(logical_circuit) if algorithm == 'qft': basis = ["h", "s", "t", "x", "z", "cx", "cz"] @@ -151,7 +170,7 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str): error_induced_circuit = insert_error_after_barrier( error_corrected_circuit, barrier_label="Encoding", - gate=XGate(), + gate=Error, qubit_index=0, ) From b0c175e74a9e57cdbb8655578db2051aff34d2ef Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 6 Jun 2026 13:11:59 +0200 Subject: [PATCH 32/72] removed wrong tests and unneccessay outputs --- tests/test_error_correction.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index 44cacb03c..ff42a84cb 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -48,7 +48,7 @@ @pytest.mark.parametrize("code", ["steane", "shor"]) -@pytest.mark.parametrize("gate", [XGate(), ZGate(), HGate(), SGate(), CXGate(), CZGate()]) +@pytest.mark.parametrize("gate", [XGate(), ZGate(), HGate(), SGate()]) def test_errorcorrection_transpiler_gate_equivalence(code:str, gate: Gate): if gate.name == 's' and code == "shor": # this SGate entails non-unitary elements and can therefore not be evaluated properly @@ -185,7 +185,7 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error # f'corrected_{code}_{algorithm}':error_corrected_circuit, # f'induced_{code}_{algorithm}':error_induced_circuit,}) - print(corrected_counts) + #print(corrected_counts) print('condensed:', condense_counts(error_corrected_circuit, corrected_counts)) print('Logical', logical_counts) From 9c71e3a446543982c727db3d42126937cb4222a2 Mon Sep 17 00:00:00 2001 From: salehalsherif Date: Sat, 6 Jun 2026 13:32:47 +0200 Subject: [PATCH 33/72] correcting tests --- pyproject.toml | 1 + tests/test_error_correction.py | 257 +++++++++++++++++---------------- 2 files changed, 132 insertions(+), 126 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index c5f2729ba..1fcbffabd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -250,6 +250,7 @@ aer = "aer" fom = "fom" bench = "bench" benchs = "benchs" +ket = "ket" [tool.repo-review.ignore] diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index ff42a84cb..0225c9bec 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -6,17 +6,10 @@ # # Licensed under the MIT License - - - - - # TODO: # uv requirements to be added: mqt.qcec, qiskit_aer - - # What do we want to test: # 1 function for each, split steane and shor for hardcoded sanity, combine for equvalencies # transpilers work as intended (simply sanity checks) ✅ @@ -29,37 +22,38 @@ from __future__ import annotations -import numpy as np -import pytest from pathlib import Path +from typing import TYPE_CHECKING + +import pytest from qiskit import QuantumCircuit, transpile -from qiskit.quantum_info import hellinger_fidelity, state_fidelity, Statevector +from qiskit.circuit import CircuitInstruction +from qiskit.circuit.library import CXGate, CZGate, HGate, SGate, XGate, ZGate +from qiskit.quantum_info import hellinger_fidelity +from qiskit_aer.primitives import SamplerV2 -from mqt.bench.error_correction.shor_transpiler import ShorTranspiler import mqt.bench.benchmark_generation as benchmark_generation -from qiskit_aer import AerSimulator # update uv requirements? -import qiskit as qk -from qiskit.circuit import CircuitInstruction, Gate -from qiskit.circuit.library import CXGate, HGate, SGate, XGate, ZGate, CZGate +from mqt.bench.error_correction.shor_transpiler import ShorTranspiler from mqt.bench.error_correction.steane_transpiler import SteaneTranspiler -from qiskit_aer.primitives import SamplerV2 -from pathlib import Path +if TYPE_CHECKING: + import qiskit as qk + from qiskit.circuit import Gate @pytest.mark.parametrize("code", ["steane", "shor"]) @pytest.mark.parametrize("gate", [XGate(), ZGate(), HGate(), SGate()]) -def test_errorcorrection_transpiler_gate_equivalence(code:str, gate: Gate): - if gate.name == 's' and code == "shor": +def test_errorcorrection_transpiler_gate_equivalence(code: str, gate: Gate) -> None: + if gate.name == "s" and code == "shor": # this SGate entails non-unitary elements and can therefore not be evaluated properly return - + num_qubits = gate.num_qubits logical_circuit = QuantumCircuit(num_qubits) logical_circuit.append(gate, qargs=list(range(num_qubits))) - + error_corrected_circuit = logical_circuit.copy() - if code == 'shor': + if code == "shor": transpiler = ShorTranspiler(error_corrected_circuit, add_syndromes=False) else: transpiler = SteaneTranspiler(error_corrected_circuit, add_syndromes=False) @@ -67,16 +61,18 @@ def test_errorcorrection_transpiler_gate_equivalence(code:str, gate: Gate): transpiler.decode_qubits() error_corrected_circuit = transpiler.transpiled_qc - assert check_equivalence(logical_circuit, error_corrected_circuit), f'Transpiler {code} does not convert Gate {gate.name} to its logical equivalent' + assert check_equivalence(logical_circuit, error_corrected_circuit), ( + f"Transpiler {code} does not convert Gate {gate.name} to its logical equivalent" + ) @pytest.mark.parametrize("code", ["steane", "shor"]) @pytest.mark.parametrize("gate", [XGate(), ZGate(), HGate(), SGate(), CXGate(), CZGate()]) -def test_errorcorrection_transpiler_gate_correctness(code: str, gate: Gate): - if gate.name == 's' and code == "shor": +def test_errorcorrection_transpiler_gate_correctness(code: str, gate: Gate) -> None: + if gate.name == "s" and code == "shor": # this takes a little longer.... return - + num_qubits = gate.num_qubits logical_circuit = QuantumCircuit(num_qubits) logical_circuit.append(gate, qargs=list(range(num_qubits))) @@ -91,19 +87,26 @@ def test_errorcorrection_transpiler_gate_correctness(code: str, gate: Gate): error_induced_circuit = error_corrected_circuit.copy() # this is for inserting phase flip in steane after the first Hadamard - #error_induced_circuit = insert_error(error_induced_circuit ,gate=ZGate(), index=16) - error_induced_circuit = insert_error(error_induced_circuit ,gate=XGate()) - + # error_induced_circuit = insert_error(error_induced_circuit ,gate=ZGate(), index=16) + error_induced_circuit = insert_error(error_induced_circuit, gate=XGate()) logical_counts, logical_circuit = run_circuit(logical_circuit) corrected_counts, error_corrected_circuit = run_circuit(error_corrected_circuit) induced_counts, error_induced_circuit = run_circuit(error_induced_circuit) - logical_corrected_fidelity = compare_distributions(logical_circuit, error_corrected_circuit, logical_counts, corrected_counts, "none", code) - corrected_induced_fidelity = compare_distributions(error_corrected_circuit, error_induced_circuit, corrected_counts, induced_counts, code, code) + logical_corrected_fidelity = compare_distributions( + logical_circuit, error_corrected_circuit, logical_counts, corrected_counts, "none", code + ) + corrected_induced_fidelity = compare_distributions( + error_corrected_circuit, error_induced_circuit, corrected_counts, induced_counts, code, code + ) - assert logical_corrected_fidelity >= 0.99, f"Error corrected circuit created by {code} transpiler for Gate {gate.name} does not match its logical circuit well enough." - assert corrected_induced_fidelity >= 0.99, f"Error corrected circuit created by {code} transpiler for Gate {gate.name} does not correct the bitflip well enough." + assert logical_corrected_fidelity >= 0.99, ( + f"Error corrected circuit created by {code} transpiler for Gate {gate.name} does not match its logical circuit well enough." + ) + assert corrected_induced_fidelity >= 0.99, ( + f"Error corrected circuit created by {code} transpiler for Gate {gate.name} does not correct the bitflip well enough." + ) def add_h_before_measurements(qc: QuantumCircuit) -> QuantumCircuit: @@ -123,37 +126,41 @@ def add_h_before_measurements(qc: QuantumCircuit) -> QuantumCircuit: return new_qc -@pytest.mark.parametrize("code", ["shor"]) # "shor", double parametrize leads to crossproduct -@pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate"]) #, , "bv", "graphstate""qft"]) -@pytest.mark.parametrize("Error", [XGate(), ZGate()]) #, , "bv", "graphstate""qft"]) -@pytest.mark.parametrize("MeasureBaseX", [True, False]) #, , "bv", "graphstate""qft"]) -def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error, MeasureBaseX:bool): - """ - Ensures the transpiler creates error-corrected circuits which produce the same result as the orinigal logical circuit. + +@pytest.mark.parametrize("code", ["shor"]) # "shor", double parametrize leads to crossproduct +@pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate"]) # , , "bv", "graphstate""qft"]) +@pytest.mark.parametrize("Error", [XGate(), ZGate()]) # , , "bv", "graphstate""qft"]) +@pytest.mark.parametrize("MeasureBaseX", [True, False]) # , , "bv", "graphstate""qft"]) +def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error, MeasureBaseX: bool) -> None: + """Ensures the transpiler creates error-corrected circuits which produce the same result as the orinigal logical circuit. Afterwards an error is introduced and the test checks, whether it is corrected. Iterates over a number of example algorithms. """ - if algorithm == 'qft' and code == "shor": + if algorithm == "qft" and code == "shor": # this takes a little longer.... return circuit_size = 3 # Initialize circuits logical_circuit = benchmark_generation.get_benchmark( - benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding=code - ) + benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding=code + ) if MeasureBaseX: logical_circuit = add_h_before_measurements(logical_circuit) - if algorithm == 'qft': + if algorithm == "qft": basis = ["h", "s", "t", "x", "z", "cx", "cz"] - logical_circuit = transpile( - logical_circuit, - basis_gates=basis - ) + logical_circuit = transpile(logical_circuit, basis_gates=basis) print(logical_circuit.decompose().count_ops()) + # Strip measure gates to avoid intermediate measurements collapsing the state before decoding + stripped_logical_circuit = QuantumCircuit(*logical_circuit.qregs) + for inst in logical_circuit.data: + if inst.operation.name != "measure": + stripped_logical_circuit.append(inst.operation, inst.qubits, []) + logical_circuit = stripped_logical_circuit + error_corrected_circuit = logical_circuit.copy() if code == "shor": transpiler = ShorTranspiler(error_corrected_circuit, add_syndromes=True) @@ -165,8 +172,8 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error error_induced_circuit = error_corrected_circuit.copy() # this is for inserting phase flip in steane after the first Hadamard - #error_induced_circuit = insert_error(error_induced_circuit ,gate=ZGate(), index=16) - #error_induced_circuit = insert_error(error_induced_circuit ,gate=XGate()) + # error_induced_circuit = insert_error(error_induced_circuit ,gate=ZGate(), index=16) + # error_induced_circuit = insert_error(error_induced_circuit ,gate=XGate()) error_induced_circuit = insert_error_after_barrier( error_corrected_circuit, barrier_label="Encoding", @@ -178,21 +185,28 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error corrected_counts, error_corrected_circuit = run_circuit(error_corrected_circuit) induced_counts, error_induced_circuit = run_circuit(error_induced_circuit) - logical_corrected_fidelity = compare_distributions(logical_circuit, error_corrected_circuit, logical_counts, corrected_counts, "none", code) - corrected_induced_fidelity = compare_distributions(error_corrected_circuit, error_induced_circuit, corrected_counts, induced_counts, code, code) + logical_corrected_fidelity = compare_distributions( + logical_circuit, error_corrected_circuit, logical_counts, corrected_counts, "none", code + ) + corrected_induced_fidelity = compare_distributions( + error_corrected_circuit, error_induced_circuit, corrected_counts, induced_counts, code, code + ) - #log_circuits({f'log_{code}_{algorithm}':logical_circuit, + # log_circuits({f'log_{code}_{algorithm}':logical_circuit, # f'corrected_{code}_{algorithm}':error_corrected_circuit, # f'induced_{code}_{algorithm}':error_induced_circuit,}) - #print(corrected_counts) - print('condensed:', condense_counts(error_corrected_circuit, corrected_counts)) - print('Logical', logical_counts) - + # print(corrected_counts) + print("condensed:", condense_counts(error_corrected_circuit, corrected_counts)) + print("Logical", logical_counts) + assert logical_corrected_fidelity >= 0.99, ( + f"Error corrected circuit created by {code} transpiler for Algorithm {algorithm} does not match its logical circuit well enough." + ) + assert corrected_induced_fidelity >= 0.99, ( + f"Error corrected circuit created by {code} transpiler for Algorithm {algorithm} does not correct the bitflip well enough." + ) - assert logical_corrected_fidelity >= 0.99, f"Error corrected circuit created by {code} transpiler for Algorithm {algorithm} does not match its logical circuit well enough." - assert corrected_induced_fidelity >= 0.99, f"Error corrected circuit created by {code} transpiler for Algorithm {algorithm} does not correct the bitflip well enough." def insert_error_after_barrier( qc: QuantumCircuit, @@ -203,26 +217,24 @@ def insert_error_after_barrier( qc = qc.copy() for i, instruction in enumerate(qc.data): - if ( - instruction.operation.name == "barrier" - and instruction.operation.label == barrier_label - ): + if instruction.operation.name == "barrier" and instruction.operation.label == barrier_label: qc.data.insert( i + 1, CircuitInstruction(gate, [qc.qubits[qubit_index]]), ) return qc - raise ValueError(f"Barrier with label {barrier_label!r} not found") + msg = f"Barrier with label {barrier_label!r} not found" + raise ValueError(msg) + def insert_error(qc: QuantumCircuit, gate: Gate = XGate(), index: int | None = None) -> QuantumCircuit: + """Adds the specified gate at the beginning of the circuit + Flips the first qubit right after the first barrier by default. """ - Adds the specified gate at the beginning of the circuit - Flips the first qubit right after the first barrier by default - """ - assert qc.num_qubits >= gate.num_qubits, f"Quantum Circuit has not enough qubits to accomodate gate {gate.name}" + assert qc.num_qubits >= gate.num_qubits, f"Quantum Circuit has not enough qubits to accommodate gate {gate.name}" assert index is None or index >= 0, f"Index must be >= 0, Index provided: {index}" - + # Finds the first barrier if index is None: for i, instruction in enumerate(qc.data): @@ -231,36 +243,30 @@ def insert_error(qc: QuantumCircuit, gate: Gate = XGate(), index: int | None = N break # Insert the error gate - qubits = qc.qubits[:gate.num_qubits] + qubits = qc.qubits[: gate.num_qubits] if index is not None: qc.data.insert(index, CircuitInstruction(gate, qubits)) else: - raise Exception("Please provide either an index or a circuit with a barrier to insert an error into") + msg = "Please provide either an index or a circuit with a barrier to insert an error into" + raise Exception(msg) return qc def check_equivalence(qc1: qk.QuantumCircuit, qc2: qk.QuantumCircuit) -> bool: - """ - Uses MQT QCEC to verify if qc1 and qc2 are equivalent - """ + """Uses MQT QCEC to verify if qc1 and qc2 are equivalent.""" import mqt.qcec from mqt.qcec.pyqcec import EquivalenceCriterion as EC verification_results = mqt.qcec.verify(qc1, qc2) - accepted_equivalencies = [ - EC.equivalent, - EC.equivalent_up_to_global_phase, - EC.probably_equivalent - ] - equivalent = verification_results.equivalence in accepted_equivalencies - return equivalent + accepted_equivalencies = [EC.equivalent, EC.equivalent_up_to_global_phase, EC.probably_equivalent] + return verification_results.equivalence in accepted_equivalencies + def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[dict, QuantumCircuit]: - """ - Simulates the circuit using AerSimulator. + """Simulates the circuit using AerSimulator. - Adds measurements to all qubits, adds new classical registers for each. + Adds measurements to all qubits, adds new classical registers for each. Reads out ONLY those measurements and returns their counts Returns: @@ -270,66 +276,65 @@ def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[dict, QuantumCir """ sampler = SamplerV2() qc.measure_all() - job = sampler.run([qc], shots=shots) + job = sampler.run([qc], shots=shots) result = job.result() # Grabbing only the desired outcomes pub_result = result[0] meas_bit_counts = pub_result.data.meas.get_counts() - # outputs reversed bitstrings, we just reverse them right back, + # outputs reversed bitstrings, we just reverse them right back, # so their indices align with the qubit indices meas_bit_counts = {k[::-1]: v for k, v in meas_bit_counts.items()} return meas_bit_counts, qc -def compare_distributions(qc1: QuantumCircuit, qc2: QuantumCircuit, counts1: dict, counts2: dict, code1: str = 'None', code2: str = 'None') -> float: - """ - Simulates 2 circuits and computes the Hellinger Fidelity between their count distributions - 1 = the same, 0 = no overlap + +def compare_distributions( + qc1: QuantumCircuit, qc2: QuantumCircuit, counts1: dict, counts2: dict, code1: str = "None", code2: str = "None" +) -> float: + """Simulates 2 circuits and computes the Hellinger Fidelity between their count distributions + 1 = the same, 0 = no overlap. If code is set to either 'steane' or 'shor' circuit error's result will be interpreted logically """ - if code1 in ['steane', 'shor']: + if code1 in ["steane", "shor"]: counts1 = condense_counts(qc1, counts1) - if code2 in ['steane', 'shor']: + if code2 in ["steane", "shor"]: counts2 = condense_counts(qc2, counts2) - fidelity = hellinger_fidelity(counts1, counts2) - return fidelity + return hellinger_fidelity(counts1, counts2) + def parse_qubits(qc: qk.QuantumCircuit, physical_qubits: str): - """ - Takes in a measurement in physical qubits and returns the corresponding logical measurement. - - Underlying circuit must use registers named 'qx' (x in int) for each logical qubit, with results in qx[0] - """ - # remove blanks caused by classical registers - physical_qubits = physical_qubits.replace(' ', '') - - # indices - import re - def is_q_integer(s: str) -> bool: - """ checks if s is of form 'qx' where x in int (e.g. 'q1', 'q23') """ - return bool(re.fullmatch(r'q\d+', s)) - - data_indices = [] - for register in qc.qregs: - if is_q_integer(register.name): - data_indices.append(qc.find_bit(register[0]).index) - - # condensing - logical_qubits = "" - for index in data_indices: - logical_qubits += physical_qubits[index] - - return logical_qubits - -def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, int]: + """Takes in a measurement in physical qubits and returns the corresponding logical measurement. + + Underlying circuit must use registers named 'qx' (x in int) for each logical qubit, with results in qx[0] """ - Takes in a result dict of a decoded physical measurement and returns logical measurements - Requires decode to place the result in the first qubit of each register named 'qx', with x an integer (e.g. 'q2') + # remove blanks caused by classical registers + physical_qubits = physical_qubits.replace(" ", "") + + # indices + import re + + def is_q_integer(s: str) -> bool: + """Checks if s is of form 'qx' where x in int (e.g. 'q1', 'q23').""" + return bool(re.fullmatch(r"q\d+", s)) + + data_indices = [qc.find_bit(register[0]).index for register in qc.qregs if is_q_integer(register.name)] + + # condensing + logical_qubits = "" + for index in data_indices: + logical_qubits += physical_qubits[index] + + return logical_qubits + + +def condense_counts(qc: qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, int]: + """Takes in a result dict of a decoded physical measurement and returns logical measurements + Requires decode to place the result in the first qubit of each register named 'qx', with x an integer (e.g. 'q2'). """ - #assert code in ['shor', 'steane'], f'Unsupported error code in condense_counts(): {code}' + # assert code in ['shor', 'steane'], f'Unsupported error code in condense_counts(): {code}' logical_counts = {} for physical_measurement, count in counts.items(): logical_measurement = parse_qubits(qc, physical_measurement) @@ -337,17 +342,17 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, i return logical_counts + def log_circuits(circuits: dict[str, QuantumCircuit]) -> None: log_dir = Path(__file__).parent / "circuit_drawings" log_dir.mkdir(exist_ok=True) for name, circuit in circuits.items(): - with open(log_dir / f"{name}_transpiled.txt", "w", encoding="utf-8") as f: + with Path(log_dir / f"{name}_transpiled.txt").open("w", encoding="utf-8") as f: f.write(f"number of qubits {circuit.num_qubits}\n") f.write(f"--- Transpiled Circuit for {name.upper()} ---\n\n") f.write(str(circuit.draw(fold=-1)) + "\n") - import matplotlib.pyplot as plt fig = circuit.draw(output="mpl", fold=-1) From 735cebbac3d597537eadeae01a9bf005842da1c6 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 6 Jun 2026 13:34:05 +0200 Subject: [PATCH 34/72] added measurement testcase --- main.py | 28 ++++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/main.py b/main.py index ab979d1d9..87d444051 100644 --- a/main.py +++ b/main.py @@ -234,7 +234,11 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, i h_circuit.z(0) #h_circuit.h(0) - logical_circuit = h_circuit + + measure_circuit = QuantumCircuit(1,1) + measure_circuit.measure(0,0) + + logical_circuit = measure_circuit #logical_circuit = benchmark_generation.get_benchmark( # benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding=code @@ -257,18 +261,18 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, i error_induced_circuit = insert_error(error_induced_circuit ,gate=XGate()) - #print(" __________________________________________________________________________________________ ") - #print('Logical Circuit:') - #print(logical_circuit) - #print(" __________________________________________________________________________________________ ") - #print('Error corrected Circuit:') - #print(error_corrected_circuit) - #print(" __________________________________________________________________________________________ ") - #print('Error Induced Circuit') - #print(error_induced_circuit) - #print(" __________________________________________________________________________________________ ") + print(" __________________________________________________________________________________________ ") + print('Logical Circuit:') + print(logical_circuit) + print(" __________________________________________________________________________________________ ") + print('Error corrected Circuit:') + print(error_corrected_circuit) + print(" __________________________________________________________________________________________ ") + print('Error Induced Circuit') + print(error_induced_circuit) + print(" __________________________________________________________________________________________ ") - print(check_equivalence(logical_circuit, error_corrected_circuit)) + #print(check_equivalence(logical_circuit, error_corrected_circuit)) #print(check_equivalence(error_corrected_circuit, error_induced_circuit)) logical_counts, logical_circuit = run_circuit(logical_circuit) From 135d7b124c043a29f75cdad6dfdfd7c1c01cfcd5 Mon Sep 17 00:00:00 2001 From: salehalsherif Date: Sat, 6 Jun 2026 13:40:38 +0200 Subject: [PATCH 35/72] Refactoring --- src/mqt/bench/benchmarks/shors_nine_qubit_code.py | 2 +- .../{error_correction => components}/shor_circuit_components.py | 0 src/mqt/bench/error_correction/shor_transpiler.py | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) rename src/mqt/bench/{error_correction => components}/shor_circuit_components.py (100%) diff --git a/src/mqt/bench/benchmarks/shors_nine_qubit_code.py b/src/mqt/bench/benchmarks/shors_nine_qubit_code.py index 04f7e59f6..752dcef32 100644 --- a/src/mqt/bench/benchmarks/shors_nine_qubit_code.py +++ b/src/mqt/bench/benchmarks/shors_nine_qubit_code.py @@ -12,7 +12,7 @@ from qiskit import ClassicalRegister from qiskit.circuit import AncillaRegister, QuantumCircuit, QuantumRegister -from mqt.bench.error_correction.shor_circuit_components import ( +from mqt.bench.component.shor_circuit_components import ( apply_nine_qubit_shors_code_bit_flip_correction, apply_nine_qubit_shors_code_phase_flip_correction, get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit, diff --git a/src/mqt/bench/error_correction/shor_circuit_components.py b/src/mqt/bench/components/shor_circuit_components.py similarity index 100% rename from src/mqt/bench/error_correction/shor_circuit_components.py rename to src/mqt/bench/components/shor_circuit_components.py diff --git a/src/mqt/bench/error_correction/shor_transpiler.py b/src/mqt/bench/error_correction/shor_transpiler.py index ec7540ad0..3abc13ecc 100644 --- a/src/mqt/bench/error_correction/shor_transpiler.py +++ b/src/mqt/bench/error_correction/shor_transpiler.py @@ -18,7 +18,7 @@ #ignore the below comment # these functions are reused from the benchmark and they should be extendable i.e. they shouldn't be private -from mqt.bench.error_correction.shor_circuit_components import ( +from mqt.bench.components.shor_circuit_components import ( apply_nine_qubit_shors_code_bit_flip_correction, apply_nine_qubit_shors_code_phase_flip_correction, get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit, From db68e108a81e25da3efce0ed19123a41423c0bb2 Mon Sep 17 00:00:00 2001 From: salehalsherif Date: Sat, 6 Jun 2026 13:48:15 +0200 Subject: [PATCH 36/72] added decode before measurements --- src/mqt/bench/error_correction/shor_transpiler.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/mqt/bench/error_correction/shor_transpiler.py b/src/mqt/bench/error_correction/shor_transpiler.py index 3abc13ecc..35791e229 100644 --- a/src/mqt/bench/error_correction/shor_transpiler.py +++ b/src/mqt/bench/error_correction/shor_transpiler.py @@ -195,6 +195,8 @@ def _logical_measure(self, logical_qubit_index: int, logical_classical_bit_index Classical post-processing would compute the majority vote across the 3 bit-flip blocks and then across the phase-flip blocks to extract the logical value. """ + ## decode + self._apply_shor_decoding(self.transpiled_qc, self.logical_qubits[logical_qubit_index].data) measurement_register_name = f"meas_{logical_qubit_index}_{logical_classical_bit_index}" physical_measurement_register = ClassicalRegister(SHOR_TOTAL_QUBITS, measurement_register_name) self.transpiled_qc.add_register(physical_measurement_register) From bcb06ad9eb3b7e7c2611ccedbed63a73c5bc0d0e Mon Sep 17 00:00:00 2001 From: "LAPTOP-N4TQ80AC\\khusa" Date: Sat, 6 Jun 2026 15:16:17 +0200 Subject: [PATCH 37/72] =?UTF-8?q?=E2=9C=85=20Add=20Steane=20components?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mqt/bench/benchmark_generation.py | 14 +- .../benchmarks/seven_qubit_steane_code.py | 133 +------- .../components/steane_circuit_components.py | 133 ++++++++ .../error_correction/steane_transpiler.py | 27 +- tests/test_error_correction.py | 2 +- tests/test_shor_equivalence.py | 156 --------- tests/test_shor_transpiler.py | 301 ------------------ tests/test_steane_transpiler.py | 54 ---- 8 files changed, 166 insertions(+), 654 deletions(-) create mode 100644 src/mqt/bench/components/steane_circuit_components.py delete mode 100644 tests/test_shor_equivalence.py delete mode 100644 tests/test_shor_transpiler.py delete mode 100644 tests/test_steane_transpiler.py diff --git a/src/mqt/bench/benchmark_generation.py b/src/mqt/bench/benchmark_generation.py index 3014a0399..13483790b 100644 --- a/src/mqt/bench/benchmark_generation.py +++ b/src/mqt/bench/benchmark_generation.py @@ -21,6 +21,8 @@ from qiskit.converters import circuit_to_dag from qiskit.transpiler import Layout, Target from typing_extensions import assert_never +from .error_correction.shor_transpiler import ShorTranspiler +from .error_correction.steane_transpiler import SteaneTranspiler if sys.version_info >= (3, 11): from typing import Unpack @@ -224,10 +226,14 @@ def get_benchmark_alg( if generate_mirror_circuit: return _create_mirror_circuit(qc, inplace=True) - #if encoding == "shor": - # return generate_shor(qc) - #elif encoding == "stean": - # return generate_stean(qc) + if encoding == "shor": + transpiler = ShorTranspiler(qc, add_syndromes=True) + transpiler.transpile() + return transpiler.transpiled_qc + elif encoding == "stean": + transpiler = SteaneTranspiler(qc, add_syndromes=True) + transpiler.transpile() + return transpiler.transpiled_qc return qc diff --git a/src/mqt/bench/benchmarks/seven_qubit_steane_code.py b/src/mqt/bench/benchmarks/seven_qubit_steane_code.py index c355774e9..a006f8902 100644 --- a/src/mqt/bench/benchmarks/seven_qubit_steane_code.py +++ b/src/mqt/bench/benchmarks/seven_qubit_steane_code.py @@ -15,125 +15,12 @@ from ._registry import register_benchmark - -def _get_seven_qubit_steane_code_encoding_circuit() -> QuantumCircuit: - """Create the 7-qubit Steane code encoding circuit. - - Encodes qubit 0 into the 7-qubit Steane code logical state: - - |0> -> (|0000000> + |1010101> + |0110011> + |1100110> + |0001111> + |1011010> + |0111100> + |1101001>) - - |1> -> (|1111111> + |0101010> + |1001100> + |0011001> + |1110000> + |0100101> + |1000011> + |0010110>). - - Returns: - QuantumCircuit: 7-qubit encoding circuit. - """ - out = QuantumCircuit(7) - # H - out.h(4) - out.h(5) - out.h(6) - # CNOT from 0 - out.cx(0, 1) - out.cx(0, 2) - # CNOT from 6 - out.cx(6, 3) - out.cx(6, 1) - out.cx(6, 0) - # CNOT from 5 - out.cx(5, 3) - out.cx(5, 2) - out.cx(5, 0) - # CNOT from 4 - out.cx(4, 3) - out.cx(4, 2) - out.cx(4, 1) - return out - - -def _get_seven_qubit_steane_code_decoding_circuit() -> QuantumCircuit: - """Create the 7-qubit Steane code decoding circuit. - - Reverses the encoding operation to extract the logical qubit back to qubit 0. - - Returns: - QuantumCircuit: 7-qubit decoding circuit (qubit 0 is the output qubit). - """ - return _get_seven_qubit_steane_code_encoding_circuit().inverse() - - -def _get_seven_qubit_steane_code_syndrome_extraction_circuit() -> QuantumCircuit: - """Create the syndrome extraction circuit for the 7-qubit Steane code. - - Extracts bit-flip and phase-flip syndromes using 6 ancilla qubits (3 for each type). - - Bit-flip syndrome extraction: - Syndrome bits measure the parity of specific qubit subsets corresponding to - the X-stabilizer generators. - - Phase-flip syndrome extraction: - Uses Hadamard gates to convert from Z to X basis, and control/target swapped - CNOTs to extract the phase-flip syndrome - - Syndrome mapping: The 3-bit syndrome value (1-7) directly identifies which - data qubit experienced an error. Syndrome 0 indicates no error. - - Returns: - QuantumCircuit: 13-qubit circuit (qubits 0-6 are data, 7-9 are bit-flip - syndrome ancillas, 10-12 are phase-flip syndrome ancillas). - """ - logical_qubit, bit_flip_syndrome, phase_flip_syndrome = QuantumRegister(7), AncillaRegister(3), AncillaRegister(3) - out = QuantumCircuit(logical_qubit, bit_flip_syndrome, phase_flip_syndrome) - # Bit-flip - for ctrl in (0, 2, 4, 6): - out.cx(logical_qubit[ctrl], bit_flip_syndrome[0]) - for ctrl in (1, 2, 5, 6): - out.cx(logical_qubit[ctrl], bit_flip_syndrome[1]) - for ctrl in (3, 4, 5, 6): - out.cx(logical_qubit[ctrl], bit_flip_syndrome[2]) - # Phase-flip - for i in range(3): - out.h(phase_flip_syndrome[i]) - for targ in (0, 2, 4, 6): - out.cx(phase_flip_syndrome[0], logical_qubit[targ]) - for targ in (1, 2, 5, 6): - out.cx(phase_flip_syndrome[1], logical_qubit[targ]) - for targ in (3, 4, 5, 6): - out.cx(phase_flip_syndrome[2], logical_qubit[targ]) - for i in range(3): - out.h(phase_flip_syndrome[i]) - return out - - -def _apply_seven_qubit_steane_code_correction( - qc: QuantumCircuit, - logical_qubit: QuantumRegister, - bit_flip_syndrome: AncillaRegister, - phase_flip_syndrome: AncillaRegister, - bit_flip_syndrome_measurement: ClassicalRegister, - phase_flip_syndrome_measurement: ClassicalRegister, -) -> None: - """Apply error correction based on syndrome measurements. - - Measures the 6 syndrome qubits and conditionally applies X/Z gates to correct - single-qubit errors on any of the 7 data qubits. - - Arguments: - qc: The quantum circuit to modify. - logical_qubit: Register containing the 7 data qubits. - bit_flip_syndrome: Register containing the 3 bit-flip syndrome qubits. - phase_flip_syndrome: Register containing the 3 phase-flip syndrome qubits. - bit_flip_syndrome_measurement: Classical register for bit-flip syndrome results. - phase_flip_syndrome_measurement: Classical register for phase-flip syndrome results. - """ - qc.measure(bit_flip_syndrome, bit_flip_syndrome_measurement) - qc.measure(phase_flip_syndrome, phase_flip_syndrome_measurement) - # Bit-flip correction: syndrome value directly indicates which qubit to correct - for i in range(7): - with qc.if_test((bit_flip_syndrome_measurement, i + 1)): - qc.x(logical_qubit[i]) - # Phase-flip correction: syndrome value directly indicates which qubit to correct - for i in range(7): - with qc.if_test((phase_flip_syndrome_measurement, i + 1)): - qc.z(logical_qubit[i]) +from mqt.bench.components.steane_circuit_components import ( + get_seven_qubit_steane_code_encoding_circuit, + get_seven_qubit_steane_code_decoding_circuit, + get_seven_qubit_steane_code_syndrome_extraction_circuit, + apply_seven_qubit_steane_code_correction +) def _create_single_logical_qubit_circuit(index: int) -> QuantumCircuit: @@ -164,20 +51,20 @@ def _create_single_logical_qubit_circuit(index: int) -> QuantumCircuit: ) # == Encoding == qc.compose( - _get_seven_qubit_steane_code_encoding_circuit(), + get_seven_qubit_steane_code_encoding_circuit(), qubits=logical_qubit[:], inplace=True, ) qc.barrier() # == Syndrome extraction == qc.compose( - _get_seven_qubit_steane_code_syndrome_extraction_circuit(), + get_seven_qubit_steane_code_syndrome_extraction_circuit(), qubits=logical_qubit[:] + bit_flip_syndrome[:] + phase_flip_syndrome[:], inplace=True, ) qc.barrier() # == Error correction == - _apply_seven_qubit_steane_code_correction( + apply_seven_qubit_steane_code_correction( qc, logical_qubit, bit_flip_syndrome, @@ -188,7 +75,7 @@ def _create_single_logical_qubit_circuit(index: int) -> QuantumCircuit: qc.barrier() # == Decoding == qc.compose( - _get_seven_qubit_steane_code_decoding_circuit(), + get_seven_qubit_steane_code_decoding_circuit(), qubits=logical_qubit[:], inplace=True, ) diff --git a/src/mqt/bench/components/steane_circuit_components.py b/src/mqt/bench/components/steane_circuit_components.py new file mode 100644 index 000000000..b5b6c3856 --- /dev/null +++ b/src/mqt/bench/components/steane_circuit_components.py @@ -0,0 +1,133 @@ +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + +"""Shor's 9-qubit code circuit components.""" + +from __future__ import annotations + + +from qiskit.circuit import AncillaRegister, QuantumCircuit, QuantumRegister, ClassicalRegister + +def get_seven_qubit_steane_code_encoding_circuit() -> QuantumCircuit: + """Create the 7-qubit Steane code encoding circuit. + + Encodes qubit 0 into the 7-qubit Steane code logical state: + - |0> -> (|0000000> + |1010101> + |0110011> + |1100110> + |0001111> + |1011010> + |0111100> + |1101001>) + - |1> -> (|1111111> + |0101010> + |1001100> + |0011001> + |1110000> + |0100101> + |1000011> + |0010110>). + + Returns: + QuantumCircuit: 7-qubit encoding circuit. + """ + out = QuantumCircuit(7) + # H + out.h(4) + out.h(5) + out.h(6) + # CNOT from 0 + out.cx(0, 1) + out.cx(0, 2) + # CNOT from 6 + out.cx(6, 3) + out.cx(6, 1) + out.cx(6, 0) + # CNOT from 5 + out.cx(5, 3) + out.cx(5, 2) + out.cx(5, 0) + # CNOT from 4 + out.cx(4, 3) + out.cx(4, 2) + out.cx(4, 1) + return out + + +def get_seven_qubit_steane_code_decoding_circuit() -> QuantumCircuit: + """Create the 7-qubit Steane code decoding circuit. + + Reverses the encoding operation to extract the logical qubit back to qubit 0. + + Returns: + QuantumCircuit: 7-qubit decoding circuit (qubit 0 is the output qubit). + """ + return get_seven_qubit_steane_code_encoding_circuit().inverse() + + +def get_seven_qubit_steane_code_syndrome_extraction_circuit() -> QuantumCircuit: + """Create the syndrome extraction circuit for the 7-qubit Steane code. + + Extracts bit-flip and phase-flip syndromes using 6 ancilla qubits (3 for each type). + + Bit-flip syndrome extraction: + Syndrome bits measure the parity of specific qubit subsets corresponding to + the X-stabilizer generators. + + Phase-flip syndrome extraction: + Uses Hadamard gates to convert from Z to X basis, and control/target swapped + CNOTs to extract the phase-flip syndrome + + Syndrome mapping: The 3-bit syndrome value (1-7) directly identifies which + data qubit experienced an error. Syndrome 0 indicates no error. + + Returns: + QuantumCircuit: 13-qubit circuit (qubits 0-6 are data, 7-9 are bit-flip + syndrome ancillas, 10-12 are phase-flip syndrome ancillas). + """ + logical_qubit, bit_flip_syndrome, phase_flip_syndrome = QuantumRegister(7), AncillaRegister(3), AncillaRegister(3) + out = QuantumCircuit(logical_qubit, bit_flip_syndrome, phase_flip_syndrome) + # Bit-flip + for ctrl in (0, 2, 4, 6): + out.cx(logical_qubit[ctrl], bit_flip_syndrome[0]) + for ctrl in (1, 2, 5, 6): + out.cx(logical_qubit[ctrl], bit_flip_syndrome[1]) + for ctrl in (3, 4, 5, 6): + out.cx(logical_qubit[ctrl], bit_flip_syndrome[2]) + # Phase-flip + for i in range(3): + out.h(phase_flip_syndrome[i]) + for targ in (0, 2, 4, 6): + out.cx(phase_flip_syndrome[0], logical_qubit[targ]) + for targ in (1, 2, 5, 6): + out.cx(phase_flip_syndrome[1], logical_qubit[targ]) + for targ in (3, 4, 5, 6): + out.cx(phase_flip_syndrome[2], logical_qubit[targ]) + for i in range(3): + out.h(phase_flip_syndrome[i]) + return out + + +def apply_seven_qubit_steane_code_correction( + qc: QuantumCircuit, + logical_qubit: QuantumRegister, + bit_flip_syndrome: AncillaRegister, + phase_flip_syndrome: AncillaRegister, + bit_flip_syndrome_measurement: ClassicalRegister, + phase_flip_syndrome_measurement: ClassicalRegister, +) -> None: + """Apply error correction based on syndrome measurements. + + Measures the 6 syndrome qubits and conditionally applies X/Z gates to correct + single-qubit errors on any of the 7 data qubits. + + Arguments: + qc: The quantum circuit to modify. + logical_qubit: Register containing the 7 data qubits. + bit_flip_syndrome: Register containing the 3 bit-flip syndrome qubits. + phase_flip_syndrome: Register containing the 3 phase-flip syndrome qubits. + bit_flip_syndrome_measurement: Classical register for bit-flip syndrome results. + phase_flip_syndrome_measurement: Classical register for phase-flip syndrome results. + """ + qc.measure(bit_flip_syndrome, bit_flip_syndrome_measurement) + qc.measure(phase_flip_syndrome, phase_flip_syndrome_measurement) + # Bit-flip correction: syndrome value directly indicates which qubit to correct + for i in range(7): + with qc.if_test((bit_flip_syndrome_measurement, i + 1)): + qc.x(logical_qubit[i]) + # Phase-flip correction: syndrome value directly indicates which qubit to correct + for i in range(7): + with qc.if_test((phase_flip_syndrome_measurement, i + 1)): + qc.z(logical_qubit[i]) \ No newline at end of file diff --git a/src/mqt/bench/error_correction/steane_transpiler.py b/src/mqt/bench/error_correction/steane_transpiler.py index 73f9c5264..c4a2fc86c 100644 --- a/src/mqt/bench/error_correction/steane_transpiler.py +++ b/src/mqt/bench/error_correction/steane_transpiler.py @@ -15,14 +15,11 @@ from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister from qiskit.circuit import AncillaRegister, barrier -# ruff: noqa: PLC2701 -# these functions are reused from the benchmark and they should be extendable i.e. they shouldn't be private -# TODO ask Patrick how to solve this issue, e.g. need to move functionality from benchmarks into dedicated helper class -from mqt.bench.benchmarks.seven_qubit_steane_code import ( - _get_seven_qubit_steane_code_encoding_circuit, - _get_seven_qubit_steane_code_decoding_circuit, - _get_seven_qubit_steane_code_syndrome_extraction_circuit, - _apply_seven_qubit_steane_code_correction +from mqt.bench.components.steane_circuit_components import ( + get_seven_qubit_steane_code_encoding_circuit, + get_seven_qubit_steane_code_decoding_circuit, + get_seven_qubit_steane_code_syndrome_extraction_circuit, + apply_seven_qubit_steane_code_correction ) if TYPE_CHECKING: @@ -91,7 +88,7 @@ def encode_qubits(self) -> None: # Phase flip encoding on the first qubit of each block self.transpiled_qc.compose( - _get_seven_qubit_steane_code_encoding_circuit(), + get_seven_qubit_steane_code_encoding_circuit(), qubits=physical_data_register[:], inplace=True, ) @@ -102,7 +99,7 @@ def decode_qubits(self) -> None: self.transpiled_qc.barrier() for logical_qubit_index in range(self.num_logical_qubits): physical_data_register = self.physical_data_registers[logical_qubit_index] - self.transpiled_qc.compose(_get_seven_qubit_steane_code_decoding_circuit(), + self.transpiled_qc.compose(get_seven_qubit_steane_code_decoding_circuit(), qubits=physical_data_register[:], inplace=True ) @@ -148,7 +145,7 @@ def _handle_measure(self, instruction: CircuitInstruction) -> None: logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[i]) logical_classical_bit_index = self.original_qc.clbits.index(instruction.clbits[i]) - self.transpiled_qc.compose(_get_seven_qubit_steane_code_decoding_circuit(), + self.transpiled_qc.compose(get_seven_qubit_steane_code_decoding_circuit(), qubits=self.physical_data_registers[logical_qubit_index], inplace=True ) @@ -217,7 +214,7 @@ def _handle_t(self, instruction: CircuitInstruction) -> None: # make ket 0 L self.transpiled_qc.compose( - _get_seven_qubit_steane_code_encoding_circuit(), + get_seven_qubit_steane_code_encoding_circuit(), qubits=t_ancilla_register[:], inplace=True, ) @@ -232,7 +229,7 @@ def _handle_t(self, instruction: CircuitInstruction) -> None: self.transpiled_qc.cx(physical_data_register, t_ancilla_register) # made logical measurement - self.transpiled_qc.compose(_get_seven_qubit_steane_code_decoding_circuit(), + self.transpiled_qc.compose(get_seven_qubit_steane_code_decoding_circuit(), qubits=t_ancilla_register, inplace=True ) @@ -304,7 +301,7 @@ def insert_syndromes(self, logical_qubit_index: int) -> None: # Syndrome extraction self.transpiled_qc.compose( - _get_seven_qubit_steane_code_syndrome_extraction_circuit(), + get_seven_qubit_steane_code_syndrome_extraction_circuit(), qubits=physical_data_register[:] + bit_flip_syndrome_register[:] + phase_flip_syndrome_register[:], inplace=True, @@ -314,7 +311,7 @@ def insert_syndromes(self, logical_qubit_index: int) -> None: # Error correction - _apply_seven_qubit_steane_code_correction( + apply_seven_qubit_steane_code_correction( self.transpiled_qc, physical_data_register, bit_flip_syndrome_register, diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index 0225c9bec..656da39d9 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -127,7 +127,7 @@ def add_h_before_measurements(qc: QuantumCircuit) -> QuantumCircuit: return new_qc -@pytest.mark.parametrize("code", ["shor"]) # "shor", double parametrize leads to crossproduct +@pytest.mark.parametrize("code", ["shor", "steane"]) # "shor", double parametrize leads to crossproduct @pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate"]) # , , "bv", "graphstate""qft"]) @pytest.mark.parametrize("Error", [XGate(), ZGate()]) # , , "bv", "graphstate""qft"]) @pytest.mark.parametrize("MeasureBaseX", [True, False]) # , , "bv", "graphstate""qft"]) diff --git a/tests/test_shor_equivalence.py b/tests/test_shor_equivalence.py deleted file mode 100644 index 433a974d1..000000000 --- a/tests/test_shor_equivalence.py +++ /dev/null @@ -1,156 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -"""Equivalence tests for Shor Transpiler gates.""" - -from __future__ import annotations - -from pathlib import Path - -import numpy as np -import pytest -from qiskit import QuantumCircuit -from qiskit.exceptions import QiskitError -from qiskit.quantum_info import DensityMatrix, Statevector, partial_trace, state_fidelity -from qiskit_aer import AerSimulator - -from mqt.bench.error_correction.shor_transpiler import ShorTranspiler - - -def verify_gate_equivalence(gate_name: str, num_qubits: int) -> None: - """Verify that a transpiled gate is mathematically equivalent to the logical gate. - - Args: - gate_name: The name of the gate to test ('h', 'x', 'z', 's', 't', 'cx', 'cz'). - num_qubits: The number of qubits the gate acts on. - """ - # Create the logical circuit and initialize it in a non-trivial state (|+> state) - qc_logical = QuantumCircuit(num_qubits) - # Apply the gate - if gate_name == "h": - qc_logical.h(0) - elif gate_name == "x": - qc_logical.x(0) - elif gate_name == "z": - qc_logical.z(0) - elif gate_name == "s": - qc_logical.s(0) - elif gate_name == "t": - qc_logical.t(0) - elif gate_name == "cx": - qc_logical.cx(0, 1) - elif gate_name == "cz": - qc_logical.cz(0, 1) - else: - msg = f"Unknown gate {gate_name}" - raise ValueError(msg) - - # Get the expected density matrix - qc_logical.copy() - # expected logical state - expected_sv_init = Statevector.from_instruction(qc_logical) - - # Transpile the circuit - # We set add_syndromes=False to prevent statevector simulation from blowing up in memory - transpiler = ShorTranspiler(qc_logical, add_syndromes=False) - transpiled_qc = transpiler.transpile() - - drawing = transpiled_qc.draw(fold=-1) - print(f"\n--- Transpiled Circuit for {gate_name.upper()} ---") - print(drawing) - - # Save to file to ensure it can be viewed regardless of pytest-xdist capturing stdout - output_dir = Path("tests/circuit_drawings") - output_dir.mkdir(parents=True, exist_ok=True) - with Path(output_dir / f"{gate_name}_transpiled.txt").open("w", encoding="utf-8") as f: - f.write(f"number of qubits {num_qubits}\n") - f.write(f"--- Transpiled Circuit for {gate_name.upper()} ---\n\n") - f.write(str(drawing) + "\n") - # Apply decoding so the logical state collapses back to the first physical qubit of each block - transpiler.decode_qubits() - - transpiled_qc.save_statevector() - - sim = AerSimulator(method="statevector") - result_transpiled = sim.run(transpiled_qc).result() - assert result_transpiled.success, f"Simulation failed: {result_transpiled.status}" - actual_sv = result_transpiled.get_statevector() - - # Extract the density matrix of the physical qubits holding the logical state - # These are the 0-th qubits of each physical data register - logical_qubits_physical = [ - transpiled_qc.find_bit(transpiler.physical_data_registers[i][0]).index - for i in range(num_qubits) - ] - - all_qubits = list(range(transpiled_qc.num_qubits)) - trace_qubits = [q for q in all_qubits if q not in logical_qubits_physical] - - actual_rho = partial_trace(actual_sv, trace_qubits) - - expected_rho_init = DensityMatrix(expected_sv_init) - - try: - actual_sv_reduced = actual_rho.to_statevector() - except QiskitError: - actual_sv_reduced = None - - # Compare the density matrices - fidelity = state_fidelity(expected_sv_init, actual_rho) - - # Save the resulting density matrices and state vectors to the text file for visual inspection - with Path(output_dir / f"{gate_name}_transpiled.txt").open("a", encoding="utf-8") as f: - f.write("\n\n=== LOGICAL EXPECTED DENSITY MATRIX ===\n") - f.write(str(np.round(expected_rho_init.data, 3)) + "\n") - f.write("\n=== ACTUAL TRANSPILED DENSITY MATRIX (REDUCED) ===\n") - f.write(str(np.round(actual_rho.data, 3)) + "\n") - - if actual_sv_reduced is not None: - f.write("\n\n=== LOGICAL EXPECTED STATE VECTOR ===\n") - f.write(str(np.round(expected_sv_init.data, 3)) + "\n") - f.write("\n=== ACTUAL TRANSPILED STATE VECTOR (REDUCED) ===\n") - f.write(str(np.round(actual_sv_reduced.data, 3)) + "\n") - - f.write(f"\nSTATE FIDELITY: {fidelity:.6f}\n") - - assert fidelity > 0.999, f"Fidelity too low: {fidelity}" - - -def test_h_equivalence() -> None: - """Test equivalence for logical H gate.""" - verify_gate_equivalence("h", 1) - - -def test_x_equivalence() -> None: - """Test equivalence for logical X gate.""" - verify_gate_equivalence("x", 1) - - -def test_z_equivalence() -> None: - """Test equivalence for logical Z gate.""" - verify_gate_equivalence("z", 1) - - -def test_s_equivalence() -> None: - """Test equivalence for logical S gate.""" - verify_gate_equivalence("s", 1) - - -@pytest.mark.skip(reason="Slow test, takes ~1-2 mins due to 27 qubit simulation") -def test_t_equivalence() -> None: - """Test equivalence for logical T gate.""" - verify_gate_equivalence("t", 1) - - -def test_cx_equivalence() -> None: - """Test equivalence for logical CX gate.""" - verify_gate_equivalence("cx", 2) - -def test_cz_equivalence() -> None: - """Test equivalence for logical CZ gate.""" - verify_gate_equivalence("cz", 2) diff --git a/tests/test_shor_transpiler.py b/tests/test_shor_transpiler.py deleted file mode 100644 index 2c3c41c13..000000000 --- a/tests/test_shor_transpiler.py +++ /dev/null @@ -1,301 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -"""Tests for Shor Transpiler.""" - -from __future__ import annotations - -import numpy as np -from qiskit import QuantumCircuit, QuantumRegister -import pytest -from mqt.bench.error_correction.shor_transpiler import ShorTranspiler - - -# this needs mpre tests -def test_shor_transpiler() -> None: - """Test that ShorTranspiler successfully transpiles a basic circuit.""" - qc = QuantumCircuit(2, 1) - qc.x(0) - qc.z(1) - qc.measure(1, 0) - - print("\n--- Logical Circuit ---") - print(qc.draw(fold=-1)) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - print("\n--- Transpiled Circuit ---") - print(transpiled_qc) - - # 2 original qubits * 9 data qubits = 18 data qubits - # 2 original qubits * 8 ancilla qubits = 16 ancilla qubits - # Total qubits = 34 - assert transpiled_qc.num_qubits == 34 - - # 2 original qubits * 8 classical bits = 16 syndrome bits - # 1 measurement * 9 bits = 9 measurement bits - # Total clbits = 25 - assert transpiled_qc.num_clbits == 25 - ##wrong expectations in the test it should check only non error correction gate mapping - ops = [inst.operation.name for inst in transpiled_qc.data] - assert "h" in ops - assert "cx" in ops - assert "measure" in ops - - -def test_shor_transpiler_unsupported_gate() -> None: - """Test that unsupported gates raise NotImplementedError.""" - qc = QuantumCircuit(1) - qc.rx(0, 0) - - transpiler = ShorTranspiler(qc) - with pytest.raises(NotImplementedError, match=r"Gate rx is not supported by ShorTranspiler\."): - transpiler.transpile() - - -def test_shor_transpiler_s_gate_structure() -> None: - """Test that the S gate teleportation circuit has the correct structure. - - Verifies that: - - A magic state ancilla register (9 qubits) is allocated. - - The teleportation measurement register (1 classical bit) is allocated. - - The circuit contains the expected gates (h, s, cx, measure). - """ - qc = QuantumCircuit(1) - qc.s(0) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - # Original: 9 data + 6 bit-flip ancilla + 2 phase-flip ancilla = 17 - # S gate adds: 9 magic data = 9 - # Total = 26 - assert transpiled_qc.num_qubits == 26 - - # Original: 6 bf meas + 2 pf meas = 8 - # S gate adds: 1 teleport meas = 1 - # Total = 9 - assert transpiled_qc.num_clbits == 9 - - ops = [inst.operation.name for inst in transpiled_qc.data] - - # Magic state prep uses h and p on the ancilla qubit - assert "p" in ops - assert "h" in ops - - # Teleportation uses cx, measure - assert "cx" in ops - assert "measure" in ops - - # Conditional correction uses if_else - assert "if_else" in ops - - # Verify the magic state register exists - reg_names = [reg.name for reg in transpiled_qc.qregs] - assert "ms0" in reg_names - - # Verify teleportation measurement register exists - creg_names = [reg.name for reg in transpiled_qc.cregs] - assert "tmeas0" in creg_names - - -def test_shor_transpiler_s_gate_followed_by_other_gates() -> None: - """Test that gates applied after the S gate target the correct register. - - After S gate teleportation, subsequent gates should operate on the original - data register since the teleportation gadget doesn't swap the pointers. - """ - qc = QuantumCircuit(1, 1) - qc.s(0) - qc.z(0) - qc.measure(0, 0) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - # Circuit should compile and run without errors - assert transpiled_qc.num_qubits > 0 - assert transpiled_qc.num_clbits > 0 - - ops = [inst.operation.name for inst in transpiled_qc.data] - assert "p" in ops # Magic state prep (phase) - assert "x" in ops # Logical Z uses physical X - assert "measure" in ops - - -def test_shor_transpiler_multiple_s_gates() -> None: - """Test that multiple S gates each allocate independent ancilla blocks. - - Two consecutive S gates should produce two independent magic state - ancilla blocks (ms0 and ms1). - """ - qc = QuantumCircuit(1) - qc.s(0) - qc.s(0) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - reg_names = [reg.name for reg in transpiled_qc.qregs] - assert "ms0" in reg_names - assert "ms1" in reg_names - - -def test_shor_transpiler_t_gate_structure() -> None: - """Test that the T gate teleportation circuit has the correct structure. - - Verifies that: - - A magic state ancilla register for T (9 qubits) is allocated. - - The teleportation measurement register for T (1 classical bit) is allocated. - - Because of the S correction, another S ancilla and measurement are also allocated conditionally. - """ - qc = QuantumCircuit(1) - qc.t(0) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - # Original: 17 qubits - # T gate adds: 9 magic data for T + 9 magic data for S = 18 - # Total = 35 - assert transpiled_qc.num_qubits == 35 - - # Original: 8 clbits - # T gate adds: 1 for T + 1 for S = 2 - # Total = 10 - assert transpiled_qc.num_clbits == 10 - - ops = [inst.operation.name for inst in transpiled_qc.data] - - # Magic state prep uses p and h - assert "p" in ops - assert "h" in ops - - # Verify the T-magic state register exists - reg_names = [reg.name for reg in transpiled_qc.qregs] - assert "anc_t_1" in reg_names - assert "ms0" in reg_names # from the S correction - - -def test_shor_transpiler_barrier() -> None: - """Test logical barrier translates to physical barrier on all involved qubits.""" - qc = QuantumCircuit(2) - qc.barrier(0) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - ops = [inst.operation.name for inst in transpiled_qc.data] - assert "barrier" in ops - - -def test_shor_transpiler_measure() -> None: - """Test logical measure maps to 9 physical measurements.""" - qc = QuantumCircuit(1, 1) - qc.measure(0, 0) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - measure_count = sum(1 for inst in transpiled_qc.data if inst.operation.name == "measure") - # At least 9 physical measurements for the single logical measurement - assert measure_count >= 9 - - -def test_shor_transpiler_h_gate() -> None: - """Test logical H gate.""" - qc = QuantumCircuit(1) - qc.h(0) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - ops = [inst.operation.name for inst in transpiled_qc.data] - assert "h" in ops - assert "swap" in ops - - -def test_shor_transpiler_x_gate() -> None: - """Test logical X gate uses Z transversally.""" - qc = QuantumCircuit(1) - qc.x(0) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - ops = [inst.operation.name for inst in transpiled_qc.data] - # Shor code logical X = Z_0 Z_3 Z_6 - assert "z" in ops - - -def test_shor_transpiler_z_gate() -> None: - """Test logical Z gate uses X transversally.""" - qc = QuantumCircuit(1) - qc.z(0) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - ops = [inst.operation.name for inst in transpiled_qc.data] - # Shor code logical Z = X_0 X_1 X_2 - assert "x" in ops - - -def test_shor_transpiler_cx_gate() -> None: - """Test logical CX gate.""" - qc = QuantumCircuit(2) - qc.cx(0, 1) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - ops = [inst.operation.name for inst in transpiled_qc.data] - assert "cx" in ops - - -def test_shor_transpiler_cz_gate() -> None: - """Test logical CZ gate.""" - qc = QuantumCircuit(2) - qc.cz(0, 1) - - transpiler = ShorTranspiler(qc) - transpiled_qc = transpiler.transpile() - - ops = [inst.operation.name for inst in transpiled_qc.data] - # CZ is implemented via H, CX, H - assert "h" in ops - assert "cx" in ops - assert "swap" in ops - - -def test_shor_transpiler_encode_decode() -> None: - """Test static encoding and decoding methods directly.""" - qc = QuantumCircuit() - reg = QuantumRegister(9, "q") - qc.add_register(reg) - - ShorTranspiler._apply_shor_encoding(qc, reg) - ops_enc = [inst.operation.name for inst in qc.data] - assert len(ops_enc) > 0 - - ShorTranspiler._apply_shor_decoding(qc, reg) - ops_dec = [inst.operation.name for inst in qc.data] - assert len(ops_dec) > len(ops_enc) - - -def test_shor_transpiler_prepare_magic() -> None: - """Test _prepare_magic directly.""" - qc = QuantumCircuit() - anc = QuantumRegister(9, "anc") - qc.add_register(anc) - - ShorTranspiler._prepare_magic(qc, anc, np.pi/2) - ops = [inst.operation.name for inst in qc.data] - assert "h" in ops - assert "p" in ops diff --git a/tests/test_steane_transpiler.py b/tests/test_steane_transpiler.py deleted file mode 100644 index 6ca267b3f..000000000 --- a/tests/test_steane_transpiler.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -"""Tests for Shor Transpiler.""" - -from __future__ import annotations - -#import pytest -from qiskit import QuantumCircuit, QuantumRegister, ClassicalRegister -import matplotlib.pyplot as plt - -from mqt.bench.error_correction.steane_transpiler import SteaneTranspiler - -def test_steane_transpiler() -> None: - """Test that ShorTranspiler successfully transpiles a basic circuit.""" - qc = QuantumCircuit(QuantumRegister(2), ClassicalRegister(1)) - qc.cx(0,1) - #qc.measure(0,0) - - print("\n--- Logical Circuit ---") - print(qc.draw(fold=-1)) - - transpiler = SteaneTranspiler(qc, False) - transpiled_qc = transpiler.transpile() - transpiled_qc.draw("mpl", fold=-1) - #plt.show() - plt.savefig("circuit.png", dpi=300, bbox_inches="tight") - - #print("\n--- Transpiled Circuit ---") - #print(transpiled_qc) - - - - # 2 original qubits * 9 data qubits = 18 data qubits - # 2 original qubits * 8 ancilla qubits = 16 ancilla qubits - # Total qubits = 34 - #assert transpiled_qc.num_qubits == 34 - - # 2 original qubits * 8 classical bits = 16 syndrome bits - # 1 measurement * 9 bits = 9 measurement bits - # Total clbits = 25 - #assert transpiled_qc.num_clbits == 25 - ##wrong expectations in the test it should check only non error correction gate mapping - ops = [inst.operation.name for inst in transpiled_qc.data] - #assert "h" in ops - #assert "cx" in ops - #assert "measure" in ops - - From 2265c5ae0f8ce8ae260d57f06f91eccf5bd20316 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Thu, 11 Jun 2026 18:35:38 +0200 Subject: [PATCH 38/72] Movedcircuit running --- main.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/main.py b/main.py index 87d444051..6526e31b3 100644 --- a/main.py +++ b/main.py @@ -261,6 +261,14 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, i error_induced_circuit = insert_error(error_induced_circuit ,gate=XGate()) + #print(check_equivalence(logical_circuit, error_corrected_circuit)) + #print(check_equivalence(error_corrected_circuit, error_induced_circuit)) + + logical_counts, logical_circuit = run_circuit(logical_circuit) + corrected_counts, error_corrected_circuit = run_circuit(error_corrected_circuit) + induced_counts, error_induced_circuit = run_circuit(error_induced_circuit) + + print(" __________________________________________________________________________________________ ") print('Logical Circuit:') print(logical_circuit) @@ -272,12 +280,6 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, i print(error_induced_circuit) print(" __________________________________________________________________________________________ ") - #print(check_equivalence(logical_circuit, error_corrected_circuit)) - #print(check_equivalence(error_corrected_circuit, error_induced_circuit)) - - logical_counts, logical_circuit = run_circuit(logical_circuit) - corrected_counts, error_corrected_circuit = run_circuit(error_corrected_circuit) - induced_counts, error_induced_circuit = run_circuit(error_induced_circuit) print(compare_distributions(logical_circuit, error_corrected_circuit, logical_counts, corrected_counts, 'none', code)) From d3517db43f408245c4d955c7e993fd3d607932b9 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Thu, 11 Jun 2026 20:47:36 +0200 Subject: [PATCH 39/72] + Added funcionality to create gate_counts.json + early gate_counts.json, counts for steane are not yet correct --- main.py | 42 ++++- tests/gate_counts.json | 340 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 379 insertions(+), 3 deletions(-) create mode 100644 tests/gate_counts.json diff --git a/main.py b/main.py index 6526e31b3..007d3cc23 100644 --- a/main.py +++ b/main.py @@ -204,8 +204,7 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, i return logical_counts - -if __name__ == "__main__": +def old_main(): for alg in ["ghz", "bv", "graphstate"]: # add QFT for code in ["shor", "stean"]: # for qubits in range(3, 5): @@ -283,4 +282,41 @@ def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, i print(compare_distributions(logical_circuit, error_corrected_circuit, logical_counts, corrected_counts, 'none', code)) - print(compare_distributions(error_corrected_circuit, error_induced_circuit, corrected_counts, induced_counts, code, code)) \ No newline at end of file + print(compare_distributions(error_corrected_circuit, error_induced_circuit, corrected_counts, induced_counts, code, code)) + + +def create_gate_counts(): + """ + Use this to create the counts for each code, algorithm and arbitray qubit numbers + """ + import json + from pathlib import Path + gates = {} + algs = {} + qs = {} + for code in ["shor", "steane"]: + algs = {} + for alg in ["ghz", "bv", "graphstate"]:#, "qft"]: # Bonus for "qft" (Part 3) + qs = {} + for qubits in range(3, 10): + qc = benchmark_generation.get_benchmark( + benchmark=alg, + level=benchmark_generation.BenchmarkLevel.ALG, + circuit_size=qubits, + encoding=code) + qs[qubits] = qc.count_ops() + algs[alg] = qs + gates[code] = algs + + log_dir = Path(__file__).parent / "tests" + log_dir.mkdir(exist_ok=True) + + filename = log_dir / f"gate_counts.json" + with open(filename, "w") as f: + json.dump(gates, f, indent=4) + + +if __name__ == "__main__": + #old_main() + + create_gate_counts() diff --git a/tests/gate_counts.json b/tests/gate_counts.json new file mode 100644 index 000000000..7c370cd55 --- /dev/null +++ b/tests/gate_counts.json @@ -0,0 +1,340 @@ +{ + "shor": { + "ghz": { + "3": { + "cx": 186, + "measure": 67, + "if_else": 60, + "h": 47, + "reset": 40, + "barrier": 27, + "swap": 3 + }, + "4": { + "cx": 259, + "measure": 92, + "if_else": 84, + "h": 61, + "reset": 56, + "barrier": 37, + "swap": 3 + }, + "5": { + "cx": 332, + "measure": 117, + "if_else": 108, + "h": 75, + "reset": 72, + "barrier": 47, + "swap": 3 + }, + "6": { + "cx": 405, + "measure": 142, + "if_else": 132, + "h": 89, + "reset": 88, + "barrier": 57, + "swap": 3 + }, + "7": { + "cx": 478, + "measure": 167, + "if_else": 156, + "reset": 104, + "h": 103, + "barrier": 67, + "swap": 3 + }, + "8": { + "cx": 551, + "measure": 192, + "if_else": 180, + "reset": 120, + "h": 117, + "barrier": 77, + "swap": 3 + }, + "9": { + "cx": 624, + "measure": 217, + "if_else": 204, + "reset": 136, + "h": 131, + "barrier": 87, + "swap": 3 + } + }, + "bv": { + "3": { + "cx": 265, + "if_else": 108, + "h": 105, + "measure": 90, + "reset": 72, + "barrier": 48, + "swap": 18, + "z": 3 + }, + "4": { + "cx": 329, + "h": 137, + "if_else": 132, + "measure": 115, + "reset": 88, + "barrier": 58, + "swap": 24, + "z": 3 + }, + "5": { + "cx": 498, + "if_else": 204, + "h": 203, + "measure": 172, + "reset": 136, + "barrier": 90, + "swap": 36, + "z": 3 + }, + "6": { + "cx": 562, + "h": 235, + "if_else": 228, + "measure": 197, + "reset": 152, + "barrier": 100, + "swap": 42, + "z": 3 + }, + "7": { + "cx": 731, + "h": 301, + "if_else": 300, + "measure": 254, + "reset": 200, + "barrier": 132, + "swap": 54, + "z": 3 + }, + "8": { + "cx": 795, + "h": 333, + "if_else": 324, + "measure": 279, + "reset": 216, + "barrier": 142, + "swap": 60, + "z": 3 + }, + "9": { + "cx": 964, + "h": 399, + "if_else": 396, + "measure": 336, + "reset": 264, + "barrier": 174, + "swap": 72, + "z": 3 + } + }, + "graphstate": { + "3": { + "cx": 435, + "if_else": 180, + "h": 159, + "measure": 147, + "reset": 120, + "barrier": 83, + "swap": 27 + }, + "4": { + "cx": 580, + "if_else": 240, + "h": 212, + "measure": 196, + "reset": 160, + "barrier": 110, + "swap": 36 + }, + "5": { + "cx": 725, + "if_else": 300, + "h": 265, + "measure": 245, + "reset": 200, + "barrier": 137, + "swap": 45 + }, + "6": { + "cx": 870, + "if_else": 360, + "h": 318, + "measure": 294, + "reset": 240, + "barrier": 164, + "swap": 54 + }, + "7": { + "cx": 1015, + "if_else": 420, + "h": 371, + "measure": 343, + "reset": 280, + "barrier": 191, + "swap": 63 + }, + "8": { + "cx": 1160, + "if_else": 480, + "h": 424, + "measure": 392, + "reset": 320, + "barrier": 218, + "swap": 72 + }, + "9": { + "cx": 1305, + "if_else": 540, + "h": 477, + "measure": 441, + "reset": 360, + "barrier": 245, + "swap": 81 + } + } + }, + "steane": { + "ghz": { + "3": { + "measure": 3, + "cx": 2, + "h": 1, + "barrier": 1 + }, + "4": { + "measure": 4, + "cx": 3, + "h": 1, + "barrier": 1 + }, + "5": { + "measure": 5, + "cx": 4, + "h": 1, + "barrier": 1 + }, + "6": { + "measure": 6, + "cx": 5, + "h": 1, + "barrier": 1 + }, + "7": { + "measure": 7, + "cx": 6, + "h": 1, + "barrier": 1 + }, + "8": { + "measure": 8, + "cx": 7, + "h": 1, + "barrier": 1 + }, + "9": { + "measure": 9, + "cx": 8, + "h": 1, + "barrier": 1 + } + }, + "bv": { + "3": { + "h": 4, + "measure": 2, + "x": 1, + "cz": 1 + }, + "4": { + "h": 6, + "measure": 3, + "x": 1, + "cz": 1 + }, + "5": { + "h": 8, + "measure": 4, + "cz": 2, + "x": 1 + }, + "6": { + "h": 10, + "measure": 5, + "cz": 2, + "x": 1 + }, + "7": { + "h": 12, + "measure": 6, + "cz": 3, + "x": 1 + }, + "8": { + "h": 14, + "measure": 7, + "cz": 3, + "x": 1 + }, + "9": { + "h": 16, + "measure": 8, + "cz": 4, + "x": 1 + } + }, + "graphstate": { + "3": { + "h": 3, + "cz": 3, + "measure": 3, + "barrier": 1 + }, + "4": { + "h": 4, + "cz": 4, + "measure": 4, + "barrier": 1 + }, + "5": { + "h": 5, + "cz": 5, + "measure": 5, + "barrier": 1 + }, + "6": { + "h": 6, + "cz": 6, + "measure": 6, + "barrier": 1 + }, + "7": { + "h": 7, + "cz": 7, + "measure": 7, + "barrier": 1 + }, + "8": { + "h": 8, + "cz": 8, + "measure": 8, + "barrier": 1 + }, + "9": { + "h": 9, + "cz": 9, + "measure": 9, + "barrier": 1 + } + } + } +} \ No newline at end of file From 6d79032a309537b624071517c85155115094d9c8 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Thu, 11 Jun 2026 20:48:42 +0200 Subject: [PATCH 40/72] + Added test for circuit structure --- tests/test_error_correction.py | 66 +++++++++++++++++++++++++++++++--- 1 file changed, 61 insertions(+), 5 deletions(-) diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index 656da39d9..0a32142fc 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -167,7 +167,7 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error else: transpiler = SteaneTranspiler(logical_circuit, add_syndromes=True) transpiler.transpile() - transpiler.decode_qubits() + #transpiler.decode_qubits() error_corrected_circuit = transpiler.transpiled_qc error_induced_circuit = error_corrected_circuit.copy() @@ -207,6 +207,60 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error f"Error corrected circuit created by {code} transpiler for Algorithm {algorithm} does not correct the bitflip well enough." ) +@pytest.mark.parametrize("logical_qubits", range(3,10)) # multiple parametrize lead to crossproducts +@pytest.mark.parametrize("alg", ["ghz", "bv", "graphstate"]) #,"qft"]) +@pytest.mark.parametrize("code", ["shor", "steane"]) +def test_error_correction_circuit_structure(code: str, alg: str, logical_qubits: int): + qc = benchmark_generation.get_benchmark( + benchmark=alg, + level=benchmark_generation.BenchmarkLevel.ALG, + circuit_size=logical_qubits, + encoding=code) + test_id = f'{logical_qubits} qubit {alg} on {code}' + + if code == "steane": + # Each logical qubit is split in 7 physical qubits + expected_qubits = 7 * logical_qubits + assert qc.num_qubits == expected_qubits, f"Expected {expected_qubits} qubits, found {qc.num_clbits} for {test_id}" + # TODO: figure out register sizes and add them here as well + + elif code == "shor": + # Each logical qubit is split in 9 physical qubits + # Additionally, 8 ancilla qubits are added as stabilisers (6Z + 2X) + # => 1 logical qubit = 17 physical qubits + expected_qubits = 17 * logical_qubits + assert qc.num_qubits == expected_qubits, f"Expected {expected_qubits} qubits, found {qc.num_clbits} for {test_id}" + # Each ancilla requires 1 clbit for syndrome extraction => 6*2 = 8 + # Additionally, 1 clbit is required for measurement => 8+1 = 9 + expected_clbits = 9 * logical_qubits + assert qc.num_clbits == expected_clbits, f"Expected {expected_clbits} classical bits, found {qc.num_clbits} for {test_id}" + + # Check quantum register sizes: 9n (data) + 6n (bit-flip syndrome) + 2n (phase-flip syndrome) + qreg_sizes = sorted(qreg.size for qreg in qc.qregs) + expected_qreg_sizes = sorted([9] * logical_qubits + [6] * logical_qubits + [2] * logical_qubits) + assert qreg_sizes == expected_qreg_sizes, f"Expected qreg sizes {expected_qreg_sizes}, found {qreg_sizes} for {test_id}" + + # Check classical register sizes: 6n (bit-flip) + 2n (phase-flip) + 1n (measurement) + creg_sizes = sorted(creg.size for creg in qc.cregs) + expected_creg_sizes = sorted([6] * logical_qubits + [2] * logical_qubits + [1] * logical_qubits) + assert creg_sizes == expected_creg_sizes, f"Expected creg sizes {expected_creg_sizes}, found {creg_sizes} for {test_id}" + + + expected_gate_counts = None + + import json + json_location = Path(__file__).parent / "gate_counts.json" + with open(f'{json_location}', 'r') as json_data: + expected_gate_counts = json.load(json_data) + json_data.close() + + assert expected_gate_counts is not None, f"Failure reading respective gate counts for {test_id}" + expected_gate_counts = expected_gate_counts[code][alg][f'{logical_qubits}'] + + # Counts the occurrence of every gate in the created circuit + created_gate_counts = qc.count_ops() + assert expected_gate_counts == created_gate_counts, f"Created circuit does not contain the expected gates for {test_id}" + def insert_error_after_barrier( qc: QuantumCircuit, @@ -344,17 +398,19 @@ def condense_counts(qc: qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, def log_circuits(circuits: dict[str, QuantumCircuit]) -> None: + from pathlib import Path + import matplotlib.pyplot as plt + log_dir = Path(__file__).parent / "circuit_drawings" log_dir.mkdir(exist_ok=True) for name, circuit in circuits.items(): - with Path(log_dir / f"{name}_transpiled.txt").open("w", encoding="utf-8") as f: + name = log_dir / f"{name}_transpiled" + with Path(f"{name}.txt").open("w", encoding="utf-8") as f: f.write(f"number of qubits {circuit.num_qubits}\n") f.write(f"--- Transpiled Circuit for {name.upper()} ---\n\n") f.write(str(circuit.draw(fold=-1)) + "\n") - import matplotlib.pyplot as plt - fig = circuit.draw(output="mpl", fold=-1) - fig.savefig(log_dir / f"{name}_transpiled.png", dpi=150, bbox_inches="tight") + fig.savefig(f"{name}.png", dpi=150, bbox_inches="tight") plt.close(fig) From 9700a8866ff401fa1529a0c81d5d3bb6a8cdb045 Mon Sep 17 00:00:00 2001 From: "LAPTOP-N4TQ80AC\\khusa" Date: Fri, 12 Jun 2026 10:36:12 +0200 Subject: [PATCH 41/72] =?UTF-8?q?=E2=9C=A8=20Adding=20QFT=20gate=20support?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mqt/bench/benchmark_generation.py | 2 +- .../error_correction/steane_transpiler.py | 60 ++++++++++++++----- 2 files changed, 46 insertions(+), 16 deletions(-) diff --git a/src/mqt/bench/benchmark_generation.py b/src/mqt/bench/benchmark_generation.py index 13483790b..bb4ab4f39 100644 --- a/src/mqt/bench/benchmark_generation.py +++ b/src/mqt/bench/benchmark_generation.py @@ -230,7 +230,7 @@ def get_benchmark_alg( transpiler = ShorTranspiler(qc, add_syndromes=True) transpiler.transpile() return transpiler.transpiled_qc - elif encoding == "stean": + elif encoding == "steane": transpiler = SteaneTranspiler(qc, add_syndromes=True) transpiler.transpile() return transpiler.transpiled_qc diff --git a/src/mqt/bench/error_correction/steane_transpiler.py b/src/mqt/bench/error_correction/steane_transpiler.py index c4a2fc86c..56d5b3254 100644 --- a/src/mqt/bench/error_correction/steane_transpiler.py +++ b/src/mqt/bench/error_correction/steane_transpiler.py @@ -12,7 +12,7 @@ from typing import TYPE_CHECKING -from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister +from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister, transpile from qiskit.circuit import AncillaRegister, barrier from mqt.bench.components.steane_circuit_components import ( @@ -42,6 +42,17 @@ def __init__(self, original_circuit: QuantumCircuit, add_syndromes = True) -> No self.add_syndromes = add_syndromes self.t_gate_count = 0 self.transpiled_qc = QuantumCircuit() + self.gate_handlers = { + "barrier": self._handle_barrier, + "measure": self._handle_measure, + "h": self._handle_h, + "x": self._handle_x, + "z": self._handle_z, + "s": self._handle_s, + "cx": self._handle_cx, + "cz": self._handle_cz, + "t": self._handle_t + } def transpile(self) -> QuantumCircuit: @@ -61,7 +72,6 @@ def encode_qubits(self) -> None: bit_flip_measurement_register = ClassicalRegister(3, f"bsm{logical_qubit_index}") phase_flip_measurement_register = ClassicalRegister(3, f"psm{logical_qubit_index}") logical_qubit_measurement_register = ClassicalRegister(1, f"logical_meas{logical_qubit_index}") - # t Gate? self.physical_data_registers.append(physical_data_register) self.bit_flip_syndromes.append(bit_flip_syndrome_register) @@ -107,22 +117,42 @@ def decode_qubits(self) -> None: def replace_gates(self) -> None: """Scan original circuit and replace gates with logical equivalents.""" - gate_handlers = { - "barrier": self._handle_barrier, - "measure": self._handle_measure, - "h": self._handle_h, - "x": self._handle_x, - "z": self._handle_z, - "s": self._handle_s, - "cx": self._handle_cx, - "cz": self._handle_cz, - "t": self._handle_t - } + + # Firstly, exapand high level gates, such as QFTGate() + normalized = QuantumCircuit(*self.original_qc.qregs, *self.original_qc.cregs) + for instruction in self.original_qc.data: + gate_name = instruction.operation.name + + if gate_name == "qft": + tmp = QuantumCircuit(len(instruction.qubits)) + tmp.append(instruction.operation, range(len(instruction.qubits))) + + tmp = transpile( + tmp, + basis_gates=["h", "x", "z", "s", "t", "cx", "cz"], + optimization_level=3, + approximation_degree=0.95, + ) + + normalized.compose( + tmp, + qubits=list(instruction.qubits), + inplace=True, + ) + + else: + normalized.append( + instruction.operation, + instruction.qubits, + instruction.clbits, + ) + + self.original_qc = normalized for instruction in self.original_qc.data: gate_name = instruction.operation.name - if gate_name in gate_handlers: - gate_handlers[gate_name](instruction) + if gate_name in self.gate_handlers: + self.gate_handlers[gate_name](instruction) else: msg = f"Gate {gate_name} is not supported by SteaneTranspiler." raise NotImplementedError(msg) From 6c4b74b9aa6ef64cf42d35c1a4f9bffb3f76070f Mon Sep 17 00:00:00 2001 From: salehalsherif Date: Fri, 12 Jun 2026 21:36:38 +0200 Subject: [PATCH 42/72] Fixed logical Measurement on shor code transpiler --- src/mqt/bench/error_correction/shor_transpiler.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mqt/bench/error_correction/shor_transpiler.py b/src/mqt/bench/error_correction/shor_transpiler.py index 35791e229..86c946f0a 100644 --- a/src/mqt/bench/error_correction/shor_transpiler.py +++ b/src/mqt/bench/error_correction/shor_transpiler.py @@ -198,11 +198,11 @@ def _logical_measure(self, logical_qubit_index: int, logical_classical_bit_index ## decode self._apply_shor_decoding(self.transpiled_qc, self.logical_qubits[logical_qubit_index].data) measurement_register_name = f"meas_{logical_qubit_index}_{logical_classical_bit_index}" - physical_measurement_register = ClassicalRegister(SHOR_TOTAL_QUBITS, measurement_register_name) + physical_measurement_register = ClassicalRegister(1, measurement_register_name) self.transpiled_qc.add_register(physical_measurement_register) physical_data_register = self.logical_qubits[logical_qubit_index].data - self.transpiled_qc.measure(physical_data_register, physical_measurement_register) + self.transpiled_qc.measure(physical_data_register[0], physical_measurement_register[0]) def _logical_h(self, logical_qubit_index: int) -> None: """Apply logical Hadamard. From 4866c8865322a539a8d995a57e066a4433f430d7 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 13 Jun 2026 10:45:19 +0200 Subject: [PATCH 43/72] new gate counts --- tests/gate_counts.json | 273 +++++++++++++++++++++++++---------------- 1 file changed, 168 insertions(+), 105 deletions(-) diff --git a/tests/gate_counts.json b/tests/gate_counts.json index 7c370cd55..a7651c7f9 100644 --- a/tests/gate_counts.json +++ b/tests/gate_counts.json @@ -3,26 +3,26 @@ "ghz": { "3": { "cx": 186, - "measure": 67, "if_else": 60, "h": 47, + "measure": 43, "reset": 40, "barrier": 27, "swap": 3 }, "4": { "cx": 259, - "measure": 92, "if_else": 84, "h": 61, + "measure": 60, "reset": 56, "barrier": 37, "swap": 3 }, "5": { "cx": 332, - "measure": 117, "if_else": 108, + "measure": 77, "h": 75, "reset": 72, "barrier": 47, @@ -30,8 +30,8 @@ }, "6": { "cx": 405, - "measure": 142, "if_else": 132, + "measure": 94, "h": 89, "reset": 88, "barrier": 57, @@ -39,8 +39,8 @@ }, "7": { "cx": 478, - "measure": 167, "if_else": 156, + "measure": 111, "reset": 104, "h": 103, "barrier": 67, @@ -48,8 +48,8 @@ }, "8": { "cx": 551, - "measure": 192, "if_else": 180, + "measure": 128, "reset": 120, "h": 117, "barrier": 77, @@ -57,8 +57,8 @@ }, "9": { "cx": 624, - "measure": 217, "if_else": 204, + "measure": 145, "reset": 136, "h": 131, "barrier": 87, @@ -70,7 +70,7 @@ "cx": 265, "if_else": 108, "h": 105, - "measure": 90, + "measure": 74, "reset": 72, "barrier": 48, "swap": 18, @@ -80,7 +80,7 @@ "cx": 329, "h": 137, "if_else": 132, - "measure": 115, + "measure": 91, "reset": 88, "barrier": 58, "swap": 24, @@ -90,7 +90,7 @@ "cx": 498, "if_else": 204, "h": 203, - "measure": 172, + "measure": 140, "reset": 136, "barrier": 90, "swap": 36, @@ -100,7 +100,7 @@ "cx": 562, "h": 235, "if_else": 228, - "measure": 197, + "measure": 157, "reset": 152, "barrier": 100, "swap": 42, @@ -110,7 +110,7 @@ "cx": 731, "h": 301, "if_else": 300, - "measure": 254, + "measure": 206, "reset": 200, "barrier": 132, "swap": 54, @@ -120,7 +120,7 @@ "cx": 795, "h": 333, "if_else": 324, - "measure": 279, + "measure": 223, "reset": 216, "barrier": 142, "swap": 60, @@ -130,7 +130,7 @@ "cx": 964, "h": 399, "if_else": 396, - "measure": 336, + "measure": 272, "reset": 264, "barrier": 174, "swap": 72, @@ -142,7 +142,7 @@ "cx": 435, "if_else": 180, "h": 159, - "measure": 147, + "measure": 123, "reset": 120, "barrier": 83, "swap": 27 @@ -151,7 +151,7 @@ "cx": 580, "if_else": 240, "h": 212, - "measure": 196, + "measure": 164, "reset": 160, "barrier": 110, "swap": 36 @@ -160,7 +160,7 @@ "cx": 725, "if_else": 300, "h": 265, - "measure": 245, + "measure": 205, "reset": 200, "barrier": 137, "swap": 45 @@ -169,7 +169,7 @@ "cx": 870, "if_else": 360, "h": 318, - "measure": 294, + "measure": 246, "reset": 240, "barrier": 164, "swap": 54 @@ -178,7 +178,7 @@ "cx": 1015, "if_else": 420, "h": 371, - "measure": 343, + "measure": 287, "reset": 280, "barrier": 191, "swap": 63 @@ -187,7 +187,7 @@ "cx": 1160, "if_else": 480, "h": 424, - "measure": 392, + "measure": 328, "reset": 320, "barrier": 218, "swap": 72 @@ -196,7 +196,7 @@ "cx": 1305, "if_else": 540, "h": 477, - "measure": 441, + "measure": 369, "reset": 360, "barrier": 245, "swap": 81 @@ -206,134 +206,197 @@ "steane": { "ghz": { "3": { - "measure": 3, - "cx": 2, - "h": 1, - "barrier": 1 + "cx": 200, + "if_else": 70, + "h": 55, + "measure": 33, + "reset": 30, + "barrier": 23 }, "4": { - "measure": 4, - "cx": 3, - "h": 1, - "barrier": 1 + "cx": 277, + "if_else": 98, + "h": 73, + "measure": 46, + "reset": 42, + "barrier": 31 }, "5": { - "measure": 5, - "cx": 4, - "h": 1, - "barrier": 1 + "cx": 354, + "if_else": 126, + "h": 91, + "measure": 59, + "reset": 54, + "barrier": 39 }, "6": { - "measure": 6, - "cx": 5, - "h": 1, - "barrier": 1 + "cx": 431, + "if_else": 154, + "h": 109, + "measure": 72, + "reset": 66, + "barrier": 47 }, "7": { - "measure": 7, - "cx": 6, - "h": 1, - "barrier": 1 + "cx": 508, + "if_else": 182, + "h": 127, + "measure": 85, + "reset": 78, + "barrier": 55 }, "8": { - "measure": 8, - "cx": 7, - "h": 1, - "barrier": 1 + "cx": 585, + "if_else": 210, + "h": 145, + "measure": 98, + "reset": 90, + "barrier": 63 }, "9": { - "measure": 9, - "cx": 8, - "h": 1, - "barrier": 1 + "cx": 662, + "if_else": 238, + "h": 163, + "measure": 111, + "reset": 102, + "barrier": 71 } }, "bv": { "3": { - "h": 4, - "measure": 2, - "x": 1, - "cz": 1 + "cx": 223, + "if_else": 98, + "h": 85, + "measure": 44, + "reset": 42, + "barrier": 30, + "x": 7, + "cz": 7 }, "4": { - "h": 6, - "measure": 3, - "x": 1, - "cz": 1 + "cx": 293, + "if_else": 126, + "h": 117, + "measure": 57, + "reset": 54, + "barrier": 39, + "x": 7, + "cz": 7 }, "5": { - "h": 8, - "measure": 4, - "cz": 2, - "x": 1 + "cx": 411, + "if_else": 182, + "h": 161, + "measure": 82, + "reset": 78, + "barrier": 55, + "cz": 14, + "x": 7 }, "6": { - "h": 10, - "measure": 5, - "cz": 2, - "x": 1 + "cx": 481, + "if_else": 210, + "h": 193, + "measure": 95, + "reset": 90, + "barrier": 64, + "cz": 14, + "x": 7 }, "7": { - "h": 12, - "measure": 6, - "cz": 3, - "x": 1 + "cx": 599, + "if_else": 266, + "h": 237, + "measure": 120, + "reset": 114, + "barrier": 80, + "cz": 21, + "x": 7 }, "8": { - "h": 14, - "measure": 7, - "cz": 3, - "x": 1 + "cx": 669, + "if_else": 294, + "h": 269, + "measure": 133, + "reset": 126, + "barrier": 89, + "cz": 21, + "x": 7 }, "9": { - "h": 16, - "measure": 8, - "cz": 4, - "x": 1 + "cx": 787, + "if_else": 350, + "h": 313, + "measure": 158, + "reset": 150, + "barrier": 105, + "cz": 28, + "x": 7 } }, "graphstate": { "3": { - "h": 3, - "cz": 3, - "measure": 3, - "barrier": 1 + "cx": 282, + "if_else": 126, + "h": 93, + "measure": 57, + "reset": 54, + "barrier": 38, + "cz": 21 }, "4": { - "h": 4, - "cz": 4, - "measure": 4, - "barrier": 1 + "cx": 376, + "if_else": 168, + "h": 124, + "measure": 76, + "reset": 72, + "barrier": 50, + "cz": 28 }, "5": { - "h": 5, - "cz": 5, - "measure": 5, - "barrier": 1 + "cx": 470, + "if_else": 210, + "h": 155, + "measure": 95, + "reset": 90, + "barrier": 62, + "cz": 35 }, "6": { - "h": 6, - "cz": 6, - "measure": 6, - "barrier": 1 + "cx": 564, + "if_else": 252, + "h": 186, + "measure": 114, + "reset": 108, + "barrier": 74, + "cz": 42 }, "7": { - "h": 7, - "cz": 7, - "measure": 7, - "barrier": 1 + "cx": 658, + "if_else": 294, + "h": 217, + "measure": 133, + "reset": 126, + "barrier": 86, + "cz": 49 }, "8": { - "h": 8, - "cz": 8, - "measure": 8, - "barrier": 1 + "cx": 752, + "if_else": 336, + "h": 248, + "measure": 152, + "reset": 144, + "barrier": 98, + "cz": 56 }, "9": { - "h": 9, - "cz": 9, - "measure": 9, - "barrier": 1 + "cx": 846, + "if_else": 378, + "h": 279, + "measure": 171, + "reset": 162, + "barrier": 110, + "cz": 63 } } } From 78a8d6b90d25a7ca9b4e908751ce9b1849bfdd4b Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 13 Jun 2026 11:20:52 +0200 Subject: [PATCH 44/72] more qft :) --- main.py | 2 +- tests/gate_counts.json | 284 ++++++++++----------------------- tests/test_error_correction.py | 10 +- 3 files changed, 87 insertions(+), 209 deletions(-) diff --git a/main.py b/main.py index 007d3cc23..f912a4006 100644 --- a/main.py +++ b/main.py @@ -296,7 +296,7 @@ def create_gate_counts(): qs = {} for code in ["shor", "steane"]: algs = {} - for alg in ["ghz", "bv", "graphstate"]:#, "qft"]: # Bonus for "qft" (Part 3) + for alg in ["ghz", "bv", "graphstate", "qft"]: # Bonus for "qft" (Part 3) qs = {} for qubits in range(3, 10): qc = benchmark_generation.get_benchmark( diff --git a/tests/gate_counts.json b/tests/gate_counts.json index a7651c7f9..14af6d1ab 100644 --- a/tests/gate_counts.json +++ b/tests/gate_counts.json @@ -1,208 +1,5 @@ { - "shor": { - "ghz": { - "3": { - "cx": 186, - "if_else": 60, - "h": 47, - "measure": 43, - "reset": 40, - "barrier": 27, - "swap": 3 - }, - "4": { - "cx": 259, - "if_else": 84, - "h": 61, - "measure": 60, - "reset": 56, - "barrier": 37, - "swap": 3 - }, - "5": { - "cx": 332, - "if_else": 108, - "measure": 77, - "h": 75, - "reset": 72, - "barrier": 47, - "swap": 3 - }, - "6": { - "cx": 405, - "if_else": 132, - "measure": 94, - "h": 89, - "reset": 88, - "barrier": 57, - "swap": 3 - }, - "7": { - "cx": 478, - "if_else": 156, - "measure": 111, - "reset": 104, - "h": 103, - "barrier": 67, - "swap": 3 - }, - "8": { - "cx": 551, - "if_else": 180, - "measure": 128, - "reset": 120, - "h": 117, - "barrier": 77, - "swap": 3 - }, - "9": { - "cx": 624, - "if_else": 204, - "measure": 145, - "reset": 136, - "h": 131, - "barrier": 87, - "swap": 3 - } - }, - "bv": { - "3": { - "cx": 265, - "if_else": 108, - "h": 105, - "measure": 74, - "reset": 72, - "barrier": 48, - "swap": 18, - "z": 3 - }, - "4": { - "cx": 329, - "h": 137, - "if_else": 132, - "measure": 91, - "reset": 88, - "barrier": 58, - "swap": 24, - "z": 3 - }, - "5": { - "cx": 498, - "if_else": 204, - "h": 203, - "measure": 140, - "reset": 136, - "barrier": 90, - "swap": 36, - "z": 3 - }, - "6": { - "cx": 562, - "h": 235, - "if_else": 228, - "measure": 157, - "reset": 152, - "barrier": 100, - "swap": 42, - "z": 3 - }, - "7": { - "cx": 731, - "h": 301, - "if_else": 300, - "measure": 206, - "reset": 200, - "barrier": 132, - "swap": 54, - "z": 3 - }, - "8": { - "cx": 795, - "h": 333, - "if_else": 324, - "measure": 223, - "reset": 216, - "barrier": 142, - "swap": 60, - "z": 3 - }, - "9": { - "cx": 964, - "h": 399, - "if_else": 396, - "measure": 272, - "reset": 264, - "barrier": 174, - "swap": 72, - "z": 3 - } - }, - "graphstate": { - "3": { - "cx": 435, - "if_else": 180, - "h": 159, - "measure": 123, - "reset": 120, - "barrier": 83, - "swap": 27 - }, - "4": { - "cx": 580, - "if_else": 240, - "h": 212, - "measure": 164, - "reset": 160, - "barrier": 110, - "swap": 36 - }, - "5": { - "cx": 725, - "if_else": 300, - "h": 265, - "measure": 205, - "reset": 200, - "barrier": 137, - "swap": 45 - }, - "6": { - "cx": 870, - "if_else": 360, - "h": 318, - "measure": 246, - "reset": 240, - "barrier": 164, - "swap": 54 - }, - "7": { - "cx": 1015, - "if_else": 420, - "h": 371, - "measure": 287, - "reset": 280, - "barrier": 191, - "swap": 63 - }, - "8": { - "cx": 1160, - "if_else": 480, - "h": 424, - "measure": 328, - "reset": 320, - "barrier": 218, - "swap": 72 - }, - "9": { - "cx": 1305, - "if_else": 540, - "h": 477, - "measure": 369, - "reset": 360, - "barrier": 245, - "swap": 81 - } - } - }, + "shor": {}, "steane": { "ghz": { "3": { @@ -398,6 +195,85 @@ "barrier": 110, "cz": 63 } + }, + "qft": { + "3": { + "cx": 772, + "if_else": 300, + "h": 243, + "measure": 135, + "reset": 126, + "barrier": 85, + "t": 42, + "sdg": 14, + "z": 14 + }, + "4": { + "cx": 1135, + "if_else": 443, + "h": 355, + "measure": 199, + "reset": 186, + "barrier": 124, + "t": 63, + "sdg": 21, + "z": 21 + }, + "5": { + "cx": 1498, + "if_else": 586, + "h": 467, + "measure": 263, + "reset": 246, + "barrier": 163, + "t": 84, + "sdg": 28, + "z": 28 + }, + "6": { + "cx": 1861, + "if_else": 729, + "h": 579, + "measure": 327, + "reset": 306, + "barrier": 202, + "t": 105, + "sdg": 35, + "z": 35 + }, + "7": { + "cx": 2224, + "if_else": 872, + "h": 691, + "measure": 391, + "reset": 366, + "barrier": 241, + "t": 126, + "sdg": 42, + "z": 42 + }, + "8": { + "cx": 2587, + "if_else": 1015, + "h": 803, + "measure": 455, + "reset": 426, + "barrier": 280, + "t": 147, + "sdg": 49, + "z": 49 + }, + "9": { + "cx": 2950, + "if_else": 1158, + "h": 915, + "measure": 519, + "reset": 486, + "barrier": 319, + "t": 168, + "sdg": 56, + "z": 56 + } } } } \ No newline at end of file diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index 0a32142fc..e88b54ecb 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -155,7 +155,7 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error print(logical_circuit.decompose().count_ops()) # Strip measure gates to avoid intermediate measurements collapsing the state before decoding - stripped_logical_circuit = QuantumCircuit(*logical_circuit.qregs) + stripped_logical_circuit = QuantumCircuit(*logical_circuit.qregs, *logical_circuit.cregs) for inst in logical_circuit.data: if inst.operation.name != "measure": stripped_logical_circuit.append(inst.operation, inst.qubits, []) @@ -220,8 +220,9 @@ def test_error_correction_circuit_structure(code: str, alg: str, logical_qubits: if code == "steane": # Each logical qubit is split in 7 physical qubits - expected_qubits = 7 * logical_qubits - assert qc.num_qubits == expected_qubits, f"Expected {expected_qubits} qubits, found {qc.num_clbits} for {test_id}" + expected_qubits = 13 * logical_qubits + found_qubits = qc.num_qubits + assert found_qubits == expected_qubits, f"Expected {expected_qubits} qubits, found {found_qubits} for {test_id}" # TODO: figure out register sizes and add them here as well elif code == "shor": @@ -229,7 +230,8 @@ def test_error_correction_circuit_structure(code: str, alg: str, logical_qubits: # Additionally, 8 ancilla qubits are added as stabilisers (6Z + 2X) # => 1 logical qubit = 17 physical qubits expected_qubits = 17 * logical_qubits - assert qc.num_qubits == expected_qubits, f"Expected {expected_qubits} qubits, found {qc.num_clbits} for {test_id}" + found_qubits = qc.num_qubits + assert found_qubits == expected_qubits, f"Expected {expected_qubits} qubits, found {found_qubits} for {test_id}" # Each ancilla requires 1 clbit for syndrome extraction => 6*2 = 8 # Additionally, 1 clbit is required for measurement => 8+1 = 9 expected_clbits = 9 * logical_qubits From ba31cbafc54bb85fac19a2b9fe217647d9969c89 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 13 Jun 2026 11:26:31 +0200 Subject: [PATCH 45/72] fixed measurement removal --- tests/test_error_correction.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index e88b54ecb..ac46f80d2 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -158,7 +158,7 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error stripped_logical_circuit = QuantumCircuit(*logical_circuit.qregs, *logical_circuit.cregs) for inst in logical_circuit.data: if inst.operation.name != "measure": - stripped_logical_circuit.append(inst.operation, inst.qubits, []) + stripped_logical_circuit.append(inst.operation, inst.qubits, inst.clbits) logical_circuit = stripped_logical_circuit error_corrected_circuit = logical_circuit.copy() From 5bfd6c2670d4bd4f3df9dd2d53af518af76cbb86 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 13 Jun 2026 11:37:14 +0200 Subject: [PATCH 46/72] more fixes --- main.py | 2 + tests/gate_counts.json | 205 ++++++++++++++++++++++++++++++++- tests/test_error_correction.py | 3 +- 3 files changed, 208 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index f912a4006..72f60215f 100644 --- a/main.py +++ b/main.py @@ -297,6 +297,8 @@ def create_gate_counts(): for code in ["shor", "steane"]: algs = {} for alg in ["ghz", "bv", "graphstate", "qft"]: # Bonus for "qft" (Part 3) + if code == 'shor' and alg == 'qft': + continue qs = {} for qubits in range(3, 10): qc = benchmark_generation.get_benchmark( diff --git a/tests/gate_counts.json b/tests/gate_counts.json index 14af6d1ab..f774f6312 100644 --- a/tests/gate_counts.json +++ b/tests/gate_counts.json @@ -1,5 +1,208 @@ { - "shor": {}, + "shor": { + "ghz": { + "3": { + "cx": 186, + "if_else": 60, + "h": 47, + "measure": 43, + "reset": 40, + "barrier": 27, + "swap": 3 + }, + "4": { + "cx": 259, + "if_else": 84, + "h": 61, + "measure": 60, + "reset": 56, + "barrier": 37, + "swap": 3 + }, + "5": { + "cx": 332, + "if_else": 108, + "measure": 77, + "h": 75, + "reset": 72, + "barrier": 47, + "swap": 3 + }, + "6": { + "cx": 405, + "if_else": 132, + "measure": 94, + "h": 89, + "reset": 88, + "barrier": 57, + "swap": 3 + }, + "7": { + "cx": 478, + "if_else": 156, + "measure": 111, + "reset": 104, + "h": 103, + "barrier": 67, + "swap": 3 + }, + "8": { + "cx": 551, + "if_else": 180, + "measure": 128, + "reset": 120, + "h": 117, + "barrier": 77, + "swap": 3 + }, + "9": { + "cx": 624, + "if_else": 204, + "measure": 145, + "reset": 136, + "h": 131, + "barrier": 87, + "swap": 3 + } + }, + "bv": { + "3": { + "cx": 265, + "if_else": 108, + "h": 105, + "measure": 74, + "reset": 72, + "barrier": 48, + "swap": 18, + "z": 3 + }, + "4": { + "cx": 329, + "h": 137, + "if_else": 132, + "measure": 91, + "reset": 88, + "barrier": 58, + "swap": 24, + "z": 3 + }, + "5": { + "cx": 498, + "if_else": 204, + "h": 203, + "measure": 140, + "reset": 136, + "barrier": 90, + "swap": 36, + "z": 3 + }, + "6": { + "cx": 562, + "h": 235, + "if_else": 228, + "measure": 157, + "reset": 152, + "barrier": 100, + "swap": 42, + "z": 3 + }, + "7": { + "cx": 731, + "h": 301, + "if_else": 300, + "measure": 206, + "reset": 200, + "barrier": 132, + "swap": 54, + "z": 3 + }, + "8": { + "cx": 795, + "h": 333, + "if_else": 324, + "measure": 223, + "reset": 216, + "barrier": 142, + "swap": 60, + "z": 3 + }, + "9": { + "cx": 964, + "h": 399, + "if_else": 396, + "measure": 272, + "reset": 264, + "barrier": 174, + "swap": 72, + "z": 3 + } + }, + "graphstate": { + "3": { + "cx": 435, + "if_else": 180, + "h": 159, + "measure": 123, + "reset": 120, + "barrier": 83, + "swap": 27 + }, + "4": { + "cx": 580, + "if_else": 240, + "h": 212, + "measure": 164, + "reset": 160, + "barrier": 110, + "swap": 36 + }, + "5": { + "cx": 725, + "if_else": 300, + "h": 265, + "measure": 205, + "reset": 200, + "barrier": 137, + "swap": 45 + }, + "6": { + "cx": 870, + "if_else": 360, + "h": 318, + "measure": 246, + "reset": 240, + "barrier": 164, + "swap": 54 + }, + "7": { + "cx": 1015, + "if_else": 420, + "h": 371, + "measure": 287, + "reset": 280, + "barrier": 191, + "swap": 63 + }, + "8": { + "cx": 1160, + "if_else": 480, + "h": 424, + "measure": 328, + "reset": 320, + "barrier": 218, + "swap": 72 + }, + "9": { + "cx": 1305, + "if_else": 540, + "h": 477, + "measure": 369, + "reset": 360, + "barrier": 245, + "swap": 81 + } + } + }, "steane": { "ghz": { "3": { diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index ac46f80d2..9330e2e60 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -142,7 +142,7 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error circuit_size = 3 # Initialize circuits logical_circuit = benchmark_generation.get_benchmark( - benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding=code + benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding="" ) if MeasureBaseX: @@ -160,6 +160,7 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error if inst.operation.name != "measure": stripped_logical_circuit.append(inst.operation, inst.qubits, inst.clbits) logical_circuit = stripped_logical_circuit + print(logical_circuit) error_corrected_circuit = logical_circuit.copy() if code == "shor": From 2b19794b5a9e9e220039c9555fcf211533b9c6af Mon Sep 17 00:00:00 2001 From: salehalsherif Date: Sat, 13 Jun 2026 11:41:02 +0200 Subject: [PATCH 47/72] Adding QFT gate to shor transpiler --- .../bench/error_correction/shor_transpiler.py | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/mqt/bench/error_correction/shor_transpiler.py b/src/mqt/bench/error_correction/shor_transpiler.py index 86c946f0a..2ca8c79fe 100644 --- a/src/mqt/bench/error_correction/shor_transpiler.py +++ b/src/mqt/bench/error_correction/shor_transpiler.py @@ -13,7 +13,7 @@ from dataclasses import dataclass import numpy as np -from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister +from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister, transpile from qiskit.circuit import AncillaRegister #ignore the below comment @@ -152,6 +152,37 @@ def _apply_shor_decoding(qc: QuantumCircuit, physical_data_register: QuantumRegi def replace_gates(self) -> None: """Scan original circuit and replace gates with logical equivalents.""" + # Firstly, expand high level gates, such as QFTGate() + normalized = QuantumCircuit(*self.original_qc.qregs, *self.original_qc.cregs) + for instruction in self.original_qc.data: + gate_name = instruction.operation.name + + if gate_name == "qft": + tmp = QuantumCircuit(len(instruction.qubits)) + tmp.append(instruction.operation, range(len(instruction.qubits))) + + tmp = transpile( + tmp, + basis_gates=["h", "x", "z", "s", "t", "cx", "cz"], + optimization_level=3, + approximation_degree=0.95, + ) + + normalized.compose( + tmp, + qubits=list(instruction.qubits), + inplace=True, + ) + + else: + normalized.append( + instruction.operation, + instruction.qubits, + instruction.clbits, + ) + + self.original_qc = normalized + for instruction in self.original_qc.data: gate_name = instruction.operation.name handler_name = f"_logical_{gate_name}" From 943211969ad5023b6330e0826b25aa17c6878e39 Mon Sep 17 00:00:00 2001 From: "LAPTOP-N4TQ80AC\\khusa" Date: Sat, 13 Jun 2026 12:10:21 +0200 Subject: [PATCH 48/72] Fix Measurements --- .../error_correction/steane_transpiler.py | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/mqt/bench/error_correction/steane_transpiler.py b/src/mqt/bench/error_correction/steane_transpiler.py index 56d5b3254..dcdac54a9 100644 --- a/src/mqt/bench/error_correction/steane_transpiler.py +++ b/src/mqt/bench/error_correction/steane_transpiler.py @@ -38,7 +38,7 @@ def __init__(self, original_circuit: QuantumCircuit, add_syndromes = True) -> No self.phase_flip_syndromes: list[AncillaRegister] = [] self.bit_flip_syndrome_measurements: list[ClassicalRegister] = [] self.phase_flip_syndrome_measurements: list[ClassicalRegister] = [] - self.logical_qubit_measurements: list[ClassicalRegister] = [] + #self.logical_qubit_measurements: list[ClassicalRegister] = [] self.add_syndromes = add_syndromes self.t_gate_count = 0 self.transpiled_qc = QuantumCircuit() @@ -71,14 +71,14 @@ def encode_qubits(self) -> None: phase_flip_syndrome_register = AncillaRegister(3, f"ps{logical_qubit_index}") bit_flip_measurement_register = ClassicalRegister(3, f"bsm{logical_qubit_index}") phase_flip_measurement_register = ClassicalRegister(3, f"psm{logical_qubit_index}") - logical_qubit_measurement_register = ClassicalRegister(1, f"logical_meas{logical_qubit_index}") + #logical_qubit_measurement_register = ClassicalRegister(1, f"logical_meas{logical_qubit_index}") self.physical_data_registers.append(physical_data_register) self.bit_flip_syndromes.append(bit_flip_syndrome_register) self.phase_flip_syndromes.append(phase_flip_syndrome_register) self.bit_flip_syndrome_measurements.append(bit_flip_measurement_register) self.phase_flip_syndrome_measurements.append(phase_flip_measurement_register) - self.logical_qubit_measurements.append(logical_qubit_measurement_register) + #self.logical_qubit_measurements.append(logical_qubit_measurement_register) all_registers.extend([ physical_data_register, @@ -86,7 +86,7 @@ def encode_qubits(self) -> None: phase_flip_syndrome_register, bit_flip_measurement_register, phase_flip_measurement_register, - logical_qubit_measurement_register + #logical_qubit_measurement_register ]) self.transpiled_qc = QuantumCircuit(*all_registers) @@ -171,19 +171,26 @@ def _handle_measure(self, instruction: CircuitInstruction) -> None: """Handle measure instruction.""" # TODO: consider measure_all(), because of new meas register everything goes wrong - for i in range(len(instruction.qubits)): - logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[i]) - logical_classical_bit_index = self.original_qc.clbits.index(instruction.clbits[i]) + for q, c in zip(instruction.qubits, instruction.clbits): + logical_qubit_index = self.original_qc.qubits.index(q) + logical_classical_bit_index = self.original_qc.clbits.index(c) self.transpiled_qc.compose(get_seven_qubit_steane_code_decoding_circuit(), qubits=self.physical_data_registers[logical_qubit_index], inplace=True ) - self.transpiled_qc.measure(self.physical_data_registers[logical_qubit_index][0], - self.logical_qubit_measurements[logical_classical_bit_index]) + measurement_register_name = f"meas_{logical_qubit_index}_{logical_classical_bit_index}" + physical_measurement_register = ClassicalRegister(1, measurement_register_name) + self.transpiled_qc.add_register(physical_measurement_register) + + physical_data_register = self.physical_data_registers[logical_qubit_index][0] + self.transpiled_qc.measure(physical_data_register, physical_measurement_register) + + #self.transpiled_qc.measure(self.physical_data_registers[logical_qubit_index][0], + # self.logical_qubit_measurements[logical_classical_bit_index]) - self.transpiled_qc.barrier(label=f"Measurement {logical_qubit_index}") + self.transpiled_qc.barrier(label=f"Measurement {logical_qubit_index}") def _handle_h(self, instruction: CircuitInstruction) -> None: """Handle Hadamard instruction.""" From 2ef22f284fe4cc4f7c206049393ec81027f2a666 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 13 Jun 2026 12:58:59 +0200 Subject: [PATCH 49/72] fixed circuit structure tests to work with dynamic numbers of classical registers --- tests/test_error_correction.py | 65 +++++++++++++++++++++++++--------- 1 file changed, 48 insertions(+), 17 deletions(-) diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index 9330e2e60..863e15961 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -212,41 +212,72 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error @pytest.mark.parametrize("alg", ["ghz", "bv", "graphstate"]) #,"qft"]) @pytest.mark.parametrize("code", ["shor", "steane"]) def test_error_correction_circuit_structure(code: str, alg: str, logical_qubits: int): + test_id = f'{logical_qubits} qubit {alg} on {code}' + qc = benchmark_generation.get_benchmark( benchmark=alg, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=logical_qubits, encoding=code) - test_id = f'{logical_qubits} qubit {alg} on {code}' + log_qc = benchmark_generation.get_benchmark( + benchmark=alg, + level=benchmark_generation.BenchmarkLevel.ALG, + circuit_size=logical_qubits, + encoding='') + + + # what do we want: + # expected qubits + # expected classical bits + # qregisters (733) steane, (962) shor + # cregisters (133) steane, (162) shor + + qubit_code_factor = -1 + classical_code_factor = -1 + + expected_qreg_sizes = [] + expected_creg_sizes = [] if code == "steane": # Each logical qubit is split in 7 physical qubits - expected_qubits = 13 * logical_qubits - found_qubits = qc.num_qubits - assert found_qubits == expected_qubits, f"Expected {expected_qubits} qubits, found {found_qubits} for {test_id}" + # Additionally, 6 ancillary registers are added + qubit_code_factor = 13 + + classical_code_factor = 6 + # TODO: figure out register sizes and add them here as well + # Check quantum register sizes: 9n (data) + 6n (bit-flip syndrome) + 2n (phase-flip syndrome) + expected_qreg_sizes = sorted([7] * logical_qubits + [3] * logical_qubits + [3] * logical_qubits) + # Check classical register sizes: 6n (bit-flip) + 2n (phase-flip) + 1 for each original clbit + expected_creg_sizes = sorted([3] * logical_qubits + [3] * logical_qubits + [1] * log_qc.num_clbits) elif code == "shor": # Each logical qubit is split in 9 physical qubits # Additionally, 8 ancilla qubits are added as stabilisers (6Z + 2X) # => 1 logical qubit = 17 physical qubits - expected_qubits = 17 * logical_qubits - found_qubits = qc.num_qubits - assert found_qubits == expected_qubits, f"Expected {expected_qubits} qubits, found {found_qubits} for {test_id}" + qubit_code_factor = 17 # Each ancilla requires 1 clbit for syndrome extraction => 6*2 = 8 - # Additionally, 1 clbit is required for measurement => 8+1 = 9 - expected_clbits = 9 * logical_qubits - assert qc.num_clbits == expected_clbits, f"Expected {expected_clbits} classical bits, found {qc.num_clbits} for {test_id}" - + classical_code_factor = 8 + # Check quantum register sizes: 9n (data) + 6n (bit-flip syndrome) + 2n (phase-flip syndrome) - qreg_sizes = sorted(qreg.size for qreg in qc.qregs) expected_qreg_sizes = sorted([9] * logical_qubits + [6] * logical_qubits + [2] * logical_qubits) - assert qreg_sizes == expected_qreg_sizes, f"Expected qreg sizes {expected_qreg_sizes}, found {qreg_sizes} for {test_id}" - # Check classical register sizes: 6n (bit-flip) + 2n (phase-flip) + 1n (measurement) - creg_sizes = sorted(creg.size for creg in qc.cregs) - expected_creg_sizes = sorted([6] * logical_qubits + [2] * logical_qubits + [1] * logical_qubits) - assert creg_sizes == expected_creg_sizes, f"Expected creg sizes {expected_creg_sizes}, found {creg_sizes} for {test_id}" + # Check classical register sizes: 6n (bit-flip) + 2n (phase-flip) + 1 for each original clbit + expected_creg_sizes = sorted([6] * logical_qubits + [2] * logical_qubits + [1] * log_qc.num_clbits) + + expected_qubits = qubit_code_factor * log_qc.num_qubits + found_qubits = qc.num_qubits + assert found_qubits == expected_qubits, f"Expected {expected_qubits} qubits, found {found_qubits} for {test_id}" + + expected_clbits = classical_code_factor * log_qc.num_qubits + log_qc.num_clbits + found_clbits = qc.num_clbits + assert found_clbits == expected_clbits, f"Expected {expected_clbits} classical bits, found {found_clbits} for {test_id}" + + qreg_sizes = sorted(qreg.size for qreg in qc.qregs) + assert qreg_sizes == expected_qreg_sizes, f"Expected qreg sizes {expected_qreg_sizes}, found {qreg_sizes} for {test_id}" + + creg_sizes = sorted(creg.size for creg in qc.cregs) + assert creg_sizes == expected_creg_sizes, f"Expected creg sizes {expected_creg_sizes}, found {creg_sizes} for {test_id}" expected_gate_counts = None From 2fa638025cb010894d998179f30ec4dddebba6c5 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 13 Jun 2026 13:19:56 +0200 Subject: [PATCH 50/72] added support for qft structural tests --- main.py | 2 - tests/gate_counts.json | 79 ++++++++++++++++++++++++++++++++++ tests/test_error_correction.py | 33 +++++++------- 3 files changed, 95 insertions(+), 19 deletions(-) diff --git a/main.py b/main.py index 72f60215f..f912a4006 100644 --- a/main.py +++ b/main.py @@ -297,8 +297,6 @@ def create_gate_counts(): for code in ["shor", "steane"]: algs = {} for alg in ["ghz", "bv", "graphstate", "qft"]: # Bonus for "qft" (Part 3) - if code == 'shor' and alg == 'qft': - continue qs = {} for qubits in range(3, 10): qc = benchmark_generation.get_benchmark( diff --git a/tests/gate_counts.json b/tests/gate_counts.json index f774f6312..fa62dacb1 100644 --- a/tests/gate_counts.json +++ b/tests/gate_counts.json @@ -201,6 +201,85 @@ "barrier": 245, "swap": 81 } + }, + "qft": { + "3": { + "cx": 788, + "if_else": 260, + "h": 185, + "measure": 179, + "reset": 168, + "barrier": 107, + "swap": 9, + "p": 8, + "x": 6 + }, + "4": { + "cx": 1162, + "if_else": 384, + "h": 268, + "measure": 264, + "reset": 248, + "barrier": 157, + "swap": 12, + "p": 12, + "x": 9 + }, + "5": { + "cx": 1536, + "if_else": 508, + "h": 351, + "measure": 349, + "reset": 328, + "barrier": 207, + "p": 16, + "swap": 15, + "x": 12 + }, + "6": { + "cx": 1910, + "if_else": 632, + "h": 434, + "measure": 434, + "reset": 408, + "barrier": 257, + "p": 20, + "swap": 18, + "x": 15 + }, + "7": { + "cx": 2284, + "if_else": 756, + "measure": 519, + "h": 517, + "reset": 488, + "barrier": 307, + "p": 24, + "swap": 21, + "x": 18 + }, + "8": { + "cx": 2658, + "if_else": 880, + "measure": 604, + "h": 600, + "reset": 568, + "barrier": 357, + "p": 28, + "swap": 24, + "x": 21 + }, + "9": { + "cx": 3032, + "if_else": 1004, + "measure": 689, + "h": 683, + "reset": 648, + "barrier": 407, + "p": 32, + "swap": 27, + "x": 24 + } } }, "steane": { diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index 863e15961..f289e5c1d 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -193,9 +193,6 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error error_corrected_circuit, error_induced_circuit, corrected_counts, induced_counts, code, code ) - # log_circuits({f'log_{code}_{algorithm}':logical_circuit, - # f'corrected_{code}_{algorithm}':error_corrected_circuit, - # f'induced_{code}_{algorithm}':error_induced_circuit,}) # print(corrected_counts) print("condensed:", condense_counts(error_corrected_circuit, corrected_counts)) @@ -209,7 +206,7 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error ) @pytest.mark.parametrize("logical_qubits", range(3,10)) # multiple parametrize lead to crossproducts -@pytest.mark.parametrize("alg", ["ghz", "bv", "graphstate"]) #,"qft"]) +@pytest.mark.parametrize("alg", ["ghz", "bv", "graphstate", "qft"]) @pytest.mark.parametrize("code", ["shor", "steane"]) def test_error_correction_circuit_structure(code: str, alg: str, logical_qubits: int): test_id = f'{logical_qubits} qubit {alg} on {code}' @@ -265,19 +262,21 @@ def test_error_correction_circuit_structure(code: str, alg: str, logical_qubits: # Check classical register sizes: 6n (bit-flip) + 2n (phase-flip) + 1 for each original clbit expected_creg_sizes = sorted([6] * logical_qubits + [2] * logical_qubits + [1] * log_qc.num_clbits) - expected_qubits = qubit_code_factor * log_qc.num_qubits - found_qubits = qc.num_qubits - assert found_qubits == expected_qubits, f"Expected {expected_qubits} qubits, found {found_qubits} for {test_id}" - - expected_clbits = classical_code_factor * log_qc.num_qubits + log_qc.num_clbits - found_clbits = qc.num_clbits - assert found_clbits == expected_clbits, f"Expected {expected_clbits} classical bits, found {found_clbits} for {test_id}" - - qreg_sizes = sorted(qreg.size for qreg in qc.qregs) - assert qreg_sizes == expected_qreg_sizes, f"Expected qreg sizes {expected_qreg_sizes}, found {qreg_sizes} for {test_id}" - - creg_sizes = sorted(creg.size for creg in qc.cregs) - assert creg_sizes == expected_creg_sizes, f"Expected creg sizes {expected_creg_sizes}, found {creg_sizes} for {test_id}" + if alg != 'qft': + # QFT creates qubits scaling with the number of t-gates -> non-trivial scaling not covered by these simple tests + expected_qubits = qubit_code_factor * log_qc.num_qubits + found_qubits = qc.num_qubits + assert found_qubits == expected_qubits, f"Expected {expected_qubits} qubits, found {found_qubits} for {test_id}" + + expected_clbits = classical_code_factor * log_qc.num_qubits + log_qc.num_clbits + found_clbits = qc.num_clbits + assert found_clbits == expected_clbits, f"Expected {expected_clbits} classical bits, found {found_clbits} for {test_id}" + + qreg_sizes = sorted(qreg.size for qreg in qc.qregs) + assert qreg_sizes == expected_qreg_sizes, f"Expected qreg sizes {expected_qreg_sizes}, found {qreg_sizes} for {test_id}" + + creg_sizes = sorted(creg.size for creg in qc.cregs) + assert creg_sizes == expected_creg_sizes, f"Expected creg sizes {expected_creg_sizes}, found {creg_sizes} for {test_id}" expected_gate_counts = None From 31216cc3cca64ed33b00958a99ad140358d3f351 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 13 Jun 2026 14:37:58 +0200 Subject: [PATCH 51/72] switched to custom measurement logic --- tests/test_error_correction.py | 35 ++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index f289e5c1d..be5737a52 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -27,7 +27,7 @@ import pytest from qiskit import QuantumCircuit, transpile -from qiskit.circuit import CircuitInstruction +from qiskit.circuit import CircuitInstruction, ClassicalRegister from qiskit.circuit.library import CXGate, CZGate, HGate, SGate, XGate, ZGate from qiskit.quantum_info import hellinger_fidelity from qiskit_aer.primitives import SamplerV2 @@ -160,7 +160,6 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error if inst.operation.name != "measure": stripped_logical_circuit.append(inst.operation, inst.qubits, inst.clbits) logical_circuit = stripped_logical_circuit - print(logical_circuit) error_corrected_circuit = logical_circuit.copy() if code == "shor": @@ -186,6 +185,10 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error corrected_counts, error_corrected_circuit = run_circuit(error_corrected_circuit) induced_counts, error_induced_circuit = run_circuit(error_induced_circuit) +# log_circuits({f'logical_{code}_{algorithm}_{Error.name}_{MeasureBaseX}': logical_circuit, +# f'corrected_{code}_{algorithm}_{Error.name}_{MeasureBaseX}':error_corrected_circuit, +# f'induced_{code}_{algorithm}_{Error.name}_{MeasureBaseX}': error_induced_circuit}) + logical_corrected_fidelity = compare_distributions( logical_circuit, error_corrected_circuit, logical_counts, corrected_counts, "none", code ) @@ -348,6 +351,21 @@ def check_equivalence(qc1: qk.QuantumCircuit, qc2: qk.QuantumCircuit) -> bool: verification_results = mqt.qcec.verify(qc1, qc2) accepted_equivalencies = [EC.equivalent, EC.equivalent_up_to_global_phase, EC.probably_equivalent] return verification_results.equivalence in accepted_equivalencies +def measure_all_named(qc: QuantumCircuit, name: str = 'measurement') -> QuantumCircuit: + """ + Adds a classical register named 'measurement' to the circuit with one bit + per qubit, then maps each qubit i to classical bit i of that register. + + Args: + qc: The QuantumCircuit to add measurements to (modified in place). + + Returns: + The same QuantumCircuit with the register and measurements added. + """ + cr = ClassicalRegister(qc.num_qubits, name=name) + qc.add_register(cr) + qc.measure(range(qc.num_qubits), cr) + return qc def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[dict, QuantumCircuit]: @@ -362,13 +380,14 @@ def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[dict, QuantumCir qc with measure_all() """ sampler = SamplerV2() - qc.measure_all() + qc = measure_all_named(qc, 'measurements') job = sampler.run([qc], shots=shots) result = job.result() # Grabbing only the desired outcomes pub_result = result[0] - meas_bit_counts = pub_result.data.meas.get_counts() + meas_bit_counts = pub_result.data.measurements.get_counts() + # outputs reversed bitstrings, we just reverse them right back, # so their indices align with the qubit indices meas_bit_counts = {k[::-1]: v for k, v in meas_bit_counts.items()} @@ -441,9 +460,9 @@ def log_circuits(circuits: dict[str, QuantumCircuit]) -> None: name = log_dir / f"{name}_transpiled" with Path(f"{name}.txt").open("w", encoding="utf-8") as f: f.write(f"number of qubits {circuit.num_qubits}\n") - f.write(f"--- Transpiled Circuit for {name.upper()} ---\n\n") + f.write(f"--- Transpiled Circuit for {name._str.upper()} ---\n\n") f.write(str(circuit.draw(fold=-1)) + "\n") - fig = circuit.draw(output="mpl", fold=-1) - fig.savefig(f"{name}.png", dpi=150, bbox_inches="tight") - plt.close(fig) + #fig = circuit.draw(output="mpl", fold=-1) + #fig.savefig(f"{name}.png", dpi=150, bbox_inches="tight") + #plt.close(fig) From 5f29699d5f1a706a6b2e5e8315af66cbab4bf06b Mon Sep 17 00:00:00 2001 From: "LAPTOP-N4TQ80AC\\khusa" Date: Sat, 13 Jun 2026 14:58:34 +0200 Subject: [PATCH 52/72] =?UTF-8?q?=E2=9C=A8=20Fixing=20Tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_error_correction.py | 34 +++++++++++----------------------- 1 file changed, 11 insertions(+), 23 deletions(-) diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index be5737a52..041f43156 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -127,22 +127,20 @@ def add_h_before_measurements(qc: QuantumCircuit) -> QuantumCircuit: return new_qc -@pytest.mark.parametrize("code", ["shor", "steane"]) # "shor", double parametrize leads to crossproduct -@pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate"]) # , , "bv", "graphstate""qft"]) -@pytest.mark.parametrize("Error", [XGate(), ZGate()]) # , , "bv", "graphstate""qft"]) -@pytest.mark.parametrize("MeasureBaseX", [True, False]) # , , "bv", "graphstate""qft"]) -def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error, MeasureBaseX: bool) -> None: +@pytest.mark.parametrize("code", ["shor", "steane"]) +@pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate"]) #"qft" is unfeasible +@pytest.mark.parametrize("Error", [XGate(), ZGate()]) +@pytest.mark.parametrize("MeasureBaseX", [True, False]) +@pytest.mark.parametrize("CIRCUIT_SIZE", [3,4,5,6,7,8,9,10]) +def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error, MeasureBaseX: bool, CIRCUIT_SIZE:int) -> None: """Ensures the transpiler creates error-corrected circuits which produce the same result as the orinigal logical circuit. Afterwards an error is introduced and the test checks, whether it is corrected. Iterates over a number of example algorithms. """ - if algorithm == "qft" and code == "shor": - # this takes a little longer.... - return - circuit_size = 3 + # Initialize circuits logical_circuit = benchmark_generation.get_benchmark( - benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding="" + benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=CIRCUIT_SIZE, encoding="" ) if MeasureBaseX: @@ -161,19 +159,14 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error stripped_logical_circuit.append(inst.operation, inst.qubits, inst.clbits) logical_circuit = stripped_logical_circuit - error_corrected_circuit = logical_circuit.copy() if code == "shor": - transpiler = ShorTranspiler(error_corrected_circuit, add_syndromes=True) + transpiler = ShorTranspiler(logical_circuit.copy(), add_syndromes=True) else: - transpiler = SteaneTranspiler(logical_circuit, add_syndromes=True) + transpiler = SteaneTranspiler(logical_circuit.copy(), add_syndromes=True) transpiler.transpile() - #transpiler.decode_qubits() + transpiler.decode_qubits() error_corrected_circuit = transpiler.transpiled_qc - error_induced_circuit = error_corrected_circuit.copy() - # this is for inserting phase flip in steane after the first Hadamard - # error_induced_circuit = insert_error(error_induced_circuit ,gate=ZGate(), index=16) - # error_induced_circuit = insert_error(error_induced_circuit ,gate=XGate()) error_induced_circuit = insert_error_after_barrier( error_corrected_circuit, barrier_label="Encoding", @@ -185,10 +178,6 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error corrected_counts, error_corrected_circuit = run_circuit(error_corrected_circuit) induced_counts, error_induced_circuit = run_circuit(error_induced_circuit) -# log_circuits({f'logical_{code}_{algorithm}_{Error.name}_{MeasureBaseX}': logical_circuit, -# f'corrected_{code}_{algorithm}_{Error.name}_{MeasureBaseX}':error_corrected_circuit, -# f'induced_{code}_{algorithm}_{Error.name}_{MeasureBaseX}': error_induced_circuit}) - logical_corrected_fidelity = compare_distributions( logical_circuit, error_corrected_circuit, logical_counts, corrected_counts, "none", code ) @@ -197,7 +186,6 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error ) - # print(corrected_counts) print("condensed:", condense_counts(error_corrected_circuit, corrected_counts)) print("Logical", logical_counts) From 21c9fd735451ee772db485e86313f5dbd68975de Mon Sep 17 00:00:00 2001 From: "LAPTOP-N4TQ80AC\\khusa" Date: Sat, 13 Jun 2026 14:59:16 +0200 Subject: [PATCH 53/72] =?UTF-8?q?=E2=9C=A8=20Removing=20QFT=20Test?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/test_error_correction.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index 041f43156..ef4c6ca3f 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -145,13 +145,7 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error if MeasureBaseX: logical_circuit = add_h_before_measurements(logical_circuit) - - if algorithm == "qft": - basis = ["h", "s", "t", "x", "z", "cx", "cz"] - - logical_circuit = transpile(logical_circuit, basis_gates=basis) - print(logical_circuit.decompose().count_ops()) - + # Strip measure gates to avoid intermediate measurements collapsing the state before decoding stripped_logical_circuit = QuantumCircuit(*logical_circuit.qregs, *logical_circuit.cregs) for inst in logical_circuit.data: From 956006e1055186dca7b505c32ccdef6824aefdd8 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 13 Jun 2026 15:04:12 +0200 Subject: [PATCH 54/72] various improvements to code quality --- tests/test_error_correction.py | 59 +++++++++++++--------------------- 1 file changed, 23 insertions(+), 36 deletions(-) diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index be5737a52..4188523a3 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -45,7 +45,7 @@ @pytest.mark.parametrize("gate", [XGate(), ZGate(), HGate(), SGate()]) def test_errorcorrection_transpiler_gate_equivalence(code: str, gate: Gate) -> None: if gate.name == "s" and code == "shor": - # this SGate entails non-unitary elements and can therefore not be evaluated properly + # this SGate includes non-unitary elements and can therefore not be evaluated properly return num_qubits = gate.num_qubits @@ -128,18 +128,22 @@ def add_h_before_measurements(qc: QuantumCircuit) -> QuantumCircuit: @pytest.mark.parametrize("code", ["shor", "steane"]) # "shor", double parametrize leads to crossproduct -@pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate"]) # , , "bv", "graphstate""qft"]) -@pytest.mark.parametrize("Error", [XGate(), ZGate()]) # , , "bv", "graphstate""qft"]) -@pytest.mark.parametrize("MeasureBaseX", [True, False]) # , , "bv", "graphstate""qft"]) -def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error, MeasureBaseX: bool) -> None: +@pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate"]) +@pytest.mark.parametrize("Error", [XGate(), ZGate()]) +@pytest.mark.parametrize("MeasureBaseX", [True, False]) +@pytest.mark.parametrize("qubits", range(3,5)) +def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error, MeasureBaseX: bool, qubits: int) -> None: """Ensures the transpiler creates error-corrected circuits which produce the same result as the orinigal logical circuit. Afterwards an error is introduced and the test checks, whether it is corrected. Iterates over a number of example algorithms. """ + test_id = f'{qubits} qubit {algorithm} on {code} with ZBasis {MeasureBaseX} and error {Error.name}' + if algorithm == "qft" and code == "shor": # this takes a little longer.... return - circuit_size = 3 + + circuit_size = qubits # Initialize circuits logical_circuit = benchmark_generation.get_benchmark( benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding="" @@ -167,13 +171,10 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error else: transpiler = SteaneTranspiler(logical_circuit, add_syndromes=True) transpiler.transpile() - #transpiler.decode_qubits() + transpiler.decode_qubits() error_corrected_circuit = transpiler.transpiled_qc error_induced_circuit = error_corrected_circuit.copy() - # this is for inserting phase flip in steane after the first Hadamard - # error_induced_circuit = insert_error(error_induced_circuit ,gate=ZGate(), index=16) - # error_induced_circuit = insert_error(error_induced_circuit ,gate=XGate()) error_induced_circuit = insert_error_after_barrier( error_corrected_circuit, barrier_label="Encoding", @@ -185,10 +186,6 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error corrected_counts, error_corrected_circuit = run_circuit(error_corrected_circuit) induced_counts, error_induced_circuit = run_circuit(error_induced_circuit) -# log_circuits({f'logical_{code}_{algorithm}_{Error.name}_{MeasureBaseX}': logical_circuit, -# f'corrected_{code}_{algorithm}_{Error.name}_{MeasureBaseX}':error_corrected_circuit, -# f'induced_{code}_{algorithm}_{Error.name}_{MeasureBaseX}': error_induced_circuit}) - logical_corrected_fidelity = compare_distributions( logical_circuit, error_corrected_circuit, logical_counts, corrected_counts, "none", code ) @@ -196,19 +193,14 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error error_corrected_circuit, error_induced_circuit, corrected_counts, induced_counts, code, code ) - - # print(corrected_counts) - print("condensed:", condense_counts(error_corrected_circuit, corrected_counts)) - print("Logical", logical_counts) - assert logical_corrected_fidelity >= 0.99, ( - f"Error corrected circuit created by {code} transpiler for Algorithm {algorithm} does not match its logical circuit well enough." + f"Error corrected circuit created does not match its logical circuit well enough for {test_id}" ) assert corrected_induced_fidelity >= 0.99, ( - f"Error corrected circuit created by {code} transpiler for Algorithm {algorithm} does not correct the bitflip well enough." + f"Error induced circuit created does not match correct the error well enough for {test_id}" ) -@pytest.mark.parametrize("logical_qubits", range(3,10)) # multiple parametrize lead to crossproducts +@pytest.mark.parametrize("logical_qubits", range(3, 10)) # multiple parametrize lead to crossproducts @pytest.mark.parametrize("alg", ["ghz", "bv", "graphstate", "qft"]) @pytest.mark.parametrize("code", ["shor", "steane"]) def test_error_correction_circuit_structure(code: str, alg: str, logical_qubits: int): @@ -225,13 +217,6 @@ def test_error_correction_circuit_structure(code: str, alg: str, logical_qubits: circuit_size=logical_qubits, encoding='') - - # what do we want: - # expected qubits - # expected classical bits - # qregisters (733) steane, (962) shor - # cregisters (133) steane, (162) shor - qubit_code_factor = -1 classical_code_factor = -1 @@ -245,11 +230,10 @@ def test_error_correction_circuit_structure(code: str, alg: str, logical_qubits: classical_code_factor = 6 - # TODO: figure out register sizes and add them here as well - # Check quantum register sizes: 9n (data) + 6n (bit-flip syndrome) + 2n (phase-flip syndrome) +# # Check quantum register sizes: 7n (data) + 3n (bit-flip syndrome) + 3n (phase-flip syndrome) expected_qreg_sizes = sorted([7] * logical_qubits + [3] * logical_qubits + [3] * logical_qubits) - # Check classical register sizes: 6n (bit-flip) + 2n (phase-flip) + 1 for each original clbit + # Check classical register sizes: 3n (bit-flip) + 3n (phase-flip) + 1 for each original clbit expected_creg_sizes = sorted([3] * logical_qubits + [3] * logical_qubits + [1] * log_qc.num_clbits) elif code == "shor": # Each logical qubit is split in 9 physical qubits @@ -265,8 +249,9 @@ def test_error_correction_circuit_structure(code: str, alg: str, logical_qubits: # Check classical register sizes: 6n (bit-flip) + 2n (phase-flip) + 1 for each original clbit expected_creg_sizes = sorted([6] * logical_qubits + [2] * logical_qubits + [1] * log_qc.num_clbits) + # QFT creates qubits scaling with the number of t-gates -> non-trivial scaling not covered by these simple tests if alg != 'qft': - # QFT creates qubits scaling with the number of t-gates -> non-trivial scaling not covered by these simple tests + expected_qubits = qubit_code_factor * log_qc.num_qubits found_qubits = qc.num_qubits assert found_qubits == expected_qubits, f"Expected {expected_qubits} qubits, found {found_qubits} for {test_id}" @@ -319,7 +304,8 @@ def insert_error_after_barrier( def insert_error(qc: QuantumCircuit, gate: Gate = XGate(), index: int | None = None) -> QuantumCircuit: - """Adds the specified gate at the beginning of the circuit + """ + Adds the specified gate at the beginning of the circuit Flips the first qubit right after the first barrier by default. """ assert qc.num_qubits >= gate.num_qubits, f"Quantum Circuit has not enough qubits to accommodate gate {gate.name}" @@ -351,6 +337,7 @@ def check_equivalence(qc1: qk.QuantumCircuit, qc2: qk.QuantumCircuit) -> bool: verification_results = mqt.qcec.verify(qc1, qc2) accepted_equivalencies = [EC.equivalent, EC.equivalent_up_to_global_phase, EC.probably_equivalent] return verification_results.equivalence in accepted_equivalencies + def measure_all_named(qc: QuantumCircuit, name: str = 'measurement') -> QuantumCircuit: """ Adds a classical register named 'measurement' to the circuit with one bit @@ -398,7 +385,8 @@ def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[dict, QuantumCir def compare_distributions( qc1: QuantumCircuit, qc2: QuantumCircuit, counts1: dict, counts2: dict, code1: str = "None", code2: str = "None" ) -> float: - """Simulates 2 circuits and computes the Hellinger Fidelity between their count distributions + """ + Simulates 2 circuits and computes the Hellinger Fidelity between their count distributions 1 = the same, 0 = no overlap. If code is set to either 'steane' or 'shor' circuit error's result will be interpreted logically @@ -440,7 +428,6 @@ def condense_counts(qc: qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, """Takes in a result dict of a decoded physical measurement and returns logical measurements Requires decode to place the result in the first qubit of each register named 'qx', with x an integer (e.g. 'q2'). """ - # assert code in ['shor', 'steane'], f'Unsupported error code in condense_counts(): {code}' logical_counts = {} for physical_measurement, count in counts.items(): logical_measurement = parse_qubits(qc, physical_measurement) From 7853e8db6aadca5611dbb04e84711e0f5bfe1db1 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 13 Jun 2026 15:29:43 +0200 Subject: [PATCH 55/72] more fixes to resolve the bungled up merge conflict --- tests/test_error_correction.py | 37 +++++++++++++++++++--------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index 0735d7434..34a2bc0f2 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -131,13 +131,13 @@ def add_h_before_measurements(qc: QuantumCircuit) -> QuantumCircuit: @pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate"]) #"qft" is unfeasible @pytest.mark.parametrize("Error", [XGate(), ZGate()]) @pytest.mark.parametrize("MeasureBaseX", [True, False]) -@pytest.mark.parametrize("CIRCUIT_SIZE", range(3,11)) +@pytest.mark.parametrize("CIRCUIT_SIZE", [3])#range(3, 11)) def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error, MeasureBaseX: bool, CIRCUIT_SIZE:int) -> None: """Ensures the transpiler creates error-corrected circuits which produce the same result as the orinigal logical circuit. Afterwards an error is introduced and the test checks, whether it is corrected. Iterates over a number of example algorithms. """ - test_id = f'{CIRCUIT_SIZE} qubit {algorithm} on {code} with ZBasis {MeasureBaseX} and error {Error.name}' + test_id = f"{CIRCUIT_SIZE} qubit {algorithm} on {code} with ZBasis {MeasureBaseX} and error {Error.name}" # Initialize circuits logical_circuit = benchmark_generation.get_benchmark( @@ -160,7 +160,6 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error transpiler = SteaneTranspiler(logical_circuit.copy(), add_syndromes=True) transpiler.transpile() transpiler.decode_qubits() - transpiler.decode_qubits() error_corrected_circuit = transpiler.transpiled_qc error_induced_circuit = error_corrected_circuit.copy() @@ -219,7 +218,7 @@ def test_error_correction_circuit_structure(code: str, alg: str, logical_qubits: classical_code_factor = 6 -# # Check quantum register sizes: 7n (data) + 3n (bit-flip syndrome) + 3n (phase-flip syndrome) + # Check quantum register sizes: 7n (data) + 3n (bit-flip syndrome) + 3n (phase-flip syndrome) expected_qreg_sizes = sorted([7] * logical_qubits + [3] * logical_qubits + [3] * logical_qubits) # Check classical register sizes: 3n (bit-flip) + 3n (phase-flip) + 1 for each original clbit @@ -240,37 +239,43 @@ def test_error_correction_circuit_structure(code: str, alg: str, logical_qubits: # QFT creates qubits scaling with the number of t-gates -> non-trivial scaling not covered by these simple tests if alg != 'qft': - expected_qubits = qubit_code_factor * log_qc.num_qubits found_qubits = qc.num_qubits assert found_qubits == expected_qubits, f"Expected {expected_qubits} qubits, found {found_qubits} for {test_id}" - + expected_clbits = classical_code_factor * log_qc.num_qubits + log_qc.num_clbits found_clbits = qc.num_clbits - assert found_clbits == expected_clbits, f"Expected {expected_clbits} classical bits, found {found_clbits} for {test_id}" + assert found_clbits == expected_clbits, ( + f"Expected {expected_clbits} classical bits, found {found_clbits} for {test_id}" + ) qreg_sizes = sorted(qreg.size for qreg in qc.qregs) - assert qreg_sizes == expected_qreg_sizes, f"Expected qreg sizes {expected_qreg_sizes}, found {qreg_sizes} for {test_id}" + assert qreg_sizes == expected_qreg_sizes, ( + f"Expected qreg sizes {expected_qreg_sizes}, found {qreg_sizes} for {test_id}" + ) creg_sizes = sorted(creg.size for creg in qc.cregs) - assert creg_sizes == expected_creg_sizes, f"Expected creg sizes {expected_creg_sizes}, found {creg_sizes} for {test_id}" - + assert creg_sizes == expected_creg_sizes, ( + f"Expected creg sizes {expected_creg_sizes}, found {creg_sizes} for {test_id}" + ) expected_gate_counts = None import json + json_location = Path(__file__).parent / "gate_counts.json" - with open(f'{json_location}', 'r') as json_data: + with Path(f"{json_location}").open("r", encoding="utf-8") as json_data: expected_gate_counts = json.load(json_data) json_data.close() assert expected_gate_counts is not None, f"Failure reading respective gate counts for {test_id}" - expected_gate_counts = expected_gate_counts[code][alg][f'{logical_qubits}'] - + expected_gate_counts = expected_gate_counts[code][alg][f"{logical_qubits}"] + # Counts the occurrence of every gate in the created circuit created_gate_counts = qc.count_ops() - assert expected_gate_counts == created_gate_counts, f"Created circuit does not contain the expected gates for {test_id}" - + assert expected_gate_counts == created_gate_counts, ( + f"Created circuit does not contain the expected gates for {test_id}" + ) def insert_error_after_barrier( qc: QuantumCircuit, @@ -323,7 +328,7 @@ def check_equivalence(qc1: qk.QuantumCircuit, qc2: qk.QuantumCircuit) -> bool: import mqt.qcec from mqt.qcec.pyqcec import EquivalenceCriterion as EC - verification_results = mqt.qcec.verify(qc1, qc2) + verification_results = mqt.qcec.verify(qc1, qc2, check_partial_equivalence=True) accepted_equivalencies = [EC.equivalent, EC.equivalent_up_to_global_phase, EC.probably_equivalent] return verification_results.equivalence in accepted_equivalencies From b3e2c8bddb71dd9a87fe8688928a20c5801c74dc Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 13 Jun 2026 15:55:47 +0200 Subject: [PATCH 56/72] automatic nox lint changes --- main.py | 322 ----- scratch.py | 11 +- src/mqt/bench/benchmark_generation.py | 3 +- .../benchmarks/seven_qubit_steane_code.py | 8 +- .../bench/benchmarks/shors_nine_qubit_code.py | 9 +- .../components/shor_circuit_components.py | 15 +- .../components/steane_circuit_components.py | 9 +- .../bench/error_correction/shor_transpiler.py | 45 +- .../error_correction/steane_transpiler.py | 79 +- tests/gate_counts.json | 1116 ++++++++--------- tests/test_error_correction.py | 74 +- 11 files changed, 684 insertions(+), 1007 deletions(-) delete mode 100644 main.py diff --git a/main.py b/main.py deleted file mode 100644 index f912a4006..000000000 --- a/main.py +++ /dev/null @@ -1,322 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -import mqt.bench.benchmark_generation as benchmark_generation -from qiskit_aer import AerSimulator # update uv requirements? -import qiskit as qk -from qiskit import QuantumCircuit -from qiskit.result.result import Result -from qiskit_aer.primitives import SamplerV2 - -from mqt.bench.error_correction.shor_transpiler import ShorTranspiler -from mqt.bench.error_correction.steane_transpiler import SteaneTranspiler -from tests.test_error_correction import insert_error -from qiskit.circuit import CircuitInstruction, Gate -from qiskit.circuit.library import XGate, HGate, ZGate -from qiskit.quantum_info import hellinger_fidelity - - -# uv requirements to be added: mqt.qcec, qiskit_aer - - -def errorcode_testing(alg: str = 'ghz', code: str = 'shor', qubits: int = 3): - assert qubits >= 3 - - base_circuit = benchmark_generation.get_benchmark( - benchmark=alg, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=qubits, encoding=code - ) - error_circuit = base_circuit.copy(name='error_circuit') - error_circuit = insert_error_gate(error_circuit) - uncorrected_circuit = benchmark_generation.get_benchmark( - benchmark=alg, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=qubits - ) - - - ### Equivalence checking - equivalent = check_equivalence(base_circuit, error_circuit) - #assert equivalent, 'Insertion of an error (flipped qubit) has lead to a new, no longer equivalent circuit' - print(f'Circuits are equivalent: {equivalent}') - - - - ### Simulated probabilistic similarity Base vs. Error-Inserted - #error_fidelity = compare_distributions(base_circuit, error_circuit) - #threshold = 0.95 # arbitrary guess - #assert fidelity > threshold, f'Simulated Hellinger Fidelity between base and error circuit is too low. Measured: {fidelity}, >Expected: {threshold}' - #print(f'Hellinger Fidelity with error: {error_fidelity}') - - ### Simulated probabilistic similarity Uncorrected vs. Error-Inserted - # TODO: put in error corrected circuit - #### Example for condensing qubits - #example = {'00000001111111': 3, '10101011111111': 1, '11111111010101': 2, '10101011111110' : 7} - #print(condense_counts(example,'stean')) - - """ - uncorrected_fidelity = compare_distributions(uncorrected_circuit, error_circuit, code='shor') - threshold = threshold # arbitrary guess - #assert fidelity > threshold, f'Simulated Hellinger Fidelity between uncorrected and error circuit is too low. Measured: {uncorrected_fidelity}, Expected: >{threshold}' - print(f'Hellinger Fidelity with error: {uncorrected_fidelity}') - """ - - -def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[dict, QuantumCircuit]: - """ - Simulates the circuit using AerSimulator. - - Adds measurements to all qubits, adds new classical registers for each. - Reads out ONLY those measurements and returns their counts - - Returns: - counts of all quantum registers - - qc with measure_all() - """ - sampler = SamplerV2() - qc.measure_all() - job = sampler.run([qc], shots=shots) - result = job.result() - - # Grabbing only the desired outcomes - pub_result = result[0] - meas_bit_counts = pub_result.data.meas.get_counts() - # outputs reversed bitstrings, we just reverse them right back, - # so their indices align with the qubit indices - meas_bit_counts = {k[::-1]: v for k, v in meas_bit_counts.items()} - - return meas_bit_counts, qc - -def insert_error(qc: QuantumCircuit, gate: Gate = XGate(), index: int | None = None) -> QuantumCircuit: - """ - Adds the specified gate at the beginning of the circuit - Flips the first qubit right after the first barrier by default - """ - assert qc.num_qubits >= gate.num_qubits, f'Quantum Circuit has not enough qubits to accomodate gate {gate.name}' - assert index is None or index >= 0, f'Index must be >= 0, Index provided: {index}' - - # Finds the first barrier - if index is None: - for i, instruction in enumerate(qc.data): - if instruction.operation.name == "barrier": - index = i + 1 - break - - # Insert the error gate - qubits = qc.qubits[:gate.num_qubits] - qc.data.insert(index, CircuitInstruction(gate, qubits)) - - return qc - -def check_equivalence(qc1: qk.QuantumCircuit, qc2: qk.QuantumCircuit) -> bool: - """ - Uses MQT QCEC to verify if qc1 and qc2 are equivalent - """ - import mqt.qcec - from mqt.qcec.pyqcec import EquivalenceCriterion as EC - - verification_results = mqt.qcec.verify(qc1, qc2) - accepted_equivalencies = [ - EC.equivalent, - EC.equivalent_up_to_global_phase, - EC.probably_equivalent - ] - equivalent = verification_results.equivalence in accepted_equivalencies - return equivalent - -def compare_distributions(qc1: QuantumCircuit, qc2: QuantumCircuit, counts1: dict, counts2: dict, code1: str = 'None', code2: str = 'None') -> float: - """ - Simulates 2 circuits and computes the Hellinger Fidelity between their count distributions - 1 = the same, 0 = no overlap - - If code is set to either 'steane' or 'shor' circuit error's result will be interpreted logically - """ - - #print(counts1) - if code1 in ['steane', 'shor']: - counts1 = condense_counts(qc1, counts1) - #print(counts1) - - #print(counts2) - if code2 in ['steane', 'shor']: - counts2 = condense_counts(qc2, counts2) - #print(counts2) - - fidelity = hellinger_fidelity(counts1, counts2) - return fidelity - -def parse_qubits(qc: qk.QuantumCircuit, physical_qubits: str): - """ - Takes in a measurement in physical qubits and returns the corresponding logical measurement. - - Underlying circuit must use registers named 'qx' (x in int) for each logical qubit, with results in qx[0] - """ - # remove blanks caused by classical registers - physical_qubits = physical_qubits.replace(' ', '') - - # indices - import re - def is_q_integer(s: str) -> bool: - """ checks if s is of form 'qx' where x in int (e.g. 'q1', 'q23') """ - return bool(re.fullmatch(r'q\d+', s)) - - data_indices = [] - for register in qc.qregs: - if is_q_integer(register.name): - data_indices.append(qc.find_bit(register[0]).index) - - # condensing - logical_qubits = "" - for index in data_indices: - logical_qubits += physical_qubits[index] - - return logical_qubits - - -#def get_logical_classical_indices(qc, name): -# logical_cregs = sorted( -# [cr for cr in qc.cregs if cr.name.startswith(name)], -# key=lambda cr: int(cr.name.replace(name, "")) -# ) -# -# indices = [] -# -# for cr in logical_cregs: -# # assuming each logical register has size 1 -# indices.append(qc.find_bit(cr[0]).index) -# -# return indices - -def condense_counts(qc:qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, int]: - """ - Takes in a result dict of a decoded physical measurement and returns logical measurements - Requires decode to place the result in the first qubit of each register named 'qx', with x an integer (e.g. 'q2') - """ - #assert code in ['shor', 'steane'], f'Unsupported error code in condense_counts(): {code}' - logical_counts = {} - for physical_measurement, count in counts.items(): - logical_measurement = parse_qubits(qc, physical_measurement) - logical_counts[logical_measurement] = logical_counts.get(logical_measurement, 0) + count - - return logical_counts - - -def old_main(): - for alg in ["ghz", "bv", "graphstate"]: # add QFT - for code in ["shor", "stean"]: - # for qubits in range(3, 5): - qubits = 3 - #print(code) - qc = benchmark_generation.get_benchmark( - benchmark=alg, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=qubits, encoding=code - ) - #print(qc) - #print(" _________ ") - - - algorithm = 'ghz' - code = 'shor' - # Initialize circuits - t_circuit = QuantumCircuit(1) - t_circuit.h(0) - t_circuit.t(0) - t_circuit.h(0) - - xcx_circuit = QuantumCircuit(2) - #xcx_circuit.x(0) - xcx_circuit.cx(0,1) - - h_circuit = QuantumCircuit(1) - h_circuit.z(0) - #h_circuit.h(0) - - - measure_circuit = QuantumCircuit(1,1) - measure_circuit.measure(0,0) - - logical_circuit = measure_circuit - - #logical_circuit = benchmark_generation.get_benchmark( - # benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding=code - # ) - error_corrected_circuit = logical_circuit.copy() - code = 'steane' - shor_transpiler = ShorTranspiler(error_corrected_circuit, add_syndromes=False) - steane_transpiler = SteaneTranspiler(logical_circuit, add_syndromes=False) - if code == 'shor': - transpiler = shor_transpiler - else: - transpiler = steane_transpiler - transpiler.transpile() - transpiler.decode_qubits() - error_corrected_circuit = transpiler.transpiled_qc - - error_induced_circuit = error_corrected_circuit.copy() - # this is for inserting phase flip in steane after the first Hadamard - #error_induced_circuit = insert_error(error_induced_circuit ,gate=ZGate(), index=16) - error_induced_circuit = insert_error(error_induced_circuit ,gate=XGate()) - - - #print(check_equivalence(logical_circuit, error_corrected_circuit)) - #print(check_equivalence(error_corrected_circuit, error_induced_circuit)) - - logical_counts, logical_circuit = run_circuit(logical_circuit) - corrected_counts, error_corrected_circuit = run_circuit(error_corrected_circuit) - induced_counts, error_induced_circuit = run_circuit(error_induced_circuit) - - - print(" __________________________________________________________________________________________ ") - print('Logical Circuit:') - print(logical_circuit) - print(" __________________________________________________________________________________________ ") - print('Error corrected Circuit:') - print(error_corrected_circuit) - print(" __________________________________________________________________________________________ ") - print('Error Induced Circuit') - print(error_induced_circuit) - print(" __________________________________________________________________________________________ ") - - - - print(compare_distributions(logical_circuit, error_corrected_circuit, logical_counts, corrected_counts, 'none', code)) - print(compare_distributions(error_corrected_circuit, error_induced_circuit, corrected_counts, induced_counts, code, code)) - - -def create_gate_counts(): - """ - Use this to create the counts for each code, algorithm and arbitray qubit numbers - """ - import json - from pathlib import Path - gates = {} - algs = {} - qs = {} - for code in ["shor", "steane"]: - algs = {} - for alg in ["ghz", "bv", "graphstate", "qft"]: # Bonus for "qft" (Part 3) - qs = {} - for qubits in range(3, 10): - qc = benchmark_generation.get_benchmark( - benchmark=alg, - level=benchmark_generation.BenchmarkLevel.ALG, - circuit_size=qubits, - encoding=code) - qs[qubits] = qc.count_ops() - algs[alg] = qs - gates[code] = algs - - log_dir = Path(__file__).parent / "tests" - log_dir.mkdir(exist_ok=True) - - filename = log_dir / f"gate_counts.json" - with open(filename, "w") as f: - json.dump(gates, f, indent=4) - - -if __name__ == "__main__": - #old_main() - - create_gate_counts() diff --git a/scratch.py b/scratch.py index 7bfa6994f..adf7e4e63 100644 --- a/scratch.py +++ b/scratch.py @@ -1,7 +1,14 @@ -import qiskit +# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM +# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH +# All rights reserved. +# +# SPDX-License-Identifier: MIT +# +# Licensed under the MIT License + from qiskit import QuantumCircuit -from qiskit_aer import AerSimulator from qiskit.quantum_info import partial_trace +from qiskit_aer import AerSimulator qc = QuantumCircuit(2, 1) qc.h(0) diff --git a/src/mqt/bench/benchmark_generation.py b/src/mqt/bench/benchmark_generation.py index bb4ab4f39..f40516c2b 100644 --- a/src/mqt/bench/benchmark_generation.py +++ b/src/mqt/bench/benchmark_generation.py @@ -21,6 +21,7 @@ from qiskit.converters import circuit_to_dag from qiskit.transpiler import Layout, Target from typing_extensions import assert_never + from .error_correction.shor_transpiler import ShorTranspiler from .error_correction.steane_transpiler import SteaneTranspiler @@ -230,7 +231,7 @@ def get_benchmark_alg( transpiler = ShorTranspiler(qc, add_syndromes=True) transpiler.transpile() return transpiler.transpiled_qc - elif encoding == "steane": + if encoding == "steane": transpiler = SteaneTranspiler(qc, add_syndromes=True) transpiler.transpile() return transpiler.transpiled_qc diff --git a/src/mqt/bench/benchmarks/seven_qubit_steane_code.py b/src/mqt/bench/benchmarks/seven_qubit_steane_code.py index a006f8902..605eea29d 100644 --- a/src/mqt/bench/benchmarks/seven_qubit_steane_code.py +++ b/src/mqt/bench/benchmarks/seven_qubit_steane_code.py @@ -13,15 +13,15 @@ from qiskit import ClassicalRegister from qiskit.circuit import AncillaRegister, QuantumCircuit, QuantumRegister -from ._registry import register_benchmark - from mqt.bench.components.steane_circuit_components import ( - get_seven_qubit_steane_code_encoding_circuit, + apply_seven_qubit_steane_code_correction, get_seven_qubit_steane_code_decoding_circuit, + get_seven_qubit_steane_code_encoding_circuit, get_seven_qubit_steane_code_syndrome_extraction_circuit, - apply_seven_qubit_steane_code_correction ) +from ._registry import register_benchmark + def _create_single_logical_qubit_circuit(index: int) -> QuantumCircuit: """Create a complete Steane code circuit for one logical qubit. diff --git a/src/mqt/bench/benchmarks/shors_nine_qubit_code.py b/src/mqt/bench/benchmarks/shors_nine_qubit_code.py index 752dcef32..1eed0886d 100644 --- a/src/mqt/bench/benchmarks/shors_nine_qubit_code.py +++ b/src/mqt/bench/benchmarks/shors_nine_qubit_code.py @@ -9,6 +9,7 @@ """Shor's 9 Qubit Code benchmark definition.""" from __future__ import annotations + from qiskit import ClassicalRegister from qiskit.circuit import AncillaRegister, QuantumCircuit, QuantumRegister @@ -18,12 +19,13 @@ get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit, get_three_qubit_bit_flip_encoding_decoding_circuit, get_three_qubit_bit_flip_syndrome_extraction_circuit, - get_three_qubit_phase_flip_encoding_circuit, get_three_qubit_phase_flip_decoding_circuit, + get_three_qubit_phase_flip_encoding_circuit, ) from ._registry import register_benchmark + def _create_single_logical_qubit_circuit(index: int) -> QuantumCircuit: """Create a complete Shor code circuit for one logical qubit. @@ -86,9 +88,7 @@ def _create_single_logical_qubit_circuit(index: int) -> QuantumCircuit: ) qc.barrier() # == Error correction == - apply_nine_qubit_shors_code_bit_flip_correction( - qc, logical_qubit, bit_flip_syndrome, bit_flip_syndrome_measurement - ) + apply_nine_qubit_shors_code_bit_flip_correction(qc, logical_qubit, bit_flip_syndrome, bit_flip_syndrome_measurement) qc.barrier() apply_nine_qubit_shors_code_phase_flip_correction( qc, logical_qubit, phase_flip_syndrome, phase_flip_syndrome_measurement @@ -107,6 +107,7 @@ def _create_single_logical_qubit_circuit(index: int) -> QuantumCircuit: qc.measure(logical_qubit[0], logical_qubit_measurement) return qc + @register_benchmark("shors_nine_qubit_code", description="Shor's 9 Qubit Code") def create_circuit(num_qubits: int) -> QuantumCircuit: """Returns a quantum circuit implementing Shor's 9 Qubit Code. diff --git a/src/mqt/bench/components/shor_circuit_components.py b/src/mqt/bench/components/shor_circuit_components.py index 46edc09a6..b7bf50ed1 100644 --- a/src/mqt/bench/components/shor_circuit_components.py +++ b/src/mqt/bench/components/shor_circuit_components.py @@ -10,9 +10,14 @@ from __future__ import annotations +from typing import TYPE_CHECKING + +from qiskit.circuit import AncillaRegister, QuantumCircuit, QuantumRegister + +if TYPE_CHECKING: + from qiskit.circuit import ClassicalRegister + -from qiskit.circuit import AncillaRegister, QuantumCircuit, QuantumRegister, ClassicalRegister - def get_three_qubit_phase_flip_decoding_circuit() -> QuantumCircuit: """Create 3-qubit phase-flip decoding circuit. @@ -29,6 +34,7 @@ def get_three_qubit_phase_flip_decoding_circuit() -> QuantumCircuit: out.cx(0, 2) return out + def get_three_qubit_bit_flip_encoding_decoding_circuit() -> QuantumCircuit: """Create 3-qubit bit-flip encoding/decoding circuit. @@ -42,6 +48,7 @@ def get_three_qubit_bit_flip_encoding_decoding_circuit() -> QuantumCircuit: out.cx(0, 2) return out + def get_three_qubit_phase_flip_encoding_circuit() -> QuantumCircuit: """Create 3-qubit phase-flip encoding circuit. @@ -58,6 +65,7 @@ def get_three_qubit_phase_flip_encoding_circuit() -> QuantumCircuit: out.h(2) return out + def get_three_qubit_bit_flip_syndrome_extraction_circuit() -> QuantumCircuit: """Create circuit to extract bit-flip syndrome from a 3-qubit block. @@ -74,6 +82,7 @@ def get_three_qubit_bit_flip_syndrome_extraction_circuit() -> QuantumCircuit: out.cx(2, 4) return out + def get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit() -> QuantumCircuit: """Create circuit to extract phase-flip syndrome across the three 3-qubit blocks. @@ -107,6 +116,7 @@ def get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit() -> Quantu out.h(phase_flip_syndrome[1]) return out + def apply_nine_qubit_shors_code_bit_flip_correction( qc: QuantumCircuit, logical_qubit: QuantumRegister, @@ -165,4 +175,3 @@ def apply_nine_qubit_shors_code_phase_flip_correction( qc.z(logical_qubit[3]) with qc.if_test((phase_flip_syndrome_measurement, 0b11)): qc.z(logical_qubit[6]) - diff --git a/src/mqt/bench/components/steane_circuit_components.py b/src/mqt/bench/components/steane_circuit_components.py index b5b6c3856..38a596ab2 100644 --- a/src/mqt/bench/components/steane_circuit_components.py +++ b/src/mqt/bench/components/steane_circuit_components.py @@ -10,8 +10,13 @@ from __future__ import annotations +from typing import TYPE_CHECKING + +from qiskit.circuit import AncillaRegister, QuantumCircuit, QuantumRegister + +if TYPE_CHECKING: + from qiskit.circuit import ClassicalRegister -from qiskit.circuit import AncillaRegister, QuantumCircuit, QuantumRegister, ClassicalRegister def get_seven_qubit_steane_code_encoding_circuit() -> QuantumCircuit: """Create the 7-qubit Steane code encoding circuit. @@ -130,4 +135,4 @@ def apply_seven_qubit_steane_code_correction( # Phase-flip correction: syndrome value directly indicates which qubit to correct for i in range(7): with qc.if_test((phase_flip_syndrome_measurement, i + 1)): - qc.z(logical_qubit[i]) \ No newline at end of file + qc.z(logical_qubit[i]) diff --git a/src/mqt/bench/error_correction/shor_transpiler.py b/src/mqt/bench/error_correction/shor_transpiler.py index 2ca8c79fe..ce8d8fad9 100644 --- a/src/mqt/bench/error_correction/shor_transpiler.py +++ b/src/mqt/bench/error_correction/shor_transpiler.py @@ -11,12 +11,13 @@ from __future__ import annotations from dataclasses import dataclass +from typing import TYPE_CHECKING import numpy as np from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister, transpile from qiskit.circuit import AncillaRegister -#ignore the below comment +# ignore the below comment # these functions are reused from the benchmark and they should be extendable i.e. they shouldn't be private from mqt.bench.components.shor_circuit_components import ( apply_nine_qubit_shors_code_bit_flip_correction, @@ -27,8 +28,8 @@ get_three_qubit_phase_flip_encoding_circuit, ) - -from collections.abc import Callable +if TYPE_CHECKING: + from collections.abc import Callable # Constants for the Shor 9-qubit code structure SHOR_TOTAL_QUBITS = 9 @@ -40,6 +41,7 @@ @dataclass class ShorLogicalQubit: """Encapsulates the physical registers representing a single Shor logical qubit.""" + data: QuantumRegister bit_flip_syndrome: AncillaRegister | None = None phase_flip_syndrome: AncillaRegister | None = None @@ -94,7 +96,7 @@ def encode_qubits(self) -> None: bit_flip_syndrome=AncillaRegister(6, f"bs{i}"), phase_flip_syndrome=AncillaRegister(2, f"ps{i}"), bit_flip_measure=ClassicalRegister(6, f"bsm{i}"), - phase_flip_measure=ClassicalRegister(2, f"psm{i}") + phase_flip_measure=ClassicalRegister(2, f"psm{i}"), ) else: logical_qubit = ShorLogicalQubit(data=data_reg) @@ -206,10 +208,7 @@ def replace_gates(self) -> None: def _logical_barrier(self, logical_qubit_indices: list[int]) -> None: """Apply logical barrier across the specified physical qubits.""" - involved_physical_data_registers = [ - self.logical_qubits[idx].data - for idx in logical_qubit_indices - ] + involved_physical_data_registers = [self.logical_qubits[idx].data for idx in logical_qubit_indices] flattened_physical_qubits = [ physical_qubit for physical_data_register in involved_physical_data_registers @@ -274,7 +273,14 @@ def _logical_z(self, logical_qubit_index: int) -> None: self.transpiled_qc.x(q) self.insert_syndromes(logical_qubit_index) - def _apply_teleportation_gadget(self, logical_qubit_index: int, phase: float, ancilla_name: str, measure_name: str, correction_callback: Callable) -> None: + def _apply_teleportation_gadget( + self, + logical_qubit_index: int, + phase: float, + ancilla_name: str, + measure_name: str, + correction_callback: Callable, + ) -> None: """Apply a magic state gate teleportation gadget (used for non-transversal S and T gates).""" ancilla_register = QuantumRegister(SHOR_TOTAL_QUBITS, ancilla_name) creg = ClassicalRegister(1, measure_name) @@ -287,7 +293,7 @@ def _apply_teleportation_gadget(self, logical_qubit_index: int, phase: float, an self._prepare_magic(self.transpiled_qc, ancilla_register, phase) # Transversal logical CNOT - self._apply_logical_cx(physical_data_register,ancilla_register) + self._apply_logical_cx(physical_data_register, ancilla_register) # Decode and measure ancilla in logical Z basis self._apply_shor_decoding(self.transpiled_qc, ancilla_register) @@ -302,6 +308,7 @@ def _apply_teleportation_gadget(self, logical_qubit_index: int, phase: float, an def _logical_s(self, logical_qubit_index: int) -> None: """Apply logical S via |Y>-state teleportation. Correction: logical Z.""" self.s_gate_count += 1 + def z_correction() -> None: self._logical_z(logical_qubit_index) @@ -310,7 +317,7 @@ def z_correction() -> None: phase=np.pi / 2, ancilla_name=f"ms{self.s_gate_count - 1}", measure_name=f"tmeas{self.s_gate_count - 1}", - correction_callback=z_correction + correction_callback=z_correction, ) def _logical_t(self, logical_qubit_index: int) -> None: @@ -325,7 +332,7 @@ def s_correction() -> None: phase=np.pi / 4, ancilla_name=f"anc_t_{self.t_gate_count}", measure_name=f"creg_t_{self.t_gate_count}", - correction_callback=s_correction + correction_callback=s_correction, ) @staticmethod @@ -338,10 +345,7 @@ def _prepare_magic(qc: QuantumCircuit, physical_ancilla_register: QuantumRegiste def _apply_logical_cx(self, control_register: QuantumRegister, target_register: QuantumRegister) -> None: """Apply transversal logical CX between two physical registers.""" for physical_qubit_index in range(SHOR_TOTAL_QUBITS): - self.transpiled_qc.cx( - target_register[physical_qubit_index], - control_register[physical_qubit_index] - ) + self.transpiled_qc.cx(target_register[physical_qubit_index], control_register[physical_qubit_index]) def _logical_cx(self, control_logical_qubit_index: int, target_logical_qubit_index: int) -> None: """Apply transversal logical CX. @@ -361,7 +365,7 @@ def _logical_cz(self, control_logical_qubit_index: int, target_logical_qubit_ind """Apply logical CZ (implemented as H-CX-H).""" self._logical_h(target_logical_qubit_index) self.transpiled_qc.barrier() - self._logical_cx(control_logical_qubit_index,target_logical_qubit_index) + self._logical_cx(control_logical_qubit_index, target_logical_qubit_index) self.transpiled_qc.barrier() self._logical_h(target_logical_qubit_index) @@ -370,7 +374,6 @@ def insert_syndromes(self, logical_qubit_index: int) -> None: if not self.add_syndromes: return - qubit = self.logical_qubits[logical_qubit_index] self.transpiled_qc.barrier() @@ -389,7 +392,8 @@ def _extract_bit_flip_syndromes(self, qubit: ShorLogicalQubit) -> None: for i in range(SHOR_NUM_BLOCKS): self.transpiled_qc.compose( get_three_qubit_bit_flip_syndrome_extraction_circuit(), - qubits=qubit.data[i * SHOR_BLOCK_SIZE : (i + 1) * SHOR_BLOCK_SIZE] + qubit.bit_flip_syndrome[i * 2 : (i + 1) * 2], + qubits=qubit.data[i * SHOR_BLOCK_SIZE : (i + 1) * SHOR_BLOCK_SIZE] + + qubit.bit_flip_syndrome[i * 2 : (i + 1) * 2], inplace=True, ) @@ -417,6 +421,3 @@ def _apply_error_corrections(self, qubit: ShorLogicalQubit) -> None: qubit.phase_flip_syndrome, qubit.phase_flip_measure, ) - - - diff --git a/src/mqt/bench/error_correction/steane_transpiler.py b/src/mqt/bench/error_correction/steane_transpiler.py index dcdac54a9..b925cc0e4 100644 --- a/src/mqt/bench/error_correction/steane_transpiler.py +++ b/src/mqt/bench/error_correction/steane_transpiler.py @@ -13,13 +13,13 @@ from typing import TYPE_CHECKING from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister, transpile -from qiskit.circuit import AncillaRegister, barrier +from qiskit.circuit import AncillaRegister from mqt.bench.components.steane_circuit_components import ( - get_seven_qubit_steane_code_encoding_circuit, + apply_seven_qubit_steane_code_correction, get_seven_qubit_steane_code_decoding_circuit, + get_seven_qubit_steane_code_encoding_circuit, get_seven_qubit_steane_code_syndrome_extraction_circuit, - apply_seven_qubit_steane_code_correction ) if TYPE_CHECKING: @@ -29,7 +29,7 @@ class SteaneTranspiler: """A high-level transpiler that encodes a QuantumCircuit using Steane's 7-qubit error correction code.""" - def __init__(self, original_circuit: QuantumCircuit, add_syndromes = True) -> None: + def __init__(self, original_circuit: QuantumCircuit, add_syndromes=True) -> None: """Initialize the transpiler with the original QuantumCircuit.""" self.original_qc = original_circuit self.num_logical_qubits = original_circuit.num_qubits @@ -38,7 +38,7 @@ def __init__(self, original_circuit: QuantumCircuit, add_syndromes = True) -> No self.phase_flip_syndromes: list[AncillaRegister] = [] self.bit_flip_syndrome_measurements: list[ClassicalRegister] = [] self.phase_flip_syndrome_measurements: list[ClassicalRegister] = [] - #self.logical_qubit_measurements: list[ClassicalRegister] = [] + # self.logical_qubit_measurements: list[ClassicalRegister] = [] self.add_syndromes = add_syndromes self.t_gate_count = 0 self.transpiled_qc = QuantumCircuit() @@ -51,10 +51,9 @@ def __init__(self, original_circuit: QuantumCircuit, add_syndromes = True) -> No "s": self._handle_s, "cx": self._handle_cx, "cz": self._handle_cz, - "t": self._handle_t + "t": self._handle_t, } - def transpile(self) -> QuantumCircuit: """Transpile the original circuit to a fault-tolerant circuit using Steane's code.""" self.encode_qubits() @@ -65,20 +64,20 @@ def encode_qubits(self) -> None: """Replace each logical qubit with a 7-qubit physical register and apply Steane encoding.""" all_registers = [] for logical_qubit_index in range(self.num_logical_qubits): - #use another name as logical_qubit + # use another name as logical_qubit physical_data_register = QuantumRegister(7, f"q{logical_qubit_index}") bit_flip_syndrome_register = AncillaRegister(3, f"bs{logical_qubit_index}") phase_flip_syndrome_register = AncillaRegister(3, f"ps{logical_qubit_index}") bit_flip_measurement_register = ClassicalRegister(3, f"bsm{logical_qubit_index}") phase_flip_measurement_register = ClassicalRegister(3, f"psm{logical_qubit_index}") - #logical_qubit_measurement_register = ClassicalRegister(1, f"logical_meas{logical_qubit_index}") + # logical_qubit_measurement_register = ClassicalRegister(1, f"logical_meas{logical_qubit_index}") self.physical_data_registers.append(physical_data_register) self.bit_flip_syndromes.append(bit_flip_syndrome_register) self.phase_flip_syndromes.append(phase_flip_syndrome_register) self.bit_flip_syndrome_measurements.append(bit_flip_measurement_register) self.phase_flip_syndrome_measurements.append(phase_flip_measurement_register) - #self.logical_qubit_measurements.append(logical_qubit_measurement_register) + # self.logical_qubit_measurements.append(logical_qubit_measurement_register) all_registers.extend([ physical_data_register, @@ -86,7 +85,7 @@ def encode_qubits(self) -> None: phase_flip_syndrome_register, bit_flip_measurement_register, phase_flip_measurement_register, - #logical_qubit_measurement_register + # logical_qubit_measurement_register ]) self.transpiled_qc = QuantumCircuit(*all_registers) @@ -98,9 +97,9 @@ def encode_qubits(self) -> None: # Phase flip encoding on the first qubit of each block self.transpiled_qc.compose( - get_seven_qubit_steane_code_encoding_circuit(), - qubits=physical_data_register[:], - inplace=True, + get_seven_qubit_steane_code_encoding_circuit(), + qubits=physical_data_register[:], + inplace=True, ) self.transpiled_qc.barrier(label="Encoding") @@ -109,16 +108,14 @@ def decode_qubits(self) -> None: self.transpiled_qc.barrier() for logical_qubit_index in range(self.num_logical_qubits): physical_data_register = self.physical_data_registers[logical_qubit_index] - self.transpiled_qc.compose(get_seven_qubit_steane_code_decoding_circuit(), - qubits=physical_data_register[:], - inplace=True - ) + self.transpiled_qc.compose( + get_seven_qubit_steane_code_decoding_circuit(), qubits=physical_data_register[:], inplace=True + ) self.transpiled_qc.barrier() def replace_gates(self) -> None: """Scan original circuit and replace gates with logical equivalents.""" - - # Firstly, exapand high level gates, such as QFTGate() + # Firstly, expand high level gates, such as QFTGate() normalized = QuantumCircuit(*self.original_qc.qregs, *self.original_qc.cregs) for instruction in self.original_qc.data: gate_name = instruction.operation.name @@ -164,21 +161,26 @@ def _handle_barrier(self, instruction: CircuitInstruction) -> None: physical_data_register = self.physical_data_registers[i] bit_flip_syndromes_register = self.bit_flip_syndromes[i] phase_flip_syndromes_register = self.phase_flip_syndromes[i] - barrier_register.extend([physical_data_register, bit_flip_syndromes_register, phase_flip_syndromes_register]) - self.transpiled_qc.barrier( *barrier_register,label=f"Barrier") + barrier_register.extend([ + physical_data_register, + bit_flip_syndromes_register, + phase_flip_syndromes_register, + ]) + self.transpiled_qc.barrier(*barrier_register, label="Barrier") def _handle_measure(self, instruction: CircuitInstruction) -> None: """Handle measure instruction.""" # TODO: consider measure_all(), because of new meas register everything goes wrong - for q, c in zip(instruction.qubits, instruction.clbits): + for q, c in zip(instruction.qubits, instruction.clbits, strict=False): logical_qubit_index = self.original_qc.qubits.index(q) logical_classical_bit_index = self.original_qc.clbits.index(c) - self.transpiled_qc.compose(get_seven_qubit_steane_code_decoding_circuit(), - qubits=self.physical_data_registers[logical_qubit_index], - inplace=True - ) + self.transpiled_qc.compose( + get_seven_qubit_steane_code_decoding_circuit(), + qubits=self.physical_data_registers[logical_qubit_index], + inplace=True, + ) measurement_register_name = f"meas_{logical_qubit_index}_{logical_classical_bit_index}" physical_measurement_register = ClassicalRegister(1, measurement_register_name) @@ -187,7 +189,7 @@ def _handle_measure(self, instruction: CircuitInstruction) -> None: physical_data_register = self.physical_data_registers[logical_qubit_index][0] self.transpiled_qc.measure(physical_data_register, physical_measurement_register) - #self.transpiled_qc.measure(self.physical_data_registers[logical_qubit_index][0], + # self.transpiled_qc.measure(self.physical_data_registers[logical_qubit_index][0], # self.logical_qubit_measurements[logical_classical_bit_index]) self.transpiled_qc.barrier(label=f"Measurement {logical_qubit_index}") @@ -227,7 +229,7 @@ def _handle_z(self, instruction: CircuitInstruction) -> None: def _handle_s(self, instruction: CircuitInstruction) -> None: """Handle S instruction.""" - #S Made cia SDG + # S Made cia SDG logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) physical_data_register = self.physical_data_registers[logical_qubit_index] @@ -266,14 +268,12 @@ def _handle_t(self, instruction: CircuitInstruction) -> None: self.transpiled_qc.cx(physical_data_register, t_ancilla_register) # made logical measurement - self.transpiled_qc.compose(get_seven_qubit_steane_code_decoding_circuit(), - qubits=t_ancilla_register, - inplace=True - ) - self.transpiled_qc.measure(t_ancilla_register[0], - t_test_register[0]) + self.transpiled_qc.compose( + get_seven_qubit_steane_code_decoding_circuit(), qubits=t_ancilla_register, inplace=True + ) + self.transpiled_qc.measure(t_ancilla_register[0], t_test_register[0]) - # Think about whther need to add error correction after these logical gates + # Think about whether need to add error correction after these logical gates # apply if_test with self.transpiled_qc.if_test((t_test_register[0], 1)): @@ -283,7 +283,6 @@ def _handle_t(self, instruction: CircuitInstruction) -> None: if self.add_syndromes: self.insert_syndromes(logical_qubit_index) - def _handle_cx(self, instruction: CircuitInstruction) -> None: """Handle CX instruction.""" control_logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) @@ -330,7 +329,7 @@ def insert_syndromes(self, logical_qubit_index: int) -> None: bit_flip_measurement_register = self.bit_flip_syndrome_measurements[logical_qubit_index] phase_flip_measurement_register = self.phase_flip_syndrome_measurements[logical_qubit_index] - self.transpiled_qc.barrier(label="Syndrom Start") + self.transpiled_qc.barrier(label="Syndrome Start") # clean ancillas self.transpiled_qc.reset(bit_flip_syndrome_register) @@ -341,12 +340,10 @@ def insert_syndromes(self, logical_qubit_index: int) -> None: get_seven_qubit_steane_code_syndrome_extraction_circuit(), qubits=physical_data_register[:] + bit_flip_syndrome_register[:] + phase_flip_syndrome_register[:], inplace=True, - ) self.transpiled_qc.barrier() - # Error correction apply_seven_qubit_steane_code_correction( self.transpiled_qc, @@ -354,7 +351,7 @@ def insert_syndromes(self, logical_qubit_index: int) -> None: bit_flip_syndrome_register, phase_flip_syndrome_register, bit_flip_measurement_register, - phase_flip_measurement_register + phase_flip_measurement_register, ) self.transpiled_qc.barrier(label="Correction End") diff --git a/tests/gate_counts.json b/tests/gate_counts.json index fa62dacb1..17d2fb387 100644 --- a/tests/gate_counts.json +++ b/tests/gate_counts.json @@ -1,561 +1,561 @@ { - "shor": { - "ghz": { - "3": { - "cx": 186, - "if_else": 60, - "h": 47, - "measure": 43, - "reset": 40, - "barrier": 27, - "swap": 3 - }, - "4": { - "cx": 259, - "if_else": 84, - "h": 61, - "measure": 60, - "reset": 56, - "barrier": 37, - "swap": 3 - }, - "5": { - "cx": 332, - "if_else": 108, - "measure": 77, - "h": 75, - "reset": 72, - "barrier": 47, - "swap": 3 - }, - "6": { - "cx": 405, - "if_else": 132, - "measure": 94, - "h": 89, - "reset": 88, - "barrier": 57, - "swap": 3 - }, - "7": { - "cx": 478, - "if_else": 156, - "measure": 111, - "reset": 104, - "h": 103, - "barrier": 67, - "swap": 3 - }, - "8": { - "cx": 551, - "if_else": 180, - "measure": 128, - "reset": 120, - "h": 117, - "barrier": 77, - "swap": 3 - }, - "9": { - "cx": 624, - "if_else": 204, - "measure": 145, - "reset": 136, - "h": 131, - "barrier": 87, - "swap": 3 - } - }, - "bv": { - "3": { - "cx": 265, - "if_else": 108, - "h": 105, - "measure": 74, - "reset": 72, - "barrier": 48, - "swap": 18, - "z": 3 - }, - "4": { - "cx": 329, - "h": 137, - "if_else": 132, - "measure": 91, - "reset": 88, - "barrier": 58, - "swap": 24, - "z": 3 - }, - "5": { - "cx": 498, - "if_else": 204, - "h": 203, - "measure": 140, - "reset": 136, - "barrier": 90, - "swap": 36, - "z": 3 - }, - "6": { - "cx": 562, - "h": 235, - "if_else": 228, - "measure": 157, - "reset": 152, - "barrier": 100, - "swap": 42, - "z": 3 - }, - "7": { - "cx": 731, - "h": 301, - "if_else": 300, - "measure": 206, - "reset": 200, - "barrier": 132, - "swap": 54, - "z": 3 - }, - "8": { - "cx": 795, - "h": 333, - "if_else": 324, - "measure": 223, - "reset": 216, - "barrier": 142, - "swap": 60, - "z": 3 - }, - "9": { - "cx": 964, - "h": 399, - "if_else": 396, - "measure": 272, - "reset": 264, - "barrier": 174, - "swap": 72, - "z": 3 - } - }, - "graphstate": { - "3": { - "cx": 435, - "if_else": 180, - "h": 159, - "measure": 123, - "reset": 120, - "barrier": 83, - "swap": 27 - }, - "4": { - "cx": 580, - "if_else": 240, - "h": 212, - "measure": 164, - "reset": 160, - "barrier": 110, - "swap": 36 - }, - "5": { - "cx": 725, - "if_else": 300, - "h": 265, - "measure": 205, - "reset": 200, - "barrier": 137, - "swap": 45 - }, - "6": { - "cx": 870, - "if_else": 360, - "h": 318, - "measure": 246, - "reset": 240, - "barrier": 164, - "swap": 54 - }, - "7": { - "cx": 1015, - "if_else": 420, - "h": 371, - "measure": 287, - "reset": 280, - "barrier": 191, - "swap": 63 - }, - "8": { - "cx": 1160, - "if_else": 480, - "h": 424, - "measure": 328, - "reset": 320, - "barrier": 218, - "swap": 72 - }, - "9": { - "cx": 1305, - "if_else": 540, - "h": 477, - "measure": 369, - "reset": 360, - "barrier": 245, - "swap": 81 - } - }, - "qft": { - "3": { - "cx": 788, - "if_else": 260, - "h": 185, - "measure": 179, - "reset": 168, - "barrier": 107, - "swap": 9, - "p": 8, - "x": 6 - }, - "4": { - "cx": 1162, - "if_else": 384, - "h": 268, - "measure": 264, - "reset": 248, - "barrier": 157, - "swap": 12, - "p": 12, - "x": 9 - }, - "5": { - "cx": 1536, - "if_else": 508, - "h": 351, - "measure": 349, - "reset": 328, - "barrier": 207, - "p": 16, - "swap": 15, - "x": 12 - }, - "6": { - "cx": 1910, - "if_else": 632, - "h": 434, - "measure": 434, - "reset": 408, - "barrier": 257, - "p": 20, - "swap": 18, - "x": 15 - }, - "7": { - "cx": 2284, - "if_else": 756, - "measure": 519, - "h": 517, - "reset": 488, - "barrier": 307, - "p": 24, - "swap": 21, - "x": 18 - }, - "8": { - "cx": 2658, - "if_else": 880, - "measure": 604, - "h": 600, - "reset": 568, - "barrier": 357, - "p": 28, - "swap": 24, - "x": 21 - }, - "9": { - "cx": 3032, - "if_else": 1004, - "measure": 689, - "h": 683, - "reset": 648, - "barrier": 407, - "p": 32, - "swap": 27, - "x": 24 - } - } + "shor": { + "ghz": { + "3": { + "cx": 186, + "if_else": 60, + "h": 47, + "measure": 43, + "reset": 40, + "barrier": 27, + "swap": 3 + }, + "4": { + "cx": 259, + "if_else": 84, + "h": 61, + "measure": 60, + "reset": 56, + "barrier": 37, + "swap": 3 + }, + "5": { + "cx": 332, + "if_else": 108, + "measure": 77, + "h": 75, + "reset": 72, + "barrier": 47, + "swap": 3 + }, + "6": { + "cx": 405, + "if_else": 132, + "measure": 94, + "h": 89, + "reset": 88, + "barrier": 57, + "swap": 3 + }, + "7": { + "cx": 478, + "if_else": 156, + "measure": 111, + "reset": 104, + "h": 103, + "barrier": 67, + "swap": 3 + }, + "8": { + "cx": 551, + "if_else": 180, + "measure": 128, + "reset": 120, + "h": 117, + "barrier": 77, + "swap": 3 + }, + "9": { + "cx": 624, + "if_else": 204, + "measure": 145, + "reset": 136, + "h": 131, + "barrier": 87, + "swap": 3 + } }, - "steane": { - "ghz": { - "3": { - "cx": 200, - "if_else": 70, - "h": 55, - "measure": 33, - "reset": 30, - "barrier": 23 - }, - "4": { - "cx": 277, - "if_else": 98, - "h": 73, - "measure": 46, - "reset": 42, - "barrier": 31 - }, - "5": { - "cx": 354, - "if_else": 126, - "h": 91, - "measure": 59, - "reset": 54, - "barrier": 39 - }, - "6": { - "cx": 431, - "if_else": 154, - "h": 109, - "measure": 72, - "reset": 66, - "barrier": 47 - }, - "7": { - "cx": 508, - "if_else": 182, - "h": 127, - "measure": 85, - "reset": 78, - "barrier": 55 - }, - "8": { - "cx": 585, - "if_else": 210, - "h": 145, - "measure": 98, - "reset": 90, - "barrier": 63 - }, - "9": { - "cx": 662, - "if_else": 238, - "h": 163, - "measure": 111, - "reset": 102, - "barrier": 71 - } - }, - "bv": { - "3": { - "cx": 223, - "if_else": 98, - "h": 85, - "measure": 44, - "reset": 42, - "barrier": 30, - "x": 7, - "cz": 7 - }, - "4": { - "cx": 293, - "if_else": 126, - "h": 117, - "measure": 57, - "reset": 54, - "barrier": 39, - "x": 7, - "cz": 7 - }, - "5": { - "cx": 411, - "if_else": 182, - "h": 161, - "measure": 82, - "reset": 78, - "barrier": 55, - "cz": 14, - "x": 7 - }, - "6": { - "cx": 481, - "if_else": 210, - "h": 193, - "measure": 95, - "reset": 90, - "barrier": 64, - "cz": 14, - "x": 7 - }, - "7": { - "cx": 599, - "if_else": 266, - "h": 237, - "measure": 120, - "reset": 114, - "barrier": 80, - "cz": 21, - "x": 7 - }, - "8": { - "cx": 669, - "if_else": 294, - "h": 269, - "measure": 133, - "reset": 126, - "barrier": 89, - "cz": 21, - "x": 7 - }, - "9": { - "cx": 787, - "if_else": 350, - "h": 313, - "measure": 158, - "reset": 150, - "barrier": 105, - "cz": 28, - "x": 7 - } - }, - "graphstate": { - "3": { - "cx": 282, - "if_else": 126, - "h": 93, - "measure": 57, - "reset": 54, - "barrier": 38, - "cz": 21 - }, - "4": { - "cx": 376, - "if_else": 168, - "h": 124, - "measure": 76, - "reset": 72, - "barrier": 50, - "cz": 28 - }, - "5": { - "cx": 470, - "if_else": 210, - "h": 155, - "measure": 95, - "reset": 90, - "barrier": 62, - "cz": 35 - }, - "6": { - "cx": 564, - "if_else": 252, - "h": 186, - "measure": 114, - "reset": 108, - "barrier": 74, - "cz": 42 - }, - "7": { - "cx": 658, - "if_else": 294, - "h": 217, - "measure": 133, - "reset": 126, - "barrier": 86, - "cz": 49 - }, - "8": { - "cx": 752, - "if_else": 336, - "h": 248, - "measure": 152, - "reset": 144, - "barrier": 98, - "cz": 56 - }, - "9": { - "cx": 846, - "if_else": 378, - "h": 279, - "measure": 171, - "reset": 162, - "barrier": 110, - "cz": 63 - } - }, - "qft": { - "3": { - "cx": 772, - "if_else": 300, - "h": 243, - "measure": 135, - "reset": 126, - "barrier": 85, - "t": 42, - "sdg": 14, - "z": 14 - }, - "4": { - "cx": 1135, - "if_else": 443, - "h": 355, - "measure": 199, - "reset": 186, - "barrier": 124, - "t": 63, - "sdg": 21, - "z": 21 - }, - "5": { - "cx": 1498, - "if_else": 586, - "h": 467, - "measure": 263, - "reset": 246, - "barrier": 163, - "t": 84, - "sdg": 28, - "z": 28 - }, - "6": { - "cx": 1861, - "if_else": 729, - "h": 579, - "measure": 327, - "reset": 306, - "barrier": 202, - "t": 105, - "sdg": 35, - "z": 35 - }, - "7": { - "cx": 2224, - "if_else": 872, - "h": 691, - "measure": 391, - "reset": 366, - "barrier": 241, - "t": 126, - "sdg": 42, - "z": 42 - }, - "8": { - "cx": 2587, - "if_else": 1015, - "h": 803, - "measure": 455, - "reset": 426, - "barrier": 280, - "t": 147, - "sdg": 49, - "z": 49 - }, - "9": { - "cx": 2950, - "if_else": 1158, - "h": 915, - "measure": 519, - "reset": 486, - "barrier": 319, - "t": 168, - "sdg": 56, - "z": 56 - } - } + "bv": { + "3": { + "cx": 265, + "if_else": 108, + "h": 105, + "measure": 74, + "reset": 72, + "barrier": 48, + "swap": 18, + "z": 3 + }, + "4": { + "cx": 329, + "h": 137, + "if_else": 132, + "measure": 91, + "reset": 88, + "barrier": 58, + "swap": 24, + "z": 3 + }, + "5": { + "cx": 498, + "if_else": 204, + "h": 203, + "measure": 140, + "reset": 136, + "barrier": 90, + "swap": 36, + "z": 3 + }, + "6": { + "cx": 562, + "h": 235, + "if_else": 228, + "measure": 157, + "reset": 152, + "barrier": 100, + "swap": 42, + "z": 3 + }, + "7": { + "cx": 731, + "h": 301, + "if_else": 300, + "measure": 206, + "reset": 200, + "barrier": 132, + "swap": 54, + "z": 3 + }, + "8": { + "cx": 795, + "h": 333, + "if_else": 324, + "measure": 223, + "reset": 216, + "barrier": 142, + "swap": 60, + "z": 3 + }, + "9": { + "cx": 964, + "h": 399, + "if_else": 396, + "measure": 272, + "reset": 264, + "barrier": 174, + "swap": 72, + "z": 3 + } + }, + "graphstate": { + "3": { + "cx": 435, + "if_else": 180, + "h": 159, + "measure": 123, + "reset": 120, + "barrier": 83, + "swap": 27 + }, + "4": { + "cx": 580, + "if_else": 240, + "h": 212, + "measure": 164, + "reset": 160, + "barrier": 110, + "swap": 36 + }, + "5": { + "cx": 725, + "if_else": 300, + "h": 265, + "measure": 205, + "reset": 200, + "barrier": 137, + "swap": 45 + }, + "6": { + "cx": 870, + "if_else": 360, + "h": 318, + "measure": 246, + "reset": 240, + "barrier": 164, + "swap": 54 + }, + "7": { + "cx": 1015, + "if_else": 420, + "h": 371, + "measure": 287, + "reset": 280, + "barrier": 191, + "swap": 63 + }, + "8": { + "cx": 1160, + "if_else": 480, + "h": 424, + "measure": 328, + "reset": 320, + "barrier": 218, + "swap": 72 + }, + "9": { + "cx": 1305, + "if_else": 540, + "h": 477, + "measure": 369, + "reset": 360, + "barrier": 245, + "swap": 81 + } + }, + "qft": { + "3": { + "cx": 788, + "if_else": 260, + "h": 185, + "measure": 179, + "reset": 168, + "barrier": 107, + "swap": 9, + "p": 8, + "x": 6 + }, + "4": { + "cx": 1162, + "if_else": 384, + "h": 268, + "measure": 264, + "reset": 248, + "barrier": 157, + "swap": 12, + "p": 12, + "x": 9 + }, + "5": { + "cx": 1536, + "if_else": 508, + "h": 351, + "measure": 349, + "reset": 328, + "barrier": 207, + "p": 16, + "swap": 15, + "x": 12 + }, + "6": { + "cx": 1910, + "if_else": 632, + "h": 434, + "measure": 434, + "reset": 408, + "barrier": 257, + "p": 20, + "swap": 18, + "x": 15 + }, + "7": { + "cx": 2284, + "if_else": 756, + "measure": 519, + "h": 517, + "reset": 488, + "barrier": 307, + "p": 24, + "swap": 21, + "x": 18 + }, + "8": { + "cx": 2658, + "if_else": 880, + "measure": 604, + "h": 600, + "reset": 568, + "barrier": 357, + "p": 28, + "swap": 24, + "x": 21 + }, + "9": { + "cx": 3032, + "if_else": 1004, + "measure": 689, + "h": 683, + "reset": 648, + "barrier": 407, + "p": 32, + "swap": 27, + "x": 24 + } + } + }, + "steane": { + "ghz": { + "3": { + "cx": 200, + "if_else": 70, + "h": 55, + "measure": 33, + "reset": 30, + "barrier": 23 + }, + "4": { + "cx": 277, + "if_else": 98, + "h": 73, + "measure": 46, + "reset": 42, + "barrier": 31 + }, + "5": { + "cx": 354, + "if_else": 126, + "h": 91, + "measure": 59, + "reset": 54, + "barrier": 39 + }, + "6": { + "cx": 431, + "if_else": 154, + "h": 109, + "measure": 72, + "reset": 66, + "barrier": 47 + }, + "7": { + "cx": 508, + "if_else": 182, + "h": 127, + "measure": 85, + "reset": 78, + "barrier": 55 + }, + "8": { + "cx": 585, + "if_else": 210, + "h": 145, + "measure": 98, + "reset": 90, + "barrier": 63 + }, + "9": { + "cx": 662, + "if_else": 238, + "h": 163, + "measure": 111, + "reset": 102, + "barrier": 71 + } + }, + "bv": { + "3": { + "cx": 223, + "if_else": 98, + "h": 85, + "measure": 44, + "reset": 42, + "barrier": 30, + "x": 7, + "cz": 7 + }, + "4": { + "cx": 293, + "if_else": 126, + "h": 117, + "measure": 57, + "reset": 54, + "barrier": 39, + "x": 7, + "cz": 7 + }, + "5": { + "cx": 411, + "if_else": 182, + "h": 161, + "measure": 82, + "reset": 78, + "barrier": 55, + "cz": 14, + "x": 7 + }, + "6": { + "cx": 481, + "if_else": 210, + "h": 193, + "measure": 95, + "reset": 90, + "barrier": 64, + "cz": 14, + "x": 7 + }, + "7": { + "cx": 599, + "if_else": 266, + "h": 237, + "measure": 120, + "reset": 114, + "barrier": 80, + "cz": 21, + "x": 7 + }, + "8": { + "cx": 669, + "if_else": 294, + "h": 269, + "measure": 133, + "reset": 126, + "barrier": 89, + "cz": 21, + "x": 7 + }, + "9": { + "cx": 787, + "if_else": 350, + "h": 313, + "measure": 158, + "reset": 150, + "barrier": 105, + "cz": 28, + "x": 7 + } + }, + "graphstate": { + "3": { + "cx": 282, + "if_else": 126, + "h": 93, + "measure": 57, + "reset": 54, + "barrier": 38, + "cz": 21 + }, + "4": { + "cx": 376, + "if_else": 168, + "h": 124, + "measure": 76, + "reset": 72, + "barrier": 50, + "cz": 28 + }, + "5": { + "cx": 470, + "if_else": 210, + "h": 155, + "measure": 95, + "reset": 90, + "barrier": 62, + "cz": 35 + }, + "6": { + "cx": 564, + "if_else": 252, + "h": 186, + "measure": 114, + "reset": 108, + "barrier": 74, + "cz": 42 + }, + "7": { + "cx": 658, + "if_else": 294, + "h": 217, + "measure": 133, + "reset": 126, + "barrier": 86, + "cz": 49 + }, + "8": { + "cx": 752, + "if_else": 336, + "h": 248, + "measure": 152, + "reset": 144, + "barrier": 98, + "cz": 56 + }, + "9": { + "cx": 846, + "if_else": 378, + "h": 279, + "measure": 171, + "reset": 162, + "barrier": 110, + "cz": 63 + } + }, + "qft": { + "3": { + "cx": 772, + "if_else": 300, + "h": 243, + "measure": 135, + "reset": 126, + "barrier": 85, + "t": 42, + "sdg": 14, + "z": 14 + }, + "4": { + "cx": 1135, + "if_else": 443, + "h": 355, + "measure": 199, + "reset": 186, + "barrier": 124, + "t": 63, + "sdg": 21, + "z": 21 + }, + "5": { + "cx": 1498, + "if_else": 586, + "h": 467, + "measure": 263, + "reset": 246, + "barrier": 163, + "t": 84, + "sdg": 28, + "z": 28 + }, + "6": { + "cx": 1861, + "if_else": 729, + "h": 579, + "measure": 327, + "reset": 306, + "barrier": 202, + "t": 105, + "sdg": 35, + "z": 35 + }, + "7": { + "cx": 2224, + "if_else": 872, + "h": 691, + "measure": 391, + "reset": 366, + "barrier": 241, + "t": 126, + "sdg": 42, + "z": 42 + }, + "8": { + "cx": 2587, + "if_else": 1015, + "h": 803, + "measure": 455, + "reset": 426, + "barrier": 280, + "t": 147, + "sdg": 49, + "z": 49 + }, + "9": { + "cx": 2950, + "if_else": 1158, + "h": 915, + "measure": 519, + "reset": 486, + "barrier": 319, + "t": 168, + "sdg": 56, + "z": 56 + } } -} \ No newline at end of file + } +} diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index 34a2bc0f2..32e05b380 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -19,14 +19,13 @@ # Have the ability to save the created circuits (utility function) ## save to file vs print vs logging? - from __future__ import annotations from pathlib import Path from typing import TYPE_CHECKING import pytest -from qiskit import QuantumCircuit, transpile +from qiskit import QuantumCircuit from qiskit.circuit import CircuitInstruction, ClassicalRegister from qiskit.circuit.library import CXGate, CZGate, HGate, SGate, XGate, ZGate from qiskit.quantum_info import hellinger_fidelity @@ -128,11 +127,13 @@ def add_h_before_measurements(qc: QuantumCircuit) -> QuantumCircuit: @pytest.mark.parametrize("code", ["shor", "steane"]) -@pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate"]) #"qft" is unfeasible +@pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate"]) # "qft" is unfeasible @pytest.mark.parametrize("Error", [XGate(), ZGate()]) @pytest.mark.parametrize("MeasureBaseX", [True, False]) -@pytest.mark.parametrize("CIRCUIT_SIZE", [3])#range(3, 11)) -def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error, MeasureBaseX: bool, CIRCUIT_SIZE:int) -> None: +@pytest.mark.parametrize("CIRCUIT_SIZE", [3]) # range(3, 11)) +def test_errorcorrection_transpiler_correctness( + code: str, algorithm: str, Error, MeasureBaseX: bool, CIRCUIT_SIZE: int +) -> None: """Ensures the transpiler creates error-corrected circuits which produce the same result as the orinigal logical circuit. Afterwards an error is introduced and the test checks, whether it is corrected. Iterates over a number of example algorithms. @@ -146,7 +147,7 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error if MeasureBaseX: logical_circuit = add_h_before_measurements(logical_circuit) - + # Strip measure gates to avoid intermediate measurements collapsing the state before decoding stripped_logical_circuit = QuantumCircuit(*logical_circuit.qregs, *logical_circuit.cregs) for inst in logical_circuit.data: @@ -188,22 +189,19 @@ def test_errorcorrection_transpiler_correctness(code: str, algorithm: str, Error f"Error induced circuit created does not match correct the error well enough for {test_id}" ) -@pytest.mark.parametrize("logical_qubits", range(3, 10)) # multiple parametrize lead to crossproducts + +@pytest.mark.parametrize("logical_qubits", range(3, 10)) # multiple parametrize lead to crossproducts @pytest.mark.parametrize("alg", ["ghz", "bv", "graphstate", "qft"]) -@pytest.mark.parametrize("code", ["shor", "steane"]) -def test_error_correction_circuit_structure(code: str, alg: str, logical_qubits: int): - test_id = f'{logical_qubits} qubit {alg} on {code}' +@pytest.mark.parametrize("code", ["shor", "steane"]) +def test_error_correction_circuit_structure(code: str, alg: str, logical_qubits: int) -> None: + test_id = f"{logical_qubits} qubit {alg} on {code}" qc = benchmark_generation.get_benchmark( - benchmark=alg, - level=benchmark_generation.BenchmarkLevel.ALG, - circuit_size=logical_qubits, - encoding=code) + benchmark=alg, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=logical_qubits, encoding=code + ) log_qc = benchmark_generation.get_benchmark( - benchmark=alg, - level=benchmark_generation.BenchmarkLevel.ALG, - circuit_size=logical_qubits, - encoding='') + benchmark=alg, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=logical_qubits, encoding="" + ) qubit_code_factor = -1 classical_code_factor = -1 @@ -224,13 +222,13 @@ def test_error_correction_circuit_structure(code: str, alg: str, logical_qubits: # Check classical register sizes: 3n (bit-flip) + 3n (phase-flip) + 1 for each original clbit expected_creg_sizes = sorted([3] * logical_qubits + [3] * logical_qubits + [1] * log_qc.num_clbits) elif code == "shor": - # Each logical qubit is split in 9 physical qubits + # Each logical qubit is split in 9 physical qubits # Additionally, 8 ancilla qubits are added as stabilisers (6Z + 2X) # => 1 logical qubit = 17 physical qubits qubit_code_factor = 17 # Each ancilla requires 1 clbit for syndrome extraction => 6*2 = 8 classical_code_factor = 8 - + # Check quantum register sizes: 9n (data) + 6n (bit-flip syndrome) + 2n (phase-flip syndrome) expected_qreg_sizes = sorted([9] * logical_qubits + [6] * logical_qubits + [2] * logical_qubits) @@ -238,7 +236,7 @@ def test_error_correction_circuit_structure(code: str, alg: str, logical_qubits: expected_creg_sizes = sorted([6] * logical_qubits + [2] * logical_qubits + [1] * log_qc.num_clbits) # QFT creates qubits scaling with the number of t-gates -> non-trivial scaling not covered by these simple tests - if alg != 'qft': + if alg != "qft": expected_qubits = qubit_code_factor * log_qc.num_qubits found_qubits = qc.num_qubits assert found_qubits == expected_qubits, f"Expected {expected_qubits} qubits, found {found_qubits} for {test_id}" @@ -277,6 +275,7 @@ def test_error_correction_circuit_structure(code: str, alg: str, logical_qubits: f"Created circuit does not contain the expected gates for {test_id}" ) + def insert_error_after_barrier( qc: QuantumCircuit, barrier_label: str, @@ -298,8 +297,7 @@ def insert_error_after_barrier( def insert_error(qc: QuantumCircuit, gate: Gate = XGate(), index: int | None = None) -> QuantumCircuit: - """ - Adds the specified gate at the beginning of the circuit + """Adds the specified gate at the beginning of the circuit Flips the first qubit right after the first barrier by default. """ assert qc.num_qubits >= gate.num_qubits, f"Quantum Circuit has not enough qubits to accommodate gate {gate.name}" @@ -332,9 +330,9 @@ def check_equivalence(qc1: qk.QuantumCircuit, qc2: qk.QuantumCircuit) -> bool: accepted_equivalencies = [EC.equivalent, EC.equivalent_up_to_global_phase, EC.probably_equivalent] return verification_results.equivalence in accepted_equivalencies -def measure_all_named(qc: QuantumCircuit, name: str = 'measurement') -> QuantumCircuit: - """ - Adds a classical register named 'measurement' to the circuit with one bit + +def measure_all_named(qc: QuantumCircuit, name: str = "measurement") -> QuantumCircuit: + """Adds a classical register named 'measurement' to the circuit with one bit per qubit, then maps each qubit i to classical bit i of that register. Args: @@ -361,7 +359,7 @@ def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[dict, QuantumCir qc with measure_all() """ sampler = SamplerV2() - qc = measure_all_named(qc, 'measurements') + qc = measure_all_named(qc, "measurements") job = sampler.run([qc], shots=shots) result = job.result() @@ -379,8 +377,7 @@ def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[dict, QuantumCir def compare_distributions( qc1: QuantumCircuit, qc2: QuantumCircuit, counts1: dict, counts2: dict, code1: str = "None", code2: str = "None" ) -> float: - """ - Simulates 2 circuits and computes the Hellinger Fidelity between their count distributions + """Simulates 2 circuits and computes the Hellinger Fidelity between their count distributions 1 = the same, 0 = no overlap. If code is set to either 'steane' or 'shor' circuit error's result will be interpreted logically @@ -428,22 +425,3 @@ def condense_counts(qc: qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, logical_counts[logical_measurement] = logical_counts.get(logical_measurement, 0) + count return logical_counts - - -def log_circuits(circuits: dict[str, QuantumCircuit]) -> None: - from pathlib import Path - import matplotlib.pyplot as plt - - log_dir = Path(__file__).parent / "circuit_drawings" - log_dir.mkdir(exist_ok=True) - - for name, circuit in circuits.items(): - name = log_dir / f"{name}_transpiled" - with Path(f"{name}.txt").open("w", encoding="utf-8") as f: - f.write(f"number of qubits {circuit.num_qubits}\n") - f.write(f"--- Transpiled Circuit for {name._str.upper()} ---\n\n") - f.write(str(circuit.draw(fold=-1)) + "\n") - - #fig = circuit.draw(output="mpl", fold=-1) - #fig.savefig(f"{name}.png", dpi=150, bbox_inches="tight") - #plt.close(fig) From 22f71bf3e06cce26e63a66848fe6c1e189aa1804 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 13 Jun 2026 15:59:39 +0200 Subject: [PATCH 57/72] added dependencies for testing --- pyproject.toml | 2 + uv.lock | 126 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 1fcbffabd..40c07c982 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,6 +31,8 @@ dependencies = [ "numpy>=2.3.2; python_version >= '3.14'", "scikit-learn>=1.5.2", "scikit-learn>=1.7.2; python_version >= '3.14'", + "mqt-qcec>=3.6.1", + "qiskit-aer>=0.17.2", ] classifiers = [ diff --git a/uv.lock b/uv.lock index a46df69a3..a26946f8e 100644 --- a/uv.lock +++ b/uv.lock @@ -1594,11 +1594,13 @@ wheels = [ name = "mqt-bench" source = { editable = "." } dependencies = [ + { name = "mqt-qcec" }, { name = "networkx", version = "3.4.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "networkx", version = "3.6.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "qiskit", extra = ["qasm3-import"] }, + { name = "qiskit-aer" }, { name = "scikit-learn", version = "1.7.2", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, { name = "scikit-learn", version = "1.8.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, ] @@ -1640,6 +1642,7 @@ test = [ [package.metadata] requires-dist = [ + { name = "mqt-qcec", specifier = ">=3.6.1" }, { name = "networkx", specifier = ">=2.8.8" }, { name = "numpy", specifier = ">=1.22" }, { name = "numpy", marker = "python_full_version >= '3.11'", specifier = ">=1.24" }, @@ -1647,6 +1650,7 @@ requires-dist = [ { name = "numpy", marker = "python_full_version >= '3.13'", specifier = ">=2.1" }, { name = "numpy", marker = "python_full_version >= '3.14'", specifier = ">=2.3.2" }, { name = "qiskit", extras = ["qasm3-import"], specifier = ">=2.0.0" }, + { name = "qiskit-aer", specifier = ">=0.17.2" }, { name = "scikit-learn", specifier = ">=1.5.2" }, { name = "scikit-learn", marker = "python_full_version >= '3.14'", specifier = ">=1.7.2" }, ] @@ -1684,6 +1688,76 @@ test = [ { name = "pytest-xdist", specifier = ">=3.8.0" }, ] +[[package]] +name = "mqt-core" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/63/95/e1429f163a477845785a71d6dea92dfa1675d5228c1a01135f38340436ed/mqt_core-3.6.1.tar.gz", hash = "sha256:ce26f34bc0a363a795c8f95a2aa19341104b2961f3a2a4e2a19e2229e9e7dcd5", size = 655317, upload-time = "2026-05-20T00:34:36.107Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/18/e0/3e362a11e4103dd4c456f381daad92c5f18a6c467426999f58c480ba2cd6/mqt_core-3.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:79de9549084ca6b107153b9fbab5987354a50c5ce692930b40679a4416f74c6c", size = 5180469, upload-time = "2026-05-20T00:33:52.936Z" }, + { url = "https://files.pythonhosted.org/packages/90/ea/2d5529b1d6983f5f20ba5e095a4a2476348c5faee92cbba847a69a8a48e9/mqt_core-3.6.1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:a5e1c7f5f2522471700e908c8e2dc1963c1458e952a6814f021e0ed13724035d", size = 5671256, upload-time = "2026-05-20T00:33:55.019Z" }, + { url = "https://files.pythonhosted.org/packages/71/ac/1cb831f10d1be99114c8df214ff96bab3ffdc9913f4494984ce3398e5f5a/mqt_core-3.6.1-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:87d93dd7a1f2a45083facc5e376b40e5c4306703eb5af56f9381678244226061", size = 6693517, upload-time = "2026-05-20T00:33:57.005Z" }, + { url = "https://files.pythonhosted.org/packages/b1/66/971c7c7e5f570899fbaed56b43523698faa0b46c575e9be114fd6a7c7eca/mqt_core-3.6.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:181b62397c04f30f4b482bfb588b1e08ea21d8ca6a78a94d45c8170d3dd0ade4", size = 7128865, upload-time = "2026-05-20T00:33:58.732Z" }, + { url = "https://files.pythonhosted.org/packages/dd/75/66776318b8399d3ee0e995cc5415b17e3b86d0debcc8f2b94054a35fd550/mqt_core-3.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:1275e6be9f4198ffa16df06443df081ffa0d9016edd5bc22f2e5fb6866d692d1", size = 4046804, upload-time = "2026-05-20T00:34:00.767Z" }, + { url = "https://files.pythonhosted.org/packages/b8/67/fdc2331352e35d938b276416008be7ffc0178eb66276b1445bc3a3767897/mqt_core-3.6.1-cp310-cp310-win_arm64.whl", hash = "sha256:74d03c9782cd2be1a7dc9f35a06815faf924caf9cf6572da815bfb59425f2c56", size = 7936592, upload-time = "2026-05-20T00:34:02.329Z" }, + { url = "https://files.pythonhosted.org/packages/48/b0/a5a7450a9ffaff0ae8de49f65cd24c63901e3fee3dc740584c09b046165c/mqt_core-3.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:27b80e793f27a963417cc06da908a31f51fae07722a20cdb5b32edd8e0276be1", size = 5181464, upload-time = "2026-05-20T00:34:04.102Z" }, + { url = "https://files.pythonhosted.org/packages/75/3d/9409c8f111c119722a8909bbfc92a7a3d8e4377e058ccfe62a46bf61ac09/mqt_core-3.6.1-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:2eae0ed0d4233749a8831bbeb32948da4031566acb742212b3681ed208289159", size = 5672582, upload-time = "2026-05-20T00:34:05.8Z" }, + { url = "https://files.pythonhosted.org/packages/fa/dc/ba457c6db680a6e9aa84895bb25da826f03fb6454cfbd496a6c0ece3a9da/mqt_core-3.6.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:1bb184b52c318b3b9d5db97fa006120cbe683a8fa9cd80790bf24b67489cd204", size = 6694132, upload-time = "2026-05-20T00:34:07.64Z" }, + { url = "https://files.pythonhosted.org/packages/20/f9/3cc61de4fab97611c89d5977156ead60eb48c2721b8d4126d0ff1704e73c/mqt_core-3.6.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:7b5c1b9750fa3e8447fd1bfd92b599688d3dc8574d3e70d1264c9a40b1b7be94", size = 7129376, upload-time = "2026-05-20T00:34:09.454Z" }, + { url = "https://files.pythonhosted.org/packages/59/b5/80713df591091312e3801a5ecb84bf93c2996dfb0dbd9a912973ed3adc9c/mqt_core-3.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:9d607b34f571500dbf0fb1ecb5f5ef95139dc32d57461a1cd0614387af778e75", size = 4047971, upload-time = "2026-05-20T00:34:11.097Z" }, + { url = "https://files.pythonhosted.org/packages/58/7e/e9ace3e69c3613f5e1b73e5497cc94c451c259ab66fc3fda077f908ffc32/mqt_core-3.6.1-cp311-cp311-win_arm64.whl", hash = "sha256:8520f7cfa3b5e7465555fe06c3c1c17c194cfca92a06ac3f1bdf3c7660c1ef88", size = 7937030, upload-time = "2026-05-20T00:34:12.761Z" }, + { url = "https://files.pythonhosted.org/packages/77/c1/423abe9632f79a8174d87b0b3f60d08d8e033e5c947c1a35e8cf7f4b4b33/mqt_core-3.6.1-cp312-abi3-macosx_11_0_arm64.whl", hash = "sha256:8136975d84f3ad721a01e4461dbd15e8ae00630e8cf8ca000e247c68d44b32cc", size = 5177324, upload-time = "2026-05-20T00:34:14.518Z" }, + { url = "https://files.pythonhosted.org/packages/8f/45/c2af228132100238121b256c84172acaaba3c4f68b45b0fcfab2a7684910/mqt_core-3.6.1-cp312-abi3-macosx_11_0_x86_64.whl", hash = "sha256:ee0433a5f4a22e6542e86558e29665219415aad730f699c555743d8fbd38a110", size = 5668253, upload-time = "2026-05-20T00:34:16.491Z" }, + { url = "https://files.pythonhosted.org/packages/bb/c1/1e4596f76d52e56b96849575795be5132320c7fa39df1dd83e1eff9f7d4a/mqt_core-3.6.1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:a7ff7726627bfe4e779fa74397b89d9e686543a99ea2132d588aee7da6ecc89d", size = 6679218, upload-time = "2026-05-20T00:34:18.481Z" }, + { url = "https://files.pythonhosted.org/packages/a2/df/de19e1e6f9f2b0cfdca08af28ff033a883a1995185724de8812728d45f3f/mqt_core-3.6.1-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:ba1cf804ce909a7f9301759cfbc83b0d7dd6234752504f24185a39f616078bfb", size = 7112143, upload-time = "2026-05-20T00:34:19.967Z" }, + { url = "https://files.pythonhosted.org/packages/5a/e4/8d18330770afe008a5f26598e5b8025404d3adab04d8fe3e590576fd31aa/mqt_core-3.6.1-cp312-abi3-win_amd64.whl", hash = "sha256:ef594511df7596c23c61d96adf8d3f805ee81aa1afb52fc589d19b2b036d88fc", size = 4040427, upload-time = "2026-05-20T00:34:22.237Z" }, + { url = "https://files.pythonhosted.org/packages/c6/1c/5f054d1c0a1f3be73135bcb324ff5c1ef1312fa2d94c78a88b7e264dec0d/mqt_core-3.6.1-cp312-abi3-win_arm64.whl", hash = "sha256:9ed5a594426937520bc2b3c8f92a5a52cb37df8a9d55661e1a6d8ec37d169b39", size = 7929919, upload-time = "2026-05-20T00:34:23.922Z" }, + { url = "https://files.pythonhosted.org/packages/3f/12/f50cadbb52bda38a4d727a90d5542c64078da8b5f0f18f36938e41877bd1/mqt_core-3.6.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:c103a008405f4b554ee7bf092cd26a7d46cf7425c5ca8fa9b0a2e0004c4d8fa9", size = 5190293, upload-time = "2026-05-20T00:34:25.703Z" }, + { url = "https://files.pythonhosted.org/packages/a7/0f/53b37b867d077124e30907bc520a80ceab0ca045f7687ea3aac301eebbab/mqt_core-3.6.1-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:0ef1c62495ed4b110f3bce89dcf0a21a1353f64010cccc1ec71a50ce0cdea26e", size = 5682141, upload-time = "2026-05-20T00:34:27.394Z" }, + { url = "https://files.pythonhosted.org/packages/eb/ce/3bb8f5f64897fc1d70d637097098c1ada7abf579a93f8d66e392a3eb5e18/mqt_core-3.6.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f7459aae9b2d80be7b32e3317d3fb86f5f5977b27c149de1e6d68c67d4c80b71", size = 6699266, upload-time = "2026-05-20T00:34:29.069Z" }, + { url = "https://files.pythonhosted.org/packages/7a/9f/87088625f8d5426caf4a66bcc288e1833999c5dd3e9718604117821b25ec/mqt_core-3.6.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6e4183e8ec739087e5f94319bfb054318b184d6fdaab2bca5207745ff89dae09", size = 7129965, upload-time = "2026-05-20T00:34:31.213Z" }, + { url = "https://files.pythonhosted.org/packages/6c/b7/4b9b70f914add8961fb736008618ea28f93b5acdba19878fde7d9fd4f30a/mqt_core-3.6.1-cp314-cp314t-win_amd64.whl", hash = "sha256:c5c7a2bdc0ff010672501cfc5735a547d9b15cab4f1604a78c687b69924ab25f", size = 4133733, upload-time = "2026-05-20T00:34:32.79Z" }, + { url = "https://files.pythonhosted.org/packages/0a/5d/4e1e3e2ea7cab9d093a865c0e2b3de0729257e4eaff8abcd654503e43aa7/mqt_core-3.6.1-cp314-cp314t-win_arm64.whl", hash = "sha256:31df5b87f5afc09bbe3f815df434f0306a56b952b4f5dc2599e4d53625031b09", size = 8008654, upload-time = "2026-05-20T00:34:34.446Z" }, +] + +[[package]] +name = "mqt-qcec" +version = "3.6.1" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "mqt-core" }, + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/d5/16/1e900e1c89a034ee8d6196de3d2b5f30711ffe159c5a4de0fdc6189374b3/mqt_qcec-3.6.1.tar.gz", hash = "sha256:0d053387fa2d660fcf8ec9ab6e5da6069634d73a3e95a7a9e5b5fed7479af3e3", size = 293070, upload-time = "2026-05-20T22:19:36.403Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/bc/e8/c3718edaf04407b42d4476cadeb08d55c45b8693cc89da74fb4050157c09/mqt_qcec-3.6.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c1623a60eb35f558db3e6bc38345d8ce6fcd165e48817f6227dc53cf064f19b1", size = 224147, upload-time = "2026-05-20T22:18:55.334Z" }, + { url = "https://files.pythonhosted.org/packages/d1/55/6e50ca989af1484118e88aa0b80208ad83d4f8b6bec9c19bbb344dce5e62/mqt_qcec-3.6.1-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:93d51dd278966aa222a4c2d867d8f61b7a40638160398cc2d09e97bdf1dab94a", size = 237244, upload-time = "2026-05-20T22:18:57.565Z" }, + { url = "https://files.pythonhosted.org/packages/f2/a9/4c61d6af5acb56ef0e1b69d650bb774e2383b7355b0071f1f1141cb31214/mqt_qcec-3.6.1-cp310-cp310-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9ca76885b3afc20460a38a3d7a4c08c4e51dd35d7fc38f12870595f312a318fb", size = 238734, upload-time = "2026-05-20T22:18:59.599Z" }, + { url = "https://files.pythonhosted.org/packages/03/36/d91036cb97f10b2276cf0a192a0a619e2ac82fa6f54cfcf354c85509677d/mqt_qcec-3.6.1-cp310-cp310-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:79018893072853030faaa3e066981c6e7b20fc2ca7cd6c5820ef997c65350ffb", size = 249180, upload-time = "2026-05-20T22:19:01.135Z" }, + { url = "https://files.pythonhosted.org/packages/d4/85/8775d220d06e02ea701d2f6994dc17be173013ad4d9eacd7435cafae1f8a/mqt_qcec-3.6.1-cp310-cp310-win_amd64.whl", hash = "sha256:2e1db239d0bec8198d1317c8b1cf08ed20c887eb63bae85590035655c8e6b623", size = 448206, upload-time = "2026-05-20T22:19:02.659Z" }, + { url = "https://files.pythonhosted.org/packages/52/c5/82510c8721b63891e72e7ad75b70f7918e42f2a7b8a2ac5ab960939a4cc3/mqt_qcec-3.6.1-cp310-cp310-win_arm64.whl", hash = "sha256:c093175c97a67c5731bbadf7cd7063abda7e1bd50ea3e80f821a9ac0b2e65095", size = 614084, upload-time = "2026-05-20T22:19:04.309Z" }, + { url = "https://files.pythonhosted.org/packages/de/0b/0619ba807fd52ab1edefb22f352d6b70e147d21053cf8fc5d3770c125e95/mqt_qcec-3.6.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:12a0640b2480f72292e75a57783403edf811650405c155a860083c601f381961", size = 224721, upload-time = "2026-05-20T22:19:05.916Z" }, + { url = "https://files.pythonhosted.org/packages/e5/e8/14d46d1ddba1aaa26136ef639028c3905488be5b83134f67241689d5cf07/mqt_qcec-3.6.1-cp311-cp311-macosx_11_0_x86_64.whl", hash = "sha256:88e36543d8ea65886360960814416c27b6f8474e255d9f4cb2254c553546dd7c", size = 237716, upload-time = "2026-05-20T22:19:07.492Z" }, + { url = "https://files.pythonhosted.org/packages/c5/56/ff4896dd651786d67fd5b6b8a3ee3ddaf384b1b9b0c60b75284c119aee88/mqt_qcec-3.6.1-cp311-cp311-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:9dc2b0fa06e5484328f1f0bba6097cb683ddc3f99b6a545aea775585a027d66a", size = 239552, upload-time = "2026-05-20T22:19:09.017Z" }, + { url = "https://files.pythonhosted.org/packages/87/5e/2d58b085e09b6d8afe4455546c5d2f0e2780f921999a5d55240edb0cf3b0/mqt_qcec-3.6.1-cp311-cp311-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d9a3bc83ef743f3365daf807f3f4618d47aa856ebdd3ac4fb7687efc6f976641", size = 250637, upload-time = "2026-05-20T22:19:10.87Z" }, + { url = "https://files.pythonhosted.org/packages/a5/7f/3268ce58888314697d6f5c6e9aa29ca238c7649bdaf8844a4cd7cca313e5/mqt_qcec-3.6.1-cp311-cp311-win_amd64.whl", hash = "sha256:b52795a6afbda923823da3485ee584a56c7fb4a4fa7c6b977091844021f6369e", size = 448256, upload-time = "2026-05-20T22:19:12.286Z" }, + { url = "https://files.pythonhosted.org/packages/9a/7a/65ac8b6a801eaa1c5c6112cc3b6158b2d8b1fc8d23e67f3a1ead3dedb7b2/mqt_qcec-3.6.1-cp311-cp311-win_arm64.whl", hash = "sha256:336dcdd229827b07cb0544fb6bcc1bb2ac38817579d2d8a231bd2f9262261bf6", size = 614236, upload-time = "2026-05-20T22:19:13.941Z" }, + { url = "https://files.pythonhosted.org/packages/01/4c/f058c0f6acf45342c9713095aa23275cc591bad35ba5c21acec3203548a7/mqt_qcec-3.6.1-cp312-abi3-macosx_11_0_arm64.whl", hash = "sha256:8300e38baaeaade42e278cc89d472f0442511b6380ff9e0366ae68993ea0e0b0", size = 222543, upload-time = "2026-05-20T22:19:15.753Z" }, + { url = "https://files.pythonhosted.org/packages/56/b8/705803cf605a4f30987031da56d3136df1be995edff4558251776b282169/mqt_qcec-3.6.1-cp312-abi3-macosx_11_0_x86_64.whl", hash = "sha256:1a649008da91b42b7a7231512ddfe78b851454bab4e6d062c29049ade5d16ec1", size = 235672, upload-time = "2026-05-20T22:19:18.024Z" }, + { url = "https://files.pythonhosted.org/packages/30/3c/c55bfc33eea06e713077cc3162e2e2f2a3a836e8ab1f13798a2864759795/mqt_qcec-3.6.1-cp312-abi3-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:03e37a3ee86ace7d8541fee0b647feb004465e2c56624a3337cdc7a77c0c6c75", size = 235953, upload-time = "2026-05-20T22:19:19.995Z" }, + { url = "https://files.pythonhosted.org/packages/ab/14/a2562098c399c1cafe48e03f8523a08c149ed8104d382d4e24cfba614d99/mqt_qcec-3.6.1-cp312-abi3-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:529835521e8e4e217cdd61215b8a7c47a349baa86b1f32408c19cb4fb0399a54", size = 246929, upload-time = "2026-05-20T22:19:21.583Z" }, + { url = "https://files.pythonhosted.org/packages/e3/f2/a17519cfab01176c03a96079c2a712ef2a2c99833abc6d97c7b9aea46969/mqt_qcec-3.6.1-cp312-abi3-win_amd64.whl", hash = "sha256:5ee3a48fbc3b145a92d9209bebaf92d01741f481789fdd5935cc840fa9029d40", size = 445481, upload-time = "2026-05-20T22:19:23.092Z" }, + { url = "https://files.pythonhosted.org/packages/5a/c8/2710ed3781bcb80b7c87451b78e081bdb31570820031eb13396623853a49/mqt_qcec-3.6.1-cp312-abi3-win_arm64.whl", hash = "sha256:c4f1068fd2ae8d926ba9048e99306b3349e06f7509a40454f55dc786c7665055", size = 611543, upload-time = "2026-05-20T22:19:24.894Z" }, + { url = "https://files.pythonhosted.org/packages/d4/9d/a2303a4bab2b1a35c58248185f89e4a5871ec14c088e9e84b7bbc80bca73/mqt_qcec-3.6.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:e888f31dacd78c27005c8e4eca45674b3295087717d47b542e62ce18c12d9a31", size = 226598, upload-time = "2026-05-20T22:19:26.828Z" }, + { url = "https://files.pythonhosted.org/packages/b1/0e/faeb4ec285f47dea7463b8b859ea9e8c7b9ed78481584dfe538e83f86eb4/mqt_qcec-3.6.1-cp314-cp314t-macosx_11_0_x86_64.whl", hash = "sha256:1d8800075dd71c125c22cb3b0b3502b5058e7cd83964e7c68b05ec9eb058421f", size = 240324, upload-time = "2026-05-20T22:19:28.427Z" }, + { url = "https://files.pythonhosted.org/packages/33/9a/3654183129053163f67b97e08ea4547947e4b30593be3f0e3c4c65510596/mqt_qcec-3.6.1-cp314-cp314t-manylinux_2_26_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:7dd36f4b68ef86d42cbe140e48922df018a47d73000b44aae9cc9fe66217632c", size = 239324, upload-time = "2026-05-20T22:19:29.935Z" }, + { url = "https://files.pythonhosted.org/packages/d4/26/ec188dde64eab22f28d6555bf4d14cb4af249f81fc710888f68f618bd720/mqt_qcec-3.6.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:cedf1a5ad487622af7edcc2f7c2af6e3ede791130bd9cd88d333fa9a5a99a0c5", size = 250367, upload-time = "2026-05-20T22:19:31.495Z" }, + { url = "https://files.pythonhosted.org/packages/db/34/02404e95f3b905689849eb797372e31f8714ab5de5a28fd3f2f695dbd20f/mqt_qcec-3.6.1-cp314-cp314t-win_amd64.whl", hash = "sha256:bb63f16760c333c3d04a56970c1bdfe21e99d0da9b9b039b0085813195e6ee7f", size = 465962, upload-time = "2026-05-20T22:19:33.096Z" }, + { url = "https://files.pythonhosted.org/packages/65/dd/3ac789c6d6e835c7c483422db15c65f566e1357ba988f2161685adb9413e/mqt_qcec-3.6.1-cp314-cp314t-win_arm64.whl", hash = "sha256:5a20662bb0e0e0d067c970fc6f89e7c74edb2fc16e054d53c615eff0746f41a6", size = 637086, upload-time = "2026-05-20T22:19:34.778Z" }, +] + [[package]] name = "myst-nb" version = "1.4.0" @@ -2756,6 +2830,58 @@ visualization = [ { name = "sympy" }, ] +[[package]] +name = "qiskit-aer" +version = "0.17.2" +source = { registry = "https://pypi.org/simple" } +dependencies = [ + { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "psutil" }, + { name = "python-dateutil" }, + { name = "qiskit" }, + { name = "scipy", version = "1.15.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, + { name = "scipy", version = "1.17.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, +] +sdist = { url = "https://files.pythonhosted.org/packages/07/6c/6b8b35f67159401580665c59ae64d676bef9e85aac4d2a50831cbe32f652/qiskit_aer-0.17.2.tar.gz", hash = "sha256:134eef8e509311955a15be543d2ba368f988f3583a2bc1f548af3196da820eb4", size = 6551618, upload-time = "2025-09-17T13:55:25.165Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/ea/7b/2d2a5477f48e920b5b12eced57b386ba8a784a27a7495da095fb00e64f1e/qiskit_aer-0.17.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:4cef22b09c6d7f1afa4886fe8ff14f54ccfe830c50f322182f06f5f8a72b48d3", size = 2505354, upload-time = "2025-09-17T13:53:56.779Z" }, + { url = "https://files.pythonhosted.org/packages/dd/d5/68b3b84d41dc52d1c7441ec2fb402d8274dfcc5c76c191d782f6457aa9c6/qiskit_aer-0.17.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:99b92f70d669e3f869bc341ed4b3b1943e8a7c85643f1d289db8b08e0e4397a4", size = 2115952, upload-time = "2025-09-17T13:53:59.107Z" }, + { url = "https://files.pythonhosted.org/packages/e0/69/df28af389bb2cb4c39ce8a36170600ff23ab09b5a7ce1c11a3a102bfc99b/qiskit_aer-0.17.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:45f149e5921f54cbefc220021b6144a197cbc132474a7ea55e36e5b7909ab67a", size = 6454168, upload-time = "2025-09-17T15:22:20.03Z" }, + { url = "https://files.pythonhosted.org/packages/e5/f7/c25bcb06d66e45914da7e80f7a0a15e390e97ce80531c9924b67ae9d1fd2/qiskit_aer-0.17.2-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a84a59750faa83186373bd32af3af43d6303c6469899aa9018031e6fa296754", size = 7974706, upload-time = "2025-09-17T13:54:00.795Z" }, + { url = "https://files.pythonhosted.org/packages/e6/ce/c16875bb584ce1dea37a232a1b4ab0f3b21ddea54ae9b6b5c36397c7d513/qiskit_aer-0.17.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:909213047102b591ab04cf4bb7d4f0a6aa56d38cee331c8a29060d1174be536d", size = 7926320, upload-time = "2025-09-17T14:51:04.845Z" }, + { url = "https://files.pythonhosted.org/packages/62/1d/4aabb58556833100f52d39139d06a494555cfb0dad04e2f515c3a62a9455/qiskit_aer-0.17.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:32b2b056b1132f21af3ec76ec4de19b1b1871aa842a8fa3f01617a88f929365c", size = 12381501, upload-time = "2025-09-17T13:54:02.37Z" }, + { url = "https://files.pythonhosted.org/packages/d5/1e/ae00ed91d4b1aa0776cf85fa1db371856037fd5c0ae6309339e42b54fa7a/qiskit_aer-0.17.2-cp310-cp310-win32.whl", hash = "sha256:ddc7360317d652ed7fbc12a83f3b3adf28258433b55f2c6022c1448473fdb407", size = 6920674, upload-time = "2025-09-17T13:54:04.823Z" }, + { url = "https://files.pythonhosted.org/packages/2f/7c/6db320f9f41adce182ba4359bc4066255b43a08059b3b841dc8807f09b8e/qiskit_aer-0.17.2-cp310-cp310-win_amd64.whl", hash = "sha256:38b2c99d7af65716f6c48616a57ffa79ec827a8fc70257f925f6904c7b1a1a0f", size = 9561666, upload-time = "2025-09-17T13:54:06.461Z" }, + { url = "https://files.pythonhosted.org/packages/71/7f/5e687162d9e0c25898a1d964a759e773f37a6921abbac0eca14c9ec9ae21/qiskit_aer-0.17.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a8723a61aa3925508a977dd92ae135f2aee465cf74bb9ed7c75b5cc98628e4b3", size = 2506674, upload-time = "2025-09-17T13:54:08.286Z" }, + { url = "https://files.pythonhosted.org/packages/6b/54/be4d6ceaa305155fac89892a8d7dd6f01eed5f161bfceb7862b4e4a853cd/qiskit_aer-0.17.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:55bdf5aacaf27576de988dc1aea915fdcb18c83a9732de2278137004fc076470", size = 2116548, upload-time = "2025-09-17T13:54:09.761Z" }, + { url = "https://files.pythonhosted.org/packages/70/63/b79fc699f5e892fcb61c4ac474b7cedcce0688dc57d1738b9c8053c23db1/qiskit_aer-0.17.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ecd74ca2ce45bbc673d5f7d2e3fdc5218dc7d05a9d24feaa84d48cf23ca028d", size = 6458841, upload-time = "2025-09-17T15:22:22.668Z" }, + { url = "https://files.pythonhosted.org/packages/5a/d7/3c2bb19b0f854fbcc6253b94632fd3feec41873c2b08b3f482ced8f54dc7/qiskit_aer-0.17.2-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4e5df2f2aa8cf189639df870303543c3febb3a282113c91bc27a8c7c4624d225", size = 7972597, upload-time = "2025-09-17T13:54:11.452Z" }, + { url = "https://files.pythonhosted.org/packages/75/ea/4c4b20415090f69a97fe8dc5e1e09549f13c88f5b523700855d4d130d65a/qiskit_aer-0.17.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:d1274f5609269fc9762835d978d9e8110ca84af2d0d1dc9b08552a34a6068520", size = 7926625, upload-time = "2025-09-17T14:51:07.607Z" }, + { url = "https://files.pythonhosted.org/packages/9e/88/f5b350f60ecefdc1ef5794ec9524dee685e3d32b8532f2142bb1afa39d32/qiskit_aer-0.17.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0bd91f682dc62c0e62c30651daf45aebd462bdbc2b1a2d028be3cc85b4b09eed", size = 12374395, upload-time = "2025-09-17T13:54:13.362Z" }, + { url = "https://files.pythonhosted.org/packages/a7/96/3f76b86e5165ab935228e1488beb9f801c3db484d915366e9ae36ea8d8ae/qiskit_aer-0.17.2-cp311-cp311-win32.whl", hash = "sha256:7ea01d85d9d6a4cddd205ed118075401323517b69624f59f5607f8a14b72fef9", size = 6921401, upload-time = "2025-09-17T13:54:15.783Z" }, + { url = "https://files.pythonhosted.org/packages/82/8f/ac0a55a6fb539355e9e629f42f03032f9e93acce2390a87a7ab8c56764f7/qiskit_aer-0.17.2-cp311-cp311-win_amd64.whl", hash = "sha256:ba8ee895803d618cf1cc13f948c5806e52c27d673d828e196f57774649b05cdf", size = 9562419, upload-time = "2025-09-17T13:54:17.445Z" }, + { url = "https://files.pythonhosted.org/packages/0a/2c/7039b1891377ef081c92af79cee230be0a01e52c21561469f6807f17fe96/qiskit_aer-0.17.2-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:5f03bf3f45f7f6cc6e480df473e4750178cb66ee7fb44d85d98c13901e81c42c", size = 2508338, upload-time = "2025-09-17T13:54:19.202Z" }, + { url = "https://files.pythonhosted.org/packages/50/02/f1d6906c2cb3ff3ec94f97656abbc56b80b80c4677ff603ee40063cf9c84/qiskit_aer-0.17.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:abb03d621cfd30e608ba8ddbb9e673707e6f790a744130d1d6355e3e10554d13", size = 2117032, upload-time = "2025-09-17T13:54:21.009Z" }, + { url = "https://files.pythonhosted.org/packages/10/b3/86a9687b2123201badcca23c0954fe17e83d648cfb1737558c00783e3ea6/qiskit_aer-0.17.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6e3ee2debad4dd9d1ff021002e82363a83ee22b1440ccbc293a444072c9fd0c1", size = 6454082, upload-time = "2025-09-17T15:22:24.811Z" }, + { url = "https://files.pythonhosted.org/packages/ca/05/45a3d07b0372317f33ce3abaa438d668b57f3ecdd0c62dcb4c2d43e44d17/qiskit_aer-0.17.2-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:782f6ba0bdd08faec19f7bbb65e95fc70b0c2d097b056fc929a95c084b57c203", size = 7974594, upload-time = "2025-09-17T13:54:22.628Z" }, + { url = "https://files.pythonhosted.org/packages/d1/c3/91ea504db5ba2c43f1fc5918ff60098aa730a5db40830a096855325b2b66/qiskit_aer-0.17.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:2e91fc4f0a26540ff0e9d0a30b8be3e0f12b4c9c59ec1afffb753944d47e1888", size = 7926812, upload-time = "2025-09-17T14:51:09.356Z" }, + { url = "https://files.pythonhosted.org/packages/30/cc/c47b356b90dd00b9b19fdcaa8f6776613db0885630f7095f92659f62b5c8/qiskit_aer-0.17.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7a8857aad723036ff818af14bbd4c3375559741366bffce7b36b4eeb306b88cf", size = 12375640, upload-time = "2025-09-17T13:54:24.582Z" }, + { url = "https://files.pythonhosted.org/packages/06/eb/8b796a34622392ee1f66b7d03ba31385ef559cd3a145ec6de2556ebb983e/qiskit_aer-0.17.2-cp312-cp312-win32.whl", hash = "sha256:a9abdb24318c417b69867c6d43aed4684b67320b1e7010f4c57c84fdeff89a13", size = 6922323, upload-time = "2025-09-17T13:54:26.703Z" }, + { url = "https://files.pythonhosted.org/packages/30/f7/5943ba7f6be0a02667593ef5f684359a62d5a46ba9dac9a0367c3ab3d1d8/qiskit_aer-0.17.2-cp312-cp312-win_amd64.whl", hash = "sha256:80c419bb3fb65a5135286ce4e98abd68b9dc836b77affbbc4b06721d53ce1e3c", size = 9563069, upload-time = "2025-09-17T13:54:28.885Z" }, + { url = "https://files.pythonhosted.org/packages/1e/96/0b7f3f7ee5cfc9dde495a2e324e53432abbfccb95a62cafa09e4fc07706d/qiskit_aer-0.17.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:a8ac09544489e34cb60bc0e615bc5ae725de581c9a6bf5cd17b57f2a7baf9f16", size = 2508547, upload-time = "2025-09-17T13:54:32.728Z" }, + { url = "https://files.pythonhosted.org/packages/d7/08/4adfd24bd337d1b1b45a0fd85a2d0f1b9b386dfc9db8135fe5abbad3a0fc/qiskit_aer-0.17.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:4c1bf5072bc54250009751350aaaed06bef15ada7ab43e6ce2c934831f6ee6ea", size = 2117037, upload-time = "2025-09-17T13:54:34.08Z" }, + { url = "https://files.pythonhosted.org/packages/93/3a/6068244629b8f04ce48fa4dddb8d0874f28e91611d90571e69d9417f22ba/qiskit_aer-0.17.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bd9d3c5e6c5d09cb0a821bf5077a14e7f5f8db5c3d700023be92ce6a05923309", size = 6454589, upload-time = "2025-09-17T15:22:26.783Z" }, + { url = "https://files.pythonhosted.org/packages/d1/68/27ddd833d700bc9f9adede6e281146c0229acce895e64dcd32f1b62c90e9/qiskit_aer-0.17.2-cp313-cp313-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:46820fb38bf85f8c6f8aaa350dd90d01b0be10d16f5f1ef4188882b3a0531f38", size = 7977976, upload-time = "2025-09-17T13:54:36.414Z" }, + { url = "https://files.pythonhosted.org/packages/1c/ca/a1cd95daf75d09d2dc1744cde95d5d18fed61a0e4922788fb916a7cd8152/qiskit_aer-0.17.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2749e6027f67e1f6b9d328d2dda2d4bf926aebd3653edc62e94c45d8237294d8", size = 12376191, upload-time = "2025-09-17T13:54:38.092Z" }, + { url = "https://files.pythonhosted.org/packages/d6/69/e2f979e2fca054b0092fc52c46da050298513b9b03531305bc3f340c7669/qiskit_aer-0.17.2-cp313-cp313-win32.whl", hash = "sha256:c3ffd40a64bfcf8a6d10cbfdca8734d49ec57502fd70dc63aae9ed3819249dd6", size = 6922275, upload-time = "2025-09-17T13:54:40.024Z" }, + { url = "https://files.pythonhosted.org/packages/ae/91/195cb69d3af4359544939378879093764bd35d8abd7ac0de840bb5477d27/qiskit_aer-0.17.2-cp313-cp313-win_amd64.whl", hash = "sha256:b38c5dfdc6cb2bacac78a47b0df8247123051564007fdecedb8ffbd4256f0f09", size = 9563116, upload-time = "2025-09-17T13:54:42.061Z" }, + { url = "https://files.pythonhosted.org/packages/2c/1b/b0516cd3d0e83ebe30e8d04d2a156dac883fd8ac4521deb12de582b2fc78/qiskit_aer-0.17.2-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:c282b9b65f2b011d740e76b0ab44201c70d8d48894ddc12e22442acbb81cc7eb", size = 2393891, upload-time = "2026-02-04T21:30:47.913Z" }, + { url = "https://files.pythonhosted.org/packages/65/5b/c8bf7942ca12d50c4c8c9fde82f25ebcd6198b98f66c51616a9225d541bc/qiskit_aer-0.17.2-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:193de16895ee989a5259331d0f1b1dcad606d506e5e831683ff165e82851a7ac", size = 2117650, upload-time = "2026-02-04T21:30:55.486Z" }, + { url = "https://files.pythonhosted.org/packages/ff/27/f7b518f0928792e454ca9018d712769abf96033902e41bb3b648ff14833b/qiskit_aer-0.17.2-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b84564d563fb06adb454f3528dcc343be327c30cbd5f9f40aec7efd21d241e55", size = 14160447, upload-time = "2026-02-04T21:30:57.829Z" }, + { url = "https://files.pythonhosted.org/packages/f6/c1/59fe9c10e8d53533990ccf1bdf87ac74f77ced57d63a233f759df02bd361/qiskit_aer-0.17.2-cp314-cp314-win_amd64.whl", hash = "sha256:5d7b22dd945df4c69d57e966efb549cf9186055e5f32c649a91b7dd1eb133f07", size = 9697912, upload-time = "2026-02-04T21:30:59.913Z" }, +] + [[package]] name = "qiskit-qasm3-import" version = "0.6.0" From 5553b37af1bb1b42dca4b7f9ac0f55ceff057cbe Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 13 Jun 2026 16:00:26 +0200 Subject: [PATCH 58/72] removed finished TODOs --- tests/test_error_correction.py | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index 32e05b380..965460715 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -6,19 +6,6 @@ # # Licensed under the MIT License -# TODO: -# uv requirements to be added: mqt.qcec, qiskit_aer - - -# What do we want to test: -# 1 function for each, split steane and shor for hardcoded sanity, combine for equvalencies -# transpilers work as intended (simply sanity checks) ✅ -# transpilers produce correct logical circuits (equivalency & simulation) -# produce circuits lead to correct results (equivalency (possible?) & simulations) -# works for all 4 given algorithms (maybe incorporate into correctness and error correction) - -# Have the ability to save the created circuits (utility function) -## save to file vs print vs logging? from __future__ import annotations from pathlib import Path From f5ba739f45961ea7066ed44152b52d6746f6d1fb Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 13 Jun 2026 16:36:42 +0200 Subject: [PATCH 59/72] removed unused file --- scratch.py | 29 ----------------------------- 1 file changed, 29 deletions(-) delete mode 100644 scratch.py diff --git a/scratch.py b/scratch.py deleted file mode 100644 index adf7e4e63..000000000 --- a/scratch.py +++ /dev/null @@ -1,29 +0,0 @@ -# Copyright (c) 2023 - 2026 Chair for Design Automation, TUM -# Copyright (c) 2025 - 2026 Munich Quantum Software Company GmbH -# All rights reserved. -# -# SPDX-License-Identifier: MIT -# -# Licensed under the MIT License - -from qiskit import QuantumCircuit -from qiskit.quantum_info import partial_trace -from qiskit_aer import AerSimulator - -qc = QuantumCircuit(2, 1) -qc.h(0) -qc.cx(0, 1) -qc.measure(0, 0) -with qc.if_test((qc.clbits[0], 1)): - qc.x(1) -qc.save_statevector() - -sim = AerSimulator(method="statevector") -result = sim.run(qc).result() -sv = result.get_statevector() -print("Statevector:") -print(sv) - -rho = partial_trace(sv, [0]) -print("Partial trace (rho):") -print(rho) From 221eb866051eb9ebea12562cc8ebf6279b67b9f4 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 13 Jun 2026 16:37:02 +0200 Subject: [PATCH 60/72] more linting changes --- .../error_correction/steane_transpiler.py | 4 +- tests/test_error_correction.py | 65 ++++++++++--------- 2 files changed, 38 insertions(+), 31 deletions(-) diff --git a/src/mqt/bench/error_correction/steane_transpiler.py b/src/mqt/bench/error_correction/steane_transpiler.py index b925cc0e4..4783d4b66 100644 --- a/src/mqt/bench/error_correction/steane_transpiler.py +++ b/src/mqt/bench/error_correction/steane_transpiler.py @@ -29,7 +29,7 @@ class SteaneTranspiler: """A high-level transpiler that encodes a QuantumCircuit using Steane's 7-qubit error correction code.""" - def __init__(self, original_circuit: QuantumCircuit, add_syndromes=True) -> None: + def __init__(self, original_circuit: QuantumCircuit, add_syndromes: bool = True) -> None: """Initialize the transpiler with the original QuantumCircuit.""" self.original_qc = original_circuit self.num_logical_qubits = original_circuit.num_qubits @@ -301,7 +301,7 @@ def _handle_cx(self, instruction: CircuitInstruction) -> None: self.insert_syndromes(control_logical_qubit_index) self.insert_syndromes(target_logical_qubit_index) - # it сould use the hadamards with cnots + # it could use the hadamards with cnots def _handle_cz(self, instruction: CircuitInstruction) -> None: """Handle CZ instruction.""" control_logical_qubit_index = self.original_qc.qubits.index(instruction.qubits[0]) diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index 965460715..844884b76 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -8,10 +8,14 @@ from __future__ import annotations +import json from pathlib import Path +from re import fullmatch from typing import TYPE_CHECKING +import mqt.qcec import pytest +from mqt.qcec.pyqcec import EquivalenceCriterion from qiskit import QuantumCircuit from qiskit.circuit import CircuitInstruction, ClassicalRegister from qiskit.circuit.library import CXGate, CZGate, HGate, SGate, XGate, ZGate @@ -115,24 +119,25 @@ def add_h_before_measurements(qc: QuantumCircuit) -> QuantumCircuit: @pytest.mark.parametrize("code", ["shor", "steane"]) @pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate"]) # "qft" is unfeasible -@pytest.mark.parametrize("Error", [XGate(), ZGate()]) -@pytest.mark.parametrize("MeasureBaseX", [True, False]) -@pytest.mark.parametrize("CIRCUIT_SIZE", [3]) # range(3, 11)) +@pytest.mark.parametrize("error", [XGate(), ZGate()]) +@pytest.mark.parametrize("measure_base_x", [True, False]) +@pytest.mark.parametrize("circuit_size", [3]) # range(3, 11)) def test_errorcorrection_transpiler_correctness( - code: str, algorithm: str, Error, MeasureBaseX: bool, CIRCUIT_SIZE: int + code: str, algorithm: str, error: Gate, measure_base_x: bool, circuit_size: int ) -> None: - """Ensures the transpiler creates error-corrected circuits which produce the same result as the orinigal logical circuit. + """Ensures the transpiler creates error-corrected circuits which produce the same result as the original logical circuit. + Afterwards an error is introduced and the test checks, whether it is corrected. Iterates over a number of example algorithms. """ - test_id = f"{CIRCUIT_SIZE} qubit {algorithm} on {code} with ZBasis {MeasureBaseX} and error {Error.name}" + test_id = f"{circuit_size} qubit {algorithm} on {code} with ZBasis {measure_base_x} and error {error.name}" # Initialize circuits logical_circuit = benchmark_generation.get_benchmark( - benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=CIRCUIT_SIZE, encoding="" + benchmark=algorithm, level=benchmark_generation.BenchmarkLevel.ALG, circuit_size=circuit_size, encoding="" ) - if MeasureBaseX: + if measure_base_x: logical_circuit = add_h_before_measurements(logical_circuit) # Strip measure gates to avoid intermediate measurements collapsing the state before decoding @@ -154,7 +159,7 @@ def test_errorcorrection_transpiler_correctness( error_induced_circuit = insert_error_after_barrier( error_corrected_circuit, barrier_label="Encoding", - gate=Error, + gate=error, qubit_index=0, ) @@ -246,8 +251,6 @@ def test_error_correction_circuit_structure(code: str, alg: str, logical_qubits: expected_gate_counts = None - import json - json_location = Path(__file__).parent / "gate_counts.json" with Path(f"{json_location}").open("r", encoding="utf-8") as json_data: expected_gate_counts = json.load(json_data) @@ -266,9 +269,11 @@ def test_error_correction_circuit_structure(code: str, alg: str, logical_qubits: def insert_error_after_barrier( qc: QuantumCircuit, barrier_label: str, - gate: Gate = XGate(), + gate: Gate | None = None, qubit_index: int = 0, ) -> QuantumCircuit: + gate = XGate() if gate is None else gate + qc = qc.copy() for i, instruction in enumerate(qc.data): @@ -283,10 +288,12 @@ def insert_error_after_barrier( raise ValueError(msg) -def insert_error(qc: QuantumCircuit, gate: Gate = XGate(), index: int | None = None) -> QuantumCircuit: - """Adds the specified gate at the beginning of the circuit +def insert_error(qc: QuantumCircuit, gate: Gate | None = None, index: int | None = None) -> QuantumCircuit: + """Adds the specified gate at the beginning of the circuit. + Flips the first qubit right after the first barrier by default. """ + gate = XGate() if gate is None else gate assert qc.num_qubits >= gate.num_qubits, f"Quantum Circuit has not enough qubits to accommodate gate {gate.name}" assert index is None or index >= 0, f"Index must be >= 0, Index provided: {index}" @@ -303,27 +310,28 @@ def insert_error(qc: QuantumCircuit, gate: Gate = XGate(), index: int | None = N qc.data.insert(index, CircuitInstruction(gate, qubits)) else: msg = "Please provide either an index or a circuit with a barrier to insert an error into" - raise Exception(msg) + raise ValueError(msg) return qc def check_equivalence(qc1: qk.QuantumCircuit, qc2: qk.QuantumCircuit) -> bool: """Uses MQT QCEC to verify if qc1 and qc2 are equivalent.""" - import mqt.qcec - from mqt.qcec.pyqcec import EquivalenceCriterion as EC - verification_results = mqt.qcec.verify(qc1, qc2, check_partial_equivalence=True) - accepted_equivalencies = [EC.equivalent, EC.equivalent_up_to_global_phase, EC.probably_equivalent] + accepted_equivalencies = [ + EquivalenceCriterion.equivalent, + EquivalenceCriterion.equivalent_up_to_global_phase, + EquivalenceCriterion.probably_equivalent, + ] return verification_results.equivalence in accepted_equivalencies def measure_all_named(qc: QuantumCircuit, name: str = "measurement") -> QuantumCircuit: - """Adds a classical register named 'measurement' to the circuit with one bit - per qubit, then maps each qubit i to classical bit i of that register. + """Adds a classical register named 'measurement' to the circuit with one bit per qubit, then maps each qubit i to classical bit i of that register. Args: qc: The QuantumCircuit to add measurements to (modified in place). + name: The name of the ClassicalRegister the measurement will be performed into Returns: The same QuantumCircuit with the register and measurements added. @@ -352,7 +360,7 @@ def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[dict, QuantumCir # Grabbing only the desired outcomes pub_result = result[0] - meas_bit_counts = pub_result.data.measurements.get_counts() + meas_bit_counts = pub_result.data.measurements.get_counts() # ty: ignore[unresolved-attribute] # outputs reversed bitstrings, we just reverse them right back, # so their indices align with the qubit indices @@ -364,9 +372,9 @@ def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[dict, QuantumCir def compare_distributions( qc1: QuantumCircuit, qc2: QuantumCircuit, counts1: dict, counts2: dict, code1: str = "None", code2: str = "None" ) -> float: - """Simulates 2 circuits and computes the Hellinger Fidelity between their count distributions - 1 = the same, 0 = no overlap. + """Simulates 2 circuits and computes the Hellinger Fidelity between their count distributions. + Hellinger Fidelity: 1 = the same, 0 = no overlap. If code is set to either 'steane' or 'shor' circuit error's result will be interpreted logically """ if code1 in ["steane", "shor"]: @@ -377,7 +385,7 @@ def compare_distributions( return hellinger_fidelity(counts1, counts2) -def parse_qubits(qc: qk.QuantumCircuit, physical_qubits: str): +def parse_qubits(qc: qk.QuantumCircuit, physical_qubits: str) -> str: """Takes in a measurement in physical qubits and returns the corresponding logical measurement. Underlying circuit must use registers named 'qx' (x in int) for each logical qubit, with results in qx[0] @@ -386,11 +394,9 @@ def parse_qubits(qc: qk.QuantumCircuit, physical_qubits: str): physical_qubits = physical_qubits.replace(" ", "") # indices - import re - def is_q_integer(s: str) -> bool: """Checks if s is of form 'qx' where x in int (e.g. 'q1', 'q23').""" - return bool(re.fullmatch(r"q\d+", s)) + return bool(fullmatch(r"q\d+", s)) data_indices = [qc.find_bit(register[0]).index for register in qc.qregs if is_q_integer(register.name)] @@ -403,7 +409,8 @@ def is_q_integer(s: str) -> bool: def condense_counts(qc: qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, int]: - """Takes in a result dict of a decoded physical measurement and returns logical measurements + """Takes in a result dict of a decoded physical measurement and returns logical measurements. + Requires decode to place the result in the first qubit of each register named 'qx', with x an integer (e.g. 'q2'). """ logical_counts = {} From bb53875453f2993b6ebcaa20290cfc06361e577f Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 13 Jun 2026 17:00:20 +0200 Subject: [PATCH 61/72] greatly improved readability of documentation --- tests/test_error_correction.py | 104 +++++++++++++++++++++++++++++++-- 1 file changed, 98 insertions(+), 6 deletions(-) diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index 844884b76..4c98dcb0c 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -34,6 +34,13 @@ @pytest.mark.parametrize("code", ["steane", "shor"]) @pytest.mark.parametrize("gate", [XGate(), ZGate(), HGate(), SGate()]) def test_errorcorrection_transpiler_gate_equivalence(code: str, gate: Gate) -> None: + """Verify that the error-correction transpiler preserves gate semantics. + + For each supported single-qubit gate, builds a minimal logical circuit + containing only that gate, transpiles it with syndrome extraction disabled, + decodes the physical qubits back to logical qubits, and then checks via + MQT QCEC that the resulting circuit is unitarily equivalent to the original. + """ if gate.name == "s" and code == "shor": # this SGate includes non-unitary elements and can therefore not be evaluated properly return @@ -59,6 +66,16 @@ def test_errorcorrection_transpiler_gate_equivalence(code: str, gate: Gate) -> N @pytest.mark.parametrize("code", ["steane", "shor"]) @pytest.mark.parametrize("gate", [XGate(), ZGate(), HGate(), SGate(), CXGate(), CZGate()]) def test_errorcorrection_transpiler_gate_correctness(code: str, gate: Gate) -> None: + """Verify that the transpiler actually corrects an introduced bit-flip error. + + Builds a minimal single-gate logical circuit, transpiles it with full syndrome + extraction enabled, and then creates a copy in which a bit-flip (X gate) is + injected after the first barrier. Both the clean and the error-induced circuits + are simulated, and the test asserts that: + + 1. The error-corrected circuit matches the logical circuit. + 2. The error-induced circuit still matches the clean corrected circuit. + """ if gate.name == "s" and code == "shor": # this takes a little longer.... return @@ -100,6 +117,20 @@ def test_errorcorrection_transpiler_gate_correctness(code: str, gate: Gate) -> N def add_h_before_measurements(qc: QuantumCircuit) -> QuantumCircuit: + """Return a copy of *qc* with an H gate inserted before every measurement. + + This switches each qubit from the Z basis to the X basis immediately prior + to measurement, which is useful for testing circuits whose final state lies + along the X axis of the Bloch sphere (e.g. circuits ending in a superposition). + + Args: + qc: The source circuit; it is not modified in place. + + Returns: + A new :class:`~qiskit.QuantumCircuit` with the same registers and + instructions as *qc*, but with an H gate prepended to every measure + operation. + """ new_qc = QuantumCircuit(*qc.qregs, *qc.cregs, name=qc.name) for instruction in qc.data: @@ -186,6 +217,24 @@ def test_errorcorrection_transpiler_correctness( @pytest.mark.parametrize("alg", ["ghz", "bv", "graphstate", "qft"]) @pytest.mark.parametrize("code", ["shor", "steane"]) def test_error_correction_circuit_structure(code: str, alg: str, logical_qubits: int) -> None: + """Verify the physical circuit structure produced by the error-correction encoder. + + Checks that the encoded circuit has the correct number of physical qubits, + classical bits, and register sizes for the given code and algorithm, and that + the exact gate counts match the reference values stored in ``gate_counts.json``. + + The expected qubit and classical-bit counts are code-dependent: + + * **Steane code**: 13 physical qubits per logical qubit (7 data + 3 bit-flip + ancilla + 3 phase-flip ancilla) and 6 classical bits per logical qubit (3 + bit-flip syndrome + 3 phase-flip syndrome), plus one bit per original clbit. + * **Shor code**: 17 physical qubits per logical qubit (9 data + 6 Z-stabiliser + ancilla + 2 X-stabiliser ancilla) and 8 classical bits per logical qubit (6 + bit-flip syndrome + 2 phase-flip syndrome), plus one bit per original clbit. + + QFT circuits are excluded from the qubit-count checks because their ancilla + qubit count scales with the number of T gates rather than the logical qubit count. + """ test_id = f"{logical_qubits} qubit {alg} on {code}" qc = benchmark_generation.get_benchmark( @@ -272,6 +321,28 @@ def insert_error_after_barrier( gate: Gate | None = None, qubit_index: int = 0, ) -> QuantumCircuit: + """Insert a fault gate immediately after the first barrier with a given label. + + Scans *qc* for a barrier whose ``.label`` attribute matches *barrier_label* + and inserts *gate* on the qubit at *qubit_index* directly after it. This + allows tests to inject a well-placed error (e.g. right after the encoding + barrier) without disturbing the rest of the circuit structure. + + Args: + qc: The circuit to inject the error into. A shallow copy is made so + the original is not modified. + barrier_label: The label of the barrier after which the gate is inserted. + gate: The fault gate to inject. Defaults to :class:`~qiskit.circuit.library.XGate` + (a bit flip) if ``None``. + qubit_index: Index into ``qc.qubits`` of the qubit to apply the gate to. + Defaults to ``0``. + + Returns: + A copy of *qc* with the error gate inserted. + + Raises: + ValueError: If no barrier with *barrier_label* is found in the circuit. + """ gate = XGate() if gate is None else gate qc = qc.copy() @@ -289,9 +360,24 @@ def insert_error_after_barrier( def insert_error(qc: QuantumCircuit, gate: Gate | None = None, index: int | None = None) -> QuantumCircuit: - """Adds the specified gate at the beginning of the circuit. + """Insert a fault gate right after the first barrier in *qc*. + + Locates the first :class:`~qiskit.circuit.Barrier` instruction and inserts + *gate* immediately after it on the first ``gate.num_qubits`` qubits. An + explicit *index* can be provided to override the barrier-search behaviour. + + Args: + qc: The circuit to inject the error into (modified in place). + gate: The fault gate to inject. Defaults to + :class:`~qiskit.circuit.library.XGate` (a bit flip) if ``None``. + index: Instruction index at which to insert the gate. If ``None`` + (default), the position right after the first barrier is used. - Flips the first qubit right after the first barrier by default. + Returns: + *qc* with the error gate inserted. + + Raises: + ValueError: If *index* is ``None`` and no barrier is found in the circuit. """ gate = XGate() if gate is None else gate assert qc.num_qubits >= gate.num_qubits, f"Quantum Circuit has not enough qubits to accommodate gate {gate.name}" @@ -327,14 +413,20 @@ def check_equivalence(qc1: qk.QuantumCircuit, qc2: qk.QuantumCircuit) -> bool: def measure_all_named(qc: QuantumCircuit, name: str = "measurement") -> QuantumCircuit: - """Adds a classical register named 'measurement' to the circuit with one bit per qubit, then maps each qubit i to classical bit i of that register. + """Add a named classical register to *qc* and measure every qubit into it. + + Creates a :class:`~qiskit.circuit.ClassicalRegister` of width + ``qc.num_qubits``, appends it to *qc*, and maps qubit *i* to bit *i* of that + register. This is a convenience wrapper used by :func:`run_circuit` to attach + measurements before simulation while keeping the register name predictable for + later result extraction. Args: - qc: The QuantumCircuit to add measurements to (modified in place). - name: The name of the ClassicalRegister the measurement will be performed into + qc: The circuit to add measurements to (modified in place). + name: Name of the new classical register. Defaults to ``"measurement"``. Returns: - The same QuantumCircuit with the register and measurements added. + The same *qc* instance with the register and measurements appended. """ cr = ClassicalRegister(qc.num_qubits, name=name) qc.add_register(cr) From 08463e3ee6929020853b455e1ef2c0890076e0b1 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 13 Jun 2026 17:13:16 +0200 Subject: [PATCH 62/72] Added module docstring --- tests/test_error_correction.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index 4c98dcb0c..fab1b46e7 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -6,6 +6,8 @@ # # Licensed under the MIT License +"""Tests for the error-correction transpilers (Steane and Shor codes).""" + from __future__ import annotations import json From 33b24ce778a4652415e9d50242b8f4a293c3f7a0 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Sat, 13 Jun 2026 17:13:57 +0200 Subject: [PATCH 63/72] updated uv.lock to reflect added dependencies --- uv.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/uv.lock b/uv.lock index f2138db72..ed1cd78f5 100644 --- a/uv.lock +++ b/uv.lock @@ -1722,7 +1722,7 @@ source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "mqt-core" }, { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "typing-extensions", marker = "python_full_version < '3.11'" }, ] sdist = { url = "https://files.pythonhosted.org/packages/d5/16/1e900e1c89a034ee8d6196de3d2b5f30711ffe159c5a4de0fdc6189374b3/mqt_qcec-3.6.1.tar.gz", hash = "sha256:0d053387fa2d660fcf8ec9ab6e5da6069634d73a3e95a7a9e5b5fed7479af3e3", size = 293070, upload-time = "2026-05-20T22:19:36.403Z" } @@ -2277,7 +2277,7 @@ name = "pexpect" version = "4.9.0" source = { registry = "https://pypi.org/simple" } dependencies = [ - { name = "ptyprocess" }, + { name = "ptyprocess", marker = "(python_full_version < '3.11' and sys_platform == 'emscripten') or (python_full_version < '3.11' and sys_platform == 'win32') or (sys_platform != 'emscripten' and sys_platform != 'win32')" }, ] sdist = { url = "https://files.pythonhosted.org/packages/42/92/cc564bf6381ff43ce1f4d06852fc19a2f11d180f23dc32d9588bee2f149d/pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f", size = 166450, upload-time = "2023-11-25T09:07:26.339Z" } wheels = [ @@ -2839,7 +2839,7 @@ version = "0.17.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "numpy", version = "2.2.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.11'" }, - { name = "numpy", version = "2.4.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "numpy", version = "2.4.6", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, { name = "psutil" }, { name = "python-dateutil" }, { name = "qiskit" }, From 80f347ff8a4d20c06305d05b8cb5d644e843300a Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Tue, 16 Jun 2026 13:33:53 +0200 Subject: [PATCH 64/72] fixed wrong import location --- src/mqt/bench/benchmarks/shors_nine_qubit_code.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mqt/bench/benchmarks/shors_nine_qubit_code.py b/src/mqt/bench/benchmarks/shors_nine_qubit_code.py index 1eed0886d..ca728a28e 100644 --- a/src/mqt/bench/benchmarks/shors_nine_qubit_code.py +++ b/src/mqt/bench/benchmarks/shors_nine_qubit_code.py @@ -13,7 +13,7 @@ from qiskit import ClassicalRegister from qiskit.circuit import AncillaRegister, QuantumCircuit, QuantumRegister -from mqt.bench.component.shor_circuit_components import ( +from mqt.bench.components.shor_circuit_components import ( apply_nine_qubit_shors_code_bit_flip_correction, apply_nine_qubit_shors_code_phase_flip_correction, get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit, From 1d0e8de59e88e0ea7a6464611b317b570fc6af17 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Tue, 16 Jun 2026 13:43:04 +0200 Subject: [PATCH 65/72] Added explicit type checking to syndrome extraction --- src/mqt/bench/error_correction/shor_transpiler.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/mqt/bench/error_correction/shor_transpiler.py b/src/mqt/bench/error_correction/shor_transpiler.py index ce8d8fad9..49ecc900b 100644 --- a/src/mqt/bench/error_correction/shor_transpiler.py +++ b/src/mqt/bench/error_correction/shor_transpiler.py @@ -388,6 +388,10 @@ def insert_syndromes(self, logical_qubit_index: int) -> None: def _extract_bit_flip_syndromes(self, qubit: ShorLogicalQubit) -> None: """Extract bit-flip syndromes for the three blocks.""" + if qubit.bit_flip_syndrome is None: + msg = "Bit-flip syndrome register is missing or not initialized." + raise ValueError(msg) + self.transpiled_qc.reset(qubit.bit_flip_syndrome) for i in range(SHOR_NUM_BLOCKS): self.transpiled_qc.compose( @@ -399,6 +403,10 @@ def _extract_bit_flip_syndromes(self, qubit: ShorLogicalQubit) -> None: def _extract_phase_flip_syndromes(self, qubit: ShorLogicalQubit) -> None: """Extract phase-flip syndromes across the blocks.""" + if qubit.phase_flip_syndrome is None: + msg = "Bit-flip syndrome register is missing or not initialized." + raise ValueError(msg) + self.transpiled_qc.reset(qubit.phase_flip_syndrome) self.transpiled_qc.compose( get_nine_qubit_shors_code_phase_flip_syndrome_extraction_circuit(), From 9576b21163bfb0b6beea2e2b9ea60e6bcf99aab2 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Tue, 16 Jun 2026 13:53:17 +0200 Subject: [PATCH 66/72] Moved testing dependencies to appropriate pytest groups --- pyproject.toml | 4 ++++ uv.lock | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 9a32c427e..c2b94cb3d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -83,6 +83,10 @@ dev = [ "prek>=0.2.27", "ty==0.0.40", ] +pytest = [ + "mqt-qcec>=3.6.1", + "qiskit-aer>=0.17.2", +] [project.scripts] "create_mqt_bench_zip" = "mqt.bench.utils:create_zip_file" diff --git a/uv.lock b/uv.lock index ed1cd78f5..e3408e866 100644 --- a/uv.lock +++ b/uv.lock @@ -1627,6 +1627,10 @@ docs = [ { name = "sphinxcontrib-svg2pdfconverter" }, { name = "sphinxext-opengraph" }, ] +pytest = [ + { name = "mqt-qcec" }, + { name = "qiskit-aer" }, +] test = [ { name = "pytest" }, { name = "pytest-console-scripts" }, @@ -1675,6 +1679,10 @@ docs = [ { name = "sphinxcontrib-svg2pdfconverter", specifier = ">=1.3.0" }, { name = "sphinxext-opengraph", specifier = ">=0.13.0" }, ] +pytest = [ + { name = "mqt-qcec", specifier = ">=3.6.1" }, + { name = "qiskit-aer", specifier = ">=0.17.2" }, +] test = [ { name = "pytest", specifier = ">=9.0.1" }, { name = "pytest-console-scripts", specifier = ">=1.4.1" }, From 5823f63166fd5cb0a015c1b4bb3c41beb48f03e9 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Tue, 16 Jun 2026 16:22:24 +0200 Subject: [PATCH 67/72] Varios little improvements to code quality --- tests/test_error_correction.py | 102 ++++++++++++++++++++++++--------- 1 file changed, 76 insertions(+), 26 deletions(-) diff --git a/tests/test_error_correction.py b/tests/test_error_correction.py index fab1b46e7..3386eb283 100644 --- a/tests/test_error_correction.py +++ b/tests/test_error_correction.py @@ -24,7 +24,7 @@ from qiskit.quantum_info import hellinger_fidelity from qiskit_aer.primitives import SamplerV2 -import mqt.bench.benchmark_generation as benchmark_generation +from mqt.bench import benchmark_generation from mqt.bench.error_correction.shor_transpiler import ShorTranspiler from mqt.bench.error_correction.steane_transpiler import SteaneTranspiler @@ -44,7 +44,8 @@ def test_errorcorrection_transpiler_gate_equivalence(code: str, gate: Gate) -> N MQT QCEC that the resulting circuit is unitarily equivalent to the original. """ if gate.name == "s" and code == "shor": - # this SGate includes non-unitary elements and can therefore not be evaluated properly + # this transpiler constructs the SGate using measure and a classically controlled operation + # therefore it can't be evaluated properly by MQT.QCEC return num_qubits = gate.num_qubits @@ -89,14 +90,13 @@ def test_errorcorrection_transpiler_gate_correctness(code: str, gate: Gate) -> N if code == "shor": transpiler = ShorTranspiler(error_corrected_circuit, add_syndromes=True) else: - transpiler = SteaneTranspiler(logical_circuit, add_syndromes=True) + transpiler = SteaneTranspiler(error_corrected_circuit, add_syndromes=True) transpiler.transpile() transpiler.decode_qubits() error_corrected_circuit = transpiler.transpiled_qc error_induced_circuit = error_corrected_circuit.copy() # this is for inserting phase flip in steane after the first Hadamard - # error_induced_circuit = insert_error(error_induced_circuit ,gate=ZGate(), index=16) error_induced_circuit = insert_error(error_induced_circuit, gate=XGate()) logical_counts, logical_circuit = run_circuit(logical_circuit) @@ -151,10 +151,10 @@ def add_h_before_measurements(qc: QuantumCircuit) -> QuantumCircuit: @pytest.mark.parametrize("code", ["shor", "steane"]) -@pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate"]) # "qft" is unfeasible +@pytest.mark.parametrize("algorithm", ["ghz", "bv", "graphstate"]) # "qft" is unfeasible: >3k gates on 34 qubits @pytest.mark.parametrize("error", [XGate(), ZGate()]) @pytest.mark.parametrize("measure_base_x", [True, False]) -@pytest.mark.parametrize("circuit_size", [3]) # range(3, 11)) +@pytest.mark.parametrize("circuit_size", [3]) def test_errorcorrection_transpiler_correctness( code: str, algorithm: str, error: Gate, measure_base_x: bool, circuit_size: int ) -> None: @@ -162,6 +162,9 @@ def test_errorcorrection_transpiler_correctness( Afterwards an error is introduced and the test checks, whether it is corrected. Iterates over a number of example algorithms. + + `circuit_size` can be any list of integers between 3 and 10 (=> up to range(3,11)). + For larger circuit sizes, gate_counts.json has to be updated """ test_id = f"{circuit_size} qubit {algorithm} on {code} with ZBasis {measure_base_x} and error {error.name}" @@ -303,9 +306,8 @@ def test_error_correction_circuit_structure(code: str, alg: str, logical_qubits: expected_gate_counts = None json_location = Path(__file__).parent / "gate_counts.json" - with Path(f"{json_location}").open("r", encoding="utf-8") as json_data: + with json_location.open("r", encoding="utf-8") as json_data: expected_gate_counts = json.load(json_data) - json_data.close() assert expected_gate_counts is not None, f"Failure reading respective gate counts for {test_id}" expected_gate_counts = expected_gate_counts[code][alg][f"{logical_qubits}"] @@ -376,14 +378,18 @@ def insert_error(qc: QuantumCircuit, gate: Gate | None = None, index: int | None (default), the position right after the first barrier is used. Returns: - *qc* with the error gate inserted. + The same *qc* instance with the error gate inserted. Raises: ValueError: If *index* is ``None`` and no barrier is found in the circuit. """ gate = XGate() if gate is None else gate - assert qc.num_qubits >= gate.num_qubits, f"Quantum Circuit has not enough qubits to accommodate gate {gate.name}" - assert index is None or index >= 0, f"Index must be >= 0, Index provided: {index}" + if qc.num_qubits < gate.num_qubits: + msg = f"Quantum Circuit has not enough qubits to accommodate gate {gate.name}" + raise ValueError(msg) + if index is not None and index < 0: + msg = f"Index must be >= 0, Index provided: {index}" + raise ValueError(msg) # Finds the first barrier if index is None: @@ -404,7 +410,16 @@ def insert_error(qc: QuantumCircuit, gate: Gate | None = None, index: int | None def check_equivalence(qc1: qk.QuantumCircuit, qc2: qk.QuantumCircuit) -> bool: - """Uses MQT QCEC to verify if qc1 and qc2 are equivalent.""" + """Uses MQT QCEC to verify if qc1 and qc2 are equivalent. + + Args: + qc1: The first quantum circuit. + qc2: The second quantum circuit. + + Returns: + True if the circuits are equivalent, equivalent up to global phase, + or probably equivalent; False otherwise. + """ verification_results = mqt.qcec.verify(qc1, qc2, check_partial_equivalence=True) accepted_equivalencies = [ EquivalenceCriterion.equivalent, @@ -437,15 +452,20 @@ def measure_all_named(qc: QuantumCircuit, name: str = "measurement") -> QuantumC def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[dict, QuantumCircuit]: - """Simulates the circuit using AerSimulator. + """Simulate the circuit using Aer's SamplerV2 and return measurement counts. - Adds measurements to all qubits, adds new classical registers for each. - Reads out ONLY those measurements and returns their counts + Adds a named classical register for measurements, simulates the circuit, + and extracts the measurement outcomes. - Returns: - counts of all quantum registers + Args: + qc: The quantum circuit to simulate. It will be modified in place to + add measurements. + shots: Number of simulation shots. Defaults to 1024. - qc with measure_all() + Returns: + Tuple containing: + - Measurement counts with bitstrings reversed to align qubit indices. + - The input circuit with measurements added. """ sampler = SamplerV2() qc = measure_all_named(qc, "measurements") @@ -456,7 +476,7 @@ def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[dict, QuantumCir pub_result = result[0] meas_bit_counts = pub_result.data.measurements.get_counts() # ty: ignore[unresolved-attribute] - # outputs reversed bitstrings, we just reverse them right back, + # get_counts() outputs reversed bitstrings, we just reverse them right back, # so their indices align with the qubit indices meas_bit_counts = {k[::-1]: v for k, v in meas_bit_counts.items()} @@ -466,10 +486,23 @@ def run_circuit(qc: QuantumCircuit, shots: int = 1024) -> tuple[dict, QuantumCir def compare_distributions( qc1: QuantumCircuit, qc2: QuantumCircuit, counts1: dict, counts2: dict, code1: str = "None", code2: str = "None" ) -> float: - """Simulates 2 circuits and computes the Hellinger Fidelity between their count distributions. + """Compute the Hellinger fidelity between two measurement distributions. + + If either code is 'steane' or 'shor', the corresponding counts are condensed + from physical qubits to logical qubits before comparison. - Hellinger Fidelity: 1 = the same, 0 = no overlap. - If code is set to either 'steane' or 'shor' circuit error's result will be interpreted logically + Args: + qc1: The first quantum circuit. + qc2: The second quantum circuit. + counts1: Measurement counts from the first circuit. + counts2: Measurement counts from the second circuit. + code1: Error correction code for the first circuit ('steane', 'shor', + or 'None'). Defaults to 'None'. + code2: Error correction code for the second circuit ('steane', 'shor', + or 'None'). Defaults to 'None'. + + Returns: + Hellinger fidelity between the distributions (1 = identical, 0 = no overlap). """ if code1 in ["steane", "shor"]: counts1 = condense_counts(qc1, counts1) @@ -480,9 +513,17 @@ def compare_distributions( def parse_qubits(qc: qk.QuantumCircuit, physical_qubits: str) -> str: - """Takes in a measurement in physical qubits and returns the corresponding logical measurement. + """Extract logical qubit measurements from a physical measurement string. + + The circuit must use registers named 'qx' (where x is an integer) for each + logical qubit, with the decoded result stored in qx[0]. + + Args: + qc: The quantum circuit containing named registers. + physical_qubits: Measurement bitstring from physical qubits. - Underlying circuit must use registers named 'qx' (x in int) for each logical qubit, with results in qx[0] + Returns: + Logical measurement bitstring extracted from the named registers. """ # remove blanks caused by classical registers physical_qubits = physical_qubits.replace(" ", "") @@ -503,9 +544,18 @@ def is_q_integer(s: str) -> bool: def condense_counts(qc: qk.QuantumCircuit, counts: dict[str, int]) -> dict[str, int]: - """Takes in a result dict of a decoded physical measurement and returns logical measurements. + """Map physical measurement counts to logical measurement counts. + + Requires the circuit to have decoded each logical qubit into the first + qubit of a register named 'qx', where x is an integer (e.g., 'q2'). - Requires decode to place the result in the first qubit of each register named 'qx', with x an integer (e.g. 'q2'). + Args: + qc: The quantum circuit with named registers. + counts: Dictionary mapping physical measurement bitstrings to counts. + + Returns: + Dictionary mapping logical measurement bitstrings to counts, where + multiple physical measurements may map to the same logical measurement. """ logical_counts = {} for physical_measurement, count in counts.items(): From 75df4d6843160470a13223b18f9382672705e89d Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Tue, 16 Jun 2026 16:23:51 +0200 Subject: [PATCH 68/72] Corrected module docstring --- src/mqt/bench/components/steane_circuit_components.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mqt/bench/components/steane_circuit_components.py b/src/mqt/bench/components/steane_circuit_components.py index 38a596ab2..4b0946744 100644 --- a/src/mqt/bench/components/steane_circuit_components.py +++ b/src/mqt/bench/components/steane_circuit_components.py @@ -6,7 +6,7 @@ # # Licensed under the MIT License -"""Shor's 9-qubit code circuit components.""" +"""Steane's 9-qubit code circuit components.""" from __future__ import annotations From bc6c18beb34b24fe1df63dd7d08bcf3236e1bb26 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Tue, 16 Jun 2026 16:24:24 +0200 Subject: [PATCH 69/72] Corrected module docstring --- src/mqt/bench/components/steane_circuit_components.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mqt/bench/components/steane_circuit_components.py b/src/mqt/bench/components/steane_circuit_components.py index 4b0946744..296aead5d 100644 --- a/src/mqt/bench/components/steane_circuit_components.py +++ b/src/mqt/bench/components/steane_circuit_components.py @@ -6,7 +6,7 @@ # # Licensed under the MIT License -"""Steane's 9-qubit code circuit components.""" +"""Steane's 7-qubit code circuit components.""" from __future__ import annotations From fe66b60b67d079b0e6098002a899c016b5ea802f Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Tue, 16 Jun 2026 17:04:48 +0200 Subject: [PATCH 70/72] Enabled Solovay-Kitaev synthesis on steanes qft decomposition --- src/mqt/bench/error_correction/steane_transpiler.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/mqt/bench/error_correction/steane_transpiler.py b/src/mqt/bench/error_correction/steane_transpiler.py index 4783d4b66..12f466df7 100644 --- a/src/mqt/bench/error_correction/steane_transpiler.py +++ b/src/mqt/bench/error_correction/steane_transpiler.py @@ -129,6 +129,7 @@ def replace_gates(self) -> None: basis_gates=["h", "x", "z", "s", "t", "cx", "cz"], optimization_level=3, approximation_degree=0.95, + unitary_synthesis_method="sk", # test ) normalized.compose( From b1fc4c149a01853baad70239c8496005213693d0 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Tue, 16 Jun 2026 17:49:33 +0200 Subject: [PATCH 71/72] added debug message regarding qiskit version --- src/mqt/bench/error_correction/steane_transpiler.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/mqt/bench/error_correction/steane_transpiler.py b/src/mqt/bench/error_correction/steane_transpiler.py index 12f466df7..8b921a5f4 100644 --- a/src/mqt/bench/error_correction/steane_transpiler.py +++ b/src/mqt/bench/error_correction/steane_transpiler.py @@ -12,6 +12,7 @@ from typing import TYPE_CHECKING +import qiskit as qk from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister, transpile from qiskit.circuit import AncillaRegister @@ -123,6 +124,9 @@ def replace_gates(self) -> None: if gate_name == "qft": tmp = QuantumCircuit(len(instruction.qubits)) tmp.append(instruction.operation, range(len(instruction.qubits))) + print("###################################################################") + print("qiskit version for debugging:", qk.__version__) + print("###################################################################") tmp = transpile( tmp, From af74ee545d06188d2c19ee40ba6b028f6a16f870 Mon Sep 17 00:00:00 2001 From: Felix Gundlach Date: Tue, 16 Jun 2026 17:57:37 +0200 Subject: [PATCH 72/72] Switched to 2-pass transpilation for Steane's --- .../error_correction/steane_transpiler.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/mqt/bench/error_correction/steane_transpiler.py b/src/mqt/bench/error_correction/steane_transpiler.py index 8b921a5f4..9b015aa72 100644 --- a/src/mqt/bench/error_correction/steane_transpiler.py +++ b/src/mqt/bench/error_correction/steane_transpiler.py @@ -12,9 +12,10 @@ from typing import TYPE_CHECKING -import qiskit as qk from qiskit import ClassicalRegister, QuantumCircuit, QuantumRegister, transpile from qiskit.circuit import AncillaRegister +from qiskit.transpiler import PassManager +from qiskit.transpiler.passes.synthesis import SolovayKitaev from mqt.bench.components.steane_circuit_components import ( apply_seven_qubit_steane_code_correction, @@ -124,9 +125,18 @@ def replace_gates(self) -> None: if gate_name == "qft": tmp = QuantumCircuit(len(instruction.qubits)) tmp.append(instruction.operation, range(len(instruction.qubits))) - print("###################################################################") - print("qiskit version for debugging:", qk.__version__) - print("###################################################################") + + # 1. break down 2 qubit gates into single qubit gates, which are still continuous + continuous_basis = ["rx", "ry", "rz", "cx", "cz"] + tmp_continuous = transpile( + tmp, basis_gates=continuous_basis, optimization_level=3, approximation_degree=0.95 + ) + + # 2. Use Solovay-Kitaev to transform continuous gates into discrete ones + # recursion_degree controls depth/accuracy trade-off + sk_pass = SolovayKitaev(recursion_degree=2, basis_gates=["h", "x", "z", "s", "t"]) + pm = PassManager([sk_pass]) + pm.run(tmp_continuous) tmp = transpile( tmp,