Skip to content

Commit 341b279

Browse files
authored
Merge pull request #615 from sandialabs/metrics-exception-handling
à la carte standard GST reports, and exception handling for computing metrics
2 parents 0be9a1e + b1e93ea commit 341b279

File tree

7 files changed

+147
-88
lines changed

7 files changed

+147
-88
lines changed

pygsti/baseobjs/basisconstructors.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ def mut(i, j, n):
5959

6060

6161
def _check_dim(dim):
62-
global MAX_BASIS_MATRIX_DIM
6362
if not isinstance(dim, _numbers.Integral):
6463
dim = max(dim) # assume dim is a list/tuple of dims & just consider max
6564
if dim > MAX_BASIS_MATRIX_DIM:

pygsti/forwardsims/termforwardsim_calc_generic.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ def prs_as_polynomials(fwdsim, rholabel, elabels, circuit, polynomial_vindices_p
115115
# for term in termlist:
116116
# print("Coeff: ",str(term.coeff))
117117

118-
global DEBUG_FCOUNT # DEBUG!!!
118+
# global DEBUG_FCOUNT # DEBUG!!!
119119
# db_part_cnt = 0
120120
# db_factor_cnt = 0
121121
#print("DB: pr_as_poly for ",str(tuple(map(str,circuit))), " max_order=",fwdsim.max_order)

pygsti/optimize/simplerlm.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -560,7 +560,7 @@ def simplish_leastsq(
560560
Jac = jac_guarded(k, num_fd_iters, obj_fn, jac_fn, f, ari, global_x, fdJac)
561561

562562
if profiler:
563-
jac_gb = Jac.nbytes/(1024.0**3) if hasattr(Jac, 'nbytes') else _np.NaN
563+
jac_gb = Jac.nbytes/(1024.0**3) if hasattr(Jac, 'nbytes') else _np.nan
564564
vals = ((f.size, global_x.size), jac_gb)
565565
profiler.memory_check("simplish_leastsq: after jacobian: shape=%s, GB=%.2f" % vals)
566566

pygsti/report/factory.py

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1197,13 +1197,36 @@ def construct_standard_report(results, title="auto",
11971197
- idt_idle_oplabel : Label, optional
11981198
The label identifying the idle gate (for use with idle tomography).
11991199
1200+
- skip_sections : tuple[str], optional
1201+
Contains names of standard report sections that should be skipped
1202+
in this particular report. Strings will be cast to lowercase,
1203+
stripped of white space, and then mapped to omitted Section classes
1204+
as follows
1205+
{
1206+
'summary' : SummarySection,
1207+
'goodness' : GoodnessSection,
1208+
'colorbox' : GoodnessColorBoxPlotSection,
1209+
'invariantgates' : GaugeInvariantsGatesSection,
1210+
'invariantgerms' : GaugeInvariantsGermsSection,
1211+
'variant' : GaugeVariantSection,
1212+
'variantraw' : GaugeVariantsRawSection,
1213+
'variantdecomp' : GaugeVariantsDecompSection,
1214+
'varianterrorgen' : GaugeVariantsErrorGenSection,
1215+
'input' : InputSection,
1216+
'meta' : MetaSection,
1217+
'help' : HelpSection
1218+
}
1219+
1220+
A KeyError will be raised if skip_sections contains a string
1221+
that is not in the keys of the above dict (after casting to
1222+
lower case and stripping white space).
1223+
12001224
verbosity : int, optional
12011225
How much detail to send to stdout.
12021226
12031227
Returns
12041228
-------
1205-
Workspace
1206-
The workspace object used to create the report
1229+
Report
12071230
"""
12081231

12091232
printer = _VerbosityPrinter.create_printer(verbosity, comm=comm)
@@ -1265,20 +1288,30 @@ def construct_standard_report(results, title="auto",
12651288
flags.add('CombineRobust')
12661289

12671290
# build section list
1268-
sections = [
1269-
_section.SummarySection(),
1270-
_section.GoodnessSection(),
1271-
_section.GoodnessColorBoxPlotSection(),
1272-
_section.GaugeInvariantsGatesSection(),
1273-
_section.GaugeInvariantsGermsSection(),
1274-
_section.GaugeVariantSection(),
1275-
_section.GaugeVariantsRawSection(),
1276-
_section.GaugeVariantsDecompSection(),
1277-
_section.GaugeVariantsErrorGenSection(),
1278-
_section.InputSection(),
1279-
_section.MetaSection(),
1280-
_section.HelpSection()
1281-
]
1291+
possible_sections = {
1292+
'summary' : _section.SummarySection(),
1293+
'goodness' : _section.GoodnessSection(),
1294+
'colorbox' : _section.GoodnessColorBoxPlotSection(),
1295+
'invariantgates' : _section.GaugeInvariantsGatesSection(),
1296+
'invariantgerms' : _section.GaugeInvariantsGermsSection(),
1297+
'variant' : _section.GaugeVariantSection(),
1298+
'variantraw' : _section.GaugeVariantsRawSection(),
1299+
'variantdecomp' : _section.GaugeVariantsDecompSection(),
1300+
'varianterrorgen' : _section.GaugeVariantsErrorGenSection(),
1301+
'input' : _section.InputSection(),
1302+
'meta' : _section.MetaSection(),
1303+
'help' : _section.HelpSection()
1304+
}
1305+
1306+
skip_sections = advanced_options.get('skip_sections', tuple())
1307+
if skip_sections:
1308+
if isinstance(skip_sections, str):
1309+
skip_sections = [skip_sections]
1310+
skip_sections = [s.lower().replace(' ','') for s in skip_sections]
1311+
for s in skip_sections:
1312+
possible_sections.pop(s)
1313+
sections = list(possible_sections.values())
1314+
# ^ This whole process won't affect ordering of objects in "sections".
12821315

12831316
if 'ShowScaling' in flags:
12841317
sections.append(_section.GoodnessScalingSection())

pygsti/report/reportables.py

Lines changed: 59 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -353,8 +353,7 @@ def rel_circuit_eigenvalues(model_a, model_b, circuit):
353353
"""
354354
A = model_a.sim.product(circuit) # "gate"
355355
B = model_b.sim.product(circuit) # "target gate"
356-
rel_op = _np.dot(_np.linalg.inv(B), A) # "relative gate" == target^{-1} * gate
357-
return _np.linalg.eigvals(rel_op)
356+
return rel_eigenvalues(A, B, None)
358357

359358

360359
Rel_circuit_eigenvalues = _modf.modelfn_factory(rel_circuit_eigenvalues)
@@ -921,13 +920,16 @@ def upper_bound_fidelity(gate, mx_basis):
921920
gate : numpy.ndarray
922921
the transfer-matrix specifying a gate's action.
923922
924-
mx_basis : Basis or {'pp', 'gm', 'std'}
925-
the basis that `gate` is in.
923+
mx_basis : Basis or string
924+
Currently restricted to Pauli-product
926925
927926
Returns
928927
-------
929928
float
930929
"""
930+
basis_str = mx_basis if isinstance(mx_basis, str) else mx_basis.name
931+
if basis_str != 'pp':
932+
raise NotImplementedError(f'Basis must be Pauli-Product, got {mx_basis}.')
931933
return _tools.fidelity_upper_bound(gate)[0]
932934

933935

@@ -1318,8 +1320,12 @@ def std_unitarity(a, b, mx_basis):
13181320
-------
13191321
float
13201322
"""
1321-
Lambda = _np.dot(a, _np.linalg.inv(b))
1322-
return _tools.unitarity(Lambda, mx_basis)
1323+
try:
1324+
Lambda = _np.dot(a, _np.linalg.inv(b))
1325+
return _tools.unitarity(Lambda, mx_basis)
1326+
except _np.linalg.LinAlgError as e:
1327+
_warnings.warn(str(e))
1328+
return _np.nan
13231329

13241330

13251331
def eigenvalue_unitarity(a, b):
@@ -1338,10 +1344,14 @@ def eigenvalue_unitarity(a, b):
13381344
-------
13391345
float
13401346
"""
1341-
Lambda = _np.dot(a, _np.linalg.inv(b))
1342-
d2 = Lambda.shape[0]
1343-
lmb = _np.linalg.eigvals(Lambda)
1344-
return float(_np.real(_np.linalg.norm(lmb)**2) - 1.0) / (d2 - 1.0)
1347+
try:
1348+
Lambda = _np.dot(a, _np.linalg.inv(b))
1349+
d2 = Lambda.shape[0]
1350+
lmb = _np.linalg.eigvals(Lambda)
1351+
return float(_np.real(_np.linalg.norm(lmb)**2) - 1.0) / (d2 - 1.0)
1352+
except _np.linalg.LinAlgError as e:
1353+
_warnings.warn(str(e))
1354+
return _np.nan
13451355

13461356

13471357
def nonunitary_entanglement_infidelity(a, b, mx_basis):
@@ -1700,9 +1710,13 @@ def rel_eigenvalues(a, b, mx_basis):
17001710
-------
17011711
numpy.ndarray
17021712
"""
1703-
target_op_inv = _np.linalg.inv(b)
1704-
rel_op = _np.dot(target_op_inv, a)
1705-
return _np.linalg.eigvals(rel_op).astype("complex") # since they generally *can* be complex
1713+
try:
1714+
target_op_inv = _np.linalg.inv(b)
1715+
rel_op = _np.dot(target_op_inv, a)
1716+
return _np.linalg.eigvals(rel_op).astype("complex") # since they generally *can* be complex
1717+
except _np.linalg.LinAlgError as e:
1718+
_warnings.warn(str(e))
1719+
return _np.nan * _np.ones(a.shape)
17061720

17071721

17081722
Rel_eigvals = _modf.opsfn_factory(rel_eigenvalues)
@@ -1790,28 +1804,8 @@ def rel_log_diff_eigenvalues(a, b, mx_basis):
17901804
# init args == (model1, model2, op_label)
17911805

17921806

1793-
def rel_gate_eigenvalues(a, b, mx_basis): # DUPLICATE of rel_eigenvalues TODO
1794-
"""
1795-
Eigenvalues of b^{-1} * a
1796-
1797-
Parameters
1798-
----------
1799-
a : numpy.ndarray
1800-
The first process (transfer) matrix.
1801-
1802-
b : numpy.ndarray
1803-
The second process (transfer) matrix.
1804-
1805-
mx_basis : Basis or {'pp', 'gm', 'std'}
1806-
the basis that `a` and `b` are in.
1807-
1808-
Returns
1809-
-------
1810-
numpy.ndarray
1811-
"""
1812-
rel_op = _np.dot(_np.linalg.inv(b), a) # "relative gate" == target^{-1} * gate
1813-
return _np.linalg.eigvals(rel_op).astype("complex") # since they generally *can* be complex
1814-
1807+
rel_gate_eigenvalues = rel_eigenvalues
1808+
# ^ An alias.
18151809

18161810
Rel_gate_eigenvalues = _modf.opsfn_factory(rel_gate_eigenvalues)
18171811
# init args == (model1, model2, op_label)
@@ -2157,21 +2151,37 @@ def general_decomposition(model_a, model_b):
21572151
gl = str(gl) # Label -> str for decomp-dict keys
21582152

21592153
target_evals = _np.linalg.eigvals(targetOp)
2160-
if _np.any(_np.isclose(target_evals, -1.0)):
2161-
target_logG = _tools.unitary_superoperator_matrix_log(targetOp, mxBasis)
2162-
logG = _tools.approximate_matrix_log(gate, target_logG)
2163-
else:
2164-
logG = _tools.real_matrix_log(gate, "warn")
2165-
if _np.linalg.norm(logG.imag) > 1e-6:
2166-
_warnings.warn("Truncating imaginary logarithm!")
2167-
logG = _np.real(logG)
2154+
failed = False
2155+
try:
2156+
if _np.any(_np.isclose(target_evals, -1.0)):
2157+
target_logG = _tools.unitary_superoperator_matrix_log(targetOp, mxBasis)
2158+
logG = _tools.approximate_matrix_log(gate, target_logG)
2159+
else:
2160+
logG = _tools.real_matrix_log(gate, "warn")
2161+
if _np.linalg.norm(logG.imag) > 1e-6:
2162+
_warnings.warn("Truncating imaginary logarithm!")
2163+
logG = _np.real(logG)
2164+
except (_np.linalg.LinAlgError, AssertionError) as e:
2165+
_warnings.warn(str(e))
2166+
logG = _np.nan * _np.ones(gate.shape)
2167+
failed = True
2168+
2169+
proj_basis = _Basis.cast('PP', model_a.dim) if model_a.state_space.is_entirely_qubits else mxBasis
2170+
basis_mxs = proj_basis.elements
2171+
blk = _LindbladCoefficientBlock('ham', proj_basis)
2172+
num_elem_errgens = len(blk.elementary_errorgens)
2173+
2174+
if failed:
2175+
decomp[gl + ' log inexactness'] = _np.nan
2176+
decomp[gl + ' axis' ] = _np.nan * _np.ones(num_elem_errgens)
2177+
decomp[gl + ' angle'] = _np.nan
2178+
decomp[gl + ' hamiltonian eigenvalues'] = _np.nan * _np.ones(basis_mxs[0].shape[0])
2179+
continue
21682180

21692181
decomp[gl + ' log inexactness'] = _np.linalg.norm(_spl.expm(logG) - gate)
21702182

21712183
#hamProjs, hamGens = _tools.std_errorgen_projections(
21722184
# logG, "hamiltonian", mxBasis, mxBasis, return_generators=True)
2173-
proj_basis = _Basis.cast('PP', model_a.dim) if model_a.state_space.is_entirely_qubits else mxBasis
2174-
blk = _LindbladCoefficientBlock('ham', proj_basis)
21752185
blk.set_from_errorgen_projections(logG, mxBasis)
21762186
hamProjs = blk.block_data
21772187
#hamGens = blk.create_lindblad_term_superoperators(mxBasis)
@@ -2185,7 +2195,6 @@ def general_decomposition(model_a, model_b):
21852195
# to *twice* this coefficient (e.g. a X(pi/2) rotn is exp( i pi/4 X ) ),
21862196
# thus the factor of 2.0 above.
21872197

2188-
basis_mxs = proj_basis.elements
21892198
# REMOVE scalings = [(_np.linalg.norm(hamGens[i]) / _np.linalg.norm(_tools.hamiltonian_to_lindbladian(mx))
21902199
# REMOVE if _np.linalg.norm(hamGens[i]) > 1e-10 else 0.0)
21912200
# REMOVE for i, mx in enumerate(basis_mxs)]
@@ -2198,6 +2207,9 @@ def general_decomposition(model_a, model_b):
21982207
for gl_other in opLabels:
21992208
rotnAngle = decomp[str(gl) + ' angle']
22002209
rotnAngle_other = decomp[str(gl_other) + ' angle']
2210+
if _np.isnan(rotnAngle) or _np.isnan(rotnAngle_other):
2211+
decomp[str(gl) + "," + str(gl_other) + " axis angle"] = _np.nan
2212+
continue
22012213

22022214
if gl == gl_other or abs(rotnAngle) < 1e-4 or abs(rotnAngle_other) < 1e-4:
22032215
decomp[str(gl) + "," + str(gl_other) + " axis angle"] = 10000.0 # sentinel for irrelevant angle
@@ -2439,7 +2451,7 @@ def info_of_opfn_by_name(name):
24392451
'respectively'),
24402452
"trace": ("1/2 Trace|Distance",
24412453
"0.5 | Chi(A) - Chi(B) |_tr"),
2442-
"diamond": ("1/2 Diamond-Dist",
2454+
"diamond": ("1/2 Diamond|Distance",
24432455
"0.5 sup | (1 x (A-B))(rho) |_tr"),
24442456
"nuinf": ("Non-unitary|Ent. Infidelity",
24452457
"(d^2-1)/d^2 [1 - sqrt( unitarity(A B^-1) )]"),

pygsti/tools/optools.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -498,8 +498,8 @@ def entanglement_fidelity(a, b, mx_basis='pp', is_tp=None, is_unitary=None):
498498

499499
#if the tp flag isn't set we'll calculate whether it is true here
500500
if is_tp is None:
501-
def is_tp_fn(x): return _np.isclose(x[0, 0], 1.0) and all(
502-
[_np.isclose(x[0, i], 0) for i in range(1,d2)])
501+
def is_tp_fn(x):
502+
return _np.isclose(x[0, 0], 1.0) and _np.allclose(x[0, 1:d2], 0)
503503

504504
is_tp= (is_tp_fn(a) and is_tp_fn(b))
505505

@@ -973,9 +973,14 @@ def povm_fidelity(model, target_model, povmlbl):
973973
-------
974974
float
975975
"""
976-
povm_mx = compute_povm_map(model, povmlbl)
977-
target_povm_mx = compute_povm_map(target_model, povmlbl)
978-
return entanglement_fidelity(povm_mx, target_povm_mx, target_model.basis)
976+
try:
977+
povm_mx = compute_povm_map(model, povmlbl)
978+
target_povm_mx = compute_povm_map(target_model, povmlbl)
979+
return entanglement_fidelity(povm_mx, target_povm_mx, target_model.basis)
980+
except AssertionError as e:
981+
se = str(e)
982+
assert '`dim` must be a perfect square' in se, se
983+
return _np.nan
979984

980985

981986
def povm_jtracedist(model, target_model, povmlbl):
@@ -997,9 +1002,14 @@ def povm_jtracedist(model, target_model, povmlbl):
9971002
-------
9981003
float
9991004
"""
1000-
povm_mx = compute_povm_map(model, povmlbl)
1001-
target_povm_mx = compute_povm_map(target_model, povmlbl)
1002-
return jtracedist(povm_mx, target_povm_mx, target_model.basis)
1005+
try:
1006+
povm_mx = compute_povm_map(model, povmlbl)
1007+
target_povm_mx = compute_povm_map(target_model, povmlbl)
1008+
return jtracedist(povm_mx, target_povm_mx, target_model.basis)
1009+
except AssertionError as e:
1010+
se = str(e)
1011+
assert '`dim` must be a perfect square' in se, se
1012+
return _np.nan
10031013

10041014

10051015
def povm_diamonddist(model, target_model, povmlbl):
@@ -1785,13 +1795,15 @@ def extract_elementary_errorgen_coefficients(errorgen, elementary_errorgen_label
17851795
"""
17861796
# the same as decompose_errorgen but given a dict/list of elementary errorgens directly instead of a basis and type
17871797
if isinstance(errorgen_basis, _Basis):
1788-
errorgen_std = _bt.change_basis(errorgen, errorgen_basis, errorgen_basis.create_equivalent('std'))
1798+
std_basis = errorgen_basis.create_equivalent('std')
1799+
errorgen_std = _bt.change_basis(errorgen, errorgen_basis, std_basis)
17891800

1790-
#expand operation matrix so it acts on entire space of dmDim x dmDim density matrices
1791-
errorgen_std = _bt.resize_std_mx(errorgen_std, 'expand', errorgen_basis.create_equivalent('std'),
1792-
errorgen_basis.create_simple_equivalent('std'))
1801+
# Expand operation matrix so it acts on entire space of dmDim x dmDim density matrices
1802+
expanded_std_basis = errorgen_basis.create_simple_equivalent('std')
1803+
errorgen_std = _bt.resize_std_mx(errorgen_std, 'expand', std_basis, expanded_std_basis)
17931804
else:
17941805
errorgen_std = _bt.change_basis(errorgen, errorgen_basis, "std")
1806+
17951807
flat_errorgen_std = errorgen_std.toarray().ravel() if _sps.issparse(errorgen_std) else errorgen_std.ravel()
17961808

17971809
d2 = errorgen_std.shape[0]

pygsti/tools/rbtheory.py

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,7 @@ def predicted_rb_number(model, target_model, weights=None, d=None, rtype='EI'):
9090
"""
9191
if d is None: d = int(round(_np.sqrt(model.dim)))
9292
p = predicted_rb_decay_parameter(model, target_model, weights=weights)
93-
r = _rbtls.p_to_r(p, d=d, rtype=rtype)
93+
r = _rbtls.p_to_r(p, d=d, rtype=rtype) if _np.isnan(p) else _np.nan
9494
return r
9595

9696

@@ -132,15 +132,18 @@ def predicted_rb_decay_parameter(model, target_model, weights=None):
132132
The second largest eigenvalue of L. This is the RB decay parameter
133133
for various types of RB.
134134
"""
135-
L = L_matrix(model, target_model, weights=weights)
136-
E = _np.absolute(_np.linalg.eigvals(L))
137-
E = _np.flipud(_np.sort(E))
138-
if abs(E[0] - 1) > 10**(-12):
139-
_warnings.warn("Output may be unreliable because the model is not approximately trace-preserving.")
140-
141-
if E[1].imag > 10**(-10):
142-
_warnings.warn("Output may be unreliable because the RB decay constant has a significant imaginary component.")
143-
p = abs(E[1])
135+
try:
136+
L = L_matrix(model, target_model, weights=weights)
137+
E = _np.absolute(_np.linalg.eigvals(L))
138+
E = _np.flipud(_np.sort(E))
139+
if abs(E[0] - 1) > 10**(-12):
140+
_warnings.warn("Output may be unreliable because the model is not approximately trace-preserving.")
141+
142+
if E[1].imag > 10**(-10):
143+
_warnings.warn("Output may be unreliable because the RB decay constant has a significant imaginary component.")
144+
p = abs(E[1])
145+
except _np.linalg.LinAlgError:
146+
p = _np.nan
144147
return p
145148

146149

0 commit comments

Comments
 (0)