From 0f800b2e95813f728b46313f6b3b564d80296854 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Mon, 26 Aug 2024 13:38:26 +0400 Subject: [PATCH 01/34] create pr --- doc/source/code-examples/advancedexamples.rst | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/doc/source/code-examples/advancedexamples.rst b/doc/source/code-examples/advancedexamples.rst index 50ebdef539..dd9e4bb5af 100644 --- a/doc/source/code-examples/advancedexamples.rst +++ b/doc/source/code-examples/advancedexamples.rst @@ -294,7 +294,8 @@ The following gates support parameter setting: should be an array or ``tf.Tensor`` of shape ``(2, 2)``. Note that a ``np.ndarray`` or a ``tf.Tensor`` may also be used in the place of -a flat list. Using :meth:`qibo.models.circuit.Circuit.set_parameters` is more +a flat list (``torch.Tensor`` is required when using the pytorch backend). +Using :meth:`qibo.models.circuit.Circuit.set_parameters` is more efficient than recreating a new circuit with new parameter values. The inverse method :meth:`qibo.models.circuit.Circuit.get_parameters` is also available and returns a list, dictionary or flat list with the current parameter values @@ -551,9 +552,9 @@ Here is a simple example using the Heisenberg XXZ model Hamiltonian: For more information on the available options of the ``vqe.minimize`` call we refer to the :ref:`Optimizers ` section of the documentation. Note that if the Stochastic Gradient Descent optimizer is used then the user -has to use a backend based on tensorflow primitives and not the default custom +has to use a backend based on tensorflow or pytorch primitives and not the default custom backend, as custom operators currently do not support automatic differentiation. -To switch the backend one can do ``qibo.set_backend("tensorflow")``. +To switch the backend one can do ``qibo.set_backend("tensorflow")`` or ``qibo.set_backend("pytorch")``. Check the :ref:`How to use automatic differentiation? ` section for more details. @@ -695,6 +696,8 @@ the model. For example the previous example would have to be modified as: How to use automatic differentiation? ------------------------------------- +ADD HERE PYTORCH EXAMPLE? + As a deep learning framework, Tensorflow supports `automatic differentiation `_. This can be used to optimize the parameters of variational circuits. For example From 5281882bfe897daf21f6234760fa0bab3900237f Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Tue, 10 Sep 2024 14:06:03 +0400 Subject: [PATCH 02/34] torch example --- doc/source/code-examples/advancedexamples.rst | 47 ++++++++++++++++--- 1 file changed, 40 insertions(+), 7 deletions(-) diff --git a/doc/source/code-examples/advancedexamples.rst b/doc/source/code-examples/advancedexamples.rst index dd9e4bb5af..0b74fe5229 100644 --- a/doc/source/code-examples/advancedexamples.rst +++ b/doc/source/code-examples/advancedexamples.rst @@ -289,9 +289,9 @@ The following gates support parameter setting: * :class:`qibo.gates.fSim`: Accepts a tuple of two parameters ``(theta, phi)``. * :class:`qibo.gates.GeneralizedfSim`: Accepts a tuple of two parameters ``(unitary, phi)``. Here ``unitary`` should be a unitary matrix given as an - array or ``tf.Tensor`` of shape ``(2, 2)``. + array or ``tf.Tensor`` of shape ``(2, 2)``. A ``torch.Tensor`` is required when using the pytorch backend. * :class:`qibo.gates.Unitary`: Accepts a single ``unitary`` parameter. This - should be an array or ``tf.Tensor`` of shape ``(2, 2)``. + should be an array or ``tf.Tensor`` of shape ``(2, 2)``. A ``torch.Tensor`` is required when using the pytorch backend. Note that a ``np.ndarray`` or a ``tf.Tensor`` may also be used in the place of a flat list (``torch.Tensor`` is required when using the pytorch backend). @@ -696,14 +696,13 @@ the model. For example the previous example would have to be modified as: How to use automatic differentiation? ------------------------------------- -ADD HERE PYTORCH EXAMPLE? +The parameters of variational circuits can be optimized using the frameworks of +Tensorflow or Pytorch. As a deep learning framework, Tensorflow supports `automatic differentiation `_. -This can be used to optimize the parameters of variational circuits. For example -the following script optimizes the parameters of two rotations so that the circuit -output matches a target state using the fidelity as the corresponding loss -function. +The following script optimizes the parameters of two rotations so that the +circuit output matches a target state using the fidelity as the corresponding loss function. Note that, as in the following example, the rotation angles have to assume real values to ensure the rotational gates are representing unitary operators. @@ -780,6 +779,40 @@ that is supported by Tensorflow, such as defining and using the `Sequential model API `_ to train them. +Similarly, Pytorch supports `automatic differentiation `_. +The following script optimizes the parameters of the variational circuit of the first example using the Pytorch framework. + +.. code-block:: python + + import qibo + qibo.set_backend("pytorch") + import torch + from qibo import gates, models + + # Optimization parameters + nepochs = 1000 + optimizer = torch.optim.Adam + target_state = torch.ones(4, dtype=torch.complex128) / 2.0 + + # Define circuit ansatz + params = torch.tensor( + torch.rand(2, dtype=torch.float64), requires_grad=True + ) + c = models.Circuit(2) + c.add(gates.RX(0, params[0])) + c.add(gates.RY(1, params[1])) + + optimizer = optimizer([params]) + + for _ in range(nepochs): + optimizer.zero_grad() + c.set_parameters(params) + final_state = c().state() + fidelity = torch.abs(torch.sum(torch.conj(target_state) * final_state)) + loss = 1 - fidelity + loss.backward() + optimizer.step() + .. _noisy-example: From 364acbfb7b229e8f2185573997f4426957d88517 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Mon, 16 Sep 2024 16:55:07 +0400 Subject: [PATCH 03/34] merge master --- doc/source/code-examples/test.py | 38 ++++++++++++++++++++++++++++++++ src/qibo/backends/pytorch.py | 6 +++-- 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 doc/source/code-examples/test.py diff --git a/doc/source/code-examples/test.py b/doc/source/code-examples/test.py new file mode 100644 index 0000000000..090ae0d9fd --- /dev/null +++ b/doc/source/code-examples/test.py @@ -0,0 +1,38 @@ +import qibo + +qibo.set_backend("pytorch") +import torch + +from qibo import gates, models + +torch.set_anomaly_enabled(True) + +# Optimization parameters +nepochs = 1 +optimizer = torch.optim.Adam +target_state = torch.ones(4, dtype=torch.complex128) / 2.0 + +# Define circuit ansatz +params = torch.rand(2, dtype=torch.float64, requires_grad=True) +print(params) +optimizer = optimizer([params]) +c = models.Circuit(2) +c.add(gates.RX(0, params[0])) +c.add(gates.RY(1, params[1])) +gate = gates.RY(0, params[1]) + +print("Gate", gate.matrix()) +print(torch.norm(gate.matrix()).grad) + +# for _ in range(nepochs): +# optimizer.zero_grad() +# c.set_parameters(params) +# final_state = c().state() +# print("state", final_state) +# fidelity = torch.abs(torch.sum(torch.conj(target_state) * final_state)) +# loss = 1 - fidelity +# loss.backward() +# optimizer.step() +# print("state", final_state) +# print("params", params) +# print("loss", loss.grad) diff --git a/src/qibo/backends/pytorch.py b/src/qibo/backends/pytorch.py index 392aeed405..e57977a1ee 100644 --- a/src/qibo/backends/pytorch.py +++ b/src/qibo/backends/pytorch.py @@ -1,5 +1,7 @@ """PyTorch backend.""" +from typing import Optional + import numpy as np from qibo import __version__ @@ -85,7 +87,7 @@ def cast( x, dtype=None, copy: bool = False, - requires_grad: bool = None, + requires_grad: Optional[bool] = None, ): """Casts input as a Torch tensor of the specified dtype. @@ -117,7 +119,6 @@ def cast( # check if dtype is an integer to remove gradients if dtype in [self.np.int32, self.np.int64, self.np.int8, self.np.int16]: requires_grad = False - if isinstance(x, self.np.Tensor): x = x.to(dtype) elif isinstance(x, list) and all(isinstance(row, self.np.Tensor) for row in x): @@ -128,6 +129,7 @@ def cast( if copy: return x.clone() + print("Casting", x) return x def is_sparse(self, x): From 9c96d413ada76351a845c627d5edcee1d4443e35 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Tue, 17 Sep 2024 15:45:27 +0400 Subject: [PATCH 04/34] refactor casting parameters --- doc/source/code-examples/test.py | 38 --------------------- src/qibo/backends/npmatrices.py | 48 ++------------------------- src/qibo/backends/numpy.py | 12 +++---- src/qibo/backends/pytorch.py | 57 +++++++++++++++++++------------- tests/test_backends.py | 16 --------- tests/test_torch_gradients.py | 29 ++++++++++++++++ 6 files changed, 70 insertions(+), 130 deletions(-) delete mode 100644 doc/source/code-examples/test.py create mode 100644 tests/test_torch_gradients.py diff --git a/doc/source/code-examples/test.py b/doc/source/code-examples/test.py deleted file mode 100644 index 090ae0d9fd..0000000000 --- a/doc/source/code-examples/test.py +++ /dev/null @@ -1,38 +0,0 @@ -import qibo - -qibo.set_backend("pytorch") -import torch - -from qibo import gates, models - -torch.set_anomaly_enabled(True) - -# Optimization parameters -nepochs = 1 -optimizer = torch.optim.Adam -target_state = torch.ones(4, dtype=torch.complex128) / 2.0 - -# Define circuit ansatz -params = torch.rand(2, dtype=torch.float64, requires_grad=True) -print(params) -optimizer = optimizer([params]) -c = models.Circuit(2) -c.add(gates.RX(0, params[0])) -c.add(gates.RY(1, params[1])) -gate = gates.RY(0, params[1]) - -print("Gate", gate.matrix()) -print(torch.norm(gate.matrix()).grad) - -# for _ in range(nepochs): -# optimizer.zero_grad() -# c.set_parameters(params) -# final_state = c().state() -# print("state", final_state) -# fidelity = torch.abs(torch.sum(torch.conj(target_state) * final_state)) -# loss = 1 - fidelity -# loss.backward() -# optimizer.step() -# print("state", final_state) -# print("params", params) -# print("loss", loss.grad) diff --git a/src/qibo/backends/npmatrices.py b/src/qibo/backends/npmatrices.py index 69f0920722..5a4a2a9b4a 100644 --- a/src/qibo/backends/npmatrices.py +++ b/src/qibo/backends/npmatrices.py @@ -17,10 +17,6 @@ def __init__(self, dtype): def _cast(self, x, dtype): return self.np.array(x, dtype=dtype) - # This method is used to cast the parameters of the gates to the right type for other backends - def _cast_parameter(self, x): - return x - @cached_property def H(self): return self._cast([[1, 1], [1, -1]], dtype=self.dtype) / math.sqrt(2) @@ -75,25 +71,20 @@ def M(self): # pragma: no cover raise_error(NotImplementedError) def RX(self, theta): - theta = self._cast_parameter(theta) cos = self.np.cos(theta / 2.0) + 0j isin = -1j * self.np.sin(theta / 2.0) return self._cast([[cos, isin], [isin, cos]], dtype=self.dtype) def RY(self, theta): - theta = self._cast_parameter(theta) cos = self.np.cos(theta / 2.0) + 0j sin = self.np.sin(theta / 2.0) + 0j return self._cast([[cos, -sin], [sin, cos]], dtype=self.dtype) def RZ(self, theta): - theta = self._cast_parameter(theta) phase = self.np.exp(0.5j * theta) return self._cast([[self.np.conj(phase), 0], [0, phase]], dtype=self.dtype) def PRX(self, theta, phi): - theta = self._cast_parameter(theta) - phi = self._cast_parameter(phi) cos = self.np.cos(theta / 2) sin = self.np.sin(theta / 2) exponent1 = -1.0j * self.np.exp(-1.0j * phi) @@ -104,25 +95,20 @@ def PRX(self, theta, phi): ) def GPI(self, phi): - phi = self._cast_parameter(phi) phase = self.np.exp(1.0j * phi) return self._cast([[0, self.np.conj(phase)], [phase, 0]], dtype=self.dtype) def GPI2(self, phi): - phi = self._cast_parameter(phi) phase = self.np.exp(1.0j * phi) return self._cast( [[1, -1.0j * self.np.conj(phase)], [-1.0j * phase, 1]], dtype=self.dtype ) / math.sqrt(2) def U1(self, theta): - theta = self._cast_parameter(theta) phase = self.np.exp(1j * theta) return self._cast([[1, 0], [0, phase]], dtype=self.dtype) def U2(self, phi, lam): - phi = self._cast_parameter(phi) - lam = self._cast_parameter(lam) eplus = self.np.exp(1j * (phi + lam) / 2.0) eminus = self.np.exp(1j * (phi - lam) / 2.0) return self._cast( @@ -131,9 +117,6 @@ def U2(self, phi, lam): ) / math.sqrt(2) def U3(self, theta, phi, lam): - theta = self._cast_parameter(theta) - phi = self._cast_parameter(phi) - lam = self._cast_parameter(lam) cost = self.np.cos(theta / 2) sint = self.np.sin(theta / 2) eplus = self.np.exp(1j * (phi + lam) / 2.0) @@ -147,8 +130,6 @@ def U3(self, theta, phi, lam): ) def U1q(self, theta, phi): - theta = self._cast_parameter(theta) - phi = self._cast_parameter(phi) return self._cast( self.U3(theta, phi - math.pi / 2, math.pi / 2 - phi), dtype=self.dtype ) @@ -179,7 +160,7 @@ def CZ(self): @cached_property def CSX(self): - a = self._cast_parameter((1 + 1j) / 2) + a = (1 + 1j) / 2 b = self.np.conj(a) return self._cast( [ @@ -193,7 +174,7 @@ def CSX(self): @cached_property def CSXDG(self): - a = self._cast_parameter((1 - 1j) / 2) + a = (1 - 1j) / 2 b = self.np.conj(a) return self._cast( [ @@ -206,7 +187,6 @@ def CSXDG(self): ) def CRX(self, theta): - theta = self._cast_parameter(theta) cos = self.np.cos(theta / 2.0) + 0j isin = -1j * self.np.sin(theta / 2.0) matrix = [ @@ -218,14 +198,12 @@ def CRX(self, theta): return self._cast(matrix, dtype=self.dtype) def CRY(self, theta): - theta = self._cast_parameter(theta) cos = self.np.cos(theta / 2.0) + 0j sin = self.np.sin(theta / 2.0) + 0j matrix = [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, cos, -sin], [0, 0, sin, cos]] return self._cast(matrix, dtype=self.dtype) def CRZ(self, theta): - theta = self._cast_parameter(theta) phase = self.np.exp(0.5j * theta) matrix = [ [1, 0, 0, 0], @@ -236,7 +214,6 @@ def CRZ(self, theta): return self._cast(matrix, dtype=self.dtype) def CU1(self, theta): - theta = self._cast_parameter(theta) phase = self.np.exp(1j * theta) matrix = [ [1, 0, 0, 0], @@ -247,8 +224,6 @@ def CU1(self, theta): return self._cast(matrix, dtype=self.dtype) def CU2(self, phi, lam): - phi = self._cast_parameter(phi) - lam = self._cast_parameter(lam) eplus = self.np.exp(1j * (phi + lam) / 2.0) / math.sqrt(2) eminus = self.np.exp(1j * (phi - lam) / 2.0) / math.sqrt(2) matrix = [ @@ -260,9 +235,6 @@ def CU2(self, phi, lam): return self._cast(matrix, dtype=self.dtype) def CU3(self, theta, phi, lam): - theta = self._cast_parameter(theta) - phi = self._cast_parameter(phi) - lam = self._cast_parameter(lam) cost = self.np.cos(theta / 2) sint = self.np.sin(theta / 2) eplus = self.np.exp(1j * (phi + lam) / 2.0) @@ -324,8 +296,6 @@ def FSWAP(self): ) def fSim(self, theta, phi): - theta = self._cast_parameter(theta) - phi = self._cast_parameter(phi) cost = self.np.cos(theta) + 0j isint = -1j * self.np.sin(theta) phase = self.np.exp(-1j * phi) @@ -355,7 +325,6 @@ def SYC(self): ) def GeneralizedfSim(self, u, phi): - phi = self._cast_parameter(phi) phase = self.np.exp(-1j * phi) return self._cast( [ @@ -368,7 +337,6 @@ def GeneralizedfSim(self, u, phi): ) def RXX(self, theta): - theta = self._cast_parameter(theta) cos = self.np.cos(theta / 2.0) + 0j isin = -1j * self.np.sin(theta / 2.0) return self._cast( @@ -382,7 +350,6 @@ def RXX(self, theta): ) def RYY(self, theta): - theta = self._cast_parameter(theta) cos = self.np.cos(theta / 2.0) + 0j isin = -1j * self.np.sin(theta / 2.0) return self._cast( @@ -396,7 +363,6 @@ def RYY(self, theta): ) def RZZ(self, theta): - theta = self._cast_parameter(theta) phase = self.np.exp(0.5j * theta) return self._cast( [ @@ -409,7 +375,6 @@ def RZZ(self, theta): ) def RZX(self, theta): - theta = self._cast_parameter(theta) cos, sin = self.np.cos(theta / 2) + 0j, self.np.sin(theta / 2) + 0j return self._cast( [ @@ -422,7 +387,6 @@ def RZX(self, theta): ) def RXXYY(self, theta): - theta = self._cast_parameter(theta) cos, sin = self.np.cos(theta / 2) + 0j, self.np.sin(theta / 2) + 0j return self._cast( [ @@ -435,11 +399,6 @@ def RXXYY(self, theta): ) def MS(self, phi0, phi1, theta): - phi0, phi1, theta = ( - self._cast_parameter(phi0), - self._cast_parameter(phi1), - self._cast_parameter(theta), - ) plus = self.np.exp(1.0j * (phi0 + phi1)) minus = self.np.exp(1.0j * (phi0 - phi1)) cos = self.np.cos(theta / 2) + 0j @@ -455,7 +414,6 @@ def MS(self, phi0, phi1, theta): ) def GIVENS(self, theta): - theta = self._cast_parameter(theta) return self._cast( [ [1, 0, 0, 0], @@ -532,8 +490,6 @@ def DEUTSCH(self, theta): ) def GeneralizedRBS(self, qubits_in, qubits_out, theta, phi): - theta = self._cast_parameter(theta) - phi = self._cast_parameter(phi) bitstring_length = len(qubits_in) + len(qubits_out) integer_in = "".join( ["1" if k in qubits_in else "0" for k in range(bitstring_length)] diff --git a/src/qibo/backends/numpy.py b/src/qibo/backends/numpy.py index bcde6ac013..11ca9a4efc 100644 --- a/src/qibo/backends/numpy.py +++ b/src/qibo/backends/numpy.py @@ -167,6 +167,7 @@ def matrix_fused(self, fgate): def apply_gate(self, gate, state, nqubits): state = self.np.reshape(state, nqubits * (2,)) matrix = gate.matrix(self) + print("matrix applied:", matrix) if gate.is_controlled_by: matrix = self.np.reshape(matrix, 2 * len(gate.target_qubits) * (2,)) ncontrol = len(gate.control_qubits) @@ -397,7 +398,8 @@ def execute_circuit(self, circuit, initial_state=None, nshots=1000): else: return self.execute_circuit(initial_state + circuit, None, nshots) elif initial_state is not None: - initial_state = self.cast(initial_state) + if initial_state.dtype != self.dtype: + initial_state = self.cast(initial_state) valid_shape = ( 2 * (2**circuit.nqubits,) if circuit.density_matrix @@ -427,9 +429,6 @@ def execute_circuit(self, circuit, initial_state=None, nshots=1000): if circuit.density_matrix: if initial_state is None: state = self.zero_density_matrix(nqubits) - else: - # cast to proper complex type - state = self.cast(initial_state) for gate in circuit.queue: state = gate.apply_density_matrix(self, state, nqubits) @@ -437,12 +436,11 @@ def execute_circuit(self, circuit, initial_state=None, nshots=1000): else: if initial_state is None: state = self.zero_state(nqubits) - else: - # cast to proper complex type - state = self.cast(initial_state) + print("state before circuit execution:", state) for gate in circuit.queue: state = gate.apply(self, state, nqubits) + print("state during circuit execution:", state) if circuit.has_unitary_channel: # here we necessarily have `density_matrix=True`, otherwise diff --git a/src/qibo/backends/pytorch.py b/src/qibo/backends/pytorch.py index e57977a1ee..e1759eff86 100644 --- a/src/qibo/backends/pytorch.py +++ b/src/qibo/backends/pytorch.py @@ -1,7 +1,5 @@ """PyTorch backend.""" -from typing import Optional - import numpy as np from qibo import __version__ @@ -14,25 +12,20 @@ class TorchMatrices(NumpyMatrices): Args: dtype (torch.dtype): Data type of the matrices. - requires_grad (bool): If ``True`` the matrices require gradient. """ - def __init__(self, dtype, requires_grad): + def __init__(self, dtype): import torch # pylint: disable=import-outside-toplevel super().__init__(dtype) self.np = torch self.dtype = dtype - self.requires_grad = requires_grad def _cast(self, x, dtype): flattened = [item for sublist in x for item in sublist] tensor_list = [self.np.as_tensor(i, dtype=dtype) for i in flattened] return self.np.stack(tensor_list).reshape(len(x), len(x)) - def _cast_parameter(self, x): - return self.np.tensor(x, dtype=self.dtype, requires_grad=self.requires_grad) - def Unitary(self, u): return self._cast(u, dtype=self.dtype) @@ -42,9 +35,6 @@ def __init__(self): super().__init__() import torch # pylint: disable=import-outside-toplevel - # Global variable to enable or disable gradient calculation - self.gradients = True - self.np = torch self.name = "pytorch" @@ -54,8 +44,11 @@ def __init__(self): "torch": self.np.__version__, } + # Default data type used for the gate matrices is complex128 self.dtype = self._torch_dtype(self.dtype) - self.matrices = TorchMatrices(self.dtype, requires_grad=self.gradients) + # Default parameters dtype is float64 + self.parameters_dtype = torch.float64 + self.matrices = TorchMatrices(self.dtype) self.device = self.np.device("cuda:0" if torch.cuda.is_available() else "cpu") self.nthreads = 0 self.tensor_types = (self.np.Tensor, np.ndarray) @@ -69,11 +62,6 @@ def __init__(self): self.np.sign = self.np.sgn self.np.flatnonzero = lambda x: self.np.nonzero(x).flatten() - def requires_grad(self, requires_grad): - """Enable or disable gradient calculation.""" - self.gradients = requires_grad - self.matrices.requires_grad = requires_grad - def _torch_dtype(self, dtype): if dtype == "float": dtype += "32" @@ -87,7 +75,7 @@ def cast( x, dtype=None, copy: bool = False, - requires_grad: Optional[bool] = None, + requires_grad: bool = False, ): """Casts input as a Torch tensor of the specified dtype. @@ -102,12 +90,9 @@ def cast( Defaults to ``None``. copy (bool, optional): If ``True``, the input tensor is copied before casting. Defaults to ``False``. - requires_grad (bool, optional): If ``True``, the input tensor requires gradient. + requires_grad (bool): If ``True``, the input tensor requires gradient. If ``False``, the input tensor does not require gradient. - If ``None``, the default gradient setting of the backend is used. """ - if requires_grad is None: - requires_grad = self.gradients if dtype is None: dtype = self.dtype @@ -129,9 +114,35 @@ def cast( if copy: return x.clone() - print("Casting", x) return x + def matrix_parametrized(self, gate): + """Convert a parametrized gate to its matrix representation in the computational basis.""" + name = gate.__class__.__name__ + _matrix = getattr(self.matrices, name) + if name == "GeneralizedRBS": + _matrix = _matrix( + qubits_in=gate.init_args[0], + qubits_out=gate.init_args[1], + theta=self.cast_parameter( + gate.init_kwargs["theta"], trainable=gate.trainable + ), + phi=self.cast_parameter( + gate.init_kwargs["phi"], trainable=gate.trainable + ), + ) + else: + parameters = ( + self.cast_parameter(param, trainable=gate.trainable) + for param in gate.parameters + ) + _matrix = _matrix(*parameters) + print("parameterized matrix:", _matrix) + return _matrix + + def cast_parameter(self, x, trainable): + return self.np.tensor(x, dtype=self.parameters_dtype, requires_grad=trainable) + def is_sparse(self, x): if isinstance(x, self.np.Tensor): return x.is_sparse diff --git a/tests/test_backends.py b/tests/test_backends.py index 1be00e6d22..0c855c9686 100644 --- a/tests/test_backends.py +++ b/tests/test_backends.py @@ -130,19 +130,3 @@ def test_list_available_backends(): assert available_backends == list_available_backends( "qibojit", "qibolab", "qibo-cloud-backends", "qibotn" ) - - -def test_gradients_pytorch(): - from qibo.backends import PyTorchBackend # pylint: disable=import-outside-toplevel - - backend = PyTorchBackend() - gate = gates.RX(0, 0.1) - matrix = gate.matrix(backend) - assert matrix.requires_grad - assert backend.gradients - backend.requires_grad(False) - gate = gates.RX(0, 0.1) - matrix = gate.matrix(backend) - assert not matrix.requires_grad - assert not backend.gradients - assert not backend.matrices.requires_grad diff --git a/tests/test_torch_gradients.py b/tests/test_torch_gradients.py new file mode 100644 index 0000000000..e70e1e80a3 --- /dev/null +++ b/tests/test_torch_gradients.py @@ -0,0 +1,29 @@ +import qibo + +qibo.set_backend("pytorch") +import torch + +from qibo import gates, models + +# Optimization parameters +nepochs = 2 +optimizer = torch.optim.Adam +target_state = torch.ones(2, dtype=torch.complex128) / 2.0 +# Define circuit ansatz +params = torch.tensor(torch.rand(1, dtype=torch.float64), requires_grad=True) +print(params) +c = models.Circuit(1) +c.add(gates.RX(0, params[0])) +optimizer = optimizer([params]) +for _ in range(nepochs): + optimizer.zero_grad() + c.set_parameters(params) + final_state = c().state() + print("final state:", final_state) + fidelity = torch.abs(torch.sum(torch.conj(target_state) * final_state)) + loss = 1 - fidelity + loss.backward() + print("loss:", loss) + print("params.grad:", params.grad) + optimizer.step() +print(params) From b0d805cf5551cec7e6f8bb3dc7fffd83a0f2b78c Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Tue, 17 Sep 2024 17:02:43 +0400 Subject: [PATCH 05/34] gradients passing but value is zero... --- src/qibo/backends/numpy.py | 3 --- src/qibo/backends/pytorch.py | 35 +++++++++++++++++++++++------------ tests/test_torch_gradients.py | 12 ++++++------ 3 files changed, 29 insertions(+), 21 deletions(-) diff --git a/src/qibo/backends/numpy.py b/src/qibo/backends/numpy.py index 11ca9a4efc..1d6e00dd2b 100644 --- a/src/qibo/backends/numpy.py +++ b/src/qibo/backends/numpy.py @@ -167,7 +167,6 @@ def matrix_fused(self, fgate): def apply_gate(self, gate, state, nqubits): state = self.np.reshape(state, nqubits * (2,)) matrix = gate.matrix(self) - print("matrix applied:", matrix) if gate.is_controlled_by: matrix = self.np.reshape(matrix, 2 * len(gate.target_qubits) * (2,)) ncontrol = len(gate.control_qubits) @@ -437,10 +436,8 @@ def execute_circuit(self, circuit, initial_state=None, nshots=1000): if initial_state is None: state = self.zero_state(nqubits) - print("state before circuit execution:", state) for gate in circuit.queue: state = gate.apply(self, state, nqubits) - print("state during circuit execution:", state) if circuit.has_unitary_channel: # here we necessarily have `density_matrix=True`, otherwise diff --git a/src/qibo/backends/pytorch.py b/src/qibo/backends/pytorch.py index e1759eff86..049c04c51a 100644 --- a/src/qibo/backends/pytorch.py +++ b/src/qibo/backends/pytorch.py @@ -121,23 +121,34 @@ def matrix_parametrized(self, gate): name = gate.__class__.__name__ _matrix = getattr(self.matrices, name) if name == "GeneralizedRBS": + for parameter in ["theta", "phi"]: + if not isinstance(gate.init_kwargs[parameter], self.np.Tensor): + gate.init_kwargs[parameter] = self.cast_parameter( + gate.init_kwargs[parameter], trainable=gate.trainable + ) + elif gate.init_kwargs[parameter].requires_grad == True: + gate.trainable = True + else: + gate.trainable = False _matrix = _matrix( qubits_in=gate.init_args[0], qubits_out=gate.init_args[1], - theta=self.cast_parameter( - gate.init_kwargs["theta"], trainable=gate.trainable - ), - phi=self.cast_parameter( - gate.init_kwargs["phi"], trainable=gate.trainable - ), + theta=gate.init_kwargs["theta"], + phi=gate.init_kwargs["phi"], ) else: - parameters = ( - self.cast_parameter(param, trainable=gate.trainable) - for param in gate.parameters - ) - _matrix = _matrix(*parameters) - print("parameterized matrix:", _matrix) + if not isinstance(gate.parameters[0], self.np.Tensor): + parameters = tuple( + self.cast_parameter(param, trainable=gate.trainable) + for param in gate.parameters + ) + gate.parameters = parameters + elif gate.parameters[0].requires_grad == True: + gate.trainable = True + print("gate.parameters:", *gate.parameters) + else: + gate.trainable = False + _matrix = _matrix(*gate.parameters) return _matrix def cast_parameter(self, x, trainable): diff --git a/tests/test_torch_gradients.py b/tests/test_torch_gradients.py index e70e1e80a3..e9bf30d714 100644 --- a/tests/test_torch_gradients.py +++ b/tests/test_torch_gradients.py @@ -9,21 +9,21 @@ nepochs = 2 optimizer = torch.optim.Adam target_state = torch.ones(2, dtype=torch.complex128) / 2.0 -# Define circuit ansatz -params = torch.tensor(torch.rand(1, dtype=torch.float64), requires_grad=True) -print(params) +params = torch.rand(1, dtype=torch.float64, requires_grad=True) +print("Initial params", params) c = models.Circuit(1) -c.add(gates.RX(0, params[0])) +gate = gates.RX(0, params) +c.add(gate) + optimizer = optimizer([params]) for _ in range(nepochs): optimizer.zero_grad() c.set_parameters(params) final_state = c().state() - print("final state:", final_state) fidelity = torch.abs(torch.sum(torch.conj(target_state) * final_state)) loss = 1 - fidelity loss.backward() print("loss:", loss) print("params.grad:", params.grad) optimizer.step() -print(params) +print("Final parameters:", params) From 0e193a446f325610226574b2087bfc695211d3e7 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Tue, 17 Sep 2024 17:50:34 +0400 Subject: [PATCH 06/34] working gradient --- Digraph.gv | 133 ++++++++++++++++++++++++++++++++++ loss | 58 +++++++++++++++ loss.jpg | Bin 0 -> 32028 bytes src/qibo/backends/pytorch.py | 1 - tests/test_torch_gradients.py | 15 +++- 5 files changed, 202 insertions(+), 5 deletions(-) create mode 100644 Digraph.gv create mode 100644 loss create mode 100644 loss.jpg diff --git a/Digraph.gv b/Digraph.gv new file mode 100644 index 0000000000..477f5f7c71 --- /dev/null +++ b/Digraph.gv @@ -0,0 +1,133 @@ +digraph { + graph [size="12,12"] + node [align=left fontname=monospace fontsize=10 height=0.2 ranksep=0.1 shape=box style=filled] + 127274956327920 [label=" + ()" fillcolor=darkolivegreen1] + 127274956315360 [label="RsubBackward1 +------------- +alpha: 1"] + 127274956315168 -> 127274956315360 + 127274956315168 -> 127274956327632 [dir=none] + 127274956327632 [label="self + ()" fillcolor=orange] + 127274956315168 [label="AbsBackward0 +-------------------- +self: [saved tensor]"] + 127274956314976 -> 127274956315168 + 127274956314976 [label="SumBackward0 +-------------------- +self_sym_sizes: (2,)"] + 127274956314880 -> 127274956314976 + 127274956314880 -> 127274956046064 [dir=none] + 127274956046064 [label="self + (2)" fillcolor=orange] + 127274956314880 [label="MulBackward0 +--------------------- +other: None +self : [saved tensor]"] + 127274956315840 -> 127274956314880 + 127274956315840 [label="ViewBackward0 +-------------------- +self_sym_sizes: (2,)"] + 127274956315936 -> 127274956315840 + 127274956315936 [label="ViewBackward0 +---------------------- +self_sym_sizes: (2, 1)"] + 127274956316032 -> 127274956315936 + 127274956316032 [label="PermuteBackward0 +---------------- +dims: (1, 0)"] + 127274956316128 -> 127274956316032 + 127274956316128 [label="ViewBackward0 +------------------------- +self_sym_sizes: (1, 1, 2)"] + 127274956316224 -> 127274956316128 + 127274956316224 -> 127274956328976 [dir=none] + 127274956328976 [label="self + (1, 1, 2)" fillcolor=orange] + 127274956316224 [label="BmmBackward0 +-------------------- +mat2: None +self: [saved tensor]"] + 127274956316320 -> 127274956316224 + 127274956316320 [label="ReshapeAliasBackward0 +---------------------- +self_sym_sizes: (2, 2)"] + 127274956316416 -> 127274956316320 + 127274956316416 [label="PermuteBackward0 +---------------- +dims: (1, 0)"] + 127274956316512 -> 127274956316416 + 127274956316512 [label="PermuteBackward0 +---------------- +dims: (0, 1)"] + 127274956316608 -> 127274956316512 + 127274956316608 [label="ViewBackward0 +---------------------- +self_sym_sizes: (2, 2)"] + 127274956316704 -> 127274956316608 + 127274956316704 [label="ViewBackward0 +-------------------- +self_sym_sizes: (4,)"] + 127274956316800 -> 127274956316704 + 127274956316800 [label="StackBackward0 +-------------- +dim: 0"] + 127274956316896 -> 127274956316800 + 127274956316896 [label="AddBackward0 +------------ +alpha: 1"] + 127274956317040 -> 127274956316896 + 127274956317040 -> 127274956045872 [dir=none] + 127274956045872 [label="self + ()" fillcolor=orange] + 127274956317040 [label="CosBackward0 +-------------------- +self: [saved tensor]"] + 127274956317136 -> 127274956317040 + 127274956317136 -> 127274956330224 [dir=none] + 127274956330224 [label="other + ()" fillcolor=orange] + 127274956317136 [label="DivBackward0 +--------------------- +other: [saved tensor] +self : None"] + 127274956317232 -> 127274956317136 + 127274956317232 [label="SelectBackward0 +-------------------- +dim : 0 +index : 0 +self_sym_sizes: (1,)"] + 127274956317328 -> 127274956317232 + 127275408982384 [label=" + (1)" fillcolor=lightblue] + 127275408982384 -> 127274956317328 + 127274956317328 [label=AccumulateGrad] + 127274956316848 -> 127274956316800 + 127274956316848 -> 127274956330896 [dir=none] + 127274956330896 [label="other + ()" fillcolor=orange] + 127274956316848 [label="MulBackward0 +--------------------- +other: [saved tensor] +self : None"] + 127274956317184 -> 127274956316848 + 127274956317184 -> 127274956045584 [dir=none] + 127274956045584 [label="self + ()" fillcolor=orange] + 127274956317184 [label="SinBackward0 +-------------------- +self: [saved tensor]"] + 127274956317472 -> 127274956317184 + 127274956317472 -> 127274956331376 [dir=none] + 127274956331376 [label="other + ()" fillcolor=orange] + 127274956317472 [label="DivBackward0 +--------------------- +other: [saved tensor] +self : None"] + 127274956317232 -> 127274956317472 + 127274956316848 -> 127274956316800 + 127274956316896 -> 127274956316800 + 127274956315360 -> 127274956327920 +} diff --git a/loss b/loss new file mode 100644 index 0000000000..88c25df5a8 --- /dev/null +++ b/loss @@ -0,0 +1,58 @@ +digraph { + graph [size="12,12"] + node [align=left fontname=monospace fontsize=10 height=0.2 ranksep=0.1 shape=box style=filled] + 139792478913072 [label=" + ()" fillcolor=darkolivegreen1] + 139792479116192 [label=RsubBackward1] + 139792479117248 -> 139792479116192 + 139792479117248 [label=AbsBackward0] + 139792479117152 -> 139792479117248 + 139792479117152 [label=SumBackward0] + 139792479117344 -> 139792479117152 + 139792479117344 [label=MulBackward0] + 139792479118064 -> 139792479117344 + 139792479118064 [label=ViewBackward0] + 139792479118592 -> 139792479118064 + 139792479118592 [label=ViewBackward0] + 139792479118688 -> 139792479118592 + 139792479118688 [label=PermuteBackward0] + 139792479118784 -> 139792479118688 + 139792479118784 [label=ViewBackward0] + 139792479118880 -> 139792479118784 + 139792479118880 [label=BmmBackward0] + 139792479118976 -> 139792479118880 + 139792479118976 [label=ReshapeAliasBackward0] + 139792479119072 -> 139792479118976 + 139792479119072 [label=PermuteBackward0] + 139792479119168 -> 139792479119072 + 139792479119168 [label=PermuteBackward0] + 139792479119264 -> 139792479119168 + 139792479119264 [label=ViewBackward0] + 139792479119360 -> 139792479119264 + 139792479119360 [label=ViewBackward0] + 139792479119456 -> 139792479119360 + 139792479119456 [label=StackBackward0] + 139792479119552 -> 139792479119456 + 139792479119552 [label=AddBackward0] + 139792479119696 -> 139792479119552 + 139792479119696 [label=CosBackward0] + 139792479119792 -> 139792479119696 + 139792479119792 [label=DivBackward0] + 139792479119888 -> 139792479119792 + 139792479119888 [label=SelectBackward0] + 139792479118400 -> 139792479119888 + 139792931783120 [label=" + (1)" fillcolor=lightblue] + 139792931783120 -> 139792479118400 + 139792479118400 [label=AccumulateGrad] + 139792479119504 -> 139792479119456 + 139792479119504 [label=MulBackward0] + 139792479119840 -> 139792479119504 + 139792479119840 [label=SinBackward0] + 139792479120032 -> 139792479119840 + 139792479120032 [label=DivBackward0] + 139792479119888 -> 139792479120032 + 139792479119504 -> 139792479119456 + 139792479119552 -> 139792479119456 + 139792479116192 -> 139792478913072 +} diff --git a/loss.jpg b/loss.jpg new file mode 100644 index 0000000000000000000000000000000000000000..d66ff0f0efb71d18b5fcd72b1062390a7e63b886 GIT binary patch literal 32028 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 literal 0 HcmV?d00001 diff --git a/src/qibo/backends/pytorch.py b/src/qibo/backends/pytorch.py index 049c04c51a..841d2c15f7 100644 --- a/src/qibo/backends/pytorch.py +++ b/src/qibo/backends/pytorch.py @@ -145,7 +145,6 @@ def matrix_parametrized(self, gate): gate.parameters = parameters elif gate.parameters[0].requires_grad == True: gate.trainable = True - print("gate.parameters:", *gate.parameters) else: gate.trainable = False _matrix = _matrix(*gate.parameters) diff --git a/tests/test_torch_gradients.py b/tests/test_torch_gradients.py index e9bf30d714..6ab296e437 100644 --- a/tests/test_torch_gradients.py +++ b/tests/test_torch_gradients.py @@ -2,13 +2,15 @@ qibo.set_backend("pytorch") import torch +from torchviz import make_dot from qibo import gates, models # Optimization parameters -nepochs = 2 +nepochs = 1001 optimizer = torch.optim.Adam -target_state = torch.ones(2, dtype=torch.complex128) / 2.0 +target_state = torch.rand(2, dtype=torch.complex128) +target_state = target_state / torch.norm(target_state) params = torch.rand(1, dtype=torch.float64, requires_grad=True) print("Initial params", params) c = models.Circuit(1) @@ -22,8 +24,13 @@ final_state = c().state() fidelity = torch.abs(torch.sum(torch.conj(target_state) * final_state)) loss = 1 - fidelity + if _ % 100 == 0: + print("loss:", loss) loss.backward() - print("loss:", loss) - print("params.grad:", params.grad) + # print("loss:", loss) + # dot = make_dot(loss) + # dot.format = "jpg" + # dot.render("loss") + # print("params.grad:", params.grad) optimizer.step() print("Final parameters:", params) From 061a43d209df450fcc36ba5ff243cfc7f2626d16 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Mon, 23 Sep 2024 18:44:26 +0400 Subject: [PATCH 07/34] solved errors --- src/qibo/backends/npmatrices.py | 25 ++++----- src/qibo/backends/numpy.py | 6 ++ src/qibo/backends/pytorch.py | 8 +-- src/qibo/gates/channels.py | 14 +++-- src/qibo/gates/gates.py | 11 +++- src/qibo/models/circuit.py | 9 +++ tests/test_cirq.py | 8 ++- tests/test_gates_gates.py | 8 ++- tests/test_models_circuit_parametrized.py | 18 +++--- tests/test_models_circuit_qasm_cirq.py | 68 +++++++++++++---------- tests/test_torch_gradients.py | 60 ++++++++++---------- 11 files changed, 131 insertions(+), 104 deletions(-) diff --git a/src/qibo/backends/npmatrices.py b/src/qibo/backends/npmatrices.py index 5a4a2a9b4a..1ec6c9aefd 100644 --- a/src/qibo/backends/npmatrices.py +++ b/src/qibo/backends/npmatrices.py @@ -62,10 +62,10 @@ def TDG(self): ) def I(self, n=2): - return self._cast(self.np.eye(n), dtype=self.dtype) + return self.np.eye(n, dtype=self.dtype) def Align(self, delay, n=2): - return self._cast(self.I(n), dtype=self.dtype) + return self.I(n) def M(self): # pragma: no cover raise_error(NotImplementedError) @@ -160,28 +160,24 @@ def CZ(self): @cached_property def CSX(self): - a = (1 + 1j) / 2 - b = self.np.conj(a) return self._cast( [ - [1, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, a, b], - [0, 0, b, a], + [1 + 0j, 0, 0, 0], + [0, 1 + 0j, 0, 0], + [0, 0, (1 + 1j) / 2, (1 - 1j) / 2], + [0, 0, (1 - 1j) / 2, (1 + 1j) / 2], ], dtype=self.dtype, ) @cached_property def CSXDG(self): - a = (1 - 1j) / 2 - b = self.np.conj(a) return self._cast( [ - [1, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, a, b], - [0, 0, b, a], + [1 + 0j, 0, 0, 0], + [0, 1 + 0j, 0, 0], + [0, 0, (1 - 1j) / 2, (1 + 1j) / 2], + [0, 0, (1 + 1j) / 2, (1 - 1j) / 2], ], dtype=self.dtype, ) @@ -472,7 +468,6 @@ def CCZ(self): ) def DEUTSCH(self, theta): - theta = self._cast_parameter(theta) sin = self.np.sin(theta) + 0j # 0j necessary for right tensorflow dtype cos = self.np.cos(theta) + 0j return self._cast( diff --git a/src/qibo/backends/numpy.py b/src/qibo/backends/numpy.py index 2216d1094a..2347ca2949 100644 --- a/src/qibo/backends/numpy.py +++ b/src/qibo/backends/numpy.py @@ -430,6 +430,9 @@ def execute_circuit(self, circuit, initial_state=None, nshots=1000): if initial_state is None: state = self.zero_density_matrix(nqubits) + else: + state = self.cast(initial_state) + for gate in circuit.queue: state = gate.apply_density_matrix(self, state, nqubits) @@ -437,6 +440,9 @@ def execute_circuit(self, circuit, initial_state=None, nshots=1000): if initial_state is None: state = self.zero_state(nqubits) + else: + state = self.cast(initial_state) + for gate in circuit.queue: state = gate.apply(self, state, nqubits) diff --git a/src/qibo/backends/pytorch.py b/src/qibo/backends/pytorch.py index 4896a8d015..504e34a4b8 100644 --- a/src/qibo/backends/pytorch.py +++ b/src/qibo/backends/pytorch.py @@ -46,8 +46,6 @@ def __init__(self): # Default data type used for the gate matrices is complex128 self.dtype = self._torch_dtype(self.dtype) - # Default parameters dtype is float64 - self.parameters_dtype = torch.float64 self.matrices = TorchMatrices(self.dtype) self.device = self.np.device("cuda:0" if torch.cuda.is_available() else "cpu") self.nthreads = 0 @@ -130,7 +128,7 @@ def matrix_parametrized(self, gate): gate.init_kwargs[parameter] = self.cast_parameter( gate.init_kwargs[parameter], trainable=gate.trainable ) - elif gate.init_kwargs[parameter].requires_grad == True: + elif gate.init_kwargs[parameter].requires_grad: gate.trainable = True else: gate.trainable = False @@ -147,7 +145,7 @@ def matrix_parametrized(self, gate): for param in gate.parameters ) gate.parameters = parameters - elif gate.parameters[0].requires_grad == True: + elif gate.parameters[0].requires_grad: gate.trainable = True else: gate.trainable = False @@ -155,7 +153,7 @@ def matrix_parametrized(self, gate): return _matrix def cast_parameter(self, x, trainable): - return self.np.tensor(x, dtype=self.parameters_dtype, requires_grad=trainable) + return self.np.tensor(x, requires_grad=trainable) def is_sparse(self, x): if isinstance(x, self.np.Tensor): diff --git a/src/qibo/gates/channels.py b/src/qibo/gates/channels.py index be43678318..ba4f285df2 100644 --- a/src/qibo/gates/channels.py +++ b/src/qibo/gates/channels.py @@ -653,12 +653,14 @@ def apply_density_matrix(self, backend, state, nqubits): self.init_kwargs["p_1"], self.init_kwargs["e_t2"], ) - matrix = [ - [1 - preset1, 0, 0, preset0], - [0, e_t2, 0, 0], - [0, 0, e_t2, 0], - [preset1, 0, 0, 1 - preset0], - ] + matrix = backend.cast( + [ + [1 - preset1, 0, 0, preset0], + [0, e_t2, 0, 0], + [0, 0, e_t2, 0], + [preset1, 0, 0, 1 - preset0], + ] + ) qubits = (qubit, qubit + nqubits) gate = Unitary(matrix, *qubits) diff --git a/src/qibo/gates/gates.py b/src/qibo/gates/gates.py index 98d6baa289..92bc569beb 100644 --- a/src/qibo/gates/gates.py +++ b/src/qibo/gates/gates.py @@ -506,13 +506,16 @@ def qasm_label(self): class Align(ParametrizedGate): """Aligns proceeding qubit operations and (optionally) waits ``delay`` amount of time. + .. note:: + For this gate, the trainable parameter is by default set to ``False``. + Args: q (int): The qubit ID. delay (int, optional): The time (in ns) for which to delay circuit execution on the specified qubits. Defaults to ``0`` (zero). """ - def __init__(self, q, delay=0, trainable=True): + def __init__(self, q, delay=0, trainable=False): if not isinstance(delay, int): raise_error( TypeError, f"delay must be type int, but it is type {type(delay)}." @@ -2600,7 +2603,7 @@ def __init__( @Gate.parameters.setter def parameters(self, x): shape = self.parameters[0].shape - engine = _check_engine(self.parameters[0]) + engine = _check_engine(x) # Reshape doesn't accept a tuple if engine is pytorch. x = x[0] if type(x) is tuple else x self._parameters = (engine.reshape(x, shape),) @@ -2633,7 +2636,9 @@ def _dagger(self): def _check_engine(array): """Check if the array is a numpy or torch tensor and return the corresponding library.""" - if array.__class__.__name__ == "Tensor": + if (array.__class__.__name__ == "Tensor") or ( + type(array) is tuple and array[0].__class__.__name__ == "Tensor" + ): import torch # pylint: disable=C0415 return torch diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index 32be6d9dc6..12ce3227e5 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -1142,6 +1142,9 @@ def from_dict(cls, raw): def to_qasm(self): """Convert circuit to QASM. + .. note:: + This method doesn't support multi-controlled gates and gates with torch Tensors parameters. + Args: filename (str): The filename where the code is saved. """ @@ -1174,6 +1177,12 @@ def to_qasm(self): qubits = ",".join(f"q[{i}]" for i in gate.qubits) if isinstance(gate, gates.ParametrizedGate): + for x in gate.parameters: + if x.__class__.__name__ == "Tensor": + raise_error( + ValueError, + "OpenQASM does not support gates with torch Tensors parameters.", + ) params = (str(x) for x in gate.parameters) name = f"{gate.qasm_label}({', '.join(params)})" else: diff --git a/tests/test_cirq.py b/tests/test_cirq.py index 8ff8498d7a..0a99e1405e 100644 --- a/tests/test_cirq.py +++ b/tests/test_cirq.py @@ -60,9 +60,13 @@ def assert_gates_equivalent( assert c.depth == target_depth if accelerators and not backend.supports_multigpu: with pytest.raises(NotImplementedError): - final_state = backend.execute_circuit(c, np.copy(initial_state)).state() + final_state = backend.execute_circuit( + c, backend.cast(initial_state, copy=True) + ).state() else: - final_state = backend.execute_circuit(c, np.copy(initial_state)).state() + final_state = backend.execute_circuit( + c, backend.cast(initial_state, copy=True) + ).state() backend.assert_allclose(final_state, target_state, atol=atol) diff --git a/tests/test_gates_gates.py b/tests/test_gates_gates.py index 153528cd17..50a07e1c08 100644 --- a/tests/test_gates_gates.py +++ b/tests/test_gates_gates.py @@ -860,6 +860,7 @@ def test_generalized_fsim(backend): phi = np.random.random() rotation = np.random.random((2, 2)) + 1j * np.random.random((2, 2)) gatelist = [gates.H(0), gates.H(1), gates.H(2)] + gate = gates.GeneralizedfSim(1, 2, rotation, phi) gatelist.append(gates.GeneralizedfSim(1, 2, rotation, phi)) final_state = apply_gates(backend, gatelist, nqubits=3) target_state = np.ones(len(final_state), dtype=complex) / np.sqrt(8) @@ -870,6 +871,7 @@ def test_generalized_fsim(backend): target_state[:4] = np.matmul(matrix, target_state[:4]) target_state[4:] = np.matmul(matrix, target_state[4:]) target_state = backend.cast(target_state, dtype=target_state.dtype) + print(final_state) backend.assert_allclose(final_state, target_state, atol=1e-6) with pytest.raises(NotImplementedError): @@ -1382,8 +1384,8 @@ def test_unitary_initialization(backend): def test_unitary_common_gates(backend): target_state = apply_gates(backend, [gates.X(0), gates.H(1)], nqubits=2) gatelist = [ - gates.Unitary(np.array([[0, 1], [1, 0]]), 0), - gates.Unitary(np.array([[1, 1], [1, -1]]) / np.sqrt(2), 1), + gates.Unitary(backend.cast([[0.0, 1.0], [1.0, 0.0]]), 0), + gates.Unitary(backend.cast([[1.0, 1.0], [1.0, -1.0]]) / np.sqrt(2), 1), ] final_state = apply_gates(backend, gatelist, nqubits=2) backend.assert_allclose(final_state, target_state, atol=1e-6) @@ -1405,7 +1407,7 @@ def test_unitary_common_gates(backend): [np.sin(thetay / 2), np.cos(thetay / 2)], ] ) - cnot = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]) + cnot = np.array([[1.0, 0, 0, 0], [0, 1.0, 0, 0], [0, 0, 0, 1.0], [0, 0, 1.0, 0]]) gatelist = [gates.Unitary(rx, 0), gates.Unitary(ry, 1), gates.Unitary(cnot, 0, 1)] final_state = apply_gates(backend, gatelist, nqubits=2) backend.assert_allclose(final_state, target_state, atol=1e-6) diff --git a/tests/test_models_circuit_parametrized.py b/tests/test_models_circuit_parametrized.py index 1a761f16eb..4e04ccd248 100644 --- a/tests/test_models_circuit_parametrized.py +++ b/tests/test_models_circuit_parametrized.py @@ -39,12 +39,12 @@ def test_set_parameters_with_list(backend, trainable): params = [0.123, 0.456, (0.789, 0.321)] c = Circuit(3) if trainable: - c.add(gates.RX(0, theta=0, trainable=trainable)) + c.add(gates.RX(0, theta=0.0, trainable=trainable)) else: c.add(gates.RX(0, theta=params[0], trainable=trainable)) - c.add(gates.RY(1, theta=0)) + c.add(gates.RY(1, theta=0.0)) c.add(gates.CZ(1, 2)) - c.add(gates.fSim(0, 2, theta=0, phi=0)) + c.add(gates.fSim(0, 2, theta=0.0, phi=0.0)) c.add(gates.H(2)) # execute once final_state = backend.execute_circuit(c) @@ -78,16 +78,16 @@ def test_circuit_set_parameters_ungates(backend, trainable, accelerators): trainable_params = [0.1, 0.3, (0.4, 0.5)] c = Circuit(3, accelerators) - c.add(gates.RX(0, theta=0)) + c.add(gates.RX(0, theta=0.0)) if trainable: - c.add(gates.CRY(0, 1, theta=0, trainable=trainable)) + c.add(gates.CRY(0, 1, theta=0.0, trainable=trainable)) else: c.add(gates.CRY(0, 1, theta=params[1], trainable=trainable)) c.add(gates.CZ(1, 2)) - c.add(gates.U1(2, theta=0)) - c.add(gates.CU2(0, 2, phi=0, lam=0)) + c.add(gates.U1(2, theta=0.0)) + c.add(gates.CU2(0, 2, phi=0.0, lam=0.0)) if trainable: - c.add(gates.U3(1, theta=0, phi=0, lam=0, trainable=trainable)) + c.add(gates.U3(1, theta=0.0, phi=0.0, lam=0.0, trainable=trainable)) else: c.add(gates.U3(1, *params[4], trainable=trainable)) # execute once @@ -127,7 +127,7 @@ def test_circuit_set_parameters_with_unitary(backend, trainable, accelerators): """Check updating parameters of circuit that contains ``Unitary`` gate.""" params = [0.1234, np.random.random((4, 4))] c = Circuit(4, accelerators) - c.add(gates.RX(0, theta=0)) + c.add(gates.RX(0, theta=0.0)) if trainable: c.add(gates.Unitary(np.zeros((4, 4)), 1, 2, trainable=trainable)) trainable_params = list(params) diff --git a/tests/test_models_circuit_qasm_cirq.py b/tests/test_models_circuit_qasm_cirq.py index f0e683e88b..34c5c54a44 100644 --- a/tests/test_models_circuit_qasm_cirq.py +++ b/tests/test_models_circuit_qasm_cirq.py @@ -133,17 +133,21 @@ def test_parametrized_gate_cirq(backend): c1.add(gates.RY(1, 0.1234)) final_state_c1 = backend.execute_circuit(c1).state() - c2 = circuit_from_qasm(c1.to_qasm()) - c2depth = len(cirq.Circuit(c2.all_operations())) - assert c1.depth == c2depth - final_state_c2 = ( - cirq.Simulator().simulate(c2).final_state_vector - ) # pylint: disable=no-member - backend.assert_allclose(final_state_c1, final_state_c2, atol=_atol) + if backend.name == "pytorch": + with pytest.raises(ValueError): + c2 = circuit_from_qasm(c1.to_qasm()) + else: + c2 = circuit_from_qasm(c1.to_qasm()) + c2depth = len(cirq.Circuit(c2.all_operations())) + assert c1.depth == c2depth + final_state_c2 = ( + cirq.Simulator().simulate(c2).final_state_vector + ) # pylint: disable=no-member + backend.assert_allclose(final_state_c1, final_state_c2, atol=_atol) - c3 = Circuit.from_qasm(c2.to_qasm()) - final_state_c3 = backend.execute_circuit(c3).state() - backend.assert_allclose(final_state_c3, final_state_c2, atol=_atol) + c3 = Circuit.from_qasm(c2.to_qasm()) + final_state_c3 = backend.execute_circuit(c3).state() + backend.assert_allclose(final_state_c3, final_state_c2, atol=_atol) def test_cu1_cirq(): @@ -163,27 +167,31 @@ def test_ugates_cirq(backend): c1.add(gates.U2(2, 0.5, 0.6)) final_state_c1 = backend.execute_circuit(c1).state() - c2 = circuit_from_qasm(c1.to_qasm()) - c2depth = len(cirq.Circuit(c2.all_operations())) - assert c1.depth == c2depth - final_state_c2 = ( - cirq.Simulator().simulate(c2).final_state_vector - ) # pylint: disable=no-member - backend.assert_allclose(final_state_c1, final_state_c2, atol=_atol) - - c3 = Circuit.from_qasm(c2.to_qasm()) - assert c3.depth == c2depth - final_state_c3 = backend.execute_circuit(c3).state() - backend.assert_allclose(final_state_c3, final_state_c2, atol=_atol) - - c1 = Circuit(3) - c1.add(gates.RX(0, 0.1)) - c1.add(gates.RZ(1, 0.4)) - c1.add(gates.U2(2, 0.5, 0.6)) - c1.add(gates.CU3(2, 1, 0.2, 0.3, 0.4)) - # catches unknown gate "cu3" - with pytest.raises(exception.QasmException): + if backend.name == "pytorch": + with pytest.raises(ValueError): + c2 = circuit_from_qasm(c1.to_qasm()) + else: c2 = circuit_from_qasm(c1.to_qasm()) + c2depth = len(cirq.Circuit(c2.all_operations())) + assert c1.depth == c2depth + final_state_c2 = ( + cirq.Simulator().simulate(c2).final_state_vector + ) # pylint: disable=no-member + backend.assert_allclose(final_state_c1, final_state_c2, atol=_atol) + + c3 = Circuit.from_qasm(c2.to_qasm()) + assert c3.depth == c2depth + final_state_c3 = backend.execute_circuit(c3).state() + backend.assert_allclose(final_state_c3, final_state_c2, atol=_atol) + + c1 = Circuit(3) + c1.add(gates.RX(0, 0.1)) + c1.add(gates.RZ(1, 0.4)) + c1.add(gates.U2(2, 0.5, 0.6)) + c1.add(gates.CU3(2, 1, 0.2, 0.3, 0.4)) + # catches unknown gate "cu3" + with pytest.raises(exception.QasmException): + c2 = circuit_from_qasm(c1.to_qasm()) def test_crotations_cirq(): diff --git a/tests/test_torch_gradients.py b/tests/test_torch_gradients.py index 6ab296e437..e3faa7a938 100644 --- a/tests/test_torch_gradients.py +++ b/tests/test_torch_gradients.py @@ -1,36 +1,34 @@ -import qibo - -qibo.set_backend("pytorch") +import pytest import torch -from torchviz import make_dot +import qibo from qibo import gates, models -# Optimization parameters -nepochs = 1001 -optimizer = torch.optim.Adam -target_state = torch.rand(2, dtype=torch.complex128) -target_state = target_state / torch.norm(target_state) -params = torch.rand(1, dtype=torch.float64, requires_grad=True) -print("Initial params", params) -c = models.Circuit(1) -gate = gates.RX(0, params) -c.add(gate) -optimizer = optimizer([params]) -for _ in range(nepochs): - optimizer.zero_grad() - c.set_parameters(params) - final_state = c().state() - fidelity = torch.abs(torch.sum(torch.conj(target_state) * final_state)) - loss = 1 - fidelity - if _ % 100 == 0: - print("loss:", loss) - loss.backward() - # print("loss:", loss) - # dot = make_dot(loss) - # dot.format = "jpg" - # dot.render("loss") - # print("params.grad:", params.grad) - optimizer.step() -print("Final parameters:", params) +def tetst_torch_gradients(): + qibo.set_backend("pytorch") + torch.seed(42) + nepochs = 1001 + optimizer = torch.optim.Adam + target_state = torch.rand(2, dtype=torch.complex128) + target_state = target_state / torch.norm(target_state) + params = torch.rand(2, dtype=torch.float64, requires_grad=True) + c = models.Circuit(1) + c.add(gates.RX(0, params[0])) + c.add(gates.RY(0, params[1])) + + initial_params = params.clone() + initial_loss = 1 - torch.abs(torch.sum(torch.conj(target_state) * c().state())) + + optimizer = optimizer([params]) + for _ in range(nepochs): + optimizer.zero_grad() + c.set_parameters(params) + final_state = c().state() + fidelity = torch.abs(torch.sum(torch.conj(target_state) * final_state)) + loss = 1 - fidelity + loss.backward() + optimizer.step() + + assert initial_loss > loss + assert initial_params[0] != params[0] From 3ce8a2d44bbc1e4ae268ae3e4fbf89438ee1cbf1 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Tue, 24 Sep 2024 17:46:47 +0400 Subject: [PATCH 08/34] solve errors --- src/qibo/backends/__init__.py | 14 +++ src/qibo/backends/pytorch.py | 34 +++--- src/qibo/transpiler/decompositions.py | 109 ++++++++++-------- src/qibo/transpiler/unitary_decompositions.py | 55 ++++----- src/qibo/transpiler/unroller.py | 59 ++++++---- tests/test_models_encodings.py | 6 +- tests/test_models_hep.py | 9 +- tests/test_models_qft.py | 6 +- tests/test_models_variational.py | 15 ++- tests/test_noise.py | 4 +- tests/test_quantum_info_entanglement.py | 4 +- tests/test_tomography_gate_set_tomography.py | 2 +- tests/test_transpiler_decompositions.py | 22 ++-- .../test_transpiler_unitary_decompositions.py | 18 +-- 14 files changed, 212 insertions(+), 145 deletions(-) diff --git a/src/qibo/backends/__init__.py b/src/qibo/backends/__init__.py index 77aaab3442..4f88952b24 100644 --- a/src/qibo/backends/__init__.py +++ b/src/qibo/backends/__init__.py @@ -198,6 +198,20 @@ def _check_backend(backend): return backend +def _find_backend(x): + """Finds the backend of a given object.""" + if isinstance(x, tuple) or isinstance(x, list): + return _find_backend(x[0]) + if x.__class__.__name__ == "tensor": + return PyTorchBackend() + elif x.__class__.__name__ == "TensorflowTensor": + return TensorflowBackend() + elif isinstance(x, np.ndarray): + return NumpyBackend() + else: + raise TypeError("Unsupported type for backend detection") + + def list_available_backends(*providers: str) -> dict: """Lists all the backends that are available.""" available_backends = MetaBackend().list_available() diff --git a/src/qibo/backends/pytorch.py b/src/qibo/backends/pytorch.py index 504e34a4b8..319f81d9c2 100644 --- a/src/qibo/backends/pytorch.py +++ b/src/qibo/backends/pytorch.py @@ -54,6 +54,7 @@ def __init__(self): # These functions in Torch works in a different way than numpy or have different names self.np.transpose = self.np.permute self.np.copy = self.np.clone + self.np.power = self.np.pow self.np.expand_dims = self.np.unsqueeze self.np.mod = self.np.remainder self.np.right_shift = self.np.bitwise_right_shift @@ -115,7 +116,6 @@ def cast( if copy: return x.clone() - return x def matrix_parametrized(self, gate): @@ -138,21 +138,29 @@ def matrix_parametrized(self, gate): theta=gate.init_kwargs["theta"], phi=gate.init_kwargs["phi"], ) - else: - if not isinstance(gate.parameters[0], self.np.Tensor): - parameters = tuple( - self.cast_parameter(param, trainable=gate.trainable) - for param in gate.parameters - ) - gate.parameters = parameters - elif gate.parameters[0].requires_grad: - gate.trainable = True - else: - gate.trainable = False - _matrix = _matrix(*gate.parameters) + return _matrix + if not self.check_parameters(gate.parameters): + new_parameters = [] + for parameter in gate.parameters: + if not isinstance(parameter, self.np.Tensor): + parameter = self.cast_parameter(parameter, trainable=gate.trainable) + elif parameter.requires_grad: + gate.trainable = True + new_parameters.append(parameter) + gate.parameters = tuple(new_parameters) + _matrix = _matrix(*gate.parameters) return _matrix + def check_parameters(self, parameters): + """Check if the parameters are torch tensors.""" + for parameter in parameters: + if not isinstance(parameter, self.np.Tensor): + return False + return True + def cast_parameter(self, x, trainable): + if isinstance(x, int) and trainable: + return self.np.tensor(x, dtype=self.np.float64, requires_grad=True) return self.np.tensor(x, requires_grad=trainable) def is_sparse(self, x): diff --git a/src/qibo/transpiler/decompositions.py b/src/qibo/transpiler/decompositions.py index 29d691402a..5a0cecb0c5 100644 --- a/src/qibo/transpiler/decompositions.py +++ b/src/qibo/transpiler/decompositions.py @@ -1,14 +1,11 @@ import numpy as np from qibo import gates -from qibo.backends import NumpyBackend from qibo.transpiler.unitary_decompositions import ( two_qubit_decomposition, u3_decomposition, ) -backend = NumpyBackend() - class GateDecompositions: """Abstract data structure that holds decompositions of gates.""" @@ -20,27 +17,27 @@ def add(self, gate, decomposition): """Register a decomposition for a gate.""" self.decompositions[gate] = decomposition - def count_2q(self, gate): + def count_2q(self, gate, backend): """Count the number of two-qubit gates in the decomposition of the given gate.""" if gate.parameters: - decomposition = self.decompositions[gate.__class__](gate) + decomposition = self.decompositions[gate.__class__](gate, backend) else: decomposition = self.decompositions[gate.__class__] return len(tuple(g for g in decomposition if len(g.qubits) > 1)) - def count_1q(self, gate): + def count_1q(self, gate, backend): """Count the number of single qubit gates in the decomposition of the given gate.""" if gate.parameters: - decomposition = self.decompositions[gate.__class__](gate) + decomposition = self.decompositions[gate.__class__](gate, backend) else: decomposition = self.decompositions[gate.__class__] return len(tuple(g for g in decomposition if len(g.qubits) == 1)) - def __call__(self, gate): + def __call__(self, gate, backend=None): """Decompose a gate.""" decomposition = self.decompositions[gate.__class__] if callable(decomposition): - decomposition = decomposition(gate) + decomposition = decomposition(gate, backend) return [ g.on_qubits({i: q for i, q in enumerate(gate.qubits)}) for g in decomposition @@ -83,7 +80,7 @@ def _u3_to_gpi2(t, p, l): gpi2_dec.add(gates.SX, [gates.GPI2(0, 0)]) gpi2_dec.add( gates.RX, - lambda gate: [ + lambda gate, backend: [ gates.Z(0), gates.GPI2(0, np.pi / 2), gates.RZ(0, gate.parameters[0] + np.pi), @@ -92,26 +89,30 @@ def _u3_to_gpi2(t, p, l): ) gpi2_dec.add( gates.RY, - lambda gate: [ + lambda gate, backend: [ gates.GPI2(0, 0), gates.RZ(0, gate.parameters[0] + np.pi), gates.GPI2(0, 0), gates.Z(0), ], ) -gpi2_dec.add(gates.RZ, lambda gate: [gates.RZ(0, gate.parameters[0])]) -gpi2_dec.add(gates.GPI2, lambda gate: [gates.GPI2(0, gate.parameters[0])]) -gpi2_dec.add(gates.U1, lambda gate: [gates.RZ(0, gate.parameters[0])]) +gpi2_dec.add(gates.RZ, lambda gate, backend: [gates.RZ(0, gate.parameters[0])]) +gpi2_dec.add(gates.GPI2, lambda gate, backend: [gates.GPI2(0, gate.parameters[0])]) +gpi2_dec.add(gates.U1, lambda gate, backend: [gates.RZ(0, gate.parameters[0])]) gpi2_dec.add( gates.U2, - lambda gate: _u3_to_gpi2(np.pi / 2, gate.parameters[0], gate.parameters[1]), + lambda gate, backend: _u3_to_gpi2( + np.pi / 2, gate.parameters[0], gate.parameters[1] + ), ) -gpi2_dec.add(gates.U3, lambda gate: _u3_to_gpi2(*gate.parameters)) +gpi2_dec.add(gates.U3, lambda gate, backend: _u3_to_gpi2(*gate.parameters)) gpi2_dec.add( - gates.Unitary, lambda gate: _u3_to_gpi2(*u3_decomposition(gate.parameters[0])) + gates.Unitary, + lambda gate, backend: _u3_to_gpi2(*u3_decomposition(gate.parameters[0], backend)), ) gpi2_dec.add( - gates.FusedGate, lambda gate: _u3_to_gpi2(*u3_decomposition(gate.matrix(backend))) + gates.FusedGate, + lambda gate, backend: _u3_to_gpi2(*u3_decomposition(gate.matrix(backend), backend)), ) # Decompose single qubit gates using U3 @@ -126,39 +127,47 @@ def _u3_to_gpi2(t, p, l): u3_dec.add(gates.TDG, [gates.RZ(0, -np.pi / 4)]) u3_dec.add(gates.SX, [gates.U3(0, np.pi / 2, -np.pi / 2, np.pi / 2)]) u3_dec.add( - gates.RX, lambda gate: [gates.U3(0, gate.parameters[0], -np.pi / 2, np.pi / 2)] + gates.RX, + lambda gate, backend: [gates.U3(0, gate.parameters[0], -np.pi / 2, np.pi / 2)], ) -u3_dec.add(gates.RY, lambda gate: [gates.U3(0, gate.parameters[0], 0, 0)]) -u3_dec.add(gates.RZ, lambda gate: [gates.RZ(0, gate.parameters[0])]) +u3_dec.add(gates.RY, lambda gate, backend: [gates.U3(0, gate.parameters[0], 0, 0)]) +u3_dec.add(gates.RZ, lambda gate, backend: [gates.RZ(0, gate.parameters[0])]) u3_dec.add( gates.PRX, - lambda gate: [ + lambda gate, backend: [ gates.RZ(0, gate.parameters[1] - np.pi / 2), gates.RY(0, -gate.parameters[0]), gates.RZ(0, gate.parameters[1] + np.pi / 2), ], ) u3_dec.add( - gates.GPI2, lambda gate: [gates.U3(0, *u3_decomposition(gate.matrix(backend)))] + gates.GPI2, + lambda gate, backend: [ + gates.U3(0, *u3_decomposition(gate.matrix(backend), backend)) + ], ) -u3_dec.add(gates.U1, lambda gate: [gates.RZ(0, gate.parameters[0])]) +u3_dec.add(gates.U1, lambda gate, backend: [gates.RZ(0, gate.parameters[0])]) u3_dec.add( gates.U2, - lambda gate: [gates.U3(0, np.pi / 2, gate.parameters[0], gate.parameters[1])], + lambda gate, backend: [ + gates.U3(0, np.pi / 2, gate.parameters[0], gate.parameters[1]) + ], ) u3_dec.add( gates.U3, - lambda gate: [ + lambda gate, backend: [ gates.U3(0, gate.parameters[0], gate.parameters[1], gate.parameters[2]) ], ) u3_dec.add( gates.Unitary, - lambda gate: [gates.U3(0, *u3_decomposition(gate.parameters[0]))], + lambda gate, backend: [gates.U3(0, *u3_decomposition(gate.parameters[0], backend))], ) u3_dec.add( gates.FusedGate, - lambda gate: [gates.U3(0, *u3_decomposition(gate.matrix(backend)))], + lambda gate, backend: [ + gates.U3(0, *u3_decomposition(gate.matrix(backend), backend)) + ], ) # register the iSWAP decompositions @@ -237,7 +246,7 @@ def _u3_to_gpi2(t, p, l): ) cz_dec.add( gates.CRX, - lambda gate: [ + lambda gate, backend: [ gates.RX(1, gate.parameters[0] / 2.0), gates.CZ(0, 1), gates.RX(1, -gate.parameters[0] / 2.0), @@ -246,7 +255,7 @@ def _u3_to_gpi2(t, p, l): ) cz_dec.add( gates.CRY, - lambda gate: [ + lambda gate, backend: [ gates.RY(1, gate.parameters[0] / 2.0), gates.CZ(0, 1), gates.RY(1, -gate.parameters[0] / 2.0), @@ -255,7 +264,7 @@ def _u3_to_gpi2(t, p, l): ) cz_dec.add( gates.CRZ, - lambda gate: [ + lambda gate, backend: [ gates.RZ(1, gate.parameters[0] / 2.0), gates.H(1), gates.CZ(0, 1), @@ -266,7 +275,7 @@ def _u3_to_gpi2(t, p, l): ) cz_dec.add( gates.CU1, - lambda gate: [ + lambda gate, backend: [ gates.RZ(0, gate.parameters[0] / 2.0), gates.H(1), gates.CZ(0, 1), @@ -278,7 +287,7 @@ def _u3_to_gpi2(t, p, l): ) cz_dec.add( gates.CU2, - lambda gate: [ + lambda gate, backend: [ gates.RZ(1, (gate.parameters[1] - gate.parameters[0]) / 2.0), gates.H(1), gates.CZ(0, 1), @@ -292,7 +301,7 @@ def _u3_to_gpi2(t, p, l): ) cz_dec.add( gates.CU3, - lambda gate: [ + lambda gate, backend: [ gates.RZ(1, (gate.parameters[2] - gate.parameters[1]) / 2.0), gates.H(1), gates.CZ(0, 1), @@ -324,7 +333,7 @@ def _u3_to_gpi2(t, p, l): ) cz_dec.add( gates.RXX, - lambda gate: [ + lambda gate, backend: [ gates.H(0), gates.CZ(0, 1), gates.RX(1, gate.parameters[0]), @@ -334,7 +343,7 @@ def _u3_to_gpi2(t, p, l): ) cz_dec.add( gates.RYY, - lambda gate: [ + lambda gate, backend: [ gates.RX(0, np.pi / 2), gates.U3(1, np.pi / 2, np.pi / 2, np.pi), gates.CZ(0, 1), @@ -346,7 +355,7 @@ def _u3_to_gpi2(t, p, l): ) cz_dec.add( gates.RZZ, - lambda gate: [ + lambda gate, backend: [ gates.H(1), gates.CZ(0, 1), gates.RX(1, gate.parameters[0]), @@ -376,15 +385,21 @@ def _u3_to_gpi2(t, p, l): ) cz_dec.add( gates.Unitary, - lambda gate: two_qubit_decomposition(0, 1, gate.parameters[0], backend=backend), + lambda gate, backend: two_qubit_decomposition( + 0, 1, gate.parameters[0], backend=backend + ), ) cz_dec.add( gates.fSim, - lambda gate: two_qubit_decomposition(0, 1, gate.matrix(backend), backend=backend), + lambda gate, backend: two_qubit_decomposition( + 0, 1, gate.matrix(backend), backend=backend + ), ) cz_dec.add( gates.GeneralizedfSim, - lambda gate: two_qubit_decomposition(0, 1, gate.matrix(backend), backend=backend), + lambda gate, backend: two_qubit_decomposition( + 0, 1, gate.matrix(backend), backend=backend + ), ) # temporary CNOT decompositions for CNOT, CZ, SWAP @@ -446,7 +461,7 @@ def _decomposition_generalized_RBS(ins, outs, theta, phi, controls): standard_decompositions.add(gates.SXDG, [gates.RX(0, -np.pi / 2, trainable=False)]) standard_decompositions.add( gates.PRX, - lambda gate: [ + lambda gate, backend: [ gates.RZ(0, -gate.parameters[1] - np.pi / 2), gates.RY(0, -gate.parameters[0]), gates.RZ(0, gate.parameters[1] + np.pi / 2), @@ -454,7 +469,7 @@ def _decomposition_generalized_RBS(ins, outs, theta, phi, controls): ) standard_decompositions.add( gates.U3, - lambda gate: [ + lambda gate, backend: [ gates.RZ(0, gate.parameters[2]), gates.SX(0), gates.RZ(0, gate.parameters[0] + np.pi), @@ -472,7 +487,7 @@ def _decomposition_generalized_RBS(ins, outs, theta, phi, controls): ) standard_decompositions.add( gates.RZX, - lambda gate: [ + lambda gate, backend: [ gates.H(1), gates.CNOT(0, 1), gates.RZ(1, gate.parameters[0]), @@ -482,7 +497,7 @@ def _decomposition_generalized_RBS(ins, outs, theta, phi, controls): ) standard_decompositions.add( gates.RXXYY, - lambda gate: [ + lambda gate, backend: [ gates.RZ(1, -np.pi / 2), gates.S(0), gates.SX(1), @@ -499,7 +514,7 @@ def _decomposition_generalized_RBS(ins, outs, theta, phi, controls): ) standard_decompositions.add( gates.RBS, - lambda gate: [ + lambda gate, backend: [ gates.H(0), gates.CNOT(0, 1), gates.H(1), @@ -511,7 +526,7 @@ def _decomposition_generalized_RBS(ins, outs, theta, phi, controls): ], ) standard_decompositions.add( - gates.GIVENS, lambda gate: gates.RBS(0, 1, -gate.parameters[0]).decompose() + gates.GIVENS, lambda gate, backend: gates.RBS(0, 1, -gate.parameters[0]).decompose() ) standard_decompositions.add( gates.FSWAP, [gates.X(1)] + gates.GIVENS(0, 1, np.pi / 2).decompose() + [gates.X(0)] @@ -542,7 +557,7 @@ def _decomposition_generalized_RBS(ins, outs, theta, phi, controls): ) standard_decompositions.add( gates.GeneralizedRBS, - lambda gate: _decomposition_generalized_RBS( + lambda gate, backend: _decomposition_generalized_RBS( ins=list(range(len(gate.init_args[0]))), outs=list( range( diff --git a/src/qibo/transpiler/unitary_decompositions.py b/src/qibo/transpiler/unitary_decompositions.py index ad892db734..3a9a154122 100644 --- a/src/qibo/transpiler/unitary_decompositions.py +++ b/src/qibo/transpiler/unitary_decompositions.py @@ -1,8 +1,8 @@ import numpy as np from qibo import gates, matrices -from qibo.backends import _check_backend -from qibo.config import PRECISION_TOL, raise_error +from qibo.backends import _find_backend +from qibo.config import raise_error magic_basis = np.array( [[1, -1j, 0, 0], [0, 0, 1, -1j], [0, 0, -1, -1j], [1, 1j, 0, 0]] @@ -15,7 +15,7 @@ H = np.array([[1, 1], [1, -1]]) / np.sqrt(2) -def u3_decomposition(unitary): +def u3_decomposition(unitary, backend): """Decomposes arbitrary one-qubit gates to U3. Args: @@ -24,18 +24,19 @@ def u3_decomposition(unitary): Returns: (float, float, float): parameters of U3 gate. """ + unitary = backend.cast(unitary) # https://github.com/Qiskit/qiskit-terra/blob/d2e3340adb79719f9154b665e8f6d8dc26b3e0aa/qiskit/quantum_info/synthesis/one_qubit_decompose.py#L221 - su2 = unitary / np.sqrt(np.linalg.det(unitary)) - theta = 2 * np.arctan2(abs(su2[1, 0]), abs(su2[0, 0])) - plus = np.angle(su2[1, 1]) - minus = np.angle(su2[1, 0]) + su2 = unitary / backend.np.sqrt(backend.np.linalg.det(unitary)) + theta = 2 * backend.np.arctan2(backend.np.abs(su2[1, 0]), backend.np.abs(su2[0, 0])) + plus = backend.np.angle(su2[1, 1]) + minus = backend.np.angle(su2[1, 0]) phi = plus + minus lam = plus - minus return theta, phi, lam -def calculate_psi(unitary, magic_basis=magic_basis, backend=None): +def calculate_psi(unitary, backend, magic_basis=magic_basis): """Solves the eigenvalue problem of :math:`U^{T} U`. See step (1) of Appendix A in arXiv:quant-ph/0011050. @@ -50,7 +51,6 @@ def calculate_psi(unitary, magic_basis=magic_basis, backend=None): Returns: ndarray: Eigenvectors in the computational basis and eigenvalues of :math:`U^{T} U`. """ - backend = _check_backend(backend) if backend.__class__.__name__ in [ "CupyBackend", @@ -84,7 +84,7 @@ def calculate_psi(unitary, magic_basis=magic_basis, backend=None): return psi, eigvals -def schmidt_decompose(state, backend=None): +def schmidt_decompose(state, backend): """Decomposes a two-qubit product state to its single-qubit parts. Args: @@ -94,7 +94,6 @@ def schmidt_decompose(state, backend=None): (ndarray, ndarray): decomposition """ - backend = _check_backend(backend) # tf.linalg.svd has a different behaviour if backend.__class__.__name__ == "TensorflowBackend": u, d, v = np.linalg.svd(backend.np.reshape(state, (2, 2))) @@ -108,7 +107,7 @@ def schmidt_decompose(state, backend=None): return u[:, 0], v[0] -def calculate_single_qubit_unitaries(psi, backend=None): +def calculate_single_qubit_unitaries(psi, backend): """Calculates local unitaries that maps a maximally entangled basis to the magic basis. See Lemma 1 of Appendix A in arXiv:quant-ph/0011050. @@ -119,13 +118,12 @@ def calculate_single_qubit_unitaries(psi, backend=None): Returns: (ndarray, ndarray): Local unitaries UA and UB that map the given basis to the magic basis. """ - backend = _check_backend(backend) psi_magic = backend.np.matmul(backend.np.conj(backend.cast(magic_basis)).T, psi) if ( backend.np.real( backend.calculate_norm_density_matrix(backend.np.imag(psi_magic)) ) - > PRECISION_TOL + > 1e-6 ): # pragma: no cover raise_error(NotImplementedError, "Given state is not real in the magic basis.") psi_bar = backend.cast(psi.T, copy=True) @@ -154,13 +152,12 @@ def calculate_single_qubit_unitaries(psi, backend=None): return ua, ub -def calculate_diagonal(unitary, ua, ub, va, vb, backend=None): +def calculate_diagonal(unitary, ua, ub, va, vb, backend): """Calculates Ud matrix that can be written as exp(-iH). See Eq. (A1) in arXiv:quant-ph/0011050. Ud is diagonal in the magic and Bell basis. """ - backend = _check_backend(backend) # normalize U_A, U_B, V_A, V_B so that detU_d = 1 # this is required so that sum(lambdas) = 0 # and Ud can be written as exp(-iH) @@ -188,7 +185,7 @@ def calculate_diagonal(unitary, ua, ub, va, vb, backend=None): def magic_decomposition(unitary, backend=None): """Decomposes an arbitrary unitary to (A1) from arXiv:quant-ph/0011050.""" - backend = _check_backend(backend) + unitary = backend.cast(unitary) psi, eigvals = calculate_psi(unitary, backend=backend) psi_tilde = backend.np.conj(backend.np.sqrt(eigvals)) * backend.np.matmul( @@ -202,10 +199,8 @@ def magic_decomposition(unitary, backend=None): return calculate_diagonal(unitary, ua, ub, va, vb, backend=backend) -def to_bell_diagonal(ud, bell_basis=bell_basis, backend=None): +def to_bell_diagonal(ud, backend, bell_basis=bell_basis): """Transforms a matrix to the Bell basis and checks if it is diagonal.""" - backend = _check_backend(backend) - ud = backend.cast(ud) bell_basis = backend.cast(bell_basis) @@ -213,20 +208,21 @@ def to_bell_diagonal(ud, bell_basis=bell_basis, backend=None): backend.np.transpose(backend.np.conj(bell_basis), (1, 0)) @ ud @ bell_basis ) ud_diag = backend.np.diag(ud_bell) - if not backend.np.allclose(backend.np.diag(ud_diag), ud_bell): # pragma: no cover + if not backend.np.allclose( + backend.np.diag(ud_diag), ud_bell, atol=1e-6, rtol=1e-6 + ): # pragma: no cover return None - uprod = backend.np.prod(ud_diag) - if not np.allclose(backend.to_numpy(uprod), 1): # pragma: no cover + uprod = backend.to_numpy(backend.np.prod(ud_diag)) + if not np.allclose(uprod, 1.0, atol=1e-6, rtol=1e-6): # pragma: no cover return None return ud_diag -def calculate_h_vector(ud_diag, backend=None): +def calculate_h_vector(ud_diag, backend): """Finds h parameters corresponding to exp(-iH). See Eq. (4)-(5) in arXiv:quant-ph/0307177. """ - backend = _check_backend(backend) lambdas = -backend.np.angle(ud_diag) hx = (lambdas[0] + lambdas[2]) / 2.0 hy = (lambdas[1] + lambdas[2]) / 2.0 @@ -234,9 +230,8 @@ def calculate_h_vector(ud_diag, backend=None): return hx, hy, hz -def cnot_decomposition(q0, q1, hx, hy, hz, backend=None): +def cnot_decomposition(q0, q1, hx, hy, hz, backend): """Performs decomposition (6) from arXiv:quant-ph/0307177.""" - backend = _check_backend(backend) h = backend.cast(H) u3 = backend.cast(-1j * matrices.H) # use corrected version from PRA paper (not arXiv) @@ -260,9 +255,8 @@ def cnot_decomposition(q0, q1, hx, hy, hz, backend=None): ] -def cnot_decomposition_light(q0, q1, hx, hy, backend=None): +def cnot_decomposition_light(q0, q1, hx, hy, backend): """Performs decomposition (24) from arXiv:quant-ph/0307177.""" - backend = _check_backend(backend) h = backend.cast(H) w = backend.cast((matrices.I - 1j * matrices.X) / np.sqrt(2)) u2 = gates.RX(0, 2 * hx).matrix(backend) @@ -291,7 +285,8 @@ def two_qubit_decomposition(q0, q1, unitary, backend=None): Returns: (list): gates implementing decomposition (24) from arXiv:quant-ph/0307177 """ - backend = _check_backend(backend) + if backend is None: + backend = _find_backend(unitary) ud_diag = to_bell_diagonal(unitary, backend=backend) ud = None diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index 9032e5e306..1e5bc0758b 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -1,6 +1,7 @@ from enum import Flag, auto from qibo import gates +from qibo.backends import _find_backend from qibo.config import raise_error from qibo.models import Circuit from qibo.transpiler._exceptions import DecompositionError @@ -75,6 +76,7 @@ class Unroller: Args: native_gates (:class:`qibo.transpiler.unroller.NativeGates`): native gates to use in the transpiled circuit. + backend (:class:`qibo.backends.Backend`): backend to use for gate matrix. Returns: (:class:`qibo.models.circuit.Circuit`): equivalent circuit with native gates. @@ -83,8 +85,10 @@ class Unroller: def __init__( self, native_gates: NativeGates, + backend=None, ): self.native_gates = native_gates + self.backend = backend def __call__(self, circuit: Circuit): """Decomposes a circuit into native gates. @@ -101,6 +105,7 @@ def __call__(self, circuit: Circuit): translate_gate( gate, self.native_gates, + backend=self.backend, ) ) return translated_circuit @@ -142,6 +147,7 @@ def assert_decomposition( def translate_gate( gate, native_gates: NativeGates, + backend=None, ): """Maps gates to a hardware-native implementation. @@ -149,10 +155,12 @@ def translate_gate( gate (:class:`qibo.gates.abstract.Gate`): gate to be decomposed. native_gates (:class:`qibo.transpiler.unroller.NativeGates`): native gates to use in the decomposition. + backend (:class:`qibo.backends.Backend`): backend to use for gate matrix. Returns: list: List of native gates that decompose the input gate. """ + if isinstance(gate, (gates.I, gates.Align)): return gate @@ -161,22 +169,27 @@ def translate_gate( gate.basis = [] return gate + if backend is None: + backend = _find_backend(gate.matrix()) + if len(gate.qubits) == 1: - return _translate_single_qubit_gates(gate, native_gates) + return _translate_single_qubit_gates(gate, native_gates, backend) - decomposition_2q = _translate_two_qubit_gates(gate, native_gates) + decomposition_2q = _translate_two_qubit_gates(gate, native_gates, backend) final_decomposition = [] for decomposed_2q_gate in decomposition_2q: if len(decomposed_2q_gate.qubits) == 1: final_decomposition += _translate_single_qubit_gates( - decomposed_2q_gate, native_gates + decomposed_2q_gate, native_gates, backend ) else: final_decomposition.append(decomposed_2q_gate) return final_decomposition -def _translate_single_qubit_gates(gate: gates.Gate, single_qubit_natives: NativeGates): +def _translate_single_qubit_gates( + gate: gates.Gate, single_qubit_natives: NativeGates, backend +): """Helper method for :meth:`translate_gate`. Maps single qubit gates to a hardware-native implementation. @@ -185,20 +198,21 @@ def _translate_single_qubit_gates(gate: gates.Gate, single_qubit_natives: Native gate (:class:`qibo.gates.abstract.Gate`): gate to be decomposed. single_qubit_natives (:class:`qibo.transpiler.unroller.NativeGates`): single qubit native gates. + backend (:class:`qibo.backends.Backend`): backend to use for gate matrix. Returns: list: List of native gates that decompose the input gate. """ if NativeGates.U3 & single_qubit_natives: - return u3_dec(gate) + return u3_dec(gate, backend) if NativeGates.GPI2 & single_qubit_natives: - return gpi2_dec(gate) + return gpi2_dec(gate, backend) raise_error(DecompositionError, "Use U3 or GPI2 as single qubit native gates") -def _translate_two_qubit_gates(gate: gates.Gate, native_gates: NativeGates): +def _translate_two_qubit_gates(gate: gates.Gate, native_gates: NativeGates, backend): """Helper method for :meth:`translate_gate`. Maps two qubit gates to a hardware-native implementation. @@ -207,6 +221,7 @@ def _translate_two_qubit_gates(gate: gates.Gate, native_gates: NativeGates): gate (:class:`qibo.gates.abstract.Gate`): gate to be decomposed. native_gates (:class:`qibo.transpiler.unroller.NativeGates`): native gates supported by the quantum hardware. + backend (:class:`qibo.backends.Backend`): backend to use for gate matrix. Returns: list: List of native gates that decompose the input gate. @@ -216,36 +231,40 @@ def _translate_two_qubit_gates(gate: gates.Gate, native_gates: NativeGates): ) is NativeGates.CZ | NativeGates.iSWAP: # Check for a special optimized decomposition. if gate.__class__ in opt_dec.decompositions: - return opt_dec(gate) + return opt_dec(gate, backend) # Check if the gate has a CZ decomposition if not gate.__class__ in iswap_dec.decompositions: - return cz_dec(gate) + return cz_dec(gate, backend) # Check the decomposition with less 2 qubit gates. - if cz_dec.count_2q(gate) < iswap_dec.count_2q(gate): + if cz_dec.count_2q(gate, backend) < iswap_dec.count_2q(gate, backend): return cz_dec(gate) - if cz_dec.count_2q(gate) > iswap_dec.count_2q(gate): - return iswap_dec(gate) + if cz_dec.count_2q(gate, backend) > iswap_dec.count_2q(gate, backend): + return iswap_dec(gate, backend) # If equal check the decomposition with less 1 qubit gates. # This is never used for now but may be useful for future generalization - if cz_dec.count_1q(gate) < iswap_dec.count_1q(gate): # pragma: no cover - return cz_dec(gate) - return iswap_dec(gate) # pragma: no cover + if cz_dec.count_1q(gate, backend) < iswap_dec.count_1q( + gate, backend + ): # pragma: no cover + return cz_dec(gate, backend) + return iswap_dec(gate, backend) # pragma: no cover if native_gates & NativeGates.CZ: - return cz_dec(gate) + return cz_dec(gate, backend) if native_gates & NativeGates.iSWAP: if gate.__class__ in iswap_dec.decompositions: - return iswap_dec(gate) + return iswap_dec(gate, backend) # First decompose into CZ - cz_decomposed = cz_dec(gate) + cz_decomposed = cz_dec(gate, backend) # Then CZ are decomposed into iSWAP iswap_decomposed = [] for g in cz_decomposed: # Need recursive function as gates.Unitary is not in iswap_dec - for g_translated in translate_gate(g, native_gates=native_gates): + for g_translated in translate_gate( + g, native_gates=native_gates, backend=backend + ): iswap_decomposed.append(g_translated) return iswap_decomposed @@ -253,7 +272,7 @@ def _translate_two_qubit_gates(gate: gates.Gate, native_gates: NativeGates): # No CZ, iSWAP gates in the native gate set # Decompose CNOT, CZ, SWAP gates into CNOT gates if native_gates & NativeGates.CNOT: - return cnot_dec_temp(gate) + return cnot_dec_temp(gate, backend) raise_error( DecompositionError, diff --git a/tests/test_models_encodings.py b/tests/test_models_encodings.py index 1a935d75e3..c8f1dacf14 100644 --- a/tests/test_models_encodings.py +++ b/tests/test_models_encodings.py @@ -135,7 +135,11 @@ def test_unary_encoder(backend, nqubits, architecture, kind): indexes = np.flatnonzero(backend.to_numpy(state)) state = backend.np.real(state[indexes]) - backend.assert_allclose(state, backend.cast(data) / backend.calculate_norm(data, 2)) + backend.assert_allclose( + state, + backend.cast(data, dtype=np.float64) / backend.calculate_norm(data, 2), + rtol=1e-5, + ) @pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) diff --git a/tests/test_models_hep.py b/tests/test_models_hep.py index fdf0a46112..13b3698cbc 100644 --- a/tests/test_models_hep.py +++ b/tests/test_models_hep.py @@ -108,4 +108,11 @@ def test_qpdf(backend, ansatz, layers, nqubits, multi_output, output): np.random.seed(0) params = np.random.rand(model.nparams) result = model.predict(params, [0.1]) - np.testing.assert_allclose(result, output, atol=1e-5) + # Pytorch backend has a different tolerance as is by default working with float32 + if backend.name == "pytorch": + atol = 1e-2 + rtol = 1e-5 + else: + atol = 1e-5 + rtol = 1e-7 + np.testing.assert_allclose(result, output, rtol=rtol, atol=atol) diff --git a/tests/test_models_qft.py b/tests/test_models_qft.py index 90daa4fe9c..1ead4cb865 100644 --- a/tests/test_models_qft.py +++ b/tests/test_models_qft.py @@ -48,10 +48,10 @@ def test_qft_matrix(backend, nqubits): c = models.QFT(nqubits) dim = 2**nqubits target_matrix = qft_matrix(dim) - backend.assert_allclose(c.unitary(backend), target_matrix) + backend.assert_allclose(c.unitary(backend), target_matrix, atol=1e-6, rtol=1e-6) c = c.invert() target_matrix = qft_matrix(dim, inverse=True) - backend.assert_allclose(c.unitary(backend), target_matrix) + backend.assert_allclose(c.unitary(backend), target_matrix, atol=1e-6, rtol=1e-6) @pytest.mark.parametrize("density_matrix", [False, True]) @@ -69,7 +69,7 @@ def test_qft_execution(backend, nqubits, random, density_matrix): final_state = backend.execute_circuit(c, backend.np.copy(initial_state))._state target_state = exact_qft(initial_state, density_matrix, backend) - backend.assert_allclose(final_state, target_state) + backend.assert_allclose(final_state, target_state, atol=1e-6, rtol=1e-6) def test_qft_errors(): diff --git a/tests/test_models_variational.py b/tests/test_models_variational.py index c596990488..f991c24edc 100644 --- a/tests/test_models_variational.py +++ b/tests/test_models_variational.py @@ -125,9 +125,10 @@ def test_vqe(backend, method, options, compile, filename): hamiltonian = hamiltonians.XXZ(nqubits=nqubits, backend=backend) np.random.seed(0) initial_parameters = np.random.uniform(0, 2 * np.pi, 2 * nqubits * layers + nqubits) - initial_parameters = backend.cast( - initial_parameters, dtype=initial_parameters.dtype - ) + if backend.name == "pytorch": + initial_parameters = backend.cast( + initial_parameters, dtype=backend.np.float32, requires_grad=True + ) v = models.VQE(circuit, hamiltonian) loss_values = [] @@ -150,7 +151,7 @@ def callback(parameters, loss_values=loss_values, vqe=v): shutil.rmtree("outcmaes") if filename is not None: assert_regression_fixture(backend, params, filename) - assert best == min(loss_values) + backend.assert_allclose(best, min(loss_values), rtol=1e-6, atol=1e-6) # test energy fluctuation state = backend.np.ones(2**nqubits) / np.sqrt(2**nqubits) @@ -276,7 +277,11 @@ def test_qaoa_optimization(backend, method, options, dense, filename): h = hamiltonians.XXZ(3, dense=dense, backend=backend) qaoa = models.QAOA(h) initial_p = [0.05, 0.06, 0.07, 0.08] - initial_p = backend.cast(initial_p, dtype=np.float64) + if backend.name == "pytorch": + initial_p = backend.cast( + initial_p, dtype=backend.np.float32, requires_grad=True + ) + best, params, _ = qaoa.minimize(initial_p, method=method, options=options) if filename is not None: assert_regression_fixture(backend, params, filename) diff --git a/tests/test_noise.py b/tests/test_noise.py index 325ec95e2d..1c0484acf0 100644 --- a/tests/test_noise.py +++ b/tests/test_noise.py @@ -85,8 +85,8 @@ def test_kraus_error(backend, density_matrix, nshots): @pytest.mark.parametrize("density_matrix", [False, True]) @pytest.mark.parametrize("nshots", [10, 100]) def test_unitary_error(backend, density_matrix, nshots): - u1 = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]) - u2 = np.array([[0, 1, 0, 0], [1, 0, 0, 0], [0, 0, 0, 1], [0, 0, 1, 0]]) + u1 = np.array([[1.0, 0, 0, 0], [0, 1.0, 0, 0], [0, 0, 0, 1.0], [0, 0, 1.0, 0]]) + u2 = np.array([[0, 1.0, 0, 0], [1.0, 0, 0, 0], [0, 0, 0, 1.0], [0, 0, 1.0, 0]]) qubits = (0, 1) p1, p2 = (0.3, 0.7) diff --git a/tests/test_quantum_info_entanglement.py b/tests/test_quantum_info_entanglement.py index a641d01c94..1dbcbd20a5 100644 --- a/tests/test_quantum_info_entanglement.py +++ b/tests/test_quantum_info_entanglement.py @@ -136,11 +136,11 @@ def test_meyer_wallach_entanglement(backend): circuit2.add(gates.CNOT(0, 1)) backend.assert_allclose( - meyer_wallach_entanglement(circuit1, backend=backend), 0.0, atol=PRECISION_TOL + meyer_wallach_entanglement(circuit1, backend=backend), 0.0, atol=1e-6, rtol=1e-6 ) backend.assert_allclose( - meyer_wallach_entanglement(circuit2, backend=backend), 0.5, atol=PRECISION_TOL + meyer_wallach_entanglement(circuit2, backend=backend), 0.5, atol=1e-6, rtol=1e-6 ) diff --git a/tests/test_tomography_gate_set_tomography.py b/tests/test_tomography_gate_set_tomography.py index bbed8cff06..d6d2ffd2df 100644 --- a/tests/test_tomography_gate_set_tomography.py +++ b/tests/test_tomography_gate_set_tomography.py @@ -285,7 +285,7 @@ def test_GST_with_transpiler(backend): Preprocessing(connectivity), Random(connectivity), Sabre(connectivity), - Unroller(NativeGates.default()), + Unroller(NativeGates.default(), backend=backend), ], int_qubit_names=True, ) diff --git a/tests/test_transpiler_decompositions.py b/tests/test_transpiler_decompositions.py index 663f73f5ff..44c299cc3b 100644 --- a/tests/test_transpiler_decompositions.py +++ b/tests/test_transpiler_decompositions.py @@ -21,7 +21,7 @@ def assert_matrices_allclose(gate, natives, backend): target_unitary = target_matrix / normalisation circuit = Circuit(len(gate.qubits)) - circuit.add(translate_gate(gate, natives)) + circuit.add(translate_gate(gate, natives, backend=backend)) native_matrix = circuit.unitary(backend) # Remove global phase from native matrix normalisation = np.power( @@ -34,10 +34,10 @@ def assert_matrices_allclose(gate, natives, backend): # There can still be phase differences of -1, -1j, 1j c = 0 for phase in [1, -1, 1j, -1j]: - if np.allclose( - backend.to_numpy(phase * native_unitary), - backend.to_numpy(target_unitary), - atol=1e-12, + if backend.np.allclose( + phase * native_unitary, + target_unitary, + atol=1e-6, ): c = 1 backend.assert_allclose(c, 1) @@ -229,15 +229,15 @@ def test_unitary_to_native(backend, nqubits, natives_1q, natives_2q, seed): assert_matrices_allclose(gate, natives_1q | natives_2q | default_natives, backend) -def test_count_1q(): +def test_count_1q(backend): from qibo.transpiler.unroller import cz_dec - np.testing.assert_allclose(cz_dec.count_1q(gates.CNOT(0, 1)), 2) - np.testing.assert_allclose(cz_dec.count_1q(gates.CRX(0, 1, 0.1)), 2) + np.testing.assert_allclose(cz_dec.count_1q(gates.CNOT(0, 1), backend), 2) + np.testing.assert_allclose(cz_dec.count_1q(gates.CRX(0, 1, 0.1), backend), 2) -def test_count_2q(): +def test_count_2q(backend): from qibo.transpiler.unroller import cz_dec - np.testing.assert_allclose(cz_dec.count_2q(gates.CNOT(0, 1)), 1) - np.testing.assert_allclose(cz_dec.count_2q(gates.CRX(0, 1, 0.1)), 2) + np.testing.assert_allclose(cz_dec.count_2q(gates.CNOT(0, 1), backend), 1) + np.testing.assert_allclose(cz_dec.count_2q(gates.CRX(0, 1, 0.1), backend), 2) diff --git a/tests/test_transpiler_unitary_decompositions.py b/tests/test_transpiler_unitary_decompositions.py index df641208bb..49f8cf2fe9 100644 --- a/tests/test_transpiler_unitary_decompositions.py +++ b/tests/test_transpiler_unitary_decompositions.py @@ -123,8 +123,8 @@ def test_ud_eigenvalues(backend, seed): @ backend.cast(bell_basis) ) ud_diag = backend.np.diag(ud_bell) - backend.assert_allclose(backend.np.diag(ud_diag), ud_bell, atol=PRECISION_TOL) - backend.assert_allclose(backend.np.prod(ud_diag), 1) + backend.assert_allclose(backend.np.diag(ud_diag), ud_bell, atol=1e-6, rtol=1e-6) + backend.assert_allclose(backend.np.prod(ud_diag), 1, atol=1e-6, rtol=1e-6) @pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) @@ -139,7 +139,7 @@ def test_calculate_h_vector(backend, seed): assert ud_diag is not None hx, hy, hz = calculate_h_vector(ud_diag, backend=backend) target_matrix = bell_unitary(hx, hy, hz, backend) - backend.assert_allclose(ud, target_matrix, atol=PRECISION_TOL) + backend.assert_allclose(ud, target_matrix, atol=1e-6, rtol=1e-6) def test_cnot_decomposition(backend): @@ -148,7 +148,7 @@ def test_cnot_decomposition(backend): c = Circuit(2) c.add(cnot_decomposition(0, 1, hx, hy, hz, backend)) final_matrix = c.unitary(backend) - backend.assert_allclose(final_matrix, target_matrix, atol=PRECISION_TOL) + backend.assert_allclose(final_matrix, target_matrix, atol=1e-6, rtol=1e-6) def test_cnot_decomposition_light(backend): @@ -157,7 +157,7 @@ def test_cnot_decomposition_light(backend): c = Circuit(2) c.add(cnot_decomposition_light(0, 1, hx, hy, backend)) final_matrix = c.unitary(backend) - backend.assert_allclose(final_matrix, target_matrix, atol=PRECISION_TOL) + backend.assert_allclose(final_matrix, target_matrix, atol=1e-6, rtol=1e-6) @pytest.mark.parametrize("seed", [None, 10, np.random.default_rng(10)]) @@ -170,7 +170,7 @@ def test_two_qubit_decomposition(backend, seed): c = Circuit(2) c.add(two_qubit_decomposition(0, 1, unitary, backend=backend)) final_matrix = c.unitary(backend) - backend.assert_allclose(final_matrix, unitary, atol=PRECISION_TOL) + backend.assert_allclose(final_matrix, unitary, atol=1e-6, rtol=1e-6) @pytest.mark.parametrize("gatename", ["CNOT", "CZ", "SWAP", "iSWAP", "fSim", "I"]) @@ -191,7 +191,7 @@ def test_two_qubit_decomposition_common_gates(backend, gatename): c = Circuit(2) c.add(two_qubit_decomposition(0, 1, matrix, backend=backend)) final_matrix = c.unitary(backend) - backend.assert_allclose(final_matrix, matrix, atol=PRECISION_TOL) + backend.assert_allclose(final_matrix, matrix, atol=1e-6, rtol=1e-6) @pytest.mark.parametrize("hz_zero", [False, True]) @@ -203,7 +203,7 @@ def test_two_qubit_decomposition_bell_unitary(backend, hz_zero): c = Circuit(2) c.add(two_qubit_decomposition(0, 1, unitary, backend=backend)) final_matrix = c.unitary(backend) - backend.assert_allclose(final_matrix, unitary, atol=PRECISION_TOL) + backend.assert_allclose(final_matrix, unitary, atol=1e-6, rtol=1e-6) def test_two_qubit_decomposition_no_entanglement(backend): @@ -224,4 +224,4 @@ def test_two_qubit_decomposition_no_entanglement(backend): else: c.add(two_qubit_decomposition(0, 1, matrix, backend=backend)) final_matrix = c.unitary(backend) - backend.assert_allclose(final_matrix, matrix, atol=PRECISION_TOL) + backend.assert_allclose(final_matrix, matrix, atol=1e-6, rtol=1e-6) From 21b08c16fa61f01164f0113fb651654d11258cf7 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Tue, 24 Sep 2024 18:54:17 +0400 Subject: [PATCH 09/34] fixed last tests --- src/qibo/models/error_mitigation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index e85b7cfd73..3a82a5a6e6 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -330,6 +330,7 @@ def _curve_fit( if backend.name == "pytorch": # pytorch has some problems with the `scipy.optim.curve_fit` function # thus we use a `torch.optim` optimizer + params = params.requires_grad_(True) loss = lambda pred, target: backend.np.mean((pred - target) ** 2) optimizer = backend.np.optim.LBFGS( [params], lr=lr, max_iter=max_iter, tolerance_grad=tolerance_grad From cba7a0174c4286facc6688ac3b6fc4c2df40ea2e Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Wed, 25 Sep 2024 13:51:51 +0400 Subject: [PATCH 10/34] fix coverage --- Digraph.gv | 133 ------------------ loss | 58 -------- loss.jpg | Bin 32028 -> 0 bytes src/qibo/backends/__init__.py | 14 -- src/qibo/backends/pytorch.py | 5 +- src/qibo/transpiler/unitary_decompositions.py | 4 +- src/qibo/transpiler/unroller.py | 4 +- 7 files changed, 5 insertions(+), 213 deletions(-) delete mode 100644 Digraph.gv delete mode 100644 loss delete mode 100644 loss.jpg diff --git a/Digraph.gv b/Digraph.gv deleted file mode 100644 index 477f5f7c71..0000000000 --- a/Digraph.gv +++ /dev/null @@ -1,133 +0,0 @@ -digraph { - graph [size="12,12"] - node [align=left fontname=monospace fontsize=10 height=0.2 ranksep=0.1 shape=box style=filled] - 127274956327920 [label=" - ()" fillcolor=darkolivegreen1] - 127274956315360 [label="RsubBackward1 -------------- -alpha: 1"] - 127274956315168 -> 127274956315360 - 127274956315168 -> 127274956327632 [dir=none] - 127274956327632 [label="self - ()" fillcolor=orange] - 127274956315168 [label="AbsBackward0 --------------------- -self: [saved tensor]"] - 127274956314976 -> 127274956315168 - 127274956314976 [label="SumBackward0 --------------------- -self_sym_sizes: (2,)"] - 127274956314880 -> 127274956314976 - 127274956314880 -> 127274956046064 [dir=none] - 127274956046064 [label="self - (2)" fillcolor=orange] - 127274956314880 [label="MulBackward0 ---------------------- -other: None -self : [saved tensor]"] - 127274956315840 -> 127274956314880 - 127274956315840 [label="ViewBackward0 --------------------- -self_sym_sizes: (2,)"] - 127274956315936 -> 127274956315840 - 127274956315936 [label="ViewBackward0 ----------------------- -self_sym_sizes: (2, 1)"] - 127274956316032 -> 127274956315936 - 127274956316032 [label="PermuteBackward0 ----------------- -dims: (1, 0)"] - 127274956316128 -> 127274956316032 - 127274956316128 [label="ViewBackward0 -------------------------- -self_sym_sizes: (1, 1, 2)"] - 127274956316224 -> 127274956316128 - 127274956316224 -> 127274956328976 [dir=none] - 127274956328976 [label="self - (1, 1, 2)" fillcolor=orange] - 127274956316224 [label="BmmBackward0 --------------------- -mat2: None -self: [saved tensor]"] - 127274956316320 -> 127274956316224 - 127274956316320 [label="ReshapeAliasBackward0 ----------------------- -self_sym_sizes: (2, 2)"] - 127274956316416 -> 127274956316320 - 127274956316416 [label="PermuteBackward0 ----------------- -dims: (1, 0)"] - 127274956316512 -> 127274956316416 - 127274956316512 [label="PermuteBackward0 ----------------- -dims: (0, 1)"] - 127274956316608 -> 127274956316512 - 127274956316608 [label="ViewBackward0 ----------------------- -self_sym_sizes: (2, 2)"] - 127274956316704 -> 127274956316608 - 127274956316704 [label="ViewBackward0 --------------------- -self_sym_sizes: (4,)"] - 127274956316800 -> 127274956316704 - 127274956316800 [label="StackBackward0 --------------- -dim: 0"] - 127274956316896 -> 127274956316800 - 127274956316896 [label="AddBackward0 ------------- -alpha: 1"] - 127274956317040 -> 127274956316896 - 127274956317040 -> 127274956045872 [dir=none] - 127274956045872 [label="self - ()" fillcolor=orange] - 127274956317040 [label="CosBackward0 --------------------- -self: [saved tensor]"] - 127274956317136 -> 127274956317040 - 127274956317136 -> 127274956330224 [dir=none] - 127274956330224 [label="other - ()" fillcolor=orange] - 127274956317136 [label="DivBackward0 ---------------------- -other: [saved tensor] -self : None"] - 127274956317232 -> 127274956317136 - 127274956317232 [label="SelectBackward0 --------------------- -dim : 0 -index : 0 -self_sym_sizes: (1,)"] - 127274956317328 -> 127274956317232 - 127275408982384 [label=" - (1)" fillcolor=lightblue] - 127275408982384 -> 127274956317328 - 127274956317328 [label=AccumulateGrad] - 127274956316848 -> 127274956316800 - 127274956316848 -> 127274956330896 [dir=none] - 127274956330896 [label="other - ()" fillcolor=orange] - 127274956316848 [label="MulBackward0 ---------------------- -other: [saved tensor] -self : None"] - 127274956317184 -> 127274956316848 - 127274956317184 -> 127274956045584 [dir=none] - 127274956045584 [label="self - ()" fillcolor=orange] - 127274956317184 [label="SinBackward0 --------------------- -self: [saved tensor]"] - 127274956317472 -> 127274956317184 - 127274956317472 -> 127274956331376 [dir=none] - 127274956331376 [label="other - ()" fillcolor=orange] - 127274956317472 [label="DivBackward0 ---------------------- -other: [saved tensor] -self : None"] - 127274956317232 -> 127274956317472 - 127274956316848 -> 127274956316800 - 127274956316896 -> 127274956316800 - 127274956315360 -> 127274956327920 -} diff --git a/loss b/loss deleted file mode 100644 index 88c25df5a8..0000000000 --- a/loss +++ /dev/null @@ -1,58 +0,0 @@ -digraph { - graph [size="12,12"] - node [align=left fontname=monospace fontsize=10 height=0.2 ranksep=0.1 shape=box style=filled] - 139792478913072 [label=" - ()" fillcolor=darkolivegreen1] - 139792479116192 [label=RsubBackward1] - 139792479117248 -> 139792479116192 - 139792479117248 [label=AbsBackward0] - 139792479117152 -> 139792479117248 - 139792479117152 [label=SumBackward0] - 139792479117344 -> 139792479117152 - 139792479117344 [label=MulBackward0] - 139792479118064 -> 139792479117344 - 139792479118064 [label=ViewBackward0] - 139792479118592 -> 139792479118064 - 139792479118592 [label=ViewBackward0] - 139792479118688 -> 139792479118592 - 139792479118688 [label=PermuteBackward0] - 139792479118784 -> 139792479118688 - 139792479118784 [label=ViewBackward0] - 139792479118880 -> 139792479118784 - 139792479118880 [label=BmmBackward0] - 139792479118976 -> 139792479118880 - 139792479118976 [label=ReshapeAliasBackward0] - 139792479119072 -> 139792479118976 - 139792479119072 [label=PermuteBackward0] - 139792479119168 -> 139792479119072 - 139792479119168 [label=PermuteBackward0] - 139792479119264 -> 139792479119168 - 139792479119264 [label=ViewBackward0] - 139792479119360 -> 139792479119264 - 139792479119360 [label=ViewBackward0] - 139792479119456 -> 139792479119360 - 139792479119456 [label=StackBackward0] - 139792479119552 -> 139792479119456 - 139792479119552 [label=AddBackward0] - 139792479119696 -> 139792479119552 - 139792479119696 [label=CosBackward0] - 139792479119792 -> 139792479119696 - 139792479119792 [label=DivBackward0] - 139792479119888 -> 139792479119792 - 139792479119888 [label=SelectBackward0] - 139792479118400 -> 139792479119888 - 139792931783120 [label=" - (1)" fillcolor=lightblue] - 139792931783120 -> 139792479118400 - 139792479118400 [label=AccumulateGrad] - 139792479119504 -> 139792479119456 - 139792479119504 [label=MulBackward0] - 139792479119840 -> 139792479119504 - 139792479119840 [label=SinBackward0] - 139792479120032 -> 139792479119840 - 139792479120032 [label=DivBackward0] - 139792479119888 -> 139792479120032 - 139792479119504 -> 139792479119456 - 139792479119552 -> 139792479119456 - 139792479116192 -> 139792478913072 -} diff --git a/loss.jpg b/loss.jpg deleted file mode 100644 index d66ff0f0efb71d18b5fcd72b1062390a7e63b886..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 32028 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 diff --git a/src/qibo/backends/__init__.py b/src/qibo/backends/__init__.py index 4f88952b24..77aaab3442 100644 --- a/src/qibo/backends/__init__.py +++ b/src/qibo/backends/__init__.py @@ -198,20 +198,6 @@ def _check_backend(backend): return backend -def _find_backend(x): - """Finds the backend of a given object.""" - if isinstance(x, tuple) or isinstance(x, list): - return _find_backend(x[0]) - if x.__class__.__name__ == "tensor": - return PyTorchBackend() - elif x.__class__.__name__ == "TensorflowTensor": - return TensorflowBackend() - elif isinstance(x, np.ndarray): - return NumpyBackend() - else: - raise TypeError("Unsupported type for backend detection") - - def list_available_backends(*providers: str) -> dict: """Lists all the backends that are available.""" available_backends = MetaBackend().list_available() diff --git a/src/qibo/backends/pytorch.py b/src/qibo/backends/pytorch.py index 319f81d9c2..19095756bb 100644 --- a/src/qibo/backends/pytorch.py +++ b/src/qibo/backends/pytorch.py @@ -128,10 +128,7 @@ def matrix_parametrized(self, gate): gate.init_kwargs[parameter] = self.cast_parameter( gate.init_kwargs[parameter], trainable=gate.trainable ) - elif gate.init_kwargs[parameter].requires_grad: - gate.trainable = True - else: - gate.trainable = False + _matrix = _matrix( qubits_in=gate.init_args[0], qubits_out=gate.init_args[1], diff --git a/src/qibo/transpiler/unitary_decompositions.py b/src/qibo/transpiler/unitary_decompositions.py index 3a9a154122..ef6f0b5951 100644 --- a/src/qibo/transpiler/unitary_decompositions.py +++ b/src/qibo/transpiler/unitary_decompositions.py @@ -1,7 +1,7 @@ import numpy as np from qibo import gates, matrices -from qibo.backends import _find_backend +from qibo.backends import _check_backend from qibo.config import raise_error magic_basis = np.array( @@ -286,7 +286,7 @@ def two_qubit_decomposition(q0, q1, unitary, backend=None): (list): gates implementing decomposition (24) from arXiv:quant-ph/0307177 """ if backend is None: - backend = _find_backend(unitary) + backend = _check_backend(backend) ud_diag = to_bell_diagonal(unitary, backend=backend) ud = None diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index 1e5bc0758b..af1a24b246 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -1,7 +1,7 @@ from enum import Flag, auto from qibo import gates -from qibo.backends import _find_backend +from qibo.backends import _check_backend from qibo.config import raise_error from qibo.models import Circuit from qibo.transpiler._exceptions import DecompositionError @@ -170,7 +170,7 @@ def translate_gate( return gate if backend is None: - backend = _find_backend(gate.matrix()) + backend = _check_backend(backend) if len(gate.qubits) == 1: return _translate_single_qubit_gates(gate, native_gates, backend) From ad25c2cc6b539c533d46357955f454eae9474f9e Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Wed, 25 Sep 2024 15:19:45 +0400 Subject: [PATCH 11/34] improve coverage --- src/qibo/transpiler/unitary_decompositions.py | 6 ++---- src/qibo/transpiler/unroller.py | 5 ++--- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/qibo/transpiler/unitary_decompositions.py b/src/qibo/transpiler/unitary_decompositions.py index ef6f0b5951..74fb7f2e02 100644 --- a/src/qibo/transpiler/unitary_decompositions.py +++ b/src/qibo/transpiler/unitary_decompositions.py @@ -1,7 +1,6 @@ import numpy as np from qibo import gates, matrices -from qibo.backends import _check_backend from qibo.config import raise_error magic_basis = np.array( @@ -274,19 +273,18 @@ def cnot_decomposition_light(q0, q1, hx, hy, backend): ] -def two_qubit_decomposition(q0, q1, unitary, backend=None): +def two_qubit_decomposition(q0, q1, unitary, backend): """Performs two qubit unitary gate decomposition (24) from arXiv:quant-ph/0307177. Args: q0 (int): index of the first qubit. q1 (int): index of the second qubit. unitary (ndarray): Unitary :math:`4 \\times 4` to be decomposed. + backend (:class:`qibo.backends.Backend`): Backend to use for calculations. Returns: (list): gates implementing decomposition (24) from arXiv:quant-ph/0307177 """ - if backend is None: - backend = _check_backend(backend) ud_diag = to_bell_diagonal(unitary, backend=backend) ud = None diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index af1a24b246..d57fde5b56 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -160,6 +160,8 @@ def translate_gate( Returns: list: List of native gates that decompose the input gate. """ + if backend is None: + backend = _check_backend(backend) if isinstance(gate, (gates.I, gates.Align)): return gate @@ -169,9 +171,6 @@ def translate_gate( gate.basis = [] return gate - if backend is None: - backend = _check_backend(backend) - if len(gate.qubits) == 1: return _translate_single_qubit_gates(gate, native_gates, backend) From af6cdf0626b014a1e87bfae52d35f4c19f2ec3c0 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Thu, 26 Sep 2024 09:03:37 +0000 Subject: [PATCH 12/34] Update src/qibo/backends/numpy.py --- src/qibo/backends/numpy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/qibo/backends/numpy.py b/src/qibo/backends/numpy.py index 2347ca2949..df45cde62f 100644 --- a/src/qibo/backends/numpy.py +++ b/src/qibo/backends/numpy.py @@ -429,7 +429,6 @@ def execute_circuit(self, circuit, initial_state=None, nshots=1000): if circuit.density_matrix: if initial_state is None: state = self.zero_density_matrix(nqubits) - else: state = self.cast(initial_state) From 53931be61f8ccde1bbfffe150cffd19d502fac1f Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Thu, 26 Sep 2024 09:03:49 +0000 Subject: [PATCH 13/34] Update src/qibo/backends/numpy.py --- src/qibo/backends/numpy.py | 1 - 1 file changed, 1 deletion(-) diff --git a/src/qibo/backends/numpy.py b/src/qibo/backends/numpy.py index df45cde62f..ec0f89bf2b 100644 --- a/src/qibo/backends/numpy.py +++ b/src/qibo/backends/numpy.py @@ -438,7 +438,6 @@ def execute_circuit(self, circuit, initial_state=None, nshots=1000): else: if initial_state is None: state = self.zero_state(nqubits) - else: state = self.cast(initial_state) From 502f0a2d6209db375c2080f68da6889930b8f597 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Thu, 26 Sep 2024 09:15:50 +0000 Subject: [PATCH 14/34] Update src/qibo/gates/gates.py --- src/qibo/gates/gates.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibo/gates/gates.py b/src/qibo/gates/gates.py index 92bc569beb..7f2836e5e5 100644 --- a/src/qibo/gates/gates.py +++ b/src/qibo/gates/gates.py @@ -507,7 +507,7 @@ class Align(ParametrizedGate): """Aligns proceeding qubit operations and (optionally) waits ``delay`` amount of time. .. note:: - For this gate, the trainable parameter is by default set to ``False``. + For this gate, the ``trainable`` parameter is by default set to ``False``. Args: q (int): The qubit ID. From 3fdf63ba8bcff8abd76bb8386796374c5b52d58d Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Thu, 26 Sep 2024 09:19:22 +0000 Subject: [PATCH 15/34] Update src/qibo/gates/gates.py --- src/qibo/gates/gates.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/qibo/gates/gates.py b/src/qibo/gates/gates.py index 7f2836e5e5..4faf30fd10 100644 --- a/src/qibo/gates/gates.py +++ b/src/qibo/gates/gates.py @@ -2605,7 +2605,8 @@ def parameters(self, x): shape = self.parameters[0].shape engine = _check_engine(x) # Reshape doesn't accept a tuple if engine is pytorch. - x = x[0] if type(x) is tuple else x + if isinstance(x, tuple): + x = x[0] self._parameters = (engine.reshape(x, shape),) for gate in self.device_gates: # pragma: no cover gate.parameters = x From 2f80f990539919fb2e51fb0354fc892794e01788 Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Thu, 26 Sep 2024 13:30:39 +0400 Subject: [PATCH 16/34] improve docstring --- src/qibo/transpiler/decompositions.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/qibo/transpiler/decompositions.py b/src/qibo/transpiler/decompositions.py index 5a0cecb0c5..b1ce5de445 100644 --- a/src/qibo/transpiler/decompositions.py +++ b/src/qibo/transpiler/decompositions.py @@ -45,15 +45,17 @@ def __call__(self, gate, backend=None): def _u3_to_gpi2(t, p, l): - """Decompose a U3 gate into GPI2 gates, the decomposition is optimized to use the minimum number of gates.. + """Decompose a :class:`qibo.gates.U3` gate into :class:`qibo.gates.GPI2` gates. + + The decomposition is optimized to use the minimum number of gates. Args: - t (float): theta parameter of U3 gate. - p (float): phi parameter of U3 gate. - l (float): lambda parameter of U3 gate. + t (float): first parameter of :class:`qibo.gates.U3` gate. + p (float): second parameter of :class:`qibo.gates.U3` gate. + l (float): third parameter of :class:`qibo.gates.U3` gate. Returns: - decomposition (list): list of native gates that decompose the U3 gate. + list: Native gates that decompose the :class:`qibo.gates.U3` gate. """ decomposition = [] if l != 0.0: From 5577d51d882f77832ae866c48219f184dcacc7cb Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Mon, 30 Sep 2024 13:19:35 +0400 Subject: [PATCH 17/34] some corrections by renato --- src/qibo/backends/pytorch.py | 15 +++++++-------- src/qibo/gates/gates.py | 2 +- src/qibo/models/circuit.py | 13 ++++++------- src/qibo/transpiler/unroller.py | 3 +-- 4 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/qibo/backends/pytorch.py b/src/qibo/backends/pytorch.py index 19095756bb..f79b0006ca 100644 --- a/src/qibo/backends/pytorch.py +++ b/src/qibo/backends/pytorch.py @@ -136,7 +136,7 @@ def matrix_parametrized(self, gate): phi=gate.init_kwargs["phi"], ) return _matrix - if not self.check_parameters(gate.parameters): + else: new_parameters = [] for parameter in gate.parameters: if not isinstance(parameter, self.np.Tensor): @@ -148,14 +148,13 @@ def matrix_parametrized(self, gate): _matrix = _matrix(*gate.parameters) return _matrix - def check_parameters(self, parameters): - """Check if the parameters are torch tensors.""" - for parameter in parameters: - if not isinstance(parameter, self.np.Tensor): - return False - return True - def cast_parameter(self, x, trainable): + """Cast a gate parameter to a torch tensor. + + Args: + x (Union[int, float, complex]): Parameter to be casted. + trainable (bool): If ``True``, the tensor requires gradient. + """ if isinstance(x, int) and trainable: return self.np.tensor(x, dtype=self.np.float64, requires_grad=True) return self.np.tensor(x, requires_grad=trainable) diff --git a/src/qibo/gates/gates.py b/src/qibo/gates/gates.py index 4faf30fd10..618279c897 100644 --- a/src/qibo/gates/gates.py +++ b/src/qibo/gates/gates.py @@ -2638,7 +2638,7 @@ def _dagger(self): def _check_engine(array): """Check if the array is a numpy or torch tensor and return the corresponding library.""" if (array.__class__.__name__ == "Tensor") or ( - type(array) is tuple and array[0].__class__.__name__ == "Tensor" + isinstance(array, tuple) and array[0].__class__.__name__ == "Tensor" ): import torch # pylint: disable=C0415 diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index 12ce3227e5..b369905571 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -1143,7 +1143,7 @@ def to_qasm(self): """Convert circuit to QASM. .. note:: - This method doesn't support multi-controlled gates and gates with torch Tensors parameters. + This method does not support multi-controlled gates and gates with ``torch.Tensor`` as parameters. Args: filename (str): The filename where the code is saved. @@ -1177,12 +1177,11 @@ def to_qasm(self): qubits = ",".join(f"q[{i}]" for i in gate.qubits) if isinstance(gate, gates.ParametrizedGate): - for x in gate.parameters: - if x.__class__.__name__ == "Tensor": - raise_error( - ValueError, - "OpenQASM does not support gates with torch Tensors parameters.", - ) + if any(x.__class__.__name__ == "Tensor" for x in gate.parameters): + raise_error( + ValueError, + "OpenQASM does not support gates with torch Tensors parameters.", + ) params = (str(x) for x in gate.parameters) name = f"{gate.qasm_label}({', '.join(params)})" else: diff --git a/src/qibo/transpiler/unroller.py b/src/qibo/transpiler/unroller.py index d57fde5b56..d3b564884b 100644 --- a/src/qibo/transpiler/unroller.py +++ b/src/qibo/transpiler/unroller.py @@ -160,8 +160,7 @@ def translate_gate( Returns: list: List of native gates that decompose the input gate. """ - if backend is None: - backend = _check_backend(backend) + backend = _check_backend(backend) if isinstance(gate, (gates.I, gates.Align)): return gate From b77723c0518a44c5b595eebae8154564f856aa1f Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Mon, 30 Sep 2024 16:12:13 +0400 Subject: [PATCH 18/34] corrections by renato --- src/qibo/backends/numpy.py | 3 +- src/qibo/backends/pytorch.py | 10 ++- src/qibo/transpiler/decompositions.py | 94 ++++++++++++++++----------- 3 files changed, 64 insertions(+), 43 deletions(-) diff --git a/src/qibo/backends/numpy.py b/src/qibo/backends/numpy.py index ec0f89bf2b..6bfcfd052a 100644 --- a/src/qibo/backends/numpy.py +++ b/src/qibo/backends/numpy.py @@ -398,8 +398,7 @@ def execute_circuit(self, circuit, initial_state=None, nshots=1000): else: return self.execute_circuit(initial_state + circuit, None, nshots) elif initial_state is not None: - if initial_state.dtype != self.dtype: - initial_state = self.cast(initial_state) + initial_state = self.cast(initial_state) valid_shape = ( 2 * (2**circuit.nqubits,) if circuit.density_matrix diff --git a/src/qibo/backends/pytorch.py b/src/qibo/backends/pytorch.py index f79b0006ca..fc5e4c5419 100644 --- a/src/qibo/backends/pytorch.py +++ b/src/qibo/backends/pytorch.py @@ -46,6 +46,8 @@ def __init__(self): # Default data type used for the gate matrices is complex128 self.dtype = self._torch_dtype(self.dtype) + # Default data type used for the real gate parameters is float64 + self.parameter_dtype = self._torch_dtype("float64") self.matrices = TorchMatrices(self.dtype) self.device = self.np.device("cuda:0" if torch.cuda.is_available() else "cpu") self.nthreads = 0 @@ -156,8 +158,12 @@ def cast_parameter(self, x, trainable): trainable (bool): If ``True``, the tensor requires gradient. """ if isinstance(x, int) and trainable: - return self.np.tensor(x, dtype=self.np.float64, requires_grad=True) - return self.np.tensor(x, requires_grad=trainable) + return self.np.tensor(x, dtype=self.parameter_dtype, requires_grad=True) + if isinstance(x, float): + return self.np.tensor( + x, dtype=self.parameter_dtype, requires_grad=trainable + ) + return self.np.tensor(x, dtype=self.dtype, requires_grad=trainable) def is_sparse(self, x): if isinstance(x, self.np.Tensor): diff --git a/src/qibo/transpiler/decompositions.py b/src/qibo/transpiler/decompositions.py index b1ce5de445..8d8fe8711f 100644 --- a/src/qibo/transpiler/decompositions.py +++ b/src/qibo/transpiler/decompositions.py @@ -20,7 +20,13 @@ def add(self, gate, decomposition): def count_2q(self, gate, backend): """Count the number of two-qubit gates in the decomposition of the given gate.""" if gate.parameters: - decomposition = self.decompositions[gate.__class__](gate, backend) + if isinstance( + gate, + (gates.FusedGate, gates.Unitary, gates.GeneralizedfSim, gates.fSim), + ): + decomposition = self.decompositions[gate.__class__](gate, backend) + else: + decomposition = self.decompositions[gate.__class__](gate) else: decomposition = self.decompositions[gate.__class__] return len(tuple(g for g in decomposition if len(g.qubits) > 1)) @@ -28,7 +34,13 @@ def count_2q(self, gate, backend): def count_1q(self, gate, backend): """Count the number of single qubit gates in the decomposition of the given gate.""" if gate.parameters: - decomposition = self.decompositions[gate.__class__](gate, backend) + if isinstance( + gate, + (gates.FusedGate, gates.Unitary, gates.GeneralizedfSim, gates.fSim), + ): + decomposition = self.decompositions[gate.__class__](gate, backend) + else: + decomposition = self.decompositions[gate.__class__](gate) else: decomposition = self.decompositions[gate.__class__] return len(tuple(g for g in decomposition if len(g.qubits) == 1)) @@ -37,7 +49,13 @@ def __call__(self, gate, backend=None): """Decompose a gate.""" decomposition = self.decompositions[gate.__class__] if callable(decomposition): - decomposition = decomposition(gate, backend) + if isinstance( + gate, + (gates.FusedGate, gates.Unitary, gates.GeneralizedfSim, gates.fSim), + ): + decomposition = decomposition(gate, backend) + else: + decomposition = decomposition(gate) return [ g.on_qubits({i: q for i, q in enumerate(gate.qubits)}) for g in decomposition @@ -82,7 +100,7 @@ def _u3_to_gpi2(t, p, l): gpi2_dec.add(gates.SX, [gates.GPI2(0, 0)]) gpi2_dec.add( gates.RX, - lambda gate, backend: [ + lambda gate: [ gates.Z(0), gates.GPI2(0, np.pi / 2), gates.RZ(0, gate.parameters[0] + np.pi), @@ -91,23 +109,21 @@ def _u3_to_gpi2(t, p, l): ) gpi2_dec.add( gates.RY, - lambda gate, backend: [ + lambda gate: [ gates.GPI2(0, 0), gates.RZ(0, gate.parameters[0] + np.pi), gates.GPI2(0, 0), gates.Z(0), ], ) -gpi2_dec.add(gates.RZ, lambda gate, backend: [gates.RZ(0, gate.parameters[0])]) -gpi2_dec.add(gates.GPI2, lambda gate, backend: [gates.GPI2(0, gate.parameters[0])]) -gpi2_dec.add(gates.U1, lambda gate, backend: [gates.RZ(0, gate.parameters[0])]) +gpi2_dec.add(gates.RZ, lambda gate: [gates.RZ(0, gate.parameters[0])]) +gpi2_dec.add(gates.GPI2, lambda gate: [gates.GPI2(0, gate.parameters[0])]) +gpi2_dec.add(gates.U1, lambda gate: [gates.RZ(0, gate.parameters[0])]) gpi2_dec.add( gates.U2, - lambda gate, backend: _u3_to_gpi2( - np.pi / 2, gate.parameters[0], gate.parameters[1] - ), + lambda gate: _u3_to_gpi2(np.pi / 2, gate.parameters[0], gate.parameters[1]), ) -gpi2_dec.add(gates.U3, lambda gate, backend: _u3_to_gpi2(*gate.parameters)) +gpi2_dec.add(gates.U3, lambda gate: _u3_to_gpi2(*gate.parameters)) gpi2_dec.add( gates.Unitary, lambda gate, backend: _u3_to_gpi2(*u3_decomposition(gate.parameters[0], backend)), @@ -130,13 +146,13 @@ def _u3_to_gpi2(t, p, l): u3_dec.add(gates.SX, [gates.U3(0, np.pi / 2, -np.pi / 2, np.pi / 2)]) u3_dec.add( gates.RX, - lambda gate, backend: [gates.U3(0, gate.parameters[0], -np.pi / 2, np.pi / 2)], + lambda gate: [gates.U3(0, gate.parameters[0], -np.pi / 2, np.pi / 2)], ) -u3_dec.add(gates.RY, lambda gate, backend: [gates.U3(0, gate.parameters[0], 0, 0)]) -u3_dec.add(gates.RZ, lambda gate, backend: [gates.RZ(0, gate.parameters[0])]) +u3_dec.add(gates.RY, lambda gate: [gates.U3(0, gate.parameters[0], 0, 0)]) +u3_dec.add(gates.RZ, lambda gate: [gates.RZ(0, gate.parameters[0])]) u3_dec.add( gates.PRX, - lambda gate, backend: [ + lambda gate: [ gates.RZ(0, gate.parameters[1] - np.pi / 2), gates.RY(0, -gate.parameters[0]), gates.RZ(0, gate.parameters[1] + np.pi / 2), @@ -144,20 +160,20 @@ def _u3_to_gpi2(t, p, l): ) u3_dec.add( gates.GPI2, - lambda gate, backend: [ - gates.U3(0, *u3_decomposition(gate.matrix(backend), backend)) + lambda gate: [ + gates.U3( + 0, np.pi / 2, gate.parameters[0] - np.pi / 2, np.pi / 2 - gate.parameters[0] + ), ], ) -u3_dec.add(gates.U1, lambda gate, backend: [gates.RZ(0, gate.parameters[0])]) +u3_dec.add(gates.U1, lambda gate: [gates.RZ(0, gate.parameters[0])]) u3_dec.add( gates.U2, - lambda gate, backend: [ - gates.U3(0, np.pi / 2, gate.parameters[0], gate.parameters[1]) - ], + lambda gate: [gates.U3(0, np.pi / 2, gate.parameters[0], gate.parameters[1])], ) u3_dec.add( gates.U3, - lambda gate, backend: [ + lambda gate: [ gates.U3(0, gate.parameters[0], gate.parameters[1], gate.parameters[2]) ], ) @@ -248,7 +264,7 @@ def _u3_to_gpi2(t, p, l): ) cz_dec.add( gates.CRX, - lambda gate, backend: [ + lambda gate: [ gates.RX(1, gate.parameters[0] / 2.0), gates.CZ(0, 1), gates.RX(1, -gate.parameters[0] / 2.0), @@ -257,7 +273,7 @@ def _u3_to_gpi2(t, p, l): ) cz_dec.add( gates.CRY, - lambda gate, backend: [ + lambda gate: [ gates.RY(1, gate.parameters[0] / 2.0), gates.CZ(0, 1), gates.RY(1, -gate.parameters[0] / 2.0), @@ -266,7 +282,7 @@ def _u3_to_gpi2(t, p, l): ) cz_dec.add( gates.CRZ, - lambda gate, backend: [ + lambda gate: [ gates.RZ(1, gate.parameters[0] / 2.0), gates.H(1), gates.CZ(0, 1), @@ -277,7 +293,7 @@ def _u3_to_gpi2(t, p, l): ) cz_dec.add( gates.CU1, - lambda gate, backend: [ + lambda gate: [ gates.RZ(0, gate.parameters[0] / 2.0), gates.H(1), gates.CZ(0, 1), @@ -289,7 +305,7 @@ def _u3_to_gpi2(t, p, l): ) cz_dec.add( gates.CU2, - lambda gate, backend: [ + lambda gate: [ gates.RZ(1, (gate.parameters[1] - gate.parameters[0]) / 2.0), gates.H(1), gates.CZ(0, 1), @@ -303,7 +319,7 @@ def _u3_to_gpi2(t, p, l): ) cz_dec.add( gates.CU3, - lambda gate, backend: [ + lambda gate: [ gates.RZ(1, (gate.parameters[2] - gate.parameters[1]) / 2.0), gates.H(1), gates.CZ(0, 1), @@ -335,7 +351,7 @@ def _u3_to_gpi2(t, p, l): ) cz_dec.add( gates.RXX, - lambda gate, backend: [ + lambda gate: [ gates.H(0), gates.CZ(0, 1), gates.RX(1, gate.parameters[0]), @@ -345,7 +361,7 @@ def _u3_to_gpi2(t, p, l): ) cz_dec.add( gates.RYY, - lambda gate, backend: [ + lambda gate: [ gates.RX(0, np.pi / 2), gates.U3(1, np.pi / 2, np.pi / 2, np.pi), gates.CZ(0, 1), @@ -357,7 +373,7 @@ def _u3_to_gpi2(t, p, l): ) cz_dec.add( gates.RZZ, - lambda gate, backend: [ + lambda gate: [ gates.H(1), gates.CZ(0, 1), gates.RX(1, gate.parameters[0]), @@ -463,7 +479,7 @@ def _decomposition_generalized_RBS(ins, outs, theta, phi, controls): standard_decompositions.add(gates.SXDG, [gates.RX(0, -np.pi / 2, trainable=False)]) standard_decompositions.add( gates.PRX, - lambda gate, backend: [ + lambda gate: [ gates.RZ(0, -gate.parameters[1] - np.pi / 2), gates.RY(0, -gate.parameters[0]), gates.RZ(0, gate.parameters[1] + np.pi / 2), @@ -471,7 +487,7 @@ def _decomposition_generalized_RBS(ins, outs, theta, phi, controls): ) standard_decompositions.add( gates.U3, - lambda gate, backend: [ + lambda gate: [ gates.RZ(0, gate.parameters[2]), gates.SX(0), gates.RZ(0, gate.parameters[0] + np.pi), @@ -489,7 +505,7 @@ def _decomposition_generalized_RBS(ins, outs, theta, phi, controls): ) standard_decompositions.add( gates.RZX, - lambda gate, backend: [ + lambda gate: [ gates.H(1), gates.CNOT(0, 1), gates.RZ(1, gate.parameters[0]), @@ -499,7 +515,7 @@ def _decomposition_generalized_RBS(ins, outs, theta, phi, controls): ) standard_decompositions.add( gates.RXXYY, - lambda gate, backend: [ + lambda gate: [ gates.RZ(1, -np.pi / 2), gates.S(0), gates.SX(1), @@ -516,7 +532,7 @@ def _decomposition_generalized_RBS(ins, outs, theta, phi, controls): ) standard_decompositions.add( gates.RBS, - lambda gate, backend: [ + lambda gate: [ gates.H(0), gates.CNOT(0, 1), gates.H(1), @@ -528,7 +544,7 @@ def _decomposition_generalized_RBS(ins, outs, theta, phi, controls): ], ) standard_decompositions.add( - gates.GIVENS, lambda gate, backend: gates.RBS(0, 1, -gate.parameters[0]).decompose() + gates.GIVENS, lambda gate: gates.RBS(0, 1, -gate.parameters[0]).decompose() ) standard_decompositions.add( gates.FSWAP, [gates.X(1)] + gates.GIVENS(0, 1, np.pi / 2).decompose() + [gates.X(0)] @@ -559,7 +575,7 @@ def _decomposition_generalized_RBS(ins, outs, theta, phi, controls): ) standard_decompositions.add( gates.GeneralizedRBS, - lambda gate, backend: _decomposition_generalized_RBS( + lambda gate: _decomposition_generalized_RBS( ins=list(range(len(gate.init_args[0]))), outs=list( range( From e3d96f356a6478c66394cf3f8a5d994c3cf6326c Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Mon, 30 Sep 2024 17:00:51 +0400 Subject: [PATCH 19/34] added test with gradients --- tests/test_torch_gradients.py | 48 +++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/tests/test_torch_gradients.py b/tests/test_torch_gradients.py index e3faa7a938..3872680e8d 100644 --- a/tests/test_torch_gradients.py +++ b/tests/test_torch_gradients.py @@ -1,13 +1,15 @@ +import numpy as np import pytest +import tensorflow as tf import torch import qibo from qibo import gates, models -def tetst_torch_gradients(): +def test_torch_gradients(): qibo.set_backend("pytorch") - torch.seed(42) + torch.manual_seed(42) nepochs = 1001 optimizer = torch.optim.Adam target_state = torch.rand(2, dtype=torch.complex128) @@ -32,3 +34,45 @@ def tetst_torch_gradients(): assert initial_loss > loss assert initial_params[0] != params[0] + + +def test_torch_tensorflow_gradients(): + qibo.set_backend("pytorch") + target_state = torch.tensor([0.0, 1.0], dtype=torch.complex128) + param = torch.tensor([0.1], dtype=torch.float64, requires_grad=True) + c = models.Circuit(1) + c.add(gates.RX(0, param[0])) + + optimizer = torch.optim.SGD + optimizer = optimizer([param], lr=1) + c.set_parameters(param) + final_state = c().state() + fidelity = torch.abs(torch.sum(torch.conj(target_state) * final_state)) + loss = 1 - fidelity + loss.backward() + torch_param_grad = param.grad.clone().item() + optimizer.step() + torch_param = param.clone().item() + + qibo.set_backend("tensorflow") + + target_state = tf.constant([0.0, 1.0], dtype=tf.complex128) + param = tf.Variable([0.1], dtype=tf.float64) + c = models.Circuit(1) + c.add(gates.RX(0, param[0])) + + optimizer = tf.optimizers.SGD(learning_rate=1.0) + + with tf.GradientTape() as tape: + c.set_parameters(param) + final_state = c().state() + fidelity = tf.abs(tf.reduce_sum(tf.math.conj(target_state) * final_state)) + loss = 1 - fidelity + + grads = tape.gradient(loss, [param]) + tf_param_grad = grads[0].numpy()[0] + optimizer.apply_gradients(zip(grads, [param])) + tf_param = param.numpy()[0] + + assert np.allclose(torch_param_grad, tf_param_grad, atol=1e-7, rtol=1e-7) + assert np.allclose(torch_param, tf_param, atol=1e-7, rtol=1e-7) From 0a76d557da911a2d3472d519656a8ff7efaffde5 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Mon, 30 Sep 2024 17:27:02 +0400 Subject: [PATCH 20/34] solve errors --- tests/test_torch_gradients.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/test_torch_gradients.py b/tests/test_torch_gradients.py index 3872680e8d..d6a11e9923 100644 --- a/tests/test_torch_gradients.py +++ b/tests/test_torch_gradients.py @@ -1,14 +1,14 @@ import numpy as np import pytest -import tensorflow as tf -import torch import qibo from qibo import gates, models +from qibo.backends import PyTorchBackend, TensorflowBackend def test_torch_gradients(): qibo.set_backend("pytorch") + torch = PyTorchBackend().np torch.manual_seed(42) nepochs = 1001 optimizer = torch.optim.Adam @@ -38,6 +38,7 @@ def test_torch_gradients(): def test_torch_tensorflow_gradients(): qibo.set_backend("pytorch") + torch = PyTorchBackend().np target_state = torch.tensor([0.0, 1.0], dtype=torch.complex128) param = torch.tensor([0.1], dtype=torch.float64, requires_grad=True) c = models.Circuit(1) @@ -55,6 +56,7 @@ def test_torch_tensorflow_gradients(): torch_param = param.clone().item() qibo.set_backend("tensorflow") + tf = TensorflowBackend().tf target_state = tf.constant([0.0, 1.0], dtype=tf.complex128) param = tf.Variable([0.1], dtype=tf.float64) From 4cf7cdc730484323b767dc2a571f5a11caa953cc Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Tue, 1 Oct 2024 13:58:53 +0400 Subject: [PATCH 21/34] corrections by andrea --- src/qibo/backends/npmatrices.py | 12 +++-- src/qibo/models/circuit.py | 8 ++- tests/test_models_circuit_qasm_cirq.py | 68 ++++++++++++-------------- 3 files changed, 41 insertions(+), 47 deletions(-) diff --git a/src/qibo/backends/npmatrices.py b/src/qibo/backends/npmatrices.py index 1ec6c9aefd..723578bd0b 100644 --- a/src/qibo/backends/npmatrices.py +++ b/src/qibo/backends/npmatrices.py @@ -160,24 +160,28 @@ def CZ(self): @cached_property def CSX(self): + a = (1 + 1j) / 2 + b = (1 - 1j) / 2 return self._cast( [ [1 + 0j, 0, 0, 0], [0, 1 + 0j, 0, 0], - [0, 0, (1 + 1j) / 2, (1 - 1j) / 2], - [0, 0, (1 - 1j) / 2, (1 + 1j) / 2], + [0, 0, a, b], + [0, 0, b, a], ], dtype=self.dtype, ) @cached_property def CSXDG(self): + a = (1 + 1j) / 2 + b = (1 - 1j) / 2 return self._cast( [ [1 + 0j, 0, 0, 0], [0, 1 + 0j, 0, 0], - [0, 0, (1 - 1j) / 2, (1 + 1j) / 2], - [0, 0, (1 + 1j) / 2, (1 - 1j) / 2], + [0, 0, b, a], + [0, 0, a, b], ], dtype=self.dtype, ) diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index 91aaf84c8d..a7c264e1ac 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -1178,11 +1178,9 @@ def to_qasm(self): qubits = ",".join(f"q[{i}]" for i in gate.qubits) if isinstance(gate, gates.ParametrizedGate): if any(x.__class__.__name__ == "Tensor" for x in gate.parameters): - raise_error( - ValueError, - "OpenQASM does not support gates with torch Tensors parameters.", - ) - params = (str(x) for x in gate.parameters) + params = (str(x.detach().item()) for x in gate.parameters) + else: + params = (str(x) for x in gate.parameters) name = f"{gate.qasm_label}({', '.join(params)})" else: name = gate.qasm_label diff --git a/tests/test_models_circuit_qasm_cirq.py b/tests/test_models_circuit_qasm_cirq.py index 34c5c54a44..f0e683e88b 100644 --- a/tests/test_models_circuit_qasm_cirq.py +++ b/tests/test_models_circuit_qasm_cirq.py @@ -133,21 +133,17 @@ def test_parametrized_gate_cirq(backend): c1.add(gates.RY(1, 0.1234)) final_state_c1 = backend.execute_circuit(c1).state() - if backend.name == "pytorch": - with pytest.raises(ValueError): - c2 = circuit_from_qasm(c1.to_qasm()) - else: - c2 = circuit_from_qasm(c1.to_qasm()) - c2depth = len(cirq.Circuit(c2.all_operations())) - assert c1.depth == c2depth - final_state_c2 = ( - cirq.Simulator().simulate(c2).final_state_vector - ) # pylint: disable=no-member - backend.assert_allclose(final_state_c1, final_state_c2, atol=_atol) + c2 = circuit_from_qasm(c1.to_qasm()) + c2depth = len(cirq.Circuit(c2.all_operations())) + assert c1.depth == c2depth + final_state_c2 = ( + cirq.Simulator().simulate(c2).final_state_vector + ) # pylint: disable=no-member + backend.assert_allclose(final_state_c1, final_state_c2, atol=_atol) - c3 = Circuit.from_qasm(c2.to_qasm()) - final_state_c3 = backend.execute_circuit(c3).state() - backend.assert_allclose(final_state_c3, final_state_c2, atol=_atol) + c3 = Circuit.from_qasm(c2.to_qasm()) + final_state_c3 = backend.execute_circuit(c3).state() + backend.assert_allclose(final_state_c3, final_state_c2, atol=_atol) def test_cu1_cirq(): @@ -167,31 +163,27 @@ def test_ugates_cirq(backend): c1.add(gates.U2(2, 0.5, 0.6)) final_state_c1 = backend.execute_circuit(c1).state() - if backend.name == "pytorch": - with pytest.raises(ValueError): - c2 = circuit_from_qasm(c1.to_qasm()) - else: + c2 = circuit_from_qasm(c1.to_qasm()) + c2depth = len(cirq.Circuit(c2.all_operations())) + assert c1.depth == c2depth + final_state_c2 = ( + cirq.Simulator().simulate(c2).final_state_vector + ) # pylint: disable=no-member + backend.assert_allclose(final_state_c1, final_state_c2, atol=_atol) + + c3 = Circuit.from_qasm(c2.to_qasm()) + assert c3.depth == c2depth + final_state_c3 = backend.execute_circuit(c3).state() + backend.assert_allclose(final_state_c3, final_state_c2, atol=_atol) + + c1 = Circuit(3) + c1.add(gates.RX(0, 0.1)) + c1.add(gates.RZ(1, 0.4)) + c1.add(gates.U2(2, 0.5, 0.6)) + c1.add(gates.CU3(2, 1, 0.2, 0.3, 0.4)) + # catches unknown gate "cu3" + with pytest.raises(exception.QasmException): c2 = circuit_from_qasm(c1.to_qasm()) - c2depth = len(cirq.Circuit(c2.all_operations())) - assert c1.depth == c2depth - final_state_c2 = ( - cirq.Simulator().simulate(c2).final_state_vector - ) # pylint: disable=no-member - backend.assert_allclose(final_state_c1, final_state_c2, atol=_atol) - - c3 = Circuit.from_qasm(c2.to_qasm()) - assert c3.depth == c2depth - final_state_c3 = backend.execute_circuit(c3).state() - backend.assert_allclose(final_state_c3, final_state_c2, atol=_atol) - - c1 = Circuit(3) - c1.add(gates.RX(0, 0.1)) - c1.add(gates.RZ(1, 0.4)) - c1.add(gates.U2(2, 0.5, 0.6)) - c1.add(gates.CU3(2, 1, 0.2, 0.3, 0.4)) - # catches unknown gate "cu3" - with pytest.raises(exception.QasmException): - c2 = circuit_from_qasm(c1.to_qasm()) def test_crotations_cirq(): From e57a0b1458f6003d93408fbb1e973e8815f55c9e Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Tue, 1 Oct 2024 14:24:18 +0400 Subject: [PATCH 22/34] example of Andrea's suggestion --- src/qibo/transpiler/decompositions.py | 27 +++++++++++++++++---------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/qibo/transpiler/decompositions.py b/src/qibo/transpiler/decompositions.py index 8d8fe8711f..4969fd6bb2 100644 --- a/src/qibo/transpiler/decompositions.py +++ b/src/qibo/transpiler/decompositions.py @@ -17,18 +17,25 @@ def add(self, gate, decomposition): """Register a decomposition for a gate.""" self.decompositions[gate] = decomposition + def _check_instance(self, gate, backend=None): + special_gates = ( + gates.FusedGate, + gates.Unitary, + gates.GeneralizedfSim, + gates.fSim, + ) + decomposition = self.decompositions[gate.__class__] + if gate.parameters: + decomposition = ( + decomposition(gate, backend) + if isinstance(gate, special_gates) + else decomposition(gate) + ) + return decomposition + def count_2q(self, gate, backend): """Count the number of two-qubit gates in the decomposition of the given gate.""" - if gate.parameters: - if isinstance( - gate, - (gates.FusedGate, gates.Unitary, gates.GeneralizedfSim, gates.fSim), - ): - decomposition = self.decompositions[gate.__class__](gate, backend) - else: - decomposition = self.decompositions[gate.__class__](gate) - else: - decomposition = self.decompositions[gate.__class__] + decomposition = self._check_instance(gate, backend) return len(tuple(g for g in decomposition if len(g.qubits) > 1)) def count_1q(self, gate, backend): From 2c871d0b389de091a9321090f4b35cd81e8594d3 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Tue, 1 Oct 2024 14:36:40 +0400 Subject: [PATCH 23/34] other corrections --- src/qibo/models/error_mitigation.py | 5 ++++- src/qibo/transpiler/decompositions.py | 21 ++------------------- 2 files changed, 6 insertions(+), 20 deletions(-) diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index 3a82a5a6e6..336f1f5acb 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -330,7 +330,6 @@ def _curve_fit( if backend.name == "pytorch": # pytorch has some problems with the `scipy.optim.curve_fit` function # thus we use a `torch.optim` optimizer - params = params.requires_grad_(True) loss = lambda pred, target: backend.np.mean((pred - target) ** 2) optimizer = backend.np.optim.LBFGS( [params], lr=lr, max_iter=max_iter, tolerance_grad=tolerance_grad @@ -432,6 +431,8 @@ def CDR( len(signature(model).parameters) - 1 ) # first arg is the input and the *params afterwards params = backend.cast(local_state.random(nparams), backend.precision) + if backend.name == "pytorch": + params.requires_grad = True optimal_params = _curve_fit( backend, model, @@ -553,6 +554,8 @@ def vnCDR( -1, len(noise_levels) ) params = backend.cast(local_state.random(len(noise_levels)), backend.precision) + if backend.name == "pytorch": + params.requires_grad = True optimal_params = _curve_fit( backend, model, diff --git a/src/qibo/transpiler/decompositions.py b/src/qibo/transpiler/decompositions.py index 4969fd6bb2..693c815e29 100644 --- a/src/qibo/transpiler/decompositions.py +++ b/src/qibo/transpiler/decompositions.py @@ -40,29 +40,12 @@ def count_2q(self, gate, backend): def count_1q(self, gate, backend): """Count the number of single qubit gates in the decomposition of the given gate.""" - if gate.parameters: - if isinstance( - gate, - (gates.FusedGate, gates.Unitary, gates.GeneralizedfSim, gates.fSim), - ): - decomposition = self.decompositions[gate.__class__](gate, backend) - else: - decomposition = self.decompositions[gate.__class__](gate) - else: - decomposition = self.decompositions[gate.__class__] + decomposition = self._check_instance(gate, backend) return len(tuple(g for g in decomposition if len(g.qubits) == 1)) def __call__(self, gate, backend=None): """Decompose a gate.""" - decomposition = self.decompositions[gate.__class__] - if callable(decomposition): - if isinstance( - gate, - (gates.FusedGate, gates.Unitary, gates.GeneralizedfSim, gates.fSim), - ): - decomposition = decomposition(gate, backend) - else: - decomposition = decomposition(gate) + decomposition = self._check_instance(gate, backend) return [ g.on_qubits({i: q for i, q in enumerate(gate.qubits)}) for g in decomposition From c7904808382b330e434fc13d5659f0fe7a4a862e Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Tue, 1 Oct 2024 15:08:59 +0400 Subject: [PATCH 24/34] fix torch test --- tests/test_backends_torch_gradients.py | 93 ++++++++++++++++++++++++++ tests/test_torch_gradients.py | 80 ---------------------- 2 files changed, 93 insertions(+), 80 deletions(-) create mode 100644 tests/test_backends_torch_gradients.py delete mode 100644 tests/test_torch_gradients.py diff --git a/tests/test_backends_torch_gradients.py b/tests/test_backends_torch_gradients.py new file mode 100644 index 0000000000..e0a630a31e --- /dev/null +++ b/tests/test_backends_torch_gradients.py @@ -0,0 +1,93 @@ +import numpy as np +import pytest + +import qibo +from qibo import gates, models + + +def test_torch_gradients(backend): + if backend.name != "pytorch": + pytest.skip("Test only valid for PyTorch backend.") + backend.np.manual_seed(42) + nepochs = 1001 + optimizer = backend.np.optim.Adam + target_state = backend.np.rand(2, dtype=backend.np.complex128) + target_state = target_state / backend.np.norm(target_state) + params = backend.np.rand(2, dtype=backend.np.float64, requires_grad=True) + c = models.Circuit(1) + c.add(gates.RX(0, params[0])) + c.add(gates.RY(0, params[1])) + + initial_params = params.clone() + initial_loss = 1 - backend.np.abs( + backend.np.sum( + backend.np.conj(target_state) * backend.execute_circuit(c).state() + ) + ) + + optimizer = optimizer([params]) + for _ in range(nepochs): + optimizer.zero_grad() + c.set_parameters(params) + final_state = backend.execute_circuit(c).state() + fidelity = backend.np.abs( + backend.np.sum(backend.np.conj(target_state) * final_state) + ) + loss = 1 - fidelity + loss.backward() + optimizer.step() + + assert initial_loss > loss + assert initial_params[0] != params[0] + + +def test_torch_tensorflow_gradients(backend): + if backend.name != "pytorch": + pytest.skip("Test only valid for PyTorch backend.") + + import tensorflow as tf # pylint: disable=import-outside-toplevel + + from qibo.backends.tensorflow import ( # pylint: disable=import-outside-toplevel + TensorflowBackend, + ) + + target_state = backend.np.tensor([0.0, 1.0], dtype=backend.np.complex128) + param = backend.np.tensor([0.1], dtype=backend.np.float64, requires_grad=True) + c = models.Circuit(1) + c.add(gates.RX(0, param[0])) + + optimizer = backend.np.optim.SGD + optimizer = optimizer([param], lr=1) + c.set_parameters(param) + final_state = backend.execute_circuit(c).state() + fidelity = backend.np.abs( + backend.np.sum(backend.np.conj(target_state) * final_state) + ) + loss = 1 - fidelity + loss.backward() + torch_param_grad = param.grad.clone().item() + optimizer.step() + torch_param = param.clone().item() + + tf_backend = TensorflowBackend() + + target_state = tf.constant([0.0, 1.0], dtype=tf.complex128) + param = tf.Variable([0.1], dtype=tf.float64) + c = models.Circuit(1) + c.add(gates.RX(0, param[0])) + + optimizer = tf.optimizers.SGD(learning_rate=1.0) + + with tf.GradientTape() as tape: + c.set_parameters(param) + final_state = tf_backend.execute_circuit(c).state() + fidelity = tf.abs(tf.reduce_sum(tf.math.conj(target_state) * final_state)) + loss = 1 - fidelity + + grads = tape.gradient(loss, [param]) + tf_param_grad = grads[0].numpy()[0] + optimizer.apply_gradients(zip(grads, [param])) + tf_param = param.numpy()[0] + + assert np.allclose(torch_param_grad, tf_param_grad, atol=1e-7, rtol=1e-7) + assert np.allclose(torch_param, tf_param, atol=1e-7, rtol=1e-7) diff --git a/tests/test_torch_gradients.py b/tests/test_torch_gradients.py deleted file mode 100644 index d6a11e9923..0000000000 --- a/tests/test_torch_gradients.py +++ /dev/null @@ -1,80 +0,0 @@ -import numpy as np -import pytest - -import qibo -from qibo import gates, models -from qibo.backends import PyTorchBackend, TensorflowBackend - - -def test_torch_gradients(): - qibo.set_backend("pytorch") - torch = PyTorchBackend().np - torch.manual_seed(42) - nepochs = 1001 - optimizer = torch.optim.Adam - target_state = torch.rand(2, dtype=torch.complex128) - target_state = target_state / torch.norm(target_state) - params = torch.rand(2, dtype=torch.float64, requires_grad=True) - c = models.Circuit(1) - c.add(gates.RX(0, params[0])) - c.add(gates.RY(0, params[1])) - - initial_params = params.clone() - initial_loss = 1 - torch.abs(torch.sum(torch.conj(target_state) * c().state())) - - optimizer = optimizer([params]) - for _ in range(nepochs): - optimizer.zero_grad() - c.set_parameters(params) - final_state = c().state() - fidelity = torch.abs(torch.sum(torch.conj(target_state) * final_state)) - loss = 1 - fidelity - loss.backward() - optimizer.step() - - assert initial_loss > loss - assert initial_params[0] != params[0] - - -def test_torch_tensorflow_gradients(): - qibo.set_backend("pytorch") - torch = PyTorchBackend().np - target_state = torch.tensor([0.0, 1.0], dtype=torch.complex128) - param = torch.tensor([0.1], dtype=torch.float64, requires_grad=True) - c = models.Circuit(1) - c.add(gates.RX(0, param[0])) - - optimizer = torch.optim.SGD - optimizer = optimizer([param], lr=1) - c.set_parameters(param) - final_state = c().state() - fidelity = torch.abs(torch.sum(torch.conj(target_state) * final_state)) - loss = 1 - fidelity - loss.backward() - torch_param_grad = param.grad.clone().item() - optimizer.step() - torch_param = param.clone().item() - - qibo.set_backend("tensorflow") - tf = TensorflowBackend().tf - - target_state = tf.constant([0.0, 1.0], dtype=tf.complex128) - param = tf.Variable([0.1], dtype=tf.float64) - c = models.Circuit(1) - c.add(gates.RX(0, param[0])) - - optimizer = tf.optimizers.SGD(learning_rate=1.0) - - with tf.GradientTape() as tape: - c.set_parameters(param) - final_state = c().state() - fidelity = tf.abs(tf.reduce_sum(tf.math.conj(target_state) * final_state)) - loss = 1 - fidelity - - grads = tape.gradient(loss, [param]) - tf_param_grad = grads[0].numpy()[0] - optimizer.apply_gradients(zip(grads, [param])) - tf_param = param.numpy()[0] - - assert np.allclose(torch_param_grad, tf_param_grad, atol=1e-7, rtol=1e-7) - assert np.allclose(torch_param, tf_param, atol=1e-7, rtol=1e-7) From 524fd6cc329f79f696e3d0eaa9b06471455987cd Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Tue, 1 Oct 2024 16:01:31 +0400 Subject: [PATCH 25/34] use infidelity from quantum info in test --- tests/test_backends_torch_gradients.py | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/tests/test_backends_torch_gradients.py b/tests/test_backends_torch_gradients.py index e0a630a31e..8c4aa02139 100644 --- a/tests/test_backends_torch_gradients.py +++ b/tests/test_backends_torch_gradients.py @@ -1,8 +1,8 @@ import numpy as np import pytest -import qibo from qibo import gates, models +from qibo.quantum_info import infidelity def test_torch_gradients(backend): @@ -19,10 +19,8 @@ def test_torch_gradients(backend): c.add(gates.RY(0, params[1])) initial_params = params.clone() - initial_loss = 1 - backend.np.abs( - backend.np.sum( - backend.np.conj(target_state) * backend.execute_circuit(c).state() - ) + initial_loss = infidelity( + target_state, backend.execute_circuit(c).state(), backend=backend ) optimizer = optimizer([params]) @@ -30,10 +28,7 @@ def test_torch_gradients(backend): optimizer.zero_grad() c.set_parameters(params) final_state = backend.execute_circuit(c).state() - fidelity = backend.np.abs( - backend.np.sum(backend.np.conj(target_state) * final_state) - ) - loss = 1 - fidelity + loss = infidelity(target_state, final_state, backend=backend) loss.backward() optimizer.step() @@ -60,10 +55,7 @@ def test_torch_tensorflow_gradients(backend): optimizer = optimizer([param], lr=1) c.set_parameters(param) final_state = backend.execute_circuit(c).state() - fidelity = backend.np.abs( - backend.np.sum(backend.np.conj(target_state) * final_state) - ) - loss = 1 - fidelity + loss = infidelity(target_state, final_state, backend=backend) loss.backward() torch_param_grad = param.grad.clone().item() optimizer.step() @@ -81,8 +73,7 @@ def test_torch_tensorflow_gradients(backend): with tf.GradientTape() as tape: c.set_parameters(param) final_state = tf_backend.execute_circuit(c).state() - fidelity = tf.abs(tf.reduce_sum(tf.math.conj(target_state) * final_state)) - loss = 1 - fidelity + loss = infidelity(target_state, final_state, backend=tf_backend) grads = tape.gradient(loss, [param]) tf_param_grad = grads[0].numpy()[0] From 77f29c9000818ccab8ae42bdae6bd32f92767144 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Wed, 2 Oct 2024 11:19:58 +0400 Subject: [PATCH 26/34] test gradients only on linux --- tests/test_backends_torch_gradients.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/test_backends_torch_gradients.py b/tests/test_backends_torch_gradients.py index 8c4aa02139..bf49950867 100644 --- a/tests/test_backends_torch_gradients.py +++ b/tests/test_backends_torch_gradients.py @@ -1,3 +1,5 @@ +import sys + import numpy as np import pytest @@ -36,6 +38,9 @@ def test_torch_gradients(backend): assert initial_params[0] != params[0] +@pytest.mark.skipif( + sys.platform != "linux", reason="Tensorflow available only when testing on linux." +) def test_torch_tensorflow_gradients(backend): if backend.name != "pytorch": pytest.skip("Test only valid for PyTorch backend.") From a7a481e0d2bd2a4b3bbb0824bfad771e06733ff5 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Thu, 3 Oct 2024 17:40:28 +0400 Subject: [PATCH 27/34] corrections by andrea --- tests/test_backends_torch_gradients.py | 26 ++++++++++++++------------ tests/test_gates_gates.py | 2 -- tests/test_models_hep.py | 9 ++------- 3 files changed, 16 insertions(+), 21 deletions(-) diff --git a/tests/test_backends_torch_gradients.py b/tests/test_backends_torch_gradients.py index bf49950867..08bacb44af 100644 --- a/tests/test_backends_torch_gradients.py +++ b/tests/test_backends_torch_gradients.py @@ -4,46 +4,48 @@ import pytest from qibo import gates, models +from qibo.backends import PyTorchBackend from qibo.quantum_info import infidelity -def test_torch_gradients(backend): - if backend.name != "pytorch": - pytest.skip("Test only valid for PyTorch backend.") +def test_torch_gradients(): + backend = PyTorchBackend() backend.np.manual_seed(42) - nepochs = 1001 + nepochs = 400 optimizer = backend.np.optim.Adam - target_state = backend.np.rand(2, dtype=backend.np.complex128) + target_state = backend.np.rand(4, dtype=backend.np.complex128) target_state = target_state / backend.np.norm(target_state) - params = backend.np.rand(2, dtype=backend.np.float64, requires_grad=True) - c = models.Circuit(1) + params = backend.np.rand(4, dtype=backend.np.float64, requires_grad=True) + c = models.Circuit(2) c.add(gates.RX(0, params[0])) - c.add(gates.RY(0, params[1])) + c.add(gates.RY(1, params[1])) + c.add(gates.U2(1, params[2], params[3])) initial_params = params.clone() initial_loss = infidelity( target_state, backend.execute_circuit(c).state(), backend=backend ) - optimizer = optimizer([params]) + optimizer = optimizer([params], lr=0.01) for _ in range(nepochs): optimizer.zero_grad() c.set_parameters(params) final_state = backend.execute_circuit(c).state() loss = infidelity(target_state, final_state, backend=backend) loss.backward() + grad = params.grad.clone().norm() optimizer.step() assert initial_loss > loss assert initial_params[0] != params[0] + assert grad.item() < 10e-3 @pytest.mark.skipif( sys.platform != "linux", reason="Tensorflow available only when testing on linux." ) -def test_torch_tensorflow_gradients(backend): - if backend.name != "pytorch": - pytest.skip("Test only valid for PyTorch backend.") +def test_torch_tensorflow_gradients(): + backend = PyTorchBackend() import tensorflow as tf # pylint: disable=import-outside-toplevel diff --git a/tests/test_gates_gates.py b/tests/test_gates_gates.py index 50a07e1c08..f47f26d613 100644 --- a/tests/test_gates_gates.py +++ b/tests/test_gates_gates.py @@ -860,7 +860,6 @@ def test_generalized_fsim(backend): phi = np.random.random() rotation = np.random.random((2, 2)) + 1j * np.random.random((2, 2)) gatelist = [gates.H(0), gates.H(1), gates.H(2)] - gate = gates.GeneralizedfSim(1, 2, rotation, phi) gatelist.append(gates.GeneralizedfSim(1, 2, rotation, phi)) final_state = apply_gates(backend, gatelist, nqubits=3) target_state = np.ones(len(final_state), dtype=complex) / np.sqrt(8) @@ -871,7 +870,6 @@ def test_generalized_fsim(backend): target_state[:4] = np.matmul(matrix, target_state[:4]) target_state[4:] = np.matmul(matrix, target_state[4:]) target_state = backend.cast(target_state, dtype=target_state.dtype) - print(final_state) backend.assert_allclose(final_state, target_state, atol=1e-6) with pytest.raises(NotImplementedError): diff --git a/tests/test_models_hep.py b/tests/test_models_hep.py index 13b3698cbc..19dd64146e 100644 --- a/tests/test_models_hep.py +++ b/tests/test_models_hep.py @@ -108,11 +108,6 @@ def test_qpdf(backend, ansatz, layers, nqubits, multi_output, output): np.random.seed(0) params = np.random.rand(model.nparams) result = model.predict(params, [0.1]) - # Pytorch backend has a different tolerance as is by default working with float32 - if backend.name == "pytorch": - atol = 1e-2 - rtol = 1e-5 - else: - atol = 1e-5 - rtol = 1e-7 + atol = 1e-5 + rtol = 1e-7 np.testing.assert_allclose(result, output, rtol=rtol, atol=atol) From e7b7217ff50654a68be3f2bd01039c5f815c7a2a Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Thu, 3 Oct 2024 18:00:56 +0400 Subject: [PATCH 28/34] more corrections --- src/qibo/backends/pytorch.py | 8 +++++--- src/qibo/models/circuit.py | 5 +---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/qibo/backends/pytorch.py b/src/qibo/backends/pytorch.py index fc5e4c5419..6aee6eb32e 100644 --- a/src/qibo/backends/pytorch.py +++ b/src/qibo/backends/pytorch.py @@ -127,7 +127,7 @@ def matrix_parametrized(self, gate): if name == "GeneralizedRBS": for parameter in ["theta", "phi"]: if not isinstance(gate.init_kwargs[parameter], self.np.Tensor): - gate.init_kwargs[parameter] = self.cast_parameter( + gate.init_kwargs[parameter] = self._cast_parameter( gate.init_kwargs[parameter], trainable=gate.trainable ) @@ -142,7 +142,9 @@ def matrix_parametrized(self, gate): new_parameters = [] for parameter in gate.parameters: if not isinstance(parameter, self.np.Tensor): - parameter = self.cast_parameter(parameter, trainable=gate.trainable) + parameter = self._cast_parameter( + parameter, trainable=gate.trainable + ) elif parameter.requires_grad: gate.trainable = True new_parameters.append(parameter) @@ -150,7 +152,7 @@ def matrix_parametrized(self, gate): _matrix = _matrix(*gate.parameters) return _matrix - def cast_parameter(self, x, trainable): + def _cast_parameter(self, x, trainable): """Cast a gate parameter to a torch tensor. Args: diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index a7c264e1ac..e9215f8c1d 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -1177,10 +1177,7 @@ def to_qasm(self): qubits = ",".join(f"q[{i}]" for i in gate.qubits) if isinstance(gate, gates.ParametrizedGate): - if any(x.__class__.__name__ == "Tensor" for x in gate.parameters): - params = (str(x.detach().item()) for x in gate.parameters) - else: - params = (str(x) for x in gate.parameters) + params = (str(x) for x in gate.parameters) name = f"{gate.qasm_label}({', '.join(params)})" else: name = gate.qasm_label From 3b731fd0a26089c6da52e8de861f3207ef52b4dd Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Thu, 3 Oct 2024 18:13:01 +0400 Subject: [PATCH 29/34] restore error in circuit quasm --- src/qibo/models/circuit.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index e9215f8c1d..a7c264e1ac 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -1177,7 +1177,10 @@ def to_qasm(self): qubits = ",".join(f"q[{i}]" for i in gate.qubits) if isinstance(gate, gates.ParametrizedGate): - params = (str(x) for x in gate.parameters) + if any(x.__class__.__name__ == "Tensor" for x in gate.parameters): + params = (str(x.detach().item()) for x in gate.parameters) + else: + params = (str(x) for x in gate.parameters) name = f"{gate.qasm_label}({', '.join(params)})" else: name = gate.qasm_label From 7e9c1e8c16b0652c5162e8bc5033bb94bad23a9b Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Thu, 3 Oct 2024 18:14:26 +0400 Subject: [PATCH 30/34] remove requires_grad from cast --- src/qibo/backends/pytorch.py | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/qibo/backends/pytorch.py b/src/qibo/backends/pytorch.py index 6aee6eb32e..3fb25e3b41 100644 --- a/src/qibo/backends/pytorch.py +++ b/src/qibo/backends/pytorch.py @@ -76,7 +76,6 @@ def cast( x, dtype=None, copy: bool = False, - requires_grad: bool = False, ): """Casts input as a Torch tensor of the specified dtype. @@ -91,8 +90,6 @@ def cast( Defaults to ``None``. copy (bool, optional): If ``True``, the input tensor is copied before casting. Defaults to ``False``. - requires_grad (bool): If ``True``, the input tensor requires gradient. - If ``False``, the input tensor does not require gradient. """ if dtype is None: @@ -102,9 +99,6 @@ def cast( elif not isinstance(dtype, self.np.dtype): dtype = self._torch_dtype(str(dtype)) - # check if dtype is an integer to remove gradients - if dtype in [self.np.int32, self.np.int64, self.np.int8, self.np.int16]: - requires_grad = False if isinstance(x, self.np.Tensor): x = x.to(dtype) elif ( @@ -114,7 +108,7 @@ def cast( ): x = self.np.stack(x) else: - x = self.np.tensor(x, dtype=dtype, requires_grad=requires_grad) + x = self.np.tensor(x, dtype=dtype) if copy: return x.clone() From b48928a1d42cf1beefcadb2e09644d8ed480bae6 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Fri, 4 Oct 2024 12:33:48 +0400 Subject: [PATCH 31/34] fix tests --- tests/test_models_variational.py | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/tests/test_models_variational.py b/tests/test_models_variational.py index f991c24edc..5ec737610f 100644 --- a/tests/test_models_variational.py +++ b/tests/test_models_variational.py @@ -124,11 +124,11 @@ def test_vqe(backend, method, options, compile, filename): circuit.add(gates.RY(q, theta=1.0)) hamiltonian = hamiltonians.XXZ(nqubits=nqubits, backend=backend) np.random.seed(0) - initial_parameters = np.random.uniform(0, 2 * np.pi, 2 * nqubits * layers + nqubits) + initial_parameters = backend.cast( + np.random.uniform(0, 2 * np.pi, 2 * nqubits * layers + nqubits), dtype="float64" + ) if backend.name == "pytorch": - initial_parameters = backend.cast( - initial_parameters, dtype=backend.np.float32, requires_grad=True - ) + initial_parameters.requires_grad = True v = models.VQE(circuit, hamiltonian) loss_values = [] @@ -276,12 +276,9 @@ def test_qaoa_optimization(backend, method, options, dense, filename): pytest.skip("Skipping scipy optimizers for tensorflow and pytorch.") h = hamiltonians.XXZ(3, dense=dense, backend=backend) qaoa = models.QAOA(h) - initial_p = [0.05, 0.06, 0.07, 0.08] + initial_p = backend.cast([0.05, 0.06, 0.07, 0.08], dtype="float64") if backend.name == "pytorch": - initial_p = backend.cast( - initial_p, dtype=backend.np.float32, requires_grad=True - ) - + initial_p.requires_grad = True best, params, _ = qaoa.minimize(initial_p, method=method, options=options) if filename is not None: assert_regression_fixture(backend, params, filename) From acdc560769b9881b067005d7feaf0b8640842a5a Mon Sep 17 00:00:00 2001 From: Renato Mello Date: Tue, 8 Oct 2024 15:49:48 +0400 Subject: [PATCH 32/34] fix merge --- src/qibo/backends/pytorch.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/qibo/backends/pytorch.py b/src/qibo/backends/pytorch.py index 7afcfa28e1..8b88fdbda7 100644 --- a/src/qibo/backends/pytorch.py +++ b/src/qibo/backends/pytorch.py @@ -14,7 +14,7 @@ class TorchMatrices(NumpyMatrices): dtype (torch.dtype): Data type of the matrices. """ - def __init__(self, dtype, requires_grad): + def __init__(self, dtype): import torch # pylint: disable=import-outside-toplevel # type: ignore super().__init__(dtype) From c01cd331860d4e5cad24a483d58375f98fc06caf Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Wed, 9 Oct 2024 13:50:22 +0400 Subject: [PATCH 33/34] last corrections by andrea --- src/qibo/models/circuit.py | 5 +---- src/qibo/models/error_mitigation.py | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/src/qibo/models/circuit.py b/src/qibo/models/circuit.py index a7c264e1ac..35d18100a6 100644 --- a/src/qibo/models/circuit.py +++ b/src/qibo/models/circuit.py @@ -1177,10 +1177,7 @@ def to_qasm(self): qubits = ",".join(f"q[{i}]" for i in gate.qubits) if isinstance(gate, gates.ParametrizedGate): - if any(x.__class__.__name__ == "Tensor" for x in gate.parameters): - params = (str(x.detach().item()) for x in gate.parameters) - else: - params = (str(x) for x in gate.parameters) + params = (str(float(x)) for x in gate.parameters) name = f"{gate.qasm_label}({', '.join(params)})" else: name = gate.qasm_label diff --git a/src/qibo/models/error_mitigation.py b/src/qibo/models/error_mitigation.py index 336f1f5acb..60a7da94af 100644 --- a/src/qibo/models/error_mitigation.py +++ b/src/qibo/models/error_mitigation.py @@ -330,6 +330,7 @@ def _curve_fit( if backend.name == "pytorch": # pytorch has some problems with the `scipy.optim.curve_fit` function # thus we use a `torch.optim` optimizer + params.requires_grad = True loss = lambda pred, target: backend.np.mean((pred - target) ** 2) optimizer = backend.np.optim.LBFGS( [params], lr=lr, max_iter=max_iter, tolerance_grad=tolerance_grad @@ -431,8 +432,6 @@ def CDR( len(signature(model).parameters) - 1 ) # first arg is the input and the *params afterwards params = backend.cast(local_state.random(nparams), backend.precision) - if backend.name == "pytorch": - params.requires_grad = True optimal_params = _curve_fit( backend, model, @@ -554,8 +553,6 @@ def vnCDR( -1, len(noise_levels) ) params = backend.cast(local_state.random(len(noise_levels)), backend.precision) - if backend.name == "pytorch": - params.requires_grad = True optimal_params = _curve_fit( backend, model, From 3f22d8d2e65fc19f6f13ccf9f85481683df52178 Mon Sep 17 00:00:00 2001 From: simone bordoni Date: Wed, 9 Oct 2024 15:07:45 +0400 Subject: [PATCH 34/34] fix test --- tests/test_models_circuit.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/test_models_circuit.py b/tests/test_models_circuit.py index 8ffa586740..c6a72a39ff 100644 --- a/tests/test_models_circuit.py +++ b/tests/test_models_circuit.py @@ -351,19 +351,19 @@ def test_circuit_light_cone(): OPENQASM 2.0; include "qelib1.inc"; qreg q[6]; -ry(0) q[0]; -ry(0) q[1]; -ry(0) q[2]; -ry(0) q[3]; -ry(0) q[4]; -ry(0) q[5]; +ry(0.0) q[0]; +ry(0.0) q[1]; +ry(0.0) q[2]; +ry(0.0) q[3]; +ry(0.0) q[4]; +ry(0.0) q[5]; cz q[0],q[1]; cz q[2],q[3]; cz q[4],q[5]; -ry(0) q[1]; -ry(0) q[2]; -ry(0) q[3]; -ry(0) q[4]; +ry(0.0) q[1]; +ry(0.0) q[2]; +ry(0.0) q[3]; +ry(0.0) q[4]; cz q[1],q[2]; cz q[3],q[4];""" assert qubit_map == {2: 0, 3: 1, 4: 2, 5: 3, 6: 4, 7: 5}