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

LRE Inference Functions #2447

Merged
merged 29 commits into from
Aug 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
428128e
update readme
purva-thakre Jul 10, 2024
90f22dc
function for sample matrix - minus unit test for sample matrix
purva-thakre Jul 16, 2024
634ea80
sample matrix unit tests
purva-thakre Jul 16, 2024
0e297a5
func: coeffs + unit tests
purva-thakre Jul 17, 2024
8b2ad8c
additional unit tests: sample matrix square, raised errors
purva-thakre Jul 17, 2024
00b92c5
unit test: chunking
purva-thakre Jul 18, 2024
d61b1cc
try: abs all threshold for test_coeffs
purva-thakre Jul 21, 2024
7d8868b
docstring cleanup: vincent's comments
purva-thakre Jul 21, 2024
c92a38a
ignore sample matrix not square matrix from coverage report
purva-thakre Jul 21, 2024
c247619
linear_combination_coefficients return docstring, type
purva-thakre Jul 23, 2024
6290f93
additional tests: compare with scaling function
purva-thakre Jul 26, 2024
aef46ab
cleanup Except block
purva-thakre Jul 29, 2024
d382767
check test coverage
purva-thakre Jul 31, 2024
30c6914
temporarily diable mypy
purva-thakre Jul 31, 2024
f320b2b
new full basis terms func
purva-thakre Jul 31, 2024
378aad8
ignore special use cases from pytest coverage
purva-thakre Aug 1, 2024
f539153
make sure coeffs sum up to 1
purva-thakre Aug 1, 2024
f1d1635
more comments + test for exp function
purva-thakre Aug 1, 2024
3ff1578
remove symbolic monomial basis terms function
purva-thakre Aug 7, 2024
eb0f9d7
add to apidoc
purva-thakre Aug 8, 2024
fd6be18
nate's suggestions
purva-thakre Aug 14, 2024
42fd2b1
nate's suggestions: det minor + remove sum(coeffs)==1.0 unit tests
purva-thakre Aug 16, 2024
83880e3
nate's suggestions + default built in for types + concatenate block i…
purva-thakre Aug 20, 2024
7e55d5f
add more details to the docstring + rename coefficients function
purva-thakre Aug 23, 2024
80fe32c
ruff failure
purva-thakre Aug 23, 2024
48bae71
cleanup docstrings
purva-thakre Aug 26, 2024
d81bd6f
Update mitiq/lre/inference/multivariate_richardson.py
purva-thakre Aug 27, 2024
d97c2f3
Update mitiq/lre/inference/multivariate_richardson.py
purva-thakre Aug 27, 2024
f0c0293
update docstring
purva-thakre Aug 27, 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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ mitiq.qem_methods()
| Readout-error mitigation | [REM](https://mitiq.readthedocs.io/en/latest/guide/rem.html) | [`mitiq.rem`](https://github.com/unitaryfund/mitiq/tree/main/mitiq/rem) | [1907.08518](https://arxiv.org/abs/1907.08518) <br>[2006.14044](https://arxiv.org/abs/2006.14044)
| Quantum Subspace Expansion | [QSE](https://mitiq.readthedocs.io/en/stable/guide/qse.html) | [`mitiq.qse`](https://github.com/unitaryfund/mitiq/tree/main/mitiq/qse) | [1903.05786](https://arxiv.org/abs/1903.05786)|
| Robust Shadow Estimation 🚧 | [RSE](https://mitiq.readthedocs.io/en/stable/guide/shadows.html)| [`mitiq.qse`](https://github.com/unitaryfund/mitiq/tree/main/mitiq/shadows) | [2011.09636](https://arxiv.org/abs/2011.09636) <br> [2002.08953](https://arxiv.org/abs/2002.08953)|
| Layerwise Richardson Extrapolation 🚧 | Coming soon | | [2402.04000](https://arxiv.org/abs/2402.04000) |
| Layerwise Richardson Extrapolation 🚧 | Coming soon | [`mitiq.lre`](https://github.com/unitaryfund/mitiq/tree/main/mitiq/lre) | [2402.04000](https://arxiv.org/abs/2402.04000) |


In addition, we also have a noise tailoring technique currently available with limited functionality:
Expand Down
5 changes: 5 additions & 0 deletions docs/source/apidoc.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,11 @@ See Ref. {cite}`Czarnik_2021_Quantum` for more details on these methods.
:members:
```

```{eval-rst}
.. automodule:: mitiq.lre.inference.multivariate_richardson
:members:
```

### Pauli Twirling

```{eval-rst}
Expand Down
7 changes: 6 additions & 1 deletion mitiq/lre/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,9 @@

"""Methods for scaling noise in circuits by layers and using multivariate extrapolation."""

from mitiq.lre.multivariate_scaling.layerwise_folding import multivariate_layer_scaling
from mitiq.lre.multivariate_scaling.layerwise_folding import multivariate_layer_scaling

from mitiq.lre.inference.multivariate_richardson import (
multivariate_richardson_coefficients,
sample_matrix,
)
194 changes: 194 additions & 0 deletions mitiq/lre/inference/multivariate_richardson.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
# Copyright (C) Unitary Fund
#
# This source code is licensed under the GPL license (v3) found in the
# LICENSE file in the root directory of this source tree.

"""Functions for multivariate richardson extrapolation as defined in
:cite:`Russo_2024_LRE`.
"""

import warnings
from itertools import product
from typing import Any, Optional

import numpy as np
from cirq import Circuit
from numpy.typing import NDArray

from mitiq.lre.multivariate_scaling.layerwise_folding import (
_get_scale_factor_vectors,
)


def _full_monomial_basis_term_exponents(
num_layers: int, degree: int
) -> list[tuple[int, ...]]:
"""Finds exponents of monomial terms required to create the sample matrix
as defined in Section IIB of :cite:`Russo_2024_LRE`.

$Mj(λ_i, d)$ is the basis of monomial terms for $l$-layers in the input
purva-thakre marked this conversation as resolved.
Show resolved Hide resolved
circuit up to a specific degree $d$. The linear combination defines our
polynomial of interest. In general, the number of monomial terms for a
variable $l$ up to degree $d$ can be determined through the Stars and
Bars method.

We assume the terms in the monomial basis are arranged in a graded
lexicographic order such that the terms with the highest total degree are
considered to be the largest and the remaining terms are arranged in
lexicographic order.
Comment on lines +35 to +38
Copy link
Member

Choose a reason for hiding this comment

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

I believe the ordering we are using here is some sort of anti-lexicographic ordering within each "grade". E.g. with grade = 1, in lexicographic ordering 10 comes after 01 since 0 comes before 1. Maybe it's "graded reverse lexicographic ordering"?\

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

raded reverse lexicographic ordering

No, this would be a different order. See section I of https://pi.math.cornell.edu/~dmehrle/notes/old/alggeo/07MonomialOrdersandDivisionAlgorithm.pdf

In the graded lexicographic order, the highest total degree is the largest along with the requirements of the lexicographic order which considers the difference between the exponents.

Copy link
Member

Choose a reason for hiding this comment

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

Okay, so I think all the confusion boils down to a potential confusion in convention.

A tuple of exponents $(a_1, a_2, \ldots, a_n)$ can be taken to represent either $x_1^{a_1}x_2^{a_2}\cdots x_n^{a_n}$ or $x_1^{a_n}x_2^{a_{n-1}}\cdots x_n^{a_1}$. The choice here is unimportant in it's own right, but important that we know which one we are working in and stay consistent.

If we are working in the second convention (which seems a bit strange to me1) then I think everything is correct. In my head, I was previously working with the former convention which means the graded lexicographic ordering on monomials with two terms and maximum degree two ($1 &lt; x_2 &lt; x_1 &lt; x_2^2 &lt; x_1x_2 &lt; x_1^2$) would read

$$(0, 0) < (0, 1) < (1, 0) < (0, 2) < (1, 1) < (2, 0)$$

whereas the latter convention reads

$$(0, 0) < (1, 0) < (0, 1) < (2, 0) < (1, 1) < (0, 2).$$

The latter convention indeed yields what is currently implemented.

So the question is does $(0, 1)$ represent $x_1$ or $x_2$?

Footnotes

  1. It's convention, though, not gospel, so 🤷🏻.


For `degree=2, num_layers=2`, the monomial terms basis are
${1, x_1, x_2, x_1**2, x_1x_2, x_2**2}$ i.e. the function returns the
exponents of x_1, x_2 as
`[(0, 0), (1, 0), (0, 1), (2, 0), (1, 1), (0, 2)]`.
"""
purva-thakre marked this conversation as resolved.
Show resolved Hide resolved
exponents = {
exps
for exps in product(range(degree + 1), repeat=num_layers)
if sum(exps) <= degree
}

return sorted(exponents, key=lambda term: (sum(term), term[::-1]))


def sample_matrix(
input_circuit: Circuit,
degree: int,
fold_multiplier: int,
num_chunks: Optional[int] = None,
) -> NDArray[Any]:
r"""
Defines the square sample matrix required for multivariate extrapolation as
defined in :cite:`Russo_2024_LRE`.

The number of monomial terms should be equal to the
number of scale factor vectors such that the monomial terms define the rows
and the scale factor vectors define the columns.

Args:
input_circuit: Circuit to be scaled.
degree: Degree of the multivariate polynomial.
fold_multiplier: Scaling gap required by unitary folding.
num_chunks: Number of desired approximately equal chunks. When the
number of chunks is the same as the layers in the input circuit,
the input circuit is unchanged.

Returns:
Matrix of the evaluated monomial basis terms from the scale factor
vectors.

Raises:
ValueError:
When the degree for the multinomial is not greater than or
equal to 1; when the fold multiplier to scale the circuit is
greater than/equal to 1; when the number of chunks for a
large circuit is 0 or when the number of chunks in a circuit is
greater than the number of layers in the input circuit.

"""
if degree < 1:
raise ValueError(
"Multinomial degree must be greater than or equal to 1."
)
if fold_multiplier < 1:
raise ValueError("Fold multiplier must be greater than or equal to 1.")
purva-thakre marked this conversation as resolved.
Show resolved Hide resolved

scale_factor_vectors = _get_scale_factor_vectors(
input_circuit, degree, fold_multiplier, num_chunks
)
num_layers = len(scale_factor_vectors[0])

# Evaluate the monomial terms using the values in the scale factor vectors
# and insert in the sample matrix
# each row is specific to each scale factor vector
# each column is a term in the monomial basis
variable_exp = _full_monomial_basis_term_exponents(num_layers, degree)
sample_matrix = np.empty((len(variable_exp), len(variable_exp)))

for i, scale_factors in enumerate(scale_factor_vectors):
for j, exponent in enumerate(variable_exp):
evaluated_terms = []
for base, exp in zip(scale_factors, exponent):
# raise scale factor value by the exponent dict value
evaluated_terms.append(base**exp)
sample_matrix[i, j] = np.prod(evaluated_terms)

# verify the matrix is square
mat_row, mat_cols = sample_matrix.shape
assert mat_row == mat_cols
Comment on lines +116 to +118
Copy link
Member

Choose a reason for hiding this comment

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

We typically use tests for this type of thing. Is there a reason we should have this assert in the code here?

Copy link
Collaborator Author

@purva-thakre purva-thakre Aug 27, 2024

Choose a reason for hiding this comment

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

I am making my way through Dan Bader's 'Python Tricks: The Book' to help me write better code. He recommends using assertions like this.


return sample_matrix


def multivariate_richardson_coefficients(
input_circuit: Circuit,
degree: int,
fold_multiplier: int,
num_chunks: Optional[int] = None,
) -> list[float]:
r"""
Defines the function to find the linear combination coefficients from the
sample matrix as required for multivariate extrapolation (defined in
:cite:`Russo_2024_LRE`).

We use the sample matrix to find the constants of linear combination
$c = (c_1, c_2, c_3, …, c_M)$ associated with a known vector of noisy
expectation values $z = (<O(λ_1)>, <O(λ_2)>, <O(λ_3)>, ..., <O(λ_M)>)^T$.

The coefficients are found through the ratio of the determinants of $M_i$
and the sample matrix. The new matrix $M_i$ is defined by replacing the ith
row of the sample matrix with $e_1 = (1, 0, 0,..., 0)$.


Args:
input_circuit: Circuit to be scaled.
degree: Degree of the multivariate polynomial.
fold_multiplier: Scaling gap required by unitary folding.
num_chunks: Number of desired approximately equal chunks. When the
number of chunks is the same as the layers in the input circuit,
the input circuit is unchanged.

Returns:
List of the evaluated monomial basis terms using the scale factor
vectors.
"""
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we add error handling?

if degree < 1:
    raise ValueError("Degree must be a positive integer")
if fold_multiplier < 1:
    raise ValueError("Fold multiplier must be a positive integer")
if num_chunks is not None and num_chunks < 1:
    raise ValueError("Number of chunks must be a positive integer")

input_sample_matrix = sample_matrix(
input_circuit, degree, fold_multiplier, num_chunks
)
num_layers = len(
_get_scale_factor_vectors(
input_circuit, degree, fold_multiplier, num_chunks
)
)
try:
det = np.linalg.det(input_sample_matrix)
except RuntimeWarning: # pragma: no cover
# taken from https://stackoverflow.com/a/19317237
warnings.warn( # pragma: no cover
"To account for overflow error, required determinant of "
+ "large sample matrix is calculated through "
+ "`np.linalg.slogdet`."
)
sign, logdet = np.linalg.slogdet( # pragma: no cover
input_sample_matrix
)
det = sign * np.exp(logdet) # pragma: no cover

if np.isinf(det):
raise ValueError( # pragma: no cover
"Determinant of sample matrix cannot be calculated as "
+ "the matrix is too large. Consider chunking your"
+ " input circuit. "
)
assert det != 0.0
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we want to check whether it's close to 0.0 and add an appropriate debug message in case it is not?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Discussed this with @natestemen yesterday. We are coming back to think about edge cases like this once the general LRE implementation is complete.

I'll go ahead and create an issue to log all the todos I need to tackle in the immediate future.

coeff_list = []
# replace a row of the sample matrix with [1, 0, 0, .., 0]
for i in range(num_layers):
sample_matrix_copy = input_sample_matrix.copy()
purva-thakre marked this conversation as resolved.
Show resolved Hide resolved
sample_matrix_copy[i] = np.array([[1] + [0] * (num_layers - 1)])
coeff_list.append(
np.linalg.det(sample_matrix_copy)
purva-thakre marked this conversation as resolved.
Show resolved Hide resolved
/ np.linalg.det(input_sample_matrix)
)

return coeff_list
Loading
Loading