From 64a75ae6d3ef530fbadde1f9d96a8a80db316b80 Mon Sep 17 00:00:00 2001 From: JulienCalistoTD <164006610+JulienCalistoTD@users.noreply.github.com> Date: Thu, 5 Dec 2024 17:36:53 +0100 Subject: [PATCH 1/3] feat: to_other_language cirq without QASM2.0 --- mpqp/core/circuit.py | 44 ++++- mpqp/core/instruction/gates/native_gates.py | 178 +++++++++++++++++- .../instruction/measurement/basis_measure.py | 4 + mpqp/execution/providers/google.py | 2 +- tests/execution/test_validity.py | 3 +- 5 files changed, 222 insertions(+), 9 deletions(-) diff --git a/mpqp/core/circuit.py b/mpqp/core/circuit.py index 4a04098f..af672326 100644 --- a/mpqp/core/circuit.py +++ b/mpqp/core/circuit.py @@ -1134,12 +1134,46 @@ def to_other_language( self.nb_qubits, ) elif language == Language.CIRQ: - qasm2_code = self.to_other_language(Language.QASM2) - if TYPE_CHECKING: - assert isinstance(qasm2_code, str) - from mpqp.qasm.qasm_to_cirq import qasm2_to_cirq_Circuit + from cirq.circuits.circuit import Circuit as CirqCircuit + from cirq.ops.named_qubit import NamedQubit + from cirq.ops.identity import I + + cirq_qubits = [NamedQubit(f"q_{i}") for i in range(self.nb_qubits)] + cirq_circuit = CirqCircuit() + + for qubit in cirq_qubits: + cirq_circuit.append(I(qubit)) + + for instruction in self.instructions: + if isinstance(instruction, ExpectationMeasure): + continue + elif isinstance(instruction, CustomGate): + custom_circuit = QCircuit(self.nb_qubits) + custom_circuit.add(instruction) + qasm2_code = custom_circuit.to_other_language(Language.QASM2) + if TYPE_CHECKING: + assert isinstance(qasm2_code, str) + from mpqp.qasm.qasm_to_cirq import qasm2_to_cirq_Circuit + + custom_cirq_circuit = qasm2_to_cirq_Circuit(qasm2_code) + cirq_circuit += custom_cirq_circuit + self.gphase += custom_circuit.gphase + elif isinstance(instruction, ControlledGate): + targets = [] + for target in instruction.targets: + targets.append(cirq_qubits[target]) + controls = [] + for control in instruction.controls: + controls.append(cirq_qubits[control]) + cirq_instruction = instruction.to_other_language(Language.CIRQ) + cirq_circuit.append(cirq_instruction.on(*controls, *targets)) + else: + targets = [] + for target in instruction.targets: + targets.append(cirq_qubits[target]) + cirq_instruction = instruction.to_other_language(Language.CIRQ) + cirq_circuit.append(cirq_instruction.on(*targets)) - cirq_circuit = qasm2_to_cirq_Circuit(qasm2_code) if cirq_proc_id: from cirq.transformers.optimize_for_target_gateset import ( optimize_for_target_gateset, diff --git a/mpqp/core/instruction/gates/native_gates.py b/mpqp/core/instruction/gates/native_gates.py index 5de0b61b..e2547556 100644 --- a/mpqp/core/instruction/gates/native_gates.py +++ b/mpqp/core/instruction/gates/native_gates.py @@ -133,6 +133,7 @@ def qasm2_gate(cls) -> str: YGate, ZGate, ) + from cirq.ops.raw_types import Gate @classproperty @abstractmethod @@ -182,6 +183,14 @@ def braket_gate( ]: pass + @classproperty + @abstractmethod + def cirq_gate( + cls, + ) -> type[Gate]: + """Returns the corresponding ``cirq`` class for this gate.""" + pass + @typechecked class RotationGate(NativeGate, ParametrizedGate, SimpleClassReprABC): @@ -236,6 +245,8 @@ def to_other_language( "export, this feature is coming very soon!" ) return self.braket_gate(theta) + elif language == Language.CIRQ: + return self.cirq_gate(theta) if language == Language.QASM2: from mpqp.qasm.mpqp_to_qasm import float_to_qasm_str @@ -340,6 +351,8 @@ def to_other_language( return self.qiskit_gate() elif language == Language.BRAKET: return self.braket_gate() + elif language == Language.CIRQ: + return self.cirq_gate elif language == Language.QASM2: instruction_str = self.qasm2_gate @@ -396,6 +409,12 @@ def qiskit_gate(cls): return IGate + @classproperty + def cirq_gate(cls): + from cirq.ops.identity import I as CirqI + + return CirqI + qlm_aqasm_keyword = "I" qiskit_string = "id" @@ -415,6 +434,8 @@ def to_other_language( return self.qiskit_gate() elif language == Language.BRAKET: return self.braket_gate() + elif language == Language.CIRQ: + return self.cirq_gate elif language == Language.QASM2: instruction_str = self.qasm2_gate @@ -452,6 +473,12 @@ def qiskit_gate(cls): return XGate + @classproperty + def cirq_gate(cls): + from cirq.ops.pauli_gates import X as CirqX + + return CirqX + qlm_aqasm_keyword = "X" qiskit_string = "x" @@ -487,6 +514,12 @@ def qiskit_gate(cls): return YGate + @classproperty + def cirq_gate(cls): + from cirq.ops.pauli_gates import Y as CirqY + + return CirqY + qlm_aqasm_keyword = "Y" qiskit_string = "y" @@ -522,6 +555,12 @@ def qiskit_gate(cls): return ZGate + @classproperty + def cirq_gate(cls): + from cirq.ops.pauli_gates import Z as CirqZ + + return CirqZ + qlm_aqasm_keyword = "Z" qiskit_string = "z" @@ -555,6 +594,12 @@ def qiskit_gate(cls): return HGate + @classproperty + def cirq_gate(cls): + from cirq.ops.common_gates import H as CirqH + + return CirqH + qlm_aqasm_keyword = "H" qiskit_string = "h" @@ -591,6 +636,12 @@ def qiskit_gate(cls): return PhaseGate + @classproperty + def cirq_gate(cls): + from cirq.ops.common_gates import ZPowGate + + return lambda theta: ZPowGate(exponent=theta / np.pi) + qlm_aqasm_keyword = "PH" qiskit_string = "p" @@ -640,6 +691,15 @@ def qiskit_gate(cls): return CPhaseGate + @classproperty + def cirq_gate(cls): + from cirq.ops.common_gates import ZPowGate + from cirq.ops.controlled_gate import ControlledGate as CirqControlledGate + + return lambda theta: CirqControlledGate(ZPowGate(exponent=theta / np.pi)) + + return controlled_phase + # TODO: this is a special case, see if it needs to be generalized qlm_aqasm_keyword = "CNOT;PH" qiskit_string = "cp" @@ -696,6 +756,12 @@ def qiskit_gate(cls): return SGate + @classproperty + def cirq_gate(cls): + from cirq.ops.common_gates import S as CirqS + + return CirqS + qlm_aqasm_keyword = "S" qiskit_string = "s" @@ -734,6 +800,12 @@ def qiskit_gate(cls): return TGate + @classproperty + def cirq_gate(cls): + from cirq.ops.common_gates import T as CirqT + + return CirqT + qlm_aqasm_keyword = "T" qiskit_string = "t" @@ -776,6 +848,12 @@ def qiskit_gate(cls): return SwapGate + @classproperty + def cirq_gate(cls): + from cirq.ops.swap_gates import SWAP as CirqSWAP + + return CirqSWAP + qlm_aqasm_keyword = "SWAP" qiskit_string = "swap" @@ -868,6 +946,40 @@ def qiskit_gate(cls): return UGate + @classproperty + def cirq_gate(cls): + from cirq.circuits.qasm_output import QasmUGate + from cirq.ops.common_gates import ry as cirq_ry, rz as cirq_rz + from cirq.ops.global_phase_op import GlobalPhaseGate + from cirq.ops.raw_types import Qid + + class CirqUGate(QasmUGate): # pyright: ignore[reportUntypedBaseClass] + def __init__( + self, theta, phi, lmda # pyright: ignore[reportMissingParameterType] + ) -> None: + self.lmda = lmda + self.theta = theta + self.phi = phi + + def __repr__(self) -> str: + return ( + f'U(' + f'theta={self.theta !r}, ' + f'phi={self.phi!r}, ' + f'lmda={self.lmda})' + ) + + def _decompose_(self, qubits: tuple[Qid, ...]): + q = qubits[0] + return [ + GlobalPhaseGate(np.exp(1j * (self.lmda + self.phi) / 2)).on(), + cirq_rz(self.lmda).on(q), + cirq_ry(self.theta).on(q), + cirq_rz(self.phi).on(q), + ] + + return CirqUGate + qlm_aqasm_keyword = "U" qiskit_string = "u" @@ -929,7 +1041,9 @@ def to_other_language( ) return self.braket_gate(self.theta, self.phi, self.gamma) - if language == Language.QASM2: + elif language == Language.CIRQ: + return self.cirq_gate(self.theta, self.phi, self.gamma) + elif language == Language.QASM2: from mpqp.qasm.mpqp_to_qasm import float_to_qasm_str instruction_str = self.qasm2_gate @@ -990,6 +1104,12 @@ def qiskit_gate(cls): return RXGate + @classproperty + def cirq_gate(cls): + from cirq.ops.common_gates import rx as CirqRx + + return CirqRx + qlm_aqasm_keyword = "RX" qiskit_string = "rx" @@ -1032,6 +1152,12 @@ def qiskit_gate(cls): return RYGate + @classproperty + def cirq_gate(cls): + from cirq.ops.common_gates import ry as CirqRy + + return CirqRy + qlm_aqasm_keyword = "RY" qiskit_string = "ry" @@ -1072,6 +1198,12 @@ def qiskit_gate(cls): return RZGate + @classproperty + def cirq_gate(cls): + from cirq.ops.common_gates import rz as CirqRz + + return CirqRz + qlm_aqasm_keyword = "RZ" qiskit_string = "rz" @@ -1117,6 +1249,12 @@ def qiskit_gate(cls): return PhaseGate + @classproperty + def cirq_gate(cls): + from cirq.ops.common_gates import ZPowGate + + return lambda theta: ZPowGate(exponent=theta / np.pi) + qlm_aqasm_keyword = "PH" qiskit_string = "p" @@ -1199,6 +1337,12 @@ def qiskit_gate(cls): return PhaseGate + @classproperty + def cirq_gate(cls): + from cirq.ops.common_gates import ZPowGate + + return lambda theta: ZPowGate(exponent=theta / np.pi) + qlm_aqasm_keyword = "PH" qiskit_string = "p" @@ -1284,6 +1428,12 @@ def qiskit_gate(cls): return CXGate + @classproperty + def cirq_gate(cls): + from cirq.ops.common_gates import CNOT as CirqCNOT + + return CirqCNOT + qlm_aqasm_keyword = "CNOT" qiskit_string = "cx" @@ -1330,6 +1480,12 @@ def qiskit_gate(cls): return CZGate + @classproperty + def cirq_gate(cls): + from cirq.ops.common_gates import CZ as CirqCZ + + return CirqCZ + qiskit_string = "cz" qlm_aqasm_keyword = "CSIGN" @@ -1385,6 +1541,13 @@ def qiskit_gate(cls): return CPhaseGate + @classproperty + def cirq_gate(cls): + from cirq.ops.controlled_gate import ControlledGate as CirqControlledGate + from cirq.ops.common_gates import ZPowGate + + return lambda theta: CirqControlledGate(ZPowGate(exponent=theta / np.pi)) + # TODO: this is a special case, see if it needs to be generalized qlm_aqasm_keyword = "CNOT;PH" qiskit_string = "cp" @@ -1478,6 +1641,13 @@ def qiskit_gate(cls): return CPhaseGate + @classproperty + def cirq_gate(cls): + from cirq.ops.controlled_gate import ControlledGate as CirqControlledGate + from cirq.ops.common_gates import ZPowGate + + return lambda theta: CirqControlledGate(ZPowGate(exponent=theta / np.pi)) + # TODO: this is a special case, see if it needs to be generalized qlm_aqasm_keyword = "CNOT;PH" qiskit_string = "cp" @@ -1574,6 +1744,12 @@ def qiskit_gate(cls): return CCXGate + @classproperty + def cirq_gate(cls): + from cirq.ops.three_qubit_gates import CCNOT as CirqCCNOT + + return CirqCCNOT + qlm_aqasm_keyword = "CCNOT" qiskit_string = "ccx" diff --git a/mpqp/core/instruction/measurement/basis_measure.py b/mpqp/core/instruction/measurement/basis_measure.py index b15e35b5..5beb6f35 100644 --- a/mpqp/core/instruction/measurement/basis_measure.py +++ b/mpqp/core/instruction/measurement/basis_measure.py @@ -92,6 +92,10 @@ def to_other_language( from qiskit.circuit import Measure return Measure() + elif language == Language.CIRQ: + from cirq.ops.measurement_gate import MeasurementGate + + return MeasurementGate(num_qubits=self.nb_qubits) if language == Language.QASM2: if self.c_targets is None: return "\n".join( diff --git a/mpqp/execution/providers/google.py b/mpqp/execution/providers/google.py index 76d27fdc..23b7107b 100644 --- a/mpqp/execution/providers/google.py +++ b/mpqp/execution/providers/google.py @@ -298,7 +298,7 @@ def extract_result_SAMPLE( data = [ Sample( - bin_str="".join(map(str, state)), + bin_str="".join(map(bin, state)), count=count, nb_qubits=nb_qubits, ) diff --git a/tests/execution/test_validity.py b/tests/execution/test_validity.py index f739eb69..2a8bed51 100644 --- a/tests/execution/test_validity.py +++ b/tests/execution/test_validity.py @@ -441,7 +441,7 @@ def test_validity_native_gate_to_other_language(language: Language): for gate in NATIVE_GATES: gate_build = random_gate([gate]) - if language in [Language.MY_QLM, Language.CIRQ, Language.QASM3]: + if language in [Language.MY_QLM, Language.QASM3]: with pytest.raises(NotImplementedError): gate_build.to_other_language(language) else: @@ -471,7 +471,6 @@ def test_validity_measure_to_other_language( measure.to_other_language(language) elif language in [ Language.MY_QLM, - Language.CIRQ, Language.BRAKET, Language.QASM3, ]: From b90e4c8bffc14fdd33b48fddc50c3499f281b73d Mon Sep 17 00:00:00 2001 From: JulienCalistoTD <164006610+JulienCalistoTD@users.noreply.github.com> Date: Tue, 17 Dec 2024 15:46:36 +0100 Subject: [PATCH 2/3] fix: handle barrier, breakpoint --- mpqp/core/circuit.py | 2 +- mpqp/tools/maths.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/mpqp/core/circuit.py b/mpqp/core/circuit.py index af672326..d26acc2a 100644 --- a/mpqp/core/circuit.py +++ b/mpqp/core/circuit.py @@ -1145,7 +1145,7 @@ def to_other_language( cirq_circuit.append(I(qubit)) for instruction in self.instructions: - if isinstance(instruction, ExpectationMeasure): + if isinstance(instruction, (ExpectationMeasure, Barrier, Breakpoint)): continue elif isinstance(instruction, CustomGate): custom_circuit = QCircuit(self.nb_qubits) diff --git a/mpqp/tools/maths.py b/mpqp/tools/maths.py index 837675d3..610cf080 100644 --- a/mpqp/tools/maths.py +++ b/mpqp/tools/maths.py @@ -161,7 +161,7 @@ def closest_unitary(matrix: Matrix): @typechecked -def cos(angle: Expr | Real) -> sp.Expr | float: +def cos(angle: Expr | Real | float) -> sp.Expr | float: """Generalization of the cosine function, to take as input either ``sympy``'s expressions or floating numbers. @@ -186,7 +186,7 @@ def cos(angle: Expr | Real) -> sp.Expr | float: @typechecked -def sin(angle: Expr | Real) -> sp.Expr | float: +def sin(angle: Expr | Real | float) -> sp.Expr | float: """Generalization of the sine function, to take as input either ``sympy``'s expressions or floating numbers. @@ -211,7 +211,7 @@ def sin(angle: Expr | Real) -> sp.Expr | float: @typechecked -def exp(angle: Expr | Complex) -> sp.Expr | complex: +def exp(angle: Expr | Complex | complex) -> sp.Expr | complex: """Generalization of the exponential function, to take as input either ``sympy``'s expressions or floating numbers. From 910ae08ec08730f1395098ca841c92e22230686e Mon Sep 17 00:00:00 2001 From: JulienCalistoTD <164006610+JulienCalistoTD@users.noreply.github.com> Date: Wed, 15 Jan 2025 11:51:57 +0100 Subject: [PATCH 3/3] chore: clean up --- mpqp/core/instruction/gates/native_gates.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/mpqp/core/instruction/gates/native_gates.py b/mpqp/core/instruction/gates/native_gates.py index e2547556..dd1811a7 100644 --- a/mpqp/core/instruction/gates/native_gates.py +++ b/mpqp/core/instruction/gates/native_gates.py @@ -698,8 +698,6 @@ def cirq_gate(cls): return lambda theta: CirqControlledGate(ZPowGate(exponent=theta / np.pi)) - return controlled_phase - # TODO: this is a special case, see if it needs to be generalized qlm_aqasm_keyword = "CNOT;PH" qiskit_string = "cp" @@ -964,7 +962,7 @@ def __init__( def __repr__(self) -> str: return ( f'U(' - f'theta={self.theta !r}, ' + f'theta={self.theta!r}, ' f'phi={self.phi!r}, ' f'lmda={self.lmda})' )