From 9835f4489837dcd1cf9591c68f007f5126441b04 Mon Sep 17 00:00:00 2001 From: Yudong Sun Date: Thu, 14 May 2026 14:58:09 +0200 Subject: [PATCH 01/20] create function for applying CNOT to a PureFaultSet Co-authored-by: Copilot --- src/mqt/qecc/circuit_synthesis/faults.py | 30 ++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/mqt/qecc/circuit_synthesis/faults.py b/src/mqt/qecc/circuit_synthesis/faults.py index 8da7d68ed..060b8b1e7 100644 --- a/src/mqt/qecc/circuit_synthesis/faults.py +++ b/src/mqt/qecc/circuit_synthesis/faults.py @@ -375,6 +375,36 @@ def permute_qubits(self, permutation: npt.NDArray[np.int8] | list[int], inplace: return PureFaultSet.from_fault_array(permuted_faults) + def apply_cnot(self, control: int, target: int, kind: str = "X", inplace: bool = True) -> PureFaultSet: + """Apply a CNOT gate to the faults in the set. + + Args: + control: The index of the control qubit. + target: The index of the target qubit. + kind: The type of faults to apply the CNOT to ('X' or 'Z'). + inplace: If True, modifies the current fault set. If False, returns a new PureFaultSet with updated faults. + + Returns: + A new PureFaultSet with updated faults if inplace is False. + """ + if control >= self.num_qubits or target >= self.num_qubits: + msg = f"Control and target indices must be less than {self.num_qubits}." + raise ValueError(msg) + if kind.capitalize() not in {"X", "Z"}: + msg = "Kind must be either 'X' or 'Z'." + raise ValueError(msg) + + updated_faults = np.copy(self.faults) + if kind == "X": + updated_faults[:, target] ^= updated_faults[:, control] + else: # kind == "Z" + updated_faults[:, control] ^= updated_faults[:, target] + + if inplace: + self.faults = updated_faults + return self + + return PureFaultSet.from_fault_array(updated_faults) def coset_leader(fault: npt.NDArray[np.int8], generators: npt.NDArray[np.int8]) -> npt.NDArray[np.int8]: """Compute the coset leader of a fault given a set of stabilizer generators.""" From e1459702bc1b1880b310296c5a8b45bf0d491566 Mon Sep 17 00:00:00 2001 From: Yudong Sun Date: Thu, 14 May 2026 14:59:01 +0200 Subject: [PATCH 02/20] Test core functionality of apply_cnot --- tests/circuit_synthesis/test_faults.py | 41 ++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/tests/circuit_synthesis/test_faults.py b/tests/circuit_synthesis/test_faults.py index a4f220d60..da5f83727 100644 --- a/tests/circuit_synthesis/test_faults.py +++ b/tests/circuit_synthesis/test_faults.py @@ -637,3 +637,44 @@ def test_permute_qubits_inplace(): fault_set.permute_qubits(permutation, inplace=True) assert fault_set != PureFaultSet.from_fault_array(faults), "Faults were not permuted correctly in place" + + +def test_apply_cnot_x(): + """Test applying a CNOT gate to the fault set.""" + faults1 = np.array([[1, 0, 0]], dtype=np.int8) + fault_set1 = PureFaultSet.from_fault_array(faults1) + + # Apply CNOT with control=0 and target=1 + fault_set1.apply_cnot(control=0, target=1, kind="X") + + expected_faults1 = np.array([[1, 1, 0]], dtype=np.int8) + assert np.array_equal(fault_set1.to_array(), expected_faults1), "CNOT gate was not applied correctly to the fault set" + + faults2 = np.array([[0, 1, 0]], dtype=np.int8) + fault_set2 = PureFaultSet.from_fault_array(faults2) + + # Apply CNOT with control=0 and target=1 + fault_set2.apply_cnot(control=0, target=1, kind="X") + + expected_faults2 = np.array([[0, 1, 0]], dtype=np.int8) + assert np.array_equal(fault_set2.to_array(), expected_faults2), "CNOT gate was not applied correctly to the fault set" + +def test_apply_cnot_z(): + """Test applying a CNOT gate to the fault set.""" + faults1 = np.array([[1, 0, 0]], dtype=np.int8) + fault_set1 = PureFaultSet.from_fault_array(faults1) + + # Apply CNOT with control=0 and target=1 + fault_set1.apply_cnot(control=0, target=1, kind="Z") + + expected_faults1 = np.array([[1, 0, 0]], dtype=np.int8) + assert np.array_equal(fault_set1.to_array(), expected_faults1), "CNOT gate was not applied correctly to the fault set" + + faults2 = np.array([[0, 1, 0]], dtype=np.int8) + fault_set2 = PureFaultSet.from_fault_array(faults2) + + # Apply CNOT with control=0 and target=1 + fault_set2.apply_cnot(control=0, target=1, kind="Z") + + expected_faults2 = np.array([[1, 1, 0]], dtype=np.int8) + assert np.array_equal(fault_set2.to_array(), expected_faults2), "CNOT gate was not applied correctly to the fault set" \ No newline at end of file From 4fe853a616ac472843e1d2d4d618d29814e72bb7 Mon Sep 17 00:00:00 2001 From: Yudong Sun Date: Thu, 14 May 2026 15:23:32 +0200 Subject: [PATCH 03/20] Make kind a property of PureFaultSet Co-authored-by: Copilot --- src/mqt/qecc/circuit_synthesis/faults.py | 35 +++++++++++++++--------- tests/circuit_synthesis/test_faults.py | 30 ++++++++++++++------ 2 files changed, 44 insertions(+), 21 deletions(-) diff --git a/src/mqt/qecc/circuit_synthesis/faults.py b/src/mqt/qecc/circuit_synthesis/faults.py index 060b8b1e7..110cf4a4b 100644 --- a/src/mqt/qecc/circuit_synthesis/faults.py +++ b/src/mqt/qecc/circuit_synthesis/faults.py @@ -28,14 +28,27 @@ class PureFaultSet: """Represents a collection of pure faults (X-type or Z-type) in a quantum circuit.""" - def __init__(self, num_qubits: int) -> None: + def __init__(self, num_qubits: int, kind: str = "X") -> None: """Initialize a PureFaultSet object. Args: num_qubits: The number of qubits in the circuit. + kind: The type of faults that this PureFaultSet represents ('X' or 'Z'). """ self.num_qubits = num_qubits self.faults = np.zeros((0, num_qubits), dtype=np.int8) # Pure faults as binary vectors + self.kind = kind + + @property + def kind(self) -> str: + """Return the type of faults in the set ('X' or 'Z').""" + return self._kind + + @kind.setter + def kind(self, value: str) -> None: + """Set the type of faults in the set ('X' or 'Z').""" + assert value.upper() in {"X", "Z"}, "Kind must be either 'X' or 'Z'." + self._kind = value.upper() def add_fault(self, fault: npt.NDArray[np.int8]) -> None: """Add a fault to the fault set. @@ -86,7 +99,7 @@ def to_array(self) -> npt.NDArray[np.int8]: return self.faults @classmethod - def from_fault_array(cls, array: npt.NDArray[np.int8]) -> PureFaultSet: + def from_fault_array(cls, array: npt.NDArray[np.int8], kind: str = "X") -> PureFaultSet: """Create a PureFaultSet from a numpy array of faults. Returns: @@ -95,7 +108,7 @@ def from_fault_array(cls, array: npt.NDArray[np.int8]) -> PureFaultSet: if array.ndim != 2: msg = "Input array must be 2-dimensional." raise ValueError(msg) - fault_set = cls(array.shape[1]) + fault_set = cls(array.shape[1], kind=kind) fault_set.faults = np.unique(array, axis=0) return fault_set @@ -124,7 +137,7 @@ def from_cnot_circuit(cls, circ: CNOTCircuit, kind: str = "X", reduce: bool = Fa qubit_faults[ctrl].append(new_fault) # Create the fault set - fs = cls.from_fault_array(np.array([fault for faults in qubit_faults for fault in faults], dtype=np.int8)) + fs = cls.from_fault_array(np.array([fault for faults in qubit_faults for fault in faults], dtype=np.int8), kind=kind) if not reduce: return fs @@ -375,29 +388,25 @@ def permute_qubits(self, permutation: npt.NDArray[np.int8] | list[int], inplace: return PureFaultSet.from_fault_array(permuted_faults) - def apply_cnot(self, control: int, target: int, kind: str = "X", inplace: bool = True) -> PureFaultSet: - """Apply a CNOT gate to the faults in the set. + def apply_cnot(self, control: int, target: int, inplace: bool = True) -> PureFaultSet: + """Apply a CNOT gate to the faults in the set, based on the type of faults (X or Z). Args: control: The index of the control qubit. target: The index of the target qubit. - kind: The type of faults to apply the CNOT to ('X' or 'Z'). inplace: If True, modifies the current fault set. If False, returns a new PureFaultSet with updated faults. Returns: A new PureFaultSet with updated faults if inplace is False. """ if control >= self.num_qubits or target >= self.num_qubits: - msg = f"Control and target indices must be less than {self.num_qubits}." - raise ValueError(msg) - if kind.capitalize() not in {"X", "Z"}: - msg = "Kind must be either 'X' or 'Z'." + msg = f"Control and target indices must be between 0 and {self.num_qubits - 1}." raise ValueError(msg) updated_faults = np.copy(self.faults) - if kind == "X": + if self.kind == "X": updated_faults[:, target] ^= updated_faults[:, control] - else: # kind == "Z" + else: # self.kind == "Z" updated_faults[:, control] ^= updated_faults[:, target] if inplace: diff --git a/tests/circuit_synthesis/test_faults.py b/tests/circuit_synthesis/test_faults.py index da5f83727..d1e86a7a6 100644 --- a/tests/circuit_synthesis/test_faults.py +++ b/tests/circuit_synthesis/test_faults.py @@ -639,42 +639,56 @@ def test_permute_qubits_inplace(): assert fault_set != PureFaultSet.from_fault_array(faults), "Faults were not permuted correctly in place" +def test_PureFaultSet_invalid_kind(): + """Test that an invalid kind raises an assertion error.""" + with pytest.raises(AssertionError, match=r"Kind must be either 'X' or 'Z'."): + pfs = PureFaultSet(5, kind="Y") + + with pytest.raises(AssertionError, match=r"Kind must be either 'X' or 'Z'."): + pfs = PureFaultSet.from_fault_array(np.array([[1, 0, 1]], dtype=np.int8), kind="Y") + + with pytest.raises(AssertionError, match=r"Kind must be either 'X' or 'Z'."): + pfs = PureFaultSet(5) + pfs.kind = "Y" + + def test_apply_cnot_x(): """Test applying a CNOT gate to the fault set.""" faults1 = np.array([[1, 0, 0]], dtype=np.int8) - fault_set1 = PureFaultSet.from_fault_array(faults1) + fault_set1 = PureFaultSet.from_fault_array(faults1, kind="X") # Apply CNOT with control=0 and target=1 - fault_set1.apply_cnot(control=0, target=1, kind="X") + fault_set1.apply_cnot(control=0, target=1) expected_faults1 = np.array([[1, 1, 0]], dtype=np.int8) assert np.array_equal(fault_set1.to_array(), expected_faults1), "CNOT gate was not applied correctly to the fault set" faults2 = np.array([[0, 1, 0]], dtype=np.int8) - fault_set2 = PureFaultSet.from_fault_array(faults2) + fault_set2 = PureFaultSet.from_fault_array(faults2, kind="X") # Apply CNOT with control=0 and target=1 - fault_set2.apply_cnot(control=0, target=1, kind="X") + fault_set2.apply_cnot(control=0, target=1) expected_faults2 = np.array([[0, 1, 0]], dtype=np.int8) assert np.array_equal(fault_set2.to_array(), expected_faults2), "CNOT gate was not applied correctly to the fault set" + def test_apply_cnot_z(): """Test applying a CNOT gate to the fault set.""" faults1 = np.array([[1, 0, 0]], dtype=np.int8) - fault_set1 = PureFaultSet.from_fault_array(faults1) + fault_set1 = PureFaultSet.from_fault_array(faults1, kind="Z") # Apply CNOT with control=0 and target=1 - fault_set1.apply_cnot(control=0, target=1, kind="Z") + fault_set1.apply_cnot(control=0, target=1) expected_faults1 = np.array([[1, 0, 0]], dtype=np.int8) assert np.array_equal(fault_set1.to_array(), expected_faults1), "CNOT gate was not applied correctly to the fault set" faults2 = np.array([[0, 1, 0]], dtype=np.int8) - fault_set2 = PureFaultSet.from_fault_array(faults2) + fault_set2 = PureFaultSet.from_fault_array(faults2, kind="Z") # Apply CNOT with control=0 and target=1 - fault_set2.apply_cnot(control=0, target=1, kind="Z") + fault_set2.apply_cnot(control=0, target=1) expected_faults2 = np.array([[1, 1, 0]], dtype=np.int8) assert np.array_equal(fault_set2.to_array(), expected_faults2), "CNOT gate was not applied correctly to the fault set" \ No newline at end of file From 51705859d21ccedbc457b21ceea8da76db1f2fd8 Mon Sep 17 00:00:00 2001 From: Yudong Sun Date: Thu, 14 May 2026 15:25:26 +0200 Subject: [PATCH 04/20] Test invalid qubit indices for apply_cnot Co-authored-by: Copilot --- src/mqt/qecc/circuit_synthesis/faults.py | 3 +++ tests/circuit_synthesis/test_faults.py | 14 +++++++++++++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/mqt/qecc/circuit_synthesis/faults.py b/src/mqt/qecc/circuit_synthesis/faults.py index 110cf4a4b..bac4355fa 100644 --- a/src/mqt/qecc/circuit_synthesis/faults.py +++ b/src/mqt/qecc/circuit_synthesis/faults.py @@ -402,6 +402,9 @@ def apply_cnot(self, control: int, target: int, inplace: bool = True) -> PureFau if control >= self.num_qubits or target >= self.num_qubits: msg = f"Control and target indices must be between 0 and {self.num_qubits - 1}." raise ValueError(msg) + if control == target: + msg = "Control and target qubits must be different." + raise ValueError(msg) updated_faults = np.copy(self.faults) if self.kind == "X": diff --git a/tests/circuit_synthesis/test_faults.py b/tests/circuit_synthesis/test_faults.py index d1e86a7a6..ab68861c0 100644 --- a/tests/circuit_synthesis/test_faults.py +++ b/tests/circuit_synthesis/test_faults.py @@ -691,4 +691,16 @@ def test_apply_cnot_z(): fault_set2.apply_cnot(control=0, target=1) expected_faults2 = np.array([[1, 1, 0]], dtype=np.int8) - assert np.array_equal(fault_set2.to_array(), expected_faults2), "CNOT gate was not applied correctly to the fault set" \ No newline at end of file + assert np.array_equal(fault_set2.to_array(), expected_faults2), "CNOT gate was not applied correctly to the fault set" + + +def test_apply_cnot_invalid_qubits(): + """Test that applying a CNOT gate with invalid qubit indices raises an error.""" + faults = np.array([[1, 0, 0]], dtype=np.int8) + fault_set = PureFaultSet.from_fault_array(faults) + + with pytest.raises(ValueError, match=r"Control and target qubits must be different."): + fault_set.apply_cnot(control=0, target=0) + + with pytest.raises(ValueError, match=r"Control and target indices must be between 0 and 2."): + fault_set.apply_cnot(control=3, target=1) \ No newline at end of file From 4fd044688423fe8177c36173fead218e5f5cf3fa Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 13:42:47 +0000 Subject: [PATCH 05/20] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mqt/qecc/circuit_synthesis/faults.py | 9 ++++++--- tests/circuit_synthesis/test_faults.py | 20 ++++++++++++++------ 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/src/mqt/qecc/circuit_synthesis/faults.py b/src/mqt/qecc/circuit_synthesis/faults.py index bac4355fa..ea98e6e28 100644 --- a/src/mqt/qecc/circuit_synthesis/faults.py +++ b/src/mqt/qecc/circuit_synthesis/faults.py @@ -43,10 +43,10 @@ def __init__(self, num_qubits: int, kind: str = "X") -> None: def kind(self) -> str: """Return the type of faults in the set ('X' or 'Z').""" return self._kind - + @kind.setter def kind(self, value: str) -> None: - """Set the type of faults in the set ('X' or 'Z').""" + """Set the type of faults in the set ('X' or 'Z').""" assert value.upper() in {"X", "Z"}, "Kind must be either 'X' or 'Z'." self._kind = value.upper() @@ -137,7 +137,9 @@ def from_cnot_circuit(cls, circ: CNOTCircuit, kind: str = "X", reduce: bool = Fa qubit_faults[ctrl].append(new_fault) # Create the fault set - fs = cls.from_fault_array(np.array([fault for faults in qubit_faults for fault in faults], dtype=np.int8), kind=kind) + fs = cls.from_fault_array( + np.array([fault for faults in qubit_faults for fault in faults], dtype=np.int8), kind=kind + ) if not reduce: return fs @@ -418,6 +420,7 @@ def apply_cnot(self, control: int, target: int, inplace: bool = True) -> PureFau return PureFaultSet.from_fault_array(updated_faults) + def coset_leader(fault: npt.NDArray[np.int8], generators: npt.NDArray[np.int8]) -> npt.NDArray[np.int8]: """Compute the coset leader of a fault given a set of stabilizer generators.""" if len(generators) == 0: diff --git a/tests/circuit_synthesis/test_faults.py b/tests/circuit_synthesis/test_faults.py index ab68861c0..38276a17e 100644 --- a/tests/circuit_synthesis/test_faults.py +++ b/tests/circuit_synthesis/test_faults.py @@ -643,7 +643,7 @@ def test_PureFaultSet_invalid_kind(): """Test that an invalid kind raises an assertion error.""" with pytest.raises(AssertionError, match=r"Kind must be either 'X' or 'Z'."): pfs = PureFaultSet(5, kind="Y") - + with pytest.raises(AssertionError, match=r"Kind must be either 'X' or 'Z'."): pfs = PureFaultSet.from_fault_array(np.array([[1, 0, 1]], dtype=np.int8), kind="Y") @@ -661,7 +661,9 @@ def test_apply_cnot_x(): fault_set1.apply_cnot(control=0, target=1) expected_faults1 = np.array([[1, 1, 0]], dtype=np.int8) - assert np.array_equal(fault_set1.to_array(), expected_faults1), "CNOT gate was not applied correctly to the fault set" + assert np.array_equal(fault_set1.to_array(), expected_faults1), ( + "CNOT gate was not applied correctly to the fault set" + ) faults2 = np.array([[0, 1, 0]], dtype=np.int8) fault_set2 = PureFaultSet.from_fault_array(faults2, kind="X") @@ -670,7 +672,9 @@ def test_apply_cnot_x(): fault_set2.apply_cnot(control=0, target=1) expected_faults2 = np.array([[0, 1, 0]], dtype=np.int8) - assert np.array_equal(fault_set2.to_array(), expected_faults2), "CNOT gate was not applied correctly to the fault set" + assert np.array_equal(fault_set2.to_array(), expected_faults2), ( + "CNOT gate was not applied correctly to the fault set" + ) def test_apply_cnot_z(): @@ -682,7 +686,9 @@ def test_apply_cnot_z(): fault_set1.apply_cnot(control=0, target=1) expected_faults1 = np.array([[1, 0, 0]], dtype=np.int8) - assert np.array_equal(fault_set1.to_array(), expected_faults1), "CNOT gate was not applied correctly to the fault set" + assert np.array_equal(fault_set1.to_array(), expected_faults1), ( + "CNOT gate was not applied correctly to the fault set" + ) faults2 = np.array([[0, 1, 0]], dtype=np.int8) fault_set2 = PureFaultSet.from_fault_array(faults2, kind="Z") @@ -691,7 +697,9 @@ def test_apply_cnot_z(): fault_set2.apply_cnot(control=0, target=1) expected_faults2 = np.array([[1, 1, 0]], dtype=np.int8) - assert np.array_equal(fault_set2.to_array(), expected_faults2), "CNOT gate was not applied correctly to the fault set" + assert np.array_equal(fault_set2.to_array(), expected_faults2), ( + "CNOT gate was not applied correctly to the fault set" + ) def test_apply_cnot_invalid_qubits(): @@ -703,4 +711,4 @@ def test_apply_cnot_invalid_qubits(): fault_set.apply_cnot(control=0, target=0) with pytest.raises(ValueError, match=r"Control and target indices must be between 0 and 2."): - fault_set.apply_cnot(control=3, target=1) \ No newline at end of file + fault_set.apply_cnot(control=3, target=1) From ab450e98ecabb161441ce3eb14fa480ace82fa26 Mon Sep 17 00:00:00 2001 From: Yudong Sun Date: Thu, 14 May 2026 15:55:20 +0200 Subject: [PATCH 06/20] add new `apply_cnot` function to changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60282b635..d78d5f349 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,7 @@ This project adheres to [Semantic Versioning], with the exception that minor rel - New `synthesize_clifford` function for general Clifford operation synthesis with configurable optimization strategies. ([#640]) ([**@pehamtom**]) - New `resynthesize_stim_circuit` function for optimizing existing Stim circuits by resynthesizing them with improved gate count or depth. ([#640]) ([**@pehamtom**]) - New `encoder_from_stabilizers_and_logicals` function for constructing encoding circuits from stabilizer and logical operator tableaux. ([#640]) ([**@pehamtom**]) +- New `apply_cnot` function to calculate the resulting `PureFaultSet` after a CNOT ([#690]) ([**@sunjerry019**]) ### Changed From 0498c8b45ef18881e7cbe7d76b12b45b2c066c4e Mon Sep 17 00:00:00 2001 From: Yudong Sun Date: Thu, 14 May 2026 16:39:28 +0200 Subject: [PATCH 07/20] new PureFaultSet in apply_cnot should return the correct kind --- src/mqt/qecc/circuit_synthesis/faults.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mqt/qecc/circuit_synthesis/faults.py b/src/mqt/qecc/circuit_synthesis/faults.py index ea98e6e28..1b6c81fdd 100644 --- a/src/mqt/qecc/circuit_synthesis/faults.py +++ b/src/mqt/qecc/circuit_synthesis/faults.py @@ -418,7 +418,7 @@ def apply_cnot(self, control: int, target: int, inplace: bool = True) -> PureFau self.faults = updated_faults return self - return PureFaultSet.from_fault_array(updated_faults) + return PureFaultSet.from_fault_array(updated_faults, kind = self.kind) def coset_leader(fault: npt.NDArray[np.int8], generators: npt.NDArray[np.int8]) -> npt.NDArray[np.int8]: From 3f67ea5c8a99f7ed73de657246956c7a8d7f7f37 Mon Sep 17 00:00:00 2001 From: Yudong Sun Date: Thu, 14 May 2026 16:44:26 +0200 Subject: [PATCH 08/20] Add a test for `inplace` parameter for apply_cnot Co-authored-by: Copilot --- tests/circuit_synthesis/test_faults.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/circuit_synthesis/test_faults.py b/tests/circuit_synthesis/test_faults.py index 38276a17e..5574566ce 100644 --- a/tests/circuit_synthesis/test_faults.py +++ b/tests/circuit_synthesis/test_faults.py @@ -712,3 +712,18 @@ def test_apply_cnot_invalid_qubits(): with pytest.raises(ValueError, match=r"Control and target indices must be between 0 and 2."): fault_set.apply_cnot(control=3, target=1) + + +def test_apply_cnot_not_inplace(): + """Test that applying a CNOT gate does not modify the original fault set when inplace=False.""" + faults = np.array([[1, 0, 0]], dtype=np.int8) + fault_set = PureFaultSet.from_fault_array(faults) + + # Apply CNOT with control=0 and target=1 without modifying the original fault set + new_fault_set = fault_set.apply_cnot(control=0, target=1, inplace=False) + + expected_new_faults = np.array([[1, 1, 0]], dtype=np.int8) + assert np.array_equal(new_fault_set.to_array(), expected_new_faults), ( + "CNOT gate was not applied correctly to the new fault set" + ) + assert np.array_equal(fault_set.to_array(), faults), "Original fault set should remain unchanged" \ No newline at end of file From 015f9913be914e405c31b6590ac4ddb78887ae8a Mon Sep 17 00:00:00 2001 From: Yudong Sun Date: Thu, 14 May 2026 16:46:32 +0200 Subject: [PATCH 09/20] code coverage: Add test for invalid dimensions when creating a PureFaultSet from a fault_array --- tests/circuit_synthesis/test_faults.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/circuit_synthesis/test_faults.py b/tests/circuit_synthesis/test_faults.py index 5574566ce..e9fff4e52 100644 --- a/tests/circuit_synthesis/test_faults.py +++ b/tests/circuit_synthesis/test_faults.py @@ -97,6 +97,13 @@ def test_from_fault_array(): # Check that the rows in the result match the expected rows, regardless of order assert set(map(tuple, result)) == set(map(tuple, faults)), "Fault set was not created correctly from array." +def test_from_fault_array_invalid_dimension(): + """Test creating a PureFaultSet from an array with invalid dimensions.""" + faults = np.array([1, 0, 1], dtype=np.int8) # 1D array instead of 2D + + with pytest.raises(ValueError, match=r"Input array must be 2-dimensional."): + PureFaultSet.from_fault_array(faults) + @pytest.mark.parametrize( ("stabs_fixture", "initial_faults", "expected_faults"), From 451da2dbb9117bc32a53304637f64890524fda0f Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 14 May 2026 14:47:18 +0000 Subject: [PATCH 10/20] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mqt/qecc/circuit_synthesis/faults.py | 2 +- tests/circuit_synthesis/test_faults.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/mqt/qecc/circuit_synthesis/faults.py b/src/mqt/qecc/circuit_synthesis/faults.py index 1b6c81fdd..7897c2dba 100644 --- a/src/mqt/qecc/circuit_synthesis/faults.py +++ b/src/mqt/qecc/circuit_synthesis/faults.py @@ -418,7 +418,7 @@ def apply_cnot(self, control: int, target: int, inplace: bool = True) -> PureFau self.faults = updated_faults return self - return PureFaultSet.from_fault_array(updated_faults, kind = self.kind) + return PureFaultSet.from_fault_array(updated_faults, kind=self.kind) def coset_leader(fault: npt.NDArray[np.int8], generators: npt.NDArray[np.int8]) -> npt.NDArray[np.int8]: diff --git a/tests/circuit_synthesis/test_faults.py b/tests/circuit_synthesis/test_faults.py index e9fff4e52..1cf4a2f48 100644 --- a/tests/circuit_synthesis/test_faults.py +++ b/tests/circuit_synthesis/test_faults.py @@ -97,6 +97,7 @@ def test_from_fault_array(): # Check that the rows in the result match the expected rows, regardless of order assert set(map(tuple, result)) == set(map(tuple, faults)), "Fault set was not created correctly from array." + def test_from_fault_array_invalid_dimension(): """Test creating a PureFaultSet from an array with invalid dimensions.""" faults = np.array([1, 0, 1], dtype=np.int8) # 1D array instead of 2D @@ -733,4 +734,4 @@ def test_apply_cnot_not_inplace(): assert np.array_equal(new_fault_set.to_array(), expected_new_faults), ( "CNOT gate was not applied correctly to the new fault set" ) - assert np.array_equal(fault_set.to_array(), faults), "Original fault set should remain unchanged" \ No newline at end of file + assert np.array_equal(fault_set.to_array(), faults), "Original fault set should remain unchanged" From 79d0164963c1ab949664e62b844a0558ae4b50d9 Mon Sep 17 00:00:00 2001 From: Yudong Sun Date: Mon, 18 May 2026 13:16:31 +0200 Subject: [PATCH 11/20] fix pre-commit check > ruff: make `test_PureFaultSet_invalid_kind` lowercase --- tests/circuit_synthesis/test_faults.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/circuit_synthesis/test_faults.py b/tests/circuit_synthesis/test_faults.py index e9fff4e52..4dffb65ac 100644 --- a/tests/circuit_synthesis/test_faults.py +++ b/tests/circuit_synthesis/test_faults.py @@ -646,7 +646,7 @@ def test_permute_qubits_inplace(): assert fault_set != PureFaultSet.from_fault_array(faults), "Faults were not permuted correctly in place" -def test_PureFaultSet_invalid_kind(): +def test_invalid_fault_kind(): """Test that an invalid kind raises an assertion error.""" with pytest.raises(AssertionError, match=r"Kind must be either 'X' or 'Z'."): pfs = PureFaultSet(5, kind="Y") From 71ceca0b353c8270b1741f2cf5bf599b985338d7 Mon Sep 17 00:00:00 2001 From: Yudong Sun Date: Mon, 18 May 2026 13:20:23 +0200 Subject: [PATCH 12/20] fix pre-commit check > ruff: `pytest.raises()` now only has a single statement --- tests/circuit_synthesis/test_faults.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/circuit_synthesis/test_faults.py b/tests/circuit_synthesis/test_faults.py index 4dffb65ac..f13a6f5c4 100644 --- a/tests/circuit_synthesis/test_faults.py +++ b/tests/circuit_synthesis/test_faults.py @@ -653,9 +653,9 @@ def test_invalid_fault_kind(): with pytest.raises(AssertionError, match=r"Kind must be either 'X' or 'Z'."): pfs = PureFaultSet.from_fault_array(np.array([[1, 0, 1]], dtype=np.int8), kind="Y") - + + pfs = PureFaultSet(5) with pytest.raises(AssertionError, match=r"Kind must be either 'X' or 'Z'."): - pfs = PureFaultSet(5) pfs.kind = "Y" From 234202987bd1ea0f00541083b768171779c33ea2 Mon Sep 17 00:00:00 2001 From: Yudong Sun Date: Fri, 22 May 2026 23:25:42 +0200 Subject: [PATCH 13/20] resolve merge conflict from main due to CHANGELOG.md --- CHANGELOG.md | 88 ---------------------------------------------------- 1 file changed, 88 deletions(-) delete mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index d78d5f349..000000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,88 +0,0 @@ - - -# Changelog - -All notable changes to this project will be documented in this file. - -The format is based on a mixture of [Keep a Changelog] and [Common Changelog]. -This project adheres to [Semantic Versioning], with the exception that minor releases may include breaking changes. - -## [Unreleased] - -### Added - -- New `MinimalCodeSwitchingCompiler` class that implements a compiler for minimal-overhead code switching on the logical level. ([#524], [arXiv:2512.04170](https://arxiv.org/abs/2512.04170)) ([**@inctechs**]) -- Added `gottesman_encoding_circuit` methods that constructs a stim encoding circuit for a given stabilizer code using the method described in Gottesman's "Surviving as a Quantum Computer in a Classical World" Chapter 6.4.1. ([#486]) ([**@pehamtom**]) -- Added class `SteaneNDFTStatePrepSimulator` for simulating non-deterministic state preparation protocols for CSS codes using verification with multiple ancilla states. ([#462]) ([**@pehamtom**]) -- Extended estimation of error rates in `NoisyNDFTStatePrepSimulator` via `secondary_logical_error_rate`. Now Z (X) error rates can also be estimated for the preparation of logical zero (plus). ([#462]) ([**@pehamtom**]) -- Added `ComposedNoiseModel` class that allows for composition of noise models. )([#462]) ([**@pehamtom**]) -- Added functionality to concatenate stim circuits along specific qubits. Add functionality to concatenate stim circuits along specific qubits ([#461]) ([**@pehamtom**]) -- Added `NoiseModel` class for applying noise to a given stim circuit. ([#453]) ([**@pehamtom**]) -- New `PureFaultSet` class for representing collections of X or Z faults. ([#443]) ([**@pehamtom**]) -- New `CNOTCircuit` class to serve as an intermediate representation during circuit synthesis for simplifying work with CSS encoding isometries. ([#443]) ([**@pehamtom**]) -- Combinatorial search methods for constructing fault-tolerant cat state preparation circuits. ([#543]) ([**@pehamtom**]) -- Lattice surgery compilation for the color code with and without movable logical qubits and layout optimization. ([#559]) ([**@LSHerzog**]) -- Extendable synthesis framework for Clifford encoding isometries supporting custom candidate generators and optimization strategies. ([#640]) ([**@pehamtom**]) -- Synthesis of non-CSS Clifford encoding isometries with support for arbitrary stabilizer codes. ([#640]) ([**@pehamtom**]) -- Rollout heuristics for improved gate-count and depth optimization in synthesized Clifford circuits. ([#640]) ([**@pehamtom**]) -- Z3-based exact synthesis methods for depth-optimal and gate-optimal encoding circuits for both CSS and non-CSS codes. ([#640]) ([**@pehamtom**]) -- New `synthesize_clifford` function for general Clifford operation synthesis with configurable optimization strategies. ([#640]) ([**@pehamtom**]) -- New `resynthesize_stim_circuit` function for optimizing existing Stim circuits by resynthesizing them with improved gate count or depth. ([#640]) ([**@pehamtom**]) -- New `encoder_from_stabilizers_and_logicals` function for constructing encoding circuits from stabilizer and logical operator tableaux. ([#640]) ([**@pehamtom**]) -- New `apply_cnot` function to calculate the resulting `PureFaultSet` after a CNOT ([#690]) ([**@sunjerry019**]) - -### Changed - -- Stop testing on x86 macOS systems ([#592]) ([**@denialhaag**]) -- Move Python tests from `test/python` to `tests`. ([#482]) ([**@denialhaag**]) -- `NoisyNDFTStatePrepSimulator` simulates generalized post-selection based state preparation protocols. Old functionality for simulating state preparation protocols post-selected on stabilizer measurements can be found in the class `VerificationNDFTStatePrepSimulator`. ([#462]) ([**@pehamtom**]) -- Refactored state preparation circuit synthesis code to utilize the new `PureFaultSet` and `CNOTCircuit` classes. ([#443]) ([**@pehamtom**]) -- Refactored encoding circuit synthesis code to utilize the new `PureFaultSet` and `CNOTCircuit` classes. ([#443]) ([**@pehamtom**]) -- Renamed `StatePrepCircuit` class to `FaultyStatePrepCircuit`, reflecting its new role in combining circuit and fault information. ([#443]) ([**@pehamtom**]) -- Changed the construction in `CatStatePreparationExperiment` to allow for ancillas with less qubits than the data cat state. -- Added resets to Cat state preparation circuits in `CatStatePreparationExperiment`. ([#652]) ([**@pehamtom**]) -- Unified encoding circuit and state preparation circuit synthesis into a common framework. ([#640]) ([**@pehamtom**]) - -### Removed - -- Drop support for Python 3.9 ([#503]) ([**@denialhaag**]) - -## [1.9.0] - 2025-03-14 - -_📚 Refer to the [GitHub Release Notes](https://github.com/munich-quantum-toolkit/qecc/releases) for previous changelogs._ - - - -[unreleased]: https://github.com/munich-quantum-toolkit/qecc/compare/v1.9.0...HEAD -[1.9.0]: https://github.com/munich-quantum-toolkit/qecc/releases/tag/v1.9.0 - - - -[#524]: https://github.com/munich-quantum-toolkit/qecc/pull/524 -[#592]: https://github.com/munich-quantum-toolkit/qecc/pull/592 -[#543]: https://github.com/munich-quantum-toolkit/qecc/pull/543 -[#503]: https://github.com/munich-quantum-toolkit/qecc/pull/503 -[#499]: https://github.com/munich-quantum-toolkit/qecc/pull/499 -[#486]: https://github.com/munich-quantum-toolkit/qecc/pull/486 -[#482]: https://github.com/munich-quantum-toolkit/qecc/pull/482 -[#462]: https://github.com/munich-quantum-toolkit/qecc/pull/462 -[#461]: https://github.com/munich-quantum-toolkit/qecc/pull/461 -[#453]: https://github.com/munich-quantum-toolkit/qecc/pull/453 -[#443]: https://github.com/munich-quantum-toolkit/qecc/pull/443 -[#559]: https://github.com/munich-quantum-toolkit/qecc/pull/559 -[#640]: https://github.com/munich-quantum-toolkit/qecc/pull/640 -[#652]: https://github.com/munich-quantum-toolkit/qecc/pull/652 - - - -[**@pehamtom**]: https://github.com/pehamtom -[**@denialhaag**]: https://github.com/denialhaag -[**@LSHerzog**]: https://github.com/LSHerzog/ -[**@inctechs**]: https://github.com/inctechs - - - -[Keep a Changelog]: https://keepachangelog.com/en/1.1.0/ -[Common Changelog]: https://common-changelog.org -[Semantic Versioning]: https://semver.org/spec/v2.0.0.html -[GitHub Release Notes]: https://github.com/munich-quantum-toolkit/qecc/releases From 58ea9544a223d9031ce09ec539e83bee753cfbc5 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 22 May 2026 21:27:21 +0000 Subject: [PATCH 14/20] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tests/circuit_synthesis/test_faults.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/circuit_synthesis/test_faults.py b/tests/circuit_synthesis/test_faults.py index 5aa7bc42b..9aa862405 100644 --- a/tests/circuit_synthesis/test_faults.py +++ b/tests/circuit_synthesis/test_faults.py @@ -654,7 +654,7 @@ def test_invalid_fault_kind(): with pytest.raises(AssertionError, match=r"Kind must be either 'X' or 'Z'."): pfs = PureFaultSet.from_fault_array(np.array([[1, 0, 1]], dtype=np.int8), kind="Y") - + pfs = PureFaultSet(5) with pytest.raises(AssertionError, match=r"Kind must be either 'X' or 'Z'."): pfs.kind = "Y" From 3984df5e82c013eaf8291c77a1ae168ac5872f9b Mon Sep 17 00:00:00 2001 From: Yudong Sun Date: Tue, 2 Jun 2026 14:14:36 +0200 Subject: [PATCH 15/20] fixes: `copy` does not propagate `kind` --- src/mqt/qecc/circuit_synthesis/faults.py | 2 +- tests/circuit_synthesis/test_faults.py | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/mqt/qecc/circuit_synthesis/faults.py b/src/mqt/qecc/circuit_synthesis/faults.py index 7897c2dba..8e92c9bcd 100644 --- a/src/mqt/qecc/circuit_synthesis/faults.py +++ b/src/mqt/qecc/circuit_synthesis/faults.py @@ -273,7 +273,7 @@ def copy(self) -> PureFaultSet: Returns: A new PureFaultSet object with the same faults and number of qubits. """ - new_set = PureFaultSet(self.num_qubits) + new_set = PureFaultSet(self.num_qubits, kind = self.kind) new_set.faults = np.copy(self.faults) return new_set diff --git a/tests/circuit_synthesis/test_faults.py b/tests/circuit_synthesis/test_faults.py index 9aa862405..bc54057f6 100644 --- a/tests/circuit_synthesis/test_faults.py +++ b/tests/circuit_synthesis/test_faults.py @@ -735,3 +735,21 @@ def test_apply_cnot_not_inplace(): "CNOT gate was not applied correctly to the new fault set" ) assert np.array_equal(fault_set.to_array(), faults), "Original fault set should remain unchanged" + + +def test_pure_fault_set_copy(): + """Test that PureFaultSet.copy() returns an independent copy.""" + faults = np.array([[1, 0, 0], [0, 1, 1]], dtype=np.int8) + fault_set = PureFaultSet.from_fault_array(faults, kind="Z") + + copied_fault_set = fault_set.copy() + + assert copied_fault_set is not fault_set + assert np.array_equal(copied_fault_set.to_array(), fault_set.to_array()) + assert copied_fault_set.kind == fault_set.kind + + copied_fault_set.apply_cnot(control=0, target=1) + + assert not np.array_equal(copied_fault_set.to_array(), fault_set.to_array()) + assert np.array_equal(fault_set.to_array(), np.unique(faults, axis=0)) + From d7cffabed632b109c47ec73c57b2eb839dfa09e2 Mon Sep 17 00:00:00 2001 From: Yudong Sun Date: Tue, 2 Jun 2026 14:31:43 +0200 Subject: [PATCH 16/20] fixes: filter_faults does not propagate kind when inplace=False. --- src/mqt/qecc/circuit_synthesis/faults.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mqt/qecc/circuit_synthesis/faults.py b/src/mqt/qecc/circuit_synthesis/faults.py index 8e92c9bcd..8877603e9 100644 --- a/src/mqt/qecc/circuit_synthesis/faults.py +++ b/src/mqt/qecc/circuit_synthesis/faults.py @@ -367,7 +367,7 @@ def filter_faults(self, pred: Callable[[npt.NDArray[np.int8]], bool], inplace: b self.faults = filtered return self - return PureFaultSet.from_fault_array(filtered) + return PureFaultSet.from_fault_array(filtered, kind = self.kind) def permute_qubits(self, permutation: npt.NDArray[np.int8] | list[int], inplace: bool = True) -> PureFaultSet: """Permute the qubits in the fault set according to a given permutation. From 3d909191453298bf7af106a544ee5c57bedd52cf Mon Sep 17 00:00:00 2001 From: Yudong Sun Date: Tue, 2 Jun 2026 14:43:13 +0200 Subject: [PATCH 17/20] fixes: combine does not propagate kind and ignores kind mismatch. --- src/mqt/qecc/circuit_synthesis/faults.py | 6 ++++- tests/circuit_synthesis/test_faults.py | 30 ++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/src/mqt/qecc/circuit_synthesis/faults.py b/src/mqt/qecc/circuit_synthesis/faults.py index 8877603e9..0cc9441fa 100644 --- a/src/mqt/qecc/circuit_synthesis/faults.py +++ b/src/mqt/qecc/circuit_synthesis/faults.py @@ -85,10 +85,14 @@ def combine(self, other: PureFaultSet, inplace: bool = False) -> PureFaultSet: raise ValueError(msg) combined_faults = np.vstack([self.faults, other.faults]) + if self.kind != other.kind: + msg = "Fault sets must have the same kind to combine." + raise ValueError(msg) + if inplace: self.faults = combined_faults return self - return PureFaultSet.from_fault_array(combined_faults) + return PureFaultSet.from_fault_array(combined_faults, kind=self.kind) def to_array(self) -> npt.NDArray[np.int8]: """Convert the fault set to a numpy array. diff --git a/tests/circuit_synthesis/test_faults.py b/tests/circuit_synthesis/test_faults.py index bc54057f6..ca03d6600 100644 --- a/tests/circuit_synthesis/test_faults.py +++ b/tests/circuit_synthesis/test_faults.py @@ -64,16 +64,42 @@ def test_add_fault_invalid_length(): def test_combine_fault_sets(): """Test combining two fault sets.""" - fault_set_1 = PureFaultSet(num_qubits=3) + fault_set_1 = PureFaultSet(num_qubits=3, kind="Z") fault_set_1.add_fault(np.array([1, 0, 1], dtype=np.int8)) - fault_set_2 = PureFaultSet(num_qubits=3) + fault_set_2 = PureFaultSet(num_qubits=3, kind="Z") fault_set_2.add_fault(np.array([0, 1, 0], dtype=np.int8)) # Combine the fault sets combined_fault_set = fault_set_1.combine(fault_set_2) expected = np.array([[1, 0, 1], [0, 1, 0]], dtype=np.int8) assert combined_fault_set.to_set() == set(map(tuple, expected)), "Fault sets were not combined correctly." + assert combined_fault_set.kind == "Z", "Fault kind was not preserved when combining fault sets." + + +def test_combine_fault_sets_different_kind(): + """Test combining two fault sets with different kinds.""" + fault_set_1 = PureFaultSet(num_qubits=3, kind="X") + fault_set_1.add_fault(np.array([1, 0, 1], dtype=np.int8)) + + fault_set_2 = PureFaultSet(num_qubits=3, kind="Z") + fault_set_2.add_fault(np.array([0, 1, 0], dtype=np.int8)) + + with pytest.raises(ValueError, match=r"Fault sets must have the same kind to combine."): + _ = fault_set_1.combine(fault_set_2) + + +def test_combine_fault_sets_inplace_false_propagates_kind(): + """Test that non-inplace combining preserves the left fault set kind.""" + fault_set_1 = PureFaultSet(num_qubits=3, kind="Z") + fault_set_1.add_fault(np.array([1, 0, 1], dtype=np.int8)) + + fault_set_2 = PureFaultSet(num_qubits=3, kind="Z") + fault_set_2.add_fault(np.array([0, 1, 0], dtype=np.int8)) + + combined_fault_set = fault_set_1.combine(fault_set_2, inplace=False) + assert combined_fault_set.kind == "Z", "Fault kind was not propagated for inplace=False combine." + assert fault_set_1.kind == "Z", "Original fault set kind should remain unchanged." def test_combine_fault_sets_invalid(): From 98d32b6cd394cbeadb726ee26638a0599c0da6c5 Mon Sep 17 00:00:00 2001 From: Yudong Sun Date: Tue, 2 Jun 2026 14:47:25 +0200 Subject: [PATCH 18/20] fixes: permute_qubits does not propagate kind when inplace=False. AND __eq__ also checks kind --- src/mqt/qecc/circuit_synthesis/faults.py | 4 ++-- tests/circuit_synthesis/test_faults.py | 10 ++++++---- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/src/mqt/qecc/circuit_synthesis/faults.py b/src/mqt/qecc/circuit_synthesis/faults.py index 0cc9441fa..dc126ba5c 100644 --- a/src/mqt/qecc/circuit_synthesis/faults.py +++ b/src/mqt/qecc/circuit_synthesis/faults.py @@ -261,7 +261,7 @@ def __eq__(self, other: object) -> bool: """ if not isinstance(other, PureFaultSet): return False - return self.num_qubits == other.num_qubits and self.to_set() == other.to_set() + return self.num_qubits == other.num_qubits and self.to_set() == other.to_set() and self.kind == other.kind def __hash__(self) -> int: """Return a hash of the PureFaultSet. @@ -392,7 +392,7 @@ def permute_qubits(self, permutation: npt.NDArray[np.int8] | list[int], inplace: self.faults = permuted_faults return self - return PureFaultSet.from_fault_array(permuted_faults) + return PureFaultSet.from_fault_array(permuted_faults, kind = self.kind) def apply_cnot(self, control: int, target: int, inplace: bool = True) -> PureFaultSet: """Apply a CNOT gate to the faults in the set, based on the type of faults (X or Z). diff --git a/tests/circuit_synthesis/test_faults.py b/tests/circuit_synthesis/test_faults.py index ca03d6600..734d1d15d 100644 --- a/tests/circuit_synthesis/test_faults.py +++ b/tests/circuit_synthesis/test_faults.py @@ -653,24 +653,26 @@ def test_not_t_distinct_four_qubits(): def test_permute_qubits_basic(): """Test basic permutation of faults.""" faults = np.array([[1, 1, 0], [0, 1, 1]], dtype=np.int8) - fault_set = PureFaultSet.from_fault_array(faults) + fault_set = PureFaultSet.from_fault_array(faults, kind="Z") permutation = [2, 0, 1] permuted_fault_set = fault_set.permute_qubits(permutation, inplace=False) assert np.array_equal(permuted_fault_set.faults, faults[:, permutation]), "Faults were not permuted correctly" - assert fault_set == PureFaultSet.from_fault_array(faults), "Original fault set should remain unchanged" + assert fault_set == PureFaultSet.from_fault_array(faults, kind="Z"), "Original fault set should remain unchanged" + assert permuted_fault_set.kind == "Z", "Fault kind should be preserved after permutation" + assert fault_set.kind == "Z", "Original fault kind should be preserved after permutation" def test_permute_qubits_inplace(): """Test inplace permutation of fault set.""" faults = np.array([[1, 1, 0], [0, 0, 1]], dtype=np.int8) - fault_set = PureFaultSet.from_fault_array(faults) + fault_set = PureFaultSet.from_fault_array(faults, kind="Z") permutation = [2, 0, 1] fault_set.permute_qubits(permutation, inplace=True) - assert fault_set != PureFaultSet.from_fault_array(faults), "Faults were not permuted correctly in place" + assert fault_set != PureFaultSet.from_fault_array(faults, kind="Z"), "Faults were not permuted correctly in place" def test_invalid_fault_kind(): From 3d34523c83c925f50eec0328f8d0a412858348d2 Mon Sep 17 00:00:00 2001 From: Yudong Sun Date: Tue, 2 Jun 2026 15:51:57 +0200 Subject: [PATCH 19/20] fixes: Missing validation for negative qubit indices --- src/mqt/qecc/circuit_synthesis/faults.py | 3 ++- tests/circuit_synthesis/test_faults.py | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/mqt/qecc/circuit_synthesis/faults.py b/src/mqt/qecc/circuit_synthesis/faults.py index dc126ba5c..d86ed7337 100644 --- a/src/mqt/qecc/circuit_synthesis/faults.py +++ b/src/mqt/qecc/circuit_synthesis/faults.py @@ -405,9 +405,10 @@ def apply_cnot(self, control: int, target: int, inplace: bool = True) -> PureFau Returns: A new PureFaultSet with updated faults if inplace is False. """ - if control >= self.num_qubits or target >= self.num_qubits: + if not (0 <= control < self.num_qubits) or not (0 <= target < self.num_qubits): msg = f"Control and target indices must be between 0 and {self.num_qubits - 1}." raise ValueError(msg) + # Dev Note: We do not allow negative indices so that we can easily check if control and target are different if control == target: msg = "Control and target qubits must be different." raise ValueError(msg) diff --git a/tests/circuit_synthesis/test_faults.py b/tests/circuit_synthesis/test_faults.py index 734d1d15d..5413d32fb 100644 --- a/tests/circuit_synthesis/test_faults.py +++ b/tests/circuit_synthesis/test_faults.py @@ -749,6 +749,9 @@ def test_apply_cnot_invalid_qubits(): with pytest.raises(ValueError, match=r"Control and target indices must be between 0 and 2."): fault_set.apply_cnot(control=3, target=1) + with pytest.raises(ValueError, match=r"Control and target indices must be between 0 and 2."): + fault_set.apply_cnot(control=-1, target=1) + def test_apply_cnot_not_inplace(): """Test that applying a CNOT gate does not modify the original fault set when inplace=False.""" From 201bf814e479dd4c280305e96078cef62bcd0cec Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 13:54:32 +0000 Subject: [PATCH 20/20] =?UTF-8?q?=F0=9F=8E=A8=20pre-commit=20fixes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/mqt/qecc/circuit_synthesis/faults.py | 6 +++--- tests/circuit_synthesis/test_faults.py | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/mqt/qecc/circuit_synthesis/faults.py b/src/mqt/qecc/circuit_synthesis/faults.py index d86ed7337..f227e62ba 100644 --- a/src/mqt/qecc/circuit_synthesis/faults.py +++ b/src/mqt/qecc/circuit_synthesis/faults.py @@ -277,7 +277,7 @@ def copy(self) -> PureFaultSet: Returns: A new PureFaultSet object with the same faults and number of qubits. """ - new_set = PureFaultSet(self.num_qubits, kind = self.kind) + new_set = PureFaultSet(self.num_qubits, kind=self.kind) new_set.faults = np.copy(self.faults) return new_set @@ -371,7 +371,7 @@ def filter_faults(self, pred: Callable[[npt.NDArray[np.int8]], bool], inplace: b self.faults = filtered return self - return PureFaultSet.from_fault_array(filtered, kind = self.kind) + return PureFaultSet.from_fault_array(filtered, kind=self.kind) def permute_qubits(self, permutation: npt.NDArray[np.int8] | list[int], inplace: bool = True) -> PureFaultSet: """Permute the qubits in the fault set according to a given permutation. @@ -392,7 +392,7 @@ def permute_qubits(self, permutation: npt.NDArray[np.int8] | list[int], inplace: self.faults = permuted_faults return self - return PureFaultSet.from_fault_array(permuted_faults, kind = self.kind) + return PureFaultSet.from_fault_array(permuted_faults, kind=self.kind) def apply_cnot(self, control: int, target: int, inplace: bool = True) -> PureFaultSet: """Apply a CNOT gate to the faults in the set, based on the type of faults (X or Z). diff --git a/tests/circuit_synthesis/test_faults.py b/tests/circuit_synthesis/test_faults.py index 5413d32fb..8f972dfb3 100644 --- a/tests/circuit_synthesis/test_faults.py +++ b/tests/circuit_synthesis/test_faults.py @@ -783,4 +783,3 @@ def test_pure_fault_set_copy(): assert not np.array_equal(copied_fault_set.to_array(), fault_set.to_array()) assert np.array_equal(fault_set.to_array(), np.unique(faults, axis=0)) -