diff --git a/pygsti/algorithms/germselection.py b/pygsti/algorithms/germselection.py index 48957b90e..bd8fd488a 100644 --- a/pygsti/algorithms/germselection.py +++ b/pygsti/algorithms/germselection.py @@ -27,6 +27,7 @@ from pygsti.baseobjs.statespace import ExplicitStateSpace as _ExplicitStateSpace from pygsti.baseobjs.statespace import QuditSpace as _QuditSpace from pygsti.models import ExplicitOpModel as _ExplicitOpModel +from pygsti.forwardsims import MatrixForwardSimulator as _MatrixForwardSimulator FLOATSIZE = 8 # in bytes: TODO: a better way @@ -57,10 +58,8 @@ def find_germs(target_model, randomize=True, randomization_strength=1e-2, Parameters ---------- - target_model : Model or list of Model - The model you are aiming to implement, or a list of models that are - copies of the model you are trying to implement (either with or - without random unitary perturbations applied to the models). + target_model : Model + The model you are aiming to implement. randomize : bool, optional Whether or not to add random unitary perturbations to the model(s) @@ -188,8 +187,14 @@ def find_germs(target_model, randomize=True, randomization_strength=1e-2, A list containing the germs making up the germ set. """ printer = _baseobjs.VerbosityPrinter.create_printer(verbosity, comm) + + if not isinstance(target_model.sim, _MatrixForwardSimulator): + target_model = target_model.copy() + target_model.sim = 'matrix' + modelList = _setup_model_list(target_model, randomize, randomization_strength, num_gs_copies, seed) + gates = list(target_model.operations.keys()) availableGermsList = [] if candidate_germ_counts is None: candidate_germ_counts = {6: 'all upto'} @@ -401,7 +406,19 @@ def find_germs(target_model, randomize=True, randomization_strength=1e-2, raise ValueError("'{}' is not a valid algorithm " "identifier.".format(algorithm)) - return germList + #force the line labels on each circuit to match the state space labels for the target model. + #this is suboptimal for many-qubit models, so will probably want to revisit this. #TODO + finalGermList = [] + for ckt in germList: + if ckt._static: + new_ckt = ckt.copy(editable=True) + new_ckt.line_labels = target_model.state_space.state_space_labels + new_ckt.done_editing() + finalGermList.append(new_ckt) + else: + ckt.line_labels = target_model.state_space.state_space_labels + finalGermList.append(ckt) + return finalGermList def compute_germ_set_score(germs, target_model=None, neighborhood=None, @@ -1351,6 +1368,10 @@ def test_germ_set_finitel(model, germs_to_test, length, weights=None, eigenvalues (from small to large) of the jacobian^T * jacobian matrix used to determine parameter amplification. """ + if not isinstance(model.sim, _MatrixForwardSimulator): + model = model.copy() + model.sim = 'matrix' + # Remove any SPAM vectors from model since we only want # to consider the set of *gate* parameters for amplification # and this makes sure our parameter counting is correct diff --git a/pygsti/forwardsims/forwardsim.py b/pygsti/forwardsims/forwardsim.py index 2ae19f2f3..727cffa8f 100644 --- a/pygsti/forwardsims/forwardsim.py +++ b/pygsti/forwardsims/forwardsim.py @@ -63,9 +63,7 @@ def cast(cls, obj : ForwardSimulator.Castable, num_qubits=None): if isinstance(obj, ForwardSimulator): return obj elif isinstance(obj, str): - if obj == "auto": - return _MapFSim() if (num_qubits is None or num_qubits > 2) else _MatrixFSim() - elif obj == "map": + if obj == "auto" or obj == "map": return _MapFSim() elif obj == "matrix": return _MatrixFSim() diff --git a/pygsti/forwardsims/mapforwardsim.py b/pygsti/forwardsims/mapforwardsim.py index 2a42f8891..89681605f 100644 --- a/pygsti/forwardsims/mapforwardsim.py +++ b/pygsti/forwardsims/mapforwardsim.py @@ -103,7 +103,6 @@ def __getstate__(self): # and this is done by the parent model which will cause _set_evotype to be called. return state - class MapForwardSimulator(_DistributableForwardSimulator, SimpleMapForwardSimulator): """ Computes circuit outcome probabilities using circuit layer maps that act on a state. diff --git a/pygsti/modelmembers/povms/__init__.py b/pygsti/modelmembers/povms/__init__.py index 3fc28cc29..b484b0bae 100644 --- a/pygsti/modelmembers/povms/__init__.py +++ b/pygsti/modelmembers/povms/__init__.py @@ -9,7 +9,6 @@ # 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. #*************************************************************************************************** -import _collections import functools as _functools import itertools as _itertools @@ -42,10 +41,40 @@ def create_from_pure_vectors(pure_vectors, povm_type, basis='pp', evotype='default', state_space=None, on_construction_error='warn'): - """ TODO: docstring -- create a POVM from a list/dict of (key, pure-vector) pairs """ + """ + Creates a Positive Operator-Valued Measure (POVM) from a list or dictionary of (key, pure-vector) pairs. + + Parameters + ---------- + pure_vectors : list or dict + A list of (key, pure-vector) pairs or a dictionary where keys are labels and values are pure state vectors. + + povm_type : str or tuple + The type of POVM to create. This can be a single string or a tuple of strings indicating the preferred types. + Supported types include 'computational', 'static pure', 'full pure', 'static', 'full', 'full TP', and any valid + Lindblad parameterization type. + + basis : str, optional + The basis in which the pure vectors are expressed. Default is 'pp'. + + evotype : str, optional + The evolution type. Default is 'default'. + + state_space : StateSpace, optional + The state space in which the POVM operates. Default is None. + + on_construction_error : str, optional + Specifies the behavior when an error occurs during POVM construction. Options are 'raise' to raise the error, + 'warn' to print a warning message, or any other value to silently ignore the error. Default is 'warn'. + + Returns + ------- + POVM + The constructed POVM object. + """ povm_type_preferences = (povm_type,) if isinstance(povm_type, str) else povm_type if not isinstance(pure_vectors, dict): # then assume it's a list of (key, value) pairs - pure_vectors = _collections.OrderedDict(pure_vectors) + pure_vectors = dict(pure_vectors) if state_space is None: state_space = _statespace.default_space_for_udim(len(next(iter(pure_vectors.values())))) @@ -94,10 +123,41 @@ def create_from_pure_vectors(pure_vectors, povm_type, basis='pp', evotype='defau def create_from_dmvecs(superket_vectors, povm_type, basis='pp', evotype='default', state_space=None, on_construction_error='warn'): - """ TODO: docstring -- create a POVM from a list/dict of (key, pure-vector) pairs """ + """ + Creates a Positive Operator-Valued Measure (POVM) from a list or dictionary of (key, superket) pairs. + + Parameters + ---------- + superket_vectors : list or dict + A list of (key, pure-vector) pairs or a dictionary where keys are labels and values are superket vectors. + i.e. vectorized density matrices. + + povm_type : str or tuple + The type of POVM to create. This can be a single string or a tuple of strings indicating the preferred types. + Supported types include 'full', 'static', 'full TP', 'computational', 'static pure', 'full pure', and any valid + Lindblad parameterization type. + + basis : str or `Basis`, optional + The basis in which the density matrix vectors are expressed. Default is 'pp'. + + evotype : str, optional + The evolution type. Default is 'default'. + + state_space : StateSpace, optional + The state space in which the POVM operates. Default is None. + + on_construction_error : str, optional + Specifies the behavior when an error occurs during POVM construction. Options are 'raise' to raise the error, + 'warn' to print a warning message, or any other value to silently ignore the error. Default is 'warn'. + + Returns + ------- + POVM + The constructed POVM object. + """ povm_type_preferences = (povm_type,) if isinstance(povm_type, str) else povm_type if not isinstance(superket_vectors, dict): # then assume it's a list of (key, value) pairs - superket_vectors = _collections.OrderedDict(superket_vectors) + superket_vectors = dict(superket_vectors) for typ in povm_type_preferences: try: @@ -140,12 +200,42 @@ def create_from_dmvecs(superket_vectors, povm_type, basis='pp', evotype='default print('Failed to construct povm with type "{}" with error: {}'.format(typ, str(err))) pass # move on to next type - raise ValueError("Could not create a POVM of type(s) %s from the given pure vectors!" % (str(povm_type))) + raise ValueError("Could not create a POVM of type(s) %s from the given density matrix vectors!" % (str(povm_type))) def create_effect_from_pure_vector(pure_vector, effect_type, basis='pp', evotype='default', state_space=None, on_construction_error='warn'): - """ TODO: docstring -- create a State from a state vector """ + """ + Creates a POVM effect from a pure state vector. + + Parameters + ---------- + pure_vector : array-like + The pure state vector from which to create the POVM effect. + + effect_type : str or tuple + The type of effect to create. This can be a single string or a tuple of strings indicating the preferred types. + Supported types include 'computational', 'static pure', 'full pure', 'static', 'full', 'static clifford', and + any valid Lindblad parameterization type. + + basis : str or `Basis` optional + The basis in which the pure vector is expressed. Default is 'pp'. + + evotype : str, optional + The evolution type. Default is 'default'. + + state_space : StateSpace, optional + The state space in which the effect operates. Default is None. + + on_construction_error : str, optional + Specifies the behavior when an error occurs during effect construction. Options are 'raise' to raise the error, + 'warn' to print a warning message, or any other value to silently ignore the error. Default is 'warn'. + + Returns + ------- + POVMEffect + The constructed POVM effect object. + """ effect_type_preferences = (effect_type,) if isinstance(effect_type, str) else effect_type if state_space is None: state_space = _statespace.default_space_for_udim(len(pure_vector)) @@ -196,6 +286,38 @@ def create_effect_from_pure_vector(pure_vector, effect_type, basis='pp', evotype def create_effect_from_dmvec(superket_vector, effect_type, basis='pp', evotype='default', state_space=None, on_construction_error='warn'): + """ + Creates a POVM effect from a density matrix vector (superket). + + Parameters + ---------- + superket_vector : array-like + The density matrix vector (superket) from which to create the POVM effect. + + effect_type : str or tuple + The type of effect to create. This can be a single string or a tuple of strings indicating the preferred types. + Supported types include 'static', 'full', and any valid Lindblad parameterization type. For other types + we first try to convert to a pure state vector and then utilize `create_effect_from_pure_vector` + + basis : str or `Basis` optional + The basis in which the superket vector is expressed. Default is 'pp'. + + evotype : str, optional + The evolution type. Default is 'default'. + + state_space : StateSpace, optional + The state space in which the effect operates. Default is None. + + on_construction_error : str, optional + Specifies the behavior when an error occurs during effect construction. Options are 'raise' to raise the error, + 'warn' to print a warning message, or any other value to silently ignore the error. Default is 'warn'. + + Returns + ------- + POVMEffect + The constructed POVM effect object. + """ + effect_type_preferences = (effect_type,) if isinstance(effect_type, str) else effect_type if state_space is None: state_space = _statespace.default_space_for_dim(len(superket_vector)) @@ -241,7 +363,8 @@ def create_effect_from_dmvec(superket_vector, effect_type, basis='pp', evotype=' def povm_type_from_op_type(op_type): - """Decode an op type into an appropriate povm type. + """ + Decode an op type into an appropriate povm type. Parameters: ----------- diff --git a/pygsti/models/modelconstruction.py b/pygsti/models/modelconstruction.py index 3bfff16b0..28e4428f4 100644 --- a/pygsti/models/modelconstruction.py +++ b/pygsti/models/modelconstruction.py @@ -883,7 +883,7 @@ def _embed_unitary(statespace, target_labels, unitary): key = _label.Label(instrument_name, inds) if isinstance(instrument_spec, str): - if instrument_spec == "Iz": + if instrument_spec == "Iz": #TODO: Create a set of standard instruments the same way we handle gates. #NOTE: this is very inefficient currently - there should be a better way of # creating an Iz instrument in the FUTURE inst_members = {} @@ -903,7 +903,15 @@ def _spec_to_densevec(spec, is_prep): num_qudits = len(qudit_labels) if isinstance(spec, str): if spec.isdigit(): # all([l in ('0', '1') for l in spec]): for qubits - bydigit_index = effect_spec + bydigit_index = spec + assert (len(bydigit_index) == num_qudits), \ + "Wrong number of qudits in '%s': expected %d" % (spec, num_qudits) + v = _np.zeros(state_space.udim) + inc = _np.flip(_np.cumprod(list(reversed(processor_spec.qudit_udims[1:] + (1,))))) + index = _np.dot(inc, list(map(int, bydigit_index))) + v[index] = 1.0 + elif (not is_prep) and spec.startswith("E_") and spec[len('E_'):].isdigit(): + bydigit_index = spec[len('E_'):] assert (len(bydigit_index) == num_qudits), \ "Wrong number of qudits in '%s': expected %d" % (spec, num_qudits) v = _np.zeros(state_space.udim) @@ -915,6 +923,14 @@ def _spec_to_densevec(spec, is_prep): assert (0 <= index < state_space.udim), \ "Index in '%s' out of bounds for state space with udim %d" % (spec, state_space.udim) v = _np.zeros(state_space.udim); v[index] = 1.0 + elif is_prep and spec.startswith("rho_") and spec[len('rho_'):].isdigit(): + bydigit_index = spec[len('rho_'):] + assert (len(bydigit_index) == num_qudits), \ + "Wrong number of qudits in '%s': expected %d" % (spec, num_qudits) + v = _np.zeros(state_space.udim) + inc = _np.flip(_np.cumprod(list(reversed(processor_spec.qudit_udims[1:] + (1,))))) + index = _np.dot(inc, list(map(int, bydigit_index))) + v[index] = 1.0 elif is_prep and spec.startswith("rho") and spec[len('rho'):].isdigit(): index = int(effect_spec[len('rho'):]) assert (0 <= index < state_space.udim), \ @@ -932,22 +948,27 @@ def _spec_to_densevec(spec, is_prep): return _bt.change_basis(_ot.state_to_dmvec(v), 'std', basis) - # elements are key, list-of-2-tuple pairs + # elements are key, list-of-2-tuple pairs or numpy array inst_members = {} - for k, lst in instrument_spec.items(): + for k, inst_effect_spec in instrument_spec.items(): + #one option is to specify the full dense instrument effect as a numpy array. + if isinstance(inst_effect_spec, _np.ndarray): + inst_members[k] = inst_effect_spec.copy() + continue member = None - if len(lst) == 2: - for effect_spec, prep_spec in lst: + if isinstance(inst_effect_spec, tuple): + inst_effect_spec = [inst_effect_spec] + elif isinstance(inst_effect_spec, list): + #elements should be 2-tuples corresponding to effect specs and prep effects respectively. + for (effect_spec, prep_spec) in inst_effect_spec: effect_vec = _spec_to_densevec(effect_spec, is_prep=False) prep_vec = _spec_to_densevec(prep_spec, is_prep=True) if member is None: member = _np.outer(effect_vec, prep_vec) else: member += _np.outer(effect_vec, prep_vec) - else: # elements are key, array of outer product already - # TODO: This appears to be the new standard format. Deprecate outer prod code above? - # But old code could still be useful. - member = lst.copy() + else: + raise ValueError('Unsupported instrument effect specification. See documentation of `QuditProcessorSpec` or `QubitProcessorSpec` for supported formats.') assert (member is not None), \ "You must provide at least one rank-1 specifier for each instrument member!" @@ -974,7 +995,7 @@ def _spec_to_densevec(spec, is_prep): local_noise = False; independent_gates = True; independent_spam = True prep_layers, povm_layers = _create_spam_layers(processor_spec, modelnoise, local_noise, ideal_prep_type, ideal_povm_type, evotype, - state_space, independent_gates, independent_spam, basis) + state_space, independent_spam, basis) for k, v in prep_layers.items(): ret.preps[k] = v for k, v in povm_layers.items(): @@ -997,7 +1018,7 @@ def _spec_to_densevec(spec, is_prep): def _create_spam_layers(processor_spec, modelnoise, local_noise, - ideal_prep_type, ideal_povm_type, evotype, state_space, independent_gates, independent_spam, + ideal_prep_type, ideal_povm_type, evotype, state_space, independent_spam, basis='pp'): """ local_noise=True creates lindblad ops that are embedded & composed 1Q ops, and assumes that modelnoise specifies 1Q noise. local_noise=False assumes modelnoise specifies n-qudit noise""" @@ -1099,7 +1120,8 @@ def _decomp_index_to_digits(i, bases): or ideal_prep_type.startswith('1+(') or ideal_prep_type.startswith('lindblad ')): if isinstance(prep_spec, str): - # Notes on conventions: When there are multiple qubits, the leftmost in a string (or, intuitively, + """ + Notes on conventions: When there are multiple qubits, the leftmost in a string (or, intuitively, # the first element in a list, e.g. [Q0_item, Q1_item, etc]) is "qubit 0". For example, in the # outcome string "01" qubit0 is 0 and qubit1 is 1. To create the full state/projector, 1Q operations # are tensored together in the same order, i.e., kron(Q0_item, Q1_item, ...). When a state is specified @@ -1108,6 +1130,7 @@ def _decomp_index_to_digits(i, bases): # where i is written normally, with the least significant bit on the right (but, perhaps # counterintuitively, this bit corresponds to the highest-indexed qubit). For example, "rho6" in a # 3-qubit system corresponds to "rho_110", that is |1> otimes |1> otimes |0> or |110>. + """ if not all([udim == 2 for udim in processor_spec.qudit_udims]): raise NotImplementedError(("State preps can currently only be constructed on a space of *qubits*" " when `ideal_prep_type == 'computational'` or is a Lindblad type")) @@ -1153,10 +1176,11 @@ def _decomp_index_to_digits(i, bases): def _create_ideal_1Q_prep(ud, i): v = _np.zeros(ud, 'd'); v[i] = 1.0 - return _state.create_from_pure_vector(v, vectype, 'pp', evotype, state_space=None) + return _state.create_from_pure_vector(v, vectype, basis, evotype, state_space=None) if isinstance(prep_spec, str): - if prep_spec.startswith('rho_') and all([l in ('0', '1') for l in prep_spec[len('rho_'):]]): + if prep_spec.startswith('rho_') and \ + all([l in [str(j) for j in processor_spec.qudit_udims[i]] for i,l in enumerate(prep_spec[len('rho_'):])]): bydigit_index = prep_spec[len('rho_'):] assert (len(bydigit_index) == num_qudits), \ "Wrong number of qudits in '%s': expected %d" % (prep_spec, num_qudits) @@ -1269,7 +1293,7 @@ def _1vec(ud, i): # constructs a vector of length `ud` with a single 1 at index def _create_ideal_1Q_povm(ud): effect_vecs = [(str(i), _1vec(ud, i)) for i in range(ud)] - return _povm.create_from_pure_vectors(effect_vecs, vectype, 'pp', + return _povm.create_from_pure_vectors(effect_vecs, vectype, basis, evotype, state_space=None) if isinstance(povm_spec, str): @@ -1322,11 +1346,12 @@ def _create_ideal_1Q_povm(ud): "You must provide at least one component effect specifier for each POVM effect!" effect_components = [] - if len(effect_spec) > 1: convert_to_dmvecs = True + if len(effect_spec) > 1: + convert_to_dmvecs = True for comp_espec in effect_spec: if isinstance(comp_espec, str): - if comp_espec.isdigit(): # all([l in ('0', '1') for l in comp_espec]) for qubits - bydigit_index = comp_espec + if comp_espec.isdigit() or (comp_espec.startswith("E_") and comp_espec[len('E_'):].isdigit()): # all([l in ('0', '1') for l in comp_espec]) for qubits + bydigit_index = comp_espec if comp_espec.isdigit() else comp_espec[len('E_'):] assert (len(bydigit_index) == num_qudits), \ "Wrong number of qudits in '%s': expected %d" % (comp_espec, num_qudits) v = _np.zeros(state_space.udim) @@ -1351,7 +1376,6 @@ def _create_ideal_1Q_povm(ud): else: raise ValueError("Invalid POVM effect spec: %s" % str(comp_espec)) effects_components.append((k, effect_components)) - if convert_to_dmvecs: effects = [] for k, effect_components in effects_components: @@ -1695,7 +1719,7 @@ def _create_crosstalk_free_model(processor_spec, modelnoise, custom_gates=None, local_noise = True prep_layers, povm_layers = _create_spam_layers(processor_spec, modelnoise, local_noise, ideal_prep_type, ideal_povm_type, evotype, - state_space, independent_gates, independent_spam, basis) + state_space, independent_spam, basis) modelnoise.warn_about_zero_counters() return _LocalNoiseModel(processor_spec, gatedict, prep_layers, povm_layers, @@ -1884,7 +1908,7 @@ def _create_cloud_crosstalk_model(processor_spec, modelnoise, custom_gates=None, local_noise = False prep_layers, povm_layers = _create_spam_layers(processor_spec, modelnoise, local_noise, 'computational', 'computational', evotype, state_space, - independent_gates, independent_spam, basis) + independent_spam, basis) if errcomp_type == 'gates': create_stencil_fn = modelnoise.create_errormap_stencil diff --git a/pygsti/processors/processorspec.py b/pygsti/processors/processorspec.py index d5c022b6f..801ea0ed0 100644 --- a/pygsti/processors/processorspec.py +++ b/pygsti/processors/processorspec.py @@ -45,74 +45,147 @@ def __init__(self): class QuditProcessorSpec(ProcessorSpec): """ The device specification for a one or more qudit quantum computer. - - Parameters - ---------- - num_qubits : int - The number of qubits in the device. - - gate_names : list of strings - The names of gates in the device. This may include standard gate - names known by pyGSTi (see below) or names which appear in the - `nonstd_gate_unitaries` argument. The set of standard gate names - includes, but is not limited to: - - - 'Gi' : the 1Q idle operation - - 'Gx','Gy','Gz' : 1-qubit pi/2 rotations - - 'Gxpi','Gypi','Gzpi' : 1-qubit pi rotations - - 'Gh' : Hadamard - - 'Gp' : phase or S-gate (i.e., ((1,0),(0,i))) - - 'Gcphase','Gcnot','Gswap' : standard 2-qubit gates - - Alternative names can be used for all or any of these gates, but - then they must be explicitly defined in the `nonstd_gate_unitaries` - dictionary. Including any standard names in `nonstd_gate_unitaries` - overrides the default (builtin) unitary with the one supplied. - - nonstd_gate_unitaries: dictionary of numpy arrays - A dictionary with keys that are gate names (strings) and values that are numpy arrays specifying - quantum gates in terms of unitary matrices. This is an additional "lookup" database of unitaries - - to add a gate to this `QubitProcessorSpec` its names still needs to appear in the `gate_names` list. - This dictionary's values specify additional (target) native gates that can be implemented in the device - as unitaries acting on ordinary pure-state-vectors, in the standard computationl basis. These unitaries - need not, and often should not, be unitaries acting on all of the qubits. E.g., a CNOT gate is specified - by a key that is the desired name for CNOT, and a value that is the standard 4 x 4 complex matrix for CNOT. - All gate names must start with 'G'. As an advanced behavior, a unitary-matrix-returning function which - takes a single argument - a tuple of label arguments - may be given instead of a single matrix to create - an operation *factory* which allows continuously-parameterized gates. This function must also return - an empty/dummy unitary when `None` is given as it's argument. - - availability : dict, optional - A dictionary whose keys are some subset of the keys (which are gate names) `nonstd_gate_unitaries` and the - strings (which are gate names) in `gate_names` and whose values are lists of qubit-label-tuples. Each - qubit-label-tuple must have length equal to the number of qubits the corresponding gate acts upon, and - causes that gate to be available to act on the specified qubits. Instead of a list of tuples, values of - `availability` may take the special values `"all-permutations"` and `"all-combinations"`, which as their - names imply, equate to all possible permutations and combinations of the appropriate number of qubit labels - (deterined by the gate's dimension). If a gate name is not present in `availability`, the default is - `"all-permutations"`. So, the availability of a gate only needs to be specified when it cannot act in every - valid way on the qubits (e.g., the device does not have all-to-all connectivity). - - geometry : {"line","ring","grid","torus"} or QubitGraph, optional - The type of connectivity among the qubits, specifying a graph used to - define neighbor relationships. Alternatively, a :class:`QubitGraph` - object with `qubit_labels` as the node labels may be passed directly. - This argument is only used as a convenient way of specifying gate - availability (edge connections are used for gates whose availability - is unspecified by `availability` or whose value there is `"all-edges"`). - - qubit_labels : list or tuple, optional - The labels (integers or strings) of the qubits. If `None`, then the integers starting with zero are used. - - aux_info : dict, optional - Any additional information that should be attached to this processor spec. - - TODO: update this docstring for qudits """ def __init__(self, qudit_labels, qudit_udims, gate_names, nonstd_gate_unitaries=None, availability=None, geometry=None, prep_names=('rho0',), povm_names=('Mdefault',), instrument_names=(), nonstd_preps=None, nonstd_povms=None, nonstd_instruments=None, aux_info=None): + """ + Parameters + ---------- + num_qubits : int + The number of qubits in the device. + + gate_names : list of strings + The names of gates in the device. This may include standard gate + names known by pyGSTi (see below) or names which appear in the + `nonstd_gate_unitaries` argument. The set of standard gate names + includes, but is not limited to: + + - 'Gi' : the 1Q idle operation + - 'Gx','Gy','Gz' : 1-qubit pi/2 rotations + - 'Gxpi','Gypi','Gzpi' : 1-qubit pi rotations + - 'Gh' : Hadamard + - 'Gp' : phase or S-gate (i.e., ((1,0),(0,i))) + - 'Gcphase','Gcnot','Gswap' : standard 2-qubit gates + + Alternative names can be used for all or any of these gates, but + then they must be explicitly defined in the `nonstd_gate_unitaries` + dictionary. Including any standard names in `nonstd_gate_unitaries` + overrides the default (builtin) unitary with the one supplied. + + nonstd_gate_unitaries: dictionary of numpy arrays + A dictionary with keys that are gate names (strings) and values that are numpy arrays specifying + quantum gates in terms of unitary matrices. This is an additional "lookup" database of unitaries - + to add a gate to this `QuditProcessorSpec` its names still needs to appear in the `gate_names` list. + This dictionary's values specify additional (target) native gates that can be implemented in the device + as unitaries acting on ordinary pure-state-vectors, in the standard computationl basis. These unitaries + need not, and often should not, be unitaries acting on all of the qubits. E.g., a CNOT gate is specified + by a key that is the desired name for CNOT, and a value that is the standard 4 x 4 complex matrix for CNOT. + All gate names must start with 'G'. As an advanced behavior, a unitary-matrix-returning function which + takes a single argument - a tuple of label arguments - may be given instead of a single matrix to create + an operation *factory* which allows continuously-parameterized gates. This function must also return + an empty/dummy unitary when `None` is given as it's argument. + + availability : dict, optional + A dictionary whose keys are some subset of the keys (which are gate names) `nonstd_gate_unitaries` and the + strings (which are gate names) in `gate_names` and whose values are lists of qubit-label-tuples. Each + qubit-label-tuple must have length equal to the number of qubits the corresponding gate acts upon, and + causes that gate to be available to act on the specified qubits. Instead of a list of tuples, values of + `availability` may take the special values `"all-permutations"` and `"all-combinations"`, which as their + names imply, equate to all possible permutations and combinations of the appropriate number of qubit labels + (deterined by the gate's dimension). If a gate name is not present in `availability`, the default is + `"all-permutations"`. So, the availability of a gate only needs to be specified when it cannot act in every + valid way on the qubits (e.g., the device does not have all-to-all connectivity). + + geometry : {"line","ring","grid","torus"} or QubitGraph, optional + The type of connectivity among the qubits, specifying a graph used to + define neighbor relationships. Alternatively, a :class:`QubitGraph` + object with `qubit_labels` as the node labels may be passed directly. + This argument is only used as a convenient way of specifying gate + availability (edge connections are used for gates whose availability + is unspecified by `availability` or whose value there is `"all-edges"`). + + prep_names : list or tuple of str, optional (default ('rho0',)) + List of strings corresponding to the names of the native state preparation + operations supported by this processor specification. State preparation names + must start with 'rho'. + + povm_names : list or tuple of str, optional (default ('Mdefault',)) + List of strings corresponding to the names of the native POVMs + supported by this processor specification. POVM names must start with + 'M'. + + instrument_names : list or tuple of str, optional (default ()) + List of strings corresponding to the names of the quantum instruments + supported by this processor specification. Instrument names must start with + 'I'. + + nonstd_preps : dict, optional (default None) + Dictionary mapping preparation names (as specified in `prep_names`) to corresponding + state preparations. The values of this dictionary can be the following specifiers: + + - numpy ndarray: numpy vector corresponding to the dense representation of the pure + state corresponding to this state preparation, written in the standard/computational + basis. + - string specifiers: For string state preparation specifiers there are two prefixes supported + which determine the parsing applied for conversion to a corresponding state preparation.: + The first prefix is 'rho_'. When this prefix is used, any digits proceeding in the string + are interpreted as digits of the base d number (where d is appropriate dimensions of the qudit + subsystems, though note this dimension might vary for each subsystem) labeling a standard basis state. + E.g. 'rho_01' when both subsystems are qubits corresponds to the state |01>. 'rho_12' when the two subsystems + are qutrits is the state |12>, etc. The second prefix is 'rho' (w/o the underscore). When this + prefix is used the following digits are interpreted as an integer. This integer is then converted + into a base d (see comment above about mixed dimensions) number labeling the corresponding standard + basis state (with the conversion using right-LSB convention). + + nonstd_povms : dict, optional (default None) + Dictionary mapping POVM names (as specified in `povm_names`) to corresponding POVMs. The values of + this dictionary can be the following specifiers: + + - string specifiers: Presently two special string specifiers are supported, 'Mdefault' and 'Mz', + both of which map to POVMs for computational-basis readout with the appropriate dimensions. + - dictionary: A dictionary whose keys are effect labels, and whose values are specifiers used to construct + corresponding effects. Effect specifiers can themselves take two forms: + - list of numpy arrays: List of numpy arrays corresponding to the component pure states whose corresponding + projectors (density matrix vectors) are summed to produce this POVM effect. When there is only a single + such pure state then one can alternatively use that numpy array directly as the value (without wrapping + in a list) for convenience. E.g. to construct a two-qubit, two-outcome, POVM corresponding to parity readout wherein + each POVM effect corresponds to a rank-2 projector onto the even or odd computational subspace the complete POVM + specifier would be: + {'even': [np.array([1,0,0,0], np.array([0,0,0,1])], 'odd': [np.array([0,1,0,0], np.array([0,0,1,0])]} + -list of string specifiers: Alternatively one can specify a list of effect specifiers using special string + notation. String specifiers can take three formats: + 1. They can be strings which directly correspond to the desired output bit/ditstring in the standard basis. + E.g. "0000" or "2212" + 2. Strings prefixed by either 'E_' or 'E' (w/o an underscore). In the first case any digits proceeding the + "E_" are interpretted as a bit/ditstring written in whatever base is appropriate given the qudit dimensions. + If prefixed by "E" (w/o an underscore) the proceeding digits are interpreted as an integer and converted into + a base d number using right-LSB convention. E.g. 'E_0000' corresponds to the state |0000>, and E15 corresponds + to |1111> (assuming this was acting on 4-qubits). + + nonstd_instruments : dict, optional (default None) + Dictionary mapping instrument names (as specified in `instrument_names`) to corresponding instruments. The values + of this dictionary can be the following specifiers: + + - string specifiers: Presently only one special string specifier is supported, 'Iz'. This corresponds to a quantum + instrument for computational-basis readout. + - dictionary: A dictionary whose keys are instrument effect labels, and whose values are specifiers used to construct the + corresponding instrument effects. Instrument effect specifiers can take the following form: + - numpy array: A numpy array corresponding to the dense representation of the instrument effect. + - lists of 2-tuples: Each tuple in this list of 2-tuples is such that the first element corresponds to a POVM effect + specifier (see `nonstd_povms` for supported options), and the second element is a state preparation specifier + (see `nonstd_preps` for supported options). These specifiers are used to construct appropriate effect and preparation + representations which are then have their outer product taken. This is done for each 2-tuple, and the outer products are + then summed to get the overall instrument effect. In the case where there is only a single 2-tuple for an instrument effect + this tuple can be used directly as the dictionary value (w/o being wrapped in a list) for convenience. + + qubit_labels : list or tuple, optional + The labels (integers or strings) of the qubits. If `None`, then the integers starting with zero are used. + + aux_info : dict, optional + Any additional information that should be attached to this processor spec. + """ num_qudits = len(qudit_labels) assert(len(qudit_udims) == num_qudits), "length of `qudit_labels` must equal that of `qubit_udims`!" assert(not (len(qudit_labels) > 1 and availability is None and geometry is None)), \ @@ -131,7 +204,7 @@ def __init__(self, qudit_labels, qudit_udims, gate_names, nonstd_gate_unitaries= self.nonstd_instruments = nonstd_instruments.copy() if (nonstd_instruments is not None) else {} # Store the unitary matrices defining the gates, as it is convenient to have these easily accessable. - self.gate_unitaries = _collections.OrderedDict() + self.gate_unitaries = dict() std_gate_unitaries = _itgs.standard_gatename_unitaries() for gname in gate_names: if gname in nonstd_gate_unitaries: @@ -182,9 +255,9 @@ def __init__(self, qudit_labels, qudit_udims, gate_names, nonstd_gate_unitaries= assert(len(self.qudit_labels) == len(self.qudit_udims)) # Set availability - if availability is None: availability = {} - self.availability = _collections.OrderedDict([(gatenm, availability.get(gatenm, 'all-edges')) - for gatenm in self.gate_names]) + if availability is None: + availability = {} + self.availability = {gatenm: availability.get(gatenm, 'all-edges') for gatenm in self.gate_names} # if _Lbl(gatenm).sslbls is not None NEEDED? self.compiled_from = None # could hold (QuditProcessorSpec, compilations) tuple if not None @@ -237,7 +310,6 @@ def _serialize_instrument(obj): nonstd_preps = {k: _serialize_state(obj) for k, obj in self.nonstd_preps.items()} nonstd_povms = {k: _serialize_povm(obj) for k, obj in self.nonstd_povms.items()} nonstd_instruments = {':'.join(k): _serialize_instrument(obj) for k, obj in self.nonstd_instruments.items()} - state.update({'qudit_labels': list(self.qudit_labels), 'qudit_udims': list(self.qudit_udims), 'gate_names': list(self.gate_names), # Note: not labels, just strings, so OK @@ -327,7 +399,6 @@ def _tuplize(x): availability = {k: _tuplize(v) for k, v in state['availability'].items()} geometry = _qgraph.QubitGraph.from_nice_serialization(state['geometry']) - return cls(state['qudit_labels'], state['qudit_udims'], state['gate_names'], nonstd_gate_unitaries, availability, geometry, state['prep_names'], state['povm_names'], [tuple(iname) for iname in state['instrument_names']], @@ -405,8 +476,8 @@ def instrument_specifier(self, name): ------- str or dict """ - if name in self.nonstd_instruments: - return self.nonstd_instruments[name] + if tuple(name) in self.nonstd_instruments.keys(): + return self.nonstd_instruments[tuple(name)] else: # assert(is_standard_instrument_name(name)) TODO return name @@ -463,8 +534,8 @@ def rename(nm): self.gate_names = tuple([rename(nm) for nm in self.gate_names]) self.nonstd_gate_unitaries = {rename(k): v for k, v in self.nonstd_gate_unitaries} - self.gate_unitaries = _collections.OrderedDict([(rename(k), v) for k, v in self.gate_unitaries]) - self.availability = _collections.OrderedDict([(rename(k), v) for k, v in self.availability]) + self.gate_unitaries = {rename(k): v for k, v in self.gate_unitaries} + self.availability = {rename(k): v for k, v in self.availability} def resolved_availability(self, gate_name, tuple_or_function="auto"): """ @@ -755,77 +826,149 @@ def global_idle_layer_label(self): class QubitProcessorSpec(QuditProcessorSpec): """ The device specification for a one or more qudit quantum computer. - - Parameters - ---------- - num_qubits : int - The number of qubits in the device. - - gate_names : list of strings - The names of gates in the device. This may include standard gate - names known by pyGSTi (see below) or names which appear in the - `nonstd_gate_unitaries` argument. The set of standard gate names - includes, but is not limited to: - - - 'Gi' : the 1Q idle operation - - 'Gx','Gy','Gz' : 1-qubit pi/2 rotations - - 'Gxpi','Gypi','Gzpi' : 1-qubit pi rotations - - 'Gh' : Hadamard - - 'Gp' : phase or S-gate (i.e., ((1,0),(0,i))) - - 'Gcphase','Gcnot','Gswap' : standard 2-qubit gates - - Alternative names can be used for all or any of these gates, but - then they must be explicitly defined in the `nonstd_gate_unitaries` - dictionary. Including any standard names in `nonstd_gate_unitaries` - overrides the default (builtin) unitary with the one supplied. - - nonstd_gate_unitaries: dictionary of numpy arrays - A dictionary with keys that are gate names (strings) and values that are numpy arrays specifying - quantum gates in terms of unitary matrices. This is an additional "lookup" database of unitaries - - to add a gate to this `QubitProcessorSpec` its names still needs to appear in the `gate_names` list. - This dictionary's values specify additional (target) native gates that can be implemented in the device - as unitaries acting on ordinary pure-state-vectors, in the standard computationl basis. These unitaries - need not, and often should not, be unitaries acting on all of the qubits. E.g., a CNOT gate is specified - by a key that is the desired name for CNOT, and a value that is the standard 4 x 4 complex matrix for CNOT. - All gate names must start with 'G'. As an advanced behavior, a unitary-matrix-returning function which - takes a single argument - a tuple of label arguments - may be given instead of a single matrix to create - an operation *factory* which allows continuously-parameterized gates. This function must also return - an empty/dummy unitary when `None` is given as it's argument. - - availability : dict, optional - A dictionary whose keys are some subset of the keys (which are gate names) `nonstd_gate_unitaries` and the - strings (which are gate names) in `gate_names` and whose values are lists of qubit-label-tuples. Each - qubit-label-tuple must have length equal to the number of qubits the corresponding gate acts upon, and - causes that gate to be available to act on the specified qubits. Instead of a list of tuples, values of - `availability` may take the special values `"all-permutations"` and `"all-combinations"`, which as their - names imply, equate to all possible permutations and combinations of the appropriate number of qubit labels - (deterined by the gate's dimension). If a gate name is not present in `availability`, the default is - `"all-permutations"`. So, the availability of a gate only needs to be specified when it cannot act in every - valid way on the qubits (e.g., the device does not have all-to-all connectivity). - - geometry : {"line","ring","grid","torus"} or QubitGraph, optional - The type of connectivity among the qubits, specifying a graph used to - define neighbor relationships. Alternatively, a :class:`QubitGraph` - object with `qubit_labels` as the node labels may be passed directly. - This argument is only used as a convenient way of specifying gate - availability (edge connections are used for gates whose availability - is unspecified by `availability` or whose value there is `"all-edges"`). - - qubit_labels : list or tuple, optional - The labels (integers or strings) of the qubits. If `None`, then the integers starting with zero are used. - - nonstd_gate_symplecticreps : dict, optional - A dictionary similar to `nonstd_gate_unitaries` that supplies, instead of a unitary matrix, the symplectic - representation of a Clifford operations, given as a 2-tuple of numpy arrays. - - aux_info : dict, optional - Any additional information that should be attached to this processor spec. """ - def __init__(self, num_qubits, gate_names, nonstd_gate_unitaries=None, availability=None, geometry=None, qubit_labels=None, nonstd_gate_symplecticreps=None, prep_names=('rho0',), povm_names=('Mdefault',), instrument_names=(), nonstd_preps=None, nonstd_povms=None, nonstd_instruments=None, aux_info=None): + """ + Parameters + ---------- + num_qubits : int + The number of qubits in the device. + + gate_names : list of strings + The names of gates in the device. This may include standard gate + names known by pyGSTi (see below) or names which appear in the + `nonstd_gate_unitaries` argument. The set of standard gate names + includes, but is not limited to: + + - 'Gi' : the 1Q idle operation + - 'Gx','Gy','Gz' : 1-qubit pi/2 rotations + - 'Gxpi','Gypi','Gzpi' : 1-qubit pi rotations + - 'Gh' : Hadamard + - 'Gp' : phase or S-gate (i.e., ((1,0),(0,i))) + - 'Gcphase','Gcnot','Gswap' : standard 2-qubit gates + + Alternative names can be used for all or any of these gates, but + then they must be explicitly defined in the `nonstd_gate_unitaries` + dictionary. Including any standard names in `nonstd_gate_unitaries` + overrides the default (builtin) unitary with the one supplied. + + nonstd_gate_unitaries: dictionary of numpy arrays + A dictionary with keys that are gate names (strings) and values that are numpy arrays specifying + quantum gates in terms of unitary matrices. This is an additional "lookup" database of unitaries - + to add a gate to this `QubitProcessorSpec` its names still needs to appear in the `gate_names` list. + This dictionary's values specify additional (target) native gates that can be implemented in the device + as unitaries acting on ordinary pure-state-vectors, in the standard computationl basis. These unitaries + need not, and often should not, be unitaries acting on all of the qubits. E.g., a CNOT gate is specified + by a key that is the desired name for CNOT, and a value that is the standard 4 x 4 complex matrix for CNOT. + All gate names must start with 'G'. As an advanced behavior, a unitary-matrix-returning function which + takes a single argument - a tuple of label arguments - may be given instead of a single matrix to create + an operation *factory* which allows continuously-parameterized gates. This function must also return + an empty/dummy unitary when `None` is given as it's argument. + + availability : dict, optional + A dictionary whose keys are some subset of the keys (which are gate names) `nonstd_gate_unitaries` and the + strings (which are gate names) in `gate_names` and whose values are lists of qubit-label-tuples. Each + qubit-label-tuple must have length equal to the number of qubits the corresponding gate acts upon, and + causes that gate to be available to act on the specified qubits. Instead of a list of tuples, values of + `availability` may take the special values `"all-permutations"` and `"all-combinations"`, which as their + names imply, equate to all possible permutations and combinations of the appropriate number of qubit labels + (deterined by the gate's dimension). If a gate name is not present in `availability`, the default is + `"all-permutations"`. So, the availability of a gate only needs to be specified when it cannot act in every + valid way on the qubits (e.g., the device does not have all-to-all connectivity). + + geometry : {"line","ring","grid","torus"} or QubitGraph, optional + The type of connectivity among the qubits, specifying a graph used to + define neighbor relationships. Alternatively, a :class:`QubitGraph` + object with `qubit_labels` as the node labels may be passed directly. + This argument is only used as a convenient way of specifying gate + availability (edge connections are used for gates whose availability + is unspecified by `availability` or whose value there is `"all-edges"`). + + qubit_labels : list or tuple, optional + The labels (integers or strings) of the qubits. If `None`, then the integers starting with zero are used. + + nonstd_gate_symplecticreps : dict, optional + A dictionary similar to `nonstd_gate_unitaries` that supplies, instead of a unitary matrix, the symplectic + representation of a Clifford operations, given as a 2-tuple of numpy arrays. + #TODO: Better explanation of this specifier. + + prep_names : list or tuple of str, optional (default ('rho0',)) + List of strings corresponding to the names of the native state preparation + operations supported by this processor specification. State preparation names + must start with 'rho'. + + povm_names : list or tuple of str, optional (default ('Mdefault',)) + List of strings corresponding to the names of the native POVMs + supported by this processor specification. POVM names must start with + 'M'. + + instrument_names : list or tuple of str, optional (default ()) + List of strings corresponding to the names of the quantum instruments + supported by this processor specification. Instrument names must start with + 'I'. + + nonstd_preps : dict, optional (default None) + Dictionary mapping preparation names (as specified in `prep_names`) to corresponding + state preparations. The values of this dictionary can be the following specifiers: + + - numpy ndarray: numpy vector corresponding to the dense representation of the pure + state corresponding to this state preparation, written in the standard/computational + basis. + - string specifiers: For string state preparation specifiers there are two prefixes supported + which determine the parsing applied for conversion to a corresponding state preparation.: + The first prefix is 'rho_'. When this prefix is used, any digits proceeding in the string + are interpreted as the bitstring labeling a standard basis state. + E.g. 'rho_01' corresponds to the state |01>. The second prefix is 'rho' (w/o the underscore). When this + prefix is used the following digits are interpreted as an integer. This integer is then converted + into a bitstring labeling the corresponding standard basis state (with the conversion using right-LSB convention). + + nonstd_povms : dict, optional (default None) + Dictionary mapping POVM names (as specified in `povm_names`) to corresponding POVMs. The values of + this dictionary can be the following specifiers: + + - string specifiers: Presently two special string specifiers are supported, 'Mdefault' and 'Mz', + both of which map to POVMs for computational-basis readout with the appropriate dimensions. + - dictionary: A dictionary whose keys are effect labels, and whose values are specifiers used to construct + corresponding effects. Effect specifiers can themselves take two forms: + - list of numpy arrays: List of numpy arrays corresponding to the component pure states whose corresponding + projectors (density matrix vectors) are summed to produce this POVM effect. When there is only a single + such pure state then one can alternatively use that numpy array directly as the value (without wrapping + in a list) for convenience. E.g. to construct a two-qubit, two-outcome, POVM corresponding to parity readout wherein + each POVM effect corresponds to a rank-2 projector onto the even or odd computational subspace the complete POVM + specifier would be: + {'even': [np.array([1,0,0,0], np.array([0,0,0,1])], 'odd': [np.array([0,1,0,0], np.array([0,0,1,0])]} + -list of string specifiers: Alternatively one can specify a list of effect specifiers using special string + notation. String specifiers can take three formats: + 1. They can be strings which directly correspond to the desired output bit/ditstring in the standard basis. + E.g. "0000" or "1001". + 2. Strings prefixed by either 'E_' or 'E' (w/o an underscore). In the first case any digits proceeding the + "E_" are interpretted as a bitstring. + If prefixed by "E" (w/o an underscore) the proceeding digits are interpreted as an integer and converted into + a base d number using right-LSB convention. E.g. 'E_0000' corresponds to the state |0000>, and E15 corresponds + to |1111> (assuming this was acting on 4-qubits). + + nonstd_instruments : dict, optional (default None) + Dictionary mapping instrument names (as specified in `instrument_names`) to corresponding instruments. The values + of this dictionary can be the following specifiers: + + - string specifiers: Presently only one special string specifier is supported, 'Iz'. This corresponds to a quantum + instrument for computational-basis readout. + - dictionary: A dictionary whose keys are instrument effect labels, and whose values are specifiers used to construct the + corresponding instrument effects. Instrument effect specifiers can take the following form: + - numpy array: A numpy array corresponding to the dense representation of the instrument effect. + - lists of 2-tuples: Each tuple in this list of 2-tuples is such that the first element corresponds to a POVM effect + specifier (see `nonstd_povms` for supported options), and the second element is a state preparation specifier + (see `nonstd_preps` for supported options). These specifiers are used to construct appropriate effect and preparation + representations which are then have their outer product taken. This is done for each 2-tuple, and the outer products are + then summed to get the overall instrument effect. In the case where there is only a single 2-tuple for an instrument effect + this tuple can be used directly as the dictionary value (w/o being wrapped in a list) for convenience. + + aux_info : dict, optional + Any additional information that should be attached to this processor spec. + """ assert(type(num_qubits) is int), "The number of qubits, n, should be an integer!" assert(not (num_qubits > 1 and availability is None and geometry is None)), \ "For multi-qubit processors you must specify either the geometry or the availability!" @@ -874,10 +1017,11 @@ def _tuplize(x): _warnings.warn(("Loading an old-format QubitProcessorSpec that doesn't contain SPAM information." " You should check to make sure you don't want/need to add this information and" " then re-save this processor spec.")) - + instrument_names = state.get('instrument_names', []) + instrument_names = [tuple(name) if isinstance(name,list) else name for name in instrument_names] return cls(len(state['qubit_labels']), state['gate_names'], nonstd_gate_unitaries, availability, geometry, state['qubit_labels'], symplectic_reps, state.get('prep_names', []), - state.get('povm_names', []), state.get('instrument_names', []), nonstd_preps, nonstd_povms, + state.get('povm_names', []), instrument_names, nonstd_preps, nonstd_povms, nonstd_instruments, state['aux_info']) @property @@ -1221,3 +1365,4 @@ def compute_2Q_connectivity(self): twoQ_connectivity[qubit_labels.index(sslbls[0]), qubit_labels.index(sslbls[1])] = True return _qgraph.QubitGraph(qubit_labels, twoQ_connectivity) + diff --git a/scripts/api_names.yaml b/scripts/api_names.yaml index c09dfd954..c07036445 100644 --- a/scripts/api_names.yaml +++ b/scripts/api_names.yaml @@ -1261,8 +1261,6 @@ objects: Model: # XXX note dereference to forward simulator methods -- better design? __name__: null bulk_dprobs: null # XXX parameter `circuit_list` -> `circuits` - bulk_evaltree: null # XXX parameter `circuit_list` -> `circuits` - bulk_evaltree_from_resources: null # XXX parameter `circuit_list` -> `circuits` bulk_fill_dprobs: null # XXX parameter `eval_tree` -> `evaltree` bulk_fill_hprobs: null # XXX parameter `eval_tree` -> `evaltree` bulk_fill_probs: null # XXX parameter `eval_tree` -> `evaltree` @@ -1282,8 +1280,6 @@ objects: __name__: null basis: null bulk_dprobs: null # XXX parameter `circuit_list` -> `circuits` - bulk_evaltree: null # XXX parameter `circuit_list` -> `circuits` - bulk_evaltree_from_resources: null # XXX parameter `circuit_list` -> `circuits` bulk_fill_dprobs: null # XXX parameter `eval_tree` -> `evaltree` bulk_fill_hprobs: null # XXX parameter `eval_tree` -> `evaltree` bulk_fill_probs: null # XXX parameter `eval_tree` -> `evaltree` @@ -1837,8 +1833,6 @@ objects: OplessModel: __name__: null bulk_dprobs: null # XXX parameter `circuit_list` -> `circuits` - bulk_evaltree: null # XXX parameter `circuit_list` -> `circuits` - bulk_evaltree_from_resources: null # XXX parameter `circuit_list` -> `circuits` bulk_fill_dprobs: null # XXX parameter `eval_tree` -> `evaltree` bulk_fill_probs: null # XXX parameter `eval_tree` -> `evaltree` bulk_probs: null # XXX parameter `circuit_list` -> `circuits` @@ -1850,7 +1844,6 @@ objects: __name__: null SuccessFailModel: __name__: null - bulk_evaltree: null # XXX parameter `circuit_list` -> `circuits` dprobs: null get_num_outcomes: compute_num_outcomes poly_probs: polynomial_probs diff --git a/test/test_packages/algorithms/test_germselection.py b/test/test_packages/algorithms/test_germselection.py index eeb7ff867..99f021400 100644 --- a/test/test_packages/algorithms/test_germselection.py +++ b/test/test_packages/algorithms/test_germselection.py @@ -121,7 +121,9 @@ def test_germsel_greedy(self): threshold = 1e6 randomizationStrength = 1e-3 neighborhoodSize = 2 - gatesetNeighborhood = pygsti.alg.randomize_model_list([std.target_model()], + model = std.target_model() + model.sim = 'matrix' + gatesetNeighborhood = pygsti.alg.randomize_model_list([model], randomization_strength=randomizationStrength, num_copies=neighborhoodSize, seed=2014) @@ -141,7 +143,9 @@ def test_germsel_greedy(self): def test_germsel_driver_greedy(self): #GREEDY options = {'threshold': 1e6 } - germs = pygsti.alg.find_germs(std.target_model(), randomize=True, randomization_strength=1e-3, + model = std.target_model() + model.sim = 'matrix' + germs = pygsti.alg.find_germs(model, randomize=True, randomization_strength=1e-3, num_gs_copies=2, seed=2017, candidate_germ_counts={3: 'all upto', 4: 10, 5:10, 6:10}, candidate_seed=2017, force="singletons", algorithm='greedy', algorithm_kwargs=options, mem_limit=None, comm=None, @@ -152,7 +156,9 @@ def test_germsel_driver_greedy(self): def test_germsel_driver_grasp(self): #more args options = {'threshold': 1e6 , 'return_all': True} - germs = pygsti.alg.find_germs(std.target_model(), randomize=True, randomization_strength=1e-3, + model = std.target_model() + model.sim = 'matrix' + germs = pygsti.alg.find_germs(model, randomize=True, randomization_strength=1e-3, num_gs_copies=2, seed=2017, candidate_germ_counts={3: 'all upto', 4: 10, 5:10, 6:10}, candidate_seed=2017, force="singletons", algorithm='grasp', algorithm_kwargs=options, mem_limit=None, @@ -166,7 +172,9 @@ def test_germsel_driver_grasp(self): def test_germsel_driver_slack(self): #SLACK options = dict(fixed_slack=False, slack_frac=0.1) - germs = pygsti.alg.find_germs(std.target_model(), randomize=True, randomization_strength=1e-3, + model = std.target_model() + model.sim = 'matrix' + germs = pygsti.alg.find_germs(model, randomize=True, randomization_strength=1e-3, num_gs_copies=2, seed=2017, candidate_germ_counts={3: 'all upto', 4: 10, 5:10, 6:10}, candidate_seed=2017, force="singletons", algorithm='slack', algorithm_kwargs=options, mem_limit=None, comm=None, diff --git a/test/test_packages/objects/test_evaltree.py b/test/test_packages/objects/test_evaltree.py index b1b1f5967..16041ec47 100644 --- a/test/test_packages/objects/test_evaltree.py +++ b/test/test_packages/objects/test_evaltree.py @@ -14,6 +14,9 @@ def setUp(self): self.circuits = pygsti.circuits.to_circuits(["Gxpi2:0", "Gypi2:0", "Gxpi2:0Gxpi2:0", "Gypi2:0Gypi2:0", "Gxpi2:0Gypi2:0"]) self.model = smq1Q_XY.target_model() + model_matrix = self.model.copy() + model_matrix.sim = 'map' + self.model_matrix = model_matrix def _test_layout(self, layout): self.assertEqual(layout.num_elements, len(self.circuits) * 2) # 2 outcomes per circuit @@ -55,120 +58,7 @@ def test_map_layout(self): #TODO: test split layouts def test_matrix_layout(self): - self._test_layout(pygsti.layouts.matrixlayout.MatrixCOPALayout(self.circuits[:], self.model)) - - #SCRATCH - # # An additional specific test added from debugging mapevaltree splitting - # mgateset = pygsti.construction.create_explicit_model( - # [('Q0',)],['Gi','Gx','Gy'], - # [ "I(Q0)","X(pi/8,Q0)", "Y(pi/8,Q0)"]) - # mgateset._calcClass = MapForwardSimulator - # - # gatestring1 = ('Gx','Gy') - # gatestring2 = ('Gx','Gy','Gy') - # gatestring3 = ('Gx',) - # gatestring4 = ('Gy','Gy') - # #mevt,mlookup,moutcome_lookup = mgateset.bulk_evaltree( [gatestring1,gatestring2] ) - # #mevt,mlookup,moutcome_lookup = mgateset.bulk_evaltree( [gatestring1,gatestring4] ) - # mevt,mlookup,moutcome_lookup = mgateset.bulk_evaltree( [gatestring1,gatestring2,gatestring3,gatestring4] ) - # print("Tree = ",mevt) - # print("Cache size = ",mevt.cache_size()) - # print("lookup = ",mlookup) - # print() - # - # self.assertEqual(mevt[:], [(0, ('Gy',), 1), - # (1, ('Gy',), None), - # (None, ('rho0', 'Gx',), 0), - # (None, ('rho0', 'Gy', 'Gy'), None)]) - # self.assertEqual(mevt.cache_size(),2) - # self.assertEqual(mevt.evaluation_order(),[2, 0, 1, 3]) - # self.assertEqual(mevt.num_final_circuits(),4) - # - # ## COPY - # mevt_copy = mevt.copy() - # print("Tree copy = ",mevt_copy) - # print("Cache size = ",mevt_copy.cache_size()) - # print("Eval order = ",mevt_copy.evaluation_order()) - # print("Num final = ",mevt_copy.num_final_circuits()) - # print() - # - # self.assertEqual(mevt_copy[:], [(0, ('Gy',), 1), - # (1, ('Gy',), None), - # (None, ('rho0', 'Gx',), 0), - # (None, ('rho0', 'Gy', 'Gy'), None)]) - # self.assertEqual(mevt_copy.cache_size(),2) - # self.assertEqual(mevt_copy.evaluation_order(),[2, 0, 1, 3]) - # self.assertEqual(mevt_copy.num_final_circuits(),4) - # - # ## SQUEEZE - # maxCacheSize = 1 - # mevt_squeeze = mevt.copy() - # mevt_squeeze.squeeze(maxCacheSize) - # print("Squeezed Tree = ",mevt_squeeze) - # print("Cache size = ",mevt_squeeze.cache_size()) - # print("Eval order = ",mevt_squeeze.evaluation_order()) - # print("Num final = ",mevt_squeeze.num_final_circuits()) - # print() - # - # self.assertEqual(mevt_squeeze[:], [(0, ('Gy',), None), - # (0, ('Gy','Gy'), None), - # (None, ('rho0', 'Gx',), 0), - # (None, ('rho0', 'Gy', 'Gy'), None)]) - # - # self.assertEqual(mevt_squeeze.cache_size(),maxCacheSize) - # self.assertEqual(mevt_squeeze.evaluation_order(),[2, 0, 1, 3]) - # self.assertEqual(mevt_squeeze.num_final_circuits(),4) - # - # #SPLIT - # mevt_split = mevt.copy() - # mlookup_splt = mevt_split.split(mlookup,num_sub_trees=4) - # print("Split tree = ",mevt_split) - # print("new lookup = ",mlookup_splt) - # print() - # - # self.assertEqual(mevt_split[:], [(None, ('rho0', 'Gx',), 0), - # (0, ('Gy',), 1), - # (1, ('Gy',), None), - # (None, ('rho0', 'Gy', 'Gy'), None)]) - # self.assertEqual(mevt_split.cache_size(),2) - # self.assertEqual(mevt_split.evaluation_order(),[0, 1, 2, 3]) - # self.assertEqual(mevt_split.num_final_circuits(),4) - # - # - # subtrees = mevt_split.sub_trees() - # print("%d subtrees" % len(subtrees)) - # self.assertEqual(len(subtrees),4) - # for i,subtree in enumerate(subtrees): - # print("Sub tree %d = " % i,subtree, - # " csize = ",subtree.cache_size(), - # " eval = ",subtree.evaluation_order(), - # " nfinal = ",subtree.num_final_circuits()) - # self.assertEqual(subtree.cache_size(),0) - # self.assertEqual(subtree.evaluation_order(),[0]) - # self.assertEqual(subtree.num_final_circuits(),1) - # - # probs = np.zeros( mevt.num_final_elements(), 'd') - # mgateset.bulk_fill_probs(probs, mevt) - # print("probs = ",probs) - # print("lookup = ",mlookup) - # self.assertArraysAlmostEqual(probs, np.array([ 0.9267767,0.0732233,0.82664074, - # 0.17335926,0.96193977,0.03806023, - # 0.85355339,0.14644661],'d')) - # - # - # squeezed_probs = np.zeros( mevt_squeeze.num_final_elements(), 'd') - # mgateset.bulk_fill_probs(squeezed_probs, mevt_squeeze) - # print("squeezed probs = ",squeezed_probs) - # print("lookup = ",mlookup) - # self.assertArraysAlmostEqual(probs, squeezed_probs) - # - # split_probs = np.zeros( mevt_split.num_final_elements(), 'd') - # mgateset.bulk_fill_probs(split_probs, mevt_split) - # print("split probs = ",split_probs) - # print("lookup = ",mlookup_splt) - # for i in range(4): #then number of original strings (num final strings) - # self.assertArraysAlmostEqual(probs[mlookup[i]], split_probs[mlookup_splt[i]]) - + self._test_layout(pygsti.layouts.matrixlayout.MatrixCOPALayout(self.circuits[:], self.model_matrix)) if __name__ == '__main__': unittest.main(verbosity=2) diff --git a/test/test_packages/objects/test_gatesets.py b/test/test_packages/objects/test_gatesets.py index b0ea5409a..9b8b1d223 100644 --- a/test/test_packages/objects/test_gatesets.py +++ b/test/test_packages/objects/test_gatesets.py @@ -41,19 +41,19 @@ def setUp(self): self.model = pygsti.models.modelconstruction.create_explicit_model_from_expressions( [('Q0',)],['Gi','Gx','Gy'], [ "I(Q0)","X(pi/8,Q0)", "Y(pi/8,Q0)"]) - + self.model.sim = 'matrix' self.tp_gateset = pygsti.models.modelconstruction.create_explicit_model_from_expressions( [('Q0',)],['Gi','Gx','Gy'], [ "I(Q0)","X(pi/8,Q0)", "Y(pi/8,Q0)"], gate_type="full TP") - + self.tp_gateset.sim = 'matrix' self.static_gateset = pygsti.models.modelconstruction.create_explicit_model_from_expressions( [('Q0',)],['Gi','Gx','Gy'], [ "I(Q0)","X(pi/8,Q0)", "Y(pi/8,Q0)"], gate_type="static") + self.static_gateset.sim = 'matrix' self.mgateset = self.model.copy() - #self.mgateset._calcClass = MapForwardSimulator self.mgateset.sim = 'map' @@ -288,62 +288,6 @@ def Split(self, color, key): return self except MemoryError: pass #OK - when memlimit is too small and splitting is unproductive - #balanced not implemented - #with self.assertRaises(NotImplementedError): - # evt,_,_,lookup,outcome_lookup = self.model.bulk_evaltree_from_resources( - # circuits, mem_limit=memLimit, distribute_method="balanced", subcalls=['bulk_fill_hprobs']) - - - @unittest.skip("Need to add a way to force layout splitting") - def test_layout_splitting(self): - circuits = [('Gx',), - ('Gy',), - ('Gx','Gy'), - ('Gy','Gy'), - ('Gy','Gx'), - ('Gx','Gx','Gx'), - ('Gx','Gy','Gx'), - ('Gx','Gy','Gy'), - ('Gy','Gy','Gy'), - ('Gy','Gx','Gx') ] - evtA,lookupA,outcome_lookupA = self.model.bulk_evaltree( circuits ) - - evtB,lookupB,outcome_lookupB = self.model.bulk_evaltree( circuits ) - lookupB = evtB.split(lookupB, max_sub_tree_size=4) - - evtC,lookupC,outcome_lookupC = self.model.bulk_evaltree( circuits ) - lookupC = evtC.split(lookupC, num_sub_trees=3) - - with self.assertRaises(ValueError): - evtBad,lkup,_ = self.model.bulk_evaltree( circuits ) - evtBad.split(lkup, num_sub_trees=3, max_sub_tree_size=4) #can't specify both - - self.assertFalse(evtA.is_split()) - self.assertTrue(evtB.is_split()) - self.assertTrue(evtC.is_split()) - self.assertEqual(len(evtA.sub_trees()), 1) - self.assertEqual(len(evtB.sub_trees()), 5) #empirically - self.assertEqual(len(evtC.sub_trees()), 3) - self.assertLessEqual(max([len(subTree) - for subTree in evtB.sub_trees()]), 4) - - #print "Lenghts = ",len(evtA.sub_trees()),len(evtB.sub_trees()),len(evtC.sub_trees()) - #print "SubTree sizes = ",[len(subTree) for subTree in evtC.sub_trees()] - - bulk_probsA = np.empty( evtA.num_final_elements(), 'd') - bulk_probsB = np.empty( evtB.num_final_elements(), 'd') - bulk_probsC = np.empty( evtC.num_final_elements(), 'd') - self.model.bulk_fill_probs(bulk_probsA, evtA) - self.model.bulk_fill_probs(bulk_probsB, evtB) - self.model.bulk_fill_probs(bulk_probsC, evtC) - - for i,opstr in enumerate(circuits): - self.assertArraysAlmostEqual(bulk_probsA[ lookupA[i] ], - bulk_probsB[ lookupB[i] ]) - self.assertArraysAlmostEqual(bulk_probsA[ lookupA[i] ], - bulk_probsC[ lookupC[i] ]) - - @unittest.skip("TODO: add backward compatibility for old gatesets?") def test_load_old_gateset(self): #pygsti.baseobjs.results.enable_old_python_results_unpickling() @@ -388,16 +332,18 @@ def test_ondemand_probabilities(self): self.assertEqual(ds[()]['2'], 0) # but we can query '2' since it's a valid outcome label gstrs = list(ds.keys()) - layout = std1Q_XYI.target_model().sim.create_layout(gstrs, dataset=ds) + model = std1Q_XYI.target_model() + model.sim = 'map' + layout = model.sim.create_layout(gstrs, dataset=ds) self.assertEqual(layout.outcomes(()), (('1',),) ) - self.assertEqual(layout.outcomes(('Gx',)), (('1',), ('0',)) ) # '1' comes first because it's the first outcome to appear - self.assertEqual(layout.outcomes(('Gx','Gy')), (('1',), ('0',)) ) + self.assertTrue(layout.outcomes(('Gx',))==(('1',), ('0',)) or layout.outcomes(('Gx',))==(('0',), ('1',))) + self.assertTrue(layout.outcomes(('Gx','Gy'))==(('1',), ('0',)) or layout.outcomes(('Gx','Gy'))==(('0',), ('1',))) self.assertEqual(layout.outcomes(('Gx',)*4), (('0',),) ) - self.assertEqual(layout.indices(()), slice(0, 1, None)) - self.assertArraysEqual(layout.indices(('Gx',)), [1,3] ) - self.assertArraysEqual(layout.indices(('Gx','Gy')), [2,4] ) + self.assertEqual(layout.indices(()), slice(0, 1, None)) + self.assertEqual(layout.indices(('Gx',)), slice(1, 3, None)) + self.assertEqual(layout.indices(('Gx','Gy')), slice(3, 5, None)) self.assertEqual(layout.indices(('Gx',)*4), slice(5, 6, None)) self.assertEqual(layout.num_elements, 6) diff --git a/test/test_packages/objects/test_instruments.py b/test/test_packages/objects/test_instruments.py index f1ab412c4..b3f201fcc 100644 --- a/test/test_packages/objects/test_instruments.py +++ b/test/test_packages/objects/test_instruments.py @@ -14,6 +14,7 @@ class InstrumentTestCase(BaseTestCase): def setUp(self): #Add an instrument to the standard target model self.target_model = std.target_model() + self.target_model.sim = 'matrix' E = self.target_model.povms['Mdefault']['0'] Erem = self.target_model.povms['Mdefault']['1'] Gmz_plus = np.dot(E,E.T) @@ -174,17 +175,12 @@ def testBasicGatesetOps(self): model = pygsti.models.modelconstruction.create_explicit_model_from_expressions( [('Q0',)],['Gi','Gx','Gy'], [ "I(Q0)","X(pi/8,Q0)", "Y(pi/8,Q0)"]) - # prep_labels=["rho0"], prep_expressions=["0"], - # effect_labels=["0","1"], effect_expressions=["0","complement"]) + model.sim= 'matrix' v0 = modelconstruction.create_spam_vector("0", "Q0", "pp") v1 = modelconstruction.create_spam_vector("1", "Q0", "pp") P0 = np.dot(v0,v0.T) P1 = np.dot(v1,v1.T) - print("v0 = ",v0) - print("P0 = ",P0) - print("P1 = ",P0) - #print("P0+P1 = ",P0+P1) model.instruments["Itest"] = pygsti.modelmembers.instruments.Instrument([('0', P0), ('1', P1)]) diff --git a/test/unit/algorithms/fixtures.py b/test/unit/algorithms/fixtures.py index a262c52de..a62ce3771 100644 --- a/test/unit/algorithms/fixtures.py +++ b/test/unit/algorithms/fixtures.py @@ -6,8 +6,8 @@ from ..util import Namespace ns = Namespace() -ns.fullTP_model = std.target_model('full TP') -ns.model = std.target_model() +ns.fullTP_model = std.target_model('full TP', simulator='matrix') +ns.model = std.target_model(simulator='matrix') ns.opLabels = list(ns.model.operations.keys()) ns.prep_fids = std.prep_fiducials() ns.meas_fids = std.meas_fiducials() diff --git a/test/unit/construction/test_modelconstruction.py b/test/unit/construction/test_modelconstruction.py index 77f2717e8..cff5fad14 100644 --- a/test/unit/construction/test_modelconstruction.py +++ b/test/unit/construction/test_modelconstruction.py @@ -523,7 +523,7 @@ def test_spamvecs_in_processorspecs(self): prep_names=("rhoA", "rhoC"), povm_names=("Ma", "Mc"), nonstd_preps={'rhoA': "rho0", 'rhoC': prep_vec}, nonstd_povms={'Ma': {'0': "0000", '1': EA}, - 'Mc': {'OutA': "0000", 'OutB': [EA, EB]}}) + 'Mc': {'OutA': "E_0000", 'OutB': [EA, EB], 'OutC': 'E13'}}) pspec_vecs = save_and_load_pspec(pspec_vecs) # make sure serialization works too self.assertEqual(pspec_vecs.prep_names, ('rhoA', 'rhoC')) @@ -540,7 +540,7 @@ def test_spamvecs_in_processorspecs(self): self.assertArraysAlmostEqual(mdl_vecs.prep_blks['layers']['rhoC'].to_dense(), prep_supervec) self.assertEqual(list(mdl_vecs.povm_blks['layers']['Ma'].keys()), ['0', '1']) - self.assertEqual(list(mdl_vecs.povm_blks['layers']['Mc'].keys()), ['OutA', 'OutB']) + self.assertEqual(list(mdl_vecs.povm_blks['layers']['Mc'].keys()), ['OutA', 'OutB', 'OutC']) def Zeffect(index): v = np.zeros(2**4, complex); v[index] = 1.0 @@ -551,6 +551,7 @@ def Zeffect(index): self.assertArraysAlmostEqual(mdl_vecs.povm_blks['layers']['Mc']['OutA'].to_dense(), Zeffect(0)) self.assertArraysAlmostEqual(mdl_vecs.povm_blks['layers']['Mc']['OutB'].to_dense(), Zeffect(14) + Zeffect(15)) + self.assertArraysAlmostEqual(mdl_vecs.povm_blks['layers']['Mc']['OutC'].to_dense(), Zeffect(13)) def test_instruments_in_processorspecs(self): #Instruments diff --git a/test/unit/objects/test_forwardsim.py b/test/unit/objects/test_forwardsim.py index 5af9aa598..5c608baee 100644 --- a/test/unit/objects/test_forwardsim.py +++ b/test/unit/objects/test_forwardsim.py @@ -63,9 +63,12 @@ def setUpClass(cls): [('Q0',)], ['Gi', 'Gx', 'Gy'], ["I(Q0)", "X(pi/8,Q0)", "Y(pi/8,Q0)"] ) + cls.model_matrix = cls.model.copy() + cls.model_matrix.sim = 'matrix' def setUp(self): self.fwdsim = self.model.sim + self.fwdsim_matrix = self.model_matrix.sim self.layout = self.fwdsim.create_layout([('Gx',), ('Gx', 'Gx')], array_types=('e', 'ep', 'epp')) self.nP = self.model.num_params self.nEls = self.layout.num_elements @@ -116,13 +119,13 @@ def test_iter_hprobs_by_rectangle(self): class MatrixForwardSimTester(ForwardSimBase, BaseCase): def test_doperation(self): - dg = self.fwdsim._doperation(L('Gx'), flat=False) - dgflat = self.fwdsim._doperation(L('Gx'), flat=True) + dg = self.fwdsim_matrix._doperation(L('Gx'), flat=False) + dgflat = self.fwdsim_matrix._doperation(L('Gx'), flat=True) # TODO assert correctness def test_hoperation(self): - hg = self.fwdsim._hoperation(L('Gx'), flat=False) - hgflat = self.fwdsim._hoperation(L('Gx'), flat=True) + hg = self.fwdsim_matrix._hoperation(L('Gx'), flat=False) + hgflat = self.fwdsim_matrix._hoperation(L('Gx'), flat=True) # TODO assert correctness @@ -130,7 +133,7 @@ class CPTPMatrixForwardSimTester(MatrixForwardSimTester): @classmethod def setUpClass(cls): super(CPTPMatrixForwardSimTester, cls).setUpClass() - cls.model = cls.model.copy() + cls.model = cls.model_matrix.copy() cls.model.set_all_parameterizations("CPTPLND") # so gates have nonzero hessians diff --git a/test/unit/objects/test_model.py b/test/unit/objects/test_model.py index fdad0a22c..ac67312bf 100644 --- a/test/unit/objects/test_model.py +++ b/test/unit/objects/test_model.py @@ -55,6 +55,7 @@ def setUpClass(cls): def setUp(self): self.model = self._model.copy() + self.model.sim = 'matrix' super(ModelBase, self).setUp() def test_construction(self): @@ -462,26 +463,6 @@ def test_bulk_fill_probs(self): self.assertAlmostEqual(1 - expected_1, actual_1[1-zero_outcome_index1]) self.assertAlmostEqual(1 - expected_2, actual_2[1-zero_outcome_index2]) - def test_bulk_fill_probs_with_split_tree(self): - self.skipTest("Need a way to manually create 'split' layouts") - # XXX is this correct? EGN: looks right to me. - evt, lookup, _ = self.model.bulk_evaltree([self.gatestring1, self.gatestring2]) - nElements = evt.num_final_elements() - probs_to_fill = np.empty(nElements, 'd') - lookup_split = evt.split(lookup, num_sub_trees=2) - - with self.assertNoWarns(): - self.model.bulk_fill_probs(probs_to_fill, evt) - - expected_1 = self._expected_probs[self.gatestring1] - expected_2 = self._expected_probs[self.gatestring2] - actual_1 = probs_to_fill[lookup_split[0]] - actual_2 = probs_to_fill[lookup_split[1]] - self.assertAlmostEqual(expected_1, actual_1[0]) - self.assertAlmostEqual(expected_2, actual_2[0]) - self.assertAlmostEqual(1 - expected_1, actual_1[1]) - self.assertAlmostEqual(1 - expected_2, actual_2[1]) - def test_bulk_dprobs(self): with self.assertNoWarns(): bulk_dprobs = self.model.sim.bulk_dprobs([self.gatestring1, self.gatestring2]) @@ -518,17 +499,6 @@ def test_bulk_fill_dprobs_with_high_smallness_threshold(self): self.model.sim.bulk_fill_dprobs(dprobs_to_fill, layout) # TODO assert correctness - def test_bulk_fill_dprobs_with_split_tree(self): - self.skipTest("Need a way to manually create 'split' layouts") - evt, lookup, _ = self.model.bulk_evaltree([self.gatestring1, self.gatestring2]) - nElements = evt.num_final_elements() - nParams = self.model.num_params - dprobs_to_fill = np.empty((nElements, nParams), 'd') - lookup_split = evt.split(lookup, num_sub_trees=2) - with self.assertNoWarns(): - self.model.bulk_fill_dprobs(dprobs_to_fill, evt) - # TODO assert correctness - def test_bulk_hprobs(self): # call normally #with self.assertNoWarns(): # - now *can* warn about inefficient evaltree (ok) @@ -581,17 +551,6 @@ def test_bulk_fill_hprobs_with_high_smallness_threshold(self): self.model.sim.bulk_fill_hprobs(hprobs_to_fill, layout) # TODO assert correctness - def test_bulk_fill_hprobs_with_split_tree(self): - self.skipTest("Need a way to manually create 'split' layouts") - evt, lookup, _ = self.model.bulk_evaltree([self.gatestring1, self.gatestring2]) - nElements = evt.num_final_elements() - nParams = self.model.num_params - hprobs_to_fill = np.empty((nElements, nParams, nParams), 'd') - lookup_split = evt.split(lookup, num_sub_trees=2) - #with self.assertNoWarns(): # - now *can* warn about inefficient evaltree (ok) - self.model.bulk_fill_hprobs(hprobs_to_fill, evt) - # TODO assert correctness - def test_iter_hprobs_by_rectangle(self): layout = self.model.sim.create_layout([self.gatestring1, self.gatestring2], array_types=('epp',)) nP = self.model.num_params @@ -607,7 +566,7 @@ def test_iter_hprobs_by_rectangle(self): all_d12cols = np.concatenate(d12cols, axis=2) # TODO assert correctness - def test_bulk_evaltree(self): + def test_layout_construction(self): # Test tree construction circuits = pc.to_circuits( [('Gx',), @@ -623,14 +582,6 @@ def test_bulk_evaltree(self): layout = self.model.sim.create_layout(circuits) - #TODO: test different forced splittings, once this is possible to do... - #evt, lookup, outcome_lookup = self.model.bulk_evaltree(circuits, max_tree_size=4) - #evt, lookup, outcome_lookup = self.model.bulk_evaltree(circuits, min_subtrees=2, max_tree_size=4) - #with self.assertWarns(Warning): - # self.model.bulk_evaltree(circuits, min_subtrees=3, max_tree_size=8) - # #balanced to trigger 2 re-splits! (Warning: could not create a tree ...) - - class StandardMethodBase(GeneralMethodBase, SimMethodBase, ThresholdMethodBase): pass @@ -721,7 +672,7 @@ def setUp(self): super(FullMapSimMethodTester, self).setUp() self.model.sim = mapforwardsim.MapForwardSimulator(self.model) - def test_bulk_evaltree(self): + def test_layout_construction(self): # Test tree construction circuits = pc.to_circuits( [('Gx',), @@ -737,14 +688,6 @@ def test_bulk_evaltree(self): layout = self.model.sim.create_layout(circuits) - #TODO: test different forced splittings, once this is possible to do... - #evt, lookup, outcome_lookup = self.model.bulk_evaltree(circuits, max_tree_size=4) - #evt, lookup, outcome_lookup = self.model.bulk_evaltree(circuits, min_subtrees=2, max_tree_size=4) - #with self.assertNoWarns(): - # self.model.bulk_evaltree(circuits, min_subtrees=3, max_tree_size=8) - # #balanced to trigger 2 re-splits! (Warning: could not create a tree ...) - - class FullHighThresholdMethodTester(FullModelBase, ThresholdMethodBase, BaseCase): def setUp(self): super(FullHighThresholdMethodTester, self).setUp() diff --git a/test/unit/objects/test_objectivefns.py b/test/unit/objects/test_objectivefns.py index b7116f75e..6e2e5549c 100644 --- a/test/unit/objects/test_objectivefns.py +++ b/test/unit/objects/test_objectivefns.py @@ -15,6 +15,7 @@ class ObjectiveFunctionData(object): def setUp(self): self.model = smqfixtures.ns.datagen_model.copy() + self.model.sim = 'matrix' self.circuits = smqfixtures.ns.circuits self.dataset = smqfixtures.ns.dataset.copy() self.sparse_dataset = smqfixtures.ns.sparse_dataset.copy()