Running QCVV Protocols

The main purpose of pyGSTi is to implement QCVV techniques that analyze some, often specific, experimental data to learn about a quantum processor. Each such technique is called a "protocol" in pyGSTi, and this term roughly corresponds to its use in the literature. To run a protocol on some data in pyGSTi, here's the general workflow:

  1. Create an ExperimentDesign object. An "experiment design" is just a description of a set of experiments. It tells you what circuits you need to run on your quantum processor. Most protocols require a certain structure to their circuits, or knowledge that their circuits have been drawn from a certain distribution, and thus have a corresponding experiment design type suited to them, e.g. StandardGSTDesign and CliffordRBDesign.

  2. Run or simulate the circuits demanded by the experiment design, and package the resulting data counts with the experiment design in a ProtocolData object. This object constitutes the input to a protocol.

  3. Create a Protocol object, usually corresponding to the type of experiment design, and pass your data object to its run() method. The result is a ProtocolResults object containing the results of running the protocol along with all the inputs that went in to generating those results.

  4. Look at the results object. It can be used to make plots, generate reports, etc.

Below are examples of how to run some of the protocols within pyGSTi. You'll notice how, for the most part, they follow the same pattern given above. Here's a list of the protocols for easy reference:


We'll begin by setting up a Workspace so we can display pretty interactive figures inline (see the intro to Workspaces tutorial for more details) within this notebook.

In [ ]:
import pygsti
import numpy as np
ws =

Gate set tomography

The most common reason to use pyGSTi is to run gate set tomography (GST). The GST protocol uses sets of periodic circuits to probe, to a precision linear in the maximum-circuit length (i.e. depth), the gates on one or more of the qubits in a quantum processor. For common gate sets with "model packs" that are built into pyGSTi, you only need to specify the maximum circuit length to construct an experiment design for GST. In the example below, data is then simulated using a model with simple depolarizing errors. The standard GST protocol is then run to produce a results object and then generate a report. To learn more about GST, see the GST Overview and GST Protocols tutorials.

In [ ]:
from pygsti.modelpacks import smq1Q_XYI

# get experiment design
exp_design = smq1Q_XYI.get_gst_experiment_design(max_max_length=32)  

# write an empty data object (creates a template to fill in), 'tutorial_files/test_gst_dir', clobber_ok=True)

# fill in the template with simulated data (you would run the experiment and use actual data)
    smq1Q_XYI.target_model().depolarize(op_noise=0.01, spam_noise=0.001),
    "tutorial_files/test_gst_dir/data/dataset.txt", num_samples=1000, seed=1234)

# load the data object back in, now with the experimental data
data ='tutorial_files/test_gst_dir')

# run the GST protocol on the data 
results = pygsti.protocols.StandardGST().run(data)

# create a report
report =
    results, title="GST Overview Tutorial Example Report")

Randomized benchmarking

Randomized benchmarking (RB) can be used to estimate the average per-Clifford error rate by fitting a simple curve to the data from randomized circuits of different depths. To create the experiment design, the user specifies a ProcessorSpec object that describes the quantum processor (see XXX), the depths (in number of Clifford gates) to use, and the number of circuits at each depth. The results from running the protocol are then used to create a plot of the RB decay curve along with the data. For more information, see the RB Overview tutorial.

In [ ]:
# define the quantum processor (or piece of a processor) we'll be doing RB on
qubit_labels = ['Q1']
gate_names = ['Gxpi2', 'Gxmpi2', 'Gypi2', 'Gympi2']
pspec = pygsti.obj.ProcessorSpec(len(qubit_labels), gate_names,
                 qubit_labels=qubit_labels, construct_models=('target','clifford','TP'))
depths = [0, 10, 20, 30]
circuits_per_depth = 50

# create an experiment design
exp_design = pygsti.protocols.CliffordRBDesign(pspec, depths, circuits_per_depth)

# write an empty data object (creates a template to fill in), 'tutorial_files/test_rb_dir', clobber_ok=True)

# fill in the template with simulated data (you would run the experiment and use actual data)
mdl_datagen = pspec.models['TP']
for gate in mdl_datagen.operation_blks['gates'].values():
    gate.depolarize(0.01)  # add depolarizing noise to all the gates in the processor
    mdl_datagen, "tutorial_files/test_rb_dir/data/dataset.txt", num_samples=1000, seed=1234)

# load the data object back in, now with the experimental data
data ='tutorial_files/test_rb_dir')

#Run RB protocol                                                                                                                                                                                     
proto = pygsti.protocols.RB()
rbresults =

