Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adding a wire_order kwarg to Tensor.sparse_matrix() #4424

Merged
merged 6 commits into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading