Skip to content

Commit

Permalink
Adding a wire_order kwarg to Tensor.sparse_matrix() (#4424)
Browse files Browse the repository at this point in the history
* `wire_order` kwarg for `Tensor.sparse_matrix()`

* update changelog

* swap kwarg order

Co-authored-by: Matthew Silverman <[email protected]>

* adapt docstring to the kwarg order swap

* add comment about using  over

---------

Co-authored-by: Matthew Silverman <[email protected]>
  • Loading branch information
BorjaRequena and timmysilv authored Aug 2, 2023
1 parent a9e4087 commit 1baad70
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 14 deletions.
4 changes: 4 additions & 0 deletions doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,10 @@
[(#4391)](https://github.com/PennyLaneAI/pennylane/pull/4391)

<h3>Bug fixes 🐛</h3>

* 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)
Expand Down
2 changes: 1 addition & 1 deletion pennylane/devices/default_qubit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = (
Expand Down
19 changes: 15 additions & 4 deletions pennylane/operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -2404,18 +2404,23 @@ 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.
This is useful for larger qubit numbers, where the dense matrix becomes very large, while
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
Expand All @@ -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
Expand All @@ -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)

Expand Down
44 changes: 35 additions & 9 deletions tests/test_operation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down

0 comments on commit 1baad70

Please sign in to comment.