Gate Sets Tutorial

This tutorial will show you how to create and use GateSet objects. GateSet objects are fundamental to pyGSTi, as each represents a set of quantum gates along with state preparation and measurement (i.e. POVM) operations. In pyGSTi, a "state space" refers to a Hilbert space of pure quantum states (often thought of as length-$d$ vectors, where $d=2^N$ for $N$ qubits). A "density matrix space" refers to a Hilbert space of density matrices, which while often thought of as $d \times d$ matrices can also be represented by length $d^2$ vectors. Mathematically, these vectors live in Hilbert-Schmidt space, the space of linear operators on the original $d\times d$ density matrix space. pyGSTi uses this Hilbert-Schmidt "Liouville" vector-representation for density matrices and POVM effects, since this allows quantum gates to be represented by $d^2 \times d^2$ matrices which act on Hilbert-Schmidt vectors.

GateSet objects have the look and feel of Python dictionaries which hold $d^2\times d^2$ gate matrices, length-$d^2$ state preparation vectors, and sets of length-$d^2$ effect vectors which encode positive operator value measures (POVMs). State preparation and POVM effect vectors are both generically referred to as "SPAM" (state preparation and measurement) vectors.

In [1]:
#Make print statements compatible with Python 2 and 3
from __future__ import print_function

import pygsti
import pygsti.construction as pc

Creating gate sets

There are more or less three ways to create GateSet objects in pyGSTi:

  • By creating an empty GateSet and setting its elements directly, possibly with the help of pygsti.construction's build_gate and build_vector functions.
  • By a single call to build_gateset, which automates the above approach.
  • By loading from a text-format gateset file using pygsti.io.load_gateset.

Creating a GateSet from scratch

Gates and SPAM vectors can be assigned to a GateSet object as to an ordinary python dictionary. Internally a GateSet holds these quantities as Gate- and SPAMVec- and POVM-derived objects, but you may assign lists, Numpy arrays, or other types of Python iterables to a GateSet key and a conversion will be performed automatically. To keep gates, state preparations, and POVMs separate, the GateSet object looks at the beginning of the dictionary key being assigned: keys beginning with rho, M, and G are categorized as state preparations, POVMs, and gates, respectively. To avoid ambiguity, each key must begin with one of these three prefixes.

To separately access the state preparations, POVMs, and gates contained in a GateSet use the preps, povms, and gates members respectively. Each one provides dictionary-like access to the underlying objects. For example, myGateset.gates['Gx'] accesses the same underlying Gate object as myGateset['Gx'], and similarly for myGateset.preps['rho0'] and myGateset['rho0']. The values of gates and state preparation vectors can be read and written in this way.

A POVM object acts similarly to dictionary of SPAMVec-derived effect vectors, but typically requires all such vectors to be initialized at once, that is, you cannot assign individual effect vectors to a POVM. The string-valued keys of a POVM label the outcome associated with each effect vector, and are therefore termed "effect labels" or "outcome labels". The outcome labels also designate data within a DataSet object (see later tutorial), and thereby associate modeled POVMs with experimental measurements.

Lastly, a basis may be specified (by giving a name and dimension, or by directly supplying a pygsti.tools.Basis object) which allows one to conveniently track how the gate matrices and SPAM vectors in a GateSet should be interpreted in post processing. In the case of a single qubit, the basis of Pauli matrices plus the identity is the natural basis. PyGSTi contains built-in support for bases consisting of the tensor product of Pauli matrices (or just the Pauli matrices in the case of 1 qubit), labelled "pp", as well as the Gell-Mann matrices, labelled "gm". It also contains a special "qutrit" basis, labelled "qt", for the case of 3-level quantum systems. In cases when there are an integral number of qubits, and the dimension equals $2^N$, the "pp" basis is usually preferred since it is more intuitive. In other cases, where the Hilbert space includes non-qubit (e.g. environmental) degrees of freedom, the Gell-Mann basis may be useful since it can be used in any dimension. Note that both the Gell-Mann and Pauli-Product bases reduce to the usual Pauli matrices plus identity in when the dimension equals 2 (1 qubit). Also note that tracking the basis doesn't affect the main GST optimization; only some post-processing tasks (e.g. tables in the reports) depend on the basis associated with a GateSet.

