diff --git a/src/qutip_qip/algorithms/__init__.py b/src/qutip_qip/algorithms/__init__.py index 8bdea8f7..010b80ed 100644 --- a/src/qutip_qip/algorithms/__init__.py +++ b/src/qutip_qip/algorithms/__init__.py @@ -1,2 +1,5 @@ from .qft import * from .qpe import * +from .error_correction.bit_flip import * +from .error_correction.phase_flip import * +from .error_correction.shor_code import * diff --git a/src/qutip_qip/algorithms/error_correction/bit_flip.py b/src/qutip_qip/algorithms/error_correction/bit_flip.py new file mode 100644 index 00000000..b4d3a0b8 --- /dev/null +++ b/src/qutip_qip/algorithms/error_correction/bit_flip.py @@ -0,0 +1,112 @@ +# bit_flip_code.py + +import numpy as np +from qutip import Qobj, tensor, basis, sigmax, sigmay +from qutip_qip.operations import Gate +from qutip_qip.circuit import QubitCircuit + +__all__ = ["BitFlipCode"] + + +class BitFlipCode: + """ + Implementation of the 3-qubit bit-flip code. + + The bit-flip code protects against X (bit-flip) errors by encoding + a single logical qubit across three physical qubits: + |0⟩ → |000⟩ + |1⟩ → |111⟩ + """ + + @staticmethod + def encode_circuit(): + """ + Create a circuit for encoding a single qubit into the 3-qubit bit-flip code. + + Returns + ------- + qc : instance of QubitCircuit + Encoding circuit for the bit-flip code. + """ + qc = QubitCircuit(3) + qc.add_gate("CNOT", controls=0, targets=1) + qc.add_gate("CNOT", controls=0, targets=2) + return qc + + @staticmethod + def syndrome_measurement_circuit(): + """ + Create a circuit for syndrome measurement of the 3-qubit bit-flip code. + + Returns + ------- + qc : instance of QubitCircuit + Syndrome measurement circuit that uses two ancilla qubits. + """ + qc = QubitCircuit(5) # 3 data qubits + 2 syndrome qubits + + # First syndrome measurement: Parity of qubits 0 and 1 + qc.add_gate("CNOT", controls=0, targets=3) + qc.add_gate("CNOT", controls=1, targets=3) + + # Second syndrome measurement: Parity of qubits 1 and 2 + qc.add_gate("CNOT", controls=1, targets=4) + qc.add_gate("CNOT", controls=2, targets=4) + + return qc + + @staticmethod + def correction_circuit(syndrome): + """ + Create a circuit for error correction based on syndrome measurement. + + Parameters + ---------- + syndrome : tuple + Two-bit syndrome measurement result (s1, s2). + + Returns + ------- + qc : instance of QubitCircuit + Correction circuit applying X gates as needed. + """ + qc = QubitCircuit(3) + s1, s2 = syndrome + + # Syndrome interpretation: + # s1=0, s2=0: No error + # s1=1, s2=0: Error on qubit 0 + # s1=1, s2=1: Error on qubit 1 + # s1=0, s2=1: Error on qubit 2 + + if s1 == 1 and s2 == 0: + # Error on qubit 0 + qc.add_gate("X", targets=0) + elif s1 == 1 and s2 == 1: + # Error on qubit 1 + qc.add_gate("X", targets=1) + elif s1 == 0 and s2 == 1: + # Error on qubit 2 + qc.add_gate("X", targets=2) + + return qc + + @staticmethod + def decode_circuit(): + """ + Create a circuit for decoding the 3-qubit bit-flip code. + + Returns + ------- + qc : instance of QubitCircuit + Decoding circuit for the bit-flip code. + """ + qc = QubitCircuit(3) + qc.add_gate("CNOT", controls=0, targets=2) + qc.add_gate("CNOT", controls=0, targets=1) + + # Add a Toffoli gate to verify the parity + # If all qubits have the same value, the result is stored in qubit 0 + qc.add_gate("TOFFOLI", controls=[1, 2], targets=0) + + return qc diff --git a/src/qutip_qip/algorithms/error_correction/phase_flip.py b/src/qutip_qip/algorithms/error_correction/phase_flip.py new file mode 100644 index 00000000..c591bfc7 --- /dev/null +++ b/src/qutip_qip/algorithms/error_correction/phase_flip.py @@ -0,0 +1,131 @@ +import numpy as np +from qutip import Qobj, tensor, basis, sigmax, sigmay, sigmaz +from qutip_qip.operations import Gate +from qutip_qip.circuit import QubitCircuit + +__all__ = ["PhaseFlipCode"] + + +class PhaseFlipCode: + """ + Implementation of the 3-qubit phase-flip code. + The phase-flip code protects against Z (phase-flip) errors by encoding + a single logical qubit across three physical qubits: + |0⟩ → |+++⟩ + |1⟩ → |---⟩ + + This is accomplished by applying Hadamard gates to transform the bit-flip code + into the phase-flip code, as phase errors in the Hadamard basis appear as bit-flips. + """ + + @staticmethod + def encode_circuit(): + """ + Create a circuit for encoding a single qubit into the 3-qubit phase-flip code. + + Returns + ------- + qc : instance of QubitCircuit + Encoding circuit for the phase-flip code. + """ + qc = QubitCircuit(3) + + # First apply Hadamard to the input qubit + qc.add_gate("SNOT", targets=0) + + # Then use the bit-flip encoding structure + qc.add_gate("CNOT", controls=0, targets=1) + qc.add_gate("CNOT", controls=0, targets=2) + + # Apply Hadamard gates to all qubits + qc.add_gate("SNOT", targets=0) + qc.add_gate("SNOT", targets=1) + qc.add_gate("SNOT", targets=2) + + return qc + + @staticmethod + def syndrome_measurement_circuit(): + """ + Create a circuit for syndrome measurement of the 3-qubit phase-flip code. + + Returns + ------- + qc : instance of QubitCircuit + Syndrome measurement circuit that uses two ancilla qubits. + """ + qc = QubitCircuit(5) # 3 data qubits + 2 syndrome qubits + + qc.add_gate("SNOT", targets=0) + qc.add_gate("SNOT", targets=1) + qc.add_gate("SNOT", targets=2) + + qc.add_gate("CNOT", controls=0, targets=3) + qc.add_gate("CNOT", controls=1, targets=3) + + qc.add_gate("CNOT", controls=1, targets=4) + qc.add_gate("CNOT", controls=2, targets=4) + + qc.add_gate("SNOT", targets=0) + qc.add_gate("SNOT", targets=1) + qc.add_gate("SNOT", targets=2) + + return qc + + @staticmethod + def correction_circuit(syndrome): + """ + Create a circuit for error correction based on syndrome measurement. + + Parameters + ---------- + syndrome : tuple + Two-bit syndrome measurement result (s1, s2). + + Returns + ------- + qc : instance of QubitCircuit + Correction circuit applying Z gates as needed. + """ + qc = QubitCircuit(3) + s1, s2 = syndrome + + # Syndrome interpretation: + # s1=0, s2=0: No error + # s1=1, s2=0: Error on qubit 0 + # s1=1, s2=1: Error on qubit 1 + # s1=0, s2=1: Error on qubit 2 + + if s1 == 1 and s2 == 0: + qc.add_gate("Z", targets=0) + elif s1 == 1 and s2 == 1: + qc.add_gate("Z", targets=1) + elif s1 == 0 and s2 == 1: + qc.add_gate("Z", targets=2) + + return qc + + @staticmethod + def decode_circuit(): + """ + Create a circuit for decoding the 3-qubit phase-flip code. + + Returns + ------- + qc : instance of QubitCircuit + Decoding circuit for the phase-flip code. + """ + qc = QubitCircuit(3) + + qc.add_gate("SNOT", targets=0) + qc.add_gate("SNOT", targets=1) + qc.add_gate("SNOT", targets=2) + + qc.add_gate("CNOT", controls=0, targets=2) + qc.add_gate("CNOT", controls=0, targets=1) + + qc.add_gate("TOFFOLI", controls=[1, 2], targets=0) + + qc.add_gate("SNOT", targets=0) + + return qc diff --git a/src/qutip_qip/algorithms/error_correction/shor_code.py b/src/qutip_qip/algorithms/error_correction/shor_code.py new file mode 100644 index 00000000..735a9864 --- /dev/null +++ b/src/qutip_qip/algorithms/error_correction/shor_code.py @@ -0,0 +1,66 @@ +import numpy as np +from qutip import Qobj, tensor, basis, sigmax, sigmay, sigmaz +from qutip_qip.operations import Gate +from qutip_qip.circuit import QubitCircuit + +__all__ = ["ShorCode"] + + +class ShorCode: + """ + Implementation of the 9-qubit Shor code. + + The Shor code protects against arbitrary single-qubit errors by combining + the 3-qubit phase-flip code with the 3-qubit bit-flip code. + + The logical states are encoded as: + |0⟩ → (|000⟩ + |111⟩)(|000⟩ + |111⟩)(|000⟩ + |111⟩) / 2√2 + |1⟩ → (|000⟩ - |111⟩)(|000⟩ - |111⟩)(|000⟩ - |111⟩) / 2√2 + + This encoding allows correction of any single-qubit error (bit-flip, phase-flip, + or both) on any of the 9 physical qubits. + """ + + @staticmethod + def encode_circuit(): + """ + Create a circuit for encoding a single qubit into the 9-qubit Shor code. + + The encoding process: + 1. Apply phase-flip encoding to the input (creating |+++⟩ or |---⟩) + 2. Apply bit-flip encoding to each of the three qubits + + Returns + ------- + qc : instance of QubitCircuit + Encoding circuit for the Shor code. + """ + qc = QubitCircuit(9) + + # Step 1: Phase-flip encoding on the first qubit + # Apply Hadamard to the input qubit + qc.add_gate("SNOT", targets=0) + + # Create the GHZ-like state by using CNOTs + qc.add_gate("CNOT", controls=0, targets=3) + qc.add_gate("CNOT", controls=0, targets=6) + + # Apply Hadamard to all three qubits + qc.add_gate("SNOT", targets=0) + qc.add_gate("SNOT", targets=3) + qc.add_gate("SNOT", targets=6) + + # Step 2: Bit-flip encoding for each of the three blocks + # First block: qubits 0,1,2 + qc.add_gate("CNOT", controls=0, targets=1) + qc.add_gate("CNOT", controls=0, targets=2) + + # Second block: qubits 3,4,5 + qc.add_gate("CNOT", controls=3, targets=4) + qc.add_gate("CNOT", controls=3, targets=5) + + # Third block: qubits 6,7,8 + qc.add_gate("CNOT", controls=6, targets=7) + qc.add_gate("CNOT", controls=6, targets=8) + + return qc diff --git a/tests/test_bit_flip.py b/tests/test_bit_flip.py new file mode 100644 index 00000000..cd7e23ac --- /dev/null +++ b/tests/test_bit_flip.py @@ -0,0 +1,89 @@ +import pytest +import numpy as np +from qutip_qip.algorithms import BitFlipCode + + +def test_encode_circuit_structure(): + """Test that the encoding circuit has the correct structure.""" + qc = BitFlipCode.encode_circuit() + + # Check circuit has correct number of qubits + assert qc.N == 3 + + # Check it has 2 CNOT gates + assert len(qc.gates) == 2 + assert all(gate.name == "CNOT" for gate in qc.gates) + + # Check gate connections - QubitCircuit uses lists for controls and targets + assert qc.gates[0].controls == [0] + assert qc.gates[0].targets[0] == 1 + assert qc.gates[1].controls == [0] + assert qc.gates[1].targets[0] == 2 + + +def test_syndrome_measurement_circuit_structure(): + """Test that the syndrome measurement circuit has the correct structure.""" + qc = BitFlipCode.syndrome_measurement_circuit() + + # Check circuit has correct number of qubits (3 data + 2 syndrome) + assert qc.N == 5 + + # Check it has 4 CNOT gates + assert len(qc.gates) == 4 + assert all(gate.name == "CNOT" for gate in qc.gates) + + # Check gate connections for syndrome measurement + assert qc.gates[0].controls == [0] and qc.gates[0].targets[0] == 3 + assert qc.gates[1].controls == [1] and qc.gates[1].targets[0] == 3 + assert qc.gates[2].controls == [1] and qc.gates[2].targets[0] == 4 + assert qc.gates[3].controls == [2] and qc.gates[3].targets[0] == 4 + + +def test_correction_circuit_no_error(): + """Test correction circuit with no error (syndrome 00).""" + qc = BitFlipCode.correction_circuit((0, 0)) + assert qc.N == 3 + assert len(qc.gates) == 0 # No correction needed + + +def test_correction_circuit_qubit0_error(): + """Test correction circuit with error on qubit 0 (syndrome 10).""" + qc = BitFlipCode.correction_circuit((1, 0)) + assert qc.N == 3 + assert len(qc.gates) == 1 + assert qc.gates[0].name == "X" and qc.gates[0].targets[0] == 0 + + +def test_correction_circuit_qubit1_error(): + """Test correction circuit with error on qubit 1 (syndrome 11).""" + qc = BitFlipCode.correction_circuit((1, 1)) + assert qc.N == 3 + assert len(qc.gates) == 1 + assert qc.gates[0].name == "X" and qc.gates[0].targets[0] == 1 + + +def test_correction_circuit_qubit2_error(): + """Test correction circuit with error on qubit 2 (syndrome 01).""" + qc = BitFlipCode.correction_circuit((0, 1)) + assert qc.N == 3 + assert len(qc.gates) == 1 + assert qc.gates[0].name == "X" and qc.gates[0].targets[0] == 2 + + +def test_decode_circuit_structure(): + """Test that the decoding circuit has the correct structure.""" + qc = BitFlipCode.decode_circuit() + + # Check circuit has correct number of qubits + assert qc.N == 3 + + # Check it has 2 CNOT gates and 1 TOFFOLI gate + assert len(qc.gates) == 3 + assert qc.gates[0].name == "CNOT" + assert qc.gates[1].name == "CNOT" + assert qc.gates[2].name == "TOFFOLI" + + # Check gate connections + assert qc.gates[0].controls == [0] and qc.gates[0].targets[0] == 2 + assert qc.gates[1].controls == [0] and qc.gates[1].targets[0] == 1 + assert qc.gates[2].controls == [1, 2] and qc.gates[2].targets[0] == 0 diff --git a/tests/test_phase_flip.py b/tests/test_phase_flip.py new file mode 100644 index 00000000..f3005186 --- /dev/null +++ b/tests/test_phase_flip.py @@ -0,0 +1,134 @@ +import pytest +import numpy as np +from qutip import Qobj, tensor, basis, fidelity +from qutip_qip.algorithms import PhaseFlipCode + + +def test_encode_circuit_structure(): + """Test that the encoding circuit has the correct structure.""" + qc = PhaseFlipCode.encode_circuit() + + # Check circuit has correct number of qubits + assert qc.N == 3 + + # Check it has correct number of gates (1 H + 2 CNOT + 3 H = 6 gates) + assert len(qc.gates) == 6 + + # Check first gate is Hadamard on qubit 0 + assert qc.gates[0].name == "SNOT" + assert qc.gates[0].targets[0] == 0 + + # Check CNOT gates + assert qc.gates[1].name == "CNOT" + assert qc.gates[1].controls == [0] + assert qc.gates[1].targets[0] == 1 + + assert qc.gates[2].name == "CNOT" + assert qc.gates[2].controls == [0] + assert qc.gates[2].targets[0] == 2 + + # Check final Hadamard gates on all qubits + assert qc.gates[3].name == "SNOT" + assert qc.gates[3].targets[0] == 0 + + assert qc.gates[4].name == "SNOT" + assert qc.gates[4].targets[0] == 1 + + assert qc.gates[5].name == "SNOT" + assert qc.gates[5].targets[0] == 2 + + +def test_syndrome_measurement_circuit_structure(): + """Test that the syndrome measurement circuit has the correct structure.""" + qc = PhaseFlipCode.syndrome_measurement_circuit() + + # Check circuit has correct number of qubits (3 data + 2 syndrome) + assert qc.N == 5 + + # Check it has the correct number of gates (3 H + 4 CNOT + 3 H = 10 gates) + assert len(qc.gates) == 10 + + # Check first three Hadamard gates on data qubits + for i in range(3): + assert qc.gates[i].name == "SNOT" + assert qc.gates[i].targets[0] == i + + # Check CNOT gates for syndrome measurement + assert qc.gates[3].name == "CNOT" + assert qc.gates[3].controls == [0] and qc.gates[3].targets[0] == 3 + + assert qc.gates[4].name == "CNOT" + assert qc.gates[4].controls == [1] and qc.gates[4].targets[0] == 3 + + assert qc.gates[5].name == "CNOT" + assert qc.gates[5].controls == [1] and qc.gates[5].targets[0] == 4 + + assert qc.gates[6].name == "CNOT" + assert qc.gates[6].controls == [2] and qc.gates[6].targets[0] == 4 + + # Check final three Hadamard gates on data qubits + for i in range(3): + assert qc.gates[7 + i].name == "SNOT" + assert qc.gates[7 + i].targets[0] == i + + +def test_correction_circuit_no_error(): + """Test correction circuit with no error (syndrome 00).""" + qc = PhaseFlipCode.correction_circuit((0, 0)) + assert qc.N == 3 + assert len(qc.gates) == 0 # No correction needed + + +def test_correction_circuit_qubit0_error(): + """Test correction circuit with error on qubit 0 (syndrome 10).""" + qc = PhaseFlipCode.correction_circuit((1, 0)) + assert qc.N == 3 + assert len(qc.gates) == 1 + assert qc.gates[0].name == "Z" and qc.gates[0].targets[0] == 0 + + +def test_correction_circuit_qubit1_error(): + """Test correction circuit with error on qubit 1 (syndrome 11).""" + qc = PhaseFlipCode.correction_circuit((1, 1)) + assert qc.N == 3 + assert len(qc.gates) == 1 + assert qc.gates[0].name == "Z" and qc.gates[0].targets[0] == 1 + + +def test_correction_circuit_qubit2_error(): + """Test correction circuit with error on qubit 2 (syndrome 01).""" + qc = PhaseFlipCode.correction_circuit((0, 1)) + assert qc.N == 3 + assert len(qc.gates) == 1 + assert qc.gates[0].name == "Z" and qc.gates[0].targets[0] == 2 + + +def test_decode_circuit_structure(): + """Test that the decoding circuit has the correct structure.""" + qc = PhaseFlipCode.decode_circuit() + + # Check circuit has correct number of qubits + assert qc.N == 3 + + # Check it has the correct number of gates (3 H + 2 CNOT + 1 TOFFOLI + 1 H = 7 gates) + assert len(qc.gates) == 7 + + # Check first three Hadamard gates + for i in range(3): + assert qc.gates[i].name == "SNOT" + assert qc.gates[i].targets[0] == i + + # Check the two CNOT gates + assert qc.gates[3].name == "CNOT" + assert qc.gates[3].controls == [0] and qc.gates[3].targets[0] == 2 + + assert qc.gates[4].name == "CNOT" + assert qc.gates[4].controls == [0] and qc.gates[4].targets[0] == 1 + + # Check the TOFFOLI gate + assert qc.gates[5].name == "TOFFOLI" + assert qc.gates[5].controls == [1, 2] and qc.gates[5].targets[0] == 0 + + # Check the final Hadamard gate + assert qc.gates[6].name == "SNOT" + assert qc.gates[6].targets[0] == 0 diff --git a/tests/test_shor_code.py b/tests/test_shor_code.py new file mode 100644 index 00000000..24a9cc08 --- /dev/null +++ b/tests/test_shor_code.py @@ -0,0 +1,45 @@ +import pytest +from qutip_qip.algorithms import ShorCode + + +def test_encode_circuit(): + """Test the Shor code encoding circuit structure.""" + qc = ShorCode.encode_circuit() + + # Check correct number of qubits + assert qc.N == 9 + + # Check total number of gates (1H + 2CNOT + 3H + 6CNOT = 12 gates) + assert len(qc.gates) == 12 + + # Check first Hadamard gate (phase-flip encoding starts) + assert qc.gates[0].name == "SNOT" and qc.gates[0].targets[0] == 0 + + # Check first level CNOTs (create GHZ-like state across blocks) + assert qc.gates[1].name == "CNOT" + assert qc.gates[1].controls == [0] and qc.gates[1].targets[0] == 3 + assert qc.gates[2].name == "CNOT" + assert qc.gates[2].controls == [0] and qc.gates[2].targets[0] == 6 + + # Check three Hadamard gates (complete phase-flip encoding) + assert qc.gates[3].name == "SNOT" and qc.gates[3].targets[0] == 0 + assert qc.gates[4].name == "SNOT" and qc.gates[4].targets[0] == 3 + assert qc.gates[5].name == "SNOT" and qc.gates[5].targets[0] == 6 + + # Check bit-flip encoding CNOTs for first block + assert qc.gates[6].name == "CNOT" + assert qc.gates[6].controls == [0] and qc.gates[6].targets[0] == 1 + assert qc.gates[7].name == "CNOT" + assert qc.gates[7].controls == [0] and qc.gates[7].targets[0] == 2 + + # Check bit-flip encoding CNOTs for second block + assert qc.gates[8].name == "CNOT" + assert qc.gates[8].controls == [3] and qc.gates[8].targets[0] == 4 + assert qc.gates[9].name == "CNOT" + assert qc.gates[9].controls == [3] and qc.gates[9].targets[0] == 5 + + # Check bit-flip encoding CNOTs for third block + assert qc.gates[10].name == "CNOT" + assert qc.gates[10].controls == [6] and qc.gates[10].targets[0] == 7 + assert qc.gates[11].name == "CNOT" + assert qc.gates[11].controls == [6] and qc.gates[11].targets[0] == 8