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

Implement SpectralNormError #5154

Merged
merged 60 commits into from
Mar 5, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
7c88147
start multi dispacthing for svd
AmintorDusko Feb 5, 2024
65bcc11
expand tensorflow dispatching for an equivalent results return
AmintorDusko Feb 5, 2024
02da268
lint
AmintorDusko Feb 5, 2024
1ef9f68
fix function doc
AmintorDusko Feb 5, 2024
5618829
Merge branch 'master' into implement/SpectralNormError
AmintorDusko Feb 7, 2024
f48b2be
update svd
AmintorDusko Feb 7, 2024
870a125
add tests for svd with different frameworks
AmintorDusko Feb 7, 2024
6d8fd8e
Merge branch 'master' into implement/SpectralNormError
AmintorDusko Feb 13, 2024
60b0c49
add SpectralNormError implementation
AmintorDusko Feb 15, 2024
c800253
Merge branch 'master' into implement/SpectralNormError
AmintorDusko Feb 15, 2024
7169b6a
add SpectralNormError to init
AmintorDusko Feb 15, 2024
8458f72
add tests for SpectralNormError
AmintorDusko Feb 15, 2024
1d7b882
some fixes
AmintorDusko Feb 15, 2024
a2b5d36
update changelog
AmintorDusko Feb 15, 2024
c76615a
update toy operator
AmintorDusko Feb 15, 2024
61c9252
update DummyOp
AmintorDusko Feb 15, 2024
6b8d7a1
Merge branch 'master' into implement/SpectralNormError
AmintorDusko Feb 16, 2024
2cd9cfd
Update pennylane/math/multi_dispatch.py
AmintorDusko Feb 16, 2024
e7d8cdd
Update pennylane/math/multi_dispatch.py
AmintorDusko Feb 16, 2024
5aac0c6
Update pennylane/resource/error.py
AmintorDusko Feb 16, 2024
fe30cd1
Update pennylane/resource/error.py
AmintorDusko Feb 16, 2024
54ee9f2
update data type
AmintorDusko Feb 16, 2024
6602504
update docstring
AmintorDusko Feb 16, 2024
939074b
format
AmintorDusko Feb 16, 2024
63ecc99
Merge branch 'master' into implement/SpectralNormError
AmintorDusko Feb 20, 2024
3c5db38
Update pennylane/resource/error.py
AmintorDusko Feb 20, 2024
af1e046
Update doc/releases/changelog-dev.md
Jaybsoni Feb 20, 2024
ed0a9a5
Update pennylane/math/multi_dispatch.py
AmintorDusko Feb 20, 2024
9596b01
Merge branch 'master' into implement/SpectralNormError
AmintorDusko Feb 26, 2024
2653eff
matrices in math-mode
AmintorDusko Feb 26, 2024
036aef3
add review suggestions
AmintorDusko Feb 26, 2024
ba9fad8
remove unused import
AmintorDusko Feb 26, 2024
574b7e5
Apply suggestions from code review
soranjh Feb 27, 2024
a08eb19
use qml matrix
soranjh Feb 27, 2024
c77bbe4
add examples to docstring
soranjh Feb 27, 2024
2629c96
fix import and update docstring
soranjh Feb 27, 2024
c7ca96b
return max singular val
soranjh Feb 27, 2024
ed1cb5f
correct import
soranjh Feb 27, 2024
d6372c3
update failing test
soranjh Feb 27, 2024
7f6a195
Apply suggestions from code review
soranjh Feb 28, 2024
c501371
add string representation
soranjh Feb 28, 2024
a130a73
Merge branch 'implement/SpectralNormError' of https://github.com/Penn…
soranjh Feb 28, 2024
6057c0f
correct math
soranjh Feb 28, 2024
9f6f2bf
correct example
soranjh Feb 28, 2024
760f80a
Merge branch 'master' into implement/SpectralNormError
soranjh Feb 28, 2024
812cd16
fix pylint
soranjh Feb 28, 2024
7c47c75
update example
soranjh Feb 29, 2024
45231f8
Merge branch 'master' into implement/SpectralNormError
soranjh Feb 29, 2024
fa736ad
merge master and fix conflict
soranjh Mar 4, 2024
aa3bb64
update matrix error
soranjh Mar 4, 2024
a95de20
update matrix test
soranjh Mar 4, 2024
6b2799c
fix pylint
soranjh Mar 4, 2024
c6ad68e
fix pylint
soranjh Mar 4, 2024
0eac07d
Apply suggestions from code review
soranjh Mar 5, 2024
41a009b
remove kwargs
soranjh Mar 5, 2024
e945ace
remove kwargs
soranjh Mar 5, 2024
eb82d18
update repr
soranjh Mar 5, 2024
f8cef1e
merge master and update changelog
soranjh Mar 5, 2024
fea9b9f
update doc
soranjh Mar 5, 2024
f4e6d49
Merge branch 'master' into implement/SpectralNormError
soranjh Mar 5, 2024
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
28 changes: 16 additions & 12 deletions doc/releases/changelog-dev.md
soranjh marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -126,10 +126,13 @@
* A function called `apply_operation` has been added to the new `qutrit_mixed` module found in `qml.devices` that applies operations to device-compatible states.
[(#5032)](https://github.com/PennyLaneAI/pennylane/pull/5032)

* Added new error tracking and propagation functionality.
* Added new error tracking and propagation functionality.
[(#5115)](https://github.com/PennyLaneAI/pennylane/pull/5115)
[(#5121)](https://github.com/PennyLaneAI/pennylane/pull/5121)

* Added new SpectralNormError class to the new error tracking functionality.
[(#5154)](https://github.com/PennyLaneAI/pennylane/pull/5154)


<h3>Improvements 🛠</h3>

Expand All @@ -146,7 +149,7 @@
* PennyLane can now use lightning provided VJPs by selecting `device_vjp=True` on the QNode.
[(#4914)](https://github.com/PennyLaneAI/pennylane/pull/4914)

* Remove queuing (`AnnotatedQueue`) from `qml.cut_circuit` and `qml.cut_circuit_mc` to improve performance
* Remove queuing (`AnnotatedQueue`) from `qml.cut_circuit` and `qml.cut_circuit_mc` to improve performance
for large workflows.
[(#5108)](https://github.com/PennyLaneAI/pennylane/pull/5108)

Expand Down Expand Up @@ -301,15 +304,15 @@
To allow for packages to register multiple compilers with PennyLane,
the `entry_points` convention under the designated group name
`pennylane.compilers` has been modified.

Previously, compilers would register `qjit` (JIT decorator),
`ops` (compiler-specific operations), and `context` (for tracing and
program capture).

Now, compilers must register `compiler_name.qjit`, `compiler_name.ops`,
and `compiler_name.context`, where `compiler_name` is replaced
by the name of the provided compiler.

For more information, please see the
[documentation on adding compilers](https://docs.pennylane.ai/en/stable/code/qml_compiler.html#adding-a-compiler).

Expand Down Expand Up @@ -339,10 +342,10 @@
[(#5048)](https://github.com/PennyLaneAI/pennylane/pull/5048)

* Controlled operators with a custom controlled version decomposes like how their
controlled counterpart decomposes, as opposed to decomposing into their controlled version.
controlled counterpart decomposes, as opposed to decomposing into their controlled version.
[(#5069)](https://github.com/PennyLaneAI/pennylane/pull/5069)
[(#5125)](https://github.com/PennyLaneAI/pennylane/pull/5125/)

For example:
```
>>> qml.ctrl(qml.RX(0.123, wires=1), control=0).decomposition()
Expand Down Expand Up @@ -478,16 +481,16 @@
* `PauliSentence.wires` no longer imposes a false order.
[(#5041)](https://github.com/PennyLaneAI/pennylane/pull/5041)

* `qml.qchem.import_state` now applies the chemist-to-physicist
* `qml.qchem.import_state` now applies the chemist-to-physicist
sign convention when initializing a PennyLane state vector from
classically pre-computed wavefunctions. That is, it interleaves
classically pre-computed wavefunctions. That is, it interleaves
spin-up/spin-down operators for the same spatial orbital index,
as standard in PennyLane (instead of commuting all spin-up
operators to the left, as is standard in quantum chemistry).
as standard in PennyLane (instead of commuting all spin-up
operators to the left, as is standard in quantum chemistry).
[(#5114)](https://github.com/PennyLaneAI/pennylane/pull/5114)

* Multi-wire controlled `CNOT` and `PhaseShift` can now be decomposed correctly.
[(#5125)](https://github.com/PennyLaneAI/pennylane/pull/5125/)
[(#5125)](https://github.com/PennyLaneAI/pennylane/pull/5125/)
[(#5148)](https://github.com/PennyLaneAI/pennylane/pull/5148)

* `draw_mpl` no longer raises an error when drawing a circuit containing an adjoint of a controlled operation.
Expand Down Expand Up @@ -533,6 +536,7 @@ Astral Cai,
Skylar Chan,
Isaac De Vlugt,
Diksha Dhawan,
Amintor Dusko
Lillian Frederiksen,
Pietropaolo Frisoni,
Eugenio Gigante,
Expand Down
1 change: 1 addition & 0 deletions pennylane/math/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
matmul,
multi_dispatch,
norm,
svd,
ones_like,
scatter,
scatter_element_add,
Expand Down
43 changes: 43 additions & 0 deletions pennylane/math/multi_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -870,6 +870,49 @@ def norm(tensor, like=None, **kwargs):
return norm(tensor, **kwargs)


@multi_dispatch(argnum=[0])
def svd(tensor, like=None, **kwargs):
trbromley marked this conversation as resolved.
Show resolved Hide resolved
"""Compute the singular value decomposition of a tensor in each interface.
For a matrix A, the singular value decomposition consist of three matrices U, S and Vh, such that:
AmintorDusko marked this conversation as resolved.
Show resolved Hide resolved
soranjh marked this conversation as resolved.
Show resolved Hide resolved

.. math::
soranjh marked this conversation as resolved.
Show resolved Hide resolved

A = U . Diag(S) . Vh
soranjh marked this conversation as resolved.
Show resolved Hide resolved

Returns:
S, U, V: full decomposition (if compute_uv is True or None)
S: only the singular values (if compute_uv is False)

soranjh marked this conversation as resolved.
Show resolved Hide resolved
"""
if like == "tensorflow":
from tensorflow.linalg import svd, adjoint

# Tensorflow results need some pos-processing to keep it similar to other frameworks.
AmintorDusko marked this conversation as resolved.
Show resolved Hide resolved
if kwargs.get("compute_uv", True):
S, U, V = svd(tensor, **kwargs)
return U, S, adjoint(V)
return svd(tensor, **kwargs)

if like == "jax":
from jax.numpy.linalg import svd

elif like == "torch":
# Torch is deprecating torch.svd() in favor of torch.linalg.svd().
soranjh marked this conversation as resolved.
Show resolved Hide resolved
# The new UI is slightly different and breaks the logic for the multi dispatching.
# This small workaround restores the compute_uv control argument.
AmintorDusko marked this conversation as resolved.
Show resolved Hide resolved
if kwargs.get("compute_uv", True) is False:
from torch.linalg import svdvals as svd
else:
from torch.linalg import svd
if kwargs.get("compute_uv", None) is not None:
kwargs.pop("compute_uv")

else:
from numpy.linalg import svd

return svd(tensor, **kwargs)


def _flat_autograd_norm(tensor, **kwargs): # pylint: disable=unused-argument
"""Helper function for computing the norm of an autograd tensor when the order or axes are not
specified. This is used for differentiability."""
Expand Down
3 changes: 2 additions & 1 deletion pennylane/resource/__init__.py
soranjh marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
:toctree: api

~AlgorithmicError
~SpectralNormError
~ErrorOperation

Resource Classes
Expand Down Expand Up @@ -123,4 +124,4 @@ def circuit(theta):
from .second_quantization import DoubleFactorization
from .measurement import estimate_error, estimate_shots
from .specs import specs
from .error import AlgorithmicError, ErrorOperation
from .error import AlgorithmicError, ErrorOperation, SpectralNormError
41 changes: 40 additions & 1 deletion pennylane/resource/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"""
from abc import ABC, abstractmethod

from pennylane.operation import Operation
from pennylane.operation import Operation, Operator, MatrixUndefinedError
from pennylane import math as fn


class AlgorithmicError(ABC):
Expand Down Expand Up @@ -77,3 +78,41 @@ def error(self) -> AlgorithmicError:
Returns:
AlgorithmicError: The error.
"""


class SpectralNormError(AlgorithmicError):
"""Class representing the spectral norm error.
The spectral norm error is defined as the distance (in spectral norm) between the true unitary we wish to apply and the approximate unitary that we actually apply.
AmintorDusko marked this conversation as resolved.
Show resolved Hide resolved
AmintorDusko marked this conversation as resolved.
Show resolved Hide resolved

Args:
error (float): The numerical value of the error

trbromley marked this conversation as resolved.
Show resolved Hide resolved
"""

def combine(self, other: AlgorithmicError):
"""A method to combine two spectral norm errors.

Args:
other (AlgorithmicError): The other instance of error being combined.
AmintorDusko marked this conversation as resolved.
Show resolved Hide resolved

Returns:
AlgorithmicError: The total error after combination.
"""
return self.__class__(self.error + other.error)

@staticmethod
def get_error(approximate_op: Operator, exact_op: Operator, **kwargs):
soranjh marked this conversation as resolved.
Show resolved Hide resolved
"""A method to allow users to compute spectral norm error between two operators.
AmintorDusko marked this conversation as resolved.
Show resolved Hide resolved

Args:
approximate_op (.Operator): The approximate operator.
exact_op (.Operator): The exact operator.

Returns:
float: The error between the exact operator and its
approximation.
"""
if approximate_op.has_matrix and exact_op.has_matrix:
return fn.svd(exact_op.matrix() - approximate_op.matrix(), compute_uv=False)[0]
Jaybsoni marked this conversation as resolved.
Show resolved Hide resolved
soranjh marked this conversation as resolved.
Show resolved Hide resolved

raise MatrixUndefinedError
soranjh marked this conversation as resolved.
Show resolved Hide resolved
83 changes: 83 additions & 0 deletions tests/math/test_multi_dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -315,3 +315,86 @@ def test_autograd_norm_gradient(self, arr):
grad = qml_grad(fn.norm)(arr)
expected_grad = (norm**-1) * arr.conj()
assert fn.allclose(grad, expected_grad)


@pytest.mark.all_interfaces
class TestSVD:
mat = [
[-0.00707107 + 0.0j, 1.00707107 + 0.0j],
[0.99292893 + 0.0j, -0.00707107 + 0.0j],
]

mats_interface = (
(
onp.array(mat),
"numpy",
),
(
torch.tensor(mat),
"torch",
),
(
tf.Variable(mat),
"tensorflow",
),
(
jnp.array(mat),
"jax",
),
)

@pytest.mark.parametrize("mat, expected_intrf", mats_interface)
@pytest.mark.parametrize(
"expected_results",
[
(
[
[
[0.92388093 + 0.0j, -0.38268036 + 0.0j],
[-0.38268048 + 0.0j, -0.9238808 + 0.0j],
],
[1.0100001, 0.98999995],
[[-0.3826802 + 0.0j, 0.9238809 + 0.0j], [-0.9238809 + 0.0j, -0.3826802 + 0.0j]],
],
),
],
)
def test_svd_full(self, mat, expected_intrf, expected_results):
"""Test that svd is correct and works for each interface. Asking for the full decomposition"""
results_svd = fn.svd(mat, compute_uv=True)
for n in range(len(expected_results)):
assert fn.get_interface(results_svd[n]) == expected_intrf
if expected_intrf == "tensorflow":
recovered_matrix = fn.matmul(
fn.matmul(
results_svd[0],
fn.diag(np.array(results_svd[1], dtype="complex128"), like=expected_intrf),
),
results_svd[2],
like=expected_intrf,
)
else:
recovered_matrix = fn.matmul(
fn.matmul(
results_svd[0],
fn.diag(results_svd[1], like=expected_intrf),
),
results_svd[2],
like=expected_intrf,
)

assert np.allclose(mat, recovered_matrix, rtol=1e-04)

@pytest.mark.parametrize("mat, expected_intrf", mats_interface)
@pytest.mark.parametrize(
"expected_results",
[
([[1.0100001, 0.98999995]]),
],
)
def test_svd_only_sv(self, mat, expected_intrf, expected_results):
"""Test that svd is correct and works for each interface. Asking only for singular values."""
results_svd = fn.svd(mat, compute_uv=False)

assert np.allclose(results_svd, expected_results)
assert fn.get_interface(results_svd) == expected_intrf
68 changes: 67 additions & 1 deletion tests/resource/test_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@
Test base AlgorithmicError class and its associated methods.
"""
import pytest
import numpy as np

import pennylane as qml
from pennylane.resource.error import AlgorithmicError, ErrorOperation
from pennylane.resource.error import AlgorithmicError, SpectralNormError, ErrorOperation
from pennylane.operation import Operation


class SimpleError(AlgorithmicError):
Expand Down Expand Up @@ -82,6 +84,70 @@ def test_get_error(self):
assert res == 0.5


class TestSpectralNormError:
"""Test methods for the SpectralNormError class"""

@pytest.mark.parametrize("err1", [0, 0.25, 0.75, 1.50, 2.50])
@pytest.mark.parametrize("err2", [0, 0.25, 0.75, 1.50, 2.50])
def test_combine(self, err1, err2):
"""Test that combine works as expected"""
ErrorObj1 = SpectralNormError(err1)
ErrorObj2 = SpectralNormError(err2)

res = ErrorObj1.combine(ErrorObj2)
assert res.error == err1 + err2
assert isinstance(res, type(ErrorObj1))

@pytest.mark.parametrize(
"phi, expected",
[
[0, 2.0000000000000004],
[0.25, 1.9980522880732308],
[0.75, 1.9828661007943447],
[1.50, 1.9370988373785705],
[2.50, 1.8662406421959807],
AmintorDusko marked this conversation as resolved.
Show resolved Hide resolved
],
)
def test_get_error(self, phi, expected):
"""Test that get_error works as expected"""
approx_op = qml.Hadamard(0)
exact_op = qml.RX(phi, 1)

res = SpectralNormError.get_error(approx_op, exact_op)
assert res == expected

@pytest.mark.parametrize(
"phi, expected",
[
[0, 1.311891347309272],
[0.25, 1.3182208123805488],
[0.75, 1.3772695464365001],
[1.50, 1.6078817482299055],
[2.50, 2.0506044587737255],
],
)
def test_custom_operator(self, phi, expected):
"""Test that get_error fails if the operator matrix is not defined"""
AmintorDusko marked this conversation as resolved.
Show resolved Hide resolved

class DummyOp(Operation): # pylint: disable=too-few-public-methods
def matrix(self):
return np.array([[0.5, 1.0], [1.2, 1.3]])

approx_op = DummyOp(1)
exact_op = qml.RX(phi, 1)

res = SpectralNormError.get_error(approx_op, exact_op)
assert res == expected

def test_no_operator_matrix_defined(self):
"""Test that get_error fails if the operator matrix is not defined"""
approx_op = Operation
exact_op = qml.RX(0.1, 1)

with pytest.raises(qml.operation.MatrixUndefinedError):
SpectralNormError.get_error(approx_op, exact_op)


class TestErrorOperation: # pylint: disable=too-few-public-methods
"""Test the base ErrorOperation class."""

Expand Down
Loading