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

QubitUnitary accepts sparse matrices as inputs #6889

Open
wants to merge 84 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
84 commits
Select commit Hold shift + click to select a range
72aa2bd
`StatePrep` accepts sparse matrices as input
JerryChen97 Jan 20, 2025
ee7c3f0
first draft
JerryChen97 Jan 24, 2025
e10cf01
isort
JerryChen97 Jan 24, 2025
a4f30eb
discard assert, to allow pad_with=0
JerryChen97 Jan 24, 2025
e71929c
block n_states > dim cases
JerryChen97 Jan 24, 2025
fd23deb
add boolean marker for future use in the whole pipeline, e.g. devices…
JerryChen97 Jan 24, 2025
53ed0cd
draft: order permute for sparse statevec
JerryChen97 Jan 24, 2025
9411ec0
wrong place
JerryChen97 Jan 24, 2025
4538d1a
embedding algorithm
JerryChen97 Jan 24, 2025
3ffc8f9
more tests
JerryChen97 Jan 27, 2025
c859837
Merge branch 'master' into `StatePrep`-accepts-sparse-matrices-as-input
JerryChen97 Jan 27, 2025
694c9e8
isort
JerryChen97 Jan 27, 2025
40c1432
rm redundant conditional branch
JerryChen97 Jan 27, 2025
de02c89
isrot test
JerryChen97 Jan 27, 2025
942bcdb
Merge branch 'master' into `StatePrep`-accepts-sparse-matrices-as-input
JerryChen97 Jan 27, 2025
34f1817
`QubitUnitary` accepts sparse matrices as inputs
JerryChen97 Jan 27, 2025
5eda77c
update init
JerryChen97 Jan 27, 2025
2fc2199
Apply suggestions from code review
JerryChen97 Jan 27, 2025
525a649
fix bug after suggestions
JerryChen97 Jan 27, 2025
403f8ee
regex
JerryChen97 Jan 27, 2025
ee20832
save...
JerryChen97 Jan 27, 2025
3dbefbf
update adjoint etc
JerryChen97 Jan 28, 2025
ebb5b03
rm unnecessary break of strings
JerryChen97 Jan 31, 2025
831304d
Update pennylane/ops/qubit/state_preparation.py
JerryChen97 Jan 31, 2025
387fb80
Update pennylane/ops/qubit/state_preparation.py
JerryChen97 Jan 31, 2025
de14b45
Update pennylane/ops/qubit/state_preparation.py
JerryChen97 Jan 31, 2025
d388c2a
Merge remote-tracking branch 'refs/remotes/origin/`StatePrep`-accepts…
JerryChen97 Jan 31, 2025
edbb15b
change log
JerryChen97 Jan 31, 2025
0aa15f4
changeloggggg
isaacdevlugt Jan 31, 2025
8ca3b79
Isaac already did it. So Revert "change log"
JerryChen97 Jan 31, 2025
05dbb85
Merge remote-tracking branch 'refs/remotes/origin/`StatePrep`-accepts…
JerryChen97 Jan 31, 2025
d24ff67
More short info to log
JerryChen97 Jan 31, 2025
158068e
resolve https://github.com/PennyLaneAI/pennylane/pull/6863#discussion…
JerryChen97 Jan 31, 2025
7e157c6
Merge remote-tracking branch 'refs/remotes/origin/`StatePrep`-accepts…
JerryChen97 Jan 31, 2025
dd18ca7
apply suggestion at https://github.com/PennyLaneAI/pennylane/pull/686…
JerryChen97 Jan 31, 2025
f6e0ad7
correct some output
JerryChen97 Jan 31, 2025
f9b478c
improve change log example
JerryChen97 Jan 31, 2025
6866871
Merge branch 'master' into `StatePrep`-accepts-sparse-matrices-as-input
JerryChen97 Jan 31, 2025
9dd92aa
merge issue fix
JerryChen97 Jan 31, 2025
f76a9cf
correct section rst
JerryChen97 Jan 31, 2025
855ebfc
reformat
JerryChen97 Jan 31, 2025
4e81229
docstring
isaacdevlugt Jan 31, 2025
8991ddf
Merge remote-tracking branch 'refs/remotes/origin/`StatePrep`-accepts…
JerryChen97 Jan 31, 2025
f306c6e
rfmt
JerryChen97 Jan 31, 2025
94ba0c6
improve implementation
JerryChen97 Jan 31, 2025
98b3287
more improvement..
JerryChen97 Jan 31, 2025
63c01c2
even denser implementation for the sparse
JerryChen97 Jan 31, 2025
bf1c1f2
minor improvement over previous error msg
JerryChen97 Feb 3, 2025
1de2acf
Initial set of tests
JerryChen97 Feb 3, 2025
e32e73c
add test for pow
JerryChen97 Feb 3, 2025
52270f9
Merge branch '`StatePrep`-accepts-sparse-matrices-as-input' into `Qub…
JerryChen97 Feb 3, 2025
9579701
1st try to fix sphinx
JerryChen97 Feb 3, 2025
6c45746
Merge branch 'master' into `QubitUnitary`-accepts-sparse-matrices-as-…
JerryChen97 Feb 4, 2025
4d0536b
fix decomposition for two sites
JerryChen97 Feb 4, 2025
0d63b41
fix adjoint issue
JerryChen97 Feb 4, 2025
b182411
new item in changelog
JerryChen97 Feb 4, 2025
e19ace6
some tests for sparse
JerryChen97 Feb 4, 2025
a24cf6b
revert unnecessary mod to multi_dispatch and do things locally
JerryChen97 Feb 4, 2025
8d72ae1
Fix an occurence where np.eye was called
JerryChen97 Feb 4, 2025
81535e8
readibility
JerryChen97 Feb 4, 2025
a250409
doc
JerryChen97 Feb 4, 2025
53a9de4
add parameters for tests
JerryChen97 Feb 4, 2025
3e3b31e
Merge branch 'master' into `QubitUnitary`-accepts-sparse-matrices-as-…
JerryChen97 Feb 5, 2025
a54a8f5
add hard convert at init of QubitUnitary and StatePrep
JerryChen97 Feb 7, 2025
cadc8f9
Merge remote-tracking branch 'refs/remotes/origin/`QubitUnitary`-acce…
JerryChen97 Feb 7, 2025
f66ffed
add compute sparse matrix
JerryChen97 Feb 7, 2025
b3648bf
add tests of compute sparse matrix
JerryChen97 Feb 7, 2025
fee05fc
debug
JerryChen97 Feb 7, 2025
628cda6
add sparsity for several basic ops, along with tests
JerryChen97 Feb 7, 2025
d0c7246
more informative pytest.skip
JerryChen97 Feb 10, 2025
c923692
skip->xfail
JerryChen97 Feb 10, 2025
167400b
revert QU.decompositions sparse-VIP branches, and implement the backe…
JerryChen97 Feb 10, 2025
35d9ab9
Merge branch 'master' into `QubitUnitary`-accepts-sparse-matrices-as-…
JerryChen97 Feb 10, 2025
b02ff12
del unused var
JerryChen97 Feb 10, 2025
0cb34f8
add a warning in docstr to indicate the limitedness
JerryChen97 Feb 11, 2025
09124f4
Merge branch 'master' into `QubitUnitary`-accepts-sparse-matrices-as-…
JerryChen97 Feb 11, 2025
6abc33a
Update pennylane/math/single_dispatch.py
JerryChen97 Feb 11, 2025
5d50ae8
Update pennylane/math/single_dispatch.py
JerryChen97 Feb 11, 2025
c394554
Update pennylane/math/single_dispatch.py
JerryChen97 Feb 11, 2025
feee2c4
Update doc/releases/changelog-dev.md
JerryChen97 Feb 12, 2025
839652b
Update pennylane/math/single_dispatch.py
JerryChen97 Feb 12, 2025
0d61d57
Update pennylane/math/single_dispatch.py
JerryChen97 Feb 12, 2025
516bc28
Update pennylane/ops/qubit/matrix_ops.py
JerryChen97 Feb 12, 2025
306bd8c
Update pennylane/ops/qubit/matrix_ops.py
JerryChen97 Feb 12, 2025
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
27 changes: 26 additions & 1 deletion doc/releases/changelog-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,31 @@

<h3>Improvements 🛠</h3>

* `qml.QubitUnitary` now accepts sparse CSR matrices (from `scipy.sparse`). This allows efficient representation of large unitaries with mostly zero entries. Note that sparse unitaries are still in early development and may not support all features of their dense counterparts.
[(#6889)](https://github.com/PennyLaneAI/pennylane/pull/6889)

```pycon
>>> import numpy as np
>>> import pennylane as qml
>>> import scipy as sp
>>> U_dense = np.eye(4) # 2-wire identity
>>> U_sparse = sp.sparse.csr_matrix(U_dense)
>>> op = qml.QubitUnitary(U_sparse, wires=[0, 1])
>>> print(op.matrix())
<Compressed Sparse Row sparse matrix of dtype 'float64'
with 4 stored elements and shape (4, 4)>
Coords Values
(0, 0) 1.0
(1, 1) 1.0
(2, 2) 1.0
(3, 3) 1.0
>>> op.matrix().toarray()
array([[1., 0., 0., 0.],
[0., 1., 0., 0.],
[0., 0., 1., 0.],
[0., 0., 0., 1.]])
```

* Add a decomposition for multi-controlled global phases into a one-less-controlled phase shift.
[(#6936)](https://github.com/PennyLaneAI/pennylane/pull/6936)

Expand Down Expand Up @@ -86,7 +111,7 @@
>>> print(new_circuit.diff_method)
'parameter-shift'
```

