From ae478ce8c616fab565f87163562d8d18d0ece583 Mon Sep 17 00:00:00 2001 From: Rohan Jain Date: Fri, 8 Dec 2023 13:25:16 -0500 Subject: [PATCH 1/5] fixtures --- tests/fixtures/basic_gates.py | 66 +++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/tests/fixtures/basic_gates.py b/tests/fixtures/basic_gates.py index d7e6b90..fbefd45 100644 --- a/tests/fixtures/basic_gates.py +++ b/tests/fixtures/basic_gates.py @@ -15,6 +15,7 @@ import cirq import pytest +import numpy as np # All of the following dictionaries map from the names of methods on Cirq Circuit objects # to the name of the equivalent pyqir BasicQisBuilder method @@ -30,6 +31,12 @@ "Z": "z", } +_two_qubit_gates = { + "CX": "cnot", + "CZ": "cz", + "SWAP": "swap" + } + def _fixture_name(s: str) -> str: return f"Fixture_{s}" @@ -38,6 +45,8 @@ def _fixture_name(s: str) -> str: def _map_gate_name(gate_name: str) -> str: if gate_name in _one_qubit_gates: return _one_qubit_gates[gate_name] + elif gate_name in _two_qubit_gates: + return _two_qubit_gates[gate_name] raise ValueError(f"Unknown Cirq gate {gate_name}") @@ -52,10 +61,63 @@ def test_fixture(): return test_fixture - # Generate simple single-qubit gate fixtures for gate in _one_qubit_gates: name = _fixture_name(gate) locals()[name] = _generate_one_qubit_fixture(gate) -single_op_tests = [_fixture_name(s) for s in _one_qubit_gates] +# Create a new function to generate a fixture for 2-qubit gates +def _generate_two_qubit_fixture(gate_name: str): + @pytest.fixture() + def test_fixture(): + circuit = cirq.Circuit() + q1 = cirq.NamedQubit("q1") + q2 = cirq.NamedQubit("q2") + circuit.append(getattr(cirq, gate_name)(q1, q2)) + return _map_gate_name(gate_name), circuit + + return test_fixture + +# Create a new function to generate a fixture for n-qubit gates +def _generate_n_qubit_fixture(gate_name: str, n: int): + @pytest.fixture() + def test_fixture(): + circuit = cirq.Circuit() + qubits = [cirq.NamedQubit(f"q{i}") for i in range(n)] + circuit.append(getattr(cirq, gate_name)(*qubits)) + return _map_gate_name(gate_name), circuit + + return test_fixture + +# New function for more complex gate structures: +def _generate_complex_gate_fixture(gate_sequence): + @pytest.fixture() + def test_fixture(): + circuit = cirq.Circuit() + qubits = [cirq.NamedQubit(f"q{i}") for i in range(len(gate_sequence))] + for gate, qubit_indices in gate_sequence: + gates_to_apply = [getattr(cirq, gate)(qubits[i]) for i in qubit_indices] + circuit.append(gates_to_apply) + return circuit + return test_fixture + +def test_qft(): + for n in range(2, 5): # Test for different numbers of qubits + circuit = cirq.Circuit() + qubits = [cirq.NamedQubit(f'q{i}') for i in range(n)] + circuit.append(cirq.qft(*qubits)) + # Add assertions or checks here + +@pytest.mark.parametrize("angle", np.linspace(0, 2*np.pi, 5)) +def test_rx_gate(angle): + qubit = cirq.NamedQubit('q') + circuit = cirq.Circuit(cirq.rx(angle)(qubit)) + # Add assertions or checks for the rotation + +def test_bell_state(): + qubits = [cirq.NamedQubit(f'q{i}') for i in range(2)] + circuit = cirq.Circuit() + circuit.append([cirq.H(qubits[0]), cirq.CNOT(qubits[0], qubits[1])]) + # Check if the circuit produces the correct entangled state + +single_op_tests = [_fixture_name(s) for s in _one_qubit_gates] \ No newline at end of file From 90f9139aa5272a22b9bdc8d2b29bf8a3a91e6042 Mon Sep 17 00:00:00 2001 From: Rohan Jain Date: Wed, 20 Dec 2023 12:20:02 -0600 Subject: [PATCH 2/5] waiting for opsets to be defined --- qbraid_qir/cirq/convert.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/qbraid_qir/cirq/convert.py b/qbraid_qir/cirq/convert.py index 7e26a62..7a84683 100644 --- a/qbraid_qir/cirq/convert.py +++ b/qbraid_qir/cirq/convert.py @@ -14,12 +14,15 @@ """ from typing import Optional +import numpy as np + import cirq from pyqir import Context, Module, qir_module from qbraid_qir.cirq.elements import CirqModule, generate_module_id from qbraid_qir.cirq.visitor import BasicQisVisitor from qbraid_qir.exceptions import QirConversionError +from qbraid_qir.cirq.opsets import CIRQ_GATES def cirq_to_qir(circuit: cirq.Circuit, name: Optional[str] = None, **kwargs) -> Module: @@ -43,10 +46,38 @@ def cirq_to_qir(circuit: cirq.Circuit, name: Optional[str] = None, **kwargs) -> if name is None: name = generate_module_id(circuit) + # create a variable for circuit.unitary that we will use to create assertions later + input_unitary = circuit.unitary() + + # do some preprocessing here that ensures that the circuit is composed only of supported gates and operation: + + # for moment in circuit: + # for op in moment: + + + # according to the gateset in CIRQ_GATES, perform gate decomposition; + for moment in circuit: + for op in moment: + if op.gate in CIRQ_GATES: + op.gate = CIRQ_GATES[op.gate] + op.gate.validate_args(op.qubits) + else: + raise QirConversionError(f"Unsupported gate {op.gate} in circuit.") + + + # ensure that input/output circuit.unitary() are equivalent. + output_unitary = circuit.unitary() + if not np.allclose(input_unitary, output_unitary): + raise QirConversionError("Cirq circuit unitary changed during conversion.") + + llvm_module = qir_module(Context(), name) module = CirqModule.from_circuit(circuit, llvm_module) + + visitor = BasicQisVisitor(**kwargs) module.accept(visitor) + err = llvm_module.verify() if err is not None: raise QirConversionError(err) From e58927d37860269e276dd714a22a6b10b5ece3f5 Mon Sep 17 00:00:00 2001 From: Rohan Jain Date: Wed, 20 Dec 2023 14:20:34 -0600 Subject: [PATCH 3/5] added some stuff to opsets using pyqir stuff --- qbraid_qir/cirq/convert.py | 36 +++++++++++++++++++++++++----------- qbraid_qir/cirq/opsets.py | 37 ++++++++++++++++++++++++++++++++++++- qbraid_qir/cirq/visitor.py | 1 + 3 files changed, 62 insertions(+), 12 deletions(-) diff --git a/qbraid_qir/cirq/convert.py b/qbraid_qir/cirq/convert.py index 7a84683..e5b4d86 100644 --- a/qbraid_qir/cirq/convert.py +++ b/qbraid_qir/cirq/convert.py @@ -17,12 +17,31 @@ import numpy as np import cirq +import qbraid.programs.cirq from pyqir import Context, Module, qir_module from qbraid_qir.cirq.elements import CirqModule, generate_module_id from qbraid_qir.cirq.visitor import BasicQisVisitor from qbraid_qir.exceptions import QirConversionError -from qbraid_qir.cirq.opsets import CIRQ_GATES +from qbraid_qir.cirq.opsets import CIRQ_GATES, get_callable_from_pyqir_name + + +def _preprocess_circuit(circuit: cirq.Circuit) -> cirq.Circuit: + """ + Preprocesses a Cirq circuit to ensure that it is compatible with the QIR conversion. + + Args: + circuit (cirq.Circuit): The Cirq circuit to preprocess. + + Returns: + cirq.Circuit: The preprocessed Cirq circuit. + + """ + # circuit = cirq.contrib.qasm_import.circuit_from_qasm(circuit.to_qasm()) # decompose? + qprogram = qbraid.programs.cirq.CirqCircuit(circuit) + qprogram._convert_to_line_qubits() + cirq_circuit = qprogram.program + return cirq_circuit def cirq_to_qir(circuit: cirq.Circuit, name: Optional[str] = None, **kwargs) -> Module: @@ -49,20 +68,16 @@ def cirq_to_qir(circuit: cirq.Circuit, name: Optional[str] = None, **kwargs) -> # create a variable for circuit.unitary that we will use to create assertions later input_unitary = circuit.unitary() - # do some preprocessing here that ensures that the circuit is composed only of supported gates and operation: - - # for moment in circuit: - # for op in moment: - + circuit = _preprocess_circuit(circuit) # according to the gateset in CIRQ_GATES, perform gate decomposition; for moment in circuit: for op in moment: - if op.gate in CIRQ_GATES: - op.gate = CIRQ_GATES[op.gate] - op.gate.validate_args(op.qubits) + if str(op.gate) in CIRQ_GATES: + # i don't know what to do here + callable = get_callable_from_pyqir_name(op) else: - raise QirConversionError(f"Unsupported gate {op.gate} in circuit.") + raise QirConversionError(f"Unsupported gate {str(op.gate)} in circuit.") # ensure that input/output circuit.unitary() are equivalent. @@ -74,7 +89,6 @@ def cirq_to_qir(circuit: cirq.Circuit, name: Optional[str] = None, **kwargs) -> llvm_module = qir_module(Context(), name) module = CirqModule.from_circuit(circuit, llvm_module) - visitor = BasicQisVisitor(**kwargs) module.accept(visitor) diff --git a/qbraid_qir/cirq/opsets.py b/qbraid_qir/cirq/opsets.py index 5c27241..0a0fbc1 100644 --- a/qbraid_qir/cirq/opsets.py +++ b/qbraid_qir/cirq/opsets.py @@ -12,5 +12,40 @@ Module defining supported Cirq operations/gates. """ +import cirq +import pyqir._native -CIRQ_GATES = {} +# "barrier", +# "mz", +# "reset", +# "s_adj", +# "t_adj", +CIRQ_GATES = { + 'TOFFOLI': pyqir._native.ccx, + 'CCX': pyqir._native.ccx, + 'CCNOT': pyqir._native.ccx, + 'CNOT': pyqir._native.cx, + 'CZ': pyqir._native.cz, + 'H': pyqir._native.h, + 'S': pyqir._native.s, + 'SWAP': pyqir._native.swap, + 'T': pyqir._native.t, + 'X': pyqir._native.x, + 'Y': pyqir._native.y, + 'Z': pyqir._native.z, + } + +def get_callable_from_pyqir_name(op: cirq.Operation): + """Get callable from pyqir name.""" + return CIRQ_GATES[str(op.gate)] + +# some testcases for the above function +circuit = cirq.Circuit() +circuit.append(cirq.ops.CNOT(cirq.LineQubit(0), cirq.LineQubit(1))) +circuit.append(cirq.ops.CNOT(cirq.LineQubit(1), cirq.LineQubit(2))) +circuit.append(cirq.ops.CNOT(cirq.LineQubit(2), cirq.LineQubit(3))) +circuit.append(cirq.ops.H(cirq.LineQubit(0))) +circuit.append(cirq.ops.H(cirq.LineQubit(1))) + +for op in circuit.all_operations(): + print(get_callable_from_pyqir_name(op)) diff --git a/qbraid_qir/cirq/visitor.py b/qbraid_qir/cirq/visitor.py index 10566c5..fc89053 100644 --- a/qbraid_qir/cirq/visitor.py +++ b/qbraid_qir/cirq/visitor.py @@ -22,6 +22,7 @@ import pyqir.rt as rt from pyqir import BasicBlock, Builder, Constant, IntType, PointerType, entry_point +from qbraid_qir.cirq.opsets import CIRQ_GATES, get_callable_from_pyqir_name from qbraid_qir.cirq.elements import CirqModule _log = logging.getLogger(name=__name__) From 23ecd9206a6bfba451ec031564b63ee6443bb559 Mon Sep 17 00:00:00 2001 From: Rohan Jain Date: Wed, 20 Dec 2023 15:27:39 -0600 Subject: [PATCH 4/5] qis updates --- qbraid_qir/cirq/opsets.py | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/qbraid_qir/cirq/opsets.py b/qbraid_qir/cirq/opsets.py index 0a0fbc1..d1cdc58 100644 --- a/qbraid_qir/cirq/opsets.py +++ b/qbraid_qir/cirq/opsets.py @@ -18,8 +18,14 @@ # "barrier", # "mz", # "reset", -# "s_adj", -# "t_adj", + +ZPOWER_DICT = { + 1: pyqir._native.z, + 0.5: pyqir._native.s, + 0.25: pyqir._native.t, + -0.5: pyqir._native.s_adj, + -0.25: pyqir._native.t_adj, +} CIRQ_GATES = { 'TOFFOLI': pyqir._native.ccx, 'CCX': pyqir._native.ccx, @@ -27,25 +33,25 @@ 'CNOT': pyqir._native.cx, 'CZ': pyqir._native.cz, 'H': pyqir._native.h, - 'S': pyqir._native.s, 'SWAP': pyqir._native.swap, - 'T': pyqir._native.t, 'X': pyqir._native.x, 'Y': pyqir._native.y, - 'Z': pyqir._native.z, + # 'Z': pyqir._native.z } def get_callable_from_pyqir_name(op: cirq.Operation): """Get callable from pyqir name.""" + if isinstance(op, cirq.ops.ZPowGate): + return ZPOWER_DICT[op.gate.exponent] return CIRQ_GATES[str(op.gate)] # some testcases for the above function circuit = cirq.Circuit() -circuit.append(cirq.ops.CNOT(cirq.LineQubit(0), cirq.LineQubit(1))) -circuit.append(cirq.ops.CNOT(cirq.LineQubit(1), cirq.LineQubit(2))) -circuit.append(cirq.ops.CNOT(cirq.LineQubit(2), cirq.LineQubit(3))) -circuit.append(cirq.ops.H(cirq.LineQubit(0))) -circuit.append(cirq.ops.H(cirq.LineQubit(1))) +# circuit.append(cirq.ops.Z(cirq.LineQubit(0))) +# circuit.append(cirq.ops.CNOT(cirq.LineQubit(1), cirq.LineQubit(2))) +# circuit.append(cirq.ops.CNOT(cirq.LineQubit(2), cirq.LineQubit(3))) +# circuit.append(cirq.ops.H(cirq.LineQubit(0))) +# circuit.append(cirq.ops.H(cirq.LineQubit(1))) -for op in circuit.all_operations(): - print(get_callable_from_pyqir_name(op)) +# for op in circuit.all_operations(): +# print(isinstance(op.gate, cirq.ops.ZPowGate)) From 36610facb22037fcdb22759a0626ad8f8e6f0858 Mon Sep 17 00:00:00 2001 From: Rohan Jain Date: Fri, 22 Dec 2023 13:06:31 -0600 Subject: [PATCH 5/5] to merge --- qbraid_qir/cirq/visitor.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qbraid_qir/cirq/visitor.py b/qbraid_qir/cirq/visitor.py index 21c1ac0..f1c8afa 100644 --- a/qbraid_qir/cirq/visitor.py +++ b/qbraid_qir/cirq/visitor.py @@ -128,6 +128,9 @@ def visit_operation(self, operation: cirq.Operation, qids: FrozenSet[cirq.Qid]): results = [pyqir.result(self._module.context, n) for n in qlabels] # call some function that depends on qubits and results + callable = get_callable_from_pyqir_name(operation) + callable(self._builder, *qubits, *results) + def ir(self) -> str: return str(self._module)