From 8307cd5ed98217dd54bd49c8a56478e490a7c4b8 Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Fri, 15 Aug 2025 18:24:10 -0600 Subject: [PATCH 01/32] Fix bug when converting unitary model to errorgen Fix a bug in the conversion from unitary model members to error generator ones present in the new SPAM conversion logic. All that was needed here was to change the space for the to_dense calls so they were consistent. Additionally adds a docstring to convert_members_inplace and set_default_gauge_group for the explicit model class. For set_default_gauge_group I have also changed the default behavior for the set_default_gauge_group function. Previously this would default to the TrivialGaugeGroup for all or most of the valid lindblad types. But in this case the unitary group is a better (though imperfect for edge cases) choice. --- pygsti/modelmembers/povms/__init__.py | 5 +- pygsti/modelmembers/states/__init__.py | 7 +-- pygsti/models/explicitmodel.py | 83 +++++++++++++++++++++----- 3 files changed, 74 insertions(+), 21 deletions(-) diff --git a/pygsti/modelmembers/povms/__init__.py b/pygsti/modelmembers/povms/__init__.py index 332102871..ba5b8ac71 100644 --- a/pygsti/modelmembers/povms/__init__.py +++ b/pygsti/modelmembers/povms/__init__.py @@ -531,7 +531,7 @@ def convert(povm, to_type, basis, ideal_povm=None, flatten_structure=False, cp_p #Collect all ideal effects base_dense_effects = [] for item in base_items: - dense_effect = item[1].to_dense() + dense_effect = item[1].to_dense(on_space='HilbertSchmidt') base_dense_effects.append(dense_effect.reshape((1,len(dense_effect)))) dense_ideal_povm = _np.concatenate(base_dense_effects, axis=0) @@ -539,7 +539,7 @@ def convert(povm, to_type, basis, ideal_povm=None, flatten_structure=False, cp_p #Collect all noisy effects dense_effects = [] for effect in povm.values(): - dense_effect = effect.to_dense() + dense_effect = effect.to_dense(on_space='HilbertSchmidt') dense_effects.append(dense_effect.reshape((1,len(dense_effect)))) dense_povm = _np.concatenate(dense_effects, axis=0) @@ -632,6 +632,7 @@ def _objfn(v): else: raise ValueError("Invalid to_type argument: %s" % to_type) except Exception as e: + raise e error_msgs[to_type] = str(e) # try next to_type raise ValueError("Could not convert POVM to to type(s): %s\n%s" % (str(to_types), str(error_msgs))) diff --git a/pygsti/modelmembers/states/__init__.py b/pygsti/modelmembers/states/__init__.py index efee8811e..205f78c21 100644 --- a/pygsti/modelmembers/states/__init__.py +++ b/pygsti/modelmembers/states/__init__.py @@ -260,13 +260,12 @@ def convert(state, to_type, basis, ideal_state=None, flatten_structure=False, cp proj_basis = 'PP' if state.state_space.is_entirely_qubits else basis errorgen = _LindbladErrorgen.from_error_generator(state.state_space.dim, to_type, proj_basis, basis, truncate=True, evotype=state.evotype) + if st is not state and not _np.allclose(st.to_dense(), state.to_dense()): - dense_st = st.to_dense() - dense_state = state.to_dense() + dense_st = st.to_dense(on_space='HilbertSchmidt') + dense_state = state.to_dense(on_space='HilbertSchmidt') num_qubits = st.state_space.num_qubits - - #GLND for states suffers from "trivial gauge" freedom. This function identifies #the physical directions to avoid this gauge. diff --git a/pygsti/models/explicitmodel.py b/pygsti/models/explicitmodel.py index 52756cc5a..e9145b148 100644 --- a/pygsti/models/explicitmodel.py +++ b/pygsti/models/explicitmodel.py @@ -332,9 +332,49 @@ def __getitem__(self, label): raise KeyError("Key %s has an invalid prefix" % label) def convert_members_inplace(self, to_type, categories_to_convert='all', labels_to_convert='all', - ideal_model=None, flatten_structure=False, set_default_gauge_group=False, cptp_truncation_tol= 1e-7, spam_cp_penalty = 1e-7): + ideal_model=None, flatten_structure=False, set_default_gauge_group=False, + cptp_truncation_tol= 1e-7, spam_cp_penalty = 1e-7): """ - TODO: docstring -- like set_all_parameterizations but doesn't set default gauge group by default + Method for converting the parameterizations of modelmembers within this model to new ones in-place. + + Parameters + ---------- + to_type : str + String specifier for the parameterization type to convert to. + + categories_to_convert : str or list of str, optional (default 'all') + Categories of modelmembers to perform conversion on. Allowed options are: + 'all', 'ops' or 'operations' (these two are aliases for the same option), + 'instruments', 'povms' or 'preps'. + + labels_to_convert : str or list of `Label`, optional (default 'all') + A string specifier, or list of `Label` objects, specifying the set + of objects (state preparations, operations, instruments, etc.) within the model + to apply the conversion to. + + ideal_model : `Model`, optional (default None) + A model containing modelmembers instantiated such that they all correspond to the ideal + actions of the given gate set elements. It is recommended that this be specified when + converting to an error-generator-based parameterization. + + flatten_structure : bool, optional (default False) + When `False`, the sub-members of composed and embedded operations + are separately converted, leaving the original modelmember structure + unchanged. When `True`, composed and embedded operations are "flattened" + into a single modelmember parameterized according to the requested `to_type`. + + set_default_gauge_group : bool, optional (default False) + A flag specifying whether the default gauge group for the model should be updated + to the default value associated with the specified value of `to_type`. + See `set_default_gauge_group_for_member_type` for more on these default gauge groups. + + cptp_truncation_tol : float, optional (default 1e-7) + Tolerance term used to enforce the CPTP constraint on gates when moving between different + parameterizations. + + spam_cp_penalty : float, optional (default 1e-7) + Penalty term used to enforce the CP constraint on SPAM when moving between different + parameterizations. """ if isinstance(categories_to_convert, str): categories_to_convert = (categories_to_convert,) if any([c in categories_to_convert for c in ('all', 'ops', 'operations')]): @@ -356,21 +396,34 @@ def convert_members_inplace(self, to_type, categories_to_convert='all', labels_t for lbl, povm in self.povms.items(): if labels_to_convert == 'all' or lbl in labels_to_convert: ideal = ideal_model.povms.get(lbl, None) if (ideal_model is not None) else None - self.povms[lbl] = _povm.convert (povm, to_type, self.basis, ideal, flatten_structure, cp_penalty=spam_cp_penalty) + self.povms[lbl] = _povm.convert(povm, to_type, self.basis, ideal, flatten_structure, cp_penalty=spam_cp_penalty) self._clean_paramvec() # param indices were probabaly updated if set_default_gauge_group: self.set_default_gauge_group_for_member_type(to_type) def set_default_gauge_group_for_member_type(self, member_type): - """ TODO: docstring """ + """ + Updates the default gauge group to the default value for the specified modelmember type. + + Parameters + ---------- + member_type : str + A string specifier for the modelmember type used to select the gauge group type. + Mapping is the following: + + - 'full' -> `FullGaugeGroup` + - 'full TP', 'TP', `TPGaugeGroup` + - 'CPTP' or Anything that is a valid lindblad type -> `UnitaryGaugeGroup` + - Otherwise -> `TrivialGaugeGroup` + """ if member_type == 'full': self.default_gauge_group = _gg.FullGaugeGroup(self.state_space, self.basis, self.evotype) - elif member_type in ['full TP', 'TP']: # TODO: get from verbose_conversion dictionary of modelmembers? + elif member_type in ('full TP', 'TP'): # TODO: get from verbose_conversion dictionary of modelmembers? self.default_gauge_group = _gg.TPGaugeGroup(self.state_space, self.basis, self.evotype) - elif member_type == 'CPTP': + elif _ot.is_valid_lindblad_paramtype(member_type) or member_type == 'CPTP': self.default_gauge_group = _gg.UnitaryGaugeGroup(self.state_space, self.basis, self.evotype) - else: # typ in ('static','H+S','S', 'H+S terms', ...) + else: self.default_gauge_group = _gg.TrivialGaugeGroup(self.state_space) def set_all_parameterizations(self, gate_type, prep_type="auto", povm_type="auto", @@ -440,14 +493,14 @@ def set_all_parameterizations(self, gate_type, prep_type="auto", povm_type="auto povmtyp = _povm.povm_type_from_op_type(gate_type) if povm_type == "auto" else povm_type ityp = _instrument.instrument_type_from_op_type(gate_type) if instrument_type == "auto" else instrument_type - try: - self.convert_members_inplace(typ, 'operations', 'all', flatten_structure=True, ideal_model=static_model, cptp_truncation_tol = cptp_truncation_tol) - self.convert_members_inplace(ityp, 'instruments', 'all', flatten_structure=True, ideal_model=static_model, cptp_truncation_tol = cptp_truncation_tol) - self.convert_members_inplace(rtyp, 'preps', 'all', flatten_structure=True, ideal_model=static_model, cptp_truncation_tol = cptp_truncation_tol, spam_cp_penalty = spam_cp_penalty) - self.convert_members_inplace(povmtyp, 'povms', 'all', flatten_structure=True, ideal_model=static_model, cptp_truncation_tol = cptp_truncation_tol, spam_cp_penalty = spam_cp_penalty) - except ValueError as e: - raise ValueError("Failed to convert members. If converting to CPTP-based models, " + - "try providing an ideal_model to avoid possible branch cuts.") from e + #try: + self.convert_members_inplace(typ, 'operations', 'all', flatten_structure=True, ideal_model=static_model, cptp_truncation_tol = cptp_truncation_tol) + self.convert_members_inplace(ityp, 'instruments', 'all', flatten_structure=True, ideal_model=static_model, cptp_truncation_tol = cptp_truncation_tol) + self.convert_members_inplace(rtyp, 'preps', 'all', flatten_structure=True, ideal_model=static_model, cptp_truncation_tol = cptp_truncation_tol, spam_cp_penalty = spam_cp_penalty) + self.convert_members_inplace(povmtyp, 'povms', 'all', flatten_structure=True, ideal_model=static_model, cptp_truncation_tol = cptp_truncation_tol, spam_cp_penalty = spam_cp_penalty) + #except ValueError as e: + # raise ValueError("Failed to convert members. If converting to CPTP-based models, " + + # "try providing an ideal_model to avoid possible branch cuts.") from e self.set_default_gauge_group_for_member_type(typ) From 5dc5a3fc5bb3678a01090ca75ed10c5a8cf25328 Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Sat, 16 Aug 2025 18:10:38 -0600 Subject: [PATCH 02/32] Partial patch for gauge optimization with 'statevec' evotype Partial patch for gauge optimization when using the statevec evotype that arises from difference in minimal reps for gauge group elements and operations. --- pygsti/modelmembers/operations/composedop.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygsti/modelmembers/operations/composedop.py b/pygsti/modelmembers/operations/composedop.py index d0603cc2c..488453f60 100644 --- a/pygsti/modelmembers/operations/composedop.py +++ b/pygsti/modelmembers/operations/composedop.py @@ -637,8 +637,8 @@ def transform_inplace(self, s): #SPECIAL CASE / HACK: for 1 & 2Q, when holding e^L * T, where T is a static gate # then try to gauge transform by setting e^L directly and leaving T alone: Smx = s.transform_matrix; Si = s.transform_matrix_inverse - Tinv = _np.linalg.inv(self.factorops[0].to_dense("minimal")) - trans_eLT = _np.dot(Si, _np.dot(self.to_dense("minimal"), Smx)) + Tinv = _np.linalg.inv(self.factorops[0].to_dense("HilbertSchmidt")) + trans_eLT = _np.dot(Si, _np.dot(self.to_dense("HilbertSchmidt"), Smx)) self.factorops[1].set_dense(_np.dot(trans_eLT, Tinv)) # set_dense(trans_eL) return From 0bbc6d6ea600799f11422e19a35ccbe1ee28855b Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Sat, 16 Aug 2025 18:12:14 -0600 Subject: [PATCH 03/32] Turn off gauge optimization for term fwdsim tests There is an incompatibility between the statevec evotype and the representations used in gauge optimization. For now turn off gauge optimization on the GST tests which use the term forward simulator. Doing this most straightforwardly required adding a new option for the GST driver function. --- pygsti/drivers/longsequence.py | 25 +++++++++++++++---- .../drivers/test_calcmethods1Q.py | 10 ++++---- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/pygsti/drivers/longsequence.py b/pygsti/drivers/longsequence.py index 63e01a4fa..6e89385da 100644 --- a/pygsti/drivers/longsequence.py +++ b/pygsti/drivers/longsequence.py @@ -318,7 +318,8 @@ def run_long_sequence_gst(data_filename_or_set, target_model_filename_or_object, advanced_options=None, comm=None, mem_limit=None, output_pkl=None, verbosity=2, checkpoint=None, checkpoint_path=None, disable_checkpointing=False, - simulator: Optional[ForwardSimulator.Castable]=None): + simulator: Optional[ForwardSimulator.Castable]=None, + gauge_opt_suite_name: str = None): """ Perform long-sequence GST (LSGST). @@ -462,6 +463,9 @@ def run_long_sequence_gst(data_filename_or_set, target_model_filename_or_object, fwdsim = ForwardSimulator.cast(simulator), and we set the .sim attribute of every Model we encounter to fwdsim. + gauge_opt_suite_name : str, optional (default None) + An optional string specifying a named gauge optimization suite. + Returns ------- Results @@ -485,11 +489,22 @@ def run_long_sequence_gst(data_filename_or_set, target_model_filename_or_object, data = _proto.ProtocolData(exp_design, ds) - if gauge_opt_params is None: + assert not (gauge_opt_suite_name and gauge_opt_params), 'Can only specify one of `gauge_opt_suite_name` or `gauge_opt_params`' + + if gauge_opt_params is None and gauge_opt_suite_name is None: gauge_opt_params = {'item_weights': {'gates': 1.0, 'spam': 0.001}} - gopt_suite = _proto.GSTGaugeOptSuite( - gaugeopt_argument_dicts=({'go0': gauge_opt_params} if gauge_opt_params else None), - gaugeopt_target=target_model) + gopt_suite = _proto.GSTGaugeOptSuite( + gaugeopt_argument_dicts= {'go0': gauge_opt_params}, + gaugeopt_target=target_model) + elif gauge_opt_suite_name is not None: + gopt_suite = _proto.GSTGaugeOptSuite( + gaugeopt_suite_names= gauge_opt_suite_name, + gaugeopt_target=target_model) + else: + gopt_suite = _proto.GSTGaugeOptSuite( + gaugeopt_argument_dicts={'go0': gauge_opt_params}, + gaugeopt_target=target_model) + initial_model = _get_gst_initial_model(target_model, advanced_options) proto = _proto.GateSetTomography(initial_model, gopt_suite, _get_gst_builders(advanced_options), diff --git a/test/test_packages/drivers/test_calcmethods1Q.py b/test/test_packages/drivers/test_calcmethods1Q.py index c5c40969d..126d4f99f 100644 --- a/test/test_packages/drivers/test_calcmethods1Q.py +++ b/test/test_packages/drivers/test_calcmethods1Q.py @@ -202,7 +202,7 @@ def test_stdgst_terms(self): target_model.from_vector(1e-10 * np.ones(target_model.num_params)) # to seed term calc (starting with perfect zeros causes trouble) results = pygsti.run_long_sequence_gst(self.ds, target_model, std.prep_fiducials(), std.meas_fiducials(), std.germs(lite=False), self.maxLengths, verbosity=0, - disable_checkpointing=True) + disable_checkpointing=True, gauge_opt_suite_name='none') #RUN BELOW LINES TO SAVE GATESET (SAVE) if regenerate_references(): @@ -222,9 +222,9 @@ def test_stdgst_terms(self): #A direct vector comparison works if python (&numpy?) versions are identical, but # gauge freedoms make this incorrectly fail in other cases - so just check sigmas - print("VEC DIFF = ",(results.estimates[results.name].models['go0'].to_vector() + print("VEC DIFF = ",(results.estimates[results.name].models['trivial_gauge_opt'].to_vector() - mdl_compare.to_vector())) - self.assertAlmostEqual( np.linalg.norm(results.estimates[results.name].models['go0'].to_vector() + self.assertAlmostEqual( np.linalg.norm(results.estimates[results.name].models['trivial_gauge_opt'].to_vector() - mdl_compare.to_vector()), 0, places=1) # Note: used to be places=3 above when comparing with cython-built files, but to match cython with # non-cython builds we loosen to places=1 @@ -240,7 +240,7 @@ def test_stdgst_prunedpath(self): target_model.from_vector(1e-10 * np.ones(target_model.num_params)) # to seed term calc (starting with perfect zeros causes trouble) results = pygsti.run_long_sequence_gst(self.ds, target_model, std.prep_fiducials(), std.meas_fiducials(), std.germs(lite=False), self.maxLengths, verbosity=3, - disable_checkpointing=True) + disable_checkpointing=True, gauge_opt_suite_name='none') #RUN BELOW LINES TO SAVE GATESET (SAVE) if regenerate_references(): @@ -275,7 +275,7 @@ def test_reducedmod_matrix(self): results = pygsti.run_long_sequence_gst(self.redmod_ds, target_model, self.redmod_fiducials, self.redmod_fiducials, self.redmod_germs, self.redmod_maxLs, verbosity=4, advanced_options={'tolerance': 1e-3}, - disable_checkpointing=True) + disable_checkpointing=True, gauge_opt_suite_name='none') #RUN BELOW LINES TO SAVE GATESET (SAVE) if regenerate_references(): From ac74ecdb0357d2223d024e92c0e82159c81dbe48 Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Sat, 16 Aug 2025 18:13:00 -0600 Subject: [PATCH 04/32] Fix deserialization bug for older models Fix a bug in deserializing models saved before the recent changes to allow saving fogi stores. --- pygsti/models/explicitmodel.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pygsti/models/explicitmodel.py b/pygsti/models/explicitmodel.py index e9145b148..210322aac 100644 --- a/pygsti/models/explicitmodel.py +++ b/pygsti/models/explicitmodel.py @@ -1676,8 +1676,9 @@ def _from_nice_serialization(cls, state): if (state['default_gauge_group'] is not None) else None param_interposer = _ModelParamsInterposer.from_nice_serialization(state['parameter_interposer']) \ if (state['parameter_interposer'] is not None) else None + fogi_store = _FirstOrderGaugeInvariantStore.from_nice_serialization(state['fogi_store']) \ - if (state['fogi_store'] is not None) else None + if (state.get('fogi_store', None) is not None) else None param_labels = state.get('parameter_labels', None) param_bounds = state.get('parameter_bounds', None) From 657eb4908643d7dbbd8b95cb4697766074456853 Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Sat, 16 Aug 2025 21:53:44 -0600 Subject: [PATCH 05/32] Patch diamond distance reportable Patch the diamond distance reportable function to account for a change to the return call signature for the diamond distance when returning the value of the SDP solution. --- pygsti/report/reportables.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygsti/report/reportables.py b/pygsti/report/reportables.py index cd1b403ee..a632aa682 100644 --- a/pygsti/report/reportables.py +++ b/pygsti/report/reportables.py @@ -560,7 +560,7 @@ def evaluate_nearby(self, nearby_model): nearby_model.sim.product(self.circuit), mxBasis) JBstd = self.d * _tools.fast_jamiolkowski_iso_std(self.B, mxBasis) J = JBstd - JAstd - val = 0.5 * (_np.vdot(J.real, self.W.real) + _np.vdot(J.imag, self.W.imag)) + val = 0.5 * (_np.vdot(J.real, self.W[0].real) + _np.vdot(J.imag, self.W[0].imag)) return val else: @@ -1274,7 +1274,7 @@ def evaluate_nearby(self, nearby_model): JAstd = self.d * _tools.fast_jamiolkowski_iso_std(A, mxBasis) JBstd = self.d * _tools.fast_jamiolkowski_iso_std(self.B, mxBasis) J = JBstd - JAstd - val = 0.5 * (_np.vdot(J.real, self.W.real) + _np.vdot(J.imag, self.W.imag)) + val = 0.5 * (_np.vdot(J.real, self.W[0].real) + _np.vdot(J.imag, self.W[0].imag)) return val def half_diamond_norm(a, b, mx_basis): From aae43e1c732d2e11304be51f5cfb9c76cd579ff6 Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Sat, 16 Aug 2025 21:59:59 -0600 Subject: [PATCH 06/32] Expire msgpack and json deprecations Some recent changes resulted in serialization using legacy json and msgpack serialization protocols breaking. This functionality has been deprecated for over two years, and so spending energy patching this doesn't make sense. Rather, this is as good a time as any to finally expire this deprecation. --- pygsti/serialization/json.py | 116 --------------------- pygsti/serialization/msgpack.py | 94 ----------------- test/test_packages/iotest/test_codecs.py | 123 ----------------------- 3 files changed, 333 deletions(-) delete mode 100644 pygsti/serialization/json.py delete mode 100644 pygsti/serialization/msgpack.py diff --git a/pygsti/serialization/json.py b/pygsti/serialization/json.py deleted file mode 100644 index 53707687b..000000000 --- a/pygsti/serialization/json.py +++ /dev/null @@ -1,116 +0,0 @@ -""" -Defines json package interface capable of encoding pyGSTi objects -""" -#*************************************************************************************************** -# Copyright 2015, 2019, 2025 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 json as _json - -from pygsti.serialization.jsoncodec import decode_obj -from pygsti.serialization.jsoncodec import encode_obj - -from pygsti.tools.legacytools import deprecate as _deprecated_fn - - -class PygstiJSONEncoder(_json.JSONEncoder): - """ - JSON Encoder capable of handling pyGSTi types - """ - - def encode(self, item): - """ - Main encoding function - - Parameters - ---------- - item : various - item to encode - - Returns - ------- - various - """ - return super(PygstiJSONEncoder, self).encode(encode_obj(item, False)) - - -_deprecation_msg= 'Use of the python json module for serialization of pygsti objects is deprecated.'\ - +' Most pysgti objects now natively support json serialization and deserialization and '\ - + 'users should migrate to that functionality when possible.' - -@_deprecated_fn(_deprecation_msg) -def dumps(obj, **kwargs): - """ - An overload of json.dumps that works with pyGSTi types - - Parameters - ---------- - obj : object - object to serialize. - - Returns - ------- - str - """ - kwargs['cls'] = PygstiJSONEncoder - return _json.dumps(obj, **kwargs) - -@_deprecated_fn(_deprecation_msg) -def dump(obj, f, **kwargs): - """ - An overload of json.dump that works with pyGSTi types - - Parameters - ---------- - obj : object - object to serialize - - f : file - output file - - Returns - ------- - None - """ - kwargs['cls'] = PygstiJSONEncoder - enc = encode_obj(obj, False) # this shouldn't be needed... bug in json I think. - return _json.dump(enc, f, **kwargs) - -@_deprecated_fn(_deprecation_msg) -def loads(s, **kwargs): - """ - An overload of json.loads that works with pyGSTi types - - Parameters - ---------- - s : str - serialized object(s) - - Returns - ------- - object - """ - decoded_json = _json.loads(s, **kwargs) # load normal JSON - return decode_obj(decoded_json, False) # makes pygsti objects - -@_deprecated_fn(_deprecation_msg) -def load(f, **kwargs): - """ - An overload of json.load that works with pyGSTi types - - Parameters - ---------- - f : file - open file to read from - - Returns - ------- - object - """ - decoded_json = _json.load(f, **kwargs) # load normal JSON - return decode_obj(decoded_json, False) # makes pygsti objects diff --git a/pygsti/serialization/msgpack.py b/pygsti/serialization/msgpack.py deleted file mode 100644 index e32a603b6..000000000 --- a/pygsti/serialization/msgpack.py +++ /dev/null @@ -1,94 +0,0 @@ -""" -Defines msgpack package interface capable of encoding pyGSTi objects -""" -#*************************************************************************************************** -# Copyright 2015, 2019, 2025 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 msgpack as _msgpack -msgpack_uses_binary_strs = _msgpack.version < (1, 0, 0) # msgpack only used binary strings in pre 1.0 versions - -from pygsti.serialization.jsoncodec import encode_obj -from pygsti.serialization.jsoncodec import decode_obj - -from pygsti.tools.legacytools import deprecate as _deprecated_fn - -_deprecation_msg= 'Use of the python msgpack module for serialization of pygsti objects is deprecated.'\ - +' Most pysgti objects now natively support json serialization and deserialization and '\ - + 'users should migrate to that functionality when possible.' - -@_deprecated_fn(_deprecation_msg) -def dumps(obj, **kwargs): - """ - An overload of msgpack.dumps that works with pyGSTi types - - Parameters - ---------- - obj : object - object to serialize. - - Returns - ------- - str - """ - enc = encode_obj(obj, msgpack_uses_binary_strs) - return _msgpack.packb(enc, **kwargs) - -@_deprecated_fn(_deprecation_msg) -def dump(obj, f, **kwargs): - """ - An overload of msgpack.dump that works with pyGSTi types - - Parameters - ---------- - obj : object - object to serialize - - f : file - output file - - Returns - ------- - None - """ - enc = encode_obj(obj, msgpack_uses_binary_strs) - _msgpack.pack(enc, f, **kwargs) - -@_deprecated_fn(_deprecation_msg) -def loads(s, **kwargs): - """ - An overload of msgpack.loads that works with pyGSTi types - - Parameters - ---------- - s : str - serialized object(s) - - Returns - ------- - object - """ - decoded_msgpack = _msgpack.unpackb(s, **kwargs) # load normal MSGPACK - return decode_obj(decoded_msgpack, msgpack_uses_binary_strs) # makes pygsti objects - -@_deprecated_fn(_deprecation_msg) -def load(f, **kwargs): - """ - An overload of msgpack.load that works with pyGSTi types - - Parameters - ---------- - f : file - open file to read from - - Returns - ------- - object - """ - decoded_msgpack = _msgpack.unpack(f, **kwargs) # load normal MSGPACK - return decode_obj(decoded_msgpack, msgpack_uses_binary_strs) # makes pygsti objects diff --git a/test/test_packages/iotest/test_codecs.py b/test/test_packages/iotest/test_codecs.py index f14d26f1e..9d5686cd1 100644 --- a/test/test_packages/iotest/test_codecs.py +++ b/test/test_packages/iotest/test_codecs.py @@ -11,8 +11,6 @@ import pygsti from pygsti.modelpacks.legacy import std1Q_XY as std -import pygsti.serialization.json as json -import pygsti.serialization.msgpack as msgpack from pygsti.baseobjs.label import CircuitLabel from ..testutils import BaseTestCase, temp_files @@ -83,127 +81,6 @@ def setUp(self): class TestCodecs(CodecsTestCase): - def test_json(self): - - #basic types - s = json.dumps(range(10)) - x = json.loads(s) - s = json.dumps(4 + 3.0j) - x = json.loads(s) - s = json.dumps(np.array([1, 2, 3, 4], 'd')) - x = json.loads(s) - s = json.dumps(testObj) - x = json.loads(s) - - #string list - s = json.dumps(self.lsgstStrings) - x = json.loads(s) - self.assertEqual(x, self.lsgstStrings) - - # DataSet - s = json.dumps(self.ds) - x = json.loads(s) - self.assertEqual(list(x.keys()), list(self.ds.keys())) - self.assertEqual(x[('Gx',)].to_dict(), self.ds[('Gx',)].to_dict()) - - # Model - s = json.dumps(self.datagen_gateset) - with open(temp_files + "/model.json",'w') as f: - json.dump(self.datagen_gateset, f) - with open(temp_files + "/model.json",'r') as f: - x = json.load(f) - s = json.dumps(self.mdl_withInst) - x = json.loads(s) - self.assertAlmostEqual(self.mdl_withInst.frobeniusdist(x),0) - - #print(s) - x._check_paramvec(True) - self.assertAlmostEqual(self.datagen_gateset.frobeniusdist(x),0) - - # Results (containing confidence region) - std.target_model()._check_paramvec() - print("target_model = ",id(std.target_model())) - print("rho0 parent = ",id(std.target_model().preps['rho0'].parent)) - - #import bpdb; bpdb.set_trace() - with open(temp_files + "/results.json",'w') as f: - json.dump(self.results, f) - print("mdl_target2 = ",id(std.target_model())) - print("rho0 parent2 = ",id(std.target_model().preps['rho0'].parent)) - std.target_model()._check_paramvec() - with open(temp_files + "/results.json",'r') as f: - x = json.load(f) - self.assertEqual(list(x.estimates.keys()), list(self.results.estimates.keys())) - self.assertEqual(list(x.estimates[x.name].confidence_region_factories.keys()), - list(self.results.estimates[self.results.name].confidence_region_factories.keys())) - - # Workspace - s = json.dumps(self.ws) - x = json.loads(s) - #TODO: comparison (?) - - #Misc other objects - for obj in self.miscObjects: - s = json.dumps(obj) - x = json.loads(s) - - - - def test_msgpack(self): - - #basic types - s = msgpack.dumps(range(10)) - x = msgpack.loads(s) - s = msgpack.dumps(4 + 3.0j) - x = msgpack.loads(s) - s = msgpack.dumps(np.array([1, 2, 3, 4], 'd')) - x = msgpack.loads(s) - s = msgpack.dumps(testObj) - x = msgpack.loads(s) - - #string list - s = msgpack.dumps(self.lsgstStrings) - x = msgpack.loads(s) - self.assertEqual(x, self.lsgstStrings) - - # DataSet - s = msgpack.dumps(self.ds) - x = msgpack.loads(s) - self.assertEqual(list(x.keys()), list(self.ds.keys())) - self.assertEqual(x[('Gx',)].to_dict(), self.ds[('Gx',)].to_dict()) - - # Model - s = msgpack.dumps(self.datagen_gateset) - with open(temp_files + "/model.mpk",'wb') as f: - msgpack.dump(self.datagen_gateset, f) - with open(temp_files + "/model.mpk",'rb') as f: - x = msgpack.load(f) - self.assertAlmostEqual(self.datagen_gateset.frobeniusdist(x),0) - s = msgpack.dumps(self.mdl_withInst) - x = msgpack.loads(s) - self.assertAlmostEqual(self.mdl_withInst.frobeniusdist(x),0) - - # Results (containing confidence region) - with open(temp_files + "/results.mpk",'wb') as f: - msgpack.dump(self.results, f) - with open(temp_files + "/results.mpk",'rb') as f: - x = msgpack.load(f) - self.assertEqual(list(x.estimates.keys()), list(self.results.estimates.keys())) - self.assertEqual(list(x.estimates[x.name].confidence_region_factories.keys()), - list(self.results.estimates[self.results.name].confidence_region_factories.keys())) - - # Workspace - s = msgpack.dumps(self.ws) - x = msgpack.loads(s) - #TODO: comparison (?) - - #Misc other objects - for obj in self.miscObjects: - s = msgpack.dumps(obj) - x = msgpack.loads(s) - - - def test_pickle(self): #basic types From 513f469d4a7189c65485061a9ef45e8f2eb52a55 Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Sat, 16 Aug 2025 22:16:55 -0600 Subject: [PATCH 07/32] JSON/msgpack expiration clean-up Cleans up additional reference to the now expired legacy json and msgpack serialization protocols. --- .../Tutorials/other/FileIO.ipynb | 81 ------------------- pygsti/modelpacks/stdtarget.py | 13 +-- pygsti/serialization/__init__.py | 7 -- .../drivers/demoCalcMethods2Q.py | 4 +- .../drivers/test_calcmethods1Q.py | 1 - 5 files changed, 2 insertions(+), 104 deletions(-) diff --git a/jupyter_notebooks/Tutorials/other/FileIO.ipynb b/jupyter_notebooks/Tutorials/other/FileIO.ipynb index 0c8c2133e..e8aa91645 100644 --- a/jupyter_notebooks/Tutorials/other/FileIO.ipynb +++ b/jupyter_notebooks/Tutorials/other/FileIO.ipynb @@ -298,87 +298,6 @@ "source": [ "Something we want to emphasize is that all of the I/O function calls (except the simulated data generation and unnecessary separate saving of the protocol) are given the same, single root directory (`\"../tutorial_files/example_root_directory\"`). This is a key feature and the intended design of pyGSTi's I/O framework - that a given experiment design, data, and analysis all live together in the same \"node\" (corresponding to a root directory) in a potential tree of experiments. In the example above, we just have a single node. By using nested experiment designs (e.g. `CombinedExperimentDesign` and `SimultaneousExperimentDesign` objects) a tree of such \"root directories\" (perhaps better called \"node directories\" then) can be built. " ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Serialization to pickle, JSON and MSGPACK formats\n", - "\n", - "**DEPRECATED: The following serialization functionality is deprecated and may not be supported/break in newer pysgti releases. Please migrate to updated json serialization functionality described above.*** \n", - "\n", - "PyGSTi contains support for reading and writing most (if not all) of its objects from and to the JSON and MessagePack formats using a pickle-like format. The modules `pygsti.io.json` and `pygsti.io.msgpack` mimic the more general Python `json` and `msgpack` packages (`json` is a standard package, `msgpack` is a separate package, and must be installed if you wish to use pyGSTi's MessagePack functionality). These, in turn, mimic the load/dump interface of the standard `pickle` module, so it's very easy to serialize data using any of these formats. Here's a brief summary of the mais advantages and disadvantages of each format:\n", - "\n", - "- pickle\n", - " - **Advantages**: a standard Python package; very easy to use; can serialize almost anything.\n", - " - **Disadvantages**: incompatibility between python2 and python3 pickles; can be large on disk (inefficient); not web safe.\n", - "- json\n", - " - **Advantages**: a standard Python package; web-safe character set.; *should* be the same on python2 or python3\n", - " - **Disadvantages**: large on disk (inefficient)\n", - "- msgpack\n", - " - **Advantages**: *should* be the same on python2 or python3; efficient binary format (small on disk)\n", - " - **Disadvantages**: needs external `msgpack` package; binary non-web-safe format.\n", - " \n", - "As stated above, because this format is fragile (will completely break when pyGSTi internals change) and not human readable, this should be used as a last resort or just as temporary storage. Below we demonstrate how to use the `io.json` and `io.msgpack` modules. Using `pickle` is essentially the same, as pyGSTi objects support being pickled too." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "tags": [ - "nbval-skip" - ] - }, - "outputs": [], - "source": [ - "import pygsti.serialization.json as json\n", - "import pygsti.serialization.msgpack as msgpack\n", - "\n", - "#Models\n", - "from pygsti.modelpacks import smq1Q_XYI\n", - "target_model = smq1Q_XYI.target_model()\n", - "json.dump(target_model, open(\"../tutorial_files/TestModel.json\",'w'))\n", - "target_model_from_json = json.load(open(\"../tutorial_files/TestModel.json\"))\n", - "\n", - "msgpack.dump(target_model, open(\"../tutorial_files/TestModel.mp\",'wb'))\n", - "target_model_from_msgpack = msgpack.load(open(\"../tutorial_files/TestModel.mp\", 'rb'))\n", - "\n", - "#DataSets\n", - "json.dump(ds, open(\"../tutorial_files/TestDataSet.json\",'w'))\n", - "ds_from_json = json.load(open(\"../tutorial_files/TestDataSet.json\"))\n", - "\n", - "msgpack.dump(ds, open(\"../tutorial_files/TestDataSet.mp\",'wb'))\n", - "ds_from_msgpack = msgpack.load(open(\"../tutorial_files/TestDataSet.mp\",'rb'))\n", - "\n", - "#MultiDataSets\n", - "json.dump(multiDS, open(\"../tutorial_files/TestMultiDataSet.json\",'w'))\n", - "multiDS_from_json = json.load(open(\"../tutorial_files/TestMultiDataSet.json\"))\n", - "\n", - "msgpack.dump(multiDS, open(\"../tutorial_files/TestMultiDataSet.mp\",'wb'))\n", - "multiDS_from_msgpack = msgpack.load(open(\"../tutorial_files/TestMultiDataSet.mp\",'rb'))\n", - "\n", - "# Timestamped-data DataSets\n", - "json.dump(tdds_fromfile, open(\"../tutorial_files/TestTDDataset.json\",'w'))\n", - "tdds_from_json = json.load(open(\"../tutorial_files/TestTDDataset.json\"))\n", - "\n", - "msgpack.dump(tdds_fromfile, open(\"../tutorial_files/TestTDDataset.mp\",'wb'))\n", - "tdds_from_msgpack = msgpack.load(open(\"../tutorial_files/TestTDDataset.mp\",'rb'))\n", - "\n", - "#Circuit Lists\n", - "json.dump(cList, open(\"../tutorial_files/TestCircuitList.json\",'w'))\n", - "cList_from_json = json.load(open(\"../tutorial_files/TestCircuitList.json\"))\n", - "\n", - "msgpack.dump(cList, open(\"../tutorial_files/TestCircuitList.mp\",'wb'))\n", - "cList_from_msgpack = msgpack.load(open(\"../tutorial_files/TestCircuitList.mp\",'rb'))" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] } ], "metadata": { diff --git a/pygsti/modelpacks/stdtarget.py b/pygsti/modelpacks/stdtarget.py index 4ba9dc796..900d91691 100644 --- a/pygsti/modelpacks/stdtarget.py +++ b/pygsti/modelpacks/stdtarget.py @@ -43,7 +43,7 @@ def _get_cachefile_names(std_module, param_type, simulator, py_version): # XXX apparently only used from _make_hs_cache_for_std_model which itself looks unused -def _write_calccache(calc_cache, key_fn, val_fn, json_too=False, comm=None): +def _write_calccache(calc_cache, key_fn, val_fn, comm=None): """ Write `caclcache`, a dictionary of compact polys, to disk in two files, one for the keys and one for the values. @@ -59,10 +59,6 @@ def _write_calccache(calc_cache, key_fn, val_fn, json_too=False, comm=None): key_fn, val_fn : str key and value filenames. - json_too : bool, optional - When true, the keys are also written in JSON format (to facilitate - python2 & 3 compatibility) - comm : mpi4py.MPI.comm Communicator for synchronizing across multiple ranks (each with different `calc_cache` args that need to be gathered. @@ -93,13 +89,6 @@ def conv_key(ky): # converts key to native python objects for faster serializat _pickle.dump(ckeys, f, protocol=_pickle.HIGHEST_PROTOCOL) print("Wrote %s" % key_fn) - if json_too: # for Python 2 & 3 compatibility - from pygsti.serialization import json as _json - key_fn_json = _os.path.splitext(key_fn)[0] + ".json" - with open(key_fn_json, 'w') as f: - _json.dump(ckeys, f) - print("Wrote %s" % key_fn_json) - if len(keys) > 0: # some procs might have 0 keys (e.g. the "scheduler") values = [calc_cache[k] for k in keys] vtape = []; ctape = [] diff --git a/pygsti/serialization/__init__.py b/pygsti/serialization/__init__.py index 138e46b55..b4fd638cf 100644 --- a/pygsti/serialization/__init__.py +++ b/pygsti/serialization/__init__.py @@ -9,10 +9,3 @@ # 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. #*************************************************************************************************** - -from . import json - -#Users may not have msgpack, which is fine. -try: - from . import msgpack -except ImportError: pass diff --git a/test/test_packages/drivers/demoCalcMethods2Q.py b/test/test_packages/drivers/demoCalcMethods2Q.py index a73d28665..06c57aded 100644 --- a/test/test_packages/drivers/demoCalcMethods2Q.py +++ b/test/test_packages/drivers/demoCalcMethods2Q.py @@ -5,13 +5,11 @@ import pygsti import pygsti.construction as pc -from pygsti.serialization import json from pygsti.modelpacks.legacy import std1Q_XY from pygsti.modelpacks.legacy import std2Q_XYCNOT as std -from pygsti.objects import Label as L +from pygsti.baseobjs import Label as L from ..testutils import BaseTestCase, compare_files - class CalcMethods2QTestCase(BaseTestCase): @classmethod diff --git a/test/test_packages/drivers/test_calcmethods1Q.py b/test/test_packages/drivers/test_calcmethods1Q.py index 126d4f99f..620f3b2ba 100644 --- a/test/test_packages/drivers/test_calcmethods1Q.py +++ b/test/test_packages/drivers/test_calcmethods1Q.py @@ -13,7 +13,6 @@ from pygsti.modelpacks import smq1Q_XY as std from pygsti.baseobjs import Label as L from pygsti.circuits import Circuit -from pygsti.serialization import json from pygsti.models import ExplicitOpModel, CloudNoiseModel import os From d9677aea122ee032289260f59fcd02215426aede Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Sat, 16 Aug 2025 23:14:43 -0600 Subject: [PATCH 08/32] Add casting logic for set_errorgen_coefficients Add in casting logic to set_errorgen_coefficients when the input lindblad dictionary has label types which do not match the object being updated. Also add handling for edge case where the update dictionary is empty. --- pygsti/modelmembers/operations/composedop.py | 17 ++++++++++++++++- .../modelmembers/operations/lindbladerrorgen.py | 2 ++ pygsti/serialization/jsoncodec.py | 2 -- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/pygsti/modelmembers/operations/composedop.py b/pygsti/modelmembers/operations/composedop.py index 488453f60..af81419a8 100644 --- a/pygsti/modelmembers/operations/composedop.py +++ b/pygsti/modelmembers/operations/composedop.py @@ -899,8 +899,23 @@ def set_errorgen_coefficients(self, lindblad_term_dict, action="update", logscal available_factor_coeffs = op.errorgen_coefficients(False, logscale_nonham) except AttributeError: continue # just skip members that don't implemnt errorgen_coefficients (?) - + + #If necessary cast the input lindblad_term_dict keys so they match the type of + #available_factor_coeffs. + if available_factor_coeffs: + available_factor_coeffs_first_lbl_type = type(next(iter(available_factor_coeffs))) + updated_values_to_set = {} + for key, val in values_to_set.items(): + if not isinstance(key, available_factor_coeffs_first_lbl_type): + #implicitly assuming there is only a single tensor product block in the case where it is an ExplicitStateSpace object. + sslbls = self.state_space.qudit_labels if isinstance(self.state_space, _statespace.QuditSpace) else self.state_space.labels[0] + updated_values_to_set[available_factor_coeffs_first_lbl_type.cast(key, sslbls)] = val + values_to_set = updated_values_to_set + + print(f'{values_to_set=}') + print(f'{available_factor_coeffs=}') Ltermdict_local = {k:v for k, v in values_to_set.items() if k in available_factor_coeffs} + print(f'{Ltermdict_local=}') op.set_errorgen_coefficients(Ltermdict_local, action, logscale_nonham, truncate) for k in Ltermdict_local: del values_to_set[k] # remove the values that we just set diff --git a/pygsti/modelmembers/operations/lindbladerrorgen.py b/pygsti/modelmembers/operations/lindbladerrorgen.py index 40b1b478e..33a241922 100644 --- a/pygsti/modelmembers/operations/lindbladerrorgen.py +++ b/pygsti/modelmembers/operations/lindbladerrorgen.py @@ -1161,6 +1161,8 @@ def set_coefficients(self, elementary_errorgens, action="update", logscale_nonha ------- None """ + if not elementary_errorgens: + return #check the first key, if local then no need to convert, otherwise convert from global. first_key = next(iter(elementary_errorgens)) if isinstance(first_key, (_GlobalElementaryErrorgenLabel, tuple)): diff --git a/pygsti/serialization/jsoncodec.py b/pygsti/serialization/jsoncodec.py index fe3915a98..377545a8d 100644 --- a/pygsti/serialization/jsoncodec.py +++ b/pygsti/serialization/jsoncodec.py @@ -10,14 +10,12 @@ # http://www.apache.org/licenses/LICENSE-2.0 or in the LICENSE file in the root pyGSTi directory. #*************************************************************************************************** -# XXX this module should certainly be rewritten as a custom `json.JSONEncoder` import base64 as _base64 import collections as _collections import importlib as _importlib import types as _types import uuid as _uuid - import numpy as _np import scipy.sparse as _sps From bf4dc6f1eeedb765be4736f44160dc681385ebec Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Sun, 17 Aug 2025 18:08:25 -0600 Subject: [PATCH 09/32] Change default gauge optimization for run_long_sequence_gst Change the default for run_long_sequence_gst's gauge optimization to use stdgaugeopt --- pygsti/drivers/longsequence.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygsti/drivers/longsequence.py b/pygsti/drivers/longsequence.py index 6e89385da..80023f898 100644 --- a/pygsti/drivers/longsequence.py +++ b/pygsti/drivers/longsequence.py @@ -319,7 +319,7 @@ def run_long_sequence_gst(data_filename_or_set, target_model_filename_or_object, output_pkl=None, verbosity=2, checkpoint=None, checkpoint_path=None, disable_checkpointing=False, simulator: Optional[ForwardSimulator.Castable]=None, - gauge_opt_suite_name: str = None): + gauge_opt_suite_name: str = 'stdgaugeopt'): """ Perform long-sequence GST (LSGST). @@ -380,7 +380,7 @@ def run_long_sequence_gst(data_filename_or_set, target_model_filename_or_object, argument, which is specified internally. The `target_model` argument, *can* be set, but is specified internally when it isn't. If `None`, then the dictionary `{'item_weights': {'gates':1.0, 'spam':0.001}}` - is used. If `False`, then then *no* gauge optimization is performed. + is used. advanced_options : dict, optional Specifies advanced options most of which deal with numerical details of @@ -463,7 +463,7 @@ def run_long_sequence_gst(data_filename_or_set, target_model_filename_or_object, fwdsim = ForwardSimulator.cast(simulator), and we set the .sim attribute of every Model we encounter to fwdsim. - gauge_opt_suite_name : str, optional (default None) + gauge_opt_suite_name : str, optional (default 'stdgaugeopt') An optional string specifying a named gauge optimization suite. Returns From 23993d8d26e0cafb512f06ca2a9a835a68f6bbdc Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Sun, 17 Aug 2025 19:52:41 -0600 Subject: [PATCH 10/32] Update gauge opt for run_long_sequence_gst_base Update the default gauge optimization settings of run_long_sequence_gst_base to match the non base counterpart. --- pygsti/drivers/bootstrap.py | 2 +- pygsti/drivers/longsequence.py | 32 ++++++++++++++++++++++---------- 2 files changed, 23 insertions(+), 11 deletions(-) diff --git a/pygsti/drivers/bootstrap.py b/pygsti/drivers/bootstrap.py index 009998fbf..345973a8f 100644 --- a/pygsti/drivers/bootstrap.py +++ b/pygsti/drivers/bootstrap.py @@ -224,7 +224,7 @@ def create_bootstrap_models(num_models, input_data_set, generation_method, datasetList[run], target_model, fiducial_prep, fiducial_measure, germs, max_lengths, verbosity=verbosity) - modelList.append(results.estimates.get('default', next(iter(results.estimates.values()))).models['go0']) + modelList.append(results.estimates.get('default', next(iter(results.estimates.values()))).models['stdgaugeopt']) if not return_data: return modelList diff --git a/pygsti/drivers/longsequence.py b/pygsti/drivers/longsequence.py index 80023f898..5b2fc3503 100644 --- a/pygsti/drivers/longsequence.py +++ b/pygsti/drivers/longsequence.py @@ -491,19 +491,17 @@ def run_long_sequence_gst(data_filename_or_set, target_model_filename_or_object, assert not (gauge_opt_suite_name and gauge_opt_params), 'Can only specify one of `gauge_opt_suite_name` or `gauge_opt_params`' - if gauge_opt_params is None and gauge_opt_suite_name is None: - gauge_opt_params = {'item_weights': {'gates': 1.0, 'spam': 0.001}} - gopt_suite = _proto.GSTGaugeOptSuite( - gaugeopt_argument_dicts= {'go0': gauge_opt_params}, - gaugeopt_target=target_model) - elif gauge_opt_suite_name is not None: + if gauge_opt_suite_name is not None: gopt_suite = _proto.GSTGaugeOptSuite( gaugeopt_suite_names= gauge_opt_suite_name, gaugeopt_target=target_model) else: + if gauge_opt_params is None: + gauge_opt_params = {'item_weights': {'gates': 1.0, 'spam': 0.001}} gopt_suite = _proto.GSTGaugeOptSuite( gaugeopt_argument_dicts={'go0': gauge_opt_params}, gaugeopt_target=target_model) + initial_model = _get_gst_initial_model(target_model, advanced_options) proto = _proto.GateSetTomography(initial_model, gopt_suite, @@ -533,7 +531,8 @@ def run_long_sequence_gst_base(data_filename_or_set, target_model_filename_or_ob advanced_options=None, comm=None, mem_limit=None, output_pkl=None, verbosity=2, checkpoint=None, checkpoint_path=None, disable_checkpointing=False, - simulator: Optional[ForwardSimulator.Castable]=None): + simulator: Optional[ForwardSimulator.Castable]=None, + gauge_opt_suite_name: Optional[str] = 'stdgaugeopt'): """ A more fundamental interface for performing end-to-end GST. @@ -624,6 +623,9 @@ def run_long_sequence_gst_base(data_filename_or_set, target_model_filename_or_ob fwdsim = ForwardSimulator.cast(simulator), and we set the .sim attribute of every Model we encounter to fwdsim. + gauge_opt_suite_name : str, optional (default 'stdgaugeopt') + An optional string specifying a named gauge optimization suite. + Returns ------- Results @@ -638,9 +640,19 @@ def run_long_sequence_gst_base(data_filename_or_set, target_model_filename_or_ob ds = _load_dataset(data_filename_or_set, comm, printer) data = _proto.ProtocolData(exp_design, ds) - if gauge_opt_params is None: - gauge_opt_params = {'item_weights': {'gates': 1.0, 'spam': 0.001}} - gopt_suite = {'go0': gauge_opt_params} if gauge_opt_params else None + assert not (gauge_opt_suite_name and gauge_opt_params), 'Can only specify one of `gauge_opt_suite_name` or `gauge_opt_params`' + + if gauge_opt_suite_name is not None: + gopt_suite = _proto.GSTGaugeOptSuite( + gaugeopt_suite_names= gauge_opt_suite_name, + gaugeopt_target=target_model) + else: + if gauge_opt_params is None: + gauge_opt_params = {'item_weights': {'gates': 1.0, 'spam': 0.001}} + gopt_suite = _proto.GSTGaugeOptSuite( + gaugeopt_argument_dicts={'go0': gauge_opt_params}, + gaugeopt_target=target_model) + initial_model = _get_gst_initial_model(target_model, advanced_options) proto = _proto.GateSetTomography(initial_model, gopt_suite, From b213ec460e2ca7258e258044c4a675122885e876 Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Sun, 17 Aug 2025 20:04:12 -0600 Subject: [PATCH 11/32] Errant print statements --- pygsti/modelmembers/operations/composedop.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/pygsti/modelmembers/operations/composedop.py b/pygsti/modelmembers/operations/composedop.py index af81419a8..4539679b5 100644 --- a/pygsti/modelmembers/operations/composedop.py +++ b/pygsti/modelmembers/operations/composedop.py @@ -911,11 +911,7 @@ def set_errorgen_coefficients(self, lindblad_term_dict, action="update", logscal sslbls = self.state_space.qudit_labels if isinstance(self.state_space, _statespace.QuditSpace) else self.state_space.labels[0] updated_values_to_set[available_factor_coeffs_first_lbl_type.cast(key, sslbls)] = val values_to_set = updated_values_to_set - - print(f'{values_to_set=}') - print(f'{available_factor_coeffs=}') Ltermdict_local = {k:v for k, v in values_to_set.items() if k in available_factor_coeffs} - print(f'{Ltermdict_local=}') op.set_errorgen_coefficients(Ltermdict_local, action, logscale_nonham, truncate) for k in Ltermdict_local: del values_to_set[k] # remove the values that we just set From bd8a49e2471ac01344a3a9e7d6d55c0d590324d9 Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Sun, 17 Aug 2025 20:28:21 -0600 Subject: [PATCH 12/32] Fix error in set_errorgen_coefficients Fix a small logic bug that meant certain dictionary values weren't be retained properly. --- pygsti/modelmembers/operations/composedop.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pygsti/modelmembers/operations/composedop.py b/pygsti/modelmembers/operations/composedop.py index 4539679b5..404a9387e 100644 --- a/pygsti/modelmembers/operations/composedop.py +++ b/pygsti/modelmembers/operations/composedop.py @@ -910,6 +910,8 @@ def set_errorgen_coefficients(self, lindblad_term_dict, action="update", logscal #implicitly assuming there is only a single tensor product block in the case where it is an ExplicitStateSpace object. sslbls = self.state_space.qudit_labels if isinstance(self.state_space, _statespace.QuditSpace) else self.state_space.labels[0] updated_values_to_set[available_factor_coeffs_first_lbl_type.cast(key, sslbls)] = val + else: + updated_values_to_set[key] = val values_to_set = updated_values_to_set Ltermdict_local = {k:v for k, v in values_to_set.items() if k in available_factor_coeffs} op.set_errorgen_coefficients(Ltermdict_local, action, logscale_nonham, truncate) From db986f1a86f1c8ceaff7aadab92107e71581ecb5 Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Sun, 17 Aug 2025 23:11:43 -0600 Subject: [PATCH 13/32] Report patches Minor patches to address some unbound variable name and related errors in color box plots, and some of type mismatches in the workspace tables. --- pygsti/report/workspaceplots.py | 34 +++++++++++++++++++++----------- pygsti/report/workspacetables.py | 8 +++++++- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/pygsti/report/workspaceplots.py b/pygsti/report/workspaceplots.py index 09d96ad3d..acc41d14e 100644 --- a/pygsti/report/workspaceplots.py +++ b/pygsti/report/workspaceplots.py @@ -45,7 +45,7 @@ def _color_boxplot(plt_data, colormap, colorbar=False, box_label_size=0, - prec=0, hover_label_fn=None, hover_labels=None): + prec=0, hover_label_fn=None, hover_labels=None, return_hover_labels=False): """ Create a color box plot. @@ -85,6 +85,10 @@ def _color_boxplot(plt_data, colormap, colorbar=False, box_label_size=0, E.g. `hover_labels[i,j]` is the string for the i-th row (y-value) and j-th column (x-value) of the plot. + return_hover_labels : bool, optional (default False) + If True, additionally return the parsed (nested) lists of + hover labels for each point. + Returns ------- plotly.Figure @@ -167,8 +171,11 @@ def _color_boxplot(plt_data, colormap, colorbar=False, box_label_size=0, ) fig = go.Figure(data=data, layout=layout) - - return ReportFigure(fig, colormap, plt_data, plt_data=plt_data) + rfig = ReportFigure(fig, colormap, plt_data, plt_data=plt_data) + if return_hover_labels: + return rfig, hover_labels + else: + return rfig def _nested_color_boxplot(plt_data_list_of_lists, colormap, @@ -349,7 +356,7 @@ def _nested_color_boxplot(plt_data_list_of_lists, colormap, #but, it looks like adding in a manual xshift value hacks around this limitation, since we *can* #place an annotation at the very edge of the plotable area, and then semi-manually shift it over. - if hoverLabels[j][i]: #unpopulated squares should have the empty string as their hover labels, skip those. + if hoverLabels is not None and hoverLabels[j][i]: #unpopulated squares should have the empty string as their hover labels, skip those. on_click_annotations.append(dict(x= data.shape[1], y= .5*data.shape[0], yanchor= 'middle', xanchor= 'left', text = hoverLabels[j][i], align= 'left', @@ -558,8 +565,8 @@ def hover_label_fn(val, i, j): else: hover_label_fn = None boxLabelSize = 8 * scale if box_labels else 0 - fig = _color_boxplot(subMxSums, colormap, colorbar, boxLabelSize, - prec, hover_label_fn) + fig, hover_labels = _color_boxplot(subMxSums, colormap, colorbar, boxLabelSize, + prec, hover_label_fn, return_hover_labels=True) #update tickvals b/c _color_boxplot doesn't do this (unlike _nested_color_boxplot) if fig is not None: fig.plotlyfig['layout']['xaxis'].update(tickvals=list(range(nXs))) @@ -567,7 +574,7 @@ def hover_label_fn(val, i, j): xBoxes = nXs yBoxes = nYs - + else: # not summing up if hover_info is True: @@ -643,7 +650,9 @@ def hover_label_fn(val, i, j, ii, jj): hover_label_widths.extend([font.getlength(substring) for substring in split_label]) #Now get the maximum width. max_annotation_width = max(hover_label_widths) - + else: + max_annotation_width = 0 + width = lmargin + 10 * xBoxes + rmargin + max_annotation_width rmargin +=max_annotation_width #manually add in some additional bottom margin for the new toggle buttons for controlling @@ -677,10 +686,11 @@ def hover_label_fn(val, i, j, ii, jj): new_y_1 = -y_abs_1/plottable_height #Now let's update the updatemenus #updatemenus should be a tuple, but for some reason this looks like - #it works... - pfig['layout']['updatemenus'][0]['y'] = new_y_0 - pfig['layout']['updatemenus'][1]['y'] = new_y_0 - pfig['layout']['updatemenus'][2]['y'] = new_y_1 + #it works... + if pfig['layout']['updatemenus']: + pfig['layout']['updatemenus'][0]['y'] = new_y_0 + pfig['layout']['updatemenus'][1]['y'] = new_y_0 + pfig['layout']['updatemenus'][2]['y'] = new_y_1 else: # fig is None => use a "No data to display" placeholder figure trace = go.Heatmap(z=_np.zeros((10, 10), 'd'), diff --git a/pygsti/report/workspacetables.py b/pygsti/report/workspacetables.py index 4147af167..1186fbccf 100644 --- a/pygsti/report/workspacetables.py +++ b/pygsti/report/workspacetables.py @@ -128,7 +128,10 @@ def _create(self, models, titles, display_as, confidence_region_infos, if confidence_region_infos is None: confidence_region_infos = [None]*len(models) - + elif not isinstance(confidence_region_infos, list): + confidence_region_infos = [confidence_region_infos] + + rhoLabels = list(models[0].preps.keys()) # use labels of 1st model povmLabels = list(models[0].povms.keys()) # use labels of 1st model @@ -460,6 +463,9 @@ def _create(self, models, titles, display_as, confidence_region_infos): if confidence_region_infos is None: confidence_region_infos = [None]*len(models) + elif not isinstance(confidence_region_infos, list): + confidence_region_infos = [confidence_region_infos] + opLabels = models[0].primitive_op_labels # use labels of 1st model instLabels = list(models[0].instruments.keys()) # requires an explicit model! From e030a35d79080d35d3abdca07a7a438fd1610073 Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Sun, 17 Aug 2025 23:12:40 -0600 Subject: [PATCH 14/32] Reference regeneration Regenerate reference files for test_packages module to account for recent changes. --- .../cmp_chk_files/Fake_Dataset_none.txt.cache | Bin 13089 -> 12678 bytes .../cmp_chk_files/Fake_Dataset_round.txt | 2 +- .../cmp_chk_files/analysis.dataset | Bin 105025 -> 105284 bytes .../cmp_chk_files/analysis_lgst.dataset | Bin 9718 -> 9891 bytes .../cmp_chk_files/calcMethods1Q.dataset | Bin 8519 -> 8663 bytes .../calcMethods1Q_redmod.dataset | Bin 1674 -> 1696 bytes .../cmp_chk_files/idt_nQsequenceCache.pkl | Bin 2028 -> 2028 bytes .../cmp_chk_files/reportgen.dataset | Bin 8520 -> 8663 bytes .../cmp_chk_files/reportgen2.dataset | Bin 8519 -> 8664 bytes .../cmp_chk_files/test1Qcalc_std_exact.json | 1628 ++++------------- .../cmp_chk_files/test1Qcalc_std_terms.json | 126 +- 11 files changed, 442 insertions(+), 1314 deletions(-) diff --git a/test/test_packages/cmp_chk_files/Fake_Dataset_none.txt.cache b/test/test_packages/cmp_chk_files/Fake_Dataset_none.txt.cache index e03979260fd4132e56f7d144f51e2ec1743184ef..8e1e21753ff13726d966e9bc759630ff4d08d3b7 100644 GIT binary patch literal 12678 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 B8;k$| diff --git a/test/test_packages/cmp_chk_files/Fake_Dataset_round.txt b/test/test_packages/cmp_chk_files/Fake_Dataset_round.txt index 51a0f7397..d0cbe0165 100644 --- a/test/test_packages/cmp_chk_files/Fake_Dataset_round.txt +++ b/test/test_packages/cmp_chk_files/Fake_Dataset_round.txt @@ -1,5 +1,5 @@ ## Columns = 0 count, 1 count -GiGiGi 865 135 +GiGiGi 865 136 GiGiGiGx 803 197 GiGiGiGy 803 197 GiGiGiGxGx 709 291 diff --git a/test/test_packages/cmp_chk_files/analysis.dataset b/test/test_packages/cmp_chk_files/analysis.dataset index 91f10c9e21d788c32bfe20d6c2a56805f4b0b601..c85d55ee60defa5056ab8adfd3c59e4c13cc23bb 100644 GIT binary patch literal 105284 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 V-=mnT4G&fO>S6M-h}YOd{y+6WU10zK diff --git a/test/test_packages/cmp_chk_files/analysis_lgst.dataset b/test/test_packages/cmp_chk_files/analysis_lgst.dataset index 086697708954800d0da127ea977c8fc9a7cc7cab..ab5fe3f2a2bd7f8c24a8b23a5b6aebffeb133363 100644 GIT binary patch literal 9891 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 uUG zMk--l;7U`+R`Mz~h-`5=zk-*@#Z=bVH^MJ`14e3DwO7->p`h8Zn90SA$S7ayD5&|m zB_Y;P)YVxvuOt0~+>?}!jy!9-W0@OcOG^cHDQ2L`_F@rlqSL=~?)KxyrxflUzu7h) zKpU)!-QRv??)!f0y(?|e_Cvou^24YeH#=g*(o}XaXQhAQVx38KF;>hNNfDfM)aG&! zzoj-i61nAN73rpnLzkzIKR@$Q8#~Qj>rbkBqR>Ac?B8D;?H>&GFXjqHA(oxb@o3|G z6q~4u^STzxtB6B;_l*n=3{to~&(EeH**W%DP9~Z~&XFfXVxHhasU@Zb` z5vYoQPk*|9zc0%>ql`@)WPg5im|gntFuVEjVYd3gVaEPC#BOYhvKgEYJ8OK;opy!& z{ZNzza8KzI+>djsC>KJxlQ^<*gY%1<*dT)@IB66E^`5&e{gOG?$_|iY>cvNR}?mZc1+;>!q@26hA11ueUr#{68D7g zx%xiZg?rd{3M=D!3D*O--i>3P>&53b--i#k-{mWRsjz$p#^5^o@E-bt91Ez2eWtK` zIKS|X!fqkoB=U@*oa^rlzK3y+AzvAv+i2&6rLgcO^y`x1*?*U>;od*-xrFb#k@MaS og^ghxCedbw9A#W9;d=n*7JgKi^*|;5TqoD diff --git a/test/test_packages/cmp_chk_files/calcMethods1Q_redmod.dataset b/test/test_packages/cmp_chk_files/calcMethods1Q_redmod.dataset index 34d84959093bf69be3163654fbad6238d80262b4..b16041e7eacdab3bbb348e2506e3ec179bd33c43 100644 GIT binary patch delta 292 zcmeC;UBJuQz%o^oWg=@LD~CpPt>(nc0#<%7^#DI+ZjIt1hAd?!4H$&@`$(Bs!&d%OU-YkCYQ-Y>sID4~rv%#2b z-W)I{hc_3D$>q%hWAb?OL79^`GsW_TYX(ix@Mg$}@@CA4nJmj}Dw)*7o|coJSYl=} zW%86B_M+5+kjjG8DH%zVvzcu@@_JZ8Lp@!9QksoZYNzxtXXcem=@R_$Slcy{vtgg7 wOOmx)=-)*Q#Xw^cODjC{((=%}DoczQRGm|Nkr}VHFr51!#7Nkzeh?(5TY|EH2c|Wt5VY1sz zzRw5yre1fw*RY>s)hUMJq#pLf(hASKwEQU<{yp3&scDI&IVCBX$t6HjGORbVvCLuu E099RVA^-pY diff --git a/test/test_packages/cmp_chk_files/idt_nQsequenceCache.pkl b/test/test_packages/cmp_chk_files/idt_nQsequenceCache.pkl index e2c2043ac3f451738e57867b5ea51c5d927e63ce..9da93424735a28fcf379badcb881cb3b7a941ab5 100644 GIT binary patch delta 74 zcmV-Q0JZ<@59|-HUjqnklRn7qc6NG`X9L0r97gTb!UYZ+lQ9In2!!zBH$Tgg3!>$#k=_1qT5LsC8TXIyVeqv%v-{0l~Q+S^xk5 delta 74 zcmV-Q0JZ<@59|-HUjqnFpS3XyK?_2YX9L0rag%NM2N=d}lQ9In2>1~Z{t)?GyOZ_= gv4@|eIcJr zmWo9+msA(@ls#2SXw95RYqMSw0upgU%_lO(pks=hlZ;{p-8{0b=2OcBeHiH;*!S&e zytJ*>4(8OEA02XhYwgUP_QD!|tqYA;Y8bQm`o`K$=~D|mU|vJQV=(7ot*6>5fY-fM zUb#^)uTj@4u?pbTi?xf!{gB1IPsF{U@K0zlt*55BucD|{tz(a~n4?1xAtXQrM70Y- zMYXY?hKR;uxS)khmV~~FBm~wVX?p6@_^Hvtm=RJNPHOkaB@vkkkdd&axfx*_i=~tM z?iXX74HZS>knqfnJY!DgW6UeUTZ^Ecc@ok2`bm^Dei|fAjkS<8e5@TxL}PU&D`Q_} zSk-=XB zD^pJce}7{dID^zs&ET(rrPNbVl~PX7A1EP`s>bSgRQ{^gw$(KaMeHojAzQ558vJ)w zRQp8xRO1@^Dz=16a{tX=wGfw&UH%2&6tsE}1p3tJ?n#8XqXVK$Oo zN>^jkcy#`0cJ{H`P(ofO39{1CnCn<$?C+VkLZlE2EQ^F&`Yf<(EcQ}$Hx8oTW2vlE zS44mX=KYlAg70guOgt7IT)i^!SfmziO;?1^)MH`QAwEu8WnSDK<4Ow1B4Vm4S(hh3 zLm;BWPO3xaA0z9ac=58SZ07UB^#T*7a-ph?14JV^#4@eJ;U7ZS`5KTx$VIBAsuzpP zQL#kTECGV@$aq+Cfz~1OkB)y^deL!fK!4p zRYJ$c!X#uO&{qUcO}YT_QdSmA_xnKdZ?fZ%$I=-&<}rcmv$P?>5m)x6`1pg`l)Lp)8&+{3g%BQz1z1T6%$KpR1Au#KQ?&_PfKY$s?tbQ07FT?BQ( zE`oMJh@cR36Vwg63EB;N3EB($3EB_61oc86L49z5paXD_po0)0C<2EFIs^j*4Zt8l zgD^zU5DXJE3{irjFhbA>93|)|j1n{o#|SzG#|b(PV+4)CI6>nOBPa$Z2|5X<2|5jD z2|5dt1Wm#eK~r#^pz|%LFaMb%L(LO@eO1ZGvt?nV>S< zCFm}^;~>>)&1ao6YTIg0=CfHfY3g~lbKB1qQ)*F7#q^}fj-sb=V>b-pVK69G!(lcy zl&p4Pys#8s#tEx2pS3gXjf%ov2PLx|Z=oU6i9^Y1O}uHwO+9J9%be;nD+LuBM6R?{ zSh1~^lAbe<4720sHd9|xou`=)8d|OCY(8#|MA@^}qFShW#LZeuhB~bl45Z(|BGC;D zX}7kZ2K&9vF&9&-@k1ntk!sG`AjaIw=-LrS$j%N$4gl=#gmh{gFVe^5{R9dTdm3b(vpgFQ>DR2 zQ?qZJf4f1srrZdpR3ljokM@R-m7?K+-f%i!G>h@vLY`$C=TBl2RdL?X;sq6P(^uY=<}>cQI4C#W(GDhu$h518K}{pp5OQ7YA&WI z(Q!rjXF^f_!0Q=af8q5p&ZS~X`R55`wE@RJCzLXK_7TpjX!8{BALI2eUccid?Y@9q!9p@P*;}-L)TafE}#6KV(-b=q_1@C{wxl1@cLyqqzl(A;KZ$|%fp8qoX zEI&c}C*#U0dspZvBi|U~yMouZsF7(xZBqAYBXazRb1m#SzB`NKPJGVZ`3>hsaRi(b zc_^dq^4~bmE{t6nK|F~Z7$@UY91VDn@0Xb`rl0Zrg17ouH59_0|e zcMeCvqcCkuKg%h;EB%j6<6IBUF}=IcwgWYMfzMHw^N!F{X7BCCIk8@=t@z$IXea#( U{m$Al8_e~X)Xc_L5l_Yb1LfAXK>z>% literal 8520 zcmeHM-A@!(6d#Z;g<3_6RxKsO%3=v1(ybxJAP^J}MT20iQE)#7W;e_3W@lDG#cWO0 z#>|`3mp(M@LlYkT6L{=X)3oW6eQKJtP1>eSzpb@BckaEjXFq1|whul58Seb<%=w+) zIrrQHd&$+kv${)jzgC@q-4cmqCX!Ko8T1v?xr8&j(kqLm73;+Zk!;K|<&9%+A5AT# zGrDQ&(NX@;x$ktp=`fzUc`KWCx^XnEjAJ)kUmXtgcRO~=d?J?A=M$l@o-iHIZaenY z`BWs5&1CdsM4yjEovCctsOL0Bll`1<9}by%DjYX^T~o(ynq(9+=;Xd**PmEU#|Dt@ zj&s|Y#>?1jH@kB~{jOi5*=RAIDPT5Q(QKuR8JnwQX6%rDjMn`99pn_nye?WNc zR(TH^6T*9`X=VQbgDIad+AJ1yXJOFQ`j;f_l}RcqG5RnQWS!k)?iGRpA|uL7^cfeu zOt9_2lF0pbi_v#$1kseQeLjiOlv7nS<*Q$6S~+T-I&@q_rd zM8vM*46>bh4WaU`W@Y@Lu_l(PvuGQ5Br0_*Oh&`=B^#L6*SH3cO(jXP@uaEPRNDI7 zU6$WzFBHk@o*Pu|^*o2HDOuBeWE?6TykDRWcfHz*QjH9LomaLR8baY>R}+Hg-rJU? z9I6o5!Y~ziCBEH8ol{EYvpuR$F59g64cnIA>uMd+lxrw3Rpo=XIV2EEDvN^KRa{cf z)VIs3mFGE%tI`utMODi9{kpAM2k*9wN9y4I=_;sF$*Mspag!-`skd3Zko>m^;ZSvM zbx@Sdr#wAL$}F?qh!-!D;ubwO;2)AOltof%s%%}7K`c!f2G!E^xvgEwmkU!$@lP9Ce8Qx$UYUq|$VJo&igrbB*4@}%>wEz_aYAB^|a zXj*)L{dfg`h^D8aRfT|QDJJF}x!)(_|50`sexCg*`OE3o^QTJZLU`)KPL-O%*;vAg zB~3iMF%z+fj>kBlLG9FCJim#b#DqaD)Dct%4Fokn6G2U|g`h3aLQo5AC1@*bBWN46 z64VMi3EBy51hv6#f_B3mg7&~Zg7!f>LG92%PzQ7p)Cv0u+7AZ^IsgHJ0&tL^gU~}z z5A+h$3;hK3!vH}8Fi6lK1PKbl5rU4u5J5w5l%S(ED*E+20;eI35r94padidNgw9g9UQcJw=q8@r|-k6%Hv zYYwroCTlkfd@lwQF@HA+k(af;j7|JeBBo|Un{9|U<9Mu;>*@PA4*@=(4WCF2cYP&I#T3XVP zZz|h+e`@x%GjG&rSG4P0QQeGWx`sNsj%EkDdOErmQW+}~O3tTPx^aFSyQq%yrV&c( zhy#ZX5A<~RbgXv#@2@TFKL9Au{n{8e@=(4W7mhs5dKBfjF>GXDBLf>5D9C_Me|mbq zFV~Y}nl?D1X-`Hq?Kiw0gC}|KRm2 zUefN15iK`2rVYQ2?=<5)<7C`oeti>ieTVpactK&p3Ah$H&O=?Wi_fkN5TH z|9swm4t?exq5Y!~ZJoUl@U_G@&-Bd%YGpe!#f~_8i}x#c>BdXYc%i z^FufS&WSwaP!Fxa-CvcQ`k@-*Lowq Z_Z8Yn|3bgJw#)`|JuEe|u@CXo*uQYwZBGCI diff --git a/test/test_packages/cmp_chk_files/reportgen2.dataset b/test/test_packages/cmp_chk_files/reportgen2.dataset index 57c00191d7b6dcdd61d8a0c607f7e74335137f8f..75822ae8e80bbf24e4449a7e11ef9458b04bdffa 100644 GIT binary patch literal 8664 zcmeHM-)|h%6}~pcc4EgQE?pw>%QX?ZPA$tZ+bA^=a_p5!IH?K7Nofi;`(vEh)p~ce zGaK2EpbeBCtww6AOr`!2@X*H!3FV!BCoo!e-E{_2FW#D%OHEI>&hT2f zSTWYh*_=_fToSLk&d6FdpKsLbMkQ~o6$|cCBWK>Lh|EFfo4$Zt)-tNOk~QVIg7cE3 zSiv%PT<6}$H*3Wi(7ooq>MrAJ?sM*wi+c9iX>Vjn3@`c2xfVBCPucF;KN<+$&F+61^Y?*}dmL$a1plMEYr-@QyCXo?V z8(wOU=q>Rx6C)!@(+n~q50*@)T6@ADyBw%AoD=@a$|-bKWD(A)BEr3hyRu3m7T-CE znkGtvrs=R3nnsPCLrIyku5@J_Yc0#w8Gl=%8I+Kn7@{?(>eMPucBs!-VltlPr7jgMG{MlV$hjssZlUQYmj9cYEbX*PJ?358rm76HOQ2PD%w)22qt1BL{oLx zoscS8)%Lc!t)ciQOL6EH8?=V#la)5#Hs3Lo#_^UT5zs^{l)*|Ob<%Z13;LYW5%5LJ z(tJu-6koKyq<8Z9>DLvi#);K*jME>o=o)KhiPoaJ5VUxEmuN{kltecm9?15sj~7TF z!Qc7zR+jclk;WS7%*I5Z($beg?$95j?f*h%8-w02fHwmC#$5h78P8#G6`9<7Uj)%L|MAA z$m;MvURrftf*un{ipk=~)K;SV8SV z%oTQy!3b};u4ZuuC2zR~bB$uzE>^TLpSLqhF+p3Lrr>_p_l1L9x|MPYT3J^cAcSowOlsxcCji? z?$DFAdLnPT3SSJBHj*gey@3?||9oR(ux!x{3O*EX`7FgU%{vRfMJ6+*sFqBMd> z>M0$>&~OHFPup3$n0MchixyY5Y6coarLj@la-IH0v0^_mBd?za?c#>v&6W~ybOzST z)vSFcE$0U6My+j1xeYWdW7()#pubN>QnV1rc`qE-*_XY3y>493+E}u$(ZIcUXW;Qr*$%KR%|{O+O8e){gE#oNP2PJJ${oHLMZY+kIaSKZP+={Hm` z*0YVWT~OI6-9}S$`g|lW&7FaKbz{Q--%?}h&eE0dUH(CjxFKFl77Qz2PtJ}d&o$D? z$+6^mwQkq5m9?r&H|&e(q6X}inXMVXsnd_lOioOW-5UG9Kl|nXNI;Zc$+`X0&a}NQ(1q{3RE5>^;%K78)U&ed}`1=duZXe$JBkYfY zpYZ$zY?&o;c`S=8LL*Vk`Z~h7VGUnSD@qQDU=48CSPO%BTb9n#9@cRzz$hko7 zWo|uA$pubv7~^l?D|N16ZVsaX`@4Ae0X%iC=-|Td>`P?a|Et0QQoj82|tP literal 8519 zcmeHMOKcof7A*&3o7kAd1Sdm?#z=9>aEI^vo2!xP82(dwc_z930F+nrpyn0pDw?EadXBJu6 zQTx`pukN{@ci*-BQt5o<+-}YNI`2g6zEq~Pm{02)q;Hze6VU7~Zk{o%%mfxv<&0(e z501TazOY^_>87ct=jEbv$r*dr5j>r%R=MbmL7G;{vAdo=KO31EbL_s=Tqdut<`PLg zXF8-k@7VpTg;c6sD(U%@zM4rpOXZ|-t)ww}?3+CI$%LsFl38=YHFfNsBZA@>oLqA3 zYwzDEW}={b!Fk?U#>cqM?sauDQ?6g5*XT2@uVFR@V7A%BOz2vf88`ZVjDhO&3&Q7s z=TqsrYG(}9S}MKo4ZII}meNG!GZuLA-dA|K&4?I-FN&=GRGXE`kuV%;YN+rS z?{&9mW-8)&9aDLa8H=9xM$^{*BZi24(HOKu(j&D=x9i_08EQ?^Iuc_Nm0kivOlCN+X%!blA*L9QbknT zoEL-I)b&^!Ax#<=@z1YKqrEDUm}@U9Q?0GE8f(M9wDyWot#UgckuRAdmcH|+=F z6NT(el}Hs)ZQs@m5${w<27jCd1EKY)7H#~x@us&`BX!3hAW=mp(iAkZUj6_J=9-Kl zU{i(Uv&o{V*i^Aix~Hsq()QJbHMTOXJnK~szo-13mQrx2;*j$Kak%?6SXXK=giT(5 z)bJE)H@lq_0{6kOY~)a-AT~xsQH>;Y+Nf*F$wH1t`^6Q9wR&I&t7lyuhi@uls4-R5 zLykERcw6c>g|utArGaValvPJw<*4tWy($7C^+N$G_(^l~KaY+As7gVJb-QY#y z7Slha!C?(j3O^={Lp8b8NpZ4}_6$N&R$0tOd}N!{kLZA6mIh5&CscJW+36&}@`IE1jmwdmGC_Jx#A;>F?Lz!2V z%Fip5N|l%BTyNLih6|a|Uen^&sqh3Dm6zh3UM8Mstd=rU&M9+MbE;CV|LutXA4kVe~Q)i+UL9 zq3sN9r#^=IsGp&J+QHBc8enLEb}_Vz1{oTpJq+!kn;E*9Zei#a8e(XOh8Y^B5r#%+ zKSTTJc7|@J2tyINgP}WUoS|`=U}%D-7@DFeLs6P$XqsXS#pobI2Wf_(89Kz!A-b2L zduf)TS(;;Lj^Yf(X@Q{yI?T{vdVrw^=mpc_T488~PBV0xk_;s&%}|=w7+NEPA%n6EWhuu{j`9rUsmM@~N(`0AV#uPi z44tJ7hBoLahMuBx44tEA7QD^n0BxA&n^ zS}*D8cqV0utKVfD?2aj1zmjHm91yajZ1;M`tLupkDC}*8oRj?k2VA^PTJ|Gx);qEv zLzM0A*H{13#h{3tIV|ild8y64uzw-CFkqhImeHb;9c6Xw@v6x>gIN5&+v);76mMG_} zv@A|`!R7LhxTUtcQ-$?)9dt|OiAzhzA3XY4hjvzbdNi$@snY1o@aUoP^yv8T=vtv< zl@j^Yf~Yq13mBq0^rn$0>cEi$cSXm?#)r=j|KDHR#eV=GK=*4)Zq=cAKdv3Tn*Hdt zw*tQn488UfPO|AM>@ zVC`>s_UrLE)_TF^ Date: Sun, 17 Aug 2025 23:14:19 -0600 Subject: [PATCH 15/32] Update gauge optimization labels Update gauge optimization labels to account for recent default gauge optimization suite change. Turn off gauge optimization for model types which do not properly support it. --- .../drivers/demoCalcMethods2Q.py | 24 ++++---- .../drivers/test_calcmethods1Q.py | 56 +++++++++++-------- .../drivers/test_continuousgates.py | 2 +- test/test_packages/drivers/test_nqubit.py | 6 +- test/test_packages/extras/test_idt.py | 2 +- test/test_packages/iotest/test_codecs.py | 2 +- .../test_packages/objects/test_instruments.py | 6 +- test/test_packages/report/reportBaseCase.py | 26 --------- test/test_packages/reportb/test_workspace.py | 15 ++--- 9 files changed, 62 insertions(+), 77 deletions(-) diff --git a/test/test_packages/drivers/demoCalcMethods2Q.py b/test/test_packages/drivers/demoCalcMethods2Q.py index 06c57aded..06a14005e 100644 --- a/test/test_packages/drivers/demoCalcMethods2Q.py +++ b/test/test_packages/drivers/demoCalcMethods2Q.py @@ -94,14 +94,14 @@ def test_stdgst_matrix(self): self.germs, self.maxLengths, advanced_options=self.advOpts, verbosity=4) #RUN BELOW LINES TO SAVE GATESET (UNCOMMENT to regenerate) - #pygsti.io.write_model(results.estimates['default'].models['go0'], + #pygsti.io.write_model(results.estimates['default'].models['stdgaugeopt'], # compare_files + "/test2Qcalc_std_exact.model","Saved Standard-Calc 2Q test model") # Note: expected nSigma of 143 is so high b/c we use very high tol of 1e-2 => result isn't very good print("MISFIT nSigma = ", results.estimates['default'].misfit_sigma()) self.assertAlmostEqual(results.estimates['default'].misfit_sigma(), 143, delta=2.0) mdl_compare = pygsti.io.load_model(compare_files + "/test2Qcalc_std_exact.model") - self.assertAlmostEqual(results.estimates['default'].models['go0'].frobeniusdist(mdl_compare), 0, places=3) + self.assertAlmostEqual(results.estimates['default'].models['stdgaugeopt'].frobeniusdist(mdl_compare), 0, places=3) def test_stdgst_map(self): # Using map-based calculation @@ -116,7 +116,7 @@ def test_stdgst_map(self): print("MISFIT nSigma = ", results.estimates['default'].misfit_sigma()) self.assertAlmostEqual(results.estimates['default'].misfit_sigma(), 143, delta=2.0) mdl_compare = pygsti.io.load_model(compare_files + "/test2Qcalc_std_exact.model") - self.assertAlmostEqual(results.estimates['default'].models['go0'].frobeniusdist(mdl_compare), 0, places=3) + self.assertAlmostEqual(results.estimates['default'].models['stdgaugeopt'].frobeniusdist(mdl_compare), 0, places=3) def test_stdgst_terms(self): # Using term-based (path integral) calculation @@ -128,13 +128,13 @@ def test_stdgst_terms(self): self.germs, self.maxLengths, verbosity=4) #RUN BELOW LINES TO SAVE GATESET (UNCOMMENT to regenerate) - #pygsti.io.json.dump(results.estimates['default'].models['go0'], + #pygsti.io.json.dump(results.estimates['default'].models['stdgaugeopt'], # open(compare_files + "/test2Qcalc_std_terms.model",'w')) print("MISFIT nSigma = ", results.estimates['default'].misfit_sigma()) self.assertAlmostEqual(results.estimates['default'].misfit_sigma(), 5, delta=1.0) mdl_compare = pygsti.serialization.json.load(open(compare_files + "/test2Qcalc_std_terms.model")) - self.assertAlmostEqual(np.linalg.norm(results.estimates['default'].models['go0'].to_vector() + self.assertAlmostEqual(np.linalg.norm(results.estimates['default'].models['stdgaugeopt'].to_vector() - mdl_compare.to_vector()), 0, places=3) # ## GST using "reduced" models @@ -153,13 +153,13 @@ def test_reducedmod_matrix(self): verbosity=4, advanced_options={'tolerance': 1e-3}) #RUN BELOW LINES TO SAVE GATESET (UNCOMMENT to regenerate) - #pygsti.io.json.dump(results.estimates['default'].models['go0'], + #pygsti.io.json.dump(results.estimates['default'].models['stdgaugeopt'], # open(compare_files + "/test2Qcalc_redmod_exact.model",'w')) print("MISFIT nSigma = ", results.estimates['default'].misfit_sigma()) self.assertAlmostEqual(results.estimates['default'].misfit_sigma(), 1.0, delta=1.0) mdl_compare = pygsti.serialization.json.load(open(compare_files + "/test2Qcalc_redmod_exact.model")) - self.assertAlmostEqual(results.estimates['default'].models['go0'].frobeniusdist(mdl_compare), 0, places=3) + self.assertAlmostEqual(results.estimates['default'].models['stdgaugeopt'].frobeniusdist(mdl_compare), 0, places=3) def test_reducedmod_map1(self): # Using dense embedded matrices and map-based calcs (maybe not really necessary to include?) @@ -174,7 +174,7 @@ def test_reducedmod_map1(self): print("MISFIT nSigma = ", results.estimates['default'].misfit_sigma()) self.assertAlmostEqual(results.estimates['default'].misfit_sigma(), 1.0, delta=1.0) mdl_compare = pygsti.serialization.json.load(open(compare_files + "/test2Qcalc_redmod_exact.model")) - self.assertAlmostEqual(results.estimates['default'].models['go0'].frobeniusdist(mdl_compare), 0, places=1) + self.assertAlmostEqual(results.estimates['default'].models['stdgaugeopt'].frobeniusdist(mdl_compare), 0, places=1) #Note: models aren't necessarily exactly equal given gauge freedoms that we don't know # how to optimizize over exactly - so this is a very loose test... @@ -191,7 +191,7 @@ def test_reducedmod_map2(self): print("MISFIT nSigma = ", results.estimates['default'].misfit_sigma()) self.assertAlmostEqual(results.estimates['default'].misfit_sigma(), 1.0, delta=1.0) mdl_compare = pygsti.serialization.json.load(open(compare_files + "/test2Qcalc_redmod_exact.model")) - self.assertAlmostEqual(np.linalg.norm(results.estimates['default'].models['go0'].to_vector() + self.assertAlmostEqual(np.linalg.norm(results.estimates['default'].models['stdgaugeopt'].to_vector() - mdl_compare.to_vector()), 0, places=1) #Note: models aren't necessarily exactly equal given gauge freedoms that we don't know # how to optimizize over exactly - so this is a very loose test... @@ -207,13 +207,13 @@ def test_reducedmod_svterm(self): verbosity=4, advanced_options={'tolerance': 1e-3}) #RUN BELOW LINES TO SAVE GATESET (UNCOMMENT to regenerate) - #pygsti.io.json.dump(results.estimates['default'].models['go0'], + #pygsti.io.json.dump(results.estimates['default'].models['stdgaugeopt'], # open(compare_files + "/test2Qcalc_redmod_terms.model",'w')) print("MISFIT nSigma = ", results.estimates['default'].misfit_sigma()) self.assertAlmostEqual(results.estimates['default'].misfit_sigma(), 3.0, delta=1.0) mdl_compare = pygsti.serialization.json.load(open(compare_files + "/test2Qcalc_redmod_terms.model")) - self.assertAlmostEqual(np.linalg.norm(results.estimates['default'].models['go0'].to_vector() + self.assertAlmostEqual(np.linalg.norm(results.estimates['default'].models['stdgaugeopt'].to_vector() - mdl_compare.to_vector()), 0, places=3) def test_reducedmod_cterm(self): @@ -229,7 +229,7 @@ def test_reducedmod_cterm(self): print("MISFIT nSigma = ", results.estimates['default'].misfit_sigma()) self.assertAlmostEqual(results.estimates['default'].misfit_sigma(), 3.0, delta=1.0) mdl_compare = pygsti.serialization.json.load(open(compare_files + "/test2Qcalc_redmod_terms.model")) - self.assertAlmostEqual(np.linalg.norm(results.estimates['default'].models['go0'].to_vector() + self.assertAlmostEqual(np.linalg.norm(results.estimates['default'].models['stdgaugeopt'].to_vector() - mdl_compare.to_vector()), 0, places=3) def test_circuitsim_stabilizer_2Qcheck(self): diff --git a/test/test_packages/drivers/test_calcmethods1Q.py b/test/test_packages/drivers/test_calcmethods1Q.py index 620f3b2ba..29bf60583 100644 --- a/test/test_packages/drivers/test_calcmethods1Q.py +++ b/test/test_packages/drivers/test_calcmethods1Q.py @@ -150,21 +150,21 @@ def test_stdgst_matrix(self): #CHECK that copy gives identical models - this is checked by other # unit tests but here we're using a true "GST model" - so do it again: print("CHECK COPY") - mdl = results.estimates[results.name].models['go0'] + mdl = results.estimates[results.name].models['stdgaugeopt'] mdl_copy = mdl.copy() print(mdl.strdiff(mdl_copy)) self.assertAlmostEqual( mdl.frobeniusdist(mdl_copy), 0, places=2) #RUN BELOW LINES TO SAVE GATESET (SAVE) if regenerate_references(): - results.estimates[results.name].models['go0'].write(compare_files + "/test1Qcalc_std_exact.json") + results.estimates[results.name].models['stdgaugeopt'].write(compare_files + "/test1Qcalc_std_exact.json") print("MISFIT nSigma = ",results.estimates[results.name].misfit_sigma()) self.assertAlmostEqual( results.estimates[results.name].misfit_sigma(), 1.0, delta=2.0) mdl_compare = ExplicitOpModel.read(compare_files + "/test1Qcalc_std_exact.json") #gauge opt before compare - gsEstimate = results.estimates[results.name].models['go0'].copy() + gsEstimate = results.estimates[results.name].models['stdgaugeopt'].copy() gsEstimate.set_all_parameterizations("full") gsEstimate = pygsti.algorithms.gaugeopt_to_target(gsEstimate, mdl_compare) print(gsEstimate.strdiff(mdl_compare)) @@ -183,7 +183,7 @@ def test_stdgst_map(self): self.assertAlmostEqual( results.estimates[results.name].misfit_sigma(), 1.0, delta=2.0) mdl_compare = ExplicitOpModel.read(compare_files + "/test1Qcalc_std_exact.json") - gsEstimate = results.estimates[results.name].models['go0'].copy() + gsEstimate = results.estimates[results.name].models['stdgaugeopt'].copy() gsEstimate.set_all_parameterizations("full") gsEstimate = pygsti.algorithms.gaugeopt_to_target(gsEstimate, mdl_compare) self.assertAlmostEqual( gsEstimate.frobeniusdist(mdl_compare), 0, places=0) @@ -205,7 +205,7 @@ def test_stdgst_terms(self): #RUN BELOW LINES TO SAVE GATESET (SAVE) if regenerate_references(): - results.estimates[results.name].models['go0'].write(compare_files + "/test1Qcalc_std_terms.json") + results.estimates[results.name].models['trivial_gauge_opt'].write(compare_files + "/test1Qcalc_std_terms.json") print("MISFIT nSigma = ",results.estimates[results.name].misfit_sigma()) self.assertAlmostEqual( results.estimates[results.name].misfit_sigma(), 1, delta=12) @@ -214,7 +214,7 @@ def test_stdgst_terms(self): # can't easily gauge opt b/c term-based models can't be converted to "full" #mdl_compare.set_all_parameterizations("full") # - #gsEstimate = results.estimates[results.name].models['go0'].copy() + #gsEstimate = results.estimates[results.name].models['stdgaugeopt'].copy() #gsEstimate.set_all_parameterizations("full") #gsEstimate = pygsti.algorithms.gaugeopt_to_target(gsEstimate, mdl_compare) #self.assertAlmostEqual( gsEstimate.frobeniusdist(mdl_compare), 0, places=0) @@ -243,7 +243,7 @@ def test_stdgst_prunedpath(self): #RUN BELOW LINES TO SAVE GATESET (SAVE) if regenerate_references(): - results.estimates[results.name].models['go0'].write(compare_files + "/test1Qcalc_std_prunedpath.json") + results.estimates[results.name].models['trivial_gauge_opt'].write(compare_files + "/test1Qcalc_std_prunedpath.json") print("MISFIT nSigma = ",results.estimates[results.name].misfit_sigma()) self.assertAlmostEqual( results.estimates[results.name].misfit_sigma(), 1, delta=2) @@ -253,9 +253,9 @@ def test_stdgst_prunedpath(self): #A direct vector comparison works if python (&numpy?) versions are identical, but # gauge freedoms make this incorrectly fail in other cases - so just check sigmas - #print("VEC DIFF = ",(results.estimates[results.name].models['go0'].to_vector() + #print("VEC DIFF = ",(results.estimates[results.name].models['stdgaugeopt'].to_vector() # - mdl_compare.to_vector())) - #self.assertAlmostEqual( np.linalg.norm(results.estimates[results.name].models['go0'].to_vector() + #self.assertAlmostEqual( np.linalg.norm(results.estimates[results.name].models['stdgaugeopt'].to_vector() # - mdl_compare.to_vector()), 0, places=3) @@ -278,12 +278,12 @@ def test_reducedmod_matrix(self): #RUN BELOW LINES TO SAVE GATESET (SAVE) if regenerate_references(): - results.estimates[results.name].models['go0'].write(compare_files + "/test1Qcalc_redmod_exact.json") + results.estimates[results.name].models['trivial_gauge_opt'].write(compare_files + "/test1Qcalc_redmod_exact.json") print("MISFIT nSigma = ",results.estimates[results.name].misfit_sigma()) self.assertAlmostEqual( results.estimates[results.name].misfit_sigma(), 0.0, delta=1.0) #mdl_compare = pygsti.io.json.load( open(compare_files + "/test1Qcalc_redmod_exact.json")) - #self.assertAlmostEqual( results.estimates[results.name].models['go0'].frobeniusdist(mdl_compare), 0, places=3) + #self.assertAlmostEqual( results.estimates[results.name].models['stdgaugeopt'].frobeniusdist(mdl_compare), 0, places=3) #NO frobeniusdist for implicit models (yet) def test_reducedmod_map1(self): @@ -296,12 +296,13 @@ def test_reducedmod_map1(self): results = pygsti.run_long_sequence_gst(self.redmod_ds, target_model, self.redmod_fiducials, self.redmod_fiducials, self.redmod_germs, self.redmod_maxLs, verbosity=4, advanced_options={'tolerance': 1e-3}, - disable_checkpointing=True) + disable_checkpointing=True, + gauge_opt_suite_name='none') print("MISFIT nSigma = ",results.estimates[results.name].misfit_sigma()) self.assertAlmostEqual( results.estimates[results.name].misfit_sigma(), 0.0, delta=1.0) #mdl_compare = pygsti.io.json.load( open(compare_files + "/test1Qcalc_redmod_exact.json")) - #self.assertAlmostEqual( results.estimates[results.name].models['go0'].frobeniusdist(mdl_compare), 0, places=1) + #self.assertAlmostEqual( results.estimates[results.name].models['stdgaugeopt'].frobeniusdist(mdl_compare), 0, places=1) #NO frobeniusdist for implicit models (yet) #Note: models aren't necessarily exactly equal given gauge freedoms that we don't know # how to optimizize over exactly - so this is a very loose test... @@ -318,7 +319,8 @@ def test_reducedmod_map1_errorgens(self): results = pygsti.run_long_sequence_gst(self.redmod_ds, target_model, self.redmod_fiducials, self.redmod_fiducials, self.redmod_germs, self.redmod_maxLs, verbosity=4, advanced_options={'tolerance': 1e-3}, - disable_checkpointing=True) + disable_checkpointing=True, + gauge_opt_suite_name='none') print("MISFIT nSigma = ",results.estimates[results.name].misfit_sigma()) self.assertAlmostEqual( results.estimates[results.name].misfit_sigma(), 0.0, delta=1.0) @@ -334,12 +336,13 @@ def test_reducedmod_map2(self): results = pygsti.run_long_sequence_gst(self.redmod_ds, target_model, self.redmod_fiducials, self.redmod_fiducials, self.redmod_germs, self.redmod_maxLs, verbosity=4, advanced_options={'tolerance': 1e-3}, - disable_checkpointing=True) + disable_checkpointing=True, + gauge_opt_suite_name='none') print("MISFIT nSigma = ",results.estimates[results.name].misfit_sigma()) self.assertAlmostEqual( results.estimates[results.name].misfit_sigma(), 0.0, delta=1.0) mdl_compare = CloudNoiseModel.read(compare_files + "/test1Qcalc_redmod_exact.json") - self.assertAlmostEqual( np.linalg.norm(results.estimates[results.name].models['go0'].to_vector() + self.assertAlmostEqual( np.linalg.norm(results.estimates[results.name].models['trivial_gauge_opt'].to_vector() - mdl_compare.to_vector()), 0, places=1) #Note: models aren't necessarily exactly equal given gauge freedoms that we don't know # how to optimizize over exactly - so this is a very loose test... @@ -375,16 +378,17 @@ def test_reducedmod_svterm(self): results = pygsti.run_long_sequence_gst(self.redmod_ds, target_model, self.redmod_fiducials, self.redmod_fiducials, self.redmod_germs, self.redmod_maxLs, verbosity=4, advanced_options={'tolerance': 1e-3}, - disable_checkpointing=True) + disable_checkpointing=True, + gauge_opt_suite_name='none') #RUN BELOW LINES TO SAVE GATESET (SAVE) if regenerate_references(): - results.estimates[results.name].models['go0'].write(compare_files + "/test1Qcalc_redmod_terms.json") + results.estimates[results.name].models['trivial_gauge_opt'].write(compare_files + "/test1Qcalc_redmod_terms.json") print("MISFIT nSigma = ",results.estimates[results.name].misfit_sigma()) self.assertAlmostEqual( results.estimates[results.name].misfit_sigma(), 0.0, delta=1.0) mdl_compare = CloudNoiseModel.read(compare_files + "/test1Qcalc_redmod_terms.json") - self.assertAlmostEqual( np.linalg.norm(results.estimates[results.name].models['go0'].to_vector() + self.assertAlmostEqual( np.linalg.norm(results.estimates[results.name].models['trivial_gauge_opt'].to_vector() - mdl_compare.to_vector()), 0, places=3) def test_reducedmod_svterm_errorgens(self): @@ -400,7 +404,8 @@ def test_reducedmod_svterm_errorgens(self): results = pygsti.run_long_sequence_gst(self.redmod_ds, target_model, self.redmod_fiducials, self.redmod_fiducials, self.redmod_germs, self.redmod_maxLs, verbosity=4, advanced_options={'tolerance': 1e-3}, - disable_checkpointing=True) + disable_checkpointing=True, + gauge_opt_suite_name='none') print("MISFIT nSigma = ",results.estimates[results.name].misfit_sigma()) self.assertAlmostEqual( results.estimates[results.name].misfit_sigma(), 0.0, delta=1.0) @@ -423,7 +428,8 @@ def test_reducedmod_prunedpath_svterm_errorgens(self): results = pygsti.run_long_sequence_gst(self.redmod_ds, target_model, self.redmod_fiducials, self.redmod_fiducials, self.redmod_germs, self.redmod_maxLs, verbosity=4, advanced_options={'tolerance': 1e-3}, - disable_checkpointing=True) + disable_checkpointing=True, + gauge_opt_suite_name='none') print("MISFIT nSigma = ",results.estimates[results.name].misfit_sigma()) self.assertAlmostEqual( results.estimates[results.name].misfit_sigma(), 0.0, delta=1.0) @@ -443,12 +449,13 @@ def test_reducedmod_cterm(self): results = pygsti.run_long_sequence_gst(self.redmod_ds, target_model, self.redmod_fiducials, self.redmod_fiducials, self.redmod_germs, self.redmod_maxLs, verbosity=4, advanced_options={'tolerance': 1e-3}, - disable_checkpointing=True) + disable_checkpointing=True, + gauge_opt_suite_name='none') print("MISFIT nSigma = ",results.estimates[results.name].misfit_sigma()) self.assertAlmostEqual( results.estimates[results.name].misfit_sigma(), 0.0, delta=1.0) mdl_compare = CloudNoiseModel.read(compare_files + "/test1Qcalc_redmod_terms.json") - self.assertAlmostEqual( np.linalg.norm(results.estimates[results.name].models['go0'].to_vector() + self.assertAlmostEqual( np.linalg.norm(results.estimates[results.name].models['trivial_gauge_opt'].to_vector() - mdl_compare.to_vector()), 0, places=1) #TODO: why this isn't more similar to svterm case?? def test_reducedmod_cterm_errorgens(self): @@ -464,7 +471,8 @@ def test_reducedmod_cterm_errorgens(self): results = pygsti.run_long_sequence_gst(self.redmod_ds, target_model, self.redmod_fiducials, self.redmod_fiducials, self.redmod_germs, self.redmod_maxLs, verbosity=4, advanced_options={'tolerance': 1e-3}, - disable_checkpointing=True) + disable_checkpointing=True, + gauge_opt_suite_name='none') print("MISFIT nSigma = ",results.estimates[results.name].misfit_sigma()) self.assertAlmostEqual( results.estimates[results.name].misfit_sigma(), 0.0, delta=1.0) diff --git a/test/test_packages/drivers/test_continuousgates.py b/test/test_packages/drivers/test_continuousgates.py index e8fe88e99..cb42ac12e 100644 --- a/test/test_packages/drivers/test_continuousgates.py +++ b/test/test_packages/drivers/test_continuousgates.py @@ -116,7 +116,7 @@ def sub_Gxrots(circuit): mdl.sim = 'map' # must use map calcs with factories (at least for now, since matrix eval trees don't know about all possible gates?) #mdl.from_vector( datagen_vec ) # DEBUG - used to see at where optimization should get us... - results = pygsti.run_long_sequence_gst_base(ds, mdl, [allStrs], gauge_opt_params=False, verbosity=3) + results = pygsti.run_long_sequence_gst_base(ds, mdl, [allStrs], gauge_opt_params=False, verbosity=3, gauge_opt_suite_name='none') _, nSigma, pval = pygsti.two_delta_logl(results.estimates[results.name].models['final iteration estimate'], results.dataset, dof_calc_method="all") diff --git a/test/test_packages/drivers/test_nqubit.py b/test/test_packages/drivers/test_nqubit.py index afc3aefc8..b57dfc3e1 100644 --- a/test/test_packages/drivers/test_nqubit.py +++ b/test/test_packages/drivers/test_nqubit.py @@ -162,7 +162,8 @@ def test_2Q(self): results = pygsti.run_long_sequence_gst_base(ds, mdl_to_optimize, lsgstLists, gauge_opt_params=False, advanced_options={'tolerance': 1e-1, 'max_iterations': 5}, verbosity=0, - disable_checkpointing= True) #probably don't care about convergence for same reason we + disable_checkpointing= True, + gauge_opt_suite_name='none') #probably don't care about convergence for same reason we #don't for the 3Q case? def test_2Q_terms(self): @@ -200,7 +201,8 @@ def test_2Q_terms(self): results = pygsti.run_long_sequence_gst_base(ds, mdl_to_optimize, lsgstLists, gauge_opt_params=False, advanced_options={'tolerance': 1e-3}, verbosity=0, - disable_checkpointing= True) + disable_checkpointing= True, + gauge_opt_suite_name='none') def test_3Q(self): diff --git a/test/test_packages/extras/test_idt.py b/test/test_packages/extras/test_idt.py index 84fcbbd91..85fa991f1 100644 --- a/test/test_packages/extras/test_idt.py +++ b/test/test_packages/extras/test_idt.py @@ -387,7 +387,7 @@ def test_idletomog_gstdata_nQ(self): # ##In FUTURE, we shouldn't need to set need to set the basis of our nQ GST results in order to make a report #for estkey in gstresults.estimates: # 'default' - # gstresults.estimates[estkey].models['go0'].basis = pygsti.baseobjs.Basis.cast("pp", 16) + # gstresults.estimates[estkey].models['stdgaugeopt'].basis = pygsti.baseobjs.Basis.cast("pp", 16) # gstresults.estimates[estkey].models['target'].basis = pygsti.baseobjs.Basis.cast("pp", 16) ##pygsti.report.create_standard_report(gstresults, temp_files + "/gstWithIdleTomogTestReport", ## "Test GST Report w/Idle Tomography Tab", diff --git a/test/test_packages/iotest/test_codecs.py b/test/test_packages/iotest/test_codecs.py index 9d5686cd1..11083cc07 100644 --- a/test/test_packages/iotest/test_codecs.py +++ b/test/test_packages/iotest/test_codecs.py @@ -63,7 +63,7 @@ def setUp(self): #make a confidence region factory estLbl = self.results.name - crfact = self.results.estimates[estLbl].add_confidence_region_factory('go0', 'final') + crfact = self.results.estimates[estLbl].add_confidence_region_factory('stdgaugeopt', 'final') crfact.compute_hessian(comm=None) crfact.project_hessian('std') diff --git a/test/test_packages/objects/test_instruments.py b/test/test_packages/objects/test_instruments.py index b3f201fcc..c3b9eabc7 100644 --- a/test/test_packages/objects/test_instruments.py +++ b/test/test_packages/objects/test_instruments.py @@ -151,8 +151,8 @@ def testIntermediateMeas(self): #LSGST results = pygsti.run_long_sequence_gst(ds, self.target_model, fiducials, fiducials, germs, max_lengths) - #print(results.estimates[results.name].models['go0']) - mdl_est = results.estimates[results.name].models['go0'] + #print(results.estimates[results.name].models['stdgaugeopt']) + mdl_est = results.estimates[results.name].models['stdgaugeopt'] mdl_est_opt = pygsti.gaugeopt_to_target(mdl_est, mdl_datagen) print("Frobdiff = ", mdl_datagen.frobeniusdist(mdl_est)) print("Frobdiff after GOpt = ", mdl_datagen.frobeniusdist(mdl_est_opt)) @@ -164,7 +164,7 @@ def testIntermediateMeas(self): self.assertEqual(mdl_targetTP.num_params,71) # 3 + 4*2 + 12*5 = 71 #print(mdl_targetTP) resultsTP = pygsti.run_long_sequence_gst(ds, mdl_targetTP, fiducials, fiducials, germs, max_lengths, verbosity=4) - mdl_est = resultsTP.estimates[resultsTP.name].models['go0'] + mdl_est = resultsTP.estimates[resultsTP.name].models['stdgaugeopt'] mdl_est_opt = pygsti.gaugeopt_to_target(mdl_est, mdl_datagen) print("TP Frobdiff = ", mdl_datagen.frobeniusdist(mdl_est)) print("TP Frobdiff after GOpt = ", mdl_datagen.frobeniusdist(mdl_est_opt)) diff --git a/test/test_packages/report/reportBaseCase.py b/test/test_packages/report/reportBaseCase.py index e380ddd53..a2a9fb893 100644 --- a/test/test_packages/report/reportBaseCase.py +++ b/test/test_packages/report/reportBaseCase.py @@ -113,32 +113,6 @@ def setUpClass(cls): 'bad_fit_threshold': -1.0}, disable_checkpointing= True) - #OLD - #lsgst_gatesets_TP = pygsti.do_iterative_mlgst(cls.ds, cls.mdl_clgst_tp, cls.lsgstStrings, verbosity=0, - # min_prob_clip=1e-4, prob_clip_interval=(-1e6,1e6), - # returnAll=True) #TP initial model => TP output models - #cls.results_logL = pygsti.objects.Results() - #cls.results_logL.init_dataset(cls.ds) - #cls.results_logL.init_circuits(cls.lsgstStructs) - #cls.results_logL.add_estimate(target_model, cls.mdl_clgst_tp, - # lsgst_gatesets_TP, - # {'objective': "logl", - # 'min_prob_clip': 1e-4, - # 'prob_clip_interval': (-1e6,1e6), 'radius': 1e-4, - # 'weights': None, 'defaultDirectory': temp_files + "", - # 'defaultBasename': "MyDefaultReportName"}) - # - #tp_target = target_model.copy(); tp_target.set_all_parameterizations("full TP") - #gaugeOptParams = gaugeOptParams.copy() #just to be safe - #gaugeOptParams['model'] = lsgst_gatesets_TP[-1] #so can gauge-propagate CIs - #gaugeOptParams['target_model'] = tp_target #so can gauge-propagate CIs - #_, gaugeEl, go_final_gateset = pygsti.gaugeopt_to_target(**gaugeOptParams) - #gaugeOptParams['_gaugeGroupEl'] = gaugeEl #so can gauge-propagate CIs - #cls.results_logL.estimates['default'].add_gaugeoptimized(gaugeOptParams, go_final_gateset) - # - ##self.results_logL.options.precision = 3 - ##self.results_logL.options.polar_precision = 2 - os.chdir(orig_cwd) def setUp(self): diff --git a/test/test_packages/reportb/test_workspace.py b/test/test_packages/reportb/test_workspace.py index aeffb0e25..d4d0367ea 100644 --- a/test/test_packages/reportb/test_workspace.py +++ b/test/test_packages/reportb/test_workspace.py @@ -136,12 +136,12 @@ def make_cr(mdl): tbls.append( w.SpamParametersTable(self.mdl, cr ) ) tbls.append( w.SpamParametersTable(gsMultiSpam, cr ) ) - tbls.append( w.GatesTable(self.mdl, ["mytitle"], display_as="boxes", confidence_region_info=cr ) ) - tbls.append( w.GatesTable(self.mdl, ["mytitle"], display_as="numbers", confidence_region_info=cr ) ) - tbls.append( w.GatesTable(gsTP, ["mytitle"], display_as="numbers", confidence_region_info=cr2TP ) ) - tbls.append( w.GatesTable(gsCPTP, ["mytitle"], display_as="numbers", confidence_region_info=cr2CPTP ) ) + tbls.append( w.GatesTable(self.mdl, ["mytitle"], display_as="boxes", confidence_region_infos=cr ) ) + tbls.append( w.GatesTable(self.mdl, ["mytitle"], display_as="numbers", confidence_region_infos=cr ) ) + tbls.append( w.GatesTable(gsTP, ["mytitle"], display_as="numbers", confidence_region_infos=cr2TP ) ) + tbls.append( w.GatesTable(gsCPTP, ["mytitle"], display_as="numbers", confidence_region_infos=cr2CPTP ) ) with self.assertRaises(ValueError): - w.GatesTable(self.mdl, ["mytitle"], display_as="foobar", confidence_region_info=cr ) + w.GatesTable(self.mdl, ["mytitle"], display_as="foobar", confidence_region_infos=cr ) tbls.append( w.ChoiTable(self.mdl, ["mytitle"], cr ) ) tbls.append( w.ChoiTable(gsQT, ["mytitle"], None ) ) @@ -316,7 +316,7 @@ def test_plot_creation(self): plts.append(w.MatrixPlot(gmx, -1, 1, ['a','b','c','d'], ['e','f','g','h'], "X", "Y", colormap = pygsti.report.colormaps.DivergingColormap(vmin=-2, vmax=2))) plts.append( w.MatrixPlot(gmx, -1,1, ['a','b','c','d'], ['e','f','g','h'], "X", "Y",colormap=None)) - plts.append( w.GateMatrixPlot(gmx, -1,1, "pp", "in", "out", box_labels=True) ) + plts.append( w.GateMatrixPlot(gmx, -1,1, "pp", 'pp', "in", "out", box_labels=True) ) #Polar plots don't work in latest plotly -- no 'r' variable -- TODO update (but these plots are unused) POLAR SKIP #plts.append( w.PolarEigenvaluePlot([np.linalg.eigvals(self.mdl.operations['Gx'])],["purple"],scale=1.5) ) @@ -672,6 +672,7 @@ def test_plot_basefns(self): # ----- _circuit_color_scatterplot ----- + colormap = pygsti.report.colormaps.SequentialColormap(0,10) pygsti.report.workspaceplots._circuit_color_scatterplot( gss, mxs, colormap, sum_up=True) pygsti.report.workspaceplots._circuit_color_scatterplot( @@ -699,7 +700,7 @@ def test_plot_basefns(self): # ---- _opmatrix_color_boxplot ---- pygsti.report.workspaceplots._opmatrix_color_boxplot( - np.identity(4,'d'), -1.0, 1.0, mx_basis="pp", mx_basis_y="gm") + np.identity(4,'d'), -1.0, 1.0, mx_basis_x="pp", mx_basis_y="gm") # test weird case when there's different bases on diff axes # ---- _matrix_color_boxplot ---- From fefdbfc4fdc8b7f5988e57b244020b2875081b04 Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Sun, 17 Aug 2025 23:16:24 -0600 Subject: [PATCH 16/32] Missed a gauge optimization setting --- test/test_packages/drivers/test_calcmethods1Q.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/test/test_packages/drivers/test_calcmethods1Q.py b/test/test_packages/drivers/test_calcmethods1Q.py index 29bf60583..235f28148 100644 --- a/test/test_packages/drivers/test_calcmethods1Q.py +++ b/test/test_packages/drivers/test_calcmethods1Q.py @@ -359,7 +359,8 @@ def test_reducedmod_map2_errorgens(self): results = pygsti.run_long_sequence_gst(self.redmod_ds, target_model, self.redmod_fiducials, self.redmod_fiducials, self.redmod_germs, self.redmod_maxLs, verbosity=4, advanced_options={'tolerance': 1e-3}, - disable_checkpointing=True) + disable_checkpointing=True, + gauge_opt_suite_name='none') print("MISFIT nSigma = ",results.estimates[results.name].misfit_sigma()) self.assertAlmostEqual( results.estimates[results.name].misfit_sigma(), 0.0, delta=1.0) From 35bc0ace9329f8da4353ad3fdc3c79610c399934 Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Sun, 17 Aug 2025 23:35:14 -0600 Subject: [PATCH 17/32] Revert license field We're encountering CI issues with this field, and some googling indicates this is because there have been some changes to the conventions for this and not all versions of the build utilities (like setuptools) support these changes across all versions yet. --- pyproject.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cca64a6e6..012cc7895 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,7 +48,6 @@ classifiers=[ "Operating System :: MacOS :: MacOS X", "Operating System :: Unix" ] -license = "Apache-2.0" [project.optional-dependencies] diamond_norm = [ From 19494444383ca9e351c549d26bdc2b99c16853e1 Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Mon, 18 Aug 2025 17:49:33 -0600 Subject: [PATCH 18/32] Revert debugging changes Revert changes from when I was debugging. --- pygsti/modelmembers/povms/__init__.py | 1 - pygsti/models/explicitmodel.py | 16 ++++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/pygsti/modelmembers/povms/__init__.py b/pygsti/modelmembers/povms/__init__.py index ba5b8ac71..1fbd899bf 100644 --- a/pygsti/modelmembers/povms/__init__.py +++ b/pygsti/modelmembers/povms/__init__.py @@ -632,7 +632,6 @@ def _objfn(v): else: raise ValueError("Invalid to_type argument: %s" % to_type) except Exception as e: - raise e error_msgs[to_type] = str(e) # try next to_type raise ValueError("Could not convert POVM to to type(s): %s\n%s" % (str(to_types), str(error_msgs))) diff --git a/pygsti/models/explicitmodel.py b/pygsti/models/explicitmodel.py index 210322aac..414dc20e9 100644 --- a/pygsti/models/explicitmodel.py +++ b/pygsti/models/explicitmodel.py @@ -493,14 +493,14 @@ def set_all_parameterizations(self, gate_type, prep_type="auto", povm_type="auto povmtyp = _povm.povm_type_from_op_type(gate_type) if povm_type == "auto" else povm_type ityp = _instrument.instrument_type_from_op_type(gate_type) if instrument_type == "auto" else instrument_type - #try: - self.convert_members_inplace(typ, 'operations', 'all', flatten_structure=True, ideal_model=static_model, cptp_truncation_tol = cptp_truncation_tol) - self.convert_members_inplace(ityp, 'instruments', 'all', flatten_structure=True, ideal_model=static_model, cptp_truncation_tol = cptp_truncation_tol) - self.convert_members_inplace(rtyp, 'preps', 'all', flatten_structure=True, ideal_model=static_model, cptp_truncation_tol = cptp_truncation_tol, spam_cp_penalty = spam_cp_penalty) - self.convert_members_inplace(povmtyp, 'povms', 'all', flatten_structure=True, ideal_model=static_model, cptp_truncation_tol = cptp_truncation_tol, spam_cp_penalty = spam_cp_penalty) - #except ValueError as e: - # raise ValueError("Failed to convert members. If converting to CPTP-based models, " + - # "try providing an ideal_model to avoid possible branch cuts.") from e + try: + self.convert_members_inplace(typ, 'operations', 'all', flatten_structure=True, ideal_model=static_model, cptp_truncation_tol = cptp_truncation_tol) + self.convert_members_inplace(ityp, 'instruments', 'all', flatten_structure=True, ideal_model=static_model, cptp_truncation_tol = cptp_truncation_tol) + self.convert_members_inplace(rtyp, 'preps', 'all', flatten_structure=True, ideal_model=static_model, cptp_truncation_tol = cptp_truncation_tol, spam_cp_penalty = spam_cp_penalty) + self.convert_members_inplace(povmtyp, 'povms', 'all', flatten_structure=True, ideal_model=static_model, cptp_truncation_tol = cptp_truncation_tol, spam_cp_penalty = spam_cp_penalty) + except ValueError as e: + raise ValueError("Failed to convert members. If converting to CPTP-based models, " + + "try providing an ideal_model to avoid possible branch cuts.") from e self.set_default_gauge_group_for_member_type(typ) From 7af7e8966e24b2a0c6a18f2147cdc5b484903483 Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Mon, 18 Aug 2025 17:50:24 -0600 Subject: [PATCH 19/32] Add conversion logic for np floats Something changed in some package which is producing np float objects which didn't have serialization logic in nameddict. Add a cast to standard python float. --- pygsti/tools/nameddict.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pygsti/tools/nameddict.py b/pygsti/tools/nameddict.py index 48a944e12..98724f678 100644 --- a/pygsti/tools/nameddict.py +++ b/pygsti/tools/nameddict.py @@ -90,6 +90,8 @@ def _serialize(x): return x elif isinstance(x, (_np.int64, _np.int32)): return int(x) + elif isinstance(x, (_np.float64, _np.float32)): + return float(x) elif isinstance(x, _NicelySerializable): return x.to_nice_serialization() elif isinstance(x, _np.ndarray): From cd9048576b130dab832f2aef47f9740162b8d277 Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Mon, 18 Aug 2025 17:55:06 -0600 Subject: [PATCH 20/32] Add type annotation --- pygsti/modelmembers/operations/lindbladerrorgen.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pygsti/modelmembers/operations/lindbladerrorgen.py b/pygsti/modelmembers/operations/lindbladerrorgen.py index 33a241922..afc17c20f 100644 --- a/pygsti/modelmembers/operations/lindbladerrorgen.py +++ b/pygsti/modelmembers/operations/lindbladerrorgen.py @@ -9,6 +9,7 @@ # 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. #*************************************************************************************************** +from __future__ import annotations import warnings as _warnings import itertools as _itertools @@ -31,6 +32,7 @@ from pygsti.tools import matrixtools as _mt from pygsti.tools import optools as _ot from pygsti import SpaceT +from typing import Literal IMAG_TOL = 1e-7 # tolerance for imaginary part being considered zero @@ -1120,7 +1122,8 @@ def error_rates(self, label_type='global'): """ return self.coefficients(return_basis=False, logscale_nonham=True, label_type=label_type) - def set_coefficients(self, elementary_errorgens, action="update", logscale_nonham=False, truncate=True): + def set_coefficients(self, elementary_errorgens, action: Literal['update', 'add', 'reset'] = "update", + logscale_nonham=False, truncate=True): """ Sets the coefficients of elementary error generator terms in this error generator. From d4eebb730eee0e2fc11230d54ad146bb143dc1cb Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Mon, 18 Aug 2025 17:56:34 -0600 Subject: [PATCH 21/32] Numpy 2.3 deprecation Numpy 2.3 expired this deprecation for tostring. --- pygsti/report/workspace.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygsti/report/workspace.py b/pygsti/report/workspace.py index 32e725472..61f806657 100644 --- a/pygsti/report/workspace.py +++ b/pygsti/report/workspace.py @@ -157,7 +157,7 @@ def ws_custom_digest(md5, v): if isinstance(v, NotApplicable): md5.update("NOTAPPLICABLE".encode('utf-8')) elif isinstance(v, SwitchValue): - md5.update(v.base.tostring()) # don't recurse to parent switchboard + md5.update(v.base.tobytes()) # don't recurse to parent switchboard else: raise _CustomDigestError() From e89ca79c57c85ff4144177d6af2826e50df6aa39 Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Mon, 18 Aug 2025 18:53:49 -0600 Subject: [PATCH 22/32] Update windows environment variable setting. --- .github/workflows/reuseable-main.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/reuseable-main.yml b/.github/workflows/reuseable-main.yml index cdb0ce56b..82ccd0bbd 100644 --- a/.github/workflows/reuseable-main.yml +++ b/.github/workflows/reuseable-main.yml @@ -67,7 +67,11 @@ jobs: python -m pip install -e .[testing] python setup.py build_ext --inplace - name: Install package (No Cython) - if: ${{ inputs.use-cython != 'true' }} + if: ${{ inputs.use-cython != 'true' }} && ${{ inputs.os == 'windows-latest' }} + run: | + set PYGSTI_CYTHON_SKIP=1 + python -m pip install -e .[testing_no_cython] + if: ${{ inputs.use-cython != 'true' }} && (${{ inputs.os == 'ubuntu-latest' }} || ${{ inputs.os == 'macos-latest' }}) run: | PYGSTI_CYTHON_SKIP=1 python -m pip install -e .[testing_no_cython] - name: Lint with flake8 (Linux only) From 41dde39867aefe788af249c027460a09d7464726 Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Mon, 18 Aug 2025 19:23:44 -0600 Subject: [PATCH 23/32] Try again at updating cython builds --- .github/workflows/reuseable-main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/reuseable-main.yml b/.github/workflows/reuseable-main.yml index 82ccd0bbd..66f2ca012 100644 --- a/.github/workflows/reuseable-main.yml +++ b/.github/workflows/reuseable-main.yml @@ -66,11 +66,12 @@ jobs: run: | python -m pip install -e .[testing] python setup.py build_ext --inplace - - name: Install package (No Cython) + - name: Install package (No Cython, Windows) if: ${{ inputs.use-cython != 'true' }} && ${{ inputs.os == 'windows-latest' }} run: | set PYGSTI_CYTHON_SKIP=1 python -m pip install -e .[testing_no_cython] + - name: Install package (No Cython, Ubuntu and MacOS) if: ${{ inputs.use-cython != 'true' }} && (${{ inputs.os == 'ubuntu-latest' }} || ${{ inputs.os == 'macos-latest' }}) run: | PYGSTI_CYTHON_SKIP=1 python -m pip install -e .[testing_no_cython] From 55b413f11c31500a4ca04d1e21c6a6dad3398285 Mon Sep 17 00:00:00 2001 From: coreyostrove <78768318+coreyostrove@users.noreply.github.com> Date: Mon, 18 Aug 2025 21:23:22 -0600 Subject: [PATCH 24/32] Add debug logging to reuseable-main.yml See if we can figure out why it is that the workflow is triggering the ubuntu and macos branch on windows. --- .github/workflows/reuseable-main.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reuseable-main.yml b/.github/workflows/reuseable-main.yml index 66f2ca012..66a4300bb 100644 --- a/.github/workflows/reuseable-main.yml +++ b/.github/workflows/reuseable-main.yml @@ -69,11 +69,13 @@ jobs: - name: Install package (No Cython, Windows) if: ${{ inputs.use-cython != 'true' }} && ${{ inputs.os == 'windows-latest' }} run: | - set PYGSTI_CYTHON_SKIP=1 + echo "the value of inputs.use-cython!= true and inputs.os == windows-latest are ${{ inputs.use-cython != 'true' }} and ${{ inputs.os == 'windows-latest' }}" + $Env:PYGSTI_CYTHON_SKIP=1 python -m pip install -e .[testing_no_cython] - - name: Install package (No Cython, Ubuntu and MacOS) + - name: Install package (No Cython, Ubuntu and MacOS) if: ${{ inputs.use-cython != 'true' }} && (${{ inputs.os == 'ubuntu-latest' }} || ${{ inputs.os == 'macos-latest' }}) run: | + echo "the value of inputs.use-cython!= true and inputs.os == ubuntu-latest are ${{ inputs.use-cython != 'true' }} and ${{ inputs.os == 'ubuntu-latest' }}" PYGSTI_CYTHON_SKIP=1 python -m pip install -e .[testing_no_cython] - name: Lint with flake8 (Linux only) if: ${{ inputs.os == 'ubuntu-latest'}} From 3ed19eaf7c43714614139d0f5660449fce1b4486 Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Mon, 18 Aug 2025 23:55:56 -0600 Subject: [PATCH 25/32] Revert "Try again at updating cython builds" This reverts commit 41dde39867aefe788af249c027460a09d7464726. --- .github/workflows/reuseable-main.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/reuseable-main.yml b/.github/workflows/reuseable-main.yml index 66f2ca012..82ccd0bbd 100644 --- a/.github/workflows/reuseable-main.yml +++ b/.github/workflows/reuseable-main.yml @@ -66,12 +66,11 @@ jobs: run: | python -m pip install -e .[testing] python setup.py build_ext --inplace - - name: Install package (No Cython, Windows) + - name: Install package (No Cython) if: ${{ inputs.use-cython != 'true' }} && ${{ inputs.os == 'windows-latest' }} run: | set PYGSTI_CYTHON_SKIP=1 python -m pip install -e .[testing_no_cython] - - name: Install package (No Cython, Ubuntu and MacOS) if: ${{ inputs.use-cython != 'true' }} && (${{ inputs.os == 'ubuntu-latest' }} || ${{ inputs.os == 'macos-latest' }}) run: | PYGSTI_CYTHON_SKIP=1 python -m pip install -e .[testing_no_cython] From a07ce6950fd932256333beb2ac6f496579f526f3 Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Mon, 18 Aug 2025 23:56:06 -0600 Subject: [PATCH 26/32] Revert "Update windows environment variable setting." This reverts commit e89ca79c57c85ff4144177d6af2826e50df6aa39. --- .github/workflows/reuseable-main.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.github/workflows/reuseable-main.yml b/.github/workflows/reuseable-main.yml index 82ccd0bbd..cdb0ce56b 100644 --- a/.github/workflows/reuseable-main.yml +++ b/.github/workflows/reuseable-main.yml @@ -67,11 +67,7 @@ jobs: python -m pip install -e .[testing] python setup.py build_ext --inplace - name: Install package (No Cython) - if: ${{ inputs.use-cython != 'true' }} && ${{ inputs.os == 'windows-latest' }} - run: | - set PYGSTI_CYTHON_SKIP=1 - python -m pip install -e .[testing_no_cython] - if: ${{ inputs.use-cython != 'true' }} && (${{ inputs.os == 'ubuntu-latest' }} || ${{ inputs.os == 'macos-latest' }}) + if: ${{ inputs.use-cython != 'true' }} run: | PYGSTI_CYTHON_SKIP=1 python -m pip install -e .[testing_no_cython] - name: Lint with flake8 (Linux only) From 680ba8214d35707f33c1466da1ca09a2e1a65368 Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Tue, 19 Aug 2025 00:06:47 -0600 Subject: [PATCH 27/32] Try making the no cython tests linux only Realistically we probably don't need to run against all OS combinations to achieve the coverage goals of this particular build. Save on some resources. --- .github/workflows/reuseable-main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/reuseable-main.yml b/.github/workflows/reuseable-main.yml index cdb0ce56b..47e0e30e5 100644 --- a/.github/workflows/reuseable-main.yml +++ b/.github/workflows/reuseable-main.yml @@ -66,8 +66,8 @@ jobs: run: | python -m pip install -e .[testing] python setup.py build_ext --inplace - - name: Install package (No Cython) - if: ${{ inputs.use-cython != 'true' }} + - name: Install package (No Cython, Linux only) + if: ${{ inputs.use-cython != 'true' && inputs.os == 'ubuntu-latest' }} run: | PYGSTI_CYTHON_SKIP=1 python -m pip install -e .[testing_no_cython] - name: Lint with flake8 (Linux only) From 663f57db49f39f78138e8429a56a3c10b83323db Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Wed, 20 Aug 2025 13:26:30 -0600 Subject: [PATCH 28/32] More changes to have no cython only on linux Previous attempt was still running partial workflows on windows and macos. --- .github/workflows/reuseable-main.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/reuseable-main.yml b/.github/workflows/reuseable-main.yml index 47e0e30e5..229bdc7ab 100644 --- a/.github/workflows/reuseable-main.yml +++ b/.github/workflows/reuseable-main.yml @@ -31,6 +31,7 @@ env: jobs: build-and-test: + if: ${{ inputs.use-cython == 'true' || inputs.os == 'ubuntu-latest' }} runs-on: ${{ inputs.os }} steps: - uses: actions/checkout@v4 From d5f7037c5405395a172b94afcd32faf1f0b9a4ba Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Wed, 20 Aug 2025 15:48:51 -0600 Subject: [PATCH 29/32] Resolve circular imports And remove a couple unused imports from fogistore.py --- pygsti/models/fogistore.py | 6 +----- pygsti/tools/leakage.py | 3 ++- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/pygsti/models/fogistore.py b/pygsti/models/fogistore.py index 06035e7ad..b494e42d0 100644 --- a/pygsti/models/fogistore.py +++ b/pygsti/models/fogistore.py @@ -13,19 +13,14 @@ import numpy as _np import scipy.sparse as _sps import copy as _copy -import warnings as _warnings -import itertools as _itertools -import collections as _collections from pygsti.baseobjs import Basis as _Basis from pygsti.tools import matrixtools as _mt -from pygsti.tools import optools as _ot from pygsti.tools import slicetools as _slct from pygsti.tools import fogitools as _fogit from pygsti.baseobjs.nicelyserializable import NicelySerializable as _NicelySerializable from pygsti.baseobjs.label import Label as _Label from pygsti.baseobjs.errorgenlabel import GlobalElementaryErrorgenLabel as _GlobalElementaryErrorgenLabel from pygsti.tools.slicetools import slice_hash as _slice_hash -from pygsti.baseobjs.errorgenspace import ErrorgenSpace as _ErrorgenSpace class FirstOrderGaugeInvariantStore(_NicelySerializable): """ @@ -353,6 +348,7 @@ def _from_nice_serialization(cls, state): allop_gauge_action = cls._decodemx(state['allop_gauge_action']) gauge_space_directions = cls._decodemx(state['gauge_space_directions']) dependent_fogi_action = state['dependent_fogi_action'] + from pygsti.baseobjs.errorgenspace import ErrorgenSpace as _ErrorgenSpace gauge_space = _ErrorgenSpace.from_nice_serialization(state['gauge_space']) norm_order = state['norm_order'] diff --git a/pygsti/tools/leakage.py b/pygsti/tools/leakage.py index d5dbe8f31..f748f6a2b 100644 --- a/pygsti/tools/leakage.py +++ b/pygsti/tools/leakage.py @@ -15,7 +15,6 @@ from pygsti.tools import optools as pgot from pygsti.tools import basistools as pgbt from pygsti.tools.basistools import stdmx_to_vec -from pygsti.processors import QubitProcessorSpec from pygsti.baseobjs.basis import TensorProdBasis, Basis, BuiltinBasis import numpy as np import warnings @@ -24,6 +23,8 @@ if TYPE_CHECKING: from pygsti.protocols.gst import ModelEstimateResults, GSTGaugeOptSuite from pygsti.models import ExplicitOpModel + from pygsti.processors import QubitProcessorSpec + # Question: include the parenthetical in the heading below? From 1313b5998763d9a7c10a48c5354433880c6f8400 Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Wed, 20 Aug 2025 16:28:06 -0600 Subject: [PATCH 30/32] Fix mpi test Strip newlines and carriage returns from output before doing error checking. --- test/unit/mpi/test_mpi.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/unit/mpi/test_mpi.py b/test/unit/mpi/test_mpi.py index 21e23a982..2c9af4706 100644 --- a/test/unit/mpi/test_mpi.py +++ b/test/unit/mpi/test_mpi.py @@ -25,7 +25,9 @@ def test_all(self, capfd: pytest.LogCaptureFixture): result = subprocess.run(subprocess_args, capture_output=False, text=True) out, err = capfd.readouterr() - if len(out) + len(err) > 0: + + #strip new lines/carriage returns before checking length. + if len(out.replace('\n', '').replace('\r', '')) + len(err.replace('\n', '').replace('\r', '')) > 0: msg = out + '\n'+ 80*'-' + err raise RuntimeError(msg) return From 8dc6316af2dc70b0fe4919264d051b1dab822b7a Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Wed, 20 Aug 2025 16:59:18 -0600 Subject: [PATCH 31/32] Lower max depth for instrument fisher info Lower the maximum depth of the fisher information computation with instruments in unit test. Also a miscellaneous workflow change. --- .github/workflows/reuseable-main.yml | 2 +- test/unit/tools/test_edesigntools.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/reuseable-main.yml b/.github/workflows/reuseable-main.yml index 229bdc7ab..1d2b38e89 100644 --- a/.github/workflows/reuseable-main.yml +++ b/.github/workflows/reuseable-main.yml @@ -87,7 +87,7 @@ jobs: if: ${{ inputs.run-extra-tests == 'true' }} run: | python -Ic "import pygsti; print(pygsti.__version__); print(pygsti.__path__)" - python -m pytest -v -n auto --dist loadscope --ignore=test/test_packages/mpi --ignore=test/test_packages/notebooks --durations=25 test/test_packages + python -m pytest -v -n auto --dist loadscope --ignore=test/test_packages/notebooks --durations=25 test/test_packages - name: Run notebook regression if: ${{ inputs.run-notebook-tests == 'true' }} run: | diff --git a/test/unit/tools/test_edesigntools.py b/test/unit/tools/test_edesigntools.py index 0a888d87b..9c9b3b5f5 100644 --- a/test/unit/tools/test_edesigntools.py +++ b/test/unit/tools/test_edesigntools.py @@ -138,7 +138,7 @@ def setUp(self): prep_fiducials = smq1Q_XYI.prep_fiducials() meas_fiducials = smq1Q_XYI.meas_fiducials() self.lsgst_list_instruments = create_lsgst_circuit_lists( - self.target_model_inst,prep_fiducials,meas_fiducials,germs,self.Ls) + self.target_model_inst,prep_fiducials,meas_fiducials,germs, self.Ls[:-1]) def test_calculate_fisher_information_matrix(self): @@ -194,7 +194,7 @@ def test_calculate_fisher_information_matrix_with_instrument(self): #Test by L: fim_approx_by_L = et.calculate_fisher_information_matrices_by_L(self.target_model_inst, self.lsgst_list_instruments, self.Ls, approx=True) - self.assertTrue(_np.linalg.norm(fim_approx-fim_approx_by_L[8])<1e-3) + self.assertTrue(_np.linalg.norm(fim_approx-fim_approx_by_L[4])<1e-3) class EdesignPaddingTester(BaseCase): From 3069a1eb938353f78bd6ae1feee1d870c68d0f9b Mon Sep 17 00:00:00 2001 From: Corey Ostrove Date: Wed, 20 Aug 2025 17:51:48 -0600 Subject: [PATCH 32/32] Gram matrix bugfix Fix a bug related to a mismatch between current behavior of DataSet.gate_labels and target model labels. --- pygsti/algorithms/core.py | 5 ++++- pygsti/algorithms/grammatrix.py | 2 +- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/pygsti/algorithms/core.py b/pygsti/algorithms/core.py index 0c2b18e73..81871d5e9 100644 --- a/pygsti/algorithms/core.py +++ b/pygsti/algorithms/core.py @@ -14,6 +14,7 @@ import collections as _collections import time as _time import copy as _copy +import warnings as _warnings import numpy as _np import scipy.optimize as _spo @@ -33,7 +34,6 @@ from pygsti.baseobjs.resourceallocation import ResourceAllocation as _ResourceAllocation from pygsti.optimize.simplerlm import Optimizer as _Optimizer, SimplerLMOptimizer as _SimplerLMOptimizer from pygsti import forwardsims as _fwdsims -from pygsti import layouts as _layouts _dummy_profiler = _DummyProfiler() @@ -392,6 +392,9 @@ def _lgst_matrix_dims(model, prep_fiducials, effect_fiducials): def _construct_ab(prep_fiducials, effect_fiducials, model, dataset, op_label_aliases=None): + if not prep_fiducials or not effect_fiducials: + _warnings.warn('Either prep_fiducials or effect_fiducials is empty. May return unexpected array.') + nRhoSpecs, nESpecs, povmLbls, povmLens = _lgst_matrix_dims( model, prep_fiducials, effect_fiducials) diff --git a/pygsti/algorithms/grammatrix.py b/pygsti/algorithms/grammatrix.py index a4dda90cf..6363554b6 100644 --- a/pygsti/algorithms/grammatrix.py +++ b/pygsti/algorithms/grammatrix.py @@ -100,7 +100,7 @@ def max_gram_rank_and_eigenvalues(dataset, target_model, max_basis_string_length if fixed_lists is not None: maxRhoStrs, maxEStrs = fixed_lists else: - maxRhoStrs = maxEStrs = max_gram_basis(dataset.gate_labels(), + maxRhoStrs = maxEStrs = max_gram_basis(target_model.primitive_op_labels, dataset, max_basis_string_length) return _gram_rank_and_evals(dataset, maxRhoStrs, maxEStrs, target_model)