Skip to content

Commit 82478d4

Browse files
authored
Avoid accessing non-existent state_space field of some Basis classes (e.g., TensorProdBasis) (sandialabs#842)
Fixes a bug in deserializing models that use a TensorProdBasis, and a similar bug in leakage/gaugeopt.py. Also cleans up whitespace and adds type annotations.
1 parent 24031da commit 82478d4

4 files changed

Lines changed: 42 additions & 7 deletions

File tree

pygsti/baseobjs/errorgenbasis.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ def _all_elements_same_type(lst):
6363
return False
6464
return True
6565

66+
6667
class ExplicitElementaryErrorgenBasis(ElementaryErrorgenBasis):
6768
"""
6869
This basis object contains the information necessary for building,
@@ -329,6 +330,7 @@ def difference(self, other_basis):
329330
difference_state_space = self.state_space
330331
return ExplicitElementaryErrorgenBasis(difference_state_space, sorted(difference_labels, key=lambda label: label.__str__()), self._basis_1q)
331332

333+
332334
class CompleteElementaryErrorgenBasis(ElementaryErrorgenBasis):
333335
"""
334336
This basis object contains the information necessary for building,
@@ -398,7 +400,6 @@ def _count_uptriangle_labels_for_support(cls, support, left_support, type_str, t
398400

399401
return cnt
400402

401-
402403
@classmethod
403404
def _create_ordered_labels(cls, type_str, basis_1q, state_space,
404405
max_weight=None, sslbl_overlap=None,
@@ -880,4 +881,4 @@ def difference(self, other_basis):
880881
other_basis : `ElementaryErrorgenBasis`
881882
`ElementaryErrorgenBasis` to construct the difference with.
882883
"""
883-
return self.to_explicit_basis().difference(other_basis)
884+
return self.to_explicit_basis().difference(other_basis)

pygsti/leakage/gaugeopt.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,15 +12,16 @@
1212
import copy
1313
from typing import Dict, List, Optional, TYPE_CHECKING, Union
1414

15-
from pygsti.baseobjs.basis import Basis
15+
from pygsti.baseobjs.basis import Basis, BuiltinBasis
1616

1717
if TYPE_CHECKING:
1818
from pygsti.models import ExplicitOpModel
1919
from pygsti.protocols.gst import GSTGaugeOptSuite, ModelEstimateResults
2020

2121

22-
def _direct_sum_unitary_group(subspace_bases, full_basis, triviality_flags=None,
23-
level_partition=None):
22+
def _direct_sum_unitary_group(subspace_bases: list[BuiltinBasis], full_basis: Basis,
23+
triviality_flags=None, level_partition=None
24+
):
2425
"""
2526
Build a gauge group that acts as an independent unitary on each summand of a
2627
direct-sum decomposition H = H₀ ⨁ H₁ ⨁ ... of Hilbert space.
@@ -186,7 +187,7 @@ def lagoified_gopparams_dicts(gopparams_dicts: List[Dict]) -> List[Dict]:
186187
# ^ We use subspace-restricted loss functions that only care about mismatches
187188
# between an estimate and a target when restricted to the computational subspace.
188189
#
189-
gg = UnitaryGaugeGroup(tm.basis.state_space, tm.basis)
190+
gg = UnitaryGaugeGroup(tm.state_space, tm.basis)
190191
inner_dict['gauge_group'] = gg
191192
inner_dict['_gaugeGroupEl'] = gg.compute_element(gg.initial_params)
192193
# ^ We insist on the unitary gauge group because other common gauge groups

pygsti/modelmembers/states/densestate.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,10 @@ def _from_memoized_dict(cls, mm_dict, serial_memo):
142142
if 'Basis object has unexpected dimension' in se and len(serial_memo) > 0:
143143
member = list(serial_memo.values())[0]
144144
basis = member.parent.basis
145-
state_space = basis.state_space
145+
# Use the parent model's state_space directly rather than basis.state_space:
146+
# composite bases (e.g. TensorProdBasis, DirectSumBasis) don't define state_space,
147+
# while every Model is guaranteed to have one.
148+
state_space = member.parent.state_space
146149
return cls(vec, basis, mm_dict['evotype'], state_space)
147150
raise e
148151

test/unit/algorithms/test_gaugeopt_correctness.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -424,6 +424,36 @@ def test_non_coordinate_subspace_rejected(self):
424424
_leakage_direct_sum_group(basis)
425425

426426

427+
class LagoifiedGopparamsDictsTester(BaseCase):
428+
"""
429+
Regression test for pygsti.leakage.gaugeopt.lagoified_gopparams_dicts.
430+
431+
It used to build its unitary gauge group from `tm.basis.state_space`, which raises
432+
AttributeError whenever `tm.basis` is a composite basis (e.g. TensorProdBasis) --
433+
the common case for any multi-subsystem model, leakage or not. It should instead use
434+
`tm.state_space`, which every Model has regardless of its basis's type.
435+
"""
436+
437+
def test_tensor_prod_basis_target_model_does_not_crash(self):
438+
from pygsti.baseobjs import ExplicitStateSpace
439+
from pygsti.baseobjs.basis import Basis, TensorProdBasis
440+
from pygsti.leakage.gaugeopt import lagoified_gopparams_dicts
441+
from pygsti.models.gaugegroup import DirectSumUnitaryGroup
442+
443+
# A qubit tensored with a leaky qutrit: `basis` is a TensorProdBasis, which (unlike
444+
# BuiltinBasis) has no `state_space` attribute of its own.
445+
basis = TensorProdBasis((Basis.cast('pp', 4), Basis.cast('l2p1', 9)))
446+
self.assertFalse(hasattr(basis, 'state_space'))
447+
state_space = ExplicitStateSpace(['Q0', 'Q1'], [2, 3])
448+
target_model = ExplicitOpModel(state_space, basis)
449+
450+
gopparams_dicts = lagoified_gopparams_dicts([{'target_model': target_model}])
451+
452+
self.assertEqual(len(gopparams_dicts), 2)
453+
self.assertIsInstance(gopparams_dicts[0]['gauge_group'], UnitaryGaugeGroup)
454+
self.assertIsInstance(gopparams_dicts[1]['gauge_group'], DirectSumUnitaryGroup)
455+
456+
427457
class FindPerfectGauge_DirectSumGaugeGroup4LevelTester(BaseCase):
428458
"""
429459
Gauge-recovery test for the generalized direct-sum gauge group on a 4-level

0 commit comments

Comments
 (0)