diff --git a/src/qutip_qip/algorithms/phase_flip.py b/src/qutip_qip/algorithms/phase_flip.py index d12056869..62319e9ef 100644 --- a/src/qutip_qip/algorithms/phase_flip.py +++ b/src/qutip_qip/algorithms/phase_flip.py @@ -40,9 +40,8 @@ def encode_circuit(self, data_qubits): """ Constructs the encoding circuit for the phase-flip code. - The logical qubit is encoded into an entangled state in the X-basis using Hadamard - (SNOT) gates followed by two CNOT gates. This creates redundancy to detect and correct - a single phase error. + The logical qubit is first encoded by two CNOT gates and then converted to the X-basis + using Hadamard (H). This creates redundancy to detect and correct a single phase error. Args: data_qubits (list[int]): Indices of 3 data qubits. @@ -57,22 +56,23 @@ def encode_circuit(self, data_qubits): raise ValueError("Expected 3 data qubits.") qc = QubitCircuit(max(data_qubits) + 1) - # Convert to X-basis - for q in data_qubits: - qc.add_gate("SNOT", targets=[q]) - # Bit-flip-style encoding control = data_qubits[0] for target in data_qubits[1:]: qc.add_gate("CNOT", controls=control, targets=target) + # Convert to X-basis + for q in data_qubits: + qc.add_gate("H", targets=[q]) + return qc def syndrome_and_correction_circuit(self, data_qubits, syndrome_qubits): """ Builds the circuit for syndrome extraction and correction. - Parity is measured between data qubit pairs using ancillas and CNOT gates. + The data qubits are temporarily converted back to the Z-basis so parity + can be measured between pairs using ancillas and CNOT gates. Measurements are stored in classical bits, and Z corrections are applied conditionally based on the measured syndrome. @@ -95,12 +95,20 @@ def syndrome_and_correction_circuit(self, data_qubits, syndrome_qubits): dq = data_qubits sq = syndrome_qubits + # Convert back from X-basis + for q in data_qubits: + qc.add_gate("H", targets=[q]) + # Parity checks qc.add_gate("CNOT", controls=dq[0], targets=sq[0]) qc.add_gate("CNOT", controls=dq[1], targets=sq[0]) qc.add_gate("CNOT", controls=dq[1], targets=sq[1]) qc.add_gate("CNOT", controls=dq[2], targets=sq[1]) + # Convert to X-basis + for q in data_qubits: + qc.add_gate("H", targets=[q]) + # Measure syndrome qubits qc.add_measurement(sq[0], sq[0], classical_store=0) qc.add_measurement(sq[1], sq[1], classical_store=1) @@ -131,8 +139,9 @@ def decode_circuit(self, data_qubits): """ Constructs the decoding circuit that reverses the encoding operation. - It first applies the inverse of the CNOT encoding, then converts the qubits - back from the X-basis to the Z-basis using Hadamard (SNOT) gates. + It first applies the Hadamard (H) gates to convert the qubits back from + the X-basis to the Z-basis, then applies the inverse of the CNOT encoding to + decode the qubits. Args: data_qubits (list[int]): Indices of 3 data qubits. @@ -147,12 +156,12 @@ def decode_circuit(self, data_qubits): raise ValueError("Expected 3 data qubits.") qc = QubitCircuit(max(data_qubits) + 1) - control = data_qubits[0] - for target in reversed(data_qubits[1:]): - qc.add_gate("CNOT", controls=control, targets=target) - # Convert back from X-basis for q in data_qubits: - qc.add_gate("SNOT", targets=[q]) + qc.add_gate("H", targets=[q]) + + control = data_qubits[0] + for target in data_qubits[:0:-1]: + qc.add_gate("CNOT", controls=control, targets=target) return qc diff --git a/tests/test_phase_flip.py b/tests/test_phase_flip.py index cea93af20..8471ff0f6 100644 --- a/tests/test_phase_flip.py +++ b/tests/test_phase_flip.py @@ -1,5 +1,6 @@ import pytest import qutip +import numpy as np from qutip_qip.circuit import QubitCircuit from qutip_qip.algorithms import PhaseFlipCode @@ -22,33 +23,38 @@ def syndrome_qubits(): def test_encode_circuit_structure(code, data_qubits): qc = code.encode_circuit(data_qubits) gate_names = [g.name for g in qc.gates] - assert gate_names.count("SNOT") == 3 + assert gate_names.count("H") == 3 assert gate_names.count("CNOT") == 2 - assert qc.gates[3].controls == [0] - assert qc.gates[3].targets == [1] - assert qc.gates[4].controls == [0] - assert qc.gates[4].targets == [2] + assert qc.gates[0].controls == [0] + assert qc.gates[0].targets == [1] + assert qc.gates[1].controls == [0] + assert qc.gates[1].targets == [2] def test_decode_circuit_structure(code, data_qubits): qc = code.decode_circuit(data_qubits) gate_names = [g.name for g in qc.gates] assert gate_names.count("CNOT") == 2 - assert gate_names.count("SNOT") == 3 - assert qc.gates[0].controls == [0] - assert qc.gates[0].targets == [2] - assert qc.gates[1].controls == [0] - assert qc.gates[1].targets == [1] + assert gate_names.count("H") == 3 + assert qc.gates[3].controls == [0] + assert qc.gates[3].targets == [2] + assert qc.gates[4].controls == [0] + assert qc.gates[4].targets == [1] -@pytest.mark.xfail(reason="Known error in phase flip code. See Issue #283") -def test_phaseflip_correction_simulation(code, data_qubits, syndrome_qubits): +@pytest.mark.parametrize("seed", [42, 123, 777, 1337, 9999]) +@pytest.mark.parametrize("error_qubit", [None, 0, 1, 2]) +def test_phaseflip_correction_simulation( + code, data_qubits, syndrome_qubits, error_qubit, seed +): """ - Simulate the full encoding, Z-error, correction, and decoding process - for a random qubit state |ψ⟩. Fidelity after correction should be ~1. + Simulate the full encoding, Z-error, correction, and decoding process. + Test across all possible single-qubit errors and multiple random initial states. """ + rng = np.random.default_rng(seed) + # Random initial qubit state - psi = qutip.rand_ket(2) + psi = qutip.rand_ket(2, seed=rng) # Full system: qubit + 2 redundant qubits + 2 ancillas state = qutip.tensor([psi] + [qutip.basis(2, 0)] * 4) @@ -57,10 +63,11 @@ def test_phaseflip_correction_simulation(code, data_qubits, syndrome_qubits): qc_encode = code.encode_circuit(data_qubits) state = qc_encode.run(state) - # Apply Z (phase-flip) error to qubit 1 - qc_error = QubitCircuit(num_qubits=5) - qc_error.add_gate("Z", targets=[1]) - state = qc_error.run(state) + # Inject error + if error_qubit is not None: + qc_error = QubitCircuit(num_qubits=5) + qc_error.add_gate("Z", targets=[error_qubit]) + state = qc_error.run(state) # Syndrome measurement and Z correction qc_correct = code.syndrome_and_correction_circuit( @@ -76,4 +83,7 @@ def test_phaseflip_correction_simulation(code, data_qubits, syndrome_qubits): final = state.ptrace(0) fidelity = qutip.fidelity(psi, final) - assert fidelity > 0.99, f"Fidelity too low: {fidelity:.4f}" + assert fidelity > 0.99, ( + f"Failed on random seed {seed} on error qubit {error_qubit}. " + f"Fidelity: {fidelity:.4f}" + )