/
fiducialpairreduction.py
607 lines (491 loc) · 28.6 KB
/
fiducialpairreduction.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
""" Functions for reducing the number of required fiducial pairs for analysis."""
from __future__ import division, print_function, absolute_import, unicode_literals
#***************************************************************************************************
# Copyright 2015, 2019 National Technology & Engineering Solutions of Sandia, LLC (NTESS).
# Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights
# in this software.
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
# in compliance with the License. You may obtain a copy of the License at
# http://www.apache.org/licenses/LICENSE-2.0 or in the LICENSE file in the root pyGSTi directory.
#***************************************************************************************************
import numpy as _np
import itertools as _itertools
import random as _random
import scipy.special as _spspecial
from ..construction import circuitconstruction as _gsc
from ..tools import remove_duplicates as _remove_duplicates
from ..tools import slicetools as _slct
from .. import objects as _objs
def _nCr(n, r):
"""Number of combinations of r items out of a set of n. Equals n!/(r!(n-r)!)"""
#f = _math.factorial; return f(n) / f(r) / f(n-r)
return _spspecial.comb(n, r)
def _random_combination(indices_tuple, r):
"""
Random selection from itertools.combinations(indices_tuple, r)
from http://docs.python.org/2/library/itertools.html#recipes
"""
n = len(indices_tuple)
iis = sorted(_random.sample(range(n), r))
return tuple(indices_tuple[i] for i in iis)
def find_sufficient_fiducial_pairs(targetModel, prepStrs, effectStrs, germList,
testLs=(256, 2048), prepovmTuples="first", tol=0.75,
searchMode="sequential", nRandom=100, seed=None,
verbosity=0, testPairList=None, memLimit=None,
minimumPairs=1):
"""
Finds a (global) set of fiducial pairs that are amplificationally complete.
A "standard" set of GST operation sequences consists of all sequences of the form:
statePrep + prepFiducial + germPower + measureFiducial + measurement
This set is typically over-complete, and it is possible to restrict the
(prepFiducial, measureFiducial) pairs to a subset of all the possible
pairs given the separate `prepStrs` and `effectStrs` lists. This function
attempts to find a set of fiducial pairs that still amplify all of the
model's parameters (i.e. is "amplificationally complete"). The test
for amplification is performed using the two germ-power lengths given by
`testLs`, and tests whether the magnitudes of the Jacobian's singular
values scale linearly with the germ-power length.
In the special case when `testPairList` is not None, the function *tests*
the given set of fiducial pairs for amplificational completeness, and
does not perform any search.
Parameters
----------
targetModel : Model
The target model used to determine amplificational completeness.
prepStrs, effectStrs, germList : list of Circuits
The (full) fiducial and germ operation sequences.
testLs : (L1,L2) tuple of ints, optional
A tuple of integers specifying the germ-power lengths to use when
checking for amplificational completeness.
prepovmTuples : list or "first", optional
A list of `(prepLabel, povmLabel)` tuples to consider when
checking for completeness. Usually this should be left as the special
(and default) value "first", which considers the first prep and POVM
contained in `targetModel`.
tol : float, optional
The tolerance for the fraction of the expected amplification that must
be observed to call a parameter "amplified".
searchMode : {"sequential","random"}, optional
If "sequential", then all potential fiducial pair sets of a given length
are considered in sequence before moving to sets of a larger size. This
can take a long time when there are many possible fiducial pairs.
If "random", then only `nRandom` randomly chosen fiducial pair sets are
considered for each set size before the set is enlarged.
nRandom : int, optional
The number of random-pair-sets to consider for a given set size.
seed : int, optional
The seed to use for generating random-pair-sets.
verbosity : int, optional
How much detail to print to stdout.
testPairList : list or None, optional
If not None, a list of (iRhoStr,iEffectStr) tuples of integers,
specifying a list of fiducial pairs (indices are into `prepStrs` and
`effectStrs`, respectively). These pairs are then tested for
amplificational completeness and the number of amplified parameters
is printed to stdout. (This is a special debugging functionality.)
memLimit : int, optional
A memory limit in bytes.
minimumPairs : int, optional
The minimium number of fiducial pairs to try (default == 1). Set this
to integers larger than 1 to avoid trying pair sets that are known to
be too small.
Returns
-------
list
A list of (iRhoStr,iEffectStr) tuples of integers, specifying a list
of fiducial pairs (indices are into `prepStrs` and `effectStrs`).
"""
printer = _objs.VerbosityPrinter.build_printer(verbosity)
#trim LSGST list of all f1+germ^exp+f2 strings to just those needed to get full rank jacobian. (compressed sensing
#like)
#tol = 0.5 #fraction of expected amplification that must be observed to call a parameter "amplified"
if prepovmTuples == "first":
firstRho = list(targetModel.preps.keys())[0]
firstPOVM = list(targetModel.povms.keys())[0]
prepovmTuples = [(firstRho, firstPOVM)]
prepovmTuples = [(_objs.Circuit((prepLbl,)), _objs.Circuit((povmLbl,)))
for prepLbl, povmLbl in prepovmTuples]
def get_derivs(L):
""" Compute all derivative info: get derivative of each <E_i|germ^exp|rho_j>
where i = composite EVec & fiducial index and j similar """
st = 0 # running row count over to-be-concatenated dPall matrix
elIndicesForPair = [[] for i in range(len(prepStrs) * len(effectStrs))]
#contains lists of final leading-dim indices corresponding to each fiducial pair
dPall = [] # one element per germ, concatenate later
for iGerm, germ in enumerate(germList):
expGerm = _gsc.repeat_with_max_length(germ, L) # could pass exponent and set to germ**exp here
lst = _gsc.create_circuit_list(
"pp[0]+f0+expGerm+f1+pp[1]", f0=prepStrs, f1=effectStrs,
expGerm=expGerm, pp=prepovmTuples, order=('f0', 'f1', 'pp'))
evTree, blkSz, _, lookup, _ = targetModel.bulk_evaltree_from_resources(
lst, memLimit=memLimit, distributeMethod="deriv",
subcalls=['bulk_fill_dprobs'], verbosity=0)
#FUTURE: assert that no instruments are allowed?
dP = _np.empty((evTree.num_final_elements(), targetModel.num_params()), 'd')
targetModel.bulk_fill_dprobs(dP, evTree, wrtBlockSize=blkSz) # num_els x num_params
dPall.append(dP)
#Add this germ's element indices for each fiducial pair (final operation sequence of evTree)
nPrepPOVM = len(prepovmTuples)
for k in range(len(prepStrs) * len(effectStrs)):
for o in range(k * nPrepPOVM, (k + 1) * nPrepPOVM):
# "original" indices into lst for k-th fiducial pair
elArray = _slct.as_array(lookup[o]) + st
elIndicesForPair[k].extend(list(elArray))
st += evTree.num_final_elements() # b/c we'll concatenate tree's elements later
return _np.concatenate(dPall, axis=0), elIndicesForPair
#indexed by [iElement, iGatesetParam] : gives d(<SP|f0+exp_iGerm+f1|AM>)/d(iGatesetParam)
# where iGerm, f0, f1, and SPAM are all bundled into iElement (but elIndicesForPair
# provides the necessary indexing for picking out certain pairs)
def get_number_amplified(M0, M1, L0, L1, verb):
""" Return the number of amplified parameters """
printer = _objs.VerbosityPrinter.build_printer(verb)
L_ratio = float(L1) / float(L0)
try:
s0 = _np.linalg.svd(M0, compute_uv=False)
s1 = _np.linalg.svd(M1, compute_uv=False)
except: # pragma: no cover
printer.warning("SVD error!!"); return 0 # pragma: no cover
#SVD did not converge -> just say no amplified params...
numAmplified = 0
printer.log("Amplified parameter test: matrices are %s and %s." % (M0.shape, M1.shape), 4)
printer.log("Index : SV(L=%d) SV(L=%d) AmpTest ( > %g ?)" % (L0, L1, tol), 4)
for i, (v0, v1) in enumerate(zip(sorted(s0, reverse=True), sorted(s1, reverse=True))):
if abs(v0) > 0.1 and (v1 / v0) / L_ratio > tol:
numAmplified += 1
printer.log("%d: %g %g %g YES" % (i, v0, v1, (v1 / v0) / L_ratio), 4)
printer.log("%d: %g %g %g NO" % (i, v0, v1, (v1 / v0) / L_ratio), 4)
return numAmplified
#rank = len( [v for v in s if v > 0.001] )
printer.log("------ Fiducial Pair Reduction --------")
L0 = testLs[0]; dP0, elIndices0 = get_derivs(L0)
L1 = testLs[1]; dP1, elIndices1 = get_derivs(L1)
fullTestMx0 = dP0
fullTestMx1 = dP1
#Get number of amplified parameters in the "full" test matrix: the one we get when we use all possible fiducial
#pairs
if testPairList is None:
maxAmplified = get_number_amplified(fullTestMx0, fullTestMx1, L0, L1, verbosity + 1)
printer.log("maximum number of amplified parameters = %s" % maxAmplified)
#Loop through fiducial pairs and add all derivative rows (1 x nModelParams) to test matrix
# then check if testMatrix has full rank ( == nModelParams)
nPossiblePairs = len(prepStrs) * len(effectStrs)
allPairIndices = list(range(nPossiblePairs))
nRhoStrs, nEStrs = len(prepStrs), len(effectStrs)
if testPairList is not None: # special mode for testing/debugging single pairlist
pairIndices0 = _np.concatenate([elIndices0[iRhoStr * nEStrs + iEStr]
for iRhoStr, iEStr in testPairList])
pairIndices1 = _np.concatenate([elIndices1[iRhoStr * nEStrs + iEStr]
for iRhoStr, iEStr in testPairList])
testMx0 = _np.take(fullTestMx0, pairIndices0, axis=0)
testMx1 = _np.take(fullTestMx1, pairIndices1, axis=0)
nAmplified = get_number_amplified(testMx0, testMx1, L0, L1, verbosity)
printer.log("Number of amplified parameters = %s" % nAmplified)
return None
bestAmplified = 0
for nNeededPairs in range(minimumPairs, nPossiblePairs):
printer.log("Beginning search for a good set of %d pairs (%d pair lists to test)" %
(nNeededPairs, _nCr(nPossiblePairs, nNeededPairs)))
bestAmplified = 0
if searchMode == "sequential":
pairIndicesToIterateOver = _itertools.combinations(allPairIndices, nNeededPairs)
elif searchMode == "random":
_random.seed(seed) # ok if seed is None
nTotalPairCombos = _nCr(len(allPairIndices), nNeededPairs)
if nRandom < nTotalPairCombos:
pairIndicesToIterateOver = [_random_combination(allPairIndices, nNeededPairs) for i in range(nRandom)]
else:
pairIndicesToIterateOver = _itertools.combinations(allPairIndices, nNeededPairs)
for pairIndicesToTest in pairIndicesToIterateOver:
pairIndices0 = _np.concatenate([elIndices0[i] for i in pairIndicesToTest])
pairIndices1 = _np.concatenate([elIndices1[i] for i in pairIndicesToTest])
testMx0 = _np.take(fullTestMx0, pairIndices0, axis=0)
testMx1 = _np.take(fullTestMx1, pairIndices1, axis=0)
nAmplified = get_number_amplified(testMx0, testMx1, L0, L1, verbosity)
bestAmplified = max(bestAmplified, nAmplified)
if printer.verbosity > 1:
ret = []
for i in pairIndicesToTest:
iRhoStr = i // nEStrs
iEStr = i - iRhoStr * nEStrs
ret.append((iRhoStr, iEStr))
printer.log("Pair list %s ==> %d amplified parameters" % (" ".join(map(str, ret)), nAmplified))
if nAmplified == maxAmplified:
ret = []
for i in pairIndicesToTest:
iRhoStr = i // nEStrs
iEStr = i - iRhoStr * nEStrs
ret.append((iRhoStr, iEStr))
return ret
printer.log(" --> Highest number of amplified parameters was %d" % bestAmplified)
#if we tried all the way to nPossiblePairs-1 and no success, just return all the pairs, which by definition will hit
#the "max-amplified" target
listOfAllPairs = [(iRhoStr, iEStr)
for iRhoStr in range(nRhoStrs)
for iEStr in range(nEStrs)]
return listOfAllPairs
def find_sufficient_fiducial_pairs_per_germ(targetModel, prepStrs, effectStrs,
germList, prepovmTuples="first",
searchMode="sequential", constrainToTP=True,
nRandom=100, seed=None, verbosity=0,
memLimit=None):
"""
Finds a per-germ set of fiducial pairs that are amplificationally complete.
A "standard" set of GST operation sequences consists of all sequences of the form:
statePrep + prepFiducial + germPower + measureFiducial + measurement
This set is typically over-complete, and it is possible to restrict the
(prepFiducial, measureFiducial) pairs to a subset of all the possible
pairs given the separate `prepStrs` and `effectStrs` lists. This function
attempts to find sets of fiducial pairs, one set per germ, that still
amplify all of the model's parameters (i.e. is "amplificationally
complete"). For each germ, a fiducial pair set is found that amplifies
all of the "parameters" (really linear combinations of them) that the
particular germ amplifies.
To test whether a set of fiducial pairs satisfies this condition, the
sum of projectors `P_i = dot(J_i,J_i^T)`, where `J_i` is a matrix of the
derivatives of each of the selected (prepFiducial+germ+effectFiducial)
sequence probabilities with respect to the i-th germ eigenvalue (or
more generally, amplified parameter), is computed. If the fiducial-pair
set is sufficient, the rank of the resulting sum (an operator) will be
equal to the total (maximal) number of parameters the germ can amplify.
Parameters
----------
targetModel : Model
The target model used to determine amplificational completeness.
prepStrs, effectStrs, germList : list of Circuits
The (full) fiducial and germ operation sequences.
prepovmTuples : list or "first", optional
A list of `(prepLabel, povmLabel)` tuples to consider when
checking for completeness. Usually this should be left as the special
(and default) value "first", which considers the first prep and POVM
contained in `targetModel`.
searchMode : {"sequential","random"}, optional
If "sequential", then all potential fiducial pair sets of a given length
are considered in sequence (per germ) before moving to sets of a larger
size. This can take a long time when there are many possible fiducial
pairs. If "random", then only `nRandom` randomly chosen fiducial pair
sets are considered for each set size before the set is enlarged.
constrainToTP : bool, optional
Whether or not to consider non-TP parameters the the germs amplify. If
the fiducal pairs will be used in a GST estimation where the model is
constrained to being trace-preserving (TP), this should be set to True.
nRandom : int, optional
The number of random-pair-sets to consider for a given set size.
seed : int, optional
The seed to use for generating random-pair-sets.
verbosity : int, optional
How much detail to print to stdout.
memLimit : int, optional
A memory limit in bytes.
Returns
-------
dict
A dictionary whose keys are the germ operation sequences and whose values are
lists of (iRhoStr,iEffectStr) tuples of integers, each specifying the
list of fiducial pairs for a particular germ (indices are into
`prepStrs` and `effectStrs`).
"""
printer = _objs.VerbosityPrinter.build_printer(verbosity)
if prepovmTuples == "first":
firstRho = list(targetModel.preps.keys())[0]
firstPOVM = list(targetModel.povms.keys())[0]
prepovmTuples = [(firstRho, firstPOVM)]
prepovmTuples = [(_objs.Circuit((prepLbl,)), _objs.Circuit((povmLbl,)))
for prepLbl, povmLbl in prepovmTuples]
pairListDict = {} # dict of lists of 2-tuples: one pair list per germ
printer.log("------ Individual Fiducial Pair Reduction --------")
with printer.progress_logging(1):
for i, germ in enumerate(germList):
#Create a new model containing static target gates and a
# special "germ" gate that is parameterized only by it's
# eigenvalues (and relevant off-diagonal elements)
gsGerm = targetModel.copy()
gsGerm.set_all_parameterizations("static")
germMx = gsGerm.product(germ)
gsGerm.operations["Ggerm"] = _objs.EigenvalueParamDenseOp(
germMx, True, constrainToTP)
printer.show_progress(i, len(germList),
suffix='-- %s germ (%d params)' %
(germ, gsGerm.num_params()))
#Debugging
#print(gsGerm.operations["Ggerm"].evals)
#print(gsGerm.operations["Ggerm"].params)
#Get dP-matrix for full set of fiducials, where
# P_ij = <E_i|germ^exp|rho_j>, i = composite EVec & fiducial index,
# j is similar, and derivs are wrt the "eigenvalues" of the germ
# (i.e. the parameters of the gsGerm model).
lst = _gsc.create_circuit_list(
"pp[0]+f0+germ+f1+pp[1]", f0=prepStrs, f1=effectStrs,
germ=_objs.Circuit(("Ggerm",)), pp=prepovmTuples,
order=('f0', 'f1', 'pp'))
evTree, blkSz, _, lookup, _ = gsGerm.bulk_evaltree_from_resources(
lst, memLimit=memLimit, distributeMethod="deriv",
subcalls=['bulk_fill_dprobs'], verbosity=0)
elIndicesForPair = [[] for i in range(len(prepStrs) * len(effectStrs))]
nPrepPOVM = len(prepovmTuples)
for k in range(len(prepStrs) * len(effectStrs)):
for o in range(k * nPrepPOVM, (k + 1) * nPrepPOVM):
# "original" indices into lst for k-th fiducial pair
elIndicesForPair[k].extend(list(_slct.indices(lookup[o])))
dPall = _np.empty((evTree.num_final_elements(), gsGerm.num_params()), 'd')
gsGerm.bulk_fill_dprobs(dPall, evTree, wrtBlockSize=blkSz) # num_els x num_params
# Construct sum of projectors onto the directions (1D spaces)
# corresponding to varying each parameter (~eigenvalue) of the
# germ. If the set of fiducials is sufficient, then the rank of
# the resulting operator will equal the number of parameters,
# indicating that the P matrix is (independently) sensitive to
# each of the germ parameters (~eigenvalues), which is *all* we
# want sensitivity to.
RANK_TOL = 1e-7
rank = _np.linalg.matrix_rank(_np.dot(dPall, dPall.T), RANK_TOL)
if rank < gsGerm.num_params(): # full fiducial set should work!
raise ValueError("Incomplete fiducial-pair set!")
#Below will take a *subset* of the rows in dPall
# depending on which (of all possible) fiducial pairs
# are being considered.
# nRhoStrs, nEStrs = len(prepStrs), len(effectStrs)
nEStrs = len(effectStrs)
nPossiblePairs = len(prepStrs) * len(effectStrs)
allPairIndices = list(range(nPossiblePairs))
#Determine which fiducial-pair indices to iterate over
goodPairList = None; maxRank = 0
for nNeededPairs in range(gsGerm.num_params(), nPossiblePairs):
printer.log("Beginning search for a good set of %d pairs (%d pair lists to test)" %
(nNeededPairs, _nCr(nPossiblePairs, nNeededPairs)), 2)
if searchMode == "sequential":
pairIndicesToIterateOver = _itertools.combinations(allPairIndices, nNeededPairs)
elif searchMode == "random":
_random.seed(seed) # ok if seed is None
nTotalPairCombos = _nCr(len(allPairIndices), nNeededPairs)
if nRandom < nTotalPairCombos:
pairIndicesToIterateOver = [_random_combination(
allPairIndices, nNeededPairs) for i in range(nRandom)]
else:
pairIndicesToIterateOver = _itertools.combinations(allPairIndices, nNeededPairs)
for pairIndicesToTest in pairIndicesToIterateOver:
#Get list of pairs as tuples for printing & returning
pairList = []
for i in pairIndicesToTest:
iRhoStr = i // nEStrs; iEStr = i - iRhoStr * nEStrs
pairList.append((iRhoStr, iEStr))
# Same computation of rank as above, but with only a
# subset of the total fiducial pairs.
elementIndicesToTest = _np.concatenate([elIndicesForPair[i] for i in pairIndicesToTest])
dP = _np.take(dPall, elementIndicesToTest, axis=0) # subset_of_num_elements x num_params
rank = _np.linalg.matrix_rank(_np.dot(dP, dP.T), RANK_TOL)
maxRank = max(maxRank, rank)
printer.log("Pair list %s ==> %d of %d amplified parameters"
% (" ".join(map(str, pairList)), rank,
gsGerm.num_params()), 3)
if rank == gsGerm.num_params():
printer.log("Found a good set of %d pairs: %s" %
(nNeededPairs, " ".join(map(str, pairList))), 2)
goodPairList = pairList
break
if goodPairList is not None:
break # exit another loop level if a solution was found
assert(goodPairList is not None)
pairListDict[germ] = goodPairList # add to final list of per-germ pairs
#TODO REMOVE: should never be called b/c of ValueError catch above for insufficient fidicials
#else:
# #we tried all the way to nPossiblePairs-1 and no success,
# # just return all the pairs
# printer.log(" --> Highest number amplified = %d of %d" %
# (maxRank, gsGerms.num_params()))
# listOfAllPairs = [ (iRhoStr,iEStr)
# for iRhoStr in range(nRhoStrs)
# for iEStr in range(nEStrs) ]
# pairListDict[germ] = listOfAllPairs
return pairListDict
def test_fiducial_pairs(fidPairs, targetModel, prepStrs, effectStrs, germList,
testLs=(256, 2048), prepovmTuples="first", tol=0.75,
verbosity=0, memLimit=None):
"""
Tests a set of global or per-germ fiducial pairs.
Determines how many model parameters (of `targetModel`) are
amplified by the fiducial pairs given by `fidPairs`, which can be
either a list of 2-tuples (for global-FPR) or a dictionary (for
per-germ FPR).
Parameters
----------
fidPairs : list or dict
Either a single list of fiducial-index pairs (2-tuples) that is applied
to every germ (global FPR) OR a per-germ dictionary of lists, each
containing the fiducial-index pairs (2-tuples) for that germ (for
per-germ FPR).
targetModel : Model
The target model used to determine amplificational completeness.
prepStrs, effectStrs, germList : list of Circuits
The (full) fiducial and germ operation sequences.
testLs : (L1,L2) tuple of ints, optional
A tuple of integers specifying the germ-power lengths to use when
checking for amplificational completeness.
prepovmTuples : list or "first", optional
A list of `(prepLabel, povmLabel)` tuples to consider when
checking for completeness. Usually this should be left as the special
(and default) value "first", which considers the first prep and POVM
contained in `targetModel`.
tol : float, optional
The tolerance for the fraction of the expected amplification that must
be observed to call a parameter "amplified".
verbosity : int, optional
How much detail to print to stdout.
memLimit : int, optional
A memory limit in bytes.
Returns
-------
numAmplified : int
"""
printer = _objs.VerbosityPrinter.build_printer(verbosity)
if prepovmTuples == "first":
firstRho = list(targetModel.preps.keys())[0]
firstPOVM = list(targetModel.povms.keys())[0]
prepovmTuples = [(firstRho, firstPOVM)]
prepovmTuples = [(_objs.Circuit((prepLbl,)), _objs.Circuit((povmLbl,)))
for prepLbl, povmLbl in prepovmTuples]
nModelParams = targetModel.num_params()
def get_derivs(L):
""" Compute all derivative info: get derivative of each <E_i|germ^exp|rho_j>
where i = composite EVec & fiducial index and j similar """
circuits = []
for germ in germList:
expGerm = _gsc.repeat_with_max_length(germ, L) # could pass exponent and set to germ**exp here
pairList = fidPairs[germ] if isinstance(fidPairs, dict) else fidPairs
circuits += _gsc.create_circuit_list("pp[0]+p[0]+expGerm+p[1]+pp[1]",
p=[(prepStrs[i], effectStrs[j]) for i, j in pairList],
pp=prepovmTuples, expGerm=expGerm, order=['p', 'pp'])
circuits = _remove_duplicates(circuits)
evTree, wrtSize, _, _, _ = targetModel.bulk_evaltree_from_resources(
circuits, memLimit=memLimit, distributeMethod="deriv",
subcalls=['bulk_fill_dprobs'], verbosity=0)
dP = _np.empty((evTree.num_final_elements(), nModelParams))
#indexed by [iSpamLabel,iCircuit,iGatesetParam] : gives d(<SP|Circuit|AM>)/d(iGatesetParam)
targetModel.bulk_fill_dprobs(dP, evTree, wrtBlockSize=wrtSize)
return dP
def get_number_amplified(M0, M1, L0, L1):
""" Return the number of amplified parameters """
L_ratio = float(L1) / float(L0)
try:
s0 = _np.linalg.svd(M0, compute_uv=False)
s1 = _np.linalg.svd(M1, compute_uv=False)
except: # pragma: no cover
printer.warning("SVD error!!"); return 0 # pragma: no cover
#SVD did not converge -> just say no amplified params...
numAmplified = 0
printer.log("Amplified parameter test: matrices are %s and %s." % (M0.shape, M1.shape), 4)
printer.log("Index : SV(L=%d) SV(L=%d) AmpTest ( > %g ?)" % (L0, L1, tol), 4)
for i, (v0, v1) in enumerate(zip(sorted(s0, reverse=True), sorted(s1, reverse=True))):
if abs(v0) > 0.1 and (v1 / v0) / L_ratio > tol:
numAmplified += 1
printer.log("%d: %g %g %g YES" % (i, v0, v1, (v1 / v0) / L_ratio), 4)
printer.log("%d: %g %g %g NO" % (i, v0, v1, (v1 / v0) / L_ratio), 4)
return numAmplified
L0, L1 = testLs
printer.log("---------- Testing Fiducial Pairs ----------")
printer.log("Getting jacobian at L=%d" % L0, 2)
dP0 = get_derivs(L0)
printer.log("Getting jacobian at L=%d" % L1, 2)
dP1 = get_derivs(L1)
printer.log("Computing number amplified", 2)
nAmplified = get_number_amplified(dP0, dP1, L0, L1)
printer.log("Number of amplified parameters = %s" % nAmplified)
return nAmplified