Source code for quanguru.classes.QSys

"""
    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