The below cell illustrates how to create a GateSet from scratch.

In [2]:
from math import sqrt
import numpy as np

#Initialize an empty GateSet object
gateset1 = pygsti.objects.GateSet()

#Populate the GateSet object with states, effects, gates,
# all in the *normalized* Pauli basis: { I/sqrt(2), X/sqrt(2), Y/sqrt(2), Z/sqrt(2) }
# where I, X, Y, and Z are the standard Pauli matrices.
gateset1['rho0'] = [ 1/sqrt(2), 0, 0, 1/sqrt(2) ] # density matrix [[1, 0], [0, 0]] in Pauli basis
gateset1['Mdefault'] = pygsti.objects.UnconstrainedPOVM(
    {'0': [ 1/sqrt(2), 0, 0, 1/sqrt(2) ],   # projector onto [[1, 0], [0, 0]] in Pauli basis
     '1': [ 1/sqrt(2), 0, 0, -1/sqrt(2) ] }) # projector onto [[0, 0], [0, 1]] in Pauli basis

gateset1['Gi'] = np.identity(4,'d') # 4x4 identity matrix
gateset1['Gx'] = [[1, 0, 0, 0],
                  [0, 1, 0, 0],
                  [0, 0, 0,-1],
                  [0, 0, 1, 0]] # pi/2 X-rotation in Pauli basis

gateset1['Gy'] = [[1, 0, 0, 0],
                  [0, 0, 0, 1],
                  [0, 0, 1, 0],
                  [0,-1, 0, 0]] # pi/2 Y-rotation in Pauli basis

#Designate the basis being used for the matrices and vectors above 
# (the "Pauli product" basis of dimension 2 - i.e. the four 2x2 Pauli matrices I,X,Y,Z)
gateset1.basis = pygsti.tools.Basis("pp",2)

pygsti.io.write_gateset(gateset1, "tutorial_files/Example_gatesetFromScratch.txt", title="My Gateset")

Check out the gate set file that was written here.

Creating a GateSet from scratch using build_gate and build_vector

The build_gate and build_vector functions take a human-readable string representation of a gate or SPAM vector, and return a Gate or SPAMVector object that gets stored in a dictionary-like GateSet or POVM object. To use these functions, you must specify what state space you're working with, and the basis for that space. These elements are encapsulated by a Basis object, which is created a via two quantities:

  1. State space dimensions: a list of integers specifying the dimension of each block in a direct-sum decomposition of the total state space. For example, [2] means just a 2-dimensional Hilbert space, and [2,2] means the direct sum of two 2-dimensional Hilbert spaces.
  2. State space labels: a list of tuples of (string) labels. Each tuple describes how to label the corresponding term of the direct-sum decomposition of the state space. Thus, the length of the state-space-labels list must be equal to the length of the state-space-dimensions list. The elements of a tuple must be strings that start with either "Q" or "L", and are followed by any letters or numbers of your choosing. A label beginning with "Q" denotes a 2-dimensional space, whereas a label beginning with "L" denotes a 1-dimensional space. The tuple itself represents a tensor product of the spaces denoted by it's elements, and so describes how to interpret a given dimension Hilbert space as the tensor product of 1- and 2-dimensional spaces. For example, the tuple ('Q0',) describes a 2-dimensional Hilbert space as that of a single qubit, and the tuple ('Q0','Q1') describes a 4-dimensional Hilbert space as that of two qubit spaces tensored together. Each tuple describes a single Hilbert-space term in the direct-sum decomposition of the entire Hilbert space, so the list [('Q0','Q1'),('L0',)] represents a Hilbert space that is the direct sum of a 4-dimensional and a 1-dimensional space; the 4-dimensional space is the a tensor product of two qubit spaces labelled 'Q0' and 'Q1' while the 1-dimensional space is labeled 'L0'. (In this case, the corresponding state space dimensions list must be [4,1], and is required as an argument to build_vector and build_gate just as a consistency check.)

While specifying the state space in this way can seem overly cumbersome for small Hilbert spaces, it allows for great flexibility when moving to more complex spaces. It is worthwhile to note that the state space labels described above are only used when interpreting the human-readable string used to specify gates and SPAM vectors in calls to build_gate (and in future versions of pyGSTi) build_vector, respectively.

build_vector currently only understands strings which are integers (e.g. "1"), for which it creates a vector performing state preparation of (or, equivalently, a state projection onto) the $i^{th}$ state of the Hilbert space, that is, the state corresponding to the $i^{th}$ row and column of the $d\times d$ density matrix.

build_gate accepts a wider range of descriptor strings, which take the form of functionName(args) and include:

  • I(label0, label1, ...) : the identity on the spaces labeled by label0, label1, etc.
  • X(theta,Qlabel), Y(theta,Qlabel), Z(theta,Qlabel) : single qubit X-, Y-, and Z-axis rotations by angle theta (in radians) on the qubit labeled by Qlabel. Note that pi can be used within an expression for theta, e.g. X(pi/2,Q0).
  • CX(theta, Qlabel1, Qlabel2), CY(theta, Qlabel1, Qlabel2), CZ(theta, Qlabel1, Qlabel2) : two-qubit controlled rotations by angle theta (in radians) on qubits Qlabel1 (the control) and Qlabel2 (the target).
In [3]:
#Specify the state space
stateSpace = [2] # Hilbert space has dimension 2; density matrix is a 2x2 matrix
spaceLabels = [('Q0',)] #interpret the 2x2 density matrix as a single qubit named 'Q0'
basis = pygsti.objects.Basis('pp', stateSpace)

#Initialize an empty GateSet object
gateset2 = pygsti.objects.GateSet()

#Populate the GateSet object with states, effects, and gates using 
# build_vector, build_gate, and build_identity_vec.   
gateset2['rho0'] = pc.basis_build_vector("0",basis)
gateset2['Mdefault'] = pygsti.objects.UnconstrainedPOVM(
    { '0': pc.basis_build_vector("0",basis),
      '1': pc.basis_build_vector("1",basis) } )
gateset2['Gi'] = pc.basis_build_gate(spaceLabels,"I(Q0)",basis)
gateset2['Gx'] = pc.basis_build_gate(spaceLabels,"X(pi/2,Q0)",basis)
gateset2['Gy'] = pc.basis_build_gate(spaceLabels,"Y(pi/2,Q0)",basis)

Create a GateSet in a single call to build_gateset

The approach illustrated above using calls to build_vector and build_gate can be performed in a single call to build_gateset. You will notice that all of the arguments to build_gateset corrspond to those used to construct a gate set using build_vector and build_gate; the build_gateset function is merely a convenience function which allows you to specify everything at once. These arguments are:

  • Args 1 & 2 : the state-space-dimensions and state-space-labels, as described above.
  • Args 3 & 4 : list-of-gate-labels, list-of-gate-expressions (labels must begin with 'G'; "expressions" being the descriptor strings passed to build_gate)
  • Args 5 & 6 : list-of-prep-labels, list-of-prep-expressions (labels must begin with 'rho'; "expressions" being the descriptor strings passed to build_vector)
  • Args 7 & 8 : list-of-effect-labels, list-of-effect-expressions (labels can be anything; "expressions" being the descriptor strings passed to build_vector). These effect vectors will comprise a single POVM named "Mdefault" by default, but which can be changed via the povmLabels argument (see doc string for details).

The optional argument basis can be set to any of the known built-in basis names (e.g. "gm", "pp", "qt", or "std") to select the basis for the gate matrices and SPAM vectors. By default, "pp" is used when possible (if the state space corresponds to an integer number of qubits), "qt" if the state space has dimension 3, and "gm" otherwise. The optional argument parameterization is used to specify the parameterization used for the created gates (see below).

In [4]:
gateset3 = pc.build_gateset( [2], [('Q0',)],
                             ['Gi','Gx','Gy'], [ "I(Q0)","X(pi/2,Q0)", "Y(pi/2,Q0)"],
                             prepLabels = ['rho0'], prepExpressions=["0"], 
                             effectLabels = ['0','1'], effectExpressions=["0","1"] ) 

In this case, the parameters to build_gateset, specify:

  • The state space is dimension 2 (i.e. the density matrix is 2x2)

  • interpret this 2-dimensional space as that of a single qubit labeled "Q0" (label must begin with 'Q')

  • there are three gates: Idle, $\pi/2$ x-rotation, $\pi/2$ y-rotation, labeled Gi, Gx, and Gy.

  • there is one state prep operation, labeled rho0, which prepares the 0-state (the first basis element of the 2D state space)

  • there is one POVM (~ measurement), named Mdefault with two effect vectors: '0' projects onto the 0-state (the first basis element of the 2D state space) and '1' projects onto the 1-state.

Note that by default, there is a single state prep, "rho0", that prepares the 0-state and a single POVM, "Mdefault", which consists of projectors onto each standard basis state that are labelled by their integer indices (so just '0' and '1' in the case of 1-qubit). Thus, all but the first four arguments used above just specify the default behavior and can be omitted:

In [5]:
gateset4 = pc.build_gateset( [2], [('Q0',)],
                            ['Gi','Gx','Gy'], [ "I(Q0)","X(pi/2,Q0)", "Y(pi/2,Q0)"] )

Load a GateSet from a file

You can also construct a GateSet object from a file using pygsti.io.load_gateset. The format of the text file should be fairly self-evident given the above discussion. Note that vector and matrix elements need not be simple numbers, but can be any mathematical expression parseable by the Python interpreter, and in addition to numbers can include "sqrt" and "pi".

In [6]:
#3) Write a text-format gateset file and read it in.
gateset5_txt = \
"""
# Example text file describing a gateset

PREP: rho0
LiouvilleVec
1/sqrt(2) 0 0 1/sqrt(2)

POVM: Mdefault

EFFECT: 0
LiouvilleVec
1/sqrt(2) 0 0 1/sqrt(2)

EFFECT: 1
LiouvilleVec
1/sqrt(2) 0 0 -1/sqrt(2)

END POVM

GATE: Gi
LiouvilleMx
1 0 0 0
0 1 0 0
0 0 1 0
0 0 0 1

GATE: Gx
LiouvilleMx
1 0 0 0
0 1 0 0
0 0 0 -1
0 0 1 0

GATE: Gy
LiouvilleMx
1 0 0 0
0 0 0 1
0 0 1 0
0 -1 0 0


BASIS: pp 2
"""
with open("tutorial_files/Example_Gateset.txt","w") as gsetfile:
    gsetfile.write(gateset5_txt)

gateset5 = pygsti.io.load_gateset("tutorial_files/Example_Gateset.txt")
In [7]:
#All four of the above gatesets are identical.  See this by taking the frobenius differences between them:
assert(gateset1.frobeniusdist(gateset2) < 1e-8)
assert(gateset1.frobeniusdist(gateset3) < 1e-8)
assert(gateset1.frobeniusdist(gateset4) < 1e-8)
assert(gateset1.frobeniusdist(gateset5) < 1e-8)

Viewing gate sets

In the cells below, we demonstrate how to print and access information within a GateSet.

In [8]:
#Printing the contents of a GateSet is easy
print("Gateset 1:\n", gateset1)
Gateset 1:
 rho0 =    0.7071        0        0   0.7071


Mdefault = Unconstrained POVM with effect vectors:
0:
 0.71
   0
   0
 0.71

1:
 0.71
   0
   0
-0.71



Gi = 
   1.0000        0        0        0
        0   1.0000        0        0
        0        0   1.0000        0
        0        0        0   1.0000


Gx = 
   1.0000        0        0        0
        0   1.0000        0        0
        0        0        0  -1.0000
        0        0   1.0000        0


Gy = 
   1.0000        0        0        0
        0        0        0   1.0000
        0        0   1.0000        0
        0  -1.0000        0        0




In [9]:
#You can also access individual gates like they're numpy arrays:
Gx = gateset1['Gx'] # a Gate object, but behaves like a numpy array

#By printing a gate, you can see that it's not just a numpy array
print("Gx = ", Gx)

#But can be accessed as one:
print("Array-like printout\n", Gx[:,:],"\n")
print("First row\n", Gx[0,:],"\n")
print("Element [2,3] = ",Gx[2,3], "\n")

Id = np.identity(4,'d')
Id_dot_Gx = np.dot(Id,Gx)
print("Id_dot_Gx\n", Id_dot_Gx, "\n")
Gx =  Fully Parameterized gate with shape (4, 4)
 1.00   0   0   0
   0 1.00   0   0
   0   0   0-1.00
   0   0 1.00   0

Array-like printout
 [[ 1.  0.  0.  0.]
 [ 0.  1.  0.  0.]
 [ 0.  0.  0. -1.]
 [ 0.  0.  1.  0.]] 

First row
 [1. 0. 0. 0.] 

Element [2,3] =  -1.0 

Id_dot_Gx
 [[ 1.  0.  0.  0.]
 [ 0.  1.  0.  0.]
 [ 0.  0.  0. -1.]
 [ 0.  0.  1.  0.]] 

Basic Operations with Gatesets

GateSet objects have a number of methods that support a variety of operations, including:

  • Depolarizing or rotating every gate
  • Writing the gate set to a file
  • Computing products of gate matrices
  • Printing more information about the gate set
In [10]:
#Add 10% depolarization noise to the gates
depol_gateset3 = gateset3.depolarize(gate_noise=0.1)

#Add a Y-axis rotation uniformly to all the gates
rot_gateset3 = gateset3.rotate(rotate=(0,0.1,0))
In [11]:
#Writing a gateset as a text file
pygsti.io.write_gateset(depol_gateset3, "tutorial_files/Example_depolarizedGateset.txt", title="My Gateset")
In [12]:
#Computing the product of gate matrices (more on this in the next tutorial on gate strings)
print("Product of Gx * Gx = \n",depol_gateset3.product(("Gx", "Gx")), end='\n\n')
print("Probabilities of outcomes of the gate\n sequence GxGx (rho0 and Mdefault assumed)= ",
      depol_gateset3.probs( ("Gx", "Gx")))
print("Probabilities of outcomes of the \"complete\" gate\n sequence rho0+GxGx+Mdefault = ",
      depol_gateset3.probs( ("rho0", "Gx", "Gx", "Mdefault")))
Product of Gx * Gx = 
 [[ 1.00000000e+00  0.00000000e+00  1.51390444e-16 -2.78344767e-16]
 [ 0.00000000e+00  8.10000000e-01  0.00000000e+00  0.00000000e+00]
 [-1.09201969e-16  0.00000000e+00 -8.10000000e-01 -3.17943723e-16]
 [ 2.63428889e-16  0.00000000e+00  3.17943723e-16 -8.10000000e-01]]

Probabilities of outcomes of the gate
 sequence GxGx (rho0 and Mdefault assumed)=  OutcomeLabelDict([(('0',), 0.09499999999999997), (('1',), 0.9049999999999998)])
Probabilities of outcomes of the "complete" gate
 sequence rho0+GxGx+Mdefault =  OutcomeLabelDict([(('0',), 0.09499999999999997), (('1',), 0.9049999999999998)])
In [13]:
#Printing more detailed information about a gateset
depol_gateset3.print_info()
rho0 =    0.7071        0        0   0.7071


Mdefault = Unconstrained POVM with effect vectors:
0:
 0.71
   0
   0
 0.71

1:
 0.71
   0
   0
-0.71



Gi = 
   1.0000        0        0        0
        0   0.9000        0        0
        0        0   0.9000        0
        0        0        0   0.9000


Gx = 
   1.0000        0        0        0
        0   0.9000        0        0
        0        0        0  -0.9000
        0        0   0.9000        0


Gy = 
   1.0000        0        0        0
        0        0        0   0.9000
        0        0   0.9000        0
        0  -0.9000        0        0






Basis =  pp
Choi Matrices:
('Choi(Gi) in pauli basis = \n', '   0.9250       +0j        0       +0j        0       +0j        0       +0j\n        0       +0j   0.0250       +0j        0       +0j        0       +0j\n        0       +0j        0       +0j   0.0250       +0j        0       +0j\n        0       +0j        0       +0j        0       +0j   0.0250       +0j\n')
('  --eigenvals = ', [0.024999999999999977, 0.024999999999999998, 0.024999999999999998, 0.9250000000000003], '\n')
('Choi(Gx) in pauli basis = \n', '   0.4750       +0j        0  +0.4500j        0       +0j        0       +0j\n        0  -0.4500j   0.4750       +0j        0       +0j        0       +0j\n        0       +0j        0       +0j   0.0250       +0j        0       +0j\n        0       +0j        0       +0j        0       +0j   0.0250       +0j\n')
('  --eigenvals = ', [0.024999999999999974, 0.02499999999999999, 0.025000000000000026, 0.9250000000000002], '\n')
('Choi(Gy) in pauli basis = \n', '   0.4750       +0j        0       +0j        0  +0.4500j        0       +0j\n        0       +0j   0.0250       +0j        0       +0j        0       +0j\n        0  -0.4500j        0       +0j   0.4750       +0j        0       +0j\n        0       +0j        0       +0j        0       +0j   0.0250       +0j\n')
('  --eigenvals = ', [0.02499999999999991, 0.02500000000000001, 0.025000000000000088, 0.9250000000000008], '\n')
('Sum of negative Choi eigenvalues = ', 0.0)

Gate Set Parameterizations

In addition to specifying a set of $d^2 \times d^2$ gate matrices and length-$d^2$ SPAM vectors, every GateSet encapsulates a parametrization, that is, a function mapping a set of real-valued parameters to its set of gate matrices and SPAM vectors. A GateSet's contents must always correspond to a valid set of parameters, which can be obtained by its to_vector method, and can always be initialized from a vector of parameters via its from_vector method. The number of parameters (obtained via num_params) is independent (and need not equal!) the total number of gate-matrix and SPAM-vector elements comprising the GateSet. For example, in a "TP-parameterized" gate set, the first row of each gate matrix is fixed at [1,0,...0], regardless to what the GateSet's underlying parameters are. When pyGSTi generates GateSet estimates the parameters of an initial GateSet (often times the "target" gate set) supplied by the caller are optimized. Thus, by its parameterization a single GateSet can determine the space of possible GateSets that are searched for a best-fit estimate.

Each gate and SPAM vector within a GateSet have independent paramterizations, so that each pygsti.objects.Gate-derived gate object and pygsti.objects.SPAMVec-derived SPAM vector has its own to_vector, from_vector, and num_params method. A GateSet's parameter vector is just the concatenation of the parameter vectors of its contents, in the order: 1) state preparation vectors, 2) measurement vectors, 3) gates.

Users are able to create their own gate parameterizations by deriving from pygsti.objects.Gate or pygsti.objects.GateMatrix (which itself derives from Gate). Included in pyGSTi are several convenient gate parameterizations which are worth knowing about:

  • The FullyParameterizedGate class defines a gate which has a parameter for every element, and thus optimizations using this gate class allow the gate matrix to be completely arbitrary.
  • The TPParameterizedGate class defines a gate whose first row must be [1,0,...0]. This corresponds to a trace-preserving (TP) gate in the Gell-Mann and Pauli-product bases. Each element in the remaining rows is a separate parameter, similar to a fully parameterized gate. Optimizations using this gate type are used to constrain the estimated gate to being trace preserving.
  • The LindbladianParameterizedGate class defines a gate whose logarithm take a particular Lindblad form. This class is fairly flexible, but is predominantly used to constrain optimizations to the set of infinitesimally-generated CPTP maps.

