Skip to content

Commit

Permalink
refactor: adapted DefinedGate to build a unique FusedGate
Browse files Browse the repository at this point in the history
  • Loading branch information
BrunoLiegiBastonLiegi committed Feb 26, 2024
1 parent 7f3728b commit 78e6bd5
Show file tree
Hide file tree
Showing 2 changed files with 81 additions and 36 deletions.
83 changes: 60 additions & 23 deletions src/qibo/_openqasm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down Expand Up @@ -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):
Expand Down Expand Up @@ -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(
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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`
Expand Down
34 changes: 21 additions & 13 deletions tests/test_models_circuit_qasm.py
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand All @@ -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():
Expand Down

0 comments on commit 78e6bd5

Please sign in to comment.