Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updating gate fusion #461

Merged
merged 34 commits into from
Sep 22, 2021
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
6b05d2e
Implement new fusion
stavros11 Sep 8, 2021
5c1eabb
Remove _fuse_copy
stavros11 Sep 8, 2021
9909016
Merge branch 'master' into fusion
stavros11 Sep 10, 2021
7878049
Remove one qubit fusion
stavros11 Sep 10, 2021
233a5e9
Fix FusedGate with single gate
stavros11 Sep 10, 2021
2340102
Add missing reshape
stavros11 Sep 13, 2021
3778607
Fix QFT fusion
stavros11 Sep 13, 2021
b3a354d
Update fusion algorithm
stavros11 Sep 13, 2021
42cd335
Simplify fusion code
stavros11 Sep 13, 2021
509d1ec
Unskip tests
stavros11 Sep 13, 2021
b4d9bd9
Remove old fusion file
stavros11 Sep 14, 2021
ef71abe
Unskip more tests
stavros11 Sep 14, 2021
3cb67e9
Remove fusion import
stavros11 Sep 14, 2021
5067e45
Fix variational test
stavros11 Sep 14, 2021
8d06b1d
Fix fusion for all circuits
stavros11 Sep 15, 2021
2f9c17b
Remove old fusion class
stavros11 Sep 15, 2021
ebe1382
Fix CallbackGate with fusion
stavros11 Sep 15, 2021
c6c36fa
Implement FusedGate._dagger
stavros11 Sep 15, 2021
f53e419
Skip parametrized tests temporarily
stavros11 Sep 15, 2021
1b6a473
Fix typo
stavros11 Sep 15, 2021
8c36c3d
Remove fusion groups
stavros11 Sep 15, 2021
aad0af1
Update fusion parametrized gates
stavros11 Sep 15, 2021
cf9157a
Fix shallow copy in fuse
stavros11 Sep 15, 2021
9853df8
Fix set_parameters for fused circuits
stavros11 Sep 15, 2021
28de310
Use _reset_unitary in parameter setter
stavros11 Sep 15, 2021
ae80c44
Disable fusion for distributed circuits
stavros11 Sep 15, 2021
2c7df31
Merge branch 'master' into fusion
stavros11 Sep 15, 2021
d055fba
Fix pylint
stavros11 Sep 15, 2021
0e0d81a
Remove print
stavros11 Sep 15, 2021
4b406dd
Improve coverage
stavros11 Sep 15, 2021
c4376fa
Add fusion test with Toffoli
stavros11 Sep 15, 2021
5583509
Merge master
stavros11 Sep 21, 2021
95ac9db
Update docstrings
stavros11 Sep 21, 2021
c3e7997
Add docstrings in fusion algorithm
stavros11 Sep 21, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions src/qibo/abstractions/abstract_gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -411,9 +411,7 @@ def parameters(self, x):
# ``circuit.set_parameters`` method works properly.
# pylint: disable=E1101
if isinstance(self, BaseBackendGate):
self._matrix = None
self._native_op_matrix = None
self._custom_op_matrix = None
self._reset_unitary()
for devgate in self.device_gates:
devgate.parameters = x

Expand Down Expand Up @@ -498,6 +496,13 @@ def _construct_unitary(self): # pragma: no cover
"""Constructs the gate's unitary matrix."""
return raise_error(NotImplementedError)

def _reset_unitary(self):
"""Resets the gate matrices back to ``None``.

Useful when the gate matrix need to be recalculated.
"""
self._matrix = None

@property
@abstractmethod
def cache(self): # pragma: no cover
Expand Down
49 changes: 27 additions & 22 deletions src/qibo/abstractions/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,13 @@ class _ParametrizedGates(list):
total number of parameters.
"""

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

def append(self, gate: gates.ParametrizedGate):
super(_ParametrizedGates, self).append(gate)
def append(self, gate):
super().append(gate)
self.set.add(gate)
self.nparams += gate.nparams

Expand Down Expand Up @@ -94,8 +94,6 @@ def __init__(self, nqubits):
self.measurement_gate = None
self.measurement_gate_result = None

self.fusion_groups = []

self._final_state = None
self.density_matrix = False
self.repeated_execution = False
Expand Down Expand Up @@ -180,6 +178,16 @@ def on_qubits(self, *q):
for gate in self.queue:
yield gate.on_qubits(*q)

def _shallow_copy(self):
"""Helper method for :meth:`qibo.abstractions.circuit.AbstractCircuit.copy`
and :meth:`qibo.core.circuit.Circuit.fuse`."""
new_circuit = self.__class__(**self.init_kwargs)
new_circuit.parametrized_gates = _ParametrizedGates(self.parametrized_gates)
new_circuit.trainable_gates = _ParametrizedGates(self.trainable_gates)
new_circuit.measurement_gate = self.measurement_gate
new_circuit.measurement_tuples = dict(self.measurement_tuples)
return new_circuit

def copy(self, deep: bool = False):
"""Creates a copy of the current ``circuit`` as a new ``Circuit`` model.

