-
-
Notifications
You must be signed in to change notification settings - Fork 166
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 Executors #2499
Merged
Merged
LRE Executors #2499
Changes from all commits
Commits
Show all changes
16 commits
Select commit
Hold shift + click to select a range
6614641
main functions without corrected mypy errors, unit tests and docstrings
purva-thakre 3db2817
add unit tests for the decorator - check for test coverage
purva-thakre 759fbc5
fix https://github.com/unitaryfund/mitiq/issues/2500#issuecomment-234…
purva-thakre 2c35e74
mypy - remove shots
purva-thakre d0f0052
more unit tests + docstrings
purva-thakre 7708f50
dosctring args formatting
purva-thakre 003d3a1
fix https://github.com/unitaryfund/mitiq/pull/2499#discussion_r175854…
purva-thakre c970474
weird chunking failure
purva-thakre 9146116
try chunking to 2
purva-thakre d926c5c
num_chunks = 5 with test_cirq * 200
purva-thakre 595360a
push to check for test coverage
purva-thakre 41d08f5
chunking failures
purva-thakre ce46c58
split decorator unit test
purva-thakre 4561acb
cleanup
purva-thakre 3d25cd2
chunking failure again + docker failure
purva-thakre 21aeb4d
nate's suggestions
purva-thakre File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
# 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. | ||
|
||
"""Extrapolation methods for Layerwise Richardson Extrapolation (LRE)""" | ||
|
||
from functools import wraps | ||
from typing import Any, Callable, Optional, Union | ||
|
||
import numpy as np | ||
from cirq import Circuit | ||
|
||
from mitiq import QPROGRAM | ||
from mitiq.lre import ( | ||
multivariate_layer_scaling, | ||
multivariate_richardson_coefficients, | ||
) | ||
from mitiq.zne.scaling import fold_gates_at_random | ||
|
||
|
||
def execute_with_lre( | ||
input_circuit: Circuit, | ||
executor: Callable[[Circuit], float], | ||
degree: int, | ||
fold_multiplier: int, | ||
folding_method: Callable[ | ||
[QPROGRAM, float], QPROGRAM | ||
] = fold_gates_at_random, # type: ignore [has-type] | ||
num_chunks: Optional[int] = None, | ||
) -> float: | ||
r""" | ||
Defines the executor required for Layerwise Richardson | ||
Extrapolation as defined in :cite:`Russo_2024_LRE`. | ||
|
||
Note that this method only works for the multivariate extrapolation | ||
methods. It does not allows a user to choose which layers in the input | ||
circuit will be scaled. | ||
|
||
.. seealso:: | ||
|
||
If you would prefer to choose the layers for unitary | ||
folding, use :func:`mitiq.zne.scaling.layer_scaling.get_layer_folding` | ||
instead. | ||
|
||
Args: | ||
input_circuit: Circuit to be scaled. | ||
executor: Executes a circuit and returns a `float` | ||
degree: Degree of the multivariate polynomial. | ||
fold_multiplier: Scaling gap value required for unitary folding which | ||
is used to generate the scale factor vectors. | ||
folding_method: Unitary folding method. Default is | ||
:func:`fold_gates_at_random`. | ||
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: | ||
Error-mitigated expectation value | ||
|
||
""" | ||
noise_scaled_circuits = multivariate_layer_scaling( | ||
input_circuit, degree, fold_multiplier, num_chunks, folding_method | ||
) | ||
|
||
linear_combination_coeffs = multivariate_richardson_coefficients( | ||
input_circuit, degree, fold_multiplier, num_chunks | ||
) | ||
|
||
# verify the linear combination coefficients and the calculated expectation | ||
# values have the same length | ||
if len(noise_scaled_circuits) != len( # pragma: no cover | ||
linear_combination_coeffs | ||
): | ||
raise AssertionError( | ||
"The number of expectation values are not equal " | ||
+ "to the number of coefficients required for " | ||
+ "multivariate extrapolation." | ||
) | ||
|
||
lre_exp_values = [] | ||
for scaled_circuit in noise_scaled_circuits: | ||
circ_exp_val = executor(scaled_circuit) | ||
lre_exp_values.append(circ_exp_val) | ||
|
||
return np.dot(lre_exp_values, linear_combination_coeffs) | ||
|
||
|
||
def mitigate_executor( | ||
executor: Callable[[Circuit], float], | ||
degree: int, | ||
fold_multiplier: int, | ||
folding_method: Callable[ | ||
[Union[Any], float], Union[Any] | ||
] = fold_gates_at_random, | ||
num_chunks: Optional[int] = None, | ||
) -> Callable[[Circuit], float]: | ||
"""Returns a modified version of the input `executor` which is | ||
error-mitigated with layerwise richardson extrapolation (LRE). | ||
|
||
Args: | ||
input_circuit: Circuit to be scaled. | ||
executor: Executes a circuit and returns a `float` | ||
degree: Degree of the multivariate polynomial. | ||
fold_multiplier Scaling gap value required for unitary folding which | ||
is used to generate the scale factor vectors. | ||
folding_method: Unitary folding method. Default is | ||
:func:`fold_gates_at_random`. | ||
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: | ||
Error-mitigated version of the circuit executor. | ||
""" | ||
|
||
@wraps(executor) | ||
def new_executor(input_circuit: Circuit) -> float: | ||
return execute_with_lre( | ||
input_circuit, | ||
executor, | ||
degree, | ||
fold_multiplier, | ||
folding_method, | ||
num_chunks, | ||
) | ||
|
||
return new_executor | ||
|
||
|
||
def lre_decorator( | ||
degree: int, | ||
fold_multiplier: int, | ||
folding_method: Callable[[Circuit, float], Circuit] = fold_gates_at_random, | ||
num_chunks: Optional[int] = None, | ||
) -> Callable[[Callable[[Circuit], float]], Callable[[Circuit], float]]: | ||
"""Decorator which adds an error-mitigation layer based on | ||
layerwise richardson extrapolation (LRE). | ||
|
||
Args: | ||
input_circuit: Circuit to be scaled. | ||
executor: Executes a circuit and returns a `float` | ||
degree: Degree of the multivariate polynomial. | ||
fold_multiplier Scaling gap value required for unitary folding which | ||
is used to generate the scale factor vectors. | ||
folding_method: Unitary folding method. Default is | ||
:func:`fold_gates_at_random`. | ||
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: | ||
Error-mitigated decorator. | ||
""" | ||
|
||
def decorator( | ||
executor: Callable[[Circuit], float], | ||
) -> Callable[[Circuit], float]: | ||
return mitigate_executor( | ||
executor, | ||
degree, | ||
fold_multiplier, | ||
folding_method, | ||
num_chunks, | ||
) | ||
|
||
return decorator |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,129 @@ | ||
"""Unit tests for the LRE extrapolation methods.""" | ||
|
||
import re | ||
|
||
import pytest | ||
from cirq import DensityMatrixSimulator, depolarize | ||
|
||
from mitiq import benchmarks | ||
from mitiq.lre import execute_with_lre, lre_decorator, mitigate_executor | ||
from mitiq.zne.scaling import fold_all, fold_global | ||
|
||
# default circuit for all unit tests | ||
test_cirq = benchmarks.generate_rb_circuits( | ||
n_qubits=1, | ||
num_cliffords=2, | ||
)[0] | ||
|
||
|
||
# default execute function for all unit tests | ||
def execute(circuit, noise_level=0.025): | ||
"""Default executor for all unit tests.""" | ||
noisy_circuit = circuit.with_noise(depolarize(p=noise_level)) | ||
rho = DensityMatrixSimulator().simulate(noisy_circuit).final_density_matrix | ||
return rho[0, 0].real | ||
|
||
|
||
noisy_val = execute(test_cirq) | ||
ideal_val = execute(test_cirq, noise_level=0) | ||
|
||
|
||
@pytest.mark.parametrize("degree, fold_multiplier", [(2, 2), (2, 3), (3, 4)]) | ||
def test_lre_exp_value(degree, fold_multiplier): | ||
"""Verify LRE executors work as expected.""" | ||
lre_exp_val = execute_with_lre( | ||
test_cirq, | ||
execute, | ||
degree=degree, | ||
fold_multiplier=fold_multiplier, | ||
) | ||
assert abs(lre_exp_val - ideal_val) <= abs(noisy_val - ideal_val) | ||
|
||
|
||
@pytest.mark.parametrize("degree, fold_multiplier", [(2, 2), (2, 3), (3, 4)]) | ||
def test_lre_exp_value_decorator(degree, fold_multiplier): | ||
"""Verify LRE mitigated executor work as expected.""" | ||
mitigated_executor = mitigate_executor( | ||
execute, degree=2, fold_multiplier=2 | ||
) | ||
exp_val_from_mitigate_executor = mitigated_executor(test_cirq) | ||
assert abs(exp_val_from_mitigate_executor - ideal_val) <= abs( | ||
noisy_val - ideal_val | ||
) | ||
|
||
|
||
def test_lre_decorator(): | ||
"""Verify LRE decorators work as expected.""" | ||
|
||
@lre_decorator(degree=2, fold_multiplier=2) | ||
def execute(circuit, noise_level=0.025): | ||
noisy_circuit = circuit.with_noise(depolarize(p=noise_level)) | ||
rho = ( | ||
DensityMatrixSimulator() | ||
.simulate(noisy_circuit) | ||
.final_density_matrix | ||
) | ||
return rho[0, 0].real | ||
|
||
assert abs(execute(test_cirq) - ideal_val) <= abs(noisy_val - ideal_val) | ||
|
||
|
||
def test_lre_decorator_raised_error(): | ||
"""Verify an error is raised when the required parameters for the decorator | ||
are not specified.""" | ||
with pytest.raises(TypeError, match=re.escape("lre_decorator() missing")): | ||
|
||
@lre_decorator() | ||
def execute(circuit, noise_level=0.025): | ||
noisy_circuit = circuit.with_noise(depolarize(p=noise_level)) | ||
rho = ( | ||
DensityMatrixSimulator() | ||
.simulate(noisy_circuit) | ||
.final_density_matrix | ||
) | ||
return rho[0, 0].real | ||
|
||
assert abs(execute(test_cirq) - ideal_val) <= abs( | ||
noisy_val - ideal_val | ||
) | ||
|
||
|
||
def test_lre_executor_with_chunking(): | ||
purva-thakre marked this conversation as resolved.
Show resolved
Hide resolved
|
||
"""Verify the executor works as expected for chunking a large circuit into | ||
a smaller circuit.""" | ||
# define a larger circuit | ||
test_cirq = benchmarks.generate_rb_circuits(n_qubits=1, num_cliffords=12)[ | ||
0 | ||
] | ||
lre_exp_val = execute_with_lre( | ||
test_cirq, execute, degree=2, fold_multiplier=2, num_chunks=14 | ||
) | ||
assert abs(lre_exp_val - ideal_val) <= abs(noisy_val - ideal_val) | ||
|
||
|
||
@pytest.mark.parametrize("num_chunks", [(1), (2), (3), (4), (5), (6), (7)]) | ||
def test_large_circuit_with_small_chunks_poor_performance(num_chunks): | ||
"""Verify chunking performs poorly when a large number of layers are | ||
chunked into a smaller number of circuit chunks.""" | ||
# define a larger circuit | ||
test_cirq = benchmarks.generate_rb_circuits(n_qubits=1, num_cliffords=15)[ | ||
0 | ||
] | ||
lre_exp_val = execute_with_lre( | ||
test_cirq, execute, degree=2, fold_multiplier=2, num_chunks=num_chunks | ||
) | ||
assert abs(lre_exp_val - ideal_val) >= abs(noisy_val - ideal_val) | ||
|
||
|
||
@pytest.mark.parametrize("input_method", [(fold_global), (fold_all)]) | ||
def test_lre_executor_with_different_folding_methods(input_method): | ||
"""Verify the executor works as expected for using non-default unitary | ||
folding methods.""" | ||
lre_exp_val = execute_with_lre( | ||
test_cirq, | ||
execute, | ||
degree=2, | ||
fold_multiplier=2, | ||
folding_method=input_method, | ||
) | ||
assert abs(lre_exp_val - ideal_val) <= abs(noisy_val - ideal_val) |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Something more in the spirit of unit tests (and more future-proof) would be a test that only checks whether the correct functions are called, and doesn't actually run the circuit through the simulator. Here you are testing lots of things at the same time (including a Google's simulator), all of which could go wrong without giving much insights on the unit you are testing here, which is the behaviour of the new decorator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cosenal @natestemen Are either of you available for a quick call on Friday (excluding the community call)? I have been unsuccessful in trying to write a unit test that utilizes mock objects.
It's unclear to me what needs to be a mock object and what must have a pre-defined value.