Skip to content

Commit

Permalink
Merge branch 'develop' into feature-svb-qol-updates
Browse files Browse the repository at this point in the history
  • Loading branch information
sserita committed Sep 19, 2024
2 parents e504581 + 548703b commit 974119f
Show file tree
Hide file tree
Showing 23 changed files with 731 additions and 351 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ doc/build
*gst_checkpoints*
*model_test_checkpoints*
*standard_gst_checkpoints*
*ibmqexperiment_checkpoint*

# Serialization Testing Artifacts #
###################################
Expand All @@ -44,6 +45,7 @@ test/output/pylint/*
test/output/individual_coverage/*/*
test/test_packages/cmp_chk_files/Fake_Dataset_none.txt.cache
**.noseids
**test_ibmq**

# Tutorial Notebook Untracked Files #
####################################
Expand Down
49 changes: 30 additions & 19 deletions pygsti/algorithms/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@
from pygsti.baseobjs.resourceallocation import ResourceAllocation as _ResourceAllocation
from pygsti.optimize.customlm import CustomLMOptimizer as _CustomLMOptimizer
from pygsti.optimize.customlm import Optimizer as _Optimizer
from pygsti import forwardsims as _fwdsims
from pygsti import layouts as _layouts

_dummy_profiler = _DummyProfiler()

