Source code for qiskit_experiments.library.quantum_volume.qv_experiment

# This code is part of Qiskit.
#
# (C) Copyright IBM 2021.
#
# This code is licensed under the Apache License, Version 2.0. You may
# obtain a copy of this license in the LICENSE.txt file in the root directory
# of this source tree or at http://www.apache.org/licenses/LICENSE-2.0.
#
# Any modifications or derivative works of this code must retain this
# copyright notice, and modified files need to carry a notice indicating
# that they have been altered from the originals.
"""
Quantum Volume Experiment class.
"""

from typing import Union, Sequence, Optional, List
from numpy.random import Generator, default_rng
from numpy.random.bit_generator import BitGenerator, SeedSequence

from qiskit.utils.optionals import HAS_AER

from qiskit import QuantumCircuit
from qiskit.circuit.library import QuantumVolume as QuantumVolumeCircuit
from qiskit import transpile
from qiskit.providers.backend import Backend
from qiskit_experiments.framework import BaseExperiment, Options
from .qv_analysis import QuantumVolumeAnalysis


[docs] class QuantumVolume(BaseExperiment): """An experiment to measure the largest random square circuit that can be run on a processor. # section: overview Quantum Volume (QV) is a single-number metric that can be measured using a concrete protocol on near-term quantum computers of modest size. The QV method quantifies the largest random circuit of equal width and depth that the computer successfully implements. Quantum computing systems with high-fidelity operations, high connectivity, large calibrated gate sets, and circuit rewriting toolchains are expected to have higher quantum volumes. The Quantum Volume is determined by the largest circuit depth :math:`d_{max}`, and equals to :math:`2^{d_{max}}`. See the `Qiskit Textbook <https://github.com/Qiskit/textbook/blob/main/notebooks/quantum-hardware/measuring-quantum-volume.ipynb>`_ for an explanation on the QV protocol. In the QV experiment we generate :class:`~qiskit.circuit.library.QuantumVolume` circuits on :math:`d` qubits, which contain :math:`d` layers, where each layer consists of random 2-qubit unitary gates from :math:`SU(4)`, followed by a random permutation on the :math:`d` qubits. Then these circuits run on the quantum backend and on an ideal simulator (either :class:`~qiskit_aer.AerSimulator` or :class:`~qiskit.quantum_info.Statevector`). A depth :math:`d` QV circuit is successful if it has `mean heavy-output probability` > 2/3 with confidence level > 0.977 (corresponding to z_value = 2), and at least 100 trials have been ran. See :class:`QuantumVolumeAnalysis` documentation for additional information on QV experiment analysis. # section: analysis_ref :class:`QuantumVolumeAnalysis` # section: manual :doc:`/manuals/verification/quantum_volume` # section: reference .. ref_arxiv:: 1 1811.12926 .. ref_arxiv:: 2 2008.08571 # section: example .. jupyter-execute:: :hide-code: # backend from qiskit_aer import AerSimulator from qiskit_ibm_runtime.fake_provider import FakeSydneyV2 backend = AerSimulator.from_backend(FakeSydneyV2()) .. jupyter-execute:: from qiskit_experiments.framework import BatchExperiment from qiskit_experiments.library import QuantumVolume qubits = tuple(range(4)) # Can use specific qubits. for example [2, 4, 7, 10] qv_exp = QuantumVolume(qubits, seed=42) qv_exp.set_transpile_options(optimization_level=3) qv_exp.set_run_options(shots=1000) expdata = qv_exp.run(backend=backend).block_for_results() display(expdata.figure(0)) for result in expdata.analysis_results(): print(result) """ def __init__( self, physical_qubits: Sequence[int], backend: Optional[Backend] = None, trials: Optional[int] = 100, seed: Optional[Union[int, SeedSequence, BitGenerator, Generator]] = None, simulation_backend: Optional[Backend] = None, ): """Initialize a quantum volume experiment. Args: physical_qubits: list of physical qubits for the experiment. backend: Optional, the backend to run the experiment on. trials: The number of trials to run the quantum volume circuit. seed: Optional, seed used to initialize ``numpy.random.default_rng`` when generating circuits. The ``default_rng`` will be initialized with this seed value every time :meth:`circuits` is called. simulation_backend: The simulator backend to use to generate the expected results. the simulator must have a 'save_probabilities' method. If None, the :class:`qiskit_aer.AerSimulator` simulator will be used (in case :external+qiskit_aer:doc:`qiskit-aer <index>` is not installed, :class:`qiskit.quantum_info.Statevector` will be used). """ super().__init__(physical_qubits, analysis=QuantumVolumeAnalysis(), backend=backend) # Set configurable options self.set_experiment_options(trials=trials, seed=seed) if not simulation_backend and HAS_AER: from qiskit_aer import AerSimulator self._simulation_backend = AerSimulator() else: self._simulation_backend = simulation_backend @classmethod def _default_experiment_options(cls) -> Options: """Default experiment options. Experiment Options: trials (int): Optional, number of times to generate new Quantum Volume circuits and calculate their heavy output. seed (None or int or SeedSequence or BitGenerator or Generator): A seed used to initialize ``numpy.random.default_rng`` when generating circuits. The ``default_rng`` will be initialized with this seed value every time :meth:`circuits` is called. """ options = super()._default_experiment_options() options.trials = 100 options.seed = None return options def _get_ideal_data(self, circuit: QuantumCircuit, **run_options) -> List[float]: """Return ideal measurement probabilities. In case the user does not have Aer installed, use Qiskit's quantum info module to calculate the ideal state. Args: circuit: the circuit to extract the ideal data from run_options: backend run options. Returns: list: list of the probabilities for each state in the circuit. """ ideal_circuit = circuit.remove_final_measurements(inplace=False) if self._simulation_backend: ideal_circuit.save_probabilities() # always transpile with optimization_level 0, even if the non ideal circuits will run # with different optimization level, because we need to compare the results to the # exact generated probabilities ideal_circuit = transpile(ideal_circuit, self._simulation_backend, optimization_level=0) ideal_result = self._simulation_backend.run(ideal_circuit, **run_options).result() probabilities = ideal_result.data().get("probabilities") else: from qiskit.quantum_info import Statevector state_vector = Statevector(ideal_circuit) probabilities = state_vector.probabilities() return list(probabilities)
[docs] def circuits(self) -> List[QuantumCircuit]: """Return a list of Quantum Volume circuits. Returns: A list of :class:`QuantumCircuit`. """ rng = default_rng(seed=self.experiment_options.seed) circuits = [] depth = self._num_qubits # Note: the trials numbering in the metadata is starting from 1 for each new experiment run for trial in range(1, self.experiment_options.trials + 1): qv_circ = QuantumVolumeCircuit(depth, depth, seed=rng) qv_circ.measure_active() qv_circ.metadata = { "depth": depth, "trial": trial, "ideal_probabilities": self._get_ideal_data(qv_circ), } circuits.append(qv_circ) return circuits