diff --git a/doc/conf.py b/doc/conf.py
index 153ca25757a..ac11bfacb8e 100644
--- a/doc/conf.py
+++ b/doc/conf.py
@@ -161,7 +161,7 @@
# Add any extra paths that contain custom files (such as robots.txt or
# .htaccess) here, relative to this directory. These files are copied
# directly to the root of the documentation.
-# html_extra_path = []
+html_extra_path = ["robots.txt"]
# If not "", a "Last updated on:" timestamp is inserted at every page bottom,
# using the given strftime format.
diff --git a/doc/releases/changelog-dev.md b/doc/releases/changelog-dev.md
index 230904748ab..bb4a9d4b84a 100644
--- a/doc/releases/changelog-dev.md
+++ b/doc/releases/changelog-dev.md
@@ -191,6 +191,10 @@
[(#4391)](https://github.com/PennyLaneAI/pennylane/pull/4391)
Bug fixes 🐛
+
+* 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)
* Replace deprecated `jax.ad` by `jax.interpreters.ad`.
[(#4403)](https://github.com/PennyLaneAI/pennylane/pull/4403)
@@ -232,6 +236,10 @@
not really have a decomposition.
[(#4407)](https://github.com/PennyLaneAI/pennylane/pull/4407)
+* `qml.transforms.split_non_commuting` now correctly works on tapes containing both `expval`
+ and `var` measurements.
+ [(#4426)](https://github.com/PennyLaneAI/pennylane/pull/4426)
+
Contributors ✍️
This release contains contributions from (in alphabetical order):
diff --git a/doc/robots.txt b/doc/robots.txt
new file mode 100644
index 00000000000..bdf20e33b80
--- /dev/null
+++ b/doc/robots.txt
@@ -0,0 +1,7 @@
+User-agent: *
+
+Disallow: /en/v0.30.0/ # Hidden version
+
+Disallow: /en/latest/ # Not hidden, but disallow from search engine results
+
+Sitemap: https://docs.pennylane.ai/sitemap.xml
diff --git a/pennylane/devices/default_qubit.py b/pennylane/devices/default_qubit.py
index ed33195fbae..0a7d739a879 100644
--- a/pennylane/devices/default_qubit.py
+++ b/pennylane/devices/default_qubit.py
@@ -614,7 +614,7 @@ def expval(self, observable, shot_range=None, bin_size=None):
# that the user provided.
for op, coeff in zip(observable.ops, observable.data):
# extract a scipy.sparse.coo_matrix representation of this Pauli word
- coo = qml.operation.Tensor(op).sparse_matrix(wires=self.wires, format="coo")
+ coo = qml.operation.Tensor(op).sparse_matrix(wire_order=self.wires, format="coo")
Hmat = qml.math.cast(qml.math.convert_like(coo.data, self.state), self.C_DTYPE)
product = (
diff --git a/pennylane/operation.py b/pennylane/operation.py
index 16920b5213c..94fba8417a2 100644
--- a/pennylane/operation.py
+++ b/pennylane/operation.py
@@ -2404,7 +2404,7 @@ def check_wires_partial_overlap(self):
return 0
def sparse_matrix(
- self, wires=None, format="csr"
+ self, wire_order=None, wires=None, format="csr"
): # pylint:disable=arguments-renamed, arguments-differ
r"""Computes, by default, a `scipy.sparse.csr_matrix` representation of this Tensor.
@@ -2412,10 +2412,15 @@ def sparse_matrix(
consisting mostly of zero entries.
Args:
- wires (Iterable): Wire labels that indicate the order of wires according to which the matrix
+ wire_order (Iterable): Wire labels that indicate the order of wires according to which the matrix
is constructed. If not provided, ``self.wires`` is used.
+ wires (Iterable): Same as ``wire_order`` to ensure compatibility with all the classes. Must only
+ provide one: either ``wire_order`` or ``wires``.
format: the output format for the sparse representation. All scipy sparse formats are accepted.
+ Raises:
+ ValueError: if both ``wire_order`` and ``wires`` are provided at the same time.
+
Returns:
:class:`scipy.sparse._csr.csr_matrix`: sparse matrix representation
@@ -2435,7 +2440,7 @@ def sparse_matrix(
If we define a custom wire ordering, the matrix representation changes
accordingly:
- >>> print(t.sparse_matrix(wires=[1, 0]))
+ >>> print(t.sparse_matrix(wire_order=[1, 0]))
(0, 1) 1
(1, 0) 1
(2, 3) -1
@@ -2444,11 +2449,17 @@ def sparse_matrix(
We can also enforce implicit identities by passing wire labels that
are not present in the constituent operations:
- >>> res = t.sparse_matrix(wires=[0, 1, 2])
+ >>> res = t.sparse_matrix(wire_order=[0, 1, 2])
>>> print(res.shape)
(8, 8)
"""
+ if wires is not None and wire_order is not None:
+ raise ValueError(
+ "Wire order has been specified twice. Provide only one of either "
+ "``wire_order`` or ``wires``, but not both."
+ )
+ wires = wires or wire_order
wires = self.wires if wires is None else Wires(wires)
list_of_sparse_ops = [eye(2, format="coo")] * len(wires)
diff --git a/pennylane/transforms/split_non_commuting.py b/pennylane/transforms/split_non_commuting.py
index 463fd90a073..1e05efdde28 100644
--- a/pennylane/transforms/split_non_commuting.py
+++ b/pennylane/transforms/split_non_commuting.py
@@ -164,10 +164,10 @@ def circuit0(x):
if len(groups) > 1:
# make one tape per commuting group
tapes = []
- for group in groups:
+ for group, indices in zip(groups, group_coeffs):
new_tape = tape.__class__(
tape._ops,
- (m.__class__(obs=o) for m, o in zip(tape.measurements, group)),
+ (tape.measurements[i].__class__(obs=o) for o, i in zip(group, indices)),
tape._prep,
)
diff --git a/tests/test_operation.py b/tests/test_operation.py
index 38941929c8c..13c93332bc9 100644
--- a/tests/test_operation.py
+++ b/tests/test_operation.py
@@ -1856,32 +1856,58 @@ def test_sparse_matrix_swapped_wires(self):
when the custom wires swap the order."""
t = qml.PauliX(0) @ qml.PauliZ(1)
+ data = [1, 1, -1, -1]
+ indices = [1, 0, 3, 2]
+ indptr = [0, 1, 2, 3, 4]
+
s = t.sparse_matrix(wires=[1, 0])
- assert np.allclose(s.data, [1, 1, -1, -1])
- assert np.allclose(s.indices, [1, 0, 3, 2])
- assert np.allclose(s.indptr, [0, 1, 2, 3, 4])
+ assert np.allclose(s.data, data)
+ assert np.allclose(s.indices, indices)
+ assert np.allclose(s.indptr, indptr)
+
+ s = t.sparse_matrix(wire_order=[1, 0])
+
+ assert np.allclose(s.data, data)
+ assert np.allclose(s.indices, indices)
+ assert np.allclose(s.indptr, indptr)
def test_sparse_matrix_extra_wire(self):
"""Tests that the correct sparse matrix representation is used
when the custom wires add an extra wire with an implied identity operation."""
t = qml.PauliX(0) @ qml.PauliZ(1)
+ data = [1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0]
+ indices = [4, 5, 6, 7, 0, 1, 2, 3]
+ indptr = [0, 1, 2, 3, 4, 5, 6, 7, 8]
+
s = t.sparse_matrix(wires=[0, 1, 2])
assert s.shape == (8, 8)
- assert np.allclose(s.data, [1.0, 1.0, -1.0, -1.0, 1.0, 1.0, -1.0, -1.0])
- assert np.allclose(s.indices, [4, 5, 6, 7, 0, 1, 2, 3])
- assert np.allclose(s.indptr, [0, 1, 2, 3, 4, 5, 6, 7, 8])
+ assert np.allclose(s.data, data)
+ assert np.allclose(s.indices, indices)
+ assert np.allclose(s.indptr, indptr)
- def test_sparse_matrix_error(self):
- """Tests that an error is raised if the sparse matrix is computed for
- a tensor whose constituent operations are not all single-qubit gates."""
+ s = t.sparse_matrix(wire_order=[0, 1, 2])
+
+ assert s.shape == (8, 8)
+ assert np.allclose(s.data, data)
+ assert np.allclose(s.indices, indices)
+ assert np.allclose(s.indptr, indptr)
+
+ def test_sparse_matrix_errors(self):
+ """Tests that errors are raised when the sparse matrix is computed for a tensor
+ whose constituent operations are not all single-qubit gates, and when both ``wires``
+ and ``wire_order`` at specified at once."""
t = qml.PauliX(0) @ qml.Hermitian(np.eye(4), wires=[1, 2])
with pytest.raises(ValueError, match="Can only compute"):
t.sparse_matrix()
+ t = qml.PauliX(0) @ qml.PauliZ(1)
+ with pytest.raises(ValueError, match="Wire order has been specified twice"):
+ t.sparse_matrix(wires=[0, 1], wire_order=[0, 1])
+
def test_copy(self):
"""Test copying of a Tensor."""
tensor = Tensor(qml.PauliX(0), qml.PauliY(1), qml.PauliZ(2))
diff --git a/tests/transforms/test_split_non_commuting.py b/tests/transforms/test_split_non_commuting.py
index fec09baa760..3cbc08fc197 100644
--- a/tests/transforms/test_split_non_commuting.py
+++ b/tests/transforms/test_split_non_commuting.py
@@ -127,6 +127,36 @@ def test_different_measurement_types(self, meas_type):
for meas in new_tape.measurements:
assert meas.return_type == the_return_type
+ def test_mixed_measurement_types(self):
+ """Test that mixing expval and var works correctly."""
+
+ with qml.queuing.AnnotatedQueue() as q:
+ qml.Hadamard(0)
+ qml.Hadamard(1)
+ qml.expval(qml.PauliX(0))
+ qml.expval(qml.PauliZ(1))
+ qml.var(qml.PauliZ(0))
+
+ tape = qml.tape.QuantumScript.from_queue(q)
+ split, _ = split_non_commuting(tape)
+
+ assert len(split) == 2
+
+ with qml.queuing.AnnotatedQueue() as q:
+ qml.Hadamard(0)
+ qml.Hadamard(1)
+ qml.expval(qml.PauliX(0))
+ qml.var(qml.PauliZ(0))
+ qml.expval(qml.PauliZ(1))
+
+ tape = qml.tape.QuantumScript.from_queue(q)
+ split, _ = split_non_commuting(tape)
+
+ assert len(split) == 2
+ assert qml.equal(split[0].measurements[0], qml.expval(qml.PauliX(0)))
+ assert qml.equal(split[0].measurements[1], qml.expval(qml.PauliZ(1)))
+ assert qml.equal(split[1].measurements[0], qml.var(qml.PauliZ(0)))
+
def test_raise_not_supported(self):
"""Test that NotImplementedError is raised when probabilities or samples are called"""
with qml.queuing.AnnotatedQueue() as q: