Gate String Tutorial

This tutorial will show you how to create and use GateString objects, which represent an ordered sequence (or "string") of gate set operations. In most cases, a GateString may optionally begin with a state-preparation label and end with a POVM label, and contains gate labels as all its other elements. In almost all cases, you'll be using a list (or even a list of lists!) of GateStrings, so we'll often be talking about "gate string lists".

A GateString object is nearly identical, and sometimes interchangeable, with a Python tuple of gate labels (i.e. the names beginning with G that label the gate operations in a GateSet) or instrument labels (beginning with I), sometimes sandwiched between a state preparation and POVM (measurement) label. GateStrings can be accessed and operated upon just as a standard Python tuple. The primary difference between a GateString and a tuple is that a GateString also contains a "string representation" of the gate sequence. This string representation gets carried along for the ride until it's needed, typically when writing a the gate string to a file. The string representation must evaluate, using pyGSTi's allowed text format for gate strings (see below), to the tuple-of-gate-labels, or "tuple representation". The string representation is intended to contain a compact and intuitive human-readable form of the gate sequence that is used for display purposes. For example, the gate string ('Gx','Gx','Gx','Gx','Gx') might have the string representation "Gx^5". If needed, the tuple and string representations of any GateString g can be accessed via g.tup or tuple(g) and g.str or str(g) respectively.

Gate strings are central to Gate Set Tomography, as they describe both real and "simulated" experiments. A GateString's ordered sequence tells the experimentalist which gates they must execute on their hardware and likewise what order to compose (i.e. multiply) the gate matrices contained in a GateSet. When a state preparation or POVM label is omitted from the beginning or end of a gate string, respectively, one must be inferred. This is currently only possible when the relevant GateSet object holds only a single state preparation or POVM. The outcomes of an experiment are labeled by 1-tuples of the given (or inferred) POVM effect labels. When the gate string contains instruments (which produce intermediate measurement outcomes), the "outcome label" is a tuple of length greater than one, where each element correponds to the outcome of a single instrument or the final POVM. Thus, by repeating an experiment one obtains counts and thereby frequencies for each outcome label. Given a GateSet one can obtain corresponding probabilities by muliplying gate matrices and contracting the product between a state preparation and POVM effect vector.

The ordering direction is important. The elements of a GateString are read from left-to-right, meaning the first (left-most) gate label is performed first. This is very natural for experiments since one can read the gate string as a script, executing each gate as one reads from left to right. However, since we insist on "normal" matrix multiplication conventions, the ordering of the matrix product is reversed from that of the gate string. For example, the gate string ('Ga','Gb','Gc'), in which Ga is performed first, corresponds to the matrix product $G_c G_b G_a$. The probability of this gate string for a SPAM label associated with the (column) vectors ($\rho_0$,$E_0$) is given by $E_0^T G_c G_b G_a \rho_0$, which can be interpreted as "prepare state 0 first, then apply gate A, then B, then C, and finally measure effect 0". While this nuance is typically hidden from the user (the GateSet functions which compute products and probabilities from GateStrings perform the order reversal internally), it becomes very important if you plan to perform such products by hand.

We'll now go over some examples of how to create and use a single GateString.

In [1]:
from __future__ import print_function
In [2]:
import pygsti # the main pyGSTi module
import pygsti.construction as pc  #shorthand
from pygsti.construction import std1Q_XY #a standard gateset & peripherals

A simple example: the single GateString

The cell below show how to create a GateString object from a tuple, optionally with a corresponding string representation. It demonstrates how to access the tuple and string representations directly, and the tuple-like operations that can be performed on a GateString.

In [3]:
#Construction of a GateString
s1 = pygsti.objects.GateString( ('Gx','Gx') ) # from a tuple
s2 = pygsti.objects.GateString( ('Gx','Gx'), "Gx^2" ) # from tuple and string representations (must match!)
s3 = pygsti.objects.GateString( None, "Gx^2" ) # from just a string representation

#All of these are equivalent (even though their string representations aren't -- only tuples are compared)
assert(s1 == s2 == s3)

#Printing displays the string representation
print("Printing")
print("s1 = %s" % s1)
print("s2 = %s" % s2)
print("s3 = %s" % s3, end='\n\n')

#Casting to tuple displays the tuple representation
print("Printing tuple(.)")
print("s1 =", tuple(s1))
print("s2 =", tuple(s2))
print("s3 =", tuple(s3), end='\n\n')

#Access to tuple or string representation directly:
print("s1.tup =", s1.tup, ",  s1.str = ", s1.str)
print("tuple(s1) =", tuple(s1), ",  str(s1) = ", str(s1), end='\n\n')

#Operations
assert(s1 == ('Gx','Gx')) #can compare with tuples
s4 = s1+s2 #addition (note this concatenates string reps)
s5 = s1*3  #integer-multplication (note this exponentiates in string rep)
print("s1 + s2 = ",s4, ", tuple = ", tuple(s4))
print("s1*3    = ",s5, ", tuple = ", tuple(s5), end='\n\n')
Printing
s1 = GxGx
s2 = Gx^2
s3 = Gx^2

Printing tuple(.)
s1 = ('Gx', 'Gx')
s2 = ('Gx', 'Gx')
s3 = ('Gx', 'Gx')

s1.tup = ('Gx', 'Gx') ,  s1.str =  GxGx
tuple(s1) = ('Gx', 'Gx') ,  str(s1) =  GxGx

