Skip to content

FOGI model serialization and some FOGI model optimization #598

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 33 commits into
base: develop
Choose a base branch
from

Conversation

juangmendoza19
Copy link

This resolves issue #595 by allowing FOGI models to be serialized. Now, any fogi model can be saved to disk with the method .write(''), and loaded back with pygsti.models.Model.read('').

The main problem was the the class FirstOrderGaugeInvariantStore was not nicely serializable, which is stored in FOGI models. All appropriate serializations were added to this class, and other classes used within.

Additionally, @coreyostrove and I modified model.py:set_parameter_values() to be executed faster when used with FOGI models

@juangmendoza19 juangmendoza19 requested review from a team as code owners June 24, 2025 21:13
@@ -88,6 +89,7 @@ def __init__(self, state_space, labels, basis_1q=None):
comprise the basis element labels for the values of the
`ElementaryErrorgenLabels` in `labels`.
"""
super().__init__()
Copy link
Author

Choose a reason for hiding this comment

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

Every object that is nicely serializable needs to have this init statement

return state
@classmethod
def from_nice_serialization(cls, state):
return cls(_StateSpace.from_nice_serialization(state['state_space']), [_GlobalElementaryErrorgenLabel.cast(label) for label in state['labels']], state['_basis_1q'] if isinstance(state['_basis_1q'], str) else _Basis.from_nice_serialization(state['_basis_1q']))
Copy link
Author

Choose a reason for hiding this comment

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

Added support for _basis_1q variables which can be strings or Basis objects according to the docstring

@@ -418,7 +418,7 @@ def create_subspace(self, labels):
StateSpace
"""
# Default, generic, implementation constructs an explicit state space
labels = set(labels)
labels = sorted(set(labels))
Copy link
Author

Choose a reason for hiding this comment

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

This change was made with @coreyostrove to make FOGI model construction deterministic

self.gauge_space = gauge_space
self.elem_errorgen_labels_by_op = elem_errorgen_labels_by_op
self.op_errorgen_indices = op_errorgen_indices
self.fogi_directions = fogi_directions
Copy link
Author

Choose a reason for hiding this comment

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

The init method of this class used to not only create a FOGIstore object, but also compute all fogi directions. In order to be able to load them from disk, we needed a simple init method which just takes attributes and spits back the object without doing any math.

self.fogv_labels = ["%d gauge action" % i for i in range(self.fogv_directions.shape[1])]

