Source code for mitiq.shadows.shadows

# 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.
"""Classical shadow estimation for quantum circuits."""

from typing import Any, Callable, Dict, List, Mapping, Optional, Tuple, Union

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

import mitiq
from mitiq import MeasurementResult
from mitiq.shadows.classical_postprocessing import (
    expectation_estimation_shadow,
    get_pauli_fidelities,
    shadow_state_reconstruction,
)
from mitiq.shadows.quantum_processing import random_pauli_measurement


[docs] def pauli_twirling_calibrate( k_calibration: int = 1, locality: Optional[int] = None, zero_state_shadow_outcomes: Optional[Tuple[List[str], List[str]]] = None, qubits: Optional[List[cirq.Qid]] = None, executor: Optional[Callable[[cirq.Circuit], MeasurementResult]] = None, num_total_measurements_calibration: Optional[int] = 20000, ) -> Dict[str, complex]: r""" This function returns the dictionary of the median of means estimation of Pauli fidelities: :math:`\{`"b": :math:`f_{b}\}_{b\in\{0,1\}^n}`. The number of :math:`f_b` is :math:`2^n`, or :math:`\sum_{i=1}^d C_n^i` if the locality :math:`d` is given. In the notation of arXiv:2011.09636, this function estimates the coefficient :math:`f_b`, which are expansion coefficients of the twirled channel :math:`\mathcal{M}=\sum_b f_b\Pi_b`. In practice, the output of this function can be used as calibration data for performing the classical shadows protocol in a way which is more robust to noise. Args: k_calibration: Number of groups of "median of means" used to solve for Pauli fidelity. locality: The locality of the operator, whose expectation value is going to be estimated by the classical shadow. e.g. if operator is Ising model Hamiltonian with nearest neighbor interaction, then locality = 2. zero_state_shadow_outcomes: The output of function :func:`shadow_quantum_processing` of zero calibrate state. qubits: The qubits to measure, needs to specify when the `zero_state_shadow_outcomes` is None. executor: The function to use to do quantum measurement, must be same as executor in `shadow_quantum_processing`. Needs to specify when the `zero_state_shadow_outcomes` is None. num_total_measurements_calibration: Number of shots per group of "median of means" used for calibration. Needs to specify when the `zero_state_shadow_outcomes` is None. Returns: A dictionary containing the calibration outcomes. """ if zero_state_shadow_outcomes is None: if qubits is None: raise TypeError( "qubits must be specified when" "zero_state_shadow_outcomes is None." ) if executor is None: raise TypeError( "executor must be specified when" "zero_state_shadow_outcomes is None." ) if num_total_measurements_calibration is None: raise TypeError( "num_total_measurements_calibration must be" "specified when zero_state_shadow_outcomes is None." ) # calibration circuit is of same qubit number with original circuit zero_circuit = cirq.Circuit() # perform random Pauli measurement one the calibration circuit calibration_measurement_outcomes = random_pauli_measurement( zero_circuit, n_total_measurements=num_total_measurements_calibration, executor=executor, qubits=qubits, ) else: calibration_measurement_outcomes = zero_state_shadow_outcomes # get the median of means estimation of Pauli fidelities return get_pauli_fidelities( calibration_measurement_outcomes, k_calibration, locality=locality )
[docs] def shadow_quantum_processing( circuit: cirq.Circuit, executor: Callable[[cirq.Circuit], MeasurementResult], num_total_measurements_shadow: int, random_seed: Optional[int] = None, qubits: Optional[List[cirq.Qid]] = None, ) -> Tuple[List[str], List[str]]: r""" This function returns the bit strings and Pauli strings corresponding to the executor measurement outcomes for a given circuit, rotated by unitaries randomly sampled from a fixed unitary ensemble :math:`\mathcal{G}`. In the current implementation, the unitaries are sampled from the local Clifford group for :math:`n` qubits, i.e., :math:`\mathcal{G} = \mathrm{Cl}_2^n`. In practice, the output of this function provides the raw experimental data necessary to perform the classical shadows protocol. Args: circuit: The circuit to execute. executor: The function to use to do quantum measurement, must be same as executor in `pauli_twirling_calibrate`. num_total_measurements_shadow: Total number of shots for shadow estimation. random_seed: The random seed to use for the shadow measurements. qubits: The qubits to measure. Returns: A dictionary containing the bit strings, the Pauli strings `bit_strings`: Circuit qubits computational basis e.g. "01..":math:`:=|0\rangle|1\rangle..`. `pauli_strings`: The local Pauli measurement performed on each qubit. e.g."XY.." means perform local X-basis measurement on the 1st qubit, local Y-basis measurement the 2ed qubit in the circuit, etc. """ if random_seed is not None: np.random.seed(random_seed) r""" Additional information: Shadow stage 1: Sample random unitary form :math:`\mathcal{g}\subset \mathrm{U}(2^n)` and perform computational basis measurement. In the current state, we have implemented local Pauli measurement, i.e. :math:`\mathcal{g} = \mathrm{Cl}_2^n`. """ # random Pauli measurement on the circuit output = random_pauli_measurement( circuit, n_total_measurements=num_total_measurements_shadow, executor=executor, qubits=qubits, ) return output
[docs] def classical_post_processing( shadow_outcomes: Tuple[List[str], List[str]], calibration_results: Optional[Dict[str, float]] = None, observables: Optional[List[mitiq.PauliString]] = None, k_shadows: Optional[int] = None, state_reconstruction: Optional[bool] = False, ) -> Mapping[str, Union[float, NDArray[Any]]]: r""" Executes a circuit with classical shadows. This function can be used for state reconstruction or expectation value estimation of observables. Args: shadow_outcomes: The output of function `shadow_quantum_processing`. calibration_results: The output of function `pauli_twirling_calibrate`. observables: The set of observables to measure. k_shadows: Number of groups of "median of means" used for shadow estimation of expectation values. state_reconstruction: Whether to reconstruct the state or estimate the expectation value of the observables. Returns: TODO: rewrite this. If `state_reconstruction` is True: state tomography matrix in :math:`\mathbb{M}_{2^n}(\mathbb{C})` if use_calibration is False, otherwise state tomography vector in :math:`\mathbb{C}^{4^d}`. If observables is given: estimated expectation values of observables. """ """ Additional information: Shadow stage 2: Estimate the expectation value of the observables OR reconstruct the state """ output: Dict[str, Union[float, NDArray[Any]]] = {} if state_reconstruction: reconstructed_state = shadow_state_reconstruction( shadow_outcomes, fidelities=calibration_results ) output["reconstructed_state"] = reconstructed_state # type: ignore elif observables is not None: if k_shadows is None: k_shadows = 1 for obs in observables: expectation_values = expectation_estimation_shadow( shadow_outcomes, obs, num_batches=k_shadows, fidelities=calibration_results, ) output[str(obs)] = expectation_values return output