Source code for QCut.execution.run

"""
Running the circuit knitting sequence end to end, waiting for the result.
"""

from __future__ import annotations

import numpy as np
from qiskit import QuantumCircuit

from QCut.errors.qcuterror import QCutError
from QCut.execution.dispatch import DEFAULT_SHOTS, SHOT_SPREAD
from QCut.execution.estimate import JobEstimate, RunEstimate, estimate_run
from QCut.execution.experiments import _check_measurement_spec, get_experiment_circuits
from QCut.execution.submit import submit_experiments
from QCut.execution.waves import MEASURE_SHARE
from QCut.job.cutresult import RawResult
from QCut.model.cutcircuit import CutCircuit, CutExperiment
from QCut.options import CutOptions
from QCut.post_process.postprocess import estimate_expectation_values
from QCut.post_process.probabilities import (
    QuasiProbabilities,
    estimate_probabilities,
)
from QCut.pre_process.circuit_preparation import get_locations_and_subcircuits
from QCut.transpilation.transpile_cut_circuits import (
    has_topology,
    transpile_subcircuits,
)
from QCut.utils.deprecation import deprecated
from QCut.utils.observables import ObservablesLike

# The old QCut.execution.circuit_knitting path resolves to this module, so the public
# names that used to live there are re-exported here too.
__all__ = [
    "run",
    "run_cut_circuit",
    "run_experiments",
    "get_experiment_circuits",
    "submit_experiments",
    "estimate_run",
    "JobEstimate",
    "RunEstimate",
    "DEFAULT_SHOTS",
    "SHOT_SPREAD",
    "MEASURE_SHARE",
]


[docs] @deprecated( "run_experiments() is planned to change in QCut 3.0: it will return a QCutJob " "without waiting, as submit_experiments() does now, and the job's result() will " "give expectation values or a distribution rather than a RawResult. The RawResult " "will stay available on the job." ) def run_experiments( cut_experiment: CutExperiment, shots: int = DEFAULT_SHOTS, backend=None, max_batch_size: int = 100, run_options: dict | None = None, ) -> RawResult: """Run experiment circuits and wait for their results. The same as :func:`submit_experiments` followed by :meth:`QCutJob.result`. The counts come back as ``[group][observable][subcircuit]``, each key ``"xxx yy"`` where ``xxx`` is the end-of-circuit measurements on the ``meas`` register and ``yy`` the QPD basis measurements on the ``qpd_meas`` register. Args: cut_experiment (CutExperiment): the experiment circuits to run shots (int): number of shots per circuit run (optional), as in :func:`submit_experiments`. backend: backend or V2 sampler used for running the circuits. max_batch_size (int): maximum number of circuits submitted per backend.run call, as in :func:`submit_experiments`. run_options (dict): passed on to every ``run`` call. Returns: RawResult: the raw counts, carrying the experiment they came from so that :func:`QCut.estimate_expectation_values` can be called on them alone """ return submit_experiments( cut_experiment, shots=shots, backend=backend, max_batch_size=max_batch_size, run_options=run_options, ).result()
[docs] @deprecated( "run_cut_circuit() is planned to change in QCut 3.0: it will return a QCutJob " "without waiting, whose result() gives the expectation values or the " "distribution. The raw counts will stay available on the job." ) def run_cut_circuit( cut_circuit: CutCircuit, observables: ObservablesLike | None = None, backend=None, max_batch_size: int = 100, options: CutOptions | None = None, shots: int = DEFAULT_SHOTS, qubits: list[int] | None = None, run_options: dict | None = None, ) -> np.ndarray | QuasiProbabilities: """After splitting the circuit run the rest of the circuit knitting sequence. Args: cut_circuit (CutCircuit): the split circuit, carrying its placeholder operations and cut locations observables: the observables to estimate, taken as qiskit's estimator takes them. See :func:`get_experiment_circuits`. backend: backend or V2 sampler to run the experiment circuits on. A device with a coupling map gets the subcircuits transpiled for it first. A sampler is not transpiled for, so it must take the circuits as built. max_batch_size (int): maximum number of circuits submitted per backend.run call (optional) options (CutOptions): configuration, overriding what the CutCircuit carries (optional) shots (int): number of shots per circuit run, as in :func:`submit_experiments` (optional) qubits (list[int]): the qubits to reconstruct a distribution over, instead of estimating observables (optional) run_options (dict): passed on to the backend or sampler, as in :func:`submit_experiments` (optional) One of observables or qubits must be provided. Returns: np.ndarray | QuasiProbabilities: one expectation value per observable, shaped like the observables given. A single observable comes back as a zero-dimensional array or, when given ``qubits``, the reconstructed distribution over them. """ _check_measurement_spec(observables, qubits) if options is not None: cut_circuit.options = options if backend is None: raise QCutError("a backend or sampler to run the circuits on is needed") if has_topology(backend): cut_circuit = transpile_subcircuits(cut_circuit, backend, optimization_level=3) cut_experiment = get_experiment_circuits(cut_circuit, observables, qubits) results = submit_experiments( cut_experiment, shots=shots, backend=backend, max_batch_size=max_batch_size, run_options=run_options, ).result() if qubits is not None: return estimate_probabilities(results) return estimate_expectation_values(results)
[docs] @deprecated( "run() is planned to change in QCut 3.0: it will return a QCutJob without " "waiting, whose result() gives the expectation values or the distribution. The " "raw counts will stay available on the job." ) def run( circuit: QuantumCircuit, observables: ObservablesLike | None = None, backend=None, max_batch_size: int = 100, options: CutOptions | None = None, shots: int = DEFAULT_SHOTS, qubits: list[int] | None = None, run_options: dict | None = None, ) -> np.ndarray | QuasiProbabilities: """Run the whole circuit knitting sequence with one function call. Args: circuit (QuantumCircuit): circuit with cut experiments observables: the observables to estimate, taken as qiskit's estimator takes them. See :func:`get_experiment_circuits`. backend: backend or V2 sampler to run the experiment circuits on. A device with a coupling map gets the subcircuits transpiled for it first. A sampler is not transpiled for, so it must take the circuits as built. max_batch_size (int): maximum number of circuits submitted per backend.run call (optional) options (CutOptions): configuration for the run (optional) shots (int): number of shots per circuit run, as in :func:`submit_experiments` (optional) qubits (list[int]): the qubits to reconstruct a distribution over, instead of estimating observables (optional) run_options (dict): passed on to the backend or sampler, as in :func:`submit_experiments` (optional) One of observables or qubits must be provided. Returns: np.ndarray | QuasiProbabilities: one expectation value per observable, shaped like the observables given.A single observable comes back as a zero-dimensional array or, when given ``qubits``, the reconstructed distribution over them. """ _check_measurement_spec(observables, qubits) # circuit = circuit.copy() cut_circuit = get_locations_and_subcircuits(circuit, options=options) return run_cut_circuit( cut_circuit, observables, backend, max_batch_size, shots=shots, qubits=qubits, run_options=run_options, )