From 8a919b6dc278162e4a3283cfba2d220ceb892d51 Mon Sep 17 00:00:00 2001 From: Maria Schuld Date: Wed, 24 Mar 2021 12:56:13 +0200 Subject: [PATCH 01/16] converted CVQNN --- pennylane/templates/layers/basic_entangler.py | 8 +- pennylane/templates/layers/cv_neural_net.py | 171 +++++------ .../test_basic_entangler.py | 101 +++--- .../test_layers/test_cv_neural_net.py | 288 ++++++++++++++++++ 4 files changed, 443 insertions(+), 125 deletions(-) rename tests/templates/{test_ansaetze => test_layers}/test_basic_entangler.py (79%) create mode 100644 tests/templates/test_layers/test_cv_neural_net.py diff --git a/pennylane/templates/layers/basic_entangler.py b/pennylane/templates/layers/basic_entangler.py index 556f94a6e83..49657b44960 100644 --- a/pennylane/templates/layers/basic_entangler.py +++ b/pennylane/templates/layers/basic_entangler.py @@ -12,12 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. r""" -Contains the ``BasicEntanglerLayers`` template. +Contains the BasicEntanglerLayers template. """ # pylint: disable=consider-using-enumerate import pennylane as qml from pennylane.operation import Operation, AnyWires -from pennylane.wires import Wires class BasicEntanglerLayers(Operation): @@ -50,8 +49,7 @@ class BasicEntanglerLayers(Operation): Args: weights (tensor_like): Weight tensor of shape ``(L, len(wires))``. Each weight is used as a parameter for the rotation. - wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or - a Wires object. + wires (Iterable): wires that the template acts on rotation (pennylane.ops.Operation): one-parameter single-qubit gate to use, if ``None``, :class:`~pennylane.ops.RX` is used as default Raises: @@ -153,7 +151,7 @@ def expand(self): for layer in range(repeat): for i in range(len(self.wires)): - self.rotation(weights[layer][i], wires=self.wires[i : i + 1]) + self.rotation(weights[layer][i], wires=self.wires[i: i + 1]) if len(self.wires) == 2: qml.CNOT(wires=self.wires) diff --git a/pennylane/templates/layers/cv_neural_net.py b/pennylane/templates/layers/cv_neural_net.py index 2fef6b8e684..ddfc7b8f7db 100644 --- a/pennylane/templates/layers/cv_neural_net.py +++ b/pennylane/templates/layers/cv_neural_net.py @@ -1,4 +1,4 @@ -# Copyright 2018-2020 Xanadu Quantum Technologies Inc. +# Copyright 2018-2021 Xanadu Quantum Technologies Inc. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,79 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. r""" -Contains the ``CVNeuralNetLayers`` template. +Contains the CVNeuralNetLayers template. """ # pylint: disable-msg=too-many-branches,too-many-arguments,protected-access import pennylane as qml -from pennylane.templates.decorator import template -from pennylane.ops import Squeezing, Displacement, Kerr -from pennylane.templates.subroutines import Interferometer -from pennylane.templates import broadcast -from pennylane.wires import Wires +from pennylane.operation import Operation, AnyWires -def _preprocess(theta_1, phi_1, varphi_1, r, phi_r, theta_2, phi_2, varphi_2, a, phi_a, k, wires): - """Validate and pre-process inputs as follows: - - * Check that the first dimensions of all weight tensors match - * Check that the other dimensions of all weight tensors are correct for the number of qubits. - - Args: - heta_1 (tensor_like): shape :math:`(L, K)` tensor of transmittivity angles for first interferometer - phi_1 (tensor_like): shape :math:`(L, K)` tensor of phase angles for first interferometer - varphi_1 (tensor_like): shape :math:`(L, M)` tensor of rotation angles to apply after first interferometer - r (tensor_like): shape :math:`(L, M)` tensor of squeezing amounts for :class:`~pennylane.ops.Squeezing` operations - phi_r (tensor_like): shape :math:`(L, M)` tensor of squeezing angles for :class:`~pennylane.ops.Squeezing` operations - theta_2 (tensor_like): shape :math:`(L, K)` tensor of transmittivity angles for second interferometer - phi_2 (tensor_like): shape :math:`(L, K)` tensor of phase angles for second interferometer - varphi_2 (tensor_like): shape :math:`(L, M)` tensor of rotation angles to apply after second interferometer - a (tensor_like): shape :math:`(L, M)` tensor of displacement magnitudes for :class:`~pennylane.ops.Displacement` operations - phi_a (tensor_like): shape :math:`(L, M)` tensor of displacement angles for :class:`~pennylane.ops.Displacement` operations - k (tensor_like): shape :math:`(L, M)` tensor of kerr parameters for :class:`~pennylane.ops.Kerr` operations - wires (Wires): wires that template acts on - - Returns: - int: number of times that the ansatz is repeated - """ - - n_wires = len(wires) - n_if = n_wires * (n_wires - 1) // 2 - - # check that first dimension is the same - weights_list = [theta_1, phi_1, varphi_1, r, phi_r, theta_2, phi_2, varphi_2, a, phi_a, k] - shapes = [qml.math.shape(w) for w in weights_list] - - first_dims = [s[0] for s in shapes] - if len(set(first_dims)) > 1: - raise ValueError( - f"The first dimension of all parameters needs to be the same, got {first_dims}" - ) - repeat = shapes[0][0] - - second_dims = [s[1] for s in shapes] - expected = [ - n_if, - n_if, - n_wires, - n_wires, - n_wires, - n_if, - n_if, - n_wires, - n_wires, - n_wires, - n_wires, - ] - if not all(e == d for e, d in zip(expected, second_dims)): - raise ValueError("Got unexpected shape for one or more parameters.") - - return repeat - - -@template -def CVNeuralNetLayers( - theta_1, phi_1, varphi_1, r, phi_r, theta_2, phi_2, varphi_2, a, phi_a, k, wires -): +class CVNeuralNetLayers(Operation): r"""A sequence of layers of a continuous-variable quantum neural network, as specified in `arXiv:1806.06871 `_. @@ -124,27 +59,93 @@ def CVNeuralNetLayers( a (tensor_like): shape :math:`(L, M)` tensor of displacement magnitudes for :class:`~pennylane.ops.Displacement` operations phi_a (tensor_like): shape :math:`(L, M)` tensor of displacement angles for :class:`~pennylane.ops.Displacement` operations k (tensor_like): shape :math:`(L, M)` tensor of kerr parameters for :class:`~pennylane.ops.Kerr` operations - wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or - a Wires object. - Raises: - ValueError: if inputs do not have the correct format + wires (Iterable): wires that the template acts on + + .. UsageDetails: + + **Parameter shapes** + + A list of shapes for the 11 weight tensors can be computed by the static method + :meth:`~.CVNeuralNetLayers.shapes` and used when creating randomly + initialised weights: + + .. code-block:: python + + shapes = CVNeuralNetLayers.shapes(n_layers=2, n_wires=2) + weights = [np.random.random(shape) for shape in shapes] + + def circuit(): + CVNeuralNetLayers(*weights, wires=[0, 1]) + return qml.expval(qml.X(0)) + """ - wires = Wires(wires) - repeat = _preprocess( - theta_1, phi_1, varphi_1, r, phi_r, theta_2, phi_2, varphi_2, a, phi_a, k, wires - ) + num_params = 11 + num_wires = AnyWires + par_domain = "A" + + def __init__(self, theta_1, phi_1, varphi_1, r, phi_r, theta_2, phi_2, varphi_2, a, phi_a, k, wires, do_queue=True): + + n_wires = len(wires) + n_if = n_wires * (n_wires - 1) // 2 + + # check that first dimension is the same + weights_list = [theta_1, phi_1, varphi_1, r, phi_r, theta_2, phi_2, varphi_2, a, phi_a, k] + shapes = [qml.math.shape(w) for w in weights_list] + first_dims = [s[0] for s in shapes] + if len(set(first_dims)) > 1: + raise ValueError( + f"The first dimension of all parameters needs to be the same, got {first_dims}" + ) + + # check second dimensions + second_dims = [s[1] for s in shapes] + expected = [n_if] * 2 + [n_wires] * 3 + [n_if] * 2 + [n_wires] * 4 + if not all(e == d for e, d in zip(expected, second_dims)): + raise ValueError("Got unexpected shape for one or more parameters.") + + self.n_layers = shapes[0][0] + + super().__init__(theta_1, phi_1, varphi_1, r, phi_r, theta_2, phi_2, varphi_2, a, phi_a, k, wires=wires, + do_queue=do_queue) + + def expand(self): + + with qml.tape.QuantumTape() as tape: + + for l in range(self.n_layers): + + qml.templates.Interferometer(theta=self.parameters[0][l], phi=self.parameters[1][l], + varphi=self.parameters[2][l], wires=self.wires) + + for i in range(len(self.wires)): + qml.Squeezing(self.parameters[3][l, i], self.parameters[4][l, i], wires=self.wires[i]) + + qml.templates.Interferometer(theta=self.parameters[5][l], phi=self.parameters[6][l], + varphi=self.parameters[7][l], wires=self.wires) + + for i in range(len(self.wires)): + qml.Displacement(self.parameters[8][l, i], self.parameters[9][l, i], wires=self.wires[i]) + + for i in range(len(self.wires)): + qml.Kerr(self.parameters[10][l, i], wires=self.wires[i]) - for l in range(repeat): + return tape - Interferometer(theta=theta_1[l], phi=phi_1[l], varphi=varphi_1[l], wires=wires) + @staticmethod + def shape(n_layers, n_wires): + r"""Returns a list of shapes for the 11 parameter tensors. - r_and_phi_r = qml.math.stack([r[l], phi_r[l]], axis=1) - broadcast(unitary=Squeezing, pattern="single", wires=wires, parameters=r_and_phi_r) + Args: + n_layers (int): number of layers + n_wires (int): number of qubits - Interferometer(theta=theta_2[l], phi=phi_2[l], varphi=varphi_2[l], wires=wires) + Returns: + list[tuple[int]]: list of shapes + """ + n_if = n_wires * (n_wires - 1) // 2 - a_and_phi_a = qml.math.stack([a[l], phi_a[l]], axis=1) - broadcast(unitary=Displacement, pattern="single", wires=wires, parameters=a_and_phi_a) + shapes = [(n_layers, n_if)] * 2 + [(n_layers, n_wires)] * 3 + [(n_layers, n_if)] * 2 + [ + (n_layers, n_wires)] * 4 - broadcast(unitary=Kerr, pattern="single", wires=wires, parameters=k[l]) + return shapes diff --git a/tests/templates/test_ansaetze/test_basic_entangler.py b/tests/templates/test_layers/test_basic_entangler.py similarity index 79% rename from tests/templates/test_ansaetze/test_basic_entangler.py rename to tests/templates/test_layers/test_basic_entangler.py index 87e91e52cf9..a9dbd9fafe3 100644 --- a/tests/templates/test_ansaetze/test_basic_entangler.py +++ b/tests/templates/test_layers/test_basic_entangler.py @@ -20,33 +20,18 @@ from pennylane import numpy as pnp -def circuit_template(weights): - qml.templates.BasicEntanglerLayers(weights, range(3)) - return qml.expval(qml.PauliZ(0)) - - -def circuit_decomposed(weights): - qml.RX(weights[0, 0], wires=0) - qml.RX(weights[0, 1], wires=1) - qml.RX(weights[0, 2], wires=2) - qml.CNOT(wires=[0, 1]) - qml.CNOT(wires=[1, 2]) - qml.CNOT(wires=[2, 0]) - return qml.expval(qml.PauliZ(0)) - - class TestDecomposition: """Tests that the template defines the correct decomposition.""" QUEUES = [ - (1, (1, 1), ["RX"]), - (2, (1, 2), ["RX", "RX", "CNOT", "RY", "RY", "RX", "RX"]), - (2, (2, 2), ["RX", "RX", "CNOT", "RX", "RX", "CNOT"]), - (3, (1, 3), ["RX", "RX", "RX", "CNOT", "CNOT", "CNOT"]), + (1, (1, 1), ["RX"], [[0]]), + (2, (1, 2), ["RX", "RX", "CNOT"], [[0], [1], [0, 1]]), + (2, (2, 2), ["RX", "RX", "CNOT", "RX", "RX", "CNOT"], [[0], [1], [0, 1], [0], [1], [0, 1]]), + (3, (1, 3), ["RX", "RX", "RX", "CNOT", "CNOT", "CNOT"], [[0], [1], [2], [0, 1], [1, 2], [2, 0]]), ] - @pytest.mark.parametrize("n_wires, weight_shape, expected_names", QUEUES) - def test_expansion(self, n_wires, weight_shape, expected_names): + @pytest.mark.parametrize("n_wires, weight_shape, expected_names, expected_wires", QUEUES) + def test_expansion(self, n_wires, weight_shape, expected_names, expected_wires): """Checks the queue for the default settings.""" weights = np.random.random(size=weight_shape) @@ -56,6 +41,7 @@ def test_expansion(self, n_wires, weight_shape, expected_names): for i, gate in enumerate(tape.operations): assert gate.name == expected_names[i] + assert gate.wires.labels == tuple(expected_wires[i]) @pytest.mark.parametrize("rotation", [qml.RY, qml.RZ]) def test_rotation(self, rotation): @@ -78,7 +64,7 @@ def test_rotation(self, rotation): ([[np.pi] * 4], 4, [-1, 1, -1, 1]), ], ) - def test_simple_target_outputs(self, weights, n_wires, target): + def test_simple_target_outputs(self, weights, n_wires, target, tol): """Tests the result of the template for simple cases.""" dev = qml.device("default.qubit", wires=n_wires) @@ -89,8 +75,7 @@ def circuit(weights): return [qml.expval(qml.PauliZ(i)) for i in range(n_wires)] expectations = circuit(weights) - for exp, target_exp in zip(expectations, target): - assert exp == target_exp + assert np.allclose(expectations, target, atol=tol, rtol=0) def test_custom_wire_labels(self, tol): """Test that template can deal with non-numeric, nonconsecutive wire labels.""" @@ -115,7 +100,7 @@ def circuit2(): assert np.allclose(dev.state, dev2.state, atol=tol, rtol=0) -class TestParameters: +class TestInputs: """Test inputs and pre-processing.""" def test_exception_wrong_dim(self): @@ -150,12 +135,45 @@ def test_shape(self, n_layers, n_wires, expected_shape): assert shape == expected_shape -class TestGradients: - """Tests that the gradient is computed correctly in all interfaces.""" +def circuit_template(weights): + qml.templates.BasicEntanglerLayers(weights, range(3)) + return qml.expval(qml.PauliZ(0)) + + +def circuit_decomposed(weights): + qml.RX(weights[0, 0], wires=0) + qml.RX(weights[0, 1], wires=1) + qml.RX(weights[0, 2], wires=2) + qml.CNOT(wires=[0, 1]) + qml.CNOT(wires=[1, 2]) + qml.CNOT(wires=[2, 0]) + return qml.expval(qml.PauliZ(0)) + + +class TestInterfaces: + """Tests that the template is compatible with all interfaces, including the computation + of gradients.""" + + def test_list_and_tuples(self, tol): + """Tests common iterables as inputs.""" + + weights = [[0.1, -1.1, 0.2]] + + dev = qml.device("default.qubit", wires=3) + + circuit = qml.QNode(circuit_template, dev) + circuit2 = qml.QNode(circuit_decomposed, dev) + + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + res = circuit(tuple(weights)) + res2 = circuit2(tuple(weights)) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) def test_autograd(self, tol): - """Tests that gradients of template and decomposed circuit - are the same in the autograd interface.""" + """Tests the autograd interface.""" weights = np.random.random(size=(1, 3)) weights = pnp.array(weights, requires_grad=True) @@ -165,6 +183,10 @@ def test_autograd(self, tol): circuit = qml.QNode(circuit_template, dev) circuit2 = qml.QNode(circuit_decomposed, dev) + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + grad_fn = qml.grad(circuit) grads = grad_fn(weights) @@ -174,8 +196,7 @@ def test_autograd(self, tol): assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) def test_jax(self, tol, skip_if_no_jax_support): - """Tests that gradients of template and decomposed circuit - are the same in the jax interface.""" + """Tests the jax interface.""" import jax import jax.numpy as jnp @@ -187,6 +208,10 @@ def test_jax(self, tol, skip_if_no_jax_support): circuit = qml.QNode(circuit_template, dev, interface="jax") circuit2 = qml.QNode(circuit_decomposed, dev, interface="jax") + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + grad_fn = jax.grad(circuit) grads = grad_fn(weights) @@ -196,8 +221,7 @@ def test_jax(self, tol, skip_if_no_jax_support): assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) def test_tf(self, tol, skip_if_no_tf_support): - """Tests that gradients of template and decomposed circuit - are the same in the tf interface.""" + """Tests the tf interface.""" import tensorflow as tf @@ -208,6 +232,10 @@ def test_tf(self, tol, skip_if_no_tf_support): circuit = qml.QNode(circuit_template, dev, interface="tf") circuit2 = qml.QNode(circuit_decomposed, dev, interface="tf") + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + with tf.GradientTape() as tape: res = circuit(weights) grads = tape.gradient(res, [weights]) @@ -219,8 +247,7 @@ def test_tf(self, tol, skip_if_no_tf_support): assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) def test_torch(self, tol, skip_if_no_torch_support): - """Tests that gradients of template and decomposed circuit - are the same in the torch interface.""" + """Tests the torch interface.""" import torch @@ -231,6 +258,10 @@ def test_torch(self, tol, skip_if_no_torch_support): circuit = qml.QNode(circuit_template, dev, interface="torch") circuit2 = qml.QNode(circuit_decomposed, dev, interface="torch") + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + res = circuit(weights) res.backward() grads = [weights.grad] diff --git a/tests/templates/test_layers/test_cv_neural_net.py b/tests/templates/test_layers/test_cv_neural_net.py new file mode 100644 index 00000000000..1334fc24342 --- /dev/null +++ b/tests/templates/test_layers/test_cv_neural_net.py @@ -0,0 +1,288 @@ +# Copyright 2018-2021 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Unit tests for the BasicEntanglerLayers template. +""" +import pytest +import numpy as np +import pennylane as qml +from pennylane import numpy as pnp +from pennylane.devices import DefaultGaussian + + +class DummyDevice(DefaultGaussian): + """Dummy Gaussian device to allow Kerr operations""" + _operation_map = DefaultGaussian._operation_map.copy() + _operation_map['Kerr'] = lambda *x, **y: np.identity(2) + + +def expected_shapes(n_layers, n_wires): + # compute the expected shapes for a given number of wires + n_if = n_wires * (n_wires - 1) // 2 + expected = [(n_layers, n_if)] * 2 + [(n_layers, n_wires)] * 3 + [(n_layers, n_if)] * 2 + [(n_layers, n_wires)] * 4 + return expected + + +class TestDecomposition: + """Tests that the template defines the correct decomposition.""" + + QUEUES = [ + (1, ["Interferometer", "Squeezing", "Interferometer", "Displacement", "Kerr"], [0]*5), + (2, ["Interferometer", "Squeezing", "Squeezing", "Interferometer", + "Displacement", "Displacement", "Kerr", "Kerr"], [[0, 1], [0], [1], [0, 1], [0], [1], [0], [1]]), + ] + + @pytest.mark.parametrize("n_wires, expected_names, expected_wires", QUEUES) + def test_expansion(self, n_wires, expected_names, expected_wires): + """Checks the queue for the default settings.""" + + shapes = expected_shapes(1, n_wires) + weights = [np.random.random(shape) for shape in shapes] + + op = qml.templates.CVNeuralNetLayers(*weights, wires=range(n_wires)) + tape = op.expand() + + for i, gate in enumerate(tape.operations): + assert gate.name == expected_names[i] + assert gate.wires.labels == tuple(expected_wires[i]) + + def test_custom_wire_labels(self, tol): + """Test that template can deal with non-numeric, nonconsecutive wire labels.""" + shapes = expected_shapes(1, 3) + weights = [np.random.random(shape) for shape in shapes] + + dev = DummyDevice(wires=3) + dev2 = DummyDevice(wires=["z", "a", "k"]) + + @qml.qnode(dev) + def circuit(): + qml.templates.CVNeuralNetLayers(*weights, wires=range(3)) + return qml.expval(qml.Identity(0)) + + @qml.qnode(dev2) + def circuit2(): + qml.templates.CVNeuralNetLayers(*weights, wires=["z", "a", "k"]) + return qml.expval(qml.Identity("z")) + + circuit() + circuit2() + + assert np.allclose(dev._state[0], dev2._state[0], atol=tol, rtol=0) + assert np.allclose(dev._state[1], dev2._state[1], atol=tol, rtol=0) + + +class TestInputs: + """Test inputs and pre-processing.""" + + def test_cvqnn_layers_exception_nlayers(self): + """Check exception if inconsistent number of layers""" + shapes = expected_shapes(1, 2) + weights = [np.random.random(shape) for shape in shapes[:-1]] + weights += [np.random.random((2, shapes[-1][1]))] + + dev = DummyDevice(wires=2) + + @qml.qnode(dev) + def circuit(): + qml.templates.CVNeuralNetLayers(*weights, wires=range(2)) + return qml.expval(qml.X(0)) + + with pytest.raises(ValueError, match="The first dimension of all parameters"): + circuit() + + def test_cvqnn_layers_exception_second_dim(self): + """Check exception if weong dimension of weights""" + shapes = expected_shapes(1, 2) + weights = [np.random.random(shape) for shape in shapes[:-1]] + weights += [np.random.random((1, shapes[-1][1]-1))] + + dev = DummyDevice(wires=2) + + @qml.qnode(dev) + def circuit(): + qml.templates.CVNeuralNetLayers(*weights, wires=range(2)) + return qml.expval(qml.X(0)) + + with pytest.raises(ValueError, match="Got unexpected shape for one or more parameters"): + circuit() + + +class TestAttributes: + """Test methods and attributes.""" + + @pytest.mark.parametrize( + "n_layers, n_wires", + [ + (2, 3), + (2, 1), + (2, 2), + ], + ) + def test_shapes(self, n_layers, n_wires, tol): + """Test that the shape method returns the correct shapes for + the weight tensors""" + + shapes = qml.templates.CVNeuralNetLayers.shape(n_layers, n_wires) + expected = expected_shapes(n_layers, n_wires) + + assert np.allclose(shapes, expected, atol=tol, rtol=0) + + +def circuit_template(*weights): + qml.templates.CVNeuralNetLayers(*weights, range(2)) + return qml.expval(qml.X(0)) + + +def circuit_decomposed(*weights): + qml.templates.Interferometer(weights[0][0], weights[1][0], weights[2][0], wires=[0, 1]) + qml.Squeezing(weights[3][0, 0], weights[4][0, 0], wires=0) + qml.Squeezing(weights[3][0, 1], weights[4][0, 1], wires=1) + qml.templates.Interferometer(weights[5][0], weights[6][0], weights[7][0], wires=[0, 1]) + qml.Displacement(weights[8][0, 0], weights[9][0, 0], wires=0) + qml.Displacement(weights[8][0, 1], weights[9][0, 1], wires=1) + qml.Kerr(weights[10][0, 0], wires=0) + qml.Kerr(weights[10][0, 1], wires=1) + return qml.expval(qml.X(0)) + + +class TestInterfaces: + """Tests that the template is compatible with all interfaces, including the computation + of gradients.""" + + def test_list_and_tuples(self, tol): + """Tests common iterables as inputs.""" + + shapes = expected_shapes(1, 2) + weights = [np.random.random(shape) for shape in shapes] + + dev = DummyDevice(wires=2) + + circuit = qml.QNode(circuit_template, dev) + circuit2 = qml.QNode(circuit_decomposed, dev) + + res = circuit(*weights) + res2 = circuit2(*weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + weights_tuple = tuple(w for w in weights) + res = circuit(*weights_tuple) + res2 = circuit2(*weights_tuple) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + def test_autograd(self, tol): + """Tests the autograd interface.""" + + shapes = expected_shapes(1, 2) + weights = [np.random.random(shape) for shape in shapes] + weights = [pnp.array(w, requires_grad=True) for w in weights] + + dev = DummyDevice(wires=2) + + circuit = qml.QNode(circuit_template, dev) + circuit2 = qml.QNode(circuit_decomposed, dev) + + res = circuit(*weights) + res2 = circuit2(*weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + grad_fn = qml.grad(circuit) + grads = grad_fn(*weights) + + grad_fn2 = qml.grad(circuit2) + grads2 = grad_fn2(*weights) + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + + def test_jax(self, tol, skip_if_no_jax_support): + """Tests the jax interface.""" + + import jax + import jax.numpy as jnp + + shapes = expected_shapes(1, 2) + weights = [np.random.random(shape) for shape in shapes] + weights = [jnp.array(w) for w in weights] + + dev = DummyDevice(wires=2) + + circuit = qml.QNode(circuit_template, dev, interface="jax") + circuit2 = qml.QNode(circuit_decomposed, dev, interface="jax") + + res = circuit(*weights) + res2 = circuit2(*weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + grad_fn = jax.grad(circuit) + grads = grad_fn(*weights) + + grad_fn2 = jax.grad(circuit2) + grads2 = grad_fn2(*weights) + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + + def test_tf(self, tol, skip_if_no_tf_support): + """Tests the tf interface.""" + + import tensorflow as tf + + shapes = expected_shapes(1, 2) + weights = [np.random.random(shape) for shape in shapes] + weights = [tf.Variable(w) for w in weights] + + dev = DummyDevice(wires=2) + + circuit = qml.QNode(circuit_template, dev, interface="tf") + circuit2 = qml.QNode(circuit_decomposed, dev, interface="tf") + + res = circuit(*weights) + res2 = circuit2(*weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + with tf.GradientTape() as tape: + res = circuit(*weights) + grads = tape.gradient(res, [*weights]) + + with tf.GradientTape() as tape2: + res2 = circuit2(*weights) + grads2 = tape2.gradient(res2, [*weights]) + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + + def test_torch(self, tol, skip_if_no_torch_support): + """Tests the torch interface.""" + + import torch + + shapes = expected_shapes(1, 2) + weights = [np.random.random(size=shape) for shape in shapes] + weights = [torch.tensor(w, requires_grad=True) for w in weights] + + dev = DummyDevice(wires=2) + + circuit = qml.QNode(circuit_template, dev, interface="torch") + circuit2 = qml.QNode(circuit_decomposed, dev, interface="torch") + + res = circuit(*weights) + res2 = circuit2(*weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + res = circuit(*weights) + res.backward() + grads = [w.grad for w in weights] + + res2 = circuit2(*weights) + res2.backward() + grads2 = [w.grad for w in weights] + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) From 680957214b7adb8c1022b6950cdacfde414074fb Mon Sep 17 00:00:00 2001 From: Maria Schuld Date: Wed, 24 Mar 2021 16:34:13 +0200 Subject: [PATCH 02/16] backup --- pennylane/templates/layers/cv_neural_net.py | 2 +- pennylane/templates/layers/random.py | 161 +++++++-------- .../templates/layers/simplified_two_design.py | 159 +++++++-------- .../templates/layers/strongly_entangling.py | 152 +++++++------- .../templates/subroutines/interferometer.py | 189 +++++++++--------- 5 files changed, 324 insertions(+), 339 deletions(-) diff --git a/pennylane/templates/layers/cv_neural_net.py b/pennylane/templates/layers/cv_neural_net.py index ddfc7b8f7db..3dab1037292 100644 --- a/pennylane/templates/layers/cv_neural_net.py +++ b/pennylane/templates/layers/cv_neural_net.py @@ -138,7 +138,7 @@ def shape(n_layers, n_wires): Args: n_layers (int): number of layers - n_wires (int): number of qubits + n_wires (int): number of wires Returns: list[tuple[int]]: list of shapes diff --git a/pennylane/templates/layers/random.py b/pennylane/templates/layers/random.py index e168386f0bf..eea4aa453e3 100644 --- a/pennylane/templates/layers/random.py +++ b/pennylane/templates/layers/random.py @@ -1,4 +1,4 @@ -# Copyright 2018-2020 Xanadu Quantum Technologies Inc. +# Copyright 2018-2021 Xanadu Quantum Technologies Inc. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,71 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. r""" -Contains the ``RandomLayers`` template. +Contains the RandomLayers template. """ # pylint: disable-msg=too-many-branches,too-many-arguments,protected-access import numpy as np import pennylane as qml -from pennylane.templates.decorator import template -from pennylane.ops import CNOT, RX, RY, RZ -from pennylane.wires import Wires +from pennylane.operation import Operation, AnyWires -def _preprocess(weights): - """Validate and pre-process inputs as follows: - - * Check that the weights tensor is 2-dimensional. - - Args: - weights (tensor_like): trainable parameters of the template - - Returns: - int: number of times that the ansatz is repeated - """ - shape = qml.math.shape(weights) - - if len(shape) != 2: - raise ValueError(f"Weights tensor must be 2-dimensional; got shape {shape}") - - repeat = shape[0] - return repeat - - -def random_layer(weights, wires, ratio_imprim, imprimitive, rotations, seed): - r"""A single random layer. - - Args: - weights (tensor_like): tensor of weights of shape ``(k,)`` - wires (Wires): wires that the template acts on - ratio_imprim (float): value between 0 and 1 that determines the ratio of imprimitive to rotation gates - imprimitive (pennylane.ops.Operation): two-qubit gate to use, defaults to :class:`~pennylane.ops.CNOT` - rotations (list[pennylane.ops.Operation]): List of Pauli-X, Pauli-Y and/or Pauli-Z gates. The frequency - determines how often a particular rotation type is used. Defaults to the use of all three - rotations with equal frequency. - seed (int): seed to generate random architecture - """ - if seed is not None: - np.random.seed(seed) - - i = 0 - while i < len(weights): - if np.random.random() > ratio_imprim: - # Apply a random rotation gate to a random wire - gate = np.random.choice(rotations) - rnd_wire = wires.select_random(1) - - gate(weights[i], wires=rnd_wire) - - i += 1 - else: - # Apply the imprimitive to two random wires - if len(wires) > 1: - rnd_wires = wires.select_random(2) - imprimitive(wires=rnd_wires) - - -@template -def RandomLayers(weights, wires, ratio_imprim=0.3, imprimitive=CNOT, rotations=None, seed=42): +class RandomLayers(Operation): r"""Layers of randomly chosen single qubit rotations and 2-qubit entangling gates, acting on randomly chosen qubits. @@ -105,8 +49,7 @@ def RandomLayers(weights, wires, ratio_imprim=0.3, imprimitive=CNOT, rotations=N Args: weights (tensor_like): weight tensor of shape ``(L, k)``, - wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or - a Wires object. + wires (Iterable): wires that the template acts on ratio_imprim (float): value between 0 and 1 that determines the ratio of imprimitive to rotation gates imprimitive (pennylane.ops.Operation): two-qubit gate to use, defaults to :class:`~pennylane.ops.CNOT` rotations (list[pennylane.ops.Operation]): List of Pauli-X, Pauli-Y and/or Pauli-Z gates. The frequency @@ -114,9 +57,6 @@ def RandomLayers(weights, wires, ratio_imprim=0.3, imprimitive=CNOT, rotations=N rotations with equal frequency. seed (int): seed to generate random architecture, defaults to 42 - Raises: - ValueError: if inputs do not have the correct format - .. UsageDetails:: **Default seed** @@ -186,7 +126,7 @@ def circuit_12(weights): ... 1: ──╰C──RZ(0.1)───╰X──╰C───────────┤ - **Automatically creating random circuits** + **Automatic creation of random circuits** To automate the process of creating different circuits with ``RandomLayers``, you can set ``seed=None`` to avoid specifying a seed. However, in this case care needs @@ -221,22 +161,75 @@ def circuit_rnd(weights): >>> np.allclose(first_call, second_call) >>> True + + **Parameter shape** + + The expected shape for the weight tensor can be computed with the static method + :meth:`~.RandomLayers.shape` and used when creating randomly + initialised weight tensors: + + .. code-block:: python + + shape = RandomLayers.shape(n_layers=2, n_wires=2) + weights = np.random.random(size=shape) """ - if seed is not None: - np.random.seed(seed) - - if rotations is None: - rotations = [RX, RY, RZ] - - wires = Wires(wires) - repeat = _preprocess(weights) - - for l in range(repeat): - random_layer( - weights=weights[l], - wires=wires, - ratio_imprim=ratio_imprim, - imprimitive=imprimitive, - rotations=rotations, - seed=seed, - ) + + num_params = 1 + num_wires = AnyWires + par_domain = "A" + + def __init__(self, weights, wires, ratio_imprim=0.3, imprimitive=qml.CNOT, rotations=None, seed=42, do_queue=True): + + if seed is not None: + np.random.seed(seed) + + self.rotations = rotations or [qml.RX, qml.RY, qml.RZ] + + shape = qml.math.shape(weights) + if len(shape) != 2: + raise ValueError(f"Weights tensor must be 2-dimensional; got shape {shape}") + + self.n_layers = shape[0] + self.imprimitive = imprimitive + self.ratio_imprimitive = ratio_imprim + + super().__init__(weights, wires=wires, do_queue=do_queue) + + def expand(self): + + shape = qml.math.shape(self.parameters[0]) + + with qml.tape.QuantumTape() as tape: + + for l in range(self.n_layers): + + i = 0 + while i < shape[0]: + if np.random.random() > self.ratio_imprim: + # Apply a random rotation gate to a random wire + gate = np.random.choice(self.rotations) + rnd_wire = self.wires.select_random(1) + + gate(self.parameters[0][i], wires=rnd_wire) + + i += 1 + else: + # Apply the imprimitive to two random wires + if len(self.wires) > 1: + rnd_wires = self.wires.select_random(2) + self.imprimitive(wires=rnd_wires) + return tape + + @staticmethod + def shape(n_layers, n_rotations): + r"""Returns the expected shape of the weights tensor. + + Args: + n_layers (int): number of layers + n_rotations (int): number of rotations + + Returns: + tuple[int]: shape + """ + + return n_layers, n_rotations \ No newline at end of file diff --git a/pennylane/templates/layers/simplified_two_design.py b/pennylane/templates/layers/simplified_two_design.py index bcf7ba87b6f..3805726ce8d 100644 --- a/pennylane/templates/layers/simplified_two_design.py +++ b/pennylane/templates/layers/simplified_two_design.py @@ -1,4 +1,4 @@ -# Copyright 2018-2020 Xanadu Quantum Technologies Inc. +# Copyright 2018-2021 Xanadu Quantum Technologies Inc. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,67 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. r""" -Contains the ``SimplifiedTwoDesign`` template. +Contains the SimplifiedTwoDesign template. """ # pylint: disable-msg=too-many-branches,too-many-arguments,protected-access import pennylane as qml -from pennylane.templates.decorator import template -from pennylane.ops import CZ, RY -from pennylane.templates import broadcast -from pennylane.wires import Wires +from pennylane.operation import Operation, AnyWires -def _preprocess(weights, initial_layer_weights, wires): - """Validate and pre-process inputs as follows: - - * Check the shapes of the two weights tensors. - - Args: - weights (tensor_like): trainable parameters of the template - initial_layer_weights (tensor_like): weight tensor for the initial rotation block, shape ``(M,)`` - wires (Wires): wires that template acts on - - Returns: - int: number of times that the ansatz is repeated - """ - shape = qml.math.shape(weights) - repeat = shape[0] - - if len(shape) > 1: - if shape[1] != len(wires) - 1: - raise ValueError( - f"Weights tensor must have second dimension of length {len(wires) - 1}; got {shape[1]}" - ) - - if shape[2] != 2: - raise ValueError( - f"Weights tensor must have third dimension of length 2; got {shape[2]}" - ) - - shape2 = qml.math.shape(initial_layer_weights) - if shape2 != (len(wires),): - raise ValueError(f"Initial layer weights must be of shape {(len(wires),)}; got {shape2}") - - return repeat - - -@template -def entangler(par1, par2, wires): - """Implements a two qubit unitary consisting of a controlled-Z entangler and Pauli-Y rotations. - - Args: - par1 (float): parameter of first Pauli-Y rotation - par2 (float): parameter of second Pauli-Y rotation - wires (Wires): two wire indices that unitary acts on - """ - - CZ(wires=wires) - RY(par1, wires=wires[0]) - RY(par2, wires=wires[1]) - - -@template -def SimplifiedTwoDesign(initial_layer_weights, weights, wires): +class SimplifiedTwoDesign(Operation): r""" Layers consisting of a simplified 2-design architecture of Pauli-Y rotations and controlled-Z entanglers proposed in `Cerezo et al. (2020) `_. @@ -108,11 +55,8 @@ def SimplifiedTwoDesign(initial_layer_weights, weights, wires): Args: initial_layer_weights (tensor_like): weight tensor for the initial rotation block, shape ``(M,)`` weights (tensor_like): tensor of rotation angles for the layers, shape ``(L, M-1, 2)`` - wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or - a Wires object. + wires (Iterable): wires that the template acts on - Raises: - ValueError: if inputs do not have the correct format .. UsageDetails:: @@ -142,43 +86,82 @@ def circuit(init_weights, weights): >>> circuit(init_weights, weights) [1., -1., 1.] - **Parameter initialization function** + **Parameter shapes** - The :mod:`~pennylane.init` module contains four parameter initialization functions: + A list of shapes for the two weights arguments can be computed with the static method + :meth:`~.SimplifiedTwoDesign.shape` and used when creating randomly + initialised weight tensors: - * ``simplified_two_design_initial_layer_normal`` - * ``simplified_two_design_initial_layer_uniform`` - * ``simplified_two_design_weights_normal``. - * ``simplified_two_design_weights_uniform``. + .. code-block:: python - They can be used as follows: + shapes = SimplifiedTwoDesign.shape(n_layers=2, n_wires=2) + weights = [np.random.random(size=shape) for shape in shapes] - .. code-block:: python + """ + num_params = 2 + num_wires = AnyWires + par_domain = "A" - from pennylane.init import (simplified_two_design_initial_layer_normal, - simplified_two_design_weights_normal) + def __init__(self, initial_layer_weights, weights, wires, do_queue=True): - n_layers = 4 - init_weights = simplified_two_design_initial_layer_normal(n_wires) - weights = simplified_two_design_weights_normal(n_layers, n_wires) + shape = qml.math.shape(weights) - >>> circuit(initial_layer_weights, weights) + if len(shape) > 1: + if shape[1] != len(wires) - 1: + raise ValueError( + f"Weights tensor must have second dimension of length {len(wires) - 1}; got {shape[1]}" + ) - """ + if shape[2] != 2: + raise ValueError( + f"Weights tensor must have third dimension of length 2; got {shape[2]}" + ) + + shape2 = qml.math.shape(initial_layer_weights) + if shape2 != (len(wires),): + raise ValueError(f"Initial layer weights must be of shape {(len(wires),)}; got {shape2}") + + self.n_layers = shape[0] + + super().__init__(initial_layer_weights, weights, wires=wires, do_queue=do_queue) + + def expand(self): + + with qml.tape.QuantumTape() as tape: + + # initial rotations + for i in range(len(self.wires)): + qml.RY(self.parameters[0][i], self.wires[i]) + + for layer in range(self.n_layers): + + # even layer of entanglers + even_wires = [self.wires[i: i + 2] for i in range(0, len(self.wires) - 1, 2)] + for i, wire_pair in enumerate(even_wires): + qml.CZ(wires=wire_pair) + qml.RY(self.parameters[1][layer, i, 0], wires=wire_pair[0]) + qml.RY(self.parameters[1][layer, i, 1], wires=wire_pair[1]) + + # odd layer of entanglers + odd_wires = [self.wires[i: i + 2] for i in range(1, len(self.wires) - 1, 2)] + for i, wire_pair in enumerate(odd_wires): + qml.CZ(wires=wire_pair) + qml.RY(self.parameters[1][layer, len(self.wires) // 2 + i, 0], wires=wire_pair[0]) + qml.RY(self.parameters[1][layer, len(self.wires) // 2 + i, 1], wires=wire_pair[1]) - wires = Wires(wires) - repeat = _preprocess(weights, initial_layer_weights, wires) + return tape - # initial rotations - broadcast(unitary=RY, pattern="single", wires=wires, parameters=initial_layer_weights) + @staticmethod + def shape(n_layers, n_wires): + r"""Returns a list of shapes for the 2 parameter tensors. - # alternate layers - for layer in range(repeat): + Args: + n_layers (int): number of layers + n_wires (int): number of wires - # even layer - weights_even = weights[layer][: len(wires) // 2] - broadcast(unitary=entangler, pattern="double", wires=wires, parameters=weights_even) + Returns: + list[tuple[int]]: list of shapes + """ - # odd layer - weights_odd = weights[layer][len(wires) // 2 :] - broadcast(unitary=entangler, pattern="double_odd", wires=wires, parameters=weights_odd) + shapes = [(n_layers, n_wires), (n_layers, n_wires-1, 2)] + return shapes diff --git a/pennylane/templates/layers/strongly_entangling.py b/pennylane/templates/layers/strongly_entangling.py index 1c65dab1de6..efe3ef92817 100644 --- a/pennylane/templates/layers/strongly_entangling.py +++ b/pennylane/templates/layers/strongly_entangling.py @@ -12,75 +12,14 @@ # See the License for the specific language governing permissions and # limitations under the License. r""" -Contains the ``StronglyEntanglingLayers`` template. +Contains the StronglyEntanglingLayers template. """ # pylint: disable-msg=too-many-branches,too-many-arguments,protected-access import pennylane as qml -from pennylane.templates.decorator import template -from pennylane.ops import CNOT, Rot -from pennylane.templates import broadcast -from pennylane.wires import Wires +from pennylane.operation import Operation, AnyWires -def _preprocess(weights, wires, ranges): - """Validate and pre-process inputs as follows: - - * Check the shape of the weights tensor. - * If ranges is None, define a default. - - Args: - weights (tensor_like): trainable parameters of the template - wires (Wires): wires that template acts on - ranges (Sequence[int]): range for each subsequent layer - - Returns: - int, list[int]: number of times that the ansatz is repeated and preprocessed ranges - """ - shape = qml.math.shape(weights) - repeat = shape[0] - - if len(shape) != 3: - raise ValueError(f"Weights tensor must be 3-dimensional; got shape {shape}") - - if shape[1] != len(wires): - raise ValueError( - f"Weights tensor must have second dimension of length {len(wires)}; got {shape[1]}" - ) - - if shape[2] != 3: - raise ValueError(f"Weights tensor must have third dimension of length 3; got {shape[2]}") - - if len(wires) > 1: - if ranges is None: - # tile ranges with iterations of range(1, n_wires) - ranges = [(l % (len(wires) - 1)) + 1 for l in range(repeat)] - else: - ranges = [0] * repeat - - return repeat, ranges - - -def strongly_entangling_layer(weights, wires, r, imprimitive): - r"""A layer applying rotations on each qubit followed by cascades of 2-qubit entangling gates. - - Args: - weights (tensor_like): weight tensor of shape ``(len(wires), 3)`` - wires (Wires): wires that the template acts on - r (int): range of the imprimitive gates of this layer, defaults to 1 - imprimitive (pennylane.ops.Operation): two-qubit gate to use, defaults to :class:`~pennylane.ops.CNOT` - """ - - broadcast(unitary=Rot, pattern="single", wires=wires, parameters=weights) - - n_wires = len(wires) - if n_wires > 1: - for i in range(n_wires): - act_on = wires.subset([i, i + r], periodic_boundary=True) - imprimitive(wires=act_on) - - -@template -def StronglyEntanglingLayers(weights, wires, ranges=None, imprimitive=CNOT): +class StronglyEntanglingLayers(Operation): r"""Layers consisting of single qubit rotations and entanglers, inspired by the circuit-centric classifier design `arXiv:1804.00633 `_. @@ -100,23 +39,88 @@ def StronglyEntanglingLayers(weights, wires, ranges=None, imprimitive=CNOT): :width: 60% :target: javascript:void(0); + .. note:: + The two-qubit gate used as the imprimitive or entangler must not depend on parameters. + Args: weights (tensor_like): weight tensor of shape ``(L, M, 3)`` - wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or - a Wires object. + wires (Iterable): wires that the template acts on ranges (Sequence[int]): sequence determining the range hyperparameter for each subsequent layer; if None using :math:`r=l \mod M` for the :math:`l`th layer and :math:`M` wires. imprimitive (pennylane.ops.Operation): two-qubit gate to use, defaults to :class:`~pennylane.ops.CNOT` - Raises: - ValueError: if inputs do not have the correct format + .. UsageDetails:: + + **Parameter shape** + + The expected shape for the weight tensor can be computed with the static method + :meth:`~.SimplifiedTwoDesign.shape` and used when creating randomly + initialised weight tensors: + + .. code-block:: python + + shape = StronglyEntanglingLayers.shape(n_layers=2, n_wires=2) + weights = np.random.random(size=shape) + """ - wires = Wires(wires) - repeat, ranges = _preprocess(weights, wires, ranges) + num_params = 1 + num_wires = AnyWires + par_domain = "A" + + def __init__(self, weights, wires, ranges=None, imprimitive=qml.CNOT, do_queue=True): + + shape = qml.math.shape(weights) + self.n_layers = shape[0] + + if len(shape) != 3: + raise ValueError(f"Weights tensor must be 3-dimensional; got shape {shape}") + + if shape[1] != len(wires): + raise ValueError( + f"Weights tensor must have second dimension of length {len(wires)}; got {shape[1]}" + ) + + if shape[2] != 3: + raise ValueError(f"Weights tensor must have third dimension of length 3; got {shape[2]}") + + if ranges is None: + if len(wires) > 1: + # tile ranges with iterations of range(1, n_wires) + self.ranges = [(l % (len(wires) - 1)) + 1 for l in range(self.n_layers)] + else: + self.ranges = [0] * self.n_layers + + self.imprimitive = imprimitive + + super().__init__(weights, wires=wires, do_queue=do_queue) + + def expand(self): + + with qml.tape.QuantumTape() as tape: + + for l in range(self.n_layers): + + for i in range(len(self.wires)): + qml.Rot(self.parameters[0][l, i], self.wires[i]) + + if len(self.wires) > 1: + for i in range(len(self.wires)): + act_on = self.wires.subset([i, i + self.ranges[l]], periodic_boundary=True) + self.imprimitive(wires=act_on) + + return tape + + @staticmethod + def shape(n_layers, n_wires): + r"""Returns the expected shape of the weights tensor. + + Args: + n_layers (int): number of layers + n_wires (int): number of wires - for l in range(repeat): + Returns: + tuple[int]: shape + """ - strongly_entangling_layer( - weights=weights[l], wires=wires, r=ranges[l], imprimitive=imprimitive - ) + return n_layers, n_wires, 3 diff --git a/pennylane/templates/subroutines/interferometer.py b/pennylane/templates/subroutines/interferometer.py index d5e27bd8562..c3dabde0e4f 100644 --- a/pennylane/templates/subroutines/interferometer.py +++ b/pennylane/templates/subroutines/interferometer.py @@ -12,51 +12,15 @@ # See the License for the specific language governing permissions and # limitations under the License. r""" -Contains the ``Interferometer`` template. +Contains the Interferometer template. """ import pennylane as qml # pylint: disable-msg=too-many-branches,too-many-arguments,protected-access -from pennylane.templates.decorator import template -from pennylane.ops import Beamsplitter, Rotation -from pennylane.wires import Wires +from pennylane.operation import Operation, AnyWires -def _preprocess(theta, phi, varphi, wires): - """Validate and pre-process inputs as follows: - - * Check the shape of the three weight tensors. - - Args: - theta (tensor_like): trainable parameters of the template - phi (tensor_like): trainable parameters of the template - varphi (tensor_like): trainable parameters of the template - wires (Wires): wires that the template acts on - - Returns: - tuple: shape of varphi tensor - """ - - n_wires = len(wires) - n_if = n_wires * (n_wires - 1) // 2 - - shape = qml.math.shape(theta) - if shape != (n_if,): - raise ValueError(f"Theta must be of shape {(n_if,)}; got {shape}.") - - shape = qml.math.shape(phi) - if shape != (n_if,): - raise ValueError(f"Phi must be of shape {(n_if,)}; got {shape}.") - - shape_varphi = qml.math.shape(varphi) - if shape_varphi != (n_wires,): - raise ValueError(f"Varphi must be of shape {(n_wires,)}; got {shape_varphi}.") - - return shape_varphi - - -@template -def Interferometer(theta, phi, varphi, wires, mesh="rectangular", beamsplitter="pennylane"): +class Interferometer2(Operation): r"""General linear interferometer, an array of beamsplitters and phase shifters. For :math:`M` wires, the general interferometer is specified by @@ -125,62 +89,103 @@ def Interferometer(theta, phi, varphi, wires, mesh="rectangular", beamsplitter=" theta (tensor_like): size :math:`(M(M-1)/2,)` tensor of transmittivity angles :math:`\theta` phi (tensor_like): size :math:`(M(M-1)/2,)` tensor of phase angles :math:`\phi` varphi (tensor_like): size :math:`(M,)` tensor of rotation angles :math:`\varphi` - wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or - a Wires object. + wires (Iterable): wires that the template acts on mesh (string): the type of mesh to use beamsplitter (str): if ``clements``, the beamsplitter convention from Clements et al. 2016 (https://dx.doi.org/10.1364/OPTICA.3.001460) is used; if ``pennylane``, the beamsplitter is implemented via PennyLane's ``Beamsplitter`` operation. - Raises: - ValueError: if inputs do not have the correct format """ - - wires = Wires(wires) - M = len(wires) - - shape_varphi = _preprocess(theta, phi, varphi, wires) - - if M == 1: - # the interferometer is a single rotation - Rotation(varphi[0], wires=wires[0]) - return - - n = 0 # keep track of free parameters - - if mesh == "rectangular": - # Apply the Clements beamsplitter array - # The array depth is N - for l in range(M): - for k, (w1, w2) in enumerate(zip(wires[:-1], wires[1:])): - # skip even or odd pairs depending on layer - if (l + k) % 2 != 1: - if beamsplitter == "clements": - Rotation(phi[n], wires=Wires(w1)) - Beamsplitter(theta[n], 0, wires=Wires([w1, w2])) - elif beamsplitter == "pennylane": - Beamsplitter(theta[n], phi[n], wires=Wires([w1, w2])) - else: - raise ValueError(f"did not recognize beamsplitter {beamsplitter}") - n += 1 - - elif mesh == "triangular": - # apply the Reck beamsplitter array - # The array depth is 2*N-3 - for l in range(2 * M - 3): - for k in range(abs(l + 1 - (M - 1)), M - 1, 2): - if beamsplitter == "clements": - Rotation(phi[n], wires=wires[k]) - Beamsplitter(theta[n], 0, wires=wires.subset([k, k + 1])) - elif beamsplitter == "pennylane": - Beamsplitter(theta[n], phi[n], wires=wires.subset([k, k + 1])) - else: - raise ValueError(f"did not recognize beamsplitter {beamsplitter} ") - n += 1 - else: - raise ValueError(f"did not recognize mesh {mesh}") - - # apply the final local phase shifts to all modes - for i in range(shape_varphi[0]): - act_on = wires[i] - Rotation(varphi[i], wires=act_on) + num_params = 3 + num_wires = AnyWires + par_domain = "A" + + def __init__(self, theta, phi, varphi, wires, mesh="rectangular", beamsplitter="pennylane", do_queue=True): + + n_wires = len(wires) + n_if = n_wires * (n_wires - 1) // 2 + + shape = qml.math.shape(theta) + if shape != (n_if,): + raise ValueError(f"Theta must be of shape {(n_if,)}; got {shape}.") + + shape = qml.math.shape(phi) + if shape != (n_if,): + raise ValueError(f"Phi must be of shape {(n_if,)}; got {shape}.") + + shape = qml.math.shape(varphi) + if shape != (n_wires,): + raise ValueError(f"Varphi must be of shape {(n_wires,)}; got {shape}.") + + self.mesh = mesh + self.beamsplitter = beamsplitter + super().__init__(theta, phi, varphi, wires=wires, do_queue=do_queue) + + def expand(self): + + theta = self.parameters[0] + phi = self.parameters[1] + varphi = self.parameters[2] + + with qml.tape.QuantumTape() as tape: + + if len(self.wires) == 1: + # the interferometer is a single rotation + qml.Rotation(varphi[0], wires=self.wires[0]) + return + + n = 0 # keep track of free parameters + + if self.mesh == "rectangular": + # Apply the Clements beamsplitter array + # The array depth is N + for l in range(len(self.wires)): + for k, (w1, w2) in enumerate(zip(self.wires[:-1], self.wires[1:])): + # skip even or odd pairs depending on layer + if (l + k) % 2 != 1: + if self.beamsplitter == "clements": + qml.Rotation(phi[n], wires=w1) + qml.Beamsplitter(theta[n], 0, wires=[w1, w2]) + elif self.beamsplitter == "pennylane": + qml.Beamsplitter(theta[n], phi[n], wires=[w1, w2]) + else: + raise ValueError(f"did not recognize beamsplitter {self.beamsplitter}") + n += 1 + + elif self.mesh == "triangular": + # apply the Reck beamsplitter array + # The array depth is 2*N-3 + for l in range(2 * len(self.wires) - 3): + for k in range(abs(l + 1 - (len(self.wires) - 1)), len(self.wires) - 1, 2): + if self.beamsplitter == "clements": + qml.Rotation(phi[n], wires=self.wires[k]) + qml.Beamsplitter(theta[n], 0, wires=self.wires[k, k + 1]) + elif self.beamsplitter == "pennylane": + qml.Beamsplitter(theta[n], phi[n], wires=self.wires[k, k + 1]) + else: + raise ValueError(f"did not recognize beamsplitter {self.beamsplitter} ") + n += 1 + else: + raise ValueError(f"did not recognize mesh {self.mesh}") + + # apply the final local phase shifts to all modes + for i in range(qml.math.shape(varphi)[0]): + act_on = self.wires[i] + qml.Rotation(varphi[i], wires=act_on) + + return tape + + @staticmethod + def shape(n_wires): + r"""Returns a list of shapes for the three parameter tensors. + + Args: + n_wires (int): number of wires + + Returns: + list[tuple[int]]: list of shapes + """ + n_if = n_wires * (n_wires - 1) // 2 + shapes = [(n_if,), (n_if,), (n_wires,)] + + return shapes From f291126f37361c0b4b269d11fc46bb780f8bf0f8 Mon Sep 17 00:00:00 2001 From: Maria Schuld Date: Thu, 25 Mar 2021 11:16:39 +0200 Subject: [PATCH 03/16] rewrite particle_conserving --- .../layers/particle_conserving_u1.py | 137 ++++++++---------- .../layers/particle_conserving_u2.py | 123 +++++++--------- 2 files changed, 112 insertions(+), 148 deletions(-) diff --git a/pennylane/templates/layers/particle_conserving_u1.py b/pennylane/templates/layers/particle_conserving_u1.py index 65defb1cdb7..62500cc9e5c 100644 --- a/pennylane/templates/layers/particle_conserving_u1.py +++ b/pennylane/templates/layers/particle_conserving_u1.py @@ -1,4 +1,4 @@ -# Copyright 2018-2020 Xanadu Quantum Technologies Inc. +# Copyright 2018-2021 Xanadu Quantum Technologies Inc. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,61 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. r""" -Contains the hardware efficient ``ParticleConservingU1`` template. +Contains the hardware-efficient ParticleConservingU1 template. """ +# pylint: disable-msg=too-many-branches,too-many-arguments,protected-access import numpy as np - import pennylane as qml - -# pylint: disable-msg=too-many-branches,too-many-arguments,protected-access -from pennylane.templates.decorator import template -from pennylane.ops import CNOT, CRot, PhaseShift, CZ -from pennylane.wires import Wires - - -def _preprocess(weights, wires, init_state): - """Validate and pre-process inputs as follows: - - * Check that the weights tensor has the correct shape. - * Extract a wire list for the subroutines of this template. - * Cast initial state to a numpy array. - - Args: - weights (tensor_like): trainable parameters of the template - wires (Wires): wires that template acts on - init_state (tensor_like): shape ``(len(wires),)`` tensor - - Returns: - int, list[Wires], array: number of times that the ansatz is repeated, wires pattern, - and preprocessed initial state - """ - if len(wires) < 2: - raise ValueError( - "This template requires the number of qubits to be greater than one;" - "got a wire sequence with {} elements".format(len(wires)) - ) - - shape = qml.math.shape(weights) - - if len(shape) != 3: - raise ValueError(f"Weights tensor must be 3-dimensional; got shape {shape}") - - if shape[1] != len(wires) - 1: - raise ValueError( - f"Weights tensor must have second dimension of length {len(wires) - 1}; got {shape[1]}" - ) - - if shape[2] != 2: - raise ValueError(f"Weights tensor must have third dimension of length 2; got {shape[2]}") - - repeat = shape[0] - - nm_wires = [wires.subset([l, l + 1]) for l in range(0, len(wires) - 1, 2)] - nm_wires += [wires.subset([l, l + 1]) for l in range(1, len(wires) - 1, 2)] - # we can extract the numpy representation here - # since init_state can never be differentiable - init_state = qml.math.toarray(init_state) - return repeat, nm_wires, init_state +from pennylane.operation import Operation, AnyWires def decompose_ua(phi, wires=None): @@ -88,20 +39,20 @@ def decompose_ua(phi, wires=None): Args: phi (float): angle :math:`\phi` defining the unitary :math:`U_A(\phi)` - wires (list[Wires]): the wires ``n`` and ``m`` the circuit acts on + wires (list[Iterable]): the wires ``n`` and ``m`` the circuit acts on """ n, m = wires - CZ(wires=wires) - CRot(-phi, np.pi, phi, wires=wires) + qml.CZ(wires=wires) + qml.CRot(-phi, np.pi, phi, wires=wires) # decomposition of C-PhaseShift(2*phi) gate - PhaseShift(-phi, wires=m) - CNOT(wires=wires) - PhaseShift(phi, wires=m) - CNOT(wires=wires) - PhaseShift(-phi, wires=n) + qml.PhaseShift(-phi, wires=m) + qml.CNOT(wires=wires) + qml.PhaseShift(phi, wires=m) + qml.CNOT(wires=wires) + qml.PhaseShift(-phi, wires=n) def u1_ex_gate(phi, theta, wires=None): @@ -113,7 +64,7 @@ def u1_ex_gate(phi, theta, wires=None): Args: phi (float): angle entering the unitary :math:`U_A(\phi)` theta (float): angle entering the rotation :math:`R(0, 2\theta, 0)` - wires (list[Wires]): the two wires ``n`` and ``m`` the circuit acts on + wires (list[Iterable]): the two wires ``n`` and ``m`` the circuit acts on """ # C-UA(phi) @@ -126,8 +77,7 @@ def u1_ex_gate(phi, theta, wires=None): decompose_ua(-phi, wires=wires) -@template -def ParticleConservingU1(weights, wires, init_state=None): +class ParticleConservingU1(Operation): r"""Implements the heuristic VQE ansatz for quantum chemistry simulations using the particle-conserving gate :math:`U_{1,\mathrm{ex}}` proposed by Barkoutsos *et al.* in `arXiv:1805.04340 `_. @@ -219,17 +169,13 @@ def ParticleConservingU1(weights, wires, init_state=None): circuit diagram. Args: - weights (array[float]): Array of weights of shape ``(D, M, 2)``. + weights (tensor_like): Array of weights of shape ``(D, M, 2)``. ``D`` is the number of entangler block layers and :math:`M=N-1` is the number of exchange gates :math:`U_{1,\mathrm{ex}}` per layer. - wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers - or strings, or a Wires object. - init_state (array[int]): length ``len(wires)`` vector representing the Hartree-Fock state + wires (Iterable): wires that the template acts on + init_state (tensor_like): iterable or shape ``(len(wires),)`` tensor representing the Hartree-Fock state used to initialize the wires - Raises: - ValueError: if inputs do not have the correct format - .. UsageDetails:: #. The number of wires :math:`N` has to be equal to the number of @@ -268,11 +214,48 @@ def ParticleConservingU1(weights, wires, init_state=None): print(cost_fn(params)) """ - wires = Wires(wires) - repeat, nm_wires, init_state = _preprocess(weights, wires, init_state) + num_params = 1 + num_wires = AnyWires + par_domain = "A" + + def __init__(self, weights, wires, init_state=None, do_queue=True): + + if len(wires) < 2: + raise ValueError( + "Expected the number of qubits to be greater than one;" + "got wires {}".format(wires) + ) + + shape = qml.math.shape(weights) + + if len(shape) != 3: + raise ValueError(f"Weights tensor must be 3-dimensional; got shape {shape}") + + if shape[1] != len(wires) - 1: + raise ValueError( + f"Weights tensor must have second dimension of length {len(wires) - 1}; got {shape[1]}" + ) + + if shape[2] != 2: + raise ValueError(f"Weights tensor must have third dimension of length 2; got {shape[2]}") + + self.n_layers = shape[0] + # we can extract the numpy representation here + # since init_state can never be differentiable + self.init_state = qml.math.toarray(init_state) + + super().__init__(weights, wires=wires, do_queue=do_queue) + + def expand(self): + + nm_wires = [self.wires[l: l + 2] for l in range(0, len(self.wires) - 1, 2)] + nm_wires += [self.wires[l: l + 2] for l in range(1, len(self.wires) - 1, 2)] + + with qml.tape.QuantumTape() as tape: - qml.BasisState(init_state, wires=wires) + qml.BasisState(self.init_state, wires=self.wires) - for l in range(repeat): - for i, wires_ in enumerate(nm_wires): - u1_ex_gate(weights[l][i][0], weights[l][i][1], wires=wires_) + for l in range(self.n_layers): + for i, wires_ in enumerate(nm_wires): + u1_ex_gate(self.parameters[0][l, i, 0], self.parameters[0][l, i, 1], wires=wires_) + return tape diff --git a/pennylane/templates/layers/particle_conserving_u2.py b/pennylane/templates/layers/particle_conserving_u2.py index 3cf17b7656b..46a4b53eda1 100644 --- a/pennylane/templates/layers/particle_conserving_u2.py +++ b/pennylane/templates/layers/particle_conserving_u2.py @@ -1,4 +1,4 @@ -# Copyright 2018-2020 Xanadu Quantum Technologies Inc. +# Copyright 2018-2021 Xanadu Quantum Technologies Inc. # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -12,59 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. r""" -Contains the hardware efficient ``ParticleConservingU2`` template. +Contains the hardware-efficient ParticleConservingU2 template. """ -import pennylane as qml - # pylint: disable-msg=too-many-branches,too-many-arguments,protected-access -from pennylane.templates.decorator import template -from pennylane.ops import CNOT, CRX, RZ -from pennylane.wires import Wires - - -def _preprocess(weights, wires, init_state): - """Validate and pre-process inputs as follows: - - * Check that the weights tensor has the correct shape. - * Extract a wire list for the subroutines of this template. - * Cast initial state to a numpy array. - - Args: - weights (tensor_like): trainable parameters of the template - wires (Wires): wires that template acts on - init_state (tensor_like): shape ``(len(wires),)`` tensor - - Returns: - int, list[Wires], array: number of times that the ansatz is repeated, wires pattern, - and preprocessed initial state - """ - - if len(wires) < 2: - raise ValueError( - "This template requires the number of qubits to be greater than one;" - "got a wire sequence with {} elements".format(len(wires)) - ) - - shape = qml.math.shape(weights) - - if len(shape) != 2: - raise ValueError(f"Weights tensor must be 2-dimensional; got shape {shape}") - - if shape[1] != 2 * len(wires) - 1: - raise ValueError( - f"Weights tensor must have a second dimension of length {2 * len(wires) - 1}; got {shape[1]}" - ) - - repeat = shape[0] - - nm_wires = [wires.subset([l, l + 1]) for l in range(0, len(wires) - 1, 2)] - nm_wires += [wires.subset([l, l + 1]) for l in range(1, len(wires) - 1, 2)] - - # we can extract the numpy representation here - # since init_state can never be differentiable - init_state = qml.math.toarray(init_state) - - return repeat, nm_wires, init_state +import pennylane as qml +from pennylane.operation import Operation, AnyWires def u2_ex_gate(phi, wires=None): @@ -88,13 +40,12 @@ def u2_ex_gate(phi, wires=None): wires (list[Wires]): the two wires ``n`` and ``m`` the circuit acts on """ - CNOT(wires=wires) - CRX(2 * phi, wires=wires[::-1]) - CNOT(wires=wires) + qml.CNOT(wires=wires) + qml.CRX(2 * phi, wires=wires[::-1]) + qml.CNOT(wires=wires) -@template -def ParticleConservingU2(weights, wires, init_state=None): +class ParticleConservingU2(Operation): r"""Implements the heuristic VQE ansatz for Quantum Chemistry simulations using the particle-conserving entangler :math:`U_\mathrm{ent}(\vec{\theta}, \vec{\phi})` proposed in `arXiv:1805.04340 `_. @@ -142,14 +93,10 @@ def ParticleConservingU2(weights, wires, init_state=None): weights (tensor_like): Weight tensor of shape ``(D, M)`` where ``D`` is the number of layers and ``M`` = ``2N-1`` is the total number of rotation ``(N)`` and exchange ``(N-1)`` gates per layer. - wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers - or strings, or a Wires object. - init_state (tensor_like): shape ``(len(wires),)`` tensor representing the Hartree-Fock state + wires (Iterable): wires that the template acts on + init_state (tensor_like): iterable or shape ``(len(wires),)`` tensor representing the Hartree-Fock state used to initialize the wires. - Raises: - ValueError: if inputs do not have the correct format - .. UsageDetails:: @@ -189,15 +136,49 @@ def ParticleConservingU2(weights, wires, init_state=None): print(cost_fn(params)) """ - wires = Wires(wires) - repeat, nm_wires, init_state = _preprocess(weights, wires, init_state) + num_params = 1 + num_wires = AnyWires + par_domain = "A" + + def __init__(self, weights, wires, init_state=None, do_queue=True): + + if len(wires) < 2: + raise ValueError( + "This template requires the number of qubits to be greater than one;" + "got a wire sequence with {} elements".format(len(wires)) + ) + + shape = qml.math.shape(weights) + + if len(shape) != 2: + raise ValueError(f"Weights tensor must be 2-dimensional; got shape {shape}") + + if shape[1] != 2 * len(wires) - 1: + raise ValueError( + f"Weights tensor must have a second dimension of length {2 * len(wires) - 1}; got {shape[1]}" + ) + + self.n_layers = shape[0] + # we can extract the numpy representation here + # since init_state can never be differentiable + self.init_state = qml.math.toarray(init_state) + + super().__init__(weights, wires=wires, do_queue=do_queue) + + def expand(self): + + nm_wires = [self.wires[l: l + 2] for l in range(0, len(self.wires) - 1, 2)] + nm_wires += [self.wires[l: l + 2] for l in range(1, len(self.wires) - 1, 2)] + + with qml.tape.QuantumTape() as tape: - qml.BasisState(init_state, wires=wires) + qml.BasisState(self.init_state, wires=self.wires) - for l in range(repeat): + for l in range(self.n_layers): - for j, _ in enumerate(wires): - RZ(weights[l, j], wires=wires[j]) + for j, _ in enumerate(self.wires): + qml.RZ(self.parameters[0][l, j], wires=self.wires[j]) - for i, wires_ in enumerate(nm_wires): - u2_ex_gate(weights[l, len(wires) + i], wires=wires_) + for i, wires_ in enumerate(nm_wires): + u2_ex_gate(self.parameters[0][l, len(self.wires) + i], wires=wires_) + return tape From 0acca6a652df8a18b2f634f3f167ec599f2eef63 Mon Sep 17 00:00:00 2001 From: Maria Schuld Date: Thu, 25 Mar 2021 15:42:37 +0200 Subject: [PATCH 04/16] add tests for simplified and stronglyent --- .../layers/particle_conserving_u2.py | 2 +- pennylane/templates/layers/random.py | 26 +- .../templates/layers/simplified_two_design.py | 8 +- .../templates/layers/strongly_entangling.py | 6 +- .../templates/subroutines/interferometer.py | 2 +- tests/templates/test_layers.py | 1 - .../test_layers/test_basic_entangler.py | 5 +- tests/templates/test_layers/test_random.py | 317 +++++++++++++++++ .../test_layers/test_simplified_twodesign.py | 325 ++++++++++++++++++ .../test_layers/test_strongly_entangling.py | 264 ++++++++++++++ ...v_neural_net.py => ttest_cv_neural_net.py} | 0 11 files changed, 932 insertions(+), 24 deletions(-) create mode 100644 tests/templates/test_layers/test_random.py create mode 100644 tests/templates/test_layers/test_simplified_twodesign.py create mode 100644 tests/templates/test_layers/test_strongly_entangling.py rename tests/templates/test_layers/{test_cv_neural_net.py => ttest_cv_neural_net.py} (100%) diff --git a/pennylane/templates/layers/particle_conserving_u2.py b/pennylane/templates/layers/particle_conserving_u2.py index 46a4b53eda1..c6e86e0733d 100644 --- a/pennylane/templates/layers/particle_conserving_u2.py +++ b/pennylane/templates/layers/particle_conserving_u2.py @@ -139,7 +139,7 @@ class ParticleConservingU2(Operation): num_params = 1 num_wires = AnyWires par_domain = "A" - + def __init__(self, weights, wires, init_state=None, do_queue=True): if len(wires) < 2: diff --git a/pennylane/templates/layers/random.py b/pennylane/templates/layers/random.py index eea4aa453e3..9079bfd6f35 100644 --- a/pennylane/templates/layers/random.py +++ b/pennylane/templates/layers/random.py @@ -170,7 +170,7 @@ def circuit_rnd(weights): .. code-block:: python - shape = RandomLayers.shape(n_layers=2, n_wires=2) + shape = RandomLayers.shape(n_layers=2, n_rotations=3) weights = np.random.random(size=shape) """ @@ -178,11 +178,9 @@ def circuit_rnd(weights): num_wires = AnyWires par_domain = "A" - def __init__(self, weights, wires, ratio_imprim=0.3, imprimitive=qml.CNOT, rotations=None, seed=42, do_queue=True): - - if seed is not None: - np.random.seed(seed) + def __init__(self, weights, wires, ratio_imprim=0.3, imprimitive=None, rotations=None, seed=42, do_queue=True): + self.seed = seed self.rotations = rotations or [qml.RX, qml.RY, qml.RZ] shape = qml.math.shape(weights) @@ -190,13 +188,16 @@ def __init__(self, weights, wires, ratio_imprim=0.3, imprimitive=qml.CNOT, rotat raise ValueError(f"Weights tensor must be 2-dimensional; got shape {shape}") self.n_layers = shape[0] - self.imprimitive = imprimitive + self.imprimitive = imprimitive or qml.CNOT self.ratio_imprimitive = ratio_imprim super().__init__(weights, wires=wires, do_queue=do_queue) def expand(self): + if self.seed is not None: + np.random.seed(self.seed) + shape = qml.math.shape(self.parameters[0]) with qml.tape.QuantumTape() as tape: @@ -205,16 +206,15 @@ def expand(self): i = 0 while i < shape[0]: - if np.random.random() > self.ratio_imprim: - # Apply a random rotation gate to a random wire + if np.random.random() > self.ratio_imprimitive: + # apply a random rotation gate to a random wire gate = np.random.choice(self.rotations) rnd_wire = self.wires.select_random(1) - - gate(self.parameters[0][i], wires=rnd_wire) - + gate(self.parameters[0][l, i], wires=rnd_wire) i += 1 + else: - # Apply the imprimitive to two random wires + # apply the entangler to two random wires if len(self.wires) > 1: rnd_wires = self.wires.select_random(2) self.imprimitive(wires=rnd_wires) @@ -232,4 +232,4 @@ def shape(n_layers, n_rotations): tuple[int]: shape """ - return n_layers, n_rotations \ No newline at end of file + return n_layers, n_rotations diff --git a/pennylane/templates/layers/simplified_two_design.py b/pennylane/templates/layers/simplified_two_design.py index 3805726ce8d..6f761a4d8ee 100644 --- a/pennylane/templates/layers/simplified_two_design.py +++ b/pennylane/templates/layers/simplified_two_design.py @@ -131,7 +131,7 @@ def expand(self): # initial rotations for i in range(len(self.wires)): - qml.RY(self.parameters[0][i], self.wires[i]) + qml.RY(self.parameters[0][i], wires=self.wires[i]) for layer in range(self.n_layers): @@ -163,5 +163,7 @@ def shape(n_layers, n_wires): list[tuple[int]]: list of shapes """ - shapes = [(n_layers, n_wires), (n_layers, n_wires-1, 2)] - return shapes + if n_wires == 1: + return [(n_wires,), (n_layers,)] + + return [(n_wires,), (n_layers, n_wires-1, 2)] diff --git a/pennylane/templates/layers/strongly_entangling.py b/pennylane/templates/layers/strongly_entangling.py index efe3ef92817..2876004e9eb 100644 --- a/pennylane/templates/layers/strongly_entangling.py +++ b/pennylane/templates/layers/strongly_entangling.py @@ -68,7 +68,7 @@ class StronglyEntanglingLayers(Operation): num_wires = AnyWires par_domain = "A" - def __init__(self, weights, wires, ranges=None, imprimitive=qml.CNOT, do_queue=True): + def __init__(self, weights, wires, ranges=None, imprimitive=None, do_queue=True): shape = qml.math.shape(weights) self.n_layers = shape[0] @@ -91,7 +91,7 @@ def __init__(self, weights, wires, ranges=None, imprimitive=qml.CNOT, do_queue=T else: self.ranges = [0] * self.n_layers - self.imprimitive = imprimitive + self.imprimitive = imprimitive or qml.CNOT super().__init__(weights, wires=wires, do_queue=do_queue) @@ -102,7 +102,7 @@ def expand(self): for l in range(self.n_layers): for i in range(len(self.wires)): - qml.Rot(self.parameters[0][l, i], self.wires[i]) + qml.Rot(self.parameters[0][l, i, 0], self.parameters[0][l, i, 1], self.parameters[0][l, i, 2], wires=self.wires[i]) if len(self.wires) > 1: for i in range(len(self.wires)): diff --git a/pennylane/templates/subroutines/interferometer.py b/pennylane/templates/subroutines/interferometer.py index c3dabde0e4f..035ea741248 100644 --- a/pennylane/templates/subroutines/interferometer.py +++ b/pennylane/templates/subroutines/interferometer.py @@ -20,7 +20,7 @@ from pennylane.operation import Operation, AnyWires -class Interferometer2(Operation): +class Interferometer(Operation): r"""General linear interferometer, an array of beamsplitters and phase shifters. For :math:`M` wires, the general interferometer is specified by diff --git a/tests/templates/test_layers.py b/tests/templates/test_layers.py index cf342cd2579..81982ccca96 100644 --- a/tests/templates/test_layers.py +++ b/tests/templates/test_layers.py @@ -28,7 +28,6 @@ ParticleConservingU2, ParticleConservingU1, ) -from pennylane.templates.layers.random import random_layer from pennylane import RX, RY, RZ, CZ, CNOT from pennylane.wires import Wires from pennylane.numpy import tensor diff --git a/tests/templates/test_layers/test_basic_entangler.py b/tests/templates/test_layers/test_basic_entangler.py index a9dbd9fafe3..b0888d086a9 100644 --- a/tests/templates/test_layers/test_basic_entangler.py +++ b/tests/templates/test_layers/test_basic_entangler.py @@ -168,8 +168,9 @@ def test_list_and_tuples(self, tol): res2 = circuit2(weights) assert qml.math.allclose(res, res2, atol=tol, rtol=0) - res = circuit(tuple(weights)) - res2 = circuit2(tuple(weights)) + weights_tuple = tuple([tuple(weights[0])]) + res = circuit(weights_tuple) + res2 = circuit2(weights_tuple) assert qml.math.allclose(res, res2, atol=tol, rtol=0) def test_autograd(self, tol): diff --git a/tests/templates/test_layers/test_random.py b/tests/templates/test_layers/test_random.py new file mode 100644 index 00000000000..f73f3d7acfb --- /dev/null +++ b/tests/templates/test_layers/test_random.py @@ -0,0 +1,317 @@ +# Copyright 2018-2021 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Unit tests for the RandomLayers template. +""" +import pytest +import numpy as np +import pennylane as qml +from pennylane import numpy as pnp + + +class TestDecomposition: + """Tests that the template defines the correct decomposition.""" + + def test_seed(self): + """Test that the circuit is fixed by the seed.""" + weights = [[0.1, 0.2, 0.3]] + + op1 = qml.templates.RandomLayers(weights, wires=range(2), seed=41) + op2 = qml.templates.RandomLayers(weights, wires=range(2), seed=42) + op3 = qml.templates.RandomLayers(weights, wires=range(2), seed=42) + + ops1 = op1.expand().operations + ops2 = op2.expand().operations + ops3 = op3.expand().operations + + assert ops1 != ops2 + assert ops2 == ops3 + + def test_random_layers_nlayers(self, n_layers): + """Test that the correct number of gates.""" + np.random.seed(12) + n_rots = 1 + n_wires = 2 + impr = qml.CNOT + weights = np.random.randn(n_layers, n_rots) + + op = qml.templates.RandomLayers(weights, wires=range(n_wires)) + ops = op.expand().operations + + types = [type(o) for o in ops] + assert len(types) - types.count(impr) == n_layers + + @pytest.mark.parametrize("ratio", [0.2, 0.6]) + def test_random_layer_ratio_imprimitive(self, ratio): + """Test the ratio of imprimitive gates.""" + n_rots = 500 + n_wires = 2 + weights = np.random.random(size=(1, n_rots)) + + op = qml.templates.RandomLayers(weights, wires=range(n_wires), ratio_imprim=ratio) + ops = op.expand().operations + + gate_names = [g.name for g in ops] + ratio_impr = gate_names.count("CNOT") / len(gate_names) + assert np.isclose(ratio_impr, ratio, atol=0.05) + + +# QUEUES = [ +# (1, (1, 1), ["RX"], [[0]]), +# (2, (1, 2), ["RX", "RX", "CNOT"], [[0], [1], [0, 1]]), +# (2, (2, 2), ["RX", "RX", "CNOT", "RX", "RX", "CNOT"], [[0], [1], [0, 1], [0], [1], [0, 1]]), +# (3, (1, 3), ["RX", "RX", "RX", "CNOT", "CNOT", "CNOT"], [[0], [1], [2], [0, 1], [1, 2], [2, 0]]), +# ] +# +# @pytest.mark.parametrize("n_wires, weight_shape, expected_names, expected_wires", QUEUES) +# def test_expansion(self, n_wires, weight_shape, expected_names, expected_wires): +# """Checks the queue for the default settings.""" +# +# weights = np.random.random(size=weight_shape) +# +# op = qml.templates.BasicEntanglerLayers(weights, wires=range(n_wires)) +# tape = op.expand() +# +# for i, gate in enumerate(tape.operations): +# assert gate.name == expected_names[i] +# assert gate.wires.labels == tuple(expected_wires[i]) +# +# @pytest.mark.parametrize("rotation", [qml.RY, qml.RZ]) +# def test_rotation(self, rotation): +# """Checks that custom rotation gate is used.""" +# +# weights = np.zeros(shape=(1, 2)) +# +# op = qml.templates.BasicEntanglerLayers(weights, wires=range(2), rotation=rotation) +# tape = op.expand() +# +# assert type(tape.operations[0]) == rotation +# assert type(tape.operations[1]) == rotation +# +# @pytest.mark.parametrize( +# "weights, n_wires, target", +# [ +# ([[np.pi]], 1, [-1]), +# ([[np.pi] * 2], 2, [-1, 1]), +# ([[np.pi] * 3], 3, [1, 1, -1]), +# ([[np.pi] * 4], 4, [-1, 1, -1, 1]), +# ], +# ) +# def test_simple_target_outputs(self, weights, n_wires, target, tol): +# """Tests the result of the template for simple cases.""" +# +# dev = qml.device("default.qubit", wires=n_wires) +# +# @qml.qnode(dev) +# def circuit(weights): +# qml.templates.BasicEntanglerLayers(weights, wires=range(n_wires)) +# return [qml.expval(qml.PauliZ(i)) for i in range(n_wires)] +# +# expectations = circuit(weights) +# assert np.allclose(expectations, target, atol=tol, rtol=0) +# +# def test_custom_wire_labels(self, tol): +# """Test that template can deal with non-numeric, nonconsecutive wire labels.""" +# weights = np.random.random(size=(1, 3)) +# +# dev = qml.device("default.qubit", wires=3) +# dev2 = qml.device("default.qubit", wires=["z", "a", "k"]) +# +# @qml.qnode(dev) +# def circuit(): +# qml.templates.BasicEntanglerLayers(weights, wires=range(3)) +# return qml.expval(qml.Identity(0)) +# +# @qml.qnode(dev2) +# def circuit2(): +# qml.templates.BasicEntanglerLayers(weights, wires=["z", "a", "k"]) +# return qml.expval(qml.Identity("z")) +# +# circuit() +# circuit2() +# +# assert np.allclose(dev.state, dev2.state, atol=tol, rtol=0) +# +# +# class TestInputs: +# """Test inputs and pre-processing.""" +# +# def test_exception_wrong_dim(self): +# """Verifies that exception is raised if the weights shape is incorrect.""" +# +# n_wires = 1 +# dev = qml.device("default.qubit", wires=n_wires) +# +# @qml.qnode(dev) +# def circuit(weights): +# qml.templates.BasicEntanglerLayers(weights=weights, wires=range(n_wires)) +# return [qml.expval(qml.PauliZ(i)) for i in range(n_wires)] +# +# with pytest.raises(ValueError, match="Weights tensor must be 2-dimensional"): +# circuit([1, 0]) +# +# with pytest.raises(ValueError, match="Weights tensor must have second dimension of length"): +# circuit([[1, 0], [1, 0]]) +# +# @pytest.mark.parametrize( +# "n_layers, n_wires, expected_shape", +# [ +# (2, 3, (2, 3)), +# (2, 1, (2, 1)), +# (2, 2, (2, 2)), +# ], +# ) +# def test_shape(self, n_layers, n_wires, expected_shape): +# """Test that the shape method returns the correct shape of the weights tensor""" +# +# shape = qml.templates.BasicEntanglerLayers.shape(n_layers, n_wires) +# assert shape == expected_shape +# +# +# def circuit_template(weights): +# qml.templates.BasicEntanglerLayers(weights, range(3)) +# return qml.expval(qml.PauliZ(0)) +# +# +# def circuit_decomposed(weights): +# qml.RX(weights[0, 0], wires=0) +# qml.RX(weights[0, 1], wires=1) +# qml.RX(weights[0, 2], wires=2) +# qml.CNOT(wires=[0, 1]) +# qml.CNOT(wires=[1, 2]) +# qml.CNOT(wires=[2, 0]) +# return qml.expval(qml.PauliZ(0)) +# +# +# class TestInterfaces: +# """Tests that the template is compatible with all interfaces, including the computation +# of gradients.""" +# +# def test_list_and_tuples(self, tol): +# """Tests common iterables as inputs.""" +# +# weights = [[0.1, -1.1, 0.2]] +# +# dev = qml.device("default.qubit", wires=3) +# +# circuit = qml.QNode(circuit_template, dev) +# circuit2 = qml.QNode(circuit_decomposed, dev) +# +# res = circuit(weights) +# res2 = circuit2(weights) +# assert qml.math.allclose(res, res2, atol=tol, rtol=0) +# +# res = circuit(tuple(weights)) +# res2 = circuit2(tuple(weights)) +# assert qml.math.allclose(res, res2, atol=tol, rtol=0) +# +# def test_autograd(self, tol): +# """Tests the autograd interface.""" +# +# weights = np.random.random(size=(1, 3)) +# weights = pnp.array(weights, requires_grad=True) +# +# dev = qml.device("default.qubit", wires=3) +# +# circuit = qml.QNode(circuit_template, dev) +# circuit2 = qml.QNode(circuit_decomposed, dev) +# +# res = circuit(weights) +# res2 = circuit2(weights) +# assert qml.math.allclose(res, res2, atol=tol, rtol=0) +# +# grad_fn = qml.grad(circuit) +# grads = grad_fn(weights) +# +# grad_fn2 = qml.grad(circuit2) +# grads2 = grad_fn2(weights) +# +# assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) +# +# def test_jax(self, tol, skip_if_no_jax_support): +# """Tests the jax interface.""" +# +# import jax +# import jax.numpy as jnp +# +# weights = jnp.array(np.random.random(size=(1, 3))) +# +# dev = qml.device("default.qubit", wires=3) +# +# circuit = qml.QNode(circuit_template, dev, interface="jax") +# circuit2 = qml.QNode(circuit_decomposed, dev, interface="jax") +# +# res = circuit(weights) +# res2 = circuit2(weights) +# assert qml.math.allclose(res, res2, atol=tol, rtol=0) +# +# grad_fn = jax.grad(circuit) +# grads = grad_fn(weights) +# +# grad_fn2 = jax.grad(circuit2) +# grads2 = grad_fn2(weights) +# +# assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) +# +# def test_tf(self, tol, skip_if_no_tf_support): +# """Tests the tf interface.""" +# +# import tensorflow as tf +# +# weights = tf.Variable(np.random.random(size=(1, 3))) +# +# dev = qml.device("default.qubit", wires=3) +# +# circuit = qml.QNode(circuit_template, dev, interface="tf") +# circuit2 = qml.QNode(circuit_decomposed, dev, interface="tf") +# +# res = circuit(weights) +# res2 = circuit2(weights) +# assert qml.math.allclose(res, res2, atol=tol, rtol=0) +# +# with tf.GradientTape() as tape: +# res = circuit(weights) +# grads = tape.gradient(res, [weights]) +# +# with tf.GradientTape() as tape2: +# res2 = circuit2(weights) +# grads2 = tape2.gradient(res2, [weights]) +# +# assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) +# +# def test_torch(self, tol, skip_if_no_torch_support): +# """Tests the torch interface.""" +# +# import torch +# +# weights = torch.tensor(np.random.random(size=(1, 3)), requires_grad=True) +# +# dev = qml.device("default.qubit", wires=3) +# +# circuit = qml.QNode(circuit_template, dev, interface="torch") +# circuit2 = qml.QNode(circuit_decomposed, dev, interface="torch") +# +# res = circuit(weights) +# res2 = circuit2(weights) +# assert qml.math.allclose(res, res2, atol=tol, rtol=0) +# +# res = circuit(weights) +# res.backward() +# grads = [weights.grad] +# +# res2 = circuit2(weights) +# res2.backward() +# grads2 = [weights.grad] +# +# assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) diff --git a/tests/templates/test_layers/test_simplified_twodesign.py b/tests/templates/test_layers/test_simplified_twodesign.py new file mode 100644 index 00000000000..9d532d55c5b --- /dev/null +++ b/tests/templates/test_layers/test_simplified_twodesign.py @@ -0,0 +1,325 @@ +# Copyright 2018-2021 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Unit tests for the SimplifiedTwoDesign template. +""" +import pytest +import numpy as np +import pennylane as qml +from pennylane import numpy as pnp + + +class TestDecomposition: + """Tests that the template defines the correct decomposition.""" + + QUEUES = [ + (1, (1,), ["RY"], [[0]]), + (2, (1, 1, 2), ["RY", "RY", "CZ", "RY", "RY"], [[0], [1], [0, 1], [0], [1]]), + (2, (2, 1, 2), ["RY", "RY", "CZ", "RY", "RY", "CZ", "RY", "RY"], [[0], [1], [0, 1], [0], [1], [0, 1], [0], [1]]), + (3, (1, 2, 2), ["RY", "RY", "RY", "CZ", "RY", "RY", "CZ", "RY", "RY"], [[0], [1], [2], [0, 1], [0], [1], [1, 2], [1], [2]]), + ] + + @pytest.mark.parametrize("n_wires, weight_shape, expected_names, expected_wires", QUEUES) + def test_expansion(self, n_wires, weight_shape, expected_names, expected_wires): + """Checks the queue for the default settings.""" + + weights = np.random.random(size=weight_shape) + initial_layer = np.random.randn(n_wires) + + op = qml.templates.SimplifiedTwoDesign(initial_layer, weights, wires=range(n_wires)) + queue = op.expand().operations + + for i, gate in enumerate(queue): + assert gate.name == expected_names[i] + assert gate.wires.labels == tuple(expected_wires[i]) + + @pytest.mark.parametrize( + "n_wires, n_layers, shape_weights", + [(1, 2, (0,)), (2, 2, (2, 1, 2)), (3, 2, (2, 2, 2)), (4, 2, (2, 3, 2))], + ) + def test_circuit_parameters(self, n_wires, n_layers, shape_weights): + """Tests the parameter values in the circuit.""" + + initial_layer = np.random.randn(n_wires) + weights = np.random.randn(*shape_weights) + + op = qml.templates.SimplifiedTwoDesign(initial_layer, weights, wires=range(n_wires)) + queue = op.expand().operations + + # test the device parameters + for l in range(n_layers): + # only select the rotation gates + ops = [gate for gate in queue if isinstance(gate, qml.RY)] + + # check each initial_layer gate parameters + for n in range(n_wires): + res_param = ops[n].parameters[0] + exp_param = initial_layer[n] + assert res_param == exp_param + + # check layer gate parameters + ops = ops[n_wires:] + exp_params = weights.flatten() + for o, exp_param in zip(ops, exp_params): + res_param = o.parameters[0] + assert res_param == exp_param + + @pytest.mark.parametrize( + "initial_layer_weights, weights, n_wires, target", + [ + ([np.pi], [], 1, [-1]), + ([np.pi] * 2, [[[np.pi] * 2]], 2, [1, 1]), + ([np.pi] * 3, [[[np.pi] * 2] * 2], 3, [1, -1, 1]), + ([np.pi] * 4, [[[np.pi] * 2] * 3], 4, [1, -1, -1, 1]), + ], + ) + def test_correct_target_output(self, initial_layer_weights, weights, n_wires, target, tol): + """Tests the result of the template for simple cases.""" + dev = qml.device("default.qubit", wires=n_wires) + + @qml.qnode(dev) + def circuit(initial_layer, weights): + qml.templates.SimplifiedTwoDesign( + initial_layer_weights=initial_layer, weights=weights, wires=range(n_wires) + ) + return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_wires)] + + expectations = circuit(initial_layer_weights, weights) + for exp, target_exp in zip(expectations, target): + assert np.allclose(exp, target_exp, atol=tol, rtol=0) + + def test_custom_wire_labels(self, tol): + """Test that template can deal with non-numeric, nonconsecutive wire labels.""" + weights = np.random.random(size=(1, 2, 2)) + initial_layer = np.random.randn(3) + + dev = qml.device("default.qubit", wires=3) + dev2 = qml.device("default.qubit", wires=["z", "a", "k"]) + + @qml.qnode(dev) + def circuit(): + qml.templates.SimplifiedTwoDesign(initial_layer, weights, wires=range(3)) + return qml.expval(qml.Identity(0)) + + @qml.qnode(dev2) + def circuit2(): + qml.templates.SimplifiedTwoDesign(initial_layer, weights, wires=["z", "a", "k"]) + return qml.expval(qml.Identity("z")) + + circuit() + circuit2() + + assert np.allclose(dev.state, dev2.state, atol=tol, rtol=0) + + +class TestInputs: + """Test inputs and pre-processing.""" + + def test_exception_wrong_dim(self): + """Verifies that exception is raised if the + number of dimensions of features is incorrect.""" + + dev = qml.device("default.qubit", wires=4) + initial_layer = np.random.randn(2) + + @qml.qnode(dev) + def circuit(initial_layer, weights): + qml.templates.SimplifiedTwoDesign(initial_layer, weights, wires=range(2)) + return qml.expval(qml.PauliZ(0)) + + with pytest.raises(ValueError, match="Weights tensor must have second dimension"): + weights = np.random.randn(2, 2, 2) + circuit(initial_layer, weights) + + with pytest.raises(ValueError, match="Weights tensor must have third dimension"): + weights = np.random.randn(2, 1, 3) + circuit(initial_layer, weights) + + with pytest.raises(ValueError, match="Initial layer weights must be of shape"): + initial_layer = np.random.randn(3) + weights = np.random.randn(2, 1, 2) + circuit(initial_layer, weights) + + +class TestAttributes: + + @pytest.mark.parametrize( + "n_layers, n_wires, expected_shape", + [ + (2, 3, [(3,), (2, 2, 2)]), + (2, 1, [(1,), (2,)]), + (2, 2, [(2,), (2, 1, 2)]), + ], + ) + def test_shape(self, n_layers, n_wires, expected_shape): + """Test that the shape method returns the correct shape of the weights tensor""" + + shape = qml.templates.SimplifiedTwoDesign.shape(n_layers, n_wires) + assert shape == expected_shape + + +def circuit_template(initial_weights, weights): + qml.templates.SimplifiedTwoDesign(initial_weights, weights, range(3)) + return qml.expval(qml.PauliZ(0)) + + +def circuit_decomposed(initial_weights, weights): + qml.RY(initial_weights[0], wires=0) + qml.RY(initial_weights[1], wires=1) + qml.RY(initial_weights[2], wires=2) + + qml.CZ(wires=[0, 1]) + qml.RY(weights[0, 0, 0], wires=0) + qml.RY(weights[0, 0, 1], wires=1) + + qml.CZ(wires=[1, 2]) + qml.RY(weights[0, 1, 0], wires=1) + qml.RY(weights[0, 1, 1], wires=2) + + return qml.expval(qml.PauliZ(0)) + + +class TestInterfaces: + """Tests that the template is compatible with all interfaces, including the computation + of gradients.""" + + def test_list_and_tuples(self, tol): + """Tests common iterables as inputs.""" + + weights = [[[[0.1, -1.1], [0.2, 0.1]], [[0.1, -1.1], [0.2, 0.1]]]] + initial_weights = [0.1, 0.2, 0.3] + + dev = qml.device("default.qubit", wires=3) + + circuit = qml.QNode(circuit_template, dev) + circuit2 = qml.QNode(circuit_decomposed, dev) + + res = circuit(initial_weights, weights) + res2 = circuit2(initial_weights, weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + weights_tuple = [[tuple(weights[0][0]), tuple(weights[0][1])]] + init_weights_tuple = tuple(initial_weights) + res = circuit(init_weights_tuple, weights_tuple) + res2 = circuit2(init_weights_tuple, weights_tuple) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + def test_autograd(self, tol): + """Tests the autograd interface.""" + + weights = np.random.random(size=(1, 2, 2)) + weights = pnp.array(weights, requires_grad=True) + initial_weights = np.random.random(size=(3,)) + initial_weights = pnp.array(initial_weights, requires_grad=True) + + dev = qml.device("default.qubit", wires=3) + + circuit = qml.QNode(circuit_template, dev) + circuit2 = qml.QNode(circuit_decomposed, dev) + + res = circuit(initial_weights, weights) + res2 = circuit2(initial_weights, weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + grad_fn = qml.grad(circuit) + grads = grad_fn(initial_weights, weights) + + grad_fn2 = qml.grad(circuit2) + grads2 = grad_fn2(initial_weights, weights) + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + assert np.allclose(grads[1], grads2[1], atol=tol, rtol=0) + + def test_jax(self, tol, skip_if_no_jax_support): + """Tests the jax interface.""" + + import jax + import jax.numpy as jnp + + weights = jnp.array(np.random.random(size=(1, 2, 2))) + initial_weights = jnp.array(np.random.random(size=(3,))) + + dev = qml.device("default.qubit", wires=3) + + circuit = qml.QNode(circuit_template, dev, interface="jax") + circuit2 = qml.QNode(circuit_decomposed, dev, interface="jax") + + res = circuit(initial_weights, weights) + res2 = circuit2(initial_weights, weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + grad_fn = jax.grad(circuit) + grads = grad_fn(initial_weights, weights) + + grad_fn2 = jax.grad(circuit2) + grads2 = grad_fn2(initial_weights, weights) + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + assert np.allclose(grads[1], grads2[1], atol=tol, rtol=0) + + def test_tf(self, tol, skip_if_no_tf_support): + """Tests the tf interface.""" + + import tensorflow as tf + + weights = tf.Variable(np.random.random(size=(1, 2, 2))) + initial_weights = tf.Variable(np.random.random(size=(3,))) + + dev = qml.device("default.qubit", wires=3) + + circuit = qml.QNode(circuit_template, dev, interface="tf") + circuit2 = qml.QNode(circuit_decomposed, dev, interface="tf") + + res = circuit(initial_weights, weights) + res2 = circuit2(initial_weights, weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + with tf.GradientTape() as tape: + res = circuit(initial_weights, weights) + grads = tape.gradient(res, [initial_weights, weights]) + + with tf.GradientTape() as tape2: + res2 = circuit2(initial_weights, weights) + grads2 = tape2.gradient(res2, [initial_weights, weights]) + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + assert np.allclose(grads[1], grads2[1], atol=tol, rtol=0) + + def test_torch(self, tol, skip_if_no_torch_support): + """Tests the torch interface.""" + + import torch + + weights = torch.tensor(np.random.random(size=(1, 2, 2)), requires_grad=True) + initial_weights = torch.tensor(np.random.random(size=(3,)), requires_grad=True) + + dev = qml.device("default.qubit", wires=3) + + circuit = qml.QNode(circuit_template, dev, interface="torch") + circuit2 = qml.QNode(circuit_decomposed, dev, interface="torch") + + res = circuit(initial_weights, weights) + res2 = circuit2(initial_weights, weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + res = circuit(initial_weights, weights) + res.backward() + grads = [weights.grad, initial_weights.grad] + + res2 = circuit2(initial_weights, weights) + res2.backward() + grads2 = [weights.grad, initial_weights.grad] + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + assert np.allclose(grads[1], grads2[1], atol=tol, rtol=0) diff --git a/tests/templates/test_layers/test_strongly_entangling.py b/tests/templates/test_layers/test_strongly_entangling.py new file mode 100644 index 00000000000..973a714234d --- /dev/null +++ b/tests/templates/test_layers/test_strongly_entangling.py @@ -0,0 +1,264 @@ +# Copyright 2018-2021 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Unit tests for the StronglyEntanglingLayers template. +""" +import pytest +import numpy as np +import pennylane as qml +from pennylane import numpy as pnp + + +class TestDecomposition: + """Tests that the template defines the correct decomposition.""" + + QUEUES = [ + (1, (1, 1, 3), ["Rot"], [[0]]), + (2, (1, 2, 3), ["Rot", "Rot", "CNOT", "CNOT"], [[0], [1], [0, 1], [1, 0]]), + (2, (2, 2, 3), ["Rot", "Rot", "CNOT", "CNOT", "Rot", "Rot", "CNOT", "CNOT"], [[0], [1], [0, 1], [1, 0], [0], [1], [0, 1], [1, 0]]), + (3, (1, 3, 3), ["Rot", "Rot", "Rot", "CNOT", "CNOT", "CNOT"], [[0], [1], [2], [0, 1], [1, 2], [2, 0]]), + ] + + @pytest.mark.parametrize("n_wires, weight_shape, expected_names, expected_wires", QUEUES) + def test_expansion(self, n_wires, weight_shape, expected_names, expected_wires): + """Checks the queue for the default settings.""" + + weights = np.random.random(size=weight_shape) + + op = qml.templates.StronglyEntanglingLayers(weights, wires=range(n_wires)) + tape = op.expand() + + for i, gate in enumerate(tape.operations): + assert gate.name == expected_names[i] + assert gate.wires.labels == tuple(expected_wires[i]) + + @pytest.mark.parametrize("n_layers, n_wires", [(2, 2), (1, 3), (2, 4)]) + def test_uses_correct_imprimitive(self, n_layers, n_wires): + """Test that correct number of entanglers are used in the circuit.""" + + weights = np.random.randn(n_layers, n_wires, 3) + + op = qml.templates.StronglyEntanglingLayers( + weights=weights, wires=range(n_wires), imprimitive=qml.CZ + ) + ops = op.expand().operations + + gate_names = [gate.name for gate in ops] + assert gate_names.count("CZ") == n_wires * n_layers + + def test_custom_wire_labels(self, tol): + """Test that template can deal with non-numeric, nonconsecutive wire labels.""" + weights = np.random.random(size=(1, 3, 3)) + + dev = qml.device("default.qubit", wires=3) + dev2 = qml.device("default.qubit", wires=["z", "a", "k"]) + + @qml.qnode(dev) + def circuit(): + qml.templates.StronglyEntanglingLayers(weights, wires=range(3)) + return qml.expval(qml.Identity(0)) + + @qml.qnode(dev2) + def circuit2(): + qml.templates.StronglyEntanglingLayers(weights, wires=["z", "a", "k"]) + return qml.expval(qml.Identity("z")) + + circuit() + circuit2() + + assert np.allclose(dev.state, dev2.state, atol=tol, rtol=0) + + +class TestInputs: + """Test inputs and pre-processing.""" + + def test_exception_wrong_dim(self): + """Verifies that exception is raised if the + number of dimensions of features is incorrect.""" + + dev = qml.device("default.qubit", wires=2) + + @qml.qnode(dev) + def circuit(weights): + qml.templates.StronglyEntanglingLayers(weights, wires=range(2)) + return qml.expval(qml.PauliZ(0)) + + with pytest.raises(ValueError, match="Weights tensor must have second dimension"): + weights = np.random.randn(2, 1, 3) + circuit(weights) + + with pytest.raises(ValueError, match="Weights tensor must have third dimension"): + weights = np.random.randn(2, 2, 1) + circuit(weights) + + with pytest.raises(ValueError, match="Weights tensor must be 3-dimensional"): + weights = np.random.randn(2, 2, 3, 1) + circuit(weights) + + +class TestAttributes: + """Tests additional methods and attributes""" + + @pytest.mark.parametrize( + "n_layers, n_wires, expected_shape", + [ + (2, 3, (2, 3, 3)), + (2, 1, (2, 1, 3)), + (2, 2, (2, 2, 3)), + ], + ) + def test_shape(self, n_layers, n_wires, expected_shape): + """Test that the shape method returns the correct shape of the weights tensor""" + + shape = qml.templates.StronglyEntanglingLayers.shape(n_layers, n_wires) + assert shape == expected_shape + + +def circuit_template(weights): + qml.templates.StronglyEntanglingLayers(weights, range(3)) + return qml.expval(qml.PauliZ(0)) + + +def circuit_decomposed(weights): + qml.Rot(weights[0, 0, 0], weights[0, 0, 1], weights[0, 0, 2], wires=0) + qml.Rot(weights[0, 1, 0], weights[0, 1, 1], weights[0, 1, 2], wires=1) + qml.Rot(weights[0, 2, 0], weights[0, 2, 1], weights[0, 2, 2], wires=2) + qml.CNOT(wires=[0, 1]) + qml.CNOT(wires=[1, 2]) + qml.CNOT(wires=[2, 0]) + return qml.expval(qml.PauliZ(0)) + + +class TestInterfaces: + """Tests that the template is compatible with all interfaces, including the computation + of gradients.""" + + def test_list_and_tuples(self, tol): + """Tests common iterables as inputs.""" + + weights = [[[0.1, 0.2, 0.3], [0.1, 0.2, 0.3], [0.1, 0.2, 0.3]]] + + dev = qml.device("default.qubit", wires=3) + + circuit = qml.QNode(circuit_template, dev) + circuit2 = qml.QNode(circuit_decomposed, dev) + + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + weights_tuple = [[tuple(weights[0][0]), tuple(weights[0][1]), tuple(weights[0][2]),]] + res = circuit(weights_tuple) + res2 = circuit2(tuple(weights_tuple)) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + def test_autograd(self, tol): + """Tests the autograd interface.""" + + weights = np.random.random(size=(1, 3, 3)) + weights = pnp.array(weights, requires_grad=True) + + dev = qml.device("default.qubit", wires=3) + + circuit = qml.QNode(circuit_template, dev) + circuit2 = qml.QNode(circuit_decomposed, dev) + + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + grad_fn = qml.grad(circuit) + grads = grad_fn(weights) + + grad_fn2 = qml.grad(circuit2) + grads2 = grad_fn2(weights) + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + + def test_jax(self, tol, skip_if_no_jax_support): + """Tests the jax interface.""" + + import jax + import jax.numpy as jnp + + weights = jnp.array(np.random.random(size=(1, 3, 3))) + + dev = qml.device("default.qubit", wires=3) + + circuit = qml.QNode(circuit_template, dev, interface="jax") + circuit2 = qml.QNode(circuit_decomposed, dev, interface="jax") + + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + grad_fn = jax.grad(circuit) + grads = grad_fn(weights) + + grad_fn2 = jax.grad(circuit2) + grads2 = grad_fn2(weights) + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + + def test_tf(self, tol, skip_if_no_tf_support): + """Tests the tf interface.""" + + import tensorflow as tf + + weights = tf.Variable(np.random.random(size=(1, 3, 3))) + + dev = qml.device("default.qubit", wires=3) + + circuit = qml.QNode(circuit_template, dev, interface="tf") + circuit2 = qml.QNode(circuit_decomposed, dev, interface="tf") + + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + with tf.GradientTape() as tape: + res = circuit(weights) + grads = tape.gradient(res, [weights]) + + with tf.GradientTape() as tape2: + res2 = circuit2(weights) + grads2 = tape2.gradient(res2, [weights]) + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + + def test_torch(self, tol, skip_if_no_torch_support): + """Tests the torch interface.""" + + import torch + + weights = torch.tensor(np.random.random(size=(1, 3, 3)), requires_grad=True) + + dev = qml.device("default.qubit", wires=3) + + circuit = qml.QNode(circuit_template, dev, interface="torch") + circuit2 = qml.QNode(circuit_decomposed, dev, interface="torch") + + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + res = circuit(weights) + res.backward() + grads = [weights.grad] + + res2 = circuit2(weights) + res2.backward() + grads2 = [weights.grad] + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) diff --git a/tests/templates/test_layers/test_cv_neural_net.py b/tests/templates/test_layers/ttest_cv_neural_net.py similarity index 100% rename from tests/templates/test_layers/test_cv_neural_net.py rename to tests/templates/test_layers/ttest_cv_neural_net.py From 4d62e4f5ccb78b7adaf2fe416c13c2531d376128 Mon Sep 17 00:00:00 2001 From: Maria Schuld Date: Thu, 25 Mar 2021 16:54:32 +0200 Subject: [PATCH 05/16] add particle1 tests --- .../layers/particle_conserving_u1.py | 33 +- .../layers/particle_conserving_u2.py | 18 + pennylane/templates/layers/random.py | 2 +- .../test_layers/test_basic_entangler.py | 12 +- .../test_particle_conserving_u1.py | 450 ++++++++++++++++ .../test_particle_conserving_u2.py | 355 +++++++++++++ tests/templates/test_layers/test_random.py | 495 ++++++++---------- .../test_layers/test_simplified_twodesign.py | 1 + 8 files changed, 1091 insertions(+), 275 deletions(-) create mode 100644 tests/templates/test_layers/test_particle_conserving_u1.py create mode 100644 tests/templates/test_layers/test_particle_conserving_u2.py diff --git a/pennylane/templates/layers/particle_conserving_u1.py b/pennylane/templates/layers/particle_conserving_u1.py index 62500cc9e5c..2a9c6d680c9 100644 --- a/pennylane/templates/layers/particle_conserving_u1.py +++ b/pennylane/templates/layers/particle_conserving_u1.py @@ -39,7 +39,7 @@ def decompose_ua(phi, wires=None): Args: phi (float): angle :math:`\phi` defining the unitary :math:`U_A(\phi)` - wires (list[Iterable]): the wires ``n`` and ``m`` the circuit acts on + wires (Iterable): the wires ``n`` and ``m`` the circuit acts on """ n, m = wires @@ -212,6 +212,17 @@ class ParticleConservingU1(Operation): layers = 2 params = qml.init.particle_conserving_u1_normal(layers, qubits) print(cost_fn(params)) + + **Parameter shape** + + The shape of the weights argument can be computed by the static method + :meth:`~.ParticleConservingU1.shape` and used when creating randomly + initialised weight tensors: + + .. code-block:: python + + shape = ParticleConservingU1.shape(n_layers=2, n_wires=2) + weights = np.random.random(size=shape) """ num_params = 1 @@ -222,7 +233,7 @@ def __init__(self, weights, wires, init_state=None, do_queue=True): if len(wires) < 2: raise ValueError( - "Expected the number of qubits to be greater than one;" + "Expected the number of qubits to be greater than one; " "got wires {}".format(wires) ) @@ -259,3 +270,21 @@ def expand(self): for i, wires_ in enumerate(nm_wires): u1_ex_gate(self.parameters[0][l, i, 0], self.parameters[0][l, i, 1], wires=wires_) return tape + + @staticmethod + def shape(n_layers, n_wires): + r"""Returns the shape of the weight tensor required for this template. + + Args: + n_layers (int): number of layers + n_wires (int): number of qubits + + Returns: + tuple[int]: shape + """ + + if n_wires < 2: + raise ValueError( + "The number of qubits must be greater than one; got 'n_wires' = {}".format(n_wires) + ) + return n_layers, n_wires - 1, 2 diff --git a/pennylane/templates/layers/particle_conserving_u2.py b/pennylane/templates/layers/particle_conserving_u2.py index c6e86e0733d..0238d5813b6 100644 --- a/pennylane/templates/layers/particle_conserving_u2.py +++ b/pennylane/templates/layers/particle_conserving_u2.py @@ -182,3 +182,21 @@ def expand(self): for i, wires_ in enumerate(nm_wires): u2_ex_gate(self.parameters[0][l, len(self.wires) + i], wires=wires_) return tape + + @staticmethod + def shape(n_layers, n_wires): + r"""Returns the shape of the weight tensor required for this template. + + Args: + n_layers (int): number of layers + n_wires (int): number of qubits + + Returns: + tuple[int]: shape + """ + + if n_wires < 2: + raise ValueError( + "The number of qubits must be greater than one; got 'n_wires' = {}".format(n_wires) + ) + return n_layers, 2*n_wires - 1 diff --git a/pennylane/templates/layers/random.py b/pennylane/templates/layers/random.py index 9079bfd6f35..1cccd3d60d7 100644 --- a/pennylane/templates/layers/random.py +++ b/pennylane/templates/layers/random.py @@ -205,7 +205,7 @@ def expand(self): for l in range(self.n_layers): i = 0 - while i < shape[0]: + while i < shape[1]: if np.random.random() > self.ratio_imprimitive: # apply a random rotation gate to a random wire gate = np.random.choice(self.rotations) diff --git a/tests/templates/test_layers/test_basic_entangler.py b/tests/templates/test_layers/test_basic_entangler.py index b0888d086a9..f3cec7de5e7 100644 --- a/tests/templates/test_layers/test_basic_entangler.py +++ b/tests/templates/test_layers/test_basic_entangler.py @@ -50,10 +50,10 @@ def test_rotation(self, rotation): weights = np.zeros(shape=(1, 2)) op = qml.templates.BasicEntanglerLayers(weights, wires=range(2), rotation=rotation) - tape = op.expand() + queue = op.expand().operations - assert type(tape.operations[0]) == rotation - assert type(tape.operations[1]) == rotation + assert type(queue) == rotation + assert type(queue) == rotation @pytest.mark.parametrize( "weights, n_wires, target", @@ -120,6 +120,10 @@ def circuit(weights): with pytest.raises(ValueError, match="Weights tensor must have second dimension of length"): circuit([[1, 0], [1, 0]]) + +class TestAttributes: + """Tests additional methods and attributes""" + @pytest.mark.parametrize( "n_layers, n_wires, expected_shape", [ @@ -168,7 +172,7 @@ def test_list_and_tuples(self, tol): res2 = circuit2(weights) assert qml.math.allclose(res, res2, atol=tol, rtol=0) - weights_tuple = tuple([tuple(weights[0])]) + weights_tuple = [tuple(weights[0])] res = circuit(weights_tuple) res2 = circuit2(weights_tuple) assert qml.math.allclose(res, res2, atol=tol, rtol=0) diff --git a/tests/templates/test_layers/test_particle_conserving_u1.py b/tests/templates/test_layers/test_particle_conserving_u1.py new file mode 100644 index 00000000000..f69f97bc6ce --- /dev/null +++ b/tests/templates/test_layers/test_particle_conserving_u1.py @@ -0,0 +1,450 @@ +# Copyright 2018-2021 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Unit tests for the ParticleConservingU1 template. +""" +import pytest +import numpy as np +import pennylane as qml +from pennylane import numpy as pnp + + +class TestDecomposition: + """Tests that the template defines the correct decomposition.""" + + @staticmethod + def _wires_gates_u1(wires): + """Auxiliary function giving the wires that the elementary gates decomposing + ``u1_ex_gate`` act on.""" + + exp_wires = [ + wires, + wires, + [wires[1]], + wires, + [wires[1]], + wires, + [wires[0]], + wires[::-1], + wires[::-1], + wires, + wires, + [wires[1]], + wires, + [wires[1]], + wires, + [wires[0]], + ] + + return exp_wires + + def test_particle_conserving_u1_operations(self): + """Test the correctness of the ParticleConservingU1 template including the gate count + and order, the wires each operation acts on and the correct use of parameters + in the circuit.""" + + qubits = 4 + layers = 2 + weights = np.random.random(size=(layers, qubits-1, 2)) + + gates_per_u1 = 16 + gates_per_layer = gates_per_u1 * (qubits - 1) + gate_count = layers * gates_per_layer + 1 + + gates_u1 = [ + qml.CZ, + qml.CRot, + qml.PhaseShift, + qml.CNOT, + qml.PhaseShift, + qml.CNOT, + qml.PhaseShift, + ] + gates_ent = gates_u1 + [qml.CZ, qml.CRot] + gates_u1 + + wires = list(range(qubits)) + + nm_wires = [wires[l: l + 2] for l in range(0, qubits - 1, 2)] + nm_wires += [wires[l: l + 2] for l in range(1, qubits - 1, 2)] + + op = qml.templates.ParticleConservingU1(weights, wires, init_state=np.array([1, 1, 0, 0])) + queue = op.expand().operations + + assert gate_count == len(queue) + + # check initialization of the qubit register + assert isinstance(queue[0], qml.BasisState) + + # check all quantum operations + idx_CRot = 8 + for l in range(layers): + for i in range(qubits - 1): + exp_wires = self._wires_gates_u1(nm_wires[i]) + + phi = weights[l, i, 0] + theta = weights[l, i, 1] + + for j, exp_gate in enumerate(gates_ent): + idx = gates_per_layer * l + gates_per_u1 * i + j + 1 + + # check that the gates are applied in the right order + assert isinstance(queue[idx], exp_gate) + + # check the wires the gates act on + assert queue[idx].wires.tolist() == exp_wires[j] + + # check that parametrized gates take the parameters \phi and \theta properly + if exp_gate is qml.CRot: + + if j < idx_CRot: + exp_params = [-phi, np.pi, phi] + if j > idx_CRot: + exp_params = [phi, np.pi, -phi] + if j == idx_CRot: + exp_params = [0, 2 * theta, 0] + + assert queue[idx].parameters == exp_params + + elif exp_gate is qml.PhaseShift: + + if j < idx_CRot: + exp_params = -phi + if j == idx_CRot / 2: + exp_params = phi + if j > idx_CRot: + exp_params = phi + if j == (3 * idx_CRot + 2) / 2: + exp_params = -phi + + assert queue[idx].parameters == exp_params + + def test_custom_wire_labels(self, tol): + """Test that template can deal with non-numeric, nonconsecutive wire labels.""" + weights = np.random.random(size=(1, 2, 2)) + init_state = np.array([1, 1, 0]) + + dev = qml.device("default.qubit", wires=3) + dev2 = qml.device("default.qubit", wires=["z", "a", "k"]) + + @qml.qnode(dev) + def circuit(): + qml.templates.ParticleConservingU1(weights, wires=range(3), init_state=init_state) + return qml.expval(qml.Identity(0)) + + @qml.qnode(dev2) + def circuit2(): + qml.templates.ParticleConservingU1(weights, wires=["z", "a", "k"], init_state=init_state) + return qml.expval(qml.Identity("z")) + + circuit() + circuit2() + + assert np.allclose(dev.state, dev2.state, atol=tol, rtol=0) + + @pytest.mark.parametrize( + ("init_state", "exp_state"), + [ + (np.array([0, 0]), np.array([1.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j])), + ( + np.array([0, 1]), + np.array([0.0 + 0.0j, 0.862093 + 0.0j, 0.0 - 0.506749j, 0.0 + 0.0j]), + ), + ( + np.array([1, 0]), + np.array([0.0 + 0.0j, 0.0 - 0.506749j, 0.862093 + 0.0j, 0.0 + 0.0j]), + ), + (np.array([1, 1]), np.array([0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 1.0 + 0.0j])), + ], + ) + def test_decomposition_u2ex(self, init_state, exp_state, tol): + """Test the decomposition of the U_{2, ex}` exchange gate by asserting the prepared + state.""" + + N = 2 + wires = range(N) + + weight = 0.53141 + + dev = qml.device("default.qubit", wires=N) + + @qml.qnode(dev) + def circuit(weight): + qml.BasisState(init_state, wires=wires) + qml.templates.layers.particle_conserving_u2.u2_ex_gate(weight, wires) + return qml.expval(qml.PauliZ(0)) + + circuit(weight) + + assert np.allclose(circuit.device.state, exp_state, atol=tol) + + + @pytest.mark.parametrize( + ("init_state", "exp_state"), + [ + (np.array([0, 0]), np.array([1.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j])), + ( + np.array([1, 1]), + np.array( + [ + 0.00000000e00 + 0.00000000e00j, + 3.18686105e-17 - 1.38160066e-17j, + 1.09332080e-16 - 2.26795885e-17j, + 1.00000000e00 + 2.77555756e-17j, + ] + ), + ), + ( + np.array([0, 1]), + np.array( + [ + 0.00000000e00 + 0.00000000e00j, + 8.23539739e-01 - 2.77555756e-17j, + -5.55434174e-01 - 1.15217954e-01j, + 3.18686105e-17 + 1.38160066e-17j, + ] + ), + ), + ( + np.array([1, 0]), + np.array( + [ + 0.00000000e00 + 0.00000000e00j, + -5.55434174e-01 + 1.15217954e-01j, + -8.23539739e-01 - 5.55111512e-17j, + 1.09332080e-16 + 2.26795885e-17j, + ] + ), + ), + ], + ) + def test_decomposition_u1ex(self, init_state, exp_state, tol): + """Test the decomposition of the U_{1, ex}` exchange gate by asserting the prepared + state.""" + + N = 2 + wires = range(N) + weights = np.array([[[0.2045368, -0.6031732]]]) + + dev = qml.device("default.qubit", wires=N) + + @qml.qnode(dev) + def circuit(weights): + qml.templates.ParticleConservingU1(weights, wires, init_state=init_state) + return qml.expval(qml.PauliZ(0)) + + circuit(weights) + + assert np.allclose(circuit.device.state, exp_state, atol=tol) + + +class TestInputs: + """Test inputs and pre-processing.""" + + @pytest.mark.parametrize( + ("weights", "n_wires", "msg_match"), + [ + (np.ones((4, 3)), 4, "Weights tensor must"), + (np.ones((4, 2, 2)), 4, "Weights tensor must"), + (np.ones((4, 3, 1)), 4, "Weights tensor must"), + ( + np.ones((4, 3, 1)), + 1, + "Expected the number of qubits", + ), + ], + ) + def test_particle_conserving_u1_exceptions(self, weights, n_wires, msg_match): + """Test that ParticleConservingU1 throws an exception if the parameter array has an illegal + shape.""" + + wires = range(n_wires) + init_state = np.array([1, 1, 0, 0]) + dev = qml.device("default.qubit", wires=n_wires) + + @qml.qnode(dev) + def circuit(): + qml.templates.ParticleConservingU1(weights=weights, wires=wires, init_state=init_state) + return qml.expval(qml.PauliZ(0)) + + with pytest.raises(ValueError, match=msg_match): + circuit() + + +class TestAttributes: + """Tests additional methods and attributes""" + + @pytest.mark.parametrize("n_layers, n_wires, expected_shape", + [ + (2, 3, (2, 2, 2)), + (2, 2, (2, 1, 2)), + ] + ) + def test_shape(self, n_layers, n_wires, expected_shape): + """Test that the shape method returns the correct shape of the weights tensor""" + + shape = qml.templates.ParticleConservingU1.shape(n_layers, n_wires) + assert shape == expected_shape + + +def circuit_template(weights): + qml.templates.ParticleConservingU1(weights, range(2), init_state=np.array([1, 1])) + return qml.expval(qml.PauliZ(0)) + + +def circuit_decomposed(weights): + qml.BasisState(np.array([1, 1]), wires=[0, 1]) + qml.CZ(wires=[0, 1]) + qml.CRot(weights[0, 0, 0], np.pi, weights[0, 0, 0], + wires=[0, 1]) + qml.PhaseShift(-weights[0, 0, 0], wires=[1]) + qml.CNOT(wires=[0, 1]) + qml.PhaseShift(weights[0, 0, 0], wires=[1]) + qml.CNOT(wires=[0, 1]) + qml.PhaseShift(-weights[0, 0, 0], wires=[0]) + qml.CZ(wires=[1, 0]) + qml.CRot(0, weights[0, 0, 1], 0, wires=[1, 0]) + qml.CZ(wires=[0, 1]) + qml.CRot(weights[0, 0, 0], np.pi, -weights[0, 0, 0], + wires=[0, 1]) + qml.PhaseShift(weights[0, 0, 0], wires=[1]) + qml.CNOT(wires=[0, 1]) + qml.PhaseShift(-weights[0, 0, 0], wires=[1]) + qml.CNOT(wires=[0, 1]) + qml.PhaseShift(weights[0, 0, 0], wires=[0]) + + return qml.expval(qml.PauliZ(0)) + + +class TestInterfaces: + """Tests that the template is compatible with all interfaces, including the computation + of gradients.""" + + def test_list_and_tuples(self, tol): + """Tests common iterables as inputs.""" + + weights = [[[0.1, -1.1]]] + + dev = qml.device("default.qubit", wires=2) + + circuit = qml.QNode(circuit_template, dev) + circuit2 = qml.QNode(circuit_decomposed, dev) + + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + weights_tuple = [tuple(weights[0])] + res = circuit(weights_tuple) + res2 = circuit2(weights_tuple) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + def test_autograd(self, tol): + """Tests the autograd interface.""" + + weights = np.random.random(size=(1, 1, 2)) + weights = pnp.array(weights, requires_grad=True) + + dev = qml.device("default.qubit", wires=2) + + circuit = qml.QNode(circuit_template, dev) + circuit2 = qml.QNode(circuit_decomposed, dev) + + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + grad_fn = qml.grad(circuit) + grads = grad_fn(weights) + + grad_fn2 = qml.grad(circuit2) + grads2 = grad_fn2(weights) + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + + def test_jax(self, tol, skip_if_no_jax_support): + """Tests the jax interface.""" + + import jax + import jax.numpy as jnp + + weights = jnp.array(np.random.random(size=(1, 1, 2))) + + dev = qml.device("default.qubit", wires=2) + + circuit = qml.QNode(circuit_template, dev, interface="jax") + circuit2 = qml.QNode(circuit_decomposed, dev, interface="jax") + + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + grad_fn = jax.grad(circuit) + grads = grad_fn(weights) + + grad_fn2 = jax.grad(circuit2) + grads2 = grad_fn2(weights) + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + + def test_tf(self, tol, skip_if_no_tf_support): + """Tests the tf interface.""" + + import tensorflow as tf + + weights = tf.Variable(np.random.random(size=(1, 1, 2))) + + dev = qml.device("default.qubit", wires=2) + + circuit = qml.QNode(circuit_template, dev, interface="tf") + circuit2 = qml.QNode(circuit_decomposed, dev, interface="tf") + + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + with tf.GradientTape() as tape: + res = circuit(weights) + grads = tape.gradient(res, [weights]) + + with tf.GradientTape() as tape2: + res2 = circuit2(weights) + grads2 = tape2.gradient(res2, [weights]) + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + + def test_torch(self, tol, skip_if_no_torch_support): + """Tests the torch interface.""" + + import torch + + weights = torch.tensor(np.random.random(size=(1, 1, 2)), requires_grad=True) + + dev = qml.device("default.qubit", wires=2) + + circuit = qml.QNode(circuit_template, dev, interface="torch") + circuit2 = qml.QNode(circuit_decomposed, dev, interface="torch") + + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + res = circuit(weights) + res.backward() + grads = [weights.grad] + + res2 = circuit2(weights) + res2.backward() + grads2 = [weights.grad] + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) diff --git a/tests/templates/test_layers/test_particle_conserving_u2.py b/tests/templates/test_layers/test_particle_conserving_u2.py new file mode 100644 index 00000000000..63a28bce1a3 --- /dev/null +++ b/tests/templates/test_layers/test_particle_conserving_u2.py @@ -0,0 +1,355 @@ +# Copyright 2018-2021 Xanadu Quantum Technologies Inc. + +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at + +# http://www.apache.org/licenses/LICENSE-2.0 + +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +""" +Unit tests for the ParticleConservingU2 template. +""" +import pytest +import numpy as np +import pennylane as qml +from pennylane import numpy as pnp + + +class TestDecomposition: + """Tests that the template defines the correct decomposition.""" + + @staticmethod + def _wires_gates_u2(wires): + """Auxiliary function giving the wires that the elementary gates decomposing + ``u1_ex_gate`` act on.""" + + exp_wires = [ + wires, + wires, + [wires[1]], + wires, + [wires[1]], + wires, + [wires[0]], + wires[::-1], + wires[::-1], + wires, + wires, + [wires[1]], + wires, + [wires[1]], + wires, + [wires[0]], + ] + + return exp_wires + + def test_particle_conserving_u1_operations(self): + """Test the correctness of the ParticleConservingU1 template including the gate count + and order, the wires each operation acts on and the correct use of parameters + in the circuit.""" + + qubits = 4 + layers = 2 + weights = np.random.random(size=(layers, qubits-1, 2)) + + gates_per_u1 = 16 + gates_per_layer = gates_per_u1 * (qubits - 1) + gate_count = layers * gates_per_layer + 1 + + gates_u1 = [ + qml.CZ, + qml.CRot, + qml.PhaseShift, + qml.CNOT, + qml.PhaseShift, + qml.CNOT, + qml.PhaseShift, + ] + gates_ent = gates_u1 + [qml.CZ, qml.CRot] + gates_u1 + + wires = list(range(qubits)) + + nm_wires = [wires[l: l + 2] for l in range(0, qubits - 1, 2)] + nm_wires += [wires[l: l + 2] for l in range(1, qubits - 1, 2)] + + op = qml.templates.ParticleConservingU1(weights, wires, init_state=np.array([1, 1, 0, 0])) + queue = op.expand().operations + + assert gate_count == len(queue) + + # check initialization of the qubit register + assert isinstance(queue[0], qml.BasisState) + + # check all quantum operations + idx_CRot = 8 + for l in range(layers): + for i in range(qubits - 1): + exp_wires = self._wires_gates_u1(nm_wires[i]) + + phi = weights[l, i, 0] + theta = weights[l, i, 1] + + for j, exp_gate in enumerate(gates_ent): + idx = gates_per_layer * l + gates_per_u1 * i + j + 1 + + # check that the gates are applied in the right order + assert isinstance(queue[idx], exp_gate) + + # check the wires the gates act on + assert queue[idx].wires.tolist() == exp_wires[j] + + # check that parametrized gates take the parameters \phi and \theta properly + if exp_gate is qml.CRot: + + if j < idx_CRot: + exp_params = [-phi, np.pi, phi] + if j > idx_CRot: + exp_params = [phi, np.pi, -phi] + if j == idx_CRot: + exp_params = [0, 2 * theta, 0] + + assert queue[idx].parameters == exp_params + + elif exp_gate is qml.PhaseShift: + + if j < idx_CRot: + exp_params = -phi + if j == idx_CRot / 2: + exp_params = phi + if j > idx_CRot: + exp_params = phi + if j == (3 * idx_CRot + 2) / 2: + exp_params = -phi + + assert queue[idx].parameters == exp_params + + def test_custom_wire_labels(self, tol): + """Test that template can deal with non-numeric, nonconsecutive wire labels.""" + weights = np.random.random(size=(1, 2, 2)) + init_state = np.array([1, 1, 0]) + + dev = qml.device("default.qubit", wires=3) + dev2 = qml.device("default.qubit", wires=["z", "a", "k"]) + + @qml.qnode(dev) + def circuit(): + qml.templates.ParticleConservingU1(weights, wires=range(3), init_state=init_state) + return qml.expval(qml.Identity(0)) + + @qml.qnode(dev2) + def circuit2(): + qml.templates.ParticleConservingU1(weights, wires=["z", "a", "k"], init_state=init_state) + return qml.expval(qml.Identity("z")) + + circuit() + circuit2() + + assert np.allclose(dev.state, dev2.state, atol=tol, rtol=0) + + +class TestInputs: + """Test inputs and pre-processing.""" + + @pytest.mark.parametrize( + ("weights", "n_wires", "msg_match"), + [ + (np.ones((4, 3)), 4, "Weights tensor must"), + (np.ones((4, 2, 2)), 4, "Weights tensor must"), + (np.ones((4, 3, 1)), 4, "Weights tensor must"), + ( + np.ones((4, 3, 1)), + 1, + "Expected the number of qubits", + ), + ], + ) + def test_particle_conserving_u1_exceptions(self, weights, n_wires, msg_match): + """Test that ParticleConservingU1 throws an exception if the parameter array has an illegal + shape.""" + + wires = range(n_wires) + init_state = np.array([1, 1, 0, 0]) + dev = qml.device("default.qubit", wires=n_wires) + + @qml.qnode(dev) + def circuit(): + qml.templates.ParticleConservingU1(weights=weights, wires=wires, init_state=init_state) + return qml.expval(qml.PauliZ(0)) + + with pytest.raises(ValueError, match=msg_match): + circuit() + + +class TestAttributes: + """Tests additional methods and attributes""" + + @pytest.mark.parametrize("n_layers, n_wires, expected_shape", + [ + (2, 3, (2, 2, 2)), + (2, 2, (2, 1, 2)), + ] + ) + def test_shape(self, n_layers, n_wires, expected_shape): + """Test that the shape method returns the correct shape of the weights tensor""" + + shape = qml.templates.ParticleConservingU1.shape(n_layers, n_wires) + assert shape == expected_shape + + +def circuit_template(weights): + qml.templates.ParticleConservingU1(weights, range(2), init_state=np.array([1, 1])) + return qml.expval(qml.PauliZ(0)) + + +def circuit_decomposed(weights): + qml.BasisState(np.array([1, 1]), wires=[0, 1]) + qml.CZ(wires=[0, 1]) + qml.CRot(weights[0, 0, 0], np.pi, weights[0, 0, 0], + wires=[0, 1]) + qml.PhaseShift(-weights[0, 0, 0], wires=[1]) + qml.CNOT(wires=[0, 1]) + qml.PhaseShift(weights[0, 0, 0], wires=[1]) + qml.CNOT(wires=[0, 1]) + qml.PhaseShift(-weights[0,0, 0], wires=[0]) + qml.CZ(wires=[1, 0]) + qml.CRot(0, weights[0, 0, 1], 0, wires=[1, 0]) + qml.CZ(wires=[0, 1]) + qml.CRot(weights[0, 0, 0], np.pi, -weights[0, 0], + wires=[0, 1]) + qml.PhaseShift(weights[0, 0, 0], wires=[1]) + qml.CNOT(wires=[0, 1]) + qml.PhaseShift(-weights[0, 0, 0], wires=[1]) + qml.CNOT(wires=[0, 1]) + qml.PhaseShift(weights[0, 0, 0], wires=[0]) + + return qml.expval(qml.PauliZ(0)) + + +class TestInterfaces: + """Tests that the template is compatible with all interfaces, including the computation + of gradients.""" + + def test_list_and_tuples(self, tol): + """Tests common iterables as inputs.""" + + weights = [[[0.1, -1.1]]] + + dev = qml.device("default.qubit", wires=2) + + circuit = qml.QNode(circuit_template, dev) + circuit2 = qml.QNode(circuit_decomposed, dev) + + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + weights_tuple = [tuple(weights[0])] + res = circuit(weights_tuple) + res2 = circuit2(weights_tuple) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + def test_autograd(self, tol): + """Tests the autograd interface.""" + + weights = np.random.random(size=(1, 1, 2)) + weights = pnp.array(weights, requires_grad=True) + + dev = qml.device("default.qubit", wires=2) + + circuit = qml.QNode(circuit_template, dev) + circuit2 = qml.QNode(circuit_decomposed, dev) + + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + grad_fn = qml.grad(circuit) + grads = grad_fn(weights) + + grad_fn2 = qml.grad(circuit2) + grads2 = grad_fn2(weights) + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + + def test_jax(self, tol, skip_if_no_jax_support): + """Tests the jax interface.""" + + import jax + import jax.numpy as jnp + + weights = jnp.array(np.random.random(size=(1, 1, 2))) + + dev = qml.device("default.qubit", wires=2) + + circuit = qml.QNode(circuit_template, dev, interface="jax") + circuit2 = qml.QNode(circuit_decomposed, dev, interface="jax") + + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + grad_fn = jax.grad(circuit) + grads = grad_fn(weights) + + grad_fn2 = jax.grad(circuit2) + grads2 = grad_fn2(weights) + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + + def test_tf(self, tol, skip_if_no_tf_support): + """Tests the tf interface.""" + + import tensorflow as tf + + weights = tf.Variable(np.random.random(size=(1, 1, 2))) + + dev = qml.device("default.qubit", wires=2) + + circuit = qml.QNode(circuit_template, dev, interface="tf") + circuit2 = qml.QNode(circuit_decomposed, dev, interface="tf") + + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + with tf.GradientTape() as tape: + res = circuit(weights) + grads = tape.gradient(res, [weights]) + + with tf.GradientTape() as tape2: + res2 = circuit2(weights) + grads2 = tape2.gradient(res2, [weights]) + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + + def test_torch(self, tol, skip_if_no_torch_support): + """Tests the torch interface.""" + + import torch + + weights = torch.tensor(np.random.random(size=(1, 1, 2)), requires_grad=True) + + dev = qml.device("default.qubit", wires=2) + + circuit = qml.QNode(circuit_template, dev, interface="torch") + circuit2 = qml.QNode(circuit_decomposed, dev, interface="torch") + + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + res = circuit(weights) + res.backward() + grads = [weights.grad] + + res2 = circuit2(weights) + res2.backward() + grads2 = [weights.grad] + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) diff --git a/tests/templates/test_layers/test_random.py b/tests/templates/test_layers/test_random.py index f73f3d7acfb..f5daa49550e 100644 --- a/tests/templates/test_layers/test_random.py +++ b/tests/templates/test_layers/test_random.py @@ -25,293 +25,252 @@ class TestDecomposition: def test_seed(self): """Test that the circuit is fixed by the seed.""" - weights = [[0.1, 0.2, 0.3]] + weights = np.random.random((2, 3)) op1 = qml.templates.RandomLayers(weights, wires=range(2), seed=41) op2 = qml.templates.RandomLayers(weights, wires=range(2), seed=42) op3 = qml.templates.RandomLayers(weights, wires=range(2), seed=42) - ops1 = op1.expand().operations - ops2 = op2.expand().operations - ops3 = op3.expand().operations + queue1 = op1.expand().operations + queue2 = op2.expand().operations + queue3 = op3.expand().operations - assert ops1 != ops2 - assert ops2 == ops3 + assert not all(g1.name == g2.name for g1, g2 in zip(queue1, queue2)) + assert all(g2.name == g3.name for g2, g3 in zip(queue2, queue3)) - def test_random_layers_nlayers(self, n_layers): - """Test that the correct number of gates.""" - np.random.seed(12) - n_rots = 1 - n_wires = 2 - impr = qml.CNOT + @pytest.mark.parametrize("n_layers, n_rots", [(3, 4), (1, 2)]) + def test_number_gates(self, n_layers, n_rots): + """Test that the number of gates is correct.""" weights = np.random.randn(n_layers, n_rots) - op = qml.templates.RandomLayers(weights, wires=range(n_wires)) + op = qml.templates.RandomLayers(weights, wires=range(2)) ops = op.expand().operations - types = [type(o) for o in ops] - assert len(types) - types.count(impr) == n_layers + gate_names = [g.name for g in ops] + assert len(gate_names) - gate_names.count("CNOT") == n_layers*n_rots @pytest.mark.parametrize("ratio", [0.2, 0.6]) - def test_random_layer_ratio_imprimitive(self, ratio): + def test_ratio_imprimitive(self, ratio): """Test the ratio of imprimitive gates.""" n_rots = 500 - n_wires = 2 weights = np.random.random(size=(1, n_rots)) - op = qml.templates.RandomLayers(weights, wires=range(n_wires), ratio_imprim=ratio) - ops = op.expand().operations + op = qml.templates.RandomLayers(weights, wires=range(2), ratio_imprim=ratio) + queue = op.expand().operations - gate_names = [g.name for g in ops] + gate_names = [gate.name for gate in queue] ratio_impr = gate_names.count("CNOT") / len(gate_names) assert np.isclose(ratio_impr, ratio, atol=0.05) + def test_random_wires(self): + """Test that random wires are picked for the gates. This is done by + taking the mean of all wires, which should be 1 for labels [0, 1, 2]""" + n_rots = 5000 + weights = np.random.random(size=(2, n_rots)) + + op = qml.templates.RandomLayers(weights, wires=range(3)) + queue = op.expand().operations + + gate_wires = [gate.wires.labels for gate in queue] + wires_flat = [item for w in gate_wires for item in w] + mean_wire = np.mean(wires_flat) + + assert np.isclose(mean_wire, 1, atol=0.05) + + def test_custom_wire_labels(self, tol): + """Test that template can deal with non-numeric, nonconsecutive wire labels.""" + weights = np.random.random(size=(1, 3)) + + dev = qml.device("default.qubit", wires=3) + dev2 = qml.device("default.qubit", wires=["z", "a", "k"]) + + @qml.qnode(dev) + def circuit(): + qml.templates.RandomLayers(weights, wires=range(3)) + return qml.expval(qml.Identity(0)) + + @qml.qnode(dev2) + def circuit2(): + qml.templates.RandomLayers(weights, wires=["z", "a", "k"]) + return qml.expval(qml.Identity("z")) + + circuit() + circuit2() + + assert np.allclose(dev.state, dev2.state, atol=tol, rtol=0) + + +class TestInputs: + """Test inputs and pre-processing.""" + + def test_exception_wrong_dim(self): + """Verifies that exception is raised if the + number of dimensions of features is incorrect.""" + + dev = qml.device("default.qubit", wires=4) + + @qml.qnode(dev) + def circuit(phi): + qml.templates.RandomLayers(phi, wires=list(range(4))) + return qml.expval(qml.PauliZ(0)) + + phi = np.array([0.04, 0.14, 3.29, 2.51]) + + with pytest.raises(ValueError, match="Weights tensor must be 2-dimensional"): + circuit(phi) + + +class TestAttributes: + """Tests additional methods and attributes""" + + @pytest.mark.parametrize( + "n_layers, n_rots, expected_shape", + [ + (2, 3, (2, 3)), + (2, 1, (2, 1)), + (2, 2, (2, 2)), + ], + ) + def test_shape(self, n_layers, n_rots, expected_shape): + """Test that the shape method returns the correct shape of the weights tensor""" + + shape = qml.templates.RandomLayers.shape(n_layers, n_rots) + assert shape == expected_shape + + +def circuit_template(weights): + qml.templates.RandomLayers(weights, range(3), seed=42) + return qml.expval(qml.PauliZ(0)) + + +def circuit_decomposed(weights): + # this structure is only true for a seed of 42 and 3 wires + qml.RX(weights[0, 0], wires=1) + qml.RX(weights[0, 1], wires=0) + qml.CNOT(wires=[1, 0]) + qml.RZ(weights[0, 2], wires=2) + return qml.expval(qml.PauliZ(0)) + + +class TestInterfaces: + """Tests that the template is compatible with all interfaces, including the computation + of gradients.""" + + def test_list_and_tuples(self, tol): + """Tests common iterables as inputs.""" + + weights = [[0.1, -1.1, 0.2]] + + dev = qml.device("default.qubit", wires=3) + + circuit = qml.QNode(circuit_template, dev) + circuit2 = qml.QNode(circuit_decomposed, dev) + + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + weights_tuple = [tuple(weights[0])] + res = circuit(weights_tuple) + res2 = circuit2(weights_tuple) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + def test_autograd(self, tol): + """Tests the autograd interface.""" + + weights = np.random.random(size=(1, 3)) + weights = pnp.array(weights, requires_grad=True) + + dev = qml.device("default.qubit", wires=3) + + circuit = qml.QNode(circuit_template, dev) + circuit2 = qml.QNode(circuit_decomposed, dev) + + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + grad_fn = qml.grad(circuit) + grads = grad_fn(weights) + + grad_fn2 = qml.grad(circuit2) + grads2 = grad_fn2(weights) + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + + def test_jax(self, tol, skip_if_no_jax_support): + """Tests the jax interface.""" + + import jax + import jax.numpy as jnp + + weights = jnp.array(np.random.random(size=(1, 3))) + + dev = qml.device("default.qubit", wires=3) + + circuit = qml.QNode(circuit_template, dev, interface="jax") + circuit2 = qml.QNode(circuit_decomposed, dev, interface="jax") + + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + grad_fn = jax.grad(circuit) + grads = grad_fn(weights) + + grad_fn2 = jax.grad(circuit2) + grads2 = grad_fn2(weights) + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + + def test_tf(self, tol, skip_if_no_tf_support): + """Tests the tf interface.""" + + import tensorflow as tf + + weights = tf.Variable(np.random.random(size=(1, 3))) + + dev = qml.device("default.qubit", wires=3) + + circuit = qml.QNode(circuit_template, dev, interface="tf") + circuit2 = qml.QNode(circuit_decomposed, dev, interface="tf") + + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + with tf.GradientTape() as tape: + res = circuit(weights) + grads = tape.gradient(res, [weights]) + + with tf.GradientTape() as tape2: + res2 = circuit2(weights) + grads2 = tape2.gradient(res2, [weights]) + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + + def test_torch(self, tol, skip_if_no_torch_support): + """Tests the torch interface.""" + + import torch + + weights = torch.tensor(np.random.random(size=(1, 3)), requires_grad=True) + + dev = qml.device("default.qubit", wires=3) + + circuit = qml.QNode(circuit_template, dev, interface="torch") + circuit2 = qml.QNode(circuit_decomposed, dev, interface="torch") + + res = circuit(weights) + res2 = circuit2(weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + res = circuit(weights) + res.backward() + grads = [weights.grad] + + res2 = circuit2(weights) + res2.backward() + grads2 = [weights.grad] -# QUEUES = [ -# (1, (1, 1), ["RX"], [[0]]), -# (2, (1, 2), ["RX", "RX", "CNOT"], [[0], [1], [0, 1]]), -# (2, (2, 2), ["RX", "RX", "CNOT", "RX", "RX", "CNOT"], [[0], [1], [0, 1], [0], [1], [0, 1]]), -# (3, (1, 3), ["RX", "RX", "RX", "CNOT", "CNOT", "CNOT"], [[0], [1], [2], [0, 1], [1, 2], [2, 0]]), -# ] -# -# @pytest.mark.parametrize("n_wires, weight_shape, expected_names, expected_wires", QUEUES) -# def test_expansion(self, n_wires, weight_shape, expected_names, expected_wires): -# """Checks the queue for the default settings.""" -# -# weights = np.random.random(size=weight_shape) -# -# op = qml.templates.BasicEntanglerLayers(weights, wires=range(n_wires)) -# tape = op.expand() -# -# for i, gate in enumerate(tape.operations): -# assert gate.name == expected_names[i] -# assert gate.wires.labels == tuple(expected_wires[i]) -# -# @pytest.mark.parametrize("rotation", [qml.RY, qml.RZ]) -# def test_rotation(self, rotation): -# """Checks that custom rotation gate is used.""" -# -# weights = np.zeros(shape=(1, 2)) -# -# op = qml.templates.BasicEntanglerLayers(weights, wires=range(2), rotation=rotation) -# tape = op.expand() -# -# assert type(tape.operations[0]) == rotation -# assert type(tape.operations[1]) == rotation -# -# @pytest.mark.parametrize( -# "weights, n_wires, target", -# [ -# ([[np.pi]], 1, [-1]), -# ([[np.pi] * 2], 2, [-1, 1]), -# ([[np.pi] * 3], 3, [1, 1, -1]), -# ([[np.pi] * 4], 4, [-1, 1, -1, 1]), -# ], -# ) -# def test_simple_target_outputs(self, weights, n_wires, target, tol): -# """Tests the result of the template for simple cases.""" -# -# dev = qml.device("default.qubit", wires=n_wires) -# -# @qml.qnode(dev) -# def circuit(weights): -# qml.templates.BasicEntanglerLayers(weights, wires=range(n_wires)) -# return [qml.expval(qml.PauliZ(i)) for i in range(n_wires)] -# -# expectations = circuit(weights) -# assert np.allclose(expectations, target, atol=tol, rtol=0) -# -# def test_custom_wire_labels(self, tol): -# """Test that template can deal with non-numeric, nonconsecutive wire labels.""" -# weights = np.random.random(size=(1, 3)) -# -# dev = qml.device("default.qubit", wires=3) -# dev2 = qml.device("default.qubit", wires=["z", "a", "k"]) -# -# @qml.qnode(dev) -# def circuit(): -# qml.templates.BasicEntanglerLayers(weights, wires=range(3)) -# return qml.expval(qml.Identity(0)) -# -# @qml.qnode(dev2) -# def circuit2(): -# qml.templates.BasicEntanglerLayers(weights, wires=["z", "a", "k"]) -# return qml.expval(qml.Identity("z")) -# -# circuit() -# circuit2() -# -# assert np.allclose(dev.state, dev2.state, atol=tol, rtol=0) -# -# -# class TestInputs: -# """Test inputs and pre-processing.""" -# -# def test_exception_wrong_dim(self): -# """Verifies that exception is raised if the weights shape is incorrect.""" -# -# n_wires = 1 -# dev = qml.device("default.qubit", wires=n_wires) -# -# @qml.qnode(dev) -# def circuit(weights): -# qml.templates.BasicEntanglerLayers(weights=weights, wires=range(n_wires)) -# return [qml.expval(qml.PauliZ(i)) for i in range(n_wires)] -# -# with pytest.raises(ValueError, match="Weights tensor must be 2-dimensional"): -# circuit([1, 0]) -# -# with pytest.raises(ValueError, match="Weights tensor must have second dimension of length"): -# circuit([[1, 0], [1, 0]]) -# -# @pytest.mark.parametrize( -# "n_layers, n_wires, expected_shape", -# [ -# (2, 3, (2, 3)), -# (2, 1, (2, 1)), -# (2, 2, (2, 2)), -# ], -# ) -# def test_shape(self, n_layers, n_wires, expected_shape): -# """Test that the shape method returns the correct shape of the weights tensor""" -# -# shape = qml.templates.BasicEntanglerLayers.shape(n_layers, n_wires) -# assert shape == expected_shape -# -# -# def circuit_template(weights): -# qml.templates.BasicEntanglerLayers(weights, range(3)) -# return qml.expval(qml.PauliZ(0)) -# -# -# def circuit_decomposed(weights): -# qml.RX(weights[0, 0], wires=0) -# qml.RX(weights[0, 1], wires=1) -# qml.RX(weights[0, 2], wires=2) -# qml.CNOT(wires=[0, 1]) -# qml.CNOT(wires=[1, 2]) -# qml.CNOT(wires=[2, 0]) -# return qml.expval(qml.PauliZ(0)) -# -# -# class TestInterfaces: -# """Tests that the template is compatible with all interfaces, including the computation -# of gradients.""" -# -# def test_list_and_tuples(self, tol): -# """Tests common iterables as inputs.""" -# -# weights = [[0.1, -1.1, 0.2]] -# -# dev = qml.device("default.qubit", wires=3) -# -# circuit = qml.QNode(circuit_template, dev) -# circuit2 = qml.QNode(circuit_decomposed, dev) -# -# res = circuit(weights) -# res2 = circuit2(weights) -# assert qml.math.allclose(res, res2, atol=tol, rtol=0) -# -# res = circuit(tuple(weights)) -# res2 = circuit2(tuple(weights)) -# assert qml.math.allclose(res, res2, atol=tol, rtol=0) -# -# def test_autograd(self, tol): -# """Tests the autograd interface.""" -# -# weights = np.random.random(size=(1, 3)) -# weights = pnp.array(weights, requires_grad=True) -# -# dev = qml.device("default.qubit", wires=3) -# -# circuit = qml.QNode(circuit_template, dev) -# circuit2 = qml.QNode(circuit_decomposed, dev) -# -# res = circuit(weights) -# res2 = circuit2(weights) -# assert qml.math.allclose(res, res2, atol=tol, rtol=0) -# -# grad_fn = qml.grad(circuit) -# grads = grad_fn(weights) -# -# grad_fn2 = qml.grad(circuit2) -# grads2 = grad_fn2(weights) -# -# assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) -# -# def test_jax(self, tol, skip_if_no_jax_support): -# """Tests the jax interface.""" -# -# import jax -# import jax.numpy as jnp -# -# weights = jnp.array(np.random.random(size=(1, 3))) -# -# dev = qml.device("default.qubit", wires=3) -# -# circuit = qml.QNode(circuit_template, dev, interface="jax") -# circuit2 = qml.QNode(circuit_decomposed, dev, interface="jax") -# -# res = circuit(weights) -# res2 = circuit2(weights) -# assert qml.math.allclose(res, res2, atol=tol, rtol=0) -# -# grad_fn = jax.grad(circuit) -# grads = grad_fn(weights) -# -# grad_fn2 = jax.grad(circuit2) -# grads2 = grad_fn2(weights) -# -# assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) -# -# def test_tf(self, tol, skip_if_no_tf_support): -# """Tests the tf interface.""" -# -# import tensorflow as tf -# -# weights = tf.Variable(np.random.random(size=(1, 3))) -# -# dev = qml.device("default.qubit", wires=3) -# -# circuit = qml.QNode(circuit_template, dev, interface="tf") -# circuit2 = qml.QNode(circuit_decomposed, dev, interface="tf") -# -# res = circuit(weights) -# res2 = circuit2(weights) -# assert qml.math.allclose(res, res2, atol=tol, rtol=0) -# -# with tf.GradientTape() as tape: -# res = circuit(weights) -# grads = tape.gradient(res, [weights]) -# -# with tf.GradientTape() as tape2: -# res2 = circuit2(weights) -# grads2 = tape2.gradient(res2, [weights]) -# -# assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) -# -# def test_torch(self, tol, skip_if_no_torch_support): -# """Tests the torch interface.""" -# -# import torch -# -# weights = torch.tensor(np.random.random(size=(1, 3)), requires_grad=True) -# -# dev = qml.device("default.qubit", wires=3) -# -# circuit = qml.QNode(circuit_template, dev, interface="torch") -# circuit2 = qml.QNode(circuit_decomposed, dev, interface="torch") -# -# res = circuit(weights) -# res2 = circuit2(weights) -# assert qml.math.allclose(res, res2, atol=tol, rtol=0) -# -# res = circuit(weights) -# res.backward() -# grads = [weights.grad] -# -# res2 = circuit2(weights) -# res2.backward() -# grads2 = [weights.grad] -# -# assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) diff --git a/tests/templates/test_layers/test_simplified_twodesign.py b/tests/templates/test_layers/test_simplified_twodesign.py index 9d532d55c5b..b88e9c38021 100644 --- a/tests/templates/test_layers/test_simplified_twodesign.py +++ b/tests/templates/test_layers/test_simplified_twodesign.py @@ -153,6 +153,7 @@ def circuit(initial_layer, weights): class TestAttributes: + """Tests additional methods and attributes""" @pytest.mark.parametrize( "n_layers, n_wires, expected_shape", From 21827db03a603051a9802c75d81e23cbf9cdd266 Mon Sep 17 00:00:00 2001 From: Maria Schuld Date: Thu, 25 Mar 2021 17:15:38 +0200 Subject: [PATCH 06/16] finished all tests - 2 external bugs remaining --- .../layers/particle_conserving_u1.py | 2 +- .../layers/particle_conserving_u2.py | 11 + tests/templates/test_layers.py | 48 --- .../test_layers/test_basic_entangler.py | 3 +- ...cv_neural_net.py => test_cv_neural_net.py} | 286 +++++++++--------- .../test_particle_conserving_u1.py | 41 +-- .../test_particle_conserving_u2.py | 257 ++++++++-------- 7 files changed, 287 insertions(+), 361 deletions(-) rename tests/templates/test_layers/{ttest_cv_neural_net.py => test_cv_neural_net.py} (50%) diff --git a/pennylane/templates/layers/particle_conserving_u1.py b/pennylane/templates/layers/particle_conserving_u1.py index 2a9c6d680c9..2d5969300b5 100644 --- a/pennylane/templates/layers/particle_conserving_u1.py +++ b/pennylane/templates/layers/particle_conserving_u1.py @@ -213,7 +213,7 @@ class ParticleConservingU1(Operation): params = qml.init.particle_conserving_u1_normal(layers, qubits) print(cost_fn(params)) - **Parameter shape** + **Parameter shape** The shape of the weights argument can be computed by the static method :meth:`~.ParticleConservingU1.shape` and used when creating randomly diff --git a/pennylane/templates/layers/particle_conserving_u2.py b/pennylane/templates/layers/particle_conserving_u2.py index 0238d5813b6..519786e355f 100644 --- a/pennylane/templates/layers/particle_conserving_u2.py +++ b/pennylane/templates/layers/particle_conserving_u2.py @@ -134,6 +134,17 @@ class ParticleConservingU2(Operation): layers = 1 params = qml.init.particle_conserving_u2_normal(layers, qubits) print(cost_fn(params)) + + **Parameter shape** + + The shape of the weights argument can be computed by the static method + :meth:`~.ParticleConservingU2.shape` and used when creating randomly + initialised weight tensors: + + .. code-block:: python + + shape = ParticleConservingU2.shape(n_layers=2, n_wires=2) + weights = np.random.random(size=shape) """ num_params = 1 diff --git a/tests/templates/test_layers.py b/tests/templates/test_layers.py index 81982ccca96..b1d7ddc77c0 100644 --- a/tests/templates/test_layers.py +++ b/tests/templates/test_layers.py @@ -718,55 +718,7 @@ def test_u2_operations(self, layers, qubits, init_state): assert res_wires == exp_wires - @pytest.mark.parametrize( - ("weights", "wires", "msg_match"), - [ - ( - np.array([[-0.080, 2.629, -0.710, 5.383, 0.646, -2.872, -3.856]]), - [0], - "This template requires the number of qubits to be greater than one", - ), - ( - np.array([[-0.080, 2.629, -0.710, 5.383]]), - [0, 1, 2, 3], - "Weights tensor must", - ), - ( - np.array( - [ - [-0.080, 2.629, -0.710, 5.383, 0.646, -2.872], - [-0.080, 2.629, -0.710, 5.383, 0.646, -2.872], - ] - ), - [0, 1, 2, 3], - "Weights tensor must", - ), - ( - np.array([-0.080, 2.629, -0.710, 5.383, 0.646, -2.872]), - [0, 1, 2, 3], - "Weights tensor must be 2-dimensional", - ), - ], - ) - def test_u2_exceptions(self, weights, wires, msg_match): - """Test that ParticleConservingU2 throws an exception if the parameters have illegal - shapes, types or values.""" - N = len(wires) - init_state = np.array([1, 1, 0, 0]) - - dev = qml.device("default.qubit", wires=N) - @qml.qnode(dev) - def circuit(): - ParticleConservingU2( - weights=weights, - wires=wires, - init_state=init_state, - ) - return qml.expval(qml.PauliZ(0)) - - with pytest.raises(ValueError, match=msg_match): - circuit() @pytest.mark.parametrize( ("weights", "wires", "expected"), diff --git a/tests/templates/test_layers/test_basic_entangler.py b/tests/templates/test_layers/test_basic_entangler.py index f3cec7de5e7..c08c8d70283 100644 --- a/tests/templates/test_layers/test_basic_entangler.py +++ b/tests/templates/test_layers/test_basic_entangler.py @@ -52,8 +52,7 @@ def test_rotation(self, rotation): op = qml.templates.BasicEntanglerLayers(weights, wires=range(2), rotation=rotation) queue = op.expand().operations - assert type(queue) == rotation - assert type(queue) == rotation + assert rotation in [type(gate) for gate in queue] @pytest.mark.parametrize( "weights, n_wires, target", diff --git a/tests/templates/test_layers/ttest_cv_neural_net.py b/tests/templates/test_layers/test_cv_neural_net.py similarity index 50% rename from tests/templates/test_layers/ttest_cv_neural_net.py rename to tests/templates/test_layers/test_cv_neural_net.py index 1334fc24342..94b3a5d1984 100644 --- a/tests/templates/test_layers/ttest_cv_neural_net.py +++ b/tests/templates/test_layers/test_cv_neural_net.py @@ -38,7 +38,7 @@ class TestDecomposition: """Tests that the template defines the correct decomposition.""" QUEUES = [ - (1, ["Interferometer", "Squeezing", "Interferometer", "Displacement", "Kerr"], [0]*5), + (1, ["Interferometer", "Squeezing", "Interferometer", "Displacement", "Kerr"], [[0]]*5), (2, ["Interferometer", "Squeezing", "Squeezing", "Interferometer", "Displacement", "Displacement", "Kerr", "Kerr"], [[0, 1], [0], [1], [0, 1], [0], [1], [0], [1]]), ] @@ -144,145 +144,145 @@ def circuit_template(*weights): return qml.expval(qml.X(0)) -def circuit_decomposed(*weights): - qml.templates.Interferometer(weights[0][0], weights[1][0], weights[2][0], wires=[0, 1]) - qml.Squeezing(weights[3][0, 0], weights[4][0, 0], wires=0) - qml.Squeezing(weights[3][0, 1], weights[4][0, 1], wires=1) - qml.templates.Interferometer(weights[5][0], weights[6][0], weights[7][0], wires=[0, 1]) - qml.Displacement(weights[8][0, 0], weights[9][0, 0], wires=0) - qml.Displacement(weights[8][0, 1], weights[9][0, 1], wires=1) - qml.Kerr(weights[10][0, 0], wires=0) - qml.Kerr(weights[10][0, 1], wires=1) - return qml.expval(qml.X(0)) - - -class TestInterfaces: - """Tests that the template is compatible with all interfaces, including the computation - of gradients.""" - - def test_list_and_tuples(self, tol): - """Tests common iterables as inputs.""" - - shapes = expected_shapes(1, 2) - weights = [np.random.random(shape) for shape in shapes] - - dev = DummyDevice(wires=2) - - circuit = qml.QNode(circuit_template, dev) - circuit2 = qml.QNode(circuit_decomposed, dev) - - res = circuit(*weights) - res2 = circuit2(*weights) - assert qml.math.allclose(res, res2, atol=tol, rtol=0) - - weights_tuple = tuple(w for w in weights) - res = circuit(*weights_tuple) - res2 = circuit2(*weights_tuple) - assert qml.math.allclose(res, res2, atol=tol, rtol=0) - - def test_autograd(self, tol): - """Tests the autograd interface.""" - - shapes = expected_shapes(1, 2) - weights = [np.random.random(shape) for shape in shapes] - weights = [pnp.array(w, requires_grad=True) for w in weights] - - dev = DummyDevice(wires=2) - - circuit = qml.QNode(circuit_template, dev) - circuit2 = qml.QNode(circuit_decomposed, dev) - - res = circuit(*weights) - res2 = circuit2(*weights) - assert qml.math.allclose(res, res2, atol=tol, rtol=0) - - grad_fn = qml.grad(circuit) - grads = grad_fn(*weights) - - grad_fn2 = qml.grad(circuit2) - grads2 = grad_fn2(*weights) - - assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) - - def test_jax(self, tol, skip_if_no_jax_support): - """Tests the jax interface.""" - - import jax - import jax.numpy as jnp - - shapes = expected_shapes(1, 2) - weights = [np.random.random(shape) for shape in shapes] - weights = [jnp.array(w) for w in weights] - - dev = DummyDevice(wires=2) - - circuit = qml.QNode(circuit_template, dev, interface="jax") - circuit2 = qml.QNode(circuit_decomposed, dev, interface="jax") - - res = circuit(*weights) - res2 = circuit2(*weights) - assert qml.math.allclose(res, res2, atol=tol, rtol=0) - - grad_fn = jax.grad(circuit) - grads = grad_fn(*weights) - - grad_fn2 = jax.grad(circuit2) - grads2 = grad_fn2(*weights) - - assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) - - def test_tf(self, tol, skip_if_no_tf_support): - """Tests the tf interface.""" - - import tensorflow as tf - - shapes = expected_shapes(1, 2) - weights = [np.random.random(shape) for shape in shapes] - weights = [tf.Variable(w) for w in weights] - - dev = DummyDevice(wires=2) - - circuit = qml.QNode(circuit_template, dev, interface="tf") - circuit2 = qml.QNode(circuit_decomposed, dev, interface="tf") - - res = circuit(*weights) - res2 = circuit2(*weights) - assert qml.math.allclose(res, res2, atol=tol, rtol=0) - - with tf.GradientTape() as tape: - res = circuit(*weights) - grads = tape.gradient(res, [*weights]) - - with tf.GradientTape() as tape2: - res2 = circuit2(*weights) - grads2 = tape2.gradient(res2, [*weights]) - - assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) - - def test_torch(self, tol, skip_if_no_torch_support): - """Tests the torch interface.""" - - import torch - - shapes = expected_shapes(1, 2) - weights = [np.random.random(size=shape) for shape in shapes] - weights = [torch.tensor(w, requires_grad=True) for w in weights] - - dev = DummyDevice(wires=2) - - circuit = qml.QNode(circuit_template, dev, interface="torch") - circuit2 = qml.QNode(circuit_decomposed, dev, interface="torch") - - res = circuit(*weights) - res2 = circuit2(*weights) - assert qml.math.allclose(res, res2, atol=tol, rtol=0) - - res = circuit(*weights) - res.backward() - grads = [w.grad for w in weights] - - res2 = circuit2(*weights) - res2.backward() - grads2 = [w.grad for w in weights] - - assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) +# def circuit_decomposed(*weights): +# qml.templates.Interferometer(weights[0][0], weights[1][0], weights[2][0], wires=[0, 1]) +# qml.Squeezing(weights[3][0, 0], weights[4][0, 0], wires=0) +# qml.Squeezing(weights[3][0, 1], weights[4][0, 1], wires=1) +# qml.templates.Interferometer(weights[5][0], weights[6][0], weights[7][0], wires=[0, 1]) +# qml.Displacement(weights[8][0, 0], weights[9][0, 0], wires=0) +# qml.Displacement(weights[8][0, 1], weights[9][0, 1], wires=1) +# qml.Kerr(weights[10][0, 0], wires=0) +# qml.Kerr(weights[10][0, 1], wires=1) +# return qml.expval(qml.X(0)) +# +# +# class TestInterfaces: +# """Tests that the template is compatible with all interfaces, including the computation +# of gradients.""" +# +# def test_list_and_tuples(self, tol): +# """Tests common iterables as inputs.""" +# +# shapes = expected_shapes(1, 2) +# weights = [np.random.random(shape) for shape in shapes] +# +# dev = DummyDevice(wires=2) +# +# circuit = qml.QNode(circuit_template, dev) +# circuit2 = qml.QNode(circuit_decomposed, dev) +# +# res = circuit(*weights) +# res2 = circuit2(*weights) +# assert qml.math.allclose(res, res2, atol=tol, rtol=0) +# +# weights_tuple = tuple(w for w in weights) +# res = circuit(*weights_tuple) +# res2 = circuit2(*weights_tuple) +# assert qml.math.allclose(res, res2, atol=tol, rtol=0) +# +# def test_autograd(self, tol): +# """Tests the autograd interface.""" +# +# shapes = expected_shapes(1, 2) +# weights = [np.random.random(shape) for shape in shapes] +# weights = [pnp.array(w, requires_grad=True) for w in weights] +# +# dev = DummyDevice(wires=2) +# +# circuit = qml.QNode(circuit_template, dev) +# circuit2 = qml.QNode(circuit_decomposed, dev) +# +# res = circuit(*weights) +# res2 = circuit2(*weights) +# assert qml.math.allclose(res, res2, atol=tol, rtol=0) +# +# grad_fn = qml.grad(circuit) +# grads = grad_fn(*weights) +# +# grad_fn2 = qml.grad(circuit2) +# grads2 = grad_fn2(*weights) +# +# assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) +# +# def test_jax(self, tol, skip_if_no_jax_support): +# """Tests the jax interface.""" +# +# import jax +# import jax.numpy as jnp +# +# shapes = expected_shapes(1, 2) +# weights = [np.random.random(shape) for shape in shapes] +# weights = [jnp.array(w) for w in weights] +# +# dev = DummyDevice(wires=2) +# +# circuit = qml.QNode(circuit_template, dev, interface="jax") +# circuit2 = qml.QNode(circuit_decomposed, dev, interface="jax") +# +# res = circuit(*weights) +# res2 = circuit2(*weights) +# assert qml.math.allclose(res, res2, atol=tol, rtol=0) +# +# grad_fn = jax.grad(circuit) +# grads = grad_fn(*weights) +# +# grad_fn2 = jax.grad(circuit2) +# grads2 = grad_fn2(*weights) +# +# assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) +# +# def test_tf(self, tol, skip_if_no_tf_support): +# """Tests the tf interface.""" +# +# import tensorflow as tf +# +# shapes = expected_shapes(1, 2) +# weights = [np.random.random(shape) for shape in shapes] +# weights = [tf.Variable(w) for w in weights] +# +# dev = DummyDevice(wires=2) +# +# circuit = qml.QNode(circuit_template, dev, interface="tf") +# circuit2 = qml.QNode(circuit_decomposed, dev, interface="tf") +# +# res = circuit(*weights) +# res2 = circuit2(*weights) +# assert qml.math.allclose(res, res2, atol=tol, rtol=0) +# +# with tf.GradientTape() as tape: +# res = circuit(*weights) +# grads = tape.gradient(res, [*weights]) +# +# with tf.GradientTape() as tape2: +# res2 = circuit2(*weights) +# grads2 = tape2.gradient(res2, [*weights]) +# +# assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) +# +# def test_torch(self, tol, skip_if_no_torch_support): +# """Tests the torch interface.""" +# +# import torch +# +# shapes = expected_shapes(1, 2) +# weights = [np.random.random(size=shape) for shape in shapes] +# weights = [torch.tensor(w, requires_grad=True) for w in weights] +# +# dev = DummyDevice(wires=2) +# +# circuit = qml.QNode(circuit_template, dev, interface="torch") +# circuit2 = qml.QNode(circuit_decomposed, dev, interface="torch") +# +# res = circuit(*weights) +# res2 = circuit2(*weights) +# assert qml.math.allclose(res, res2, atol=tol, rtol=0) +# +# res = circuit(*weights) +# res.backward() +# grads = [w.grad for w in weights] +# +# res2 = circuit2(*weights) +# res2.backward() +# grads2 = [w.grad for w in weights] +# +# assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) diff --git a/tests/templates/test_layers/test_particle_conserving_u1.py b/tests/templates/test_layers/test_particle_conserving_u1.py index f69f97bc6ce..14d48de6fd2 100644 --- a/tests/templates/test_layers/test_particle_conserving_u1.py +++ b/tests/templates/test_layers/test_particle_conserving_u1.py @@ -49,7 +49,7 @@ def _wires_gates_u1(wires): return exp_wires - def test_particle_conserving_u1_operations(self): + def test_operations(self): """Test the correctness of the ParticleConservingU1 template including the gate count and order, the wires each operation acts on and the correct use of parameters in the circuit.""" @@ -152,43 +152,6 @@ def circuit2(): assert np.allclose(dev.state, dev2.state, atol=tol, rtol=0) - @pytest.mark.parametrize( - ("init_state", "exp_state"), - [ - (np.array([0, 0]), np.array([1.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j])), - ( - np.array([0, 1]), - np.array([0.0 + 0.0j, 0.862093 + 0.0j, 0.0 - 0.506749j, 0.0 + 0.0j]), - ), - ( - np.array([1, 0]), - np.array([0.0 + 0.0j, 0.0 - 0.506749j, 0.862093 + 0.0j, 0.0 + 0.0j]), - ), - (np.array([1, 1]), np.array([0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 1.0 + 0.0j])), - ], - ) - def test_decomposition_u2ex(self, init_state, exp_state, tol): - """Test the decomposition of the U_{2, ex}` exchange gate by asserting the prepared - state.""" - - N = 2 - wires = range(N) - - weight = 0.53141 - - dev = qml.device("default.qubit", wires=N) - - @qml.qnode(dev) - def circuit(weight): - qml.BasisState(init_state, wires=wires) - qml.templates.layers.particle_conserving_u2.u2_ex_gate(weight, wires) - return qml.expval(qml.PauliZ(0)) - - circuit(weight) - - assert np.allclose(circuit.device.state, exp_state, atol=tol) - - @pytest.mark.parametrize( ("init_state", "exp_state"), [ @@ -264,7 +227,7 @@ class TestInputs: ), ], ) - def test_particle_conserving_u1_exceptions(self, weights, n_wires, msg_match): + def test_exceptions(self, weights, n_wires, msg_match): """Test that ParticleConservingU1 throws an exception if the parameter array has an illegal shape.""" diff --git a/tests/templates/test_layers/test_particle_conserving_u2.py b/tests/templates/test_layers/test_particle_conserving_u2.py index 63a28bce1a3..248b3da21ee 100644 --- a/tests/templates/test_layers/test_particle_conserving_u2.py +++ b/tests/templates/test_layers/test_particle_conserving_u2.py @@ -23,115 +23,107 @@ class TestDecomposition: """Tests that the template defines the correct decomposition.""" - @staticmethod - def _wires_gates_u2(wires): - """Auxiliary function giving the wires that the elementary gates decomposing - ``u1_ex_gate`` act on.""" - - exp_wires = [ - wires, - wires, - [wires[1]], - wires, - [wires[1]], - wires, - [wires[0]], - wires[::-1], - wires[::-1], - wires, - wires, - [wires[1]], - wires, - [wires[1]], - wires, - [wires[0]], - ] - - return exp_wires - - def test_particle_conserving_u1_operations(self): - """Test the correctness of the ParticleConservingU1 template including the gate count + @pytest.mark.parametrize( + "layers, qubits, init_state", + [ + (2, 4, np.array([1, 1, 0, 0])), + (1, 6, np.array([1, 1, 0, 0, 0, 0])), + (1, 5, np.array([1, 1, 0, 0, 0])), + ], + ) + def test_operations(self, layers, qubits, init_state): + """Test the correctness of the ParticleConservingU2 template including the gate count and order, the wires each operation acts on and the correct use of parameters in the circuit.""" + weights = np.random.normal(0, 2 * np.pi, (layers, 2 * qubits - 1)) - qubits = 4 - layers = 2 - weights = np.random.random(size=(layers, qubits-1, 2)) - - gates_per_u1 = 16 - gates_per_layer = gates_per_u1 * (qubits - 1) - gate_count = layers * gates_per_layer + 1 + n_gates = 1 + (qubits + (qubits - 1) * 3) * layers - gates_u1 = [ - qml.CZ, - qml.CRot, - qml.PhaseShift, - qml.CNOT, - qml.PhaseShift, - qml.CNOT, - qml.PhaseShift, - ] - gates_ent = gates_u1 + [qml.CZ, qml.CRot] + gates_u1 + exp_gates = ( + [qml.RZ] * qubits + ([qml.CNOT] + [qml.CRX] + [qml.CNOT]) * (qubits - 1) + ) * layers - wires = list(range(qubits)) - - nm_wires = [wires[l: l + 2] for l in range(0, qubits - 1, 2)] - nm_wires += [wires[l: l + 2] for l in range(1, qubits - 1, 2)] - - op = qml.templates.ParticleConservingU1(weights, wires, init_state=np.array([1, 1, 0, 0])) + op = qml.templates.ParticleConservingU2(weights, wires=range(qubits), init_state=init_state) queue = op.expand().operations - assert gate_count == len(queue) + # number of gates + assert len(queue) == n_gates - # check initialization of the qubit register + # initialization assert isinstance(queue[0], qml.BasisState) - # check all quantum operations - idx_CRot = 8 - for l in range(layers): - for i in range(qubits - 1): - exp_wires = self._wires_gates_u1(nm_wires[i]) + # order of gates + for op1, op2 in zip(queue[1:], exp_gates): + assert isinstance(op1, op2) - phi = weights[l, i, 0] - theta = weights[l, i, 1] + # gate parameter + params = np.array( + [ + queue[i].parameters + for i in range(1, n_gates) + if queue[i].parameters != [] + ] + ) + weights[:, qubits:] = weights[:, qubits:] * 2 + assert np.allclose(params.flatten(), weights.flatten()) + + # gate wires + wires = range(qubits) + nm_wires = [wires[l: l + 2] for l in range(0, qubits - 1, 2)] + nm_wires += [wires[l: l + 2] for l in range(1, qubits - 1, 2)] - for j, exp_gate in enumerate(gates_ent): - idx = gates_per_layer * l + gates_per_u1 * i + j + 1 + exp_wires = [] + for _ in range(layers): + for i in range(qubits): + exp_wires.append([wires[i]]) + for j in nm_wires: + exp_wires.append(list(j)) + exp_wires.append(list(j[::-1])) + exp_wires.append(list(j)) - # check that the gates are applied in the right order - assert isinstance(queue[idx], exp_gate) + res_wires = [queue[i].wires.tolist() for i in range(1, n_gates)] - # check the wires the gates act on - assert queue[idx].wires.tolist() == exp_wires[j] + assert res_wires == exp_wires + + @pytest.mark.parametrize( + ("init_state", "exp_state"), + [ + (np.array([0, 0]), np.array([1.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j])), + ( + np.array([0, 1]), + np.array([0.0 + 0.0j, 0.862093 + 0.0j, 0.0 - 0.506749j, 0.0 + 0.0j]), + ), + ( + np.array([1, 0]), + np.array([0.0 + 0.0j, 0.0 - 0.506749j, 0.862093 + 0.0j, 0.0 + 0.0j]), + ), + (np.array([1, 1]), np.array([0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 1.0 + 0.0j])), + ], + ) + def test_decomposition_u2ex(self, init_state, exp_state, tol): + """Test the decomposition of the U_{2, ex}` exchange gate by asserting the prepared + state.""" - # check that parametrized gates take the parameters \phi and \theta properly - if exp_gate is qml.CRot: + N = 2 + wires = range(N) - if j < idx_CRot: - exp_params = [-phi, np.pi, phi] - if j > idx_CRot: - exp_params = [phi, np.pi, -phi] - if j == idx_CRot: - exp_params = [0, 2 * theta, 0] + weight = 0.53141 - assert queue[idx].parameters == exp_params + dev = qml.device("default.qubit", wires=N) - elif exp_gate is qml.PhaseShift: + @qml.qnode(dev) + def circuit(weight): + qml.BasisState(init_state, wires=wires) + qml.templates.layers.particle_conserving_u2.u2_ex_gate(weight, wires) + return qml.expval(qml.PauliZ(0)) - if j < idx_CRot: - exp_params = -phi - if j == idx_CRot / 2: - exp_params = phi - if j > idx_CRot: - exp_params = phi - if j == (3 * idx_CRot + 2) / 2: - exp_params = -phi + circuit(weight) - assert queue[idx].parameters == exp_params + assert np.allclose(circuit.device.state, exp_state, atol=tol) def test_custom_wire_labels(self, tol): """Test that template can deal with non-numeric, nonconsecutive wire labels.""" - weights = np.random.random(size=(1, 2, 2)) + weights = np.random.random(size=(1, 5)) init_state = np.array([1, 1, 0]) dev = qml.device("default.qubit", wires=3) @@ -139,12 +131,12 @@ def test_custom_wire_labels(self, tol): @qml.qnode(dev) def circuit(): - qml.templates.ParticleConservingU1(weights, wires=range(3), init_state=init_state) + qml.templates.ParticleConservingU2(weights, wires=range(3), init_state=init_state) return qml.expval(qml.Identity(0)) @qml.qnode(dev2) def circuit2(): - qml.templates.ParticleConservingU1(weights, wires=["z", "a", "k"], init_state=init_state) + qml.templates.ParticleConservingU2(weights, wires=["z", "a", "k"], init_state=init_state) return qml.expval(qml.Identity("z")) circuit() @@ -157,29 +149,50 @@ class TestInputs: """Test inputs and pre-processing.""" @pytest.mark.parametrize( - ("weights", "n_wires", "msg_match"), + ("weights", "wires", "msg_match"), [ - (np.ones((4, 3)), 4, "Weights tensor must"), - (np.ones((4, 2, 2)), 4, "Weights tensor must"), - (np.ones((4, 3, 1)), 4, "Weights tensor must"), ( - np.ones((4, 3, 1)), - 1, - "Expected the number of qubits", + np.array([[-0.080, 2.629, -0.710, 5.383, 0.646, -2.872, -3.856]]), + [0], + "This template requires the number of qubits to be greater than one", + ), + ( + np.array([[-0.080, 2.629, -0.710, 5.383]]), + [0, 1, 2, 3], + "Weights tensor must", + ), + ( + np.array( + [ + [-0.080, 2.629, -0.710, 5.383, 0.646, -2.872], + [-0.080, 2.629, -0.710, 5.383, 0.646, -2.872], + ] + ), + [0, 1, 2, 3], + "Weights tensor must", + ), + ( + np.array([-0.080, 2.629, -0.710, 5.383, 0.646, -2.872]), + [0, 1, 2, 3], + "Weights tensor must be 2-dimensional", ), ], ) - def test_particle_conserving_u1_exceptions(self, weights, n_wires, msg_match): - """Test that ParticleConservingU1 throws an exception if the parameter array has an illegal - shape.""" - - wires = range(n_wires) + def test_exceptions(self, weights, wires, msg_match): + """Test that ParticleConservingU2 throws an exception if the parameters have illegal + shapes, types or values.""" + N = len(wires) init_state = np.array([1, 1, 0, 0]) - dev = qml.device("default.qubit", wires=n_wires) + + dev = qml.device("default.qubit", wires=N) @qml.qnode(dev) def circuit(): - qml.templates.ParticleConservingU1(weights=weights, wires=wires, init_state=init_state) + qml.templates.ParticleConservingU2( + weights=weights, + wires=wires, + init_state=init_state, + ) return qml.expval(qml.PauliZ(0)) with pytest.raises(ValueError, match=msg_match): @@ -191,43 +204,31 @@ class TestAttributes: @pytest.mark.parametrize("n_layers, n_wires, expected_shape", [ - (2, 3, (2, 2, 2)), - (2, 2, (2, 1, 2)), + (2, 3, (2, 5)), + (2, 2, (2, 3)), + (1, 3, (1, 5)), + ] ) def test_shape(self, n_layers, n_wires, expected_shape): """Test that the shape method returns the correct shape of the weights tensor""" - shape = qml.templates.ParticleConservingU1.shape(n_layers, n_wires) + shape = qml.templates.ParticleConservingU2.shape(n_layers, n_wires) assert shape == expected_shape def circuit_template(weights): - qml.templates.ParticleConservingU1(weights, range(2), init_state=np.array([1, 1])) + qml.templates.ParticleConservingU2(weights, range(2), init_state=np.array([1, 1])) return qml.expval(qml.PauliZ(0)) def circuit_decomposed(weights): qml.BasisState(np.array([1, 1]), wires=[0, 1]) - qml.CZ(wires=[0, 1]) - qml.CRot(weights[0, 0, 0], np.pi, weights[0, 0, 0], - wires=[0, 1]) - qml.PhaseShift(-weights[0, 0, 0], wires=[1]) - qml.CNOT(wires=[0, 1]) - qml.PhaseShift(weights[0, 0, 0], wires=[1]) + qml.RZ(weights[0, 0], wires=[0]) + qml.RZ(weights[0, 1], wires=[1]) qml.CNOT(wires=[0, 1]) - qml.PhaseShift(-weights[0,0, 0], wires=[0]) - qml.CZ(wires=[1, 0]) - qml.CRot(0, weights[0, 0, 1], 0, wires=[1, 0]) - qml.CZ(wires=[0, 1]) - qml.CRot(weights[0, 0, 0], np.pi, -weights[0, 0], - wires=[0, 1]) - qml.PhaseShift(weights[0, 0, 0], wires=[1]) + qml.CRX(weights[0, 2], wires=[1, 0]) qml.CNOT(wires=[0, 1]) - qml.PhaseShift(-weights[0, 0, 0], wires=[1]) - qml.CNOT(wires=[0, 1]) - qml.PhaseShift(weights[0, 0, 0], wires=[0]) - return qml.expval(qml.PauliZ(0)) @@ -238,7 +239,7 @@ class TestInterfaces: def test_list_and_tuples(self, tol): """Tests common iterables as inputs.""" - weights = [[[0.1, -1.1]]] + weights = [[0.1, -1.1, 0.2]] dev = qml.device("default.qubit", wires=2) @@ -257,7 +258,7 @@ def test_list_and_tuples(self, tol): def test_autograd(self, tol): """Tests the autograd interface.""" - weights = np.random.random(size=(1, 1, 2)) + weights = np.random.random(size=(1, 3)) weights = pnp.array(weights, requires_grad=True) dev = qml.device("default.qubit", wires=2) @@ -283,7 +284,7 @@ def test_jax(self, tol, skip_if_no_jax_support): import jax import jax.numpy as jnp - weights = jnp.array(np.random.random(size=(1, 1, 2))) + weights = jnp.array(np.random.random(size=(1, 3))) dev = qml.device("default.qubit", wires=2) @@ -307,7 +308,7 @@ def test_tf(self, tol, skip_if_no_tf_support): import tensorflow as tf - weights = tf.Variable(np.random.random(size=(1, 1, 2))) + weights = tf.Variable(np.random.random(size=(1, 3))) dev = qml.device("default.qubit", wires=2) @@ -333,7 +334,7 @@ def test_torch(self, tol, skip_if_no_torch_support): import torch - weights = torch.tensor(np.random.random(size=(1, 1, 2)), requires_grad=True) + weights = torch.tensor(np.random.random(size=(1, 3)), requires_grad=True) dev = qml.device("default.qubit", wires=2) From 1431e3547996b56f583d3c564ab63f4db83e8ae0 Mon Sep 17 00:00:00 2001 From: Maria Schuld Date: Thu, 25 Mar 2021 17:36:54 +0200 Subject: [PATCH 07/16] update changelog --- .github/CHANGELOG.md | 7 +- tests/templates/test_layers.py | 1010 -------------------------------- 2 files changed, 4 insertions(+), 1013 deletions(-) delete mode 100644 tests/templates/test_layers.py diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 2ed70fb58ba..0f9aaa69f61 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -339,11 +339,12 @@

Improvements

-- The `QAOAEmbedding` and `BasicEntanglerLayers` are now classes inheriting +- The embedding and layer templates are now classes inheriting from `Operation`, and define the ansatz in their `expand()` method. This change does not affect the user interface. - - For convenience, the class has a method that returns the shape of the + [(#1163)](https://github.com/PennyLaneAI/pennylane/pull/1163) + + For convenience, some templates now provide a method that returns the shape of the trainable parameter tensor, i.e., ```python diff --git a/tests/templates/test_layers.py b/tests/templates/test_layers.py deleted file mode 100644 index b1d7ddc77c0..00000000000 --- a/tests/templates/test_layers.py +++ /dev/null @@ -1,1010 +0,0 @@ -# Copyright 2018-2020 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Unit tests for the :mod:`pennylane.template.layers` module. -Integration tests should be placed into ``test_templates.py``. -""" -# pylint: disable=protected-access,cell-var-from-loop -import pytest -import pennylane as qml -from pennylane import numpy as np -from pennylane.templates.layers import ( - CVNeuralNetLayers, - StronglyEntanglingLayers, - RandomLayers, - BasicEntanglerLayers, - SimplifiedTwoDesign, - ParticleConservingU2, - ParticleConservingU1, -) -from pennylane import RX, RY, RZ, CZ, CNOT -from pennylane.wires import Wires -from pennylane.numpy import tensor -from pennylane.init import particle_conserving_u1_normal - -TOLERANCE = 1e-8 - - -class TestCVNeuralNet: - """Tests for the CVNeuralNet from the pennylane.template module.""" - - # Have a fixed number of subsystems in this handcoded test - @pytest.fixture(scope="class") - def num_subsystems(self): - return 4 - - @pytest.fixture(scope="class") - def weights(self): - return [ - np.array( - [ - [5.48791879, 6.08552046, 5.46131036, 3.33546468, 1.46227521, 0.0716208], - [3.36869403, 0.63074883, 4.59400392, 5.9040016, 5.92704296, 2.35455147], - ] - ), - np.array( - [ - [2.70471535, 2.52804815, 3.28406182, 3.0058243, 3.48940764, 3.41419504], - [3.74320919, 4.15936005, 3.20807161, 2.95870535, 0.05574621, 0.42660569], - ] - ), - np.array( - [ - [4.7808479, 4.47598146, 3.89357744, 2.67721355], - [2.73203094, 2.71115444, 1.16794164, 3.32823666], - ] - ), - np.array( - [ - [0.27344502, 0.68431314, 0.30026443, 0.23128064], - [0.45945175, 0.53255468, 0.28383751, 0.34263728], - ] - ), - np.array( - [ - [2.3936353, 4.80135971, 5.89867895, 2.00867023], - [5.14552399, 3.31578667, 5.90119363, 4.54515204], - ] - ), - np.array( - [ - [0.4134863, 6.17555778, 0.80334114, 2.02400747, 0.44574704, 1.41227118], - [5.16969442, 3.6890488, 4.43916808, 3.20808287, 5.21543123, 4.52815349], - ] - ), - np.array( - [ - [2.47328111, 5.63064513, 2.17059932, 6.1873632, 0.18052879, 2.20970037], - [5.44288268, 1.27806129, 1.87574979, 2.98956484, 3.10140853, 3.81814174], - ] - ), - np.array( - [ - [5.03318258, 4.01017269, 0.43159284, 3.7928101], - [3.5329307, 4.79661266, 5.0683084, 1.87631749], - ] - ), - np.array( - [ - [1.61159166, 0.1608155, 0.96535086, 1.60132783], - [0.36293094, 1.30725604, 0.11578591, 1.5983082], - ] - ), - np.array( - [ - [6.21267547, 3.71076099, 0.34060195, 2.86031556], - [3.20443756, 6.26536946, 6.18450567, 1.50406923], - ] - ), - np.array( - [ - [0.1376345, 0.22541113, 0.14306356, 0.13019402], - [0.26999146, 0.26256351, 0.14722687, 0.23137066], - ] - ), - ] - - def test_cvneuralnet_uses_correct_weights(self, weights): - """Tests that the CVNeuralNetLayers template uses the weigh parameters correctly.""" - - with qml.tape.OperationRecorder() as rec: - CVNeuralNetLayers(*weights, wires=range(4)) - - # Test that gates appear in the right order for each layer: - # BS-R-S-BS-R-D-K - for l in range(2): - gates = [ - qml.Beamsplitter, - qml.Rotation, - qml.Squeezing, - qml.Beamsplitter, - qml.Rotation, - qml.Displacement, - ] - - # count the position of each group of gates in the layer - num_gates_per_type = [0, 6, 4, 4, 6, 4, 4, 4] - s = np.cumsum(num_gates_per_type) - gc = l * sum(num_gates_per_type) + np.array(list(zip(s[:-1], s[1:]))) - - # loop through expected gates - for idx, g in enumerate(gates): - # loop through where these gates should be in the queue - for opidx, op in enumerate(rec.queue[gc[idx, 0] : gc[idx, 1]]): - # check that op in queue is correct gate - assert isinstance(op, g) - - # test that the parameters are correct - res_params = op.parameters - - if idx == 0: - # first BS - exp_params = [weights[0][l][opidx], weights[1][l][opidx]] - elif idx == 1: - # first rot - exp_params = [weights[2][l][opidx]] - elif idx == 2: - # squeezing - exp_params = [weights[3][l][opidx], weights[4][l][opidx]] - elif idx == 3: - # second BS - exp_params = [weights[5][l][opidx], weights[6][l][opidx]] - elif idx == 4: - # second rot - exp_params = [weights[7][l][opidx]] - elif idx == 5: - # displacement - exp_params = [weights[8][l][opidx], weights[9][l][opidx]] - - assert res_params == exp_params - - def test_cvqnn_layers_exception_nlayers(self, gaussian_device_4modes): - """Integration test for the CVNeuralNetLayers method.""" - - def circuit(weights): - CVNeuralNetLayers(*weights, wires=range(4)) - return qml.expval(qml.X(0)) - - qnode = qml.QNode(circuit, gaussian_device_4modes) - - wrong_weights = [np.array([1]) if i < 10 else np.array([1, 1]) for i in range(11)] - with pytest.raises(ValueError, match="The first dimension of all parameters"): - qnode(wrong_weights) - - def test_cvqnn_layers_exception_second_dim(self, weights, gaussian_device_4modes): - """Integration test for the CVNeuralNetLayers method.""" - - def circuit(*weights): - CVNeuralNetLayers(*weights, wires=range(4)) - return qml.expval(qml.X(0)) - - qnode = qml.QNode(circuit, gaussian_device_4modes) - - wrong_weights = [np.array([[0, 1, 0, 0, 0], [0, 1, 0, 0, 0]])] + weights[1:] - with pytest.raises(ValueError, match="Got unexpected shape for one or more parameters"): - qnode(*wrong_weights) - - -class TestStronglyEntangling: - """Tests for the StronglyEntanglingLayers method from the pennylane.templates.layers module.""" - - @pytest.mark.parametrize("n_layers", range(1, 4)) - def test_single_qubit(self, n_layers): - weights = np.zeros((n_layers, 1, 3)) - with qml.tape.OperationRecorder() as rec: - StronglyEntanglingLayers(weights, wires=range(1)) - - assert len(rec.queue) == n_layers - assert all([isinstance(q, qml.Rot) for q in rec.queue]) - assert all([q._wires[0] == 0 for q in rec.queue]) - - def test_uses_correct_weights(self, n_subsystems): - """Test that correct weights are used in the circuit.""" - np.random.seed(12) - n_layers = 2 - num_wires = n_subsystems - - weights = np.random.randn(n_layers, num_wires, 3) - - with qml.tape.OperationRecorder() as rec: - StronglyEntanglingLayers(weights, wires=range(num_wires)) - - # Test that gates appear in the right order - exp_gates = [qml.Rot] * num_wires + [qml.CNOT] * num_wires - exp_gates *= n_layers - res_gates = rec.queue - - for op1, op2 in zip(res_gates, exp_gates): - assert isinstance(op1, op2) - - # test the device parameters - for l in range(n_layers): - - layer_ops = rec.queue[2 * l * num_wires : 2 * (l + 1) * num_wires] - - # check each rotation gate parameter - for n in range(num_wires): - res_params = layer_ops[n].parameters - exp_params = weights[l, n, :] - assert sum([r == e for r, e in zip(res_params, exp_params)]) - - def test_uses_correct_number_of_imprimitives(self, n_layers, n_subsystems): - """Test that correct number of imprimitives are used in the circuit.""" - imprimitive = CZ - weights = np.random.randn(n_layers, n_subsystems, 3) - - with qml.tape.OperationRecorder() as rec: - StronglyEntanglingLayers( - weights=weights, wires=range(n_subsystems), imprimitive=imprimitive - ) - - types = [type(q) for q in rec.queue] - assert types.count(imprimitive) == n_subsystems * n_layers - - def test_exception_wrong_dim(self): - """Verifies that exception is raised if the - number of dimensions of features is incorrect.""" - - dev = qml.device("default.qubit", wires=2) - - @qml.qnode(dev) - def circuit(weights): - StronglyEntanglingLayers(weights, wires=range(2)) - return qml.expval(qml.PauliZ(0)) - - with pytest.raises(ValueError, match="Weights tensor must have second dimension"): - weights = np.random.randn(2, 1, 3) - circuit(weights) - - with pytest.raises(ValueError, match="Weights tensor must have third dimension"): - weights = np.random.randn(2, 2, 1) - circuit(weights) - - with pytest.raises(ValueError, match="Weights tensor must be 3-dimensional"): - weights = np.random.randn(2, 2, 3, 1) - circuit(weights) - - -class TestRandomLayers: - """Tests for the RandomLayers method from the pennylane.templates module.""" - - @pytest.fixture(scope="class", params=[0.2, 0.6]) - def ratio(self, request): - return request.param - - @pytest.fixture(scope="class", params=[CNOT, CZ]) - def impr(self, request): - return request.param - - @pytest.fixture(scope="class", params=[[RX], [RY, RZ]]) - def rots(self, request): - return request.param - - def test_same_circuit_for_same_seed(self, tol, seed): - """Test that RandomLayers() creates the same circuit when using the same seed.""" - dev = qml.device("default.qubit", wires=2) - weights = [[0.1, 0.2, 0.3]] - - def circuit(weights): - RandomLayers(weights=weights, wires=range(2), seed=seed) - return qml.expval(qml.PauliZ(0)) - - qnode1 = qml.QNode(circuit, dev) - qnode2 = qml.QNode(circuit, dev) - assert np.allclose(qnode1(weights), qnode2(weights), atol=tol) - - def test_different_circuits_for_different_seeds(self, tol): - """Test that RandomLayers() does not necessarily have the same output for two different seeds.""" - dev = qml.device("default.qubit", wires=2) - weights = [[0.1, 0.2, 0.3]] - - def circuit1(weights): - RandomLayers(weights=weights, wires=range(2), seed=10) - return qml.expval(qml.PauliZ(0)) - - def circuit2(weights): - RandomLayers(weights=weights, wires=range(2), seed=20) - return qml.expval(qml.PauliZ(0)) - - qnode1 = qml.QNode(circuit1, dev) - qnode2 = qml.QNode(circuit2, dev) - - assert not np.allclose(qnode1(weights), qnode2(weights), atol=tol) - - @pytest.mark.parametrize("mutable", [True, False]) - def test_same_circuit_in_each_qnode_call(self, mutable, tol): - """Test that RandomLayers() creates the same circuit in two calls of a qnode.""" - dev = qml.device("default.qubit", wires=2) - weights = [[0.1, 0.2, 0.3]] - - @qml.qnode(dev, mutable=mutable) - def circuit(weights): - RandomLayers(weights=weights, wires=range(2)) - return qml.expval(qml.PauliZ(0)) - - first_call = circuit(weights) - second_call = circuit(weights) - assert np.allclose(first_call, second_call, atol=tol) - - @pytest.mark.xfail(reason="mutable qnodes are deprecated") - def test_no_seed(self, tol): - """Test that two calls to a qnode with RandomLayers() for 'seed=None' option create the - same circuit for immutable qnodes.""" - - dev = qml.device("default.qubit", wires=2) - weights = [[0.1] * 100] - - @qml.qnode(dev, mutable=False) - def circuit(weights): - RandomLayers(weights=weights, wires=range(2), seed=None) - return qml.expval(qml.PauliZ(0)) - - first_call = circuit(weights) - second_call = circuit(weights) - assert np.allclose(first_call, second_call, atol=tol) - - def test_random_layers_nlayers(self, n_layers): - """Test that RandomLayers() picks the correct number of gates.""" - np.random.seed(12) - n_rots = 1 - n_wires = 2 - impr = CNOT - weights = np.random.randn(n_layers, n_rots) - - with qml.tape.OperationRecorder() as rec: - RandomLayers(weights=weights, wires=range(n_wires)) - - types = [type(q) for q in rec.queue] - assert len(types) - types.count(impr) == n_layers - - def test_random_layer_ratio_imprimitive(self, ratio): - """Test that random_layer() has the right ratio of imprimitive gates.""" - n_rots = 500 - n_wires = 2 - impr = CNOT - weights = np.random.randn(n_rots) - - with qml.tape.OperationRecorder() as rec: - random_layer( - weights=weights, - wires=Wires(range(n_wires)), - ratio_imprim=ratio, - imprimitive=CNOT, - rotations=[RX, RY, RZ], - seed=42, - ) - - types = [type(q) for q in rec.queue] - ratio_impr = types.count(impr) / len(types) - assert np.isclose(ratio_impr, ratio, atol=0.05) - - def test_random_layer_gate_types(self, n_subsystems, impr, rots): - """Test that random_layer() uses the correct types of gates.""" - n_rots = 20 - weights = np.random.randn(n_rots) - - with qml.tape.OperationRecorder() as rec: - random_layer( - weights=weights, - wires=Wires(range(n_subsystems)), - ratio_imprim=0.3, - imprimitive=impr, - rotations=rots, - seed=42, - ) - - types = [type(q) for q in rec.queue] - unique = set(types) - gates = {impr, *rots} - assert unique == gates - - def test_random_layer_numgates(self, n_subsystems): - """Test that random_layer() uses the correct number of gates.""" - n_rots = 5 - weights = np.random.randn(n_rots) - - with qml.tape.OperationRecorder() as rec: - random_layer( - weights=weights, - wires=Wires(range(n_subsystems)), - ratio_imprim=0.3, - imprimitive=qml.CNOT, - rotations=[RX, RY, RZ], - seed=42, - ) - - types = [type(q) for q in rec.queue] - assert len(types) - types.count(qml.CNOT) == n_rots - - def test_random_layer_randomwires(self, n_subsystems): - """Test that random_layer() picks random wires.""" - n_rots = 500 - weights = np.random.randn(n_rots) - - with qml.tape.OperationRecorder() as rec: - random_layer( - weights=weights, - wires=Wires(range(n_subsystems)), - ratio_imprim=0.3, - imprimitive=qml.CNOT, - rotations=[RX, RY, RZ], - seed=42, - ) - - wires = [q._wires.tolist() for q in rec.queue] - wires_flat = [item for w in wires for item in w] - mean_wire = np.mean(wires_flat) - assert np.isclose(mean_wire, (n_subsystems - 1) / 2, atol=0.05) - - def test_random_layer_weights(self, n_subsystems, tol): - """Test that random_layer() uses the correct weights.""" - np.random.seed(12) - n_rots = 5 - weights = np.random.randn(n_rots) - - with qml.tape.OperationRecorder() as rec: - random_layer( - weights=weights, - wires=Wires(range(n_subsystems)), - ratio_imprim=0.3, - imprimitive=qml.CNOT, - rotations=[RX, RY, RZ], - seed=4, - ) - - params = [q.parameters for q in rec.queue] - params_flat = [item for p in params for item in p] - assert np.allclose(weights.flatten(), params_flat, atol=tol) - - def test_tensor_gradient_no_error(self, monkeypatch): - """Tests that the gradient calculation of a circuit that contains a - RandomLayers template taking a PennyLane tensor as differentiable - argument executes without error. - """ - dev = qml.device("default.qubit", wires=4) - - @qml.qnode(dev) - def circuit(phi): - - # Random quantum circuit - RandomLayers(phi, wires=list(range(4))) - return qml.expval(qml.PauliZ(0)) - - phi = tensor([[0.04439891, 0.14490549, 3.29725643, 2.51240058]]) - - qml.jacobian(circuit)(phi) - - def test_exception_wrong_dim(self): - """Verifies that exception is raised if the - number of dimensions of features is incorrect.""" - - dev = qml.device("default.qubit", wires=4) - - @qml.qnode(dev) - def circuit(phi): - RandomLayers(phi, wires=list(range(4))) - return qml.expval(qml.PauliZ(0)) - - phi = np.array([0.04439891, 0.14490549, 3.29725643, 2.51240058]) - - with pytest.raises(ValueError, match="Weights tensor must be 2-dimensional"): - circuit(phi) - -class TestSimplifiedTwoDesign: - """Tests for the SimplifiedTwoDesign method from the pennylane.templates.layers module.""" - - @pytest.mark.parametrize( - "n_wires, n_layers, shape_weights", - [(1, 2, (0,)), (2, 2, (2, 1, 2)), (3, 2, (2, 2, 2)), (4, 2, (2, 3, 2))], - ) - def test_circuit_queue(self, n_wires, n_layers, shape_weights): - """Tests the gate types in the circuit.""" - np.random.seed(42) - initial_layer = np.random.randn(n_wires) - weights = np.random.randn(*shape_weights) - - with qml.tape.OperationRecorder() as rec: - SimplifiedTwoDesign(initial_layer, weights, wires=range(n_wires)) - - # Test that gates appear in the right order - exp_gates = [qml.CZ, qml.RY, qml.RY] * ((n_wires // 2) + (n_wires - 1) // 2) - exp_gates *= n_layers - exp_gates = [qml.RY] * n_wires + exp_gates - - res_gates = rec.queue - - for op1, op2 in zip(res_gates, exp_gates): - assert isinstance(op1, op2) - - @pytest.mark.parametrize( - "n_wires, n_layers, shape_weights", - [(1, 2, (0,)), (2, 2, (2, 1, 2)), (3, 2, (2, 2, 2)), (4, 2, (2, 3, 2))], - ) - def test_circuit_parameters(self, n_wires, n_layers, shape_weights): - """Tests the parameter values in the circuit.""" - np.random.seed(42) - initial_layer = np.random.randn(n_wires) - weights = np.random.randn(*shape_weights) - - with qml.tape.OperationRecorder() as rec: - SimplifiedTwoDesign(initial_layer, weights, wires=range(n_wires)) - - # test the device parameters - for l in range(n_layers): - # only select the rotation gates - ops = [gate for gate in rec.queue if isinstance(gate, qml.RY)] - - # check each initial_layer gate parameters - for n in range(n_wires): - res_param = ops[n].parameters[0] - exp_param = initial_layer[n] - assert res_param == exp_param - - # check layer gate parameters - ops = ops[n_wires:] - exp_params = weights.flatten() - for o, exp_param in zip(ops, exp_params): - res_param = o.parameters[0] - assert res_param == exp_param - - @pytest.mark.parametrize( - "initial_layer_weights, weights, n_wires, target", - [ - ([np.pi], [], 1, [-1]), - ([np.pi] * 2, [[[np.pi] * 2]], 2, [1, 1]), - ([np.pi] * 3, [[[np.pi] * 2] * 2], 3, [1, -1, 1]), - ([np.pi] * 4, [[[np.pi] * 2] * 3], 4, [1, -1, -1, 1]), - ], - ) - def test_correct_target_output(self, initial_layer_weights, weights, n_wires, target): - """Tests the result of the template for simple cases.""" - dev = qml.device("default.qubit", wires=n_wires) - - @qml.qnode(dev) - def circuit(initial_layer, weights): - SimplifiedTwoDesign( - initial_layer_weights=initial_layer, weights=weights, wires=range(n_wires) - ) - return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_wires)] - - expectations = circuit(initial_layer_weights, weights) - for exp, target_exp in zip(expectations, target): - assert pytest.approx(exp, target_exp, abs=TOLERANCE) - - def test_exception_wrong_dim(self): - """Verifies that exception is raised if the - number of dimensions of features is incorrect.""" - - dev = qml.device("default.qubit", wires=4) - initial_layer = np.random.randn(2) - - @qml.qnode(dev) - def circuit(initial_layer, weights): - SimplifiedTwoDesign(initial_layer, weights, wires=range(2)) - return qml.expval(qml.PauliZ(0)) - - with pytest.raises(ValueError, match="Weights tensor must have second dimension"): - weights = np.random.randn(2, 2, 2) - circuit(initial_layer, weights) - - with pytest.raises(ValueError, match="Weights tensor must have third dimension"): - weights = np.random.randn(2, 1, 3) - circuit(initial_layer, weights) - - with pytest.raises(ValueError, match="Initial layer weights must be of shape"): - initial_layer = np.random.randn(3) - weights = np.random.randn(2, 1, 2) - circuit(initial_layer, weights) - - -class TestBasicEntangler: - """Tests for the BasicEntanglerLayers method from the pennylane.templates.layers module.""" - - @pytest.mark.parametrize( - "weights, n_wires, target", - [ - ([[np.pi]], 1, [-1]), - ([[np.pi] * 2], 2, [-1, 1]), - ([[np.pi] * 3], 3, [1, 1, -1]), - ([[np.pi] * 4], 4, [-1, 1, -1, 1]), - ], - ) - def test_simple_target_outputs(self, weights, n_wires, target): - """Tests the result of the template for simple cases.""" - - dev = qml.device("default.qubit", wires=n_wires) - - @qml.qnode(dev) - def circuit(weights): - BasicEntanglerLayers(weights=weights, wires=range(n_wires), rotation=RX) - return [qml.expval(qml.PauliZ(wires=i)) for i in range(n_wires)] - - expectations = circuit(weights) - for exp, target_exp in zip(expectations, target): - assert exp == target_exp - - def test_exception_wrong_dim(self): - """Verifies that exception is raised if the - number of dimensions of features is incorrect.""" - - n_wires = 1 - dev = qml.device('default.qubit', wires=n_wires) - - @qml.qnode(dev) - def circuit(weights): - BasicEntanglerLayers(weights=weights, wires=range(n_wires)) - return [qml.expval(qml.PauliZ(i)) for i in range(n_wires)] - - with pytest.raises(ValueError, match="Weights tensor must be 2-dimensional"): - circuit([1, 0]) - - with pytest.raises(ValueError, match="Weights tensor must have second dimension of length"): - circuit([[1, 0], [1, 0]]) - - -class TestParticleConservingU2: - """Tests for the ParticleConservingU2 template from the pennylane.templates.layers module.""" - - @pytest.mark.parametrize( - "layers, qubits, init_state", - [ - (2, 4, np.array([1, 1, 0, 0])), - (1, 6, np.array([1, 1, 0, 0, 0, 0])), - (1, 5, np.array([1, 1, 0, 0, 0])), - ], - ) - def test_u2_operations(self, layers, qubits, init_state): - """Test the correctness of the ParticleConservingU2 template including the gate count - and order, the wires each operation acts on and the correct use of parameters - in the circuit.""" - weights = np.random.normal(0, 2 * np.pi, (layers, 2 * qubits - 1)) - - n_gates = 1 + (qubits + (qubits - 1) * 3) * layers - - exp_gates = ( - [qml.RZ] * qubits + ([qml.CNOT] + [qml.CRX] + [qml.CNOT]) * (qubits - 1) - ) * layers - - with qml.tape.OperationRecorder() as rec: - ParticleConservingU2(weights, wires=range(qubits), init_state=init_state) - - # number of gates - assert len(rec.queue) == n_gates - - # initialization - assert isinstance(rec.queue[0], qml.BasisState) - - # order of gates - for op1, op2 in zip(rec.queue[1:], exp_gates): - assert isinstance(op1, op2) - - # gate parameter - params = np.array( - [ - rec.queue[i].parameters - for i in range(1, n_gates) - if rec.queue[i].parameters != [] - ] - ) - weights[:, qubits:] = weights[:, qubits:] * 2 - assert np.allclose(params.flatten(), weights.flatten()) - - # gate wires - wires = Wires(range(qubits)) - nm_wires = [wires.subset([l, l + 1]) for l in range(0, qubits - 1, 2)] - nm_wires += [wires.subset([l, l + 1]) for l in range(1, qubits - 1, 2)] - - exp_wires = [] - for _ in range(layers): - for i in range(qubits): - exp_wires.append(wires.subset([i])) - for j in nm_wires: - exp_wires.append(j) - exp_wires.append(j[::-1]) - exp_wires.append(j) - - res_wires = [rec.queue[i]._wires for i in range(1, n_gates)] - - assert res_wires == exp_wires - - - - @pytest.mark.parametrize( - ("weights", "wires", "expected"), - [ - ( - np.array([[-2.712, -1.958, 1.875, 1.811, 0.296, -0.412, 1.723]]), - [0, 1, 2, 3], - [-1.0, 0.95402475, -0.95402475, 1.0], - ) - ], - ) - def test_u2_integration(self, weights, wires, expected): - """Test integration with PennyLane and gradient calculations""" - - N = len(wires) - dev = qml.device("default.qubit", wires=N) - - @qml.qnode(dev) - def circuit(): - ParticleConservingU2( - weights, - wires, - init_state=np.array([1, 1, 0, 0]), - ) - return [qml.expval(qml.PauliZ(w)) for w in range(N)] - - res = circuit() - assert np.allclose(res, np.array(expected)) - - -class TestParticleConservingU1: - """Tests for the ParticleConservingU1 template from the - pennylane.templates.layers module.""" - - @staticmethod - def _wires_gates_u1(wires): - """Auxiliary function giving the wires that the elementary gates decomposing - ``u1_ex_gate`` act on.""" - - exp_wires = [ - wires, - wires, - wires[1], - wires, - wires[1], - wires, - wires[0], - wires[::-1], - wires[::-1], - wires, - wires, - wires[1], - wires, - wires[1], - wires, - wires[0], - ] - - return exp_wires - - def test_particle_conserving_u1_operations(self): - """Test the correctness of the ParticleConservingU1 template including the gate count - and order, the wires each operation acts on and the correct use of parameters - in the circuit.""" - - qubits = 4 - layers = 2 - weights = particle_conserving_u1_normal(layers, qubits) - - gates_per_u1 = 16 - gates_per_layer = gates_per_u1 * (qubits - 1) - gate_count = layers * gates_per_layer + 1 - - gates_u1 = [ - qml.CZ, - qml.CRot, - qml.PhaseShift, - qml.CNOT, - qml.PhaseShift, - qml.CNOT, - qml.PhaseShift, - ] - gates_ent = gates_u1 + [qml.CZ, qml.CRot] + gates_u1 - - wires = Wires(range(qubits)) - - nm_wires = [wires.subset([l, l + 1]) for l in range(0, qubits - 1, 2)] - nm_wires += [wires.subset([l, l + 1]) for l in range(1, qubits - 1, 2)] - - with qml.tape.OperationRecorder() as rec: - ParticleConservingU1(weights, wires, init_state=np.array([1, 1, 0, 0])) - - assert gate_count == len(rec.queue) - - # check initialization of the qubit register - assert isinstance(rec.queue[0], qml.BasisState) - - # check all quantum operations - idx_CRot = 8 - for l in range(layers): - for i in range(qubits - 1): - exp_wires = self._wires_gates_u1(nm_wires[i]) - - phi = weights[l, i, 0] - theta = weights[l, i, 1] - - for j, exp_gate in enumerate(gates_ent): - idx = gates_per_layer * l + gates_per_u1 * i + j + 1 - - # check that the gates are applied in the right order - assert isinstance(rec.queue[idx], exp_gate) - - # check the wires the gates act on - assert rec.queue[idx]._wires == Wires(exp_wires[j]) - - # check that parametrized gates take the parameters \phi and \theta properly - if exp_gate is qml.CRot: - - if j < idx_CRot: - exp_params = [-phi, np.pi, phi] - if j > idx_CRot: - exp_params = [phi, np.pi, -phi] - if j == idx_CRot: - exp_params = [0, 2 * theta, 0] - - assert rec.queue[idx].parameters == exp_params - - elif exp_gate is qml.PhaseShift: - - if j < idx_CRot: - exp_params = -phi - if j == idx_CRot / 2: - exp_params = phi - if j > idx_CRot: - exp_params = phi - if j == (3 * idx_CRot + 2) / 2: - exp_params = -phi - - assert rec.queue[idx].parameters == exp_params - - @pytest.mark.parametrize( - ("weights", "n_wires", "msg_match"), - [ - (np.ones((4, 3)), 4, "Weights tensor must"), - (np.ones((4, 2, 2)), 4, "Weights tensor must"), - (np.ones((4, 3, 1)), 4, "Weights tensor must"), - ( - np.ones((4, 3, 1)), - 1, - "This template requires the number of qubits to be greater than one", - ), - ], - ) - def test_particle_conserving_u1_exceptions(self, weights, n_wires, msg_match): - """Test that ParticleConservingU1 throws an exception if the parameter array has an illegal - shape.""" - - wires = range(n_wires) - dev = qml.device("default.qubit", wires=n_wires) - - def circuit(weights=weights, wires=None, init_state=None): - ParticleConservingU1(weights=weights, wires=wires, init_state=init_state) - return qml.expval(qml.PauliZ(0)) - - with pytest.raises(ValueError, match=msg_match): - circuit(weights=weights, wires=wires, init_state=np.array([1, 1, 0, 0])) - - def test_integration(self, tol): - """Test integration with PennyLane to compute expectation values""" - - N = 4 - wires = range(N) - layers = 2 - weights = np.array( - [ - [[-0.09009989, -0.00090317], [-0.16034551, -0.13278097], [-0.02926428, 0.05175079]], - [[-0.07988132, 0.11315495], [-0.16079166, 0.09439518], [-0.04321269, 0.13678911]], - ] - ) - - dev = qml.device("default.qubit", wires=N) - - @qml.qnode(dev) - def circuit(weights): - ParticleConservingU1(weights, wires, init_state=np.array([1, 1, 0, 0])) - return [qml.expval(qml.PauliZ(w)) for w in range(N)] - - res = circuit(weights) - - exp = np.array([-0.99993177, -0.9853332, 0.98531251, 0.99995246]) - assert np.allclose(res, np.array(exp), atol=tol) - - - @pytest.mark.parametrize( - ("init_state", "exp_state"), - [ - (np.array([0, 0]), np.array([1.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j])), - ( - np.array([0, 1]), - np.array([0.0 + 0.0j, 0.862093 + 0.0j, 0.0 - 0.506749j, 0.0 + 0.0j]), - ), - ( - np.array([1, 0]), - np.array([0.0 + 0.0j, 0.0 - 0.506749j, 0.862093 + 0.0j, 0.0 + 0.0j]), - ), - (np.array([1, 1]), np.array([0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 1.0 + 0.0j])), - ], - ) - def test_decomposition_u2ex(self, init_state, exp_state, tol): - """Test the decomposition of the U_{2, ex}` exchange gate by asserting the prepared - state.""" - - N = 2 - wires = range(N) - wires = Wires(wires) - - weight = 0.53141 - - dev = qml.device("default.qubit", wires=N) - - @qml.qnode(dev) - def circuit(weight): - qml.BasisState(init_state, wires=wires) - qml.templates.layers.particle_conserving_u2.u2_ex_gate(weight, wires) - return qml.expval(qml.PauliZ(0)) - - circuit(weight) - - assert np.allclose(circuit.device.state, exp_state, atol=tol) - - - @pytest.mark.parametrize( - ("init_state", "exp_state"), - [ - (np.array([0, 0]), np.array([1.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j, 0.0 + 0.0j])), - ( - np.array([1, 1]), - np.array( - [ - 0.00000000e00 + 0.00000000e00j, - 3.18686105e-17 - 1.38160066e-17j, - 1.09332080e-16 - 2.26795885e-17j, - 1.00000000e00 + 2.77555756e-17j, - ] - ), - ), - ( - np.array([0, 1]), - np.array( - [ - 0.00000000e00 + 0.00000000e00j, - 8.23539739e-01 - 2.77555756e-17j, - -5.55434174e-01 - 1.15217954e-01j, - 3.18686105e-17 + 1.38160066e-17j, - ] - ), - ), - ( - np.array([1, 0]), - np.array( - [ - 0.00000000e00 + 0.00000000e00j, - -5.55434174e-01 + 1.15217954e-01j, - -8.23539739e-01 - 5.55111512e-17j, - 1.09332080e-16 + 2.26795885e-17j, - ] - ), - ), - ], - ) - def test_decomposition_u1ex(self, init_state, exp_state, tol): - """Test the decomposition of the U_{1, ex}` exchange gate by asserting the prepared - state.""" - - N = 2 - wires = range(N) - layers = 1 - weights = np.array([[[0.2045368, -0.6031732]]]) - - dev = qml.device("default.qubit", wires=N) - - @qml.qnode(dev) - def circuit(weights): - ParticleConservingU1(weights, wires, init_state=init_state) - return qml.expval(qml.PauliZ(0)) - - circuit(weights) - - assert np.allclose(circuit.device.state, exp_state, atol=tol) From 892256cbb75790dafebb2178de338f56e76b28cc Mon Sep 17 00:00:00 2001 From: Maria Schuld Date: Thu, 25 Mar 2021 17:37:48 +0200 Subject: [PATCH 08/16] black --- pennylane/templates/layers/basic_entangler.py | 2 +- pennylane/templates/layers/cv_neural_net.py | 76 +++++++++++++++---- .../layers/particle_conserving_u1.py | 12 ++- .../layers/particle_conserving_u2.py | 6 +- pennylane/templates/layers/random.py | 21 +++-- .../templates/layers/simplified_two_design.py | 28 ++++--- .../templates/layers/strongly_entangling.py | 21 +++-- .../templates/subroutines/interferometer.py | 16 ++-- .../test_layers/test_basic_entangler.py | 7 +- .../test_layers/test_cv_neural_net.py | 30 ++++++-- .../test_particle_conserving_u1.py | 35 ++++----- .../test_particle_conserving_u2.py | 68 ++++++++--------- tests/templates/test_layers/test_random.py | 2 +- .../test_layers/test_simplified_twodesign.py | 14 +++- .../test_layers/test_strongly_entangling.py | 26 +++++-- 15 files changed, 243 insertions(+), 121 deletions(-) diff --git a/pennylane/templates/layers/basic_entangler.py b/pennylane/templates/layers/basic_entangler.py index 49657b44960..78e55d8c6bb 100644 --- a/pennylane/templates/layers/basic_entangler.py +++ b/pennylane/templates/layers/basic_entangler.py @@ -151,7 +151,7 @@ def expand(self): for layer in range(repeat): for i in range(len(self.wires)): - self.rotation(weights[layer][i], wires=self.wires[i: i + 1]) + self.rotation(weights[layer][i], wires=self.wires[i : i + 1]) if len(self.wires) == 2: qml.CNOT(wires=self.wires) diff --git a/pennylane/templates/layers/cv_neural_net.py b/pennylane/templates/layers/cv_neural_net.py index 3dab1037292..9c36ca2caf2 100644 --- a/pennylane/templates/layers/cv_neural_net.py +++ b/pennylane/templates/layers/cv_neural_net.py @@ -84,7 +84,22 @@ def circuit(): num_wires = AnyWires par_domain = "A" - def __init__(self, theta_1, phi_1, varphi_1, r, phi_r, theta_2, phi_2, varphi_2, a, phi_a, k, wires, do_queue=True): + def __init__( + self, + theta_1, + phi_1, + varphi_1, + r, + phi_r, + theta_2, + phi_2, + varphi_2, + a, + phi_a, + k, + wires, + do_queue=True, + ): n_wires = len(wires) n_if = n_wires * (n_wires - 1) // 2 @@ -106,8 +121,21 @@ def __init__(self, theta_1, phi_1, varphi_1, r, phi_r, theta_2, phi_2, varphi_2, self.n_layers = shapes[0][0] - super().__init__(theta_1, phi_1, varphi_1, r, phi_r, theta_2, phi_2, varphi_2, a, phi_a, k, wires=wires, - do_queue=do_queue) + super().__init__( + theta_1, + phi_1, + varphi_1, + r, + phi_r, + theta_2, + phi_2, + varphi_2, + a, + phi_a, + k, + wires=wires, + do_queue=do_queue, + ) def expand(self): @@ -115,17 +143,29 @@ def expand(self): for l in range(self.n_layers): - qml.templates.Interferometer(theta=self.parameters[0][l], phi=self.parameters[1][l], - varphi=self.parameters[2][l], wires=self.wires) + qml.templates.Interferometer( + theta=self.parameters[0][l], + phi=self.parameters[1][l], + varphi=self.parameters[2][l], + wires=self.wires, + ) for i in range(len(self.wires)): - qml.Squeezing(self.parameters[3][l, i], self.parameters[4][l, i], wires=self.wires[i]) + qml.Squeezing( + self.parameters[3][l, i], self.parameters[4][l, i], wires=self.wires[i] + ) - qml.templates.Interferometer(theta=self.parameters[5][l], phi=self.parameters[6][l], - varphi=self.parameters[7][l], wires=self.wires) + qml.templates.Interferometer( + theta=self.parameters[5][l], + phi=self.parameters[6][l], + varphi=self.parameters[7][l], + wires=self.wires, + ) for i in range(len(self.wires)): - qml.Displacement(self.parameters[8][l, i], self.parameters[9][l, i], wires=self.wires[i]) + qml.Displacement( + self.parameters[8][l, i], self.parameters[9][l, i], wires=self.wires[i] + ) for i in range(len(self.wires)): qml.Kerr(self.parameters[10][l, i], wires=self.wires[i]) @@ -136,16 +176,20 @@ def expand(self): def shape(n_layers, n_wires): r"""Returns a list of shapes for the 11 parameter tensors. - Args: - n_layers (int): number of layers - n_wires (int): number of wires + Args: + n_layers (int): number of layers + n_wires (int): number of wires - Returns: - list[tuple[int]]: list of shapes + Returns: + list[tuple[int]]: list of shapes """ n_if = n_wires * (n_wires - 1) // 2 - shapes = [(n_layers, n_if)] * 2 + [(n_layers, n_wires)] * 3 + [(n_layers, n_if)] * 2 + [ - (n_layers, n_wires)] * 4 + shapes = ( + [(n_layers, n_if)] * 2 + + [(n_layers, n_wires)] * 3 + + [(n_layers, n_if)] * 2 + + [(n_layers, n_wires)] * 4 + ) return shapes diff --git a/pennylane/templates/layers/particle_conserving_u1.py b/pennylane/templates/layers/particle_conserving_u1.py index 2d5969300b5..03008d6be01 100644 --- a/pennylane/templates/layers/particle_conserving_u1.py +++ b/pennylane/templates/layers/particle_conserving_u1.py @@ -248,7 +248,9 @@ def __init__(self, weights, wires, init_state=None, do_queue=True): ) if shape[2] != 2: - raise ValueError(f"Weights tensor must have third dimension of length 2; got {shape[2]}") + raise ValueError( + f"Weights tensor must have third dimension of length 2; got {shape[2]}" + ) self.n_layers = shape[0] # we can extract the numpy representation here @@ -259,8 +261,8 @@ def __init__(self, weights, wires, init_state=None, do_queue=True): def expand(self): - nm_wires = [self.wires[l: l + 2] for l in range(0, len(self.wires) - 1, 2)] - nm_wires += [self.wires[l: l + 2] for l in range(1, len(self.wires) - 1, 2)] + nm_wires = [self.wires[l : l + 2] for l in range(0, len(self.wires) - 1, 2)] + nm_wires += [self.wires[l : l + 2] for l in range(1, len(self.wires) - 1, 2)] with qml.tape.QuantumTape() as tape: @@ -268,7 +270,9 @@ def expand(self): for l in range(self.n_layers): for i, wires_ in enumerate(nm_wires): - u1_ex_gate(self.parameters[0][l, i, 0], self.parameters[0][l, i, 1], wires=wires_) + u1_ex_gate( + self.parameters[0][l, i, 0], self.parameters[0][l, i, 1], wires=wires_ + ) return tape @staticmethod diff --git a/pennylane/templates/layers/particle_conserving_u2.py b/pennylane/templates/layers/particle_conserving_u2.py index 519786e355f..940e0c32089 100644 --- a/pennylane/templates/layers/particle_conserving_u2.py +++ b/pennylane/templates/layers/particle_conserving_u2.py @@ -178,8 +178,8 @@ def __init__(self, weights, wires, init_state=None, do_queue=True): def expand(self): - nm_wires = [self.wires[l: l + 2] for l in range(0, len(self.wires) - 1, 2)] - nm_wires += [self.wires[l: l + 2] for l in range(1, len(self.wires) - 1, 2)] + nm_wires = [self.wires[l : l + 2] for l in range(0, len(self.wires) - 1, 2)] + nm_wires += [self.wires[l : l + 2] for l in range(1, len(self.wires) - 1, 2)] with qml.tape.QuantumTape() as tape: @@ -210,4 +210,4 @@ def shape(n_layers, n_wires): raise ValueError( "The number of qubits must be greater than one; got 'n_wires' = {}".format(n_wires) ) - return n_layers, 2*n_wires - 1 + return n_layers, 2 * n_wires - 1 diff --git a/pennylane/templates/layers/random.py b/pennylane/templates/layers/random.py index 1cccd3d60d7..e193e75803b 100644 --- a/pennylane/templates/layers/random.py +++ b/pennylane/templates/layers/random.py @@ -178,7 +178,16 @@ def circuit_rnd(weights): num_wires = AnyWires par_domain = "A" - def __init__(self, weights, wires, ratio_imprim=0.3, imprimitive=None, rotations=None, seed=42, do_queue=True): + def __init__( + self, + weights, + wires, + ratio_imprim=0.3, + imprimitive=None, + rotations=None, + seed=42, + do_queue=True, + ): self.seed = seed self.rotations = rotations or [qml.RX, qml.RY, qml.RZ] @@ -224,12 +233,12 @@ def expand(self): def shape(n_layers, n_rotations): r"""Returns the expected shape of the weights tensor. - Args: - n_layers (int): number of layers - n_rotations (int): number of rotations + Args: + n_layers (int): number of layers + n_rotations (int): number of rotations - Returns: - tuple[int]: shape + Returns: + tuple[int]: shape """ return n_layers, n_rotations diff --git a/pennylane/templates/layers/simplified_two_design.py b/pennylane/templates/layers/simplified_two_design.py index 6f761a4d8ee..cddb6facd3c 100644 --- a/pennylane/templates/layers/simplified_two_design.py +++ b/pennylane/templates/layers/simplified_two_design.py @@ -119,7 +119,9 @@ def __init__(self, initial_layer_weights, weights, wires, do_queue=True): shape2 = qml.math.shape(initial_layer_weights) if shape2 != (len(wires),): - raise ValueError(f"Initial layer weights must be of shape {(len(wires),)}; got {shape2}") + raise ValueError( + f"Initial layer weights must be of shape {(len(wires),)}; got {shape2}" + ) self.n_layers = shape[0] @@ -136,18 +138,22 @@ def expand(self): for layer in range(self.n_layers): # even layer of entanglers - even_wires = [self.wires[i: i + 2] for i in range(0, len(self.wires) - 1, 2)] + even_wires = [self.wires[i : i + 2] for i in range(0, len(self.wires) - 1, 2)] for i, wire_pair in enumerate(even_wires): qml.CZ(wires=wire_pair) qml.RY(self.parameters[1][layer, i, 0], wires=wire_pair[0]) qml.RY(self.parameters[1][layer, i, 1], wires=wire_pair[1]) # odd layer of entanglers - odd_wires = [self.wires[i: i + 2] for i in range(1, len(self.wires) - 1, 2)] + odd_wires = [self.wires[i : i + 2] for i in range(1, len(self.wires) - 1, 2)] for i, wire_pair in enumerate(odd_wires): qml.CZ(wires=wire_pair) - qml.RY(self.parameters[1][layer, len(self.wires) // 2 + i, 0], wires=wire_pair[0]) - qml.RY(self.parameters[1][layer, len(self.wires) // 2 + i, 1], wires=wire_pair[1]) + qml.RY( + self.parameters[1][layer, len(self.wires) // 2 + i, 0], wires=wire_pair[0] + ) + qml.RY( + self.parameters[1][layer, len(self.wires) // 2 + i, 1], wires=wire_pair[1] + ) return tape @@ -155,15 +161,15 @@ def expand(self): def shape(n_layers, n_wires): r"""Returns a list of shapes for the 2 parameter tensors. - Args: - n_layers (int): number of layers - n_wires (int): number of wires + Args: + n_layers (int): number of layers + n_wires (int): number of wires - Returns: - list[tuple[int]]: list of shapes + Returns: + list[tuple[int]]: list of shapes """ if n_wires == 1: return [(n_wires,), (n_layers,)] - return [(n_wires,), (n_layers, n_wires-1, 2)] + return [(n_wires,), (n_layers, n_wires - 1, 2)] diff --git a/pennylane/templates/layers/strongly_entangling.py b/pennylane/templates/layers/strongly_entangling.py index 2876004e9eb..e897dde943c 100644 --- a/pennylane/templates/layers/strongly_entangling.py +++ b/pennylane/templates/layers/strongly_entangling.py @@ -82,7 +82,9 @@ def __init__(self, weights, wires, ranges=None, imprimitive=None, do_queue=True) ) if shape[2] != 3: - raise ValueError(f"Weights tensor must have third dimension of length 3; got {shape[2]}") + raise ValueError( + f"Weights tensor must have third dimension of length 3; got {shape[2]}" + ) if ranges is None: if len(wires) > 1: @@ -102,7 +104,12 @@ def expand(self): for l in range(self.n_layers): for i in range(len(self.wires)): - qml.Rot(self.parameters[0][l, i, 0], self.parameters[0][l, i, 1], self.parameters[0][l, i, 2], wires=self.wires[i]) + qml.Rot( + self.parameters[0][l, i, 0], + self.parameters[0][l, i, 1], + self.parameters[0][l, i, 2], + wires=self.wires[i], + ) if len(self.wires) > 1: for i in range(len(self.wires)): @@ -115,12 +122,12 @@ def expand(self): def shape(n_layers, n_wires): r"""Returns the expected shape of the weights tensor. - Args: - n_layers (int): number of layers - n_wires (int): number of wires + Args: + n_layers (int): number of layers + n_wires (int): number of wires - Returns: - tuple[int]: shape + Returns: + tuple[int]: shape """ return n_layers, n_wires, 3 diff --git a/pennylane/templates/subroutines/interferometer.py b/pennylane/templates/subroutines/interferometer.py index 035ea741248..d73e9697b2e 100644 --- a/pennylane/templates/subroutines/interferometer.py +++ b/pennylane/templates/subroutines/interferometer.py @@ -100,7 +100,9 @@ class Interferometer(Operation): num_wires = AnyWires par_domain = "A" - def __init__(self, theta, phi, varphi, wires, mesh="rectangular", beamsplitter="pennylane", do_queue=True): + def __init__( + self, theta, phi, varphi, wires, mesh="rectangular", beamsplitter="pennylane", do_queue=True + ): n_wires = len(wires) n_if = n_wires * (n_wires - 1) // 2 @@ -149,7 +151,9 @@ def expand(self): elif self.beamsplitter == "pennylane": qml.Beamsplitter(theta[n], phi[n], wires=[w1, w2]) else: - raise ValueError(f"did not recognize beamsplitter {self.beamsplitter}") + raise ValueError( + f"did not recognize beamsplitter {self.beamsplitter}" + ) n += 1 elif self.mesh == "triangular": @@ -179,11 +183,11 @@ def expand(self): def shape(n_wires): r"""Returns a list of shapes for the three parameter tensors. - Args: - n_wires (int): number of wires + Args: + n_wires (int): number of wires - Returns: - list[tuple[int]]: list of shapes + Returns: + list[tuple[int]]: list of shapes """ n_if = n_wires * (n_wires - 1) // 2 shapes = [(n_if,), (n_if,), (n_wires,)] diff --git a/tests/templates/test_layers/test_basic_entangler.py b/tests/templates/test_layers/test_basic_entangler.py index c08c8d70283..ee30ceec686 100644 --- a/tests/templates/test_layers/test_basic_entangler.py +++ b/tests/templates/test_layers/test_basic_entangler.py @@ -27,7 +27,12 @@ class TestDecomposition: (1, (1, 1), ["RX"], [[0]]), (2, (1, 2), ["RX", "RX", "CNOT"], [[0], [1], [0, 1]]), (2, (2, 2), ["RX", "RX", "CNOT", "RX", "RX", "CNOT"], [[0], [1], [0, 1], [0], [1], [0, 1]]), - (3, (1, 3), ["RX", "RX", "RX", "CNOT", "CNOT", "CNOT"], [[0], [1], [2], [0, 1], [1, 2], [2, 0]]), + ( + 3, + (1, 3), + ["RX", "RX", "RX", "CNOT", "CNOT", "CNOT"], + [[0], [1], [2], [0, 1], [1, 2], [2, 0]], + ), ] @pytest.mark.parametrize("n_wires, weight_shape, expected_names, expected_wires", QUEUES) diff --git a/tests/templates/test_layers/test_cv_neural_net.py b/tests/templates/test_layers/test_cv_neural_net.py index 94b3a5d1984..76caedb1781 100644 --- a/tests/templates/test_layers/test_cv_neural_net.py +++ b/tests/templates/test_layers/test_cv_neural_net.py @@ -23,14 +23,20 @@ class DummyDevice(DefaultGaussian): """Dummy Gaussian device to allow Kerr operations""" + _operation_map = DefaultGaussian._operation_map.copy() - _operation_map['Kerr'] = lambda *x, **y: np.identity(2) + _operation_map["Kerr"] = lambda *x, **y: np.identity(2) def expected_shapes(n_layers, n_wires): # compute the expected shapes for a given number of wires n_if = n_wires * (n_wires - 1) // 2 - expected = [(n_layers, n_if)] * 2 + [(n_layers, n_wires)] * 3 + [(n_layers, n_if)] * 2 + [(n_layers, n_wires)] * 4 + expected = ( + [(n_layers, n_if)] * 2 + + [(n_layers, n_wires)] * 3 + + [(n_layers, n_if)] * 2 + + [(n_layers, n_wires)] * 4 + ) return expected @@ -38,9 +44,21 @@ class TestDecomposition: """Tests that the template defines the correct decomposition.""" QUEUES = [ - (1, ["Interferometer", "Squeezing", "Interferometer", "Displacement", "Kerr"], [[0]]*5), - (2, ["Interferometer", "Squeezing", "Squeezing", "Interferometer", - "Displacement", "Displacement", "Kerr", "Kerr"], [[0, 1], [0], [1], [0, 1], [0], [1], [0], [1]]), + (1, ["Interferometer", "Squeezing", "Interferometer", "Displacement", "Kerr"], [[0]] * 5), + ( + 2, + [ + "Interferometer", + "Squeezing", + "Squeezing", + "Interferometer", + "Displacement", + "Displacement", + "Kerr", + "Kerr", + ], + [[0, 1], [0], [1], [0, 1], [0], [1], [0], [1]], + ), ] @pytest.mark.parametrize("n_wires, expected_names, expected_wires", QUEUES) @@ -105,7 +123,7 @@ def test_cvqnn_layers_exception_second_dim(self): """Check exception if weong dimension of weights""" shapes = expected_shapes(1, 2) weights = [np.random.random(shape) for shape in shapes[:-1]] - weights += [np.random.random((1, shapes[-1][1]-1))] + weights += [np.random.random((1, shapes[-1][1] - 1))] dev = DummyDevice(wires=2) diff --git a/tests/templates/test_layers/test_particle_conserving_u1.py b/tests/templates/test_layers/test_particle_conserving_u1.py index 14d48de6fd2..4d8c9325e12 100644 --- a/tests/templates/test_layers/test_particle_conserving_u1.py +++ b/tests/templates/test_layers/test_particle_conserving_u1.py @@ -56,7 +56,7 @@ def test_operations(self): qubits = 4 layers = 2 - weights = np.random.random(size=(layers, qubits-1, 2)) + weights = np.random.random(size=(layers, qubits - 1, 2)) gates_per_u1 = 16 gates_per_layer = gates_per_u1 * (qubits - 1) @@ -75,8 +75,8 @@ def test_operations(self): wires = list(range(qubits)) - nm_wires = [wires[l: l + 2] for l in range(0, qubits - 1, 2)] - nm_wires += [wires[l: l + 2] for l in range(1, qubits - 1, 2)] + nm_wires = [wires[l : l + 2] for l in range(0, qubits - 1, 2)] + nm_wires += [wires[l : l + 2] for l in range(1, qubits - 1, 2)] op = qml.templates.ParticleConservingU1(weights, wires, init_state=np.array([1, 1, 0, 0])) queue = op.expand().operations @@ -144,7 +144,9 @@ def circuit(): @qml.qnode(dev2) def circuit2(): - qml.templates.ParticleConservingU1(weights, wires=["z", "a", "k"], init_state=init_state) + qml.templates.ParticleConservingU1( + weights, wires=["z", "a", "k"], init_state=init_state + ) return qml.expval(qml.Identity("z")) circuit() @@ -221,9 +223,9 @@ class TestInputs: (np.ones((4, 2, 2)), 4, "Weights tensor must"), (np.ones((4, 3, 1)), 4, "Weights tensor must"), ( - np.ones((4, 3, 1)), - 1, - "Expected the number of qubits", + np.ones((4, 3, 1)), + 1, + "Expected the number of qubits", ), ], ) @@ -247,12 +249,13 @@ def circuit(): class TestAttributes: """Tests additional methods and attributes""" - @pytest.mark.parametrize("n_layers, n_wires, expected_shape", - [ - (2, 3, (2, 2, 2)), - (2, 2, (2, 1, 2)), - ] - ) + @pytest.mark.parametrize( + "n_layers, n_wires, expected_shape", + [ + (2, 3, (2, 2, 2)), + (2, 2, (2, 1, 2)), + ], + ) def test_shape(self, n_layers, n_wires, expected_shape): """Test that the shape method returns the correct shape of the weights tensor""" @@ -268,8 +271,7 @@ def circuit_template(weights): def circuit_decomposed(weights): qml.BasisState(np.array([1, 1]), wires=[0, 1]) qml.CZ(wires=[0, 1]) - qml.CRot(weights[0, 0, 0], np.pi, weights[0, 0, 0], - wires=[0, 1]) + qml.CRot(weights[0, 0, 0], np.pi, weights[0, 0, 0], wires=[0, 1]) qml.PhaseShift(-weights[0, 0, 0], wires=[1]) qml.CNOT(wires=[0, 1]) qml.PhaseShift(weights[0, 0, 0], wires=[1]) @@ -278,8 +280,7 @@ def circuit_decomposed(weights): qml.CZ(wires=[1, 0]) qml.CRot(0, weights[0, 0, 1], 0, wires=[1, 0]) qml.CZ(wires=[0, 1]) - qml.CRot(weights[0, 0, 0], np.pi, -weights[0, 0, 0], - wires=[0, 1]) + qml.CRot(weights[0, 0, 0], np.pi, -weights[0, 0, 0], wires=[0, 1]) qml.PhaseShift(weights[0, 0, 0], wires=[1]) qml.CNOT(wires=[0, 1]) qml.PhaseShift(-weights[0, 0, 0], wires=[1]) diff --git a/tests/templates/test_layers/test_particle_conserving_u2.py b/tests/templates/test_layers/test_particle_conserving_u2.py index 248b3da21ee..7e6a70d078a 100644 --- a/tests/templates/test_layers/test_particle_conserving_u2.py +++ b/tests/templates/test_layers/test_particle_conserving_u2.py @@ -40,8 +40,8 @@ def test_operations(self, layers, qubits, init_state): n_gates = 1 + (qubits + (qubits - 1) * 3) * layers exp_gates = ( - [qml.RZ] * qubits + ([qml.CNOT] + [qml.CRX] + [qml.CNOT]) * (qubits - 1) - ) * layers + [qml.RZ] * qubits + ([qml.CNOT] + [qml.CRX] + [qml.CNOT]) * (qubits - 1) + ) * layers op = qml.templates.ParticleConservingU2(weights, wires=range(qubits), init_state=init_state) queue = op.expand().operations @@ -58,19 +58,15 @@ def test_operations(self, layers, qubits, init_state): # gate parameter params = np.array( - [ - queue[i].parameters - for i in range(1, n_gates) - if queue[i].parameters != [] - ] + [queue[i].parameters for i in range(1, n_gates) if queue[i].parameters != []] ) weights[:, qubits:] = weights[:, qubits:] * 2 assert np.allclose(params.flatten(), weights.flatten()) # gate wires wires = range(qubits) - nm_wires = [wires[l: l + 2] for l in range(0, qubits - 1, 2)] - nm_wires += [wires[l: l + 2] for l in range(1, qubits - 1, 2)] + nm_wires = [wires[l : l + 2] for l in range(0, qubits - 1, 2)] + nm_wires += [wires[l : l + 2] for l in range(1, qubits - 1, 2)] exp_wires = [] for _ in range(layers): @@ -136,7 +132,9 @@ def circuit(): @qml.qnode(dev2) def circuit2(): - qml.templates.ParticleConservingU2(weights, wires=["z", "a", "k"], init_state=init_state) + qml.templates.ParticleConservingU2( + weights, wires=["z", "a", "k"], init_state=init_state + ) return qml.expval(qml.Identity("z")) circuit() @@ -152,29 +150,29 @@ class TestInputs: ("weights", "wires", "msg_match"), [ ( - np.array([[-0.080, 2.629, -0.710, 5.383, 0.646, -2.872, -3.856]]), - [0], - "This template requires the number of qubits to be greater than one", + np.array([[-0.080, 2.629, -0.710, 5.383, 0.646, -2.872, -3.856]]), + [0], + "This template requires the number of qubits to be greater than one", ), ( - np.array([[-0.080, 2.629, -0.710, 5.383]]), - [0, 1, 2, 3], - "Weights tensor must", + np.array([[-0.080, 2.629, -0.710, 5.383]]), + [0, 1, 2, 3], + "Weights tensor must", ), ( - np.array( - [ - [-0.080, 2.629, -0.710, 5.383, 0.646, -2.872], - [-0.080, 2.629, -0.710, 5.383, 0.646, -2.872], - ] - ), - [0, 1, 2, 3], - "Weights tensor must", + np.array( + [ + [-0.080, 2.629, -0.710, 5.383, 0.646, -2.872], + [-0.080, 2.629, -0.710, 5.383, 0.646, -2.872], + ] + ), + [0, 1, 2, 3], + "Weights tensor must", ), ( - np.array([-0.080, 2.629, -0.710, 5.383, 0.646, -2.872]), - [0, 1, 2, 3], - "Weights tensor must be 2-dimensional", + np.array([-0.080, 2.629, -0.710, 5.383, 0.646, -2.872]), + [0, 1, 2, 3], + "Weights tensor must be 2-dimensional", ), ], ) @@ -202,14 +200,14 @@ def circuit(): class TestAttributes: """Tests additional methods and attributes""" - @pytest.mark.parametrize("n_layers, n_wires, expected_shape", - [ - (2, 3, (2, 5)), - (2, 2, (2, 3)), - (1, 3, (1, 5)), - - ] - ) + @pytest.mark.parametrize( + "n_layers, n_wires, expected_shape", + [ + (2, 3, (2, 5)), + (2, 2, (2, 3)), + (1, 3, (1, 5)), + ], + ) def test_shape(self, n_layers, n_wires, expected_shape): """Test that the shape method returns the correct shape of the weights tensor""" diff --git a/tests/templates/test_layers/test_random.py b/tests/templates/test_layers/test_random.py index f5daa49550e..534472cc4a1 100644 --- a/tests/templates/test_layers/test_random.py +++ b/tests/templates/test_layers/test_random.py @@ -47,7 +47,7 @@ def test_number_gates(self, n_layers, n_rots): ops = op.expand().operations gate_names = [g.name for g in ops] - assert len(gate_names) - gate_names.count("CNOT") == n_layers*n_rots + assert len(gate_names) - gate_names.count("CNOT") == n_layers * n_rots @pytest.mark.parametrize("ratio", [0.2, 0.6]) def test_ratio_imprimitive(self, ratio): diff --git a/tests/templates/test_layers/test_simplified_twodesign.py b/tests/templates/test_layers/test_simplified_twodesign.py index b88e9c38021..238926dce09 100644 --- a/tests/templates/test_layers/test_simplified_twodesign.py +++ b/tests/templates/test_layers/test_simplified_twodesign.py @@ -26,8 +26,18 @@ class TestDecomposition: QUEUES = [ (1, (1,), ["RY"], [[0]]), (2, (1, 1, 2), ["RY", "RY", "CZ", "RY", "RY"], [[0], [1], [0, 1], [0], [1]]), - (2, (2, 1, 2), ["RY", "RY", "CZ", "RY", "RY", "CZ", "RY", "RY"], [[0], [1], [0, 1], [0], [1], [0, 1], [0], [1]]), - (3, (1, 2, 2), ["RY", "RY", "RY", "CZ", "RY", "RY", "CZ", "RY", "RY"], [[0], [1], [2], [0, 1], [0], [1], [1, 2], [1], [2]]), + ( + 2, + (2, 1, 2), + ["RY", "RY", "CZ", "RY", "RY", "CZ", "RY", "RY"], + [[0], [1], [0, 1], [0], [1], [0, 1], [0], [1]], + ), + ( + 3, + (1, 2, 2), + ["RY", "RY", "RY", "CZ", "RY", "RY", "CZ", "RY", "RY"], + [[0], [1], [2], [0, 1], [0], [1], [1, 2], [1], [2]], + ), ] @pytest.mark.parametrize("n_wires, weight_shape, expected_names, expected_wires", QUEUES) diff --git a/tests/templates/test_layers/test_strongly_entangling.py b/tests/templates/test_layers/test_strongly_entangling.py index 973a714234d..691a58cd15d 100644 --- a/tests/templates/test_layers/test_strongly_entangling.py +++ b/tests/templates/test_layers/test_strongly_entangling.py @@ -26,8 +26,18 @@ class TestDecomposition: QUEUES = [ (1, (1, 1, 3), ["Rot"], [[0]]), (2, (1, 2, 3), ["Rot", "Rot", "CNOT", "CNOT"], [[0], [1], [0, 1], [1, 0]]), - (2, (2, 2, 3), ["Rot", "Rot", "CNOT", "CNOT", "Rot", "Rot", "CNOT", "CNOT"], [[0], [1], [0, 1], [1, 0], [0], [1], [0, 1], [1, 0]]), - (3, (1, 3, 3), ["Rot", "Rot", "Rot", "CNOT", "CNOT", "CNOT"], [[0], [1], [2], [0, 1], [1, 2], [2, 0]]), + ( + 2, + (2, 2, 3), + ["Rot", "Rot", "CNOT", "CNOT", "Rot", "Rot", "CNOT", "CNOT"], + [[0], [1], [0, 1], [1, 0], [0], [1], [0, 1], [1, 0]], + ), + ( + 3, + (1, 3, 3), + ["Rot", "Rot", "Rot", "CNOT", "CNOT", "CNOT"], + [[0], [1], [2], [0, 1], [1, 2], [2, 0]], + ), ] @pytest.mark.parametrize("n_wires, weight_shape, expected_names, expected_wires", QUEUES) @@ -50,8 +60,8 @@ def test_uses_correct_imprimitive(self, n_layers, n_wires): weights = np.random.randn(n_layers, n_wires, 3) op = qml.templates.StronglyEntanglingLayers( - weights=weights, wires=range(n_wires), imprimitive=qml.CZ - ) + weights=weights, wires=range(n_wires), imprimitive=qml.CZ + ) ops = op.expand().operations gate_names = [gate.name for gate in ops] @@ -158,7 +168,13 @@ def test_list_and_tuples(self, tol): res2 = circuit2(weights) assert qml.math.allclose(res, res2, atol=tol, rtol=0) - weights_tuple = [[tuple(weights[0][0]), tuple(weights[0][1]), tuple(weights[0][2]),]] + weights_tuple = [ + [ + tuple(weights[0][0]), + tuple(weights[0][1]), + tuple(weights[0][2]), + ] + ] res = circuit(weights_tuple) res2 = circuit2(tuple(weights_tuple)) assert qml.math.allclose(res, res2, atol=tol, rtol=0) From 2b1e6396d85b43251c55b970c164f4fc02031c67 Mon Sep 17 00:00:00 2001 From: Maria Schuld Date: Thu, 25 Mar 2021 17:59:31 +0200 Subject: [PATCH 09/16] delete old tests that are now failing --- tests/templates/test_integration.py | 133 +------ tests/templates/test_subroutines.py | 518 ++++++++++++++-------------- 2 files changed, 265 insertions(+), 386 deletions(-) diff --git a/tests/templates/test_integration.py b/tests/templates/test_integration.py index 3bb341b708c..72080f7720c 100644 --- a/tests/templates/test_integration.py +++ b/tests/templates/test_integration.py @@ -76,18 +76,7 @@ # Note that the template is called in all tests using 2 wires -QUBIT_DIFFABLE_NONDIFFABLE = [(qml.templates.AmplitudeEmbedding, - {'features': [1 / 2, 1 / 2, 1 / 2, 1 / 2]}, - {'wires': [0, 1], 'normalize': False}, - 2), - (qml.templates.AmplitudeEmbedding, - {'features': [1 / 2, 1 / 2, 1 / 2, 1 / 2]}, - {'wires': [0, 1], 'normalize': True}, - 2), - (qml.templates.BasisEmbedding, - {}, - {'wires': [0, 1], 'features': [1, 0]}, - 2), +QUBIT_DIFFABLE_NONDIFFABLE = [ (qml.templates.MottonenStatePreparation, {'state_vector': np.array([1 / 2, 1 / 2, 1 / 2, 1 / 2])}, {'wires': [0, 1]}, @@ -96,41 +85,12 @@ {}, {'wires': [0, 1], 'basis_state': np.array([1, 0])}, 2), - (qml.templates.StronglyEntanglingLayers, - {'weights': [[[4.54, 4.79, 2.98], [4.93, 4.11, 5.58]], - [[6.08, 5.94, 0.05], [2.44, 5.07, 0.95]]]}, - {'wires': [0, 1]}, - 2), - (qml.templates.RandomLayers, - {'weights': [[0.56, 5.14], [2.21, 4.27]]}, - {'wires': [0, 1]}, - 2), - (qml.templates.AngleEmbedding, - {'features': [1., 2.]}, - {'wires': [0, 1]}, - 2), - (qml.templates.QAOAEmbedding, - {'features': [1., 2.], - 'weights': [[0.1, 0.1, 0.1]]}, - {'wires': [0, 1]}, - 2), + (qml.templates.broadcast, {'parameters': [[1.], [1.]]}, {'wires': [0, 1], 'unitary': qml.RX, 'pattern': 'single'}, 2), - (qml.templates.SimplifiedTwoDesign, - {'initial_layer_weights': [1., 1.], - 'weights': [[[1., 1.]]]}, - {'wires': [0, 1]}, - 2), - (qml.templates.BasicEntanglerLayers, - {'weights': [[1., 1.]]}, - {'wires': [0, 1], 'rotation': qml.RX}, - 2), - (qml.templates.IQPEmbedding, - {}, - {'wires': [0, 1], 'features': [1., 1.]}, - 2), + (qml.templates.SingleExcitationUnitary, {'weight': 0.56}, {'wires': [0, 1]}, @@ -145,14 +105,7 @@ {'wires': [0, 1, 2, 3], 's_wires': [[0, 1, 2], [1, 2, 3]], 'd_wires': [[[0, 1], [2, 3]]], 'init_state':np.array([1, 1, 0, 0], requires_grad=False)}, 4), - (qml.templates.ParticleConservingU2, - {'weights': np.array([[0.35172862, 0.60808317, 1.44397231]])}, - {'wires': [0, 1], 'init_state': np.array([1, 0])}, - 2), - (qml.templates.ParticleConservingU1, - {'weights': [[[ 0.17586701, -0.20382066]]]}, - {'wires': [0, 1], 'init_state': np.array([1, 0], requires_grad=False)}, - 2), + (qml.templates.QuantumPhaseEstimation, {}, { @@ -163,35 +116,7 @@ 3), ] -CV_DIFFABLE_NONDIFFABLE = [(qml.templates.DisplacementEmbedding, - {'features': [1., 2.]}, - {'wires': [0, 1]}, - 2), - (qml.templates.SqueezingEmbedding, - {'features': [1., 2.]}, - {'wires': [0, 1]}, - 2), - (qml.templates.CVNeuralNetLayers, - {'theta_1': [[2.31], [1.22]], - 'phi_1': [[3.47], [2.01]], - 'varphi_1': [[0.93, 1.58], [5.07, 4.82]], - 'r': [[0.21, 0.12], [-0.09, 0.04]], - 'phi_r': [[4.76, 6.08], [6.09, 6.22]], - 'theta_2': [[4.83], [1.70]], - 'phi_2': [[4.74], [5.39]], - 'varphi_2': [[0.88, 0.62], [1.09, 3.02]], - 'a': [[-0.01, -0.05], [0.08, -0.19]], - 'phi_a': [[1.89, 3.59], [1.49, 3.71]], - 'k': [[0.09, 0.03], [-0.14, 0.04]]}, - {'wires': [0, 1]}, - 2), - (qml.templates.Interferometer, - {'theta': [2.31], - 'phi': [3.49], - 'varphi': [0.98, 1.54]}, - {'wires': [0, 1]}, - 2), - ] +CV_DIFFABLE_NONDIFFABLE = [] # List templates in NO_OP_BEFORE that do not allow for operations # before they are called in a quantum function. @@ -267,53 +192,7 @@ {'weights': qml.init.basic_entangler_layers_normal(n_layers=3, n_wires=3), 'wires': range(3)}), ] -CV_INIT = [(qml.templates.CVNeuralNetLayers, - {'theta_1': qml.init.cvqnn_layers_theta_uniform(n_layers=3, n_wires=1), - 'phi_1': qml.init.cvqnn_layers_phi_uniform(n_layers=3, n_wires=1), - 'varphi_1': qml.init.cvqnn_layers_varphi_uniform(n_layers=3, n_wires=1), - 'r': qml.init.cvqnn_layers_r_uniform(n_layers=3, n_wires=1), - 'phi_r': qml.init.cvqnn_layers_phi_r_uniform(n_layers=3, n_wires=1), - 'theta_2': qml.init.cvqnn_layers_theta_uniform(n_layers=3, n_wires=1), - 'phi_2': qml.init.cvqnn_layers_phi_uniform(n_layers=3, n_wires=1), - 'varphi_2': qml.init.cvqnn_layers_varphi_uniform(n_layers=3, n_wires=1), - 'a': qml.init.cvqnn_layers_a_uniform(n_layers=3, n_wires=1), - 'phi_a': qml.init.cvqnn_layers_phi_a_uniform(n_layers=3, n_wires=1), - 'k': qml.init.cvqnn_layers_kappa_uniform(n_layers=3, n_wires=1), - 'wires': range(1)}), - (qml.templates.CVNeuralNetLayers, - {'theta_1': qml.init.cvqnn_layers_theta_normal(n_layers=3, n_wires=2), - 'phi_1': qml.init.cvqnn_layers_phi_normal(n_layers=3, n_wires=2), - 'varphi_1': qml.init.cvqnn_layers_varphi_normal(n_layers=3, n_wires=2), - 'r': qml.init.cvqnn_layers_r_normal(n_layers=3, n_wires=2), - 'phi_r': qml.init.cvqnn_layers_phi_r_normal(n_layers=3, n_wires=2), - 'theta_2': qml.init.cvqnn_layers_theta_normal(n_layers=3, n_wires=2), - 'phi_2': qml.init.cvqnn_layers_phi_normal(n_layers=3, n_wires=2), - 'varphi_2': qml.init.cvqnn_layers_varphi_normal(n_layers=3, n_wires=2), - 'a': qml.init.cvqnn_layers_a_normal(n_layers=3, n_wires=2), - 'phi_a': qml.init.cvqnn_layers_phi_a_normal(n_layers=3, n_wires=2), - 'k': qml.init.cvqnn_layers_kappa_normal(n_layers=3, n_wires=2), - 'wires': range(2)}), - (qml.templates.Interferometer, - {'phi': qml.init.interferometer_phi_uniform(n_wires=1), - 'varphi': qml.init.interferometer_varphi_uniform(n_wires=1), - 'theta': qml.init.interferometer_theta_uniform(n_wires=1), - 'wires': range(1)}), - (qml.templates.Interferometer, - {'phi': qml.init.interferometer_phi_normal(n_wires=1), - 'varphi': qml.init.interferometer_varphi_normal(n_wires=1), - 'theta': qml.init.interferometer_theta_normal(n_wires=1), - 'wires': range(1)}), - (qml.templates.Interferometer, - {'phi': qml.init.interferometer_phi_uniform(n_wires=3), - 'varphi': qml.init.interferometer_varphi_uniform(n_wires=3), - 'theta': qml.init.interferometer_theta_uniform(n_wires=3), - 'wires': range(3)}), - (qml.templates.Interferometer, - {'phi': qml.init.interferometer_phi_normal(n_wires=3), - 'varphi': qml.init.interferometer_varphi_normal(n_wires=3), - 'theta': qml.init.interferometer_theta_normal(n_wires=3), - 'wires': range(3)}) - ] +CV_INIT = [] class TestIntegrationQnode: diff --git a/tests/templates/test_subroutines.py b/tests/templates/test_subroutines.py index ef3f4a8e9a7..6cffc65c8de 100644 --- a/tests/templates/test_subroutines.py +++ b/tests/templates/test_subroutines.py @@ -110,265 +110,265 @@ def test_tuple_to_word(self, tuple, expected_word): assert _tuple_to_word(tuple) == expected_word -class TestInterferometer: - """Tests for the Interferometer from the pennylane.template.layers module.""" - - def test_invalid_mesh_exception(self): - """Test that Interferometer() raises correct exception when mesh not recognized.""" - dev = qml.device("default.gaussian", wires=2) - varphi = [0.42342, 0.234] - - @qml.qnode(dev) - def circuit(varphi, mesh=None): - Interferometer(theta=[0.21], phi=[0.53], varphi=varphi, mesh=mesh, wires=[0, 1]) - return qml.expval(qml.NumberOperator(0)) - - with pytest.raises(ValueError, match="did not recognize mesh"): - circuit(varphi, mesh="a") - - @pytest.mark.parametrize("mesh", ["rectangular", "triangular"]) - def test_invalid_beamsplitter_exception(self, mesh): - """Test that Interferometer() raises correct exception when beamsplitter not recognized.""" - dev = qml.device("default.gaussian", wires=2) - varphi = [0.42342, 0.234] - - @qml.qnode(dev) - def circuit(varphi, bs=None): - Interferometer(theta=[0.21], phi=[0.53], varphi=varphi, beamsplitter=bs, mesh=mesh, wires=[0, 1]) - return qml.expval(qml.NumberOperator(0)) - - with pytest.raises(ValueError, match="did not recognize beamsplitter"): - circuit(varphi, bs="a") - - def test_clements_beamsplitter_convention(self, tol): - """test the beamsplitter convention""" - N = 2 - wires = range(N) - - theta = [0.321] - phi = [0.234] - varphi = [0.42342, 0.1121] - - with qml.tape.OperationRecorder() as rec_rect: - Interferometer( - theta, phi, varphi, mesh="rectangular", beamsplitter="clements", wires=wires - ) - - with qml.tape.OperationRecorder() as rec_tria: - Interferometer( - theta, phi, varphi, mesh="triangular", beamsplitter="clements", wires=wires - ) - - for rec in [rec_rect, rec_tria]: - assert len(rec.queue) == 4 - - assert isinstance(rec.queue[0], qml.Rotation) - assert rec.queue[0].parameters == phi - - assert isinstance(rec.queue[1], qml.Beamsplitter) - assert rec.queue[1].parameters == [theta[0], 0] - - assert isinstance(rec.queue[2], qml.Rotation) - assert rec.queue[2].parameters == [varphi[0]] - - assert isinstance(rec.queue[3], qml.Rotation) - assert rec.queue[3].parameters == [varphi[1]] - - def test_one_mode(self, tol): - """Test that a one mode interferometer correctly gives a rotation gate""" - varphi = [0.42342] - - with qml.tape.OperationRecorder() as rec: - Interferometer(theta=[], phi=[], varphi=varphi, wires=0) - - assert len(rec.queue) == 1 - assert isinstance(rec.queue[0], qml.Rotation) - assert np.allclose(rec.queue[0].parameters, varphi, atol=tol) - - def test_two_mode_rect(self, tol): - """Test that a two mode interferometer using the rectangular mesh - correctly gives a beamsplitter+rotation gate""" - N = 2 - wires = range(N) - - theta = [0.321] - phi = [0.234] - varphi = [0.42342, 0.1121] - - with qml.tape.OperationRecorder() as rec: - Interferometer(theta, phi, varphi, wires=wires) - - isinstance(rec.queue[0], qml.Beamsplitter) - assert rec.queue[0].parameters == theta + phi - - assert isinstance(rec.queue[1], qml.Rotation) - assert rec.queue[1].parameters == [varphi[0]] - - assert isinstance(rec.queue[2], qml.Rotation) - assert rec.queue[2].parameters == [varphi[1]] - - def test_two_mode_triangular(self, tol): - """Test that a two mode interferometer using the triangular mesh - correctly gives a beamsplitter+rotation gate""" - N = 2 - wires = range(N) - - theta = [0.321] - phi = [0.234] - varphi = [0.42342, 0.1121] - - with qml.tape.OperationRecorder() as rec: - Interferometer(theta, phi, varphi, mesh="triangular", wires=wires) - - assert len(rec.queue) == 3 - - assert isinstance(rec.queue[0], qml.Beamsplitter) - assert rec.queue[0].parameters == theta + phi - - assert isinstance(rec.queue[1], qml.Rotation) - assert rec.queue[1].parameters == [varphi[0]] - - assert isinstance(rec.queue[2], qml.Rotation) - assert rec.queue[2].parameters == [varphi[1]] - - def test_three_mode(self, tol): - """Test that a three mode interferometer using either mesh gives the correct gates""" - N = 3 - wires = range(N) - - theta = [0.321, 0.4523, 0.21321] - phi = [0.234, 0.324, 0.234] - varphi = [0.42342, 0.234, 0.1121] - - with qml.tape.OperationRecorder() as rec_rect: - Interferometer(theta, phi, varphi, wires=wires) - - with qml.tape.OperationRecorder() as rec_tria: - Interferometer(theta, phi, varphi, wires=wires) - - for rec in [rec_rect, rec_tria]: - # test both meshes (both give identical results for the 3 mode case). - assert len(rec.queue) == 6 - - expected_bs_wires = [[0, 1], [1, 2], [0, 1]] - - for idx, op in enumerate(rec_rect.queue[:3]): - assert isinstance(op, qml.Beamsplitter) - assert op.parameters == [theta[idx], phi[idx]] - assert op.wires == Wires(expected_bs_wires[idx]) - - for idx, op in enumerate(rec.queue[3:]): - assert isinstance(op, qml.Rotation) - assert op.parameters == [varphi[idx]] - assert op.wires == Wires([idx]) - - def test_four_mode_rect(self, tol): - """Test that a 4 mode interferometer using rectangular mesh gives the correct gates""" - N = 4 - wires = range(N) - - theta = [0.321, 0.4523, 0.21321, 0.123, 0.5234, 1.23] - phi = [0.234, 0.324, 0.234, 1.453, 1.42341, -0.534] - varphi = [0.42342, 0.234, 0.4523, 0.1121] - - with qml.tape.OperationRecorder() as rec: - Interferometer(theta, phi, varphi, wires=wires) - - assert len(rec.queue) == 10 - - expected_bs_wires = [[0, 1], [2, 3], [1, 2], [0, 1], [2, 3], [1, 2]] - - for idx, op in enumerate(rec.queue[:6]): - assert isinstance(op, qml.Beamsplitter) - assert op.parameters == [theta[idx], phi[idx]] - assert op.wires == Wires(expected_bs_wires[idx]) - - for idx, op in enumerate(rec.queue[6:]): - assert isinstance(op, qml.Rotation) - assert op.parameters == [varphi[idx]] - assert op.wires == Wires([idx]) - - def test_four_mode_triangular(self, tol): - """Test that a 4 mode interferometer using triangular mesh gives the correct gates""" - N = 4 - wires = range(N) - - theta = [0.321, 0.4523, 0.21321, 0.123, 0.5234, 1.23] - phi = [0.234, 0.324, 0.234, 1.453, 1.42341, -0.534] - varphi = [0.42342, 0.234, 0.4523, 0.1121] - - with qml.tape.OperationRecorder() as rec: - Interferometer(theta, phi, varphi, mesh="triangular", wires=wires) - - assert len(rec.queue) == 10 - - expected_bs_wires = [[2, 3], [1, 2], [0, 1], [2, 3], [1, 2], [2, 3]] - - for idx, op in enumerate(rec.queue[:6]): - assert isinstance(op, qml.Beamsplitter) - assert op.parameters == [theta[idx], phi[idx]] - assert op.wires == Wires(expected_bs_wires[idx]) - - for idx, op in enumerate(rec.queue[6:]): - assert isinstance(op, qml.Rotation) - assert op.parameters == [varphi[idx]] - assert op.wires == Wires([idx]) - - def test_integration(self, tol): - """test integration with PennyLane and gradient calculations""" - N = 4 - wires = range(N) - dev = qml.device("default.gaussian", wires=N) - - sq = np.array( - [ - [0.8734294, 0.96854066], - [0.86919454, 0.53085569], - [0.23272833, 0.0113988], - [0.43046882, 0.40235136], - ] - ) - - theta = np.array([3.28406182, 3.0058243, 3.48940764, 3.41419504, 4.7808479, 4.47598146]) - phi = np.array([3.89357744, 2.67721355, 1.81631197, 6.11891294, 2.09716418, 1.37476761]) - varphi = np.array([0.4134863, 6.17555778, 0.80334114, 2.02400747]) - - @qml.qnode(dev) - def circuit(theta, phi, varphi): - for w in wires: - qml.Squeezing(sq[w][0], sq[w][1], wires=w) - - Interferometer(theta=theta, phi=phi, varphi=varphi, wires=wires) - return [qml.expval(qml.NumberOperator(w)) for w in wires] - - res = circuit(theta, phi, varphi) - expected = np.array([0.96852694, 0.23878521, 0.82310606, 0.16547786]) - assert np.allclose(res, expected, atol=tol) - - def test_interferometer_wrong_dim(self): - """Integration test for the CVNeuralNetLayers method.""" - dev = qml.device("default.gaussian", wires=4) - - @qml.qnode(dev) - def circuit(theta, phi, varphi): - Interferometer(theta=theta, phi=phi, varphi=varphi, wires=range(4)) - return qml.expval(qml.X(0)) - - theta = np.array([3.28406182, 3.0058243, 3.48940764, 3.41419504, 4.7808479, 4.47598146]) - phi = np.array([3.89357744, 2.67721355, 1.81631197, 6.11891294, 2.09716418, 1.37476761]) - varphi = np.array([0.4134863, 6.17555778, 0.80334114, 2.02400747]) - - with pytest.raises(ValueError, match=r"Theta must be of shape \(6,\)"): - wrong_theta = np.array([0.1, 0.2]) - circuit(wrong_theta, phi, varphi) - - with pytest.raises(ValueError, match=r"Phi must be of shape \(6,\)"): - wrong_phi = np.array([0.1, 0.2]) - circuit(theta, wrong_phi, varphi) - - with pytest.raises(ValueError, match=r"Varphi must be of shape \(4,\)"): - wrong_varphi = np.array([0.1, 0.2]) - circuit(theta, phi, wrong_varphi) +# class TestInterferometer: +# """Tests for the Interferometer from the pennylane.template.layers module.""" +# +# def test_invalid_mesh_exception(self): +# """Test that Interferometer() raises correct exception when mesh not recognized.""" +# dev = qml.device("default.gaussian", wires=2) +# varphi = [0.42342, 0.234] +# +# @qml.qnode(dev) +# def circuit(varphi, mesh=None): +# Interferometer(theta=[0.21], phi=[0.53], varphi=varphi, mesh=mesh, wires=[0, 1]) +# return qml.expval(qml.NumberOperator(0)) +# +# with pytest.raises(ValueError, match="did not recognize mesh"): +# circuit(varphi, mesh="a") +# +# @pytest.mark.parametrize("mesh", ["rectangular", "triangular"]) +# def test_invalid_beamsplitter_exception(self, mesh): +# """Test that Interferometer() raises correct exception when beamsplitter not recognized.""" +# dev = qml.device("default.gaussian", wires=2) +# varphi = [0.42342, 0.234] +# +# @qml.qnode(dev) +# def circuit(varphi, bs=None): +# Interferometer(theta=[0.21], phi=[0.53], varphi=varphi, beamsplitter=bs, mesh=mesh, wires=[0, 1]) +# return qml.expval(qml.NumberOperator(0)) +# +# with pytest.raises(ValueError, match="did not recognize beamsplitter"): +# circuit(varphi, bs="a") +# +# def test_clements_beamsplitter_convention(self, tol): +# """test the beamsplitter convention""" +# N = 2 +# wires = range(N) +# +# theta = [0.321] +# phi = [0.234] +# varphi = [0.42342, 0.1121] +# +# with qml.tape.OperationRecorder() as rec_rect: +# Interferometer( +# theta, phi, varphi, mesh="rectangular", beamsplitter="clements", wires=wires +# ) +# +# with qml.tape.OperationRecorder() as rec_tria: +# Interferometer( +# theta, phi, varphi, mesh="triangular", beamsplitter="clements", wires=wires +# ) +# +# for rec in [rec_rect, rec_tria]: +# assert len(rec.queue) == 4 +# +# assert isinstance(rec.queue[0], qml.Rotation) +# assert rec.queue[0].parameters == phi +# +# assert isinstance(rec.queue[1], qml.Beamsplitter) +# assert rec.queue[1].parameters == [theta[0], 0] +# +# assert isinstance(rec.queue[2], qml.Rotation) +# assert rec.queue[2].parameters == [varphi[0]] +# +# assert isinstance(rec.queue[3], qml.Rotation) +# assert rec.queue[3].parameters == [varphi[1]] +# +# def test_one_mode(self, tol): +# """Test that a one mode interferometer correctly gives a rotation gate""" +# varphi = [0.42342] +# +# with qml.tape.OperationRecorder() as rec: +# Interferometer(theta=[], phi=[], varphi=varphi, wires=0) +# +# assert len(rec.queue) == 1 +# assert isinstance(rec.queue[0], qml.Rotation) +# assert np.allclose(rec.queue[0].parameters, varphi, atol=tol) +# +# def test_two_mode_rect(self, tol): +# """Test that a two mode interferometer using the rectangular mesh +# correctly gives a beamsplitter+rotation gate""" +# N = 2 +# wires = range(N) +# +# theta = [0.321] +# phi = [0.234] +# varphi = [0.42342, 0.1121] +# +# with qml.tape.OperationRecorder() as rec: +# Interferometer(theta, phi, varphi, wires=wires) +# +# isinstance(rec.queue[0], qml.Beamsplitter) +# assert rec.queue[0].parameters == theta + phi +# +# assert isinstance(rec.queue[1], qml.Rotation) +# assert rec.queue[1].parameters == [varphi[0]] +# +# assert isinstance(rec.queue[2], qml.Rotation) +# assert rec.queue[2].parameters == [varphi[1]] +# +# def test_two_mode_triangular(self, tol): +# """Test that a two mode interferometer using the triangular mesh +# correctly gives a beamsplitter+rotation gate""" +# N = 2 +# wires = range(N) +# +# theta = [0.321] +# phi = [0.234] +# varphi = [0.42342, 0.1121] +# +# with qml.tape.OperationRecorder() as rec: +# Interferometer(theta, phi, varphi, mesh="triangular", wires=wires) +# +# assert len(rec.queue) == 3 +# +# assert isinstance(rec.queue[0], qml.Beamsplitter) +# assert rec.queue[0].parameters == theta + phi +# +# assert isinstance(rec.queue[1], qml.Rotation) +# assert rec.queue[1].parameters == [varphi[0]] +# +# assert isinstance(rec.queue[2], qml.Rotation) +# assert rec.queue[2].parameters == [varphi[1]] +# +# def test_three_mode(self, tol): +# """Test that a three mode interferometer using either mesh gives the correct gates""" +# N = 3 +# wires = range(N) +# +# theta = [0.321, 0.4523, 0.21321] +# phi = [0.234, 0.324, 0.234] +# varphi = [0.42342, 0.234, 0.1121] +# +# with qml.tape.OperationRecorder() as rec_rect: +# Interferometer(theta, phi, varphi, wires=wires) +# +# with qml.tape.OperationRecorder() as rec_tria: +# Interferometer(theta, phi, varphi, wires=wires) +# +# for rec in [rec_rect, rec_tria]: +# # test both meshes (both give identical results for the 3 mode case). +# assert len(rec.queue) == 6 +# +# expected_bs_wires = [[0, 1], [1, 2], [0, 1]] +# +# for idx, op in enumerate(rec_rect.queue[:3]): +# assert isinstance(op, qml.Beamsplitter) +# assert op.parameters == [theta[idx], phi[idx]] +# assert op.wires == Wires(expected_bs_wires[idx]) +# +# for idx, op in enumerate(rec.queue[3:]): +# assert isinstance(op, qml.Rotation) +# assert op.parameters == [varphi[idx]] +# assert op.wires == Wires([idx]) +# +# def test_four_mode_rect(self, tol): +# """Test that a 4 mode interferometer using rectangular mesh gives the correct gates""" +# N = 4 +# wires = range(N) +# +# theta = [0.321, 0.4523, 0.21321, 0.123, 0.5234, 1.23] +# phi = [0.234, 0.324, 0.234, 1.453, 1.42341, -0.534] +# varphi = [0.42342, 0.234, 0.4523, 0.1121] +# +# with qml.tape.OperationRecorder() as rec: +# Interferometer(theta, phi, varphi, wires=wires) +# +# assert len(rec.queue) == 10 +# +# expected_bs_wires = [[0, 1], [2, 3], [1, 2], [0, 1], [2, 3], [1, 2]] +# +# for idx, op in enumerate(rec.queue[:6]): +# assert isinstance(op, qml.Beamsplitter) +# assert op.parameters == [theta[idx], phi[idx]] +# assert op.wires == Wires(expected_bs_wires[idx]) +# +# for idx, op in enumerate(rec.queue[6:]): +# assert isinstance(op, qml.Rotation) +# assert op.parameters == [varphi[idx]] +# assert op.wires == Wires([idx]) +# +# def test_four_mode_triangular(self, tol): +# """Test that a 4 mode interferometer using triangular mesh gives the correct gates""" +# N = 4 +# wires = range(N) +# +# theta = [0.321, 0.4523, 0.21321, 0.123, 0.5234, 1.23] +# phi = [0.234, 0.324, 0.234, 1.453, 1.42341, -0.534] +# varphi = [0.42342, 0.234, 0.4523, 0.1121] +# +# with qml.tape.OperationRecorder() as rec: +# Interferometer(theta, phi, varphi, mesh="triangular", wires=wires) +# +# assert len(rec.queue) == 10 +# +# expected_bs_wires = [[2, 3], [1, 2], [0, 1], [2, 3], [1, 2], [2, 3]] +# +# for idx, op in enumerate(rec.queue[:6]): +# assert isinstance(op, qml.Beamsplitter) +# assert op.parameters == [theta[idx], phi[idx]] +# assert op.wires == Wires(expected_bs_wires[idx]) +# +# for idx, op in enumerate(rec.queue[6:]): +# assert isinstance(op, qml.Rotation) +# assert op.parameters == [varphi[idx]] +# assert op.wires == Wires([idx]) +# +# def test_integration(self, tol): +# """test integration with PennyLane and gradient calculations""" +# N = 4 +# wires = range(N) +# dev = qml.device("default.gaussian", wires=N) +# +# sq = np.array( +# [ +# [0.8734294, 0.96854066], +# [0.86919454, 0.53085569], +# [0.23272833, 0.0113988], +# [0.43046882, 0.40235136], +# ] +# ) +# +# theta = np.array([3.28406182, 3.0058243, 3.48940764, 3.41419504, 4.7808479, 4.47598146]) +# phi = np.array([3.89357744, 2.67721355, 1.81631197, 6.11891294, 2.09716418, 1.37476761]) +# varphi = np.array([0.4134863, 6.17555778, 0.80334114, 2.02400747]) +# +# @qml.qnode(dev) +# def circuit(theta, phi, varphi): +# for w in wires: +# qml.Squeezing(sq[w][0], sq[w][1], wires=w) +# +# Interferometer(theta=theta, phi=phi, varphi=varphi, wires=wires) +# return [qml.expval(qml.NumberOperator(w)) for w in wires] +# +# res = circuit(theta, phi, varphi) +# expected = np.array([0.96852694, 0.23878521, 0.82310606, 0.16547786]) +# assert np.allclose(res, expected, atol=tol) +# +# def test_interferometer_wrong_dim(self): +# """Integration test for the CVNeuralNetLayers method.""" +# dev = qml.device("default.gaussian", wires=4) +# +# @qml.qnode(dev) +# def circuit(theta, phi, varphi): +# Interferometer(theta=theta, phi=phi, varphi=varphi, wires=range(4)) +# return qml.expval(qml.X(0)) +# +# theta = np.array([3.28406182, 3.0058243, 3.48940764, 3.41419504, 4.7808479, 4.47598146]) +# phi = np.array([3.89357744, 2.67721355, 1.81631197, 6.11891294, 2.09716418, 1.37476761]) +# varphi = np.array([0.4134863, 6.17555778, 0.80334114, 2.02400747]) +# +# with pytest.raises(ValueError, match=r"Theta must be of shape \(6,\)"): +# wrong_theta = np.array([0.1, 0.2]) +# circuit(wrong_theta, phi, varphi) +# +# with pytest.raises(ValueError, match=r"Phi must be of shape \(6,\)"): +# wrong_phi = np.array([0.1, 0.2]) +# circuit(theta, wrong_phi, varphi) +# +# with pytest.raises(ValueError, match=r"Varphi must be of shape \(4,\)"): +# wrong_varphi = np.array([0.1, 0.2]) +# circuit(theta, phi, wrong_varphi) class TestSingleExcitationUnitary: From 28c3f7e3f1bd0bef3647f9986946e01d49a9369a Mon Sep 17 00:00:00 2001 From: Maria Schuld Date: Tue, 6 Apr 2021 10:12:53 +0200 Subject: [PATCH 10/16] implement some review comments --- pennylane/templates/layers/cv_neural_net.py | 2 +- tests/templates/test_layers/test_cv_neural_net.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pennylane/templates/layers/cv_neural_net.py b/pennylane/templates/layers/cv_neural_net.py index 9c36ca2caf2..8ab4c59d618 100644 --- a/pennylane/templates/layers/cv_neural_net.py +++ b/pennylane/templates/layers/cv_neural_net.py @@ -65,7 +65,7 @@ class CVNeuralNetLayers(Operation): **Parameter shapes** - A list of shapes for the 11 weight tensors can be computed by the static method + A list of shapes for the 11 input parameter tensors can be computed by the static method :meth:`~.CVNeuralNetLayers.shapes` and used when creating randomly initialised weights: diff --git a/tests/templates/test_layers/test_cv_neural_net.py b/tests/templates/test_layers/test_cv_neural_net.py index 76caedb1781..439585f871f 100644 --- a/tests/templates/test_layers/test_cv_neural_net.py +++ b/tests/templates/test_layers/test_cv_neural_net.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """ -Unit tests for the BasicEntanglerLayers template. +Unit tests for the CVNeuralNetLayers template. """ import pytest import numpy as np From d3e748b5e5c5f30cfa39baf3307cebf06af01e5d Mon Sep 17 00:00:00 2001 From: Maria Schuld Date: Tue, 6 Apr 2021 10:29:08 +0200 Subject: [PATCH 11/16] reverse changes to interferometer --- .../templates/subroutines/interferometer.py | 193 ++++--- .../test_layers/test_cv_neural_net.py | 12 +- tests/templates/test_subroutines.py | 518 +++++++++--------- 3 files changed, 359 insertions(+), 364 deletions(-) diff --git a/pennylane/templates/subroutines/interferometer.py b/pennylane/templates/subroutines/interferometer.py index 13d12fdd75b..bfc3b6de390 100644 --- a/pennylane/templates/subroutines/interferometer.py +++ b/pennylane/templates/subroutines/interferometer.py @@ -12,15 +12,51 @@ # See the License for the specific language governing permissions and # limitations under the License. r""" -Contains the Interferometer template. +Contains the ``Interferometer`` template. """ import pennylane as qml # pylint: disable-msg=too-many-branches,too-many-arguments,protected-access -from pennylane.operation import Operation, AnyWires +from pennylane.templates.decorator import template +from pennylane.ops import Beamsplitter, Rotation +from pennylane.wires import Wires -class Interferometer(Operation): +def _preprocess(theta, phi, varphi, wires): + """Validate and pre-process inputs as follows: + + * Check the shape of the three weight tensors. + + Args: + theta (tensor_like): trainable parameters of the template + phi (tensor_like): trainable parameters of the template + varphi (tensor_like): trainable parameters of the template + wires (Wires): wires that the template acts on + + Returns: + tuple: shape of varphi tensor + """ + + n_wires = len(wires) + n_if = n_wires * (n_wires - 1) // 2 + + shape = qml.math.shape(theta) + if shape != (n_if,): + raise ValueError(f"Theta must be of shape {(n_if,)}; got {shape}.") + + shape = qml.math.shape(phi) + if shape != (n_if,): + raise ValueError(f"Phi must be of shape {(n_if,)}; got {shape}.") + + shape_varphi = qml.math.shape(varphi) + if shape_varphi != (n_wires,): + raise ValueError(f"Varphi must be of shape {(n_wires,)}; got {shape_varphi}.") + + return shape_varphi + + +@template +def Interferometer(theta, phi, varphi, wires, mesh="rectangular", beamsplitter="pennylane"): r"""General linear interferometer, an array of beamsplitters and phase shifters. For :math:`M` wires, the general interferometer is specified by @@ -89,107 +125,62 @@ class Interferometer(Operation): theta (tensor_like): size :math:`(M(M-1)/2,)` tensor of transmittivity angles :math:`\theta` phi (tensor_like): size :math:`(M(M-1)/2,)` tensor of phase angles :math:`\phi` varphi (tensor_like): size :math:`(M,)` tensor of rotation angles :math:`\varphi` - wires (Iterable): wires that the template acts on + wires (Iterable or Wires): Wires that the template acts on. Accepts an iterable of numbers or strings, or + a Wires object. mesh (string): the type of mesh to use beamsplitter (str): if ``clements``, the beamsplitter convention from Clements et al. 2016 (https://dx.doi.org/10.1364/OPTICA.3.001460) is used; if ``pennylane``, the beamsplitter is implemented via PennyLane's ``Beamsplitter`` operation. + Raises: + ValueError: if inputs do not have the correct format """ - num_params = 3 - num_wires = AnyWires - par_domain = "A" - - def __init__( - self, theta, phi, varphi, wires, mesh="rectangular", beamsplitter="pennylane", do_queue=True - ): - - n_wires = len(wires) - n_if = n_wires * (n_wires - 1) // 2 - - shape = qml.math.shape(theta) - if shape != (n_if,): - raise ValueError(f"Theta must be of shape {(n_if,)}; got {shape}.") - - shape = qml.math.shape(phi) - if shape != (n_if,): - raise ValueError(f"Phi must be of shape {(n_if,)}; got {shape}.") - - shape = qml.math.shape(varphi) - if shape != (n_wires,): - raise ValueError(f"Varphi must be of shape {(n_wires,)}; got {shape}.") - - self.mesh = mesh - self.beamsplitter = beamsplitter - super().__init__(theta, phi, varphi, wires=wires, do_queue=do_queue) - - def expand(self): - - theta = self.parameters[0] - phi = self.parameters[1] - varphi = self.parameters[2] - - with qml.tape.QuantumTape() as tape: - - if len(self.wires) == 1: - # the interferometer is a single rotation - qml.Rotation(varphi[0], wires=self.wires[0]) - return - - n = 0 # keep track of free parameters - - if self.mesh == "rectangular": - # Apply the Clements beamsplitter array - # The array depth is N - for l in range(len(self.wires)): - for k, (w1, w2) in enumerate(zip(self.wires[:-1], self.wires[1:])): - # skip even or odd pairs depending on layer - if (l + k) % 2 != 1: - if self.beamsplitter == "clements": - qml.Rotation(phi[n], wires=w1) - qml.Beamsplitter(theta[n], 0, wires=[w1, w2]) - elif self.beamsplitter == "pennylane": - qml.Beamsplitter(theta[n], phi[n], wires=[w1, w2]) - else: - raise ValueError( - f"did not recognize beamsplitter {self.beamsplitter}" - ) - n += 1 - - elif self.mesh == "triangular": - # apply the Reck beamsplitter array - # The array depth is 2*N-3 - for l in range(2 * len(self.wires) - 3): - for k in range(abs(l + 1 - (len(self.wires) - 1)), len(self.wires) - 1, 2): - if self.beamsplitter == "clements": - qml.Rotation(phi[n], wires=self.wires[k]) - qml.Beamsplitter(theta[n], 0, wires=self.wires[k, k + 1]) - elif self.beamsplitter == "pennylane": - qml.Beamsplitter(theta[n], phi[n], wires=self.wires[k, k + 1]) - else: - raise ValueError(f"did not recognize beamsplitter {self.beamsplitter} ") - n += 1 - else: - raise ValueError(f"did not recognize mesh {self.mesh}") - - # apply the final local phase shifts to all modes - for i in range(qml.math.shape(varphi)[0]): - act_on = self.wires[i] - qml.Rotation(varphi[i], wires=act_on) - - return tape - - @staticmethod - def shape(n_wires): - r"""Returns a list of shapes for the three parameter tensors. - - Args: - n_wires (int): number of wires - - Returns: - list[tuple[int]]: list of shapes - """ - n_if = n_wires * (n_wires - 1) // 2 - shapes = [(n_if,), (n_if,), (n_wires,)] - - return shapes + + wires = Wires(wires) + M = len(wires) + + shape_varphi = _preprocess(theta, phi, varphi, wires) + + if M == 1: + # the interferometer is a single rotation + Rotation(varphi[0], wires=wires[0]) + return + + n = 0 # keep track of free parameters + + if mesh == "rectangular": + # Apply the Clements beamsplitter array + # The array depth is N + for l in range(M): + for k, (w1, w2) in enumerate(zip(wires[:-1], wires[1:])): + # skip even or odd pairs depending on layer + if (l + k) % 2 != 1: + if beamsplitter == "clements": + Rotation(phi[n], wires=Wires(w1)) + Beamsplitter(theta[n], 0, wires=Wires([w1, w2])) + elif beamsplitter == "pennylane": + Beamsplitter(theta[n], phi[n], wires=Wires([w1, w2])) + else: + raise ValueError(f"did not recognize beamsplitter {beamsplitter}") + n += 1 + + elif mesh == "triangular": + # apply the Reck beamsplitter array + # The array depth is 2*N-3 + for l in range(2 * M - 3): + for k in range(abs(l + 1 - (M - 1)), M - 1, 2): + if beamsplitter == "clements": + Rotation(phi[n], wires=wires[k]) + Beamsplitter(theta[n], 0, wires=wires.subset([k, k + 1])) + elif beamsplitter == "pennylane": + Beamsplitter(theta[n], phi[n], wires=wires.subset([k, k + 1])) + else: + raise ValueError(f"did not recognize beamsplitter {beamsplitter} ") + n += 1 + else: + raise ValueError(f"did not recognize mesh {mesh}") + + # apply the final local phase shifts to all modes + for i in range(shape_varphi[0]): + act_on = wires[i] + Rotation(varphi[i], wires=act_on) diff --git a/tests/templates/test_layers/test_cv_neural_net.py b/tests/templates/test_layers/test_cv_neural_net.py index 439585f871f..99072a7cb71 100644 --- a/tests/templates/test_layers/test_cv_neural_net.py +++ b/tests/templates/test_layers/test_cv_neural_net.py @@ -44,20 +44,24 @@ class TestDecomposition: """Tests that the template defines the correct decomposition.""" QUEUES = [ - (1, ["Interferometer", "Squeezing", "Interferometer", "Displacement", "Kerr"], [[0]] * 5), + (1, ["Rotation", "Squeezing", "Rotation", "Displacement", "Kerr"], [[0]] * 5), ( 2, [ - "Interferometer", + "Beamsplitter", # Interferometer 1 + "Rotation", # Interferometer 1 + "Rotation", # Interferometer 1 "Squeezing", "Squeezing", - "Interferometer", + "Beamsplitter", # Interferometer 2 + "Rotation", # Interferometer 2 + "Rotation", # Interferometer 2 "Displacement", "Displacement", "Kerr", "Kerr", ], - [[0, 1], [0], [1], [0, 1], [0], [1], [0], [1]], + [[0, 1], [0], [1], [0], [1], [0, 1], [0], [1], [0], [1], [0], [1]], ), ] diff --git a/tests/templates/test_subroutines.py b/tests/templates/test_subroutines.py index 6cffc65c8de..ef3f4a8e9a7 100644 --- a/tests/templates/test_subroutines.py +++ b/tests/templates/test_subroutines.py @@ -110,265 +110,265 @@ def test_tuple_to_word(self, tuple, expected_word): assert _tuple_to_word(tuple) == expected_word -# class TestInterferometer: -# """Tests for the Interferometer from the pennylane.template.layers module.""" -# -# def test_invalid_mesh_exception(self): -# """Test that Interferometer() raises correct exception when mesh not recognized.""" -# dev = qml.device("default.gaussian", wires=2) -# varphi = [0.42342, 0.234] -# -# @qml.qnode(dev) -# def circuit(varphi, mesh=None): -# Interferometer(theta=[0.21], phi=[0.53], varphi=varphi, mesh=mesh, wires=[0, 1]) -# return qml.expval(qml.NumberOperator(0)) -# -# with pytest.raises(ValueError, match="did not recognize mesh"): -# circuit(varphi, mesh="a") -# -# @pytest.mark.parametrize("mesh", ["rectangular", "triangular"]) -# def test_invalid_beamsplitter_exception(self, mesh): -# """Test that Interferometer() raises correct exception when beamsplitter not recognized.""" -# dev = qml.device("default.gaussian", wires=2) -# varphi = [0.42342, 0.234] -# -# @qml.qnode(dev) -# def circuit(varphi, bs=None): -# Interferometer(theta=[0.21], phi=[0.53], varphi=varphi, beamsplitter=bs, mesh=mesh, wires=[0, 1]) -# return qml.expval(qml.NumberOperator(0)) -# -# with pytest.raises(ValueError, match="did not recognize beamsplitter"): -# circuit(varphi, bs="a") -# -# def test_clements_beamsplitter_convention(self, tol): -# """test the beamsplitter convention""" -# N = 2 -# wires = range(N) -# -# theta = [0.321] -# phi = [0.234] -# varphi = [0.42342, 0.1121] -# -# with qml.tape.OperationRecorder() as rec_rect: -# Interferometer( -# theta, phi, varphi, mesh="rectangular", beamsplitter="clements", wires=wires -# ) -# -# with qml.tape.OperationRecorder() as rec_tria: -# Interferometer( -# theta, phi, varphi, mesh="triangular", beamsplitter="clements", wires=wires -# ) -# -# for rec in [rec_rect, rec_tria]: -# assert len(rec.queue) == 4 -# -# assert isinstance(rec.queue[0], qml.Rotation) -# assert rec.queue[0].parameters == phi -# -# assert isinstance(rec.queue[1], qml.Beamsplitter) -# assert rec.queue[1].parameters == [theta[0], 0] -# -# assert isinstance(rec.queue[2], qml.Rotation) -# assert rec.queue[2].parameters == [varphi[0]] -# -# assert isinstance(rec.queue[3], qml.Rotation) -# assert rec.queue[3].parameters == [varphi[1]] -# -# def test_one_mode(self, tol): -# """Test that a one mode interferometer correctly gives a rotation gate""" -# varphi = [0.42342] -# -# with qml.tape.OperationRecorder() as rec: -# Interferometer(theta=[], phi=[], varphi=varphi, wires=0) -# -# assert len(rec.queue) == 1 -# assert isinstance(rec.queue[0], qml.Rotation) -# assert np.allclose(rec.queue[0].parameters, varphi, atol=tol) -# -# def test_two_mode_rect(self, tol): -# """Test that a two mode interferometer using the rectangular mesh -# correctly gives a beamsplitter+rotation gate""" -# N = 2 -# wires = range(N) -# -# theta = [0.321] -# phi = [0.234] -# varphi = [0.42342, 0.1121] -# -# with qml.tape.OperationRecorder() as rec: -# Interferometer(theta, phi, varphi, wires=wires) -# -# isinstance(rec.queue[0], qml.Beamsplitter) -# assert rec.queue[0].parameters == theta + phi -# -# assert isinstance(rec.queue[1], qml.Rotation) -# assert rec.queue[1].parameters == [varphi[0]] -# -# assert isinstance(rec.queue[2], qml.Rotation) -# assert rec.queue[2].parameters == [varphi[1]] -# -# def test_two_mode_triangular(self, tol): -# """Test that a two mode interferometer using the triangular mesh -# correctly gives a beamsplitter+rotation gate""" -# N = 2 -# wires = range(N) -# -# theta = [0.321] -# phi = [0.234] -# varphi = [0.42342, 0.1121] -# -# with qml.tape.OperationRecorder() as rec: -# Interferometer(theta, phi, varphi, mesh="triangular", wires=wires) -# -# assert len(rec.queue) == 3 -# -# assert isinstance(rec.queue[0], qml.Beamsplitter) -# assert rec.queue[0].parameters == theta + phi -# -# assert isinstance(rec.queue[1], qml.Rotation) -# assert rec.queue[1].parameters == [varphi[0]] -# -# assert isinstance(rec.queue[2], qml.Rotation) -# assert rec.queue[2].parameters == [varphi[1]] -# -# def test_three_mode(self, tol): -# """Test that a three mode interferometer using either mesh gives the correct gates""" -# N = 3 -# wires = range(N) -# -# theta = [0.321, 0.4523, 0.21321] -# phi = [0.234, 0.324, 0.234] -# varphi = [0.42342, 0.234, 0.1121] -# -# with qml.tape.OperationRecorder() as rec_rect: -# Interferometer(theta, phi, varphi, wires=wires) -# -# with qml.tape.OperationRecorder() as rec_tria: -# Interferometer(theta, phi, varphi, wires=wires) -# -# for rec in [rec_rect, rec_tria]: -# # test both meshes (both give identical results for the 3 mode case). -# assert len(rec.queue) == 6 -# -# expected_bs_wires = [[0, 1], [1, 2], [0, 1]] -# -# for idx, op in enumerate(rec_rect.queue[:3]): -# assert isinstance(op, qml.Beamsplitter) -# assert op.parameters == [theta[idx], phi[idx]] -# assert op.wires == Wires(expected_bs_wires[idx]) -# -# for idx, op in enumerate(rec.queue[3:]): -# assert isinstance(op, qml.Rotation) -# assert op.parameters == [varphi[idx]] -# assert op.wires == Wires([idx]) -# -# def test_four_mode_rect(self, tol): -# """Test that a 4 mode interferometer using rectangular mesh gives the correct gates""" -# N = 4 -# wires = range(N) -# -# theta = [0.321, 0.4523, 0.21321, 0.123, 0.5234, 1.23] -# phi = [0.234, 0.324, 0.234, 1.453, 1.42341, -0.534] -# varphi = [0.42342, 0.234, 0.4523, 0.1121] -# -# with qml.tape.OperationRecorder() as rec: -# Interferometer(theta, phi, varphi, wires=wires) -# -# assert len(rec.queue) == 10 -# -# expected_bs_wires = [[0, 1], [2, 3], [1, 2], [0, 1], [2, 3], [1, 2]] -# -# for idx, op in enumerate(rec.queue[:6]): -# assert isinstance(op, qml.Beamsplitter) -# assert op.parameters == [theta[idx], phi[idx]] -# assert op.wires == Wires(expected_bs_wires[idx]) -# -# for idx, op in enumerate(rec.queue[6:]): -# assert isinstance(op, qml.Rotation) -# assert op.parameters == [varphi[idx]] -# assert op.wires == Wires([idx]) -# -# def test_four_mode_triangular(self, tol): -# """Test that a 4 mode interferometer using triangular mesh gives the correct gates""" -# N = 4 -# wires = range(N) -# -# theta = [0.321, 0.4523, 0.21321, 0.123, 0.5234, 1.23] -# phi = [0.234, 0.324, 0.234, 1.453, 1.42341, -0.534] -# varphi = [0.42342, 0.234, 0.4523, 0.1121] -# -# with qml.tape.OperationRecorder() as rec: -# Interferometer(theta, phi, varphi, mesh="triangular", wires=wires) -# -# assert len(rec.queue) == 10 -# -# expected_bs_wires = [[2, 3], [1, 2], [0, 1], [2, 3], [1, 2], [2, 3]] -# -# for idx, op in enumerate(rec.queue[:6]): -# assert isinstance(op, qml.Beamsplitter) -# assert op.parameters == [theta[idx], phi[idx]] -# assert op.wires == Wires(expected_bs_wires[idx]) -# -# for idx, op in enumerate(rec.queue[6:]): -# assert isinstance(op, qml.Rotation) -# assert op.parameters == [varphi[idx]] -# assert op.wires == Wires([idx]) -# -# def test_integration(self, tol): -# """test integration with PennyLane and gradient calculations""" -# N = 4 -# wires = range(N) -# dev = qml.device("default.gaussian", wires=N) -# -# sq = np.array( -# [ -# [0.8734294, 0.96854066], -# [0.86919454, 0.53085569], -# [0.23272833, 0.0113988], -# [0.43046882, 0.40235136], -# ] -# ) -# -# theta = np.array([3.28406182, 3.0058243, 3.48940764, 3.41419504, 4.7808479, 4.47598146]) -# phi = np.array([3.89357744, 2.67721355, 1.81631197, 6.11891294, 2.09716418, 1.37476761]) -# varphi = np.array([0.4134863, 6.17555778, 0.80334114, 2.02400747]) -# -# @qml.qnode(dev) -# def circuit(theta, phi, varphi): -# for w in wires: -# qml.Squeezing(sq[w][0], sq[w][1], wires=w) -# -# Interferometer(theta=theta, phi=phi, varphi=varphi, wires=wires) -# return [qml.expval(qml.NumberOperator(w)) for w in wires] -# -# res = circuit(theta, phi, varphi) -# expected = np.array([0.96852694, 0.23878521, 0.82310606, 0.16547786]) -# assert np.allclose(res, expected, atol=tol) -# -# def test_interferometer_wrong_dim(self): -# """Integration test for the CVNeuralNetLayers method.""" -# dev = qml.device("default.gaussian", wires=4) -# -# @qml.qnode(dev) -# def circuit(theta, phi, varphi): -# Interferometer(theta=theta, phi=phi, varphi=varphi, wires=range(4)) -# return qml.expval(qml.X(0)) -# -# theta = np.array([3.28406182, 3.0058243, 3.48940764, 3.41419504, 4.7808479, 4.47598146]) -# phi = np.array([3.89357744, 2.67721355, 1.81631197, 6.11891294, 2.09716418, 1.37476761]) -# varphi = np.array([0.4134863, 6.17555778, 0.80334114, 2.02400747]) -# -# with pytest.raises(ValueError, match=r"Theta must be of shape \(6,\)"): -# wrong_theta = np.array([0.1, 0.2]) -# circuit(wrong_theta, phi, varphi) -# -# with pytest.raises(ValueError, match=r"Phi must be of shape \(6,\)"): -# wrong_phi = np.array([0.1, 0.2]) -# circuit(theta, wrong_phi, varphi) -# -# with pytest.raises(ValueError, match=r"Varphi must be of shape \(4,\)"): -# wrong_varphi = np.array([0.1, 0.2]) -# circuit(theta, phi, wrong_varphi) +class TestInterferometer: + """Tests for the Interferometer from the pennylane.template.layers module.""" + + def test_invalid_mesh_exception(self): + """Test that Interferometer() raises correct exception when mesh not recognized.""" + dev = qml.device("default.gaussian", wires=2) + varphi = [0.42342, 0.234] + + @qml.qnode(dev) + def circuit(varphi, mesh=None): + Interferometer(theta=[0.21], phi=[0.53], varphi=varphi, mesh=mesh, wires=[0, 1]) + return qml.expval(qml.NumberOperator(0)) + + with pytest.raises(ValueError, match="did not recognize mesh"): + circuit(varphi, mesh="a") + + @pytest.mark.parametrize("mesh", ["rectangular", "triangular"]) + def test_invalid_beamsplitter_exception(self, mesh): + """Test that Interferometer() raises correct exception when beamsplitter not recognized.""" + dev = qml.device("default.gaussian", wires=2) + varphi = [0.42342, 0.234] + + @qml.qnode(dev) + def circuit(varphi, bs=None): + Interferometer(theta=[0.21], phi=[0.53], varphi=varphi, beamsplitter=bs, mesh=mesh, wires=[0, 1]) + return qml.expval(qml.NumberOperator(0)) + + with pytest.raises(ValueError, match="did not recognize beamsplitter"): + circuit(varphi, bs="a") + + def test_clements_beamsplitter_convention(self, tol): + """test the beamsplitter convention""" + N = 2 + wires = range(N) + + theta = [0.321] + phi = [0.234] + varphi = [0.42342, 0.1121] + + with qml.tape.OperationRecorder() as rec_rect: + Interferometer( + theta, phi, varphi, mesh="rectangular", beamsplitter="clements", wires=wires + ) + + with qml.tape.OperationRecorder() as rec_tria: + Interferometer( + theta, phi, varphi, mesh="triangular", beamsplitter="clements", wires=wires + ) + + for rec in [rec_rect, rec_tria]: + assert len(rec.queue) == 4 + + assert isinstance(rec.queue[0], qml.Rotation) + assert rec.queue[0].parameters == phi + + assert isinstance(rec.queue[1], qml.Beamsplitter) + assert rec.queue[1].parameters == [theta[0], 0] + + assert isinstance(rec.queue[2], qml.Rotation) + assert rec.queue[2].parameters == [varphi[0]] + + assert isinstance(rec.queue[3], qml.Rotation) + assert rec.queue[3].parameters == [varphi[1]] + + def test_one_mode(self, tol): + """Test that a one mode interferometer correctly gives a rotation gate""" + varphi = [0.42342] + + with qml.tape.OperationRecorder() as rec: + Interferometer(theta=[], phi=[], varphi=varphi, wires=0) + + assert len(rec.queue) == 1 + assert isinstance(rec.queue[0], qml.Rotation) + assert np.allclose(rec.queue[0].parameters, varphi, atol=tol) + + def test_two_mode_rect(self, tol): + """Test that a two mode interferometer using the rectangular mesh + correctly gives a beamsplitter+rotation gate""" + N = 2 + wires = range(N) + + theta = [0.321] + phi = [0.234] + varphi = [0.42342, 0.1121] + + with qml.tape.OperationRecorder() as rec: + Interferometer(theta, phi, varphi, wires=wires) + + isinstance(rec.queue[0], qml.Beamsplitter) + assert rec.queue[0].parameters == theta + phi + + assert isinstance(rec.queue[1], qml.Rotation) + assert rec.queue[1].parameters == [varphi[0]] + + assert isinstance(rec.queue[2], qml.Rotation) + assert rec.queue[2].parameters == [varphi[1]] + + def test_two_mode_triangular(self, tol): + """Test that a two mode interferometer using the triangular mesh + correctly gives a beamsplitter+rotation gate""" + N = 2 + wires = range(N) + + theta = [0.321] + phi = [0.234] + varphi = [0.42342, 0.1121] + + with qml.tape.OperationRecorder() as rec: + Interferometer(theta, phi, varphi, mesh="triangular", wires=wires) + + assert len(rec.queue) == 3 + + assert isinstance(rec.queue[0], qml.Beamsplitter) + assert rec.queue[0].parameters == theta + phi + + assert isinstance(rec.queue[1], qml.Rotation) + assert rec.queue[1].parameters == [varphi[0]] + + assert isinstance(rec.queue[2], qml.Rotation) + assert rec.queue[2].parameters == [varphi[1]] + + def test_three_mode(self, tol): + """Test that a three mode interferometer using either mesh gives the correct gates""" + N = 3 + wires = range(N) + + theta = [0.321, 0.4523, 0.21321] + phi = [0.234, 0.324, 0.234] + varphi = [0.42342, 0.234, 0.1121] + + with qml.tape.OperationRecorder() as rec_rect: + Interferometer(theta, phi, varphi, wires=wires) + + with qml.tape.OperationRecorder() as rec_tria: + Interferometer(theta, phi, varphi, wires=wires) + + for rec in [rec_rect, rec_tria]: + # test both meshes (both give identical results for the 3 mode case). + assert len(rec.queue) == 6 + + expected_bs_wires = [[0, 1], [1, 2], [0, 1]] + + for idx, op in enumerate(rec_rect.queue[:3]): + assert isinstance(op, qml.Beamsplitter) + assert op.parameters == [theta[idx], phi[idx]] + assert op.wires == Wires(expected_bs_wires[idx]) + + for idx, op in enumerate(rec.queue[3:]): + assert isinstance(op, qml.Rotation) + assert op.parameters == [varphi[idx]] + assert op.wires == Wires([idx]) + + def test_four_mode_rect(self, tol): + """Test that a 4 mode interferometer using rectangular mesh gives the correct gates""" + N = 4 + wires = range(N) + + theta = [0.321, 0.4523, 0.21321, 0.123, 0.5234, 1.23] + phi = [0.234, 0.324, 0.234, 1.453, 1.42341, -0.534] + varphi = [0.42342, 0.234, 0.4523, 0.1121] + + with qml.tape.OperationRecorder() as rec: + Interferometer(theta, phi, varphi, wires=wires) + + assert len(rec.queue) == 10 + + expected_bs_wires = [[0, 1], [2, 3], [1, 2], [0, 1], [2, 3], [1, 2]] + + for idx, op in enumerate(rec.queue[:6]): + assert isinstance(op, qml.Beamsplitter) + assert op.parameters == [theta[idx], phi[idx]] + assert op.wires == Wires(expected_bs_wires[idx]) + + for idx, op in enumerate(rec.queue[6:]): + assert isinstance(op, qml.Rotation) + assert op.parameters == [varphi[idx]] + assert op.wires == Wires([idx]) + + def test_four_mode_triangular(self, tol): + """Test that a 4 mode interferometer using triangular mesh gives the correct gates""" + N = 4 + wires = range(N) + + theta = [0.321, 0.4523, 0.21321, 0.123, 0.5234, 1.23] + phi = [0.234, 0.324, 0.234, 1.453, 1.42341, -0.534] + varphi = [0.42342, 0.234, 0.4523, 0.1121] + + with qml.tape.OperationRecorder() as rec: + Interferometer(theta, phi, varphi, mesh="triangular", wires=wires) + + assert len(rec.queue) == 10 + + expected_bs_wires = [[2, 3], [1, 2], [0, 1], [2, 3], [1, 2], [2, 3]] + + for idx, op in enumerate(rec.queue[:6]): + assert isinstance(op, qml.Beamsplitter) + assert op.parameters == [theta[idx], phi[idx]] + assert op.wires == Wires(expected_bs_wires[idx]) + + for idx, op in enumerate(rec.queue[6:]): + assert isinstance(op, qml.Rotation) + assert op.parameters == [varphi[idx]] + assert op.wires == Wires([idx]) + + def test_integration(self, tol): + """test integration with PennyLane and gradient calculations""" + N = 4 + wires = range(N) + dev = qml.device("default.gaussian", wires=N) + + sq = np.array( + [ + [0.8734294, 0.96854066], + [0.86919454, 0.53085569], + [0.23272833, 0.0113988], + [0.43046882, 0.40235136], + ] + ) + + theta = np.array([3.28406182, 3.0058243, 3.48940764, 3.41419504, 4.7808479, 4.47598146]) + phi = np.array([3.89357744, 2.67721355, 1.81631197, 6.11891294, 2.09716418, 1.37476761]) + varphi = np.array([0.4134863, 6.17555778, 0.80334114, 2.02400747]) + + @qml.qnode(dev) + def circuit(theta, phi, varphi): + for w in wires: + qml.Squeezing(sq[w][0], sq[w][1], wires=w) + + Interferometer(theta=theta, phi=phi, varphi=varphi, wires=wires) + return [qml.expval(qml.NumberOperator(w)) for w in wires] + + res = circuit(theta, phi, varphi) + expected = np.array([0.96852694, 0.23878521, 0.82310606, 0.16547786]) + assert np.allclose(res, expected, atol=tol) + + def test_interferometer_wrong_dim(self): + """Integration test for the CVNeuralNetLayers method.""" + dev = qml.device("default.gaussian", wires=4) + + @qml.qnode(dev) + def circuit(theta, phi, varphi): + Interferometer(theta=theta, phi=phi, varphi=varphi, wires=range(4)) + return qml.expval(qml.X(0)) + + theta = np.array([3.28406182, 3.0058243, 3.48940764, 3.41419504, 4.7808479, 4.47598146]) + phi = np.array([3.89357744, 2.67721355, 1.81631197, 6.11891294, 2.09716418, 1.37476761]) + varphi = np.array([0.4134863, 6.17555778, 0.80334114, 2.02400747]) + + with pytest.raises(ValueError, match=r"Theta must be of shape \(6,\)"): + wrong_theta = np.array([0.1, 0.2]) + circuit(wrong_theta, phi, varphi) + + with pytest.raises(ValueError, match=r"Phi must be of shape \(6,\)"): + wrong_phi = np.array([0.1, 0.2]) + circuit(theta, wrong_phi, varphi) + + with pytest.raises(ValueError, match=r"Varphi must be of shape \(4,\)"): + wrong_varphi = np.array([0.1, 0.2]) + circuit(theta, phi, wrong_varphi) class TestSingleExcitationUnitary: From 292f8fea0b8594126b7f6cbe8f8f873ac7ceb2f8 Mon Sep 17 00:00:00 2001 From: Maria Schuld Date: Tue, 6 Apr 2021 11:49:36 +0200 Subject: [PATCH 12/16] improve codecov --- tests/templates/test_layers/test_particle_conserving_u1.py | 6 ++++++ tests/templates/test_layers/test_particle_conserving_u2.py | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/tests/templates/test_layers/test_particle_conserving_u1.py b/tests/templates/test_layers/test_particle_conserving_u1.py index 4d8c9325e12..51ef48d3180 100644 --- a/tests/templates/test_layers/test_particle_conserving_u1.py +++ b/tests/templates/test_layers/test_particle_conserving_u1.py @@ -262,6 +262,12 @@ def test_shape(self, n_layers, n_wires, expected_shape): shape = qml.templates.ParticleConservingU1.shape(n_layers, n_wires) assert shape == expected_shape + def test_shape_exception_not_enough_qubits(self): + """Test that the shape function warns if there are not enough qubits.""" + + with pytest.raises(ValueError, match="The number of qubits must be greater than one"): + qml.templates.ParticleConservingU1.shape(3, 1) + def circuit_template(weights): qml.templates.ParticleConservingU1(weights, range(2), init_state=np.array([1, 1])) diff --git a/tests/templates/test_layers/test_particle_conserving_u2.py b/tests/templates/test_layers/test_particle_conserving_u2.py index 7e6a70d078a..ab71995fd3c 100644 --- a/tests/templates/test_layers/test_particle_conserving_u2.py +++ b/tests/templates/test_layers/test_particle_conserving_u2.py @@ -214,6 +214,12 @@ def test_shape(self, n_layers, n_wires, expected_shape): shape = qml.templates.ParticleConservingU2.shape(n_layers, n_wires) assert shape == expected_shape + def test_shape_exception_not_enough_qubits(self): + """Test that the shape function warns if there are not enough qubits.""" + + with pytest.raises(ValueError, match="The number of qubits must be greater than one"): + qml.templates.ParticleConservingU2.shape(3, 1) + def circuit_template(weights): qml.templates.ParticleConservingU2(weights, range(2), init_state=np.array([1, 1])) From 2e5aaf0a9958af34b13270c5b065de89600eb48e Mon Sep 17 00:00:00 2001 From: Maria Schuld Date: Thu, 8 Apr 2021 07:05:17 +0200 Subject: [PATCH 13/16] Update tests/templates/test_layers/test_basic_entangler.py Co-authored-by: Chase Roberts --- tests/templates/test_layers/test_basic_entangler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/templates/test_layers/test_basic_entangler.py b/tests/templates/test_layers/test_basic_entangler.py index ee30ceec686..f93deb14d32 100644 --- a/tests/templates/test_layers/test_basic_entangler.py +++ b/tests/templates/test_layers/test_basic_entangler.py @@ -79,7 +79,7 @@ def circuit(weights): return [qml.expval(qml.PauliZ(i)) for i in range(n_wires)] expectations = circuit(weights) - assert np.allclose(expectations, target, atol=tol, rtol=0) + np.testing.assert_allclose(expectations, target, atol=tol, rtol=0) def test_custom_wire_labels(self, tol): """Test that template can deal with non-numeric, nonconsecutive wire labels.""" From 7ae4d574bdea9d700c730b85e12abebd81602f51 Mon Sep 17 00:00:00 2001 From: Maria Schuld Date: Thu, 8 Apr 2021 07:06:10 +0200 Subject: [PATCH 14/16] Update tests/templates/test_layers/test_basic_entangler.py Co-authored-by: Chase Roberts --- tests/templates/test_layers/test_basic_entangler.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/templates/test_layers/test_basic_entangler.py b/tests/templates/test_layers/test_basic_entangler.py index f93deb14d32..d607e18ef37 100644 --- a/tests/templates/test_layers/test_basic_entangler.py +++ b/tests/templates/test_layers/test_basic_entangler.py @@ -174,7 +174,7 @@ def test_list_and_tuples(self, tol): res = circuit(weights) res2 = circuit2(weights) - assert qml.math.allclose(res, res2, atol=tol, rtol=0) + np.testing.assert_allclose(res, res2, atol=tol, rtol=0) weights_tuple = [tuple(weights[0])] res = circuit(weights_tuple) From 22358f163105571443ddc0a7995c6b12bca72c8a Mon Sep 17 00:00:00 2001 From: Maria Schuld Date: Thu, 8 Apr 2021 07:18:00 +0200 Subject: [PATCH 15/16] implement review comments --- .../test_layers/test_basic_entangler.py | 12 +- .../test_layers/test_cv_neural_net.py | 293 +++++++++--------- .../test_particle_conserving_u1.py | 12 +- .../test_particle_conserving_u2.py | 12 +- tests/templates/test_layers/test_random.py | 12 +- .../test_layers/test_simplified_twodesign.py | 12 +- .../test_layers/test_strongly_entangling.py | 12 +- 7 files changed, 187 insertions(+), 178 deletions(-) diff --git a/tests/templates/test_layers/test_basic_entangler.py b/tests/templates/test_layers/test_basic_entangler.py index d607e18ef37..57f71917760 100644 --- a/tests/templates/test_layers/test_basic_entangler.py +++ b/tests/templates/test_layers/test_basic_entangler.py @@ -204,10 +204,10 @@ def test_autograd(self, tol): assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) - def test_jax(self, tol, skip_if_no_jax_support): + def test_jax(self, tol): """Tests the jax interface.""" - import jax + jax = pytest.importorskip("jax") import jax.numpy as jnp weights = jnp.array(np.random.random(size=(1, 3))) @@ -229,10 +229,10 @@ def test_jax(self, tol, skip_if_no_jax_support): assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) - def test_tf(self, tol, skip_if_no_tf_support): + def test_tf(self, tol): """Tests the tf interface.""" - import tensorflow as tf + tf = pytest.importorskip("tensorflow") weights = tf.Variable(np.random.random(size=(1, 3))) @@ -255,10 +255,10 @@ def test_tf(self, tol, skip_if_no_tf_support): assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) - def test_torch(self, tol, skip_if_no_torch_support): + def test_torch(self, tol): """Tests the torch interface.""" - import torch + torch = pytest.importorskip("torch") weights = torch.tensor(np.random.random(size=(1, 3)), requires_grad=True) diff --git a/tests/templates/test_layers/test_cv_neural_net.py b/tests/templates/test_layers/test_cv_neural_net.py index 99072a7cb71..a6f64357873 100644 --- a/tests/templates/test_layers/test_cv_neural_net.py +++ b/tests/templates/test_layers/test_cv_neural_net.py @@ -166,145 +166,154 @@ def circuit_template(*weights): return qml.expval(qml.X(0)) -# def circuit_decomposed(*weights): -# qml.templates.Interferometer(weights[0][0], weights[1][0], weights[2][0], wires=[0, 1]) -# qml.Squeezing(weights[3][0, 0], weights[4][0, 0], wires=0) -# qml.Squeezing(weights[3][0, 1], weights[4][0, 1], wires=1) -# qml.templates.Interferometer(weights[5][0], weights[6][0], weights[7][0], wires=[0, 1]) -# qml.Displacement(weights[8][0, 0], weights[9][0, 0], wires=0) -# qml.Displacement(weights[8][0, 1], weights[9][0, 1], wires=1) -# qml.Kerr(weights[10][0, 0], wires=0) -# qml.Kerr(weights[10][0, 1], wires=1) -# return qml.expval(qml.X(0)) -# -# -# class TestInterfaces: -# """Tests that the template is compatible with all interfaces, including the computation -# of gradients.""" -# -# def test_list_and_tuples(self, tol): -# """Tests common iterables as inputs.""" -# -# shapes = expected_shapes(1, 2) -# weights = [np.random.random(shape) for shape in shapes] -# -# dev = DummyDevice(wires=2) -# -# circuit = qml.QNode(circuit_template, dev) -# circuit2 = qml.QNode(circuit_decomposed, dev) -# -# res = circuit(*weights) -# res2 = circuit2(*weights) -# assert qml.math.allclose(res, res2, atol=tol, rtol=0) -# -# weights_tuple = tuple(w for w in weights) -# res = circuit(*weights_tuple) -# res2 = circuit2(*weights_tuple) -# assert qml.math.allclose(res, res2, atol=tol, rtol=0) -# -# def test_autograd(self, tol): -# """Tests the autograd interface.""" -# -# shapes = expected_shapes(1, 2) -# weights = [np.random.random(shape) for shape in shapes] -# weights = [pnp.array(w, requires_grad=True) for w in weights] -# -# dev = DummyDevice(wires=2) -# -# circuit = qml.QNode(circuit_template, dev) -# circuit2 = qml.QNode(circuit_decomposed, dev) -# -# res = circuit(*weights) -# res2 = circuit2(*weights) -# assert qml.math.allclose(res, res2, atol=tol, rtol=0) -# -# grad_fn = qml.grad(circuit) -# grads = grad_fn(*weights) -# -# grad_fn2 = qml.grad(circuit2) -# grads2 = grad_fn2(*weights) -# -# assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) -# -# def test_jax(self, tol, skip_if_no_jax_support): -# """Tests the jax interface.""" -# -# import jax -# import jax.numpy as jnp -# -# shapes = expected_shapes(1, 2) -# weights = [np.random.random(shape) for shape in shapes] -# weights = [jnp.array(w) for w in weights] -# -# dev = DummyDevice(wires=2) -# -# circuit = qml.QNode(circuit_template, dev, interface="jax") -# circuit2 = qml.QNode(circuit_decomposed, dev, interface="jax") -# -# res = circuit(*weights) -# res2 = circuit2(*weights) -# assert qml.math.allclose(res, res2, atol=tol, rtol=0) -# -# grad_fn = jax.grad(circuit) -# grads = grad_fn(*weights) -# -# grad_fn2 = jax.grad(circuit2) -# grads2 = grad_fn2(*weights) -# -# assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) -# -# def test_tf(self, tol, skip_if_no_tf_support): -# """Tests the tf interface.""" -# -# import tensorflow as tf -# -# shapes = expected_shapes(1, 2) -# weights = [np.random.random(shape) for shape in shapes] -# weights = [tf.Variable(w) for w in weights] -# -# dev = DummyDevice(wires=2) -# -# circuit = qml.QNode(circuit_template, dev, interface="tf") -# circuit2 = qml.QNode(circuit_decomposed, dev, interface="tf") -# -# res = circuit(*weights) -# res2 = circuit2(*weights) -# assert qml.math.allclose(res, res2, atol=tol, rtol=0) -# -# with tf.GradientTape() as tape: -# res = circuit(*weights) -# grads = tape.gradient(res, [*weights]) -# -# with tf.GradientTape() as tape2: -# res2 = circuit2(*weights) -# grads2 = tape2.gradient(res2, [*weights]) -# -# assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) -# -# def test_torch(self, tol, skip_if_no_torch_support): -# """Tests the torch interface.""" -# -# import torch -# -# shapes = expected_shapes(1, 2) -# weights = [np.random.random(size=shape) for shape in shapes] -# weights = [torch.tensor(w, requires_grad=True) for w in weights] -# -# dev = DummyDevice(wires=2) -# -# circuit = qml.QNode(circuit_template, dev, interface="torch") -# circuit2 = qml.QNode(circuit_decomposed, dev, interface="torch") -# -# res = circuit(*weights) -# res2 = circuit2(*weights) -# assert qml.math.allclose(res, res2, atol=tol, rtol=0) -# -# res = circuit(*weights) -# res.backward() -# grads = [w.grad for w in weights] -# -# res2 = circuit2(*weights) -# res2.backward() -# grads2 = [w.grad for w in weights] -# -# assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) +def circuit_decomposed(*weights): + # Interferometer (replace with operation once this template is refactored) + qml.Beamsplitter(weights[0][0, 0], weights[1][0, 0], wires=[0, 1]) + qml.Rotation(weights[2][0, 0], wires=0) + qml.Rotation(weights[2][0, 1], wires=1) + + qml.Squeezing(weights[3][0, 0], weights[4][0, 0], wires=0) + qml.Squeezing(weights[3][0, 1], weights[4][0, 1], wires=1) + + # Interferometer + qml.Beamsplitter(weights[5][0, 0], weights[6][0, 0], wires=[0, 1]) + qml.Rotation(weights[7][0, 0], wires=0) + qml.Rotation(weights[7][0, 1], wires=1) + + qml.Displacement(weights[8][0, 0], weights[9][0, 0], wires=0) + qml.Displacement(weights[8][0, 1], weights[9][0, 1], wires=1) + qml.Kerr(weights[10][0, 0], wires=0) + qml.Kerr(weights[10][0, 1], wires=1) + return qml.expval(qml.X(0)) + + +class TestInterfaces: + """Tests that the template is compatible with all interfaces, including the computation + of gradients.""" + + def test_list_and_tuples(self, tol): + """Tests common iterables as inputs.""" + + shapes = expected_shapes(1, 2) + weights = [np.random.random(shape) for shape in shapes] + + dev = DummyDevice(wires=2) + + circuit = qml.QNode(circuit_template, dev) + circuit2 = qml.QNode(circuit_decomposed, dev) + + res = circuit(*weights) + res2 = circuit2(*weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + weights_tuple = tuple(w for w in weights) + res = circuit(*weights_tuple) + res2 = circuit2(*weights_tuple) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + def test_autograd(self, tol): + """Tests the autograd interface.""" + + shapes = expected_shapes(1, 2) + weights = [np.random.random(shape) for shape in shapes] + weights = [pnp.array(w, requires_grad=True) for w in weights] + + dev = DummyDevice(wires=2) + + circuit = qml.QNode(circuit_template, dev) + circuit2 = qml.QNode(circuit_decomposed, dev) + + res = circuit(*weights) + res2 = circuit2(*weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + grad_fn = qml.grad(circuit) + grads = grad_fn(*weights) + + grad_fn2 = qml.grad(circuit2) + grads2 = grad_fn2(*weights) + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + + def test_jax(self, tol): + """Tests the jax interface.""" + + jax = pytest.importorskip("jax") + import jax.numpy as jnp + + shapes = expected_shapes(1, 2) + weights = [np.random.random(shape) for shape in shapes] + weights = [jnp.array(w) for w in weights] + + dev = DummyDevice(wires=2) + + circuit = qml.QNode(circuit_template, dev, interface="jax") + circuit2 = qml.QNode(circuit_decomposed, dev, interface="jax") + + res = circuit(*weights) + res2 = circuit2(*weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + grad_fn = jax.grad(circuit) + grads = grad_fn(*weights) + + grad_fn2 = jax.grad(circuit2) + grads2 = grad_fn2(*weights) + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + + def test_tf(self, tol): + """Tests the tf interface.""" + + tf = pytest.importorskip("tensorflow") + + shapes = expected_shapes(1, 2) + weights = [np.random.random(shape) for shape in shapes] + weights = [tf.Variable(w) for w in weights] + + dev = DummyDevice(wires=2) + + circuit = qml.QNode(circuit_template, dev, interface="tf") + circuit2 = qml.QNode(circuit_decomposed, dev, interface="tf") + + res = circuit(*weights) + res2 = circuit2(*weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + with tf.GradientTape() as tape: + res = circuit(*weights) + grads = tape.gradient(res, [*weights]) + + with tf.GradientTape() as tape2: + res2 = circuit2(*weights) + grads2 = tape2.gradient(res2, [*weights]) + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) + + def test_torch(self, tol): + """Tests the torch interface.""" + + torch = pytest.importorskip("torch") + + shapes = expected_shapes(1, 2) + weights = [np.random.random(size=shape) for shape in shapes] + weights = [torch.tensor(w, requires_grad=True) for w in weights] + + dev = DummyDevice(wires=2) + + circuit = qml.QNode(circuit_template, dev, interface="torch") + circuit2 = qml.QNode(circuit_decomposed, dev, interface="torch") + + res = circuit(*weights) + res2 = circuit2(*weights) + assert qml.math.allclose(res, res2, atol=tol, rtol=0) + + res = circuit(*weights) + res.backward() + grads = [w.grad for w in weights] + + res2 = circuit2(*weights) + res2.backward() + grads2 = [w.grad for w in weights] + + assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) diff --git a/tests/templates/test_layers/test_particle_conserving_u1.py b/tests/templates/test_layers/test_particle_conserving_u1.py index 51ef48d3180..a7d64976dcf 100644 --- a/tests/templates/test_layers/test_particle_conserving_u1.py +++ b/tests/templates/test_layers/test_particle_conserving_u1.py @@ -342,10 +342,10 @@ def test_autograd(self, tol): assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) - def test_jax(self, tol, skip_if_no_jax_support): + def test_jax(self, tol): """Tests the jax interface.""" - import jax + jax = pytest.importorskip("jax") import jax.numpy as jnp weights = jnp.array(np.random.random(size=(1, 1, 2))) @@ -367,10 +367,10 @@ def test_jax(self, tol, skip_if_no_jax_support): assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) - def test_tf(self, tol, skip_if_no_tf_support): + def test_tf(self, tol): """Tests the tf interface.""" - import tensorflow as tf + tf = pytest.importorskip("tensorflow") weights = tf.Variable(np.random.random(size=(1, 1, 2))) @@ -393,10 +393,10 @@ def test_tf(self, tol, skip_if_no_tf_support): assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) - def test_torch(self, tol, skip_if_no_torch_support): + def test_torch(self, tol): """Tests the torch interface.""" - import torch + torch = pytest.importorskip("torch") weights = torch.tensor(np.random.random(size=(1, 1, 2)), requires_grad=True) diff --git a/tests/templates/test_layers/test_particle_conserving_u2.py b/tests/templates/test_layers/test_particle_conserving_u2.py index ab71995fd3c..f741956a521 100644 --- a/tests/templates/test_layers/test_particle_conserving_u2.py +++ b/tests/templates/test_layers/test_particle_conserving_u2.py @@ -282,10 +282,10 @@ def test_autograd(self, tol): assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) - def test_jax(self, tol, skip_if_no_jax_support): + def test_jax(self, tol): """Tests the jax interface.""" - import jax + jax = pytest.importorskip("jax") import jax.numpy as jnp weights = jnp.array(np.random.random(size=(1, 3))) @@ -307,10 +307,10 @@ def test_jax(self, tol, skip_if_no_jax_support): assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) - def test_tf(self, tol, skip_if_no_tf_support): + def test_tf(self, tol): """Tests the tf interface.""" - import tensorflow as tf + tf = pytest.importorskip("tensorflow") weights = tf.Variable(np.random.random(size=(1, 3))) @@ -333,10 +333,10 @@ def test_tf(self, tol, skip_if_no_tf_support): assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) - def test_torch(self, tol, skip_if_no_torch_support): + def test_torch(self, tol): """Tests the torch interface.""" - import torch + torch = pytest.importorskip("torch") weights = torch.tensor(np.random.random(size=(1, 3)), requires_grad=True) diff --git a/tests/templates/test_layers/test_random.py b/tests/templates/test_layers/test_random.py index 534472cc4a1..01b3f9a69e7 100644 --- a/tests/templates/test_layers/test_random.py +++ b/tests/templates/test_layers/test_random.py @@ -198,10 +198,10 @@ def test_autograd(self, tol): assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) - def test_jax(self, tol, skip_if_no_jax_support): + def test_jax(self, tol): """Tests the jax interface.""" - import jax + jax = pytest.importorskip("jax") import jax.numpy as jnp weights = jnp.array(np.random.random(size=(1, 3))) @@ -223,10 +223,10 @@ def test_jax(self, tol, skip_if_no_jax_support): assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) - def test_tf(self, tol, skip_if_no_tf_support): + def test_tf(self, tol): """Tests the tf interface.""" - import tensorflow as tf + tf = pytest.importorskip("tensorflow") weights = tf.Variable(np.random.random(size=(1, 3))) @@ -249,10 +249,10 @@ def test_tf(self, tol, skip_if_no_tf_support): assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) - def test_torch(self, tol, skip_if_no_torch_support): + def test_torch(self, tol): """Tests the torch interface.""" - import torch + torch = pytest.importorskip("torch") weights = torch.tensor(np.random.random(size=(1, 3)), requires_grad=True) diff --git a/tests/templates/test_layers/test_simplified_twodesign.py b/tests/templates/test_layers/test_simplified_twodesign.py index 238926dce09..91cd5af434a 100644 --- a/tests/templates/test_layers/test_simplified_twodesign.py +++ b/tests/templates/test_layers/test_simplified_twodesign.py @@ -252,10 +252,10 @@ def test_autograd(self, tol): assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) assert np.allclose(grads[1], grads2[1], atol=tol, rtol=0) - def test_jax(self, tol, skip_if_no_jax_support): + def test_jax(self, tol): """Tests the jax interface.""" - import jax + jax = pytest.importorskip("jax") import jax.numpy as jnp weights = jnp.array(np.random.random(size=(1, 2, 2))) @@ -279,10 +279,10 @@ def test_jax(self, tol, skip_if_no_jax_support): assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) assert np.allclose(grads[1], grads2[1], atol=tol, rtol=0) - def test_tf(self, tol, skip_if_no_tf_support): + def test_tf(self, tol): """Tests the tf interface.""" - import tensorflow as tf + tf = pytest.importorskip("tensorflow") weights = tf.Variable(np.random.random(size=(1, 2, 2))) initial_weights = tf.Variable(np.random.random(size=(3,))) @@ -307,10 +307,10 @@ def test_tf(self, tol, skip_if_no_tf_support): assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) assert np.allclose(grads[1], grads2[1], atol=tol, rtol=0) - def test_torch(self, tol, skip_if_no_torch_support): + def test_torch(self, tol): """Tests the torch interface.""" - import torch + torch = pytest.importorskip("torch") weights = torch.tensor(np.random.random(size=(1, 2, 2)), requires_grad=True) initial_weights = torch.tensor(np.random.random(size=(3,)), requires_grad=True) diff --git a/tests/templates/test_layers/test_strongly_entangling.py b/tests/templates/test_layers/test_strongly_entangling.py index 691a58cd15d..826047671fa 100644 --- a/tests/templates/test_layers/test_strongly_entangling.py +++ b/tests/templates/test_layers/test_strongly_entangling.py @@ -202,10 +202,10 @@ def test_autograd(self, tol): assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) - def test_jax(self, tol, skip_if_no_jax_support): + def test_jax(self, tol): """Tests the jax interface.""" - import jax + jax = pytest.importorskip("jax") import jax.numpy as jnp weights = jnp.array(np.random.random(size=(1, 3, 3))) @@ -227,10 +227,10 @@ def test_jax(self, tol, skip_if_no_jax_support): assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) - def test_tf(self, tol, skip_if_no_tf_support): + def test_tf(self, tol): """Tests the tf interface.""" - import tensorflow as tf + tf = pytest.importorskip("tensorflow") weights = tf.Variable(np.random.random(size=(1, 3, 3))) @@ -253,10 +253,10 @@ def test_tf(self, tol, skip_if_no_tf_support): assert np.allclose(grads[0], grads2[0], atol=tol, rtol=0) - def test_torch(self, tol, skip_if_no_torch_support): + def test_torch(self, tol): """Tests the torch interface.""" - import torch + torch = pytest.importorskip("torch") weights = torch.tensor(np.random.random(size=(1, 3, 3)), requires_grad=True) From 23caff8fdf5f2611bb0ec1f4909fb4ca7db6a02f Mon Sep 17 00:00:00 2001 From: Maria Schuld Date: Thu, 8 Apr 2021 09:35:26 +0200 Subject: [PATCH 16/16] delete old integration tests --- tests/templates/test_integration.py | 642 ---------------------------- 1 file changed, 642 deletions(-) delete mode 100644 tests/templates/test_integration.py diff --git a/tests/templates/test_integration.py b/tests/templates/test_integration.py deleted file mode 100644 index 72080f7720c..00000000000 --- a/tests/templates/test_integration.py +++ /dev/null @@ -1,642 +0,0 @@ -# Copyright 2018-2020 Xanadu Quantum Technologies Inc. - -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at - -# http://www.apache.org/licenses/LICENSE-2.0 - -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -""" -Integration tests for templates. - -New **templates** are added as follows: - -* extend the fixtures ``QUBIT_DIFFABLE_NONDIFFABLE`` or ``CV_DIFFABLE_NONDIFFABLE`` - by the new template -* extend the fixtures ``QUBIT_INIT`` or ``CV_INIT`` if you want to test integration with initialization - functions from the ``pennylane.init`` module. - -Note that a template may need to be manually excluded from a test, -as shown for the templates listed in NO_OPS_BEFORE, which do not allow for -operations to be executed before the template is called. - -Templates are tested with a range of interfaces. To test templates with an additional interface: - -* Try to import the interface and add its variable creation function to INTERFACES -* Extend the fixture ``interfaces`` -* Add the interface gradient computation to the TestGradientIntegration tests -""" -# pylint: disable=protected-access,cell-var-from-loop -import pytest -import pennylane as qml -from pennylane import numpy as np - -####################################### -# Interfaces and their methods to create differentiable tensors - -INTERFACES = [('autograd', lambda x: np.array(x, requires_grad=True))] - -try: - import torch - INTERFACES.append(('torch', lambda x: torch.tensor(x, requires_grad=True))) -except ImportError as e: - pass - -try: - import tensorflow as tf - - if tf.__version__[0] == "1": - tf.enable_eager_execution() - - from tensorflow import Variable as TFVariable - INTERFACES.append(('tf', lambda x: TFVariable(x, dtype=tf.float64))) - -except ImportError as e: - pass - -######################################### -# Fixtures for integration tests -######################################### - -# Each entry to QUBIT_DIFFABLE_NONDIFFABLE or CV_DIFFABLE_NONDIFFABLE -# adds a template with specified inputs to the integration tests -# ``TestIntegrationQnode``, ``TestIntegrationOtherOps``, ``TestIntegrationGradient`` - -# The entries have the following form: -# (template, dict of differentiable arguments, dict of non-differentiable arguments, n_wires) - -# "Differentiable arguments" to a template are those that in principle allow a user to compute gradients for, -# while "nondifferentiable arguments" must always be passed as auxiliary (keyword) arguments to a qnode. -# n_wires is the number of wires the device needs. - -# Note that the template is called in all tests using 2 wires - -QUBIT_DIFFABLE_NONDIFFABLE = [ - (qml.templates.MottonenStatePreparation, - {'state_vector': np.array([1 / 2, 1 / 2, 1 / 2, 1 / 2])}, - {'wires': [0, 1]}, - 2), - (qml.templates.BasisStatePreparation, - {}, - {'wires': [0, 1], 'basis_state': np.array([1, 0])}, - 2), - - (qml.templates.broadcast, - {'parameters': [[1.], [1.]]}, - {'wires': [0, 1], 'unitary': qml.RX, 'pattern': 'single'}, - 2), - - (qml.templates.SingleExcitationUnitary, - {'weight': 0.56}, - {'wires': [0, 1]}, - 2), - (qml.templates.DoubleExcitationUnitary, - {'weight': 0.56}, - {'wires1': [0, 1], - 'wires2': [2, 3]}, - 4), - (qml.templates.UCCSD, - {'weights': [3.90575761, -1.89772083, -1.36689032]}, - {'wires': [0, 1, 2, 3], 's_wires': [[0, 1, 2], [1, 2, 3]], - 'd_wires': [[[0, 1], [2, 3]]], 'init_state':np.array([1, 1, 0, 0], requires_grad=False)}, - 4), - - (qml.templates.QuantumPhaseEstimation, - {}, - { - "unitary": np.array([[0, 1], [1, 0]]), - "target_wires": [0], - "estimation_wires": [1, 2], - }, - 3), - ] - -CV_DIFFABLE_NONDIFFABLE = [] - -# List templates in NO_OP_BEFORE that do not allow for operations -# before they are called in a quantum function. -# These templates will be skipped in tests of that nature. - -NO_OP_BEFORE = ["AmplitudeEmbedding", "UCCSD", "ParticleConservingU2", "ParticleConservingU1"] - -# Each entry to QUBIT_INIT and CV_INIT adds a template with specified inputs to the -# integration tests ``TestIntegrationInitFunctions`` - -# The entries have the following form: -# -# (template, dict of arguments) -# -# The dictionary of arguments calls the initialization function, and contains all other arguments that need to -# be defined in the template. - -QUBIT_INIT = [(qml.templates.StronglyEntanglingLayers, - {'weights': qml.init.strong_ent_layers_uniform(n_layers=3, n_wires=2), 'wires': range(2)}), - (qml.templates.StronglyEntanglingLayers, - {'weights': qml.init.strong_ent_layers_uniform(n_layers=2, n_wires=3), 'wires': range(3)}), - (qml.templates.StronglyEntanglingLayers, - {'weights': qml.init.strong_ent_layers_normal(n_layers=3, n_wires=2), 'wires': range(2)}), - (qml.templates.StronglyEntanglingLayers, - {'weights': qml.init.strong_ent_layers_normal(n_layers=2, n_wires=3), 'wires': range(3)}), - (qml.templates.RandomLayers, - {'weights': qml.init.random_layers_uniform(n_layers=3, n_rots=2, n_wires=1), 'wires': range(1)}), - (qml.templates.RandomLayers, - {'weights': qml.init.random_layers_uniform(n_layers=3, n_rots=2, n_wires=2), 'wires': range(2)}), - (qml.templates.RandomLayers, - {'weights': qml.init.random_layers_normal(n_layers=2, n_rots=2, n_wires=1), 'wires': range(1)}), - (qml.templates.RandomLayers, - {'weights': qml.init.random_layers_normal(n_layers=2, n_rots=2, n_wires=2), 'wires': range(2)}), - (qml.templates.QAOAEmbedding, - {'features': [1.], 'weights': qml.init.qaoa_embedding_uniform(n_layers=3, n_wires=1), - 'wires': range(1)}), - (qml.templates.QAOAEmbedding, - {'features': [1., 2.], 'weights': qml.init.qaoa_embedding_uniform(n_layers=3, n_wires=2), - 'wires': range(2)}), - (qml.templates.QAOAEmbedding, - {'features': [1., 2., 3.], 'weights': qml.init.qaoa_embedding_uniform(n_layers=2, n_wires=3), - 'wires': range(3)}), - (qml.templates.QAOAEmbedding, - {'features': [1.], 'weights': qml.init.qaoa_embedding_normal(n_layers=3, n_wires=1), - 'wires': range(1)}), - (qml.templates.QAOAEmbedding, - {'features': [1., 2.], 'weights': qml.init.qaoa_embedding_normal(n_layers=3, n_wires=2), - 'wires': range(2)}), - (qml.templates.QAOAEmbedding, - {'features': [1., 2., 3.], 'weights': qml.init.qaoa_embedding_normal(n_layers=3, n_wires=3), - 'wires': range(3)}), - (qml.templates.SimplifiedTwoDesign, - {'initial_layer_weights': qml.init.simplified_two_design_initial_layer_uniform(n_wires=4), - 'weights': qml.init.simplified_two_design_weights_uniform(n_layers=3, n_wires=4), - 'wires': range(4)}), - (qml.templates.SimplifiedTwoDesign, - {'initial_layer_weights': qml.init.simplified_two_design_initial_layer_normal(n_wires=4), - 'weights': qml.init.simplified_two_design_weights_normal(n_layers=3, n_wires=4), - 'wires': range(4)}), - (qml.templates.BasicEntanglerLayers, - {'weights': qml.init.basic_entangler_layers_uniform(n_layers=1, n_wires=1), 'wires': range(1)}), - (qml.templates.BasicEntanglerLayers, - {'weights': qml.init.basic_entangler_layers_uniform(n_layers=3, n_wires=1), 'wires': range(1)}), - (qml.templates.BasicEntanglerLayers, - {'weights': qml.init.basic_entangler_layers_uniform(n_layers=3, n_wires=2), 'wires': range(2)}), - (qml.templates.BasicEntanglerLayers, - {'weights': qml.init.basic_entangler_layers_uniform(n_layers=3, n_wires=3), 'wires': range(3)}), - (qml.templates.BasicEntanglerLayers, - {'weights': qml.init.basic_entangler_layers_normal(n_layers=3, n_wires=1), 'wires': range(1)}), - (qml.templates.BasicEntanglerLayers, - {'weights': qml.init.basic_entangler_layers_normal(n_layers=3, n_wires=2), 'wires': range(2)}), - (qml.templates.BasicEntanglerLayers, - {'weights': qml.init.basic_entangler_layers_normal(n_layers=3, n_wires=3), 'wires': range(3)}), - ] - -CV_INIT = [] - - -class TestIntegrationQnode: - """Tests the integration of templates into qnodes when differentiable arguments are passed as - primary or auxiliary arguments to the qnode, using different interfaces. - - "Differentiable arguments" to a template are those that in principle allow a user to compute gradients for, - while "nondifferentiable arguments" must always be passed as auxiliary (keyword) arguments to a qnode. - - The tests are motivated by the fact that the way arguments are passed to the qnode - influences what shape and/or type the argument has inside the qnode, which is where the template calls it. - - All templates should work no matter how the "differentiable arguments" are passed to the qnode. - """ - - @pytest.mark.parametrize("template, diffable, nondiffable, n_wires", QUBIT_DIFFABLE_NONDIFFABLE) - @pytest.mark.parametrize("interface, to_var", INTERFACES) - def test_qubit_qnode_primary_args(self, template, diffable, nondiffable, n_wires, interface, to_var): - """Tests integration of qubit templates with other operations.""" - - # Extract keys and items - keys_diffable = [*diffable] - diffable = list(diffable.values()) - - # Turn into correct format - diffable = [to_var(i) for i in diffable] - - # Generate qnode in which differentiable arguments are passed - # as primary argument - dev = qml.device('default.qubit', wires=n_wires) - - @qml.qnode(dev, interface=interface, diff_method="parameter-shift") - def circuit(*diffable, keys_diffable=None, nondiffable=None): - # Turn diffables back into dictionary - all_args = dict(zip(keys_diffable, diffable)) - - # Merge with nondiffables - all_args.update(nondiffable) - - template(**all_args) - return qml.expval(qml.Identity(0)) - - # Check that execution does not throw error - print(diffable) - circuit(*diffable, keys_diffable=keys_diffable, nondiffable=nondiffable) - - @pytest.mark.parametrize("template, diffable, nondiffable, n_wires", CV_DIFFABLE_NONDIFFABLE) - @pytest.mark.parametrize("interface, to_var", INTERFACES) - def test_cv_qnode_primary_args(self, template, diffable, nondiffable, n_wires, - interface, to_var, gaussian_dummy): - """Tests integration of cv templates passing differentiable arguments as positional arguments to qnode.""" - - # Extract keys and items - keys_diffable = [*diffable] - diffable = list(diffable.values()) - - # Turn into correct format - diffable = [to_var(i) for i in diffable] - - # Generate qnode in which differentiable arguments are passed - # as primary argument - dev = gaussian_dummy(n_wires) - - @qml.qnode(dev, interface=interface) - def circuit(*diffable, keys_diffable=None, nondiffable=None): - # Turn diffables back into dictionary - all_args = dict(zip(keys_diffable, diffable)) - - # Merge with nondiffables - all_args.update(nondiffable) - - template(**all_args) - return qml.expval(qml.Identity(0)) - - # Check that execution does not throw error - circuit(*diffable, keys_diffable=keys_diffable, nondiffable=nondiffable) - - @pytest.mark.parametrize("template, diffable, nondiffable, n_wires", QUBIT_DIFFABLE_NONDIFFABLE) - @pytest.mark.parametrize("interface, to_var", INTERFACES) - def test_qubit_qnode_auxiliary_args(self, template, diffable, nondiffable, n_wires, interface, to_var): - """Tests integration of qubit templates passing differentiable arguments as auxiliary arguments to qnode.""" - - # Change type of differentiable arguments - # Fix: templates should all take arrays AND lists, at the moment this is not the case - diffable = {k: np.array(v) for k, v in diffable.items()} - - # Merge differentiable and non-differentiable arguments - all_args = {**diffable, **nondiffable} - - # Generate qnode - dev = qml.device('default.qubit', wires=n_wires) - - # Generate qnode in which differentiable arguments are passed - # as auxiliary argument - @qml.qnode(dev, interface=interface) - def circuit(all_args=None): - template(**all_args) - return qml.expval(qml.Identity(0)) - - # Check that execution does not throw error - circuit(all_args=all_args) - - @pytest.mark.parametrize("template, diffable, nondiffable, n_wires", CV_DIFFABLE_NONDIFFABLE) - @pytest.mark.parametrize("interface, to_var", INTERFACES) - def test_qubit_cv_auxiliary_args(self, template, diffable, nondiffable, n_wires, - interface, to_var, gaussian_dummy): - """Tests integration of cv templates passing differentiable arguments as auxiliary arguments to qnode.""" - - # Change type of differentiable arguments - # Fix: templates should all take arrays AND lists, at the moment this is not the case - diffable = {k: np.array(v) for k, v in diffable.items()} - - # Merge differentiable and non-differentiable arguments - all_args = {**diffable, **nondiffable} - - # Generate qnode in which differentiable arguments are passed - # as primary argument - dev = gaussian_dummy(n_wires) - - @qml.qnode(dev, interface=interface) - def circuit(all_args=None): - template(**all_args) - return qml.expval(qml.Identity(0)) - - # Check that execution does not throw error - circuit(all_args=all_args) - - -# hand-coded templates for the operation integration test -@qml.template -def QubitTemplate(w): - qml.PauliX(wires=w) - - -@qml.template -def CVTemplate(w): - qml.Displacement(1., 1., wires=w) - - -class TestIntegrationOtherOps: - """Tests the integration of templates into qnodes where the template is called - together with other operations or templates.""" - - @pytest.mark.parametrize("op_before_template", [True, False]) - @pytest.mark.parametrize("template, diffable, nondiffable, n_wires", QUBIT_DIFFABLE_NONDIFFABLE) - def test_qubit_template_followed_by_operations(self, template, diffable, nondiffable, n_wires, op_before_template): - """Tests integration of qubit templates with other operations.""" - - # skip this test if template does not allow for operations before - if template.__name__ in NO_OP_BEFORE and op_before_template: - pytest.skip("Template does not allow operations before - skipping this test.") - - # Change type of differentiable arguments - # Fix: templates should all take arrays AND lists, at the moment this is not the case - diffable = {k: np.array(v) for k, v in diffable.items()} - - # Merge differentiable and non-differentiable arguments - nondiffable = nondiffable.copy() - nondiffable.update(diffable) - - # Generate qnode - dev = qml.device('default.qubit', wires=n_wires) - - @qml.qnode(dev) - def circuit(nondiffable=None): - # Circuit with operations before and - # after the template is called - if op_before_template: - QubitTemplate(w=0) - qml.PauliX(wires=0) - template(**nondiffable) - if not op_before_template: - QubitTemplate(w=0) - qml.PauliX(wires=1) - return [qml.expval(qml.Identity(0)), qml.expval(qml.PauliX(1))] - - # Check that execution does not throw error - circuit(nondiffable=nondiffable) - - @pytest.mark.parametrize("op_before_template", [True, False]) - @pytest.mark.parametrize("template, diffable, nondiffable, n_wires", CV_DIFFABLE_NONDIFFABLE) - def test_cv_template_followed_by_operations(self, template, diffable, nondiffable, n_wires, gaussian_dummy, - op_before_template): - """Tests integration of cv templates passing differentiable arguments as auxiliary arguments to qnode.""" - - # Change type of differentiable arguments - # Fix: templates should all take arrays AND lists, at the moment this is not the case - diffable = {k: np.array(v) for k, v in diffable.items()} - - # Merge differentiable and non-differentiable arguments - nondiffable = nondiffable.copy() - nondiffable.update(diffable) - - # Make qnode - dev = gaussian_dummy(n_wires) - - @qml.qnode(dev) - def circuit(nondiffable=None): - - # Circuit with operations before and - # after the template is called - if op_before_template: - CVTemplate(w=0) - qml.Displacement(1., 1., wires=0) - template(**nondiffable) - if not op_before_template: - CVTemplate(w=0) - qml.Displacement(1., 1., wires=1) - return [qml.expval(qml.Identity(0)), qml.expval(qml.X(1))] - - # Check that execution does not throw error - circuit(nondiffable=nondiffable) - - -class TestIntegrationGradient: - """Tests that gradients of circuits with templates can be computed.""" - - @pytest.mark.parametrize("template, diffable, nondiffable, n_wires", QUBIT_DIFFABLE_NONDIFFABLE) - @pytest.mark.parametrize("interface, to_var", INTERFACES) - def test_integration_qubit_grad(self, template, diffable, nondiffable, n_wires, interface, to_var): - """Tests that gradient calculations of qubit templates execute without error.""" - if template.__name__ in ["AmplitudeEmbedding"]: - pytest.skip("Template cannot be differentiated") - - # Extract keys and items - keys_diffable = [*diffable] - diffable = list(diffable.values()) - - # Turn into correct format - diffable = [to_var(i) for i in diffable] - - # Make qnode - dev = qml.device('default.qubit', wires=n_wires) - - @qml.qnode(dev, interface=interface) - def circuit(*diffable): - - # Turn diffables back into dictionaries - dict = {key: item for key, item in zip(keys_diffable, diffable)} - - # Merge diffables and nondiffables - dict.update(nondiffable) - - # Circuit - template(**dict) - return qml.expval(qml.Identity(0)) - - # Do gradient check for every differentiable argument - for argnum in range(len(diffable)): - - # Check gradients in numpy interface - if interface == 'autograd': - grd = qml.grad(circuit, argnum=[argnum]) - grd(*diffable) - - # Check gradients in torch interface - if interface == 'torch': - for i in range(len(diffable)): - if i != argnum: - diffable[i].requires_grad = False - diffable[argnum].requires_grad = True - - res = circuit(*diffable) - res.backward() - diffable[argnum].grad.numpy() - - # Check gradients in tf interface - if interface == 'tf': - with tf.GradientTape() as tape: - loss = circuit(*diffable) - tape.gradient(loss, diffable[argnum]) - - @pytest.mark.parametrize("template, diffable, nondiffable, n_wires", CV_DIFFABLE_NONDIFFABLE) - @pytest.mark.parametrize("interface, to_var", INTERFACES) - def test_integration_cv_grad(self, template, diffable, nondiffable, n_wires, interface, to_var, gaussian_dummy): - """Tests that gradient calculations of cv templates execute without error.""" - - # Extract keys and items - keys_diffable = [*diffable] - diffable = list(diffable.values()) - - # Turn into correct format - diffable = [to_var(i) for i in diffable] - - # Make qnode - dev = gaussian_dummy(n_wires) - - @qml.qnode(dev, interface=interface) - def circuit(*diffable): - - # Turn diffables back into dictionaries - dict = {key: item for key, item in zip(keys_diffable, diffable)} - - # Merge diffables and nondiffables - dict.update(nondiffable) - - # Circuit - template(**dict) - return qml.expval(qml.Identity(0)) - - # Do gradient check for every differentiable argument - for argnum in range(len(diffable)): - - # Check gradients in numpy interface - if interface == 'autograd': - grd = qml.grad(circuit, argnum=[argnum]) - grd(*diffable) - - # Check gradients in torch interface - if interface == 'torch': - for i in range(len(diffable)): - if i != argnum: - diffable[i].requires_grad = False - diffable[argnum].requires_grad = True - - res = circuit(*diffable) - res.backward() - diffable[argnum].grad.numpy() - - # Check gradients in tf interface - if interface == 'tf': - with tf.GradientTape() as tape: - loss = circuit(*diffable) - tape.gradient(loss, diffable[argnum]) - - -class TestInitializationIntegration: - """Tests integration with the parameter initialization functions from pennylane.init""" - - @pytest.mark.parametrize("template, dict", QUBIT_INIT) - def test_integration_qubit_init(self, template, dict): - """Tests that parameter initializations are compatible with qubit templates.""" - - n_wires = len(dict['wires']) - dev = qml.device('default.qubit', wires=n_wires) - - @qml.qnode(dev) - def circuit(): - template(**dict) - return qml.expval(qml.Identity(0)) - - # Check that execution does not throw error - circuit() - - @pytest.mark.parametrize("template, dict", CV_INIT) - def test_integration_cv_init(self, template, dict, gaussian_dummy): - """Tests that parameter initializations are compatible with cv templates.""" - - n_wires = len(dict['wires']) - dev = gaussian_dummy(n_wires) - - @qml.qnode(dev) - def circuit(): - template(**dict) - return qml.expval(qml.Identity(0)) - - # Check that execution does not throw error - circuit() - - -class TestNonConsecutiveWires: - """Tests that a template results in the same state whether we use nonconsecutive wire labels or not. - """ - - @pytest.mark.parametrize("template, diffable, nondiffable, n_wires", QUBIT_DIFFABLE_NONDIFFABLE) - def test_qubit_result_is_wire_label_independent(self, template, diffable, nondiffable, n_wires): - """Tests that qubit templates produce the same state when using two different wire labellings.""" - - # merge differentiable and non-differentiable arguments: - # we don't need them separate here - # Extract keys and items - if template.__name__ == "AmplitudeEmbedding": - diffable = diffable.copy() - diffable["features"] = np.array(diffable["features"]) - - kwargs = {**nondiffable, **diffable} - - # construct qnode with consecutive wires - dev_consec = qml.device('default.qubit', wires=n_wires) - @qml.qnode(dev_consec) - def circuit_consec(): - template(**kwargs) - return qml.expval(qml.Identity(wires=0)) - - # construct qnode with nonconsecutive wires - non_consecutive_strings = ['z', 'b', 'f', 'a', 'k', 'c', 'r', 's', 'd'] - nonconsecutive_wires = non_consecutive_strings[: n_wires] # make flexible size wires argument - kwargs2 = kwargs.copy() - if 'wires' in kwargs2: - kwargs2['wires'] = nonconsecutive_wires - # DoubleExcitationLayers does not have a wires kwarg - if template.__name__ == 'DoubleExcitationUnitary': - kwargs2['wires1'] = nonconsecutive_wires[:2] - kwargs2['wires2'] = nonconsecutive_wires[2:] - # some kwargs in UCSSD need to be manually replaced - if template.__name__ == 'UCCSD': - kwargs2['s_wires'] = [nonconsecutive_wires[:3], nonconsecutive_wires[1:]] - kwargs2['d_wires'] = [[nonconsecutive_wires[:2], nonconsecutive_wires[2:]]] - if template.__name__ == "QuantumPhaseEstimation": - kwargs2["target_wires"] = [nonconsecutive_wires[0]] - kwargs2["estimation_wires"] = nonconsecutive_wires[1:3] - - dev_nonconsec = qml.device('default.qubit', wires=nonconsecutive_wires) - - @qml.qnode(dev_nonconsec) - def circuit_nonconsec(): - template(**kwargs2) - return qml.expval(qml.Identity(wires=nonconsecutive_wires[0])) - - # run circuits - circuit_consec() - circuit_nonconsec() - - assert np.allclose(dev_consec.state, dev_nonconsec.state) - - @pytest.mark.parametrize("template, diffable, nondiffable, n_wires", CV_DIFFABLE_NONDIFFABLE) - def test_cv_result_is_wire_label_independent(self, template, diffable, nondiffable, n_wires, gaussian_dummy): - """Tests integration of cv templates with non-integer and non-consecutive wires.""" - - # merge differentiable and non-differentiable arguments: - # we don't need them separate here - kwargs = {**nondiffable, **diffable} - - # Construct qnode with consecutive wires - dev_consec = gaussian_dummy(wires=kwargs['wires']) - - @qml.qnode(dev_consec) - def circuit_consec(): - template(**kwargs) - return qml.expval(qml.Identity(wires=0)) - - # Construct qnode with nonconsecutive wires - kwargs2 = kwargs.copy() - non_consecutive_strings = ['z', 'b', 'f', 'a', 'k', 'c', 'r', 's', 'd'] - nonconsecutive_wires = non_consecutive_strings[: n_wires] # make flexible size wires argument - kwargs2['wires'] = nonconsecutive_wires - dev_nonconsec = gaussian_dummy(wires=nonconsecutive_wires) - - @qml.qnode(dev_nonconsec) - def circuit_nonconsec(): - template(**kwargs2) - return qml.expval(qml.Identity(wires=nonconsecutive_wires[0])) - - circuit_consec() - circuit_nonconsec() - - assert np.allclose(dev_consec._state[0], dev_nonconsec._state[0])