-
Notifications
You must be signed in to change notification settings - Fork 55
/
fiducialpairreduction.py
1576 lines (1258 loc) · 82.3 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
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
"""
Functions for reducing the number of required fiducial pairs for analysis.
"""
#***************************************************************************************************
# 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 itertools as _itertools
import random as _random
import numpy as _np
import scipy.special as _spspecial
import scipy.linalg as _sla
from math import ceil
import time
from pygsti import baseobjs as _baseobjs
from pygsti import circuits as _circuits
from pygsti.circuits import circuitconstruction as _gsc
from pygsti.modelmembers.operations import EigenvalueParamDenseOp as _EigenvalueParamDenseOp
from pygsti.tools import apply_aliases_to_circuits as _apply_aliases_to_circuits
from pygsti.tools import remove_duplicates as _remove_duplicates
from pygsti.tools import slicetools as _slct
from pygsti.tools.legacytools import deprecate as _deprecated_fn
from pygsti.algorithms.germselection import construct_update_cache, minamide_style_inverse_trace, compact_EVD, compact_EVD_via_SVD
from pygsti.algorithms import scoring as _scoring
from pygsti.tools.matrixtools import print_mx
import warnings
def _nCr(n, r): # noqa
"""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)
@_deprecated_fn('find_sufficient_fiducial_pairs_per_germ_power')
def find_sufficient_fiducial_pairs(target_model, prep_fiducials, meas_fiducials, germs,
test_lengths=(256, 2048), prep_povm_tuples="first", tol=0.75,
search_mode="sequential", n_random=100, seed=None,
verbosity=0, test_pair_list=None, mem_limit=None,
minimum_pairs=1):
"""
Finds a (global) set of fiducial pairs that are amplificationally complete.
A "standard" set of GST circuits consists of all circuits 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 `prep_fiducials` and `meas_fiducials` 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
`test_lengths`, and tests whether the magnitudes of the Jacobian's singular
values scale linearly with the germ-power length.
In the special case when `test_pair_list` is not None, the function *tests*
the given set of fiducial pairs for amplificational completeness, and
does not perform any search.
Parameters
----------
target_model : Model
The target model used to determine amplificational completeness.
prep_fiducials : list of Circuits
Fiducial circuits used to construct an informationally complete
effective preparation.
meas_fiducials : list of Circuits
Fiducial circuits used to construct an informationally complete
effective measurement.
germs : list of Circuits
The germ circuits that are repeated to amplify errors.
test_lengths : (L1,L2) tuple of ints, optional
A tuple of integers specifying the germ-power lengths to use when
checking for amplificational completeness.
prep_povm_tuples : 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 `target_model`.
tol : float, optional
The tolerance for the fraction of the expected amplification that must
be observed to call a parameter "amplified".
search_mode : {"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 `n_random` randomly chosen fiducial pair sets are
considered for each set size before the set is enlarged.
n_random : 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.
test_pair_list : list or None, optional
If not None, a list of (prepfid_index,measfid_index) tuples of integers,
specifying a list of fiducial pairs (indices are into `prep_fiducials` and
`meas_fiducials`, 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.)
mem_limit : int, optional
A memory limit in bytes.
minimum_pairs : 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 (prepfid_index,measfid_index) tuples of integers, specifying a list
of fiducial pairs (indices are into `prep_fiducials` and `meas_fiducials`).
"""
printer = _baseobjs.VerbosityPrinter.create_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 prep_povm_tuples == "first":
firstRho = list(target_model.preps.keys())[0]
firstPOVM = list(target_model.povms.keys())[0]
prep_povm_tuples = [(firstRho, firstPOVM)]
prep_povm_tuples = [(_circuits.Circuit((prepLbl,)), _circuits.Circuit((povmLbl,)))
for prepLbl, povmLbl in prep_povm_tuples]
def _get_derivs(length):
""" 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(prep_fiducials) * len(meas_fiducials))]
#contains lists of final leading-dim indices corresponding to each fiducial pair
dPall = [] # one element per germ, concatenate later
for iGerm, germ in enumerate(germs):
expGerm = _gsc.repeat_with_max_length(germ, length) # could pass exponent and set to germ**exp here
lst = _gsc.create_circuits(
"pp[0]+f0+expGerm+f1+pp[1]", f0=prep_fiducials, f1=meas_fiducials,
expGerm=expGerm, pp=prep_povm_tuples, order=('f0', 'f1', 'pp'))
resource_alloc = _baseobjs.ResourceAllocation(comm=None, mem_limit=mem_limit)
layout = target_model.sim.create_layout(lst, None, resource_alloc, array_types=('ep',), verbosity=0)
#FUTURE: assert that no instruments are allowed?
local_dP = layout.allocate_local_array('ep', 'd')
target_model.sim.bulk_fill_dprobs(local_dP, layout, None) # num_els x num_params
dP = local_dP.copy() # local == global (no layout.gather required) b/c we used comm=None above
layout.free_local_array(local_dP) # not needed - local_dP isn't shared (comm=None)
dPall.append(dP)
#Add this germ's element indices for each fiducial pair (final circuit of evTree)
nPrepPOVM = len(prep_povm_tuples)
for k in range(len(prep_fiducials) * len(meas_fiducials)):
for o in range(k * nPrepPOVM, (k + 1) * nPrepPOVM):
# "original" indices into lst for k-th fiducial pair
elArray = _slct.to_array(layout.indices_for_index(o)) + st
elIndicesForPair[k].extend(list(elArray))
st += layout.num_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, len0, len1, verb):
""" Return the number of amplified parameters """
printer = _baseobjs.VerbosityPrinter.create_printer(verb)
L_ratio = float(len1) / float(len0)
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 ?)" % (len0, len1, 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 = test_lengths[0]; dP0, elIndices0 = _get_derivs(L0)
L1 = test_lengths[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 test_pair_list 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(prep_fiducials) * len(meas_fiducials)
allPairIndices = list(range(nPossiblePairs))
nRhoStrs, nEStrs = len(prep_fiducials), len(meas_fiducials)
if test_pair_list is not None: # special mode for testing/debugging single pairlist
pairIndices0 = _np.concatenate([elIndices0[prepfid_index * nEStrs + iEStr]
for prepfid_index, iEStr in test_pair_list])
pairIndices1 = _np.concatenate([elIndices1[prepfid_index * nEStrs + iEStr]
for prepfid_index, iEStr in test_pair_list])
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(minimum_pairs, nPossiblePairs):
printer.log("Beginning search for a good set of %d pairs (%d pair lists to test)" %
(nNeededPairs, _nCr(nPossiblePairs, nNeededPairs)))
bestAmplified = 0
if search_mode == "sequential":
pairIndicesToIterateOver = _itertools.combinations(allPairIndices, nNeededPairs)
elif search_mode == "random":
_random.seed(seed) # ok if seed is None
nTotalPairCombos = _nCr(len(allPairIndices), nNeededPairs)
if n_random < nTotalPairCombos:
pairIndicesToIterateOver = [_random_combination(allPairIndices, nNeededPairs) for i in range(n_random)]
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:
prepfid_index = i // nEStrs
iEStr = i - prepfid_index * nEStrs
ret.append((prepfid_index, iEStr))
printer.log("Pair list %s ==> %d amplified parameters" % (" ".join(map(str, ret)), nAmplified))
if nAmplified == maxAmplified:
ret = []
for i in pairIndicesToTest:
prepfid_index = i // nEStrs
iEStr = i - prepfid_index * nEStrs
ret.append((prepfid_index, 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 = [(prepfid_index, iEStr)
for prepfid_index in range(nRhoStrs)
for iEStr in range(nEStrs)]
return listOfAllPairs
def find_sufficient_fiducial_pairs_per_germ(target_model, prep_fiducials, meas_fiducials,
germs, pre_povm_tuples="first",
search_mode="random", constrain_to_tp=True,
n_random=100, min_iterations=None, base_loweig_tol= 1e-1,
seed=None ,verbosity=0, num_soln_returned=1, type_soln_returned='best', retry_for_smaller=True,
mem_limit=None):
"""
Finds a per-germ set of fiducial pairs that are amplificationally complete.
A "standard" set of GST circuits consists of all circuits 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 `prep_fiducials` and `meas_fiducials` 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
----------
target_model : Model
The target model used to determine amplificational completeness.
prep_fiducials : list of Circuits
Fiducial circuits used to construct an informationally complete
effective preparation.
meas_fiducials : list of Circuits
Fiducial circuits used to construct an informationally complete
effective measurement.
germs : list of Circuits
The germ circuits that are repeated to amplify errors.
pre_povm_tuples : 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 `target_model`.
search_mode : {"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 `n_random` randomly chosen fiducial pair
sets are considered for each set size before the set is enlarged.
constrain_to_tp : 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.
n_random : int, optional
The number of random-pair-sets to consider for a given set size.
min_iterations : int, optional
A minimum number of candidate fiducial sets to try for a given
set size before allowing the search to exit early in the event
an acceptable candidate solution has already been found.
base_loweig_tol : float, optional (default 1e-1)
A relative threshold value for determining if a fiducial set
is an acceptable candidate solution. The tolerance value indicates
the decrease in the magnitude of the smallest eigenvalue of
the Jacobian we're will to accept relative to that of the full
fiducial set.
seed : int, optional
The seed to use for generating random-pair-sets.
verbosity : int, optional
How much detail to print to stdout.
num_soln_returned : int, optional
The number of candidate solutions to return for each run of the fiducial pair search.
type_soln_returned : str, optional
Which type of criteria to use when selecting which of potentially many candidate fiducial pairs to search through.
Currently only "best" supported which returns the num_soln_returned best candidates as measured by minimum eigenvalue.
retry_for_smaller : bool, optional
If true then a second search is performed seeded by the candidate solution sets found in the first pass.
The search routine then randomly subsamples sets of fiducial pairs from these candidate solutions to see if
a smaller subset will suffice.
mem_limit : int, optional
A memory limit in bytes.
Returns
-------
dict
A dictionary whose keys are the germ circuits and whose values are
lists of (iRhoFid,iMeasFid) tuples of integers, each specifying the
list of fiducial pairs for a particular germ (indices are into
`prep_fiducials` and `meas_fiducials`).
"""
printer = _baseobjs.VerbosityPrinter.create_printer(verbosity)
if pre_povm_tuples == "first":
firstRho = list(target_model.preps.keys())[0]
firstPOVM = list(target_model.povms.keys())[0]
pre_povm_tuples = [(firstRho, firstPOVM)]
#brief intercession to calculate the number of degrees of freedom for the povm.
num_effects= len(list(target_model.povms[pre_povm_tuples[0][1]].keys()))
dof_per_povm= num_effects-1
pre_povm_tuples = [(_circuits.Circuit((prepLbl,)), _circuits.Circuit((povmLbl,)))
for prepLbl, povmLbl in pre_povm_tuples]
pairListDict = {} # dict of lists of 2-tuples: one pair list per germ
if min_iterations is None:
min_iterations = min(n_random // 2, 1000) if search_mode == 'random' else 10 # HARDCODED
#also assert that the number of iterations is less than the number of random samples
if search_mode=='random':
assert(min_iterations<=n_random)
if (not retry_for_smaller) and (num_soln_returned>1):
warnings.warn('You are not retrying for smaller solutions, so returning more than 1 candidate solution is not useful.')
printer.log("------ Per Germ (L=1) Fiducial Pair Reduction --------")
with printer.progress_logging(1):
for i, germ in enumerate(germs):
#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 = target_model.copy()
gsGerm.set_all_parameterizations("static")
germMx = gsGerm.sim.product(germ)
gsGerm.operations["Ggerm"] = _EigenvalueParamDenseOp(
germMx, True, constrain_to_tp)
printer.show_progress(i, len(germs),
suffix='-- %s germ (%d params)' %
(repr(germ), gsGerm.num_params))
#Determine which fiducial-pair indices to iterate over
#initial run
candidate_solution_list, bestFirstEval = _get_per_germ_power_fidpairs(prep_fiducials, meas_fiducials, pre_povm_tuples,
gsGerm, 1, mem_limit,
printer, search_mode, seed, n_random, dof_per_povm,
min_iterations, base_loweig_tol, candidate_set_seed=None,
num_soln_returned=num_soln_returned, type_soln_returned=type_soln_returned)
#the algorithm isn't guaranteed to actually find the requested number of solutions, so check how many there actually are
#by checking the length of the list of returned eigenvalues.
actual_num_soln_returned= len(bestFirstEval)
printer.log('Found %d solutions out of %d requested.'%(actual_num_soln_returned, num_soln_returned),2)
#The goodPairList is now a dictionary with the keys corresponding to the minimum eigenvalue of the candidate solution.
#iterate through the values of the dictionary.
if retry_for_smaller:
printer.log('Resampling from returned solutions to search for smaller sets.',2)
assert(actual_num_soln_returned!=0)
updated_solns=[]
for candidate_solution in candidate_solution_list.values():
#now do a seeded run for each of the candidate solutions returned in the initial run:
#for these internal runs just return a single solution.
reducedPairlist, bestFirstEval = _get_per_germ_power_fidpairs(prep_fiducials, meas_fiducials, pre_povm_tuples,
gsGerm, 1, mem_limit,
printer, search_mode, seed, n_random, dof_per_povm,
min_iterations, base_loweig_tol, candidate_set_seed=candidate_solution,
num_soln_returned= 1, type_soln_returned='best')
#This should now return a dictionary with a single entry. Append that entry to a running list which we'll process at the end.
updated_solns.append(list(reducedPairlist.values())[0])
printer.log('Finished resampling from returned solutions to search for smaller sets.',2)
#At the very worst we should find that the updated solutions are the same length as the original candidate we seeded with
#(in fact, it would just return the seed in that case). So we should be able to just check for which of the lists of fiducial pairs is shortest.
solution_lengths= [len(fid_pair_list) for fid_pair_list in updated_solns]
#get the index of the minimum length set.
min_length_idx= _np.argmin(solution_lengths)
#set the value of goodPairList to be this value.
goodPairList= updated_solns[min_length_idx]
#print some output about the minimum eigenvalue acheived.
printer.log('Minimum Eigenvalue Achieved: %f' %(bestFirstEval[0]), 3)
else:
#take the first entry of the candidate solution list if there is more than one.
goodPairList= list(candidate_solution_list.values())[0]
bestFirstEval=bestFirstEval[0]
#print some output about the minimum eigenvalue acheived.
printer.log('Minimum Eigenvalue Achieved: %f' %(bestFirstEval), 3)
try:
assert(goodPairList is not None)
except AssertionError as err:
print('Failed to find an acceptable fiducial set for germ power pair: ', germ)
print(err)
pairListDict[germ] = goodPairList # add to final list of per-germ pairs
return pairListDict
def find_sufficient_fiducial_pairs_per_germ_greedy(target_model, prep_fiducials, meas_fiducials,
germs, pre_povm_tuples="first", constrain_to_tp=True,
inv_trace_tol= 10, initial_seed_mode='random',
evd_tol=1e-10, sensitivity_threshold=1e-10, seed=None ,verbosity=0, check_complete_fid_set=True,
mem_limit=None):
"""
Finds a per-germ set of fiducial pairs that are amplificationally complete.
A "standard" set of GST circuits consists of all circuits 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 `prep_fiducials` and `meas_fiducials` 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
----------
target_model : Model
The target model used to determine amplificational completeness.
prep_fiducials : list of Circuits
Fiducial circuits used to construct an informationally complete
effective preparation.
meas_fiducials : list of Circuits
Fiducial circuits used to construct an informationally complete
effective measurement.
germs : list of Circuits
The germ circuits that are repeated to amplify errors.
pre_povm_tuples : 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 `target_model`.
search_mode : {"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 `n_random` randomly chosen fiducial pair
sets are considered for each set size before the set is enlarged.
constrain_to_tp : 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.
n_random : int, optional
The number of random-pair-sets to consider for a given set size.
min_iterations : int, optional
A minimum number of candidate fiducial sets to try for a given
set size before allowing the search to exit early in the event
an acceptable candidate solution has already been found.
base_loweig_tol : float, optional (default 1e-1)
A relative threshold value for determining if a fiducial set
is an acceptable candidate solution. The tolerance value indicates
the decrease in the magnitude of the smallest eigenvalue of
the Jacobian we're will to accept relative to that of the full
fiducial set.
seed : int, optional
The seed to use for generating random-pair-sets.
verbosity : int, optional
How much detail to print to stdout.
num_soln_returned : int, optional
The number of candidate solutions to return for each run of the fiducial pair search.
type_soln_returned : str, optional
Which type of criteria to use when selecting which of potentially many candidate fiducial pairs to search through.
Currently only "best" supported which returns the num_soln_returned best candidates as measured by minimum eigenvalue.
retry_for_smaller : bool, optional
If true then a second search is performed seeded by the candidate solution sets found in the first pass.
The search routine then randomly subsamples sets of fiducial pairs from these candidate solutions to see if
a smaller subset will suffice.
mem_limit : int, optional
A memory limit in bytes.
Returns
-------
dict
A dictionary whose keys are the germ circuits and whose values are
lists of (iRhoFid,iMeasFid) tuples of integers, each specifying the
list of fiducial pairs for a particular germ (indices are into
`prep_fiducials` and `meas_fiducials`).
"""
printer = _baseobjs.VerbosityPrinter.create_printer(verbosity)
if pre_povm_tuples == "first":
firstRho = list(target_model.preps.keys())[0]
firstPOVM = list(target_model.povms.keys())[0]
pre_povm_tuples = [(firstRho, firstPOVM)]
#brief intercession to calculate the number of degrees of freedom for the povm.
num_effects= len(list(target_model.povms[pre_povm_tuples[0][1]].keys()))
dof_per_povm= num_effects-1
pre_povm_tuples = [(_circuits.Circuit((prepLbl,)), _circuits.Circuit((povmLbl,)))
for prepLbl, povmLbl in pre_povm_tuples]
pairListDict = {} # dict of lists of 2-tuples: one pair list per germ
printer.log("------ Per Germ (L=1) Fiducial Pair Reduction --------")
with printer.progress_logging(1):
for i, germ in enumerate(germs):
#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 = target_model.copy()
gsGerm.set_all_parameterizations("static")
germMx = gsGerm.sim.product(germ)
gsGerm.operations["Ggerm"] = _EigenvalueParamDenseOp(
germMx, True, constrain_to_tp)
printer.show_progress(i, len(germs),
suffix='-- %s germ (%d params)' %
(repr(germ), gsGerm.num_params))
#Determine which fiducial-pair indices to iterate over
#initial run
candidate_solution_list, best_score = _get_per_germ_power_fidpairs_greedy(prep_fiducials, meas_fiducials, pre_povm_tuples,
gsGerm, 1, mem_limit,
printer, seed, dof_per_povm,
inv_trace_tol, initial_seed_mode=initial_seed_mode,
check_complete_fid_set=check_complete_fid_set, evd_tol=evd_tol,
sensitivity_threshold=sensitivity_threshold)
#print some output about the minimum eigenvalue acheived.
printer.log('Score Achieved: ' + str(best_score), 2)
try:
assert(candidate_solution_list is not None)
except AssertionError as err:
print('Failed to find an acceptable fiducial set for germ power pair: ', germ)
print(err)
pairListDict[germ] = candidate_solution_list # add to final list of per-germ pairs
return pairListDict
def find_sufficient_fiducial_pairs_per_germ_power(target_model, prep_fiducials, meas_fiducials,
germs, max_lengths,
pre_povm_tuples="first",
search_mode="random", constrain_to_tp=True,
trunc_scheme="whole germ powers",
n_random=100, min_iterations=None, base_loweig_tol= 1e-1, seed=None,
verbosity=0, mem_limit=None, per_germ_candidate_set=None):
"""
Finds a per-germ set of fiducial pairs that are amplificationally complete.
A "standard" set of GST circuits consists of all circuits of the form:
Case: trunc_scheme == 'whole germ powers':
state_prep + prep_fiducial + pygsti.circuits.repeat_with_max_length(germ,L) + meas_fiducial + meas
Case: trunc_scheme == 'truncated germ powers':
state_prep + prep_fiducial + pygsti.circuits.repeat_and_truncate(germ,L) + meas_fiducial + meas
Case: trunc_scheme == 'length as exponent':
state_prep + prep_fiducial + germ^L + meas_fiducial + meas
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 `prep_fiducials` and `meas_fiducials` 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
----------
target_model : Model
The target model used to determine amplificational completeness.
prep_fiducials : list of Circuits
Fiducial circuits used to construct an informationally complete
effective preparation.
meas_fiducials : list of Circuits
Fiducial circuits used to construct an informationally complete
effective measurement.
germs : list of Circuits
The germ circuits that are repeated to amplify errors.
max_lengths: list of int
The germ powers (number of repetitions) to be used to amplify errors.
pre_povm_tuples : 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 `target_model`.
search_mode : {"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 `n_random` randomly chosen fiducial pair
sets are considered for each set size before the set is enlarged.
constrain_to_tp : 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.
n_random : int, optional
The number of random-pair-sets to consider for a given set size.
min_iterations: int, optional
The number of random-pair-sets to consider before we allow the algorithm
to terminate early if it has found an acceptable candidate fiducial set.
If left with the default value of None then this is 1/2 the value of
n_random.
base_loweig_tol: float, optional
A relative tolerance to apply to candidate fiducial pair sets.
Gives the multiplicative reduction in the magnitude of the minimum
eigenvalue relative to the value for the full fiducial set
the user is willing to tolerate.
seed : int, optional
The seed to use for generating random-pair-sets.
verbosity : int, optional
How much detail to print to stdout.
mem_limit : int, optional
A memory limit in bytes.
per_germ_candidate_set : dict, optional
If specified, this is a dictionary with keys given by the germ set. This dictionary
is a previously found candidate set of fiducials output from the per-germ FPR function
find_sufficient_fiducial_pairs_per_germ.
Returns
-------
dict
A dictionary whose keys are the germ circuits and whose values are
lists of (iRhoFid,iMeasFid) tuples of integers, each specifying the
list of fiducial pairs for a particular germ (indices are into
`prep_fiducials` and `meas_fiducials`).
"""
printer = _baseobjs.VerbosityPrinter.create_printer(verbosity)
if pre_povm_tuples == "first":
firstRho = list(target_model.preps.keys())[0]
firstPOVM = list(target_model.povms.keys())[0]
pre_povm_tuples = [(firstRho, firstPOVM)]
pre_povm_tuples = [(_circuits.Circuit((prepLbl,)), _circuits.Circuit((povmLbl,)))
for prepLbl, povmLbl in pre_povm_tuples]
pairListDict = {} # dict of lists of 2-tuples: one pair list per germ
low_eigvals = {}
#base_loweig_threshold = 1e-2 # HARDCODED
#Check whether the user has passed in a candidate set as a seed from a previous run of
#per-germ FPR.
if per_germ_candidate_set is not None:
#in that case check that all of the germs are accounted for.
try:
assert(set(per_germ_candidate_set.keys()) == set(germs))
except AssertionError as err:
print('Candidate germs in seed set not equal to germs passed into this function.')
print(err)
printer.log("------ Per Germ-Power Fiducial Pair Reduction --------")
printer.log(" Using %s germ power truncation scheme" % trunc_scheme)
with printer.progress_logging(1):
for i, germ_power in enumerate(_itertools.product(germs, max_lengths)):
germ, L = germ_power
# TODO: Could check for when we become identity and skip rest
#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 = target_model.copy()
gsGerm.set_all_parameterizations("static")
germMx = gsGerm.sim.product(germ)
gsGerm.operations["Ggerm"] = _EigenvalueParamDenseOp(
germMx, True, constrain_to_tp)
# SS: Difference from _per_germ version
if trunc_scheme == "whole germ powers":
power = _gsc.repeat_count_with_max_length(germ, L)
# TODO: Truncation doesn't work nicely with a single "germ"
#elif trunc_scheme == "truncated germ powers":
# germPowerCirc = _gsc.repeat_and_truncate(germ, L)
elif trunc_scheme == "length as exponent":
power = L
else:
raise ValueError("Truncation scheme %s not allowed" % trunc_scheme)
if power == 0:
# Skip empty circuits (i.e. germ^power > max_length)
printer.show_progress(i, len(germs) * len(max_lengths),
suffix='-- %s germ skipped since longer than max length %d' %
(repr(germ), L))
continue
printer.show_progress(i, len(germs) * len(max_lengths),
suffix='-- %s germ, %d L (%d params)' %
(repr(germ), L, gsGerm.num_params))
#Determine which fiducial-pair indices to iterate over
#TODO: Evaluate the value of the minimum number of iterations before the algorithm for
#getting candidate fiducial pairs is allowed to exit early.
if min_iterations is None:
min_iterations = min(n_random // 2, 1000) if search_mode == 'random' else 10 # HARDCODED
#also assert that the number of iterations is less than the number of random samples
if search_mode=='random':
assert(min_iterations<=n_random)
#if there is a candidate fiducial seed set pass that in, otherwise pass in None.
if per_germ_candidate_set is not None:
candidate_set_seed= per_germ_candidate_set[germ]
else:
candidate_set_seed= None
goodPairList, _ = _get_per_germ_power_fidpairs(prep_fiducials, meas_fiducials, pre_povm_tuples,
gsGerm, power, mem_limit,
printer, search_mode, seed, n_random,
min_iterations, base_loweig_tol, candidate_set_seed,
num_soln_returned=1, type_soln_returned='best')
#This should now return a dictionary with a single entry. pull that entry out.
goodPairList= list(goodPairList.values())[0]
try:
assert(goodPairList is not None)
except AssertionError as err:
print('Failed to find an acceptable fiducial set for germ power pair: ', germ_power)
print(err)
pairListDict[germ_power] = goodPairList # add to final list of per-germ-power pairs
return pairListDict
def test_fiducial_pairs(fid_pairs, target_model, prep_fiducials, meas_fiducials, germs,
test_lengths=(256, 2048), pre_povm_tuples="first", tol=0.75,
verbosity=0, mem_limit=None):
"""
Tests a set of global or per-germ fiducial pairs.
Determines how many model parameters (of `target_model`) are
amplified by the fiducial pairs given by `fid_pairs`, which can be
either a list of 2-tuples (for global-FPR) or a dictionary (for
per-germ FPR).
Parameters
----------
fid_pairs : 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).
target_model : Model
The target model used to determine amplificational completeness.
prep_fiducials : list of Circuits
Fiducial circuits used to construct an informationally complete
effective preparation.
meas_fiducials : list of Circuits
Fiducial circuits used to construct an informationally complete
effective measurement.
germs : list of Circuits
The germ circuits that are repeated to amplify errors.
test_lengths : (L1,L2) tuple of ints, optional
A tuple of integers specifying the germ-power lengths to use when
checking for amplificational completeness.
pre_povm_tuples : 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 `target_model`.
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.
mem_limit : int, optional
A memory limit in bytes.
Returns
-------
numAmplified : int
"""
printer = _baseobjs.VerbosityPrinter.create_printer(verbosity)
if pre_povm_tuples == "first":
firstRho = list(target_model.preps.keys())[0]
firstPOVM = list(target_model.povms.keys())[0]
pre_povm_tuples = [(firstRho, firstPOVM)]
pre_povm_tuples = [(_circuits.Circuit((prepLbl,)), _circuits.Circuit((povmLbl,)))
for prepLbl, povmLbl in pre_povm_tuples]
def _get_derivs(length):
""" 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 germs:
expGerm = _gsc.repeat_with_max_length(germ, length) # could pass exponent and set to germ**exp here
pairList = fid_pairs[germ] if isinstance(fid_pairs, dict) else fid_pairs
circuits += _gsc.create_circuits("pp[0]+p[0]+expGerm+p[1]+pp[1]",
p=[(prep_fiducials[i], meas_fiducials[j]) for i, j in pairList],
pp=pre_povm_tuples, expGerm=expGerm, order=['p', 'pp'])
circuits = _remove_duplicates(circuits)
resource_alloc = _baseobjs.ResourceAllocation(comm=None, mem_limit=mem_limit)
layout = target_model.sim.create_layout(circuits, None, resource_alloc, array_types=('ep',), verbosity=0)
local_dP = layout.allocate_local_array('ep', 'd')
target_model.sim.bulk_fill_dprobs(local_dP, layout, None)
dP = local_dP.copy() # local == global (no layout.gather required) b/c we used comm=None above
layout.free_local_array(local_dP) # not needed - local_dP isn't shared (comm=None)
return dP
def _get_number_amplified(m0, m1, len0, len1):
""" Return the number of amplified parameters """
L_ratio = float(len1) / float(len0)
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 ?)" % (len0, len1, tol), 4)
for i, (v0, v1) in enumerate(zip(sorted(s0, reverse=True), sorted(s1, reverse=True))):