* Devices can now configure whether or not ML framework data is sent to them
via an `ExecutionConfig.convert_to_numpy` parameter. End-to-end jitting on
`default.qubit` is used if the user specified a `jax.random.PRNGKey` as a seed.
Expand Down
64 changes: 64 additions & 0 deletions pennylane/math/single_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@
# pylint: disable=wrong-import-order
import autoray as ar
import numpy as np
import scipy as sp
from packaging.version import Version
from scipy.linalg import block_diag as _scipy_block_diag
from scipy.sparse.linalg import splu

from .interface_utils import get_deep_interface
from .utils import is_abstract
Expand Down Expand Up @@ -68,6 +70,68 @@ def _builtins_shape(x):
ar.register_function("scipy", "ndim", np.ndim)


# -------------------------------- SciPy Sparse --------------------------------- #
# the following is required to ensure that general SciPy sparse matrices are
# not automatically 'unwrapped' to dense NumPy arrays. Note that we assume
# that whenever the backend is 'scipy', the input is a SciPy sparse matrix.


def _det_sparse(x):
"""Compute determinant of sparse matrices without densification"""

assert sp.sparse.issparse(x), TypeError(f"Expected SciPy sparse, got {type(x)}")

x = sp.sparse.csr_matrix(x)
if x.shape != (2, 2):
return _generic_sparse_det(x)

# Direct array access
indptr, indices, data = x.indptr, x.indices, x.data
values = {(i, j): 0.0 for i in range(2) for j in range(2)}
for i in range(2):
for j_idx in range(indptr[i], indptr[i + 1]):
j = indices[j_idx]
values[(i, j)] = data[j_idx]

return values[(0, 0)] * values[(1, 1)] - values[(0, 1)] * values[(1, 0)]


def _generic_sparse_det(A):
"""Compute the determinant of a sparse matrix using LU decomposition."""

assert hasattr(A, "tocsc"), TypeError(f"Expected SciPy sparse, got {type(A)}")

A_csc = A.tocsc()
lu = splu(A_csc)
U_diag = lu.U.diagonal()
det_A = np.prod(U_diag)
parity = _permutation_parity(lu.perm_r)
return det_A * parity


def _permutation_parity(perm):
"""Compute the parity of a permutation."""

parity = 1
visited = [False] * len(perm)
for i in range(len(perm)):
if not visited[i]:
cycle_length = 0
j = i
while not visited[j]:
visited[j] = True
j = perm[j]
cycle_length += 1

if cycle_length > 0:
parity *= (-1) ** (cycle_length - 1)
return parity


ar.register_function("scipy", "linalg.det", _det_sparse)
ar.register_function("scipy", "linalg.eigs", sp.sparse.linalg.eigs)
ar.register_function("scipy", "trace", lambda x: x.trace())
Comment on lines +131 to +133
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are necessary for decompositions to run successfully. Further registers will be added in a following PR #6947 so that this PR does not go far beyond initial scope


# -------------------------------- NumPy --------------------------------- #

ar.register_function("numpy", "flatten", lambda x: x.flatten())
Expand Down
4 changes: 4 additions & 0 deletions pennylane/math/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# pylint: disable=wrong-import-order
import autoray as ar
import numpy as _np
import scipy as sp

