diff --git a/pygsti/baseobjs/label.py b/pygsti/baseobjs/label.py index db1d92745..254b9b03c 100644 --- a/pygsti/baseobjs/label.py +++ b/pygsti/baseobjs/label.py @@ -13,6 +13,7 @@ import itertools as _itertools import numbers as _numbers import sys as _sys +import copy as _copy import numpy as _np @@ -138,7 +139,8 @@ def __new__(cls, name, state_space_labels=None, time=None, args=None): return LabelStr.init(name, time) else: - if args is not None: return LabelTupWithArgs.init(name, state_space_labels, time, args) + if args is not None: + return LabelTupWithArgs.init(name, state_space_labels, time, args) else: if time == 0.0: return LabelTup.init(name, state_space_labels) @@ -200,6 +202,8 @@ def is_simple(self): return self.IS_SIMPLE + def copy(self): + return _copy.deepcopy(self) class LabelTup(Label, tuple): diff --git a/pygsti/circuits/circuit.py b/pygsti/circuits/circuit.py index 8cbeef237..0fafc4c03 100644 --- a/pygsti/circuits/circuit.py +++ b/pygsti/circuits/circuit.py @@ -9,22 +9,26 @@ # 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 collections as _collections import itertools as _itertools import warnings as _warnings +from typing import List, Sequence, Literal, Tuple, Any, Hashable, Union, Optional, TYPE_CHECKING +if TYPE_CHECKING: + import stim + from cirq.circuits.circuit import Circuit as CirqCircuit import numpy as _np -from pygsti.baseobjs.label import Label as _Label, CircuitLabel as _CircuitLabel +from pygsti.baseobjs.label import Label as _Label, CircuitLabel as _CircuitLabel, LabelTupTup as _LabelTupTup + from pygsti.baseobjs import outcomelabeldict as _ld, _compatibility as _compat from pygsti.tools import internalgates as _itgs from pygsti.tools import slicetools as _slct from pygsti.tools.legacytools import deprecate as _deprecate_fn -from typing import Union, Optional, TYPE_CHECKING -if TYPE_CHECKING: - import stim + #Externally, we'd like to do thinks like: # c = Circuit( LabelList ) @@ -47,6 +51,19 @@ +' unitary from standard_gatename_unitaries which matches up to a global phase.' _warnings.filterwarnings('module', message=msg, category=UserWarning) + +############################################################################################## +# NOTE(Riley): these types are work-in-progress. They don't make a whole lot of sense to me +# right now. It might be possible that they just _DONT_ make sense, and yet they're correct +# in the context of the current implementation. +_NestedLabelSeq = List[Union[_Label, Sequence[_Label]]] +# ^ An alias to make it easier to see how subsequent types relate. +# Don't use this in function signatures. +LayerTupLike = Union[Tuple[_LabelTupTup, ...], _NestedLabelSeq, Tuple[_Label, ...]] +LabelsLike = Union[Tuple[_NestedLabelSeq, ...], _NestedLabelSeq] +############################################################################################## + + def _np_to_quil_def_str(name, input_array): """ Write a DEFGATE block for RQC quil for an arbitrary one- or two-qubit unitary gate. @@ -271,9 +288,11 @@ def from_tuple(cls, tup): else: return cls(tup) - def __init__(self, layer_labels=(), line_labels='auto', num_lines=None, editable=False, - stringrep=None, name='', check=True, expand_subcircuits="default", - occurrence=None, compilable_layer_indices=None): + def __init__(self, + layer_labels=(), line_labels: Union[str,Tuple[Any,...]] = 'auto', num_lines: Optional[int] = None, + editable=False, stringrep=None, name='', check=True, expand_subcircuits="default", occurrence=None, + compilable_layer_indices=None + ): """ Creates a new Circuit object, encapsulating a quantum circuit. @@ -517,7 +536,7 @@ def _fastinit(cls, labels, line_labels, editable, name='', stringrep=None, occur #Note: If editing _bare_init one should also check _copy_init in case changes must be propagated. def _bare_init(self, labels, line_labels, editable, name='', stringrep=None, occurrence=None, compilable_layer_indices_tup=()): - self._labels = labels + self._labels : LabelsLike = labels self._line_labels = tuple(line_labels) self._occurrence_id = occurrence self._compilable_layer_indices_tup = compilable_layer_indices_tup # always a tuple, but can be empty. @@ -647,7 +666,7 @@ def occurrence(self, value): self._str = None # regenerate string rep (it may have updated) @property - def layertup(self): + def layertup(self) -> LayerTupLike: """ This Circuit's layers as a standard Python tuple of layer Labels. @@ -1024,7 +1043,7 @@ def num_lines(self): """ return len(self._line_labels) - def copy(self, editable='auto'): + def copy(self, editable: Union[bool, Literal['auto']] = 'auto') -> Circuit: """ Returns a copy of the circuit. @@ -1223,7 +1242,7 @@ def extract_labels(self, layers=None, lines=None, strict=True): 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 (('*',), ()): + if self._line_labels not in (('*',), ()): new_hashable_tup = self._labels[layers] + ('@',) + self._line_labels else: new_hashable_tup = self._labels[layers] @@ -1387,7 +1406,7 @@ def set_labels(self, lbls, layers=None, lines=None): else: # single layer using integer layer index (so lbls is a single Label) self._labels[layers[0]].extend(_label_to_nested_lists_of_simple_labels(lbls, def_sslbls)) - def insert_idling_layers(self, insert_before, num_to_insert, lines=None): + def insert_idling_layers(self, insert_before: int, num_to_insert: int, lines=None) -> Circuit: """ Inserts into this circuit one or more idling (blank) layers, returning a copy. @@ -1450,8 +1469,10 @@ def insert_idling_layers_inplace(self, insert_before, num_to_insert, lines=None) None """ assert(not self._static), "Cannot edit a read-only circuit!" - if insert_before is None: insert_before = len(self._labels) - elif insert_before < 0: insert_before = len(self._labels) + insert_before + if insert_before is None: + insert_before = len(self._labels) + elif insert_before < 0: + insert_before = len(self._labels) + insert_before if lines is None: # insert complete layers for i in range(num_to_insert): @@ -1459,6 +1480,9 @@ def insert_idling_layers_inplace(self, insert_before, num_to_insert, lines=None) #Shift compilable layer indices as needed if self._compilable_layer_indices_tup: + if num_to_insert <= 0 and insert_before < len(self._labels): + raise ValueError('Undefined behavior (at least until the ' \ + 'documentation is updated).') shifted_inds = [i if (i < insert_before) else (i + num_to_insert) for i in self._compilable_layer_indices_tup] self._compilable_layer_indices_tup = tuple(shifted_inds) @@ -2235,7 +2259,7 @@ def insert_layer_inplace(self, circuit_layer, j): self.insert_labels_into_layers_inplace([circuit_layer], j) - def insert_circuit(self, circuit, j): + def insert_circuit(self, circuit: Circuit, j: int) -> Circuit: """ Inserts a circuit into this circuit, returning a copy. @@ -2263,7 +2287,7 @@ def insert_circuit(self, circuit, j): if self._static: cpy.done_editing() return cpy - def insert_circuit_inplace(self, circuit, j): + def insert_circuit_inplace(self, circuit: Circuit, j: int): """ Inserts a circuit into this circuit. @@ -2298,7 +2322,7 @@ def insert_circuit_inplace(self, circuit, j): labels_to_insert = circuit.extract_labels(layers=None, lines=lines_to_insert) self.insert_labels_into_layers_inplace(labels_to_insert, j) - def append_circuit(self, circuit): + def append_circuit(self, circuit: Circuit) -> Circuit: """ Append a circuit to the end of this circuit, returning a copy. @@ -2316,7 +2340,7 @@ def append_circuit(self, circuit): """ return self.insert_circuit(circuit, self.num_layers) - def append_circuit_inplace(self, circuit): + def append_circuit_inplace(self, circuit: Circuit): """ Append a circuit to the end of this circuit. @@ -2335,7 +2359,7 @@ def append_circuit_inplace(self, circuit): assert(not self._static), "Cannot edit a read-only circuit!" self.insert_circuit_inplace(circuit, self.num_layers) - def prefix_circuit(self, circuit): + def prefix_circuit(self, circuit: Circuit) -> Circuit: """ Prefix a circuit to the beginning of this circuit, returning a copy. @@ -2353,7 +2377,7 @@ def prefix_circuit(self, circuit): """ return self.insert_circuit(circuit, 0) - def prefix_circuit_inplace(self, circuit): + def prefix_circuit_inplace(self, circuit: Circuit): """ Prefix a circuit to the beginning of this circuit. @@ -2372,7 +2396,7 @@ def prefix_circuit_inplace(self, circuit): assert(not self._static), "Cannot edit a read-only circuit!" self.insert_circuit_inplace(circuit, 0) - def tensor_circuit_inplace(self, circuit, line_order=None): + def tensor_circuit_inplace(self, circuit: Circuit, line_order=None): """ The tensor product of this circuit and `circuit`. @@ -2426,7 +2450,7 @@ def tensor_circuit_inplace(self, circuit, line_order=None): self.insert_labels_as_lines_inplace(circuit._labels, line_labels=circuit.line_labels) self._line_labels = new_line_labels # essentially just reorders labels if needed - def tensor_circuit(self, circuit, line_order=None): + def tensor_circuit(self, circuit: Circuit, line_order=None) -> Circuit: """ The tensor product of this circuit and `circuit`, returning a copy. @@ -2454,7 +2478,7 @@ def tensor_circuit(self, circuit, line_order=None): if self._static: cpy.done_editing() return cpy - def replace_layer_with_circuit_inplace(self, circuit, j): + def replace_layer_with_circuit_inplace(self, circuit: Circuit, j): """ Replaces the `j`-th layer of this circuit with `circuit`. @@ -2474,7 +2498,7 @@ def replace_layer_with_circuit_inplace(self, circuit, j): del self[j] self.insert_labels_into_layers_inplace(circuit, j) - def replace_layer_with_circuit(self, circuit, j): + def replace_layer_with_circuit(self, circuit: Circuit, j) -> Circuit: """ Replaces the `j`-th layer of this circuit with `circuit`, returning a copy. @@ -2523,14 +2547,23 @@ def replace(obj): # obj is either a simple label or a list if isinstance(obj, _Label): if obj.name == old_gatename: newobj = _Label(new_gatename, obj.sslbls) - else: newobj = obj + else: + newobj = obj + elif obj == old_gatename: + if len(obj) == 0: + sslbls = self.line_labels + else: + import warnings + warnings.warn(f'Cannot infer target of gate(s) of {obj}.') + sslbls = tuple() + newobj = _Label((new_gatename,) + sslbls) else: newobj = [replace(sub) for sub in obj] return newobj self._labels = replace(self._labels) - def replace_gatename(self, old_gatename, new_gatename): + def replace_gatename(self, old_gatename, new_gatename) -> Circuit: """ Returns a copy of this Circuit except that `old_gatename` is changed to `new_gatename`. @@ -2591,7 +2624,7 @@ def replace(obj): # obj is either a simple label or a list self._labels = replace(self._labels) - def replace_gatename_with_idle(self, gatename): + def replace_gatename_with_idle(self, gatename) -> Circuit: """ Returns a copy of this Circuit with a given gatename treated as an idle gate. @@ -2613,7 +2646,7 @@ def replace_gatename_with_idle(self, gatename): if self._static: cpy.done_editing() return cpy - def replace_layer(self, old_layer, new_layer): + def replace_layer(self, old_layer, new_layer) -> Circuit: """ Returns a copy of this Circuit except that `old_layer` is changed to `new_layer`. @@ -2644,7 +2677,7 @@ def replace_layer(self, old_layer, new_layer): occurrence=self._occurrence_id, compilable_layer_indices=self._compilable_layer_indices_tup) - def replace_layers_with_aliases(self, alias_dict): + def replace_layers_with_aliases(self, alias_dict) -> Circuit: """ Performs a find and replace using layer aliases. @@ -2844,7 +2877,7 @@ def map_sslbls(obj): # obj is either a simple label or a list return newobj self._labels = map_sslbls(self._labels) - def map_state_space_labels(self, mapper): + def map_state_space_labels(self, mapper) -> Circuit: """ Creates a new Circuit whose line labels are updated according to the mapping function `mapper`. @@ -2885,7 +2918,7 @@ def reorder_lines_inplace(self, order): assert(set(order) == set(self._line_labels)), "The line labels must be the same!" self._line_labels = tuple(order) - def reorder_lines(self, order): + def reorder_lines(self, order: Sequence[_Label]) -> Circuit: """ Reorders the lines (wires/qubits) of the circuit, returning a copy. @@ -3002,7 +3035,7 @@ def delete_idling_lines_inplace(self, idle_layer_labels=None): self._line_labels = tuple([x for x in self._line_labels if x in all_sslbls]) # preserve order - def delete_idling_lines(self, idle_layer_labels=None): + def delete_idling_lines(self, idle_layer_labels : Optional[Sequence[_Label]]=None) -> Circuit: """ Removes from this circuit all lines that are idling at every layer, returning a copy. @@ -3320,7 +3353,7 @@ def layer_label(self, j): ), "Circuit layer label invalid! Circuit is only of depth {}".format(self.num_layers) return self[j] - def layer_with_idles(self, j, idle_gate_name='I'): + def layer_with_idles(self, j, idle_gate_name: Union[str, _Label]='I'): """ Returns a tuple of the components of the layer at depth `j`, with `idle_gate_name` at empty circuit locations. @@ -3342,7 +3375,7 @@ def layer_with_idles(self, j, idle_gate_name='I'): """ return tuple(self.layer_label_with_idles(j, idle_gate_name).components) - def layer_label_with_idles(self, j, idle_gate_name='I'): + def layer_label_with_idles(self, j, idle_gate_name: Union[str, _Label]='I'): """ Returns the layer, as a :class:`Label`, at depth j, with `idle_gate_name` at empty circuit locations. @@ -3765,7 +3798,6 @@ def _write_q_circuit_tex(self, filename): # TODO f.write("\\end{document}") f.close() - def convert_to_stim_tableau_layers(self, gate_name_conversions: Optional[dict[str, stim.Tableau]] = None, num_qubits: Optional[int] = None, qubit_label_conversions: Optional[dict[Union[str, int], int]] = None) -> list[stim.Tableau]: @@ -3937,7 +3969,6 @@ def convert_to_stim_tableau(self, gate_name_conversions: Optional[dict[str, stim tableau= layer*tableau return tableau - def convert_to_cirq(self, qubit_conversion, wait_duration=None, @@ -3999,9 +4030,9 @@ def convert_to_cirq(self, return cirq.Circuit(moments) - @classmethod - def from_cirq(cls, circuit, qubit_conversion=None, cirq_gate_conversion= None, - remove_implied_idles = True, global_idle_replacement_label = 'auto'): + @staticmethod + def from_cirq(circuit: CirqCircuit, qubit_conversion=None, cirq_gate_conversion=None, + remove_implied_idles=True, global_idle_replacement_label='auto') -> Circuit: """ Converts and instantiates a pyGSTi Circuit object from a Cirq Circuit object. @@ -4179,9 +4210,9 @@ def from_cirq(cls, circuit, qubit_conversion=None, cirq_gate_conversion= None, #labels to include all of the qubits appearing in the cirq circuit, otherwise #we'll let the Circuit constructor figure this out. if seen_global_idle: - return cls(circuit_layers, line_labels = tuple(sorted([qubit_conversion[qubit] for qubit in all_cirq_qubits]))) + return Circuit(circuit_layers, line_labels = tuple(sorted([qubit_conversion[qubit] for qubit in all_cirq_qubits]))) else: - return cls(circuit_layers) + return Circuit(circuit_layers) def convert_to_quil(self, num_qubits=None, @@ -4633,10 +4664,13 @@ def done_editing(self): """ if not self._static: self._static = True - self._labels = tuple([layer_lbl if isinstance(layer_lbl, _Label) - else _Label(layer_lbl) for layer_lbl in self._labels]) + t = [LL if isinstance(LL, _Label) else _Label(LL) for LL in self._labels] + self._labels = tuple(t) # type: ignore self._hashable_tup = self.tup self._hash = hash(self._hashable_tup) + self._str = None + self._str = self.str # this accessor recomputes the value of self._str + class CompressedCircuit(object): """ @@ -4667,7 +4701,7 @@ class CompressedCircuit(object): takes more time but could result in better compressing. """ - def __init__(self, circuit, min_len_to_compress=20, max_period_to_look_for=20): + def __init__(self, circuit: Circuit, min_len_to_compress=20, max_period_to_look_for=20): """ Create a new CompressedCircuit object diff --git a/pygsti/forwardsims/forwardsim.py b/pygsti/forwardsims/forwardsim.py index 85d43899e..943a874ed 100644 --- a/pygsti/forwardsims/forwardsim.py +++ b/pygsti/forwardsims/forwardsim.py @@ -21,7 +21,10 @@ from pygsti.baseobjs.resourceallocation import ResourceAllocation as _ResourceAllocation from pygsti.baseobjs.nicelyserializable import NicelySerializable as _NicelySerializable from pygsti.tools import slicetools as _slct -from typing import Union, Callable, Literal +from typing import Union, Callable, Literal, Optional, TYPE_CHECKING + +if TYPE_CHECKING: + from pygsti.models.model import OpModel class ForwardSimulator(_NicelySerializable): @@ -96,7 +99,7 @@ def _array_types_for_method(cls, method_name): return ('ep', 'ep') + cls._array_types_for_method('_bulk_fill_dprobs_block') return () - def __init__(self, model=None): + def __init__(self, model: Optional[OpModel] = None): super().__init__() #self.dim = model.dim self.model = model @@ -128,11 +131,11 @@ def __getstate__(self): return state_dict @property - def model(self): + def model(self) -> Union[OpModel, None]: return self._model @model.setter - def model(self, val): + def model(self, val: OpModel): self._model = val try: evotype = None if val is None else self._model.evotype diff --git a/pygsti/layouts/evaltree.py b/pygsti/layouts/evaltree.py index d8aa24f85..6d6f6a983 100644 --- a/pygsti/layouts/evaltree.py +++ b/pygsti/layouts/evaltree.py @@ -340,7 +340,7 @@ def _get_start_indices(max_intersect): (_time.time() - tm)); tm = _time.time() #merge_method = "fast" - #Another possible algorith (but slower) + #Another possible algorithm (but slower) #if merge_method == "best": # while len(indicesLeft) > 0: # iToMergeInto,_ = min(enumerate(map(len,subTreeSetList)), diff --git a/pygsti/layouts/prefixtable.py b/pygsti/layouts/prefixtable.py index 8049c4b15..30fe7cb65 100644 --- a/pygsti/layouts/prefixtable.py +++ b/pygsti/layouts/prefixtable.py @@ -15,6 +15,7 @@ from math import ceil from pygsti.baseobjs import Label as _Label from pygsti.circuits.circuit import SeparatePOVMCircuit as _SeparatePOVMCircuit +from pygsti.tools.tqdm import our_tqdm class PrefixTable(object): @@ -689,7 +690,7 @@ def _cache_hits(circuit_reps, circuit_lengths): cacheIndices = [] # indices into circuits_to_evaluate of the results to cache cache_hits = [0]*len(circuit_reps) - for i in range(len(circuit_reps)): + for i in our_tqdm(range(len(circuit_reps)), 'Prefix table : _cache_hits '): circuit = circuit_reps[i] L = circuit_lengths[i] # can be a Circuit or a label tuple for cached_index in reversed(cacheIndices): @@ -707,10 +708,11 @@ def _build_table(sorted_circuits_to_evaluate, cache_hits, max_cache_size, circui # Build prefix table: construct list, only caching items with hits > 0 (up to max_cache_size) cacheIndices = [] # indices into circuits_to_evaluate of the results to cache - table_contents = [None]*len(sorted_circuits_to_evaluate) + num_sorted_circuits = len(sorted_circuits_to_evaluate) + table_contents = [None]*num_sorted_circuits curCacheSize = 0 - for j, (i, _) in zip(orig_indices,enumerate(sorted_circuits_to_evaluate)): - + for i in our_tqdm(range(num_sorted_circuits), 'Prefix table : _build_table '): + j = orig_indices[i] circuit_rep = circuit_reps[i] L = circuit_lengths[i] diff --git a/pygsti/modelmembers/modelmember.py b/pygsti/modelmembers/modelmember.py index 11a4f25e9..c8461758b 100644 --- a/pygsti/modelmembers/modelmember.py +++ b/pygsti/modelmembers/modelmember.py @@ -314,7 +314,7 @@ def relink_parent(self, parent): assert(self._parent is None), "Cannot relink parent: parent is not None!" self._parent = parent # assume no dependent objects - def unlink_parent(self, force=False): + def unlink_parent(self, force: bool=False): """ Remove the parent-link of this member. @@ -330,12 +330,14 @@ def unlink_parent(self, force=False): parent still contains references to it. If `False`, the parent is only set to `None` when its parent contains no reference to it. + Passed through to unlink_parent calls for submembers of this model member. + Returns ------- None """ for subm in self.submembers(): - subm.unlink_parent() + subm.unlink_parent(force) if (self.parent is not None) and (force or self.parent._obj_refcount(self) == 0): self._parent = None diff --git a/pygsti/modelmembers/operations/opfactory.py b/pygsti/modelmembers/operations/opfactory.py index cbaed54c3..cdeabadb2 100644 --- a/pygsti/modelmembers/operations/opfactory.py +++ b/pygsti/modelmembers/operations/opfactory.py @@ -9,6 +9,8 @@ # 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 numpy as _np from pygsti.modelmembers.operations.staticunitaryop import StaticUnitaryOp as _StaticUnitaryOp @@ -24,9 +26,10 @@ from pygsti.baseobjs import basis as _basis from pygsti.evotypes import Evotype as _Evotype from pygsti.tools import optools as _ot +from pygsti.modelmembers.operations.linearop import LinearOperator as _LinearOperator -def op_from_factories(factory_dict, lbl): +def op_from_factories(factory_dict: dict[_Lbl, OpFactory], lbl: _Lbl) -> _LinearOperator: """ Create an operator for `lbl` from the factories in `factory_dict`. diff --git a/pygsti/models/model.py b/pygsti/models/model.py index 5f6f8e940..49d4c3249 100644 --- a/pygsti/models/model.py +++ b/pygsti/models/model.py @@ -1806,17 +1806,18 @@ def complete_circuits(self, circuits, prep_lbl_to_prepend=None, povm_lbl_to_appe already has a prep label this argument will be ignored. povm_lbl_to_append : Label, optional (default None) - Optional user specified prep label to prepend. If not + Optional user specified povm label to prepend. If not specified will use the default value as given by :meth:_default_primitive_prep_layer_lbl. If the circuit - already has a prep label this argument will be ignored. - + already has a povm label this argument will be ignored. + return_split : bool, optional (default False) If True we additionally return a list of tuples of the form: (prep_label, no_spam_circuit, povm_label) for each circuit. This is of the same format returned by :meth:split_circuits when using the kwarg combination: erroron=('prep', 'povm'), split_prep=True, split_povm=True + Returns ------- Circuit @@ -1836,7 +1837,7 @@ def complete_circuits(self, circuits, prep_lbl_to_prepend=None, povm_lbl_to_appe #precompute unique default povm labels. unique_sslbls = set([ckt._line_labels for ckt in circuits]) - default_povm_labels = {sslbls:(self._default_primitive_povm_layer_lbl(sslbls),) for sslbls in unique_sslbls} + default_povm_labels = {sslbls: (self._default_primitive_povm_layer_lbl(sslbls),) for sslbls in unique_sslbls} comp_circuits = [] if return_split: diff --git a/pygsti/optimize/customsolve.py b/pygsti/optimize/customsolve.py index f03423400..69cf86034 100644 --- a/pygsti/optimize/customsolve.py +++ b/pygsti/optimize/customsolve.py @@ -23,6 +23,9 @@ _fastcalc = None +CUSTOM_SOLVE_THRESHOLD = 10_000 + + def custom_solve(a, b, x, ari, resource_alloc, proc_threshold=100): """ Simple parallel Gaussian Elimination with pivoting. @@ -95,7 +98,7 @@ def custom_solve(a, b, x, ari, resource_alloc, proc_threshold=100): return #Just gather everything to one processor and compute there: - if comm.size < proc_threshold and a.shape[1] < 10000: + if comm.size < proc_threshold and a.shape[1] < CUSTOM_SOLVE_THRESHOLD: # We're not exactly sure where scipy is better, but until we speed up / change gaussian-elim # alg the scipy alg is much faster for small numbers of procs and so should be used unless # A is too large to be gathered to the root proc. @@ -163,9 +166,12 @@ def custom_solve(a, b, x, ari, resource_alloc, proc_threshold=100): # Step 1: find the index of the row that is the best pivot. # each proc looks for its best pivot (Note: it should not consider rows already pivoted on) potential_pivot_indices = all_row_indices[potential_pivot_mask] - ibest_global, ibest_local, h, k = _find_pivot(a, b, icol, potential_pivot_indices, my_row_slice, - shared_floats, shared_ints, resource_alloc, comm, host_comm, - smbuf1, smbuf2, smbuf3, host_index_buf, host_val_buf) + ibest_global, ibest_local, h, k = _find_pivot( + a, icol, potential_pivot_indices, + my_row_slice, shared_floats, shared_ints, resource_alloc, + comm, host_comm, smbuf1, smbuf1b, smbuf2, + smbuf3, host_index_buf, host_val_buf + ) # Step 2: proc that owns best row (holds that row and is root of param-fine comm) broadcasts it pivot_row, pivot_b = _broadcast_pivot_row(a, b, ibest_local, h, k, shared_rowb, local_pivot_rowb, @@ -210,8 +216,12 @@ def custom_solve(a, b, x, ari, resource_alloc, proc_threshold=100): return -def _find_pivot(a, b, icol, potential_pivot_inds, my_row_slice, shared_floats, shared_ints, - resource_alloc, comm, host_comm, buf1, buf1b, buf2, buf3, best_host_indices, best_host_vals): +def _find_pivot( + a, icol, potential_pivot_inds, + my_row_slice, shared_floats, shared_ints, resource_alloc, + comm, host_comm, buf1, buf1b, + buf2, buf3, best_host_indices, best_host_vals + ): #print(f'Length potential_pivot_inds {len(potential_pivot_inds)}') #print(f'potential_pivot_inds: {potential_pivot_inds}') diff --git a/pygsti/optimize/optimize.py b/pygsti/optimize/optimize.py index a0bed3ef5..186473482 100644 --- a/pygsti/optimize/optimize.py +++ b/pygsti/optimize/optimize.py @@ -17,11 +17,7 @@ import numpy as _np import scipy.optimize as _spo -try: - from scipy.optimize import Result as _optResult # for earlier scipy versions -except: - from scipy.optimize import OptimizeResult as _optResult # for later scipy versions - +from scipy.optimize import OptimizeResult as _optResult from pygsti.optimize.customcg import fmax_cg from pygsti.baseobjs.verbosityprinter import VerbosityPrinter as _VerbosityPrinter diff --git a/pygsti/tools/tqdm.py b/pygsti/tools/tqdm.py new file mode 100644 index 000000000..f3d66fe8d --- /dev/null +++ b/pygsti/tools/tqdm.py @@ -0,0 +1,7 @@ +try: + from tqdm import tqdm + def our_tqdm(iterator, message): + return tqdm(iterator, message) +except ImportError: + def our_tqdm(iterator, ignore): + return iterator diff --git a/test/unit/extras/ibmq/test_ibmqexperiment.py b/test/unit/extras/ibmq/test_ibmqexperiment.py index 4e4c25ed4..15b310c19 100644 --- a/test/unit/extras/ibmq/test_ibmqexperiment.py +++ b/test/unit/extras/ibmq/test_ibmqexperiment.py @@ -2,16 +2,23 @@ from pygsti.extras.devices.experimentaldevice import ExperimentalDevice from pygsti.extras import ibmq from pygsti.processors import CliffordCompilationRules as CCR -from qiskit.providers.fake_provider import GenericBackendV2 -from qiskit_ibm_runtime import QiskitRuntimeService + +try: + from qiskit.providers.fake_provider import GenericBackendV2 + from qiskit_ibm_runtime import QiskitRuntimeService + HAVE_QISKIT = True +except ImportError: + HAVE_QISKIT = False from pygsti.protocols import MirrorRBDesign as RMCDesign from pygsti.protocols import PeriodicMirrorCircuitDesign as PMCDesign from pygsti.protocols import ByDepthSummaryStatistics from pygsti.modelpacks import smq1Q_XY from pygsti.protocols import StandardGSTDesign import numpy as np +import pytest -class IBMQExperimentTester(): +@pytest.mark.skipif(not HAVE_QISKIT, reason="IBMQ experiment tests require qiskit.") +class IBMQExperimentTester: @classmethod def setup_class(cls): cls.backend = GenericBackendV2(num_qubits=4) @@ -144,4 +151,4 @@ def test_e2e_MCM_gst(self): exp.transpile(self.backend) exp.submit(self.backend) exp.monitor() - exp.retrieve_results() \ No newline at end of file + exp.retrieve_results() diff --git a/test/unit/mpi/run_me_with_mpiexec.py b/test/unit/mpi/run_me_with_mpiexec.py index 04a781d28..ea9c21065 100644 --- a/test/unit/mpi/run_me_with_mpiexec.py +++ b/test/unit/mpi/run_me_with_mpiexec.py @@ -10,8 +10,12 @@ import pygsti from pygsti.modelpacks import smq1Q_XYI as std +from pygsti.optimize import customsolve +customsolve.CUSTOM_SOLVE_THRESHOLD = 10 wcomm = MPI.COMM_WORLD +ALLOWED_MESSAGE = f'Running with CUSTOM_SOLVE_THRESHOLD = {customsolve.CUSTOM_SOLVE_THRESHOLD}' +print(ALLOWED_MESSAGE) class ParallelTest(object): diff --git a/test/unit/mpi/test_mpi.py b/test/unit/mpi/test_mpi.py index 21e23a982..2627ab614 100644 --- a/test/unit/mpi/test_mpi.py +++ b/test/unit/mpi/test_mpi.py @@ -3,12 +3,14 @@ import pytest import subprocess import sys +import shutil try: from mpi4py import MPI except (ImportError, RuntimeError): MPI = None +from run_me_with_mpiexec import ALLOWED_MESSAGE class MPITester: @@ -23,9 +25,21 @@ def test_all(self, capfd: pytest.LogCaptureFixture): if sys.platform == "darwin": subprocess_args.insert(3, "-oversubscribe") + if shutil.which('mpiexec') is None: + msg = \ + """ + mpi4py is installed, but mpiexec is not available. We're exitng this test + with an error. + + If you think mpiexec should be available in this shell, perhaps you need to run + `module load mpi` or `spack load mpi` (or something similar) first. + """ + raise RuntimeError(msg) result = subprocess.run(subprocess_args, capture_output=False, text=True) out, err = capfd.readouterr() - if len(out) + len(err) > 0: + tmp = out + err + tmp = [t for t in tmp if t != ALLOWED_MESSAGE] + if len(tmp) > 0: msg = out + '\n'+ 80*'-' + err raise RuntimeError(msg) return