Source code for mitiq.pec.pec

# Copyright (C) 2020 Unitary Fund
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program.  If not, see <https://www.gnu.org/licenses/>.

"""High-level probabilistic error cancellation tools."""

from typing import (
    Optional,
    Callable,
    Union,
    Sequence,
    Tuple,
    Dict,
    Any,
    cast,
)
from functools import wraps
import warnings
import numpy as np

from cirq import Circuit as CirqCircuit

from mitiq import Executor, Observable, QPROGRAM, QuantumResult
from mitiq.pec import sample_circuit, OperationRepresentation
from mitiq.interface import convert_to_mitiq, convert_from_mitiq


[docs]class LargeSampleWarning(Warning): """Warning is raised when PEC sample size is greater than 10 ** 5 """ pass
_LARGE_SAMPLE_WARN = ( "The number of PEC samples is very large. It may take several minutes." " It may be necessary to reduce 'precision' or 'num_samples'." )
[docs]def execute_with_pec( circuit: QPROGRAM, executor: Union[Executor, Callable[[QPROGRAM], QuantumResult]], observable: Optional[Observable] = None, *, representations: Sequence[OperationRepresentation], precision: float = 0.03, num_samples: Optional[int] = None, force_run_all: bool = True, random_state: Optional[Union[int, np.random.RandomState]] = None, full_output: bool = False, ) -> Union[float, Tuple[float, Dict[str, Any]]]: r"""Evaluates the expectation value associated to the input circuit using probabilistic error cancellation (PEC) [Temme2017]_ [Endo2018]_. This function implements PEC by: 1. Sampling different implementable circuits from the quasi-probability representation of the input circuit; 2. Evaluating the noisy expectation values associated to the sampled circuits (through the "executor" function provided by the user); 3. Estimating the ideal expectation value from a suitable linear combination of the noisy ones. Args: circuit: The input circuit to execute with error-mitigation. executor: Executes a circuit and returns a `QuantumResult`. observable: 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. representations: Representations (basis expansions) of each operation in the input circuit. precision: The desired estimation precision (assuming the observable is bounded by 1). The number of samples is deduced according to the formula (one_norm / precision) ** 2, where 'one_norm' is related to the negativity of the quasi-probability representation [Temme2017]_. If 'num_samples' is explicitly set by the user, 'precision' is ignored and has no effect. num_samples: The number of noisy circuits to be sampled for PEC. If not given, this is deduced from the argument 'precision'. force_run_all: If True, all sampled circuits are executed regardless of uniqueness, else a minimal unique set is executed. random_state: Seed for sampling circuits. full_output: If False only the average PEC value is returned. If True a dictionary containing all PEC data is returned too. Returns: pec_value: The PEC estimate of the ideal expectation value associated to the input circuit. pec_data: A dictionary which contains all the raw data involved in the PEC process (including the PEC estimation error). The error is estimated as pec_std / sqrt(num_samples), where 'pec_std' is the standard deviation of the PEC samples, i.e., the square root of the mean squared deviation of the sampled values from 'pec_value'. This is returned only if ``full_output`` is ``True``. .. [Endo2018] : Suguru Endo, Simon C. Benjamin, Ying Li, "Practical Quantum Error Mitigation for Near-Future Applications" *Phys. Rev. **X 8**, 031027 (2018), (https://arxiv.org/abs/1712.09271). .. [Takagi2020] : Ryuji Takagi, "Optimal resource cost for error mitigation," (https://arxiv.org/abs/2006.12509). """ if isinstance(random_state, int): random_state = np.random.RandomState(random_state) if not (0 < precision <= 1): raise ValueError( "The value of 'precision' should be within the interval (0, 1]," f" but precision is {precision}." ) converted_circuit, input_type = convert_to_mitiq(circuit) # Get the 1-norm of the circuit quasi-probability representation _, _, norm = sample_circuit( converted_circuit, representations, num_samples=1, ) # Deduce the number of samples (if not given by the user) if not isinstance(num_samples, int): num_samples = int((norm / precision) ** 2) # Issue warning for very large sample size if num_samples > 10 ** 5: warnings.warn(_LARGE_SAMPLE_WARN, LargeSampleWarning) # Sample all the circuits sampled_circuits, signs, _ = sample_circuit( converted_circuit, representations, random_state=random_state, num_samples=num_samples, ) # Convert back to the original type sampled_circuits = [ convert_from_mitiq(cast(CirqCircuit, c), input_type) for c in sampled_circuits ] # Execute all sampled circuits if not isinstance(executor, Executor): executor = Executor(executor) results = executor.evaluate(sampled_circuits, observable, force_run_all) # Evaluate unbiased estimators [Temme2017] [Endo2018] [Takagi2020] unbiased_estimators = [ norm * s * val # type: ignore[operator] for s, val in zip(signs, results) ] pec_value = np.average(unbiased_estimators) if not full_output: return pec_value # Build dictionary with additional results and data pec_data: Dict[str, Any] = { "num_samples": num_samples, "precision": precision, "pec_value": pec_value, "pec_error": np.std(unbiased_estimators) / np.sqrt(num_samples), "unbiased_estimators": unbiased_estimators, "measured_expectation_values": results, "sampled_circuits": sampled_circuits, } return pec_value, pec_data
[docs]def mitigate_executor( executor: Callable[[QPROGRAM], QuantumResult], observable: Optional[Observable] = None, *, representations: Sequence[OperationRepresentation], precision: float = 0.03, num_samples: Optional[int] = None, force_run_all: bool = True, random_state: Optional[Union[int, np.random.RandomState]] = None, full_output: bool = False, ) -> Callable[[QPROGRAM], Union[float, Tuple[float, Dict[str, Any]]]]: """Returns a probabilistic error cancellation (PEC) mitigated version of the input 'executor'. The input `executor` executes a circuit with an arbitrary backend and produces an expectation value (without any error mitigation). The returned executor executes the circuit with the same backend but uses probabilistic error cancellation to produce the PEC estimate of the ideal expectation value associated to the input circuit as well as A dictionary which contains all the raw data involved in the PEC process. Args: executor: Executes a circuit and returns a `QuantumResult`. observable: 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. representations: Representations (basis expansions) of each operation in the input circuit. precision: The desired estimation precision (assuming the observable is bounded by 1). The number of samples is deduced according to the formula (one_norm / precision) ** 2, where 'one_norm' is related to the negativity of the quasi-probability representation [Temme2017]_. If 'num_samples' is explicitly set, 'precision' is ignored and has no effect. num_samples: The number of noisy circuits to be sampled for PEC. If not given, this is deduced from the argument 'precision'. force_run_all: If True, all sampled circuits are executed regardless of uniqueness, else a minimal unique set is executed. random_state: Seed for sampling circuits. full_output: If False only the average PEC value is returned. If True a dictionary containing all PEC data is returned too. """ @wraps(executor) def new_executor( circuit: QPROGRAM, ) -> Union[float, Tuple[float, Dict[str, Any]]]: return execute_with_pec( circuit, executor, observable, representations=representations, precision=precision, num_samples=num_samples, force_run_all=force_run_all, random_state=random_state, full_output=full_output, ) return new_executor
[docs]def pec_decorator( observable: Optional[Observable] = None, *, representations: Sequence[OperationRepresentation], precision: float = 0.03, num_samples: Optional[int] = None, force_run_all: bool = True, random_state: Optional[Union[int, np.random.RandomState]] = None, full_output: bool = False, ) -> Callable[ [Callable[[Union[QPROGRAM, Any, Any, Any]], QuantumResult]], Callable[ [Union[QPROGRAM, Any, Any, Any]], Union[float, Tuple[float, Dict[str, Any]]], ], ]: """Decorator which adds probabilistic error cancellation (PEC) mitigation to an executor function, i.e., a function which executes a quantum circuit with an arbitrary backend and returns the PEC estimate of the ideal expectation value associated to the input circuit as well as A dictionary which contains all the raw data involved in the PEC process. Args: observable: Observable to compute the expectation value of. If None, the `executor` function being decorated must return an expectation value. Otherwise, the `QuantumResult` returned by this `executor` is used to compute the expectation of the observable. representations: Representations (basis expansions) of each operation in the input circuit. precision: The desired estimation precision (assuming the observable is bounded by 1). The number of samples is deduced according to the formula (one_norm / precision) ** 2, where 'one_norm' is related to the negativity of the quasi-probability representation [Temme2017]_. If 'num_samples' is explicitly set by the user, 'precision' is ignored and has no effect. num_samples: The number of noisy circuits to be sampled for PEC. If not given, this is deduced from the argument 'precision'. force_run_all: If True, all sampled circuits are executed regardless of uniqueness, else a minimal unique set is executed. random_state: Seed for sampling circuits. full_output: If False only the average PEC value is returned. If True a dictionary containing all PEC data is returned too. """ def decorator( executor: Callable[[QPROGRAM], QuantumResult] ) -> Callable[[QPROGRAM], Union[float, Tuple[float, Dict[str, Any]]]]: return mitigate_executor( executor, observable, representations=representations, precision=precision, num_samples=num_samples, force_run_all=force_run_all, random_state=random_state, full_output=full_output, ) return decorator