Source code for qiskit.ignis.verification.randomized_benchmarking.fitters

# -*- coding: utf-8 -*-

# This code is part of Qiskit.
#
# (C) Copyright IBM 2019.
#
# 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.

# pylint: disable=invalid-name

# NOTE: this is because BasicUtils doesn't define any arguments on it's methods
# so the implementations here don't match the abstract class. This needs to
# be fixed or reworked without an abstract class
# pylint: disable=arguments-differ

"""
Functions used for the analysis of randomized benchmarking results.
"""

from abc import ABC, abstractmethod
from scipy.optimize import curve_fit
import numpy as np
from qiskit import QiskitError
from qiskit.quantum_info.analysis.average import average_data
from ..tomography import marginal_counts
from ...utils import build_counts_dict_from_list

try:
    from matplotlib import pyplot as plt
    HAS_MATPLOTLIB = True
except ImportError:
    HAS_MATPLOTLIB = False


class RBFitterBase(ABC):
    """
        Abstract base class (ABS) for fitters for randomized benchmarking.
    """

    @property
    @abstractmethod
    def raw_data(self):
        """Return raw data."""
        return

    @property
    @abstractmethod
    def cliff_lengths(self):
        """Return clifford lengths."""
        return

    @property
    @abstractmethod
    def ydata(self):
        """Return ydata (means and std devs)."""
        return

    @property
    @abstractmethod
    def fit(self):
        """Return fit."""
        return

    @property
    @abstractmethod
    def rb_fit_fun(self):
        """Return the fit function rb_fit_fun."""
        return

    @property
    @abstractmethod
    def seeds(self):
        """Return the number of loaded seeds."""
        return

    @property
    @abstractmethod
    def results(self):
        """Return all the results."""
        return

    @abstractmethod
    def add_data(self):
        """
        Add a new result. Re calculate the raw data, means and
        fit.
        """

        return

    @abstractmethod
    def calc_data(self):
        """Retrieve probabilities of success from execution results."""

        return

    @abstractmethod
    def calc_statistics(self):
        """Extract averages and std dev from the raw data."""

        return

    @abstractmethod
    def fit_data_pattern(self):
        """
        Fit the RB results of a particular pattern
        to an exponential curve.
        """

        return

    @abstractmethod
    def fit_data(self):
        """
        Fit the RB results to an exponential curve.
        """

        return

    @abstractmethod
    def plot_rb_data(self):
        """
        Plot randomized benchmarking data of a single pattern.

        """

        return


[docs]class RBFitter(RBFitterBase): """ Class for fitters for randomized benchmarking. """ def __init__(self, backend_result, cliff_lengths, rb_pattern=None): """ Args: backend_result (Result): list of results (qiskit.Result). cliff_lengths (list): the Clifford lengths, 2D list i x j where i is the number of patterns, j is the number of cliffords lengths. rb_pattern (list): the pattern for the RB sequences. """ if rb_pattern is None: rb_pattern = [[0]] self._cliff_lengths = cliff_lengths self._rb_pattern = rb_pattern self._raw_data = [] self._ydata = [] self._fit = [{} for e in rb_pattern] self._nseeds = [] self._circ_name_type = '' self._result_list = [] self.add_data(backend_result) @property def raw_data(self): """Return raw data.""" return self._raw_data @raw_data.setter def raw_data(self, raw_data): if raw_data is None: self._raw_data = [] else: self._raw_data = raw_data @property def cliff_lengths(self): """Return clifford lengths.""" return self._cliff_lengths @property def ydata(self): """Return ydata (means and std devs).""" return self._ydata @ydata.setter def ydata(self, ydata): if ydata is None: self._ydata = [] else: self._ydata = ydata @property def fit(self): """Return fit.""" return self._fit @fit.setter def fit(self, fit): if fit is None: self._fit = [] else: self._fit = fit @property def rb_fit_fun(self): """Return the fit function rb_fit_fun.""" return self._rb_fit_fun @property def seeds(self): """Return the number of loaded seeds.""" return self._nseeds @property def results(self): """Return all the results.""" return self._result_list
[docs] def add_data(self, new_backend_result, rerun_fit=True): """ Add a new result. Re calculate the raw data, means and fit. Args: new_backend_result (list): list of RB results. rerun_fit (bool): re calculate the means and fit the result. Additional information: Assumes that the executed 'result' is the output of circuits generated by randomized_benchmarking_seq. """ if new_backend_result is None: return if not isinstance(new_backend_result, list): new_backend_result = [new_backend_result] for result in new_backend_result: self._result_list.append(result) # update the number of seeds *if* new ones # added. Note, no checking if we've done all the # cliffords for rbcirc in result.results: nseeds_circ = int(rbcirc.header.name.split('_')[-1]) if nseeds_circ not in self._nseeds: self._nseeds.append(nseeds_circ) if rerun_fit: self.calc_data() self.calc_statistics() self.fit_data()
@staticmethod def _rb_fit_fun(x, a, alpha, b): """Function used to fit RB.""" # pylint: disable=invalid-name return a * alpha ** x + b
[docs] def calc_data(self): """Retrieve probabilities of success from execution results. Outputs results into an internal variable _raw_data which is a 3-dimensional list, where item (i,j,k) is the probability to measure the ground state for the set of qubits in pattern "i" for seed no. j and vector length self._cliff_lengths[i][k]. Additional information: Assumes that the executed 'result' is the output of circuits generated by randomized_benchmarking_seq. """ # The type of the circuit name, e.g. rb or rb_interleaved # as it appears in the result (before _length_%d_seed_%d) self._circ_name_type = self._result_list[0].results[0]. \ header.name.split("_length")[0] circ_counts = {} circ_shots = {} for seed in self._nseeds: for circ, _ in enumerate(self._cliff_lengths[0]): circ_name = self._circ_name_type + '_length_%d_seed_%d' \ % (circ, seed) count_list = [] for result in self._result_list: try: count_list.append(result.get_counts(circ_name)) except (QiskitError, KeyError): pass circ_counts[circ_name] = \ build_counts_dict_from_list(count_list) circ_shots[circ_name] = sum(circ_counts[circ_name].values()) self._raw_data = [] startind = 0 for patt_ind in range(len(self._rb_pattern)): string_of_0s = '' string_of_0s = string_of_0s.zfill(len(self._rb_pattern[patt_ind])) self._raw_data.append([]) endind = startind+len(self._rb_pattern[patt_ind]) for seedidx, seed in enumerate(self._nseeds): self._raw_data[-1].append([]) for k, _ in enumerate(self._cliff_lengths[patt_ind]): circ_name = self._circ_name_type + '_length_%d_seed_%d' \ % (k, seed) counts_subspace = marginal_counts( circ_counts[circ_name], np.arange(startind, endind)) self._raw_data[-1][seedidx].append( counts_subspace.get(string_of_0s, 0) / circ_shots[circ_name]) startind = endind
[docs] def calc_statistics(self): """Extract averages and std dev from the raw data (self._raw_data). Assumes that self._calc_data has been run. Output into internal _ydata variable. ydata is a list of dictionaries (length number of patterns). Dictionary ydata[i]: * ydata[i]['mean'] is a numpy_array of length n; entry j of this array contains the mean probability of success over seeds, for vector length self._cliff_lengths[i][j]. * ydata[i]['std'] is a numpy_array of length n; entry j of this array contains the std of the probability of success over seeds, for vector length self._cliff_lengths[i][j]. """ self._ydata = [] for patt_ind in range(len(self._rb_pattern)): self._ydata.append({}) self._ydata[-1]['mean'] = np.mean(self._raw_data[patt_ind], 0) if len(self._raw_data[patt_ind]) == 1: # 1 seed self._ydata[-1]['std'] = None else: self._ydata[-1]['std'] = np.std(self._raw_data[patt_ind], 0)
[docs] def fit_data_pattern(self, patt_ind, fit_guess): """ Fit the RB results of a particular pattern to an exponential curve. Args: patt_ind (int): index of the data pattern to fit. fit_guess (list): guess values for the fit. Puts the results into a list of fit dictionaries where each dictionary corresponds to a pattern and has fields: * ``params`` - three parameters of rb_fit_fun. The middle one is the exponent. * ``err`` - the error limits of the parameters. * ``epc`` - error per Clifford. """ lens = self._cliff_lengths[patt_ind] qubits = self._rb_pattern[patt_ind] # if at least one of the std values is zero, then sigma is replaced # by None if not self._ydata[patt_ind]['std'] is None: sigma = self._ydata[patt_ind]['std'].copy() if len(sigma) - np.count_nonzero(sigma) > 0: sigma = None else: sigma = None params, pcov = curve_fit(self._rb_fit_fun, lens, self._ydata[patt_ind]['mean'], sigma=sigma, p0=fit_guess, bounds=([0, 0, 0], [1, 1, 1])) alpha = params[1] # exponent params_err = np.sqrt(np.diag(pcov)) alpha_err = params_err[1] nrb = 2 ** len(qubits) epc = (nrb-1)/nrb*(1-alpha) epc_err = (nrb-1)/nrb*alpha_err/alpha self._fit[patt_ind] = {'params': params, 'params_err': params_err, 'epc': epc, 'epc_err': epc_err}
[docs] def fit_data(self): """Fit the RB results to an exponential curve. Fit each of the patterns. Use the data to construct guess values for the fits. Puts the results into a list of fit dictionaries where each dictionary corresponds to a pattern and has fields: * ``params`` - three parameters of rb_fit_fun. The middle one is the exponent. * ``err`` - the error limits of the parameters. * ``epc`` - error per Clifford. """ for patt_ind, _ in enumerate(self._rb_pattern): qubits = self._rb_pattern[patt_ind] # Should decay to 1/2^n fit_guess = [0.95, 0.99, 1/2**len(qubits)] # Use the first two points to guess the decay param y0 = self._ydata[patt_ind]['mean'][0] y1 = self._ydata[patt_ind]['mean'][1] dcliff = (self._cliff_lengths[patt_ind][1] - self._cliff_lengths[patt_ind][0]) dy = ((y1 - fit_guess[2]) / (y0 - fit_guess[2])) alpha_guess = dy**(1/dcliff) if alpha_guess < 1.0: fit_guess[1] = alpha_guess if y0 > fit_guess[2]: fit_guess[0] = ((y0 - fit_guess[2]) / fit_guess[1]**self._cliff_lengths[patt_ind][0]) self.fit_data_pattern(patt_ind, tuple(fit_guess))
[docs] def plot_rb_data(self, pattern_index=0, ax=None, add_label=True, show_plt=True): """Plot randomized benchmarking data of a single pattern. Args: pattern_index (int): which RB pattern to plot. ax (Axes): plot axis (if passed in). add_label (bool): Add an EPC label. show_plt (bool): display the plot. Raises: ImportError: if matplotlib is not installed. """ fit_function = self._rb_fit_fun if not HAS_MATPLOTLIB: raise ImportError('The function plot_rb_data needs matplotlib. ' 'Run "pip install matplotlib" before.') if ax is None: plt.figure() ax = plt.gca() xdata = self._cliff_lengths[pattern_index] # Plot the result for each sequence for one_seed_data in self._raw_data[pattern_index]: ax.plot(xdata, one_seed_data, color='gray', linestyle='none', marker='x') # Plot the mean with error bars ax.errorbar(xdata, self._ydata[pattern_index]['mean'], yerr=self._ydata[pattern_index]['std'], color='r', linestyle='--', linewidth=3) # Plot the fit ax.plot(xdata, fit_function(xdata, *self._fit[pattern_index]['params']), color='blue', linestyle='-', linewidth=2) ax.tick_params(labelsize=14) ax.set_xlabel('Clifford Length', fontsize=16) ax.set_ylabel('Ground State Population', fontsize=16) ax.grid(True) if add_label: bbox_props = dict(boxstyle="round,pad=0.3", fc="white", ec="black", lw=2) ax.text(0.6, 0.9, "alpha: %.3f(%.1e) EPC: %.3e(%.1e)" % (self._fit[pattern_index]['params'][1], self._fit[pattern_index]['params_err'][1], self._fit[pattern_index]['epc'], self._fit[pattern_index]['epc_err']), ha="center", va="center", size=14, bbox=bbox_props, transform=ax.transAxes) if show_plt: plt.show()
[docs]class InterleavedRBFitter(RBFitterBase): """ Class for fitters for interleaved RB, derived from RBFitterBase class. Contains two RBFitter objects: the original RBFitter and the interleaved RBFitter. """ def __init__(self, original_result, interleaved_result, cliff_lengths, rb_pattern=None): """ Args: original_result (list): list of results of the original RB sequence (qiskit.Result). interleaved_result (list): list of results of the interleaved RB sequence (qiskit.Result). cliff_lengths (list): the Clifford lengths, 2D list i x j where i is the number of patterns, j is the number of cliffords lengths. rb_pattern (list): the pattern for the RB sequences. """ self._cliff_lengths = cliff_lengths self._rb_pattern = rb_pattern self._fit_interleaved = [] self._rbfit_original = RBFitter( original_result, cliff_lengths, rb_pattern) self._rbfit_interleaved = RBFitter( interleaved_result, cliff_lengths, rb_pattern) self.rbfit_std.add_data(original_result) self.rbfit_int.add_data(interleaved_result) if not (original_result is None and interleaved_result is None): self.fit_data() @property def rbfit_std(self): """Return the original RB fitter.""" return self._rbfit_original @property def rbfit_int(self): """Return the interleaved RB fitter.""" return self._rbfit_interleaved @property def cliff_lengths(self): """Return clifford lengths.""" return self._cliff_lengths @property def fit(self): """Return fit as a 2 element list.""" return [self.rbfit_std.fit, self.rbfit_int.fit] @property def fit_int(self): """Return interleaved fit parameters.""" return self._fit_interleaved @property def rb_fit_fun(self): """Return the fit function rb_fit_fun.""" return self.rbfit_std.rb_fit_fun @property def seeds(self): """Return the number of loaded seeds as a 2 element list.""" return [self.rbfit_std.seeds, self.rbfit_int.seeds] @property def results(self): """Return all the results as a 2 element list.""" return [self.rbfit_std.results, self.rbfit_int.results] @property def ydata(self): """Return ydata (means and std devs) as a 2 element list.""" return [self.rbfit_std.ydata, self.rbfit_int.ydata] @property def raw_data(self): """Return raw_data as a 2 element list.""" return [self.rbfit_std.raw_data, self.rbfit_int.raw_data]
[docs] def add_data(self, new_original_result, new_interleaved_result, rerun_fit=True): """ Add a new result. Args: new_original_result (list): list of RB results of the original circuits. new_interleaved_result (list): list of RB results of the interleaved circuits. rerun_fit (bool): re-calculate the means and fit the result. Additional information: Assumes that the executed 'result' is the output of circuits generated by randomized_benchmarking_seq. """ self.rbfit_std.add_data(new_original_result, rerun_fit) self.rbfit_int.add_data(new_interleaved_result, rerun_fit) if rerun_fit: self.fit_data()
[docs] def calc_data(self): """Retrieve probabilities of success from execution results. Outputs results into an internal variables: _raw_original_data and _raw_interleaved_data """ self.rbfit_std.calc_data() self.rbfit_int.calc_data()
[docs] def calc_statistics(self): """Extract averages and std dev. Output [ydata_original, ydata_interleaved] """ self.rbfit_std.calc_statistics() self.rbfit_int.calc_statistics()
[docs] def fit_data_pattern(self, patt_ind, fit_guess, fit_index=0): """ Fit the RB results of a particular pattern to an exponential curve. Args: patt_ind (int): index of the data to fit. fit_guess (list): guess values for the fit. fit_index (int): 0 fit the standard data, 1 fit the interleaved data. """ if fit_index == 0: self.rbfit_std.fit_data_pattern(patt_ind, fit_guess) else: self.rbfit_int.fit_data_pattern(patt_ind, fit_guess)
[docs] def fit_data(self): """ Fit the interleaved RB results. Fit each of the patterns. According to the paper: "Efficient measurement of quantum gate error by interleaved randomized benchmarking" (arXiv:1203.4550) - Equations (4) and (5). Puts the results into a list of fit dictionaries: where each dictionary corresponds to a pattern and has fields: * 'epc_est' - the estimated error per the interleaved Clifford. * 'epc_est_error' - the estimated error derived from the params_err. * 'systematic_err' - systematic error bound of epc_est. * 'systematic_err_L' = epc_est - systematic_err (left error bound). * 'systematic_err_R' = epc_est + systematic_err (right error bound). """ self.rbfit_std.fit_data() self.rbfit_int.fit_data() self._fit_interleaved = [] for patt_ind, (_, qubits) in enumerate(zip(self._cliff_lengths, self._rb_pattern)): # calculate nrb=d=2^n: nrb = 2 ** len(qubits) # Calculate alpha (=p) and alpha_c (=p_c): alpha = self.rbfit_std.fit[patt_ind]['params'][1] alpha_c = self.rbfit_int.fit[patt_ind]['params'][1] # Calculate their errors: alpha_err = self.rbfit_std.fit[patt_ind]['params_err'][1] alpha_c_err = self.rbfit_int.fit[patt_ind]['params_err'][1] # Calculate epc_est (=r_c^est) - Eq. (4): epc_est = (nrb - 1) * (1 - alpha_c / alpha) / nrb # Calculate the systematic error bounds - Eq. (5): systematic_err_1 = (nrb - 1) * (abs(alpha - alpha_c / alpha) + (1 - alpha)) / nrb systematic_err_2 = 2 * (nrb * nrb - 1) * (1 - alpha) / \ (alpha * nrb * nrb) + 4 * (np.sqrt(1 - alpha)) * \ (np.sqrt(nrb * nrb - 1)) / alpha systematic_err = min(systematic_err_1, systematic_err_2) systematic_err_L = epc_est - systematic_err systematic_err_R = epc_est + systematic_err # Calculate epc_est_error alpha_err_sq = (alpha_err / alpha) * (alpha_err / alpha) alpha_c_err_sq = (alpha_c_err / alpha_c) * (alpha_c_err / alpha_c) epc_est_err = ((nrb - 1) / nrb) * (alpha_c / alpha) \ * (np.sqrt(alpha_err_sq + alpha_c_err_sq)) self._fit_interleaved.append({'alpha': alpha, 'alpha_err': alpha_err, 'alpha_c': alpha_c, 'alpha_c_err': alpha_c_err, 'epc_est': epc_est, 'epc_est_err': epc_est_err, 'systematic_err': systematic_err, 'systematic_err_L': systematic_err_L, 'systematic_err_R': systematic_err_R})
[docs] def plot_rb_data(self, pattern_index=0, ax=None, add_label=True, show_plt=True): """ Plot interleaved randomized benchmarking data of a single pattern. Args: pattern_index (int): which RB pattern to plot. ax (Axes): plot axis (if passed in). add_label (bool): Add an EPC label. show_plt (bool): display the plot. Raises: ImportError: if matplotlib is not installed. """ if not HAS_MATPLOTLIB: raise ImportError('The function plot_interleaved_rb_data \ needs matplotlib. Run "pip install matplotlib" before.') if ax is None: plt.figure() ax = plt.gca() xdata = self._cliff_lengths[pattern_index] # Plot the original and interleaved result for each sequence for one_seed_data in self.raw_data[0][pattern_index]: ax.plot(xdata, one_seed_data, color='blue', linestyle='none', marker='x') for one_seed_data in self.raw_data[1][pattern_index]: ax.plot(xdata, one_seed_data, color='red', linestyle='none', marker='+') # Plot the fit std_fit_function = self.rbfit_std.rb_fit_fun int_fit_function = self.rbfit_int.rb_fit_fun ax.plot(xdata, std_fit_function(xdata, *self.fit[0][pattern_index]['params']), color='blue', linestyle='-', linewidth=2, label='Standard RB') ax.tick_params(labelsize=14) ax.plot(xdata, int_fit_function(xdata, *self.fit[1][pattern_index]['params']), color='red', linestyle='-', linewidth=2, label='Interleaved RB') ax.tick_params(labelsize=14) ax.set_xlabel('Clifford Length', fontsize=16) ax.set_ylabel('Ground State Population', fontsize=16) ax.grid(True) ax.legend(loc='lower left') if add_label: bbox_props = dict(boxstyle="round,pad=0.3", fc="white", ec="black", lw=2) ax.text(0.6, 0.9, "alpha: %.3f(%.1e) alpha_c: %.3e(%.1e) \n \ EPC_est: %.3e(%.1e)" % (self._fit_interleaved[pattern_index]['alpha'], self._fit_interleaved[pattern_index]['alpha_err'], self._fit_interleaved[pattern_index]['alpha_c'], self._fit_interleaved[pattern_index]['alpha_c_err'], self._fit_interleaved[pattern_index]['epc_est'], self._fit_interleaved[pattern_index]['epc_est_err']), ha="center", va="center", size=14, bbox=bbox_props, transform=ax.transAxes) if show_plt: plt.show()
[docs]class PurityRBFitter(RBFitterBase): """ Class for fitter for purity RB. Derived from RBFitterBase class. """ def __init__(self, purity_result, npurity, cliff_lengths, rb_pattern=None): """ Args: purity_result (list): list of results of the 3^n purity RB sequences per seed (qiskit.Result). npurity (int): equals 3^n (where n is the dimension). cliff_lengths (list): the Clifford lengths, 2D list i x j where i is the number of patterns, j is the number of cliffords lengths. rb_pattern (list): the pattern for the RB sequences. """ if rb_pattern is None: rb_pattern = [[0]] self._cliff_lengths = cliff_lengths self._rb_pattern = rb_pattern self._npurity = npurity self._nq = len(rb_pattern[0]) # all patterns have same length self._fit = [{} for e in rb_pattern] self._circ_name_type = '' self._zdict_ops = [] self.add_zdict_ops() # rb purity fitter self._rbfit_purity = RBFitter(purity_result, cliff_lengths, rb_pattern) self.add_data(purity_result) @property def rbfit_pur(self): """Return the purity RB fitter.""" return self._rbfit_purity @property def raw_data(self): """Return raw data.""" return self.rbfit_pur.raw_data @property def cliff_lengths(self): """Return clifford lengths.""" return self.cliff_lengths @property def ydata(self): """Return ydata (means and std devs).""" return self.rbfit_pur.ydata @property def fit(self): """Return the purity fit parameters.""" return self.rbfit_pur.fit @property def rb_fit_fun(self): """Return the fit function rb_fit_fun.""" return self.rbfit_pur.rb_fit_fun @property def seeds(self): """Return the number of loaded seeds.""" return self.rbfit_pur.seeds @property def results(self): """Return all the results.""" return self.rbfit_pur.results @staticmethod def _rb_pur_fit_fun(x, a, alpha, b): """Function used to fit purity rb.""" # pylint: disable=invalid-name return a * alpha ** (2 * x) + b
[docs] @staticmethod def F234(n, a, b): """Function than maps: 2^n x 3^n --> 4^n , namely: (a,b) --> c where a in 2^n, b in 3^n, c in 4^n """ # 0 <--> I # 1 <--> X # 2 <--> Y # 3 <--> Z LUT = [[0, 0, 0], [3, 1, 2]] # compute bits aseq = [] bseq = [] aa = a bb = b for i in range(n): aseq.append(np.mod(aa, 2)) bseq.append(np.mod(bb, 3)) aa = np.floor_divide(aa, 2) bb = np.floor_divide(bb, 3) c = 0 for i in range(n): c += (4 ** i) * LUT[aseq[i]][bseq[i]] return c
[docs] def add_zdict_ops(self): """Creating all Z-correlators in order to compute the expectation values.""" statedict = {("{0:0%db}" % self._nq).format(i): 1 for i in range(2 ** self._nq)} for i in range(2 ** self._nq): self._zdict_ops.append(statedict.copy()) for j in range(2 ** self._nq): if bin(i & j).count('1') % 2 != 0: self._zdict_ops[-1][("{0:0%db}" % self._nq).format(j)] = -1
[docs] def add_data(self, new_purity_result, rerun_fit=True): """ Add a new result. Args: new_purity_result (list): list of RB results of the purity RB circuits. rerun_fit (bool): re-calculate the means and fit the result. Additional information: Assumes that the executed 'result' is the output of circuits generated by randomized_benchmarking_seq where is_purity = True. """ if new_purity_result is None: return self.rbfit_pur.add_data(new_purity_result, rerun_fit) if rerun_fit: self.calc_data() self.calc_statistics() self.fit_data()
[docs] def calc_data(self): """ Retrieve probabilities of success from execution results. Measure the purity calculation into an internal variable _raw_data which is a 3-dimensional list, where item (i,j,k) is the purity of the set of qubits in pattern "i" for seed no. j and vector length self._cliff_lengths[i][k]. Additional information: Assumes that the executed 'result' is the output of circuits generated by randomized_benchmarking_seq, """ circ_counts = {} circ_shots = {} result_count = 0 # Calculating the result output for _, seed in enumerate(self.rbfit_pur.seeds): for pur in range(self._npurity): self._circ_name_type = self.rbfit_pur.results[ result_count].results[0].header.name.split("_length")[0] result_count += 1 for circ, _ in enumerate(self._cliff_lengths[0]): circ_name = self._circ_name_type + '_length_%d_seed_%d' \ % (circ, seed) count_list = [] for result in self.rbfit_pur.results: try: count_list.append(result.get_counts(circ_name)) except (QiskitError, KeyError): pass circ_name = 'rb_purity_' + str(pur) + \ '_length_%d_seed_%d' % (circ, seed) circ_counts[circ_name] = build_counts_dict_from_list( count_list) circ_shots[circ_name] = sum(circ_counts[circ_name]. values()) # Calculating raw_data self.rbfit_pur.raw_data = [] startind = 0 # for each pattern for patt_ind, _ in enumerate(self._rb_pattern): endind = startind + len(self._rb_pattern[patt_ind]) self.rbfit_pur.raw_data.append([]) # for each seed for seedidx, seed in enumerate(self.rbfit_pur.seeds): self.rbfit_pur.raw_data[-1].append([]) # for each length for k, _ in enumerate(self._cliff_lengths[0]): # vector of the 4^n correlators and counts corr_vec = [0] * (4 ** self._nq) count_vec = [0] * (4 ** self._nq) # corr_list = [[] for e in range(4 ** self._nq)] for pur in range(self._npurity): circ_name = 'rb_purity_' + str(pur) + \ '_length_%d_seed_%d' % (k, seed) # marginal counts for the pattern counts_subspace = marginal_counts( circ_counts[circ_name], np.arange(startind, endind)) # calculating the vector of 4^n correlators for indcorr in range(2 ** self._nq): zcorr = average_data(counts_subspace, self._zdict_ops[indcorr]) zind = self.F234(self._nq, indcorr, pur) corr_vec[zind] += zcorr count_vec[zind] += 1 # calculating the purity purity = 0 for idx, _ in enumerate(corr_vec): purity += (corr_vec[idx]/count_vec[idx]) ** 2 purity = purity / (2 ** self._nq) self.rbfit_pur.raw_data[-1][seedidx].append(purity) startind = endind
[docs] def calc_statistics(self): """Extract averages and std dev from the raw data (self._raw_data). Assumes that self._calc_data has been run. Output into internal _ydata variable. ydata is a list of dictionaries (length number of patterns): Dictionary ydata[i]: * ydata[i]['mean'] is a numpy_array of length n; entry j of this array contains the mean probability of success over seeds, for vector length self._cliff_lengths[i][j]. * ydata[i]['std'] is a numpy_array of length n; entry j of this array contains the std of the probability of success over seeds, for vector length self._cliff_lengths[i][j]. """ self.rbfit_pur.calc_statistics()
[docs] def fit_data_pattern(self, patt_ind, fit_guess): """ Fit the RB results of a particular pattern to an exponential curve. Args: patt_ind (int): index of the subsystem to fit. fit_guess (list): guess values for the fit. Puts the results into a list of fit dictionaries where each dictionary corresponds to a pattern and has fields: * ``params`` - three parameters of rb_fit_fun. The middle one is the exponent. * ``err`` - the error limits of the parameters. """ self.rbfit_pur.fit_data_pattern(patt_ind, fit_guess)
[docs] def fit_data(self): """Fit the Purity RB results to an exponential curve. Use the data to construct guess values for the fits. Puts the results into a list of fit dictionaries where each dictionary corresponds to a pattern and has fields: * ``params`` - three parameters of rb_fit_fun. The middle one is the exponent. * ``err`` - the error limits of the parameters. * ``epc`` - Error per Clifford. * ``pepc`` - Purity Error per Clifford. """ self.rbfit_pur.fit_data() for patt_ind, (_, _) in enumerate(zip(self._cliff_lengths, self._rb_pattern)): # Calculate alpha (=p): # fitting the curve: A*p^(2m)+B # where m is the Clifford length alpha = self.rbfit_pur.fit[patt_ind]['params'][1] alpha_pur = np.sqrt(alpha) self.rbfit_pur.fit[patt_ind]['params'][1] = alpha_pur # calculate the error of alpha alpha_err = self.rbfit_pur.fit[patt_ind]['params_err'][1] alpha_pur_err = alpha_err / (2 * np.sqrt(alpha_pur)) self.rbfit_pur.fit[patt_ind]['params_err'][1] = \ alpha_pur_err # calculate purity error per clifford (pepc) nrb = 2 ** self._nq pepc = (nrb-1)/nrb * (1-alpha_pur) self.rbfit_pur.fit[patt_ind]['pepc'] = \ pepc pepc_err = (nrb-1)/nrb * alpha_pur_err / alpha_pur self.rbfit_pur.fit[patt_ind]['pepc_err'] = \ pepc_err
[docs] def plot_rb_data(self, pattern_index=0, ax=None, add_label=True, show_plt=True): """Plot purity RB data of a single pattern.""" fit_function = self._rb_pur_fit_fun if not HAS_MATPLOTLIB: raise ImportError('The function plot_rb_data needs matplotlib. ' 'Run "pip install matplotlib" before.') if ax is None: plt.figure() ax = plt.gca() xdata = self._cliff_lengths[pattern_index] # Plot the result for each sequence for one_seed_data in self.rbfit_pur.raw_data[pattern_index]: ax.plot(xdata, one_seed_data, color='gray', linestyle='none', marker='x') # Plot the mean with error bars ax.errorbar(xdata, self.rbfit_pur.ydata[pattern_index]['mean'], yerr=self.rbfit_pur.ydata[pattern_index]['std'], color='r', linestyle='--', linewidth=3) # Plot the fit ax.plot(xdata, fit_function(xdata, *self.rbfit_pur.fit[pattern_index][ 'params']), color='blue', linestyle='-', linewidth=2) ax.tick_params(labelsize=14) ax.set_xlabel('Clifford Length', fontsize=16) ax.set_ylabel('Trace of Rho Square', fontsize=16) ax.grid(True) if add_label: bbox_props = dict(boxstyle="round,pad=0.3", fc="white", ec="black", lw=2) ax.text(0.6, 0.9, "alpha: %.3f(%.1e) PEPC: %.3e(%.1e)" % (self.rbfit_pur.fit[pattern_index]['params'][1], self.rbfit_pur.fit[pattern_index]['params_err'][1], self.rbfit_pur.fit[pattern_index]['pepc'], self.rbfit_pur.fit[pattern_index]['pepc_err']), ha="center", va="center", size=14, bbox=bbox_props, transform=ax.transAxes) if show_plt: plt.show()
[docs]class CNOTDihedralRBFitter(RBFitterBase): """Class for fitters for non-Clifford CNOT-Dihedral RB. Derived from RBFitterBase class. Contains two RBFitter objects. """ def __init__(self, cnotdihedral_Z_result, cnotdihedral_X_result, elmnts_lengths, rb_pattern=None): """ Args: cnotdihedral_Z_result (qiskit.Result): list of results of the RB sequence that measures the ground state. cnotdihedral_X_result (qiskit.Result): list of results of the RB sequence that measures the :math:`|+...+>` state. elmnts_lengths (list): the group elements lengths, 2D list i x j where i is the number of patterns, j is the number of elements lengths. rb_pattern (list): the pattern for the RB sequences. """ # Initialize a new CNOTDihedralRBFitter. self._cliff_lengths = elmnts_lengths self._rb_pattern = rb_pattern self._fit_cnotdihedral = [] self._rbfit_Z = RBFitter( cnotdihedral_Z_result, elmnts_lengths, rb_pattern) self._rbfit_X = RBFitter( cnotdihedral_X_result, elmnts_lengths, rb_pattern) self.rbfit_Z.add_data(cnotdihedral_Z_result) self.rbfit_X.add_data(cnotdihedral_X_result) if not (cnotdihedral_Z_result is None and cnotdihedral_X_result is None): self.fit_data() @property def rbfit_Z(self): """Return the cnotdihedral Z fitter.""" return self._rbfit_Z @property def rbfit_X(self): """Return the cnotdihedral X fitter.""" return self._rbfit_X @property def cliff_lengths(self): """Return group elements lengths.""" return self._cliff_lengths @property def fit(self): """Return fit as a 2 element list.""" return [self.rbfit_Z.fit, self.rbfit_X.fit] @property def fit_cnotdihedral(self): """Return cnotdihedral fit parameters.""" return self._fit_cnotdihedral @property def rb_fit_fun(self): """Return the fit function rb_fit_fun.""" return self.rbfit_Z.rb_fit_fun @property def seeds(self): """Return the number of loaded seeds as a 2 element list.""" return [self.rbfit_Z.seeds, self.rbfit_X.seeds] @property def results(self): """Return all the results as a 2 element list.""" return [self.rbfit_Z.results, self.rbfit_X.results] @property def ydata(self): """Return ydata (means and std devs) as a 2 element list.""" return [self.rbfit_Z.ydata, self.rbfit_X.ydata] @property def raw_data(self): """Return raw_data as 2 element list.""" return [self.rbfit_Z.raw_data, self.rbfit_X.raw_data]
[docs] def add_data(self, new_cnotdihedral_Z_result, new_cnotdihedral_X_result, rerun_fit=True): """ Add a new result. Args: new_cnotdihedral_Z_result (list): list of rb results of the cnot-dihedral Z circuits. new_cnotdihedral_X_result (list): list of rb results of the cnot-dihedral X circuits. rerun_fit (bool): re-calculate the means and fit the result. Additional information: Assumes that the executed 'result' is the output of circuits generated by randomized_benchmarking_seq. """ self.rbfit_Z.add_data(new_cnotdihedral_Z_result, rerun_fit) self.rbfit_X.add_data(new_cnotdihedral_X_result, rerun_fit) if rerun_fit: self.fit_data()
[docs] def calc_data(self): """ Retrieve probabilities of success from execution results. Outputs results into an internal variable: _raw_data . """ self.rbfit_Z.calc_data() self.rbfit_X.calc_data()
[docs] def calc_statistics(self): """ Extract averages and std dev. Outputs results into an internal variable: _ydata . """ self.rbfit_Z.calc_statistics() self.rbfit_X.calc_statistics()
[docs] def fit_data_pattern(self, patt_ind, fit_guess, fit_index=0): """ Fit the RB results of a particular pattern to an exponential curve. Args: patt_ind (int): index of the data pattern to fit. fit_guess (list): guess values for the fit. fit_index (int): 0 fit the standard data, 1 fit the interleaved data. """ if fit_index == 0: self.rbfit_Z.fit_data_pattern(patt_ind, fit_guess) else: self.rbfit_X.fit_data_pattern(patt_ind, fit_guess)
[docs] def fit_data(self): """Fit the non-Clifford cnot-dihedral RB results. Fit each of the patterns. According to the paper: `Scalable randomized benchmarking of non-Clifford gates <https://www.nature.com/articles/npjqi201612>`_ Returns: list: A list of dictionaries where each dictionary corresponds to a pattern and has fields: * ``alpha`` - alpha parameter of the non-Clifford cnot-dihedral RB. * ``'alpha_err`` - the error of the alpha parameter of the non-Clifford cnot-dihedral RB. * ``epg_est`` - the estimated error per a CNOT-dihedral element. * ``epg_est_error`` - the estimated error derived from the params_err. """ self.rbfit_Z.fit_data() self.rbfit_X.fit_data() self._fit_cnotdihedral = [] for patt_ind, (_, qubits) in enumerate(zip(self._cliff_lengths, self._rb_pattern)): # calculate nrb=d=2^n: nrb = 2 ** len(qubits) # Calculate alpha_Z and alpha_R: alpha_Z = self.rbfit_Z.fit[patt_ind]['params'][1] alpha_R = self.rbfit_X.fit[patt_ind]['params'][1] # Calculate their errors: alpha_Z_err = self.rbfit_Z.fit[patt_ind]['params_err'][1] alpha_R_err = self.rbfit_X.fit[patt_ind]['params_err'][1] # Calculate alpha: alpha = (alpha_Z + nrb * alpha_R) / (nrb + 1) # Calculate alpha_err: alpha_Z_err_sq = (alpha_Z_err / alpha_Z / (nrb + 1)) ** 2 alpha_R_err_sq = (nrb * alpha_R_err / alpha_R / (nrb + 1)) ** 2 alpha_err = np.sqrt(alpha_Z_err_sq + alpha_R_err_sq) # Calculate epg_est: epg_est = (nrb - 1) * (1 - alpha) / nrb # Calculate epg_est_error epg_est_err = (nrb - 1) / nrb * alpha_err / alpha self._fit_cnotdihedral.append({'alpha': alpha, 'alpha_err': alpha_err, 'epg_est': epg_est, 'epg_est_err': epg_est_err})
[docs] def plot_rb_data(self, pattern_index=0, ax=None, add_label=True, show_plt=True): """ Plot non-Clifford cnot-dihedral randomized benchmarking data of a single pattern. Args: pattern_index (int): which RB pattern to plot. ax (Axes): plot axis (if passed in). add_label (bool): Add an EPG label. show_plt (bool): display the plot. Raises: ImportError: if matplotlib is not installed. """ if not HAS_MATPLOTLIB: raise ImportError('The function plot_interleaved_rb_data \ needs matplotlib. Run "pip install matplotlib" before.') if ax is None: plt.figure() ax = plt.gca() xdata = self._cliff_lengths[pattern_index] # Plot the original and interleaved result for each sequence for one_seed_data in self.raw_data[0][pattern_index]: ax.plot(xdata, one_seed_data, color='blue', linestyle='none', marker='x') for one_seed_data in self.raw_data[1][pattern_index]: ax.plot(xdata, one_seed_data, color='red', linestyle='none', marker='+') # Plot the fit rbfit_Z_function = self.rbfit_Z.rb_fit_fun rbfit_X_function = self.rbfit_X.rb_fit_fun ax.plot(xdata, rbfit_Z_function(xdata, *self.fit[0] [pattern_index]['params']), color='blue', linestyle='-', linewidth=2, label='Measure state |0...0>') ax.tick_params(labelsize=14) ax.plot(xdata, rbfit_X_function(xdata, *self.fit[1] [pattern_index]['params']), color='red', linestyle='-', linewidth=2, label='Measure state |+...+>') ax.tick_params(labelsize=14) ax.set_xlabel('CNOT-Dihedral Length', fontsize=16) ax.set_ylabel('Ground State Population', fontsize=16) ax.grid(True) ax.legend(loc='lower left') if add_label: bbox_props = dict(boxstyle="round,pad=0.3", fc="white", ec="black", lw=2) ax.text(0.6, 0.9, "alpha: %.3f(%.1e) EPG_est: %.3e(%.1e)" % (self._fit_cnotdihedral[pattern_index]['alpha'], self._fit_cnotdihedral[pattern_index]['alpha_err'], self._fit_cnotdihedral[pattern_index]['epg_est'], self._fit_cnotdihedral[pattern_index]['epg_est_err']), ha="center", va="center", size=14, bbox=bbox_props, transform=ax.transAxes) if show_plt: plt.show()