From 411e234894eb6329783d032dc12c144d89b99f12 Mon Sep 17 00:00:00 2001 From: Purva Thakre <66048318+purva-thakre@users.noreply.github.com> Date: Tue, 1 Oct 2024 14:53:53 -0500 Subject: [PATCH 01/38] LRE Executors (#2499) * main functions without corrected mypy errors, unit tests and docstrings * add unit tests for the decorator - check for test coverage * fix https://github.com/unitaryfund/mitiq/issues/2500#issuecomment-2344872470 * mypy - remove shots * more unit tests + docstrings * dosctring args formatting * fix https://github.com/unitaryfund/mitiq/pull/2499#discussion_r1758547125 * weird chunking failure * try chunking to 2 * num_chunks = 5 with test_cirq * 200 * push to check for test coverage * chunking failures * split decorator unit test * cleanup * chunking failure again + docker failure * nate's suggestions --- .gitignore | 2 +- docs/source/apidoc.md | 5 ++ mitiq/lre/__init__.py | 4 +- mitiq/lre/lre.py | 170 ++++++++++++++++++++++++++++++++++++ mitiq/lre/tests/test_lre.py | 129 +++++++++++++++++++++++++++ 5 files changed, 308 insertions(+), 2 deletions(-) create mode 100644 mitiq/lre/lre.py create mode 100644 mitiq/lre/tests/test_lre.py diff --git a/.gitignore b/.gitignore index 523b7c4a7..1770047cd 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,7 @@ mitiq.egg-info/ dist/ build/ jupyter_execute/ - +.mypy_cache/ # Coverage reports coverage.xml .coverage diff --git a/docs/source/apidoc.md b/docs/source/apidoc.md index 9a3fbf10f..e9e5fe65a 100644 --- a/docs/source/apidoc.md +++ b/docs/source/apidoc.md @@ -89,6 +89,11 @@ See Ref. {cite}`Czarnik_2021_Quantum` for more details on these methods. ### Layerwise Richardson Extrapolation +```{eval-rst} +.. automodule:: mitiq.lre.lre + :members: +``` + ```{eval-rst} .. automodule:: mitiq.lre.multivariate_scaling.layerwise_folding :members: diff --git a/mitiq/lre/__init__.py b/mitiq/lre/__init__.py index 71e51c78e..353568cdd 100644 --- a/mitiq/lre/__init__.py +++ b/mitiq/lre/__init__.py @@ -10,4 +10,6 @@ from mitiq.lre.inference.multivariate_richardson import ( multivariate_richardson_coefficients, sample_matrix, -) \ No newline at end of file +) + +from mitiq.lre.lre import execute_with_lre, mitigate_executor, lre_decorator \ No newline at end of file diff --git a/mitiq/lre/lre.py b/mitiq/lre/lre.py new file mode 100644 index 000000000..c2fd7b52a --- /dev/null +++ b/mitiq/lre/lre.py @@ -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 diff --git a/mitiq/lre/tests/test_lre.py b/mitiq/lre/tests/test_lre.py new file mode 100644 index 000000000..4679e746a --- /dev/null +++ b/mitiq/lre/tests/test_lre.py @@ -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(): + """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) From 935e2876a15eb26d668fab0ea540618e027646fb Mon Sep 17 00:00:00 2001 From: nate stemen Date: Wed, 2 Oct 2024 20:53:04 -0700 Subject: [PATCH 02/38] Move class documentation to class docstrings (#2525) * move class init docstrings to class docstring * remove redundant line --- docs/source/apidoc.md | 2 +- mitiq/executor/executor.py | 16 ++--- mitiq/interface/mitiq_qiskit/transpiler.py | 9 +-- mitiq/observable/observable.py | 8 +-- mitiq/observable/pauli.py | 75 +++++++++++----------- mitiq/pec/types/types.py | 56 ++++++++-------- mitiq/zne/inference.py | 32 ++++----- 7 files changed, 88 insertions(+), 110 deletions(-) diff --git a/docs/source/apidoc.md b/docs/source/apidoc.md index e9e5fe65a..b489a3f6d 100644 --- a/docs/source/apidoc.md +++ b/docs/source/apidoc.md @@ -133,7 +133,6 @@ See Ref. {cite}`Czarnik_2021_Quantum` for more details on these methods. :members: ``` - #### Learning-based PEC ```{eval-rst} @@ -383,6 +382,7 @@ See Ref. {cite}`Czarnik_2021_Quantum` for more details on these methods. .. automodule:: mitiq.interface.mitiq_pyquil.conversions :members: ``` + #### Qibo Conversions ```{eval-rst} diff --git a/mitiq/executor/executor.py b/mitiq/executor/executor.py index 7ace20c78..3dcef180e 100644 --- a/mitiq/executor/executor.py +++ b/mitiq/executor/executor.py @@ -57,6 +57,13 @@ class Executor: """Tool for efficiently scheduling/executing quantum programs and storing the results. + + Args: + executor: A function which inputs a program and outputs a + ``mitiq.QuantumResult``, or inputs a sequence of programs and + outputs a sequence of ``mitiq.QuantumResult`` s. + max_batch_size: Maximum number of programs that can be sent in a + single batch (if the executor is batched). """ def __init__( @@ -64,15 +71,6 @@ def __init__( executor: Callable[[Union[QPROGRAM, Sequence[QPROGRAM]]], Any], max_batch_size: int = 75, ) -> None: - """Initializes an Executor. - - Args: - executor: A function which inputs a program and outputs a - ``mitiq.QuantumResult``, or inputs a sequence of programs and - outputs a sequence of ``mitiq.QuantumResult`` s. - max_batch_size: Maximum number of programs that can be sent in a - single batch (if the executor is batched). - """ self._executor = executor executor_annotation = inspect.getfullargspec(executor).annotations diff --git a/mitiq/interface/mitiq_qiskit/transpiler.py b/mitiq/interface/mitiq_qiskit/transpiler.py index 32e72efd2..1a966d291 100644 --- a/mitiq/interface/mitiq_qiskit/transpiler.py +++ b/mitiq/interface/mitiq_qiskit/transpiler.py @@ -37,14 +37,12 @@ class ApplyMitiqLayout(TransformationPass): # type: ignore qubits by applying the Layout given in `property_set`. Requires either of passes to set/select Layout, e.g. `SetLayout`, `TrivialLayout`. Assumes the Layout has full physical qubits. + + Args: + new_qregs: The new quantum registers for the circuit. """ def __init__(self, new_qregs: List[QuantumRegister]) -> None: - """ApplyMitiqLayout constructor. - - Args: - new_qregs: The new quantum registers for the circuit. - """ super().__init__() self._new_qregs = new_qregs @@ -104,7 +102,6 @@ class ClearLayout(TransformationPass): # type: ignore """Clears the layout of the DAGCircuit""" def __init__(self) -> None: - """ClearLayout""" super().__init__() def run(self, dag: DAGCircuit) -> None: diff --git a/mitiq/observable/observable.py b/mitiq/observable/observable.py index 09661e29a..ce7d978bc 100644 --- a/mitiq/observable/observable.py +++ b/mitiq/observable/observable.py @@ -20,15 +20,11 @@ class Observable: """A quantum observable typically used to compute its mitigated expectation value. + Args: + paulis: PauliStrings used to define the observable. """ def __init__(self, *paulis: PauliString) -> None: - """Initializes an `Observable` with :class:`.PauliString` objects. - - Args: - paulis: PauliStrings used to define the observable. - - """ self._paulis = _combine_duplicate_pauli_strings(paulis) self._groups: List[PauliStringCollection] self._ngroups: int diff --git a/mitiq/observable/pauli.py b/mitiq/observable/pauli.py index 56d4e3a96..bf68af671 100644 --- a/mitiq/observable/pauli.py +++ b/mitiq/observable/pauli.py @@ -18,9 +18,21 @@ class PauliString: - """A `PauliString` is a (tensor) product of single-qubit Pauli gates I, X, - Y, and Z, with a leading (real or complex) coefficient. `PauliString`s can - be measured in any `mitiq.QPROGRAM`. + """A ``PauliString`` is a (tensor) product of single-qubit Pauli gates + :math:`I, X, Y`, and :math:`Z`, with a leading (real or complex) + coefficient. ``PauliString`` objects can be measured in any + ``mitiq.QPROGRAM``. + + Args: + spec: String specifier of the PauliString. Should only contain + characters 'I', 'X', 'Y', and 'Z'. + coeff: Coefficient of the PauliString. + support: Qubits the ``spec`` acts on, if provided. + + Examples: + >>> PauliString(spec="IXY") # X(1)*Y(2) + >>> PauliString(spec="ZZ", coeff=-0.5) # -0.5*Z(0)*Z(1) + >>> PauliString(spec="XZ", support=(10, 17)) # X(10)*Z(17) """ _string_to_gate_map = {"I": cirq.I, "X": cirq.X, "Y": cirq.Y, "Z": cirq.Z} @@ -31,19 +43,6 @@ def __init__( coeff: complex = 1.0, support: Optional[Sequence[int]] = None, ) -> None: - """Initialize a PauliString. - - Args: - spec: String specifier of the PauliString. Should only contain - characters 'I', 'X', 'Y', and 'Z'. - coeff: Coefficient of the PauliString. - support: Qubits the ``spec`` acts on, if provided. - - Examples: - >>> PauliString(spec="IXY") # X(1)*Y(2) - >>> PauliString(spec="ZZ", coeff=-0.5) # -0.5*Z(0)*Z(1) - >>> PauliString(spec="XZ", support=(10, 17)) # X(10)*Z(17) - """ if not set(spec).issubset(set(self._string_to_gate_map.keys())): raise ValueError( f"One or more invalid characters in spec {spec}. Valid " @@ -181,34 +180,32 @@ def __repr__(self) -> str: class PauliStringCollection: """A collection of PauliStrings that qubit-wise commute and so can be measured with a single circuit. + + Args: + paulis: PauliStrings to add to the collection. + check_precondition: If True, raises an error if some of the + ``PauliString`` objects do not qubit-wise commute. + + Example: + >>> pcol = PauliStringCollection( + >>> PauliString(spec="X"), + >>> PauliString(spec="IZ", coeff=-2.2) + >>> ) + >>> print(pcol) # X(0) + (-2.2+0j)*Z(1) + >>> print(pcol.support()) # {0, 1} + >>> + >>> # XZ qubit-wise commutes with X(0) and Z(1), so can be added. + >>> print(pcol.can_add(PauliString(spec="XZ"))) # True. + >>> pcol.add(PauliString(spec="XZ")) + >>> print(pcol) # X(0) + (-2.2+0j)*Z(1) + X(0)*Z(1) + >>> + >>> # Z(0) doesn't qubit-wise commute with X(0), so can't be added. + >>> print(pcol.can_add(PauliString(spec="Z"))) # False. """ def __init__( self, *paulis: PauliString, check_precondition: bool = True ) -> None: - """Initializes a `PauliStringCollection`. - - Args: - paulis: PauliStrings to add to the collection. - check_precondition: If True, raises an error if some of the - `PauliString`s do not qubit-wise commute. - - Example: - >>> pcol = PauliStringCollection( - >>> PauliString(spec="X"), - >>> PauliString(spec="IZ", coeff=-2.2) - >>> ) - >>> print(pcol) # X(0) + (-2.2+0j)*Z(1) - >>> print(pcol.support()) # {0, 1} - >>> - >>> # XZ qubit-wise commutes with X(0) and Z(1), so can be added. - >>> print(pcol.can_add(PauliString(spec="XZ"))) # True. - >>> pcol.add(PauliString(spec="XZ")) - >>> print(pcol) # X(0) + (-2.2+0j)*Z(1) + X(0)*Z(1) - >>> - >>> # Z(0) doesn't qubit-wise commute with X(0), so can't be added. - >>> print(pcol.can_add(PauliString(spec="Z"))) # False. - """ self._paulis_by_weight: Dict[int, TCounter[PauliString]] = dict() self.add(*paulis, check_precondition=check_precondition) diff --git a/mitiq/pec/types/types.py b/mitiq/pec/types/types.py index 515b323a6..8325aa33c 100644 --- a/mitiq/pec/types/types.py +++ b/mitiq/pec/types/types.py @@ -24,7 +24,18 @@ class NoisyOperation: """An operation (or sequence of operations) which a noisy quantum computer - can actually implement.p + can actually implement. + + Args: + circuit: A short circuit which, when executed on a given noisy + quantum computer, generates a noisy channel. It typically + contains a single-gate or a short sequence of gates. + channel_matrix: Superoperator representation of the noisy channel + which is generated when executing the input ``circuit`` on the + noisy quantum computer. + + Raises: + TypeError: If ``ideal`` is not a ``QPROGRAM``. """ def __init__( @@ -32,19 +43,6 @@ def __init__( circuit: QPROGRAM, channel_matrix: Optional[npt.NDArray[np.complex64]] = None, ) -> None: - """Initializes a NoisyOperation. - - Args: - circuit: A short circuit which, when executed on a given noisy - quantum computer, generates a noisy channel. It typically - contains a single-gate or a short sequence of gates. - channel_matrix: Superoperator representation of the noisy channel - which is generated when executing the input ``circuit`` on the - noisy quantum computer. - - Raises: - TypeError: If ``ideal`` is not a ``QPROGRAM``. - """ self._native_circuit = deepcopy(circuit) try: @@ -135,6 +133,20 @@ def __init__(self, *args: Any, **kwargs: Any) -> None: class OperationRepresentation: """A decomposition (basis expansion) of an operation or sequence of operations in a basis of noisy, implementable operations. + + Args: + ideal: The ideal operation desired to be implemented. + basis_expansion: Representation of the ideal operation in a basis + of ``NoisyOperation`` objects. + is_qubit_dependent: If True, the representation + corresponds to the operation on the specific qubits defined in + ``ideal``. If False, the representation is valid for the same + gate even if acting on different qubits from those specified in + ``ideal``. + + Raises: + TypeError: If all keys of ``basis_expansion`` are not instances of + ``NoisyOperation`` objects. """ def __init__( @@ -144,22 +156,6 @@ def __init__( coeffs: List[float], is_qubit_dependent: bool = True, ) -> None: - """Initializes an OperationRepresentation. - - Args: - ideal: The ideal operation desired to be implemented. - basis_expansion: Representation of the ideal operation in a basis - of `NoisyOperation`s. - is_qubit_dependent: If True, the representation - corresponds to the operation on the specific qubits defined in - `ideal`. If False, the representation is valid for the same - gate even if acting on different qubits from those specified in - `ideal`. - - Raises: - TypeError: If all keys of `basis_expansion` are not instances of - `NoisyOperation`s. - """ if not all(isinstance(o, NoisyOperation) for o in noisy_operations): raise TypeError( "All elements of `noisy_operations` must be " diff --git a/mitiq/zne/inference.py b/mitiq/zne/inference.py index 87ebb1712..409b08acf 100644 --- a/mitiq/zne/inference.py +++ b/mitiq/zne/inference.py @@ -420,6 +420,19 @@ class BatchedFactory(Factory, ABC): Specific (non-adaptive) extrapolation algorithms are derived from this class by defining the `reduce` method. + + Args: + scale_factors: Sequence of noise scale factors at which expectation + values should be measured. + shot_list: Optional sequence of integers corresponding to the + number of samples taken for each expectation value. If this + argument is explicitly passed to the factory, it must have the + same length of scale_factors and the executor function must + accept "shots" as a valid keyword argument. + + Raises: + ValueError: If the number of scale factors is less than 2. + TypeError: If shot_list is provided and has any non-integer values. """ def __init__( @@ -427,21 +440,6 @@ def __init__( scale_factors: Sequence[float], shot_list: Optional[List[int]] = None, ) -> None: - """Constructs a BatchedFactory. - - Args: - scale_factors: Sequence of noise scale factors at which expectation - values should be measured. - shot_list: Optional sequence of integers corresponding to the - number of samples taken for each expectation value. If this - argument is explicitly passed to the factory, it must have the - same length of scale_factors and the executor function must - accept "shots" as a valid keyword argument. - - Raises: - ValueError: If the number of scale factors is less than 2. - TypeError: If shot_list is provided and has any non-integer values. - """ if len(scale_factors) < 2: raise ValueError("At least 2 scale factors are necessary.") @@ -805,7 +803,6 @@ def __init__( order: int, shot_list: Optional[List[int]] = None, ) -> None: - """Instantiates a new object of this Factory class.""" if order > len(scale_factors) - 1: raise ValueError( "The extrapolation order cannot exceed len(scale_factors) - 1." @@ -1127,7 +1124,6 @@ def __init__( avoid_log: bool = False, shot_list: Optional[List[int]] = None, ) -> None: - """Instantiate an new object of this Factory class.""" super(ExpFactory, self).__init__(scale_factors, shot_list) if not (asymptote is None or isinstance(asymptote, float)): raise ValueError( @@ -1247,7 +1243,6 @@ def __init__( avoid_log: bool = False, shot_list: Optional[List[int]] = None, ) -> None: - """Instantiates a new object of this Factory class.""" super(PolyExpFactory, self).__init__(scale_factors, shot_list) if not (asymptote is None or isinstance(asymptote, float)): raise ValueError( @@ -1519,7 +1514,6 @@ def __init__( avoid_log: bool = False, max_scale_factor: float = 6.0, ) -> None: - """Instantiate a new object of this Factory class.""" super(AdaExpFactory, self).__init__() if not (asymptote is None or isinstance(asymptote, float)): raise ValueError( From b7fe9d7dde35be64b2ff63e9007d0ae0bf90289a Mon Sep 17 00:00:00 2001 From: nate stemen Date: Thu, 3 Oct 2024 07:43:04 -0700 Subject: [PATCH 03/38] remove automated issue greeting (#2526) --- .github/workflows/greeting.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/greeting.yml b/.github/workflows/greeting.yml index e0c5a11d0..3a2e0faa2 100644 --- a/.github/workflows/greeting.yml +++ b/.github/workflows/greeting.yml @@ -10,6 +10,3 @@ jobs: with: repo-token: ${{ secrets.GITHUB_TOKEN }} pr-message: 'Hello @${{ github.actor }}, thank you for submitting a PR to Mitiq! We will respond as soon as possible, and if you have any questions in the meantime, you can ask us on the [Unitary Fund Discord](http://discord.unitary.fund).' - issue-message: | - Hello @${{ github.actor }}, thank you for your interest in Mitiq! - If this is a bug report, please provide screenshots and/or **minimum viable code to reproduce your issue**, so we can do our best to help get it fixed. If you have any questions in the meantime, you can also ask us on the [Unitary Fund Discord](http://discord.unitary.fund). From 06849ef39eeba9506d93fb060cae86364758ad6f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 5 Oct 2024 08:31:50 -0500 Subject: [PATCH 04/38] Bump pyqrack from 1.30.24 to 1.30.30 (#2521) Bumps [pyqrack](https://github.com/vm6502q/pyqrack) from 1.30.24 to 1.30.30. - [Release notes](https://github.com/vm6502q/pyqrack/releases) - [Commits](https://github.com/vm6502q/pyqrack/compare/v1.30.24...v1.30.30) --- updated-dependencies: - dependency-name: pyqrack dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements-dev.txt b/requirements/requirements-dev.txt index 0c0804355..82c3039f1 100644 --- a/requirements/requirements-dev.txt +++ b/requirements/requirements-dev.txt @@ -28,4 +28,4 @@ bqskit==1.1.1 seaborn==0.13.0 stim==1.13.0 stimcirq==1.13.0 -pyqrack==1.30.24 \ No newline at end of file +pyqrack==1.30.30 \ No newline at end of file From d7f3b5bb3d3c6afce235cb4925c259fa53fe9600 Mon Sep 17 00:00:00 2001 From: Purva Thakre <66048318+purva-thakre@users.noreply.github.com> Date: Mon, 7 Oct 2024 07:16:42 -0500 Subject: [PATCH 05/38] Update broken link in the readme (#2528) * new link * typo in zne user guide * Update README.md Co-authored-by: nate stemen --------- Co-authored-by: nate stemen --- README.md | 2 +- docs/source/guide/zne-2-use-case.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 0abe48457..6dee8d2f4 100644 --- a/README.md +++ b/README.md @@ -147,7 +147,7 @@ You can use Mitiq with any backend you have access to that can interface with su If you use Mitiq in your research, please reference the [Mitiq whitepaper](https://quantum-journal.org/papers/q-2022-08-11-774/) using the bibtex entry found in [`CITATION.bib`](https://github.com/unitaryfund/mitiq/blob/main/CITATION.bib). -A list of papers citing Mitiq can be found on [Google Scholar](https://scholar.google.com/scholar?cites=12810395086731011605) / [Semantic Scholar](https://api.semanticscholar.org/CorpusID:221555755?). +A list of papers citing Mitiq can be found on [Google Scholar](https://scholar.google.com/scholar?oi=bibs&hl=en&cites=1985661232443186918) / [Semantic Scholar](https://api.semanticscholar.org/CorpusID:221555755?). ## License diff --git a/docs/source/guide/zne-2-use-case.md b/docs/source/guide/zne-2-use-case.md index 2d4c5c21d..b657ec815 100644 --- a/docs/source/guide/zne-2-use-case.md +++ b/docs/source/guide/zne-2-use-case.md @@ -17,7 +17,7 @@ kernelspec: Zero noise extrapolation is one of the simplest error mitigation techniques and, in many practical situations, it can be applied with a relatively small sampling cost. The main advantage of ZNE is that the technique can be applied without a detailed knowledge of -the undelying noise model. Therefore it can be a good option in situations where +the underlying noise model. Therefore it can be a good option in situations where tomography is impractical. From 0526250ba8bf64a1292267275bd3192975acfec6 Mon Sep 17 00:00:00 2001 From: nate stemen Date: Mon, 7 Oct 2024 20:54:18 -0700 Subject: [PATCH 06/38] Raise TypeError when a dictionary is passed to MeasurementResult constructor (#2523) * add missing license statement * raise error when passing dict to MeasurementResult it's natural to think that you can pass results directly from some sort of experiment into the MeasurementResult class. * add instatiation example * upgrade note to warning --- mitiq/tests/test_measurement_result.py | 5 +++++ mitiq/tests/test_typing.py | 5 +++++ mitiq/typing.py | 12 +++++++++++- 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/mitiq/tests/test_measurement_result.py b/mitiq/tests/test_measurement_result.py index 676187cc2..925ef16a4 100644 --- a/mitiq/tests/test_measurement_result.py +++ b/mitiq/tests/test_measurement_result.py @@ -38,6 +38,11 @@ def test_measurement_result_not_bits(): MeasurementResult(result=[[0, 0], [0, 1], [-1, 0]]) +def test_measurement_result_invoked_with_dict(): + with pytest.raises(TypeError, match="from_counts"): + MeasurementResult({"001": 123, "010": 456}) + + def test_filter_qubits(): result = MeasurementResult([[0, 0, 1], [0, 1, 0], [1, 0, 0]]) assert np.allclose(result.filter_qubits([0]), np.array([[0], [0], [1]])) diff --git a/mitiq/tests/test_typing.py b/mitiq/tests/test_typing.py index 58f15b198..fd6b25dd0 100644 --- a/mitiq/tests/test_typing.py +++ b/mitiq/tests/test_typing.py @@ -1,3 +1,8 @@ +# 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. + import os from mitiq.typing import SUPPORTED_PROGRAM_TYPES diff --git a/mitiq/typing.py b/mitiq/typing.py index 8fddbe463..37e9e0584 100644 --- a/mitiq/typing.py +++ b/mitiq/typing.py @@ -112,7 +112,12 @@ class MeasurementResult: ``tuple(range(self.nqubits))``, where ``self.nqubits`` is the bitstring length deduced from ``result``. - Note: + Example: + >>> mr = MeasurementResult(["001", "010", "001"]) + >>> mr.get_counts() + {'001': 2, '010': 1} + + Warning: Use caution when selecting the default option for ``qubit_indices``, especially when estimating an :class:`.Observable` acting on a subset of qubits. In this case Mitiq @@ -125,6 +130,11 @@ class MeasurementResult: def __post_init__(self) -> None: # Validate arguments + if isinstance(self.result, dict): + raise TypeError( + "Use the MeasurementResult.from_counts method to instantiate " + "a MeasurementResult object from a dictionary." + ) symbols = set(b for bits in self.result for b in bits) if not (symbols.issubset({0, 1}) or symbols.issubset({"0", "1"})): raise ValueError("Bitstrings should look like '011' or [0, 1, 1].") From 66feaf0fe38f19759636e7f535feb82f2e539475 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 16:28:30 +0200 Subject: [PATCH 07/38] Bump pyscf from 2.6.2 to 2.7.0 (#2518) Bumps [pyscf](https://github.com/pyscf/pyscf) from 2.6.2 to 2.7.0. - [Release notes](https://github.com/pyscf/pyscf/releases) - [Changelog](https://github.com/pyscf/pyscf/blob/master/CHANGELOG) - [Commits](https://github.com/pyscf/pyscf/compare/v2.6.2...v2.7.0) --- updated-dependencies: - dependency-name: pyscf dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements-dev.txt b/requirements/requirements-dev.txt index 82c3039f1..5b0aee7e4 100644 --- a/requirements/requirements-dev.txt +++ b/requirements/requirements-dev.txt @@ -21,7 +21,7 @@ sphinx-gallery==0.15.0 nbsphinx==0.9.3 matplotlib==3.8.1 pandas==2.1.3 -pyscf==2.6.2; sys_platform != 'win32' +pyscf==2.7.0; sys_platform != 'win32' openfermion==1.6.1; sys_platform != 'win32' openfermionpyscf==0.5; sys_platform != 'win32' bqskit==1.1.1 From bc8fdf93def215a127888a7952cd28406ab73ecd Mon Sep 17 00:00:00 2001 From: nate stemen Date: Wed, 9 Oct 2024 22:21:13 -0700 Subject: [PATCH 08/38] add QOSS survey banner (#2533) --- docs/source/conf.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/source/conf.py b/docs/source/conf.py index 0f52e1c76..6e4440250 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -393,6 +393,9 @@ def get_incollection_template(self, e): html_extra_path = ["robots.txt"] html_theme_options = { + "announcement": "The Unitary Fund 2024 Quantum Open Source Software Survey\ + is here! Take \ + the survey now!", "icon_links": [ { "name": "Source Repository", From 7e4fb9230f62eb2e64c53bf2009bc5c4ddc6815d Mon Sep 17 00:00:00 2001 From: Purva Thakre <66048318+purva-thakre@users.noreply.github.com> Date: Thu, 10 Oct 2024 22:35:32 -0500 Subject: [PATCH 09/38] Add theory, intro and use case pages of LRE user guide (#2522) * draft 1 * vincent + nate feedback * remove : for warning block * Apply suggestions from code review Co-authored-by: nate stemen * nate's feedback round 2 * Apply suggestions from code review Co-authored-by: nate stemen * clarify theory sections * gen monomial terms * Add intro section to LRE docs (#2535) * add intro and use case pages Co-Authored-By: Purva Thakre * clean up intro/use case * clarify depth comment * wordsmithing --------- Co-authored-by: Purva Thakre * change wording of Bi matrix * cleanup first section * fix l/L typo --------- Co-authored-by: nate stemen Co-authored-by: Purva Thakre --- docs/source/guide/guide.md | 3 +- docs/source/guide/lre-1-intro.md | 152 ++++++++++++++++++++++++++++ docs/source/guide/lre-2-use-case.md | 35 +++++++ docs/source/guide/lre-5-theory.md | 95 +++++++++++++++++ docs/source/guide/lre.md | 28 +++++ 5 files changed, 312 insertions(+), 1 deletion(-) create mode 100644 docs/source/guide/lre-1-intro.md create mode 100644 docs/source/guide/lre-2-use-case.md create mode 100644 docs/source/guide/lre-5-theory.md create mode 100644 docs/source/guide/lre.md diff --git a/docs/source/guide/guide.md b/docs/source/guide/guide.md index 40528efa7..d75a3571c 100644 --- a/docs/source/guide/guide.md +++ b/docs/source/guide/guide.md @@ -8,11 +8,12 @@ core-concepts.md zne.md pec.md cdr.md -shadows.md ddd.md +lre.md rem.md qse.md pt.md +shadows.md error-mitigation.md glossary.md ``` diff --git a/docs/source/guide/lre-1-intro.md b/docs/source/guide/lre-1-intro.md new file mode 100644 index 000000000..660483730 --- /dev/null +++ b/docs/source/guide/lre-1-intro.md @@ -0,0 +1,152 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.11.1 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# How do I use LRE? + +LRE works in two main stages: generate noise-scaled circuits via layerwise scaling, and apply inference to resulting measurements post-execution. + +This workflow can be executed by a single call to {func}`.execute_with_lre`. +If more control is needed over the protocol, Mitiq provides {func}`.multivariate_layer_scaling` and {func}`.multivariate_richardson_coefficients` to handle the first and second steps respectively. + +```{danger} +LRE is currently compatible with quantum programs written using `cirq`. +Work on making this technique compatible with other frontends is ongoing. 🚧 +``` + +## Problem Setup + +To demonstrate the use of LRE, we'll first define a quantum circuit, and a method of executing circuits for demonstration purposes. + +For simplicity, we define a circuit whose unitary compiles to the identity operation. +Here we will use a randomized benchmarking circuit on a single qubit, visualized below. + +```{code-cell} ipython3 +from mitiq import benchmarks + + +circuit = benchmarks.generate_rb_circuits(n_qubits=1, num_cliffords=3)[0] + +print(circuit) +``` + +We define an [executor](executors.md) which simulates the input circuit subjected to depolarizing noise, and returns the probability of measuring the ground state. +By altering the value for `noise_level`, ideal and noisy expectation values can be obtained. + +```{code-cell} ipython3 +from cirq import DensityMatrixSimulator, depolarize + + +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 +``` + +Compare the noisy and ideal expectation values: + +```{code-cell} ipython3 +noisy = execute(circuit) +ideal = execute(circuit, noise_level=0.0) +print(f"Error without mitigation: {abs(ideal - noisy) :.5f}") +``` + +## Apply LRE directly + +With the circuit and executor defined, we just need to choose the polynomial extrapolation degree as well as the fold multiplier. + +```{code-cell} ipython3 +from mitiq.lre import execute_with_lre + + +degree = 2 +fold_multiplier = 3 + +mitigated = execute_with_lre( + circuit, + execute, + degree=degree, + fold_multiplier=fold_multiplier, +) + +print(f"Error with mitigation (LRE): {abs(ideal - mitigated):.{3}}") +``` + +As you can see, the technique is extremely simple to apply, and no knowledge of the hardware/simulator noise is required. + +## Step by step application of LRE + +In this section we demonstrate the use of {func}`.multivariate_layer_scaling` and {func}`.multivariate_richardson_coefficients` for those who might want to inspect the intermediary circuits, and have more control over the protocol. + +### Create noise-scaled circuits + +We start by creating a number of noise-scaled circuits which we will pass to the executor. + +```{code-cell} ipython3 +from mitiq.lre import multivariate_layer_scaling + + +noise_scaled_circuits = multivariate_layer_scaling(circuit, degree, fold_multiplier) +num_scaled_circuits = len(noise_scaled_circuits) + +print(f"total number of noise-scaled circuits for LRE = {num_scaled_circuits}") +print( + f"Average circuit depth = {sum(len(circuit) for circuit in noise_scaled_circuits) / num_scaled_circuits}" +) +``` + +As you can see, the noise scaled circuits are on average much longer than the original circuit. +An example noise-scaled circuit is shown below. + +```{code-cell} ipython3 +noise_scaled_circuits[3] +``` + +With the many noise-scaled circuits in hand, we can run them through our executor to obtain the expectation values. + +```{code-cell} ipython3 +noise_scaled_exp_values = [ + execute(circuit) for circuit in noise_scaled_circuits +] +``` + +### Classical inference + +The penultimate step here is to fetch the coefficients we'll use to combine the noisy data we obtained above. +The astute reader will note that we haven't defined or used a `degree` or `fold_multiplier` parameter, and this is where they are both needed. + +```{code-cell} ipython3 +from mitiq.lre import multivariate_richardson_coefficients + + +coefficients = multivariate_richardson_coefficients( + circuit, + fold_multiplier=fold_multiplier, + degree=degree, +) +``` + +Each noise scaled circuit has a coefficient of linear combination and a noisy expectation value associated with it. + +### Combine the results + +```{code-cell} ipython3 +mitigated = sum( + exp_val * coeff + for exp_val, coeff in zip(noise_scaled_exp_values, coefficients) +) +print( + f"Error with mitigation (LRE): {abs(ideal - mitigated):.{3}}" +) +``` + +As you can see we again see a nice improvement in the accuracy using a two stage application of LRE. diff --git a/docs/source/guide/lre-2-use-case.md b/docs/source/guide/lre-2-use-case.md new file mode 100644 index 000000000..12b1d1395 --- /dev/null +++ b/docs/source/guide/lre-2-use-case.md @@ -0,0 +1,35 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.10.3 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +# When should I use LRE? + +## Advantages + +Just as in ZNE, LRE can also be applied without a detailed knowledge of the underlying noise model as the effectiveness of the technique depends on the choice of scale factors. +Thus, LRE is useful in scenarios where tomography is impractical. + +The sampling overhead is flexible wherein the cost can be reduced by using larger values for the fold multiplier (used to +create the noise-scaled circuits) or by chunking a larger circuit to fold groups of layers of circuits instead of each one individually. + +## Disadvantages + +When using a large circuit, the number of noise scaled circuits grows polynomially such that the execution time rises because we require the sample matrix to be a square matrix (more details in the [theory](lre-5-theory.md) section). + +When reducing the sampling cost by using a larger fold multiplier, the bias for polynomial extrapolation increases as one moves farther away from the zero-noise limit. + +Chunking a large circuit with a lower number of chunks to reduce the sampling cost can reduce the performance of LRE. +In ZNE parlance, this is equivalent to local folding faring better than global folding in LRE when we use a higher number of chunks in LRE. + +```{attention} +We are currently investigating the issue related to the performance of chunking large circuits. +``` diff --git a/docs/source/guide/lre-5-theory.md b/docs/source/guide/lre-5-theory.md new file mode 100644 index 000000000..2fa960bf2 --- /dev/null +++ b/docs/source/guide/lre-5-theory.md @@ -0,0 +1,95 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.11.4 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# What is the theory behind LRE? + +Similar to [ZNE](zne.md), LRE works in two steps: + +- **Step 1:** Intentionally create multiple noise-scaled but logically equivalent circuits by scaling each layer or chunk of the input circuit through unitary folding. + +- **Step 2:** Extrapolate to the noiseless limit using multivariate richardson extrapolation. + +The noise-scaled circuits in ZNE are scaled by the user choosing which layers of the input circuit to fold whereas in LRE +each noise-scaled circuit scales the layers in the input circuit in a specific pattern. +LRE leverages the flexible configuration space of layerwise unitary folding, allowing for a more nuanced mitigation of errors by treating the noise level of each layer of the quantum circuit as an independent variable. + +## Step 1: Create noise-scaled circuits + +The goal is to create noise-scaled circuits of different depths where the layers in each circuit are scaled in a specific pattern as a result of [unitary folding](zne-5-theory.md). +This pattern is described by the vector of scale factor vectors which are generated after the fold multiplier and degree for multivariate Richardson extrapolation are chosen. + +Suppose we're interested in the value of some observable of a circuit $C$ that has $l$ layers. +For each layer $0 \leq L \leq l$ we can choose a scale factor for how much to scale that particular layer. +Thus a vector $\lambda \in \mathbb{R}^l_+$ corresponds to a folding configuration where $\lambda_0$ corresponds to the scale factor for the first layer, and $\lambda_{l - 1}$ is the scale factor to apply on the circuits final layer. + +Fix the number of noise-scaled circuits we wish to generate at $M\in\mathbb{N}$. +Define $\Lambda = (λ_1, λ_2, \ldots, λ_M)^T$ to be the collection of scale factors and let $(C_{λ_1}, C_{λ_2}, \ldots, C_{λ_M})^T$ denote the noise-scaled circuits corresponding to each scale factor. + +After $d$ is fixed as the degree of the multivariate polynomial, we define $M_j(λ_i, d)$ to be the terms in the polynomial arranged in increasing order. +In general, the number of monomial terms with $l$ variables up to degree $d$ can be determined +through the [stars and bars method](https://en.wikipedia.org/wiki/Stars_and_bars_%28combinatorics%29). + +For example, if $C$ has 2 layers, the degree of the extrapolating polynomial is 2, the basis of monomials contains 6 terms: $\{1, λ_1, λ_2, {λ_1}^2, λ_1 \cdot λ_2, {λ_2}^2 \}$. + +$$ +\text{total number of terms in the monomial basis with max degree } d = \binom{d + l}{d} +$$ + +As the choice for the degree of the extrapolating polynomial is 2, we search for the number of terms with total degree 2 using the following formula: + +$$ +\text{number of terms in the monomial basis with total degree } d = \binom{d + l - 1}{d} +$$ + +Terms with total degree 2 are 3 calculated by $\binom{2 + 2 -1}{2} = 3$ and correspond to $\{{λ_1}^2, λ_1 \cdot λ_2, {λ_2}^2 \}$. + +Similarly, number of terms with total degree 1 and 0 can be calculated as $\binom{1 + 2 -1}{1} = 2:\{λ_1, λ_2\}$ and $\binom{0 + 2 -1}{0}= 1: \{1\}$ respectively. + +These terms in the monomial basis define the rows of the square sample matrix as shown below: + +$$ +\mathbf{A}(\Lambda, d) = +\begin{bmatrix} + M_1(λ_1, d) & M_2(λ_1, d) & \cdots & M_N(λ_1, d) \\ + M_1(λ_2, d) & M_2(λ_2, d) & \cdots & M_N(λ_2, d) \\ + \vdots & \vdots & \ddots & \vdots \\ + M_1(λ_N, d) & M_2(λ_N, d) & \cdots & M_N(λ_N, d) +\end{bmatrix} +$$ + +For our example circuit of $l=2$ and $d=2$, each row defined by the generic monomial terms $\{M_1(λ_i, d), M_2(λ_i, d), \ldots, M_N(λ_i, d)\}$ in the sample matrix $\mathbf{A}$ will instead be replaced by $\{1, λ_1, λ_2, {λ_1}^2, λ_1 \cdot λ_2, {λ_2}^2 \}$. + +Here, each monomial term in the sample matrix $\mathbf{A}$ is then evaluated using the values in the scale factor vectors. In Step 2, this sample matrix will be utilized to obtain our mitigated expectation value. + +## Step 2: Extrapolate to the noiseless limit + +Each noise scaled circuit $C_{λ_i}$ has an expectation value $\langle O(λ_i) \rangle$ associated with it such that we can define a vector of the noisy expectation values $z = (\langle O(λ_1) \rangle, \langle O(λ_2) \rangle, \ldots, \langle O(λ_M)\rangle)^T$. +These values can then be combined via a linear combination to estimate the ideal value $variable$. + +$$ +O_{\mathrm{LRE}} = \sum_{i=1}^{M} \eta_i \langle O(λ_i) \rangle. +$$ + +Finding the coefficients in the linear combination becomes a problem solvable through a system of linear equations $\mathbf{A} c = z$ where $c$ is the coefficients vector $(\eta_1, \eta_2, \ldots, \eta_N)^T$, $z$ is the vector of the noisy expectation values and $\mathbf{A}$ is the sample matrix evaluated using the values in the scale factor vectors. + +The [general multivariate Lagrange interpolation polynomial](https://www.siam.org/media/wkvnvame/a_simple_expression_for_multivariate.pdf) is defined by a new matrix $\mathbf{B}_i$ obtained by replacing the $i$-th row of the sample matrix $\mathbf{A}$ with monomial terms evaluated using the generic variable λ. Thus, matrix $\mathbf{B}_i$ represents an interpolating polynomial in variable λ of degree $d$. As we only need to find the noiseless expectation value, we can skip calculating the full vector of linear combination coefficients if we use the [Lagrange interpolation formula](https://files.eric.ed.gov/fulltext/EJ1231189.pdf) evaluated at $λ = 0$ i.e. the zero-noise limit. + +To get the matrix $\mathbf{B}_i(\mathbf{0})$, replace the $i$-th row of the sample matrix $\mathbf{A}$ by $\mathbf{e}_i=(1, 0, \ldots, 0)$ where except $M_1(0, d) = 1$ all the other monomial terms are zero when $λ=0$. + +$$ +O_{\rm LRE} = \sum_{i=1}^M \langle O (\boldsymbol{\lambda}_i)\rangle \frac{\det \left(\mathbf{B}_i (\boldsymbol{0}) \right)}{\det \left(\mathbf{A}\right)} +$$ + +To summarize, based on a user's choice of degree of extrapolating polynomial for some circuit, expectation values from noise scaled circuits created in a specific pattern along with multivariate Lagrange interpolation of the sample matrix evaluated using the scale factor vectors are used to find error mitigated expectation value. + +Additional details on the LRE functionality are available in the [API-doc](https://mitiq.readthedocs.io/en/stable/apidoc.html#module-mitiq.lre.multivariate_scaling.layerwise_folding). diff --git a/docs/source/guide/lre.md b/docs/source/guide/lre.md new file mode 100644 index 000000000..b9a54e121 --- /dev/null +++ b/docs/source/guide/lre.md @@ -0,0 +1,28 @@ +```{warning} +The user guide for LRE in Mitiq is currently under construction. +``` + +# Layerwise Richardson Extrapolation + +Layerwise Richardson Extrapolation (LRE), an error mitigation technique, introduced in +{cite}`Russo_2024_LRE` extends the ideas found in ZNE by allowing users to create multiple noise-scaled variations of the input +circuit such that the noiseless expectation value is extrapolated from the execution of each +noisy circuit. + +Layerwise Richardson Extrapolation (LRE), an error mitigation technique, introduced in +{cite}`Russo_2024_LRE` works by creating multiple noise-scaled variations of the input +circuit such that the noiseless expectation value is extrapolated from the execution of each +noisy circuit (see the section [What is the theory behind LRE?](lre-5-theory.md)). Compared to +Zero-Noise Extrapolation, this technique treats the noise in each layer of the circuit +as an independent variable to be scaled and then extrapolated independently. + +You can get started with LRE in Mitiq with the following sections of the user guide: + +```{toctree} +--- +maxdepth: 1 +--- +lre-1-intro.md +lre-2-use-case.md +lre-5-theory.md +``` From 445af55e54e34f74c74d827ebf31f4d27747796f Mon Sep 17 00:00:00 2001 From: nate stemen Date: Fri, 11 Oct 2024 02:48:52 -0700 Subject: [PATCH 10/38] 0.40.0 release (#2536) --- CHANGELOG.md | 55 ++++++++++++++++++++++++++++++++++++++++++++- VERSION.txt | 2 +- docs/source/conf.py | 1 + 3 files changed, 56 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index af0976148..622ef6a60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,60 @@ ## Version 0.40.0 -_In development._ +([Full Changelog](https://github.com/unitaryfund/mitiq/compare/v0.39.0...v0.40.0)) + +### Highlights + +🔉 A new quantum error-mitigation technique is available in Mitiq! +**Layerwise Richardson Extrapolation** is now available through `mitiq.lre.execute_with_lre`. +Documentation is also available in the user guide, with more advanced docs and demonstrations coming in the next release. +Special thanks to Purva Thakre for this contribution! + +🥇 We had two **first time contributions** from @ecarlander and @mbrotos! +Thank you both for your contributions! + +🛡️ A **helpful error message** is raised when passing data of the incorrect type to the `MeasurementResult` class, where before it silently gave confusing results. + +#### ✨ Enhancements +- LRE Executors (#2499) [@purva-thakre] +- LRE Inference Functions (#2447) [@purva-thakre] +- Raise TypeError when a dictionary is passed to MeasurementResult constructor (#2523) [@natestemen] + +#### 📓 Documentation + +- Add theory, intro and use case pages of LRE user guide (#2522) [@purva-thakre] +- add QOSS survey banner (#2533) [@natestemen] +- Update broken link in the readme (#2528) [@purva-thakre] +- Move class documentation to class docstrings (#2525) [@natestemen] +- QSE docs cleanup (#2490) [@natestemen] +- Add tags to tutorials (#2467) [@purva-thakre] +- Correct CDR and VNCDR acronyms in example (#2479) [@bdg221] +- added roadmap link to readme (#2468) [@ecarlander] +- Update ibmq-backends.md (#2474) [@mbrotos] + +#### 🧑🏽‍💻 Developer Improvements + +- Remove `make requirements` (#2481) [@purva-thakre] +- Fix flaky REM test / refactor (#2464) [@natestemen] + +#### 📦 Dependency Updates + +- Bump pyscf from 2.6.2 to 2.7.0 (#2518) [@dependabot] +- Bump pyqrack from 1.30.24 to 1.30.30 (#2521) [@dependabot] +- Bump pyqrack from 1.30.22 to 1.30.24 (#2497) [@dependabot] +- Update qiskit requirement from ~=1.2.1 to ~=1.2.2 (#2507) [@dependabot] +- Bump qibo from 0.2.10 to 0.2.12 (#2506) [@dependabot] +- Update qiskit-aer requirement from ~=0.15.0 to ~=0.15.1 (#2504) [@dependabot] +- Update qiskit requirement from ~=1.2.0 to ~=1.2.1 (#2503) [@dependabot] +- Bump pyqrack from 1.30.20 to 1.30.22 (#2489) [@dependabot] +- Update qiskit-aer requirement from ~=0.14.2 to ~=0.15.0 (#2484) [@dependabot] +- Bump pyqrack from 1.30.8 to 1.30.20 (#2487) [@dependabot] +- Update cirq-core requirement from <1.4.0,>=1.0.0 to >=1.0.0,<1.5.0 (#2390) [@dependabot] +- Update qiskit requirement from ~=1.1.1 to ~=1.2.0 (#2482) [@dependabot] +- Update scipy requirement from <=1.14.0,>=1.10.1 to >=1.10.1,<=1.14.1 (#2477) [@dependabot] +- Bump pyqrack from 1.30.0 to 1.30.8 (#2476) [@dependabot] +- Bump sphinx from 7.2.6 to 8.0.2 (#2455) [@dependabot] +- Bump qibo from 0.2.9 to 0.2.10 (#2458) [@dependabot] ## Version 0.39.0 diff --git a/VERSION.txt b/VERSION.txt index 1994dd092..9b0025a78 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -0.40.0dev +0.40.0 diff --git a/docs/source/conf.py b/docs/source/conf.py index 6e4440250..061cdd6d9 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -234,6 +234,7 @@ def setup(app): r"https://link\.aps\.org/doi/.*", r"https://www\.sciencedirect\.com/science/article/.*", r"https://github.com/unitaryfund/mitiq/compare/.*", + r"https://github.com/unitaryfund/mitiq/projects/7", ] linkcheck_retries = 3 From 8693d0e9b07000b069b197f4f25f6600cde263cb Mon Sep 17 00:00:00 2001 From: Alessandro Cosentino Date: Fri, 11 Oct 2024 12:58:58 +0200 Subject: [PATCH 11/38] reset dev mode post-release (#2539) --- CHANGELOG.md | 4 ++++ VERSION.txt | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 622ef6a60..07310f3d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## Version 0.41.0 + +_In development._ + ## Version 0.40.0 ([Full Changelog](https://github.com/unitaryfund/mitiq/compare/v0.39.0...v0.40.0)) diff --git a/VERSION.txt b/VERSION.txt index 9b0025a78..2b8101664 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -0.40.0 +0.41.0dev From 54f7171488baf9f86261f33ee721c91029bb58a8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 21 Oct 2024 08:24:05 -0700 Subject: [PATCH 12/38] Bump pyqrack from 1.30.30 to 1.31.1 (#2534) * Bump pyqrack from 1.30.30 to 1.31.1 Bumps [pyqrack](https://github.com/vm6502q/pyqrack) from 1.30.30 to 1.31.1. - [Release notes](https://github.com/vm6502q/pyqrack/releases) - [Commits](https://github.com/vm6502q/pyqrack/compare/v1.30.30...v1.31.1) --- updated-dependencies: - dependency-name: pyqrack dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] * ignore minor/patch releases from qrack --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: nate stemen --- .github/dependabot.yml | 2 ++ requirements/requirements-dev.txt | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index e8ddafecc..32c8d1d56 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -42,6 +42,8 @@ updates: update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "pandas" update-types: ["version-update:semver-minor", "version-update:semver-patch"] + - dependency-name: "qrack" + update-types: ["version-update:semver-minor", "version-update:semver-patch"] # Allow up to 10 open pull requests for pip dependencies open-pull-requests-limit: 10 labels: diff --git a/requirements/requirements-dev.txt b/requirements/requirements-dev.txt index 5b0aee7e4..798ec5846 100644 --- a/requirements/requirements-dev.txt +++ b/requirements/requirements-dev.txt @@ -28,4 +28,4 @@ bqskit==1.1.1 seaborn==0.13.0 stim==1.13.0 stimcirq==1.13.0 -pyqrack==1.30.30 \ No newline at end of file +pyqrack==1.31.1 \ No newline at end of file From ecfa7dcfded49341f8f42245d81bd7a49be1cf84 Mon Sep 17 00:00:00 2001 From: Purva Thakre <66048318+purva-thakre@users.noreply.github.com> Date: Tue, 22 Oct 2024 15:49:03 -0500 Subject: [PATCH 13/38] LRE build error (#2541) * minor change * try full path * line - rtd failure * undo full path * try changing import order * import test failures * try adding init files * import paths in user guide * submodules in main init * new line in init --- docs/source/guide/lre-1-intro.md | 9 ++++----- mitiq/lre/__init__.py | 9 +++------ mitiq/lre/inference/__init__.py | 3 +++ mitiq/lre/lre.py | 6 ++++-- mitiq/lre/multivariate_scaling/__init__.py | 6 ++++++ 5 files changed, 20 insertions(+), 13 deletions(-) create mode 100644 mitiq/lre/inference/__init__.py create mode 100644 mitiq/lre/multivariate_scaling/__init__.py diff --git a/docs/source/guide/lre-1-intro.md b/docs/source/guide/lre-1-intro.md index 660483730..8e195ad1e 100644 --- a/docs/source/guide/lre-1-intro.md +++ b/docs/source/guide/lre-1-intro.md @@ -78,6 +78,7 @@ mitigated = execute_with_lre( fold_multiplier=fold_multiplier, ) + print(f"Error with mitigation (LRE): {abs(ideal - mitigated):.{3}}") ``` @@ -92,7 +93,7 @@ In this section we demonstrate the use of {func}`.multivariate_layer_scaling` an We start by creating a number of noise-scaled circuits which we will pass to the executor. ```{code-cell} ipython3 -from mitiq.lre import multivariate_layer_scaling +from mitiq.lre.multivariate_scaling import multivariate_layer_scaling noise_scaled_circuits = multivariate_layer_scaling(circuit, degree, fold_multiplier) @@ -125,7 +126,7 @@ The penultimate step here is to fetch the coefficients we'll use to combine the The astute reader will note that we haven't defined or used a `degree` or `fold_multiplier` parameter, and this is where they are both needed. ```{code-cell} ipython3 -from mitiq.lre import multivariate_richardson_coefficients +from mitiq.lre.inference import multivariate_richardson_coefficients coefficients = multivariate_richardson_coefficients( @@ -144,9 +145,7 @@ mitigated = sum( exp_val * coeff for exp_val, coeff in zip(noise_scaled_exp_values, coefficients) ) -print( - f"Error with mitigation (LRE): {abs(ideal - mitigated):.{3}}" -) +print(f"Error with mitigation (LRE): {abs(ideal - mitigated):.{3}}") ``` As you can see we again see a nice improvement in the accuracy using a two stage application of LRE. diff --git a/mitiq/lre/__init__.py b/mitiq/lre/__init__.py index 353568cdd..1060e50c4 100644 --- a/mitiq/lre/__init__.py +++ b/mitiq/lre/__init__.py @@ -5,11 +5,8 @@ """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.lre import execute_with_lre, mitigate_executor, lre_decorator -from mitiq.lre.inference.multivariate_richardson import ( - multivariate_richardson_coefficients, - sample_matrix, -) +from mitiq.lre import multivariate_scaling -from mitiq.lre.lre import execute_with_lre, mitigate_executor, lre_decorator \ No newline at end of file +from mitiq.lre import inference diff --git a/mitiq/lre/inference/__init__.py b/mitiq/lre/inference/__init__.py new file mode 100644 index 000000000..3411c080f --- /dev/null +++ b/mitiq/lre/inference/__init__.py @@ -0,0 +1,3 @@ +from mitiq.lre.inference.multivariate_richardson import ( + multivariate_richardson_coefficients, +) diff --git a/mitiq/lre/lre.py b/mitiq/lre/lre.py index c2fd7b52a..e292e15d1 100644 --- a/mitiq/lre/lre.py +++ b/mitiq/lre/lre.py @@ -12,10 +12,12 @@ from cirq import Circuit from mitiq import QPROGRAM -from mitiq.lre import ( - multivariate_layer_scaling, +from mitiq.lre.inference import ( multivariate_richardson_coefficients, ) +from mitiq.lre.multivariate_scaling import ( + multivariate_layer_scaling, +) from mitiq.zne.scaling import fold_gates_at_random diff --git a/mitiq/lre/multivariate_scaling/__init__.py b/mitiq/lre/multivariate_scaling/__init__.py new file mode 100644 index 000000000..816dfaa20 --- /dev/null +++ b/mitiq/lre/multivariate_scaling/__init__.py @@ -0,0 +1,6 @@ + +"""Methods for scaling noise in circuits by layers as required for LRE.""" + +from mitiq.lre.multivariate_scaling.layerwise_folding import ( + multivariate_layer_scaling, +) From 36e02854e6d92c75eccc0881b51e8ea9f18e8de2 Mon Sep 17 00:00:00 2001 From: nate stemen Date: Thu, 24 Oct 2024 23:03:26 -0700 Subject: [PATCH 14/38] remove failing test; simplify layerwise ZNE tests (#2545) * move param test to module; add test * remove flaky test * use numbered indices --- mitiq/lre/tests/test_lre.py | 14 --- mitiq/zne/scaling/tests/test_layer_scaling.py | 86 ++++++++++++++----- mitiq/zne/tests/test_zne.py | 34 ++++---- 3 files changed, 83 insertions(+), 51 deletions(-) diff --git a/mitiq/lre/tests/test_lre.py b/mitiq/lre/tests/test_lre.py index 4679e746a..9afb9715d 100644 --- a/mitiq/lre/tests/test_lre.py +++ b/mitiq/lre/tests/test_lre.py @@ -101,20 +101,6 @@ def test_lre_executor_with_chunking(): 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 diff --git a/mitiq/zne/scaling/tests/test_layer_scaling.py b/mitiq/zne/scaling/tests/test_layer_scaling.py index 1208eb552..bb84f2baa 100644 --- a/mitiq/zne/scaling/tests/test_layer_scaling.py +++ b/mitiq/zne/scaling/tests/test_layer_scaling.py @@ -5,9 +5,16 @@ """Unit tests for scaling by layer.""" +import random +from itertools import product +from unittest.mock import patch + +import pytest from cirq import Circuit, LineQubit, ops -from mitiq.zne.scaling import layer_folding +from mitiq import SUPPORTED_PROGRAM_TYPES +from mitiq.interface import convert_from_mitiq, convert_to_mitiq +from mitiq.zne.scaling import get_layer_folding, layer_folding def test_layer_folding_with_measurements(): @@ -53,24 +60,59 @@ def test_layer_folding(): # Iterate over every possible combination of layerwise folds for a maximum # number of 5-folds. total_folds = 5 - for i1 in range(total_folds): - for i2 in range(total_folds): - for i3 in range(total_folds): - layers_to_fold = [i1, i2, i3] - - folded_circuit = layer_folding(circ, layers_to_fold) - - # For a given layer, the number of copies on a layer will be - # 2n + 1 where "n" is the number of folds to perform. - qreg = LineQubit.range(3) - correct = Circuit( - # Layer-1 - [ops.H.on_each(*qreg)] * (2 * (layers_to_fold[0]) + 1), - # Layer-2 - [ops.CNOT.on(qreg[0], qreg[1])] - * (2 * (layers_to_fold[1]) + 1), - [ops.X.on(qreg[2])] * (2 * (layers_to_fold[1]) + 1), - # Layer-3 - [ops.TOFFOLI.on(*qreg)] * (2 * (layers_to_fold[2]) + 1), - ) - assert folded_circuit == correct + for i1, i2, i3 in product(range(total_folds), repeat=3): + folded_circuit = layer_folding(circ, [i1, i2, i3]) + + # For a given layer, the number of copies on a layer will be + # 2n + 1 where "n" is the number of folds to perform. + a, b, c = LineQubit.range(3) + correct = Circuit( + # Layer-1 + [ops.H.on_each(*(a, b, c))] * (2 * i1 + 1), + # Layer-2 + [ops.CNOT.on(a, b)] * (2 * i2 + 1), + [ops.X.on(c)] * (2 * i2 + 1), + # Layer-3 + [ops.TOFFOLI.on(*(a, b, c))] * (2 * i3 + 1), + ) + assert folded_circuit == correct + + +@pytest.mark.parametrize("circuit_type", SUPPORTED_PROGRAM_TYPES.keys()) +def test_layer_folding_all_qprograms(circuit_type): + """This test only ensures proper depth of layer-folded non-cirq circuits + as the mitiq conversion functions alter structure/gate composition.""" + qreg = LineQubit.range(3) + circuit = Circuit( + [ops.H.on_each(*qreg)], + [ops.CNOT.on(qreg[0], qreg[1])], + [ops.X.on(qreg[2])], + [ops.TOFFOLI.on(*qreg)], + ) + num_layers = len(circuit) + circuit = convert_from_mitiq(circuit, circuit_type) + layers_to_fold = random.choices(range(5), k=num_layers) + folded_circuit = layer_folding(circuit, layers_to_fold) + folded_mitiq_circuit = convert_to_mitiq(folded_circuit)[0] + num_ideal_layers = sum(2 * n + 1 for n in layers_to_fold) + if circuit_type == "pyquil": + # this block is needed for pyquil because of some quirks that pop up + # when converting to and from pyquil that does not make exact equality. + assert len(folded_mitiq_circuit) >= num_ideal_layers + else: + assert len(folded_mitiq_circuit) == num_ideal_layers + + +@patch("mitiq.zne.scaling.layer_scaling.layer_folding") +def test_get_layer_folding(mock_layer_folding): + a, b = LineQubit.range(2) + circuit = Circuit(ops.X(a), ops.CNOT(a, b), ops.Y(b)) + layer_index = 1 + scale_factor = 3 + + folding_func = get_layer_folding(layer_index) + folding_func(circuit, scale_factor) + + mock_layer_folding.assert_called_once_with( + circuit, layers_to_fold=[0, 1, 0] + ) diff --git a/mitiq/zne/tests/test_zne.py b/mitiq/zne/tests/test_zne.py index 6bd1379f0..ad528e156 100644 --- a/mitiq/zne/tests/test_zne.py +++ b/mitiq/zne/tests/test_zne.py @@ -6,7 +6,9 @@ """Unit tests for zero-noise extrapolation.""" import functools +import random from typing import List +from unittest.mock import Mock import cirq import numpy as np @@ -493,32 +495,34 @@ def test_execute_with_zne_with_supported_circuits(circuit_type): assert abs(unmitigated - expected) > abs(zne_value - expected) -@pytest.mark.parametrize("circuit_type", SUPPORTED_PROGRAM_TYPES.keys()) -def test_layerwise_execute_with_zne_with_supported_circuits(circuit_type): +def test_layerwise_folding_with_zne(): # Define a circuit equivalent to the identity qreg = cirq.LineQubit.range(2) - cirq_circuit = cirq.Circuit( + circuit = cirq.Circuit( cirq.H.on_each(qreg), cirq.CNOT(*qreg), cirq.CNOT(*qreg), cirq.H.on_each(qreg), ) - # Convert to one of the supported program types - circuit = convert_from_mitiq(cirq_circuit, circuit_type) - expected = generic_executor(circuit, noise_level=0.0) - unmitigated = generic_executor(circuit) - # Use odd scale factors for deterministic results - fac = RichardsonFactory([1, 3, 5]) - # Layerwise-fold + circuit_depth = len(circuit) + mock_executor = Mock(side_effect=lambda _: random.random()) layer_to_fold = 0 fold_layer_func = get_layer_folding(layer_to_fold) + scale_factors = [1, 3, 5] + factory = RichardsonFactory(scale_factors) - zne_value = execute_with_zne( - circuit, generic_executor, factory=fac, scale_noise=fold_layer_func + execute_with_zne( + circuit, mock_executor, factory=factory, scale_noise=fold_layer_func ) - - # Test zero noise limit is better than unmitigated expectation value - assert abs(unmitigated - expected) > abs(zne_value - expected) + assert mock_executor.call_count == len(scale_factors) + circuit_depths = [ + len(args[0]) for args, kwargs in mock_executor.call_args_list + ] + assert circuit_depths == [ + circuit_depth, + circuit_depth + 2, + circuit_depth + 4, + ] def test_execute_with_zne_transpiled_qiskit_circuit(): From c0688d89d835b9a7fde51da2ca267356e826b28c Mon Sep 17 00:00:00 2001 From: nate stemen Date: Mon, 28 Oct 2024 17:57:28 -0700 Subject: [PATCH 15/38] remove qosss banner (#2548) --- docs/source/conf.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/docs/source/conf.py b/docs/source/conf.py index 061cdd6d9..18e400168 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -394,9 +394,6 @@ def get_incollection_template(self, e): html_extra_path = ["robots.txt"] html_theme_options = { - "announcement": "The Unitary Fund 2024 Quantum Open Source Software Survey\ - is here! Take \ - the survey now!", "icon_links": [ { "name": "Source Repository", From e9b0322096af209b2c7dcccbb360ab1e8485d0b7 Mon Sep 17 00:00:00 2001 From: jpacold Date: Tue, 5 Nov 2024 13:37:32 -0700 Subject: [PATCH 16/38] Add dynamical phase transitions tutorial (#2546) * Add dynamical phase transitions tutorial * Fix docs build, minor comment edits * escape character in matplotlib axis label * Expand introduction * More text revisions, fix heading levels * Rename tutorial * Clean up some notation * Variables for value of depolarizing noise level * Add brief summary section * Add summary of other topics covered by paper * Tags and inline documentation link * Fix indents, reword some commentary * Fix documentation link, revise introduction * Link to paper via reference, fix tex typo * Add note on choice of Ising model parameters * Style fixes * Notation (specify that $\alpha$ is the noise scale factor) * shorter import statement Co-authored-by: nate stemen --------- Co-authored-by: nate stemen --- .../_thumbnails/loschmidt_echo_qiskit.png | Bin 0 -> 47420 bytes docs/source/conf.py | 1 + docs/source/examples/examples.md | 1 + .../examples/loschmidt_echo_revival_zne.md | 425 ++++++++++++++++++ docs/source/refs.bib | 9 + 5 files changed, 436 insertions(+) create mode 100644 docs/source/_thumbnails/loschmidt_echo_qiskit.png create mode 100644 docs/source/examples/loschmidt_echo_revival_zne.md diff --git a/docs/source/_thumbnails/loschmidt_echo_qiskit.png b/docs/source/_thumbnails/loschmidt_echo_qiskit.png new file mode 100644 index 0000000000000000000000000000000000000000..206f8a38cf51fa964be6666820844e9a8e68b03f GIT binary patch literal 47420 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 ZNE with Qiskit: Layerwise folding ZNE with Qiskit: Quantum simulation of quantum many body scars +ZNE with Qiskit: Simulation of Loschmidt echo revival ZNE with Cirq: Solving MaxCut with QAOA ZNE with Cirq: Hamiltonian simulation with Pauli gates ZNE with Cirq: Energy of molecular Hydrogen diff --git a/docs/source/examples/loschmidt_echo_revival_zne.md b/docs/source/examples/loschmidt_echo_revival_zne.md new file mode 100644 index 000000000..1e00b5ad5 --- /dev/null +++ b/docs/source/examples/loschmidt_echo_revival_zne.md @@ -0,0 +1,425 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.16.4 +kernelspec: + display_name: Python 3 (ipykernel) + language: python + name: python3 +--- + +```{tags} zne, qiskit, intermediate +``` + +# ZNE with Qiskit: Simulation of Loschmidt Echo Revival + +This tutorial replicates some of the results from {cite}`Javanmard_2022_arxiv`. We build a circuit that simulates the time-evolution of a transverse-field Ising model, then run ideal, noisy, and noise-mitigated simulations of the circuit. + +In particular, let $\Lambda(t)$ be the _Loschmidt echo_, the probability that the system returns to its initial state at time $t$. $\Lambda(t)$ has a quasi-periodic series of peaks that are flattened as the noise level increases. Here, we demonstrate how to simulate the Loschmidt echo and use zero-noise extrapolation to mitigate the effects of noise. + +The paper considers some additional effects of noise, which are outside the scope of this tutorial: + +* Let $\lambda(t) = \lim_{N\to\infty} -\log(\Lambda(t))/N$, where $N$ is the number of sites in the Ising model. Dynamical quantum phase transitions (DQPTs) occur at values of $t$ where $\lambda(t)$ is not analytic. DQPTs are observed at different times in the ideal and noisy simulations, and occur more frequently in the noisy system. + +* Noise weakens the correlations between adjacent sites. + ++++ + +## Model definition + +The Ising model that we will simulate has the Hamiltonian + +$$H = H_{zz} + H_{xx} + H_{x}$$ + +where $H_{zz}$ and $H_{xx}$ are the interactions between neighboring sites and $H_x$ is the interaction with the external magnetic field. Specifically, for $N$ sites, + +$$H_{zz} = -\frac{1}{2} \left[ \sum_{i=0}^{N-2}J_z Z_i Z_{i+1} \right], \hspace{0.4cm} H_{xx} = -\frac{1}{2} \left[ \sum_{i=0}^{N-2}J_x X_{i} X_{i+1} \right], \hspace{0.4cm} H_x = -\frac{1}{2} \left[ \sum_{i=0}^{N-1} h_x X_i \right]$$ + +where $X_i$ and $Z_i$ are the Pauli operators acting on site $i$, $J_z$ and $J_x$ are the $z$- and $x$-components of the spin-spin coupling, and $h_x$ is the strength of the external field. Here we will set $J_z > 0$ and $J_x > 0$, so that the spins at adjacent sites are correlated, and set $h_x > 0$ so that each spin prefers to have a positive $x$-component. (Strictly speaking, since $J_x \neq 0$ this is a Heisenberg model rather than an Ising model.) + +Assuming the system is in state $\ket{\psi_0}$ at $t = 0$, we want to compute the Loschmidt echo, + +$$\Lambda(t) = \left|\bra{\psi_0}U(t)\ket{\psi_0}\right|^2,$$ + +where $U(t) = \exp(-iHt)$ is the time-evolution operator. + ++++ + +## Reformulation as a quantum circuit + +To simulate how the model behaves over $0 \leq t \leq t_{\textrm{max}}$, we divide the interval into $M$ steps. Letting $\delta t = t_{\textrm{max}}/M$, we have + +$$U(k\delta t) = [\exp(-iH\delta t)]^k \hspace{0.25cm} (k = 0, \ldots, M)$$ + +Next we decompose $\exp(-iH\delta t)$. Up to an $\mathcal{O}(\delta t^2)$ error (since the terms in $H$ do not commute), + +$$\exp(-i \left[H_{zz} + H_{xx} + H_{x}\right] \delta t) \approx \exp(-i H_{zz} \delta t)\exp(-i H_{xx} \delta t)\exp(-i H_{x} \delta t)$$ + +Now we observe that each term in the decomposition corresponds to a series of gates in an $N$-qubit circuit. For example, + +$$\exp(-i H_{zz} \delta t) = \prod_{i=0}^{N-2} \exp\left( -i\frac{J_z\delta t }{2} Z_i Z_{i+1} \right)$$ + +Using the fact that $Z_i Z_{i+1} = I \otimes \cdots Z \otimes Z \cdots \otimes I$, we can rewrite this as a product of [$R_{ZZ}$ gates](https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.library.RZZGate), + +$$\prod_{i=0}^{N-2} R_{ZZ}^{(i, i+1)}(J_z \delta t )$$ + +Similarly, the terms $\exp(-i H_{xx} \delta t)$ and $\exp(-i H_{x} \delta t)$ can be rewritten in terms of [$R_{XX}$](https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.library.RXXGate) and [$R_X$](https://docs.quantum.ibm.com/api/qiskit/qiskit.circuit.library.RXGate) gates, yielding + +$$\exp(-iH\delta t) \approx \prod_{i=0}^{N-2} R_{ZZ}^{(i, i+1)}(J_z \delta t) \prod_{i=0}^{N-2} R_{XX}^{(i, i+1)}(J_x \delta t) \prod_{i=0}^{N-1} R_{X}^{i}(h_x \delta t)$$ + +To compute $\Lambda(k\delta t)$, we repeat this sequence of gates $k$ times. The circuit is implemented by the function in the following cell. Note that: +* We use $\ket{\psi_0} = H^{\otimes N}\ket{0^{\otimes N}}$, i.e. the spin at every site starts out parallel to the external magnetic field. +* The $R_{ZZ}$ gates commute with each other, so we can organize them into two "layers", one layer for the pairs of adjacent qubits $(i, i+1)$ with $i$ even and another for the pairs with $i$ odd. The $R_{XX}$ gates are organized in a similar way. + +```{code-cell} ipython3 +from qiskit import QuantumCircuit + +def create_circuit(delta_t : float, + k : int, + n_qubits: int = 6, + measure : bool = True, + J_z : float = 1.0, + J_x : float = 0.1, + h_x : float = 0.1) -> QuantumCircuit: + theta = J_z * delta_t + phi = J_x * delta_t + gamma = h_x * delta_t + + circuit = QuantumCircuit(n_qubits) + + circuit.h(circuit.qubits) + + for _ in range(k): + for ii in range(0, n_qubits-1, 2): + circuit.rzz(theta, ii, ii+1) + for ii in range(1, n_qubits-1, 2): + circuit.rzz(theta, ii, ii+1) + + for ii in range(0, n_qubits-1, 2): + circuit.rxx(phi, ii, ii+1) + for ii in range(1, n_qubits-1, 2): + circuit.rxx(phi, ii, ii+1) + + circuit.rx(gamma, circuit.qubits) + + circuit.h(circuit.qubits) + + if measure: + circuit.measure_all() + + return circuit +``` + +```{code-cell} ipython3 +print(create_circuit(0.01, 1)) +``` + +## Ideal simulation + +To get a sense of the ideal behavior of the circuit, we will run a noiseless state-vector simulation. First we define a dataclass to hold all the simulation parameters, and functions to visualize the results. + +```{code-cell} ipython3 +import numpy as np +import dataclasses + +@dataclasses.dataclass +class SimulationParams: + '''Simulation parameters. Note that by default we use + much coarser time steps than in the paper, so that + the noisy simulations later in this demo run in + a reasonably short time.''' + t_max : float = 8.5 + M : int = 25 + n_qubits : int = 6 + dt : float = dataclasses.field(init=False) + t_vals : np.ndarray[float] = dataclasses.field(init=False) + + # Only used in noisy simulations + n_shots : int = 2048 + + def __post_init__(self): + self.dt = self.t_max / self.M + self.t_vals = np.linspace(0., self.t_max, self.M + 1, endpoint=True) +``` + +```{code-cell} ipython3 +from matplotlib import pyplot as plt + +def setup_plot(title : str = None): + plt.figure(figsize=(6.0, 4.0)) + plt.xlabel("$t$") + plt.ylabel("$\\Lambda(t)$") + if title is not None: + plt.title(title) + +def add_to_plot(x : np.ndarray[float], + y : np.ndarray[float], + label : str, + legend : list[str]): + if label == "ideal": + plt.plot(x, y, color="black") + elif label == "mitigated": + plt.plot(x, y, marker='s', markersize=5) + else: + plt.plot(x, y, marker='.', markersize=10) + legend.append(label) +``` + +To get the ideal result for $\Lambda(k\delta t)$, we omit the measurements from the circuit, and read the probability amplitude $\bra{0^{\otimes N}} H^{\otimes N} U(k \delta t) H^{\otimes N} \ket{0^{\otimes N}}$ directly from the final state vector. + +```{code-cell} ipython3 +from qiskit_aer import QasmSimulator + +def simulate_ideal(circuit: QuantumCircuit) -> float: + simulator = QasmSimulator(method="statevector", noise_model=None) + + circuit.save_statevector() + job = simulator.run(circuit, shots=1) + + psi = job.result().data()["statevector"] + + # Get the probability of returning to |00...0> + amp_0 = np.asarray(psi)[0] + return np.abs(amp_0.real**2 + amp_0.imag**2) + +def run_ideal(params : SimulationParams = SimulationParams()) -> tuple[np.ndarray[float]]: + echo = np.array([ + simulate_ideal( + create_circuit(params.dt, k, n_qubits=params.n_qubits, measure=False) + ) + for k in range(0, params.M + 1) + ]) + + return params.t_vals, echo +``` + +```{code-cell} ipython3 +# Run the state-vector simulation +result_ideal = run_ideal(SimulationParams(t_max=25.0, M=200)) +``` + +```{code-cell} ipython3 +setup_plot("State-vector simulation") +legend = [] +add_to_plot(*result_ideal, "ideal", legend) +plt.legend(legend) +plt.show() +``` + +The result shows a series of Loschmidt echo peaks. Intuitively, for a noisy system we expect that as $t$ increases $\Lambda(t)$ will approach $1/2^N$, where $N$ is the number of qubits. This means that as the noise level increases, the peaks will be suppressed, starting at larger values of $t$. At higher levels of noise, we may not be able to detect the Loschmidt echo at all. + +In the rest of this notebook, we will run simulations with two different noise models, and try to reconstruct the peak at $t \approx 6.5$ with zero-noise extrapolation. The next cell re-plots the ideal simulation result over the values of $t$ that we will consider. + +```{code-cell} ipython3 +result_ideal = run_ideal(SimulationParams(M=100)) +setup_plot("State-vector simulation") +legend = [] +add_to_plot(*result_ideal, "ideal", legend) +plt.legend(legend) +plt.show() +``` + +## Simulation with depolarizing noise + +The next few cells run a simulation with depolarizing noise. We transpile the circuit using 1-qubit rotations and CNOT as the basis gate set, and optionally use gate folding to scale the noise. + +```{code-cell} ipython3 +from qiskit.compiler import transpile +from qiskit_aer.noise import NoiseModel +from qiskit_aer.noise.errors.standard_errors import depolarizing_error +from mitiq.zne.scaling import fold_gates_at_random + +def simulate_noisy(circuit: QuantumCircuit, + noise_model: NoiseModel, + n_shots : int, + scale_factor: float = None) -> float: + # Transpile the circuit + backend = QasmSimulator(noise_model=noise_model) + exec_circuit = transpile( + circuit, + backend=backend, + basis_gates=["u1", "u2", "u3", "cx"], + optimization_level=0 + ) + + # Apply gate folding + folded_circuit = exec_circuit if scale_factor is None \ + else fold_gates_at_random(exec_circuit, scale_factor) + + job = backend.run(folded_circuit, shots=n_shots) + + # Get the probability of returning to |00...0> + counts = job.result().get_counts() + ket = "0" * circuit.num_qubits + if ket in counts: + return counts[ket]/n_shots + return 0.0 + +def run_depolarizing_noise(params : SimulationParams = SimulationParams(), + noise_level : float = 0.001, + scale_factor : float = None) -> tuple[np.ndarray[float]]: + basis_gates = ["u1", "u2", "u3", "cx"] + noise_model = NoiseModel(basis_gates) + # Add depolarizing noise to the 1-qubit gates in the basis set + noise_model.add_all_qubit_quantum_error( + depolarizing_error(noise_level, 1), basis_gates[:3] + ) + + echo = np.array([ + simulate_noisy( + create_circuit(params.dt, k, n_qubits=params.n_qubits, measure=True), + noise_model, + params.n_shots, + scale_factor + ) + for k in range(0, params.M + 1) + ]) + + return params.t_vals, echo +``` + +```{code-cell} ipython3 +low_noise = 0.0005 +result_depolarizing = run_depolarizing_noise(noise_level=low_noise) +``` + +```{code-cell} ipython3 +setup_plot("Ideal and noisy simulations") +legend = [] +add_to_plot(*result_ideal, "ideal", legend) +add_to_plot( + *result_depolarizing, + "depolarizing noise, $p = {}$".format(low_noise), + legend) +plt.legend(legend) +plt.show() +``` + +As expected, the Loschmidt echo revival is weaker than in the ideal cases, and we get a lower peak. Applying gate folding suppresses the peak further; in the next cell we do this for scale factors $\alpha = 1, 2, 3$. + +```{code-cell} ipython3 +scale_factors = [1, 2, 3] +result_depolarizing_scaled = [ + run_depolarizing_noise( + noise_level=low_noise, + scale_factor=alpha) + for alpha in scale_factors +] +``` + +```{code-cell} ipython3 +setup_plot("Scaled noisy simulations") +legend = [] +add_to_plot(*result_ideal, "ideal", legend) +for alpha, result in zip(scale_factors, result_depolarizing_scaled): + add_to_plot(*result, "$\\alpha = {}$".format(alpha), legend) +plt.legend(legend) +plt.show() +``` + +## Error mitigation with zero-noise extrapolation +At this level of noise, we can use ZNE to mostly recover the ideal result. Running the noisy simulation is expensive (especially as the scale factor $\alpha$ increases), so rather than using the high-level functions for ZNE, we apply the static method `RichardsonFactory.extrapolate` to the results from the previous cell. + +```{code-cell} ipython3 +from mitiq.zne.inference import RichardsonFactory + +result_zne = RichardsonFactory.extrapolate( + scale_factors, + [r[1] for r in result_depolarizing_scaled] +) +``` + +```{code-cell} ipython3 +setup_plot("ZNE for depolarizing noise model, $p = {}$".format(low_noise)) +legend = [] +add_to_plot(*result_ideal, "ideal", legend) +for alpha, result in zip(scale_factors, result_depolarizing_scaled): + add_to_plot(*result, "$\\alpha = {}$".format(alpha), legend) +add_to_plot(result_depolarizing_scaled[0][0], result_zne, "mitigated", legend) +plt.legend(legend) +plt.show() +``` + +Increasing the baseline noise level makes it much harder to reconstruct the peak with ZNE. + +```{code-cell} ipython3 +high_noise = 0.005 +scale_factors = [1, 2, 3] +result_depolarizing_scaled = [ + run_depolarizing_noise( + noise_level=high_noise, + scale_factor=alpha) + for alpha in scale_factors +] +result_zne = RichardsonFactory.extrapolate( + scale_factors, + [r[1] for r in result_depolarizing_scaled] +) +``` + +```{code-cell} ipython3 +setup_plot("ZNE for depolarizing noise model, $p = {}$".format(high_noise)) +legend = [] +add_to_plot(*result_ideal, "ideal", legend) +for alpha, result in zip(scale_factors, result_depolarizing_scaled): + add_to_plot(*result, "$\\alpha = {}$".format(alpha), legend) +add_to_plot(result_depolarizing_scaled[0][0], result_zne, "mitigated", legend) +plt.legend(legend) +plt.show() +``` + +## Simulation with realistic device noise + +Next, we run simulations using the noise model of the IBM Nairobi device. Again, the relatively high noise level makes it difficult to recover something close to the ideal signal. + +```{code-cell} ipython3 +from qiskit_ibm_runtime.fake_provider.backends import FakeNairobiV2 + +def run_ibm_nairobi_noise(params : SimulationParams = SimulationParams(), + scale_factor : float = None) -> tuple[np.ndarray[float]]: + noise_model = NoiseModel.from_backend(FakeNairobiV2()) + + echo = np.array([ + simulate_noisy( + create_circuit(params.dt, k, n_qubits=params.n_qubits, measure=True), + noise_model, + params.n_shots, + scale_factor + ) + for k in range(0, params.M + 1) + ]) + + return params.t_vals, echo +``` + +```{code-cell} ipython3 +scale_factors = [1, 2, 3] +result_ibm_nairobi_scaled = [ + run_ibm_nairobi_noise(scale_factor=alpha) for alpha in scale_factors +] +``` + +```{code-cell} ipython3 +result_zne = RichardsonFactory.extrapolate( + scale_factors, + [r[1] for r in result_ibm_nairobi_scaled] +) +``` + +```{code-cell} ipython3 +setup_plot("ZNE for IBM Nairobi noise model") +legend = [] +add_to_plot(*result_ideal, "ideal", legend) +for alpha, result in zip(scale_factors, result_ibm_nairobi_scaled): + add_to_plot(*result, "$\\alpha = {}$".format(alpha), legend) +add_to_plot(result_depolarizing_scaled[0][0], result_zne, "mitigated", legend) +plt.legend(legend) +plt.show() +``` + +## Summary + +For the system considered in this tutorial, the effectiveness of ZNE depends strongly on the level of noise. Under low-noise conditions, we can accurately recover the ideal Loschmidt echo signal. At higher noise levels, increasing the scale factor $\alpha$ almost completely flattens the first revival peak. As a result, the extrapolation procedure can qualitatively recover some of the signal, but is not quantitatively reliable. diff --git a/docs/source/refs.bib b/docs/source/refs.bib index f7b676235..c96b3fb3d 100644 --- a/docs/source/refs.bib +++ b/docs/source/refs.bib @@ -395,6 +395,15 @@ @book{James_2021_statlearning url = {https://www.statlearning.com/} } +@misc{Javanmard_2022_arxiv, + author = {Javanmard, Younes and Liaubaite, Ugne and {Osborne}, {Tobias J.} and Santos, {Luis}}, + title = {Quantum simulation of dynamical phase transitions in noisy quantum devices}, + year = {2022}, + month = {nov}, + archiveprefix = {arXiv}, + eprint = {2211.08318}, + primaryclass = {quant-ph}, +} @misc{Jurcevic_2021_arxiv, author = {{Jurcevic}, Petar and {Javadi-Abhari}, Ali and {Bishop}, Lev S. and {Lauer}, Isaac and {Bogorin}, Daniela F. and {Brink}, Markus and {Capelluto}, Lauren and {G{\"u}nl{\"u}k}, Oktay and {Itoko}, Toshinari and {Kanazawa}, Naoki and {Kandala}, Abhinav and {Keefe}, George A. and {Krsulich}, Kevin and {Landers}, William and {Lewandowski}, Eric P. and {McClure}, Douglas T. and {Nannicini}, Giacomo and {Narasgond}, Adinath and {Nayfeh}, Hasan M. and {Pritchett}, Emily and {Rothwell}, Mary Beth and {Srinivasan}, Srikanth and {Sundaresan}, Neereja and {Wang}, Cindy and {Wei}, Ken X. and {Wood}, Christopher J. and {Yau}, Jeng-Bang and {Zhang}, Eric J. and {Dial}, Oliver E. and {Chow}, Jerry M. and {Gambetta}, Jay M.}, From c763f200257fdc5a8d33e19b9cae646f9f269081 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:37:26 -0800 Subject: [PATCH 17/38] Update qiskit requirement from ~=1.2.2 to ~=1.2.4 (#2527) Updates the requirements on [qiskit](https://github.com/Qiskit/qiskit) to permit the latest version. - [Release notes](https://github.com/Qiskit/qiskit/releases) - [Changelog](https://github.com/Qiskit/qiskit/blob/main/docs/release_notes.rst) - [Commits](https://github.com/Qiskit/qiskit/compare/1.2.2...1.2.4) --- updated-dependencies: - dependency-name: qiskit dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/requirements-qiskit.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements-qiskit.txt b/requirements/requirements-qiskit.txt index 0894296d7..7d78c68c7 100644 --- a/requirements/requirements-qiskit.txt +++ b/requirements/requirements-qiskit.txt @@ -1,4 +1,4 @@ -qiskit~=1.2.2 +qiskit~=1.2.4 qiskit-aer~=0.15.1 qiskit-ibm-runtime~=0.20.0 ply==3.11 \ No newline at end of file From 790260654ff257d37acf3526c38066e41aaa5896 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:37:47 -0800 Subject: [PATCH 18/38] Bump stimcirq from 1.13.0 to 1.14.0 (#2510) Bumps [stimcirq](https://github.com/quantumlib/stim) from 1.13.0 to 1.14.0. - [Release notes](https://github.com/quantumlib/stim/releases) - [Commits](https://github.com/quantumlib/stim/compare/v1.13.0...v1.14.0) --- updated-dependencies: - dependency-name: stimcirq dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements-dev.txt b/requirements/requirements-dev.txt index 798ec5846..9ac507340 100644 --- a/requirements/requirements-dev.txt +++ b/requirements/requirements-dev.txt @@ -27,5 +27,5 @@ openfermionpyscf==0.5; sys_platform != 'win32' bqskit==1.1.1 seaborn==0.13.0 stim==1.13.0 -stimcirq==1.13.0 +stimcirq==1.14.0 pyqrack==1.31.1 \ No newline at end of file From ed4d7cf2dd9ccee64f0d5f8051484737ccdc8759 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 5 Nov 2024 13:39:05 -0800 Subject: [PATCH 19/38] Bump pyqrack from 1.31.1 to 1.32.11 (#2554) Bumps [pyqrack](https://github.com/vm6502q/pyqrack) from 1.31.1 to 1.32.11. - [Release notes](https://github.com/vm6502q/pyqrack/releases) - [Commits](https://github.com/vm6502q/pyqrack/compare/v1.31.1...v1.32.11) --- updated-dependencies: - dependency-name: pyqrack dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements-dev.txt b/requirements/requirements-dev.txt index 9ac507340..93f757bff 100644 --- a/requirements/requirements-dev.txt +++ b/requirements/requirements-dev.txt @@ -28,4 +28,4 @@ bqskit==1.1.1 seaborn==0.13.0 stim==1.13.0 stimcirq==1.14.0 -pyqrack==1.31.1 \ No newline at end of file +pyqrack==1.32.11 \ No newline at end of file From 378d12fcc6fcfa3d3a476ab816748edcccfcb639 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Nov 2024 09:40:55 -0800 Subject: [PATCH 20/38] Bump pytest-cov from 5.0.0 to 6.0.0 (#2556) Bumps [pytest-cov](https://github.com/pytest-dev/pytest-cov) from 5.0.0 to 6.0.0. - [Changelog](https://github.com/pytest-dev/pytest-cov/blob/master/CHANGELOG.rst) - [Commits](https://github.com/pytest-dev/pytest-cov/compare/v5.0.0...v6.0.0) --- updated-dependencies: - dependency-name: pytest-cov dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements-dev.txt b/requirements/requirements-dev.txt index 93f757bff..6e8ac5beb 100644 --- a/requirements/requirements-dev.txt +++ b/requirements/requirements-dev.txt @@ -1,7 +1,7 @@ # Unit tests, coverage, and formatting/style. pytest==8.0.0 pytest-xdist[psutil]==3.0.2 -pytest-cov==5.0.0 +pytest-cov==6.0.0 ruff==0.3.1 mypy==1.0.0 types-tabulate From 546d4d1308e18ad3cb8e8bb0655987e675a01c65 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Nov 2024 09:41:27 -0800 Subject: [PATCH 21/38] Bump qibo from 0.2.12 to 0.2.13 (#2549) * Bump qibo from 0.2.12 to 0.2.13 Bumps [qibo](https://github.com/qiboteam/qibo) from 0.2.12 to 0.2.13. - [Release notes](https://github.com/qiboteam/qibo/releases) - [Commits](https://github.com/qiboteam/qibo/compare/v0.2.12...v0.2.13) --- updated-dependencies: - dependency-name: qibo dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * unpin qibo --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: nate stemen --- requirements/requirements-qibo.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements-qibo.txt b/requirements/requirements-qibo.txt index 704b3e8e8..4a610df8f 100644 --- a/requirements/requirements-qibo.txt +++ b/requirements/requirements-qibo.txt @@ -1 +1 @@ -qibo==0.2.12 # TODO: unpin this +qibo~=0.2.7 From a629c05bb366efc960ceae0be31397d3518ca63d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 6 Nov 2024 09:41:58 -0800 Subject: [PATCH 22/38] Bump stim from 1.13.0 to 1.14.0 (#2511) Bumps [stim](https://github.com/quantumlib/stim) from 1.13.0 to 1.14.0. - [Release notes](https://github.com/quantumlib/stim/releases) - [Commits](https://github.com/quantumlib/stim/compare/v1.13.0...v1.14.0) --- updated-dependencies: - dependency-name: stim dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements-dev.txt b/requirements/requirements-dev.txt index 6e8ac5beb..6a80ce0d4 100644 --- a/requirements/requirements-dev.txt +++ b/requirements/requirements-dev.txt @@ -26,6 +26,6 @@ openfermion==1.6.1; sys_platform != 'win32' openfermionpyscf==0.5; sys_platform != 'win32' bqskit==1.1.1 seaborn==0.13.0 -stim==1.13.0 +stim==1.14.0 stimcirq==1.14.0 pyqrack==1.32.11 \ No newline at end of file From 05c45b2521f93c749e2483164fecb5278cf2cbba Mon Sep 17 00:00:00 2001 From: nate stemen Date: Wed, 6 Nov 2024 11:54:40 -0800 Subject: [PATCH 23/38] Ensure LRE compatibility with all supported frontends (#2547) * add non-cirq support in LRE * clean up some utility conversions * use directly for parametrization * replace magic number with computed * split test into two --- mitiq/calibration/settings.py | 2 +- mitiq/interface/conversions.py | 42 ++++++++++++++---- .../lre/inference/multivariate_richardson.py | 2 + mitiq/lre/lre.py | 21 ++++----- .../multivariate_scaling/layerwise_folding.py | 8 +++- mitiq/lre/tests/test_layerwise_folding.py | 43 ++++++++++++++----- mitiq/lre/tests/test_lre.py | 27 +++++++++++- mitiq/typing.py | 29 +++++++------ 8 files changed, 127 insertions(+), 47 deletions(-) diff --git a/mitiq/calibration/settings.py b/mitiq/calibration/settings.py index 871b691d5..e87037d35 100644 --- a/mitiq/calibration/settings.py +++ b/mitiq/calibration/settings.py @@ -95,7 +95,7 @@ def converted_circuit( """ circuit = self.circuit.copy() circuit.append(cirq.measure(circuit.all_qubits())) - return convert_from_mitiq(circuit, circuit_type.value) + return convert_from_mitiq(circuit, circuit_type.name) @property def num_qubits(self) -> int: diff --git a/mitiq/interface/conversions.py b/mitiq/interface/conversions.py index 79f80792d..c6887c20a 100644 --- a/mitiq/interface/conversions.py +++ b/mitiq/interface/conversions.py @@ -6,7 +6,18 @@ """Functions for converting to/from Mitiq's internal circuit representation.""" from functools import wraps -from typing import Any, Callable, Dict, Iterable, Optional, Tuple, cast +from typing import ( + Any, + Callable, + Collection, + Concatenate, + Dict, + Optional, + ParamSpec, + Tuple, + TypeVar, + cast, +) import cirq @@ -150,6 +161,7 @@ def convert_from_mitiq( circuit: Mitiq circuit to convert. conversion_type: String specifier for the converted circuit type. """ + conversion_type = conversion_type.lower() conversion_function: Callable[[cirq.Circuit], QPROGRAM] if conversion_type == "qiskit": from mitiq.interface.mitiq_qiskit.conversions import to_qiskit @@ -199,13 +211,21 @@ def conversion_function(circ: cirq.Circuit) -> cirq.Circuit: return converted_circuit +P = ParamSpec("P") +R = TypeVar("R") + + def accept_any_qprogram_as_input( - accept_cirq_circuit_function: Callable[[cirq.Circuit], Any], -) -> Callable[[QPROGRAM], Any]: + accept_cirq_circuit_function: Callable[Concatenate[cirq.Circuit, P], R], +) -> Callable[Concatenate[QPROGRAM, P], R]: + """Converts functions which take as input cirq.Circuit object (and return + anything), to function which can accept any QPROGRAM. + """ + @wraps(accept_cirq_circuit_function) def accept_any_qprogram_function( - circuit: QPROGRAM, *args: Any, **kwargs: Any - ) -> Any: + circuit: QPROGRAM, *args: P.args, **kwargs: P.kwargs + ) -> R: cirq_circuit, _ = convert_to_mitiq(circuit) return accept_cirq_circuit_function(cirq_circuit, *args, **kwargs) @@ -245,15 +265,19 @@ def qprogram_modifier( def atomic_one_to_many_converter( - cirq_circuit_modifier: Callable[..., Iterable[cirq.Circuit]], -) -> Callable[..., Iterable[QPROGRAM]]: + cirq_circuit_modifier: Callable[..., Collection[cirq.Circuit]], +) -> Callable[..., Collection[QPROGRAM]]: + """Convert function which returns multiple cirq.Circuits into a function + which returns multiple QPROGRAM instances. + """ + @wraps(cirq_circuit_modifier) def qprogram_modifier( circuit: QPROGRAM, *args: Any, **kwargs: Any - ) -> Iterable[QPROGRAM]: + ) -> Collection[QPROGRAM]: mitiq_circuit, input_circuit_type = convert_to_mitiq(circuit) - modified_circuits: Iterable[cirq.Circuit] = cirq_circuit_modifier( + modified_circuits = cirq_circuit_modifier( mitiq_circuit, *args, **kwargs ) diff --git a/mitiq/lre/inference/multivariate_richardson.py b/mitiq/lre/inference/multivariate_richardson.py index 00e3f5670..6dac868c5 100644 --- a/mitiq/lre/inference/multivariate_richardson.py +++ b/mitiq/lre/inference/multivariate_richardson.py @@ -15,6 +15,7 @@ from cirq import Circuit from numpy.typing import NDArray +from mitiq.interface import accept_any_qprogram_as_input from mitiq.lre.multivariate_scaling.layerwise_folding import ( _get_scale_factor_vectors, ) @@ -120,6 +121,7 @@ def sample_matrix( return sample_matrix +@accept_any_qprogram_as_input def multivariate_richardson_coefficients( input_circuit: Circuit, degree: int, diff --git a/mitiq/lre/lre.py b/mitiq/lre/lre.py index e292e15d1..0a3d2c573 100644 --- a/mitiq/lre/lre.py +++ b/mitiq/lre/lre.py @@ -9,7 +9,6 @@ from typing import Any, Callable, Optional, Union import numpy as np -from cirq import Circuit from mitiq import QPROGRAM from mitiq.lre.inference import ( @@ -22,8 +21,8 @@ def execute_with_lre( - input_circuit: Circuit, - executor: Callable[[Circuit], float], + input_circuit: QPROGRAM, + executor: Callable[[QPROGRAM], float], degree: int, fold_multiplier: int, folding_method: Callable[ @@ -90,14 +89,14 @@ def execute_with_lre( def mitigate_executor( - executor: Callable[[Circuit], float], + executor: Callable[[QPROGRAM], 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]: +) -> Callable[[QPROGRAM], float]: """Returns a modified version of the input `executor` which is error-mitigated with layerwise richardson extrapolation (LRE). @@ -119,7 +118,7 @@ def mitigate_executor( """ @wraps(executor) - def new_executor(input_circuit: Circuit) -> float: + def new_executor(input_circuit: QPROGRAM) -> float: return execute_with_lre( input_circuit, executor, @@ -135,9 +134,11 @@ def new_executor(input_circuit: Circuit) -> float: def lre_decorator( degree: int, fold_multiplier: int, - folding_method: Callable[[Circuit, float], Circuit] = fold_gates_at_random, + folding_method: Callable[ + [QPROGRAM, float], QPROGRAM + ] = fold_gates_at_random, num_chunks: Optional[int] = None, -) -> Callable[[Callable[[Circuit], float]], Callable[[Circuit], float]]: +) -> Callable[[Callable[[QPROGRAM], float]], Callable[[QPROGRAM], float]]: """Decorator which adds an error-mitigation layer based on layerwise richardson extrapolation (LRE). @@ -159,8 +160,8 @@ def lre_decorator( """ def decorator( - executor: Callable[[Circuit], float], - ) -> Callable[[Circuit], float]: + executor: Callable[[QPROGRAM], float], + ) -> Callable[[QPROGRAM], float]: return mitigate_executor( executor, degree, diff --git a/mitiq/lre/multivariate_scaling/layerwise_folding.py b/mitiq/lre/multivariate_scaling/layerwise_folding.py index 9bd883fca..ab38e8c88 100644 --- a/mitiq/lre/multivariate_scaling/layerwise_folding.py +++ b/mitiq/lre/multivariate_scaling/layerwise_folding.py @@ -15,6 +15,7 @@ from cirq import Circuit from mitiq import QPROGRAM +from mitiq.interface import accept_qprogram_and_validate from mitiq.utils import _append_measurements, _pop_measurements from mitiq.zne.scaling import fold_gates_at_random from mitiq.zne.scaling.folding import _check_foldable @@ -134,7 +135,7 @@ def _get_scale_factor_vectors( ] -def multivariate_layer_scaling( +def _multivariate_layer_scaling( input_circuit: Circuit, degree: int, fold_multiplier: int, @@ -208,3 +209,8 @@ def multivariate_layer_scaling( multiple_folded_circuits.append(folded_circuit) return multiple_folded_circuits + + +multivariate_layer_scaling = accept_qprogram_and_validate( + _multivariate_layer_scaling, one_to_many=True +) diff --git a/mitiq/lre/tests/test_layerwise_folding.py b/mitiq/lre/tests/test_layerwise_folding.py index b1870450d..12ab80f29 100644 --- a/mitiq/lre/tests/test_layerwise_folding.py +++ b/mitiq/lre/tests/test_layerwise_folding.py @@ -6,11 +6,14 @@ """Unit tests for scaling noise by unitary folding of layers in the input circuit to allow for multivariate extrapolation.""" +import math from copy import deepcopy import pytest from cirq import Circuit, LineQubit, ops +from mitiq import SUPPORTED_PROGRAM_TYPES +from mitiq.interface import convert_from_mitiq from mitiq.lre.multivariate_scaling.layerwise_folding import ( _get_chunks, _get_num_layers_without_measurements, @@ -30,14 +33,35 @@ test_circuit1_with_measurements.append(ops.measure_each(*qreg1)) -def test_multivariate_layerwise_scaling(): - """Checks if multiple scaled circuits are returned to fit the required - folding pattern for multivariate extrapolation.""" - multiple_scaled_circuits = multivariate_layer_scaling( - test_circuit1, 2, 2, 3 +def test_multivariate_layerwise_scaling_num_circuits(): + """Ensure the correct number of circuits are generated.""" + degree, fold_multiplier, num_chunks = 2, 2, 3 + scaled_circuits = multivariate_layer_scaling( + test_circuit1, degree, fold_multiplier, num_chunks ) - assert len(multiple_scaled_circuits) == 10 + depth = len(test_circuit1) + # number of circuit is `degree` + `depth` choose `degree` + assert len(scaled_circuits) == math.comb(degree + depth, degree) + + +@pytest.mark.parametrize("circuit_type", SUPPORTED_PROGRAM_TYPES) +def test_multivariate_layerwise_scaling_types(circuit_type): + """Ensure layer scaling returns circuits of the correct type.""" + circuit = convert_from_mitiq(test_circuit1, circuit_type.name) + degree, fold_multiplier, num_chunks = 2, 2, 3 + scaled_circuits = multivariate_layer_scaling( + circuit, degree, fold_multiplier, num_chunks + ) + + assert all( + isinstance(circuit, circuit_type.value) for circuit in scaled_circuits + ) + + +def test_multivariate_layerwise_scaling_cirq(): + """Ensure the folding pattern is correct for a nontrivial case.""" + scaled_circuits = multivariate_layer_scaling(test_circuit1, 2, 2, 3) folding_pattern = [ (1, 1, 1), (5, 1, 1), @@ -50,16 +74,15 @@ def test_multivariate_layerwise_scaling(): (1, 5, 5), (1, 1, 9), ] - - for i, scale_factor_vector in enumerate(folding_pattern): - scale_layer1, scale_layer2, scale_layer3 = scale_factor_vector + for scale_factors, circuit in zip(folding_pattern, scaled_circuits): + scale_layer1, scale_layer2, scale_layer3 = scale_factors expected_circuit = Circuit( [ops.H.on_each(*qreg1)] * scale_layer1, [ops.CNOT.on(qreg1[0], qreg1[1]), ops.X.on(qreg1[2])] * scale_layer2, [ops.TOFFOLI.on(*qreg1)] * scale_layer3, ) - assert expected_circuit == multiple_scaled_circuits[i] + assert expected_circuit == circuit @pytest.mark.parametrize( diff --git a/mitiq/lre/tests/test_lre.py b/mitiq/lre/tests/test_lre.py index 9afb9715d..879570ef0 100644 --- a/mitiq/lre/tests/test_lre.py +++ b/mitiq/lre/tests/test_lre.py @@ -1,11 +1,14 @@ """Unit tests for the LRE extrapolation methods.""" +import math +import random import re +from unittest.mock import Mock import pytest from cirq import DensityMatrixSimulator, depolarize -from mitiq import benchmarks +from mitiq import SUPPORTED_PROGRAM_TYPES, benchmarks from mitiq.lre import execute_with_lre, lre_decorator, mitigate_executor from mitiq.zne.scaling import fold_all, fold_global @@ -40,8 +43,28 @@ def test_lre_exp_value(degree, fold_multiplier): assert abs(lre_exp_val - ideal_val) <= abs(noisy_val - ideal_val) +@pytest.mark.parametrize("circuit_type", SUPPORTED_PROGRAM_TYPES.keys()) +def test_lre_all_qprogram(circuit_type): + """Verify LRE works with all supported frontends.""" + degree, fold_multiplier = 2, 3 + circuit = benchmarks.generate_ghz_circuit(3, circuit_type) + depth = 3 # not all circuit types have a simple way to compute depth + + mock_executor = Mock(side_effect=lambda _: random.random()) + + lre_exp_val = execute_with_lre( + circuit, + mock_executor, + degree=degree, + fold_multiplier=fold_multiplier, + ) + + assert isinstance(lre_exp_val, float) + assert mock_executor.call_count == math.comb(degree + depth, degree) + + @pytest.mark.parametrize("degree, fold_multiplier", [(2, 2), (2, 3), (3, 4)]) -def test_lre_exp_value_decorator(degree, fold_multiplier): +def test_lre_mitigate_executor(degree, fold_multiplier): """Verify LRE mitigated executor work as expected.""" mitigated_executor = mitigate_executor( execute, degree=2, fold_multiplier=2 diff --git a/mitiq/typing.py b/mitiq/typing.py index 37e9e0584..7bcf03a70 100644 --- a/mitiq/typing.py +++ b/mitiq/typing.py @@ -20,7 +20,6 @@ from typing import ( Any, Dict, - Iterable, List, Optional, Sequence, @@ -37,25 +36,17 @@ class EnhancedEnumMeta(EnumMeta): def __str__(cls) -> str: - return ", ".join([member.value for member in cast(Type[Enum], cls)]) + return ", ".join( + [member.name.lower() for member in cast(Type[Enum], cls)] + ) class EnhancedEnum(Enum, metaclass=EnhancedEnumMeta): # This is for backwards compatibility with the old representation # of SUPPORTED_PROGRAM_TYPES, which was a dictionary @classmethod - def keys(cls) -> Iterable[str]: - return [member.value for member in cls] - - -# Supported quantum programs. -class SUPPORTED_PROGRAM_TYPES(EnhancedEnum): - BRAKET = "braket" - CIRQ = "cirq" - PENNYLANE = "pennylane" - PYQUIL = "pyquil" - QIBO = "qibo" - QISKIT = "qiskit" + def keys(cls) -> list[str]: + return [member.name.lower() for member in cls] try: @@ -90,6 +81,16 @@ class SUPPORTED_PROGRAM_TYPES(EnhancedEnum): ] +# Supported quantum programs. +class SUPPORTED_PROGRAM_TYPES(EnhancedEnum): + BRAKET = _BKCircuit + CIRQ = _Circuit + PENNYLANE = _QuantumTape + PYQUIL = _Program + QIBO = _QiboCircuit + QISKIT = _QuantumCircuit + + # Define MeasurementResult, a result obtained by measuring qubits on a quantum # computer. Bitstring = Union[str, List[int]] From 56f5e4998fe6374c763129570180f6744c953cf8 Mon Sep 17 00:00:00 2001 From: FarLab Date: Thu, 7 Nov 2024 03:28:52 +0100 Subject: [PATCH 24/38] LRE ZNE comparison tutorial (#2551) * add lre-zne-comparison.md to examples folder in docs * add tags to lre-zne-comparison.md in examples folder * add lre-zne-comparison.md to the examples.md file * fix broken link in lre-zne-comparison.md * cleaned up some cells * fix broken link in lre-zne-comparison.md * incorporated comments from Nate's review * replace statistics.mean with numpy.mean * change name in gallery * code formatting --------- Co-authored-by: nate stemen --- docs/source/examples/examples.md | 3 +- docs/source/examples/lre-zne-comparison.md | 190 +++++++++++++++++++++ 2 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 docs/source/examples/lre-zne-comparison.md diff --git a/docs/source/examples/examples.md b/docs/source/examples/examples.md index 13111b82c..edb7f6c64 100644 --- a/docs/source/examples/examples.md +++ b/docs/source/examples/examples.md @@ -29,6 +29,7 @@ ZNE and CDR with Cirq: 1D Ising Simulation ZNE with PennyLane + Cirq: Energy of molecular Hydrogen ZNE with BQSKit compiled circuits ZNE on Stim backend with Cirq: Logical randomized benchmarking circuits +LRE vs ZNE: comparing performance and overhead PEC on a Braket simulator: Mirror circuits PEC with Cirq: Learning representations Classical Shadows with Cirq: State Reconstruction and Observable Estimation @@ -40,4 +41,4 @@ GGI Summer School ZNE Hands-On Tutorial Composing techniques: REM + ZNE Composing techniques: DDD + ZNE The Mitiq paper code -``` \ No newline at end of file +``` diff --git a/docs/source/examples/lre-zne-comparison.md b/docs/source/examples/lre-zne-comparison.md new file mode 100644 index 000000000..e17afb395 --- /dev/null +++ b/docs/source/examples/lre-zne-comparison.md @@ -0,0 +1,190 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.16.1 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# Comparing LRE and ZNE + +Both LRE and ZNE work in two main stages: generate noise-scaled circuits via scaling, and apply inference to resulting measurements post-execution. + +This workflow can be executed by a single call to `execute_with_lre` or `execute_with_zne`. + +For resource estimation, Mitiq provides `multivariate_layer_scaling` to inspect the circuits that are to be executed. + +For ZNE we have access to the scaled circuits using the function `scaled_circuits`. + +## Problem Setup + +For this demonstration, we'll first define a quantum circuit, and a method of executing circuits for demonstration purposes. + +Here we will use the rotated randomized benchmarking circuits on a single qubit and generate 50 random such circuits. + +```{code-cell} ipython3 +from mitiq.benchmarks import generate_rotated_rb_circuits + +circuits = generate_rotated_rb_circuits( + n_qubits=1, num_cliffords=3, theta=0.7, trials=50, seed=4 +) + +print(circuits[0]) +``` + +We define an [executor](../guide/executors.md) which simulates the input circuit subjected to depolarizing noise, and returns the probability of measuring the ground state. + +By altering the value for `noise_level`, ideal and noisy expectation values can be obtained. + +```{code-cell} ipython3 +from cirq import DensityMatrixSimulator, depolarize + + +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 +``` + +Let's compute the expectation values with and without noise. + +```{code-cell} ipython3 +# Collect ideal and noisy values (probability of measuring 0 across all circuits). +noisy_values = [] +ideal_values = [] +for circuit in circuits: + noisy_values.append(execute(circuit)) + ideal_values.append(execute(circuit, noise_level=0.0)) +``` + +```{code-cell} ipython3 +import numpy as np + +# The theoretical value for the probability of measuring 0 when taking +# an average over all the rotated rb circuits. +p = lambda theta: 1 - (2 / 3) * np.sin(theta / 2) ** 2 + +print(f"Average error for noisy values: {abs(np.mean(noisy_values) - p(0.7))}") +print(f"Average error for ideal values: {abs(np.mean(ideal_values) - p(0.7))}") +``` + +For the ideal values we still see a small error, because we are only taking the average over 50 rotated randomized benchmarking circuits, so there will be noise due to randomness. + +The ideal value, defined in the funcion `p`, is attained when computing this average over all the rotated randomized benchmarking circuits. + +If you increase the number of circuits, you will find that the average error for ideal values tends to zero. + +## Apply LRE and ZNE directly + +With the circuit and executor defined, we just need to choose the polynomial extrapolation degree as well as the fold multiplier. + +For ZNE we use the default values for the scale factors. + +```{code-cell} ipython3 +from mitiq.lre import execute_with_lre +from mitiq.zne import execute_with_zne + +degree = 2 +fold_multiplier = 3 + +# Collect mitigated values (probability of measuring 0 across all circuits) using LRE and ZNE. +mitigated_values_lre = [] +mitigated_values_zne = [] + +for circuit in circuits: + mitigated_lre = execute_with_lre( + circuit, execute, degree=degree, fold_multiplier=fold_multiplier + ) + mitigated_values_lre.append(mitigated_lre) + mitigated_zne = execute_with_zne(circuit, execute) + mitigated_values_zne.append(mitigated_zne) +``` + +```{code-cell} ipython3 +error_lre = abs(np.mean(mitigated_values_lre) - p(0.7)) +error_zne = abs(np.mean(mitigated_values_zne) - p(0.7)) + +print(f"Average error of mitigated values using LRE: {error_lre}") +print(f"Average error of mitigated values using ZNE: {error_zne}") +``` + +## Resource estimation + +We now compare the resources required (number of circuits to run and circuit depth) to run these protocols to have a fair comparison. First we do this for LRE. + +```{code-cell} ipython3 +from mitiq.lre.multivariate_scaling import multivariate_layer_scaling + +avg_num_scaled_circuits_lre = 0.0 +avg_depth_scaled_circuits_lre = 0.0 + +for circuit in circuits: + noise_scaled_circuits_lre = multivariate_layer_scaling( + circuit, degree, fold_multiplier + ) + num_scaled_circuits_lre = len(noise_scaled_circuits_lre) + + avg_num_scaled_circuits_lre += num_scaled_circuits_lre / len(circuits) + avg_depth_scaled_circuits_lre += ( + (1 / len(circuits)) + * sum(len(circuit) for circuit in noise_scaled_circuits_lre) + / num_scaled_circuits_lre + ) + +print( + f"Average number of noise-scaled circuits for LRE = {avg_num_scaled_circuits_lre}" +) +print(f"Average circuit depth = {avg_depth_scaled_circuits_lre}") +``` + +Next for ZNE. + +```{code-cell} ipython3 +from mitiq.zne.scaling import fold_gates_at_random +from mitiq.zne.zne import scaled_circuits + +scale_factors = [1.0, 2.0, 3.0] + +avg_num_scaled_circuits_zne = 0.0 +avg_depth_scaled_circuits_zne = 0.0 + +for circuit in circuits: + noise_scaled_circuits_zne = scaled_circuits( + circuit=circuit, + scale_factors=[1.0, 2.0, 3.0], + scale_method=fold_gates_at_random, + ) + num_scaled_circuits_zne = len(noise_scaled_circuits_zne) + + avg_num_scaled_circuits_zne += num_scaled_circuits_zne / len(circuits) + avg_depth_scaled_circuits_zne += ( + (1 / len(circuits)) + * sum(len(circuit) for circuit in noise_scaled_circuits_zne) + / num_scaled_circuits_zne + ) + +print( + f"Average number of noise-scaled circuits for ZNE = {avg_num_scaled_circuits_zne}" +) +print(f"Average circuit depth = {avg_depth_scaled_circuits_zne}") +``` + +```{code-cell} ipython3 +print(f"Error improvement LRE over ZNE: {error_zne/error_lre}") +print( + f"Ratio number of circuits required for LRE vs ZNE: {avg_num_scaled_circuits_lre/avg_num_scaled_circuits_zne}" +) +``` + +## Conclusion + +With an additional cost of many circuits---in this case, around 17 times more---LRE achieves a notable improvement, reducing the error rate by approximately threefold. + +Although our current tests were limited in the number of circuits, which means these results carry some uncertainty, the potential of LRE is clear. + +There’s exciting promise and further research will help us better understand the balance between performance gains and resource requirements. From 53807c08cc1e893ebd117c074316f9f02c8a1192 Mon Sep 17 00:00:00 2001 From: Purva Thakre <66048318+purva-thakre@users.noreply.github.com> Date: Thu, 7 Nov 2024 09:19:17 -0600 Subject: [PATCH 25/38] Complete LRE user guide (#2542) * make private function public * check build and import path for part 3 * lre test change glossary * part 3 cleanup * glossary and readme * workflow diagram * Vincent: Apply suggestions from code review Co-authored-by: Vincent Russo * vincent's suggestions: part 2 * part 4 draft * underscore unit test * remove cirq warning * Apply suggestions from code review Co-authored-by: nate stemen * build error * workflow diagram and other edits * chunking folding methods * nate's suggested changes Co-authored-by: nate stemen * workflow_diagram + remove low level * suggestions round 2 * toctree * duplicated text landing page * add formula for the scale factor vector * formatting * fix docstring formatting * minor tweaking --------- Co-authored-by: Vincent Russo Co-authored-by: nate stemen --- README.md | 2 +- docs/source/guide/glossary.md | 4 + docs/source/guide/lre-1-intro.md | 5 - docs/source/guide/lre-3-options.md | 243 ++++++++++++++++++ docs/source/guide/lre-4-low-level.md | 37 +++ docs/source/guide/lre.md | 16 +- docs/source/img/lre_workflow_steps.png | Bin 0 -> 105005 bytes .../lre/inference/multivariate_richardson.py | 11 +- mitiq/lre/multivariate_scaling/__init__.py | 1 + .../multivariate_scaling/layerwise_folding.py | 4 +- mitiq/lre/tests/test_layerwise_folding.py | 8 +- 11 files changed, 307 insertions(+), 24 deletions(-) create mode 100644 docs/source/guide/lre-3-options.md create mode 100644 docs/source/guide/lre-4-low-level.md create mode 100644 docs/source/img/lre_workflow_steps.png diff --git a/README.md b/README.md index 6dee8d2f4..645843cc0 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,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)
[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)
[2002.08953](https://arxiv.org/abs/2002.08953)| -| Layerwise Richardson Extrapolation 🚧 | Coming soon | [`mitiq.lre`](https://github.com/unitaryfund/mitiq/tree/main/mitiq/lre) | [2402.04000](https://arxiv.org/abs/2402.04000) | +| Layerwise Richardson Extrapolation | [LRE](https://mitiq.readthedocs.io/en/stable/guide/lre.html) | [`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: diff --git a/docs/source/guide/glossary.md b/docs/source/guide/glossary.md index 4410fe272..fdbdd8bd5 100644 --- a/docs/source/guide/glossary.md +++ b/docs/source/guide/glossary.md @@ -56,6 +56,10 @@ trained with quantum circuits that resemble the circuit of interest, but which a : Sequences of gates are applied to slack windows (single-qubit idle windows) in a quantum circuit to reduce the coupling between the qubits and the environment, mitigating the effects of noise. +[Layerwise Richardson Extrapolation (LRE)](lre.md) +: Expectation values from multiple layerwise noise-scaled circuits are used to compute the error-mitigated expectation value +through multivariate Richardson extrapolation. + [Probabilistic Error Cancellation (PEC)](pec.md) : Ideal operations are represented as quasi-probability distributions over noisy implementable operations, and unbiased estimates of expectation values are obtained by averaging over circuits sampled according to this representation. diff --git a/docs/source/guide/lre-1-intro.md b/docs/source/guide/lre-1-intro.md index 8e195ad1e..298a4a0f5 100644 --- a/docs/source/guide/lre-1-intro.md +++ b/docs/source/guide/lre-1-intro.md @@ -18,11 +18,6 @@ LRE works in two main stages: generate noise-scaled circuits via layerwise scali This workflow can be executed by a single call to {func}`.execute_with_lre`. If more control is needed over the protocol, Mitiq provides {func}`.multivariate_layer_scaling` and {func}`.multivariate_richardson_coefficients` to handle the first and second steps respectively. -```{danger} -LRE is currently compatible with quantum programs written using `cirq`. -Work on making this technique compatible with other frontends is ongoing. 🚧 -``` - ## Problem Setup To demonstrate the use of LRE, we'll first define a quantum circuit, and a method of executing circuits for demonstration purposes. diff --git a/docs/source/guide/lre-3-options.md b/docs/source/guide/lre-3-options.md new file mode 100644 index 000000000..e9894b0af --- /dev/null +++ b/docs/source/guide/lre-3-options.md @@ -0,0 +1,243 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.11.1 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# What additional options are available when using LRE? + +In [](lre-1-intro.md), {func}`.execute_with_lre` was used to calculated the error mitigated expectation values. +In this section, we will outline the optional arguments that can be used and adjusted with this technique. + +```python +from mitiq import lre + +lre_value = lre.execute_with_lre( + circuit, + executor, + degree, + fold_multiplier, + folding_method=<"noise scaling method imported from zne.scaling.folding">, + num_chunks=<"number of chunks to group a large circuit into">, +) +``` + +The hyperparameters that can be controlled are: + +- `degree`: to modify the extrapolating polynomial +- `fold_multiplier`: to control how the circuit is scaled +- `folding_method`: to choose the unitary folding method +- `num_chunks`: to alter the sampling cost + +## Controlling how the noise-scaled circuits are created + +### Extrapolating polynomial + +The chosen degree of the extrapolating polynomial affects the way in which circuits get scaled, as we'll see below. +For this example, we'll define a circuit consisting of 4 layers. + +```{code-cell} ipython3 +from cirq import LineQubit, Circuit, CZ, CNOT, H + + +q0, q1, q2, q3 = LineQubit.range(4) +circuit = Circuit( + H(q0), + CNOT.on(q0, q1), + CZ.on(q1, q2), + CNOT.on(q2, q3), +) + +print(circuit) +``` + +For `degree = 2`, the scale factor pattern is generated through the terms in the monomial basis for the multivariate polynomial. For more information, see [](lre-5-theory.md). + +Here, $\lambda_i$ refers to the folding factor for the $i$-th layer. The example monomial basis is given by: + +$$\{1, λ_1, λ_2, λ_3, λ_4, λ_1^2, λ_1 λ_2, λ_1 λ_3, λ_1 λ_4, λ_2^2, λ_2 λ_3, λ_2 λ_4, λ_3^2, λ_3 λ_4, λ_4^2\}$$ + +Each vector of scale factor vectors is given by $\boldsymbol{\lambda}_i = \boldsymbol{1} + 2 \boldsymbol{m}_i$ where $\boldsymbol{1} = (1, 1, \ldots, 1)$ and $\boldsymbol{m}_i$ is a vector of non-negative integers representing the number of times a layer is to be folded as dictated by the fold multiplier. + +```{code-cell} ipython3 +from mitiq.lre.multivariate_scaling import get_scale_factor_vectors + +scale_factors = get_scale_factor_vectors(circuit, degree=2, fold_multiplier=2) + +print(scale_factors) +``` + +In the noise scaled circuits created using the above scale factor vectors: + +- The term $1$ in the monomial terms basis corresponds to the `degree = 0` term in the polynomial which is equivalent to + the $\lambda_1^0\lambda_2^0\lambda_3^0\lambda_4^0$ term. Due to this term, the first noise-scaled circuit is unchanged. + +- due to the $λ_1$ term in the monomial basis, the second noise-scaled circuit only scales the first layer in the circuit. + +- due to the $λ_2$ term in the monomial basis, the next noise-scaled circuit only scales the second layer in the circuit + +- and so on. + +The total number of noise-scaled circuits is given by $\binom{d + l - 1}{d}$ where $l$ is the number of layers in the circuit and $d$ is the chosen degree of the multivariate polynomial as discussed in [](lre-5-theory.md). + +```{code-cell} ipython3 +print(f"Total number of noise scaled circuits created: {len(scale_factors)}") +``` + +As the `fold_multiplier` is changed, the number of scaled circuits remains the same but how the layers are scaled +is altered. + +```{code-cell} ipython3 +scale_factors_diff_fold_multiplier = get_scale_factor_vectors( + circuit, + degree=2, + fold_multiplier=3, +) + + +print(f"Total number of noise-scaled circuits created with different" + f" fold_multiplier: {len(scale_factors_diff_fold_multiplier)}") + +print(f"Scale factor for some noise scaled circuit with degree=2 " + f"and fold_multiplier=2: \n {scale_factors[-2]}") + +print(f"Scale factor for some noise scaled circuit with degree= 2 " + f"but fold_multiplier=3: \n {scale_factors_diff_fold_multiplier[-2]}") +``` + +Both the number of noise scaled circuits and scale factor vectors are changed when a different value for `degree` is used while keeping everything else the same. + +```{code-cell} ipython3 +scale_factors_diff_degree = get_scale_factor_vectors( + circuit, + degree=3, + fold_multiplier=2, +) + +print(f"Total number of noise scaled circuits created: " + f"{len(scale_factors_diff_degree)}") +``` + +Thus, even though `degree` and `fold_multiplier` are required to use {func}`.execute_with_lre`, they function as a tunable +hyperparameter affecting the performance of the technique. + +## Chunking a circuit into fewer layers + +When you have a large circuit, the size of the sample matrix increases as the number of monomial terms scale polynomially. The size of the sample matrix influences sampling cost. In such a case, a circuit of 100 layers could be grouped into 4 chunks where each chunk consists of 25 collated layers. The noise scaling function {func}`.multivariate_layer_scaling` +treats each chunk as a layer to be scaled when the parameter `num_chunks` is used. Thus, for the 100 layer circuit grouped into 4 chunks with `degree = 2` and `fold_multiplier = 2`, only 15 noise-scaled circuits are created i.e. sample matrix is reduced to dimension $15 \times 15$. + +```{caution} +Reducing the sampling cost by chunking the circuit can affect the performance of the technique. +``` + +Suppose we want to chunk our example circuit into 2 chunks while using `degree = 2` and +`fold_multiplier = 2`. The sample matrix defined by the monomial terms is reduced in size as chunking the circuit +will create a new monomial basis for the extrapolating polynomial. + +$$\{1, λ_1, λ_2, λ_1^2, λ_1 λ_2, λ_2^2\}$$ + +The scale factor vectors change as shown below: + +```{code-cell} ipython3 +scale_factors_with_chunking = get_scale_factor_vectors( + circuit, + degree=2, + fold_multiplier=2, + num_chunks=2, +) + +print(scale_factors_with_chunking) +``` + +Thus, the total number of noise-scaled circuits is reduced by chunking the circuit into fewer layers to be folded. + +```{code-cell} ipython3 +print(f"Total number of noise scaled circuits with chunking: " + f"{len(scale_factors_with_chunking)}") + +print(f"Total number of noise scaled circuits without chunking: " + f"{len(scale_factors)}") +``` + +How the noise-scaled circuits are chunked differs greatly as each chunk in the circuit is now equivalent to a layer to be folded via unitary folding. In the example below, we compare the second noise-scaled circuit in a chunked and a non-chunked +circuit which corresponds to the $λ_1$ term in the monomial basis. + +```{code-cell} ipython3 +from mitiq.lre.multivariate_scaling import multivariate_layer_scaling + +# apply chunking +chunked_circ = multivariate_layer_scaling( + circuit, degree=2, fold_multiplier=2, num_chunks=2 +)[1] + + +# skip chunking +non_chunked_circ = multivariate_layer_scaling( + circuit, degree=2, fold_multiplier=2 +)[1] + + +print("original circuit: ", circuit, sep="\n") +print("Noise scaled circuit created with chunking: ", chunked_circ, sep="\n") +print( + "Noise scaled circuit created without chunking: ", + non_chunked_circ, + sep="\n", +) +``` + +### Noise scaling method + +The default choice for unitary folding in {func}`.execute_with_lre` and {func}`.multivariate_layer_scaling` is +{func}`.fold_gates_at_random()`. + +However, there are two other choices as well: {func}`.fold_all()` and {func}`.fold_global()` which can be used for the +`folding_method` parameter in {func}`.execute_with_lre`. + +```{tip} +The choice of folding method matters only when chunking is employed. +Otherwise the noise scaled circuits created using either of the folding methods will look identical as they are created by scaling each layer as required. +``` + +```{code-cell} ipython3 +from mitiq.zne.scaling import fold_all, fold_global + + +# apply local folding +local_fold_circ = multivariate_layer_scaling( + circuit, degree=2, fold_multiplier=2, folding_method=fold_all +)[-2] + + +# apply global folding +global_fold_circ = multivariate_layer_scaling( + circuit, + degree=2, + fold_multiplier=2, + num_chunks=2, + folding_method=fold_global, +)[-2] + + +print("original circuit: ", circuit, sep="\n") +print( + "Noise scaled circuit created using local unitary folding: ", + local_fold_circ, + sep="\n", +) +print( + "Noise scaled circuit created using global unitary folding and chunking: ", + global_fold_circ, + sep="\n", +) +``` + +This section showed in detail how to vary the default and non-default parameters required by the technique. +An in-depth discussion on these is provided in [](lre-4-low-level.md) diff --git a/docs/source/guide/lre-4-low-level.md b/docs/source/guide/lre-4-low-level.md new file mode 100644 index 000000000..07cdee8c4 --- /dev/null +++ b/docs/source/guide/lre-4-low-level.md @@ -0,0 +1,37 @@ +--- +jupytext: + text_representation: + extension: .md + format_name: myst + format_version: 0.13 + jupytext_version: 1.11.1 +kernelspec: + display_name: Python 3 + language: python + name: python3 +--- + +# What happens when I use LRE? + +As shown in the figure below, LRE works in two steps, layerwise noise scaling and extrapolation. + +The noise-scaled circuits are created through the functions in {mod}`mitiq.lre.multivariate_scaling.layerwise_folding` while the error-mitigated expectation value is estimated by using the functions in {mod}`mitiq.lre.inference.multivariate_richardson`. + +```{figure} ../img/lre_workflow_steps.png +--- +width: 700px +name: lre-overview2 +--- +The diagram shows the workflow of the layerwise Richardson extrapolation (LRE) in Mitiq. +``` + +**The first step** involves generating and executing layerwise noise-scaled quantum circuits. + +- The user provides a `QPROGRAM` i.e. a frontend supported quantum circuit. +- Mitiq generates a set of layerwise noise-scaled circuits by applying unitary folding based on a set of pre-determined scale factor vectors. + +The noise-scaled circuits are then executed on a backend, obtaining a set of noisy expectation values. + +**The second step** involves inferring the error mitigated expectation value from the measured results through multivariate richardson extrapolation. + +The function {func}`.execute_with_lre` accomplishes both steps behind the scenes to estimate the error mitigate expectation value. Additional information is available in [](lre-1-intro.md). The linked page also has a section demonstrating how to apply each step individually. diff --git a/docs/source/guide/lre.md b/docs/source/guide/lre.md index b9a54e121..69bf5e39a 100644 --- a/docs/source/guide/lre.md +++ b/docs/source/guide/lre.md @@ -1,13 +1,13 @@ -```{warning} -The user guide for LRE in Mitiq is currently under construction. -``` # Layerwise Richardson Extrapolation -Layerwise Richardson Extrapolation (LRE), an error mitigation technique, introduced in -{cite}`Russo_2024_LRE` extends the ideas found in ZNE by allowing users to create multiple noise-scaled variations of the input -circuit such that the noiseless expectation value is extrapolated from the execution of each -noisy circuit. +```{figure} ../img/lre_workflow_steps.png +--- +width: 700px +name: lre-overview +--- +The diagram shows the workflow of the layerwise Richardson extrapolation (LRE) in Mitiq. +``` Layerwise Richardson Extrapolation (LRE), an error mitigation technique, introduced in {cite}`Russo_2024_LRE` works by creating multiple noise-scaled variations of the input @@ -24,5 +24,7 @@ maxdepth: 1 --- lre-1-intro.md lre-2-use-case.md +lre-3-options.md +lre-4-low-level.md lre-5-theory.md ``` diff --git a/docs/source/img/lre_workflow_steps.png b/docs/source/img/lre_workflow_steps.png new file mode 100644 index 0000000000000000000000000000000000000000..540fad61debac754b80f11b2309b087f509a57a0 GIT binary patch literal 105005 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 literal 0 HcmV?d00001 diff --git a/mitiq/lre/inference/multivariate_richardson.py b/mitiq/lre/inference/multivariate_richardson.py index 6dac868c5..daa6ac71f 100644 --- a/mitiq/lre/inference/multivariate_richardson.py +++ b/mitiq/lre/inference/multivariate_richardson.py @@ -17,7 +17,7 @@ from mitiq.interface import accept_any_qprogram_as_input from mitiq.lre.multivariate_scaling.layerwise_folding import ( - _get_scale_factor_vectors, + get_scale_factor_vectors, ) @@ -94,7 +94,7 @@ def sample_matrix( if fold_multiplier < 1: raise ValueError("Fold multiplier must be greater than or equal to 1.") - scale_factor_vectors = _get_scale_factor_vectors( + scale_factor_vectors = get_scale_factor_vectors( input_circuit, degree, fold_multiplier, num_chunks ) num_layers = len(scale_factor_vectors[0]) @@ -134,8 +134,9 @@ def multivariate_richardson_coefficients( :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 = (, , , ..., )^T$. + $c = (c_1, c_2, …, c_M)$ associated with a known vector of noisy + expectation values :math:`z = (\langle O(λ_1)\rangle, + \langle O(λ_2)\rangle, ..., \langle O(λ_M)\rangle)^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 @@ -158,7 +159,7 @@ def multivariate_richardson_coefficients( input_circuit, degree, fold_multiplier, num_chunks ) num_layers = len( - _get_scale_factor_vectors( + get_scale_factor_vectors( input_circuit, degree, fold_multiplier, num_chunks ) ) diff --git a/mitiq/lre/multivariate_scaling/__init__.py b/mitiq/lre/multivariate_scaling/__init__.py index 816dfaa20..15aa5be6b 100644 --- a/mitiq/lre/multivariate_scaling/__init__.py +++ b/mitiq/lre/multivariate_scaling/__init__.py @@ -3,4 +3,5 @@ from mitiq.lre.multivariate_scaling.layerwise_folding import ( multivariate_layer_scaling, + get_scale_factor_vectors, ) diff --git a/mitiq/lre/multivariate_scaling/layerwise_folding.py b/mitiq/lre/multivariate_scaling/layerwise_folding.py index ab38e8c88..f4cb3493a 100644 --- a/mitiq/lre/multivariate_scaling/layerwise_folding.py +++ b/mitiq/lre/multivariate_scaling/layerwise_folding.py @@ -90,7 +90,7 @@ def _get_chunks( ] -def _get_scale_factor_vectors( +def get_scale_factor_vectors( input_circuit: Circuit, degree: int, fold_multiplier: int, @@ -189,7 +189,7 @@ def _multivariate_layer_scaling( circuit_copy = deepcopy(input_circuit) terminal_measurements = _pop_measurements(circuit_copy) - scaling_pattern = _get_scale_factor_vectors( + scaling_pattern = get_scale_factor_vectors( circuit_copy, degree, fold_multiplier, num_chunks ) diff --git a/mitiq/lre/tests/test_layerwise_folding.py b/mitiq/lre/tests/test_layerwise_folding.py index 12ab80f29..25ba07b55 100644 --- a/mitiq/lre/tests/test_layerwise_folding.py +++ b/mitiq/lre/tests/test_layerwise_folding.py @@ -17,7 +17,7 @@ from mitiq.lre.multivariate_scaling.layerwise_folding import ( _get_chunks, _get_num_layers_without_measurements, - _get_scale_factor_vectors, + get_scale_factor_vectors, multivariate_layer_scaling, ) @@ -242,7 +242,7 @@ def test_get_scale_factor_vectors_no_chunking( test_input, degree, test_fold_multiplier, expected_scale_factor_vectors ): """Verifies vectors of scale factors are calculated accurately.""" - calculated_scale_factor_vectors = _get_scale_factor_vectors( + calculated_scale_factor_vectors = get_scale_factor_vectors( test_input, degree, test_fold_multiplier ) @@ -261,7 +261,7 @@ def test_get_scale_factor_vectors_with_chunking( test_input, degree, test_fold_multiplier, test_chunks, expected_size ): """Verifies vectors of scale factors are calculated accurately.""" - calculated_scale_factor_vectors = _get_scale_factor_vectors( + calculated_scale_factor_vectors = get_scale_factor_vectors( test_input, degree, test_fold_multiplier, test_chunks ) @@ -293,7 +293,7 @@ def test_invalid_num_chunks(test_input, num_chunks, error_msg): """Ensures that the number of intended chunks in the input circuit raises an error for an invalid value.""" with pytest.raises(ValueError, match=error_msg): - _get_scale_factor_vectors(test_input, 2, 2, num_chunks) + get_scale_factor_vectors(test_input, 2, 2, num_chunks) @pytest.mark.parametrize( From 45778e82975fdd90e0685651d451de93b4107e9b Mon Sep 17 00:00:00 2001 From: nate stemen Date: Thu, 7 Nov 2024 07:19:56 -0800 Subject: [PATCH 26/38] 0.41.0 release (#2557) * release notes * acknowledgements * add discussion link --- CHANGELOG.md | 31 +++++++++++++++++++++++++++++-- VERSION.txt | 2 +- 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07310f3d7..9b0c52727 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,34 @@ ## Version 0.41.0 -_In development._ +([Full Changelog](https://github.com/unitaryfund/mitiq/compare/v0.40.0...v0.41.0)) + +### Highlights + +📓 The Layerwise Richardson Extrapolation **(LRE) user guide is complete**! +The user guide contains information about both the ins and outs of using the implementation, as well as covering the theory behind the technique so you can make judgements about when to apply the technique. +In addition to finishing the user guide, we also have a new tutorial comparing both the performance and overhead needed for LRE and ZNE. +Big thanks to @purva-thakre and @FarLab for the documentation! + +📹 As part of launching LRE we made a **short tutorial video** to showcase the technique, along with how to use it. +Check it out [here](https://www.youtube.com/watch?v=47GWi4h7TWM)! + +🧑‍🔬 **First time contributor** @jpacold recreated results from [a paper](https://arxiv.org/abs/2211.08318) on phase transitions in the Ising model. +Both the paper authors, and @jpacold both used Mitiq's ZNE module to apply error mitigation. +This is both an informative tutorial on turning physics problems into something amenable on a quantum computer, and a class in applying error-mitigation. + +#### ✨ Enhancements + +- Ensure LRE compatibility with all supported frontends (#2547) [@natestemen] + +#### 🧑🏽‍💻 Developer Improvements + +- remove failing test; simplify layerwise ZNE tests (#2545) [@natestemen] + +### 📞 Call for ideas + +We're currently looking into what features we could add to make Mitiq more **noise-aware**. +If you have ideas and features requests in this area, do make a post on the GitHub discussion [here](https://github.com/unitaryfund/mitiq/discussions/2193)! ## Version 0.40.0 @@ -16,7 +43,7 @@ Documentation is also available in the user guide, with more advanced docs and d Special thanks to Purva Thakre for this contribution! 🥇 We had two **first time contributions** from @ecarlander and @mbrotos! -Thank you both for your contributions! +Thank you both for your contributions! 🛡️ A **helpful error message** is raised when passing data of the incorrect type to the `MeasurementResult` class, where before it silently gave confusing results. diff --git a/VERSION.txt b/VERSION.txt index 2b8101664..72a8a6313 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -0.41.0dev +0.41.0 From 33151844f54e3bee1f41ffd406c3783022bc9f6a Mon Sep 17 00:00:00 2001 From: Alessandro Cosentino Date: Wed, 13 Nov 2024 13:55:47 +0100 Subject: [PATCH 27/38] Fix import of mirror qv circuit function (#2570) --- mitiq/benchmarks/__init__.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mitiq/benchmarks/__init__.py b/mitiq/benchmarks/__init__.py index ae0adbcee..d8c6a4509 100644 --- a/mitiq/benchmarks/__init__.py +++ b/mitiq/benchmarks/__init__.py @@ -8,6 +8,7 @@ generate_rotated_rb_circuits, ) from mitiq.benchmarks.mirror_circuits import generate_mirror_circuit +from mitiq.benchmarks.mirror_qv_circuits import generate_mirror_qv_circuit from mitiq.benchmarks.ghz_circuits import generate_ghz_circuit from mitiq.benchmarks.quantum_volume_circuits import ( generate_quantum_volume_circuit, From 485e9a1aa9ccba429abff027bb15cdf73f966c85 Mon Sep 17 00:00:00 2001 From: Purva Thakre <66048318+purva-thakre@users.noreply.github.com> Date: Thu, 14 Nov 2024 10:31:47 -0600 Subject: [PATCH 28/38] dev mode (#2573) --- CHANGELOG.md | 5 +++++ VERSION.txt | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9b0c52727..1f9da3f16 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog + +## Version 0.42.0 + +In Development. + ## Version 0.41.0 ([Full Changelog](https://github.com/unitaryfund/mitiq/compare/v0.40.0...v0.41.0)) diff --git a/VERSION.txt b/VERSION.txt index 72a8a6313..dbb503fd4 100644 --- a/VERSION.txt +++ b/VERSION.txt @@ -1 +1 @@ -0.41.0 +0.42.0dev From 9561b9fc17f50254d3a29a87a839219ba7c72680 Mon Sep 17 00:00:00 2001 From: Brian G Date: Thu, 14 Nov 2024 08:37:55 -0800 Subject: [PATCH 29/38] Update Calibrator method docstrings for formatting (#2571) * Update docstring for formatting * Trigger RTD --- mitiq/calibration/calibrator.py | 86 +++++++++++++++++---------------- 1 file changed, 45 insertions(+), 41 deletions(-) diff --git a/mitiq/calibration/calibrator.py b/mitiq/calibration/calibrator.py index e88a86eaf..4f81fb6c0 100644 --- a/mitiq/calibration/calibrator.py +++ b/mitiq/calibration/calibrator.py @@ -94,30 +94,32 @@ def _get_errors( return noisy_error, mitigated_error def log_results_flat(self) -> None: - """Prints calibration results in the following form - ┌──────────────────────────┬──────────────────────────────┬────────────────────────────┐ - │ benchmark │ strategy │ performance │ - ├──────────────────────────┼──────────────────────────────┼────────────────────────────┤ - │ Type: rb │ Technique: ZNE │ ✔ │ - │ Num qubits: 2 │ Factory: Richardson │ Noisy error: 0.101 │ - │ Circuit depth: 323 │ Scale factors: 1.0, 3.0, 5.0 │ Mitigated error: 0.0294 │ - │ Two qubit gate count: 77 │ Scale method: fold_global │ Improvement factor: 3.4398 │ - ├──────────────────────────┼──────────────────────────────┼────────────────────────────┤ - │ Type: rb │ Technique: ZNE │ ✔ │ - │ Num qubits: 2 │ Factory: Richardson │ Noisy error: 0.101 │ - │ Circuit depth: 323 │ Scale factors: 1.0, 2.0, 3.0 │ Mitigated error: 0.0501 │ - │ Two qubit gate count: 77 │ Scale method: fold_global │ Improvement factor: 2.016 │ - ├──────────────────────────┼──────────────────────────────┼────────────────────────────┤ - │ Type: ghz │ Technique: ZNE │ ✔ │ - │ Num qubits: 2 │ Factory: Richardson │ Noisy error: 0.0128 │ - │ Circuit depth: 2 │ Scale factors: 1.0, 2.0, 3.0 │ Mitigated error: 0.0082 │ - │ Two qubit gate count: 1 │ Scale method: fold_global │ Improvement factor: 1.561 │ - ├──────────────────────────┼──────────────────────────────┼────────────────────────────┤ - │ Type: ghz │ Technique: ZNE │ ✘ │ - │ Num qubits: 2 │ Factory: Richardson │ Noisy error: 0.0128 │ - │ Circuit depth: 2 │ Scale factors: 1.0, 3.0, 5.0 │ Mitigated error: 0.0137 │ - │ Two qubit gate count: 1 │ Scale method: fold_global │ Improvement factor: 0.9369 │ - └──────────────────────────┴──────────────────────────────┴────────────────────────────┘ + """ + Prints the results of the calibrator in flat form:: + + ┌──────────────────────────┬──────────────────────────────┬────────────────────────────┐ + │ benchmark │ strategy │ performance │ + ├──────────────────────────┼──────────────────────────────┼────────────────────────────┤ + │ Type: rb │ Technique: ZNE │ ✔ │ + │ Num qubits: 2 │ Factory: Richardson │ Noisy error: 0.101 │ + │ Circuit depth: 323 │ Scale factors: 1.0, 3.0, 5.0 │ Mitigated error: 0.0294 │ + │ Two qubit gate count: 77 │ Scale method: fold_global │ Improvement factor: 3.4398 │ + ├──────────────────────────┼──────────────────────────────┼────────────────────────────┤ + │ Type: rb │ Technique: ZNE │ ✔ │ + │ Num qubits: 2 │ Factory: Richardson │ Noisy error: 0.101 │ + │ Circuit depth: 323 │ Scale factors: 1.0, 2.0, 3.0 │ Mitigated error: 0.0501 │ + │ Two qubit gate count: 77 │ Scale method: fold_global │ Improvement factor: 2.016 │ + ├──────────────────────────┼──────────────────────────────┼────────────────────────────┤ + │ Type: ghz │ Technique: ZNE │ ✔ │ + │ Num qubits: 2 │ Factory: Richardson │ Noisy error: 0.0128 │ + │ Circuit depth: 2 │ Scale factors: 1.0, 2.0, 3.0 │ Mitigated error: 0.0082 │ + │ Two qubit gate count: 1 │ Scale method: fold_global │ Improvement factor: 1.561 │ + ├──────────────────────────┼──────────────────────────────┼────────────────────────────┤ + │ Type: ghz │ Technique: ZNE │ ✘ │ + │ Num qubits: 2 │ Factory: Richardson │ Noisy error: 0.0128 │ + │ Circuit depth: 2 │ Scale factors: 1.0, 3.0, 5.0 │ Mitigated error: 0.0137 │ + │ Two qubit gate count: 1 │ Scale method: fold_global │ Improvement factor: 0.9369 │ + └──────────────────────────┴──────────────────────────────┴────────────────────────────┘ """ # noqa: E501 table: List[List[Union[str, float]]] = [] headers: List[str] = ["benchmark", "strategy", "performance"] @@ -140,23 +142,25 @@ def log_results_flat(self) -> None: return print(tabulate(table, headers, tablefmt="simple_grid")) def log_results_cartesian(self) -> None: - """Prints calibration results in the following form - ┌──────────────────────────────┬────────────────────────────┬────────────────────────────┐ - │ strategy\benchmark │ Type: rb │ Type: ghz │ - │ │ Num qubits: 2 │ Num qubits: 2 │ - │ │ Circuit depth: 337 │ Circuit depth: 2 │ - │ │ Two qubit gate count: 80 │ Two qubit gate count: 1 │ - ├──────────────────────────────┼────────────────────────────┼────────────────────────────┤ - │ Technique: ZNE │ ✔ │ ✘ │ - │ Factory: Richardson │ Noisy error: 0.1128 │ Noisy error: 0.0117 │ - │ Scale factors: 1.0, 2.0, 3.0 │ Mitigated error: 0.0501 │ Mitigated error: 0.0439 │ - │ Scale method: fold_global │ Improvement factor: 2.2515 │ Improvement factor: 0.2665 │ - ├──────────────────────────────┼────────────────────────────┼────────────────────────────┤ - │ Technique: ZNE │ ✔ │ ✘ │ - │ Factory: Richardson │ Noisy error: 0.1128 │ Noisy error: 0.0117 │ - │ Scale factors: 1.0, 3.0, 5.0 │ Mitigated error: 0.0408 │ Mitigated error: 0.0171 │ - │ Scale method: fold_global │ Improvement factor: 2.7672 │ Improvement factor: 0.6852 │ - └──────────────────────────────┴────────────────────────────┴────────────────────────────┘ + """ + Prints the results of the calibrator in cartesian form:: + + ┌──────────────────────────────┬────────────────────────────┬────────────────────────────┐ + │ strategy\\benchmark │ Type: rb │ Type: ghz │ + │ │ Num qubits: 2 │ Num qubits: 2 │ + │ │ Circuit depth: 337 │ Circuit depth: 2 │ + │ │ Two qubit gate count: 80 │ Two qubit gate count: 1 │ + ├──────────────────────────────┼────────────────────────────┼────────────────────────────┤ + │ Technique: ZNE │ ✔ │ ✘ │ + │ Factory: Richardson │ Noisy error: 0.1128 │ Noisy error: 0.0117 │ + │ Scale factors: 1.0, 2.0, 3.0 │ Mitigated error: 0.0501 │ Mitigated error: 0.0439 │ + │ Scale method: fold_global │ Improvement factor: 2.2515 │ Improvement factor: 0.2665 │ + ├──────────────────────────────┼────────────────────────────┼────────────────────────────┤ + │ Technique: ZNE │ ✔ │ ✘ │ + │ Factory: Richardson │ Noisy error: 0.1128 │ Noisy error: 0.0117 │ + │ Scale factors: 1.0, 3.0, 5.0 │ Mitigated error: 0.0408 │ Mitigated error: 0.0171 │ + │ Scale method: fold_global │ Improvement factor: 2.7672 │ Improvement factor: 0.6852 │ + └──────────────────────────────┴────────────────────────────┴────────────────────────────┘ """ # noqa: E501 table: List[List[str]] = [] headers: List[str] = ["strategy\\benchmark"] From 16899a4753b2045b4a3b5bc0a8ae7d267a42867a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 09:22:41 +0100 Subject: [PATCH 30/38] Bump pyqrack from 1.32.11 to 1.32.21 (#2580) Bumps [pyqrack](https://github.com/vm6502q/pyqrack) from 1.32.11 to 1.32.21. - [Release notes](https://github.com/vm6502q/pyqrack/releases) - [Commits](https://github.com/vm6502q/pyqrack/compare/v1.32.11...v1.32.21) --- updated-dependencies: - dependency-name: pyqrack dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/requirements-dev.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements-dev.txt b/requirements/requirements-dev.txt index 6a80ce0d4..a9e42c8f3 100644 --- a/requirements/requirements-dev.txt +++ b/requirements/requirements-dev.txt @@ -28,4 +28,4 @@ bqskit==1.1.1 seaborn==0.13.0 stim==1.14.0 stimcirq==1.14.0 -pyqrack==1.32.11 \ No newline at end of file +pyqrack==1.32.21 \ No newline at end of file From 8e721ad6a8e87e64222f35da6d168da10549e030 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 18 Nov 2024 09:56:21 +0100 Subject: [PATCH 31/38] Bump codecov/codecov-action from 4 to 5 (#2576) Bumps [codecov/codecov-action](https://github.com/codecov/codecov-action) from 4 to 5. - [Release notes](https://github.com/codecov/codecov-action/releases) - [Changelog](https://github.com/codecov/codecov-action/blob/main/CHANGELOG.md) - [Commits](https://github.com/codecov/codecov-action/compare/v4...v5) --- updated-dependencies: - dependency-name: codecov/codecov-action dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 56af99557..f095f9b74 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -90,7 +90,7 @@ jobs: - name: Submit coverage report to Codecov # Only submit to Codecov once. if: ${{ matrix.python-version == '3.12' && matrix.os == 'ubuntu-latest'}} - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true From 56e7c005ddab3d554a61b7a4dd8a4a34daec1b26 Mon Sep 17 00:00:00 2001 From: Jake Muff <67475147+JMuff22@users.noreply.github.com> Date: Tue, 19 Nov 2024 22:53:04 +0200 Subject: [PATCH 32/38] Quick fix: Fix typo and headings in classical shadows tutorial (#2574) * Fix spelling channal > channel * Fix heading in shadows tutorial * Add note about equation 10 numerical constants --- docs/source/examples/shadows_tutorial.md | 10 +++++++--- docs/source/guide/shadows-5-theory.md | 2 +- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/docs/source/examples/shadows_tutorial.md b/docs/source/examples/shadows_tutorial.md index 42038321d..15df315e1 100644 --- a/docs/source/examples/shadows_tutorial.md +++ b/docs/source/examples/shadows_tutorial.md @@ -159,11 +159,15 @@ Based on the theorem, if the error rate of fidelity is $\epsilon$, i.e. |F(\rho,\sigma)-1|\leq\epsilon, \end{equation} then the minimum number of measurements $N$ (number of snapshots) should be: -\begin{equation} +```{math} +:label: eq-label N = \frac{34}{\epsilon^2}\left\|\rho-\mathrm{Tr}(\rho)/{2^n}\mathbb{I}\right\|_{\mathrm{shadow}}^2 -\end{equation} +``` with the shadow norm upper bound of the random Pauli measurement $\left\|\cdot\right\|_{\mathrm{shadow}}\leq 2^k\|\cdot\|_\infty$ when the operator acting on $k$ qubits, we have $N\leq 34\epsilon^{-2}2^{2n}+\mathcal{O}(e^{-n})$. Based on Fuchs–van de Graaf inequalities and properties of $L_p$ norm, $\|\rho-\sigma\|_2\leq \|\rho-\sigma\|_1 \leq (1-F(\rho,\sigma))^{1/2}$, the $L_2$ norm distance between the state reconstructed through classical shadow estimation and the state prepared by the circuit is upperbound by the fidelity error rate $\epsilon$. The dependency of the bound number of measurements $N$ to achieve the error rate $\epsilon$ is depicted in function `n_measurements_tomography_bound`. +```{note} +Equation {eq}`eq-label` comes from equation S13 in the paper {cite}`huang2020predicting`. It contains some numerical constants and as noted by Remark 1 these constants result from a worst case argument. You may see values much smaller in practice. +``` ```{code-cell} ipython3 # error rate of state reconstruction epsilon < 1. @@ -426,7 +430,7 @@ When we realize this code, it's important to consider that we record the equival Consequently, computing the mean estimator involves counting the number of exact matches between the observable and the classical shadow, and then multiplying the result by the appropriate sign. In the following, we present the function `expectation_estimation_shadow`, which allows for estimating any observable based on a classical shadow. This is realised by the main function `execute_with_shadows` when *state_reconstruction =* **False**. -###4.3 Shadow Estimation Bound on Estimation of Expectation Values of Observables +### 4.3 Shadow Estimation Bound on Estimation of Expectation Values of Observables The shadow estimation bound of operator expectation values is given by the following theorem: _________________________________________________________________________ diff --git a/docs/source/guide/shadows-5-theory.md b/docs/source/guide/shadows-5-theory.md index 54297006a..e4e598963 100644 --- a/docs/source/guide/shadows-5-theory.md +++ b/docs/source/guide/shadows-5-theory.md @@ -151,7 +151,7 @@ calculate $K$ estimators each of which is the average of $N$ single-round estima & \hat{f} = \mathrm{median}\{\bar{f}^{(1)},\cdots\bar{f}^{(K)}\}_{1\leq k\leq K} \end{eqnarray} The number of $\{f_m\}$ is related to the number of irreducible representations in the PTM[^1] representation of the twirling group. When the twirling group is the local Clifford group, the number of irreducible representations is $2^n$. -### 2.2 Noiseless Pauli Fidelity --- Ideal Inverse channal vs Estimate Noisy Inverse channel +### 2.2 Noiseless Pauli Fidelity --- Ideal Inverse channel vs Estimate Noisy Inverse channel One could check that in the absence of noise in the quantum gates ($\Lambda\equiv\mathbb{I}$), the value of the Pauli fidelity $\hat{f}_{b}^{\mathrm{ideal}}\equiv \mathrm{Tr}(\mathcal{M}_z \Pi_b)/\mathrm{Tr}\Pi_b = 3^{-|{b}|}$, where $|b|$ is the count of $|1\rangle$ found in z-eigenstates $|b\rangle:=|b_i\rangle^{\otimes n}$. When the noisy channel $\widehat{\mathcal{M}}$ is considered, the inverse of the noise channel $\widehat{\mathcal{M}}^{-1}$ can be obtained by: From 0262bfc086c9b84d186c825a2b0e99db7a4645bf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Nov 2024 23:11:43 -0800 Subject: [PATCH 33/38] Bump pyqrack from 1.32.21 to 1.32.27 (#2582) * Bump pyqrack from 1.32.21 to 1.32.27 Bumps [pyqrack](https://github.com/vm6502q/pyqrack) from 1.32.21 to 1.32.27. - [Release notes](https://github.com/vm6502q/pyqrack/releases) - [Commits](https://github.com/vm6502q/pyqrack/compare/v1.32.21...v1.32.27) --- updated-dependencies: - dependency-name: pyqrack dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] * use correct dependency name --------- Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: nate stemen --- .github/dependabot.yml | 2 +- requirements/requirements-dev.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 32c8d1d56..dd5140360 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -42,7 +42,7 @@ updates: update-types: ["version-update:semver-minor", "version-update:semver-patch"] - dependency-name: "pandas" update-types: ["version-update:semver-minor", "version-update:semver-patch"] - - dependency-name: "qrack" + - dependency-name: "pyqrack" update-types: ["version-update:semver-minor", "version-update:semver-patch"] # Allow up to 10 open pull requests for pip dependencies open-pull-requests-limit: 10 diff --git a/requirements/requirements-dev.txt b/requirements/requirements-dev.txt index a9e42c8f3..29ea9392d 100644 --- a/requirements/requirements-dev.txt +++ b/requirements/requirements-dev.txt @@ -28,4 +28,4 @@ bqskit==1.1.1 seaborn==0.13.0 stim==1.14.0 stimcirq==1.14.0 -pyqrack==1.32.21 \ No newline at end of file +pyqrack==1.32.27 \ No newline at end of file From edeede6952cb650f8f60aa0dec3632bef0b623a3 Mon Sep 17 00:00:00 2001 From: Brian G Date: Fri, 22 Nov 2024 08:09:14 -0800 Subject: [PATCH 34/38] Add log param to calibration guide (#2568) * Add log param to calibration guide * Change from relative to absolute path * Update log param description and links * Update cross-reference * Update run docstring with log param * Remove meta data updates * remove whitespace * remove jupytext format --- docs/source/guide/calibrators.md | 19 +++++++++++++++---- mitiq/calibration/calibrator.py | 8 +++++++- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/docs/source/guide/calibrators.md b/docs/source/guide/calibrators.md index e6db0f4f4..617dc94d1 100644 --- a/docs/source/guide/calibrators.md +++ b/docs/source/guide/calibrators.md @@ -57,6 +57,19 @@ print(cal.get_cost()) cal.run() ``` +## Verbose Log Output + +To print results from the experiments the calibrator performs, the `log` parameter can be passed in to the `run` method with either the value of `flat` or `cartesian`. By using the `log` parameter, detailed information about each experiment is printed when the `run` method completes. + +The detailed information can also be generated from the results of the calibrator after `run` is called by calling either {meth}`.log_results_flat` or {meth}`.log_results_cartesian`. + +The two options display the information in different formats, though both use a cross (✘) or a check (✔) to signal whether the error mitigation expirement obtained an expectation value better than the non-mitigated one. + +```{code-cell} ipython3 +# cal.results.log_results_flat() +cal.results.log_results_cartesian() +``` + ## Applying the optimal error mitigation strategy We first define randomized benchmarking circuit to test the effect of error mitigation. @@ -86,12 +99,10 @@ def execute(circuit, noise_level=0.001): cal.execute_with_mitigation(circuit, execute) ``` -```{code-cell} ipython3 - -``` - ## Tutorial You can find an example on quantum error mitigation calibration in the **[Examples](../examples/calibration-tutorial.md)** section of the documentation. This example illustrates functionalities from the calibration module using ZNE on a simulated IBM Quantum backend using Qiskit, defining a new settings object. + ++++ diff --git a/mitiq/calibration/calibrator.py b/mitiq/calibration/calibrator.py index 4f81fb6c0..f034d0bc8 100644 --- a/mitiq/calibration/calibrator.py +++ b/mitiq/calibration/calibrator.py @@ -297,7 +297,13 @@ def get_cost(self) -> Dict[str, int]: } def run(self, log: Optional[OutputForm] = None) -> None: - """Runs all the circuits required for calibration.""" + """Runs all the circuits required for calibration. + + args: + log: If set, detailed results of each experiment run by the + calibrator are printed. The value corresponds to the format of + the information and can be set to “flat” or “cartesian”. + """ if not self.results.is_missing_data(): self.results.reset_data() From 97702bfa0ad698f555cc71c7dd17f78e48a73cd9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 24 Nov 2024 16:11:15 +0100 Subject: [PATCH 35/38] Update qibo requirement from ~=0.2.7 to ~=0.2.13 (#2559) Updates the requirements on [qibo](https://github.com/qiboteam/qibo) to permit the latest version. - [Release notes](https://github.com/qiboteam/qibo/releases) - [Commits](https://github.com/qiboteam/qibo/compare/v0.2.7...v0.2.13) --- updated-dependencies: - dependency-name: qibo dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- requirements/requirements-qibo.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements/requirements-qibo.txt b/requirements/requirements-qibo.txt index 4a610df8f..993a565a8 100644 --- a/requirements/requirements-qibo.txt +++ b/requirements/requirements-qibo.txt @@ -1 +1 @@ -qibo~=0.2.7 +qibo~=0.2.13 From be6a76ce940033ddd58538f3fe9e989b8e536850 Mon Sep 17 00:00:00 2001 From: "Duong H. D" <79622476+gluonhiggs@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:01:38 +0700 Subject: [PATCH 36/38] Fix converting Rxx and similar Qiskit gates (#2579) --- AUTHORS | 1 + mitiq/interface/mitiq_qiskit/conversions.py | 5 ++++- .../mitiq_qiskit/tests/test_conversions_qiskit.py | 8 ++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/AUTHORS b/AUTHORS index d253a68a5..b796b2cdd 100644 --- a/AUTHORS +++ b/AUTHORS @@ -59,3 +59,4 @@ Vladimir Kozhukalov Francesc Sabater Emiliano Godinez Tommy Nguyen +Duong H. D. Tran \ No newline at end of file diff --git a/mitiq/interface/mitiq_qiskit/conversions.py b/mitiq/interface/mitiq_qiskit/conversions.py index 72fb4c94f..77d62e2e8 100644 --- a/mitiq/interface/mitiq_qiskit/conversions.py +++ b/mitiq/interface/mitiq_qiskit/conversions.py @@ -249,14 +249,17 @@ def from_qiskit(circuit: qiskit.QuantumCircuit) -> cirq.Circuit: Returns: Mitiq circuit representation equivalent to the input Qiskit circuit. """ + try: mitiq_circuit = from_qasm(qasm2.dumps(circuit)) except QasmException: # Try to decompose circuit before running # This is necessary for converting qiskit circuits with # custom packaged gates, e.g., QFT gates - circuit = circuit.decompose() + GATES_TO_DECOMPOSE = ["rxx", "rzz", "rzx", "ryy", "QFT"] + circuit = circuit.decompose(gates_to_decompose=GATES_TO_DECOMPOSE) mitiq_circuit = from_qasm(qasm2.dumps(circuit)) + return mitiq_circuit diff --git a/mitiq/interface/mitiq_qiskit/tests/test_conversions_qiskit.py b/mitiq/interface/mitiq_qiskit/tests/test_conversions_qiskit.py index 4ceb8d5d4..52d146cf3 100644 --- a/mitiq/interface/mitiq_qiskit/tests/test_conversions_qiskit.py +++ b/mitiq/interface/mitiq_qiskit/tests/test_conversions_qiskit.py @@ -476,3 +476,11 @@ def test_remove_identity_from_idle_with_multiple_registers(): input_multi, input_single = _multi_reg_circuits() assert circuit_multi_reg == input_multi assert circuit_single_reg == input_single + + +def test_convert_to_mitiq_with_rx_and_rzz(): + """Tests that convert_to_mitiq works with RX and RZZ gates.""" + test_qc = qiskit.QuantumCircuit(2) + test_qc.rx(0.1, 0) + test_qc.rzz(0.1, 0, 1) + assert convert_to_mitiq(test_qc) From a2a6818a37989551ee2c3da4fc322020e722134b Mon Sep 17 00:00:00 2001 From: Alessandro Cosentino Date: Mon, 2 Dec 2024 14:32:50 +0100 Subject: [PATCH 37/38] Fix constant value in ZNE docs (#2591) --- docs/source/guide/zne-3-options.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/guide/zne-3-options.md b/docs/source/guide/zne-3-options.md index 4439b444b..c5613bad0 100644 --- a/docs/source/guide/zne-3-options.md +++ b/docs/source/guide/zne-3-options.md @@ -485,7 +485,7 @@ noise_scaling_function = partial( fidelities = {"single": 1.0}, # Avoid folding single-qubit gates random_state=random_state, # Useful to get reproducible results ) -# Exponential fit with scale factors [1, 2, 3], assuming an infinite-noise limit of 0.5. +# Exponential fit with scale factors [1, 2, 3], assuming an infinite-noise limit of 0.25. factory = zne.inference.ExpFactory(scale_factors=[1, 2, 3], asymptote=0.25) zne_value = zne.execute_with_zne( From da77ec0a2934af416cb0891eff689b761290ed7e Mon Sep 17 00:00:00 2001 From: Brian G Date: Tue, 3 Dec 2024 13:32:59 -0800 Subject: [PATCH 38/38] Throw execption for multi measurement (#2593) --- mitiq/observable/pauli.py | 15 +++++++++++++++ mitiq/observable/tests/test_pauli.py | 11 +++++++++++ 2 files changed, 26 insertions(+) diff --git a/mitiq/observable/pauli.py b/mitiq/observable/pauli.py index bf68af671..bc314e132 100644 --- a/mitiq/observable/pauli.py +++ b/mitiq/observable/pauli.py @@ -279,11 +279,26 @@ def _measure_in( basis_rotations = set() support = set() + qubits_with_measurements = set[cirq.Qid]() + + # Find any existing measurement gates in the circuit + for _, op, _ in circuit.findall_operations_with_gate_type( + cirq.MeasurementGate + ): + qubits_with_measurements.update(op.qubits) + for pauli in paulis.elements: basis_rotations.update(pauli._basis_rotations()) support.update(pauli._qubits_to_measure()) measured = circuit + basis_rotations + cirq.measure(*sorted(support)) + if support & qubits_with_measurements: + raise ValueError( + f"More than one measurement found for qubits: " + f"{support & qubits_with_measurements}. Only a single " + f"measurement is allowed per qubit." + ) + # Transform circuit back to original qubits. reverse_qubit_map = dict(zip(qubit_map.values(), qubit_map.keys())) return measured.transform_qubits(lambda q: reverse_qubit_map[q]) diff --git a/mitiq/observable/tests/test_pauli.py b/mitiq/observable/tests/test_pauli.py index 2cf00eda5..e71d25e48 100644 --- a/mitiq/observable/tests/test_pauli.py +++ b/mitiq/observable/tests/test_pauli.py @@ -137,6 +137,17 @@ def test_pauli_measure_in_bad_qubits_error(): pauli.measure_in(circuit) +def test_pauli_measure_in_multi__measurement_per_qubit(): + n = 4 + pauli = PauliString(spec="Z" * n) + circuit = cirq.Circuit(cirq.H.on_each(cirq.LineQubit.range(n))) + + # add a measurement to qubit 0 and 1 + circuit = circuit + cirq.measure(cirq.LineQubit(0), cirq.LineQubit(1)) + with pytest.raises(ValueError, match="More than one measurement"): + pauli.measure_in(circuit) + + def test_can_be_measured_with_single_qubit(): pauli = PauliString(spec="Z")