diff --git a/src/qibo/_openqasm.py b/src/qibo/_openqasm.py index 3bd0d44a80..2eb6a7bcff 100644 --- a/src/qibo/_openqasm.py +++ b/src/qibo/_openqasm.py @@ -20,32 +20,72 @@ class DefinedGate: """ def __init__( - self, gates: list, qubits: Union[list, tuple], args: Union[list, tuple] + self, + name: str, + gates: list, + qubits: Union[list, tuple], + args: Union[list, tuple], ): + self.name = name self.gates = gates self.qubits = qubits self.args = args - def get_gates(self, qubits: Union[list, tuple], args: Union[list, tuple]): - """Return the list of gates composing the defined gate applied on the - specified qubits with the specified ``args``. + def get_gate(self, qubits: Union[list, tuple], args: Union[list, tuple]): + """Returns the gates composing the defined gate applied on the + specified qubits with the specified ``args`` as a unique :class:`qibo.gates.special.FusedGate`. Args: qubits (list or tuple): Qubits where to apply the gates. args (list or tuple): Arguments to evaluate the gates on. Returns: - list: gates of the composed gate evaluated on the input qubits with the input arguments. + :class:`qibo.gates.special.FusedGate`: the composed gate evaluated on the input qubits with the input arguments. """ qubit_map = dict(zip(self.qubits, qubits)) args_map = dict(zip(self.args, args)) - gates = [] - for gate in self.gates: - new_qubits = [qubit_map[q] for q in gate.qubits] - kwargs = gate.init_kwargs - new_args = [args_map.get(arg, arg) for arg in kwargs.values()] - gates.append(gate.__class__(*new_qubits, *new_args)) - return gates + return self._construct_fused_gate(self.gates, qubits, qubit_map, args_map) + + def _construct_fused_gate(self, gates, qubits, qubit_map, args_map): + """Constructs a :class:`qibo.gates.special.FusedGate` out of the provided list of gates on the specified qubits. + + Args: + gates (list(:class:`qibo.gates.Gate`)): List of gates to build the fused gate from. + qubits (list(int)): List of qubits to construct the gate on. + qubit_map (dict): Mapping between the placeholders for the qubits contained in `gates` and the actual qubits indices to apply them on. + args_map (dict): Mapping between the placeholders for the kwargs contained in `gates` and the actual kwargs values. + + Returns: + (qibo.gates.special.FusedGate): The resulting fused gate. + """ + fused_gate = FusedGate(*qubits) + for gate in gates: + if not isinstance(gate, FusedGate): + new_qubits, new_args = self._compile_gate_qubits_and_args( + gate, qubit_map, args_map + ) + fused_gate.append(gate.__class__(*new_qubits, *new_args)) + else: + qubits = [qubit_map[q] for q in gate.qubits] + fused_gate.append( + self._construct_fused_gate(gate.gates, qubits, qubit_map, args_map) + ) + return fused_gate + + def _compile_gate_qubits_and_args(self, gate, qubit_map, args_map): + """Compile the qubits and arguments placeholders contained in the input gate with their actual values. + + Args: + gate (:class:`qibo.gates.Gate`): The input gate containing the qubits and arguments placeholders. + qubit_map (dict): Mapping between the placeholders for the qubits contained in `gate` and the actual qubits indices to apply them on. + args_map (dict): Mapping between the placeholders for the kwargs contained in `gate` and the actual kwargs values. + + Returns: + tuple(list, list): The compiled qubits and arguments. + """ + new_qubits = [qubit_map[q] for q in gate.qubits] + new_args = [args_map.get(arg, arg) for arg in gate.init_kwargs.values()] + return new_qubits, new_args def _qibo_gate_name(gate): @@ -94,8 +134,7 @@ def to_circuit( nqubits = 0 for statement in parsed.statements: if isinstance(statement, openqasm3.ast.QuantumGate): - for gate in self._get_gate(statement): - gates.append(gate) + gates.append(self._get_gate(statement)) elif isinstance(statement, openqasm3.ast.QuantumMeasurementStatement): gates.append(self._get_measurement(statement)) elif isinstance(statement, openqasm3.ast.QubitDeclaration): @@ -160,11 +199,9 @@ def _get_gate(self, gate): # check whether the gate exists in qibo.gates already if _qibo_gate_name(gate.name.name) in dir(qibo.gates): try: - gates = [ - getattr(qibo.gates, _qibo_gate_name(gate.name.name))( - *qubits, *init_args - ) - ] + gate = getattr(qibo.gates, _qibo_gate_name(gate.name.name))( + *qubits, *init_args + ) # the gate exists in qibo.gates but invalid construction except TypeError: raise_error( @@ -173,7 +210,7 @@ def _get_gate(self, gate): # check whether the gate was defined by the user elif gate.name.name in self.defined_gates: try: - gates = self.defined_gates.get(gate.name.name).get_gates( + gate = self.defined_gates.get(gate.name.name).get_gate( qubits, init_args ) # the gate exists in self.defined_gates but invalid construction @@ -184,7 +221,7 @@ def _get_gate(self, gate): # undefined gate else: raise_error(ValueError, f"Undefined gate at span: {gate.span}") - return gates + return gate def _unroll_expression(self, expr): """Unrolls an argument definition expression to retrieve the @@ -213,8 +250,8 @@ def _def_gate(self, definition): name = definition.name.name qubits = [self._get_qubit(q) for q in definition.qubits] args = [self._unroll_expression(expr) for expr in definition.arguments] - gates = [g for gate in definition.body for g in self._get_gate(gate)] - self.defined_gates.update({name: DefinedGate(gates, qubits, args)}) + gates = [self._get_gate(gate) for gate in definition.body] + self.defined_gates.update({name: DefinedGate(name, gates, qubits, args)}) def _reorder_registers(self, measurements): """Reorders the registers of the provided :class:`qibo.gates.measurements.M` diff --git a/tests/test_models_circuit_qasm.py b/tests/test_models_circuit_qasm.py index e9717c9af5..867aec5a8e 100644 --- a/tests/test_models_circuit_qasm.py +++ b/tests/test_models_circuit_qasm.py @@ -488,7 +488,7 @@ def test_from_qasm_invalid_parametrized_gates(): c = Circuit.from_qasm(target) -def test_from_qasm_gate_command(): +def test_from_qasm_gate_command(backend): target = """OPENQASM 2.0; include "qelib1.inc"; gate bob(theta,alpha) q0,q1 { h q1; cx q0,q1; rz(theta) q1; rx(alpha) q0; h q1; } @@ -497,18 +497,26 @@ def test_from_qasm_gate_command(): bob(-pi/2,pi) q[0],q[2]; alice q[1],q[0];""" c = Circuit.from_qasm(target) - for i in range(2): - assert isinstance(c.queue[0 + 5 * i], gates.H) - assert isinstance(c.queue[1 + 5 * i], gates.CNOT) - assert isinstance(c.queue[2 + 5 * i], gates.RZ) - assert isinstance(c.queue[3 + 5 * i], gates.RX) - assert isinstance(c.queue[4 + 5 * i], gates.H) - assert isinstance(c.queue[10], gates.X) - assert isinstance(c.queue[11], gates.H) - assert isinstance(c.queue[12], gates.CNOT) - assert isinstance(c.queue[13], gates.RZ) - assert isinstance(c.queue[14], gates.RX) - assert isinstance(c.queue[15], gates.H) + print(c.draw()) + + def bob(theta, alpha, q0, q1): + gate = gates.FusedGate(q0, q1) + gate.append(gates.H(q1)) + gate.append(gates.CNOT(q0, q1)) + gate.append(gates.RZ(q1, theta=theta)) + gate.append(gates.RX(q0, theta=alpha)) + gate.append(gates.H(q1)) + return gate + + def alice(q0, q1): + gate = gates.FusedGate(q0, q1) + gate.append(bob(np.pi / 4, np.pi, q0, q1)) + gate.append(gates.X(q0)) + gate.append(bob(-np.pi / 4, np.pi / 2, q0, q1)) + return gate + + backend.assert_allclose(c.queue[0].matrix(), bob(-np.pi / 2, np.pi, 0, 2).matrix()) + backend.assert_allclose(c.queue[1].matrix(), alice(1, 0).matrix()) def test_from_qasm_unsupported_statement():