def test_fused_gate_construct_unitary(backend): gate = gates.FusedGate(0, 1) gate.add(gates.H(0)) gate.add(gates.H(1)) gate.add(gates.CZ(0, 1)) hmatrix = np.array([[1, 1], [1, -1]]) / np.sqrt(2) czmatrix = np.diag([1, 1, 1, -1]) target_matrix = czmatrix @ np.kron(hmatrix, hmatrix) K.assert_allclose(gate.matrix, target_matrix)
def unitary(self): """Creates the unitary matrix corresponding to all circuit gates. This is a ``(2 ** nqubits, 2 ** nqubits)`` matrix obtained by multiplying all circuit gates. """ from qibo import gates fgate = gates.FusedGate(*range(self.nqubits)) for gate in self.queue: fgate.append(gate) return fgate.matrix
def test_fused_gate_construct_unitary(backend, nqubits): gate = gates.FusedGate(0, 1) gate.append(gates.H(0)) gate.append(gates.H(1)) gate.append(gates.CZ(0, 1)) hmatrix = np.array([[1, 1], [1, -1]]) / np.sqrt(2) czmatrix = np.diag([1, 1, 1, -1]) target_matrix = czmatrix @ np.kron(hmatrix, hmatrix) if nqubits > 2: gate.append(gates.TOFFOLI(0, 1, 2)) toffoli = np.eye(8) toffoli[-2:, -2:] = np.array([[0, 1], [1, 0]]) target_matrix = toffoli @ np.kron(target_matrix, np.eye(2)) K.assert_allclose(gate.matrix, target_matrix)
def test_fused_gate_init(backend): gate = gates.FusedGate(0) gate = gates.FusedGate(0, 1) if K.op is not None: with pytest.raises(NotImplementedError): gate = gates.FusedGate(0, 1, 2)
def fuse(self): """Creates an equivalent circuit with the gates fused up to two-qubits. Returns: A :class:`qibo.core.circuit.Circuit` object containing :class:`qibo.abstractions.gates.FusedGate` gates, each of which corresponds to a group of some original gates. For more details on the fusion algorithm we refer to the :ref:`Circuit fusion <circuit-fusion>` section. Example: :: from qibo import models, gates c = models.Circuit(2) c.add([gates.H(0), gates.H(1)]) c.add(gates.CNOT(0, 1)) c.add([gates.Y(0), gates.Y(1)]) # create circuit with fused gates fused_c = c.fuse() # now ``fused_c`` contains a single ``FusedGate`` that is # equivalent to applying the five original gates """ from qibo import gates from qibo.abstractions.circuit import _Queue 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