s1 + s2 =  GxGxGx^2 , tuple =  ('Gx', 'Gx', 'Gx', 'Gx')
s1*3    =  (GxGx)^3 , tuple =  ('Gx', 'Gx', 'Gx', 'Gx', 'Gx', 'Gx')

List Construction Functions: pygsti.construction and create_gatestring_list

Usually you'll be working with entire lists of GateString objects which define some part of the experiments utilized by Gate Set Tomography. pyGSTi provides several functions for constructing gate string lists, which we not demonstrate.

The workhorse function is pygsti.construction.create_gatestring_list, 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 [4]:
As = [('a1',),('a2',)]
Bs = [('b1','b2'), ('b3','b4')]

def rep2(x):
    return x+x

list1 = pc.create_gatestring_list("a", a=As)
list2 = pc.create_gatestring_list("a+b", a=As, b=Bs, order=['a','b'])
list3 = pc.create_gatestring_list("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)))
list1 = [('a1',), ('a2',)]
list2 = [GateString(a1b1b2), GateString(a1b3b4), GateString(a2b1b2), GateString(a2b3b4)]
list3 = ['a1a1c', 'a2a2c']

Many of the gate 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 gate strings 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 pygsti.construction.repeat_xxx . 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 [5]:
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
('A', 'B', 'C', 'A', 'B')
('A', 'B', 'C')
1

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

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

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

gateStrings2 = pc.create_gatestring_list("f0+T(germ,N)+f1", f0=fids, f1=fids,
                                        germ=germs, N=3, T=pc.repeat_and_truncate,
                                        order=["germ","f0","f1"])

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

gateStrings3 = pc.create_gatestring_list("f0+T(germ,N)+f1", f0=fids, f1=fids,
                                        germ=germs, N=3, T=pc.repeat_with_max_length,
                                        order=["germ","f0","f1"])
print("gateStrings3 = \n", "\n".join(map(str,gateStrings3)), "\n")
gateStrings1 = 
 Gf0(G0)^2Gf0
Gf0(G0)^2Gf1
Gf1(G0)^2Gf0
Gf1(G0)^2Gf1
Gf0(G1aG1b)^2Gf0
Gf0(G1aG1b)^2Gf1
Gf1(G1aG1b)^2Gf0
Gf1(G1aG1b)^2Gf1 

gateStrings2 = 
 Gf0G0G0G0Gf0
Gf0G0G0G0Gf1
Gf1G0G0G0Gf0
Gf1G0G0G0Gf1
Gf0G1aG1bG1aGf0
Gf0G1aG1bG1aGf1
Gf1G1aG1bG1aGf0
Gf1G1aG1bG1aGf1 

gateStrings3 = 
 Gf0(G0)^3Gf0
Gf0(G0)^3Gf1
Gf1(G0)^3Gf0
Gf1(G0)^3Gf1
Gf0(G1aG1b)Gf0
Gf0(G1aG1b)Gf1
Gf1(G1aG1b)Gf0
Gf1(G1aG1b)Gf1 

In addition to create_gatestring_list, the pygsti.construction.list_xxx functions provide ways of constructing common gate string lists. The example below shows how to construct all possible gate strings within a certain length range, as well as how to construct the set of gate strings needed to run Linear Gate Set Tomography given a set of fiducial strings.

In [7]:
myGates = [ 'Gx', 'Gy' ]  #gate labels -- often just gateset.gates.keys()
allStringsInLengthRange = pc.list_all_gatestrings(myGates, minlength=0, maxlength=2)
print("\nAll strings using %s up to length 2 = \n" \
    % str(myGates), "\n".join(map(str,allStringsInLengthRange)))
All strings using ['Gx', 'Gy'] up to length 2 = 
 {}
Gx
Gy
GxGx
GxGy
GyGx
GyGy
In [8]:
myFiducialList = pc.gatestring_list([ ('Gf1',), ('Gf2',) ])  #list of fiducials

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

print("\nLGST strings = \n","\n".join(map(str,lgstStrings)))
LGST strings = 
 Gf1
Gf2
Gf1Gf1
Gf1Gf2
Gf2Gf1
Gf2Gf2
Gf1(Gx)Gf1
Gf1(Gx)Gf2
Gf2(Gx)Gf1
Gf2(Gx)Gf2
Gf1(Gy)Gf1
Gf1(Gy)Gf2
Gf2(Gy)Gf1
Gf2(Gy)Gf2

Manipulating GateStrings

Sometimes it is useful to manipulate a GateString (or a list of them) via find & replace operations. The manipulate_gatestring and manipulate_gatestring_list functions take as input a set of replacement "rules" and process one or more GateString 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 [9]:
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 [10]:
from pygsti.objects import GateString
from pygsti.construction import manipulate_gatestring

print(manipulate_gatestring(GateString(tuple('BAB')), sequenceRules))
print(manipulate_gatestring(GateString(tuple('ABA')), sequenceRules))
print(manipulate_gatestring(GateString(tuple('CAB')), sequenceRules))
print(manipulate_gatestring(GateString(tuple('ABC')), sequenceRules))
B''AB'
AB'A
CA'B'
AB'C'
In [11]:
# You can also process an entire list of gate strings in bulk
orig_lst = pygsti.construction.gatestring_list([ tuple('BAB'), tuple('ABA'), tuple('CAB'), tuple('ABC')])
lst = pygsti.construction.manipulate_gatestring_list(orig_lst, sequenceRules)
print('\n'.join([str(s) for s in lst]))
B''AB'
AB'A
CA'B'
AB'C'

Gate Label "Aliases"

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

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