In this notebook, we'll demonstrate how to select preparation and measurement fiducials for the standard single-qubit $X, Y, I$ (or $X, Y$) gate set. The results are very straightforward to generalize for a non $X, Y, I$ single-qubit gateset or a multiqubit gate set.

Fiducial selection is not quite as much of a "dark art" as germ selection is, but there are nonetheless equally valid choices one can make in terms of inputs to the fiducial selection function. At present, we demonstrate only a subset of the functionality, but we endeavour to explain the remaining functionality as well.

In [1]:
#Import relevant modules.

import pygsti
import numpy as _np

from pygsti.algorithms import fiducialselection as FS

from pygsti.construction import std1Q_XYI
from pygsti.construction import std1Q_XY

import matplotlib.pyplot as plt
%matplotlib inline

import time

Different target gatesets require different fiducials. Thus, we must first define the target gateset.

It's worth noting that unlike with germ selection, we will use perfect gates. (In fact, imperfect gates for fiducial selection can introduce errors that we don't want.)

In [2]:
#Define the target gate set we will select fiducials for.
#Here, it is the standard X pi/2, Y pi/2, I gate set.

gs_target = std1Q_XYI.gs_target

Let's now try to actually pick out a fiducial set. The fiducial selection output will strongly depend on several different inputs to the function optimize_integer_fiducials_slack. These inputs include:

  • prepOrMeas: Whether we are attempting to compute preparation fiducials or measurement fiducials.
  • fiducialList : The list of candidate germs from which the fiducials set will be chosen.
  • initialWeights : The initial subset of candidate fiducials which the optimizer tests. The default here is None, meaning that all candidate fiducials are included in the first fiducial set test.
  • fixedSlack : The absolute score a fiducial set is allowed to achieve
  • slackFrac : The relative score a fiducial set is allowed to achieve

Note: Only one of fixedSlack or slackFrac may be set. Typically we will use fixedSlack, and we find that fixedSlack ~1 is sufficient. These arguments determine the relaxation scheme used to reduce the fiducial list size.

  • forceEmpty: Whether or not the fiducial set must contain the empty string.
  • fixedNum : Whether or not we are forcing the fiducial set to be a fixed size
  • scoreFunc : Whether the objective function only tries to minimize how insensitive we are for our most insensitive direction in Hilbert-Schmidt space, or if it tries to make us as sensitive as possible to all directions in Hilbert-Schmidt space

Here we demonstrate particular choices for the above inputs. By parameter counting, one can see that these particular instances yield "optimal" results (as there are four elements of a state or measurement effect, and we choose four fiducials).

However, we make no claims of optimality for these choices in general (particularly when 2 or more qubits are considered).

End users are encouraged to experiment themselves with these inputs. They are also welcome to contact the pygsti development team at pygsti@sandia.gov.

In [3]:
#Let's try to pick out a fiducial set.  

#First, we generate a candidate set which we'll attempt to prune.
#Here, we're looking at all gate string sequences of maximum length 2.

max_length = 2
gates = ['Gx','Gy']#We omit any identity operations here, as we don't want them in our fiducials.

#Important for the minlength arg to equal 0, so we include the empty string.
testFidList = pygsti.construction.list_all_gatestrings(gates,0,max_length)

Don't worry if the optimize_integer_fiducials_slack function below throws a divide by zero warning; this just means one of the tested cases was really bad.

In [4]:
#Compute the preparation fiducials

start = time.time()
prepFidList1 = FS.optimize_integer_fiducials_slack(gs_target,testFidList,prepOrMeas='prep',\
                                                   initialWeights=None,slackFrac=1)
end = time.time()
print
print "Fiducial selection completed in {0} seconds.".format(round(end-start, 7))
print prepFidList1
Starting fiducial set optimization. Lower score is better.
Iteration 0: score=35, nFids=7
Moving to better neighbor
Iteration 1: score=36, nFids=6
Moving to better neighbor
Iteration 2: score=35, nFids=5
Moving to better neighbor
Iteration 3: score=32, nFids=4
Stationary point found!
score =  64.0
weights =  [1 0 0 0 1 1 1]
L1(weights) =  4

Fiducial selection completed in 0.0117061 seconds.
[GateString({}), GateString(GxGy), GateString(GyGx), GateString(GyGy)]
In [5]:
# Compute the measurement fiducials 

start = time.time()
measFidList1 = FS.optimize_integer_fiducials_slack(gs_target,testFidList,prepOrMeas='meas',\
                                                   initialWeights=None,slackFrac=1)
end = time.time()
print
print "Fiducial selection completed in {0} seconds.".format(round(end-start, 7))
print measFidList1
Starting fiducial set optimization. Lower score is better.
Iteration 0: score=35, nFids=7
Moving to better neighbor
Iteration 1: score=36, nFids=6
Moving to better neighbor
Iteration 2: score=35, nFids=5
Moving to better neighbor
Iteration 3: score=32, nFids=4
Stationary point found!
score =  64.0
weights =  [1 0 0 0 1 1 1]
L1(weights) =  4

Fiducial selection completed in 0.0108709 seconds.
[GateString({}), GateString(GxGy), GateString(GyGx), GateString(GyGy)]
In [6]:
print "We have selected our preparation fiducials to be:"
for fid in prepFidList1:
    print '\t',fid
We have selected our preparation fiducials to be:
	{}
	GxGy
	GyGx
	GyGy
In [7]:
print "We have selected our measurement fiducials to be:"
for fid in measFidList1:
    print '\t',fid
We have selected our measurement fiducials to be:
	{}
	GxGy
	GyGx
	GyGy

The key property we wish for our fiducials to have is informational completeness. Below, we test whether our selected preparation and measurement fiducuials are in fact so.

In [8]:
FS.test_fiducial_list(gs_target,prepFidList1,'prep',returnAll=True)
Out[8]:
(True,
 array([ 0.21922359,  0.5       ,  1.        ,  2.28077641]),
 32.000000000000007)
In [9]:
FS.test_fiducial_list(gs_target,measFidList1,'meas',returnAll=True)
Out[9]:
(True,
 array([ 0.21922359,  0.5       ,  1.        ,  2.28077641]),
 32.000000000000007)

Note that in practice, we use a set of six fiducials both for prep and measure (which can be created using std1Q_XYI.fiducials). This is for greater numerical stability; for single-qubit GST, we recommend 6 preparation and 6 measurement fiducials, as the added cost is not too great, and this provides prep and measure fiducials corresponding to all 6 antipodal points on the Bloch sphere, providing a nice degree of symmetry.

However, for multiqubit GST, experimental resource constraints become more relevant, and we recommend simply directly using the outputs of the fiducial selection code.

That said, one can, for one or more quibits, force optimize_integer_fiducials_slack to return a fiducial set of fixed size. Instead of running an integer program over fiducial sets of different sizes, we can instead score all fiducial sets of a fixed size (that are subsets of the input set) and select the best one.
This can become expensive quickly, but it is very feasible for at least single-qubit gate sets, as exhibited below.

To turn this functionality on, set the fixedNum keyword argument to be equal to the fiducial set size you want.

Also, a warning: If there does not exist an informationally complete fiducial set of the desired size, you may still receive an output from optimize_integer_fiducials_slack, so it is important to check the score of the fiducial set (either via the returnAll keyword arg, or the function test_fiducial_list.)

In [10]:
#Again, we'll try to pick out a fiducial set, but now we will insist that the set be of size 6.
#We'll look at all gate string sequences of maximum length 3.

max_length = 3
gates = ['Gx','Gy']

testFidList_force6 = pygsti.construction.list_all_gatestrings(gates,0,max_length)
In [11]:
#Let's again forceEmpty to be True, and see what we get for preparation fiducials.
start = time.time()
prepFidList1_force6 = FS.optimize_integer_fiducials_slack(gs_target,testFidList_force6,prepOrMeas='prep',\
                                                          initialWeights=None,slackFrac=1,fixedNum=6,\
                                                          forceEmpty=True)
end = time.time()
print
print "Fiducial selection completed in {0} seconds.".format(round(end-start, 7))
print prepFidList1_force6
Starting fiducial set optimization. Lower score is better.
Output set is required to be of size 6
Total number of fiducial sets to be checked is 2002.0
If this is very large, you may wish to abort.

Fiducial selection completed in 0.860805 seconds.
[GateString({}), GateString(Gx), GateString(Gy), GateString(GxGx), GateString(GxGxGx), GateString(GxGxGy)]
/home/enielse/research/pyGSTi/packages/pygsti/algorithms/fiducialselection.py:355: RuntimeWarning: divide by zero encountered in divide
  return sum(1./_np.abs(input_array))
In [12]:
#Let's set forceEmpty = False
start = time.time()
prepFidList1_force6 = FS.optimize_integer_fiducials_slack(gs_target,testFidList_force6,prepOrMeas='prep',\
                                                          initialWeights=None,slackFrac=1,fixedNum=6,\
                                                          forceEmpty=False)
end = time.time()
print
print "Fiducial selection completed in {0} seconds.".format(round(end-start, 7))
print prepFidList1_force6
#Conveniently, we get the same results as above!
Starting fiducial set optimization. Lower score is better.
Output set is required to be of size 6
Total number of fiducial sets to be checked is 5005.0
If this is very large, you may wish to abort.

Fiducial selection completed in 1.701298 seconds.
[GateString({}), GateString(Gx), GateString(Gy), GateString(GxGx), GateString(GxGxGx), GateString(GxGxGy)]
In [13]:
#Now let's make a measurement fiducial set with forceEmpty = True
start = time.time()
measFidList1_force6 = FS.optimize_integer_fiducials_slack(gs_target,testFidList_force6,prepOrMeas='meas',\
                                                          initialWeights=None,slackFrac=1,fixedNum=6,\
                                                          forceEmpty=True)
end = time.time()
print
print "Fiducial selection completed in {0} seconds.".format(round(end-start, 7))
print measFidList1_force6
Starting fiducial set optimization. Lower score is better.
Output set is required to be of size 6
Total number of fiducial sets to be checked is 2002.0
If this is very large, you may wish to abort.

Fiducial selection completed in 0.8509941 seconds.
[GateString({}), GateString(Gx), GateString(Gy), GateString(GxGx), GateString(GxGxGx), GateString(GyGxGx)]
In [14]:
#Now let's make a measurement fiducial set with forceEmpty = False
start = time.time()
measFidList1_force6 = FS.optimize_integer_fiducials_slack(gs_target,testFidList_force6,prepOrMeas='meas',\
                                                          initialWeights=None,slackFrac=1,fixedNum=6,\
                                                          forceEmpty=False)
end = time.time()
print
print "Fiducial selection completed in {0} seconds.".format(round(end-start, 7))
print measFidList1_force6
Starting fiducial set optimization. Lower score is better.
Output set is required to be of size 6
Total number of fiducial sets to be checked is 5005.0
If this is very large, you may wish to abort.

Fiducial selection completed in 1.6874199 seconds.
[GateString({}), GateString(Gx), GateString(Gy), GateString(GxGx), GateString(GxGxGx), GateString(GyGxGx)]
In [15]:
print "We have selected our size-6 preparation fiducials to be:"
for fid in prepFidList1_force6:
    print '\t',fid
We have selected our size-6 preparation fiducials to be:
	{}
	Gx
	Gy
	GxGx
	GxGxGx
	GxGxGy
In [16]:
print "We have selected our size-6 measurement fiducials to be:"
for fid in measFidList1_force6:
    print '\t',fid
We have selected our size-6 measurement fiducials to be:
	{}
	Gx
	Gy
	GxGx
	GxGxGx
	GyGxGx
In [17]:
FS.test_fiducial_list(gs_target,prepFidList1_force6,'prep',returnAll=True)
Out[17]:
(True, array([ 1.,  1.,  1.,  3.]), 20.0)
In [18]:
FS.test_fiducial_list(gs_target,measFidList1_force6,'meas',returnAll=True)
Out[18]:
(True, array([ 1.,  1.,  1.,  3.]), 20.0)
In [19]:
#Lastly, let's compare to the "standard" 6-fiducial set we use as our default:
print "std1Q_XYI.fiducials =", std1Q_XYI.fiducials
print FS.test_fiducial_list(gs_target,std1Q_XYI.fiducials,'prep',returnAll=True)
print FS.test_fiducial_list(gs_target,std1Q_XYI.fiducials,'meas',returnAll=True)
std1Q_XYI.fiducials = [GateString({}), GateString(Gx), GateString(Gy), GateString(GxGx), GateString(GxGxGx), GateString(GyGyGy)]
(True, array([ 1.,  1.,  1.,  3.]), 20.0)
(True, array([ 1.,  1.,  1.,  3.]), 20.0)

The "standard" fiducials are very similar to the automatically selected ones, and score the same! The notable difference is that prep and measurement fiducials are different when automatically selected; our default sets are the same for both prep and measure. This is because each "standard" fiducial is symmetric; the automated fiducials reverse gate order between preparation and measure.