diff --git a/doc/source/code-examples/advancedexamples.rst b/doc/source/code-examples/advancedexamples.rst index be9f33d463..5fce706dc9 100644 --- a/doc/source/code-examples/advancedexamples.rst +++ b/doc/source/code-examples/advancedexamples.rst @@ -2331,6 +2331,100 @@ Setting an empty transpiler is equivalent to disabling transpilation. set_transpiler(Passes()) + +.. _tutorials_set_transpiler: + +How to optimize a circuit during transpilation? +----------------------------------------------- + +This process optimizes quantum circuits by detecting and removing gates +that do not affect the final output. Such gates may cancel each other out or +simply have no operational impact. Eliminating them shortens the circuit depth, +reduces resource consumption, and enhances execution on quantum hardware. + +In the current Qibo version, five techniques are available: + +- Preprocessing (:class:`qibo.transpiler.optimizer.Preprocessing`): pads the circuit with unused qubits to match the number of physical qubits. +- Rearrange (:class:`qibo.transpiler.optimizer.Rearrange`): rearranges gates using Qibo's fusion algorithm. +- InverseCancellation (:class:`qibo.transpiler.optimizer.InverseCancellation`): eliminates adjacent gate pairs in the quantum circuit. +- RotationGateFusion (:class:`qibo.transpiler.optimizer.RotationGateFusion`): combines RX, RY, and RZ rotation gates in a quantum circuit. +- U3GateFusion (:class:`qibo.transpiler.optimizer.U3GateFusion`): merges pairs of U3 gates in the circuit. + + +In this example, we used the following passes to transpile the circuit on a star-shaped hardware connectivity, +optimize the circuit, and target a custom set of native gates: +:class:`qibo.transpiler.optimizer.InverseCancellation`, +:class:`qibo.transpiler.optimizer.RotationGateFusion`, +:class:`qibo.transpiler.optimizer.Preprocessing`, +:class:`qibo.transpiler.placer.Random`, +:class:`qibo.transpiler.router.ShortestPaths`, +:class:`qibo.transpiler.unroller.Unroller`, and +:class:`qibo.transpiler.optimizer.U3GateFusion`. + + +.. testcode:: python + + import networkx as nx + + from qibo import gates + from qibo.models import Circuit + from qibo.transpiler.asserts import assert_transpiling + + from qibo.transpiler import ( + NativeGates, + Passes, + Preprocessing, + ShortestPaths, + Unroller, + InverseCancellation, + RotationGateFusion, + U3GateFusion + ) + + # Define hardware connectivity + def star_connectivity(): + chip = nx.Graph([("q0", "q2"), ("q1", "q2"), ("q2", "q3"), ("q2", "q4")]) + return chip + + # Define a custom set of native gates + glist = [gates.U3, gates.RZ, gates.Z, gates.CZ] + natives = NativeGates(0).from_gatelist(glist) + + # Define a custom transpiler pipeline + custom_passes = [InverseCancellation(), RotationGateFusion(), Preprocessing(), Random(), ShortestPaths(), + Unroller(native_gates=natives), U3GateFusion()] + + # Create a transpiler pipeline with hardware configuration + transpiler = Passes(custom_passes, connectivity=star_connectivity()) + + + # Create a quantum circuit with 5 qubits + # Define the hardware qubit names to be used in wire_names + circuit = Circuit(5, wire_names=["q0", "q1", "q2", "q3", "q4"]) + circuit.add(gates.H(0)) + circuit.add(gates.H(0)) + circuit.add(gates.CNOT(0, 2)) + circuit.add(gates.CNOT(3, 4)) + circuit.add(gates.X(1)) + circuit.add(gates.X(1)) + circuit.add(gates.Z(4)) + circuit.add(gates.Z(4)) + circuit.add(gates.RX(1, 3.14)) + circuit.add(gates.RY(1, 3.14)) + + # Transpile the circuit using the custom transpiler pipeline + transpiled_circuit, final_layout = transpiler(circuit) + + # Verify that the transpiled circuit can be executed on the hardware (optional) + assert_transpiling( + original_circuit=circuit, + transpiled_circuit=transpiled_circuit, + connectivity=star_connectivity(), + final_layout=final_layout, + native_gates=natives + ) + + .. _gst_example: How to perform Gate Set Tomography? diff --git a/src/qibo/transpiler/__init__.py b/src/qibo/transpiler/__init__.py index 5a37d8818d..29022f1fe5 100644 --- a/src/qibo/transpiler/__init__.py +++ b/src/qibo/transpiler/__init__.py @@ -1,4 +1,10 @@ -from qibo.transpiler.optimizer import Preprocessing, Rearrange +from qibo.transpiler.optimizer import ( + InverseCancellation, + Preprocessing, + Rearrange, + RotationGateFusion, + U3GateFusion, +) from qibo.transpiler.pipeline import Passes from qibo.transpiler.placer import ( Random, diff --git a/src/qibo/transpiler/optimizer.py b/src/qibo/transpiler/optimizer.py index 4f46325c16..9ce65cff00 100644 --- a/src/qibo/transpiler/optimizer.py +++ b/src/qibo/transpiler/optimizer.py @@ -1,6 +1,7 @@ from typing import Optional import networkx as nx +import numpy as np from qibo import gates from qibo.config import raise_error @@ -66,3 +67,309 @@ def __call__(self, circuit: Circuit): else: new.add(fgate) return new + + +class InverseCancellation(Optimizer): + + def __init__(self): + """Identify and remove pairs of adjacent gates from + a quantum circuit. + """ + + self.inverse_gates = ( + gates.H | gates.Y | gates.Z | gates.X | gates.CNOT | gates.CZ | gates.SWAP + ) + self.pos_rm_inv_gate = [] + + def __call__(self, circuit: Circuit) -> Circuit: + """Perform pattern recognition to detect and eliminate adjacent gate pairs + in the quantum circuit. + + Args: + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to have gates identified. + + Returns: + circuit (:class:`qibo.models.circuit.Circuit`): A new circuit with the pairs of + adjacent gates removed. + """ + + if 0 == circuit.ngates or circuit.gates_of_type(gates.FusedGate): + return circuit + + self._find_pos_rm(circuit.nqubits, circuit.queue) + + if 0 == len(self.pos_rm_inv_gate): + return circuit + + tmp_new_circuit = circuit.copy(True) + new_circuit = circuit.__class__(**tmp_new_circuit.init_kwargs) + for i, gate in enumerate(tmp_new_circuit.queue): + if not i in self.pos_rm_inv_gate: + new_circuit.add(gate) + return new_circuit + + def _find_pos_rm(self, nqubits: int, list_gates: list): + """Identify and mark pairs of inverse gates that + can be removed from the circuit. + + Args: + nqubits (int): number of qubits. + list_gates (list): a list of gates (:class:`qibo.gates.abstract.Gate`). + """ + + previous_gates = [None] * nqubits + + for i, gate in enumerate(list_gates): + + primary_qubit = gate.init_args[0] + other_qubits = gate.init_args[1:] + + if previous_gates[primary_qubit] is None: + self._update_previous_gates(gate.init_args, i, gate, previous_gates) + + elif isinstance(gate, self.inverse_gates): + same_gate = self._same_gates(gate, previous_gates[primary_qubit][1]) + + secondary_match = True + if other_qubits: # for multi-qubits gates + conditions = [] + + for q in other_qubits: + has_previous_gate = previous_gates[q] is not None + is_same_gate = self._same_gates(gate, previous_gates[q][1]) + conditions.append(has_previous_gate and is_same_gate) + + secondary_match = all(conditions) + + if same_gate and secondary_match: + self.pos_rm_inv_gate.extend([previous_gates[primary_qubit][0], i]) + + for q in gate.init_args: + previous_gates[q] = None + else: + self._update_previous_gates(gate.init_args, i, gate, previous_gates) + else: + self._update_previous_gates(gate.init_args, i, gate, previous_gates) + + @staticmethod + def _update_previous_gates(qubits, idx, gate, previous_gates): + """Helper function to update previous gate tracking.""" + + for q in qubits: + previous_gates[q] = (idx, gate) + + @staticmethod + def _same_gates(gate1: gates.Gate, gate2: gates.Gate) -> bool: + """Determine whether two gates are considered the same. + + Args: + gate1: The first gate (:class:`qibo.gates.abstract.Gate`). + gate2: The second gate (:class:`qibo.gates.abstract.Gate`). + + Returns: + True if the gates are the same, otherwise False. + """ + + if gate1 is None or gate2 is None: + return False + + paramaters_gate1 = (gate1.name, gate1.init_args, gate1.init_kwargs) + paramaters_gate2 = (gate2.name, gate2.init_args, gate2.init_kwargs) + + return bool(paramaters_gate1 == paramaters_gate2) + + +class RotationGateFusion(Optimizer): + + def __init__(self): + """Identify and fuse rotated gates (RX, RY, RZ) from + a quantum circuit. + """ + + self.rotated_gates = gates.RX | gates.RY | gates.RZ + self.gates = [] + + def __call__(self, circuit: Circuit) -> Circuit: + """Find and combine RX, RY, and RZ rotation gates in a quantum circuit. + + Args: + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to have gates identified. + + Returns: + circuit (:class:`qibo.models.circuit.Circuit`): A new circuit with the + fuse-rotated gates removed + """ + + if 0 == circuit.ngates or circuit.gates_of_type(gates.FusedGate): + return circuit + + tmp_new_circuit = circuit.copy(True) + self._merge_rotation_gates(tmp_new_circuit.nqubits, tmp_new_circuit.queue) + + new_circuit = circuit.__class__(**tmp_new_circuit.init_kwargs) + for gate in self.gates: + new_circuit.add(gate) + return new_circuit + + def _merge_rotation_gates(self, nqubits: int, list_gates: list): + """Identify and accumulate rotation angles for + consecutive rotation gates of the same type. + + Args: + nqubits (int): number of qubits. + list_gates (list): a list of gates (:class:`qibo.gates.abstract.Gate`). + """ + + previous_gates = [None] * nqubits + + for gate in list_gates: + + primary_qubit = gate.init_args[0] + + if isinstance(gate, self.rotated_gates): + prev_gate = previous_gates[primary_qubit] + if isinstance(prev_gate, gate.__class__): + tmp_gate = gate.__class__( + primary_qubit, prev_gate.parameters[0] + gate.parameters[0] + ) + previous_gates[primary_qubit] = tmp_gate + else: + if isinstance(prev_gate, self.rotated_gates): + self.gates.append(prev_gate) + previous_gates[primary_qubit] = gate + else: + # Flush stored rotations before adding new gate + for q in gate.init_args: + if isinstance(previous_gates[q], self.rotated_gates): + self.gates.append(previous_gates[q]) + previous_gates[q] = None + + self.gates.append(gate) + for q in gate.qubits: + previous_gates[q] = gate + + # Append any remaining rotation gates + self.gates.extend( + g for g in previous_gates if isinstance(g, self.rotated_gates) + ) + + +class U3GateFusion(Optimizer): + + def __init__(self): + """Merge pairs of U3 gates in the circuit""" + + self.gates = [] + + def __call__(self, circuit: Circuit) -> Circuit: + """Optimize the circuit by merging U3 gate pairs. + + Args: + circuit (:class:`qibo.models.circuit.Circuit`): Circuit to have gates identified. + + Returns: + circuit (:class:`qibo.models.circuit.Circuit`): A new circuit where pairs of + U3 gates were merged. + """ + + if 0 == circuit.ngates or circuit.gates_of_type(gates.FusedGate): + return circuit + + tmp_new_circuit = circuit.copy(True) + self._merge_u3gates(tmp_new_circuit.nqubits, tmp_new_circuit.queue) + + new_circuit = circuit.__class__(**tmp_new_circuit.init_kwargs) + + for gate in self.gates: + new_circuit.add(gate) + return new_circuit + + def _merge_u3gates(self, nqubits: int, list_gates: list): + """Identify pairs of U3 gates that can be merged. + + Args: + nqubits (int): number of qubits. + list_gates (list): a list of gates (:class:`qibo.gates.abstract.Gate`). + """ + + previous_gates = [None] * nqubits + + for gate in list_gates: + + primary_qubit = gate.init_args[0] # Extract primary qubit + + if isinstance(gate, gates.U3): + prev_gate = previous_gates[primary_qubit] + if isinstance(prev_gate, gates.U3): + tmp_gate = self._create_u3_fusion(primary_qubit, prev_gate, gate) + previous_gates[primary_qubit] = tmp_gate + else: + previous_gates[primary_qubit] = gate + else: + # Flush stored U3 before adding new gate + for q in gate.init_args: + if isinstance(previous_gates[q], gates.U3): + self.gates.append(previous_gates[q]) + previous_gates[q] = None + + self.gates.append(gate) + for q in gate.qubits: + previous_gates[q] = gate # Track current gate + + # Append any remaining U3 gates in one pass + self.gates.extend(g for g in previous_gates if isinstance(g, gates.U3)) + + @staticmethod + def _extract_u3_params(unitary_matrix: np.ndarray): + """Extract the theta, phi, and lambda parameters from a fused U3 unitary matrix. + + Args: + unitary_matrix (np.ndarray): a unitary matrix. + """ + + theta_r = 2 * np.arccos( + np.sqrt(np.abs(unitary_matrix[0, 0] * unitary_matrix[1, 1])) + ) + sin_r = np.sin(theta_r / 2) + cos_r = np.cos(theta_r / 2) + + if 0 == cos_r: + lambda_r = -1j * np.log(-1 * unitary_matrix[0, 1]) + phi_r = -1j * np.log(unitary_matrix[1, 0]) + elif 0 == sin_r: + lambda_r = -1j * np.log(unitary_matrix[1, 1]) + phi_r = 1j * np.log(unitary_matrix[0, 0]) + else: + phi_r = -1j * np.log( + (unitary_matrix[1, 0] * unitary_matrix[1, 1]) / (sin_r * cos_r) + ) + lambda_r = -1j * np.log( + (sin_r * unitary_matrix[1, 1]) / (unitary_matrix[1, 0] * cos_r) + ) + + return theta_r, np.real(phi_r), np.real(lambda_r) + + @staticmethod + def _create_u3_fusion( + qubit: int, gate1: gates.Gate, gate2: gates.Gate + ) -> gates.Gate: + """Create a U3 gate using two U3 gates. + + Args: + qubit: Ids of the qubit to apply the gate U3. + gate1: The first gate (:class:`qibo.gates.abstract.Gate`). + gate2: The second gate (:class:`qibo.gates.abstract.Gate`). + + Returns: + :class:`qibo.gates.Gate`: a gate representing a fused U3 unitary matrix. + """ + + if gate1 is None or gate2 is None: + raise_error(ValueError, "_create_u3_fusion: a/two gate/s is/are None.") + + if not isinstance(gate1, gates.U3) or not isinstance(gate2, gates.U3): + raise_error(ValueError, "_create_u3_fusion: a/two gate/s is/are not U3.") + + u_final = np.dot(gate2.matrix(), gate1.matrix()) + theta, phi, lam = U3GateFusion._extract_u3_params(u_final) + return gates.U3(qubit, theta, phi, lam) diff --git a/tests/test_transpiler_optimizer.py b/tests/test_transpiler_optimizer.py index 500f507178..f40d4a9063 100644 --- a/tests/test_transpiler_optimizer.py +++ b/tests/test_transpiler_optimizer.py @@ -3,33 +3,39 @@ from qibo import gates from qibo.models import Circuit -from qibo.transpiler.optimizer import Preprocessing, Rearrange +from qibo.transpiler.optimizer import ( + InverseCancellation, + Preprocessing, + Rearrange, + RotationGateFusion, + U3GateFusion, +) def test_preprocessing_error(star_connectivity): - circ = Circuit(7) + circuit = Circuit(7) preprocesser = Preprocessing(connectivity=star_connectivity()) with pytest.raises(ValueError): - new_circuit = preprocesser(circuit=circ) + new_circuit = preprocesser(circuit=circuit) - circ = Circuit(5, wire_names=[0, 1, 2, "q3", "q4"]) + circuit = Circuit(5, wire_names=[0, 1, 2, "q3", "q4"]) with pytest.raises(ValueError): - new_circuit = preprocesser(circuit=circ) + new_circuit = preprocesser(circuit=circuit) def test_preprocessing_same(star_connectivity): - circ = Circuit(5) - circ.add(gates.CNOT(0, 1)) + circuit = Circuit(5) + circuit.add(gates.CNOT(0, 1)) preprocesser = Preprocessing(connectivity=star_connectivity()) - new_circuit = preprocesser(circuit=circ) + new_circuit = preprocesser(circuit=circuit) assert new_circuit.ngates == 1 def test_preprocessing_add(star_connectivity): - circ = Circuit(3) - circ.add(gates.CNOT(0, 1)) + circuit = Circuit(3) + circuit.add(gates.CNOT(0, 1)) preprocesser = Preprocessing(connectivity=star_connectivity()) - new_circuit = preprocesser(circuit=circ) + new_circuit = preprocesser(circuit=circuit) assert new_circuit.ngates == 1 assert new_circuit.nqubits == 5 @@ -43,3 +49,493 @@ def test_fusion(): fusion = Rearrange(max_qubits=1) fused_circ = fusion(circuit) assert isinstance(fused_circ.queue[0], gates.Unitary) + + +def test_inversecancellation_no_gates(backend): + circuit = Circuit(1) + target = circuit.unitary(backend) + + inverse_cancellation = InverseCancellation() + new_circuit = inverse_cancellation(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_inversecancellation_fusedgates(backend): + circuit = Circuit(1) + circuit.add(gates.H(0)) + circuit.add(gates.X(0)) + fused_circuit = circuit.fuse() + target = fused_circuit.unitary(backend) + + inverse_cancellation = InverseCancellation() + new_circuit = inverse_cancellation(circuit=fused_circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_inversecancellation_gates_x(backend): + circuit = Circuit(1) + circuit.add(gates.X(0)) + circuit.add(gates.X(0)) + target = circuit.unitary(backend) + + inverse_cancellation = InverseCancellation() + new_circuit = inverse_cancellation(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_inversecancellation_gates_y(backend): + circuit = Circuit(1) + circuit.add(gates.Y(0)) + circuit.add(gates.Y(0)) + target = circuit.unitary(backend) + + inverse_cancellation = InverseCancellation() + new_circuit = inverse_cancellation(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_inversecancellation_gates_z(backend): + circuit = Circuit(1) + circuit.add(gates.Z(0)) + circuit.add(gates.Z(0)) + target = circuit.unitary(backend) + + inverse_cancellation = InverseCancellation() + new_circuit = inverse_cancellation(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_inversecancellation_gates_h(backend): + circuit = Circuit(1) + circuit.add(gates.H(0)) + circuit.add(gates.H(0)) + target = circuit.unitary(backend) + + inverse_cancellation = InverseCancellation() + new_circuit = inverse_cancellation(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_inversecancellation_gates_cnot(backend): + circuit = Circuit(2) + circuit.add(gates.CNOT(0, 1)) + circuit.add(gates.CNOT(0, 1)) + target = circuit.unitary(backend) + + inverse_cancellation = InverseCancellation() + new_circuit = inverse_cancellation(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_inversecancellation_gates_cnot_x_q1(backend): + circuit = Circuit(2) + circuit.add(gates.CNOT(0, 1)) + circuit.add(gates.X(1)) + circuit.add(gates.CNOT(0, 1)) + circuit.add(gates.X(1)) + target = circuit.unitary(backend) + + inverse_cancellation = InverseCancellation() + new_circuit = inverse_cancellation(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_inversecancellation_gates_cnot_x_q0(backend): + circuit = Circuit(2) + circuit.add(gates.CNOT(0, 1)) + circuit.add(gates.X(0)) + circuit.add(gates.CNOT(0, 1)) + circuit.add(gates.X(0)) + target = circuit.unitary(backend) + + inverse_cancellation = InverseCancellation() + new_circuit = inverse_cancellation(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_inversecancellation_gates_cz(backend): + circuit = Circuit(2) + circuit.add(gates.CZ(0, 1)) + circuit.add(gates.CZ(0, 1)) + target = circuit.unitary(backend) + + inverse_cancellation = InverseCancellation() + new_circuit = inverse_cancellation(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_inversecancellation_gates_cz_x_q0(backend): + circuit = Circuit(2) + circuit.add(gates.CZ(0, 1)) + circuit.add(gates.X(0)) + circuit.add(gates.CZ(0, 1)) + circuit.add(gates.X(0)) + target = circuit.unitary(backend) + + inverse_cancellation = InverseCancellation() + new_circuit = inverse_cancellation(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_inversecancellation_gates_cz_x_q1(backend): + circuit = Circuit(2) + circuit.add(gates.CZ(0, 1)) + circuit.add(gates.X(1)) + circuit.add(gates.CZ(0, 1)) + circuit.add(gates.X(1)) + target = circuit.unitary(backend) + + inverse_cancellation = InverseCancellation() + new_circuit = inverse_cancellation(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_inversecancellation_gates_swap(backend): + circuit = Circuit(2) + circuit.add(gates.SWAP(0, 1)) + circuit.add(gates.SWAP(0, 1)) + target = circuit.unitary(backend) + + inverse_cancellation = InverseCancellation() + new_circuit = inverse_cancellation(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_inversecancellation_gates_swap_x_q0(backend): + circuit = Circuit(2) + circuit.add(gates.SWAP(0, 1)) + circuit.add(gates.X(0)) + circuit.add(gates.SWAP(0, 1)) + circuit.add(gates.X(0)) + target = circuit.unitary(backend) + + inverse_cancellation = InverseCancellation() + new_circuit = inverse_cancellation(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_inversecancellation_gates_swap_x_q1(backend): + circuit = Circuit(2) + circuit.add(gates.SWAP(0, 1)) + circuit.add(gates.X(1)) + circuit.add(gates.SWAP(0, 1)) + circuit.add(gates.X(1)) + target = circuit.unitary(backend) + + inverse_cancellation = InverseCancellation() + new_circuit = inverse_cancellation(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_inversecancellation_gates_various(backend): + circuit = Circuit(3) + circuit.add(gates.SWAP(0, 1)) + circuit.add(gates.TOFFOLI(0, 1, 2)) + circuit.add(gates.SWAP(0, 1)) + circuit.add(gates.X(1)) + target = circuit.unitary(backend) + + inverse_cancellation = InverseCancellation() + new_circuit = inverse_cancellation(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_rotationgatefusion_no_gates(backend): + circuit = Circuit(1) + target = circuit.unitary(backend) + + rotation_gate_fusion = RotationGateFusion() + new_circuit = rotation_gate_fusion(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_rotationgatefusion_fusedgates(backend): + circuit = Circuit(2) + circuit.add(gates.H(0)) + circuit.add(gates.X(1)) + fused_circuit = circuit.fuse() + target = fused_circuit.unitary(backend) + + rotation_gate_fusion = RotationGateFusion() + new_circuit = rotation_gate_fusion(circuit=fused_circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_rotationgatefusion_rx_rx(backend): + circuit = Circuit(1) + circuit.add(gates.RX(0, 3.15)) + circuit.add(gates.RX(0, 3.15)) + target = circuit.unitary(backend) + + rotation_gate_fusion = RotationGateFusion() + new_circuit = rotation_gate_fusion(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_rotationgatefusion_ry(backend): + circuit = Circuit(1) + circuit.add(gates.RY(0, 3.15)) + target = circuit.unitary(backend) + + rotation_gate_fusion = RotationGateFusion() + new_circuit = rotation_gate_fusion(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_rotationgatefusion_ry_ry(backend): + circuit = Circuit(1) + circuit.add(gates.RY(0, 3.15)) + circuit.add(gates.RY(0, 3.15)) + target = circuit.unitary(backend) + + rotation_gate_fusion = RotationGateFusion() + new_circuit = rotation_gate_fusion(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_rotationgatefusion_rz_rz(backend): + circuit = Circuit(1) + circuit.add(gates.RZ(0, 3.15)) + circuit.add(gates.RZ(0, 3.15)) + target = circuit.unitary(backend) + + rotation_gate_fusion = RotationGateFusion() + new_circuit = rotation_gate_fusion(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_rotationgatefusion_rx_ry(backend): + circuit = Circuit(1) + circuit.add(gates.RX(0, 3.15)) + circuit.add(gates.RY(0, 3.15)) + target = circuit.unitary(backend) + + rotation_gate_fusion = RotationGateFusion() + new_circuit = rotation_gate_fusion(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_rotationgatefusion_rxs_cnot(backend): + circuit = Circuit(2) + circuit.add(gates.CNOT(0, 1)) + circuit.add(gates.RX(0, 3.15)) + circuit.add(gates.CNOT(1, 0)) + circuit.add(gates.RX(0, 3.15)) + target = circuit.unitary(backend) + + rotation_gate_fusion = RotationGateFusion() + new_circuit = rotation_gate_fusion(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_rotationgatefusion_rxs_swaps(backend): + circuit = Circuit(2) + circuit.add(gates.SWAP(0, 1)) + circuit.add(gates.RX(0, 3.15)) + circuit.add(gates.SWAP(0, 1)) + circuit.add(gates.RX(0, 3.15)) + target = circuit.unitary(backend) + + rotation_gate_fusion = RotationGateFusion() + new_circuit = rotation_gate_fusion(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_rotationgatefusion_rxs_toffoli(backend): + circuit = Circuit(3) + circuit.add(gates.RX(0, 3.15)) + circuit.add(gates.TOFFOLI(0, 1, 2)) + circuit.add(gates.RX(0, 3.15)) + target = circuit.unitary(backend) + + rotation_gate_fusion = RotationGateFusion() + new_circuit = rotation_gate_fusion(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_rotationgatefusion_rx_toffoli(backend): + circuit = Circuit(3) + circuit.add(gates.TOFFOLI(0, 1, 2)) + circuit.add(gates.RX(0, 3.15)) + circuit.add(gates.RX(0, 3.15)) + target = circuit.unitary(backend) + + rotation_gate_fusion = RotationGateFusion() + new_circuit = rotation_gate_fusion(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_u3gatefusion_no_gates(backend): + circuit = Circuit(1) + target = circuit.unitary(backend) + + u3_gate_fusion = U3GateFusion() + new_circuit = u3_gate_fusion(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_u3gatefusion_fusedgates(backend): + circuit = Circuit(2) + circuit.add(gates.X(0)) + circuit.add(gates.H(1)) + fused_circuit = circuit.fuse() + target = fused_circuit.unitary(backend) + + u3_gate_fusion = U3GateFusion() + new_circuit = u3_gate_fusion(circuit=fused_circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_u3gatefusion_one_u3(backend): + circuit = Circuit(1) + circuit.add(gates.U3(0, 0.5, 0.2, 0.3)) + target = circuit.unitary(backend) + + u3_gate_fusion = U3GateFusion() + new_circuit = u3_gate_fusion(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_u3gatefusion_two_u3(backend): + circuit = Circuit(1) + circuit.add(gates.U3(0, 0.5, 0.2, 0.3)) + circuit.add(gates.U3(0, 1.0, 0.4, 0.6)) + target = circuit.unitary(backend) + + u3_gate_fusion = U3GateFusion() + new_circuit = u3_gate_fusion(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_u3gatefusion_three_u3(backend): + circuit = Circuit(1) + circuit.add(gates.U3(0, 0.5, 0.2, 0.3)) + circuit.add(gates.U3(0, 1.0, 0.4, 0.6)) + circuit.add(gates.U3(0, 1.0, 0.4, 0.6)) + target = circuit.unitary(backend) + + u3_gate_fusion = U3GateFusion() + new_circuit = u3_gate_fusion(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_u3gatefusion_two_u3_diff(backend): + circuit = Circuit(2) + circuit.add(gates.U3(0, 0.5, 0.2, 0.3)) + circuit.add(gates.U3(1, 0.5, 0.2, 0.3)) + target = circuit.unitary(backend) + + u3_gate_fusion = U3GateFusion() + new_circuit = u3_gate_fusion(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_u3gatefusion_two_u3_diff_cnot(backend): + circuit = Circuit(2) + circuit.add(gates.U3(0, 0.5, 0.2, 0.3)) + circuit.add(gates.CNOT(0, 1)) + circuit.add(gates.U3(1, 0.5, 0.2, 0.3)) + target = circuit.unitary(backend) + + u3_gate_fusion = U3GateFusion() + new_circuit = u3_gate_fusion(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_u3gatefusion_two_u3_cnot(backend): + circuit = Circuit(2) + circuit.add(gates.U3(0, 0.5, 0.2, 0.3)) + circuit.add(gates.CNOT(0, 1)) + circuit.add(gates.U3(0, 0.5, 0.2, 0.3)) + target = circuit.unitary(backend) + + u3_gate_fusion = U3GateFusion() + new_circuit = u3_gate_fusion(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target) + + +def test_u3gatefusion_u3_various_gates(backend): + circuit = Circuit(2) + circuit.add(gates.RX(0, 0.5)) + circuit.add(gates.U3(0, 0.5, 0.2, 0.3)) + circuit.add(gates.U3(0, 0.5, 0.2, 0.3)) + circuit.add(gates.CNOT(0, 1)) + circuit.add(gates.U3(0, 0.5, 0.2, 0.3)) + circuit.add(gates.U3(1, 0.5, 0.2, 0.3)) + target = circuit.unitary(backend) + + u3_gate_fusion = U3GateFusion() + new_circuit = u3_gate_fusion(circuit=circuit) + unitary = new_circuit.unitary(backend) + + backend.assert_allclose(unitary, target)