Expand Down Expand Up @@ -400,7 +402,7 @@ def _construct_ab(prep_fiducials, effect_fiducials, model, dataset, op_label_ali
for j, rhostr in enumerate(prep_fiducials):
opLabelString = rhostr + estr # LEXICOGRAPHICAL VS MATRIX ORDER
dsStr = opLabelString.replace_layers_with_aliases(op_label_aliases)
expd_circuit_outcomes = opLabelString.expand_instruments_and_separate_povm(model)
expd_circuit_outcomes = model.expand_instruments_and_separate_povm(opLabelString)
assert(len(expd_circuit_outcomes) == 1), "No instruments are allowed in LGST fiducials!"
unique_key = next(iter(expd_circuit_outcomes.keys()))
outcomes = expd_circuit_outcomes[unique_key]
Expand Down Expand Up @@ -429,7 +431,7 @@ def _construct_x_matrix(prep_fiducials, effect_fiducials, model, op_label_tuple,
for j, rhostr in enumerate(prep_fiducials):
opLabelString = rhostr + _circuits.Circuit(op_label_tuple, line_labels=rhostr.line_labels) + estr
dsStr = opLabelString.replace_layers_with_aliases(op_label_aliases)
expd_circuit_outcomes = opLabelString.expand_instruments_and_separate_povm(model)
expd_circuit_outcomes = model.expand_instruments_and_separate_povm(opLabelString)
dsRow_fractions = dataset[dsStr].fractions
assert(len(expd_circuit_outcomes) == nVariants)

Expand Down Expand Up @@ -677,16 +679,10 @@ def run_gst_fit(mdc_store, optimizer, objective_function_builder, verbosity=0):
if _np.linalg.norm(mdc_store.model.to_vector() - v_cmp) > 1e-6:
raise ValueError("MPI ERROR: *different* MC2GST start models"
" given to different processors!") # pragma: no cover

#MEM from ..baseobjs.profiler import Profiler
#MEM debug_prof = Profiler(comm)
#MEM debug_prof.print_memory("run_gst_fit1", True)


if objective_function_builder is not None:
objective_function_builder = _objfns.ObjectiveFunctionBuilder.cast(objective_function_builder)
#MEM debug_prof.print_memory("run_gst_fit2", True)
objective = objective_function_builder.build_from_store(mdc_store, printer) # (objective is *also* a store)
#MEM debug_prof.print_memory("run_gst_fit3", True)
else:
assert(isinstance(mdc_store, _objfns.ObjectiveFunction)), \
"When `objective_function_builder` is None, `mdc_store` must be an objective fn!"
Expand All @@ -705,14 +701,8 @@ def run_gst_fit(mdc_store, optimizer, objective_function_builder, verbosity=0):

printer.log("Completed in %.1fs" % (_time.time() - tStart), 1)

#if target_model is not None:
# target_vec = target_model.to_vector()
# targetErrVec = _objective_func(target_vec)
# return minErrVec, soln_gs, targetErrVec
profiler.add_time("do_mc2gst: total time", tStart)
#TODO: evTree.permute_computation_to_original(minErrVec) #Doesn't work b/c minErrVec is flattened
# but maybe best to just remove minErrVec from return value since this isn't very useful
# anyway?

return opt_result, objective


Expand Down Expand Up @@ -888,10 +878,30 @@ def _max_array_types(artypes_list): # get the maximum number of each array type
#The ModelDatasetCircuitsStore
printer.log('Precomputing CircuitOutcomeProbabilityArray layouts for each iteration.', 2)
precomp_layouts = []

#pre-compute a dictionary caching completed circuits for layout construction performance.
unique_circuits = list({ckt for circuit_list in circuit_lists for ckt in circuit_list})
if isinstance(mdl.sim, (_fwdsims.MatrixForwardSimulator, _fwdsims.MapForwardSimulator)):
precomp_layout_circuit_cache = mdl.sim.create_copa_layout_circuit_cache(unique_circuits, mdl, dataset=dataset)
else:
precomp_layout_circuit_cache = None

for i, circuit_list in enumerate(circuit_lists):
printer.log(f'Layout for iteration {i}', 2)
precomp_layouts.append(mdl.sim.create_layout(circuit_list, dataset, resource_alloc, array_types, verbosity= printer - 1))

precomp_layouts.append(mdl.sim.create_layout(circuit_list, dataset, resource_alloc, array_types, verbosity= printer - 1,
layout_creation_circuit_cache = precomp_layout_circuit_cache))

#precompute a cache of possible outcome counts for each circuits to accelerate MDC store creation
if isinstance(mdl, _models.model.OpModel):
if precomp_layout_circuit_cache is not None: #then grab the split circuits from there.
expanded_circuit_outcome_list = mdl.bulk_expand_instruments_and_separate_povm(unique_circuits,
completed_circuits= precomp_layout_circuit_cache['completed_circuits'].values())
else:
expanded_circuit_outcome_list = mdl.bulk_expand_instruments_and_separate_povm(unique_circuits)
outcome_count_by_circuit_cache = {ckt: len(outcome_tup) for ckt,outcome_tup in zip(unique_circuits, expanded_circuit_outcome_list)}
else:
outcome_count_by_circuit_cache = {ckt: mdl.compute_num_outcomes(ckt) for ckt in unique_circuits}

with printer.progress_logging(1):
for i in range(starting_index, len(circuit_lists)):
circuitsToEstimate = circuit_lists[i]
Expand All @@ -908,7 +918,8 @@ def _max_array_types(artypes_list): # get the maximum number of each array type
mdl.basis = start_model.basis # set basis in case of CPTP constraints (needed?)
initial_mdc_store = _objfns.ModelDatasetCircuitsStore(mdl, dataset, circuitsToEstimate, resource_alloc,
array_types=array_types, verbosity=printer - 1,
precomp_layout = precomp_layouts[i])
precomp_layout = precomp_layouts[i],
outcome_count_by_circuit=outcome_count_by_circuit_cache)
mdc_store = initial_mdc_store

for j, obj_fn_builder in enumerate(iteration_objfn_builders):
Expand Down
2 changes: 1 addition & 1 deletion pygsti/circuits/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
# http://www.apache.org/licenses/LICENSE-2.0 or in the LICENSE file in the root pyGSTi directory.
#***************************************************************************************************

from .circuit import Circuit
from .circuit import Circuit, SeparatePOVMCircuit
from .circuitlist import CircuitList
from .circuitstructure import CircuitPlaquette, FiducialPairPlaquette, \
GermFiducialPairPlaquette, PlaquetteGridCircuitStructure
Expand Down
149 changes: 33 additions & 116 deletions pygsti/circuits/circuit.py
Original file line number Diff line number Diff line change
Expand Up @@ -1095,13 +1095,12 @@ def _proc_lines_arg(self, lines):
def _proc_key_arg(self, key):
""" Pre-process the key argument used by many methods """
if isinstance(key, tuple):
if len(key) != 2: return IndexError("Index must be of the form <layerIndex>,<lineIndex>")
layers = key[0]
lines = key[1]
if len(key) != 2:
return IndexError("Index must be of the form <layerIndex>,<lineIndex>")
else:
return key[0], key[1]
else:
layers = key
lines = None
return layers, lines
return key, None

def _layer_components(self, ilayer):
""" Get the components of the `ilayer`-th layer as a list/tuple. """
Expand Down Expand Up @@ -1191,22 +1190,41 @@ def extract_labels(self, layers=None, lines=None, strict=True):
`layers` is a single integer and as a `Circuit` otherwise.
Note: if you want a `Circuit` when only selecting one layer,
set `layers` to a slice or tuple containing just a single index.
Note that the returned circuit doesn't retain any original
metadata, such as the compilable layer indices or occurence id.
"""
nonint_layers = not isinstance(layers, int)

#Shortcut for common case when lines == None and when we're only taking a layer slice/index
if lines is None:
assert(layers is not None)
if nonint_layers is False: return self.layertup[layers]
if isinstance(layers, slice) and strict is True: # if strict=False, then need to recompute line labels
return Circuit._fastinit(self._labels[layers], self._line_labels, not self._static)
if lines is None and layers is not None:
if self._static:
if not nonint_layers:
return self._labels[layers]
if isinstance(layers, slice) and strict is True: # if strict=False, then need to recompute line labels
#can speed this up a measurably by manually computing the new hashable tuple value and hash
if not self._line_labels in (('*',), ()):
new_hashable_tup = self._labels[layers] + ('@',) + self._line_labels
else:
new_hashable_tup = self._labels[layers]
ret = Circuit.__new__(Circuit)
return ret._copy_init(self._labels[layers], self._line_labels, not self._static, hashable_tup= new_hashable_tup, precomp_hash=hash(new_hashable_tup))
else:
if not nonint_layers:
return self.layertup[layers]
if isinstance(layers, slice) and strict is True: # if strict=False, then need to recompute line labels
return Circuit._fastinit(self._labels[layers], self._line_labels, not self._static)
#otherwise assert both are not None:


layers = self._proc_layers_arg(layers)
lines = self._proc_lines_arg(lines)
if len(layers) == 0 or len(lines) == 0:
return Circuit._fastinit(() if self._static else [],
tuple(lines) if self._static else lines,
not self._static) if nonint_layers else None # zero-area region
if self._static:
return Circuit._fastinit((), tuple(lines), False) # zero-area region
else:
return Circuit._fastinit(() if self._static else [],
tuple(lines) if self._static else lines,
not self._static) # zero-area region

ret = []
if self._static:
Expand Down Expand Up @@ -3447,7 +3465,7 @@ def num_gates(self):
"""
if self._static:
def cnt(lbl): # obj a Label, perhaps compound
if lbl.is_simple(): # a simple label
if lbl.IS_SIMPLE: # a simple label
return 1 if (lbl.sslbls is not None) else 0
else:
return sum([cnt(sublbl) for sublbl in lbl.components])
Expand Down Expand Up @@ -4428,107 +4446,6 @@ def done_editing(self):
self._hashable_tup = self.tup
self._hash = hash(self._hashable_tup)

def expand_instruments_and_separate_povm(self, model, observed_outcomes=None):
"""
Creates a dictionary of :class:`SeparatePOVMCircuit` objects from expanding the instruments of this circuit.
Each key of the returned dictionary replaces the instruments in this circuit with a selection
of their members. (The size of the resulting dictionary is the product of the sizes of
each instrument appearing in this circuit when `observed_outcomes is None`). Keys are stored
as :class:`SeparatePOVMCircuit` objects so it's easy to keep track of which POVM outcomes (effects)
correspond to observed data. This function is, for the most part, used internally to process
a circuit before computing its outcome probabilities.
Parameters
----------
model : Model
The model used to provide necessary details regarding the expansion, including:
- default SPAM layers
- definitions of instrument-containing layers
- expansions of individual instruments and POVMs
Returns
-------
OrderedDict
A dict whose keys are :class:`SeparatePOVMCircuit` objects and whose
values are tuples of the outcome labels corresponding to this circuit,
one per POVM effect held in the key.
"""
complete_circuit = model.complete_circuit(self)
expanded_circuit_outcomes = _collections.OrderedDict()
povm_lbl = complete_circuit[-1] # "complete" circuits always end with a POVM label
circuit_without_povm = complete_circuit[0:len(complete_circuit) - 1]

def create_tree(lst):
subs = _collections.OrderedDict()
for el in lst:
if len(el) > 0:
if el[0] not in subs: subs[el[0]] = []
subs[el[0]].append(el[1:])
return _collections.OrderedDict([(k, create_tree(sub_lst)) for k, sub_lst in subs.items()])

def add_expanded_circuit_outcomes(circuit, running_outcomes, ootree, start):
"""
"""
cir = circuit if start == 0 else circuit[start:] # for performance, avoid uneeded slicing
for k, layer_label in enumerate(cir, start=start):
components = layer_label.components
#instrument_inds = _np.nonzero([model._is_primitive_instrument_layer_lbl(component)
# for component in components])[0] # SLOWER than statement below
instrument_inds = _np.array([i for i, component in enumerate(components)
if model._is_primitive_instrument_layer_lbl(component)])
if instrument_inds.size > 0:
# This layer contains at least one instrument => recurse with instrument(s) replaced with
# all combinations of their members.
component_lookup = {i: comp for i, comp in enumerate(components)}
instrument_members = [model._member_labels_for_instrument(components[i])
for i in instrument_inds] # also components of outcome labels
for selected_instrmt_members in _itertools.product(*instrument_members):
expanded_layer_lbl = component_lookup.copy()
expanded_layer_lbl.update({i: components[i] + "_" + sel
for i, sel in zip(instrument_inds, selected_instrmt_members)})
expanded_layer_lbl = _Label([expanded_layer_lbl[i] for i in range(len(components))])

if ootree is not None:
new_ootree = ootree
for sel in selected_instrmt_members:
new_ootree = new_ootree.get(sel, {})
if len(new_ootree) == 0: continue # no observed outcomes along this outcome-tree path
else:
new_ootree = None

add_expanded_circuit_outcomes(circuit[0:k] + Circuit((expanded_layer_lbl,)) + circuit[k + 1:],
running_outcomes + selected_instrmt_members, new_ootree, k + 1)
break

else: # no more instruments to process: `cir` contains no instruments => add an expanded circuit
assert(circuit not in expanded_circuit_outcomes) # shouldn't be possible to generate duplicates...
elabels = model._effect_labels_for_povm(povm_lbl) if (observed_outcomes is None) \
else tuple(ootree.keys())
outcomes = tuple((running_outcomes + (elabel,) for elabel in elabels))
expanded_circuit_outcomes[SeparatePOVMCircuit(circuit, povm_lbl, elabels)] = outcomes

ootree = create_tree(observed_outcomes) if observed_outcomes is not None else None # tree of observed outcomes
# e.g. [('0','00'), ('0','01'), ('1','10')] ==> {'0': {'00': {}, '01': {}}, '1': {'10': {}}}

if model._has_instruments():
add_expanded_circuit_outcomes(circuit_without_povm, (), ootree, start=0)
else:
# It may be helpful to cache the set of elabels for a POVM (maybe within the model?) because
# currently the call to _effect_labels_for_povm may be a bottleneck. It's needed, even when we have
# observed outcomes, because there may be some observed outcomes that aren't modeled (e.g. leakage states)
if observed_outcomes is None:
elabels = model._effect_labels_for_povm(povm_lbl)
else:
possible_lbls = set(model._effect_labels_for_povm(povm_lbl))
elabels = tuple([oo for oo in ootree.keys() if oo in possible_lbls])
outcomes = tuple(((elabel,) for elabel in elabels))
expanded_circuit_outcomes[SeparatePOVMCircuit(circuit_without_povm, povm_lbl, elabels)] = outcomes

return expanded_circuit_outcomes


class CompressedCircuit(object):
"""
A "compressed" Circuit that requires less disk space.
Expand Down
Loading

0 comments on commit 974119f

Please sign in to comment.