Skip to content

Commit

Permalink
Add docstrings in fusion algorithm
Browse files Browse the repository at this point in the history
  • Loading branch information
stavros11 committed Sep 21, 2021
1 parent 95ac9db commit c3e7997
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 1 deletion.
43 changes: 43 additions & 0 deletions src/qibo/core/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ def _add_layer(self, gate):
def fuse(self):
"""Creates an equivalent :class:`qibo.core.circuit.Circuit` with gates fused up to two-qubits.
The current fusion algorithm with create up to two-qubit fused gates.
Returns:
The equivalent :class:`qibo.core.circuit.Circuit` object where the gates are fused.
Expand All @@ -70,69 +72,110 @@ def fuse(self):
from qibo.abstractions.abstract_gates import SpecialGate

class FusedQueue(_Queue):
"""Helper queue implementation that checks if a gate already exists
in queue to avoid re-appending it.
"""

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.set = set()

def append(self, gate):
"""Appends a gate in queue only if it is not already in."""
# Use a ``set`` instead of the original ``list`` to check if
# the gate already exists in queue as lookup is typically
# more efficient for sets
# (although actual performance difference is probably negligible)
if gate not in self.set:
self.set.add(gate)
super().append(gate)

# new circuit queue that will hold the fused gates
fused_queue = FusedQueue(self.nqubits)
# dictionary that maps each qubit id (int) to the corresponding
# active ``FusedGate`` that is part of
fused_gates = collections.OrderedDict()
# use ``OrderedDict`` so that the original gate order is not changed
for gate in self.queue:
qubits = gate.qubits
if len(qubits) == 1:
# add one-qubit gates to the active ``FusedGate`` of this qubit
# or create a new one if it does not exist
q = qubits[0]
if q not in fused_gates:
fused_gates[q] = gates.FusedGate(q)
fused_gates.get(q).add(gate)

elif len(qubits) == 2:
# fuse two-qubit gates
q0, q1 = tuple(sorted(qubits))
if (q0 in fused_gates and q1 in fused_gates and
fused_gates.get(q0) == fused_gates.get(q1)):
# if the target qubit pair is compatible with the active
# ``FusedGate`` of both qubits then add it to the ``FusedGate``
fused_gates.get(q0).add(gate)
else:
# otherwise we need to create a new ``FusedGate`` and
# update the active gates of both target qubits
fgate = gates.FusedGate(q0, q1)
if q0 in fused_gates:
# first qubit has existing active gate
ogate = fused_gates.pop(q0)
if len(ogate.target_qubits) == 1:
# existing active gate is one-qubit so we just add
# it to the new ``FusedGate``
fgate.add(ogate)
else:
# existing active gate is two-qubit so we need to
# add it to the new queue
fused_queue.append(ogate)
if q1 in fused_gates:
# second qubit has existing active gate
ogate = fused_gates.pop(q1)
if len(ogate.target_qubits) == 1:
# existing active gate is one-qubit so we just add
# it to the new ``FusedGate``
fgate.add(ogate)
else:
# existing active gate is two-qubit so we need to
# add it to the new queue
fused_queue.append(ogate)
# add the two-qubit gate to the newly created ``FusedGate``
# and update the active ``FusedGate``s of both target qubits
fgate.add(gate)
fused_gates[q0], fused_gates[q1] = fgate, fgate

elif isinstance(gate, SpecialGate):
# ``SpecialGate``s act on all qubits (like a barrier) so we
# so we need to temporarily stop the fusion, add all active
# gates in the new queue and restart fusion after the barrier
for g in fused_gates.values():
fused_queue.append(g)
fused_gates = collections.OrderedDict()
fused_queue.append(gate)

else:
# gate has more than two target qubits so it cannot be included
# in the ``FusedGate``s which support up to two qubits.
# Therefore we deactivate the ``FusedGate``s of all target qubits
for q in qubits:
if q in fused_gates:
fused_queue.append(fused_gates.pop(q))
fused_queue.append(gate)

for gate in fused_gates.values():
# add remaining active ``FusedGate``s in the new queue
fused_queue.append(gate)

queue = _Queue(self.nqubits)
for gate in fused_queue:
if isinstance(gate, gates.FusedGate) and len(gate.gates) == 1:
# replace ``FusedGate``s that contain only one gate by this
# gate for efficiency
gate = gate.gates[0]
queue.append(gate)

# create a circuit and assign the new queue
new_circuit = self._shallow_copy()
new_circuit.queue = queue
return new_circuit
Expand Down
2 changes: 1 addition & 1 deletion src/qibo/core/gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -1102,7 +1102,7 @@ def _construct_unitary(self):
simulation instead of applying the fused gates one by one.
Note that this method assumes maximum two target qubits and should be
update if the fusion algorithm is extended to gates of higher rank.
updated if the fusion algorithm is extended to gates of higher rank.
"""
matrix = K.qnp.eye(2 ** len(self.target_qubits))
for gate in self.gates:
Expand Down

0 comments on commit c3e7997

Please sign in to comment.