Expand All @@ -192,27 +200,25 @@ def copy(self, deep: bool = False):
The copied circuit object.
"""
import copy
new_circuit = self.__class__(**self.init_kwargs)
if deep:
new_circuit = self.__class__(**self.init_kwargs)
for gate in self.queue:
if isinstance(gate, gates.FusedGate): # pragma: no cover
# impractical case
raise_error(NotImplementedError, "Cannot create deep copy "
"of fused circuit.")

new_gate = copy.copy(gate)
new_circuit.queue.append(new_gate)
if isinstance(gate, gates.ParametrizedGate):
new_circuit.parametrized_gates.append(new_gate)
if gate.trainable:
new_circuit.trainable_gates.append(new_gate)
new_circuit.measurement_gate = copy.copy(self.measurement_gate)
if self.fusion_groups: # pragma: no cover
# impractical case
raise_error(NotImplementedError, "Cannot create deep copy of fused "
"circuit.")
new_circuit.measurement_tuples = dict(self.measurement_tuples)
else:
new_circuit = self._shallow_copy()
new_circuit.queue = copy.copy(self.queue)
new_circuit.parametrized_gates = list(self.parametrized_gates)
new_circuit.trainable_gates = list(self.trainable_gates)
new_circuit.measurement_gate = self.measurement_gate
new_circuit.fusion_groups = list(self.fusion_groups)
new_circuit.measurement_tuples = dict(self.measurement_tuples)
return new_circuit

def invert(self):
Expand Down Expand Up @@ -517,9 +523,6 @@ def _set_parameters_list(self, parameters: List, n: int):
"the circuit contains {} parametrized gates."
"".format(n, len(self.trainable_gates)))

for fusion_group in self.fusion_groups:
fusion_group.update()

def set_parameters(self, parameters):
"""Updates the parameters of the circuit's parametrized gates.

