diff --git a/.github/CHANGELOG.md b/.github/CHANGELOG.md index 4d77ccedc58..4f14a5eb90f 100644 --- a/.github/CHANGELOG.md +++ b/.github/CHANGELOG.md @@ -537,14 +537,16 @@ 1: ──RY(1.35)──╰X──RY(0.422)──╰X──┤ ``` -- The embedding templates, as well as `BasicEntanglerLayers`, are now classes inheriting +- The embedding and state preparation templates, + as well as `BasicEntanglerLayers`, are now classes inheriting from `Operation`, and define the ansatz in their `expand()` method. This change does not affect the user interface. [(#1138)](https://github.com/PennyLaneAI/pennylane/pull/1138) [(#1156)](https://github.com/PennyLaneAI/pennylane/pull/1156) - For convenience, `BasicEntanglerLayers` has a method that returns the shape of the - trainable parameter tensor, i.e., + For convenience, some templates now have a method that returns the expected + shape of the trainable parameter tensor, which can be used to create + random tensors. ```python shape = qml.templates.BasicEntanglerLayers.shape(n_layers=2, n_wires=4) diff --git a/pennylane/tape/tape.py b/pennylane/tape/tape.py index 8179be4a50f..60764fcf6d7 100644 --- a/pennylane/tape/tape.py +++ b/pennylane/tape/tape.py @@ -1103,7 +1103,7 @@ def to_openqasm(self, wires=None, rotations=True): op.inv() # decompose the queue - operations = tape.expand(stop_at=lambda obj: obj.name in OPENQASM_GATES).operations + operations = tape.expand(depth=2, stop_at=lambda obj: obj.name in OPENQASM_GATES).operations # create the QASM code representing the operations for op in operations: diff --git a/pennylane/templates/state_preparations/arbitrary_state_preparation.py b/pennylane/templates/state_preparations/arbitrary_state_preparation.py index d834e4a605d..aa23dc3b2ba 100644 --- a/pennylane/templates/state_preparations/arbitrary_state_preparation.py +++ b/pennylane/templates/state_preparations/arbitrary_state_preparation.py @@ -12,28 +12,12 @@ # See the License for the specific language governing permissions and # limitations under the License. r""" -Contains the ``ArbitraryStatePreparation`` template. +Contains the ArbitraryStatePreparation template. """ +# pylint: disable=trailing-comma-tuple import functools import pennylane as qml -from pennylane.templates.decorator import template -from pennylane.wires import Wires - - -def _preprocess(weights, wires): - """Validate and pre-process inputs as follows: - - * Check the shape of the weights tensor. - - Args: - weights (tensor_like): trainable parameters of the template - wires (Wires): wires that template acts on - """ - shape = qml.math.shape(weights) - if shape != (2 ** (len(wires) + 1) - 2,): - raise ValueError( - f"Weights tensor must be of shape {(2 ** (len(wires) + 1) - 2,)}; got {shape}." - ) +from pennylane.operation import Operation, AnyWires @functools.lru_cache() @@ -60,8 +44,7 @@ def _state_preparation_pauli_words(num_wires): return single_qubit_words + multi_qubit_words -@template -def ArbitraryStatePreparation(weights, wires): +class ArbitraryStatePreparation(Operation): """Implements an arbitrary state preparation on the specified wires. An arbitrary state on :math:`n` wires is parametrized by :math:`2^{n+1} - 2` @@ -79,19 +62,53 @@ def ArbitraryStatePreparation(weights, wires): @qml.qnode(dev) def vqe(weights): - qml.ArbitraryStatePreparations(weights, wires=[0, 1, 2, 3]) + qml.templates.ArbitraryStatePreparation(weights, wires=[0, 1, 2, 3]) return qml.expval(qml.Hermitian(H, wires=[0, 1, 2, 3])) + The shape of the weights parameter can be computed as follows: + + .. code-block:: python + + shape = qml.templates.ArbitraryStatePreparation.shape(n_wires=4) + + Args: - weights (tensor_like): The angles of the Pauli word rotations, needs to have length :math:`2^(n+1) - 2` + weights (tensor_like): Angles of the Pauli word rotations. Needs to have length :math:`2^(n+1) - 2` where :math:`n` is the number of wires the template acts upon. - 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 """ - wires = Wires(wires) - _preprocess(weights, wires) + num_params = 1 + num_wires = AnyWires + par_domain = "A" + + def __init__(self, weights, wires, do_queue=True): + + shape = qml.math.shape(weights) + if shape != (2 ** (len(wires) + 1) - 2,): + raise ValueError( + f"Weights tensor must be of shape {(2 ** (len(wires) + 1) - 2,)}; got {shape}." + ) + + super().__init__(weights, wires=wires, do_queue=do_queue) + + def expand(self): + + with qml.tape.QuantumTape() as tape: + for i, pauli_word in enumerate(_state_preparation_pauli_words(len(self.wires))): + qml.PauliRot(self.parameters[0][i], pauli_word, wires=self.wires) + + return tape + + @staticmethod + def shape(n_wires): + r"""Returns the required shape for the weight tensor. + + Args: + n_wires (int): number of wires - for i, pauli_word in enumerate(_state_preparation_pauli_words(len(wires))): - qml.PauliRot(weights[i], pauli_word, wires=wires) + Returns: + tuple[int]: shape + """ + return (2 ** (n_wires + 1) - 2,) diff --git a/pennylane/templates/state_preparations/basis.py b/pennylane/templates/state_preparations/basis.py index c92ce502168..5b326e938e5 100644 --- a/pennylane/templates/state_preparations/basis.py +++ b/pennylane/templates/state_preparations/basis.py @@ -12,71 +12,69 @@ # See the License for the specific language governing permissions and # limitations under the License. r""" -Contains the ``BasisStatePreparation`` template. +Contains the BasisStatePreparation template. """ import pennylane as qml -from pennylane.templates.decorator import template -from pennylane.wires import Wires +from pennylane.operation import Operation, AnyWires -def _preprocess(basis_state, wires): - """Validate and pre-process inputs as follows: +class BasisStatePreparation(Operation): + r""" + Prepares a basis state on the given wires using a sequence of Pauli-X gates. - * Check the shape of the basis state. - * Cast basis state to a numpy array. + .. warning:: - Args: - basis_state (tensor_like): basis state to prepare - wires (Wires): wires that template acts on + ``basis_state`` influences the circuit architecture and is therefore incompatible with + gradient computations. - Returns: - array: preprocessed basis state - """ - shape = qml.math.shape(basis_state) + **Example** - if len(shape) != 1: - raise ValueError(f"Basis state must be one-dimensional; got shape {shape}.") + .. code-block:: python - n_bits = shape[0] - if n_bits != len(wires): - raise ValueError(f"Basis state must be of length {len(wires)}; got length {n_bits}.") + dev = qml.device("default.qubit", wires=4) - basis_state = list(qml.math.toarray(basis_state)) + @qml.qnode(dev) + def circuit(basis_state): + qml.templates.BasisStatePreparation(basis_state, wires=range(4)) - if not all(bit in [0, 1] for bit in basis_state): - raise ValueError(f"Basis state must only consist of 0s and 1s; got {basis_state}") + return [qml.expval(qml.PauliZ(wires=i)) for i in range(4)] - # we return the input as a list of values, since - # it is not differentiable - return basis_state + basis_state = [0, 1, 1, 0] + print(circuit(basis_state)) # [ 1. -1. -1. 1.] + Args: + basis_state (array): Input array of shape ``(n,)``, where n is the number of wires + the state preparation acts on. + wires (Iterable): wires that the template acts on + """ + num_params = 1 + num_wires = AnyWires + par_domain = "A" -@template -def BasisStatePreparation(basis_state, wires): - r""" - Prepares a basis state on the given wires using a sequence of Pauli X gates. + def __init__(self, basis_state, wires, do_queue=True): - .. warning:: + shape = qml.math.shape(basis_state) - ``basis_state`` influences the circuit architecture and is therefore incompatible with - gradient computations. Ensure that ``basis_state`` is not passed to the qnode by positional - arguments. + if len(shape) != 1: + raise ValueError(f"Basis state must be one-dimensional; got shape {shape}.") - Args: - basis_state (array): Input array of shape ``(N,)``, where N is the number of wires - the state preparation acts on. ``N`` must be smaller or equal to the total - number of wires of the device. - 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 = Wires(wires) + n_bits = shape[0] + if n_bits != len(wires): + raise ValueError(f"Basis state must be of length {len(wires)}; got length {n_bits}.") + + # we can extract a list here, because embedding is not differentiable + basis_state = list(qml.math.toarray(basis_state)) + + if not all(bit in [0, 1] for bit in basis_state): + raise ValueError(f"Basis state must only consist of 0s and 1s; got {basis_state}") + + super().__init__(basis_state, wires=wires, do_queue=do_queue) - basis_state = _preprocess(basis_state, wires) + def expand(self): - for wire, state in zip(wires, basis_state): - if state == 1: - qml.PauliX(wire) + with qml.tape.QuantumTape() as tape: + for wire, state in zip(self.wires, self.parameters[0]): + if state == 1: + qml.PauliX(wire) + return tape diff --git a/pennylane/templates/state_preparations/mottonen.py b/pennylane/templates/state_preparations/mottonen.py index 099170b4607..860eeb80f50 100644 --- a/pennylane/templates/state_preparations/mottonen.py +++ b/pennylane/templates/state_preparations/mottonen.py @@ -12,51 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. r""" -Contains the ``MottonenStatePreparation`` template. +Contains the MottonenStatePreparation template. """ import numpy as np - import pennylane as qml - -from pennylane.templates.decorator import template -from pennylane.wires import Wires - - -def _preprocess(state_vector, wires): - """Validate and pre-process inputs as follows: - - * Check the shape of the state vector. - * Check that the state vector is normalized. - * Extract polar coordinates of the state vector. - - Args: - state_vector (tensor_like): amplitude state vector to prepare - wires (Wires): wires that template acts on - - Returns: - tensor_like, tensor_like, Wires: amplitudes a, phases omega and preprocessed wires - """ - shape = qml.math.shape(state_vector) - - if len(shape) != 1: - raise ValueError(f"State vector must be a one-dimensional vector; got shape {shape}.") - - n_amplitudes = shape[0] - if n_amplitudes != 2 ** len(wires): - raise ValueError( - f"State vector must be of length {2 ** len(wires)} or less; got length {n_amplitudes}." - ) - - norm = qml.math.sum(qml.math.abs(state_vector) ** 2) - if not qml.math.allclose(norm, 1.0, atol=1e-3): - raise ValueError("State vector has to be of length 1.0, got {}".format(norm)) - a = qml.math.abs(state_vector) - omega = qml.math.angle(state_vector) - - # change ordering of wires, since original code was written for IBM machines - wires_reverse = wires[::-1] - - return a, omega, wires_reverse +from pennylane.operation import Operation, AnyWires # pylint: disable=len-as-condition,arguments-out-of-order,consider-using-enumerate @@ -248,17 +208,18 @@ def _get_alpha_y(a, n, k): return 2 * qml.math.arcsin(qml.math.sqrt(division)) -@template -def MottonenStatePreparation(state_vector, wires): +class MottonenStatePreparation(Operation): r""" Prepares an arbitrary state on the given wires using a decomposition into gates developed by `Möttönen et al. (2004) `_. The state is prepared via a sequence - of "uniformly controlled rotations". A uniformly controlled rotation on a target qubit is - composed from all possible controlled rotations on said qubit and can be used to address individual - elements of the state vector. In the work of Möttönen et al., the inverse of their state preparation - is constructed by first equalizing the phases of the state vector via uniformly controlled Z rotations + of uniformly controlled rotations. A uniformly controlled rotation on a target qubit is + composed from all possible controlled rotations on the qubit and can be used to address individual + elements of the state vector. + + In the work of Möttönen et al., inverse state preparation + is executed by first equalizing the phases of the state vector via uniformly controlled Z rotations, and then rotating the now real state vector into the direction of the state :math:`|0\rangle` via uniformly controlled Y rotations. @@ -268,36 +229,66 @@ def MottonenStatePreparation(state_vector, wires): The final state is only equal to the input state vector up to a global phase. + .. warning:: + + Due to non-trivial classical processing of the state vector, + this template is not always fully differentiable. + Args: - state_vector (tensor_like): Input array of shape ``(2^N,)``, where N is the number of wires - the state preparation acts on. The input array must be normalized, and ``N`` must be smaller - or equal to the total number of wires. - 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 + state_vector (tensor_like): Input array of shape ``(2^n,)``, where ``n`` is the number of wires + the state preparation acts on. The input array must be normalized. + wires (Iterable): wires that the template acts on + """ - ############### - # Input checks + num_params = 1 + num_wires = AnyWires + par_domain = "A" + + def __init__(self, state_vector, wires, do_queue=True): + + shape = qml.math.shape(state_vector) + + if len(shape) != 1: + raise ValueError(f"State vector must be a one-dimensional vector; got shape {shape}.") + + n_amplitudes = shape[0] + if n_amplitudes != 2 ** len(wires): + raise ValueError( + f"State vector must be of length {2 ** len(wires)} or less; got length {n_amplitudes}." + ) + + norm = qml.math.sum(qml.math.abs(state_vector) ** 2) + if not qml.math.allclose(norm, 1.0, atol=1e-3): + raise ValueError("State vector has to be of norm 1.0, got {}".format(norm)) + + super().__init__(state_vector, wires=wires, do_queue=do_queue) + + def expand(self): + + a = qml.math.abs(self.parameters[0]) + omega = qml.math.angle(self.parameters[0]) + + # change ordering of wires, since original code + # was written for IBM machines + wires_reverse = self.wires[::-1] - wires = Wires(wires) + with qml.tape.QuantumTape() as tape: - a, omega, wires_reverse = _preprocess(state_vector, wires) + # Apply inverse y rotation cascade to prepare correct absolute values of amplitudes + for k in range(len(wires_reverse), 0, -1): + alpha_y_k = _get_alpha_y(a, len(wires_reverse), k) + control = wires_reverse[k:] + target = wires_reverse[k - 1] + _uniform_rotation_dagger(qml.RY, alpha_y_k, control, target) - # Apply inverse y rotation cascade to prepare correct absolute values of amplitudes - for k in range(len(wires_reverse), 0, -1): - alpha_y_k = _get_alpha_y(a, len(wires_reverse), k) - control = wires_reverse[k:] - target = wires_reverse[k - 1] - _uniform_rotation_dagger(qml.RY, alpha_y_k, control, target) + # If necessary, apply inverse z rotation cascade to prepare correct phases of amplitudes + if not qml.math.allclose(omega, 0): + for k in range(len(wires_reverse), 0, -1): + alpha_z_k = _get_alpha_z(omega, len(wires_reverse), k) + control = wires_reverse[k:] + target = wires_reverse[k - 1] + if len(alpha_z_k) > 0: + _uniform_rotation_dagger(qml.RZ, alpha_z_k, control, target) - # If necessary, apply inverse z rotation cascade to prepare correct phases of amplitudes - if not qml.math.allclose(omega, 0): - for k in range(len(wires_reverse), 0, -1): - alpha_z_k = _get_alpha_z(omega, len(wires_reverse), k) - control = wires_reverse[k:] - target = wires_reverse[k - 1] - if len(alpha_z_k) > 0: - _uniform_rotation_dagger(qml.RZ, alpha_z_k, control, target) + return tape diff --git a/tests/tape/test_tape.py b/tests/tape/test_tape.py index e296ec97020..9ae9f155bcc 100644 --- a/tests/tape/test_tape.py +++ b/tests/tape/test_tape.py @@ -671,7 +671,8 @@ def test_decomposition_removing_parameters(self): with QuantumTape() as tape: qml.BasisState(np.array([1]), wires=0) - new_tape = tape.expand() + # since expansion calls `BasisStatePreparation` we have to expand twice + new_tape = tape.expand(depth=2) assert len(new_tape.operations) == 1 assert new_tape.operations[0].name == "PauliX" @@ -728,7 +729,7 @@ def test_nesting_and_decomposition(self): qml.probs(wires=0), qml.probs(wires="a") new_tape = tape.expand() - assert len(new_tape.operations) == 5 + assert len(new_tape.operations) == 4 def test_stopping_criterion(self): """Test that gates specified in the stop_at @@ -758,7 +759,7 @@ def test_depth_expansion(self): qml.RY(0.2, wires="a") qml.probs(wires=0), qml.probs(wires="a") - new_tape = tape.expand(depth=2) + new_tape = tape.expand(depth=3) assert len(new_tape.operations) == 11 def test_stopping_criterion_with_depth(self): @@ -794,7 +795,7 @@ def test_measurement_expansion(self): new_tape = tape.expand(expand_measurements=True) - assert len(new_tape.operations) == 6 + assert len(new_tape.operations) == 5 expected = [qml.operation.Probability, qml.operation.Expectation, qml.operation.Variance] assert [m.return_type is r for m, r in zip(new_tape.measurements, expected)] diff --git a/tests/templates/test_embeddings/test_amplitude.py b/tests/templates/test_embeddings/test_amplitude.py index 9ade6e53aab..baf265d363b 100644 --- a/tests/templates/test_embeddings/test_amplitude.py +++ b/tests/templates/test_embeddings/test_amplitude.py @@ -292,9 +292,10 @@ def test_autograd(self, tol): assert qml.math.allclose(res, res2, atol=tol, rtol=0) - def test_jax(self, tol, skip_if_no_jax_support): + def test_jax(self, tol): """Tests jax tensors.""" + jax = pytest.importorskip("jax") import jax.numpy as jnp features = jnp.array([1 / 2, 0, 1 / 2, 0, 1 / 2, 1 / 2, 0, 0]) @@ -309,10 +310,10 @@ def test_jax(self, tol, skip_if_no_jax_support): assert qml.math.allclose(res, res2, atol=tol, rtol=0) - def test_tf(self, tol, skip_if_no_tf_support): + def test_tf(self, tol): """Tests tf tensors.""" - import tensorflow as tf + tf = pytest.importorskip("tensorflow") features = tf.Variable([1 / 2, 0, 1 / 2, 0, 1 / 2, 1 / 2, 0, 0]) @@ -326,10 +327,10 @@ def test_tf(self, tol, skip_if_no_tf_support): assert qml.math.allclose(res, res2, atol=tol, rtol=0) - def test_torch(self, tol, skip_if_no_torch_support): + def test_torch(self, tol): """Tests torch tensors.""" - import torch + torch = pytest.importorskip("torch") features = torch.tensor([1 / 2, 0, 1 / 2, 0, 1 / 2, 1 / 2, 0, 0], requires_grad=True) diff --git a/tests/templates/test_embeddings/test_angle.py b/tests/templates/test_embeddings/test_angle.py index 5e51744fe1c..41554077fb8 100644 --- a/tests/templates/test_embeddings/test_angle.py +++ b/tests/templates/test_embeddings/test_angle.py @@ -209,10 +209,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 features = jnp.array([1.0, 1.0, 1.0]) @@ -234,10 +234,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") features = tf.Variable([1.0, 1.0, 1.0]) @@ -260,10 +260,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") features = torch.tensor([1.0, 1.0, 1.0], requires_grad=True) diff --git a/tests/templates/test_embeddings/test_basis.py b/tests/templates/test_embeddings/test_basis.py index 8b9a4cbcb07..54c6e0bde87 100644 --- a/tests/templates/test_embeddings/test_basis.py +++ b/tests/templates/test_embeddings/test_basis.py @@ -168,9 +168,10 @@ def test_autograd(self, tol): assert qml.math.allclose(res, res2, atol=tol, rtol=0) - def test_jax(self, tol, skip_if_no_jax_support): + def test_jax(self, tol): """Tests the jax interface.""" + jax = pytest.importorskip("jax") import jax.numpy as jnp features = jnp.array([0, 1, 0]) @@ -185,10 +186,10 @@ def test_jax(self, tol, skip_if_no_jax_support): assert qml.math.allclose(res, res2, 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") features = tf.Variable([0, 1, 0]) @@ -202,10 +203,10 @@ def test_tf(self, tol, skip_if_no_tf_support): assert qml.math.allclose(res, res2, 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") features = torch.tensor([0, 1, 0]) diff --git a/tests/templates/test_embeddings/test_displacement_emb.py b/tests/templates/test_embeddings/test_displacement_emb.py index 78f30168abe..40027fb715d 100644 --- a/tests/templates/test_embeddings/test_displacement_emb.py +++ b/tests/templates/test_embeddings/test_displacement_emb.py @@ -209,10 +209,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 features = jnp.array([1.0, 1.0, 1.0]) @@ -234,10 +234,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") features = tf.Variable([1.0, 1.0, 1.0]) @@ -260,10 +260,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") features = torch.tensor([1.0, 1.0, 1.0], requires_grad=True) diff --git a/tests/templates/test_embeddings/test_iqp_emb.py b/tests/templates/test_embeddings/test_iqp_emb.py index 92cc09fefaa..5e95e50404e 100644 --- a/tests/templates/test_embeddings/test_iqp_emb.py +++ b/tests/templates/test_embeddings/test_iqp_emb.py @@ -188,10 +188,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 features = jnp.array(np.random.random(size=(2,))) @@ -213,10 +213,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") features = tf.Variable(np.random.random(size=(2,))) @@ -239,10 +239,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") features = torch.tensor(np.random.random(size=(2,)), requires_grad=True) diff --git a/tests/templates/test_embeddings/test_qaoa_emb.py b/tests/templates/test_embeddings/test_qaoa_emb.py index fab6b544922..381a4532f82 100644 --- a/tests/templates/test_embeddings/test_qaoa_emb.py +++ b/tests/templates/test_embeddings/test_qaoa_emb.py @@ -307,10 +307,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 features = jnp.array(np.random.random(size=(2,))) @@ -334,10 +334,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") features = tf.Variable(np.random.random(size=(2,))) weights = tf.Variable(np.random.random(size=(1, 3))) @@ -362,10 +362,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") features = torch.tensor(np.random.random(size=(2,)), requires_grad=True) weights = torch.tensor(np.random.random(size=(1, 3)), requires_grad=True) diff --git a/tests/templates/test_embeddings/test_squeezing_emb.py b/tests/templates/test_embeddings/test_squeezing_emb.py index 90e28a30121..a080cc1008f 100644 --- a/tests/templates/test_embeddings/test_squeezing_emb.py +++ b/tests/templates/test_embeddings/test_squeezing_emb.py @@ -207,10 +207,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 features = jnp.array([1.0, 1.0, 1.0]) @@ -232,10 +232,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") features = tf.Variable([1.0, 1.0, 1.0]) @@ -258,10 +258,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") features = torch.tensor([1.0, 1.0, 1.0], requires_grad=True) diff --git a/tests/templates/test_state_preparations.py b/tests/templates/test_state_preparations.py deleted file mode 100644 index fe5a8dc5426..00000000000 --- a/tests/templates/test_state_preparations.py +++ /dev/null @@ -1,548 +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.state_preparations` module. -Integration tests should be placed into ``test_templates.py``. -""" -# pylint: disable=protected-access,cell-var-from-loop - -import math -from unittest.mock import patch -from pennylane import numpy as np -import pytest -import pennylane as qml -from pennylane.templates.state_preparations import ( - BasisStatePreparation, - MottonenStatePreparation, - ArbitraryStatePreparation, -) -from pennylane.templates.state_preparations.mottonen import gray_code -from pennylane.templates.state_preparations.arbitrary_state_preparation import ( - _state_preparation_pauli_words, -) -from pennylane.templates.state_preparations.mottonen import _get_alpha_y -from pennylane.wires import Wires - - -class TestHelperFunctions: - """Tests the functionality of helper functions.""" - - # fmt: off - @pytest.mark.parametrize("rank,expected_gray_code", [ - (1, ['0', '1']), - (2, ['00', '01', '11', '10']), - (3, ['000', '001', '011', '010', '110', '111', '101', '100']), - ]) - # fmt: on - def test_gray_code(self, rank, expected_gray_code): - """Tests that the function gray_code generates the proper - Gray code of given rank.""" - - assert gray_code(rank) == expected_gray_code - - @pytest.mark.parametrize( - "num_wires,expected_pauli_words", - [ - (1, ["X", "Y"]), - (2, ["XI", "YI", "IX", "IY", "XX", "XY"]), - ( - 3, - [ - "XII", - "YII", - "IXI", - "IYI", - "IIX", - "IIY", - "IXX", - "IXY", - "XXI", - "XYI", - "XIX", - "XIY", - "XXX", - "XXY", - ], - ), - ], - ) - def test_state_preparation_pauli_words(self, num_wires, expected_pauli_words): - """Test that the correct Pauli words are returned.""" - for idx, pauli_word in enumerate(_state_preparation_pauli_words(num_wires)): - assert expected_pauli_words[idx] == pauli_word - - -class TestBasisStatePreparation: - """Tests the template BasisStatePreparation.""" - - # fmt: off - @pytest.mark.parametrize("basis_state,wires,target_wires", [ - ([0], [0], []), - ([0], [1], []), - ([1], [0], [0]), - ([1], [1], [1]), - ([0, 1], [0, 1], [1]), - ([1, 0], [1, 4], [1]), - ([1, 1], [0, 2], [0, 2]), - ([1, 0], [4, 5], [4]), - ([0, 0, 1, 0], [1, 2, 3, 4], [3]), - ([1, 1, 1, 0], [1, 2, 6, 8], [1, 2, 6]), - ([1, 0, 1, 1], [1, 2, 6, 8], [1, 6, 8]), - ]) - # fmt: on - def test_correct_pl_gates(self, basis_state, wires, target_wires): - """Tests that the template BasisStatePreparation calls the correct - PennyLane gates on the correct wires.""" - - with patch("pennylane.PauliX") as mock: - BasisStatePreparation(basis_state, wires) - - called_wires = [args[0] for args, kwargs in mock.call_args_list] - - assert len(target_wires) == len(called_wires) - assert Wires(called_wires) == Wires(target_wires) - - # fmt: off - @pytest.mark.parametrize("basis_state,wires,target_state", [ - ([0], [0], [0, 0, 0]), - ([0], [1], [0, 0, 0]), - ([1], [0], [1, 0, 0]), - ([1], [1], [0, 1, 0]), - ([0, 1], [0, 1], [0, 1, 0]), - ([1, 1], [0, 2], [1, 0, 1]), - ([1, 1], [1, 2], [0, 1, 1]), - ([1, 0], [0, 2], [1, 0, 0]), - ([1, 1, 0], [0, 1, 2], [1, 1, 0]), - ([1, 0, 1], [0, 1, 2], [1, 0, 1]), - ]) - # fmt: on - def test_state_preparation(self, tol, qubit_device_3_wires, basis_state, wires, target_state): - """Tests that the template BasisStatePreparation integrates correctly with PennyLane.""" - - @qml.qnode(qubit_device_3_wires) - def circuit(): - BasisStatePreparation(basis_state, wires) - - # Pauli Z gates identify the basis state - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) - - # Convert from Pauli Z eigenvalues to basis state - output_state = [0 if x == 1.0 else 1 for x in circuit()] - - assert np.allclose(output_state, target_state, atol=tol, rtol=0) - - # fmt: off - @pytest.mark.parametrize("basis_state,wires", [ - ([0], [0, 1]), - ([0, 1], [0]), - ]) - # fmt: on - def test_error_num_qubits(self, basis_state, wires): - """Tests that the correct error message is raised when the number - of qubits doesn't match the number of wires.""" - - with pytest.raises(ValueError, match="Basis state must be of (shape|length)"): - BasisStatePreparation(basis_state, wires) - - # fmt: off - @pytest.mark.parametrize("basis_state,wires", [ - ([3], [0]), - ([1, 0, 2], [0, 1, 2]), - ]) - # fmt: on - def test_error_basis_state_format(self, basis_state, wires): - """Tests that the correct error messages is raised when - the basis state contains numbers different from 0 and 1.""" - - with pytest.raises(ValueError, match="Basis state must only (contain|consist)"): - BasisStatePreparation(basis_state, wires) - - 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(basis_state): - BasisStatePreparation(basis_state, wires=range(2)) - return qml.expval(qml.PauliZ(0)) - - with pytest.raises(ValueError, match="Basis state must be one-dimensional"): - basis_state = np.array([[0, 1]]) - circuit(basis_state) - - with pytest.raises(ValueError, match="Basis state must be of length"): - basis_state = np.array([0, 1, 0]) - circuit(basis_state) - - with pytest.raises(ValueError, match="Basis state must only consist of"): - basis_state = np.array([0, 2]) - circuit(basis_state) - - -class TestMottonenStatePreparation: - """Tests the template MottonenStatePreparation.""" - - # fmt: off - @pytest.mark.parametrize("state_vector,wires,target_state", [ - ([1, 0], [0], [1, 0, 0, 0, 0, 0, 0, 0]), - ([1, 0], [1], [1, 0, 0, 0, 0, 0, 0, 0]), - ([1, 0], [2], [1, 0, 0, 0, 0, 0, 0, 0]), - ([0, 1], [0], [0, 0, 0, 0, 1, 0, 0, 0]), - ([0, 1], [1], [0, 0, 1, 0, 0, 0, 0, 0]), - ([0, 1], [2], [0, 1, 0, 0, 0, 0, 0, 0]), - ([0, 1, 0, 0], [0, 1], [0, 0, 1, 0, 0, 0, 0, 0]), - ([0, 0, 0, 1], [0, 2], [0, 0, 0, 0, 0, 1, 0, 0]), - ([0, 0, 0, 1], [1, 2], [0, 0, 0, 1, 0, 0, 0, 0]), - ([1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 2], [1, 0, 0, 0, 0, 0, 0, 0]), - ([0, 0, 0, 0, 1j, 0, 0, 0], [0, 1, 2], [0, 0, 0, 0, 1j, 0, 0, 0]), - ([1/2, 0, 0, 0, 1/2, 1j/2, -1/2, 0], [0, 1, 2], [1/2, 0, 0, 0, 1/2, 1j/2, -1/2, 0]), - ([1/3, 0, 0, 0, 2j/3, 2j/3, 0, 0], [0, 1, 2], [1/3, 0, 0, 0, 2j/3, 2j/3, 0, 0]), - ([2/3, 0, 0, 0, 1/3, 0, 0, 2/3], [0, 1, 2], [2/3, 0, 0, 0, 1/3, 0, 0, 2/3]), - ( - [1/math.sqrt(8), 1j/math.sqrt(8), 1/math.sqrt(8), -1j/math.sqrt(8), 1/math.sqrt(8), 1/math.sqrt(8), 1/math.sqrt(8), 1j/math.sqrt(8)], - [0, 1, 2], - [1/math.sqrt(8), 1j/math.sqrt(8), 1/math.sqrt(8), -1j/math.sqrt(8), 1/math.sqrt(8), 1/math.sqrt(8), 1/math.sqrt(8), 1j/math.sqrt(8)], - ), - ( - [-0.17133152-0.18777771j, 0.00240643-0.40704011j, 0.18684538-0.36315606j, -0.07096948+0.104501j, 0.30357755-0.23831927j, -0.38735106+0.36075556j, 0.12351096-0.0539908j, 0.27942828-0.24810483j], - [0, 1, 2], - [-0.17133152-0.18777771j, 0.00240643-0.40704011j, 0.18684538-0.36315606j, -0.07096948+0.104501j, 0.30357755-0.23831927j, -0.38735106+0.36075556j, 0.12351096-0.0539908j, 0.27942828-0.24810483j], - ), - ( - [-0.29972867+0.04964242j, -0.28309418+0.09873227j, 0.00785743-0.37560696j, -0.3825148 +0.00674343j, -0.03008048+0.31119167j, 0.03666351-0.15935903j, -0.25358831+0.35461265j, -0.32198531+0.33479292j], - [0, 1, 2], - [-0.29972867+0.04964242j, -0.28309418+0.09873227j, 0.00785743-0.37560696j, -0.3825148 +0.00674343j, -0.03008048+0.31119167j, 0.03666351-0.15935903j, -0.25358831+0.35461265j, -0.32198531+0.33479292j], - ), - ( - [-0.39340123+0.05705932j, 0.1980509 -0.24234781j, 0.27265585-0.0604432j, -0.42641249+0.25767258j, 0.40386614-0.39925987j, 0.03924761+0.13193724j, -0.06059103-0.01753834j, 0.21707136-0.15887973j], - [0, 1, 2], - [-0.39340123+0.05705932j, 0.1980509 -0.24234781j, 0.27265585-0.0604432j, -0.42641249+0.25767258j, 0.40386614-0.39925987j, 0.03924761+0.13193724j, -0.06059103-0.01753834j, 0.21707136-0.15887973j], - ), - ( - [-1.33865287e-01+0.09802308j, 1.25060033e-01+0.16087698j, -4.14678130e-01-0.00774832j, 1.10121136e-01+0.37805482j, -3.21284864e-01+0.21521063j, -2.23121454e-04+0.28417422j, 5.64131205e-02+0.38135286j, 2.32694503e-01+0.41331133j], - [0, 1, 2], - [-1.33865287e-01+0.09802308j, 1.25060033e-01+0.16087698j, -4.14678130e-01-0.00774832j, 1.10121136e-01+0.37805482j, -3.21284864e-01+0.21521063j, -2.23121454e-04+0.28417422j, 5.64131205e-02+0.38135286j, 2.32694503e-01+0.41331133j], - ), - ([1/2, 0, 0, 0, 1j/2, 0, 1j/math.sqrt(2), 0], [0, 1, 2], [1/2, 0, 0, 0, 1j/2, 0, 1j/math.sqrt(2), 0]), - ([1/2, 0, 1j/2, 1j/math.sqrt(2)], [0, 1], [1/2, 0, 0, 0, 1j/2, 0, 1j/math.sqrt(2), 0]), - ]) - # fmt: on - def test_state_preparation_fidelity( - self, tol, qubit_device_3_wires, state_vector, wires, target_state - ): - """Tests that the template MottonenStatePreparation integrates correctly with PennyLane - and produces states with correct fidelity.""" - - @qml.qnode(qubit_device_3_wires) - def circuit(): - MottonenStatePreparation(state_vector, wires) - - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) - - circuit() - - state = circuit.device.state.ravel() - fidelity = abs(np.vdot(state, target_state)) ** 2 - - # We test for fidelity here, because the vector themselves will hardly match - # due to imperfect state preparation - assert np.isclose(fidelity, 1, atol=tol, rtol=0) - - # fmt: off - @pytest.mark.parametrize("state_vector,wires,target_state", [ - ([1, 0], [0], [1, 0, 0, 0, 0, 0, 0, 0]), - ([1, 0], [1], [1, 0, 0, 0, 0, 0, 0, 0]), - ([1, 0], [2], [1, 0, 0, 0, 0, 0, 0, 0]), - ([0, 1], [0], [0, 0, 0, 0, 1, 0, 0, 0]), - ([0, 1], [1], [0, 0, 1, 0, 0, 0, 0, 0]), - ([0, 1], [2], [0, 1, 0, 0, 0, 0, 0, 0]), - ([0, 1, 0, 0], [0, 1], [0, 0, 1, 0, 0, 0, 0, 0]), - ([0, 0, 0, 1], [0, 2], [0, 0, 0, 0, 0, 1, 0, 0]), - ([0, 0, 0, 1], [1, 2], [0, 0, 0, 1, 0, 0, 0, 0]), - ([1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 2], [1, 0, 0, 0, 0, 0, 0, 0]), - ([0, 0, 0, 0, 1j, 0, 0, 0], [0, 1, 2], [0, 0, 0, 0, 1j, 0, 0, 0]), - ([1/2, 0, 0, 0, 1/2, 1j/2, -1/2, 0], [0, 1, 2], [1/2, 0, 0, 0, 1/2, 1j/2, -1/2, 0]), - ([1/3, 0, 0, 0, 2j/3, 2j/3, 0, 0], [0, 1, 2], [1/3, 0, 0, 0, 2j/3, 2j/3, 0, 0]), - ([2/3, 0, 0, 0, 1/3, 0, 0, 2/3], [0, 1, 2], [2/3, 0, 0, 0, 1/3, 0, 0, 2/3]), - ( - [1/math.sqrt(8), 1j/math.sqrt(8), 1/math.sqrt(8), -1j/math.sqrt(8), 1/math.sqrt(8), 1/math.sqrt(8), 1/math.sqrt(8), 1j/math.sqrt(8)], - [0, 1, 2], - [1/math.sqrt(8), 1j/math.sqrt(8), 1/math.sqrt(8), -1j/math.sqrt(8), 1/math.sqrt(8), 1/math.sqrt(8), 1/math.sqrt(8), 1j/math.sqrt(8)], - ), - ( - [-0.17133152-0.18777771j, 0.00240643-0.40704011j, 0.18684538-0.36315606j, -0.07096948+0.104501j, 0.30357755-0.23831927j, -0.38735106+0.36075556j, 0.12351096-0.0539908j, 0.27942828-0.24810483j], - [0, 1, 2], - [-0.17133152-0.18777771j, 0.00240643-0.40704011j, 0.18684538-0.36315606j, -0.07096948+0.104501j, 0.30357755-0.23831927j, -0.38735106+0.36075556j, 0.12351096-0.0539908j, 0.27942828-0.24810483j], - ), - ( - [-0.29972867+0.04964242j, -0.28309418+0.09873227j, 0.00785743-0.37560696j, -0.3825148 +0.00674343j, -0.03008048+0.31119167j, 0.03666351-0.15935903j, -0.25358831+0.35461265j, -0.32198531+0.33479292j], - [0, 1, 2], - [-0.29972867+0.04964242j, -0.28309418+0.09873227j, 0.00785743-0.37560696j, -0.3825148 +0.00674343j, -0.03008048+0.31119167j, 0.03666351-0.15935903j, -0.25358831+0.35461265j, -0.32198531+0.33479292j], - ), - ( - [-0.39340123+0.05705932j, 0.1980509 -0.24234781j, 0.27265585-0.0604432j, -0.42641249+0.25767258j, 0.40386614-0.39925987j, 0.03924761+0.13193724j, -0.06059103-0.01753834j, 0.21707136-0.15887973j], - [0, 1, 2], - [-0.39340123+0.05705932j, 0.1980509 -0.24234781j, 0.27265585-0.0604432j, -0.42641249+0.25767258j, 0.40386614-0.39925987j, 0.03924761+0.13193724j, -0.06059103-0.01753834j, 0.21707136-0.15887973j], - ), - ( - [-1.33865287e-01+0.09802308j, 1.25060033e-01+0.16087698j, -4.14678130e-01-0.00774832j, 1.10121136e-01+0.37805482j, -3.21284864e-01+0.21521063j, -2.23121454e-04+0.28417422j, 5.64131205e-02+0.38135286j, 2.32694503e-01+0.41331133j], - [0, 1, 2], - [-1.33865287e-01+0.09802308j, 1.25060033e-01+0.16087698j, -4.14678130e-01-0.00774832j, 1.10121136e-01+0.37805482j, -3.21284864e-01+0.21521063j, -2.23121454e-04+0.28417422j, 5.64131205e-02+0.38135286j, 2.32694503e-01+0.41331133j], - ), - ([1/2, 0, 0, 0, 1j/2, 0, 1j/math.sqrt(2), 0], [0, 1, 2], [1/2, 0, 0, 0, 1j/2, 0, 1j/math.sqrt(2), 0]), - ([1/2, 0, 1j/2, 1j/math.sqrt(2)], [0, 1], [1/2, 0, 0, 0, 1j/2, 0, 1j/math.sqrt(2), 0]), - ]) - # fmt: on - def test_state_preparation_probability_distribution( - self, tol, qubit_device_3_wires, state_vector, wires, target_state - ): - """Tests that the template MottonenStatePreparation integrates correctly with PennyLane - and produces states with correct probability distribution.""" - - @qml.qnode(qubit_device_3_wires) - def circuit(): - MottonenStatePreparation(state_vector, wires) - - return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) - - circuit() - - state = circuit.device.state.ravel() - - probabilities = np.abs(state) ** 2 - target_probabilities = np.abs(target_state) ** 2 - - assert np.allclose(probabilities, target_probabilities, atol=tol, rtol=0) - - # fmt: off - @pytest.mark.parametrize("state_vector, wires", [ - ([1/2, 1/2], [0]), - ([2/3, 0, 2j/3, -2/3], [0, 1]), - ]) - # fmt: on - def test_error_state_vector_not_normalized(self, state_vector, wires): - """Tests that the correct error messages is raised if - the given state vector is not normalized.""" - - with pytest.raises(ValueError, match="State vector has to be of length"): - MottonenStatePreparation(state_vector, wires) - - # fmt: off - @pytest.mark.parametrize("state_vector,wires", [ - ([0, 1, 0], [0, 1]), - ([0, 1, 0, 0, 0], [0]), - ]) - # fmt: on - def test_error_num_entries(self, state_vector, wires): - """Tests that the correct error messages is raised if - the number of entries in the given state vector does not match - with the number of wires in the system.""" - - with pytest.raises(ValueError, match="State vector must be of (length|shape)"): - MottonenStatePreparation(state_vector, wires) - - @pytest.mark.parametrize( - "current_qubit, expected", - [ - (1, np.array([0, 0, 0, 1.23095942])), - (2, np.array([2.01370737, 3.14159265])), - (3, np.array([1.15927948])), - ], - ) - def test_get_alpha_y(self, current_qubit, expected, tol): - """Test the _get_alpha_y helper function.""" - - state = np.array([np.sqrt(0.2), 0, np.sqrt(0.5), 0, 0, 0, np.sqrt(0.2), np.sqrt(0.1)]) - res = _get_alpha_y(state, 3, current_qubit) - assert np.allclose(res, expected, atol=tol) - - 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(state_vector): - MottonenStatePreparation(state_vector, wires=range(2)) - return qml.expval(qml.PauliZ(0)) - - with pytest.raises(ValueError, match="State vector must be a one-dimensional"): - state_vector = np.array([[0, 1]]) - circuit(state_vector) - - with pytest.raises(ValueError, match="State vector must be of length"): - state_vector = np.array([0, 1]) - circuit(state_vector) - - with pytest.raises(ValueError, match="State vector has to be of length"): - state_vector = np.array([0, 2, 0, 0]) - circuit(state_vector) - - # fmt: off - @pytest.mark.parametrize("state_vector, n_wires", [ - ([1/2, 1/2, 1/2, 1/2], 2), - ([1, 0, 0, 0], 2), - ([0, 1, 0, 0], 2), - ([0, 0, 0, 1], 2), - ([0, 1, 0, 0, 0, 0, 0, 0], 3), - ([0, 0, 0, 0, 1, 0, 0, 0], 3), - ([2/3, 0, 0, 0, 1/3, 0, 0, 2/3], 3), - ([1/2, 0, 0, 0, 1/2, 1/2, 1/2, 0], 3), - ([1/3, 0, 0, 0, 2/3, 2/3, 0, 0], 3), - ([2/3, 0, 0, 0, 1/3, 0, 0, 2/3], 3), - ]) - # fmt: on - def test_RZ_skipped(self, state_vector, n_wires): - """Tests whether the cascade of RZ gates is skipped for real-valued states""" - - n_CNOT = 2 ** n_wires - 2 - - dev = qml.device("default.qubit", wires=n_wires) - - @qml.qnode(dev) - def circuit(state_vector): - MottonenStatePreparation(state_vector, wires=range(n_wires)) - return qml.expval(qml.PauliX(wires=0)) - - # when the RZ cascade is skipped, CNOT gates should only be those required for RY cascade - circuit(state_vector) - - assert circuit.qtape.get_resources()["CNOT"] == n_CNOT - - @pytest.mark.parametrize( - "state_vector", [np.array([0.70710678, 0.70710678]), np.array([0.70710678, 0.70710678j])] - ) - def test_gradient_evaluated(self, state_vector): - """Test that the gradient is successfully calculated for a simple example. This test only - checks that the gradient is calculated without an error.""" - dev = qml.device("default.qubit", wires=1) - - @qml.qnode(dev) - def circuit(state_vector): - MottonenStatePreparation(state_vector, wires=range(1)) - return qml.expval(qml.PauliZ(0)) - - qml.grad(circuit)(state_vector) - - -class TestArbitraryStatePreparation: - """Test the ArbitraryStatePreparation template.""" - - def test_correct_gates_single_wire(self): - """Test that the correct gates are applied on a single wire.""" - weights = np.array([0, 1], dtype=float) - - with qml.tape.OperationRecorder() as rec: - ArbitraryStatePreparation(weights, wires=[0]) - - assert rec.queue[0].name == "PauliRot" - - assert rec.queue[0].data[0] == weights[0] - assert rec.queue[0].data[1] == "X" - assert rec.queue[0].wires == Wires([0]) - - assert rec.queue[1].name == "PauliRot" - assert rec.queue[1].data[0] == weights[1] - assert rec.queue[1].data[1] == "Y" - assert rec.queue[1].wires == Wires([0]) - - def test_correct_gates_two_wires(self): - """Test that the correct gates are applied on on two wires.""" - weights = np.array([0, 1, 2, 3, 4, 5], dtype=float) - - with qml.tape.OperationRecorder() as rec: - ArbitraryStatePreparation(weights, wires=[0, 1]) - - assert rec.queue[0].name == "PauliRot" - - assert rec.queue[0].data[0] == weights[0] - assert rec.queue[0].data[1] == "XI" - assert rec.queue[0].wires == Wires([0, 1]) - - assert rec.queue[1].name == "PauliRot" - assert rec.queue[1].data[0] == weights[1] - assert rec.queue[1].data[1] == "YI" - assert rec.queue[1].wires == Wires([0, 1]) - - assert rec.queue[2].name == "PauliRot" - assert rec.queue[2].data[0] == weights[2] - assert rec.queue[2].data[1] == "IX" - assert rec.queue[2].wires == Wires([0, 1]) - - assert rec.queue[3].name == "PauliRot" - assert rec.queue[3].data[0] == weights[3] - assert rec.queue[3].data[1] == "IY" - assert rec.queue[3].wires == Wires([0, 1]) - - assert rec.queue[4].name == "PauliRot" - assert rec.queue[4].data[0] == weights[4] - assert rec.queue[4].data[1] == "XX" - assert rec.queue[4].wires == Wires([0, 1]) - - assert rec.queue[5].name == "PauliRot" - assert rec.queue[5].data[0] == weights[5] - assert rec.queue[5].data[1] == "XY" - assert rec.queue[5].wires == Wires([0, 1]) - - def test_GHZ_generation(self, qubit_device_3_wires, tol): - """Test that the template prepares a GHZ state.""" - GHZ_state = np.array([1 / math.sqrt(2), 0, 0, 0, 0, 0, 0, 1 / math.sqrt(2)]) - - weights = np.zeros(14) - weights[13] = math.pi / 2 - - @qml.qnode(qubit_device_3_wires) - def circuit(weights): - ArbitraryStatePreparation(weights, [0, 1, 2]) - - return qml.expval(qml.PauliZ(0)) - - circuit(weights) - - assert np.allclose(circuit.device.state, GHZ_state, atol=tol, rtol=0) - - def test_even_superposition_generation(self, qubit_device_3_wires, tol): - """Test that the template prepares a even superposition state.""" - even_superposition_state = np.ones(8) / math.sqrt(8) - - weights = np.zeros(14) - weights[1] = math.pi / 2 - weights[3] = math.pi / 2 - weights[5] = math.pi / 2 - - @qml.qnode(qubit_device_3_wires) - def circuit(weights): - ArbitraryStatePreparation(weights, [0, 1, 2]) - - return qml.expval(qml.PauliZ(0)) - - circuit(weights) - - assert np.allclose(circuit.device.state, even_superposition_state, atol=tol, rtol=0) - - 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=3) - - @qml.qnode(dev) - def circuit(weights): - ArbitraryStatePreparation(weights, wires=range(3)) - return qml.expval(qml.PauliZ(0)) - - with pytest.raises(ValueError, match="Weights tensor must be of shape"): - weights = np.zeros(12) - circuit(weights) diff --git a/tests/templates/test_state_preparations/test_arbitrary_state_prep.py b/tests/templates/test_state_preparations/test_arbitrary_state_prep.py new file mode 100644 index 00000000000..3baffbc960d --- /dev/null +++ b/tests/templates/test_state_preparations/test_arbitrary_state_prep.py @@ -0,0 +1,352 @@ +# 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 ArbitraryStatePreparation template. +""" +import pytest +import numpy as np +import pennylane as qml +from pennylane import numpy as pnp +from pennylane.templates.state_preparations.arbitrary_state_preparation import ( + _state_preparation_pauli_words, +) + + +class TestHelpers: + """Tests for the Pauli-word helper function.""" + + @pytest.mark.parametrize( + "num_wires,expected_pauli_words", + [ + (1, ["X", "Y"]), + (2, ["XI", "YI", "IX", "IY", "XX", "XY"]), + ( + 3, + [ + "XII", + "YII", + "IXI", + "IYI", + "IIX", + "IIY", + "IXX", + "IXY", + "XXI", + "XYI", + "XIX", + "XIY", + "XXX", + "XXY", + ], + ), + ], + ) + def test_state_preparation_pauli_words(self, num_wires, expected_pauli_words): + """Test that the correct Pauli words are returned.""" + for idx, pauli_word in enumerate(_state_preparation_pauli_words(num_wires)): + assert expected_pauli_words[idx] == pauli_word + + +class TestDecomposition: + """Tests that the template defines the correct decomposition.""" + + def test_correct_gates_single_wire(self): + """Test that the correct gates are applied on a single wire.""" + weights = np.array([0, 1], dtype=float) + + op = qml.templates.ArbitraryStatePreparation(weights, wires=[0]) + queue = op.expand().operations + + assert queue[0].name == "PauliRot" + + assert queue[0].data[0] == weights[0] + assert queue[0].data[1] == "X" + assert queue[0].wires.labels == (0,) + + assert queue[1].name == "PauliRot" + assert queue[1].data[0] == weights[1] + assert queue[1].data[1] == "Y" + assert queue[1].wires.labels == (0,) + + def test_correct_gates_two_wires(self): + """Test that the correct gates are applied on on two wires.""" + weights = np.array([0, 1, 2, 3, 4, 5], dtype=float) + + op = qml.templates.ArbitraryStatePreparation(weights, wires=[0, 1]) + queue = op.expand().operations + + assert queue[0].name == "PauliRot" + + assert queue[0].data[0] == weights[0] + assert queue[0].data[1] == "XI" + assert queue[0].wires.labels == (0, 1) + + assert queue[1].name == "PauliRot" + assert queue[1].data[0] == weights[1] + assert queue[1].data[1] == "YI" + assert queue[1].wires.labels == (0, 1) + + assert queue[2].name == "PauliRot" + assert queue[2].data[0] == weights[2] + assert queue[2].data[1] == "IX" + assert queue[2].wires.labels == (0, 1) + + assert queue[3].name == "PauliRot" + assert queue[3].data[0] == weights[3] + assert queue[3].data[1] == "IY" + assert queue[3].wires.labels == (0, 1) + + assert queue[4].name == "PauliRot" + assert queue[4].data[0] == weights[4] + assert queue[4].data[1] == "XX" + assert queue[4].wires.labels == (0, 1) + + assert queue[5].name == "PauliRot" + assert queue[5].data[0] == weights[5] + assert queue[5].data[1] == "XY" + assert queue[5].wires.labels == (0, 1) + + def test_GHZ_generation(self, qubit_device_3_wires, tol): + """Test that the template prepares a GHZ state.""" + GHZ_state = np.array([1 / np.sqrt(2), 0, 0, 0, 0, 0, 0, 1 / np.sqrt(2)]) + + weights = np.zeros(14) + weights[13] = np.pi / 2 + + @qml.qnode(qubit_device_3_wires) + def circuit(weights): + qml.templates.ArbitraryStatePreparation(weights, [0, 1, 2]) + return qml.expval(qml.PauliZ(0)) + + circuit(weights) + + assert np.allclose(circuit.device.state, GHZ_state, atol=tol, rtol=0) + + def test_even_superposition_generation(self, qubit_device_3_wires, tol): + """Test that the template prepares an even superposition state.""" + even_superposition_state = np.ones(8) / np.sqrt(8) + + weights = np.zeros(14) + weights[1] = np.pi / 2 + weights[3] = np.pi / 2 + weights[5] = np.pi / 2 + + @qml.qnode(qubit_device_3_wires) + def circuit(weights): + qml.templates.ArbitraryStatePreparation(weights, [0, 1, 2]) + + return qml.expval(qml.PauliZ(0)) + + circuit(weights) + + assert np.allclose(circuit.device.state, even_superposition_state, 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=(2 ** 4 - 2)) + + dev = qml.device("default.qubit", wires=3) + dev2 = qml.device("default.qubit", wires=["z", "a", "k"]) + + @qml.qnode(dev) + def circuit(): + qml.templates.ArbitraryStatePreparation(weights, wires=range(3)) + return qml.expval(qml.Identity(0)) + + @qml.qnode(dev2) + def circuit2(): + qml.templates.ArbitraryStatePreparation(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=3) + + @qml.qnode(dev) + def circuit(weights): + qml.templates.ArbitraryStatePreparation(weights, wires=range(3)) + return qml.expval(qml.PauliZ(0)) + + with pytest.raises(ValueError, match="Weights tensor must be of shape"): + weights = np.zeros(12) + circuit(weights) + + +class TestAttributes: + """Tests additional methods and attributes""" + + @pytest.mark.parametrize( + "n_wires, expected_shape", + [ + (3, (14,)), + (1, (2,)), + (2, (6,)), + ], + ) + def test_shape(self, n_wires, expected_shape): + """Test that the shape method returns the correct shape of the weights tensor""" + + shape = qml.templates.ArbitraryStatePreparation.shape(n_wires) + assert shape == expected_shape + + +def circuit_template(weights): + qml.templates.ArbitraryStatePreparation(weights, range(2)) + return qml.expval(qml.PauliZ(0)) + + +def circuit_decomposed(weights): + qml.PauliRot(weights[0], "XI", wires=[0, 1]) + qml.PauliRot(weights[1], "YI", wires=[0, 1]) + qml.PauliRot(weights[2], "IX", wires=[0, 1]) + qml.PauliRot(weights[3], "IY", wires=[0, 1]) + qml.PauliRot(weights[4], "XX", wires=[0, 1]) + qml.PauliRot(weights[5], "XY", wires=[0, 1]) + 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 = [1] * 6 + + 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) + 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=(6,)) + 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): + """Tests the jax interface.""" + + jax = pytest.importorskip("jax") + + import jax.numpy as jnp + + weights = jnp.array(np.random.random(size=(6,))) + + 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): + """Tests the tf interface.""" + + tf = pytest.importorskip("tensorflow") + + weights = tf.Variable(np.random.random(size=(6,))) + + 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): + """Tests the torch interface.""" + + torch = pytest.importorskip("torch") + + weights = torch.tensor(np.random.random(size=(6,)), 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_state_preparations/test_basis_state_prep.py b/tests/templates/test_state_preparations/test_basis_state_prep.py new file mode 100644 index 00000000000..75842f55f76 --- /dev/null +++ b/tests/templates/test_state_preparations/test_basis_state_prep.py @@ -0,0 +1,151 @@ +# 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 BasisStatePreparation template. +""" +import pytest +import numpy as np +import pennylane as qml + + +class TestDecomposition: + """Tests that the template defines the correct decomposition.""" + + # fmt: off + @pytest.mark.parametrize("basis_state,wires,target_wires", [ + ([0], [0], []), + ([0], [1], []), + ([1], [0], [0]), + ([1], [1], [1]), + ([0, 1], [0, 1], [1]), + ([1, 0], [1, 4], [1]), + ([1, 1], [0, 2], [0, 2]), + ([1, 0], [4, 5], [4]), + ([0, 0, 1, 0], [1, 2, 3, 4], [3]), + ([1, 1, 1, 0], [1, 2, 6, 8], [1, 2, 6]), + ([1, 0, 1, 1], [1, 2, 6, 8], [1, 6, 8]), + ]) + # fmt: on + def test_correct_pl_gates(self, basis_state, wires, target_wires): + """Tests queue for simple cases.""" + + op = qml.templates.BasisStatePreparation(basis_state, wires) + queue = op.expand().operations + + for id, gate in enumerate(queue): + assert gate.name == "PauliX" + assert gate.wires.tolist() == [target_wires[id]] + + # fmt: off + @pytest.mark.parametrize("basis_state,wires,target_state", [ + ([0], [0], [0, 0, 0]), + ([0], [1], [0, 0, 0]), + ([1], [0], [1, 0, 0]), + ([1], [1], [0, 1, 0]), + ([0, 1], [0, 1], [0, 1, 0]), + ([1, 1], [0, 2], [1, 0, 1]), + ([1, 1], [1, 2], [0, 1, 1]), + ([1, 0], [0, 2], [1, 0, 0]), + ([1, 1, 0], [0, 1, 2], [1, 1, 0]), + ([1, 0, 1], [0, 1, 2], [1, 0, 1]), + ]) + # fmt: on + def test_state_preparation(self, tol, qubit_device_3_wires, basis_state, wires, target_state): + """Tests that the template produces the correct expectation values.""" + + @qml.qnode(qubit_device_3_wires) + def circuit(): + qml.templates.BasisStatePreparation(basis_state, wires) + + # Pauli Z gates identify the basis state + return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) + + # Convert from Pauli Z eigenvalues to basis state + output_state = [0 if x == 1.0 else 1 for x in circuit()] + + assert np.allclose(output_state, target_state, atol=tol, rtol=0) + + def test_custom_wire_labels(self, tol): + """Test that template can deal with non-numeric, nonconsecutive wire labels.""" + basis_state = [0, 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.BasisStatePreparation(basis_state, wires=range(3)) + return qml.expval(qml.Identity(0)) + + @qml.qnode(dev2) + def circuit2(): + qml.templates.BasisStatePreparation(basis_state, 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.""" + + # fmt: off + @pytest.mark.parametrize("basis_state,wires", [ + ([0], [0, 1]), + ([0, 1], [0]), + ]) + # fmt: on + def test_error_num_qubits(self, basis_state, wires): + """Tests that the correct error message is raised when the number + of qubits does not match the number of wires.""" + + with pytest.raises(ValueError, match="Basis state must be of (shape|length)"): + qml.templates.BasisStatePreparation(basis_state, wires) + + # fmt: off + @pytest.mark.parametrize("basis_state,wires", [ + ([3], [0]), + ([1, 0, 2], [0, 1, 2]), + ]) + # fmt: on + def test_error_basis_state_format(self, basis_state, wires): + """Tests that the correct error messages is raised when + the basis state contains numbers different from 0 and 1.""" + + with pytest.raises(ValueError, match="Basis state must only (contain|consist)"): + qml.templates.BasisStatePreparation(basis_state, wires) + + 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(basis_state): + qml.templates.BasisStatePreparation(basis_state, wires=range(2)) + return qml.expval(qml.PauliZ(0)) + + with pytest.raises(ValueError, match="Basis state must be one-dimensional"): + basis_state = np.array([[0, 1]]) + circuit(basis_state) + + with pytest.raises(ValueError, match="Basis state must be of length"): + basis_state = np.array([0, 1, 0]) + circuit(basis_state) + + with pytest.raises(ValueError, match="Basis state must only consist of"): + basis_state = np.array([0, 2]) + circuit(basis_state) diff --git a/tests/templates/test_state_preparations/test_mottonen_state_prep.py b/tests/templates/test_state_preparations/test_mottonen_state_prep.py new file mode 100644 index 00000000000..902e5bdcdd9 --- /dev/null +++ b/tests/templates/test_state_preparations/test_mottonen_state_prep.py @@ -0,0 +1,342 @@ +# 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 ArbitraryStatePreparation template. +""" +import pytest +import numpy as np +import pennylane as qml +from pennylane import numpy as pnp +from pennylane.templates.state_preparations.mottonen import gray_code, _get_alpha_y + + +class TestHelpers: + """Tests the helper functions for classical pre-processsing.""" + + # fmt: off + @pytest.mark.parametrize("rank,expected_gray_code", [ + (1, ['0', '1']), + (2, ['00', '01', '11', '10']), + (3, ['000', '001', '011', '010', '110', '111', '101', '100']), + ]) + # fmt: on + def test_gray_code(self, rank, expected_gray_code): + """Tests that the function gray_code generates the proper + Gray code of given rank.""" + + assert gray_code(rank) == expected_gray_code + + @pytest.mark.parametrize( + "current_qubit, expected", + [ + (1, np.array([0, 0, 0, 1.23095942])), + (2, np.array([2.01370737, 3.14159265])), + (3, np.array([1.15927948])), + ], + ) + def test_get_alpha_y(self, current_qubit, expected, tol): + """Test the _get_alpha_y helper function.""" + + state = np.array([np.sqrt(0.2), 0, np.sqrt(0.5), 0, 0, 0, np.sqrt(0.2), np.sqrt(0.1)]) + res = _get_alpha_y(state, 3, current_qubit) + assert np.allclose(res, expected, atol=tol) + + +class TestDecomposition: + """Tests that the template defines the correct decomposition.""" + + # fmt: off + @pytest.mark.parametrize("state_vector,wires,target_state", [ + ([1, 0], [0], [1, 0, 0, 0, 0, 0, 0, 0]), + ([1, 0], [1], [1, 0, 0, 0, 0, 0, 0, 0]), + ([1, 0], [2], [1, 0, 0, 0, 0, 0, 0, 0]), + ([0, 1], [0], [0, 0, 0, 0, 1, 0, 0, 0]), + ([0, 1], [1], [0, 0, 1, 0, 0, 0, 0, 0]), + ([0, 1], [2], [0, 1, 0, 0, 0, 0, 0, 0]), + ([0, 1, 0, 0], [0, 1], [0, 0, 1, 0, 0, 0, 0, 0]), + ([0, 0, 0, 1], [0, 2], [0, 0, 0, 0, 0, 1, 0, 0]), + ([0, 0, 0, 1], [1, 2], [0, 0, 0, 1, 0, 0, 0, 0]), + ([1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 2], [1, 0, 0, 0, 0, 0, 0, 0]), + ([0, 0, 0, 0, 1j, 0, 0, 0], [0, 1, 2], [0, 0, 0, 0, 1j, 0, 0, 0]), + ([1 / 2, 0, 0, 0, 1 / 2, 1j / 2, -1 / 2, 0], [0, 1, 2], [1 / 2, 0, 0, 0, 1 / 2, 1j / 2, -1 / 2, 0]), + ([1 / 3, 0, 0, 0, 2j / 3, 2j / 3, 0, 0], [0, 1, 2], [1 / 3, 0, 0, 0, 2j / 3, 2j / 3, 0, 0]), + ([2 / 3, 0, 0, 0, 1 / 3, 0, 0, 2 / 3], [0, 1, 2], [2 / 3, 0, 0, 0, 1 / 3, 0, 0, 2 / 3]), + ( + [1 / np.sqrt(8), 1j / np.sqrt(8), 1 / np.sqrt(8), -1j / np.sqrt(8), 1 / np.sqrt(8), + 1 / np.sqrt(8), 1 / np.sqrt(8), 1j / np.sqrt(8)], + [0, 1, 2], + [1 / np.sqrt(8), 1j / np.sqrt(8), 1 / np.sqrt(8), -1j / np.sqrt(8), 1 / np.sqrt(8), + 1 / np.sqrt(8), 1 / np.sqrt(8), 1j / np.sqrt(8)], + ), + ( + [-0.17133152 - 0.18777771j, 0.00240643 - 0.40704011j, 0.18684538 - 0.36315606j, -0.07096948 + 0.104501j, + 0.30357755 - 0.23831927j, -0.38735106 + 0.36075556j, 0.12351096 - 0.0539908j, + 0.27942828 - 0.24810483j], + [0, 1, 2], + [-0.17133152 - 0.18777771j, 0.00240643 - 0.40704011j, 0.18684538 - 0.36315606j, -0.07096948 + 0.104501j, + 0.30357755 - 0.23831927j, -0.38735106 + 0.36075556j, 0.12351096 - 0.0539908j, + 0.27942828 - 0.24810483j], + ), + ( + [-0.29972867 + 0.04964242j, -0.28309418 + 0.09873227j, 0.00785743 - 0.37560696j, + -0.3825148 + 0.00674343j, -0.03008048 + 0.31119167j, 0.03666351 - 0.15935903j, + -0.25358831 + 0.35461265j, -0.32198531 + 0.33479292j], + [0, 1, 2], + [-0.29972867 + 0.04964242j, -0.28309418 + 0.09873227j, 0.00785743 - 0.37560696j, + -0.3825148 + 0.00674343j, -0.03008048 + 0.31119167j, 0.03666351 - 0.15935903j, + -0.25358831 + 0.35461265j, -0.32198531 + 0.33479292j], + ), + ( + [-0.39340123 + 0.05705932j, 0.1980509 - 0.24234781j, 0.27265585 - 0.0604432j, -0.42641249 + 0.25767258j, + 0.40386614 - 0.39925987j, 0.03924761 + 0.13193724j, -0.06059103 - 0.01753834j, + 0.21707136 - 0.15887973j], + [0, 1, 2], + [-0.39340123 + 0.05705932j, 0.1980509 - 0.24234781j, 0.27265585 - 0.0604432j, -0.42641249 + 0.25767258j, + 0.40386614 - 0.39925987j, 0.03924761 + 0.13193724j, -0.06059103 - 0.01753834j, + 0.21707136 - 0.15887973j], + ), + ( + [-1.33865287e-01 + 0.09802308j, 1.25060033e-01 + 0.16087698j, -4.14678130e-01 - 0.00774832j, + 1.10121136e-01 + 0.37805482j, -3.21284864e-01 + 0.21521063j, -2.23121454e-04 + 0.28417422j, + 5.64131205e-02 + 0.38135286j, 2.32694503e-01 + 0.41331133j], + [0, 1, 2], + [-1.33865287e-01 + 0.09802308j, 1.25060033e-01 + 0.16087698j, -4.14678130e-01 - 0.00774832j, + 1.10121136e-01 + 0.37805482j, -3.21284864e-01 + 0.21521063j, -2.23121454e-04 + 0.28417422j, + 5.64131205e-02 + 0.38135286j, 2.32694503e-01 + 0.41331133j], + ), + ([1 / 2, 0, 0, 0, 1j / 2, 0, 1j / np.sqrt(2), 0], [0, 1, 2], + [1 / 2, 0, 0, 0, 1j / 2, 0, 1j / np.sqrt(2), 0]), + ([1 / 2, 0, 1j / 2, 1j / np.sqrt(2)], [0, 1], [1 / 2, 0, 0, 0, 1j / 2, 0, 1j / np.sqrt(2), 0]), + ]) + # fmt: on + def test_state_preparation_fidelity( + self, tol, qubit_device_3_wires, state_vector, wires, target_state + ): + """Tests that the template produces correct states with high fidelity.""" + + @qml.qnode(qubit_device_3_wires) + def circuit(): + qml.templates.MottonenStatePreparation(state_vector, wires) + return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) + + circuit() + + state = circuit.device.state.ravel() + fidelity = abs(np.vdot(state, target_state)) ** 2 + + # We test for fidelity here, because the vector themselves will hardly match + # due to imperfect state preparation + assert np.isclose(fidelity, 1, atol=tol, rtol=0) + + # fmt: off + @pytest.mark.parametrize("state_vector,wires,target_state", [ + ([1, 0], [0], [1, 0, 0, 0, 0, 0, 0, 0]), + ([1, 0], [1], [1, 0, 0, 0, 0, 0, 0, 0]), + ([1, 0], [2], [1, 0, 0, 0, 0, 0, 0, 0]), + ([0, 1], [0], [0, 0, 0, 0, 1, 0, 0, 0]), + ([0, 1], [1], [0, 0, 1, 0, 0, 0, 0, 0]), + ([0, 1], [2], [0, 1, 0, 0, 0, 0, 0, 0]), + ([0, 1, 0, 0], [0, 1], [0, 0, 1, 0, 0, 0, 0, 0]), + ([0, 0, 0, 1], [0, 2], [0, 0, 0, 0, 0, 1, 0, 0]), + ([0, 0, 0, 1], [1, 2], [0, 0, 0, 1, 0, 0, 0, 0]), + ([1, 0, 0, 0, 0, 0, 0, 0], [0, 1, 2], [1, 0, 0, 0, 0, 0, 0, 0]), + ([0, 0, 0, 0, 1j, 0, 0, 0], [0, 1, 2], [0, 0, 0, 0, 1j, 0, 0, 0]), + ([1 / 2, 0, 0, 0, 1 / 2, 1j / 2, -1 / 2, 0], [0, 1, 2], [1 / 2, 0, 0, 0, 1 / 2, 1j / 2, -1 / 2, 0]), + ([1 / 3, 0, 0, 0, 2j / 3, 2j / 3, 0, 0], [0, 1, 2], [1 / 3, 0, 0, 0, 2j / 3, 2j / 3, 0, 0]), + ([2 / 3, 0, 0, 0, 1 / 3, 0, 0, 2 / 3], [0, 1, 2], [2 / 3, 0, 0, 0, 1 / 3, 0, 0, 2 / 3]), + ( + [1 / np.sqrt(8), 1j / np.sqrt(8), 1 / np.sqrt(8), -1j / np.sqrt(8), 1 / np.sqrt(8), + 1 / np.sqrt(8), 1 / np.sqrt(8), 1j / np.sqrt(8)], + [0, 1, 2], + [1 / np.sqrt(8), 1j / np.sqrt(8), 1 / np.sqrt(8), -1j / np.sqrt(8), 1 / np.sqrt(8), + 1 / np.sqrt(8), 1 / np.sqrt(8), 1j / np.sqrt(8)], + ), + ( + [-0.17133152 - 0.18777771j, 0.00240643 - 0.40704011j, 0.18684538 - 0.36315606j, -0.07096948 + 0.104501j, + 0.30357755 - 0.23831927j, -0.38735106 + 0.36075556j, 0.12351096 - 0.0539908j, + 0.27942828 - 0.24810483j], + [0, 1, 2], + [-0.17133152 - 0.18777771j, 0.00240643 - 0.40704011j, 0.18684538 - 0.36315606j, -0.07096948 + 0.104501j, + 0.30357755 - 0.23831927j, -0.38735106 + 0.36075556j, 0.12351096 - 0.0539908j, + 0.27942828 - 0.24810483j], + ), + ( + [-0.29972867 + 0.04964242j, -0.28309418 + 0.09873227j, 0.00785743 - 0.37560696j, + -0.3825148 + 0.00674343j, -0.03008048 + 0.31119167j, 0.03666351 - 0.15935903j, + -0.25358831 + 0.35461265j, -0.32198531 + 0.33479292j], + [0, 1, 2], + [-0.29972867 + 0.04964242j, -0.28309418 + 0.09873227j, 0.00785743 - 0.37560696j, + -0.3825148 + 0.00674343j, -0.03008048 + 0.31119167j, 0.03666351 - 0.15935903j, + -0.25358831 + 0.35461265j, -0.32198531 + 0.33479292j], + ), + ( + [-0.39340123 + 0.05705932j, 0.1980509 - 0.24234781j, 0.27265585 - 0.0604432j, -0.42641249 + 0.25767258j, + 0.40386614 - 0.39925987j, 0.03924761 + 0.13193724j, -0.06059103 - 0.01753834j, + 0.21707136 - 0.15887973j], + [0, 1, 2], + [-0.39340123 + 0.05705932j, 0.1980509 - 0.24234781j, 0.27265585 - 0.0604432j, -0.42641249 + 0.25767258j, + 0.40386614 - 0.39925987j, 0.03924761 + 0.13193724j, -0.06059103 - 0.01753834j, + 0.21707136 - 0.15887973j], + ), + ( + [-1.33865287e-01 + 0.09802308j, 1.25060033e-01 + 0.16087698j, -4.14678130e-01 - 0.00774832j, + 1.10121136e-01 + 0.37805482j, -3.21284864e-01 + 0.21521063j, -2.23121454e-04 + 0.28417422j, + 5.64131205e-02 + 0.38135286j, 2.32694503e-01 + 0.41331133j], + [0, 1, 2], + [-1.33865287e-01 + 0.09802308j, 1.25060033e-01 + 0.16087698j, -4.14678130e-01 - 0.00774832j, + 1.10121136e-01 + 0.37805482j, -3.21284864e-01 + 0.21521063j, -2.23121454e-04 + 0.28417422j, + 5.64131205e-02 + 0.38135286j, 2.32694503e-01 + 0.41331133j], + ), + ([1 / 2, 0, 0, 0, 1j / 2, 0, 1j / np.sqrt(2), 0], [0, 1, 2], + [1 / 2, 0, 0, 0, 1j / 2, 0, 1j / np.sqrt(2), 0]), + ([1 / 2, 0, 1j / 2, 1j / np.sqrt(2)], [0, 1], [1 / 2, 0, 0, 0, 1j / 2, 0, 1j / np.sqrt(2), 0]), + ]) + # fmt: on + def test_state_preparation_probability_distribution( + self, tol, qubit_device_3_wires, state_vector, wires, target_state + ): + """Tests that the template produces states with correct probability distribution.""" + + @qml.qnode(qubit_device_3_wires) + def circuit(): + qml.templates.MottonenStatePreparation(state_vector, wires) + return qml.expval(qml.PauliZ(0)), qml.expval(qml.PauliZ(1)), qml.expval(qml.PauliZ(2)) + + circuit() + + state = circuit.device.state.ravel() + + probabilities = np.abs(state) ** 2 + target_probabilities = np.abs(target_state) ** 2 + + assert np.allclose(probabilities, target_probabilities, atol=tol, rtol=0) + + # fmt: off + + @pytest.mark.parametrize("state_vector, n_wires", [ + ([1 / 2, 1 / 2, 1 / 2, 1 / 2], 2), + ([1, 0, 0, 0], 2), + ([0, 1, 0, 0], 2), + ([0, 0, 0, 1], 2), + ([0, 1, 0, 0, 0, 0, 0, 0], 3), + ([0, 0, 0, 0, 1, 0, 0, 0], 3), + ([2 / 3, 0, 0, 0, 1 / 3, 0, 0, 2 / 3], 3), + ([1 / 2, 0, 0, 0, 1 / 2, 1 / 2, 1 / 2, 0], 3), + ([1 / 3, 0, 0, 0, 2 / 3, 2 / 3, 0, 0], 3), + ([2 / 3, 0, 0, 0, 1 / 3, 0, 0, 2 / 3], 3), + ]) + # fmt: on + def test_RZ_skipped(self, state_vector, n_wires): + """Tests that the cascade of RZ gates is skipped for real-valued states.""" + + n_CNOT = 2 ** n_wires - 2 + + dev = qml.device("default.qubit", wires=n_wires) + + @qml.qnode(dev) + def circuit(state_vector): + qml.templates.MottonenStatePreparation(state_vector, wires=range(n_wires)) + return qml.expval(qml.PauliX(wires=0)) + + # when the RZ cascade is skipped, CNOT gates should only be those required for RY cascade + circuit(state_vector) + + assert circuit.qtape.get_resources()["CNOT"] == n_CNOT + + def test_custom_wire_labels(self, tol): + """Test that template can deal with non-numeric, nonconsecutive wire labels.""" + state = np.array([1/2, 1/2, 0, 1/2, 0, 1/2, 0, 0]) + + dev = qml.device("default.qubit", wires=3) + dev2 = qml.device("default.qubit", wires=["z", "a", "k"]) + + @qml.qnode(dev) + def circuit(): + qml.templates.MottonenStatePreparation(state, wires=range(3)) + return qml.expval(qml.Identity(0)) + + @qml.qnode(dev2) + def circuit2(): + qml.templates.MottonenStatePreparation(state, 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.""" + + # fmt: off + @pytest.mark.parametrize("state_vector, wires", [ + ([1/2, 1/2], [0]), + ([2/3, 0, 2j/3, -2/3], [0, 1]), + ]) + # fmt: on + def test_error_state_vector_not_normalized(self, state_vector, wires): + """Tests that the correct error messages is raised if + the given state vector is not normalized.""" + + with pytest.raises(ValueError, match="State vector has to be of norm"): + qml.templates.MottonenStatePreparation(state_vector, wires) + + # fmt: off + @pytest.mark.parametrize("state_vector,wires", [ + ([0, 1, 0], [0, 1]), + ([0, 1, 0, 0, 0], [0]), + ]) + # fmt: on + def test_error_num_entries(self, state_vector, wires): + """Tests that the correct error messages is raised if + the number of entries in the given state vector does not match + with the number of wires in the system.""" + + with pytest.raises(ValueError, match="State vector must be of (length|shape)"): + qml.templates.MottonenStatePreparation(state_vector, wires) + + @pytest.mark.parametrize("state_vector", [ + ([[0, 0, 1, 0]]), + ([[0, 1], [1, 0], [0, 0], [0, 0]]), + ]) + def test_exception_wrong_shape(self, state_vector): + """Verifies that exception is raised if the + number of dimensions of features is incorrect.""" + + with pytest.raises(ValueError, match="State vector must be a one-dimensional"): + qml.templates.MottonenStatePreparation(state_vector, 2) + + +class TestGradient: + """Tests gradients.""" + + # TODO: Currently the template fails for more elaborate gradient + # tests, i.e. when the state contains zeros. + # Make the template fully differentiable and test it. + + @pytest.mark.parametrize( + "state_vector", [np.array([0.70710678, 0.70710678]), np.array([0.70710678, 0.70710678j])] + ) + def test_gradient_evaluated(self, state_vector): + """Test that the gradient is successfully calculated for a simple example. This test only + checks that the gradient is calculated without an error.""" + dev = qml.device("default.qubit", wires=1) + + @qml.qnode(dev) + def circuit(state_vector): + qml.templates.MottonenStatePreparation(state_vector, wires=range(1)) + return qml.expval(qml.PauliZ(0)) + + qml.grad(circuit)(state_vector) diff --git a/tests/templates/test_subroutines.py b/tests/templates/test_subroutines.py index ef3f4a8e9a7..1bc9dbacd5a 100644 --- a/tests/templates/test_subroutines.py +++ b/tests/templates/test_subroutines.py @@ -1572,7 +1572,7 @@ def test_phase_estimated_two_qubit(self): ) qml.probs(estimation_wires) - tape = tape.expand() + tape = tape.expand(depth=2) res = tape.execute(dev).flatten() if phase < 0: