diff --git a/cqlib/circuits/gates/i.py b/cqlib/circuits/gates/i.py index bb2d19b75dcb7f61567957fc1af5edf3f6ef0e91..ff801b4a2e02d308fadaac8ecfcb8e3a15204562 100644 --- a/cqlib/circuits/gates/i.py +++ b/cqlib/circuits/gates/i.py @@ -33,7 +33,7 @@ class I(Gate): def __init__(self, t: int, label: Optional[str] = None): """ - Initialize the I gate + Initialize the "I" gate Args: t (int): The duration the gate acts, in integer units of 0.5 ns. diff --git a/cqlib/circuits/gates/rx.py b/cqlib/circuits/gates/rx.py index 3834942cdb7bd5750a7b9258551034b251e2886b..1a526708ac9031f6de773df80b11514eb281e3b5 100644 --- a/cqlib/circuits/gates/rx.py +++ b/cqlib/circuits/gates/rx.py @@ -122,16 +122,19 @@ class CRX(ControlledGate): ValueError: If the number of qubits is not exactly two. """ # pylint: disable=import-outside-toplevel - from cqlib.circuits.gates.h import H + from cqlib.circuits.gates.y import Y2M, Y2P from cqlib.circuits.gates.z import CZ from cqlib.circuits.gates.rz import RZ from cqlib.circuits.instruction_data import InstructionData control_qubit, target_qubit = self._parse_qubits(qubits) return [ - InstructionData(H(), [target_qubit]), - InstructionData(CZ(), [control_qubit, target_qubit]), + InstructionData(Y2M(), [target_qubit]), InstructionData(RZ(self.params[0] / 2), [target_qubit]), + InstructionData(Y2P(), [target_qubit]), + InstructionData(CZ(), [control_qubit, target_qubit]), + InstructionData(Y2M(), [target_qubit]), + InstructionData(RZ(-self.params[0] / 2), [target_qubit]), + InstructionData(Y2P(), [target_qubit]), InstructionData(CZ(), [control_qubit, target_qubit]), - InstructionData(H(), [target_qubit]), ] diff --git a/cqlib/circuits/gates/ry.py b/cqlib/circuits/gates/ry.py index e2a02ffc020dcf8f31f2b267fa8d5ac48622c3de..786f74167d0b8c0eb37c26048f4316f02af77d29 100644 --- a/cqlib/circuits/gates/ry.py +++ b/cqlib/circuits/gates/ry.py @@ -12,6 +12,8 @@ # that they have been altered from the originals. """Defines the Ry (rotation around the Y-axis) gate.""" +from math import pi + from typing import Union, Optional, Sequence import numpy as np @@ -121,15 +123,23 @@ class CRY(ControlledGate): ValueError: If the number of qubits is not exactly two. """ # pylint: disable=import-outside-toplevel - from cqlib.circuits.gates.h import H + from cqlib.circuits.gates.y import Y2M, Y2P from cqlib.circuits.gates.z import CZ + from cqlib.circuits.gates.rz import RZ from cqlib.circuits.instruction_data import InstructionData control_qubit, target_qubit = self._parse_qubits(qubits) return [ - InstructionData(H(), [target_qubit]), + InstructionData(Y2M(), [target_qubit]), + InstructionData(RZ(pi / 2), [target_qubit]), + InstructionData(Y2P(), [target_qubit]), + InstructionData(RZ(self.params[0] / 2 + pi), [target_qubit]), + InstructionData(Y2P(), [target_qubit]), InstructionData(CZ(), [control_qubit, target_qubit]), - InstructionData(RY(self.params[0]), [target_qubit]), + InstructionData(Y2M(), [target_qubit]), + InstructionData(RZ(-self.params[0] / 2), [target_qubit]), + InstructionData(Y2P(), [target_qubit]), InstructionData(CZ(), [control_qubit, target_qubit]), - InstructionData(H(), [target_qubit]), + InstructionData(RZ(pi / 2), [target_qubit]), + InstructionData(Y2P(), [target_qubit]), ] diff --git a/cqlib/circuits/gates/rz.py b/cqlib/circuits/gates/rz.py index bc05321b0c3fb521dfbffa3c5acbd9c53156c7b0..5c01af3b1642ede8ab325132fe7532139cd489b4 100644 --- a/cqlib/circuits/gates/rz.py +++ b/cqlib/circuits/gates/rz.py @@ -12,6 +12,7 @@ # that they have been altered from the originals. """Defines the Rz (rotation around the Z-axis) and CRz (Controlled-Rz) gate.""" +from math import pi from typing import Union, Optional, Sequence import numpy as np @@ -115,13 +116,20 @@ class CRZ(ControlledGate): Raises: ValueError: If the number of qubits is not exactly two. """ - # pylint: disable=import-outside-toplevel + # pylint: disable=import-outside-toplevel, cyclic-import from cqlib.circuits.gates.z import CZ + from cqlib.circuits.gates.y import Y2M, Y2P from cqlib.circuits.instruction_data import InstructionData control_qubit, target_qubit = self._parse_qubits(qubits) return [ + InstructionData(RZ(self.params[0] / 2 + pi), [target_qubit]), + InstructionData(Y2P(), [target_qubit]), + InstructionData(CZ(), [control_qubit, target_qubit]), + InstructionData(Y2M(), [target_qubit]), InstructionData(RZ(-self.params[0] / 2), [target_qubit]), + InstructionData(Y2P(), [target_qubit]), InstructionData(CZ(), [control_qubit, target_qubit]), - InstructionData(RZ(self.params[0] / 2), [target_qubit]), + InstructionData(RZ(pi), [target_qubit]), + InstructionData(Y2P(), [target_qubit]), ] diff --git a/cqlib/circuits/gates/swap.py b/cqlib/circuits/gates/swap.py index bd9df941e79a30fe21524697852426a5af4d235e..1ab14eaa4563f19fdd00249b7b64ac174fa93e84 100644 --- a/cqlib/circuits/gates/swap.py +++ b/cqlib/circuits/gates/swap.py @@ -72,7 +72,7 @@ class SWAP(Gate): """ # pylint: disable=import-outside-toplevel from cqlib.circuits.gates.z import CZ - from cqlib.circuits.gates.h import H + from cqlib.circuits.gates.y import Y2P, Y2M from cqlib.circuits.qubit import Qubit from cqlib.circuits.instruction_data import InstructionData @@ -85,17 +85,17 @@ class SWAP(Gate): q1 = qubits[1] return [ # First CNOT - InstructionData(H(), [q1]), + InstructionData(Y2M(), [q1]), InstructionData(CZ(), [q0, q1]), - InstructionData(H(), [q1]), + InstructionData(Y2P(), [q1]), # Second CNOT - InstructionData(H(), [q0]), + InstructionData(Y2M(), [q0]), InstructionData(CZ(), [q1, q0]), - InstructionData(H(), [q0]), + InstructionData(Y2P(), [q0]), # Third CNOT - InstructionData(H(), [q1]), + InstructionData(Y2M(), [q1]), InstructionData(CZ(), [q0, q1]), - InstructionData(H(), [q1]), + InstructionData(Y2P(), [q1]), ] diff --git a/cqlib/circuits/gates/u.py b/cqlib/circuits/gates/u.py index 25a4594d20376c4e1421634c9cc87701f9decf4f..b01bcaa712186eb3e191d6a6ce1102b543e6c109 100644 --- a/cqlib/circuits/gates/u.py +++ b/cqlib/circuits/gates/u.py @@ -91,13 +91,14 @@ class U(Gate): """ # pylint: disable=import-outside-toplevel from cqlib.circuits.gates.rz import RZ - from cqlib.circuits.gates.rx import RX + from cqlib.circuits.gates.y import Y2P, Y2M + qubit = qubits[0] theta, phi, lam = (float(param) for param in self.params) return [ - InstructionData(RZ(lam), [qubit]), - InstructionData(RX(math.pi / 2), [qubit]), + InstructionData(RZ(lam + math.pi / 2), [qubit]), + InstructionData(Y2P(), [qubit]), InstructionData(RZ(theta), [qubit]), - InstructionData(RX(-math.pi / 2), [qubit]), - InstructionData(RZ(phi), [qubit]), + InstructionData(Y2M(), [qubit]), + InstructionData(RZ(phi - math.pi / 2), [qubit]), ] diff --git a/cqlib/circuits/gates/x.py b/cqlib/circuits/gates/x.py index e14df5c40e76997cca0080937cd38523b0dceab7..afae6432b0e304bedb35bbea1763f2ca76d882ab 100644 --- a/cqlib/circuits/gates/x.py +++ b/cqlib/circuits/gates/x.py @@ -27,6 +27,7 @@ Classes: X2P: Rotates around the x-axis of the Bloch sphere by pi/2. X2M: Rotates around the x-axis of the Bloch sphere by -pi/2. """ +import math from typing import Type, Optional, Sequence import numpy as np @@ -211,38 +212,43 @@ class CCX(ControlledGate): ValueError: If the number of qubits is not exactly three. """ # pylint: disable=import-outside-toplevel - from cqlib.circuits.gates.h import H from cqlib.circuits.gates.z import CZ - from cqlib.circuits.gates.t import T, TD + from cqlib.circuits.gates.rz import RZ + from cqlib.circuits.gates.y import Y2P, Y2M from cqlib.circuits.instruction_data import InstructionData control_qubit_0, control_qubit_1, target_qubit = self._parse_qubits(qubits) return [ InstructionData(CZ(), [control_qubit_1, target_qubit]), - InstructionData(H(), [target_qubit]), - InstructionData(TD(), [target_qubit]), - InstructionData(H(), [target_qubit]), + InstructionData(Y2M(), [target_qubit]), + InstructionData(RZ(-math.pi / 4), [target_qubit]), + InstructionData(Y2P(), [target_qubit]), + InstructionData(CZ(), [control_qubit_0, target_qubit]), - InstructionData(H(), [target_qubit]), - InstructionData(T(), [target_qubit]), - InstructionData(H(), [target_qubit]), + InstructionData(Y2M(), [target_qubit]), + InstructionData(RZ(math.pi / 4), [target_qubit]), + InstructionData(Y2P(), [target_qubit]), + InstructionData(CZ(), [control_qubit_1, target_qubit]), - InstructionData(T(), [control_qubit_1]), - InstructionData(H(), [control_qubit_1]), - InstructionData(H(), [target_qubit]), - InstructionData(TD(), [target_qubit]), - InstructionData(H(), [target_qubit]), + InstructionData(RZ(math.pi * 5 / 4), [control_qubit_1]), + InstructionData(Y2P(), [control_qubit_1]), + InstructionData(Y2M(), [target_qubit]), + InstructionData(RZ(-math.pi / 4), [target_qubit]), + InstructionData(Y2P(), [target_qubit]), + InstructionData(CZ(), [control_qubit_0, target_qubit]), InstructionData(CZ(), [control_qubit_0, control_qubit_1]), - InstructionData(T(), [control_qubit_0]), - InstructionData(H(), [control_qubit_1]), - InstructionData(TD(), [control_qubit_1]), - InstructionData(H(), [control_qubit_1]), + InstructionData(Y2M(), [target_qubit]), + InstructionData(RZ(math.pi / 4), [target_qubit]), + InstructionData(Y2P(), [target_qubit]), + InstructionData(Y2M(), [control_qubit_1]), + InstructionData(RZ(-math.pi / 4), [control_qubit_1]), + InstructionData(Y2P(), [control_qubit_1]), + InstructionData(RZ(math.pi / 4), [control_qubit_0]), + InstructionData(CZ(), [control_qubit_0, control_qubit_1]), - InstructionData(H(), [control_qubit_1]), - InstructionData(H(), [target_qubit]), - InstructionData(T(), [target_qubit]), - InstructionData(H(), [target_qubit]), + InstructionData(Y2M(), [control_qubit_1]), + InstructionData(RZ(math.pi), [control_qubit_1]), ] diff --git a/cqlib/circuits/gates/y.py b/cqlib/circuits/gates/y.py index ccb1cf19f2d85a6db14d2cd0726bdc0874ca4669..a4ab99db475b98ef9925c23088de616cd05cfa9b 100644 --- a/cqlib/circuits/gates/y.py +++ b/cqlib/circuits/gates/y.py @@ -20,6 +20,7 @@ Classes: Y2P: Performs a rotation around the y-axis by pi/2. Y2M: Performs a rotation around the y-axis by -pi/2. """ +import math from typing import Optional, Sequence import numpy as np @@ -94,9 +95,9 @@ class CY(ControlledGate): """ return np.array([ [1, 0, 0, 0], + [0, 1, 0, 0], [0, 0, 0, -1j], - [0, 0, 1, 0], - [0, 1j, 0, 0], + [0, 0, 1j, 0], ], dtype=dtype) def to_qcis(self, qubits: Sequence) -> list: @@ -108,18 +109,17 @@ class CY(ControlledGate): It requires exactly three qubits. """ # pylint: disable=import-outside-toplevel - from cqlib.circuits.gates.h import H from cqlib.circuits.gates.z import CZ - from cqlib.circuits.gates.s import S, SD + from cqlib.circuits.gates.rz import RZ from cqlib.circuits.instruction_data import InstructionData control_qubit, target_qubit = self._parse_qubits(qubits) return [ - InstructionData(SD(), [target_qubit]), - InstructionData(H(), [target_qubit]), + InstructionData(RZ(math.pi / 2), [target_qubit]), + InstructionData(Y2P(), [target_qubit]), InstructionData(CZ(), [control_qubit, target_qubit]), - InstructionData(H(), [target_qubit]), - InstructionData(S(), [target_qubit]), + InstructionData(Y2M(), [target_qubit]), + InstructionData(RZ(-math.pi / 2), [target_qubit]), ] diff --git a/requirements-dev.txt b/requirements-dev.txt index 25ed76d98a6f35ecc2b280ea559230ae737ddf40..3eff412314340575a895a14e11c04c4eb4b7f05e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -8,4 +8,5 @@ delocate poetry-core pipx psutil -pytest-mpl \ No newline at end of file +pytest-mpl +pennylane \ No newline at end of file diff --git a/tests/circuit/test_gates.py b/tests/circuit/test_gates.py new file mode 100644 index 0000000000000000000000000000000000000000..88d8e397e8c3c5d18ce9db5e99faf7abfed77e61 --- /dev/null +++ b/tests/circuit/test_gates.py @@ -0,0 +1,187 @@ +# This code is part of cqlib. +# +# Copyright (C) 2025 China Telecom Quantum Group, QuantumCTek Co., Ltd., +# Center for Excellence in Quantum Information and Quantum Physics. +# +# This code is licensed under the Apache License, Version 2.0. You may +# obtain a copy of this license in the LICENSE file in the root directory +# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +# +# Any modifications or derivative works of this code must retain this +# copyright notice, and modified files need to carry a notice indicating +# that they have been altered from the originals. + +""" test gates """ + +# pylint: disable=missing-function-docstring +from math import pi +from random import random +import numpy as np + +from cqlib import Qubit, Circuit +from cqlib.circuits import gates as cqlib_gates +from cqlib.circuits.gates.gate import Gate +from cqlib.simulator import StatevectorSimulator + +try: + import pennylane as qml +except ImportError: + qml = None + + +def close(d1, d2): + """close""" + if not isinstance(d1, np.ndarray): + d1 = np.asarray(d1) + if not isinstance(d2, np.ndarray): + d2 = np.asarray(d2) + # print(f'\nd1: {d1}\nd2: {d2}\n') + return np.allclose(d1, d2) + + +def qcis_close(qcis_gate: Gate): + """qcis_close""" + qs = [Qubit(i) for i in range(qcis_gate.num_qubits)] + circuit1 = Circuit(qs) + circuit1.append(qcis_gate, qs) + + circuit2 = Circuit(qs) + for gate in qcis_gate.to_qcis(qs): + circuit2.append_instruction_data(gate) + + state1 = StatevectorSimulator(circuit1).statevector() + state2 = StatevectorSimulator(circuit2).statevector() + # print(f'\nstate1: {state1}\nstate2: {state2}\n') + + p = None + for k, v1 in state1.items(): + v2 = state2.get(k, 0) + # Eliminate the difference caused by different global phases + if p is None and not np.isclose(v1, 0) and not np.isclose(v2, 0): + p = v1 / v2 + if p is not None: + v2 = p * v2 + + if not close(v1, v2): + return False + return True + + +def test_h(): + """test h""" + assert close(cqlib_gates.H(), qml.H.compute_matrix()) + + +def test_r(): + """test rx/ry/rz""" + for i in range(10): + theta = i / 5 * np.pi * random() + assert close(cqlib_gates.RX(theta), qml.RX.compute_matrix(theta)) + assert close(cqlib_gates.RY(theta), qml.RY.compute_matrix(theta)) + assert close(cqlib_gates.RZ(theta), qml.RZ.compute_matrix(theta)) + + +def test_s(): + """test s/sd""" + assert close(cqlib_gates.S(), qml.S.compute_matrix()) + assert close(cqlib_gates.SD(), qml.matrix(qml.adjoint(qml.S))(wires=0)) + + +def test_t(): + """test t/td""" + assert close(cqlib_gates.T(), qml.T.compute_matrix()) + assert close(cqlib_gates.TD(), qml.matrix(qml.adjoint(qml.T))(wires=0)) + + +def test_xyz(): + """test x/y/z""" + assert close(cqlib_gates.X(), qml.X.compute_matrix()) + assert close(cqlib_gates.Y(), qml.Y.compute_matrix()) + assert close(cqlib_gates.Z(), qml.Z.compute_matrix()) + + +def test_cz(): + assert close(cqlib_gates.CZ(), qml.CZ.compute_matrix()) + + +def test_cx(): + assert close(cqlib_gates.CX(), qml.CNOT.compute_matrix()) + assert qcis_close(cqlib_gates.CX()) + + +def test_cy(): + assert close(cqlib_gates.CY(), qml.CY.compute_matrix()) + assert qcis_close(cqlib_gates.CY()) + + +def test_crx(): + for i in range(10): + theta = i / 5 * np.pi + assert close(cqlib_gates.CRX(theta=theta), qml.CRX.compute_matrix(theta)) + assert qcis_close(cqlib_gates.CRX(theta)) + + +def test_cry(): + for i in range(10): + theta = i / 5 * np.pi + assert close(cqlib_gates.CRY(theta=theta), qml.CRY.compute_matrix(theta)) + assert qcis_close(cqlib_gates.CRY(theta)) + + +def test_crz(): + for i in range(10): + theta = i / 5 * np.pi + assert close(cqlib_gates.CRZ(theta=theta), qml.CRZ.compute_matrix(theta)) + assert qcis_close(cqlib_gates.CRZ(theta)) + + +def test_ccx(): + assert close(cqlib_gates.CCX(), qml.Toffoli.compute_matrix()) + assert qcis_close(cqlib_gates.CCX()) + + +def test_swap(): + assert close(cqlib_gates.SWAP(), qml.SWAP.compute_matrix()) + assert qcis_close(cqlib_gates.SWAP()) + + +def test_raw_gates(): + assert close(cqlib_gates.X2P(), qml.RX.compute_matrix(pi / 2)) + assert close(cqlib_gates.X2M(), qml.RX.compute_matrix(-pi / 2)) + assert close(cqlib_gates.Y2P(), qml.RY.compute_matrix(pi / 2)) + assert close(cqlib_gates.Y2M(), qml.RY.compute_matrix(-pi / 2)) + + +def test_xy2p(): + for i in range(10): + theta = i / 5 * np.pi * random() + + def xy2p_ops(t): + qml.RZ(np.pi / 2 - t, wires=0) + qml.RY(np.pi / 2, wires=0) + qml.RZ(t - np.pi / 2, wires=0) + + def xy2m_ops(t): + qml.RZ(-np.pi / 2 - t, wires=0) + qml.RY(np.pi / 2, wires=0) + qml.RZ(t + np.pi / 2, wires=0) + + assert close( + cqlib_gates.XY2P(theta=theta), + qml.matrix(xy2p_ops, wire_order=[0])(theta) + ) + assert close( + cqlib_gates.XY2M(theta=theta), + qml.matrix(xy2m_ops, wire_order=[0])(theta) + ) + + +def test_u(): + for i in range(10): + theta = i / 5 * np.pi * random() + phi = i / 6 * np.pi * random() + lam = i / 8 * np.pi * random() + assert close( + cqlib_gates.U(theta=theta, phi=phi, lam=lam), + qml.U3.compute_matrix(theta, phi, lam) + )