Circuit Lists Tutorial

This tutorial will show you how to create lists of Circuit objects. In some cases, we'll want to construct and use a list (or even a list of lists!) of Circuits, so we've dedicated this tutorial to talking about "circuit lists". We utilize the fact that Circuit objects behave and compose as tuples of layer-labels (see the Circuit tutorial).

In [ ]:
import pygsti
import as pc
In [ ]:
from pygsti.modelpacks import smq1Q_XY #a standard model, packaged with related information

List Construction Functions: and create_circuits

You'll often be working with entire lists of Circuit objects which define some part of the experiments utilized by algorithms such as Gate Set Tomography. pyGSTi provides several functions for constructing circuit lists, which we not demonstrate.

The workhorse function is, which executes its positional arguments within a nested loop given by iterable keyword arguments. That's a mouthful, so let's look at a few examples:

In [ ]:
As = [('a1',),('a2',)]
Bs = [('b1','b2'), ('b3','b4')]

def rep2(x):
    return x+x

list1 = pc.create_circuits("a", a=As)
list2 = pc.create_circuits("a+b", a=As, b=Bs, order=['a','b'])
list3 = pc.create_circuits("R(a)+c", a=As, c=[('c',)], R=rep2)

print("list1 = %s" % list(map(tuple, list1)))
print("list2 = %s" % list2)
print("list3 = %s" % list(map(str,list3)))

Many of the operation sequences used by Gate Set Tomography are composed of three parts. A "preparation fiducial" sequence is followed by a "repeated germ" sequence, which is followed by a "measurement fiducial" sequence. We won't get into why this structure is used, but simply use this fact to motivate looking at operation sequences of the form $f_1 + R(g) + f_2$, where the $f_1$ and $f_2$ fiducial sequences are simple short sequences are $R(g)$ is a possibly long sequence that is generated by repeating a short sequence $g$ called a "germ".

It is possible to generate "repeated germ" sequences in several ways using the functions . In modern GST, germ sequences are always repeated an integer number of times rather than being truncated to a precise length, so repeat_with_max_length is used instead of repeat_and_truncate. Below we demonstrate the use of these functions.

In [ ]:
print(pc.repeat_and_truncate(('A', 'B', 'C'), 5)) #args (x,N): repeat x until it is exactly length N

print(pc.repeat_with_max_length(('A', 'B', 'C'), 5)) #args (x,N): repeat x the maximum integer number of times so len(x) < N

print(pc.repeat_count_with_max_length(('A', 'B', 'C'), 5)) #args (x,N): the maximum integer number of times so len(x) < N

We can combine a repeated germ sequence between two fiducial sequences using create_circuits. This demonstrates the power of the create_circuits to perform nested loops. We also introduce the "bulk-conversion" function circuit_list, which creates a list of Circuit objects from a list of tuples.

In [ ]:
fids  = pc.to_circuits( [ ('Gf0',), ('Gf1',)    ] ) #fiducial strings
germs = pc.to_circuits( [ ('G0',), ('G1a','G1b')] ) #germ strings

circuits1 = pc.create_circuits("f0+germ*e+f1", f0=fids, f1=fids,
                                       germ=germs, e=2, order=["germ","f0","f1"])
print("circuits1 = \n", "\n".join(map(str,circuits1)),"\n")

circuits2 = pc.create_circuits("f0+T(germ,N)+f1", f0=fids, f1=fids,
                                        germ=germs, N=3, T=pc.repeat_and_truncate,

print("circuits2 = \n", "\n".join(map(str,circuits2)),"\n")

circuits3 = pc.create_circuits("f0+T(germ,N)+f1", f0=fids, f1=fids,
                                        germ=germs, N=3, T=pc.repeat_with_max_length,
print("circuits3 = \n", "\n".join(map(str,circuits3)), "\n")

In addition to create_circuits, the functions provide ways of constructing common operation sequence lists. The example below shows how to construct all possible operation sequences within a certain length range, as well as how to construct the set of operation sequences needed to run Linear LinearOperator Set Tomography given a set of fiducial strings.

In [ ]:
myGates = [ 'Gx', 'Gy' ]  #operation labels -- often just model.operations.keys()
allStringsInLengthRange = pc.list_all_circuits(myGates, minlength=0, maxlength=2)
print("\nAll strings using %s up to length 2 = \n" \
    % str(myGates), "\n".join(map(str,allStringsInLengthRange)))
In [ ]:
myFiducialList = pc.to_circuits([ ('Gf1',), ('Gf2',) ])  #list of fiducials

lgstStrings = pc.create_lgst_circuits(myFiducialList,myFiducialList,myGates)

print("\nLGST strings = \n","\n".join(map(str,lgstStrings)))

Manipulating Circuits

Sometimes it is useful to manipulate a circuits (or a list of them) via find & replace operations. The manipulate_circuit and manipulate_circuits functions take as input a set of replacement "rules" and process one or more circuits objects accordingly. For example, the rules

  • ab $\rightarrow$ AB' (if B follows A, prime B)
  • BA $\rightarrow$ B''A (if B precedes A, double-prime B)
  • CA $\rightarrow$ CA' (if A follows C, prime A)
  • BC $\rightarrow$ BC' (if C follows B, prime C)

are specified by the dictionary:

In [ ]:
sequenceRules = [
        (("A", "B"), ("A", "B'")),
        (("B", "A"), ("B''", "A")),
        (("C", "A"), ("C", "A'")),
        (("B", "C"), ("B", "C'"))]

Will produce the output:

  • BAB $\rightarrow$ B''AB'
  • ABA $\rightarrow$ AB'A (frustrated!)
  • CAB $\rightarrow$ CA'B'
  • ABC $\rightarrow$ AB'C'
In [ ]:
from pygsti.objects import Circuit
from import manipulate_circuit

print(manipulate_circuit(Circuit(tuple('BAB')), sequenceRules))
print(manipulate_circuit(Circuit(tuple('ABA')), sequenceRules))
print(manipulate_circuit(Circuit(tuple('CAB')), sequenceRules))
print(manipulate_circuit(Circuit(tuple('ABC')), sequenceRules))
In [ ]:
# You can also process an entire list of operation sequences in bulk
orig_lst =[ tuple('BAB'), tuple('ABA'), tuple('CAB'), tuple('ABC')])
lst =, sequenceRules)
print('\n'.join([str(s) for s in lst]))

Gate Label "Aliases"

A similar but simpler type of manipulation called "operation label aliasing" is used in pyGSTi to map a operation label into another operation label only for DataSet lookups. The mapping is similar to manipulate_circuit's find & replace functionality, except that (at least currently) the string to find can be only a single operation label (and so isn't even a string at all). The support for operation label aliasing within pyGSTi's algorithms aids in mapping many Model models onto the same data (often with simpler gate labelling).

In [ ]:
#TODO: remove Aliasing or provide examples?