#Create a RB plot

Model testing (see whether data agrees with a model)

GST fits a parameterized model to a data set (see above), which can require many circuits to be run, and take a long time to analyze. It is also possible to create a model in pyGSTi and test how well that model fits a set of data. The circuits used to make this comparison don't need to have any special structure, and the time required to perform the analysis it greatly reduce. In the example below we create a simple 2-qubit model and test it against the output of five hand-selected sequences. For more information, see the model testing tutorial.

In [ ]:
# create a dataset file that is just a list of circuits run and their outcomes
dataset_txt = \
"""## Columns = 00 count, 01 count, 10 count, 11 count
{}@(0,1)            100   0   0   0
Gx:[email protected](0,1)           55   5  40   0
Gx:0Gy:[email protected](0,1)       20  27  23  30
Gx:0^[email protected](0,1)         85   3  10   2
Gx:0Gcnot:0:[email protected](0,1)  45   1   4  50
with open("tutorial_files/Example_Short_Dataset.txt","w") as f:
ds ="tutorial_files/Example_Short_Dataset.txt")

# package the dataset into a data object, using the default experiment design
#  that has essentially no structure.
data = pygsti.protocols.ProtocolData(None, ds)

# create a model to test
mdl_to_test =,1),
    [(),      ('Gx',0),    ('Gy',0),    ('Gx',1),    ('Gy',1),    ('Gcnot',0,1)],
    ["I(0,1)","X(pi/2,0)", "Y(pi/2,0)", "X(pi/2,1)", "Y(pi/2,1)", "CNOT(0,1)"])
mdl_to_test = mdl_to_test.depolarize(op_noise=0.01)  # a guess at what the noise is...

# run the ModelTest protocol on the data
proto = pygsti.protocols.ModelTest(mdl_to_test)
results =

print("Number of std-devaiation away from expected = ", results.estimates['ModelTest'].misfit_sigma())


Robust Phase Estimation (RPE) determines the phase of a target gate U by iterated action of that gate on a superposition of eigenstates of U. Upwards of 30% error in counts can be tolerated, due to any cause (e.g., statistical noise or calibration error). In this example, the π/2 phase of an X rotation is determined using only noisy X_pi/2 gates (and SPAM).

Additional modelpacks for characterizing different phases of different gates will be made available in the future. In the meantime, if you wish to characterize, using RPE, the phase of a gate that is neither X_pi/2 nor Y_pi/2, please email either [email protected] or [email protected]

In [ ]:
# An experiment design
from pygsti.modelpacks import smq1Q_Xpi2_rpe, smq1Q_XYI
exp_design = smq1Q_Xpi2_rpe.get_rpe_experiment_design(max_max_length=64)

# write an empty data object (creates a template to fill in), 'tutorial_files/test_rpe_dir', clobber_ok=True)

# fill in the template with simulated data (you would run the experiment and use actual data)
    smq1Q_XYI.target_model().depolarize(op_noise=0.01, spam_noise=0.1),
    "tutorial_files/test_rpe_dir/data/dataset.txt", num_samples=1000, seed=1234)

# load the data object back in, now with the experimental data
data ='tutorial_files/test_rpe_dir/')

# Run the RPE Protocol
results = pygsti.protocols.rpe.RobustPhaseEstimation().run(data)


Drift Characterization

Time-series data can be analyzed for significant indications of drift (time variance in circuit outcome probabilities). See the tutorial on drift characterization for more details.

In [ ]:
from pygsti.modelpacks import smq1Q_XYI
exp_design = smq1Q_XYI.get_gst_experiment_design(max_max_length=4), 'tutorial_files/test_drift_dir', clobber_ok=True)

# Simulate time dependent data (right now, this just uses a time-independent model so this is uninteresting)                                                       
datagen_model = smq1Q_XYI.target_model().depolarize(op_noise=0.05, spam_noise=0.1)
datagen_model.sim = "map" # only map-type can generate time-dep data
                          # can also construct this as target_model(simulator="map") above, 'tutorial_files/test_drift_dir/data/dataset.txt',
                                               num_samples=10, seed=2020, times=range(10))

gst_data ='tutorial_files/test_drift_dir')
stability_protocol = pygsti.protocols.StabilityAnalysis()
results =

report =, title='Demo Drift Report')

What's next?

This concludes our overview of how to use Protocol objects in pyGSTi. This high-level API is relatively new to pyGSTi, and there's more functionality available from the lower level API described in the using essential objects tutorial. If there's something you want to do with pyGSTi that isn't covered here, you should take a look through the table of contents there.