r"""
Contains the classes for protocols.
.. currentmodule:: quanguru.classes.QPro
.. autosummary::
genericProtocol
qProtocol
copyStep
freeEvolution
Gate
Update
.. |c| unicode:: U+2705
.. |x| unicode:: U+274C
.. |w| unicode:: U+2000
======================= ================== ================ ===============
**Function Name** **Docstrings** **Unit Tests** **Tutorials**
======================= ================== ================ ===============
`genericProtocol` |w| |w| |w| |x| |w| |w| |x| |w| |w| |x|
`qProtocol` |w| |w| |w| |x| |w| |w| |x| |w| |w| |x|
`copyStep` |w| |w| |w| |x| |w| |w| |x| |w| |w| |x|
`freeEvolution` |w| |w| |w| |x| |w| |w| |x| |w| |w| |x|
`Gate` |w| |w| |w| |x| |w| |w| |x| |w| |w| |x|
`Update` |w| |w| |w| |x| |w| |w| |x| |w| |w| |x|
======================= ================== ================ ===============
"""
from ..QuantumToolbox import evolution as lio #pylint: disable=relative-beyond-top-level
from ..QuantumToolbox.operators import identity #pylint: disable=relative-beyond-top-level
from .base import qBase, addDecorator
from .baseClasses import updateBase
from .QSimBase import _parameter
from .QSimComp import QSimComp
from .QSweep import Sweep
[docs]class genericProtocol(QSimComp): # pylint: disable = too-many-instance-attributes
label = 'genericProtocol'
#: (**class attribute**) number of instances created internally by the library
_internalInstances: int = 0
#: (**class attribute**) number of instances created explicitly by the user
_externalInstances: int = 0
#: (**class attribute**) number of total instances = _internalInstances + _externalInstances
_instances: int = 0
#: (**class attribute**) to store number of exponentiations, incremented by _increaseExponentiationCount method
numberOfExponentiations = 0
[docs] @classmethod
def _increaseExponentiationCount(cls):
r"""
This is a classmethod (used internally) to increment the `numberOfExponentiations` count.
"""
cls.numberOfExponentiations += 1
return cls.numberOfExponentiations
__slots__ = ['__currentState', '__inProtocol', '__fixed', '__ratio', '__updates', '__dissipator', '_openSys',
'_getUnitary', 'timeDependency', '__identity', 'sampleStates', 'stepSample']
def __init__(self, **kwargs):
super().__init__(_internal=kwargs.pop('_internal', False))
#: during time evolution, the current state of the protocol at the current time is stored in this attribute
#: it is an instance of `_parameter` so that the nested-protocols can refer to the same state that is store
#: by the outer-most protocol.
self.__currentState = _parameter()
#: stores an identity matrix with the total dimension of the system of the protocol.
#: this is used internally and introduces slight performance enhancement.
self.__identity = None
#: some steps might be used more than once in the same protocol or in different protocol/s. with this boolean,
#: it is determined if self is already a step in a protocol, so that a `copyStep` (of self) is used when self is
#: used again as a step in the same protocol or in different protocol/s.
self.__inProtocol = False
#: when parameters of a step are going to be fixed during the whole simulation (including parameter sweeps),
#: we can label it as fixed to optimize number of exponentiations.
self.__fixed = False
#: ratio of the stepSize of self to the stepSize of simulation. Used for higher order Trotterisations, so that
#: there is only one step size to be swept and the relative step sizes are determined by this ratio, which
#: might also be negative as required by higher (than 2) Trotterisation orders.
self.__ratio = 1
#: stores a list of updates for this protocol/step.
self.__updates = []
#: when constructing the unitary evolution of a protocol, certain parts of this creation might be replaced by
#: other methods depending on the solution method we used. this attribute stores the `_getUnitary`, which is
#: part of getUnitary method that (uses createUnitary and) returns the unitary matrix.
# TODO docs - Create a tutorial explaning _getUnitary.
self._getUnitary = self._defGetUnitary
#: when we use compute function with delStates, we might want to sample several number of time-steps first, then
#: call the compute, which might provide performance benefits when using other solution methods than matrix
#: exponentiation. This list is used for this purpose (and also relies on samples number of self.simulation).
# TODO requires a tutorial and NOTE not completely decided on every detail of how to use this.
self.sampleStates = []
#: boolean to determine if a single state or a list of states is samples (related to sampleStates)
self.stepSample = False
#: This is an instance of sweep class, but it is called at every time-step, so that we can change a parameter
#: as a function of time.
# TODO new approach of _timeDependency method in QSystem is much more flexible and
# intuitive than this, and I will more that approach to QSimComp class to make it also available here.
self.timeDependency = Sweep(superSys=self)
#: a dictionary to store the dissipator objects used in open-system simulations
self.__dissipator = {}
#: boolean to determine if it is an open-system simulation.
self._openSys = False
self._named__setKwargs(**kwargs) # pylint: disable=no-member
@property
def hc(self):
return copyStep(self, hc=True)
@property
def dimension(self):
return 1
@property
def _dissipator(self):
return self._genericProtocol__dissipator
@property
def _isOpen(self):
subOpen = any((s._isOpen for s in self.subSys.values() if hasattr(s, '_isOpen'))) # pylint: disable=protected-access
self._openSys = len(self._dissipator) > 0 or subOpen or self._openSys
for s in self.subSys.values():
if hasattr(s, '_openSys'):
s._openSys = self._openSys # pylint: disable=protected-access
return self._openSys
@property
def currentState(self):
return self._genericProtocol__currentState.value
@currentState.setter
def currentState(self, inp):
self._genericProtocol__currentState.value = inp
@QSimComp.initialState.setter # pylint: disable=no-member
def initialState(self, inp):
self.simulation._initialStateInput = inp # pylint: disable=protected-access
self.simulation._stateBase__initialState.value = self.superSys._createState(inp) # pylint:disable=W0212,E1101
[docs] def createUpdate(self, **kwargs):
update = Update(**kwargs)
self.addUpdate(update)
return update
[docs] def addUpdate(self, *args):
for update in args:
self._genericProtocol__updates.append(update) # pylint: disable=no-member
@property
def updates(self):
return self._genericProtocol__updates
@property
def ratio(self):
return self._genericProtocol__ratio
@ratio.setter
def ratio(self, val):
self._genericProtocol__ratio = val # pylint: disable=assigning-non-slot
@property
def system(self):
return self.superSys
@system.setter
def system(self, supSys):
self.superSys = supSys # pylint: disable=no-member
[docs] def prepare(self, collapseOps = None, decayRates = None):
if self.fixed is True:
self.getUnitary(collapseOps, decayRates)
for step in self.subSys.values():
if isinstance(step, genericProtocol):
step.prepare(collapseOps, decayRates)
@property
def fixed(self):
return self._genericProtocol__fixed
@fixed.setter
def fixed(self, boolean):
self._genericProtocol__fixed = boolean # pylint: disable=assigning-non-slot
@QSimComp.superSys.setter # pylint: disable=no-member
def superSys(self, supSys):
QSimComp.superSys.fset(self, supSys) # pylint: disable=no-member
supSys._paramBoundBase__paramBound[self.name] = self # pylint: disable=protected-access
if self.simulation._timeBase__bound is None:
self.simulation._bound(supSys.simulation) # pylint: disable=protected-access
self.simulation._qBase__subSys[self] = self.superSys # pylint: disable=protected-access
[docs] def unitary(self):
collapseOps = None if not self._isOpen else [ds.jOperMatrix for ds in self._dissipator.keys()]
decayRates = None if not self._isOpen else list(self._dissipator.values())
if self.superSys is not None:
self.superSys._timeDependency() # pylint: disable=no-member
if self._paramUpdated:
if not self.fixed:
self._paramBoundBase__matrix = self.getUnitary(collapseOps, decayRates) # pylint: disable=assigning-non-slot
elif self._paramBoundBase__matrix is None: # pylint: disable=no-member
self._paramBoundBase__matrix = self.getUnitary(collapseOps, decayRates) # pylint: disable=assigning-non-slot
return self._paramBoundBase__matrix # pylint: disable=no-member
[docs] def getUnitary(self, collapseOps = None, decayRates = None):
if collapseOps is None:
collapseOps = None if not self._isOpen else [ds.jOperMatrix for ds in self._dissipator.keys()]
decayRates = None if not self._isOpen else list(self._dissipator.values())
else:
collapseOps = collapseOps + [ds.jOperMatrix for ds in self._dissipator.keys()]
decayRates = decayRates + list(self._dissipator.values())
if self.superSys is not None:
self.superSys._timeDependency() # pylint: disable=no-member
for update in self._genericProtocol__updates:
update.setup()
self._paramBoundBase__matrix = self._getUnitary(collapseOps, decayRates) # pylint: disable=no-member, assigning-non-slot
for update in self._genericProtocol__updates:
update.setback()
self._paramUpdatedToFalse()
return self._paramBoundBase__matrix # pylint: disable=no-member
[docs] def _paramUpdatedToFalse(self):
self._paramBoundBase__paramUpdated = False # pylint: disable=assigning-non-slot
[docs] def _defGetUnitary(self, collapseOps = None, decayRates = None):
runCreate = False
if self._paramUpdated:
if not self.fixed:
runCreate = True
elif self._paramBoundBase__matrix is None: # pylint: disable=no-member
runCreate = True
if runCreate:
lc = 1
td = False
if len(self.timeDependency.sweeps) > 0:
lc = self.timeDependency.indMultip
td = True
unitary = self._identity(openSys=self._isOpen)
for ind in range(lc):
if td:
self.timeDependency.runSweep(self.timeDependency._indicesForSweep(ind, *self.timeDependency.inds))
unitary = self._createUnitary(collapseOps, decayRates) @ unitary # pylint: disable=no-member
self._paramBoundBase__matrix = unitary # pylint: disable=assigning-non-slot
return self._paramBoundBase__matrix # pylint: disable=no-member
[docs] def _identity(self, openSys=False):
dimension = 0
if self.superSys is None:
dimension = list(self.subSys.values())[0]._totalDim#pylint:disable=E0237,E1101
dimension = self.superSys._totalDim if (dimension == 0) else dimension # pylint: disable=E0237, E1101
#elif self._genericProtocol__identity is None:
# dimension = self.superSys._totalDim # pylint: disable=E0237, E1101
#elif self._genericProtocol__identity.shape[0] != self.superSys._totalDim: # pylint: disable=E1101
# dimension = self.superSys._totalDim # pylint: disable=E0237, E1101
self._genericProtocol__identity = identity(dimension = dimension**2 if openSys else dimension)# pylint: disable=E0237, E1101
return self._genericProtocol__identity
[docs]class qProtocol(genericProtocol):
label = 'qProtocol'
#: (**class attribute**) number of instances created internally by the library
_internalInstances: int = 0
#: (**class attribute**) number of instances created explicitly by the user
_externalInstances: int = 0
#: (**class attribute**) number of total instances = _internalInstances + _externalInstances
_instances: int = 0
__slots__ = []
def __init__(self, **kwargs):
super().__init__(_internal=kwargs.pop('_internal', False))
self._named__setKwargs(**kwargs) # pylint: disable=no-member
[docs] def _paramUpdatedToFalse(self):
super()._paramUpdatedToFalse()
for step in self.subSys.values():
step._paramUpdatedToFalse()
@genericProtocol._paramUpdated.getter
def _paramUpdated(self):# pylint: disable=invalid-overridden-method
for subS in self.subSys.values():
if subS._paramUpdated:
self._paramBoundBase__paramUpdated = True # pylint: disable=assigning-non-slot
break
return self._paramBoundBase__paramUpdated # pylint: disable=no-member
@property
def steps(self):
return self._qBase__subSys # pylint: disable=no-member
@steps.setter
def steps(self, stps):
self.addStep(*stps)
[docs] def addSubSys(self, subSys, **kwargs):
return self.addStep(subSys, **kwargs)
[docs] def addStep(self, *args):
'''
Copy step ensures the exponentiation
'''
for step in args:
if isinstance(step, copyStep):
super().addSubSys(step)
elif step._genericProtocol__inProtocol:
super().addSubSys(copyStep(step))
else:
super().addSubSys(step)
step._genericProtocol__inProtocol = True
step._genericProtocol__currentState._bound = self._genericProtocol__currentState #pylint:disable=W0212,E1101
step.simulation._bound(self.simulation, re=True) # pylint: disable=protected-access
if ((step.superSys is None) and (self.superSys is not None)):
step.superSys = self.superSys
[docs] def _puValues(self, step, vals):#pylint:disable=dangerous-default-value
subSysList = list(self.subSys.values())
ind = subSysList.index(step) + 1
if len(vals) == 0:
for st in subSysList[ind:]:
vals.append(st._paramUpdated)
else:
for ind2, st in enumerate(subSysList[ind:]):
st._paramUpdated = vals[ind2]
return vals
[docs] def _defCreateUnitary(self, collapseOps = None, decayRates = None):
unitary = self._identity(openSys=self._isOpen) # pylint: disable=no-member
for step in self.steps.values():
vals = self._puValues(step, [])
unitary = step.getUnitary(collapseOps, decayRates) @ unitary
self._puValues(step, vals)
return unitary
qProtocol._createUnitary = qProtocol._defCreateUnitary
[docs]class copyStep(qBase):
label = 'copyStep'
#: (**class attribute**) number of instances created internally by the library
_internalInstances: int = 0
#: (**class attribute**) number of instances created explicitly by the user
_externalInstances: int = 0
#: (**class attribute**) number of total instances = _internalInstances + _externalInstances
_instances: int = 0
__slots__ = ['hc']
def __init__(self, superSys, **kwargs):
super().__init__(_internal=kwargs.pop('_internal', False))
self.superSys = superSys
self.hc = False
self._named__setKwargs(**kwargs) # pylint: disable=no-member
[docs] def _paramUpdatedToFalse(self):
pass
@property
def _paramUpdated(self):
return self.superSys._paramUpdated
@property
def simulation(self):
return self.superSys.simulation
@_paramUpdated.setter
def _paramUpdated(self, boolean):
pass
[docs] def getUnitary(self, collapseOps = None, decayRates = None): #pylint:disable=unused-argument
self.superSys.unitary()
return self.superSys._hc if self.hc else self.superSys._paramBoundBase__matrix #pylint:disable=protected-access
[docs] def unitary(self): #pylint:disable=unused-argument
self.superSys.unitary()
return self.superSys._hc if self.hc else self.superSys._paramBoundBase__matrix #pylint:disable=protected-access
@property
def _isOpen(self):
return self.superSys._isOpen # pylint: disable=protected-access
[docs]class freeEvolution(genericProtocol):
label = 'freeEvolution'
#: (**class attribute**) number of instances created internally by the library
_internalInstances: int = 0
#: (**class attribute**) number of instances created explicitly by the user
_externalInstances: int = 0
#: (**class attribute**) number of total instances = _internalInstances + _externalInstances
_instances: int = 0
__slots__ = []
def __init__(self, **kwargs):
super().__init__(_internal=kwargs.pop('_internal', False))
self._named__setKwargs(**kwargs) # pylint: disable=no-member
_freqCoef = 1 #2 * np.pi
[docs] def matrixExponentiation(self, collapseOps = None, decayRates = None):
self._increaseExponentiationCount()
hamiltonian = self.superSys.totalHam if hasattr(self.superSys, 'totalHam') else self.superSys.totalHamiltonian #pylint:disable=no-member
unitary = lio.LiouvillianExp(self._freqCoef * hamiltonian, # pylint: disable=no-member
timeStep=((self.simulation.stepSize*self.ratio)/self.simulation.samples),
collapseOperators=collapseOps, decayRates=decayRates)
self._paramBoundBase__matrix = unitary # pylint: disable=assigning-non-slot
return unitary
freeEvolution._createUnitary = freeEvolution.matrixExponentiation
[docs]class Gate(genericProtocol):
label = 'Gate'
#: (**class attribute**) number of instances created internally by the library
_internalInstances: int = 0
#: (**class attribute**) number of instances created explicitly by the user
_externalInstances: int = 0
#: (**class attribute**) number of total instances = _internalInstances + _externalInstances
_instances: int = 0
__slots__ = ['__implementation']
def __init__(self, **kwargs):
super().__init__(_internal=kwargs.pop('_internal', False))
self.__implementation = None
self._named__setKwargs(**kwargs) # pylint: disable=no-member
@property
def system(self):
return list(self.subSys.values())
[docs] @addDecorator
def addSubSys(self, subSys, **kwargs):
newSys = super().addSubSys(subSys, **kwargs)
if self.simulation._timeBase__bound is None:
self.simulation._bound(newSys.simulation) # pylint: disable=protected-access
# FIXME this becomes 'fixed' unless the dimension is changed.
#if self.implementation.lower() != 'instant':
newSys._paramBoundBase__paramBound[self.name] = self
return newSys
@system.setter
def system(self, sys):
self.addSubSys(sys)
[docs] def addSys(self, sys):
self.system = sys
@property
def implementation(self):
return self._Gate__implementation
@implementation.setter
def implementation(self, typeStr):
self._Gate__implementation = typeStr # pylint: disable=assigning-non-slot
[docs]class Update(updateBase):
label = 'Update'
#: (**class attribute**) number of instances created internally by the library
_internalInstances: int = 0
#: (**class attribute**) number of instances created explicitly by the user
_externalInstances: int = 0
#: (**class attribute**) number of total instances = _internalInstances + _externalInstances
_instances: int = 0
__slots__ = ['value', '__memoryValue', 'setup', 'setback']
def __init__(self, **kwargs):
super().__init__(_internal=kwargs.pop('_internal', False))
self.value = None
self.setup = self._setup
self.setback = self._setback
self.__memoryValue = None
self._named__setKwargs(**kwargs) # pylint: disable=no-member
@property
def memoryValue(self):
return self._Update__memoryValue
@memoryValue.setter
def memoryValue(self, value):
self._Update__memoryValue = value # pylint: disable=assigning-non-slot
@property
def updateFunction(self):
"""
The updateFunction property:
- **getter** : ``returns _updateBase__function`` which is used in custom sweeps
- **setter** : sets ``_updateBase__function`` callable
- **type** : ``callable``
"""
return self._updateBase__function # pylint: disable=no-member
@updateFunction.setter
def updateFunction(self, func):
self._updateBase__function = func # pylint: disable=assigning-non-slot
[docs] def _setup(self):
self.memoryValue = getattr(list(self.subSys.values())[0], self.key)
if self._updateBase__function is None: # pylint: disable=no-member
super()._runUpdate(self.value)
else:
self._updateBase__function(self) # pylint: disable=no-member
[docs] def _setback(self):
if self.value != self.memoryValue:
super()._runUpdate(self.memoryValue)