"""
Contains classes for Quantum systems.
.. currentmodule:: quanguru.classes.QSys
.. autosummary::
genericQSys
QuantumSystemOld
compQSystem
termTimeDep
term
qSystem
qCoupling
SpinOld
QubitOld
CavityOld
_initStDec
_computeDef
_calculateDef
.. |c| unicode:: U+2705
.. |x| unicode:: U+274C
.. |w| unicode:: U+2000
======================= ================== ================ ===============
**Function Name** **Docstrings** **Unit Tests** **Tutorials**
======================= ================== ================ ===============
`genericQSys` |w| |w| |w| |c| |w| |w| |x| |w| |w| |x|
`QuantumSystemOld` |w| |w| |w| |c| |w| |w| |x| |w| |w| |x|
`compQSystem` |w| |w| |w| |c| |w| |w| |x| |w| |w| |x|
`termTimeDep` |w| |w| |w| |c| |w| |w| |x| |w| |w| |x|
`term` |w| |w| |w| |c| |w| |w| |x| |w| |w| |x|
`qSystem` |w| |w| |w| |c| |w| |w| |x| |w| |w| |x|
`qCoupling` |w| |w| |w| |c| |w| |w| |x| |w| |w| |x|
`SpinOld` |w| |w| |w| |c| |w| |w| |x| |w| |w| |x|
`QubitOld` |w| |w| |w| |c| |w| |w| |x| |w| |w| |x|
`CavityOld` |w| |w| |w| |c| |w| |w| |x| |w| |w| |x|
`_initStDec` |w| |w| |w| |c| |w| |w| |x| |w| |w| |x|
`_computeDef` |w| |w| |w| |c| |w| |w| |x| |w| |w| |x|
`_calculateDef` |w| |w| |w| |c| |w| |w| |x| |w| |w| |x|
======================= ================== ================ ===============
""" #pylint: disable=too-many-lines
from collections import OrderedDict
from numpy import (int64, int32, int16, ndarray)
from scipy.sparse import issparse
from ..QuantumToolbox import operators as qOps #pylint: disable=relative-beyond-top-level
from ..QuantumToolbox import linearAlgebra as linAlg #pylint: disable=relative-beyond-top-level
from ..QuantumToolbox import states as qSta #pylint: disable=relative-beyond-top-level
from .base import addDecorator, _recurseIfList
from .baseClasses import paramBoundBase
from .QSimComp import QSimComp
from .QSimBase import setAttr
#from quanguru.classes.exceptions import qSystemInitErrors, qCouplingInitErrors
from .QPro import freeEvolution
[docs]def _initStDec(_createAstate):
r"""
Decorater to handle different inputs for initial state creation.
"""
def wrapper(obj, inp=None):
if (issparse(inp) or isinstance(inp, ndarray)):
if inp.shape[0] != obj.dimension:
raise ValueError('Dimension mismatch')
state = inp
else:
if inp is None:
inp = obj.simulation._stateBase__initialStateInput.value
if (issparse(inp) or isinstance(inp, ndarray)):
if inp.shape[0] != obj.dimension:
raise ValueError('Dimension mismatch')
state = inp
else:
if isinstance(obj.dimension, int):
state = _createAstate(obj, inp)
else:
state = None
return state
return wrapper
[docs]def _computeDef(sys, state): # pylint: disable=unused-argument
r"""
Dummy compute method used when creating a copy of quantum systems.
TODO I am not happy with this solution.
"""
[docs]def _calculateDef(sys): # pylint: disable=unused-argument
r"""
Dummy calculate method used when creating a copy of quantum systems.
TODO I am not happy with this solution.
"""
[docs]class genericQSys(QSimComp):
r"""
Base class for both single (:class:`~qSystem`) and composite (:class:`~compQSystem`) quantum system classes, and I
hope to combine those two classes in here. Currently, a proxy :class:`~QuantumSystem` is introduced as a
temporary solution.
"""
#: (**class attribute**) class label used in default naming
label = 'genericQSys'
#: (**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__ = ['__unitary', '__dimension', '__dimsBefore', '__dimsAfter', '_inpCoef']
def __init__(self, **kwargs):
super().__init__(_internal=kwargs.pop('_internal', False))
#: an internal :class:`~freeEvolution` protocol, this is the default evolution when a simulation is run.
self.__unitary = freeEvolution(_internal=True)
self._genericQSys__unitary.superSys = self # pylint: disable=no-member
self._QSimComp__simulation.addQSystems(subS=self, Protocol=self._freeEvol) # pylint: disable=no-member
#: dimension of Hilbert space of the quantum system
self.__dimension = None
#: boolean to determine whether initialState inputs contains complex coefficients (the probability amplitudes)
#: or the populations
self._inpCoef = False
#: Total dimension of the other quantum systems **before** ``self`` in a composite system.
#: It is 1, when ``self``` is the first system in the composite system or ``self`` is not in a composite system.
self.__dimsBefore = 1
#: Total dimension of the other quantum systems **after** ``self`` in a composite system.
#: It is 1, when ``self`` is the last system in the composite system.
self.__dimsAfter = 1
self._named__setKwargs(**kwargs) # pylint: disable=no-member
[docs] def __add__(self, other):
r"""
With this method, ``+`` creates a composite quantum system between ``self`` and the ``other`` quantum system.
"""
if (isinstance(self, compQSystem) and isinstance(other, qSystem)):
self.addSubSys(other)
newComp = self
elif (isinstance(self, compQSystem) and isinstance(other, compQSystem)):
if len(self.subSys) == 0:
newComp = other
else:
newComp = compQSystem()
newComp.addSubSys([self, other.copy() if (other is self) else other])
elif (isinstance(self, qSystem) and isinstance(other, qSystem)): # noqa: W504
newComp = compQSystem()
# FIXME 'stepCount' getter creates problem with None defaults
newComp.simulation._copyVals(self.simulation, ['totalTime', 'stepSize', 'delStates'])
newComp.compute = _computeDef
newComp.simulation.compute = _computeDef
#newComp.calculate = _calculateDef
#newComp.simulation.calculate = _calculateDef
newComp.addSubSys([self, other.copy() if (other is self) else other])
elif isinstance(self, qSystem) and isinstance(other, compQSystem):
other.addSubSys(self)
newComp = other
elif isinstance(other, (float, int)):
newComp = self
return newComp
[docs] def _hasInSubs(self, other):
r"""
Returns True if the given system is in self.subSys or in any other subSys nested inside the self.subSys.
"""
return (other in self.subSys.values() or any(qs._hasInSubs(other) for qs in self.subSys.values())) # pylint:disable=protected-access
[docs] def __sub__(self, other):
r"""
With this method, ``-`` removes the ``other`` from ``self``, which should be the composite quantum system
containing other.
"""
self._removeSubSysExc(other, _exclude=[])
return self
[docs] def __rmul__(self, other):
r"""
With this method, ``*`` creates a composite quantum system that contains ``N=other`` many quantum systems with
the same ``type`` as ``self``.
"""
newComp = compQSystem()
newComp.addSubSys(self)
for _ in range(other - 1):
newComp.addSubSys(self.copy())
return newComp
[docs] def copy(self, **kwargs): # pylint: disable=arguments-differ
r"""
Create a copy of ``self`` and also change the parameter of the newly created copy with ``kwargs``.
"""
subSysList = []
for sys in self.subSys.values():
subSysList.append(sys.copy())
if isinstance(self, qSystem):
newSys = super().copy(dimension=self.dimension, terms=subSysList)
elif isinstance(self, compQSystem):
newSys = super().copy()
for sys in subSysList:
newSys.addSubSys(sys)
if self.simulation._stateBase__initialStateInput._value is not None:
newSys.initialState = self.simulation._stateBase__initialStateInput.value #pylint:disable=assigning-non-slot
newSys._named__setKwargs(**kwargs) #pylint:disable=no-member
return newSys
@property
def ind(self):
r"""
If ``self`` is in a composite quantum system, this return an index representing the position of ``self`` in the
composite system, else it returns 0.
Also, the first system in a composite quantum system is at index 0.
"""
ind = 0
if self.superSys is not None:
ind += list(self.superSys.subSys.values()).index(self)
if self.superSys.superSys is not None:
ind += self.superSys.ind
return ind
@property
def _dimsBefore(self):
r"""
Property to set and get the :attr:`~genericQSys.__dimsBefore`. Getter can be used to get information, but the
setter is intended purely for internal use.
"""
return self._genericQSys__dimsBefore if self._genericQSys__dimsBefore != 0 else 1
@_dimsBefore.setter
def _dimsBefore(self, val):
if not isinstance(val, int):
raise ValueError('?')
oldVal = self._dimsBefore
setAttr(self, '_genericQSys__dimsBefore', val)
for sys in self.subSys.values():
sys.delMatrices(_exclude=[]) # pylint: disable=protected-access
if isinstance(sys, genericQSys):
sys._dimsBefore = int((sys._dimsBefore*val)/oldVal)
@property
def _dimsAfter(self):
r"""
Property to set and get the :attr:`~genericQSys.__dimsAfter`. Getter can be used to get information, but the
setter is intended purely for internal use.
"""
return self._genericQSys__dimsAfter if self._genericQSys__dimsAfter != 0 else 1
@_dimsAfter.setter
def _dimsAfter(self, val):
if not isinstance(val, int):
raise ValueError('?')
oldVal = self._dimsAfter
setAttr(self, '_genericQSys__dimsAfter', val)
for sys in self.subSys.values():
sys.delMatrices(_exclude=[]) # pylint: disable=protected-access
if isinstance(sys, genericQSys):
sys._dimsAfter = int((sys._dimsAfter*val)/oldVal)
@property
def dimension(self):
r"""
Property to get the dimension of the quantum system. There is no setter as it is handled internally.
"""
if self._genericQSys__dimension is None:
try:
dims = self.subSysDimensions
self._genericQSys__dimension = 1 # pylint: disable=assigning-non-slot
for val in dims:
self._genericQSys__dimension *= val # pylint: disable=assigning-non-slot
except AttributeError:
print(f'dimension? {self.name}')
return self._genericQSys__dimension
@property
def _totalDim(self):
r"""
:attr:`genericQSys.dimension` returns the dimension of a quantum system itself, meaning it does not contain the
dimensions of the other systems if ``self`` is in a composite system, ``_totalDim`` returns the total dimension
by also taking the dimensions before and after ``self`` in a composte system.
"""
return self.dimension * self._dimsBefore * self._dimsAfter#pylint:disable=E1101
@property
def _freeEvol(self):
r"""
Property to get the ``default`` internal ``freeEvolution`` proptocol.
"""
return self._genericQSys__unitary
[docs] def unitary(self):
r"""
Returns the unitary evolution operator for ``self``.
"""
unitary = self._genericQSys__unitary.unitary()
self._paramBoundBase__paramUpdated = False # pylint: disable=assigning-non-slot
return unitary
@QSimComp.initialState.setter # pylint: disable=no-member
def initialState(self, inp):
r"""
Sets the initial state from a given input ``inp``,
see :meth:`baseClasses.stateBase.initialState` for different types of inputs.
"""
if self.superSys is not None:
self.superSys.simulation._stateBase__initialState._value = None
self.simulation.initialState = inp # pylint: disable=no-member, protected-access
if (isinstance(self, compQSystem) and isinstance(inp, list)):
for ind, it in enumerate(inp):
list(self.qSystems.values())[ind].initialState = it # pylint: disable=no-member
[docs] def _constructMatrices(self):
r"""
The matrices for operators constructed and de-constructed whenever they should be, and this method is used
internally in various places when the matrices are needed to be constructed.
"""
for sys in self.subSys.values():
sys._constructMatrices() # pylint: disable=protected-access
[docs] def addProtocol(self, protocol=None, system=None, protocolRemove=None):
r"""
adds the given ``protocol`` into ``self.simulation`` and uses ``self`` as ``system`` if it is not given.
It also can removed a protocol (``protocolRemove``) at the same time.
"""
if system is None:
system = self
self.simulation.addProtocol(protocol=protocol, system=system, protocolRemove=protocolRemove)
[docs] def _timeDependency(self, time=None):
r"""
Passes down the current time in evolution to all the ``subSys``, which eventually are either ``term`` or
``qCoupling`` objects that are child classes of ``termTimeDep``, which updates the ``frequency`` of the
corresponding coupling or term using the ``timeDependency`` method created and given by the user.
TODO Create a demo and hyperlink here.
"""
if time is None:
time = self.simulation._currentTime
for sys in self.subSys.values():
sys._timeDependency(time)
return time
[docs]class QuantumSystemOld(genericQSys):
r"""
This class can be used for creating either a composite or a single quantum system depending on the given ``kwargs``,
if no ``kwargs`` given, it creates a composite system by default.
"""
#: (**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
[docs] def __new__(cls, sysType='composite', **kwargs):
singleKeys = ['frequency', 'operator', 'order', 'dimension']
for key in singleKeys:
if key in kwargs:
sysType = 'single'
if sysType == 'composite':
newCls = compQSystem
elif sysType == 'single':
newCls = qSystem
elif sysType == 'system coupling':
newCls = qCoupling
if newCls != cls:
instance = newCls(**kwargs)
return instance
__slots__ = []
[docs]class compQSystem(genericQSys):
r"""
Class for composite quantum systems.
"""
#: (**class attribute**) class label used in default naming
label = 'QuantumSystemOld'
#: (**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__ = ['__qCouplings', '__qSystems']
def __init__(self, **kwargs):
if self.__class__.__name__ == 'compQSystem':
compQSystem._externalInstances = qSystem._instances + compQSystem._instances
super().__init__(_internal=kwargs.pop('_internal', False))
#: an ordered dictionary for the coupling terms
self.__qCouplings = OrderedDict()
#: an ordered dictionary for the quantum systems
self.__qSystems = OrderedDict()
self._named__setKwargs(**kwargs) # pylint: disable=no-member
[docs] def _timeDependency(self, time=None):
r"""
Passes down the current time in evolution to all the ``subSys``, which eventually are either ``term`` or
``qCoupling`` objects that are child classes of ``termTimeDep``, which updates the ``frequency`` of the
corresponding coupling or term using the ``timeDependency`` method created and given by the user.
TODO Create a demo and hyperlink here.
"""
time = super()._timeDependency(time=time)
for coupling in self.qCouplings.values():
coupling._timeDependency(time)
@property
def subSysDimensions(self):
r"""
Returns a list of dimensions of the quantum systems contained in ``self``, which is a composite system.
"""
return [sys.dimension for sys in self.subSys.values()]
@property
def freeHam(self):
r"""
returns the Hamiltonian without the coupling terms.
"""
ham = sum(val.totalHam for val in self.qSystems.values())
return ham
@property
def totalHam(self): # pylint: disable=invalid-overridden-method
r"""
returns the total Hamiltonian of ``self`` including the coupling terms.
"""
if ((self._paramUpdated) or (self._paramBoundBase__matrix is None)): # pylint: disable=no-member
self._paramBoundBase__matrix = self.freeHam + self.couplingHam # pylint: disable=assigning-non-slot
self._paramBoundBase__paramUpdated = False # pylint: disable=assigning-non-slot
return self._paramBoundBase__matrix # pylint: disable=no-member, assigning-non-slot
@property
def couplingHam(self):
r"""
returns only the coupling terms of the Hamiltonian.
"""
cham = sum(val.totalHam for val in self.qCouplings.values())
return cham
@property
def qSystems(self):
r"""
returns the ordered dictionary that contains the sub-systems.
"""
return self._compQSystem__qSystems # pylint: disable=no-member
[docs] @addDecorator
def addSubSys(self, subSys, **kwargs): # pylint: disable=arguments-differ
r"""
Add a quantum system to ``self``. Note that composite systems can contain other composite systems as sub-systems
"""
if subSys not in self.subSys.values():
subSys = super().addSubSys(subSys, **kwargs)
if isinstance(subSys, qCoupling):
self._compQSystem__addCoupling(self._qBase__subSys.pop(subSys.name)) # pylint: disable=no-member
elif isinstance(subSys, genericQSys):
self._compQSystem__addSub(subSys)# pylint: disable=no-member
else:
raise TypeError('?')
subSys._paramBoundBase__paramBound[self.name] = self # pylint: disable=protected-access
self._paramUpdated = True
return subSys
[docs] def createSubSys(self, subSysClass, **kwargs):
r"""
Create a subsystem of the given ``subSysClass`` class and also set the given ``kwargs`` to newly created system.
"""
return self.addSubSys(subSysClass, **kwargs)
def __addSub(self, subSys):
r"""
internal method used to update relevant information (such as dimension before/after) for the existing and newly
added sub-systems. This is purely for internal use.
"""
for subS in self._compQSystem__qSystems.values():
subS._dimsAfter *= subSys.dimension
subSys._dimsBefore *= subS.dimension
if subSys._paramBoundBase__matrix is not None:
for sys in subSys.subSys.values():
sys._paramBoundBase__matrix = None
# TODO big question here
subSys.simulation._bound(self.simulation) # pylint: disable=protected-access
self._compQSystem__qSystems[subSys.name] = subSys
subSys.superSys = self
self._genericQSys__dimension = None # pylint: disable=assigning-non-slot
return subSys
[docs] @_recurseIfList
def _removeSubSysExc(self, subSys, _exclude=[]):#pylint:disable=arguments-differ,dangerous-default-value,too-many-branches
r"""
Removes a quantum system from the composite system ``self`` and updates the relevant information in the
remaining sub-systems (such as dimension before/after).
"""
subSys = self.getByNameOrAlias(subSys)
couplings = list(self.qCouplings.values())
for coupling in couplings:
coupling._removeSubSysExc(subSys, _exclude=_exclude)# pylint: disable=protected-access
if len(coupling._qBase__subSys) == 0: # pylint: disable=protected-access
self.qCouplings.pop(coupling.name)
if subSys in list(self.subSys.values()):
for qS in self.subSys.values():
qS.simulation._stateBase__initialState._value = None
if qS.ind < subSys.ind:
qS._dimsAfter = int(qS._dimsAfter/subSys.dimension)
elif qS.ind > subSys.ind:
qS._dimsBefore = int(qS._dimsBefore/subSys.dimension)
self.qSystems.pop(subSys.name)
_exclude.append(self)
super()._removeSubSysExc(subSys, _exclude=_exclude)
elif subSys in self.qCouplings.values():
self.qCouplings.pop(subSys.name)
if self not in _exclude:
_exclude.append(self)
if ((self._dimsAfter != 1) or (self._dimsBefore != 1)):
if self.ind < subSys.superSys.ind:
self._dimsAfter = int(self._dimsAfter/subSys.dimension)
elif self.ind > subSys.superSys.ind:
self._dimsBefore = int(self._dimsBefore/subSys.dimension)
for sys in self.subSys.values():
sys._removeSubSysExc(subSys, _exclude=_exclude) # pylint: disable=protected-access
#_exclude.append(sys)
if self.superSys is not None:
self.superSys._removeSubSysExc(subSys, _exclude=_exclude) # pylint: disable=protected-access
_exclude.append(self.superSys)
#subSys.superSys = None
subSys._dimsAfter = 1
subSys._dimsBefore = 1
self.delMatrices(_exclude=[])
self.simulation._stateBase__initialState._value = None
self._genericQSys__dimension = None # pylint: disable=assigning-non-slot
@property
def qCouplings(self):
r"""
returns the ordered dictionary of coupling terms.
"""
return self._compQSystem__qCouplings
def __addCoupling(self, couplingObj):
r"""
Internal method used when adding a coupling term.
"""
self._compQSystem__qCouplings[couplingObj.name] = couplingObj
couplingObj.superSys = self
return couplingObj
[docs] def createSysCoupling(self, *args, **kwargs):
r"""
Creates a coupling term, sets the ``kwargs`` for that coupling, and uses ``args`` for the coupling operators
and the corresponding operators, see addTerm in qCoupling to understand how args works.
TODO Create a tutorial and hyperlink here
"""
newCoupling = self.addSubSys(qCoupling, **kwargs)
newCoupling.addTerm(*args)
return newCoupling
[docs] def addSysCoupling(self, couplingObj):
r"""
Adds the given coupling term to ``self``.
TODO Create a tutorial and hyperlink here
"""
self.addSubSys(couplingObj)
[docs] @_initStDec
def _createAstate(self, inp=None):
r"""
Creates the initial state using the ``inp`` input, and this method is for internal use.
"""
if inp is None:
inp = [qsys._createAstate() for qsys in self.subSys.values()] #pylint:disable=protected-access
elif isinstance(inp, list):
inp = [qsys._createAstate(inp[ind]) for ind, qsys in enumerate(self.subSys.values())]#pylint:disable=protected-access
else:
raise TypeError('?')
return linAlg.tensorProd(*inp)
[docs] def _constructMatrices(self):
r"""
The matrices for operators constructed and de-constructed whenever they should be, and this method is used
internally in various places when the matrices are needed to be constructed.
"""
super()._constructMatrices()
for sys in self.qCouplings.values():
sys._constructMatrices() # pylint: disable=protected-access
[docs] def updateDimension(self, qSys, newDimVal, oldDimVal=None, _exclude=[]):#pylint:disable=dangerous-default-value,too-many-branches
r"""
This method is called when the dimension of a subSys ``qSys`` is changed, so that the relevant changes are
reflected to dimension before/after information for the other systems in the composite system.
"""
# TODO can be combined with removeSubSys by a decorator or another method to simplfy both
if oldDimVal is None:
oldDimVal = qSys._genericQSys__dimension
self._genericQSys__dimension = None # pylint: disable=assigning-non-slot
if qSys in self.qSystems.values():
_exclude.append(self)
qSys._genericQSys__dimension = newDimVal
ind = qSys.ind
for qS in self.qSystems.values():
if qS.ind < ind:
qS._dimsAfter = int((qS._dimsAfter*newDimVal)/oldDimVal)
elif qS.ind > ind:
qS._dimsBefore = int((qS._dimsBefore*newDimVal)/oldDimVal)
#if self.simulation._stateBase__initialStateInput.value is not None: # pylint: disable=no-member
# self.initialState = self.simulation._stateBase__initialStateInput.value # pylint: disable=no-member
self._paramUpdated = True
#self._constructMatrices()
#for sys in self.subSys.values():
# if sys.simulation._stateBase__initialStateInput.value is not None:
# sys.initialState = sys.simulation._stateBase__initialStateInput.value
if self not in _exclude:
_exclude.append(self)
if ((self._dimsAfter != 1) or (self._dimsBefore != 1)):
if self.ind < qSys.superSys.ind:
self._dimsAfter = int((self._dimsAfter*newDimVal)/oldDimVal)
elif self.ind > qSys.superSys.ind:
self._dimsBefore = int((self._dimsBefore*newDimVal)/oldDimVal)
else:
for sys in self.subSys.values():
if sys not in _exclude:
_exclude.append(sys)
if sys.ind < qSys.superSys.ind:
sys._dimsAfter = int((sys._dimsAfter*newDimVal)/oldDimVal)
elif sys.ind > qSys.superSys.ind:
sys._dimsBefore = int((sys._dimsBefore*newDimVal)/oldDimVal)
if self.superSys is not None:
self.superSys.updateDimension(qSys=qSys, newDimVal=newDimVal, oldDimVal=oldDimVal, _exclude=_exclude)
self.delMatrices(_exclude=[])
for c in self.qCouplings.values():
c.delMatrices(_exclude=[])
return qSys
[docs]class termTimeDep(paramBoundBase):
r"""
Parent class for :class:`~term` and :class:`~qCoupling` and I hope to combine those two in here.
"""
#: (**class attribute**) class label used in default naming
label = '_timeDep'
#: (**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__ = ['timeDependency', '__frequency', '__order', '__operator']
def __init__(self, **kwargs):
super().__init__(_internal=kwargs.pop('_internal', False))
#: function that can be assigned by the user to update the parameters a function of time. The library passes the
#: current time to this function
self.timeDependency = None
#: frequency of the term, it is is the coupling strength in the case of coupling term
self.__frequency = None
# TODO Create a tutorial
#: the order/power for the operator of the term. The operator is raised to the power in this value
self.__order = 1
#: operator for the term
self.__operator = None
self._named__setKwargs(**kwargs) # pylint: disable=no-member
[docs] def copy(self, **kwargs): # pylint: disable=arguments-differ
r"""
Create a copy ``self`` and change the values of the attributes given in ``kwargs``.
"""
newSys = super().copy(frequency=self.frequency, operator=self.operator, order=self.order, **kwargs)
return newSys
@property
def operator(self):
r"""
Sets and gets the operator for term.
"""
return self._termTimeDep__operator
@operator.setter
def operator(self, op):
self._paramBoundBase__matrix = None # pylint: disable=assigning-non-slot
setAttr(self, '_termTimeDep__operator', op)
@property
def order(self):
r"""
Sets and gets the order of the operator of the term.
"""
return self._termTimeDep__order
@order.setter
def order(self, ordVal):
setAttr(self, '_termTimeDep__order', ordVal)
if self._paramBoundBase__matrix is not None: # pylint: disable=no-member
self.freeMat = None
@property
def frequency(self):
r"""
Sets and gets the frequency of the term.
"""
return self._termTimeDep__frequency
@frequency.setter
def frequency(self, freq):
freq = 0 if freq == 0.0 else freq
setAttr(self, '_termTimeDep__frequency', freq)
[docs] def _constructMatrices(self):
r"""
The matrices for operators constructed and de-constructed whenever they should be, and this method is used
internally in various places when the matrices are needed to be constructed.
Currently, this is just pass and extended in the child classes, and the goal is to combine those methods in here
"""
@property
def totalHam(self):
r"""
Return the total Hamiltonian for this term.
"""
return self.frequency*self.freeMat
@property
def freeMat(self):
r"""
Gets and sets the free matrix, ie without the frequency (or, equivalently frequency=1) of the term.
"""
#if ((self._paramBoundBase__matrix is None) or (self._paramUpdated)): # pylint: disable=no-member
if self._paramBoundBase__matrix is None: # pylint: disable=no-member
self.freeMat = None
self._paramBoundBase__paramUpdated = False # pylint: disable=assigning-non-slot
return self._paramBoundBase__matrix # pylint: disable=no-member
@freeMat.setter
def freeMat(self, qMat):
if qMat is not None:
self._paramBoundBase__matrix = qMat # pylint: disable=no-member, assigning-non-slot
else:
#if len(self._qBase__subSys) == 0: # pylint: disable=no-member
# raise ValueError('No operator is given for coupling Hamiltonian')
#if self.operator is None:
# raise ValueError('No operator is given for free Hamiltonian')
self._constructMatrices()
[docs] def _timeDependency(self, time=None):
r"""
Internal method that passes the current time to ``timeDependency`` method that needs to be defined by the user
to update the relevant parameters (such as frequency of the term) as a function of time.
"""
if time is None:
time = self.superSys.simulation._currentTime
if callable(self.timeDependency):
if hasattr(self, 'frequency'):
self.frequency = self.timeDependency(self, time) # pylint: disable=assigning-non-slot,not-callable
elif hasattr(self, 'couplingStrength'):
self.couplingStrength = self.timeDependency(self, time) #pylint:disable=assigning-non-slot,not-callable
[docs]class term(termTimeDep):
r"""
Term object for simple (i.e. non-coupling) terms in the Hamiltonian.
"""
#: (**class attribute**) class label used in default naming
label = 'term'
#: (**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__ = []
@paramBoundBase.superSys.setter
def superSys(self, supSys):
r"""
Extends superSys setter to also add aliases to self.
New aliases are (any name/alias of superSys) + Term + (number of terms)
TODO What if there is already a superSys, and also alias list contains user given aliases as well.
"""
paramBoundBase.superSys.fset(self, supSys) # pylint: disable=no-member
termCount = len(self.superSys.subSys) if self in self.superSys.subSys.values() else len(self.superSys.subSys)+1 # pylint: disable=no-member,line-too-long # noqa: E501
self.alias = [na+"Term"+str(termCount) for na in self.superSys.name._aliasClass__members()] # pylint: disable=no-member, protected-access,line-too-long # noqa: E501
@property
def _freeMatSimple(self):
r"""
Return the matrix corresponding to the operator of the term, but this method does not make it into a composite
operator even if the term belongs to a system in a composite quantum system.
"""
h = self._constructMatrices(dimsBefore=1, dimsAfter=1, setMat=False)
return h
[docs] def _constructMatrices(self, dimsBefore=None, dimsAfter=None, setMat=True): #pylint:disable=arguments-differ
r"""
The matrices for operators constructed and de-constructed whenever they should be, and this method is used
internally in various places when the matrices are needed to be constructed.
"""
if dimsBefore is None:
dimsBefore = self.superSys._dimsBefore # pylint: disable=no-member
if dimsAfter is None:
dimsAfter = self.superSys._dimsAfter # pylint: disable=no-member
if not (isinstance(self.superSys.dimension, (int, int64, int32, int16)) and callable(self.operator)): # pylint: disable=no-member
raise TypeError('?')
dimension = self.superSys._genericQSys__dimension # pylint: disable=no-member
if self.operator in [qOps.Jz, qOps.Jy, qOps.Jx, qOps.Jm, qOps.Jp, qOps.Js]:
dimension = 0.5*(dimension-1)
if self.operator not in [qOps.sigmam, qOps.sigmap, qOps.sigmax, qOps.sigmay, qOps.sigmaz]:
mat = linAlg._matPower(qOps.compositeOp(self.operator(dimension), #pylint:disable=assigning-non-slot, protected-access
dimsBefore, dimsAfter), self.order)
else: # pylint: disable=bare-except
mat = linAlg._matPower(qOps.compositeOp( # pylint: disable=no-member, assigning-non-slot, protected-access
self.operator(), dimsBefore, dimsAfter), self.order)
if setMat:
self._paramBoundBase__matrix = mat #pylint:disable=assigning-non-slot
return mat
[docs]class qSystem(genericQSys):
r"""
Class for single quantum systems, used as the parent for :class:`~Cavity`, :class:`~Spin`, and :class:`~Qubit`.
"""
#: (**class attribute**) class label used in default naming
label = 'QuantumSystemOld'
#: (**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__ = []
#@qSystemInitErrors
def __init__(self, **kwargs):
if self.__class__.__name__ == 'qSystem':
qSystem._externalInstances = qSystem._instances + compQSystem._instances
super().__init__(_internal=kwargs.pop('_internal', False))
# TODO
qSysKwargs = ['terms', 'subSys', 'name', 'superSys', 'dimension', 'alias']
for key in qSysKwargs:
val = kwargs.pop(key, None)
if val is not None:
setattr(self, key, val)
if len(self.subSys) == 0:
self.addSubSys(term(superSys=self, **kwargs))
self._named__setKwargs(**kwargs) # pylint: disable=no-member
# @genericQSys.name.setter #pylint: disable=no-member
# def name(self, name):
# oldName = self.name
# genericQSys.name.fset(self, name) # pylint: disable=no-member
# for ii, sys in enumerate(self.subSys.values()):
# if sys.name == (oldName + 'term' + str(ii)):
# sys.name = self.superSys.name + 'term' + str(ii+1) # pylint: disable=no-member
@genericQSys.dimension.setter # pylint: disable=no-member
def dimension(self, newDimVal):
r"""
Setter for the dimension of single quantum system. It deletes the existing matrices for the operators, unitaries
etc, that uses the previous dimension information. It also takes care of updating the dimension before/after
information for the other quantum system systems if ``self`` is in a composite system.
"""
if not isinstance(newDimVal, (int, int64, int32, int16)):
raise ValueError('Dimension is not int')
oldDimVal = self._genericQSys__dimension # pylint: disable=no-member
for sys in self.subSys.values():
sys.delMatrices(_exclude=[]) # pylint: disable=protected-access
setAttr(self, '_genericQSys__dimension', newDimVal)
# FIXME these should be called only if oldDim != newDim
#if self.simulation._stateBase__initialStateInput.value is not None: # pylint: disable=protected-access
# self.initialState = self.simulation._stateBase__initialStateInput.value # pylint: disable=protected-access
if isinstance(self.superSys, compQSystem):
self.superSys.updateDimension(self, newDimVal, oldDimVal, _exclude=[]) # pylint: disable=no-member
@property
def totalHam(self): # pylint: disable=invalid-overridden-method
r"""
Returns the total Hamiltonian of the single quantum system.
"""
if ((self._paramUpdated) or (self._paramBoundBase__matrix is None)): # pylint: disable=no-member
h = sum((obj.frequency * obj.freeMat) for obj in self.subSys.values())
self._paramBoundBase__matrix = h # pylint: disable=assigning-non-slot
self._paramBoundBase__paramUpdated = False # pylint: disable=assigning-non-slot
return self._paramBoundBase__matrix # pylint: disable=no-member
@property
def _totalHamSimple(self):
r"""
returns the total Hamiltonian of the single quantum system, but this method does not take the dimension
before/after into account even if ``self`` is a sub-system of a composite system.
"""
return sum((obj.frequency * obj._freeMatSimple) for obj in self.subSys.values())#pylint:disable=protected-access
@property
def freeMat(self):
r"""
returns the free (i.e. no frequency or, equivalently frequency=1) matrix for the operator of the first term in
its Hamiltonian.
"""
return self.firstTerm.freeMat # pylint: disable=no-member
@freeMat.setter
def freeMat(self, qOpsFunc):
r"""
Setter for the freeMat. This is used internally in construct matrices methods.
"""
if callable(qOpsFunc):
self.firstTerm.operator = qOpsFunc
self.firstTerm._constructMatrices() # pylint: disable=protected-access
elif qOpsFunc is not None:
self.firstTerm._paramBoundBase__matrix = qOpsFunc # pylint: disable=assigning-non-slot
else:
if self.firstTerm.operator is None:
raise ValueError('No operator is given for free Hamiltonian')
self.firstTerm._constructMatrices() # pylint: disable=protected-access
@property
def operator(self):
r"""
Gets a list of the operators for the terms in its Hamiltonian, and sets the operator only for the first term.
"""
operators = [obj._termTimeDep__operator for obj in list(self.subSys.values())] # pylint: disable=protected-access
return operators if len(operators) > 1 else operators[0]
@operator.setter
def operator(self, op):
self.firstTerm.operator = op
@property
def frequency(self):
r"""
Setter and getter of the frequency of the first term in its Hamiltonian.
"""
#frequencies = [obj._termTimeDep__frequency for obj in list(self.subSys.values())] # pylint: disable=protected-access
#return frequencies if len(frequencies) > 1 else frequencies[0]
return self.firstTerm.frequency
@frequency.setter
def frequency(self, freq):
self.firstTerm.frequency = freq
@property
def order(self):
r"""
Sets and gets the order/power of the operator of the first term in its Hamiltonian.
"""
orders = [obj._termTimeDep__order for obj in list(self.subSys.values())] # pylint: disable=protected-access
return orders if len(orders) > 1 else orders[0]
@order.setter
def order(self, ordVal):
self.firstTerm.order = ordVal
@property
def firstTerm(self):
r"""
Returns the first term in its Hamiltonian
"""
return list(self.subSys.values())[0]
@property
def terms(self):
r"""
returns a list of the term objects used for its Hamiltonian.
"""
qSys = list(self.subSys.values())
return qSys if len(qSys) > 1 else qSys[0]
[docs] @addDecorator
def addSubSys(self, subSys, **kwargs):
r"""
extends the addSubSys method from the parent classes and uses it add terms to its Hamiltonian.
"""
if not isinstance(subSys, term):
raise TypeError('?')
kwargs['superSys'] = self
newS = super().addSubSys(subSys, **kwargs)
# FIXME use setAttr, check also for operator
self._paramUpdated = True
newS._paramBoundBase__paramBound[self.name] = self # pylint: disable=protected-access
return subSys
[docs] @_recurseIfList
def _removeSubSysExc(self, subSys, _exclude=[]): # pylint: disable=arguments-differ, dangerous-default-value
r"""
Method to remove a term from its Hamiltonian.
"""
if self not in _exclude:
_exclude.append(self)
subSys = self.getByNameOrAlias(subSys)
if self.superSys is not None:
self.superSys._removeSubSysExc(subSys, _exclude=_exclude) # pylint: disable=protected-access
if subSys in self.subSys.values():
super()._removeSubSysExc(subSys, _exclude=_exclude)
@terms.setter
def terms(self, subSys):
r"""
add terms to its Hamiltonian with this setter.
"""
genericQSys.subSys.fset(self, subSys) # pylint: disable=no-member
for sys in self.subSys.values():
sys.superSys = self
[docs] def addTerm(self, operator, frequency=0, order=1):
r"""
Calls the addSubSys to add terms, this method is created to provide a more intuitive name than addSubSys
"""
newTerm = self.addSubSys(term(operator=operator, frequency=frequency, order=order, superSys=self))
return newTerm
[docs] @_recurseIfList
def removeTerm(self, termObj):
r"""
Calls the removeSubSys to remove terms, this method is created to provide a more intuitive name than
removeSubSys
"""
self._removeSubSysExc(termObj, _exclude=[])
[docs] @_initStDec
def _createAstate(self, inp=None):
r"""
Internal method to create initial states.
"""
if inp is None:
raise ValueError(self.name + ' is not given an initial state')
return qSta.superPos(self.dimension, inp, not self._inpCoef)
[docs]class SpinOld(qSystem): # pylint: disable=too-many-ancestors
r"""
Object for a single Spin system with spin j (jValue).
"""
#: (**class attribute**) class label used in default naming
label = 'SpinOld'
#: (**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__ = ['__jValue']
def __init__(self, **kwargs):
super().__init__(terms=kwargs.pop('terms', None), subSys=kwargs.pop('subSys', None),
_internal=kwargs.pop('_internal', False))
#: operator for (the first term in) its Hamiltonian
self.operator = qOps.Jz
#: spin quantum number
self.__jValue = None
self._named__setKwargs(**kwargs) # pylint: disable=no-member
@property
def jValue(self):
r"""
Gets and sets the spin quantum number
"""
return (self._genericQSys__dimension-1)/2 # pylint: disable=no-member
@jValue.setter
def jValue(self, value):
self._SpinOld__jValue = value # pylint: disable=assigning-non-slot
self.dimension = int((2*value) + 1)
[docs]class QubitOld(SpinOld): # pylint: disable=too-many-ancestors
r"""
Spin 1/2 special case of Spin class, i.e. a Qubit.
"""
#: (**class attribute**) class label used in default naming
label = 'QubitOld'
#: (**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__(terms=kwargs.pop('terms', None), subSys=kwargs.pop('subSys', None),
_internal=kwargs.pop('_internal', False))
kwargs['dimension'] = 2
self.operator = qOps.Jz
self._named__setKwargs(**kwargs) # pylint: disable=no-member
[docs]class CavityOld(qSystem): # pylint: disable=too-many-ancestors
r"""
Cavity class, the only difference from a generic quantum object is that, by default, its operator is the number
operator.
"""
#: (**class attribute**) class label used in default naming
label = 'CavityOld'
#: (**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__(terms=kwargs.pop('terms', None), subSys=kwargs.pop('subSys', None),
_internal=kwargs.pop('_internal', False))
self.operator = qOps.number
self._named__setKwargs(**kwargs) # pylint: disable=no-member
[docs]class qCoupling(termTimeDep):
r"""
Class to create coupling terms between quantum systems.
"""
#: (**class attribute**) class label used in default naming
label = 'qCoupling'
#: (**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__ = []
#@qCouplingInitErrors
def __init__(self, *args, **kwargs):
super().__init__(_internal=kwargs.pop('_internal', False))
self._named__setKwargs(**kwargs) # pylint: disable=no-member
self.addTerm(*args)
# TODO might define setters
@property
def couplingOperators(self):
r"""
returns a list of coupling operators stored in this coupling term
"""
ops = []
for co in self._qBase__subSys.values(): # pylint: disable=no-member
ops.append(co[1])
return ops
@property
def coupledSystems(self):
r"""
returns the list of coupled systems by this coupling term
"""
ops = []
for co in self._qBase__subSys.values(): # pylint: disable=no-member
ops.append(co[0])
return ops
@property
def couplingStrength(self):
r"""
Gets and sets the coupling strength for this coupling. This is simply an alternative terminology for frequency.
"""
return self.frequency
@couplingStrength.setter
def couplingStrength(self, strength):
self.frequency = strength
def __coupOrdering(self, qts):
r"""
method used internally to make some sorting of the operators. This is implemented so that there are some
flexibilities for user when creating coupling.
"""
qts = sorted(qts, key=lambda x: x[0], reverse=False)
oper = qts[0][1]
for ops in range(len(qts)-1):
oper = oper @ qts[ops+1][1]
return oper
[docs] def _constructMatrices(self):
r"""
The matrices for operators constructed and de-constructed whenever they should be, and this method is used
internally in various places when the matrices are needed to be constructed.
"""
cMats = []
for ind in range(len(self._qBase__subSys)): # pylint: disable=no-member
qts = []
for indx in range(len(list(self._qBase__subSys.values())[ind])): # pylint: disable=no-member
sys = list(self._qBase__subSys.values())[ind][0][indx] # pylint: disable=no-member
order = sys.ind
oper = list(self._qBase__subSys.values())[ind][1][indx] # pylint: disable=no-member
if oper in [qOps.sigmam, qOps.sigmap, qOps.sigmax, qOps.sigmay, qOps.sigmaz]:
cHam = qOps.compositeOp(oper(), sys._dimsBefore, sys._dimsAfter)
else:
dimension = sys._genericQSys__dimension
if oper in [qOps.Jz, qOps.Jy, qOps.Jx, qOps.Jm, qOps.Jp, qOps.Js]:
dimension = 0.5*(dimension-1)
cHam = qOps.compositeOp(oper(dimension), sys._dimsBefore, sys._dimsAfter)
ts = [order, cHam]
qts.append(ts)
cMats.append(self._qCoupling__coupOrdering(qts))
#h = []
#if ((self.couplingStrength != 0) or (self.couplingStrength is not None)):
# h = [self.couplingStrength * sum(cMats)]
self._paramBoundBase__matrix = sum(cMats) # pylint: disable=assigning-non-slot
return self._paramBoundBase__matrix # pylint: disable=no-member
def __addTerm(self, count, ind, sys, *args):
r"""
used internally when adding terms to the coupling.
"""
if callable(args[count][ind]):
lo = len(self.subSys)
self._qBase__subSys[str(lo)] = (sys, tuple(args[count])) # pylint: disable=no-member
count += 1
if count < len(args):
count = self.__addTerm(count, ind, sys, *args)
return count
[docs] def addTerm(self, *args):
r"""
method to add terms to the coupling.
"""
counter = 0
while counter in range(len(args)):
# TODO write a generalisation for this one
if isinstance(self.getByNameOrAlias(args[counter][0]), qSystem):
qSystems = [self.getByNameOrAlias(obj) for obj in args[counter]]
for qsys in qSystems:
qsys._paramBoundBase__paramBound[self.name] = self
if callable(args[counter+1][1]):
#if tuple(args[counter + 1]) in self._qBase__subSys.keys(): # pylint: disable=no-member
# print(tuple(args[counter + 1]), 'already exists')
lo = len(self.subSys)
self._qBase__subSys[str(lo)] = (qSystems, tuple(args[counter + 1])) # pylint: disable=no-member
counter += 2
# TODO does not have to pass qSystem around
if counter < len(args):
counter = self._qCoupling__addTerm(counter, 1, qSystems, *args)
else:
# TODO raise a meaningful error
break
self._paramBoundBase__matrix = None # pylint: disable=assigning-non-slot
return self
[docs] @_recurseIfList
def removeSysCoupling(self, sys):
r"""
method to remove terms from the coupling, simply calls removeSubSys, this method is to create terminology
"""
self._removeSubSysExc(sys, _exclude=[])
[docs] @_recurseIfList
def _removeSubSysExc(self, subSys, _exclude=[]): # pylint: disable=dangerous-default-value
r"""
method to remove terms from the coupling
"""
vals = self._qBase__subSys.values() # pylint: disable=no-member
for ind, val in enumerate(vals):
systs = val[0]
if subSys in systs:
self._qBase__subSys.pop(str(ind)) # pylint: disable=no-member