Source code for quanguru.classes.QSystem

"""
    Contains the QuantumSystem class used for quantum systems

    .. currentmodule:: quanguru.classes.QSystem

    .. autosummary::

        QuantumSystem

    .. |c| unicode:: U+2705
    .. |x| unicode:: U+274C
    .. |w| unicode:: U+2000

    =======================    ==================    ================   ===============
       **Function Name**        **Docstrings**        **Unit Tests**     **Tutorials**
    =======================    ==================    ================   ===============
      `QuantumSystem`            |w| |w| |w| |c|       |w| |w| |c|        |w| |w| |c|
    =======================    ==================    ================   ===============

"""

import warnings
from typing import Any
from numpy import ndarray, integer
from scipy.sparse import spmatrix

from .base import addDecorator, _recurseIfList, aliasDict
from .QSimComp import QSimComp
from .QPro import freeEvolution
from .QSimBase import setAttr
from .QTerms import QTerm
from .exceptions import checkVal, checkNotVal, checkCorType

from ..QuantumToolbox.linearAlgebra import tensorProd #pylint: disable=relative-beyond-top-level
from ..QuantumToolbox.states import superPos #pylint: disable=relative-beyond-top-level
from ..QuantumToolbox.operators import number, Jz

[docs]def _initStDec(_createInitialState): r""" Decorater to handle different inputs for initial state creation. """ def wrapper(obj, inp=None, _maxInput=1): # if the given input is a state with consistent shape, simply returns it back # if the shape is inconsistent raises an error if isinstance(inp, (ndarray, spmatrix)): checkVal(inp.shape[0], obj.dimension, 'Dimension mismatch with the initial state input and the dimesion of'+ ' the system') state = inp else: # if the input is None, tries using the initialStateInput of the simulation object # this is introduced as convenience so that we can call the _createInitialState without any argument # and it will use the input set through the initial state setter if inp is None: inp = obj.simulation._initialStateInput state = _createInitialState(obj, inp, _maxInput=_maxInput) return state return wrapper
[docs]class QuantumSystem(QSimComp): # pylint:disable=too-many-instance-attributes r""" Class for quantum systems, both for single and composite systems, which can also be nested. """ #: (**class attribute**) class label used in default naming label = 'QuantumSystem' #: (**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__ = ['__terms', '__dimension', '__firstTerm', '__compSys', '__dimsBefore', '__dimsAfter', '_inpCoef', '__unitary', '__compOpers'] def __init__(self, **kwargs): super().__init__(_internal=kwargs.pop('_internal', False)) #: First term is also stored in __firstTerm attribute self.__firstTerm = None #: dictionary of the terms self.__terms = aliasDict() #: dimension of Hilbert space of the quantum system self.__dimension = 1 #: boolean flag for composite/single systems self.__compSys = None #: 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 #: boolean to determine whether initialState inputs contains complex coefficients (the probability amplitudes) #: or the populations self._inpCoef = kwargs.pop("_inpCoef", False) #: a dictionary to store arbitrary composite operators that are shaped and updated internally self.__compOpers = {} #: an internal :class:`~freeEvolution` protocol, this is the default evolution when a simulation is run. self.__unitary = freeEvolution(_internal=True) self._QuantumSystem__unitary.superSys = self # pylint: disable=no-member self._QSimComp__simulation.addQSystems(subS=self, Protocol=self._freeEvol) # pylint: disable=no-member self._named__setKwargs(**kwargs) # pylint:disable=no-member # matrices @property def _compositeOperator(self): r""" A property to store a function pointer (for operator) as key of a dictionary where the value is the internally created composite operator. """ for oper, operMat in self._QuantumSystem__compOpers.items(): if operMat is None: self._QuantumSystem__compOpers[oper] = QTerm._dimInput(self, oper, 1) # pylint: disable=protected-access return self._QuantumSystem__compOpers @_compositeOperator.setter def _compositeOperator(self, oper): self._QuantumSystem__compOpers[oper] = None
[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. """ noSubSys = (len(self.subSys) == 0) noTerms = (len(self.terms) == 0) selfComp = self._isComposite if (noSubSys and noTerms): if selfComp is False: warnings.warn(f'{self.name} is given a dimension ({self.dimension}), but it is not given any term/s.'+\ 'If it is intentional, please ignore this') else: warnings.warn(f'{self.name} is not given any sub-system/s or any term/s.' + \ 'If it is intentional, please ignore this') for sys in self.subSys.values(): sys._constructMatrices() # pylint: disable=protected-access for ter in self.terms.values(): ter._constructMatrices() # pylint: disable=protected-access
[docs] def _timeDependency(self, time=None): r""" An internal method used to pass down the current time in evolution to all the ``subSys`` and ``terms``. The term objects timeDependency functions are used for updating relevant parameters as a function of time. """ if time is None: time = self.simulation._currentTime for sys in self.subSys.values(): sys._timeDependency(time) for ter in self.terms.values(): ter._timeDependency(time) return time
[docs] @_initStDec def _createInitialState(self, inp=None, _maxInput=1): r""" Method that creates a state from the given input, which is handeled by the _initStDec decorator for different input cases. """ if self._isComposite: inp = [qsys._initialStateInput for qsys in self.subSys.values()] if inp is None else inp subSysStates = [qsys._createInitialState(inp[ind], qsys.simulation._setGetMaxInput(inp[ind])) for ind, qsys in enumerate(self.subSys.values())] # pylint: disable=protected-access,line-too-long initialState = tensorProd(*subSysStates) if not any(inst is None for inst in subSysStates) else None else: checkNotVal(inp, None, self.name + ' is not given an initial state') initialState = superPos(self.dimension, inp, not self._inpCoef) if _maxInput < self.dimension else None if initialState is None: warnings.warn(f'Initial state input ({inp}) is larger than or equal to system dimension ({self.dimension})'+ f', initial state is set to {initialState}') return initialState
_createState = _createInitialState # pylint:disable=protected-access @QSimComp.initialState.setter # pylint: disable=no-member def initialState(self, inp): r""" Sets the initial state from a given input ``inp``. """ checkNotVal(self._QuantumSystem__compSys,None,f'Type of {self.name} is ambiguous. Single and composite systems'+ ' handle initial state inputs differently, so you need to set other'+ f' relevant parameters to determine the type of {self.name}') if self.superSys is not None: self.superSys.simulation._stateBase__initialState.value = None # breaks the bound to the other _parameter if self._isComposite: if not isinstance(inp, (ndarray, spmatrix)): checkCorType(inp, (list, tuple), 'Composite state initial state input') checkVal(len(inp), len(self.subSys),f'Number of inputs ({len(inp)}) to initial state should be the'+ f' same as number of sub-system ({len(self.subSys)}) of {self.name}') for ind, qsys in enumerate(self.subSys.values()): qsys.initialState = inp[ind] self.simulation.initialState = inp # pylint: disable=no-member, protected-access @property def _subSysHamiltonian(self): r""" returns the sum of sub-system Hamiltonian """ return sum(val.totalHamiltonian for val in self.subSys.values()) @property def totalHamiltonian(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._subSysHamiltonian + self._termHamiltonian # 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 _termHamiltonian(self): r""" returns the sum of term Hamiltonian """ return sum(val.totalHamiltonian for val in self.terms.values() if val.operator is not None) # dimension methods and properties @property def _totalDim(self): r""" :attr:`QuantumSystem.dimension` returns the dimension of a quantum system itself, meaning it does not contain 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 dimension(self): r""" Property to get the dimension of any quantum system and also to set the dimension of single quantum systems. It calculates the dimension of a composite system on every call. For composite systems, setter raises a warning. For single system, setter also calls _updateDimension on its `superSys` (if there is one). """ if self._isComposite: # pylint:disable=no-member self._QuantumSystem__dimension = 1 # pylint:disable=assigning-non-slot for su in self.subSys.values(): self._QuantumSystem__dimension *= su.dimension # pylint:disable=assigning-non-slot,no-member return self._QuantumSystem__dimension # pylint:disable=no-member
[docs] def _hasConsistentDimensionTerm(self, dim): isConsistent = all(QTerm._isCorrectPauliDim(term.qSystem, term.operator, dim) for term in self.terms.values()) #pylint:disable=protected-access if self.superSys is not None: isConsistent = isConsistent and self.superSys._hasConsistentDimensionTerm(dim) #pylint:disable=protected-access return isConsistent
@dimension.setter def dimension(self, dim): if self._QuantumSystem__compSys is None: # pylint:disable=no-member self._QuantumSystem__compSys = bool(len(self.subSys)) # pylint:disable=assigning-non-slot if not self._isComposite: # pylint:disable=no-member checkCorType(dim, (int, integer), "dimension of a QuantumSystem has to be an integer") checkVal(dim>0, True, "dimension of a QuantumSystem has to be larger than 0") self._hasConsistentDimensionTerm(dim) oldVal = getattr(self, '_QuantumSystem__dimension') setAttr(self, '_QuantumSystem__dimension', dim) if self._paramUpdated: self.delMatrices(_exclude=[]) if self.superSys is not None:# to change dimsBefore/After of other systems if self in subSys of superSys self.superSys._updateDimension(self, dim, oldVal) # pylint:disable=protected-access else: warnings.warn(self.name + ' is a composite system, cannot set dimension') @property def subSysDimensions(self): r""" Returns a (nested) list of dimensions of the quantum systems contained in ``self``, if self is composite, else returns self.dimension. """ return [sys.subSysDimensions for sys in self.subSys.values()] if self._isComposite else self.dimension
[docs] def _updateDimension(self, subSys, newDim, oldDim): r""" Internal method to update dimension before/after information of the sub-systems when the dimension of a sub-system is updated. It is called in the dimension setter. Parameters ---------- subSys : The sub-system whose dimension is being updated. newDim : int The new dimension of the subSys oldDim : int The old dimension of the subSys """ self.delMatrices(_exclude=[]) for qsys in self.subSys.values():#update dimsBefore/After of other sub-system by comparing their ind with subSys if qsys.ind < subSys.ind: qsys._dimsAfter = int((qsys._dimsAfter*newDim)/oldDim) if qsys.ind > subSys.ind: qsys._dimsBefore = int((qsys._dimsBefore*newDim)/oldDim) if self.superSys is not None: # for nested structures, we still need to call _updateDimension on self.superSys self.superSys._updateDimension(self, newDim, oldDim) # pylint:disable=protected-access
def __dimsABUpdate(self, attrName, val): r""" Common parts of the dimsBefore/After setters are combined in this method. Parameters ---------- attrName : str _dimsBefore/After as a string to be used with setAttr&setattr val : int new value of _dimsBefore/After """ oldVal = getattr(self, attrName) setAttr(self, '_QuantumSystem_'+attrName, val) for qsys in self.subSys.values(): setattr(qsys, attrName, int((getattr(qsys, attrName)*val)/oldVal)) @property def _dimsBefore(self): r""" Property to set and get the :attr:`~QuantumSystem.__dimsBefore`. Getter can be used to get information, but the setter is intended purely for internal use. """ return self._QuantumSystem__dimsBefore @_dimsBefore.setter def _dimsBefore(self, val): self._QuantumSystem__dimsABUpdate('_dimsBefore', val) @property def _dimsAfter(self): r""" Property to set and get the :attr:`~QuantumSystem.__dimsAfter`. Getter can be used to get information, but the setter is intended purely for internal use. """ return self._QuantumSystem__dimsAfter @_dimsAfter.setter def _dimsAfter(self, val): self._QuantumSystem__dimsABUpdate('_dimsAfter', val) @property def _isComposite(self): r""" Used internally to set _QuantumSystem__compSys boolean, never query this before _QuantumSystem__compSys is set by some internal call. Otherwise, this will always return False (because subSys dict is always empty initially) """ if self._QuantumSystem__compSys is None: warnings.warn(f'{self.name} type (whether it is a single or composite system) is ambiguous'+\ 'If it is intentional, please ignore this') return self._QuantumSystem__compSys # pylint:disable=no-member
[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
# sub-system methods and properties @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. This is mainly used for _updateDimension where we compare the position of a sub-system against the others to determine whether _dimsBefore/After needs to be updated. """ ind = 0 if self.superSys is not None: ind += list(self.superSys.subSys.values()).index(self) return ind 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.subSys.values(): subS._dimsAfter *= subSys.dimension subSys._dimsBefore *= subS.dimension return subSys
[docs] @addDecorator def addSubSys(self, subSys, **kwargs): r""" Extends the addSubSys method for composite quantum systems to set the __compSys boolean (to True, if None), update the dimsBefore/After of the sub-systems, set self as superSys of the sub-system, and set _paramUpdated to True, or it raises a TypeError if __compSys is already set to False. Also, it places self into the _paramBoundBase__paramBound dictionary of the sub-system, so that parameter updates of the sub-system also sets the _paramUpdated of self to True. Note that composite systems can contain other composite systems as sub-systems. """ if self in subSys.subSys.values(): raise ValueError(f"{self.name} is a subsystem of {subSys.name}. " + f"Cannot add {subSys.name} as subsystem to {self.name}. " + "Circular subsystem addition is not allowed.") if self._QuantumSystem__compSys is None: # pylint:disable=no-member self._QuantumSystem__compSys = True # pylint:disable=assigning-non-slot elif self._QuantumSystem__compSys is False: # pylint:disable=no-member raise TypeError("Cannot add a sub-system to a single quantum system " + self.name) if subSys not in self.subSys.values(): self._QuantumSystem__addSub(subSys) subSys.superSys = self self._paramUpdated = True subSys._paramBoundBase__paramBound[self.name] = self # pylint: disable=protected-access super().addSubSys(subSys, **kwargs) self.delMatrices(_exclude=[]) return subSys
[docs] def delMatrices(self, _exclude=[]): # pylint: disable=dangerous-default-value r""" This method extends :meth:`delMatrices <QSimComp.delMatrices>` of :class:`QSimComp` to also call :meth:`delMatrices <paramBoundBase.delMatrices>` on terms. """ super().delMatrices(_exclude=_exclude) for ter in self.terms.values(): _exclude = ter.delMatrices(_exclude=_exclude) for oper in self._QuantumSystem__compOpers.keys(): self._QuantumSystem__compOpers[oper] = None return _exclude
[docs] def __add__(self, other): r""" overload the + operator to create a composite quantum system between ``self`` and the ``other`` quantum system. """ other = self.getByNameOrAlias(other) checkCorType(other, QuantumSystem, "{other} is not an instance of QuantumSystem") if ((self._QuantumSystem__compSys in (True, None)) and (not other._isComposite)): self.addSubSys(other.copy() if (other is self) else other) newComp = self elif ((self._QuantumSystem__compSys is None) and other._QuantumSystem__compSys): newComp = other elif self._QuantumSystem__compSys is other._QuantumSystem__compSys: newComp = QuantumSystem(subSys=[self, other.copy() if (other is self) else other]) # TODO copy the simulation parameters, and what to do with compute and calculate? elif ((not self._isComposite) and (other._isComposite)): other.addSubSys(self) newComp = other return newComp
[docs] def __sub__(self, other): r""" overload the ``-`` operator to remove the ``other`` from ``self``, which should be the composite quantum system containing/connected-to the other. """ self._removeSubSysExc(other, _exclude=[]) return self
[docs] @_recurseIfList def _removeSubSysExc(self, subSys: Any, _exclude=[]) -> None: # pylint:disable=dangerous-default-value r""" Method to remove a given subSys (which might be a single or composite system) from a composite system, which might be self or any other composite system in the nested-system structure. This method traverses through the nested-structure to find the subSys (that will be removed) and uses _exclude to avoid infinite loops, that, for example, might be created when a system calls _removeSubSysExc on its superSys which calls _removeSubSysExc on its subSys, by calling _removeSubSysExc on self if self is not in _exclude. Since _exclude needs to be empty in each call, this method should not be called directly, removeSubSys is a wrapper around this and always calls this with an empty _exclude. Raises an error if removeSubSys is called on a single system. This method also updates the dimsBefore/After of the remaining sub-systems (and the removed system), deletes the existing matrices so that they are re-created with the proper dimensions. When removing a single system, it sets the dimension of the single system to 1, so that all the dimsBefore/After information are updated by the dimensionUpdate method. However, we might still need the removed system, so its dimension is stored (in oldDim) and set back again (into _QuantumSystem__dimension) after its removed. When removing a composite system, it makes _removeSubSysExc to each sub-system of the removed composite system, and these nested calls sets the dimensions of all the single systems below the removed composite to 1 so that the dimsBefore/After information are again updated by the dimensionUpdate method. However, the removed composite system might still be needed, and we might not want these dimensions to be 1 or these single systems to be removed from the removed composite system. These are avoided by storing&setting the dimensions back to their original values for single systems and adding the removed sub-systems of removed composite systems back in. """ checkNotVal(self._isComposite, False, f"{self.name} is not a composite. removeSubSys cannot be called on single systems") subSys = self.getByNameOrAlias(subSys) if subSys in self.subSys.values(): _exclude.append(self) if subSys._isComposite: # pylint:disable=protected-access # need to create this for two reasons # 1. because subSys.subSys changes its size due to _removeSubSysExc calls # 2. we add these systems back again after removal qsysList = list(subSys.subSys.values()) for qsys in qsysList: subSys._removeSubSysExc(qsys, _exclude=_exclude)#pylint:disable=protected-access super()._removeSubSysExc(subSys, _exclude=_exclude) # add the sub-systems of subSys back again subSys.addSubSys(qsysList) else: oldDim = subSys.dimension subSys.dimension = 1 super()._removeSubSysExc(subSys, _exclude=_exclude) setAttr(self, '_QuantumSystem__dimension', oldDim) subSys.superSys = None subSys._dimsAfter = 1 subSys._dimsBefore = 1 subSys.delMatrices(_exclude=[]) _exclude.append(subSys) else: if self not in _exclude: _exclude.append(self) for qsys in self.subSys.values(): if qsys._isComposite: #pylint:disable=protected-access qsys._removeSubSysExc(subSys, _exclude=_exclude) #pylint:disable=protected-access if subSys in _exclude: break else: if self.superSys is not None: self.superSys._removeSubSysExc(subSys, _exclude=_exclude) #pylint:disable=protected-access if subSys is not self: termObjs = list(self.terms.values()) for ter in termObjs: ter._removeTermIfQSysInList(self, subSys)#pylint:disable=protected-access self.delMatrices(_exclude=[]) self._paramUpdated = True
#free evolution composition and protocols # TODO test these with the protocol tests @property def _freeEvol(self): r""" Property to get the ``default`` internal ``freeEvolution`` proptocol. """ return self._QuantumSystem__unitary
[docs] def unitary(self): r""" Returns the unitary evolution operator for ``self``. """ unitary = self._QuantumSystem__unitary.unitary() self._paramBoundBase__paramUpdated = False # pylint: disable=assigning-non-slot return unitary
[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)
# TODO THESE NEEDS TESTS
[docs] def createTerm(self, operator, frequency=None, qSystem=None, order=None, superSys=None, **kwargs): #pylint:disable=too-many-arguments r""" Method to create a new term with the given parameters and also set the given kwargs to the new term. Parameters ---------- operator : Callable or List[Callable] operator/s of the term frequency : frequency of the term, by default None qSystem : QuantumSystem quantum system/s for the given operator/s, and it is self if no system is given order : order/s of the operator/s, it is set to 1 by default if no order value is given Returns ------- QTerm Newly created QTerm object """ if qSystem is None: qSystem = self if isinstance(qSystem, (list, tuple)): checkVal(self._isComposite, True, "Cannot add a multi-operator term (ie a coupling) to a single system") qSystem = [self.getByNameOrAlias(qsys) for qsys in qSystem] for qsys in qSystem: checkVal(self._hasInSubs(qsys), True, f"Cannot add a multi-operator term (ie a coupling) to {self.name}, because {qsys.name} is not"+ f"contained in the {self.name}") else: qSystem = self.getByNameOrAlias(qSystem) superSys = self if superSys is None else superSys newTerm = QTerm._createTerm(superSys=superSys, qSystem=qSystem, operator=operator, order=order,#pylint:disable=protected-access frequency=frequency, **kwargs) qObj = self if isinstance(qSystem, (list, tuple)) else qSystem qObj.addTerms(newTerm) return newTerm
@property def terms(self): r""" Property to get & set the terms of the quantum system. Note that the setter is not intended for adding a new term, but replace the all the existing terms with the given term/s (which can be a single term or a list of terms, and it also works with names and/or aliases). In order to add an additional term to existing ones, use ``addTerm`` method. """ return self._QuantumSystem__terms # pylint:disable=no-member
[docs] @addDecorator def addTerms(self, trm, **kwargs): r""" Method to add an existing term to self and also optionally set some of the term parameters through the kwargs. """ checkCorType(trm, QTerm, f"addTerms argument/s ({trm.name})") supSys = kwargs.pop('superSys', self) trm._named__setKwargs(**kwargs) # pylint: disable=W0212 if len(self.terms) == 0: self._QuantumSystem__firstTerm = trm #pylint:disable=assigning-non-slot self._QuantumSystem__terms[trm.name] = trm # pylint:disable=no-member self._paramUpdated = True trm.superSys = supSys trm._paramBoundBase__paramBound[self.name] = self # pylint: disable=protected-access, no-member
[docs] def _removeTermExc(self, termObj, _exclude=[]): #pylint:disable=dangerous-default-value r""" The method to find and remove the term from a quantum system. """ termObj = self.getByNameOrAlias(termObj) checkCorType(termObj, QTerm, f"Given object {termObj.name} is not a term") if termObj in self.terms.values(): _exclude.append(self) self.terms.pop(termObj.name) _exclude.append(termObj) else: if self not in _exclude: _exclude.append(self) for qsys in self.subSys.values(): qsys._removeTermExc(termObj, _exclude) #pylint:disable=protected-access if termObj in _exclude: break else: if self.superSys is not None: self.superSys._removeTermExc(termObj, _exclude) #pylint:disable=protected-access self.delMatrices(_exclude=[]) self._paramUpdated = True
[docs] @_recurseIfList def removeTerm(self, termObj): r""" Method to remove the given term from the quantum system. This can be called on any system in a composite system to remove any term, even if it belongs to another sub-system. This is intended so that the term of a subsystem can be removed through the composite system, especially for nested-composite systems. You can also give a list of terms (or their named/aliases) to be removed. This method is a wrapper around the actual remove method the ``_removeTermExc`` so that it is called with an empty _exclude list, which is used to avoid infinite recursions when finding the term inside a nested system. Note that it will raise an error, if the given term is the first-term if the system. """ termObj = self.getByNameOrAlias(termObj) self._removeTermExc(termObj=termObj, _exclude=[])
[docs] def resetTerms(self): r""" Method to delete all the existing terms by assigning a new empty dictionary. """ self._QuantumSystem__terms = aliasDict() # pylint:disable=assigning-non-slot
@terms.setter def terms(self, trm): self.resetTerms() self.addTerms(trm) @property def _firstTerm(self): r""" Property to get the first term of the quantum system. """ if self._QuantumSystem__firstTerm is None: self.addTerms(QTerm(qSystem=self)) return self._QuantumSystem__firstTerm # pylint:disable=no-member @property def frequency(self): r""" Property to get & set the frequency of the first term of the quantum system. """ return self._firstTerm.frequency @frequency.setter def frequency(self, freq): self._firstTerm.frequency = freq @property def operator(self): r""" Property to get & set the operator of the first term of the quantum system. """ return self._firstTerm.operator @operator.setter def operator(self, op): self._firstTerm.operator = op @property def order(self): r""" Property to get & set the order of the first term of the quantum system. """ return self._firstTerm.order @order.setter def order(self, odr): self._firstTerm.order = odr @property def _freeMatrix(self): r""" Property to get & set the free (i.e. no frequency or, equivalently frequency=1) matrix for the first term. """ return self._firstTerm._freeMatrix # pylint: disable=protected-access @_freeMatrix.setter def _freeMatrix(self, qMat): self._firstTerm._freeMatrix = qMat # pylint: disable=protected-access
[docs] def copy(self, **kwargs): newSys = super().copy() for qsys in self.subSys.values(): cqsys = qsys.copy() cqsys.alias = qsys.name + "_" + cqsys.name newSys.addSubSys(cqsys) subSysList = list(newSys.subSys.values()) for ter in self.terms.values(): if isinstance(ter.qSystem, QuantumSystem): qSystemNames = newSys else: qSystemNames = [] for qsys in ter.qSystem: qSystemNames.append(qsys.name + "_" + subSysList[qsys.ind].name) if newSys._QuantumSystem__firstTerm is None:#pylint:disable=no-member,protected-access newSys.createTerm(qSystem=qSystemNames, #pylint:disable=no-member operator=ter.operator, frequency=ter.frequency, order=ter.order) else: newSys._firstTerm.qSystem=qSystemNames#pylint:disable=no-member,protected-access newSys._firstTerm.operator=ter.operator#pylint:disable=no-member,protected-access newSys._firstTerm.frequency=ter.frequency#pylint:disable=no-member,protected-access newSys._firstTerm.order=ter.order#pylint:disable=no-member,protected-access if self.simulation._stateBase__initialStateInput._value is not None: newSys.initialState = self.simulation._stateBase__initialStateInput.value #pylint:disable=assigning-non-slot if not self._isComposite: newSys.dimension = self.dimension#pylint:disable=assigning-non-slot newSys._inpCoef = self._inpCoef # pylint: disable=protected-access,assigning-non-slot newSys._named__setKwargs(**kwargs) #pylint:disable=no-member return newSys
[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 = QuantumSystem() newComp.addSubSys(self) for _ in range(other - 1): newComp.addSubSys(self.copy()) return newComp
[docs]class Cavity(QuantumSystem): # 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 = 'Cavity' #: (**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), _inpCoef=kwargs.pop("_inpCoef", False)) self._QuantumSystem__compSys = False #pylint:disable=assigning-non-slot self.operator = number self._named__setKwargs(**kwargs) # pylint: disable=no-member
[docs]class Spin(QuantumSystem): # 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 = 'Spin' #: (**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__(_internal=kwargs.pop('_internal', False), _inpCoef=kwargs.pop("_inpCoef", False)) self._QuantumSystem__compSys = False #pylint:disable=assigning-non-slot #: operator for (the first term in) its Hamiltonian self.operator = 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._QuantumSystem__dimension-1)/2 # pylint: disable=no-member @jValue.setter def jValue(self, value): self._Spin__jValue = value # pylint: disable=assigning-non-slot self.dimension = int((2*value) + 1)
[docs]class Qubit(Spin): # 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 = 'Qubit' #: (**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), _inpCoef=kwargs.pop("_inpCoef", False)) self._QuantumSystem__compSys = False #pylint:disable=assigning-non-slot kwargs['dimension'] = 2 self.dimension = 2 self.operator = Jz self._named__setKwargs(**kwargs) # pylint: disable=no-member