# Copyright (C) Unitary Fund
#
# This source code is licensed under the GPL license (v3) found in the
# LICENSE file in the root directory of this source tree.
"""Functions to calculate parameters for depolarizing noise and biased noise
models via a learning-based technique."""
from typing import Any, Dict, List, Optional, Tuple
import numpy as np
import numpy.typing as npt
from scipy.optimize import minimize
from mitiq import QPROGRAM, Executor, Observable
from mitiq.cdr import generate_training_circuits
from mitiq.pec import execute_with_pec
from mitiq.pec.representations.biased_noise import (
represent_operation_with_local_biased_noise,
)
from mitiq.pec.representations.depolarizing import (
represent_operation_with_local_depolarizing_noise,
)
[docs]
def learn_biased_noise_parameters(
operations_to_learn: List[QPROGRAM],
circuit: QPROGRAM,
ideal_executor: Executor,
noisy_executor: Executor,
pec_kwargs: Dict[str, Any] = {},
num_training_circuits: int = 5,
fraction_non_clifford: float = 0.2,
training_random_state: Optional[np.random.RandomState] = None,
epsilon0: float = 0.05,
eta0: float = 1,
observable: Optional[Observable] = None,
**learning_kwargs: Dict["str", Any],
) -> Tuple[bool, float, float]:
r"""This function learns the biased noise parameters epsilon and eta
associated to a set of input operations. The learning process is based on
the execution of a set of training circuits on a noisy backend and on a
classical simulator. The training circuits are near-Clifford approximations
of the input circuit. A biased noise model characterization is assumed.
Args:
operations_to_learn: The ideal operations to learn the noise model of.
circuit: The full quantum program as defined by the user.
ideal_executor: Simulates a circuit and returns
a noiseless ``QuantumResult``.
noisy_executor: Executes a circuit on a noisy backend
and returns a ``QuantumResult``.
pec_kwargs (optional): Options to pass to ``execute_w_pec`` for the
error-mitigated expectation value obtained from executing
the training circuits.
num_training_circuits: Number of near-Clifford circuits to be
generated for training.
fraction_non_clifford: The (approximate) fraction of non-Clifford
gates in each training circuit.
training_random_state: Seed for sampling the training circuits.
epsilon0: Initial guess for noise strength.
eta0: Initial guess for noise bias.
observable (optional): Observable to compute the expectation value of.
If None, the ``executor`` must return an expectation value.
Otherwise the `QuantumResult` returned by `executor` is used to
compute the expectation of the observable.
learning_kwargs (optional): Additional data and options including
``method`` an optimization method supported by
``scipy.optimize.minimize`` and settings for the chosen
optimization method.
Returns:
A 3-tuple containing a flag indicating whether or not the optimizer
exited successfully, the optimized noise strength epsilon, and the
optimized noise bias, eta.
.. note:: Using this function may require some tuning. One of the main
challenges is setting a good value of ``num_samples`` in the PEC
options ``pec_kwargs``. Setting a small value of ``num_samples`` is
typically necessary to obtain a reasonable execution time. On the other
hand, using a number of PEC samples that is too small can result in a
large statistical error, ultimately causing the optimization process to
fail.
"""
training_circuits = generate_training_circuits(
circuit=circuit,
num_training_circuits=num_training_circuits,
fraction_non_clifford=fraction_non_clifford,
random_state=training_random_state,
)
ideal_values = np.array(
ideal_executor.evaluate(training_circuits, observable)
)
pec_data, method, minimize_kwargs = _parse_learning_kwargs(
learning_kwargs=learning_kwargs
)
result = minimize(
biased_noise_loss_function,
[epsilon0, eta0],
args=(
operations_to_learn,
training_circuits,
ideal_values,
noisy_executor,
pec_kwargs,
pec_data,
observable,
),
method=method,
**minimize_kwargs,
)
success = result.success
epsilon_opt, eta_opt = result.x
return success, epsilon_opt, eta_opt
[docs]
def learn_depolarizing_noise_parameter(
operations_to_learn: List[QPROGRAM],
circuit: QPROGRAM,
ideal_executor: Executor,
noisy_executor: Executor,
pec_kwargs: Dict[str, Any] = {},
num_training_circuits: int = 5,
fraction_non_clifford: float = 0.2,
training_random_state: Optional[np.random.RandomState] = None,
epsilon0: float = 0.05,
observable: Optional[Observable] = None,
**learning_kwargs: Dict["str", Any],
) -> Tuple[bool, float]:
r"""This function learns the depolarizing noise parameter (epsilon)
associated to a set of input operations. The learning process is based on
the execution of a set of training circuits on a noisy backend and on a
classical simulator. The training circuits are near-Clifford approximations
of the input circuit. A depolarizing noise model characterization is
assumed.
Args:
operations_to_learn: The ideal operations to learn the noise model of.
circuit: The full quantum program as defined by the user.
ideal_executor: Simulates a circuit and returns
a noiseless ``QuantumResult``.
noisy_executor: Executes a circuit on a noisy backend
and returns a ``QuantumResult``.
pec_kwargs (optional): Options to pass to ``execute_w_pec`` for the
error-mitigated expectation value obtained from executing
the training circuits.
num_training_circuits: Number of near-Clifford circuits to be
generated for training.
fraction_non_clifford: The (approximate) fraction of non-Clifford
gates in each training circuit.
training_random_state: Seed for sampling the training circuits.
epsilon0: Initial guess for noise strength.
observable (optional): Observable to compute the expectation value of.
If None, the ``executor`` must return an expectation value.
Otherwise the `QuantumResult` returned by `executor` is used to
compute the expectation of the observable.
learning_kwargs (optional): Additional data and options including
``method`` an optimization method supported by
``scipy.optimize.minimize`` and settings for the chosen
optimization method.
Returns:
A 2-tuple containing flag indicating whether or not the optimizer
exited successfully and the optimized noise strength epsilon.
.. note:: Using this function may require some tuning. One of the main
challenges is setting a good value of ``num_samples`` in the PEC
options ``pec_kwargs``. Setting a small value of ``num_samples`` is
typically necessary to obtain a reasonable execution time. On the other
hand, using a number of PEC samples that is too small can result in a
large statistical error, ultimately causing the optimization process to
fail.
"""
training_circuits = generate_training_circuits(
circuit=circuit,
num_training_circuits=num_training_circuits,
fraction_non_clifford=fraction_non_clifford,
random_state=training_random_state,
)
ideal_values = np.array(
ideal_executor.evaluate(training_circuits, observable)
)
pec_data, method, minimize_kwargs = _parse_learning_kwargs(
learning_kwargs=learning_kwargs
)
result = minimize(
depolarizing_noise_loss_function,
epsilon0,
args=(
operations_to_learn,
training_circuits,
ideal_values,
noisy_executor,
pec_kwargs,
pec_data,
observable,
),
method=method,
**minimize_kwargs,
)
success = result.success
epsilon_opt = result.x[0]
return success, epsilon_opt
[docs]
def depolarizing_noise_loss_function(
epsilon: npt.NDArray[np.float64],
operations_to_mitigate: List[QPROGRAM],
training_circuits: List[QPROGRAM],
ideal_values: npt.NDArray[np.float64],
noisy_executor: Executor,
pec_kwargs: Dict[str, Any],
pec_data: Optional[npt.NDArray[np.float64]] = None,
observable: Optional[Observable] = None,
) -> float:
r"""Loss function for optimizing quasi-probability representations
assuming a depolarizing noise model depending on one real parameter.
Args:
epsilon: Array of optimization parameters epsilon
(local noise strength) and eta (noise bias between reduced
dephasing and depolarizing channels).
operations_to_mitigate: List of ideal operations to be represented by
a combination of noisy operations.
training_circuits: List of training circuits for generating the
error-mitigated expectation values.
ideal_values: Expectation values obtained by noiseless simulations.
noisy_executor: Executes the circuit with noise and returns a
`QuantumResult`.
pec_kwargs: Options to pass to `execute_w_pec` for the error-mitigated
expectation value obtained from executing the training circuits.
pec_data (optional): 2-D array of error-mitigated expection values for
model training.
observable (optional): Observable to compute the expectation value of.
If None, the ``executor`` must return an expectation value.
Otherwise the ``QuantumResult`` returned by ``executor`` is used to
compute the expectation of the observable.
Returns: Mean squared error between the error-mitigated values and
the ideal values, over the training set.
"""
if pec_data is not None:
ind = np.abs(pec_data[:, 0] - epsilon).argmin()
mitigated_values = pec_data[ind, 1:]
else:
representations = [
represent_operation_with_local_depolarizing_noise(
operation,
epsilon[0],
)
for operation in operations_to_mitigate
]
mitigated_values = np.array(
[
execute_with_pec(
circuit=training_circuit,
observable=observable,
executor=noisy_executor,
representations=representations,
full_output=False,
**pec_kwargs,
)
for training_circuit in training_circuits
]
)
return np.mean((mitigated_values - ideal_values) ** 2)
[docs]
def biased_noise_loss_function(
params: npt.NDArray[np.float64],
operations_to_mitigate: List[QPROGRAM],
training_circuits: List[QPROGRAM],
ideal_values: npt.NDArray[np.float64],
noisy_executor: Executor,
pec_kwargs: Dict[str, Any],
pec_data: Optional[npt.NDArray[np.float64]] = None,
observable: Optional[Observable] = None,
) -> float:
r"""Loss function for optimizing quasi-probability representations
assuming a biased noise model depending on two real parameters.
Args:
params: Array of optimization parameters epsilon
(local noise strength) and eta (noise bias between reduced
dephasing and depolarizing channels).
operations_to_mitigate: List of ideal operations to be represented by
a combination of noisy operations.
training_circuits: List of training circuits for generating the
error-mitigated expectation values.
ideal_values: Expectation values obtained by noiseless simulations.
noisy_executor: Executes the circuit with noise and returns a
`QuantumResult`.
pec_kwargs: Options to pass to `execute_w_pec` for the error-mitigated
expectation value obtained from executing the training circuits.
pec_data (optional): 3-D array of error-mitigated expection values for
model training.
observable (optional): Observable to compute the expectation value of.
If None, the ``executor`` must return an expectation value.
Otherwise the ``QuantumResult`` returned by ``executor`` is used to
compute the expectation of the observable.
Returns: Mean squared error between the error-mitigated values and
the ideal values, over the training set.
"""
epsilon = params[0]
eta = params[1]
if pec_data is not None:
ind_eps = np.abs(pec_data[:, 0, 0] - epsilon).argmin()
ind_eta = np.abs(pec_data[0, :, 0] - eta).argmin()
mitigated_values = pec_data[ind_eps, ind_eta, :]
else:
representations = [
represent_operation_with_local_biased_noise(
operation,
epsilon,
eta,
)
for operation in operations_to_mitigate
]
mitigated_values = np.array(
[
execute_with_pec(
circuit=training_circuit,
observable=observable,
executor=noisy_executor,
representations=representations,
full_output=False,
**pec_kwargs,
)
for training_circuit in training_circuits
]
)
return np.mean((mitigated_values - ideal_values) ** 2)
def _parse_learning_kwargs(
learning_kwargs: Dict[str, Any],
) -> Tuple[npt.NDArray[np.float64], str, Dict[str, Any]]:
r"""Function for handling additional options and data for the learning
functions.
Args:
learning_kwargs: Additional data and options including
``pec_data`` from pre-executed runs of PEC on training circuits,
``method`` an optimization method supported by
``scipy.optimize.minimize``, and settings for the chosen
optimization method.
Returns:
Values contained in learning_kwargs or defaults if not specified.
"""
minimize_kwargs = learning_kwargs.get("learning_kwargs")
if minimize_kwargs is None:
minimize_kwargs = {}
pec_data = minimize_kwargs.pop("pec_data", None)
method = minimize_kwargs.pop("method", "Nelder-Mead")
return pec_data, method, minimize_kwargs