supermarq.qcvv ============== .. py:module:: supermarq.qcvv .. autoapi-nested-parse:: A toolkit of QCVV routines. Submodules ---------- .. toctree:: :maxdepth: 1 /autoapi/supermarq/qcvv/base_experiment/index /autoapi/supermarq/qcvv/irb/index /autoapi/supermarq/qcvv/xeb/index Classes ------- .. autoapisummary:: supermarq.qcvv.BenchmarkingExperiment supermarq.qcvv.BenchmarkingResults supermarq.qcvv.IRB supermarq.qcvv.IRBResults supermarq.qcvv.Sample supermarq.qcvv.XEB supermarq.qcvv.XEBResults supermarq.qcvv.XEBSample Package Contents ---------------- .. py:class:: BenchmarkingExperiment(num_qubits: int, *, random_seed: int | numpy.random.Generator | None = None, **kwargs: Any) Bases: :py:obj:`abc.ABC`, :py:obj:`Generic`\ [\ :py:obj:`ResultsT`\ ] Base class for gate benchmarking experiments. The interface for implementing these experiments is as follows: #. First instantiate the desired experiment object .. code:: experiment = ExampleExperiment(<>) #. Prepare the circuits and run the experiment on the desired target. This can either be a custom simulator or a real device name. For example: .. code:: noise_model = cirq.depolarize(p=0.01, n_qubits=1) sim = cirq.DensityMatrixSimulator(noise=noise_model) experiment.prepare_experiment(<>) experiment.run_with_simulator(simulator=sim, <>) #. Then we analyse the results. If the target was a local simulator this will be available as soon as the :code:`run_with_simulator()` method has finished executing. On the other hand if a real device was accessed via Superstaq then it may take time for the data to be available from the server. The :code:`collect_data()` will return :code:`True` when all data has been collected and is ready to be analysed. .. code:: if experiment.collect_data(): results = experiment.analyze_results(<>) #. The final results of the experiment will be stored in the :code:`results` attribute as a :class:`BenchmarkingResults` of values, while all the data from the experiment will be stored in the :code:`raw_data` attribute as a :class:`~pandas.DataFrame`. Some experiments may include additional data attributes for data generated during the analysis. .. code:: results = experiment.results data = experiment.raw_data Additionally it is possible to pre-compile the experimental circuits for a given device using .. code:: experiment.prepare_experiment(<>) experiment.compile_circuits(target=<>) And then to run the experiment using a custom callable function for evaluating the circuits. For example this could be a function that uses a connection to a local device. .. code:: experiment.run_with_callable(<>) When implementing a new experiment, 4 methods need to be implemented: #. :meth:`_build_circuits`: Given a number of circuits and an iterable of the different numbers of layers to use, return a list of :class:`Sample` objects that need to be sampled during the experiment. #. :meth:`_process_probabilities`: Take the probability distribution over the computational basis resulting from running each circuit and combine the relevant details into a :class:`pandas.DataFrame`. #. :meth:`analyze_results`: Analyse the data in the :attr:`raw_data` dataframe and return a :class:`BenchmarkingResults` object containing the results of the experiment. #. :meth:`plot_results`: Produce any relevant plots that are useful for understanding the results of the experiment. Additionally the :class:`BenchmarkingResults` dataclass needs subclassing to hold the specific results of the new experiment. .. py:method:: analyze_results(plot_results: bool = True) -> ResultsT :abstractmethod: Perform the experiment analysis and store the results in the `results` attribute. :param plot_results: Whether to generate plots of the results. Defaults to False. :returns: A named tuple of the final results from the experiment. .. py:method:: collect_data(force: bool = False) -> bool Collect the data from the samples and process it into the :attr:`raw_data` attribute. If the data is successfully stored in the :attr:`raw_data` attribute then the function will return :code:`True`. If either not all jobs submitted to the server have completed, or not all samples have probability results then no data will be saved in :attr:`raw_data` and the function will return :code:`False`. This check can be overridden with :code:`force=True` in which case only the samples which have probability results will be used to generate the results dataframe. :param force: Whether to override the check that all data is present. Defaults to False. :raises RuntimeError: If :code:`force=True` but there are no samples with any data. :raises RuntimeError: If the experiment has not yet been run. :returns: Whether the results dataframe has been successfully created. .. py:method:: compile_circuits(target: str, **kwargs: Any) -> None Compiles the experiment circuits for the given device. Useful if the samples are not going to be run via Superstaq. :param target: The device to compile to. :param kwargs: Additional desired compile options. .. py:method:: plot_results() -> None :abstractmethod: Plot the results of the experiment .. py:method:: prepare_experiment(num_circuits: int, cycle_depths: collections.abc.Iterable[int], overwrite: bool = False) -> None Prepares the circuits needed for the experiment :param num_circuits: Number of circuits to run. :param cycle_depths: An iterable of the different layer depths to use during the experiment. :param overwrite: Whether to force an experiment run even if there is existing data that would be over written in the process. Defaults to False. :raises RuntimeError: If the experiment has already been run once and the `overwrite` argument is not True :raises ValueError: If any of the cycle depths provided negative or zero. .. py:method:: run_on_device(target: str, repetitions: int = 10000, method: str | None = None, overwrite: bool = False, **target_options: Any) -> cirq_superstaq.Job Submit the circuit samples to the desired target device and store the resulting probabilities. The set of circuits is partitioned as necessary to not exceed the maximum circuits that can be submitted to the given target device. The function then waits for the jobs to complete before saving the resulting probability distributions. :param target: The name of a Superstaq target. :param repetitions: The number of shots to sample. Defaults to 10,000. :param method: Optional method to use on the Superstaq device. Defaults to None corresponding to normal running. :param target_options: Optional configuration dictionary passed when submitting the job. :param overwrite: Whether to force an experiment run even if there is existing data that would be over written in the process. Defaults to False. :returns: The superstaq job containing all the circuits submitted as part of the experiment. .. py:method:: run_with_callable(circuit_eval_func: collections.abc.Callable[[cirq.Circuit], dict[str, float]], overwrite: bool = False, **kwargs: Any) -> None Evaluates the probabilities for each circuit using a user provided callable function. This function should take a circuit as input and return a dictionary of probabilities for each bitstring (including states with zero probability). :param circuit_eval_func: The custom function to use when evaluating circuit probabilities. :param overwrite: Whether to force an experiment run even if there is existing data that would be over written in the process. Defaults to False. :param kwargs: Additional arguments to pass to the custom function. :raises RuntimeError: If the returned probabilities dictionary keys is missing include an incorrect number of bits. :raises RuntimeError: If the returned probabilities dictionary values do not sum to 1.0. .. py:method:: run_with_simulator(simulator: cirq.Sampler | None = None, repetitions: int = 10000, overwrite: bool = False) -> None Use the local simulator to sample the circuits and store the resulting probabilities. :param simulator: A local :class:`~cirq.Sampler` to use. If None then the default :class:`cirq.Simulator` simulator is used. Defaults to None. :param repetitions: The number of shots to sample. Defaults to 10,000. :param overwrite: Whether to force an experiment run even if there is existing data that would be over written in the process. Defaults to False. .. py:property:: num_qubits :type: int Returns: The number of qubits used in the experiment .. py:attribute:: qubits The qubits used in the experiment. .. py:property:: raw_data :type: pandas.DataFrame The data from the most recently run experiment. :raises RuntimeError: If no results are available. .. py:property:: results :type: ResultsT The results from the most recently run experiment. :raises RuntimeError: If no results are available. .. py:property:: samples :type: collections.abc.Sequence[Sample] The samples generated during the experiment. :raises RuntimeError: If no samples are available. .. py:property:: targets :type: frozenset[str] Returns: A set of the unique target that each sample was submitted to .. py:class:: BenchmarkingResults Bases: :py:obj:`abc.ABC` A dataclass for storing the results of the experiment. Requires subclassing for each new experiment type. .. py:attribute:: experiment_name :type: str The name of the experiment. .. py:attribute:: target :type: str The target device that was used. .. py:attribute:: total_circuits :type: int The total number of circuits used in the experiment. .. py:class:: IRB(interleaved_gate: cirq.Gate | None = cirq.Z, num_qubits: int | None = 1, clifford_op_gateset: cirq.CompilationTargetGateset = cirq.CZTargetGateset(), *, random_seed: int | numpy.random.Generator | None = None) Bases: :py:obj:`supermarq.qcvv.base_experiment.BenchmarkingExperiment`\ [\ :py:obj:`Union`\ [\ :py:obj:`IRBResults`\ , :py:obj:`RBResults`\ ]\ ] Interleaved random benchmarking (IRB) experiment. IRB estimates the gate error of specified Clifford gate, :math:`\mathcal{C}^*`. This is achieved by first choosing a random sequence, :math:`\{\mathcal{C_i}\}_m` of :math:`m` Clifford gates and then using this to generate two circuits. The first is generated by appending to this sequence the single gate that corresponds to the inverse of the original sequence. The second circuit it obtained by inserting the interleaving gate, :math:`\mathcal{C}^*` after each gate in the sequence and then again appending the corresponding inverse element of the new circuit. Thus both circuits correspond to the identity operation. We run both circuits on the specified target and calculate the probability of measuring the resulting state in the ground state, :math:`p(0...0)`. This gives the circuit fidelity .. math:: f(m) = 2p(0...0) - 1 We can then fit an exponential decay :math:`\log(f) \sim m` to this circuit fidelity for each circuit, with decay rates :math:`\alpha` and :math:`\tilde{\alpha}` for the circuit without and with interleaving respectively. Finally the gate error of the specified gate, :math:`\mathcal{C}^*` is estimated as .. math:: e_{\mathcal{C}^*} = \frac{1}{2} \left(1 - \frac{\tilde{\alpha}}{\alpha}\right) For more details see: https://arxiv.org/abs/1203.4550 .. py:method:: analyze_results(plot_results: bool = True) -> IRBResults | RBResults Analyse the experiment results and estimate the interleaved gate error. :param plot_results: Whether to generate plots of the results. Defaults to False. :returns: A named tuple of the final results from the experiment. .. py:method:: exp_decay(x: numpy.typing.NDArray[numpy.float_], A: float, alpha: float, B: float) -> numpy.typing.NDArray[numpy.float_] :staticmethod: Exponential decay of the form .. math:: A \alpha^x + B :param x: x :param A: Decay constant :param alpha: Decay coefficient :param B: Additive constant :returns: Exponential decay applied to x. .. py:method:: gates_per_clifford(samples: int = 500) -> dict[str, float] Samples a number of random Clifford operations and calculates the average number of single and two qubit gates used to implement them. Note this depends on the gateset chosen for the experiment. :param samples: Number of samples to use. Defaults to 500. :returns: A dictionary with the average number of one and two qubit gates used. .. py:method:: plot_results() -> None Plot the exponential decay of the circuit fidelity with cycle depth. .. py:method:: random_clifford() -> cirq.CliffordGate Returns: A random clifford gate with the correct number of qubits for the current experiment. .. py:method:: random_single_qubit_clifford() -> cirq.SingleQubitCliffordGate Choose a random single qubit clifford gate. :returns: The random clifford gate. .. py:method:: random_two_qubit_clifford() -> cirq.CliffordGate Choose a random two qubit clifford gate. For algorithm details see https://arxiv.org/pdf/1402.4848 & https://arxiv.org/pdf/1210.7011. :returns: The random clifford gate. .. py:attribute:: clifford_op_gateset The gateset to use when implementing Clifford operations. .. py:class:: IRBResults Bases: :py:obj:`supermarq.qcvv.base_experiment.BenchmarkingResults` Data structure for the IRB experiment results. .. py:attribute:: average_interleaved_gate_error :type: float | None Estimate of the interleaving gate error. .. py:attribute:: average_interleaved_gate_error_std :type: float | None Standard deviation of the estimate for the interleaving gate error. .. py:attribute:: experiment_name :value: 'IRB' The name of the experiment. .. py:attribute:: irb_decay_coefficient :type: float | None Decay coefficient estimate with the interleaving gate. .. py:attribute:: irb_decay_coefficient_std :type: float | None Standard deviation of the decay coefficient estimate with the interleaving gate. .. py:attribute:: rb_decay_coefficient :type: float Decay coefficient estimate without the interleaving gate. .. py:attribute:: rb_decay_coefficient_std :type: float Standard deviation of the decay coefficient estimate without the interleaving gate. .. py:class:: Sample A sample circuit to use along with any data about the circuit that is needed for analysis .. py:property:: circuit :type: cirq.Circuit Returns: The circuit used for the experiment. Defaults to the compiled circuit if available and if not returns the raw circuit. .. py:attribute:: compiled_circuit :type: cirq.Circuit | None :value: None The compiled circuit. Only used if the circuits are compiled for a specific target. .. py:attribute:: data :type: dict[str, Any] The corresponding data about the circuit .. py:attribute:: job :type: cirq_superstaq.Job | None :value: None The superstaq job corresponding to the sample. Defaults to None if no job is associated with the sample. .. py:attribute:: probabilities :type: dict[str, float] | None :value: None The probabilities of the computational basis states .. py:attribute:: raw_circuit :type: cirq.Circuit The raw (i.e. pre-compiled) sample circuit. .. py:property:: target :type: str Returns: The name of the target that the sample was submitted to. .. py:class:: XEB(single_qubit_gate_set: list[cirq.Gate] | None = None, two_qubit_gate: cirq.Gate | None = cirq.CZ, *, random_seed: int | numpy.random.Generator | None = None) Bases: :py:obj:`supermarq.qcvv.base_experiment.BenchmarkingExperiment`\ [\ :py:obj:`XEBResults`\ ] Cross-entropy benchmarking (XEB) experiment. The XEB experiment can be used to estimate the combined fidelity of a repeating cycle of gates. In our case, where we restrict ourselves to two qubits, we use cycles made up of two randomly selected single qubit phased XZ gates and a constant two qubit gate. This is illustrated as follows: For each randomly generated circuit, with a given number of cycle, we compare the simulated state probabilities, :math:`p(x)` with those achieved by running the circuit on a given target, :math:`\hat{p}(x)`. The fidelity of a circuit containing :math:`d` cycles, :math:`f_d` can then be estimated as .. math:: \sum_{x \in \{0, 1\}^n} p(x) \hat{p}(x) - \frac{1}{2^n} = f_d \left(\sum_{x \in \{0, 1\}^n} p(x)^2 - \frac{1}{2^n}\right) We can therefore fit a linear model to estimate the value of :math:`f_d`. We the estimate the fidelity of the cycle, :math:`f_{\mathrm{cycle}}` as .. math:: f_d = A(f_{cycle})^d Thus fitting another linear model to :math:`\log(f_d) \sim d` provides us with an estimate of the cycle fidelity. For more details see: https://www.nature.com/articles/s41586-019-1666-5 .. py:method:: analyze_results(plot_results: bool = True) -> XEBResults Analyse the results and calculate the estimated circuit fidelity. :param plot_results: Whether to generate the data plots. Defaults to True. :type plot_results: optional :returns: The final results from the experiment. .. py:method:: plot_results() -> None Plot the experiment data and the corresponding fits. .. py:property:: circuit_fidelities :type: pandas.DataFrame The circuit fidelity calculations from the most recently run experiment. :raises RuntimeError: If no data is available. .. py:property:: samples :type: collections.abc.Sequence[XEBSample] The samples generated during the experiment. :raises RuntimeError: If no samples are available. .. py:attribute:: single_qubit_gate_set :type: list[cirq.Gate] The single qubit gates to randomly sample from .. py:attribute:: two_qubit_gate :type: cirq.Gate | None The two qubit gate to use for interleaving. .. py:class:: XEBResults Bases: :py:obj:`supermarq.qcvv.base_experiment.BenchmarkingResults` Results from an XEB experiment. .. py:attribute:: cycle_fidelity_estimate :type: float Estimated cycle fidelity. .. py:attribute:: cycle_fidelity_estimate_std :type: float Standard deviation for the cycle fidelity estimate. .. py:attribute:: experiment_name :value: 'XEB' .. py:class:: XEBSample Bases: :py:obj:`supermarq.qcvv.base_experiment.Sample` The samples used in XEB experiments. .. py:method:: sum_target_cross_sample_probs() -> float Compute the dot product between the sample and target probabilities :raises RuntimeError: If either the target or sample probabilities have not yet been initialised. :returns: The dot product between the sample and target probabilities. :rtype: float .. py:method:: sum_target_probs_square() -> float Compute the sum of the squared target probabilities. :raises RuntimeError: If no target probabilities have been initialised. :returns: The sum of squared target probabilities. :rtype: float .. py:attribute:: sample_probabilities :type: dict[str, float] | None :value: None The sample probabilities obtained from the chosen target .. py:attribute:: target_probabilities :type: dict[str, float] | None :value: None The target probabilities obtained through a noiseless simulator