diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 7be85112af2..74910e463b0 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -310,6 +310,9 @@ array([False, False])
Bug fixes 🐛
+* `qml.Projector` is pickle-able again.
+ [(#4452)](https://github.com/PennyLaneAI/pennylane/pull/4452)
+
* Allow sparse matrix calculation of `SProd`s containing a `Tensor`. When using
`Tensor.sparse_matrix()`, it is recommended to use the `wire_order` keyword argument over `wires`.
[(#4424)](https://github.com/PennyLaneAI/pennylane/pull/4424)
diff --git a/pennylane/_qubit_device.py b/pennylane/_qubit_device.py
index 6a8369bca59..15ee883a947 100644
--- a/pennylane/_qubit_device.py
+++ b/pennylane/_qubit_device.py
@@ -63,6 +63,7 @@
VnEntropyMP,
Shots,
)
+from pennylane.ops.qubit.observables import BasisStateProjector
from pennylane.resource import Resources
from pennylane.operation import operation_derivative, Operation
from pennylane.tape import QuantumScript, QuantumTape
@@ -1674,9 +1675,7 @@ def marginal_prob(self, prob, wires=None):
return self._reshape(prob, flat_shape)
def expval(self, observable, shot_range=None, bin_size=None):
- if observable.name == "Projector" and len(observable.parameters[0]) == len(
- observable.wires
- ):
+ if isinstance(observable, BasisStateProjector):
# branch specifically to handle the basis state projector observable
idx = int("".join(str(i) for i in observable.parameters[0]), 2)
probs = self.probability(
@@ -1706,9 +1705,7 @@ def expval(self, observable, shot_range=None, bin_size=None):
return np.squeeze(np.mean(samples, axis=axis))
def var(self, observable, shot_range=None, bin_size=None):
- if observable.name == "Projector" and len(observable.parameters[0]) == len(
- observable.wires
- ):
+ if isinstance(observable, BasisStateProjector):
# branch specifically to handle the basis state projector observable
idx = int("".join(str(i) for i in observable.parameters[0]), 2)
probs = self.probability(
diff --git a/pennylane/measurements/expval.py b/pennylane/measurements/expval.py
index e696546ff56..883c6598103 100644
--- a/pennylane/measurements/expval.py
+++ b/pennylane/measurements/expval.py
@@ -19,7 +19,7 @@
import pennylane as qml
from pennylane.operation import Operator
-from pennylane.ops import Projector
+from pennylane.ops.qubit.observables import BasisStateProjector
from pennylane.wires import Wires
from .measurements import Expectation, SampleMeasurement, StateMeasurement
@@ -104,8 +104,8 @@ def process_samples(
shot_range: Tuple[int] = None,
bin_size: int = None,
):
- if isinstance(self.obs, Projector):
- # branch specifically to handle the projector observable
+ if isinstance(self.obs, BasisStateProjector):
+ # branch specifically to handle the basis state projector observable
idx = int("".join(str(i) for i in self.obs.parameters[0]), 2)
probs = qml.probs(wires=self.wires).process_samples(
samples=samples, wire_order=wire_order, shot_range=shot_range, bin_size=bin_size
@@ -122,8 +122,8 @@ def process_samples(
return qml.math.squeeze(qml.math.mean(samples, axis=axis))
def process_state(self, state: Sequence[complex], wire_order: Wires):
- if isinstance(self.obs, Projector):
- # branch specifically to handle the projector observable
+ if isinstance(self.obs, BasisStateProjector):
+ # branch specifically to handle the basis state projector observable
idx = int("".join(str(i) for i in self.obs.parameters[0]), 2)
probs = qml.probs(wires=self.wires).process_state(state=state, wire_order=wire_order)
return probs[idx]
diff --git a/pennylane/measurements/var.py b/pennylane/measurements/var.py
index 60fd35da323..568285b124e 100644
--- a/pennylane/measurements/var.py
+++ b/pennylane/measurements/var.py
@@ -20,7 +20,7 @@
import pennylane as qml
from pennylane.operation import Operator
-from pennylane.ops import Projector
+from pennylane.ops.qubit.observables import BasisStateProjector
from pennylane.wires import Wires
from .measurements import SampleMeasurement, StateMeasurement, Variance
@@ -104,8 +104,8 @@ def process_samples(
shot_range: Tuple[int] = None,
bin_size: int = None,
):
- if isinstance(self.obs, Projector):
- # branch specifically to handle the projector observable
+ if isinstance(self.obs, BasisStateProjector):
+ # branch specifically to handle the basis state projector observable
idx = int("".join(str(i) for i in self.obs.parameters[0]), 2)
# we use ``self.wires`` instead of ``self.obs`` because the observable was
# already applied before the sampling
@@ -125,8 +125,8 @@ def process_samples(
return qml.math.squeeze(qml.math.var(samples, axis=axis))
def process_state(self, state: Sequence[complex], wire_order: Wires):
- if isinstance(self.obs, Projector):
- # branch specifically to handle the projector observable
+ if isinstance(self.obs, BasisStateProjector):
+ # branch specifically to handle the basis state projector observable
idx = int("".join(str(i) for i in self.obs.parameters[0]), 2)
# we use ``self.wires`` instead of ``self.obs`` because the observable was
# already applied to the state
diff --git a/pennylane/ops/qubit/observables.py b/pennylane/ops/qubit/observables.py
index 9246de99dc7..823e2250755 100644
--- a/pennylane/ops/qubit/observables.py
+++ b/pennylane/ops/qubit/observables.py
@@ -365,11 +365,10 @@ class Projector(Observable):
0.25
"""
+ name = "Projector"
num_wires = AnyWires
num_params = 1
"""int: Number of trainable parameters that the operator depends on."""
- _basis_state_type = None # type if Projector inherits from _BasisStateProjector
- _state_vector_type = None # type if Projector inherits from _StateVectorProjector
def __new__(cls, state, wires, **_):
"""Changes parents based on the state representation.
@@ -396,16 +395,10 @@ def __new__(cls, state, wires, **_):
raise ValueError(f"Input state must be one-dimensional; got shape {shape}.")
if len(state) == len(wires):
- if cls._basis_state_type is None:
- base_cls = (_BasisStateProjector, Projector)
- cls._basis_state_type = type("Projector", base_cls, dict(cls.__dict__))
- return object.__new__(cls._basis_state_type)
+ return object.__new__(BasisStateProjector)
if len(state) == 2 ** len(wires):
- if cls._state_vector_type is None:
- base_cls = (_StateVectorProjector, Projector)
- cls._state_vector_type = type("Projector", base_cls, dict(cls.__dict__))
- return object.__new__(cls._state_vector_type)
+ return object.__new__(StateVectorProjector)
raise ValueError(
"Input state should have the same length as the wires in the case "
@@ -418,28 +411,25 @@ def pow(self, z):
"""Raise this projector to the power ``z``."""
return [copy(self)] if (isinstance(z, int) and z > 0) else super().pow(z)
- def __copy__(self):
- copied_op = self.__new__(Projector, self.data[0], self.wires)
- copied_op.data = copy(self.data)
- for attr, value in vars(self).items():
- if attr != "data":
- setattr(copied_op, attr, value)
- return copied_op
+class BasisStateProjector(Projector):
+ r"""Observable corresponding to the state projector :math:`P=\ket{\phi}\bra{\phi}`, where
+ :math:`\phi` denotes a basis state."""
-
-class _BasisStateProjector(Observable):
# The call signature should be the same as Projector.__new__ for the positional
# arguments, but with free key word arguments.
def __init__(self, state, wires, id=None):
wires = Wires(wires)
- state = list(qml.math.toarray(state))
+ state = list(qml.math.toarray(state).astype(int))
if not set(state).issubset({0, 1}):
raise ValueError(f"Basis state must only consist of 0s and 1s; got {state}")
super().__init__(state, wires=wires, id=id)
+ def __new__(cls): # pylint: disable=arguments-differ
+ return object.__new__(cls)
+
def label(self, decimals=None, base_label=None, cache=None):
r"""A customizable string representation of the operator.
@@ -455,7 +445,7 @@ def label(self, decimals=None, base_label=None, cache=None):
**Example:**
- >>> _BasisStateProjector([0, 1, 0], wires=(0, 1, 2)).label()
+ >>> BasisStateProjector([0, 1, 0], wires=(0, 1, 2)).label()
'|010⟩⟨010|'
"""
@@ -472,7 +462,7 @@ def compute_matrix(basis_state): # pylint: disable=arguments-differ
The canonical matrix is the textbook matrix representation that does not consider wires.
Implicitly, this assumes that the wires of the operator correspond to the global wire order.
- .. seealso:: :meth:`~._BasisStateProjector.matrix`
+ .. seealso:: :meth:`~.BasisStateProjector.matrix`
Args:
basis_state (Iterable): basis state to project on
@@ -482,7 +472,7 @@ def compute_matrix(basis_state): # pylint: disable=arguments-differ
**Example**
- >>> _BasisStateProjector.compute_matrix([0, 1])
+ >>> BasisStateProjector.compute_matrix([0, 1])
[[0. 0. 0. 0.]
[0. 1. 0. 0.]
[0. 0. 0. 0.]
@@ -506,7 +496,7 @@ def compute_eigvals(basis_state): # pylint: disable=arguments-differ
Otherwise, no particular order for the eigenvalues is guaranteed.
- .. seealso:: :meth:`~._BasisStateProjector.eigvals`
+ .. seealso:: :meth:`~.BasisStateProjector.eigvals`
Args:
basis_state (Iterable): basis state to project on
@@ -516,7 +506,7 @@ def compute_eigvals(basis_state): # pylint: disable=arguments-differ
**Example**
- >>> _BasisStateProjector.compute_eigvals([0, 1])
+ >>> BasisStateProjector.compute_eigvals([0, 1])
[0. 1. 0. 0.]
"""
w = np.zeros(2 ** len(basis_state))
@@ -537,7 +527,7 @@ def compute_diagonalizing_gates(
The diagonalizing gates rotate the state into the eigenbasis
of the operator.
- .. seealso:: :meth:`~._BasisStateProjector.diagonalizing_gates`.
+ .. seealso:: :meth:`~.BasisStateProjector.diagonalizing_gates`.
Args:
basis_state (Iterable): basis state that the operator projects on
@@ -547,13 +537,16 @@ def compute_diagonalizing_gates(
**Example**
- >>> _BasisStateProjector.compute_diagonalizing_gates([0, 1, 0, 0], wires=[0, 1])
+ >>> BasisStateProjector.compute_diagonalizing_gates([0, 1, 0, 0], wires=[0, 1])
[]
"""
return []
-class _StateVectorProjector(Observable):
+class StateVectorProjector(Projector):
+ r"""Observable corresponding to the state projector :math:`P=\ket{\phi}\bra{\phi}`, where
+ :math:`\phi` denotes a state."""
+
# The call signature should be the same as Projector.__new__ for the positional
# arguments, but with free key word arguments.
def __init__(self, state, wires, id=None):
@@ -562,6 +555,9 @@ def __init__(self, state, wires, id=None):
super().__init__(state, wires=wires, id=id)
+ def __new__(cls): # pylint: disable=arguments-differ
+ return object.__new__(cls)
+
def label(self, decimals=None, base_label=None, cache=None):
r"""A customizable string representation of the operator.
@@ -578,14 +574,14 @@ def label(self, decimals=None, base_label=None, cache=None):
**Example:**
>>> state_vector = np.array([0, 1, 1, 0])/np.sqrt(2)
- >>> _StateVectorProjector(state_vector, wires=(0, 1)).label()
+ >>> StateVectorProjector(state_vector, wires=(0, 1)).label()
'P'
- >>> _StateVectorProjector(state_vector, wires=(0, 1)).label(base_label="hi!")
+ >>> StateVectorProjector(state_vector, wires=(0, 1)).label(base_label="hi!")
'hi!'
>>> dev = qml.device("default.qubit", wires=1)
>>> @qml.qnode(dev)
>>> def circuit(state):
- ... return qml.expval(_StateVectorProjector(state, [0]))
+ ... return qml.expval(StateVectorProjector(state, [0]))
>>> print(qml.draw(circuit)([1, 0]))
0: ───┤ <|0⟩⟨0|>
>>> print(qml.draw(circuit)(np.array([1, 1]) / np.sqrt(2)))
@@ -631,7 +627,7 @@ def compute_matrix(state_vector): # pylint: disable=arguments-differ,arguments-
The projector of the state :math:`\frac{1}{\sqrt{2}}(\ket{01}+\ket{10})`
- >>> _StateVectorProjector.compute_matrix([0, 1/np.sqrt(2), 1/np.sqrt(2), 0])
+ >>> StateVectorProjector.compute_matrix([0, 1/np.sqrt(2), 1/np.sqrt(2), 0])
[[0. 0. 0. 0.]
[0. 0.5 0.5 0.]
[0. 0.5 0.5 0.]
@@ -652,7 +648,7 @@ def compute_eigvals(state_vector): # pylint: disable=arguments-differ,arguments
Otherwise, no particular order for the eigenvalues is guaranteed.
- .. seealso:: :meth:`~._StateVectorProjector.eigvals`
+ .. seealso:: :meth:`~.StateVectorProjector.eigvals`
Args:
state_vector (Iterable): state vector to project on
@@ -662,7 +658,7 @@ def compute_eigvals(state_vector): # pylint: disable=arguments-differ,arguments
**Example**
- >>> _StateVectorProjector.compute_eigvals([0, 0, 1, 0])
+ >>> StateVectorProjector.compute_eigvals([0, 0, 1, 0])
array([1, 0, 0, 0])
"""
w = qml.math.zeros_like(state_vector)
@@ -682,7 +678,7 @@ def compute_diagonalizing_gates(
The diagonalizing gates rotate the state into the eigenbasis
of the operator.
- .. seealso:: :meth:`~._StateVectorProjector.diagonalizing_gates`.
+ .. seealso:: :meth:`~.StateVectorProjector.diagonalizing_gates`.
Args:
state_vector (Iterable): state vector that the operator projects on.
@@ -693,7 +689,7 @@ def compute_diagonalizing_gates(
**Example**
>>> state_vector = np.array([1., 1j])/np.sqrt(2)
- >>> _StateVectorProjector.compute_diagonalizing_gates(state_vector, wires=[0])
+ >>> StateVectorProjector.compute_diagonalizing_gates(state_vector, wires=[0])
[QubitUnitary(array([[ 0.70710678+0.j , 0. -0.70710678j],
[ 0. +0.70710678j, -0.70710678+0.j ]]), wires=[0])]
"""
diff --git a/tests/devices/experimental/test_default_qubit_2.py b/tests/devices/experimental/test_default_qubit_2.py
index e381fc63eb8..8beb9a45fff 100644
--- a/tests/devices/experimental/test_default_qubit_2.py
+++ b/tests/devices/experimental/test_default_qubit_2.py
@@ -1787,6 +1787,26 @@ def test_shot_vectors(self, max_workers, n_qubits, shots):
assert np.all(np.logical_or(np.logical_or(r[1] == 0, r[1] == 1), r[1] == 2))
+class TestDynamicType:
+ """Tests the compatibility with dynamic type classes such as `qml.Projector`."""
+
+ @pytest.mark.parametrize("n_wires", [1, 2, 3])
+ @pytest.mark.parametrize("max_workers", [None, 1, 2])
+ def test_projector(self, max_workers, n_wires):
+ """Test that qml.Projector yields the expected results for both of its subclasses."""
+ wires = list(range(n_wires))
+ dev = DefaultQubit2(max_workers=max_workers)
+ ops = [qml.Hadamard(q) for q in wires]
+ basis_state = np.zeros((n_wires,))
+ state_vector = np.zeros((2**n_wires,))
+ state_vector[0] = 1
+
+ for state in [basis_state, state_vector]:
+ qs = qml.tape.QuantumScript(ops, [qml.expval(qml.Projector(state, wires))])
+ res = dev.execute(qs)
+ assert np.isclose(res, 1 / 2**n_wires)
+
+
@pytest.mark.parametrize("max_workers", [None, 1, 2])
def test_broadcasted_parameter(max_workers):
"""Test that DefaultQubit2 handles broadcasted parameters as expected."""
diff --git a/tests/gradients/parameter_shift/test_parameter_shift.py b/tests/gradients/parameter_shift/test_parameter_shift.py
index 8aa6fe6cfa4..d2e9af06f72 100644
--- a/tests/gradients/parameter_shift/test_parameter_shift.py
+++ b/tests/gradients/parameter_shift/test_parameter_shift.py
@@ -2183,17 +2183,17 @@ def test_recycling_unshifted_tape_result(self):
# + 2 operations x 2 shifted positions + 1 unshifted term <--
assert len(tapes) == (2 * 2 + 1) + (2 * 2 + 1)
- def test_projector_variance(self, tol):
+ @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector
+ def test_projector_variance(self, state, tol):
"""Test that the variance of a projector is correctly returned"""
dev = qml.device("default.qubit", wires=2)
- P = np.array([1])
x, y = 0.765, -0.654
with qml.queuing.AnnotatedQueue() as q:
qml.RX(x, wires=0)
qml.RY(y, wires=1)
qml.CNOT(wires=[0, 1])
- qml.var(qml.Projector(P, wires=0) @ qml.PauliX(1))
+ qml.var(qml.Projector(state, wires=0) @ qml.PauliX(1))
tape = qml.tape.QuantumScript.from_queue(q)
tape.trainable_params = {0, 1}
@@ -2983,17 +2983,17 @@ def test_expval_and_variance(self, tol):
# assert gradA == pytest.approx(expected, abs=tol)
# assert gradF == pytest.approx(expected, abs=tol)
- def test_projector_variance(self, tol):
+ @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector
+ def test_projector_variance(self, state, tol):
"""Test that the variance of a projector is correctly returned"""
dev = qml.device("default.qubit", wires=2)
- P = np.array([1])
x, y = 0.765, -0.654
with qml.queuing.AnnotatedQueue() as q:
qml.RX(x, wires=0)
qml.RY(y, wires=1)
qml.CNOT(wires=[0, 1])
- qml.var(qml.Projector(P, wires=0) @ qml.PauliX(1))
+ qml.var(qml.Projector(state, wires=0) @ qml.PauliX(1))
tape = qml.tape.QuantumScript.from_queue(q)
tape.trainable_params = {0, 1}
diff --git a/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py b/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py
index f0e62b76e8f..c673d08cbb6 100644
--- a/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py
+++ b/tests/gradients/parameter_shift/test_parameter_shift_shot_vec.py
@@ -1835,18 +1835,18 @@ def test_expval_and_variance_multi_param(self):
for gradF in all_gradF:
assert gradF == pytest.approx(expected, abs=finite_diff_tol)
- def test_projector_variance(self):
+ @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector
+ def test_projector_variance(self, state):
"""Test that the variance of a projector is correctly returned"""
shot_vec = many_shots_shot_vector
dev = qml.device("default.qubit", wires=2, shots=shot_vec)
- P = np.array([1])
x, y = 0.765, -0.654
with qml.queuing.AnnotatedQueue() as q:
qml.RX(x, wires=0)
qml.RY(y, wires=1)
qml.CNOT(wires=[0, 1])
- qml.var(qml.Projector(P, wires=0) @ qml.PauliX(1))
+ qml.var(qml.Projector(state, wires=0) @ qml.PauliX(1))
tape = qml.tape.QuantumScript.from_queue(q, shots=shot_vec)
tape.trainable_params = {0, 1}
diff --git a/tests/interfaces/default_qubit_2_integration/test_autograd_qnode_default_qubit_2.py b/tests/interfaces/default_qubit_2_integration/test_autograd_qnode_default_qubit_2.py
index 1cefb4e5793..5932332c68d 100644
--- a/tests/interfaces/default_qubit_2_integration/test_autograd_qnode_default_qubit_2.py
+++ b/tests/interfaces/default_qubit_2_integration/test_autograd_qnode_default_qubit_2.py
@@ -1296,7 +1296,8 @@ def cost_fn(x, y):
assert res[1].shape == ()
assert np.allclose(res, expected, atol=tol, rtol=0)
- def test_projector(self, interface, dev, diff_method, grad_on_execution, tol):
+ @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector
+ def test_projector(self, state, interface, dev, diff_method, grad_on_execution, tol):
"""Test that the variance of a projector is correctly returned"""
kwargs = dict(
diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution
@@ -1310,7 +1311,7 @@ def test_projector(self, interface, dev, diff_method, grad_on_execution, tol):
elif diff_method == "hadamard":
pytest.skip("Hadamard gradient does not support variances.")
- P = np.array([1], requires_grad=False)
+ P = np.array(state, requires_grad=False)
x, y = np.array([0.765, -0.654], requires_grad=True)
@qnode(dev, **kwargs)
diff --git a/tests/interfaces/default_qubit_2_integration/test_jax_jit_qnode_default_qubit_2.py b/tests/interfaces/default_qubit_2_integration/test_jax_jit_qnode_default_qubit_2.py
index f0f9dc287b1..c5685328dbf 100644
--- a/tests/interfaces/default_qubit_2_integration/test_jax_jit_qnode_default_qubit_2.py
+++ b/tests/interfaces/default_qubit_2_integration/test_jax_jit_qnode_default_qubit_2.py
@@ -1230,7 +1230,8 @@ def cost_fn(x, y):
expected = np.array([-np.sin(x) * np.cos(y) / 2, -np.cos(x) * np.sin(y) / 2])
assert np.allclose(res, expected, atol=tol, rtol=0)
- def test_projector(self, dev, diff_method, grad_on_execution, interface, tol):
+ @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector
+ def test_projector(self, state, dev, diff_method, grad_on_execution, interface, tol):
"""Test that the variance of a projector is correctly returned"""
gradient_kwargs = {}
if diff_method == "adjoint":
@@ -1242,7 +1243,7 @@ def test_projector(self, dev, diff_method, grad_on_execution, interface, tol):
gradient_kwargs = {"h": H_FOR_SPSA}
tol = TOL_FOR_SPSA
- P = jax.numpy.array([1])
+ P = jax.numpy.array(state)
x, y = 0.765, -0.654
@qnode(
diff --git a/tests/interfaces/default_qubit_2_integration/test_jax_qnode_default_qubit_2.py b/tests/interfaces/default_qubit_2_integration/test_jax_qnode_default_qubit_2.py
index 82193eb33a8..a102b851bae 100644
--- a/tests/interfaces/default_qubit_2_integration/test_jax_qnode_default_qubit_2.py
+++ b/tests/interfaces/default_qubit_2_integration/test_jax_qnode_default_qubit_2.py
@@ -1151,7 +1151,8 @@ def cost_fn(x, y):
expected = np.array([-np.sin(x) * np.cos(y) / 2, -np.cos(x) * np.sin(y) / 2])
assert np.allclose(res, expected, atol=tol, rtol=0)
- def test_projector(self, dev, diff_method, grad_on_execution, interface, tol):
+ @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector
+ def test_projector(self, state, dev, diff_method, grad_on_execution, interface, tol):
"""Test that the variance of a projector is correctly returned"""
gradient_kwargs = {}
if diff_method == "adjoint":
@@ -1163,7 +1164,7 @@ def test_projector(self, dev, diff_method, grad_on_execution, interface, tol):
gradient_kwargs = {"h": H_FOR_SPSA}
tol = TOL_FOR_SPSA
- P = jax.numpy.array([1])
+ P = jax.numpy.array(state)
x, y = 0.765, -0.654
@qnode(
diff --git a/tests/interfaces/default_qubit_2_integration/test_tensorflow_qnode_default_qubit_2.py b/tests/interfaces/default_qubit_2_integration/test_tensorflow_qnode_default_qubit_2.py
index cf0b1fc2b2c..f7d722b3344 100644
--- a/tests/interfaces/default_qubit_2_integration/test_tensorflow_qnode_default_qubit_2.py
+++ b/tests/interfaces/default_qubit_2_integration/test_tensorflow_qnode_default_qubit_2.py
@@ -908,7 +908,8 @@ def cost_fn(x, y):
expected = [-np.sin(x) * np.cos(y) / 2, -np.cos(x) * np.sin(y) / 2]
assert np.allclose(grad, expected, atol=tol, rtol=0)
- def test_projector(self, dev, diff_method, grad_on_execution, tol, interface):
+ @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector
+ def test_projector(self, state, dev, diff_method, grad_on_execution, tol, interface):
"""Test that the variance of a projector is correctly returned"""
kwargs = dict(
diff_method=diff_method, grad_on_execution=grad_on_execution, interface=interface
@@ -922,7 +923,7 @@ def test_projector(self, dev, diff_method, grad_on_execution, tol, interface):
kwargs["num_directions"] = 20
tol = TOL_FOR_SPSA
- P = tf.constant([1])
+ P = tf.constant(state)
x, y = 0.765, -0.654
weights = tf.Variable([x, y], dtype=tf.float64)
diff --git a/tests/interfaces/default_qubit_2_integration/test_torch_qnode_default_qubit_2.py b/tests/interfaces/default_qubit_2_integration/test_torch_qnode_default_qubit_2.py
index 73752759be4..7e7c4fc1de5 100644
--- a/tests/interfaces/default_qubit_2_integration/test_torch_qnode_default_qubit_2.py
+++ b/tests/interfaces/default_qubit_2_integration/test_torch_qnode_default_qubit_2.py
@@ -1008,7 +1008,8 @@ def cost_fn(x, y):
)
assert torch.allclose(res, expected, atol=tol, rtol=0)
- def test_projector(self, interface, dev, diff_method, grad_on_execution, tol):
+ @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector
+ def test_projector(self, state, interface, dev, diff_method, grad_on_execution, tol):
"""Test that the variance of a projector is correctly returned"""
kwargs = dict(
diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution
@@ -1022,7 +1023,7 @@ def test_projector(self, interface, dev, diff_method, grad_on_execution, tol):
elif diff_method == "hadamard":
pytest.skip("Hadamard does not support variances.")
- P = torch.tensor([1], requires_grad=False)
+ P = torch.tensor(state, requires_grad=False)
x, y = 0.765, -0.654
weights = torch.tensor([x, y], requires_grad=True, dtype=torch.float64)
diff --git a/tests/interfaces/test_autograd_qnode.py b/tests/interfaces/test_autograd_qnode.py
index 30d96a8e449..2c2c7f7a98c 100644
--- a/tests/interfaces/test_autograd_qnode.py
+++ b/tests/interfaces/test_autograd_qnode.py
@@ -1433,7 +1433,8 @@ def cost_fn(x, y):
assert res[1].shape == ()
assert np.allclose(res, expected, atol=tol, rtol=0)
- def test_projector(self, interface, dev_name, diff_method, grad_on_execution, tol):
+ @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector
+ def test_projector(self, state, interface, dev_name, diff_method, grad_on_execution, tol):
"""Test that the variance of a projector is correctly returned"""
kwargs = dict(
diff_method=diff_method, interface=interface, grad_on_execution=grad_on_execution
@@ -1447,7 +1448,7 @@ def test_projector(self, interface, dev_name, diff_method, grad_on_execution, to
pytest.skip("Hadamard gradient does not support variances.")
dev = qml.device(dev_name, wires=2)
- P = np.array([1], requires_grad=False)
+ P = np.array(state, requires_grad=False)
x, y = np.array([0.765, -0.654], requires_grad=True)
@qnode(dev, **kwargs)
diff --git a/tests/interfaces/test_jax_jit_qnode.py b/tests/interfaces/test_jax_jit_qnode.py
index 4209e9912d1..add55abf1ac 100644
--- a/tests/interfaces/test_jax_jit_qnode.py
+++ b/tests/interfaces/test_jax_jit_qnode.py
@@ -1368,7 +1368,8 @@ def cost_fn(x, y):
expected = np.array([-np.sin(x) * np.cos(y) / 2, -np.cos(x) * np.sin(y) / 2])
assert np.allclose(res, expected, atol=tol, rtol=0)
- def test_projector(self, dev_name, diff_method, grad_on_execution, interface, tol):
+ @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector
+ def test_projector(self, state, dev_name, diff_method, grad_on_execution, interface, tol):
"""Test that the variance of a projector is correctly returned"""
gradient_kwargs = {}
if diff_method == "adjoint":
@@ -1381,7 +1382,7 @@ def test_projector(self, dev_name, diff_method, grad_on_execution, interface, to
tol = TOL_FOR_SPSA
dev = qml.device(dev_name, wires=2)
- P = jax.numpy.array([1])
+ P = jax.numpy.array(state)
x, y = 0.765, -0.654
@qnode(
diff --git a/tests/interfaces/test_jax_qnode.py b/tests/interfaces/test_jax_qnode.py
index 6cb97570ec7..127d467b099 100644
--- a/tests/interfaces/test_jax_qnode.py
+++ b/tests/interfaces/test_jax_qnode.py
@@ -1315,7 +1315,8 @@ def cost_fn(x, y):
expected = np.array([-np.sin(x) * np.cos(y) / 2, -np.cos(x) * np.sin(y) / 2])
assert np.allclose(res, expected, atol=tol, rtol=0)
- def test_projector(self, dev_name, diff_method, grad_on_execution, interface, tol):
+ @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector
+ def test_projector(self, state, dev_name, diff_method, grad_on_execution, interface, tol):
"""Test that the variance of a projector is correctly returned"""
gradient_kwargs = {}
if diff_method == "adjoint":
@@ -1328,7 +1329,7 @@ def test_projector(self, dev_name, diff_method, grad_on_execution, interface, to
tol = TOL_FOR_SPSA
dev = qml.device(dev_name, wires=2)
- P = jax.numpy.array([1])
+ P = jax.numpy.array(state)
x, y = 0.765, -0.654
@qnode(
diff --git a/tests/interfaces/test_tensorflow_qnode.py b/tests/interfaces/test_tensorflow_qnode.py
index 5758dbafc16..e8bca0f901b 100644
--- a/tests/interfaces/test_tensorflow_qnode.py
+++ b/tests/interfaces/test_tensorflow_qnode.py
@@ -1147,7 +1147,8 @@ def cost_fn(x, y):
expected = [-np.sin(x) * np.cos(y) / 2, -np.cos(x) * np.sin(y) / 2]
assert np.allclose(grad, expected, atol=tol, rtol=0)
- def test_projector(self, dev_name, diff_method, grad_on_execution, tol, interface):
+ @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector
+ def test_projector(self, state, dev_name, diff_method, grad_on_execution, tol, interface):
"""Test that the variance of a projector is correctly returned"""
kwargs = dict(
diff_method=diff_method, grad_on_execution=grad_on_execution, interface=interface
@@ -1161,7 +1162,7 @@ def test_projector(self, dev_name, diff_method, grad_on_execution, tol, interfac
tol = TOL_FOR_SPSA
dev = qml.device(dev_name, wires=2)
- P = tf.constant([1])
+ P = tf.constant(state)
x, y = 0.765, -0.654
weights = tf.Variable([x, y], dtype=tf.float64)
diff --git a/tests/interfaces/test_torch_qnode.py b/tests/interfaces/test_torch_qnode.py
index c3f95740f36..28bae2e9f2e 100644
--- a/tests/interfaces/test_torch_qnode.py
+++ b/tests/interfaces/test_torch_qnode.py
@@ -1198,7 +1198,8 @@ def cost_fn(x, y):
)
assert torch.allclose(res, expected, atol=tol, rtol=0)
- def test_projector(self, interface, dev_name, diff_method, grad_on_execution, tol):
+ @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector
+ def test_projector(self, state, interface, dev_name, diff_method, grad_on_execution, tol):
"""Test that the variance of a projector is correctly returned"""
kwargs = dict(
diff_method=diff_method, grad_on_execution=grad_on_execution, interface=interface
@@ -1212,7 +1213,7 @@ def test_projector(self, interface, dev_name, diff_method, grad_on_execution, to
pytest.skip("Hadamard does not support variances.")
dev = qml.device(dev_name, wires=2)
- P = torch.tensor([1], requires_grad=False)
+ P = torch.tensor(state, requires_grad=False)
x, y = 0.765, -0.654
weights = torch.tensor([x, y], requires_grad=True, dtype=torch.float64)
diff --git a/tests/legacy/test_legacy_autograd_qnode_old.py b/tests/legacy/test_legacy_autograd_qnode_old.py
index f9bdb596618..2800817e2ff 100644
--- a/tests/legacy/test_legacy_autograd_qnode_old.py
+++ b/tests/legacy/test_legacy_autograd_qnode_old.py
@@ -1206,7 +1206,8 @@ def cost_fn(x, y):
expected = np.array([-np.sin(x) * np.cos(y) / 2, -np.cos(x) * np.sin(y) / 2])
assert np.allclose(res, expected, atol=tol, rtol=0)
- def test_projector(self, interface, dev_name, diff_method, mode, tol):
+ @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector
+ def test_projector(self, state, interface, dev_name, diff_method, mode, tol):
"""Test that the variance of a projector is correctly returned"""
kwargs = dict(diff_method=diff_method, interface=interface, mode=mode)
if diff_method == "adjoint":
@@ -1216,7 +1217,7 @@ def test_projector(self, interface, dev_name, diff_method, mode, tol):
tol = TOL_FOR_SPSA
dev = qml.device(dev_name, wires=2)
- P = np.array([1], requires_grad=False)
+ P = np.array(state, requires_grad=False)
x, y = np.array([0.765, -0.654], requires_grad=True)
@qnode(dev, **kwargs)
diff --git a/tests/legacy/test_legacy_jax_qnode_old.py b/tests/legacy/test_legacy_jax_qnode_old.py
index 9384f55ff01..f44cf4a6db3 100644
--- a/tests/legacy/test_legacy_jax_qnode_old.py
+++ b/tests/legacy/test_legacy_jax_qnode_old.py
@@ -1041,7 +1041,8 @@ def cost_fn(x, y):
expected = np.array([-np.sin(x) * np.cos(y) / 2, -np.cos(x) * np.sin(y) / 2])
assert np.allclose(res, expected, atol=tol, rtol=0)
- def test_projector(self, dev_name, diff_method, mode, interface, tol):
+ @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector
+ def test_projector(self, state, dev_name, diff_method, mode, interface, tol):
"""Test that the variance of a projector is correctly returned"""
kwargs = dict(diff_method=diff_method, interface=interface, mode=mode)
if diff_method == "adjoint":
@@ -1051,7 +1052,7 @@ def test_projector(self, dev_name, diff_method, mode, interface, tol):
tol = TOL_FOR_SPSA
dev = qml.device(dev_name, wires=2)
- P = jax.numpy.array([1])
+ P = jax.numpy.array(state)
x, y = 0.765, -0.654
@qnode(dev, **kwargs)
diff --git a/tests/legacy/test_legacy_parameter_shift_old.py b/tests/legacy/test_legacy_parameter_shift_old.py
index ef54f4725a2..20ffd327452 100644
--- a/tests/legacy/test_legacy_parameter_shift_old.py
+++ b/tests/legacy/test_legacy_parameter_shift_old.py
@@ -1334,10 +1334,11 @@ def test_recycling_unshifted_tape_result(self):
# + 2 operations x 2 shifted positions + 1 unshifted term <--
assert len(tapes) == (2 * 2 + 1) + (2 * 2 + 1)
- def test_projector_variance(self, tol):
+ @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector
+ def test_projector_variance(self, state, tol):
"""Test that the variance of a projector is correctly returned"""
dev = qml.device("default.qubit", wires=2)
- P = np.array([1])
+ P = np.array(state)
x, y = 0.765, -0.654
with qml.queuing.AnnotatedQueue() as q:
@@ -2058,10 +2059,11 @@ def test_expval_and_variance(self, tol):
assert gradF == pytest.approx(expected, abs=tol)
"""
- def test_projector_variance(self, tol):
+ @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector
+ def test_projector_variance(self, state, tol):
"""Test that the variance of a projector is correctly returned"""
dev = qml.device("default.qubit", wires=2)
- P = np.array([1])
+ P = np.array(state)
x, y = 0.765, -0.654
with qml.queuing.AnnotatedQueue() as q:
diff --git a/tests/legacy/test_legacy_tensorflow_qnode_old.py b/tests/legacy/test_legacy_tensorflow_qnode_old.py
index d4e3b79f540..42e2a0fb7b9 100644
--- a/tests/legacy/test_legacy_tensorflow_qnode_old.py
+++ b/tests/legacy/test_legacy_tensorflow_qnode_old.py
@@ -965,7 +965,8 @@ def cost_fn(x, y):
expected = [-np.sin(x) * np.cos(y) / 2, -np.cos(x) * np.sin(y) / 2]
assert np.allclose(grad, expected, atol=tol, rtol=0)
- def test_projector(self, interface, dev_name, diff_method, mode, tol):
+ @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector
+ def test_projector(self, state, interface, dev_name, diff_method, mode, tol):
"""Test that the variance of a projector is correctly returned"""
kwargs = dict(diff_method=diff_method, interface=interface, mode=mode)
if diff_method == "adjoint":
@@ -975,7 +976,7 @@ def test_projector(self, interface, dev_name, diff_method, mode, tol):
tol = TOL_FOR_SPSA
dev = qml.device(dev_name, wires=2)
- P = tf.constant([1])
+ P = tf.constant(state)
x, y = 0.765, -0.654
weights = tf.Variable([x, y], dtype=tf.float64)
diff --git a/tests/legacy/test_legacy_torch_qnode_old.py b/tests/legacy/test_legacy_torch_qnode_old.py
index eac213e1a3a..16ee1af124a 100644
--- a/tests/legacy/test_legacy_torch_qnode_old.py
+++ b/tests/legacy/test_legacy_torch_qnode_old.py
@@ -974,7 +974,8 @@ def cost_fn(x, y):
)
assert torch.allclose(res, expected, atol=tol, rtol=0)
- def test_projector(self, interface, dev_name, diff_method, mode, tol):
+ @pytest.mark.parametrize("state", [[1], [0, 1]]) # Basis state and state vector
+ def test_projector(self, state, interface, dev_name, diff_method, mode, tol):
"""Test that the variance of a projector is correctly returned"""
kwargs = dict(diff_method=diff_method, interface=interface, mode=mode)
if diff_method == "adjoint":
@@ -984,7 +985,7 @@ def test_projector(self, interface, dev_name, diff_method, mode, tol):
tol = TOL_FOR_SPSA
dev = qml.device(dev_name, wires=2)
- P = torch.tensor([1], requires_grad=False)
+ P = torch.tensor(state, requires_grad=False)
x, y = 0.765, -0.654
weights = torch.tensor([x, y], requires_grad=True, dtype=torch.float64)
diff --git a/tests/measurements/test_expval.py b/tests/measurements/test_expval.py
index efcdc082c26..3d0e5c009b2 100644
--- a/tests/measurements/test_expval.py
+++ b/tests/measurements/test_expval.py
@@ -149,25 +149,24 @@ def test_shape_shot_vector(self, obs):
dev = qml.device("default.qubit", wires=3, shots=shot_vector)
assert res.shape(dev, Shots(shot_vector)) == ((), (), ())
+ @pytest.mark.parametrize("state", [np.array([0, 0, 0]), np.array([1, 0, 0, 0, 0, 0, 0, 0])])
@pytest.mark.parametrize("shots", [None, 1000, [1000, 10000]])
- def test_projector_expval(self, shots, mocker):
- """Tests that the expectation of a ``Projector`` object is computed correctly."""
+ def test_projector_expval(self, state, shots, mocker):
+ """Tests that the expectation of a ``Projector`` object is computed correctly for both of
+ its subclasses."""
dev = qml.device("default.qubit", wires=3, shots=shots)
np.random.seed(42)
- basis_state = np.array([0, 0, 0])
-
@qml.qnode(dev)
def circuit():
qml.Hadamard(0)
- return qml.expval(qml.Projector(basis_state, wires=range(3)))
+ return qml.expval(qml.Projector(state, wires=range(3)))
new_dev = circuit.device
spy = mocker.spy(qml.QubitDevice, "expval")
res = circuit()
expected = [0.5, 0.5] if isinstance(shots, list) else 0.5
-
assert np.allclose(res, expected, atol=0.02, rtol=0.02)
custom_measurement_process(new_dev, spy)
diff --git a/tests/measurements/test_var.py b/tests/measurements/test_var.py
index e32a08dea86..191e6937cfa 100644
--- a/tests/measurements/test_var.py
+++ b/tests/measurements/test_var.py
@@ -139,18 +139,17 @@ def test_shape_shot_vector(self, obs):
dev = qml.device("default.qubit", wires=3, shots=shot_vector)
assert res.shape(dev, Shots(shot_vector)) == ((), (), ())
+ @pytest.mark.parametrize("state", [np.array([0, 0, 0]), np.array([1, 0, 0, 0, 0, 0, 0, 0])])
@pytest.mark.parametrize("shots", [None, 1000, [1000, 10000]])
- def test_projector_var(self, shots, mocker):
+ def test_projector_var(self, state, shots, mocker):
"""Tests that the variance of a ``Projector`` object is computed correctly."""
dev = qml.device("default.qubit", wires=3, shots=shots)
spy = mocker.spy(qml.QubitDevice, "var")
- basis_state = np.array([0, 0, 0])
-
@qml.qnode(dev)
def circuit():
qml.Hadamard(0)
- return qml.var(qml.Projector(basis_state, wires=range(3)))
+ return qml.var(qml.Projector(state, wires=range(3)))
res = circuit()
expected = [0.25, 0.25] if isinstance(shots, list) else 0.25
diff --git a/tests/ops/qubit/test_observables.py b/tests/ops/qubit/test_observables.py
index e030e17014c..a52d6711e2c 100644
--- a/tests/ops/qubit/test_observables.py
+++ b/tests/ops/qubit/test_observables.py
@@ -14,12 +14,13 @@
"""Unit tests for qubit observables."""
# pylint: disable=protected-access
import functools
+import pickle
import pytest
import numpy as np
from gate_data import I, X, Y, Z, H
import pennylane as qml
-from pennylane.ops.qubit.observables import _BasisStateProjector, _StateVectorProjector
+from pennylane.ops.qubit.observables import BasisStateProjector, StateVectorProjector
@pytest.fixture(autouse=True)
@@ -467,7 +468,7 @@ def test_basisstate_projector(self):
basis_state = [0, 1, 1, 0]
wires = range(len(basis_state))
basis_state_projector = qml.Projector(basis_state, wires)
- assert isinstance(basis_state_projector, _BasisStateProjector)
+ assert isinstance(basis_state_projector, BasisStateProjector)
second_projector = qml.Projector(basis_state, wires)
assert qml.equal(second_projector, basis_state_projector)
@@ -477,7 +478,7 @@ def test_statevector_projector(self):
state_vector = np.array([1, 1, 1, 1]) / 2
wires = [0, 1]
state_vector_projector = qml.Projector(state_vector, wires)
- assert isinstance(state_vector_projector, _StateVectorProjector)
+ assert isinstance(state_vector_projector, StateVectorProjector)
second_projector = qml.Projector(state_vector, wires)
assert qml.equal(second_projector, state_vector_projector)
@@ -518,6 +519,25 @@ def test_exception_bad_input(self):
state = np.random.randint(2, size=(2, 4))
qml.Projector(state, range(4))
+ def test_serialization(self):
+ """Tests that Projector is pickle-able."""
+ # Basis state projector
+ proj = qml.Projector([1], wires=[0], id="Timmy")
+ serialization = pickle.dumps(proj)
+ new_proj = pickle.loads(serialization)
+ assert type(new_proj) is type(proj)
+ assert qml.equal(new_proj, proj)
+ assert new_proj.id == proj.id # Ensure they are identical
+
+ # State vector projector
+ proj = qml.Projector([0, 1], wires=[0])
+ serialization = pickle.dumps(proj)
+ new_proj = pickle.loads(serialization)
+
+ assert type(new_proj) is type(proj)
+ assert qml.equal(new_proj, proj)
+ assert new_proj.id == proj.id # Ensure they are identical
+
class TestBasisStateProjector:
"""Tests for the basis state projector observable."""
@@ -551,7 +571,7 @@ def test_projector_diagonalization(self, basis_state):
diag_gates = qml.Projector(basis_state, wires=range(num_wires)).diagonalizing_gates()
assert diag_gates == []
- diag_gates_static = _BasisStateProjector.compute_diagonalizing_gates(
+ diag_gates_static = BasisStateProjector.compute_diagonalizing_gates(
basis_state, wires=range(num_wires)
)
assert diag_gates_static == []
@@ -612,7 +632,7 @@ def circuit(basis_state):
def test_matrix_representation(self, basis_state, expected, n_wires, tol):
"""Test that the matrix representation is defined correctly"""
res_dynamic = qml.Projector(basis_state, wires=range(n_wires)).matrix()
- res_static = _BasisStateProjector.compute_matrix(basis_state)
+ res_static = BasisStateProjector.compute_matrix(basis_state)
assert np.allclose(res_dynamic, expected, atol=tol)
assert np.allclose(res_static, expected, atol=tol)
@@ -637,7 +657,7 @@ def test_projector_diagonalization(self, state_vector, tol):
num_wires = np.log2(len(state_vector)).astype(int)
proj = qml.Projector(state_vector, wires=range(num_wires))
diag_gates = proj.diagonalizing_gates()
- diag_gates_static = _StateVectorProjector.compute_diagonalizing_gates(
+ diag_gates_static = StateVectorProjector.compute_diagonalizing_gates(
state_vector, wires=range(num_wires)
)
@@ -654,7 +674,7 @@ def test_matrix_representation(self, state_vector, expected, tol):
"""Test that the matrix representation is defined correctly"""
num_wires = np.log2(len(state_vector)).astype(int)
res_dynamic = qml.Projector(state_vector, wires=range(num_wires)).matrix()
- res_static = _StateVectorProjector.compute_matrix(state_vector)
+ res_static = StateVectorProjector.compute_matrix(state_vector)
assert np.allclose(res_dynamic, expected, atol=tol)
assert np.allclose(res_static, expected, atol=tol)