From 371adfa21bf07ad94dab155819cc0f9b7507fb40 Mon Sep 17 00:00:00 2001 From: Mathias Weiden Date: Wed, 17 Jan 2024 12:10:57 -0800 Subject: [PATCH 1/3] Added get_qasm support for ControlledGates --- bqskit/ir/gates/composed/controlled.py | 18 ++++++++++++++ tests/ir/lang/test_controlled_qasm.py | 33 ++++++++++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 tests/ir/lang/test_controlled_qasm.py diff --git a/bqskit/ir/gates/composed/controlled.py b/bqskit/ir/gates/composed/controlled.py index 3ed8a1537..e4b7736e2 100644 --- a/bqskit/ir/gates/composed/controlled.py +++ b/bqskit/ir/gates/composed/controlled.py @@ -9,6 +9,7 @@ from bqskit.ir.gate import Gate from bqskit.ir.gates.composedgate import ComposedGate +from bqskit.ir.location import CircuitLocation from bqskit.qis.unitary.differentiable import DifferentiableUnitary from bqskit.qis.unitary.unitary import RealVector from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix @@ -286,6 +287,23 @@ def __init__( ctrl_U = np.kron(self.ctrl, U) + self.ihalf self._utry = UnitaryMatrix(ctrl_U, self.radixes) + def get_qasm(self, params: RealVector, location: CircuitLocation) -> str: + """ + Override default `Gate.get_qasm` method. + + If the target gate is a standard gate, this function will output + qasm in the form 'c+'. Otherwise an error will be raised. + + Raises: + AttributeError: If the target gate is non-standard. + """ + qasm_name = 'c' * self.num_controls + self.gate.qasm_name + return '{}({}) q[{}];\n'.format( + qasm_name, + ', '.join([str(p) for p in params]), + '], q['.join([str(q) for q in location]), + ).replace('()', '') + def get_unitary(self, params: RealVector = []) -> UnitaryMatrix: """Return the unitary for this gate, see :class:`Unitary` for more.""" if hasattr(self, '_utry'): diff --git a/tests/ir/lang/test_controlled_qasm.py b/tests/ir/lang/test_controlled_qasm.py new file mode 100644 index 000000000..9bb096cb5 --- /dev/null +++ b/tests/ir/lang/test_controlled_qasm.py @@ -0,0 +1,33 @@ +from __future__ import annotations + +from bqskit.ir.lang.qasm2 import OPENQASM2Language + + +class TestControlledQASM: + def test_cu1(self) -> None: + + input_qasm = ( + 'OPENQASM 2.0;\n' + 'include "qelib1.inc";\n' + 'qreg q[2];\n' + 'cu1(3.1415) q[0], q[1];\n' + ) + circuit = OPENQASM2Language().decode(input_qasm) + + output_qasm = circuit.to('qasm') + + assert input_qasm == output_qasm + + def test_cu3(self) -> None: + + input_qasm = ( + 'OPENQASM 2.0;\n' + 'include "qelib1.inc";\n' + 'qreg q[2];\n' + 'cu3(3.1415, 0.0, -4.0) q[0], q[1];\n' + ) + circuit = OPENQASM2Language().decode(input_qasm) + + output_qasm = circuit.to('qasm') + + assert input_qasm == output_qasm From 2eae5509b1f7f20649d0bb11d11b5a56dba49ca5 Mon Sep 17 00:00:00 2001 From: Mathias Weiden Date: Thu, 18 Jan 2024 09:32:51 -0800 Subject: [PATCH 2/3] ControlledGate only supports standard OpenQASM2.0 identifiers --- bqskit/ir/gates/composed/controlled.py | 30 +++++++---- tests/ir/lang/test_controlled_qasm.py | 69 ++++++++++++++++++++++++++ 2 files changed, 89 insertions(+), 10 deletions(-) diff --git a/bqskit/ir/gates/composed/controlled.py b/bqskit/ir/gates/composed/controlled.py index e4b7736e2..580d1e6e1 100644 --- a/bqskit/ir/gates/composed/controlled.py +++ b/bqskit/ir/gates/composed/controlled.py @@ -287,22 +287,32 @@ def __init__( ctrl_U = np.kron(self.ctrl, U) + self.ihalf self._utry = UnitaryMatrix(ctrl_U, self.radixes) - def get_qasm(self, params: RealVector, location: CircuitLocation) -> str: + @property + def qasm_name(self) -> str: """ - Override default `Gate.get_qasm` method. + Override default `Gate.qasm_name` method. - If the target gate is a standard gate, this function will output + If the core gate is a standard gate, this function will output qasm in the form 'c+'. Otherwise an error will be raised. Raises: - AttributeError: If the target gate is non-standard. + ValueError: If the core gate is non-standard in OpenQASM 2.0. """ - qasm_name = 'c' * self.num_controls + self.gate.qasm_name - return '{}({}) q[{}];\n'.format( - qasm_name, - ', '.join([str(p) for p in params]), - '], q['.join([str(q) for q in location]), - ).replace('()', '') + _core_gate = self.gate.qasm_name + if self.num_controls <= 2: + _controls = 'c' * self.num_controls + else: + _controls = f'c{self.num_controls}' + qasm_name = _controls + _core_gate + supported_gates = ('cu1', 'cu2', 'cu3', 'cswap', 'c3x', 'c4x') + if qasm_name not in supported_gates: + raise ValueError( + f'Controlled gate {_core_gate} with {self.num_controls} ' + 'controls is not a standard OpenQASM 2.0 identifier. ' + 'To encode this gate, try decomposing it into gates with' + 'standard identifiers.' + ) + return qasm_name def get_unitary(self, params: RealVector = []) -> UnitaryMatrix: """Return the unitary for this gate, see :class:`Unitary` for more.""" diff --git a/tests/ir/lang/test_controlled_qasm.py b/tests/ir/lang/test_controlled_qasm.py index 9bb096cb5..d39949421 100644 --- a/tests/ir/lang/test_controlled_qasm.py +++ b/tests/ir/lang/test_controlled_qasm.py @@ -18,6 +18,20 @@ def test_cu1(self) -> None: assert input_qasm == output_qasm + def test_cu2(self) -> None: + + input_qasm = ( + 'OPENQASM 2.0;\n' + 'include "qelib1.inc";\n' + 'qreg q[2];\n' + 'cu2(3.1415, 0.0) q[0], q[1];\n' + ) + circuit = OPENQASM2Language().decode(input_qasm) + + output_qasm = circuit.to('qasm') + + assert input_qasm == output_qasm + def test_cu3(self) -> None: input_qasm = ( @@ -31,3 +45,58 @@ def test_cu3(self) -> None: output_qasm = circuit.to('qasm') assert input_qasm == output_qasm + + def test_cswap(self) -> None: + + input_qasm = ( + 'OPENQASM 2.0;\n' + 'include "qelib1.inc";\n' + 'qreg q[3];\n' + 'cswap q[0], q[1], q[2];\n' + ) + circuit = OPENQASM2Language().decode(input_qasm) + + output_qasm = circuit.to('qasm') + + assert input_qasm == output_qasm + + def test_c3x(self) -> None: + + input_qasm = ( + 'OPENQASM 2.0;\n' + 'include "qelib1.inc";\n' + 'qreg q[4];\n' + 'c3x q[0], q[1], q[2], q[3];\n' + ) + circuit = OPENQASM2Language().decode(input_qasm) + + output_qasm = circuit.to('qasm') + + assert input_qasm == output_qasm + + def test_c4x(self) -> None: + + input_qasm = ( + 'OPENQASM 2.0;\n' + 'include "qelib1.inc";\n' + 'qreg q[5];\n' + 'c4x q[0], q[1], q[2], q[3], q[4];\n' + ) + circuit = OPENQASM2Language().decode(input_qasm) + + output_qasm = circuit.to('qasm') + + assert input_qasm == output_qasm + + def test_ch(self) -> None: + + input_qasm = ( + 'OPENQASM 2.0;\n' + 'include "qelib1.inc";\n' + 'qreg q[2];\n' + 'ch q[0], q[1];\n' + ) + try: + OPENQASM2Language().decode(input_qasm) + except ValueError: + assert True From 5463f9f54343e6b80dcc8ea9a80a39ce687f28ec Mon Sep 17 00:00:00 2001 From: Mathias Weiden Date: Thu, 18 Jan 2024 09:35:39 -0800 Subject: [PATCH 3/3] Passes pre-commit --- bqskit/ir/gates/composed/controlled.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/bqskit/ir/gates/composed/controlled.py b/bqskit/ir/gates/composed/controlled.py index 580d1e6e1..0056e1883 100644 --- a/bqskit/ir/gates/composed/controlled.py +++ b/bqskit/ir/gates/composed/controlled.py @@ -9,7 +9,6 @@ from bqskit.ir.gate import Gate from bqskit.ir.gates.composedgate import ComposedGate -from bqskit.ir.location import CircuitLocation from bqskit.qis.unitary.differentiable import DifferentiableUnitary from bqskit.qis.unitary.unitary import RealVector from bqskit.qis.unitary.unitarymatrix import UnitaryMatrix @@ -310,7 +309,7 @@ def qasm_name(self) -> str: f'Controlled gate {_core_gate} with {self.num_controls} ' 'controls is not a standard OpenQASM 2.0 identifier. ' 'To encode this gate, try decomposing it into gates with' - 'standard identifiers.' + 'standard identifiers.', ) return qasm_name