@classmethod
def compute_fogis(cls, gauge_action_matrices_by_op, gauge_action_gauge_spaces_by_op, errorgen_coefficient_labels_by_op,
Copy link
Author

Choose a reason for hiding this comment

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

The old __init__method became compute_fogis(). It is decorated as a classmethod such that it can be used without the existence of a fogi_store object. As it intended to be used, at the end it creates an object which is returned back to the user.

@@ -657,56 +657,6 @@ def num_modeltest_params(self):
self._clean_paramvec()
return Model.num_modeltest_params.fget(self)

@property
Copy link
Author

Choose a reason for hiding this comment

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

It seems like a mistake that develop had all these methods duplicated

index_mm_map[gpidx].append(obj)
index_mm_label_map[gpidx].append(lbl)
self._index_mm_map = index_mm_map
self._index_mm_label_map = index_mm_label_map
Copy link
Author

Choose a reason for hiding this comment

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

the resulting index_mm_map was incorrect for FOGI models. This is necessary for our set_parameter_values optimization


indices = non_zero_errgens[0]
values = new_errgen_vec[indices]
vec_to_access = new_errgen_vec
Copy link
Author

Choose a reason for hiding this comment

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

This if statement captures which error generators are affected by the FOGI vector changing.

Copy link
Author

Choose a reason for hiding this comment

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

vec_to_access is a new variable which will always hold the vector of object parameters. In the case of FOGI models, it will always correspond to the updated error generator vector.

#and c) the effect is a child of that POVM.

if isinstance(self._layer_rules, _ExplicitLayerRules):
updated_children = []
Copy link
Author

Choose a reason for hiding this comment

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

unique_mms identifies which operations correspond to the error generators that are changing, which then allows us to only update those in our model inside the loop. This way we only update the objects in our model which are affected by the values that were modified by the user.

@@ -1304,52 +1253,71 @@ def set_parameter_values(self, indices, values, close=False):
-------
None
"""
orig_param_vec = self._paramvec.copy()
Copy link
Author

Choose a reason for hiding this comment

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

All of the code below assumes that parameter interposers are only used for FOGI models, and that FOGI models are built from GLND parameterizations (i.e. every FOGI quantity is defined in terms of error generators)

@@ -2553,8 +2521,6 @@ def setup_fogi(self, initial_gauge_basis, create_complete_basis_fn=None,
dependent_fogi_action='drop', include_spam=True, primitive_op_labels=None):

from pygsti.baseobjs.errorgenbasis import CompleteElementaryErrorgenBasis as _CompleteElementaryErrorgenBasis
from pygsti.baseobjs.errorgenbasis import ExplicitElementaryErrorgenBasis as _ExplicitElementaryErrorgenBasis
from pygsti.baseobjs.errorgenspace import ErrorgenSpace as _ErrorgenSpace
Copy link
Author

Choose a reason for hiding this comment

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

These two imports were never used

@coreyostrove coreyostrove added this to the 0.9.14 milestone Jun 25, 2025
Copy link
Contributor

@coreyostrove coreyostrove left a comment

Choose a reason for hiding this comment

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

Mostly looks good. A few requests inline. Also a general request to identify whether or not we have unit test coverage for the new functionality that is being added for both the serialization cases and the updated code path for set_parameter_values when using a FOGI model/model with interposer.

Copy link
Contributor

@coreyostrove coreyostrove left a comment

Choose a reason for hiding this comment

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

Only a few very minor requests for a) a couple one-sentence docstrings (see comments in code) and b) for another merge of develop into your branch to sync up changes from PRs merged in earlier today (there is at least one conflict/unintentional reversion that would be introduced otherwise). After that I think this is good to go.

@@ -762,8 +802,10 @@ def label_index(self, label, ok_if_missing=False, identity_label='I'):
elif eetype in ('C', 'A'):
assert(len(trivial_bel) == 1) # assumes this is a single character
nontrivial_inds = [i for i, letter in enumerate(bels[0]) if letter != trivial_bel]
left_support = tuple([self.sslbls[i] for i in nontrivial_inds])

left_support = tuple([label.sslbls[i] for i in nontrivial_inds])
Copy link
Contributor

Choose a reason for hiding this comment

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

Pinging @enielse regarding this change. The intention of this was to address an bug related to an edge case. I'll let @juangmendoza19 explain more.

Copy link
Author

Choose a reason for hiding this comment

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

It seems like label_index() is supposed to identify different representations of the same label. For example, this can be seen in test_errorgenbasis.py:test_label_index(), where ('C', ['XI', 'YI']) is supposed to be identified as ('C', ('X', 'Y'), (0,)).

Conversely, the label ('C', ('X', 'Y'), (1,)) should be identified as equivalent to ('C', ['IX', 'IY']). This is not the case. The current label_index() code in develop returns None for this example. This problem prevented 2 qubit FOGI models from being constructed.

I was uncertain of what exactly the code within the function is doing, but by following how the variables transformed in each line, I concluded that maybe the line changed above was being indexed into the wrong array. The fix I provided passes test_label_index() and also seems to correctly identify when the trivial subspace is not in the first register.

We would like your input on this problem and solution @enielse

@@ -730,3 +746,55 @@ def tearDown(self):
# def test_randomize_with_unitary_raises(self):
# with self.assertRaises(AssertionError):
# self.model.randomize_with_unitary(1, rand_state=np.random.RandomState()) # scale shouldn't matter

Copy link
Author

Choose a reason for hiding this comment

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

All of the code below is not original to this PR, it was moved from test_nice_serialization.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants