diff --git a/docs/tutorial.md b/docs/tutorial.md index 7cb13be0..29633632 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -152,20 +152,24 @@ except Exception as e: ## Modifying a circuit -### Merging gates +### Merging single qubit gates -OpenSquirrel can merge consecutive quantum gates. Currently, this is only done for single-qubit gates. The resulting gate is labeled as an "anonymous gate". Since those gates have no name, the placeholder `` is used instead. +All single-qubit gates appearing in a circuit can be merged by applying `merge_single_qubit_gates()` to the circuit. +Note that multi-qubit gates remain untouched and single-qubit gates are not merged across any multi-qubit gates. +The gate that results from the merger of single-qubit gates will, in general, comprise an arbitrary rotation and, therefore, not be a known gate. +In OpenSquirrel an unrecognized gate is deemed _anonymous_. +When a circuit that contains anonymous gates is written to a cQASM string, the semantic representation of the anonymous gate is exported. +Note that the semantic representation of an anonymous gate is not compliant cQASM. ```python import math -builder = CircuitBuilder(qubit_register_size=1) -for i in range(16): - builder.rx(Qubit(0), Float(math.pi / 16)) +builder = CircuitBuilder(1) +for i in range(4): + builder.Rx(Qubit(0), Float(math.pi / 4)) circuit = builder.to_circuit() -# Merge single qubit gates circuit.merge_single_qubit_gates() circuit ``` @@ -174,15 +178,7 @@ circuit qubit[1] q - - -You can inspect what the gate has become in terms of the Bloch sphere rotation it represents: - -```python -circuit.ir.statements[0] -``` - - BlochSphereRotation(Qubit[0], axis=[1. 0. 0.], angle=3.141592653589795, phase=0.0) + Anonymous gate: BlochSphereRotation(Qubit[0], axis=[1. 0. 0.], angle=3.14159, phase=0.0) In the above example, OpenSquirrel has merged all the Rx gates together. Yet, for now, OpenSquirrel does not recognize that this results in a single Rx over the cumulated angle of the individual rotations. Moreover, it does not recognize that the result corresponds to the X-gate (up to a global phase difference). At a later stage, we may want OpenSquirrel to recognize the resultant gate in the case it is part of the set of known gates. diff --git a/opensquirrel/ir.py b/opensquirrel/ir.py index 068c91ae..be664d13 100644 --- a/opensquirrel/ir.py +++ b/opensquirrel/ir.py @@ -12,6 +12,14 @@ from opensquirrel.common import ATOL, are_matrices_equivalent_up_to_global_phase, normalize_angle +REPR_DECIMALS = 5 + + +def repr_round( + value: float | Axis | NDArray[np.complex64], decimals: int = REPR_DECIMALS +) -> float | Axis | NDArray[np.complex64]: + return np.round(value, decimals) + class IRVisitor(ABC): def visit_comment(self, comment: Comment) -> Any: @@ -247,6 +255,7 @@ def get_qubit_operands(self) -> list[Qubit]: class Gate(Statement, ABC): + def __init__( self, generator: Callable[..., Gate] | None = None, @@ -265,7 +274,9 @@ def __eq__(self, other: object) -> bool: @property def name(self) -> str: - return self.generator.__name__ if self.generator else "" + if self.generator: + return self.generator.__name__ + return "Anonymous gate: " + self.__repr__() @property def is_anonymous(self) -> bool: @@ -309,7 +320,10 @@ def identity(q: Qubit) -> BlochSphereRotation: return BlochSphereRotation(qubit=q, axis=(1, 0, 0), angle=0, phase=0) def __repr__(self) -> str: - return f"BlochSphereRotation({self.qubit}, axis={self.axis}, angle={self.angle}, phase={self.phase})" + return ( + f"BlochSphereRotation({self.qubit}, axis={repr_round(self.axis)}, angle={repr_round(self.angle)}," + f" phase={repr_round(self.phase)})" + ) def __eq__(self, other: object) -> bool: if not isinstance(other, BlochSphereRotation): @@ -355,7 +369,7 @@ def __init__( self.operands = operands def __repr__(self) -> str: - return f"MatrixGate(qubits={self.operands}, matrix={self.matrix})" + return f"MatrixGate(qubits={self.operands}, matrix={repr_round(self.matrix)})" def accept(self, visitor: IRVisitor) -> Any: visitor.visit_gate(self) diff --git a/opensquirrel/writer/writer.py b/opensquirrel/writer/writer.py index 5e7106d8..8eefb682 100644 --- a/opensquirrel/writer/writer.py +++ b/opensquirrel/writer/writer.py @@ -42,6 +42,10 @@ def visit_measure(self, measure: Measure) -> None: def visit_gate(self, gate: Gate) -> None: gate_name = gate.name if gate.is_anonymous: + if "MatrixGate" in gate_name: + # In the case of a MatrixGate the newlines should be removed from the array + # such that the array is printed on a single line. + gate_name = gate_name.replace("\n", "") self.output += f"{gate_name}\n" return if any(not isinstance(arg, Qubit) for arg in gate.arguments): # type: ignore[union-attr] diff --git a/test/docs/__init__.py b/test/docs/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/test/docs/test_tutorial.py b/test/docs/test_tutorial.py new file mode 100644 index 00000000..db7a29cf --- /dev/null +++ b/test/docs/test_tutorial.py @@ -0,0 +1,21 @@ +import math + +from opensquirrel import CircuitBuilder +from opensquirrel.ir import Float, Qubit + + +def test_anonymous_gate(): + builder = CircuitBuilder(1) + for i in range(4): + builder.Rx(Qubit(0), Float(math.pi / 4)) + circuit = builder.to_circuit() + circuit.merge_single_qubit_gates() + assert ( + str(circuit) + == """version 3.0 + +qubit[1] q + +Anonymous gate: BlochSphereRotation(Qubit[0], axis=[1. 0. 0.], angle=3.14159, phase=0.0) +""" + ) diff --git a/test/writer/test_cqasm_lite_writer.py b/test/writer/test_cqasm_lite_writer.py index b87aab09..bcd6e821 100644 --- a/test/writer/test_cqasm_lite_writer.py +++ b/test/writer/test_cqasm_lite_writer.py @@ -51,7 +51,7 @@ def test_anonymous_gate() -> None: qubit[2] q CR(1.234) q[0], q[1] - +Anonymous gate: BlochSphereRotation(Qubit[0], axis=[0.57735 0.57735 0.57735], angle=1.23, phase=0.0) CR(1.234) q[0], q[1] """ ) diff --git a/test/writer/test_writer.py b/test/writer/test_writer.py index c04dc64f..8c7e57ca 100644 --- a/test/writer/test_writer.py +++ b/test/writer/test_writer.py @@ -1,6 +1,9 @@ +import numpy as np + +from opensquirrel import CircuitBuilder from opensquirrel.circuit import Circuit from opensquirrel.default_gates import CR, H -from opensquirrel.ir import IR, BlochSphereRotation, Comment, Float, Qubit +from opensquirrel.ir import IR, BlochSphereRotation, Comment, ControlledGate, Float, MatrixGate, Qubit from opensquirrel.register_manager import QubitRegister, RegisterManager from opensquirrel.writer import writer @@ -36,21 +39,23 @@ def test_write() -> None: def test_anonymous_gate() -> None: - register_manager = RegisterManager(QubitRegister(2)) - ir = IR() - ir.add_gate(CR(Qubit(0), Qubit(1), Float(1.234))) - ir.add_gate(BlochSphereRotation(Qubit(0), axis=(1, 1, 1), angle=1.23)) - ir.add_gate(CR(Qubit(0), Qubit(1), Float(1.234))) - circuit = Circuit(register_manager, ir) - + qc = CircuitBuilder(2, 2) + qc.H(Qubit(0)) + qc.ir.add_gate(BlochSphereRotation(Qubit(0), axis=(1, 1, 1), angle=1.23)) + qc.ir.add_gate(ControlledGate(Qubit(0), BlochSphereRotation(Qubit(0), axis=(1, 1, 1), angle=1.23))) + qc.ir.add_gate(MatrixGate(np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]), [Qubit(0), Qubit(1)])) + qc.CR(Qubit(0), Qubit(1), Float(1.234)) assert ( - writer.circuit_to_string(circuit) + str(qc.to_circuit()) == """version 3.0 qubit[2] q +bit[2] b -CR(1.234) q[0], q[1] - +H q[0] +Anonymous gate: BlochSphereRotation(Qubit[0], axis=[0.57735 0.57735 0.57735], angle=1.23, phase=0.0) +Anonymous gate: ControlledGate(control_qubit=Qubit[0], BlochSphereRotation(Qubit[0], axis=[0.57735 0.57735 0.57735], angle=1.23, phase=0.0)) +Anonymous gate: MatrixGate(qubits=[Qubit[0], Qubit[1]], matrix=[[1 0 0 0] [0 1 0 0] [0 0 0 1] [0 0 1 0]]) CR(1.234) q[0], q[1] """ )