diff --git a/pygsti/optimize/optimize.py b/pygsti/optimize/optimize.py index d63550e28..a0bed3ef5 100644 --- a/pygsti/optimize/optimize.py +++ b/pygsti/optimize/optimize.py @@ -159,10 +159,7 @@ def _basin_callback(x, f, accept): elif method == "L-BFGS-B": opts['gtol'] = opts['ftol'] = tol # gradient norm and fractional y-tolerance elif method == "Nelder-Mead": opts['maxfev'] = maxfev # max fn evals (note: ftol and xtol can also be set) - if method in ("BFGS", "CG", "Newton-CG", "L-BFGS-B", "TNC", "SLSQP", "dogleg", "trust-ncg"): # use jacobian - solution = _spo.minimize(fn, x0, options=opts, method=method, tol=tol, callback=callback, jac=jac) - else: - solution = _spo.minimize(fn, x0, options=opts, method=method, tol=tol, callback=callback) + solution = _spo.minimize(fn, x0, options=opts, method=method, tol=tol, callback=callback, jac=jac) return solution diff --git a/pygsti/protocols/gst.py b/pygsti/protocols/gst.py index fe119cd1b..2353bd31f 100644 --- a/pygsti/protocols/gst.py +++ b/pygsti/protocols/gst.py @@ -857,6 +857,14 @@ class GSTGaugeOptSuite(_NicelySerializable): given by the target model, which are used as the default when `gaugeopt_target` is None. """ + + STANDARD_SUITENAMES = ("stdgaugeopt", "stdgaugeopt-unreliable2Q", "stdgaugeopt-tt", "stdgaugeopt-safe", + "stdgaugeopt-noconversion", "stdgaugeopt-noconversion-safe") + + SPECIAL_SUITENAMES = ("varySpam", "varySpamWt", "varyValidSpamWt", "toggleValidSpam", + "varySpam-unreliable2Q", "varySpamWt-unreliable2Q", + "varyValidSpamWt-unreliable2Q", "toggleValidSpam-unreliable2Q") + @classmethod def cast(cls, obj): if obj is None: @@ -872,14 +880,13 @@ def cast(cls, obj): def __init__(self, gaugeopt_suite_names=None, gaugeopt_argument_dicts=None, gaugeopt_target=None): super().__init__() - if gaugeopt_suite_names is not None: - if gaugeopt_suite_names == 'none': - self.gaugeopt_suite_names = None - else: - self.gaugeopt_suite_names = (gaugeopt_suite_names,) \ - if isinstance(gaugeopt_suite_names, str) else tuple(gaugeopt_suite_names) - else: + if gaugeopt_suite_names is None or gaugeopt_suite_names == 'none': self.gaugeopt_suite_names = None + elif isinstance(gaugeopt_suite_names, str): + self.gaugeopt_suite_names = (gaugeopt_suite_names,) + else: + self.gaugeopt_suite_names = tuple(gaugeopt_suite_names) + if gaugeopt_argument_dicts is not None: self.gaugeopt_argument_dicts = gaugeopt_argument_dicts.copy() @@ -985,92 +992,98 @@ def to_dictionary(self, model, unreliable_ops=(), verbosity=0): return gaugeopt_suite_dict - def _update_gaugeopt_dict_from_suitename(self, gaugeopt_suite_dict, root_lbl, suite_name, model, - unreliable_ops, printer): - if suite_name in ("stdgaugeopt", "stdgaugeopt-unreliable2Q", "stdgaugeopt-tt", "stdgaugeopt-safe", - "stdgaugeopt-noconversion", "stdgaugeopt-noconversion-safe"): + @staticmethod + def _update_gaugeopt_dict_from_suitename(gaugeopt_suite_dict, root_lbl, suite_name, model, unreliable_ops, printer): + + if suite_name in GSTGaugeOptSuite.STANDARD_SUITENAMES: - stages = [] # multi-stage gauge opt gg = model.default_gauge_group - convert_to = {'to_type': "full TP", 'flatten_structure': True, 'set_default_gauge_group': True} \ + + if gg is None: + return + + stages = [] # multi-stage gauge opt + + from pygsti.models.gaugegroup import TrivialGaugeGroup, UnitaryGaugeGroup, \ + SpamGaugeGroup, TPSpamGaugeGroup + + convert_to = {'to_type': "full", 'flatten_structure': True, 'set_default_gauge_group': False} \ if ('noconversion' not in suite_name and gg.name not in ("Full", "TP")) else None - if isinstance(gg, _models.gaugegroup.TrivialGaugeGroup) and convert_to is None: + if isinstance(gg, TrivialGaugeGroup) and convert_to is None: if suite_name == "stdgaugeopt-unreliable2Q" and model.dim == 16: - if any([gl in model.operations.keys() for gl in unreliable_ops]): + if any([gl in model.operations for gl in unreliable_ops]): gaugeopt_suite_dict[root_lbl] = {'verbosity': printer} else: #just do a single-stage "trivial" gauge opts using default group gaugeopt_suite_dict[root_lbl] = {'verbosity': printer} - - elif gg is not None: - metric = 'frobeniustt' if suite_name == 'stdgaugeopt-tt' else 'frobenius' - - #Stage 1: plain vanilla gauge opt to get into "right ballpark" - if gg.name in ("Full", "TP"): - stages.append( - { - 'gates_metric': metric, 'spam_metric': metric, - 'item_weights': {'gates': 1.0, 'spam': 1.0}, - 'verbosity': printer - }) - - #Stage 2: unitary gauge opt that tries to nail down gates (at - # expense of spam if needed) - stages.append( - { - 'convert_model_to': convert_to, - 'gates_metric': metric, 'spam_metric': metric, - 'item_weights': {'gates': 1.0, 'spam': 0.0}, - 'gauge_group': _models.gaugegroup.UnitaryGaugeGroup(model.state_space, - model.basis, model.evotype), - 'oob_check_interval': 1 if ('-safe' in suite_name) else 0, - 'verbosity': printer - }) - - #Stage 3: spam gauge opt that fixes spam scaling at expense of - # non-unital parts of gates (but shouldn't affect these - # elements much since they should be small from Stage 2). - s3gg = _models.gaugegroup.SpamGaugeGroup if (gg.name == "Full") else \ - _models.gaugegroup.TPSpamGaugeGroup - stages.append( - { - 'convert_model_to': convert_to, - 'gates_metric': metric, 'spam_metric': metric, - 'item_weights': {'gates': 0.0, 'spam': 1.0}, - 'spam_penalty_factor': 1.0, - 'gauge_group': s3gg(model.state_space, model.evotype), - 'oob_check_interval': 1, - 'verbosity': printer - }) - - if suite_name == "stdgaugeopt-unreliable2Q" and model.dim == 16: - if any([gl in model.operations.keys() for gl in unreliable_ops]): - stage2_item_weights = {'gates': 1, 'spam': 0.0} - for gl in unreliable_ops: - if gl in model.operations.keys(): stage2_item_weights[gl] = 0.01 - stages_2qubit_unreliable = [stage.copy() for stage in stages] # ~deep copy of stages - istage2 = 1 if gg.name in ("Full", "TP") else 0 - stages_2qubit_unreliable[istage2]['item_weights'] = stage2_item_weights - gaugeopt_suite_dict[root_lbl] = stages_2qubit_unreliable # add additional gauge opt - else: - _warnings.warn(("`unreliable2Q` was given as a gauge opt suite, but none of the" - " gate names in 'unreliable_ops', i.e., %s," - " are present in the target model. Omitting 'single-2QUR' gauge opt.") - % (", ".join(unreliable_ops))) + return + + metric = 'frobeniustt' if suite_name == 'stdgaugeopt-tt' else 'frobenius' + ss = model.state_space + et = model.evotype + + # Stage 1: plain vanilla gauge opt to get into "right ballpark" + if gg.name in ("Full", "TP"): + stages.append({ + 'gates_metric': metric, 'spam_metric': metric, + 'item_weights': {'gates': 1.0, 'spam': 1.0}, + 'verbosity': printer + }) + + # Stage 2: unitary gauge opt that tries to nail down gates (at + # expense of spam if needed) + s2gg = UnitaryGaugeGroup(ss, model.basis, et) + stages.append({ + 'convert_model_to': convert_to, + 'gates_metric': metric, 'spam_metric': metric, + 'item_weights': {'gates': 1.0, 'spam': 0.0}, + 'gauge_group': s2gg, + 'oob_check_interval': 1 if ('-safe' in suite_name) else 0, + 'verbosity': printer + }) + + # Stage 3: spam gauge opt that fixes spam scaling at expense of + # non-unital parts of gates (but shouldn't affect these + # elements much since they should be small from Stage 2). + s3gg = SpamGaugeGroup(ss, et) if (gg.name == "Full") else TPSpamGaugeGroup(ss, et) + stages.append({ + 'convert_model_to': convert_to, + 'gates_metric': metric, 'spam_metric': metric, + 'item_weights': {'gates': 0.0, 'spam': 1.0}, + 'spam_penalty_factor': 1.0, + 'gauge_group': s3gg, + 'oob_check_interval': 1, + 'verbosity': printer + }) + + if suite_name == "stdgaugeopt-unreliable2Q" and model.dim == 16: + if any([gl in model.operations for gl in unreliable_ops]): + stage2_item_weights = {'gates': 1.0, 'spam': 0.0} + for gl in unreliable_ops: + if gl in model.operations: + stage2_item_weights[gl] = 0.01 + stages_2qubit_unreliable = [stage.copy() for stage in stages] # ~deep copy of stages + istage2 = 1 if gg.name in ("Full", "TP") else 0 + stages_2qubit_unreliable[istage2]['item_weights'] = stage2_item_weights + gaugeopt_suite_dict[root_lbl] = stages_2qubit_unreliable # add additional gauge opt else: - gaugeopt_suite_dict[root_lbl] = stages # can be a list of stage dictionaries + _warnings.warn(("`unreliable2Q` was given as a gauge opt suite, but none of the" + " gate names in 'unreliable_ops', i.e., %s," + " are present in the target model. Omitting 'single-2QUR' gauge opt.") + % (", ".join(unreliable_ops))) + else: + gaugeopt_suite_dict[root_lbl] = stages # can be a list of stage dictionaries - elif suite_name in ("varySpam", "varySpamWt", "varyValidSpamWt", "toggleValidSpam") or \ - suite_name in ("varySpam-unreliable2Q", "varySpamWt-unreliable2Q", - "varyValidSpamWt-unreliable2Q", "toggleValidSpam-unreliable2Q"): + elif suite_name in GSTGaugeOptSuite.SPECIAL_SUITENAMES: - base_wts = {'gates': 1} + base_wts = {'gates': 1.0} if suite_name.endswith("unreliable2Q") and model.dim == 16: - if any([gl in model.operations.keys() for gl in unreliable_ops]): - base = {'gates': 1} + if any([gl in model.operations for gl in unreliable_ops]): + base = {'gates': 1.0} for gl in unreliable_ops: - if gl in model.operations.keys(): base[gl] = 0.01 + if gl in model.operations: + base[gl] = 0.01 base_wts = base if suite_name == "varySpam": @@ -1097,9 +1110,6 @@ def _update_gaugeopt_dict_from_suitename(self, gaugeopt_suite_dict, root_lbl, su 'item_weights': item_weights, 'spam_penalty_factor': valid_spam, 'verbosity': printer} - elif suite_name == "unreliable2Q": - raise ValueError(("unreliable2Q is no longer a separate 'suite'. You should precede it with the suite" - " name, e.g. 'stdgaugeopt-unreliable2Q' or 'varySpam-unreliable2Q'")) elif suite_name == 'none': gaugeopt_suite_dict[root_lbl] = None else: @@ -2091,9 +2101,9 @@ def _add_gauge_opt(results, base_est_label, gaugeopt_suite, starting_model, """ printer = _baseobjs.VerbosityPrinter.create_printer(verbosity, comm) - #Get gauge optimization dictionary - gaugeopt_suite_dict = gaugeopt_suite.to_dictionary(starting_model, - unreliable_ops, printer - 1) + gaugeopt_suite_dict = gaugeopt_suite.to_dictionary( + starting_model, unreliable_ops, printer - 1 + ) #Gauge optimize to list of gauge optimization parameters for go_label, goparams in gaugeopt_suite_dict.items(): @@ -2505,6 +2515,7 @@ def _compute_1d_reference_values_and_name(estimate, badfit_options, gaugeopt_sui spamdd[key] = 0.5 * _tools.optools.povm_diamonddist(gaugeopt_model, target_model, key) dd[lbl]['SPAM'] = sum(spamdd.values()) + return dd, 'diamond distance' else: raise ValueError("Invalid wildcard1d_reference value (%s) in bad-fit options!" diff --git a/pygsti/tools/basistools.py b/pygsti/tools/basistools.py index 0d8bdea85..ac8becbf6 100644 --- a/pygsti/tools/basistools.py +++ b/pygsti/tools/basistools.py @@ -15,9 +15,9 @@ import numpy as _np from pygsti.baseobjs.basisconstructors import _basis_constructor_dict -# from ..baseobjs.basis import Basis, BuiltinBasis, DirectSumBasis from pygsti.baseobjs import basis as _basis + @lru_cache(maxsize=1) def basis_matrices(name_or_basis, dim, sparse=False): """ diff --git a/pygsti/tools/optools.py b/pygsti/tools/optools.py index 309c5b931..598a72605 100644 --- a/pygsti/tools/optools.py +++ b/pygsti/tools/optools.py @@ -163,13 +163,6 @@ def psd_square_root(mat): """ _warnings.warn(message) evals[evals < 0] = 0.0 - tr = _np.sum(evals) - if abs(tr - 1) > __VECTOR_TOL__: - message = f""" - The PSD part of the input matrix is not trace-1 up to tolerance {__VECTOR_TOL__}. - Beware result! - """ - _warnings.warn(message) sqrt_mat = U @ (_np.sqrt(evals).reshape((-1, 1)) * U.T.conj()) return sqrt_mat @@ -1031,9 +1024,14 @@ def povm_diamonddist(model, target_model, povmlbl): ------- float """ - povm_mx = compute_povm_map(model, povmlbl) - target_povm_mx = compute_povm_map(target_model, povmlbl) - return diamonddist(povm_mx, target_povm_mx, target_model.basis) + try: + povm_mx = compute_povm_map(model, povmlbl) + target_povm_mx = compute_povm_map(target_model, povmlbl) + return diamonddist(povm_mx, target_povm_mx, target_model.basis) + except AssertionError as e: + assert '`dim` must be a perfect square' in str(e) + return _np.NaN + def instrument_infidelity(a, b, mx_basis): """ diff --git a/test/unit/tools/test_optools.py b/test/unit/tools/test_optools.py index e94e84d74..af0a17d49 100644 --- a/test/unit/tools/test_optools.py +++ b/test/unit/tools/test_optools.py @@ -48,7 +48,7 @@ def test_unitary_to_pauligate(self): # U_2Q is 4x4 unitary matrix operating on isolated two-qubit space (CX(pi) rotation) op_2Q = ot.unitary_to_pauligate(U_2Q) - op_2Q_inv = ot.process_mx_to_unitary(bt.change_basis(op_2Q, 'pp', 'std')) + op_2Q_inv = ot.std_process_mx_to_unitary(bt.change_basis(op_2Q, 'pp', 'std')) self.assertArraysAlmostEqual(U_2Q, op_2Q_inv) def test_decompose_gate_matrix(self):