Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
8307cd5
Fix bug when converting unitary model to errorgen
Aug 16, 2025
5dc5a3f
Partial patch for gauge optimization with 'statevec' evotype
Aug 17, 2025
0bbc6d6
Turn off gauge optimization for term fwdsim tests
Aug 17, 2025
ac74ecd
Fix deserialization bug for older models
Aug 17, 2025
657eb49
Patch diamond distance reportable
Aug 17, 2025
aae43e1
Expire msgpack and json deprecations
Aug 17, 2025
513f469
JSON/msgpack expiration clean-up
Aug 17, 2025
d9677ae
Add casting logic for set_errorgen_coefficients
Aug 17, 2025
dc0506e
Merge branch 'develop' into bugfixes-914
Aug 17, 2025
bf4dc6f
Change default gauge optimization for run_long_sequence_gst
Aug 18, 2025
23993d8
Update gauge opt for run_long_sequence_gst_base
Aug 18, 2025
b213ec4
Errant print statements
Aug 18, 2025
bd8a49e
Fix error in set_errorgen_coefficients
Aug 18, 2025
db986f1
Report patches
Aug 18, 2025
e030a35
Reference regeneration
Aug 18, 2025
63e86c0
Update gauge optimization labels
Aug 18, 2025
fefdbfc
Missed a gauge optimization setting
Aug 18, 2025
35bc0ac
Revert license field
Aug 18, 2025
1949444
Revert debugging changes
Aug 18, 2025
7af7e89
Add conversion logic for np floats
Aug 18, 2025
cd90485
Add type annotation
Aug 18, 2025
d4eebb7
Numpy 2.3 deprecation
Aug 18, 2025
e89ca79
Update windows environment variable setting.
Aug 19, 2025
41dde39
Try again at updating cython builds
Aug 19, 2025
55b413f
Add debug logging to reuseable-main.yml
coreyostrove Aug 19, 2025
3ed19ea
Revert "Try again at updating cython builds"
Aug 19, 2025
a07ce69
Revert "Update windows environment variable setting."
Aug 19, 2025
680ba82
Try making the no cython tests linux only
Aug 19, 2025
5dddaec
Merge branch 'bugfixes-914' of https://github.com/sandialabs/pyGSTi i…
Aug 19, 2025
b4b0a40
Merge branch 'develop' into bugfixes-914
Aug 20, 2025
663f57d
More changes to have no cython only on linux
Aug 20, 2025
d5f7037
Resolve circular imports
Aug 20, 2025
1313b59
Fix mpi test
Aug 20, 2025
8dc6316
Lower max depth for instrument fisher info
Aug 20, 2025
3069a1e
Gram matrix bugfix
Aug 20, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .github/workflows/reuseable-main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -66,8 +67,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)
Expand All @@ -86,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: |
Expand Down
81 changes: 0 additions & 81 deletions jupyter_notebooks/Tutorials/other/FileIO.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
5 changes: 4 additions & 1 deletion pygsti/algorithms/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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()

Expand Down Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion pygsti/algorithms/grammatrix.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
2 changes: 1 addition & 1 deletion pygsti/drivers/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
49 changes: 38 additions & 11 deletions pygsti/drivers/longsequence.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 = 'stdgaugeopt'):
"""
Perform long-sequence GST (LSGST).

Expand Down Expand Up @@ -379,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
Expand Down Expand Up @@ -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 'stdgaugeopt')
An optional string specifying a named gauge optimization suite.

Returns
-------
Results
Expand All @@ -485,11 +489,20 @@ 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:
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)
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,
_get_gst_builders(advanced_options),
Expand Down Expand Up @@ -518,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.

Expand Down Expand Up @@ -609,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
Expand All @@ -623,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,
Expand Down
19 changes: 16 additions & 3 deletions pygsti/modelmembers/operations/composedop.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are we limiting this hack for state_space.dim <= 16?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Presumably just because it is expensive to perform the dense operations required with 3 or more qubits, but is faster than whatever other code path is used for 2Q and 3Q. This is incidentally not the biggest problem with this function at the moment, though. See #620 for more on that.

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

Expand Down Expand Up @@ -899,7 +899,20 @@ 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
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)
for k in Ltermdict_local:
Expand Down
7 changes: 6 additions & 1 deletion pygsti/modelmembers/operations/lindbladerrorgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -1161,6 +1164,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)):
Expand Down
4 changes: 2 additions & 2 deletions pygsti/modelmembers/povms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -531,15 +531,15 @@ 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)

#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)
Expand Down
7 changes: 3 additions & 4 deletions pygsti/modelmembers/states/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
Loading
Loading