# pylint: disable=import-outside-toplevel
from autograd.numpy.numpy_boxes import ArrayBox
Expand Down Expand Up @@ -174,6 +175,9 @@ def convert_like(tensor1, tensor2):
dev = tensor2.device
return np.asarray(tensor1, device=dev, like=interface)

if interface == "scipy":
return sp.sparse.csr_matrix(tensor1)

return np.asarray(tensor1, like=interface)


Expand Down
56 changes: 54 additions & 2 deletions pennylane/ops/op_math/decompositions/single_qubit_unitary.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@
"""Contains transforms and helpers functions for decomposing arbitrary unitary
operations into elementary gates.
"""
from functools import singledispatch

import numpy as np
import scipy as sp

import pennylane as qml
from pennylane import math
Expand All @@ -42,11 +44,16 @@ def _convert_to_su2(U, return_global_phase=False):
with np.errstate(divide="ignore", invalid="ignore"):
determinants = math.linalg.det(U)
phase = math.angle(determinants) / 2
U = math.cast_like(U, determinants) * math.exp(-1j * math.cast_like(phase, 1j))[:, None, None]
U = (
math.cast_like(U, determinants) * math.exp(-1j * math.cast_like(phase, 1j))[:, None, None]
if not sp.sparse.issparse(U)
else U * math.exp(-1j * phase)
)

return (U, phase) if return_global_phase else U


@singledispatch
def _zyz_get_rotation_angles(U):
r"""Computes the rotation angles :math:`\phi`, :math:`\theta`, :math:`\omega`
for a unitary :math:`U` that is :math:`SU(2)`
Expand Down Expand Up @@ -91,6 +98,51 @@ def _zyz_get_rotation_angles(U):
return phis, thetas, omegas


@_zyz_get_rotation_angles.register(sp.sparse.csr_matrix)
def _zyz_get_rotation_angles_sparse(U):
r"""Computes the rotation angles :math:`\phi`, :math:`\theta`, :math:`\omega`
for a unitary :math:`U` that is :math:`SU(2)`, sparse case

Args:
U (array[complex]): A matrix that is :math:`SU(2)`

Returns:
tuple[array[float]]: A tuple containing the rotation angles
:math:`\phi`, :math:`\theta`, :math:`\omega`

"""

assert sp.sparse.issparse(U), "Do not use this method if U is not sparse"

u00 = U[0, 0]
u01 = U[0, 1]
u10 = U[1, 0]

# For batched U or single U with non-zero off-diagonal, compute the
# normal decomposition instead
off_diagonal_elements = math.clip(math.abs(u01), 0, 1)
thetas = 2 * math.arcsin(off_diagonal_elements)

# Compute phi and omega from the angles of the top row; use atan2 to keep
# the angle within -np.pi and np.pi, and add very small value to the real
# part to avoid division by zero.
epsilon = 1e-64
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I remember that there was a discussion some time ago in a stand-up, mentioning a previous discussion about epsilons in pennylane (when, why, and how they should be used), but I don't know the details. Might be relevant in this context. Probably @mlxd or @AmintorDusko can help us here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am also very curious about many occurences of epsilon existing in many places of our codebase. If there is quick fix to them I'm happy to help clean them up here, but if there's quite some extended discussion TBD then I'll suggest we skip over it.

angles_U00 = math.arctan2(math.imag(u00), math.real(u00) + epsilon)
angles_U10 = math.arctan2(math.imag(u10), math.real(u10) + epsilon)

phis = -angles_U10 - angles_U00
omegas = angles_U10 - angles_U00

phis, thetas, omegas = map(math.squeeze, [phis, thetas, omegas])

# Normalize the angles
phis = phis % (4 * np.pi)
thetas = thetas % (4 * np.pi)
omegas = omegas % (4 * np.pi)

return phis, thetas, omegas


def _rot_decomposition(U, wire, return_global_phase=False):
r"""Compute the decomposition of a single-qubit matrix :math:`U` in terms of
elementary operations, as a single :class:`.RZ` gate or a :class:`.Rot` gate.
Expand Down Expand Up @@ -167,7 +219,7 @@ def _get_single_qubit_rot_angles_via_matrix(
of the matrix of the target operation using ZYZ rotations.
"""
# Cast to batched format for more consistent code
U = math.expand_dims(U, axis=0) if len(U.shape) == 2 else U
U = math.expand_dims(U, axis=0) if len(U.shape) == 2 and not sp.sparse.issparse(U) else U

# Convert to SU(2) format and extract global phase
U_su2, global_phase = _convert_to_su2(U, return_global_phase=True)
Expand Down
9 changes: 9 additions & 0 deletions pennylane/ops/op_math/decompositions/two_qubit_unitary.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
import warnings

import numpy as np
import scipy as sp

import pennylane as qml
from pennylane import math
Expand Down Expand Up @@ -102,6 +103,9 @@
)


global_arrays_name = ["E", "Edag", "CNOT01", "CNOT10", "SWAP", "S_SX", "v_one_cnot", "q_one_cnot"]


def _convert_to_su4(U):
r"""Convert a 4x4 matrix to :math:`SU(4)`.

Expand Down Expand Up @@ -208,7 +212,7 @@
[0], # arbitrary value for x
)

return math.convert_like(A, U), math.convert_like(B, U)

Check notice on line 215 in pennylane/ops/op_math/decompositions/two_qubit_unitary.py

View check run for this annotation

codefactor.io / CodeFactor

pennylane/ops/op_math/decompositions/two_qubit_unitary.py#L215

Possibly using variable 'B' before assignment (possibly-used-before-assignment)


def _extract_su2su2_prefactors(U, V):
Expand Down Expand Up @@ -621,6 +625,11 @@
_check_differentiability_warning(U)
# First, we note that this method works only for SU(4) gates, meaning that
# we need to rescale the matrix by its determinant.
if sp.sparse.issparse(U):
# Convert all the global elements to sparse matrices in-place
for name in global_arrays_name:
array = globals()[name]
globals()[name] = sp.sparse.csr_matrix(array)
U = _convert_to_su4(U)

# The next thing we will do is compute the number of CNOTs needed, as this affects
Expand All @@ -631,7 +640,7 @@
with qml.QueuingManager.stop_recording():
if qml.math.is_abstract(U):
decomp = _decomposition_3_cnots(U, wires)
elif num_cnots == 0:

Check notice on line 643 in pennylane/ops/op_math/decompositions/two_qubit_unitary.py

View check run for this annotation

codefactor.io / CodeFactor

pennylane/ops/op_math/decompositions/two_qubit_unitary.py#L643

Possibly using variable 'num_cnots' before assignment (possibly-used-before-assignment)
decomp = _decomposition_0_cnots(U, wires)
elif num_cnots == 1:
decomp = _decomposition_1_cnot(U, wires)
Expand Down
64 changes: 53 additions & 11 deletions pennylane/ops/qubit/matrix_ops.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@
from typing import Optional, Union

import numpy as np
import scipy as sp
from scipy.linalg import fractional_matrix_power
from scipy.sparse import csr_matrix

import pennylane as qml
from pennylane import numpy as pnp
Expand Down Expand Up @@ -78,6 +80,10 @@ class QubitUnitary(Operation):
r"""QubitUnitary(U, wires)
Apply an arbitrary unitary matrix with a dimension that is a power of two.

.. warning::

The sparse matrix representation of QubitUnitary is still under development. Currently we only support a limited set of interfaces that preserve the sparsity of the matrix, including ..method::`adjoint`, ..method::`pow`, ..method::`compute_sparse_matrix` and ..method::`compute_decomposition`. Differentiability is not supported for sparse matrices.

Comment on lines +83 to +86
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a warning to indicate the limitedness for any potential users in between this PR and the final dispatch system is completed. @PietropaoloFrisoni

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @JerryChen97 . Although I am fine with that, I am not sure that a warning is necessary here. Maybe a simple statement in the doc (without the warning) is sufficient. I would ask @isaacdevlugt just to be sure

**Details:**

* Number of wires: Any (the operation can act on any number of wires)
Expand All @@ -86,7 +92,7 @@ class QubitUnitary(Operation):
* Gradient recipe: None

Args:
U (array[complex]): square unitary matrix
U (array[complex] or csr_matrix): square unitary matrix
wires (Sequence[int] or int): the wire(s) the operation acts on
id (str): custom label given to an operator instance,
can be useful for some applications where the instance has to be identified
Expand Down Expand Up @@ -121,7 +127,7 @@ class QubitUnitary(Operation):

def __init__(
self,
U: TensorLike,
U: Union[TensorLike, csr_matrix],
wires: WiresLike,
id: Optional[str] = None,
unitary_check: bool = False,
Expand All @@ -135,19 +141,16 @@ def __init__(
if len(U_shape) not in {2, 3} or U_shape[-2:] != (dim, dim):
raise ValueError(
f"Input unitary must be of shape {(dim, dim)} or (batch_size, {dim}, {dim}) "
f"to act on {len(wires)} wires."
f"to act on {len(wires)} wires. Got shape {U_shape} instead."
)

# If the matrix is sparse, we need to convert it to a csr_matrix
if sp.sparse.issparse(U):
U = U.tocsr()

# Check for unitarity; due to variable precision across the different ML frameworks,
# here we issue a warning to check the operation, instead of raising an error outright.
if unitary_check and not (
qml.math.is_abstract(U)
or qml.math.allclose(
qml.math.einsum("...ij,...kj->...ik", U, qml.math.conj(U)),
qml.math.eye(dim),
atol=1e-6,
)
):
if unitary_check and not self._unitary_check(U, dim):
warnings.warn(
f"Operator {U}\n may not be unitary. "
"Verify unitarity of operation, or use a datatype with increased precision.",
Expand All @@ -156,6 +159,18 @@ def __init__(

super().__init__(U, wires=wires, id=id)

@staticmethod
def _unitary_check(U, dim):
if isinstance(U, csr_matrix):
U_dagger = U.conjugate().transpose()
identity = sp.sparse.eye(dim, format="csr")
return sp.sparse.linalg.norm(U @ U_dagger - identity) < 1e-10
return qml.math.allclose(
qml.math.einsum("...ij,...kj->...ik", U, qml.math.conj(U)),
qml.math.eye(dim),
atol=1e-6,
)

@staticmethod
def compute_matrix(U: TensorLike): # pylint: disable=arguments-differ
r"""Representation of the operator as a canonical matrix in the computational basis (static method).
Expand All @@ -180,6 +195,26 @@ def compute_matrix(U: TensorLike): # pylint: disable=arguments-differ
"""
return U

@staticmethod
def compute_sparse_matrix(U: TensorLike): # pylint: disable=arguments-differ
r"""Representation of the operator as a sparse matrix.

Args:
U (tensor_like): unitary matrix

Returns:
csr_matrix: sparse matrix representation

**Example**

>>> U = np.array([[0.98877108+0.j, 0.-0.14943813j], [0.-0.14943813j, 0.98877108+0.j]])
>>> qml.QubitUnitary.compute_sparse_matrix(U)
<2x2 sparse matrix of type '<class 'numpy.complex128'>'
with 2 stored elements in Compressed Sparse Row format>
"""
U = qml.math.asarray(U, like="numpy")
return sp.sparse.csr_matrix(U)

@staticmethod
def compute_decomposition(U: TensorLike, wires: WiresLike):
r"""Representation of the operator as a product of other operators (static method).
Expand Down Expand Up @@ -236,10 +271,17 @@ def has_decomposition(self) -> bool:

def adjoint(self) -> "QubitUnitary":
U = self.matrix()
if isinstance(U, csr_matrix):
adjoint_sp_mat = U.conjugate().transpose()
# Note: it is necessary to explicitly cast back to csr, or it will be come csc
JerryChen97 marked this conversation as resolved.
Show resolved Hide resolved
return QubitUnitary(csr_matrix(adjoint_sp_mat), wires=self.wires)
return QubitUnitary(qml.math.moveaxis(qml.math.conj(U), -2, -1), wires=self.wires)
JerryChen97 marked this conversation as resolved.
Show resolved Hide resolved

def pow(self, z: Union[int, float]):
mat = self.matrix()
if isinstance(mat, csr_matrix):
pow_mat = sp.sparse.linalg.matrix_power(mat, z)
return [QubitUnitary(pow_mat, wires=self.wires)]
if isinstance(z, int) and qml.math.get_deep_interface(mat) != "tensorflow":
pow_mat = qml.math.linalg.matrix_power(mat, z)
elif self.batch_size is not None or qml.math.shape(z) != ():
Expand Down
Loading