diff --git a/pygsti/modelmembers/instruments/mcminstrument.py b/pygsti/modelmembers/instruments/composedinstrument.py similarity index 88% rename from pygsti/modelmembers/instruments/mcminstrument.py rename to pygsti/modelmembers/instruments/composedinstrument.py index 2e5dbc661..cd2e02db8 100644 --- a/pygsti/modelmembers/instruments/mcminstrument.py +++ b/pygsti/modelmembers/instruments/composedinstrument.py @@ -13,7 +13,7 @@ import collections as _collections import numpy as _np from pygsti.modelmembers import modelmember as _mm -from pygsti.modelmembers.instruments.mcminstrumentop import MCMInstrumentOp as _MCMInstrumentOp +from pygsti.modelmembers.instruments.composedinstrumentop import ComposedInstrumentOp as _ComposedInstrumentOp from pygsti.modelmembers import operations as _op @@ -22,7 +22,7 @@ from pygsti.baseobjs.label import Label as _Label from pygsti.tools import optools as _ot -class MCMInstrument(_mm.ModelMember, _collections.OrderedDict): +class ComposedInstrument(_mm.ModelMember, _collections.OrderedDict): """ A new class for representing a quantum instrument, the mathematical description of an intermediate measurement. This class relies on the "auxiliary picture" where @@ -51,7 +51,7 @@ class MCMInstrument(_mm.ModelMember, _collections.OrderedDict): The parameterization for this instrument, either "CPTPLND" or "GLND" (full TP). """ - def __init__(self, evotype="default", state_space=None, items=None, parameterization="CPTPLND"): + def __init__(self, evotype="default", state_space=None, items=None, type="MCM", parameterization="CPTPLND"): self.parameterization = parameterization #Calculate some necessary matrices to define the isometries below @@ -59,12 +59,18 @@ def __init__(self, evotype="default", state_space=None, items=None, parameteriza one = 1/_np.sqrt(2) * _np.array([[1],[0],[0],[-1]]) CNOT = _ot.unitary_to_pauligate(_np.array([[1,0,0,0], [0,1,0,0],[0,0,0,1],[0,0,1,0]])) - dim = 4 - self.aux_GATES = CNOT - + if type == "MCM": + dim = 4 + self.aux_GATES = CNOT + elif type == "PC": + dim = 16 + self.aux_GATES = _np.kron(CNOT, _np.identity(4)) @ _np.kron(_np.identity(4), CNOT) @ _np.kron(CNOT, _np.identity(4)) + else: + raise NotImplementedError("Only parity check and MCM implemented currently.") + #Define the error generator as the all ones string if parameterization == "TP Map": - self.noise_map = _op.FullTPOp(_np.identity(16), basis='pp') + self.noise_map = _op.FullTPOp(_np.identity(dim*4), basis='pp') else: error_gen = _op.LindbladErrorgen.from_error_generator(_np.zeros((dim*4,dim*4)), parameterization=self.parameterization) self.noise_map = _op.ExpErrorgenOp(error_gen) @@ -79,7 +85,7 @@ def __init__(self, evotype="default", state_space=None, items=None, parameteriza #Create the ordered dictionary structure characteristic of instruments items = [] for i in range(2): - items.append([f'p{i}', _MCMInstrumentOp(self.noise_map, i, self.right_isometry, self.left_isometry, 'pp')]) + items.append([f'p{i}', _ComposedInstrumentOp(self.noise_map, i, self.right_isometry, self.left_isometry, 'pp')]) #some necessary initialization _collections.OrderedDict.__init__(self, items) @@ -124,7 +130,7 @@ def from_vector(self, v, close=False, dirty_value=True): """ self.noise_map.from_vector(v) for i in range(2): - self[f'p{i}'] = _MCMInstrumentOp(self.noise_map, i, self.right_isometry, self.left_isometry, 'pp') + self[f'p{i}'] = _ComposedInstrumentOp(self.noise_map, i, self.right_isometry, self.left_isometry, 'pp') self.dirty = dirty_value def submembers(self): @@ -225,7 +231,7 @@ def transform_inplace(self,s): Si = s.transform_matrix_inverse self.noise_map = _op.FullTPOp(_np.kron(Si, _np.eye(4)) @ self.noise_map.to_dense() @ self.aux_GATES @ _np.kron(Smx, _np.eye(4)) @ self.aux_GATES) for i in range(2): - self[f'p{i}'] = _MCMInstrumentOp(self.noise_map, i, self.right_isometry, self.left_isometry, 'pp') + self[f'p{i}'] = _ComposedInstrumentOp(self.noise_map, i, self.right_isometry, self.left_isometry, 'pp') self.dirty = True def __str__(self): diff --git a/pygsti/modelmembers/instruments/mcminstrumentop.py b/pygsti/modelmembers/instruments/composedinstrumentop.py similarity index 93% rename from pygsti/modelmembers/instruments/mcminstrumentop.py rename to pygsti/modelmembers/instruments/composedinstrumentop.py index 20935d03d..763f298e0 100644 --- a/pygsti/modelmembers/instruments/mcminstrumentop.py +++ b/pygsti/modelmembers/instruments/composedinstrumentop.py @@ -16,7 +16,7 @@ from pygsti.baseobjs import statespace as _statespace -class MCMInstrumentOp(_DenseOperator): +class ComposedInstrumentOp(_DenseOperator): """ An element of a :class:`MCMInstrument`. @@ -51,9 +51,9 @@ class MCMInstrumentOp(_DenseOperator): """ def __init__(self, noise_map, index, right_isometry, left_isometry, basis=None): - dim = 4 self.index = index self.noise_map = noise_map + dim = int(self.noise_map.dim/4) self.op_right_iso = right_isometry self.op_left_iso = left_isometry[index] @@ -93,14 +93,14 @@ def deriv_wrt_params(self, wrt_filter=None): numpy array Array of derivatives with shape (dimension^2, num_params) """ - + map_dim = self.noise_map.dim noise_map_derivMx = self.noise_map.deriv_wrt_params() ptm_wrt_params = [] - for param_num in range(240): + for param_num in range(self.noise_map.num_params): ptm_noise_map_derivMx = [] - for matrix_el in range(256): + for matrix_el in range(map_dim*map_dim): ptm_noise_map_derivMx += [noise_map_derivMx[matrix_el][param_num]] - ptm_wrt_params += [list(_np.ravel(self.op_left_iso @ _np.reshape(ptm_noise_map_derivMx, (16,16)) @ self.op_right_iso))] + ptm_wrt_params += [list(_np.ravel(self.op_left_iso @ _np.reshape(ptm_noise_map_derivMx, (map_dim,map_dim)) @ self.op_right_iso))] derivMx = _np.array(ptm_wrt_params).transpose() if wrt_filter is None: return derivMx diff --git a/pygsti/modelmembers/instruments/paritycheckinst.py b/pygsti/modelmembers/instruments/paritycheckinst.py deleted file mode 100644 index 7f2f4bc41..000000000 --- a/pygsti/modelmembers/instruments/paritycheckinst.py +++ /dev/null @@ -1,222 +0,0 @@ -""" -Defines the ParityCheckInst class -""" -#*************************************************************************************************** -# Copyright 2015, 2019 National Technology & Engineering Solutions of Sandia, LLC (NTESS). -# Under the terms of Contract DE-NA0003525 with NTESS, the U.S. Government retains certain rights -# in this software. -# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except -# 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 as _collections -import numpy as _np -from pygsti.modelmembers import modelmember as _mm -from pygsti.modelmembers import operations as _op -from pygsti.baseobjs import statespace as _statespace -from pygsti.tools import matrixtools as _mt -from pygsti.baseobjs.label import Label as _Label -from pygsti.report import reportables as _rp -from pygsti.tools import optools as _ot - - -class ParityCheckInst(_mm.ModelMember, _collections.OrderedDict): - """ - A new class for representing a quantum instrument, the mathematical description - of an intermediate measurement. This class relies on the "auxiliary picture" where - a n-qubit m-outcome intermediate measurement corresponds to a circuit diagram with - n plus m auxiliary qubits. - - Using this picture allows us to extract error generators corresponding to the circuit - diagram and lays the ground work for a CPTPLND parameterization. - - Parameters - ---------- - member_ops : dict of LinearOperator objects - A dict (or list of key,value pairs) of the gates. - evotype : Evotype or str, optional - The evolution type. If `None`, the evotype is inferred - from the first instrument member. If `len(member_ops) == 0` in this case, - an error is raised. - - state_space : StateSpace, optional - The state space for this POVM. If `None`, the space is inferred - from the first instrument member. If `len(member_ops) == 0` in this case, - an error is raised. - - items : list or dict, optional - Initial values. This should only be used internally in de-serialization. - """ - - def __init__(self, evotype="default", state_space=None, items=None, parameterization="CPTPLND"): - self.parameterization = parameterization - - #Calculate some necessary matrices to define the isometries below - zero = 1/_np.sqrt(2) * _np.array([[1],[0],[0],[1]]) - one = 1/_np.sqrt(2) * _np.array([[1],[0],[0],[-1]]) - CNOT = _ot.unitary_to_pauligate(_np.array([[1,0,0,0], [0,1,0,0],[0,0,0,1],[0,0,1,0]])) - - dim = 16 - self.aux_GATES = _np.kron(CNOT, _np.identity(4)) @ _np.kron(_np.identity(4), CNOT) @ _np.kron(CNOT, _np.identity(4)) - - #Define the error generator as the all ones string - self.error_gen = _op.LindbladErrorgen.from_error_generator(_np.zeros((dim*4,dim*4)), parameterization=self.parameterization) - - if state_space is None: - state_space = _statespace.default_space_for_dim(dim) - - #Define the isometries that convert from auxiliary to standard picture - self.left_isometry = [_np.kron(_np.identity(dim), zero.T), _np.kron(_np.identity(dim), one.T)] - self.right_isometry = self.aux_GATES @ _np.kron(_np.identity(dim), zero) - - #Create the ordered dictionary structure characteristic of instruments - items = [] - self.noise_map = _op.ExpErrorgenOp(self.error_gen) - for i in range(2): - items.append([f'p{i}', _op.StaticArbitraryOp(self.left_isometry[i] @ self.noise_map.to_dense() @ self.right_isometry)]) - - #some necessary initialization - _collections.OrderedDict.__init__(self, items) - _mm.ModelMember.__init__(self, state_space, evotype) - self.init_gpindices() - self._paramlbls = self.error_gen._paramlbls - - def to_vector(self): - """ - Gives the underlying vector of parameters. - - Returns - ------- - numpy array - a 1D numpy array with length == num_params(). - """ - return self.error_gen.to_vector() - - def from_vector(self, v, close=False, dirty_value=True): - """ - Initialize the Instrument using a vector of its parameters. - - Parameters - ---------- - v : numpy array - The 1D vector of gate parameters. Length - must == num_params(). - - close : bool, optional - Whether `v` is close to this Instrument's current - set of parameters. Under some circumstances, when this - is true this call can be completed more quickly. - - dirty_value : bool, optional - The value to set this object's "dirty flag" to before exiting this - call. This is passed as an argument so it can be updated *recursively*. - Leave this set to `True` unless you know what you're doing. - - Returns - ------- - None - """ - self.error_gen.from_vector(v) - self.noise_map = _op.ExpErrorgenOp(self.error_gen) - for i in range(2): - self[f'p{i}'] = _op.StaticArbitraryOp(self.left_isometry[i] @ self.noise_map.to_dense() @ self.right_isometry) - self.dirty = dirty_value - - @property - def num_params(self): - """ - Get the number of independent parameters which specify this Instrument. - - Returns - ------- - int - the number of independent parameters. - """ - return self.error_gen.num_params - - def simplify_operations(self, prefix=""): - """ - Creates a dictionary of simplified instrument operations. - - Returns a dictionary of operations that belong to the Instrument's parent - `Model` - that is, whose `gpindices` are set to all or a subset of - this instruments's gpindices. These are used internally within - computations involving the parent `Model`. - - Parameters - ---------- - prefix : str - A string, usually identitying this instrument, which may be used - to prefix the simplified gate keys. - - Returns - ------- - OrderedDict of Gates - """ - #Create a "simplified" (Model-referencing) set of element gates - simplified = _collections.OrderedDict() - if isinstance(prefix, _Label): # Deal with case when prefix isn't just a string - for k, g in self.items(): - simplified[_Label(prefix.name + "_" + k, prefix.sslbls)] = g - else: - if prefix: prefix += "_" - for k, g in self.items(): - simplified[prefix + k] = g - return simplified - - @property - def parameter_labels(self): - """ - Get the labels of the independent parameters which specify this instrument. - - Returns - ------- - array - array of parameter labels - """ - return self.error_gen.parameter_labels - - @property - def num_elements(self): - """ - Return the number of total gate elements in this instrument. - - This is in general different from the number of *parameters*, - which are the number of free variables used to generate all of - the matrix *elements*. - - Returns - ------- - int - """ - return sum([g.size for g in self.values()]) - - def transform_inplace(self,s): - """ - Update each Instrument element matrix `O` with `inv(s) * O * s` - and update the error generator/noise map appropriately. - - Parameters - ---------- - s : GaugeGroupElement - A gauge group element which specifies the "s" matrix - (and it's inverse) used in the above similarity transform. - - Returns - ------- - None - """ - Smx = s.transform_matrix - Si = s.transform_matrix_inverse - self.noise_map = _op.FullTPOp(_np.kron(Si, _np.eye(4)) @ self.noise_map.to_dense() @ self.aux_GATES @ _np.kron(Smx, _np.eye(4)) @ self.aux_GATES) - self.error_gen = _op.LindbladErrorgen.from_operation_matrix(self.noise_map, parameterization=self.parameterization) - for i in range(2): - self[f'p{i}'] = _op.StaticArbitraryOp(self.left_isometry[i] @ self.noise_map.to_dense() @ self.right_isometry) - self.dirty = True - - def __str__(self): - s = f'ParityCheckInstrument representing a two-qubit parity check with elements:\n' - for lbl, element in self.items(): - s += "%s:\n%s\n" % (lbl, _mt.mx_to_string(element.to_dense(), width=4, prec=3)) - return s \ No newline at end of file