Expand Down Expand Up @@ -569,9 +572,6 @@ def set_parameters(self, parameters):
elif isinstance(parameters, self.param_tensor_types):
self._set_parameters_list(parameters, int(parameters.shape[0]))
elif isinstance(parameters, dict):
if self.fusion_groups:
raise_error(TypeError, "Cannot accept new parameters as dictionary "
"for fused circuits. Use list, tuple or array.")
diff = set(parameters.keys()) - self.trainable_gates.set
if diff:
raise_error(KeyError, "Dictionary contains gates {} which are "
Expand All @@ -582,6 +582,11 @@ def set_parameters(self, parameters):
else:
raise_error(TypeError, "Invalid type of parameters {}."
"".format(type(parameters)))
# Reset ``FusedGate`` matrices so that they are recalculated with the
# updated parameters.
for gate in self.queue:
if isinstance(gate, gates.FusedGate):
gate._reset_unitary()

def get_parameters(self, format: str = "list",
include_not_trainable: bool = False
Expand Down
30 changes: 30 additions & 0 deletions src/qibo/abstractions/gates.py
Original file line number Diff line number Diff line change
Expand Up @@ -1550,3 +1550,33 @@ def __init__(self, q, t1, t2, time, excited_population=0, seed=None):
seed=seed)
# this case can only be applied to density matrices
self.density_matrix = True


class FusedGate(Gate):
mlazzarin marked this conversation as resolved.
Show resolved Hide resolved

def __init__(self, *q):
super().__init__()
self.name = "fused"
self.target_qubits = tuple(q)
self.init_args = list(q)
self.qubit_set = set(q)
self.gates = []

def add(self, gate):
if not set(gate.qubits).issubset(self.qubit_set):
raise_error(ValueError, "Cannot add gate that targets {} "
"in fused gate acting on {}."
"".format(gate.qubits, self.qubits))
if isinstance(gate, self.__class__):
self.gates.extend(gate.gates)
else:
self.gates.append(gate)

def __iter__(self):
return iter(self.gates)

def _dagger(self):
dagger = self.__class__(*self.init_args)
for gate in self.gates[::-1]:
dagger.add(gate.dagger())
return dagger
108 changes: 71 additions & 37 deletions src/qibo/core/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ class Circuit(circuit.AbstractCircuit):
Args:
nqubits (int): Total number of qubits in the circuit.
"""
from qibo.core import fusion

def __init__(self, nqubits):
super(Circuit, self).__init__(nqubits)
Expand All @@ -47,32 +46,6 @@ def _add_layer(self, gate):
self._set_nqubits(gate.additional_unitary)
self.queue.append(gate.additional_unitary)

def _fuse_copy(self):
"""Helper method for ``circuit.fuse``.

For standard (non-distributed) circuits this creates a copy of the
circuit with deep-copying the parametrized gates only.
For distributed circuits a fully deep copy should be created.
"""
import copy
from qibo.abstractions.abstract_gates import ParametrizedGate
new_circuit = self.__class__(**self.init_kwargs)
for gate in self.queue:
if isinstance(gate, ParametrizedGate):
if gate.trainable:
new_gate = copy.copy(gate)
new_circuit.queue.append(new_gate)
new_circuit.parametrized_gates.append(new_gate)
new_circuit.trainable_gates.append(new_gate)
else:
new_circuit.queue.append(gate)
new_circuit.parametrized_gates.append(gate)
else:
new_circuit.queue.append(gate)
new_circuit.measurement_gate = copy.copy(self.measurement_gate)
new_circuit.measurement_tuples = dict(self.measurement_tuples)
return new_circuit

def fuse(self):
mlazzarin marked this conversation as resolved.
Show resolved Hide resolved
"""Creates an equivalent ``Circuit`` with gates fused up to two-qubits.

Expand All @@ -89,18 +62,79 @@ def fuse(self):
c.add([gates.Y(0), gates.Y(1)])
# create circuit with fused gates
fused_c = c.fuse()
# now ``fused_c`` contains only one ``gates.Unitary`` gate
# that is equivalent to applying the five gates of the original
# circuit.
# now ``fused_c`` contains a single gate that is equivalent
# to applying the five gates of the original circuit.
"""
from qibo import gates
from qibo.abstractions.circuit import _Queue
new_circuit = self._fuse_copy()
new_circuit.fusion_groups = self.fusion.FusionGroup.from_queue(
new_circuit.queue)
new_circuit.queue = _Queue(self.nqubits)
for group in new_circuit.fusion_groups:
for gate in group.gates:
new_circuit.queue.append(gate)
from qibo.abstractions.abstract_gates import SpecialGate

class FusedQueue(_Queue):

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

def append(self, gate):
if gate not in self.set:
self.set.add(gate)
super().append(gate)

fused_queue = FusedQueue(self.nqubits)
fused_gates = collections.OrderedDict()
for gate in self.queue:
qubits = gate.qubits
if len(qubits) == 1:
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:
q0, q1 = tuple(sorted(qubits))
if (q0 in fused_gates and q1 in fused_gates and
fused_gates.get(q0) == fused_gates.get(q1)):
fused_gates.get(q0).add(gate)
else:
fgate = gates.FusedGate(q0, q1)
if q0 in fused_gates:
ogate = fused_gates.pop(q0)
if len(ogate.target_qubits) == 1:
fgate.add(ogate)
else:
fused_queue.append(ogate)
if q1 in fused_gates:
ogate = fused_gates.pop(q1)
if len(ogate.target_qubits) == 1:
fgate.add(ogate)
else:
fused_queue.append(ogate)
fgate.add(gate)
fused_gates[q0], fused_gates[q1] = fgate, fgate

elif isinstance(gate, SpecialGate):
for g in fused_gates.values():
fused_queue.append(g)
fused_gates = collections.OrderedDict()
fused_queue.append(gate)

else:
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():
fused_queue.append(gate)

queue = _Queue(self.nqubits)
for gate in fused_queue:
if isinstance(gate, gates.FusedGate) and len(gate.gates) == 1:
gate = gate.gates[0]
queue.append(gate)

new_circuit = self._shallow_copy()
new_circuit.queue = queue
return new_circuit

def _eager_execute(self, state):
Expand Down
9 changes: 2 additions & 7 deletions src/qibo/core/distcircuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,9 @@ def copy(self, deep: bool = True):
"circuits because they modify gate objects.")
return super().copy(deep)

def _fuse_copy(self):
return self.copy(deep=True)

def fuse(self):
if self.queues.queues:
raise_error(RuntimeError, "Cannot fuse distributed circuit after "
"its first execution.")
return super().fuse()
raise_error(NotImplementedError, "Fusion is not implemented for "
"distributed circuits.")

def with_noise(self, noise_map, measurement_noise=None):
raise_error(NotImplementedError, "Distributed circuit does not support "
Expand Down
Loading