Similarly, there are FullyParameterizedSPAMVec and TPParameterizedSPAMVec classes, the latter which fixes its first element to $\sqrt{d}$, where $d^2$ is the vector length, as this is the appropriate value for a unit-trace state preparation.

We now illustrate how one map specify the type of paramterization in build_gateset, and change the parameterizations of all of a GateSet's contents using its set_all_parameterizaions method.

In [14]:
# Speciy basis as 'gm' for Gell-Mann (could also be 'pp' for Pauli-Product)
# and parameterization to 'TP', so that gates are TPParameterizedGates
gateset6 = pc.build_gateset( [2], [('Q0',)],
                             ['Gi',], [ "I(Q0)"],
                             basis='pp', parameterization="TP")

#See that gates and prep vectors are TP, whereas previous GateSet's have
# fully parameterized elements
print("gateset6 gate type = ", type(gateset6['Gi']))
print("gateset6 prep type = ", type(gateset6['rho0']))
print("gateset5 gate type = ", type(gateset5['Gi']))
print("gateset5 prep type = ", type(gateset5['rho0']))

#Switch parameterization to CPTP gates
gateset5.set_all_parameterizations('CPTP')
print("\nAfter setting all parameterizations to CPTP:")
print("gateset6 gate type = ", type(gateset6['Gi']))
print("gateset6 prep type = ", type(gateset6['rho0'])) #Same as before; no CPTP prep type
gateset6 gate type =  <class 'pygsti.objects.gate.TPParameterizedGate'>
gateset6 prep type =  <class 'pygsti.objects.spamvec.TPParameterizedSPAMVec'>
gateset5 gate type =  <class 'pygsti.objects.gate.FullyParameterizedGate'>
gateset5 prep type =  <class 'pygsti.objects.spamvec.FullyParameterizedSPAMVec'>

After setting all parameterizations to CPTP:
gateset6 gate type =  <class 'pygsti.objects.gate.TPParameterizedGate'>
gateset6 prep type =  <class 'pygsti.objects.spamvec.TPParameterizedSPAMVec'>

To alter an individual gate or SPAM vector's parameterization, one can simply construct a Gate or SPAMVec object with the desired parameterization and assign it to the GateSet.

In [15]:
newGate = pygsti.objects.TPParameterizedGate(gateset6['Gi'])
gateset6['Gi'] = newGate
print("gateset6['Gi'] =",gateset6['Gi'])
gateset6['Gi'] = TP Parameterized gate with shape (4, 4)
 1.00   0   0   0
   0 1.00   0   0
   0   0 1.00   0
   0   0   0 1.00

NOTE: When a Gate or SPAMVec-derived object is assigned as an element of a GateSet (as above), the object replaces any existing object with the given key. However, if any other type of object is assigned to a GateSet element, an attempt is made to initialize or update the existing existing gate using the assigned data (using its set_matrix function internally). For example:

In [16]:
import numpy as np
numpy_array = np.array( [[1, 0, 0, 0],
                         [0, 0.5, 0, 0],
                         [0, 0, 0.5, 0],
                         [0, 0, 0, 0.5]], 'd')
gateset6['Gi'] = numpy_array # after assignment with a numpy array...
print("gateset6['Gi'] =",gateset6['Gi']) # this is STILL a TPParameterizedGate object

#If you try to assign a gate to something that is either invalid or it doesn't know how
# to deal with, it will raise an exception
invalid_TP_array = np.array( [[2, 1, 3, 0],
                              [0, 0.5, 0, 0],
                              [0, 0, 0.5, 0],
                              [0, 0, 0, 0.5]], 'd')
try:
    gateset6['Gi'] = invalid_TP_array
except ValueError as e:
    print("ERROR!! " + str(e))
gateset6['Gi'] = TP Parameterized gate with shape (4, 4)
 1.00   0   0   0
   0 0.50   0   0
   0   0 0.50   0
   0   0   0 0.50

ERROR!! Cannot set TPParameterizedGate: invalid form for 1st row!
In [ ]:
 
In [ ]:
 
In [ ]: