From bb9ecde7a4bb62c6dad33f7ef1263dab81e5e14d Mon Sep 17 00:00:00 2001 From: Eric Date: Thu, 25 Aug 2022 16:44:11 -0400 Subject: [PATCH 001/247] Add Dimensions class to Qobj --- qutip/core/__init__.py | 1 + qutip/core/cy/qobjevo.pxd | 4 +- qutip/core/cy/qobjevo.pyx | 129 ++---- qutip/core/dimensions.py | 522 +++++++++++++++++++++++-- qutip/core/energy_restricted.py | 278 +++++++++++++ qutip/core/operators.py | 108 +---- qutip/core/options.py | 10 +- qutip/core/qobj.py | 246 ++++++------ qutip/core/states.py | 129 +----- qutip/core/superop_reps.py | 4 +- qutip/core/superoperator.py | 109 +++++- qutip/random_objects.py | 6 +- qutip/solve/mesolve.py | 14 +- qutip/tests/core/test_dimensions.py | 84 +--- qutip/tests/core/test_ptrace.py | 2 + qutip/tests/core/test_qobj.py | 2 +- qutip/tests/core/test_superop_reps.py | 5 +- qutip/tests/core/test_superoper.py | 4 +- qutip/tests/solve/test_piqs.py | 2 +- qutip/tests/test_enr_state_operator.py | 4 +- qutip/tests/test_random.py | 5 +- 21 files changed, 1072 insertions(+), 596 deletions(-) create mode 100644 qutip/core/energy_restricted.py diff --git a/qutip/core/__init__.py b/qutip/core/__init__.py index 6473367d24..d22c550229 100644 --- a/qutip/core/__init__.py +++ b/qutip/core/__init__.py @@ -11,4 +11,5 @@ from .superop_reps import * from .subsystem_apply import * from .blochredfield import * +from .energy_restricted import * from . import gates diff --git a/qutip/core/cy/qobjevo.pxd b/qutip/core/cy/qobjevo.pxd index d02f3bf18b..c96a04176d 100644 --- a/qutip/core/cy/qobjevo.pxd +++ b/qutip/core/cy/qobjevo.pxd @@ -6,10 +6,8 @@ from qutip.core.data.base cimport idxint cdef class QobjEvo: cdef: list elements - readonly list dims + readonly object _dims readonly (idxint, idxint) shape - readonly str type - readonly str superrep int _issuper int _isoper double _shift_dt diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index c73cefe562..a01b807546 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -9,9 +9,9 @@ from functools import partial import qutip from .. import Qobj from .. import data as _data +from ..dimensions import Dimensions from ..coefficient import coefficient, CompilationOptions from ._element import * -from ..dimensions import type_from_dims from qutip.settings import settings from qutip.core.cy._element cimport _BaseElement @@ -19,7 +19,6 @@ from qutip.core.data cimport Dense, Data, dense from qutip.core.data.expect cimport * from qutip.core.data.reshape cimport (column_stack_dense, column_unstack_dense) from qutip.core.cy.coefficient cimport Coefficient -from qutip.core.qobj import _MATMUL_TYPE_LOOKUP from libc.math cimport fabs __all__ = ['QobjEvo'] @@ -127,7 +126,6 @@ cdef class QobjEvo: QobjEvo(f, args={'w': 1j}) - For list based :obj:`~QobjEvo`, the list must consist of :obj`~Qobj` or ``[Qobj, Coefficient]`` pairs: @@ -184,12 +182,9 @@ cdef class QobjEvo: order=3, copy=True, compress=True, function_style=None): if isinstance(Q_object, QobjEvo): - self.dims = Q_object.dims.copy() + self._dims = Q_object._dims self.shape = Q_object.shape - self.type = Q_object.type self._shift_dt = ( Q_object)._shift_dt - self._issuper = ( Q_object)._issuper - self._isoper = ( Q_object)._isoper self.elements = ( Q_object).elements.copy() if args: self.arguments(args) @@ -198,10 +193,8 @@ cdef class QobjEvo: return self.elements = [] - self.dims = None + self._dims = None self.shape = (0, 0) - self._issuper = -1 - self._isoper = -1 self._shift_dt = 0 args = args or {} @@ -263,27 +256,15 @@ cdef class QobjEvo: " received: {!r}".format(op) ) - if self.dims is None: - self.dims = qobj.dims + if self._dims is None: + self._dims = qobj._dims self.shape = qobj.shape - self.type = qobj.type - self.superrep = qobj.superrep - elif self.dims != qobj.dims or self.shape != qobj.shape: + elif self._dims != qobj._dims: raise ValueError( f"QobjEvo term {op!r} has dims {qobj.dims!r} and shape" f" {qobj.shape!r} but previous terms had dims {self.dims!r}" f" and shape {self.shape!r}." ) - elif self.type != qobj.type: - raise ValueError( - f"QobjEvo term {op!r} has type {qobj.type!r} but " - f"previous terms had type {self.type!r}." - ) - elif self.superrep != qobj.superrep: - raise ValueError( - f"QobjEvo term {op!r} has superrep {qobj.superrep!r} but " - f"previous terms had superrep {self.superrep!r}." - ) return out @@ -312,10 +293,7 @@ cdef class QobjEvo: if _args is not None: kwargs.update(_args) return QobjEvo(self, args=kwargs)(t) - return Qobj( - self._call(t), dims=self.dims, copy=False, - type=self.type, superrep=self.superrep - ) + return Qobj(self._call(t), dims=self._dims, copy=False) cpdef Data _call(QobjEvo self, double t): t = self._prepare(t, None) @@ -393,19 +371,19 @@ cdef class QobjEvo: def __iadd__(QobjEvo self, other): cdef _BaseElement element if isinstance(other, QobjEvo): - if other.dims != self.dims: + if other._dims != self._dims: raise TypeError("incompatible dimensions" + str(self.dims) + ", " + str(other.dims)) for element in ( other).elements: self.elements.append(element) elif isinstance(other, Qobj): - if other.dims != self.dims: + if other._dims != self._dims: raise TypeError("incompatible dimensions" + str(self.dims) + ", " + str(other.dims)) self.elements.append(_ConstantElement(other)) elif ( isinstance(other, numbers.Number) and - self.dims[0] == self.dims[1] + self._dims[0] == self._dims[1] ): self.elements.append(_ConstantElement(other * qutip.qeye(self.dims[0]))) else: @@ -440,26 +418,15 @@ cdef class QobjEvo: if isinstance(left, QobjEvo): return left.copy().__imatmul__(right) elif isinstance(left, Qobj): - if left.dims[1] != ( right).dims[0]: + if left._dims[1] != ( right)._dims[0]: raise TypeError("incompatible dimensions" + str(left.dims[1]) + ", " + str(( right).dims[0])) - - type_ =_MATMUL_TYPE_LOOKUP.get((left.type, right.type)) - if type_ is None: - raise TypeError( - "incompatible matmul types " - + repr(left.type) + " and " + repr(right.type) - ) - res = right.copy() - res.dims = [left.dims[0], right.dims[1]] + res._dims = Dimensions([left._dims[0], right._dims[1]]) res.shape = (left.shape[0], right.shape[1]) - res.type = type_ left = _ConstantElement(left) res.elements = [left @ element for element in res.elements] - res._issuper = -1 - res._isoper = -1 return res else: return NotImplemented @@ -467,26 +434,15 @@ cdef class QobjEvo: def __rmatmul__(QobjEvo self, other): cdef QobjEvo res if isinstance(other, Qobj): - if other.dims[1] != self.dims[0]: + if other._dims[1] != self._dims[0]: raise TypeError("incompatible dimensions" + - str(other.dims[1]) + ", " + - str(self.dims[0])) - - type_ =_MATMUL_TYPE_LOOKUP.get((other.type, self.type)) - if type_ is None: - raise TypeError( - "incompatible matmul types " - + repr(other.type) + " and " + repr(self.type) - ) - + str(other._dims[1]) + ", " + + str(self._dims[0])) res = self.copy() - res.dims = [other.dims[0], res.dims[1]] + res._dims = Dimensions([other._dims[0], res._dims[1]]) res.shape = (other.shape[0], res.shape[1]) - res.type = type_ other = _ConstantElement(other) res.elements = [other @ element for element in res.elements] - res._issuper = -1 - res._isoper = -1 return res else: return NotImplemented @@ -497,19 +453,8 @@ cdef class QobjEvo: raise TypeError("incompatible dimensions" + str(self.dims[1]) + ", " + str(other.dims[0])) - - type_ =_MATMUL_TYPE_LOOKUP.get((self.type, other.type)) - if type_ is None: - raise TypeError( - "incompatible matmul types " - + repr(self.type) + " and " + repr(other.type) - ) - - self.dims = [self.dims[0], other.dims[1]] + self._dims = Dimensions([self.dims[0], other.dims[1]]) self.shape = (self.shape[0], other.shape[1]) - self.type = type_ - self._issuper = -1 - self._isoper = -1 if isinstance(other, Qobj): other = _ConstantElement(other) self.elements = [element @ other for element in self.elements] @@ -676,11 +621,8 @@ cdef class QobjEvo: raise TypeError("The op_mapping function must return a Qobj") cdef QobjEvo res = self.copy() res.elements = [element.linear_map(op_mapping) for element in res.elements] - res.dims = res.elements[0].qobj(0).dims + res._dims = res.elements[0].qobj(0)._dims res.shape = res.elements[0].qobj(0).shape - res.type = res.elements[0].qobj(0).type - res._issuper = res.elements[0].qobj(0).issuper - res._isoper = res.elements[0].qobj(0).isoper if not _skip_check: if res(0) != out: raise ValueError("The mapping is not linear") @@ -768,19 +710,28 @@ cdef class QobjEvo: @property def isoper(self): """Indicates if the system represents an operator.""" - # TODO: isoper should be part of dims - if self._isoper == -1: - self._isoper = type_from_dims(self.dims) == "oper" - return self._isoper + return self._dims.type == "oper" @property def issuper(self): """Indicates if the system represents a superoperator.""" - # TODO: issuper should/will be part of dims - # remove self._issuper then - if self._issuper == -1: - self._issuper = type_from_dims(self.dims) == "super" - return self._issuper + return self._dims.issuper + + @property + def dims(self): + return self._dims.as_list() + + @property + def type(self): + return self._dims.type + + @property + def superrep(self): + return self._dims.superrep + + @superrep.setter + def superrep(self, super_rep): + self._dims = Dimensions(self._dims.as_list(), rep=super_rep) ########################################################################### # operation methods # @@ -812,8 +763,8 @@ cdef class QobjEvo: raise ValueError("Must be an operator or super operator to compute" " an expectation value") if not ( - (self.dims[1] == state.dims[0]) or - (self.issuper and self.dims[1] == state.dims) + (self._dims[1] == state._dims[0]) or + (self.issuper and self._dims[1] == state._dims) ): raise ValueError("incompatible dimensions " + str(self.dims) + ", " + str(state.dims)) @@ -899,12 +850,12 @@ cdef class QobjEvo: if not isinstance(state, Qobj): raise TypeError("A Qobj state is expected") - if self.dims[1] != state.dims[0]: + if self._dims[1] != state._dims[0]: raise ValueError("incompatible dimensions " + str(self.dims[1]) + ", " + str(state.dims[0])) return Qobj(self.matmul_data(t, state.data), - dims=[self.dims[0],state.dims[1]], + dims=[self._dims[0], state._dims[1]], copy=False ) diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index ce81364ae0..064fe98b6d 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -8,6 +8,7 @@ import numpy as np from operator import getitem from functools import partial +from qutip.settings import settings def is_scalar(dims): @@ -261,35 +262,9 @@ def dims_to_tensor_perm(dims): index of the tensor ``data`` corresponding to the ``idx``th dimension of ``dims``. """ - # We figure out the type of the dims specification, - # relaxing the requirement that operators be square. - # This means that dims_type need not coincide with - # Qobj.type, but that works fine for our purposes here. - dims_type = type_from_dims(dims, enforce_square=False) - perm = enumerate_flat(dims) - if dims_type in ('oper', 'ket', 'bra'): - return flatten(perm) - - # If the type is other, we need to figure out if the - # dims is superlike on its outputs and inputs - # This is the case if the dims type for left or right - # are, respectively, oper-like. - if dims_type == 'other': - raise NotImplementedError("Not yet implemented for type='other'.") - - # If we're still here, the story is more complicated. We'll - # follow the strategy of creating a permutation by using - # enumerate_flat then transforming the result to swap - # input and output indices of vectorized matrices, then flattening - # the result. We'll then rebuild indices using this permutation. - if dims_type in ('operator-ket', 'super'): - # Swap the input and output spaces of the right part of - # perm. - perm[1] = list(reversed(perm[1])) - if dims_type in ('operator-bra', 'super'): - # Ditto, but for the left indices. - perm[0] = list(reversed(perm[0])) - return flatten(perm) + if isinstance(dims, list): + dims = Dimensions(dims) + return dims.get_tensor_perm() def dims_to_tensor_shape(dims): @@ -344,3 +319,492 @@ def dims_idxs_to_tensor_idxs(dims, indices): """ perm = dims_to_tensor_perm(dims) return deep_map(partial(getitem, perm), indices) + + +def to_tensor_rep(q_oper): + """ + Transform a ``Qobj`` to a numpy array of one with it's shape the dimensions + flattened. + + ``` + ket.dims == [[2, 3], [1]] + to_tensor_rep(ket).shape == (2, 3, 1) + + oper.dims == [[2, 3], [2, 3]] + to_tensor_rep(oper).shape == (2, 3, 2, 3) + + super.dims == [[[2, 3], [2, 3]], [[2, 3], [2, 3]]] + to_tensor_rep(super).shape == (2, 3, 2, 3, 2, 3, 2, 3) + ``` + """ + dims = q_oper._dims + data = q_oper.full().reshape(dims.get_tensor_shape()) + return data.transpose(dims.get_tensor_perm()) + + +def from_tensor_rep(tensorrep, dims): + """ + Reverse operator of :func:`to_tensor_rep`. + Create a Qobj From a N-dimensions numpy array and dimensions with N + indices. + """ + from . import Qobj + dims = Dimensions(dims) + data = tensorrep.transpose(np.argsort(dims.get_tensor_perm())) + return Qobj(data.reshape(dims.shape), dims=dims) + + +def _frozen(*args, **kwargs): + raise RuntimeError("Dimension cannot be modified.") + + +class MetaSpace(type): + def __call__(cls, *args, rep=None): + """ + Select which subclass is instanciated. + """ + if cls is Space and len(args) == 1 and isinstance(args[0], list): + # From a list of int. + return cls.from_list(*args, rep=rep) + elif len(args) == 1 and isinstance(args[0], Space): + # Already a Space + return args[0] + + if cls is Space: + if len(args) == 0: + # Empty space: a Field. + cls = Field + elif len(args) == 1 and args[0] == 1: + # Space(1): a Field. + cls = Field + elif len(args) == 1 and isinstance(args[0], Dimensions): + # Making a Space out of a Dimensions object: Super Operator. + cls = SuperSpace + elif len(args) > 1 and all(isinstance(arg, Space) for arg in args): + # list of space: tensor product space. + cls = Compound + + if settings.core['auto_tidyup_dims']: + if cls is Compound and all(isinstance(arg, Field) for arg in args): + cls = Field + if cls is SuperSpace and args[0].type == "scalar": + cls = Field + + args = tuple([tuple(arg) if isinstance(arg, list) else arg + for arg in args]) + + if cls is Field: + return cls.field_instance + if cls is SuperSpace: + args = *args, rep or 'super' + if args not in cls._stored_dims: + instance = cls.__new__(cls) + instance.__init__(*args) + cls._stored_dims[args] = instance + return cls._stored_dims[args] + + def from_list(cls, list_dims, rep=None): + if not isinstance(list_dims[0], list): + # Tensor + spaces = [Space(size) for size in list_dims] + elif len(list_dims) == 1: + # [[2, 3]]: tensor with an extra layer of list. + spaces = [Space(size) for size in list_dims[0]] + elif len(list_dims) % 2 == 0: + # Superoperators or tensor of Superoperators + spaces = [ + Space(Dimensions( + Space(list_dims[i+1]), + Space(list_dims[i]) + ), rep=rep) + for i in range(0, len(list_dims), 2) + ] + else: + raise ValueError(f'Format not understood {list_dims}') + + if len(spaces) == 1: + return spaces[0] + elif len(spaces) >= 2: + return Space(*spaces) + raise ValueError("Bad list format") + + +class Space(metaclass=MetaSpace): + _stored_dims = {} + def __init__(self, dims): + idims = int(dims) + if dims <= 0 or idims != dims: + raise ValueError("Dimensions must be integers >= 0") + # Size of the hilbert space + self.size = dims + self.issuper = False + # Super representation, should be an empty string except for SuperSpace + self.superrep = "" + # Does the size and dims match directly: size == prod(dims) + self._pure_dims = True + self.__setitem__ = _frozen + + def __eq__(self, other): + return self is other or ( + type(other) is type(self) + and other.size == self.size + ) + + def __hash__(self): + return hash(self.size) + + def __repr__(self): + return f"Space({self.size})" + + def as_list(self): + return [self.size] + + def __str__(self): + return str(self.as_list()) + + def dims2idx(self, dims): + return dims + + def idx2dims(self, idx): + return [idx] + + def step(self): + return [1] + + def flat(self): + return [self.size] + + def remove(self, idx): + raise RuntimeError("Cannot delete a flat space.") + + def replace(self, idx, new): + return Space(new) + + + +class Field(Space): + field_instance = None + def __init__(self): + self.size = 1 + self.issuper = False + self.superrep = "" + self._pure_dims = True + self.__setitem__ = _frozen + + def __eq__(self, other): + return type(other) is Field + + def __hash__(self): + return hash(0) + + def __repr__(self): + return "Field()" + + def as_list(self): + return [1] + + def step(self): + return [1] + + def flat(self): + return [1] + + def remove(self, idx): + return self + + def replace(self, idx, new): + return Space(new) + + +Field.field_instance = Field.__new__(Field) +Field.field_instance.__init__() + + +class Compound(Space): + _stored_dims = {} + def __init__(self, *spaces): + self.spaces = [] + for space in spaces: + if isinstance(space, Compound): + self.spaces += space.spaces + else: + self.spaces += [space] + self.spaces = tuple(self.spaces) + self.size = np.prod([space.size for space in self.spaces]) + self.issuper = any(space.issuper for space in self.spaces) + self._pure_dims = all(space._pure_dims for space in self.spaces) + superrep = [space.superrep for space in self.spaces] + if all(superrep[0] == rep for rep in superrep): + self.superrep = superrep[0] + else: + # We could also raise an error + self.superrep = 'mixed' + self.__setitem__ = _frozen + + def __eq__(self, other): + return self is other or ( + type(other) is type(self) + and len(self.spaces) == len(other.spaces) + and all(self_space == other_space + for self_space, other_space + in zip(self.spaces, other.spaces)) + ) + + def __hash__(self): + return hash(self.spaces) + + def __repr__(self): + parts_rep = ", ".join(repr(space) for space in self.spaces) + return f"Compound({parts_rep})" + + def as_list(self): + return sum([space.as_list() for space in self.spaces], []) + + def dims2idx(self, dims): + print('Compound', dims) + pos = 0 + step = 1 + for space, dim in zip(self.spaces[::-1], dims[::-1]): + pos += space.dims2idx(dim) * step + step *= space.size + return pos + + def idx2dims(self, idx): + dims = [] + for space in self.spaces[::-1]: + idx, dim = divmod(idx, space.size) + dims = space.idx2dims(dim) + dims + return dims + + def step(self): + steps = [] + step = 1 + for space in self.spaces[::-1]: + steps = [step * N for N in space.step()] + steps + step *= space.size + return steps + + def flat(self): + return sum([space.flat() for space in self.spaces], []) + + def remove(self, idx): + new_spaces = [] + for space in self.spaces: + n_indices = len(space.flat()) + idx_space = [i for i in idx if i= n_indices] + new_space = space.remove(idx_space) + if new_spaces: + return Compound(*new_spaces) + return Field() + + def replace(self, idx, new): + new_spaces = [] + for space in self.spaces: + n_indices = len(space.flat()) + if 0 <= idx < n_indices: + new_spaces.append(space.replace(idx, new)) + else: + new_spaces.append(space) + idx -= n_indices + return Compound(*new_spaces) + + +class SuperSpace(Space): + _stored_dims = {} + def __init__(self, oper, rep='super'): + self.oper = oper + self.superrep = rep + self.size = oper.shape[0] * oper.shape[1] + self.issuper = True + self._pure_dims = oper._pure_dims + self.__setitem__ = _frozen + + def __eq__(self, other): + return ( + self is other + or self.oper == other + or ( + type(other) is type(self) + and self.oper == other.oper + and self.superrep == other.superrep + ) + ) + + def __hash__(self): + return hash((self.oper, self.superrep)) + + def __repr__(self): + return f"Super({repr(self.oper)}, rep={self.superrep})" + + def as_list(self): + return self.oper.as_list() + + def dims2idx(self, dims): + print('SuperSpace', dims) + posl, posr = self.oper.dims2idx(dims) + return posl + posr * self.oper.shape[0] + + def idx2dims(self, idx): + posl = idx % self.oper.shape[0] + posr = idx // self.oper.shape[0] + return self.oper.idx2dims(posl, posr) + + def step(self): + stepl, stepr = self.oper.step() + step = self.oper.shape[0] + return stepl + [step * N for N in stepr] + + def flat(self): + return sum(self.oper.flat(), []) + + def remove(self, idx): + new_dims = self.oper.remove(idx) + if new_dims.type == 'scalar': + return Field() + return SuperSpace(self.oper.remove(idx), rep=self.superrep) + + def replace(self, idx, new): + return SuperSpace(self.oper.swap(idx, new), rep=self.superrep) + + +class MetaDims(type): + def __call__(cls, *args, rep=None): + if isinstance(args[0], list): + args = ( + Space(args[0][1], rep=rep), + Space(args[0][0], rep=rep) + ) + elif len(args) == 1 and isinstance(args[0], Dimensions): + return args[0] + elif len(args) != 2: + raise NotImplementedError('No Dual, Ket, Bra...', args) + elif ( + settings.core["auto_tidyup_dims"] + and args[0] == args[1] == Field() + ): + return Field() + + if args not in cls._stored_dims: + instance = cls.__new__(cls) + instance.__init__(*args) + cls._stored_dims[args] = instance + return cls._stored_dims[args] + + +class Dimensions(metaclass=MetaDims): + _stored_dims = {} + _type = None + + def __init__(self, from_, to_): + self.from_ = from_ + self.to_ = to_ + self.shape = to_.size, from_.size + self.issuper = from_.issuper or to_.issuper + self._pure_dims = from_._pure_dims and to_._pure_dims + self.issquare = False + if self.from_.size == 1 and self.to_.size == 1: + self.type = 'scalar' + self.issquare = True + self.superrep = "" + elif self.from_.size == 1: + self.type = 'operator-ket' if self.issuper else 'ket' + self.superrep = self.to_.superrep + elif self.to_.size == 1: + self.type = 'operator-bra' if self.issuper else 'bra' + self.superrep = self.from_.superrep + elif self.from_ == self.to_: + self.type = 'super' if self.issuper else 'oper' + self.superrep = self.from_.superrep + self.issquare = True + else: + self.type = 'super' if self.issuper else 'oper' + if self.from_.superrep == self.to_.superrep: + self.superrep = self.from_.superrep + else: + self.superrep = 'mixed' + self.__setitem__ = _frozen + + def __eq__(self, other): + return (self is other + or ( + type(self) is type(other) + and self.to_ == other.to_ + and self.from_ == other.from_ + ) + ) + + def __hash__(self): + return hash((self.to_, self.from_)) + + def __repr__(self): + return f"Dimensions({repr(self.from_)}, {repr(self.to_)})" + + def __str__(self): + return str(self.as_list()) + + def as_list(self): + return [self.to_.as_list(), self.from_.as_list()] + + def __getitem__(self, key): + if key == 0: + return self.to_ + elif key == 1: + return self.from_ + + def dims2idx(self, dims): + return self.to_.dims2idx(dims[0]), self.from_.dims2idx(dims[1]) + + def idx2dims(self, idxl, idxr): + return [self.to_.idx2dims(idxl), self.from_.idx2dims(idxr)] + + def step(self): + return [self.to_.step(), self.from_.step()] + + def flat(self): + return [self.to_.flat(), self.from_.flat()] + + def get_tensor_shape(self): + # dims_to_tensor_shape + stepl = self.to_.step() + flatl = self.to_.flat() + stepr = self.from_.step() + flatr = self.from_.flat() + return tuple(np.concatenate([ + np.array(flatl)[np.argsort(stepl)[::-1]], + np.array(flatr)[np.argsort(stepr)[::-1]], + ])) + + def get_tensor_perm(self): + # dims_to_tensor_perm + stepl = self.to_.step() + stepr = self.from_.step() + return list(np.concatenate([ + np.argsort(stepl)[::-1], + np.argsort(stepr)[::-1] + len(stepl) + ])) + + def remove(self, idx): + if not isinstance(idx, list): + idx = [idx] + if not idx: + return self + idx = sorted(idx) + n_indices = len(self.to_.flat()) + idx_to = [i for i in idx if i < n_indices] + idx_from = [i-n_indices for i in idx if i >= n_indices] + return Dimensions( + self.from_.remove(idx_from), + self.to_.remove(idx_to), + ) + + def replace(self, idx, new): + n_indices = len(self.to_.flat()) + if idx < n_indices: + new_to = self.to_.replace(idx, new) + new_from = self.from_ + else: + new_to = self.to_ + new_from = self.from_.replace(idx-n_indices, new) + + return Dimensions(new_from, new_to) diff --git a/qutip/core/energy_restricted.py b/qutip/core/energy_restricted.py new file mode 100644 index 0000000000..d65fbe709e --- /dev/null +++ b/qutip/core/energy_restricted.py @@ -0,0 +1,278 @@ +from .dimensions import Space +from .states import state_number_enumerate +from . import data as _data +from . import Qobj, qdiags +import numpy as np +import scipy.sparse + + +__all__ = ['enr_state_dictionaries', 'enr_fock', + 'enr_thermal_dm', 'enr_destroy', 'enr_identity'] + + +def enr_state_dictionaries(dims, excitations): + """ + Return the number of states, and lookup-dictionaries for translating + a state tuple to a state index, and vice versa, for a system with a given + number of components and maximum number of excitations. + + Parameters + ---------- + dims: list + A list with the number of states in each sub-system. + + excitations : integer + The maximum numbers of dimension + + Returns + ------- + nstates, state2idx, idx2state: integer, dict, dict + The number of states `nstates`, a dictionary for looking up state + indices from a state tuple, and a dictionary for looking up state + state tuples from state indices. + """ + nstates = 0 + state2idx = {} + idx2state = {} + + for state in state_number_enumerate(dims, excitations): + state2idx[state] = nstates + idx2state[nstates] = state + nstates += 1 + + return nstates, state2idx, idx2state + + +class EnrSpace(Space): + _stored_dims = {} + + def __init__(self, dims, excitations): + self.dims = tuple(dims) + self.n_excitations = excitations + enr_dicts = enr_state_dictionaries(dims, excitations) + self.size, self.state2idx, self.idx2state = enr_dicts + self.issuper = False + self.superrep = "" + self._pure_dims = False + + def __eq__(self, other): + return ( + self is other + or ( + type(other) is type(self) + and self.dims == other.dims + and self.n_excitations == other.n_excitations + ) + ) + + def __hash__(self): + return hash((self.dims, self.n_excitations)) + + def __repr__(self): + return f"EnrSpace({self.dims}, {self.n_excitations})" + + def as_list(self): + return list(self.dims) + + def dims2idx(self, dims): + return self.state2idx[tuple(dims)] + + def idx2dims(self, idx): + return self.idx2state[idx] + + +def enr_fock(dims, excitations, state, *, dtype=_data.Dense): + """ + Generate the Fock state representation in a excitation-number restricted + state space. The `dims` argument is a list of integers that define the + number of quantums states of each component of a composite quantum system, + and the `excitations` specifies the maximum number of excitations for + the basis states that are to be included in the state space. The `state` + argument is a tuple of integers that specifies the state (in the number + basis representation) for which to generate the Fock state representation. + + Parameters + ---------- + dims : list + A list of the dimensions of each subsystem of a composite quantum + system. + + excitations : integer + The maximum number of excitations that are to be included in the + state space. + + state : list of integers + The state in the number basis representation. + + dtype : type or str + Storage representation. Any data-layer known to `qutip.data.to` is + accepted. + + Returns + ------- + ket : Qobj + A Qobj instance that represent a Fock state in the exication-number- + restricted state space defined by `dims` and `exciations`. + + """ + nstates, state2idx, _ = enr_state_dictionaries(dims, excitations) + try: + data =_data.one_element[dtype]((nstates, 1), + (state2idx[tuple(state)], 0), 1) + except KeyError: + msg = ( + "state tuple " + str(tuple(state)) + + " is not in the restricted state space." + ) + raise ValueError(msg) from None + return Qobj(data, dims=[EnrSpace(dims, excitations), [1]*len(dims)], + type='ket', copy=False) + + +def enr_thermal_dm(dims, excitations, n, *, dtype=_data.CSR): + """ + Generate the density operator for a thermal state in the excitation-number- + restricted state space defined by the `dims` and `exciations` arguments. + See the documentation for enr_fock for a more detailed description of + these arguments. The temperature of each mode in dims is specified by + the average number of excitatons `n`. + + Parameters + ---------- + dims : list + A list of the dimensions of each subsystem of a composite quantum + system. + + excitations : integer + The maximum number of excitations that are to be included in the + state space. + + n : integer + The average number of exciations in the thermal state. `n` can be + a float (which then applies to each mode), or a list/array of the same + length as dims, in which each element corresponds specifies the + temperature of the corresponding mode. + + dtype : type or str + Storage representation. Any data-layer known to `qutip.data.to` is + accepted. + + Returns + ------- + dm : Qobj + Thermal state density matrix. + """ + nstates, _, idx2state = enr_state_dictionaries(dims, excitations) + enr_dims = [EnrSpace(dims, excitations)] * 2 + if not isinstance(n, (list, np.ndarray)): + n = np.ones(len(dims)) * n + else: + n = np.asarray(n) + + diags = [np.prod((n / (n + 1)) ** np.array(state)) + for idx, state in idx2state.items()] + diags /= np.sum(diags) + out = qdiags(diags, 0, dims=enr_dims, + shape=(nstates, nstates), dtype=dtype) + out._isherm = True + return out + + +def enr_destroy(dims, excitations, *, dtype=_data.CSR): + """ + Generate annilation operators for modes in a excitation-number-restricted + state space. For example, consider a system consisting of 4 modes, each + with 5 states. The total hilbert space size is 5**4 = 625. If we are + only interested in states that contain up to 2 excitations, we only need + to include states such as + + (0, 0, 0, 0) + (0, 0, 0, 1) + (0, 0, 0, 2) + (0, 0, 1, 0) + (0, 0, 1, 1) + (0, 0, 2, 0) + ... + + This function creates annihilation operators for the 4 modes that act + within this state space: + + a1, a2, a3, a4 = enr_destroy([5, 5, 5, 5], excitations=2) + + From this point onwards, the annihiltion operators a1, ..., a4 can be + used to setup a Hamiltonian, collapse operators and expectation-value + operators, etc., following the usual pattern. + + Parameters + ---------- + dims : list + A list of the dimensions of each subsystem of a composite quantum + system. + + excitations : integer + The maximum number of excitations that are to be included in the + state space. + + dtype : type or str + Storage representation. Any data-layer known to `qutip.data.to` is + accepted. + + Returns + ------- + a_ops : list of qobj + A list of annihilation operators for each mode in the composite + quantum system described by dims. + """ + nstates, _, idx2state = enr_state_dictionaries(dims, excitations) + enr_dims = [EnrSpace(dims, excitations)] * 2 + + a_ops = [scipy.sparse.lil_matrix((nstates, nstates), dtype=np.complex128) + for _ in dims] + + for n1, state1 in idx2state.items(): + for n2, state2 in idx2state.items(): + for idx, a in enumerate(a_ops): + s1 = [s for idx2, s in enumerate(state1) if idx != idx2] + s2 = [s for idx2, s in enumerate(state2) if idx != idx2] + if (state1[idx] == state2[idx] - 1) and (s1 == s2): + a[n1, n2] = np.sqrt(state2[idx]) + + return [Qobj(a, dims=enr_dims).to(dtype) for a in a_ops] + + +def enr_identity(dims, excitations, *, dtype=_data.CSR): + """ + Generate the identity operator for the excitation-number restricted + state space defined by the `dims` and `exciations` arguments. See the + docstring for enr_fock for a more detailed description of these arguments. + + Parameters + ---------- + dims : list + A list of the dimensions of each subsystem of a composite quantum + system. + + excitations : integer + The maximum number of excitations that are to be included in the + state space. + + state : list of integers + The state in the number basis representation. + + dtype : type or str + Storage representation. Any data-layer known to `qutip.data.to` is + accepted. + + Returns + ------- + op : Qobj + A Qobj instance that represent the identity operator in the + exication-number-restricted state space defined by `dims` and + `exciations`. + """ + dims = [EnrSpace(dims, excitations)] * 2 + return Qobj(_data.identity[dtype](dims[0].size), + dims=dims, + isherm=True, + isunitary=True, + copy=False) diff --git a/qutip/core/operators.py b/qutip/core/operators.py index 140ff82d59..455ab529e5 100644 --- a/qutip/core/operators.py +++ b/qutip/core/operators.py @@ -7,8 +7,8 @@ 'spin_J_set', 'sigmap', 'sigmam', 'sigmax', 'sigmay', 'sigmaz', 'destroy', 'create', 'qeye', 'identity', 'position', 'momentum', 'num', 'squeeze', 'squeezing', 'displace', 'commutator', - 'qutrit_ops', 'qdiags', 'phase', 'qzero', 'enr_destroy', - 'enr_identity', 'charge', 'tunneling', 'qft'] + 'qutrit_ops', 'qdiags', 'phase', 'qzero', 'charge', 'tunneling', + 'qft'] import numbers @@ -849,110 +849,6 @@ def phase(N, phi0=0, *, dtype=_data.Dense): return Qobj(ops, dims=[[N], [N]], type='oper', copy=False).to(dtype) -def enr_destroy(dims, excitations, *, dtype=_data.CSR): - """ - Generate annilation operators for modes in a excitation-number-restricted - state space. For example, consider a system consisting of 4 modes, each - with 5 states. The total hilbert space size is 5**4 = 625. If we are - only interested in states that contain up to 2 excitations, we only need - to include states such as - - (0, 0, 0, 0) - (0, 0, 0, 1) - (0, 0, 0, 2) - (0, 0, 1, 0) - (0, 0, 1, 1) - (0, 0, 2, 0) - ... - - This function creates annihilation operators for the 4 modes that act - within this state space: - - a1, a2, a3, a4 = enr_destroy([5, 5, 5, 5], excitations=2) - - From this point onwards, the annihiltion operators a1, ..., a4 can be - used to setup a Hamiltonian, collapse operators and expectation-value - operators, etc., following the usual pattern. - - Parameters - ---------- - dims : list - A list of the dimensions of each subsystem of a composite quantum - system. - - excitations : integer - The maximum number of excitations that are to be included in the - state space. - - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is - accepted. - - Returns - ------- - a_ops : list of qobj - A list of annihilation operators for each mode in the composite - quantum system described by dims. - """ - from .states import enr_state_dictionaries - - nstates, state2idx, idx2state = enr_state_dictionaries(dims, excitations) - - a_ops = [scipy.sparse.lil_matrix((nstates, nstates), dtype=np.complex128) - for _ in dims] - - for n1, state1 in enumerate(idx2state): - for idx, s in enumerate(state1): - # if s > 0, the annihilation operator of mode idx has a non-zero - # entry with one less excitation in mode idx in the final state - if s > 0: - state2 = state1[:idx] + (s-1,) + state1[idx+1:] - n2 = state2idx[state2] - a_ops[idx][n2, n1] = np.sqrt(s) - - return [Qobj(a, dims=[dims, dims]).to(dtype) for a in a_ops] - - -def enr_identity(dims, excitations, *, dtype=_data.CSR): - """ - Generate the identity operator for the excitation-number restricted - state space defined by the `dims` and `exciations` arguments. See the - docstring for enr_fock for a more detailed description of these arguments. - - Parameters - ---------- - dims : list - A list of the dimensions of each subsystem of a composite quantum - system. - - excitations : integer - The maximum number of excitations that are to be included in the - state space. - - state : list of integers - The state in the number basis representation. - - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is - accepted. - - Returns - ------- - op : Qobj - A Qobj instance that represent the identity operator in the - exication-number-restricted state space defined by `dims` and - `exciations`. - """ - from .states import enr_state_dictionaries - nstates, _, _ = enr_state_dictionaries(dims, excitations) - return Qobj(_data.identity[dtype](nstates), - dims=[dims, dims], - type='oper', - isherm=True, - isunitary=True, - copy=False) - - def charge(Nmax, Nmin=None, frac=1, *, dtype=_data.CSR): """ Generate the diagonal charge operator over charge states diff --git a/qutip/core/options.py b/qutip/core/options.py index 19adef49a3..0e6cb9e5c9 100644 --- a/qutip/core/options.py +++ b/qutip/core/options.py @@ -59,8 +59,12 @@ class CoreOptions(QutipOptions): auto_tidyup : bool Whether to tidyup during sparse operations. - auto_tidyup_dims : bool [True] - use auto tidyup dims on multiplication + auto_tidyup_dims : bool [False] + Use auto tidyup dims on multiplication, tensor, etc. + Without auto_tidyup_dims: + ``basis([2, 2]).dims == [[2, 2], [1, 1]]`` + With auto_tidyup_dims: + ``basis([2, 2]).dims == [[2, 2], [1]]`` auto_herm : boolTrue detect hermiticity @@ -97,7 +101,7 @@ class CoreOptions(QutipOptions): # use auto tidyup "auto_tidyup": True, # use auto tidyup dims on multiplication - "auto_tidyup_dims": True, + "auto_tidyup_dims": False, # detect hermiticity "auto_herm": True, # general absolute tolerance diff --git a/qutip/core/qobj.py b/qutip/core/qobj.py index 215748ae95..71c59a5116 100644 --- a/qutip/core/qobj.py +++ b/qutip/core/qobj.py @@ -18,30 +18,10 @@ from ..settings import settings from . import data as _data from .dimensions import ( - type_from_dims, enumerate_flat, collapse_dims_super, flatten, unflatten, + enumerate_flat, collapse_dims_super, flatten, unflatten, Dimensions ) -_ADJOINT_TYPE_LOOKUP = { - 'oper': 'oper', - 'super': 'super', - 'ket': 'bra', - 'bra': 'ket', - 'operator-ket': 'operator-bra', - 'operator-bra': 'operator-ket', -} - -_MATMUL_TYPE_LOOKUP = { - ('oper', 'ket'): 'ket', - ('oper', 'oper'): 'oper', - ('ket', 'bra'): 'oper', - ('bra', 'oper'): 'bra', - ('super', 'super'): 'super', - ('super', 'operator-ket'): 'operator-ket', - ('operator-bra', 'super'): 'operator-bra', - ('operator-ket', 'operator-bra'): 'super', -} - _NORM_FUNCTION_LOOKUP = { 'tr': _data.norm.trace, 'one': _data.norm.one, @@ -60,27 +40,33 @@ def isbra(x): - return isinstance(x, Qobj) and x.type == 'bra' + from .cy.qobjevo import QobjEvo + return isinstance(x, (Qobj, QobjEvo)) and x.type in ['bra', 'scalar'] def isket(x): - return isinstance(x, Qobj) and x.type == 'ket' + from .cy.qobjevo import QobjEvo + return isinstance(x, (Qobj, QobjEvo)) and x.type in ['ket', 'scalar'] def isoper(x): - return isinstance(x, Qobj) and x.type == 'oper' + from .cy.qobjevo import QobjEvo + return isinstance(x, (Qobj, QobjEvo)) and x.type in ['oper', 'scalar'] def isoperbra(x): - return isinstance(x, Qobj) and x.type == 'operator-bra' + from .cy.qobjevo import QobjEvo + return isinstance(x, (Qobj, QobjEvo)) and x.type in ['operator-bra'] def isoperket(x): - return isinstance(x, Qobj) and x.type == 'operator-ket' + from .cy.qobjevo import QobjEvo + return isinstance(x, (Qobj, QobjEvo)) and x.type in ['operator-ket'] def issuper(x): - return isinstance(x, Qobj) and x.type == 'super' + from .cy.qobjevo import QobjEvo + return isinstance(x, (Qobj, QobjEvo)) and x.type in ['super'] def isherm(x): @@ -99,15 +85,13 @@ def out(self, other): return method(self, other) if ( self.type in ('oper', 'super') - and self.dims[0] == self.dims[1] + and self._dims[0] == self._dims[1] and isinstance(other, numbers.Number) ): scale = complex(other) other = Qobj(_data.identity(self.shape[0], scale, dtype=type(self.data)), - dims=self.dims, - type=self.type, - superrep=self.superrep, + dims=self._dims, isherm=(scale.imag == 0), isunitary=(abs(abs(scale)-1) < settings.core['atol']), copy=False) @@ -116,21 +100,12 @@ def out(self, other): other = Qobj(other, type=self.type) except TypeError: return NotImplemented - if self.dims != other.dims: + if self._dims != other._dims: msg = ( "incompatible dimensions " + repr(self.dims) + " and " + repr(other.dims) ) raise ValueError(msg) - if self.type != other.type: - msg = "incompatible types " + self.type + " and " + other.type - raise ValueError(msg) - if self.superrep != other.superrep: - msg = ( - "incompatible superoperator representations" - + self.superrep + " and " + other.superrep - ) - raise ValueError(msg) return method(self, other) return out @@ -297,57 +272,97 @@ class Qobj: def _initialize_data(self, arg, dims, copy): if isinstance(arg, _data.Data): - self.dims = dims or [[arg.shape[0]], [arg.shape[1]]] self._data = arg.copy() if copy else arg + self.dims = dims or [[arg.shape[0]], [arg.shape[1]]] elif isinstance(arg, Qobj): - self.dims = dims or arg.dims.copy() self._data = arg.data.copy() if copy else arg.data + if dims: + self.dims = dims + else: + self._dims = arg._dims elif arg is None or isinstance(arg, numbers.Number): self.dims = dims or [[1], [1]] - size = np.prod(self.dims[0]) + size = self._dims[0].size if arg is None: self._data = _data.zeros(size, size) else: self._data = _data.identity(size, scale=complex(arg)) else: self._data = _data.create(arg, copy=copy) + if ( + dims + and self._data.shape != dims.shape + and self._data.shape == dims.shape[::-1] + ): + self._data = _data.transpose(self._data) self.dims = dims or [[self._data.shape[0]], [self._data.shape[1]]] def __init__(self, arg=None, dims=None, type=None, copy=True, superrep=None, isherm=None, isunitary=None): - self._initialize_data(arg, dims, copy) - self.type = type or type_from_dims(self.dims) + self._dims = None + self._data = None + self.type = None self._isherm = isherm self._isunitary = isunitary + self._superrep = None + if isinstance(dims, list): + dims = Dimensions(dims, rep=superrep) + self._initialize_data(arg, dims, copy) + self.type = type or self._dims.type - if self.type == 'super' and type_from_dims(self.dims) == 'oper': - if self._data.shape[0] != self._data.shape[1]: - raise ValueError("".join([ - "cannot build superoperator from nonsquare data of shape ", - repr(self._data.shape), - ])) - root = int(np.sqrt(self._data.shape[0])) - if root * root != self._data.shape[0]: - raise ValueError("".join([ - "cannot build superoperator from nonsquare subspaces ", - "of size ", - repr(self._data.shape[0]), - ])) - self.dims = [[[root]]*2]*2 - if self.type in ['super', 'operator-ket', 'operator-bra']: - superrep = superrep or 'super' - self.superrep = superrep + # Dims are guessed from the data and need to be changed to super. + if ( + type in ['super', 'operator-ket', 'operator-bra'] + and self.type in ['oper', 'ket', 'bra'] + ): + root_right = int(np.sqrt(self._data.shape[0])) + root_left = int(np.sqrt(self._data.shape[1])) + if ( + root_right * root_right != self._data.shape[0] + and root_left * root_left != self._data.shape[1] + ): + raise ValueError( + "cannot build superoperator from nonsquare subspaces" + ) + self.dims = [[[root_right]]*2, [[root_left]]*2] + if superrep and self.type in ['super', 'operator-ket', 'operator-bra']: + self.superrep = superrep def copy(self): """Create identical copy""" return Qobj(arg=self._data, dims=self.dims, - type=self.type, - superrep=self.superrep, isherm=self._isherm, isunitary=self._isunitary, copy=True) + @property + def dims(self): + return self._dims.as_list() + + @dims.setter + def dims(self, dims): + dims = Dimensions(dims, rep=self.superrep) + if self._data and dims.shape != self._data.shape: + raise ValueError('Provided dimensions do not match the data: ' + + f"{dims.shape} vs {self._data.shape}") + self._dims = dims + self.type = self._dims.type + + @property + def superrep(self): + if self._superrep: + return self._superrep + elif self.type in ['super', 'operator-ket', 'operator-bra']: + return self._dims.superrep + else: + return None + + @superrep.setter + def superrep(self, super_rep): + self._dims = Dimensions(self._dims.as_list(), rep=super_rep) + self._superrep = super_rep + @property def data(self): return self._data @@ -393,10 +408,9 @@ def to(self, data_type): if type(self.data) is data_type: return self return Qobj(converter(self._data), - dims=self.dims, - type=self.type, - superrep=self.superrep, + dims=self._dims, isherm=self._isherm, + superrep=self.superrep, isunitary=self._isunitary, copy=False) @@ -406,8 +420,7 @@ def __add__(self, other): return self.copy() isherm = (self._isherm and other._isherm) or None return Qobj(_data.add(self._data, other._data), - dims=self.dims, - type=self.type, + dims=self._dims, superrep=self.superrep, isherm=isherm, copy=False) @@ -421,8 +434,7 @@ def __sub__(self, other): return self.copy() isherm = (self._isherm and other._isherm) or None return Qobj(_data.sub(self._data, other._data), - dims=self.dims, - type=self.type, + dims=self._dims, superrep=self.superrep, isherm=isherm, copy=False) @@ -459,10 +471,9 @@ def __mul__(self, other): isunitary = None return Qobj(out, - dims=self.dims, - type=self.type, - superrep=self.superrep, + dims=self._dims, isherm=isherm, + superrep=self.superrep, isunitary=isunitary, copy=False) @@ -477,38 +488,22 @@ def __matmul__(self, other): other = Qobj(other) except TypeError: return NotImplemented - if self.dims[1] != other.dims[0]: + if self._dims[1] != other._dims[0]: raise TypeError("".join([ "incompatible dimensions ", repr(self.dims), " and ", repr(other.dims), ])) - if self.superrep != other.superrep: - raise TypeError("".join([ - "incompatible superoperator representations ", - repr(self.superrep), - " and ", - repr(other.superrep), - ])) if ( (self.isbra and other.isket) or (self.isoperbra and other.isoperket) ): return _data.inner(self.data, other.data) - try: - type_ = _MATMUL_TYPE_LOOKUP[(self.type, other.type)] - except KeyError: - raise TypeError( - "incompatible matmul types " - + repr(self.type) + " and " + repr(other.type) - ) from None return Qobj(_data.matmul(self.data, other.data), - dims=[self.dims[0], other.dims[1]], - type=type_, + dims=Dimensions(other._dims[1], self._dims[0]), isunitary=self._isunitary and other._isunitary, - superrep=self.superrep, copy=False) def __truediv__(self, other): @@ -518,9 +513,7 @@ def __truediv__(self, other): def __neg__(self): return Qobj(_data.neg(self._data), - dims=self.dims.copy(), - type=self.type, - superrep=self.superrep, + dims=self._dims, isherm=self._isherm, isunitary=self._isunitary, copy=False) @@ -544,7 +537,7 @@ def __getitem__(self, ind): def __eq__(self, other): if self is other: return True - if not isinstance(other, Qobj) or self.dims != other.dims: + if not isinstance(other, Qobj) or self._dims != other._dims: return False return _data.iszero(_data.sub(self._data, other._data), tol=settings.core['atol']) @@ -552,7 +545,7 @@ def __eq__(self, other): def __pow__(self, n, m=None): # calculates powers of Qobj if ( self.type not in ('oper', 'super') - or self.dims[0] != self.dims[1] + or self._dims[0] != self._dims[1] or m is not None or not isinstance(n, numbers.Integral) or n < 0 @@ -664,9 +657,7 @@ def dag(self): if self._isherm: return self.copy() return Qobj(_data.adjoint(self._data), - dims=[self.dims[1], self.dims[0]], - type=_ADJOINT_TYPE_LOOKUP[self.type], - superrep=self.superrep, + dims=Dimensions(self._dims[0], self._dims[1]), isherm=self._isherm, isunitary=self._isunitary, copy=False) @@ -674,9 +665,7 @@ def dag(self): def conj(self): """Get the element-wise conjugation of the quantum object.""" return Qobj(_data.conj(self._data), - dims=self.dims.copy(), - type=self.type, - superrep=self.superrep, + dims=self._dims, isherm=self._isherm, isunitary=self._isunitary, copy=False) @@ -690,9 +679,7 @@ def trans(self): Transpose of input operator. """ return Qobj(_data.transpose(self._data), - dims=[self.dims[1], self.dims[0]], - type=_ADJOINT_TYPE_LOOKUP[self.type], - superrep=self.superrep, + dims=Dimensions(self._dims[0], self._dims[1]), isherm=self._isherm, isunitary=self._isunitary, copy=False) @@ -767,11 +754,10 @@ def proj(self): """ if not (self.isket or self.isbra): raise TypeError("projection is only defined for bras and kets") - dims = ([self.dims[0], self.dims[0]] if self.isket - else [self.dims[1], self.dims[1]]) + dims = ([self._dims[0], self._dims[0]] if self.isket + else [self._dims[1], self._dims[1]]) return Qobj(_data.project(self._data), dims=dims, - type='oper', isherm=True, copy=False) @@ -860,12 +846,10 @@ def expm(self, dtype=_data.Dense): TypeError Quantum operator is not square. """ - if self.dims[0] != self.dims[1]: + if self._dims[0] != self._dims[1]: raise TypeError("expm is only valid for square operators") return Qobj(_data.expm(self._data, dtype=dtype), - dims=self.dims, - type=self.type, - superrep=self.superrep, + dims=self._dims, isherm=self._isherm, copy=False) @@ -908,7 +892,7 @@ def sqrtm(self, sparse=False, tol=0, maxiter=100000): The sparse eigensolver is much slower than the dense version. Use sparse only if memory requirements demand it. """ - if self.dims[0] != self.dims[1]: + if self._dims[0] != self._dims[1]: raise TypeError('sqrt only valid on square matrices') if isinstance(self.data, _data.CSR) and sparse: evals, evecs = _data.eigs_csr(self.data, @@ -927,9 +911,7 @@ def sqrtm(self, sparse=False, tol=0, maxiter=100000): else: spDv = _data.matmul(dV, _data.inv(evecs)) return Qobj(_data.matmul(evecs, spDv), - dims=self.dims, - type=self.type, - superrep=self.superrep, + dims=self._dims, copy=False) def cosm(self): @@ -952,7 +934,7 @@ def cosm(self): Uses the Q.expm() method. """ - if self.dims[0] != self.dims[1]: + if self._dims[0] != self._dims[1]: raise TypeError('invalid operand for matrix cosine') return 0.5 * ((1j * self).expm() + (-1j * self).expm()) @@ -975,7 +957,7 @@ def sinm(self): ----- Uses the Q.expm() method. """ - if self.dims[0] != self.dims[1]: + if self._dims[0] != self._dims[1]: raise TypeError('invalid operand for matrix sine') return -0.5j * ((1j * self).expm() - (-1j * self).expm()) @@ -1002,9 +984,7 @@ def inv(self, sparse=False): data = self.data return Qobj(_data.inv(data), - dims=[self.dims[1], self.dims[0]], - type=self.type, - superrep=self.superrep, + dims=[self._dims[1], self._dims[0]], copy=False) def unit(self, inplace=False, norm=None, kwargs=None): @@ -1101,7 +1081,10 @@ def ptrace(self, sel, dtype=None): raise ValueError("partial trace is not defined on non-square maps") dims = flatten(dims[0]) new_data = _data.ptrace(data, dims, sel, dtype=dtype) - new_dims = [[dims[x] for x in sel]] * 2 + if sel: + new_dims = [[dims[x] for x in sel]] * 2 + else: + new_dims = None out = Qobj(new_data, dims=new_dims, type='oper', copy=False) if self.isoperket: return operator_to_vector(out) @@ -1204,13 +1187,12 @@ def permute(self, order): elif self.isket: dims = [new_structure, self.dims[1]] else: - if self.dims[0] != self.dims[1]: + if self._dims[0] != self._dims[1]: raise TypeError("undefined for non-square operators") dims = [new_structure, new_structure] data = _data.permute.dimensions(self.data, structure, order) return Qobj(data, dims=dims, - type=self.type, isherm=self._isherm, isunitary=self._isunitary, copy=False) @@ -1226,13 +1208,12 @@ def permute(self, order): elif self.isoperket: dims = [new_structure, self.dims[1]] else: - if self.dims[0] != self.dims[1]: + if self._dims[0] != self._dims[1]: raise TypeError("undefined for non-square operators") dims = [new_structure, new_structure] data = _data.permute.dimensions(self.data, flat_structure, flat_order) return Qobj(data, dims=dims, - type=self.type, superrep=self.superrep, copy=False) @@ -1308,7 +1289,6 @@ def transform(self, inpt, inverse=False): data = _data.matmul(_data.matmul(S, self.data), S.adjoint()) return Qobj(data, dims=self.dims, - type=self.type, isherm=self._isherm, superrep=self.superrep, copy=False) @@ -1320,7 +1300,6 @@ def trunc_neg(self, method="clip"): of this instance, then renormalizing to obtain a valid density operator. - Parameters ---------- method : str @@ -1369,11 +1348,7 @@ def trunc_neg(self, method="clip"): _data.project(state.data), value) out_data = _data.mul(out_data, 1/_data.norm.trace(out_data)) - return Qobj(out_data, - dims=self.dims.copy(), - type=self.type, - isherm=True, - copy=False) + return Qobj(out_data, dims=self._dims, isherm=True, copy=False) def matrix_element(self, bra, ket): """Calculates a matrix element. @@ -1691,7 +1666,6 @@ def istp(self): for index in super_index]): qobj = Qobj(qobj.data, dims=collapse_dims_super(qobj.dims), - type=qobj.type, superrep=qobj.superrep, copy=False) # We use the condition from John Watrous' lecture notes, diff --git a/qutip/core/states.py b/qutip/core/states.py index bd820e0d88..b612f5083c 100644 --- a/qutip/core/states.py +++ b/qutip/core/states.py @@ -4,8 +4,7 @@ 'state_number_index', 'state_index_number', 'state_number_qobj', 'phase_basis', 'zero_ket', 'spin_state', 'spin_coherent', 'bell_state', 'singlet_state', 'triplet_states', 'w_state', - 'ghz_state', 'enr_state_dictionaries', 'enr_fock', - 'enr_thermal_dm'] + 'ghz_state'] import itertools import numbers @@ -911,132 +910,6 @@ def state_number_qobj(dims, state, *, dtype=_data.Dense): return basis(dims, state, dtype=dtype) -# Excitation-number restricted (enr) states - -def enr_state_dictionaries(dims, excitations): - """ - Return the number of states, and lookup-dictionaries for translating - a state tuple to a state index, and vice versa, for a system with a given - number of components and maximum number of excitations. - - Parameters - ---------- - dims: list - A list with the number of states in each sub-system. - - excitations : integer - The maximum numbers of dimension - - Returns - ------- - nstates, state2idx, idx2state: integer, dict, list - The number of states `nstates`, a dictionary for looking up state - indices from a state tuple, and a list containing the state tuples - ordered by state indices. state2idx and idx2state are reverses of - each other, i.e., state2idx[idx2state[idx]] = idx and - idx2state[state2idx[state]] = state. - """ - idx2state = list(state_number_enumerate(dims, excitations)) - state2idx = {state: idx for idx, state in enumerate(idx2state)} - nstates = len(idx2state) - - return nstates, state2idx, idx2state - - -def enr_fock(dims, excitations, state, *, dtype=_data.Dense): - """ - Generate the Fock state representation in a excitation-number restricted - state space. The `dims` argument is a list of integers that define the - number of quantums states of each component of a composite quantum system, - and the `excitations` specifies the maximum number of excitations for - the basis states that are to be included in the state space. The `state` - argument is a tuple of integers that specifies the state (in the number - basis representation) for which to generate the Fock state representation. - - Parameters - ---------- - dims : list - A list of the dimensions of each subsystem of a composite quantum - system. - - excitations : integer - The maximum number of excitations that are to be included in the - state space. - - state : list of integers - The state in the number basis representation. - - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is - accepted. - - Returns - ------- - ket : Qobj - A Qobj instance that represent a Fock state in the exication-number- - restricted state space defined by `dims` and `exciations`. - - """ - nstates, state2idx, _ = enr_state_dictionaries(dims, excitations) - try: - data =_data.one_element[dtype]((nstates, 1), - (state2idx[tuple(state)], 0), 1) - except KeyError: - msg = ( - "The state tuple " + str(tuple(state)) - + " is not in the restricted state space." - ) - raise ValueError(msg) from None - return Qobj(data, dims=[dims, [1]*len(dims)], type='ket', copy=False) - - -def enr_thermal_dm(dims, excitations, n, *, dtype=_data.CSR): - """ - Generate the density operator for a thermal state in the excitation-number- - restricted state space defined by the `dims` and `exciations` arguments. - See the documentation for enr_fock for a more detailed description of - these arguments. The temperature of each mode in dims is specified by - the average number of excitatons `n`. - - Parameters - ---------- - dims : list - A list of the dimensions of each subsystem of a composite quantum - system. - - excitations : integer - The maximum number of excitations that are to be included in the - state space. - - n : integer - The average number of exciations in the thermal state. `n` can be - a float (which then applies to each mode), or a list/array of the same - length as dims, in which each element corresponds specifies the - temperature of the corresponding mode. - - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is - accepted. - - Returns - ------- - dm : Qobj - Thermal state density matrix. - """ - nstates, _, idx2state = enr_state_dictionaries(dims, excitations) - if not isinstance(n, (list, np.ndarray)): - n = np.ones(len(dims)) * n - else: - n = np.asarray(n) - - diags = [np.prod((n / (n + 1)) ** np.array(state)) for state in idx2state] - diags /= np.sum(diags) - out = qdiags(diags, 0, dims=[dims, dims], - shape=(nstates, nstates), dtype=dtype) - out._isherm = True - return out - - def phase_basis(N, m, phi0=0, *, dtype=_data.Dense): """ Basis vector for the mth phase of the Pegg-Barnett phase operator. diff --git a/qutip/core/superop_reps.py b/qutip/core/superop_reps.py index c48f05fd1a..f22ed73595 100644 --- a/qutip/core/superop_reps.py +++ b/qutip/core/superop_reps.py @@ -324,8 +324,8 @@ def _choi_to_stinespring(q_oper, threshold=1e-10): B += tensor(KR, basis(dK, idx_kraus)) # There is no input (right) Kraus index, so strip that off. - del A.dims[1][-1] - del B.dims[1][-1] + A.dims = [out_left + [dK], out_right] + B.dims = [in_left + [dK], in_right] return A, B diff --git a/qutip/core/superoperator.py b/qutip/core/superoperator.py index 972d3db625..9e9c81f701 100644 --- a/qutip/core/superoperator.py +++ b/qutip/core/superoperator.py @@ -10,6 +10,7 @@ from .qobj import Qobj from . import data as _data +from .dimensions import Compound, SuperSpace, Space def _map_over_compound_operators(f): """ @@ -387,26 +388,98 @@ def sprepost(A, B): copy=False) +def _to_super_of_tensor(q_oper): + """ + Transform a superoperator composed of multiple space into a superoperator + over a composite spaces. + """ + msg = "Reshuffling is only supported for square operators." + if not q_oper._dims.issuper: + raise TypeError("Reshuffling is only supported on type='super' " + "or type='operator-ket'.") + if q_oper.isoper and not q_oper._dims.issquare: + raise NotImplementedError(msg) + + dims = q_oper._dims[0] + if isinstance(dims, SuperSpace): + return q_oper.copy() + + perm_idxs = [[], []] + + if isinstance(dims, Compound): + shift = 0 + for space in dims.spaces: + if not isinstance(space, SuperSpace) or not space.oper.issquare: + raise NotImplementedError(msg) + space_dims = space.oper.to_ + if type(space_dims) is Space: + perm_idxs[0] += [shift] + perm_idxs[1] += [shift + 1] + shift += 2 + elif isinstance(space_dims, Compound): + N = len(space_dims.spaces) + perm_idxs[0] += [shift + i for i in range(N)] + perm_idxs[1] += [shift + N + i for i in range(N)] + shift += 2 * N + else: + # ENR space or other complex spaces + raise NotImplementedError("Reshuffling with non standard space" + "is not supported.") + + return q_oper.permute(perm_idxs) + + +def _to_tensor_of_super(q_oper): + """ + Transform a superoperator composed of multiple space into a tensor of + superoperator on each spaces. + """ + msg = "Reshuffling is only supported for square operators." + if not q_oper._dims[0].issuper: + raise TypeError("Reshuffling is only supported on type='super' " + "or type='operator-ket'.") + + dims = q_oper._dims[0] + perm_idxs = [] + + if isinstance(dims, Compound): + shift = 0 + for space in dims.spaces: + if not isinstance(space, SuperSpace) or not space.oper.issquare: + raise TypeError(msg) + space_dims = space.oper.to_ + if type(space_dims) is Space: + perm_idxs += [[shift], [shift + 1]] + shift += 2 + elif isinstance(space_dims, Compound): + N = len(space_dims.spaces) + idxs = range(0, N * 2, 2) + perm_idxs += [[i + shift] for i in idxs] + perm_idxs += [[i + shift + 1] for i in idxs] + shift += N * 2 + else: + # ENR space or other complex spaces + raise NotImplementedError("Reshuffling with non standard space" + "is not supported.") + elif isinstance(dims, SuperSpace): + if isinstance(dims.oper.to_, Compound): + step = len(dims.oper.to_.spaces) + perm_idxs = sum([[[i], [i+step]] for i in range(step)], []) + else: + return q_oper + + return q_oper.permute(perm_idxs) + + def reshuffle(q_oper): """ - Column-reshuffles a ``type="super"`` Qobj. + Column-reshuffles a super operator or a operator-ket Qobj. """ - if q_oper.type not in ('super', 'operator-ket'): + if q_oper.type not in ["super", "operator-ket"]: raise TypeError("Reshuffling is only supported on type='super' " "or type='operator-ket'.") - # How many indices are there, and how many subsystems can we decompose - # each index into? - n_indices = len(q_oper.dims[0]) - n_subsystems = len(q_oper.dims[0][0]) - # Generate a list of lists (lol) that represents the permutation order we - # need. It's easiest to do so if we make an array, then turn it into a lol - # by using map(list, ...). That array is generated by using reshape and - # transpose to turn an array like [a, b, a, b, ..., a, b] into one like - # [a, a, ..., a, b, b, ..., b]. - perm_idxs = map(list, - np.arange(n_subsystems * n_indices)[ - np.arange(n_subsystems * n_indices).reshape( - (n_indices, n_subsystems)).T.flatten() - ].reshape((n_subsystems, n_indices)) - ) - return q_oper.permute(list(perm_idxs)) + + if isinstance(q_oper._dims[0], Compound): + return _to_super_of_tensor(q_oper) + else: + return _to_tensor_of_super(q_oper) diff --git a/qutip/random_objects.py b/qutip/random_objects.py index b0bcf85c18..fcf69f60c5 100644 --- a/qutip/random_objects.py +++ b/qutip/random_objects.py @@ -488,7 +488,7 @@ def rand_ket(dimensions, density=1, distribution="haar", *, Y.data = 1.0j * (generator.random(len(X.data)) - 0.5) X = _data.csr.CSR(X + Y) ket = Qobj(_data.mul(X, 1 / _data.norm.l2(X)), - copy=False, type="ket", isherm=False, isunitary=False) + copy=False, isherm=False, isunitary=False) ket.dims = [dims[0], [1]] return ket.to(dtype) @@ -583,7 +583,7 @@ def rand_dm(dimensions, density=0.75, distribution="ginibre", *, H = _merge_shuffle_blocks(blocks, generator) H /= H.trace() - return Qobj(H, dims=dims, type='oper', isherm=True, copy=False).to(dtype) + return Qobj(H, dims=dims, isherm=True, copy=False).to(dtype) def _rand_dm_ginibre(N, rank, generator): @@ -653,7 +653,7 @@ def rand_kraus_map(dimensions, *, seed=None, dtype=_data.Dense): big_unitary = rand_unitary(N ** 3, seed=seed, dtype=dtype).full() orthog_cols = np.array(big_unitary[:, :N]) oper_list = np.reshape(orthog_cols, (N ** 2, N, N)) - return [Qobj(x, dims=dims, type='oper', copy=False).to(dtype) + return [Qobj(x, dims=dims, copy=False).to(dtype) for x in oper_list] diff --git a/qutip/solve/mesolve.py b/qutip/solve/mesolve.py index cc95e0816a..f8728c9d9a 100644 --- a/qutip/solve/mesolve.py +++ b/qutip/solve/mesolve.py @@ -524,12 +524,18 @@ def get_curr_state_data(r): copy=False))) for m in range(n_expt_op): - if cdata.shape[1] == size: - cdata = _data.column_stack_dense(cdata, inplace=False) if not isinstance(e_ops[m], Qobj) and callable(e_ops[m]): - output.expect[m][t_idx] = e_ops[m](t, Qobj(cdata, dims=dims)) + if cdata.shape[0] != size: + qdata = _data.column_unstack_dense(cdata, size, inplace=False) + else: + qdata = cdata + output.expect[m][t_idx] = e_ops[m](t, Qobj(qdata, dims=dims)) else: - val = _data.expect_super(e_ops_data[m], cdata) + if cdata.shape[1] != 1: + qdata = _data.column_stack_dense(cdata, inplace=False) + else: + qdata = cdata + val = _data.expect_super(e_ops_data[m], qdata) if e_ops[m].isherm and rho0.isherm: val = val.real output.expect[m][t_idx] = val diff --git a/qutip/tests/core/test_dimensions.py b/qutip/tests/core/test_dimensions.py index a32c302941..80cebf3f04 100644 --- a/qutip/tests/core/test_dimensions.py +++ b/qutip/tests/core/test_dimensions.py @@ -4,47 +4,11 @@ import collections import qutip from qutip.core.dimensions import ( - type_from_dims, flatten, unflatten, enumerate_flat, deep_remove, deep_map, + flatten, unflatten, enumerate_flat, deep_remove, deep_map, dims_idxs_to_tensor_idxs, dims_to_tensor_shape, dims_to_tensor_perm, - collapse_dims_super, collapse_dims_oper, + collapse_dims_super, collapse_dims_oper, Dimensions ) -_v = "vector" -_vo = "vectorized_oper" - - -@pytest.mark.parametrize(["rank", "actual_type", "scalar"], [ - pytest.param([1], _v, True, id="scalar"), - pytest.param([1, 1], _v, True, id="tensor scalar"), - pytest.param([[1]], _vo, True, id="nested scalar"), - pytest.param([[1], [1]], _vo, True, id="nested tensor scalar 1"), - pytest.param([[1, 1]], _vo, True, id="nested tensor scalar 2"), - pytest.param([2], _v, False, id="vector"), - pytest.param([2, 3], _v, False, id="tensor vector"), - pytest.param([1, 2, 3], _v, False, id="tensor vector with 1d subspace 1"), - pytest.param([2, 1, 1], _v, False, id="tensor vector with 1d subspace 2"), - pytest.param([[2]], _vo, False, id="vectorised operator"), - pytest.param([[2, 3]], _vo, False, id="vector tensor operator"), - pytest.param([[1, 3]], _vo, False, id="vector tensor operator with 1d"), -]) -@pytest.mark.parametrize("test_type", ["scalar", _v, _vo]) -def test_rank_type_detection(rank, actual_type, scalar, test_type): - """ - Test the rank detection tests `is_scalar`, `is_vector` and - `is_vectorized_oper` for a range of different test cases. These tests are - designed to be called on individual elements of the two-element `dims` - parameter of `Qobj`s, so they're testing the row-rank and column-rank. - - It's possible to be both a scalar and something else, but "vector" and - "vectorized_oper" are mutually exclusive. - - These functions aren't properly specified for improper dimension setups, so - there are no tests for those. - """ - expected = scalar if test_type == "scalar" else (actual_type == test_type) - function = getattr(qutip.dimensions, "is_" + test_type) - assert function(rank) == expected - @pytest.mark.parametrize(["base", "flat"], [ pytest.param([[[0], 1], 2], [0, 1, 2], id="standard"), @@ -116,23 +80,23 @@ def test_deep_map(base, mapping): @pytest.mark.parametrize("indices", [ pytest.param(_Indices([[2], [1]], [0, 1], (2, 1)), id="ket preserved"), - pytest.param(_Indices([[2, 3], [1, 1]], [0, 1, 2, 3], (2, 3, 1, 1)), + pytest.param(_Indices([[2, 3], [1]], [0, 1, 2], (2, 3, 1)), id="tensor-ket preserved"), pytest.param(_Indices([[1], [2]], [0, 1], (1, 2)), id="bra preserved"), - pytest.param(_Indices([[1, 1], [2, 2]], [0, 1, 2, 3], (1, 1, 2, 2)), + pytest.param(_Indices([[1], [2, 2]], [0, 1, 2], (1, 2, 2)), id="tensor-bra preserved"), pytest.param(_Indices([[2], [3]], [0, 1], (2, 3)), id="oper preserved"), - pytest.param(_Indices([[2, 3], [1, 0]], [0, 1, 2, 3], (2, 3, 1, 0)), + pytest.param(_Indices([[2, 3], [1, 2]], [0, 1, 2, 3], (2, 3, 1, 2)), id="tensor-oper preserved"), pytest.param(_Indices([[[2, 4], [6, 8]], [[1, 3], [5, 7]]], [2, 3, 0, 1, 6, 7, 4, 5], (6, 8, 2, 4, 5, 7, 1, 3)), id="super-oper"), pytest.param(_Indices([[[2, 4], [6, 8]], [1]], - [0, 1, 2, 3, 4], (2, 4, 6, 8, 1)), + [2, 3, 0, 1, 4], (6, 8, 2, 4, 1)), id="operator-ket"), pytest.param(_Indices([[1], [[2, 4], [6, 8]]], - [0, 1, 2, 3, 4], (1, 2, 4, 6, 8)), + [0, 3, 4, 1, 2], (1, 6, 8, 2, 4)), id="operator-bra"), ]) class TestSuperOperatorDimsModification: @@ -151,30 +115,18 @@ def test_dims_to_tensor_shape(self, indices): class TestTypeFromDims: - @pytest.mark.parametrize(["base", "expected", "enforce_square"], [ - pytest.param([[2], [2]], 'oper', True), - pytest.param([[2, 3], [2, 3]], 'oper', True), - pytest.param([[2], [3]], 'other', True), - pytest.param([[2], [3]], 'oper', False), - pytest.param([[2], [1]], 'ket', True), - pytest.param([[1], [2]], 'bra', True), - pytest.param([[[2, 3], [2, 3]], [1]], 'operator-ket', True), - pytest.param([[1], [[2, 3], [2, 3]]], 'operator-bra', True), - pytest.param([[[3], [3]], [[2, 3], [2, 3]]], 'other', True), - pytest.param([[[3], [3]], [[2, 3], [2, 3]]], 'super', False), - pytest.param([[[2], [3, 3]], [[3], [2, 3]]], 'other', True), - ]) - def test_type_from_dims(self, base, expected, enforce_square): - assert type_from_dims(base, enforce_square=enforce_square) == expected - - @pytest.mark.parametrize("qobj", [ - pytest.param(qutip.rand_ket(10), id='ket'), - pytest.param(qutip.rand_ket(10).dag(), id='bra'), - pytest.param(qutip.rand_dm(10), id='oper'), - pytest.param(qutip.to_super(qutip.rand_dm(10)), id='super'), + @pytest.mark.parametrize(["base", "expected"], [ + pytest.param([[2], [2]], 'oper'), + pytest.param([[2, 3], [2, 3]], 'oper'), + pytest.param([[2], [3]], 'oper'), + pytest.param([[2], [1]], 'ket'), + pytest.param([[1], [2]], 'bra'), + pytest.param([[[2, 3], [2, 3]], [1]], 'operator-ket'), + pytest.param([[1], [[2, 3], [2, 3]]], 'operator-bra'), + pytest.param([[[3], [3]], [[2, 3], [2, 3]]], 'super'), ]) - def test_qobj_dims_match_qobj(self, qobj): - assert type_from_dims(qobj.dims) == qobj.type + def test_Dimensions_type(self, base, expected): + assert Dimensions(base).type == expected class TestCollapseDims: diff --git a/qutip/tests/core/test_ptrace.py b/qutip/tests/core/test_ptrace.py index 40efd15109..bb71e3581e 100644 --- a/qutip/tests/core/test_ptrace.py +++ b/qutip/tests/core/test_ptrace.py @@ -13,6 +13,8 @@ def expected(qobj, sel): sel = sorted(sel) dims = [[x for i, x in enumerate(qobj.dims[0]) if i in sel]]*2 new_shape = (np.prod(dims[0], dtype=int),) * 2 + if not dims[0]: + dims = None out = qobj.full() before, after = 1, qobj.shape[0] for i, dim in enumerate(qobj.dims[0]): diff --git a/qutip/tests/core/test_qobj.py b/qutip/tests/core/test_qobj.py index b2ae1718e9..ab504a6023 100644 --- a/qutip/tests/core/test_qobj.py +++ b/qutip/tests/core/test_qobj.py @@ -101,7 +101,7 @@ def test_QobjType(): N = 9 super_data = np.random.random((N, N)) - super_qobj = qutip.Qobj(super_data, dims=[[[3]], [[3]]]) + super_qobj = qutip.Qobj(super_data, dims=[[[3], [3]], [[3], [3]]]) assert super_qobj.type == 'super' assert super_qobj.issuper assert super_qobj.superrep == 'super' diff --git a/qutip/tests/core/test_superop_reps.py b/qutip/tests/core/test_superop_reps.py index 31c538d118..c64719e571 100644 --- a/qutip/tests/core/test_superop_reps.py +++ b/qutip/tests/core/test_superop_reps.py @@ -222,10 +222,11 @@ def test_choi_tr(self): ) / 2 # The partial transpose map, whose Choi matrix is SWAP - ptr_swap = Qobj(swap(), type='super', superrep='choi') + ptr_swap = Qobj(swap(), dims=[[[2], [2]]]*2, type='super', superrep='choi') # Subnormalized maps (representing erasure channels, for instance) - subnorm_map = Qobj(identity(4) * 0.9, type='super', superrep='super') + subnorm_map = Qobj(identity(4) * 0.9, dims=[[[2], [2]]]*2, + type='super', superrep='super') @pytest.mark.parametrize(['qobj', 'shouldhp', 'shouldcp', 'shouldtp'], [ pytest.param(S, True, True, False, id="conjugatio by create op"), diff --git a/qutip/tests/core/test_superoper.py b/qutip/tests/core/test_superoper.py index b5ba6fd105..80e1b3de57 100644 --- a/qutip/tests/core/test_superoper.py +++ b/qutip/tests/core/test_superoper.py @@ -43,7 +43,7 @@ def testsuperrep(self): with pytest.raises(TypeError) as err: bad_vec = as_vec.copy() - bad_vec.superrep = "" + bad_vec.superrep = "bad" qutip.vector_to_operator(bad_vec) assert err.value.args[0] == ("only defined for operator-kets " "in super format") @@ -156,7 +156,7 @@ def test_reshuffle(self): U = qutip.tensor(U1, U2, U3) S = qutip.to_super(U) S_col = qutip.reshuffle(S) - assert S_col.dims[0] == [[2, 2], [3, 3], [4, 4]] + assert S_col.dims[0] == [[2], [2], [3], [3], [4], [4]] assert qutip.reshuffle(S_col) == S def test_sprepost(self): diff --git a/qutip/tests/solve/test_piqs.py b/qutip/tests/solve/test_piqs.py index a069a2905b..3f8877e0d7 100644 --- a/qutip/tests/solve/test_piqs.py +++ b/qutip/tests/solve/test_piqs.py @@ -1113,7 +1113,7 @@ def test_liouvillian(self): true_L.dims = [[[2], [2]], [[2], [2]]] true_H = [[1.0 + 0.0j, 1.0 + 0.0j], [1.0 + 0.0j, -1.0 + 0.0j]] true_H = Qobj(true_H) - true_H.dims = [[[2], [2]]] + true_H.dims = [[2], [2]] true_liouvillian = [ [-4, -1.0j, 1.0j, 3], [-1.0j, -3.54999995 + 2.0j, 0, 1.0j], diff --git a/qutip/tests/test_enr_state_operator.py b/qutip/tests/test_enr_state_operator.py index 9002c3759b..45a4a2226f 100644 --- a/qutip/tests/test_enr_state_operator.py +++ b/qutip/tests/test_enr_state_operator.py @@ -44,7 +44,7 @@ def test_no_restrictions(self, dimensions): iden = [qutip.qeye(n) for n in dimensions] for i, test in enumerate(test_operators): expected = qutip.tensor(iden[:i] + [a[i]] + iden[i+1:]) - assert test == expected + assert test.data == expected.data assert test.dims == [dimensions, dimensions] def test_space_size_reduction(self, dimensions, n_excitations): @@ -82,7 +82,7 @@ def test_fock_state(dimensions, n_excitations): def test_fock_state_error(): with pytest.raises(ValueError) as e: state = qutip.enr_fock([2, 2, 2], 1, [1, 1, 1]) - assert str(e.value).startswith("The state tuple ") + assert str(e.value).startswith("state tuple ") def _reference_dm(dimensions, n_excitations, nbars): diff --git a/qutip/tests/test_random.py b/qutip/tests/test_random.py index fcbf1b4280..9ce667c286 100644 --- a/qutip/tests/test_random.py +++ b/qutip/tests/test_random.py @@ -202,7 +202,10 @@ def test_rand_ket(dimensions, distribution, dtype): """ random_qobj = rand_ket(dimensions, distribution=distribution, dtype=dtype) - assert random_qobj.type == 'ket' + target_type = "ket" + if isinstance(dimensions, list) and isinstance(dimensions[0], list): + target_type = "operator-ket" + assert random_qobj.type == target_type assert abs(random_qobj.norm() - 1) < 1e-14 if isinstance(dimensions, int): From 027e65b67df95c4451485d89e89f0ac1154408d1 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 19 Sep 2022 09:28:36 -0400 Subject: [PATCH 002/247] simpler qobj init --- qutip/core/cy/qobjevo.pyx | 4 ---- qutip/core/qobj.py | 33 +++++++++++++++------------------ qutip/random_objects.py | 6 +++--- 3 files changed, 18 insertions(+), 25 deletions(-) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index a01b807546..ee4151ea70 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -729,10 +729,6 @@ cdef class QobjEvo: def superrep(self): return self._dims.superrep - @superrep.setter - def superrep(self, super_rep): - self._dims = Dimensions(self._dims.as_list(), rep=super_rep) - ########################################################################### # operation methods # ########################################################################### diff --git a/qutip/core/qobj.py b/qutip/core/qobj.py index 17284f742b..bee8578562 100644 --- a/qutip/core/qobj.py +++ b/qutip/core/qobj.py @@ -271,22 +271,18 @@ class Qobj: __array_ufunc__ = None def _initialize_data(self, arg, dims, copy): + self._dims = None + self._data = None if isinstance(arg, _data.Data): self._data = arg.copy() if copy else arg self.dims = dims or [[arg.shape[0]], [arg.shape[1]]] elif isinstance(arg, Qobj): self._data = arg.data.copy() if copy else arg.data - if dims: - self.dims = dims - else: - self._dims = arg._dims - elif arg is None or isinstance(arg, numbers.Number): - self.dims = dims or [[1], [1]] - size = self._dims[0].size - if arg is None: - self._data = _data.zeros(size, size) - else: - self._data = _data.identity(size, scale=complex(arg)) + self._dims = dims or arg._dims + if arg._isherm is not None: + self._isherm = arg._isherm + if arg._isunitary is not None: + self._isunitary = arg._isunitary else: self._data = _data.create(arg, copy=copy) if ( @@ -299,14 +295,11 @@ def _initialize_data(self, arg, dims, copy): def __init__(self, arg=None, dims=None, type=None, copy=True, superrep=None, isherm=None, isunitary=None): - self._dims = None - self._data = None - self.type = None self._isherm = isherm self._isunitary = isunitary self._superrep = None if isinstance(dims, list): - dims = Dimensions(dims, rep=superrep) + dims = Dimensions(dims) self._initialize_data(arg, dims, copy) self.type = type or self._dims.type @@ -351,9 +344,10 @@ def dims(self, dims): @property def superrep(self): - if self._superrep: - return self._superrep - elif self.type in ['super', 'operator-ket', 'operator-bra']: + if ( + self._dims + and self._dims.type in ['super', 'operator-ket', 'operator-bra'] + ): return self._dims.superrep else: return None @@ -371,6 +365,9 @@ def data(self): def data(self, data): if not isinstance(data, _data.Data): raise TypeError('Qobj data must be a data-layer format.') + if self._dims and self._dims.shape != data.shape: + raise ValueError('Provided data do not match the dimensions: ' + + f"{dims.shape} vs {self._data.shape}") self._data = data def to(self, data_type): diff --git a/qutip/random_objects.py b/qutip/random_objects.py index fcf69f60c5..b0bcf85c18 100644 --- a/qutip/random_objects.py +++ b/qutip/random_objects.py @@ -488,7 +488,7 @@ def rand_ket(dimensions, density=1, distribution="haar", *, Y.data = 1.0j * (generator.random(len(X.data)) - 0.5) X = _data.csr.CSR(X + Y) ket = Qobj(_data.mul(X, 1 / _data.norm.l2(X)), - copy=False, isherm=False, isunitary=False) + copy=False, type="ket", isherm=False, isunitary=False) ket.dims = [dims[0], [1]] return ket.to(dtype) @@ -583,7 +583,7 @@ def rand_dm(dimensions, density=0.75, distribution="ginibre", *, H = _merge_shuffle_blocks(blocks, generator) H /= H.trace() - return Qobj(H, dims=dims, isherm=True, copy=False).to(dtype) + return Qobj(H, dims=dims, type='oper', isherm=True, copy=False).to(dtype) def _rand_dm_ginibre(N, rank, generator): @@ -653,7 +653,7 @@ def rand_kraus_map(dimensions, *, seed=None, dtype=_data.Dense): big_unitary = rand_unitary(N ** 3, seed=seed, dtype=dtype).full() orthog_cols = np.array(big_unitary[:, :N]) oper_list = np.reshape(orthog_cols, (N ** 2, N, N)) - return [Qobj(x, dims=dims, copy=False).to(dtype) + return [Qobj(x, dims=dims, type='oper', copy=False).to(dtype) for x in oper_list] From a220e78ac090cdc0429a4ab0d6f7ff8faa35e7e7 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 19 Sep 2022 10:40:00 -0400 Subject: [PATCH 003/247] Fix doc build --- doc/apidoc/functions.rst | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/doc/apidoc/functions.rst b/doc/apidoc/functions.rst index dd90f9290f..408e543d4b 100644 --- a/doc/apidoc/functions.rst +++ b/doc/apidoc/functions.rst @@ -11,14 +11,21 @@ Quantum States -------------- .. automodule:: qutip.core.states - :members: basis, bell_state, bra, coherent, coherent_dm, enr_state_dictionaries, enr_thermal_dm, enr_fock, fock, fock_dm, ghz_state, maximally_mixed_dm, ket, ket2dm, phase_basis, projection, qutrit_basis, singlet_state, spin_state, spin_coherent, state_number_enumerate, state_number_index, state_index_number, state_number_qobj, thermal_dm, triplet_states, w_state, zero_ket + :members: basis, bell_state, bra, coherent, coherent_dm, fock, fock_dm, ghz_state, maximally_mixed_dm, ket, ket2dm, phase_basis, projection, qutrit_basis, singlet_state, spin_state, spin_coherent, state_number_enumerate, state_number_index, state_index_number, state_number_qobj, thermal_dm, triplet_states, w_state, zero_ket Quantum Operators ----------------- .. automodule:: qutip.core.operators - :members: charge, commutator, create, destroy, displace, enr_destroy, enr_identity, jmat, num, qeye, identity, momentum, phase, position, qdiags, qutrit_ops, qzero, sigmam, sigmap, sigmax, sigmay, sigmaz, spin_Jx, spin_Jy, spin_Jz, spin_Jm, spin_Jp, squeeze, squeezing, tunneling + :members: charge, commutator, create, destroy, displace, jmat, num, qeye, identity, momentum, phase, position, qdiags, qutrit_ops, qzero, sigmam, sigmap, sigmax, sigmay, sigmaz, spin_Jx, spin_Jy, spin_Jz, spin_Jm, spin_Jp, squeeze, squeezing, tunneling + + +Energy restricted Operators +--------------------------- + +.. automodule:: qutip.core.energy_restricted + :members: enr_state_dictionaries, enr_thermal_dm, enr_fock, enr_destroy, enr_identity .. _functions-rand: From 07a024477ef03d46178bc18e0409e3e3e0635a44 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 19 Sep 2022 13:58:53 -0400 Subject: [PATCH 004/247] Add docstrings --- qutip/core/cy/qobjevo.pyx | 4 +-- qutip/core/dimensions.py | 69 +++++++++++++++++++++++++++++++++++---- 2 files changed, 65 insertions(+), 8 deletions(-) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index ee4151ea70..dcbce47f43 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -436,8 +436,8 @@ cdef class QobjEvo: if isinstance(other, Qobj): if other._dims[1] != self._dims[0]: raise TypeError("incompatible dimensions" + - str(other._dims[1]) + ", " + - str(self._dims[0])) + str(other.dims[1]) + ", " + + str(self.dims[0])) res = self.copy() res._dims = Dimensions([other._dims[0], res._dims[1]]) res.shape = (other.shape[0], res.shape[1]) diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index 064fe98b6d..1cb9439609 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -338,8 +338,8 @@ def to_tensor_rep(q_oper): ``` """ dims = q_oper._dims - data = q_oper.full().reshape(dims.get_tensor_shape()) - return data.transpose(dims.get_tensor_perm()) + data = q_oper.full().reshape(dims._get_tensor_shape()) + return data.transpose(dims._get_tensor_perm()) def from_tensor_rep(tensorrep, dims): @@ -350,7 +350,7 @@ def from_tensor_rep(tensorrep, dims): """ from . import Qobj dims = Dimensions(dims) - data = tensorrep.transpose(np.argsort(dims.get_tensor_perm())) + data = tensorrep.transpose(np.argsort(dims._get_tensor_perm())) return Qobj(data.reshape(dims.shape), dims=dims) @@ -463,23 +463,45 @@ def __str__(self): return str(self.as_list()) def dims2idx(self, dims): + """ + Transform dimensions indices to full array indices. + """ return dims def idx2dims(self, idx): + """ + Transform full array indices to dimensions indices. + """ return [idx] def step(self): + """ + Get the step in the array between for each dimensions index. + + If element ``[i, j, k]`` is ``ket.full()[m, 0]`` then element + ``[i, j+1, k]`` is ``ket.full()[m + ket._dims.step()[1], 0]``. + """ return [1] def flat(self): + """ Dimensions as a flat list. """ return [self.size] def remove(self, idx): + """ + Remove a Space from a Dimensons or complex Space. + + ``Space([2, 3, 4]).remove(1) == Space([2, 4])`` + """ raise RuntimeError("Cannot delete a flat space.") def replace(self, idx, new): - return Space(new) + """ + Reshape a Space from a Dimensons or complex Space. + ``Space([2, 3, 4]).replace(1, 5) == Space([2, 5, 4])`` + """ + return Space(new) class Field(Space): @@ -744,6 +766,9 @@ def __str__(self): return str(self.as_list()) def as_list(self): + """ + Return the list representation of the Dimensions object. + """ return [self.to_.as_list(), self.from_.as_list()] def __getitem__(self, key): @@ -753,18 +778,36 @@ def __getitem__(self, key): return self.from_ def dims2idx(self, dims): + """ + Transform dimensions indices to full array indices. + """ return self.to_.dims2idx(dims[0]), self.from_.dims2idx(dims[1]) def idx2dims(self, idxl, idxr): + """ + Transform full array indices to dimensions indices. + """ return [self.to_.idx2dims(idxl), self.from_.idx2dims(idxr)] def step(self): + """ + Get the step in the array between for each dimensions index. + + If element ``[i, j, k]`` is ``ket.full()[m, 0]`` then element + ``[i, j+1, k]`` is ``ket.full()[m + ket._dims.step()[1], 0]``. + """ return [self.to_.step(), self.from_.step()] def flat(self): + """ Dimensions as a flat list. """ return [self.to_.flat(), self.from_.flat()] - def get_tensor_shape(self): + def _get_tensor_shape(self): + """ + Get the shape to of the Nd tensor with one dimensions for each + Dimension index. The order of the space values are not in the order of + the Dimension index. + """ # dims_to_tensor_shape stepl = self.to_.step() flatl = self.to_.flat() @@ -775,7 +818,11 @@ def get_tensor_shape(self): np.array(flatr)[np.argsort(stepr)[::-1]], ])) - def get_tensor_perm(self): + def _get_tensor_perm(self): + """ + Get the permutation of a tensor created using ``_get_tensor_shape`` to + reorder the tensor dimensions with those of the Dimensions object. + """ # dims_to_tensor_perm stepl = self.to_.step() stepr = self.from_.step() @@ -785,6 +832,11 @@ def get_tensor_perm(self): ])) def remove(self, idx): + """ + Remove a Space from a Dimensons or complex Space. + + ``Space([2, 3, 4]).remove(1) == Space([2, 4])`` + """ if not isinstance(idx, list): idx = [idx] if not idx: @@ -799,6 +851,11 @@ def remove(self, idx): ) def replace(self, idx, new): + """ + Reshape a Space from a Dimensons or complex Space. + + ``Space([2, 3, 4]).replace(1, 5) == Space([2, 5, 4])`` + """ n_indices = len(self.to_.flat()) if idx < n_indices: new_to = self.to_.replace(idx, new) From 5ef01522d442ebe9b9e3c17f2e469b55def48b91 Mon Sep 17 00:00:00 2001 From: Eric Date: Mon, 19 Sep 2022 16:01:07 -0400 Subject: [PATCH 005/247] type as property --- qutip/core/dimensions.py | 2 +- qutip/core/qobj.py | 30 ++++++++++++++++++++++++------ qutip/random_objects.py | 23 ++++++++++++++++++----- qutip/tests/core/test_qobj.py | 3 --- qutip/tests/test_random.py | 14 ++++++++++---- 5 files changed, 53 insertions(+), 19 deletions(-) diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index 1cb9439609..c9eceebbbb 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -264,7 +264,7 @@ def dims_to_tensor_perm(dims): """ if isinstance(dims, list): dims = Dimensions(dims) - return dims.get_tensor_perm() + return dims._get_tensor_perm() def dims_to_tensor_shape(dims): diff --git a/qutip/core/qobj.py b/qutip/core/qobj.py index bee8578562..bbc418b21d 100644 --- a/qutip/core/qobj.py +++ b/qutip/core/qobj.py @@ -301,12 +301,11 @@ def __init__(self, arg=None, dims=None, type=None, if isinstance(dims, list): dims = Dimensions(dims) self._initialize_data(arg, dims, copy) - self.type = type or self._dims.type # Dims are guessed from the data and need to be changed to super. if ( type in ['super', 'operator-ket', 'operator-bra'] - and self.type in ['oper', 'ket', 'bra'] + and self._dims.type in ['oper', 'ket', 'bra'] ): root_right = int(np.sqrt(self._data.shape[0])) root_left = int(np.sqrt(self._data.shape[1])) @@ -318,8 +317,9 @@ def __init__(self, arg=None, dims=None, type=None, "cannot build superoperator from nonsquare subspaces" ) self.dims = [[[root_right]]*2, [[root_left]]*2] - if superrep and self.type in ['super', 'operator-ket', 'operator-bra']: - self.superrep = superrep + + self.type = type + self.superrep = superrep def copy(self): """Create identical copy""" @@ -342,6 +342,21 @@ def dims(self, dims): self._dims = dims self.type = self._dims.type + @property + def type(self): + return self._type + + @type.setter + def type(self, val): + if not val: + self._type = self._dims.type + elif self._dims.type == "scalar": + self._type = val + elif self._dims.type == val: + self._type = val + else: + raise TypeError("Type does not match dimensions.") + @property def superrep(self): if ( @@ -354,8 +369,11 @@ def superrep(self): @superrep.setter def superrep(self, super_rep): - self._dims = Dimensions(self._dims.as_list(), rep=super_rep) - self._superrep = super_rep + if ( + super_rep + and self._dims.type in ['super', 'operator-ket', 'operator-bra'] + ): + self._dims = Dimensions(self._dims.as_list(), rep=super_rep) @property def data(self): diff --git a/qutip/random_objects.py b/qutip/random_objects.py index b0bcf85c18..5787bb6749 100644 --- a/qutip/random_objects.py +++ b/qutip/random_objects.py @@ -259,6 +259,9 @@ def rand_herm(dimensions, density=0.30, distribution="fill", *, repeatedly used for generating matrices larger than ~1000x1000. """ N, dims = _implicit_tensor_dimensions(dimensions) + type = None + if N == 1: + type = "oper" generator = _get_generator(seed) if distribution not in ["eigen", "fill", "pos_def"]: raise ValueError("distribution must be one of {'eigen', 'fill', " @@ -273,7 +276,7 @@ def rand_herm(dimensions, density=0.30, distribution="fill", *, out = _rand_jacobi_rotation(out, generator) while _data.csr.nnz(out) < 0.95 * nvals: out = _rand_jacobi_rotation(out, generator) - out = Qobj(out, type='oper', dims=dims, isherm=True, copy=False) + out = Qobj(out, type=type, dims=dims, isherm=True, copy=False) else: pos_def = distribution == "pos_def" @@ -282,7 +285,7 @@ def rand_herm(dimensions, density=0.30, distribution="fill", *, else: M = _rand_herm_dense(N, density, pos_def, generator) - out = Qobj(M, type='oper', dims=dims, isherm=True, copy=False) + out = Qobj(M, type=type, dims=dims, isherm=True, copy=False) return out.to(dtype) @@ -367,6 +370,9 @@ def rand_unitary(dimensions, density=1, distribution="haar", *, Unitary quantum operator. """ N, dims = _implicit_tensor_dimensions(dimensions) + type = None + if N == 1: + type = "oper" if distribution not in ["haar", "exp"]: raise ValueError("distribution must be one of {'haar', 'exp'}") generator = _get_generator(seed) @@ -385,7 +391,7 @@ def rand_unitary(dimensions, density=1, distribution="haar", *, merged = _merge_shuffle_blocks(blocks, generator) - mat = Qobj(merged, type='oper', dims=dims, isunitary=True, copy=False) + mat = Qobj(merged, type=type, dims=dims, isunitary=True, copy=False) return mat.to(dtype) @@ -471,6 +477,9 @@ def rand_ket(dimensions, density=1, distribution="haar", *, """ generator = _get_generator(seed) N, dims = _implicit_tensor_dimensions(dimensions) + type = None + if N == 1: + type = "ket" if distribution not in ["haar", "fill"]: raise ValueError("distribution must be one of {'haar', 'fill'}") @@ -488,8 +497,9 @@ def rand_ket(dimensions, density=1, distribution="haar", *, Y.data = 1.0j * (generator.random(len(X.data)) - 0.5) X = _data.csr.CSR(X + Y) ket = Qobj(_data.mul(X, 1 / _data.norm.l2(X)), - copy=False, type="ket", isherm=False, isunitary=False) + copy=False, isherm=False, isunitary=False) ket.dims = [dims[0], [1]] + ket.type = type return ket.to(dtype) @@ -543,6 +553,9 @@ def rand_dm(dimensions, density=0.75, distribution="ginibre", *, """ generator = _get_generator(seed) N, dims = _implicit_tensor_dimensions(dimensions) + type = None + if N == 1: + type = "oper" distributions = set(["eigen", "ginibre", "hs", "pure", "herm"]) if distribution not in distributions: raise ValueError(f"distribution must be one of {distributions}") @@ -583,7 +596,7 @@ def rand_dm(dimensions, density=0.75, distribution="ginibre", *, H = _merge_shuffle_blocks(blocks, generator) H /= H.trace() - return Qobj(H, dims=dims, type='oper', isherm=True, copy=False).to(dtype) + return Qobj(H, dims=dims, type=type, isherm=True, copy=False).to(dtype) def _rand_dm_ginibre(N, rank, generator): diff --git a/qutip/tests/core/test_qobj.py b/qutip/tests/core/test_qobj.py index 92eb30eba3..a919acfb97 100644 --- a/qutip/tests/core/test_qobj.py +++ b/qutip/tests/core/test_qobj.py @@ -254,11 +254,8 @@ def test_QobjAddition(): q3 = qutip.Qobj(data3) q4 = q1 + q2 - q4_type = q4.type q4_isherm = q4.isherm - q4._type = None q4._isherm = None # clear cached values - assert q4_type == q4.type assert q4_isherm == q4.isherm # check elementwise addition/subtraction diff --git a/qutip/tests/test_random.py b/qutip/tests/test_random.py index 9ce667c286..ebbacf3a70 100644 --- a/qutip/tests/test_random.py +++ b/qutip/tests/test_random.py @@ -284,7 +284,13 @@ def test_random_seeds(function, seed): def test_kraus_map(dimensions, dtype): - kmap = rand_kraus_map(dimensions, dtype=dtype) - _assert_metadata(kmap[0], dimensions, dtype) - with CoreOptions(atol=1e-9): - assert kraus_to_choi(kmap).iscptp + if isinstance(dimensions, list) and isinstance(dimensions[0], list): + # Each element of a kraus map cannot be a super operators + with pytest.raises(TypeError) as err: + kmap = rand_kraus_map(dimensions, dtype=dtype) + assert "Type does not match dimensions" in str(err.value) + else: + kmap = rand_kraus_map(dimensions, dtype=dtype) + _assert_metadata(kmap[0], dimensions, dtype) + with CoreOptions(atol=1e-9): + assert kraus_to_choi(kmap).iscptp From 8f1094ed6016bc72e23e4a05b138006168a13e09 Mon Sep 17 00:00:00 2001 From: Kosuke Mizuno <43668684+KosukeMizuno@users.noreply.github.com> Date: Fri, 13 Jan 2023 14:58:19 +0900 Subject: [PATCH 006/247] add arguments for plot_wigner_fock_distribution --- doc/changes/2057.feature | 1 + qutip/visualization.py | 43 ++++++++++++++++++++++++++++++++++------ 2 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 doc/changes/2057.feature diff --git a/doc/changes/2057.feature b/doc/changes/2057.feature new file mode 100644 index 0000000000..27bdf6eb1a --- /dev/null +++ b/doc/changes/2057.feature @@ -0,0 +1 @@ +Add arguments of plot_wigner() and plot_wigner_fock_distribution() to specify parameters for wigner(). (#2057) \ No newline at end of file diff --git a/qutip/visualization.py b/qutip/visualization.py index 598325cfa6..2c9785d494 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -15,7 +15,7 @@ import warnings import itertools as it import numpy as np -from numpy import pi, array, sin, cos, angle, log2 +from numpy import pi, array, sin, cos, angle, log2, sqrt from packaging.version import parse as parse_version @@ -1027,7 +1027,9 @@ def fock_distribution(rho, offset=0, fig=None, ax=None, def plot_wigner(rho, fig=None, ax=None, figsize=(6, 6), cmap=None, alpha_max=7.5, colorbar=False, - method='clenshaw', projection='2d'): + method='clenshaw', g=sqrt(2), + sparse=False, parfor=False, + projection='2d'): """ Plot the the Wigner function for a density matrix (or ket) that describes an oscillator mode. @@ -1061,6 +1063,18 @@ def plot_wigner(rho, fig=None, ax=None, figsize=(6, 6), The method used for calculating the wigner function. See the documentation for qutip.wigner for details. + g : float + Scaling factor for `a = 0.5 * g * (x + iy)`, default `g = sqrt(2)`. + See the documentation for qutip.wigner for details. + + sparse : bool {False, True} + Flag for sparse format. + See the documentation for qutip.wigner for details. + + parfor : bool {False, True} + Flag for parallel calculation. + See the documentation for qutip.wigner for details. + projection: string {'2d', '3d'} Specify whether the Wigner function is to be plotted as a contour graph ('2d') or surface plot ('3d'). @@ -1085,7 +1099,9 @@ def plot_wigner(rho, fig=None, ax=None, figsize=(6, 6), rho = ket2dm(rho) xvec = np.linspace(-alpha_max, alpha_max, 200) - W0 = wigner(rho, xvec, xvec, method=method) + W0 = wigner(rho, xvec, xvec, + method=method, g=g, + sparse=sparse, parfor=parfor) W, yvec = W0 if isinstance(W0, tuple) else (W0, xvec) @@ -1120,7 +1136,9 @@ def plot_wigner(rho, fig=None, ax=None, figsize=(6, 6), def plot_wigner_fock_distribution(rho, fig=None, axes=None, figsize=(8, 4), cmap=None, alpha_max=7.5, colorbar=False, - method='iterative', projection='2d'): + method='clenshaw', g=sqrt(2), + sparse=False, parfor=False, + projection='2d'): """ Plot the Fock distribution and the Wigner function for a density matrix (or ket) that describes an oscillator mode. @@ -1150,10 +1168,22 @@ def plot_wigner_fock_distribution(rho, fig=None, axes=None, figsize=(8, 4), Whether (True) or not (False) a colorbar should be attached to the Wigner function graph. - method : string {'iterative', 'laguerre', 'fft'} + method : string {'clenshaw', 'iterative', 'laguerre', 'fft'} The method used for calculating the wigner function. See the documentation for qutip.wigner for details. + g : float + Scaling factor for `a = 0.5 * g * (x + iy)`, default `g = sqrt(2)`. + See the documentation for qutip.wigner for details. + + sparse : bool {False, True} + Flag for sparse format. + See the documentation for qutip.wigner for details. + + parfor : bool {False, True} + Flag for parallel calculation. + See the documentation for qutip.wigner for details. + projection: string {'2d', '3d'} Specify whether the Wigner function is to be plotted as a contour graph ('2d') or surface plot ('3d'). @@ -1180,7 +1210,8 @@ def plot_wigner_fock_distribution(rho, fig=None, axes=None, figsize=(8, 4), plot_fock_distribution(rho, fig=fig, ax=axes[0]) plot_wigner(rho, fig=fig, ax=axes[1], figsize=figsize, cmap=cmap, - alpha_max=alpha_max, colorbar=colorbar, method=method, + alpha_max=alpha_max, colorbar=colorbar, + method=method, g=g, sparse=sparse, parfor=parfor, projection=projection) return fig, axes From a975637c540ca31b820e6a481682927438eb8023 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 14 Feb 2023 11:49:31 -0500 Subject: [PATCH 007/247] Add __all__ to dimensions.py --- qutip/core/dimensions.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index 3bf76c84dd..02e47d0723 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -10,6 +10,12 @@ from qutip.settings import settings +__all__ = [ + "is_scalar", "is_vector", "is_vectorized_oper", + "to_tensor_rep", "from_tensor_rep", "Space", "Dimensions" +] + + def is_scalar(dims): """ Returns True if a dims specification is effectively @@ -389,13 +395,17 @@ def __call__(cls, *args, rep=None): if cls is SuperSpace and args[0].type == "scalar": cls = Field - args = tuple([tuple(arg) if isinstance(arg, list) else arg - for arg in args]) + args = tuple([ + tuple(arg) if isinstance(arg, list) else arg + for arg in args + ]) if cls is Field: return cls.field_instance + if cls is SuperSpace: args = *args, rep or 'super' + if args not in cls._stored_dims: instance = cls.__new__(cls) instance.__init__(*args) From 697d2463a4eaa2414a3f52faee51558bb04fcc69 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 28 Jun 2023 16:45:15 -0400 Subject: [PATCH 008/247] Add towncrier entry --- doc/changes/1996.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/1996.feature diff --git a/doc/changes/1996.feature b/doc/changes/1996.feature new file mode 100644 index 0000000000..0daabffbed --- /dev/null +++ b/doc/changes/1996.feature @@ -0,0 +1 @@ +Create a Dimension class From 654d714f6378c3ad04240c70d939021b5b215815 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 25 Jul 2023 15:41:37 -0400 Subject: [PATCH 009/247] Add structure in QobjEvo --- qutip/core/cy/qobjevo.pxd | 1 + qutip/core/cy/qobjevo.pyx | 81 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 82 insertions(+) diff --git a/qutip/core/cy/qobjevo.pxd b/qutip/core/cy/qobjevo.pxd index 07e0d3df7c..37838a3d63 100644 --- a/qutip/core/cy/qobjevo.pxd +++ b/qutip/core/cy/qobjevo.pxd @@ -12,6 +12,7 @@ cdef class QobjEvo: readonly str superrep int _issuper int _isoper + #readonly dict feedback_functions cpdef Data _call(QobjEvo self, double t) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 388551dd39..8f60dcd9d6 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -402,6 +402,13 @@ cdef class QobjEvo: cdef object _prepare(QobjEvo self, object t, Data state=None): """ Precomputation before computing getting the element at `t`""" # We keep the function for feedback eventually + if self.feedback_functions is not None: + new_args = { + key: func(t, state) + for key, func in self.feedback_functions + } + self.arguments(**new_args) + return t def copy(QobjEvo self): @@ -434,6 +441,30 @@ cdef class QobjEvo: for element in self.elements ] + def add_feedback(QobjEvo self, str key, object feedback): + if self.feedback_functions == None: + self.feedback_functions = {} + if feedback == "data" and self.issuper: + self.feedback_functions[key] = _Column_Stacker(self.shape[1]) + elif feedback in ["raw", "data"]: + self.feedback_functions[key] = _pass_through + elif feedback in ["qobj", "Qobj"]: + self.feedback_functions[key] = _To_Qobj(self.dims, self.issuper) + elif isinstance(feedback, [Qobj, QobjEvo]): + if isinstance(feedback, Qobj): + feedback = QobjEvo(feedback) + if feedback.dims == self.dims: + self.feedback_functions[key] = feedback.expect_data + elif self.issuper and self.dims[1][0] == feedback.dims: + # tr(op @ dm) cases + self.feedback_functions[key] = _Expect_Stacked(feedback) + else: + raise TypeError( + "dims of the feedback operator do " + "not fit the original system." + ) + else: + ValueError("Feedback type not understood.") ########################################################################### # Math function # @@ -1017,3 +1048,53 @@ cdef class QobjEvo: part = (<_BaseElement> element) out = part.matmul_data_t(t, state, out) return out + + +cdef class _Expect_Stacked: + cdef QobjEvo oper + + def __init__(self, oper): + self.oper = oper + + def __call__(self, t, state): + return self.oper.expect_data(t, column_unstack(state, self.oper.shape[0])) + + +cdef class _To_Qobj: + cdef list dims, dims_flat + cdef bint issuper + cdef idxint N + + def __init__(self, base): + self.dims = base.dims + if not base.issuper: + self.dims_flat = [1 for _ in base.dims[0]] + self.issuper = base.issuper + self.N = int(base.shape[0]**0.5) + + def __call__(self, t, state): + if state.shape[0] == state.shape[1]: + out = Qobj(state, dims=self.dims) + elif self.issuper and state.shape[1] == 1: + state = column_unstack(state, self.N) + out = Qobj(state, dims=self.dims[1]) + elif state.shape[1] == 1: + out = Qobj(state, dims=[self.dims[1], self.dims_flat]) + else: + # rectangular state dims are patially lost... + out = Qobj(state, dims=[self.dims[1], [state.shape[1]]]) + return out + + +def _pass_through(t, state): + return state + + +def _Column_Stacker: + cdef idxint N + + def __init__(self, shape): + self.N = int(shape**0.5) + + def __call__(self, t, state): + return column_unstack(state, self.N) From 5f573c4b9384824ec784bdd7a34bcd8cd4a4fbfb Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 26 Jul 2023 13:31:25 -0400 Subject: [PATCH 010/247] qobjevo with feedback --- qutip/core/cy/qobjevo.pxd | 2 +- qutip/core/cy/qobjevo.pyx | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/qutip/core/cy/qobjevo.pxd b/qutip/core/cy/qobjevo.pxd index 37838a3d63..05abcd7d48 100644 --- a/qutip/core/cy/qobjevo.pxd +++ b/qutip/core/cy/qobjevo.pxd @@ -12,7 +12,7 @@ cdef class QobjEvo: readonly str superrep int _issuper int _isoper - #readonly dict feedback_functions + readonly dict feedback_functions cpdef Data _call(QobjEvo self, double t) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 8f60dcd9d6..f06e1459d3 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -405,7 +405,7 @@ cdef class QobjEvo: if self.feedback_functions is not None: new_args = { key: func(t, state) - for key, func in self.feedback_functions + for key, func in self.feedback_functions.items() } self.arguments(**new_args) @@ -461,7 +461,7 @@ cdef class QobjEvo: else: raise TypeError( "dims of the feedback operator do " - "not fit the original system." + "not match the original system." ) else: ValueError("Feedback type not understood.") From 62c0734d28d03816965ca5138123c8ec19efc9d7 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 26 Jul 2023 15:34:31 -0400 Subject: [PATCH 011/247] Add tests for feedback for QobjEvo --- qutip/core/cy/qobjevo.pyx | 19 +++++--- qutip/tests/core/test_qobjevo.py | 74 ++++++++++++++++++++++++++++++-- 2 files changed, 83 insertions(+), 10 deletions(-) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index f06e1459d3..7e1cb04e66 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -449,13 +449,13 @@ cdef class QobjEvo: elif feedback in ["raw", "data"]: self.feedback_functions[key] = _pass_through elif feedback in ["qobj", "Qobj"]: - self.feedback_functions[key] = _To_Qobj(self.dims, self.issuper) - elif isinstance(feedback, [Qobj, QobjEvo]): + self.feedback_functions[key] = _To_Qobj(self) + elif isinstance(feedback, (Qobj, QobjEvo)): if isinstance(feedback, Qobj): feedback = QobjEvo(feedback) if feedback.dims == self.dims: self.feedback_functions[key] = feedback.expect_data - elif self.issuper and self.dims[1][0] == feedback.dims: + elif self.issuper and self.dims[1] == feedback.dims: # tr(op @ dm) cases self.feedback_functions[key] = _Expect_Stacked(feedback) else: @@ -1057,7 +1057,10 @@ cdef class _Expect_Stacked: self.oper = oper def __call__(self, t, state): - return self.oper.expect_data(t, column_unstack(state, self.oper.shape[0])) + return self.oper.expect_data( + t, + _data.column_unstack(state, self.oper.shape[0]) + ) cdef class _To_Qobj: @@ -1076,7 +1079,7 @@ cdef class _To_Qobj: if state.shape[0] == state.shape[1]: out = Qobj(state, dims=self.dims) elif self.issuper and state.shape[1] == 1: - state = column_unstack(state, self.N) + state = _data.column_unstack(state, self.N) out = Qobj(state, dims=self.dims[1]) elif state.shape[1] == 1: out = Qobj(state, dims=[self.dims[1], self.dims_flat]) @@ -1090,11 +1093,13 @@ def _pass_through(t, state): return state -def _Column_Stacker: +cdef class _Column_Stacker: cdef idxint N def __init__(self, shape): self.N = int(shape**0.5) def __call__(self, t, state): - return column_unstack(state, self.N) + if state.shape[1] == 1: + return _data.column_unstack(state, self.N) + return state diff --git a/qutip/tests/core/test_qobjevo.py b/qutip/tests/core/test_qobjevo.py index 77fe8fba93..257ed92762 100644 --- a/qutip/tests/core/test_qobjevo.py +++ b/qutip/tests/core/test_qobjevo.py @@ -1,9 +1,11 @@ import operator import pytest -from qutip import (Qobj, QobjEvo, coefficient, qeye, sigmax, sigmaz, - rand_stochastic, rand_herm, rand_ket, liouvillian, - basis, spre, spost, to_choi) +from qutip import ( + Qobj, QobjEvo, coefficient, qeye, sigmax, sigmaz, rand_stochastic, + rand_herm, rand_ket, liouvillian, basis, spre, spost, to_choi, expect, + rand_ket, rand_dm, operator_to_vector +) import numpy as np from numpy.testing import assert_allclose @@ -513,3 +515,69 @@ def test_QobjEvo_to_list(coeff_type, pseudo_qevo): assert len(as_list) == 2 restored = QobjEvo(as_list) _assert_qobjevo_equivalent(qevo, restored) + + +class Feedback_Checker_Coefficient: + def __init__(self): + self.state = None + + def __call__(self, t, data=None, qobj=None, e_val=None): + if self.state is not None: + if data is not None: + assert data == self.state.data + if qobj is not None: + assert qobj == self.state + if e_val is not None: + assert e_val == expect(qeye(self.state.dims[0]), self.state) + return 1. + + +def test_feedback_oper(): + checker = Feedback_Checker_Coefficient() + qevo = QobjEvo([qeye(2), checker]) + qevo.add_feedback("data", "data") + qevo.add_feedback("qobj", "qobj") + qevo.add_feedback("e_val", qeye(2)) + + checker.state = rand_ket(2) + qevo.expect(0, checker.state) + qevo.matmul_data(0, checker.state.data) + + checker.state = rand_ket(2) + qevo.expect(0, checker.state) + qevo.matmul_data(0, checker.state.data) + + +def test_feedback_super(): + checker = Feedback_Checker_Coefficient() + qevo = QobjEvo([spre(qeye(2)), checker]) + qevo.add_feedback("data", "data") + qevo.add_feedback("qobj", "qobj") + qevo.add_feedback("e_val", qeye(2)) + + checker.state = rand_dm(2) + qevo.expect(0, operator_to_vector(checker.state)) + qevo.matmul_data(0, operator_to_vector(checker.state).data) + + qevo.add_feedback("e_val", spre(qeye(2))) + + checker.state = rand_dm(2) + qevo.expect(0, operator_to_vector(checker.state)) + qevo.matmul_data(0, operator_to_vector(checker.state).data) + + checker = Feedback_Checker_Coefficient() + qevo = QobjEvo([spre(qeye(2)), checker]) + qevo.add_feedback("data", "data") + qevo.add_feedback("qobj", "qobj") + + checker.state = rand_dm(4) + checker.state.dims = [[[2],[2]], [[2],[2]]] + qevo.matmul_data(0, checker.state.data) + + checker = Feedback_Checker_Coefficient() + qevo = QobjEvo([spre(qeye(2)), checker]) + qevo.add_feedback("data", "raw") + + checker.state = operator_to_vector(rand_dm(2)) + qevo.expect(0, checker.state) + qevo.matmul_data(0, checker.state.data) From af730db5531b7bab402218a0ead40705ea0b7090 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 26 Jul 2023 16:26:47 -0400 Subject: [PATCH 012/247] Add function in solver --- qutip/core/cy/qobjevo.pyx | 22 ++++++++++++++++++++++ qutip/solver/solver_base.py | 24 ++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 7e1cb04e66..250b6b18c3 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -442,6 +442,28 @@ cdef class QobjEvo: ] def add_feedback(QobjEvo self, str key, object feedback): + """ + Register an argument to be updated with the state during `matmul` and + `expect`. + + Equivalent to do: + `solver.argument(key=state_t)` + + Parameters + ---------- + key : str + Arguments key to update. + + type : str, Qobj, QobjEvo + Format of the `state_t`. + - "qobj": As a Qobj, either a ket or dm. + - "data": As a qutip data layer object. Density matrices will be + square matrix. + - "raw": As a qutip data layer object. Density matrices will be + columns stacked: shape=(N**2, 1). + - Qobj, QobjEvo: The value is updated with the expectation value of + the given operator and the state. + """ if self.feedback_functions == None: self.feedback_functions = {} if feedback == "data" and self.issuper: diff --git a/qutip/solver/solver_base.py b/qutip/solver/solver_base.py index 028eb448f3..b2d9137dba 100644 --- a/qutip/solver/solver_base.py +++ b/qutip/solver/solver_base.py @@ -394,3 +394,27 @@ def add_integrator(cls, integrator, key): " of `qutip.solver.Integrator`") cls._avail_integrators[key] = integrator + + def add_feedback(self, key, type): + """ + Register an argument to be updated with the state during the evolution. + + Equivalent to do: + `solver.argument(key=state_t)` + + Parameters + ---------- + key : str + Arguments key to update. + + type : str, Qobj, QobjEvo + Format of the `state_t`. + - "qobj": As a Qobj, either a ket or dm. + - "data": As a qutip data layer object. Density matrices will be + square matrix. + - "raw": As a qutip data layer object. Density matrices will be + columns stacked: shape=(N**2, 1). + - Qobj, QobjEvo: The value is updated with the expectation value of + the given operator and the state. + """ + self.rhs.add_feedback(key, type) From 82eddaa1e3c970f968f201554ee5c421e7c8d085 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 27 Jul 2023 17:18:35 -0400 Subject: [PATCH 013/247] feedback in most solver --- qutip/core/cy/qobjevo.pxd | 2 +- qutip/core/cy/qobjevo.pyx | 111 +++++++++++++++++---------- qutip/solver/floquet.py | 5 ++ qutip/solver/mcsolve.py | 7 ++ qutip/solver/nm_mcsolve.py | 5 ++ qutip/solver/solver_base.py | 2 +- qutip/tests/core/test_qobjevo.py | 20 ++--- qutip/tests/solver/test_brmesolve.py | 16 ++++ qutip/tests/solver/test_mcsolve.py | 20 +++++ qutip/tests/solver/test_mesolve.py | 16 ++++ qutip/tests/solver/test_sesolve.py | 16 ++++ 11 files changed, 167 insertions(+), 53 deletions(-) diff --git a/qutip/core/cy/qobjevo.pxd b/qutip/core/cy/qobjevo.pxd index 05abcd7d48..159b71dc17 100644 --- a/qutip/core/cy/qobjevo.pxd +++ b/qutip/core/cy/qobjevo.pxd @@ -12,7 +12,7 @@ cdef class QobjEvo: readonly str superrep int _issuper int _isoper - readonly dict feedback_functions + readonly dict _feedback_functions cpdef Data _call(QobjEvo self, double t) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 250b6b18c3..68c24e8ba0 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -402,10 +402,10 @@ cdef class QobjEvo: cdef object _prepare(QobjEvo self, object t, Data state=None): """ Precomputation before computing getting the element at `t`""" # We keep the function for feedback eventually - if self.feedback_functions is not None: + if self._feedback_functions is not None and state is not None: new_args = { key: func(t, state) - for key, func in self.feedback_functions.items() + for key, func in self._feedback_functions.items() } self.arguments(**new_args) @@ -441,45 +441,53 @@ cdef class QobjEvo: for element in self.elements ] - def add_feedback(QobjEvo self, str key, object feedback): - """ - Register an argument to be updated with the state during `matmul` and - `expect`. - - Equivalent to do: - `solver.argument(key=state_t)` - - Parameters - ---------- - key : str - Arguments key to update. - - type : str, Qobj, QobjEvo - Format of the `state_t`. - - "qobj": As a Qobj, either a ket or dm. - - "data": As a qutip data layer object. Density matrices will be - square matrix. - - "raw": As a qutip data layer object. Density matrices will be - columns stacked: shape=(N**2, 1). - - Qobj, QobjEvo: The value is updated with the expectation value of - the given operator and the state. - """ - if self.feedback_functions == None: - self.feedback_functions = {} + def _add_feedback(QobjEvo self, str key, feedback, normalize=False): + """ + Register an argument to be updated with the state during `matmul` and + `expect`. + + Equivalent to do: + `solver.argument(key=state_t)` + + Parameters + ---------- + key: str + Arguments key to update. + + type: str, Qobj, QobjEvo + Format of the `state_t`. + - "qobj": As a Qobj, either a ket or dm. + - "data": As a qutip data layer object. Density matrices will be + square matrix. + - "raw": As a qutip data layer object. Density matrices will be + columns stacked: shape=(N**2, 1). + - Qobj, QobjEvo: The value is updated with the expectation value of + the given operator and the state. + + normalize: bool + Whether to normalize the state before using it. + """ + if self._feedback_functions is None: + self._feedback_functions = {} if feedback == "data" and self.issuper: - self.feedback_functions[key] = _Column_Stacker(self.shape[1]) + self._feedback_functions[key] = \ + _Column_Stacker(self.shape[1], normalize) + elif feedback == "data" and normalize: + self._feedback_functions[key] = _normalize elif feedback in ["raw", "data"]: - self.feedback_functions[key] = _pass_through + self._feedback_functions[key] = _pass_through elif feedback in ["qobj", "Qobj"]: - self.feedback_functions[key] = _To_Qobj(self) + self._feedback_functions[key] = _To_Qobj(self, normalize) elif isinstance(feedback, (Qobj, QobjEvo)): if isinstance(feedback, Qobj): feedback = QobjEvo(feedback) if feedback.dims == self.dims: - self.feedback_functions[key] = feedback.expect_data + self._feedback_functions[key] = \ + _Expect(feedback, normalize, False) elif self.issuper and self.dims[1] == feedback.dims: # tr(op @ dm) cases - self.feedback_functions[key] = _Expect_Stacked(feedback) + self._feedback_functions[key] = \ + _Expect(feedback, normalize, True) else: raise TypeError( "dims of the feedback operator do " @@ -1072,30 +1080,37 @@ cdef class QobjEvo: return out -cdef class _Expect_Stacked: +cdef class _Expect: cdef QobjEvo oper + cdef bint normalize + cdef bint stack - def __init__(self, oper): + def __init__(self, oper, normalize, stack): self.oper = oper + self.normalize = normalize + self.stack = stack def __call__(self, t, state): - return self.oper.expect_data( - t, - _data.column_unstack(state, self.oper.shape[0]) - ) + if self.stack: + state = _data.column_unstack(state, self.oper.shape[0]) + if self.normalize: + state = _normalize(None, state) + return self.oper.expect_data(t, state) cdef class _To_Qobj: cdef list dims, dims_flat cdef bint issuper cdef idxint N + cdef bint normalize - def __init__(self, base): + def __init__(self, base, normalize): self.dims = base.dims if not base.issuper: self.dims_flat = [1 for _ in base.dims[0]] self.issuper = base.issuper self.N = int(base.shape[0]**0.5) + self.normalize = normalize def __call__(self, t, state): if state.shape[0] == state.shape[1]: @@ -1108,6 +1123,8 @@ cdef class _To_Qobj: else: # rectangular state dims are patially lost... out = Qobj(state, dims=[self.dims[1], [state.shape[1]]]) + if self.normalize: + out = out.unit() return out @@ -1115,13 +1132,25 @@ def _pass_through(t, state): return state +def _normalize(t, state): + if state.shape[0] == state.shape[1]: + norm = _data.trace(state) + else: + norm = _data.norm.frobenius(state) + return _data.mul(state, 1 / norm) + + cdef class _Column_Stacker: cdef idxint N + cdef bint normalize - def __init__(self, shape): + def __init__(self, shape, normalize): self.N = int(shape**0.5) + self.normalize = normalize def __call__(self, t, state): if state.shape[1] == 1: - return _data.column_unstack(state, self.N) + state = _data.column_unstack(state, self.N) + if self.normalize: + state = _normalize(None, state) return state diff --git a/qutip/solver/floquet.py b/qutip/solver/floquet.py index 688315d19a..e578bec933 100644 --- a/qutip/solver/floquet.py +++ b/qutip/solver/floquet.py @@ -809,6 +809,11 @@ def _argument(self, args): if args: raise ValueError("FMESolver cannot update arguments") + def add_feedback(self, key, type): + raise NotImplementedError( + "The floquet solver does not support feedback currently." + ) + def start(self, state0, t0, *, floquet=False): """ Set the initial state and time for a step evolution. diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index a68b92561a..ffd0da6923 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -417,6 +417,13 @@ def _argument(self, args): for n_op in self._n_ops: n_op.arguments(args) + def add_feedback(self, key, type): + self.rhs._add_feedback(key, type, True) + for c_op in self._c_ops: + c_op._add_feedback(key, type, True) + for n_op in self._n_ops: + n_op._add_feedback(key, type, True) + def _run_one_traj(self, seed, state, tlist, e_ops): """ Run one trajectory and return the result. diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index f97298b390..b73d480f49 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -412,6 +412,11 @@ def _argument(self, args): ] super()._argument(args) + def add_feedback(self, key, type): + raise NotImplementedError( + "NM mcsolve does not support feedback currently." + ) + def rate_shift(self, t): """ Return the rate shift at time ``t``. diff --git a/qutip/solver/solver_base.py b/qutip/solver/solver_base.py index b2d9137dba..880dc8732b 100644 --- a/qutip/solver/solver_base.py +++ b/qutip/solver/solver_base.py @@ -417,4 +417,4 @@ def add_feedback(self, key, type): - Qobj, QobjEvo: The value is updated with the expectation value of the given operator and the state. """ - self.rhs.add_feedback(key, type) + self.rhs._add_feedback(key, type) diff --git a/qutip/tests/core/test_qobjevo.py b/qutip/tests/core/test_qobjevo.py index 257ed92762..b5100cb450 100644 --- a/qutip/tests/core/test_qobjevo.py +++ b/qutip/tests/core/test_qobjevo.py @@ -535,9 +535,9 @@ def __call__(self, t, data=None, qobj=None, e_val=None): def test_feedback_oper(): checker = Feedback_Checker_Coefficient() qevo = QobjEvo([qeye(2), checker]) - qevo.add_feedback("data", "data") - qevo.add_feedback("qobj", "qobj") - qevo.add_feedback("e_val", qeye(2)) + qevo._add_feedback("data", "data") + qevo._add_feedback("qobj", "qobj") + qevo._add_feedback("e_val", qeye(2)) checker.state = rand_ket(2) qevo.expect(0, checker.state) @@ -551,15 +551,15 @@ def test_feedback_oper(): def test_feedback_super(): checker = Feedback_Checker_Coefficient() qevo = QobjEvo([spre(qeye(2)), checker]) - qevo.add_feedback("data", "data") - qevo.add_feedback("qobj", "qobj") - qevo.add_feedback("e_val", qeye(2)) + qevo._add_feedback("data", "data") + qevo._add_feedback("qobj", "qobj") + qevo._add_feedback("e_val", qeye(2)) checker.state = rand_dm(2) qevo.expect(0, operator_to_vector(checker.state)) qevo.matmul_data(0, operator_to_vector(checker.state).data) - qevo.add_feedback("e_val", spre(qeye(2))) + qevo._add_feedback("e_val", spre(qeye(2))) checker.state = rand_dm(2) qevo.expect(0, operator_to_vector(checker.state)) @@ -567,8 +567,8 @@ def test_feedback_super(): checker = Feedback_Checker_Coefficient() qevo = QobjEvo([spre(qeye(2)), checker]) - qevo.add_feedback("data", "data") - qevo.add_feedback("qobj", "qobj") + qevo._add_feedback("data", "data") + qevo._add_feedback("qobj", "qobj") checker.state = rand_dm(4) checker.state.dims = [[[2],[2]], [[2],[2]]] @@ -576,7 +576,7 @@ def test_feedback_super(): checker = Feedback_Checker_Coefficient() qevo = QobjEvo([spre(qeye(2)), checker]) - qevo.add_feedback("data", "raw") + qevo._add_feedback("data", "raw") checker.state = operator_to_vector(rand_dm(2)) qevo.expect(0, checker.state) diff --git a/qutip/tests/solver/test_brmesolve.py b/qutip/tests/solver/test_brmesolve.py index 7b1aef0663..007ff6bf22 100644 --- a/qutip/tests/solver/test_brmesolve.py +++ b/qutip/tests/solver/test_brmesolve.py @@ -294,3 +294,19 @@ def test_hamiltonian_taking_arguments(): args = brmesolve([[H, 'ii']], psi0, times, a_ops, e_ops, args=args) for arg, no_arg in zip(args.expect, no_args.expect): np.testing.assert_allclose(arg, no_arg, atol=1e-10) + + +def test_feedback(): + N = 10 + tol = 1e-4 + psi0 = qutip.basis(N, 7) + a = qutip.destroy(N) + H = qutip.QobjEvo(qutip.num(N)) + a_op = ( + qutip.QobjEvo(a + a.dag()), + qutip.coefficient("(A.real - 4)*(w > 0)", args={"A": 7.+0j, "w": 0.}) + ) + solver = qutip.BRSolver(H, [a_op]) + solver.add_feedback("A", qutip.num(N)) + result = solver.run(psi0, np.linspace(0, 3, 31), e_ops=[qutip.num(N)]) + assert np.all(result.expect[0] > 4. - tol) diff --git a/qutip/tests/solver/test_mcsolve.py b/qutip/tests/solver/test_mcsolve.py index be5b5b3bad..160c90baa9 100644 --- a/qutip/tests/solver/test_mcsolve.py +++ b/qutip/tests/solver/test_mcsolve.py @@ -437,3 +437,23 @@ def test_dynamic_arguments(): c_ops = [[a, _dynamic], [a.dag(), _dynamic]] mc = mcsolve(H, state, times, c_ops, ntraj=25, args={"collapse": []}) assert all(len(collapses) <= 1 for collapses in mc.col_which) + + +def test_feedback(): + + def f(t, A): + return (A-4.) + + N = 10 + tol = 1e-6 + psi0 = qutip.basis(N, 7) + a = qutip.destroy(N) + H = qutip.QobjEvo(qutip.num(N)) + solver = qutip.MCSolver( + H, c_ops=[qutip.QobjEvo([a, f], args={"A": 7.})] + ) + solver.add_feedback("A", qutip.num(N)) + result = solver.run( + psi0, np.linspace(0, 3, 31), e_ops=[qutip.num(N)], ntraj=10 + ) + assert np.all(result.expect[0] > 4. - tol) diff --git a/qutip/tests/solver/test_mesolve.py b/qutip/tests/solver/test_mesolve.py index 7a72a02f64..1fd88efb5d 100644 --- a/qutip/tests/solver/test_mesolve.py +++ b/qutip/tests/solver/test_mesolve.py @@ -680,3 +680,19 @@ def test_mesolve_bad_state(): def test_mesolve_bad_options(): with pytest.raises(TypeError): MESolver(qutip.qeye(4), [], options=False) + + +def test_feedback(): + + def f(t, A): + return (A-4.) + + N = 10 + tol = 1e-14 + psi0 = qutip.basis(N, 7) + a = qutip.destroy(N) + H = qutip.QobjEvo(qutip.num(N)) + solver = qutip.MESolver(H) + solver.add_feedback("A", qutip.num(N)) + result = solver.run(psi0, np.linspace(0, 30, 301), e_ops=[qutip.num(N)]) + assert np.all(result.expect[0] > 4. - tol) diff --git a/qutip/tests/solver/test_sesolve.py b/qutip/tests/solver/test_sesolve.py index 12e0e23754..218cf619a9 100644 --- a/qutip/tests/solver/test_sesolve.py +++ b/qutip/tests/solver/test_sesolve.py @@ -304,3 +304,19 @@ def test_krylovsolve(always_compute_step): options = {"always_compute_step", always_compute_step} krylov_sol = krylovsolve(H, psi0, tlist, 20, e_ops=[e_op]).expect[0] np.testing.assert_allclose(ref, krylov_sol) + + +def test_feedback(): + + def f(t, A, qobj=None): + return (A-2.) + + N = 4 + tol = 1e-14 + psi0 = qutip.basis(N, N-1) + a = qutip.destroy(N) + H = qutip.QobjEvo([qutip.num(N), [a+a.dag(), f]], args={"A": N-1}) + solver = qutip.SESolver(H) + solver.add_feedback("A", qutip.num(N)) + result = solver.run(psi0, np.linspace(0, 30, 301), e_ops=[qutip.num(N)]) + assert np.all(result.expect[0] > 2 - tol) From 2725f59a1e2c2890fac3d28d26b2aef22c2ffc0a Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 28 Jul 2023 14:01:15 -0400 Subject: [PATCH 014/247] stochastic feedback --- qutip/core/cy/qobjevo.pyx | 15 +++---- qutip/solver/mcsolve.py | 69 ++++++++++++++++++++---------- qutip/solver/multitraj.py | 39 ++++++++++++++--- qutip/solver/sode/rouchon.py | 8 ++++ qutip/solver/sode/sode.py | 15 +++++++ qutip/solver/solver_base.py | 1 + qutip/solver/stochastic.py | 32 +++++++++++++- qutip/tests/core/test_qobjevo.py | 3 +- qutip/tests/solver/test_mcsolve.py | 35 +++++++++------ 9 files changed, 166 insertions(+), 51 deletions(-) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 68c24e8ba0..c9a945b3b6 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -198,6 +198,7 @@ cdef class QobjEvo: self._issuper = ( Q_object)._issuper self._isoper = ( Q_object)._isoper self.elements = ( Q_object).elements.copy() + self._feedback_functions = Q_object._feedback_functions.copy() if args: self.arguments(args) if compress: @@ -209,6 +210,7 @@ cdef class QobjEvo: self.shape = (0, 0) self._issuper = -1 self._isoper = -1 + self._feedback_functions = {} args = args or {} if ( @@ -481,18 +483,13 @@ cdef class QobjEvo: elif isinstance(feedback, (Qobj, QobjEvo)): if isinstance(feedback, Qobj): feedback = QobjEvo(feedback) - if feedback.dims == self.dims: - self._feedback_functions[key] = \ - _Expect(feedback, normalize, False) - elif self.issuper and self.dims[1] == feedback.dims: + if self.issuper and self.dims[1] == feedback.dims: # tr(op @ dm) cases self._feedback_functions[key] = \ _Expect(feedback, normalize, True) else: - raise TypeError( - "dims of the feedback operator do " - "not match the original system." - ) + self._feedback_functions[key] = \ + _Expect(feedback, normalize, False) else: ValueError("Feedback type not understood.") @@ -527,6 +524,7 @@ cdef class QobjEvo: str(self.dims) + ", " + str(other.dims)) for element in ( other).elements: self.elements.append(element) + self._feedback_functions.update(other._feedback_functions) elif isinstance(other, Qobj): if other.dims != self.dims: raise TypeError("incompatible dimensions" + @@ -648,6 +646,7 @@ cdef class QobjEvo: for left, right in itertools.product( self.elements, ( other).elements )] + self._feedback_functions.update(other._feedback_functions) else: return NotImplemented return self diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index ffd0da6923..a1b253e101 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -155,21 +155,59 @@ def mcsolve(H, state, tlist, c_ops=(), e_ops=None, ntraj=500, *, return result +class _MCSystem: + def __init__(self, rhs, c_ops, n_ops): + self.rhs = rhs + self.c_ops = c_ops + self.n_ops = n_ops + self._collapse_key = "" + + def __call__(self): + return self.rhs + + def __getattr__(self, attr): + if hasattr(self.rhs, attr): + return getattr(self.rhs, attr) + raise AttributeError + + def arguments(self, args): + self.rhs.arguments(args) + for c_op in self.c_ops: + c_op.arguments(args) + for n_op in self.n_ops: + n_op.arguments(args) + + def add_feedback(self, key, type): + if type == "collapse": + self._collapse_key = key + return + self.rhs._add_feedback(key, type, True) + for c_op in self.c_ops: + c_op._add_feedback(key, type, True) + for n_op in self.n_ops: + n_op._add_feedback(key, type, True) + + def register_feedback(self, type, val): + if type == "collapse" and self._collapse_key: + self.arguments({self._collapse_key: val}) + + class MCIntegrator: """ Integrator like object for mcsolve trajectory. """ name = "mcsolve" - def __init__(self, integrator, c_ops, n_ops, options=None): + def __init__(self, integrator, system, options=None): self._integrator = integrator - self._c_ops = c_ops - self._n_ops = n_ops + self.system = system + self._c_ops = system.c_ops + self._n_ops = system.n_ops self.options = options self._generator = None self.method = f"{self.name} {self._integrator.method}" self._is_set = False - self.issuper = c_ops[0].issuper + self.issuper = self._c_ops[0].issuper def set_state(self, t, state0, generator): """ @@ -187,6 +225,7 @@ def set_state(self, t, state0, generator): Random number generator. """ self.collapses = [] + self.system.register_feedback("collapse", self.collapses) self._generator = generator self.target_norm = self._generator.random() self._integrator.set_state(t, state0) @@ -386,7 +425,8 @@ def __init__(self, H, c_ops, *, options=None): self._num_collapse = len(self._c_ops) - super().__init__(rhs, options=options) + system = _MCSystem(rhs, self._c_ops, self._n_ops) + super().__init__(system, options=options) def _restore_state(self, data, *, copy=True): """ @@ -409,21 +449,6 @@ def _initialize_stats(self): }) return stats - def _argument(self, args): - self._integrator.arguments(args) - self.rhs.arguments(args) - for c_op in self._c_ops: - c_op.arguments(args) - for n_op in self._n_ops: - n_op.arguments(args) - - def add_feedback(self, key, type): - self.rhs._add_feedback(key, type, True) - for c_op in self._c_ops: - c_op._add_feedback(key, type, True) - for n_op in self._n_ops: - n_op._add_feedback(key, type, True) - def _run_one_traj(self, seed, state, tlist, e_ops): """ Run one trajectory and return the result. @@ -445,9 +470,9 @@ def _get_integrator(self): integrator = method else: raise ValueError("Integrator method not supported.") - integrator_instance = integrator(self.rhs, self.options) + integrator_instance = integrator(self.system(), self.options) mc_integrator = self.mc_integrator_class( - integrator_instance, self._c_ops, self._n_ops, self.options + integrator_instance, self.system, self.options ) self._init_integrator_time = time() - _time_start return mc_integrator diff --git a/qutip/solver/multitraj.py b/qutip/solver/multitraj.py index e9fdd1d672..0ebd0a56ce 100644 --- a/qutip/solver/multitraj.py +++ b/qutip/solver/multitraj.py @@ -2,11 +2,34 @@ from .parallel import _get_map from time import time from .solver_base import Solver +from ..core import QobjEvo import numpy as np __all__ = ["MultiTrajSolver"] +class _MTSystem: + def __init__(self, rhs): + self.rhs = rhs + + def __call__(self): + return self.rhs + + def arguments(self, args): + self.rhs.arguments(args) + + def add_feedback(self, key, type): + self.rhs._add_feedback(key, type) + + def register_feedback(self, type, val): + pass + + def __getattr__(self, attr): + if hasattr(self.rhs, attr): + return getattr(self.rhs, attr) + raise AttributeError + + class MultiTrajSolver(Solver): """ Basic class for multi-trajectory evolutions. @@ -47,7 +70,11 @@ class MultiTrajSolver(Solver): } def __init__(self, rhs, *, options=None): - self.rhs = rhs + if isinstance(rhs, QobjEvo): + self.system = _MTSystem(rhs) + else: + self.system = rhs + self.rhs = self.system() self.options = options self.seed_sequence = np.random.SeedSequence() self._integrator = self._get_integrator() @@ -231,7 +258,11 @@ def _read_seed(self, seed, ntraj): def _argument(self, args): """Update the args, for the `rhs` and `c_ops` and other operators.""" if args: - self.rhs.arguments(args) + self.system.arguments(args) + + def add_feedback(self, key, type): + self.system.add_feedback(key, type) + self._integrator.reset(hard=True) def _get_generator(self, seed): """ @@ -250,7 +281,3 @@ def _get_generator(self, seed): else: generator = np.random.default_rng(seed) return generator - - @classmethod - def avail_integrators(cls): - return cls._avail_integrators diff --git a/qutip/solver/sode/rouchon.py b/qutip/solver/sode/rouchon.py index 30c48fd0cb..19731ea6d3 100644 --- a/qutip/solver/sode/rouchon.py +++ b/qutip/solver/sode/rouchon.py @@ -132,5 +132,13 @@ def options(self): def options(self, new_options): Integrator.options.fset(self, new_options) + def reset(self, hard=False): + if self._is_set: + state = self.get_state() + if hard: + raise NotImplementedError + if self._is_set: + self.set_state(*state) + StochasticSolver.add_integrator(RouchonSODE, "rouchon") diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index 94b04409a5..4bbc03a3e8 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -44,6 +44,7 @@ class SIntegrator(Integrator): keys, not the full options object passed to the solver. Options' keys included here will be supported by the :cls:SolverOdeOptions. """ + _is_set = False def set_state(self, t, state0, generator): """ @@ -63,6 +64,7 @@ def set_state(self, t, state0, generator): self.t = t self.state = state0 self.generator = generator + self._is_set = True def get_state(self, copy=True): return self.t, self.state, self.generator @@ -92,6 +94,16 @@ def integrate(self, t, copy=True): def mcstep(self, t, copy=True): raise NotImplementedError + def reset(self, hard=False): + print("reset") + if self._is_set: + state = self.get_state() + if hard: + self.system = self.rhs(self.options) + self.step_func = self.stepper(self.system).run + if self._is_set: + self.set_state(*state) + class _Explicit_Simple_Integrator(SIntegrator): """ @@ -108,6 +120,7 @@ class _Explicit_Simple_Integrator(SIntegrator): def __init__(self, rhs, options): self._options = self.integrator_options.copy() self.options = options + self.rhs = rhs self.system = rhs(self.options) self.step_func = self.stepper(self.system).run @@ -169,6 +182,7 @@ class _Implicit_Simple_Integrator(_Explicit_Simple_Integrator): def __init__(self, rhs, options): self._options = self.integrator_options.copy() self.options = options + self.rhs = rhs self.system = rhs(self.options) self.step_func = self.stepper( self.system, @@ -246,6 +260,7 @@ class PredCorr_SODE(_Explicit_Simple_Integrator): def __init__(self, rhs, options): self._options = self.integrator_options.copy() self.options = options + self.rhs = rhs self.system = rhs(self.options) self.step_func = self.stepper( self.system, self.options["alpha"], self.options["eta"] diff --git a/qutip/solver/solver_base.py b/qutip/solver/solver_base.py index 880dc8732b..652f7658c7 100644 --- a/qutip/solver/solver_base.py +++ b/qutip/solver/solver_base.py @@ -409,6 +409,7 @@ def add_feedback(self, key, type): type : str, Qobj, QobjEvo Format of the `state_t`. + - "qobj": As a Qobj, either a ket or dm. - "data": As a qutip data layer object. Density matrices will be square matrix. diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index d5d0504a85..a7da813b94 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -198,6 +198,7 @@ def __init__(self, issuper, H, sc_ops, c_ops, heterodyne): self.issuper = issuper self.heterodyne = heterodyne + self._noise_key = None if heterodyne: sc_ops = [] @@ -211,7 +212,7 @@ def __init__(self, issuper, H, sc_ops, c_ops, heterodyne): else: self.dims = self.H.dims - def __call__(self, options): + def __call__(self, options={}): if self.issuper: return StochasticOpenSystem( self.H, self.sc_ops, self.c_ops, options.get("derr_dt", 1e-6) @@ -219,6 +220,27 @@ def __call__(self, options): else: return StochasticClosedSystem(self.H, self.sc_ops) + def arguments(self, args): + self.H.arguments(args) + for c_op in self.c_ops: + c_op.arguments(args) + for sc_op in self.sc_ops: + sc_op.arguments(args) + + def add_feedback(self, key, type): + if type == "noise": + self._noise_key = key + return + self.H._add_feedback(key, type) + for c_op in self.c_ops: + c_op._add_feedback(key, type) + for sc_op in self.sc_ops: + sc_op._add_feedback(key, type) + + def register_feedback(self, type, val): + if type == "noise" and self._noise_key: + self.arguments({self._noise_key: val}) + def smesolve( H, rho0, tlist, c_ops=(), sc_ops=(), heterodyne=False, *, @@ -495,7 +517,13 @@ def __init__(self, H, sc_ops, heterodyne, *, c_ops=(), options=None): raise ValueError("c_ops are not supported by ssesolve.") rhs = _StochasticRHS(self._open, H, sc_ops, c_ops, heterodyne) - super().__init__(rhs, options=options) + self.rhs = rhs + self.system = rhs + self.options = options + self.seed_sequence = np.random.SeedSequence() + self._integrator = self._get_integrator() + self._state_metadata = {} + self.stats = self._initialize_stats() if heterodyne: self._m_ops = [] diff --git a/qutip/tests/core/test_qobjevo.py b/qutip/tests/core/test_qobjevo.py index b5100cb450..6c235dff7d 100644 --- a/qutip/tests/core/test_qobjevo.py +++ b/qutip/tests/core/test_qobjevo.py @@ -528,7 +528,8 @@ def __call__(self, t, data=None, qobj=None, e_val=None): if qobj is not None: assert qobj == self.state if e_val is not None: - assert e_val == expect(qeye(self.state.dims[0]), self.state) + expected = expect(qeye(self.state.dims[0]), self.state) + assert e_val == pytest.approx(expected, abs=1e-7) return 1. diff --git a/qutip/tests/solver/test_mcsolve.py b/qutip/tests/solver/test_mcsolve.py index 160c90baa9..89babb8699 100644 --- a/qutip/tests/solver/test_mcsolve.py +++ b/qutip/tests/solver/test_mcsolve.py @@ -439,21 +439,32 @@ def test_dynamic_arguments(): assert all(len(collapses) <= 1 for collapses in mc.col_which) -def test_feedback(): - - def f(t, A): - return (A-4.) - - N = 10 +@pytest.mark.parametrize(["func", "kind", "val0"], [ + pytest.param( + lambda t, A: A-4, + lambda: qutip.num(10), + 7.+0j, + id="expect" + ), + pytest.param( + lambda t, A: (len(A) < 3) * 1.0, + lambda: "collapse", + [], + id="collapse" + ), +]) +def test_feedback(func, kind, val0): tol = 1e-6 - psi0 = qutip.basis(N, 7) - a = qutip.destroy(N) - H = qutip.QobjEvo(qutip.num(N)) + psi0 = qutip.basis(10, 7) + a = qutip.destroy(10) + H = qutip.QobjEvo(qutip.num(10)) solver = qutip.MCSolver( - H, c_ops=[qutip.QobjEvo([a, f], args={"A": 7.})] + H, + c_ops=[qutip.QobjEvo([a, func], args={"A": val0})], + options={"map": "serial"} ) - solver.add_feedback("A", qutip.num(N)) + solver.add_feedback("A", kind()) result = solver.run( - psi0, np.linspace(0, 3, 31), e_ops=[qutip.num(N)], ntraj=10 + psi0,np.linspace(0, 3, 31), e_ops=[qutip.num(10)], ntraj=10 ) assert np.all(result.expect[0] > 4. - tol) From 315496fd4aacffd0ad8d35f511e594b766a0813d Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 31 Jul 2023 17:07:48 -0400 Subject: [PATCH 015/247] stochastic wiener feedback --- qutip/solver/sode/_noise.py | 38 ++++++++++++++++++++++++++++++++++-- qutip/solver/sode/rouchon.py | 12 ++++-------- qutip/solver/sode/sode.py | 26 +++++++++++++----------- qutip/solver/stochastic.py | 2 +- 4 files changed, 56 insertions(+), 22 deletions(-) diff --git a/qutip/solver/sode/_noise.py b/qutip/solver/sode/_noise.py index ab5993c8b9..2d3f1adb7f 100644 --- a/qutip/solver/sode/_noise.py +++ b/qutip/solver/sode/_noise.py @@ -1,11 +1,45 @@ import numpy as np -__all__ = [] +__all__ = ["Wiener"] + + +class Wiener: + """ + Wiener process. + """ + def __init__(self, t0, dt, generator, shape): + self.t0 = t0 + self.dt = dt + self.generator = generator + self.t_end = t0 + self.shape = shape + self.process = np.zeros((1,) + shape, dtype=float) + + def _extend(self, t): + N_new_vals = int((t - self.t_end + self.dt*0.01) // self.dt) + dW = self.generator.normal( + 0, np.sqrt(self.dt), size=(N_new_vals,) + self.shape + ) + W = self.process[-1, :, :] + np.cumsum(dW, axis=0) + self.process = np.concatenate((self.process, W), axis=0) + self.t_end = self.t0 + (self.process.shape[0] - 1) * self.dt + + def dW(self, t, N): + if t + N * self.dt > self.t_end: + self._extend(t + N * self.dt) + idx0 = int((t - self.t0 + self.dt * 0.01) // self.dt) + return np.diff(self.process[idx0:idx0 + N + 1, :, :], axis=0) + + def __call__(self, t): + if t > self.t_end: + self._extend(t) + idx = int((t - self.t0 + self.dt * 0.01) // self.dt) + return self.process[idx, :, :] class _Noise: """ - Weiner process generator used for tests. + Wiener process generator used for tests. """ def __init__(self, T, dt, num=1): diff --git a/qutip/solver/sode/rouchon.py b/qutip/solver/sode/rouchon.py index 19731ea6d3..b6f11fa4f8 100644 --- a/qutip/solver/sode/rouchon.py +++ b/qutip/solver/sode/rouchon.py @@ -4,6 +4,7 @@ from ..stochastic import StochasticSolver from .sode import SIntegrator from ..integrator.integrator import Integrator +from ._noise import Wiener __all__ = ["RouchonSODE"] @@ -72,15 +73,10 @@ def integrate(self, t, copy=True): dt = self.options["dt"] N, extra = np.divmod(delta_t, dt) N = int(N) - if extra > self.options["tol"]: - # Not a whole number of steps. + if extra > 0.5 * dt: + # Not a whole number of steps, round to higher N += 1 - dt = delta_t / N - dW = self.generator.normal( - 0, - np.sqrt(dt), - size=(N, self.num_collapses) - ) + dW = self.wiener.dW(self.t, N) if self._issuper: self.state = unstack_columns(self.state) diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index 4bbc03a3e8..2c7764e84a 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -2,7 +2,7 @@ from . import _sode from ..integrator.integrator import Integrator from ..stochastic import StochasticSolver, SMESolver - +from ._noise import Wiener __all__ = ["SIntegrator", "PlatenSODE", "PredCorr_SODE"] @@ -63,11 +63,19 @@ def set_state(self, t, state0, generator): """ self.t = t self.state = state0 - self.generator = generator + if isinstance(generator, Wiener): + self.wiener = generator + else: + self.wiener = Wiener( + t, self.options["dt"], generator, + (self.N_dw, self.system.num_collapse) + ) + self.rhs.register_feedback("wiener_process", self.wiener) + self.system = self.rhs(self.options) self._is_set = True def get_state(self, copy=True): - return self.t, self.state, self.generator + return self.t, self.state, self.wiener def integrate(self, t, copy=True): """ @@ -95,7 +103,6 @@ def mcstep(self, t, copy=True): raise NotImplementedError def reset(self, hard=False): - print("reset") if self._is_set: state = self.get_state() if hard: @@ -121,7 +128,7 @@ def __init__(self, rhs, options): self._options = self.integrator_options.copy() self.options = options self.rhs = rhs - self.system = rhs(self.options) + self.system = None self.step_func = self.stepper(self.system).run def integrate(self, t, copy=True): @@ -134,13 +141,10 @@ def integrate(self, t, copy=True): dt = self.options["dt"] N, extra = np.divmod(delta_t, dt) N = int(N) - if extra > self.options["tol"]: - # Not a whole number of steps. + if extra > 0.5 * dt: + # Not a whole number of steps, round to higher N += 1 - dt = delta_t / N - dW = self.generator.normal( - 0, np.sqrt(dt), size=(N, self.N_dw, self.system.num_collapse) - ) + dW = self.wiener.dW(self.t, N) self.state = self.step_func(self.t, self.state, dt, dW, N) self.t += dt * N diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index a7da813b94..b2f7dc1feb 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -238,7 +238,7 @@ def add_feedback(self, key, type): sc_op._add_feedback(key, type) def register_feedback(self, type, val): - if type == "noise" and self._noise_key: + if type == "wiener_process" and self._noise_key: self.arguments({self._noise_key: val}) From cfeddf07f722a158675b8a5d554126087be8c061 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 1 Aug 2023 14:05:04 -0400 Subject: [PATCH 016/247] Test for stochastic feedback --- qutip/solver/mcsolve.py | 2 ++ qutip/solver/sode/rouchon.py | 37 ++++++++++++++++++++++++-- qutip/solver/sode/sode.py | 7 ++++- qutip/tests/solver/test_sode_method.py | 4 +-- qutip/tests/solver/test_stochastic.py | 22 +++++++++++++++ 5 files changed, 67 insertions(+), 5 deletions(-) diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index a1b253e101..914d06b86c 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -166,6 +166,8 @@ def __call__(self): return self.rhs def __getattr__(self, attr): + if attr == "rhs": + raise AttributeError if hasattr(self.rhs, attr): return getattr(self.rhs, attr) raise AttributeError diff --git a/qutip/solver/sode/rouchon.py b/qutip/solver/sode/rouchon.py index b6f11fa4f8..f485996541 100644 --- a/qutip/solver/sode/rouchon.py +++ b/qutip/solver/sode/rouchon.py @@ -35,7 +35,12 @@ class RouchonSODE(SIntegrator): def __init__(self, rhs, options): self._options = self.integrator_options.copy() self.options = options + self.rhs = rhs + self.system = None + self._make_operators() + def _make_operators(self): + rhs = self.rhs self.H = rhs.H if self.H.issuper: raise TypeError("The rouchon stochastic integration method can't" @@ -63,12 +68,40 @@ def __init__(self, rhs, options): self.id = _data.identity[dtype](self.H.shape[0]) + def set_state(self, t, state0, generator): + """ + Set the state of the SODE solver. + + Parameters + ---------- + t : float + Initial time + + state0 : qutip.Data + Initial state. + + generator : numpy.random.generator + Random number generator. + """ + self.t = t + self.state = state0 + if isinstance(generator, Wiener): + self.wiener = generator + else: + self.wiener = Wiener( + t, self.options["dt"], generator, + (1, self.num_collapses,) + ) + self.rhs.register_feedback("wiener_process", self.wiener) + self._make_operators() + self._is_set = True + def integrate(self, t, copy=True): delta_t = (t - self.t) if delta_t < 0: raise ValueError("Stochastic integration need increasing times") elif delta_t == 0: - return self.t, self.state, np.zeros(self.N_dw) + return self.t, self.state, np.zeros() dt = self.options["dt"] N, extra = np.divmod(delta_t, dt) @@ -76,7 +109,7 @@ def integrate(self, t, copy=True): if extra > 0.5 * dt: # Not a whole number of steps, round to higher N += 1 - dW = self.wiener.dW(self.t, N) + dW = self.wiener.dW(self.t, N)[:, 0, :] if self._issuper: self.state = unstack_columns(self.state) diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index 2c7764e84a..e35e3eeb88 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -66,12 +66,17 @@ def set_state(self, t, state0, generator): if isinstance(generator, Wiener): self.wiener = generator else: + if self.system: + num_collapse = self.system.num_collapse + else: + num_collapse = len(self.rhs.sc_ops) self.wiener = Wiener( t, self.options["dt"], generator, - (self.N_dw, self.system.num_collapse) + (self.N_dw, num_collapse) ) self.rhs.register_feedback("wiener_process", self.wiener) self.system = self.rhs(self.options) + self.step_func = self.stepper(self.system).run self._is_set = True def get_state(self, copy=True): diff --git a/qutip/tests/solver/test_sode_method.py b/qutip/tests/solver/test_sode_method.py index 075dbe9a13..356a2ce70a 100644 --- a/qutip/tests/solver/test_sode_method.py +++ b/qutip/tests/solver/test_sode_method.py @@ -100,8 +100,8 @@ def get_error_order_integrator(integrator, ref_integrator, state, plot=False): # state = rand_ket(system.dims[0]).data err = np.zeros(len(ts), dtype=float) for i, t in enumerate(ts): - integrator.options["dt"] = 0.1 - ref_integrator.options["dt"] = 0.1 + integrator.options["dt"] = t + ref_integrator.options["dt"] = t integrator.set_state(0., state, np.random.default_rng(0)) ref_integrator.set_state(0., state, np.random.default_rng(0)) out = integrator.integrate(t)[1] diff --git a/qutip/tests/solver/test_stochastic.py b/qutip/tests/solver/test_stochastic.py index f6843c997e..0e2c6d1874 100644 --- a/qutip/tests/solver/test_stochastic.py +++ b/qutip/tests/solver/test_stochastic.py @@ -316,3 +316,25 @@ def test_m_ops(heterodyne): noise = res.expect[0][1:] - res.measurement[0][0] assert np.mean(noise) == pytest.approx(0., abs=std/50**0.5 * 4) assert np.std(noise) == pytest.approx(std, abs=std/50**0.5 * 4) + + +def test_feedback(): + tol = 0.05 + N = 10 + ntraj = 5 + + def func(t, A): + return (A - 6) * (A.real > 6.) + + H = num(10) + sc_ops = [QobjEvo([destroy(N), func], args={"A": 8})] + psi0 = basis(N, N-2) + + times = np.linspace(0, 10, 101) + options = {"map": "serial", "dt": 0.001} + + solver = SMESolver(H, sc_ops=sc_ops, heterodyne=False, options=options) + solver.add_feedback("A", spre(num(10))) + results = solver.run(psi0, times, e_ops=[num(N)], ntraj=ntraj) + + assert np.all(results.expect[0] > 6.-1e-6) From 88460061ba7ea5df6861a53498cbfad85555427c Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 1 Aug 2023 15:49:44 -0400 Subject: [PATCH 017/247] Add docstrings --- qutip/solver/mcsolve.py | 33 ++++++++++++++++ qutip/solver/multitraj.py | 3 ++ qutip/solver/sode/_noise.py | 2 +- qutip/solver/stochastic.py | 56 ++++++++++++++++++++++++++- qutip/tests/solver/test_stochastic.py | 10 +++-- 5 files changed, 98 insertions(+), 6 deletions(-) diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index 914d06b86c..eccb0a29c5 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -156,6 +156,9 @@ def mcsolve(H, state, tlist, c_ops=(), e_ops=None, ntraj=500, *, class _MCSystem: + """ + Container for the operators of the solver. + """ def __init__(self, rhs, c_ops, n_ops): self.rhs = rhs self.c_ops = c_ops @@ -552,3 +555,33 @@ def avail_integrators(cls): **Solver.avail_integrators(), **cls._avail_integrators, } + + def add_feedback(self, key, type): + """ + Register an argument to be updated with the state during the evolution. + + Equivalent to do: + `solver.argument(key=state_t)` + + Parameters + ---------- + key : str + Arguments key to update. + + type : str, Qobj, QobjEvo + Format of the `state_t`. + + - "qobj": As a Qobj, either a ket or dm. + - "data": As a qutip data layer object. Density matrices will be + square matrix. + - "raw": As a qutip data layer object. Density matrices will be + columns stacked: shape=(N**2, 1). + - Qobj, QobjEvo: The value is updated with the expectation value of + the given operator and the state. + - "collapse": The value is replaced by a list of + ``(collapse time, collapse operator number)``. It start as an + empty list. This list is the same as the one returned in the + evolution result and thus should not be modified. + """ + self.system.add_feedback(key, type) + self._integrator.reset(hard=True) diff --git a/qutip/solver/multitraj.py b/qutip/solver/multitraj.py index 0ebd0a56ce..465cb45eea 100644 --- a/qutip/solver/multitraj.py +++ b/qutip/solver/multitraj.py @@ -9,6 +9,9 @@ class _MTSystem: + """ + Container for the operators of the solver. + """ def __init__(self, rhs): self.rhs = rhs diff --git a/qutip/solver/sode/_noise.py b/qutip/solver/sode/_noise.py index 2d3f1adb7f..e1b4592a49 100644 --- a/qutip/solver/sode/_noise.py +++ b/qutip/solver/sode/_noise.py @@ -34,7 +34,7 @@ def __call__(self, t): if t > self.t_end: self._extend(t) idx = int((t - self.t0 + self.dt * 0.01) // self.dt) - return self.process[idx, :, :] + return self.process[idx, 0, :] class _Noise: diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index b2f7dc1feb..e7e4310de1 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -228,7 +228,32 @@ def arguments(self, args): sc_op.arguments(args) def add_feedback(self, key, type): - if type == "noise": + """ + Register an argument to be updated with the state during the evolution. + + Equivalent to do: + `solver.argument(key=state_t)` + + Parameters + ---------- + key : str + Arguments key to update. + + type : str, Qobj, QobjEvo + Format of the `state_t`. + + - "qobj": As a Qobj, either a ket or dm. + - "data": As a qutip data layer object. Density matrices will be + square matrix. + - "raw": As a qutip data layer object. Density matrices will be + columns stacked: shape=(N**2, 1). + - Qobj, QobjEvo: The value is updated with the expectation value of + the given operator and the state. + - "wiener_process": The value is replaced by a function ``W(t)`` + that return the wiener process value at the time t. The process + is a step function with step of lenght ``options["dt"]``. + """ + if type == "wiener_process": self._noise_key = key return self.H._add_feedback(key, type) @@ -699,6 +724,35 @@ def options(self): def options(self, new_options): MultiTrajSolver.options.fset(self, new_options) + def add_feedback(self, key, type): + """ + Register an argument to be updated with the state during the evolution. + + Equivalent to do: + `solver.argument(key=state_t)` + + Parameters + ---------- + key : str + Arguments key to update. + + type : str, Qobj, QobjEvo + Format of the `state_t`. + + - "qobj": As a Qobj, either a ket or dm. + - "data": As a qutip data layer object. Density matrices will be + square matrix. + - "raw": As a qutip data layer object. Density matrices will be + columns stacked: shape=(N**2, 1). + - Qobj, QobjEvo: The value is updated with the expectation value of + the given operator and the state. + - "wiener_process": The value is replaced by a function ``W(t)`` + that return the wiener process value at the time t. The process + is a step function with step of lenght ``options["dt"]``. + """ + self.system.add_feedback(key, type) + self._integrator.reset(hard=True) + class SMESolver(StochasticSolver): r""" diff --git a/qutip/tests/solver/test_stochastic.py b/qutip/tests/solver/test_stochastic.py index 0e2c6d1874..5256dc9905 100644 --- a/qutip/tests/solver/test_stochastic.py +++ b/qutip/tests/solver/test_stochastic.py @@ -323,18 +323,20 @@ def test_feedback(): N = 10 ntraj = 5 - def func(t, A): - return (A - 6) * (A.real > 6.) + def func(t, A, W): + return (A - 6) * (A.real > 6.) * W(t) H = num(10) - sc_ops = [QobjEvo([destroy(N), func], args={"A": 8})] - psi0 = basis(N, N-2) + sc_ops = [QobjEvo([destroy(N), func], args={"A": 8, "W": lambda t: 0.})] + psi0 = basis(N, N-3) times = np.linspace(0, 10, 101) options = {"map": "serial", "dt": 0.001} solver = SMESolver(H, sc_ops=sc_ops, heterodyne=False, options=options) solver.add_feedback("A", spre(num(10))) + solver.add_feedback("W", "wiener_process") results = solver.run(psi0, times, e_ops=[num(N)], ntraj=ntraj) assert np.all(results.expect[0] > 6.-1e-6) + assert np.all(results.expect[0][-20:] < 6.7) From bd018ea54e336b37e106028d51487af9b41236a9 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 2 Aug 2023 10:27:23 -0400 Subject: [PATCH 018/247] Fix warnings in stochastic feedback test --- qutip/solver/stochastic.py | 7 +++++-- qutip/tests/solver/test_stochastic.py | 4 ++-- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index e7e4310de1..777e5acabe 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -747,8 +747,11 @@ def add_feedback(self, key, type): - Qobj, QobjEvo: The value is updated with the expectation value of the given operator and the state. - "wiener_process": The value is replaced by a function ``W(t)`` - that return the wiener process value at the time t. The process - is a step function with step of lenght ``options["dt"]``. + that return an array of wiener processes value at the time t. The + wiener process for the i-th sc_ops is the i-th element for + homodyne detection and the (2i, 2i+1) pairs of process in + heterodyne detection. The process is a step function with step of + lenght ``options["dt"]``. """ self.system.add_feedback(key, type) self._integrator.reset(hard=True) diff --git a/qutip/tests/solver/test_stochastic.py b/qutip/tests/solver/test_stochastic.py index 5256dc9905..382184c911 100644 --- a/qutip/tests/solver/test_stochastic.py +++ b/qutip/tests/solver/test_stochastic.py @@ -324,10 +324,10 @@ def test_feedback(): ntraj = 5 def func(t, A, W): - return (A - 6) * (A.real > 6.) * W(t) + return (A - 6) * (A.real > 6.) * W(t)[0] H = num(10) - sc_ops = [QobjEvo([destroy(N), func], args={"A": 8, "W": lambda t: 0.})] + sc_ops = [QobjEvo([destroy(N), func], args={"A": 8, "W": lambda t: [0.]})] psi0 = basis(N, N-3) times = np.linspace(0, 10, 101) From f0f8e9aace92879957445e035d92bf228d1d36b7 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 4 Aug 2023 14:02:02 -0400 Subject: [PATCH 019/247] Fix comments --- qutip/core/qobj.py | 27 +++++++++++++++------------ qutip/solver/heom/bofin_solvers.py | 1 - 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/qutip/core/qobj.py b/qutip/core/qobj.py index 8e4980bfdd..e0a269ad17 100644 --- a/qutip/core/qobj.py +++ b/qutip/core/qobj.py @@ -271,35 +271,38 @@ class Qobj: __array_ufunc__ = None def _initialize_data(self, arg, dims, copy): - self._dims = None - self._data = None if isinstance(arg, _data.Data): self._data = arg.copy() if copy else arg - self.dims = dims or [[arg.shape[0]], [arg.shape[1]]] + self._dims = Dimensions(dims or [[arg.shape[0]], [arg.shape[1]]]) elif isinstance(arg, Qobj): self._data = arg.data.copy() if copy else arg.data - self._dims = dims or arg._dims - if arg._isherm is not None: + self._dims = Dimensions(dims or arg._dims) + if self._isherm is None and arg._isherm is not None: self._isherm = arg._isherm - if arg._isunitary is not None: + if self._isunitary is None and arg._isunitary is not None: self._isunitary = arg._isunitary else: self._data = _data.create(arg, copy=copy) + dims = Dimensions( + dims or [[self._data.shape[0]], [self._data.shape[1]]] + ) if ( dims + and self._data.shape[1] == 1 and self._data.shape != dims.shape and self._data.shape == dims.shape[::-1] ): + # 1D array are ket, convert to bra if bra dims are passed. self._data = _data.transpose(self._data) - self.dims = dims or [[self._data.shape[0]], [self._data.shape[1]]] + self._dims = dims + if self._dims.shape != self._data.shape: + raise ValueError('Provided dimensions do not match the data: ' + + f"{self._dims.shape} vs {self._data.shape}") def __init__(self, arg=None, dims=None, type=None, copy=True, superrep=None, isherm=None, isunitary=None): self._isherm = isherm self._isunitary = isunitary - self._superrep = None - if isinstance(dims, list): - dims = Dimensions(dims) self._initialize_data(arg, dims, copy) # Dims are guessed from the data and need to be changed to super. @@ -336,7 +339,7 @@ def dims(self): @dims.setter def dims(self, dims): dims = Dimensions(dims, rep=self.superrep) - if self._data and dims.shape != self._data.shape: + if dims.shape != self._data.shape: raise ValueError('Provided dimensions do not match the data: ' + f"{dims.shape} vs {self._data.shape}") self._dims = dims @@ -383,7 +386,7 @@ def data(self): def data(self, data): if not isinstance(data, _data.Data): raise TypeError('Qobj data must be a data-layer format.') - if self._dims and self._dims.shape != data.shape: + if self._dims.shape != data.shape: raise ValueError('Provided data do not match the dimensions: ' + f"{dims.shape} vs {self._data.shape}") self._data = data diff --git a/qutip/solver/heom/bofin_solvers.py b/qutip/solver/heom/bofin_solvers.py index f650f5be8a..63a8c62962 100644 --- a/qutip/solver/heom/bofin_solvers.py +++ b/qutip/solver/heom/bofin_solvers.py @@ -882,7 +882,6 @@ def _kron(x): # needed. assert isinstance(rhs_mat, _csr.CSR) assert isinstance(rhs, QobjEvo) - print(rhs.dims, rhs_dims) assert rhs.dims == rhs_dims return rhs From 2f3974ccaa5a717a8d77c86c69ececdf24b9eb7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Thu, 10 Aug 2023 11:09:30 -0400 Subject: [PATCH 020/247] Apply suggestions from code review Co-authored-by: Simon Cross --- qutip/core/dimensions.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index 02e47d0723..81adc34780 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -328,8 +328,7 @@ def dims_idxs_to_tensor_idxs(dims, indices): def to_tensor_rep(q_oper): """ - Transform a ``Qobj`` to a numpy array of one with it's shape the dimensions - flattened. + Transform a ``Qobj`` to a numpy array whose shape is the flattened dimensions. ``` ket.dims == [[2, 3], [1]] @@ -442,8 +441,8 @@ class Space(metaclass=MetaSpace): _stored_dims = {} def __init__(self, dims): idims = int(dims) - if dims <= 0 or idims != dims: - raise ValueError("Dimensions must be integers >= 0") + if idims <= 0 or idims != dims: + raise ValueError("Dimensions must be integers > 0") # Size of the hilbert space self.size = dims self.issuper = False @@ -510,6 +509,8 @@ def replace(self, idx, new): ``Space([2, 3, 4]).replace(1, 5) == Space([2, 5, 4])`` """ + if idx != 0: + raise ValueError("Cannot replace a non-zero index in a flat space.") return Space(new) From 38800d83cebce4389864313a26a4683c78de11f9 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 10 Aug 2023 11:53:50 -0400 Subject: [PATCH 021/247] improve code climate --- qutip/core/dimensions.py | 13 ++++++++++--- qutip/core/energy_restricted.py | 7 +++++-- qutip/core/operators.py | 15 ++++++++------- qutip/core/qobj.py | 6 +----- 4 files changed, 24 insertions(+), 17 deletions(-) diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index 81adc34780..72b2917627 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -328,7 +328,8 @@ def dims_idxs_to_tensor_idxs(dims, indices): def to_tensor_rep(q_oper): """ - Transform a ``Qobj`` to a numpy array whose shape is the flattened dimensions. + Transform a ``Qobj`` to a numpy array whose shape is the flattened + dimensions. ``` ket.dims == [[2, 3], [1]] @@ -439,6 +440,7 @@ def from_list(cls, list_dims, rep=None): class Space(metaclass=MetaSpace): _stored_dims = {} + def __init__(self, dims): idims = int(dims) if idims <= 0 or idims != dims: @@ -510,12 +512,15 @@ def replace(self, idx, new): ``Space([2, 3, 4]).replace(1, 5) == Space([2, 5, 4])`` """ if idx != 0: - raise ValueError("Cannot replace a non-zero index in a flat space.") + raise ValueError( + "Cannot replace a non-zero index in a flat space." + ) return Space(new) class Field(Space): field_instance = None + def __init__(self): self.size = 1 self.issuper = False @@ -554,6 +559,7 @@ def replace(self, idx, new): class Compound(Space): _stored_dims = {} + def __init__(self, *spaces): self.spaces = [] for space in spaces: @@ -623,7 +629,7 @@ def remove(self, idx): new_spaces = [] for space in self.spaces: n_indices = len(space.flat()) - idx_space = [i for i in idx if i= n_indices] new_space = space.remove(idx_space) if new_spaces: @@ -644,6 +650,7 @@ def replace(self, idx, new): class SuperSpace(Space): _stored_dims = {} + def __init__(self, oper, rep='super'): self.oper = oper self.superrep = rep diff --git a/qutip/core/energy_restricted.py b/qutip/core/energy_restricted.py index 20f3a0d6d6..3c6405c349 100644 --- a/qutip/core/energy_restricted.py +++ b/qutip/core/energy_restricted.py @@ -119,8 +119,11 @@ def enr_fock(dims, excitations, state, *, dtype=None): dtype = dtype or settings.core["default_dtype"] or _data.Dense nstates, state2idx, _ = enr_state_dictionaries(dims, excitations) try: - data =_data.one_element[dtype]((nstates, 1), - (state2idx[tuple(state)], 0), 1) + data = _data.one_element[dtype]( + (nstates, 1), + (state2idx[tuple(state)], 0), + 1 + ) except KeyError: msg = ( "state tuple " + str(tuple(state)) diff --git a/qutip/core/operators.py b/qutip/core/operators.py index 363e4db76e..d1cbb2dcce 100644 --- a/qutip/core/operators.py +++ b/qutip/core/operators.py @@ -3,13 +3,14 @@ of commonly occuring quantum operators. """ -__all__ = ['jmat', 'spin_Jx', 'spin_Jy', 'spin_Jz', 'spin_Jm', 'spin_Jp', - 'spin_J_set', 'sigmap', 'sigmam', 'sigmax', 'sigmay', 'sigmaz', - 'destroy', 'create', 'fdestroy', 'fcreate', 'qeye', 'identity', - 'position', 'momentum', 'num', 'squeeze', 'squeezing', 'displace', - 'commutator', 'qutrit_ops', 'qdiags', 'phase', 'qzero', 'charge', - 'tunneling', 'qft', 'qzero_like', 'qeye_like', 'swap', - ] +__all__ = [ + 'jmat', 'spin_Jx', 'spin_Jy', 'spin_Jz', 'spin_Jm', 'spin_Jp', + 'spin_J_set', 'sigmap', 'sigmam', 'sigmax', 'sigmay', 'sigmaz', + 'destroy', 'create', 'fdestroy', 'fcreate', 'qeye', 'identity', + 'position', 'momentum', 'num', 'squeeze', 'squeezing', 'displace', + 'commutator', 'qutrit_ops', 'qdiags', 'phase', 'qzero', 'charge', + 'tunneling', 'qft', 'qzero_like', 'qeye_like', 'swap', +] import numbers diff --git a/qutip/core/qobj.py b/qutip/core/qobj.py index e0a269ad17..73429d9e4c 100644 --- a/qutip/core/qobj.py +++ b/qutip/core/qobj.py @@ -82,7 +82,7 @@ def _require_equal_type(method): @functools.wraps(method) def out(self, other): if other == 0: - return method(self, other) + return self.copy() if ( self.type in ('oper', 'super') and self._dims[0] == self._dims[1] @@ -434,8 +434,6 @@ def to(self, data_type): @_require_equal_type def __add__(self, other): - if other == 0: - return self.copy() isherm = (self._isherm and other._isherm) or None return Qobj(_data.add(self._data, other._data), dims=self._dims, @@ -448,8 +446,6 @@ def __radd__(self, other): @_require_equal_type def __sub__(self, other): - if other == 0: - return self.copy() isherm = (self._isherm and other._isherm) or None return Qobj(_data.sub(self._data, other._data), dims=self._dims, From 028d9a8c44cd1ae6e827855eee4ed40816466282 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 28 Aug 2023 15:07:31 -0400 Subject: [PATCH 022/247] Add towncrier --- doc/changes/2210.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/2210.feature diff --git a/doc/changes/2210.feature b/doc/changes/2210.feature new file mode 100644 index 0000000000..99dba0c718 --- /dev/null +++ b/doc/changes/2210.feature @@ -0,0 +1 @@ +Restore feedback to solvers From c3a07c7406964fa98642cda5e51ba360152797a0 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 28 Aug 2023 16:44:32 -0400 Subject: [PATCH 023/247] sode.stepper set in only one place --- qutip/solver/sode/rouchon.py | 1 - qutip/solver/sode/sode.py | 38 ++++++------------------------------ 2 files changed, 6 insertions(+), 33 deletions(-) diff --git a/qutip/solver/sode/rouchon.py b/qutip/solver/sode/rouchon.py index f485996541..49e9538fea 100644 --- a/qutip/solver/sode/rouchon.py +++ b/qutip/solver/sode/rouchon.py @@ -36,7 +36,6 @@ def __init__(self, rhs, options): self._options = self.integrator_options.copy() self.options = options self.rhs = rhs - self.system = None self._make_operators() def _make_operators(self): diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index e35e3eeb88..aef1dae565 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -45,6 +45,7 @@ class SIntegrator(Integrator): included here will be supported by the :cls:SolverOdeOptions. """ _is_set = False + _stepper_options = [] def set_state(self, t, state0, generator): """ @@ -66,17 +67,14 @@ def set_state(self, t, state0, generator): if isinstance(generator, Wiener): self.wiener = generator else: - if self.system: - num_collapse = self.system.num_collapse - else: - num_collapse = len(self.rhs.sc_ops) + num_collapse = len(self.rhs.sc_ops) self.wiener = Wiener( t, self.options["dt"], generator, (self.N_dw, num_collapse) ) self.rhs.register_feedback("wiener_process", self.wiener) - self.system = self.rhs(self.options) - self.step_func = self.stepper(self.system).run + opt = [self.options[key] for key in self._stepper_options] + self.step_func = self.stepper(self.rhs(self.options), *opt).run self._is_set = True def get_state(self, copy=True): @@ -110,10 +108,6 @@ def mcstep(self, t, copy=True): def reset(self, hard=False): if self._is_set: state = self.get_state() - if hard: - self.system = self.rhs(self.options) - self.step_func = self.stepper(self.system).run - if self._is_set: self.set_state(*state) @@ -133,8 +127,6 @@ def __init__(self, rhs, options): self._options = self.integrator_options.copy() self.options = options self.rhs = rhs - self.system = None - self.step_func = self.stepper(self.system).run def integrate(self, t, copy=True): delta_t = t - self.t @@ -185,20 +177,10 @@ class _Implicit_Simple_Integrator(_Explicit_Simple_Integrator): "solve_method": None, "solve_options": {}, } + _stepper_options = ["solve_method", "solve_options"] stepper = None N_dw = 0 - def __init__(self, rhs, options): - self._options = self.integrator_options.copy() - self.options = options - self.rhs = rhs - self.system = rhs(self.options) - self.step_func = self.stepper( - self.system, - self.options["solve_method"], - self.options["solve_options"], - ).run - @property def options(self): """ @@ -265,15 +247,7 @@ class PredCorr_SODE(_Explicit_Simple_Integrator): } stepper = _sode.PredCorr N_dw = 1 - - def __init__(self, rhs, options): - self._options = self.integrator_options.copy() - self.options = options - self.rhs = rhs - self.system = rhs(self.options) - self.step_func = self.stepper( - self.system, self.options["alpha"], self.options["eta"] - ).run + _stepper_options = ["alpha", "eta"] @property def options(self): From dd9a435d3648ce205dd42d7e7f248d004a96dd0b Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Fri, 29 Sep 2023 13:30:11 +0900 Subject: [PATCH 024/247] Test that nm_mcsolve works with improved sampling. --- qutip/tests/solver/test_nm_mcsolve.py | 74 ++++++++++++++++++--------- 1 file changed, 49 insertions(+), 25 deletions(-) diff --git a/qutip/tests/solver/test_nm_mcsolve.py b/qutip/tests/solver/test_nm_mcsolve.py index e47b638e4b..c7775e514e 100644 --- a/qutip/tests/solver/test_nm_mcsolve.py +++ b/qutip/tests/solver/test_nm_mcsolve.py @@ -7,7 +7,8 @@ from qutip.solver.nm_mcsolve import nm_mcsolve, NonMarkovianMCSolver -def test_agreement_with_mesolve_for_negative_rates(): +@pytest.mark.parametrize("improved_sampling", [True, False]) +def test_agreement_with_mesolve_for_negative_rates(improved_sampling): """ A rough test that nm_mcsolve agress with mesolve in the presence of negative rates. @@ -39,7 +40,7 @@ def test_agreement_with_mesolve_for_negative_rates(): mc_result = nm_mcsolve( H, psi0, times, ops_and_rates, args=args, e_ops=e_ops, ntraj=2000, - options={"rtol": 1e-8}, + options={"rtol": 1e-8, "improved_sampling": improved_sampling}, seeds=0, ) @@ -173,10 +174,13 @@ def _assert_expect(self, result, expected, tol): for test, expected_part in zip(result.expect, expected): np.testing.assert_allclose(test, expected_part, rtol=tol) + @pytest.mark.parametrize("improved_sampling", [True, False]) def test_states_and_expect( - self, hamiltonian, args, ops_and_rates, expected, tol + self, hamiltonian, args, ops_and_rates, expected, tol, + improved_sampling ): - options = {"store_states": True, "map": "serial"} + options = {"store_states": True, "map": "serial", + "improved_sampling": improved_sampling} result = nm_mcsolve( hamiltonian, self.state, self.times, args=args, ops_and_rates=ops_and_rates, @@ -221,10 +225,13 @@ def pytest_generate_tests(self, metafunc): # runtimes shorter. The known-good cases are still tested in the other # test cases, this is just testing the single-output behaviour. + @pytest.mark.parametrize("improved_sampling", [True, False]) def test_states_only( - self, hamiltonian, args, ops_and_rates, expected, tol + self, hamiltonian, args, ops_and_rates, expected, tol, + improved_sampling ): - options = {"store_states": True, "map": "serial"} + options = {"store_states": True, "map": "serial", + "improved_sampling": improved_sampling} result = nm_mcsolve( hamiltonian, self.state, self.times, args=args, ops_and_rates=ops_and_rates, @@ -232,13 +239,16 @@ def test_states_only( ) self._assert_states(result, expected, tol) + @pytest.mark.parametrize("improved_sampling", [True, False]) def test_expect_only( - self, hamiltonian, args, ops_and_rates, expected, tol + self, hamiltonian, args, ops_and_rates, expected, tol, + improved_sampling ): + options = {"map": "serial", "improved_sampling": improved_sampling} result = nm_mcsolve( hamiltonian, self.state, self.times, args=args, ops_and_rates=ops_and_rates, - e_ops=self.e_ops, ntraj=self.ntraj, options={'map': 'serial'}, + e_ops=self.e_ops, ntraj=self.ntraj, options=options, ) self._assert_expect(result, expected, tol) @@ -392,8 +402,9 @@ def test_states_outputs(keep_runs_results): assert data.stats['end_condition'] == "ntraj reached" +@pytest.mark.parametrize("improved_sampling", [True, False]) @pytest.mark.parametrize('keep_runs_results', [True, False]) -def test_expectation_outputs(keep_runs_results): +def test_expectation_outputs(keep_runs_results, improved_sampling): # We're just testing the output value, so it's important whether certain # things are complex or real, but not what the magnitudes of constants are. focks = 5 @@ -417,8 +428,7 @@ def test_expectation_outputs(keep_runs_results): options={ "keep_runs_results": keep_runs_results, "map": "serial", - }, - ) + "improved_sampling": improved_sampling}) assert isinstance(data.average_expect[0][1], float) assert isinstance(data.average_expect[1][1], float) assert isinstance(data.average_expect[2][1], complex) @@ -463,9 +473,11 @@ class TestSeeds: (qutip.tensor(qutip.qeye(sizes[:2]), a[2]), 2 * dampings[2]), ] - def test_seeds_can_be_reused(self): + @pytest.mark.parametrize("improved_sampling", [True, False]) + def test_seeds_can_be_reused(self, improved_sampling): args = (self.H, self.state, self.times) - kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj} + kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj, + 'options': {"improved_sampling": improved_sampling}} first = nm_mcsolve(*args, **kwargs) second = nm_mcsolve(*args, seeds=first.seeds, **kwargs) for first_t, second_t in zip(first.col_times, second.col_times): @@ -473,9 +485,11 @@ def test_seeds_can_be_reused(self): for first_w, second_w in zip(first.col_which, second.col_which): np.testing.assert_equal(first_w, second_w) - def test_seeds_are_not_reused_by_default(self): + @pytest.mark.parametrize("improved_sampling", [True, False]) + def test_seeds_are_not_reused_by_default(self, improved_sampling): args = (self.H, self.state, self.times) - kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj} + kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj, + 'options': {"improved_sampling": improved_sampling}} first = nm_mcsolve(*args, **kwargs) second = nm_mcsolve(*args, **kwargs) assert not all(np.array_equal(first_t, second_t) @@ -485,26 +499,32 @@ def test_seeds_are_not_reused_by_default(self): for first_w, second_w in zip(first.col_which, second.col_which)) + @pytest.mark.parametrize("improved_sampling", [True, False]) @pytest.mark.parametrize('seed', [1, np.random.SeedSequence(2)]) - def test_seed_type(self, seed): + def test_seed_type(self, seed, improved_sampling): args = (self.H, self.state, self.times) - kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj} + kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj, + 'options': {"improved_sampling": improved_sampling}} first = nm_mcsolve(*args, seeds=copy(seed), **kwargs) second = nm_mcsolve(*args, seeds=copy(seed), **kwargs) for f_seed, s_seed in zip(first.seeds, second.seeds): assert f_seed.state == s_seed.state - def test_bad_seed(self): + @pytest.mark.parametrize("improved_sampling", [True, False]) + def test_bad_seed(self, improved_sampling): args = (self.H, self.state, self.times) - kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj} + kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj, + 'options': {"improved_sampling": improved_sampling}} with pytest.raises(ValueError): nm_mcsolve(*args, seeds=[1], **kwargs) - def test_generator(self): + @pytest.mark.parametrize("improved_sampling", [True, False]) + def test_generator(self, improved_sampling): args = (self.H, self.state, self.times) kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj} first = nm_mcsolve( - *args, seeds=1, options={'bitgenerator': 'MT19937'}, + *args, seeds=1, options={'bitgenerator': 'MT19937', + 'improved_sampling': improved_sampling}, **kwargs, ) second = nm_mcsolve(*args, seeds=1, **kwargs) @@ -533,7 +553,8 @@ def test_stepping(self): assert state_1 == state_2 -def test_timeout(): +@pytest.mark.parametrize("improved_sampling", [True, False]) +def test_timeout(improved_sampling): size = 10 ntraj = 1000 a = qutip.destroy(size) @@ -548,12 +569,14 @@ def test_timeout(): e_ops = [qutip.num(size)] res = nm_mcsolve( H, state, times, ops_and_rates, e_ops, ntraj=ntraj, - options={'map': 'serial'}, timeout=1e-6, + options={'map': 'serial', + 'improved_sampling': improved_sampling}, timeout=1e-6, ) assert res.stats['end_condition'] == 'timeout' -def test_super_H(): +@pytest.mark.parametrize("improved_sampling", [True, False]) +def test_super_H(improved_sampling): size = 10 ntraj = 1000 a = qutip.destroy(size) @@ -573,7 +596,8 @@ def test_super_H(): ) mc = nm_mcsolve( qutip.liouvillian(H), state, times, ops_and_rates, e_ops, ntraj=ntraj, - target_tol=0.1, options={'map': 'serial'}, + target_tol=0.1, options={'map': 'serial', + 'improved_sampling': improved_sampling}, ) np.testing.assert_allclose(mc_expected.expect[0], mc.expect[0], atol=0.5) From 9aef23eb938c1027985cd913b543591db179fb27 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Fri, 29 Sep 2023 13:43:35 +0900 Subject: [PATCH 025/247] Deactivated improved sampling for nm_mcsolve. --- qutip/solver/nm_mcsolve.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index f97298b390..75deccace1 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -2,6 +2,7 @@ import functools import numbers +import warnings import numpy as np import scipy @@ -339,6 +340,13 @@ def __init__( ): self.options = options + if self.options["improved_sampling"]: + warnings.warn( + "NonMarkovianMCSolver currently does not support 'improved_sampling'. " + "Improved sampling has been deactivated.") + self.options["improved_sampling"] = False + options.update({"improved_sampling": False}) + ops_and_rates = [ _parse_op_and_rate(op, rate, args=args or {}) for op, rate in ops_and_rates From 3bc0a239c5451ab0c3b361daa9fa538f05f23ce2 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Fri, 29 Sep 2023 14:01:43 +0900 Subject: [PATCH 026/247] Added changelog --- doc/changes/2234.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/2234.bugfix diff --git a/doc/changes/2234.bugfix b/doc/changes/2234.bugfix new file mode 100644 index 0000000000..f9e0bb5ffe --- /dev/null +++ b/doc/changes/2234.bugfix @@ -0,0 +1 @@ +Disabled broken "improved sampling" for `nm_mcsolve`. \ No newline at end of file From b56b6db4f6633f8d7bf0e0517e7a9111f7199e9c Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Fri, 29 Sep 2023 14:49:04 +0900 Subject: [PATCH 027/247] Formatting --- qutip/solver/nm_mcsolve.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index 75deccace1..14950bc783 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -342,8 +342,8 @@ def __init__( if self.options["improved_sampling"]: warnings.warn( - "NonMarkovianMCSolver currently does not support 'improved_sampling'. " - "Improved sampling has been deactivated.") + "NonMarkovianMCSolver currently does not support " + "'improved_sampling'. Improved sampling has been deactivated.") self.options["improved_sampling"] = False options.update({"improved_sampling": False}) From 97904b8d2fd4ae5df804d890e20ee6b2f59d89b5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 03:01:47 +0000 Subject: [PATCH 028/247] Bump urllib3 from 1.26.14 to 1.26.17 in /doc Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.14 to 1.26.17. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.26.14...1.26.17) --- updated-dependencies: - dependency-name: urllib3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 68e490ddb4..bee76af5ec 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -43,6 +43,6 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 traitlets==5.9.0 -urllib3==1.26.14 +urllib3==1.26.17 wcwidth==0.2.6 wheel==0.38.4 From c8c0ed4d5bb4ee66011fc9b552517d72b6e9a874 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 4 Oct 2023 01:19:48 +0000 Subject: [PATCH 029/247] Bump pillow from 9.4.0 to 10.0.1 in /doc Bumps [pillow](https://github.com/python-pillow/Pillow) from 9.4.0 to 10.0.1. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/9.4.0...10.0.1) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 68e490ddb4..9cfe2c74dc 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -21,7 +21,7 @@ packaging==23.0 parso==0.8.3 pexpect==4.8.0 pickleshare==0.7.5 -Pillow==9.4.0 +Pillow==10.0.1 prompt-toolkit==3.0.38 ptyprocess==0.7.0 Pygments==2.15.0 From f4d0e7ea093240649bb33b4f504e6f2db3a0f35a Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 4 Oct 2023 16:14:11 -0400 Subject: [PATCH 030/247] Fix for matplotlib 3.8 --- qutip/bloch.py | 1 + qutip/tests/test_bloch.py | 1 + qutip/tests/test_visualization.py | 13 +++++++++++++ qutip/visualization.py | 7 +++++-- 4 files changed, 20 insertions(+), 2 deletions(-) diff --git a/qutip/bloch.py b/qutip/bloch.py index 980502dd9d..05718de4e6 100644 --- a/qutip/bloch.py +++ b/qutip/bloch.py @@ -809,6 +809,7 @@ def plot_points(self): length = np.ceil(num_points/len(self.point_default_color)) color = np.tile(self.point_default_color, length.astype(int)) color = color[indperm] + color = list(color) if self.point_style[k] in ['s', 'm']: self.axes.scatter(np.real(points[1]), diff --git a/qutip/tests/test_bloch.py b/qutip/tests/test_bloch.py index 33e8fb2fae..f503302606 100644 --- a/qutip/tests/test_bloch.py +++ b/qutip/tests/test_bloch.py @@ -213,6 +213,7 @@ def plot_point_ref(self, fig, point_kws): np.ceil(points.shape[1]/len(point_colors) ).astype(int)) colors = colors[:points.shape[1]] + colors = list(colors) point_size = point_sizes[idx % len(point_sizes)] point_marker = point_markers[idx % len(point_markers)] point_alpha = kw.get("alpha", 1.0) diff --git a/qutip/tests/test_visualization.py b/qutip/tests/test_visualization.py index b4c2a7e365..2d2901e1be 100644 --- a/qutip/tests/test_visualization.py +++ b/qutip/tests/test_visualization.py @@ -85,6 +85,7 @@ def test_set_ticklabels(): with pytest.raises(Exception) as exc_info: fig, ax = qutip.hinton(rho, x_basis=[1]) assert str(exc_info.value) == text + plt.close() def test_equal_shape(): @@ -94,6 +95,7 @@ def test_equal_shape(): with pytest.raises(Exception) as exc_info: fig, ax = qutip.hinton(rhos) assert str(exc_info.value) == text + plt.close() @pytest.mark.parametrize('args', [ @@ -178,6 +180,7 @@ def test_hinton_ValueError0(): with pytest.raises(ValueError) as exc_info: fig, ax = qutip.hinton(rho) assert str(exc_info.value) == text + plt.close() @pytest.mark.parametrize('transform, args, error_message', [ @@ -192,6 +195,7 @@ def test_hinton_ValueError1(transform, args, error_message): with pytest.raises(ValueError) as exc_info: fig, ax = qutip.hinton(rho, **args) assert str(exc_info.value) == error_message + plt.close() @pytest.mark.parametrize('args', [ @@ -241,6 +245,7 @@ def test_update_yaxis(response): y_basis=[1]) assert str(exc_info.value) == text + plt.close() @pytest.mark.parametrize('response', [ @@ -261,6 +266,7 @@ def test_update_xaxis(response): fig, ax = qutip.matrix_histogram(qutip.rand_dm(5), x_basis=[1]) assert str(exc_info.value) == text + plt.close() def test_get_matrix_components(): @@ -346,6 +352,7 @@ def test_matrix_histogram_ValueError(args, expected): fig, ax = qutip.matrix_histogram(qutip.rand_dm(5), **args) assert str(exc_info.value) in expected + plt.close() @pytest.mark.parametrize('args', [ @@ -368,6 +375,7 @@ def test_plot_energy_levels_ValueError(): with pytest.raises(ValueError) as exc_info: fig, ax = qutip.plot_energy_levels(1) assert str(exc_info.value) == "H_list must be a list of Qobj instances" + plt.close() @pytest.mark.parametrize('rho_type, args', [ @@ -440,6 +448,7 @@ def test_plot_wigner_ValueError(): fig, ax = qutip.plot_wigner(rho, projection=1) assert str(exc_info.value) == text + plt.close() @pytest.mark.parametrize('n_of_results, n_of_e_ops, one_axes, args', [ @@ -532,6 +541,7 @@ def test_plot_spin_distribution_ValueError(): with pytest.raises(ValueError) as exc_info: fig, ax = qutip.plot_spin_distribution(Q, THETA, PHI, projection=1) assert str(exc_info.value) == text + plt.close() @pytest.mark.parametrize('args', [ @@ -595,6 +605,7 @@ def test_plot_qubism_Error(ket, args, expected): with pytest.raises(Exception) as exc_info: fig, ax = qutip.plot_qubism(state, **args) assert str(exc_info.value) == expected + plt.close() def test_plot_qubism_dimension(): @@ -605,6 +616,7 @@ def test_plot_qubism_dimension(): with pytest.raises(Exception) as exc_info: qutip.plot_qubism(ket, how='pairs_skewed') assert str(exc_info.value) == text + plt.close() @pytest.mark.parametrize('args', [ @@ -638,3 +650,4 @@ def test_plot_schmidt_Error(): with pytest.raises(Exception) as exc_info: fig, ax = qutip.plot_schmidt(state) assert str(exc_info.value) == text + plt.close() diff --git a/qutip/visualization.py b/qutip/visualization.py index 32cb6e401c..36f08cad65 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -1209,8 +1209,11 @@ def plot_wigner(rho, xvec=None, yvec=None, method='clenshaw', artist_list = list() for W in Ws: if projection == '2d': - cf = ax.contourf(xvec, yvec, W, 100, norm=norm, - cmap=cmap).collections + if parse_version(mpl.__version__) >= parse_version('3.8'): + cf = [ax.contourf(xvec, yvec, W, 100, norm=norm, cmap=cmap)] + else: + cf = ax.contourf(xvec, yvec, W, 100, norm=norm, + cmap=cmap).collections else: X, Y = np.meshgrid(xvec, yvec) cf = [ax.plot_surface(X, Y, W, rstride=5, cstride=5, linewidth=0.5, From ea6f9d080043611bd461b88d605b911e992495de Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 5 Oct 2023 09:43:36 -0400 Subject: [PATCH 031/247] use scipy svd everywhere --- qutip/tests/core/data/test_linalg.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/tests/core/data/test_linalg.py b/qutip/tests/core/data/test_linalg.py index 56a1bb2b6b..b257ea0e00 100644 --- a/qutip/tests/core/data/test_linalg.py +++ b/qutip/tests/core/data/test_linalg.py @@ -99,7 +99,7 @@ def test_incorrect_shape_mismatch(self): class TestSVD(): def op_numpy(self, A): - return np.linalg.svd(A) + return scipy.linalg.svd(A) def _gen_dm(self, N, rank, dtype): return qutip.rand_dm(N, rank=rank, dtype=dtype).data From fafee92252f129d3ccfd3afbcad2ab2f9844e241 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Fri, 6 Oct 2023 13:51:22 +0900 Subject: [PATCH 032/247] Removed improved_sampling from nmmcsolver options --- qutip/solver/nm_mcsolve.py | 9 +--- qutip/tests/solver/test_nm_mcsolve.py | 74 +++++++++------------------ 2 files changed, 26 insertions(+), 57 deletions(-) diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index 14950bc783..eae04dc41a 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -2,7 +2,6 @@ import functools import numbers -import warnings import numpy as np import scipy @@ -330,6 +329,7 @@ class NonMarkovianMCSolver(MCSolver): "completeness_atol": 1e-8, "martingale_quad_limit": 100, } + del solver_options["improved_sampling"] # both classes will be partially initialized in constructor trajectory_resultclass = NmmcTrajectoryResult @@ -340,13 +340,6 @@ def __init__( ): self.options = options - if self.options["improved_sampling"]: - warnings.warn( - "NonMarkovianMCSolver currently does not support " - "'improved_sampling'. Improved sampling has been deactivated.") - self.options["improved_sampling"] = False - options.update({"improved_sampling": False}) - ops_and_rates = [ _parse_op_and_rate(op, rate, args=args or {}) for op, rate in ops_and_rates diff --git a/qutip/tests/solver/test_nm_mcsolve.py b/qutip/tests/solver/test_nm_mcsolve.py index c7775e514e..e47b638e4b 100644 --- a/qutip/tests/solver/test_nm_mcsolve.py +++ b/qutip/tests/solver/test_nm_mcsolve.py @@ -7,8 +7,7 @@ from qutip.solver.nm_mcsolve import nm_mcsolve, NonMarkovianMCSolver -@pytest.mark.parametrize("improved_sampling", [True, False]) -def test_agreement_with_mesolve_for_negative_rates(improved_sampling): +def test_agreement_with_mesolve_for_negative_rates(): """ A rough test that nm_mcsolve agress with mesolve in the presence of negative rates. @@ -40,7 +39,7 @@ def test_agreement_with_mesolve_for_negative_rates(improved_sampling): mc_result = nm_mcsolve( H, psi0, times, ops_and_rates, args=args, e_ops=e_ops, ntraj=2000, - options={"rtol": 1e-8, "improved_sampling": improved_sampling}, + options={"rtol": 1e-8}, seeds=0, ) @@ -174,13 +173,10 @@ def _assert_expect(self, result, expected, tol): for test, expected_part in zip(result.expect, expected): np.testing.assert_allclose(test, expected_part, rtol=tol) - @pytest.mark.parametrize("improved_sampling", [True, False]) def test_states_and_expect( - self, hamiltonian, args, ops_and_rates, expected, tol, - improved_sampling + self, hamiltonian, args, ops_and_rates, expected, tol ): - options = {"store_states": True, "map": "serial", - "improved_sampling": improved_sampling} + options = {"store_states": True, "map": "serial"} result = nm_mcsolve( hamiltonian, self.state, self.times, args=args, ops_and_rates=ops_and_rates, @@ -225,13 +221,10 @@ def pytest_generate_tests(self, metafunc): # runtimes shorter. The known-good cases are still tested in the other # test cases, this is just testing the single-output behaviour. - @pytest.mark.parametrize("improved_sampling", [True, False]) def test_states_only( - self, hamiltonian, args, ops_and_rates, expected, tol, - improved_sampling + self, hamiltonian, args, ops_and_rates, expected, tol ): - options = {"store_states": True, "map": "serial", - "improved_sampling": improved_sampling} + options = {"store_states": True, "map": "serial"} result = nm_mcsolve( hamiltonian, self.state, self.times, args=args, ops_and_rates=ops_and_rates, @@ -239,16 +232,13 @@ def test_states_only( ) self._assert_states(result, expected, tol) - @pytest.mark.parametrize("improved_sampling", [True, False]) def test_expect_only( - self, hamiltonian, args, ops_and_rates, expected, tol, - improved_sampling + self, hamiltonian, args, ops_and_rates, expected, tol ): - options = {"map": "serial", "improved_sampling": improved_sampling} result = nm_mcsolve( hamiltonian, self.state, self.times, args=args, ops_and_rates=ops_and_rates, - e_ops=self.e_ops, ntraj=self.ntraj, options=options, + e_ops=self.e_ops, ntraj=self.ntraj, options={'map': 'serial'}, ) self._assert_expect(result, expected, tol) @@ -402,9 +392,8 @@ def test_states_outputs(keep_runs_results): assert data.stats['end_condition'] == "ntraj reached" -@pytest.mark.parametrize("improved_sampling", [True, False]) @pytest.mark.parametrize('keep_runs_results', [True, False]) -def test_expectation_outputs(keep_runs_results, improved_sampling): +def test_expectation_outputs(keep_runs_results): # We're just testing the output value, so it's important whether certain # things are complex or real, but not what the magnitudes of constants are. focks = 5 @@ -428,7 +417,8 @@ def test_expectation_outputs(keep_runs_results, improved_sampling): options={ "keep_runs_results": keep_runs_results, "map": "serial", - "improved_sampling": improved_sampling}) + }, + ) assert isinstance(data.average_expect[0][1], float) assert isinstance(data.average_expect[1][1], float) assert isinstance(data.average_expect[2][1], complex) @@ -473,11 +463,9 @@ class TestSeeds: (qutip.tensor(qutip.qeye(sizes[:2]), a[2]), 2 * dampings[2]), ] - @pytest.mark.parametrize("improved_sampling", [True, False]) - def test_seeds_can_be_reused(self, improved_sampling): + def test_seeds_can_be_reused(self): args = (self.H, self.state, self.times) - kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj, - 'options': {"improved_sampling": improved_sampling}} + kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj} first = nm_mcsolve(*args, **kwargs) second = nm_mcsolve(*args, seeds=first.seeds, **kwargs) for first_t, second_t in zip(first.col_times, second.col_times): @@ -485,11 +473,9 @@ def test_seeds_can_be_reused(self, improved_sampling): for first_w, second_w in zip(first.col_which, second.col_which): np.testing.assert_equal(first_w, second_w) - @pytest.mark.parametrize("improved_sampling", [True, False]) - def test_seeds_are_not_reused_by_default(self, improved_sampling): + def test_seeds_are_not_reused_by_default(self): args = (self.H, self.state, self.times) - kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj, - 'options': {"improved_sampling": improved_sampling}} + kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj} first = nm_mcsolve(*args, **kwargs) second = nm_mcsolve(*args, **kwargs) assert not all(np.array_equal(first_t, second_t) @@ -499,32 +485,26 @@ def test_seeds_are_not_reused_by_default(self, improved_sampling): for first_w, second_w in zip(first.col_which, second.col_which)) - @pytest.mark.parametrize("improved_sampling", [True, False]) @pytest.mark.parametrize('seed', [1, np.random.SeedSequence(2)]) - def test_seed_type(self, seed, improved_sampling): + def test_seed_type(self, seed): args = (self.H, self.state, self.times) - kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj, - 'options': {"improved_sampling": improved_sampling}} + kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj} first = nm_mcsolve(*args, seeds=copy(seed), **kwargs) second = nm_mcsolve(*args, seeds=copy(seed), **kwargs) for f_seed, s_seed in zip(first.seeds, second.seeds): assert f_seed.state == s_seed.state - @pytest.mark.parametrize("improved_sampling", [True, False]) - def test_bad_seed(self, improved_sampling): + def test_bad_seed(self): args = (self.H, self.state, self.times) - kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj, - 'options': {"improved_sampling": improved_sampling}} + kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj} with pytest.raises(ValueError): nm_mcsolve(*args, seeds=[1], **kwargs) - @pytest.mark.parametrize("improved_sampling", [True, False]) - def test_generator(self, improved_sampling): + def test_generator(self): args = (self.H, self.state, self.times) kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj} first = nm_mcsolve( - *args, seeds=1, options={'bitgenerator': 'MT19937', - 'improved_sampling': improved_sampling}, + *args, seeds=1, options={'bitgenerator': 'MT19937'}, **kwargs, ) second = nm_mcsolve(*args, seeds=1, **kwargs) @@ -553,8 +533,7 @@ def test_stepping(self): assert state_1 == state_2 -@pytest.mark.parametrize("improved_sampling", [True, False]) -def test_timeout(improved_sampling): +def test_timeout(): size = 10 ntraj = 1000 a = qutip.destroy(size) @@ -569,14 +548,12 @@ def test_timeout(improved_sampling): e_ops = [qutip.num(size)] res = nm_mcsolve( H, state, times, ops_and_rates, e_ops, ntraj=ntraj, - options={'map': 'serial', - 'improved_sampling': improved_sampling}, timeout=1e-6, + options={'map': 'serial'}, timeout=1e-6, ) assert res.stats['end_condition'] == 'timeout' -@pytest.mark.parametrize("improved_sampling", [True, False]) -def test_super_H(improved_sampling): +def test_super_H(): size = 10 ntraj = 1000 a = qutip.destroy(size) @@ -596,8 +573,7 @@ def test_super_H(improved_sampling): ) mc = nm_mcsolve( qutip.liouvillian(H), state, times, ops_and_rates, e_ops, ntraj=ntraj, - target_tol=0.1, options={'map': 'serial', - 'improved_sampling': improved_sampling}, + target_tol=0.1, options={'map': 'serial'}, ) np.testing.assert_allclose(mc_expected.expect[0], mc.expect[0], atol=0.5) From 13bceaf937eccb21cb9a601d6c4c62190f67ac4e Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Fri, 6 Oct 2023 14:20:31 +0900 Subject: [PATCH 033/247] Override options property in nmmc solver to add docstring --- qutip/solver/nm_mcsolve.py | 97 +++++++++++++++++++++++++++++++++++--- 1 file changed, 90 insertions(+), 7 deletions(-) diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index 14950bc783..5de674a46a 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -125,6 +125,8 @@ def nm_mcsolve(H, state, tlist, ops_and_rates=(), e_ops=None, ntraj=500, *, - martingale_quad_limit : float or int, [100] An upper bound on the number of subintervals used in the adaptive integration of the martingale. + + Note that the 'improved_sampling' option is not currently supported. seeds : int, SeedSequence, list, [optional] Seed for the random number generator. It can be a single seed used to @@ -340,13 +342,6 @@ def __init__( ): self.options = options - if self.options["improved_sampling"]: - warnings.warn( - "NonMarkovianMCSolver currently does not support " - "'improved_sampling'. Improved sampling has been deactivated.") - self.options["improved_sampling"] = False - options.update({"improved_sampling": False}) - ops_and_rates = [ _parse_op_and_rate(op, rate, args=args or {}) for op, rate in ops_and_rates @@ -524,6 +519,94 @@ def run(self, state, tlist, *args, **kwargs): return result + @property + def options(self): + """ + Options for non-Markovian Monte Carlo solver: + + store_final_state: bool, default=False + Whether or not to store the final state of the evolution in the + result class. + + store_states: bool, default=None + Whether or not to store the state vectors or density matrices. + On `None` the states will be saved if no expectation operators are + given. + + progress_bar: str {'text', 'enhanced', 'tqdm', ''}, default="text" + How to present the solver progress. + 'tqdm' uses the python module of the same name and raise an error if + not installed. Empty string or False will disable the bar. + + progress_kwargs: dict, default={"chunk_size":10} + Arguments to pass to the progress_bar. Qutip's bars use + ``chunk_size``. + + keep_runs_results: bool + Whether to store results from all trajectories or just store the + averages. + + method: str, default="adams" + Which ODE integrator methods are supported. + + map: str {"serial", "parallel", "loky"} + How to run the trajectories. "parallel" uses concurent module to + run in parallel while "loky" use the module of the same name to do + so. + + job_timeout: None, int + Maximum time to compute one trajectory. + + num_cpus: None, int + Number of cpus to use when running in parallel. ``None`` detect the + number of available cpus. + + bitgenerator: {None, "MT19937", "PCG64", "PCG64DXSM", ...} + Which of numpy.random's bitgenerator to use. With ``None``, your + numpy version's default is used. + + mc_corr_eps: float + Small number used to detect non-physical collapse caused by + numerical imprecision. + + norm_t_tol: float + Tolerance in time used when finding the collapse. + + norm_tol: float + Tolerance in norm used when finding the collapse. + + norm_steps: int + Maximum number of tries to find the collapse. + + completeness_rtol: float, default=1e-5 + Used in determining whether the given Lindblad operators satisfy + a certain completeness relation. If they do not, an additional + Lindblad operator is added automatically (with zero rate). + + completeness_atol: float, default=1e-8 + Used in determining whether the given Lindblad operators satisfy + a certain completeness relation. If they do not, an additional + Lindblad operator is added automatically (with zero rate). + + martingale_quad_limit: float or int, default=100 + An upper bound on the number of subintervals used in the adaptive + integration of the martingale. + + Note that the 'improved_sampling' option is not currently supported. + """ + return self._options + + @options.setter + def options(self, new_options): + if (new_options is not None and + "improved_sampling" in new_options and + new_options["improved_sampling"]): + warnings.warn( + "NonMarkovianMCSolver currently does not support " + "'improved_sampling'. Improved sampling has been deactivated.") + new_options["improved_sampling"] = False + MCSolver.options.fset(self, new_options) + start.__doc__ = MultiTrajSolver.start.__doc__ step.__doc__ = MultiTrajSolver.step.__doc__ run.__doc__ = MultiTrajSolver.run.__doc__ From 2eec9bdb73b9f54dcee32cb37b1d03ef2c672711 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Fri, 6 Oct 2023 17:59:37 +0900 Subject: [PATCH 034/247] Marked tests with improved sampling as xfail --- qutip/tests/solver/test_nm_mcsolve.py | 28 +++++++++++++++------------ 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/qutip/tests/solver/test_nm_mcsolve.py b/qutip/tests/solver/test_nm_mcsolve.py index c7775e514e..3e2d0e823b 100644 --- a/qutip/tests/solver/test_nm_mcsolve.py +++ b/qutip/tests/solver/test_nm_mcsolve.py @@ -7,7 +7,11 @@ from qutip.solver.nm_mcsolve import nm_mcsolve, NonMarkovianMCSolver -@pytest.mark.parametrize("improved_sampling", [True, False]) +parametrize_for_improved_sampling = pytest.mark.parametrize( + "improved_sampling", [pytest.param(True, marks=pytest.mark.xfail), False]) + + +@parametrize_for_improved_sampling def test_agreement_with_mesolve_for_negative_rates(improved_sampling): """ A rough test that nm_mcsolve agress with mesolve in the @@ -174,7 +178,7 @@ def _assert_expect(self, result, expected, tol): for test, expected_part in zip(result.expect, expected): np.testing.assert_allclose(test, expected_part, rtol=tol) - @pytest.mark.parametrize("improved_sampling", [True, False]) + @parametrize_for_improved_sampling def test_states_and_expect( self, hamiltonian, args, ops_and_rates, expected, tol, improved_sampling @@ -225,7 +229,7 @@ def pytest_generate_tests(self, metafunc): # runtimes shorter. The known-good cases are still tested in the other # test cases, this is just testing the single-output behaviour. - @pytest.mark.parametrize("improved_sampling", [True, False]) + @parametrize_for_improved_sampling def test_states_only( self, hamiltonian, args, ops_and_rates, expected, tol, improved_sampling @@ -239,7 +243,7 @@ def test_states_only( ) self._assert_states(result, expected, tol) - @pytest.mark.parametrize("improved_sampling", [True, False]) + @parametrize_for_improved_sampling def test_expect_only( self, hamiltonian, args, ops_and_rates, expected, tol, improved_sampling @@ -402,7 +406,7 @@ def test_states_outputs(keep_runs_results): assert data.stats['end_condition'] == "ntraj reached" -@pytest.mark.parametrize("improved_sampling", [True, False]) +@parametrize_for_improved_sampling @pytest.mark.parametrize('keep_runs_results', [True, False]) def test_expectation_outputs(keep_runs_results, improved_sampling): # We're just testing the output value, so it's important whether certain @@ -473,7 +477,7 @@ class TestSeeds: (qutip.tensor(qutip.qeye(sizes[:2]), a[2]), 2 * dampings[2]), ] - @pytest.mark.parametrize("improved_sampling", [True, False]) + @parametrize_for_improved_sampling def test_seeds_can_be_reused(self, improved_sampling): args = (self.H, self.state, self.times) kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj, @@ -485,7 +489,7 @@ def test_seeds_can_be_reused(self, improved_sampling): for first_w, second_w in zip(first.col_which, second.col_which): np.testing.assert_equal(first_w, second_w) - @pytest.mark.parametrize("improved_sampling", [True, False]) + @parametrize_for_improved_sampling def test_seeds_are_not_reused_by_default(self, improved_sampling): args = (self.H, self.state, self.times) kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj, @@ -499,7 +503,7 @@ def test_seeds_are_not_reused_by_default(self, improved_sampling): for first_w, second_w in zip(first.col_which, second.col_which)) - @pytest.mark.parametrize("improved_sampling", [True, False]) + @parametrize_for_improved_sampling @pytest.mark.parametrize('seed', [1, np.random.SeedSequence(2)]) def test_seed_type(self, seed, improved_sampling): args = (self.H, self.state, self.times) @@ -510,7 +514,7 @@ def test_seed_type(self, seed, improved_sampling): for f_seed, s_seed in zip(first.seeds, second.seeds): assert f_seed.state == s_seed.state - @pytest.mark.parametrize("improved_sampling", [True, False]) + @parametrize_for_improved_sampling def test_bad_seed(self, improved_sampling): args = (self.H, self.state, self.times) kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj, @@ -518,7 +522,7 @@ def test_bad_seed(self, improved_sampling): with pytest.raises(ValueError): nm_mcsolve(*args, seeds=[1], **kwargs) - @pytest.mark.parametrize("improved_sampling", [True, False]) + @parametrize_for_improved_sampling def test_generator(self, improved_sampling): args = (self.H, self.state, self.times) kwargs = {'ops_and_rates': self.ops_and_rates, 'ntraj': self.ntraj} @@ -553,7 +557,7 @@ def test_stepping(self): assert state_1 == state_2 -@pytest.mark.parametrize("improved_sampling", [True, False]) +@parametrize_for_improved_sampling def test_timeout(improved_sampling): size = 10 ntraj = 1000 @@ -575,7 +579,7 @@ def test_timeout(improved_sampling): assert res.stats['end_condition'] == 'timeout' -@pytest.mark.parametrize("improved_sampling", [True, False]) +@parametrize_for_improved_sampling def test_super_H(improved_sampling): size = 10 ntraj = 1000 From c1f266f5ba01f13dfd9bb5322519309ab11ae906 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Fri, 6 Oct 2023 18:10:16 +0900 Subject: [PATCH 035/247] Formatting --- qutip/solver/nm_mcsolve.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index 5de674a46a..6da1f69312 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -125,7 +125,7 @@ def nm_mcsolve(H, state, tlist, ops_and_rates=(), e_ops=None, ntraj=500, *, - martingale_quad_limit : float or int, [100] An upper bound on the number of subintervals used in the adaptive integration of the martingale. - + Note that the 'improved_sampling' option is not currently supported. seeds : int, SeedSequence, list, [optional] @@ -535,8 +535,8 @@ def options(self): progress_bar: str {'text', 'enhanced', 'tqdm', ''}, default="text" How to present the solver progress. - 'tqdm' uses the python module of the same name and raise an error if - not installed. Empty string or False will disable the bar. + 'tqdm' uses the python module of the same name and raise an error + if not installed. Empty string or False will disable the bar. progress_kwargs: dict, default={"chunk_size":10} Arguments to pass to the progress_bar. Qutip's bars use @@ -591,7 +591,7 @@ def options(self): martingale_quad_limit: float or int, default=100 An upper bound on the number of subintervals used in the adaptive integration of the martingale. - + Note that the 'improved_sampling' option is not currently supported. """ return self._options @@ -600,7 +600,7 @@ def options(self): def options(self, new_options): if (new_options is not None and "improved_sampling" in new_options and - new_options["improved_sampling"]): + new_options["improved_sampling"]): warnings.warn( "NonMarkovianMCSolver currently does not support " "'improved_sampling'. Improved sampling has been deactivated.") From f1481bbc3f9d05da25df2a82c28f3f1b14169b1a Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 6 Oct 2023 13:09:19 -0400 Subject: [PATCH 036/247] Conditional cython requirement --- pyproject.toml | 3 ++- setup.cfg | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index f854fa46f5..2462aab09d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -3,7 +3,8 @@ requires = [ "setuptools", "packaging", "wheel", - "cython>=0.29.20", + "cython>=0.29.20; python_version>='3.10'", + "cython>=0.29.20<3.0.3; python_version<='3.9'", # See https://numpy.org/doc/stable/user/depending_on_numpy.html for # the recommended way to build against numpy's C API: "oldest-supported-numpy", diff --git a/setup.cfg b/setup.cfg index 1eb1f11cd2..53a489c822 100644 --- a/setup.cfg +++ b/setup.cfg @@ -37,7 +37,8 @@ install_requires = setup_requires = numpy>=1.13.3 scipy>=1.0 - cython>=0.29.20 + cython>=0.29.20; python_version>='3.10' + cython>=0.29.20<3.0.3; python_version<='3.9' packaging [options.packages.find] From 1771694fc700a3ae96e83b6ec25617abc468c771 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 6 Oct 2023 13:47:36 -0400 Subject: [PATCH 037/247] List explicit version at install --- .github/workflows/tests.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 096e5cb593..b05bb7555f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -133,7 +133,8 @@ jobs: if [[ -n "${{ matrix.conda-extra-pkgs }}" ]]; then conda install "${{ matrix.conda-extra-pkgs }}" fi - python -m pip install -e .[$QUTIP_TARGET] + conda list + python -m pip install --no-build-isolation -e .[$QUTIP_TARGET] -v python -m pip install "coverage${{ matrix.coverage-requirement }}" python -m pip install pytest-cov coveralls pytest-fail-slow From 528b319cc3d830d14af604c7c5c9c208b4dcd397 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 6 Oct 2023 13:52:19 -0400 Subject: [PATCH 038/247] verbose install --- .github/workflows/tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b05bb7555f..b903ba8091 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -133,8 +133,7 @@ jobs: if [[ -n "${{ matrix.conda-extra-pkgs }}" ]]; then conda install "${{ matrix.conda-extra-pkgs }}" fi - conda list - python -m pip install --no-build-isolation -e .[$QUTIP_TARGET] -v + python -m pip install -e .[$QUTIP_TARGET] -vv python -m pip install "coverage${{ matrix.coverage-requirement }}" python -m pip install pytest-cov coveralls pytest-fail-slow From 4b2a8b9537bdc355e555d8439231ce8046f1d997 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 6 Oct 2023 13:58:42 -0400 Subject: [PATCH 039/247] update requirement formatting --- pyproject.toml | 2 +- setup.cfg | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 2462aab09d..230bbad6b3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ requires = [ "packaging", "wheel", "cython>=0.29.20; python_version>='3.10'", - "cython>=0.29.20<3.0.3; python_version<='3.9'", + "cython>=0.29.20,<3.0.3; python_version<='3.9'", # See https://numpy.org/doc/stable/user/depending_on_numpy.html for # the recommended way to build against numpy's C API: "oldest-supported-numpy", diff --git a/setup.cfg b/setup.cfg index 53a489c822..b19c56a92d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -38,7 +38,7 @@ setup_requires = numpy>=1.13.3 scipy>=1.0 cython>=0.29.20; python_version>='3.10' - cython>=0.29.20<3.0.3; python_version<='3.9' + cython>=0.29.20,<3.0.3; python_version<='3.9' packaging [options.packages.find] From 3e8a0164b7af70f8459a26ca6c7f7fcc6fccf58f Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 6 Oct 2023 14:10:38 -0400 Subject: [PATCH 040/247] Fix runtime requirement --- setup.cfg | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index b19c56a92d..d5373c9c85 100644 --- a/setup.cfg +++ b/setup.cfg @@ -47,7 +47,8 @@ include = qutip* [options.extras_require] graphics = matplotlib>=1.2.1 runtime_compilation = - cython>=0.29.20 + cython>=0.29.20; python_version>='3.10' + cython>=0.29.20,<3.0.3; python_version<='3.9' filelock semidefinite = cvxpy>=1.0 From 28f21a0e1a7f0d1db9310e6f84f218ce5ce1c0e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Sat, 7 Oct 2023 12:48:04 -0400 Subject: [PATCH 041/247] Update .github/workflows/tests.yml --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b903ba8091..096e5cb593 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -133,7 +133,7 @@ jobs: if [[ -n "${{ matrix.conda-extra-pkgs }}" ]]; then conda install "${{ matrix.conda-extra-pkgs }}" fi - python -m pip install -e .[$QUTIP_TARGET] -vv + python -m pip install -e .[$QUTIP_TARGET] python -m pip install "coverage${{ matrix.coverage-requirement }}" python -m pip install pytest-cov coveralls pytest-fail-slow From ec74d988632de08310d0fc81987bb11e5d5ffb3e Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Tue, 10 Oct 2023 13:54:51 +0900 Subject: [PATCH 042/247] Bugfix: MCSolver should not assume availability of improved_sampling option --- qutip/solver/mcsolve.py | 4 ++-- qutip/solver/nm_mcsolve.py | 7 ------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index 9682d73437..5b0deac800 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -471,7 +471,7 @@ def run(self, state, tlist, ntraj=1, *, probability is used as a lower-bound for random numbers in future monte carlo runs """ - if not self.options["improved_sampling"]: + if not self.options.get("improved_sampling", False): return super().run(state, tlist, ntraj=ntraj, args=args, e_ops=e_ops, timeout=timeout, target_tol=target_tol, seed=seed) @@ -526,7 +526,7 @@ def _get_integrator(self): @property def resultclass(self): - if self.options["improved_sampling"]: + if self.options.get("improved_sampling", False): return McResultImprovedSampling else: return McResult diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index fbada33f9b..1df49bdeb0 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -598,13 +598,6 @@ def options(self): @options.setter def options(self, new_options): - if (new_options is not None and - "improved_sampling" in new_options and - new_options["improved_sampling"]): - warnings.warn( - "NonMarkovianMCSolver currently does not support " - "'improved_sampling'. Improved sampling has been deactivated.") - new_options["improved_sampling"] = False MCSolver.options.fset(self, new_options) start.__doc__ = MultiTrajSolver.start.__doc__ From 8294c1e5f11fec00a8b5ca2822f22252d53a5585 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 18 Oct 2023 01:24:35 +0000 Subject: [PATCH 043/247] Bump urllib3 from 1.26.17 to 1.26.18 in /doc Bumps [urllib3](https://github.com/urllib3/urllib3) from 1.26.17 to 1.26.18. - [Release notes](https://github.com/urllib3/urllib3/releases) - [Changelog](https://github.com/urllib3/urllib3/blob/main/CHANGES.rst) - [Commits](https://github.com/urllib3/urllib3/compare/1.26.17...1.26.18) --- updated-dependencies: - dependency-name: urllib3 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 495bfdc78e..7cf91640dd 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -43,6 +43,6 @@ sphinxcontrib-jsmath==1.0.1 sphinxcontrib-qthelp==1.0.3 sphinxcontrib-serializinghtml==1.1.5 traitlets==5.9.0 -urllib3==1.26.17 +urllib3==1.26.18 wcwidth==0.2.6 wheel==0.38.4 From 59845032a216d74054b0c1d39d7a25492ebabd4d Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 18 Oct 2023 09:59:44 -0400 Subject: [PATCH 044/247] In qobjevo init try --- qutip/core/cy/qobjevo.pyx | 26 ++++++++++++++------------ qutip/core/data/dia.pyx | 1 + qutip/tests/solver/test_mesolve.py | 4 ++-- 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index c9a945b3b6..60194524b1 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -188,9 +188,9 @@ cdef class QobjEvo: qevo = H0 + H1 * coeff """ - def __init__(QobjEvo self, Q_object, args=None, tlist=None, - order=3, copy=True, compress=True, - function_style=None, boundary_conditions=None): + def __init__(QobjEvo self, Q_object, args=None, copy=True, compress=True, *, + function_style=None, feedback=None, + tlist=None, order=3, boundary_conditions=None): if isinstance(Q_object, QobjEvo): self.dims = Q_object.dims.copy() self.shape = Q_object.shape @@ -212,6 +212,9 @@ cdef class QobjEvo: self._isoper = -1 self._feedback_functions = {} args = args or {} + if feedback is not None: + for key, feed in feedback.items(): + args = self._add_feedback(key, feed, args) if ( isinstance(Q_object, list) @@ -443,7 +446,7 @@ cdef class QobjEvo: for element in self.elements ] - def _add_feedback(QobjEvo self, str key, feedback, normalize=False): + def _add_feedback(QobjEvo self, str key, feedback, args): """ Register an argument to be updated with the state during `matmul` and `expect`. @@ -456,7 +459,7 @@ cdef class QobjEvo: key: str Arguments key to update. - type: str, Qobj, QobjEvo + feedback: str, Qobj, QobjEvo Format of the `state_t`. - "qobj": As a Qobj, either a ket or dm. - "data": As a qutip data layer object. Density matrices will be @@ -466,11 +469,10 @@ cdef class QobjEvo: - Qobj, QobjEvo: The value is updated with the expectation value of the given operator and the state. - normalize: bool - Whether to normalize the state before using it. + args: dict + dictionary to add the initial / default value first. """ - if self._feedback_functions is None: - self._feedback_functions = {} + if feedback == "data" and self.issuper: self._feedback_functions[key] = \ _Column_Stacker(self.shape[1], normalize) @@ -479,17 +481,17 @@ cdef class QobjEvo: elif feedback in ["raw", "data"]: self._feedback_functions[key] = _pass_through elif feedback in ["qobj", "Qobj"]: - self._feedback_functions[key] = _To_Qobj(self, normalize) + self._feedback_functions[key] = _To_Qobj(self) elif isinstance(feedback, (Qobj, QobjEvo)): if isinstance(feedback, Qobj): feedback = QobjEvo(feedback) if self.issuper and self.dims[1] == feedback.dims: # tr(op @ dm) cases self._feedback_functions[key] = \ - _Expect(feedback, normalize, True) + _Expect(feedback, True) else: self._feedback_functions[key] = \ - _Expect(feedback, normalize, False) + _Expect(feedback, False) else: ValueError("Feedback type not understood.") diff --git a/qutip/core/data/dia.pyx b/qutip/core/data/dia.pyx index 2b37d09159..92d68b8f89 100644 --- a/qutip/core/data/dia.pyx +++ b/qutip/core/data/dia.pyx @@ -438,6 +438,7 @@ cdef Dia diags_( The shape of the output. The result does not need to be square, but the diagonals must be of the correct length to fit in. """ + cdef base.idxint i out = empty(n_rows, n_cols, len(offsets)) out.num_diag = len(offsets) for i in range(len(offsets)): diff --git a/qutip/tests/solver/test_mesolve.py b/qutip/tests/solver/test_mesolve.py index 1fd88efb5d..190dbc4dba 100644 --- a/qutip/tests/solver/test_mesolve.py +++ b/qutip/tests/solver/test_mesolve.py @@ -690,9 +690,9 @@ def f(t, A): N = 10 tol = 1e-14 psi0 = qutip.basis(N, 7) - a = qutip.destroy(N) + a = qutip.QobjEvo([qutip.destroy(N), f]) H = qutip.QobjEvo(qutip.num(N)) - solver = qutip.MESolver(H) + solver = qutip.MESolver(H, c_ops=[a]) solver.add_feedback("A", qutip.num(N)) result = solver.run(psi0, np.linspace(0, 30, 301), e_ops=[qutip.num(N)]) assert np.all(result.expect[0] > 4. - tol) From 3b7285c3b95dde3884567575f40925c7b36fdd57 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 18 Oct 2023 10:15:03 -0400 Subject: [PATCH 045/247] fix type of loop index --- qutip/core/data/dia.pyx | 1 + 1 file changed, 1 insertion(+) diff --git a/qutip/core/data/dia.pyx b/qutip/core/data/dia.pyx index 2b37d09159..92d68b8f89 100644 --- a/qutip/core/data/dia.pyx +++ b/qutip/core/data/dia.pyx @@ -438,6 +438,7 @@ cdef Dia diags_( The shape of the output. The result does not need to be square, but the diagonals must be of the correct length to fit in. """ + cdef base.idxint i out = empty(n_rows, n_cols, len(offsets)) out.num_diag = len(offsets) for i in range(len(offsets)): From 0b3fd9afeffafd8ae53d534b1e711ba78f2790d0 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 18 Oct 2023 13:44:57 -0400 Subject: [PATCH 046/247] Improved nogil + except --- qutip/core/data/add.pyx | 3 ++- qutip/core/data/csr.pxd | 6 +++--- qutip/core/data/csr.pyx | 4 ++-- qutip/core/data/expect.pxd | 8 ++++---- qutip/core/data/expect.pyx | 20 ++++++++++++-------- qutip/core/data/inner.pxd | 4 ++-- qutip/core/data/inner.pyx | 12 +++++++----- qutip/core/data/matmul.pyx | 4 ++-- qutip/core/data/project.pyx | 6 ++++-- qutip/core/data/trace.pyx | 6 ++++-- 10 files changed, 42 insertions(+), 31 deletions(-) diff --git a/qutip/core/data/add.pyx b/qutip/core/data/add.pyx index 45515af87a..abebef990e 100644 --- a/qutip/core/data/add.pyx +++ b/qutip/core/data/add.pyx @@ -27,7 +27,7 @@ __all__ = [ cdef int _ONE=1 -cdef void _check_shape(Data left, Data right) except * nogil: +cdef int _check_shape(Data left, Data right) except -1 nogil: if left.shape[0] != right.shape[0] or left.shape[1] != right.shape[1]: raise ValueError( "incompatible matrix shapes " @@ -35,6 +35,7 @@ cdef void _check_shape(Data left, Data right) except * nogil: + " and " + str(right.shape) ) + return 0 cdef idxint _add_csr(Accumulator *acc, CSR a, CSR b, CSR c, double tol) nogil: diff --git a/qutip/core/data/csr.pxd b/qutip/core/data/csr.pxd index 4c813564ef..4bc2b090b7 100644 --- a/qutip/core/data/csr.pxd +++ b/qutip/core/data/csr.pxd @@ -67,7 +67,7 @@ cdef inline Accumulator acc_alloc(size_t size): acc._sorted = True return acc -cdef inline void acc_scatter(Accumulator *acc, double complex value, base.idxint position) nogil: +cdef inline void acc_scatter(Accumulator *acc, double complex value, base.idxint position) noexcept nogil: """ Add a value to the accumulator for this row, in column `position`. The value is added on to any value already scattered into this position. @@ -85,7 +85,7 @@ cdef inline void acc_scatter(Accumulator *acc, double complex value, base.idxint acc._sorted &= acc.nnz == 0 or acc.nonzero[acc.nnz - 1] < position acc.nnz += 1 -cdef inline base.idxint acc_gather(Accumulator *acc, double complex *values, base.idxint *indices, double tol=0) nogil: +cdef inline base.idxint acc_gather(Accumulator *acc, double complex *values, base.idxint *indices, double tol=0) noexcept nogil: """ Copy all the accumulated values into this row into the output pointers. This will always output its values in sorted order. The pointers should @@ -115,7 +115,7 @@ cdef inline base.idxint acc_gather(Accumulator *acc, double complex *values, bas nnz += 1 return nnz -cdef inline void acc_reset(Accumulator *acc) nogil: +cdef inline void acc_reset(Accumulator *acc) noexcept nogil: """Prepare the accumulator to accept the next row of input.""" # We actually don't need to do anything to reset other than to change # our sentinel values; the sentinel `_cur_row` makes it easy to detect diff --git a/qutip/core/data/csr.pyx b/qutip/core/data/csr.pyx index 182c79714b..33acc941af 100644 --- a/qutip/core/data/csr.pyx +++ b/qutip/core/data/csr.pyx @@ -296,7 +296,7 @@ cpdef CSR copy_structure(CSR matrix): return out -cpdef inline base.idxint nnz(CSR matrix) nogil: +cpdef inline base.idxint nnz(CSR matrix) noexcept nogil: """Get the number of non-zero elements of a CSR matrix.""" return matrix.row_index[matrix.shape[0]] @@ -311,7 +311,7 @@ ctypedef fused _swap_data: double complex base.idxint -cdef inline void _sorter_swap(_swap_data *a, _swap_data *b) nogil: +cdef inline void _sorter_swap(_swap_data *a, _swap_data *b) noexcept nogil: a[0], b[0] = b[0], a[0] cdef class Sorter: diff --git a/qutip/core/data/expect.pxd b/qutip/core/data/expect.pxd index fd2c108fe2..26e1c4a565 100644 --- a/qutip/core/data/expect.pxd +++ b/qutip/core/data/expect.pxd @@ -3,13 +3,13 @@ from qutip.core.data cimport CSR, Dense, Data, Dia -cpdef double complex expect_csr(CSR op, CSR state) except * nogil -cpdef double complex expect_super_csr(CSR op, CSR state) except * nogil +cpdef double complex expect_csr(CSR op, CSR state) except * +cpdef double complex expect_super_csr(CSR op, CSR state) except * -cpdef double complex expect_csr_dense(CSR op, Dense state) except * nogil +cpdef double complex expect_csr_dense(CSR op, Dense state) except * cpdef double complex expect_super_csr_dense(CSR op, Dense state) except * nogil -cpdef double complex expect_dense(Dense op, Dense state) except * nogil +cpdef double complex expect_dense(Dense op, Dense state) except * cpdef double complex expect_super_dense(Dense op, Dense state) except * nogil cpdef double complex expect_dia(Dia op, Dia state) except * diff --git a/qutip/core/data/expect.pyx b/qutip/core/data/expect.pyx index a05969ddbb..1314e43658 100644 --- a/qutip/core/data/expect.pyx +++ b/qutip/core/data/expect.pyx @@ -24,7 +24,7 @@ __all__ = [ 'expect_super_csr_dense', 'expect_super_dia_dense', 'expect_super_data', ] -cdef void _check_shape_ket(Data op, Data state) except * nogil: +cdef int _check_shape_ket(Data op, Data state) except -1 nogil: if ( op.shape[1] != state.shape[0] # Matrix multiplication or state.shape[1] != 1 # State is ket @@ -32,8 +32,9 @@ cdef void _check_shape_ket(Data op, Data state) except * nogil: ): raise ValueError("incorrect input shapes " + str(op.shape) + " and " + str(state.shape)) + return 0 -cdef void _check_shape_dm(Data op, Data state) except * nogil: +cdef int _check_shape_dm(Data op, Data state) except -1 nogil: if ( op.shape[1] != state.shape[0] # Matrix multiplication or state.shape[0] != state.shape[1] # State is square @@ -41,15 +42,18 @@ cdef void _check_shape_dm(Data op, Data state) except * nogil: ): raise ValueError("incorrect input shapes " + str(op.shape) + " and " + str(state.shape)) + return 0 -cdef void _check_shape_super(Data op, Data state) except * nogil: +cdef int _check_shape_super(Data op, Data state) except -1 nogil: if state.shape[1] != 1: raise ValueError("expected a column-stacked matrix") if ( op.shape[1] != state.shape[0] # Matrix multiplication or op.shape[0] != op.shape[1] # Square matrix ): - raise ValueError("incompatible shapes " + str(op.shape) + ", " + str(state.shape)) + raise ValueError("incompatible shapes " + + str(op.shape) + ", " + str(state.shape)) + return 0 cdef double complex _expect_csr_ket(CSR op, CSR state) except * nogil: @@ -94,7 +98,7 @@ cdef double complex _expect_csr_dm(CSR op, CSR state) except * nogil: return out -cpdef double complex expect_super_csr(CSR op, CSR state) except * nogil: +cpdef double complex expect_super_csr(CSR op, CSR state) except *: """ Perform the operation `tr(op @ state)` where `op` is supplied as a superoperator, and `state` is a column-stacked operator. @@ -112,7 +116,7 @@ cpdef double complex expect_super_csr(CSR op, CSR state) except * nogil: return out -cpdef double complex expect_csr(CSR op, CSR state) except * nogil: +cpdef double complex expect_csr(CSR op, CSR state) except *: """ Get the expectation value of the operator `op` over the state `state`. The state can be either a ket or a density matrix. @@ -186,7 +190,7 @@ cdef double complex _expect_dense_dense_dm(Dense op, Dense state) except * nogil return out -cpdef double complex expect_csr_dense(CSR op, Dense state) except * nogil: +cpdef double complex expect_csr_dense(CSR op, Dense state) except *: """ Get the expectation value of the operator `op` over the state `state`. The state can be either a ket or a density matrix. @@ -201,7 +205,7 @@ cpdef double complex expect_csr_dense(CSR op, Dense state) except * nogil: return _expect_csr_dense_dm(op, state) -cpdef double complex expect_dense(Dense op, Dense state) except * nogil: +cpdef double complex expect_dense(Dense op, Dense state) except *: """ Get the expectation value of the operator `op` over the state `state`. The state can be either a ket or a density matrix. diff --git a/qutip/core/data/inner.pxd b/qutip/core/data/inner.pxd index 5c202060a6..ab7f6d6b24 100644 --- a/qutip/core/data/inner.pxd +++ b/qutip/core/data/inner.pxd @@ -2,5 +2,5 @@ from qutip.core.data.csr cimport CSR -cpdef double complex inner_csr(CSR left, CSR right, bint scalar_is_ket=*) except * nogil -cpdef double complex inner_op_csr(CSR left, CSR op, CSR right, bint scalar_is_ket=*) except * nogil +cpdef double complex inner_csr(CSR left, CSR right, bint scalar_is_ket=*) except * +cpdef double complex inner_op_csr(CSR left, CSR op, CSR right, bint scalar_is_ket=*) except * diff --git a/qutip/core/data/inner.pyx b/qutip/core/data/inner.pyx index d488ae5210..01b3cd0da8 100644 --- a/qutip/core/data/inner.pyx +++ b/qutip/core/data/inner.pyx @@ -21,7 +21,7 @@ __all__ = [ ] -cdef void _check_shape_inner(Data left, Data right) except * nogil: +cdef int _check_shape_inner(Data left, Data right) except -1 nogil: if ( (left.shape[0] != 1 and left.shape[1] != 1) or right.shape[1] != 1 @@ -32,8 +32,9 @@ cdef void _check_shape_inner(Data left, Data right) except * nogil: + " and " + str(right.shape) ) + return 0 -cdef void _check_shape_inner_op(Data left, Data op, Data right) except * nogil: +cdef int _check_shape_inner_op(Data left, Data op, Data right) except -1 nogil: cdef bint left_shape = left.shape[0] == 1 or left.shape[1] == 1 cdef bint left_op = ( (left.shape[0] == 1 and left.shape[1] == op.shape[0]) @@ -50,6 +51,7 @@ cdef void _check_shape_inner_op(Data left, Data op, Data right) except * nogil: " and ", str(right.shape), ])) + return 0 cdef double complex _inner_csr_bra_ket(CSR left, CSR right) nogil: cdef size_t col, ptr_bra, ptr_ket @@ -72,7 +74,7 @@ cdef double complex _inner_csr_ket_ket(CSR left, CSR right) nogil: out += conj(left.data[ptr_l]) * right.data[ptr_r] return out -cpdef double complex inner_csr(CSR left, CSR right, bint scalar_is_ket=False) except * nogil: +cpdef double complex inner_csr(CSR left, CSR right, bint scalar_is_ket=False) except *: """ Compute the complex inner product . The shape of `left` is used to determine if it has been supplied as a ket or a bra. The result of @@ -238,7 +240,7 @@ cpdef double complex inner_op_dia(Dia left, Dia op, Dia right, return inner cpdef double complex inner_op_csr(CSR left, CSR op, CSR right, - bint scalar_is_ket=False) except * nogil: + bint scalar_is_ket=False) except *: """ Compute the complex inner product . The shape of `left` is used to determine if it has been supplied as a ket or a bra. The result of @@ -251,7 +253,7 @@ cpdef double complex inner_op_csr(CSR left, CSR op, CSR right, """ _check_shape_inner_op(left, op, right) cdef double complex l - if left.shape[0] == left.shape[1] == op.shape[0] == op.shape[1] == right.shape[1] == 1: + if 1 == left.shape[1] == left.shape[0] == op.shape[0] == op.shape[1] == right.shape[1]: if not (csr.nnz(left) and csr.nnz(op) and csr.nnz(right)): return 0 l = conj(left.data[0]) if scalar_is_ket else left.data[0] diff --git a/qutip/core/data/matmul.pyx b/qutip/core/data/matmul.pyx index 06942c1314..238520c13c 100644 --- a/qutip/core/data/matmul.pyx +++ b/qutip/core/data/matmul.pyx @@ -59,7 +59,7 @@ __all__ = [ ] -cdef void _check_shape(Data left, Data right, Data out=None) except * nogil: +cdef int _check_shape(Data left, Data right, Data out=None) except -1 nogil: if left.shape[1] != right.shape[0]: raise ValueError( "incompatible matrix shapes " @@ -78,7 +78,7 @@ cdef void _check_shape(Data left, Data right, Data out=None) except * nogil: + " but needed " + str((left.shape[0], right.shape[1])) ) - + return 0 cdef idxint _matmul_csr_estimate_nnz(CSR left, CSR right): """ diff --git a/qutip/core/data/project.pyx b/qutip/core/data/project.pyx index 45fa7ac956..8b3b5e4751 100644 --- a/qutip/core/data/project.pyx +++ b/qutip/core/data/project.pyx @@ -15,7 +15,7 @@ __all__ = [ ] -cdef void _project_ket_csr(CSR ket, CSR out) nogil: +cdef int _project_ket_csr(CSR ket, CSR out) except -1 nogil: """ Calculate the projection of the given ket, and place the output in out. """ @@ -33,9 +33,10 @@ cdef void _project_ket_csr(CSR ket, CSR out) nogil: for ptr in range(nnz_in): out.data[offset + ptr] = ket.data[row] * conj(ket.data[ptr]) offset += nnz_in + return 0 -cdef void _project_bra_csr(CSR bra, CSR out) nogil: +cdef int _project_bra_csr(CSR bra, CSR out) except -1 nogil: """ Calculate the projection of the given bra, and place the output in out. """ @@ -66,6 +67,7 @@ cdef void _project_bra_csr(CSR bra, CSR out) nogil: # Handle all zero rows after the last non-zero entry. for row_out in range(row_out, out.shape[0]): out.row_index[row_out + 1] = cur + return 0 cpdef CSR project_csr(CSR state): diff --git a/qutip/core/data/trace.pyx b/qutip/core/data/trace.pyx index 210444dc31..3460424de1 100644 --- a/qutip/core/data/trace.pyx +++ b/qutip/core/data/trace.pyx @@ -15,18 +15,20 @@ __all__ = [ ] -cdef void _check_shape(Data matrix) except * nogil: +cdef int _check_shape(Data matrix) except -1 nogil: if matrix.shape[0] != matrix.shape[1]: raise ValueError("".join([ "matrix shape ", str(matrix.shape), " is not square.", ])) + return 0 -cdef void _check_shape_oper_ket(int N, Data matrix) except * nogil: +cdef int _check_shape_oper_ket(int N, Data matrix) except -1 nogil: if matrix.shape[0] != N * N or matrix.shape[1] != 1: raise ValueError("".join([ "matrix ", str(matrix.shape), " is not a stacked square matrix." ])) + return 0 cpdef double complex trace_csr(CSR matrix) except * nogil: From 179c8869184637d258186ee40fd2aec0530fd1f3 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 19 Oct 2023 15:54:37 -0400 Subject: [PATCH 047/247] Add deprecation warnings to solver --- qutip/solver/brmesolve.py | 5 +- qutip/solver/mcsolve.py | 6 +- qutip/solver/mesolve.py | 6 +- qutip/solver/sesolve.py | 5 +- qutip/solver/solver_base.py | 96 +++++++++++++++++++++++++++ qutip/solver/stochastic.py | 8 ++- qutip/tests/solver/test_stochastic.py | 24 +++++++ 7 files changed, 139 insertions(+), 11 deletions(-) diff --git a/qutip/solver/brmesolve.py b/qutip/solver/brmesolve.py index 7bfc8b27ad..f23cf48adc 100644 --- a/qutip/solver/brmesolve.py +++ b/qutip/solver/brmesolve.py @@ -12,12 +12,12 @@ from ..core.blochredfield import bloch_redfield_tensor, SpectraCoefficient from ..core.cy.coefficient import InterCoefficient from ..core import data as _data -from .solver_base import Solver +from .solver_base import Solver, _solver_deprecation from .options import _SolverOptions def brmesolve(H, psi0, tlist, a_ops=[], e_ops=[], c_ops=[], - args={}, sec_cutoff=0.1, options=None): + args={}, sec_cutoff=0.1, options=None, **kwargs): """ Solves for the dynamics of a system using the Bloch-Redfield master equation, given an input Hamiltonian, Hermitian bath-coupling terms and @@ -142,6 +142,7 @@ def brmesolve(H, psi0, tlist, a_ops=[], e_ops=[], c_ops=[], the ``a_ops``, but it is usually less efficient than manually choosing it. """ + options = _solver_deprecation(kwargs, options, "br") H = QobjEvo(H, args=args, tlist=tlist) c_ops = c_ops if c_ops is not None else [] diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index 5b0deac800..043231196e 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -3,7 +3,7 @@ import numpy as np from ..core import QobjEvo, spre, spost, Qobj, unstack_columns from .multitraj import MultiTrajSolver -from .solver_base import Solver, Integrator +from .solver_base import Solver, Integrator, _solver_deprecation from .result import McResult, McTrajectoryResult, McResultImprovedSampling from .mesolve import mesolve, MESolver import qutip.core.data as _data @@ -11,7 +11,8 @@ def mcsolve(H, state, tlist, c_ops=(), e_ops=None, ntraj=500, *, - args=None, options=None, seeds=None, target_tol=None, timeout=None): + args=None, options=None, seeds=None, target_tol=None, timeout=None, + **kwargs): r""" Monte Carlo evolution of a state vector :math:`|\psi \rangle` for a given Hamiltonian and sets of collapse operators. Options for the @@ -130,6 +131,7 @@ def mcsolve(H, state, tlist, c_ops=(), e_ops=None, ntraj=500, *, The simulation will end when the first end condition is reached between ``ntraj``, ``timeout`` and ``target_tol``. """ + options = _solver_deprecation(kwargs, options, "mc") H = QobjEvo(H, args=args, tlist=tlist) if not isinstance(c_ops, (list, tuple)): c_ops = [c_ops] diff --git a/qutip/solver/mesolve.py b/qutip/solver/mesolve.py index 5792038af1..023c477eda 100644 --- a/qutip/solver/mesolve.py +++ b/qutip/solver/mesolve.py @@ -10,11 +10,12 @@ from .. import (Qobj, QobjEvo, isket, liouvillian, ket2dm, lindblad_dissipator) from ..core import stack_columns, unstack_columns from ..core.data import to -from .solver_base import Solver +from .solver_base import Solver, _solver_deprecation from .sesolve import sesolve, SESolver -def mesolve(H, rho0, tlist, c_ops=None, e_ops=None, args=None, options=None): +def mesolve(H, rho0, tlist, c_ops=None, e_ops=None, args=None, options=None, + **kwargs): """ Master equation evolution of a density matrix for a given Hamiltonian and set of collapse operators, or a Liouvillian. @@ -125,6 +126,7 @@ def mesolve(H, rho0, tlist, c_ops=None, e_ops=None, args=None, options=None): is an empty list of `store_states=True` in options]. """ + options = _solver_deprecation(kwargs, options) H = QobjEvo(H, args=args, tlist=tlist) c_ops = c_ops if c_ops is not None else [] diff --git a/qutip/solver/sesolve.py b/qutip/solver/sesolve.py index 0914624232..9498be186b 100644 --- a/qutip/solver/sesolve.py +++ b/qutip/solver/sesolve.py @@ -7,10 +7,10 @@ import numpy as np from time import time from .. import Qobj, QobjEvo -from .solver_base import Solver +from .solver_base import Solver, _solver_deprecation -def sesolve(H, psi0, tlist, e_ops=None, args=None, options=None): +def sesolve(H, psi0, tlist, e_ops=None, args=None, options=None, **kwargs): """ Schrodinger equation evolution of a state vector or unitary matrix for a given Hamiltonian. @@ -98,6 +98,7 @@ def sesolve(H, psi0, tlist, e_ops=None, args=None, options=None): or density matrices corresponding to the times in `tlist` [if `e_ops` is an empty list of `store_states=True` in options]. """ + options = _solver_deprecation(kwargs, options) H = QobjEvo(H, args=args, tlist=tlist) solver = SESolver(H, options=options) return solver.run(psi0, tlist, e_ops=e_ops) diff --git a/qutip/solver/solver_base.py b/qutip/solver/solver_base.py index 028eb448f3..c9c82b3c81 100644 --- a/qutip/solver/solver_base.py +++ b/qutip/solver/solver_base.py @@ -7,6 +7,7 @@ from .integrator import Integrator from ..ui.progressbar import progress_bars from time import time +import warnings class Solver: @@ -394,3 +395,98 @@ def add_integrator(cls, integrator, key): " of `qutip.solver.Integrator`") cls._avail_integrators[key] = integrator + + +def _solver_deprecation(kwargs, options, solver="me"): + """ + Function to help the transition from v4 to v5. + Raise warnings for solver input that where moved from parameter to options. + """ + if options is None: + options = {} + # TODO remove by 5.1 + if "progress_bar" in kwargs: + warnings.warn( + '"progress_bar" is now included in options:\n' + 'Use `options={"progress_bar": False / True / "tqdm" / "enhanced"}`' + ) + options["progress_bar"] = kwargs.pop("progress_bar") + + if "_safe_mode" in kwargs: + warnings.warn( + '"_safe_mode" is no longer supported.' + ) + del kwargs["_safe_mode"] + + if "verbose" in kwargs and solver == "br": + warnings.warn( + '"verbose" is no longer supported.' + ) + del kwargs["verbose"] + + if "tol" in kwargs and solver == "br": + warnings.warn( + 'The "tol" parameter is no longer used. ' + '`qutip.settings.core["auto_tidyup_atol"]` ' + 'is now used for rounding small values in sparse arrays.' + ) + del kwargs["tol"] + + if "map_func" in kwargs and solver in ["mc", "stoc"]: + warnings.warn( + '"map_func" is now included in options:\n' + 'Use `options={"map": "serial" / "parallel" / "loky"}`' + ) + # 4.X pass the function, 5.X use a string + del kwargs["map_func"] + + if "map_kwargs" in kwargs and solver in ["mc", "stoc"]: + warnings.warn( + '"map_kwargs" are now included in options:\n' + 'Use `options={"num_cpus": N, "job_timeout": Nsec}`' + ) + del kwargs["map_kwargs"] + + if "nsubsteps" in kwargs and solver == "stoc": + warnings.warn( + '"nsubsteps" is now replaced by "dt" in options:\n' + 'Use `options={"dt": 0.001}`\n' + 'The given value of "nsubsteps" is ignored in this call.' + ) + # Could be (tlist[1] - tlist[0]) / kwargs["nsubsteps"] + del kwargs["nsubsteps"] + + if "tol" in kwargs and solver == "stoc": + warnings.warn( + 'The "tol" parameter is now the "atol" options:\n' + 'Use `options={"atol": tol}`' + ) + options["atol"] = kwargs.pop("tol") + + if "store_all_expect" in kwargs and solver == "stoc": + warnings.warn( + 'The "store_all_expect" parameter is now the ' + '"keep_runs_results" options:\n' + 'Use `options={"keep_runs_results": False / True}`' + ) + options["keep_runs_results"] = kwargs.pop("store_all_expect") + + if "store_measurement" in kwargs and solver == "stoc": + warnings.warn( + 'The "store_measurement" parameter is now an options:\n' + 'Use `options={"store_measurement": False / True}`' + ) + options["store_measurement"] = kwargs.pop("store_measurement") + + if ("dW_factors" in kwargs or "m_ops" in kwargs) and solver == "stoc": + raise TypeError( + '"m_ops" and "dW_factors" are now properties of ' + 'the stochastic solver class, use:\n' + '>>> solver = SMESolver(H, c_ops)\n' + '>>> solver.m_ops = m_ops\n' + '>>> solver.dW_factors = dW_factors\n' + ) + + if kwargs: + raise TypeError(f"unexpected keyword argument {kwargs.keys()}") + return options diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index d5d0504a85..464adb65b2 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -7,7 +7,7 @@ import numpy as np from collections.abc import Iterable from functools import partial - +from .solver_base import _solver_deprecation class StochasticTrajResult(Result): def _post_init(self, m_ops=(), dw_factor=(), heterodyne=False): @@ -223,7 +223,7 @@ def __call__(self, options): def smesolve( H, rho0, tlist, c_ops=(), sc_ops=(), heterodyne=False, *, e_ops=(), args={}, ntraj=500, options=None, - seeds=None, target_tol=None, timeout=None, + seeds=None, target_tol=None, timeout=None, **kwargs ): """ Solve stochastic master equation. @@ -335,6 +335,7 @@ def smesolve( An instance of the class :class:`qutip.solver.Result`. """ + options = _solver_deprecation(kwargs, options, "stoc") H = QobjEvo(H, args=args, tlist=tlist) c_ops = [QobjEvo(c_op, args=args, tlist=tlist) for c_op in c_ops] sc_ops = [QobjEvo(c_op, args=args, tlist=tlist) for c_op in sc_ops] @@ -350,7 +351,7 @@ def smesolve( def ssesolve( H, psi0, tlist, sc_ops=(), heterodyne=False, *, e_ops=(), args={}, ntraj=500, options=None, - seeds=None, target_tol=None, timeout=None, + seeds=None, target_tol=None, timeout=None, **kwargs ): """ Solve stochastic Schrodinger equation. @@ -455,6 +456,7 @@ def ssesolve( output: :class:`qutip.solver.Result` An instance of the class :class:`qutip.solver.Result`. """ + options = _solver_deprecation(kwargs, options, "stoc") H = QobjEvo(H, args=args, tlist=tlist) sc_ops = [QobjEvo(c_op, args=args, tlist=tlist) for c_op in sc_ops] sol = SSESolver(H, sc_ops, options=options, heterodyne=heterodyne) diff --git a/qutip/tests/solver/test_stochastic.py b/qutip/tests/solver/test_stochastic.py index f6843c997e..c18ed9d126 100644 --- a/qutip/tests/solver/test_stochastic.py +++ b/qutip/tests/solver/test_stochastic.py @@ -316,3 +316,27 @@ def test_m_ops(heterodyne): noise = res.expect[0][1:] - res.measurement[0][0] assert np.mean(noise) == pytest.approx(0., abs=std/50**0.5 * 4) assert np.std(noise) == pytest.approx(std, abs=std/50**0.5 * 4) + + +def test_deprecation_warnings(): + with pytest.warns(UserWarning, match=r'map_func'): + ssesolve(qeye(2), basis(2), [0, 1e-5], [qeye(2)], map_func=None) + + with pytest.warns(UserWarning, match=r'progress_bar'): + ssesolve(qeye(2), basis(2), [0, 1e-5], [qeye(2)], progress_bar=None) + + with pytest.warns(UserWarning, match=r'nsubsteps'): + ssesolve(qeye(2), basis(2), [0, 1e-5], [qeye(2)], nsubsteps=None) + + with pytest.warns(UserWarning, match=r'map_func'): + ssesolve(qeye(2), basis(2), [0, 1e-5], [qeye(2)], map_func=None) + + with pytest.warns(UserWarning, match=r'store_all_expect'): + ssesolve(qeye(2), basis(2), [0, 1e-5], [qeye(2)], store_all_expect=1) + + with pytest.warns(UserWarning, match=r'store_measurement'): + ssesolve(qeye(2), basis(2), [0, 1e-5], [qeye(2)], store_measurement=1) + + with pytest.raises(TypeError) as err: + ssesolve(qeye(2), basis(2), [0, 1e-5], [qeye(2)], m_ops=1) + assert '"m_ops" and "dW_factors"' in str(err.value) From 2ee19cdf4062f18cad017353e7906dffb431de4e Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 19 Oct 2023 16:58:25 -0400 Subject: [PATCH 048/247] Set as FuturWarning --- qutip/solver/solver_base.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/qutip/solver/solver_base.py b/qutip/solver/solver_base.py index c9c82b3c81..f542eb0826 100644 --- a/qutip/solver/solver_base.py +++ b/qutip/solver/solver_base.py @@ -407,20 +407,23 @@ def _solver_deprecation(kwargs, options, solver="me"): # TODO remove by 5.1 if "progress_bar" in kwargs: warnings.warn( - '"progress_bar" is now included in options:\n' - 'Use `options={"progress_bar": False / True / "tqdm" / "enhanced"}`' + '"progress_bar" is now included in options:\n Use ' + '`options={"progress_bar": False / True / "tqdm" / "enhanced"}`', + FutureWarning ) options["progress_bar"] = kwargs.pop("progress_bar") if "_safe_mode" in kwargs: warnings.warn( - '"_safe_mode" is no longer supported.' + '"_safe_mode" is no longer supported.', + FutureWarning ) del kwargs["_safe_mode"] if "verbose" in kwargs and solver == "br": warnings.warn( - '"verbose" is no longer supported.' + '"verbose" is no longer supported.', + FutureWarning ) del kwargs["verbose"] @@ -428,14 +431,16 @@ def _solver_deprecation(kwargs, options, solver="me"): warnings.warn( 'The "tol" parameter is no longer used. ' '`qutip.settings.core["auto_tidyup_atol"]` ' - 'is now used for rounding small values in sparse arrays.' + 'is now used for rounding small values in sparse arrays.', + FutureWarning ) del kwargs["tol"] if "map_func" in kwargs and solver in ["mc", "stoc"]: warnings.warn( '"map_func" is now included in options:\n' - 'Use `options={"map": "serial" / "parallel" / "loky"}`' + 'Use `options={"map": "serial" / "parallel" / "loky"}`', + FutureWarning ) # 4.X pass the function, 5.X use a string del kwargs["map_func"] @@ -443,7 +448,8 @@ def _solver_deprecation(kwargs, options, solver="me"): if "map_kwargs" in kwargs and solver in ["mc", "stoc"]: warnings.warn( '"map_kwargs" are now included in options:\n' - 'Use `options={"num_cpus": N, "job_timeout": Nsec}`' + 'Use `options={"num_cpus": N, "job_timeout": Nsec}`', + FutureWarning ) del kwargs["map_kwargs"] @@ -451,7 +457,8 @@ def _solver_deprecation(kwargs, options, solver="me"): warnings.warn( '"nsubsteps" is now replaced by "dt" in options:\n' 'Use `options={"dt": 0.001}`\n' - 'The given value of "nsubsteps" is ignored in this call.' + 'The given value of "nsubsteps" is ignored in this call.', + FutureWarning ) # Could be (tlist[1] - tlist[0]) / kwargs["nsubsteps"] del kwargs["nsubsteps"] @@ -459,7 +466,8 @@ def _solver_deprecation(kwargs, options, solver="me"): if "tol" in kwargs and solver == "stoc": warnings.warn( 'The "tol" parameter is now the "atol" options:\n' - 'Use `options={"atol": tol}`' + 'Use `options={"atol": tol}`', + FutureWarning ) options["atol"] = kwargs.pop("tol") @@ -467,14 +475,16 @@ def _solver_deprecation(kwargs, options, solver="me"): warnings.warn( 'The "store_all_expect" parameter is now the ' '"keep_runs_results" options:\n' - 'Use `options={"keep_runs_results": False / True}`' + 'Use `options={"keep_runs_results": False / True}`', + FutureWarning ) options["keep_runs_results"] = kwargs.pop("store_all_expect") if "store_measurement" in kwargs and solver == "stoc": warnings.warn( 'The "store_measurement" parameter is now an options:\n' - 'Use `options={"store_measurement": False / True}`' + 'Use `options={"store_measurement": False / True}`', + FutureWarning ) options["store_measurement"] = kwargs.pop("store_measurement") From 448d0b5539319cf67eaa540fcefe81869cf5ea6e Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 19 Oct 2023 17:02:28 -0400 Subject: [PATCH 049/247] map also accept the function --- qutip/solver/parallel.py | 5 ++++- qutip/solver/solver_base.py | 3 +-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index 83bf727a0a..6e56d01ce9 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -385,9 +385,12 @@ def loky_pmap(task, values, task_args=None, task_kwargs=None, _get_map = { + parallel_map: parallel_map, "parallel_map": parallel_map, "parallel": parallel_map, + serial_map: serial_map, "serial_map": serial_map, "serial": serial_map, - "loky": loky_pmap + loky_pmap: loky_pmap, + "loky": loky_pmap, } diff --git a/qutip/solver/solver_base.py b/qutip/solver/solver_base.py index f542eb0826..867b5b2c6a 100644 --- a/qutip/solver/solver_base.py +++ b/qutip/solver/solver_base.py @@ -442,8 +442,7 @@ def _solver_deprecation(kwargs, options, solver="me"): 'Use `options={"map": "serial" / "parallel" / "loky"}`', FutureWarning ) - # 4.X pass the function, 5.X use a string - del kwargs["map_func"] + options=["map"] = kwargs.pop("map_func") if "map_kwargs" in kwargs and solver in ["mc", "stoc"]: warnings.warn( From 27451bcc6e3526abcd5bcd01d9b0fe0e66246f86 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 19 Oct 2023 17:13:16 -0400 Subject: [PATCH 050/247] Test expect FuturWarning --- qutip/solver/solver_base.py | 2 +- qutip/tests/solver/test_stochastic.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/qutip/solver/solver_base.py b/qutip/solver/solver_base.py index 867b5b2c6a..97068b7783 100644 --- a/qutip/solver/solver_base.py +++ b/qutip/solver/solver_base.py @@ -442,7 +442,7 @@ def _solver_deprecation(kwargs, options, solver="me"): 'Use `options={"map": "serial" / "parallel" / "loky"}`', FutureWarning ) - options=["map"] = kwargs.pop("map_func") + options["map"] = kwargs.pop("map_func") if "map_kwargs" in kwargs and solver in ["mc", "stoc"]: warnings.warn( diff --git a/qutip/tests/solver/test_stochastic.py b/qutip/tests/solver/test_stochastic.py index c18ed9d126..336648836e 100644 --- a/qutip/tests/solver/test_stochastic.py +++ b/qutip/tests/solver/test_stochastic.py @@ -319,22 +319,22 @@ def test_m_ops(heterodyne): def test_deprecation_warnings(): - with pytest.warns(UserWarning, match=r'map_func'): + with pytest.warns(FutureWarning, match=r'map_func'): ssesolve(qeye(2), basis(2), [0, 1e-5], [qeye(2)], map_func=None) - with pytest.warns(UserWarning, match=r'progress_bar'): + with pytest.warns(FutureWarning, match=r'progress_bar'): ssesolve(qeye(2), basis(2), [0, 1e-5], [qeye(2)], progress_bar=None) - with pytest.warns(UserWarning, match=r'nsubsteps'): + with pytest.warns(FutureWarning, match=r'nsubsteps'): ssesolve(qeye(2), basis(2), [0, 1e-5], [qeye(2)], nsubsteps=None) - with pytest.warns(UserWarning, match=r'map_func'): + with pytest.warns(FutureWarning, match=r'map_func'): ssesolve(qeye(2), basis(2), [0, 1e-5], [qeye(2)], map_func=None) - with pytest.warns(UserWarning, match=r'store_all_expect'): + with pytest.warns(FutureWarning, match=r'store_all_expect'): ssesolve(qeye(2), basis(2), [0, 1e-5], [qeye(2)], store_all_expect=1) - with pytest.warns(UserWarning, match=r'store_measurement'): + with pytest.warns(FutureWarning, match=r'store_measurement'): ssesolve(qeye(2), basis(2), [0, 1e-5], [qeye(2)], store_measurement=1) with pytest.raises(TypeError) as err: From 232651dacd7cf098782386057db3079fd26342ef Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 19 Oct 2023 18:32:16 -0400 Subject: [PATCH 051/247] Revert map dict --- qutip/solver/parallel.py | 3 --- qutip/solver/solver_base.py | 2 +- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index 6e56d01ce9..92061b116f 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -385,12 +385,9 @@ def loky_pmap(task, values, task_args=None, task_kwargs=None, _get_map = { - parallel_map: parallel_map, "parallel_map": parallel_map, "parallel": parallel_map, - serial_map: serial_map, "serial_map": serial_map, "serial": serial_map, - loky_pmap: loky_pmap, "loky": loky_pmap, } diff --git a/qutip/solver/solver_base.py b/qutip/solver/solver_base.py index 97068b7783..c8e743fea4 100644 --- a/qutip/solver/solver_base.py +++ b/qutip/solver/solver_base.py @@ -442,7 +442,7 @@ def _solver_deprecation(kwargs, options, solver="me"): 'Use `options={"map": "serial" / "parallel" / "loky"}`', FutureWarning ) - options["map"] = kwargs.pop("map_func") + del kwargs["map_func"] if "map_kwargs" in kwargs and solver in ["mc", "stoc"]: warnings.warn( From 74d5a8be763eb30986776cac4f1812988b140559 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 26 Oct 2023 16:59:58 -0400 Subject: [PATCH 052/247] Feedback at QobjEvo creation --- qutip/core/cy/qobjevo.pxd | 1 + qutip/core/cy/qobjevo.pyx | 153 +++++++++++++++++++------------ qutip/solver/brmesolve.py | 1 + qutip/solver/floquet.py | 2 + qutip/solver/mcsolve.py | 18 ++-- qutip/solver/sode/sode.py | 2 +- qutip/solver/solver_base.py | 5 +- qutip/solver/stochastic.py | 12 +-- qutip/tests/core/test_qobjevo.py | 31 ++++--- 9 files changed, 132 insertions(+), 93 deletions(-) diff --git a/qutip/core/cy/qobjevo.pxd b/qutip/core/cy/qobjevo.pxd index 159b71dc17..3b86bc799c 100644 --- a/qutip/core/cy/qobjevo.pxd +++ b/qutip/core/cy/qobjevo.pxd @@ -13,6 +13,7 @@ cdef class QobjEvo: int _issuper int _isoper readonly dict _feedback_functions + readonly list _solver_only_feedback cpdef Data _call(QobjEvo self, double t) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 60194524b1..cddbb42577 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -106,6 +106,13 @@ cdef class QobjEvo: Refer to Scipy's documentation for further details: https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.make_interp_spline.html + feedback: dict + Set of arguments that update automatically when used in a solver. ie. + With ``args={"psi": psi0}, feedback={"psi": "qobj"}`` the arguments + ``psi`` will take the value of the ket at the time the operator is used + during the evolution of ``sesolve`` allowing non-linear Hamiltonian, + etc. See :method:`QobjEvo.add_feedback` for more information. + Inserting an initial value in ``args`` is required. Attributes ---------- @@ -199,6 +206,7 @@ cdef class QobjEvo: self._isoper = ( Q_object)._isoper self.elements = ( Q_object).elements.copy() self._feedback_functions = Q_object._feedback_functions.copy() + self._solver_only_feedback = Q_object._solver_only_feedback.copy() if args: self.arguments(args) if compress: @@ -211,10 +219,11 @@ cdef class QobjEvo: self._issuper = -1 self._isoper = -1 self._feedback_functions = {} + self._solver_only_feedback = [] args = args or {} if feedback is not None: for key, feed in feedback.items(): - args = self._add_feedback(key, feed, args) + self.add_feedback(key, feed) if ( isinstance(Q_object, list) @@ -251,6 +260,10 @@ cdef class QobjEvo: repr_str = f'{cls}: dims = {self.dims}, shape = {self.shape}, ' repr_str += f'type = {self.type}, superrep = {self.superrep}, ' repr_str += f'isconstant = {self.isconstant}, num_elements = {self.num_elements}' + if self._feedback_functions: + repr_str += '\nQobjEvo depedent on the state when available.' + if self._solver_only_feedback: + repr_str += f'\nQobjEvo uses extra information from solver: {self._solver_only_feedback}' return repr_str def _read_element(self, op, copy, tlist, args, order, function_style, @@ -446,7 +459,7 @@ cdef class QobjEvo: for element in self.elements ] - def _add_feedback(QobjEvo self, str key, feedback, args): + def add_feedback(QobjEvo self, str key, feedback): """ Register an argument to be updated with the state during `matmul` and `expect`. @@ -464,36 +477,43 @@ cdef class QobjEvo: - "qobj": As a Qobj, either a ket or dm. - "data": As a qutip data layer object. Density matrices will be square matrix. - - "raw": As a qutip data layer object. Density matrices will be - columns stacked: shape=(N**2, 1). - Qobj, QobjEvo: The value is updated with the expectation value of the given operator and the state. - - args: dict - dictionary to add the initial / default value first. + - str: Other solver specific feedback. See corresponding solver + documentation for available values. """ - - if feedback == "data" and self.issuper: - self._feedback_functions[key] = \ - _Column_Stacker(self.shape[1], normalize) - elif feedback == "data" and normalize: - self._feedback_functions[key] = _normalize - elif feedback in ["raw", "data"]: + if feedback == "data": self._feedback_functions[key] = _pass_through elif feedback in ["qobj", "Qobj"]: self._feedback_functions[key] = _To_Qobj(self) elif isinstance(feedback, (Qobj, QobjEvo)): if isinstance(feedback, Qobj): feedback = QobjEvo(feedback) - if self.issuper and self.dims[1] == feedback.dims: - # tr(op @ dm) cases - self._feedback_functions[key] = \ - _Expect(feedback, True) - else: - self._feedback_functions[key] = \ - _Expect(feedback, False) + self._feedback_functions[key] = _Expect(feedback) + elif isinstance(feedback, str): + self._solver_only_feedback.append((key, feedback)) else: - ValueError("Feedback type not understood.") + raise ValueError("feedback not understood.") + + def _register_feedback(self, solvers_feeds, solver="solver"): + """ + Function to receive feedback source from + """ + if not self._solver_only_feedback: + return self + res = self.copy() + new_args = {} + for key, feed in self._solver_only_feedback: + if feed not in solvers_feeds: + raise ValueError( + f"Desired feedback {key} is not available for the {solver}." + ) + if callable(solvers_feeds[feed]): + res._feedback_functions[key] = solvers_feeds[feed] + else: + new_args[key] = solvers_feeds[feed] + res.arguments(**new_args) + return res ########################################################################### # Math function # @@ -527,6 +547,7 @@ cdef class QobjEvo: for element in ( other).elements: self.elements.append(element) self._feedback_functions.update(other._feedback_functions) + self._solver_only_feedback += other._solver_only_feedback elif isinstance(other, Qobj): if other.dims != self.dims: raise TypeError("incompatible dimensions" + @@ -589,7 +610,16 @@ cdef class QobjEvo: res.elements = [left @ element for element in res.elements] res._issuper = -1 res._isoper = -1 + + new_feed = {} + for key, func in right._feedback_functions.items(): + if isinstance(func, _To_Qobj): + new_feed[key] = _To_Qobj(res) + else: + new_feed[key] = func + res._feedback_functions = new_feed return res + else: return NotImplemented @@ -616,6 +646,14 @@ cdef class QobjEvo: res.elements = [other @ element for element in res.elements] res._issuper = -1 res._isoper = -1 + + new_feed = {} + for key, func in self._feedback_functions.items(): + if isinstance(func, _To_Qobj): + new_feed[key] = _To_Qobj(res) + else: + new_feed[key] = func + res._feedback_functions = new_feed return res else: return NotImplemented @@ -648,7 +686,18 @@ cdef class QobjEvo: for left, right in itertools.product( self.elements, ( other).elements )] + self._feedback_functions.update(other._feedback_functions) + self._solver_only_feedback += other._solver_only_feedback + + new_feed = {} + for key, func in self._feedback_functions.items(): + if isinstance(func, _To_Qobj): + new_feed[key] = _To_Qobj(res) + else: + new_feed[key] = func + self._feedback_functions = new_feed + else: return NotImplemented return self @@ -719,6 +768,7 @@ cdef class QobjEvo: cdef QobjEvo res = self.copy() res.elements = [element.linear_map(Qobj.trans) for element in res.elements] + res.dims = [res.dims[1], res.dims[0]] return res def conj(self): @@ -733,6 +783,7 @@ cdef class QobjEvo: cdef QobjEvo res = self.copy() res.elements = [element.linear_map(Qobj.dag, True) for element in res.elements] + res.dims = [res.dims[1], res.dims[0]] return res def to(self, data_type): @@ -801,6 +852,13 @@ cdef class QobjEvo: res.type = res.elements[0].qobj(0).type res._issuper = res.elements[0].qobj(0).issuper res._isoper = res.elements[0].qobj(0).isoper + res._feedback_functions = {} + for key, func in self._feedback_functions.items(): + if isinstance(func, _To_Qobj): + res._feedback_functions[key] = _To_Qobj(res) + else: + res._feedback_functions[key] = func + if not _skip_check: if res(0) != out: raise ValueError("The mapping is not linear") @@ -1083,20 +1141,21 @@ cdef class QobjEvo: cdef class _Expect: cdef QobjEvo oper - cdef bint normalize cdef bint stack - def __init__(self, oper, normalize, stack): + def __init__(self, oper): self.oper = oper - self.normalize = normalize - self.stack = stack + self.N2 = oper.shape[1] def __call__(self, t, state): - if self.stack: - state = _data.column_unstack(state, self.oper.shape[0]) - if self.normalize: - state = _normalize(None, state) - return self.oper.expect_data(t, state) + if self.state[0] == self.N: + return self.oper.expect_data(t, state) + if self.state[0] == self.N**2 and self.state[1] == 1: + return self.oper.expect_data(t, _data.column_unstack(state, self.N)) + raise ValueError( + f"Shape of the expect operator ({self.oper.shape}) " + f"does not match the state ({self.state.shape})." + ) cdef class _To_Qobj: @@ -1105,53 +1164,25 @@ cdef class _To_Qobj: cdef idxint N cdef bint normalize - def __init__(self, base, normalize): + def __init__(self, base): self.dims = base.dims if not base.issuper: self.dims_flat = [1 for _ in base.dims[0]] self.issuper = base.issuper self.N = int(base.shape[0]**0.5) - self.normalize = normalize def __call__(self, t, state): if state.shape[0] == state.shape[1]: out = Qobj(state, dims=self.dims) elif self.issuper and state.shape[1] == 1: - state = _data.column_unstack(state, self.N) - out = Qobj(state, dims=self.dims[1]) + out = Qobj(_data.column_unstack(state, self.N), dims=self.dims[1]) elif state.shape[1] == 1: out = Qobj(state, dims=[self.dims[1], self.dims_flat]) else: # rectangular state dims are patially lost... out = Qobj(state, dims=[self.dims[1], [state.shape[1]]]) - if self.normalize: - out = out.unit() return out def _pass_through(t, state): return state - - -def _normalize(t, state): - if state.shape[0] == state.shape[1]: - norm = _data.trace(state) - else: - norm = _data.norm.frobenius(state) - return _data.mul(state, 1 / norm) - - -cdef class _Column_Stacker: - cdef idxint N - cdef bint normalize - - def __init__(self, shape, normalize): - self.N = int(shape**0.5) - self.normalize = normalize - - def __call__(self, t, state): - if state.shape[1] == 1: - state = _data.column_unstack(state, self.N) - if self.normalize: - state = _normalize(None, state) - return state diff --git a/qutip/solver/brmesolve.py b/qutip/solver/brmesolve.py index f23cf48adc..865d42f095 100644 --- a/qutip/solver/brmesolve.py +++ b/qutip/solver/brmesolve.py @@ -278,6 +278,7 @@ def __init__(self, H, a_ops, c_ops=None, sec_cutoff=0.1, *, options=None): self._integrator = self._get_integrator() self._state_metadata = {} self.stats = self._initialize_stats() + self.rhs._register_feedback({}, solver=self.name) def _initialize_stats(self): diff --git a/qutip/solver/floquet.py b/qutip/solver/floquet.py index d941f04697..848967cf17 100644 --- a/qutip/solver/floquet.py +++ b/qutip/solver/floquet.py @@ -87,6 +87,8 @@ def __init__( # Default computation tlist = np.linspace(0, T, 101) memoize = 101 + if H._feedback_functions or H._solver_only_feedback: + raise NotImplementedError("FloquetBasis does not support feedback") self.U = Propagator(H, args=args, options=options, memoize=memoize) for t in tlist: # Do the evolution by steps to save the intermediate results. diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index b4f79c2f79..fca12e732c 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -188,18 +188,18 @@ def arguments(self, args): n_op.arguments(args) def add_feedback(self, key, type): - if type == "collapse": - self._collapse_key = key - return - self.rhs._add_feedback(key, type, True) + self.rhs._add_feedback(key, type) for c_op in self.c_ops: - c_op._add_feedback(key, type, True) + c_op._add_feedback(key, type) for n_op in self.n_ops: - n_op._add_feedback(key, type, True) + n_op._add_feedback(key, type) - def register_feedback(self, type, val): - if type == "collapse" and self._collapse_key: - self.arguments({self._collapse_key: val}) + def register_feedback(self, key, val): + self.rhs._register_feedback({key: val}, solver="McSolver") + for c_op in self.c_ops: + c_op._register_feedback({key: val}, solver="McSolver") + for n_op in self.n_ops: + n_op._register_feedback({key: val}, solver="McSolver") class MCIntegrator: diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index aef1dae565..272052c7a4 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -72,7 +72,7 @@ def set_state(self, t, state0, generator): t, self.options["dt"], generator, (self.N_dw, num_collapse) ) - self.rhs.register_feedback("wiener_process", self.wiener) + self.rhs.register_feedback(self.wiener) opt = [self.options[key] for key in self._stepper_options] self.step_func = self.stepper(self.rhs(self.options), *opt).run self._is_set = True diff --git a/qutip/solver/solver_base.py b/qutip/solver/solver_base.py index 8715ac205a..a6bc82a78a 100644 --- a/qutip/solver/solver_base.py +++ b/qutip/solver/solver_base.py @@ -51,6 +51,7 @@ def __init__(self, rhs, *, options=None): self._integrator = self._get_integrator() self._state_metadata = {} self.stats = self._initialize_stats() + self.rhs._register_feedback({}, solver=self.name) def _initialize_stats(self): """ Return the initial values for the solver stats. @@ -413,13 +414,11 @@ def add_feedback(self, key, type): - "qobj": As a Qobj, either a ket or dm. - "data": As a qutip data layer object. Density matrices will be - square matrix. - - "raw": As a qutip data layer object. Density matrices will be columns stacked: shape=(N**2, 1). - Qobj, QobjEvo: The value is updated with the expectation value of the given operator and the state. """ - self.rhs._add_feedback(key, type) + self.rhs.add_feedback(key, type) def _solver_deprecation(kwargs, options, solver="me"): diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index 3098875e78..8278c3cec1 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -253,18 +253,18 @@ def add_feedback(self, key, type): that return the wiener process value at the time t. The process is a step function with step of lenght ``options["dt"]``. """ - if type == "wiener_process": - self._noise_key = key - return self.H._add_feedback(key, type) for c_op in self.c_ops: c_op._add_feedback(key, type) for sc_op in self.sc_ops: sc_op._add_feedback(key, type) - def register_feedback(self, type, val): - if type == "wiener_process" and self._noise_key: - self.arguments({self._noise_key: val}) + def register_feedback(self, val): + self.H._register_feedback("wiener_process", val) + for c_op in self.c_ops: + c_op._register_feedback("wiener_process", val) + for sc_op in self.sc_ops: + sc_op._register_feedback("wiener_process", val) def smesolve( diff --git a/qutip/tests/core/test_qobjevo.py b/qutip/tests/core/test_qobjevo.py index 6c235dff7d..715946efd3 100644 --- a/qutip/tests/core/test_qobjevo.py +++ b/qutip/tests/core/test_qobjevo.py @@ -97,7 +97,9 @@ def pseudo_qevo(request): @pytest.fixture def all_qevo(pseudo_qevo, coeff_type): - return QobjEvo(*pseudo_qevo[coeff_type]) + base, args, *tlist = pseudo_qevo[coeff_type] + if tlist: tlist = tlist[0] + return QobjEvo(base, args, tlist=tlist) @pytest.fixture @@ -127,7 +129,9 @@ def _div(a, b): def test_call(pseudo_qevo, coeff_type): # test creation of QobjEvo and call - qevo = QobjEvo(*pseudo_qevo[coeff_type]) + base, args, *tlist = pseudo_qevo[coeff_type] + if tlist: tlist = tlist[0] + qevo = QobjEvo(base, args, tlist=tlist) assert isinstance(qevo(0), Qobj) assert qevo.isoper assert not qevo.isconstant @@ -510,7 +514,9 @@ def test_QobjEvo_isherm_flag_knowcase(): ['func_coeff', 'string', 'array', 'logarray'] ) def test_QobjEvo_to_list(coeff_type, pseudo_qevo): - qevo = QobjEvo(*pseudo_qevo[coeff_type]) + base, args, *tlist = pseudo_qevo[coeff_type] + if tlist: tlist = tlist[0] + qevo = QobjEvo(base, args, tlist=tlist) as_list = qevo.to_list() assert len(as_list) == 2 restored = QobjEvo(as_list) @@ -536,9 +542,9 @@ def __call__(self, t, data=None, qobj=None, e_val=None): def test_feedback_oper(): checker = Feedback_Checker_Coefficient() qevo = QobjEvo([qeye(2), checker]) - qevo._add_feedback("data", "data") - qevo._add_feedback("qobj", "qobj") - qevo._add_feedback("e_val", qeye(2)) + qevo.add_feedback("data", "data") + qevo.add_feedback("qobj", "qobj") + qevo.add_feedback("e_val", qeye(2)) checker.state = rand_ket(2) qevo.expect(0, checker.state) @@ -552,15 +558,15 @@ def test_feedback_oper(): def test_feedback_super(): checker = Feedback_Checker_Coefficient() qevo = QobjEvo([spre(qeye(2)), checker]) - qevo._add_feedback("data", "data") - qevo._add_feedback("qobj", "qobj") - qevo._add_feedback("e_val", qeye(2)) + qevo.add_feedback("data", "data") + qevo.add_feedback("qobj", "qobj") + qevo.add_feedback("e_val", qeye(2)) checker.state = rand_dm(2) qevo.expect(0, operator_to_vector(checker.state)) qevo.matmul_data(0, operator_to_vector(checker.state).data) - qevo._add_feedback("e_val", spre(qeye(2))) + qevo.add_feedback("e_val", spre(qeye(2))) checker.state = rand_dm(2) qevo.expect(0, operator_to_vector(checker.state)) @@ -568,8 +574,8 @@ def test_feedback_super(): checker = Feedback_Checker_Coefficient() qevo = QobjEvo([spre(qeye(2)), checker]) - qevo._add_feedback("data", "data") - qevo._add_feedback("qobj", "qobj") + qevo.add_feedback("data", "data") + qevo.add_feedback("qobj", "qobj") checker.state = rand_dm(4) checker.state.dims = [[[2],[2]], [[2],[2]]] @@ -577,7 +583,6 @@ def test_feedback_super(): checker = Feedback_Checker_Coefficient() qevo = QobjEvo([spre(qeye(2)), checker]) - qevo._add_feedback("data", "raw") checker.state = operator_to_vector(rand_dm(2)) qevo.expect(0, checker.state) From d6524705a43e78e5a05b74f7822a03c3424066bf Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 31 Oct 2023 14:33:12 -0400 Subject: [PATCH 053/247] feedback in QobjEvo and solvers --- qutip/core/cy/qobjevo.pyx | 22 +++++++++------------- qutip/solver/floquet.py | 5 ++++- qutip/solver/mcsolve.py | 8 +++++--- qutip/solver/multitraj.py | 2 +- qutip/solver/sode/rouchon.py | 2 +- qutip/solver/stochastic.py | 12 ++++++------ qutip/tests/core/test_qobjevo.py | 15 ++++++++++----- qutip/tests/solver/test_mesolve.py | 7 +++++-- 8 files changed, 41 insertions(+), 32 deletions(-) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index cddbb42577..b5fc10237c 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -499,21 +499,15 @@ cdef class QobjEvo: """ Function to receive feedback source from """ - if not self._solver_only_feedback: - return self - res = self.copy() + print(solvers_feeds, solver) new_args = {} for key, feed in self._solver_only_feedback: if feed not in solvers_feeds: raise ValueError( f"Desired feedback {key} is not available for the {solver}." ) - if callable(solvers_feeds[feed]): - res._feedback_functions[key] = solvers_feeds[feed] - else: - new_args[key] = solvers_feeds[feed] - res.arguments(**new_args) - return res + new_args[key] = solvers_feeds[feed] + self.arguments(**new_args) ########################################################################### # Math function # @@ -1142,19 +1136,21 @@ cdef class QobjEvo: cdef class _Expect: cdef QobjEvo oper cdef bint stack + cdef int N, N2 def __init__(self, oper): self.oper = oper - self.N2 = oper.shape[1] + self.N = oper.shape[1] + self.N2 = oper.shape[1]**2 def __call__(self, t, state): - if self.state[0] == self.N: + if state.shape[0] == self.N: return self.oper.expect_data(t, state) - if self.state[0] == self.N**2 and self.state[1] == 1: + if state.shape[0] == self.N2 and state.shape[1] == 1: return self.oper.expect_data(t, _data.column_unstack(state, self.N)) raise ValueError( f"Shape of the expect operator ({self.oper.shape}) " - f"does not match the state ({self.state.shape})." + f"does not match the state ({state.shape})." ) diff --git a/qutip/solver/floquet.py b/qutip/solver/floquet.py index 848967cf17..47ec5f85b1 100644 --- a/qutip/solver/floquet.py +++ b/qutip/solver/floquet.py @@ -87,7 +87,10 @@ def __init__( # Default computation tlist = np.linspace(0, T, 101) memoize = 101 - if H._feedback_functions or H._solver_only_feedback: + if ( + isinstance(H, QobjEvo) + and (H._feedback_functions or H._solver_only_feedback) + ): raise NotImplementedError("FloquetBasis does not support feedback") self.U = Propagator(H, args=args, options=options, memoize=memoize) for t in tlist: diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index fca12e732c..88ba81225b 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -188,13 +188,14 @@ def arguments(self, args): n_op.arguments(args) def add_feedback(self, key, type): - self.rhs._add_feedback(key, type) + self.rhs.add_feedback(key, type) for c_op in self.c_ops: - c_op._add_feedback(key, type) + c_op.add_feedback(key, type) for n_op in self.n_ops: - n_op._add_feedback(key, type) + n_op.add_feedback(key, type) def register_feedback(self, key, val): + print("_MCSystem.register_feedback", val, id(val)) self.rhs._register_feedback({key: val}, solver="McSolver") for c_op in self.c_ops: c_op._register_feedback({key: val}, solver="McSolver") @@ -245,6 +246,7 @@ def set_state(self, t, state0, generator, a trajectory with jumps """ self.collapses = [] + print(id(self.collapses)) self.system.register_feedback("collapse", self.collapses) self._generator = generator if no_jump: diff --git a/qutip/solver/multitraj.py b/qutip/solver/multitraj.py index 6ba1128037..3842475cfb 100644 --- a/qutip/solver/multitraj.py +++ b/qutip/solver/multitraj.py @@ -22,7 +22,7 @@ def arguments(self, args): self.rhs.arguments(args) def add_feedback(self, key, type): - self.rhs._add_feedback(key, type) + self.rhs.add_feedback(key, type) def register_feedback(self, type, val): pass diff --git a/qutip/solver/sode/rouchon.py b/qutip/solver/sode/rouchon.py index 49e9538fea..d8d7c010c6 100644 --- a/qutip/solver/sode/rouchon.py +++ b/qutip/solver/sode/rouchon.py @@ -91,7 +91,7 @@ def set_state(self, t, state0, generator): t, self.options["dt"], generator, (1, self.num_collapses,) ) - self.rhs.register_feedback("wiener_process", self.wiener) + self.rhs.register_feedback(self.wiener) self._make_operators() self._is_set = True diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index 8278c3cec1..73f2cff032 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -253,18 +253,18 @@ def add_feedback(self, key, type): that return the wiener process value at the time t. The process is a step function with step of lenght ``options["dt"]``. """ - self.H._add_feedback(key, type) + self.H.add_feedback(key, type) for c_op in self.c_ops: - c_op._add_feedback(key, type) + c_op.add_feedback(key, type) for sc_op in self.sc_ops: - sc_op._add_feedback(key, type) + sc_op.add_feedback(key, type) def register_feedback(self, val): - self.H._register_feedback("wiener_process", val) + self.H._register_feedback({"wiener_process": val}) for c_op in self.c_ops: - c_op._register_feedback("wiener_process", val) + c_op._register_feedback({"wiener_process": val}) for sc_op in self.sc_ops: - sc_op._register_feedback("wiener_process", val) + sc_op._register_feedback({"wiener_process": val}) def smesolve( diff --git a/qutip/tests/core/test_qobjevo.py b/qutip/tests/core/test_qobjevo.py index 715946efd3..029089dd80 100644 --- a/qutip/tests/core/test_qobjevo.py +++ b/qutip/tests/core/test_qobjevo.py @@ -524,12 +524,15 @@ def test_QobjEvo_to_list(coeff_type, pseudo_qevo): class Feedback_Checker_Coefficient: - def __init__(self): + def __init__(self, stacked=True): self.state = None + self.stacked = stacked def __call__(self, t, data=None, qobj=None, e_val=None): if self.state is not None: - if data is not None: + if data is not None and self.stacked: + assert data == operator_to_vector(self.state).data + elif data is not None: assert data == self.state.data if qobj is not None: assert qobj == self.state @@ -548,10 +551,12 @@ def test_feedback_oper(): checker.state = rand_ket(2) qevo.expect(0, checker.state) - qevo.matmul_data(0, checker.state.data) - checker.state = rand_ket(2) qevo.expect(0, checker.state) + + checker.state = rand_ket(2) + qevo.matmul_data(0, checker.state.data) + checker.state = rand_ket(2) qevo.matmul_data(0, checker.state.data) @@ -572,7 +577,7 @@ def test_feedback_super(): qevo.expect(0, operator_to_vector(checker.state)) qevo.matmul_data(0, operator_to_vector(checker.state).data) - checker = Feedback_Checker_Coefficient() + checker = Feedback_Checker_Coefficient(stacked=False) qevo = QobjEvo([spre(qeye(2)), checker]) qevo.add_feedback("data", "data") qevo.add_feedback("qobj", "qobj") diff --git a/qutip/tests/solver/test_mesolve.py b/qutip/tests/solver/test_mesolve.py index 190dbc4dba..c25c1dd0ec 100644 --- a/qutip/tests/solver/test_mesolve.py +++ b/qutip/tests/solver/test_mesolve.py @@ -690,9 +690,12 @@ def f(t, A): N = 10 tol = 1e-14 psi0 = qutip.basis(N, 7) - a = qutip.QobjEvo([qutip.destroy(N), f]) + a = qutip.QobjEvo( + [qutip.destroy(N), f], + args={"A": 0.}, + feedback={"A": qutip.num(N)} + ) H = qutip.QobjEvo(qutip.num(N)) solver = qutip.MESolver(H, c_ops=[a]) - solver.add_feedback("A", qutip.num(N)) result = solver.run(psi0, np.linspace(0, 30, 301), e_ops=[qutip.num(N)]) assert np.all(result.expect[0] > 4. - tol) From f1257f7728cb9a92f100a241713160e6e0eff0f0 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 31 Oct 2023 18:14:40 -0400 Subject: [PATCH 054/247] Update doc and tests --- qutip/core/cy/qobjevo.pyx | 5 ++--- qutip/solver/brmesolve.py | 25 +++++++++++++++++++++++++ qutip/solver/mcsolve.py | 6 +----- qutip/solver/stochastic.py | 8 +++----- qutip/tests/core/test_qobjevo.py | 19 +++++++++++-------- qutip/tests/solver/test_sesolve.py | 2 +- qutip/tests/solver/test_stochastic.py | 8 +++++--- 7 files changed, 48 insertions(+), 25 deletions(-) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index b5fc10237c..8809d5aa4a 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -479,8 +479,8 @@ cdef class QobjEvo: square matrix. - Qobj, QobjEvo: The value is updated with the expectation value of the given operator and the state. - - str: Other solver specific feedback. See corresponding solver - documentation for available values. + - str: Other solver specific feedback. See corresponding solver's + ``add_feedback`` function's documentation for available values. """ if feedback == "data": self._feedback_functions[key] = _pass_through @@ -499,7 +499,6 @@ cdef class QobjEvo: """ Function to receive feedback source from """ - print(solvers_feeds, solver) new_args = {} for key, feed in self._solver_only_feedback: if feed not in solvers_feeds: diff --git a/qutip/solver/brmesolve.py b/qutip/solver/brmesolve.py index 865d42f095..c1de5e1b5f 100644 --- a/qutip/solver/brmesolve.py +++ b/qutip/solver/brmesolve.py @@ -362,3 +362,28 @@ def _apply_options(self, keys): else: self._integrator.options = self._options self._integrator.reset(hard=True) + + def add_feedback(self, key, type): + """ + Register an argument to be updated with the state during the evolution. + + Equivalent to do: + `solver.argument(key=state_t)` + + The state will not be in the lab basis, but in the evolution basis. + + Parameters + ---------- + key : str + Arguments key to update. + + type : str, Qobj, QobjEvo + Format of the `state_t`. + + - "qobj": As a Qobj, either a ket or dm. + - "data": As a qutip data layer object. Density matrices will be + columns stacked: shape=(N**2, 1). + - Qobj, QobjEvo: The value is updated with the expectation value of + the given operator and the state. + """ + self.rhs.add_feedback(key, type) diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index 88ba81225b..d24f951c3c 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -195,7 +195,6 @@ def add_feedback(self, key, type): n_op.add_feedback(key, type) def register_feedback(self, key, val): - print("_MCSystem.register_feedback", val, id(val)) self.rhs._register_feedback({key: val}, solver="McSolver") for c_op in self.c_ops: c_op._register_feedback({key: val}, solver="McSolver") @@ -246,7 +245,6 @@ def set_state(self, t, state0, generator, a trajectory with jumps """ self.collapses = [] - print(id(self.collapses)) self.system.register_feedback("collapse", self.collapses) self._generator = generator if no_jump: @@ -663,12 +661,10 @@ def add_feedback(self, key, type): Arguments key to update. type : str, Qobj, QobjEvo - Format of the `state_t`. + Solver or evolution state. - "qobj": As a Qobj, either a ket or dm. - "data": As a qutip data layer object. Density matrices will be - square matrix. - - "raw": As a qutip data layer object. Density matrices will be columns stacked: shape=(N**2, 1). - Qobj, QobjEvo: The value is updated with the expectation value of the given operator and the state. diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index 73f2cff032..b9b5e61ef3 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -743,17 +743,15 @@ def add_feedback(self, key, type): - "qobj": As a Qobj, either a ket or dm. - "data": As a qutip data layer object. Density matrices will be - square matrix. - - "raw": As a qutip data layer object. Density matrices will be columns stacked: shape=(N**2, 1). - Qobj, QobjEvo: The value is updated with the expectation value of the given operator and the state. - "wiener_process": The value is replaced by a function ``W(t)`` - that return an array of wiener processes value at the time t. The - wiener process for the i-th sc_ops is the i-th element for + that return an array of wiener processes values at the time t. + The wiener process for the i-th sc_ops is the i-th element for homodyne detection and the (2i, 2i+1) pairs of process in heterodyne detection. The process is a step function with step of - lenght ``options["dt"]``. + length ``options["dt"]``. """ self.system.add_feedback(key, type) self._integrator.reset(hard=True) diff --git a/qutip/tests/core/test_qobjevo.py b/qutip/tests/core/test_qobjevo.py index 029089dd80..71d8dedc38 100644 --- a/qutip/tests/core/test_qobjevo.py +++ b/qutip/tests/core/test_qobjevo.py @@ -544,10 +544,12 @@ def __call__(self, t, data=None, qobj=None, e_val=None): def test_feedback_oper(): checker = Feedback_Checker_Coefficient() - qevo = QobjEvo([qeye(2), checker]) - qevo.add_feedback("data", "data") + qevo = QobjEvo( + [qeye(2), checker], + args={"data":None, "e_val": 0.0}, + feedback={"data": "data", "e_val": qeye(2)} + ) qevo.add_feedback("qobj", "qobj") - qevo.add_feedback("e_val", qeye(2)) checker.state = rand_ket(2) qevo.expect(0, checker.state) @@ -562,10 +564,12 @@ def test_feedback_oper(): def test_feedback_super(): checker = Feedback_Checker_Coefficient() - qevo = QobjEvo([spre(qeye(2)), checker]) - qevo.add_feedback("data", "data") + qevo = QobjEvo( + [spre(qeye(2)), checker], + args={"data":None, "e_val": 0.0}, + feedback={"data": "data", "e_val": qeye(2)} + ) qevo.add_feedback("qobj", "qobj") - qevo.add_feedback("e_val", qeye(2)) checker.state = rand_dm(2) qevo.expect(0, operator_to_vector(checker.state)) @@ -578,8 +582,7 @@ def test_feedback_super(): qevo.matmul_data(0, operator_to_vector(checker.state).data) checker = Feedback_Checker_Coefficient(stacked=False) - qevo = QobjEvo([spre(qeye(2)), checker]) - qevo.add_feedback("data", "data") + qevo = QobjEvo([spre(qeye(2)), checker], feedback={"data": "data"}) qevo.add_feedback("qobj", "qobj") checker.state = rand_dm(4) diff --git a/qutip/tests/solver/test_sesolve.py b/qutip/tests/solver/test_sesolve.py index 218cf619a9..630845e190 100644 --- a/qutip/tests/solver/test_sesolve.py +++ b/qutip/tests/solver/test_sesolve.py @@ -316,7 +316,7 @@ def f(t, A, qobj=None): psi0 = qutip.basis(N, N-1) a = qutip.destroy(N) H = qutip.QobjEvo([qutip.num(N), [a+a.dag(), f]], args={"A": N-1}) + H.add_feedback("A", qutip.num(N)) solver = qutip.SESolver(H) - solver.add_feedback("A", qutip.num(N)) result = solver.run(psi0, np.linspace(0, 30, 301), e_ops=[qutip.num(N)]) assert np.all(result.expect[0] > 2 - tol) diff --git a/qutip/tests/solver/test_stochastic.py b/qutip/tests/solver/test_stochastic.py index 230de3f368..68d61cf912 100644 --- a/qutip/tests/solver/test_stochastic.py +++ b/qutip/tests/solver/test_stochastic.py @@ -327,15 +327,17 @@ def func(t, A, W): return (A - 6) * (A.real > 6.) * W(t)[0] H = num(10) - sc_ops = [QobjEvo([destroy(N), func], args={"A": 8, "W": lambda t: [0.]})] + sc_ops = [QobjEvo( + [destroy(N), func], + args={"A": 8, "W": lambda t: [0.]}, + feedback={"W": "wiener_process", "A": spre(num(10))}, + )] psi0 = basis(N, N-3) times = np.linspace(0, 10, 101) options = {"map": "serial", "dt": 0.001} solver = SMESolver(H, sc_ops=sc_ops, heterodyne=False, options=options) - solver.add_feedback("A", spre(num(10))) - solver.add_feedback("W", "wiener_process") results = solver.run(psi0, times, e_ops=[num(N)], ntraj=ntraj) assert np.all(results.expect[0] > 6.-1e-6) From 115206ed928f9f857066d55f53ef2b0e40d6f224 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 1 Nov 2023 13:46:06 -0400 Subject: [PATCH 055/247] Fix doc and tests --- qutip/solver/mcsolve.py | 5 +++++ qutip/tests/solver/test_mesolve.py | 2 +- qutip/tests/solver/test_stochastic.py | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index d24f951c3c..b56b126f8a 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -655,6 +655,11 @@ def add_feedback(self, key, type): Equivalent to do: `solver.argument(key=state_t)` + .. note:: + + The state passed by the monte-carlo solver are the unnormalized + states used internally. + Parameters ---------- key : str diff --git a/qutip/tests/solver/test_mesolve.py b/qutip/tests/solver/test_mesolve.py index c25c1dd0ec..bdc01cf00b 100644 --- a/qutip/tests/solver/test_mesolve.py +++ b/qutip/tests/solver/test_mesolve.py @@ -693,7 +693,7 @@ def f(t, A): a = qutip.QobjEvo( [qutip.destroy(N), f], args={"A": 0.}, - feedback={"A": qutip.num(N)} + feedback={"A": qutip.spre(qutip.num(N))} ) H = qutip.QobjEvo(qutip.num(N)) solver = qutip.MESolver(H, c_ops=[a]) diff --git a/qutip/tests/solver/test_stochastic.py b/qutip/tests/solver/test_stochastic.py index 68d61cf912..eb662c9704 100644 --- a/qutip/tests/solver/test_stochastic.py +++ b/qutip/tests/solver/test_stochastic.py @@ -330,7 +330,7 @@ def func(t, A, W): sc_ops = [QobjEvo( [destroy(N), func], args={"A": 8, "W": lambda t: [0.]}, - feedback={"W": "wiener_process", "A": spre(num(10))}, + feedback={"W": "wiener_process", "A": num(10)}, )] psi0 = basis(N, N-3) From 6b007c948588d5c7e7f5b156ce3794552cb56846 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 1 Nov 2023 15:02:23 -0400 Subject: [PATCH 056/247] Fix doc build --- qutip/core/cy/qobjevo.pyx | 4 +++- qutip/solver/solver_base.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 8809d5aa4a..1b74397a18 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -111,7 +111,7 @@ cdef class QobjEvo: With ``args={"psi": psi0}, feedback={"psi": "qobj"}`` the arguments ``psi`` will take the value of the ket at the time the operator is used during the evolution of ``sesolve`` allowing non-linear Hamiltonian, - etc. See :method:`QobjEvo.add_feedback` for more information. + etc. See :meth:`QobjEvo.add_feedback` for more information. Inserting an initial value in ``args`` is required. Attributes @@ -474,6 +474,7 @@ cdef class QobjEvo: feedback: str, Qobj, QobjEvo Format of the `state_t`. + - "qobj": As a Qobj, either a ket or dm. - "data": As a qutip data layer object. Density matrices will be square matrix. @@ -481,6 +482,7 @@ cdef class QobjEvo: the given operator and the state. - str: Other solver specific feedback. See corresponding solver's ``add_feedback`` function's documentation for available values. + """ if feedback == "data": self._feedback_functions[key] = _pass_through diff --git a/qutip/solver/solver_base.py b/qutip/solver/solver_base.py index a6bc82a78a..e40cae95fa 100644 --- a/qutip/solver/solver_base.py +++ b/qutip/solver/solver_base.py @@ -134,8 +134,8 @@ def run(self, state0, tlist, *, args=None, e_ops=None): values. Function[s] must have the signature f(t : float, state : Qobj) -> expect. - Return - ------ + Returns + ------- results : :class:`qutip.solver.Result` Results of the evolution. States and/or expect will be saved. You can control the saved data in the options. From 4c417994cb4fa0b89b56eb30a5d25edc166f89ee Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 2 Nov 2023 16:59:55 -0400 Subject: [PATCH 057/247] Faster steadystate, mainly for Dia --- qutip/core/data/csr.pyx | 37 +++++++++++++++++-------------------- qutip/solver/steadystate.py | 17 +++++++++++------ 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/qutip/core/data/csr.pyx b/qutip/core/data/csr.pyx index 33acc941af..c7eed323c2 100644 --- a/qutip/core/data/csr.pyx +++ b/qutip/core/data/csr.pyx @@ -643,29 +643,26 @@ cdef CSR from_coo_pointers( cpdef CSR from_dia(Dia matrix): - if matrix.num_diag == 0: - return zeros(*matrix.shape) - mat = matrix.as_scipy() - ordered = np.argsort(mat.offsets) - nnz = len(mat.offsets) * max(mat.shape) - ptrs = np.zeros(mat.shape[0]+1, dtype=idxint_dtype) - indices = np.zeros(nnz, dtype=idxint_dtype) - data = np.zeros(nnz, dtype=complex) - - ptr = 0 - for row in range(mat.shape[0]): - for idx in ordered: - off = mat.offsets[idx] - if off + row < 0: + cdef base.idxint col, diag, i, ptr=0 + cdef base.idxint nrows=matrix.shape[0], ncols=matrix.shape[1] + cdef base.idxint nnz = matrix.num_diag * min(matrix.shape) + cdef double complex[:] data = np.zeros(nnz, dtype=complex) + cdef base.idxint[:] cols = np.zeros(nnz, dtype=idxint_dtype) + cdef base.idxint[:] rows = np.zeros(nnz, dtype=idxint_dtype) + + for i in range(matrix.num_diag): + diag = matrix.offsets[i] + + for col in range(ncols): + if col - diag < 0 or col - diag >= nrows: continue - elif off + row >= mat.shape[1]: - break - indices[ptr] = off + row - data[ptr] = mat.data[idx, off + row] + data[ptr] = matrix.data[i * ncols + col] + rows[ptr] = col - diag + cols[ptr] = col ptr += 1 - ptrs[row + 1] = ptr - return CSR((data, indices, ptrs), matrix.shape, copy=False) + return from_coo_pointers(&rows[0], &cols[0], &data[0], matrix.shape[0], + matrix.shape[1], nnz) cdef inline base.idxint _diagonal_length( diff --git a/qutip/solver/steadystate.py b/qutip/solver/steadystate.py index 41fe9e4b63..6ba1dd1df9 100644 --- a/qutip/solver/steadystate.py +++ b/qutip/solver/steadystate.py @@ -126,9 +126,10 @@ def steadystate(A, c_ops=[], *, method='direct', solver=None, **kwargs): raise TypeError('Cannot calculate the steady state for a ' + 'non-dissipative system.') if not A.issuper: - A = liouvillian(A) - for op in c_ops: - A += lindblad_dissipator(op) + A = liouvillian(A, c_ops) + else: + for op in c_ops: + A += lindblad_dissipator(op) if "-" in method: # to support v4's "power-gmres" method @@ -202,10 +203,14 @@ def _steadystate_direct(A, weight, **kw): N = A.shape[0] n = int(N**0.5) dtype = type(A.data) + if dtype == _data.Dia: + # Dia is bad at vector, the following matmul is 10x slower with Dia + # than CSR and Dia is missing optimization such as `use_wbm`. + dtype = _data.CSR weight_vec = _data.column_stack(_data.diag([weight] * n, 0, dtype=dtype)) - weight_mat = _data.kron( - weight_vec.transpose(), - _data.one_element[dtype]((N, 1), (0, 0), 1) + weight_mat = _data.matmul( + _data.one_element[dtype]((N, 1), (0, 0), 1), + weight_vec.transpose() ) L = _data.add(weight_mat, A.data) b = _data.one_element[dtype]((N, 1), (0, 0), weight) From aac8a74697947345ebdedac9ad4d8e87324f24e8 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 3 Nov 2023 12:56:00 -0400 Subject: [PATCH 058/247] Add build_dir options to compilation options. --- qutip/core/coefficient.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/qutip/core/coefficient.py b/qutip/core/coefficient.py index 0c6ed689be..e23b08f275 100644 --- a/qutip/core/coefficient.py +++ b/qutip/core/coefficient.py @@ -245,6 +245,9 @@ class CompilationOptions(QutipOptions): clean_on_error : bool [True] When writing a cython file that cannot be imported, erase it. + + build_dir: str [None] + cythonize's build_dir. """ _link_flags = "" _compiler_flags = "" @@ -275,6 +278,7 @@ class CompilationOptions(QutipOptions): "link_flags": _link_flags, "extra_import": "", "clean_on_error": True, + "build_dir": None, } _settings_name = "compile" @@ -554,7 +558,9 @@ def compile_code(code, file_name, parsed, c_opt): include_dirs=[np.get_include()], language='c++' ) - ext_modules = cythonize(coeff_file, force=True) + ext_modules = cythonize( + coeff_file, force=True, build_dir=c_opt['build_dir'] + ) setup(ext_modules=ext_modules) except Exception as e: if c_opt['clean_on_error']: From c87ab9a1bfbec1236349daa7ae97500df216d46a Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 3 Nov 2023 14:27:49 -0400 Subject: [PATCH 059/247] Improve fidelity's docstring --- qutip/core/metrics.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/qutip/core/metrics.py b/qutip/core/metrics.py index acd43eba49..433588d143 100644 --- a/qutip/core/metrics.py +++ b/qutip/core/metrics.py @@ -9,13 +9,10 @@ 'hellinger_dist', 'hilbert_dist', 'average_gate_fidelity', 'process_fidelity', 'unitarity', 'dnorm'] -import warnings - import numpy as np from scipy import linalg as la import scipy.sparse as sp -from .superop_reps import (to_kraus, to_choi, _to_superpauli, to_super, - kraus_to_choi) +from .superop_reps import to_choi, _to_superpauli, to_super, kraus_to_choi from .superoperator import operator_to_vector, vector_to_operator from .operators import qeye, qeye_like from .states import ket2dm @@ -31,7 +28,13 @@ def fidelity(A, B): """ Calculates the fidelity (pseudo-metric) between two density matrices. - See: Nielsen & Chuang, "Quantum Computation and Quantum Information" + + Notes + ----- + Use the definition from Nielsen & Chuang, "Quantum Computation and Quantum + Information". For the fidelity defined in A. Gilchrist, N.K. Langford, + M.A. Nielsen, Phys. Rev. A 71, 062310 (2005), please use + :func:`process_fidelity` instead. Parameters ---------- @@ -196,7 +199,7 @@ def process_fidelity(oper, target=None): if isinstance(oper, list): # oper is a list of Kraus operators return _process_fidelity_to_id([k * target.dag() for k in oper]) elif oper.type == 'oper': - return _process_fidelity_to_id(oper*target.dag()) + return _process_fidelity_to_id(oper * target.dag()) elif oper.type == 'super': oper_super = to_super(oper) target_dag_super = to_super(target.dag()) From 93745d823ab95fdcf7419c44d11000df3e020f69 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 3 Nov 2023 15:43:06 -0400 Subject: [PATCH 060/247] Add towncrier entry --- doc/changes/2257.misc | 1 + qutip/core/metrics.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 doc/changes/2257.misc diff --git a/doc/changes/2257.misc b/doc/changes/2257.misc new file mode 100644 index 0000000000..7d75604039 --- /dev/null +++ b/doc/changes/2257.misc @@ -0,0 +1 @@ +Improve fidelity doc-string diff --git a/qutip/core/metrics.py b/qutip/core/metrics.py index 433588d143..3f03bf3387 100644 --- a/qutip/core/metrics.py +++ b/qutip/core/metrics.py @@ -34,7 +34,7 @@ def fidelity(A, B): Use the definition from Nielsen & Chuang, "Quantum Computation and Quantum Information". For the fidelity defined in A. Gilchrist, N.K. Langford, M.A. Nielsen, Phys. Rev. A 71, 062310 (2005), please use - :func:`process_fidelity` instead. + :func:`qutip.core.metrics.process_fidelity` instead. Parameters ---------- From 2b7501ea9c8dff34c4374f33917f0bc546e3e97d Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 3 Nov 2023 16:17:25 -0400 Subject: [PATCH 061/247] Even better docstring --- qutip/core/metrics.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qutip/core/metrics.py b/qutip/core/metrics.py index 3f03bf3387..64a57687f7 100644 --- a/qutip/core/metrics.py +++ b/qutip/core/metrics.py @@ -31,10 +31,10 @@ def fidelity(A, B): Notes ----- - Use the definition from Nielsen & Chuang, "Quantum Computation and Quantum - Information". For the fidelity defined in A. Gilchrist, N.K. Langford, - M.A. Nielsen, Phys. Rev. A 71, 062310 (2005), please use - :func:`qutip.core.metrics.process_fidelity` instead. + Uses the definition from Nielsen & Chuang, "Quantum Computation and Quantum + Information". It is the square root of the fidelity defined in + R. Jozsa, Journal of Modern Optics, 41:12, 2315 (1994), used in + :func:`qutip.core.metrics.process_fidelity`. Parameters ---------- From d98511e7b0f78c912b760e3b38afbb2c06631202 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 6 Nov 2023 16:12:46 -0500 Subject: [PATCH 062/247] Fix broken links --- doc/changelog.rst | 2 +- doc/contributors.rst | 4 ++-- doc/development/contributing.rst | 6 +++--- doc/development/ideas/quantum-error-mitigation.rst | 3 ++- doc/development/release_distribution.rst | 2 +- doc/development/roadmap.rst | 2 +- doc/frontmatter.rst | 6 +++--- doc/guide/dynamics/dynamics-options.rst | 2 +- doc/guide/dynamics/dynamics-stochastic.rst | 13 ++++++++++--- doc/guide/guide-tensor.rst | 10 +++++----- doc/installation.rst | 5 +---- qutip/core/metrics.py | 3 +-- qutip/entropy.py | 2 +- qutip/solver/countstat.py | 2 +- qutip/solver/floquet.py | 4 ++-- qutip/solver/heom/bofin_solvers.py | 12 +++--------- qutip/solver/integrator/qutip_integrator.py | 4 ++-- qutip/solver/integrator/scipy_integrator.py | 6 +++--- qutip/solver/integrator/verner7efficient.py | 4 ++-- qutip/solver/integrator/verner9efficient.py | 4 ++-- qutip/solver/mcsolve.py | 10 +++++----- qutip/solver/multitraj.py | 8 ++++---- qutip/solver/nm_mcsolve.py | 6 +++--- qutip/solver/result.py | 6 +++--- qutip/solver/solver_base.py | 8 ++++---- qutip/solver/steadystate.py | 3 +-- qutip/solver/stochastic.py | 2 +- 27 files changed, 68 insertions(+), 71 deletions(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index ad63be5726..ca2cc79f61 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -501,7 +501,7 @@ Improvements - Added transparency parameter to the add_point, add_vector and add_states methods in the Bloch and Bloch3d classes. (`#1837 `_ by Xavier Spronken) - Support ``Path`` objects in ``qutip.fileio``. (`#1813 `_ by Adrià Labay) - Improved the weighting in steadystate solver, so that the default weight matches the documented behaviour and the dense solver applies the weights in the same manner as the sparse solver. (`#1275 `_ and `#1802 `_ by NS2 Group at LPS and Simon Cross) -- Added a ``color_style`` option to the ``hinton`` plotting function. (`#1595 `_ by Cassandra Granade) +- Added a ``color_style`` option to the ``hinton`` plotting function. (`#1595 `_ by Cassandra Granade) - Improved the scaling of ``floquet_master_equation_rates`` and ``floquet_master_equation_tensor`` and fixed transposition and basis change errors in ``floquet_master_equation_tensor`` and ``floquet_markov_mesolve``. (`#1248 `_ by Camille Le Calonnec, Jake Lishman and Eric Giguère) - Removed ``linspace_with`` and ``view_methods`` from ``qutip.utilities``. For the former it is far better to use ``numpy.linspace`` and for the later Python's in-built ``help`` function or other tools. (`#1680 `_ by Eric Giguère) - Added support for passing callable functions as ``e_ops`` to ``mesolve`` and ``sesolve``. (`#1655 `_ by Marek Narożniak) diff --git a/doc/contributors.rst b/doc/contributors.rst index e112abeeee..da55af92f3 100644 --- a/doc/contributors.rst +++ b/doc/contributors.rst @@ -99,7 +99,6 @@ Lead Developers - `Neill Lambert `_ - `Eric Giguère `_ - `Boxi Li `_ -- `Jake Lishman `_ - `Simon Cross `_ - `Asier Galicia `_ @@ -107,9 +106,10 @@ Past Lead Developers ==================== - `Robert Johansson `_ (RIKEN) -- `Paul Nation `_ (Korea University) +- `Paul Nation `_ (Korea University) - `Chris Granade `_ - `Arne Grimsmo `_ +- `Jake Lishman `_ .. _developers-contributors: diff --git a/doc/development/contributing.rst b/doc/development/contributing.rst index de2af1b5c0..083754475e 100644 --- a/doc/development/contributing.rst +++ b/doc/development/contributing.rst @@ -8,7 +8,7 @@ Quick Start =========== QuTiP is developed through wide collaboration using the ``git`` version-control system, with the main repositories hosted in the `qutip organisation on GitHub `_. -You will need to be familiar with ``git`` as a tool, and the `GitHub Flow `_ workflow for branching and making pull requests. +You will need to be familiar with ``git`` as a tool, and the `GitHub Flow `_ workflow for branching and making pull requests. The exact details of environment set-up, build process and testing vary by repository and are discussed below, however in overview, the steps to contribute are: #. Consider creating an issue on the GitHub page of the relevant repository, describing the change you think should be made and why, so we can discuss details with you and make sure it is appropriate. @@ -102,7 +102,7 @@ Code Style The biggest concern you should always have is to make it easy for your code to be read and understood by the person who comes next. -All new contributions must follow `PEP 8 style `_; all pull requests will be passed through a linter that will complain if you violate it. +All new contributions must follow `PEP 8 style `_; all pull requests will be passed through a linter that will complain if you violate it. You should use the ``pycodestyle`` package locally (available on ``pip``) to test you satisfy the requirements before you push your commits, since this is rather faster than pushing 10 different commits trying to fix minor niggles. Keep in mind that there is quite a lot of freedom in this style, especially when it comes to line breaks. If a line is too long, consider the *best* way to split it up with the aim of making the code readable, not just the first thing that doesn't generate a warning. @@ -152,7 +152,7 @@ When making a pull request, we require that you add a towncrier entry along with You should create a file named ``.`` in the ``doc/changes`` directory, where the PR number should be substituted for ````, and ```` is either ``feature``, ``bugfix``, ``doc``, ``removal``, ``misc``, or ``deprecation``, depending on the type of change included in the PR. -You can also create this file by installing ``towncrier`` and running +You can also create this file by installing ``towncrier`` and running towncrier create . diff --git a/doc/development/ideas/quantum-error-mitigation.rst b/doc/development/ideas/quantum-error-mitigation.rst index 17e279ba12..2c8a68b9a1 100644 --- a/doc/development/ideas/quantum-error-mitigation.rst +++ b/doc/development/ideas/quantum-error-mitigation.rst @@ -20,7 +20,8 @@ device models, new noise models and integration with the existing general framework for quantum circuits (`qutip.qip.circuit`). There are also possible applications such as error mitigation techniques ([1]_, [2]_, [3]_). -The tutorial notebooks can be found at https://qutip.org/tutorials.html#nisq. A +The tutorial notebooks can be found in the Quantum information processing +section of https://qutip.org/qutip-tutorials/index-v5.html. A recent presentation on the FOSDEM conference may help you get an overview (https://fosdem.org/2020/schedule/event/quantum_qutip/). See also the Github Project page for a collection of related issues and ongoing Pull Requests. diff --git a/doc/development/release_distribution.rst b/doc/development/release_distribution.rst index 5d6956014d..75c581cbf9 100644 --- a/doc/development/release_distribution.rst +++ b/doc/development/release_distribution.rst @@ -324,7 +324,7 @@ HTML File Updates Conda Forge +++++++++++ -If not done previously then fork the `qutip-feedstock `_. +If not done previously then fork the `qutip-feedstock `_. Checkout a new branch on your fork, e.g. :: diff --git a/doc/development/roadmap.rst b/doc/development/roadmap.rst index 36f61a0600..c422d98d7c 100644 --- a/doc/development/roadmap.rst +++ b/doc/development/roadmap.rst @@ -435,7 +435,7 @@ HEOM revamp :tag: heom-revamp :status: completed :admin lead: `Neill `_ -:main dev: `Simon Cross `_, `Tarun Raheja `_ +:main dev: `Simon Cross `_, `Tarun Raheja `_ An overhaul of the HEOM solver, to incorporate the improvements pioneered in BoFiN. diff --git a/doc/frontmatter.rst b/doc/frontmatter.rst index af1db23c7b..ba45c750a1 100644 --- a/doc/frontmatter.rst +++ b/doc/frontmatter.rst @@ -9,11 +9,11 @@ Frontmatter About This Documentation ========================== -This document contains a user guide and automatically generated API documentation for QuTiP. A PDF version of this text is available at the `documentation page `_. +This document contains a user guide and automatically generated API documentation for QuTiP. A PDF version of this text is available at the `documentation page `_. **For more information see the** `QuTiP project web page`_. -.. _QuTiP project web page: https://www.qutip.org +.. _QuTiP project web page: https://qutip.org/ :Author: J.R. Johansson @@ -165,7 +165,7 @@ Several libraries rely on QuTiP for quantum physics or quantum information proce :QPtomographer: `QPtomographer `_ derive quantum error bars for quantum processes in terms of the diamond norm to a reference quantum channel -:QuNetSim: `QuNetSim `_ is a quantum networking simulation framework to develop and test protocols for quantum networks +:QuNetSim: `QuNetSim `_ is a quantum networking simulation framework to develop and test protocols for quantum networks :qupulse: `qupulse `_ is a toolkit to facilitate experiments involving pulse driven state manipulation of physical qubits diff --git a/doc/guide/dynamics/dynamics-options.rst b/doc/guide/dynamics/dynamics-options.rst index 587000ca84..a3bc831067 100644 --- a/doc/guide/dynamics/dynamics-options.rst +++ b/doc/guide/dynamics/dynamics-options.rst @@ -29,7 +29,7 @@ Options supported by the ODE integration depend on the "method" options of the s help(MESolver.integrator("adams").options) -See `Integrator <../../apidoc/classes.html#classes-ode>`_ for a list of supported methods. +See `Integrator `_ for a list of supported methods. As an example, let us consider changing the integrator, turn the GUI off, and strengthen the absolute tolerance. diff --git a/doc/guide/dynamics/dynamics-stochastic.rst b/doc/guide/dynamics/dynamics-stochastic.rst index faa2baf9a4..3931177ead 100644 --- a/doc/guide/dynamics/dynamics-stochastic.rst +++ b/doc/guide/dynamics/dynamics-stochastic.rst @@ -61,7 +61,8 @@ If the user also requires the measurement output, the options entry ``{"store_me Per default, homodyne is used. Heterodyne detections can be easily simulated by passing the arguments ``'heterodyne=True'`` to :func:`qutip.solver.stochastic.ssesolve`. -Examples of how to solve the stochastic Schrodinger equation using QuTiP can be found in this `development notebook `_. +.. + Examples of how to solve the stochastic Schrodinger equation using QuTiP can be found in this `development notebook <...TODO-Merge 61...>`_. Stochastic Master Equation ========================== @@ -168,8 +169,14 @@ where :math:`x` is the operator passed using ``m_ops``. The results are availabl ax.set_xlabel('Time') ax.legend() - -For other examples on :func:`qutip.solver.stochastic.smesolve`, see the `following notebook `_, as well as these notebooks available at `QuTiP Tutorials page `_: `heterodyne detection `_, `inefficient detection `_, and `feedback control `_. +.. + TODO merge qutip-tutorials#61 + For other examples on :func:`qutip.solver.stochastic.smesolve`, see the + `following notebook <...>`_, as well as these notebooks available at + `QuTiP Tutorials page `_: + `heterodyne detection <...>`_, + `inefficient detection <...>`_, and + `feedback control `_. .. plot:: :context: reset diff --git a/doc/guide/guide-tensor.rst b/doc/guide/guide-tensor.rst index beb6c26b7a..b870ba2672 100644 --- a/doc/guide/guide-tensor.rst +++ b/doc/guide/guide-tensor.rst @@ -421,9 +421,8 @@ represent the composition of two systems. QuTiP also allows more general tensor manipulations that are useful for converting between superoperator representations [WBC11]_. In particular, the :func:`~qutip.core.tensor.tensor_contract` function allows for -contracting one or more pairs of indices. As detailed in -the `channel contraction tutorial`_, this can be used to find -superoperators that represent partial trace maps. +contracting one or more pairs of indices. +This can be used to find superoperators that represent partial trace maps. Using this functionality, we can construct some quite exotic maps, such as a map from :math:`3 \times 3` operators to :math:`2 \times 2` operators: @@ -434,5 +433,6 @@ operators: [[[2], [2]], [[3], [3]]] - -.. _channel contraction tutorial: https://nbviewer.ipython.org/github/qutip/qutip-notebooks/blob/master/examples/superop-contract.ipynb +.. + TODO: remake from notebook to tutorials + .. _channel contraction tutorial: github/qutip/qutip-notebooks/blob/master/examples/superop-contract.ipynb diff --git a/doc/installation.rst b/doc/installation.rst index efce6af430..fbed3cea04 100644 --- a/doc/installation.rst +++ b/doc/installation.rst @@ -79,9 +79,6 @@ QuTiP will detect if it is being used within one of these richer environments, a Installing with conda ===================== -QuTiP is designed to work best when using the `Anaconda `_ or `Intel `_ Python distributions that support the conda package management system. -It is still possible to use ``pip`` to install QuTiP while using conda, but uniformly using conda will make complete dependency management easier. - If you already have your conda environment set up, and have the ``conda-forge`` channel available, then you can install QuTiP using: .. code-block:: bash @@ -267,7 +264,7 @@ At the end, the testing report should report a success; it is normal for some te Skips may be tests that do not run on your operating system, or tests of optional components that you have not installed the dependencies for. If any failures or errors occur, please check that you have installed all of the required modules. See the next section on how to check the installed versions of the QuTiP dependencies. -If these tests still fail, then head on over to the `QuTiP Discussion Board `_ or `the GitHub issues page `_ and post a message detailing your particular issue. +If these tests still fail, then head on over to the `QuTiP Discussion Board `_ or `the GitHub issues page `_ and post a message detailing your particular issue. .. _install-about: diff --git a/qutip/core/metrics.py b/qutip/core/metrics.py index acd43eba49..36ded9dd38 100644 --- a/qutip/core/metrics.py +++ b/qutip/core/metrics.py @@ -433,7 +433,7 @@ def dnorm(A, B=None, solver="CVXOPT", verbose=False, force_solve=False, Calculates the diamond norm of the quantum map q_oper, using the simplified semidefinite program of [Wat13]_. - The diamond norm SDP is solved by using CVXPY_. + The diamond norm SDP is solved by using `CVXPY `_. Parameters ---------- @@ -463,7 +463,6 @@ def dnorm(A, B=None, solver="CVXOPT", verbose=False, force_solve=False, ImportError If CVXPY cannot be imported. - .. _cvxpy: https://www.cvxpy.org/en/latest/ """ if cvxpy is None: # pragma: no cover raise ImportError("dnorm() requires CVXPY to be installed.") diff --git a/qutip/entropy.py b/qutip/entropy.py index cd62a69b76..c22bef3648 100644 --- a/qutip/entropy.py +++ b/qutip/entropy.py @@ -90,7 +90,7 @@ def concurrence(rho): References ---------- - .. [1] https://en.wikipedia.org/wiki/Concurrence_(quantum_computing) + .. [1] `https://en.wikipedia.org/wiki/Concurrence_(quantum_computing)` """ if rho.isket and rho.dims != [[2, 2], [1, 1]]: diff --git a/qutip/solver/countstat.py b/qutip/solver/countstat.py index f6a3a4341b..270960a1ed 100644 --- a/qutip/solver/countstat.py +++ b/qutip/solver/countstat.py @@ -189,7 +189,7 @@ def countstat_current_noise(L, c_ops, wlist=None, rhoss=None, J_ops=None, .. note:: The algoryth is described in page 67 of "Electrons in nanostructures" C. Flindt, PhD Thesis, available online: - https://orbit.dtu.dk/fedora/objects/orbit:82314/datastreams/file_4732600/content + https://orbit.dtu.dk/en/publications/electrons-in-nanostructures-coherent-manipulation-and-counting-st Returns -------- diff --git a/qutip/solver/floquet.py b/qutip/solver/floquet.py index 61c139d349..d18ae33421 100644 --- a/qutip/solver/floquet.py +++ b/qutip/solver/floquet.py @@ -751,7 +751,7 @@ class FMESolver(MESolver): name = "fmmesolve" _avail_integrators = {} - resultclass = FloquetResult + _resultclass = FloquetResult solver_options = { "progress_bar": "text", "progress_kwargs": {"chunk_size": 10}, @@ -907,7 +907,7 @@ def run(self, state0, tlist, *, floquet=False, args=None, e_ops=None): _data0 = self._prepare_state(state0) self._integrator.set_state(tlist[0], _data0) stats = self._initialize_stats() - results = self.resultclass( + results = self._resultclass( e_ops, self.options, solver=self.name, diff --git a/qutip/solver/heom/bofin_solvers.py b/qutip/solver/heom/bofin_solvers.py index d38553c902..ee9717f77e 100644 --- a/qutip/solver/heom/bofin_solvers.py +++ b/qutip/solver/heom/bofin_solvers.py @@ -136,8 +136,8 @@ def idx(self, label): int The index of the label within the list of ADO labels. - Note - ---- + .. note:: + This implementation of the ``.idx(...)`` method is just for reference and documentation. To avoid the cost of a Python function call, it is replaced with @@ -589,7 +589,7 @@ class HEOMSolver(Solver): """ name = "heomsolver" - resultclass = HEOMResult + _resultclass = HEOMResult _avail_integrators = {} solver_options = { "progress_bar": "text", @@ -903,17 +903,11 @@ def steady_state( Specifies the the maximum number of iterative refinement steps that the MKL PARDISO solver performs. - For a complete description, see iparm(8) in - http://cali2.unilim.fr/intel-xe/mkl/mklman/GUID-264E311E-ACED-4D56-AC31-E9D3B11D1CBF.htm. - mkl_weighted_matching : bool MKL PARDISO can use a maximum weighted matching algorithm to permute large elements close the diagonal. This strategy adds an additional level of reliability to the factorization methods. - For a complete description, see iparm(13) in - http://cali2.unilim.fr/intel-xe/mkl/mklman/GUID-264E311E-ACED-4D56-AC31-E9D3B11D1CBF.htm. - Returns ------- steady_state : Qobj diff --git a/qutip/solver/integrator/qutip_integrator.py b/qutip/solver/integrator/qutip_integrator.py index 03749a57bd..3b8f654baa 100644 --- a/qutip/solver/integrator/qutip_integrator.py +++ b/qutip/solver/integrator/qutip_integrator.py @@ -18,7 +18,7 @@ class IntegratorVern7(Integrator): sparse, GPU or other data layer objects to be used efficiently by the solver in their native formats. - See http://people.math.sfu.ca/~jverner/ for a detailed description of the + See https://www.sfu.ca/~jverner/ for a detailed description of the methods. Usable with ``method="vern7"`` @@ -111,7 +111,7 @@ class IntegratorVern9(IntegratorVern7): sparse, GPU or other data layer objects to be used efficiently by the solver in their native formats. - See http://people.math.sfu.ca/~jverner/ for a detailed description of the + See https://www.sfu.ca/~jverner/ for a detailed description of the methods. Usable with ``method="vern9"`` diff --git a/qutip/solver/integrator/scipy_integrator.py b/qutip/solver/integrator/scipy_integrator.py index dac1028a61..9eeeeac009 100644 --- a/qutip/solver/integrator/scipy_integrator.py +++ b/qutip/solver/integrator/scipy_integrator.py @@ -21,7 +21,7 @@ class IntegratorScipyAdams(Integrator): """ Integrator using Scipy `ode` with zvode integrator using adams method. Ordinary Differential Equation solver by netlib - (http://www.netlib.org/odepack). + (https://www.netlib.org/odepack). Usable with ``method="adams"`` """ @@ -197,7 +197,7 @@ class IntegratorScipyBDF(IntegratorScipyAdams): """ Integrator using Scipy `ode` with zvode integrator using bdf method. Ordinary Differential Equation solver by netlib - (http://www.netlib.org/odepack). + (https://www.netlib.org/odepack). Usable with ``method="bdf"`` """ @@ -356,7 +356,7 @@ def options(self, new_options): class IntegratorScipylsoda(IntegratorScipyDop853): """ Integrator using Scipy `ode` with lsoda integrator. ODE solver by netlib - (http://www.netlib.org/odepack) Automatically choose between 'Adams' and + (https://www.netlib.org/odepack) Automatically choose between 'Adams' and 'BDF' methods to solve both stiff and non-stiff systems. Usable with ``method="lsoda"`` diff --git a/qutip/solver/integrator/verner7efficient.py b/qutip/solver/integrator/verner7efficient.py index c49882a221..071fd99237 100644 --- a/qutip/solver/integrator/verner7efficient.py +++ b/qutip/solver/integrator/verner7efficient.py @@ -1,10 +1,10 @@ """ Provide a cython implimentation verner 'most-efficient' order 7 runge-Kutta method. -See http://people.math.sfu.ca/~jverner/ +See https://www.sfu.ca/~jverner/ """ # Verner 7 Efficient -# http://people.math.sfu.ca/~jverner/RKV76.IIa.Efficient.00001675585.081206.CoeffsOnlyFLOAT +# https://www.sfu.ca/~jverner/RKV76.IIa.Efficient.00001675585.081206.CoeffsOnlyFLOAT __all__ = ["vern7_coeff"] import numpy as np order = 7 diff --git a/qutip/solver/integrator/verner9efficient.py b/qutip/solver/integrator/verner9efficient.py index 02b1d92681..0112498cf3 100644 --- a/qutip/solver/integrator/verner9efficient.py +++ b/qutip/solver/integrator/verner9efficient.py @@ -1,10 +1,10 @@ """ Provide a cython implimentation verner 'most-efficient' order 9 runge-Kutta method. -See http://people.math.sfu.ca/~jverner/ +See https://www.sfu.ca/~jverner/ """ # Verner 9 Efficient -# http://people.math.sfu.ca/~jverner/ +# https://www.sfu.ca/~jverner/ __all__ = ["vern9_coeff"] import numpy as np diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index 043231196e..175c17bfd7 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -364,8 +364,8 @@ class MCSolver(MultiTrajSolver): Options for the evolution. """ name = "mcsolve" - trajectory_resultclass = McTrajectoryResult - mc_integrator_class = MCIntegrator + _trajectory_resultclass = McTrajectoryResult + _mc_integrator_class = MCIntegrator solver_options = { "progress_bar": "text", "progress_kwargs": {"chunk_size": 10}, @@ -444,7 +444,7 @@ def _argument(self, args): def _initialize_run_one_traj(self, seed, state, tlist, e_ops, no_jump=False, jump_prob_floor=0.0): - result = self.trajectory_resultclass(e_ops, self.options) + result = self._trajectory_resultclass(e_ops, self.options) generator = self._get_generator(seed) self._integrator.set_state(tlist[0], state, generator, no_jump=no_jump, @@ -520,14 +520,14 @@ def _get_integrator(self): else: raise ValueError("Integrator method not supported.") integrator_instance = integrator(self.rhs, self.options) - mc_integrator = self.mc_integrator_class( + mc_integrator = self._mc_integrator_class( integrator_instance, self._c_ops, self._n_ops, self.options ) self._init_integrator_time = time() - _time_start return mc_integrator @property - def resultclass(self): + def _resultclass(self): if self.options.get("improved_sampling", False): return McResultImprovedSampling else: diff --git a/qutip/solver/multitraj.py b/qutip/solver/multitraj.py index e422413afc..6fcb1abeb9 100644 --- a/qutip/solver/multitraj.py +++ b/qutip/solver/multitraj.py @@ -27,8 +27,8 @@ class MultiTrajSolver(Solver): Options for the solver. """ name = "generic multi trajectory" - resultclass = MultiTrajResult - trajectory_resultclass = Result + _resultclass = MultiTrajResult + _trajectory_resultclass = Result _avail_integrators = {} # Class of option used by the solver @@ -109,7 +109,7 @@ def _initialize_run(self, state, ntraj=1, args=None, e_ops=(), stats = self._initialize_stats() seeds = self._read_seed(seed, ntraj) - result = self.resultclass( + result = self._resultclass( e_ops, self.options, solver=self.name, stats=stats ) result.add_end_condition(ntraj, target_tol) @@ -206,7 +206,7 @@ def run(self, state, tlist, ntraj=1, *, return result def _initialize_run_one_traj(self, seed, state, tlist, e_ops): - result = self.trajectory_resultclass(e_ops, self.options) + result = self._trajectory_resultclass(e_ops, self.options) generator = self._get_generator(seed) self._integrator.set_state(tlist[0], state, generator) result.add(tlist[0], self._restore_state(state, copy=False)) diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index 1df49bdeb0..080a5e5aa9 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -324,7 +324,7 @@ class NonMarkovianMCSolver(MCSolver): seeds=prev_result.seeds """ name = "nm_mcsolve" - resultclass = NmmcResult + _resultclass = NmmcResult solver_options = { **MCSolver.solver_options, "completeness_rtol": 1e-5, @@ -334,7 +334,7 @@ class NonMarkovianMCSolver(MCSolver): del solver_options["improved_sampling"] # both classes will be partially initialized in constructor - trajectory_resultclass = NmmcTrajectoryResult + _trajectory_resultclass = NmmcTrajectoryResult mc_integrator_class = NmMCIntegrator def __init__( @@ -369,7 +369,7 @@ def __init__( for op, sqrt_shifted_rate in zip(self.ops, self._sqrt_shifted_rates) ] - self.trajectory_resultclass = functools.partial( + self._trajectory_resultclass = functools.partial( NmmcTrajectoryResult, __nm_solver=self, ) self.mc_integrator_class = functools.partial( diff --git a/qutip/solver/result.py b/qutip/solver/result.py index 7544d09761..a6d7fc9a26 100644 --- a/qutip/solver/result.py +++ b/qutip/solver/result.py @@ -363,7 +363,7 @@ class MultiTrajResult(_BaseResult): kw : dict Additional parameters specific to a result sub-class. - Properties + Attributes ---------- times : list A list of the times at which the expectation values and states were @@ -911,7 +911,7 @@ class McResult(MultiTrajResult): kw : dict Additional parameters specific to a result sub-class. - Properties + Attributes ---------- collapse : list For each runs, a list of every collapse as a tuple of the time it @@ -1204,7 +1204,7 @@ class NmmcResult(McResult): kw : dict Additional parameters specific to a result sub-class. - Properties + Attributes ---------- average_trace : list The average trace (i.e., averaged over all trajectories) at each time. diff --git a/qutip/solver/solver_base.py b/qutip/solver/solver_base.py index c8e743fea4..d41b793158 100644 --- a/qutip/solver/solver_base.py +++ b/qutip/solver/solver_base.py @@ -40,7 +40,7 @@ class Solver: "normalize_output": "ket", "method": "adams", } - resultclass = Result + _resultclass = Result def __init__(self, rhs, *, options=None): if isinstance(rhs, (QobjEvo, Qobj)): @@ -133,8 +133,8 @@ def run(self, state0, tlist, *, args=None, e_ops=None): values. Function[s] must have the signature f(t : float, state : Qobj) -> expect. - Return - ------ + Returns + ------- results : :class:`qutip.solver.Result` Results of the evolution. States and/or expect will be saved. You can control the saved data in the options. @@ -144,7 +144,7 @@ def run(self, state0, tlist, *, args=None, e_ops=None): self._integrator.set_state(tlist[0], _data0) self._argument(args) stats = self._initialize_stats() - results = self.resultclass( + results = self._resultclass( e_ops, self.options, solver=self.name, stats=stats, ) diff --git a/qutip/solver/steadystate.py b/qutip/solver/steadystate.py index 41fe9e4b63..85c9f01b71 100644 --- a/qutip/solver/steadystate.py +++ b/qutip/solver/steadystate.py @@ -453,8 +453,7 @@ def pseudo_inverse(L, rhoss=None, w=None, method='splu', *, use_rcm=False, cast the problem as an Ax=b type problem where the explicit calculation of the inverse is not required. See page 67 of "Electrons in nanostructures" C. Flindt, PhD Thesis available online: - https://orbit.dtu.dk/fedora/objects/orbit:82314/datastreams/ - file_4732600/content + https://orbit.dtu.dk/en/publications/electrons-in-nanostructures-coherent-manipulation-and-counting-st Note also that the definition of the pseudo-inverse herein is different from numpys pinv() alone, as it includes pre and post projection onto diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index 464adb65b2..aaa0865c95 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -472,7 +472,7 @@ class StochasticSolver(MultiTrajSolver): """ name = "StochasticSolver" - resultclass = StochasticResult + _resultclass = StochasticResult _avail_integrators = {} system = None solver_options = { From 6f2962cd5adcc1a92dff7983052a5e661f1e565e Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 6 Nov 2023 16:25:55 -0500 Subject: [PATCH 063/247] Add non Dia steadystate tests --- qutip/solver/steadystate.py | 8 ++++---- qutip/tests/solver/test_steadystate.py | 8 +++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/qutip/solver/steadystate.py b/qutip/solver/steadystate.py index 6ba1dd1df9..33f2a07c9e 100644 --- a/qutip/solver/steadystate.py +++ b/qutip/solver/steadystate.py @@ -220,14 +220,14 @@ def _steadystate_direct(A, weight, **kw): if isinstance(L, _data.CSR): L, b = _permute_wbm(L, b) else: - warn("Only CSR matrice can be permuted.", RuntimeWarning) + warn("Only CSR matrices can be permuted.", RuntimeWarning) use_rcm = False if kw.pop("use_rcm", False): if isinstance(L, _data.CSR): L, b, perm = _permute_rcm(L, b) use_rcm = True else: - warn("Only CSR matrice can be permuted.", RuntimeWarning) + warn("Only CSR matrices can be permuted.", RuntimeWarning) if kw.pop("use_precond", False): if isinstance(L, (_data.CSR, _data.Dia)): kw["M"] = _compute_precond(L, kw) @@ -276,14 +276,14 @@ def _steadystate_power(A, **kw): if isinstance(L, _data.CSR): L, y = _permute_wbm(L, y) else: - warn("Only CSR matrice can be permuted.", RuntimeWarning) + warn("Only CSR matrices can be permuted.", RuntimeWarning) use_rcm = False if kw.pop("use_rcm", False): if isinstance(L, _data.CSR): L, y, perm = _permute_rcm(L, y) use_rcm = True else: - warn("Only CSR matrice can be permuted.", RuntimeWarning) + warn("Only CSR matrices can be permuted.", RuntimeWarning) if kw.pop("use_precond", False): if isinstance(L, (_data.CSR, _data.Dia)): kw["M"] = _compute_precond(L, kw) diff --git a/qutip/tests/solver/test_steadystate.py b/qutip/tests/solver/test_steadystate.py index 6ae02a6489..74287a81b3 100644 --- a/qutip/tests/solver/test_steadystate.py +++ b/qutip/tests/solver/test_steadystate.py @@ -29,10 +29,12 @@ pytest.param('iterative-bicgstab', {'atol': 1e-12, "tol": 1e-10}, id="iterative-bicgstab"), ]) -def test_qubit(method, kwargs): +@pytest.mark.parametrize("dtype", ["dense", "dia", "csr"]) +@pytest.mark.filterwarnings("ignore:Only CSR matrices:RuntimeWarning") +def test_qubit(method, kwargs, dtype): # thermal steadystate of a qubit: compare numerics with analytical formula - sz = qutip.sigmaz() - sm = qutip.destroy(2) + sz = qutip.sigmaz().to(dtype) + sm = qutip.destroy(2, dtype=dtype) H = 0.5 * 2 * np.pi * sz gamma1 = 0.05 From 403029dc41753c7f2807ba9fc6de38e44f246398 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 7 Nov 2023 10:17:45 -0500 Subject: [PATCH 064/247] _data.solve mkl accept dia --- qutip/core/data/solve.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/qutip/core/data/solve.py b/qutip/core/data/solve.py index 31fc6c9659..db7abcafec 100644 --- a/qutip/core/data/solve.py +++ b/qutip/core/data/solve.py @@ -75,6 +75,9 @@ def solve_csr_dense(matrix: Union[CSR, Dia], target: Dense, method=None, raise ValueError("mkl is not available") elif method == "mkl_spsolve": solver = mkl_spsolve + # mkl does not support dia. + if isinstance(matrix, Dia): + matrix = _data.to("CSR", matrix) else: raise ValueError(f"Unknown sparse solver {method}.") From 71ed0c3c374ddd2ecf69b062775e98de41d8495f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Wed, 8 Nov 2023 10:19:38 -0500 Subject: [PATCH 065/247] Apply suggestions from code review Co-authored-by: Simon Cross --- qutip/core/cy/qobjevo.pyx | 44 ++++++++++++++++++++++----------------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 1b74397a18..7c495256b9 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -107,12 +107,16 @@ cdef class QobjEvo: https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.make_interp_spline.html feedback: dict - Set of arguments that update automatically when used in a solver. ie. - With ``args={"psi": psi0}, feedback={"psi": "qobj"}`` the arguments - ``psi`` will take the value of the ket at the time the operator is used - during the evolution of ``sesolve`` allowing non-linear Hamiltonian, - etc. See :meth:`QobjEvo.add_feedback` for more information. - Inserting an initial value in ``args`` is required. + A dictionary of arguments that update automatically when this + :obj:`QobjEvo` is used in a solver. For example, passing + `args={"psi": psi0}, feedback={"psi": "qobj"}` will result in the argument + `psi` being updated to the current value of the state each time the + operator is used during the evolution of `sesolve`. Feedback allows + implementing non-linear Hamiltonian and other exotic constructions. + See :meth:`QobjEvo.add_feedback` for more information on the kinds + of feedback supported. + If an argument is specified in `feedback`, an initial value for it is + required to be specified in `args`. Attributes ---------- @@ -461,28 +465,30 @@ cdef class QobjEvo: def add_feedback(QobjEvo self, str key, feedback): """ - Register an argument to be updated with the state during `matmul` and - `expect`. + Register an argument to be updated with the state when solving for the evolution of + a quantum system with a solver. - Equivalent to do: + In simple cases where the feedback argument is the current system state, + feedback is equivalent to calling: `solver.argument(key=state_t)` + within the solver at each time `t` that the solver calls the `QobjEvo`. Parameters ---------- key: str - Arguments key to update. + Name of the arguments to update with feedback. feedback: str, Qobj, QobjEvo - Format of the `state_t`. - - - "qobj": As a Qobj, either a ket or dm. - - "data": As a qutip data layer object. Density matrices will be - square matrix. - - Qobj, QobjEvo: The value is updated with the expectation value of - the given operator and the state. - - str: Other solver specific feedback. See corresponding solver's + The format of the feedback: + + - `"qobj"`: the `state` at time `t` as a `Qobj`. Either a ket or a density matrix, depending + on the solver. + - `"data"`: the `state` at time `t` as a QuTiP data layer object. Either a ket (column vector) or + density matrix (square matrix), depending on the solver. The type of the data layer object + depends on the solver and the system being solved. + - Qobj, QobjEvo: the expectation value of the given operator and the `state` at time `t`. + - Other `str` values: Solver specific feedback. See the corresponding solver's ``add_feedback`` function's documentation for available values. - """ if feedback == "data": self._feedback_functions[key] = _pass_through From 97edd7adf751842fd0b3d0ebef0dc3266d768333 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 8 Nov 2023 16:43:05 -0500 Subject: [PATCH 066/247] Fix issues from PR review comments in QobjEvo --- qutip/core/cy/qobjevo.pxd | 2 +- qutip/core/cy/qobjevo.pyx | 145 +++++++++++++++----------- qutip/solver/stochastic.py | 10 +- qutip/tests/solver/test_stochastic.py | 2 +- 4 files changed, 95 insertions(+), 64 deletions(-) diff --git a/qutip/core/cy/qobjevo.pxd b/qutip/core/cy/qobjevo.pxd index 3b86bc799c..da224649fd 100644 --- a/qutip/core/cy/qobjevo.pxd +++ b/qutip/core/cy/qobjevo.pxd @@ -13,7 +13,7 @@ cdef class QobjEvo: int _issuper int _isoper readonly dict _feedback_functions - readonly list _solver_only_feedback + readonly set _solver_only_feedback cpdef Data _call(QobjEvo self, double t) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 7c495256b9..089a2701b1 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -209,7 +209,10 @@ cdef class QobjEvo: self._issuper = ( Q_object)._issuper self._isoper = ( Q_object)._isoper self.elements = ( Q_object).elements.copy() - self._feedback_functions = Q_object._feedback_functions.copy() + if Q_object._feedback_functions: + self._feedback_functions = Q_object._feedback_functions.copy() + else: + self._feedback_functions = None self._solver_only_feedback = Q_object._solver_only_feedback.copy() if args: self.arguments(args) @@ -222,8 +225,8 @@ cdef class QobjEvo: self.shape = (0, 0) self._issuper = -1 self._isoper = -1 - self._feedback_functions = {} - self._solver_only_feedback = [] + self._feedback_functions = None + self._solver_only_feedback = set() args = args or {} if feedback is not None: for key, feed in feedback.items(): @@ -263,11 +266,15 @@ cdef class QobjEvo: cls = self.__class__.__name__ repr_str = f'{cls}: dims = {self.dims}, shape = {self.shape}, ' repr_str += f'type = {self.type}, superrep = {self.superrep}, ' - repr_str += f'isconstant = {self.isconstant}, num_elements = {self.num_elements}' + repr_str += f'isconstant = {self.isconstant}, ' + repr_str += f'num_elements = {self.num_elements}' + feedback_pairs = [pair for pair in self._solver_only_feedback] if self._feedback_functions: - repr_str += '\nQobjEvo depedent on the state when available.' - if self._solver_only_feedback: - repr_str += f'\nQobjEvo uses extra information from solver: {self._solver_only_feedback}' + for key, val in self._feedback_functions: + feedback_pairs.append((key, val)) + if feedback_pairs: + repr_str += f', feedback = {feedback_pairs}' + return repr_str def _read_element(self, op, copy, tlist, args, order, function_style, @@ -465,11 +472,11 @@ cdef class QobjEvo: def add_feedback(QobjEvo self, str key, feedback): """ - Register an argument to be updated with the state when solving for the evolution of - a quantum system with a solver. + Register an argument to be updated with the state when solving for the + evolution of a quantum system with a solver. - In simple cases where the feedback argument is the current system state, - feedback is equivalent to calling: + In simple cases where the feedback argument is the current system + state, feedback is equivalent to calling: `solver.argument(key=state_t)` within the solver at each time `t` that the solver calls the `QobjEvo`. @@ -481,17 +488,24 @@ cdef class QobjEvo: feedback: str, Qobj, QobjEvo The format of the feedback: - - `"qobj"`: the `state` at time `t` as a `Qobj`. Either a ket or a density matrix, depending - on the solver. - - `"data"`: the `state` at time `t` as a QuTiP data layer object. Either a ket (column vector) or - density matrix (square matrix), depending on the solver. The type of the data layer object - depends on the solver and the system being solved. - - Qobj, QobjEvo: the expectation value of the given operator and the `state` at time `t`. - - Other `str` values: Solver specific feedback. See the corresponding solver's - ``add_feedback`` function's documentation for available values. + - `"qobj"`: the `state` at time `t` as a `Qobj`. Either a ket or a + density matrix, depending on the solver. + - `"data"`: the `state` at time `t` as a QuTiP data layer object. + Usually a ket or colunm-stacked density matrix (column vectors), + but can be a density matrix or operator (square matrices) + depending on the solver, integration method and initial state. + The type of the data layer object depends on the solver and the + system being solved. + - Qobj, QobjEvo: the expectation value of the given operator and + the `state` at time `t`. + - Other `str` values: Solver specific feedback. See the + corresponding solver's ``add_feedback`` function's documentation + for available values. """ + if self._feedback_functions is None: + self._feedback_functions = {} if feedback == "data": - self._feedback_functions[key] = _pass_through + self._feedback_functions[key] = _Pass_Through() elif feedback in ["qobj", "Qobj"]: self._feedback_functions[key] = _To_Qobj(self) elif isinstance(feedback, (Qobj, QobjEvo)): @@ -499,13 +513,22 @@ cdef class QobjEvo: feedback = QobjEvo(feedback) self._feedback_functions[key] = _Expect(feedback) elif isinstance(feedback, str): - self._solver_only_feedback.append((key, feedback)) + self._solver_only_feedback.add((key, feedback)) else: raise ValueError("feedback not understood.") - def _register_feedback(self, solvers_feeds, solver="solver"): + def _register_feedback(self, solvers_feeds, solver): """ - Function to receive feedback source from + Receive feedback source from solver. + + Parameters + ---------- + solvers_feeds : dict[str] + When ``feedback={key: solver_specific}`` is used, update arguments + with ``args[key] = solvers_feeds[solver_specific]``. + + solver: str + Name of the solver for the error message. """ new_args = {} for key, feed in self._solver_only_feedback: @@ -516,6 +539,24 @@ cdef class QobjEvo: new_args[key] = solvers_feeds[feed] self.arguments(**new_args) + def _update_feedback(QobjEvo self, QobjEvo other=None): + """ + Merge feedback from ``op`` into self. + """ + if other is not None: + if self._feedback_functions is None and other._feedback_functions: + self._feedback_functions = other._feedback_functions.copy() + elif other._feedback_functions: + self._feedback_functions.update(other._feedback_functions) + self._solver_only_feedback |= other._solver_only_feedback + + if self._feedback_functions is not None: + for key, func in self._feedback_functions.items(): + # Update dims in ``_To_Qobj`` + if isinstance(func, _To_Qobj): + self._feedback_functions[key] = _To_Qobj(self) + + ########################################################################### # Math function # ########################################################################### @@ -547,8 +588,8 @@ cdef class QobjEvo: str(self.dims) + ", " + str(other.dims)) for element in ( other).elements: self.elements.append(element) - self._feedback_functions.update(other._feedback_functions) - self._solver_only_feedback += other._solver_only_feedback + self._update_feedback(other) + elif isinstance(other, Qobj): if other.dims != self.dims: raise TypeError("incompatible dimensions" + @@ -611,14 +652,8 @@ cdef class QobjEvo: res.elements = [left @ element for element in res.elements] res._issuper = -1 res._isoper = -1 + res._update_feedback() - new_feed = {} - for key, func in right._feedback_functions.items(): - if isinstance(func, _To_Qobj): - new_feed[key] = _To_Qobj(res) - else: - new_feed[key] = func - res._feedback_functions = new_feed return res else: @@ -647,14 +682,7 @@ cdef class QobjEvo: res.elements = [other @ element for element in res.elements] res._issuper = -1 res._isoper = -1 - - new_feed = {} - for key, func in self._feedback_functions.items(): - if isinstance(func, _To_Qobj): - new_feed[key] = _To_Qobj(res) - else: - new_feed[key] = func - res._feedback_functions = new_feed + res._update_feedback() return res else: return NotImplemented @@ -681,23 +709,14 @@ cdef class QobjEvo: if isinstance(other, Qobj): other = _ConstantElement(other) self.elements = [element @ other for element in self.elements] + self._update_feedback() elif isinstance(other, QobjEvo): self.elements = [left @ right for left, right in itertools.product( self.elements, ( other).elements )] - - self._feedback_functions.update(other._feedback_functions) - self._solver_only_feedback += other._solver_only_feedback - - new_feed = {} - for key, func in self._feedback_functions.items(): - if isinstance(func, _To_Qobj): - new_feed[key] = _To_Qobj(res) - else: - new_feed[key] = func - self._feedback_functions = new_feed + self._update_feedback(other) else: return NotImplemented @@ -853,12 +872,7 @@ cdef class QobjEvo: res.type = res.elements[0].qobj(0).type res._issuper = res.elements[0].qobj(0).issuper res._isoper = res.elements[0].qobj(0).isoper - res._feedback_functions = {} - for key, func in self._feedback_functions.items(): - if isinstance(func, _To_Qobj): - res._feedback_functions[key] = _To_Qobj(res) - else: - res._feedback_functions[key] = func + res._update_feedback() if not _skip_check: if res(0) != out: @@ -1160,6 +1174,9 @@ cdef class _Expect: f"does not match the state ({state.shape})." ) + def __repr__(self): + return "Expect" + cdef class _To_Qobj: cdef list dims, dims_flat @@ -1186,6 +1203,16 @@ cdef class _To_Qobj: out = Qobj(state, dims=[self.dims[1], [state.shape[1]]]) return out + def __repr__(self): + return "Qobj" + -def _pass_through(t, state): - return state +cdef class _Pass_Through: + def __init__(self): + pass + + def __call__(self, t, state): + return state + + def __repr__(self): + return "data" diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index b9b5e61ef3..f4efc471af 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -260,11 +260,15 @@ def add_feedback(self, key, type): sc_op.add_feedback(key, type) def register_feedback(self, val): - self.H._register_feedback({"wiener_process": val}) + self.H._register_feedback({"wiener_process": val}, "stochatic solver") for c_op in self.c_ops: - c_op._register_feedback({"wiener_process": val}) + c_op._register_feedback( + {"wiener_process": val}, "stochatic solver" + ) for sc_op in self.sc_ops: - sc_op._register_feedback({"wiener_process": val}) + sc_op._register_feedback( + {"wiener_process": val}, "stochatic solver" + ) def smesolve( diff --git a/qutip/tests/solver/test_stochastic.py b/qutip/tests/solver/test_stochastic.py index eb662c9704..6a78f43322 100644 --- a/qutip/tests/solver/test_stochastic.py +++ b/qutip/tests/solver/test_stochastic.py @@ -321,7 +321,7 @@ def test_m_ops(heterodyne): def test_feedback(): tol = 0.05 N = 10 - ntraj = 5 + ntraj = 2 def func(t, A, W): return (A - 6) * (A.real > 6.) * W(t)[0] From 85a6fbeb76fa80d51beb7f4311070af826c5643a Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 9 Nov 2023 11:15:19 -0500 Subject: [PATCH 067/247] fix doc build --- qutip/core/cy/qobjevo.pyx | 16 +++++++++------- qutip/solver/stochastic.py | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 089a2701b1..8ec0150403 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -109,12 +109,12 @@ cdef class QobjEvo: feedback: dict A dictionary of arguments that update automatically when this :obj:`QobjEvo` is used in a solver. For example, passing - `args={"psi": psi0}, feedback={"psi": "qobj"}` will result in the argument - `psi` being updated to the current value of the state each time the - operator is used during the evolution of `sesolve`. Feedback allows - implementing non-linear Hamiltonian and other exotic constructions. - See :meth:`QobjEvo.add_feedback` for more information on the kinds - of feedback supported. + `args={"psi": psi0}, feedback={"psi": "qobj"}` will result in the + argument `psi` being updated to the current value of the state each + time the operator is used during the evolution of `sesolve`. Feedback + allows implementing non-linear Hamiltonian and other exotic + constructions. See :meth:`QobjEvo.add_feedback` for more information on + the kinds of feedback supported. If an argument is specified in `feedback`, an initial value for it is required to be specified in `args`. @@ -477,7 +477,9 @@ cdef class QobjEvo: In simple cases where the feedback argument is the current system state, feedback is equivalent to calling: - `solver.argument(key=state_t)` + + ```solver.argument(key=state_t)`` + within the solver at each time `t` that the solver calls the `QobjEvo`. Parameters diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index f4efc471af..a90010b064 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -212,7 +212,7 @@ def __init__(self, issuper, H, sc_ops, c_ops, heterodyne): else: self.dims = self.H.dims - def __call__(self, options={}): + def __call__(self, options): if self.issuper: return StochasticOpenSystem( self.H, self.sc_ops, self.c_ops, options.get("derr_dt", 1e-6) From cfbd72d5a0f18249d233e2469b1f2de4ad848ac4 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 9 Nov 2023 13:39:27 -0500 Subject: [PATCH 068/247] Faster feedback --- qutip/core/cy/_element.pyx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qutip/core/cy/_element.pyx b/qutip/core/cy/_element.pyx index de2f574690..0313902a6d 100644 --- a/qutip/core/cy/_element.pyx +++ b/qutip/core/cy/_element.pyx @@ -263,7 +263,8 @@ cdef class _BaseElement: def __call__(self, t, args=None): if args: cache = [] - self = self.replace_arguments(args, cache) + new = self.replace_arguments(args, cache) + return new.qobj(t) * new.coeff(t) return self.qobj(t) * self.coeff(t) @@ -365,7 +366,7 @@ cdef class _EvoElement(_BaseElement): def replace_arguments(self, args, cache=None): return _EvoElement( - self._qobj.copy(), + self._qobj, self._coefficient.replace_arguments(args) ) From cbdf1cfb43ac0e668e16dc0ff6af5ba50b6b1b7a Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 10 Nov 2023 18:06:18 -0500 Subject: [PATCH 069/247] Fix internal link and note formating --- doc/apidoc/classes.rst | 38 +++++++- doc/apidoc/functions.rst | 2 +- .../dynamics/dynamics-bloch-redfield.rst | 6 +- doc/guide/dynamics/dynamics-intro.rst | 40 ++++---- doc/guide/dynamics/dynamics-monte.rst | 10 +- doc/guide/dynamics/dynamics-time.rst | 44 ++++----- doc/guide/guide-correlation.rst | 4 +- qutip/bloch.py | 18 ++-- qutip/bloch3d.py | 11 +-- qutip/core/_brtools.pyx | 2 +- qutip/core/blochredfield.py | 22 ++--- qutip/core/coefficient.py | 10 +- qutip/core/cy/_element.pyx | 38 ++++---- qutip/core/cy/coefficient.pyx | 60 ++++++------ qutip/core/cy/qobjevo.pyx | 74 ++++++++------- qutip/core/data/convert.pyx | 6 +- qutip/core/gates.py | 16 ++-- qutip/core/metrics.py | 14 +-- qutip/core/operators.py | 8 +- qutip/core/options.py | 10 +- qutip/core/qobj.py | 42 ++++----- qutip/core/states.py | 16 ++-- qutip/core/subsystem_apply.py | 6 +- qutip/core/tensor.py | 10 +- qutip/entropy.py | 4 +- qutip/legacy/nonmarkov/memorycascade.py | 26 +++--- qutip/legacy/rcsolve.py | 2 +- qutip/partial_transpose.py | 4 +- qutip/piqs/_piqs.pyx | 2 +- qutip/piqs/piqs.py | 52 +++++------ qutip/random_objects.py | 2 +- qutip/solver/brmesolve.py | 46 ++++----- qutip/solver/correlation.py | 66 ++++++------- qutip/solver/countstat.py | 8 +- qutip/solver/cy/nm_mcsolve.pyx | 10 +- qutip/solver/floquet.py | 93 ++++++++++--------- qutip/solver/floquet_bwcomp.py | 22 ++--- qutip/solver/heom/bofin_solvers.py | 42 ++++----- qutip/solver/integrator/explicit_rk.pyx | 2 +- qutip/solver/integrator/integrator.py | 2 +- qutip/solver/krylovsolve.py | 12 +-- qutip/solver/mcsolve.py | 14 +-- qutip/solver/mesolve.py | 40 ++++---- qutip/solver/multitraj.py | 12 +-- qutip/solver/nm_mcsolve.py | 18 ++-- qutip/solver/nonmarkov/transfertensor.py | 14 +-- qutip/solver/propagator.py | 45 +++++---- qutip/solver/result.py | 57 ++++++------ qutip/solver/scattering.py | 18 ++-- qutip/solver/sesolve.py | 28 +++--- qutip/solver/sode/rouchon.py | 12 +-- qutip/solver/solver_base.py | 29 +++--- qutip/solver/spectrum.py | 6 +- qutip/solver/steadystate.py | 47 +++++----- qutip/solver/stochastic.py | 52 +++++------ qutip/visualization.py | 2 +- qutip/wigner.py | 2 +- 57 files changed, 673 insertions(+), 625 deletions(-) diff --git a/doc/apidoc/classes.rst b/doc/apidoc/classes.rst index 270e6c4acc..747834ab1c 100644 --- a/doc/apidoc/classes.rst +++ b/doc/apidoc/classes.rst @@ -59,17 +59,20 @@ Solvers :inherited-members: :show-inheritance: - -.. autoclass:: qutip.solver.stochastic.SMESolver +.. autoclass:: qutip.solver.floquet.FMESolver :members: :inherited-members: :show-inheritance: -.. autoclass:: qutip.solver.stochastic.SSESolver +.. autoclass:: qutip.solver.floquet.FloquetBasis :members: :inherited-members: :show-inheritance: +.. autoclass:: qutip.solver.propagator.Propagator + :members: + :inherited-members: + :show-inheritance: .. _classes-monte-carlo-solver: @@ -136,6 +139,22 @@ Non-Markovian HEOM Solver :members: +.. _classes-stochastic: + +Stochastic Solver +----------------- + +.. autoclass:: qutip.solver.stochastic.SMESolver + :members: + :inherited-members: + :show-inheritance: + +.. autoclass:: qutip.solver.stochastic.SSESolver + :members: + :inherited-members: + :show-inheritance: + + .. _classes-ode: Integrator @@ -206,6 +225,19 @@ Solver Options and Results .. autoclass:: qutip.solver.result.Result :members: + :inherited-members: + +.. autoclass:: qutip.solver.result.MultiTrajResult + :members: + :inherited-members: + +.. autoclass:: qutip.solver.result.McResult + :members: + :show-inheritance: + +.. autoclass:: qutip.solver.result.NmmcResult + :members: + :show-inheritance: .. _classes-piqs: diff --git a/doc/apidoc/functions.rst b/doc/apidoc/functions.rst index 3c781b4200..21ac49a458 100644 --- a/doc/apidoc/functions.rst +++ b/doc/apidoc/functions.rst @@ -158,7 +158,7 @@ Floquet States and Floquet-Markov Master Equation ------------------------------------------------- .. automodule:: qutip.solver.floquet - :members: fmmesolve, fsesolve, FloquetBasis, FMESolver, floquet_tensor + :members: fmmesolve, fsesolve, floquet_tensor Stochastic Schrödinger Equation and Master Equation diff --git a/doc/guide/dynamics/dynamics-bloch-redfield.rst b/doc/guide/dynamics/dynamics-bloch-redfield.rst index ceaf9a997c..93f4b2db9d 100644 --- a/doc/guide/dynamics/dynamics-bloch-redfield.rst +++ b/doc/guide/dynamics/dynamics-bloch-redfield.rst @@ -290,7 +290,7 @@ where the resulting `output` is an instance of the class :class:`qutip.Result`. :: output = brmesolve(H, psi0, tlist, a_ops=[[sigmax(), ohmic_spectrum]], e_ops=e_ops, sec_cutoff=-1) - + will simulate the same example as above without the secular approximation. Note that using the non-secular version may lead to negativity issues. .. _td-bloch-redfield: @@ -394,9 +394,9 @@ A full example is: plt.figure() - plt.plot(times,res_brme.expect[0], label=r'$a^{+}a$') + plt.plot(times, res_brme.expect[0], label=r'$a^{+}a$') - plt.plot(times,res_brme.expect[1], label=r'$a+a^{+}$') + plt.plot(times, res_brme.expect[1], label=r'$a+a^{+}$') plt.legend() diff --git a/doc/guide/dynamics/dynamics-intro.rst b/doc/guide/dynamics/dynamics-intro.rst index b74663ab0a..dab7359a11 100644 --- a/doc/guide/dynamics/dynamics-intro.rst +++ b/doc/guide/dynamics/dynamics-intro.rst @@ -4,27 +4,27 @@ Introduction ************ -Although in some cases, we want to find the stationary states of -a quantum system, often we are interested in the dynamics: -how the state of a system or an ensemble of systems evolves with time. QuTiP provides -many ways to model dynamics. - -Broadly speaking, there are two categories -of dynamical models: unitary and non-unitary. In unitary evolution, -the state of the system remains normalized. In non-unitary, or -dissipative, systems, it does not. - -There are two kinds of quantum systems: open systems that interact -with a larger environment and closed systems that do not. -In a closed system, the state can be described by a state vector, -although when there is entanglement a density matrix may be -needed instead. When we are modeling an open system, or an ensemble +Although in some cases, we want to find the stationary states of +a quantum system, often we are interested in the dynamics: +how the state of a system or an ensemble of systems evolves with time. QuTiP provides +many ways to model dynamics. + +Broadly speaking, there are two categories +of dynamical models: unitary and non-unitary. In unitary evolution, +the state of the system remains normalized. In non-unitary, or +dissipative, systems, it does not. + +There are two kinds of quantum systems: open systems that interact +with a larger environment and closed systems that do not. +In a closed system, the state can be described by a state vector, +although when there is entanglement a density matrix may be +needed instead. When we are modeling an open system, or an ensemble of systems, the use of the density matrix is mandatory. -Collapse operators are used to model the collapse of the state vector +Collapse operators are used to model the collapse of the state vector that can occur when a measurement is performed. -The following tables lists some of the solvers QuTiP provides for dynamic quantum systems and indicates the type of object +The following tables lists some of the solvers QuTiP provides for dynamic quantum systems and indicates the type of object returned by the solver: .. list-table:: QuTiP Solvers @@ -47,17 +47,17 @@ returned by the solver: - Array of expectation values - Exponential series with collapse operators * - bloch_redfield_solve() - - :func:`qutip.solver` + - :func:`.solver` - * - floquet_markov_solve() - :func:`qutip.solver.Result` - Floquet-Markov master equation * - fmmesolve() - - :func:`qutip.solver` + - :func:`.solver` - Floquet-Markov master equation * - smesolve() - :func:`qutip.solver.Result` - Stochastic master equation * - ssesolve() - :func:`qutip.solver.Result` - - Stochastic Schrödinger equation \ No newline at end of file + - Stochastic Schrödinger equation diff --git a/doc/guide/dynamics/dynamics-monte.rst b/doc/guide/dynamics/dynamics-monte.rst index 5dda6b35eb..7eb83bfbf6 100644 --- a/doc/guide/dynamics/dynamics-monte.rst +++ b/doc/guide/dynamics/dynamics-monte.rst @@ -184,8 +184,13 @@ For instance, consider the following T1 simulation of a qubit with a lifetime of omega = 2.0 * np.pi * 1.0 H0 = -0.5 * omega * sigmaz() gamma = 1/10000 - data = mcsolve([H0], psi0, times, [np.sqrt(gamma) * sm], [sm.dag() * sm], ntraj=100) - data_imp = mcsolve([H0], psi0, times, [np.sqrt(gamma) * sm], [sm.dag() * sm],ntraj=100, options={"improved_sampling": True}) + data = mcsolve( + [H0], psi0, times, [np.sqrt(gamma) * sm], [sm.dag() * sm], ntraj=100 + ) + data_imp = mcsolve( + [H0], psi0, times, [np.sqrt(gamma) * sm], [sm.dag() * sm], ntraj=100, + options={"improved_sampling": True} + ) plt.figure() plt.plot(times, data.expect[0], label="original") @@ -322,7 +327,6 @@ This is done by using a liouvillian including the dissipative interaction instea plt.show() - .. _monte-nonmarkov: Monte Carlo for Non-Markovian Dynamics diff --git a/doc/guide/dynamics/dynamics-time.rst b/doc/guide/dynamics/dynamics-time.rst index 4885803a65..85f3d613d2 100644 --- a/doc/guide/dynamics/dynamics-time.rst +++ b/doc/guide/dynamics/dynamics-time.rst @@ -13,11 +13,11 @@ we assumed that the systems under consideration were described by time-independe However, many systems have explicit time dependence in either the Hamiltonian, or the collapse operators describing coupling to the environment, and sometimes both components might depend on time. The time-evolutions solvers such as :func:`sesolve`, :func:`brmesolve`, etc. are all capable of handling time-dependent Hamiltonians and collapse terms. -QuTiP use :class:`QobjEvo` to represent time-dependent quantum operators. -There are three different ways to build a :class:`QobjEvo`: : +QuTiP use :obj:`.QobjEvo` to represent time-dependent quantum operators. +There are three different ways to build a :obj:`.QobjEvo`: : -1. **Function based**: Build the time dependent operator from a function returning a :class:`Qobj`: +1. **Function based**: Build the time dependent operator from a function returning a :obj:`.Qobj`: .. code-block:: python @@ -32,7 +32,7 @@ There are three different ways to build a :class:`QobjEvo`: : H_t = QobjEvo([num(N), [create(N), lambda t: np.sin(t)], [destroy(N), lambda t: np.sin(t)]]) -3. **coefficent based**: The product of a :class:`Qobj` with a :class:`Coefficient` result in a :class:`QobjEvo`: +3. **coefficent based**: The product of a :obj:`.Qobj` with a :obj:`.Coefficient` result in a :obj:`.QobjEvo`: .. code-block:: python @@ -42,11 +42,11 @@ There are three different ways to build a :class:`QobjEvo`: : These 3 examples will create the same time dependent operator, however the function based method will usually be slower when used in solver. -Solvers will accept a :class:`QobjEvo`: when an operator is expected: this include the Hamiltonian ``H``, collapse operators, expectation values operators, the operator of :func:`brmesolve`'s ``a_ops``, etc. +Solvers will accept a :obj:`.QobjEvo`: when an operator is expected: this include the Hamiltonian ``H``, collapse operators, expectation values operators, the operator of :func:`brmesolve`'s ``a_ops``, etc. Exception are :func:`krylovsolve`'s Hamiltonian and HEOM's Bath operators. -Most solvers will accept any format that could be made into a :class:`QobjEvo`: for the Hamiltonian. +Most solvers will accept any format that could be made into a :obj:`.QobjEvo`: for the Hamiltonian. All of the following are equivalent: @@ -57,7 +57,7 @@ All of the following are equivalent: result = mesolve(oper, ...) -Collapse operator also accept a list of object that could be made into :class:`QobjEvo`:. +Collapse operator also accept a list of object that could be made into :obj:`.QobjEvo`:. However one needs to be careful about not confusing the list nature of the `c_ops` parameter with list format quantum system. In the following call: @@ -66,7 +66,7 @@ In the following call: result = mesolve(H_t, ..., c_ops=[num(N), [destroy(N) + create(N), lambda t: np.sin(t)]]) :func:`mesolve` will see 2 collapses operators: ``num(N)`` and ``[destroy(N) + create(N), lambda t: np.sin(t)]``. -It is therefore preferred to pass each collapse operator as either a :class:`Qobj`: or a :class:`QobjEvo`:. +It is therefore preferred to pass each collapse operator as either a :obj:`.Qobj`: or a :obj:`.QobjEvo`:. As an example, we will look at a case with a time-dependent Hamiltonian of the form :math:`H=H_{0}+f(t)H_{1}` where :math:`f(t)` is the time-dependent driving strength given as :math:`f(t)=A\exp\left[-\left( t/\sigma \right)^{2}\right]`. @@ -157,7 +157,7 @@ In addition, we can also consider the decay of a simple Harmonic oscillator with Qobjevo ======= -:class:`QobjEvo` as a time dependent quantum system, as it's main functionality create a :class:`Qobj` at a time: +:obj:`.QobjEvo` as a time dependent quantum system, as it's main functionality create a :obj:`.Qobj` at a time: .. doctest:: [basics] :options: +NORMALIZE_WHITESPACE @@ -169,7 +169,7 @@ Qobjevo [1. 1.]] -:class:`QobjEvo` shares a lot of properties with the :class:`Qobj`. +:obj:`.QobjEvo` shares a lot of properties with the :obj:`.Qobj`. +---------------+------------------+----------------------------------------+ | Property | Attribute | Description | @@ -188,7 +188,7 @@ Qobjevo +---------------+------------------+----------------------------------------+ -:class:`QobjEvo`'s follow the same mathematical operations rules than :class:`Qobj`. +:obj:`.QobjEvo`'s follow the same mathematical operations rules than :obj:`.Qobj`. They can be added, subtracted and multiplied with scalar, ``Qobj`` and ``QobjEvo``. They also support the `dag` and `trans` and `conj` method and can be used for tensor operations and super operator transformation: @@ -215,7 +215,7 @@ Until now, the coefficient were only functions of time. In the definition of ``H1_coeff``, the driving amplitude ``A`` and width ``sigma`` were hardcoded with their numerical values. This is fine for problems that are specialized, or that we only want to run once. However, in many cases, we would like study the same problem with a range of parameters and not have to worry about manually changing the values on each run. -QuTiP allows you to accomplish this using by adding extra arguments to coefficients function that make the :class:`QobjEvo`. +QuTiP allows you to accomplish this using by adding extra arguments to coefficients function that make the :obj:`.QobjEvo`. For instance, instead of explicitly writing 9 for the amplitude and 5 for the width of the gaussian driving term, we can add an `args` positional variable: @@ -239,7 +239,7 @@ or, new from v5, add the extra parameter directly: When the second positional input of the coefficient function is named ``args``, the arguments are passed as a Python dictionary of ``key: value`` pairs. Otherwise the coefficient function is called as ``coeff(t, **args)``. In the last example, ``args = {'A': a, 'sigma': b}`` where ``a`` and ``b`` are the two parameters for the amplitude and width, respectively. -This ``args`` dictionary need to be given at creation of the :class:`QobjEvo` when function using then are included: +This ``args`` dictionary need to be given at creation of the :obj:`.QobjEvo` when function using then are included: .. plot:: :context: close-figs @@ -248,7 +248,7 @@ This ``args`` dictionary need to be given at creation of the :class:`QobjEvo` wh args={'A': 9, 'sigma': 5} qevo = QobjEvo(system, args=args) -But without ``args``, the :class:`QobjEvo` creation will fail: +But without ``args``, the :obj:`.QobjEvo` creation will fail: .. plot:: :context: close-figs @@ -258,7 +258,7 @@ But without ``args``, the :class:`QobjEvo` creation will fail: except TypeError as err: print(err) -When evaluation the :class:`QobjEvo` at a time, new arguments can be passed either with the ``args`` dictionary positional arguments, or with specific keywords arguments: +When evaluation the :obj:`.QobjEvo` at a time, new arguments can be passed either with the ``args`` dictionary positional arguments, or with specific keywords arguments: .. plot:: :context: close-figs @@ -272,7 +272,7 @@ Whether the original coefficient used the ``args`` or specific input does not ma It is fine to mix the different signatures. Solver calls take an ``args`` input that is used to build the time dependent system. -If the Hamiltonian or collapse operators are already :class:`QobjEvo`, their arguments will be overwritten. +If the Hamiltonian or collapse operators are already :obj:`.QobjEvo`, their arguments will be overwritten. .. code-block:: python @@ -282,7 +282,7 @@ If the Hamiltonian or collapse operators are already :class:`QobjEvo`, their arg mesolve(system, ..., args=args) -To update arguments of an existing time dependent quantum system, you can pass the previous object as the input of a :class:`QobjEvo` with new ``args``: +To update arguments of an existing time dependent quantum system, you can pass the previous object as the input of a :obj:`.QobjEvo` with new ``args``: .. plot:: @@ -294,7 +294,7 @@ To update arguments of an existing time dependent quantum system, you can pass t print(new_qevo(1)) -:class:`QobjEvo` created from a monolithic function can also use arguments: +:obj:`.QobjEvo` created from a monolithic function can also use arguments: .. code-block:: python @@ -305,7 +305,7 @@ To update arguments of an existing time dependent quantum system, you can pass t H_t = QobjEvo(oper, args={"w": np.pi}) -When merging two or more :class:`QobjEvo`, each will keep it arguments, but calling it with updated are will affect all parts: +When merging two or more :obj:`.QobjEvo`, each will keep it arguments, but calling it with updated are will affect all parts: .. plot:: @@ -321,7 +321,7 @@ When merging two or more :class:`QobjEvo`, each will keep it arguments, but call Coefficients ============ -To build time dependent quantum system we often use a list of :class:`Qobj` and *coefficient*. +To build time dependent quantum system we often use a list of :obj:`.Qobj` and *coefficient*. These *coefficients* represent the strength of the corresponding quantum object a function that of time. Up to now, we used functions for these, but QuTiP support multiple formats: ``callable``, ``strings``, ``array``. @@ -417,7 +417,7 @@ It is therefore better to create the QobjEvo using an extended range prior to th ) -Different coefficient types can be mixed in a :class:`QobjEvo`. +Different coefficient types can be mixed in a :obj:`.QobjEvo`. Given the multiple choices of input style, the first question that arises is which option to choose? @@ -434,7 +434,7 @@ Lastly the spline method is usually as fast the string method, but it cannot be Accessing the state from solver =============================== -In QuTiP 4.4 to 4.7, it was possible to request that the solver pass the state, expectation values or collapse operators via arguments to :class:`QobjEvo`. Support for this is not yet available in QuTiP 5. +In QuTiP 4.4 to 4.7, it was possible to request that the solver pass the state, expectation values or collapse operators via arguments to :obj:`.QobjEvo`. Support for this is not yet available in QuTiP 5. .. plot:: :context: reset diff --git a/doc/guide/guide-correlation.rst b/doc/guide/guide-correlation.rst index 6353077608..2ca650263f 100644 --- a/doc/guide/guide-correlation.rst +++ b/doc/guide/guide-correlation.rst @@ -67,7 +67,9 @@ The following code demonstrates how to calculate the :math:`\left$') diff --git a/qutip/bloch.py b/qutip/bloch.py index 05718de4e6..26f77a48e7 100644 --- a/qutip/bloch.py +++ b/qutip/bloch.py @@ -159,11 +159,11 @@ def __init__(self, fig=None, axes=None, view=None, figsize=None, self.vector_default_color = ['g', '#CC6600', 'b', 'r'] # List that stores the display colors for each vector self.vector_color = [] - #: Width of Bloch vectors, default = 5 + # Width of Bloch vectors, default = 5 self.vector_width = 3 - #: Style of Bloch vectors, default = '-\|>' (or 'simple') + # Style of Bloch vectors, default = '-\|>' (or 'simple') self.vector_style = '-|>' - #: Sets the width of the vectors arrowhead + # Sets the width of the vectors arrowhead self.vector_mutation = 20 # ---point options--- @@ -331,12 +331,12 @@ def add_points(self, points, meth='s', colors=None, alpha=1.0): alpha : float, default=1. Transparency value for the vectors. Values between 0 and 1. - .. note:: - - When using ``meth=l`` in QuTiP 4.6, the line transparency defaulted - to ``0.75`` and there was no way to alter it. - When the ``alpha`` parameter was added in QuTiP 4.7, the default - became ``alpha=1.0`` for values of ``meth``. + Notes + ----- + When using ``meth=l`` in QuTiP 4.6, the line transparency defaulted + to ``0.75`` and there was no way to alter it. + When the ``alpha`` parameter was added in QuTiP 4.7, the default + became ``alpha=1.0`` for values of ``meth``. """ points = np.asarray(points) diff --git a/qutip/bloch3d.py b/qutip/bloch3d.py index d387b6c4d3..99458e0793 100644 --- a/qutip/bloch3d.py +++ b/qutip/bloch3d.py @@ -5,7 +5,7 @@ class Bloch3d: - """Class for plotting data on a 3D Bloch sphere using mayavi. + r"""Class for plotting data on a 3D Bloch sphere using mayavi. Valid data can be either points, vectors, or qobj objects corresponding to state vectors or density matrices. for a two-state system (or subsystem). @@ -39,7 +39,7 @@ class Bloch3d: Transparency of Bloch sphere itself. sphere_color : str {'#808080'} Color of Bloch sphere. - size : list {[500,500]} + size : list {[500, 500]} Size of Bloch sphere plot in pixels. Best to have both numbers the same otherwise you will have a Bloch sphere that looks like a football. vector_color : list {['r', 'g', 'b', 'y']} @@ -48,15 +48,15 @@ class Bloch3d: Width of displayed vectors. view : list {[45,65]} Azimuthal and Elevation viewing angles. - xlabel : list {``['|x>', '']``} + xlabel : list {['|x>', '']} List of strings corresponding to +x and -x axes labels, respectively. xlpos : list {[1.07,-1.07]} Positions of +x and -x labels respectively. - ylabel : list {``['|y>', '']``} + ylabel : list {['|y>', '']} List of strings corresponding to +y and -y axes labels, respectively. ylpos : list {[1.07,-1.07]} Positions of +y and -y labels respectively. - zlabel : list {``['|0>', '|1>']``} + zlabel : list {['|0>', '|1>']} List of strings corresponding to +z and -z axes labels, respectively. zlpos : list {[1.07,-1.07]} Positions of +z and -z labels respectively. @@ -70,7 +70,6 @@ class Bloch3d: standard text. Of course you can post-process the generated figures later to add LaTeX using other software if needed. - """ def __init__(self, fig=None): # ----check for mayavi----- diff --git a/qutip/core/_brtools.pyx b/qutip/core/_brtools.pyx index 45144053c4..eb43e758b0 100644 --- a/qutip/core/_brtools.pyx +++ b/qutip/core/_brtools.pyx @@ -37,7 +37,7 @@ cdef class SpectraCoefficient(Coefficient): return self.coeff_t(t) * self.coeff_w(self.w) cpdef Coefficient copy(self): - """Return a copy of the :obj:`Coefficient`.""" + """Return a copy of the :obj:`.Coefficient`.""" return SpectraCoefficient(self.coeff_t, self.coeff_w, self.w) def replace_arguments(self, _args=None, *, w=None, **kwargs): diff --git a/qutip/core/blochredfield.py b/qutip/core/blochredfield.py index e23f6af90a..b3c0509572 100644 --- a/qutip/core/blochredfield.py +++ b/qutip/core/blochredfield.py @@ -33,13 +33,13 @@ def bloch_redfield_tensor(H, a_ops, c_ops=[], sec_cutoff=0.1, a_op : :class:`qutip.Qobj`, :class:`qutip.QobjEvo` The operator coupling to the environment. Must be hermitian. - spectra : :class:`Coefficient`, func, str + spectra : :obj:`.Coefficient`, func, str The corresponding bath spectra. - Can be a :class:`~Coefficient` using an 'w' args, a function of the - frequency or a string. The :class:`SpectraCoefficient` can be used for - array based coefficient. + Can be a :obj:`.Coefficient` using an 'w' args, a function of the + frequency or a string. The :class:`SpectraCoefficient` can be used + for array based coefficient. The spectra can depend on ``t`` if the corresponding - ``a_op`` is a :class:`QobjEvo`. + ``a_op`` is a :obj:`.QobjEvo`. Example: @@ -117,13 +117,13 @@ def brterm(H, a_op, spectra, sec_cutoff=0.1, a_op : :class:`qutip.Qobj`, :class:`qutip.QobjEvo` The operator coupling to the environment. Must be hermitian. - spectra : :class:`Coefficient`, func, str + spectra : :obj:`.Coefficient`, func, str The corresponding bath spectra. - Can be a :class:`~Coefficient` using an 'w' args, a function of the + Can be a :obj:`.Coefficient` using an 'w' args, a function of the frequency or a string. The :class:`SpectraCoefficient` can be used for array based coefficient. The spectra can depend on ``t`` if the corresponding - ``a_op`` is a :class:`QobjEvo`. + ``a_op`` is a :obj:`.QobjEvo`. Example: @@ -147,12 +147,12 @@ def brterm(H, a_op, spectra, sec_cutoff=0.1, Returns ------- - R, [evecs]: :class:`~Qobj`, :class:`~QobjEvo` or tuple + R, [evecs]: :obj:`.Qobj`, :obj:`.QobjEvo` or tuple If ``fock_basis``, return the Bloch Redfield tensor in the outside basis. Otherwise return the Bloch Redfield tensor in the diagonalized Hamiltonian basis and the eigenvectors of the Hamiltonian as hstacked - column. The tensors and, if given, evecs, will be :obj:`~QobjEvo` if - the ``H`` and ``a_op`` is time dependent, :obj:`Qobj` otherwise. + column. The tensors and, if given, evecs, will be :obj:`.QobjEvo` if + the ``H`` and ``a_op`` is time dependent, :obj:`.Qobj` otherwise. """ if isinstance(H, _EigenBasisTransform): Hdiag = H diff --git a/qutip/core/coefficient.py b/qutip/core/coefficient.py index 0c6ed689be..ff62866a46 100644 --- a/qutip/core/coefficient.py +++ b/qutip/core/coefficient.py @@ -479,7 +479,7 @@ def make_cy_code(code, variables, constants, raw, compile_opt): @cython.auto_pickle(True) cdef class StrCoefficient(Coefficient): \"\"\" - String compiled as a :obj:`Coefficient` using cython. + String compiled as a :obj:`.Coefficient` using cython. \"\"\" cdef: str codeString @@ -490,7 +490,7 @@ def __init__(self, base, var, cte, args): {init_cte}{init_var}{init_arg} cpdef Coefficient copy(self): - \"\"\"Return a copy of the :obj:`Coefficient`.\"\"\" + \"\"\"Return a copy of the :obj:`.Coefficient`.\"\"\" cdef StrCoefficient out = StrCoefficient.__new__(StrCoefficient) out.codeString = self.codeString {copy_cte}{copy_var} @@ -498,9 +498,9 @@ def __init__(self, base, var, cte, args): def replace_arguments(self, _args=None, **kwargs): \"\"\" - Return a :obj:`Coefficient` with args changed for :obj:`Coefficient` - built from 'str' or a python function. Or a the :obj:`Coefficient` - itself if the :obj:`Coefficient` does not use arguments. New arguments + Return a :obj:`.Coefficient` with args changed for :obj:`.Coefficient` + built from 'str' or a python function. Or a the :obj:`.Coefficient` + itself if the :obj:`.Coefficient` does not use arguments. New arguments can be passed as a dict or as keywords. Parameters diff --git a/qutip/core/cy/_element.pyx b/qutip/core/cy/_element.pyx index de2f574690..dca41ab570 100644 --- a/qutip/core/cy/_element.pyx +++ b/qutip/core/cy/_element.pyx @@ -23,11 +23,11 @@ cdef class _BaseElement: terms used by QobjEvo and solvers to describe operators. Conceptually each term is given by ``coeff(t) * qobj(t)`` where - ``coeff`` is a complex coefficient and ``qobj`` is a :obj:`~Qobj`. Both + ``coeff`` is a complex coefficient and ``qobj`` is a :obj:`.Qobj`. Both are functions of time. :meth:`~_BaseElement.coeff` returns the - coefficient at ``t``. :meth:`~_BaseElement.qobj` returns the :obj:`~Qobj`. + coefficient at ``t``. :meth:`~_BaseElement.qobj` returns the :obj:`.Qobj`. - For example, a :obj:`QobjEvo` instance created by:: + For example, a :obj:`.QobjEvo` instance created by:: QobjEvo([sigmax(), [sigmay(), 'cos(pi * t)']]) @@ -37,9 +37,9 @@ cdef class _BaseElement: :obj:`~_BaseElement` defines the interface to time-dependent terms. Sub-classes implement terms defined in different ways. For example, :obj:`~_ConstantElement` implements a term that - consists only of a constant :obj:`~Qobj` (i.e. where there is no dependence + consists only of a constant :obj:`.Qobj` (i.e. where there is no dependence on ``t``), :obj:`~_EvoElement` implements a term that consists of a - time-dependet :obj:`~Coefficient` times a constant :obj:`~Qobj`, and + time-dependet :obj:`.Coefficient` times a constant :obj:`.Qobj`, and so on. .. note:: @@ -61,7 +61,7 @@ cdef class _BaseElement: """ cpdef Data data(self, t): """ - Returns the underlying :obj:`~Data` of the :obj:`~Qobj` component + Returns the underlying :obj:`~Data` of the :obj:`.Qobj` component of the term at time ``t``. Parameters @@ -72,7 +72,7 @@ cdef class _BaseElement: Returns ------- :obj:`~Data` - The underlying data of the :obj:`~Qobj` component of the term + The underlying data of the :obj:`.Qobj` component of the term at time ``t``. """ raise NotImplementedError( @@ -81,7 +81,7 @@ cdef class _BaseElement: cpdef object qobj(self, t): """ - Returns the :obj:`~Qobj` component of the term at time ``t``. + Returns the :obj:`.Qobj` component of the term at time ``t``. Parameters ---------- @@ -90,8 +90,8 @@ cdef class _BaseElement: Returns ------- - :obj:`~Qobj` - The :obj:`~Qobj` component of the term at time ``t``. + :obj:`.Qobj` + The :obj:`.Qobj` component of the term at time ``t``. """ raise NotImplementedError( "Sub-classes of _BaseElement should implement .qobj(t)." @@ -175,7 +175,7 @@ cdef class _BaseElement: def linear_map(self, f, anti=False): """ Return a new element representing a linear transformation ``f`` - of the :obj:`~Qobj` portion of this element and possibly a + of the :obj:`.Qobj` portion of this element and possibly a complex conjucation of the coefficient portion (when ``f`` is an antilinear map). @@ -186,7 +186,7 @@ cdef class _BaseElement: Parameters ---------- f : function - The linear transformation to apply to the :obj:`~Qobj` of this + The linear transformation to apply to the :obj:`.Qobj` of this element. anti : bool Whether to take the complex conjugate of the coefficient. Default @@ -206,7 +206,7 @@ cdef class _BaseElement: """ Return a copy of the element with the (possible) additional arguments to any time-dependent functions updated to the given argument values. - The arguments of any contained :obj:`~Coefficient` instances are also + The arguments of any contained :obj:`.Coefficient` instances are also replaced. If the operation does not modify this element, the original element @@ -269,8 +269,8 @@ cdef class _BaseElement: cdef class _ConstantElement(_BaseElement): """ - Constant part of a list format :obj:`QobjEvo`. - A constant :obj:`QobjEvo` will contain one `_ConstantElement`:: + Constant part of a list format :obj:`.QobjEvo`. + A constant :obj:`.QobjEvo` will contain one `_ConstantElement`:: qevo = QobjEvo(H0) qevo.elements = [_ConstantElement(H0)] @@ -315,7 +315,7 @@ cdef class _ConstantElement(_BaseElement): cdef class _EvoElement(_BaseElement): """ - A pair of a :obj:`Qobj` and a :obj:`Coefficient` from the list format + A pair of a :obj:`.Qobj` and a :obj:`.Coefficient` from the list format time-dependent operator:: qevo = QobjEvo([[H0, coeff0], [H1, coeff1]]) @@ -372,7 +372,7 @@ cdef class _EvoElement(_BaseElement): cdef class _FuncElement(_BaseElement): """ - Used with :obj:`QobjEvo` to build an evolution term from a function with + Used with :obj:`.QobjEvo` to build an evolution term from a function with either the signature: :: func(t: float, ...) -> Qobj @@ -384,7 +384,7 @@ cdef class _FuncElement(_BaseElement): In the new style, ``func`` may accept arbitrary named arguments and is called as ``func(t, **args)``. - A :obj:`QobjEvo` created from such a function contains one + A :obj:`.QobjEvo` created from such a function contains one :obj:`_FuncElement`: :: qevo = QobjEvo(func, args=args) @@ -496,7 +496,7 @@ cdef class _MapElement(_BaseElement): """ :obj:`_FuncElement` decorated with linear tranformations. - Linear tranformations available in :obj:`QobjEvo` include transpose, + Linear tranformations available in :obj:`.QobjEvo` include transpose, adjoint, conjugate, convertion and product with number:: ``` op = QobjEvo(f, args=args) diff --git a/qutip/core/cy/coefficient.pyx b/qutip/core/cy/coefficient.pyx index 5ecc6bf9cb..42a84cfca6 100644 --- a/qutip/core/cy/coefficient.pyx +++ b/qutip/core/cy/coefficient.pyx @@ -79,9 +79,9 @@ def coefficient_function_parameters(func, style=None): cdef class Coefficient: """ `Coefficient` are the time-dependant scalar of a `[Qobj, coeff]` pair - composing time-dependant operator in list format for :obj:`QobjEvo`. + composing time-dependant operator in list format for :obj:`.QobjEvo`. - :obj:`Coefficient` are immutable. + :obj:`.Coefficient` are immutable. """ def __init__(self, args, **_): self.args = args @@ -90,7 +90,7 @@ cdef class Coefficient: """ Replace the arguments (``args``) of a coefficient. - Returns a new :obj:`Coefficient` if the coefficient has arguments, or + Returns a new :obj:`.Coefficient` if the coefficient has arguments, or the original coefficient if it does not. Arguments to replace may be supplied either in a dictionary as the first position argument, or passed as keywords, or as a combination of the two. Arguments not @@ -114,7 +114,7 @@ cdef class Coefficient: Parameters ---------- t : float - Time at which to evaluate the :obj:`Coefficient`. + Time at which to evaluate the :obj:`.Coefficient`. _args : dict Dictionary of arguments to use instead of the stored ones. @@ -128,7 +128,7 @@ cdef class Coefficient: return self._call(t) cdef double complex _call(self, double t) except *: - """Core computation of the :obj:`Coefficient`.""" + """Core computation of the :obj:`.Coefficient`.""" # All Coefficient sub-classes should overwrite this or __call__ return complex(self(t)) @@ -152,22 +152,22 @@ cdef class Coefficient: return NotImplemented cpdef Coefficient copy(self): - """Return a copy of the :obj:`Coefficient`.""" + """Return a copy of the :obj:`.Coefficient`.""" return pickle.loads(pickle.dumps(self)) def conj(self): - """ Return a conjugate :obj:`Coefficient` of this""" + """ Return a conjugate :obj:`.Coefficient` of this""" return ConjCoefficient(self) def _cdc(self): - """ Return a :obj:`Coefficient` being the norm of this""" + """ Return a :obj:`.Coefficient` being the norm of this""" return NormCoefficient(self) @cython.auto_pickle(True) cdef class FunctionCoefficient(Coefficient): """ - :obj:`Coefficient` wrapping a Python function. + :obj:`.Coefficient` wrapping a Python function. Parameters ---------- @@ -220,7 +220,7 @@ cdef class FunctionCoefficient(Coefficient): return self.func(t, self.args) cpdef Coefficient copy(self): - """Return a copy of the :obj:`Coefficient`.""" + """Return a copy of the :obj:`.Coefficient`.""" return FunctionCoefficient( self.func, self.args.copy(), @@ -232,7 +232,7 @@ cdef class FunctionCoefficient(Coefficient): """ Replace the arguments (``args``) of a coefficient. - Returns a new :obj:`Coefficient` if the coefficient has arguments, or + Returns a new :obj:`.Coefficient` if the coefficient has arguments, or the original coefficient if it does not. Arguments to replace may be supplied either in a dictionary as the first position argument, or passed as keywords, or as a combination of the two. Arguments not @@ -269,7 +269,7 @@ def proj(x): cdef class StrFunctionCoefficient(Coefficient): """ - A :obj:`Coefficient` defined by a string containing a simple Python + A :obj:`.Coefficient` defined by a string containing a simple Python expression. The string should contain a compilable Python expression that results in a @@ -348,7 +348,7 @@ def coeff(t, args): return self.func(t, self.args) cpdef Coefficient copy(self): - """Return a copy of the :obj:`Coefficient`.""" + """Return a copy of the :obj:`.Coefficient`.""" return StrFunctionCoefficient(self.base, self.args.copy()) def __reduce__(self): @@ -358,7 +358,7 @@ def coeff(t, args): """ Replace the arguments (``args``) of a coefficient. - Returns a new :obj:`Coefficient` if the coefficient has arguments, or + Returns a new :obj:`.Coefficient` if the coefficient has arguments, or the original coefficient if it does not. Arguments to replace may be supplied either in a dictionary as the first position argument, or passed as keywords, or as a combination of the two. Arguments not @@ -381,7 +381,7 @@ def coeff(t, args): cdef class InterCoefficient(Coefficient): """ - A :obj:`Coefficient` built from an interpolation of a numpy array. + A :obj:`.Coefficient` built from an interpolation of a numpy array. Parameters ---------- @@ -532,7 +532,7 @@ cdef class InterCoefficient(Coefficient): return cls.restore(tlist, poly) cpdef Coefficient copy(self): - """Return a copy of the :obj:`Coefficient`.""" + """Return a copy of the :obj:`.Coefficient`.""" return InterCoefficient.restore(*self.np_arrays, self.dt) @@ -556,7 +556,7 @@ cdef Coefficient add_inter(InterCoefficient left, InterCoefficient right): @cython.auto_pickle(True) cdef class SumCoefficient(Coefficient): """ - A :obj:`Coefficient` built from the sum of two other coefficients. + A :obj:`.Coefficient` built from the sum of two other coefficients. A :obj:`SumCoefficient` is returned as the result of the addition of two coefficients, e.g. :: @@ -574,14 +574,14 @@ cdef class SumCoefficient(Coefficient): return self.first._call(t) + self.second._call(t) cpdef Coefficient copy(self): - """Return a copy of the :obj:`Coefficient`.""" + """Return a copy of the :obj:`.Coefficient`.""" return SumCoefficient(self.first.copy(), self.second.copy()) def replace_arguments(self, _args=None, **kwargs): """ Replace the arguments (``args``) of a coefficient. - Returns a new :obj:`Coefficient` if the coefficient has arguments, or + Returns a new :obj:`.Coefficient` if the coefficient has arguments, or the original coefficient if it does not. Arguments to replace may be supplied either in a dictionary as the first position argument, or passed as keywords, or as a combination of the two. Arguments not @@ -604,7 +604,7 @@ cdef class SumCoefficient(Coefficient): @cython.auto_pickle(True) cdef class MulCoefficient(Coefficient): """ - A :obj:`Coefficient` built from the product of two other coefficients. + A :obj:`.Coefficient` built from the product of two other coefficients. A :obj:`MulCoefficient` is returned as the result of the multiplication of two coefficients, e.g. :: @@ -622,14 +622,14 @@ cdef class MulCoefficient(Coefficient): return self.first._call(t) * self.second._call(t) cpdef Coefficient copy(self): - """Return a copy of the :obj:`Coefficient`.""" + """Return a copy of the :obj:`.Coefficient`.""" return MulCoefficient(self.first.copy(), self.second.copy()) def replace_arguments(self, _args=None, **kwargs): """ Replace the arguments (``args``) of a coefficient. - Returns a new :obj:`Coefficient` if the coefficient has arguments, or + Returns a new :obj:`.Coefficient` if the coefficient has arguments, or the original coefficient if it does not. Arguments to replace may be supplied either in a dictionary as the first position argument, or passed as keywords, or as a combination of the two. Arguments not @@ -652,7 +652,7 @@ cdef class MulCoefficient(Coefficient): @cython.auto_pickle(True) cdef class ConjCoefficient(Coefficient): """ - The conjugate of a :obj:`Coefficient`. + The conjugate of a :obj:`.Coefficient`. A :obj:`ConjCoefficient` is returned by ``Coefficient.conj()`` and ``qutip.coefficent.conj(Coefficient)``. @@ -666,14 +666,14 @@ cdef class ConjCoefficient(Coefficient): return conj(self.base._call(t)) cpdef Coefficient copy(self): - """Return a copy of the :obj:`Coefficient`.""" + """Return a copy of the :obj:`.Coefficient`.""" return ConjCoefficient(self.base.copy()) def replace_arguments(self, _args=None, **kwargs): """ Replace the arguments (``args``) of a coefficient. - Returns a new :obj:`Coefficient` if the coefficient has arguments, or + Returns a new :obj:`.Coefficient` if the coefficient has arguments, or the original coefficient if it does not. Arguments to replace may be supplied either in a dictionary as the first position argument, or passed as keywords, or as a combination of the two. Arguments not @@ -695,7 +695,7 @@ cdef class ConjCoefficient(Coefficient): @cython.auto_pickle(True) cdef class NormCoefficient(Coefficient): """ - The L2 :func:`norm` of a :obj:`Coefficient`. A shortcut for + The L2 :func:`norm` of a :obj:`.Coefficient`. A shortcut for ``conj(coeff) * coeff``. :obj:`NormCoefficient` is returned by @@ -710,7 +710,7 @@ cdef class NormCoefficient(Coefficient): """ Replace the arguments (``args``) of a coefficient. - Returns a new :obj:`Coefficient` if the coefficient has arguments, or + Returns a new :obj:`.Coefficient` if the coefficient has arguments, or the original coefficient if it does not. Arguments to replace may be supplied either in a dictionary as the first position argument, or passed as keywords, or as a combination of the two. Arguments not @@ -732,7 +732,7 @@ cdef class NormCoefficient(Coefficient): return norm(self.base._call(t)) cpdef Coefficient copy(self): - """Return a copy of the :obj:`Coefficient`.""" + """Return a copy of the :obj:`.Coefficient`.""" return NormCoefficient(self.base.copy()) @@ -752,7 +752,7 @@ cdef class ConstantCoefficient(Coefficient): """ Replace the arguments (``args``) of a coefficient. - Returns a new :obj:`Coefficient` if the coefficient has arguments, or + Returns a new :obj:`.Coefficient` if the coefficient has arguments, or the original coefficient if it does not. Arguments to replace may be supplied either in a dictionary as the first position argument, or passed as keywords, or as a combination of the two. Arguments not @@ -772,5 +772,5 @@ cdef class ConstantCoefficient(Coefficient): return self.value cpdef Coefficient copy(self): - """Return a copy of the :obj:`Coefficient`.""" + """Return a copy of the :obj:`.Coefficient`.""" return self diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 388551dd39..8e8aafc6db 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -29,29 +29,29 @@ cdef class QobjEvo: A class for representing time-dependent quantum objects, such as quantum operators and states. - Importantly, :obj:`~QobjEvo` instances are used to represent such + Importantly, :obj:`.QobjEvo` instances are used to represent such time-dependent quantum objects when working with QuTiP solvers. - A :obj:`~QobjEvo` instance may be constructed from one of the following: + A :obj:`.QobjEvo` instance may be constructed from one of the following: * a callable ``f(t: double, args: dict) -> Qobj`` that returns the value of the quantum object at time ``t``. - * a ``[Qobj, Coefficient]`` pair, where :obj:`~Coefficient` may also be + * a ``[Qobj, Coefficient]`` pair, where :obj:`.Coefficient` may also be any item that can be used to construct a coefficient (e.g. a function, a numpy array of coefficient values, a string expression). - * a :obj:`~Qobj` (which creates a constant :obj:`~QobjEvo` term). + * a :obj:`.Qobj` (which creates a constant :obj:`.QobjEvo` term). - * a list of such callables, pairs or :obj:`~Qobj`\s. + * a list of such callables, pairs or :obj:`.Qobj`\s. - * a :obj:`~QobjEvo` (in which case a copy is created, all other arguments + * a :obj:`.QobjEvo` (in which case a copy is created, all other arguments are ignored except ``args`` which, if passed, replaces the existing arguments). Parameters ---------- - Q_object : callable, list or Qobj + Q_object : callable, list or :obj:`.Qobj` A specification of the time-depedent quantum object. See the paragraph above for a full description and the examples section below for examples. @@ -80,15 +80,15 @@ cdef class QobjEvo: ``0`` use previous or left value. copy : bool, default=True - Whether to make a copy of the :obj:`Qobj` instances supplied in + Whether to make a copy of the :obj:`.Qobj` instances supplied in the ``Q_object`` parameter. compress : bool, default=True - Whether to compress the :obj:`QobjEvo` instance terms after the + Whether to compress the :obj:`.QobjEvo` instance terms after the instance has been created. This sums the constant terms in a single term and combines - ``[Qobj, coefficient]`` pairs with the same :obj:`~Qobj` into a single + ``[Qobj, coefficient]`` pairs with the same :obj:`.Qobj` into a single pair containing the sum of the coefficients. See :meth:`compress`. @@ -126,7 +126,7 @@ cdef class QobjEvo: Examples -------- - A :obj:`~QobjEvo` constructed from a function: + A :obj:`.QobjEvo` constructed from a function: .. code-block:: @@ -136,14 +136,14 @@ cdef class QobjEvo: QobjEvo(f, args={'w': 1j}) - For list based :obj:`~QobjEvo`, the list must consist of :obj`~Qobj` or + For list based :obj:`.QobjEvo`, the list must consist of :obj:`.Qobj` or ``[Qobj, Coefficient]`` pairs: .. code-block:: QobjEvo([H0, [H1, coeff1], [H2, coeff2]], args=args) - The coefficients may be specified either using a :obj:`~Coefficient` + The coefficients may be specified either using a :obj:`.Coefficient` object or by a function, string, numpy array or any object that can be passed to the :func:`~coefficient` function. See the documentation of :func:`coefficient` for a full description. @@ -179,8 +179,8 @@ cdef class QobjEvo: The coeffients array must have the same len as the tlist. - A :obj:`~QobjEvo` may also be built using simple arithmetic operations - combining :obj:`~Qobj` with :obj:`~Coefficient`, for example: + A :obj:`.QobjEvo` may also be built using simple arithmetic operations + combining :obj:`.Qobj` with :obj:`.Coefficient`, for example: .. code-block:: python @@ -333,12 +333,12 @@ cdef class QobjEvo: def __call__(self, double t, dict _args=None, **kwargs): """ - Get the :class:`~Qobj` at ``t``. + Get the :obj:`.Qobj` at ``t``. Parameters ---------- t : float - Time at which the ``QobjEvo`` is to be evalued. + Time at which the :obj:`.QobjEvo` is to be evalued. _args : dict [optional] New arguments as a dict. Update args with ``arguments(new_args)``. @@ -347,10 +347,11 @@ cdef class QobjEvo: New arguments as a keywors. Update args with ``arguments(**new_args)``. - .. note:: - If both the positional ``_args`` and keywords are passed new values - from both will be used. If a key is present with both, the - ``_args`` dict value will take priority. + Notes + ----- + If both the positional ``_args`` and keywords are passed new values + from both will be used. If a key is present with both, the + ``_args`` dict value will take priority. """ if _args is not None or kwargs: if _args is not None: @@ -405,7 +406,7 @@ cdef class QobjEvo: return t def copy(QobjEvo self): - """Return a copy of this `QobjEvo`""" + """Return a copy of this :obj:`.QobjEvo`""" return QobjEvo(self, compress=False) def arguments(QobjEvo self, dict _args=None, **kwargs): @@ -421,10 +422,11 @@ cdef class QobjEvo: New arguments as a keywors. Update args with ``arguments(**new_args)``. - .. note:: - If both the positional ``_args`` and keywords are passed new values - from both will be used. If a key is present with both, the ``_args`` - dict value will take priority. + Notes + ----- + If both the positional ``_args`` and keywords are passed new values + from both will be used. If a key is present with both, the ``_args`` + dict value will take priority. """ if _args is not None: kwargs.update(_args) @@ -683,7 +685,7 @@ cdef class QobjEvo: which respectively construct a dense matrix store and a compressed sparse row one. - The `QobjEvo` is transformed inplace. + The :obj:`.QobjEvo` is transformed inplace. Parameters ---------- @@ -770,16 +772,16 @@ cdef class QobjEvo: def compress(self): """ - Look for redundance in the :obj:`~QobjEvo` components: + Look for redundance in the :obj:`.QobjEvo` components: - Constant parts, (:class:`~Qobj` without :class:`~Coefficient`) will be + Constant parts, (:obj:`.Qobj` without :obj:`.Coefficient`) will be summed. - Pairs ``[Qobj, Coefficient]`` with the same :class:`~Qobj` are merged. + Pairs ``[Qobj, Coefficient]`` with the same :obj:`.Qobj` are merged. Example: ``[[sigmax(), f1], [sigmax(), f2]] -> [[sigmax(), f1+f2]]`` - The :class:`~QobjEvo` is transformed inplace. + The :obj:`.QobjEvo` is transformed inplace. Returns ------- @@ -816,12 +818,12 @@ cdef class QobjEvo: Returns ------- list_qevo: list - The QobjEvo as a list, element are either :class:`Qobj` for + The QobjEvo as a list, element are either :obj:`.Qobj` for constant parts, ``[Qobj, Coefficient]`` for coefficient based term. - The original format of the :class:`Coefficient` is not restored. - Lastly if the original `QobjEvo` is constructed with a function - returning a Qobj, the term is returned as a pair of the original - function and args (``dict``). + The original format of the :obj:`.Coefficient` is not restored. + Lastly if the original :obj:`.QobjEvo` is constructed with a + function returning a Qobj, the term is returned as a pair of the + original function and args (``dict``). """ out = [] for element in self.elements: diff --git a/qutip/core/data/convert.pyx b/qutip/core/data/convert.pyx index d2681551b1..9b430d99af 100644 --- a/qutip/core/data/convert.pyx +++ b/qutip/core/data/convert.pyx @@ -314,7 +314,7 @@ cdef class _to: def register_aliases(self, aliases, layer_type): """ Register a user frendly name for a data-layer type to be recognized by - the :method:`parse` method. + the :meth:`parse` method. Parameters ---------- @@ -323,7 +323,7 @@ cdef class _to: layer_type : type Data-layer type, must have been registered with - :method:`add_conversions` first. + :meth:`add_conversions` first. """ if layer_type not in self.dtypes: raise ValueError( @@ -461,7 +461,7 @@ cdef class _create: def __call__(self, arg, shape=None, copy=True): """ - Build a :class:`qutip.data.Data` object from arg. + Build a :class:`.Data` object from arg. Parameters ---------- diff --git a/qutip/core/gates.py b/qutip/core/gates.py index 5b3d988d10..ee97c819f6 100644 --- a/qutip/core/gates.py +++ b/qutip/core/gates.py @@ -49,7 +49,7 @@ def cy_gate(*, dtype="csr"): Returns ------- - result : :class:`qutip.Qobj` + result : :class:`.Qobj` Quantum object for operator describing the rotation. """ return Qobj( @@ -69,7 +69,7 @@ def cz_gate(*, dtype="csr"): Returns ------- - result : :class:`qutip.Qobj` + result : :class:`.Qobj` Quantum object for operator describing the rotation. """ return qdiags([1, 1, 1, -1], dims=[[2, 2], [2, 2]], dtype=dtype) @@ -86,7 +86,7 @@ def s_gate(*, dtype="csr"): Returns ------- - result : :class:`qutip.Qobj` + result : :class:`.Qobj` Quantum object for operator describing a 90 degree rotation around the z-axis. @@ -105,7 +105,7 @@ def cs_gate(*, dtype="csr"): Returns ------- - result : :class:`qutip.Qobj` + result : :class:`.Qobj` Quantum object for operator describing the rotation. """ @@ -123,7 +123,7 @@ def t_gate(*, dtype="csr"): Returns ------- - result : :class:`qutip.Qobj` + result : :class:`.Qobj` Quantum object for operator describing a phase shift of pi/4. """ @@ -141,7 +141,7 @@ def ct_gate(*, dtype="csr"): Returns ------- - result : :class:`qutip.Qobj` + result : :class:`.Qobj` Quantum object for operator describing the rotation. """ @@ -289,7 +289,7 @@ def qrot(theta, phi, *, dtype="dense"): Returns ------- - qrot_gate : :class:`qutip.Qobj` + qrot_gate : :class:`.Qobj` Quantum object representation of physical qubit rotation under a rabi pulse. """ @@ -537,7 +537,7 @@ def molmer_sorensen(theta, *, dtype="dense"): Returns ------- - molmer_sorensen_gate: :class:`qutip.Qobj` + molmer_sorensen_gate: :class:`.Qobj` Quantum object representation of the Mølmer–Sørensen gate. """ return Qobj( diff --git a/qutip/core/metrics.py b/qutip/core/metrics.py index 36ded9dd38..45ed022b65 100644 --- a/qutip/core/metrics.py +++ b/qutip/core/metrics.py @@ -115,7 +115,7 @@ def _process_fidelity_to_id(oper): to the identity quantum channel. Parameters ---------- - oper : :class:`qutip.Qobj`/list + oper : :class:`.Qobj`/list A unitary operator, or a superoperator in supermatrix, Choi or chi-matrix form, or a list of Kraus operators Returns @@ -154,10 +154,10 @@ def process_fidelity(oper, target=None): Parameters ---------- - oper : :class:`qutip.Qobj`/list + oper : :class:`.Qobj`/list A unitary operator, or a superoperator in supermatrix, Choi or chi-matrix form, or a list of Kraus operators - target : :class:`qutip.Qobj`/list + target : :class:`.Qobj`/list A unitary operator, or a superoperator in supermatrix, Choi or chi-matrix form, or a list of Kraus operators @@ -217,10 +217,10 @@ def average_gate_fidelity(oper, target=None): Parameters ---------- - oper : :class:`qutip.Qobj`/list + oper : :class:`.Qobj`/list A unitary operator, or a superoperator in supermatrix, Choi or chi-matrix form, or a list of Kraus operators - target : :class:`qutip.Qobj` + target : :class:`.Qobj` A unitary operator Returns @@ -387,9 +387,9 @@ def hellinger_dist(A, B, sparse=False, tol=0): Parameters ---------- - A : :class:`qutip.Qobj` + A : :class:`.Qobj` Density matrix or state vector. - B : :class:`qutip.Qobj` + B : :class:`.Qobj` Density matrix or state vector with same dimensions as A. tol : float Tolerance used by sparse eigensolver, if used. (0=Machine precision) diff --git a/qutip/core/operators.py b/qutip/core/operators.py index f51f1de878..b3e041ead6 100644 --- a/qutip/core/operators.py +++ b/qutip/core/operators.py @@ -886,7 +886,7 @@ def squeeze(N, z, offset=0, *, dtype=None): Returns ------- - oper : :class:`qutip.Qobj` + oper : :class:`.Qobj` Squeezing operator. @@ -917,10 +917,10 @@ def squeezing(a1, a2, z): Parameters ---------- - a1 : :class:`qutip.Qobj` + a1 : :class:`.Qobj` Operator 1. - a2 : :class:`qutip.Qobj` + a2 : :class:`.Qobj` Operator 2. z : float/complex @@ -928,7 +928,7 @@ def squeezing(a1, a2, z): Returns ------- - oper : :class:`qutip.Qobj` + oper : :class:`.Qobj` Squeezing operator. """ diff --git a/qutip/core/options.py b/qutip/core/options.py index 1724c81890..a1a9ca3e66 100644 --- a/qutip/core/options.py +++ b/qutip/core/options.py @@ -52,7 +52,7 @@ def __exit__(self, exc_type, exc_value, exc_traceback): class CoreOptions(QutipOptions): """ - Options used by the core of qutip such as the tolerance of :class:`Qobj` + Options used by the core of qutip such as the tolerance of :obj:`.Qobj` comparison or coefficient's format. Values can be changed in ``qutip.settings.core`` or by using context: @@ -74,9 +74,9 @@ class CoreOptions(QutipOptions): Used to choose QobjEvo.expect output type auto_tidyup_atol : float {1e-14} - The absolute tolerance used in automatic tidyup (see the ``auto_tidyup`` - parameter above) and the default value of ``atol`` used in - :method:`Qobj.tidyup`. + The absolute tolerance used in automatic tidyup (see the + ``auto_tidyup`` parameter above) and the default value of ``atol`` used + in :meth:`Qobj.tidyup`. function_coefficient_style : str {"auto"} The signature expected by function coefficients. The options are: @@ -95,7 +95,7 @@ class CoreOptions(QutipOptions): ``pythonic`` is used. default_dtype : Nonetype, str, type {None} - When set, functions creating :class:`Qobj`, such as :func:"qeye" or + When set, functions creating :obj:`.Qobj`, such as :func:"qeye" or :func:"rand_herm", will use the specified data type. Any data-layer known to ``qutip.data.to`` is accepted. When ``None``, these functions will default to a sensible data type. diff --git a/qutip/core/qobj.py b/qutip/core/qobj.py index 2560a90b54..c629d140ef 100644 --- a/qutip/core/qobj.py +++ b/qutip/core/qobj.py @@ -755,12 +755,12 @@ def proj(self): Parameters ---------- - Q : :class:`qutip.Qobj` + Q : :class:`.Qobj` Input bra or ket vector Returns ------- - P : :class:`qutip.Qobj` + P : :class:`.Qobj` Projection operator. """ if not (self.isket or self.isbra): @@ -874,7 +874,7 @@ def expm(self, dtype=_data.Dense): Returns ------- - oper : :class:`qutip.Qobj` + oper : :class:`.Qobj` Exponentiated quantum operator. Raises @@ -898,7 +898,7 @@ def logm(self): Returns ------- - oper : :class:`qutip.Qobj` + oper : :class:`.Qobj` Logarithm of the quantum operator. Raises @@ -941,7 +941,7 @@ def sqrtm(self, sparse=False, tol=0, maxiter=100000): Returns ------- - oper : :class:`qutip.Qobj` + oper : :class:`.Qobj` Matrix square root of operator. Raises @@ -985,7 +985,7 @@ def cosm(self): Returns ------- - oper : :class:`qutip.Qobj` + oper : :class:`.Qobj` Matrix cosine of operator. Raises @@ -1009,7 +1009,7 @@ def sinm(self): Returns ------- - oper : :class:`qutip.Qobj` + oper : :class:`.Qobj` Matrix sine of operator. Raises @@ -1032,7 +1032,7 @@ def inv(self, sparse=False): Returns ------- - oper : :class:`qutip.Qobj` + oper : :class:`.Qobj` Matrix inverse of operator. Raises @@ -1069,7 +1069,7 @@ def unit(self, inplace=False, norm=None, kwargs=None): Returns ------- - obj : :class:`qutip.Qobj` + obj : :class:`.Qobj` Normalized quantum object. Will be the `self` object if in place. """ norm = self.norm(norm=norm, kwargs=kwargs) @@ -1119,7 +1119,7 @@ def ptrace(self, sel, dtype=None): Returns ------- - oper : :class:`qutip.Qobj` + oper : :class:`.Qobj` Quantum object representing partial trace with selected components remaining. """ @@ -1239,7 +1239,7 @@ def permute(self, order): Returns ------- - P : :class:`qutip.Qobj` + P : :class:`.Qobj` Permuted quantum object. """ if self.type in ('bra', 'ket', 'oper'): @@ -1294,7 +1294,7 @@ def tidyup(self, atol=None): Returns ------- - oper : :class:`qutip.Qobj` + oper : :class:`.Qobj` Quantum object with small elements removed. """ atol = atol or settings.core['auto_tidyup_atol'] @@ -1316,7 +1316,7 @@ def transform(self, inpt, inverse=False): Returns ------- - oper : :class:`qutip.Qobj` + oper : :class:`.Qobj` Operator in new basis. Notes @@ -1377,7 +1377,7 @@ def trunc_neg(self, method="clip"): Returns ------- - oper : :class:`qutip.Qobj` + oper : :class:`.Qobj` A valid density operator. """ if not self.isherm: @@ -1429,10 +1429,10 @@ def matrix_element(self, bra, ket): Parameters ---------- - bra : :class:`qutip.Qobj` + bra : :class:`.Qobj` Quantum object of type 'bra' or 'ket' - ket : :class:`qutip.Qobj` + ket : :class:`.Qobj` Quantum object of type 'ket'. Returns @@ -1466,7 +1466,7 @@ def overlap(self, other): Parameters ---------- - other : :class:`qutip.Qobj` + other : :class:`.Qobj` Quantum object for a state vector of type 'ket', 'bra' or density matrix. @@ -1653,7 +1653,7 @@ def groundstate(self, sparse=False, tol=0, maxiter=100000, safe=True): ------- eigval : float Eigenvalue for the ground state of quantum operator. - eigvec : :class:`qutip.Qobj` + eigvec : :class:`.Qobj` Eigenket for the ground state of quantum operator. Notes @@ -1679,7 +1679,7 @@ def dnorm(self, B=None): Parameters ---------- - B : :class:`qutip.Qobj` or None + B : :class:`.Qobj` or None If B is not None, the diamond distance d(A, B) = dnorm(A - B) between this operator and B is returned instead of the diamond norm. @@ -1802,14 +1802,14 @@ def ptrace(Q, sel): Parameters ---------- - Q : :class:`qutip.Qobj` + Q : :class:`.Qobj` Composite quantum object. sel : int/list An ``int`` or ``list`` of components to keep after partial trace. Returns ------- - oper : :class:`qutip.Qobj` + oper : :class:`.Qobj` Quantum object representing partial trace with selected components remaining. diff --git a/qutip/core/states.py b/qutip/core/states.py index a3c310998a..7ab4922ee5 100644 --- a/qutip/core/states.py +++ b/qutip/core/states.py @@ -21,6 +21,7 @@ from .tensor import tensor from .. import settings + def _promote_to_zero_list(arg, length): """ Ensure `arg` is a list of length `length`. If `arg` is None it is promoted @@ -68,7 +69,7 @@ def basis(dimensions, n=None, offset=None, *, dtype=None): Returns ------- - state : :class:`qutip.Qobj` + state : :class:`.Qobj` Qobj representing the requested number state ``|n>``. Examples @@ -153,6 +154,7 @@ def qutrit_basis(*, dtype=None): ] return out + _COHERENT_METHODS = ('operator', 'analytic') @@ -883,6 +885,9 @@ def state_number_qobj(dims, state, *, dtype=None): Return a Qobj representation of a quantum state specified by the state array `state`. + .. note:: + Deprecated in QuTiP 5.0, use :func:`basis` instead. + Example: >>> state_number_qobj([2, 2, 2], [1, 0, 1]) # doctest: +SKIP @@ -912,11 +917,8 @@ def state_number_qobj(dims, state, *, dtype=None): Returns ------- - state : :class:`qutip.Qobj` - The state as a :class:`qutip.Qobj` instance. - - .. note:: - Deprecated in QuTiP 5.0, use :func:`basis` instead. + state : :class:`.Qobj` + The state as a :class:`.Qobj` instance. """ dtype = dtype or settings.core["default_dtype"] or _data.Dense warnings.warn("basis() is a drop-in replacement for this", @@ -1308,7 +1310,7 @@ def w_state(N=3, *, dtype=None): Returns ------- - W : :obj:`~Qobj` + W : :obj:`.Qobj` N-qubit W-state """ inds = np.zeros(N, dtype=int) diff --git a/qutip/core/subsystem_apply.py b/qutip/core/subsystem_apply.py index 013bcbc7d2..e7bea30a2f 100644 --- a/qutip/core/subsystem_apply.py +++ b/qutip/core/subsystem_apply.py @@ -21,10 +21,10 @@ def subsystem_apply(state, channel, mask, reference=False): Parameters ---------- - state : :class:`qutip.qobj` + state : :class:`.Qobj` A density matrix or ket. - channel : :class:`qutip.qobj` + channel : :class:`.Qobj` A propagator, either an `oper` or `super`. mask : *list* / *array* @@ -37,7 +37,7 @@ def subsystem_apply(state, channel, mask, reference=False): Returns ------- - rho_out: :class:`qutip.qobj` + rho_out: :class:`.Qobj` A density matrix with the selected subsystems transformed according to the specified channel. """ diff --git a/qutip/core/tensor.py b/qutip/core/tensor.py index 3a3661752c..6adacc6582 100644 --- a/qutip/core/tensor.py +++ b/qutip/core/tensor.py @@ -348,7 +348,7 @@ def _check_oper_dims(oper, dims=None, targets=None): Parameters ---------- - oper : :class:`qutip.Qobj` + oper : :class:`.Qobj` The quantum object to be checked. dims : list, optional A list of integer for the dimension of each composite system. @@ -379,8 +379,8 @@ def _targets_to_list(targets, oper=None, N=None): ---------- targets : int or list of int The indices of subspace that are acted on. - oper : :class:`qutip.Qobj`, optional - An operator, the type of the :class:`qutip.Qobj` + oper : :class:`.Qobj`, optional + An operator, the type of the :class:`.Qobj` has to be an operator and the dimension matches the tensored qubit Hilbert space e.g. dims = ``[[2, 2, 2], [2, 2, 2]]`` @@ -425,7 +425,7 @@ def expand_operator(oper, dims, targets): Parameters ---------- - oper : :class:`qutip.Qobj` + oper : :class:`.Qobj` An operator that act on the subsystem, has to be an operator and the dimension matches the tensored dims Hilbert space e.g. oper.dims = ``[[2, 3], [2, 3]]`` @@ -437,7 +437,7 @@ def expand_operator(oper, dims, targets): Returns ------- - expanded_oper : :class:`qutip.Qobj` + expanded_oper : :class:`.Qobj` The expanded operator acting on a system with desired dimension. """ from .operators import identity diff --git a/qutip/entropy.py b/qutip/entropy.py index c22bef3648..d8dddeea87 100644 --- a/qutip/entropy.py +++ b/qutip/entropy.py @@ -195,10 +195,10 @@ def entropy_relative(rho, sigma, base=e, sparse=False, tol=1e-12): Parameters ---------- - rho : :class:`qutip.Qobj` + rho : :class:`.Qobj` First density matrix (or ket which will be converted to a density matrix). - sigma : :class:`qutip.Qobj` + sigma : :class:`.Qobj` Second density matrix (or ket which will be converted to a density matrix). base : {e,2} diff --git a/qutip/legacy/nonmarkov/memorycascade.py b/qutip/legacy/nonmarkov/memorycascade.py index 0cdf18cd3b..a069bca7b4 100644 --- a/qutip/legacy/nonmarkov/memorycascade.py +++ b/qutip/legacy/nonmarkov/memorycascade.py @@ -41,14 +41,14 @@ class MemoryCascade: Attributes ---------- - H_S : :class:`qutip.Qobj` + H_S : :class:`.Qobj` System Hamiltonian (can also be a Liouvillian) - L1 : :class:`qutip.Qobj` / list of :class:`qutip.Qobj` + L1 : :class:`.Qobj` / list of :class:`.Qobj` System operators coupling into the feedback loop. Can be a single operator or a list of operators. - L2 : :class:`qutip.Qobj` / list of :class:`qutip.Qobj` + L2 : :class:`.Qobj` / list of :class:`.Qobj` System operators coupling out of the feedback loop. Can be a single operator or a list of operators. L2 must have the same length as L1. @@ -57,7 +57,7 @@ class MemoryCascade: operators in L2 by the feedback channel. Defaults to an n by n identity matrix where n is the number of elements in L1/L2. - c_ops_markov : :class:`qutip.Qobj` / list of :class:`qutip.Qobj` + c_ops_markov : :class:`.Qobj` / list of :class:`.Qobj` Decay operators describing conventional Markovian decay channels. Can be a single operator or a list of operators. @@ -140,7 +140,7 @@ def propagator(self, t, tau, notrace=False): and a propagator for a single system is returned. Returns ------- - : :class:`qutip.Qobj` + : :class:`.Qobj` time-propagator for reduced system dynamics """ k = int(t / tau) + 1 @@ -186,12 +186,12 @@ def outfieldpropagator( tau : float time-delay - c1 : :class:`qutip.Qobj` + c1 : :class:`.Qobj` system collapse operator that couples to the in-loop field in question (only needs to be specified if self.L1 has more than one element) - c2 : :class:`qutip.Qobj` + c2 : :class:`.Qobj` system collapse operator that couples to the output field in question (only needs to be specified if self.L2 has more than one element) @@ -204,7 +204,7 @@ def outfieldpropagator( Returns ------- - : :class:`qutip.Qobj` + : :class:`.Qobj` time-propagator for computing field correlation function """ if c1 is None and len(self.L1) == 1: @@ -278,7 +278,7 @@ def rhot(self, rho0, t, tau): Parameters ---------- - rho0 : :class:`qutip.Qobj` + rho0 : :class:`.Qobj` initial density matrix or state vector (ket) t : float @@ -289,7 +289,7 @@ def rhot(self, rho0, t, tau): Returns ------- - : :class:`qutip.Qobj` + : :class:`.Qobj` density matrix at time :math:`t` """ if isket(rho0): @@ -307,7 +307,7 @@ def outfieldcorr(self, rho0, blist, tlist, tau, c1=None, c2=None): Parameters ---------- - rho0 : :class:`qutip.Qobj` + rho0 : :class:`.Qobj` initial density matrix or state vector (ket). blist : array_like @@ -325,12 +325,12 @@ def outfieldcorr(self, rho0, blist, tlist, tau, c1=None, c2=None): tau : float time-delay - c1 : :class:`qutip.Qobj` + c1 : :class:`.Qobj` system collapse operator that couples to the in-loop field in question (only needs to be specified if self.L1 has more than one element) - c2 : :class:`qutip.Qobj` + c2 : :class:`.Qobj` system collapse operator that couples to the output field in question (only needs to be specified if self.L2 has more than one element) diff --git a/qutip/legacy/rcsolve.py b/qutip/legacy/rcsolve.py index b171946ba2..ee7796ddfb 100644 --- a/qutip/legacy/rcsolve.py +++ b/qutip/legacy/rcsolve.py @@ -33,7 +33,7 @@ def rcsolve(Hsys, psi0, tlist, e_ops, Q, wc, alpha, N, w_th, sparse=False, Initial state of the system. tlist: List. Time over which system evolves. - e_ops: list of :class:`qutip.Qobj` / callback function single + e_ops: list of :class:`.Qobj` / callback function single Single operator or list of operators for which to evaluate expectation values. Q: Qobj diff --git a/qutip/partial_transpose.py b/qutip/partial_transpose.py index 61871369d8..47b7f09bcd 100644 --- a/qutip/partial_transpose.py +++ b/qutip/partial_transpose.py @@ -23,7 +23,7 @@ def partial_transpose(rho, mask, method='dense'): Parameters ---------- - rho : :class:`qutip.qobj` + rho : :class:`.Qobj` A density matrix. mask : *list* / *array* @@ -37,7 +37,7 @@ def partial_transpose(rho, mask, method='dense'): Returns ------- - rho_pr: :class:`qutip.qobj` + rho_pr: :class:`.Qobj` A density matrix with the selected subsystems transposed. diff --git a/qutip/piqs/_piqs.pyx b/qutip/piqs/_piqs.pyx index fcb614dccd..07096e192b 100644 --- a/qutip/piqs/_piqs.pyx +++ b/qutip/piqs/_piqs.pyx @@ -286,7 +286,7 @@ cdef class Dicke(object): Returns ---------- - lindblad_qobj: :class: qutip.Qobj + lindblad_qobj: :class:`.Qobj` The matrix size is (nds**2, nds**2) where nds is the number of Dicke states. """ diff --git a/qutip/piqs/piqs.py b/qutip/piqs/piqs.py index a8f14e7668..7aaefe6a8f 100644 --- a/qutip/piqs/piqs.py +++ b/qutip/piqs/piqs.py @@ -160,7 +160,7 @@ def dicke_blocks(rho): Parameters ---------- - rho : :class:`qutip.Qobj` + rho : :class:`.Qobj` A 2D block-diagonal matrix of ones with dimension (nds,nds), where nds is the number of Dicke states for N two-level systems. @@ -199,7 +199,7 @@ def dicke_blocks_full(rho): Parameters ---------- - rho : :class:`qutip.Qobj` + rho : :class:`.Qobj` A 2D block-diagonal matrix of ones with dimension (nds,nds), where nds is the number of Dicke states for N two-level systems. @@ -244,7 +244,7 @@ def dicke_function_trace(f, rho): f : function A Taylor-expandable function of `rho`. - rho : :class:`qutip.Qobj` + rho : :class:`.Qobj` A density matrix in the Dicke basis. Returns @@ -280,7 +280,7 @@ def entropy_vn_dicke(rho): Parameters ---------- - rho : :class:`qutip.Qobj` + rho : :class:`.Qobj` A 2D block-diagonal matrix of ones with dimension (nds,nds), where nds is the number of Dicke states for N two-level systems. @@ -300,7 +300,7 @@ def purity_dicke(rho): Parameters ---------- - rho : :class:`qutip.Qobj` + rho : :class:`.Qobj` Density matrix in the Dicke basis of qutip.piqs.jspin(N), for N spins. Returns @@ -335,7 +335,7 @@ class Dicke(object): N: int The number of two-level systems. - hamiltonian : :class:`qutip.Qobj` + hamiltonian : :class:`.Qobj` A Hamiltonian in the Dicke basis. The matrix dimensions are (nds, nds), @@ -372,7 +372,7 @@ class Dicke(object): N: int The number of two-level systems. - hamiltonian : :class:`qutip.Qobj` + hamiltonian : :class:`.Qobj` A Hamiltonian in the Dicke basis. The matrix dimensions are (nds, nds), @@ -468,7 +468,7 @@ def lindbladian(self): Returns ------- - lindbladian : :class:`qutip.Qobj` + lindbladian : :class:`.Qobj` The Lindbladian matrix as a `qutip.Qobj`. """ cythonized_dicke = _Dicke( @@ -487,7 +487,7 @@ def liouvillian(self): Returns ------- - liouv : :class:`qutip.Qobj` + liouv : :class:`.Qobj` The Liouvillian matrix for the system. """ lindblad = self.lindbladian() @@ -508,7 +508,7 @@ def pisolve(self, initial_state, tlist): Parameters ========== - initial_state : :class:`qutip.Qobj` + initial_state : :class:`.Qobj` An initial state specified as a density matrix of `qutip.Qbj` type. @@ -745,7 +745,7 @@ def spin_algebra(N, op=None): Returns ------- - spin_operators: list or :class: qutip.Qobj + spin_operators: list or :class:`.Qobj` A list of `qutip.Qobj` operators - [sx, sy, sz] or the requested operator. """ @@ -820,7 +820,7 @@ def _jspin_uncoupled(N, op=None): Returns ------- - collective_operators: list or :class: qutip.Qobj + collective_operators: list or :class:`.Qobj` A list of `qutip.Qobj` representing all the operators in uncoupled" basis or a single operator requested. """ @@ -878,7 +878,7 @@ def jspin(N, op=None, basis="dicke"): Returns ------- - j_alg: list or :class: qutip.Qobj + j_alg: list or :class:`.Qobj` A list of `qutip.Qobj` representing all the operators in the "dicke" or "uncoupled" basis or a single operator requested. """ @@ -1050,7 +1050,7 @@ def dicke_basis(N, jmm1=None): Returns ------- - rho: :class: qutip.Qobj + rho: :class:`.Qobj` The density matrix in the Dicke basis. """ if jmm1 is None: @@ -1091,7 +1091,7 @@ def dicke(N, j, m): Returns ------- - rho: :class: qutip.Qobj + rho: :class:`.Qobj` The density matrix. """ nds = num_dicke_states(N) @@ -1118,7 +1118,7 @@ def _uncoupled_excited(N): Returns ------- - psi0: :class: qutip.Qobj + psi0: :class:`.Qobj` The density matrix for the excited state in the uncoupled basis. """ N = int(N) @@ -1140,7 +1140,7 @@ def _uncoupled_superradiant(N): Returns ------- - psi0: :class: qutip.Qobj + psi0: :class:`.Qobj` The density matrix for the superradiant state in the full Hilbert space. """ @@ -1163,7 +1163,7 @@ def _uncoupled_ground(N): Returns ------- - psi0: :class: qutip.Qobj + psi0: :class:`.Qobj` The density matrix for the ground state in the full Hilbert space. """ N = int(N) @@ -1185,7 +1185,7 @@ def _uncoupled_ghz(N): Returns ------- - ghz: :class: qutip.Qobj + ghz: :class:`.Qobj` The density matrix for the GHZ state in the full Hilbert space. """ N = int(N) @@ -1221,7 +1221,7 @@ def _uncoupled_css(N, a, b): Returns ------- - css: :class: qutip.Qobj + css: :class:`.Qobj` The density matrix for the CSS state in the full Hilbert space. """ N = int(N) @@ -1270,7 +1270,7 @@ def excited(N, basis="dicke"): Returns ------- - state: :class: qutip.Qobj + state: :class:`.Qobj` The excited state density matrix in the requested basis. """ if basis == "uncoupled": @@ -1299,7 +1299,7 @@ def superradiant(N, basis="dicke"): Returns ------- - state: :class: qutip.Qobj + state: :class:`.Qobj` The superradiant state density matrix in the requested basis. """ if basis == "uncoupled": @@ -1350,7 +1350,7 @@ def css( Returns ------- - rho: :class: qutip.Qobj + rho: :class:`.Qobj` The CSS state density matrix. """ if coordinates == "polar": @@ -1406,7 +1406,7 @@ def ghz(N, basis="dicke"): Returns ------- - state: :class: qutip.Qobj + state: :class:`.Qobj` The GHZ state density matrix in the requested basis. """ if basis == "uncoupled": @@ -1438,7 +1438,7 @@ def ground(N, basis="dicke"): Returns ------- - state: :class: qutip.Qobj + state: :class:`.Qobj` The ground state density matrix in the requested basis. """ if basis == "uncoupled": @@ -1463,7 +1463,7 @@ def identity_uncoupled(N): Returns ------- - identity: :class: qutip.Qobj + identity: :class:`.Qobj` The identity matrix. """ N = int(N) diff --git a/qutip/random_objects.py b/qutip/random_objects.py index be759ae9db..1342021749 100644 --- a/qutip/random_objects.py +++ b/qutip/random_objects.py @@ -250,7 +250,7 @@ def rand_herm(dimensions, density=0.30, distribution="fill", *, Returns ------- - oper : :class:`qobj` + oper : :obj:`.Qobj` Hermitian quantum operator. Notes diff --git a/qutip/solver/brmesolve.py b/qutip/solver/brmesolve.py index f23cf48adc..a7d8e58134 100644 --- a/qutip/solver/brmesolve.py +++ b/qutip/solver/brmesolve.py @@ -26,10 +26,10 @@ def brmesolve(H, psi0, tlist, a_ops=[], e_ops=[], c_ops=[], Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo` + H : :obj:`.Qobj`, :obj:`.QobjEvo` Possibly time-dependent system Liouvillian or Hamiltonian as a Qobj or - QobjEvo. list of [:class:`Qobj`, :class:`Coefficient`] or callable that - can be made into :class:`QobjEvo` are also accepted. + QobjEvo. list of [:obj:`.Qobj`, :obj:`.Coefficient`] or callable that + can be made into :obj:`.QobjEvo` are also accepted. psi0: Qobj Initial density matrix or state vector (ket). @@ -41,10 +41,10 @@ def brmesolve(H, psi0, tlist, a_ops=[], e_ops=[], c_ops=[], Nested list of system operators that couple to the environment, and the corresponding bath spectra. - a_op : :class:`qutip.Qobj`, :class:`qutip.QobjEvo` + a_op : :obj:`qutip.Qobj`, :obj:`qutip.QobjEvo` The operator coupling to the environment. Must be hermitian. - spectra : :class:`Coefficient`, str, func + spectra : :obj:`.Coefficient`, str, func The corresponding bath spectral responce. Can be a `Coefficient` using an 'w' args, a function of the frequence or a string. Coefficient build from a numpy array are @@ -52,7 +52,7 @@ def brmesolve(H, psi0, tlist, a_ops=[], e_ops=[], c_ops=[], expected to be of the signature ``f(w)`` or ``f(t, w, **args)``. The spectra function can depend on ``t`` if the corresponding - ``a_op`` is a :class:`QobjEvo`. + ``a_op`` is a :obj:`.QobjEvo`. Example: @@ -66,20 +66,20 @@ def brmesolve(H, psi0, tlist, a_ops=[], e_ops=[], c_ops=[], ] .. note: - ``Cubic_Spline`` has been replaced by :class:`Coefficient`: + ``Cubic_Spline`` has been replaced by :obj:`.Coefficient`: ``spline = qutip.coefficient(array, tlist=times)`` Whether the ``a_ops`` is time dependent is decided by the type of - the operator: :class:`Qobj` vs :class:`QobjEvo` instead of the type + the operator: :obj:`.Qobj` vs :obj:`.QobjEvo` instead of the type of the spectra. - e_ops : list of :class:`Qobj` / callback function + e_ops : list of :obj:`.Qobj` / callback function Single operator or list of operators for which to evaluate expectation values or callable or list of callable. Callable signature must be, `f(t: float, state: Qobj)`. See :func:`expect` for more detail of operator expectation - c_ops : list of (:class:`QobjEvo`, :class:`QobjEvo` compatible format) + c_ops : list of (:obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format) List of collapse operators. args : dict @@ -129,9 +129,9 @@ def brmesolve(H, psi0, tlist, a_ops=[], e_ops=[], c_ops=[], Returns ------- - result: :class:`qutip.solver.Result` + result: :obj:`qutip.solver.Result` - An instance of the class :class:`qutip.solver.Result`, which contains + An instance of the class :obj:`qutip.solver.Result`, which contains either an array of expectation values, for operators given in e_ops, or a list of states for the times specified by `tlist`. @@ -182,22 +182,22 @@ class BRSolver(Solver): Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo` + H : :obj:`.Qobj`, :obj:`.QobjEvo` Possibly time-dependent system Liouvillian or Hamiltonian as a Qobj or - QobjEvo. list of [:class:`Qobj`, :class:`Coefficient`] or callable that - can be made into :class:`QobjEvo` are also accepted. + QobjEvo. list of [:obj:`.Qobj`, :obj:`.Coefficient`] or callable that + can be made into :obj:`.QobjEvo` are also accepted. a_ops : list of (a_op, spectra) Nested list of system operators that couple to the environment, and the corresponding bath spectra. - a_op : :class:`qutip.Qobj`, :class:`qutip.QobjEvo` + a_op : :obj:`.Qobj`, :obj:`.QobjEvo` The operator coupling to the environment. Must be hermitian. - spectra : :class:`Coefficient` + spectra : :obj:`.Coefficient` The corresponding bath spectra. As a `Coefficient` using an 'w' - args. Can depend on ``t`` only if a_op is a :class:`qutip.QobjEvo`. - :class:`SpectraCoefficient` can be used to conver a coefficient + args. Can depend on ``t`` only if a_op is a :obj:`qutip.QobjEvo`. + :obj:`SpectraCoefficient` can be used to conver a coefficient depending on ``t`` to one depending on ``w``. Example: @@ -211,7 +211,7 @@ class BRSolver(Solver): (c+c.dag(), SpectraCoefficient(coefficient(array, tlist=ws))), ] - c_ops : list of :class:`Qobj`, :class:`QobjEvo` + c_ops : list of :obj:`.Qobj`, :obj:`.QobjEvo` Single collapse operator, or list of collapse operators, or a list of Lindblad dissipator. None is equivalent to an empty list. @@ -223,14 +223,14 @@ class BRSolver(Solver): Cutoff for secular approximation. Use ``-1`` if secular approximation is not used when evaluating bath-coupling terms. - attributes + Attributes ---------- stats: dict Diverse diagnostic statistics of the evolution. """ name = "brmesolve" solver_options = { - "progress_bar": "text", + "progress_bar": "", "progress_kwargs": {"chunk_size":10}, "store_final_state": False, "store_states": None, @@ -319,7 +319,7 @@ def options(self): normalize_output: bool, default=False Normalize output state to hide ODE numerical errors. - progress_bar: str {'text', 'enhanced', 'tqdm', ''}, default="text" + progress_bar: str {'text', 'enhanced', 'tqdm', ''}, default="" How to present the solver progress. 'tqdm' uses the python module of the same name and raise an error if not installed. Empty string or False will disable the bar. diff --git a/qutip/solver/correlation.py b/qutip/solver/correlation.py index fcbeff8e13..b2916e8ebb 100644 --- a/qutip/solver/correlation.py +++ b/qutip/solver/correlation.py @@ -38,9 +38,9 @@ def correlation_2op_1t(H, state0, taulist, c_ops, a_op, b_op, Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo` + H : :obj:`.Qobj`, :obj:`.QobjEvo` System Hamiltonian, may be time-dependent for solver choice of `me`. - state0 : :class:`Qobj` + state0 : :obj:`.Qobj` Initial state density matrix :math:`\rho(t_0)` or state vector :math:`\psi(t_0)`. If 'state0' is 'None', then the steady state will be used as the initial state. The 'steady-state' is only implemented @@ -48,11 +48,11 @@ def correlation_2op_1t(H, state0, taulist, c_ops, a_op, b_op, taulist : array_like List of times for :math:`\tau`. taulist must be positive and contain the element `0`. - c_ops : list of {:class:`Qobj`, :class:`QobjEvo`} + c_ops : list of {:obj:`.Qobj`, :obj:`.QobjEvo`} List of collapse operators - a_op : :class:`Qobj`, :class:`QobjEvo` + a_op : :obj:`.Qobj`, :obj:`.QobjEvo` Operator A. - b_op : :class:`Qobj`, :class:`QobjEvo` + b_op : :obj:`.Qobj`, :obj:`.QobjEvo` Operator B. reverse : bool {False} If `True`, calculate :math:`\left` instead of @@ -101,9 +101,9 @@ def correlation_2op_2t(H, state0, tlist, taulist, c_ops, a_op, b_op, Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo` + H : :obj:`.Qobj`, :obj:`.QobjEvo` System Hamiltonian, may be time-dependent for solver choice of `me`. - state0 : :class:`Qobj` + state0 : :obj:`.Qobj` Initial state density matrix :math:`\rho(t_0)` or state vector :math:`\psi(t_0)`. If 'state0' is 'None', then the steady state will be used as the initial state. The 'steady-state' is only implemented @@ -116,11 +116,11 @@ def correlation_2op_2t(H, state0, tlist, taulist, c_ops, a_op, b_op, taulist : array_like List of times for :math:`\tau`. taulist must be positive and contain the element `0`. - c_ops : list of {:class:`Qobj`, :class:`QobjEvo`} + c_ops : list of {:obj:`.Qobj`, :obj:`.QobjEvo`} List of collapse operators - a_op : :class:`Qobj`, :class:`QobjEvo` + a_op : :obj:`.Qobj`, :obj:`.QobjEvo` Operator A. - b_op : :class:`Qobj`, :class:`QobjEvo` + b_op : :obj:`.Qobj`, :obj:`.QobjEvo` Operator B. reverse : bool {False} If `True`, calculate :math:`\left` instead of @@ -175,9 +175,9 @@ def correlation_3op_1t(H, state0, taulist, c_ops, a_op, b_op, c_op, Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo` + H : :obj:`.Qobj`, :obj:`.QobjEvo` System Hamiltonian, may be time-dependent for solver choice of `me`. - state0 : :class:`Qobj` + state0 : :obj:`.Qobj` Initial state density matrix :math:`\rho(t_0)` or state vector :math:`\psi(t_0)`. If 'state0' is 'None', then the steady state will be used as the initial state. The 'steady-state' is only implemented @@ -185,13 +185,13 @@ def correlation_3op_1t(H, state0, taulist, c_ops, a_op, b_op, c_op, taulist : array_like List of times for :math:`\tau`. taulist must be positive and contain the element `0`. - c_ops : list of {:class:`Qobj`, :class:`QobjEvo`} + c_ops : list of {:obj:`.Qobj`, :obj:`.QobjEvo`} List of collapse operators - a_op : :class:`Qobj`, :class:`QobjEvo` + a_op : :obj:`.Qobj`, :obj:`.QobjEvo` Operator A. - b_op : :class:`Qobj`, :class:`QobjEvo` + b_op : :obj:`.Qobj`, :obj:`.QobjEvo` Operator B. - c_op : :class:`Qobj`, :class:`QobjEvo` + c_op : :obj:`.Qobj`, :obj:`.QobjEvo` Operator C. solver : str {'me', 'es'} Choice of solver, `me` for master-equation, and `es` for exponential @@ -234,9 +234,9 @@ def correlation_3op_2t(H, state0, tlist, taulist, c_ops, a_op, b_op, c_op, Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo` + H : :obj:`.Qobj`, :obj:`.QobjEvo` System Hamiltonian, may be time-dependent for solver choice of `me`. - state0 : :class:`Qobj` + state0 : :obj:`.Qobj` Initial state density matrix :math:`\rho(t_0)` or state vector :math:`\psi(t_0)`. If 'state0' is 'None', then the steady state will be used as the initial state. The 'steady-state' is only implemented @@ -249,13 +249,13 @@ def correlation_3op_2t(H, state0, tlist, taulist, c_ops, a_op, b_op, c_op, taulist : array_like List of times for :math:`\tau`. taulist must be positive and contain the element `0`. - c_ops : list of {:class:`Qobj`, :class:`QobjEvo`} + c_ops : list of {:obj:`.Qobj`, :obj:`.QobjEvo`} List of collapse operators - a_op : :class:`Qobj`, :class:`QobjEvo` + a_op : :obj:`.Qobj`, :obj:`.QobjEvo` Operator A. - b_op : :class:`Qobj`, :class:`QobjEvo` + b_op : :obj:`.Qobj`, :obj:`.QobjEvo` Operator B. - c_op : :class:`Qobj`, :class:`QobjEvo` + c_op : :obj:`.Qobj`, :obj:`.QobjEvo` Operator C. solver : str {'me', 'es'} Choice of solver, `me` for master-equation, and `es` for exponential @@ -310,9 +310,9 @@ def coherence_function_g1( Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo` + H : :obj:`.Qobj`, :obj:`.QobjEvo` System Hamiltonian, may be time-dependent for solver choice of `me`. - state0 : :class:`Qobj` + state0 : :obj:`.Qobj` Initial state density matrix :math:`\rho(t_0)` or state vector :math:`\psi(t_0)`. If 'state0' is 'None', then the steady state will be used as the initial state. The 'steady-state' is only implemented @@ -320,9 +320,9 @@ def coherence_function_g1( taulist : array_like List of times for :math:`\tau`. taulist must be positive and contain the element `0`. - c_ops : list of {:class:`Qobj`, :class:`QobjEvo`} + c_ops : list of {:obj:`.Qobj`, :obj:`.QobjEvo`} List of collapse operators - a_op : :class:`Qobj`, :class:`QobjEvo` + a_op : :obj:`.Qobj`, :obj:`.QobjEvo` Operator A. solver : str {'me', 'es'} Choice of solver, `me` for master-equation, and `es` for exponential @@ -369,9 +369,9 @@ def coherence_function_g2(H, state0, taulist, c_ops, a_op, solver="me", Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo` + H : :obj:`.Qobj`, :obj:`.QobjEvo` System Hamiltonian, may be time-dependent for solver choice of `me`. - state0 : :class:`Qobj` + state0 : :obj:`.Qobj` Initial state density matrix :math:`\rho(t_0)` or state vector :math:`\psi(t_0)`. If 'state0' is 'None', then the steady state will be used as the initial state. The 'steady-state' is only implemented @@ -382,7 +382,7 @@ def coherence_function_g2(H, state0, taulist, c_ops, a_op, solver="me", c_ops : list List of collapse operators, may be time-dependent for solver choice of `me`. - a_op : :class:`Qobj` + a_op : :obj:`.Qobj` Operator A. args : dict Dictionary of arguments to be passed to solver. @@ -434,16 +434,16 @@ def correlation_3op(solver, state0, tlist, taulist, A=None, B=None, C=None): :math:`\left`. - from a open system :class:`Solver`. + from a open system :class:`.Solver`. Note: it is not possible to calculate a physically meaningful correlation where :math:`\tau<0`. Parameters ---------- - solver : :class:`MESolver`, :class:`BRSolver` + solver : :class:`.MESolver`, :class:`.BRSolver` Qutip solver for an open system. - state0 : :class:`Qobj` + state0 : :obj:`.Qobj` Initial state density matrix :math:`\rho(t_0)` or state vector :math:`\psi(t_0)`. tlist : array_like @@ -452,7 +452,7 @@ def correlation_3op(solver, state0, tlist, taulist, A=None, B=None, C=None): taulist : array_like List of times for :math:`\tau`. taulist must be positive and contain the element `0`. - A, B, C: :class:`Qobj`, :class:`QobjEvo`, optional, default=None + A, B, C: :obj:`.Qobj`, :obj:`.QobjEvo`, optional, default=None Operators ``A``, ``B``, ``C`` from the equation ```` in the Schrodinger picture. They do not need to be all provided. For exemple, if ``A`` is not provided, ```` is computed. diff --git a/qutip/solver/countstat.py b/qutip/solver/countstat.py index 270960a1ed..a37a66e0ea 100644 --- a/qutip/solver/countstat.py +++ b/qutip/solver/countstat.py @@ -30,14 +30,14 @@ def countstat_current(L, c_ops=None, rhoss=None, J_ops=None): Parameters ---------- - L : :class:`qutip.Qobj` + L : :class:`.Qobj` Qobj representing the system Liouvillian. c_ops : array / list (optional) List of current collapse operators. Required if either ``rhoss`` or ``J_ops`` is not given. - rhoss : :class:`qutip.Qobj` (optional) + rhoss : :class:`.Qobj` (optional) The steadystate density matrix for the given system Liouvillian ``L`` and collapse operators. If not given, it defaults to ``steadystate(L, c_ops)``. @@ -155,13 +155,13 @@ def countstat_current_noise(L, c_ops, wlist=None, rhoss=None, J_ops=None, Parameters ---------- - L : :class:`qutip.Qobj` + L : :class:`.Qobj` Qobj representing the system Liouvillian. c_ops : array / list List of current collapse operators. - rhoss : :class:`qutip.Qobj` (optional) + rhoss : :class:`.Qobj` (optional) The steadystate density matrix corresponding the system Liouvillian `L`. diff --git a/qutip/solver/cy/nm_mcsolve.pyx b/qutip/solver/cy/nm_mcsolve.pyx index 41dff11a97..bf58763163 100644 --- a/qutip/solver/cy/nm_mcsolve.pyx +++ b/qutip/solver/cy/nm_mcsolve.pyx @@ -21,7 +21,7 @@ cdef class RateShiftCoefficient(Coefficient): Parameters ---------- - coeffs : list of :class:`Coefficient` + coeffs : list of :obj:`.Coefficient` The list of coefficients to determine the rate shift of. """ def __init__(self, list coeffs): @@ -34,7 +34,7 @@ cdef class RateShiftCoefficient(Coefficient): """ Replace the arguments (``args``) of a coefficient. - Returns a new :obj:`Coefficient` if the coefficient has arguments, or + Returns a new :obj:`.Coefficient` if the coefficient has arguments, or the original coefficient if it does not. Arguments to replace may be supplied either in a dictionary as the first position argument, or passed as keywords, or as a combination of the two. Arguments not @@ -70,7 +70,7 @@ cdef class RateShiftCoefficient(Coefficient): return real(self._call(t)) cpdef Coefficient copy(self): - """Return a copy of the :obj:`Coefficient`.""" + """Return a copy of the :obj:`.Coefficient`.""" return RateShiftCoefficient( [coeff.copy() for coeff in self.coeffs], ) @@ -89,7 +89,7 @@ cdef class SqrtRealCoefficient(Coefficient): """ Replace the arguments (``args``) of a coefficient. - Returns a new :obj:`Coefficient` if the coefficient has arguments, or + Returns a new :obj:`.Coefficient` if the coefficient has arguments, or the original coefficient if it does not. Arguments to replace may be supplied either in a dictionary as the first position argument, or passed as keywords, or as a combination of the two. Arguments not @@ -112,5 +112,5 @@ cdef class SqrtRealCoefficient(Coefficient): return sqrt(real(self.base._call(t))) cpdef Coefficient copy(self): - """Return a copy of the :obj:`Coefficient`.""" + """Return a copy of the :obj:`.Coefficient`.""" return SqrtRealCoefficient(self.base.copy()) diff --git a/qutip/solver/floquet.py b/qutip/solver/floquet.py index d18ae33421..c8e7757aa7 100644 --- a/qutip/solver/floquet.py +++ b/qutip/solver/floquet.py @@ -24,10 +24,10 @@ class FloquetBasis: Attributes ---------- - U : :class:`Propagator` + U : :class:`.Propagator` The propagator of the Hamiltonian over one period. - evecs : :class:`qutip.data.Data` + evecs : :class:`.Data` Matrix where each column is an initial Floquet mode. e_quasi : np.ndarray[float] @@ -47,7 +47,7 @@ def __init__( """ Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo`, QobjEvo compatible format. + H : :obj:`.Qobj`, :obj:`.QobjEvo`, QobjEvo compatible format. System Hamiltonian, with period `T`. T : float @@ -129,7 +129,7 @@ def mode(self, t, data=False): Returns ------- - output : list[:class:`Qobj`], :class:`qutip.data.Data` + output : list[:obj:`.Qobj`], :class:`.Data` A list of Floquet states for the time ``t`` or the states as column in a single matrix. """ @@ -160,7 +160,7 @@ def state(self, t, data=False): Returns ------- - output : list[:class:`Qobj`], :class:`qutip.data.Data` + output : list[:obj:`.Qobj`], :class:`.Data` A list of Floquet states for the time ``t`` or the states as column in a single matrix. """ @@ -181,7 +181,7 @@ def from_floquet_basis(self, floquet_basis, t=0): Parameters ---------- - floquet_basis : :class:`Qobj`, :class:`qutip.data.Data` + floquet_basis : :obj:`.Qobj`, :class:`.Data` Initial state in the Floquet basis at time ``t``. May be either a ket or density matrix. @@ -190,7 +190,7 @@ def from_floquet_basis(self, floquet_basis, t=0): Returns ------- - output : :class:`Qobj`, :class:`qutip.data.Data` + output : :obj:`.Qobj`, :class:`.Data` The state in the lab basis. The return type is the same as the type of the input state. """ @@ -219,7 +219,7 @@ def to_floquet_basis(self, lab_basis, t=0): Parameters ---------- - lab_basis : :class:`Qobj`, :class:`qutip.data.Data` + lab_basis : :obj:`.Qobj`, :class:`.Data` Initial state in the lab basis. t : float [0] @@ -227,7 +227,7 @@ def to_floquet_basis(self, lab_basis, t=0): Returns ------- - output : :class:`Qobj`, :class:`qutip.data.Data` + output : :obj:`.Qobj`, :class:`.Data` The state in the Floquet basis. The return type is the same as the type of the input state. """ @@ -283,7 +283,7 @@ def _floquet_X_matrices(floquet_basis, c_ops, kmax, ntimes=100): floquet_basis : :class:`FloquetBasis` The system Hamiltonian wrapped in a FloquetBasis object. - c_ops : list of :class:`Qobj` + c_ops : list of :obj:`.Qobj` The collapse operators describing the dissipation. kmax : int @@ -294,7 +294,7 @@ def _floquet_X_matrices(floquet_basis, c_ops, kmax, ntimes=100): Returns ------- - X : list of dict of :class:`qutip.data.Data` + X : list of dict of :class:`.Data` A dict of the sidebands ``k`` for the X matrices of each c_ops """ T = floquet_basis.T @@ -321,7 +321,7 @@ def _floquet_gamma_matrices(X, delta, J_cb): Parameters ---------- - X : list of dict of :class:`qutip.data.Data` + X : list of dict of :class:`.Data` Floquet X matrices created by :func:`_floquet_X_matrices`. delta : np.ndarray @@ -336,7 +336,7 @@ def _floquet_gamma_matrices(X, delta, J_cb): Returns ------- - gammas : dict of :class:`qutip.data.Data` + gammas : dict of :class:`.Data` A dict mapping the sidebands ``k`` to their gamma matrices. """ N = delta.shape[0] @@ -369,7 +369,7 @@ def _floquet_A_matrix(delta, gamma, w_th): delta : np.ndarray Floquet delta tensor created by :func:`_floquet_delta_tensor`. - gamma : dict of :class:`qutip.data.Data` + gamma : dict of :class:`.Data` Floquet gamma matrices created by :func:`_floquet_gamma_matrices`. w_th : float @@ -411,7 +411,7 @@ def _floquet_master_equation_tensor(A): Parameters ---------- - A : :class:`qutip.data.Data` + A : :class:`.Data` Floquet-Markov master equation rate matrix. Returns @@ -447,13 +447,13 @@ def floquet_tensor(H, c_ops, spectra_cb, T=0, w_th=0.0, kmax=5, nT=100): Parameters ---------- - H : :class:`QobjEvo` + H : :obj:`.QobjEvo` Periodic Hamiltonian T : float The period of the time-dependence of the hamiltonian. - c_ops : list of :class:`qutip.qobj` + c_ops : list of :class:`.Qobj` list of collapse operators. spectra_cb : list callback functions @@ -494,18 +494,18 @@ def fsesolve(H, psi0, tlist, e_ops=None, T=0.0, args=None, options=None): Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo`, :class:`QobjEvo` compatible format. - Periodic system Hamiltonian as :class:`QobjEvo`. List of - [:class:`Qobj`, :class:`Coefficient`] or callable that - can be made into :class:`QobjEvo` are also accepted. + H : :obj:`.Qobj`, :obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format. + Periodic system Hamiltonian as :obj:`.QobjEvo`. List of + [:obj:`.Qobj`, :obj:`.Coefficient`] or callable that + can be made into :obj:`.QobjEvo` are also accepted. - psi0 : :class:`qutip.qobj` + psi0 : :class:`.Qobj` Initial state vector (ket). If an operator is provided, tlist : *list* / *array* List of times for :math:`t`. - e_ops : list of :class:`qutip.qobj` / callback function, optional + e_ops : list of :class:`.Qobj` / callback function, optional List of operators for which to evaluate expectation values. If this list is empty, the state vectors for each time in `tlist` will be returned instead of expectation values. @@ -531,8 +531,8 @@ def fsesolve(H, psi0, tlist, e_ops=None, T=0.0, args=None, options=None): Returns ------- - output : :class:`qutip.solver.Result` - An instance of the class :class:`qutip.solver.Result`, which + output : :class:`.Result` + An instance of the class :class:`.Result`, which contains either an *array* of expectation values or an array of state vectors, for the times specified by `tlist`. """ @@ -576,22 +576,22 @@ def fmmesolve( Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo`, :class:`QobjEvo` compatible format. - Periodic system Hamiltonian as :class:`QobjEvo`. List of - [:class:`Qobj`, :class:`Coefficient`] or callable that - can be made into :class:`QobjEvo` are also accepted. + H : :obj:`.Qobj`, :obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format. + Periodic system Hamiltonian as :obj:`.QobjEvo`. List of + [:obj:`.Qobj`, :obj:`.Coefficient`] or callable that + can be made into :obj:`.QobjEvo` are also accepted. - rho0 / psi0 : :class:`qutip.Qobj` + rho0 / psi0 : :class:`.Qobj` Initial density matrix or state vector (ket). tlist : *list* / *array* List of times for :math:`t`. - c_ops : list of :class:`qutip.Qobj` + c_ops : list of :class:`.Qobj` List of collapse operators. Time dependent collapse operators are not supported. - e_ops : list of :class:`qutip.Qobj` / callback function + e_ops : list of :class:`.Qobj` / callback function List of operators for which to evaluate expectation values. The states are reverted to the lab basis before applying the @@ -653,9 +653,9 @@ def fmmesolve( Returns ------- - result: :class:`qutip.Result` + result: :class:`.Result` - An instance of the class :class:`qutip.Result`, which contains + An instance of the class :class:`.Result`, which contains the expectation values for the times specified by `tlist`, and/or the state density matrices corresponding to the times. """ @@ -724,14 +724,14 @@ class FMESolver(MESolver): Parameters ---------- - floquet_basis : :class:`qutip.FloquetBasis` + floquet_basis : :class:`.FloquetBasis` The system Hamiltonian wrapped in a FloquetBasis object. Choosing a different integrator for the ``floquet_basis`` than for the evolution of the floquet state can improve the performance. - a_ops : list of tuple(:class:`qutip.Qobj`, callable) + a_ops : list of tuple(:class:`.Qobj`, callable) List of collapse operators and the corresponding function for the noise - power spectrum. The collapse operator must be a :class:`Qobj` and + power spectrum. The collapse operator must be a :obj:`.Qobj` and cannot be time dependent. The spectrum function must take and return an numpy array. @@ -815,7 +815,7 @@ def start(self, state0, t0, *, floquet=False): Parameters ---------- - state0 : :class:`Qobj` + state0 : :obj:`.Qobj` Initial state of the evolution. t0 : double @@ -831,7 +831,7 @@ def start(self, state0, t0, *, floquet=False): def step(self, t, *, args=None, copy=True, floquet=False): """ - Evolve the state to ``t`` and return the state as a :class:`Qobj`. + Evolve the state to ``t`` and return the state as a :obj:`.Qobj`. Parameters ---------- @@ -848,10 +848,11 @@ def step(self, t, *, args=None, copy=True, floquet=False): args : dict, optional {None} Not supported - .. note:: - The state must be initialized first by calling ``start`` or - ``run``. If ``run`` is called, ``step`` will continue from the last - time and state obtained. + Notes + ----- + The state must be initialized first by calling ``start`` or + ``run``. If ``run`` is called, ``step`` will continue from the last + time and state obtained. """ if args: raise ValueError("FMESolver cannot update arguments") @@ -868,12 +869,12 @@ def run(self, state0, tlist, *, floquet=False, args=None, e_ops=None): For a ``state0`` at time ``tlist[0]`` do the evolution as directed by ``rhs`` and for each time in ``tlist`` store the state and/or - expectation values in a :class:`Result`. The evolution method and + expectation values in a :class:`.Result`. The evolution method and stored results are determined by ``options``. Parameters ---------- - state0 : :class:`Qobj` + state0 : :obj:`.Qobj` Initial state of the evolution. tlist : list of double @@ -895,7 +896,7 @@ def run(self, state0, tlist, *, floquet=False, args=None, e_ops=None): Returns ------- - results : :class:`qutip.solver.FloquetResult` + results : :class:`FloquetResult` Results of the evolution. States and/or expect will be saved. You can control the saved data in the options. """ diff --git a/qutip/solver/floquet_bwcomp.py b/qutip/solver/floquet_bwcomp.py index 8ee683ffd8..6f3a059466 100644 --- a/qutip/solver/floquet_bwcomp.py +++ b/qutip/solver/floquet_bwcomp.py @@ -28,7 +28,7 @@ def floquet_modes(H, T, args=None, sort=False, U=None, options=None): Calculate the initial Floquet modes Phi_alpha(0) for a driven system with period T. - Deprecated from qutip v5. Use :class:`FloquetBasis` instead: + Deprecated from qutip v5. Use :class:`.FloquetBasis` instead: fbasis = FloquetBasis(H, T, args=args, options=options, sort=sort) f_mode_0 = fbasis.mode(0) @@ -48,7 +48,7 @@ def floquet_modes_t(f_modes_0, f_energies, t, H, T, args=None, options=None): Calculate the Floquet modes at times tlist Phi_alpha(tlist) propagting the initial Floquet modes Phi_alpha(0). - Deprecated from qutip v5. Use :class:`FloquetBasis` instead: + Deprecated from qutip v5. Use :class:`.FloquetBasis` instead: fbasis = FloquetBasis(H, T, args=args, options=options) f_mode_t = fbasis.mode(t) @@ -68,7 +68,7 @@ def floquet_modes_table( period. Can later be used as a table to look up the floquet modes for any time. - Deprecated from qutip v5. Use :class:`FloquetBasis` instead: + Deprecated from qutip v5. Use :class:`.FloquetBasis` instead: fbasis = FloquetBasis(H, T, args=args, options=options, precompute=tlist) """ @@ -80,7 +80,7 @@ def floquet_modes_t_lookup(f_modes_table_t, t, T): Lookup the floquet mode at time t in the pre-calculated table of floquet modes in the first period of the time-dependence. - Deprecated from qutip v5. Use :class:`FloquetBasis` instead: + Deprecated from qutip v5. Use :class:`.FloquetBasis` instead: f_modes_table_t = fbasis = FloquetBasis(...) f_mode_t = f_modes_table_t.mode(t) @@ -95,7 +95,7 @@ def floquet_states(f_modes_t, f_energies, t): """ Evaluate the floquet states at time t given the Floquet modes at that time. - Deprecated from qutip v5. Use :class:`FloquetBasis` instead: + Deprecated from qutip v5. Use :class:`.FloquetBasis` instead: fbasis = FloquetBasis(H, T, args=args, options=options) f_state_t = fbasis.state(t) @@ -114,7 +114,7 @@ def floquet_states_t(f_modes_0, f_energies, t, H, T, args=None, options=None): """ Evaluate the floquet states at time t given the initial Floquet modes. - Deprecated from qutip v5. Use :class:`FloquetBasis` instead: + Deprecated from qutip v5. Use :class:`.FloquetBasis` instead: fbasis = FloquetBasis(H, T, args=args, options=options) f_state_t = fbasis.state(t) @@ -132,7 +132,7 @@ def floquet_wavefunction(f_modes_t, f_energies, f_coeff, t): Evaluate the wavefunction for a time t using the Floquet state decompositon, given the Floquet modes at time `t`. - Deprecated from qutip v5. Use :class:`FloquetBasis` instead: + Deprecated from qutip v5. Use :class:`.FloquetBasis` instead: fbasis = FloquetBasis(H, T, args=args, options=options) psi_t = fbasis.from_floquet_basis(f_coeff, t) @@ -150,7 +150,7 @@ def floquet_wavefunction_t( Evaluate the wavefunction for a time t using the Floquet state decompositon, given the initial Floquet modes. - Deprecated from qutip v5. Use :class:`FloquetBasis` instead: + Deprecated from qutip v5. Use :class:`.FloquetBasis` instead: fbasis = FloquetBasis(H, T, args=args, options=options) psi_t = fbasis.from_floquet_basis(f_coeff, t) @@ -168,7 +168,7 @@ def floquet_state_decomposition(f_states, f_energies, psi): Decompose the wavefunction `psi` (typically an initial state) in terms of the Floquet states, :math:`\psi = \sum_\alpha c_\alpha \psi_\alpha(0)`. - Deprecated from qutip v5. Use :class:`FloquetBasis` instead: + Deprecated from qutip v5. Use :class:`.FloquetBasis` instead: fbasis = FloquetBasis(H, T, args=args, options=options) f_coeff = fbasis.to_floquet_basis(psi) @@ -208,9 +208,9 @@ def floquet_master_equation_rates( No longer used. f_energies : Any No longer used. - c_op : :class:`qutip.qobj` + c_op : :class:`.Qobj` The collapse operators describing the dissipation. - H : :class:`qutip.qobj` + H : :class:`.Qobj` System Hamiltonian, time-dependent with period `T`. T : float The period of the time-dependence of the hamiltonian. diff --git a/qutip/solver/heom/bofin_solvers.py b/qutip/solver/heom/bofin_solvers.py index ee9717f77e..fe55aa7c70 100644 --- a/qutip/solver/heom/bofin_solvers.py +++ b/qutip/solver/heom/bofin_solvers.py @@ -136,8 +136,8 @@ def idx(self, label): int The index of the label within the list of ADO labels. - .. note:: - + Notes + ----- This implementation of the ``.idx(...)`` method is just for reference and documentation. To avoid the cost of a Python function call, it is replaced with @@ -326,7 +326,7 @@ class HierarchyADOsState: Parameters ---------- - rho : :class:`~qutip.Qobj` + rho : :class:`.Qobj` The current state of the system (i.e. the 0th component of the hierarchy). ados : :class:`HierarchyADOs` @@ -427,10 +427,10 @@ def heomsolve( Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo` + H : :obj:`.Qobj`, :obj:`.QobjEvo` Possibly time-dependent system Liouvillian or Hamiltonian as a Qobj or - QobjEvo. list of [:class:`Qobj`, :class:`Coefficient`] or callable that - can be made into :class:`QobjEvo` are also accepted. + QobjEvo. list of [:obj:`.Qobj`, :obj:`.Coefficient`] or callable that + can be made into :obj:`.QobjEvo` are also accepted. bath : Bath or list of Bath A :obj:`Bath` containing the exponents of the expansion of the @@ -444,9 +444,9 @@ def heomsolve( The maximum depth of the heirarchy (i.e. the maximum number of bath exponent "excitations" to retain). - state0 : :class:`~Qobj` or :class:`~HierarchyADOsState` or array-like - If ``rho0`` is a :class:`~Qobj` the it is the initial state - of the system (i.e. a :obj:`~Qobj` density matrix). + state0 : :obj:`.Qobj` or :class:`~HierarchyADOsState` or array-like + If ``rho0`` is a :obj:`.Qobj` the it is the initial state + of the system (i.e. a :obj:`.Qobj` density matrix). If it is a :class:`~HierarchyADOsState` or array-like, then ``rho0`` gives the initial state of all ADOs. @@ -465,8 +465,8 @@ def heomsolve( An ordered list of times at which to return the value of the state. e_ops : Qobj / QobjEvo / callable / list / dict / None, optional - A list or dictionary of operators as :class:`~Qobj`, - :class:`~QobjEvo` and/or callable functions (they can be mixed) or + A list or dictionary of operators as :obj:`.Qobj`, + :obj:`.QobjEvo` and/or callable functions (they can be mixed) or a single operator or callable function. For an operator ``op``, the result will be computed using ``(state * op).tr()`` and the state at each time ``t``. For callable functions, ``f``, the result is @@ -553,10 +553,10 @@ class HEOMSolver(Solver): Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo` + H : :obj:`.Qobj`, :obj:`.QobjEvo` Possibly time-dependent system Liouvillian or Hamiltonian as a Qobj or - QobjEvo. list of [:class:`Qobj`, :class:`Coefficient`] or callable that - can be made into :class:`QobjEvo` are also accepted. + QobjEvo. list of [:obj:`.Qobj`, :obj:`.Coefficient`] or callable that + can be made into :obj:`.QobjEvo` are also accepted. bath : Bath or list of Bath A :obj:`Bath` containing the exponents of the expansion of the @@ -581,7 +581,7 @@ class HEOMSolver(Solver): The description of the hierarchy constructed from the given bath and maximum depth. - rhs : :obj:`QobjEvo` + rhs : :obj:`.QobjEvo` The right-hand side (RHS) of the hierarchy evolution ODE. Internally the system and bath coupling operators are converted to :class:`qutip.data.CSR` instances during construction of the RHS, @@ -967,9 +967,9 @@ def run(self, state0, tlist, *, args=None, e_ops=None): Parameters ---------- - state0 : :class:`~Qobj` or :class:`~HierarchyADOsState` or array-like - If ``rho0`` is a :class:`~Qobj` the it is the initial state - of the system (i.e. a :obj:`~Qobj` density matrix). + state0 : :obj:`.Qobj` or :class:`~HierarchyADOsState` or array-like + If ``rho0`` is a :obj:`.Qobj` the it is the initial state + of the system (i.e. a :obj:`.Qobj` density matrix). If it is a :class:`~HierarchyADOsState` or array-like, then ``rho0`` gives the initial state of all ADOs. @@ -991,8 +991,8 @@ def run(self, state0, tlist, *, args=None, e_ops=None): Change the ``args`` of the RHS for the evolution. e_ops : Qobj / QobjEvo / callable / list / dict / None, optional - A list or dictionary of operators as :class:`~Qobj`, - :class:`~QobjEvo` and/or callable functions (they can be mixed) or + A list or dictionary of operators as :obj:`.Qobj`, + :obj:`.QobjEvo` and/or callable functions (they can be mixed) or a single operator or callable function. For an operator ``op``, the result will be computed using ``(state * op).tr()`` and the state at each time ``t``. For callable functions, ``f``, the result is @@ -1092,7 +1092,7 @@ def start(self, state0, t0): Parameters ---------- - state0 : :class:`Qobj` + state0 : :obj:`.Qobj` Initial state of the evolution. This may provide either just the initial density matrix of the system, or the full set of ADOs for the hierarchy. See the documentation for ``rho0`` in the diff --git a/qutip/solver/integrator/explicit_rk.pyx b/qutip/solver/integrator/explicit_rk.pyx index 33749a90b7..a643de4a44 100644 --- a/qutip/solver/integrator/explicit_rk.pyx +++ b/qutip/solver/integrator/explicit_rk.pyx @@ -65,7 +65,7 @@ cdef Data iadd_data(Data left, Data right, double complex factor): cdef class Explicit_RungeKutta: """ Qutip implementation of Runge Kutta ODE. - Works in :class:`Data` allowing solving using sparse and gpu data. + Works in :class:`.Data` allowing solving using sparse and gpu data. Parameters ---------- diff --git a/qutip/solver/integrator/integrator.py b/qutip/solver/integrator/integrator.py index 44df5546f1..7b547403cc 100644 --- a/qutip/solver/integrator/integrator.py +++ b/qutip/solver/integrator/integrator.py @@ -20,7 +20,7 @@ class Integrator: """ A wrapper around ODE solvers. It ensures a common interface for Solver usage. - It takes and return states as :class:`qutip.core.data.Data`, it may return + It takes and return states as :class:`.Data`, it may return a different data-type than the input type. Parameters diff --git a/qutip/solver/krylovsolve.py b/qutip/solver/krylovsolve.py index 99fa78b229..cfefeb9748 100644 --- a/qutip/solver/krylovsolve.py +++ b/qutip/solver/krylovsolve.py @@ -25,12 +25,12 @@ def krylovsolve( Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo`, :class:`QobjEvo` compatible format. + H : :obj:`.Qobj`, :obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format. System Hamiltonian as a Qobj or QobjEvo for time-dependent - Hamiltonians. List of [:class:`Qobj`, :class:`Coefficient`] or callable - that can be made into :class:`QobjEvo` are also accepted. + Hamiltonians. List of [:obj:`.Qobj`, :obj:`.Coefficient`] or callable + that can be made into :obj:`.QobjEvo` are also accepted. - psi0 : :class:`qutip.qobj` + psi0 : :class:`.Qobj` initial state vector (ket) or initial unitary operator `psi0 = U` @@ -41,7 +41,7 @@ def krylovsolve( Dimension of Krylov approximation subspaces used for the time evolution approximation. - e_ops : :class:`qutip.qobj`, callable, or list. + e_ops : :class:`.Qobj`, callable, or list. Single operator or list of operators for which to evaluate expectation values or callable or list of callable. Callable signature must be, `f(t: float, state: Qobj)`. @@ -86,7 +86,7 @@ def krylovsolve( Returns ------- - result: :class:`qutip.Result` + result: :class:`.Result` An instance of the class :class:`qutip.Result`, which contains a *list of array* `result.expect` of expectation values for the times diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index 175c17bfd7..ce4e1974dc 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -20,13 +20,13 @@ def mcsolve(H, state, tlist, c_ops=(), e_ops=None, ntraj=500, *, Parameters ---------- - H : :class:`qutip.Qobj`, :class:`qutip.QobjEvo`, ``list``, callable. + H : :class:`.Qobj`, :class:`.QobjEvo`, ``list``, callable. System Hamiltonian as a Qobj, QobjEvo. It can also be any input type - that QobjEvo accepts (see :class:`qutip.QobjEvo`'s documentation). + that QobjEvo accepts (see :class:`.QobjEvo`'s documentation). ``H`` can also be a superoperator (liouvillian) if some collapse operators are to be treated deterministically. - state : :class:`qutip.Qobj` + state : :class:`.Qobj` Initial state vector. tlist : array_like @@ -34,7 +34,7 @@ def mcsolve(H, state, tlist, c_ops=(), e_ops=None, ntraj=500, *, c_ops : list A ``list`` of collapse operators in any input type that QobjEvo accepts - (see :class:`qutip.QobjEvo`'s documentation). They must be operators + (see :class:`.QobjEvo`'s documentation). They must be operators even if ``H`` is a superoperator. If none are given, the solver will defer to ``sesolve`` or ``mesolve``. @@ -349,15 +349,15 @@ class MCSolver(MultiTrajSolver): Parameters ---------- - H : :class:`qutip.Qobj`, :class:`qutip.QobjEvo`, ``list``, callable. + H : :class:`.Qobj`, :class:`.QobjEvo`, ``list``, callable. System Hamiltonian as a Qobj, QobjEvo. It can also be any input type - that QobjEvo accepts (see :class:`qutip.QobjEvo`'s documentation). + that QobjEvo accepts (see :class:`.QobjEvo`'s documentation). ``H`` can also be a superoperator (liouvillian) if some collapse operators are to be treated deterministically. c_ops : list A ``list`` of collapse operators in any input type that QobjEvo accepts - (see :class:`qutip.QobjEvo`'s documentation). They must be operators + (see :class:`.QobjEvo`'s documentation). They must be operators even if ``H`` is a superoperator. options : dict, [optional] diff --git a/qutip/solver/mesolve.py b/qutip/solver/mesolve.py index 023c477eda..ac3fb4b285 100644 --- a/qutip/solver/mesolve.py +++ b/qutip/solver/mesolve.py @@ -39,8 +39,8 @@ def mesolve(H, rho0, tlist, c_ops=None, e_ops=None, args=None, options=None, **Time-dependent operators** - For time-dependent problems, `H` and `c_ops` can be a :class:`QobjEvo` or - object that can be interpreted as :class:`QobjEvo` such as a list of + For time-dependent problems, `H` and `c_ops` can be a :obj:`.QobjEvo` or + object that can be interpreted as :obj:`.QobjEvo` such as a list of (Qobj, Coefficient) pairs or a function. **Additional options** @@ -50,30 +50,30 @@ def mesolve(H, rho0, tlist, c_ops=None, e_ops=None, args=None, options=None, `store_final_state` options can be used to store states even though expectation values are requested via the `e_ops` argument. - .. note:: - - When no collapse operator are given and the `H` is not a superoperator, - it will defer to :func:`sesolve`. + Notes + ----- + When no collapse operator are given and the `H` is not a superoperator, + it will defer to :func:`sesolve`. Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo`, :class:`QobjEvo` compatible format. + H : :obj:`.Qobj`, :obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format. Possibly time-dependent system Liouvillian or Hamiltonian as a Qobj or - QobjEvo. List of [:class:`Qobj`, :class:`Coefficient`] or callable that - can be made into :class:`QobjEvo` are also accepted. + QobjEvo. List of [:obj:`.Qobj`, :obj:`.Coefficient`] or callable that + can be made into :obj:`.QobjEvo` are also accepted. - rho0 : :class:`Qobj` + rho0 : :obj:`.Qobj` initial density matrix or state vector (ket). tlist : *list* / *array* list of times for :math:`t`. - c_ops : list of (:class:`QobjEvo`, :class:`QobjEvo` compatible format) + c_ops : list of (:obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format) Single collapse operator, or list of collapse operators, or a list of Liouvillian superoperators. None is equivalent to an empty list. - e_ops : list of :class:`Qobj` / callback function + e_ops : list of :obj:`.Qobj` / callback function Single operator or list of operators for which to evaluate expectation values or callable or list of callable. Callable signature must be, `f(t: float, state: Qobj)`. @@ -117,9 +117,9 @@ def mesolve(H, rho0, tlist, c_ops=None, e_ops=None, args=None, options=None, Returns ------- - result: :class:`qutip.Result` + result: :obj:`qutip.Result` - An instance of the class :class:`qutip.Result`, which contains + An instance of the class :obj:`qutip.Result`, which contains a *list of array* `result.expect` of expectation values for the times specified by `tlist`, and/or a *list* `result.states` of state vectors or density matrices corresponding to the times in `tlist` [if `e_ops` @@ -162,12 +162,12 @@ class MESolver(SESolver): Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo` + H : :obj:`.Qobj`, :obj:`.QobjEvo` Possibly time-dependent system Liouvillian or Hamiltonian as a Qobj or - QobjEvo. List of [:class:`Qobj`, :class:`Coefficient`] or callable that - can be made into :class:`QobjEvo` are also accepted. + QobjEvo. List of [:obj:`.Qobj`, :obj:`.Coefficient`] or callable that + can be made into :obj:`.QobjEvo` are also accepted. - c_ops : list of :class:`Qobj`, :class:`QobjEvo` + c_ops : list of :obj:`.Qobj`, :obj:`.QobjEvo` Single collapse operator, or list of collapse operators, or a list of Liouvillian superoperators. None is equivalent to an empty list. @@ -175,7 +175,7 @@ class MESolver(SESolver): Options for the solver, see :obj:`MESolver.options` and `Integrator <./classes.html#classes-ode>`_ for a list of all options. - attributes + Attributes ---------- stats: dict Diverse diagnostic statistics of the evolution. @@ -183,7 +183,7 @@ class MESolver(SESolver): name = "mesolve" _avail_integrators = {} solver_options = { - "progress_bar": "text", + "progress_bar": "", "progress_kwargs": {"chunk_size":10}, "store_final_state": False, "store_states": None, diff --git a/qutip/solver/multitraj.py b/qutip/solver/multitraj.py index 6fcb1abeb9..7118be8a54 100644 --- a/qutip/solver/multitraj.py +++ b/qutip/solver/multitraj.py @@ -11,7 +11,7 @@ class MultiTrajSolver(Solver): """ Basic class for multi-trajectory evolutions. - As :class:`Solver` it can ``run`` or ``step`` evolution. + As :class:`.Solver` it can ``run`` or ``step`` evolution. It manages the random seed for each trajectory. The actual evolution is done by a single trajectory solver:: @@ -60,7 +60,7 @@ def start(self, state, t0, seed=None): Parameters ---------- - state : :class:`Qobj` + state : :obj:`.Qobj` Initial state of the evolution. t0 : double @@ -81,7 +81,7 @@ def start(self, state, t0, seed=None): def step(self, t, *, args=None, copy=True): """ - Evolve the state to ``t`` and return the state as a :class:`Qobj`. + Evolve the state to ``t`` and return the state as a :obj:`.Qobj`. Parameters ---------- @@ -131,12 +131,12 @@ def run(self, state, tlist, ntraj=1, *, For a ``state`` at time ``tlist[0]`` do the evolution as directed by ``rhs`` and for each time in ``tlist`` store the state and/or - expectation values in a :class:`Result`. The evolution method and + expectation values in a :class:`.Result`. The evolution method and stored results are determined by ``options``. Parameters ---------- - state : :class:`Qobj` + state : :obj:`.Qobj` Initial state of the evolution. tlist : list of double @@ -177,7 +177,7 @@ def run(self, state, tlist, ntraj=1, *, Returns ------- - results : :class:`qutip.solver.MultiTrajResult` + results : :class:`.MultiTrajResult` Results of the evolution. States and/or expect will be saved. You can control the saved data in the options. diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index 080a5e5aa9..cec9af73a6 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -38,13 +38,13 @@ def nm_mcsolve(H, state, tlist, ops_and_rates=(), e_ops=None, ntraj=500, *, Parameters ---------- - H : :class:`qutip.Qobj`, :class:`qutip.QobjEvo`, ``list``, callable. + H : :class:`.Qobj`, :class:`.QobjEvo`, ``list``, callable. System Hamiltonian as a Qobj, QobjEvo. It can also be any input type - that QobjEvo accepts (see :class:`qutip.QobjEvo`'s documentation). + that QobjEvo accepts (see :class:`.QobjEvo`'s documentation). ``H`` can also be a superoperator (liouvillian) if some collapse operators are to be treated deterministically. - state : :class:`qutip.Qobj` + state : :class:`.Qobj` Initial state vector. tlist : array_like @@ -52,7 +52,7 @@ def nm_mcsolve(H, state, tlist, ops_and_rates=(), e_ops=None, ntraj=500, *, ops_and_rates : list A ``list`` of tuples ``(L, Gamma)``, where the Lindblad operator ``L`` - is a :class:`qutip.Qobj` and ``Gamma`` represents the corresponding + is a :class:`.Qobj` and ``Gamma`` represents the corresponding rate, which is allowed to be negative. The Lindblad operators must be operators even if ``H`` is a superoperator. If none are given, the solver will defer to ``sesolve`` or ``mesolve``. Each rate ``Gamma`` @@ -149,7 +149,7 @@ def nm_mcsolve(H, state, tlist, ops_and_rates=(), e_ops=None, ntraj=500, *, Returns ------- - results : :class:`qutip.solver.NmmcResult` + results : :class:`.NmmcResult` Object storing all results from the simulation. Compared to a result returned by ``mcsolve``, this result contains the additional field ``trace`` (and ``runs_trace`` if ``store_final_state`` is set). Note @@ -290,21 +290,21 @@ def set_state(self, t, *args, **kwargs): class NonMarkovianMCSolver(MCSolver): """ Monte Carlo Solver for Lindblad equations with "rates" that may be - negative. The ``c_ops`` parameter of :class:`qutip.MCSolver` is replaced by + negative. The ``c_ops`` parameter of :class:`.MCSolver` is replaced by an ``ops_and_rates`` parameter to allow for negative rates. Options for the underlying ODE solver are given by the Options class. Parameters ---------- - H : :class:`qutip.Qobj`, :class:`qutip.QobjEvo`, ``list``, callable. + H : :class:`.Qobj`, :class:`.QobjEvo`, ``list``, callable. System Hamiltonian as a Qobj, QobjEvo. It can also be any input type - that QobjEvo accepts (see :class:`qutip.QobjEvo` documentation). + that QobjEvo accepts (see :class:`.QobjEvo` documentation). ``H`` can also be a superoperator (liouvillian) if some collapse operators are to be treated deterministically. ops_and_rates : list A ``list`` of tuples ``(L, Gamma)``, where the Lindblad operator ``L`` - is a :class:`qutip.Qobj` and ``Gamma`` represents the corresponding + is a :class:`.Qobj` and ``Gamma`` represents the corresponding rate, which is allowed to be negative. The Lindblad operators must be operators even if ``H`` is a superoperator. Each rate ``Gamma`` may be just a number (in the case of a constant rate) or, otherwise, specified diff --git a/qutip/solver/nonmarkov/transfertensor.py b/qutip/solver/nonmarkov/transfertensor.py index fd4f1721c7..9a360952fa 100644 --- a/qutip/solver/nonmarkov/transfertensor.py +++ b/qutip/solver/nonmarkov/transfertensor.py @@ -22,19 +22,19 @@ def ttmsolve(dynmaps, state0, times, e_ops=[], num_learning=0, options=None): Parameters ---------- - dynmaps : list of :class:`qutip.Qobj`, callable + dynmaps : list of :class:`.Qobj`, callable List of precomputed dynamical maps (superoperators) for the first times of ``times`` or a callback function that returns the superoperator at a given time. - state0 : :class:`qutip.Qobj` + state0 : :class:`.Qobj` Initial density matrix or state vector (ket). times : array_like List of times :math:`t_n` at which to compute results. Must be uniformily spaced. - e_ops : :class:`qutip.qobj`, callable, or list. + e_ops : :class:`.qobj`, callable, or list. Single operator or list of operators for which to evaluate expectation values or callable or list of callable. Callable signature must be, `f(t: float, state: Qobj)`. @@ -62,8 +62,8 @@ def ttmsolve(dynmaps, state0, times, e_ops=[], num_learning=0, options=None): Returns ------- - output: :class:`qutip.solver.Result` - An instance of the class :class:`qutip.solver.Result`. + output: :class:`.Result` + An instance of the class :class:`.Result`. .. [1] Javier Cerrillo and Jianshu Cao, Phys. Rev. Lett 112, 110401 (2014) """ @@ -148,7 +148,7 @@ def _generatetensors(dynmaps, threshold): Parameters ---------- - dynmaps : list of :class:`qutip.Qobj` + dynmaps : list of :class:`.Qobj` List of precomputed dynamical maps (superoperators) at the times specified in `learningtimes`. @@ -158,7 +158,7 @@ def _generatetensors(dynmaps, threshold): Returns ------- - Tensors, diffs: list of :class:`qutip.Qobj.` + Tensors, diffs: list of :class:`.Qobj.` A list of transfer tensors :math:`T_1,\dots,T_K` """ Tensors = [] diff --git a/qutip/solver/propagator.py b/qutip/solver/propagator.py index e85ad5c5a0..da80883bd0 100644 --- a/qutip/solver/propagator.py +++ b/qutip/solver/propagator.py @@ -22,10 +22,11 @@ def propagator(H, t, c_ops=(), args=None, options=None, **kwargs): Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo`, :class:`QobjEvo` compatible format. - Possibly time-dependent system Liouvillian or Hamiltonian as a Qobj or - QobjEvo. ``list`` of [:class:`Qobj`, :class:`Coefficient`] or callable - that can be made into :class:`QobjEvo` are also accepted. + H : :obj:`.Qobj`, :obj:`.QobjEvo`, + :obj:`.QobjEvo` compatible format. Possibly + time-dependent system Liouvillian or Hamiltonian as a Qobj or QobjEvo. + ``list`` of [:obj:`.Qobj`, :obj:`.Coefficient`] or callable + that can be made into :obj:`.QobjEvo` are also accepted. t : float or array-like Time or list of times for which to evaluate the propagator. @@ -41,12 +42,12 @@ def propagator(H, t, c_ops=(), args=None, options=None, **kwargs): Options for the solver. **kwargs : - Extra parameters to use when creating the :class:`QobjEvo` from a list - format ``H``. + Extra parameters to use when creating the + :obj:`.QobjEvo` from a list format ``H``. Returns ------- - U : qobj, list + U : :obj:`.Qobj`, list Instance representing the propagator(s) :math:`U(t)`. Return a single Qobj when ``t`` is a number or a list when ``t`` is a list. @@ -83,12 +84,12 @@ def propagator_steadystate(U): Parameters ---------- - U : qobj + U : :obj:`.Qobj` Operator representing the propagator. Returns ------- - a : qobj + a : :obj:`.Qobj` Instance representing the steady-state density matrix. """ evals, estates = U.eigenstates() @@ -116,14 +117,14 @@ class Propagator: Parameters ---------- - system : :class:`Qobj`, :class:`QobjEvo`, :class:`Solver` + system : :obj:`.Qobj`, :obj:`.QobjEvo`, :class:`.Solver` Possibly time-dependent system driving the evolution, either already - packaged in a solver, such as :class:`SESolver` or :class:`BRSolver`, - or the Liouvillian or Hamiltonian as a :class:`Qobj`, :class:`QobjEvo`. - ``list`` of [:class:`Qobj`, :class:`Coefficient`] or callable that can - be made into :class:`QobjEvo` are also accepted. + packaged in a solver, such as :class:`.SESolver` or :class:`.BRSolver`, + or the Liouvillian or Hamiltonian as a :obj:`.Qobj`, + :obj:`.QobjEvo`. ``list`` of [:obj:`.Qobj`, :obj:`.Coefficient`] + or callable that can be made into :obj:`.QobjEvo` are also accepted. - Solvers that run non-deterministacilly, such as :class:`MCSolver`, are + Solvers that run non-deterministacilly, such as :class:`.MCSolver`, are not supported. c_ops : list, optional @@ -143,11 +144,15 @@ class Propagator: Absolute tolerance for the time. If a previous propagator was computed at a time within tolerance, that propagator will be returned. - .. note:: - The :class:`Propagator` is not a :class:`QobjEvo` so it cannot be used - for operations with :class:`Qobj` or :class:`QobjEvo`. It can be made - into a :class:`QobjEvo` with :: - U = QobjEvo(Propagator(H)) + Notes + ----- + The :class:`Propagator` is not a :obj:`.QobjEvo` so + it cannot be used for operations with :obj:`.Qobj` or + :obj:`.QobjEvo`. It can be made into a + :obj:`.QobjEvo` with :: + + U = QobjEvo(Propagator(H)) + """ def __init__(self, system, *, c_ops=(), args=None, options=None, memoize=10, tol=1e-14): diff --git a/qutip/solver/result.py b/qutip/solver/result.py index a6d7fc9a26..6f4bfb3db4 100644 --- a/qutip/solver/result.py +++ b/qutip/solver/result.py @@ -13,7 +13,7 @@ class _QobjExpectEop: Parameters ---------- - op : :obj:`~Qobj` + op : :obj:`.Qobj` The expectation value operator. """ def __init__(self, op): @@ -120,10 +120,10 @@ class Result(_BaseResult): Parameters ---------- - e_ops : :obj:`~Qobj`, :obj:`~QobjEvo`, function or list or dict of these + e_ops : :obj:`.Qobj`, :obj:`.QobjEvo`, function or list or dict of these The ``e_ops`` parameter defines the set of values to record at - each time step ``t``. If an element is a :obj:`~Qobj` or - :obj:`~QobjEvo` the value recorded is the expectation value of that + each time step ``t``. If an element is a :obj:`.Qobj` or + :obj:`.QobjEvo` the value recorded is the expectation value of that operator given the state at ``t``. If the element is a function, ``f``, the value recorded is ``f(t, state)``. @@ -151,11 +151,11 @@ class Result(_BaseResult): A list of the times at which the expectation values and states were recorded. - states : list of :obj:`~Qobj` + states : list of :obj:`.Qobj` The state at each time ``t`` (if the recording of the state was requested). - final_state : :obj:`~Qobj`: + final_state : :obj:`.Qobj`: The final state (if the recording of the final state was requested). expect : list of arrays of expectation values @@ -280,17 +280,17 @@ def add(self, t, state): t : float The time of the added state. - state : typically a :obj:`~Qobj` - The state a time ``t``. Usually this is a :obj:`~Qobj` with + state : typically a :obj:`.Qobj` + The state a time ``t``. Usually this is a :obj:`.Qobj` with suitable dimensions, but it sub-classes of result might support other forms of the state. - .. note:: + Notes + ----- + The expectation values, i.e. ``e_ops``, and states are recorded by + the state processors (see ``.add_processor``). - The expectation values, i.e. ``e_ops``, and states are recorded by - the state processors (see ``.add_processor``). - - Additional processors may be added by sub-classes. + Additional processors may be added by sub-classes. """ self.times.append(t) @@ -337,10 +337,10 @@ class MultiTrajResult(_BaseResult): Parameters ---------- - e_ops : :obj:`~Qobj`, :obj:`~QobjEvo`, function or list or dict of these + e_ops : :obj:`.Qobj`, :obj:`.QobjEvo`, function or list or dict of these The ``e_ops`` parameter defines the set of values to record at - each time step ``t``. If an element is a :obj:`~Qobj` or - :obj:`~QobjEvo` the value recorded is the expectation value of that + each time step ``t``. If an element is a :obj:`.Qobj` or + :obj:`.QobjEvo` the value recorded is the expectation value of that operator given the state at ``t``. If the element is a function, ``f``, the value recorded is ``f(t, state)``. @@ -369,19 +369,19 @@ class MultiTrajResult(_BaseResult): A list of the times at which the expectation values and states were recorded. - average_states : list of :obj:`~Qobj` + average_states : list of :obj:`.Qobj` The state at each time ``t`` (if the recording of the state was requested) averaged over all trajectories as a density matrix. - runs_states : list of list of :obj:`~Qobj` + runs_states : list of list of :obj:`.Qobj` The state for each trajectory and each time ``t`` (if the recording of the states and trajectories was requested) - final_state : :obj:`~Qobj: + final_state : :obj:`.Qobj`: The final state (if the recording of the final state was requested) averaged over all trajectories as a density matrix. - runs_final_state : list of :obj:`~Qobj` + runs_final_state : list of :obj:`.Qobj` The final state for each trajectory (if the recording of the final state and trajectories was requested). @@ -651,6 +651,7 @@ def add_end_condition(self, ntraj, target_tol=None): Set the condition to stop the computing trajectories when the certain condition are fullfilled. Supported end condition for multi trajectories computation are: + - Reaching a number of trajectories. - Error bar on the expectation values reach smaller than a given tolerance. @@ -872,7 +873,7 @@ def __add__(self, other): class McTrajectoryResult(Result): """ - Result class used by the :class:`qutip.MCSolver` for single trajectories. + Result class used by the :class:`.MCSolver` for single trajectories. """ def __init__(self, e_ops, options, *args, **kwargs): @@ -886,10 +887,10 @@ class McResult(MultiTrajResult): Parameters ---------- - e_ops : :obj:`~Qobj`, :obj:`~QobjEvo`, function or list or dict of these + e_ops : :obj:`.Qobj`, :obj:`.QobjEvo`, function or list or dict of these The ``e_ops`` parameter defines the set of values to record at - each time step ``t``. If an element is a :obj:`~Qobj` or - :obj:`~QobjEvo` the value recorded is the expectation value of that + each time step ``t``. If an element is a :obj:`.Qobj` or + :obj:`.QobjEvo` the value recorded is the expectation value of that operator given the state at ``t``. If the element is a function, ``f``, the value recorded is ``f(t, state)``. @@ -1149,7 +1150,7 @@ def photocurrent(self): class NmmcTrajectoryResult(McTrajectoryResult): """ - Result class used by the :class:`qutip.NonMarkovianMCSolver` for single + Result class used by the :class:`.NonMarkovianMCSolver` for single trajectories. Additionally stores the trace of the state along the trajectory. """ @@ -1179,10 +1180,10 @@ class NmmcResult(McResult): Parameters ---------- - e_ops : :obj:`~Qobj`, :obj:`~QobjEvo`, function or list or dict of these + e_ops : :obj:`.Qobj`, :obj:`.QobjEvo`, function or list or dict of these The ``e_ops`` parameter defines the set of values to record at - each time step ``t``. If an element is a :obj:`~Qobj` or - :obj:`~QobjEvo` the value recorded is the expectation value of that + each time step ``t``. If an element is a :obj:`.Qobj` or + :obj:`.QobjEvo` the value recorded is the expectation value of that operator given the state at ``t``. If the element is a function, ``f``, the value recorded is ``f(t, state)``. diff --git a/qutip/solver/scattering.py b/qutip/solver/scattering.py index 5855832dbb..b1de5dfae3 100644 --- a/qutip/solver/scattering.py +++ b/qutip/solver/scattering.py @@ -53,7 +53,7 @@ def photon_scattering_amplitude(propagator, c_ops, tlist, taus, psi, psit): Parameters ---------- - propagator : :class:Propagator + propagator : :class:`.Propagator` Propagator c_ops : list list of collapse operators for each waveguide; these are assumed to @@ -129,7 +129,7 @@ def temporal_basis_vector(waveguide_emission_indices, n_time_bins, Returns ------- - temporal_basis_vector : :class: qutip.Qobj + temporal_basis_vector : :class:`.Qobj` A basis vector representing photon scattering at the specified indices. If there are W waveguides, T times, and N photon emissions, then the basis vector has dimensionality (W*T)^N. @@ -189,12 +189,12 @@ def temporal_scattered_state(H, psi0, n_emissions, c_ops, tlist, Parameters ---------- - H : :class: qutip.Qobj or list + H : :class:`.Qobj` or list System-waveguide(s) Hamiltonian or effective Hamiltonian in Qobj or list-callback format. If construct_effective_hamiltonian is not specified, an effective Hamiltonian is constructed from `H` and `c_ops`. - psi0 : :class: qutip.Qobj + psi0 : :class:`.Qobj` Initial state density matrix :math:`\\rho(t_0)` or state vector :math:`\\psi(t_0)`. n_emissions : int @@ -206,7 +206,7 @@ def temporal_scattered_state(H, psi0, n_emissions, c_ops, tlist, tlist : array_like List of times for :math:`\\tau_i`. tlist should contain 0 and exceed the pulse duration / temporal region of interest. - system_zero_state : :class: qutip.Qobj + system_zero_state : :class:`.Qobj` State representing zero excitations in the system. Defaults to :math:`\\psi(t_0)` construct_effective_hamiltonian : bool @@ -217,7 +217,7 @@ def temporal_scattered_state(H, psi0, n_emissions, c_ops, tlist, Returns ------- - phi_n : :class: qutip.Qobj + phi_n : :class:`.Qobj` The scattered bath state projected onto the temporal basis given by tlist. If there are W waveguides, T times, and N photon emissions, then the state is a tensor product state with dimensionality T^(W*N). @@ -241,12 +241,12 @@ def scattering_probability(H, psi0, n_emissions, c_ops, tlist, Parameters ---------- - H : :class: qutip.Qobj or list + H : :class:`.Qobj` or list System-waveguide(s) Hamiltonian or effective Hamiltonian in Qobj or list-callback format. If construct_effective_hamiltonian is not specified, an effective Hamiltonian is constructed from H and `c_ops`. - psi0 : :class: qutip.Qobj + psi0 : :class:`.Qobj` Initial state density matrix :math:`\\rho(t_0)` or state vector :math:`\\psi(t_0)`. n_emissions : int @@ -260,7 +260,7 @@ def scattering_probability(H, psi0, n_emissions, c_ops, tlist, List of times for :math:`\\tau_i`. tlist should contain 0 and exceed the pulse duration / temporal region of interest; tlist need not be linearly spaced. - system_zero_state : :class: qutip.Qobj + system_zero_state : :class:`.Qobj` State representing zero excitations in the system. Defaults to `basis(systemDims, 0)`. construct_effective_hamiltonian : bool diff --git a/qutip/solver/sesolve.py b/qutip/solver/sesolve.py index 9498be186b..4d16e5aa10 100644 --- a/qutip/solver/sesolve.py +++ b/qutip/solver/sesolve.py @@ -29,25 +29,25 @@ def sesolve(H, psi0, tlist, e_ops=None, args=None, options=None, **kwargs): **Time-dependent operators** - For time-dependent problems, `H` and `c_ops` can be a :class:`QobjEvo` or - object that can be interpreted as :class:`QobjEvo` such as a list of + For time-dependent problems, `H` and `c_ops` can be a :obj:`.QobjEvo` or + object that can be interpreted as :obj:`.QobjEvo` such as a list of (Qobj, Coefficient) pairs or a function. Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo`, :class:`QobjEvo` compatible format. + H : :obj:`.Qobj`, :obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format. System Hamiltonian as a Qobj or QobjEvo for time-dependent - Hamiltonians. List of [:class:`Qobj`, :class:`Coefficient`] or callable - that can be made into :class:`QobjEvo` are also accepted. + Hamiltonians. List of [:obj:`.Qobj`, :obj:`.Coefficient`] or callable + that can be made into :obj:`.QobjEvo` are also accepted. - psi0 : :class:`qutip.qobj` + psi0 : :obj:`qutip.qobj` initial state vector (ket) or initial unitary operator `psi0 = U` tlist : *list* / *array* list of times for :math:`t`. - e_ops : :class:`qutip.qobj`, callable, or list. + e_ops : :obj:`qutip.qobj`, callable, or list. Single operator or list of operators for which to evaluate expectation values or callable or list of callable. Callable signature must be, `f(t: float, state: Qobj)`. @@ -90,9 +90,9 @@ def sesolve(H, psi0, tlist, e_ops=None, args=None, options=None, **kwargs): Returns ------- - result: :class:`qutip.Result` + result: :obj:`qutip.Result` - An instance of the class :class:`qutip.Result`, which contains + An instance of the class :obj:`qutip.Result`, which contains a *list of array* `result.expect` of expectation values for the times specified by `tlist`, and/or a *list* `result.states` of state vectors or density matrices corresponding to the times in `tlist` [if `e_ops` @@ -111,10 +111,10 @@ class SESolver(Solver): Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo` + H : :obj:`.Qobj`, :obj:`.QobjEvo` System Hamiltonian as a Qobj or QobjEvo for time-dependent - Hamiltonians. List of [:class:`Qobj`, :class:`Coefficient`] or callable - that can be made into :class:`QobjEvo` are also accepted. + Hamiltonians. List of [:obj:`.Qobj`, :obj:`.Coefficient`] or callable + that can be made into :obj:`.QobjEvo` are also accepted. options : dict, optional Options for the solver, see :obj:`SESolver.options` and @@ -128,7 +128,7 @@ class SESolver(Solver): name = "sesolve" _avail_integrators = {} solver_options = { - "progress_bar": "text", + "progress_bar": "", "progress_kwargs": {"chunk_size":10}, "store_final_state": False, "store_states": None, @@ -171,7 +171,7 @@ def options(self): normalize_output: bool, default=True Normalize output state to hide ODE numerical errors. - progress_bar: str {'text', 'enhanced', 'tqdm', ''}, {} + progress_bar: str {"text", "enhanced", "tqdm", ""}, default="" How to present the solver progress. 'tqdm' uses the python module of the same name and raise an error if not installed. Empty string or False will disable the bar. diff --git a/qutip/solver/sode/rouchon.py b/qutip/solver/sode/rouchon.py index 30c48fd0cb..9376bd0b2d 100644 --- a/qutip/solver/sode/rouchon.py +++ b/qutip/solver/sode/rouchon.py @@ -19,12 +19,12 @@ class RouchonSODE(SIntegrator): - Order: strong 1 - .. note:: - - This method should be used with very small ``dt``. Unlike other - methods that will return unphysical state (negative eigenvalues, Nans) - when the time step is too large, this method will return state that - seems normal. + Notes + ----- + This method should be used with very small ``dt``. Unlike other + methods that will return unphysical state (negative eigenvalues, Nans) + when the time step is too large, this method will return state that + seems normal. """ integrator_options = { "dt": 0.0001, diff --git a/qutip/solver/solver_base.py b/qutip/solver/solver_base.py index d41b793158..a906147a5f 100644 --- a/qutip/solver/solver_base.py +++ b/qutip/solver/solver_base.py @@ -13,12 +13,12 @@ class Solver: """ Runner for an evolution. - Can run the evolution at once using :method:`run` or step by step using - :method:`start` and :method:`step`. + Can run the evolution at once using :meth:`run` or step by step using + :meth:`start` and :meth:`step`. Parameters ---------- - rhs : Qobj, QobjEvo + rhs : :obj:`.Qobj`, :obj:`.QobjEvo` Right hand side of the evolution:: d state / dt = rhs @ state @@ -67,7 +67,7 @@ def _prepare_state(self, state): Extract the data of the Qobj state. Is responsible for dims checks, preparing the data (stack columns, ...) - determining the dims of the output for :method:`_restore_state`. + determining the dims of the output for :meth:`_restore_state`. Should return the state's data such that it can be used by Integrators. """ @@ -111,12 +111,12 @@ def run(self, state0, tlist, *, args=None, e_ops=None): For a ``state0`` at time ``tlist[0]`` do the evolution as directed by ``rhs`` and for each time in ``tlist`` store the state and/or - expectation values in a :class:`Result`. The evolution method and + expectation values in a :class:`.Result`. The evolution method and stored results are determined by ``options``. Parameters ---------- - state0 : :class:`Qobj` + state0 : :obj:`.Qobj` Initial state of the evolution. tlist : list of double @@ -135,7 +135,7 @@ def run(self, state0, tlist, *, args=None, e_ops=None): Returns ------- - results : :class:`qutip.solver.Result` + results : :obj:`qutip.solver.Result` Results of the evolution. States and/or expect will be saved. You can control the saved data in the options. """ @@ -167,11 +167,10 @@ def run(self, state0, tlist, *, args=None, e_ops=None): def start(self, state0, t0): """ Set the initial state and time for a step evolution. - ``options`` for the evolutions are read at this step. Parameters ---------- - state0 : :class:`Qobj` + state0 : :obj:`.Qobj` Initial state of the evolution. t0 : double @@ -183,7 +182,7 @@ def start(self, state0, t0): def step(self, t, *, args=None, copy=True): """ - Evolve the state to ``t`` and return the state as a :class:`Qobj`. + Evolve the state to ``t`` and return the state as a :obj:`.Qobj`. Parameters ---------- @@ -198,10 +197,12 @@ def step(self, t, *, args=None, copy=True): copy : bool, optional {True} Whether to return a copy of the data or the data in the ODE solver. - .. note:: - The state must be initialized first by calling ``start`` or - ``run``. If ``run`` is called, ``step`` will continue from the last - time and state obtained. + Notes + ----- + The state must be initialized first by calling :meth:`start` or + :meth:`run`. If :meth:`run` is called, :meth:`step` will continue from + the last time and state obtained. + """ if not self._integrator._is_set: raise RuntimeError("The `start` method must called first") diff --git a/qutip/solver/spectrum.py b/qutip/solver/spectrum.py index cb92e1f0e6..29da20667d 100644 --- a/qutip/solver/spectrum.py +++ b/qutip/solver/spectrum.py @@ -25,15 +25,15 @@ def spectrum(H, wlist, c_ops, a_op, b_op, solver="es"): Parameters ---------- - H : :class:`qutip.qobj` + H : :class:`.qobj` system Hamiltonian. wlist : array_like List of frequencies for :math:`\omega`. c_ops : list List of collapse operators. - a_op : Qobj + a_op : :class:`.qobj` Operator A. - b_op : Qobj + b_op : :class:`.qobj` Operator B. solver : str Choice of solver (`es` for exponential series and diff --git a/qutip/solver/steadystate.py b/qutip/solver/steadystate.py index 85c9f01b71..372c72564c 100644 --- a/qutip/solver/steadystate.py +++ b/qutip/solver/steadystate.py @@ -42,7 +42,7 @@ def steadystate(A, c_ops=[], *, method='direct', solver=None, **kwargs): Parameters ---------- - A : :obj:`~Qobj` + A : :obj:`.Qobj` A Hamiltonian or Liouvillian operator. c_op_list : list @@ -117,10 +117,9 @@ def steadystate(A, c_ops=[], *, method='direct', solver=None, **kwargs): info : dict, optional Dictionary containing solver-specific information about the solution. - .. note:: - - The SVD method works only for dense operators (i.e. small systems). - + Notes + ----- + The SVD method works only for dense operators (i.e. small systems). """ if not A.issuper and not c_ops: raise TypeError('Cannot calculate the steady state for a ' + @@ -321,13 +320,13 @@ def steadystate_floquet(H_0, c_ops, Op_t, w_d=1.0, n_it=3, sparse=False, Parameters ---------- - H_0 : :obj:`~Qobj` + H_0 : :obj:`.Qobj` A Hamiltonian or Liouvillian operator. c_ops : list A list of collapse operators. - Op_t : :obj:`~Qobj` + Op_t : :obj:`.Qobj` The the interaction operator which is multiplied by the cosine w_d : float, default 1.0 @@ -364,11 +363,11 @@ def steadystate_floquet(H_0, c_ops, Op_t, w_d=1.0, n_it=3, sparse=False, dm : qobj Steady state density matrix. - .. note:: - - See: Sze Meng Tan, - https://copilot.caltech.edu/documents/16743/qousersguide.pdf, - Section (10.16) + Notes + ----- + See: Sze Meng Tan, + https://copilot.caltech.edu/documents/16743/qousersguide.pdf, + Section (10.16) """ @@ -446,18 +445,18 @@ def pseudo_inverse(L, rhoss=None, w=None, method='splu', *, use_rcm=False, R : Qobj Returns a Qobj instance representing the pseudo inverse of L. - .. note:: - - In general the inverse of a sparse matrix will be dense. If you - are applying the inverse to a density matrix then it is better to - cast the problem as an Ax=b type problem where the explicit calculation - of the inverse is not required. See page 67 of "Electrons in - nanostructures" C. Flindt, PhD Thesis available online: - https://orbit.dtu.dk/en/publications/electrons-in-nanostructures-coherent-manipulation-and-counting-st - - Note also that the definition of the pseudo-inverse herein is different - from numpys pinv() alone, as it includes pre and post projection onto - the subspace defined by the projector Q. + Notes + ----- + In general the inverse of a sparse matrix will be dense. If you + are applying the inverse to a density matrix then it is better to + cast the problem as an Ax=b type problem where the explicit calculation + of the inverse is not required. See page 67 of "Electrons in + nanostructures" C. Flindt, PhD Thesis available online: + https://orbit.dtu.dk/en/publications/electrons-in-nanostructures-coherent-manipulation-and-counting-st + + Note also that the definition of the pseudo-inverse herein is different + from numpys pinv() alone, as it includes pre and post projection onto + the subspace defined by the projector Q. """ if rhoss is None: diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index aaa0865c95..334fd839ee 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -230,25 +230,25 @@ def smesolve( Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo`, :class:`QobjEvo` compatible format. + H : :obj:`.Qobj`, :obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format. System Hamiltonian as a Qobj or QobjEvo for time-dependent - Hamiltonians. List of [:class:`Qobj`, :class:`Coefficient`] or callable - that can be made into :class:`QobjEvo` are also accepted. + Hamiltonians. List of [:obj:`.Qobj`, :obj:`.Coefficient`] or callable + that can be made into :obj:`.QobjEvo` are also accepted. - rho0 : :class:`qutip.Qobj` + rho0 : :class:`.Qobj` Initial density matrix or state vector (ket). tlist : *list* / *array* List of times for :math:`t`. - c_ops : list of (:class:`QobjEvo`, :class:`QobjEvo` compatible format) + c_ops : list of (:obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format) Deterministic collapse operator which will contribute with a standard Lindblad type of dissipation. - sc_ops : list of (:class:`QobjEvo`, :class:`QobjEvo` compatible format) + sc_ops : list of (:obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format) List of stochastic collapse operators. - e_ops : : :class:`qutip.qobj`, callable, or list. + e_ops : : :class:`.qobj`, callable, or list. Single operator or list of operators for which to evaluate expectation values or callable or list of callable. Callable signature must be, `f(t: float, state: Qobj)`. @@ -331,9 +331,9 @@ def smesolve( Returns ------- - output: :class:`qutip.solver.Result` + output: :class:`.Result` - An instance of the class :class:`qutip.solver.Result`. + An instance of the class :class:`.Result`. """ options = _solver_deprecation(kwargs, options, "stoc") H = QobjEvo(H, args=args, tlist=tlist) @@ -358,21 +358,21 @@ def ssesolve( Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo`, :class:`QobjEvo` compatible format. + H : :obj:`.Qobj`, :obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format. System Hamiltonian as a Qobj or QobjEvo for time-dependent - Hamiltonians. List of [:class:`Qobj`, :class:`Coefficient`] or callable - that can be made into :class:`QobjEvo` are also accepted. + Hamiltonians. List of [:obj:`.Qobj`, :obj:`.Coefficient`] or callable + that can be made into :obj:`.QobjEvo` are also accepted. - psi0 : :class:`qutip.Qobj` + psi0 : :class:`.Qobj` Initial state vector (ket). tlist : *list* / *array* List of times for :math:`t`. - sc_ops : list of (:class:`QobjEvo`, :class:`QobjEvo` compatible format) + sc_ops : list of (:obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format) List of stochastic collapse operators. - e_ops : :class:`qutip.qobj`, callable, or list. + e_ops : :class:`.qobj`, callable, or list. Single operator or list of operators for which to evaluate expectation values or callable or list of callable. Callable signature must be, `f(t: float, state: Qobj)`. @@ -453,8 +453,8 @@ def ssesolve( Returns ------- - output: :class:`qutip.solver.Result` - An instance of the class :class:`qutip.solver.Result`. + output: :class:`.Result` + An instance of the class :class:`.Result`. """ options = _solver_deprecation(kwargs, options, "stoc") H = QobjEvo(H, args=args, tlist=tlist) @@ -680,12 +680,12 @@ class SMESolver(StochasticSolver): Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo`, :class:`QobjEvo` compatible format. + H : :obj:`.Qobj`, :obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format. System Hamiltonian as a Qobj or QobjEvo for time-dependent - Hamiltonians. List of [:class:`Qobj`, :class:`Coefficient`] or callable - that can be made into :class:`QobjEvo` are also accepted. + Hamiltonians. List of [:obj:`.Qobj`, :obj:`.Coefficient`] or callable + that can be made into :obj:`.QobjEvo` are also accepted. - sc_ops : list of (:class:`QobjEvo`, :class:`QobjEvo` compatible format) + sc_ops : list of (:obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format) List of stochastic collapse operators. heterodyne : bool, [False] @@ -720,16 +720,16 @@ class SSESolver(StochasticSolver): Parameters ---------- - H : :class:`Qobj`, :class:`QobjEvo`, :class:`QobjEvo` compatible format. + H : :obj:`.Qobj`, :obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format. System Hamiltonian as a Qobj or QobjEvo for time-dependent - Hamiltonians. List of [:class:`Qobj`, :class:`Coefficient`] or callable - that can be made into :class:`QobjEvo` are also accepted. + Hamiltonians. List of [:obj:`.Qobj`, :obj:`.Coefficient`] or callable + that can be made into :obj:`.QobjEvo` are also accepted. - c_ops : list of (:class:`QobjEvo`, :class:`QobjEvo` compatible format) + c_ops : list of (:obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format) Deterministic collapse operator which will contribute with a standard Lindblad type of dissipation. - sc_ops : list of (:class:`QobjEvo`, :class:`QobjEvo` compatible format) + sc_ops : list of (:obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format) List of stochastic collapse operators. heterodyne : bool, [False] diff --git a/qutip/visualization.py b/qutip/visualization.py index 36f08cad65..e026c86179 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -1248,7 +1248,7 @@ def plot_expectation_values(results, ylabels=None, *, instances. Parameters ---------- - results : (list of) :class:`qutip.solver.Result` + results : (list of) :class:`.Result` List of results objects returned by any of the QuTiP evolution solvers. ylabels : list of strings, optional diff --git a/qutip/wigner.py b/qutip/wigner.py index 48abd66385..bedc67c0ed 100644 --- a/qutip/wigner.py +++ b/qutip/wigner.py @@ -687,7 +687,7 @@ class QFunc: See Also -------- :obj:`.qfunc` : - a single function version, which will involve computing several + A single function version, which will involve computing several quantities multiple times in order to use less memory. """ From 0594c1d5e515b55a2bc3e68b605ded9c11d5262a Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Mon, 13 Nov 2023 12:37:28 +0900 Subject: [PATCH 070/247] Modified _BaseResult to remove feedback of options --- qutip/solver/result.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/qutip/solver/result.py b/qutip/solver/result.py index 7544d09761..66e3508ee1 100644 --- a/qutip/solver/result.py +++ b/qutip/solver/result.py @@ -79,7 +79,10 @@ def __init__(self, options, *, solver=None, stats=None): self._state_processors = [] self._state_processors_require_copy = False - self.options = options + # do not store a reference to the solver + options_copy = options.copy() + options_copy._feedback = None + self.options = options_copy def _e_ops_to_dict(self, e_ops): """ Convert the supplied e_ops to a dictionary of Eop instances. """ From 949c1e202d6c489d7dad8d2c637af79d90bdda45 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Mon, 13 Nov 2023 14:20:59 +0900 Subject: [PATCH 071/247] Options parameter may be dict or _SolverOptions --- qutip/solver/result.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/qutip/solver/result.py b/qutip/solver/result.py index 66e3508ee1..7226b222f5 100644 --- a/qutip/solver/result.py +++ b/qutip/solver/result.py @@ -1,6 +1,6 @@ """ Class for solve function results""" import numpy as np -from ..core import Qobj, QobjEvo, expect, isket, ket2dm, qzero, qzero_like +from ..core import Qobj, QobjEvo, expect, isket, ket2dm, qzero_like __all__ = ["Result", "MultiTrajResult", "McResult", "NmmcResult", "McTrajectoryResult", "McResultImprovedSampling"] @@ -79,9 +79,10 @@ def __init__(self, options, *, solver=None, stats=None): self._state_processors = [] self._state_processors_require_copy = False - # do not store a reference to the solver + # make sure not to store a reference to the solver options_copy = options.copy() - options_copy._feedback = None + if hasattr(options_copy, '_feedback'): + options_copy._feedback = None self.options = options_copy def _e_ops_to_dict(self, e_ops): From 250692b32c96081e1418d9a1e6b7c23393fe1ed9 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Mon, 13 Nov 2023 14:46:32 +0900 Subject: [PATCH 072/247] Added changelog --- doc/changes/2262.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/2262.bugfix diff --git a/doc/changes/2262.bugfix b/doc/changes/2262.bugfix new file mode 100644 index 0000000000..7413a5f513 --- /dev/null +++ b/doc/changes/2262.bugfix @@ -0,0 +1 @@ +Fixed result objects storing a reference to the solver through options._feedback. \ No newline at end of file From 72b786b15caf77cb45326def91a75dd676abec0e Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 13 Nov 2023 10:34:11 -0500 Subject: [PATCH 073/247] Cleaner bloch docstring --- doc/apidoc/classes.rst | 25 ++++++++++++++----------- qutip/bloch3d.py | 8 ++++---- qutip/core/cy/qobjevo.pyx | 1 + qutip/solver/multitraj.py | 6 +++--- qutip/solver/result.py | 6 +++--- 5 files changed, 25 insertions(+), 21 deletions(-) diff --git a/doc/apidoc/classes.rst b/doc/apidoc/classes.rst index 747834ab1c..3999f911f7 100644 --- a/doc/apidoc/classes.rst +++ b/doc/apidoc/classes.rst @@ -253,22 +253,25 @@ Permutational Invariance .. _classes-distributions: Distribution functions ----------------------------- +---------------------- .. autoclass:: qutip.distributions.Distribution :members: -.. autoclass:: qutip.distributions.WignerDistribution - :members: +.. + Docstrings are empty... -.. autoclass:: qutip.distributions.QDistribution - :members: + .. autoclass:: qutip.distributions.WignerDistribution + :members: -.. autoclass:: qutip.distributions.TwoModeQuadratureCorrelation - :members: + .. autoclass:: qutip.distributions.QDistribution + :members: -.. autoclass:: qutip.distributions.HarmonicOscillatorWaveFunction - :members: + .. autoclass:: qutip.distributions.TwoModeQuadratureCorrelation + :members: -.. autoclass:: qutip.distributions.HarmonicOscillatorProbabilityFunction - :members: + .. autoclass:: qutip.distributions.HarmonicOscillatorWaveFunction + :members: + + .. autoclass:: qutip.distributions.HarmonicOscillatorProbabilityFunction + :members: diff --git a/qutip/bloch3d.py b/qutip/bloch3d.py index 99458e0793..22f5ec25ce 100644 --- a/qutip/bloch3d.py +++ b/qutip/bloch3d.py @@ -5,7 +5,7 @@ class Bloch3d: - r"""Class for plotting data on a 3D Bloch sphere using mayavi. + """Class for plotting data on a 3D Bloch sphere using mayavi. Valid data can be either points, vectors, or qobj objects corresponding to state vectors or density matrices. for a two-state system (or subsystem). @@ -48,15 +48,15 @@ class Bloch3d: Width of displayed vectors. view : list {[45,65]} Azimuthal and Elevation viewing angles. - xlabel : list {['|x>', '']} + xlabel : list {['\|x>', '']} List of strings corresponding to +x and -x axes labels, respectively. xlpos : list {[1.07,-1.07]} Positions of +x and -x labels respectively. - ylabel : list {['|y>', '']} + ylabel : list {['\|y>', '']} List of strings corresponding to +y and -y axes labels, respectively. ylpos : list {[1.07,-1.07]} Positions of +y and -y labels respectively. - zlabel : list {['|0>', '|1>']} + zlabel : list {["\|0>", '\|1>']} List of strings corresponding to +z and -z axes labels, respectively. zlpos : list {[1.07,-1.07]} Positions of +z and -z labels respectively. diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 8e8aafc6db..9e3b2e348f 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -10,6 +10,7 @@ import qutip from .. import Qobj from .. import data as _data from ..coefficient import coefficient, CompilationOptions +from .coefficient import Coefficient from ._element import * from ..dimensions import type_from_dims from qutip.settings import settings diff --git a/qutip/solver/multitraj.py b/qutip/solver/multitraj.py index 7118be8a54..1579a05721 100644 --- a/qutip/solver/multitraj.py +++ b/qutip/solver/multitraj.py @@ -71,9 +71,9 @@ def start(self, state, t0, seed=None): to spawn seeds for each trajectory or a list of seed, one for each trajectory. - ..note :: - When using step evolution, only one trajectory can be computed at - once. + Notes + ----- + When using step evolution, only one trajectory can be computed at once. """ seeds = self._read_seed(seed, 1) generator = self._get_generator(seeds[0]) diff --git a/qutip/solver/result.py b/qutip/solver/result.py index 6f4bfb3db4..0bfb3f554c 100644 --- a/qutip/solver/result.py +++ b/qutip/solver/result.py @@ -632,8 +632,8 @@ def add(self, trajectory_info): - trajectory : :class:`Result` Run result for one evolution over the times. - Return - ------ + Returns + ------- remaing_traj : number Return the number of trajectories still needed to reach the target tolerance. If no tolerance is provided, return infinity. @@ -651,7 +651,7 @@ def add_end_condition(self, ntraj, target_tol=None): Set the condition to stop the computing trajectories when the certain condition are fullfilled. Supported end condition for multi trajectories computation are: - + - Reaching a number of trajectories. - Error bar on the expectation values reach smaller than a given tolerance. From 5538149bc3758be31e2540fd2130a9a0a3aec965 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 13 Nov 2023 14:45:22 -0500 Subject: [PATCH 074/247] Review class api --- doc/apidoc/classes.rst | 24 ++++++++--- doc/apidoc/functions.rst | 7 +++ qutip/bloch.py | 14 +++--- qutip/bloch3d.py | 2 +- qutip/core/coefficient.py | 17 +++++--- qutip/core/cy/qobjevo.pyx | 41 ++++++++++-------- qutip/core/qobj.py | 47 ++++++++++++++------- qutip/solver/brmesolve.py | 18 ++++---- qutip/solver/floquet.py | 1 + qutip/solver/heom/bofin_solvers.py | 22 +++++----- qutip/solver/integrator/integrator.py | 6 +-- qutip/solver/integrator/krylov.py | 12 +++--- qutip/solver/integrator/qutip_integrator.py | 16 +++---- qutip/solver/integrator/scipy_integrator.py | 44 +++++++++---------- qutip/solver/mcsolve.py | 26 ++++++------ qutip/solver/multitraj.py | 14 +++--- qutip/solver/nm_mcsolve.py | 32 +++++++------- qutip/solver/propagator.py | 12 +++--- qutip/solver/sesolve.py | 12 +++--- qutip/solver/sode/itotaylor.py | 16 +++---- qutip/solver/sode/rouchon.py | 4 +- qutip/solver/sode/sode.py | 20 ++++----- qutip/solver/solver_base.py | 2 +- qutip/solver/stochastic.py | 32 +++++++------- 24 files changed, 243 insertions(+), 198 deletions(-) diff --git a/doc/apidoc/classes.rst b/doc/apidoc/classes.rst index 3999f911f7..19f0ed685b 100644 --- a/doc/apidoc/classes.rst +++ b/doc/apidoc/classes.rst @@ -11,6 +11,7 @@ Qobj .. autoclass:: qutip.core.qobj.Qobj :members: + :special-members: __call__ .. _classes-qobjevo: @@ -19,6 +20,7 @@ QobjEvo .. autoclass:: qutip.core.cy.qobjevo.QobjEvo :members: + :special-members: __call__ .. _classes-bloch: @@ -48,31 +50,33 @@ Solvers :members: :inherited-members: :show-inheritance: + :exclude-members: add_integrator .. autoclass:: qutip.solver.mesolve.MESolver :members: :inherited-members: :show-inheritance: + :exclude-members: add_integrator .. autoclass:: qutip.solver.brmesolve.BRSolver :members: :inherited-members: :show-inheritance: + :exclude-members: add_integrator .. autoclass:: qutip.solver.floquet.FMESolver :members: :inherited-members: :show-inheritance: + :exclude-members: add_integrator .. autoclass:: qutip.solver.floquet.FloquetBasis :members: - :inherited-members: - :show-inheritance: .. autoclass:: qutip.solver.propagator.Propagator :members: :inherited-members: - :show-inheritance: + :special-members: __call__ .. _classes-monte-carlo-solver: @@ -84,11 +88,13 @@ Monte Carlo Solvers :members: :inherited-members: :show-inheritance: + :exclude-members: add_integrator .. autoclass:: qutip.solver.nm_mcsolve.NonMarkovianMCSolver :members: :inherited-members: :show-inheritance: + :exclude-members: add_integrator .. _classes-non_markov_heom: @@ -147,12 +153,12 @@ Stochastic Solver .. autoclass:: qutip.solver.stochastic.SMESolver :members: :inherited-members: - :show-inheritance: + :exclude-members: add_integrator .. autoclass:: qutip.solver.stochastic.SSESolver :members: :inherited-members: - :show-inheritance: + :exclude-members: add_integrator .. _classes-ode: @@ -226,18 +232,22 @@ Solver Options and Results .. autoclass:: qutip.solver.result.Result :members: :inherited-members: + :exclude-members: add_processor, add .. autoclass:: qutip.solver.result.MultiTrajResult :members: :inherited-members: + :exclude-members: add_processor, add, add_end_condition .. autoclass:: qutip.solver.result.McResult :members: - :show-inheritance: + :inherited-members: + :exclude-members: add_processor, add, add_end_condition .. autoclass:: qutip.solver.result.NmmcResult :members: - :show-inheritance: + :inherited-members: + :exclude-members: add_processor, add, add_end_condition .. _classes-piqs: diff --git a/doc/apidoc/functions.rst b/doc/apidoc/functions.rst index 21ac49a458..4eb9817c36 100644 --- a/doc/apidoc/functions.rst +++ b/doc/apidoc/functions.rst @@ -168,6 +168,13 @@ Stochastic Schrödinger Equation and Master Equation :members: ssesolve, smesolve +Constructing time dependent systems +----------------------------------- + +.. automodule:: qutip.core.coefficient + :members: coefficient + + Hierarchical Equations of Motion -------------------------------- diff --git a/qutip/bloch.py b/qutip/bloch.py index 26f77a48e7..0978ad48bd 100644 --- a/qutip/bloch.py +++ b/qutip/bloch.py @@ -367,7 +367,7 @@ def add_states(self, state, kind='vector', colors=None, alpha=1.0): Parameters ---------- - state : Qobj + state : :obj:`.Qobj` Input state vector. kind : {'vector', 'point'} @@ -450,7 +450,7 @@ def add_annotation(self, state_or_vector, text, **kwargs): Parameters ---------- - state_or_vector : Qobj/array/list/tuple + state_or_vector : :obj:`.Qobj`/array/list/tuple Position for the annotaion. Qobj of a qubit or a vector of 3 elements. @@ -489,11 +489,11 @@ def add_arc(self, start, end, fmt="b", steps=None, **kwargs): Parameters ---------- - start : Qobj or array-like + start : :obj:`.Qobj` or array-like Array with cartesian coordinates of the first point, or a state vector or density matrix that can be mapped to a point on or within the Bloch sphere. - end : Qobj or array-like + end : :obj:`.Qobj` or array-like Array with cartesian coordinates of the second point, or a state vector or density matrix that can be mapped to a point on or within the Bloch sphere. @@ -563,11 +563,11 @@ def add_line(self, start, end, fmt="k", **kwargs): Parameters ---------- - start : Qobj or array-like + start : :obj:`.Qobj` or array-like Array with cartesian coordinates of the first point, or a state vector or density matrix that can be mapped to a point on or within the Bloch sphere. - end : Qobj or array-like + end : :obj:`.Qobj` or array-like Array with cartesian coordinates of the second point, or a state vector or density matrix that can be mapped to a point on or within the Bloch sphere. @@ -881,7 +881,7 @@ def save(self, name=None, format='png', dirc=None, dpin=None): name : str Name of saved image. Must include path and format as well. - i.e. '/Users/Paul/Desktop/bloch.png' + i.e. '/Users/Me/Desktop/bloch.png' This overrides the 'format' and 'dirc' arguments. format : str Format of output image. diff --git a/qutip/bloch3d.py b/qutip/bloch3d.py index 22f5ec25ce..160b9662e1 100644 --- a/qutip/bloch3d.py +++ b/qutip/bloch3d.py @@ -483,7 +483,7 @@ def save(self, name=None, format='png', dirc=None): ---------- name : str Name of saved image. Must include path and format as well. - i.e. '/Users/Paul/Desktop/bloch.png' + i.e. '/Users/Me/Desktop/bloch.png' This overrides the 'format' and 'dirc' arguments. format : str Format of output image. Default is 'png'. diff --git a/qutip/core/coefficient.py b/qutip/core/coefficient.py index ff62866a46..2de41b5d39 100644 --- a/qutip/core/coefficient.py +++ b/qutip/core/coefficient.py @@ -66,7 +66,7 @@ def coefficient(base, *, tlist=None, args={}, args_ctypes={}, For function based coefficients, the function signature must be either: * ``f(t, ...)`` where the other arguments are supplied as ordinary - "pythonic" arguments (e.g. ``f(t, w, a=5)) + "pythonic" arguments (e.g. ``f(t, w, a=5)``) * ``f(t, args)`` where the arguments are supplied in a "dict" named ``args`` @@ -76,14 +76,14 @@ def coefficient(base, *, tlist=None, args={}, args_ctypes={}, or ``function_style="dict"``. *Examples* - # pythonic style function signature + - pythonic style function signature def f1_t(t, w): return np.exp(-1j * t * w) coeff1 = coefficient(f1_t, args={"w": 1.}) - # dict style function signature + - dict style function signature def f2_t(t, args): return np.exp(-1j * t * args["w"]) @@ -92,16 +92,19 @@ def f2_t(t, args): For string based coeffients, the string must be a compilable python code resulting in a complex. The following symbols are defined: - sin cos tan asin acos atan pi - sinh cosh tanh asinh acosh atanh - exp log log10 erf zerf sqrt - real imag conj abs norm arg proj + + sin, cos, tan, asin, acos, atan, pi, + sinh, cosh, tanh, asinh, acosh, atanh, + exp, log, log10, erf, zerf, sqrt, + real, imag, conj, abs, norm, arg, proj, numpy as np, scipy.special as spe (python interface) and cython_special (scipy cython interface) *Examples* + coeff = coefficient('exp(-1j*w1*t)', args={"w1":1.}) + 'args' is needed for string coefficient at compilation. It is a dict of (name:object). The keys must be a valid variables string. diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 9e3b2e348f..e9d05647fb 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -10,7 +10,6 @@ import qutip from .. import Qobj from .. import data as _data from ..coefficient import coefficient, CompilationOptions -from .coefficient import Coefficient from ._element import * from ..dimensions import type_from_dims from qutip.settings import settings @@ -38,9 +37,9 @@ cdef class QobjEvo: * a callable ``f(t: double, args: dict) -> Qobj`` that returns the value of the quantum object at time ``t``. - * a ``[Qobj, Coefficient]`` pair, where :obj:`.Coefficient` may also be - any item that can be used to construct a coefficient (e.g. a function, - a numpy array of coefficient values, a string expression). + * a ``[Qobj, Coefficient]`` pair, where the :obj:`Coefficient` may be any + item that :func:`.coefficient` can accept (e.g. a function, a numpy + array of coefficient values, a string expression). * a :obj:`.Qobj` (which creates a constant :obj:`.QobjEvo` term). @@ -144,10 +143,10 @@ cdef class QobjEvo: QobjEvo([H0, [H1, coeff1], [H2, coeff2]], args=args) - The coefficients may be specified either using a :obj:`.Coefficient` - object or by a function, string, numpy array or any object that - can be passed to the :func:`~coefficient` function. See the documentation - of :func:`coefficient` for a full description. + The coefficients may be specified either using a :obj:`Coefficient` object + or by a function, string, numpy array or any object that can be passed to + the :func:`.coefficient` function. See the documentation of + :func:`.coefficient` for a full description. An example of a coefficient specified by a function: @@ -181,7 +180,7 @@ cdef class QobjEvo: The coeffients array must have the same len as the tlist. A :obj:`.QobjEvo` may also be built using simple arithmetic operations - combining :obj:`.Qobj` with :obj:`.Coefficient`, for example: + combining :obj:`.Qobj` with :obj:`Coefficient`, for example: .. code-block:: python @@ -682,16 +681,16 @@ cdef class QobjEvo: storage representation. The different storage representations available are the "data-layer - types". By default, these are `qutip.data.Dense` and `qutip.data.CSR`, - which respectively construct a dense matrix store and a compressed - sparse row one. + types". By default, these are :obj:`.Dense`, :obj:`.Dia` and + :obj:`.CSR`, which respectively construct a dense matrix, diagonal + sparse matrixand a compressed sparse row one. The :obj:`.QobjEvo` is transformed inplace. Parameters ---------- data_type : type - The data-layer type that the data of this `Qobj` should be + The data-layer type that the data of this :obj:`.Qobj` should be converted to. Returns @@ -716,9 +715,17 @@ cdef class QobjEvo: Apply mapping to each Qobj contribution. Example: - ``QobjEvo([sigmax(), coeff]).linear_map(spre)`` + + ``QobjEvo([sigmax(), coeff]).linear_map(spre)`` + gives the same result has - ``QobjEvo([spre(sigmax()), coeff])`` + + ``QobjEvo([spre(sigmax()), coeff])`` + + Parameters + ---------- + op_mapping: callable + Funtion to apply to each elements. Returns ------- @@ -775,7 +782,7 @@ cdef class QobjEvo: """ Look for redundance in the :obj:`.QobjEvo` components: - Constant parts, (:obj:`.Qobj` without :obj:`.Coefficient`) will be + Constant parts, (:obj:`.Qobj` without :obj:`Coefficient`) will be summed. Pairs ``[Qobj, Coefficient]`` with the same :obj:`.Qobj` are merged. @@ -821,7 +828,7 @@ cdef class QobjEvo: list_qevo: list The QobjEvo as a list, element are either :obj:`.Qobj` for constant parts, ``[Qobj, Coefficient]`` for coefficient based term. - The original format of the :obj:`.Coefficient` is not restored. + The original format of the :obj:`Coefficient` is not restored. Lastly if the original :obj:`.QobjEvo` is constructed with a function returning a Qobj, the term is returned as a pair of the original function and args (``dict``). diff --git a/qutip/core/qobj.py b/qutip/core/qobj.py index c629d140ef..88cdafcea8 100644 --- a/qutip/core/qobj.py +++ b/qutip/core/qobj.py @@ -364,11 +364,12 @@ def to(self, data_type): storage representation. The different storage representations available are the "data-layer - types" which are known to `qutip.data.to`. By default, these are - `qutip.data.Dense` and `qutip.data.CSR`, which respectively construct a - dense matrix store and a compressed sparse row one. Certain algorithms - and operations may be faster or more accurate when using a more - appropriate data store. + types" which are known to :obj:`qutip.core.data.to`. By default, these + are :class:`~qutip.core.data.CSR`, :class:`~qutip.core.data.Dense` and + :class:`~qutip.core.data.Dia`, which respectively construct a + compressed sparse row matrix, diagonal matrix and a dense one. Certain + algorithms and operations may be faster or more accurate when using a + more appropriate data store. If the data store is already in the format requested, the function returns `self`. Otherwise, it returns a copy of itself with the data @@ -377,14 +378,14 @@ def to(self, data_type): Parameters ---------- data_type : type - The data-layer type that the data of this `Qobj` should be + The data-layer type that the data of this :class:`Qobj` should be converted to. Returns ------- Qobj - A new `Qobj` if a type conversion took place with the data stored - in the requested format, or `self` if not. + A new :class:`Qobj` if a type conversion took place with the data + stored in the requested format, or `self` if not. """ try: converter = _data.to[data_type] @@ -1209,28 +1210,42 @@ def contract(self, inplace=False): def permute(self, order): """ Permute the tensor structure of a quantum object. For example, - ``qutip.tensor(x, y).permute([1, 0])`` + + ``qutip.tensor(x, y).permute([1, 0])`` + will give the same result as - ``qutip.tensor(y, x)`` + + ``qutip.tensor(y, x)`` + and - ``qutip.tensor(a, b, c).permute([1, 2, 0])`` + + ``qutip.tensor(a, b, c).permute([1, 2, 0])`` + will be the same as - ``qutip.tensor(b, c, a)`` + + ``qutip.tensor(b, c, a)`` For regular objects (bras, kets and operators) we expect ``order`` to be a flat list of integers, which specifies the new order of the tensor product. For superoperators, we expect ``order`` to be something like - ``[[0, 2], [1, 3]]`` + + ``[[0, 2], [1, 3]]`` + which tells us to permute according to [0, 2, 1, 3], and then group indices according to the length of each sublist. As another example, permuting a superoperator with dimensions of - ``[[[1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3]]]`` + + ``[[[1, 2, 3], [1, 2, 3]], [[1, 2, 3], [1, 2, 3]]]`` + by an ``order`` - ``[[0, 3], [1, 4], [2, 5]]`` + + ``[[0, 3], [1, 4], [2, 5]]`` + should give a new object with dimensions - ``[[[1, 1], [2, 2], [3, 3]], [[1, 1], [2, 2], [3, 3]]]``. + + ``[[[1, 1], [2, 2], [3, 3]], [[1, 1], [2, 2], [3, 3]]]``. Parameters ---------- diff --git a/qutip/solver/brmesolve.py b/qutip/solver/brmesolve.py index a7d8e58134..c1f2cb5ec0 100644 --- a/qutip/solver/brmesolve.py +++ b/qutip/solver/brmesolve.py @@ -196,7 +196,7 @@ class BRSolver(Solver): spectra : :obj:`.Coefficient` The corresponding bath spectra. As a `Coefficient` using an 'w' - args. Can depend on ``t`` only if a_op is a :obj:`qutip.QobjEvo`. + args. Can depend on ``t`` only if a_op is a :obj:`.QobjEvo`. :obj:`SpectraCoefficient` can be used to conver a coefficient depending on ``t`` to one depending on ``w``. @@ -307,35 +307,35 @@ def options(self): """ Options for bloch redfield solver: - store_final_state: bool, default=False + store_final_state: bool, default: False Whether or not to store the final state of the evolution in the result class. - store_states: bool, default=None + store_states: bool, default: None Whether or not to store the state vectors or density matrices. On `None` the states will be saved if no expectation operators are given. - normalize_output: bool, default=False + normalize_output: bool, default: False Normalize output state to hide ODE numerical errors. - progress_bar: str {'text', 'enhanced', 'tqdm', ''}, default="" + progress_bar: str {'text', 'enhanced', 'tqdm', ''}, default: "" How to present the solver progress. 'tqdm' uses the python module of the same name and raise an error if not installed. Empty string or False will disable the bar. - progress_kwargs: dict, default={"chunk_size":10} + progress_kwargs: dict, default: {"chunk_size":10} Arguments to pass to the progress_bar. Qutip's bars use ``chunk_size``. - tensor_type: str ['sparse', 'dense', 'data'], default="sparse" + tensor_type: str ['sparse', 'dense', 'data'], default: "sparse" Which data type to use when computing the brtensor. With a cutoff 'sparse' is usually the most efficient. - sparse_eigensolver: bool, default=False + sparse_eigensolver: bool, default: False Whether to use the sparse eigensolver - method: str, default="adams" + method: str, default: "adams" Which ODE integrator methods are supported. """ return self._options diff --git a/qutip/solver/floquet.py b/qutip/solver/floquet.py index c8e7757aa7..86c0c2d982 100644 --- a/qutip/solver/floquet.py +++ b/qutip/solver/floquet.py @@ -8,6 +8,7 @@ import numpy as np from qutip.core import data as _data +from qutip.core.data import Data from qutip import Qobj, QobjEvo from .propagator import Propagator from .mesolve import MESolver diff --git a/qutip/solver/heom/bofin_solvers.py b/qutip/solver/heom/bofin_solvers.py index fe55aa7c70..4852c60196 100644 --- a/qutip/solver/heom/bofin_solvers.py +++ b/qutip/solver/heom/bofin_solvers.py @@ -916,7 +916,7 @@ def steady_state( steady_ados : :class:`HierarchyADOsState` The steady state of the full ADO hierarchy. A particular ADO may be extracted from the full state by calling - :meth:`HEOMSolver.extract`. + :meth:`extract`. """ if not self.L_sys.isconstant: raise ValueError( @@ -1025,7 +1025,7 @@ def run(self, state0, tlist, *, args=None, e_ops=None): at tme ``t``. The keys are those given by ``e_ops`` if it was a dict, otherwise they are the indexes of the supplied ``e_ops``. - See :class:`~HEOMResult` and :class:`~Result` for the complete + See :class:`~HEOMResult` and :class:`.Result` for the complete list of attributes. """ return super().run(state0, tlist, args=args, e_ops=e_ops) @@ -1108,36 +1108,36 @@ def options(self): """ Options for HEOMSolver: - store_final_state: bool, default=False + store_final_state: bool, default: False Whether or not to store the final state of the evolution in the result class. - store_states: bool, default=None + store_states: bool, default: None Whether or not to store the state vectors or density matrices. On `None` the states will be saved if no expectation operators are given. - normalize_output: bool, default=False + normalize_output: bool, default: False Normalize output state to hide ODE numerical errors. - progress_bar: str {'text', 'enhanced', 'tqdm', ''}, default="text" + progress_bar: str {'text', 'enhanced', 'tqdm', ''}, default: "text" How to present the solver progress. 'tqdm' uses the python module of the same name and raise an error if not installed. Empty string or False will disable the bar. - progress_kwargs: dict, default={"chunk_size": 10} + progress_kwargs: dict, default: {"chunk_size": 10} Arguments to pass to the progress_bar. Qutip's bars use ``chunk_size``. - method: str, default="adams" + method: str, default: "adams" Which ordinary differential equation integration method to use. - state_data_type: str, default="dense" + state_data_type: str, default: "dense" Name of the data type of the state used during the ODE evolution. Use an empty string to keep the input state type. Many integrators support only work with `Dense`. - store_ados : bool, default=False + store_ados : bool, default: False Whether or not to store the HEOM ADOs. Only relevant when using the HEOM solver. """ @@ -1222,7 +1222,7 @@ class HSolverDL(HEOMSolver): If set to None the default options will be used. Keyword only. Default: None. - combine : bool, default True + combine : bool, default: True Whether to combine exponents with the same frequency (and coupling operator). See :meth:`BosonicBath.combine` for details. Keyword only. Default: True. diff --git a/qutip/solver/integrator/integrator.py b/qutip/solver/integrator/integrator.py index 7b547403cc..f9c2598e17 100644 --- a/qutip/solver/integrator/integrator.py +++ b/qutip/solver/integrator/integrator.py @@ -111,7 +111,7 @@ def integrate(self, t, copy=True): t : float Time to integrate to, should be larger than the previous time. - copy : bool [True] + copy : bool, default: True Whether to return a copy of the state or the state itself. Returns @@ -142,7 +142,7 @@ def mcstep(self, t, copy=True): the last integrate call was use with ``step=True``, the time can be between the time at the start of the last call and now. - copy : bool [True] + copy : bool, default: True Whether to return a copy of the state or the state itself. Returns @@ -173,7 +173,7 @@ def get_state(self, copy=True): Parameters ---------- - copy : bool (True) + copy : bool, default: True Whether to return the data stored in the Integrator or a copy. Returns diff --git a/qutip/solver/integrator/krylov.py b/qutip/solver/integrator/krylov.py index d9cf5757ed..5559020982 100644 --- a/qutip/solver/integrator/krylov.py +++ b/qutip/solver/integrator/krylov.py @@ -204,26 +204,26 @@ def options(self): """ Supported options by krylov method: - atol : float, default=1e-7 + atol : float, default: 1e-7 Absolute tolerance. - nsteps : int, default=100 + nsteps : int, default: 100 Max. number of internal steps/call. - min_step, max_step : float, default=(1e-5, 1e5) + min_step, max_step : float, default: (1e-5, 1e5) Minimum and maximum step size. - krylov_dim: int, default=0 + krylov_dim: int, default: 0 Dimension of Krylov approximation subspaces used for the time evolution approximation. If the defaut 0 is given, the dimension is calculated from the system size N, using `min(int((N + 100)**0.5), N-1)`. - sub_system_tol: float, default=1e-7 + sub_system_tol: float, default: 1e-7 Tolerance to detect a happy breakdown. A happy breakdown occurs when the initial ket is in a subspace of the Hamiltonian smaller than ``krylov_dim``. - always_compute_step: bool, default=False + always_compute_step: bool, default: False If True, the step length is computed each time a new Krylov subspace is computed. Otherwise it is computed only once when creating the integrator. diff --git a/qutip/solver/integrator/qutip_integrator.py b/qutip/solver/integrator/qutip_integrator.py index 3b8f654baa..943c6740ea 100644 --- a/qutip/solver/integrator/qutip_integrator.py +++ b/qutip/solver/integrator/qutip_integrator.py @@ -71,27 +71,27 @@ def options(self): """ Supported options by verner method: - atol : float, default=1e-8 + atol : float, default: 1e-8 Absolute tolerance. - rtol : float, default=1e-6 + rtol : float, default: 1e-6 Relative tolerance. - nsteps : int, default=1000 + nsteps : int, default: 1000 Max. number of internal steps/call. - first_step : float, default=0 + first_step : float, default: 0 Size of initial step (0 = automatic). - min_step : float, default=0 + min_step : float, default: 0 Minimum step size (0 = automatic). - max_step : float, default=0 + max_step : float, default: 0 Maximum step size (0 = automatic) When using pulses, change to half the thinest pulse otherwise it may be skipped. - interpolate : bool, default=True + interpolate : bool, default: True Whether to use interpolation step, faster most of the time. """ return self._options @@ -183,7 +183,7 @@ def options(self): """ Supported options by "diag" method: - eigensolver_dtype : str, default="dense" + eigensolver_dtype : str, default: "dense" Qutip data type {"dense", "csr", etc.} to use when computing the eigenstates. The dense eigen solver is usually faster and more stable. diff --git a/qutip/solver/integrator/scipy_integrator.py b/qutip/solver/integrator/scipy_integrator.py index 9eeeeac009..36b8f78e2d 100644 --- a/qutip/solver/integrator/scipy_integrator.py +++ b/qutip/solver/integrator/scipy_integrator.py @@ -163,25 +163,25 @@ def options(self): """ Supported options by zvode integrator: - atol : float, default=1e-8 + atol : float, default: 1e-8 Absolute tolerance. - rtol : float, default=1e-6 + rtol : float, default: 1e-6 Relative tolerance. - order : int, default=12, 'adams' or 5, 'bdf' + order : int, default: 12, 'adams' or 5, 'bdf' Order of integrator <=12 'adams', <=5 'bdf' - nsteps : int, default=2500 + nsteps : int, default: 2500 Max. number of internal steps/call. - first_step : float, default=0 + first_step : float, default: 0 Size of initial step (0 = automatic). - min_step : float, default=0 + min_step : float, default: 0 Minimum step size (0 = automatic). - max_step : float, default=0 + max_step : float, default: 0 Maximum step size (0 = automatic) When using pulses, change to half the thinest pulse otherwise it may be skipped. @@ -323,25 +323,25 @@ def options(self): """ Supported options by dop853 integrator: - atol : float, default=1e-8 + atol : float, default: 1e-8 Absolute tolerance. - rtol : float, default=1e-6 + rtol : float, default: 1e-6 Relative tolerance. - nsteps : int, default=2500 + nsteps : int, default: 2500 Max. number of internal steps/call. - first_step : float, default=0 + first_step : float, default: 0 Size of initial step (0 = automatic). - max_step : float, default=0 + max_step : float, default: 0 Maximum step size (0 = automatic) - ifactor, dfactor : float, default=6., 0.3 + ifactor, dfactor : float, default: 6., 0.3 Maximum factor to increase/decrease step size by in one step - beta : float, default=0 + beta : float, default: 0 Beta parameter for stabilised step size control. See scipy.integrate.ode ode for more detail @@ -488,30 +488,30 @@ def options(self): """ Supported options by lsoda integrator: - atol : float, default=1e-8 + atol : float, default: 1e-8 Absolute tolerance. - rtol : float, default=1e-6 + rtol : float, default: 1e-6 Relative tolerance. - nsteps : int, default=2500 + nsteps : int, default: 2500 Max. number of internal steps/call. - max_order_ns : int, default=12 + max_order_ns : int, default: 12 Maximum order used in the nonstiff case (<= 12). - max_order_s : int, default=5 + max_order_s : int, default: 5 Maximum order used in the stiff case (<= 5). - first_step : float, default=0 + first_step : float, default: 0 Size of initial step (0 = automatic). - max_step : float, default=0 + max_step : float, default: 0 Maximum step size (0 = automatic) When using pulses, change to half the thinest pulse otherwise it may be skipped. - min_step : float, default=0 + min_step : float, default: 0 Minimum step size (0 = automatic) """ return self._options diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index ce4e1974dc..15ba1a0093 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -349,7 +349,7 @@ class MCSolver(MultiTrajSolver): Parameters ---------- - H : :class:`.Qobj`, :class:`.QobjEvo`, ``list``, callable. + H : :class:`.Qobj`, :class:`.QobjEvo`, list, callable. System Hamiltonian as a Qobj, QobjEvo. It can also be any input type that QobjEvo accepts (see :class:`.QobjEvo`'s documentation). ``H`` can also be a superoperator (liouvillian) if some collapse @@ -538,32 +538,32 @@ def options(self): """ Options for monte carlo solver: - store_final_state: bool, default=False + store_final_state: bool, default: False Whether or not to store the final state of the evolution in the result class. - store_states: bool, default=None + store_states: bool, default: None Whether or not to store the state vectors or density matrices. On `None` the states will be saved if no expectation operators are given. - progress_bar: str {'text', 'enhanced', 'tqdm', ''}, default="text" + progress_bar: str {'text', 'enhanced', 'tqdm', ''}, default: "text" How to present the solver progress. 'tqdm' uses the python module of the same name and raise an error if not installed. Empty string or False will disable the bar. - progress_kwargs: dict, default={"chunk_size":10} + progress_kwargs: dict, default: {"chunk_size":10} Arguments to pass to the progress_bar. Qutip's bars use ``chunk_size``. - keep_runs_results: bool + keep_runs_results: bool, default: False Whether to store results from all trajectories or just store the averages. - method: str, default="adams" + method: str, default: "adams" Which ODE integrator methods are supported. - map: str {"serial", "parallel", "loky"} + map: str {"serial", "parallel", "loky"}, default: "serial" How to run the trajectories. "parallel" uses concurent module to run in parallel while "loky" use the module of the same name to do so. @@ -579,20 +579,20 @@ def options(self): Which of numpy.random's bitgenerator to use. With ``None``, your numpy version's default is used. - mc_corr_eps: float + mc_corr_eps: float, default: 1e-10 Small number used to detect non-physical collapse caused by numerical imprecision. - norm_t_tol: float + norm_t_tol: float, default: 1e-6 Tolerance in time used when finding the collapse. - norm_tol: float + norm_tol: float, default: 1e-4 Tolerance in norm used when finding the collapse. - norm_steps: int + norm_steps: int, default: 5 Maximum number of tries to find the collapse. - improved_sampling: Bool + improved_sampling: Bool, default: False Whether to use the improved sampling algorithm of Abdelhafez et al. PRA (2019) """ diff --git a/qutip/solver/multitraj.py b/qutip/solver/multitraj.py index 1579a05721..86f3324162 100644 --- a/qutip/solver/multitraj.py +++ b/qutip/solver/multitraj.py @@ -66,7 +66,7 @@ def start(self, state, t0, seed=None): t0 : double Initial time of the evolution. - seed : int, SeedSequence, list, {None} + seed : int, SeedSequence, list, optional Seed for the random number generator. It can be a single seed used to spawn seeds for each trajectory or a list of seed, one for each trajectory. @@ -88,12 +88,12 @@ def step(self, t, *, args=None, copy=True): t : double Time to evolve to, must be higher than the last call. - args : dict, optional {None} + args : dict, optional Update the ``args`` of the system. The change is effective from the beginning of the interval. Changing ``args`` can slow the evolution. - copy : bool, optional {True} + copy : bool, default: True Whether to return a copy of the data or the data in the ODE solver. """ if not self._integrator._is_set: @@ -148,7 +148,7 @@ def run(self, state, tlist, ntraj=1, *, ntraj : int Number of trajectories to add. - args : dict, optional {None} + args : dict, optional Change the ``args`` of the rhs for the evolution. e_ops : list @@ -156,14 +156,14 @@ def run(self, state, tlist, ntraj=1, *, Alternatively, function[s] with the signature f(t, state) -> expect can be used. - timeout : float, optional [1e8] + timeout : float, optional Maximum time in seconds for the trajectories to run. Once this time is reached, the simulation will end even if the number of trajectories is less than ``ntraj``. The map function, set in options, can interupt the running trajectory or wait for it to finish. Set to an arbitrary high number to disable. - target_tol : {float, tuple, list}, optional [None] + target_tol : {float, tuple, list}, optional Target tolerance of the evolution. The evolution will compute trajectories until the error on the expectation values is lower than this tolerance. The maximum number of trajectories employed is @@ -172,7 +172,7 @@ def run(self, state, tlist, ntraj=1, *, of absolute and relative tolerance, in that order. Lastly, it can be a list of pairs of (atol, rtol) for each e_ops. - seed : {int, SeedSequence, list} optional + seed : {int, SeedSequence, list}, optional Seed or list of seeds for each trajectories. Returns diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index cec9af73a6..71e79dcc27 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -335,7 +335,7 @@ class NonMarkovianMCSolver(MCSolver): # both classes will be partially initialized in constructor _trajectory_resultclass = NmmcTrajectoryResult - mc_integrator_class = NmMCIntegrator + _mc_integrator_class = NmMCIntegrator def __init__( self, H, ops_and_rates, *_args, args=None, options=None, **kwargs, @@ -372,7 +372,7 @@ def __init__( self._trajectory_resultclass = functools.partial( NmmcTrajectoryResult, __nm_solver=self, ) - self.mc_integrator_class = functools.partial( + self._mc_integrator_class = functools.partial( NmMCIntegrator, __martingale=self._martingale, ) super().__init__(H, c_ops, *_args, options=options, **kwargs) @@ -524,32 +524,32 @@ def options(self): """ Options for non-Markovian Monte Carlo solver: - store_final_state: bool, default=False + store_final_state: bool, default: False Whether or not to store the final state of the evolution in the result class. - store_states: bool, default=None + store_states: bool, default: None Whether or not to store the state vectors or density matrices. On `None` the states will be saved if no expectation operators are given. - progress_bar: str {'text', 'enhanced', 'tqdm', ''}, default="text" + progress_bar: str {'text', 'enhanced', 'tqdm', ''}, default: "text" How to present the solver progress. 'tqdm' uses the python module of the same name and raise an error if not installed. Empty string or False will disable the bar. - progress_kwargs: dict, default={"chunk_size":10} + progress_kwargs: dict, default: {"chunk_size":10} Arguments to pass to the progress_bar. Qutip's bars use ``chunk_size``. - keep_runs_results: bool + keep_runs_results: bool, default: False Whether to store results from all trajectories or just store the averages. - method: str, default="adams" + method: str, default: "adams" Which ODE integrator methods are supported. - map: str {"serial", "parallel", "loky"} + map: str {"serial", "parallel", "loky"}, default: "serial" How to run the trajectories. "parallel" uses concurent module to run in parallel while "loky" use the module of the same name to do so. @@ -565,30 +565,30 @@ def options(self): Which of numpy.random's bitgenerator to use. With ``None``, your numpy version's default is used. - mc_corr_eps: float + mc_corr_eps: float, default: 1e-10 Small number used to detect non-physical collapse caused by numerical imprecision. - norm_t_tol: float + norm_t_tol: float, default: 1e-6 Tolerance in time used when finding the collapse. - norm_tol: float + norm_tol: float, default: 1e-4 Tolerance in norm used when finding the collapse. - norm_steps: int + norm_steps: int, default: 5 Maximum number of tries to find the collapse. - completeness_rtol: float, default=1e-5 + completeness_rtol: float, default: 1e-5 Used in determining whether the given Lindblad operators satisfy a certain completeness relation. If they do not, an additional Lindblad operator is added automatically (with zero rate). - completeness_atol: float, default=1e-8 + completeness_atol: float, default: 1e-8 Used in determining whether the given Lindblad operators satisfy a certain completeness relation. If they do not, an additional Lindblad operator is added automatically (with zero rate). - martingale_quad_limit: float or int, default=100 + martingale_quad_limit: float or int, default: 100 An upper bound on the number of subintervals used in the adaptive integration of the martingale. diff --git a/qutip/solver/propagator.py b/qutip/solver/propagator.py index da80883bd0..e2990359cd 100644 --- a/qutip/solver/propagator.py +++ b/qutip/solver/propagator.py @@ -109,7 +109,9 @@ class Propagator: A generator of propagator for a system. Usage: + U = Propagator(H, c_ops) + psi_t = U(t) @ psi_0 Save some previously computed propagator are stored to speed up subsequent @@ -128,19 +130,19 @@ class Propagator: not supported. c_ops : list, optional - List of Qobj or QobjEvo collapse operators. + List of :obj:`.Qobj` or :obj:`.QobjEvo` collapse operators. - args : dictionary + args : dictionary, optional Parameters to callback functions for time-dependent Hamiltonians and collapse operators. - options : dict + options : dict, optional Options for the solver. - memoize : int [10] + memoize : int, default: 10 Max number of propagator to save. - tol : float [1e-14] + tol : float, default: 1e-14 Absolute tolerance for the time. If a previous propagator was computed at a time within tolerance, that propagator will be returned. diff --git a/qutip/solver/sesolve.py b/qutip/solver/sesolve.py index 4d16e5aa10..d6d7be977e 100644 --- a/qutip/solver/sesolve.py +++ b/qutip/solver/sesolve.py @@ -159,28 +159,28 @@ def options(self): """ Solver's options: - store_final_state: bool, default=False + store_final_state: bool, default: False Whether or not to store the final state of the evolution in the result class. - store_states: bool, default=None + store_states: bool, default: None Whether or not to store the state vectors or density matrices. On `None` the states will be saved if no expectation operators are given. - normalize_output: bool, default=True + normalize_output: bool, default: True Normalize output state to hide ODE numerical errors. - progress_bar: str {"text", "enhanced", "tqdm", ""}, default="" + progress_bar: str {"text", "enhanced", "tqdm", ""}, default: "" How to present the solver progress. 'tqdm' uses the python module of the same name and raise an error if not installed. Empty string or False will disable the bar. - progress_kwargs: dict, default={"chunk_size": 10} + progress_kwargs: dict, default: {"chunk_size": 10} Arguments to pass to the progress_bar. Qutip's bars use ``chunk_size``. - method: str, default="adams" + method: str, default: "adams" Which ordinary differential equation integration method to use. """ return self._options diff --git a/qutip/solver/sode/itotaylor.py b/qutip/solver/sode/itotaylor.py index 040f9e1ec8..6ad4df1545 100644 --- a/qutip/solver/sode/itotaylor.py +++ b/qutip/solver/sode/itotaylor.py @@ -55,13 +55,13 @@ def options(self): """ Supported options by Order 1.5 strong Taylor Stochastic Integrators: - dt : float, default=0.001 + dt : float, default: 0.001 Internal time step. - tol : float, default=1e-10 + tol : float, default: 1e-10 Relative tolerance. - derr_dt : float, default=1e-6 + derr_dt : float, default: 1e-6 Finite time difference used to compute the derrivative of the hamiltonian and ``sc_ops``. """ @@ -126,22 +126,22 @@ def options(self): Supported options by Implicit Order 1.5 strong Taylor Stochastic Integrators: - dt : float, default=0.001 + dt : float, default: 0.001 Internal time step. - tol : float, default=1e-10 + tol : float, default: 1e-10 Tolerance for the time steps. - solve_method : str, default=None + solve_method : str, default: None Method used for solver the ``Ax=b`` of the implicit step. Accept methods supported by :func:`qutip.core.data.solve`. When the system is constant, the inverse of the matrix ``A`` can be used by entering ``inv``. - solve_options : dict, default={} + solve_options : dict, default: {} Options to pass to the call to :func:`qutip.core.data.solve`. - derr_dt : float, default=1e-6 + derr_dt : float, default: 1e-6 Finite time difference used to compute the derrivative of the hamiltonian and ``sc_ops``. """ diff --git a/qutip/solver/sode/rouchon.py b/qutip/solver/sode/rouchon.py index 9376bd0b2d..79ed74bdf6 100644 --- a/qutip/solver/sode/rouchon.py +++ b/qutip/solver/sode/rouchon.py @@ -120,10 +120,10 @@ def options(self): """ Supported options by Rouchon Stochastic Integrators: - dt : float, default=0.001 + dt : float, default: 0.001 Internal time step. - tol : float, default=1e-7 + tol : float, default: 1e-7 Relative tolerance. """ return self._options diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index 94b04409a5..aa8538e845 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -139,10 +139,10 @@ def options(self): """ Supported options by Explicit Stochastic Integrators: - dt : float, default=0.001 + dt : float, default: 0.001 Internal time step. - tol : float, default=1e-10 + tol : float, default: 1e-10 Tolerance for the time steps. """ return self._options @@ -181,19 +181,19 @@ def options(self): """ Supported options by Implicit Stochastic Integrators: - dt : float, default=0.001 + dt : float, default: 0.001 Internal time step. - tol : float, default=1e-10 + tol : float, default: 1e-10 Tolerance for the time steps. - solve_method : str, default=None + solve_method : str, default: None Method used for solver the ``Ax=b`` of the implicit step. Accept methods supported by :func:`qutip.core.data.solve`. When the system is constant, the inverse of the matrix ``A`` can be used by entering ``inv``. - solve_options : dict, default={} + solve_options : dict, default: {} Options to pass to the call to :func:`qutip.core.data.solve`. """ return self._options @@ -256,17 +256,17 @@ def options(self): """ Supported options by Explicit Stochastic Integrators: - dt : float, default=0.001 + dt : float, default: 0.001 Internal time step. - tol : float, default=1e-10 + tol : float, default: 1e-10 Tolerance for the time steps. - alpha : float, default=0. + alpha : float, default: 0. Implicit factor to the drift. eff_drift ~= drift(t) * (1-alpha) + drift(t+dt) * alpha - eta : float, default=0.5 + eta : float, default: 0.5 Implicit factor to the diffusion. eff_diffusion ~= diffusion(t) * (1-eta) + diffusion(t+dt) * eta """ diff --git a/qutip/solver/solver_base.py b/qutip/solver/solver_base.py index a906147a5f..748a491b40 100644 --- a/qutip/solver/solver_base.py +++ b/qutip/solver/solver_base.py @@ -135,7 +135,7 @@ def run(self, state0, tlist, *, args=None, e_ops=None): Returns ------- - results : :obj:`qutip.solver.Result` + results : :obj:`.Result` Results of the evolution. States and/or expect will be saved. You can control the saved data in the options. """ diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index 334fd839ee..9bbc3f1884 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -621,49 +621,49 @@ def options(self): """ Options for stochastic solver: - store_final_state: bool, default=False + store_final_state: bool, default: False Whether or not to store the final state of the evolution in the result class. - store_states: bool, default=None + store_states: None, bool, default: None Whether or not to store the state vectors or density matrices. On `None` the states will be saved if no expectation operators are given. - store_measurement: bool, [False] + store_measurement: bool, default: False Whether to store the measurement for each trajectories. Storing measurements will also store the wiener process, or brownian noise for each trajectories. - progress_bar: str {'text', 'enhanced', 'tqdm', ''}, default="text" + progress_bar: str {'text', 'enhanced', 'tqdm', ''}, default: "text" How to present the solver progress. 'tqdm' uses the python module of the same name and raise an error if not installed. Empty string or False will disable the bar. - progress_kwargs: dict, default={"chunk_size":10} + progress_kwargs: dict, default: {"chunk_size":10} Arguments to pass to the progress_bar. Qutip's bars use ``chunk_size``. - keep_runs_results: bool + keep_runs_results: bool, default: False Whether to store results from all trajectories or just store the averages. - method: str, default="rouchon" + method: str, default: "taylor1.5" Which ODE integrator methods are supported. - map: str {"serial", "parallel", "loky"}, default="serial" + map: str {"serial", "parallel", "loky"}, default: "serial" How to run the trajectories. "parallel" uses concurent module to run in parallel while "loky" use the module of the same name to do so. - job_timeout: None, int, default=None + job_timeout: None, int, default: None Maximum time to compute one trajectory. - num_cpus: None, int, default=None + num_cpus: None, int, default: None Number of cpus to use when running in parallel. ``None`` detect the number of available cpus. - bitgenerator: {None, "MT19937", "PCG64DXSM", ...}, default=None + bitgenerator: {None, "MT19937", "PCG64DXSM", ...}, default: None Which of numpy.random's bitgenerator to use. With ``None``, your numpy version's default is used. """ @@ -688,10 +688,10 @@ class SMESolver(StochasticSolver): sc_ops : list of (:obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format) List of stochastic collapse operators. - heterodyne : bool, [False] + heterodyne : bool, default: False Whether to use heterodyne or homodyne detection. - options : dict, [optional] + options : dict, optional Options for the solver, see :obj:`SMESolver.options` and `SIntegrator <./classes.html#classes-sode>`_ for a list of all options. """ @@ -732,10 +732,10 @@ class SSESolver(StochasticSolver): sc_ops : list of (:obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format) List of stochastic collapse operators. - heterodyne : bool, [False] + heterodyne : bool, default: False Whether to use heterodyne or homodyne detection. - options : dict, [optional] + options : dict, optional Options for the solver, see :obj:`SSESolver.options` and `SIntegrator <./classes.html#classes-sode>`_ for a list of all options. """ @@ -750,7 +750,7 @@ class SSESolver(StochasticSolver): "store_measurement": False, "keep_runs_results": False, "normalize_output": False, - "method": "platen", + "method": "taylor1.5", "map": "serial", "job_timeout": None, "num_cpus": None, From dc8b813de363902dba3309d9358c0b50f0d6fec5 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 13 Nov 2023 16:29:15 -0500 Subject: [PATCH 075/247] Unite stochatis default ODE --- qutip/solver/stochastic.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index 9bbc3f1884..06c5e76a0f 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -648,7 +648,7 @@ def options(self): Whether to store results from all trajectories or just store the averages. - method: str, default: "taylor1.5" + method: str, default: "platen" Which ODE integrator methods are supported. map: str {"serial", "parallel", "loky"}, default: "serial" @@ -706,7 +706,7 @@ class SMESolver(StochasticSolver): "store_measurement": False, "keep_runs_results": False, "normalize_output": False, - "method": "taylor1.5", + "method": "platen", "map": "serial", "job_timeout": None, "num_cpus": None, @@ -750,7 +750,7 @@ class SSESolver(StochasticSolver): "store_measurement": False, "keep_runs_results": False, "normalize_output": False, - "method": "taylor1.5", + "method": "platen", "map": "serial", "job_timeout": None, "num_cpus": None, From 344baba4d989adfdcf46cf307e30e34e743bef0f Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 14 Nov 2023 08:34:04 -0500 Subject: [PATCH 076/247] function api up to super --- doc/apidoc/functions.rst | 2 +- qutip/core/operators.py | 163 ++++++++++++++++++++------------------- qutip/core/states.py | 155 +++++++++++++++++++------------------ qutip/random_objects.py | 67 ++++++++-------- 4 files changed, 195 insertions(+), 192 deletions(-) diff --git a/doc/apidoc/functions.rst b/doc/apidoc/functions.rst index 4eb9817c36..63d9a0e6ad 100644 --- a/doc/apidoc/functions.rst +++ b/doc/apidoc/functions.rst @@ -18,7 +18,7 @@ Quantum Operators ----------------- .. automodule:: qutip.core.operators - :members: charge, commutator, create, destroy, displace, enr_destroy, enr_identity, fcreate, fdestroy, jmat, num, qeye, identity, momentum, phase, position, qdiags, qutrit_ops, qzero, sigmam, sigmap, sigmax, sigmay, sigmaz, spin_Jx, spin_Jy, spin_Jz, spin_Jm, spin_Jp, squeeze, squeezing, tunneling + :members: charge, commutator, create, destroy, displace, enr_destroy, enr_identity, fcreate, fdestroy, jmat, num, qeye, identity, momentum, phase, position, qdiags, qutrit_ops, qzero, sigmam, sigmap, sigmax, sigmay, sigmaz, spin_Jx, spin_Jy, spin_Jz, spin_Jm, spin_Jp, squeeze, squeezing, tunneling, qeye_like, qzero_like .. _functions-rand: diff --git a/qutip/core/operators.py b/qutip/core/operators.py index b3e041ead6..1fcb97049f 100644 --- a/qutip/core/operators.py +++ b/qutip/core/operators.py @@ -45,8 +45,8 @@ def qdiags(diagonals, offsets=None, dims=None, shape=None, *, Shape of operator. If omitted, a square operator large enough to contain the diagonals is generated. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Examples @@ -75,12 +75,12 @@ def jmat(j, which=None, *, dtype=None): j : float Spin of operator - which : str + which : str, optional Which operator to return 'x','y','z','+','-'. - If no args given, then output is ['x','y','z'] + If not given, then output is ['x','y','z'] - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -110,11 +110,6 @@ def jmat(j, which=None, *, dtype=None): [[ 1. 0. 0.] [ 0. 0. 0.] [ 0. 0. -1.]]] - - Notes - ----- - If no 'args' input, then returns array of ['x','y','z'] operators. - """ dtype = dtype or settings.core["default_dtype"] or _data.CSR if int(2 * j) != 2 * j or j < 0: @@ -179,8 +174,8 @@ def spin_Jx(j, *, dtype=None): j : float Spin of operator - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -200,8 +195,8 @@ def spin_Jy(j, *, dtype=None): j : float Spin of operator - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -221,8 +216,8 @@ def spin_Jz(j, *, dtype=None): j : float Spin of operator - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -242,8 +237,8 @@ def spin_Jm(j, *, dtype=None): j : float Spin of operator - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -263,8 +258,8 @@ def spin_Jp(j, *, dtype=None): j : float Spin of operator - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -284,8 +279,8 @@ def spin_J_set(j, *, dtype=None): j : float Spin of operators - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -398,12 +393,12 @@ def destroy(N, offset=0, *, dtype=None): N : int Dimension of Hilbert space. - offset : int (default 0) + offset : int, default: 0 The lowest number state that is included in the finite number state representation of the operator. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -437,12 +432,12 @@ def create(N, offset=0, *, dtype=None): N : int Dimension of Hilbert space. - offset : int (default 0) + offset : int, default: 0 The lowest number state that is included in the finite number state representation of the operator. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -450,10 +445,6 @@ def create(N, offset=0, *, dtype=None): oper : qobj Qobj for raising operator. - offset : int (default 0) - The lowest number state that is included in the finite number state - representation of the operator. - Examples -------- >>> create(4) # doctest: +SKIP @@ -489,10 +480,14 @@ def fdestroy(n_sites, site, dtype=None): n_sites : int Number of sites in Fock space. - site : int (default 0) + site : int, default: 0 The site in Fock space to add a fermion to. Corresponds to j in the above JW transform. + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is + accepted. + Returns ------- oper : qobj @@ -522,7 +517,7 @@ def fcreate(n_sites, site, dtype=None): .. math:: a_j = \\sigma_z^{\\otimes j} - \\otimes (frac{sigma_x - i sigma_y}{2}) + \\otimes (\\frac{\\sigma_x - i \\sigma_y}{2}) \\otimes I^{\\otimes N-j-1} @@ -535,6 +530,10 @@ def fcreate(n_sites, site, dtype=None): The site in Fock space to add a fermion to. Corresponds to j in the above JW transform. + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is + accepted. + Returns ------- oper : qobj @@ -579,6 +578,10 @@ def _f_op(n_sites, site, action, dtype=None): The site in Fock space to create/destroy a fermion on. Corresponds to j in the above JW transform. + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is + accepted. + Returns ------- oper : qobj @@ -654,8 +657,8 @@ def qzero(dimensions, *, dtype=None): the new Qobj are set to this list. This can produce either `oper` or `super` depending on the passed `dimensions`. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -708,8 +711,8 @@ def qeye(dimensions, *, dtype=None): the new Qobj are set to this list. This can produce either `oper` or `super` depending on the passed `dimensions`. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -774,19 +777,19 @@ def qeye_like(qobj): def position(N, offset=0, *, dtype=None): """ - Position operator x=1/sqrt(2)*(a+a.dag()) + Position operator :math:`x = 1 / sqrt(2) * (a + a.dag())` Parameters ---------- N : int Number of Fock states in Hilbert space. - offset : int (default 0) + offset : int, default: 0 The lowest number state that is included in the finite number state representation of the operator. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -809,12 +812,12 @@ def momentum(N, offset=0, *, dtype=None): N : int Number of Fock states in Hilbert space. - offset : int (default 0) + offset : int, default: 0 The lowest number state that is included in the finite number state representation of the operator. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -837,12 +840,12 @@ def num(N, offset=0, *, dtype=None): N : int The dimension of the Hilbert space. - offset : int (default 0) + offset : int, default: 0 The lowest number state that is included in the finite number state representation of the operator. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -876,12 +879,12 @@ def squeeze(N, z, offset=0, *, dtype=None): z : float/complex Squeezing parameter. - offset : int (default 0) + offset : int, default: 0 The lowest number state that is included in the finite number state representation of the operator. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -947,12 +950,12 @@ def displace(N, alpha, offset=0, *, dtype=None): alpha : float/complex Displacement amplitude. - offset : int (default 0) + offset : int, default: 0 The lowest number state that is included in the finite number state representation of the operator. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -981,6 +984,14 @@ def commutator(A, B, kind="normal"): """ Return the commutator of kind `kind` (normal, anti) of the two operators A and B. + + Parameters + ---------- + A, B : :obj:`Qobj`, :obj:`QobjEvo` + Operators to compute the commutator + + kind: ste {"normal", "anti"}, default: "anti" + Which kind of commutator to compute. """ if kind == 'normal': return A @ B - B @ A @@ -998,8 +1009,8 @@ def qutrit_ops(*, dtype=None): Parameters ---------- - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -1031,11 +1042,11 @@ def phase(N, phi0=0, *, dtype=None): N : int Number of basis states in Hilbert space. - phi0 : float + phi0 : float, default: 0 Reference phase. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -1092,8 +1103,8 @@ def enr_destroy(dims, excitations, *, dtype=None): The maximum number of excitations that are to be included in the state space. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -1138,11 +1149,8 @@ def enr_identity(dims, excitations, *, dtype=None): The maximum number of excitations that are to be included in the state space. - state : list of integers - The state in the number basis representation. - - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -1173,14 +1181,14 @@ def charge(Nmax, Nmin=None, frac=1, *, dtype=None): Nmax : int Maximum charge state to consider. - Nmin : int (default = -Nmax) + Nmin : int, default: -Nmax Lowest charge state to consider. - frac : float (default = 1) + frac : float, default: 1 Specify fractional charge if needed. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -1212,22 +1220,17 @@ def tunneling(N, m=1, *, dtype=None): N : int Number of basis states in Hilbert space. - m : int (default = 1) + m : int, default: 1 Number of excitations in tunneling event. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns ------- T : Qobj Tunneling operator. - - Notes - ----- - .. versionadded:: 3.2 - """ diags = [np.ones(N-m, dtype=int), np.ones(N-m, dtype=int)] T = qdiags(diags, [m, -m], dtype=dtype) @@ -1247,7 +1250,7 @@ def qft(dimensions, *, dtype="dense"): the new Qobj are set to this list. dtype : str or type, [keyword only] [optional] - Storage representation. Any data-layer known to `qutip.data.to` is + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns diff --git a/qutip/core/states.py b/qutip/core/states.py index 7ab4922ee5..eb300a6319 100644 --- a/qutip/core/states.py +++ b/qutip/core/states.py @@ -63,8 +63,8 @@ def basis(dimensions, n=None, offset=None, *, dtype=None): The lowest number state that is included in the finite number state representation of the state in the relevant dimension. - dtype : type or str - storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -135,8 +135,8 @@ def basis(dimensions, n=None, offset=None, *, dtype=None): def qutrit_basis(*, dtype=None): """Basis states for a three level system (qutrit) - dtype : type or str - storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -171,16 +171,16 @@ def coherent(N, alpha, offset=0, method=None, *, dtype=None): alpha : float/complex Eigenvalue of coherent state. - offset : int (default 0) + offset : int, default: 0 The lowest number state that is included in the finite number state representation of the state. Using a non-zero offset will make the default method 'analytic'. - method : string {'operator', 'analytic'} + method : string {'operator', 'analytic'}, optional Method for generating coherent state. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -249,7 +249,7 @@ def coherent(N, alpha, offset=0, method=None, *, dtype=None): def coherent_dm(N, alpha, offset=0, method='operator', *, dtype=None): """Density matrix representation of a coherent state. - Constructed via outer product of :func:`qutip.states.coherent` + Constructed via outer product of :func:`coherent` Parameters ---------- @@ -259,15 +259,15 @@ def coherent_dm(N, alpha, offset=0, method='operator', *, dtype=None): alpha : float/complex Eigenvalue for coherent state. - offset : int (default 0) + offset : int, default: 0 The lowest number state that is included in the finite number state representation of the state. - method : string {'operator', 'analytic'} + method : string {'operator', 'analytic'}, optional Method for generating coherent density matrix. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -306,7 +306,7 @@ def coherent_dm(N, alpha, offset=0, method='operator', *, dtype=None): def fock_dm(dimensions, n=None, offset=None, *, dtype=None): """Density matrix representation of a Fock state - Constructed via outer product of :func:`qutip.states.fock`. + Constructed via outer product of :func:`basis`. Parameters ---------- @@ -314,18 +314,18 @@ def fock_dm(dimensions, n=None, offset=None, *, dtype=None): Number of Fock states in Hilbert space. If a list, then the resultant object will be a tensor product over spaces with those dimensions. - n : int or list of ints, optional (default 0 for all dimensions) + n : int or list of ints, default: 0 for all dimensions Integer corresponding to desired number state, defaults to 0 for all dimensions if omitted. The shape must match ``dimensions``, e.g. if ``dimensions`` is a list, then ``n`` must either be omitted or a list of equal length. - offset : int or list of ints, optional (default 0 for all dimensions) + offset : int or list of ints, default: 0 for all dimensions The lowest number state that is included in the finite number state representation of the state in the relevant dimension. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -351,7 +351,7 @@ def fock_dm(dimensions, n=None, offset=None, *, dtype=None): def fock(dimensions, n=None, offset=None, *, dtype=None): """Bosonic Fock (number) state. - Same as :func:`qutip.states.basis`. + Same as :func:`basis`. Parameters ---------- @@ -359,18 +359,18 @@ def fock(dimensions, n=None, offset=None, *, dtype=None): Number of Fock states in Hilbert space. If a list, then the resultant object will be a tensor product over spaces with those dimensions. - n : int or list of ints, optional (default 0 for all dimensions) + n : int or list of ints, default: 0 for all dimensions Integer corresponding to desired number state, defaults to 0 for all dimensions if omitted. The shape must match ``dimensions``, e.g. if ``dimensions`` is a list, then ``n`` must either be omitted or a list of equal length. - offset : int or list of ints, optional (default 0 for all dimensions) + offset : int or list of ints, default: 0 for all dimensions The lowest number state that is included in the finite number state representation of the state in the relevant dimension. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -402,12 +402,12 @@ def thermal_dm(N, n, method='operator', *, dtype=None): n : float Expectation value for number of particles in thermal state. - method : string {'operator', 'analytic'} + method : string {'operator', 'analytic'}, default: 'operator' ``string`` that sets the method used to generate the thermal state probabilities - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -480,13 +480,13 @@ def maximally_mixed_dm(N, *, dtype=None): N : int Number of basis states in Hilbert space. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns ------- - dm : qobj + dm : :obj:`.Qobj` Thermal state density matrix. """ dtype = dtype or settings.core["default_dtype"] or _data.Dia @@ -499,16 +499,16 @@ def maximally_mixed_dm(N, *, dtype=None): def ket2dm(Q): """ Takes input ket or bra vector and returns density matrix formed by outer - product. This is completely identical to calling `Q.proj()`. + product. This is completely identical to calling ``Q.proj()``. Parameters ---------- - Q : qobj + Q : :obj:`.Qobj` Ket or bra type quantum object. Returns ------- - dm : qobj + dm : :obj:`.Qobj` Density matrix formed by outer product of `Q`. Examples @@ -541,12 +541,12 @@ def projection(N, n, m, offset=None, *, dtype=None): n, m : float The number states in the projection. - offset : int (default 0) + offset : int, default: 0 The lowest number state that is included in the finite number state representation of the projector. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -573,8 +573,8 @@ def qstate(string, *, dtype=None): qstate : qobj Qobj for tensor product corresponding to input string. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Notes @@ -641,12 +641,12 @@ def ket(seq, dim=2, *, dtype=None): Note: for dimension > 9 you need to use a list. - dim : int (default: 2) / list of ints + dim : int or list of ints, default: 2 Space dimension for each particle: int if there are the same, list if they are different. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -731,8 +731,8 @@ def bra(seq, dim=2, *, dtype=None): Space dimension for each particle: int if there are the same, list if they are different. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -785,7 +785,7 @@ def state_number_enumerate(dims, excitations=None): dims : list or array The quantum state dimensions array, as it would appear in a Qobj. - excitations : integer (None) + excitations : integer, optional Restrict state space to states with excitation numbers below or equal to this value. @@ -911,8 +911,8 @@ def state_number_qobj(dims, state, *, dtype=None): state : list State number array. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -948,8 +948,8 @@ def enr_state_dictionaries(dims, excitations): The number of states `nstates`, a dictionary for looking up state indices from a state tuple, and a list containing the state tuples ordered by state indices. state2idx and idx2state are reverses of - each other, i.e., state2idx[idx2state[idx]] = idx and - idx2state[state2idx[state]] = state. + each other, i.e., ``state2idx[idx2state[idx]] = idx`` and + ``idx2state[state2idx[state]] = state``. """ idx2state = list(state_number_enumerate(dims, excitations)) state2idx = {state: idx for idx, state in enumerate(idx2state)} @@ -981,8 +981,8 @@ def enr_fock(dims, excitations, state, *, dtype=None): state : list of integers The state in the number basis representation. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -1030,8 +1030,8 @@ def enr_thermal_dm(dims, excitations, n, *, dtype=None): length as dims, in which each element corresponds specifies the temperature of the corresponding mode. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -1065,13 +1065,13 @@ def phase_basis(N, m, phi0=0, *, dtype=None): m : int Integer corresponding to the mth discrete phase - phi_m = phi0 + 2 * pi * m / N + ``phi_m = phi0 + 2 * pi * m / N`` - phi0 : float (default=0) + phi0 : float, default: 0 Reference phase angle. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -1100,12 +1100,13 @@ def zero_ket(N, dims=None, *, dtype=None): ---------- N : int Hilbert space dimensionality - dims : list + + dims : list, optional Optional dimensions if ket corresponds to a composite Hilbert space. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -1130,11 +1131,11 @@ def spin_state(j, m, type='ket', *, dtype=None): m : int Eigenvalue of the spin-j Sz operator. - type : string {'ket', 'bra', 'dm'} + type : string {'ket', 'bra', 'dm'}, default: 'ket' Type of state to generate. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -1169,11 +1170,11 @@ def spin_coherent(j, theta, phi, type='ket', *, dtype=None): phi : float Angle from x axis. - type : string {'ket', 'bra', 'dm'} + type : string {'ket', 'bra', 'dm'}, default: 'ket' Type of state to generate. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -1224,11 +1225,11 @@ def bell_state(state='00', *, dtype=None): Parameters ---------- - state : str ['00', '01', `10`, `11`] + state : str ['00', '01', '10', '11'] Which bell state to return - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -1252,8 +1253,8 @@ def singlet_state(*, dtype=None): Parameters ---------- - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -1277,8 +1278,8 @@ def triplet_states(*, dtype=None): Parameters ---------- - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -1301,11 +1302,11 @@ def w_state(N=3, *, dtype=None): Parameters ---------- - N : int (default=3) + N : int, default: 3 Number of qubits in state - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -1328,11 +1329,11 @@ def ghz_state(N=3, *, dtype=None): Parameters ---------- - N : int (default=3) + N : int, default: 3 Number of qubits in state - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns diff --git a/qutip/random_objects.py b/qutip/random_objects.py index 1342021749..b55c32bf0e 100644 --- a/qutip/random_objects.py +++ b/qutip/random_objects.py @@ -222,10 +222,10 @@ def rand_herm(dimensions, density=0.30, distribution="fill", *, the new Qobj are set to this list. This can produce either `oper` or `super` depending on the passed `dimensions`. - density : float, [0.30] + density : float, default: 0.30 Density between [0,1] of output Hermitian operator. - distribution : str {"fill", "pos_def", "eigen"} + distribution : str {"fill", "pos_def", "eigen"}, default: "fill" Method used to obtain the density matrices. - "fill" : Uses :math:`H=0.5*(X+X^{+})` where :math:`X` is a randomly @@ -244,8 +244,8 @@ def rand_herm(dimensions, density=0.30, distribution="fill", *, Seed to create the random number generator or a pre prepared generator. When none is suplied, a default generator is used. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -347,10 +347,10 @@ def rand_unitary(dimensions, density=1, distribution="haar", *, the new Qobj are set to this list. This can produce either `oper` or `super` depending on the passed `dimensions`. - density : float, [1] + density : float, default: 1 Density between [0,1] of output unitary operator. - distribution : ["haar", "exp"] + distribution : str {"haar", "exp"}, default: "haar" Method used to obtain the unitary matrices. - haar : Haar random unitary matrix using the algorithm of [Mez07]_. @@ -361,8 +361,8 @@ def rand_unitary(dimensions, density=1, distribution="haar", *, Seed to create the random number generator or a pre prepared generator. When none is suplied, a default generator is used. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -450,11 +450,11 @@ def rand_ket(dimensions, density=1, distribution="haar", *, the new Qobj are set to this list. This can produce either `oper` or `super` depending on the passed `dimensions`. - density : float, [1] + density : float, default: 1 Density between [0,1] of output ket state when using the ``fill`` method. - distribution : str {"haar", "fill"} + distribution : str {"haar", "fill"}, default: "haar" Method used to obtain the kets. - haar : Haar random pure state obtained by applying a Haar random @@ -465,8 +465,8 @@ def rand_ket(dimensions, density=1, distribution="haar", *, Seed to create the random number generator or a pre prepared generator. When none is suplied, a default generator is used. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -512,11 +512,12 @@ def rand_dm(dimensions, density=0.75, distribution="ginibre", *, the new Qobj are set to this list. This can produce either ``oper`` or ``super`` depending on the passed ``dimensions``. - density : float + density : float, default: 0.75 Density between [0,1] of output density matrix. Used by the "pure", "eigen" and "herm". - distribution : str {"ginibre", "hs", "pure", "eigen", "uniform"} + distribution : str {"ginibre", "hs", "pure", "eigen", "uniform"}, \ +default: "ginibre" Method used to obtain the density matrices. - "ginibre" : Ginibre random density operator of rank ``rank`` by using @@ -539,8 +540,8 @@ def rand_dm(dimensions, density=0.75, distribution="ginibre", *, Seed to create the random number generator or a pre prepared generator. When none is suplied, a default generator is used. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -628,8 +629,7 @@ def _rand_dm_ginibre(N, rank, generator): return rho -def rand_kraus_map(dimensions, *, seed=None, - dtype=None): +def rand_kraus_map(dimensions, *, seed=None, dtype=None): """ Creates a random CPTP map on an N-dimensional Hilbert space in Kraus form. @@ -646,8 +646,8 @@ def rand_kraus_map(dimensions, *, seed=None, Seed to create the random number generator or a pre prepared generator. When none is suplied, a default generator is used. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -667,8 +667,7 @@ def rand_kraus_map(dimensions, *, seed=None, for x in oper_list] -def rand_super(dimensions, *, superrep="super", seed=None, - dtype=None): +def rand_super(dimensions, *, superrep="super", seed=None, dtype=None): """ Returns a randomly drawn superoperator acting on operators acting on N dimensions. @@ -681,15 +680,15 @@ def rand_super(dimensions, *, superrep="super", seed=None, the new Qobj are set to this list. This can produce either `oper` or `super` depending on the passed `dimensions`. - superrop : str, optional, {"super"} + superrop : str, default: "super" Representation of the super operator seed : int, SeedSequence, Generator, optional Seed to create the random number generator or a pre prepared generator. When none is suplied, a default generator is used. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. """ dtype = dtype or settings.core["default_dtype"] or _data.Dense @@ -729,11 +728,11 @@ def rand_super_bcsz(dimensions, enforce_tp=True, rank=None, *, density matrices this superoperator is applied to: ``dimensions=[2,2]`` ``dims=[[[2,2],[2,2]], [[2,2],[2,2]]]``. - enforce_tp : bool + enforce_tp : bool, default: True If True, the trace-preserving condition of [BCSZ08]_ is enforced; otherwise only complete positivity is enforced. - rank : int or None + rank : int, optional Rank of the sampled superoperator. If None, a full-rank superoperator is generated. @@ -741,11 +740,11 @@ def rand_super_bcsz(dimensions, enforce_tp=True, rank=None, *, Seed to create the random number generator or a pre prepared generator. When none is suplied, a default generator is used. - superrop : str, optional, {"super"} + superrop : str, default: "super" representation of the - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns @@ -825,18 +824,18 @@ def rand_stochastic(dimensions, density=0.75, kind='left', the new Qobj are set to this list. This can produce either `oper` or `super` depending on the passed `dimensions`. - density : float, [0.75] + density : float, default: 0.75 Density between [0,1] of output density matrix. - kind : str (Default = 'left') + kind : str {"left", "right"}, default: "left" Generate 'left' or 'right' stochastic matrix. seed : int, SeedSequence, Generator, optional Seed to create the random number generator or a pre prepared generator. When none is suplied, a default generator is used. - dtype : type or str - Storage representation. Any data-layer known to `qutip.data.to` is + dtype : type or str, optional + Storage representation. Any data-layer known to ``qutip.data.to`` is accepted. Returns From d1d1959079f1a74dfec87fb612c719273fbe5b0b Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 14 Nov 2023 10:28:01 -0500 Subject: [PATCH 077/247] function api up to measurement --- doc/apidoc/functions.rst | 2 +- qutip/continuous_variables.py | 62 +++++++++++++++++------------------ qutip/core/dimensions.py | 5 +++ qutip/core/expect.py | 9 ++--- qutip/core/metrics.py | 40 +++++++++++----------- qutip/core/superop_reps.py | 37 ++++++++++++++------- qutip/core/superoperator.py | 51 +++++++++++++++------------- qutip/core/tensor.py | 2 ++ qutip/entropy.py | 18 +++++----- qutip/partial_transpose.py | 6 ++-- 10 files changed, 131 insertions(+), 101 deletions(-) diff --git a/doc/apidoc/functions.rst b/doc/apidoc/functions.rst index 63d9a0e6ad..2beb8deab1 100644 --- a/doc/apidoc/functions.rst +++ b/doc/apidoc/functions.rst @@ -54,7 +54,7 @@ Operators and Superoperator Dimensions -------------------------------------- .. automodule:: qutip.core.dimensions - :members: is_scalar, is_vector, is_vectorized_oper, type_from_dims, flatten, deep_remove, unflatten, collapse_dims_oper, collapse_dims_super, enumerate_flat, deep_map, dims_to_tensor_perm, dims_to_tensor_shape, dims_idxs_to_tensor_idxs + :members: is_scalar, is_vector, is_vectorized_oper, collapse_dims_oper, collapse_dims_super, dims_to_tensor_perm, dims_to_tensor_shape, dims_idxs_to_tensor_idxs Functions acting on states and operators diff --git a/qutip/continuous_variables.py b/qutip/continuous_variables.py index 519f070ffa..bcd4391ce1 100644 --- a/qutip/continuous_variables.py +++ b/qutip/continuous_variables.py @@ -24,7 +24,7 @@ def correlation_matrix(basis, rho=None): ---------- basis : list List of operators that defines the basis for the correlation matrix. - rho : Qobj + rho : Qobj, optional Density matrix for which to calculate the correlation matrix. If `rho` is `None`, then a matrix of correlation matrix operators is returned instead of expectation values of those operators. @@ -71,7 +71,7 @@ def covariance_matrix(basis, rho, symmetrized=True): List of operators that defines the basis for the covariance matrix. rho : Qobj Density matrix for which to calculate the covariance matrix. - symmetrized : bool {True, False} + symmetrized : bool, default: True Flag indicating whether the symmetrized (default) or non-symmetrized correlation matrix is to be calculated. @@ -103,12 +103,12 @@ def correlation_matrix_field(a1, a2, rho=None): Field operator for mode 1. a2 : Qobj Field operator for mode 2. - rho : Qobj + rho : Qobj, optional Density matrix for which to calculate the covariance matrix. Returns ------- - cov_mat : ndarray + cov_mat : ndarray Array of complex numbers or Qobj's A 2-dimensional *array* of covariance values, or, if rho=0, a matrix of operators. @@ -129,17 +129,17 @@ def correlation_matrix_quadrature(a1, a2, rho=None, g=np.sqrt(2)): Field operator for mode 1. a2 : Qobj Field operator for mode 2. - rho : Qobj + rho : Qobj, optional Density matrix for which to calculate the covariance matrix. - g : float - Scaling factor for `a = 0.5 * g * (x + iy)`, default `g = sqrt(2)`. - The value of `g` is related to the value of `hbar` in the commutation - relation `[x, y] = i * hbar` via `hbar=2/g ** 2` giving the default - value `hbar=1`. + g : float, default: sqrt(2) + Scaling factor for ``a = 0.5 * g * (x + iy)``, default ``g = sqrt(2)``. + The value of ``g`` is related to the value of ``hbar`` in the + commutation relation ``[x, y] = i * hbar`` via ``hbar=2/g ** 2`` giving + the default value ``hbar=1``. Returns ------- - corr_mat : ndarray + corr_mat : ndarray Array of complex numbers or Qobj's A 2-dimensional *array* of covariance values for the field quadratures, or, if rho=0, a matrix of operators. @@ -163,31 +163,31 @@ def wigner_covariance_matrix(a1=None, a2=None, R=None, rho=None, g=np.sqrt(2)): :math:`R = (q_1, p_1, q_2, p_2)^T` is the vector with quadrature operators for the two modes. - Alternatively, if `R = None`, and if annihilation operators `a1` and `a2` - for the two modes are supplied instead, the quadrature correlation matrix - is constructed from the annihilation operators before then the covariance - matrix is calculated. + Alternatively, if ``R = None``, and if annihilation operators ``a1`` and + ``a2`` for the two modes are supplied instead, the quadrature correlation + matrix is constructed from the annihilation operators before then the + covariance matrix is calculated. Parameters ---------- - a1 : Qobj + a1 : Qobj, optional Field operator for mode 1. - a2 : Qobj + a2 : Qobj, optional Field operator for mode 2. - R : ndarray + R : ndarray, optional The quadrature correlation matrix. - rho : Qobj + rho : Qobj, optional Density matrix for which to calculate the covariance matrix. - g : float - Scaling factor for `a = 0.5 * g * (x + iy)`, default `g = sqrt(2)`. - The value of `g` is related to the value of `hbar` in the commutation - relation `[x, y] = i * hbar` via `hbar=2/g ** 2` giving the default - value `hbar=1`. + g : float, default: sqrt(2) + Scaling factor for ``a = 0.5 * g * (x + iy)``, default ``g = sqrt(2)``. + The value of ``g`` is related to the value of ``hbar`` in the + commutation relation ``[x, y] = i * hbar`` via ``hbar=2/g ** 2`` giving + the default value ``hbar=1``. Returns ------- @@ -229,19 +229,19 @@ def logarithmic_negativity(V, g=np.sqrt(2)): Parameters ---------- - V : *2d array* + V : ndarray The covariance matrix. - g : float - Scaling factor for `a = 0.5 * g * (x + iy)`, default `g = sqrt(2)`. - The value of `g` is related to the value of `hbar` in the commutation - relation `[x, y] = i * hbar` via `hbar=2/g ** 2` giving the default - value `hbar=1`. + g : float, default: sqrt(2) + Scaling factor for ``a = 0.5 * g * (x + iy)``, default ``g = sqrt(2)``. + The value of ``g`` is related to the value of ``hbar`` in the + commutation relation ``[x, y] = i * hbar`` via ``hbar=2/g ** 2`` giving + the default value ``hbar=1``. Returns ------- - N : float + N : float The logarithmic negativity for the two-mode Gaussian state that is described by the the Wigner covariance matrix V. diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index a2a4003a52..b124d50a9c 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -13,6 +13,11 @@ def is_scalar(dims): """ Returns True if a dims specification is effectively a scalar (has dimension 1). + + Parameters + ---------- + dims : list + Dimension's list representation """ return np.prod(flatten(dims)) == 1 diff --git a/qutip/core/expect.py b/qutip/core/expect.py index 65888f8c7e..9051c53f72 100644 --- a/qutip/core/expect.py +++ b/qutip/core/expect.py @@ -9,8 +9,9 @@ def expect(oper, state): """ Calculate the expectation value for operator(s) and state(s). The - expectation of state `k` on operator `A` is defined as `k.dag() @ A @ k`, - and for density matrix `R` on operator `A` it is `trace(A @ R)`. + expectation of state ``k`` on operator ``A`` is defined as + ``k.dag() @ A @ k``, and for density matrix ``R`` on operator ``A`` it is + ``trace(A @ R)``. Parameters ---------- @@ -23,7 +24,7 @@ def expect(oper, state): Returns ------- expt : float/complex/array-like - Expectation value. ``real`` if `oper` is Hermitian, ``complex`` + Expectation value. ``real`` if ``oper`` is Hermitian, ``complex`` otherwise. A (nested) array of expectaction values of state or operator are arrays. @@ -86,7 +87,7 @@ def variance(oper, state): Operator for expectation value. state : qobj/list - A single or `list` of quantum states or density matrices.. + A single or ``list`` of quantum states or density matrices.. Returns ------- diff --git a/qutip/core/metrics.py b/qutip/core/metrics.py index 45ed022b65..3c78589495 100644 --- a/qutip/core/metrics.py +++ b/qutip/core/metrics.py @@ -157,7 +157,7 @@ def process_fidelity(oper, target=None): oper : :class:`.Qobj`/list A unitary operator, or a superoperator in supermatrix, Choi or chi-matrix form, or a list of Kraus operators - target : :class:`.Qobj`/list + target : :class:`.Qobj`/list, optional A unitary operator, or a superoperator in supermatrix, Choi or chi-matrix form, or a list of Kraus operators @@ -171,8 +171,8 @@ def process_fidelity(oper, target=None): Since Qutip 5.0, this function computes the process fidelity as defined for example in: A. Gilchrist, N.K. Langford, M.A. Nielsen, Phys. Rev. A 71, 062310 (2005). Previously, it computed a function - that is now implemented in - :func:`control.fidcomp.FidCompUnitary.get_fidelity`. + that is now implemented as ``get_fidelity`` in qutip-qtrl. + The definition of state fidelity that the process fidelity is based on is the one from R. Jozsa, Journal of Modern Optics, 41:12, 2315 (1994). It is the square of the one implemented in @@ -260,9 +260,9 @@ def tracedist(A, B, sparse=False, tol=0): Density matrix or state vector. B : qobj Density matrix or state vector with same dimensions as A. - tol : float - Tolerance used by sparse eigensolver, if used. (0=Machine precision) - sparse : {False, True} + tol : float, default: 0 + Tolerance used by sparse eigensolver, if used. (0 = Machine precision) + sparse : bool, default: False Use sparse eigensolver. Returns @@ -379,7 +379,8 @@ def hellinger_dist(A, B, sparse=False, tol=0): Calculates the quantum Hellinger distance between two density matrices. Formula: - hellinger_dist(A, B) = sqrt(2-2*Tr(sqrt(A)*sqrt(B))) + + ``hellinger_dist(A, B) = sqrt(2 - 2 * tr(sqrt(A) * sqrt(B)))`` See: D. Spehner, F. Illuminati, M. Orszag, and W. Roga, "Geometric measures of quantum correlations with Bures and Hellinger distances" @@ -391,9 +392,9 @@ def hellinger_dist(A, B, sparse=False, tol=0): Density matrix or state vector. B : :class:`.Qobj` Density matrix or state vector with same dimensions as A. - tol : float - Tolerance used by sparse eigensolver, if used. (0=Machine precision) - sparse : {False, True} + tol : float, default: 0 + Tolerance used by sparse eigensolver, if used. (0 = Machine precision) + sparse : bool, default: False Use sparse eigensolver. Returns @@ -403,9 +404,10 @@ def hellinger_dist(A, B, sparse=False, tol=0): Examples -------- - >>> x=fock_dm(5,3) - >>> y=coherent_dm(5,1) - >>> np.testing.assert_almost_equal(hellinger_dist(x,y), 1.3725145002591095) + >>> x = fock_dm(5,3) + >>> y = coherent_dm(5,1) + >>> np.allclose(hellinger_dist(x, y), 1.3725145002591095) + True """ if A.isket or A.isbra: sqrtmA = ket2dm(A) @@ -441,15 +443,15 @@ def dnorm(A, B=None, solver="CVXOPT", verbose=False, force_solve=False, Quantum map to take the diamond norm of. B : Qobj or None If provided, the diamond norm of :math:`A - B` is taken instead. - solver : str - Solver to use with CVXPY. One of "CVXOPT" (default) or "SCS". The - latter tends to be significantly faster, but somewhat less accurate. - verbose : bool + solver : str {"CVXOPT", "SCS"}, default: "CVXOPT" + Solver to use with CVXPY. "SCS" tends to be significantly faster, but + somewhat less accurate. + verbose : bool, default: False If True, prints additional information about the solution. - force_solve : bool + force_solve : bool, default: False If True, forces dnorm to solve the associated SDP, even if a special case is known for the argument. - sparse : bool + sparse : bool, default: True Whether to use sparse matrices in the convex optimisation problem. Default True. diff --git a/qutip/core/superop_reps.py b/qutip/core/superop_reps.py index c48f05fd1a..e09977e96c 100644 --- a/qutip/core/superop_reps.py +++ b/qutip/core/superop_reps.py @@ -142,7 +142,12 @@ def _choi_to_kraus(q_oper, tol=1e-9): def kraus_to_choi(kraus_list): """ Take a list of Kraus operators and returns the Choi matrix for the channel - represented by the Kraus operators in `kraus_list` + represented by the Kraus operators in `kraus_list`. + + Parameters + ---------- + kraus_list : list of Qobj + Kraus super operator. """ kraus_mat_list = [k.full() for k in kraus_list] op_rng = list(range(kraus_mat_list[0].shape[1])) @@ -162,6 +167,11 @@ def kraus_to_choi(kraus_list): def kraus_to_super(kraus_list): """ Convert a list of Kraus operators to a superoperator. + + Parameters + ---------- + kraus_list : list of Qobj + Kraus super operator. """ return to_super(kraus_to_choi(kraus_list)) @@ -351,8 +361,9 @@ def to_choi(q_oper): Raises ------ - TypeError: if the given quantum object is not a map, or cannot be converted - to Choi representation. + TypeError: + If the given quantum object is not a map, or cannot be converted to + Choi representation. """ if q_oper.type == 'super': if q_oper.superrep == 'choi': @@ -393,7 +404,8 @@ def to_chi(q_oper): Raises ------ - TypeError: if the given quantum object is not a map, or cannot be converted + TypeError: + If the given quantum object is not a map, or cannot be converted to Chi representation. """ if q_oper.type == 'super': @@ -470,7 +482,7 @@ def to_kraus(q_oper, tol=1e-9): ``q_oper`` is ``type="oper"``, then it is taken to act by conjugation, such that ``to_kraus(A) == to_kraus(sprepost(A, A.dag())) == [A]``. - tol : Float + tol : Float, default: 1e-9 Optional threshold parameter for eigenvalues/Kraus ops to be discarded. The default is to=1e-9. @@ -500,19 +512,22 @@ def to_kraus(q_oper, tol=1e-9): def to_stinespring(q_oper, threshold=1e-10): r""" - Converts a Qobj representing a quantum map $\Lambda$ to a pair of partial - isometries $A$ and $B$ such that $\Lambda(X) = \Tr_2(A X B^\dagger)$ for - all inputs $X$, where the partial trace is taken over a a new index on the - output dimensions of $A$ and $B$. + Converts a Qobj representing a quantum map :math:`\Lambda` to a pair of + partial isometries ``A`` and ``B`` such that + :math:`\Lambda(X) = \Tr_2(A X B^\dagger)` for all inputs ``X``, where the + partial trace is taken over a a new index on the output dimensions of + ``A`` and ``B``. - For completely positive inputs, $A$ will always equal $B$ up to precision - errors. + For completely positive inputs, ``A`` will always equal ``B`` up to + precision errors. Parameters ---------- q_oper : Qobj Superoperator to be converted to a Stinespring pair. + threshold : float, default: 1e-10 + Returns ------- A, B : Qobj diff --git a/qutip/core/superoperator.py b/qutip/core/superoperator.py index 3ed59fc623..76c47faddb 100644 --- a/qutip/core/superoperator.py +++ b/qutip/core/superoperator.py @@ -34,23 +34,25 @@ def liouvillian(H=None, c_ops=None, data_only=False, chi=None): Parameters ---------- - H : Qobj or QobjEvo (optional) + H : Qobj or QobjEvo, optional System Hamiltonian or Hamiltonian component of a Liouvillian. Considered `0` if not given. - c_ops : array_like of Qobj or QobjEvo + c_ops : array_like of Qobj or QobjEvo, optional A ``list`` or ``array`` of collapse operators. - data_only : bool [False] + data_only : bool, default: False Return the data object instead of a Qobj - chi : array_like of float [None] - In some systems it is possible to determine the statistical moments (mean, variance, etc) of the - probability distributions of occupation of various states by numerically evaluating the derivatives - of the steady state occupation probability as a function of artificial phase parameters ``chi`` - which are included in the :func:`lindblad_dissipator` for each collapse operator. See - the documentation of :func:`lindblad_dissipator` for references and further details. - This parameter is deprecated and may be removed in QuTiP 5. + chi : array_like of float, optional + In some systems it is possible to determine the statistical moments + (mean, variance, etc) of the probability distributions of occupation of + various states by numerically evaluating the derivatives of the steady + state occupation probability as a function of artificial phase + parameters ``chi`` which are included in the + :func:`lindblad_dissipator` for each collapse operator. See the + documentation of :func:`lindblad_dissipator` for references and further + details. This parameter is deprecated and may be removed in QuTiP 5. Returns ------- @@ -135,20 +137,25 @@ def lindblad_dissipator(a, b=None, data_only=False, chi=None): a : Qobj or QobjEvo Left part of collapse operator. - b : Qobj or QobjEvo (optional) + b : Qobj or QobjEvo, optional Right part of collapse operator. If not specified, b defaults to a. - chi : float [None] - In some systems it is possible to determine the statistical moments (mean, variance, etc) of the - probability distribution of the occupation numbers of states by numerically evaluating the derivatives - of the steady state occupation probability as a function of an artificial phase parameter ``chi`` - which multiplies the ``a \\rho a^dagger`` term of the dissipator by ``e ^ (i * chi)``. The factor ``e ^ (i * chi)`` - is introduced via the generating function of the statistical moments. For examples of the technique, - see `Full counting statistics of nano-electromechanical systems `_ - and `Photon-mediated electron transport in hybrid circuit-QED `_. - This parameter is deprecated and may be removed in QuTiP 5. - - data_only : bool [False] + chi : float, optional + In some systems it is possible to determine the statistical moments + (mean, variance, etc) of the probability distribution of the occupation + numbers of states by numerically evaluating the derivatives of the + steady state occupation probability as a function of an artificial + phase parameter ``chi`` which multiplies the ``a \\rho a^dagger`` term + of the dissipator by ``e ^ (i * chi)``. The factor ``e ^ (i * chi)`` is + introduced via the generating function of the statistical moments. For + examples of the technique, see `Full counting statistics of + nano-electromechanical systems + `_ and `Photon-mediated + electron transport in hybrid circuit-QED + `_. This parameter is deprecated and + may be removed in QuTiP 5. + + data_only : bool, default: False Return the data object instead of a Qobj Returns diff --git a/qutip/core/tensor.py b/qutip/core/tensor.py index 6adacc6582..6bffb3abca 100644 --- a/qutip/core/tensor.py +++ b/qutip/core/tensor.py @@ -291,6 +291,8 @@ def tensor_contract(qobj, *pairs): Parameters ---------- + qobj: Qobj + Operator to contract subspaces on. pairs : tuple One or more tuples ``(i, j)`` indicating that the diff --git a/qutip/entropy.py b/qutip/entropy.py index d8dddeea87..6f3f5cc7a6 100644 --- a/qutip/entropy.py +++ b/qutip/entropy.py @@ -17,9 +17,9 @@ def entropy_vn(rho, base=e, sparse=False): ---------- rho : qobj Density matrix. - base : {e,2} + base : {e, 2}, default: e Base of logarithm. - sparse : {False,True} + sparse : bool, default: False Use sparse eigensolver. Returns @@ -159,9 +159,9 @@ def entropy_mutual(rho, selA, selB, base=e, sparse=False): `int` or `list` of first selected density matrix components. selB : int/list `int` or `list` of second selected density matrix components. - base : {e,2} + base : {e, 2}, default: e Base of logarithm. - sparse : {False,True} + sparse : bool, default: False Use sparse eigensolver. Returns @@ -201,12 +201,12 @@ def entropy_relative(rho, sigma, base=e, sparse=False, tol=1e-12): sigma : :class:`.Qobj` Second density matrix (or ket which will be converted to a density matrix). - base : {e,2} + base : {e, 2}, default: e Base of logarithm. Defaults to e. - sparse : bool + sparse : bool, default: False Flag to use sparse solver when determining the eigenvectors of the density matrices. Defaults to False. - tol : float + tol : float, default: 1e-12 Tolerance to use to detect 0 eigenvalues or dot producted between eigenvectors. Defaults to 1e-12. @@ -295,9 +295,9 @@ def entropy_conditional(rho, selB, base=e, sparse=False): Density matrix of composite object selB : int/list Selected components for density matrix B - base : {e,2} + base : {e, 2}, default: e Base of logarithm. - sparse : {False,True} + sparse : bool, default: False Use sparse eigensolver. Returns diff --git a/qutip/partial_transpose.py b/qutip/partial_transpose.py index 47b7f09bcd..919bfa9e63 100644 --- a/qutip/partial_transpose.py +++ b/qutip/partial_transpose.py @@ -29,16 +29,14 @@ def partial_transpose(rho, mask, method='dense'): mask : *list* / *array* A mask that selects which subsystems should be transposed. - method : str - choice of method, `dense` or `sparse`. The default method - is `dense`. The `sparse` implementation can be faster for + method : str {"dense", "sparse"}, default: "dense" + Choice of method. The "sparse" implementation can be faster for large and sparse systems (hundreds of quantum states). Returns ------- rho_pr: :class:`.Qobj` - A density matrix with the selected subsystems transposed. """ From 475ff6482064581f1dd57a832c40bd9888dfc487 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 14 Nov 2023 17:30:50 -0500 Subject: [PATCH 078/247] function api review up to visualization --- qutip/core/coefficient.py | 26 +++-- qutip/piqs/piqs.py | 79 ++++++------- qutip/solver/brmesolve.py | 88 +++++++-------- qutip/solver/correlation.py | 108 +++++++++--------- qutip/solver/floquet.py | 110 +++++++++--------- qutip/solver/heom/bofin_solvers.py | 68 +++++------ qutip/solver/krylovsolve.py | 79 ++++++------- qutip/solver/mcsolve.py | 118 +++++++++---------- qutip/solver/mesolve.py | 94 ++++++++-------- qutip/solver/nm_mcsolve.py | 122 ++++++++++---------- qutip/solver/scattering.py | 16 +-- qutip/solver/sesolve.py | 85 +++++++------- qutip/solver/spectrum.py | 14 +-- qutip/solver/steadystate.py | 36 +++--- qutip/solver/stochastic.py | 175 +++++++++++++++-------------- qutip/tests/piqs/test_piqs.py | 2 +- 16 files changed, 620 insertions(+), 600 deletions(-) diff --git a/qutip/core/coefficient.py b/qutip/core/coefficient.py index 2de41b5d39..bc1014f99c 100644 --- a/qutip/core/coefficient.py +++ b/qutip/core/coefficient.py @@ -75,20 +75,21 @@ def coefficient(base, *, tlist=None, args={}, args_ctypes={}, may be overriden here by specifying either ``function_style="pythonic"`` or ``function_style="dict"``. - *Examples* - - pythonic style function signature + *Examples*: - def f1_t(t, w): - return np.exp(-1j * t * w) + - pythonic style function signature:: - coeff1 = coefficient(f1_t, args={"w": 1.}) + def f1_t(t, w): + return np.exp(-1j * t * w) - - dict style function signature + coeff1 = coefficient(f1_t, args={"w": 1.}) - def f2_t(t, args): - return np.exp(-1j * t * args["w"]) + - dict style function signature:: - coeff2 = coefficient(f2_t, args={"w": 1.}) + def f2_t(t, args): + return np.exp(-1j * t * args["w"]) + + coeff2 = coefficient(f2_t, args={"w": 1.}) For string based coeffients, the string must be a compilable python code resulting in a complex. The following symbols are defined: @@ -101,7 +102,7 @@ def f2_t(t, args): scipy.special as spe (python interface) and cython_special (scipy cython interface) - *Examples* + *Examples*:: coeff = coefficient('exp(-1j*w1*t)', args={"w1":1.}) @@ -119,7 +120,8 @@ def f2_t(t, args): interpolation. When ``order = 0``, the interpolation is step function that evaluates to the most recent value. - *Examples* + *Examples*:: + tlist = np.logspace(-5,0,100) H = QobjEvo(np.exp(-1j*tlist), tlist=tlist) @@ -143,7 +145,7 @@ def f2_t(t, args): tlist : iterable, optional Times for each element of an array based coefficient. - function_style : str, ["dict", "pythonic", None] + function_style : str {"dict", "pythonic", None}, optional Function signature of function based coefficients. args_ctypes : dict, optional diff --git a/qutip/piqs/piqs.py b/qutip/piqs/piqs.py index 7aaefe6a8f..73b3995581 100644 --- a/qutip/piqs/piqs.py +++ b/qutip/piqs/piqs.py @@ -156,7 +156,8 @@ def isdiagonal(mat): # nonlinear functions of the density matrix def dicke_blocks(rho): - """Create the list of blocks for block-diagonal density matrix in the Dicke basis. + """Create the list of blocks for block-diagonal density matrix in the Dicke + basis. Parameters ---------- @@ -167,7 +168,7 @@ def dicke_blocks(rho): Returns ------- - square_blocks: list of np.array + square_blocks: list of np.ndarray Give back the blocks list. """ @@ -281,7 +282,7 @@ def entropy_vn_dicke(rho): Parameters ---------- rho : :class:`.Qobj` - A 2D block-diagonal matrix of ones with dimension (nds,nds), + A 2D block-diagonal matrix of ones with dimension (nds, nds), where nds is the number of Dicke states for N two-level systems. @@ -598,8 +599,8 @@ def coefficient_matrix(self): def energy_degeneracy(N, m): """Calculate the number of Dicke states with same energy. - The use of the `Decimals` class allows to explore N > 1000, - unlike the built-in function `scipy.special.binom` + The use of the ``Decimals`` class allows to explore N > 1000, + unlike the built-in function ``scipy.special.binom``. Parameters ---------- @@ -867,14 +868,13 @@ def jspin(N, op=None, basis="dicke"): N: int Number of two-level systems. - op: str + op: str {'x', 'y', 'z', '+', '-'}, optional The operator to return 'x','y','z','+','-'. If no operator given, then output is the list of operators for ['x','y','z']. - basis: str - The basis of the operators - "dicke" or "uncoupled" - default: "dicke". + basis: str {"dicke", "uncoupled"}, default: "dicke" + The basis of the operators. Returns ------- @@ -956,34 +956,29 @@ def collapse_uncoupled( N: int The number of two-level systems. - emission: float + emission: float, default: 0.0 Incoherent emission coefficient (also nonradiative emission). - default: 0.0 - dephasing: float + + dephasing: float, default: 0.0 Local dephasing coefficient. - default: 0.0 - pumping: float + pumping: float, default: 0.0 Incoherent pumping coefficient. - default: 0.0 - collective_emission: float + collective_emission: float, default: 0.0 Collective (superradiant) emmission coefficient. - default: 0.0 - collective_pumping: float + collective_pumping: float, default: 0.0 Collective pumping coefficient. - default: 0.0 - collective_dephasing: float + collective_dephasing: float, default: 0.0 Collective dephasing coefficient. - default: 0.0 Returns ------- c_ops: list - The list of collapse operators as `qutip.Qobj` for the system. + The list of collapse operators as :obj:`.Qobj` for the system. """ N = int(N) @@ -1028,7 +1023,7 @@ def collapse_uncoupled( # State definitions in the Dicke basis with an option for basis transformation -def dicke_basis(N, jmm1=None): +def dicke_basis(N, jmm1): r""" Initialize the density matrix of a Dicke state for several (j, m, m1). @@ -1053,7 +1048,7 @@ def dicke_basis(N, jmm1=None): rho: :class:`.Qobj` The density matrix in the Dicke basis. """ - if jmm1 is None: + if not isinstance(jmm1, dict): msg = "Please specify the jmm1 values as a dictionary" msg += "or use the `excited(N)` function to create an" msg += "excited state where jmm1 = {(N/2, N/2, N/2): 1}" @@ -1257,7 +1252,7 @@ def excited(N, basis="dicke"): Generate the density matrix for the excited state. This state is given by (N/2, N/2) in the default Dicke basis. If the - argument `basis` is "uncoupled" then it generates the state in a + argument ``basis`` is "uncoupled" then it generates the state in a 2**N dim Hilbert space. Parameters @@ -1265,8 +1260,8 @@ def excited(N, basis="dicke"): N: int The number of two-level systems. - basis: str - The basis to use. Either "dicke" or "uncoupled". + basis: str, {"dicke", "uncoupled"}, default: "dicke" + The basis to use. Returns ------- @@ -1294,8 +1289,8 @@ def superradiant(N, basis="dicke"): N: int The number of two-level systems. - basis: str - The basis to use. Either "dicke" or "uncoupled". + basis: str, {"dicke", "uncoupled"}, default: "dicke" + The basis to use. Returns ------- @@ -1338,15 +1333,15 @@ def css( N: int The number of two-level systems. - x, y: float + x, y: float, default: sqrt(1/2) The coefficients of the CSS state. - basis: str - The basis to use. Either "dicke" or "uncoupled". + basis: str {"dicke", "uncoupled"}, default: "dicke" + The basis to use. - coordinates: str - Either "cartesian" or "polar". If polar then the coefficients - are constructed as sin(x/2), cos(x/2)e^(iy). + coordinates: str {"cartesian", "polar"}, default: "cartesian" + If polar then the coefficients are constructed as + :math:`sin(x/2), cos(x/2)e^(iy)``. Returns ------- @@ -1393,7 +1388,7 @@ def ghz(N, basis="dicke"): """ Generate the density matrix of the GHZ state. - If the argument `basis` is "uncoupled" then it generates the state + If the argument ``basis`` is "uncoupled" then it generates the state in a :math:`2^N`-dimensional Hilbert space. Parameters @@ -1401,8 +1396,8 @@ def ghz(N, basis="dicke"): N: int The number of two-level systems. - basis: str - The basis to use. Either "dicke" or "uncoupled". + basis: str, {"dicke", "uncoupled"}, default: "dicke" + The basis to use. Returns ------- @@ -1425,7 +1420,7 @@ def ground(N, basis="dicke"): Generate the density matrix of the ground state. This state is given by (N/2, -N/2) in the Dicke basis. If the argument - `basis` is "uncoupled" then it generates the state in a + ``basis`` is "uncoupled" then it generates the state in a :math:`2^N`-dimensional Hilbert space. Parameters @@ -1433,8 +1428,8 @@ def ground(N, basis="dicke"): N: int The number of two-level systems. - basis: str - The basis to use. Either "dicke" or "uncoupled" + basis: str, {"dicke", "uncoupled"}, default: "dicke" + The basis to use. Returns ------- @@ -1483,7 +1478,7 @@ def block_matrix(N, elements="ones"): ---------- N : int Number of two-level systems. - elements : str {'ones' (default),'degeneracy'} + elements : str {'ones', 'degeneracy'}, default: 'ones' Returns ------- diff --git a/qutip/solver/brmesolve.py b/qutip/solver/brmesolve.py index c1f2cb5ec0..90efda7eca 100644 --- a/qutip/solver/brmesolve.py +++ b/qutip/solver/brmesolve.py @@ -16,8 +16,8 @@ from .options import _SolverOptions -def brmesolve(H, psi0, tlist, a_ops=[], e_ops=[], c_ops=[], - args={}, sec_cutoff=0.1, options=None, **kwargs): +def brmesolve(H, psi0, tlist, a_ops=(), e_ops=(), c_ops=(), + args=None, sec_cutoff=0.1, options=None, **kwargs): """ Solves for the dynamics of a system using the Bloch-Redfield master equation, given an input Hamiltonian, Hermitian bath-coupling terms and @@ -41,7 +41,7 @@ def brmesolve(H, psi0, tlist, a_ops=[], e_ops=[], c_ops=[], Nested list of system operators that couple to the environment, and the corresponding bath spectra. - a_op : :obj:`qutip.Qobj`, :obj:`qutip.QobjEvo` + a_op : :obj:`.Qobj`, :obj:`.QobjEvo` The operator coupling to the environment. Must be hermitian. spectra : :obj:`.Coefficient`, str, func @@ -73,76 +73,70 @@ def brmesolve(H, psi0, tlist, a_ops=[], e_ops=[], c_ops=[], the operator: :obj:`.Qobj` vs :obj:`.QobjEvo` instead of the type of the spectra. - e_ops : list of :obj:`.Qobj` / callback function + e_ops : list of :obj:`.Qobj` / callback function, optional Single operator or list of operators for which to evaluate expectation values or callable or list of callable. Callable signature must be, `f(t: float, state: Qobj)`. See :func:`expect` for more detail of operator expectation - c_ops : list of (:obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format) + c_ops : list of (:obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format), optional List of collapse operators. - args : dict + args : dict, optional Dictionary of parameters for time-dependent Hamiltonians and collapse operators. The key ``w`` is reserved for the spectra function. - sec_cutoff : float {0.1} + sec_cutoff : float, default: 0.1 Cutoff for secular approximation. Use ``-1`` if secular approximation is not used when evaluating bath-coupling terms. - options : None / dict + options : dict, optional Dictionary of options for the solver. - - store_final_state : bool - Whether or not to store the final state of the evolution in the - result class. - - store_states : bool, None - Whether or not to store the state vectors or density matrices. - On `None` the states will be saved if no expectation operators are - given. - - normalize_output : bool - Normalize output state to hide ODE numerical errors. - - progress_bar : str {'text', 'enhanced', 'tqdm', ''} - How to present the solver progress. - 'tqdm' uses the python module of the same name and raise an error - if not installed. Empty string or False will disable the bar. - - progress_kwargs : dict - kwargs to pass to the progress_bar. Qutip's bars use `chunk_size`. - - tensor_type : str ['sparse', 'dense', 'data'] - Which data type to use when computing the brtensor. - With a cutoff 'sparse' is usually the most efficient. - - sparse_eigensolver : bool {False} - Whether to use the sparse eigensolver - - method : str ["adams", "bdf", "lsoda", "dop853", "vern9", etc.] - Which differential equation integration method to use. - - atol, rtol : float - Absolute and relative tolerance of the ODE integrator. - - nsteps : - Maximum number of (internally defined) steps allowed in one ``tlist`` - step. - - max_step : float, 0 - Maximum lenght of one internal step. When using pulses, it should be - less than half the width of the thinnest pulse. + - | store_final_state : bool + | Whether or not to store the final state of the evolution in the + result class. + - | store_states : bool, None + | Whether or not to store the state vectors or density matrices. + On `None` the states will be saved if no expectation operators are + given. + - | normalize_output : bool + | Normalize output state to hide ODE numerical errors. + - | progress_bar : str {'text', 'enhanced', 'tqdm', ''} + | How to present the solver progress. + 'tqdm' uses the python module of the same name and raise an error + if not installed. Empty string or False will disable the bar. + - | progress_kwargs : dict + | kwargs to pass to the progress_bar. Qutip's bars use `chunk_size`. + - | tensor_type : str ['sparse', 'dense', 'data'] + | Which data type to use when computing the brtensor. + With a cutoff 'sparse' is usually the most efficient. + - | sparse_eigensolver : bool {False} + Whether to use the sparse eigensolver + - | method : str ["adams", "bdf", "lsoda", "dop853", "vern9", etc.] + Which differential equation integration method to use. + - | atol, rtol : float + | Absolute and relative tolerance of the ODE integrator. + - | nsteps : int + | Maximum number of (internally defined) steps allowed in one + ``tlist`` step. + - | max_step : float, 0 + | Maximum lenght of one internal step. When using pulses, it should + be less than half the width of the thinnest pulse. Other options could be supported depending on the integration method, see `Integrator <./classes.html#classes-ode>`_. Returns ------- - result: :obj:`qutip.solver.Result` + result: :obj:`.Result` An instance of the class :obj:`qutip.solver.Result`, which contains either an array of expectation values, for operators given in e_ops, - or a list of states for the times specified by `tlist`. - - .. note: - The option ``operator_data_type`` is used to determine in which format - the bloch redfield tensor is computed. Use 'csr' for sparse and 'dense' - for dense array. With 'data', it will try to use the same data type as - the ``a_ops``, but it is usually less efficient than manually choosing - it. + or a list of states for the times specified by ``tlist``. """ options = _solver_deprecation(kwargs, options, "br") + args = args or {} H = QobjEvo(H, args=args, tlist=tlist) c_ops = c_ops if c_ops is not None else [] diff --git a/qutip/solver/correlation.py b/qutip/solver/correlation.py index b2916e8ebb..312211aba8 100644 --- a/qutip/solver/correlation.py +++ b/qutip/solver/correlation.py @@ -27,8 +27,8 @@ def correlation_2op_1t(H, state0, taulist, c_ops, a_op, b_op, - solver="me", reverse=False, args={}, - options={}): + solver="me", reverse=False, args=None, + options=None): r""" Calculate the two-operator one-time correlation function: :math:`\left` @@ -54,19 +54,20 @@ def correlation_2op_1t(H, state0, taulist, c_ops, a_op, b_op, Operator A. b_op : :obj:`.Qobj`, :obj:`.QobjEvo` Operator B. - reverse : bool {False} - If `True`, calculate :math:`\left` instead of + reverse : bool, default: False + If ``True``, calculate :math:`\left` instead of :math:`\left`. - solver : str {'me', 'es'} - Choice of solver, `me` for master-equation, and `es` for exponential - series. `es` is equivalent to `me` with ``options={"method": "diag"}``. + solver : str {'me', 'es'}, default: 'me' + Choice of solver, ``me`` for master-equation, and ``es`` for + exponential series. ``es`` is equivalent to `me` with + ``options={"method": "diag"}``. options : dict, optional - Options for the solver. Only used with `me` solver. + Options for the solver. Returns ------- corr_vec : ndarray - An array of correlation values for the times specified by `taulist`. + An array of correlation values for the times specified by ``taulist``. See Also -------- @@ -91,8 +92,8 @@ def correlation_2op_1t(H, state0, taulist, c_ops, a_op, b_op, def correlation_2op_2t(H, state0, tlist, taulist, c_ops, a_op, b_op, - solver="me", reverse=False, args={}, - options={}): + solver="me", reverse=False, args=None, + options=None): r""" Calculate the two-operator two-time correlation function: :math:`\left` @@ -122,20 +123,21 @@ def correlation_2op_2t(H, state0, tlist, taulist, c_ops, a_op, b_op, Operator A. b_op : :obj:`.Qobj`, :obj:`.QobjEvo` Operator B. - reverse : bool {False} - If `True`, calculate :math:`\left` instead of + reverse : bool, default: False + If ``True``, calculate :math:`\left` instead of :math:`\left`. - solver : str {'me', 'es'} - Choice of solver, `me` for master-equation, and `es` for exponential - series. `es` is equivalent to `me` with ``options={"method": "diag"}``. + solver : str {'me', 'es'}, default: 'me' + Choice of solver, ``me`` for master-equation, and ``es`` for + exponential series. ``es`` is equivalent to `me` with + ``options={"method": "diag"}``. options : dict, optional - Options for the solver. Only used with `me` solver. + Options for the solver. Returns ------- corr_mat : ndarray An 2-dimensional array (matrix) of correlation values for the times - specified by `tlist` (first index) and `taulist` (second index). + specified by ``tlist`` (first index) and ``taulist`` (second index). See Also -------- @@ -162,8 +164,7 @@ def correlation_2op_2t(H, state0, tlist, taulist, c_ops, a_op, b_op, def correlation_3op_1t(H, state0, taulist, c_ops, a_op, b_op, c_op, - solver="me", args={}, - options={}): + solver="me", args=None, options=None): r""" Calculate the three-operator two-time correlation function: :math:`\left` along one time axis using the @@ -176,7 +177,7 @@ def correlation_3op_1t(H, state0, taulist, c_ops, a_op, b_op, c_op, Parameters ---------- H : :obj:`.Qobj`, :obj:`.QobjEvo` - System Hamiltonian, may be time-dependent for solver choice of `me`. + System Hamiltonian, may be time-dependent for solver choice of ``me``. state0 : :obj:`.Qobj` Initial state density matrix :math:`\rho(t_0)` or state vector :math:`\psi(t_0)`. If 'state0' is 'None', then the steady state will @@ -193,16 +194,17 @@ def correlation_3op_1t(H, state0, taulist, c_ops, a_op, b_op, c_op, Operator B. c_op : :obj:`.Qobj`, :obj:`.QobjEvo` Operator C. - solver : str {'me', 'es'} - Choice of solver, `me` for master-equation, and `es` for exponential - series. `es` is equivalent to `me` with ``options={"method": "diag"}``. + solver : str {'me', 'es'}, default: 'me' + Choice of solver, ``me`` for master-equation, and ``es`` for + exponential series. ``es`` is equivalent to `me` with + ``options={"method": "diag"}``. options : dict, optional - Options for the solver. Only used with `me` solver. + Options for the solver. Returns ------- corr_vec : array - An array of correlation values for the times specified by `taulist`. + An array of correlation values for the times specified by ``taulist``. See Also -------- @@ -221,8 +223,7 @@ def correlation_3op_1t(H, state0, taulist, c_ops, a_op, b_op, c_op, def correlation_3op_2t(H, state0, tlist, taulist, c_ops, a_op, b_op, c_op, - solver="me", args={}, - options={}): + solver="me", args=None, options=None): r""" Calculate the three-operator two-time correlation function: :math:`\left` along two time axes using the @@ -235,7 +236,7 @@ def correlation_3op_2t(H, state0, tlist, taulist, c_ops, a_op, b_op, c_op, Parameters ---------- H : :obj:`.Qobj`, :obj:`.QobjEvo` - System Hamiltonian, may be time-dependent for solver choice of `me`. + System Hamiltonian, may be time-dependent for solver choice of ``me``. state0 : :obj:`.Qobj` Initial state density matrix :math:`\rho(t_0)` or state vector :math:`\psi(t_0)`. If 'state0' is 'None', then the steady state will @@ -257,17 +258,18 @@ def correlation_3op_2t(H, state0, tlist, taulist, c_ops, a_op, b_op, c_op, Operator B. c_op : :obj:`.Qobj`, :obj:`.QobjEvo` Operator C. - solver : str {'me', 'es'} - Choice of solver, `me` for master-equation, and `es` for exponential - series. `es` is equivalent to `me` with ``options={"method": "diag"}``. + solver : str {'me', 'es'}, default: 'me' + Choice of solver, ``me`` for master-equation, and ``es`` for + exponential series. ``es`` is equivalent to `me` with + ``options={"method": "diag"}``. options : dict, optional - Options for the solver. Only used with `me` solver. + Options for the solver. Only used with ``me`` solver. Returns ------- corr_mat : array An 2-dimensional array (matrix) of correlation values for the times - specified by `tlist` (first index) and `taulist` (second index). + specified by ``tlist`` (first index) and ``taulist`` (second index). See Also -------- @@ -293,7 +295,7 @@ def correlation_3op_2t(H, state0, tlist, taulist, c_ops, a_op, b_op, c_op, # high level correlation def coherence_function_g1( - H, state0, taulist, c_ops, a_op, solver="me", args={}, options={} + H, state0, taulist, c_ops, a_op, solver="me", args=None, options=None ): r""" Calculate the normalized first-order quantum coherence function: @@ -311,7 +313,7 @@ def coherence_function_g1( Parameters ---------- H : :obj:`.Qobj`, :obj:`.QobjEvo` - System Hamiltonian, may be time-dependent for solver choice of `me`. + System Hamiltonian, may be time-dependent for solver choice of ``me``. state0 : :obj:`.Qobj` Initial state density matrix :math:`\rho(t_0)` or state vector :math:`\psi(t_0)`. If 'state0' is 'None', then the steady state will @@ -324,11 +326,14 @@ def coherence_function_g1( List of collapse operators a_op : :obj:`.Qobj`, :obj:`.QobjEvo` Operator A. - solver : str {'me', 'es'} - Choice of solver, `me` for master-equation, and `es` for exponential - series. `es` is equivalent to `me` with ``options={"method": "diag"}``. + solver : str {'me', 'es'}, default: 'me' + Choice of solver, ``me`` for master-equation, and ``es`` for + exponential series. ``es`` is equivalent to `me` with + ``options={"method": "diag"}``. + args : dict, optional + dictionary of parameters for time-dependent Hamiltonians options : dict, optional - Options for the solver. Only used with `me` solver. + Options for the solver. Returns ------- @@ -353,7 +358,7 @@ def coherence_function_g1( def coherence_function_g2(H, state0, taulist, c_ops, a_op, solver="me", - args={}, options={}): + args=None, options=None): r""" Calculate the normalized second-order quantum coherence function: @@ -370,7 +375,7 @@ def coherence_function_g2(H, state0, taulist, c_ops, a_op, solver="me", Parameters ---------- H : :obj:`.Qobj`, :obj:`.QobjEvo` - System Hamiltonian, may be time-dependent for solver choice of `me`. + System Hamiltonian, may be time-dependent for solver choice of ``me``. state0 : :obj:`.Qobj` Initial state density matrix :math:`\rho(t_0)` or state vector :math:`\psi(t_0)`. If 'state0' is 'None', then the steady state will @@ -381,16 +386,17 @@ def coherence_function_g2(H, state0, taulist, c_ops, a_op, solver="me", the element `0`. c_ops : list List of collapse operators, may be time-dependent for solver choice of - `me`. + ``me``. a_op : :obj:`.Qobj` Operator A. - args : dict + args : dict, optional Dictionary of arguments to be passed to solver. - solver : str {'me', 'es'} - Choice of solver, `me` for master-equation, and `es` for exponential - series. `es` is equivalent to `me` with ``options={"method": "diag"}``. + solver : str {'me', 'es'}, default: 'me' + Choice of solver, ``me`` for master-equation, and ``es`` for + exponential series. ``es`` is equivalent to ``me`` with + ``options={"method": "diag"}``. options : dict, optional - Options for the solver. Only used with `me` solver. + Options for the solver. Returns ------- @@ -452,7 +458,7 @@ def correlation_3op(solver, state0, tlist, taulist, A=None, B=None, C=None): taulist : array_like List of times for :math:`\tau`. taulist must be positive and contain the element `0`. - A, B, C: :obj:`.Qobj`, :obj:`.QobjEvo`, optional, default=None + A, B, C : :class:`.Qobj`, :class:`.QobjEvo`, optional, default=None Operators ``A``, ``B``, ``C`` from the equation ```` in the Schrodinger picture. They do not need to be all provided. For exemple, if ``A`` is not provided, ```` is computed. @@ -461,8 +467,8 @@ def correlation_3op(solver, state0, tlist, taulist, A=None, B=None, C=None): ------- corr_mat : array An 2-dimensional array (matrix) of correlation values for the times - specified by `tlist` (first index) and `taulist` (second index). If - `tlist` is `None`, then a 1-dimensional array of correlation values + specified by ``tlist`` (first index) and `taulist` (second index). If + ``tlist`` is ``None``, then a 1-dimensional array of correlation values is returned instead. """ taulist = np.asarray(taulist) diff --git a/qutip/solver/floquet.py b/qutip/solver/floquet.py index 86c0c2d982..999f36672a 100644 --- a/qutip/solver/floquet.py +++ b/qutip/solver/floquet.py @@ -448,11 +448,12 @@ def floquet_tensor(H, c_ops, spectra_cb, T=0, w_th=0.0, kmax=5, nT=100): Parameters ---------- - H : :obj:`.QobjEvo` - Periodic Hamiltonian + H : :obj:`.QobjEvo`, :obj:`.FloquetBasis` + Periodic Hamiltonian a floquet basis system. - T : float - The period of the time-dependence of the hamiltonian. + T : float, optional + The period of the time-dependence of the hamiltonian. Optional if ``H`` + is a ``FloquetBasis`` object. c_ops : list of :class:`.Qobj` list of collapse operators. @@ -461,12 +462,15 @@ def floquet_tensor(H, c_ops, spectra_cb, T=0, w_th=0.0, kmax=5, nT=100): List of callback functions that compute the noise power spectrum as a function of frequency for the collapse operators in `c_ops`. - w_th : float + w_th : float, default: 0.0 The temperature in units of frequency. - kmax : int + kmax : int, default: 5 The truncation of the number of sidebands (default 5). + nT : int, default: 100 + The number of integration steps (for calculating X) within one period. + Returns ------- output : array @@ -520,15 +524,15 @@ def fsesolve(H, psi0, tlist, e_ops=None, T=0.0, args=None, options=None): options : dict, optional Options for the results. - - store_final_state : bool - Whether or not to store the final state of the evolution in the - result class. - - store_states : bool, None - Whether or not to store the state vectors or density matrices. - On `None` the states will be saved if no expectation operators are - given. - - normalize_output : bool - Normalize output state to hide ODE numerical errors. + - | store_final_state : bool + | Whether or not to store the final state of the evolution in the + result class. + - | store_states : bool, None + | Whether or not to store the state vectors or density matrices. + On `None` the states will be saved if no expectation operators are + given. + - | normalize_output : bool + | Normalize output state to hide ODE numerical errors. Returns ------- @@ -588,23 +592,23 @@ def fmmesolve( tlist : *list* / *array* List of times for :math:`t`. - c_ops : list of :class:`.Qobj` + c_ops : list of :class:`.Qobj`, optional List of collapse operators. Time dependent collapse operators are not - supported. + supported. Fall back on :func:`fsesolve` if not provided. - e_ops : list of :class:`.Qobj` / callback function + e_ops : list of :class:`.Qobj` / callback function, optional List of operators for which to evaluate expectation values. The states are reverted to the lab basis before applying the - spectra_cb : list callback functions + spectra_cb : list callback functions, default: ``lambda w: (w > 0)`` List of callback functions that compute the noise power spectrum as a function of frequency for the collapse operators in `c_ops`. - T : float + T : float, default=tlist[-1] The period of the time-dependence of the hamiltonian. The default value - 'None' indicates that the 'tlist' spans a single period of the driving. + ``0`` indicates that the 'tlist' spans a single period of the driving. - w_th : float + w_th : float, default: 0.0 The temperature of the environment in units of frequency. For example, if the Hamiltonian written in units of 2pi GHz, and the temperature is given in K, use the following conversion: @@ -614,40 +618,40 @@ def fmmesolve( kB = 1.38e-23 args['w_th'] = temperature * (kB / h) * 2 * pi * 1e-9 - args : *dictionary* + args : dict, optional Dictionary of parameters for time-dependent Hamiltonian - options : None / dict + options : dict, optional Dictionary of options for the solver. - - store_final_state : bool - Whether or not to store the final state of the evolution in the - result class. - - store_states : bool, None - Whether or not to store the state vectors or density matrices. - On `None` the states will be saved if no expectation operators are - given. - - store_floquet_states : bool - Whether or not to store the density matrices in the floquet basis in - ``result.floquet_states``. - - normalize_output : bool - Normalize output state to hide ODE numerical errors. - - progress_bar : str {'text', 'enhanced', 'tqdm', ''} - How to present the solver progress. - 'tqdm' uses the python module of the same name and raise an error - if not installed. Empty string or False will disable the bar. - - progress_kwargs : dict - kwargs to pass to the progress_bar. Qutip's bars use `chunk_size`. - - method : str ["adams", "bdf", "lsoda", "dop853", "vern9", etc.] - Which differential equation integration method to use. - - atol, rtol : float - Absolute and relative tolerance of the ODE integrator. - - nsteps : - Maximum number of (internally defined) steps allowed in one ``tlist`` - step. - - max_step : float, 0 - Maximum lenght of one internal step. When using pulses, it should be - less than half the width of the thinnest pulse. + - | store_final_state : bool + | Whether or not to store the final state of the evolution in the + result class. + - | store_states : bool, None + | Whether or not to store the state vectors or density matrices. + On `None` the states will be saved if no expectation operators are + given. + - | store_floquet_states : bool + | Whether or not to store the density matrices in the floquet basis + in ``result.floquet_states``. + - | normalize_output : bool + | Normalize output state to hide ODE numerical errors. + - | progress_bar : str {'text', 'enhanced', 'tqdm', ''} + | How to present the solver progress. + 'tqdm' uses the python module of the same name and raise an error + if not installed. Empty string or False will disable the bar. + - | progress_kwargs : dict + | kwargs to pass to the progress_bar. Qutip's bars use `chunk_size`. + - | method : str ["adams", "bdf", "lsoda", "dop853", "vern9", etc.] + | Which differential equation integration method to use. + - | atol, rtol : float + | Absolute and relative tolerance of the ODE integrator. + - | nsteps : int + | Maximum number of (internally defined) steps allowed in one + ``tlist`` step. + - | max_step : float + | Maximum lenght of one internal step. When using pulses, it should + be less than half the width of the thinnest pulse. Other options could be supported depending on the integration method, see `Integrator <./classes.html#classes-ode>`_. @@ -657,7 +661,7 @@ def fmmesolve( result: :class:`.Result` An instance of the class :class:`.Result`, which contains - the expectation values for the times specified by `tlist`, and/or the + the expectation values for the times specified by ``tlist``, and/or the state density matrices corresponding to the times. """ if c_ops is None and rho0.isket: diff --git a/qutip/solver/heom/bofin_solvers.py b/qutip/solver/heom/bofin_solvers.py index 4852c60196..ded013367f 100644 --- a/qutip/solver/heom/bofin_solvers.py +++ b/qutip/solver/heom/bofin_solvers.py @@ -474,43 +474,43 @@ def heomsolve( ``expect`` and ``e_data`` attributes of the result (see the return section below). - args : dict, optional {None} + args : dict, optional Change the ``args`` of the RHS for the evolution. - options : dict, optional {None} + options : dict, optional Generic solver options. - - store_final_state : bool - Whether or not to store the final state of the evolution in the - result class. - - store_states : bool, None - Whether or not to store the state vectors or density matrices. - On `None` the states will be saved if no expectation operators are - given. - - store_ados : bool {False, True} - Whether or not to store the HEOM ADOs. - - normalize_output : bool - Normalize output state to hide ODE numerical errors. - - progress_bar : str {'text', 'enhanced', 'tqdm', ''} - How to present the solver progress. - 'tqdm' uses the python module of the same name and raise an error - if not installed. Empty string or False will disable the bar. - - progress_kwargs : dict - kwargs to pass to the progress_bar. Qutip's bars use `chunk_size`. - - state_data_type: str {'dense'} - Name of the data type of the state used during the ODE evolution. - Use an empty string to keep the input state type. Many integrator can - only work with `Dense`. - - method : str ["adams", "bdf", "lsoda", "dop853", "vern9", etc.] - Which differential equation integration method to use. - - atol, rtol : float - Absolute and relative tolerance of the ODE integrator. - - nsteps : - Maximum number of (internally defined) steps allowed in one ``tlist`` - step. - - max_step : float, 0 - Maximum lenght of one internal step. When using pulses, it should be - less than half the width of the thinnest pulse. + - | store_final_state : bool + | Whether or not to store the final state of the evolution in the + result class. + - | store_states : bool, None + | Whether or not to store the state vectors or density matrices. + On `None` the states will be saved if no expectation operators are + given. + - | store_ados : bool + | Whether or not to store the HEOM ADOs. + - | normalize_output : bool + | Normalize output state to hide ODE numerical errors. + - | progress_bar : str {'text', 'enhanced', 'tqdm', ''} + | How to present the solver progress. + 'tqdm' uses the python module of the same name and raise an error + if not installed. Empty string or False will disable the bar. + - | progress_kwargs : dict + | kwargs to pass to the progress_bar. Qutip's bars use `chunk_size`. + - | state_data_type: str {'dense', 'CSR', 'Dia', } + | Name of the data type of the state used during the ODE evolution. + Use an empty string to keep the input state type. Many integrator + can only work with `Dense`. + - | method : str ["adams", "bdf", "lsoda", "dop853", "vern9", etc.] + | Which differential equation integration method to use. + - | atol, rtol : float + | Absolute and relative tolerance of the ODE integrator. + - | nsteps : int + | Maximum number of (internally defined) steps allowed in one + ``tlist`` step. + - | max_step : float, + | Maximum lenght of one internal step. When using pulses, it should + be less than half the width of the thinnest pulse. Returns ------- @@ -537,7 +537,7 @@ def heomsolve( at tme ``t``. The keys are those given by ``e_ops`` if it was a dict, otherwise they are the indexes of the supplied ``e_ops``. - See :class:`~HEOMResult` and :class:`~Result` for the complete + See :class:`~HEOMResult` and :class:`.Result` for the complete list of attributes. """ H = QobjEvo(H, args=args, tlist=tlist) diff --git a/qutip/solver/krylovsolve.py b/qutip/solver/krylovsolve.py index cfefeb9748..30747aaac1 100644 --- a/qutip/solver/krylovsolve.py +++ b/qutip/solver/krylovsolve.py @@ -41,58 +41,59 @@ def krylovsolve( Dimension of Krylov approximation subspaces used for the time evolution approximation. - e_ops : :class:`.Qobj`, callable, or list. + e_ops : :class:`.Qobj`, callable, or list, optional Single operator or list of operators for which to evaluate expectation values or callable or list of callable. Callable signature must be, `f(t: float, state: Qobj)`. - See :func:`expect` for more detail of operator expectation. + See :func:`~qutip.core.expect.expect` for more detail of operator + expectation. - args : None / *dictionary* + args : dict, optional dictionary of parameters for time-dependent Hamiltonians - options : None / dict + options : dict, optional Dictionary of options for the solver. - - store_final_state : bool, [False] - Whether or not to store the final state of the evolution in the - result class. - - store_states : bool, [None] - Whether or not to store the state vectors or density matrices. - On `None` the states will be saved if no expectation operators are - given. - - normalize_output : bool, [True] - Normalize output state to hide ODE numerical errors. - - progress_bar : str {'text', 'enhanced', 'tqdm', ''}, ["text"] - How to present the solver progress. - 'tqdm' uses the python module of the same name and raise an error - if not installed. Empty string or False will disable the bar. - - progress_kwargs : dict, [{"chunk_size": 10}] - kwargs to pass to the progress_bar. Qutip's bars use `chunk_size`. - - atol: float [1e-7] - Absolute and relative tolerance of the ODE integrator. - - nsteps : int [100] - Maximum number of (internally defined) steps allowed in one ``tlist`` - step. - - min_step, max_step : float, [1e-5, 1e5] - Miniumum and maximum lenght of one internal step. - - always_compute_step: bool [False] - If True, the step lenght is computed each time a new Krylov - subspace is computed. Otherwise it is computed only once when - creating the integrator. - - sub_system_tol: float, [1e-7] - Tolerance to detect an happy breakdown. An happy breakdown happens - when the initial ket is in a subspace of the Hamiltonian smaller - than ``krylov_dim``. + - | store_final_state : bool + | Whether or not to store the final state of the evolution in the + result class. + - | store_states : bool, None + | Whether or not to store the state vectors or density matrices. + On `None` the states will be saved if no expectation operators are + given. + - | normalize_output : bool + | Normalize output state to hide ODE numerical errors. + - | progress_bar : str {'text', 'enhanced', 'tqdm', ''} + | How to present the solver progress. + 'tqdm' uses the python module of the same name and raise an error + if not installed. Empty string or False will disable the bar. + - | progress_kwargs : dict + | kwargs to pass to the progress_bar. Qutip's bars use `chunk_size`. + - | atol: float + | Absolute tolerance of the ODE integrator. + - | nsteps : int + | Maximum number of (internally defined) steps allowed in one + ``tlist`` step. + - | min_step, max_step : float + | Miniumum and maximum lenght of one internal step. + - | always_compute_step: bool + | If True, the step lenght is computed each time a new Krylov + subspace is computed. Otherwise it is computed only once when + creating the integrator. + - | sub_system_tol: float + | Tolerance to detect an happy breakdown. An happy breakdown happens + when the initial ket is in a subspace of the Hamiltonian smaller + than ``krylov_dim``. Returns ------- result: :class:`.Result` - An instance of the class :class:`qutip.Result`, which contains - a *list of array* `result.expect` of expectation values for the times - specified by `tlist`, and/or a *list* `result.states` of state vectors - or density matrices corresponding to the times in `tlist` [if `e_ops` - is an empty list of `store_states=True` in options]. + An instance of the class :class:`.Result`, which contains + a *list of array* ``result.expect`` of expectation values for the times + specified by ``tlist``, and/or a *list* ``result.states`` of state + vectors or density matrices corresponding to the times in ``tlist`` [if + ``e_ops`` is an empty list of ``store_states=True`` in options]. """ H = QobjEvo(H, args=args, tlist=tlist) options = options or {} diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index 15ba1a0093..3649e52999 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -38,76 +38,79 @@ def mcsolve(H, state, tlist, c_ops=(), e_ops=None, ntraj=500, *, even if ``H`` is a superoperator. If none are given, the solver will defer to ``sesolve`` or ``mesolve``. - e_ops : list, [optional] + e_ops : list, optional A ``list`` of operator as Qobj, QobjEvo or callable with signature of (t, state: Qobj) for calculating expectation values. When no ``e_ops`` are given, the solver will default to save the states. - ntraj : int + ntraj : int, default: 500 Maximum number of trajectories to run. Can be cut short if a time limit is passed with the ``timeout`` keyword or if the target tolerance is reached, see ``target_tol``. - args : None / dict + args : dict, optional Arguments for time-dependent Hamiltonian and collapse operator terms. - options : None / dict + options : dict, optional Dictionary of options for the solver. - - store_final_state : bool, [False] - Whether or not to store the final state of the evolution in the - result class. - - store_states : bool, NoneType, [None] - Whether or not to store the state vectors or density matrices. - On `None` the states will be saved if no expectation operators are - given. - - progress_bar : str {'text', 'enhanced', 'tqdm', ''}, ['text'] - How to present the solver progress. - 'tqdm' uses the python module of the same name and raise an error - if not installed. Empty string or False will disable the bar. - - progress_kwargs : dict, [{"chunk_size": 10}] - kwargs to pass to the progress_bar. Qutip's bars use `chunk_size`. - - method : str {"adams", "bdf", "dop853", "vern9", etc.}, ["adams"] - Which differential equation integration method to use. - - keep_runs_results : bool, [False] - Whether to store results from all trajectories or just store the - averages. - - map : str {"serial", "parallel", "loky"}, ["serial"] - How to run the trajectories. "parallel" uses concurent module to run - in parallel while "loky" use the module of the same name to do so. - - job_timeout : NoneType, int, [None] - Maximum time to compute one trajectory. - - num_cpus : NoneType, int, [None] - Number of cpus to use when running in parallel. ``None`` detect the - number of available cpus. - - norm_t_tol, norm_tol, norm_steps : float, float, int, [1e-6, 1e-4, 5] - Parameters used to find the collapse location. ``norm_t_tol`` and - ``norm_tol`` are the tolerance in time and norm respectively. - An error will be raised if the collapse could not be found within - ``norm_steps`` tries. - - mc_corr_eps : float, [1e-10] - Small number used to detect non-physical collapse caused by numerical - imprecision. - - atol, rtol : float, [1e-8, 1e-6] - Absolute and relative tolerance of the ODE integrator. - - nsteps : int [2500] - Maximum number of (internally defined) steps allowed in one ``tlist`` - step. - - max_step : float, [0] - Maximum lenght of one internal step. When using pulses, it should be - less than half the width of the thinnest pulse. - - improved_sampling : Bool - Whether to use the improved sampling algorithm from Abdelhafez et al. - PRA (2019) - - seeds : int, SeedSequence, list, [optional] + - | store_final_state : bool + | Whether or not to store the final state of the evolution in the + result class. + - | store_states : bool, None + | Whether or not to store the state vectors or density matrices. + On `None` the states will be saved if no expectation operators are + given. + - | normalize_output : bool + | Normalize output state to hide ODE numerical errors. + - | progress_bar : str {'text', 'enhanced', 'tqdm', ''} + | How to present the solver progress. + 'tqdm' uses the python module of the same name and raise an error + if not installed. Empty string or False will disable the bar. + - | progress_kwargs : dict + | kwargs to pass to the progress_bar. Qutip's bars use `chunk_size`. + - | method : str ["adams", "bdf", "lsoda", "dop853", "vern9", etc.] + | Which differential equation integration method to use. + - | atol, rtol : float + | Absolute and relative tolerance of the ODE integrator. + - | nsteps : int + | Maximum number of (internally defined) steps allowed in one ``tlist`` + step. + - | max_step : float + | Maximum lenght of one internal step. When using pulses, it should be + less than half the width of the thinnest pulse. + - | keep_runs_results : bool, [False] + | Whether to store results from all trajectories or just store the + averages. + - | map : str {"serial", "parallel", "loky"} + | How to run the trajectories. "parallel" uses concurent module to + run in parallel while "loky" use the module of the same name to do + so. + - | job_timeout : int + | Maximum time to compute one trajectory. + - | num_cpus : int + | Number of cpus to use when running in parallel. ``None`` detect the + number of available cpus. + - | norm_t_tol, norm_tol, norm_steps : float, float, int + | Parameters used to find the collapse location. ``norm_t_tol`` and + ``norm_tol`` are the tolerance in time and norm respectively. + An error will be raised if the collapse could not be found within + ``norm_steps`` tries. + - | mc_corr_eps : float + | Small number used to detect non-physical collapse caused by + numerical imprecision. + - | improved_sampling : Bool + | Whether to use the improved sampling algorithm from Abdelhafez et + al. PRA (2019) + + seeds : int, SeedSequence, list, optional Seed for the random number generator. It can be a single seed used to spawn seeds for each trajectory or a list of seeds, one for each trajectory. Seeds are saved in the result and they can be reused with:: seeds=prev_result.seeds - target_tol : float, tuple, list, [optional] + target_tol : float, tuple, list, optional Target tolerance of the evolution. The evolution will compute trajectories until the error on the expectation values is lower than this tolerance. The maximum number of trajectories employed is @@ -116,20 +119,21 @@ def mcsolve(H, state, tlist, c_ops=(), e_ops=None, ntraj=500, *, relative tolerance, in that order. Lastly, it can be a list of pairs of (atol, rtol) for each e_ops. - timeout : float, [optional] + timeout : float, optional Maximum time for the evolution in second. When reached, no more trajectories will be computed. Returns ------- - results : :class:`qutip.solver.McResult` + results : :class:`.McResult` Object storing all results from the simulation. Which results is saved depends on the presence of ``e_ops`` and the options used. ``collapse`` and ``photocurrent`` is available to Monte Carlo simulation results. - .. note: - The simulation will end when the first end condition is reached between - ``ntraj``, ``timeout`` and ``target_tol``. + Notes + ----- + The simulation will end when the first end condition is reached between + ``ntraj``, ``timeout`` and ``target_tol``. """ options = _solver_deprecation(kwargs, options, "mc") H = QobjEvo(H, args=args, tlist=tlist) diff --git a/qutip/solver/mesolve.py b/qutip/solver/mesolve.py index ac3fb4b285..f00e734f69 100644 --- a/qutip/solver/mesolve.py +++ b/qutip/solver/mesolve.py @@ -20,35 +20,35 @@ def mesolve(H, rho0, tlist, c_ops=None, e_ops=None, args=None, options=None, Master equation evolution of a density matrix for a given Hamiltonian and set of collapse operators, or a Liouvillian. - Evolve the state vector or density matrix (`rho0`) using a given - Hamiltonian or Liouvillian (`H`) and an optional set of collapse operators - (`c_ops`), by integrating the set of ordinary differential equations + Evolve the state vector or density matrix (``rho0``) using a given + Hamiltonian or Liouvillian (``H``) and an optional set of collapse operators + (``c_ops``), by integrating the set of ordinary differential equations that define the system. In the absence of collapse operators the system is evolved according to the unitary evolution of the Hamiltonian. The output is either the state vector at arbitrary points in time - (`tlist`), or the expectation values of the supplied operators - (`e_ops`). If e_ops is a callback function, it is invoked for each - time in `tlist` with time and the state as arguments, and the function + (``tlist``), or the expectation values of the supplied operators + (``e_ops``). If e_ops is a callback function, it is invoked for each + time in ``tlist`` with time and the state as arguments, and the function does not use any return values. - If either `H` or the Qobj elements in `c_ops` are superoperators, they + If either ``H`` or the Qobj elements in ``c_ops`` are superoperators, they will be treated as direct contributions to the total system Liouvillian. This allows the solution of master equations that are not in standard Lindblad form. **Time-dependent operators** - For time-dependent problems, `H` and `c_ops` can be a :obj:`.QobjEvo` or - object that can be interpreted as :obj:`.QobjEvo` such as a list of + For time-dependent problems, ``H`` and ``c_ops`` can be a :obj:`.QobjEvo` + or object that can be interpreted as :obj:`.QobjEvo` such as a list of (Qobj, Coefficient) pairs or a function. **Additional options** - Additional options to mesolve can be set via the `options` argument. Many - ODE integration options can be set this way, and the `store_states` and - `store_final_state` options can be used to store states even though - expectation values are requested via the `e_ops` argument. + Additional options to mesolve can be set via the ``options`` argument. Many + ODE integration options can be set this way, and the ``store_states`` and + ``store_final_state`` options can be used to store states even though + expectation values are requested via the ``e_ops`` argument. Notes ----- @@ -73,57 +73,57 @@ def mesolve(H, rho0, tlist, c_ops=None, e_ops=None, args=None, options=None, Single collapse operator, or list of collapse operators, or a list of Liouvillian superoperators. None is equivalent to an empty list. - e_ops : list of :obj:`.Qobj` / callback function + e_ops : list of :obj:`.Qobj` / callback function, optional Single operator or list of operators for which to evaluate expectation values or callable or list of callable. Callable signature must be, `f(t: float, state: Qobj)`. See :func:`expect` for more detail of operator expectation. - args : None / *dictionary* + args : dict, optional dictionary of parameters for time-dependent Hamiltonians and collapse operators. - options : None / dict + options : dict, optional Dictionary of options for the solver. - - store_final_state : bool - Whether or not to store the final state of the evolution in the - result class. - - store_states : bool, None - Whether or not to store the state vectors or density matrices. - On `None` the states will be saved if no expectation operators are - given. - - normalize_output : bool - Normalize output state to hide ODE numerical errors. - - progress_bar : str {'text', 'enhanced', 'tqdm', ''} - How to present the solver progress. - 'tqdm' uses the python module of the same name and raise an error - if not installed. Empty string or False will disable the bar. - - progress_kwargs : dict - kwargs to pass to the progress_bar. Qutip's bars use `chunk_size`. - - method : str ["adams", "bdf", "lsoda", "dop853", "vern9", etc.] - Which differential equation integration method to use. - - atol, rtol : float - Absolute and relative tolerance of the ODE integrator. - - nsteps : - Maximum number of (internally defined) steps allowed in one ``tlist`` - step. - - max_step : float, 0 - Maximum lenght of one internal step. When using pulses, it should be - less than half the width of the thinnest pulse. + - | store_final_state : bool + | Whether or not to store the final state of the evolution in the + result class. + - | store_states : bool, None + | Whether or not to store the state vectors or density matrices. + On `None` the states will be saved if no expectation operators are + given. + - | normalize_output : bool + | Normalize output state to hide ODE numerical errors. + - | progress_bar : str {'text', 'enhanced', 'tqdm', ''} + | How to present the solver progress. + 'tqdm' uses the python module of the same name and raise an error + if not installed. Empty string or False will disable the bar. + - | progress_kwargs : dict + | kwargs to pass to the progress_bar. Qutip's bars use `chunk_size`. + - | method : str ["adams", "bdf", "lsoda", "dop853", "vern9", etc.] + | Which differential equation integration method to use. + - | atol, rtol : float + | Absolute and relative tolerance of the ODE integrator. + - | nsteps : int + | Maximum number of (internally defined) steps allowed in one ``tlist`` + step. + - | max_step : float + | Maximum lenght of one internal step. When using pulses, it should be + less than half the width of the thinnest pulse. Other options could be supported depending on the integration method, see `Integrator <./classes.html#classes-ode>`_. Returns ------- - result: :obj:`qutip.Result` + result: :obj:`.Result` - An instance of the class :obj:`qutip.Result`, which contains - a *list of array* `result.expect` of expectation values for the times - specified by `tlist`, and/or a *list* `result.states` of state vectors - or density matrices corresponding to the times in `tlist` [if `e_ops` - is an empty list of `store_states=True` in options]. + An instance of the class :obj:`.Result`, which contains a *list of + array* ``result.expect`` of expectation values for the times specified + by ``tlist``, and/or a *list* ``result.states`` of state vectors or + density matrices corresponding to the times in ``tlist`` [if ``e_ops`` + is an empty list of ``store_states=True`` in options]. """ options = _solver_deprecation(kwargs, options) diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index 71e79dcc27..0f982e3946 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -57,84 +57,92 @@ def nm_mcsolve(H, state, tlist, ops_and_rates=(), e_ops=None, ntraj=500, *, operators even if ``H`` is a superoperator. If none are given, the solver will defer to ``sesolve`` or ``mesolve``. Each rate ``Gamma`` may be just a number (in the case of a constant rate) or, otherwise, - specified using any format accepted by :func:`qutip.coefficient`. + specified using any format accepted by + :func:`~qutip.core.coefficient.coefficient`. - e_ops : list, [optional] + e_ops : list, optional A ``list`` of operator as Qobj, QobjEvo or callable with signature of (t, state: Qobj) for calculating expectation values. When no ``e_ops`` are given, the solver will default to save the states. - ntraj : int + ntraj : int, default: 500 Maximum number of trajectories to run. Can be cut short if a time limit is passed with the ``timeout`` keyword or if the target tolerance is reached, see ``target_tol``. - args : None / dict + args : dict, optional Arguments for time-dependent Hamiltonian and collapse operator terms. - options : None / dict + options : dict, optional Dictionary of options for the solver. - - store_final_state : bool, [False] - Whether or not to store the final state of the evolution in the - result class. - - store_states : bool, NoneType, [None] - Whether or not to store the state density matrices. - On ``None`` the states will be saved if no expectation operators are - given. - - progress_bar : str {'text', 'enhanced', 'tqdm', ''}, ['text'] - How to present the solver progress. - 'tqdm' uses the python module of the same name and raise an error - if not installed. Empty string or False will disable the bar. - - progress_kwargs : dict, [{"chunk_size": 10}] - kwargs to pass to the progress_bar. Qutip's bars use ``chunk_size``. - - method : str {"adams", "bdf", "dop853", "vern9", etc.}, ["adams"] - Which differential equation integration method to use. - - keep_runs_results : bool, [False] - Whether to store results from all trajectories or just store the - averages. - - map : str {"serial", "parallel", "loky"}, ["serial"] - How to run the trajectories. "parallel" uses concurrent module to run - in parallel while "loky" use the module of the same name to do so. - - job_timeout : NoneType, int, [None] - Maximum time to compute one trajectory. - - num_cpus : NoneType, int, [None] - Number of cpus to use when running in parallel. ``None`` detect the - number of available cpus. - - norm_t_tol, norm_tol, norm_steps : float, float, int, [1e-6, 1e-4, 5] - Parameters used to find the collapse location. ``norm_t_tol`` and - ``norm_tol`` are the tolerance in time and norm respectively. - An error will be raised if the collapse could not be found within - ``norm_steps`` tries. - - mc_corr_eps : float, [1e-10] - Small number used to detect non-physical collapse caused by numerical - imprecision. - - atol, rtol : float, [1e-8, 1e-6] - Absolute and relative tolerance of the ODE integrator. - - nsteps : int [2500] - Maximum number of (internally defined) steps allowed in one ``tlist`` - step. - - max_step : float, [0] - Maximum length of one internal step. When using pulses, it should be - less than half the width of the thinnest pulse. - - completeness_rtol, completeness_atol : float, float, [1e-5, 1e-8] - Parameters used in determining whether the given Lindblad operators - satisfy a certain completeness relation. If they do not, an - additional Lindblad operator is added automatically (with zero rate). - - martingale_quad_limit : float or int, [100] - An upper bound on the number of subintervals used in the adaptive - integration of the martingale. + - | store_final_state : bool + | Whether or not to store the final state of the evolution in the + result class. + - | store_states : bool, None + | Whether or not to store the state vectors or density matrices. + On `None` the states will be saved if no expectation operators are + given. + - | normalize_output : bool + | Normalize output state to hide ODE numerical errors. + - | progress_bar : str {'text', 'enhanced', 'tqdm', ''} + | How to present the solver progress. + 'tqdm' uses the python module of the same name and raise an error + if not installed. Empty string or False will disable the bar. + - | progress_kwargs : dict + | kwargs to pass to the progress_bar. Qutip's bars use `chunk_size`. + - | method : str ["adams", "bdf", "lsoda", "dop853", "vern9", etc.] + | Which differential equation integration method to use. + - | atol, rtol : float + | Absolute and relative tolerance of the ODE integrator. + - | nsteps : int + | Maximum number of (internally defined) steps allowed in one ``tlist`` + step. + - | max_step : float + | Maximum lenght of one internal step. When using pulses, it should be + less than half the width of the thinnest pulse. + - | keep_runs_results : bool, [False] + | Whether to store results from all trajectories or just store the + averages. + - | map : str {"serial", "parallel", "loky"} + | How to run the trajectories. "parallel" uses concurent module to + run in parallel while "loky" use the module of the same name to do + so. + - | job_timeout : int + | Maximum time to compute one trajectory. + - | num_cpus : int + | Number of cpus to use when running in parallel. ``None`` detect the + number of available cpus. + - | norm_t_tol, norm_tol, norm_steps : float, float, int + | Parameters used to find the collapse location. ``norm_t_tol`` and + ``norm_tol`` are the tolerance in time and norm respectively. + An error will be raised if the collapse could not be found within + ``norm_steps`` tries. + - | mc_corr_eps : float + | Small number used to detect non-physical collapse caused by + numerical imprecision. + - | improved_sampling : Bool + | Whether to use the improved sampling algorithm from Abdelhafez et + al. PRA (2019) + - | completeness_rtol, completeness_atol : float, float + | Parameters used in determining whether the given Lindblad operators + satisfy a certain completeness relation. If they do not, an + additional Lindblad operator is added automatically (with zero + rate). + - | martingale_quad_limit : float or int + An upper bound on the number of subintervals used in the adaptive + integration of the martingale. Note that the 'improved_sampling' option is not currently supported. - seeds : int, SeedSequence, list, [optional] + seeds : int, SeedSequence, list, optional Seed for the random number generator. It can be a single seed used to spawn seeds for each trajectory or a list of seeds, one for each trajectory. Seeds are saved in the result and they can be reused with:: seeds=prev_result.seeds - target_tol : float, tuple, list, [optional] + target_tol : float, tuple, list, optional Target tolerance of the evolution. The evolution will compute trajectories until the error on the expectation values is lower than this tolerance. The maximum number of trajectories employed is @@ -143,7 +151,7 @@ def nm_mcsolve(H, state, tlist, ops_and_rates=(), e_ops=None, ntraj=500, *, relative tolerance, in that order. Lastly, it can be a list of pairs of (atol, rtol) for each e_ops. - timeout : float, [optional] + timeout : float, optional Maximum time for the evolution in seconds. When reached, no more trajectories will be computed. diff --git a/qutip/solver/scattering.py b/qutip/solver/scattering.py index b1de5dfae3..23b5450fe9 100644 --- a/qutip/solver/scattering.py +++ b/qutip/solver/scattering.py @@ -104,6 +104,8 @@ def _temporal_basis_dims(waveguide_emission_indices, n_time_bins, """ Return the dims of the ``temporal_basis_vector``. """ + # TODO: Review n_emissions: change the number of dims but the equivalent + # does not exist in _temporal_basis_idx num_col = len(waveguide_emission_indices) if n_emissions is None: n_emissions = sum( @@ -113,8 +115,7 @@ def _temporal_basis_dims(waveguide_emission_indices, n_time_bins, return [num_col * n_time_bins] * n_emissions -def temporal_basis_vector(waveguide_emission_indices, n_time_bins, - n_emissions=None): +def temporal_basis_vector(waveguide_emission_indices, n_time_bins): """ Generate a temporal basis vector for emissions at specified time bins into specified waveguides. @@ -135,8 +136,7 @@ def temporal_basis_vector(waveguide_emission_indices, n_time_bins, basis vector has dimensionality (W*T)^N. """ idx = _temporal_basis_idx(waveguide_emission_indices, n_time_bins) - dims = _temporal_basis_dims(waveguide_emission_indices, - n_time_bins, n_emissions) + dims = _temporal_basis_dims(waveguide_emission_indices, n_time_bins, None) return basis(dims, list(idx)) @@ -206,10 +206,10 @@ def temporal_scattered_state(H, psi0, n_emissions, c_ops, tlist, tlist : array_like List of times for :math:`\\tau_i`. tlist should contain 0 and exceed the pulse duration / temporal region of interest. - system_zero_state : :class:`.Qobj` + system_zero_state : :class:`.Qobj`, optional State representing zero excitations in the system. Defaults to :math:`\\psi(t_0)` - construct_effective_hamiltonian : bool + construct_effective_hamiltonian : bool, default: True Whether an effective Hamiltonian should be constructed from H and c_ops: :math:`H_{eff} = H - \\frac{i}{2} \\sum_n \\sigma_n^\\dagger \\sigma_n` @@ -260,10 +260,10 @@ def scattering_probability(H, psi0, n_emissions, c_ops, tlist, List of times for :math:`\\tau_i`. tlist should contain 0 and exceed the pulse duration / temporal region of interest; tlist need not be linearly spaced. - system_zero_state : :class:`.Qobj` + system_zero_state : :class:`.Qobj`, optional State representing zero excitations in the system. Defaults to `basis(systemDims, 0)`. - construct_effective_hamiltonian : bool + construct_effective_hamiltonian : bool, default: True Whether an effective Hamiltonian should be constructed from H and c_ops: :math:`H_{eff} = H - \\frac{i}{2} \\sum_n \\sigma_n^\\dagger \\sigma_n` diff --git a/qutip/solver/sesolve.py b/qutip/solver/sesolve.py index d6d7be977e..ebf1d08649 100644 --- a/qutip/solver/sesolve.py +++ b/qutip/solver/sesolve.py @@ -15,22 +15,22 @@ def sesolve(H, psi0, tlist, e_ops=None, args=None, options=None, **kwargs): Schrodinger equation evolution of a state vector or unitary matrix for a given Hamiltonian. - Evolve the state vector (`psi0`) using a given - Hamiltonian (`H`), by integrating the set of ordinary differential + Evolve the state vector (``psi0``) using a given + Hamiltonian (``H``), by integrating the set of ordinary differential equations that define the system. Alternatively evolve a unitary matrix in solving the Schrodinger operator equation. The output is either the state vector or unitary matrix at arbitrary points - in time (`tlist`), or the expectation values of the supplied operators - (`e_ops`). If e_ops is a callback function, it is invoked for each + in time (``tlist``), or the expectation values of the supplied operators + (``e_ops``). If e_ops is a callback function, it is invoked for each time in `tlist` with time and the state as arguments, and the function does not use any return values. e_ops cannot be used in conjunction with solving the Schrodinger operator equation **Time-dependent operators** - For time-dependent problems, `H` and `c_ops` can be a :obj:`.QobjEvo` or - object that can be interpreted as :obj:`.QobjEvo` such as a list of + For time-dependent problems, ``H`` and ``c_ops`` can be a :obj:`.QobjEvo` + or object that can be interpreted as :obj:`.QobjEvo` such as a list of (Qobj, Coefficient) pairs or a function. Parameters @@ -40,63 +40,64 @@ def sesolve(H, psi0, tlist, e_ops=None, args=None, options=None, **kwargs): Hamiltonians. List of [:obj:`.Qobj`, :obj:`.Coefficient`] or callable that can be made into :obj:`.QobjEvo` are also accepted. - psi0 : :obj:`qutip.qobj` + psi0 : :obj:`.Qobj` initial state vector (ket) or initial unitary operator `psi0 = U` tlist : *list* / *array* list of times for :math:`t`. - e_ops : :obj:`qutip.qobj`, callable, or list. + e_ops : :obj:`.Qobj`, callable, or list, optional Single operator or list of operators for which to evaluate expectation values or callable or list of callable. Callable signature must be, `f(t: float, state: Qobj)`. - See :func:`expect` for more detail of operator expectation. + See :func:`~qutip.core.expect.expect` for more detail of operator + expectation. - args : None / *dictionary* + args : dict, optional dictionary of parameters for time-dependent Hamiltonians - options : None / dict + options : dict, optional Dictionary of options for the solver. - - store_final_state : bool - Whether or not to store the final state of the evolution in the - result class. - - store_states : bool, None - Whether or not to store the state vectors or density matrices. - On `None` the states will be saved if no expectation operators are - given. - - normalize_output : bool - Normalize output state to hide ODE numerical errors. - - progress_bar : str {'text', 'enhanced', 'tqdm', ''} - How to present the solver progress. - 'tqdm' uses the python module of the same name and raise an error - if not installed. Empty string or False will disable the bar. - - progress_kwargs : dict - kwargs to pass to the progress_bar. Qutip's bars use `chunk_size`. - - method : str ["adams", "bdf", "lsoda", "dop853", "vern9", etc.] - Which differential equation integration method to use. - - atol, rtol : float - Absolute and relative tolerance of the ODE integrator. - - nsteps : - Maximum number of (internally defined) steps allowed in one ``tlist`` - step. - - max_step : float, 0 - Maximum lenght of one internal step. When using pulses, it should be - less than half the width of the thinnest pulse. + - | store_final_state : bool + | Whether or not to store the final state of the evolution in the + result class. + - | store_states : bool, None + | Whether or not to store the state vectors or density matrices. + On `None` the states will be saved if no expectation operators are + given. + - | normalize_output : bool + | Normalize output state to hide ODE numerical errors. + - | progress_bar : str {'text', 'enhanced', 'tqdm', ''} + | How to present the solver progress. + 'tqdm' uses the python module of the same name and raise an error + if not installed. Empty string or False will disable the bar. + - | progress_kwargs : dict + | kwargs to pass to the progress_bar. Qutip's bars use `chunk_size`. + - | method : str ["adams", "bdf", "lsoda", "dop853", "vern9", etc.] + | Which differential equation integration method to use. + - | atol, rtol : float + | Absolute and relative tolerance of the ODE integrator. + - | nsteps : int + | Maximum number of (internally defined) steps allowed in one ``tlist`` + step. + - | max_step : float + | Maximum lenght of one internal step. When using pulses, it should be + less than half the width of the thinnest pulse. Other options could be supported depending on the integration method, see `Integrator <./classes.html#classes-ode>`_. Returns ------- - result: :obj:`qutip.Result` + result: :obj:`.Result` - An instance of the class :obj:`qutip.Result`, which contains - a *list of array* `result.expect` of expectation values for the times - specified by `tlist`, and/or a *list* `result.states` of state vectors - or density matrices corresponding to the times in `tlist` [if `e_ops` - is an empty list of `store_states=True` in options]. + An instance of the class :obj:`.Result`, which contains a *list of + array* ``result.expect`` of expectation values for the times specified + by ``tlist``, and/or a *list* ``result.states`` of state vectors or + density matrices corresponding to the times in ``tlist`` [if ``e_ops`` + is an empty list of ``store_states=True`` in options]. """ options = _solver_deprecation(kwargs, options) H = QobjEvo(H, args=args, tlist=tlist) diff --git a/qutip/solver/spectrum.py b/qutip/solver/spectrum.py index 29da20667d..2ed206d52c 100644 --- a/qutip/solver/spectrum.py +++ b/qutip/solver/spectrum.py @@ -20,7 +20,7 @@ def spectrum(H, wlist, c_ops, a_op, b_op, solver="es"): \lim_{t \to \infty} \left e^{-i\omega\tau} d\tau. - using the solver indicated by the `solver` parameter. Note: this spectrum + using the solver indicated by the ``solver`` parameter. Note: this spectrum is only defined for stationary statistics (uses steady state rho0) Parameters @@ -31,13 +31,13 @@ def spectrum(H, wlist, c_ops, a_op, b_op, solver="es"): List of frequencies for :math:`\omega`. c_ops : list List of collapse operators. - a_op : :class:`.qobj` + a_op : :class:`.Qobj` Operator A. - b_op : :class:`.qobj` + b_op : :class:`.Qobj` Operator B. - solver : str - Choice of solver (`es` for exponential series and - `pi` for psuedo-inverse, `solve` for generic solver). + solver : str, {'es', 'pi', 'solve'}, default: 'es' + Choice of solver, ``es`` for exponential series and + ``pi`` for psuedo-inverse, ``solve`` for generic solver. Returns ------- @@ -70,7 +70,7 @@ def spectrum_correlation_fft(tlist, y, inverse=False): list/array of times :math:`t` which the correlation function is given. y : array_like list/array of correlations corresponding to time delays :math:`t`. - inverse: boolean + inverse: bool, default: False boolean parameter for using a positive exponent in the Fourier Transform instead. Default is False. diff --git a/qutip/solver/steadystate.py b/qutip/solver/steadystate.py index 372c72564c..8b33696ddb 100644 --- a/qutip/solver/steadystate.py +++ b/qutip/solver/steadystate.py @@ -48,14 +48,14 @@ def steadystate(A, c_ops=[], *, method='direct', solver=None, **kwargs): c_op_list : list A list of collapse operators. - method : str, default='direct' + method : str, {"direct", "eigen", "svd", "power"}, default: "direct" The allowed methods are composed of 2 parts, the steadystate method: - "direct": Solving ``L(rho_ss) = 0`` - "eigen" : Eigenvalue problem - "svd" : Singular value decomposition - "power" : Inverse-power method - solver : str, default=None + solver : str, optional 'direct' and 'power' methods only. Solver to use when solving the ``L(rho_ss) = 0`` equation. Default supported solver are: @@ -73,12 +73,12 @@ def steadystate(A, c_ops=[], *, method='direct', solver=None, **kwargs): Extra options for these solver can be passed in ``**kw``. - use_rcm : bool, default False + use_rcm : bool, default: False Use reverse Cuthill-Mckee reordering to minimize fill-in in the LU factorization of the Liouvillian. Used with 'direct' or 'power' method. - use_wbm : bool, default False + use_wbm : bool, default: False Use Weighted Bipartite Matching reordering to make the Liouvillian diagonally dominant. This is useful for iterative preconditioners only. Used with 'direct' or 'power' method. @@ -89,17 +89,17 @@ def steadystate(A, c_ops=[], *, method='direct', solver=None, **kwargs): Liouvillian elements if not specified by the user. Used with 'direct' method. - power_tol : float, default 1e-12 + power_tol : float, default: 1e-12 Tolerance for the solution when using the 'power' method. - power_maxiter : int, default 10 + power_maxiter : int, default: 10 Maximum number of iteration to use when looking for a solution when using the 'power' method. - power_eps: double, default 1e-15 + power_eps: double, default: 1e-15 Small weight used in the "power" method. - sparse: bool + sparse: bool, default: True Whether to use the sparse eigen solver with the "eigen" method (default sparse). With "direct" and "power" method, when the solver is not specified, it is used to set whether "solve" or "spsolve" is @@ -329,16 +329,16 @@ def steadystate_floquet(H_0, c_ops, Op_t, w_d=1.0, n_it=3, sparse=False, Op_t : :obj:`.Qobj` The the interaction operator which is multiplied by the cosine - w_d : float, default 1.0 + w_d : float, default: 1.0 The frequency of the drive - n_it : int, default 3 + n_it : int, default: 3 The number of iterations for the solver - sparse : bool, default False + sparse : bool, default: False Solve for the steady state using sparse algorithms. - solver : str, default=None + solver : str, optional Solver to use when solving the linear system. Default supported solver are: @@ -407,20 +407,20 @@ def pseudo_inverse(L, rhoss=None, w=None, method='splu', *, use_rcm=False, L : Qobj A Liouvillian superoperator for which to compute the pseudo inverse. - rhoss : Qobj + rhoss : Qobj, optional A steadystate density matrix as Qobj instance, for the Liouvillian superoperator L. - w : double + w : double, optional frequency at which to evaluate pseudo-inverse. Can be zero for dense systems and large sparse systems. Small sparse systems can fail for zero frequencies. - sparse : bool + sparse : bool, optional Flag that indicate whether to use sparse or dense matrix methods when computing the pseudo inverse. - method : string + method : str, optional Method used to compte matrix inverse. Choice are 'pinv' to use scipy's function of the same name, or a linear system solver. @@ -437,6 +437,10 @@ def pseudo_inverse(L, rhoss=None, w=None, method='splu', *, use_rcm=False, own solver. When ``L`` use these data backends, see the corresponding libraries ``linalg`` for available solver. + use_rcm : bool, default: False + Use reverse Cuthill-Mckee reordering to minimize fill-in in the LU + factorization of the Liouvillian. + kwargs : dictionary Additional keyword arguments for setting parameters for solver methods. diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index 06c5e76a0f..b9b7152f1e 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -241,30 +241,30 @@ def smesolve( tlist : *list* / *array* List of times for :math:`t`. - c_ops : list of (:obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format) + c_ops : list of (:obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format), optional Deterministic collapse operator which will contribute with a standard Lindblad type of dissipation. sc_ops : list of (:obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format) List of stochastic collapse operators. - e_ops : : :class:`.qobj`, callable, or list. + e_ops : : :class:`.qobj`, callable, or list, optional Single operator or list of operators for which to evaluate expectation values or callable or list of callable. Callable signature must be, `f(t: float, state: Qobj)`. - See :func:`expect` for more detail of operator expectation. + See :func:`.expect` for more detail of operator expectation. - args : None / *dictionary* + args : dict, optional Dictionary of parameters for time-dependent Hamiltonians and collapse operators. - ntraj : int [500] + ntraj : int, default: 500 Number of trajectories to compute. - heterodyne : bool [False] + heterodyne : bool, default: False Whether to use heterodyne or homodyne detection. - seeds : int, SeedSequence, list, [optional] + seeds : int, SeedSequence, list, optional Seed for the random number generator. It can be a single seed used to spawn seeds for each trajectory or a list of seeds, one for each trajectory. Seeds are saved in the result and they can be reused with:: @@ -282,48 +282,49 @@ def smesolve( relative tolerance, in that order. Lastly, it can be a list of pairs of ``(atol, rtol)`` for each e_ops. - timeout : float [optional] + timeout : float, optional Maximum time for the evolution in second. When reached, no more trajectories will be computed. Overwrite the option of the same name. - options : None / dict + options : dict, optional Dictionary of options for the solver. - - store_final_state : bool, [False] - Whether or not to store the final state of the evolution in the - result class. - - store_states : bool, None, [None] - Whether or not to store the state vectors or density matrices. - On `None` the states will be saved if no expectation operators are - given. - - store_measurement: bool, [False] - Whether to store the measurement and wiener process for each - trajectories. - - keep_runs_results : bool, [False] - Whether to store results from all trajectories or just store the - averages. - - normalize_output : bool, [False] - Normalize output state to hide ODE numerical errors. - - progress_bar : str {'text', 'enhanced', 'tqdm', ''}, ["text"] - How to present the solver progress. - 'tqdm' uses the python module of the same name and raise an error - if not installed. Empty string or False will disable the bar. - - progress_kwargs : dict, [{"chunk_size": 10}] - kwargs to pass to the progress_bar. Qutip's bars use `chunk_size`. - - method : str, ["rouchon"] - Which stochastic differential equation integration method to use. - Main ones are {"euler", "rouchon", "platen", "taylor1.5_imp"} - - map : str {"serial", "parallel", "loky"}, ["serial"] - How to run the trajectories. "parallel" uses concurent module to run - in parallel while "loky" use the module of the same name to do so. - - job_timeout : NoneType, int, [None] - Maximum time to compute one trajectory. - - num_cpus : NoneType, int, [None] - Number of cpus to use when running in parallel. ``None`` detect the - number of available cpus. - - dt : float [0.001 ~ 0.0001] - The finite steps lenght for the Stochastic integration method. - Default change depending on the integrator. + - | store_final_state : bool + | Whether or not to store the final state of the evolution in the + result class. + - | store_states : bool, None + | Whether or not to store the state vectors or density matrices. + On `None` the states will be saved if no expectation operators are + given. + - | store_measurement: bool + | Whether to store the measurement and wiener process for each + trajectories. + - | keep_runs_results : bool + | Whether to store results from all trajectories or just store the + averages. + - | normalize_output : bool + | Normalize output state to hide ODE numerical errors. + - | progress_bar : str {'text', 'enhanced', 'tqdm', ''} + | How to present the solver progress. + 'tqdm' uses the python module of the same name and raise an error + if not installed. Empty string or False will disable the bar. + - | progress_kwargs : dict + | kwargs to pass to the progress_bar. Qutip's bars use `chunk_size`. + - | method : str + | Which stochastic differential equation integration method to use. + Main ones are {"euler", "rouchon", "platen", "taylor1.5_imp"} + - | map : str {"serial", "parallel", "loky"} + | How to run the trajectories. "parallel" uses concurent module to + run in parallel while "loky" use the module of the same name to do + so. + - | job_timeout : NoneType, int + | Maximum time to compute one trajectory. + - | num_cpus : NoneType, int + | Number of cpus to use when running in parallel. ``None`` detect the + number of available cpus. + - | dt : float + | The finite steps lenght for the Stochastic integration method. + Default change depending on the integrator. Other options could be supported depending on the integration method, see `SIntegrator <./classes.html#classes-sode>`_. @@ -332,7 +333,6 @@ def smesolve( ------- output: :class:`.Result` - An instance of the class :class:`.Result`. """ options = _solver_deprecation(kwargs, options, "stoc") @@ -372,23 +372,23 @@ def ssesolve( sc_ops : list of (:obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format) List of stochastic collapse operators. - e_ops : :class:`.qobj`, callable, or list. + e_ops : :class:`.qobj`, callable, or list, optional Single operator or list of operators for which to evaluate expectation values or callable or list of callable. Callable signature must be, `f(t: float, state: Qobj)`. See :func:`expect` for more detail of operator expectation. - args : None / *dictionary* + args : dict, optional Dictionary of parameters for time-dependent Hamiltonians and collapse operators. - ntraj : int [500] + ntraj : int, default: 500 Number of trajectories to compute. - heterodyne : bool [False] + heterodyne : bool, default: False Whether to use heterodyne or homodyne detection. - seeds : int, SeedSequence, list, [optional] + seeds : int, SeedSequence, list, optional Seed for the random number generator. It can be a single seed used to spawn seeds for each trajectory or a list of seeds, one for each trajectory. Seeds are saved in the result and they can be reused with:: @@ -404,48 +404,49 @@ def ssesolve( relative tolerance, in that order. Lastly, it can be a list of pairs of (atol, rtol) for each e_ops. - timeout : float [optional] + timeout : float, optional Maximum time for the evolution in second. When reached, no more trajectories will be computed. Overwrite the option of the same name. - options : None / dict + options : dict, optional Dictionary of options for the solver. - - store_final_state : bool, [False] - Whether or not to store the final state of the evolution in the - result class. - - store_states : bool, None, [None] - Whether or not to store the state vectors or density matrices. - On `None` the states will be saved if no expectation operators are - given. - - store_measurement: bool, [False] - Whether to store the measurement and wiener process, or brownian - noise for each trajectories. - - keep_runs_results : bool, [False] - Whether to store results from all trajectories or just store the - averages. - - normalize_output : bool, [False] - Normalize output state to hide ODE numerical errors. - - progress_bar : str {'text', 'enhanced', 'tqdm', ''}, ["text"] - How to present the solver progress. - 'tqdm' uses the python module of the same name and raise an error - if not installed. Empty string or False will disable the bar. - - progress_kwargs : dict, [{"chunk_size": 10}] - kwargs to pass to the progress_bar. Qutip's bars use `chunk_size`. - - method : str, ["rouchon"] - Which stochastic differential equation integration method to use. - Main ones are {"euler", "rouchon", "platen", "taylor1.5_imp"} - - map : str {"serial", "parallel", "loky"}, ["serial"] - How to run the trajectories. "parallel" uses concurent module to run - in parallel while "loky" use the module of the same name to do so. - - job_timeout : NoneType, int, [None] - Maximum time to compute one trajectory. - - num_cpus : NoneType, int, [None] - Number of cpus to use when running in parallel. ``None`` detect the - number of available cpus. - - dt : float [0.001 ~ 0.0001] - The finite steps lenght for the Stochastic integration method. - Default change depending on the integrator. + - | store_final_state : bool + | Whether or not to store the final state of the evolution in the + result class. + - | store_states : bool, None + | Whether or not to store the state vectors or density matrices. + On `None` the states will be saved if no expectation operators are + given. + - | store_measurement: bool + Whether to store the measurement and wiener process, or brownian + noise for each trajectories. + - | keep_runs_results : bool + | Whether to store results from all trajectories or just store the + averages. + - | normalize_output : bool + | Normalize output state to hide ODE numerical errors. + - | progress_bar : str {'text', 'enhanced', 'tqdm', ''} + | How to present the solver progress. + 'tqdm' uses the python module of the same name and raise an error + if not installed. Empty string or False will disable the bar. + - | progress_kwargs : dict + | kwargs to pass to the progress_bar. Qutip's bars use `chunk_size`. + - | method : str + | Which stochastic differential equation integration method to use. + Main ones are {"euler", "rouchon", "platen", "taylor1.5_imp"} + - | map : str {"serial", "parallel", "loky"} + How to run the trajectories. "parallel" uses concurent module to + run in parallel while "loky" use the module of the same name to do + so. + - | job_timeout : NoneType, int + | Maximum time to compute one trajectory. + - | num_cpus : NoneType, int + | Number of cpus to use when running in parallel. ``None`` detect the + number of available cpus. + - | dt : float + | The finite steps lenght for the Stochastic integration method. + Default change depending on the integrator. Other options could be supported depending on the integration method, see `SIntegrator <./classes.html#classes-sode>`_. diff --git a/qutip/tests/piqs/test_piqs.py b/qutip/tests/piqs/test_piqs.py index 54e17b91bd..315a447983 100644 --- a/qutip/tests/piqs/test_piqs.py +++ b/qutip/tests/piqs/test_piqs.py @@ -854,7 +854,7 @@ def test_dicke_basis(self): assert_equal(test_dicke_basis, true_dicke_basis) # error - assert_raises(AttributeError, dicke_basis, N) + assert_raises(AttributeError, dicke_basis, N, None) def test_dicke(self): """ From ffb4b32340e17b195cf152d66bc2ca250e5d73a2 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 15 Nov 2023 07:31:09 -0500 Subject: [PATCH 079/247] Fix latex build --- qutip/solver/brmesolve.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qutip/solver/brmesolve.py b/qutip/solver/brmesolve.py index 90efda7eca..ae5fc223fd 100644 --- a/qutip/solver/brmesolve.py +++ b/qutip/solver/brmesolve.py @@ -101,9 +101,9 @@ def brmesolve(H, psi0, tlist, a_ops=(), e_ops=(), c_ops=(), On `None` the states will be saved if no expectation operators are given. - | normalize_output : bool - | Normalize output state to hide ODE numerical errors. - - | progress_bar : str {'text', 'enhanced', 'tqdm', ''} - | How to present the solver progress. + | Normalize output state to hide ODE numerical errors. + - | progress_bar : str {'text', 'enhanced', 'tqdm', ''} + | How to present the solver progress. 'tqdm' uses the python module of the same name and raise an error if not installed. Empty string or False will disable the bar. - | progress_kwargs : dict From 1ce7271daf45e7466ad64f35be751496d8e477c5 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 15 Nov 2023 10:16:06 -0500 Subject: [PATCH 080/247] Rest of apidoc function --- doc/apidoc/functions.rst | 14 +---- qutip/animation.py | 77 +++++++++++------------ qutip/fileio.py | 18 +++--- qutip/ipynbtools.py | 6 +- qutip/matplotlib_utilities.py | 14 ++--- qutip/simdiag.py | 8 +-- qutip/solver/nonmarkov/transfertensor.py | 6 +- qutip/solver/parallel.py | 46 +++++++------- qutip/tomography.py | 17 ++--- qutip/utilities.py | 14 ++--- qutip/visualization.py | 80 ++++++++++++------------ qutip/wigner.py | 18 +++--- 12 files changed, 157 insertions(+), 161 deletions(-) diff --git a/doc/apidoc/functions.rst b/doc/apidoc/functions.rst index 2beb8deab1..024b71fa2c 100644 --- a/doc/apidoc/functions.rst +++ b/doc/apidoc/functions.rst @@ -290,26 +290,16 @@ Parallelization --------------- .. automodule:: qutip.solver.parallel - :members: parallel_map, serial_map + :members: parallel_map, serial_map, loky_pmap .. _functions-ipython: -Semidefinite Programming ------------------------- - -.. Was this removed - .. automodule:: qutip.semidefinite - :members: complex_var, herm, pos_noherm, pos, dens, kron, conj, bmat, bmat, memoize, qudit_swap, dnorm_problem - - -.. _functions-semidefinite: - IPython Notebook Tools ---------------------- .. automodule:: qutip.ipynbtools - :members: parallel_map, version_table + :members: version_table .. _functions-misc: diff --git a/qutip/animation.py b/qutip/animation.py index 326d73ea59..61cd5de7ed 100644 --- a/qutip/animation.py +++ b/qutip/animation.py @@ -30,13 +30,13 @@ def anim_wigner_sphere(wigners, reflections=False, *, cmap=None, wigners : list of transformations The wigner transformation at `steps` different theta and phi. - reflections : bool, default=False + reflections : bool, default: False If the reflections of the sphere should be plotted as well. cmap : a matplotlib colormap instance, optional Color map to use when plotting. - colorbar : bool, default=True + colorbar : bool, default: True Whether (True) or not (False) a colorbar should be attached. fig : a matplotlib Figure instance, optional @@ -69,7 +69,7 @@ def anim_hinton(rhos, x_basis=None, y_basis=None, color_style="scaled", Parameters ---------- - rhos : :class:`qutip.solver.Result` or list of :class:`qutip.Qobj` + rhos : :class:`.Result` or list of :class:`.Qobj` Input density matrix or superoperator. .. note:: @@ -83,7 +83,7 @@ def anim_hinton(rhos, x_basis=None, y_basis=None, color_style="scaled", y_basis : list of strings, optional list of y ticklabels to represent y basis of the input. - color_style : string, default="scaled" + color_style : str, {"scaled", "threshold", "phase"}, default: "scaled" Determines how colors are assigned to each square: @@ -97,14 +97,14 @@ def anim_hinton(rhos, x_basis=None, y_basis=None, color_style="scaled", - If set to ``"phase"``, each color is chosen according to the angle of the corresponding matrix element. - label_top : bool, default=True + label_top : bool, default: True If True, x ticklabels will be placed on top, otherwise they will appear below the plot. cmap : a matplotlib colormap instance, optional Color map to use when plotting. - colorbar : bool, default=True + colorbar : bool, default: True Whether (True) or not (False) a colorbar should be attached. fig : a matplotlib Figure instance, optional @@ -152,7 +152,7 @@ def anim_sphereplot(V, theta, phi, *, cmap=None, cmap : a matplotlib colormap instance, optional Color map to use when plotting. - colorbar : bool, default=True + colorbar : bool, default: True Whether (True) or not (False) a colorbar should be attached. fig : a matplotlib Figure instance, optional @@ -184,7 +184,7 @@ def anim_matrix_histogram(Ms, x_basis=None, y_basis=None, limits=None, Parameters ---------- - Ms : list of matrices or :class:`qutip.solver.Result` + Ms : list of matrices or :class:`.Result` The matrix to visualize x_basis : list of strings, optional @@ -196,7 +196,7 @@ def anim_matrix_histogram(Ms, x_basis=None, y_basis=None, limits=None, limits : list/array with two float numbers, optional The z-axis limits [min, max] - bar_style : string, default="real" + bar_style : str, {"real", "img", "abs", "phase"}, default: "real" - If set to ``"real"`` (default), each bar is plotted as the real part of the corresponding matrix element @@ -210,7 +210,7 @@ def anim_matrix_histogram(Ms, x_basis=None, y_basis=None, limits=None, color_limits : list/array with two float numbers, optional The limits of colorbar [min, max] - color_style : string, default="real" + color_style : str, {"real", "img", "abs", "phase"}, default: "real" Determines how colors are assigned to each square: - If set to ``"real"`` (default), each color is chosen @@ -225,7 +225,7 @@ def anim_matrix_histogram(Ms, x_basis=None, y_basis=None, limits=None, cmap : a matplotlib colormap instance, optional Color map to use when plotting. - colorbar : bool, default=True + colorbar : bool, default: True show colorbar fig : a matplotlib Figure instance, optional @@ -242,40 +242,40 @@ def anim_matrix_histogram(Ms, x_basis=None, y_basis=None, limits=None, 'zticks' : list of numbers, optional A list of z-axis tick locations. - 'bars_spacing' : float, default=0.1 + 'bars_spacing' : float, default: 0.1 spacing between bars. - 'bars_alpha' : float, default=1. + 'bars_alpha' : float, default: 1. transparency of bars, should be in range 0 - 1 - 'bars_lw' : float, default=0.5 + 'bars_lw' : float, default: 0.5 linewidth of bars' edges. - 'bars_edgecolor' : color, default='k' + 'bars_edgecolor' : color, default: 'k' The colors of the bars' edges. Examples: 'k', (0.1, 0.2, 0.5) or '#0f0f0f80'. - 'shade' : bool, default=True + 'shade' : bool, default: True Whether to shade the dark sides of the bars (True) or not (False). The shading is relative to plot's source of light. - 'azim' : float, default=-35 + 'azim' : float, default: -35 The azimuthal viewing angle. - 'elev' : float, default=35 + 'elev' : float, default: 35 The elevation viewing angle. - 'stick' : bool, default=False + 'stick' : bool, default: False Changes xlim and ylim in such a way that bars next to XZ and YZ planes will stick to those planes. This option has no effect if ``ax`` is passed as a parameter. - 'cbar_pad' : float, default=0.04 + 'cbar_pad' : float, default: 0.04 The fraction of the original axes between the colorbar and the new image axes. (i.e. the padding between the 3D figure and the colorbar). - 'cbar_to_z' : bool, default=False + 'cbar_to_z' : bool, default: False Whether to set the color of maximum and minimum z-values to the maximum and minimum colors in the colorbar (True) or not (False). @@ -313,16 +313,16 @@ def anim_fock_distribution(rhos, fock_numbers=None, color="green", Parameters ---------- - rhos : :class:`qutip.solver.Result` or list of :class:`qutip.Qobj` + rhos : :class:`.Result` or list of :class:`.Qobj` The density matrix (or ket) of the state to visualize. fock_numbers : list of strings, optional list of x ticklabels to represent fock numbers - color : color or list of colors, default="green" + color : color or list of colors, default: "green" The colors of the bar faces. - unit_y_range : bool, default=True + unit_y_range : bool, default: True Set y-axis limits [0, 1] or not fig : a matplotlib Figure instance, optional @@ -355,7 +355,7 @@ def anim_wigner(rhos, xvec=None, yvec=None, method='clenshaw', Parameters ---------- - rhos : :class:`qutip.solver.Result` or list of :class:`qutip.Qobj` + rhos : :class:`.Result` or list of :class:`.Qobj` The density matrix (or ket) of the state to visualize. xvec : array_like, optional @@ -365,19 +365,18 @@ def anim_wigner(rhos, xvec=None, yvec=None, method='clenshaw', y-coordinates at which to calculate the Wigner function. Does not apply to the 'fft' method. - method : string {'clenshaw', 'iterative', 'laguerre', 'fft'}, - default='clenshaw' + method : str {'clenshaw', 'iterative', 'laguerre', 'fft'}, default: 'clenshaw' The method used for calculating the wigner function. See the documentation for qutip.wigner for details. - projection: string {'2d', '3d'}, default='2d' + projection: str {'2d', '3d'}, default: '2d' Specify whether the Wigner function is to be plotted as a contour graph ('2d') or surface plot ('3d'). cmap : a matplotlib cmap instance, optional The colormap. - colorbar : bool, default=False + colorbar : bool, default: False Whether (True) or not (False) a colorbar should be attached to the Wigner function graph. @@ -418,7 +417,7 @@ def anim_spin_distribution(Ps, THETA, PHI, projection='2d', *, PHI : matrix Meshgrid matrix for the phi coordinate. Its range is between 0 and 2*pi - projection: string {'2d', '3d'}, default='2d' + projection: str {'2d', '3d'}, default: '2d' Specify whether the spin distribution function is to be plotted as a 2D projection where the surface of the unit sphere is mapped on the unit disk ('2d') or surface plot ('3d'). @@ -426,7 +425,7 @@ def anim_spin_distribution(Ps, THETA, PHI, projection='2d', *, cmap : a matplotlib cmap instance, optional The colormap. - colorbar : bool, default=False + colorbar : bool, default: False Whether (True) or not (False) a colorbar should be attached to the Wigner function graph. @@ -463,25 +462,25 @@ def anim_qubism(kets, theme='light', how='pairs', grid_iteration=1, Parameters ---------- - kets : :class:`qutip.solver.Result` or list of :class:`qutip.Qobj` + kets : :class:`.Result` or list of :class:`.Qobj` Pure states for animation. - theme : 'light' or 'dark', default='light' + theme : str {'light', 'dark'}, default: 'light' Set coloring theme for mapping complex values into colors. See: complex_array_to_rgb. - how : 'pairs', 'pairs_skewed' or 'before_after', default='pairs' + how : str {'pairs', 'pairs_skewed', 'before_after'}, default: 'pairs' Type of Qubism plotting. Options: - 'pairs' - typical coordinates, - 'pairs_skewed' - for ferromagnetic/antriferromagnetic plots, - 'before_after' - related to Schmidt plot (see also: plot_schmidt). - grid_iteration : int, default=1 + grid_iteration : int, default: 1 Helper lines to be drawn on plot. Show tiles for 2*grid_iteration particles vs all others. - legend_iteration : int or 'grid_iteration' or 'all', default=0 + legend_iteration : int or 'grid_iteration' or 'all', default: 0 Show labels for first ``2*legend_iteration`` particles. Option 'grid_iteration' sets the same number of particles as for grid_iteration. Option 'all' makes label for all particles. Typically @@ -535,10 +534,10 @@ def anim_schmidt(kets, theme='light', splitting=None, Parameters ---------- - ket : :class:`qutip.solver.Result` or list of :class:`qutip.Qobj` + ket : :class:`.Result` or list of :class:`.Qobj` Pure states for animation. - theme : 'light' or 'dark', default='light' + theme : str {'light', 'dark'}, default: 'light' Set coloring theme for mapping complex values into colors. See: complex_array_to_rgb. @@ -546,7 +545,7 @@ def anim_schmidt(kets, theme='light', splitting=None, Plot for a number of first particles versus the rest. If not given, it is (number of particles + 1) // 2. - labels_iteration : int or pair of ints, default=(3,2) + labels_iteration : int or pair of ints, default: (3, 2) Number of particles to be shown as tick labels, for first (vertical) and last (horizontal) particles, respectively. diff --git a/qutip/fileio.py b/qutip/fileio.py index ac94dd41f5..8f20eea862 100644 --- a/qutip/fileio.py +++ b/qutip/fileio.py @@ -20,11 +20,11 @@ def file_data_store(filename, data, numtype="complex", numformat="decimal", Name of data file to be stored, including extension. data: array_like Data to be written to file. - numtype : str {'complex, 'real'} + numtype : str {'complex, 'real'}, default: 'complex' Type of numerical data. - numformat : str {'decimal','exp'} + numformat : str {'decimal','exp'}, default: 'decimal' Format for written data. - sep : str + sep : str, default: ',' Single-character field seperator. Usually a tab, space, comma, or semicolon. @@ -114,7 +114,7 @@ def file_data_read(filename, sep=None): ---------- filename : str or pathlib.Path Name of file containing reqested data. - sep : str + sep : str, optional Seperator used to store data. Returns @@ -217,7 +217,7 @@ def qsave(data, name='qutip_data'): ---------- data : instance/array_like Input Python object to be stored. - filename : str or pathlib.Path + filename : str or pathlib.Path, default: "qutip_data" Name of output data file. """ @@ -230,13 +230,13 @@ def qsave(data, name='qutip_data'): pickle.dump(data, fileObject) -def qload(name): +def qload(filename): """ - Loads data file from file named 'filename.qu' in current directory. + Loads data file from file ``filename`` in current directory. Parameters ---------- - name : str or pathlib.Path + filename : str or pathlib.Path Name of data file to be loaded. Returns @@ -245,7 +245,7 @@ def qload(name): Object retrieved from requested file. """ - path = Path(name) + path = Path(filename) path = path.with_suffix(path.suffix + ".qu") with open(path, "rb") as fileObject: diff --git a/qutip/ipynbtools.py b/qutip/ipynbtools.py index 48e2eeb14b..2424891922 100644 --- a/qutip/ipynbtools.py +++ b/qutip/ipynbtools.py @@ -56,10 +56,14 @@ def version_table(verbose=False): different packages that were used to run the notebook. This should make it possible to reproduce the environment and the calculation later on. + Parameters + ---------- + verbose : bool, default: False + Add extra information about install location. Returns ------- - version_table: string + version_table: str Return an HTML-formatted string containing version information for QuTiP dependencies. diff --git a/qutip/matplotlib_utilities.py b/qutip/matplotlib_utilities.py index 3181b29192..869a5deaaa 100644 --- a/qutip/matplotlib_utilities.py +++ b/qutip/matplotlib_utilities.py @@ -27,24 +27,24 @@ def wigner_cmap(W, levels=1024, shift=0, max_color='#09224F', ---------- W : array Wigner function array, or any array. - levels : int + levels : int, default: 1024 Number of color levels to create. - shift : float + shift : float, default: 0 Shifts the value at which Wigner elements are emphasized. This parameter should typically be negative and small (i.e -1e-5). - max_color : str + max_color : str, default: '#09224F' String for color corresponding to maximum value of data. Accepts any string format compatible with the Matplotlib.colors.ColorConverter. - mid_color : str + mid_color : str, default: '#FFFFFF' Color corresponding to zero values. Accepts any string format compatible with the Matplotlib.colors.ColorConverter. - min_color : str + min_color : str, default: '#530017' Color corresponding to minimum data values. Accepts any string format compatible with the Matplotlib.colors.ColorConverter. - neg_color : str + neg_color : str, default: '#FF97D4' Color that starts highlighting negative values. Accepts any string format compatible with the Matplotlib.colors.ColorConverter. - invert : bool + invert : bool, default: False Invert the color scheme for negative values so that smaller negative values have darker color. diff --git a/qutip/simdiag.py b/qutip/simdiag.py index 2f875ff82b..dbd5e1fd56 100644 --- a/qutip/simdiag.py +++ b/qutip/simdiag.py @@ -47,18 +47,18 @@ def simdiag(ops, evals: bool = True, *, Parameters ---------- - ops : list/array + ops : list, array ``list`` or ``array`` of qobjs representing commuting Hermitian operators. - evals : bool [True] + evals : bool, default: True Whether to return the eigenvalues for each ops and eigenvectors or just the eigenvectors. - tol : float [1e-14] + tol : float, default: 1e-14 Tolerance for detecting degenerate eigenstates. - safe_mode : bool [True] + safe_mode : bool, default: True Whether to check that all ops are Hermitian and commuting. If set to ``False`` and operators are not commuting, the eigenvectors returned will often be eigenvectors of only the first operator. diff --git a/qutip/solver/nonmarkov/transfertensor.py b/qutip/solver/nonmarkov/transfertensor.py index 9a360952fa..2c87b9dd92 100644 --- a/qutip/solver/nonmarkov/transfertensor.py +++ b/qutip/solver/nonmarkov/transfertensor.py @@ -34,17 +34,17 @@ def ttmsolve(dynmaps, state0, times, e_ops=[], num_learning=0, options=None): List of times :math:`t_n` at which to compute results. Must be uniformily spaced. - e_ops : :class:`.qobj`, callable, or list. + e_ops : :class:`.Qobj`, callable, or list, optional Single operator or list of operators for which to evaluate expectation values or callable or list of callable. Callable signature must be, `f(t: float, state: Qobj)`. See :func:`expect` for more detail of operator expectation. - num_learning : int + num_learning : int, default: 0 Number of times used to construct the dynmaps operators when ``dynmaps`` is a callable. - options : dictionary + options : dictionary, optional Dictionary of options for the solver. - store_final_state : bool diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index 92061b116f..5c2b6b85c9 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -56,7 +56,7 @@ def serial_map(task, values, task_args=None, task_kwargs=None, result = [task(value, *task_args, **task_kwargs) for value in values] - This function work as a drop-in replacement of :func:`qutip.parallel_map`. + This function work as a drop-in replacement of :func:`parallel_map`. Parameters ---------- @@ -65,20 +65,20 @@ def serial_map(task, values, task_args=None, task_kwargs=None, values : array / list The list or array of values for which the ``task`` function is to be evaluated. - task_args : list / dictionary + task_args : list, optional The optional additional argument to the ``task`` function. - task_kwargs : list / dictionary + task_kwargs : dictionary, optional The optional additional keyword argument to the ``task`` function. - reduce_func : func (optional) + reduce_func : func, optional If provided, it will be called with the output of each tasks instead of storing a them in a list. It should return None or a number. When returning a number, it represent the estimation of the number of task left. On a return <= 0, the map will end early. - progress_bar : string + progress_bar : str, optional Progress bar options's string for showing progress. - progress_bar_kwargs : dict + progress_bar_kwargs : dict, optional Options for the progress bar. - map_kw: dict (optional) + map_kw: dict, optional Dictionary containing: - timeout: float, Maximum time (sec) for the whole map. - fail_fast: bool, Raise an error at the first. @@ -136,8 +136,8 @@ def parallel_map(task, values, task_args=None, task_kwargs=None, reduce_func=None, map_kw=None, progress_bar=None, progress_bar_kwargs={}): """ - Parallel execution of a mapping of `values` to the function `task`. This - is functionally equivalent to:: + Parallel execution of a mapping of ``values`` to the function ``task``. + This is functionally equivalent to:: result = [task(value, *task_args, **task_kwargs) for value in values] @@ -148,21 +148,21 @@ def parallel_map(task, values, task_args=None, task_kwargs=None, values : array / list The list or array of values for which the ``task`` function is to be evaluated. - task_args : list / dictionary + task_args : list, optional The optional additional argument to the ``task`` function. - task_kwargs : list / dictionary + task_kwargs : dictionary, optional The optional additional keyword argument to the ``task`` function. - reduce_func : func (optional) + reduce_func : func, optional If provided, it will be called with the output of each tasks instead of storing a them in a list. Note that the order in which results are passed to ``reduce_func`` is not defined. It should return None or a number. When returning a number, it represent the estimation of the number of task left. On a return <= 0, the map will end early. - progress_bar : string + progress_bar : str, optional Progress bar options's string for showing progress. - progress_bar_kwargs : dict + progress_bar_kwargs : dict, optional Options for the progress bar. - map_kw: dict (optional) + map_kw: dict, optional Dictionary containing entry for: - timeout: float, Maximum time (sec) for the whole map. - job_timeout: float, Maximum time (sec) for each job in the map. @@ -279,8 +279,8 @@ def loky_pmap(task, values, task_args=None, task_kwargs=None, reduce_func=None, map_kw=None, progress_bar=None, progress_bar_kwargs={}): """ - Parallel execution of a mapping of `values` to the function `task`. This - is functionally equivalent to:: + Parallel execution of a mapping of ``values`` to the function ``task``. + This is functionally equivalent to:: result = [task(value, *task_args, **task_kwargs) for value in values] @@ -293,20 +293,20 @@ def loky_pmap(task, values, task_args=None, task_kwargs=None, values : array / list The list or array of values for which the ``task`` function is to be evaluated. - task_args : list / dictionary + task_args : list, optional The optional additional argument to the ``task`` function. - task_kwargs : list / dictionary + task_kwargs : dictionary, optional The optional additional keyword argument to the ``task`` function. - reduce_func : func (optional) + reduce_func : func, optional If provided, it will be called with the output of each tasks instead of storing a them in a list. It should return None or a number. When returning a number, it represent the estimation of the number of task left. On a return <= 0, the map will end early. - progress_bar : string + progress_bar : str, optional Progress bar options's string for showing progress. - progress_bar_kwargs : dict + progress_bar_kwargs : dict, optional Options for the progress bar. - map_kw: dict (optional) + map_kw: dict, optional Dictionary containing entry for: - timeout: float, Maximum time (sec) for the whole map. - job_timeout: float, Maximum time (sec) for each job in the map. diff --git a/qutip/tomography.py b/qutip/tomography.py index ebff6a0c68..185c4145a9 100644 --- a/qutip/tomography.py +++ b/qutip/tomography.py @@ -41,11 +41,11 @@ def qpt_plot(chi, lbls_list, title=None, fig=None, axes=None): Input QPT chi matrix. lbls_list : list List of labels for QPT plot axes. - title : string + title : str, optional Plot title. - fig : figure instance + fig : figure instance, optional User defined figure instance used for generating QPT plot. - axes : list of figure axis instance + axes : list of figure axis instance, optional User defined figure axis instance (list of two axes) used for generating QPT plot. @@ -99,17 +99,20 @@ def qpt_plot_combined(chi, lbls_list, title=None, lbls_list : list List of labels for QPT plot axes. - title : string + title : str, optional Plot title. - fig : figure instance + fig : figure instance, optional User defined figure instance used for generating QPT plot. - ax : figure axis instance + figsize : (int, int), default: (8, 6) + Size of the figure when the ``fig`` is not provided. + + ax : figure axis instance, optional User defined figure axis instance used for generating QPT plot (alternative to the fig argument). - threshold: float (None) + threshold: float, optional Threshold for when bars of smaller height should be transparent. If not set, all bars are colored according to the color map. diff --git a/qutip/utilities.py b/qutip/utilities.py index 8d6383071a..2032b4be30 100644 --- a/qutip/utilities.py +++ b/qutip/utilities.py @@ -16,17 +16,17 @@ def n_thermal(w, w_th): Parameters ---------- - w : *float* or *array* + w : float or ndarray Frequency of the oscillator. - w_th : *float* + w_th : float The temperature in units of frequency (or the same units as `w`). Returns ------- - n_avg : *float* or *array* + n_avg : float or array Return the number of average photons in thermal equilibrium for a an oscillator with the given frequency and temperature. @@ -150,11 +150,11 @@ def convert_unit(value, orig="meV", to="GHz"): value : float / array The energy in the old unit. - orig : string - The name of the original unit ("J", "eV", "meV", "GHz", "mK") + orig : str, {"J", "eV", "meV", "GHz", "mK"}, default: "meV" + The name of the original unit. - to : string - The name of the new unit ("J", "eV", "meV", "GHz", "mK") + to : str, {"J", "eV", "meV", "GHz", "mK"}, default: "GHz" + The name of the new unit. Returns ------- diff --git a/qutip/visualization.py b/qutip/visualization.py index e026c86179..8ef1c47220 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -119,13 +119,13 @@ def plot_wigner_sphere(wigner, reflections=False, *, cmap=None, wigner : a wigner transformation The wigner transformation at `steps` different theta and phi. - reflections : bool, default=False + reflections : bool, default: False If the reflections of the sphere should be plotted as well. cmap : a matplotlib colormap instance, optional Color map to use when plotting. - colorbar : bool, default=True + colorbar : bool, default: True Whether (True) or not (False) a colorbar should be attached. fig : a matplotlib Figure instance, optional @@ -296,7 +296,7 @@ def hinton(rho, x_basis=None, y_basis=None, color_style="scaled", y_basis : list of strings, optional list of y ticklabels to represent y basis of the input. - color_style : string, default="scaled" + color_style : str, {"scaled", "threshold", "phase"}, default: "scaled" Determines how colors are assigned to each square: @@ -310,14 +310,14 @@ def hinton(rho, x_basis=None, y_basis=None, color_style="scaled", - If set to ``"phase"``, each color is chosen according to the angle of the corresponding matrix element. - label_top : bool, default=True + label_top : bool, default: True If True, x ticklabels will be placed on top, otherwise they will appear below the plot. cmap : a matplotlib colormap instance, optional Color map to use when plotting. - colorbar : bool, default=True + colorbar : bool, default: True Whether (True) or not (False) a colorbar should be attached. fig : a matplotlib Figure instance, optional @@ -340,16 +340,16 @@ def hinton(rho, x_basis=None, y_basis=None, color_style="scaled", Examples -------- >>> import qutip - >>> + >>> dm = qutip.rand_dm(4) >>> fig, ax = qutip.hinton(dm) >>> fig.show() - >>> + >>> qutip.settings.colorblind_safe = True >>> fig, ax = qutip.hinton(dm, color_style="threshold") >>> fig.show() >>> qutip.settings.colorblind_safe = False - >>> + >>> fig, ax = qutip.hinton(dm, color_style="phase") >>> fig.show() """ @@ -502,7 +502,7 @@ def sphereplot(values, theta, phi, *, cmap : a matplotlib colormap instance, optional Color map to use when plotting. - colorbar : bool, default=True + colorbar : bool, default: True Whether (True) or not (False) a colorbar should be attached. fig : a matplotlib Figure instance, optional @@ -691,7 +691,7 @@ def matrix_histogram(M, x_basis=None, y_basis=None, limits=None, limits : list/array with two float numbers, optional The z-axis limits [min, max] - bar_style : string, default="real" + bar_style : str, {"real", "img", "abs", "phase"}, default: "real" - If set to ``"real"`` (default), each bar is plotted as the real part of the corresponding matrix element @@ -705,7 +705,7 @@ def matrix_histogram(M, x_basis=None, y_basis=None, limits=None, color_limits : list/array with two float numbers, optional The limits of colorbar [min, max] - color_style : string, default="real" + color_style : str, {"real", "img", "abs", "phase"}, default: "real" Determines how colors are assigned to each square: - If set to ``"real"`` (default), each color is chosen @@ -720,7 +720,7 @@ def matrix_histogram(M, x_basis=None, y_basis=None, limits=None, cmap : a matplotlib colormap instance, optional Color map to use when plotting. - colorbar : bool, default=True + colorbar : bool, default: True show colorbar fig : a matplotlib Figure instance, optional @@ -737,40 +737,40 @@ def matrix_histogram(M, x_basis=None, y_basis=None, limits=None, 'zticks' : list of numbers, optional A list of z-axis tick locations. - 'bars_spacing' : float, default=0.1 + 'bars_spacing' : float, default: 0.1 spacing between bars. - 'bars_alpha' : float, default=1. + 'bars_alpha' : float, default: 1. transparency of bars, should be in range 0 - 1 - 'bars_lw' : float, default=0.5 + 'bars_lw' : float, default: 0.5 linewidth of bars' edges. - 'bars_edgecolor' : color, default='k' + 'bars_edgecolor' : color, default: 'k' The colors of the bars' edges. Examples: 'k', (0.1, 0.2, 0.5) or '#0f0f0f80'. - 'shade' : bool, default=True + 'shade' : bool, default: True Whether to shade the dark sides of the bars (True) or not (False). The shading is relative to plot's source of light. - 'azim' : float, default=-35 + 'azim' : float, default: -35 The azimuthal viewing angle. - 'elev' : float, default=35 + 'elev' : float, default: 35 The elevation viewing angle. - 'stick' : bool, default=False + 'stick' : bool, default: False Changes xlim and ylim in such a way that bars next to XZ and YZ planes will stick to those planes. This option has no effect if ``ax`` is passed as a parameter. - 'cbar_pad' : float, default=0.04 + 'cbar_pad' : float, default: 0.04 The fraction of the original axes between the colorbar and the new image axes. (i.e. the padding between the 3D figure and the colorbar). - 'cbar_to_z' : bool, default=False + 'cbar_to_z' : bool, default: False Whether to set the color of maximum and minimum z-values to the maximum and minimum colors in the colorbar (True) or not (False). @@ -977,7 +977,7 @@ def plot_energy_levels(H_list, h_labels=None, energy_levels=None, N=0, *, A list of yticklabels to the left of energy levels of the initial Hamiltonian. - N : int, default=0 + N : int, default: 0 The number of energy levels to plot fig : a matplotlib Figure instance, optional @@ -1062,16 +1062,16 @@ def plot_fock_distribution(rho, fock_numbers=None, color="green", Parameters ---------- - rho : `qutip.Qobj` + rho : :obj:`.Qobj` The density matrix (or ket) of the state to visualize. fock_numbers : list of strings, optional list of x ticklabels to represent fock numbers - color : color or list of colors, default="green" + color : color or list of colors, default: "green" The colors of the bar faces. - unit_y_range : bool, default=True + unit_y_range : bool, default: True Set y-axis limits [0, 1] or not fig : a matplotlib Figure instance, optional @@ -1135,7 +1135,7 @@ def plot_wigner(rho, xvec=None, yvec=None, method='clenshaw', Parameters ---------- - rho : `qutip.Qobj` + rho : :obj:`.Qobj` The density matrix (or ket) of the state to visualize. xvec : array_like, optional @@ -1145,19 +1145,18 @@ def plot_wigner(rho, xvec=None, yvec=None, method='clenshaw', y-coordinates at which to calculate the Wigner function. Does not apply to the 'fft' method. - method : string {'clenshaw', 'iterative', 'laguerre', 'fft'}, - default='clenshaw' + method : str {'clenshaw', 'iterative', 'laguerre', 'fft'}, default: 'clenshaw' The method used for calculating the wigner function. See the documentation for qutip.wigner for details. - projection: string {'2d', '3d'}, default='2d' + projection: str {'2d', '3d'}, default: '2d' Specify whether the Wigner function is to be plotted as a contour graph ('2d') or surface plot ('3d'). cmap : a matplotlib cmap instance, optional The colormap. - colorbar : bool, default=False + colorbar : bool, default: False Whether (True) or not (False) a colorbar should be attached to the Wigner function graph. @@ -1246,6 +1245,7 @@ def plot_expectation_values(results, ylabels=None, *, Visualize the results (expectation values) for an evolution solver. `results` is assumed to be an instance of Result, or a list of Result instances. + Parameters ---------- results : (list of) :class:`.Result` @@ -1312,7 +1312,7 @@ def plot_spin_distribution(P, THETA, PHI, projection='2d', *, PHI : matrix Meshgrid matrix for the phi coordinate. Its range is between 0 and 2*pi - projection: string {'2d', '3d'}, default='2d' + projection: str {'2d', '3d'}, default: '2d' Specify whether the spin distribution function is to be plotted as a 2D projection where the surface of the unit sphere is mapped on the unit disk ('2d') or surface plot ('3d'). @@ -1320,7 +1320,7 @@ def plot_spin_distribution(P, THETA, PHI, projection='2d', *, cmap : a matplotlib cmap instance, optional The colormap. - colorbar : bool, default=False + colorbar : bool, default: False Whether (True) or not (False) a colorbar should be attached to the Wigner function graph. @@ -1419,7 +1419,7 @@ def complex_array_to_rgb(X, theme='light', rmax=None): X : array Array (of any dimension) of complex numbers. - theme : 'light' or 'dark', default='light' + theme : str {'light', 'dark'}, default: 'light' Set coloring theme for mapping complex values into colors. rmax : float, optional @@ -1590,22 +1590,22 @@ def plot_qubism(ket, theme='light', how='pairs', grid_iteration=1, ket : Qobj Pure state for plotting. - theme : 'light' or 'dark', default='light' + theme : str {'light', 'dark'}, default: 'light' Set coloring theme for mapping complex values into colors. See: complex_array_to_rgb. - how : 'pairs', 'pairs_skewed' or 'before_after', default='pairs' + how : str {'pairs', 'pairs_skewed' or 'before_after'}, default: 'pairs' Type of Qubism plotting. Options: - 'pairs' - typical coordinates, - 'pairs_skewed' - for ferromagnetic/antriferromagnetic plots, - 'before_after' - related to Schmidt plot (see also: plot_schmidt). - grid_iteration : int, default=1 + grid_iteration : int, default: 1 Helper lines to be drawn on plot. Show tiles for 2*grid_iteration particles vs all others. - legend_iteration : int or 'grid_iteration' or 'all', default=0 + legend_iteration : int or 'grid_iteration' or 'all', default: 0 Show labels for first ``2*legend_iteration`` particles. Option 'grid_iteration' sets the same number of particles as for grid_iteration. Option 'all' makes label for all particles. Typically @@ -1774,7 +1774,7 @@ def plot_schmidt(ket, theme='light', splitting=None, ket : Qobj Pure state for plotting. - theme : 'light' or 'dark', default='light' + theme : str {'light', 'dark'}, default: 'light' Set coloring theme for mapping complex values into colors. See: complex_array_to_rgb. @@ -1782,7 +1782,7 @@ def plot_schmidt(ket, theme='light', splitting=None, Plot for a number of first particles versus the rest. If not given, it is (number of particles + 1) // 2. - labels_iteration : int or pair of ints, default=(3,2) + labels_iteration : int or pair of ints, default: (3, 2) Number of particles to be shown as tick labels, for first (vertical) and last (horizontal) particles, respectively. diff --git a/qutip/wigner.py b/qutip/wigner.py index bedc67c0ed..dceeffbb87 100644 --- a/qutip/wigner.py +++ b/qutip/wigner.py @@ -161,7 +161,7 @@ def _angle_slice(slicearray, theta, phi): return theta, phi -def wigner(psi, xvec, yvec, method='clenshaw', g=sqrt(2), +def wigner(psi, xvec, yvec=None, method='clenshaw', g=sqrt(2), sparse=False, parfor=False): """Wigner function for a state vector or density matrix at points `xvec + i * yvec`. @@ -179,13 +179,13 @@ def wigner(psi, xvec, yvec, method='clenshaw', g=sqrt(2), y-coordinates at which to calculate the Wigner function. Does not apply to the 'fft' method. - g : float + g : float, default: sqrt(2) Scaling factor for `a = 0.5 * g * (x + iy)`, default `g = sqrt(2)`. The value of `g` is related to the value of `hbar` in the commutation relation `[x, y] = i * hbar` via `hbar=2/g^2` giving the default value `hbar=1`. - method : string {'clenshaw', 'iterative', 'laguerre', 'fft'} + method : string {'clenshaw', 'iterative', 'laguerre', 'fft'}, default: 'clenshaw' Select method 'clenshaw' 'iterative', 'laguerre', or 'fft', where 'clenshaw' and 'iterative' use an iterative method to evaluate the Wigner functions for density matrices :math:`|m>~50). 'clenshaw' is a fast and numerically stable method. - sparse : bool {False, True} + sparse : bool, optional Tells the default solver whether or not to keep the input density matrix in sparse format. As the dimensions of the density matrix grow, setthing this flag can result in increased performance. - parfor : bool {False, True} + parfor : bool, optional Flag for calculating the Laguerre polynomial based Wigner function method='laguerre' in parallel using the parfor function. @@ -657,13 +657,13 @@ class QFunc: xvec, yvec : array_like x- and y-coordinates at which to calculate the Husimi-Q function. - g : float, default sqrt(2) + g : float, default: sqrt(2) Scaling factor for ``a = 0.5 * g * (x + iy)``. The value of `g` is related to the value of `hbar` in the commutation relation :math:`[x,\,y] = i\hbar` via :math:`\hbar=2/g^2`, so the default corresponds to :math:`\hbar=1`. - memory : real, default 1024 + memory : real, default: 1024 Size in MB that may be used internally as workspace. This class will raise ``MemoryError`` if subsequently passed a state of sufficiently large dimension that this bound would be exceeded. In those cases, use @@ -791,13 +791,13 @@ def qfunc( xvec, yvec : array_like x- and y-coordinates at which to calculate the Husimi-Q function. - g : float, default sqrt(2) + g : float, default: sqrt(2) Scaling factor for ``a = 0.5 * g * (x + iy)``. The value of `g` is related to the value of :math:`\hbar` in the commutation relation :math:`[x,\,y] = i\hbar` via :math:`\hbar=2/g^2`, so the default corresponds to :math:`\hbar=1`. - precompute_memory : real, default 1024 + precompute_memory : real, default: 1024 Size in MB that may be used during calculations as working space when dealing with density-matrix inputs. This is ignored for state-vector inputs. The bound is not quite exact due to other, order-of-magnitude From 034abf6b3746e80d1dedf4c1560d23a228b53e23 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 15 Nov 2023 12:21:53 -0500 Subject: [PATCH 081/247] Fix formating in propagator --- qutip/solver/propagator.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/qutip/solver/propagator.py b/qutip/solver/propagator.py index e2990359cd..f5f2c1d78e 100644 --- a/qutip/solver/propagator.py +++ b/qutip/solver/propagator.py @@ -22,10 +22,9 @@ def propagator(H, t, c_ops=(), args=None, options=None, **kwargs): Parameters ---------- - H : :obj:`.Qobj`, :obj:`.QobjEvo`, - :obj:`.QobjEvo` compatible format. Possibly - time-dependent system Liouvillian or Hamiltonian as a Qobj or QobjEvo. - ``list`` of [:obj:`.Qobj`, :obj:`.Coefficient`] or callable + H : :obj:`.Qobj`, :obj:`.QobjEvo`, :obj:`.QobjEvo` compatible format + Possibly time-dependent system Liouvillian or Hamiltonian as a Qobj or + QobjEvo. ``list`` of [:obj:`.Qobj`, :obj:`.Coefficient`] or callable that can be made into :obj:`.QobjEvo` are also accepted. t : float or array-like From 009e605a44cc8dae803583880eea4278000d0fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Wed, 15 Nov 2023 15:19:07 -0500 Subject: [PATCH 082/247] Apply suggestions from code review Co-authored-by: Simon Cross --- doc/apidoc/functions.rst | 2 +- qutip/core/cy/qobjevo.pyx | 2 +- qutip/core/dimensions.py | 25 ++++++++++++------------- qutip/core/qobj.py | 5 +---- 4 files changed, 15 insertions(+), 19 deletions(-) diff --git a/doc/apidoc/functions.rst b/doc/apidoc/functions.rst index a7bbffc760..a2d9651be1 100644 --- a/doc/apidoc/functions.rst +++ b/doc/apidoc/functions.rst @@ -21,7 +21,7 @@ Quantum Operators :members: charge, commutator, create, destroy, displace, fcreate, fdestroy, jmat, num, qeye, identity, momentum, phase, position, qdiags, qutrit_ops, qzero, sigmam, sigmap, sigmax, sigmay, sigmaz, spin_Jx, spin_Jy, spin_Jz, spin_Jm, spin_Jp, squeeze, squeezing, tunneling -Energy restricted Operators +Energy Restricted Operators --------------------------- .. automodule:: qutip.core.energy_restricted diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index e4ea54f23e..07321fa157 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -488,7 +488,7 @@ cdef class QobjEvo: str(left.dims[1]) + ", " + str(( right).dims[0])) res = right.copy() - res._dims = Dimensions([left._dims[0], right._dims[1]]) + res._dims = Dimensions((left._dims[0], right._dims[1])) res.shape = (left.shape[0], right.shape[1]) left = _ConstantElement(left) res.elements = [left @ element for element in res.elements] diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index 72b2917627..851a2f04ac 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -366,7 +366,7 @@ def _frozen(*args, **kwargs): class MetaSpace(type): def __call__(cls, *args, rep=None): """ - Select which subclass is instanciated. + Select which subclass is instantiated. """ if cls is Space and len(args) == 1 and isinstance(args[0], list): # From a list of int. @@ -435,7 +435,8 @@ def from_list(cls, list_dims, rep=None): return spaces[0] elif len(spaces) >= 2: return Space(*spaces) - raise ValueError("Bad list format") + else: + raise ValueError(f'Format not understood {list_dims}') class Space(metaclass=MetaSpace): @@ -582,10 +583,7 @@ def __init__(self, *spaces): def __eq__(self, other): return self is other or ( type(other) is type(self) - and len(self.spaces) == len(other.spaces) - and all(self_space == other_space - for self_space, other_space - in zip(self.spaces, other.spaces)) + self.spaces == other.spaces ) def __hash__(self): @@ -599,7 +597,8 @@ def as_list(self): return sum([space.as_list() for space in self.spaces], []) def dims2idx(self, dims): - print('Compound', dims) + if len(dims) != len(self.spaces): + raise ValueError("Length of supplied dims does not match the number of subspaces.") pos = 0 step = 1 for space, dim in zip(self.spaces[::-1], dims[::-1]): @@ -680,7 +679,6 @@ def as_list(self): return self.oper.as_list() def dims2idx(self, dims): - print('SuperSpace', dims) posl, posr = self.oper.dims2idx(dims) return posl + posr * self.oper.shape[0] @@ -701,21 +699,21 @@ def remove(self, idx): new_dims = self.oper.remove(idx) if new_dims.type == 'scalar': return Field() - return SuperSpace(self.oper.remove(idx), rep=self.superrep) + return SuperSpace(new_dims, rep=self.superrep) def replace(self, idx, new): - return SuperSpace(self.oper.swap(idx, new), rep=self.superrep) + return SuperSpace(self.oper.replace(idx, new), rep=self.superrep) class MetaDims(type): def __call__(cls, *args, rep=None): - if isinstance(args[0], list): + if len(args) == 1 and isinstance(args[0], Dimensions): + return args[0] + elif len(args) == 1 and isinstance(args[0], list): args = ( Space(args[0][1], rep=rep), Space(args[0][0], rep=rep) ) - elif len(args) == 1 and isinstance(args[0], Dimensions): - return args[0] elif len(args) != 2: raise NotImplementedError('No Dual, Ket, Bra...', args) elif ( @@ -793,6 +791,7 @@ def __getitem__(self, key): return self.to_ elif key == 1: return self.from_ + raise IndexError("Dimensions index out of range") def dims2idx(self, dims): """ diff --git a/qutip/core/qobj.py b/qutip/core/qobj.py index 73429d9e4c..671f35f123 100644 --- a/qutip/core/qobj.py +++ b/qutip/core/qobj.py @@ -1141,10 +1141,7 @@ def ptrace(self, sel, dtype=None): raise ValueError("partial trace is not defined on non-square maps") dims = flatten(dims[0]) new_data = _data.ptrace(data, dims, sel, dtype=dtype) - if sel: - new_dims = [[dims[x] for x in sel]] * 2 - else: - new_dims = None + new_dims = [[dims[x] for x in sel]] * 2 if sel else None out = Qobj(new_data, dims=new_dims, type='oper', copy=False) if self.isoperket: return operator_to_vector(out) From 9c722959e05e0efe97254d24c931640af48968c8 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 16 Nov 2023 06:47:50 -0500 Subject: [PATCH 083/247] Improve from comments --- qutip/core/dimensions.py | 24 ++++++++++++++++-------- qutip/core/qobj.py | 11 +++++++---- 2 files changed, 23 insertions(+), 12 deletions(-) diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index 72b2917627..0e7e3a47f9 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -256,7 +256,7 @@ def dims_to_tensor_perm(dims): Parameters ---------- - dims : list + dims : list, Dimensions Dimensions specification for a Qobj. Returns @@ -281,7 +281,7 @@ def dims_to_tensor_shape(dims): Parameters ---------- - dims : list + dims : list, Dimensions Dimensions specification for a Qobj. Returns @@ -306,7 +306,7 @@ def dims_idxs_to_tensor_idxs(dims, indices): Parameters ---------- - dims : list + dims : list, Dimensions Dimensions specification for a Qobj. indices : int, list or tuple @@ -449,7 +449,7 @@ def __init__(self, dims): self.size = dims self.issuper = False # Super representation, should be an empty string except for SuperSpace - self.superrep = "" + self.superrep = None # Does the size and dims match directly: size == prod(dims) self._pure_dims = True self.__setitem__ = _frozen @@ -524,7 +524,7 @@ class Field(Space): def __init__(self): self.size = 1 self.issuper = False - self.superrep = "" + self.superrep = None self._pure_dims = True self.__setitem__ = _frozen @@ -562,6 +562,8 @@ class Compound(Space): def __init__(self, *spaces): self.spaces = [] + if len(spaces) <= 1: + raise ValueError("Compound need multiple space to join.") for space in spaces: if isinstance(space, Compound): self.spaces += space.spaces @@ -569,14 +571,20 @@ def __init__(self, *spaces): self.spaces += [space] self.spaces = tuple(self.spaces) self.size = np.prod([space.size for space in self.spaces]) - self.issuper = any(space.issuper for space in self.spaces) + self.issuper = all(space.issuper for space in self.spaces) + if not self.issuper and any(space.issuper for space in self.spaces): + raise TypeError( + "Cannot create compound space of super and non super." + ) self._pure_dims = all(space._pure_dims for space in self.spaces) superrep = [space.superrep for space in self.spaces] if all(superrep[0] == rep for rep in superrep): self.superrep = superrep[0] else: - # We could also raise an error - self.superrep = 'mixed' + raise TypeError( + "Cannot create compound space of of super operators " + "with different representation." + ) self.__setitem__ = _frozen def __eq__(self, other): diff --git a/qutip/core/qobj.py b/qutip/core/qobj.py index 73429d9e4c..430a652ac1 100644 --- a/qutip/core/qobj.py +++ b/qutip/core/qobj.py @@ -322,7 +322,8 @@ def __init__(self, arg=None, dims=None, type=None, self.dims = [[[root_right]]*2, [[root_left]]*2] self.type = type - self.superrep = superrep + if superrep is not None: + self.superrep = superrep def copy(self): """Create identical copy""" @@ -362,6 +363,7 @@ def type(self, val): @property def superrep(self): + return self._dims.superrep if ( self._dims and self._dims.type in ['super', 'operator-ket', 'operator-bra'] @@ -373,10 +375,11 @@ def superrep(self): @superrep.setter def superrep(self, super_rep): if ( - super_rep - and self._dims.type in ['super', 'operator-ket', 'operator-bra'] + not self._dims.type in ['super', 'operator-ket', 'operator-bra'] + and super_rep ): - self._dims = Dimensions(self._dims.as_list(), rep=super_rep) + raise TypeError("The Qobj does not ahve a superrep") + self._dims = Dimensions(self._dims.as_list(), rep=super_rep) @property def data(self): From b800eb7bf04b2354213b506fe0c695ccca5b4457 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Thu, 16 Nov 2023 07:05:44 -0500 Subject: [PATCH 084/247] Update qutip/core/operators.py Co-authored-by: Simon Cross --- qutip/core/operators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/core/operators.py b/qutip/core/operators.py index 1fcb97049f..bd2d9dc20f 100644 --- a/qutip/core/operators.py +++ b/qutip/core/operators.py @@ -988,7 +988,7 @@ def commutator(A, B, kind="normal"): Parameters ---------- A, B : :obj:`Qobj`, :obj:`QobjEvo` - Operators to compute the commutator + The operators to compute the commutator of. kind: ste {"normal", "anti"}, default: "anti" Which kind of commutator to compute. From 49ca94c0fbaa589ca9ab2623c86f45bb1d162515 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Thu, 16 Nov 2023 07:05:51 -0500 Subject: [PATCH 085/247] Update qutip/core/operators.py Co-authored-by: Simon Cross --- qutip/core/operators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/core/operators.py b/qutip/core/operators.py index bd2d9dc20f..7ab192fc4c 100644 --- a/qutip/core/operators.py +++ b/qutip/core/operators.py @@ -990,7 +990,7 @@ def commutator(A, B, kind="normal"): A, B : :obj:`Qobj`, :obj:`QobjEvo` The operators to compute the commutator of. - kind: ste {"normal", "anti"}, default: "anti" + kind: str {"normal", "anti"}, default: "anti" Which kind of commutator to compute. """ if kind == 'normal': From 332c4d815391c23e507ea2837376af24ea99c351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Thu, 16 Nov 2023 07:06:11 -0500 Subject: [PATCH 086/247] Update qutip/core/superop_reps.py Co-authored-by: Simon Cross --- qutip/core/superop_reps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/core/superop_reps.py b/qutip/core/superop_reps.py index e09977e96c..516256f3eb 100644 --- a/qutip/core/superop_reps.py +++ b/qutip/core/superop_reps.py @@ -171,7 +171,7 @@ def kraus_to_super(kraus_list): Parameters ---------- kraus_list : list of Qobj - Kraus super operator. + The list of Kraus super operators to convert. """ return to_super(kraus_to_choi(kraus_list)) From 27a59dd07f10251d14a282d5eb5a6bc8bbcee816 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Thu, 16 Nov 2023 07:06:21 -0500 Subject: [PATCH 087/247] Update qutip/core/superop_reps.py Co-authored-by: Simon Cross --- qutip/core/superop_reps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/core/superop_reps.py b/qutip/core/superop_reps.py index 516256f3eb..22eac8c26c 100644 --- a/qutip/core/superop_reps.py +++ b/qutip/core/superop_reps.py @@ -147,7 +147,7 @@ def kraus_to_choi(kraus_list): Parameters ---------- kraus_list : list of Qobj - Kraus super operator. + The list of Kraus super operators to convert. """ kraus_mat_list = [k.full() for k in kraus_list] op_rng = list(range(kraus_mat_list[0].shape[1])) From 47057077041fb0c85fe38203645a91e52e030a2d Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 16 Nov 2023 07:07:32 -0500 Subject: [PATCH 088/247] Add doctring in superoper_rep --- qutip/core/superop_reps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/core/superop_reps.py b/qutip/core/superop_reps.py index e09977e96c..77a6fdd421 100644 --- a/qutip/core/superop_reps.py +++ b/qutip/core/superop_reps.py @@ -484,7 +484,6 @@ def to_kraus(q_oper, tol=1e-9): tol : Float, default: 1e-9 Optional threshold parameter for eigenvalues/Kraus ops to be discarded. - The default is to=1e-9. Returns ------- @@ -527,6 +526,7 @@ def to_stinespring(q_oper, threshold=1e-10): Superoperator to be converted to a Stinespring pair. threshold : float, default: 1e-10 + Threshold parameter for eigenvalues/Kraus ops to be discarded. Returns ------- From 05037cc42096a194fcf59bb126e7b9de9c7ce289 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 16 Nov 2023 07:58:52 -0500 Subject: [PATCH 089/247] Remove old type check functions --- doc/apidoc/functions.rst | 2 +- qutip/core/dimensions.py | 29 ++--------------------------- 2 files changed, 3 insertions(+), 28 deletions(-) diff --git a/doc/apidoc/functions.rst b/doc/apidoc/functions.rst index d2682486d2..5f2487c080 100644 --- a/doc/apidoc/functions.rst +++ b/doc/apidoc/functions.rst @@ -61,7 +61,7 @@ Operators and Superoperator Dimensions -------------------------------------- .. automodule:: qutip.core.dimensions - :members: is_scalar, is_vector, is_vectorized_oper, type_from_dims, flatten, deep_remove, unflatten, collapse_dims_oper, collapse_dims_super, enumerate_flat, deep_map, dims_to_tensor_perm, dims_to_tensor_shape, dims_idxs_to_tensor_idxs + :members: type_from_dims, flatten, deep_remove, unflatten, collapse_dims_oper, collapse_dims_super, enumerate_flat, deep_map, dims_to_tensor_perm, dims_to_tensor_shape, dims_idxs_to_tensor_idxs Functions acting on states and operators diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index 80fc697dd3..67ad214b29 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -10,32 +10,7 @@ from qutip.settings import settings -__all__ = [ - "is_scalar", "is_vector", "is_vectorized_oper", - "to_tensor_rep", "from_tensor_rep", "Space", "Dimensions" -] - - -def is_scalar(dims): - """ - Returns True if a dims specification is effectively - a scalar (has dimension 1). - """ - return np.prod(flatten(dims)) == 1 - - -def is_vector(dims): - return ( - isinstance(dims, list) and - isinstance(dims[0], (int, np.integer)) - ) - - -def is_vectorized_oper(dims): - return ( - isinstance(dims, list) and - isinstance(dims[0], list) - ) +__all__ = ["to_tensor_rep", "from_tensor_rep", "Space", "Dimensions"] def type_from_dims(dims, enforce_square=False): @@ -606,7 +581,7 @@ def as_list(self): def dims2idx(self, dims): if len(dims) != len(self.spaces): - raise ValueError("Length of supplied dims does not match the number of subspaces.") + raise ValueError("Length of supplied dims does not match the number of subspaces.") pos = 0 step = 1 for space, dim in zip(self.spaces[::-1], dims[::-1]): From 205cb61d4ab34a1f78eb3574ae1a3137b61ca266 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 16 Nov 2023 10:28:33 -0500 Subject: [PATCH 090/247] restore mkl doc link in HEOM --- qutip/solver/heom/bofin_solvers.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/qutip/solver/heom/bofin_solvers.py b/qutip/solver/heom/bofin_solvers.py index ded013367f..102c58449e 100644 --- a/qutip/solver/heom/bofin_solvers.py +++ b/qutip/solver/heom/bofin_solvers.py @@ -903,11 +903,17 @@ def steady_state( Specifies the the maximum number of iterative refinement steps that the MKL PARDISO solver performs. + For a complete description, see iparm(7) in + https://www.intel.com/content/www/us/en/docs/onemkl/developer-reference-c/2023-0/pardiso-iparm-parameter.html + mkl_weighted_matching : bool MKL PARDISO can use a maximum weighted matching algorithm to permute large elements close the diagonal. This strategy adds an additional level of reliability to the factorization methods. + For a complete description, see iparm(12) in + https://www.intel.com/content/www/us/en/docs/onemkl/developer-reference-c/2023-0/pardiso-iparm-parameter.html + Returns ------- steady_state : Qobj From 954424c06a364f6565d66068751f0fe9dfabc67d Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 16 Nov 2023 11:15:33 -0500 Subject: [PATCH 091/247] Rename feedback classes --- qutip/core/cy/qobjevo.pyx | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 8ec0150403..12c8179007 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -507,13 +507,13 @@ cdef class QobjEvo: if self._feedback_functions is None: self._feedback_functions = {} if feedback == "data": - self._feedback_functions[key] = _Pass_Through() + self._feedback_functions[key] = _DataFeedback() elif feedback in ["qobj", "Qobj"]: - self._feedback_functions[key] = _To_Qobj(self) + self._feedback_functions[key] = _QobjFeedback(self) elif isinstance(feedback, (Qobj, QobjEvo)): if isinstance(feedback, Qobj): feedback = QobjEvo(feedback) - self._feedback_functions[key] = _Expect(feedback) + self._feedback_functions[key] = _ExpectFeedback(feedback) elif isinstance(feedback, str): self._solver_only_feedback.add((key, feedback)) else: @@ -554,9 +554,9 @@ cdef class QobjEvo: if self._feedback_functions is not None: for key, func in self._feedback_functions.items(): - # Update dims in ``_To_Qobj`` - if isinstance(func, _To_Qobj): - self._feedback_functions[key] = _To_Qobj(self) + # Update dims in ``_QobjFeedback`` + if isinstance(func, _QobjFeedback): + self._feedback_functions[key] = _QobjFeedback(self) ########################################################################### @@ -1156,7 +1156,7 @@ cdef class QobjEvo: return out -cdef class _Expect: +cdef class _ExpectFeedback: cdef QobjEvo oper cdef bint stack cdef int N, N2 @@ -1177,10 +1177,10 @@ cdef class _Expect: ) def __repr__(self): - return "Expect" + return "ExpectFeedback" -cdef class _To_Qobj: +cdef class _QobjFeedback: cdef list dims, dims_flat cdef bint issuper cdef idxint N @@ -1206,10 +1206,10 @@ cdef class _To_Qobj: return out def __repr__(self): - return "Qobj" + return "QobjFeedback" -cdef class _Pass_Through: +cdef class _DataFeedback: def __init__(self): pass @@ -1217,4 +1217,4 @@ cdef class _Pass_Through: return state def __repr__(self): - return "data" + return "DataFeedback" From cced731c528b3bf33f223ebb3bc356c51ed8b11e Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 16 Nov 2023 16:58:28 -0500 Subject: [PATCH 092/247] guide dynamic start --- doc/guide/dynamics/dynamics-data.rst | 61 +++--- doc/guide/dynamics/dynamics-intro.rst | 105 +++++----- doc/guide/dynamics/dynamics-master.rst | 238 ++++++++++++++------- doc/guide/dynamics/dynamics-monte.rst | 253 +++++++++++++++-------- qutip/solver/nonmarkov/transfertensor.py | 2 +- 5 files changed, 433 insertions(+), 226 deletions(-) diff --git a/doc/guide/dynamics/dynamics-data.rst b/doc/guide/dynamics/dynamics-data.rst index d6c616dbbe..c0b1daea71 100644 --- a/doc/guide/dynamics/dynamics-data.rst +++ b/doc/guide/dynamics/dynamics-data.rst @@ -9,9 +9,12 @@ Dynamics Simulation Results The solver.Result Class ======================= -Before embarking on simulating the dynamics of quantum systems, we will first look at the data structure used for returning the simulation results. -This object is a :func:`qutip.Result` class that stores all the crucial data needed for analyzing and plotting the results of a simulation. -A generic ``Result`` object ``result`` contains the following properties for storing simulation data: +Before embarking on simulating the dynamics of quantum systems, we will first +look at the data structure used for returning the simulation results. This +object is a :func:`~qutip.solver.result.Result` class that stores all the +crucial data needed for analyzing and plotting the results of a simulation. +A generic ``Result`` object ``result`` contains the following properties for +storing simulation data: .. cssclass:: table-striped @@ -31,8 +34,7 @@ A generic ``Result`` object ``result`` contains the following properties for sto +------------------------+-----------------------------------------------------------------------+ | ``result.final_state`` | State vector or density matrix at the last time of the evolution. | +------------------------+-----------------------------------------------------------------------+ -| ``result.stats`` | Various statistics about the evolution including the integration | -| | method used, number of collapse operators etc. | +| ``result.stats`` | Various statistics about the evolution. | +------------------------+-----------------------------------------------------------------------+ @@ -41,9 +43,11 @@ A generic ``Result`` object ``result`` contains the following properties for sto Accessing Result Data ====================== -To understand how to access the data in a Result object we will use an example as a guide, although we do not worry about the simulation details at this stage. -Like all solvers, the Master Equation solver used in this example returns an Result object, here called simply ``result``. -To see what is contained inside ``result`` we can use the print function: +To understand how to access the data in a Result object we will use an example +as a guide, although we do not worry about the simulation details at this stage. +Like all solvers, the Master Equation solver used in this example returns an +Result object, here called simply ``result``. To see what is contained inside +``result`` we can use the print function: .. doctest:: :options: +SKIP @@ -63,10 +67,11 @@ To see what is contained inside ``result`` we can use the print function: State not saved. > -The first line tells us that this data object was generated from the Master Equation solver :func:`mesolve`. -Next we have the statistics including the ODE solver used, setup time, number of collpases. -Then the integration interval is described, followed with the number of expectation value computed. -Finally, it says whether the states are stored. +The first line tells us that this data object was generated from the Master +Equation solver :func:`.mesolve`. Next we have the statistics including the ODE +solver used, setup time, number of collpases. Then the integration interval is +described, followed with the number of expectation value computed. Finally, it +says whether the states are stored. Now we have all the information needed to analyze the simulation results. To access the data for the two expectation values one can do: @@ -78,7 +83,8 @@ To access the data for the two expectation values one can do: expt0 = result.expect[0] expt1 = result.expect[1] -Recall that Python uses C-style indexing that begins with zero (i.e., [0] => 1st collapse operator data). +Recall that Python uses C-style indexing that begins with zero (i.e., +[0] => 1st collapse operator data). Alternatively, expectation values can be obtained as a dictionary: .. testcode:: @@ -88,8 +94,8 @@ Alternatively, expectation values can be obtained as a dictionary: ... expt_sx = result.e_data["sx"] -When ``e_ops`` is a list, ``e_data`` ca be used with the list index. -Together with the array of times at which these expectation values are calculated: +When ``e_ops`` is a list, ``e_data`` ca be used with the list index. Together +with the array of times at which these expectation values are calculated: .. testcode:: :skipif: True @@ -106,15 +112,21 @@ we can plot the resulting expectation values: show() -State vectors, or density matrices, are accessed in a similar manner, although typically one does not need an index (i.e [0]) since there is only one list for each of these components. -Some other solver can have other output, :func:`heomsolve`'s results can have ``ado_states`` output if the options ``store_ados`` is set, similarly, :func:`fmesolve` can return `floquet_states`. +State vectors, or density matrices, are accessed in a similar manner, although +typically one does not need an index (i.e [0]) since there is only one list for +each of these components. Some other solver can have other output, +:func:`.heomsolve`'s results can have ``ado_states`` output if the options +``store_ados`` is set, similarly, :func:`.fmmesolve` can return +``floquet_states``. Multiple Trajectories Solver Results ==================================== -Solver which compute multiple trajectories such as the Monte Carlo Equations Solvers or the Stochastics Solvers result will differ depending on whether the trajectories are flags to be saved. +Solver which compute multiple trajectories such as the Monte Carlo Equations +Solvers or the Stochastics Solvers result will differ depending on whether the +trajectories are flags to be saved. For example: .. doctest:: @@ -129,10 +141,12 @@ For example: (1, 25, 11) -When the runs are not saved, the expectation values and states are averaged over all trajectories, while a list over the runs are given when they are stored. -For a fix output format, ``average_expect`` return the average, while ``runs_states`` return the list over trajectories. -The ``runs_`` output will return ``None`` when the trajectories are not saved. -Standard derivation of the expectation values is also available: +When the runs are not saved, the expectation values and states are averaged +over all trajectories, while a list over the runs are given when they are stored. +For a fix output format, ``average_expect`` return the average, while +``runs_states`` return the list over trajectories. The ``runs_`` output will +return ``None`` when the trajectories are not saved. Standard derivation of the +expectation values is also available: +-------------------------+----------------------+------------------------------------------------------------------------+ | Reduced result | Trajectories results | Description | @@ -150,7 +164,8 @@ Standard derivation of the expectation values is also available: | ``std_e_data`` | | Dictionary of standard derivation of the expectation values. | +-------------------------+----------------------+------------------------------------------------------------------------+ -Multiple trajectories results also keep the trajectories seeds to allows recomputing the results. +Multiple trajectories results also keep the trajectories seeds to allows +recomputing the results. .. testcode:: :skipif: True diff --git a/doc/guide/dynamics/dynamics-intro.rst b/doc/guide/dynamics/dynamics-intro.rst index dab7359a11..096b3ba3e0 100644 --- a/doc/guide/dynamics/dynamics-intro.rst +++ b/doc/guide/dynamics/dynamics-intro.rst @@ -6,58 +6,71 @@ Introduction Although in some cases, we want to find the stationary states of a quantum system, often we are interested in the dynamics: -how the state of a system or an ensemble of systems evolves with time. QuTiP provides -many ways to model dynamics. - -Broadly speaking, there are two categories -of dynamical models: unitary and non-unitary. In unitary evolution, -the state of the system remains normalized. In non-unitary, or -dissipative, systems, it does not. +how the state of a system or an ensemble of systems evolves with time. +QuTiP provides many ways to model dynamics. There are two kinds of quantum systems: open systems that interact with a larger environment and closed systems that do not. -In a closed system, the state can be described by a state vector, -although when there is entanglement a density matrix may be -needed instead. When we are modeling an open system, or an ensemble -of systems, the use of the density matrix is mandatory. - -Collapse operators are used to model the collapse of the state vector -that can occur when a measurement is performed. +In a closed system, the state can be described by a state vector. +When we are modeling an open system, or an ensemble of systems, +the use of the density matrix is mandatory. -The following tables lists some of the solvers QuTiP provides for dynamic quantum systems and indicates the type of object -returned by the solver: +The following table lists of the solvers QuTiP provides for dynamic +quantum systems and indicates the type of object returned by the solver: .. list-table:: QuTiP Solvers - :widths: 25 25 50 + :widths: 50 25 25 25 :header-rows: 1 - * - Solver + * - Equation + - Function + - Class - Returns - - Remarks - * - sesolve() - - :func:`qutip.solver.Result` - - Unitary evolution, single system - * - mesolve() - - :func:`qutip.solver.Result` - - Lindblad master eqn. or Von Neuman eqn. Density matrix. - * - mcsolve() - - :func:`qutip.solver.Result` - - Monte Carlo with collapse operators - * - essolve() - - Array of expectation values - - Exponential series with collapse operators - * - bloch_redfield_solve() - - :func:`.solver` - - - * - floquet_markov_solve() - - :func:`qutip.solver.Result` - - Floquet-Markov master equation - * - fmmesolve() - - :func:`.solver` - - Floquet-Markov master equation - * - smesolve() - - :func:`qutip.solver.Result` - - Stochastic master equation - * - ssesolve() - - :func:`qutip.solver.Result` - - Stochastic Schrödinger equation + * - Unitary evolution, Schrödinger equation. + - :func:`~qutip.solver.sesolve.sesolve` + - :obj:`~qutip.solver.sesolve.SESolver` + - :obj:`~qutip.solver.result.Result` + * - Periodic unitary evolution, Schrödinger equation. + - :func:`~qutip.solver.floquet.fsesolve` + - None + - :obj:`~qutip.solver.result.Result` + * - Schrödinger equation using Krylov method + - :func:`~qutip.solver.krylovsolve.krylovsolve` + - None + - :obj:`~qutip.solver.result.Result` + * - Lindblad master eqn. or Von Neuman eqn. + - :func:`~qutip.solver.mesolve.mesolve` + - :obj:`~qutip.solver.mesolve.MESolver` + - :obj:`~qutip.solver.result.Result` + * - Monte Carlo with collapse operators + - :func:`~qutip.solver.mcsolve.mcsolve` + - :obj:`~qutip.solver.mcsolve.MCSolver` + - :obj:`~qutip.solver.result.McResult` + * - Non-Markovian Monte Carlo with collapse operators + - :func:`~qutip.solver.nm_mcsolve.nm_mcsolve` + - :obj:`~qutip.solver.nm_mcsolve.NonMarkovianMCSolver` + - :obj:`~qutip.solver.result.NmmcResult` + * - Bloch-Redfield master equation + - :func:`~qutip.solver.mesolve.brmesolve` + - :obj:`~qutip.solver.mesolve.BRSolver` + - :obj:`~qutip.solver.result.Result` + * - Floquet-Markov master equation + - :func:`~qutip.solver.floquet.fmmesolve` + - :obj:`~qutip.solver.floquet.FMESolver` + - :obj:`~qutip.solver.floquet.FloquetResult` + * - Stochastic Schrödinger equation + - :func:`~qutip.solver.stochastic.ssesolve` + - :obj:`~qutip.solver.stochastic.SSESolver` + - :obj:`~qutip.solver.result.MultiTrajResult` + * - Stochastic master equation + - :func:`~qutip.solver.stochastic.smesolve` + - :obj:`~qutip.solver.stochastic.SMESolver` + - :obj:`~qutip.solver.result.MultiTrajResult` + * - Transfer Tensor Method time-evolution + - :func:`~qutip.solver.nonmarkov.transfertensor.ttmsolve` + - None + - :obj:`~qutip.solver.result.Result` + * - Hierarchical Equations of Motion evolution + - :func:`~qutip.solver.heom.bofin_solvers.heomsolve` + - :obj:`~qutip.solver.heom.bofin_solvers.HEOMSolver` + - :obj:`~qutip.solver.heom.bofin_solvers.HSolverDL` diff --git a/doc/guide/dynamics/dynamics-master.rst b/doc/guide/dynamics/dynamics-master.rst index 832b971e3f..d0dd25ae0b 100644 --- a/doc/guide/dynamics/dynamics-master.rst +++ b/doc/guide/dynamics/dynamics-master.rst @@ -15,19 +15,43 @@ The dynamics of a closed (pure) quantum system is governed by the Schrödinger e i\hbar\frac{\partial}{\partial t}\Psi = \hat H \Psi, -where :math:`\Psi` is the wave function, :math:`\hat H` the Hamiltonian, and :math:`\hbar` is Planck's constant. In general, the Schrödinger equation is a partial differential equation (PDE) where both :math:`\Psi` and :math:`\hat H` are functions of space and time. For computational purposes it is useful to expand the PDE in a set of basis functions that span the Hilbert space of the Hamiltonian, and to write the equation in matrix and vector form +where :math:`\Psi` is the wave function, :math:`\hat H` the Hamiltonian, and +:math:`\hbar` is Planck's constant. In general, the Schrödinger equation is a +partial differential equation (PDE) where both :math:`\Psi` and :math:`\hat H` +are functions of space and time. For computational purposes it is useful to +expand the PDE in a set of basis functions that span the Hilbert space of the +Hamiltonian, and to write the equation in matrix and vector form .. math:: i\hbar\frac{d}{dt}\left|\psi\right> = H \left|\psi\right> -where :math:`\left|\psi\right>` is the state vector and :math:`H` is the matrix representation of the Hamiltonian. This matrix equation can, in principle, be solved by diagonalizing the Hamiltonian matrix :math:`H`. In practice, however, it is difficult to perform this diagonalization unless the size of the Hilbert space (dimension of the matrix :math:`H`) is small. Analytically, it is a formidable task to calculate the dynamics for systems with more than two states. If, in addition, we consider dissipation due to the inevitable interaction with a surrounding environment, the computational complexity grows even larger, and we have to resort to numerical calculations in all realistic situations. This illustrates the importance of numerical calculations in describing the dynamics of open quantum systems, and the need for efficient and accessible tools for this task. - -The Schrödinger equation, which governs the time-evolution of closed quantum systems, is defined by its Hamiltonian and state vector. In the previous section, :ref:`tensor`, we showed how Hamiltonians and state vectors are constructed in QuTiP. -Given a Hamiltonian, we can calculate the unitary (non-dissipative) time-evolution of an arbitrary state vector :math:`\left|\psi_0\right>` (``psi0``) using the QuTiP function :func:`qutip.sesolve`. -It evolves the state vector and evaluates the expectation values for a set of operators ``e_ops`` at the points in time in the list ``times``, using an ordinary differential equation solver. - -For example, the time evolution of a quantum spin-1/2 system with tunneling rate 0.1 that initially is in the up state is calculated, and the expectation values of the :math:`\sigma_z` operator evaluated, with the following code +where :math:`\left|\psi\right>` is the state vector and :math:`H` is the matrix +representation of the Hamiltonian. This matrix equation can, in principle, be +solved by diagonalizing the Hamiltonian matrix :math:`H`. In practice, however, +it is difficult to perform this diagonalization unless the size of the Hilbert +space (dimension of the matrix :math:`H`) is small. Analytically, it is a +formidable task to calculate the dynamics for systems with more than two states. +If, in addition, we consider dissipation due to the inevitable interaction with +a surrounding environment, the computational complexity grows even larger, and +we have to resort to numerical calculations in all realistic situations. This +illustrates the importance of numerical calculations in describing the dynamics +of open quantum systems, and the need for efficient and accessible tools for +this task. + +The Schrödinger equation, which governs the time-evolution of closed quantum +systems, is defined by its Hamiltonian and state vector. In the previous +section, :ref:`tensor`, we showed how Hamiltonians and state vectors are +constructed in QuTiP. Given a Hamiltonian, we can calculate the unitary +(non-dissipative) time-evolution of an arbitrary state vector +:math:`\left|\psi_0\right>` (``psi0``) using the QuTiP solver :obj:`.SESolver` +or the function :func:`.sesolve`. It evolves the state vector and evaluates the +expectation values for a set of operators ``e_ops`` at the points in time in +the list ``times``, using an ordinary differential equation solver. + +For example, the time evolution of a quantum spin-1/2 system with tunneling rate +0.1 that initially is in the up state is calculated, and the expectation values +of the :math:`\sigma_z` operator evaluated, with the following code .. plot:: :context: reset @@ -35,33 +59,39 @@ For example, the time evolution of a quantum spin-1/2 system with tunneling rate >>> H = 2*np.pi * 0.1 * sigmax() >>> psi0 = basis(2, 0) >>> times = np.linspace(0.0, 10.0, 20) - >>> result = sesolve(H, psi0, times, e_ops=[sigmaz()]) - + >>> solver = SESolver(H) + >>> result = solver.run(psi0, times, e_ops=[sigmaz()]) + >>> result.expect + [array([ 1. , 0.78914057, 0.24548543, -0.40169579, -0.87947417, + -0.98636112, -0.67728018, -0.08257665, 0.54695111, 0.94581862, + 0.94581574, 0.54694361, -0.08258559, -0.67728679, -0.9863626 , + -0.87946979, -0.40168705, 0.24549517, 0.78914703, 1. ])] -See the next section for examples on how dissipation is included by defining a list of collapse operators and using :func:`qutip.mesolve` instead. +See the next section for examples on evolution with dissipation using +:func:`.mesolve`. -The function returns an instance of :class:`qutip.Result`, as described in the previous section :ref:`solver_result`. -The attribute ``expect`` in ``result`` is a list of expectation values for the operators that are included in the list in the fifth argument. -Adding operators to this list results in a larger output list returned by the function (one array of numbers, corresponding to the times in times, for each operator) +The function returns an instance of :class:`.Result`, as described in the +previous section :ref:`solver_result`. The attribute ``expect`` in ``result`` +is a list of expectation values for the operator(s) that are passed to the +``e_ops`` parameter. Passing multiple operators to ``e_ops`` as a list or dict +results in a vector of expectation value for each operators. ``result.e_data`` +present the expectation values as a dict of list of expect outputs, while +``result.expect`` coerce the values to numpy arrays. .. plot:: :context: close-figs - >>> result = sesolve(H, psi0, times, e_ops=[sigmaz(), sigmay()]) - >>> result.expect # doctest: +NORMALIZE_WHITESPACE - [array([ 1. , 0.78914057, 0.24548559, -0.40169513, -0.8794735 , - -0.98636142, -0.67728219, -0.08258023, 0.54694721, 0.94581685, - 0.94581769, 0.54694945, -0.08257765, -0.67728015, -0.98636097, - -0.87947476, -0.40169736, 0.24548326, 0.78913896, 1. ]), - array([ 0.00000000e+00, -6.14212640e-01, -9.69400240e-01, -9.15773457e-01, - -4.75947849e-01, 1.64593874e-01, 7.35723339e-01, 9.96584419e-01, - 8.37167094e-01, 3.24700624e-01, -3.24698160e-01, -8.37165632e-01, - -9.96584633e-01, -7.35725221e-01, -1.64596567e-01, 4.75945525e-01, - 9.15772479e-01, 9.69400830e-01, 6.14214701e-01, 2.77159958e-06])] + >>> solver.run(psi0, times, e_ops={"s_z": sigmaz(), "s_y": sigmay()}).e_data + {'s_z': [1.0, 0.7891405656865187, 0.24548542861367784, -0.40169578982499127, + ..., 0.24549516882108563, 0.7891470300925004, 0.9999999999361128], + 's_y': [0.0, -0.6142126403681064, -0.9694002807604085, -0.9157731664756708, + ..., 0.9693978141534602, 0.6142043348073879, -1.1303742482923297e-05]} -The resulting list of expectation values can easily be visualized using matplotlib's plotting functions: + +The resulting expectation values can easily be visualized using matplotlib's +plotting functions: .. plot:: :context: close-figs @@ -71,21 +101,24 @@ The resulting list of expectation values can easily be visualized using matplotl >>> times = np.linspace(0.0, 10.0, 100) >>> result = sesolve(H, psi0, times, [sigmaz(), sigmay()]) >>> fig, ax = plt.subplots() - >>> ax.plot(result.times, result.expect[0]) # doctest: +SKIP - >>> ax.plot(result.times, result.expect[1]) # doctest: +SKIP - >>> ax.set_xlabel('Time') # doctest: +SKIP - >>> ax.set_ylabel('Expectation values') # doctest: +SKIP - >>> ax.legend(("Sigma-Z", "Sigma-Y")) # doctest: +SKIP - >>> plt.show() # doctest: +SKIP - -If an empty list of operators is passed to the ``e_ops`` parameter, the :func:`qutip.sesolve` and :func:`qutip.mesolve` functions return a :class:`qutip.Result` instance that contains a list of state vectors for the times specified in ``times`` + >>> ax.plot(result.times, result.expect[0]) + >>> ax.plot(result.times, result.expect[1]) + >>> ax.set_xlabel('Time') + >>> ax.set_ylabel('Expectation values') + >>> ax.legend(("Sigma-Z", "Sigma-Y")) + >>> plt.show() + +If an empty list of operators is passed to the ``e_ops`` parameter, the +:func:`.sesolve` and :func:`.mesolve` functions return a :class:`.Result` +instance that contains a list of state vectors for the times specified in +``times`` .. plot:: :context: close-figs >>> times = [0.0, 1.0] >>> result = sesolve(H, psi0, times, []) - >>> result.states # doctest: +NORMALIZE_WHITESPACE + >>> result.states [Quantum object: dims = [[2], [1]], shape = (2, 1), type = ket Qobj data = [[1.] @@ -99,52 +132,106 @@ If an empty list of operators is passed to the ``e_ops`` parameter, the :func:`q Non-unitary evolution ======================= -While the evolution of the state vector in a closed quantum system is deterministic, open quantum systems are stochastic in nature. The effect of an environment on the system of interest is to induce stochastic transitions between energy levels, and to introduce uncertainty in the phase difference between states of the system. The state of an open quantum system is therefore described in terms of ensemble averaged states using the density matrix formalism. A density matrix :math:`\rho` describes a probability distribution of quantum states :math:`\left|\psi_n\right>`, in a matrix representation :math:`\rho = \sum_n p_n \left|\psi_n\right>\left<\psi_n\right|`, where :math:`p_n` is the classical probability that the system is in the quantum state :math:`\left|\psi_n\right>`. The time evolution of a density matrix :math:`\rho` is the topic of the remaining portions of this section. +While the evolution of the state vector in a closed quantum system is +deterministic, open quantum systems are stochastic in nature. The effect of an +environment on the system of interest is to induce stochastic transitions +between energy levels, and to introduce uncertainty in the phase difference +between states of the system. The state of an open quantum system is therefore +described in terms of ensemble averaged states using the density matrix +formalism. A density matrix :math:`\rho` describes a probability distribution +of quantum states :math:`\left|\psi_n\right>`, in a matrix representation +:math:`\rho = \sum_n p_n \left|\psi_n\right>\left<\psi_n\right|`, where +:math:`p_n` is the classical probability that the system is in the quantum state +:math:`\left|\psi_n\right>`. The time evolution of a density matrix :math:`\rho` +is the topic of the remaining portions of this section. .. _master-master: The Lindblad Master equation ============================= -The standard approach for deriving the equations of motion for a system interacting with its environment is to expand the scope of the system to include the environment. The combined quantum system is then closed, and its evolution is governed by the von Neumann equation +The standard approach for deriving the equations of motion for a system +interacting with its environment is to expand the scope of the system to +include the environment. The combined quantum system is then closed, and its +evolution is governed by the von Neumann equation .. math:: :label: neumann_total \dot \rho_{\rm tot}(t) = -\frac{i}{\hbar}[H_{\rm tot}, \rho_{\rm tot}(t)], -the equivalent of the Schrödinger equation :eq:`schrodinger` in the density matrix formalism. Here, the total Hamiltonian +the equivalent of the Schrödinger equation :eq:`schrodinger` in the density +matrix formalism. Here, the total Hamiltonian .. math:: H_{\rm tot} = H_{\rm sys} + H_{\rm env} + H_{\rm int}, -includes the original system Hamiltonian :math:`H_{\rm sys}`, the Hamiltonian for the environment :math:`H_{\rm env}`, and a term representing the interaction between the system and its environment :math:`H_{\rm int}`. Since we are only interested in the dynamics of the system, we can at this point perform a partial trace over the environmental degrees of freedom in Eq. :eq:`neumann_total`, and thereby obtain a master equation for the motion of the original system density matrix. The most general trace-preserving and completely positive form of this evolution is the Lindblad master equation for the reduced density matrix :math:`\rho = {\rm Tr}_{\rm env}[\rho_{\rm tot}]` +includes the original system Hamiltonian :math:`H_{\rm sys}`, the Hamiltonian +for the environment :math:`H_{\rm env}`, and a term representing the interaction +between the system and its environment :math:`H_{\rm int}`. Since we are only +interested in the dynamics of the system, we can at this point perform a partial +trace over the environmental degrees of freedom in Eq. :eq:`neumann_total`, and +thereby obtain a master equation for the motion of the original system density +matrix. The most general trace-preserving and completely positive form of this +evolution is the Lindblad master equation for the reduced density matrix +:math:`\rho = {\rm Tr}_{\rm env}[\rho_{\rm tot}]` .. math:: :label: lindblad_master_equation \dot\rho(t)=-\frac{i}{\hbar}[H(t),\rho(t)]+\sum_n \frac{1}{2} \left[2 C_n \rho(t) C_n^\dagger - \rho(t) C_n^\dagger C_n - C_n^\dagger C_n \rho(t)\right] -where the :math:`C_n = \sqrt{\gamma_n} A_n` are collapse operators, and :math:`A_n` are the operators through which the environment couples to the system in :math:`H_{\rm int}`, and :math:`\gamma_n` are the corresponding rates. The derivation of Eq. :eq:`lindblad_master_equation` may be found in several sources, and will not be reproduced here. Instead, we emphasize the approximations that are required to arrive at the master equation in the form of Eq. :eq:`lindblad_master_equation` from physical arguments, and hence perform a calculation in QuTiP: - -- **Separability:** At :math:`t=0` there are no correlations between the system and its environment such that the total density matrix can be written as a tensor product :math:`\rho^I_{\rm tot}(0) = \rho^I(0) \otimes \rho^I_{\rm env}(0)`. - -- **Born approximation:** Requires: (1) that the state of the environment does not significantly change as a result of the interaction with the system; (2) The system and the environment remain separable throughout the evolution. These assumptions are justified if the interaction is weak, and if the environment is much larger than the system. In summary, :math:`\rho_{\rm tot}(t) \approx \rho(t)\otimes\rho_{\rm env}`. - -- **Markov approximation** The time-scale of decay for the environment :math:`\tau_{\rm env}` is much shorter than the smallest time-scale of the system dynamics :math:`\tau_{\rm sys} \gg \tau_{\rm env}`. This approximation is often deemed a "short-memory environment" as it requires that environmental correlation functions decay on a time-scale fast compared to those of the system. - -- **Secular approximation** Stipulates that elements in the master equation corresponding to transition frequencies satisfy :math:`|\omega_{ab}-\omega_{cd}| \ll 1/\tau_{\rm sys}`, i.e., all fast rotating terms in the interaction picture can be neglected. It also ignores terms that lead to a small renormalization of the system energy levels. This approximation is not strictly necessary for all master-equation formalisms (e.g., the Block-Redfield master equation), but it is required for arriving at the Lindblad form :eq:`lindblad_master_equation` which is used in :func:`qutip.mesolve`. - - -For systems with environments satisfying the conditions outlined above, the Lindblad master equation :eq:`lindblad_master_equation` governs the time-evolution of the system density matrix, giving an ensemble average of the system dynamics. In order to ensure that these approximations are not violated, it is important that the decay rates :math:`\gamma_n` be smaller than the minimum energy splitting in the system Hamiltonian. Situations that demand special attention therefore include, for example, systems strongly coupled to their environment, and systems with degenerate or nearly degenerate energy levels. +where the :math:`C_n = \sqrt{\gamma_n} A_n` are collapse operators, and +:math:`A_n` are the operators through which the environment couples to the +system in :math:`H_{\rm int}`, and :math:`\gamma_n` are the corresponding rates. +The derivation of Eq. :eq:`lindblad_master_equation` may be found in several +sources, and will not be reproduced here. Instead, we emphasize the +approximations that are required to arrive at the master equation in the form +of Eq. :eq:`lindblad_master_equation` from physical arguments, and hence +perform a calculation in QuTiP: + +- **Separability:** At :math:`t=0` there are no correlations between the system + and its environment such that the total density matrix can be written as a + tensor product :math:`\rho^I_{\rm tot}(0) = \rho^I(0) \otimes \rho^I_{\rm env}(0)`. + +- **Born approximation:** Requires: (1) that the state of the environment does + not significantly change as a result of the interaction with the system; + (2) The system and the environment remain separable throughout the evolution. + These assumptions are justified if the interaction is weak, and if the + environment is much larger than the system. In summary, + :math:`\rho_{\rm tot}(t) \approx \rho(t)\otimes\rho_{\rm env}`. + +- **Markov approximation** The time-scale of decay for the environment + :math:`\tau_{\rm env}` is much shorter than the smallest time-scale of the + system dynamics :math:`\tau_{\rm sys} \gg \tau_{\rm env}`. This approximation + is often deemed a "short-memory environment" as it requires that environmental + correlation functions decay on a time-scale fast compared to those of the system. + +- **Secular approximation** Stipulates that elements in the master equation corresponding + to transition frequencies satisfy :math:`|\omega_{ab}-\omega_{cd}| \ll 1/\tau_{\rm sys}`, + i.e., all fast rotating terms in the interaction picture can be neglected. + It also ignores terms that lead to a small renormalization of the system energy levels. + This approximation is not strictly necessary for all master-equation formalisms + (e.g., the Block-Redfield master equation), but it is required for arriving + at the Lindblad form :eq:`lindblad_master_equation` which is used in :func:`.mesolve`. + + +For systems with environments satisfying the conditions outlined above, the +Lindblad master equation :eq:`lindblad_master_equation` governs the +time-evolution of the system density matrix, giving an ensemble average of the +system dynamics. In order to ensure that these approximations are not violated, +it is important that the decay rates :math:`\gamma_n` be smaller than the +minimum energy splitting in the system Hamiltonian. Situations that demand +special attention therefore include, for example, systems strongly coupled to +their environment, and systems with degenerate or nearly degenerate energy levels. For non-unitary evolution of a quantum systems, i.e., evolution that includes incoherent processes such as relaxation and dephasing, it is common to use -master equations. In QuTiP, the function :func:`qutip.mesolve` is used for both: +master equations. In QuTiP, the function :func:`.mesolve` is used for both: the evolution according to the Schrödinger equation and to the master equation, -even though these two equations of motion are very different. The :func:`qutip.mesolve` +even though these two equations of motion are very different. The :func:`.mesolve` function automatically determines if it is sufficient to use the Schrödinger equation (if no collapse operators were given) or if it has to use the master equation (if collapse operators were given). Note that to calculate @@ -161,15 +248,14 @@ describe the strength of the processes. In QuTiP, the product of the square root of the rate and the operator that describe the dissipation process is called a collapse operator. A list of collapse operators (``c_ops``) is passed as the fourth argument to the -:func:`qutip.mesolve` function in order to define the dissipation processes in the master -equation. When the ``c_ops`` isn't empty, the :func:`qutip.mesolve` function will use +:func:`.mesolve` function in order to define the dissipation processes in the master +equation. When the ``c_ops`` isn't empty, the :func:`.mesolve` function will use the master equation instead of the unitary Schrödinger equation. Using the example with the spin dynamics from the previous section, we can easily add a relaxation process (describing the dissipation of energy from the spin to its environment), by adding ``np.sqrt(0.05) * sigmax()`` in the fourth -parameter to the :func:`qutip.mesolve` function and moving the expectation -operators ``[sigmaz(), sigmay()]`` to the fifth argument. +parameter to the :func:`.mesolve` function. .. plot:: @@ -178,18 +264,22 @@ operators ``[sigmaz(), sigmay()]`` to the fifth argument. >>> times = np.linspace(0.0, 10.0, 100) >>> result = mesolve(H, psi0, times, [np.sqrt(0.05) * sigmax()], e_ops=[sigmaz(), sigmay()]) >>> fig, ax = plt.subplots() - >>> ax.plot(times, result.expect[0]) # doctest: +SKIP - >>> ax.plot(times, result.expect[1]) # doctest: +SKIP - >>> ax.set_xlabel('Time') # doctest: +SKIP - >>> ax.set_ylabel('Expectation values') # doctest: +SKIP - >>> ax.legend(("Sigma-Z", "Sigma-Y")) # doctest: +SKIP - >>> plt.show() # doctest: +SKIP + >>> ax.plot(times, result.expect[0]) + >>> ax.plot(times, result.expect[1]) + >>> ax.set_xlabel('Time') + >>> ax.set_ylabel('Expectation values') + >>> ax.legend(("Sigma-Z", "Sigma-Y")) + >>> plt.show() -Here, 0.05 is the rate and the operator :math:`\sigma_x` (:func:`qutip.sigmax`) describes the dissipation -process. +Here, 0.05 is the rate and the operator :math:`\sigma_x` (:func:`.sigmax`) +describes the dissipation process. -Now a slightly more complex example: Consider a two-level atom coupled to a leaky single-mode cavity through a dipole-type interaction, which supports a coherent exchange of quanta between the two systems. If the atom initially is in its groundstate and the cavity in a 5-photon Fock state, the dynamics is calculated with the lines following code +Now a slightly more complex example: Consider a two-level atom coupled to a +leaky single-mode cavity through a dipole-type interaction, which supports a +coherent exchange of quanta between the two systems. If the atom initially is +in its groundstate and the cavity in a 5-photon Fock state, the dynamics is +calculated with the lines following code .. plot:: :context: close-figs @@ -200,15 +290,15 @@ Now a slightly more complex example: Consider a two-level atom coupled to a leak >>> sm = tensor(destroy(2), qeye(10)) >>> H = 2 * np.pi * a.dag() * a + 2 * np.pi * sm.dag() * sm + 2 * np.pi * 0.25 * (sm * a.dag() + sm.dag() * a) >>> result = mesolve(H, psi0, times, [np.sqrt(0.1)*a], e_ops=[a.dag()*a, sm.dag()*sm]) - >>> plt.figure() # doctest: +SKIP - >>> plt.plot(times, result.expect[0]) # doctest: +SKIP - >>> plt.plot(times, result.expect[1]) # doctest: +SKIP - >>> plt.xlabel('Time') # doctest: +SKIP - >>> plt.ylabel('Expectation values') # doctest: +SKIP - >>> plt.legend(("cavity photon number", "atom excitation probability")) # doctest: +SKIP - >>> plt.show() # doctest: +SKIP + >>> plt.figure() + >>> plt.plot(times, result.expect[0]) + >>> plt.plot(times, result.expect[1]) + >>> plt.xlabel('Time') + >>> plt.ylabel('Expectation values') + >>> plt.legend(("cavity photon number", "atom excitation probability")) + >>> plt.show() .. plot:: :context: reset :include-source: false - :nofigs: \ No newline at end of file + :nofigs: diff --git a/doc/guide/dynamics/dynamics-monte.rst b/doc/guide/dynamics/dynamics-monte.rst index 7eb83bfbf6..6db67e1d05 100644 --- a/doc/guide/dynamics/dynamics-monte.rst +++ b/doc/guide/dynamics/dynamics-monte.rst @@ -10,52 +10,86 @@ Monte Carlo Solver Introduction ============= -Where as the density matrix formalism describes the ensemble average over many identical realizations of a quantum system, the Monte Carlo (MC), or quantum-jump approach to wave function evolution, allows for simulating an individual realization of the system dynamics. Here, the environment is continuously monitored, resulting in a series of quantum jumps in the system wave function, conditioned on the increase in information gained about the state of the system via the environmental measurements. In general, this evolution is governed by the Schrödinger equation with a **non-Hermitian** effective Hamiltonian +Where as the density matrix formalism describes the ensemble average over many +identical realizations of a quantum system, the Monte Carlo (MC), or +quantum-jump approach to wave function evolution, allows for simulating an +individual realization of the system dynamics. Here, the environment is +continuously monitored, resulting in a series of quantum jumps in the system +wave function, conditioned on the increase in information gained about the +state of the system via the environmental measurements. In general, this +evolution is governed by the Schrödinger equation with a **non-Hermitian** +effective Hamiltonian .. math:: :label: heff H_{\rm eff}=H_{\rm sys}-\frac{i\hbar}{2}\sum_{i}C^{+}_{n}C_{n}, -where again, the :math:`C_{n}` are collapse operators, each corresponding to a separate irreversible process with rate :math:`\gamma_{n}`. Here, the strictly negative non-Hermitian portion of Eq. :eq:`heff` gives rise to a reduction in the norm of the wave function, that to first-order in a small time :math:`\delta t`, is given by :math:`\left<\psi(t+\delta t)|\psi(t+\delta t)\right>=1-\delta p` where +where again, the :math:`C_{n}` are collapse operators, each corresponding to a +separate irreversible process with rate :math:`\gamma_{n}`. Here, the strictly +negative non-Hermitian portion of Eq. :eq:`heff` gives rise to a reduction in +the norm of the wave function, that to first-order in a small time +:math:`\delta t`, is given by +:math:`\left<\psi(t+\delta t)|\psi(t+\delta t)\right>=1-\delta p` where .. math:: :label: jump \delta p =\delta t \sum_{n}\left<\psi(t)|C^{+}_{n}C_{n}|\psi(t)\right>, -and :math:`\delta t` is such that :math:`\delta p \ll 1`. With a probability of remaining in the state :math:`\left|\psi(t+\delta t)\right>` given by :math:`1-\delta p`, the corresponding quantum jump probability is thus Eq. :eq:`jump`. If the environmental measurements register a quantum jump, say via the emission of a photon into the environment, or a change in the spin of a quantum dot, the wave function undergoes a jump into a state defined by projecting :math:`\left|\psi(t)\right>` using the collapse operator :math:`C_{n}` corresponding to the measurement +and :math:`\delta t` is such that :math:`\delta p \ll 1`. With a probability +of remaining in the state :math:`\left|\psi(t+\delta t)\right>` given by +:math:`1-\delta p`, the corresponding quantum jump probability is thus Eq. +:eq:`jump`. If the environmental measurements register a quantum jump, say via +the emission of a photon into the environment, or a change in the spin of a +quantum dot, the wave function undergoes a jump into a state defined by +projecting :math:`\left|\psi(t)\right>` using the collapse operator +:math:`C_{n}` corresponding to the measurement .. math:: :label: project \left|\psi(t+\delta t)\right>=C_{n}\left|\psi(t)\right>/\left<\psi(t)|C_{n}^{+}C_{n}|\psi(t)\right>^{1/2}. -If more than a single collapse operator is present in Eq. :eq:`heff`, the probability of collapse due to the :math:`i\mathrm{th}`-operator :math:`C_{i}` is given by +If more than a single collapse operator is present in Eq. :eq:`heff`, the +probability of collapse due to the :math:`i\mathrm{th}`-operator :math:`C_{i}` +is given by .. math:: :label: pcn P_{i}(t)=\left<\psi(t)|C_{i}^{+}C_{i}|\psi(t)\right>/\delta p. -Evaluating the MC evolution to first-order in time is quite tedious. Instead, QuTiP uses the following algorithm to simulate a single realization of a quantum system. Starting from a pure state :math:`\left|\psi(0)\right>`: +Evaluating the MC evolution to first-order in time is quite tedious. Instead, +QuTiP uses the following algorithm to simulate a single realization of a quantum system. Starting from a pure state :math:`\left|\psi(0)\right>`: -- **Ia:** Choose a random number :math:`r_1` between zero and one, representing the probability that a quantum jump occurs. +- **Ia:** Choose a random number :math:`r_1` between zero and one, representing + the probability that a quantum jump occurs. -- **Ib:** Choose a random number :math:`r_2` between zero and one, used to select which collapse operator was responsible for the jump. +- **Ib:** Choose a random number :math:`r_2` between zero and one, used to + select which collapse operator was responsible for the jump. -- **II:** Integrate the Schrödinger equation, using the effective Hamiltonian :eq:`heff` until a time :math:`\tau` such that the norm of the wave function satisfies :math:`\left<\psi(\tau)\right.\left|\psi(\tau)\right> = r_1`, at which point a jump occurs. +- **II:** Integrate the Schrödinger equation, using the effective Hamiltonian + :eq:`heff` until a time :math:`\tau` such that the norm of the wave function + satisfies :math:`\left<\psi(\tau)\right.\left|\psi(\tau)\right> = r_1`, at + which point a jump occurs. -- **III:** The resultant jump projects the system at time :math:`\tau` into one of the renormalized states given by Eq. :eq:`project`. The corresponding collapse operator :math:`C_{n}` is chosen such that :math:`n` is the smallest integer satisfying: +- **III:** The resultant jump projects the system at time :math:`\tau` into one + of the renormalized states given by Eq. :eq:`project`. The corresponding + collapse operator :math:`C_{n}` is chosen such that :math:`n` is the smallest + integer satisfying: .. math:: :label: mc3 \sum_{i=1}^{n} P_{n}(\tau) \ge r_2 -where the individual :math:`P_{n}` are given by Eq. :eq:`pcn`. Note that the left hand side of Eq. :eq:`mc3` is, by definition, normalized to unity. +where the individual :math:`P_{n}` are given by Eq. :eq:`pcn`. Note that the +left hand side of Eq. :eq:`mc3` is, by definition, normalized to unity. -- **IV:** Using the renormalized state from step III as the new initial condition at time :math:`\tau`, draw a new random number, and repeat the above procedure until the final simulation time is reached. +- **IV:** Using the renormalized state from step III as the new initial + condition at time :math:`\tau`, draw a new random number, and repeat the + above procedure until the final simulation time is reached. .. _monte-qutip: @@ -63,16 +97,22 @@ where the individual :math:`P_{n}` are given by Eq. :eq:`pcn`. Note that the le Monte Carlo in QuTiP ==================== -In QuTiP, Monte Carlo evolution is implemented with the :func:`qutip.mcsolve` function. It takes nearly the same arguments as the :func:`qutip.mesolve` -function for master-equation evolution, except that the initial state must be a ket vector, as oppose to a density matrix, and there is an optional keyword parameter ``ntraj`` that defines the number of stochastic trajectories to be simulated. By default, ``ntraj=500`` indicating that 500 Monte Carlo trajectories will be performed. +In QuTiP, Monte Carlo evolution is implemented with the :func:`.mcsolve` +function. It takes nearly the same arguments as the :func:`.mesolve` +function for master-equation evolution, except that the initial state must be a +ket vector, as oppose to a density matrix, and there is an optional keyword +parameter ``ntraj`` that defines the number of stochastic trajectories to be +simulated. By default, ``ntraj=500`` indicating that 500 Monte Carlo +trajectories will be performed. -To illustrate the use of the Monte Carlo evolution of quantum systems in QuTiP, let's again consider the case of a two-level atom coupled to a leaky cavity. The only differences to the master-equation treatment is that in this case we invoke the :func:`qutip.mcsolve` function instead of :func:`qutip.mesolve` +To illustrate the use of the Monte Carlo evolution of quantum systems in QuTiP, +let's again consider the case of a two-level atom coupled to a leaky cavity. +The only differences to the master-equation treatment is that in this case we +invoke the :func:`.mcsolve` function instead of :func:`.mesolve` .. plot:: :context: reset - from qutip.solver.mcsolve import MCSolver, mcsolve - times = np.linspace(0.0, 10.0, 200) psi0 = tensor(fock(2, 0), fock(10, 8)) a = tensor(qeye(2), destroy(10)) @@ -90,24 +130,47 @@ To illustrate the use of the Monte Carlo evolution of quantum systems in QuTiP, .. guide-dynamics-mc1: -The advantage of the Monte Carlo method over the master equation approach is that only the state vector is required to be kept in the computers memory, as opposed to the entire density matrix. For large quantum system this becomes a significant advantage, and the Monte Carlo solver is therefore generally recommended for such systems. For example, simulating a Heisenberg spin-chain consisting of 10 spins with random parameters and initial states takes almost 7 times longer using the master equation rather than Monte Carlo approach with the default number of trajectories running on a quad-CPU machine. Furthermore, it takes about 7 times the memory as well. However, for small systems, the added overhead of averaging a large number of stochastic trajectories to obtain the open system dynamics, as well as starting the multiprocessing functionality, outweighs the benefit of the minor (in this case) memory saving. Master equation methods are therefore generally more efficient when Hilbert space sizes are on the order of a couple of hundred states or smaller. +The advantage of the Monte Carlo method over the master equation approach is that +only the state vector is required to be kept in the computers memory, as opposed +to the entire density matrix. For large quantum system this becomes a significant +advantage, and the Monte Carlo solver is therefore generally recommended for such +systems. For example, simulating a Heisenberg spin-chain consisting of 10 spins +with random parameters and initial states takes almost 7 times longer using the +master equation rather than Monte Carlo approach with the default number of +trajectories running on a quad-CPU machine. Furthermore, it takes about 7 times +the memory as well. However, for small systems, the added overhead of averaging +a large number of stochastic trajectories to obtain the open system dynamics, as +well as starting the multiprocessing functionality, outweighs the benefit of the +minor (in this case) memory saving. Master equation methods are therefore +generally more efficient when Hilbert space sizes are on the order of a couple +of hundred states or smaller. Monte Carlo Solver Result ------------------------- -The Monte Carlo solver returns a :class:`qutip.MultitrajResult` object consisting of expectation values and/or states. -The main difference with :func:`qutip.mesolve`'s :class:`qutip.Result` is that it optionally stores the result of each trajectory together with their averages. -When trajectories are stored, ``result.runs_expect`` is a list over the expectation operators, trajectories and times in that order. -The averages are stored in ``result.average_expect`` and the standard derivation of the expectation values in ``result.std_expect``. -When the states are returned, ``result.runs_states`` will be an array of length ``ntraj``. Each element contains an array of "Qobj" type ket with the same number of elements as ``times``. ``result.average_states`` is a list of density matrices computed as the average of the states at each time step. -Furthermore, the output will also contain a list of times at which the collapse occurred, and which collapse operators did the collapse. These can be obtained in ``result.col_times`` and ``result.col_which`` respectively. +The Monte Carlo solver returns a :class:`.McResult` object consisting of +expectation values and/or states. The main difference with :func:`.mesolve`'s +:class:`.Result` is that it optionally stores the result of each trajectory +together with their averages. When trajectories are stored, ``result.runs_expect`` +is a list over the expectation operators, trajectories and times in that order. +The averages are stored in ``result.average_expect`` and the standard derivation +of the expectation values in ``result.std_expect``. When the states are returned, +``result.runs_states`` will be an array of length ``ntraj``. Each element +contains an array of "Qobj" type ket with the same number of elements as ``times``. +``result.average_states`` is a list of density matrices computed as the average +of the states at each time step. Furthermore, the output will also contain a +list of times at which the collapse occurred, and which collapse operators did +the collapse. These can be obtained in ``result.col_times`` and +``result.col_which`` respectively. Photocurrent ------------ -The photocurrent, previously computed using the ``photocurrent_sesolve`` and ``photocurrent_sesolve`` functions, are now included in the output of :func:`qutip.solver.mcsolve` as ``result.photocurrent``. +The photocurrent, previously computed using the ``photocurrent_sesolve`` and +``photocurrent_sesolve`` functions, are now included in the output of +:func:`.mcsolve` as ``result.photocurrent``. .. plot:: @@ -134,9 +197,11 @@ Changing the Number of Trajectories ----------------------------------- By default, the ``mcsolve`` function runs 500 trajectories. -This value was chosen because it gives good accuracy, Monte Carlo errors scale as :math:`1/n` where :math:`n` is the number of trajectories, and simultaneously does not take an excessive amount of time to run. -However, you can change the number of trajectories to fit your needs. -In order to run 1000 trajectories in the above example, we can simply modify the call to ``mcsolve`` like: +This value was chosen because it gives good accuracy, Monte Carlo errors scale +as :math:`1/n` where :math:`n` is the number of trajectories, and simultaneously +does not take an excessive amount of time to run. However, you can change the +number of trajectories to fit your needs. In order to run 1000 trajectories in +the above example, we can simply modify the call to ``mcsolve`` like: .. plot:: :context: close-figs @@ -144,35 +209,38 @@ In order to run 1000 trajectories in the above example, we can simply modify the data = mcsolve(H, psi0, times, [np.sqrt(0.1) * a], e_ops=[a.dag() * a, sm.dag() * sm], ntraj=1000) where we have added the keyword argument ``ntraj=1000`` at the end of the inputs. -Now, the Monte Carlo solver will calculate expectation values for both operators, ``a.dag() * a, sm.dag() * sm`` averaging over 1000 trajectories. +Now, the Monte Carlo solver will calculate expectation values for both operators, +``a.dag() * a, sm.dag() * sm`` averaging over 1000 trajectories. Using the Improved Sampling Algorithm ------------------------------------- -Oftentimes, quantum jumps are rare. This is especially true in the context of simulating gates -for quantum information purposes, where typical gate times are orders of magnitude smaller than -typical timescales for decoherence. In this case, using the standard monte-carlo sampling algorithm, -we often repeatedly sample the no-jump trajectory. We can thus reduce the number of required runs -by only sampling the no-jump trajectory once. We then extract the no-jump probability :math:`p`, -and for all future runs we only sample random numbers :math:`r_1` where :math:`r_1>p`, thus ensuring -that a jump will occur. When it comes time to compute expectation values, we weight the no-jump -trajectory by :math:`p` and the jump trajectories by :math:`1-p`. This algorithm is described -in [Abd19]_ and can be utilized by setting the option ``"improved_sampling"`` in the call to -``mcsolve``: +Oftentimes, quantum jumps are rare. This is especially true in the context of +simulating gates for quantum information purposes, where typical gate times are +orders of magnitude smaller than typical timescales for decoherence. In this case, +using the standard monte-carlo sampling algorithm, we often repeatedly sample the +no-jump trajectory. We can thus reduce the number of required runs by only +sampling the no-jump trajectory once. We then extract the no-jump probability +:math:`p`, and for all future runs we only sample random numbers :math:`r_1` +where :math:`r_1>p`, thus ensuring that a jump will occur. When it comes time to +compute expectation values, we weight the no-jump trajectory by :math:`p` and +the jump trajectories by :math:`1-p`. This algorithm is described in [Abd19]_ +and can be utilized by setting the option ``"improved_sampling"`` in the call +to ``mcsolve``: .. plot:: :context: close-figs data = mcsolve(H, psi0, times, [np.sqrt(0.1) * a], e_ops=[a.dag() * a, sm.dag() * sm], options={"improved_sampling": True}) -where in this case the first run samples the no-jump trajectory, and the remaining 499 trajectories are all -guaranteed to include (at least) one jump. +where in this case the first run samples the no-jump trajectory, and the +remaining 499 trajectories are all guaranteed to include (at least) one jump. -The power of this algorithm is most obvious when considering systems that rarely undergo jumps. -For instance, consider the following T1 simulation of a qubit with a lifetime of 10 microseconds -(assuming time is in units of nanoseconds) +The power of this algorithm is most obvious when considering systems that rarely +undergo jumps. For instance, consider the following T1 simulation of a qubit with +a lifetime of 10 microseconds (assuming time is in units of nanoseconds) .. plot:: @@ -203,8 +271,8 @@ For instance, consider the following T1 simulation of a qubit with a lifetime of plt.show() -The original sampling algorithm samples the no-jump trajectory on average 96.7% of the time, while the improved -sampling algorithm only does so once. +The original sampling algorithm samples the no-jump trajectory on average 96.7% +of the time, while the improved sampling algorithm only does so once. .. _monte-reuse: @@ -215,12 +283,17 @@ Reusing Hamiltonian Data .. note:: This section covers a specialized topic and may be skipped if you are new to QuTiP. -In order to solve a given simulation as fast as possible, the solvers in QuTiP take the given input operators and break them down into simpler components before passing them on to the ODE solvers. -Although these operations are reasonably fast, the time spent organizing data can become appreciable when repeatedly solving a system over, for example, many different initial conditions. -In cases such as this, the Monte Carlo Solver may be reused after the initial configuration, thus speeding up calculations. +In order to solve a given simulation as fast as possible, the solvers in QuTiP +take the given input operators and break them down into simpler components before +passing them on to the ODE solvers. Although these operations are reasonably fast, +the time spent organizing data can become appreciable when repeatedly solving a +system over, for example, many different initial conditions. In cases such as +this, the Monte Carlo Solver may be reused after the initial configuration, thus +speeding up calculations. -Using the previous example, we will calculate the dynamics for two different initial states, with the Hamiltonian data being reused on the second call +Using the previous example, we will calculate the dynamics for two different +initial states, with the Hamiltonian data being reused on the second call .. plot:: :context: close-figs @@ -247,7 +320,9 @@ Using the previous example, we will calculate the dynamics for two different ini .. guide-dynamics-mc2: -The ``MCSolver`` also allows adding new trajectories after the first computation. This is shown in the next example where the results of two separated runs with identical conditions are merged into a single ``result`` object. +The ``MCSolver`` also allows adding new trajectories after the first computation. +This is shown in the next example where the results of two separated runs with +identical conditions are merged into a single ``result`` object. .. plot:: :context: close-figs @@ -275,7 +350,8 @@ The ``MCSolver`` also allows adding new trajectories after the first computation This can be used to explore the convergence of the Monte Carlo solver. -For example, the following code block plots expectation values for 1, 10 and 100 trajectories: +For example, the following code block plots expectation values for 1, 10 and 100 +trajectories: .. plot:: :context: close-figs @@ -305,8 +381,9 @@ For example, the following code block plots expectation values for 1, 10 and 100 Open Systems ------------ -``mcsolve`` can be used to study system with have measured and dissipative interaction with the bath. -This is done by using a liouvillian including the dissipative interaction instead of an Hamiltonian. +``mcsolve`` can be used to study system with have measured and dissipative +interaction with the bath. This is done by using a liouvillian including the +dissipative interaction instead of an Hamiltonian. .. plot:: :context: close-figs @@ -332,7 +409,9 @@ This is done by using a liouvillian including the dissipative interaction instea Monte Carlo for Non-Markovian Dynamics -------------------------------------- -The Monte Carlo solver of QuTiP can also be used to solve the dynamics of time-local non-Markovian master equations, i.e., master equations of the Lindblad form +The Monte Carlo solver of QuTiP can also be used to solve the dynamics of +time-local non-Markovian master equations, i.e., master equations of the Lindblad +form .. math:: :label: lindblad_master_equation_with_rates @@ -340,21 +419,26 @@ The Monte Carlo solver of QuTiP can also be used to solve the dynamics of time-l \dot\rho(t) = -\frac{i}{\hbar} [H, \rho(t)] + \sum_n \frac{\gamma_n(t)}{2} \left[2 A_n \rho(t) A_n^\dagger - \rho(t) A_n^\dagger A_n - A_n^\dagger A_n \rho(t)\right] with "rates" :math:`\gamma_n(t)` that can take negative values. -This can be done with the :func:`qutip.nm_mcsolve` function. -The function is based on the influence martingale formalism [Donvil22]_ and formally requires that the collapse operators :math:`A_n` satisfy a completeness relation of the form +This can be done with the :func:`.nm_mcsolve` function. +The function is based on the influence martingale formalism [Donvil22]_ and +formally requires that the collapse operators :math:`A_n` satisfy a completeness +relation of the form .. math:: :label: nmmcsolve_completeness \sum_n A_n^\dagger A_n = \alpha \mathbb{I} , -where :math:`\mathbb{I}` is the identity operator on the system Hilbert space and :math:`\alpha>0`. -Note that when the collapse operators of a model don't satisfy such a relation, ``qutip.nm_mcsolve`` automatically adds an extra collapse operator such that :eq:`nmmcsolve_completeness` is satisfied. +where :math:`\mathbb{I}` is the identity operator on the system Hilbert space +and :math:`\alpha>0`. +Note that when the collapse operators of a model don't satisfy such a relation, +``.nm_mcsolve`` automatically adds an extra collapse operator such that +:eq:`nmmcsolve_completeness` is satisfied. The rate corresponding to this extra collapse operator is set to zero. Technically, the influence martingale formalism works as follows. -We introduce an influence martingale :math:`\mu(t)`, which follows the evolution of the system state. -When no jump happens, it evolves as +We introduce an influence martingale :math:`\mu(t)`, which follows the evolution +of the system state. When no jump happens, it evolves as .. math:: :label: influence_cont @@ -362,7 +446,8 @@ When no jump happens, it evolves as \mu(t) = \exp\left( \alpha\int_0^t K(\tau) d\tau \right) where :math:`K(t)` is for now an arbitrary function. -When a jump corresponding to the collapse operator :math:`A_n` happens, the influence martingale becomes +When a jump corresponding to the collapse operator :math:`A_n` happens, the +influence martingale becomes .. math:: :label: influence_disc @@ -376,33 +461,37 @@ Assuming that the state :math:`\bar\rho(t)` computed by the Monte Carlo average \bar\rho(t) = \frac{1}{N}\sum_{l=1}^N |\psi_l(t)\rangle\langle \psi_l(t)| -solves a Lindblad master equation with collapse operators :math:`A_n` and rates :math:`\Gamma_n(t)`, the state :math:`\rho(t)` defined by +solves a Lindblad master equation with collapse operators :math:`A_n` and rates +:math:`\Gamma_n(t)`, the state :math:`\rho(t)` defined by .. math:: :label: mc_martingale_state \rho(t) = \frac{1}{N}\sum_{l=1}^N \mu_l(t) |\psi_l(t)\rangle\langle \psi_l(t)| -solves a Lindblad master equation with collapse operators :math:`A_n` and shifted rates :math:`\gamma_n(t)-K(t)`. -Thus, while :math:`\Gamma_n(t) \geq 0`, the new "rates" :math:`\gamma_n(t) = \Gamma_n(t) - K(t)` satisfy no positivity requirement. +solves a Lindblad master equation with collapse operators :math:`A_n` and shifted +rates :math:`\gamma_n(t)-K(t)`. Thus, while :math:`\Gamma_n(t) \geq 0`, the new +"rates" :math:`\gamma_n(t) = \Gamma_n(t) - K(t)` satisfy no positivity requirement. -The input of :func:`qutip.nm_mcsolve` is almost the same as for :func:`qutip.mcsolve`. -The only difference is how the collapse operators and rate functions should be defined. -``nm_mcsolve`` requires collapse operators :math:`A_n` and target "rates" :math:`\gamma_n` (which are allowed to take negative values) to be given in list form ``[[C_1, gamma_1], [C_2, gamma_2], ...]``. -Note that we give the actual rate and not its square root, and that ``nm_mcsolve`` automatically computes associated jump rates :math:`\Gamma_n(t)\geq0` appropriate for simulation. +The input of :func:`.nm_mcsolve` is almost the same as for :func:`.mcsolve`. +The only difference is how the collapse operators and rate functions should be +defined. ``nm_mcsolve`` requires collapse operators :math:`A_n` and target "rates" +:math:`\gamma_n` (which are allowed to take negative values) to be given in list +form ``[[C_1, gamma_1], [C_2, gamma_2], ...]``. Note that we give the actual +rate and not its square root, and that ``nm_mcsolve`` automatically computes +associated jump rates :math:`\Gamma_n(t)\geq0` appropriate for simulation. -We conclude with a simple example demonstrating the usage of the ``nm_mcsolve`` function. -For more elaborate, physically motivated examples, we refer to the `accompanying tutorial notebook `_. +We conclude with a simple example demonstrating the usage of the ``nm_mcsolve`` +function. For more elaborate, physically motivated examples, we refer to the +`accompanying tutorial notebook `_. .. plot:: :context: reset - import qutip as qt - times = np.linspace(0, 1, 201) - psi0 = qt.basis(2, 1) - a0 = qt.destroy(2) + psi0 = basis(2, 1) + a0 = destroy(2) H = a0.dag() * a0 # Rate functions @@ -414,16 +503,16 @@ For more elaborate, physically motivated examples, we refer to the `accompanying ops_and_rates = [] ops_and_rates.append([a0.dag(), gamma1]) ops_and_rates.append([a0, gamma2]) - MCSol = qt.nm_mcsolve(H, psi0, times, ops_and_rates, - args={'kappa': 1.0 / 0.129, 'nth': 0.063}, - e_ops=[a0.dag() * a0, a0 * a0.dag()], - options={'map': 'parallel'}, ntraj=2500) + MCSol = nm_mcsolve(H, psi0, times, ops_and_rates, + args={'kappa': 1.0 / 0.129, 'nth': 0.063}, + e_ops=[a0.dag() * a0, a0 * a0.dag()], + options={'map': 'parallel'}, ntraj=2500) # mesolve integration for comparison - d_ops = [[qt.lindblad_dissipator(a0.dag(), a0.dag()), gamma1], - [qt.lindblad_dissipator(a0, a0), gamma2]] - MESol = qt.mesolve(H, psi0, times, d_ops, e_ops=[a0.dag() * a0, a0 * a0.dag()], - args={'kappa': 1.0 / 0.129, 'nth': 0.063}) + d_ops = [[lindblad_dissipator(a0.dag(), a0.dag()), gamma1], + [lindblad_dissipator(a0, a0), gamma2]] + MESol = mesolve(H, psi0, times, d_ops, e_ops=[a0.dag() * a0, a0 * a0.dag()], + args={'kappa': 1.0 / 0.129, 'nth': 0.063}) plt.figure() plt.plot(times, MCSol.expect[0], 'g', diff --git a/qutip/solver/nonmarkov/transfertensor.py b/qutip/solver/nonmarkov/transfertensor.py index 2c87b9dd92..ce8bea512f 100644 --- a/qutip/solver/nonmarkov/transfertensor.py +++ b/qutip/solver/nonmarkov/transfertensor.py @@ -15,7 +15,7 @@ from qutip import spre, vector_to_operator, operator_to_vector, Result -def ttmsolve(dynmaps, state0, times, e_ops=[], num_learning=0, options=None): +def ttmsolve(dynmaps, state0, times, e_ops=(), num_learning=0, options=None): """ Expand time-evolution using the Transfer Tensor Method [1]_, based on a set of precomputed dynamical maps. From a93c7d51d29155f0c0fa73880f45197664a350de Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 17 Nov 2023 10:42:54 -0500 Subject: [PATCH 093/247] start removing Qobj(type) --- doc/apidoc/functions.rst | 2 +- qutip/core/cy/qobjevo.pyx | 4 +-- qutip/core/dimensions.py | 63 ++++++++++++++++----------------- qutip/core/energy_restricted.py | 2 +- qutip/core/operators.py | 22 ++++++------ qutip/core/qobj.py | 23 ++++-------- qutip/core/states.py | 6 +--- 7 files changed, 53 insertions(+), 69 deletions(-) diff --git a/doc/apidoc/functions.rst b/doc/apidoc/functions.rst index 5f2487c080..2d5bf6c725 100644 --- a/doc/apidoc/functions.rst +++ b/doc/apidoc/functions.rst @@ -61,7 +61,7 @@ Operators and Superoperator Dimensions -------------------------------------- .. automodule:: qutip.core.dimensions - :members: type_from_dims, flatten, deep_remove, unflatten, collapse_dims_oper, collapse_dims_super, enumerate_flat, deep_map, dims_to_tensor_perm, dims_to_tensor_shape, dims_idxs_to_tensor_idxs + :members: flatten, deep_remove, unflatten, collapse_dims_oper, collapse_dims_super, enumerate_flat, deep_map, dims_to_tensor_perm, dims_to_tensor_shape, dims_idxs_to_tensor_idxs Functions acting on states and operators diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 07321fa157..9ebfa42f7a 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -488,7 +488,7 @@ cdef class QobjEvo: str(left.dims[1]) + ", " + str(( right).dims[0])) res = right.copy() - res._dims = Dimensions((left._dims[0], right._dims[1])) + res._dims = Dimensions(left._dims[0], right._dims[1]) res.shape = (left.shape[0], right.shape[1]) left = _ConstantElement(left) res.elements = [left @ element for element in res.elements] @@ -811,7 +811,7 @@ cdef class QobjEvo: @property def superrep(self): - return self._dims.superrep or None + return self._dims.superrep ########################################################################### # operation methods # diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index 67ad214b29..669f133b90 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -13,35 +13,6 @@ __all__ = ["to_tensor_rep", "from_tensor_rep", "Space", "Dimensions"] -def type_from_dims(dims, enforce_square=False): - bra_like, ket_like = map(is_scalar, dims) - - if bra_like: - if is_vector(dims[1]): - return 'bra' - elif is_vectorized_oper(dims[1]): - return 'operator-bra' - - if ket_like: - if is_vector(dims[0]): - return 'ket' - elif is_vectorized_oper(dims[0]): - return 'operator-ket' - - elif is_vector(dims[0]) and (dims[0] == dims[1] or not enforce_square): - return 'oper' - - elif ( - is_vectorized_oper(dims[0]) - and ( - not enforce_square - or (dims[0] == dims[1] and dims[0][0] == dims[1][0]) - ) - ): - return 'super' - return 'other' - - def flatten(l): """Flattens a list of lists to the first level. @@ -493,6 +464,9 @@ def replace(self, idx, new): ) return Space(new) + def replace_superrep(self, super_rep): + return self + class Field(Space): field_instance = None @@ -565,7 +539,7 @@ def __init__(self, *spaces): def __eq__(self, other): return self is other or ( - type(other) is type(self) + type(other) is type(self) and self.spaces == other.spaces ) @@ -629,6 +603,11 @@ def replace(self, idx, new): idx -= n_indices return Compound(*new_spaces) + def replace_superrep(self, super_rep): + return Compound( + *[space.replace_superrep(super_rep) for space in self.spaces] + ) + class SuperSpace(Space): _stored_dims = {} @@ -687,17 +666,21 @@ def remove(self, idx): def replace(self, idx, new): return SuperSpace(self.oper.replace(idx, new), rep=self.superrep) + def replace_superrep(self, super_rep): + return SuperSpace(self.oper, rep=super_rep) + class MetaDims(type): def __call__(cls, *args, rep=None): if len(args) == 1 and isinstance(args[0], Dimensions): return args[0] - elif len(args) == 1 and isinstance(args[0], list): + elif len(args) == 1 and len(args[0]) == 2: args = ( Space(args[0][1], rep=rep), Space(args[0][0], rep=rep) ) elif len(args) != 2: + print(args) raise NotImplementedError('No Dual, Ket, Bra...', args) elif ( settings.core["auto_tidyup_dims"] @@ -720,13 +703,19 @@ def __init__(self, from_, to_): self.from_ = from_ self.to_ = to_ self.shape = to_.size, from_.size - self.issuper = from_.issuper or to_.issuper + if from_.issuper != to_.issuper: + raise NotImplementedError( + "Operator with both space and superspace dimensions are not " + "supported. Please open an issue if you have an use case for " + "these." + ) + self.issuper = from_.issuper self._pure_dims = from_._pure_dims and to_._pure_dims self.issquare = False if self.from_.size == 1 and self.to_.size == 1: self.type = 'scalar' self.issquare = True - self.superrep = "" + self.superrep = None elif self.from_.size == 1: self.type = 'operator-ket' if self.issuper else 'ket' self.superrep = self.to_.superrep @@ -864,3 +853,11 @@ def replace(self, idx, new): new_from = self.from_.replace(idx-n_indices, new) return Dimensions(new_from, new_to) + + def replace_superrep(self, super_rep): + if not self.issuper and super_rep is not None: + raise TypeError("Can't set a superrep of a non super object.") + return Dimensions( + self.from_.replace_superrep(super_rep), + self.to_.replace_superrep(super_rep) + ) diff --git a/qutip/core/energy_restricted.py b/qutip/core/energy_restricted.py index 3c6405c349..2ffe6298c9 100644 --- a/qutip/core/energy_restricted.py +++ b/qutip/core/energy_restricted.py @@ -53,7 +53,7 @@ def __init__(self, dims, excitations): enr_dicts = enr_state_dictionaries(dims, excitations) self.size, self.state2idx, self.idx2state = enr_dicts self.issuper = False - self.superrep = "" + self.superrep = None self._pure_dims = False def __eq__(self, other): diff --git a/qutip/core/operators.py b/qutip/core/operators.py index d1cbb2dcce..6e4c6d8b04 100644 --- a/qutip/core/operators.py +++ b/qutip/core/operators.py @@ -65,7 +65,7 @@ def qdiags(diagonals, offsets=None, dims=None, shape=None, *, dtype = dtype or settings.core["default_dtype"] or _data.Dia offsets = [0] if offsets is None else offsets data = _data.diag[dtype](diagonals, offsets, shape) - return Qobj(data, dims=dims, type='oper', copy=False) + return Qobj(data, dims=dims, copy=False) def jmat(j, which=None, *, dtype=None): @@ -130,21 +130,21 @@ def jmat(j, which=None, *, dtype=None): dims = [[int(2*j + 1)]]*2 if which == '+': - return Qobj(_jplus(j, dtype=dtype), dims=dims, type='oper', + return Qobj(_jplus(j, dtype=dtype), dims=dims, isherm=False, isunitary=False, copy=False) if which == '-': - return Qobj(_jplus(j, dtype=dtype).adjoint(), dims=dims, type='oper', + return Qobj(_jplus(j, dtype=dtype).adjoint(), dims=dims, isherm=False, isunitary=False, copy=False) if which == 'x': A = _jplus(j, dtype=dtype) - return Qobj(_data.add(A, A.adjoint()), dims=dims, type='oper', + return Qobj(_data.add(A, A.adjoint()), dims=dims, isherm=True, isunitary=False, copy=False) * 0.5 if which == 'y': A = _data.mul(_jplus(j, dtype=dtype), -0.5j) - return Qobj(_data.add(A, A.adjoint()), dims=dims, type='oper', + return Qobj(_data.add(A, A.adjoint()), dims=dims, isherm=True, isunitary=False, copy=False) if which == 'z': - return Qobj(_jz(j, dtype=dtype), dims=dims, type='oper', + return Qobj(_jz(j, dtype=dtype), dims=dims, isherm=True, isunitary=False, copy=False) raise ValueError('Invalid spin operator: ' + which) @@ -669,7 +669,7 @@ def qzero(dimensions, *, dtype=None): size, dimensions = _implicit_tensor_dimensions(dimensions) # A sparse matrix with no data is equal to a zero matrix. type_ = 'super' if isinstance(dimensions[0][0], list) else 'oper' - return Qobj(_data.zeros[dtype](size, size), dims=dimensions, type=type_, + return Qobj(_data.zeros[dtype](size, size), dims=dimensions, isherm=True, isunitary=False, copy=False) @@ -692,7 +692,7 @@ def qzero_like(qobj): if isinstance(qobj, QobjEvo): qobj = qobj(0) return Qobj( - _data.zeros_like(qobj.data), dims=qobj.dims, type=qobj.type, + _data.zeros_like(qobj.data), dims=qobj.dims, superrep=qobj.superrep, isherm=True, isunitary=False, copy=False ) @@ -740,7 +740,7 @@ def qeye(dimensions, *, dtype=None): dtype = dtype or settings.core["default_dtype"] or _data.Dia size, dimensions = _implicit_tensor_dimensions(dimensions) type_ = 'super' if isinstance(dimensions[0][0], list) else 'oper' - return Qobj(_data.identity[dtype](size), dims=dimensions, type=type_, + return Qobj(_data.identity[dtype](size), dims=dimensions, isherm=True, isunitary=True, copy=False) @@ -768,7 +768,7 @@ def qeye_like(qobj): if isinstance(qobj, QobjEvo): qobj = qobj(0) return Qobj( - _data.identity_like(qobj.data), dims=qobj.dims, type=qobj.type, + _data.identity_like(qobj.data), dims=qobj.dims, superrep=qobj.superrep, isherm=True, isunitary=True, copy=False ) @@ -1055,7 +1055,7 @@ def phase(N, phi0=0, *, dtype=None): states = np.array([np.sqrt(kk) / np.sqrt(N) * np.exp(1j * n * kk) for kk in phim]) ops = np.sum([np.outer(st, st.conj()) for st in states], axis=0) - return Qobj(ops, dims=[[N], [N]], type='oper', copy=False).to(dtype) + return Qobj(ops, dims=[[N], [N]], copy=False).to(dtype) def charge(Nmax, Nmin=None, frac=1, *, dtype=None): diff --git a/qutip/core/qobj.py b/qutip/core/qobj.py index eb0b714bac..f1ef95dbd5 100644 --- a/qutip/core/qobj.py +++ b/qutip/core/qobj.py @@ -299,13 +299,14 @@ def _initialize_data(self, arg, dims, copy): raise ValueError('Provided dimensions do not match the data: ' + f"{self._dims.shape} vs {self._data.shape}") - def __init__(self, arg=None, dims=None, type=None, + def __init__(self, arg=None, dims=None, # type=None, copy=True, superrep=None, isherm=None, isunitary=None): self._isherm = isherm self._isunitary = isunitary self._initialize_data(arg, dims, copy) # Dims are guessed from the data and need to be changed to super. + """ if ( type in ['super', 'operator-ket', 'operator-bra'] and self._dims.type in ['oper', 'ket', 'bra'] @@ -320,8 +321,9 @@ def __init__(self, arg=None, dims=None, type=None, "cannot build superoperator from nonsquare subspaces" ) self.dims = [[[root_right]]*2, [[root_left]]*2] + """ - self.type = type + # self.type = type if superrep is not None: self.superrep = superrep @@ -348,10 +350,11 @@ def dims(self, dims): @property def type(self): - return self._type + return self._dims.type # self._type @type.setter def type(self, val): + raise TypeError("Type does not match dimensions.") if not val: self._type = self._dims.type elif self._dims.type == "scalar": @@ -364,22 +367,10 @@ def type(self, val): @property def superrep(self): return self._dims.superrep - if ( - self._dims - and self._dims.type in ['super', 'operator-ket', 'operator-bra'] - ): - return self._dims.superrep - else: - return None @superrep.setter def superrep(self, super_rep): - if ( - not self._dims.type in ['super', 'operator-ket', 'operator-bra'] - and super_rep - ): - raise TypeError("The Qobj does not ahve a superrep") - self._dims = Dimensions(self._dims.as_list(), rep=super_rep) + self._dims = self._dims.replace_superrep(super_rep) @property def data(self): diff --git a/qutip/core/states.py b/qutip/core/states.py index 7f11f47048..1a259f866a 100644 --- a/qutip/core/states.py +++ b/qutip/core/states.py @@ -124,7 +124,6 @@ def basis(dimensions, n=None, offset=None, *, dtype=None): data = _data.one_element[dtype]((size, 1), (location, 0), 1) return Qobj(data, dims=[dimensions, [1]*n_dimensions], - type='ket', isherm=False, isunitary=False, copy=False) @@ -234,10 +233,7 @@ def coherent(N, alpha, offset=0, method=None, *, dtype=None): s = np.prod(np.sqrt(np.arange(1, offset + 1))) # sqrt factorial data[0] = np.exp(-abs(alpha)**2 * 0.5) * alpha**offset / s np.cumprod(data, out=sqrtn) # Reuse sqrtn array - return Qobj(sqrtn, - dims=[[N], [1]], - type='ket', - copy=False).to(dtype) + return Qobj(sqrtn, dims=[[N], [1]], copy=False).to(dtype) raise TypeError( "The method option can only take values in " + repr(_COHERENT_METHODS) ) From 5c73919f08e705db3b735dcea870e218a8402fb1 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 17 Nov 2023 10:43:54 -0500 Subject: [PATCH 094/247] start removing Qobj(type) --- qutip/core/states.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qutip/core/states.py b/qutip/core/states.py index 1a259f866a..8d18d43f91 100644 --- a/qutip/core/states.py +++ b/qutip/core/states.py @@ -486,7 +486,7 @@ def maximally_mixed_dm(N, *, dtype=None): if not isinstance(N, numbers.Integral) or N <= 0: raise ValueError("N must be integer N > 0") return Qobj(_data.identity[dtype](N, scale=1/N), dims=[[N], [N]], - type='oper', isherm=True, isunitary=(N == 1), copy=False) + isherm=True, isunitary=(N == 1), copy=False) def ket2dm(Q): @@ -954,7 +954,7 @@ def phase_basis(N, m, phi0=0, *, dtype=None): phim = phi0 + (2.0 * np.pi * m) / N n = np.arange(N)[:, np.newaxis] data = np.exp(1.0j * n * phim) / np.sqrt(N) - return Qobj(data, dims=[[N], [1]], type='ket', copy=False).to(dtype) + return Qobj(data, dims=[[N], [1]], copy=False).to(dtype) def zero_ket(N, dims=None, *, dtype=None): @@ -980,7 +980,7 @@ def zero_ket(N, dims=None, *, dtype=None): """ dtype = dtype or settings.core["default_dtype"] or _data.Dense - return Qobj(_data.zeros[dtype](N, 1), dims=dims, type='ket', copy=False) + return Qobj(_data.zeros[dtype](N, 1), dims=dims, copy=False) def spin_state(j, m, type='ket', *, dtype=None): From 7aa4bf0ddf8faf7a8a087fa875f1ba984a7265f4 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 17 Nov 2023 15:43:34 -0500 Subject: [PATCH 095/247] Remove type setting --- qutip/core/_brtensor.pyx | 3 +- qutip/core/cy/qobjevo.pyx | 1 - qutip/core/dimensions.py | 18 +++++----- qutip/core/energy_restricted.py | 2 +- qutip/core/operators.py | 8 ++--- qutip/core/qobj.py | 49 ++++++++++++++------------- qutip/core/superop_reps.py | 10 +----- qutip/core/superoperator.py | 7 +--- qutip/core/tensor.py | 13 ++++--- qutip/random_objects.py | 40 ++++++++-------------- qutip/solver/floquet.py | 6 ++-- qutip/solver/propagator.py | 1 - qutip/solver/solver_base.py | 1 - qutip/tests/core/test_operators.py | 2 +- qutip/tests/core/test_ptrace.py | 2 +- qutip/tests/core/test_superop_reps.py | 8 ++--- qutip/tests/test_random.py | 2 +- 17 files changed, 73 insertions(+), 100 deletions(-) diff --git a/qutip/core/_brtensor.pyx b/qutip/core/_brtensor.pyx index 530cf63d1b..984e016ede 100644 --- a/qutip/core/_brtensor.pyx +++ b/qutip/core/_brtensor.pyx @@ -266,8 +266,7 @@ cdef class _BlochRedfieldElement(_BaseElement): raise ValueError('Invalid tensortype') cpdef object qobj(self, t): - return Qobj(self.data(t), dims=self.dims, type="super", - copy=False, superrep="super") + return Qobj(self.data(t), dims=self.dims, copy=False, superrep="super") cpdef object coeff(self, t): return 1. diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 9ebfa42f7a..56d9071baa 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -355,7 +355,6 @@ cdef class QobjEvo: return Qobj( out, dims=self._dims, copy=False, isherm=isherm or None, - type=self.type, superrep=self.superrep ) cpdef Data _call(QobjEvo self, double t): diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index 669f133b90..955b1af277 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -680,7 +680,6 @@ def __call__(cls, *args, rep=None): Space(args[0][0], rep=rep) ) elif len(args) != 2: - print(args) raise NotImplementedError('No Dual, Ket, Bra...', args) elif ( settings.core["auto_tidyup_dims"] @@ -703,12 +702,6 @@ def __init__(self, from_, to_): self.from_ = from_ self.to_ = to_ self.shape = to_.size, from_.size - if from_.issuper != to_.issuper: - raise NotImplementedError( - "Operator with both space and superspace dimensions are not " - "supported. Please open an issue if you have an use case for " - "these." - ) self.issuper = from_.issuper self._pure_dims = from_._pure_dims and to_._pure_dims self.issquare = False @@ -717,17 +710,26 @@ def __init__(self, from_, to_): self.issquare = True self.superrep = None elif self.from_.size == 1: + self.issuper = self.to_.issuper self.type = 'operator-ket' if self.issuper else 'ket' self.superrep = self.to_.superrep elif self.to_.size == 1: + self.issuper = self.from_.issuper self.type = 'operator-bra' if self.issuper else 'bra' self.superrep = self.from_.superrep elif self.from_ == self.to_: + self.issuper = self.from_.issuper self.type = 'super' if self.issuper else 'oper' self.superrep = self.from_.superrep self.issquare = True else: - self.type = 'super' if self.issuper else 'oper' + if from_.issuper != to_.issuper: + raise NotImplementedError( + "Operator with both space and superspace dimensions are not " + "supported. Please open an issue if you have an use case for " + f"these: {from_}, {to_}]" + ) + self.type = 'super' if self.from_.issuper else 'oper' if self.from_.superrep == self.to_.superrep: self.superrep = self.from_.superrep else: diff --git a/qutip/core/energy_restricted.py b/qutip/core/energy_restricted.py index 2ffe6298c9..d3af0696ad 100644 --- a/qutip/core/energy_restricted.py +++ b/qutip/core/energy_restricted.py @@ -131,7 +131,7 @@ def enr_fock(dims, excitations, state, *, dtype=None): ) raise ValueError(msg) from None return Qobj(data, dims=[EnrSpace(dims, excitations), [1]*len(dims)], - type='ket', copy=False) + copy=False) def enr_thermal_dm(dims, excitations, n, *, dtype=None): diff --git a/qutip/core/operators.py b/qutip/core/operators.py index 6e4c6d8b04..f4ba5c5599 100644 --- a/qutip/core/operators.py +++ b/qutip/core/operators.py @@ -692,8 +692,8 @@ def qzero_like(qobj): if isinstance(qobj, QobjEvo): qobj = qobj(0) return Qobj( - _data.zeros_like(qobj.data), dims=qobj.dims, - superrep=qobj.superrep, isherm=True, isunitary=False, copy=False + _data.zeros_like(qobj.data), dims=qobj._dims, + isherm=True, isunitary=False, copy=False ) @@ -768,8 +768,8 @@ def qeye_like(qobj): if isinstance(qobj, QobjEvo): qobj = qobj(0) return Qobj( - _data.identity_like(qobj.data), dims=qobj.dims, - superrep=qobj.superrep, isherm=True, isunitary=True, copy=False + _data.identity_like(qobj.data), dims=qobj._dims, + isherm=True, isunitary=True, copy=False ) diff --git a/qutip/core/qobj.py b/qutip/core/qobj.py index f1ef95dbd5..2871cb65a8 100644 --- a/qutip/core/qobj.py +++ b/qutip/core/qobj.py @@ -82,7 +82,7 @@ def _require_equal_type(method): @functools.wraps(method) def out(self, other): if other == 0: - return self.copy() + return method(self, other) if ( self.type in ('oper', 'super') and self._dims[0] == self._dims[1] @@ -97,7 +97,7 @@ def out(self, other): copy=False) if not isinstance(other, Qobj): try: - other = Qobj(other, type=self.type) + other = Qobj(other, dims=self._dims) except TypeError: return NotImplemented if self._dims != other._dims: @@ -299,13 +299,17 @@ def _initialize_data(self, arg, dims, copy): raise ValueError('Provided dimensions do not match the data: ' + f"{self._dims.shape} vs {self._data.shape}") - def __init__(self, arg=None, dims=None, # type=None, + def __init__(self, arg=None, dims=None, type=None, copy=True, superrep=None, isherm=None, isunitary=None): self._isherm = isherm self._isunitary = isunitary self._initialize_data(arg, dims, copy) # Dims are guessed from the data and need to be changed to super. + + if type is not None: + raise Exception(type, self._dims.type) + """ if ( type in ['super', 'operator-ket', 'operator-bra'] @@ -346,7 +350,7 @@ def dims(self, dims): raise ValueError('Provided dimensions do not match the data: ' + f"{dims.shape} vs {self._data.shape}") self._dims = dims - self.type = self._dims.type + # self.type = self._dims.type @property def type(self): @@ -354,7 +358,9 @@ def type(self): @type.setter def type(self, val): - raise TypeError("Type does not match dimensions.") + if self._dims.type != val: + raise TypeError(f"Tried to set type {self._dims.type} to {val}") + """ if not val: self._type = self._dims.type elif self._dims.type == "scalar": @@ -363,6 +369,7 @@ def type(self, val): self._type = val else: raise TypeError("Type does not match dimensions.") + """ @property def superrep(self): @@ -428,11 +435,11 @@ def to(self, data_type): @_require_equal_type def __add__(self, other): - isherm = (self._isherm and other._isherm) or None + if other == 0: + return self.copy() return Qobj(_data.add(self._data, other._data), dims=self._dims, - superrep=self.superrep, - isherm=isherm, + isherm=(self._isherm and other._isherm) or None, copy=False) def __radd__(self, other): @@ -440,11 +447,11 @@ def __radd__(self, other): @_require_equal_type def __sub__(self, other): - isherm = (self._isherm and other._isherm) or None + if other == 0: + return self.copy() return Qobj(_data.sub(self._data, other._data), dims=self._dims, - superrep=self.superrep, - isherm=isherm, + isherm=(self._isherm and other._isherm) or None, copy=False) def __rsub__(self, other): @@ -558,9 +565,7 @@ def __pow__(self, n, m=None): # calculates powers of Qobj ): return NotImplemented return Qobj(_data.pow(self._data, n), - dims=self.dims, - type=self.type, - superrep=self.superrep, + dims=self._dims, isherm=self._isherm, isunitary=self._isunitary, copy=False) @@ -876,7 +881,7 @@ def expm(self, dtype=_data.Dense): TypeError Quantum operator is not square. """ - if self._dims[0] != self._dims[1]: + if not self._dims.issquare: raise TypeError("expm is only valid for square operators") return Qobj(_data.expm(self._data, dtype=dtype), dims=self._dims, @@ -898,12 +903,10 @@ def logm(self): TypeError Quantum operator is not square. """ - if self.dims[0] != self.dims[1]: + if not self._dims.issquare: raise TypeError("expm is only valid for square operators") return Qobj(_data.logm(self._data), - dims=self.dims, - type=self.type, - superrep=self.superrep, + dims=self._dims, isherm=self._isherm, copy=False) @@ -1136,7 +1139,7 @@ def ptrace(self, sel, dtype=None): dims = flatten(dims[0]) new_data = _data.ptrace(data, dims, sel, dtype=dtype) new_dims = [[dims[x] for x in sel]] * 2 if sel else None - out = Qobj(new_data, dims=new_dims, type='oper', copy=False) + out = Qobj(new_data, dims=new_dims, copy=False) if self.isoperket: return operator_to_vector(out) if self.isoperbra: @@ -1192,7 +1195,7 @@ def contract(self, inplace=False): if inplace: self.dims = dims return self - return Qobj(self.data.copy(), dims=dims, type=self.type, copy=False) + return Qobj(self.data.copy(), dims=dims, copy=False) def permute(self, order): """ @@ -1548,12 +1551,10 @@ def eigenstates(self, sparse=False, sort='low', eigvals=0, if self.type == 'super': new_dims = [self.dims[0], [1]] - new_type = 'operator-ket' else: new_dims = [self.dims[0], [1]*len(self.dims[0])] - new_type = 'ket' ekets = np.empty((evecs.shape[1],), dtype=object) - ekets[:] = [Qobj(vec, dims=new_dims, type=new_type, copy=False) + ekets[:] = [Qobj(vec, dims=new_dims, copy=False) for vec in _data.split_columns(evecs, False)] norms = np.array([ket.norm() for ket in ekets]) if phase_fix is None: diff --git a/qutip/core/superop_reps.py b/qutip/core/superop_reps.py index f22ed73595..4654b364fb 100644 --- a/qutip/core/superop_reps.py +++ b/qutip/core/superop_reps.py @@ -59,7 +59,6 @@ def _superpauli_basis(nq=1): sci.indptr[-1] = nnz return Qobj(data.adjoint(), dims=dims, - type='super', superrep='super', isherm=False, isunitary=False, @@ -132,7 +131,7 @@ def _choi_to_kraus(q_oper, tol=1e-9): dims = [q_oper.dims[0][1], q_oper.dims[0][0]] shape = (np.prod(q_oper.dims[0][1]), np.prod(q_oper.dims[0][0])) return [Qobj(_data.mul(unstack_columns(vec.data, shape=shape), np.sqrt(val)), - dims=dims, type='oper', copy='False') + dims=dims, copy='False') for val, vec in zip(vals, vecs) if abs(val) >= tol] @@ -154,7 +153,6 @@ def kraus_to_choi(kraus_list): ) return Qobj(np.hstack(np.hstack(choi_blocks)), dims=[kraus_list[0].dims[::-1]]*2, - type='super', superrep='choi', copy=False) @@ -186,7 +184,6 @@ def _super_tofrom_choi(q_oper): data = data.reshape([s0, s1, s0, s1]).transpose(3, 1, 2, 0).reshape(d0, d1) return Qobj(data, dims=new_dims, - type='super', superrep='super' if q_oper.superrep == 'choi' else 'choi', copy=False) @@ -202,7 +199,6 @@ def _choi_to_chi(q_oper): B = _superpauli_basis(nq).data return Qobj(_data.matmul(_data.matmul(B.adjoint(), q_oper.data), B), dims=q_oper.dims, - type='super', superrep='chi', copy=False) @@ -223,7 +219,6 @@ def _chi_to_choi(q_oper): 1 / q_oper.shape[0] ), dims=q_oper.dims, - type='super', superrep='choi', copy=False) @@ -244,7 +239,6 @@ def _svd_u_to_kraus(U, S, d, dK, indims, outdims): return [ Qobj(x, dims=[outdims, indims], - type='oper', copy=False) for x in data ] @@ -308,13 +302,11 @@ def _choi_to_stinespring(q_oper, threshold=1e-10): A = Qobj(_data.zeros(dK * dL, dL), dims=[out_left + [dK], out_right + [1]], - type='oper', isherm=True, isunitary=False, copy=False) B = Qobj(_data.zeros(dK * dR, dR), dims=[in_left + [dK], in_right + [1]], - type='oper', isherm=True, isunitary=False, copy=False) diff --git a/qutip/core/superoperator.py b/qutip/core/superoperator.py index 6543f210e2..7ba6cb09be 100644 --- a/qutip/core/superoperator.py +++ b/qutip/core/superoperator.py @@ -116,7 +116,6 @@ def liouvillian(H=None, c_ops=None, data_only=False, chi=None): else: return Qobj(data, dims=sop_dims, - type='super', superrep='super', copy=False) @@ -201,7 +200,6 @@ def operator_to_vector(op): "in super representation") return Qobj(stack_columns(op.data), dims=[op.dims, [1]], - type='operator-ket', superrep="super", copy=False) @@ -315,7 +313,6 @@ def spost(A): data = _data.kron_transpose(A.data, _data.identity_like(A.data)) return Qobj(data, dims=[A.dims, A.dims], - type='super', superrep='super', isherm=A._isherm, copy=False) @@ -339,8 +336,7 @@ def spre(A): raise TypeError('Input is not a quantum operator') data = _data.kron(_data.identity_like(A.data), A.data) return Qobj(data, - dims=[A.dims, A.dims], - type='super', + dims=[A._dims, A._dims], superrep='super', isherm=A._isherm, copy=False) @@ -382,7 +378,6 @@ def sprepost(A, B): _drop_projected_dims(B.dims[0])]] return Qobj(_data.kron_transpose(B.data, A.data), dims=dims, - type='super', superrep='super', isherm=A._isherm and B._isherm, copy=False) diff --git a/qutip/core/tensor.py b/qutip/core/tensor.py index 3a3661752c..4262516b46 100644 --- a/qutip/core/tensor.py +++ b/qutip/core/tensor.py @@ -83,26 +83,25 @@ def tensor(*args): "In tensor products of superroperators,", " all must have the same representation" ])) - type = args[0].type + isherm = args[0]._isherm isunitary = args[0]._isunitary out_data = args[0].data - dims_l = [d for arg in args for d in arg.dims[0]] - dims_r = [d for arg in args for d in arg.dims[1]] + dims_l = [args[0]._dims[0]] + dims_r = [args[0]._dims[1]] for arg in args[1:]: out_data = _data.kron(out_data, arg.data) # If both _are_ Hermitian and/or unitary, then so is the output, but if # both _aren't_, then output still can be. isherm = (isherm and arg._isherm) or None isunitary = (isunitary and arg._isunitary) or None - if arg.type != type: - type = None + dims_l.append(arg._dims[0]) + dims_r.append(arg._dims[1]) + return Qobj(out_data, dims=[dims_l, dims_r], - type=type, isherm=isherm, isunitary=isunitary, - superrep=args[0].superrep, copy=False) diff --git a/qutip/random_objects.py b/qutip/random_objects.py index 8d36a4b7a3..45251cdd07 100644 --- a/qutip/random_objects.py +++ b/qutip/random_objects.py @@ -23,7 +23,7 @@ from . import (Qobj, create, destroy, jmat, basis, to_super, to_choi, to_chi, to_kraus, to_stinespring) from .core import data as _data -from .core.dimensions import flatten +from .core.dimensions import flatten, Dimensions from . import settings @@ -260,9 +260,6 @@ def rand_herm(dimensions, density=0.30, distribution="fill", *, repeatedly used for generating matrices larger than ~1000x1000. """ N, dims = _implicit_tensor_dimensions(dimensions) - type = None - if N == 1: - type = "oper" generator = _get_generator(seed) if distribution not in ["eigen", "fill", "pos_def"]: raise ValueError("distribution must be one of {'eigen', 'fill', " @@ -277,7 +274,7 @@ def rand_herm(dimensions, density=0.30, distribution="fill", *, out = _rand_jacobi_rotation(out, generator) while _data.csr.nnz(out) < 0.95 * nvals: out = _rand_jacobi_rotation(out, generator) - out = Qobj(out, type=type, dims=dims, isherm=True, copy=False) + out = Qobj(out, dims=dims, isherm=True, copy=False) dtype = dtype or settings.core["default_dtype"] or _data.CSR else: @@ -289,7 +286,7 @@ def rand_herm(dimensions, density=0.30, distribution="fill", *, M = _rand_herm_dense(N, density, pos_def, generator) dtype = dtype or settings.core["default_dtype"] or _data.Dense - out = Qobj(M, type=type, dims=dims, isherm=True, copy=False) + out = Qobj(M, dims=dims, isherm=True, copy=False) return out.to(dtype) @@ -375,9 +372,6 @@ def rand_unitary(dimensions, density=1, distribution="haar", *, """ dtype = dtype or settings.core["default_dtype"] or _data.Dense N, dims = _implicit_tensor_dimensions(dimensions) - type = None - if N == 1: - type = "oper" if distribution not in ["haar", "exp"]: raise ValueError("distribution must be one of {'haar', 'exp'}") generator = _get_generator(seed) @@ -396,7 +390,7 @@ def rand_unitary(dimensions, density=1, distribution="haar", *, merged = _merge_shuffle_blocks(blocks, generator) - mat = Qobj(merged, type=type, dims=dims, isunitary=True, copy=False) + mat = Qobj(merged, dims=dims, isunitary=True, copy=False) return mat.to(dtype) @@ -483,13 +477,12 @@ def rand_ket(dimensions, density=1, distribution="haar", *, dtype = dtype or settings.core["default_dtype"] or _data.Dense generator = _get_generator(seed) N, dims = _implicit_tensor_dimensions(dimensions) - type = None - if N == 1: - type = "ket" if distribution not in ["haar", "fill"]: raise ValueError("distribution must be one of {'haar', 'fill'}") - if distribution == "haar": + if N == 1: + ket = rand_unitary(1, seed=generator) + elif distribution == "haar": ket = rand_unitary(N, density, "haar", seed=generator) @ basis(N, 0) else: X = scipy.sparse.rand(N, 1, density, format='csr', @@ -505,7 +498,6 @@ def rand_ket(dimensions, density=1, distribution="haar", *, ket = Qobj(_data.mul(X, 1 / _data.norm.l2(X)), copy=False, isherm=False, isunitary=False) ket.dims = [dims[0], [1]] - ket.type = type return ket.to(dtype) @@ -561,9 +553,6 @@ def rand_dm(dimensions, density=0.75, distribution="ginibre", *, dtype = dtype or settings.core["default_dtype"] or _data.Dense generator = _get_generator(seed) N, dims = _implicit_tensor_dimensions(dimensions) - type = None - if N == 1: - type = "oper" distributions = set(["eigen", "ginibre", "hs", "pure", "herm"]) if distribution not in distributions: raise ValueError(f"distribution must be one of {distributions}") @@ -604,7 +593,7 @@ def rand_dm(dimensions, density=0.75, distribution="ginibre", *, H = _merge_shuffle_blocks(blocks, generator) H /= H.trace() - return Qobj(H, dims=dims, type=type, isherm=True, copy=False).to(dtype) + return Qobj(H, dims=dims, isherm=True, copy=False).to(dtype) def _rand_dm_ginibre(N, rank, generator): @@ -671,17 +660,18 @@ def rand_kraus_map(dimensions, *, seed=None, """ dtype = dtype or settings.core["default_dtype"] or _data.Dense N, dims = _implicit_tensor_dimensions(dimensions) + dims = Dimensions(dims) + if dims.issuper: + raise TypeError("Each kraus operator cannot itself a super operator.") # Random unitary (Stinespring Dilation) big_unitary = rand_unitary(N ** 3, seed=seed, dtype=dtype).full() orthog_cols = np.array(big_unitary[:, :N]) oper_list = np.reshape(orthog_cols, (N ** 2, N, N)) - return [Qobj(x, dims=dims, type='oper', copy=False).to(dtype) - for x in oper_list] + return [Qobj(x, dims=dims, copy=False).to(dtype) for x in oper_list] -def rand_super(dimensions, *, superrep="super", seed=None, - dtype=None): +def rand_super(dimensions, *, superrep="super", seed=None, dtype=None): """ Returns a randomly drawn superoperator acting on operators acting on N dimensions. @@ -807,9 +797,9 @@ def rand_super_bcsz(dimensions, enforce_tp=True, rank=None, *, # marking the dimensions as that of a type=super (that is, # with left and right compound indices, each representing # left and right indices on the underlying Hilbert space). - D = Qobj(np.dot(Z, np.dot(XXdag, Z)), dims=tmp_dims, type='super') + D = Qobj(np.dot(Z, np.dot(XXdag, Z)), dims=tmp_dims) else: - D = N * Qobj(XXdag / np.trace(XXdag), dims=tmp_dims, type='super') + D = N * Qobj(XXdag / np.trace(XXdag), dims=tmp_dims) # Since [BCSZ08] gives a row-stacking Choi matrix, but QuTiP # expects a column-stacking Choi matrix, we must permute the indices. diff --git a/qutip/solver/floquet.py b/qutip/solver/floquet.py index 61c139d349..03b74557da 100644 --- a/qutip/solver/floquet.py +++ b/qutip/solver/floquet.py @@ -110,7 +110,7 @@ def _as_ketlist(self, kets_mat): """ dims = [self.U(0).dims[0], [1]] return [ - Qobj(ket, dims=dims, type="ket") + Qobj(ket, dims=dims) for ket in _data.split_columns(kets_mat) ] @@ -483,9 +483,7 @@ def floquet_tensor(H, c_ops, spectra_cb, T=0, w_th=0.0, kmax=5, nT=100): a = _floquet_A_matrix(delta, gamma, w_th) r = _floquet_master_equation_tensor(a) dims = floquet_basis.U(0).dims - return Qobj( - r, dims=[dims, dims], type="super", superrep="super", copy=False - ) + return Qobj(r, dims=[dims, dims], superrep="super", copy=False) def fsesolve(H, psi0, tlist, e_ops=None, T=0.0, args=None, options=None): diff --git a/qutip/solver/propagator.py b/qutip/solver/propagator.py index e85ad5c5a0..cb1595021b 100644 --- a/qutip/solver/propagator.py +++ b/qutip/solver/propagator.py @@ -98,7 +98,6 @@ def propagator_steadystate(U): rho_data = _data.mul(rho_data, 0.5 / _data.trace(rho_data)) return Qobj(_data.add(rho_data, _data.adjoint(rho_data)), dims=U.dims[0], - type='oper', isherm=True, copy=False) diff --git a/qutip/solver/solver_base.py b/qutip/solver/solver_base.py index c8e743fea4..26fd015b0c 100644 --- a/qutip/solver/solver_base.py +++ b/qutip/solver/solver_base.py @@ -83,7 +83,6 @@ def _prepare_state(self, state): self._state_metadata = { 'dims': state.dims, - 'type': state.type, 'isherm': state.isherm and not (self.rhs.dims == state.dims) } if self.rhs.dims[1] == state.dims: diff --git a/qutip/tests/core/test_operators.py b/qutip/tests/core/test_operators.py index 5a887280cf..46fa020481 100644 --- a/qutip/tests/core/test_operators.py +++ b/qutip/tests/core/test_operators.py @@ -298,7 +298,7 @@ def test_qft(dims): @pytest.mark.parametrize('N', [1, 3, 5, 8]) -@pytest.mark.parametrize('M', [1, 3, 5, 8]) +@pytest.mark.parametrize('M', [2, 3, 5, 8]) def test_swap(N, M): ket1 = qutip.rand_ket(N) ket2 = qutip.rand_ket(M) diff --git a/qutip/tests/core/test_ptrace.py b/qutip/tests/core/test_ptrace.py index bb71e3581e..86d610e507 100644 --- a/qutip/tests/core/test_ptrace.py +++ b/qutip/tests/core/test_ptrace.py @@ -24,7 +24,7 @@ def expected(qobj, sel): continue tmp_dims = (before, dim, after) * 2 out = np.einsum('aibcid->abcd', out.reshape(tmp_dims)) - return qutip.Qobj(out.reshape(new_shape), dims=dims, type='oper') + return qutip.Qobj(out.reshape(new_shape), dims=dims) @pytest.fixture(params=[_data.CSR, _data.Dense], ids=['CSR', 'Dense']) diff --git a/qutip/tests/core/test_superop_reps.py b/qutip/tests/core/test_superop_reps.py index a2caaab146..80da2bd821 100644 --- a/qutip/tests/core/test_superop_reps.py +++ b/qutip/tests/core/test_superop_reps.py @@ -185,10 +185,10 @@ def test_random_iscptp(self, superoperator): + to_super(tensor(qeye(2), sigmay()))), True, True, True, id="linear combination of bipartite unitaries"), - pytest.param(Qobj(swap(), type='super', superrep='choi'), + pytest.param(Qobj(swap(), dims=[[[2],[2]]]*2, superrep='choi'), True, False, True, id="partial transpose map"), - pytest.param(Qobj(qeye(4)*0.9, type='super'), True, True, False, + pytest.param(Qobj(qeye(4)*0.9, dims=[[[2],[2]]]*2), True, True, False, id="subnormalized map"), pytest.param(basis(2, 0), False, False, False, id="ket"), ]) @@ -223,11 +223,11 @@ def test_choi_tr(self): ) / 2 # The partial transpose map, whose Choi matrix is SWAP - ptr_swap = Qobj(swap(), dims=[[[2], [2]]]*2, type='super', superrep='choi') + ptr_swap = Qobj(swap(), dims=[[[2], [2]]]*2, superrep='choi') # Subnormalized maps (representing erasure channels, for instance) subnorm_map = Qobj(identity(4) * 0.9, dims=[[[2], [2]]]*2, - type='super', superrep='super') + superrep='super') @pytest.mark.parametrize(['qobj', 'shouldhp', 'shouldcp', 'shouldtp'], [ pytest.param(S, True, True, False, id="conjugatio by create op"), diff --git a/qutip/tests/test_random.py b/qutip/tests/test_random.py index afbf9e0d20..8d6ff441e8 100644 --- a/qutip/tests/test_random.py +++ b/qutip/tests/test_random.py @@ -288,7 +288,7 @@ def test_kraus_map(dimensions, dtype): # Each element of a kraus map cannot be a super operators with pytest.raises(TypeError) as err: kmap = rand_kraus_map(dimensions, dtype=dtype) - assert "Type does not match dimensions" in str(err.value) + assert "super operator" in str(err.value) else: kmap = rand_kraus_map(dimensions, dtype=dtype) _assert_metadata(kmap[0], dimensions, dtype) From 3fb13221143c154d85d8ced23c82706c5e7b8a69 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 20 Nov 2023 15:43:43 -0500 Subject: [PATCH 096/247] Rm type and reshape in Qobj.__init__ --- qutip/core/qobj.py | 36 ++------------------------------ qutip/tests/core/test_metrics.py | 2 +- qutip/tests/core/test_qobj.py | 2 +- 3 files changed, 4 insertions(+), 36 deletions(-) diff --git a/qutip/core/qobj.py b/qutip/core/qobj.py index 2871cb65a8..0432ccb79c 100644 --- a/qutip/core/qobj.py +++ b/qutip/core/qobj.py @@ -283,51 +283,19 @@ def _initialize_data(self, arg, dims, copy): self._isunitary = arg._isunitary else: self._data = _data.create(arg, copy=copy) - dims = Dimensions( + self._dims = Dimensions( dims or [[self._data.shape[0]], [self._data.shape[1]]] ) - if ( - dims - and self._data.shape[1] == 1 - and self._data.shape != dims.shape - and self._data.shape == dims.shape[::-1] - ): - # 1D array are ket, convert to bra if bra dims are passed. - self._data = _data.transpose(self._data) - self._dims = dims if self._dims.shape != self._data.shape: raise ValueError('Provided dimensions do not match the data: ' + f"{self._dims.shape} vs {self._data.shape}") - def __init__(self, arg=None, dims=None, type=None, + def __init__(self, arg=None, dims=None, copy=True, superrep=None, isherm=None, isunitary=None): self._isherm = isherm self._isunitary = isunitary self._initialize_data(arg, dims, copy) - # Dims are guessed from the data and need to be changed to super. - - if type is not None: - raise Exception(type, self._dims.type) - - """ - if ( - type in ['super', 'operator-ket', 'operator-bra'] - and self._dims.type in ['oper', 'ket', 'bra'] - ): - root_right = int(np.sqrt(self._data.shape[0])) - root_left = int(np.sqrt(self._data.shape[1])) - if ( - root_right * root_right != self._data.shape[0] - and root_left * root_left != self._data.shape[1] - ): - raise ValueError( - "cannot build superoperator from nonsquare subspaces" - ) - self.dims = [[[root_right]]*2, [[root_left]]*2] - """ - - # self.type = type if superrep is not None: self.superrep = superrep diff --git a/qutip/tests/core/test_metrics.py b/qutip/tests/core/test_metrics.py index e52a074b32..575cafdc97 100644 --- a/qutip/tests/core/test_metrics.py +++ b/qutip/tests/core/test_metrics.py @@ -286,7 +286,7 @@ def had_mixture(x): def swap_map(x): base = (1j * x * swap()).expm() dims = [[[2], [2]], [[2], [2]]] - return Qobj(base, dims=dims, type='super', superrep='super') + return Qobj(base, dims=dims, superrep='super') def adc_choi(x): diff --git a/qutip/tests/core/test_qobj.py b/qutip/tests/core/test_qobj.py index 9403d18277..9ad09fe6fa 100644 --- a/qutip/tests/core/test_qobj.py +++ b/qutip/tests/core/test_qobj.py @@ -118,7 +118,7 @@ def test_QobjType(): assert super_qobj.isoperket assert super_qobj.superrep == 'super' - super_data = np.random.random(N) + super_data = np.random.random((1, N)) super_qobj = qutip.Qobj(super_data, dims=[[[1]], [[3], [3]]]) assert super_qobj.type == 'operator-bra' assert super_qobj.isoperbra From 80c6aadab439e8dc85c1f2e2306dd7c070b52b97 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 20 Nov 2023 17:07:07 -0500 Subject: [PATCH 097/247] Stricker list format test --- qutip/core/dimensions.py | 20 ++++++++++++++------ qutip/core/qobj.py | 18 +----------------- qutip/tests/core/test_dimensions.py | 11 +++++++++++ 3 files changed, 26 insertions(+), 23 deletions(-) diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index 955b1af277..680a00f1aa 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -350,7 +350,7 @@ def __call__(cls, *args, rep=None): return cls.field_instance if cls is SuperSpace: - args = *args, rep or 'super' + args = (*args, rep or 'super') if args not in cls._stored_dims: instance = cls.__new__(cls) @@ -359,7 +359,14 @@ def __call__(cls, *args, rep=None): return cls._stored_dims[args] def from_list(cls, list_dims, rep=None): - if not isinstance(list_dims[0], list): + if len(list_dims) == 0: + raise ValueError("Empty list can't be used as dims.") + elif ( + sum(isinstance(entry, list) for entry in list_dims) + not in [0, len(list_dims)] + ): + raise ValueError(f"Format dims not understood {list_dims}.") + elif not isinstance(list_dims[0], list): # Tensor spaces = [Space(size) for size in list_dims] elif len(list_dims) == 1: @@ -725,9 +732,9 @@ def __init__(self, from_, to_): else: if from_.issuper != to_.issuper: raise NotImplementedError( - "Operator with both space and superspace dimensions are not " - "supported. Please open an issue if you have an use case for " - f"these: {from_}, {to_}]" + "Operator with both space and superspace dimensions are " + "not supported. Please open an issue if you have an use " + f"case for these: {from_}, {to_}]" ) self.type = 'super' if self.from_.issuper else 'oper' if self.from_.superrep == self.to_.superrep: @@ -737,7 +744,8 @@ def __init__(self, from_, to_): self.__setitem__ = _frozen def __eq__(self, other): - return (self is other + return ( + self is other or ( type(self) is type(other) and self.to_ == other.to_ diff --git a/qutip/core/qobj.py b/qutip/core/qobj.py index 0432ccb79c..a6212daf10 100644 --- a/qutip/core/qobj.py +++ b/qutip/core/qobj.py @@ -318,26 +318,10 @@ def dims(self, dims): raise ValueError('Provided dimensions do not match the data: ' + f"{dims.shape} vs {self._data.shape}") self._dims = dims - # self.type = self._dims.type @property def type(self): - return self._dims.type # self._type - - @type.setter - def type(self, val): - if self._dims.type != val: - raise TypeError(f"Tried to set type {self._dims.type} to {val}") - """ - if not val: - self._type = self._dims.type - elif self._dims.type == "scalar": - self._type = val - elif self._dims.type == val: - self._type = val - else: - raise TypeError("Type does not match dimensions.") - """ + return self._dims.type @property def superrep(self): diff --git a/qutip/tests/core/test_dimensions.py b/qutip/tests/core/test_dimensions.py index 80cebf3f04..0686e28b71 100644 --- a/qutip/tests/core/test_dimensions.py +++ b/qutip/tests/core/test_dimensions.py @@ -151,3 +151,14 @@ def test_oper(self, base, expected): ]) def test_super(self, base, expected): assert collapse_dims_super(base) == expected + + +@pytest.mark.parametrize("dims_list", [ + pytest.param([0], id="zero"), + pytest.param([], id="empty"), + pytest.param([1, [2]], id="mixed depth"), + pytest.param([[2], [3], [4]], id="bay type"), +]) +def test_bad_dims(dims_list): + with pytest.raises(ValueError): + Dimensions([dims_list, [1]]) From 475bb4801e3177f114f840d9e28baf0148736092 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 22 Nov 2023 17:07:10 -0500 Subject: [PATCH 098/247] up to floquet --- .../dynamics/dynamics-bloch-redfield.rst | 103 ++++---- doc/guide/dynamics/dynamics-floquet.rst | 107 +++++--- doc/guide/dynamics/dynamics-krylov.rst | 63 +++-- doc/guide/dynamics/dynamics-monte.rst | 20 +- doc/guide/dynamics/dynamics-stochastic.rst | 60 +++-- doc/guide/dynamics/dynamics-time.rst | 243 +++++++++++------- qutip/solver/krylovsolve.py | 3 +- 7 files changed, 370 insertions(+), 229 deletions(-) diff --git a/doc/guide/dynamics/dynamics-bloch-redfield.rst b/doc/guide/dynamics/dynamics-bloch-redfield.rst index 93f4b2db9d..9f29de7388 100644 --- a/doc/guide/dynamics/dynamics-bloch-redfield.rst +++ b/doc/guide/dynamics/dynamics-bloch-redfield.rst @@ -87,16 +87,19 @@ This allows us to write master equation in terms of system operators and bath co g_{\alpha\beta}(-\tau) \left[\rho_S(t)A_\alpha(t-\tau)A_\beta(t) - A_\alpha(t)\rho_S(t)A_\beta(t-\tau)\right] \right\}, -where :math:`g_{\alpha\beta}(\tau) = {\rm Tr}_B\left[B_\alpha(t)B_\beta(t-\tau)\rho_B\right] = \left`, since the bath state :math:`\rho_B` is a steady state. +where :math:`g_{\alpha\beta}(\tau) = {\rm Tr}_B\left[B_\alpha(t)B_\beta(t-\tau)\rho_B\right] = \left`, +since the bath state :math:`\rho_B` is a steady state. -In the eigenbasis of the system Hamiltonian, where :math:`A_{mn}(t) = A_{mn} e^{i\omega_{mn}t}`, :math:`\omega_{mn} = \omega_m - \omega_n` and :math:`\omega_m` are the eigenfrequencies corresponding the eigenstate :math:`\left|m\right>`, we obtain in matrix form in the Schrödinger picture +In the eigenbasis of the system Hamiltonian, where :math:`A_{mn}(t) = A_{mn} e^{i\omega_{mn}t}`, +:math:`\omega_{mn} = \omega_m - \omega_n` and :math:`\omega_m` are the eigenfrequencies +corresponding the eigenstate :math:`\left|m\right>`, we obtain in matrix form in the Schrödinger picture .. math:: \frac{d}{dt}\rho_{ab}(t) - = - -i\omega_{ab}\rho_{ab}(t) - -\hbar^{-2} + =& + -i\omega_{ab}\rho_{ab}(t) \nonumber\\ + &-\hbar^{-2} \sum_{\alpha,\beta} \sum_{c,d}^{\rm sec} \int_0^\infty d\tau\; @@ -107,7 +110,7 @@ In the eigenbasis of the system Hamiltonian, where :math:`A_{mn}(t) = A_{mn} e^{ A^\alpha_{ac} A^\beta_{db} e^{i\omega_{ca}\tau} \right] \right. \nonumber\\ - + + &+ \left. g_{\alpha\beta}(-\tau) \left[\delta_{ac}\sum_n A^\alpha_{dn}A^\beta_{nb} e^{i\omega_{nd}\tau} @@ -185,8 +188,9 @@ Bloch-Redfield master equation in QuTiP -In QuTiP, the Bloch-Redfield tensor Eq. :eq:`br-tensor` can be calculated using the function :func:`qutip.bloch_redfield.bloch_redfield_tensor`. -It takes two mandatory arguments: The system Hamiltonian :math:`H`, a nested list of operator :math:`A_\alpha`, spectral density functions :math:`S_\alpha(\omega)` pairs that characterize the coupling between system and bath. +In QuTiP, the Bloch-Redfield tensor Eq. :eq:`br-tensor` can be calculated using the function :func:`.bloch_redfield_tensor`. +It takes two mandatory arguments: The system Hamiltonian :math:`H`, a nested list of operator +:math:`A_\alpha`, spectral density functions :math:`S_\alpha(\omega)` pairs that characterize the coupling between system and bath. The spectral density functions are Python callback functions that takes the (angular) frequency as a single argument. To illustrate how to calculate the Bloch-Redfield tensor, let's consider a two-level atom @@ -231,13 +235,22 @@ To illustrate how to calculate the Bloch-Redfield tensor, let's consider a two-l [ 0. +0.j 0. +0.j 0. +0.j -0.24514517+0.j ]] -Note that it is also possible to add Lindblad dissipation superoperators in the Bloch-Refield tensor by passing the operators via the ``c_ops`` keyword argument like you would in the :func:`qutip.mesolve` or :func:`qutip.mcsolve` functions. -For convenience, the function :func:`qutip.bloch_redfield_tensor` also returns the basis transformation operator, the eigen vector matrix, since they are calculated in the process of calculating the Bloch-Redfield tensor `R`, and the `ekets` are usually needed again later when transforming operators between the laboratory basis and the eigen basis. -The tensor can be obtained in the laboratory basis by setting ``fock_basis=True``, in that case, the transformation operator is not returned. +Note that it is also possible to add Lindblad dissipation superoperators in the +Bloch-Refield tensor by passing the operators via the ``c_ops`` keyword argument +like you would in the :func:`.mesolve` or :func:`.mcsolve` functions. +For convenience, the function :func:`.bloch_redfield_tensor` also returns the basis +transformation operator, the eigen vector matrix, since they are calculated in the +process of calculating the Bloch-Redfield tensor `R`, and the `ekets` are usually +needed again later when transforming operators between the laboratory basis and the eigen basis. +The tensor can be obtained in the laboratory basis by setting ``fock_basis=True``, +in that case, the transformation operator is not returned. -The evolution of a wavefunction or density matrix, according to the Bloch-Redfield master equation :eq:`br-final`, can be calculated using the QuTiP function :func:`qutip.mesolve` using Bloch-Refield tensor in the laboratory basis instead of a liouvillian. -For example, to evaluate the expectation values of the :math:`\sigma_x`, :math:`\sigma_y`, and :math:`\sigma_z` operators for the example above, we can use the following code: +The evolution of a wavefunction or density matrix, according to the Bloch-Redfield +master equation :eq:`br-final`, can be calculated using the QuTiP function :func:`.mesolve` +using Bloch-Refield tensor in the laboratory basis instead of a liouvillian. +For example, to evaluate the expectation values of the :math:`\sigma_x`, +:math:`\sigma_y`, and :math:`\sigma_z` operators for the example above, we can use the following code: .. plot:: :context: @@ -274,24 +287,35 @@ For example, to evaluate the expectation values of the :math:`\sigma_x`, :math:` sphere.make_sphere() -The two steps of calculating the Bloch-Redfield tensor and evolving according to the corresponding master equation can be combined into one by using the function :func:`qutip.brmesolve`, which takes same arguments as :func:`qutip.mesolve` and :func:`qutip.mcsolve`, save for the additional nested list of operator-spectrum pairs that is called ``a_ops``. +The two steps of calculating the Bloch-Redfield tensor and evolving according to +the corresponding master equation can be combined into one by using the function +:func:`.brmesolve`, which takes same arguments as :func:`.mesolve` and +:func:`.mcsolve`, save for the additional nested list of operator-spectrum +pairs that is called ``a_ops``. .. plot:: :context: close-figs output = brmesolve(H, psi0, tlist, a_ops=[[sigmax(),ohmic_spectrum]], e_ops=e_ops) -where the resulting `output` is an instance of the class :class:`qutip.Result`. +where the resulting `output` is an instance of the class :class:`.Result`. .. note:: - While the code example simulates the Bloch-Redfield equation in the secular approximation, QuTiP's implementation allows the user to simulate the non-secular version of the Bloch-Redfield equation by setting ``sec_cutoff=-1``, as well as do a partial secular approximation by setting it to a ``float`` , this float will become the cutoff for the sum in :eq:`br-final` meaning terms with :math:`|\omega_{ab}-\omega_{cd}|` greater than the cutoff will be neglected. + While the code example simulates the Bloch-Redfield equation in the secular + approximation, QuTiP's implementation allows the user to simulate the non-secular + version of the Bloch-Redfield equation by setting ``sec_cutoff=-1``, as well as + do a partial secular approximation by setting it to a ``float`` , this float + will become the cutoff for the sum in :eq:`br-final` meaning terms with + :math:`|\omega_{ab}-\omega_{cd}|` greater than the cutoff will be neglected. Its default value is 0.1 which corresponds to the secular approximation. For example the command :: - output = brmesolve(H, psi0, tlist, a_ops=[[sigmax(), ohmic_spectrum]], e_ops=e_ops, sec_cutoff=-1) + output = brmesolve(H, psi0, tlist, a_ops=[[sigmax(), ohmic_spectrum]], + e_ops=e_ops, sec_cutoff=-1) - will simulate the same example as above without the secular approximation. Note that using the non-secular version may lead to negativity issues. + will simulate the same example as above without the secular approximation. + Note that using the non-secular version may lead to negativity issues. .. _td-bloch-redfield: @@ -300,17 +324,23 @@ Time-dependent Bloch-Redfield Dynamics If you have not done so already, please read the section: :ref:`time`. -As we have already discussed, the Bloch-Redfield master equation requires transforming into the eigenbasis of the system Hamiltonian. +As we have already discussed, the Bloch-Redfield master equation requires transforming +into the eigenbasis of the system Hamiltonian. For time-independent systems, this transformation need only be done once. -However, for time-dependent systems, one must move to the instantaneous eigenbasis at each time-step in the evolution, thus greatly increasing the computational complexity of the dynamics. +However, for time-dependent systems, one must move to the instantaneous eigenbasis +at each time-step in the evolution, thus greatly increasing the computational complexity of the dynamics. In addition, the requirement for computing all the eigenvalues severely limits the scalability of the method. -Fortunately, this eigen decomposition occurs at the Hamiltonian level, as opposed to the super-operator level, and thus, with efficient programming, one can tackle many systems that are commonly encountered. +Fortunately, this eigen decomposition occurs at the Hamiltonian level, as opposed to the +super-operator level, and thus, with efficient programming, one can tackle many systems that are commonly encountered. -For time-dependent Hamiltonians, the Hamiltonian itself can be passed into the solver like any other time dependent Hamiltonian, as thus we will not discuss this topic further. +For time-dependent Hamiltonians, the Hamiltonian itself can be passed into the solver +like any other time dependent Hamiltonian, as thus we will not discuss this topic further. Instead, here the focus is on time-dependent bath coupling terms. -To this end, suppose that we have a dissipative harmonic oscillator, where the white-noise dissipation rate decreases exponentially with time :math:`\kappa(t) = \kappa(0)\exp(-t)`. -In the Lindblad or Monte Carlo solvers, this could be implemented as a time-dependent collapse operator list ``c_ops = [[a, 'sqrt(kappa*exp(-t))']]``. +To this end, suppose that we have a dissipative harmonic oscillator, where the white-noise +dissipation rate decreases exponentially with time :math:`\kappa(t) = \kappa(0)\exp(-t)`. +In the Lindblad or Monte Carlo solvers, this could be implemented as a time-dependent +collapse operator list ``c_ops = [[a, 'sqrt(kappa*exp(-t))']]``. In the Bloch-Redfield solver, the bath coupling terms must be Hermitian. As such, in this example, our coupling operator is the position operator ``a+a.dag()``. The complete example, and comparison to the analytic expression is: @@ -320,31 +350,21 @@ The complete example, and comparison to the analytic expression is: :context: close-figs N = 10 # number of basis states to consider - a = destroy(N) - H = a.dag() * a - psi0 = basis(N, 9) # initial state - kappa = 0.2 # coupling to oscillator - a_ops = [ ([a+a.dag(), f'sqrt({kappa}*exp(-t))'], '(w>=0)') ] - tlist = np.linspace(0, 10, 100) out = brmesolve(H, psi0, tlist, a_ops, e_ops=[a.dag() * a]) - actual_answer = 9.0 * np.exp(-kappa * (1.0 - np.exp(-tlist))) plt.figure() - plt.plot(tlist, out.expect[0]) - plt.plot(tlist, actual_answer) - plt.show() @@ -361,7 +381,8 @@ In this example, the ``a_ops`` list would be: ] -where the first tuple element ``[[a, 'exp(1j*t)'], [a.dag(), 'exp(-1j*t)']]`` tells the solver what is the time-dependent Hermitian coupling operator. +where the first tuple element ``[[a, 'exp(1j*t)'], [a.dag(), 'exp(-1j*t)']]`` tells +the solver what is the time-dependent Hermitian coupling operator. The second tuple ``f'{kappa} * (w >= 0)'``, gives the noise power spectrum. A full example is: @@ -369,37 +390,25 @@ A full example is: :context: close-figs N = 10 - w0 = 1.0 * 2 * np.pi - g = 0.05 * w0 - kappa = 0.15 - times = np.linspace(0, 25, 1000) a = destroy(N) - H = w0 * a.dag() * a + g * (a + a.dag()) - psi0 = ket2dm((basis(N, 4) + basis(N, 2) + basis(N, 0)).unit()) - a_ops = [[ QobjEvo([[a, 'exp(1j*t)'], [a.dag(), 'exp(-1j*t)']]), (f'{kappa} * (w >= 0)') ]] - e_ops = [a.dag() * a, a + a.dag()] res_brme = brmesolve(H, psi0, times, a_ops, e_ops) plt.figure() - plt.plot(times, res_brme.expect[0], label=r'$a^{+}a$') - plt.plot(times, res_brme.expect[1], label=r'$a+a^{+}$') - plt.legend() - plt.show() Further examples on time-dependent Bloch-Redfield simulations can be found in the online tutorials. diff --git a/doc/guide/dynamics/dynamics-floquet.rst b/doc/guide/dynamics/dynamics-floquet.rst index 7d3da8c38e..b5f67b6d95 100644 --- a/doc/guide/dynamics/dynamics-floquet.rst +++ b/doc/guide/dynamics/dynamics-floquet.rst @@ -81,7 +81,8 @@ so that :math:`\Phi_\alpha(t) = \exp(i\epsilon_\alpha t/\hbar) U(t,0)\Phi_\alpha Floquet formalism in QuTiP -------------------------- -QuTiP provides a family of functions to calculate the Floquet modes and quasi energies, Floquet state decomposition, etc., given a time-dependent Hamiltonian on the *callback format*, *list-string format* and *list-callback format* (see, e.g., :func:`qutip.mesolve` for details). +QuTiP provides a family of functions to calculate the Floquet modes and quasi energies, +Floquet state decomposition, etc., given a time-dependent Hamiltonian. Consider for example the case of a strongly driven two-level atom, described by the Hamiltonian @@ -92,8 +93,7 @@ Consider for example the case of a strongly driven two-level atom, described by In QuTiP we can define this Hamiltonian as follows: -.. plot:: - :context: reset +.. code-block:: python >>> delta = 0.2 * 2*np.pi >>> eps0 = 1.0 * 2*np.pi @@ -104,10 +104,11 @@ In QuTiP we can define this Hamiltonian as follows: >>> args = {'w': omega} >>> H = [H0, [H1, 'sin(w * t)']] -The :math:`t=0` Floquet modes corresponding to the Hamiltonian :eq:`eq_driven_qubit` can then be calculated using the :class:`qutip.FloquetBasis` class, which encapsulates the Floquet modes and the quasienergies: +The :math:`t=0` Floquet modes corresponding to the Hamiltonian :eq:`eq_driven_qubit` +can then be calculated using the :class:`.FloquetBasis` class, which encapsulates +the Floquet modes and the quasienergies: -.. plot:: - :context: close-figs +.. code-block:: python >>> T = 2*np.pi / omega >>> floquet_basis = FloquetBasis(H, T, args) @@ -126,9 +127,12 @@ The :math:`t=0` Floquet modes corresponding to the Hamiltonian :eq:`eq_driven_qu [0.72964231+0.j ]]] For some problems interesting observations can be draw from the quasienergy levels alone. -Consider for example the quasienergies for the driven two-level system introduced above as a function of the driving amplitude, calculated and plotted in the following example. +Consider for example the quasienergies for the driven two-level system introduced +above as a function of the driving amplitude, calculated and plotted in the following example. For certain driving amplitudes the quasienergy levels cross. -Since the quasienergies can be associated with the time-scale of the long-term dynamics due that the driving, degenerate quasienergies indicates a "freezing" of the dynamics (sometimes known as coherent destruction of tunneling). +Since the quasienergies can be associated with the time-scale of the long-term dynamics +due that the driving, degenerate quasienergies indicates a "freezing" of the dynamics +(sometimes known as coherent destruction of tunneling). .. plot:: :context: close-figs @@ -155,7 +159,8 @@ Since the quasienergies can be associated with the time-scale of the long-term d >>> plt.title(r'Floquet quasienergies') # doctest: +SKIP >>> plt.show() # doctest: +SKIP -Given the Floquet modes at :math:`t=0`, we obtain the Floquet mode at some later time :math:`t` using :meth:`FloquetBasis.mode`: +Given the Floquet modes at :math:`t=0`, we obtain the Floquet mode at some later +time :math:`t` using :meth:`.FloquetBasis.mode`: .. plot:: :context: close-figs @@ -171,8 +176,10 @@ Given the Floquet modes at :math:`t=0`, we obtain the Floquet mode at some later [[-0.37793106-0.00431336j] [-0.89630512+0.23191946j]]] -The purpose of calculating the Floquet modes is to find the wavefunction solution to the original problem :eq:`eq_driven_qubit` given some initial state :math:`\left|\psi_0\right>`. -To do that, we first need to decompose the initial state in the Floquet states, using the function :meth:`FloquetBasis.to_floquet_basis` +The purpose of calculating the Floquet modes is to find the wavefunction solution +to the original problem :eq:`eq_driven_qubit` given some initial state :math:`\left|\psi_0\right>`. +To do that, we first need to decompose the initial state in the Floquet states, +using the function :meth:`.FloquetBasis.to_floquet_basis` .. plot:: :context: close-figs @@ -183,7 +190,9 @@ To do that, we first need to decompose the initial state in the Floquet states, [(-0.645265993068382+0.7304552549315746j), (0.15517002114250228-0.1612116102238258j)] -and given this decomposition of the initial state in the Floquet states we can easily evaluate the wavefunction that is the solution to :eq:`eq_driven_qubit` at an arbitrary time :math:`t` using the function :meth:`FloquetBasis.from_floquet_basis`: +and given this decomposition of the initial state in the Floquet states we can easily +evaluate the wavefunction that is the solution to :eq:`eq_driven_qubit` at an arbitrary +time :math:`t` using the function :meth:`.FloquetBasis.from_floquet_basis`: .. plot:: :context: close-figs @@ -191,7 +200,8 @@ and given this decomposition of the initial state in the Floquet states we can e >>> t = 10 * np.random.rand() >>> psi_t = floquet_basis.from_floquet_basis(f_coeff, t) -The following example illustrates how to use the functions introduced above to calculate and plot the time-evolution of :eq:`eq_driven_qubit`. +The following example illustrates how to use the functions introduced above to calculate +and plot the time-evolution of :eq:`eq_driven_qubit`. .. plot:: guide/scripts/floquet_ex1.py :width: 4.0in @@ -200,17 +210,25 @@ The following example illustrates how to use the functions introduced above to c Pre-computing the Floquet modes for one period ---------------------------------------------- -When evaluating the Floquet states or the wavefunction at many points in time it is useful to pre-compute the Floquet modes for the first period of the driving with the required times. -The list of times to pre-compute modes for may be passed to :class:`FloquetBasis` using `precompute=tlist`, and then `:meth:`FloquetBasis.from_floquet_basis` and :meth:`FloquetBasis.to_floquet_basis` can be used to efficiently retrieve the wave function at the pre-computed times. -The following example illustrates how the example from the previous section can be solved more efficiently using these functions for pre-computing the Floquet modes: +When evaluating the Floquet states or the wavefunction at many points in time it +is useful to pre-compute the Floquet modes for the first period of the driving with +the required times. The list of times to pre-compute modes for may be passed to +:class:`.FloquetBasis` using ``precompute=tlist``, and then +:meth:`.FloquetBasis.from_floquet_basis` and :meth:`.FloquetBasis.to_floquet_basis` +can be used to efficiently retrieve the wave function at the pre-computed times. +The following example illustrates how the example from the previous section can be +solved more efficiently using these functions for pre-computing the Floquet modes: .. plot:: guide/scripts/floquet_ex2.py :width: 4.0in :include-source: -Note that the parameters and the Hamiltonian used in this example is not the same as in the previous section, and hence the different appearance of the resulting figure. +Note that the parameters and the Hamiltonian used in this example is not the same as +in the previous section, and hence the different appearance of the resulting figure. -For convenience, all the steps described above for calculating the evolution of a quantum system using the Floquet formalisms are encapsulated in the function :func:`qutip.floquet.fsesolve`. Using this function, we could have achieved the same results as in the examples above using +For convenience, all the steps described above for calculating the evolution of a +quantum system using the Floquet formalisms are encapsulated in the function :func:`.fsesolve`. +Using this function, we could have achieved the same results as in the examples above using .. code-block:: python @@ -222,21 +240,32 @@ For convenience, all the steps described above for calculating the evolution of Floquet theory for dissipative evolution ======================================== -A driven system that is interacting with its environment is not necessarily well described by the standard Lindblad master equation, since its dissipation process could be time-dependent due to the driving. In such cases a rigorious approach would be to take the driving into account when deriving the master equation. This can be done in many different ways, but one way common approach is to derive the master equation in the Floquet basis. That approach results in the so-called Floquet-Markov master equation, see Grifoni et al., Physics Reports 304, 299 (1998) for details. +A driven system that is interacting with its environment is not necessarily well +described by the standard Lindblad master equation, since its dissipation process +could be time-dependent due to the driving. In such cases a rigorious approach would +be to take the driving into account when deriving the master equation. This can be +done in many different ways, but one way common approach is to derive the master +equation in the Floquet basis. That approach results in the so-called Floquet-Markov +master equation, see Grifoni et al., Physics Reports 304, 299 (1998) for details. -For a brief summary of the derivation, the important contents for the implementation in QuTiP are listed below. +For a brief summary of the derivation, the important contents for the implementation +in QuTiP are listed below. -The floquet mode :math:`\ket{\phi_\alpha(t)}` refers to a full class of quasienergies defined by :math:`\epsilon_\alpha + k \Omega` for arbitrary :math:`k`. Hence, the quasienenergy difference between two floquet modes is given by +The floquet mode :math:`\ket{\phi_\alpha(t)}` refers to a full class of quasienergies +defined by :math:`\epsilon_\alpha + k \Omega` for arbitrary :math:`k`. Hence, the +quasienenergy difference between two floquet modes is given by .. math:: \Delta_{\alpha \beta k} = \frac{\epsilon_\alpha - \epsilon_\beta}{\hbar} + k \Omega -For any coupling operator :math:`q` (given by the user) the matrix elements in the floquet basis are calculated as: +For any coupling operator :math:`q` (given by the user) the matrix elements in +the floquet basis are calculated as: .. math:: X_{\alpha \beta k} = \frac{1}{T} \int_0^T dt \; e^{-ik \Omega t} \bra{\phi_\alpha(t)}q\ket{\phi_\beta(t)} -From the matrix elements and the spectral density :math:`J(\omega)`, the decay rate :math:`\gamma_{\alpha \beta k}` is defined: +From the matrix elements and the spectral density :math:`J(\omega)`, the decay +rate :math:`\gamma_{\alpha \beta k}` is defined: .. math:: \gamma_{\alpha \beta k} = 2 \pi J(\Delta_{\alpha \beta k}) | X_{\alpha \beta k}|^2 @@ -258,13 +287,23 @@ The density matrix of the system then evolves according to: The Floquet-Markov master equation in QuTiP ------------------------------------------- -The QuTiP function :func:`qutip.floquet.fmmesolve` implements the Floquet-Markov master equation. It calculates the dynamics of a system given its initial state, a time-dependent Hamiltonian, a list of operators through which the system couples to its environment and a list of corresponding spectral-density functions that describes the environment. In contrast to the :func:`qutip.mesolve` and :func:`qutip.mcsolve`, and the :func:`qutip.floquet.fmmesolve` does characterize the environment with dissipation rates, but extract the strength of the coupling to the environment from the noise spectral-density functions and the instantaneous Hamiltonian parameters (similar to the Bloch-Redfield master equation solver :func:`qutip.bloch_redfield.brmesolve`). +The QuTiP function :func:`.fmmesolve` implements the Floquet-Markov master equation. +It calculates the dynamics of a system given its initial state, a time-dependent +Hamiltonian, a list of operators through which the system couples to its environment +and a list of corresponding spectral-density functions that describes the environment. +In contrast to the :func:`.mesolve` and :func:`.mcsolve`, and the :func:`.fmmesolve` +does characterize the environment with dissipation rates, but extract the strength +of the coupling to the environment from the noise spectral-density functions and +the instantaneous Hamiltonian parameters (similar to the Bloch-Redfield master +equation solver :func:`.brmesolve`). .. note:: - Currently the :func:`qutip.floquet.fmmesolve` can only accept a single environment coupling operator and spectral-density function. + Currently the :func:`.fmmesolve` can only accept a single environment coupling + operator and spectral-density function. -The noise spectral-density function of the environment is implemented as a Python callback function that is passed to the solver. For example: +The noise spectral-density function of the environment is implemented as a Python +callback function that is passed to the solver. For example: .. code-block:: python @@ -273,18 +312,26 @@ The noise spectral-density function of the environment is implemented as a Pytho def noise_spectrum(omega): return (omega>0) * 0.5 * gamma1 * omega/(2*pi) -The other parameters are similar to the :func:`qutip.mesolve` and :func:`qutip.mcsolve`, and the same format for the return value is used :class:`qutip.solve.solver.Result`. The following example extends the example studied above, and uses :func:`qutip.floquet.fmmesolve` to introduce dissipation into the calculation +The other parameters are similar to the :func:`.mesolve` and :func:`.mcsolve`, +and the same format for the return value is used :class:`.Result`. +The following example extends the example studied above, and uses :func:`.fmmesolve` +to introduce dissipation into the calculation .. plot:: guide/scripts/floquet_ex3.py :width: 4.0in :include-source: -Finally, :func:`qutip.solver.floquet.fmmesolve` always expects the ``e_ops`` to be specified in the laboratory basis (as for other solvers) and we can calculate expectation values using: +Finally, :func:`.fmmesolve` always expects the ``e_ops`` to +be specified in the laboratory basis (as for other solvers) and we can calculate +expectation values using: + +.. code-block:: python - output = fmmesolve(H, psi0, tlist, [sigmax()], e_ops=[num(2)], spectra_cb=[noise_spectrum], T=T, args=args) + output = fmmesolve(H, psi0, tlist, [sigmax()], e_ops=[num(2)], + spectra_cb=[noise_spectrum], T=T, args=args) p_ex = output.expect[0] .. plot:: :context: reset :include-source: false - :nofigs: \ No newline at end of file + :nofigs: diff --git a/doc/guide/dynamics/dynamics-krylov.rst b/doc/guide/dynamics/dynamics-krylov.rst index 732157254b..b596072fb8 100644 --- a/doc/guide/dynamics/dynamics-krylov.rst +++ b/doc/guide/dynamics/dynamics-krylov.rst @@ -9,48 +9,77 @@ Krylov Solver Introduction ============= -The Krylov-subspace method is a standard method to approximate quantum dynamics. Let :math:`\left|\psi\right\rangle` be a state in a :math:`D`-dimensional complex Hilbert space that evolves under a time-independent Hamiltonian :math:`H`. Then, the :math:`N`-dimensional Krylov subspace associated with that state and Hamiltonian is given by +The Krylov-subspace method is a standard method to approximate quantum dynamics. +Let :math:`\left|\psi\right\rangle` be a state in a :math:`D`-dimensional +complex Hilbert space that evolves under a time-independent Hamiltonian :math:`H`. +Then, the :math:`N`-dimensional Krylov subspace associated with that state and +Hamiltonian is given by .. math:: :label: krylovsubspace \mathcal{K}_{N}=\operatorname{span}\left\{|\psi\rangle, H|\psi\rangle, \ldots, H^{N-1}|\psi\rangle\right\}, -where the dimension :math:`N>> from qutip import jmat, rand_ket - >>> from qutip.solver.krylovsolve import krylovsolve - >>> import numpy as np - >>> import matplotlib.pyplot as plt >>> dim = 100 - >>> e_ops = [jmat((dim - 1) / 2.0, "x"), jmat((dim - 1) / 2.0, "y"), jmat((dim - 1) / 2.0, "z")] - >>> H = .5*jmat((dim - 1) / 2.0, "z") + .5*jmat((dim - 1) / 2.0, "x") + >>> jx = jmat((dim - 1) / 2.0, "x") + >>> jy = jmat((dim - 1) / 2.0, "y") + >>> jz = jmat((dim - 1) / 2.0, "z") + >>> e_ops = [jx, jy, jz] + >>> H = (jz + jx) / 2 >>> psi0 = rand_ket(dim, seed=1) >>> tlist = np.linspace(0.0, 10.0, 200) >>> results = krylovsolve(H, psi0, tlist, krylov_dim=20, e_ops=e_ops) diff --git a/doc/guide/dynamics/dynamics-monte.rst b/doc/guide/dynamics/dynamics-monte.rst index 6db67e1d05..98c13a83bc 100644 --- a/doc/guide/dynamics/dynamics-monte.rst +++ b/doc/guide/dynamics/dynamics-monte.rst @@ -16,7 +16,7 @@ quantum-jump approach to wave function evolution, allows for simulating an individual realization of the system dynamics. Here, the environment is continuously monitored, resulting in a series of quantum jumps in the system wave function, conditioned on the increase in information gained about the -state of the system via the environmental measurements. In general, this +state of the system via the environmental measurements. In general, this evolution is governed by the Schrödinger equation with a **non-Hermitian** effective Hamiltonian @@ -61,7 +61,8 @@ is given by P_{i}(t)=\left<\psi(t)|C_{i}^{+}C_{i}|\psi(t)\right>/\delta p. Evaluating the MC evolution to first-order in time is quite tedious. Instead, -QuTiP uses the following algorithm to simulate a single realization of a quantum system. Starting from a pure state :math:`\left|\psi(0)\right>`: +QuTiP uses the following algorithm to simulate a single realization of a quantum system. +Starting from a pure state :math:`\left|\psi(0)\right>`: - **Ia:** Choose a random number :math:`r_1` between zero and one, representing the probability that a quantum jump occurs. @@ -180,8 +181,9 @@ The photocurrent, previously computed using the ``photocurrent_sesolve`` and psi0 = tensor(fock(2, 0), fock(10, 8)) a = tensor(qeye(2), destroy(10)) sm = tensor(destroy(2), qeye(10)) + e_ops = [a.dag() * a, sm.dag() * sm] H = 2*np.pi*a.dag()*a + 2*np.pi*sm.dag()*sm + 2*np.pi*0.25*(sm*a.dag() + sm.dag()*a) - data = mcsolve(H, psi0, times, [np.sqrt(0.1) * a], e_ops=[a.dag() * a, sm.dag() * sm]) + data = mcsolve(H, psi0, times, [np.sqrt(0.1) * a], e_ops=e_ops) plt.figure() plt.plot((times[:-1] + times[1:])/2, data.photocurrent[0]) @@ -206,7 +208,7 @@ the above example, we can simply modify the call to ``mcsolve`` like: .. plot:: :context: close-figs - data = mcsolve(H, psi0, times, [np.sqrt(0.1) * a], e_ops=[a.dag() * a, sm.dag() * sm], ntraj=1000) + data = mcsolve(H, psi0, times, [np.sqrt(0.1) * a], e_ops=e_ops, ntraj=1000) where we have added the keyword argument ``ntraj=1000`` at the end of the inputs. Now, the Monte Carlo solver will calculate expectation values for both operators, @@ -233,7 +235,7 @@ to ``mcsolve``: .. plot:: :context: close-figs - data = mcsolve(H, psi0, times, [np.sqrt(0.1) * a], e_ops=[a.dag() * a, sm.dag() * sm], options={"improved_sampling": True}) + data = mcsolve(H, psi0, times, [np.sqrt(0.1) * a], options={"improved_sampling": True}) where in this case the first run samples the no-jump trajectory, and the remaining 499 trajectories are all guaranteed to include (at least) one jump. @@ -339,9 +341,9 @@ identical conditions are merged into a single ``result`` object. data_merged = data1 + data2 plt.figure() - plt.plot(times, data1.expect[0], times, data1.expect[1], lw=2) - plt.plot(times, data2.expect[0], '--', times, data2.expect[1], '--', lw=2) - plt.plot(times, data_merged.expect[0], ':', times, data_merged.expect[1], ':', lw=2) + plt.plot(times, data1.expect[0], 'b-', times, data1.expect[1], 'g-', lw=2) + plt.plot(times, data2.expect[0], 'b--', times, data2.expect[1], 'g--', lw=2) + plt.plot(times, data_merged.expect[0], 'b:', times, data_merged.expect[1], 'g:', lw=2) plt.title('Monte Carlo time evolution') plt.xlabel('Time', fontsize=14) plt.ylabel('Expectation values', fontsize=14) @@ -432,7 +434,7 @@ relation of the form where :math:`\mathbb{I}` is the identity operator on the system Hilbert space and :math:`\alpha>0`. Note that when the collapse operators of a model don't satisfy such a relation, -``.nm_mcsolve`` automatically adds an extra collapse operator such that +``nm_mcsolve`` automatically adds an extra collapse operator such that :eq:`nmmcsolve_completeness` is satisfied. The rate corresponding to this extra collapse operator is set to zero. diff --git a/doc/guide/dynamics/dynamics-stochastic.rst b/doc/guide/dynamics/dynamics-stochastic.rst index 3931177ead..f62e1b6474 100644 --- a/doc/guide/dynamics/dynamics-stochastic.rst +++ b/doc/guide/dynamics/dynamics-stochastic.rst @@ -15,7 +15,7 @@ QuTiP by solving the general equation .. math:: :label: general_form - d \rho (t) = d_1 \rho dt + \sum_n d_{2,n} \rho dW_n, + d \rho (t) = d_1 \rho \, dt + \sum_n d_{2,n} \rho \, dW_n, where :math:`dW_n` is a Wiener increment, which has the expectation values :math:`E[dW] = 0` and :math:`E[dW^2] = dt`. @@ -41,7 +41,8 @@ where :math:`H` is the Hamiltonian, :math:`S_n` are the stochastic collapse oper e_n = \left<\psi(t)|S_n + S_n^\dagger|\psi(t)\right> -In QuTiP, this equation can be solved using the function :func:`qutip.solver.stochastic.ssesolve`, which is implemented by defining :math:`d_1` and :math:`d_{2,n}` from Equation :eq:`general_form` as +In QuTiP, this equation can be solved using the function :func:`~qutip.solver.stochastic.ssesolve`, +which is implemented by defining :math:`d_1` and :math:`d_{2,n}` from Equation :eq:`general_form` as .. math:: :label: d1_def @@ -55,14 +56,19 @@ and d_{2, n} = S_n - \frac{e_n}{2}. -The solver :func:`qutip.solver.stochastic.ssesolve` will construct the operators :math:`d_1` and :math:`d_{2,n}` once the user passes the Hamiltonian (``H``) and the stochastic operator list (``sc_ops``). -As with the :func:`qutip.solver.mcsolve.mcsolve`, the number of trajectories and the seed for the noise realisation can be fixed using the arguments: ``ntraj`` and ``seeds``, respectively. -If the user also requires the measurement output, the options entry ``{"store_measurement": True}`` should be included. +The solver :func:`~qutip.solver.stochastic.ssesolve` will construct the operators +:math:`d_1` and :math:`d_{2,n}` once the user passes the Hamiltonian (``H``) and +the stochastic operator list (``sc_ops``). As with the :func:`~qutip.solver.mcsolve.mcsolve`, +the number of trajectories and the seed for the noise realisation can be fixed using +the arguments: ``ntraj`` and ``seeds``, respectively. If the user also requires the +measurement output, the options entry ``{"store_measurement": True}`` should be included. -Per default, homodyne is used. Heterodyne detections can be easily simulated by passing the arguments ``'heterodyne=True'`` to :func:`qutip.solver.stochastic.ssesolve`. +Per default, homodyne is used. Heterodyne detections can be easily simulated by passing +the arguments ``'heterodyne=True'`` to :func:`~qutip.solver.stochastic.ssesolve`. .. - Examples of how to solve the stochastic Schrodinger equation using QuTiP can be found in this `development notebook <...TODO-Merge 61...>`_. + Examples of how to solve the stochastic Schrodinger equation using QuTiP + can be found in this `development notebook <...TODO-Merge 61...>`_. Stochastic Master Equation ========================== @@ -90,12 +96,15 @@ and .. math:: :label: h_cal - \mathcal{H}[A]\rho = A\rho(t) + \rho(t) A^\dagger - \tr[A\rho(t) + \rho(t) A^\dagger]. + \mathcal{H}[A]\rho = A\rho(t) + \rho(t) A^\dagger - \mathrm{tr}[A\rho(t) + \rho(t) A^\dagger]. -In QuTiP, solutions for the stochastic master equation are obtained using the solver :func:`qutip.solver.stochastic.smesolve`. -The implementation takes into account 2 types of collapse operators. :math:`C_i` (``c_ops``) represent the dissipation in the environment, while :math:`S_n` (``sc_ops``) are monitored operators. -The deterministic part of the evolution, described by the :math:`d_1` in Equation :eq:`general_form`, takes into account all operators :math:`C_i` and :math:`S_n`: +In QuTiP, solutions for the stochastic master equation are obtained using the solver +:func:`~qutip.solver.stochastic.smesolve`. The implementation takes into account 2 +types of collapse operators. :math:`C_i` (``c_ops``) represent the dissipation in +the environment, while :math:`S_n` (``sc_ops``) are monitored operators. +The deterministic part of the evolution, described by the :math:`d_1` in Equation +:eq:`general_form`, takes into account all operators :math:`C_i` and :math:`S_n`: .. math:: :label: liouvillian @@ -110,31 +119,34 @@ The stochastic part, :math:`d_{2,n}`, is given solely by the operators :math:`S_ .. math:: :label: stochastic_smesolve - d_{2,n} = S_n \rho(t) + \rho(t) S_n^\dagger - \tr \left(S_n \rho (t) - + \rho(t) S_n^\dagger \right)\rho(t). + d_{2,n} = S_n \rho(t) + \rho(t) S_n^\dagger - \mathrm{tr}\left(S_n \rho (t) + + \rho(t) S_n^\dagger \right)\,\rho(t). As in the stochastic Schrodinger equation, heterodyne detection can be chosen by passing ``heterodyne=True``. Example ------- -Below, we solve the dynamics for an optical cavity at 0K whose output is monitored using homodyne detection. -The cavity decay rate is given by :math:`\kappa` and the :math:`\Delta` is the cavity detuning with respect to the driving field. -The measurement operators can be passed using the option ``m_ops``. The homodyne current :math:`J_x` is calculated using +Below, we solve the dynamics for an optical cavity at 0K whose output is monitored +using homodyne detection. The cavity decay rate is given by :math:`\kappa` and the +:math:`\Delta` is the cavity detuning with respect to the driving field. +The measurement operators can be passed using the option ``m_ops``. The homodyne +current :math:`J_x` is calculated using .. math:: :label: measurement_result J_x = \langle x \rangle + dW / dt, -where :math:`x` is the operator passed using ``m_ops``. The results are available in ``result.measurements``. +where :math:`x` is the operator passed using ``m_ops``. The results are available +in ``result.measurements``. .. plot:: :context: reset - import numpy as np - import matplotlib.pyplot as plt - import qutip + #import numpy as np + #import matplotlib.pyplot as plt + #import qutip # parameters DIM = 20 # Hilbert space dimension @@ -144,20 +156,20 @@ where :math:`x` is the operator passed using ``m_ops``. The results are availabl NUMBER_OF_TRAJECTORIES = 500 # operators - a = qutip.destroy(DIM) + a = destroy(DIM) x = a + a.dag() H = DELTA * a.dag() * a - rho_0 = qutip.coherent(DIM, np.sqrt(INTENSITY)) + rho_0 = coherent(DIM, np.sqrt(INTENSITY)) times = np.arange(0, 1, 0.0025) - stoc_solution = qutip.smesolve( + stoc_solution = smesolve( H, rho_0, times, c_ops=[], sc_ops=[np.sqrt(KAPPA) * a], e_ops=[x], ntraj=NUMBER_OF_TRAJECTORIES, - options={"dt": 0.00125, "store_measurement":True,} + options={"dt": 0.00125, "store_measurement": True,} ) fig, ax = plt.subplots() diff --git a/doc/guide/dynamics/dynamics-time.rst b/doc/guide/dynamics/dynamics-time.rst index 85f3d613d2..d8ec31f2c8 100644 --- a/doc/guide/dynamics/dynamics-time.rst +++ b/doc/guide/dynamics/dynamics-time.rst @@ -11,10 +11,11 @@ Time-Dependent Operators In the previous examples of quantum evolution, we assumed that the systems under consideration were described by time-independent Hamiltonians. However, many systems have explicit time dependence in either the Hamiltonian, -or the collapse operators describing coupling to the environment, and sometimes both components might depend on time. -The time-evolutions solvers such as :func:`sesolve`, :func:`brmesolve`, etc. are all capable of handling time-dependent Hamiltonians and collapse terms. +or the collapse operators describing coupling to the environment, and sometimes +both components might depend on time. The time-evolutions solvers such as :func:`.sesolve`, +:func:`.brmesolve`, etc. are all capable of handling time-dependent Hamiltonians and collapse terms. QuTiP use :obj:`.QobjEvo` to represent time-dependent quantum operators. -There are three different ways to build a :obj:`.QobjEvo`: : +There are three different ways to build a :obj:`.QobjEvo`: 1. **Function based**: Build the time dependent operator from a function returning a :obj:`.Qobj`: @@ -23,30 +24,35 @@ There are three different ways to build a :obj:`.QobjEvo`: : def oper(t): return num(N) + (destroy(N) + create(N)) * np.sin(t) + H_t = QobjEvo(oper) -1. **List based**: The time dependent quantum operator is represented as a list of ``qobj`` and ``[qobj, coefficient]`` pairs. +1. **List based**: The time dependent quantum operator is represented as a list of ``qobj`` and ``[qobj, coefficient]`` pairs: .. code-block:: python H_t = QobjEvo([num(N), [create(N), lambda t: np.sin(t)], [destroy(N), lambda t: np.sin(t)]]) -3. **coefficent based**: The product of a :obj:`.Qobj` with a :obj:`.Coefficient` result in a :obj:`.QobjEvo`: +3. **coefficent based**: The product of a :obj:`.Qobj` with a :obj:`.Coefficient`, +created by the :func:`.coefficient` function, result in a :obj:`.QobjEvo`: .. code-block:: python coeff = coefficent(lambda t: np.sin(t)) H_t = num(N) + (destroy(N) + create(N)) * coeff -These 3 examples will create the same time dependent operator, however the function based method will usually be slower when used in solver. +These 3 examples will create the same time dependent operator, however the function +based method will usually be slower when used in solver. -Solvers will accept a :obj:`.QobjEvo`: when an operator is expected: this include the Hamiltonian ``H``, collapse operators, expectation values operators, the operator of :func:`brmesolve`'s ``a_ops``, etc. -Exception are :func:`krylovsolve`'s Hamiltonian and HEOM's Bath operators. +Most solvers accept a :obj:`.QobjEvo` when an operator is expected: this include +the Hamiltonian ``H``, collapse operators, expectation values operators, the operator +of :func:`.brmesolve`'s ``a_ops``, etc. Exception are :func:`.krylovsolve`'s +Hamiltonian and HEOM's Bath operators. -Most solvers will accept any format that could be made into a :obj:`.QobjEvo`: for the Hamiltonian. +Most solvers will accept any format that could be made into a :obj:`.QobjEvo` for the Hamiltonian. All of the following are equivalent: @@ -57,19 +63,23 @@ All of the following are equivalent: result = mesolve(oper, ...) -Collapse operator also accept a list of object that could be made into :obj:`.QobjEvo`:. -However one needs to be careful about not confusing the list nature of the `c_ops` parameter with list format quantum system. -In the following call: +Collapse operator also accept a list of object that could be made into :obj:`.QobjEvo`. +However one needs to be careful about not confusing the list nature of the `c_ops` +parameter with list format quantum system. In the following call: .. code-block:: python result = mesolve(H_t, ..., c_ops=[num(N), [destroy(N) + create(N), lambda t: np.sin(t)]]) -:func:`mesolve` will see 2 collapses operators: ``num(N)`` and ``[destroy(N) + create(N), lambda t: np.sin(t)]``. -It is therefore preferred to pass each collapse operator as either a :obj:`.Qobj`: or a :obj:`.QobjEvo`:. +:func:`.mesolve` will see 2 collapses operators: +``num(N)`` and ``[destroy(N) + create(N), lambda t: np.sin(t)]``. +It is therefore preferred to pass each collapse operator as either a :obj:`.Qobj` +or a :obj:`.QobjEvo`. -As an example, we will look at a case with a time-dependent Hamiltonian of the form :math:`H=H_{0}+f(t)H_{1}` where :math:`f(t)` is the time-dependent driving strength given as :math:`f(t)=A\exp\left[-\left( t/\sigma \right)^{2}\right]`. +As an example, we will look at a case with a time-dependent Hamiltonian of the form +:math:`H=H_{0}+f(t)H_{1}` where :math:`f(t)` is the time-dependent driving strength +given as :math:`f(t)=A\exp\left[-\left( t/\sigma \right)^{2}\right]`. The following code sets up the problem .. plot:: @@ -104,7 +114,9 @@ The following code sets up the problem H0 = -g * (sigma_ge.dag() * a + a.dag() * sigma_ge) # time-independent term H1 = (sigma_ue.dag() + sigma_ue) # time-dependent term -Given that we have a single time-dependent Hamiltonian term, and constant collapse terms, we need to specify a single Python function for the coefficient :math:`f(t)`. In this case, one can simply do +Given that we have a single time-dependent Hamiltonian term, and constant collapse terms, +we need to specify a single Python function for the coefficient :math:`f(t)`. +In this case, one can simply do .. plot:: :context: close-figs @@ -113,8 +125,10 @@ Given that we have a single time-dependent Hamiltonian term, and constant collap def H1_coeff(t): return 9 * np.exp(-(t / 5.) ** 2) -In this case, the return value depends only on time. However it is possible to add optional arguments to the call, see `Using arguments`_. -Having specified our coefficient function, we can now specify the Hamiltonian in list format and call the solver (in this case :func:`qutip.mesolve`) +In this case, the return value depends only on time. However it is possible to +add optional arguments to the call, see `Using arguments`_. +Having specified our coefficient function, we can now specify the Hamiltonian in +list format and call the solver (in this case :func:`.mesolve`) .. plot:: :context: close-figs @@ -133,8 +147,10 @@ We can call the Monte Carlo solver in the exact same way (if using the default ` output = mcsolve(H, psi0, t, c_ops, [ada, sigma_UU, sigma_GG]) -The output from the master equation solver is identical to that shown in the examples, the Monte Carlo however will be noticeably off, suggesting we should increase the number of trajectories for this example. -In addition, we can also consider the decay of a simple Harmonic oscillator with time-varying decay rate +The output from the master equation solver is identical to that shown in the examples, +the Monte Carlo however will be noticeably off, suggesting we should increase the number +of trajectories for this example. In addition, we can also consider the decay of a +simple Harmonic oscillator with time-varying decay rate .. plot:: :context: close-figs @@ -157,7 +173,8 @@ In addition, we can also consider the decay of a simple Harmonic oscillator with Qobjevo ======= -:obj:`.QobjEvo` as a time dependent quantum system, as it's main functionality create a :obj:`.Qobj` at a time: +:obj:`.QobjEvo` as a time dependent quantum system, as it's main functionality +create a :obj:`.Qobj` at a time: .. doctest:: [basics] :options: +NORMALIZE_WHITESPACE @@ -171,26 +188,28 @@ Qobjevo :obj:`.QobjEvo` shares a lot of properties with the :obj:`.Qobj`. -+---------------+------------------+----------------------------------------+ -| Property | Attribute | Description | -+===============+==================+========================================+ -| Dimensions | ``Q.dims`` | List keeping track of shapes for | -| | | individual components of a | -| | | multipartite system (for tensor | -| | | products and partial traces). | -+---------------+------------------+----------------------------------------+ -| Shape | ``Q.shape`` | Dimensions of underlying data matrix. | -+---------------+------------------+----------------------------------------+ -| Type | ``Q.type`` | Is object of type 'ket, 'bra', | -| | | 'oper', or 'super'? | -+---------------+------------------+----------------------------------------+ -| is constant? | ``Q.isconstant`` | Is the operator Hermitian or not? | -+---------------+------------------+----------------------------------------+ ++----------------+------------------+----------------------------------------+ +| Property | Attribute | Description | ++================+==================+========================================+ +| Dimensions | ``Q.dims`` | List keeping track of shapes | +| | | the tensor structure. | ++----------------+------------------+----------------------------------------+ +| Shape | ``Q.shape`` | Dimensions of underlying data matrix. | ++----------------+------------------+----------------------------------------+ +| Type | ``Q.type`` | Is object of type 'ket, 'bra', | +| | | 'oper', or 'super'? | ++----------------+------------------+----------------------------------------+ +| Representation | ``Q.superrep`` | Representation used if `type` is | +| | | 'super'? | ++----------------+------------------+----------------------------------------+ +| Is constant | ``Q.isconstant`` | Does the QobjEvo depend on time. | ++----------------+------------------+----------------------------------------+ :obj:`.QobjEvo`'s follow the same mathematical operations rules than :obj:`.Qobj`. They can be added, subtracted and multiplied with scalar, ``Qobj`` and ``QobjEvo``. -They also support the `dag` and `trans` and `conj` method and can be used for tensor operations and super operator transformation: +They also support the ``dag`` and ``trans`` and ``conj`` method and can be used +for tensor operations and super operator transformation: .. code-block:: python @@ -211,62 +230,73 @@ Or equivalently: Using arguments --------------- -Until now, the coefficient were only functions of time. -In the definition of ``H1_coeff``, the driving amplitude ``A`` and width ``sigma`` were hardcoded with their numerical values. +Until now, the coefficient were only functions of time. In the definition of ``H1_coeff``, +the driving amplitude ``A`` and width ``sigma`` were hardcoded with their numerical values. This is fine for problems that are specialized, or that we only want to run once. -However, in many cases, we would like study the same problem with a range of parameters and not have to worry about manually changing the values on each run. -QuTiP allows you to accomplish this using by adding extra arguments to coefficients function that make the :obj:`.QobjEvo`. -For instance, instead of explicitly writing 9 for the amplitude and 5 for the width of the gaussian driving term, we can add an `args` positional variable: +However, in many cases, we would like study the same problem with a range of parameters and +not have to worry about manually changing the values on each run. +QuTiP allows you to accomplish this using by adding extra arguments to coefficients +function that make the :obj:`.QobjEvo`. For instance, instead of explicitly writing +9 for the amplitude and 5 for the width of the gaussian driving term, we can add an +`args` positional variable: -.. plot:: - :context: close-figs +.. code-block:: python - def H1_coeff(t, args): - return args['A'] * np.exp(-(t/args['sigma'])**2) + >>> def H1_coeff(t, args): + >>> return args['A'] * np.exp(-(t/args['sigma'])**2) or, new from v5, add the extra parameter directly: -.. plot:: - :context: close-figs +.. code-block:: python - def H1_coeff(t, A, sigma): - return A * np.exp(-(t / sigma)**2) + >>> def H1_coeff(t, A, sigma): + >>> return A * np.exp(-(t / sigma)**2) -When the second positional input of the coefficient function is named ``args``, the arguments are passed as a Python dictionary of ``key: value`` pairs. +When the second positional input of the coefficient function is named ``args``, +the arguments are passed as a Python dictionary of ``key: value`` pairs. Otherwise the coefficient function is called as ``coeff(t, **args)``. -In the last example, ``args = {'A': a, 'sigma': b}`` where ``a`` and ``b`` are the two parameters for the amplitude and width, respectively. -This ``args`` dictionary need to be given at creation of the :obj:`.QobjEvo` when function using then are included: +In the last example, ``args = {'A': a, 'sigma': b}`` where ``a`` and ``b`` are the +two parameters for the amplitude and width, respectively. +This ``args`` dictionary need to be given at creation of the :obj:`.QobjEvo` when +function using then are included: -.. plot:: - :context: close-figs +.. code-block:: python - system = [H0, [H1, H1_coeff]] - args={'A': 9, 'sigma': 5} - qevo = QobjEvo(system, args=args) + >>> system = [sigmaz(), [sigmax(), H1_coeff]] + >>> args={'A': 9, 'sigma': 5} + >>> qevo = QobjEvo(system, args=args) But without ``args``, the :obj:`.QobjEvo` creation will fail: -.. plot:: - :context: close-figs - - try: - QobjEvo(system) - except TypeError as err: - print(err) +.. code-block:: python -When evaluation the :obj:`.QobjEvo` at a time, new arguments can be passed either with the ``args`` dictionary positional arguments, or with specific keywords arguments: + >>> QobjEvo(system) + TypeError: H1_coeff() missing 2 required positional arguments: 'A' and 'sigma' -.. plot:: - :context: close-figs +When evaluation the :obj:`.QobjEvo` at a time, new arguments can be passed either +with the ``args`` dictionary positional arguments, or with specific keywords arguments: - print(qevo(1)) - print(qevo(1, {"A": 5, "sigma": 0.2})) - print(qevo(1, A=5)) +.. code-block:: python + >>> print(qevo(1)) + Quantum object: dims=[[2], [2]], shape=(2, 2), type='oper', isherm=True + Qobj data = + [[ 1. 8.64710495] + [ 8.64710495 -1. ]] + >>> print(qevo(1, {"A": 5, "sigma": 0.2})) + Quantum object: dims=[[2], [2]], shape=(2, 2), type='oper', isherm=True + Qobj data = + [[ 1.00000000e+00 6.94397193e-11] + [ 6.94397193e-11 -1.00000000e+00]] + >>> print(qevo(1, A=5)) + Quantum object: dims=[[2], [2]], shape=(2, 2), type='oper', isherm=True + Qobj data = + [[ 1. 4.8039472] + [ 4.8039472 -1. ]] Whether the original coefficient used the ``args`` or specific input does not matter. It is fine to mix the different signatures. @@ -282,16 +312,15 @@ If the Hamiltonian or collapse operators are already :obj:`.QobjEvo`, their argu mesolve(system, ..., args=args) -To update arguments of an existing time dependent quantum system, you can pass the previous object as the input of a :obj:`.QobjEvo` with new ``args``: +To update arguments of an existing time dependent quantum system, you can pass the +previous object as the input of a :obj:`.QobjEvo` with new ``args``: -.. plot:: - :context: close-figs +.. code-block:: python - print(qevo(1)) - print(qevo(1, {"A": 5, "sigma": 0.2})) - new_qevo = QobjEvo(qevo, args={"A": 5, "sigma": 0.2}) - print(new_qevo(1)) + >>> new_qevo = QobjEvo(qevo, args={"A": 5, "sigma": 0.2}) + >>> new_qevo(1) == qevo(1, {"A": 5, "sigma": 0.2}) + True :obj:`.QobjEvo` created from a monolithic function can also use arguments: @@ -305,25 +334,34 @@ To update arguments of an existing time dependent quantum system, you can pass t H_t = QobjEvo(oper, args={"w": np.pi}) -When merging two or more :obj:`.QobjEvo`, each will keep it arguments, but calling it with updated are will affect all parts: +When merging two or more :obj:`.QobjEvo`, each will keep it arguments, but +calling it with updated are will affect all parts: -.. plot:: - :context: close-figs +.. code-block:: python - qevo1 = QobjEvo([[sigmap(), lambda t, a: a], [sigmam(), lambda t, a, b: a+1j*b]], args={"a": 1, "b":2}) - qevo2 = QobjEvo([[num(2), lambda t, a, c: a+1j*c]], args={"a": 2, "c":2}) - summed_evo = qevo1 + qevo2 - print(summed_evo(0)) - print(summed_evo(0, a=3, b=1)) + >>> qevo1 = QobjEvo([[sigmap(), lambda t, a: a]], args={"a": 1}) + >>> qevo2 = QobjEvo([[sigmam(), lambda t, a: a]], args={"a": 2}) + >>> summed_evo = qevo1 + qevo2 + >>> print(summed_evo(0)) + Quantum object: dims=[[2], [2]], shape=(2, 2), type='oper', isherm=False + Qobj data = + [[0. 1.] + [2. 0.]] + >>> print(summed_evo(0, a=3, b=1)) + Quantum object: dims=[[2], [2]], shape=(2, 2), type='oper', isherm=True + Qobj data = + [[0. 3.] + [3. 0.]] Coefficients ============ -To build time dependent quantum system we often use a list of :obj:`.Qobj` and *coefficient*. -These *coefficients* represent the strength of the corresponding quantum object a function that of time. -Up to now, we used functions for these, but QuTiP support multiple formats: ``callable``, ``strings``, ``array``. +To build time dependent quantum system we often use a list of :obj:`.Qobj` and +:obj:`.Coefficient`. These :obj:`.Coefficient` represent the strength of the corresponding +quantum object a function that of time. Up to now, we used functions for these, +but QuTiP support multiple formats: ``callable``, ``strings``, ``array``. **Function coefficients** : @@ -341,12 +379,15 @@ Any function or method that can be called by ``f(t, args)``, ``f(t, **args)`` is **String coefficients** : Use a string containing a simple Python expression. -The variable ``t``, common mathematical functions such as ``sin`` or ``exp`` an variable in args will be available. -If available, the string will be compiled using cython, fixing variable type when possible, allowing slightly faster execution than function. -While the speed up is usually very small, in long evolution, numerous calls to the functions are made and it's can accumulate. -From version 5, compilation of the coefficient is done only once and saved between sessions. -When either the cython or filelock modules are not available, the code will be executed in python using ``exec`` with the same environment . -This, however, as no advantage over using python function. +The variable ``t``, common mathematical functions such as ``sin`` or ``exp`` an +variable in args will be available. If available, the string will be compiled using +cython, fixing variable type when possible, allowing slightly faster execution than function. +While the speed up is usually very small, in long evolution, numerous calls to the +functions are made and it's can accumulate. From version 5, compilation of the +coefficient is done only once and saved between sessions. When either the cython or +filelock modules are not available, the code will be executed in python using +``exec`` with the same environment . This, however, as no advantage over using +python function. .. code-block:: python @@ -361,7 +402,8 @@ Here is a list of defined variables: ``sinh``, ``cosh``, ``tanh``, ``asinh``, ``acosh``, ``atanh``, ``exp``, ``log``, ``log10``, ``erf``, ``zerf``, ``sqrt``, ``real``, ``imag``, ``conj``, ``abs``, ``norm``, ``arg``, ``proj``, - ``np`` (numpy) and ``spe`` (scipy.special). + ``np`` (numpy), ``spe`` (scipy.special) and ``cython_special`` + (scipy cython interface). **Array coefficients** : @@ -399,7 +441,8 @@ Outside the interpolation range, the first or last value are used. plt.legend() -When using array coefficients in solver, if the time dependent quantum system is in list format, the solver tlist is used as times of the array. +When using array coefficients in solver, if the time dependent quantum system is +in list format, the solver tlist is used as times of the array. This is often not ideal as the interpolation is usually less precise close the extremities of the range. It is therefore better to create the QobjEvo using an extended range prior to the solver: @@ -412,9 +455,9 @@ It is therefore better to create the QobjEvo using an extended range prior to th coeff = np.exp(-times) c_ops = [QobjEvo([destroy(N), coeff], tlist=times)] - plt.plot( - mesolve(qeye(N), basis(N, N-1), np.linspace(0, 1, 11), c_ops=c_ops, e_ops=[num(N)]).expect - ) + tlist = np.linspace(0, 1, 11) + data = mesolve(qeye(N), basis(N, N-1), tlist, c_ops=c_ops, e_ops=[num(N)]).expect[0] + plt.plot(tlist, data) Different coefficient types can be mixed in a :obj:`.QobjEvo`. diff --git a/qutip/solver/krylovsolve.py b/qutip/solver/krylovsolve.py index 30747aaac1..6143aae06c 100644 --- a/qutip/solver/krylovsolve.py +++ b/qutip/solver/krylovsolve.py @@ -31,8 +31,7 @@ def krylovsolve( that can be made into :obj:`.QobjEvo` are also accepted. psi0 : :class:`.Qobj` - initial state vector (ket) - or initial unitary operator `psi0 = U` + Initial state vector (ket) tlist : *list* / *array* list of times for :math:`t`. From dd75c2ada5637ef0fe61edc45ad24384f7054725 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 23 Nov 2023 10:18:44 -0500 Subject: [PATCH 099/247] Fix link in dynamics --- doc/guide/dynamics/dynamics-options.rst | 14 ++++++++----- doc/guide/dynamics/dynamics-piqs.rst | 27 +++++++++++++++++++------ 2 files changed, 30 insertions(+), 11 deletions(-) diff --git a/doc/guide/dynamics/dynamics-options.rst b/doc/guide/dynamics/dynamics-options.rst index a3bc831067..5531450d09 100644 --- a/doc/guide/dynamics/dynamics-options.rst +++ b/doc/guide/dynamics/dynamics-options.rst @@ -9,7 +9,8 @@ Setting Options for the Dynamics Solvers from qutip.solver.mesolve import MESolver, mesolve import numpy as np -Occasionally it is necessary to change the built in parameters of the dynamics solvers used by for example the :func:`qutip.mesolve` and :func:`qutip.mcsolve` functions. +Occasionally it is necessary to change the built in parameters of the dynamics +solvers used by for example the :func:`.mesolve` and :func:`.mcsolve` functions. The options for all dynamics solvers may be changed by using the dictionaries. .. testcode:: [dynamics_options] @@ -23,22 +24,25 @@ Supported solver options and their default can be seen using the class interface help(MESolver.options) -Options supported by the ODE integration depend on the "method" options of the solver, they can be listed through the integrator method of the solvers: +Options supported by the ODE integration depend on the "method" options of the solver, +they can be listed through the integrator method of the solvers: .. testcode:: [dynamics_options] help(MESolver.integrator("adams").options) -See `Integrator `_ for a list of supported methods. +See :ref:`classes-ode` for a list of supported methods. -As an example, let us consider changing the integrator, turn the GUI off, and strengthen the absolute tolerance. +As an example, let us consider changing the integrator, turn the GUI off, and +strengthen the absolute tolerance. .. testcode:: [dynamics_options] options = {method="bdf", "atol": 1e-10, "progress_bar": False} -To use these new settings we can use the keyword argument ``options`` in either the :func:`qutip.mesolve` and :func:`qutip.mcsolve` function:: +To use these new settings we can use the keyword argument ``options`` in either +the :func:`.mesolve` and :func:`.mcsolve` function:: >>> mesolve(H0, psi0, tlist, c_op_list, [sigmaz()], options=options) diff --git a/doc/guide/dynamics/dynamics-piqs.rst b/doc/guide/dynamics/dynamics-piqs.rst index e004ab64f8..f13cf8b3a4 100644 --- a/doc/guide/dynamics/dynamics-piqs.rst +++ b/doc/guide/dynamics/dynamics-piqs.rst @@ -53,10 +53,9 @@ The system's Liouvillian can be built using :code:`system.liouvillian()`. The pr .. code-block:: python - from piqs import Dicke - from qutip import steadystate + from qutip import piqs N = 10 - system = Dicke(N, emission = 1, pumping = 2) + system = piqs.Dicke(N, emission = 1, pumping = 2) L = system.liouvillian() steady = steadystate(L) @@ -109,6 +108,22 @@ For more example of use, see the "Permutational Invariant Lindblad Dynamics" sec - ``Dicke.c_ops()`` - The collapse operators for the ensemble can be called by the `c_ops` method of the Dicke class. -Note that the mathematical object representing the density matrix of the full system that is manipulated (or obtained from `steadystate`) in the Dicke-basis formalism used here is a *representative of the density matrix*. This *representative object* is of linear size N^2, whereas the full density matrix is defined over a 2^N Hilbert space. In order to calculate nonlinear functions of such density matrix, such as the Von Neumann entropy or the purity, it is necessary to take into account the degeneracy of each block of such block-diagonal density matrix. Note that as long as one calculates expected values of operators, being Tr[A*rho] a *linear* function of `rho`, the *representative density matrix* give straightforwardly the correct result. When a *nonlinear* function of the density matrix needs to be calculated, one needs to weigh each degenerate block correctly; this is taken care by the `dicke_function_trace` in `qutip.piqs`, and the user can use it to define general nonlinear functions that can be described as the trace of a Taylor expandable function. Two nonlinear functions that use `dicke_function_trace` and are already implemented are `purity_dicke`, to calculate the purity of a density matrix in the Dicke basis, and `entropy_vn_dicke`, which can be used to calculate the Von Neumann entropy. - -More functions relative to the `qutip.piqs` module can be found at :ref:`apidoc`. Attributes to the :class:`qutip.piqs.Dicke` and :class:`qutip.piqs.Pim` class can also be found there. +Note that the mathematical object representing the density matrix of the full system +that is manipulated (or obtained from `steadystate`) in the Dicke-basis formalism +used here is a *representative of the density matrix*. This *representative object* +is of linear size N^2, whereas the full density matrix is defined over a 2^N Hilbert +space. In order to calculate nonlinear functions of such density matrix, such as the +Von Neumann entropy or the purity, it is necessary to take into account the degeneracy +of each block of such block-diagonal density matrix. Note that as long as one calculates +expected values of operators, being Tr[A*rho] a *linear* function of `rho`, the +*representative density matrix* give straightforwardly the correct result. When a +*nonlinear* function of the density matrix needs to be calculated, one needs to +weigh each degenerate block correctly; this is taken care by the `dicke_function_trace` +in :obj:`.piqs`, and the user can use it to define general nonlinear functions that +can be described as the trace of a Taylor expandable function. Two nonlinear functions +that use `dicke_function_trace` and are already implemented are `purity_dicke`, to +calculate the purity of a density matrix in the Dicke basis, and `entropy_vn_dicke`, +which can be used to calculate the Von Neumann entropy. + +More functions relative to the :obj:`qutip.piqs` module can be found at :ref:`apidoc`. +Attributes to the :class:`.piqs.Dicke` and :class:`.piqs.Pim` class can also be found there. From 3fea9be7a1067ce5d36d4cb8327814f007c97be2 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 23 Nov 2023 11:59:50 -0500 Subject: [PATCH 100/247] rename piqs guide --- doc/guide/{dynamics/dynamics-piqs.rst => guide-piqs.rst} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename doc/guide/{dynamics/dynamics-piqs.rst => guide-piqs.rst} (100%) diff --git a/doc/guide/dynamics/dynamics-piqs.rst b/doc/guide/guide-piqs.rst similarity index 100% rename from doc/guide/dynamics/dynamics-piqs.rst rename to doc/guide/guide-piqs.rst From c07a912efdef223e88228a4348ca4c31a5087478 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 23 Nov 2023 17:09:23 -0500 Subject: [PATCH 101/247] mcsolve doc rework --- doc/guide/dynamics/dynamics-class-api.rst | 102 +++++++ doc/guide/dynamics/dynamics-data.rst | 20 +- doc/guide/dynamics/dynamics-intro.rst | 8 +- doc/guide/dynamics/dynamics-monte.rst | 315 ++++++++-------------- doc/guide/dynamics/dynamics-nmmonte.rst | 130 +++++++++ doc/guide/guide-dynamics.rst | 3 +- doc/guide/guide.rst | 2 +- 7 files changed, 367 insertions(+), 213 deletions(-) create mode 100644 doc/guide/dynamics/dynamics-class-api.rst create mode 100644 doc/guide/dynamics/dynamics-nmmonte.rst diff --git a/doc/guide/dynamics/dynamics-class-api.rst b/doc/guide/dynamics/dynamics-class-api.rst new file mode 100644 index 0000000000..4ad74a6f5c --- /dev/null +++ b/doc/guide/dynamics/dynamics-class-api.rst @@ -0,0 +1,102 @@ + + +.. _monte-reuse: + +Reusing Hamiltonian Data +------------------------ + +.. note:: This section covers a specialized topic and may be skipped if you are new to QuTiP. + + +In order to solve a given simulation as fast as possible, the solvers in QuTiP +take the given input operators and break them down into simpler components before +passing them on to the ODE solvers. Although these operations are reasonably fast, +the time spent organizing data can become appreciable when repeatedly solving a +system over, for example, many different initial conditions. In cases such as +this, the Monte Carlo Solver may be reused after the initial configuration, thus +speeding up calculations. + + +Using the previous example, we will calculate the dynamics for two different +initial states, with the Hamiltonian data being reused on the second call + +.. plot:: + :context: close-figs + + times = np.linspace(0.0, 10.0, 200) + psi0 = tensor(fock(2, 0), fock(10, 5)) + a = tensor(qeye(2), destroy(10)) + sm = tensor(destroy(2), qeye(10)) + + H = 2*np.pi*a.dag()*a + 2*np.pi*sm.dag()*sm + 2*np.pi*0.25*(sm*a.dag() + sm.dag()*a) + solver = MCSolver(H, c_ops=[np.sqrt(0.1) * a]) + data1 = solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=100) + psi1 = tensor(fock(2, 0), coherent(10, 2 - 1j)) + data2 = solver.run(psi1, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=100) + + plt.figure() + plt.plot(times, data1.expect[0], "b", times, data1.expect[1], "r", lw=2) + plt.plot(times, data2.expect[0], 'b--', times, data2.expect[1], 'r--', lw=2) + plt.title('Monte Carlo time evolution') + plt.xlabel('Time', fontsize=14) + plt.ylabel('Expectation values', fontsize=14) + plt.legend(("cavity photon number", "atom excitation probability")) + plt.show() + +.. guide-dynamics-mc2: + +The ``MCSolver`` also allows adding new trajectories after the first computation. +This is shown in the next example where the results of two separated runs with +identical conditions are merged into a single ``result`` object. + +.. plot:: + :context: close-figs + + times = np.linspace(0.0, 10.0, 200) + psi0 = tensor(fock(2, 0), fock(10, 5)) + a = tensor(qeye(2), destroy(10)) + sm = tensor(destroy(2), qeye(10)) + + H = 2*np.pi*a.dag()*a + 2*np.pi*sm.dag()*sm + 2*np.pi*0.25*(sm*a.dag() + sm.dag()*a) + solver = MCSolver(H, c_ops=[np.sqrt(0.1) * a]) + data1 = solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=1, seed=1) + data2 = solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=1, seed=3) + data_merged = data1 + data2 + + plt.figure() + plt.plot(times, data1.expect[0], 'b-', times, data1.expect[1], 'g-', lw=2) + plt.plot(times, data2.expect[0], 'b--', times, data2.expect[1], 'g--', lw=2) + plt.plot(times, data_merged.expect[0], 'b:', times, data_merged.expect[1], 'g:', lw=2) + plt.title('Monte Carlo time evolution') + plt.xlabel('Time', fontsize=14) + plt.ylabel('Expectation values', fontsize=14) + plt.legend(("cavity photon number", "atom excitation probability")) + plt.show() + + +This can be used to explore the convergence of the Monte Carlo solver. +For example, the following code block plots expectation values for 1, 10 and 100 +trajectories: + +.. plot:: + :context: close-figs + + solver = MCSolver(H, c_ops=[np.sqrt(0.1) * a]) + + data1 = solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=1) + data10 = data1 + solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=9) + data100 = data10 + solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=90) + + expt1 = data1.expect + expt10 = data10.expect + expt100 = data100.expect + + plt.figure() + plt.plot(times, expt1[0], label="ntraj=1") + plt.plot(times, expt10[0], label="ntraj=10") + plt.plot(times, expt100[0], label="ntraj=100") + plt.title('Monte Carlo time evolution') + plt.xlabel('Time') + plt.ylabel('Expectation values') + plt.legend() + plt.show() diff --git a/doc/guide/dynamics/dynamics-data.rst b/doc/guide/dynamics/dynamics-data.rst index c0b1daea71..0c24f398fc 100644 --- a/doc/guide/dynamics/dynamics-data.rst +++ b/doc/guide/dynamics/dynamics-data.rst @@ -164,10 +164,28 @@ expectation values is also available: | ``std_e_data`` | | Dictionary of standard derivation of the expectation values. | +-------------------------+----------------------+------------------------------------------------------------------------+ -Multiple trajectories results also keep the trajectories seeds to allows +Multiple trajectories results also keep the trajectories ``seeds`` to allows recomputing the results. .. testcode:: :skipif: True seeds = result.seeds + +One last feature specific to multi-trajectories results is the addition operation +that can be used to merge sets of trajectories. + + +.. code-block:: + + >>> run1 = smesolve(H, psi, np.linspace(0, 1, 11), c_ops, e_ops=[num(N)], ntraj=25) + >>> print(run1.num_trajectories) + 25 + >>> run2 = smesolve(H, psi, np.linspace(0, 1, 11), c_ops, e_ops=[num(N)], ntraj=25) + >>> print(run2.num_trajectories) + 25 + >>> merged = run1 + run2 + >>> print(merged.num_trajectories) + 50 + +This allows to improve statistics while keeping previous computation. diff --git a/doc/guide/dynamics/dynamics-intro.rst b/doc/guide/dynamics/dynamics-intro.rst index 096b3ba3e0..cfdf0c1ce0 100644 --- a/doc/guide/dynamics/dynamics-intro.rst +++ b/doc/guide/dynamics/dynamics-intro.rst @@ -30,7 +30,7 @@ quantum systems and indicates the type of object returned by the solver: - :func:`~qutip.solver.sesolve.sesolve` - :obj:`~qutip.solver.sesolve.SESolver` - :obj:`~qutip.solver.result.Result` - * - Periodic unitary evolution, Schrödinger equation. + * - Periodic Schrödinger equation. - :func:`~qutip.solver.floquet.fsesolve` - None - :obj:`~qutip.solver.result.Result` @@ -42,11 +42,11 @@ quantum systems and indicates the type of object returned by the solver: - :func:`~qutip.solver.mesolve.mesolve` - :obj:`~qutip.solver.mesolve.MESolver` - :obj:`~qutip.solver.result.Result` - * - Monte Carlo with collapse operators + * - Monte Carlo evolution - :func:`~qutip.solver.mcsolve.mcsolve` - :obj:`~qutip.solver.mcsolve.MCSolver` - :obj:`~qutip.solver.result.McResult` - * - Non-Markovian Monte Carlo with collapse operators + * - Non-Markovian Monte Carlo - :func:`~qutip.solver.nm_mcsolve.nm_mcsolve` - :obj:`~qutip.solver.nm_mcsolve.NonMarkovianMCSolver` - :obj:`~qutip.solver.result.NmmcResult` @@ -73,4 +73,4 @@ quantum systems and indicates the type of object returned by the solver: * - Hierarchical Equations of Motion evolution - :func:`~qutip.solver.heom.bofin_solvers.heomsolve` - :obj:`~qutip.solver.heom.bofin_solvers.HEOMSolver` - - :obj:`~qutip.solver.heom.bofin_solvers.HSolverDL` + - :obj:`~qutip.solver.heom.bofin_solvers.HEOMResult` diff --git a/doc/guide/dynamics/dynamics-monte.rst b/doc/guide/dynamics/dynamics-monte.rst index 98c13a83bc..954cf537ae 100644 --- a/doc/guide/dynamics/dynamics-monte.rst +++ b/doc/guide/dynamics/dynamics-monte.rst @@ -215,6 +215,70 @@ Now, the Monte Carlo solver will calculate expectation values for both operators ``a.dag() * a, sm.dag() * sm`` averaging over 1000 trajectories. +Other than a target number of trajectories, it is possible to use a computation +time or errors bars as condition to stop computing trajectories. + +``timeout`` is quite simple as ``mcsolve`` will stop starting the computation of +new trajectories when it is reached. Thus: + + +.. code-block:: + + data = mcsolve(H, psi0, times, [np.sqrt(0.1) * a], e_ops=e_ops, ntraj=1000, timeout=60) + +Will compute 1 minute of trajectories or 1000, which ever is reached first. +The solver will finish any trajectory started when the timeout is reached. Therefore +if the computation time of a single trajectory is quite long, the overall computation +time can be much longer that the provided timeout. + +Lastly, ``mcsolve`` can be instructed to stop when the statistical error of the +expectation values get under a certain value. When computing the average over +trajectories, the error on these are computed using +:ref:`Jackknife resampling ` +for each expect and each time and the computation will be stopped when all these values +are under the tolerance passed to ``target_tol``. Therefore: + +.. code-block:: + + data = mcsolve(H, psi0, times, [np.sqrt(0.1) * a], e_ops=e_ops, + ntraj=1000, target_tol=0.01, timeout=600) + +will stop either after all errors bars on expectation values are under ``0.01``, 1000 +trajectories are computed or 10 min have passed, whichever comes first. When a +single values is passed, it is used as the absolute value of the tolerance. +When a pair of values is passed, it is understood as an absolute and relative +tolerance pair. For even finer control, one such pair can be passed for each ``e_ops``. +For example: + +.. code-block:: + + data = mcsolve(H, psi0, times, c_ops, e_ops=e_ops, target_tol=[ + (1e-5, 0.1), + (0, 0), + ]) + +will stop when the error bars on the expectation values of the first ``e_ops`` are +under 10% of their average values. + +If after computation of some trajectories, it is determined that more are needed, it +is possible to add trajectories to existing result by adding result together: + +.. code-block:: + + >>> run1 = mcsolve(H, psi, times, c_ops, e_ops=e_ops, ntraj=25) + >>> print(run1.num_trajectories) + 25 + >>> run2 = mcsolve(H, psi, times, c_ops, e_ops=e_ops, ntraj=25) + >>> print(run2.num_trajectories) + 25 + >>> merged = run1 + run2 + >>> print(merged.num_trajectories) + 50 + +Note that this merging operation only check that the result are compatible: +same e_ops and same tlist. It does not check that the same initial state or +Hamiltonian where used. + Using the Improved Sampling Algorithm ------------------------------------- @@ -277,106 +341,70 @@ The original sampling algorithm samples the no-jump trajectory on average 96.7% of the time, while the improved sampling algorithm only does so once. -.. _monte-reuse: - -Reusing Hamiltonian Data ------------------------- +.. monte-seeds: -.. note:: This section covers a specialized topic and may be skipped if you are new to QuTiP. +Reproducibility with Monte-Carlo +-------------------------------- +For reproducibility of Monte-Carlo computations it is possible to set the seed -In order to solve a given simulation as fast as possible, the solvers in QuTiP -take the given input operators and break them down into simpler components before -passing them on to the ODE solvers. Although these operations are reasonably fast, -the time spent organizing data can become appreciable when repeatedly solving a -system over, for example, many different initial conditions. In cases such as -this, the Monte Carlo Solver may be reused after the initial configuration, thus -speeding up calculations. +.. code-block:: + >>> res1 = mcsolve(H, psi0, tlist, c_ops, e_ops=e_ops, seeds=1, ntraj=1) + >>> res2 = mcsolve(H, psi0, tlist, c_ops, e_ops=e_ops, seeds=1, ntraj=1) + >>> res3 = mcsolve(H, psi0, tlist, c_ops, e_ops=e_ops, seeds=2, ntraj=1) + >>> np.allclose(res1, res2) + True + >>> np.allclose(res1, res3) + False -Using the previous example, we will calculate the dynamics for two different -initial states, with the Hamiltonian data being reused on the second call - -.. plot:: - :context: close-figs - - times = np.linspace(0.0, 10.0, 200) - psi0 = tensor(fock(2, 0), fock(10, 5)) - a = tensor(qeye(2), destroy(10)) - sm = tensor(destroy(2), qeye(10)) +The ``seeds`` parameter can either be an integer or numpy SeedSequence, which +will then be used to create seeds for each trajectories. Or a list of +interger/SeedSequence with one seed for each trajectories. Seeds available in +the result object can be used to redo the same evolution: - H = 2*np.pi*a.dag()*a + 2*np.pi*sm.dag()*sm + 2*np.pi*0.25*(sm*a.dag() + sm.dag()*a) - solver = MCSolver(H, c_ops=[np.sqrt(0.1) * a]) - data1 = solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=100) - psi1 = tensor(fock(2, 0), coherent(10, 2 - 1j)) - data2 = solver.run(psi1, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=100) - plt.figure() - plt.plot(times, data1.expect[0], "b", times, data1.expect[1], "r", lw=2) - plt.plot(times, data2.expect[0], 'b--', times, data2.expect[1], 'r--', lw=2) - plt.title('Monte Carlo time evolution') - plt.xlabel('Time', fontsize=14) - plt.ylabel('Expectation values', fontsize=14) - plt.legend(("cavity photon number", "atom excitation probability")) - plt.show() +.. code-block:: -.. guide-dynamics-mc2: + >>> res1 = mcsolve(H, psi0, tlist, c_ops, e_ops=e_ops, ntraj=10) + >>> res2 = mcsolve(H, psi0, tlist, c_ops, e_ops=e_ops, seeds=res1.seeds, ntraj=10) + >>> np.allclose(res1, res2) + True -The ``MCSolver`` also allows adding new trajectories after the first computation. -This is shown in the next example where the results of two separated runs with -identical conditions are merged into a single ``result`` object. -.. plot:: - :context: close-figs +.. monte-parallel: - times = np.linspace(0.0, 10.0, 200) - psi0 = tensor(fock(2, 0), fock(10, 5)) - a = tensor(qeye(2), destroy(10)) - sm = tensor(destroy(2), qeye(10)) +Running trajectories in parallel +-------------------------------- - H = 2*np.pi*a.dag()*a + 2*np.pi*sm.dag()*sm + 2*np.pi*0.25*(sm*a.dag() + sm.dag()*a) - solver = MCSolver(H, c_ops=[np.sqrt(0.1) * a]) - data1 = solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=1, seed=1) - data2 = solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=1, seed=3) - data_merged = data1 + data2 +Monte-Carlo evolutions often need hundreds of trajectories to obtain sufficient +statistics. Since all trajectories are independent of each other, they can be computed +in parallel. The option ``map`` can take ``"serial"``, ``"parallel"`` or ``"loky"``. +Both ``"parallel"`` and ``"loky"`` compute trajectories on multiple cpus using +respectively the multiprocessing and loky python modules. - plt.figure() - plt.plot(times, data1.expect[0], 'b-', times, data1.expect[1], 'g-', lw=2) - plt.plot(times, data2.expect[0], 'b--', times, data2.expect[1], 'g--', lw=2) - plt.plot(times, data_merged.expect[0], 'b:', times, data_merged.expect[1], 'g:', lw=2) - plt.title('Monte Carlo time evolution') - plt.xlabel('Time', fontsize=14) - plt.ylabel('Expectation values', fontsize=14) - plt.legend(("cavity photon number", "atom excitation probability")) - plt.show() +.. code-block:: + >>> res_parallel = mcsolve(H, psi0, tlist, c_ops, e_ops=e_ops, options={"map": "parallel"}, seeds=1) + >>> res_serial = mcsolve(H, psi0, tlist, c_ops, e_ops=e_ops, options={"map": "serial"}, seeds=1) + >>> np.allclose(res_parallel.average_expect, res_serial.average_expect) + True -This can be used to explore the convergence of the Monte Carlo solver. -For example, the following code block plots expectation values for 1, 10 and 100 -trajectories: +Note the when running in parallel, the order in which the trajectories are added +to the result can differ. Therefore -.. plot:: - :context: close-figs +.. code-block:: - solver = MCSolver(H, c_ops=[np.sqrt(0.1) * a]) + >>> print(res_parallel.seeds[:3]) + [SeedSequence(entropy=1,spawn_key=(1,),), + SeedSequence(entropy=1,spawn_key=(0,),), + SeedSequence(entropy=1,spawn_key=(2,),)] - data1 = solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=1) - data10 = data1 + solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=9) - data100 = data10 + solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=90) + >>> print(res_serial.seeds[:3]) + [SeedSequence(entropy=1,spawn_key=(0,),), + SeedSequence(entropy=1,spawn_key=(1,),), + SeedSequence(entropy=1,spawn_key=(2,),)] - expt1 = data1.expect - expt10 = data10.expect - expt100 = data100.expect - - plt.figure() - plt.plot(times, expt1[0], label="ntraj=1") - plt.plot(times, expt10[0], label="ntraj=10") - plt.plot(times, expt100[0], label="ntraj=100") - plt.title('Monte Carlo time evolution') - plt.xlabel('Time') - plt.ylabel('Expectation values') - plt.legend() - plt.show() .. openmcsolve: @@ -406,131 +434,6 @@ dissipative interaction instead of an Hamiltonian. plt.show() -.. _monte-nonmarkov: - -Monte Carlo for Non-Markovian Dynamics --------------------------------------- - -The Monte Carlo solver of QuTiP can also be used to solve the dynamics of -time-local non-Markovian master equations, i.e., master equations of the Lindblad -form - -.. math:: - :label: lindblad_master_equation_with_rates - - \dot\rho(t) = -\frac{i}{\hbar} [H, \rho(t)] + \sum_n \frac{\gamma_n(t)}{2} \left[2 A_n \rho(t) A_n^\dagger - \rho(t) A_n^\dagger A_n - A_n^\dagger A_n \rho(t)\right] - -with "rates" :math:`\gamma_n(t)` that can take negative values. -This can be done with the :func:`.nm_mcsolve` function. -The function is based on the influence martingale formalism [Donvil22]_ and -formally requires that the collapse operators :math:`A_n` satisfy a completeness -relation of the form - -.. math:: - :label: nmmcsolve_completeness - - \sum_n A_n^\dagger A_n = \alpha \mathbb{I} , - -where :math:`\mathbb{I}` is the identity operator on the system Hilbert space -and :math:`\alpha>0`. -Note that when the collapse operators of a model don't satisfy such a relation, -``nm_mcsolve`` automatically adds an extra collapse operator such that -:eq:`nmmcsolve_completeness` is satisfied. -The rate corresponding to this extra collapse operator is set to zero. - -Technically, the influence martingale formalism works as follows. -We introduce an influence martingale :math:`\mu(t)`, which follows the evolution -of the system state. When no jump happens, it evolves as - -.. math:: - :label: influence_cont - - \mu(t) = \exp\left( \alpha\int_0^t K(\tau) d\tau \right) - -where :math:`K(t)` is for now an arbitrary function. -When a jump corresponding to the collapse operator :math:`A_n` happens, the -influence martingale becomes - -.. math:: - :label: influence_disc - - \mu(t+\delta t) = \mu(t)\left(\frac{K(t)-\gamma_n(t)}{\gamma_n(t)}\right) - -Assuming that the state :math:`\bar\rho(t)` computed by the Monte Carlo average - -.. math:: - :label: mc_paired_state - - \bar\rho(t) = \frac{1}{N}\sum_{l=1}^N |\psi_l(t)\rangle\langle \psi_l(t)| - -solves a Lindblad master equation with collapse operators :math:`A_n` and rates -:math:`\Gamma_n(t)`, the state :math:`\rho(t)` defined by - -.. math:: - :label: mc_martingale_state - - \rho(t) = \frac{1}{N}\sum_{l=1}^N \mu_l(t) |\psi_l(t)\rangle\langle \psi_l(t)| - -solves a Lindblad master equation with collapse operators :math:`A_n` and shifted -rates :math:`\gamma_n(t)-K(t)`. Thus, while :math:`\Gamma_n(t) \geq 0`, the new -"rates" :math:`\gamma_n(t) = \Gamma_n(t) - K(t)` satisfy no positivity requirement. - -The input of :func:`.nm_mcsolve` is almost the same as for :func:`.mcsolve`. -The only difference is how the collapse operators and rate functions should be -defined. ``nm_mcsolve`` requires collapse operators :math:`A_n` and target "rates" -:math:`\gamma_n` (which are allowed to take negative values) to be given in list -form ``[[C_1, gamma_1], [C_2, gamma_2], ...]``. Note that we give the actual -rate and not its square root, and that ``nm_mcsolve`` automatically computes -associated jump rates :math:`\Gamma_n(t)\geq0` appropriate for simulation. - -We conclude with a simple example demonstrating the usage of the ``nm_mcsolve`` -function. For more elaborate, physically motivated examples, we refer to the -`accompanying tutorial notebook `_. - - -.. plot:: - :context: reset - - times = np.linspace(0, 1, 201) - psi0 = basis(2, 1) - a0 = destroy(2) - H = a0.dag() * a0 - - # Rate functions - gamma1 = "kappa * nth" - gamma2 = "kappa * (nth+1) + 12 * np.exp(-2*t**3) * (-np.sin(15*t)**2)" - # gamma2 becomes negative during some time intervals - - # nm_mcsolve integration - ops_and_rates = [] - ops_and_rates.append([a0.dag(), gamma1]) - ops_and_rates.append([a0, gamma2]) - MCSol = nm_mcsolve(H, psi0, times, ops_and_rates, - args={'kappa': 1.0 / 0.129, 'nth': 0.063}, - e_ops=[a0.dag() * a0, a0 * a0.dag()], - options={'map': 'parallel'}, ntraj=2500) - - # mesolve integration for comparison - d_ops = [[lindblad_dissipator(a0.dag(), a0.dag()), gamma1], - [lindblad_dissipator(a0, a0), gamma2]] - MESol = mesolve(H, psi0, times, d_ops, e_ops=[a0.dag() * a0, a0 * a0.dag()], - args={'kappa': 1.0 / 0.129, 'nth': 0.063}) - - plt.figure() - plt.plot(times, MCSol.expect[0], 'g', - times, MCSol.expect[1], 'b', - times, MCSol.trace, 'r') - plt.plot(times, MESol.expect[0], 'g--', - times, MESol.expect[1], 'b--') - plt.title('Monte Carlo time evolution') - plt.xlabel('Time') - plt.ylabel('Expectation values') - plt.legend((r'$\langle 1 | \rho | 1 \rangle$', - r'$\langle 0 | \rho | 0 \rangle$', - r'$\operatorname{tr} \rho$')) - plt.show() - - .. plot:: :context: reset :include-source: false diff --git a/doc/guide/dynamics/dynamics-nmmonte.rst b/doc/guide/dynamics/dynamics-nmmonte.rst new file mode 100644 index 0000000000..fb8c0bcbde --- /dev/null +++ b/doc/guide/dynamics/dynamics-nmmonte.rst @@ -0,0 +1,130 @@ +.. _monte-nonmarkov: + +******************************************* +Monte Carlo for Non-Markovian Dynamics +******************************************* + +The Monte Carlo solver of QuTiP can also be used to solve the dynamics of +time-local non-Markovian master equations, i.e., master equations of the Lindblad +form + +.. math:: + :label: lindblad_master_equation_with_rates + + \dot\rho(t) = -\frac{i}{\hbar} [H, \rho(t)] + \sum_n \frac{\gamma_n(t)}{2} \left[2 A_n \rho(t) A_n^\dagger - \rho(t) A_n^\dagger A_n - A_n^\dagger A_n \rho(t)\right] + +with "rates" :math:`\gamma_n(t)` that can take negative values. +This can be done with the :func:`.nm_mcsolve` function. +The function is based on the influence martingale formalism [Donvil22]_ and +formally requires that the collapse operators :math:`A_n` satisfy a completeness +relation of the form + +.. math:: + :label: nmmcsolve_completeness + + \sum_n A_n^\dagger A_n = \alpha \mathbb{I} , + +where :math:`\mathbb{I}` is the identity operator on the system Hilbert space +and :math:`\alpha>0`. +Note that when the collapse operators of a model don't satisfy such a relation, +``nm_mcsolve`` automatically adds an extra collapse operator such that +:eq:`nmmcsolve_completeness` is satisfied. +The rate corresponding to this extra collapse operator is set to zero. + +Technically, the influence martingale formalism works as follows. +We introduce an influence martingale :math:`\mu(t)`, which follows the evolution +of the system state. When no jump happens, it evolves as + +.. math:: + :label: influence_cont + + \mu(t) = \exp\left( \alpha\int_0^t K(\tau) d\tau \right) + +where :math:`K(t)` is for now an arbitrary function. +When a jump corresponding to the collapse operator :math:`A_n` happens, the +influence martingale becomes + +.. math:: + :label: influence_disc + + \mu(t+\delta t) = \mu(t)\left(\frac{K(t)-\gamma_n(t)}{\gamma_n(t)}\right) + +Assuming that the state :math:`\bar\rho(t)` computed by the Monte Carlo average + +.. math:: + :label: mc_paired_state + + \bar\rho(t) = \frac{1}{N}\sum_{l=1}^N |\psi_l(t)\rangle\langle \psi_l(t)| + +solves a Lindblad master equation with collapse operators :math:`A_n` and rates +:math:`\Gamma_n(t)`, the state :math:`\rho(t)` defined by + +.. math:: + :label: mc_martingale_state + + \rho(t) = \frac{1}{N}\sum_{l=1}^N \mu_l(t) |\psi_l(t)\rangle\langle \psi_l(t)| + +solves a Lindblad master equation with collapse operators :math:`A_n` and shifted +rates :math:`\gamma_n(t)-K(t)`. Thus, while :math:`\Gamma_n(t) \geq 0`, the new +"rates" :math:`\gamma_n(t) = \Gamma_n(t) - K(t)` satisfy no positivity requirement. + +The input of :func:`.nm_mcsolve` is almost the same as for :func:`.mcsolve`. +The only difference is how the collapse operators and rate functions should be +defined. ``nm_mcsolve`` requires collapse operators :math:`A_n` and target "rates" +:math:`\gamma_n` (which are allowed to take negative values) to be given in list +form ``[[C_1, gamma_1], [C_2, gamma_2], ...]``. Note that we give the actual +rate and not its square root, and that ``nm_mcsolve`` automatically computes +associated jump rates :math:`\Gamma_n(t)\geq0` appropriate for simulation. + +We conclude with a simple example demonstrating the usage of the ``nm_mcsolve`` +function. For more elaborate, physically motivated examples, we refer to the +`accompanying tutorial notebook `_. + + +.. plot:: + :context: reset + + times = np.linspace(0, 1, 201) + psi0 = basis(2, 1) + a0 = destroy(2) + H = a0.dag() * a0 + + # Rate functions + gamma1 = "kappa * nth" + gamma2 = "kappa * (nth+1) + 12 * np.exp(-2*t**3) * (-np.sin(15*t)**2)" + # gamma2 becomes negative during some time intervals + + # nm_mcsolve integration + ops_and_rates = [] + ops_and_rates.append([a0.dag(), gamma1]) + ops_and_rates.append([a0, gamma2]) + MCSol = nm_mcsolve(H, psi0, times, ops_and_rates, + args={'kappa': 1.0 / 0.129, 'nth': 0.063}, + e_ops=[a0.dag() * a0, a0 * a0.dag()], + options={'map': 'parallel'}, ntraj=2500) + + # mesolve integration for comparison + d_ops = [[lindblad_dissipator(a0.dag(), a0.dag()), gamma1], + [lindblad_dissipator(a0, a0), gamma2]] + MESol = mesolve(H, psi0, times, d_ops, e_ops=[a0.dag() * a0, a0 * a0.dag()], + args={'kappa': 1.0 / 0.129, 'nth': 0.063}) + + plt.figure() + plt.plot(times, MCSol.expect[0], 'g', + times, MCSol.expect[1], 'b', + times, MCSol.trace, 'r') + plt.plot(times, MESol.expect[0], 'g--', + times, MESol.expect[1], 'b--') + plt.title('Monte Carlo time evolution') + plt.xlabel('Time') + plt.ylabel('Expectation values') + plt.legend((r'$\langle 1 | \rho | 1 \rangle$', + r'$\langle 0 | \rho | 0 \rangle$', + r'$\operatorname{tr} \rho$')) + plt.show() + + +.. plot:: + :context: reset + :include-source: false + :nofigs: diff --git a/doc/guide/guide-dynamics.rst b/doc/guide/guide-dynamics.rst index 8f6d85538c..679495d047 100644 --- a/doc/guide/guide-dynamics.rst +++ b/doc/guide/guide-dynamics.rst @@ -16,5 +16,6 @@ Time Evolution and Quantum System Dynamics dynamics/dynamics-time.rst dynamics/dynamics-bloch-redfield.rst dynamics/dynamics-floquet.rst - dynamics/dynamics-piqs.rst + dynamics/dynamics-nmmonte.rst dynamics/dynamics-options.rst + dynamics/dynamics-class-api.rst diff --git a/doc/guide/guide.rst b/doc/guide/guide.rst index 39fdc1ccd3..9ecb149e22 100644 --- a/doc/guide/guide.rst +++ b/doc/guide/guide.rst @@ -15,11 +15,11 @@ Users Guide guide-dynamics.rst guide-heom.rst guide-steady.rst + guide-piqs.rst guide-correlation.rst guide-control.rst guide-bloch.rst guide-visualization.rst - guide-parfor.rst guide-saving.rst guide-random.rst guide-settings.rst From fd10c31ed6f6e0726c943b1fe4243197de77f4e7 Mon Sep 17 00:00:00 2001 From: Tobias Schmale Date: Fri, 24 Nov 2023 14:55:23 +0100 Subject: [PATCH 102/247] fix simdiag not returning orthonormal eigenvectors --- qutip/simdiag.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/simdiag.py b/qutip/simdiag.py index dbd5e1fd56..63d2a6e107 100644 --- a/qutip/simdiag.py +++ b/qutip/simdiag.py @@ -22,7 +22,7 @@ def _degen(tol, vecs, ops, i=0): / (1 - np.abs(dot)**2)**0.5) subspace = vecs.conj().T @ ops[i].full() @ vecs - eigvals, eigvecs = la.eig(subspace) + eigvals, eigvecs = la.eigh(subspace) perm = np.argsort(eigvals) eigvals = eigvals[perm] From 171430a4d3fb4ad6ae45fafeb23787716838ae5d Mon Sep 17 00:00:00 2001 From: Tobias Schmale Date: Fri, 24 Nov 2023 15:18:25 +0100 Subject: [PATCH 103/247] include changelog --- doc/changes/2269.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/2269.bugfix diff --git a/doc/changes/2269.bugfix b/doc/changes/2269.bugfix new file mode 100644 index 0000000000..48c2b06fa5 --- /dev/null +++ b/doc/changes/2269.bugfix @@ -0,0 +1 @@ +Fixed simdiag not returning orthonormal eigenvectors. \ No newline at end of file From aab47310b7b6c45d07604be5c1a4c42767a1667b Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 24 Nov 2023 15:23:53 -0500 Subject: [PATCH 104/247] Fix seed and link between guide pages --- doc/guide/dynamics/dynamics-class-api.rst | 12 +-- doc/guide/dynamics/dynamics-monte.rst | 86 +++++++++++----------- doc/guide/dynamics/dynamics-stochastic.rst | 6 ++ doc/guide/guide.rst | 1 + qutip/solver/mcsolve.py | 8 +- qutip/solver/multitraj.py | 10 +-- qutip/solver/nm_mcsolve.py | 20 ++--- qutip/solver/stochastic.py | 4 +- 8 files changed, 74 insertions(+), 73 deletions(-) diff --git a/doc/guide/dynamics/dynamics-class-api.rst b/doc/guide/dynamics/dynamics-class-api.rst index 4ad74a6f5c..eea99b3913 100644 --- a/doc/guide/dynamics/dynamics-class-api.rst +++ b/doc/guide/dynamics/dynamics-class-api.rst @@ -1,12 +1,14 @@ +.. _solver_api: -.. _monte-reuse: + +******************************************* +Solver Class Interface +******************************************* Reusing Hamiltonian Data ------------------------ -.. note:: This section covers a specialized topic and may be skipped if you are new to QuTiP. - In order to solve a given simulation as fast as possible, the solvers in QuTiP take the given input operators and break them down into simpler components before @@ -59,8 +61,8 @@ identical conditions are merged into a single ``result`` object. H = 2*np.pi*a.dag()*a + 2*np.pi*sm.dag()*sm + 2*np.pi*0.25*(sm*a.dag() + sm.dag()*a) solver = MCSolver(H, c_ops=[np.sqrt(0.1) * a]) - data1 = solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=1, seed=1) - data2 = solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=1, seed=3) + data1 = solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=1, seeds=1) + data2 = solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=1, seeds=3) data_merged = data1 + data2 plt.figure() diff --git a/doc/guide/dynamics/dynamics-monte.rst b/doc/guide/dynamics/dynamics-monte.rst index 954cf537ae..df1ae29229 100644 --- a/doc/guide/dynamics/dynamics-monte.rst +++ b/doc/guide/dynamics/dynamics-monte.rst @@ -166,33 +166,6 @@ the collapse. These can be obtained in ``result.col_times`` and ``result.col_which`` respectively. -Photocurrent ------------- - -The photocurrent, previously computed using the ``photocurrent_sesolve`` and -``photocurrent_sesolve`` functions, are now included in the output of -:func:`.mcsolve` as ``result.photocurrent``. - - -.. plot:: - :context: close-figs - - times = np.linspace(0.0, 10.0, 200) - psi0 = tensor(fock(2, 0), fock(10, 8)) - a = tensor(qeye(2), destroy(10)) - sm = tensor(destroy(2), qeye(10)) - e_ops = [a.dag() * a, sm.dag() * sm] - H = 2*np.pi*a.dag()*a + 2*np.pi*sm.dag()*sm + 2*np.pi*0.25*(sm*a.dag() + sm.dag()*a) - data = mcsolve(H, psi0, times, [np.sqrt(0.1) * a], e_ops=e_ops) - - plt.figure() - plt.plot((times[:-1] + times[1:])/2, data.photocurrent[0]) - plt.title('Monte Carlo Photocurrent') - plt.xlabel('Time') - plt.ylabel('Photon detections') - plt.show() - - .. _monte-ntraj: Changing the Number of Trajectories @@ -205,10 +178,9 @@ does not take an excessive amount of time to run. However, you can change the number of trajectories to fit your needs. In order to run 1000 trajectories in the above example, we can simply modify the call to ``mcsolve`` like: -.. plot:: - :context: close-figs +.. code-block:: - data = mcsolve(H, psi0, times, [np.sqrt(0.1) * a], e_ops=e_ops, ntraj=1000) + data = mcsolve(H, psi0, times, c_ops e_ops=e_ops, ntraj=1000) where we have added the keyword argument ``ntraj=1000`` at the end of the inputs. Now, the Monte Carlo solver will calculate expectation values for both operators, @@ -226,7 +198,7 @@ new trajectories when it is reached. Thus: data = mcsolve(H, psi0, times, [np.sqrt(0.1) * a], e_ops=e_ops, ntraj=1000, timeout=60) -Will compute 1 minute of trajectories or 1000, which ever is reached first. +Will compute 60 seconds of trajectories or 1000, which ever is reached first. The solver will finish any trajectory started when the timeout is reached. Therefore if the computation time of a single trajectory is quite long, the overall computation time can be much longer that the provided timeout. @@ -234,7 +206,7 @@ time can be much longer that the provided timeout. Lastly, ``mcsolve`` can be instructed to stop when the statistical error of the expectation values get under a certain value. When computing the average over trajectories, the error on these are computed using -:ref:`Jackknife resampling ` +`jackknife resampling `_ for each expect and each time and the computation will be stopped when all these values are under the tolerance passed to ``target_tol``. Therefore: @@ -244,7 +216,7 @@ are under the tolerance passed to ``target_tol``. Therefore: ntraj=1000, target_tol=0.01, timeout=600) will stop either after all errors bars on expectation values are under ``0.01``, 1000 -trajectories are computed or 10 min have passed, whichever comes first. When a +trajectories are computed or 10 minutes have passed, whichever comes first. When a single values is passed, it is used as the absolute value of the tolerance. When a pair of values is passed, it is understood as an absolute and relative tolerance pair. For even finer control, one such pair can be passed for each ``e_ops``. @@ -341,10 +313,10 @@ The original sampling algorithm samples the no-jump trajectory on average 96.7% of the time, while the improved sampling algorithm only does so once. -.. monte-seeds: +.. _monte-seeds: -Reproducibility with Monte-Carlo --------------------------------- +Reproducibility +--------------- For reproducibility of Monte-Carlo computations it is possible to set the seed @@ -372,7 +344,7 @@ the result object can be used to redo the same evolution: True -.. monte-parallel: +.. _monte-parallel: Running trajectories in parallel -------------------------------- @@ -381,13 +353,14 @@ Monte-Carlo evolutions often need hundreds of trajectories to obtain sufficient statistics. Since all trajectories are independent of each other, they can be computed in parallel. The option ``map`` can take ``"serial"``, ``"parallel"`` or ``"loky"``. Both ``"parallel"`` and ``"loky"`` compute trajectories on multiple cpus using -respectively the multiprocessing and loky python modules. +respectively the `multiprocessing `_ +and `loky `_ python modules. .. code-block:: - >>> res_parallel = mcsolve(H, psi0, tlist, c_ops, e_ops=e_ops, options={"map": "parallel"}, seeds=1) - >>> res_serial = mcsolve(H, psi0, tlist, c_ops, e_ops=e_ops, options={"map": "serial"}, seeds=1) - >>> np.allclose(res_parallel.average_expect, res_serial.average_expect) + >>> res_par = mcsolve(H, psi0, tlist, c_ops, e_ops=e_ops, options={"map": "parallel"}, seeds=1) + >>> res_ser = mcsolve(H, psi0, tlist, c_ops, e_ops=e_ops, options={"map": "serial"}, seeds=1) + >>> np.allclose(res_par.average_expect, res_ser.average_expect) True Note the when running in parallel, the order in which the trajectories are added @@ -395,17 +368,44 @@ to the result can differ. Therefore .. code-block:: - >>> print(res_parallel.seeds[:3]) + >>> print(res_par.seeds[:3]) [SeedSequence(entropy=1,spawn_key=(1,),), SeedSequence(entropy=1,spawn_key=(0,),), SeedSequence(entropy=1,spawn_key=(2,),)] - >>> print(res_serial.seeds[:3]) + >>> print(res_ser.seeds[:3]) [SeedSequence(entropy=1,spawn_key=(0,),), SeedSequence(entropy=1,spawn_key=(1,),), SeedSequence(entropy=1,spawn_key=(2,),)] +Photocurrent +------------ + +The photocurrent, previously computed using the ``photocurrent_sesolve`` and +``photocurrent_sesolve`` functions, are now included in the output of +:func:`.mcsolve` as ``result.photocurrent``. + + +.. plot:: + :context: close-figs + + times = np.linspace(0.0, 10.0, 200) + psi0 = tensor(fock(2, 0), fock(10, 8)) + a = tensor(qeye(2), destroy(10)) + sm = tensor(destroy(2), qeye(10)) + e_ops = [a.dag() * a, sm.dag() * sm] + H = 2*np.pi*a.dag()*a + 2*np.pi*sm.dag()*sm + 2*np.pi*0.25*(sm*a.dag() + sm.dag()*a) + data = mcsolve(H, psi0, times, [np.sqrt(0.1) * a], e_ops=e_ops) + + plt.figure() + plt.plot((times[:-1] + times[1:])/2, data.photocurrent[0]) + plt.title('Monte Carlo Photocurrent') + plt.xlabel('Time') + plt.ylabel('Photon detections') + plt.show() + + .. openmcsolve: Open Systems diff --git a/doc/guide/dynamics/dynamics-stochastic.rst b/doc/guide/dynamics/dynamics-stochastic.rst index f62e1b6474..975c1e96bd 100644 --- a/doc/guide/dynamics/dynamics-stochastic.rst +++ b/doc/guide/dynamics/dynamics-stochastic.rst @@ -190,6 +190,12 @@ in ``result.measurements``. `inefficient detection <...>`_, and `feedback control `_. + +The stochastic solvers share many features with :func:`.mcsolve`, such as +end conditions, seed control and running in parallel. See the sections +:ref:`monte-ntraj`, :ref:`monte-seeds` and :ref:`monte-parallel` for detail. + + .. plot:: :context: reset :include-source: false diff --git a/doc/guide/guide.rst b/doc/guide/guide.rst index 9ecb149e22..820a82fa07 100644 --- a/doc/guide/guide.rst +++ b/doc/guide/guide.rst @@ -20,6 +20,7 @@ Users Guide guide-control.rst guide-bloch.rst guide-visualization.rst + guide-parfor.rst guide-saving.rst guide-random.rst guide-settings.rst diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index 3649e52999..9d38030374 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -160,7 +160,7 @@ def mcsolve(H, state, tlist, c_ops=(), e_ops=None, ntraj=500, *, mc = MCSolver(H, c_ops, options=options) result = mc.run(state, tlist=tlist, ntraj=ntraj, e_ops=e_ops, - seed=seeds, target_tol=target_tol, timeout=timeout) + seeds=seeds, target_tol=target_tol, timeout=timeout) return result @@ -469,7 +469,7 @@ def _run_one_traj(self, seed, state, tlist, e_ops, no_jump=False, return seed, result def run(self, state, tlist, ntraj=1, *, - args=None, e_ops=(), timeout=None, target_tol=None, seed=None): + args=None, e_ops=(), timeout=None, target_tol=None, seeds=None): """ Do the evolution of the Quantum system. See the overridden method for further details. The modification @@ -480,7 +480,7 @@ def run(self, state, tlist, ntraj=1, *, if not self.options.get("improved_sampling", False): return super().run(state, tlist, ntraj=ntraj, args=args, e_ops=e_ops, timeout=timeout, - target_tol=target_tol, seed=seed) + target_tol=target_tol, seeds=seeds) stats, seeds, result, map_func, map_kw, state0 = self._initialize_run( state, ntraj, @@ -488,7 +488,7 @@ def run(self, state, tlist, ntraj=1, *, e_ops=e_ops, timeout=timeout, target_tol=target_tol, - seed=seed, + seeds=seeds, ) # first run the no-jump trajectory start_time = time() diff --git a/qutip/solver/multitraj.py b/qutip/solver/multitraj.py index 86f3324162..aca4a0a8bb 100644 --- a/qutip/solver/multitraj.py +++ b/qutip/solver/multitraj.py @@ -103,11 +103,11 @@ def step(self, t, *, args=None, copy=True): return self._restore_state(state, copy=copy) def _initialize_run(self, state, ntraj=1, args=None, e_ops=(), - timeout=None, target_tol=None, seed=None): + timeout=None, target_tol=None, seeds=None): start_time = time() self._argument(args) stats = self._initialize_stats() - seeds = self._read_seed(seed, ntraj) + seeds = self._read_seed(seeds, ntraj) result = self._resultclass( e_ops, self.options, solver=self.name, stats=stats @@ -125,7 +125,7 @@ def _initialize_run(self, state, ntraj=1, args=None, e_ops=(), return stats, seeds, result, map_func, map_kw, state0 def run(self, state, tlist, ntraj=1, *, - args=None, e_ops=(), timeout=None, target_tol=None, seed=None): + args=None, e_ops=(), timeout=None, target_tol=None, seeds=None): """ Do the evolution of the Quantum system. @@ -172,7 +172,7 @@ def run(self, state, tlist, ntraj=1, *, of absolute and relative tolerance, in that order. Lastly, it can be a list of pairs of (atol, rtol) for each e_ops. - seed : {int, SeedSequence, list}, optional + seeds : {int, SeedSequence, list}, optional Seed or list of seeds for each trajectories. Returns @@ -192,7 +192,7 @@ def run(self, state, tlist, ntraj=1, *, e_ops=e_ops, timeout=timeout, target_tol=target_tol, - seed=seed, + seeds=seeds, ) start_time = time() map_func( diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index 0f982e3946..8fb5de497a 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -186,7 +186,7 @@ def nm_mcsolve(H, state, tlist, ops_and_rates=(), e_ops=None, ntraj=500, *, nmmc = NonMarkovianMCSolver(H, ops_and_rates, options=options) result = nmmc.run(state, tlist=tlist, ntraj=ntraj, e_ops=e_ops, - seed=seeds, target_tol=target_tol, timeout=timeout) + seeds=seeds, target_tol=target_tol, timeout=timeout) return result @@ -323,13 +323,6 @@ class NonMarkovianMCSolver(MCSolver): options : SolverOptions, [optional] Options for the evolution. - - seed : int, SeedSequence, list, [optional] - Seed for the random number generator. It can be a single seed used to - spawn seeds for each trajectory or a list of seed, one for each - trajectory. Seeds are saved in the result and can be reused with:: - - seeds=prev_result.seeds """ name = "nm_mcsolve" _resultclass = NmmcResult @@ -346,7 +339,7 @@ class NonMarkovianMCSolver(MCSolver): _mc_integrator_class = NmMCIntegrator def __init__( - self, H, ops_and_rates, *_args, args=None, options=None, **kwargs, + self, H, ops_and_rates, args=None, options=None, ): self.options = options @@ -383,7 +376,7 @@ def __init__( self._mc_integrator_class = functools.partial( NmMCIntegrator, __martingale=self._martingale, ) - super().__init__(H, c_ops, *_args, options=options, **kwargs) + super().__init__(H, c_ops, options=options) def _check_completeness(self, ops_and_rates): """ @@ -516,13 +509,12 @@ def step(self, t, *, args=None, copy=True): state = ket2dm(state) return state * self.current_martingale() - def run(self, state, tlist, *args, **kwargs): + def run(self, state, tlist, ntraj=1, *, args=None, **kwargs): # update `args` dictionary before precomputing martingale - if 'args' in kwargs: - self._argument(kwargs.pop('args')) + self._argument(args) self._martingale.initialize(tlist[0], cache=tlist) - result = super().run(state, tlist, *args, **kwargs) + result = super().run(state, tlist, ntraj, **kwargs) self._martingale.reset() return result diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index b9b7152f1e..e43766c247 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -344,7 +344,7 @@ def smesolve( ) return sol.run( rho0, tlist, ntraj, e_ops=e_ops, - seed=seeds, target_tol=target_tol, timeout=timeout, + seeds=seeds, target_tol=target_tol, timeout=timeout, ) @@ -463,7 +463,7 @@ def ssesolve( sol = SSESolver(H, sc_ops, options=options, heterodyne=heterodyne) return sol.run( psi0, tlist, ntraj, e_ops=e_ops, - seed=seeds, target_tol=target_tol, timeout=timeout, + seeds=seeds, target_tol=target_tol, timeout=timeout, ) From ac7af295f59f7e50c95a1f8ebdd5c33c82508999 Mon Sep 17 00:00:00 2001 From: Tobias Schmale Date: Mon, 27 Nov 2023 17:54:34 +0100 Subject: [PATCH 105/247] add test --- qutip/tests/test_simdiag.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/qutip/tests/test_simdiag.py b/qutip/tests/test_simdiag.py index a45076fd60..cad8f589f5 100644 --- a/qutip/tests/test_simdiag.py +++ b/qutip/tests/test_simdiag.py @@ -73,6 +73,28 @@ def test_simdiag_degen_large(): atol=1e-13 ) +def test_simdiag_orthonormal_eigenvectors(): + + # Special matrix that used to be problematic (see Issue #2268) + a = np.array([[1, 0, 1, -1, 0], + [0, 4, 0, 0, 1], + [1, 0, 4, 1, 0], + [-1, 0, 1, 4, 0], + [0, 1, 0, 0, 4]]) + + b = np.eye(5) + + _, evecs = qutip.simdiag([qutip.Qobj(a), qutip.Qobj(b)]) + evecs = np.array([evec.full() for evec in evecs]).squeeze() + + # Check that eigenvectors form an othonormal basis + # (<=> matrix of eigenvectors is unitary) + np.testing.assert_allclose( + evecs@evecs.conj().T, + np.eye(len(evecs)), + atol=1e-13 + ) + def test_simdiag_no_input(): with pytest.raises(ValueError): From b954cd335124ccaf1ef29f3cee0782efc92c6412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Mon, 27 Nov 2023 12:16:06 -0500 Subject: [PATCH 106/247] Update qutip/tests/test_simdiag.py --- qutip/tests/test_simdiag.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/qutip/tests/test_simdiag.py b/qutip/tests/test_simdiag.py index cad8f589f5..16fc6682e2 100644 --- a/qutip/tests/test_simdiag.py +++ b/qutip/tests/test_simdiag.py @@ -82,9 +82,7 @@ def test_simdiag_orthonormal_eigenvectors(): [-1, 0, 1, 4, 0], [0, 1, 0, 0, 4]]) - b = np.eye(5) - - _, evecs = qutip.simdiag([qutip.Qobj(a), qutip.Qobj(b)]) + _, evecs = qutip.simdiag([qutip.Qobj(a), qutip.qeye(5)]) evecs = np.array([evec.full() for evec in evecs]).squeeze() # Check that eigenvectors form an othonormal basis From d5faf32812d3938b3b5175d6cfee71bfbb5043c1 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 27 Nov 2023 12:38:10 -0500 Subject: [PATCH 107/247] finish class page --- doc/guide/dynamics/dynamics-class-api.rst | 104 ---------------------- doc/guide/dynamics/dynamics-monte.rst | 30 +++++++ doc/guide/guide-dynamics.rst | 2 +- 3 files changed, 31 insertions(+), 105 deletions(-) delete mode 100644 doc/guide/dynamics/dynamics-class-api.rst diff --git a/doc/guide/dynamics/dynamics-class-api.rst b/doc/guide/dynamics/dynamics-class-api.rst deleted file mode 100644 index eea99b3913..0000000000 --- a/doc/guide/dynamics/dynamics-class-api.rst +++ /dev/null @@ -1,104 +0,0 @@ -.. _solver_api: - - - -******************************************* -Solver Class Interface -******************************************* - -Reusing Hamiltonian Data ------------------------- - - -In order to solve a given simulation as fast as possible, the solvers in QuTiP -take the given input operators and break them down into simpler components before -passing them on to the ODE solvers. Although these operations are reasonably fast, -the time spent organizing data can become appreciable when repeatedly solving a -system over, for example, many different initial conditions. In cases such as -this, the Monte Carlo Solver may be reused after the initial configuration, thus -speeding up calculations. - - -Using the previous example, we will calculate the dynamics for two different -initial states, with the Hamiltonian data being reused on the second call - -.. plot:: - :context: close-figs - - times = np.linspace(0.0, 10.0, 200) - psi0 = tensor(fock(2, 0), fock(10, 5)) - a = tensor(qeye(2), destroy(10)) - sm = tensor(destroy(2), qeye(10)) - - H = 2*np.pi*a.dag()*a + 2*np.pi*sm.dag()*sm + 2*np.pi*0.25*(sm*a.dag() + sm.dag()*a) - solver = MCSolver(H, c_ops=[np.sqrt(0.1) * a]) - data1 = solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=100) - psi1 = tensor(fock(2, 0), coherent(10, 2 - 1j)) - data2 = solver.run(psi1, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=100) - - plt.figure() - plt.plot(times, data1.expect[0], "b", times, data1.expect[1], "r", lw=2) - plt.plot(times, data2.expect[0], 'b--', times, data2.expect[1], 'r--', lw=2) - plt.title('Monte Carlo time evolution') - plt.xlabel('Time', fontsize=14) - plt.ylabel('Expectation values', fontsize=14) - plt.legend(("cavity photon number", "atom excitation probability")) - plt.show() - -.. guide-dynamics-mc2: - -The ``MCSolver`` also allows adding new trajectories after the first computation. -This is shown in the next example where the results of two separated runs with -identical conditions are merged into a single ``result`` object. - -.. plot:: - :context: close-figs - - times = np.linspace(0.0, 10.0, 200) - psi0 = tensor(fock(2, 0), fock(10, 5)) - a = tensor(qeye(2), destroy(10)) - sm = tensor(destroy(2), qeye(10)) - - H = 2*np.pi*a.dag()*a + 2*np.pi*sm.dag()*sm + 2*np.pi*0.25*(sm*a.dag() + sm.dag()*a) - solver = MCSolver(H, c_ops=[np.sqrt(0.1) * a]) - data1 = solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=1, seeds=1) - data2 = solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=1, seeds=3) - data_merged = data1 + data2 - - plt.figure() - plt.plot(times, data1.expect[0], 'b-', times, data1.expect[1], 'g-', lw=2) - plt.plot(times, data2.expect[0], 'b--', times, data2.expect[1], 'g--', lw=2) - plt.plot(times, data_merged.expect[0], 'b:', times, data_merged.expect[1], 'g:', lw=2) - plt.title('Monte Carlo time evolution') - plt.xlabel('Time', fontsize=14) - plt.ylabel('Expectation values', fontsize=14) - plt.legend(("cavity photon number", "atom excitation probability")) - plt.show() - - -This can be used to explore the convergence of the Monte Carlo solver. -For example, the following code block plots expectation values for 1, 10 and 100 -trajectories: - -.. plot:: - :context: close-figs - - solver = MCSolver(H, c_ops=[np.sqrt(0.1) * a]) - - data1 = solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=1) - data10 = data1 + solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=9) - data100 = data10 + solver.run(psi0, times, e_ops=[a.dag() * a, sm.dag() * sm], ntraj=90) - - expt1 = data1.expect - expt10 = data10.expect - expt100 = data100.expect - - plt.figure() - plt.plot(times, expt1[0], label="ntraj=1") - plt.plot(times, expt10[0], label="ntraj=10") - plt.plot(times, expt100[0], label="ntraj=100") - plt.title('Monte Carlo time evolution') - plt.xlabel('Time') - plt.ylabel('Expectation values') - plt.legend() - plt.show() diff --git a/doc/guide/dynamics/dynamics-monte.rst b/doc/guide/dynamics/dynamics-monte.rst index df1ae29229..d3ce602a7f 100644 --- a/doc/guide/dynamics/dynamics-monte.rst +++ b/doc/guide/dynamics/dynamics-monte.rst @@ -252,6 +252,36 @@ same e_ops and same tlist. It does not check that the same initial state or Hamiltonian where used. +This can be used to explore the convergence of the Monte Carlo solver. +For example, the following code block plots expectation values for 1, 10 and 100 +trajectories: + +.. plot:: + :context: close-figs + + solver = MCSolver(H, c_ops=[np.sqrt(0.1) * a]) + c_ops=[np.sqrt(0.1) * a] + e_ops = [a.dag() * a, sm.dag() * sm] + + data1 = mcsolve(H, psi0, times, c_ops, e_ops=e_ops, ntraj=1) + data10 = data1 + mcsolve(H, psi0, times, c_ops, e_ops=e_ops, ntraj=9) + data100 = data10 + mcsolve(H, psi0, times, c_ops, e_ops=e_ops, ntraj=90) + + expt1 = data1.expect + expt10 = data10.expect + expt100 = data100.expect + + plt.figure() + plt.plot(times, expt1[0], label="ntraj=1") + plt.plot(times, expt10[0], label="ntraj=10") + plt.plot(times, expt100[0], label="ntraj=100") + plt.title('Monte Carlo time evolution') + plt.xlabel('Time') + plt.ylabel('Expectation values') + plt.legend() + plt.show() + + Using the Improved Sampling Algorithm ------------------------------------- diff --git a/doc/guide/guide-dynamics.rst b/doc/guide/guide-dynamics.rst index 679495d047..031b0fadbb 100644 --- a/doc/guide/guide-dynamics.rst +++ b/doc/guide/guide-dynamics.rst @@ -18,4 +18,4 @@ Time Evolution and Quantum System Dynamics dynamics/dynamics-floquet.rst dynamics/dynamics-nmmonte.rst dynamics/dynamics-options.rst - dynamics/dynamics-class-api.rst + dynamics/dynamics-class.rst From 30e0e9590ec67fd7c1674079eee6edbc20cbb3f5 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 27 Nov 2023 14:42:34 -0500 Subject: [PATCH 108/247] Add dynamixs-class file --- doc/guide/dynamics/dynamics-class.rst | 120 ++++++++++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 doc/guide/dynamics/dynamics-class.rst diff --git a/doc/guide/dynamics/dynamics-class.rst b/doc/guide/dynamics/dynamics-class.rst new file mode 100644 index 0000000000..7b76cc3834 --- /dev/null +++ b/doc/guide/dynamics/dynamics-class.rst @@ -0,0 +1,120 @@ +.. _solver_class: + + + +******************************************* +Solver Class Interface +******************************************* + +New from QuTiP version 5, solver such as :func:`.mesolve`, :func:`.mcsolve` has +a class interface allowing reusing the Hamiltonian, finer tuning, etc. + +Reusing Hamiltonian Data +------------------------ + +There are many case where a quantum systems need to be studied with multiple +evolution, whether it is changing the initial state or changing parameters. +In order to solve a given simulation as fast as possible, the solvers in QuTiP +take the given input operators and prepare them for the ODE solvers. +Although these operations are usually reasonably fast, but for some solvers, +such as :func:`.brmesolve` or :func:`.fmmesolve`, the overhead can be significative. +Even for simpler solvers, the time spent organizing data can become appreciable +when repeatedly solving a system. + +The class interface allows to setup the system once and reuse it with various +parameters. Most ``...solve`` function have a paired ``...Solver`` class, with a +:obj:`~.MESolver.run` to run the evolution. At class +instance creation, the physics (``H``, ``c_ops``, ``a_ops``, etc.) and options +are passed. The initial state, times and expectation operators are only passed +when calling ``run``: + +.. plot:: + :context: close-figs + + times = np.linspace(0.0, 6.0, 601) + a = tensor(qeye(2), destroy(10)) + sm = tensor(destroy(2), qeye(10)) + e_ops = [a.dag() * a, sm.dag() * sm] + H = QobjEvo( + [a.dag()*a + sm.dag()*sm, [(sm*a.dag() + sm.dag()*a), lambda t, A: A]], + args={"A": 0.5*np.pi} + ) + + solver = MESolver(H, c_ops=[np.sqrt(0.1) * a], options={"atol": 1e-8}) + solver.options["normalize_output"] = True + psi0 = tensor(fock(2, 0), fock(10, 5)) + data1 = solver.run(psi0, times, e_ops=e_ops) + psi1 = tensor(fock(2, 0), coherent(10, 2 - 1j)) + data2 = solver.run(psi1, times, e_ops=e_ops) + + plt.figure() + plt.plot(times, data1.expect[0], "b", times, data1.expect[1], "r", lw=2) + plt.plot(times, data2.expect[0], 'b--', times, data2.expect[1], 'r--', lw=2) + plt.title('Master Equation time evolution') + plt.xlabel('Time', fontsize=14) + plt.ylabel('Expectation values', fontsize=14) + plt.legend(("cavity photon number", "atom excitation probability")) + plt.show() + + +Note that as shown, options can be set at initialization or with the +``options`` property. + +The simulation parameters, the ``args`` of the :class:`.QobjEvo` passed as system +operators, can be updated at the start of a run: + +.. plot:: + :context: close-figs + + data1 = solver.run(psi0, times, e_ops=e_ops) + data2 = solver.run(psi0, times, e_ops=e_ops, args={"A": 0.25*np.pi}) + data3 = solver.run(psi0, times, e_ops=e_ops, args={"A": 0.125*np.pi}) + + plt.figure() + plt.plot(times, data1.expect[0], label="A=pi/2") + plt.plot(times, data2.expect[0], label="A=pi/4") + plt.plot(times, data3.expect[0], label="A=pi/8") + plt.title('Master Equation time evolution') + plt.xlabel('Time', fontsize=14) + plt.ylabel('Expectation values', fontsize=14) + plt.legend() + plt.show() + + +Stepping through the run +------------------------ + +The solver also allows to run through a simulation one step at a time, updating +args at each step: + + +.. plot:: + :context: close-figs + + data = [5.] + solver.start(state0=psi0, t0=times[0]) + for t in times[1:]: + psi_t = solver.step(t, args={"A": np.pi*np.exp(-(t-3)**2)}) + data.append(expect(e_ops[0], psi_t)) + + plt.figure() + plt.plot(times, data) + plt.title('Master Equation time evolution') + plt.xlabel('Time', fontsize=14) + plt.ylabel('Expectation values', fontsize=14) + plt.legend(("cavity photon number")) + plt.show() + + +.. note:: + + This is an example only, updating a constant ``args`` parameter between step + should not replace using function as QobjEvo's coefficient. + +.. note:: + + It is possible to hold have multiple solver to advance with ``step`` in + parallel, but many ODE solver, including the default ``adams`` method, can only + have one instance at a time. QuTiP will work with multiple solver instances + using these ODE solvers at a cost to performance. In these situations, using + ``dop853`` or ``vern9`` method are recommended. From a6d6fb8666730672f5da550d9bbd7adbcf36c9d9 Mon Sep 17 00:00:00 2001 From: Edward Thomas Date: Tue, 28 Nov 2023 14:14:33 +0000 Subject: [PATCH 109/247] Fix display of LaTeX in outputs --- qutip/core/qobj.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutip/core/qobj.py b/qutip/core/qobj.py index 2236a3276c..d42e08b9bc 100644 --- a/qutip/core/qobj.py +++ b/qutip/core/qobj.py @@ -603,10 +603,10 @@ def _repr_latex_(self): cols.append(None) cols += list(range(n_cols - half_length, n_cols)) # Make the data array. - data = r'\begin{equation*}\left(\begin{array}{*{11}c}' + data = r'$$\left(\begin{array}{cc}' data += r"\\".join(_latex_row(row, cols, self.data.to_array()) for row in rows) - data += r'\end{array}\right)\end{equation*}' + data += r'\end{array}\right)$$' return self._str_header() + data def __and__(self, other): From a4b945836a2c9f085f92f65f8344578a7baa4f10 Mon Sep 17 00:00:00 2001 From: Edward Thomas Date: Tue, 28 Nov 2023 14:19:24 +0000 Subject: [PATCH 110/247] Update changelog --- doc/changes/2172.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/2172.bugfix diff --git a/doc/changes/2172.bugfix b/doc/changes/2172.bugfix new file mode 100644 index 0000000000..05d74ec7cc --- /dev/null +++ b/doc/changes/2172.bugfix @@ -0,0 +1 @@ +Fix LaTeX display of Qobj state in Jupyter cell outputs \ No newline at end of file From 6e45a33d0ea81a142823c9acb7145474db6ca2da Mon Sep 17 00:00:00 2001 From: Edward Thomas Date: Tue, 28 Nov 2023 14:45:21 +0000 Subject: [PATCH 111/247] Rename change file with PR number --- doc/changes/2272.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/2272.bugfix diff --git a/doc/changes/2272.bugfix b/doc/changes/2272.bugfix new file mode 100644 index 0000000000..05d74ec7cc --- /dev/null +++ b/doc/changes/2272.bugfix @@ -0,0 +1 @@ +Fix LaTeX display of Qobj state in Jupyter cell outputs \ No newline at end of file From 3f0ad48e330583153d28e594224270cec6fd50e7 Mon Sep 17 00:00:00 2001 From: Edward Thomas Date: Tue, 28 Nov 2023 14:51:08 +0000 Subject: [PATCH 112/247] Remove old change file --- doc/changes/2172.bugfix | 1 - 1 file changed, 1 deletion(-) delete mode 100644 doc/changes/2172.bugfix diff --git a/doc/changes/2172.bugfix b/doc/changes/2172.bugfix deleted file mode 100644 index 05d74ec7cc..0000000000 --- a/doc/changes/2172.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix LaTeX display of Qobj state in Jupyter cell outputs \ No newline at end of file From 37f194a0729908f72702a1d461accaebe324a481 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 28 Nov 2023 13:35:29 -0500 Subject: [PATCH 113/247] Add towncrier --- doc/changes/2271.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/2271.misc diff --git a/doc/changes/2271.misc b/doc/changes/2271.misc new file mode 100644 index 0000000000..42e4934c3c --- /dev/null +++ b/doc/changes/2271.misc @@ -0,0 +1 @@ +Improve documentation in guide/dynamics From da81b4b807cc480c2632f15941b6531637df3523 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 30 Nov 2023 14:37:27 -0500 Subject: [PATCH 114/247] max_step section added --- doc/guide/dynamics/dynamics-time.rst | 33 ++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/doc/guide/dynamics/dynamics-time.rst b/doc/guide/dynamics/dynamics-time.rst index d8ec31f2c8..2945673311 100644 --- a/doc/guide/dynamics/dynamics-time.rst +++ b/doc/guide/dynamics/dynamics-time.rst @@ -472,6 +472,39 @@ Of course, for small system sizes and evolution times, the difference will be mi Lastly the spline method is usually as fast the string method, but it cannot be modified once created. +.. _time_max_step: + +Working with pulses +=================== + +Special care is needed when working with pulses. ODE solvers decide the step +length automatically and can miss thin pulses when not properly warned. +Integrations methods with variables step have the ``max_step`` options that +control the maximum length of a single internal integration step. This value +should be set to under half the pulse width to be certain they are not missed. + +For example, the following pulse is missed without fixing the maximum step length. + +.. plot:: + :context: close-figs + + def pulse(t): + return 10 * np.pi * (0.7 < t < 0.75) + + tlist = np.linspace(0, 1, 201) + H = [sigmaz(), [sigmax(), pulse]] + psi0 = basis(2,1) + + data1 = sesolve(H, psi0, tlist, e_ops=num(2)).expect[0] + data2 = sesolve(H, psi0, tlist, e_ops=num(2), options={"max_step": 0.01}).expect[0] + + plt.plot(tlist, data1, label="no max_step") + plt.plot(tlist, data2, label="fixed max_step") + plt.fill_between(tlist, [pulse(t) for t in tlist], color="g", alpha=0.2, label="pulse") + plt.ylim([-0.1, 1.1]) + plt.legend(loc="center left") + + .. _time-dynargs: Accessing the state from solver From 206f960456831d14220199a2b1bfc361313c2c85 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 1 Dec 2023 15:19:24 -0500 Subject: [PATCH 115/247] Add propagator page --- doc/guide/dynamics/dynamics-propagator.rst | 81 ++++++++++++++++++++++ doc/guide/guide-dynamics.rst | 1 + 2 files changed, 82 insertions(+) create mode 100644 doc/guide/dynamics/dynamics-propagator.rst diff --git a/doc/guide/dynamics/dynamics-propagator.rst b/doc/guide/dynamics/dynamics-propagator.rst new file mode 100644 index 0000000000..6f4f9fdd56 --- /dev/null +++ b/doc/guide/dynamics/dynamics-propagator.rst @@ -0,0 +1,81 @@ +.. _propagator: + +********************* +Computing propagators +********************* + +Sometime the evolution of a single state is not sufficient and the full propagator +is desired. QuTiP has the :func:`.propagator` function to compute them: + +.. code-block:: + + >>> H = sigmaz() + np.pi *sigmax() + >>> psi_t = sesolve(H, basis(2, 1), [0, 0.5, 1]).states + >>> prop = propagator(H, [0, 0.5, 1]) + + >>> print((psi_t[1] - prop[1] @ basis(2, 1)).norm()) + 2.455965272327082e-06 + + >>> print((psi_t[2] - prop[2] @ basis(2, 1)).norm()) + 2.0071900004562142e-06 + + +The first argument is the Hamiltonian, any time dependent system format is +accepted. The function also take an optional `c_ops` for collapse operator, +when used, propagator for density matrices are computed +:math:`\rho(t) = U(t)(\rho(0))`: + +.. code-block:: + + >>> rho_t = mesolve(H, fock_dm(2, 1), [0, 0.5, 1], c_ops=[sigmam()]).states + >>> prop = propagator(H, [0, 0.5, 1], c_ops=[sigmam()]) + + >>> print((rho_t[1] - prop[1](fock_dm(2, 1))).norm()) + 7.23009476734681e-07 + + >>> print((rho_t[2] - prop[2](fock_dm(2, 1))).norm()) + 1.2666967766644768e-06 + + +The propagator is also available in class format: + +.. code-block:: + + >>> U = Propagator(H, c_ops=[sigmam()]) + + >>> state_0_5 = U(0.5)(fock_dm(2, 1)) + >>> state_1 = U(1., t_start=0.5)(state_0_5) + + >>> print((rho_t[1] - state_0_5).norm()) + 7.23009476734681e-07 + + >>> print((rho_t[2] - state_1).norm()) + 8.355518501351504e-07 + +The :obj:`.Propagator` can take ``options`` and ``args`` as a solver instance. + +.. _propagator_solver: + + +Using solver to compute propagator +================================== + +Many solver accept an operator as initial state input, when an identity matrix is +passed, the propagator is computed, this can be used to compute propagator of +Bloch-Redfield or Floquet equations: + +.. code-block:: + + >>> delta = 0.2 * 2*np.pi + >>> eps0 = 1.0 * 2*np.pi + >>> gamma1 = 0.5 + + >>> H = - delta/2.0 * sigmax() - eps0/2.0 * sigmaz() + + >>> def ohmic_spectrum(w): + >>> if w == 0.0: # dephasing inducing noise + >>> return gamma1 + >>> else: # relaxation inducing noise + >>> return gamma1 / 2 * (w / (2 * np.pi)) * (w > 0.0) + + >>> prop = brmesolve(H, qeye(2), [0, 1], a_ops=[[sigmax(), ohmic_spectrum]]).final_state diff --git a/doc/guide/guide-dynamics.rst b/doc/guide/guide-dynamics.rst index 031b0fadbb..27c3de727a 100644 --- a/doc/guide/guide-dynamics.rst +++ b/doc/guide/guide-dynamics.rst @@ -19,3 +19,4 @@ Time Evolution and Quantum System Dynamics dynamics/dynamics-nmmonte.rst dynamics/dynamics-options.rst dynamics/dynamics-class.rst + dynamics/dynamics-propagator.rst From f65f1b42339cc8b9f68bd733f0254d3abcd718a8 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 4 Dec 2023 10:49:21 -0500 Subject: [PATCH 116/247] empty feedback dict --- qutip/core/cy/qobjevo.pyx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index c089f24df5..54bb8c9663 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -208,7 +208,7 @@ cdef class QobjEvo: if Q_object._feedback_functions: self._feedback_functions = Q_object._feedback_functions.copy() else: - self._feedback_functions = None + self._feedback_functions = {} self._solver_only_feedback = Q_object._solver_only_feedback.copy() if args: self.arguments(args) @@ -219,7 +219,7 @@ cdef class QobjEvo: self.elements = [] self._dims = None self.shape = (0, 0) - self._feedback_functions = None + self._feedback_functions = {} self._solver_only_feedback = set() args = args or {} if feedback is not None: @@ -405,7 +405,7 @@ cdef class QobjEvo: cdef object _prepare(QobjEvo self, object t, Data state=None): """ Precomputation before computing getting the element at `t`""" # We keep the function for feedback eventually - if self._feedback_functions is not None and state is not None: + if self._feedback_functions and state is not None: new_args = { key: func(t, state) for key, func in self._feedback_functions.items() @@ -479,8 +479,6 @@ cdef class QobjEvo: corresponding solver's ``add_feedback`` function's documentation for available values. """ - if self._feedback_functions is None: - self._feedback_functions = {} if feedback == "data": self._feedback_functions[key] = _DataFeedback() elif feedback in ["qobj", "Qobj"]: @@ -521,13 +519,13 @@ cdef class QobjEvo: Merge feedback from ``op`` into self. """ if other is not None: - if self._feedback_functions is None and other._feedback_functions: + if not self._feedback_functions and other._feedback_functions: self._feedback_functions = other._feedback_functions.copy() elif other._feedback_functions: self._feedback_functions.update(other._feedback_functions) self._solver_only_feedback |= other._solver_only_feedback - if self._feedback_functions is not None: + if self._feedback_functions: for key, func in self._feedback_functions.items(): # Update dims in ``_QobjFeedback`` if isinstance(func, _QobjFeedback): From 33a6c37fa1e958b27b2fb991dc7647c7a12dca4d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Mon, 4 Dec 2023 13:17:47 -0500 Subject: [PATCH 117/247] Apply suggestions from code review Co-authored-by: Simon Cross --- qutip/core/cy/qobjevo.pyx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index c089f24df5..97b076ccac 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -198,7 +198,7 @@ cdef class QobjEvo: qevo = H0 + H1 * coeff """ - def __init__(QobjEvo self, Q_object, args=None, copy=True, compress=True, *, + def __init__(QobjEvo self, Q_object, args=None, *, copy=True, compress=True, function_style=None, feedback=None, tlist=None, order=3, boundary_conditions=None): if isinstance(Q_object, QobjEvo): @@ -492,7 +492,7 @@ cdef class QobjEvo: elif isinstance(feedback, str): self._solver_only_feedback.add((key, feedback)) else: - raise ValueError("feedback not understood.") + raise ValueError(r"feedback {key!r} with value {feedback!r} not understood.") def _register_feedback(self, solvers_feeds, solver): """ From 82e8b2d8e80f2fa1cacaf90ab11f885112dde988 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 4 Dec 2023 15:42:30 -0500 Subject: [PATCH 118/247] Apply comments --- qutip/solver/mcsolve.py | 4 ++-- qutip/solver/multitraj.py | 4 +++- qutip/solver/sode/rouchon.py | 5 ++++- qutip/solver/sode/sode.py | 5 +++++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index 9ca65a4dca..22efb3742d 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -2,7 +2,7 @@ import numpy as np from ..core import QobjEvo, spre, spost, Qobj, unstack_columns -from .multitraj import MultiTrajSolver +from .multitraj import MultiTrajSolver, _MTSystem from .solver_base import Solver, Integrator, _solver_deprecation from .result import McResult, McTrajectoryResult, McResultImprovedSampling from .mesolve import mesolve, MESolver @@ -164,7 +164,7 @@ def mcsolve(H, state, tlist, c_ops=(), e_ops=None, ntraj=500, *, return result -class _MCSystem: +class _MCSystem(_MTSystem): """ Container for the operators of the solver. """ diff --git a/qutip/solver/multitraj.py b/qutip/solver/multitraj.py index 36c13f2988..1e910f29d5 100644 --- a/qutip/solver/multitraj.py +++ b/qutip/solver/multitraj.py @@ -75,8 +75,10 @@ class MultiTrajSolver(Solver): def __init__(self, rhs, *, options=None): if isinstance(rhs, QobjEvo): self.system = _MTSystem(rhs) - else: + elif isinstance(rhs, _MTSystem): self.system = rhs + else: + raise TypeError("The system should be a QobjEvo") self.rhs = self.system() self.options = options self.seed_sequence = np.random.SeedSequence() diff --git a/qutip/solver/sode/rouchon.py b/qutip/solver/sode/rouchon.py index 0c2e10789c..c939215f83 100644 --- a/qutip/solver/sode/rouchon.py +++ b/qutip/solver/sode/rouchon.py @@ -164,7 +164,10 @@ def reset(self, hard=False): if self._is_set: state = self.get_state() if hard: - raise NotImplementedError + raise NotImplementedError( + "Changing stochastic integrator " + "options is not supported." + ) if self._is_set: self.set_state(*state) diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index 45cacb7825..d1652c6bef 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -109,6 +109,11 @@ def reset(self, hard=False): if self._is_set: state = self.get_state() self.set_state(*state) + if hard: + raise NotImplementedError( + "Changing stochastic integrator " + "options is not supported." + ) class _Explicit_Simple_Integrator(SIntegrator): From cde22a567549806f9891a660cd192a4e0fa7b8dd Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 5 Dec 2023 16:35:42 -0500 Subject: [PATCH 119/247] Feedback as class method --- qutip/core/cy/qobjevo.pyx | 170 +++++++++--------------------------- qutip/solver/brmesolve.py | 36 ++++---- qutip/solver/floquet.py | 24 +++-- qutip/solver/mcsolve.py | 52 ++++++++++- qutip/solver/mesolve.py | 32 +++++++ qutip/solver/sesolve.py | 29 ++++++ qutip/solver/solver_base.py | 31 +++---- qutip/solver/stochastic.py | 74 +++++++++++----- 8 files changed, 259 insertions(+), 189 deletions(-) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index e672f2b9f3..d1707fcbf1 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -105,18 +105,6 @@ cdef class QobjEvo: Refer to Scipy's documentation for further details: https://docs.scipy.org/doc/scipy/reference/generated/scipy.interpolate.make_interp_spline.html - feedback: dict - A dictionary of arguments that update automatically when this - :obj:`QobjEvo` is used in a solver. For example, passing - `args={"psi": psi0}, feedback={"psi": "qobj"}` will result in the - argument `psi` being updated to the current value of the state each - time the operator is used during the evolution of `sesolve`. Feedback - allows implementing non-linear Hamiltonian and other exotic - constructions. See :meth:`QobjEvo.add_feedback` for more information on - the kinds of feedback supported. - If an argument is specified in `feedback`, an initial value for it is - required to be specified in `args`. - Attributes ---------- dims : list @@ -199,7 +187,7 @@ cdef class QobjEvo: """ def __init__(QobjEvo self, Q_object, args=None, *, copy=True, compress=True, - function_style=None, feedback=None, + function_style=None, tlist=None, order=3, boundary_conditions=None): if isinstance(Q_object, QobjEvo): self._dims = Q_object._dims @@ -220,11 +208,8 @@ cdef class QobjEvo: self._dims = None self.shape = (0, 0) self._feedback_functions = {} - self._solver_only_feedback = set() - args = args or {} - if feedback is not None: - for key, feed in feedback.items(): - self.add_feedback(key, feed) + self._solver_only_feedback = {} + args = self._read_args(args or {}) if ( isinstance(Q_object, list) @@ -262,10 +247,11 @@ cdef class QobjEvo: repr_str += f'type = {self.type}, superrep = {self.superrep}, ' repr_str += f'isconstant = {self.isconstant}, ' repr_str += f'num_elements = {self.num_elements}' - feedback_pairs = [pair for pair in self._solver_only_feedback] - if self._feedback_functions: - for key, val in self._feedback_functions: - feedback_pairs.append((key, val)) + feedback_pairs = [] + for key, val in self._feedback_functions.items(): + feedback_pairs.append(key + ":" + repr(val)) + for key, val in self._solver_only_feedback.items(): + feedback_pairs.append(key + ":" + val) if feedback_pairs: repr_str += f', feedback = {feedback_pairs}' @@ -440,57 +426,32 @@ cdef class QobjEvo: if _args is not None: kwargs.update(_args) cache = [] + kwargs = self._read_args(kwargs) self.elements = [ element.replace_arguments(kwargs, cache=cache) for element in self.elements ] - def add_feedback(QobjEvo self, str key, feedback): + def _read_args(self, args): """ - Register an argument to be updated with the state when solving for the - evolution of a quantum system with a solver. - - In simple cases where the feedback argument is the current system - state, feedback is equivalent to calling: - - ```solver.argument(key=state_t)`` - - within the solver at each time `t` that the solver calls the `QobjEvo`. - - Parameters - ---------- - key: str - Name of the arguments to update with feedback. - - feedback: str, Qobj, QobjEvo - The format of the feedback: - - - `"qobj"`: the `state` at time `t` as a `Qobj`. Either a ket or a - density matrix, depending on the solver. - - `"data"`: the `state` at time `t` as a QuTiP data layer object. - Usually a ket or colunm-stacked density matrix (column vectors), - but can be a density matrix or operator (square matrices) - depending on the solver, integration method and initial state. - The type of the data layer object depends on the solver and the - system being solved. - - Qobj, QobjEvo: the expectation value of the given operator and - the `state` at time `t`. - - Other `str` values: Solver specific feedback. See the - corresponding solver's ``add_feedback`` function's documentation - for available values. + Filter feedback args from normal args. """ - if feedback == "data": - self._feedback_functions[key] = _DataFeedback() - elif feedback in ["qobj", "Qobj"]: - self._feedback_functions[key] = _QobjFeedback(self) - elif isinstance(feedback, (Qobj, QobjEvo)): - if isinstance(feedback, Qobj): - feedback = QobjEvo(feedback) - self._feedback_functions[key] = _ExpectFeedback(feedback) - elif isinstance(feedback, str): - self._solver_only_feedback.add((key, feedback)) - else: - raise ValueError(r"feedback {key!r} with value {feedback!r} not understood.") + new_args = {} + for key, val in args.items: + if isinstance(val, _Feedback): + new_args[key] = val.prepare(self._dims) + if callable(val): + self._feedback_functions[key] = val + else: + self._solver_only_feedback[key] = val.code + else: + new_args[key] = val + if key in self._feedback_functions: + del self._feedback_functions[key] + if key in self._solver_only_feedback + del self._solver_only_feedback[key] + + return new_args def _register_feedback(self, solvers_feeds, solver): """ @@ -506,7 +467,7 @@ cdef class QobjEvo: Name of the solver for the error message. """ new_args = {} - for key, feed in self._solver_only_feedback: + for key, feed in self._solver_only_feedback.items(): if feed not in solvers_feeds: raise ValueError( f"Desired feedback {key} is not available for the {solver}." @@ -523,7 +484,11 @@ cdef class QobjEvo: self._feedback_functions = other._feedback_functions.copy() elif other._feedback_functions: self._feedback_functions.update(other._feedback_functions) - self._solver_only_feedback |= other._solver_only_feedback + + if not self._solver_only_feedback and other._solver_only_feedback: + self._solver_only_feedback = other._solver_only_feedback.copy() + elif other._solver_only_feedback: + self._solver_only_feedback.update(other._solver_only_feedback) if self._feedback_functions: for key, func in self._feedback_functions.items(): @@ -531,7 +496,6 @@ cdef class QobjEvo: if isinstance(func, _QobjFeedback): self._feedback_functions[key] = _QobjFeedback(self) - ########################################################################### # Math function # ########################################################################### @@ -1106,65 +1070,13 @@ cdef class QobjEvo: return out -cdef class _ExpectFeedback: - cdef QobjEvo oper - cdef bint stack - cdef int N, N2 - - def __init__(self, oper): - self.oper = oper - self.N = oper.shape[1] - self.N2 = oper.shape[1]**2 - - def __call__(self, t, state): - if state.shape[0] == self.N: - return self.oper.expect_data(t, state) - if state.shape[0] == self.N2 and state.shape[1] == 1: - return self.oper.expect_data(t, _data.column_unstack(state, self.N)) - raise ValueError( - f"Shape of the expect operator ({self.oper.shape}) " - f"does not match the state ({state.shape})." - ) - - def __repr__(self): - return "ExpectFeedback" - - -cdef class _QobjFeedback: - cdef list dims, dims_flat - cdef bint issuper - cdef idxint N - cdef bint normalize - - def __init__(self, base): - self.dims = base.dims - if not base.issuper: - self.dims_flat = [1 for _ in base.dims[0]] - self.issuper = base.issuper - self.N = int(base.shape[0]**0.5) - - def __call__(self, t, state): - if state.shape[0] == state.shape[1]: - out = Qobj(state, dims=self.dims) - elif self.issuper and state.shape[1] == 1: - out = Qobj(_data.column_unstack(state, self.N), dims=self.dims[1]) - elif state.shape[1] == 1: - out = Qobj(state, dims=[self.dims[1], self.dims_flat]) - else: - # rectangular state dims are patially lost... - out = Qobj(state, dims=[self.dims[1], [state.shape[1]]]) - return out - - def __repr__(self): - return "QobjFeedback" - - -cdef class _DataFeedback: +class _Feedback: def __init__(self): - pass - - def __call__(self, t, state): - return state + raise NotImplementedError("Use subclass") - def __repr__(self): - return "DataFeedback" + def prepare(self, dims): + """ + Connect the feedback to the QobjEvo. + Do the dims check. + Return the default value. + """ diff --git a/qutip/solver/brmesolve.py b/qutip/solver/brmesolve.py index 9a93dfca71..79e6a2545e 100644 --- a/qutip/solver/brmesolve.py +++ b/qutip/solver/brmesolve.py @@ -14,6 +14,7 @@ from ..core import data as _data from .solver_base import Solver, _solver_deprecation from .options import _SolverOptions +from ._feedback import _QobjFeedback, _DataFeedback def brmesolve(H, psi0, tlist, a_ops=(), e_ops=(), c_ops=(), @@ -357,27 +358,32 @@ def _apply_options(self, keys): self._integrator.options = self._options self._integrator.reset(hard=True) - def add_feedback(self, key, type): + @classmethod + def StateFeedback(cls, default=None, raw_data=False): """ - Register an argument to be updated with the state during the evolution. + State of the evolution to be used in a time-dependent operator. - Equivalent to do: - `solver.argument(key=state_t)` + When used as an args: - The state will not be in the lab basis, but in the evolution basis. + H = QobjEvo([op, func], args={"state": BRMESolver.StateFeedback()}) + + The ``func`` will receive the density matrix as ``state`` during the + evolution. Parameters ---------- - key : str - Arguments key to update. + default : Qobj or qutip.core.data.Data, optional + Initial value to be used at setup of the system. + + raw_data : bool, default : False + If True, the raw matrix will be passed instead of a Qobj. + For density matrices, the matrices can be column stacked or square + depending on the integration method. - type : str, Qobj, QobjEvo - Format of the `state_t`. + .. note:: - - "qobj": As a Qobj, either a ket or dm. - - "data": As a qutip data layer object. Density matrices will be - columns stacked: shape=(N**2, 1). - - Qobj, QobjEvo: The value is updated with the expectation value of - the given operator and the state. + The state will not be in the lab basis, but in the evolution basis. """ - self.rhs.add_feedback(key, type) + if raw_data: + return _DataFeedback(default, open=True) + return _QobjFeedback(default, open=True) diff --git a/qutip/solver/floquet.py b/qutip/solver/floquet.py index a341f74671..bfcc94e7fe 100644 --- a/qutip/solver/floquet.py +++ b/qutip/solver/floquet.py @@ -816,11 +816,6 @@ def _argument(self, args): if args: raise ValueError("FMESolver cannot update arguments") - def add_feedback(self, key, type): - raise NotImplementedError( - "The floquet solver does not support feedback currently." - ) - def start(self, state0, t0, *, floquet=False): """ Set the initial state and time for a step evolution. @@ -943,3 +938,22 @@ def run(self, state0, tlist, *, floquet=False, args=None, e_ops=None): # TODO: It would be nice if integrator could give evolution statistics # stats.update(_integrator.stats) return results + + @classmethod + def ExpectFeedback(cls): + """ + Expect of the state of the evolution to be used in a time-dependent + operator. + + Not not implemented for FMESolver + """ + raise NotImplementedError("Feedback not implemented for floquet solver.") + + @classmethod + def StateFeedback(cls): + """ + State of the evolution to be used in a time-dependent operator. + + Not not implemented for FMESolver + """ + raise NotImplementedError("Feedback not implemented for floquet solver.") diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index 22efb3742d..a930aeeafc 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -6,6 +6,7 @@ from .solver_base import Solver, Integrator, _solver_deprecation from .result import McResult, McTrajectoryResult, McResultImprovedSampling from .mesolve import mesolve, MESolver +from ._feedback import _QobjFeedback, _DataFeedback, _CollapseFeedback import qutip.core.data as _data from time import time @@ -249,7 +250,7 @@ def set_state(self, t, state0, generator, a trajectory with jumps """ self.collapses = [] - self.system.register_feedback("collapse", self.collapses) + self.system.register_feedback("CollapseFeedback", self.collapses) self._generator = generator if no_jump: self.target_norm = 0.0 @@ -684,3 +685,52 @@ def add_feedback(self, key, type): """ self.system.add_feedback(key, type) self._integrator.reset(hard=True) + + @classmethod + def CollapseFeedback(cls, default=None): + """ + Collapse of the trajectory argument for time dependent systems. + + When used as an args: + + QobjEvo([op, func], args={"cols": MCSolver.CollapseFeedback()}) + + The ``func`` will receive a list of ``(time, operator number)`` for + each collapses of the trajectory as ``cols``. + + Parameters + ---------- + default : callable, optional + Default function used outside the solver. + When not passed, an empty list is passed. + """ + return _CollapseFeedback(default) + + @classmethod + def StateFeedback(cls, default=None, raw_data=False, open=False): + """ + State of the evolution to be used in a time-dependent operator. + + When used as an args: + + H = QobjEvo([op, func], args={"state": MCSolver.StateFeedback()}) + + The ``func`` will receive the density matrix as ``state`` during the + evolution. + + Parameters + ---------- + default : Qobj or qutip.core.data.Data, optional + Initial value to be used at setup of the system. + + open : bool, default False + Set to ``True`` when using the monte carlo solver for open systems. + + raw_data : bool, default : False + If True, the raw matrix will be passed instead of a Qobj. + For density matrices, the matrices can be column stacked or square + depending on the integration method. + """ + if raw_data: + return _DataFeedback(default, open=open) + return _QobjFeedback(default, open=open) diff --git a/qutip/solver/mesolve.py b/qutip/solver/mesolve.py index f00e734f69..69c2da2f72 100644 --- a/qutip/solver/mesolve.py +++ b/qutip/solver/mesolve.py @@ -12,6 +12,7 @@ from ..core.data import to from .solver_base import Solver, _solver_deprecation from .sesolve import sesolve, SESolver +from ._feedback import _QobjFeedback, _DataFeedback def mesolve(H, rho0, tlist, c_ops=None, e_ops=None, args=None, options=None, @@ -217,3 +218,34 @@ def _initialize_stats(self): "num_collapse": self._num_collapse, }) return stats + + @classmethod + def StateFeedback(cls, default=None, raw_data=False, prop=False): + """ + State of the evolution to be used in a time-dependent operator. + + When used as an args: + + H = QobjEvo([op, func], args={"state": MESolver.StateFeedback()}) + + The ``func`` will receive the density matrix as ``state`` during the + evolution. + + Parameters + ---------- + default : Qobj or qutip.core.data.Data, optionals + Initial value to be used at setup of the system. + + prop : bool, default : False + Set to True when computing propagators. + The default with take the shape of the propagator instead of a + state. + + raw_data : bool, default : False + If True, the raw matrix will be passed instead of a Qobj. + For density matrices, the matrices can be column stacked or square + depending on the integration method. + """ + if raw_data: + return _DataFeedback(default, open=True, prop=prop) + return _QobjFeedback(default, open=True, prop=prop) diff --git a/qutip/solver/sesolve.py b/qutip/solver/sesolve.py index ebf1d08649..879dd57fb4 100644 --- a/qutip/solver/sesolve.py +++ b/qutip/solver/sesolve.py @@ -8,6 +8,7 @@ from time import time from .. import Qobj, QobjEvo from .solver_base import Solver, _solver_deprecation +from ._feedback import _QobjFeedback, _DataFeedback def sesolve(H, psi0, tlist, e_ops=None, args=None, options=None, **kwargs): @@ -189,3 +190,31 @@ def options(self): @options.setter def options(self, new_options): Solver.options.fset(self, new_options) + + @classmethod + def StateFeedback(cls, default=None, raw_data=False, prop=False): + """ + State of the evolution to be used in a time-dependent operator. + + When used as an args: + + H = QobjEvo([op, func], args={"state": SESolver.StateFeedback()}) + + The ``func`` will receive the ket as ``state`` during the evolution. + + Parameters + ---------- + default : Qobj or qutip.core.data.Data, optional + Initial value to be used at setup of the system. + + prop : bool, default : False + Set to True when using sesolve for computing propagators. + + raw_data : bool, default : False + If True, the raw matrix will be passed instead of a Qobj. + For density matrices, the matrices can be column stacked or square + depending on the integration method. + """ + if raw_data: + return _DataFeedback(default, open=False, prop=prop) + return _QobjFeedback(default, open=False, prop=prop) diff --git a/qutip/solver/solver_base.py b/qutip/solver/solver_base.py index f2c991ed46..98731bc122 100644 --- a/qutip/solver/solver_base.py +++ b/qutip/solver/solver_base.py @@ -6,6 +6,7 @@ from .result import Result from .integrator import Integrator from ..ui.progressbar import progress_bars +from ._feedback import _ExpectFeedback from time import time import warnings @@ -397,28 +398,28 @@ def add_integrator(cls, integrator, key): cls._avail_integrators[key] = integrator - def add_feedback(self, key, type): + @classmethod + def ExpectFeedback(cls, operator, default=None): """ - Register an argument to be updated with the state during the evolution. + Expectation value of the instantaneous state of the evolution to be + used by a time-dependent operator. + + When used as an args: - Equivalent to do: - `solver.argument(key=state_t)` + H = QobjEvo([op, func], args={"E0": Solver.ExpectFeedback(oper)}) + + The ``func`` will receive ``expect(oper, state)`` as ``E0`` during the + evolution. Parameters ---------- - key : str - Arguments key to update. - - type : str, Qobj, QobjEvo - Format of the `state_t`. + operator : Qobj, QobjEvo + Operator to compute the expectation values of. - - "qobj": As a Qobj, either a ket or dm. - - "data": As a qutip data layer object. Density matrices will be - columns stacked: shape=(N**2, 1). - - Qobj, QobjEvo: The value is updated with the expectation value of - the given operator and the state. + default : float, default : 0. + Initial value to be used at setup. """ - self.rhs.add_feedback(key, type) + return _ExpectFeedback(operator, default) def _solver_deprecation(kwargs, options, solver="me"): diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index d0ca50c52c..ec231b4039 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -8,6 +8,7 @@ from collections.abc import Iterable from functools import partial from .solver_base import _solver_deprecation +from ._feedback import _QobjFeedback, _DataFeedback, _WeinerFeedback class StochasticTrajResult(Result): def _post_init(self, m_ops=(), dw_factor=(), heterodyne=False): @@ -259,15 +260,15 @@ def add_feedback(self, key, type): for sc_op in self.sc_ops: sc_op.add_feedback(key, type) - def register_feedback(self, val): + def _register_feedback(self, val): self.H._register_feedback({"wiener_process": val}, "stochatic solver") for c_op in self.c_ops: c_op._register_feedback( - {"wiener_process": val}, "stochatic solver" + {"WeinerFeedback": val}, "stochatic solver" ) for sc_op in self.sc_ops: sc_op._register_feedback( - {"wiener_process": val}, "stochatic solver" + {"WeinerFeedback": val}, "stochatic solver" ) @@ -527,6 +528,7 @@ class StochasticSolver(MultiTrajSolver): _resultclass = StochasticResult _avail_integrators = {} system = None + _open = None solver_options = { "progress_bar": "text", "progress_kwargs": {"chunk_size": 10}, @@ -731,35 +733,59 @@ def options(self): def options(self, new_options): MultiTrajSolver.options.fset(self, new_options) - def add_feedback(self, key, type): + @classmethod + def WeinerFeedback(cls, default=None): """ - Register an argument to be updated with the state during the evolution. + Weiner function of the trajectory argument for time dependent systems. - Equivalent to do: - `solver.argument(key=state_t)` + When used as an args: + + QobjEvo([op, func], args={"W": SMESolver.WeinerFeedback()}) + + The ``func`` will receive a function as ``W`` that return an array of + wiener processes values at ``t``. The wiener process for the i-th + sc_ops is the i-th element for homodyne detection and the (2i, 2i+1) + pairs of process in heterodyne detection. The process is a step + function with step of length ``options["dt"]``. Parameters ---------- - key : str - Arguments key to update. + default : callable, optional + Default function used outside the solver. + When not passed, a function returning ``np.array([0])`` is used. + """ + return _WeinerFeedback(default) - type : str, Qobj, QobjEvo - Format of the `state_t`. + @classmethod + def StateFeedback(cls, default=None, raw_data=False): + """ + State of the evolution to be used in a time-dependent operator. + + When used as an args: + + H = QobjEvo([op, func], args={"state": SMESolver.StateFeedback()}) + + The ``func`` will receive the density matrix as ``state`` during the + evolution. + + Parameters + ---------- + default : Qobj or qutip.core.data.Data, optional + Initial value to be used at setup of the system. + + raw_data : bool, default : False + If True, the raw matrix will be passed instead of a Qobj. + For density matrices, the matrices can be column stacked or square + depending on the integration method. + + .. note:: + + Not supported by the ``rouchon`` mehtod. - - "qobj": As a Qobj, either a ket or dm. - - "data": As a qutip data layer object. Density matrices will be - columns stacked: shape=(N**2, 1). - - Qobj, QobjEvo: The value is updated with the expectation value of - the given operator and the state. - - "wiener_process": The value is replaced by a function ``W(t)`` - that return an array of wiener processes values at the time t. - The wiener process for the i-th sc_ops is the i-th element for - homodyne detection and the (2i, 2i+1) pairs of process in - heterodyne detection. The process is a step function with step of - length ``options["dt"]``. """ - self.system.add_feedback(key, type) - self._integrator.reset(hard=True) + if raw_data: + return _DataFeedback(default, open=cls._open) + return _QobjFeedback(default, open=cls._open) class SMESolver(StochasticSolver): From d89d35c911a7909d9df823f2d70266592e68c85e Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 5 Dec 2023 16:57:39 -0500 Subject: [PATCH 120/247] Feedback as classmethod --- qutip/core/cy/qobjevo.pxd | 2 +- qutip/core/cy/qobjevo.pyx | 4 +- qutip/solver/_feedback.py | 140 +++++++++++++++++++++++++++++++++++ qutip/solver/mcsolve.py | 50 ++----------- qutip/solver/multitraj.py | 9 +-- qutip/solver/sode/rouchon.py | 2 +- qutip/solver/sode/sode.py | 2 +- qutip/solver/stochastic.py | 38 ++-------- 8 files changed, 160 insertions(+), 87 deletions(-) create mode 100644 qutip/solver/_feedback.py diff --git a/qutip/core/cy/qobjevo.pxd b/qutip/core/cy/qobjevo.pxd index e356d076cc..04793dc825 100644 --- a/qutip/core/cy/qobjevo.pxd +++ b/qutip/core/cy/qobjevo.pxd @@ -11,7 +11,7 @@ cdef class QobjEvo: int _issuper int _isoper readonly dict _feedback_functions - readonly set _solver_only_feedback + readonly dict _solver_only_feedback cpdef Data _call(QobjEvo self, double t) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index d1707fcbf1..b18a31aafe 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -437,7 +437,7 @@ cdef class QobjEvo: Filter feedback args from normal args. """ new_args = {} - for key, val in args.items: + for key, val in args.items(): if isinstance(val, _Feedback): new_args[key] = val.prepare(self._dims) if callable(val): @@ -448,7 +448,7 @@ cdef class QobjEvo: new_args[key] = val if key in self._feedback_functions: del self._feedback_functions[key] - if key in self._solver_only_feedback + if key in self._solver_only_feedback: del self._solver_only_feedback[key] return new_args diff --git a/qutip/solver/_feedback.py b/qutip/solver/_feedback.py new file mode 100644 index 0000000000..875eb2e5e2 --- /dev/null +++ b/qutip/solver/_feedback.py @@ -0,0 +1,140 @@ +from qutip.core.data.constant import zeros +from qutip.core.cy.qobjevo import QobjEvo, _Feedback +from qutip.core.dimensions import Dimensions +import numpy as np + +def _expected_feedback_dims(dims, dm, prop): + """ + Return the state expected dimension from the operator dimension + + dims : dims of the operator + dm : if the state is a dm or ket + prop : if the evolution evolve state or propagator. + """ + if not self.open: + # Ket + dims = Dimensions(dims[1], Field()) + elif dims.issuper: + # Density matrix, operator already super + dims = dims[1].oper + else: + # operator not super, dm dims match oper + dims = dims + if not prop: + return dims + elif dims.isket: + return Dimensions(dims[0], dims[0]) + else: + return Dimensions(dims, dims) + + +class _ExpectFeedback(_Feedback): + def __init__(self, oper, default=None): + self.oper = QobjEvo(oper) + self.N = oper.shape[1] + self.N2 = oper.shape[1]**2 + self.default = default + + def prepare(self, dims): + if not ( + self.oper._dims == dims + or self.oper._dims[1] == dims + ): + raise ValueError( + f"Dimensions of the expect operator ({self.oper.dims}) " + f"does not match the operator ({dims})." + ) + return self.default or 0. + + def __call__(self, t, state): + if state.shape[0] == self.N: + return self.oper.expect_data(t, state) + if state.shape[0] == self.N2 and state.shape[1] == 1: + return self.oper.expect_data(t, _data.column_unstack(state, self.N)) + raise ValueError( + f"Shape of the expect operator ({self.oper.shape}) " + f"does not match the state ({state.shape})." + ) + + def __repr__(self): + return "ExpectFeedback" + + +class _QobjFeedback(_Feedback): + def __init__(self, default=None, prop=False, open=True): + self.open = open + self.prop = prop + self.default = default + + def prepare(self, dims): + self.dims = _expected_feedback_dims(dims, self.open, self.prop) + + if self.default is not None and self.default.dims == self.dims: + return self.default + elif self.default is not None and self.default.dims[0] == self.dims[0]: + # Catch rectangular state and propagator when prop is not set. + self.dims = self.default.dims + return self.default + + return qzero(dims) + + def __call__(self, t, state): + if state.shape == self.dims.shape: + out = Qobj(state, dims=self.dims) + else: + out = Qobj(reshape_dense(state, *self.dims.shape), dims=self.dims) + + return out + + def __repr__(self): + return "QobjFeedback" + + +class _DataFeedback(_Feedback): + def __init__(self, default=None, open=True, prop=False): + self.open = open + self.default = default + self.prop = prop + + def prepare(self, dims): + if self.default is not None: + return self.default + dims = _expected_feedback_dims(dims, self.open, self.prop) + if not self.prop: + return zeros[self.dtype](np.prod(dims.shape), 1) + return zeros["csr"](*dims.shape) + + def __call__(self, t, state): + return state + + def __repr__(self): + return "DataFeedback" + + +class _CollapseFeedback(_Feedback): + code = "CollapseFeedback" + + def __init__(self, default=None): + self.default = default + + def prepare(self, dims): + return self.default or [] + + def __repr__(self): + return "CollapseFeedback" + + +def _default_weiner(t): + return np.zeros(1) + +class _WeinerFeedback(_Feedback): + code = "WeinerFeedback" + + def __init__(self, default=None): + self.default = default + + def prepare(self, dims): + return self.default or _default_weiner + + def __repr__(self): + return "WeinerFeedback" diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index a930aeeafc..143c7fcc48 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -192,14 +192,7 @@ def arguments(self, args): for n_op in self.n_ops: n_op.arguments(args) - def add_feedback(self, key, type): - self.rhs.add_feedback(key, type) - for c_op in self.c_ops: - c_op.add_feedback(key, type) - for n_op in self.n_ops: - n_op.add_feedback(key, type) - - def register_feedback(self, key, val): + def _register_feedback(self, key, val): self.rhs._register_feedback({key: val}, solver="McSolver") for c_op in self.c_ops: c_op._register_feedback({key: val}, solver="McSolver") @@ -250,7 +243,7 @@ def set_state(self, t, state0, generator, a trajectory with jumps """ self.collapses = [] - self.system.register_feedback("CollapseFeedback", self.collapses) + self.system._register_feedback("CollapseFeedback", self.collapses) self._generator = generator if no_jump: self.target_norm = 0.0 @@ -653,39 +646,6 @@ def avail_integrators(cls): **cls._avail_integrators, } - def add_feedback(self, key, type): - """ - Register an argument to be updated with the state during the evolution. - - Equivalent to do: - `solver.argument(key=state_t)` - - .. note:: - - The state passed by the monte-carlo solver are the unnormalized - states used internally. - - Parameters - ---------- - key : str - Arguments key to update. - - type : str, Qobj, QobjEvo - Solver or evolution state. - - - "qobj": As a Qobj, either a ket or dm. - - "data": As a qutip data layer object. Density matrices will be - columns stacked: shape=(N**2, 1). - - Qobj, QobjEvo: The value is updated with the expectation value of - the given operator and the state. - - "collapse": The value is replaced by a list of - ``(collapse time, collapse operator number)``. It start as an - empty list. This list is the same as the one returned in the - evolution result and thus should not be modified. - """ - self.system.add_feedback(key, type) - self._integrator.reset(hard=True) - @classmethod def CollapseFeedback(cls, default=None): """ @@ -703,6 +663,12 @@ def CollapseFeedback(cls, default=None): default : callable, optional Default function used outside the solver. When not passed, an empty list is passed. + + .. note:: + + CollapseFeedback can't be added to a running solver when updating + arguments between steps: ``solver.step(..., args={})``. + """ return _CollapseFeedback(default) diff --git a/qutip/solver/multitraj.py b/qutip/solver/multitraj.py index 1e910f29d5..c7afec6859 100644 --- a/qutip/solver/multitraj.py +++ b/qutip/solver/multitraj.py @@ -21,10 +21,7 @@ def __call__(self): def arguments(self, args): self.rhs.arguments(args) - def add_feedback(self, key, type): - self.rhs.add_feedback(key, type) - - def register_feedback(self, type, val): + def _register_feedback(self, type, val): pass def __getattr__(self, attr): @@ -284,10 +281,6 @@ def _argument(self, args): if args: self.system.arguments(args) - def add_feedback(self, key, type): - self.system.add_feedback(key, type) - self._integrator.reset(hard=True) - def _get_generator(self, seed): """ Read the seed and create the random number generator. diff --git a/qutip/solver/sode/rouchon.py b/qutip/solver/sode/rouchon.py index c939215f83..a00d8e41de 100644 --- a/qutip/solver/sode/rouchon.py +++ b/qutip/solver/sode/rouchon.py @@ -91,7 +91,7 @@ def set_state(self, t, state0, generator): t, self.options["dt"], generator, (1, self.num_collapses,) ) - self.rhs.register_feedback(self.wiener) + self.rhs._register_feedback(self.wiener) self._make_operators() self._is_set = True diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index d1652c6bef..33ffaae96a 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -72,7 +72,7 @@ def set_state(self, t, state0, generator): t, self.options["dt"], generator, (self.N_dw, num_collapse) ) - self.rhs.register_feedback(self.wiener) + self.rhs._register_feedback(self.wiener) opt = [self.options[key] for key in self._stepper_options] self.step_func = self.stepper(self.rhs(self.options), *opt).run self._is_set = True diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index ec231b4039..2af9feaab5 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -228,38 +228,6 @@ def arguments(self, args): for sc_op in self.sc_ops: sc_op.arguments(args) - def add_feedback(self, key, type): - """ - Register an argument to be updated with the state during the evolution. - - Equivalent to do: - `solver.argument(key=state_t)` - - Parameters - ---------- - key : str - Arguments key to update. - - type : str, Qobj, QobjEvo - Format of the `state_t`. - - - "qobj": As a Qobj, either a ket or dm. - - "data": As a qutip data layer object. Density matrices will be - square matrix. - - "raw": As a qutip data layer object. Density matrices will be - columns stacked: shape=(N**2, 1). - - Qobj, QobjEvo: The value is updated with the expectation value of - the given operator and the state. - - "wiener_process": The value is replaced by a function ``W(t)`` - that return the wiener process value at the time t. The process - is a step function with step of lenght ``options["dt"]``. - """ - self.H.add_feedback(key, type) - for c_op in self.c_ops: - c_op.add_feedback(key, type) - for sc_op in self.sc_ops: - sc_op.add_feedback(key, type) - def _register_feedback(self, val): self.H._register_feedback({"wiener_process": val}, "stochatic solver") for c_op in self.c_ops: @@ -753,6 +721,12 @@ def WeinerFeedback(cls, default=None): default : callable, optional Default function used outside the solver. When not passed, a function returning ``np.array([0])`` is used. + + .. note:: + + WeinerFeedback can't be added to a running solver when updating + arguments between steps: ``solver.step(..., args={})``. + """ return _WeinerFeedback(default) From c80ab059c6050944034f519df032983a54e56b6c Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 6 Dec 2023 14:12:11 -0500 Subject: [PATCH 121/247] Improve docstring --- qutip/core/cy/qobjevo.pyx | 31 ++++--- qutip/core/dimensions.py | 54 +++++++----- qutip/solver/_feedback.py | 119 ++++++++++++++------------ qutip/solver/brmesolve.py | 4 +- qutip/solver/mcsolve.py | 9 +- qutip/solver/mesolve.py | 4 +- qutip/solver/sesolve.py | 4 +- qutip/solver/solver_base.py | 4 +- qutip/solver/stochastic.py | 6 +- qutip/tests/core/test_qobjevo.py | 42 +++++---- qutip/tests/solver/test_brmesolve.py | 6 +- qutip/tests/solver/test_mcsolve.py | 33 ++----- qutip/tests/solver/test_mesolve.py | 3 +- qutip/tests/solver/test_sesolve.py | 6 +- qutip/tests/solver/test_stochastic.py | 6 +- 15 files changed, 171 insertions(+), 160 deletions(-) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index b18a31aafe..3337ccd449 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -238,6 +238,12 @@ cdef class QobjEvo: ) ) + for key in self._feedback_functions: + # During _read_args, the dims could not have been set yet. + # To set the dims, for function QobjEvo, they need to be called. + # But to be called, the feedback args need to be read... + self._feedback_functions[key].check_consistancy(self._dims) + if compress: self.compress() @@ -396,7 +402,11 @@ cdef class QobjEvo: key: func(t, state) for key, func in self._feedback_functions.items() } - self.arguments(**new_args) + cache = [] + self.elements = [ + element.replace_arguments(new_args, cache=cache) + for element in self.elements + ] return t @@ -439,7 +449,9 @@ cdef class QobjEvo: new_args = {} for key, val in args.items(): if isinstance(val, _Feedback): - new_args[key] = val.prepare(self._dims) + new_args[key] = val.default + if self._dims is not None: + val.check_consistancy(self._dims) if callable(val): self._feedback_functions[key] = val else: @@ -491,10 +503,8 @@ cdef class QobjEvo: self._solver_only_feedback.update(other._solver_only_feedback) if self._feedback_functions: - for key, func in self._feedback_functions.items(): - # Update dims in ``_QobjFeedback`` - if isinstance(func, _QobjFeedback): - self._feedback_functions[key] = _QobjFeedback(self) + for key in self._feedback_functions: + self._feedback_functions[key].check_consistancy(self._dims) ########################################################################### # Math function # @@ -1071,12 +1081,13 @@ cdef class QobjEvo: class _Feedback: + default = None + def __init__(self): raise NotImplementedError("Use subclass") - def prepare(self, dims): + def check_consistancy(self, dims): """ - Connect the feedback to the QobjEvo. - Do the dims check. - Return the default value. + Raise an error when the dims of the e_ops / state don't match. + Tell the dims to the feedback for reconstructing the Qobj. """ diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index a495eec961..eb5dd3bf1c 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -438,10 +438,12 @@ def __init__(self, dims): self.__setitem__ = _frozen def __eq__(self, other): - return self is other or ( - type(other) is type(self) - and other.size == self.size - ) + if isinstance(other, (Space, Dimensions)): + return self is other or ( + type(other) is type(self) + and other.size == self.size + ) + return NotImplemented def __hash__(self): return hash(self.size) @@ -574,10 +576,12 @@ def __init__(self, *spaces): self.__setitem__ = _frozen def __eq__(self, other): - return self is other or ( - type(other) is type(self) and - self.spaces == other.spaces - ) + if isinstance(other, (Space, Dimensions)): + return self is other or ( + type(other) is type(self) and + self.spaces == other.spaces + ) + return NotImplemented def __hash__(self): return hash(self.spaces) @@ -657,15 +661,17 @@ def __init__(self, oper, rep='super'): self.__setitem__ = _frozen def __eq__(self, other): - return ( - self is other - or self.oper == other - or ( - type(other) is type(self) - and self.oper == other.oper - and self.superrep == other.superrep + if isinstance(other, (Space, Dimensions)): + return ( + self is other + or self.oper == other + or ( + type(other) is type(self) + and self.oper == other.oper + and self.superrep == other.superrep + ) ) - ) + return NotImplemented def __hash__(self): return hash((self.oper, self.superrep)) @@ -773,14 +779,16 @@ def __init__(self, from_, to_): self.__setitem__ = _frozen def __eq__(self, other): - return ( - self is other - or ( - type(self) is type(other) - and self.to_ == other.to_ - and self.from_ == other.from_ + if isinstance(other, Dimensions): + return ( + self is other + or ( + self.to_ == other.to_ + and self.from_ == other.from_ + ) ) - ) + return NotImplemented + def __hash__(self): return hash((self.to_, self.from_)) diff --git a/qutip/solver/_feedback.py b/qutip/solver/_feedback.py index 875eb2e5e2..eb4beb9bc4 100644 --- a/qutip/solver/_feedback.py +++ b/qutip/solver/_feedback.py @@ -1,50 +1,26 @@ -from qutip.core.data.constant import zeros from qutip.core.cy.qobjevo import QobjEvo, _Feedback -from qutip.core.dimensions import Dimensions +from qutip.core.dimensions import Dimensions, Field, SuperSpace +import qutip.core.data as _data +from qutip.core.qobj import Qobj import numpy as np -def _expected_feedback_dims(dims, dm, prop): - """ - Return the state expected dimension from the operator dimension - - dims : dims of the operator - dm : if the state is a dm or ket - prop : if the evolution evolve state or propagator. - """ - if not self.open: - # Ket - dims = Dimensions(dims[1], Field()) - elif dims.issuper: - # Density matrix, operator already super - dims = dims[1].oper - else: - # operator not super, dm dims match oper - dims = dims - if not prop: - return dims - elif dims.isket: - return Dimensions(dims[0], dims[0]) - else: - return Dimensions(dims, dims) - - class _ExpectFeedback(_Feedback): - def __init__(self, oper, default=None): + def __init__(self, oper, default=0.): self.oper = QobjEvo(oper) self.N = oper.shape[1] self.N2 = oper.shape[1]**2 self.default = default - def prepare(self, dims): + def check_consistancy(self, dims): if not ( self.oper._dims == dims - or self.oper._dims[1] == dims + or self.oper._dims[1] == dims # super e_op, oper QobjEvo + or self.oper._dims == dims[0] # oper e_op, super QobjEvo ): raise ValueError( f"Dimensions of the expect operator ({self.oper.dims}) " f"does not match the operator ({dims})." ) - return self.default or 0. def __call__(self, t, state): if state.shape[0] == self.N: @@ -66,24 +42,43 @@ def __init__(self, default=None, prop=False, open=True): self.prop = prop self.default = default - def prepare(self, dims): - self.dims = _expected_feedback_dims(dims, self.open, self.prop) - - if self.default is not None and self.default.dims == self.dims: - return self.default - elif self.default is not None and self.default.dims[0] == self.dims[0]: - # Catch rectangular state and propagator when prop is not set. - self.dims = self.default.dims - return self.default - - return qzero(dims) + def check_consistancy(self, dims): + if not self.open: + # Ket + self.dims = Dimensions(Field(), dims[1]) + elif dims.issuper: + # Density matrix, operator already super + self.dims = dims[1].oper + else: + # operator not super, dm dims match oper + self.dims = dims + if self.prop and self.dims[1] == Field(): + self.dims = Dimensions(self.dims[0], self.dims[0]) + elif self.prop: + self.dims = Dimensions(SuperSpace(self.dims), SuperSpace(self.dims)) + + if self.default is None: + pass + elif self.default._dims == self.dims: + pass + elif self.default._dims[0] == self.dims[0]: + # Catch rectangular state and propagator when the flag is not set. + self.dims = self.default._dims + else: + # The state could not be used for qevo @ default... + raise TypeError( + f"The dimensions of the default state ({self.default.dims}) " + f"does not match the operators ({self.dims})." + ) def __call__(self, t, state): if state.shape == self.dims.shape: out = Qobj(state, dims=self.dims) else: - out = Qobj(reshape_dense(state, *self.dims.shape), dims=self.dims) - + out = Qobj( + _data.column_unstack(state, self.dims.shape[0]), + dims=self.dims + ) return out def __repr__(self): @@ -96,13 +91,22 @@ def __init__(self, default=None, open=True, prop=False): self.default = default self.prop = prop - def prepare(self, dims): - if self.default is not None: - return self.default - dims = _expected_feedback_dims(dims, self.open, self.prop) - if not self.prop: - return zeros[self.dtype](np.prod(dims.shape), 1) - return zeros["csr"](*dims.shape) + def check_consistancy(self, dims): + if self.default is None: + return + if not ( + dims.shape[1] == self.default.shape[0] + or (dims.shape[1]**2 == self.default.shape[0] and self.open) + ): + raise ValueError( + f"The shape of the default state {self.default.shape} " + f"does not match the operators {dims.shape}." + ) + if self.prop and self.default.shape[0] != self.default.shape[1]: + raise ValueError( + f"The default state is expected to be square when computing " + f"propagators, but is {self.default.shape}." + ) def __call__(self, t, state): return state @@ -115,10 +119,10 @@ class _CollapseFeedback(_Feedback): code = "CollapseFeedback" def __init__(self, default=None): - self.default = default + self.default = default or [] - def prepare(self, dims): - return self.default or [] + def check_consistancy(self, dims): + pass def __repr__(self): return "CollapseFeedback" @@ -127,14 +131,15 @@ def __repr__(self): def _default_weiner(t): return np.zeros(1) + class _WeinerFeedback(_Feedback): code = "WeinerFeedback" def __init__(self, default=None): - self.default = default + self.default = default or _default_weiner - def prepare(self, dims): - return self.default or _default_weiner + def check_consistancy(self, dims): + pass def __repr__(self): return "WeinerFeedback" diff --git a/qutip/solver/brmesolve.py b/qutip/solver/brmesolve.py index 79e6a2545e..2851eb8f4b 100644 --- a/qutip/solver/brmesolve.py +++ b/qutip/solver/brmesolve.py @@ -365,14 +365,14 @@ def StateFeedback(cls, default=None, raw_data=False): When used as an args: - H = QobjEvo([op, func], args={"state": BRMESolver.StateFeedback()}) + ``QobjEvo([op, func], args={"state": BRMESolver.StateFeedback()})`` The ``func`` will receive the density matrix as ``state`` during the evolution. Parameters ---------- - default : Qobj or qutip.core.data.Data, optional + default : Qobj or qutip.core.data.Data, default : None Initial value to be used at setup of the system. raw_data : bool, default : False diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index 143c7fcc48..b65a37cb60 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -653,16 +653,15 @@ def CollapseFeedback(cls, default=None): When used as an args: - QobjEvo([op, func], args={"cols": MCSolver.CollapseFeedback()}) + ``QobjEvo([op, func], args={"cols": MCSolver.CollapseFeedback()})`` The ``func`` will receive a list of ``(time, operator number)`` for each collapses of the trajectory as ``cols``. Parameters ---------- - default : callable, optional + default : callable, default : [] Default function used outside the solver. - When not passed, an empty list is passed. .. note:: @@ -679,14 +678,14 @@ def StateFeedback(cls, default=None, raw_data=False, open=False): When used as an args: - H = QobjEvo([op, func], args={"state": MCSolver.StateFeedback()}) + ``QobjEvo([op, func], args={"state": MCSolver.StateFeedback()})`` The ``func`` will receive the density matrix as ``state`` during the evolution. Parameters ---------- - default : Qobj or qutip.core.data.Data, optional + default : Qobj or qutip.core.data.Data, default : None Initial value to be used at setup of the system. open : bool, default False diff --git a/qutip/solver/mesolve.py b/qutip/solver/mesolve.py index 69c2da2f72..caafc5ef1c 100644 --- a/qutip/solver/mesolve.py +++ b/qutip/solver/mesolve.py @@ -226,14 +226,14 @@ def StateFeedback(cls, default=None, raw_data=False, prop=False): When used as an args: - H = QobjEvo([op, func], args={"state": MESolver.StateFeedback()}) + ``QobjEvo([op, func], args={"state": MESolver.StateFeedback()})`` The ``func`` will receive the density matrix as ``state`` during the evolution. Parameters ---------- - default : Qobj or qutip.core.data.Data, optionals + default : Qobj or qutip.core.data.Data, default : None Initial value to be used at setup of the system. prop : bool, default : False diff --git a/qutip/solver/sesolve.py b/qutip/solver/sesolve.py index 879dd57fb4..8d43988299 100644 --- a/qutip/solver/sesolve.py +++ b/qutip/solver/sesolve.py @@ -198,13 +198,13 @@ def StateFeedback(cls, default=None, raw_data=False, prop=False): When used as an args: - H = QobjEvo([op, func], args={"state": SESolver.StateFeedback()}) + ``QobjEvo([op, func], args={"state": SESolver.StateFeedback()})`` The ``func`` will receive the ket as ``state`` during the evolution. Parameters ---------- - default : Qobj or qutip.core.data.Data, optional + default : Qobj or qutip.core.data.Data, default : None Initial value to be used at setup of the system. prop : bool, default : False diff --git a/qutip/solver/solver_base.py b/qutip/solver/solver_base.py index 98731bc122..b2daff550d 100644 --- a/qutip/solver/solver_base.py +++ b/qutip/solver/solver_base.py @@ -399,14 +399,14 @@ def add_integrator(cls, integrator, key): cls._avail_integrators[key] = integrator @classmethod - def ExpectFeedback(cls, operator, default=None): + def ExpectFeedback(cls, operator, default=0.): """ Expectation value of the instantaneous state of the evolution to be used by a time-dependent operator. When used as an args: - H = QobjEvo([op, func], args={"E0": Solver.ExpectFeedback(oper)}) + ``QobjEvo([op, func], args={"E0": Solver.ExpectFeedback(oper)})`` The ``func`` will receive ``expect(oper, state)`` as ``E0`` during the evolution. diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index 2af9feaab5..0164deaccb 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -708,7 +708,7 @@ def WeinerFeedback(cls, default=None): When used as an args: - QobjEvo([op, func], args={"W": SMESolver.WeinerFeedback()}) + ``QobjEvo([op, func], args={"W": SMESolver.WeinerFeedback()})`` The ``func`` will receive a function as ``W`` that return an array of wiener processes values at ``t``. The wiener process for the i-th @@ -737,14 +737,14 @@ def StateFeedback(cls, default=None, raw_data=False): When used as an args: - H = QobjEvo([op, func], args={"state": SMESolver.StateFeedback()}) + ``QobjEvo([op, func], args={"state": SMESolver.StateFeedback()})`` The ``func`` will receive the density matrix as ``state`` during the evolution. Parameters ---------- - default : Qobj or qutip.core.data.Data, optional + default : Qobj or qutip.core.data.Data, default : None Initial value to be used at setup of the system. raw_data : bool, default : False diff --git a/qutip/tests/core/test_qobjevo.py b/qutip/tests/core/test_qobjevo.py index 71d8dedc38..8afe72ce7d 100644 --- a/qutip/tests/core/test_qobjevo.py +++ b/qutip/tests/core/test_qobjevo.py @@ -2,9 +2,9 @@ import pytest from qutip import ( - Qobj, QobjEvo, coefficient, qeye, sigmax, sigmaz, rand_stochastic, + Qobj, QobjEvo, coefficient, qeye, sigmax, sigmaz, num, rand_stochastic, rand_herm, rand_ket, liouvillian, basis, spre, spost, to_choi, expect, - rand_ket, rand_dm, operator_to_vector + rand_ket, rand_dm, operator_to_vector, SESolver, MESolver ) import numpy as np from numpy.testing import assert_allclose @@ -543,13 +543,17 @@ def __call__(self, t, data=None, qobj=None, e_val=None): def test_feedback_oper(): - checker = Feedback_Checker_Coefficient() + checker = Feedback_Checker_Coefficient(stacked=False) + checker.state = basis(2, 1) qevo = QobjEvo( [qeye(2), checker], - args={"data":None, "e_val": 0.0}, - feedback={"data": "data", "e_val": qeye(2)} + args={ + "e_val": SESolver.ExpectFeedback(qeye(2), default=1.), + "data": SESolver.StateFeedback(default=checker.state.data, + raw_data=True), + "qobj": SESolver.StateFeedback(default=checker.state), + }, ) - qevo.add_feedback("qobj", "qobj") checker.state = rand_ket(2) qevo.expect(0, checker.state) @@ -566,32 +570,32 @@ def test_feedback_super(): checker = Feedback_Checker_Coefficient() qevo = QobjEvo( [spre(qeye(2)), checker], - args={"data":None, "e_val": 0.0}, - feedback={"data": "data", "e_val": qeye(2)} + args={ + "e_val": MESolver.ExpectFeedback(qeye(2)), + "data": MESolver.StateFeedback(raw_data=True), + "qobj": MESolver.StateFeedback(), + }, ) - qevo.add_feedback("qobj", "qobj") checker.state = rand_dm(2) qevo.expect(0, operator_to_vector(checker.state)) qevo.matmul_data(0, operator_to_vector(checker.state).data) - qevo.add_feedback("e_val", spre(qeye(2))) + qevo.arguments(e_val=MESolver.ExpectFeedback(spre(qeye(2)))) checker.state = rand_dm(2) qevo.expect(0, operator_to_vector(checker.state)) qevo.matmul_data(0, operator_to_vector(checker.state).data) checker = Feedback_Checker_Coefficient(stacked=False) - qevo = QobjEvo([spre(qeye(2)), checker], feedback={"data": "data"}) - qevo.add_feedback("qobj", "qobj") + qevo = QobjEvo( + [spre(qeye(2)), checker], + args={ + "data": MESolver.StateFeedback(raw_data=True, prop=True), + "qobj": MESolver.StateFeedback(prop=True), + }, + ) checker.state = rand_dm(4) checker.state.dims = [[[2],[2]], [[2],[2]]] qevo.matmul_data(0, checker.state.data) - - checker = Feedback_Checker_Coefficient() - qevo = QobjEvo([spre(qeye(2)), checker]) - - checker.state = operator_to_vector(rand_dm(2)) - qevo.expect(0, checker.state) - qevo.matmul_data(0, checker.state.data) diff --git a/qutip/tests/solver/test_brmesolve.py b/qutip/tests/solver/test_brmesolve.py index 007ff6bf22..a8420fa27e 100644 --- a/qutip/tests/solver/test_brmesolve.py +++ b/qutip/tests/solver/test_brmesolve.py @@ -307,6 +307,8 @@ def test_feedback(): qutip.coefficient("(A.real - 4)*(w > 0)", args={"A": 7.+0j, "w": 0.}) ) solver = qutip.BRSolver(H, [a_op]) - solver.add_feedback("A", qutip.num(N)) - result = solver.run(psi0, np.linspace(0, 3, 31), e_ops=[qutip.num(N)]) + result = solver.run( + psi0, np.linspace(0, 3, 31), e_ops=[qutip.num(N)], + args={"A": qutip.BRSolver.ExpectFeedback(qutip.num(N))} + ) assert np.all(result.expect[0] > 4. - tol) diff --git a/qutip/tests/solver/test_mcsolve.py b/qutip/tests/solver/test_mcsolve.py index 0896653ac2..dd7dcceaf8 100644 --- a/qutip/tests/solver/test_mcsolve.py +++ b/qutip/tests/solver/test_mcsolve.py @@ -451,50 +451,29 @@ def test_MCSolver_stepping(): assert state.isket -# Defined in module-scope so it's pickleable. -def _dynamic(t, args): - return 0 if args["collapse"] else 1 - - -@pytest.mark.xfail(reason="current limitation of McSolve") -def test_dynamic_arguments(): - """Test dynamically updated arguments are usable.""" - size = 5 - a = qutip.destroy(size) - H = qutip.num(size) - times = np.linspace(0, 1, 11) - state = qutip.basis(size, 2) - - c_ops = [[a, _dynamic], [a.dag(), _dynamic]] - mc = mcsolve(H, state, times, c_ops, ntraj=25, args={"collapse": []}) - assert all(len(collapses) <= 1 for collapses in mc.col_which) - - -@pytest.mark.parametrize(["func", "kind", "val0"], [ +@pytest.mark.parametrize(["func", "kind"], [ pytest.param( lambda t, A: A-4, - lambda: qutip.num(10), - 7.+0j, + lambda: qutip.MCSolver.ExpectFeedback(qutip.num(10)), + # 7.+0j, id="expect" ), pytest.param( lambda t, A: (len(A) < 3) * 1.0, - lambda: "collapse", - [], + lambda: qutip.MCSolver.CollapseFeedback(), id="collapse" ), ]) -def test_feedback(func, kind, val0): +def test_feedback(func, kind): tol = 1e-6 psi0 = qutip.basis(10, 7) a = qutip.destroy(10) H = qutip.QobjEvo(qutip.num(10)) solver = qutip.MCSolver( H, - c_ops=[qutip.QobjEvo([a, func], args={"A": val0})], + c_ops=[qutip.QobjEvo([a, func], args={"A": kind()})], options={"map": "serial"} ) - solver.add_feedback("A", kind()) result = solver.run( psi0,np.linspace(0, 3, 31), e_ops=[qutip.num(10)], ntraj=10 ) diff --git a/qutip/tests/solver/test_mesolve.py b/qutip/tests/solver/test_mesolve.py index bdc01cf00b..5a79d59dda 100644 --- a/qutip/tests/solver/test_mesolve.py +++ b/qutip/tests/solver/test_mesolve.py @@ -692,8 +692,7 @@ def f(t, A): psi0 = qutip.basis(N, 7) a = qutip.QobjEvo( [qutip.destroy(N), f], - args={"A": 0.}, - feedback={"A": qutip.spre(qutip.num(N))} + args={"A": MESolver.ExpectFeedback(qutip.spre(qutip.num(N)))} ) H = qutip.QobjEvo(qutip.num(N)) solver = qutip.MESolver(H, c_ops=[a]) diff --git a/qutip/tests/solver/test_sesolve.py b/qutip/tests/solver/test_sesolve.py index 630845e190..cfa5ee86ea 100644 --- a/qutip/tests/solver/test_sesolve.py +++ b/qutip/tests/solver/test_sesolve.py @@ -315,8 +315,10 @@ def f(t, A, qobj=None): tol = 1e-14 psi0 = qutip.basis(N, N-1) a = qutip.destroy(N) - H = qutip.QobjEvo([qutip.num(N), [a+a.dag(), f]], args={"A": N-1}) - H.add_feedback("A", qutip.num(N)) + H = qutip.QobjEvo( + [qutip.num(N), [a+a.dag(), f]], + args={"A": SESolver.ExpectFeedback(qutip.num(N), default=3.)} + ) solver = qutip.SESolver(H) result = solver.run(psi0, np.linspace(0, 30, 301), e_ops=[qutip.num(N)]) assert np.all(result.expect[0] > 2 - tol) diff --git a/qutip/tests/solver/test_stochastic.py b/qutip/tests/solver/test_stochastic.py index 6a78f43322..ebbf194daf 100644 --- a/qutip/tests/solver/test_stochastic.py +++ b/qutip/tests/solver/test_stochastic.py @@ -329,8 +329,10 @@ def func(t, A, W): H = num(10) sc_ops = [QobjEvo( [destroy(N), func], - args={"A": 8, "W": lambda t: [0.]}, - feedback={"W": "wiener_process", "A": num(10)}, + args={ + "A": SMESolver.ExpectFeedback(num(10)), + "W": SMESolver.WeinerFeedback() + } )] psi0 = basis(N, N-3) From 9874be2f0899c61abbe913295cc1554e619e0518 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 6 Dec 2023 14:50:48 -0500 Subject: [PATCH 122/247] Fix pep8 --- qutip/core/dimensions.py | 40 +++++++++++++++++---------------------- qutip/solver/_feedback.py | 13 +++++++++---- qutip/solver/floquet.py | 8 ++++++-- 3 files changed, 32 insertions(+), 29 deletions(-) diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index eb5dd3bf1c..167230744e 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -438,12 +438,11 @@ def __init__(self, dims): self.__setitem__ = _frozen def __eq__(self, other): - if isinstance(other, (Space, Dimensions)): - return self is other or ( - type(other) is type(self) - and other.size == self.size - ) - return NotImplemented + return self is other or ( + type(other) is type(self) + and other.size == self.size + ) + def __hash__(self): return hash(self.size) @@ -576,12 +575,10 @@ def __init__(self, *spaces): self.__setitem__ = _frozen def __eq__(self, other): - if isinstance(other, (Space, Dimensions)): - return self is other or ( - type(other) is type(self) and - self.spaces == other.spaces - ) - return NotImplemented + return self is other or ( + type(other) is type(self) and + self.spaces == other.spaces + ) def __hash__(self): return hash(self.spaces) @@ -661,17 +658,15 @@ def __init__(self, oper, rep='super'): self.__setitem__ = _frozen def __eq__(self, other): - if isinstance(other, (Space, Dimensions)): - return ( - self is other - or self.oper == other - or ( - type(other) is type(self) - and self.oper == other.oper - and self.superrep == other.superrep - ) + return ( + self is other + or self.oper == other + or ( + type(other) is type(self) + and self.oper == other.oper + and self.superrep == other.superrep ) - return NotImplemented + ) def __hash__(self): return hash((self.oper, self.superrep)) @@ -789,7 +784,6 @@ def __eq__(self, other): ) return NotImplemented - def __hash__(self): return hash((self.to_, self.from_)) diff --git a/qutip/solver/_feedback.py b/qutip/solver/_feedback.py index eb4beb9bc4..820526d479 100644 --- a/qutip/solver/_feedback.py +++ b/qutip/solver/_feedback.py @@ -4,6 +4,7 @@ from qutip.core.qobj import Qobj import numpy as np + class _ExpectFeedback(_Feedback): def __init__(self, oper, default=0.): self.oper = QobjEvo(oper) @@ -14,8 +15,8 @@ def __init__(self, oper, default=0.): def check_consistancy(self, dims): if not ( self.oper._dims == dims - or self.oper._dims[1] == dims # super e_op, oper QobjEvo - or self.oper._dims == dims[0] # oper e_op, super QobjEvo + or self.oper._dims[1] == dims # super e_op, oper QobjEvo + or self.oper._dims == dims[0] # oper e_op, super QobjEvo ): raise ValueError( f"Dimensions of the expect operator ({self.oper.dims}) " @@ -26,7 +27,9 @@ def __call__(self, t, state): if state.shape[0] == self.N: return self.oper.expect_data(t, state) if state.shape[0] == self.N2 and state.shape[1] == 1: - return self.oper.expect_data(t, _data.column_unstack(state, self.N)) + return self.oper.expect_data( + t, _data.column_unstack(state, self.N) + ) raise ValueError( f"Shape of the expect operator ({self.oper.shape}) " f"does not match the state ({state.shape})." @@ -55,7 +58,9 @@ def check_consistancy(self, dims): if self.prop and self.dims[1] == Field(): self.dims = Dimensions(self.dims[0], self.dims[0]) elif self.prop: - self.dims = Dimensions(SuperSpace(self.dims), SuperSpace(self.dims)) + self.dims = Dimensions( + SuperSpace(self.dims), SuperSpace(self.dims) + ) if self.default is None: pass diff --git a/qutip/solver/floquet.py b/qutip/solver/floquet.py index bfcc94e7fe..d5d1d20b60 100644 --- a/qutip/solver/floquet.py +++ b/qutip/solver/floquet.py @@ -947,7 +947,9 @@ def ExpectFeedback(cls): Not not implemented for FMESolver """ - raise NotImplementedError("Feedback not implemented for floquet solver.") + raise NotImplementedError( + "Feedback not implemented for floquet solver." + ) @classmethod def StateFeedback(cls): @@ -956,4 +958,6 @@ def StateFeedback(cls): Not not implemented for FMESolver """ - raise NotImplementedError("Feedback not implemented for floquet solver.") + raise NotImplementedError( + "Feedback not implemented for floquet solver." + ) From 879422c822b61b902dcccd0315b7122d5da9aada Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 6 Dec 2023 14:55:44 -0500 Subject: [PATCH 123/247] Fix pep8 --- qutip/core/dimensions.py | 1 - 1 file changed, 1 deletion(-) diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index 167230744e..7e0f17cd57 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -443,7 +443,6 @@ def __eq__(self, other): and other.size == self.size ) - def __hash__(self): return hash(self.size) From ca0fd024333c85de23d3efa4332c7ef6f8f5792a Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 7 Dec 2023 09:12:14 -0500 Subject: [PATCH 124/247] Add test for QobjEvo.dag dims --- qutip/core/cy/qobjevo.pyx | 9 +++------ qutip/tests/core/test_qobjevo.py | 26 +++++++++++++++++++++++++- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 3337ccd449..1741d08b5e 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -193,10 +193,7 @@ cdef class QobjEvo: self._dims = Q_object._dims self.shape = Q_object.shape self.elements = ( Q_object).elements.copy() - if Q_object._feedback_functions: - self._feedback_functions = Q_object._feedback_functions.copy() - else: - self._feedback_functions = {} + self._feedback_functions = Q_object._feedback_functions.copy() self._solver_only_feedback = Q_object._solver_only_feedback.copy() if args: self.arguments(args) @@ -704,7 +701,7 @@ cdef class QobjEvo: cdef QobjEvo res = self.copy() res.elements = [element.linear_map(Qobj.trans) for element in res.elements] - res._dims = Dimensions(res._dims[1], res._dims[0]) + res._dims = Dimensions(res._dims[0], res._dims[1]) return res def conj(self): @@ -719,7 +716,7 @@ cdef class QobjEvo: cdef QobjEvo res = self.copy() res.elements = [element.linear_map(Qobj.dag, True) for element in res.elements] - res._dims = Dimensions(res._dims[1], res._dims[0]) + res._dims = Dimensions(res._dims[0], res._dims[1]) return res def to(self, data_type): diff --git a/qutip/tests/core/test_qobjevo.py b/qutip/tests/core/test_qobjevo.py index 8afe72ce7d..9fc8454d15 100644 --- a/qutip/tests/core/test_qobjevo.py +++ b/qutip/tests/core/test_qobjevo.py @@ -49,6 +49,10 @@ def __call__(self, t, args={}): def __getitem__(self, which): return getattr(self, which)() + @property + def _dims(self): + return self.qobj._dims + N = 3 args = {'w1': 1, "w2": 2} @@ -108,6 +112,8 @@ def other_qevo(all_qevo): def _assert_qobjevo_equivalent(obj1, obj2, tol=1e-8): + assert obj1._dims == obj1(0)._dims + assert obj2._dims == obj2(0)._dims for t in TESTTIMES: _assert_qobj_almost_eq(obj1(t), obj2(t), tol) @@ -291,8 +297,26 @@ def test_unary(all_qevo, unary_op): "QobjEvo arithmetic" obj = all_qevo for t in TESTTIMES: - as_qevo = unary_op(obj)(t) + transformed = unary_op(obj) + as_qevo = transformed(t) + as_qobj = unary_op(obj(t)) + assert transformed._dims == as_qevo._dims + _assert_qobj_almost_eq(as_qevo, as_qobj) + + +@pytest.mark.parametrize('unary_op', [ + pytest.param(lambda a: a.conj(), id="conj"), + pytest.param(lambda a: a.dag(), id="dag"), + pytest.param(lambda a: a.trans(), id="trans"), + pytest.param(lambda a: -a, id="neg"), +]) +def test_unary_ket(unary_op): + obj = QobjEvo(rand_ket(5)) + for t in TESTTIMES: + transformed = unary_op(obj) + as_qevo = transformed(t) as_qobj = unary_op(obj(t)) + assert transformed._dims == as_qevo._dims _assert_qobj_almost_eq(as_qevo, as_qobj) From 6a3033debadeb5b76af3af1b6820b43a1f39e236 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 8 Dec 2023 10:24:29 -0500 Subject: [PATCH 125/247] Fix note formating in docstrings --- qutip/solver/brmesolve.py | 8 ++++---- qutip/solver/mcsolve.py | 10 +++++----- qutip/solver/stochastic.py | 18 +++++++++--------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/qutip/solver/brmesolve.py b/qutip/solver/brmesolve.py index 2851eb8f4b..4b54a70e8c 100644 --- a/qutip/solver/brmesolve.py +++ b/qutip/solver/brmesolve.py @@ -370,6 +370,10 @@ def StateFeedback(cls, default=None, raw_data=False): The ``func`` will receive the density matrix as ``state`` during the evolution. + .. note:: + + The state will not be in the lab basis, but in the evolution basis. + Parameters ---------- default : Qobj or qutip.core.data.Data, default : None @@ -379,10 +383,6 @@ def StateFeedback(cls, default=None, raw_data=False): If True, the raw matrix will be passed instead of a Qobj. For density matrices, the matrices can be column stacked or square depending on the integration method. - - .. note:: - - The state will not be in the lab basis, but in the evolution basis. """ if raw_data: return _DataFeedback(default, open=True) diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index b65a37cb60..266adc9b0a 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -658,16 +658,16 @@ def CollapseFeedback(cls, default=None): The ``func`` will receive a list of ``(time, operator number)`` for each collapses of the trajectory as ``cols``. - Parameters - ---------- - default : callable, default : [] - Default function used outside the solver. - .. note:: CollapseFeedback can't be added to a running solver when updating arguments between steps: ``solver.step(..., args={})``. + Parameters + ---------- + default : callable, default : [] + Default function used outside the solver. + """ return _CollapseFeedback(default) diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index 0164deaccb..e023e10990 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -716,17 +716,17 @@ def WeinerFeedback(cls, default=None): pairs of process in heterodyne detection. The process is a step function with step of length ``options["dt"]``. + .. note:: + + WeinerFeedback can't be added to a running solver when updating + arguments between steps: ``solver.step(..., args={})``. + Parameters ---------- default : callable, optional Default function used outside the solver. When not passed, a function returning ``np.array([0])`` is used. - .. note:: - - WeinerFeedback can't be added to a running solver when updating - arguments between steps: ``solver.step(..., args={})``. - """ return _WeinerFeedback(default) @@ -742,6 +742,10 @@ def StateFeedback(cls, default=None, raw_data=False): The ``func`` will receive the density matrix as ``state`` during the evolution. + .. note:: + + Not supported by the ``rouchon`` mehtod. + Parameters ---------- default : Qobj or qutip.core.data.Data, default : None @@ -752,10 +756,6 @@ def StateFeedback(cls, default=None, raw_data=False): For density matrices, the matrices can be column stacked or square depending on the integration method. - .. note:: - - Not supported by the ``rouchon`` mehtod. - """ if raw_data: return _DataFeedback(default, open=cls._open) From 6707a4b024b6c570988e27948190425b5651e137 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 14 Dec 2023 09:15:16 -0500 Subject: [PATCH 126/247] Fix spelling --- qutip/core/cy/qobjevo.pyx | 8 ++++---- qutip/solver/_feedback.py | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 1741d08b5e..67c5195c44 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -239,7 +239,7 @@ cdef class QobjEvo: # During _read_args, the dims could not have been set yet. # To set the dims, for function QobjEvo, they need to be called. # But to be called, the feedback args need to be read... - self._feedback_functions[key].check_consistancy(self._dims) + self._feedback_functions[key].check_consistency(self._dims) if compress: self.compress() @@ -448,7 +448,7 @@ cdef class QobjEvo: if isinstance(val, _Feedback): new_args[key] = val.default if self._dims is not None: - val.check_consistancy(self._dims) + val.check_consistency(self._dims) if callable(val): self._feedback_functions[key] = val else: @@ -501,7 +501,7 @@ cdef class QobjEvo: if self._feedback_functions: for key in self._feedback_functions: - self._feedback_functions[key].check_consistancy(self._dims) + self._feedback_functions[key].check_consistency(self._dims) ########################################################################### # Math function # @@ -1083,7 +1083,7 @@ class _Feedback: def __init__(self): raise NotImplementedError("Use subclass") - def check_consistancy(self, dims): + def check_consistency(self, dims): """ Raise an error when the dims of the e_ops / state don't match. Tell the dims to the feedback for reconstructing the Qobj. diff --git a/qutip/solver/_feedback.py b/qutip/solver/_feedback.py index 820526d479..79d82259cc 100644 --- a/qutip/solver/_feedback.py +++ b/qutip/solver/_feedback.py @@ -12,7 +12,7 @@ def __init__(self, oper, default=0.): self.N2 = oper.shape[1]**2 self.default = default - def check_consistancy(self, dims): + def check_consistency(self, dims): if not ( self.oper._dims == dims or self.oper._dims[1] == dims # super e_op, oper QobjEvo @@ -45,7 +45,7 @@ def __init__(self, default=None, prop=False, open=True): self.prop = prop self.default = default - def check_consistancy(self, dims): + def check_consistency(self, dims): if not self.open: # Ket self.dims = Dimensions(Field(), dims[1]) @@ -96,7 +96,7 @@ def __init__(self, default=None, open=True, prop=False): self.default = default self.prop = prop - def check_consistancy(self, dims): + def check_consistency(self, dims): if self.default is None: return if not ( @@ -126,7 +126,7 @@ class _CollapseFeedback(_Feedback): def __init__(self, default=None): self.default = default or [] - def check_consistancy(self, dims): + def check_consistency(self, dims): pass def __repr__(self): @@ -143,7 +143,7 @@ class _WeinerFeedback(_Feedback): def __init__(self, default=None): self.default = default or _default_weiner - def check_consistancy(self, dims): + def check_consistency(self, dims): pass def __repr__(self): From 10888d5bb36bf3f49b3f5b18ecf15be5d6efd065 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 18 Dec 2023 16:25:43 -0500 Subject: [PATCH 127/247] Add Feedback example --- doc/guide/dynamics/dynamics-class.rst | 38 +++++++++++++++++++++++++++ doc/guide/dynamics/dynamics-time.rst | 5 ---- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/doc/guide/dynamics/dynamics-class.rst b/doc/guide/dynamics/dynamics-class.rst index 7b76cc3834..ddca52865f 100644 --- a/doc/guide/dynamics/dynamics-class.rst +++ b/doc/guide/dynamics/dynamics-class.rst @@ -118,3 +118,41 @@ args at each step: have one instance at a time. QuTiP will work with multiple solver instances using these ODE solvers at a cost to performance. In these situations, using ``dop853`` or ``vern9`` method are recommended. + + +Accessing the state from solver +=============================== + +The state during the evolution is assessible to from properties of the solver classes. +Each solver have the ``StateFeedback`` and ``ExpectFeedback`` class method that can +be passed as arguments to time dependent systems. For example, ``ExpectFeedback`` +can be used to create a system which uncouple when there are 5 or less photon in the +cavity. + +.. plot:: + :context: close-figs + + def f(t, e1): + ex = (e1.real - 5) + return (ex > 0) * ex * 10 + + times = np.linspace(0.0, 1.0, 301) + a = tensor(qeye(2), destroy(10)) + sm = tensor(destroy(2), qeye(10)) + e_ops = [a.dag() * a, sm.dag() * sm] + psi0 = tensor(fock(2, 0), fock(10, 8)) + e_ops = [a.dag() * a, sm.dag() * sm] + + H = [a*a.dag(), [sm*a.dag() + sm.dag()*a, f]] + data = mesolve(H, psi0, times, c_ops=[a], e_ops=e_ops, + args={"e1": MESolver.ExpectFeedback(a.dag() * a)} + ).expect + + plt.figure() + plt.plot(times, data[0]) + plt.plot(times, data[1]) + plt.title('Master Equation time evolution') + plt.xlabel('Time', fontsize=14) + plt.ylabel('Expectation values', fontsize=14) + plt.legend(("cavity photon number", "atom excitation probability")) + plt.show() diff --git a/doc/guide/dynamics/dynamics-time.rst b/doc/guide/dynamics/dynamics-time.rst index 2945673311..da9c63464e 100644 --- a/doc/guide/dynamics/dynamics-time.rst +++ b/doc/guide/dynamics/dynamics-time.rst @@ -505,11 +505,6 @@ For example, the following pulse is missed without fixing the maximum step lengt plt.legend(loc="center left") -.. _time-dynargs: - -Accessing the state from solver -=============================== - In QuTiP 4.4 to 4.7, it was possible to request that the solver pass the state, expectation values or collapse operators via arguments to :obj:`.QobjEvo`. Support for this is not yet available in QuTiP 5. .. plot:: From 8bd57b4a75de1bf300c89cf4fba828ef235c5ff2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Tue, 19 Dec 2023 10:57:37 -0500 Subject: [PATCH 128/247] Apply suggestions from code review Co-authored-by: Simon Cross --- doc/guide/dynamics/dynamics-class.rst | 50 ++++++++++++---------- doc/guide/dynamics/dynamics-data.rst | 2 +- doc/guide/dynamics/dynamics-monte.rst | 23 +++++----- doc/guide/dynamics/dynamics-propagator.rst | 16 +++---- doc/guide/dynamics/dynamics-stochastic.rst | 5 +-- doc/guide/dynamics/dynamics-time.rst | 10 ++--- 6 files changed, 55 insertions(+), 51 deletions(-) diff --git a/doc/guide/dynamics/dynamics-class.rst b/doc/guide/dynamics/dynamics-class.rst index ddca52865f..fb9c99ca31 100644 --- a/doc/guide/dynamics/dynamics-class.rst +++ b/doc/guide/dynamics/dynamics-class.rst @@ -6,24 +6,29 @@ Solver Class Interface ******************************************* -New from QuTiP version 5, solver such as :func:`.mesolve`, :func:`.mcsolve` has -a class interface allowing reusing the Hamiltonian, finer tuning, etc. +In QuTiP version 5 and later, solvers such as :func:`.mesolve`, :func:`.mcsolve` also have +a class interface. The class interface allows reusing the Hamiltonian and fine tuning +many details of how the solver is run. + +Examples of some of the solver class features are given below. Reusing Hamiltonian Data ------------------------ -There are many case where a quantum systems need to be studied with multiple -evolution, whether it is changing the initial state or changing parameters. -In order to solve a given simulation as fast as possible, the solvers in QuTiP -take the given input operators and prepare them for the ODE solvers. -Although these operations are usually reasonably fast, but for some solvers, -such as :func:`.brmesolve` or :func:`.fmmesolve`, the overhead can be significative. +There are many cases where one would like to study multiple evolutions of +the same quantum system, whether by changing the initial state or other parameters. +In order to evolve a given system as fast as possible, the solvers in QuTiP +take the given input operators (Hamiltonian, collapse operators, etc) and prepare +them for use with the selected ODE solver. + +These operations are usually reasonably fast, but for some solvers, such as +:func:`.brmesolve` or :func:`.fmmesolve`, the overhead can be significant. Even for simpler solvers, the time spent organizing data can become appreciable when repeatedly solving a system. -The class interface allows to setup the system once and reuse it with various +The class interface allows us to setup the system once and reuse it with various parameters. Most ``...solve`` function have a paired ``...Solver`` class, with a -:obj:`~.MESolver.run` to run the evolution. At class +``..Solver.run`` method to run the evolution. At class instance creation, the physics (``H``, ``c_ops``, ``a_ops``, etc.) and options are passed. The initial state, times and expectation operators are only passed when calling ``run``: @@ -84,7 +89,7 @@ operators, can be updated at the start of a run: Stepping through the run ------------------------ -The solver also allows to run through a simulation one step at a time, updating +The solver class also allows to run through a simulation one step at a time, updating args at each step: @@ -109,24 +114,25 @@ args at each step: .. note:: This is an example only, updating a constant ``args`` parameter between step - should not replace using function as QobjEvo's coefficient. + should not replace using a function as QobjEvo's coefficient. .. note:: - It is possible to hold have multiple solver to advance with ``step`` in - parallel, but many ODE solver, including the default ``adams`` method, can only - have one instance at a time. QuTiP will work with multiple solver instances - using these ODE solvers at a cost to performance. In these situations, using - ``dop853`` or ``vern9`` method are recommended. + It is possible to create multiple solvers and to advance them using ``step`` in + parallel. However, many ODE solver, including the default ``adams`` method, only + allow one instance at a time per process. QuTiP supports using multiple solver instances + of these ODE solvers but with a performance cost. In these situations, using + ``dop853`` or ``vern9`` integration method is recommended instead. + +Feedback: Accessing the solver state from evolution operators +=================================================== -Accessing the state from solver -=============================== +The state of the system during the evolution is accessible via properties of the solver classes. -The state during the evolution is assessible to from properties of the solver classes. -Each solver have the ``StateFeedback`` and ``ExpectFeedback`` class method that can +Each solver has a ``StateFeedback`` and ``ExpectFeedback`` class method that can be passed as arguments to time dependent systems. For example, ``ExpectFeedback`` -can be used to create a system which uncouple when there are 5 or less photon in the +can be used to create a system which uncouples when there are 5 or fewer photons in the cavity. .. plot:: diff --git a/doc/guide/dynamics/dynamics-data.rst b/doc/guide/dynamics/dynamics-data.rst index 0c24f398fc..e1efafe9b1 100644 --- a/doc/guide/dynamics/dynamics-data.rst +++ b/doc/guide/dynamics/dynamics-data.rst @@ -188,4 +188,4 @@ that can be used to merge sets of trajectories. >>> print(merged.num_trajectories) 50 -This allows to improve statistics while keeping previous computation. +This allows one to improve statistics while keeping previous computations. diff --git a/doc/guide/dynamics/dynamics-monte.rst b/doc/guide/dynamics/dynamics-monte.rst index d3ce602a7f..bdb29d253e 100644 --- a/doc/guide/dynamics/dynamics-monte.rst +++ b/doc/guide/dynamics/dynamics-monte.rst @@ -247,8 +247,8 @@ is possible to add trajectories to existing result by adding result together: >>> print(merged.num_trajectories) 50 -Note that this merging operation only check that the result are compatible: -same e_ops and same tlist. It does not check that the same initial state or +Note that this merging operation only checks that the result are compatible -- +i.e. that the ``e_ops`` and ``tlist`` are the same. It does not check that the same initial state or Hamiltonian where used. @@ -348,7 +348,8 @@ of the time, while the improved sampling algorithm only does so once. Reproducibility --------------- -For reproducibility of Monte-Carlo computations it is possible to set the seed +For reproducibility of Monte-Carlo computations it is possible to set the seed of the random +number generator: .. code-block:: @@ -360,9 +361,9 @@ For reproducibility of Monte-Carlo computations it is possible to set the seed >>> np.allclose(res1, res3) False -The ``seeds`` parameter can either be an integer or numpy SeedSequence, which -will then be used to create seeds for each trajectories. Or a list of -interger/SeedSequence with one seed for each trajectories. Seeds available in +The ``seeds`` parameter can either be an integer or a numpy ``SeedSequence``, which +will then be used to create seeds for each trajectory. Alternatively it may be a list of +intergers or ``SeedSequence``s with one seed for each trajectories. Seeds available in the result object can be used to redo the same evolution: @@ -382,7 +383,7 @@ Running trajectories in parallel Monte-Carlo evolutions often need hundreds of trajectories to obtain sufficient statistics. Since all trajectories are independent of each other, they can be computed in parallel. The option ``map`` can take ``"serial"``, ``"parallel"`` or ``"loky"``. -Both ``"parallel"`` and ``"loky"`` compute trajectories on multiple cpus using +Both ``"parallel"`` and ``"loky"`` compute trajectories on multiple CPUs using respectively the `multiprocessing `_ and `loky `_ python modules. @@ -393,7 +394,7 @@ and `loky `_ python modules. >>> np.allclose(res_par.average_expect, res_ser.average_expect) True -Note the when running in parallel, the order in which the trajectories are added +Note that when running in parallel, the order in which the trajectories are added to the result can differ. Therefore .. code-block:: @@ -441,9 +442,9 @@ The photocurrent, previously computed using the ``photocurrent_sesolve`` and Open Systems ------------ -``mcsolve`` can be used to study system with have measured and dissipative -interaction with the bath. This is done by using a liouvillian including the -dissipative interaction instead of an Hamiltonian. +``mcsolve`` can be used to study systems which have measurement and dissipative +interactions with their environment. This is done by passing a Liouvillian including the +dissipative interaction to the solver instead of a Hamiltonian. .. plot:: :context: close-figs diff --git a/doc/guide/dynamics/dynamics-propagator.rst b/doc/guide/dynamics/dynamics-propagator.rst index 6f4f9fdd56..4d228d561a 100644 --- a/doc/guide/dynamics/dynamics-propagator.rst +++ b/doc/guide/dynamics/dynamics-propagator.rst @@ -21,8 +21,8 @@ is desired. QuTiP has the :func:`.propagator` function to compute them: The first argument is the Hamiltonian, any time dependent system format is -accepted. The function also take an optional `c_ops` for collapse operator, -when used, propagator for density matrices are computed +accepted. The function also accepts an optional `c_ops` argument for collapse operators. +When used, a propagator for density matrices is computed: :math:`\rho(t) = U(t)(\rho(0))`: .. code-block:: @@ -37,7 +37,7 @@ when used, propagator for density matrices are computed 1.2666967766644768e-06 -The propagator is also available in class format: +The propagator function is also available as a class: .. code-block:: @@ -57,12 +57,12 @@ The :obj:`.Propagator` can take ``options`` and ``args`` as a solver instance. .. _propagator_solver: -Using solver to compute propagator -================================== +Using a solver to compute a propagator +================================= -Many solver accept an operator as initial state input, when an identity matrix is -passed, the propagator is computed, this can be used to compute propagator of -Bloch-Redfield or Floquet equations: +Many solvers accept an operator as the initial state. When an identity matrix is +passed as the initial state, the propagator is computed. This can be used to compute +a propagator for Bloch-Redfield or Floquet equations: .. code-block:: diff --git a/doc/guide/dynamics/dynamics-stochastic.rst b/doc/guide/dynamics/dynamics-stochastic.rst index 975c1e96bd..8e60bc2c44 100644 --- a/doc/guide/dynamics/dynamics-stochastic.rst +++ b/doc/guide/dynamics/dynamics-stochastic.rst @@ -144,9 +144,6 @@ in ``result.measurements``. .. plot:: :context: reset - #import numpy as np - #import matplotlib.pyplot as plt - #import qutip # parameters DIM = 20 # Hilbert space dimension @@ -193,7 +190,7 @@ in ``result.measurements``. The stochastic solvers share many features with :func:`.mcsolve`, such as end conditions, seed control and running in parallel. See the sections -:ref:`monte-ntraj`, :ref:`monte-seeds` and :ref:`monte-parallel` for detail. +:ref:`monte-ntraj`, :ref:`monte-seeds` and :ref:`monte-parallel` for details. .. plot:: diff --git a/doc/guide/dynamics/dynamics-time.rst b/doc/guide/dynamics/dynamics-time.rst index da9c63464e..409674a93d 100644 --- a/doc/guide/dynamics/dynamics-time.rst +++ b/doc/guide/dynamics/dynamics-time.rst @@ -191,8 +191,8 @@ create a :obj:`.Qobj` at a time: +----------------+------------------+----------------------------------------+ | Property | Attribute | Description | +================+==================+========================================+ -| Dimensions | ``Q.dims`` | List keeping track of shapes | -| | | the tensor structure. | +| Dimensions | ``Q.dims`` | Shapes the tensor structure. | + +----------------+------------------+----------------------------------------+ | Shape | ``Q.shape`` | Dimensions of underlying data matrix. | +----------------+------------------+----------------------------------------+ @@ -230,7 +230,7 @@ Or equivalently: Using arguments --------------- -Until now, the coefficient were only functions of time. In the definition of ``H1_coeff``, +Until now, the coefficients were only functions of time. In the definition of ``H1_coeff``, the driving amplitude ``A`` and width ``sigma`` were hardcoded with their numerical values. This is fine for problems that are specialized, or that we only want to run once. However, in many cases, we would like study the same problem with a range of parameters and @@ -477,9 +477,9 @@ Lastly the spline method is usually as fast the string method, but it cannot be Working with pulses =================== -Special care is needed when working with pulses. ODE solvers decide the step +Special care is needed when working with pulses. ODE solvers select the step length automatically and can miss thin pulses when not properly warned. -Integrations methods with variables step have the ``max_step`` options that +Integrations methods with variable step sizes have the ``max_step`` option that control the maximum length of a single internal integration step. This value should be set to under half the pulse width to be certain they are not missed. From 4920d2de6bef21ef81b27f931b1f3db053a0f9e0 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 19 Dec 2023 12:01:03 -0500 Subject: [PATCH 129/247] Remove section for feedback --- doc/guide/dynamics/dynamics-class.rst | 4 +++- doc/guide/dynamics/dynamics-time.rst | 2 -- doc/guide/guide-dynamics.rst | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/guide/dynamics/dynamics-class.rst b/doc/guide/dynamics/dynamics-class.rst index fb9c99ca31..3469768c6f 100644 --- a/doc/guide/dynamics/dynamics-class.rst +++ b/doc/guide/dynamics/dynamics-class.rst @@ -125,8 +125,10 @@ args at each step: ``dop853`` or ``vern9`` integration method is recommended instead. + + Feedback: Accessing the solver state from evolution operators -=================================================== +============================================================= The state of the system during the evolution is accessible via properties of the solver classes. diff --git a/doc/guide/dynamics/dynamics-time.rst b/doc/guide/dynamics/dynamics-time.rst index 409674a93d..897fc71102 100644 --- a/doc/guide/dynamics/dynamics-time.rst +++ b/doc/guide/dynamics/dynamics-time.rst @@ -505,8 +505,6 @@ For example, the following pulse is missed without fixing the maximum step lengt plt.legend(loc="center left") -In QuTiP 4.4 to 4.7, it was possible to request that the solver pass the state, expectation values or collapse operators via arguments to :obj:`.QobjEvo`. Support for this is not yet available in QuTiP 5. - .. plot:: :context: reset :include-source: false diff --git a/doc/guide/guide-dynamics.rst b/doc/guide/guide-dynamics.rst index 27c3de727a..63d8eae06b 100644 --- a/doc/guide/guide-dynamics.rst +++ b/doc/guide/guide-dynamics.rst @@ -14,9 +14,9 @@ Time Evolution and Quantum System Dynamics dynamics/dynamics-krylov.rst dynamics/dynamics-stochastic.rst dynamics/dynamics-time.rst + dynamics/dynamics-class.rst dynamics/dynamics-bloch-redfield.rst dynamics/dynamics-floquet.rst dynamics/dynamics-nmmonte.rst dynamics/dynamics-options.rst - dynamics/dynamics-class.rst dynamics/dynamics-propagator.rst From 20c6983afc6982138361aa5fda54acd607394444 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 19 Dec 2023 12:03:52 -0500 Subject: [PATCH 130/247] Fix warning from github formatting --- doc/guide/dynamics/dynamics-monte.rst | 2 +- doc/guide/dynamics/dynamics-propagator.rst | 2 +- doc/guide/dynamics/dynamics-time.rst | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/doc/guide/dynamics/dynamics-monte.rst b/doc/guide/dynamics/dynamics-monte.rst index bdb29d253e..4ca406bf90 100644 --- a/doc/guide/dynamics/dynamics-monte.rst +++ b/doc/guide/dynamics/dynamics-monte.rst @@ -363,7 +363,7 @@ number generator: The ``seeds`` parameter can either be an integer or a numpy ``SeedSequence``, which will then be used to create seeds for each trajectory. Alternatively it may be a list of -intergers or ``SeedSequence``s with one seed for each trajectories. Seeds available in +intergers or ``SeedSequence`` s with one seed for each trajectories. Seeds available in the result object can be used to redo the same evolution: diff --git a/doc/guide/dynamics/dynamics-propagator.rst b/doc/guide/dynamics/dynamics-propagator.rst index 4d228d561a..c464aadefa 100644 --- a/doc/guide/dynamics/dynamics-propagator.rst +++ b/doc/guide/dynamics/dynamics-propagator.rst @@ -58,7 +58,7 @@ The :obj:`.Propagator` can take ``options`` and ``args`` as a solver instance. Using a solver to compute a propagator -================================= +====================================== Many solvers accept an operator as the initial state. When an identity matrix is passed as the initial state, the propagator is computed. This can be used to compute diff --git a/doc/guide/dynamics/dynamics-time.rst b/doc/guide/dynamics/dynamics-time.rst index 897fc71102..0c60c5e482 100644 --- a/doc/guide/dynamics/dynamics-time.rst +++ b/doc/guide/dynamics/dynamics-time.rst @@ -192,7 +192,6 @@ create a :obj:`.Qobj` at a time: | Property | Attribute | Description | +================+==================+========================================+ | Dimensions | ``Q.dims`` | Shapes the tensor structure. | - +----------------+------------------+----------------------------------------+ | Shape | ``Q.shape`` | Dimensions of underlying data matrix. | +----------------+------------------+----------------------------------------+ From 595c3c8736c3f2bb953c698c0830de22f63bb9ed Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 20 Dec 2023 16:42:04 -0500 Subject: [PATCH 131/247] Start fixing links --- doc/guide/guide-basics.rst | 9 +++- doc/guide/guide-states.rst | 99 ++++++++++++++++++++------------------ 2 files changed, 60 insertions(+), 48 deletions(-) diff --git a/doc/guide/guide-basics.rst b/doc/guide/guide-basics.rst index 072b9a939d..fa86e506ab 100644 --- a/doc/guide/guide-basics.rst +++ b/doc/guide/guide-basics.rst @@ -179,6 +179,8 @@ Therefore, QuTiP includes predefined objects for a variety of states and operato +--------------------------+----------------------------+----------------------------------------+ | Identity | ``qeye(N)`` | N = number of levels in Hilbert space. | +--------------------------+----------------------------+----------------------------------------+ +| Identity-like | ``qeye_like(qobj)`` | qobj = Object to copy dimensions from. | ++--------------------------+----------------------------+----------------------------------------+ | Lowering (destruction) | ``destroy(N)`` | same as above | | operator | | | +--------------------------+----------------------------+----------------------------------------+ @@ -326,7 +328,8 @@ For the destruction operator above: -The data attribute returns a message stating that the data is a sparse matrix. All ``Qobj`` instances store their data as a sparse matrix to save memory. To access the underlying dense matrix one needs to use the :func:`qutip.Qobj.full` function as described below. +The data attribute returns a message stating that the data is a sparse matrix. All ``Qobj`` instances store their data as a sparse matrix to save memory. +To access the underlying dense matrix one needs to use the :meth:`.Qobj.full` function as described below. .. _basics-qobj-math: @@ -429,6 +432,8 @@ Like attributes, the quantum object class has defined functions (methods) that o +-----------------+-------------------------------+----------------------------------------+ | Groundstate | ``Q.groundstate()`` | Eigenval & eigket of Qobj groundstate. | +-----------------+-------------------------------+----------------------------------------+ +| Matrix inverse | ``Q.inv()`` | Matrix inverse of the Qobj. | ++-----------------+-------------------------------+----------------------------------------+ | Matrix Element | ``Q.matrix_element(bra,ket)`` | Matrix element | +-----------------+-------------------------------+----------------------------------------+ | Norm | ``Q.norm()`` | Returns L2 norm for states, | @@ -454,6 +459,8 @@ Like attributes, the quantum object class has defined functions (methods) that o +-----------------+-------------------------------+----------------------------------------+ | Trace | ``Q.tr()`` | Returns trace of quantum object. | +-----------------+-------------------------------+----------------------------------------+ +| Conversion | ``Q.to(dtype)`` | Convert the matrix format CSR / Dense. | ++-----------------+-------------------------------+----------------------------------------+ | Transform | ``Q.transform(inpt)`` | A basis transformation defined by | | | | matrix or list of kets 'inpt' . | +-----------------+-------------------------------+----------------------------------------+ diff --git a/doc/guide/guide-states.rst b/doc/guide/guide-states.rst index 3cabf6e020..4d4efc3c53 100644 --- a/doc/guide/guide-states.rst +++ b/doc/guide/guide-states.rst @@ -17,7 +17,7 @@ In the previous guide section :ref:`basics`, we saw how to create states and ope State Vectors (kets or bras) ============================== -Here we begin by creating a Fock :func:`qutip.states.basis` vacuum state vector :math:`\left|0\right>` with in a Hilbert space with 5 number states, from 0 to 4: +Here we begin by creating a Fock :func:`.basis` vacuum state vector :math:`\left|0\right>` with in a Hilbert space with 5 number states, from 0 to 4: .. testcode:: [states] @@ -41,7 +41,7 @@ Here we begin by creating a Fock :func:`qutip.states.basis` vacuum state vector -and then create a lowering operator :math:`\left(\hat{a}\right)` corresponding to 5 number states using the :func:`qutip.destroy` function: +and then create a lowering operator :math:`\left(\hat{a}\right)` corresponding to 5 number states using the :func:`.destroy` function: .. testcode:: [states] @@ -102,7 +102,8 @@ We see that, as expected, the vacuum is transformed to the zero vector. A more [0.] [0.]] -The raising operator has in indeed raised the state `vec` from the vacuum to the :math:`\left| 1\right>` state. Instead of using the dagger ``Qobj.dag()`` method to raise the state, we could have also used the built in :func:`qutip.create` function to make a raising operator: +The raising operator has in indeed raised the state `vec` from the vacuum to the :math:`\left| 1\right>` state. +Instead of using the dagger ``Qobj.dag()`` method to raise the state, we could have also used the built in :func:`.create` function to make a raising operator: .. testcode:: [states] @@ -237,7 +238,9 @@ Notice how in this last example, application of the number operator does not giv [0.] [0.]] -Since we are giving a demonstration of using states and operators, we have done a lot more work than we should have. For example, we do not need to operate on the vacuum state to generate a higher number Fock state. Instead we can use the :func:`qutip.states.basis` (or :func:`qutip.states.fock`) function to directly obtain the required state: +Since we are giving a demonstration of using states and operators, we have done a lot more work than we should have. +For example, we do not need to operate on the vacuum state to generate a higher number Fock state. +Instead we can use the :func:`.basis` (or :func:`.fock`) function to directly obtain the required state: .. testcode:: [states] @@ -258,7 +261,7 @@ Since we are giving a demonstration of using states and operators, we have done [0.] [0.]] -Notice how it is automatically normalized. We can also use the built in :func:`qutip.num` operator: +Notice how it is automatically normalized. We can also use the built in :func:`.num` operator: .. testcode:: [states] @@ -319,7 +322,7 @@ We can also create superpositions of states: [0. ] [0. ]] -where we have used the :func:`qutip.Qobj.unit` method to again normalize the state. Operating with the number function again: +where we have used the :meth:`.Qobj.unit` method to again normalize the state. Operating with the number function again: .. testcode:: [states] @@ -338,7 +341,7 @@ where we have used the :func:`qutip.Qobj.unit` method to again normalize the sta [0. ] [0. ]] -We can also create coherent states and squeezed states by applying the :func:`qutip.displace` and :func:`qutip.squeeze` functions to the vacuum state: +We can also create coherent states and squeezed states by applying the :func:`.displace` and :func:`.squeeze` functions to the vacuum state: .. testcode:: [states] @@ -380,7 +383,7 @@ We can also create coherent states and squeezed states by applying the :func:`qu [-0.02688063-0.23828775j] [ 0.26352814+0.11512178j]] -Of course, displacing the vacuum gives a coherent state, which can also be generated using the built in :func:`qutip.states.coherent` function. +Of course, displacing the vacuum gives a coherent state, which can also be generated using the built in :func:`.coherent` function. .. _states-dm: @@ -411,7 +414,7 @@ The simplest density matrix is created by forming the outer-product :math:`\left [0. 0. 0. 0. 0.] [0. 0. 0. 0. 0.]] -A similar task can also be accomplished via the :func:`qutip.states.fock_dm` or :func:`qutip.states.ket2dm` functions: +A similar task can also be accomplished via the :func:`.fock_dm` or :func:`.ket2dm` functions: .. testcode:: [states] @@ -466,7 +469,8 @@ If we want to create a density matrix with equal classical probability of being [0. 0. 0. 0. 0. ] [0. 0. 0. 0. 0.5]] -or use ``0.5 * fock_dm(5, 2) + 0.5 * fock_dm(5, 4)``. There are also several other built-in functions for creating predefined density matrices, for example :func:`qutip.states.coherent_dm` and :func:`qutip.states.thermal_dm` which create coherent state and thermal state density matrices, respectively. +or use ``0.5 * fock_dm(5, 2) + 0.5 * fock_dm(5, 4)``. +There are also several other built-in functions for creating predefined density matrices, for example :func:`.coherent_dm` and :func:`.thermal_dm` which create coherent state and thermal state density matrices, respectively. .. testcode:: [states] @@ -503,7 +507,8 @@ or use ``0.5 * fock_dm(5, 2) + 0.5 * fock_dm(5, 4)``. There are also several oth [0. 0. 0. 0.08046635 0. ] [0. 0. 0. 0. 0.04470353]] -QuTiP also provides a set of distance metrics for determining how close two density matrix distributions are to each other. Included are the trace distance :func:`qutip.core.metrics.tracedist`, fidelity :func:`qutip.core.metrics.fidelity`, Hilbert-Schmidt distance :func:`qutip.core.metrics.hilbert_dist`, Bures distance :func:`qutip.core.metrics.bures_dist`, Bures angle :func:`qutip.core.metrics.bures_angle`, and quantum Hellinger distance :func:`qutip.core.metrics.hellinger_dist`. +QuTiP also provides a set of distance metrics for determining how close two density matrix distributions are to each other. +Included are the trace distance :func:`.tracedist`, fidelity :func:`.fidelity`, Hilbert-Schmidt distance :func:`.hilbert_dist`, Bures distance :func:`.bures_dist`, Bures angle :func:`.bures_angle`, and quantum Hellinger distance :func:`.hellinger_dist`. .. testcode:: [states] @@ -534,7 +539,7 @@ For a pure state and a mixed state, :math:`1 - F^{2} \le T` which can also be ve Qubit (two-level) systems ========================= -Having spent a fair amount of time on basis states that represent harmonic oscillator states, we now move on to qubit, or two-level quantum systems (for example a spin-1/2). To create a state vector corresponding to a qubit system, we use the same :func:`qutip.states.basis`, or :func:`qutip.states.fock`, function with only two levels: +Having spent a fair amount of time on basis states that represent harmonic oscillator states, we now move on to qubit, or two-level quantum systems (for example a spin-1/2). To create a state vector corresponding to a qubit system, we use the same :func:`.basis`, or :func:`.fock`, function with only two levels: .. testcode:: [states] @@ -547,7 +552,7 @@ Now at this point one may ask how this state is different than that of a harmoni vac = basis(2, 0) -At this stage, there is no difference. This should not be surprising as we called the exact same function twice. The difference between the two comes from the action of the spin operators :func:`qutip.sigmax`, :func:`qutip.sigmay`, :func:`qutip.sigmaz`, :func:`qutip.sigmap`, and :func:`qutip.sigmam` on these two-level states. For example, if ``vac`` corresponds to the vacuum state of a harmonic oscillator, then, as we have already seen, we can use the raising operator to get the :math:`\left|1\right>` state: +At this stage, there is no difference. This should not be surprising as we called the exact same function twice. The difference between the two comes from the action of the spin operators :func:`.sigmax`, :func:`.sigmay`, :func:`.sigmaz`, :func:`.sigmap`, and :func:`.sigmam` on these two-level states. For example, if ``vac`` corresponds to the vacuum state of a harmonic oscillator, then, as we have already seen, we can use the raising operator to get the :math:`\left|1\right>` state: .. testcode:: [states] @@ -579,7 +584,7 @@ At this stage, there is no difference. This should not be surprising as we call [[0.] [1.]] -For a spin system, the operator analogous to the raising operator is the sigma-plus operator :func:`qutip.sigmap`. Operating on the ``spin`` state gives: +For a spin system, the operator analogous to the raising operator is the sigma-plus operator :func:`.sigmap`. Operating on the ``spin`` state gives: .. testcode:: [states] @@ -609,7 +614,7 @@ For a spin system, the operator analogous to the raising operator is the sigma-p [[0.] [0.]] -Now we see the difference! The :func:`qutip.sigmap` operator acting on the ``spin`` state returns the zero vector. Why is this? To see what happened, let us use the :func:`qutip.sigmaz` operator: +Now we see the difference! The :func:`.sigmap` operator acting on the ``spin`` state returns the zero vector. Why is this? To see what happened, let us use the :func:`.sigmaz` operator: .. testcode:: [states] @@ -669,7 +674,7 @@ Now we see the difference! The :func:`qutip.sigmap` operator acting on the ``sp [[ 0.] [-1.]] -The answer is now apparent. Since the QuTiP :func:`qutip.sigmaz` function uses the standard z-basis representation of the sigma-z spin operator, the ``spin`` state corresponds to the :math:`\left|\uparrow\right>` state of a two-level spin system while ``spin2`` gives the :math:`\left|\downarrow\right>` state. Therefore, in our previous example ``sigmap() * spin``, we raised the qubit state out of the truncated two-level Hilbert space resulting in the zero state. +The answer is now apparent. Since the QuTiP :func:`.sigmaz` function uses the standard z-basis representation of the sigma-z spin operator, the ``spin`` state corresponds to the :math:`\left|\uparrow\right>` state of a two-level spin system while ``spin2`` gives the :math:`\left|\downarrow\right>` state. Therefore, in our previous example ``sigmap() * spin``, we raised the qubit state out of the truncated two-level Hilbert space resulting in the zero state. While at first glance this convention might seem somewhat odd, it is in fact quite handy. For one, the spin operators remain in the conventional form. Second, when the spin system is in the :math:`\left|\uparrow\right>` state: @@ -689,14 +694,14 @@ While at first glance this convention might seem somewhat odd, it is in fact qui the non-zero component is the zeroth-element of the underlying matrix (remember that python uses c-indexing, and matrices start with the zeroth element). The :math:`\left|\downarrow\right>` state therefore has a non-zero entry in the first index position. This corresponds nicely with the quantum information definitions of qubit states, where the excited :math:`\left|\uparrow\right>` state is label as :math:`\left|0\right>`, and the :math:`\left|\downarrow\right>` state by :math:`\left|1\right>`. -If one wants to create spin operators for higher spin systems, then the :func:`qutip.jmat` function comes in handy. +If one wants to create spin operators for higher spin systems, then the :func:`.jmat` function comes in handy. .. _states-expect: Expectation values =================== -Some of the most important information about quantum systems comes from calculating the expectation value of operators, both Hermitian and non-Hermitian, as the state or density matrix of the system varies in time. Therefore, in this section we demonstrate the use of the :func:`qutip.expect` function. To begin: +Some of the most important information about quantum systems comes from calculating the expectation value of operators, both Hermitian and non-Hermitian, as the state or density matrix of the system varies in time. Therefore, in this section we demonstrate the use of the :func:`.expect` function. To begin: .. testcode:: [states] @@ -721,7 +726,7 @@ Some of the most important information about quantum systems comes from calculat np.testing.assert_almost_equal(expect(c, cat), 0.9999999999999998j) -The :func:`qutip.expect` function also accepts lists or arrays of state vectors or density matrices for the second input: +The :func:`.expect` function also accepts lists or arrays of state vectors or density matrices for the second input: .. testcode:: [states] @@ -749,9 +754,9 @@ The :func:`qutip.expect` function also accepts lists or arrays of state vectors [ 0.+0.j 0.+1.j -1.+0.j 0.-1.j] -Notice how in this last example, all of the return values are complex numbers. This is because the :func:`qutip.expect` function looks to see whether the operator is Hermitian or not. If the operator is Hermitian, then the output will always be real. In the case of non-Hermitian operators, the return values may be complex. Therefore, the :func:`qutip.expect` function will return an array of complex values for non-Hermitian operators when the input is a list/array of states or density matrices. +Notice how in this last example, all of the return values are complex numbers. This is because the :func:`.expect` function looks to see whether the operator is Hermitian or not. If the operator is Hermitian, then the output will always be real. In the case of non-Hermitian operators, the return values may be complex. Therefore, the :func:`.expect` function will return an array of complex values for non-Hermitian operators when the input is a list/array of states or density matrices. -Of course, the :func:`qutip.expect` function works for spin states and operators: +Of course, the :func:`.expect` function works for spin states and operators: .. testcode:: [states] @@ -797,8 +802,8 @@ in two copies of that Hilbert space, [Hav03]_, [Wat13]_. This isomorphism is implemented in QuTiP by the -:obj:`~qutip.superoperator.operator_to_vector` and -:obj:`~qutip.superoperator.vector_to_operator` functions: +:obj:`.operator_to_vector` and +:obj:`.vector_to_operator` functions: .. testcode:: [states] @@ -842,7 +847,7 @@ This isomorphism is implemented in QuTiP by the np.testing.assert_almost_equal((rho - rho2).norm(), 0) -The :attr:`~qutip.Qobj.type` attribute indicates whether a quantum object is +The :attr:`.Qobj.type` attribute indicates whether a quantum object is a vector corresponding to an operator (``operator-ket``), or its Hermitian conjugate (``operator-bra``). @@ -883,7 +888,7 @@ between :math:`\mathcal{L}(\mathcal{H})` and :math:`\mathcal{H} \otimes \mathcal Since :math:`\mathcal{H} \otimes \mathcal{H}` is a vector space, linear maps on this space can be represented as matrices, often called *superoperators*. -Using the :obj:`~qutip.Qobj`, the :obj:`~qutip.superoperator.spre` and :obj:`~qutip.superoperator.spost` functions, supermatrices +Using the :obj:`.Qobj`, the :obj:`.spre` and :obj:`.spost` functions, supermatrices corresponding to left- and right-multiplication respectively can be quickly constructed. @@ -893,7 +898,7 @@ constructed. S = spre(X) * spost(X.dag()) # Represents conjugation by X. -Note that this is done automatically by the :obj:`~qutip.superop_reps.to_super` function when given +Note that this is done automatically by the :obj:`.to_super` function when given ``type='oper'`` input. .. testcode:: [states] @@ -921,8 +926,8 @@ Quantum objects representing superoperators are denoted by ``type='super'``: [1. 0. 0. 0.]] Information about superoperators, such as whether they represent completely -positive maps, is exposed through the :attr:`~qutip.Qobj.iscp`, :attr:`~qutip.Qobj.istp` -and :attr:`~qutip.Qobj.iscptp` attributes: +positive maps, is exposed through the :attr:`.Qobj.iscp`, :attr:`.Qobj.istp` +and :attr:`.Qobj.iscptp` attributes: .. testcode:: [states] @@ -936,7 +941,7 @@ and :attr:`~qutip.Qobj.iscptp` attributes: True True True In addition, dynamical generators on this extended space, often called -*Liouvillian superoperators*, can be created using the :func:`~qutip.superoperator.liouvillian` function. Each of these takes a Hamiltonian along with +*Liouvillian superoperators*, can be created using the :func:`.liouvillian` function. Each of these takes a Hamiltonian along with a list of collapse operators, and returns a ``type="super"`` object that can be exponentiated to find the superoperator for that evolution. @@ -1001,8 +1006,8 @@ convention, J(\Lambda) = (\mathbb{1} \otimes \Lambda) [|\mathbb{1}\rangle\!\rangle \langle\!\langle \mathbb{1}|]. -In QuTiP, :math:`J(\Lambda)` can be found by calling the :func:`~qutip.superop_reps.to_choi` -function on a ``type="super"`` :obj:`~qutip.Qobj`. +In QuTiP, :math:`J(\Lambda)` can be found by calling the :func:`.to_choi` +function on a ``type="super"`` :obj:`.Qobj`. .. testcode:: [states] @@ -1042,7 +1047,7 @@ function on a ``type="super"`` :obj:`~qutip.Qobj`. [0. 0. 0. 0.] [1. 0. 0. 1.]] -If a :obj:`~qutip.Qobj` instance is already in the Choi :attr:`~qutip.Qobj.superrep`, then calling :func:`~qutip.superop_reps.to_choi` +If a :obj:`.Qobj` instance is already in the Choi :attr:`.Qobj.superrep`, then calling :func:`.to_choi` does nothing: .. testcode:: [states] @@ -1061,8 +1066,8 @@ does nothing: [0. 1. 1. 0.] [0. 0. 0. 0.]] -To get back to the superoperator representation, simply use the :func:`~qutip.superop_reps.to_super` function. -As with :func:`~qutip.superop_reps.to_choi`, :func:`~qutip.superop_reps.to_super` is idempotent: +To get back to the superoperator representation, simply use the :func:`.to_super` function. +As with :func:`.to_choi`, :func:`.to_super` is idempotent: .. testcode:: [states] @@ -1116,7 +1121,7 @@ we have that = \sum_i |A_i\rangle\!\rangle \langle\!\langle A_i| = J(\Lambda). The Kraus representation of a hermicity-preserving map can be found in QuTiP -using the :func:`~qutip.superop_reps.to_kraus` function. +using the :func:`.to_kraus` function. .. testcode:: [states] @@ -1219,9 +1224,9 @@ using the :func:`~qutip.superop_reps.to_kraus` function. [[0. 0. ] [0. 0.70710678]]] -As with the other representation conversion functions, :func:`~qutip.superop_reps.to_kraus` -checks the :attr:`~qutip.Qobj.superrep` attribute of its input, and chooses an appropriate -conversion method. Thus, in the above example, we can also call :func:`~qutip.superop_reps.to_kraus` +As with the other representation conversion functions, :func:`.to_kraus` +checks the :attr:`.Qobj.superrep` attribute of its input, and chooses an appropriate +conversion method. Thus, in the above example, we can also call :func:`.to_kraus` on ``J``. .. testcode:: [states] @@ -1285,7 +1290,7 @@ all operators :math:`X` acting on :math:`\mathcal{H}`, where the partial trace is over a new index that corresponds to the index in the Kraus summation. Conversion to Stinespring -is handled by the :func:`~qutip.superop_reps.to_stinespring` +is handled by the :func:`.to_stinespring` function. .. testcode:: [states] @@ -1373,7 +1378,7 @@ the :math:`\chi`-matrix representation, where :math:`\{B_\alpha\}` is a basis for the space of matrices acting on :math:`\mathcal{H}`. In QuTiP, this basis is taken to be the Pauli basis :math:`B_\alpha = \sigma_\alpha / \sqrt{2}`. Conversion to the -:math:`\chi` formalism is handled by the :func:`~qutip.superop_reps.to_chi` +:math:`\chi` formalism is handled by the :func:`.to_chi` function. .. testcode:: [states] @@ -1414,9 +1419,9 @@ the :math:`\chi_{00}` element: Here, the factor of 4 comes from the dimension of the underlying Hilbert space :math:`\mathcal{H}`. As with the superoperator and Choi representations, the :math:`\chi` representation is -denoted by the :attr:`~qutip.Qobj.superrep`, such that :func:`~qutip.superop_reps.to_super`, -:func:`~qutip.superop_reps.to_choi`, :func:`~qutip.superop_reps.to_kraus`, -:func:`~qutip.superop_reps.to_stinespring` and :func:`~qutip.superop_reps.to_chi` +denoted by the :attr:`.Qobj.superrep`, such that :func:`.to_super`, +:func:`.to_choi`, :func:`.to_kraus`, +:func:`.to_stinespring` and :func:`.to_chi` all convert from the :math:`\chi` representation appropriately. Properties of Quantum Maps @@ -1425,7 +1430,7 @@ Properties of Quantum Maps In addition to converting between the different representations of quantum maps, QuTiP also provides attributes to make it easy to check if a map is completely positive, trace preserving and/or hermicity preserving. Each of these attributes -uses :attr:`~qutip.Qobj.superrep` to automatically perform any needed conversions. +uses :attr:`.Qobj.superrep` to automatically perform any needed conversions. In particular, a quantum map is said to be positive (but not necessarily completely positive) if it maps all positive operators to positive operators. For instance, the @@ -1451,7 +1456,7 @@ with negative eigenvalues. Complete positivity addresses this by requiring that a map returns positive operators for all positive operators, and does so even under tensoring with another map. The Choi matrix is very useful here, as it can be shown that a map is completely positive if and only if its Choi matrix -is positive [Wat13]_. QuTiP implements this check with the :attr:`~qutip.Qobj.iscp` +is positive [Wat13]_. QuTiP implements this check with the :attr:`.Qobj.iscp` attribute. As an example, notice that the snippet above already calculates the Choi matrix of the transpose map by acting it on half of an entangled pair. We simply need to manually set the ``dims`` and ``superrep`` attributes to reflect the @@ -1477,7 +1482,7 @@ That is, :math:`\Lambda(\rho) = (\Lambda(\rho))^\dagger` for all :math:`\rho` su :math:`\rho = \rho^\dagger`. To see this, we note that :math:`(\rho^{\mathrm{T}})^\dagger = \rho^*`, the complex conjugate of :math:`\rho`. By assumption, :math:`\rho = \rho^\dagger = (\rho^*)^{\mathrm{T}}`, though, such that :math:`\Lambda(\rho) = \Lambda(\rho^\dagger) = \rho^*`. -We can confirm this by checking the :attr:`~qutip.Qobj.ishp` attribute: +We can confirm this by checking the :attr:`.Qobj.ishp` attribute: .. testcode:: [states] @@ -1492,7 +1497,7 @@ We can confirm this by checking the :attr:`~qutip.Qobj.ishp` attribute: Next, we note that the transpose map does preserve the trace of its inputs, such that :math:`\operatorname{Tr}(\Lambda[\rho]) = \operatorname{Tr}(\rho)` for all :math:`\rho`. -This can be confirmed by the :attr:`~qutip.Qobj.istp` attribute: +This can be confirmed by the :attr:`.Qobj.istp` attribute: .. testcode:: [states] From c58a35511b9c6ca143802d9beda761d458e5540c Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Thu, 21 Dec 2023 07:14:47 +0900 Subject: [PATCH 132/247] parallel.py: fixed typos in docstrings, minor changes to loky_pmap (make its structure more similar to parallel_map for readability) --- qutip/solver/parallel.py | 51 +++++++++++++++++++++------------------- 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index fdd1685042..7f486e94d2 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -149,15 +149,15 @@ def parallel_map(task, values, task_args=None, task_kwargs=None, The list or array of values for which the ``task`` function is to be evaluated. task_args : list, optional - The optional additional argument to the ``task`` function. + The optional additional arguments to the ``task`` function. task_kwargs : dictionary, optional - The optional additional keyword argument to the ``task`` function. + The optional additional keyword arguments to the ``task`` function. reduce_func : func, optional - If provided, it will be called with the output of each tasks instead of - storing a them in a list. Note that the order in which results are + If provided, it will be called with the output of each task instead of + storing them in a list. Note that the order in which results are passed to ``reduce_func`` is not defined. It should return None or a - number. When returning a number, it represent the estimation of the - number of task left. On a return <= 0, the map will end early. + number. When returning a number, it represents the estimation of the + number of tasks left. On a return <= 0, the map will end early. progress_bar : str, optional Progress bar options's string for showing progress. progress_bar_kwargs : dict, optional @@ -166,8 +166,8 @@ def parallel_map(task, values, task_args=None, task_kwargs=None, Dictionary containing entry for: - timeout: float, Maximum time (sec) for the whole map. - job_timeout: float, Maximum time (sec) for each job in the map. - - num_cpus: int, Number of job to run at once. - - fail_fast: bool, Raise an error at the first. + - num_cpus: int, Number of jobs to run at once. + - fail_fast: bool, Abort at the first error. Returns ------- @@ -294,14 +294,15 @@ def loky_pmap(task, values, task_args=None, task_kwargs=None, The list or array of values for which the ``task`` function is to be evaluated. task_args : list, optional - The optional additional argument to the ``task`` function. + The optional additional arguments to the ``task`` function. task_kwargs : dictionary, optional - The optional additional keyword argument to the ``task`` function. + The optional additional keyword arguments to the ``task`` function. reduce_func : func, optional - If provided, it will be called with the output of each tasks instead of - storing a them in a list. It should return None or a number. When - returning a number, it represent the estimation of the number of task - left. On a return <= 0, the map will end early. + If provided, it will be called with the output of each task instead of + storing them in a list. Note that the order in which results are + passed to ``reduce_func`` is not defined. It should return None or a + number. When returning a number, it represents the estimation of the + number of tasks left. On a return <= 0, the map will end early. progress_bar : str, optional Progress bar options's string for showing progress. progress_bar_kwargs : dict, optional @@ -310,8 +311,8 @@ def loky_pmap(task, values, task_args=None, task_kwargs=None, Dictionary containing entry for: - timeout: float, Maximum time (sec) for the whole map. - job_timeout: float, Maximum time (sec) for each job in the map. - - num_cpus: int, Number of job to run at once. - - fail_fast: bool, Raise an error at the first. + - num_cpus: int, Number of jobs to run at once. + - fail_fast: bool, Abort at the first error. Returns ------- @@ -327,23 +328,25 @@ def loky_pmap(task, values, task_args=None, task_kwargs=None, if task_kwargs is None: task_kwargs = {} map_kw = _read_map_kw(map_kw) - os.environ['QUTIP_IN_PARALLEL'] = 'TRUE' - from loky import get_reusable_executor, TimeoutError + end_time = map_kw['timeout'] + time.time() + job_time = map_kw['job_timeout'] progress_bar = progress_bars[progress_bar]( len(values), **progress_bar_kwargs ) - executor = get_reusable_executor(max_workers=map_kw['num_cpus']) - end_time = map_kw['timeout'] + time.time() - job_time = map_kw['job_timeout'] - results = None - remaining_ntraj = None errors = {} + remaining_ntraj = None if reduce_func is None: results = [None] * len(values) + else: + results = None + os.environ['QUTIP_IN_PARALLEL'] = 'TRUE' + from loky import get_reusable_executor, TimeoutError try: + executor = get_reusable_executor(max_workers=map_kw['num_cpus']) + jobs = [executor.submit(task, value, *task_args, **task_kwargs) for value in values] @@ -373,9 +376,9 @@ def loky_pmap(task, values, task_args=None, task_kwargs=None, [job.cancel() for job in jobs] finally: + os.environ['QUTIP_IN_PARALLEL'] = 'FALSE' executor.shutdown() progress_bar.finished() - os.environ['QUTIP_IN_PARALLEL'] = 'FALSE' if errors: raise MapExceptions( f"{len(errors)} iterations failed in loky_pmap", From aee39d63ac18854a370989ba037ed5917d5ec50a Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Thu, 21 Dec 2023 07:31:23 +0900 Subject: [PATCH 133/247] Fixed small bug in parallel_map --- qutip/solver/parallel.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index 7f486e94d2..91297338d1 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -203,11 +203,11 @@ def _done_callback(future): if not future.cancelled(): try: result = future.result() + remaining_ntraj = result_func(future._i, result) + if remaining_ntraj is not None and remaining_ntraj <= 0: + finished.append(True) except Exception as e: errors[future._i] = e - remaining_ntraj = result_func(future._i, result) - if remaining_ntraj is not None and remaining_ntraj <= 0: - finished.append(True) progress_bar.update() if sys.version_info >= (3, 7): From b00fb0396d10efc060e0ab00d62786d071068eb2 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Thu, 21 Dec 2023 08:31:48 +0900 Subject: [PATCH 134/247] Fixed a bug where CTRL+C had to be pressed twice to exit the program during parallel_map --- qutip/solver/parallel.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index 91297338d1..7cf250aa39 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -208,6 +208,18 @@ def _done_callback(future): finished.append(True) except Exception as e: errors[future._i] = e + except KeyboardInterrupt: + # When a keyboard interrupt happens, it is raised in the main + # thread and in all worker threads. The worker threads have + # already returned and the main thread is only waiting for the + # ProcessPoolExecutor to shutdown before exiting. If the call + # to `future.result()` in this callback function raises the + # KeyboardInterrupt again, it makes the system enter a kind of + # deadlock state, where the user has to press CTRL+C a second + # time to actually end the program. For that reason, we + # silently ignore the KeyboardInterrupt here, avoiding the + # deadlock and allowing the main thread to exit. + pass progress_bar.update() if sys.version_info >= (3, 7): From 3332bd0ee783c7d048fac5d2a7efdd441e21f17f Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Thu, 21 Dec 2023 09:06:18 +0900 Subject: [PATCH 135/247] parallel_map: PEP8, remove unused variable --- qutip/solver/parallel.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index 7cf250aa39..afb8dd3685 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -184,7 +184,6 @@ def parallel_map(task, values, task_args=None, task_kwargs=None, task_kwargs = {} map_kw = _read_map_kw(map_kw) end_time = map_kw['timeout'] + time.time() - job_time = map_kw['job_timeout'] progress_bar = progress_bars[progress_bar]( len(values), **progress_bar_kwargs @@ -194,10 +193,11 @@ def parallel_map(task, values, task_args=None, task_kwargs=None, finished = [] if reduce_func is not None: results = None - result_func = lambda i, value: reduce_func(value) + def result_func(_, value): + return reduce_func(value) else: results = [None] * len(values) - result_func = lambda i, value: results.__setitem__(i, value) + result_func = results.__setitem__ def _done_callback(future): if not future.cancelled(): From 1227bc2e7f86adabdb6804ccc13717a2361e719c Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 21 Dec 2023 15:01:52 -0500 Subject: [PATCH 136/247] Fix links to apidoc Remove reference to Bloch3D Remove page on parfor Add section about data layer Replace quantum control section with link to qutip-qtrl documentation --- doc/apidoc/classes.rst | 3 - doc/apidoc/functions.rst | 2 +- doc/biblio.rst | 19 ++-- doc/guide/figures/bloch3d+data.png | Bin 49982 -> 0 bytes doc/guide/figures/bloch3d+points.png | Bin 55538 -> 0 bytes doc/guide/figures/bloch3d-blank.png | Bin 46765 -> 0 bytes doc/guide/guide-basics.rst | 42 +++++++-- doc/guide/guide-bloch.rst | 110 ++---------------------- doc/guide/guide-control.rst | 66 +------------- doc/guide/guide-correlation.rst | 18 ++-- doc/guide/guide-measurement.rst | 8 +- doc/guide/guide-parfor.rst | 119 ------------------------- doc/guide/guide-piqs.rst | 8 +- doc/guide/guide-random.rst | 48 +++++------ doc/guide/guide-saving.rst | 9 +- doc/guide/guide-steady.rst | 12 +-- doc/guide/guide-super.rst | 17 ++-- doc/guide/guide-tensor.rst | 124 +++++---------------------- doc/guide/guide-visualization.rst | 7 +- doc/guide/guide.rst | 3 +- doc/guide/heom/intro.rst | 2 +- 21 files changed, 145 insertions(+), 472 deletions(-) delete mode 100644 doc/guide/figures/bloch3d+data.png delete mode 100644 doc/guide/figures/bloch3d+points.png delete mode 100644 doc/guide/figures/bloch3d-blank.png delete mode 100644 doc/guide/guide-parfor.rst diff --git a/doc/apidoc/classes.rst b/doc/apidoc/classes.rst index 19f0ed685b..ecd0cd7a97 100644 --- a/doc/apidoc/classes.rst +++ b/doc/apidoc/classes.rst @@ -31,9 +31,6 @@ Bloch sphere .. autoclass:: qutip.bloch.Bloch :members: -.. autoclass:: qutip.bloch3d.Bloch3d - :members: - Distributions ------------- diff --git a/doc/apidoc/functions.rst b/doc/apidoc/functions.rst index c54628f1c4..220ce0c62d 100644 --- a/doc/apidoc/functions.rst +++ b/doc/apidoc/functions.rst @@ -41,7 +41,7 @@ Random Operators and States --------------------------- .. automodule:: qutip.random_objects - :members: rand_dm, rand_herm, rand_ket, rand_stochastic, rand_unitary, rand_super, rand_super_bcsz + :members: rand_dm, rand_herm, rand_ket, rand_stochastic, rand_unitary, rand_super, rand_super_bcsz, rand_kraus_map Superoperators and Liouvillians diff --git a/doc/biblio.rst b/doc/biblio.rst index 248e492fa4..1bdabce891 100644 --- a/doc/biblio.rst +++ b/doc/biblio.rst @@ -1,5 +1,5 @@ .. _biblo: - + Bibliography ============ @@ -14,7 +14,7 @@ Bibliography .. The trick with |text|_ is to get an italic link, and is described in the Docutils FAQ at https://docutils.sourceforge.net/FAQ.html#is-nested-inline-markup-possible. - + .. |theory-qi| replace:: *Theory of Quantum Information* .. _theory-qi: https://cs.uwaterloo.ca/~watrous/TQI-notes/ @@ -39,10 +39,10 @@ Bibliography .. [WBC11] C. Wood, J. Biamonte, D. G. Cory, *Tensor networks and graphical calculus for open quantum systems*. :arxiv:`1111.6950` - + .. [dAless08] D. d’Alessandro, *Introduction to Quantum Control and Dynamics*, (Chapman & Hall/CRC, 2008). - + .. [Byrd95] R. H. Byrd, P. Lu, J. Nocedal, and C. Zhu, *A Limited Memory Algorithm for Bound Constrained Optimization*, SIAM J. Sci. Comput. **16**, 1190 (1995). :doi:`10.1137/0916069` @@ -51,19 +51,16 @@ Bibliography .. [Lloyd14] S. Lloyd and S. Montangero, *Information theoretical analysis of quantum optimal control*, Phys. Rev. Lett. **113**, 010502 (2014). :doi:`10.1103/PhysRevLett.113.010502` - + .. [Doria11] P. Doria, T. Calarco & S. Montangero, *Optimal Control Technique for Many-Body Quantum Dynamics*, Phys. Rev. Lett. **106**, 190501 (2011). :doi:`10.1103/PhysRevLett.106.190501` - + .. [Caneva11] T. Caneva, T. Calarco, & S. Montangero, *Chopped random-basis quantum optimization*, Phys. Rev. A **84**, 022326 (2011). :doi:`10.1103/PhysRevA.84.022326` - + .. [Rach15] N. Rach, M. M. Müller, T. Calarco, and S. Montangero, *Dressing the chopped-random-basis optimization: A bandwidth-limited access to the trap-free landscape*, Phys. Rev. A. **92**, 062343 (2015). :doi:`10.1103/PhysRevA.92.062343` -.. [DYNAMO] - S. Machnes, U. Sander, S. J. Glaser, P. De Fouquieres, A. Gruslys, S. Schirmer, and T. Schulte-Herbrueggen, *Comparing, Optimising and Benchmarking Quantum Control Algorithms in a Unifying Programming Framework*, Phys. Rev. A. **84**, 022305 (2010). :arxiv:`1011.4874` - .. [Wis09] Wiseman, H. M. & Milburn, G. J. *Quantum Measurement and Control*, (Cambridge University Press, 2009). @@ -76,4 +73,4 @@ Bibliography B. Donvil, P. Muratore-Ginanneschi, *Quantum trajectory framework for general time-local master equations*, Nat Commun **13**, 4140 (2022). :doi:`10.1038/s41467-022-31533-8`. .. [Abd19] - M. Abdelhafez, D. I. Schuster, J. Koch, *Gradient-based optimal control of open quantum systems using quantumtrajectories and automatic differentiation*, Phys. Rev. A **99**, 052327 (2019). :doi:`10.1103/PhysRevA.99.052327`. \ No newline at end of file + M. Abdelhafez, D. I. Schuster, J. Koch, *Gradient-based optimal control of open quantum systems using quantumtrajectories and automatic differentiation*, Phys. Rev. A **99**, 052327 (2019). :doi:`10.1103/PhysRevA.99.052327`. diff --git a/doc/guide/figures/bloch3d+data.png b/doc/guide/figures/bloch3d+data.png deleted file mode 100644 index 4214368d3b3121e9dab7fd055ec60a139437473a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 49982 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
pW{;iVf-Y_HySpeaXbES⪙kXmf|snxH&lDNeBw|F@@jGS<}}J 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 Kwo=-}?|%RU1LWKQ diff --git a/doc/guide/figures/bloch3d+points.png b/doc/guide/figures/bloch3d+points.png deleted file mode 100644 index 14a8030365a17e72e44923f507c3c4c655da8c31..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 55538 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 X`1r7woQ#T9 diff --git a/doc/guide/figures/bloch3d-blank.png b/doc/guide/figures/bloch3d-blank.png deleted file mode 100644 index 7888a7a89671c1f557efeb328d7c09b8a2389f19..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 46765 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 diff --git a/doc/guide/guide-basics.rst b/doc/guide/guide-basics.rst index fa86e506ab..19b035903b 100644 --- a/doc/guide/guide-basics.rst +++ b/doc/guide/guide-basics.rst @@ -1,8 +1,8 @@ .. _basics: -************************************ +*********************************** Basic Operations on Quantum Objects -************************************ +*********************************** .. _basics-first: @@ -323,13 +323,43 @@ For the destruction operator above: False >>> q.data + Dia(shape=(4, 4), num_diag=1) + + +The ``data`` attribute returns a Qutip diagonal matrix. +``Qobj`` instances store their data in Qutip matrix format. +In the core qutip module, the ``Dense``, ``CSR`` and ``Dia`` formats are available, but other module can add other formats. +For example, the qutip-jax module add ``Jax`` and ``JaxDia`` formats. +One can always access the underlying matrix as a numpy array using :meth:`.Qobj.full`. +It is also possible to access the underlying data as is in a common format using :meth:`.Qobj.data_as`. + +.. doctest:: [basics] + :options: +NORMALIZE_WHITESPACE + + >>> q.data_as("dia_matrix") + <4x4 sparse matrix of type '' + with 3 stored elements (1 diagonals) in DIAgonal format> + + +Conversion between storage type is done using the :meth:`.Qobj.to` method. + +.. doctest:: [basics] + :options: +NORMALIZE_WHITESPACE + + >>> q.to("CSR").data + CSR(shape=(4, 4), nnz=3) + + >>> q.to("CSR").data_as("CSR_matrix") <4x4 sparse matrix of type '' - with 3 stored elements in Compressed Sparse Row format> + with 3 stored elements in Compressed Sparse Row format> +Note that :meth:`.Qobj.data_as` does not do the conversion. -The data attribute returns a message stating that the data is a sparse matrix. All ``Qobj`` instances store their data as a sparse matrix to save memory. -To access the underlying dense matrix one needs to use the :meth:`.Qobj.full` function as described below. +QuTiP will do conversion when needed to keep everything working in any format. +However these conversions could slow down the computations and it is recomented to keep to one family of format. +For example, core qutip ``Dense`` and ``CSR`` work well together and binary operation between these format is efficient. +However binary operations between ``Dense`` and ``Jax`` should be avoided since it is not clear whether the operation will be executed by Jax, (possibly on GPU) or numpy. .. _basics-qobj-math: @@ -400,7 +430,7 @@ In addition, the logic operators "is equal" `==` and "is not equal" `!=` are als .. _basics-functions: Functions operating on Qobj class -================================== +================================= Like attributes, the quantum object class has defined functions (methods) that operate on ``Qobj`` class instances. For a general quantum object ``Q``: diff --git a/doc/guide/guide-bloch.rst b/doc/guide/guide-bloch.rst index 976264320e..696b18c4e9 100644 --- a/doc/guide/guide-bloch.rst +++ b/doc/guide/guide-bloch.rst @@ -9,39 +9,28 @@ Plotting on the Bloch Sphere Introduction ============ -When studying the dynamics of a two-level system, it is often convenient to visualize the state of the system by plotting the state-vector or density matrix on the Bloch sphere. In QuTiP, we have created two different classes to allow for easy creation and manipulation of data sets, both vectors and data points, on the Bloch sphere. The :class:`qutip.bloch.Bloch` class, uses Matplotlib to render the Bloch sphere, where as :class:`qutip.bloch3d.Bloch3d` uses the Mayavi rendering engine to generate a more faithful 3D reconstruction of the Bloch sphere. +When studying the dynamics of a two-level system, it is often convenient to visualize the state of the system by plotting the state-vector or density matrix on the Bloch sphere. In QuTiP, we have created two different classes to allow for easy creation and manipulation of data sets, both vectors and data points, on the Bloch sphere. .. _bloch-class: -The Bloch and Bloch3d Classes -============================= +The Bloch Class +=============== In QuTiP, creating a Bloch sphere is accomplished by calling either: .. plot:: - :context: + :context: reset b = qutip.Bloch() -which will load an instance of the :class:`qutip.bloch.Bloch` class, or using :: - - >>> b3d = qutip.Bloch3d() - -that loads the :class:`qutip.bloch3d.Bloch3d` version. Before getting into the details of these objects, we can simply plot the blank Bloch sphere associated with these instances via: +which will load an instance of the :class:`~qutip.bloch.Bloch` class. +Before getting into the details of these objects, we can simply plot the blank Bloch sphere associated with these instances via: .. plot:: :context: b.make_sphere() -or - -.. _image-blank3d: - -.. figure:: figures/bloch3d-blank.png - :width: 3.5in - :figclass: align-center - In addition to the ``show`` command, see the API documentation for :class:`~qutip.bloch.Bloch` for a full list of other available functions. As an example, we can add a single data point: @@ -87,14 +76,7 @@ In total, the code for constructing our Bloch sphere with one vector, one state, b.add_states(up) b.render() -where we have removed the extra ``show()`` commands. Replacing ``b=Bloch()`` with ``b=Bloch3d()`` in the above code generates the following 3D Bloch sphere. - -.. _image-bloch3ddata: - -.. figure:: figures/bloch3d+data.png - :width: 3.5in - :figclass: align-center - +where we have removed the extra ``show()`` commands. We can also plot multiple points, vectors, and states at the same time by passing list or arrays instead of individual elements. Before giving an example, we can use the `clear()` command to remove the current data from our Bloch sphere instead of creating a new instance: @@ -183,26 +165,9 @@ Now, the data points cycle through a variety of predefined colors. Now lets add b.add_points([xz, yz, zz]) # no 'm' b.render() -Again, the same plot can be generated using the :class:`qutip.bloch3d.Bloch3d` class by replacing ``Bloch`` with ``Bloch3d``: - -.. figure:: figures/bloch3d+points.png - :width: 3.5in - :figclass: align-center A more slick way of using this 'multi' color feature is also given in the example, where we set the color of the markers as a function of time. -Differences Between Bloch and Bloch3d -------------------------------------- -While in general the ``Bloch`` and ``Bloch3d`` classes are interchangeable, there are some important differences to consider when choosing between them. - -- The ``Bloch`` class uses Matplotlib to generate figures. As such, the data plotted on the sphere is in reality just a 2D object. In contrast the ``Bloch3d`` class uses the 3D rendering engine from VTK via mayavi to generate the sphere and the included data. In this sense the ``Bloch3d`` class is much more advanced, as objects are rendered in 3D leading to a higher quality figure. - -- Only the ``Bloch`` class can be embedded in a Matplotlib figure window. Thus if you want to combine a Bloch sphere with another figure generated in QuTiP, you can not use ``Bloch3d``. Of course you can always post-process your figures using other software to get the desired result. - -- Due to limitations in the rendering engine, the ``Bloch3d`` class does not support LaTeX for text. Again, you can get around this by post-processing. - -- The user customizable attributes for the ``Bloch`` and ``Bloch3d`` classes are not identical. Therefore, if you change the properties of one of the classes, these changes will cause an exception if the class is switched. - .. _bloch-config: @@ -270,67 +235,6 @@ At the end of the last section we saw that the colors and marker shapes of the d | b.zlpos | Position of z-axis labels | ``[1.2, -1.2]`` | +---------------+---------------------------------------------------------+-------------------------------------------------+ -Bloch3d Class Options ---------------------- - -The Bloch3d sphere is also customizable. Note however that the attributes for the ``Bloch3d`` class are not in one-to-one -correspondence to those of the ``Bloch`` class due to the different underlying rendering engines. Assuming ``b=Bloch3d()``: - -.. tabularcolumns:: | p{3cm} | p{7cm} | p{7cm} | - -.. cssclass:: table-striped - -+---------------+---------------------------------------------------------+---------------------------------------------+ -| Attribute | Function | Default Setting | -+===============+=========================================================+=============================================+ -| b.fig | User supplied Mayavi Figure instance. Set by ``fig`` | ``None`` | -| | keyword arg. | | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.font_color | Color of fonts | ``'black'`` | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.font_scale | Scale of fonts | 0.08 | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.frame | Draw wireframe for sphere? | ``True`` | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.frame_alpha | Transparency of wireframe | 0.05 | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.frame_color | Color of wireframe | ``'gray'`` | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.frame_num | Number of wireframe elements to draw | 8 | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.frame_radius| Radius of wireframe lines | 0.005 | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.point_color | List of colors for Bloch point markers to cycle through | ``['r', 'g', 'b', 'y']`` | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.point_mode | Type of point markers to draw | ``'sphere'`` | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.point_size | Size of points | 0.075 | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.sphere_alpha| Transparency of Bloch sphere | 0.1 | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.sphere_color| Color of Bloch sphere | ``'#808080'`` | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.size | Sets size of figure window | ``[500, 500]`` (500x500 pixels) | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.vector_color| List of colors for Bloch vectors to cycle through | ``['r', 'g', 'b', 'y']`` | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.vector_width| Width of Bloch vectors | 3 | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.view | Azimuthal and Elevation viewing angles | ``[45, 65]`` | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.xlabel | Labels for x-axis | ``['|x>', '']`` +x and -x | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.xlpos | Position of x-axis labels | ``[1.07, -1.07]`` | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.ylabel | Labels for y-axis | ``['$y$', '']`` +y and -y | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.ylpos | Position of y-axis labels | ``[1.07, -1.07]`` | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.zlabel | Labels for z-axis | ``['|0>', '|1>']`` +z and -z | -+---------------+---------------------------------------------------------+---------------------------------------------+ -| b.zlpos | Position of z-axis labels | ``[1.07, -1.07]`` | -+---------------+---------------------------------------------------------+---------------------------------------------+ - These properties can also be accessed via the print command: .. doctest:: diff --git a/doc/guide/guide-control.rst b/doc/guide/guide-control.rst index f3c31ad7e9..b5baf9b0d5 100644 --- a/doc/guide/guide-control.rst +++ b/doc/guide/guide-control.rst @@ -191,65 +191,7 @@ algorithm. Optimal Quantum Control in QuTiP ================================ -There are two separate implementations of optimal control inside QuTiP. The -first is an implementation of first order GRAPE, and is not further described -here, but there are the example notebooks. The second is referred to as Qtrl -(when a distinction needs to be made) as this was its name before it was -integrated into QuTiP. Qtrl uses the Scipy optimize functions to perform the -multi-variable optimisation, typically the L-BFGS-B method for GRAPE and -Nelder-Mead for CRAB. The GRAPE implementation in Qtrl was initially based on -the open-source package DYNAMO, which is a MATLAB implementation, and is -described in [DYNAMO]_. It has since been restructured and extended for -flexibility and compatibility within QuTiP. - -The rest of this section describes the Qtrl implementation and how to use it. - -Object Model - The Qtrl code is organised in a hierarchical object model in order to try and maximise configurability whilst maintaining some clarity. It is not necessary to understand the model in order to use the pulse optimisation functions, but it is the most flexible method of using Qtrl. If you just want to use a simple single function call interface, then jump to :ref:`pulseoptim-functions` - -.. figure:: figures/qtrl-code_object_model.png - :align: center - :width: 3.5in - - Qtrl code object model. - -The object's properties and methods are described in detail in the documentation, so that will not be repeated here. - -OptimConfig - The OptimConfig object is used simply to hold configuration parameters used by all the objects. Typically this is the subclass types for the other objects and parameters for the users specific requirements. The ``loadparams`` module can be used read parameter values from a configuration file. - -Optimizer - This acts as a wrapper to the ``Scipy.optimize`` functions that perform the work of the pulse optimisation algorithms. Using the main classes the user can specify which of the optimisation methods are to be used. There are subclasses specifically for the BFGS and L-BFGS-B methods. There is another subclass for using the CRAB algorithm. - -Dynamics - This is mainly a container for the lists that hold the dynamics generators, propagators, and time evolution operators in each timeslot. The combining of dynamics generators is also complete by this object. Different subclasses support a range of types of quantum systems, including closed systems with unitary dynamics, systems with quadratic Hamiltonians that have Gaussian states and symplectic transforms, and a general subclass that can be used for open system dynamics with Lindbladian operators. - -PulseGen - There are many subclasses of pulse generators that generate different types of pulses as the initial amplitudes for the optimisation. Often the goal cannot be achieved from all starting conditions, and then typically some kind of random pulse is used and repeated optimisations are performed until the desired infidelity is reached or the minimum infidelity found is reported. - There is a specific subclass that is used by the CRAB algorithm to generate the pulses based on the basis coefficients that are being optimised. - -TerminationConditions - This is simply a convenient place to hold all the properties that will determine when the single optimisation run terminates. Limits can be set for number of iterations, time, and of course the target infidelity. - -Stats - Performance data are optionally collected during the optimisation. This object is shared to a single location to store, calculate and report run statistics. - -FidelityComputer - The subclass of the fidelity computer determines the type of fidelity measure. These are closely linked to the type of dynamics in use. These are also the most commonly user customised subclasses. - -PropagatorComputer - This object computes propagators from one timeslot to the next and also the propagator gradient. The options are using the spectral decomposition or Frechet derivative, as discussed above. - -TimeslotComputer - Here the time evolution is computed by calling the methods of the other computer objects. - -OptimResult - The result of a pulse optimisation run is returned as an object with properties for the outcome in terms of the infidelity, reason for termination, performance statistics, final evolution, and more. - -.. _pulseoptim-functions: - -Using the pulseoptim functions -============================== -The simplest method for optimising a control pulse is to call one of the functions in the ``pulseoptim`` module. This automates the creation and configuration of the necessary objects, generation of initial pulses, running the optimisation and returning the result. There are functions specifically for unitary dynamics, and also specifically for the CRAB algorithm (GRAPE is the default). The ``optimise_pulse`` function can in fact be used for unitary dynamics and / or the CRAB algorithm, the more specific functions simply have parameter names that are more familiar in that application. - -A semi-automated method is to use the ``create_optimizer_objects`` function to generate and configure all the objects, then manually set the initial pulse and call the optimisation. This would be more efficient when repeating runs with different starting conditions. +The Quantum Control part of qutip has been moved to it's own project. +Previously available implementation is now located in the `qutip-qtrl `_ module. +A newer interface with upgraded capacities is also being developped in `qutip-qoc `_. +Please give these module a look. diff --git a/doc/guide/guide-correlation.rst b/doc/guide/guide-correlation.rst index 2ca650263f..a7362e42cd 100644 --- a/doc/guide/guide-correlation.rst +++ b/doc/guide/guide-correlation.rst @@ -4,7 +4,7 @@ Two-time correlation functions ****************************** -With the QuTiP time-evolution functions (for example :func:`qutip.mesolve` and :func:`qutip.mcsolve`), a state vector or density matrix can be evolved from an initial state at :math:`t_0` to an arbitrary time :math:`t`, :math:`\rho(t)=V(t, t_0)\left\{\rho(t_0)\right\}`, where :math:`V(t, t_0)` is the propagator defined by the equation of motion. The resulting density matrix can then be used to evaluate the expectation values of arbitrary combinations of *same-time* operators. +With the QuTiP time-evolution functions (for example :func:`.mesolve` and :func:`.mcsolve`), a state vector or density matrix can be evolved from an initial state at :math:`t_0` to an arbitrary time :math:`t`, :math:`\rho(t)=V(t, t_0)\left\{\rho(t_0)\right\}`, where :math:`V(t, t_0)` is the propagator defined by the equation of motion. The resulting density matrix can then be used to evaluate the expectation values of arbitrary combinations of *same-time* operators. To calculate *two-time* correlation functions on the form :math:`\left`, we can use the quantum regression theorem (see, e.g., [Gar03]_) to write @@ -45,7 +45,7 @@ QuTiP provides a family of functions that assists in the process of calculating +----------------------------------+--------------------------------------------------+ -The most common use-case is to calculate the two time correlation function :math:`\left`. :func:`qutip.correlation_2op_1t` performs this task with sensible default values, but only allows using the :func:`mesolve` solver. From QuTiP 5.0 we added :func:`qutip.correlation_3op`. This function can also calculate correlation functions with two or three operators and with one or two times. Most importantly, this function accepts alternative solvers such as :func:`brmesolve`. +The most common use-case is to calculate the two time correlation function :math:`\left`. :func:`.correlation_2op_1t` performs this task with sensible default values, but only allows using the :func:`.mesolve` solver. From QuTiP 5.0 we added :func:`.correlation_3op`. This function can also calculate correlation functions with two or three operators and with one or two times. Most importantly, this function accepts alternative solvers such as :func:`.brmesolve`. .. _correlation-steady: @@ -55,7 +55,7 @@ Steadystate correlation function The following code demonstrates how to calculate the :math:`\left` correlation for a leaky cavity with three different relaxation rates. .. plot:: - :context: + :context: close-figs times = np.linspace(0,10.0,200) a = destroy(10) @@ -85,7 +85,7 @@ Given a correlation function :math:`\left` we can define the S(\omega) = \int_{-\infty}^{\infty} \left e^{-i\omega\tau} d\tau. -In QuTiP, we can calculate :math:`S(\omega)` using either :func:`qutip.correlation.spectrum_ss`, which first calculates the correlation function using one of the time-dependent solvers and then performs the Fourier transform semi-analytically, or we can use the function :func:`qutip.correlation.spectrum_correlation_fft` to numerically calculate the Fourier transform of a given correlation data using FFT. +In QuTiP, we can calculate :math:`S(\omega)` using either :func:`.spectrum`, which first calculates the correlation function using one of the time-dependent solvers and then performs the Fourier transform semi-analytically, or we can use the function :func:`.spectrum_correlation_fft` to numerically calculate the Fourier transform of a given correlation data using FFT. The following example demonstrates how these two functions can be used to obtain the emission power spectrum. @@ -99,13 +99,13 @@ The following example demonstrates how these two functions can be used to obtain Non-steadystate correlation function ==================================== -More generally, we can also calculate correlation functions of the kind :math:`\left`, i.e., the correlation function of a system that is not in its steady state. In QuTiP, we can evaluate such correlation functions using the function :func:`qutip.correlation.correlation_2op_2t`. The default behavior of this function is to return a matrix with the correlations as a function of the two time coordinates (:math:`t_1` and :math:`t_2`). +More generally, we can also calculate correlation functions of the kind :math:`\left`, i.e., the correlation function of a system that is not in its steady state. In QuTiP, we can evaluate such correlation functions using the function :func:`.correlation_2op_2t`. The default behavior of this function is to return a matrix with the correlations as a function of the two time coordinates (:math:`t_1` and :math:`t_2`). .. plot:: guide/scripts/correlation_ex2.py :width: 5.0in :include-source: -However, in some cases we might be interested in the correlation functions on the form :math:`\left`, but only as a function of time coordinate :math:`t_2`. In this case we can also use the :func:`qutip.correlation.correlation_2op_2t` function, if we pass the density matrix at time :math:`t_1` as second argument, and `None` as third argument. The :func:`qutip.correlation.correlation_2op_2t` function then returns a vector with the correlation values corresponding to the times in `taulist` (the fourth argument). +However, in some cases we might be interested in the correlation functions on the form :math:`\left`, but only as a function of time coordinate :math:`t_2`. In this case we can also use the :func:`.correlation_2op_2t` function, if we pass the density matrix at time :math:`t_1` as second argument, and `None` as third argument. The :func:`.correlation_2op_2t` function then returns a vector with the correlation values corresponding to the times in `taulist` (the fourth argument). Example: first-order optical coherence function ----------------------------------------------- @@ -116,7 +116,7 @@ This example demonstrates how to calculate a correlation function on the form :m :width: 5.0in :include-source: -For convenience, the steps for calculating the first-order coherence function have been collected in the function :func:`qutip.correlation.coherence_function_g1`. +For convenience, the steps for calculating the first-order coherence function have been collected in the function :func:`.coherence_function_g1`. Example: second-order optical coherence function ------------------------------------------------ @@ -129,7 +129,7 @@ The second-order optical coherence function, with time-delay :math:`\tau`, is de For a coherent state :math:`g^{(2)}(\tau) = 1`, for a thermal state :math:`g^{(2)}(\tau=0) = 2` and it decreases as a function of time (bunched photons, they tend to appear together), and for a Fock state with :math:`n` photons :math:`g^{(2)}(\tau = 0) = n(n - 1)/n^2 < 1` and it increases with time (anti-bunched photons, more likely to arrive separated in time). -To calculate this type of correlation function with QuTiP, we can use :func:`qutip.correlation.correlation_3op_1t`, which computes a correlation function on the form :math:`\left` (three operators, one delay-time vector). +To calculate this type of correlation function with QuTiP, we can use :func:`.correlation_3op_1t`, which computes a correlation function on the form :math:`\left` (three operators, one delay-time vector). We first have to combine the central two operators into one single one as they are evaluated at the same time, e.g. here we do :math:`a^\dagger(\tau)a(\tau) = (a^\dagger a)(\tau)`. The following code calculates and plots :math:`g^{(2)}(\tau)` as a function of :math:`\tau` for a coherent, thermal and Fock state. @@ -138,4 +138,4 @@ The following code calculates and plots :math:`g^{(2)}(\tau)` as a function of : :width: 5.0in :include-source: -For convenience, the steps for calculating the second-order coherence function have been collected in the function :func:`qutip.correlation.coherence_function_g2`. +For convenience, the steps for calculating the second-order coherence function have been collected in the function :func:`.coherence_function_g2`. diff --git a/doc/guide/guide-measurement.rst b/doc/guide/guide-measurement.rst index 2d74d1fab7..89149da74d 100644 --- a/doc/guide/guide-measurement.rst +++ b/doc/guide/guide-measurement.rst @@ -42,8 +42,8 @@ along the z-axis. We choose what to measure (in this case) by selecting a **measurement operator**. For example, -we could select :func:`~qutip.operators.sigmaz` which measures the z-component of the -spin of a spin-1/2 particle, or :func:`~qutip.operators.sigmax` which measures the +we could select :func:`.sigmaz` which measures the z-component of the +spin of a spin-1/2 particle, or :func:`.sigmax` which measures the x-component: .. testcode:: @@ -276,7 +276,7 @@ when called with a single observable: - `eigenstates` is an array of the eigenstates of the measurement operator, i.e. a list of the possible final states after the measurement is complete. - Each element of the array is a :obj:`~qutip.Qobj`. + Each element of the array is a :obj:`.Qobj`. - `probabilities` is a list of the probabilities of each measurement result. In our example the value is `[0.5, 0.5]` since the `up` state has equal @@ -343,7 +343,7 @@ the following result. The function :func:`~qutip.measurement.measurement_statistics` then returns two values: * `collapsed_states` is an array of the possible final states after the - measurement is complete. Each element of the array is a :obj:`~qutip.Qobj`. + measurement is complete. Each element of the array is a :obj:`.Qobj`. * `probabilities` is a list of the probabilities of each measurement outcome. diff --git a/doc/guide/guide-parfor.rst b/doc/guide/guide-parfor.rst deleted file mode 100644 index 4491cf30af..0000000000 --- a/doc/guide/guide-parfor.rst +++ /dev/null @@ -1,119 +0,0 @@ -.. _parfor: - -****************************************** -Parallel computation -****************************************** - -Parallel map and parallel for-loop ----------------------------------- - -Often one is interested in the output of a given function as a single-parameter is varied. -For instance, we can calculate the steady-state response of our system as the driving frequency is varied. -In cases such as this, where each iteration is independent of the others, we can speedup the calculation by performing the iterations in parallel. -In QuTiP, parallel computations may be performed using the :func:`qutip.solver.parallel.parallel_map` function. - -To use the this function we need to define a function of one or more variables, and the range over which one of these variables are to be evaluated. For example: - - -.. doctest:: - :skipif: not os_nt - :options: +NORMALIZE_WHITESPACE - - >>> result = parallel_map(func1, range(10)) - - >>> result_array = np.array(result) - - >>> print(result_array[:, 0]) # == a - [0 1 2 3 4 5 6 7 8 9] - - >>> print(result_array[:, 1]) # == b - [ 0 1 4 9 16 25 36 49 64 81] - - >>> print(result_array[:, 2]) # == c - [ 0 1 8 27 64 125 216 343 512 729] - - >>> print(result) - [(0, 0, 0), (1, 1, 1), (2, 4, 8), (3, 9, 27), (4, 16, 64)] - - -The :func:`qutip.solver.parallel.parallel_map` function is not limited to just numbers, but also works for a variety of outputs: - -.. doctest:: - :skipif: not os_nt - :options: +NORMALIZE_WHITESPACE - - >>> def func2(x): return x, Qobj(x), 'a' * x - - >>> results = parallel_map(func2, range(5)) - - >>> print([result[0] for result in results]) - [0 1 2 3 4] - - >>> print([result[1] for result in results]) - [Quantum object: dims = [[1], [1]], shape = (1, 1), type = bra - Qobj data = - [[0.]] - Quantum object: dims = [[1], [1]], shape = (1, 1), type = bra - Qobj data = - [[1.]] - Quantum object: dims = [[1], [1]], shape = (1, 1), type = bra - Qobj data = - [[2.]] - Quantum object: dims = [[1], [1]], shape = (1, 1), type = bra - Qobj data = - [[3.]] - Quantum object: dims = [[1], [1]], shape = (1, 1), type = bra - Qobj data = - [[4.]]] - - >>>print([result[2] for result in results]) - ['' 'a' 'aa' 'aaa' 'aaaa'] - - -One can also define functions with **multiple** input arguments and keyword arguments. - -.. doctest:: - :skipif: not os_nt - :options: +NORMALIZE_WHITESPACE - - >>> def sum_diff(x, y, z=0): return x + y, x - y, z - - >>> parallel_map(sum_diff, [1, 2, 3], task_args=(np.array([4, 5, 6]),), task_kwargs=dict(z=5.0)) - [(array([5, 6, 7]), array([-3, -4, -5]), 5.0), - (array([6, 7, 8]), array([-2, -3, -4]), 5.0), - (array([7, 8, 9]), array([-1, -2, -3]), 5.0)] - - -The :func:`qutip.solver.parallel.parallel_map` function supports progressbar by setting the keyword argument `progress_bar` to `True`. -The number of cpu used can also be controlled using the `map_kw` keyword, per default, all available cpus are used. - -.. doctest:: - :options: +SKIP - - >>> import time - - >>> def func(x): time.sleep(1) - - >>> result = parallel_map(func, range(50), progress_bar=True, map_kw={"num_cpus": 2}) - - 10.0%. Run time: 3.10s. Est. time left: 00:00:00:27 - 20.0%. Run time: 5.11s. Est. time left: 00:00:00:20 - 30.0%. Run time: 8.11s. Est. time left: 00:00:00:18 - 40.0%. Run time: 10.15s. Est. time left: 00:00:00:15 - 50.0%. Run time: 13.15s. Est. time left: 00:00:00:13 - 60.0%. Run time: 15.15s. Est. time left: 00:00:00:10 - 70.0%. Run time: 18.15s. Est. time left: 00:00:00:07 - 80.0%. Run time: 20.15s. Est. time left: 00:00:00:05 - 90.0%. Run time: 23.15s. Est. time left: 00:00:00:02 - 100.0%. Run time: 25.15s. Est. time left: 00:00:00:00 - Total run time: 28.91s - -There is a function called :func:`qutip.solver.parallel.serial_map` that works as a non-parallel drop-in replacement for :func:`qutip.solver.parallel.parallel_map`, which allows easy switching between serial and parallel computation. -Qutip also has the function :func:`qutip.solver.parallel.loky_map` as another drop-in replacement. It use the `loky` module instead of `multiprocessing` to run in parallel. -Parallel processing is useful for repeated tasks such as generating plots corresponding to the dynamical evolution of your system, or simultaneously simulating different parameter configurations. - - -IPython-based parallel_map --------------------------- - -When QuTiP is used with IPython interpreter, there is an alternative parallel for-loop implementation in the QuTiP module :func:`qutip.ipynbtools`, see :func:`qutip.ipynbtools.parallel_map`. The advantage of this parallel_map implementation is based on IPython's powerful framework for parallelization, so the compute processes are not confined to run on the same host as the main process. diff --git a/doc/guide/guide-piqs.rst b/doc/guide/guide-piqs.rst index f13cf8b3a4..3167918a85 100644 --- a/doc/guide/guide-piqs.rst +++ b/doc/guide/guide-piqs.rst @@ -32,7 +32,7 @@ where :math:`J_{\alpha,n}=\frac{1}{2}\sigma_{\alpha,n}` are SU(2) Pauli spin ope The inclusion of local processes in the dynamics lead to using a Liouvillian space of dimension :math:`4^N`. By exploiting the permutational invariance of identical particles [2-8], the Liouvillian :math:`\mathcal{D}_\text{TLS}(\rho)` can be built as a block-diagonal matrix in the basis of Dicke states :math:`|j, m \rangle`. The system under study is defined by creating an object of the -:code:`Dicke` class, e.g. simply named +:class:`~qutip.piqs.piqs.Dicke` class, e.g. simply named :code:`system`, whose first attribute is - :code:`system.N`, the number of TLSs of the system :math:`N`. @@ -48,8 +48,10 @@ The rates for collective and local processes are simply defined as Then the :code:`system.lindbladian()` creates the total TLS Lindbladian superoperator matrix. Similarly, :code:`system.hamiltonian` defines the TLS hamiltonian of the system :math:`H_\text{TLS}`. -The system's Liouvillian can be built using :code:`system.liouvillian()`. The properties of a Piqs object can be visualized by simply calling -:code:`system`. We give two basic examples on the use of *PIQS*. In the first example the incoherent emission of N driven TLSs is considered. +The system's Liouvillian can be built using :code:`system.liouvillian()`. +The properties of a Piqs object can be visualized by simply calling :code:`system`. +We give two basic examples on the use of *PIQS*. +In the first example the incoherent emission of N driven TLSs is considered. .. code-block:: python diff --git a/doc/guide/guide-random.rst b/doc/guide/guide-random.rst index 82b5920ea8..86d0abc1cd 100644 --- a/doc/guide/guide-random.rst +++ b/doc/guide/guide-random.rst @@ -11,7 +11,7 @@ Generating Random Quantum States & Operators QuTiP includes a collection of random state, unitary and channel generators for simulations, Monte Carlo evaluation, theorem evaluation, and code testing. Each of these objects can be sampled from one of several different distributions. -For example, a random Hermitian operator can be sampled by calling `rand_herm` function: +For example, a random Hermitian operator can be sampled by calling :func:`.rand_herm` function: .. doctest:: [random] :hide: @@ -40,23 +40,23 @@ For example, a random Hermitian operator can be sampled by calling `rand_herm` f .. cssclass:: table-striped -+-------------------------------+--------------------------------------------+------------------------------------------+ -| Random Variable Type | Sampling Functions | Dimensions | -+===============================+============================================+==========================================+ -| State vector (``ket``) | `rand_ket`, | :math:`N \times 1` | -+-------------------------------+--------------------------------------------+------------------------------------------+ -| Hermitian operator (``oper``) | `rand_herm` | :math:`N \times N` | -+-------------------------------+--------------------------------------------+------------------------------------------+ -| Density operator (``oper``) | `rand_dm`, | :math:`N \times N` | -+-------------------------------+--------------------------------------------+------------------------------------------+ -| Unitary operator (``oper``) | `rand_unitary`, | :math:`N \times N` | -+-------------------------------+--------------------------------------------+------------------------------------------+ -| stochastic matrix (``oper``) | `rand_stochastic`, | :math:`N \times N` | -+-------------------------------+--------------------------------------------+------------------------------------------+ -| CPTP channel (``super``) | `rand_super`, `rand_super_bcsz` | :math:`(N \times N) \times (N \times N)` | -+-------------------------------+--------------------------------------------+------------------------------------------+ -| CPTP map (list of ``oper``) | `rand_kraus_map` | :math:`N \times N` (N**2 operators) | -+-------------------------------+--------------------------------------------+------------------------------------------+ ++-------------------------------+-----------------------------------------------+------------------------------------------+ +| Random Variable Type | Sampling Functions | Dimensions | ++===============================+===============================================+==========================================+ +| State vector (``ket``) | :func:`.rand_ket` | :math:`N \times 1` | ++-------------------------------+-----------------------------------------------+------------------------------------------+ +| Hermitian operator (``oper``) | :func:`.rand_herm` | :math:`N \times N` | ++-------------------------------+-----------------------------------------------+------------------------------------------+ +| Density operator (``oper``) | :func:`.rand_dm` | :math:`N \times N` | ++-------------------------------+-----------------------------------------------+------------------------------------------+ +| Unitary operator (``oper``) | :func:`.rand_unitary` | :math:`N \times N` | ++-------------------------------+-----------------------------------------------+------------------------------------------+ +| stochastic matrix (``oper``) | :func:`.rand_stochastic` | :math:`N \times N` | ++-------------------------------+-----------------------------------------------+------------------------------------------+ +| CPTP channel (``super``) | :func:`.rand_super`, :func:`.rand_super_bcsz` | :math:`(N \times N) \times (N \times N)` | ++-------------------------------+-----------------------------------------------+------------------------------------------+ +| CPTP map (list of ``oper``) | :func:`.rand_kraus_map` | :math:`N \times N` (N**2 operators) | ++-------------------------------+-----------------------------------------------+------------------------------------------+ In all cases, these functions can be called with a single parameter :math:`dimensions` that can be the size of the relevant Hilbert space or the dimensions of a random state, unitary or channel. @@ -69,9 +69,9 @@ In all cases, these functions can be called with a single parameter :math:`dimen >>> rand_super_bcsz([[2, 3], [2, 3]]).dims [[[2, 3], [2, 3]], [[2, 3], [2, 3]]] -Several of the random `Qobj` function in QuTiP support additional parameters as well, namely *density* and *distribution*. -`rand_dm`, `rand_herm`, `rand_unitary` and `rand_ket` can be created using multiple method controlled by *distribution*. -The `rand_ket`, `rand_herm` and `rand_unitary` functions can return quantum objects such that a fraction of the elements are identically equal to zero. +Several of the random :class:`.Qobj` function in QuTiP support additional parameters as well, namely *density* and *distribution*. +:func:`.rand_dm`, :func:`.rand_herm`, :func:`.rand_unitary` and :func:`.rand_ket` can be created using multiple method controlled by *distribution*. +The :func:`.rand_ket`, :func:`.rand_herm` and :func:`.rand_unitary` functions can return quantum objects such that a fraction of the elements are identically equal to zero. The ratio of nonzero elements is passed as the ``density`` keyword argument. By contrast, `rand_super_bcsz` take as an argument the rank of the generated object, such that passing ``rank=1`` returns a random pure state or unitary channel, respectively. Passing ``rank=None`` specifies that the generated object should be full-rank for the given dimension. @@ -115,7 +115,7 @@ See the API documentation: :ref:`functions-rand` for details. Random objects with a given eigen spectrum ========================================== -It is also possible to generate random Hamiltonian (``rand_herm``) and densitiy matrices (``rand_dm``) with a given eigen spectrum. +It is also possible to generate random Hamiltonian (:func:`.rand_herm`) and densitiy matrices (:func:`.rand_dm`) with a given eigen spectrum. This is done by passing an array to eigenvalues argument to either function and choosing the "eigen" distribution. For example, @@ -153,9 +153,9 @@ This technique requires many steps to build the desired quantum object, and is t Composite random objects ======================== -In many cases, one is interested in generating random quantum objects that correspond to composite systems generated using the :func:`qutip.tensor.tensor` function. +In many cases, one is interested in generating random quantum objects that correspond to composite systems generated using the :func:`.tensor` function. Specifying the tensor structure of a quantum object is done passing a list for the first argument. -The resulting quantum objects size will be the product of the elements in the list and the resulting :class:`qutip.Qobj` dimensions will be ``[dims, dims]``: +The resulting quantum objects size will be the product of the elements in the list and the resulting :class:`.Qobj` dimensions will be ``[dims, dims]``: .. doctest:: [random] :hide: diff --git a/doc/guide/guide-saving.rst b/doc/guide/guide-saving.rst index ca78c98558..d419dfd24d 100644 --- a/doc/guide/guide-saving.rst +++ b/doc/guide/guide-saving.rst @@ -10,7 +10,7 @@ With time-consuming calculations it is often necessary to store the results to f Storing and loading QuTiP objects ================================= -To store and load arbitrary QuTiP related objects (:class:`qutip.Qobj`, :class:`qutip.solve.solver.Result`, etc.) there are two functions: :func:`qutip.fileio.qsave` and :func:`qutip.fileio.qload`. The function :func:`qutip.fileio.qsave` takes an arbitrary object as first parameter and an optional filename as second parameter (default filename is `qutip_data.qu`). The filename extension is always `.qu`. The function :func:`qutip.fileio.qload` takes a mandatory filename as first argument and loads and returns the objects in the file. +To store and load arbitrary QuTiP related objects (:class:`.Qobj`, :class:`.Result`, etc.) there are two functions: :func:`qutip.fileio.qsave` and :func:`qutip.fileio.qload`. The function :func:`qutip.fileio.qsave` takes an arbitrary object as first parameter and an optional filename as second parameter (default filename is `qutip_data.qu`). The filename extension is always `.qu`. The function :func:`qutip.fileio.qload` takes a mandatory filename as first argument and loads and returns the objects in the file. To illustrate how these functions can be used, consider a simple calculation of the steadystate of the harmonic oscillator :: @@ -18,7 +18,7 @@ To illustrate how these functions can be used, consider a simple calculation of >>> c_ops = [np.sqrt(0.5) * a, np.sqrt(0.25) * a.dag()] >>> rho_ss = steadystate(H, c_ops) -The steadystate density matrix `rho_ss` is an instance of :class:`qutip.Qobj`. It can be stored to a file `steadystate.qu` using :: +The steadystate density matrix `rho_ss` is an instance of :class:`.Qobj`. It can be stored to a file `steadystate.qu` using :: >>> qsave(rho_ss, 'steadystate') >>> !ls *.qu @@ -32,7 +32,8 @@ and it can later be loaded again, and used in further calculations :: >>> a = destroy(10) >>> np.testing.assert_almost_equal(expect(a.dag() * a, rho_ss_loaded), 0.9902248289345061) -The nice thing about the :func:`qutip.fileio.qsave` and :func:`qutip.fileio.qload` functions is that almost any object can be stored and load again later on. We can for example store a list of density matrices as returned by :func:`qutip.mesolve` :: +The nice thing about the :func:`qutip.fileio.qsave` and :func:`qutip.fileio.qload` functions is that almost any object can be stored and load again later on. +We can for example store a list of density matrices as returned by :func:`.mesolve` :: >>> a = destroy(10); H = a.dag() * a ; c_ops = [np.sqrt(0.5) * a, np.sqrt(0.25) * a.dag()] >>> psi0 = rand_ket(10) @@ -65,7 +66,7 @@ The :func:`qutip.fileio.file_data_store` takes two mandatory and three optional where `filename` is the name of the file, `data` is the data to be written to the file (must be a *numpy* array), `numtype` (optional) is a flag indicating numerical type that can take values `complex` or `real`, `numformat` (optional) specifies the numerical format that can take the values `exp` for the format `1.0e1` and `decimal` for the format `10.0`, and `sep` (optional) is an arbitrary single-character field separator (usually a tab, space, comma, semicolon, etc.). -A common use for the :func:`qutip.fileio.file_data_store` function is to store the expectation values of a set of operators for a sequence of times, e.g., as returned by the :func:`qutip.mesolve` function, which is what the following example does +A common use for the :func:`qutip.fileio.file_data_store` function is to store the expectation values of a set of operators for a sequence of times, e.g., as returned by the :func:`.mesolve` function, which is what the following example does .. plot:: :context: diff --git a/doc/guide/guide-steady.rst b/doc/guide/guide-steady.rst index 08e7846e84..41fb1af809 100644 --- a/doc/guide/guide-steady.rst +++ b/doc/guide/guide-steady.rst @@ -19,7 +19,7 @@ Although the requirement for time-independence seems quite resitrictive, one can Steady State solvers in QuTiP ============================= -In QuTiP, the steady-state solution for a system Hamiltonian or Liouvillian is given by :func:`qutip.steadystate.steadystate`. This function implements a number of different methods for finding the steady state, each with their own pros and cons, where the method used can be chosen using the ``method`` keyword argument. +In QuTiP, the steady-state solution for a system Hamiltonian or Liouvillian is given by :func:`.steadystate`. This function implements a number of different methods for finding the steady state, each with their own pros and cons, where the method used can be chosen using the ``method`` keyword argument. .. cssclass:: table-striped @@ -44,7 +44,7 @@ In QuTiP, the steady-state solution for a system Hamiltonian or Liouvillian is g - Steady-state solution via the **dense** SVD of the Liouvillian. -The function :func:`qutip.steadystate` can take either a Hamiltonian and a list +The function :func:`.steadystate` can take either a Hamiltonian and a list of collapse operators as input, generating internally the corresponding Liouvillian super operator in Lindblad form, or alternatively, a Liouvillian passed by the user. @@ -89,7 +89,7 @@ Kernel library that comes with the Anacoda (2.5+) and Intel Python distributions. This gives a substantial increase in performance compared with the standard SuperLU method used by SciPy. To verify that QuTiP can find the necessary libraries, one can check for ``INTEL MKL Ext: True`` in the QuTiP -about box (:func:`qutip.about`). +about box (:func:`.about`). .. _steady-usage: @@ -98,7 +98,7 @@ Using the Steadystate Solver ============================= Solving for the steady state solution to the Lindblad master equation for a -general system with :func:`qutip.steadystate` can be accomplished +general system with :func:`.steadystate` can be accomplished using:: >>> rho_ss = steadystate(H, c_ops) @@ -122,7 +122,7 @@ method, and ``solver="spsolve"`` indicate to use the sparse solver. Sparse solvers may still use quite a large amount of memory when they factorize the matrix since the Liouvillian usually has a large bandwidth. -To address this, :func:`qutip.steadystate` allows one to use the bandwidth minimization algorithms +To address this, :func:`.steadystate` allows one to use the bandwidth minimization algorithms listed in :ref:`steady-args`. For example: .. code-block:: python @@ -211,7 +211,7 @@ The following additional solver arguments are available for the steady-state sol See the corresponding documentation from scipy for a full list. -Further information can be found in the :func:`qutip.steadystate` docstrings. +Further information can be found in the :func:`.steadystate` docstrings. .. _steady-example: diff --git a/doc/guide/guide-super.rst b/doc/guide/guide-super.rst index 76ec70cb19..887e60620a 100644 --- a/doc/guide/guide-super.rst +++ b/doc/guide/guide-super.rst @@ -6,7 +6,7 @@ Superoperators, Pauli Basis and Channel Contraction written by `Christopher Granade `, Institute for Quantum Computing -In this guide, we will demonstrate the :func:`tensor_contract` function, which contracts one or more pairs of indices of a Qobj. This functionality can be used to find rectangular superoperators that implement the partial trace channel :math:S(\rho) = \Tr_2(\rho)`, for instance. Using this functionality, we can quickly turn a system-environment representation of an open quantum process into a superoperator representation. +In this guide, we will demonstrate the :func:`.tensor_contract` function, which contracts one or more pairs of indices of a Qobj. This functionality can be used to find rectangular superoperators that implement the partial trace channel :math:S(\rho) = \Tr_2(\rho)`, for instance. Using this functionality, we can quickly turn a system-environment representation of an open quantum process into a superoperator representation. .. _super-representation-plotting: @@ -17,7 +17,7 @@ Superoperator Representations and Plotting We start off by first demonstrating plotting of superoperators, as this will be useful to us in visualizing the results of a contracted channel. -In particular, we will use Hinton diagrams as implemented by :func:`qutip.visualization.hinton`, which +In particular, we will use Hinton diagrams as implemented by :func:`~qutip.visualization.hinton`, which show the real parts of matrix elements as squares whose size and color both correspond to the magnitude of each element. To illustrate, we first plot a few density operators. .. plot:: @@ -35,7 +35,7 @@ We show superoperators as matrices in the *Pauli basis*, such that any Hermicity As an example, conjugation by :math:`\sigma_z` leaves :math:`\mathbb{1}` and :math:`\sigma_z` invariant, but flips the sign of :math:`\sigma_x` and :math:`\sigma_y`. This is indicated in Hinton diagrams by a negative-valued square for the sign change and a positive-valued square for a +1 sign. .. plot:: - :context: + :context: close-figs hinton(to_super(sigmaz())) @@ -43,7 +43,7 @@ As an example, conjugation by :math:`\sigma_z` leaves :math:`\mathbb{1}` and :ma As a couple more examples, we also consider the supermatrix for a Hadamard transform and for :math:`\sigma_z \otimes H`. .. plot:: - :context: + :context: close-figs hinton(to_super(hadamard_transform())) hinton(to_super(tensor(sigmaz(), hadamard_transform()))) @@ -66,7 +66,8 @@ We can think of the :math:`\scriptstyle \rm CNOT` here as a system-environment r :width: 2.5in -The two tensor wires on the left indicate where we must take a tensor contraction to obtain the measurement map. Numbering the tensor wires from 0 to 3, this corresponds to a :func:`tensor_contract` argument of ``(1, 3)``. +The two tensor wires on the left indicate where we must take a tensor contraction to obtain the measurement map. +Numbering the tensor wires from 0 to 3, this corresponds to a :func:`.tensor_contract` argument of ``(1, 3)``. .. plot:: :context: @@ -74,7 +75,7 @@ The two tensor wires on the left indicate where we must take a tensor contractio tensor_contract(to_super(identity([2, 2])), (1, 3)) -Meanwhile, the :func:`super_tensor` function implements the swap on the right, such that we can quickly find the preparation map. +Meanwhile, the :func:`.super_tensor` function implements the swap on the right, such that we can quickly find the preparation map. .. plot:: :context: @@ -86,14 +87,14 @@ Meanwhile, the :func:`super_tensor` function implements the swap on the right, s For a :math:`\scriptstyle \rm CNOT` system-environment model, the composition of these maps should give us a completely dephasing channel. The channel on both qubits is just the superunitary :math:`\scriptstyle \rm CNOT` channel: .. plot:: - :context: + :context: close-figs hinton(to_super(cnot())) We now complete by multiplying the superunitary :math:`\scriptstyle \rm CNOT` by the preparation channel above, then applying the partial trace channel by contracting the second and fourth index indices. As expected, this gives us a dephasing map. .. plot:: - :context: + :context: close-figs hinton(tensor_contract(to_super(cnot()), (1, 3)) * s_prep) diff --git a/doc/guide/guide-tensor.rst b/doc/guide/guide-tensor.rst index b870ba2672..dab91bcfa9 100644 --- a/doc/guide/guide-tensor.rst +++ b/doc/guide/guide-tensor.rst @@ -12,7 +12,7 @@ Tensor products To describe the states of multipartite quantum systems - such as two coupled qubits, a qubit coupled to an oscillator, etc. - we need to expand the Hilbert space by taking the tensor product of the state vectors for each of the system components. Similarly, the operators acting on the state vectors in the combined Hilbert space (describing the coupled system) are formed by taking the tensor product of the individual operators. -In QuTiP the function :func:`qutip.core.tensor.tensor` is used to accomplish this task. This function takes as argument a collection:: +In QuTiP the function :func:`~qutip.core.tensor.tensor` is used to accomplish this task. This function takes as argument a collection:: >>> tensor(op1, op2, op3) # doctest: +SKIP @@ -58,7 +58,7 @@ or equivalently using the ``list`` format: [0.] [0.]] -This is straightforward to generalize to more qubits by adding more component state vectors in the argument list to the :func:`qutip.core.tensor.tensor` function, as illustrated in the following example: +This is straightforward to generalize to more qubits by adding more component state vectors in the argument list to the :func:`~qutip.core.tensor.tensor` function, as illustrated in the following example: .. testcode:: [tensor] @@ -83,7 +83,7 @@ This is straightforward to generalize to more qubits by adding more component st This state is slightly more complicated, describing two qubits in a superposition between the up and down states, while the third qubit is in its ground state. -To construct operators that act on an extended Hilbert space of a combined system, we similarly pass a list of operators for each component system to the :func:`qutip.core.tensor.tensor` function. For example, to form the operator that represents the simultaneous action of the :math:`\sigma_x` operator on two qubits: +To construct operators that act on an extended Hilbert space of a combined system, we similarly pass a list of operators for each component system to the :func:`~qutip.core.tensor.tensor` function. For example, to form the operator that represents the simultaneous action of the :math:`\sigma_x` operator on two qubits: .. testcode:: [tensor] @@ -125,7 +125,7 @@ To create operators in a combined Hilbert space that only act on a single compon Example: Constructing composite Hamiltonians ============================================ -The :func:`qutip.core.tensor.tensor` function is extensively used when constructing Hamiltonians for composite systems. Here we'll look at some simple examples. +The :func:`~qutip.core.tensor.tensor` function is extensively used when constructing Hamiltonians for composite systems. Here we'll look at some simple examples. .. _tensor-product-example-2qubits: @@ -189,15 +189,16 @@ A two-level system coupled to a cavity: The Jaynes-Cummings model The simplest possible quantum mechanical description for light-matter interaction is encapsulated in the Jaynes-Cummings model, which describes the coupling between a two-level atom and a single-mode electromagnetic field (a cavity mode). Denoting the energy splitting of the atom and cavity ``omega_a`` and ``omega_c``, respectively, and the atom-cavity interaction strength ``g``, the Jaynes-Cummings Hamiltonian can be constructed as: -.. testcode:: [tensor] +.. plot:: + :context: reset - N = 10 + N = 6 omega_a = 1.0 omega_c = 1.25 - g = 0.05 + g = 0.75 a = tensor(identity(2), destroy(N)) @@ -207,95 +208,7 @@ The simplest possible quantum mechanical description for light-matter interactio H = 0.5 * omega_a * sz + omega_c * a.dag() * a + g * (a.dag() * sm + a * sm.dag()) - print(H) - -**Output**: - -.. testoutput:: [tensor] - :options: +NORMALIZE_WHITESPACE - - Quantum object: dims = [[2, 10], [2, 10]], shape = (20, 20), type = oper, isherm = True - Qobj data = - [[ 0.5 0. 0. 0. 0. 0. - 0. 0. 0. 0. 0. 0. - 0. 0. 0. 0. 0. 0. - 0. 0. ] - [ 0. 1.75 0. 0. 0. 0. - 0. 0. 0. 0. 0.05 0. - 0. 0. 0. 0. 0. 0. - 0. 0. ] - [ 0. 0. 3. 0. 0. 0. - 0. 0. 0. 0. 0. 0.07071068 - 0. 0. 0. 0. 0. 0. - 0. 0. ] - [ 0. 0. 0. 4.25 0. 0. - 0. 0. 0. 0. 0. 0. - 0.08660254 0. 0. 0. 0. 0. - 0. 0. ] - [ 0. 0. 0. 0. 5.5 0. - 0. 0. 0. 0. 0. 0. - 0. 0.1 0. 0. 0. 0. - 0. 0. ] - [ 0. 0. 0. 0. 0. 6.75 - 0. 0. 0. 0. 0. 0. - 0. 0. 0.1118034 0. 0. 0. - 0. 0. ] - [ 0. 0. 0. 0. 0. 0. - 8. 0. 0. 0. 0. 0. - 0. 0. 0. 0.12247449 0. 0. - 0. 0. ] - [ 0. 0. 0. 0. 0. 0. - 0. 9.25 0. 0. 0. 0. - 0. 0. 0. 0. 0.13228757 0. - 0. 0. ] - [ 0. 0. 0. 0. 0. 0. - 0. 0. 10.5 0. 0. 0. - 0. 0. 0. 0. 0. 0.14142136 - 0. 0. ] - [ 0. 0. 0. 0. 0. 0. - 0. 0. 0. 11.75 0. 0. - 0. 0. 0. 0. 0. 0. - 0.15 0. ] - [ 0. 0.05 0. 0. 0. 0. - 0. 0. 0. 0. -0.5 0. - 0. 0. 0. 0. 0. 0. - 0. 0. ] - [ 0. 0. 0.07071068 0. 0. 0. - 0. 0. 0. 0. 0. 0.75 - 0. 0. 0. 0. 0. 0. - 0. 0. ] - [ 0. 0. 0. 0.08660254 0. 0. - 0. 0. 0. 0. 0. 0. - 2. 0. 0. 0. 0. 0. - 0. 0. ] - [ 0. 0. 0. 0. 0.1 0. - 0. 0. 0. 0. 0. 0. - 0. 3.25 0. 0. 0. 0. - 0. 0. ] - [ 0. 0. 0. 0. 0. 0.1118034 - 0. 0. 0. 0. 0. 0. - 0. 0. 4.5 0. 0. 0. - 0. 0. ] - [ 0. 0. 0. 0. 0. 0. - 0.12247449 0. 0. 0. 0. 0. - 0. 0. 0. 5.75 0. 0. - 0. 0. ] - [ 0. 0. 0. 0. 0. 0. - 0. 0.13228757 0. 0. 0. 0. - 0. 0. 0. 0. 7. 0. - 0. 0. ] - [ 0. 0. 0. 0. 0. 0. - 0. 0. 0.14142136 0. 0. 0. - 0. 0. 0. 0. 0. 8.25 - 0. 0. ] - [ 0. 0. 0. 0. 0. 0. - 0. 0. 0. 0.15 0. 0. - 0. 0. 0. 0. 0. 0. - 9.5 0. ] - [ 0. 0. 0. 0. 0. 0. - 0. 0. 0. 0. 0. 0. - 0. 0. 0. 0. 0. 0. - 0. 10.75 ]] + hinton(H, fig=plt.figure(figsize=(12, 12))) Here ``N`` is the number of Fock states included in the cavity mode. @@ -305,7 +218,12 @@ Here ``N`` is the number of Fock states included in the cavity mode. Partial trace ============= -The partial trace is an operation that reduces the dimension of a Hilbert space by eliminating some degrees of freedom by averaging (tracing). In this sense it is therefore the converse of the tensor product. It is useful when one is interested in only a part of a coupled quantum system. For open quantum systems, this typically involves tracing over the environment leaving only the system of interest. In QuTiP the class method :func:`qutip.Qobj.ptrace` is used to take partial traces. :func:`qutip.Qobj.ptrace` acts on the :class:`qutip.Qobj` instance for which it is called, and it takes one argument ``sel``, which is a ``list`` of integers that mark the component systems that should be **kept**. All other components are traced out. +The partial trace is an operation that reduces the dimension of a Hilbert space by eliminating some degrees of freedom by averaging (tracing). +In this sense it is therefore the converse of the tensor product. +It is useful when one is interested in only a part of a coupled quantum system. +For open quantum systems, this typically involves tracing over the environment leaving only the system of interest. +In QuTiP the class method :meth:`~qutip.core.qobj.Qobj.ptrace` is used to take partial traces. :meth:`~qutip.core.qobj.Qobj.ptrace` acts on the :class:`~qutip.core.qobj.Qobj` instance for which it is called, and it takes one argument ``sel``, which is a ``list`` of integers that mark the component systems that should be **kept**. +All other components are traced out. For example, the density matrix describing a single qubit obtained from a coupled two-qubit system is obtained via: @@ -374,8 +292,8 @@ using the isomorphism To represent superoperators acting on :math:`\mathcal{L}(\mathcal{H}_1 \otimes \mathcal{H}_2)` thus takes some tensor rearrangement to get the desired ordering :math:`\mathcal{H}_1 \otimes \mathcal{H}_2 \otimes \mathcal{H}_1 \otimes \mathcal{H}_2`. -In particular, this means that :func:`qutip.tensor` does not act as -one might expect on the results of :func:`qutip.superop_reps.to_super`: +In particular, this means that :func:`.tensor` does not act as +one might expect on the results of :func:`.to_super`: .. doctest:: [tensor] @@ -394,8 +312,8 @@ of the compound index with dims ``[2, 3]``. In the latter case, however, each of the Hilbert space indices is listed independently and in the wrong order. -The :func:`qutip.tensor.super_tensor` function performs the needed -rearrangement, providing the most direct analog to :func:`qutip.tensor` on +The :func:`.super_tensor` function performs the needed +rearrangement, providing the most direct analog to :func:`.tensor` on the underlying Hilbert space. In particular, for any two ``type="oper"`` Qobjs ``A`` and ``B``, ``to_super(tensor(A, B)) == super_tensor(to_super(A), to_super(B))`` and ``operator_to_vector(tensor(A, B)) == super_tensor(operator_to_vector(A), operator_to_vector(B))``. Returning to the previous example: @@ -405,8 +323,8 @@ Qobjs ``A`` and ``B``, ``to_super(tensor(A, B)) == super_tensor(to_super(A), to_ >>> super_tensor(to_super(A), to_super(B)).dims [[[2, 3], [2, 3]], [[2, 3], [2, 3]]] -The :func:`qutip.tensor.composite` function automatically switches between -:func:`qutip.tensor` and :func:`qutip.tensor.super_tensor` based on the ``type`` +The :func:`.composite` function automatically switches between +:func:`.tensor` and :func:`.super_tensor` based on the ``type`` of its arguments, such that ``composite(A, B)`` returns an appropriate Qobj to represent the composition of two systems. diff --git a/doc/guide/guide-visualization.rst b/doc/guide/guide-visualization.rst index 2b91b30ebb..c80ffc6973 100644 --- a/doc/guide/guide-visualization.rst +++ b/doc/guide/guide-visualization.rst @@ -282,7 +282,7 @@ structure and relative importance of various elements. QuTiP offers a few functions for quickly visualizing matrix data in the form of histograms, :func:`qutip.visualization.matrix_histogram` and as Hinton diagram of weighted squares, :func:`qutip.visualization.hinton`. -These functions takes a :class:`qutip.Qobj` as first argument, and optional arguments to, +These functions takes a :class:`.Qobj` as first argument, and optional arguments to, for example, set the axis labels and figure title (see the function's documentation for details). @@ -390,7 +390,8 @@ Note that to obtain :math:`\chi` with this method we have to construct a matrix Implementation in QuTiP ----------------------- -In QuTiP, the procedure described above is implemented in the function :func:`qutip.tomography.qpt`, which returns the :math:`\chi` matrix given a density matrix propagator. To illustrate how to use this function, let's consider the SWAP gate for two qubits. In QuTiP the function :func:`qutip.core.operators.swap` generates the unitary transformation for the state kets: +In QuTiP, the procedure described above is implemented in the function :func:`qutip.tomography.qpt`, which returns the :math:`\chi` matrix given a density matrix propagator. +To illustrate how to use this function, let's consider the SWAP gate for two qubits. In QuTiP the function :func:`.swap` generates the unitary transformation for the state kets: .. plot:: @@ -430,4 +431,4 @@ We are now ready to compute :math:`\chi` using :func:`qutip.tomography.qpt`, and -For a slightly more advanced example, where the density matrix propagator is calculated from the dynamics of a system defined by its Hamiltonian and collapse operators using the function :func:`qutip.propagator.propagator`, see notebook "Time-dependent master equation: Landau-Zener transitions" on the tutorials section on the QuTiP web site. +For a slightly more advanced example, where the density matrix propagator is calculated from the dynamics of a system defined by its Hamiltonian and collapse operators using the function :func:`.propagator`, see notebook "Time-dependent master equation: Landau-Zener transitions" on the tutorials section on the QuTiP web site. diff --git a/doc/guide/guide.rst b/doc/guide/guide.rst index 820a82fa07..e450ca737e 100644 --- a/doc/guide/guide.rst +++ b/doc/guide/guide.rst @@ -17,11 +17,10 @@ Users Guide guide-steady.rst guide-piqs.rst guide-correlation.rst - guide-control.rst guide-bloch.rst guide-visualization.rst - guide-parfor.rst guide-saving.rst guide-random.rst guide-settings.rst guide-measurement.rst + guide-control.rst diff --git a/doc/guide/heom/intro.rst b/doc/guide/heom/intro.rst index 5ae195250c..fd3c2ea624 100644 --- a/doc/guide/heom/intro.rst +++ b/doc/guide/heom/intro.rst @@ -40,4 +40,4 @@ In addition to support for bosonic environments, QuTiP also provides support for feriomic environments which is described in :doc:`fermionic`. Both bosonic and fermionic environments are supported via a single solver, -:class:`~qutip.nonmarkov.heom.HEOMSolver`, that supports solving for both dynamics and steady-states. +:class:`.HEOMSolver`, that supports solving for both dynamics and steady-states. From d4aeb62d8ad23fd5a90c0827c819d4d4108d7062 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Fri, 22 Dec 2023 11:05:19 +0900 Subject: [PATCH 137/247] loky_pmap: remove broken `job_timeout` option --- qutip/solver/parallel.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index afb8dd3685..78fa6274cd 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -322,7 +322,6 @@ def loky_pmap(task, values, task_args=None, task_kwargs=None, map_kw: dict, optional Dictionary containing entry for: - timeout: float, Maximum time (sec) for the whole map. - - job_timeout: float, Maximum time (sec) for each job in the map. - num_cpus: int, Number of jobs to run at once. - fail_fast: bool, Abort at the first error. @@ -341,7 +340,6 @@ def loky_pmap(task, values, task_args=None, task_kwargs=None, task_kwargs = {} map_kw = _read_map_kw(map_kw) end_time = map_kw['timeout'] + time.time() - job_time = map_kw['job_timeout'] progress_bar = progress_bars[progress_bar]( len(values), **progress_bar_kwargs @@ -363,7 +361,7 @@ def loky_pmap(task, values, task_args=None, task_kwargs=None, for value in values] for n, job in enumerate(jobs): - remaining_time = min(end_time - time.time(), job_time) + remaining_time = max(end_time - time.time(), 0) try: result = job.result(remaining_time) except Exception as err: From 9f90ee2d463fa29172faa3826e09578734d8b30b Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Fri, 22 Dec 2023 11:15:03 +0900 Subject: [PATCH 138/247] loky_pmap: match behavior of parallel_map in case of time-out --- qutip/solver/parallel.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index 78fa6274cd..c713b1cd1e 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -364,6 +364,9 @@ def loky_pmap(task, values, task_args=None, task_kwargs=None, remaining_time = max(end_time - time.time(), 0) try: result = job.result(remaining_time) + except TimeoutError: + [job.cancel() for job in jobs] + break except Exception as err: if map_kw["fail_fast"]: raise err @@ -382,12 +385,10 @@ def loky_pmap(task, values, task_args=None, task_kwargs=None, [job.cancel() for job in jobs] raise e - except TimeoutError: - [job.cancel() for job in jobs] - finally: os.environ['QUTIP_IN_PARALLEL'] = 'FALSE' - executor.shutdown() + executor.shutdown(kill_workers=True) + progress_bar.finished() if errors: raise MapExceptions( From cf9070bc18d1c0c9b4479f92c6aff25f9bd28fae Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Fri, 22 Dec 2023 11:31:46 +0900 Subject: [PATCH 139/247] loky_pmap: corrected docstring --- qutip/solver/parallel.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index c713b1cd1e..52d585101a 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -311,8 +311,8 @@ def loky_pmap(task, values, task_args=None, task_kwargs=None, The optional additional keyword arguments to the ``task`` function. reduce_func : func, optional If provided, it will be called with the output of each task instead of - storing them in a list. Note that the order in which results are - passed to ``reduce_func`` is not defined. It should return None or a + storing them in a list. Note that the results are passed to + ``reduce_func`` in the original order. It should return None or a number. When returning a number, it represents the estimation of the number of tasks left. On a return <= 0, the map will end early. progress_bar : str, optional From bbd09995a55e66f09d92ddcf7897d7004c223491 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Sat, 23 Dec 2023 16:26:20 +0900 Subject: [PATCH 140/247] Removed all references to `job_timeout` (which was not working) --- qutip/solver/mcsolve.py | 6 ------ qutip/solver/multitraj.py | 2 -- qutip/solver/nm_mcsolve.py | 5 ----- qutip/solver/parallel.py | 2 -- qutip/solver/solver_base.py | 2 +- qutip/solver/stochastic.py | 10 ---------- qutip/tests/solver/test_parallel.py | 2 -- 7 files changed, 1 insertion(+), 28 deletions(-) diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index 827e7b4add..a55b1a094c 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -87,8 +87,6 @@ def mcsolve(H, state, tlist, c_ops=(), e_ops=None, ntraj=500, *, | How to run the trajectories. "parallel" uses concurent module to run in parallel while "loky" use the module of the same name to do so. - - | job_timeout : int - | Maximum time to compute one trajectory. - | num_cpus : int | Number of cpus to use when running in parallel. ``None`` detect the number of available cpus. @@ -416,7 +414,6 @@ class MCSolver(MultiTrajSolver): "keep_runs_results": False, "method": "adams", "map": "serial", - "job_timeout": None, "num_cpus": None, "bitgenerator": None, "mc_corr_eps": 1e-10, @@ -603,9 +600,6 @@ def options(self): run in parallel while "loky" use the module of the same name to do so. - job_timeout: None, int - Maximum time to compute one trajectory. - num_cpus: None, int Number of cpus to use when running in parallel. ``None`` detect the number of available cpus. diff --git a/qutip/solver/multitraj.py b/qutip/solver/multitraj.py index dc213203a5..60f52a7202 100644 --- a/qutip/solver/multitraj.py +++ b/qutip/solver/multitraj.py @@ -64,7 +64,6 @@ class MultiTrajSolver(Solver): "normalize_output": False, "method": "", "map": "serial", - "job_timeout": None, "num_cpus": None, "bitgenerator": None, } @@ -146,7 +145,6 @@ def _initialize_run(self, state, ntraj=1, args=None, e_ops=(), map_func = _get_map[self.options['map']] map_kw = { 'timeout': timeout, - 'job_timeout': self.options['job_timeout'], 'num_cpus': self.options['num_cpus'], } state0 = self._prepare_state(state) diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index bcf8c375a4..690a86c1be 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -108,8 +108,6 @@ def nm_mcsolve(H, state, tlist, ops_and_rates=(), e_ops=None, ntraj=500, *, | How to run the trajectories. "parallel" uses concurent module to run in parallel while "loky" use the module of the same name to do so. - - | job_timeout : int - | Maximum time to compute one trajectory. - | num_cpus : int | Number of cpus to use when running in parallel. ``None`` detect the number of available cpus. @@ -559,9 +557,6 @@ def options(self): run in parallel while "loky" use the module of the same name to do so. - job_timeout: None, int - Maximum time to compute one trajectory. - num_cpus: None, int Number of cpus to use when running in parallel. ``None`` detect the number of available cpus. diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index 52d585101a..4e51c2c8a3 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -25,7 +25,6 @@ default_map_kw = { - 'job_timeout': threading.TIMEOUT_MAX, 'timeout': threading.TIMEOUT_MAX, 'num_cpus': available_cpu_count(), 'fail_fast': True, @@ -165,7 +164,6 @@ def parallel_map(task, values, task_args=None, task_kwargs=None, map_kw: dict, optional Dictionary containing entry for: - timeout: float, Maximum time (sec) for the whole map. - - job_timeout: float, Maximum time (sec) for each job in the map. - num_cpus: int, Number of jobs to run at once. - fail_fast: bool, Abort at the first error. diff --git a/qutip/solver/solver_base.py b/qutip/solver/solver_base.py index b2daff550d..28a4c8ba60 100644 --- a/qutip/solver/solver_base.py +++ b/qutip/solver/solver_base.py @@ -472,7 +472,7 @@ def _solver_deprecation(kwargs, options, solver="me"): if "map_kwargs" in kwargs and solver in ["mc", "stoc"]: warnings.warn( '"map_kwargs" are now included in options:\n' - 'Use `options={"num_cpus": N, "job_timeout": Nsec}`', + 'Use `options={"num_cpus": N}`', FutureWarning ) del kwargs["map_kwargs"] diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index d4e3c3267a..f548b2a907 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -337,8 +337,6 @@ def smesolve( | How to run the trajectories. "parallel" uses concurent module to run in parallel while "loky" use the module of the same name to do so. - - | job_timeout : NoneType, int - | Maximum time to compute one trajectory. - | num_cpus : NoneType, int | Number of cpus to use when running in parallel. ``None`` detect the number of available cpus. @@ -459,8 +457,6 @@ def ssesolve( How to run the trajectories. "parallel" uses concurent module to run in parallel while "loky" use the module of the same name to do so. - - | job_timeout : NoneType, int - | Maximum time to compute one trajectory. - | num_cpus : NoneType, int | Number of cpus to use when running in parallel. ``None`` detect the number of available cpus. @@ -507,7 +503,6 @@ class StochasticSolver(MultiTrajSolver): "normalize_output": False, "method": "taylor1.5", "map": "serial", - "job_timeout": None, "num_cpus": None, "bitgenerator": None, } @@ -684,9 +679,6 @@ def options(self): run in parallel while "loky" use the module of the same name to do so. - job_timeout: None, int, default: None - Maximum time to compute one trajectory. - num_cpus: None, int, default: None Number of cpus to use when running in parallel. ``None`` detect the number of available cpus. @@ -796,7 +788,6 @@ class SMESolver(StochasticSolver): "normalize_output": False, "method": "platen", "map": "serial", - "job_timeout": None, "num_cpus": None, "bitgenerator": None, } @@ -840,7 +831,6 @@ class SSESolver(StochasticSolver): "normalize_output": False, "method": "platen", "map": "serial", - "job_timeout": None, "num_cpus": None, "bitgenerator": None, } diff --git a/qutip/tests/solver/test_parallel.py b/qutip/tests/solver/test_parallel.py index bb53496edb..bc3af0d62f 100644 --- a/qutip/tests/solver/test_parallel.py +++ b/qutip/tests/solver/test_parallel.py @@ -34,7 +34,6 @@ def test_map(map, num_cpus): args = (1, 2, 3) kwargs = {'d': 4, 'e': 5, 'f': 6} map_kw = { - 'job_timeout': threading.TIMEOUT_MAX, 'timeout': threading.TIMEOUT_MAX, 'num_cpus': num_cpus, } @@ -60,7 +59,6 @@ def test_map_accumulator(map, num_cpus): args = (1, 2, 3) kwargs = {'d': 4, 'e': 5, 'f': 6} map_kw = { - 'job_timeout': threading.TIMEOUT_MAX, 'timeout': threading.TIMEOUT_MAX, 'num_cpus': num_cpus, } From 6fac55660563ec88c19e8e7308a8e7e2420aaaa6 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Sat, 23 Dec 2023 16:28:22 +0900 Subject: [PATCH 141/247] Towncrier entry --- doc/changes/2280.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/2280.bugfix diff --git a/doc/changes/2280.bugfix b/doc/changes/2280.bugfix new file mode 100644 index 0000000000..9d54bab013 --- /dev/null +++ b/doc/changes/2280.bugfix @@ -0,0 +1 @@ +Improved behavior of `parallel_map` and `loky_pmap` in the case of timeouts, errors or keyboard interrupts \ No newline at end of file From 09967bbde50061e250b7d6905b71d64af2f1187b Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Tue, 26 Dec 2023 16:08:46 +0900 Subject: [PATCH 142/247] Unified parallel_map and loky_pmap --- qutip/solver/parallel.py | 265 +++++++++++++++++++++------------------ 1 file changed, 143 insertions(+), 122 deletions(-) diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index 4e51c2c8a3..4038650ad8 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -131,57 +131,34 @@ def serial_map(task, values, task_args=None, task_kwargs=None, return results -def parallel_map(task, values, task_args=None, task_kwargs=None, - reduce_func=None, map_kw=None, - progress_bar=None, progress_bar_kwargs={}): +def _generic_pmap(task, values, task_args, task_kwargs, + reduce_func, timeout, fail_fast, + progress_bar, progress_bar_kwargs, + setup_executor, extract_result, shutdown_executor): """ - Parallel execution of a mapping of ``values`` to the function ``task``. - This is functionally equivalent to:: - - result = [task(value, *task_args, **task_kwargs) for value in values] - - Parameters - ---------- - task : a Python function - The function that is to be called for each value in ``task_vec``. - values : array / list - The list or array of values for which the ``task`` function is to be - evaluated. - task_args : list, optional - The optional additional arguments to the ``task`` function. - task_kwargs : dictionary, optional - The optional additional keyword arguments to the ``task`` function. - reduce_func : func, optional - If provided, it will be called with the output of each task instead of - storing them in a list. Note that the order in which results are - passed to ``reduce_func`` is not defined. It should return None or a - number. When returning a number, it represents the estimation of the - number of tasks left. On a return <= 0, the map will end early. - progress_bar : str, optional - Progress bar options's string for showing progress. - progress_bar_kwargs : dict, optional - Options for the progress bar. - map_kw: dict, optional - Dictionary containing entry for: - - timeout: float, Maximum time (sec) for the whole map. - - num_cpus: int, Number of jobs to run at once. - - fail_fast: bool, Abort at the first error. - - Returns - ------- - result : list - The result list contains the value of - ``task(value, *task_args, **task_kwargs)`` for - each value in ``values``. If a ``reduce_func`` is provided, and empty - list will be returned. - + Common functionality for parallel_map, loky_pmap and mpi_pmap. + The parameters `setup_executor`, `extract_value` and `destroy_executor` + are callback functions with the following signatures: + + setup_executor: () -> ProcessPoolExecutor, int + The second return value specifies the number of workers + + extract_result: (index: int, future: Future) -> Any + index: Corresponds to the indices of the `values` list + future: The Future that has finished running + + shutdown_executor: (executor: ProcessPoolExecutor, + active_tasks: set[Future]) -> None + executor: The ProcessPoolExecutor that was created in setup_executor + active_tasks: A set of Futures that are currently still being executed + (non-empty if: timeout, error, or reduce_func requesting exit) """ + if task_args is None: task_args = () if task_kwargs is None: task_kwargs = {} - map_kw = _read_map_kw(map_kw) - end_time = map_kw['timeout'] + time.time() + end_time = timeout + time.time() progress_bar = progress_bars[progress_bar]( len(values), **progress_bar_kwargs @@ -191,6 +168,7 @@ def parallel_map(task, values, task_args=None, task_kwargs=None, finished = [] if reduce_func is not None: results = None + def result_func(_, value): return reduce_func(value) else: @@ -200,7 +178,7 @@ def result_func(_, value): def _done_callback(future): if not future.cancelled(): try: - result = future.result() + result = extract_result(future._i, future) remaining_ntraj = result_func(future._i, result) if remaining_ntraj is not None and remaining_ntraj <= 0: finished.append(True) @@ -220,25 +198,20 @@ def _done_callback(future): pass progress_bar.update() - if sys.version_info >= (3, 7): - # ProcessPoolExecutor only supports mp_context from 3.7 onwards - ctx_kw = {"mp_context": mp_context} - else: - ctx_kw = {} - os.environ['QUTIP_IN_PARALLEL'] = 'TRUE' try: - with concurrent.futures.ProcessPoolExecutor( - max_workers=map_kw['num_cpus'], **ctx_kw, - ) as executor: + executor, num_workers = setup_executor() + with executor: waiting = set() i = 0 + aborted = False + while i < len(values): # feed values to the executor, ensuring that there is at # most one task per worker at any moment in time so that # we can shutdown without waiting for greater than the time # taken by the longest task - if len(waiting) >= map_kw['num_cpus']: + if len(waiting) >= num_workers: # no space left, wait for a task to complete or # the time to run out timeout = max(0, end_time - time.time()) @@ -249,12 +222,13 @@ def _done_callback(future): ) if ( time.time() >= end_time - or (errors and map_kw['fail_fast']) + or (errors and fail_fast) or finished ): # no time left, exit the loop + aborted = True break - while len(waiting) < map_kw['num_cpus'] and i < len(values): + while len(waiting) < num_workers and i < len(values): # space and time available, add tasks value = values[i] future = executor.submit( @@ -268,13 +242,21 @@ def _done_callback(future): waiting.add(future) i += 1 - timeout = max(0, end_time - time.time()) - concurrent.futures.wait(waiting, timeout=timeout) + if not aborted: + # all tasks have been submitted, timeout has not been reaches + # -> wait for all workers to finish before shutting down + timeout = max(0, end_time - time.time()) + _done, waiting = concurrent.futures.wait( + waiting, + timeout=timeout, + return_when=concurrent.futures.ALL_COMPLETED + ) + shutdown_executor(executor, waiting) finally: os.environ['QUTIP_IN_PARALLEL'] = 'FALSE' progress_bar.finished() - if errors and map_kw["fail_fast"]: + if errors and fail_fast: raise list(errors.values())[0] elif errors: raise MapExceptions( @@ -285,17 +267,15 @@ def _done_callback(future): return results -def loky_pmap(task, values, task_args=None, task_kwargs=None, - reduce_func=None, map_kw=None, - progress_bar=None, progress_bar_kwargs={}): +def parallel_map(task, values, task_args=None, task_kwargs=None, + reduce_func=None, map_kw=None, + progress_bar=None, progress_bar_kwargs={}): """ Parallel execution of a mapping of ``values`` to the function ``task``. This is functionally equivalent to:: result = [task(value, *task_args, **task_kwargs) for value in values] - Use the loky module instead of multiprocessing. - Parameters ---------- task : a Python function @@ -309,8 +289,8 @@ def loky_pmap(task, values, task_args=None, task_kwargs=None, The optional additional keyword arguments to the ``task`` function. reduce_func : func, optional If provided, it will be called with the output of each task instead of - storing them in a list. Note that the results are passed to - ``reduce_func`` in the original order. It should return None or a + storing them in a list. Note that the order in which results are + passed to ``reduce_func`` is not defined. It should return None or a number. When returning a number, it represents the estimation of the number of tasks left. On a return <= 0, the map will end early. progress_bar : str, optional @@ -332,68 +312,109 @@ def loky_pmap(task, values, task_args=None, task_kwargs=None, list will be returned. """ - if task_args is None: - task_args = () - if task_kwargs is None: - task_kwargs = {} + map_kw = _read_map_kw(map_kw) - end_time = map_kw['timeout'] + time.time() + if sys.version_info >= (3, 7): + # ProcessPoolExecutor only supports mp_context from 3.7 onwards + ctx_kw = {"mp_context": mp_context} + else: + ctx_kw = {} - progress_bar = progress_bars[progress_bar]( - len(values), **progress_bar_kwargs - ) + def setup_executor(): + num_workers = map_kw['num_cpus'] + executor = concurrent.futures.ProcessPoolExecutor( + max_workers=num_workers, **ctx_kw, + ) + return executor, num_workers + + def extract_result (_, future): + return future.result() - errors = {} - remaining_ntraj = None - if reduce_func is None: - results = [None] * len(values) - else: - results = None + def shutdown_executor(executor, _): + # Since `ProcessPoolExecutor` leaves no other option, + # we wait for all worker processes to finish their current task + executor.shutdown() - os.environ['QUTIP_IN_PARALLEL'] = 'TRUE' - from loky import get_reusable_executor, TimeoutError - try: - executor = get_reusable_executor(max_workers=map_kw['num_cpus']) + return _generic_pmap(task, values, task_args, task_kwargs, + reduce_func, map_kw['timeout'], map_kw['fail_fast'], + progress_bar, progress_bar_kwargs, + setup_executor, extract_result, shutdown_executor) - jobs = [executor.submit(task, value, *task_args, **task_kwargs) - for value in values] - for n, job in enumerate(jobs): - remaining_time = max(end_time - time.time(), 0) - try: - result = job.result(remaining_time) - except TimeoutError: - [job.cancel() for job in jobs] - break - except Exception as err: - if map_kw["fail_fast"]: - raise err - else: - errors[n] = err - else: - if reduce_func is not None: - remaining_ntraj = reduce_func(result) - else: - results[n] = result - progress_bar.update() - if remaining_ntraj is not None and remaining_ntraj <= 0: - break - - except KeyboardInterrupt as e: - [job.cancel() for job in jobs] - raise e +def loky_pmap(task, values, task_args=None, task_kwargs=None, + reduce_func=None, map_kw=None, + progress_bar=None, progress_bar_kwargs={}): + """ + Parallel execution of a mapping of ``values`` to the function ``task``. + This is functionally equivalent to:: - finally: - os.environ['QUTIP_IN_PARALLEL'] = 'FALSE' - executor.shutdown(kill_workers=True) + result = [task(value, *task_args, **task_kwargs) for value in values] + + Use the loky module instead of multiprocessing. + + Parameters + ---------- + task : a Python function + The function that is to be called for each value in ``task_vec``. + values : array / list + The list or array of values for which the ``task`` function is to be + evaluated. + task_args : list, optional + The optional additional arguments to the ``task`` function. + task_kwargs : dictionary, optional + The optional additional keyword arguments to the ``task`` function. + reduce_func : func, optional + If provided, it will be called with the output of each task instead of + storing them in a list. Note that the order in which results are + passed to ``reduce_func`` is not defined. It should return None or a + number. When returning a number, it represents the estimation of the + number of tasks left. On a return <= 0, the map will end early. + progress_bar : str, optional + Progress bar options's string for showing progress. + progress_bar_kwargs : dict, optional + Options for the progress bar. + map_kw: dict, optional + Dictionary containing entry for: + - timeout: float, Maximum time (sec) for the whole map. + - num_cpus: int, Number of jobs to run at once. + - fail_fast: bool, Abort at the first error. + + Returns + ------- + result : list + The result list contains the value of + ``task(value, *task_args, **task_kwargs)`` for + each value in ``values``. If a ``reduce_func`` is provided, and empty + list will be returned. + + """ + + from loky import get_reusable_executor + from loky.process_executor import ShutdownExecutorError + map_kw = _read_map_kw(map_kw) + + def setup_executor(): + num_workers = map_kw['num_cpus'] + executor = get_reusable_executor(max_workers=num_workers) + return executor, num_workers + + def extract_result (_, future: concurrent.futures.Future): + if isinstance(future.exception(), ShutdownExecutorError): + # Task was aborted due to timeout etc + return None + return future.result() + + def shutdown_executor(executor, active_tasks): + # If there are still tasks running, we kill all workers in order to + # return immediately. Otherwise, `kill_workers` is set to False so + # that the worker threads can be reused in subsequent loky_pmap calls. + executor.shutdown(kill_workers=(len(active_tasks) > 0)) + + return _generic_pmap(task, values, task_args, task_kwargs, + reduce_func, map_kw['timeout'], map_kw['fail_fast'], + progress_bar, progress_bar_kwargs, + setup_executor, extract_result, shutdown_executor) - progress_bar.finished() - if errors: - raise MapExceptions( - f"{len(errors)} iterations failed in loky_pmap", - errors, results - ) - return results _get_map = { From d08c235957c4687b86211e18898932f527c5d3c3 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Tue, 26 Dec 2023 17:10:44 +0900 Subject: [PATCH 143/247] Added mpi_pmap --- qutip/solver/parallel.py | 79 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index 4038650ad8..ca85aae101 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -272,7 +272,7 @@ def parallel_map(task, values, task_args=None, task_kwargs=None, progress_bar=None, progress_bar_kwargs={}): """ Parallel execution of a mapping of ``values`` to the function ``task``. - This is functionally equivalent to:: + This is functionally equivalent to: result = [task(value, *task_args, **task_kwargs) for value in values] @@ -346,7 +346,7 @@ def loky_pmap(task, values, task_args=None, task_kwargs=None, progress_bar=None, progress_bar_kwargs={}): """ Parallel execution of a mapping of ``values`` to the function ``task``. - This is functionally equivalent to:: + This is functionally equivalent to: result = [task(value, *task_args, **task_kwargs) for value in values] @@ -416,6 +416,80 @@ def shutdown_executor(executor, active_tasks): setup_executor, extract_result, shutdown_executor) +def mpi_pmap(task, values, task_args=None, task_kwargs=None, + reduce_func=None, map_kw=None, + progress_bar=None, progress_bar_kwargs={}): + """ + Parallel execution of a mapping of ``values`` to the function ``task``. + This is functionally equivalent to: + + result = [task(value, *task_args, **task_kwargs) for value in values] + + Uses the mpi4py module to execute the tasks asynchronously with MPI + processes. For more information, consult the documentation of mpi4py and + the mpi4py.MPIPoolExecutor class. + + Parameters + ---------- + task : a Python function + The function that is to be called for each value in ``task_vec``. + values : array / list + The list or array of values for which the ``task`` function is to be + evaluated. + task_args : list, optional + The optional additional arguments to the ``task`` function. + task_kwargs : dictionary, optional + The optional additional keyword arguments to the ``task`` function. + reduce_func : func, optional + If provided, it will be called with the output of each task instead of + storing them in a list. Note that the order in which results are + passed to ``reduce_func`` is not defined. It should return None or a + number. When returning a number, it represents the estimation of the + number of tasks left. On a return <= 0, the map will end early. + progress_bar : str, optional + Progress bar options's string for showing progress. + progress_bar_kwargs : dict, optional + Options for the progress bar. + map_kw: dict, optional + Dictionary containing entry for: + - timeout: float, Maximum time (sec) for the whole map. + - num_cpus: int, Number of jobs to run at once. + - fail_fast: bool, Abort at the first error. + All remaining entries of map_kw will be passed to the + mpi4py.MPIPoolExecutor constructor. + + Returns + ------- + result : list + The result list contains the value of + ``task(value, *task_args, **task_kwargs)`` for + each value in ``values``. If a ``reduce_func`` is provided, and empty + list will be returned. + + """ + + from mpi4py.futures import MPIPoolExecutor + map_kw = _read_map_kw(map_kw) + timeout = map_kw.pop('timeout') + num_workers = map_kw.pop('num_cpus') + fail_fast = map_kw.pop('fail_fast') + + def setup_executor(): + executor = MPIPoolExecutor(max_workers=num_workers, **map_kw) + return executor, num_workers + + def extract_result (_, future): + return future.result() + + def shutdown_executor(executor, _): + executor.shutdown() + + return _generic_pmap(task, values, task_args, task_kwargs, + reduce_func, timeout, fail_fast, + progress_bar, progress_bar_kwargs, + setup_executor, extract_result, shutdown_executor) + + _get_map = { "parallel_map": parallel_map, @@ -423,4 +497,5 @@ def shutdown_executor(executor, active_tasks): "serial_map": serial_map, "serial": serial_map, "loky": loky_pmap, + "mpi": mpi_pmap } From 923bfda53bd831ead3271e337e61d9342268ede2 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Wed, 27 Dec 2023 14:19:11 +0900 Subject: [PATCH 144/247] Clean up stochastic.py imports --- qutip/solver/stochastic.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index f548b2a907..632a23a041 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -1,11 +1,10 @@ __all__ = ["smesolve", "SMESolver", "ssesolve", "SSESolver"] -from .sode.ssystem import * +from .sode.ssystem import StochasticOpenSystem, StochasticClosedSystem from .result import MultiTrajResult, Result, ExpectOp from .multitraj import MultiTrajSolver -from .. import Qobj, QobjEvo, liouvillian, lindblad_dissipator +from .. import Qobj, QobjEvo import numpy as np -from collections.abc import Iterable from functools import partial from .solver_base import _solver_deprecation from ._feedback import _QobjFeedback, _DataFeedback, _WeinerFeedback From 8a727118fe76a9c75c6c0c5ee8ef333403b6102a Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Wed, 27 Dec 2023 15:24:44 +0900 Subject: [PATCH 145/247] Added mpi_options option to MultiTrajSolver and all subclasses. Cleaned up options docstrings of these classes and related solve functions. --- doc/apidoc/functions.rst | 2 +- qutip/solver/mcsolve.py | 56 ++++++++++++++---------- qutip/solver/multitraj.py | 8 ++-- qutip/solver/nm_mcsolve.py | 52 ++++++++++++++--------- qutip/solver/parallel.py | 6 +-- qutip/solver/stochastic.py | 87 ++++++++++++++++++-------------------- 6 files changed, 116 insertions(+), 95 deletions(-) diff --git a/doc/apidoc/functions.rst b/doc/apidoc/functions.rst index c54628f1c4..6f8f4b4f93 100644 --- a/doc/apidoc/functions.rst +++ b/doc/apidoc/functions.rst @@ -297,7 +297,7 @@ Parallelization --------------- .. automodule:: qutip.solver.parallel - :members: parallel_map, serial_map, loky_pmap + :members: parallel_map, serial_map, loky_pmap, mpi_pmap .. _functions-ipython: diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index a55b1a094c..adb2677e3e 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -62,8 +62,6 @@ def mcsolve(H, state, tlist, c_ops=(), e_ops=None, ntraj=500, *, | Whether or not to store the state vectors or density matrices. On `None` the states will be saved if no expectation operators are given. - - | normalize_output : bool - | Normalize output state to hide ODE numerical errors. - | progress_bar : str {'text', 'enhanced', 'tqdm', ''} | How to present the solver progress. 'tqdm' uses the python module of the same name and raise an error @@ -78,18 +76,26 @@ def mcsolve(H, state, tlist, c_ops=(), e_ops=None, ntraj=500, *, | Maximum number of (internally defined) steps allowed in one ``tlist`` step. - | max_step : float - | Maximum lenght of one internal step. When using pulses, it should be + | Maximum length of one internal step. When using pulses, it should be less than half the width of the thinnest pulse. - | keep_runs_results : bool, [False] | Whether to store results from all trajectories or just store the averages. - - | map : str {"serial", "parallel", "loky"} - | How to run the trajectories. "parallel" uses concurent module to - run in parallel while "loky" use the module of the same name to do - so. + - | map : str {"serial", "parallel", "loky", "mpi"} + | How to run the trajectories. "parallel" uses the multiprocessing + module to run in parallel while "loky" and "mpi" use the "loky" and + "mpi4py" modules to do so. + - | mpi_options : dict + | Only applies if map is "mpi". This dictionary will be passed as + keyword arguments to the `mpi4py.futures.MPIPoolExecutor` + constructor. Note that the `max_workers` argument is provided + separately through the `num_cpus` option. - | num_cpus : int | Number of cpus to use when running in parallel. ``None`` detect the number of available cpus. + - | bitgenerator : {None, "MT19937", "PCG64", "PCG64DXSM", ...} + Which of numpy.random's bitgenerator to use. With `None`, your + numpy version's default is used. - | norm_t_tol, norm_tol, norm_steps : float, float, int | Parameters used to find the collapse location. ``norm_t_tol`` and ``norm_tol`` are the tolerance in time and norm respectively. @@ -102,6 +108,10 @@ def mcsolve(H, state, tlist, c_ops=(), e_ops=None, ntraj=500, *, | Whether to use the improved sampling algorithm from Abdelhafez et al. PRA (2019) + Additional options may be available depending on the selected + differential equation integration method, see + `Integrator <./classes.html#classes-ode>`_. + seeds : int, SeedSequence, list, optional Seed for the random number generator. It can be a single seed used to spawn seeds for each trajectory or a list of seeds, one for each @@ -407,21 +417,15 @@ class MCSolver(MultiTrajSolver): _trajectory_resultclass = McTrajectoryResult _mc_integrator_class = MCIntegrator solver_options = { - "progress_bar": "text", - "progress_kwargs": {"chunk_size": 10}, - "store_final_state": False, - "store_states": None, - "keep_runs_results": False, + **MultiTrajSolver.solver_options, "method": "adams", - "map": "serial", - "num_cpus": None, - "bitgenerator": None, "mc_corr_eps": 1e-10, "norm_steps": 5, "norm_t_tol": 1e-6, "norm_tol": 1e-4, "improved_sampling": False, } + del solver_options["normalize_output"] def __init__(self, H, c_ops, *, options=None): _time_start = time() @@ -589,16 +593,22 @@ def options(self): ``chunk_size``. keep_runs_results: bool, default: False - Whether to store results from all trajectories or just store the - averages. + Whether to store results from all trajectories or just store the + averages. method: str, default: "adams" - Which ODE integrator methods are supported. - - map: str {"serial", "parallel", "loky"}, default: "serial" - How to run the trajectories. "parallel" uses concurent module to - run in parallel while "loky" use the module of the same name to do - so. + Which differential equation integration method to use. + + map: str {"serial", "parallel", "loky", "mpi"}, default: "serial" + How to run the trajectories. "parallel" uses the multiprocessing + module to run in parallel while "loky" and "mpi" use the "loky" and + "mpi4py" modules to do so. + + mpi_options: dict, default: {} + Only applies if map is "mpi". This dictionary will be passed as + keyword arguments to the `mpi4py.futures.MPIPoolExecutor` + constructor. Note that the `max_workers` argument is provided + separately through the `num_cpus` option. num_cpus: None, int Number of cpus to use when running in parallel. ``None`` detect the diff --git a/qutip/solver/multitraj.py b/qutip/solver/multitraj.py index 60f52a7202..679bffab3d 100644 --- a/qutip/solver/multitraj.py +++ b/qutip/solver/multitraj.py @@ -1,5 +1,5 @@ from .result import Result, MultiTrajResult -from .parallel import _get_map +from .parallel import _get_map, mpi_pmap from time import time from .solver_base import Solver from ..core import QobjEvo @@ -64,6 +64,7 @@ class MultiTrajSolver(Solver): "normalize_output": False, "method": "", "map": "serial", + "mpi_options": {}, "num_cpus": None, "bitgenerator": None, } @@ -143,10 +144,11 @@ def _initialize_run(self, state, ntraj=1, args=None, e_ops=(), result.add_end_condition(ntraj, target_tol) map_func = _get_map[self.options['map']] - map_kw = { + map_kw = self.options['mpi_options'] if map_func == mpi_pmap else {} + map_kw.update({ 'timeout': timeout, 'num_cpus': self.options['num_cpus'], - } + }) state0 = self._prepare_state(state) stats['preparation time'] += time() - start_time return stats, seeds, result, map_func, map_kw, state0 diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index 690a86c1be..a8df2b93ce 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -83,8 +83,6 @@ def nm_mcsolve(H, state, tlist, ops_and_rates=(), e_ops=None, ntraj=500, *, | Whether or not to store the state vectors or density matrices. On `None` the states will be saved if no expectation operators are given. - - | normalize_output : bool - | Normalize output state to hide ODE numerical errors. - | progress_bar : str {'text', 'enhanced', 'tqdm', ''} | How to present the solver progress. 'tqdm' uses the python module of the same name and raise an error @@ -99,18 +97,26 @@ def nm_mcsolve(H, state, tlist, ops_and_rates=(), e_ops=None, ntraj=500, *, | Maximum number of (internally defined) steps allowed in one ``tlist`` step. - | max_step : float - | Maximum lenght of one internal step. When using pulses, it should be + | Maximum length of one internal step. When using pulses, it should be less than half the width of the thinnest pulse. - | keep_runs_results : bool, [False] | Whether to store results from all trajectories or just store the averages. - - | map : str {"serial", "parallel", "loky"} - | How to run the trajectories. "parallel" uses concurent module to - run in parallel while "loky" use the module of the same name to do - so. + - | map : str {"serial", "parallel", "loky", "mpi"} + | How to run the trajectories. "parallel" uses the multiprocessing + module to run in parallel while "loky" and "mpi" use the "loky" and + "mpi4py" modules to do so. + - | mpi_options : dict + | Only applies if map is "mpi". This dictionary will be passed as + keyword arguments to the `mpi4py.futures.MPIPoolExecutor` + constructor. Note that the `max_workers` argument is provided + separately through the `num_cpus` option. - | num_cpus : int | Number of cpus to use when running in parallel. ``None`` detect the number of available cpus. + - | bitgenerator : {None, "MT19937", "PCG64", "PCG64DXSM", ...} + Which of numpy.random's bitgenerator to use. With `None`, your + numpy version's default is used. - | norm_t_tol, norm_tol, norm_steps : float, float, int | Parameters used to find the collapse location. ``norm_t_tol`` and ``norm_tol`` are the tolerance in time and norm respectively. @@ -119,9 +125,6 @@ def nm_mcsolve(H, state, tlist, ops_and_rates=(), e_ops=None, ntraj=500, *, - | mc_corr_eps : float | Small number used to detect non-physical collapse caused by numerical imprecision. - - | improved_sampling : Bool - | Whether to use the improved sampling algorithm from Abdelhafez et - al. PRA (2019) - | completeness_rtol, completeness_atol : float, float | Parameters used in determining whether the given Lindblad operators satisfy a certain completeness relation. If they do not, an @@ -132,6 +135,9 @@ def nm_mcsolve(H, state, tlist, ops_and_rates=(), e_ops=None, ntraj=500, *, integration of the martingale. Note that the 'improved_sampling' option is not currently supported. + Additional options may be available depending on the selected + differential equation integration method, see + `Integrator <./classes.html#classes-ode>`_. seeds : int, SeedSequence, list, optional Seed for the random number generator. It can be a single seed used to @@ -538,24 +544,30 @@ def options(self): progress_bar: str {'text', 'enhanced', 'tqdm', ''}, default: "text" How to present the solver progress. - 'tqdm' uses the python module of the same name and raise an error - if not installed. Empty string or False will disable the bar. + 'tqdm' uses the python module of the same name and raise an error if + not installed. Empty string or False will disable the bar. progress_kwargs: dict, default: {"chunk_size":10} Arguments to pass to the progress_bar. Qutip's bars use ``chunk_size``. keep_runs_results: bool, default: False - Whether to store results from all trajectories or just store the - averages. + Whether to store results from all trajectories or just store the + averages. method: str, default: "adams" - Which ODE integrator methods are supported. - - map: str {"serial", "parallel", "loky"}, default: "serial" - How to run the trajectories. "parallel" uses concurent module to - run in parallel while "loky" use the module of the same name to do - so. + Which differential equation integration method to use. + + map: str {"serial", "parallel", "loky", "mpi"}, default: "serial" + How to run the trajectories. "parallel" uses the multiprocessing + module to run in parallel while "loky" and "mpi" use the "loky" and + "mpi4py" modules to do so. + + mpi_options: dict, default: {} + Only applies if map is "mpi". This dictionary will be passed as + keyword arguments to the `mpi4py.futures.MPIPoolExecutor` + constructor. Note that the `max_workers` argument is provided + separately through the `num_cpus` option. num_cpus: None, int Number of cpus to use when running in parallel. ``None`` detect the diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index ca85aae101..20dfc29dcd 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -272,7 +272,7 @@ def parallel_map(task, values, task_args=None, task_kwargs=None, progress_bar=None, progress_bar_kwargs={}): """ Parallel execution of a mapping of ``values`` to the function ``task``. - This is functionally equivalent to: + This is functionally equivalent to:: result = [task(value, *task_args, **task_kwargs) for value in values] @@ -346,7 +346,7 @@ def loky_pmap(task, values, task_args=None, task_kwargs=None, progress_bar=None, progress_bar_kwargs={}): """ Parallel execution of a mapping of ``values`` to the function ``task``. - This is functionally equivalent to: + This is functionally equivalent to:: result = [task(value, *task_args, **task_kwargs) for value in values] @@ -421,7 +421,7 @@ def mpi_pmap(task, values, task_args=None, task_kwargs=None, progress_bar=None, progress_bar_kwargs={}): """ Parallel execution of a mapping of ``values`` to the function ``task``. - This is functionally equivalent to: + This is functionally equivalent to:: result = [task(value, *task_args, **task_kwargs) for value in values] diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index 632a23a041..8e1cc9a033 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -332,13 +332,21 @@ def smesolve( - | method : str | Which stochastic differential equation integration method to use. Main ones are {"euler", "rouchon", "platen", "taylor1.5_imp"} - - | map : str {"serial", "parallel", "loky"} - | How to run the trajectories. "parallel" uses concurent module to - run in parallel while "loky" use the module of the same name to do - so. + - | map : str {"serial", "parallel", "loky", "mpi"} + | How to run the trajectories. "parallel" uses the multiprocessing + module to run in parallel while "loky" and "mpi" use the "loky" and + "mpi4py" modules to do so. + - | mpi_options : dict + | Only applies if map is "mpi". This dictionary will be passed as + keyword arguments to the `mpi4py.futures.MPIPoolExecutor` + constructor. Note that the `max_workers` argument is provided + separately through the `num_cpus` option. - | num_cpus : NoneType, int | Number of cpus to use when running in parallel. ``None`` detect the number of available cpus. + - | bitgenerator : {None, "MT19937", "PCG64", "PCG64DXSM", ...} + Which of numpy.random's bitgenerator to use. With `None`, your + numpy version's default is used. - | dt : float | The finite steps lenght for the Stochastic integration method. Default change depending on the integrator. @@ -452,13 +460,21 @@ def ssesolve( - | method : str | Which stochastic differential equation integration method to use. Main ones are {"euler", "rouchon", "platen", "taylor1.5_imp"} - - | map : str {"serial", "parallel", "loky"} - How to run the trajectories. "parallel" uses concurent module to - run in parallel while "loky" use the module of the same name to do - so. + - | map : str {"serial", "parallel", "loky", "mpi"} + | How to run the trajectories. "parallel" uses the multiprocessing + module to run in parallel while "loky" and "mpi" use the "loky" and + "mpi4py" modules to do so. + - | mpi_options : dict + | Only applies if map is "mpi". This dictionary will be passed as + keyword arguments to the `mpi4py.futures.MPIPoolExecutor` + constructor. Note that the `max_workers` argument is provided + separately through the `num_cpus` option. - | num_cpus : NoneType, int | Number of cpus to use when running in parallel. ``None`` detect the number of available cpus. + - | bitgenerator : {None, "MT19937", "PCG64", "PCG64DXSM", ...} + Which of numpy.random's bitgenerator to use. With `None`, your + numpy version's default is used. - | dt : float | The finite steps lenght for the Stochastic integration method. Default change depending on the integrator. @@ -493,17 +509,9 @@ class StochasticSolver(MultiTrajSolver): system = None _open = None solver_options = { - "progress_bar": "text", - "progress_kwargs": {"chunk_size": 10}, - "store_final_state": False, - "store_states": None, + **MultiTrajSolver.solver_options, + "method": "platen", "store_measurement": False, - "keep_runs_results": False, - "normalize_output": False, - "method": "taylor1.5", - "map": "serial", - "num_cpus": None, - "bitgenerator": None, } def __init__(self, H, sc_ops, heterodyne, *, c_ops=(), options=None): @@ -670,13 +678,22 @@ def options(self): Whether to store results from all trajectories or just store the averages. + normalize_output: bool + Normalize output state to hide ODE numerical errors. + method: str, default: "platen" - Which ODE integrator methods are supported. + Which differential equation integration method to use. + + map: str {"serial", "parallel", "loky", "mpi"}, default: "serial" + How to run the trajectories. "parallel" uses the multiprocessing + module to run in parallel while "loky" and "mpi" use the "loky" and + "mpi4py" modules to do so. - map: str {"serial", "parallel", "loky"}, default: "serial" - How to run the trajectories. "parallel" uses concurent module to - run in parallel while "loky" use the module of the same name to do - so. + mpi_options: dict, default: {} + Only applies if map is "mpi". This dictionary will be passed as + keyword arguments to the `mpi4py.futures.MPIPoolExecutor` + constructor. Note that the `max_workers` argument is provided + separately through the `num_cpus` option. num_cpus: None, int, default: None Number of cpus to use when running in parallel. ``None`` detect the @@ -778,17 +795,7 @@ class SMESolver(StochasticSolver): _avail_integrators = {} _open = True solver_options = { - "progress_bar": "text", - "progress_kwargs": {"chunk_size": 10}, - "store_final_state": False, - "store_states": None, - "store_measurement": False, - "keep_runs_results": False, - "normalize_output": False, - "method": "platen", - "map": "serial", - "num_cpus": None, - "bitgenerator": None, + **StochasticSolver.solver_options } @@ -821,15 +828,5 @@ class SSESolver(StochasticSolver): _avail_integrators = {} _open = False solver_options = { - "progress_bar": "text", - "progress_kwargs": {"chunk_size": 10}, - "store_final_state": False, - "store_states": None, - "store_measurement": False, - "keep_runs_results": False, - "normalize_output": False, - "method": "platen", - "map": "serial", - "num_cpus": None, - "bitgenerator": None, + **StochasticSolver.solver_options } From 4f0f079d24a2349fd196241e8861eefb055c0087 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Wed, 27 Dec 2023 15:46:11 +0900 Subject: [PATCH 146/247] Added mpi_pmap to tests --- qutip/tests/solver/test_parallel.py | 28 ++++++++++++++++++++++------ 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/qutip/tests/solver/test_parallel.py b/qutip/tests/solver/test_parallel.py index bc3af0d62f..b7836983ee 100644 --- a/qutip/tests/solver/test_parallel.py +++ b/qutip/tests/solver/test_parallel.py @@ -4,7 +4,7 @@ import threading from qutip.solver.parallel import ( - parallel_map, serial_map, loky_pmap, MapExceptions + parallel_map, serial_map, loky_pmap, mpi_pmap, MapExceptions ) @@ -22,6 +22,7 @@ def _func2(x, a, b, c, d=0, e=0, f=0): @pytest.mark.parametrize('map', [ pytest.param(parallel_map, id='parallel_map'), pytest.param(loky_pmap, id='loky_pmap'), + pytest.param(mpi_pmap, id='mpi_pmap'), pytest.param(serial_map, id='serial_map'), ]) @pytest.mark.parametrize('num_cpus', @@ -29,7 +30,9 @@ def _func2(x, a, b, c, d=0, e=0, f=0): ids=['1', '2']) def test_map(map, num_cpus): if map is loky_pmap: - loky = pytest.importorskip("loky") + pytest.importorskip("loky") + if map is mpi_pmap: + pytest.importorskip("mpi4py") args = (1, 2, 3) kwargs = {'d': 4, 'e': 5, 'f': 6} @@ -48,6 +51,7 @@ def test_map(map, num_cpus): @pytest.mark.parametrize('map', [ pytest.param(parallel_map, id='parallel_map'), pytest.param(loky_pmap, id='loky_pmap'), + pytest.param(mpi_pmap, id='mpi_pmap'), pytest.param(serial_map, id='serial_map'), ]) @pytest.mark.parametrize('num_cpus', @@ -55,7 +59,10 @@ def test_map(map, num_cpus): ids=['1', '2']) def test_map_accumulator(map, num_cpus): if map is loky_pmap: - loky = pytest.importorskip("loky") + pytest.importorskip("loky") + if map is mpi_pmap: + pytest.importorskip("mpi4py") + args = (1, 2, 3) kwargs = {'d': 4, 'e': 5, 'f': 6} map_kw = { @@ -84,11 +91,14 @@ def func(i): @pytest.mark.parametrize('map', [ pytest.param(parallel_map, id='parallel_map'), pytest.param(loky_pmap, id='loky_pmap'), + pytest.param(mpi_pmap, id='mpi_pmap'), pytest.param(serial_map, id='serial_map'), ]) def test_map_pass_error(map): if map is loky_pmap: - loky = pytest.importorskip("loky") + pytest.importorskip("loky") + if map is mpi_pmap: + pytest.importorskip("mpi4py") with pytest.raises(CustomException) as err: map(func, range(10)) @@ -98,11 +108,14 @@ def test_map_pass_error(map): @pytest.mark.parametrize('map', [ pytest.param(parallel_map, id='parallel_map'), pytest.param(loky_pmap, id='loky_pmap'), + pytest.param(mpi_pmap, id='mpi_pmap'), pytest.param(serial_map, id='serial_map'), ]) def test_map_store_error(map): if map is loky_pmap: - loky = pytest.importorskip("loky") + pytest.importorskip("loky") + if map is mpi_pmap: + pytest.importorskip("mpi4py") with pytest.raises(MapExceptions) as err: map(func, range(10), map_kw={"fail_fast": False}) @@ -122,11 +135,14 @@ def test_map_store_error(map): @pytest.mark.parametrize('map', [ pytest.param(parallel_map, id='parallel_map'), pytest.param(loky_pmap, id='loky_pmap'), + pytest.param(mpi_pmap, id='mpi_pmap'), pytest.param(serial_map, id='serial_map'), ]) def test_map_early_end(map): if map is loky_pmap: - loky = pytest.importorskip("loky") + pytest.importorskip("loky") + if map is mpi_pmap: + pytest.importorskip("mpi4py") results = [] From a82532ada6992ebbc4bfc928d2c0f6713366d247 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Sun, 31 Dec 2023 13:09:08 +0900 Subject: [PATCH 147/247] parallel_map: fix keyboard interrupt handling --- qutip/solver/parallel.py | 26 +++++++++++--------------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index 4e51c2c8a3..880365a30d 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -199,25 +199,21 @@ def result_func(_, value): def _done_callback(future): if not future.cancelled(): - try: + ex = future.exception() + if isinstance(ex, KeyboardInterrupt): + # When a keyboard interrupt happens, it is raised in the main + # thread and in all worker threads. At this point in the code, + # the worker threads have already returned and the main thread + # is only waiting for the ProcessPoolExecutor to shutdown + # before exiting. We therefore return immediately. + return + if isinstance(ex, Exception): + errors[future._i] = ex + else: result = future.result() remaining_ntraj = result_func(future._i, result) if remaining_ntraj is not None and remaining_ntraj <= 0: finished.append(True) - except Exception as e: - errors[future._i] = e - except KeyboardInterrupt: - # When a keyboard interrupt happens, it is raised in the main - # thread and in all worker threads. The worker threads have - # already returned and the main thread is only waiting for the - # ProcessPoolExecutor to shutdown before exiting. If the call - # to `future.result()` in this callback function raises the - # KeyboardInterrupt again, it makes the system enter a kind of - # deadlock state, where the user has to press CTRL+C a second - # time to actually end the program. For that reason, we - # silently ignore the KeyboardInterrupt here, avoiding the - # deadlock and allowing the main thread to exit. - pass progress_bar.update() if sys.version_info >= (3, 7): From 1529237b401bf8f5271bf878498ca6cc9d07f772 Mon Sep 17 00:00:00 2001 From: anonymousdouble <112695649+anonymousdouble@users.noreply.github.com> Date: Tue, 2 Jan 2024 15:28:45 +1100 Subject: [PATCH 148/247] Update conftest.py --- qutip/tests/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/tests/conftest.py b/qutip/tests/conftest.py index 2ac625ee47..17fbccf055 100644 --- a/qutip/tests/conftest.py +++ b/qutip/tests/conftest.py @@ -100,7 +100,7 @@ def _patched_build_err_msg(arrays, err_msg, header='Items are not equal:', except Exception as exc: r = '[repr failed for <{}>: {}]'.format(type(a).__name__, exc) # [diff] The original truncates the output to 3 lines here. - msg.append(' %s: %s' % (names[i], r)) + msg.append(f' {names[i]}: {r}') return '\n'.join(msg) From 68251cd3d59f4f6e9a79e12c5ea516f0a00e5103 Mon Sep 17 00:00:00 2001 From: Bogdan Reznychenko <100156521+theodotk@users.noreply.github.com> Date: Thu, 4 Jan 2024 16:17:54 -0800 Subject: [PATCH 149/247] Faster kraus_to_choi --- qutip/core/superop_reps.py | 42 ++++++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 18 deletions(-) diff --git a/qutip/core/superop_reps.py b/qutip/core/superop_reps.py index 0f518384bd..5393e399b7 100644 --- a/qutip/core/superop_reps.py +++ b/qutip/core/superop_reps.py @@ -138,28 +138,34 @@ def _choi_to_kraus(q_oper, tol=1e-9): # Individual conversions from Kraus operators are public because the output # list of Kraus operators is not itself a quantum object. -def kraus_to_choi(kraus_list): - """ - Take a list of Kraus operators and returns the Choi matrix for the channel - represented by the Kraus operators in `kraus_list`. +def kraus_to_choi(kraus_ops: list[Qobj]) -> Qobj: + r""" + Convert a list of Kraus operators into Choi representation of the channel. + + Essentially, kraus operators are a decomposition of a Choi matrix, and its reconstruction from them should go as + $E = \sum_{i} |K_i\rangle\rangle \langle\langle K_i|$, where we use vector representation of Kraus operators. Parameters ---------- - kraus_list : list of Qobj - The list of Kraus super operators to convert. + kraus_ops : list[Qobj] + The list of Kraus operators to be converted to Choi representation. + + Returns + ------- + choi : Qobj + A quantum object representing the same map as ``kraus_ops``, such that + ``choi.superrep == "choi"``. """ - kraus_mat_list = [k.full() for k in kraus_list] - op_rng = list(range(kraus_mat_list[0].shape[1])) - choi_blocks = np.array( - [[sum(op[:, c_ix, None] @ np.conj(op[None, :, r_ix]) - for op in kraus_mat_list) - for r_ix in op_rng] - for c_ix in op_rng] - ) - return Qobj(np.hstack(np.hstack(choi_blocks)), - dims=[kraus_list[0].dims[::-1]]*2, - superrep='choi', - copy=False) + num_ops = len(kraus_ops) + # If Kraus ops have dims [M, N] in qutip notation (act on [N, N] density matrix and produce [M, M] d.m.), + # Choi matrix Hilbert space will be [[M, N], [M, N]] because Choi Hilbert space is (output space) x (input space). + choi_dims = [kraus_ops[0].dims] * 2 + # transform a list of Qobj matrices list[sum_ij k_ij |i>> = sum_I k_I |I>> + kraus_vectors = np.reshape(np.asarray(kraus_ops), (num_ops, -1)) + # sum_{I} |k_I|^2 |I>>< Date: Thu, 4 Jan 2024 17:39:35 -0800 Subject: [PATCH 150/247] adapt to qutip 5 --- qutip/core/superop_reps.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutip/core/superop_reps.py b/qutip/core/superop_reps.py index 5393e399b7..51671ddcf4 100644 --- a/qutip/core/superop_reps.py +++ b/qutip/core/superop_reps.py @@ -156,13 +156,13 @@ def kraus_to_choi(kraus_ops: list[Qobj]) -> Qobj: A quantum object representing the same map as ``kraus_ops``, such that ``choi.superrep == "choi"``. """ - num_ops = len(kraus_ops) + len_op = np.prod(kraus_ops[0].shape) # If Kraus ops have dims [M, N] in qutip notation (act on [N, N] density matrix and produce [M, M] d.m.), # Choi matrix Hilbert space will be [[M, N], [M, N]] because Choi Hilbert space is (output space) x (input space). choi_dims = [kraus_ops[0].dims] * 2 # transform a list of Qobj matrices list[sum_ij k_ij |i>> = sum_I k_I |I>> - kraus_vectors = np.reshape(np.asarray(kraus_ops), (num_ops, -1)) + kraus_vectors = np.asarray([np.reshape(kraus_op.full(), len_op, "F") for kraus_op in kraus_ops]) # sum_{I} |k_I|^2 |I>>< Date: Thu, 4 Jan 2024 17:54:20 -0800 Subject: [PATCH 151/247] towncrier --- doc/changes/2284.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/2284.misc diff --git a/doc/changes/2284.misc b/doc/changes/2284.misc new file mode 100644 index 0000000000..8d4ffffcdc --- /dev/null +++ b/doc/changes/2284.misc @@ -0,0 +1 @@ +Rework `kraus_to_choi` making it faster \ No newline at end of file From d60829395a8397cce33aebc29b64dc11016cbdd9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BE=D0=B3=D0=B4=D0=B0=D0=BD=20=D0=91=D0=BE=D0=B3?= =?UTF-8?q?=D0=B4=D0=B0=D0=BD?= Date: Thu, 4 Jan 2024 17:56:46 -0800 Subject: [PATCH 152/247] rm type --- qutip/core/superop_reps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/core/superop_reps.py b/qutip/core/superop_reps.py index 51671ddcf4..ab9058ed90 100644 --- a/qutip/core/superop_reps.py +++ b/qutip/core/superop_reps.py @@ -138,7 +138,7 @@ def _choi_to_kraus(q_oper, tol=1e-9): # Individual conversions from Kraus operators are public because the output # list of Kraus operators is not itself a quantum object. -def kraus_to_choi(kraus_ops: list[Qobj]) -> Qobj: +def kraus_to_choi(kraus_ops): r""" Convert a list of Kraus operators into Choi representation of the channel. From d44f74027c8e8d643482c7d90d0b50096890edc6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=91=D0=BE=D0=B3=D0=B4=D0=B0=D0=BD=20=D0=91=D0=BE=D0=B3?= =?UTF-8?q?=D0=B4=D0=B0=D0=BD?= Date: Thu, 4 Jan 2024 18:03:12 -0800 Subject: [PATCH 153/247] line lenghts --- qutip/core/superop_reps.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/qutip/core/superop_reps.py b/qutip/core/superop_reps.py index ab9058ed90..6f3b1764f7 100644 --- a/qutip/core/superop_reps.py +++ b/qutip/core/superop_reps.py @@ -142,8 +142,10 @@ def kraus_to_choi(kraus_ops): r""" Convert a list of Kraus operators into Choi representation of the channel. - Essentially, kraus operators are a decomposition of a Choi matrix, and its reconstruction from them should go as - $E = \sum_{i} |K_i\rangle\rangle \langle\langle K_i|$, where we use vector representation of Kraus operators. + Essentially, kraus operators are a decomposition of a Choi matrix, + and its reconstruction from them should go as + $E = \sum_{i} |K_i\rangle\rangle \langle\langle K_i|$, + where we use vector representation of Kraus operators. Parameters ---------- @@ -157,14 +159,20 @@ def kraus_to_choi(kraus_ops): ``choi.superrep == "choi"``. """ len_op = np.prod(kraus_ops[0].shape) - # If Kraus ops have dims [M, N] in qutip notation (act on [N, N] density matrix and produce [M, M] d.m.), - # Choi matrix Hilbert space will be [[M, N], [M, N]] because Choi Hilbert space is (output space) x (input space). + # If Kraus ops have dims [M, N] in qutip notation (act on [N, N] density + # matrix and produce [M, M] d.m.), Choi matrix Hilbert space will + # be [[M, N], [M, N]] because Choi Hilbert space + # is (output space) x (input space). choi_dims = [kraus_ops[0].dims] * 2 # transform a list of Qobj matrices list[sum_ij k_ij |i>> = sum_I k_I |I>> - kraus_vectors = np.asarray([np.reshape(kraus_op.full(), len_op, "F") for kraus_op in kraus_ops]) + kraus_vectors = np.asarray( + [np.reshape(kraus_op.full(), len_op, "F") for kraus_op in kraus_ops] + ) # sum_{I} |k_I|^2 |I>>< Date: Thu, 4 Jan 2024 18:46:05 -0800 Subject: [PATCH 154/247] fix docs --- qutip/core/superop_reps.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/core/superop_reps.py b/qutip/core/superop_reps.py index 6f3b1764f7..9a5d7222c3 100644 --- a/qutip/core/superop_reps.py +++ b/qutip/core/superop_reps.py @@ -144,7 +144,7 @@ def kraus_to_choi(kraus_ops): Essentially, kraus operators are a decomposition of a Choi matrix, and its reconstruction from them should go as - $E = \sum_{i} |K_i\rangle\rangle \langle\langle K_i|$, + :math:`E = \sum_{i} |K_i\rangle\rangle \langle\langle K_i|`, where we use vector representation of Kraus operators. Parameters From 6d6b985a20714682b82f4f0569138cf8e0fc59c0 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 8 Jan 2024 14:44:44 -0500 Subject: [PATCH 155/247] Ignore deprecation warnings from cython 0.29 --- .github/workflows/tests.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 096e5cb593..f44aa7e8f2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -52,6 +52,7 @@ jobs: scipy-requirement: ">=1.5,<1.6" condaforge: 1 oldcython: 1 + pytest-extra-options: "-W ignore:dep_util:DeprecationWarning" # No MKL runs. MKL is now the default for conda installations, but # not necessarily for pip. @@ -75,6 +76,7 @@ jobs: python-version: "3.10" condaforge: 1 oldcython: 1 + pytest-extra-options: "-W ignore:dep_util:DeprecationWarning" # Python 3.11 and latest numpy # Use conda-forge to provide Python 3.11 and latest numpy From c445ffe180d7709c9cfb1b702fc13c88eb04126c Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 8 Jan 2024 14:49:46 -0500 Subject: [PATCH 156/247] add towncrier --- doc/changes/2288.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/2288.bugfix diff --git a/doc/changes/2288.bugfix b/doc/changes/2288.bugfix new file mode 100644 index 0000000000..f0dd3bc183 --- /dev/null +++ b/doc/changes/2288.bugfix @@ -0,0 +1 @@ +Ignore deprecation warnings from cython 0.29.X in tests. \ No newline at end of file From 584849a7803e7730798dc8a47111c2f7264c9ee2 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 9 Jan 2024 15:55:31 -0500 Subject: [PATCH 157/247] State functions take dims as first input --- qutip/core/dimensions.py | 25 ++++++- qutip/core/states.py | 116 ++++++++++++++++++++------------ qutip/tests/core/test_states.py | 17 +++-- 3 files changed, 105 insertions(+), 53 deletions(-) diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index 7e0f17cd57..38d98f447b 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -5,6 +5,7 @@ # Everything should be explicitly imported, not made available by default. import numpy as np +import numbers from operator import getitem from functools import partial from qutip.settings import settings @@ -459,12 +460,20 @@ def dims2idx(self, dims): """ Transform dimensions indices to full array indices. """ - return dims + if not isinstance(dims, list) or len(dims) != 1: + raise ValueError("Dimensions must be a list of one element") + if not (0 <= dims[0] < self.size): + raise IndexError("Dimensions out of range") + if not isinstance(dims[0], numbers.Integral): + raise TypeError("Dimensions must be integers") + return dims[0] def idx2dims(self, idx): """ Transform full array indices to dimensions indices. """ + if not (0 <= idx < self.size): + raise IndexError("Index out of range") return [idx] def step(self): @@ -502,6 +511,9 @@ def replace(self, idx, new): def replace_superrep(self, super_rep): return self + + def scalar_like(self): + return Field() class Field(Space): @@ -595,7 +607,7 @@ def dims2idx(self, dims): pos = 0 step = 1 for space, dim in zip(self.spaces[::-1], dims[::-1]): - pos += space.dims2idx(dim) * step + pos += space.dims2idx([dim]) * step step *= space.size return pos @@ -643,6 +655,9 @@ def replace_superrep(self, super_rep): return Compound( *[space.replace_superrep(super_rep) for space in self.spaces] ) + + def scalar_like(self): + return [space.scalar_like() for space in self.spaces] class SuperSpace(Space): @@ -704,6 +719,9 @@ def replace(self, idx, new): def replace_superrep(self, super_rep): return SuperSpace(self.oper, rep=super_rep) + + def scalar_like(self): + return self.oper.scalar_like() class MetaDims(type): @@ -901,3 +919,6 @@ def replace_superrep(self, super_rep): self.from_.replace_superrep(super_rep), self.to_.replace_superrep(super_rep) ) + + def scalar_like(self): + return [self.to_.scalar_like(), self.from_.scalar_like()] diff --git a/qutip/core/states.py b/qutip/core/states.py index 79bc89529a..8e891115d2 100644 --- a/qutip/core/states.py +++ b/qutip/core/states.py @@ -18,6 +18,7 @@ from .qobj import Qobj from .operators import jmat, displace, qdiags from .tensor import tensor +from .dimensions import Space, Dimensions from .. import settings @@ -43,12 +44,28 @@ def _promote_to_zero_list(arg, length): raise TypeError("Dimensions must be an integer or list of integers.") +def _to_space(dimensions): + """ + Convert `dimensions` to a :class:`.Space`. + + Returns + ------- + space : :class:`.Space` + """ + if isinstance(dimensions, Space): + return dimensions + elif isinstance(dimensions, list): + return Space(dimensions) + else: + return Space([dimensions]) + + def basis(dimensions, n=None, offset=None, *, dtype=None): """Generates the vector representation of a Fock state. Parameters ---------- - dimensions : int or list of ints + dimensions : int or list of ints, Space Number of Fock states in Hilbert space. If a list, then the resultant object will be a tensor product over spaces with those dimensions. @@ -106,25 +123,37 @@ def basis(dimensions, n=None, offset=None, *, dtype=None): """ dtype = dtype or settings.core["default_dtype"] or _data.Dense - # Promote all parameters to lists to simplify later logic. - if not isinstance(dimensions, list): - dimensions = [dimensions] - n_dimensions = len(dimensions) - ns = [m-off for m, off in zip(_promote_to_zero_list(n, n_dimensions), - _promote_to_zero_list(offset, n_dimensions))] - if any((not isinstance(x, numbers.Integral)) or x < 0 for x in dimensions): - raise ValueError("All dimensions must be >= 0.") - if not all(0 <= n < dimension for n, dimension in zip(ns, dimensions)): - raise ValueError("All basis indices must be " - "`offset <= n < dimension+offset`.") - location, size = 0, 1 - for m, dimension in zip(reversed(ns), reversed(dimensions)): - location += m*size - size *= dimension + # Promote all parameters to Space to simplify later logic. + dimensions = _to_space(dimensions) + + size = dimensions.size + if n is None: + location = 0 + elif offset: + if not isinstance(offset, list): offset = [offset] + if not isinstance(n, list): n = [n] + if len(n) != len(dimensions.as_list()) or len(offset) != len(n): + raise ValueError("All list inputs must be the same length.") + + n_off = [m-off for m, off in zip(n, offset)] + try: + location = dimensions.dims2idx(n_off) + except IndexError: + raise ValueError("All basis indices must be integers in the range " + "`offset <= n < dimension+offset`.") + else: + if not isinstance(n, list): n = [n] + if len(n) != len(dimensions.as_list()): + raise ValueError("All list inputs must be the same length.") + try: + location = dimensions.dims2idx(n) + except IndexError: + raise ValueError("All basis indices must be integers in the range " + "`0 <= n < dimension`.") data = _data.one_element[dtype]((size, 1), (location, 0), 1) return Qobj(data, - dims=[dimensions, [1]*n_dimensions], + dims=Dimensions((dimensions, dimensions.scalar_like())), isherm=False, isunitary=False, copy=False) @@ -305,7 +334,7 @@ def fock_dm(dimensions, n=None, offset=None, *, dtype=None): Parameters ---------- - dimensions : int or list of ints + dimensions : int or list of ints, Space Number of Fock states in Hilbert space. If a list, then the resultant object will be a tensor product over spaces with those dimensions. @@ -350,7 +379,7 @@ def fock(dimensions, n=None, offset=None, *, dtype=None): Parameters ---------- - dimensions : int or list of ints + dimensions : int or list of ints, Space Number of Fock states in Hilbert space. If a list, then the resultant object will be a tensor product over spaces with those dimensions. @@ -465,15 +494,16 @@ def thermal_dm(N, n, method='operator', *, dtype=None): return out -def maximally_mixed_dm(N, *, dtype=None): +def maximally_mixed_dm(dimensions, *, dtype=None): """ Returns the maximally mixed density matrix for a Hilbert space of dimension N. Parameters ---------- - N : int - Number of basis states in Hilbert space. + dimensions : int or list of ints, Space + Number of Fock states in Hilbert space. If a list, then the resultant + object will be a tensor product over spaces with those dimensions. dtype : type or str, optional Storage representation. Any data-layer known to ``qutip.data.to`` is @@ -485,9 +515,10 @@ def maximally_mixed_dm(N, *, dtype=None): Thermal state density matrix. """ dtype = dtype or settings.core["default_dtype"] or _data.Dia - if not isinstance(N, numbers.Integral) or N <= 0: - raise ValueError("N must be integer N > 0") - return Qobj(_data.identity[dtype](N, scale=1/N), dims=[[N], [N]], + dimensions = _to_space(dimensions) + N = dimensions.size + + return Qobj(_data.identity[dtype](N, scale=1/N), dims=[dimensions, dimensions], isherm=True, isunitary=(N == 1), copy=False) @@ -523,15 +554,16 @@ def ket2dm(Q): raise TypeError("Input is not a ket or bra vector.") -def projection(N, n, m, offset=None, *, dtype=None): +def projection(dimensions, n, m, offset=None, *, dtype=None): r""" The projection operator that projects state :math:`\lvert m\rangle` on state :math:`\lvert n\rangle`. Parameters ---------- - N : int - Number of basis states in Hilbert space. + dimensions : int or list of ints, Space + Number of Fock states in Hilbert space. If a list, then the resultant + object will be a tensor product over spaces with those dimensions. n, m : float The number states in the projection. @@ -550,8 +582,8 @@ def projection(N, n, m, offset=None, *, dtype=None): Requested projection operator. """ dtype = dtype or settings.core["default_dtype"] or _data.CSR - return basis(N, n, offset=offset, dtype=dtype) @ \ - basis(N, m, offset=offset, dtype=dtype).dag() + return basis(dimensions, n, offset=offset, dtype=dtype) @ \ + basis(dimensions, m, offset=offset, dtype=dtype).dag() def qstate(string, *, dtype=None): @@ -959,18 +991,15 @@ def phase_basis(N, m, phi0=0, *, dtype=None): return Qobj(data, dims=[[N], [1]], copy=False).to(dtype) -def zero_ket(N, dims=None, *, dtype=None): +def zero_ket(dimensions, *, dtype=None): """ Creates the zero ket vector with shape Nx1 and dimensions `dims`. Parameters ---------- - N : int - Hilbert space dimensionality - - dims : list, optional - Optional dimensions if ket corresponds to - a composite Hilbert space. + dimensions : int or list of ints, Space + Number of Fock states in Hilbert space. If a list, then the resultant + object will be a tensor product over spaces with those dimensions. dtype : type or str, optional Storage representation. Any data-layer known to ``qutip.data.to`` is @@ -983,7 +1012,10 @@ def zero_ket(N, dims=None, *, dtype=None): """ dtype = dtype or settings.core["default_dtype"] or _data.Dense - return Qobj(_data.zeros[dtype](N, 1), dims=dims, copy=False) + dimensions = _to_space(dimensions) + N = dimensions.size + return Qobj(_data.zeros[dtype](N, 1), + dims=[dimensions, dimensions.scalar_like()], copy=False) def spin_state(j, m, type='ket', *, dtype=None): @@ -1162,14 +1194,14 @@ def triplet_states(*, dtype=None): ] -def w_state(N=3, *, dtype=None): +def w_state(N, *, dtype=None): """ Returns the N-qubit W-state: ``[ |100..0> + |010..0> + |001..0> + ... |000..1> ] / sqrt(n)`` Parameters ---------- - N : int, default: 3 + N : int Number of qubits in state dtype : type or str, optional @@ -1189,14 +1221,14 @@ def w_state(N=3, *, dtype=None): return np.sqrt(1 / N) * state -def ghz_state(N=3, *, dtype=None): +def ghz_state(N, *, dtype=None): """ Returns the N-qubit GHZ-state: ``[ |00...00> + |11...11> ] / sqrt(2)`` Parameters ---------- - N : int, default: 3 + N : int Number of qubits in state dtype : type or str, optional diff --git a/qutip/tests/core/test_states.py b/qutip/tests/core/test_states.py index be17941dec..c32d9105d4 100644 --- a/qutip/tests/core/test_states.py +++ b/qutip/tests/core/test_states.py @@ -25,12 +25,12 @@ def test_implicit_tensor_basis_like(to_test, size, n): @pytest.mark.parametrize("size, n, offset, msg", [ ([2, 2], [0, 1, 1], [0, 0], "All list inputs must be the same length."), - ([2, 2], [0, 1], 0, "All list inputs must be the same length."), - (-1, 0, 0, "All dimensions must be >= 0."), - (1.5, 0, 0, "All dimensions must be >= 0."), - (5, 5, 0, "All basis indices must be `offset <= n < dimension+offset`."), - (5, 0, 2, "All basis indices must be `offset <= n < dimension+offset`."), -], ids=["n too long", "offset too long", "neg dims", + ([2, 2], [1, 1], 1, "All list inputs must be the same length."), + (-1, 0, 0, "Dimensions must be integers > 0"), + (1.5, 0, 0, "Dimensions must be integers > 0"), + (5, 5, 0, "All basis indices must be integers in the range `0 <= n < dimension`."), + (5, 0, 2, "All basis indices must be integers in the range `offset <= n < dimension+offset`."), +], ids=["n too long", "offset too short", "neg dims", "fraction dims", "n too large", "n too small"] ) def test_basis_error(size, n, offset, msg): @@ -42,8 +42,7 @@ def test_basis_error(size, n, offset, msg): def test_basis_error_type(): with pytest.raises(TypeError) as e: qutip.basis(5, 3.5) - assert str(e.value) == ("Dimensions must be an integer " - "or list of integers.") + assert str(e.value) == ("Dimensions must be integers") @pytest.mark.parametrize("size, n, m", [ @@ -183,7 +182,7 @@ def test_spin_output(func): def test_maximally_mixed_dm_error(N): with pytest.raises(ValueError) as e: qutip.maximally_mixed_dm(N) - assert str(e.value) == "N must be integer N > 0" + assert str(e.value) == "Dimensions must be integers > 0" def test_TripletStateNorm(): From 7e5ae2e63be5573bc23ed4744c80ad1b04720f09 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 9 Jan 2024 17:00:56 -0500 Subject: [PATCH 158/247] operator function accept Space --- qutip/core/operators.py | 20 ++++++++++++++------ qutip/core/states.py | 4 ++-- qutip/tests/core/test_operators.py | 6 ++++++ 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/qutip/core/operators.py b/qutip/core/operators.py index 2ef1f3ff03..118cc35a0a 100644 --- a/qutip/core/operators.py +++ b/qutip/core/operators.py @@ -19,7 +19,7 @@ from . import data as _data from .qobj import Qobj -from .dimensions import flatten +from .dimensions import flatten, Space from .. import settings @@ -638,7 +638,7 @@ def _implicit_tensor_dimensions(dimensions): dimensions : list Dimension list in the form required by ``Qobj`` creation. """ - if not isinstance(dimensions, list): + if not isinstance(dimensions, (list, Space)): dimensions = [dimensions] flat = flatten(dimensions) if not all(isinstance(x, numbers.Integral) and x >= 0 for x in flat): @@ -646,17 +646,20 @@ def _implicit_tensor_dimensions(dimensions): return np.prod(flat), [dimensions, dimensions] -def qzero(dimensions, *, dtype=None): +def qzero(dimensions, dims_right=None, *, dtype=None): """ Zero operator. Parameters ---------- - dimensions : (int) or (list of int) or (list of list of int) + dimensions : (int) or (list of int) or (list of list of int), Space Dimension of Hilbert space. If provided as a list of ints, then the dimension is the product over this list, but the ``dims`` property of the new Qobj are set to this list. This can produce either `oper` or `super` depending on the passed `dimensions`. + + dims_right : (int) or (list of int) or (list of list of int), Space, optional + Dimension of right Hilbert space when the operator is rectangular. dtype : type or str, optional Storage representation. Any data-layer known to ``qutip.data.to`` is @@ -670,9 +673,14 @@ def qzero(dimensions, *, dtype=None): """ dtype = dtype or settings.core["default_dtype"] or _data.CSR size, dimensions = _implicit_tensor_dimensions(dimensions) + if dims_right is not None: + size_right, dims_right = _implicit_tensor_dimensions(dims_right) + dimensions = [dimensions[0], dims_right[1]] + else: + size_right = size # A sparse matrix with no data is equal to a zero matrix. type_ = 'super' if isinstance(dimensions[0][0], list) else 'oper' - return Qobj(_data.zeros[dtype](size, size), dims=dimensions, + return Qobj(_data.zeros[dtype](size, size_right), dims=dimensions, isherm=True, isunitary=False, copy=False) @@ -706,7 +714,7 @@ def qeye(dimensions, *, dtype=None): Parameters ---------- - dimensions : (int) or (list of int) or (list of list of int) + dimensions : (int) or (list of int) or (list of list of int), Space Dimension of Hilbert space. If provided as a list of ints, then the dimension is the product over this list, but the ``dims`` property of the new Qobj are set to this list. This can produce either `oper` or diff --git a/qutip/core/states.py b/qutip/core/states.py index 8e891115d2..ac6001781d 100644 --- a/qutip/core/states.py +++ b/qutip/core/states.py @@ -18,7 +18,7 @@ from .qobj import Qobj from .operators import jmat, displace, qdiags from .tensor import tensor -from .dimensions import Space, Dimensions +from .dimensions import Space from .. import settings @@ -153,7 +153,7 @@ def basis(dimensions, n=None, offset=None, *, dtype=None): data = _data.one_element[dtype]((size, 1), (location, 0), 1) return Qobj(data, - dims=Dimensions((dimensions, dimensions.scalar_like())), + dims=[dimensions, dimensions.scalar_like()], isherm=False, isunitary=False, copy=False) diff --git a/qutip/tests/core/test_operators.py b/qutip/tests/core/test_operators.py index 46fa020481..690d879b7e 100644 --- a/qutip/tests/core/test_operators.py +++ b/qutip/tests/core/test_operators.py @@ -133,6 +133,12 @@ def test_implicit_tensor_creation(to_test, dimensions): assert implicit.dims == [dimensions, dimensions] +def test_qzero_rectangular(): + assert qutip.qzero([2, 3], [3, 4]).dims == [[2, 3], [3, 4]] + assert qutip.qzero([2], [3]).dims == [[2], [3]] + assert qutip.qzero([2, 3], [3]).dims == [[2, 3], [3]] + + @pytest.mark.parametrize("to_test", [qutip.qzero, qutip.qeye, qutip.identity]) def test_super_operator_creation(to_test): size = 2 From e4afb64059cf8efec8349e3614a7e595e37840d6 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 10 Jan 2024 13:24:48 -0500 Subject: [PATCH 159/247] Make docstring uniform --- qutip/core/dimensions.py | 2 ++ qutip/core/operators.py | 42 ++++++++++++++---------------- qutip/core/states.py | 14 +++++----- qutip/tests/core/test_operators.py | 16 ++++++++++++ qutip/tests/core/test_states.py | 13 +++++++++ 5 files changed, 58 insertions(+), 29 deletions(-) diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index 38d98f447b..20f5300ea6 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -28,6 +28,8 @@ def flatten(l): [0, 1, 2] """ + if isinstance(l, (Space, Dimensions)): + l = l.as_list() if not isinstance(l, list): return [l] else: diff --git a/qutip/core/operators.py b/qutip/core/operators.py index 118cc35a0a..2197279e57 100644 --- a/qutip/core/operators.py +++ b/qutip/core/operators.py @@ -392,7 +392,7 @@ def destroy(N, offset=0, *, dtype=None): Parameters ---------- N : int - Dimension of Hilbert space. + Number of basis states in the Hilbert space. offset : int, default: 0 The lowest number state that is included in the finite number state @@ -431,7 +431,7 @@ def create(N, offset=0, *, dtype=None): Parameters ---------- N : int - Dimension of Hilbert space. + Number of basis states in the Hilbert space. offset : int, default: 0 The lowest number state that is included in the finite number state @@ -653,13 +653,13 @@ def qzero(dimensions, dims_right=None, *, dtype=None): Parameters ---------- dimensions : (int) or (list of int) or (list of list of int), Space - Dimension of Hilbert space. If provided as a list of ints, then the - dimension is the product over this list, but the ``dims`` property of - the new Qobj are set to this list. This can produce either `oper` or - `super` depending on the passed `dimensions`. + Number of basis states in the Hilbert space. If provided as a list of + ints, then the dimension is the product over this list, but the + ``dims`` property of the new Qobj are set to this list. This can + produce either `oper` or `super` depending on the passed `dimensions`. dims_right : (int) or (list of int) or (list of list of int), Space, optional - Dimension of right Hilbert space when the operator is rectangular. + Number of basis states in the right Hilbert space when the operator is rectangular. dtype : type or str, optional Storage representation. Any data-layer known to ``qutip.data.to`` is @@ -679,7 +679,6 @@ def qzero(dimensions, dims_right=None, *, dtype=None): else: size_right = size # A sparse matrix with no data is equal to a zero matrix. - type_ = 'super' if isinstance(dimensions[0][0], list) else 'oper' return Qobj(_data.zeros[dtype](size, size_right), dims=dimensions, isherm=True, isunitary=False, copy=False) @@ -715,10 +714,10 @@ def qeye(dimensions, *, dtype=None): Parameters ---------- dimensions : (int) or (list of int) or (list of list of int), Space - Dimension of Hilbert space. If provided as a list of ints, then the - dimension is the product over this list, but the ``dims`` property of - the new Qobj are set to this list. This can produce either `oper` or - `super` depending on the passed `dimensions`. + Number of basis states in the Hilbert space. If provided as a list of + ints, then the dimension is the product over this list, but the + ``dims`` property of the new Qobj are set to this list. This can + produce either `oper` or `super` depending on the passed `dimensions`. dtype : type or str, optional Storage representation. Any data-layer known to ``qutip.data.to`` is @@ -750,7 +749,6 @@ def qeye(dimensions, *, dtype=None): """ dtype = dtype or settings.core["default_dtype"] or _data.Dia size, dimensions = _implicit_tensor_dimensions(dimensions) - type_ = 'super' if isinstance(dimensions[0][0], list) else 'oper' return Qobj(_data.identity[dtype](size), dims=dimensions, isherm=True, isunitary=True, copy=False) @@ -791,7 +789,7 @@ def position(N, offset=0, *, dtype=None): Parameters ---------- N : int - Number of Fock states in Hilbert space. + Number of basis states in the Hilbert space. offset : int, default: 0 The lowest number state that is included in the finite number state @@ -819,7 +817,7 @@ def momentum(N, offset=0, *, dtype=None): Parameters ---------- N : int - Number of Fock states in Hilbert space. + Number of basis states in the Hilbert space. offset : int, default: 0 The lowest number state that is included in the finite number state @@ -847,7 +845,7 @@ def num(N, offset=0, *, dtype=None): Parameters ---------- N : int - The dimension of the Hilbert space. + Number of basis states in the Hilbert space. offset : int, default: 0 The lowest number state that is included in the finite number state @@ -954,7 +952,7 @@ def displace(N, alpha, offset=0, *, dtype=None): Parameters ---------- N : int - Dimension of Hilbert space. + Number of basis states in the Hilbert space. alpha : float/complex Displacement amplitude. @@ -1049,7 +1047,7 @@ def phase(N, phi0=0, *, dtype=None): Parameters ---------- N : int - Number of basis states in Hilbert space. + Number of basis states in the Hilbert space. phi0 : float, default: 0 Reference phase. @@ -1124,7 +1122,7 @@ def tunneling(N, m=1, *, dtype=None): Parameters ---------- N : int - Number of basis states in Hilbert space. + Number of basis states in the Hilbert space. m : int, default: 1 Number of excitations in tunneling event. @@ -1151,9 +1149,9 @@ def qft(dimensions, *, dtype="dense"): Parameters ---------- dimensions : (int) or (list of int) or (list of list of int) - Dimension of Hilbert space. If provided as a list of ints, then the - dimension is the product over this list, but the ``dims`` property of - the new Qobj are set to this list. + Number of basis states in the Hilbert space. If provided as a list of + ints, then the dimension is the product over this list, but the + ``dims`` property of the new Qobj are set to this list. dtype : str or type, [keyword only] [optional] Storage representation. Any data-layer known to ``qutip.data.to`` is diff --git a/qutip/core/states.py b/qutip/core/states.py index ac6001781d..980d1b54e4 100644 --- a/qutip/core/states.py +++ b/qutip/core/states.py @@ -278,7 +278,7 @@ def coherent_dm(N, alpha, offset=0, method='operator', *, dtype=None): Parameters ---------- N : int - Number of Fock states in Hilbert space. + Number of basis states in Hilbert space. alpha : float/complex Eigenvalue for coherent state. @@ -335,7 +335,7 @@ def fock_dm(dimensions, n=None, offset=None, *, dtype=None): Parameters ---------- dimensions : int or list of ints, Space - Number of Fock states in Hilbert space. If a list, then the resultant + Number of basis states in Hilbert space. If a list, then the resultant object will be a tensor product over spaces with those dimensions. n : int or list of ints, default: 0 for all dimensions @@ -380,7 +380,7 @@ def fock(dimensions, n=None, offset=None, *, dtype=None): Parameters ---------- dimensions : int or list of ints, Space - Number of Fock states in Hilbert space. If a list, then the resultant + Number of basis states in Hilbert space. If a list, then the resultant object will be a tensor product over spaces with those dimensions. n : int or list of ints, default: 0 for all dimensions @@ -502,7 +502,7 @@ def maximally_mixed_dm(dimensions, *, dtype=None): Parameters ---------- dimensions : int or list of ints, Space - Number of Fock states in Hilbert space. If a list, then the resultant + Number of basis states in Hilbert space. If a list, then the resultant object will be a tensor product over spaces with those dimensions. dtype : type or str, optional @@ -562,7 +562,7 @@ def projection(dimensions, n, m, offset=None, *, dtype=None): Parameters ---------- dimensions : int or list of ints, Space - Number of Fock states in Hilbert space. If a list, then the resultant + Number of basis states in Hilbert space. If a list, then the resultant object will be a tensor product over spaces with those dimensions. n, m : float @@ -960,7 +960,7 @@ def phase_basis(N, m, phi0=0, *, dtype=None): Parameters ---------- N : int - Number of basis vectors in Hilbert space. + Number of basis states in Hilbert space. m : int Integer corresponding to the mth discrete phase @@ -998,7 +998,7 @@ def zero_ket(dimensions, *, dtype=None): Parameters ---------- dimensions : int or list of ints, Space - Number of Fock states in Hilbert space. If a list, then the resultant + Number of basis states in Hilbert space. If a list, then the resultant object will be a tensor product over spaces with those dimensions. dtype : type or str, optional diff --git a/qutip/tests/core/test_operators.py b/qutip/tests/core/test_operators.py index 690d879b7e..1d5584421d 100644 --- a/qutip/tests/core/test_operators.py +++ b/qutip/tests/core/test_operators.py @@ -125,11 +125,14 @@ def test_diagonal_raise(function, message): 1, [1], [1, 1], + qutip.dimensions.Space([2, 3, 4]), ]) def test_implicit_tensor_creation(to_test, dimensions): implicit = to_test(dimensions) if isinstance(dimensions, numbers.Integral): dimensions = [dimensions] + if isinstance(dimensions, qutip.dimensions.Space): + dimensions = dimensions.as_list() assert implicit.dims == [dimensions, dimensions] @@ -137,6 +140,7 @@ def test_qzero_rectangular(): assert qutip.qzero([2, 3], [3, 4]).dims == [[2, 3], [3, 4]] assert qutip.qzero([2], [3]).dims == [[2], [3]] assert qutip.qzero([2, 3], [3]).dims == [[2, 3], [3]] + assert qutip.qzero(qutip.dimensions.Space([2, 3]), qutip.dimensions.Space([3])).dims == [[2, 3], [3]] @pytest.mark.parametrize("to_test", [qutip.qzero, qutip.qeye, qutip.identity]) @@ -371,3 +375,15 @@ def test_fcreate_fdestroy(n_sites): assert qutip.commutator(c_0, d_1, 'anti') == zero_tensor assert qutip.commutator(c_1, d_0, 'anti') == zero_tensor assert qutip.commutator(identity, c_0) == zero_tensor + +@pytest.mark.parametrize(['func', 'args'], [ + (qutip.qzero, (None,)), + (qutip.fock, (None,)), + (qutip.fock_dm, (None,)), + (qutip.maximally_mixed_dm, ()), + (qutip.projection, ([0, 1, 1], [1, 1, 0])), + (qutip.zero_ket, ()), +], ids=_id_func) +def test_state_space_input(func, args): + dims = qutip.dimensions.Space([2, 2, 2]) + assert func(dims, *args) == func([2, 2, 2], *args) \ No newline at end of file diff --git a/qutip/tests/core/test_states.py b/qutip/tests/core/test_states.py index c32d9105d4..6168ddb76c 100644 --- a/qutip/tests/core/test_states.py +++ b/qutip/tests/core/test_states.py @@ -315,3 +315,16 @@ def test_state_type(func, args, alias, dtype): else: for obj in object: assert isinstance(obj.data, dtype) + + +@pytest.mark.parametrize(['func', 'args'], [ + (qutip.basis, (None,)), + (qutip.fock, (None,)), + (qutip.fock_dm, (None,)), + (qutip.maximally_mixed_dm, ()), + (qutip.projection, ([0, 1, 1], [1, 1, 0])), + (qutip.zero_ket, ()), +], ids=_id_func) +def test_state_space_input(func, args): + dims = qutip.dimensions.Space([2, 2, 2]) + assert func(dims, *args) == func([2, 2, 2], *args) \ No newline at end of file From 7c390da7f9e24be27301db11fb19e9fa2e759a0a Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 10 Jan 2024 14:15:53 -0500 Subject: [PATCH 160/247] Rename qubit's N variable name --- qutip/core/states.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/qutip/core/states.py b/qutip/core/states.py index 980d1b54e4..f7a29480a1 100644 --- a/qutip/core/states.py +++ b/qutip/core/states.py @@ -1194,14 +1194,14 @@ def triplet_states(*, dtype=None): ] -def w_state(N, *, dtype=None): +def w_state(N_qubit, *, dtype=None): """ Returns the N-qubit W-state: ``[ |100..0> + |010..0> + |001..0> + ... |000..1> ] / sqrt(n)`` Parameters ---------- - N : int + N_qubit : int Number of qubits in state dtype : type or str, optional @@ -1213,22 +1213,22 @@ def w_state(N, *, dtype=None): W : :obj:`.Qobj` N-qubit W-state """ - inds = np.zeros(N, dtype=int) + inds = np.zeros(N_qubit, dtype=int) inds[0] = 1 - state = basis([2]*N, list(inds), dtype=dtype) - for kk in range(1, N): - state += basis([2]*N, list(np.roll(inds, kk)), dtype=dtype) - return np.sqrt(1 / N) * state + state = basis([2]*N_qubit, list(inds), dtype=dtype) + for kk in range(1, N_qubit): + state += basis([2]*N_qubit, list(np.roll(inds, kk)), dtype=dtype) + return np.sqrt(1 / N_qubit) * state -def ghz_state(N, *, dtype=None): +def ghz_state(N_qubit, *, dtype=None): """ Returns the N-qubit GHZ-state: ``[ |00...00> + |11...11> ] / sqrt(2)`` Parameters ---------- - N : int + N_qubit : int Number of qubits in state dtype : type or str, optional @@ -1240,5 +1240,5 @@ def ghz_state(N, *, dtype=None): G : qobj N-qubit GHZ-state """ - return np.sqrt(0.5) * (basis([2]*N, [0]*N, dtype=dtype) + - basis([2]*N, [1]*N, dtype=dtype)) + return np.sqrt(0.5) * (basis([2]*N_qubit, [0]*N_qubit, dtype=dtype) + + basis([2]*N_qubit, [1]*N_qubit, dtype=dtype)) From 988fb135759fb88873228a84de87dc7df02fa2c5 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 10 Jan 2024 14:38:00 -0500 Subject: [PATCH 161/247] Add towncrier --- doc/changes/2289.misc | 1 + qutip/tests/core/test_operators.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) create mode 100644 doc/changes/2289.misc diff --git a/doc/changes/2289.misc b/doc/changes/2289.misc new file mode 100644 index 0000000000..182a215512 --- /dev/null +++ b/doc/changes/2289.misc @@ -0,0 +1 @@ +Improve states and operator parameters documentation. \ No newline at end of file diff --git a/qutip/tests/core/test_operators.py b/qutip/tests/core/test_operators.py index 1d5584421d..f9db90245a 100644 --- a/qutip/tests/core/test_operators.py +++ b/qutip/tests/core/test_operators.py @@ -386,4 +386,4 @@ def test_fcreate_fdestroy(n_sites): ], ids=_id_func) def test_state_space_input(func, args): dims = qutip.dimensions.Space([2, 2, 2]) - assert func(dims, *args) == func([2, 2, 2], *args) \ No newline at end of file + assert func(dims, *args) == func([2, 2, 2], *args) From 3935254f75cf34dad736c2d7e3a5e0450b19d31e Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 10 Jan 2024 15:04:34 -0500 Subject: [PATCH 162/247] Fix forgotten entry --- qutip/core/states.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/core/states.py b/qutip/core/states.py index f7a29480a1..8df0cb9260 100644 --- a/qutip/core/states.py +++ b/qutip/core/states.py @@ -66,7 +66,7 @@ def basis(dimensions, n=None, offset=None, *, dtype=None): Parameters ---------- dimensions : int or list of ints, Space - Number of Fock states in Hilbert space. If a list, then the resultant + Number of basis states in Hilbert space. If a list, then the resultant object will be a tensor product over spaces with those dimensions. n : int or list of ints, optional (default 0 for all dimensions) From 058bda2a575ecabc9b6e8cdd4dc9f6ec2a10b9e0 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 10 Jan 2024 15:36:51 -0500 Subject: [PATCH 163/247] pep8 --- qutip/core/dimensions.py | 8 ++++---- qutip/core/operators.py | 21 +++++++++++---------- qutip/core/states.py | 18 +++++++++++------- 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index 20f5300ea6..b354ef4010 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -513,7 +513,7 @@ def replace(self, idx, new): def replace_superrep(self, super_rep): return self - + def scalar_like(self): return Field() @@ -657,7 +657,7 @@ def replace_superrep(self, super_rep): return Compound( *[space.replace_superrep(super_rep) for space in self.spaces] ) - + def scalar_like(self): return [space.scalar_like() for space in self.spaces] @@ -721,7 +721,7 @@ def replace(self, idx, new): def replace_superrep(self, super_rep): return SuperSpace(self.oper, rep=super_rep) - + def scalar_like(self): return self.oper.scalar_like() @@ -921,6 +921,6 @@ def replace_superrep(self, super_rep): self.from_.replace_superrep(super_rep), self.to_.replace_superrep(super_rep) ) - + def scalar_like(self): return [self.to_.scalar_like(), self.from_.scalar_like()] diff --git a/qutip/core/operators.py b/qutip/core/operators.py index 2197279e57..700aec1bcd 100644 --- a/qutip/core/operators.py +++ b/qutip/core/operators.py @@ -652,14 +652,15 @@ def qzero(dimensions, dims_right=None, *, dtype=None): Parameters ---------- - dimensions : (int) or (list of int) or (list of list of int), Space - Number of basis states in the Hilbert space. If provided as a list of - ints, then the dimension is the product over this list, but the - ``dims`` property of the new Qobj are set to this list. This can + dimensions : int, list of int, list of list of int, Space + Number of basis states in the Hilbert space. If provided as a list of + ints, then the dimension is the product over this list, but the + ``dims`` property of the new Qobj are set to this list. This can produce either `oper` or `super` depending on the passed `dimensions`. - - dims_right : (int) or (list of int) or (list of list of int), Space, optional - Number of basis states in the right Hilbert space when the operator is rectangular. + + dims_right : int, list of int, list of list of int, Space, optional + Number of basis states in the right Hilbert space when the operator is + rectangular. dtype : type or str, optional Storage representation. Any data-layer known to ``qutip.data.to`` is @@ -715,8 +716,8 @@ def qeye(dimensions, *, dtype=None): ---------- dimensions : (int) or (list of int) or (list of list of int), Space Number of basis states in the Hilbert space. If provided as a list of - ints, then the dimension is the product over this list, but the - ``dims`` property of the new Qobj are set to this list. This can + ints, then the dimension is the product over this list, but the + ``dims`` property of the new Qobj are set to this list. This can produce either `oper` or `super` depending on the passed `dimensions`. dtype : type or str, optional @@ -1150,7 +1151,7 @@ def qft(dimensions, *, dtype="dense"): ---------- dimensions : (int) or (list of int) or (list of list of int) Number of basis states in the Hilbert space. If provided as a list of - ints, then the dimension is the product over this list, but the + ints, then the dimension is the product over this list, but the ``dims`` property of the new Qobj are set to this list. dtype : str or type, [keyword only] [optional] diff --git a/qutip/core/states.py b/qutip/core/states.py index 8df0cb9260..6e29584a4d 100644 --- a/qutip/core/states.py +++ b/qutip/core/states.py @@ -130,11 +130,13 @@ def basis(dimensions, n=None, offset=None, *, dtype=None): if n is None: location = 0 elif offset: - if not isinstance(offset, list): offset = [offset] - if not isinstance(n, list): n = [n] + if not isinstance(offset, list): + offset = [offset] + if not isinstance(n, list): + n = [n] if len(n) != len(dimensions.as_list()) or len(offset) != len(n): raise ValueError("All list inputs must be the same length.") - + n_off = [m-off for m, off in zip(n, offset)] try: location = dimensions.dims2idx(n_off) @@ -142,7 +144,8 @@ def basis(dimensions, n=None, offset=None, *, dtype=None): raise ValueError("All basis indices must be integers in the range " "`offset <= n < dimension+offset`.") else: - if not isinstance(n, list): n = [n] + if not isinstance(n, list): + n = [n] if len(n) != len(dimensions.as_list()): raise ValueError("All list inputs must be the same length.") try: @@ -517,8 +520,9 @@ def maximally_mixed_dm(dimensions, *, dtype=None): dtype = dtype or settings.core["default_dtype"] or _data.Dia dimensions = _to_space(dimensions) N = dimensions.size - - return Qobj(_data.identity[dtype](N, scale=1/N), dims=[dimensions, dimensions], + + return Qobj(_data.identity[dtype](N, scale=1/N), + dims=[dimensions, dimensions], isherm=True, isunitary=(N == 1), copy=False) @@ -1014,7 +1018,7 @@ def zero_ket(dimensions, *, dtype=None): dtype = dtype or settings.core["default_dtype"] or _data.Dense dimensions = _to_space(dimensions) N = dimensions.size - return Qobj(_data.zeros[dtype](N, 1), + return Qobj(_data.zeros[dtype](N, 1), dims=[dimensions, dimensions.scalar_like()], copy=False) From f8ac0d19d1eedb1f519f70610c7fa44d2a893381 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 11 Jan 2024 14:26:25 -0500 Subject: [PATCH 164/247] Remove bloch3d --- doc/QuTiP_tree_plot/qutip-structure.py | 2 +- qutip/__init__.py | 1 - qutip/bloch3d.py | 516 ------------------------- 3 files changed, 1 insertion(+), 518 deletions(-) delete mode 100644 qutip/bloch3d.py diff --git a/doc/QuTiP_tree_plot/qutip-structure.py b/doc/QuTiP_tree_plot/qutip-structure.py index 7234598fba..b112303b0f 100755 --- a/doc/QuTiP_tree_plot/qutip-structure.py +++ b/doc/QuTiP_tree_plot/qutip-structure.py @@ -37,7 +37,7 @@ ("#043c6b", {"settings", "configrc", "solver"}), # Visualisation ("#3f8fd2", { - "bloch", "bloch3d", "sphereplot", "orbital", "visualization", "wigner", + "bloch", "sphereplot", "orbital", "visualization", "wigner", "distributions", "tomography", "topology", }), # Operators diff --git a/qutip/__init__.py b/qutip/__init__.py index ff8d76a7e8..19f373c8b1 100644 --- a/qutip/__init__.py +++ b/qutip/__init__.py @@ -39,7 +39,6 @@ from .bloch import * from .visualization import * from .animation import * -from .bloch3d import * from .matplotlib_utilities import * # library functions diff --git a/qutip/bloch3d.py b/qutip/bloch3d.py deleted file mode 100644 index 160b9662e1..0000000000 --- a/qutip/bloch3d.py +++ /dev/null @@ -1,516 +0,0 @@ -__all__ = ['Bloch3d'] - -import numpy as np -from . import Qobj, expect, sigmax, sigmay, sigmaz - - -class Bloch3d: - """Class for plotting data on a 3D Bloch sphere using mayavi. - Valid data can be either points, vectors, or qobj objects - corresponding to state vectors or density matrices. for - a two-state system (or subsystem). - - Attributes - ---------- - fig : instance {None} - User supplied Matplotlib Figure instance for plotting Bloch sphere. - font_color : str {'black'} - Color of font used for Bloch sphere labels. - font_scale : float {0.08} - Scale for font used for Bloch sphere labels. - frame : bool {True} - Draw frame for Bloch sphere - frame_alpha : float {0.05} - Sets transparency of Bloch sphere frame. - frame_color : str {'gray'} - Color of sphere wireframe. - frame_num : int {8} - Number of frame elements to draw. - frame_radius : floats {0.005} - Width of wireframe. - point_color : list {['r', 'g', 'b', 'y']} - List of colors for Bloch sphere point markers to cycle through. - i.e. By default, points 0 and 4 will both be blue ('r'). - point_mode : string {'sphere','cone','cube','cylinder','point'} - Point marker shapes. - point_size : float {0.075} - Size of points on Bloch sphere. - sphere_alpha : float {0.1} - Transparency of Bloch sphere itself. - sphere_color : str {'#808080'} - Color of Bloch sphere. - size : list {[500, 500]} - Size of Bloch sphere plot in pixels. Best to have both numbers the same - otherwise you will have a Bloch sphere that looks like a football. - vector_color : list {['r', 'g', 'b', 'y']} - List of vector colors to cycle through. - vector_width : int {3} - Width of displayed vectors. - view : list {[45,65]} - Azimuthal and Elevation viewing angles. - xlabel : list {['\|x>', '']} - List of strings corresponding to +x and -x axes labels, respectively. - xlpos : list {[1.07,-1.07]} - Positions of +x and -x labels respectively. - ylabel : list {['\|y>', '']} - List of strings corresponding to +y and -y axes labels, respectively. - ylpos : list {[1.07,-1.07]} - Positions of +y and -y labels respectively. - zlabel : list {["\|0>", '\|1>']} - List of strings corresponding to +z and -z axes labels, respectively. - zlpos : list {[1.07,-1.07]} - Positions of +z and -z labels respectively. - - Notes - ----- - The use of mayavi for 3D rendering of the Bloch sphere comes with - a few limitations: I) You can not embed a Bloch3d figure into a - matplotlib window. II) The use of LaTex is not supported by the - mayavi rendering engine. Therefore all labels must be defined using - standard text. Of course you can post-process the generated figures - later to add LaTeX using other software if needed. - - """ - def __init__(self, fig=None): - # ----check for mayavi----- - try: - from mayavi import mlab - except: - raise Exception("This function requires the mayavi module.") - - # ---Image options--- - self.fig = None - self.user_fig = None - # check if user specified figure or axes. - if fig: - self.user_fig = fig - # The size of the figure in inches, default = [500,500]. - self.size = [500, 500] - # Azimuthal and Elvation viewing angles, default = [45,65]. - self.view = [45, 65] - # Image background color - self.bgcolor = 'white' - # Image foreground color. Other options can override. - self.fgcolor = 'black' - - # ---Sphere options--- - # Color of Bloch sphere, default = #808080 - self.sphere_color = '#808080' - # Transparency of Bloch sphere, default = 0.1 - self.sphere_alpha = 0.1 - - # ---Frame options--- - # Draw frame? - self.frame = True - # number of lines to draw for frame - self.frame_num = 8 - # Color of wireframe, default = 'gray' - self.frame_color = 'black' - # Transparency of wireframe, default = 0.2 - self.frame_alpha = 0.05 - # Radius of frame lines - self.frame_radius = 0.005 - - # --Axes--- - # Axes color - self.axes_color = 'black' - # Transparency of axes - self.axes_alpha = 0.4 - # Radius of axes lines - self.axes_radius = 0.005 - - # ---Labels--- - # Labels for x-axis (in LaTex), default = ['$x$',''] - self.xlabel = ['|x>', ''] - # Position of x-axis labels, default = [1.2,-1.2] - self.xlpos = [1.07, -1.07] - # Labels for y-axis (in LaTex), default = ['$y$',''] - self.ylabel = ['|y>', ''] - # Position of y-axis labels, default = [1.1,-1.1] - self.ylpos = [1.07, -1.07] - # Labels for z-axis - self.zlabel = ['|0>', '|1>'] - # Position of z-axis labels, default = [1.05,-1.05] - self.zlpos = [1.07, -1.07] - - # ---Font options--- - # Color of fonts, default = 'black' - self.font_color = 'black' - # Size of fonts, default = 20 - self.font_scale = 0.08 - - # ---Vector options--- - # Object used for representing vectors on Bloch sphere. - # List of colors for Bloch vectors, default = ['b','g','r','y'] - self.vector_color = ['r', 'g', 'b', 'y'] - # Width of Bloch vectors, default = 2 - self.vector_width = 2.0 - # Height of vector head - self.vector_head_height = 0.15 - # Radius of vector head - self.vector_head_radius = 0.075 - - # ---Point options--- - # List of colors for Bloch point markers, default = ['b','g','r','y'] - self.point_color = ['r', 'g', 'b', 'y'] - # Size of point markers - self.point_size = 0.06 - # Shape of point markers - # Options: 'cone' or 'cube' or 'cylinder' or 'point' or 'sphere'. - # Default = 'sphere' - self.point_mode = 'sphere' - - # ---Data lists--- - # Data for point markers - self.points = [] - # Data for Bloch vectors - self.vectors = [] - # Number of times sphere has been saved - self.savenum = 0 - # Style of points, 'm' for multiple colors, 's' for single color - self.point_style = [] - # Transparency of points - self.point_alpha = [] - # Transparency of vectors - self.vector_alpha = [] - - def __str__(self): - s = "" - s += "Bloch3D data:\n" - s += "-----------\n" - s += "Number of points: " + str(len(self.points)) + "\n" - s += "Number of vectors: " + str(len(self.vectors)) + "\n" - s += "\n" - s += "Bloch3D sphere properties:\n" - s += "--------------------------\n" - s += "axes_alpha: " + str(self.axes_alpha) + "\n" - s += "axes_color: " + str(self.axes_color) + "\n" - s += "axes_radius: " + str(self.axes_radius) + "\n" - s += "bgcolor: " + str(self.bgcolor) + "\n" - s += "fgcolor: " + str(self.fgcolor) + "\n" - s += "font_color: " + str(self.font_color) + "\n" - s += "font_scale: " + str(self.font_scale) + "\n" - s += "frame: " + str(self.frame) + "\n" - s += "frame_alpha: " + str(self.frame_alpha) + "\n" - s += "frame_color: " + str(self.frame_color) + "\n" - s += "frame_num: " + str(self.frame_num) + "\n" - s += "frame_radius: " + str(self.frame_radius) + "\n" - s += "point_color: " + str(self.point_color) + "\n" - s += "point_mode: " + str(self.point_mode) + "\n" - s += "point_size: " + str(self.point_size) + "\n" - s += "sphere_alpha: " + str(self.sphere_alpha) + "\n" - s += "sphere_color: " + str(self.sphere_color) + "\n" - s += "size: " + str(self.size) + "\n" - s += "vector_color: " + str(self.vector_color) + "\n" - s += "vector_width: " + str(self.vector_width) + "\n" - s += "vector_head_height: " + str(self.vector_head_height) + "\n" - s += "vector_head_radius: " + str(self.vector_head_radius) + "\n" - s += "view: " + str(self.view) + "\n" - s += "xlabel: " + str(self.xlabel) + "\n" - s += "xlpos: " + str(self.xlpos) + "\n" - s += "ylabel: " + str(self.ylabel) + "\n" - s += "ylpos: " + str(self.ylpos) + "\n" - s += "zlabel: " + str(self.zlabel) + "\n" - s += "zlpos: " + str(self.zlpos) + "\n" - return s - - def clear(self): - """Resets the Bloch sphere data sets to empty. - """ - self.points = [] - self.vectors = [] - self.point_style = [] - - def add_points(self, points, meth='s', alpha=1.0): - """Add a list of data points to bloch sphere. - - Parameters - ---------- - points : array/list - Collection of data points. - - meth : str {'s','m'} - Type of points to plot, use 'm' for multicolored. - - alpha : float, default=1. - Transparency value for the vectors. Values between 0 and 1. - - """ - if not isinstance(points[0], (list, np.ndarray)): - points = [[points[0]], [points[1]], [points[2]]] - points = np.array(points) - if meth == 's': - if len(points[0]) == 1: - pnts = np.array( - [[points[0][0]], [points[1][0]], [points[2][0]]]) - pnts = np.append(pnts, points, axis=1) - else: - pnts = points - self.points.append(pnts) - self.point_style.append('s') - else: - self.points.append(points) - self.point_style.append('m') - self.point_alpha.append(alpha) - - def add_states(self, state, kind='vector', alpha=1.0): - """Add a state vector Qobj to Bloch sphere. - - Parameters - ---------- - state : qobj - Input state vector. - - kind : str {'vector','point'} - Type of object to plot. - - alpha : float, default=1. - Transparency value for the vectors. Values between 0 and 1. - """ - if isinstance(state, Qobj): - state = [state] - for st in state: - if kind == 'vector': - vec = [expect(sigmax(), st), expect(sigmay(), st), - expect(sigmaz(), st)] - self.add_vectors(vec, alpha=alpha) - elif kind == 'point': - pnt = [expect(sigmax(), st), expect(sigmay(), st), - expect(sigmaz(), st)] - self.add_points(pnt, alpha=alpha) - - def add_vectors(self, vectors, alpha=1.0): - """Add a list of vectors to Bloch sphere. - - Parameters - ---------- - vectors : array/list - Array with vectors of unit length or smaller. - - alpha : float, default=1. - Transparency value for the vectors. Values between 0 and 1. - - """ - if isinstance(vectors[0], (list, np.ndarray)): - for vec in vectors: - self.vectors.append(vec) - self.vector_alpha.append(alpha) - else: - self.vectors.append(vectors) - self.vector_alpha.append(alpha) - - def plot_vectors(self): - """ - Plots vectors on the Bloch sphere. - """ - from mayavi import mlab - from tvtk.api import tvtk - import matplotlib.colors as colors - ii = 0 - for k in range(len(self.vectors)): - vec = np.array(self.vectors[k]) - norm = np.linalg.norm(vec) - theta = np.arccos(vec[2] / norm) - phi = np.arctan2(vec[1], vec[0]) - vec -= 0.5 * self.vector_head_height * \ - np.array([np.sin(theta) * np.cos(phi), - np.sin(theta) * np.sin(phi), np.cos(theta)]) - - color = colors.colorConverter.to_rgb( - self.vector_color[np.mod(k, len(self.vector_color))]) - - mlab.plot3d([0, vec[0]], [0, vec[1]], [0, vec[2]], - name='vector' + str(ii), tube_sides=100, - line_width=self.vector_width, - opacity=self.vector_alpha[k], - color=color) - - cone = tvtk.ConeSource(height=self.vector_head_height, - radius=self.vector_head_radius, - resolution=100) - cone_mapper = tvtk.PolyDataMapper( - input_connection=cone.output_port) - prop = tvtk.Property(opacity=self.vector_alpha[k], color=color) - cc = tvtk.Actor(mapper=cone_mapper, property=prop) - cc.rotate_z(np.degrees(phi)) - cc.rotate_y(-90 + np.degrees(theta)) - cc.position = vec - self.fig.scene.add_actor(cc) - - def plot_points(self): - """ - Plots points on the Bloch sphere. - """ - from mayavi import mlab - import matplotlib.colors as colors - for k in range(len(self.points)): - num = len(self.points[k][0]) - dist = [np.sqrt(self.points[k][0][j] ** 2 + - self.points[k][1][j] ** 2 + - self.points[k][2][j] ** 2) for j in range(num)] - if any(abs(dist - dist[0]) / dist[0] > 1e-12): - # combine arrays so that they can be sorted together - # and sort rates from lowest to highest - zipped = sorted(zip(dist, range(num))) - dist, indperm = zip(*zipped) - indperm = np.array(indperm) - else: - indperm = range(num) - if self.point_style[k] == 's': - color = colors.colorConverter.to_rgb( - self.point_color[np.mod(k, len(self.point_color))]) - mlab.points3d( - self.points[k][0][indperm], self.points[k][1][indperm], - self.points[k][2][indperm], figure=self.fig, - resolution=100, scale_factor=self.point_size, - mode=self.point_mode, color=color, - opacity=self.point_alpha[k]) - - elif self.point_style[k] == 'm': - pnt_colors = np.array(self.point_color * np.ceil( - num / float(len(self.point_color)))) - pnt_colors = pnt_colors[0:num] - pnt_colors = list(pnt_colors[indperm]) - for kk in range(num): - mlab.points3d( - self.points[k][0][ - indperm[kk]], self.points[k][1][indperm[kk]], - self.points[k][2][ - indperm[kk]], figure=self.fig, resolution=100, - scale_factor=self.point_size, mode=self.point_mode, - color=colors.colorConverter.to_rgb(pnt_colors[kk]), - opacity=self.point_alpha[k]) - - def make_sphere(self): - """ - Plots Bloch sphere and data sets. - """ - # setup plot - # Figure instance for Bloch sphere plot - from mayavi import mlab - import matplotlib.colors as colors - if self.user_fig: - self.fig = self.user_fig - else: - self.fig = mlab.figure( - 1, size=self.size, - bgcolor=colors.colorConverter.to_rgb(self.bgcolor), - fgcolor=colors.colorConverter.to_rgb(self.fgcolor)) - - sphere = mlab.points3d( - 0, 0, 0, figure=self.fig, scale_mode='none', scale_factor=2, - color=colors.colorConverter.to_rgb(self.sphere_color), - resolution=100, opacity=self.sphere_alpha, name='bloch_sphere') - - # Thse commands make the sphere look better - sphere.actor.property.specular = 0.45 - sphere.actor.property.specular_power = 5 - sphere.actor.property.backface_culling = True - - # make frame for sphere surface - if self.frame: - theta = np.linspace(0, 2 * np.pi, 100) - for angle in np.linspace(-np.pi, np.pi, self.frame_num): - xlat = np.cos(theta) * np.cos(angle) - ylat = np.sin(theta) * np.cos(angle) - zlat = np.ones_like(theta) * np.sin(angle) - xlon = np.sin(angle) * np.sin(theta) - ylon = np.cos(angle) * np.sin(theta) - zlon = np.cos(theta) - mlab.plot3d( - xlat, ylat, zlat, - color=colors.colorConverter.to_rgb(self.frame_color), - opacity=self.frame_alpha, tube_radius=self.frame_radius) - mlab.plot3d( - xlon, ylon, zlon, - color=colors.colorConverter.to_rgb(self.frame_color), - opacity=self.frame_alpha, tube_radius=self.frame_radius) - - # add axes - axis = np.linspace(-1.0, 1.0, 10) - other = np.zeros_like(axis) - mlab.plot3d( - axis, other, other, - color=colors.colorConverter.to_rgb(self.axes_color), - tube_radius=self.axes_radius, opacity=self.axes_alpha) - mlab.plot3d( - other, axis, other, - color=colors.colorConverter.to_rgb(self.axes_color), - tube_radius=self.axes_radius, opacity=self.axes_alpha) - mlab.plot3d( - other, other, axis, - color=colors.colorConverter.to_rgb(self.axes_color), - tube_radius=self.axes_radius, opacity=self.axes_alpha) - - # add data to sphere - self.plot_points() - self.plot_vectors() - - # #add labels - mlab.text3d(0, 0, self.zlpos[0], self.zlabel[0], - color=colors.colorConverter.to_rgb(self.font_color), - scale=self.font_scale) - mlab.text3d(0, 0, self.zlpos[1], self.zlabel[1], - color=colors.colorConverter.to_rgb(self.font_color), - scale=self.font_scale) - mlab.text3d(self.xlpos[0], 0, 0, self.xlabel[0], - color=colors.colorConverter.to_rgb(self.font_color), - scale=self.font_scale) - mlab.text3d(self.xlpos[1], 0, 0, self.xlabel[1], - color=colors.colorConverter.to_rgb(self.font_color), - scale=self.font_scale) - mlab.text3d(0, self.ylpos[0], 0, self.ylabel[0], - color=colors.colorConverter.to_rgb(self.font_color), - scale=self.font_scale) - mlab.text3d(0, self.ylpos[1], 0, self.ylabel[1], - color=colors.colorConverter.to_rgb(self.font_color), - scale=self.font_scale) - - def show(self): - """ - Display the Bloch sphere and corresponding data sets. - """ - from mayavi import mlab - self.make_sphere() - mlab.view(azimuth=self.view[0], elevation=self.view[1], distance=5) - if self.fig: - mlab.show() - - def save(self, name=None, format='png', dirc=None): - """Saves Bloch sphere to file of type ``format`` in directory ``dirc``. - - Parameters - ---------- - name : str - Name of saved image. Must include path and format as well. - i.e. '/Users/Me/Desktop/bloch.png' - This overrides the 'format' and 'dirc' arguments. - format : str - Format of output image. Default is 'png'. - dirc : str - Directory for output images. Defaults to current working directory. - - Returns - ------- - File containing plot of Bloch sphere. - - """ - from mayavi import mlab - import os - self.make_sphere() - mlab.view(azimuth=self.view[0], elevation=self.view[1], distance=5) - if dirc: - if not os.path.isdir(os.getcwd() + "/" + str(dirc)): - os.makedirs(os.getcwd() + "/" + str(dirc)) - if name is None: - if dirc: - mlab.savefig(os.getcwd() + "/" + str(dirc) + '/bloch_' + - str(self.savenum) + '.' + format) - else: - mlab.savefig(os.getcwd() + '/bloch_' + str(self.savenum) + - '.' + format) - else: - mlab.savefig(name) - self.savenum += 1 - if self.fig: - mlab.close(self.fig) From 60ba0a25798e27b0be550158e789850136c1b45a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Jan 2024 20:44:29 +0000 Subject: [PATCH 165/247] Bump jinja2 from 3.1.2 to 3.1.3 in /doc Bumps [jinja2](https://github.com/pallets/jinja) from 3.1.2 to 3.1.3. - [Release notes](https://github.com/pallets/jinja/releases) - [Changelog](https://github.com/pallets/jinja/blob/main/CHANGES.rst) - [Commits](https://github.com/pallets/jinja/compare/3.1.2...3.1.3) --- updated-dependencies: - dependency-name: jinja2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index 7cf91640dd..aa58f0e06e 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -11,7 +11,7 @@ idna==3.4 imagesize==1.4.1 ipython==8.11.0 jedi==0.18.2 -Jinja2==3.1.2 +Jinja2==3.1.3 kiwisolver==1.4.4 MarkupSafe==2.1.2 matplotlib==3.7.1 From 2d0556345b7b36c131864bce0080c460455d7eb4 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 12 Jan 2024 17:45:11 -0500 Subject: [PATCH 166/247] Faster Qobj.matmul --- qutip/core/dimensions.py | 19 +++++++++++++++++++ qutip/core/qobj.py | 41 ++++++++++++++++------------------------ 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index 7e0f17cd57..7e7c4538d1 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -783,6 +783,25 @@ def __eq__(self, other): ) return NotImplemented + def __ne__(self, other): + if isinstance(other, Dimensions): + return not ( + self is other + or ( + self.to_ == other.to_ + and self.from_ == other.from_ + ) + ) + return NotImplemented + + def __matmul__(self, other): + if self.from_ != other.to_: + raise TypeError(f"incompatible dimensions {self} and {other}") + args = other.from_, self.to_ + if args in Dimensions._stored_dims: + return Dimensions._stored_dims[args] + return Dimensions(*args) + def __hash__(self): return hash((self.to_, self.from_)) diff --git a/qutip/core/qobj.py b/qutip/core/qobj.py index d42e08b9bc..9c1692df4d 100644 --- a/qutip/core/qobj.py +++ b/qutip/core/qobj.py @@ -377,12 +377,11 @@ def to(self, data_type): converter = _data.to[data_type] except (KeyError, TypeError): raise ValueError("Unknown conversion type: " + str(data_type)) - if type(self.data) is data_type: + if type(self._data) is data_type: return self return Qobj(converter(self._data), dims=self._dims, isherm=self._isherm, - superrep=self.superrep, isunitary=self._isunitary, copy=False) @@ -441,7 +440,6 @@ def __mul__(self, other): return Qobj(out, dims=self._dims, isherm=isherm, - superrep=self.superrep, isunitary=isunitary, copy=False) @@ -456,23 +454,16 @@ def __matmul__(self, other): other = Qobj(other) except TypeError: return NotImplemented - if self._dims[1] != other._dims[0]: - raise TypeError("".join([ - "incompatible dimensions ", - repr(self.dims), - " and ", - repr(other.dims), - ])) - if ( - (self.isbra and other.isket) - or (self.isoperbra and other.isoperket) - ): - return _data.inner(self.data, other.data) + new_dims = self._dims @ other._dims + if new_dims.type == 'scalar': + return _data.inner(self._data, other._data) - return Qobj(_data.matmul(self.data, other.data), - dims=Dimensions(other._dims[1], self._dims[0]), - isunitary=self._isunitary and other._isunitary, - copy=False) + return Qobj( + _data.matmul(self._data, other._data), + dims=new_dims, + isunitary=self._isunitary and other._isunitary, + copy=False + ) def __truediv__(self, other): return self.__mul__(1 / other) @@ -526,7 +517,7 @@ def __pow__(self, n, m=None): # calculates powers of Qobj def _str_header(self): out = ", ".join([ "Quantum object: dims=" + str(self.dims), - "shape=" + str(self.data.shape), + "shape=" + str(self._data.shape), "type=" + repr(self.type), ]) if self.type in ('oper', 'super'): @@ -701,7 +692,7 @@ def norm(self, norm=None, kwargs=None): "vector norm must be in " + repr(_NORM_ALLOWED_VECTOR) ) kwargs = kwargs or {} - return _NORM_FUNCTION_LOOKUP[norm](self.data, **kwargs) + return _NORM_FUNCTION_LOOKUP[norm](self._data, **kwargs) def proj(self): """Form the projector from a given ket or bra vector. @@ -755,8 +746,8 @@ def purity(self): if self.type in ("super", "operator-ket", "operator-bra"): raise TypeError('purity is only defined for states.') if self.isket or self.isbra: - return _data.norm.l2(self.data)**2 - return _data.trace(_data.matmul(self.data, self.data)).real + return _data.norm.l2(self._data)**2 + return _data.trace(_data.matmul(self._data, self._data)).real def full(self, order='C', squeeze=False): """Dense array from quantum object. @@ -794,7 +785,7 @@ def data_as(self, format=None, copy=True): data : numpy.ndarray, scipy.sparse.matrix_csr, etc. Matrix in the type of the underlying libraries. """ - return _data.extract(self.data, format, copy) + return _data.extract(self._data, format, copy) def diag(self): """Diagonal elements of quantum object. @@ -1733,7 +1724,7 @@ def isunitary(self): return self._isunitary @property - def shape(self): return self.data.shape + def shape(self): return self._data.shape isbra = property(isbra) isket = property(isket) From e377795a7e83e888e939e210808dfdfb8f21d095 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Tue, 16 Jan 2024 13:40:18 +0900 Subject: [PATCH 167/247] Fix mistake made in merge --- qutip/solver/parallel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index 5a64468963..50e0df3977 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -188,7 +188,7 @@ def _done_callback(future): if isinstance(ex, Exception): errors[future._i] = ex else: - result = future.result() + result = extract_result(future._i, future) remaining_ntraj = result_func(future._i, result) if remaining_ntraj is not None and remaining_ntraj <= 0: finished.append(True) From 86605cc187c6f7e78f2902582049d33eb53d39ef Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Tue, 16 Jan 2024 14:53:01 +0900 Subject: [PATCH 168/247] Fixed bug in handling ShutdownExecutorError --- qutip/solver/parallel.py | 102 +++++++++++++++++++++------------------ 1 file changed, 55 insertions(+), 47 deletions(-) diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index 50e0df3977..2d9a1aead5 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -3,7 +3,7 @@ mappings, using the builtin Python module multiprocessing or the loky parallel execution library. """ -__all__ = ['parallel_map', 'serial_map', 'loky_pmap'] +__all__ = ['parallel_map', 'serial_map', 'loky_pmap', 'mpi_pmap'] import multiprocessing import os @@ -131,21 +131,20 @@ def serial_map(task, values, task_args=None, task_kwargs=None, return results -def _generic_pmap(task, values, task_args, task_kwargs, - reduce_func, timeout, fail_fast, +def _generic_pmap(task, values, task_args, task_kwargs, reduce_func, + timeout, fail_fast, num_workers, progress_bar, progress_bar_kwargs, setup_executor, extract_result, shutdown_executor): """ Common functionality for parallel_map, loky_pmap and mpi_pmap. - The parameters `setup_executor`, `extract_value` and `destroy_executor` + The parameters `setup_executor`, `extract_result` and `shutdown_executor` are callback functions with the following signatures: - setup_executor: () -> ProcessPoolExecutor, int - The second return value specifies the number of workers + setup_executor: () -> ProcessPoolExecutor - extract_result: (index: int, future: Future) -> Any - index: Corresponds to the indices of the `values` list - future: The Future that has finished running + extract_result: Future -> (Any, BaseException) + If there was an exception e, returns (None, e). + Otherwise returns (result, None). shutdown_executor: (executor: ProcessPoolExecutor, active_tasks: set[Future]) -> None @@ -177,18 +176,17 @@ def result_func(_, value): def _done_callback(future): if not future.cancelled(): - ex = future.exception() - if isinstance(ex, KeyboardInterrupt): + result, exception = extract_result(future) + if isinstance(exception, KeyboardInterrupt): # When a keyboard interrupt happens, it is raised in the main # thread and in all worker threads. At this point in the code, # the worker threads have already returned and the main thread # is only waiting for the ProcessPoolExecutor to shutdown # before exiting. We therefore return immediately. return - if isinstance(ex, Exception): - errors[future._i] = ex + if isinstance(exception, Exception): + errors[future._i] = exception else: - result = extract_result(future._i, future) remaining_ntraj = result_func(future._i, result) if remaining_ntraj is not None and remaining_ntraj <= 0: finished.append(True) @@ -196,8 +194,7 @@ def _done_callback(future): os.environ['QUTIP_IN_PARALLEL'] = 'TRUE' try: - executor, num_workers = setup_executor() - with executor: + with setup_executor() as executor: waiting = set() i = 0 aborted = False @@ -317,24 +314,27 @@ def parallel_map(task, values, task_args=None, task_kwargs=None, ctx_kw = {} def setup_executor(): - num_workers = map_kw['num_cpus'] - executor = concurrent.futures.ProcessPoolExecutor( - max_workers=num_workers, **ctx_kw, + return concurrent.futures.ProcessPoolExecutor( + max_workers=map_kw['num_cpus'], **ctx_kw, ) - return executor, num_workers - def extract_result (_, future): - return future.result() + def extract_result (future: concurrent.futures.Future): + exception = future.exception() + if exception is not None: + return None, exception + return future.result(), None def shutdown_executor(executor, _): # Since `ProcessPoolExecutor` leaves no other option, # we wait for all worker processes to finish their current task executor.shutdown() - return _generic_pmap(task, values, task_args, task_kwargs, - reduce_func, map_kw['timeout'], map_kw['fail_fast'], - progress_bar, progress_bar_kwargs, - setup_executor, extract_result, shutdown_executor) + return _generic_pmap( + task, values, task_args, task_kwargs, reduce_func, + map_kw['timeout'], map_kw['fail_fast'], map_kw['num_cpus'], + progress_bar, progress_bar_kwargs, + setup_executor, extract_result, shutdown_executor + ) def loky_pmap(task, values, task_args=None, task_kwargs=None, @@ -390,26 +390,30 @@ def loky_pmap(task, values, task_args=None, task_kwargs=None, map_kw = _read_map_kw(map_kw) def setup_executor(): - num_workers = map_kw['num_cpus'] - executor = get_reusable_executor(max_workers=num_workers) - return executor, num_workers + return get_reusable_executor(max_workers=map_kw['num_cpus']) - def extract_result (_, future: concurrent.futures.Future): - if isinstance(future.exception(), ShutdownExecutorError): + def extract_result (future: concurrent.futures.Future): + exception = future.exception() + if isinstance(exception, ShutdownExecutorError): # Task was aborted due to timeout etc - return None - return future.result() + return None, None + if exception is not None: + return None, exception + return future.result(), None def shutdown_executor(executor, active_tasks): # If there are still tasks running, we kill all workers in order to # return immediately. Otherwise, `kill_workers` is set to False so # that the worker threads can be reused in subsequent loky_pmap calls. - executor.shutdown(kill_workers=(len(active_tasks) > 0)) - - return _generic_pmap(task, values, task_args, task_kwargs, - reduce_func, map_kw['timeout'], map_kw['fail_fast'], - progress_bar, progress_bar_kwargs, - setup_executor, extract_result, shutdown_executor) + kill_workers = len(active_tasks) > 0 + executor.shutdown(kill_workers=kill_workers) + + return _generic_pmap( + task, values, task_args, task_kwargs, reduce_func, + map_kw['timeout'], map_kw['fail_fast'], map_kw['num_cpus'], + progress_bar, progress_bar_kwargs, + setup_executor, extract_result, shutdown_executor + ) def mpi_pmap(task, values, task_args=None, task_kwargs=None, @@ -471,19 +475,23 @@ def mpi_pmap(task, values, task_args=None, task_kwargs=None, fail_fast = map_kw.pop('fail_fast') def setup_executor(): - executor = MPIPoolExecutor(max_workers=num_workers, **map_kw) - return executor, num_workers + return MPIPoolExecutor(max_workers=num_workers, **map_kw) - def extract_result (_, future): - return future.result() + def extract_result (future): + exception = future.exception() + if exception is not None: + return None, exception + return future.result(), None def shutdown_executor(executor, _): executor.shutdown() - return _generic_pmap(task, values, task_args, task_kwargs, - reduce_func, timeout, fail_fast, - progress_bar, progress_bar_kwargs, - setup_executor, extract_result, shutdown_executor) + return _generic_pmap( + task, values, task_args, task_kwargs, reduce_func, + timeout, fail_fast, num_workers, + progress_bar, progress_bar_kwargs, + setup_executor, extract_result, shutdown_executor + ) From cea831871aa6fbfd4a48a67fc9195a623ea4fd16 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 16 Jan 2024 12:12:18 -0500 Subject: [PATCH 169/247] Faster is_type_ proterties --- qutip/core/cy/qobjevo.pyx | 24 ++++++++++++++++++++++-- qutip/core/qobj.py | 35 +++++++++++++++++++++++++++++------ 2 files changed, 51 insertions(+), 8 deletions(-) diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index 67c5195c44..c2b4d5d3ae 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -908,12 +908,12 @@ cdef class QobjEvo: @property def isoper(self): """Indicates if the system represents an operator.""" - return self._dims.type == "oper" + return self._dims.type in ['oper', 'scalar'] @property def issuper(self): """Indicates if the system represents a superoperator.""" - return self._dims.issuper + return self._dims.type == 'super' @property def dims(self): @@ -927,6 +927,26 @@ cdef class QobjEvo: def superrep(self): return self._dims.superrep + @property + def isbra(self): + """Indicates if the system represents a bra state.""" + return self._dims.type in ['bra', 'scalar'] + + @property + def isket(self): + """Indicates if the system represents a ket state.""" + return self._dims.type in ['ket', 'scalar'] + + @property + def isoperket(self): + """Indicates if the system represents a operator-ket state.""" + return self._dims.type == 'operator-ket' + + @property + def isoperbra(self): + """Indicates if the system represents a operator-bra state.""" + return self._dims.type == 'operator-bra' + ########################################################################### # operation methods # ########################################################################### diff --git a/qutip/core/qobj.py b/qutip/core/qobj.py index 9c1692df4d..d545f25788 100644 --- a/qutip/core/qobj.py +++ b/qutip/core/qobj.py @@ -1726,12 +1726,35 @@ def isunitary(self): @property def shape(self): return self._data.shape - isbra = property(isbra) - isket = property(isket) - isoper = property(isoper) - issuper = property(issuper) - isoperbra = property(isoperbra) - isoperket = property(isoperket) + @property + def isoper(self): + """Indicates if the Qobj represents an operator.""" + return self._dims.type in ['oper', 'scalar'] + + @property + def isbra(self): + """Indicates if the Qobj represents a bra state.""" + return self._dims.type in ['bra', 'scalar'] + + @property + def isket(self): + """Indicates if the Qobj represents a ket state.""" + return self._dims.type in ['ket', 'scalar'] + + @property + def issuper(self): + """Indicates if the Qobj represents a superoperator.""" + return self._dims.type == 'super' + + @property + def isoperket(self): + """Indicates if the Qobj represents a operator-ket state.""" + return self._dims.type == 'operator-ket' + + @property + def isoperbra(self): + """Indicates if the Qobj represents a operator-bra state.""" + return self._dims.type == 'operator-bra' def ptrace(Q, sel): From 13ad4e63308ba4cb1878d73fbbb90664c2bc7d17 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 16 Jan 2024 12:50:06 -0500 Subject: [PATCH 170/247] Less Qobj.add overhead --- qutip/core/qobj.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/qutip/core/qobj.py b/qutip/core/qobj.py index d545f25788..45efd80d89 100644 --- a/qutip/core/qobj.py +++ b/qutip/core/qobj.py @@ -81,11 +81,18 @@ def _require_equal_type(method): """ @functools.wraps(method) def out(self, other): + if isinstance(other, Qobj): + if self._dims != other._dims: + msg = ( + "incompatible dimensions " + + repr(self.dims) + " and " + repr(other.dims) + ) + raise ValueError(msg) + return method(self, other) if other == 0: return method(self, other) if ( - self.type in ('oper', 'super') - and self._dims[0] == self._dims[1] + self._dims.issquare and isinstance(other, numbers.Number) ): scale = complex(other) @@ -95,17 +102,13 @@ def out(self, other): isherm=(scale.imag == 0), isunitary=(abs(abs(scale)-1) < settings.core['atol']), copy=False) - if not isinstance(other, Qobj): + else: try: + # This allow `Qobj + array` if the shape is good. + # Do we really want that? other = Qobj(other, dims=self._dims) except TypeError: return NotImplemented - if self._dims != other._dims: - msg = ( - "incompatible dimensions " - + repr(self.dims) + " and " + repr(other.dims) - ) - raise ValueError(msg) return method(self, other) return out @@ -389,7 +392,8 @@ def to(self, data_type): def __add__(self, other): if other == 0: return self.copy() - return Qobj(_data.add(self._data, other._data), + new_data = _data.add(self._data, other._data) + return Qobj(new_data, dims=self._dims, isherm=(self._isherm and other._isherm) or None, copy=False) From 29558e016bce3a9fd568bd1cee3df342d067270a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Wed, 17 Jan 2024 19:06:05 -0500 Subject: [PATCH 171/247] Update qutip/core/qobj.py Co-authored-by: Simon Cross --- qutip/core/qobj.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qutip/core/qobj.py b/qutip/core/qobj.py index 45efd80d89..def8530ec8 100644 --- a/qutip/core/qobj.py +++ b/qutip/core/qobj.py @@ -1728,7 +1728,9 @@ def isunitary(self): return self._isunitary @property - def shape(self): return self._data.shape + def shape(self): + """Return the shape of the Qobj data.""" + return self._data.shape @property def isoper(self): From 4623f3ed5239e961236907d6ce5eb90ab0f9e67b Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Thu, 18 Jan 2024 17:19:44 +0900 Subject: [PATCH 172/247] Fixed bugs when adding MultiTrajResults --- qutip/solver/result.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qutip/solver/result.py b/qutip/solver/result.py index b1d1bf2bbd..8c0d025513 100644 --- a/qutip/solver/result.py +++ b/qutip/solver/result.py @@ -829,6 +829,7 @@ def __add__(self, other): raise ValueError("Shared `times` are is required to merge results") new = self.__class__(self._raw_ops, self.options, solver=self.solver, stats=self.stats) + new.e_ops = self.e_ops if self.trajectories and other.trajectories: new.trajectories = self.trajectories + other.trajectories new.num_trajectories = self.num_trajectories + other.num_trajectories @@ -836,7 +837,8 @@ def __add__(self, other): new.seeds = self.seeds + other.seeds if self._sum_states is not None and other._sum_states is not None: - new._sum_states = self._sum_states + other._sum_states + new._sum_states = [state1 + state2 for state1, state2 + in zip(self._sum_states, other._sum_states)] if ( self._sum_final_states is not None From af32f38d60eecb9d4fe348c416b62d30e300cb72 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Thu, 18 Jan 2024 17:37:39 +0900 Subject: [PATCH 173/247] Small fix --- qutip/solver/multitraj.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/qutip/solver/multitraj.py b/qutip/solver/multitraj.py index 679bffab3d..4b600434df 100644 --- a/qutip/solver/multitraj.py +++ b/qutip/solver/multitraj.py @@ -144,7 +144,9 @@ def _initialize_run(self, state, ntraj=1, args=None, e_ops=(), result.add_end_condition(ntraj, target_tol) map_func = _get_map[self.options['map']] - map_kw = self.options['mpi_options'] if map_func == mpi_pmap else {} + map_kw = {} + if map_func == mpi_pmap: + map_kw.update(self.options['mpi_options']) map_kw.update({ 'timeout': timeout, 'num_cpus': self.options['num_cpus'], From e911319377d000b143a7af2a5bcc25c6d186706f Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Thu, 18 Jan 2024 18:41:25 +0900 Subject: [PATCH 174/247] Formatting, small bug in parallel.py --- qutip/solver/nm_mcsolve.py | 4 ++-- qutip/solver/parallel.py | 22 ++++++++++++---------- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index a8df2b93ce..a328713c4a 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -544,8 +544,8 @@ def options(self): progress_bar: str {'text', 'enhanced', 'tqdm', ''}, default: "text" How to present the solver progress. - 'tqdm' uses the python module of the same name and raise an error if - not installed. Empty string or False will disable the bar. + 'tqdm' uses the python module of the same name and raise an error + if not installed. Empty string or False will disable the bar. progress_kwargs: dict, default: {"chunk_size":10} Arguments to pass to the progress_bar. Qutip's bars use diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index 2d9a1aead5..fd0956c125 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -139,7 +139,7 @@ def _generic_pmap(task, values, task_args, task_kwargs, reduce_func, Common functionality for parallel_map, loky_pmap and mpi_pmap. The parameters `setup_executor`, `extract_result` and `shutdown_executor` are callback functions with the following signatures: - + setup_executor: () -> ProcessPoolExecutor extract_result: Future -> (Any, BaseException) @@ -184,8 +184,11 @@ def _done_callback(future): # is only waiting for the ProcessPoolExecutor to shutdown # before exiting. We therefore return immediately. return - if isinstance(exception, Exception): - errors[future._i] = exception + if exception is not None: + if isinstance(exception, Exception): + errors[future._i] = exception + else: + raise exception else: remaining_ntraj = result_func(future._i, result) if remaining_ntraj is not None and remaining_ntraj <= 0: @@ -317,8 +320,8 @@ def setup_executor(): return concurrent.futures.ProcessPoolExecutor( max_workers=map_kw['num_cpus'], **ctx_kw, ) - - def extract_result (future: concurrent.futures.Future): + + def extract_result(future: concurrent.futures.Future): exception = future.exception() if exception is not None: return None, exception @@ -391,8 +394,8 @@ def loky_pmap(task, values, task_args=None, task_kwargs=None, def setup_executor(): return get_reusable_executor(max_workers=map_kw['num_cpus']) - - def extract_result (future: concurrent.futures.Future): + + def extract_result(future: concurrent.futures.Future): exception = future.exception() if isinstance(exception, ShutdownExecutorError): # Task was aborted due to timeout etc @@ -476,8 +479,8 @@ def mpi_pmap(task, values, task_args=None, task_kwargs=None, def setup_executor(): return MPIPoolExecutor(max_workers=num_workers, **map_kw) - - def extract_result (future): + + def extract_result(future): exception = future.exception() if exception is not None: return None, exception @@ -494,7 +497,6 @@ def shutdown_executor(executor, _): ) - _get_map = { "parallel_map": parallel_map, "parallel": parallel_map, From 35bfdb0bd4e3e11cc1f87b32cb37bdafb9cb6b01 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Thu, 18 Jan 2024 18:46:11 +0900 Subject: [PATCH 175/247] Towncrier entry --- doc/changes/2296.feature | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/2296.feature diff --git a/doc/changes/2296.feature b/doc/changes/2296.feature new file mode 100644 index 0000000000..8fed655652 --- /dev/null +++ b/doc/changes/2296.feature @@ -0,0 +1 @@ +Added mpi_pmap, which uses the mpi4py module to run computations in parallel through the MPI interface. \ No newline at end of file From 3c1393f2661e3a98d776c0c363b9e71af7dcbf8f Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 18 Jan 2024 12:27:12 -0500 Subject: [PATCH 176/247] Add test for Dimensions _eq_, _ne_, _matmul_ --- qutip/core/qobj.py | 19 +++++-------------- qutip/tests/core/test_dimensions.py | 27 +++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/qutip/core/qobj.py b/qutip/core/qobj.py index def8530ec8..bdc72ca67e 100644 --- a/qutip/core/qobj.py +++ b/qutip/core/qobj.py @@ -91,10 +91,7 @@ def out(self, other): return method(self, other) if other == 0: return method(self, other) - if ( - self._dims.issquare - and isinstance(other, numbers.Number) - ): + if self._dims.issquare and isinstance(other, numbers.Number): scale = complex(other) other = Qobj(_data.identity(self.shape[0], scale, dtype=type(self.data)), @@ -102,14 +99,9 @@ def out(self, other): isherm=(scale.imag == 0), isunitary=(abs(abs(scale)-1) < settings.core['atol']), copy=False) - else: - try: - # This allow `Qobj + array` if the shape is good. - # Do we really want that? - other = Qobj(other, dims=self._dims) - except TypeError: - return NotImplemented - return method(self, other) + return method(self, other) + return NotImplemented + return out @@ -392,8 +384,7 @@ def to(self, data_type): def __add__(self, other): if other == 0: return self.copy() - new_data = _data.add(self._data, other._data) - return Qobj(new_data, + return Qobj(_data.add(self._data, other._data), dims=self._dims, isherm=(self._isherm and other._isherm) or None, copy=False) diff --git a/qutip/tests/core/test_dimensions.py b/qutip/tests/core/test_dimensions.py index 0686e28b71..7be9d27600 100644 --- a/qutip/tests/core/test_dimensions.py +++ b/qutip/tests/core/test_dimensions.py @@ -162,3 +162,30 @@ def test_super(self, base, expected): def test_bad_dims(dims_list): with pytest.raises(ValueError): Dimensions([dims_list, [1]]) + + +@pytest.mark.parametrize("space_l", [[1], [2], [2, 3]]) +@pytest.mark.parametrize("space_m", [[1], [2], [2, 3]]) +@pytest.mark.parametrize("space_r", [[1], [2], [2, 3]]) +def test_dims_matmul(space_l, space_m, space_r): + dims_l = Dimensions([space_l, space_m]) + dims_r = Dimensions([space_m, space_r]) + assert dims_l @ dims_r == Dimensions([space_l, space_r]) + + +def test_dims_matmul_bad(): + dims_l = Dimensions([[1], [3]]) + dims_r = Dimensions([[2], [2]]) + with pytest.raises(TypeError): + dims_l @ dims_r + + +def test_dims_comparison(): + assert Dimensions([[1], [2]]) == Dimensions([[1], [2]]) + assert not Dimensions([[1], [2]]) != Dimensions([[1], [2]]) + assert Dimensions([[1], [2]]) != Dimensions([[2], [1]]) + assert not Dimensions([[1], [2]]) == Dimensions([[2], [1]]) + assert Dimensions([[1], [2]])[1] == Dimensions([[1], [2]])[1] + assert Dimensions([[1], [2]])[0] != Dimensions([[1], [2]])[1] + assert not Dimensions([[1], [2]])[1] != Dimensions([[1], [2]])[1] + assert not Dimensions([[1], [2]])[0] != Dimensions([[1], [2]])[0] From 07798bd6677fddea0dd9b345b0b254f773e86754 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Mon, 22 Jan 2024 14:39:46 +0900 Subject: [PATCH 177/247] Added mpi4py to setup.cfg --- setup.cfg | 1 + 1 file changed, 1 insertion(+) diff --git a/setup.cfg b/setup.cfg index d5373c9c85..2bde0b8619 100644 --- a/setup.cfg +++ b/setup.cfg @@ -60,6 +60,7 @@ ipython = ipython extras = loky + mpi4py tqdm ; This uses ConfigParser's string interpolation to include all the above ; dependencies into one single target, convenient for testing full builds. From 57cca54994d2b70c9c99761b4726d1676c430b9e Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Mon, 22 Jan 2024 14:44:57 +0900 Subject: [PATCH 178/247] Explicit list of solver options --- qutip/solver/mcsolve.py | 11 +++++++++-- qutip/solver/nm_mcsolve.py | 16 ++++++++++++++-- qutip/solver/stochastic.py | 37 ++++++++++++++++++++++++++++++++++--- 3 files changed, 57 insertions(+), 7 deletions(-) diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index adb2677e3e..72b049553c 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -417,7 +417,15 @@ class MCSolver(MultiTrajSolver): _trajectory_resultclass = McTrajectoryResult _mc_integrator_class = MCIntegrator solver_options = { - **MultiTrajSolver.solver_options, + "progress_bar": "text", + "progress_kwargs": {"chunk_size": 10}, + "store_final_state": False, + "store_states": None, + "keep_runs_results": False, + "map": "serial", + "mpi_options": {}, + "num_cpus": None, + "bitgenerator": None, "method": "adams", "mc_corr_eps": 1e-10, "norm_steps": 5, @@ -425,7 +433,6 @@ class MCSolver(MultiTrajSolver): "norm_tol": 1e-4, "improved_sampling": False, } - del solver_options["normalize_output"] def __init__(self, H, c_ops, *, options=None): _time_start = time() diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index a328713c4a..31e7cbd4a4 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -331,12 +331,24 @@ class NonMarkovianMCSolver(MCSolver): name = "nm_mcsolve" _resultclass = NmmcResult solver_options = { - **MCSolver.solver_options, + "progress_bar": "text", + "progress_kwargs": {"chunk_size": 10}, + "store_final_state": False, + "store_states": None, + "keep_runs_results": False, + "map": "serial", + "mpi_options": {}, + "num_cpus": None, + "bitgenerator": None, + "method": "adams", + "mc_corr_eps": 1e-10, + "norm_steps": 5, + "norm_t_tol": 1e-6, + "norm_tol": 1e-4, "completeness_rtol": 1e-5, "completeness_atol": 1e-8, "martingale_quad_limit": 100, } - del solver_options["improved_sampling"] # both classes will be partially initialized in constructor _trajectory_resultclass = NmmcTrajectoryResult diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index 8e1cc9a033..0fcf362afe 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -509,7 +509,16 @@ class StochasticSolver(MultiTrajSolver): system = None _open = None solver_options = { - **MultiTrajSolver.solver_options, + "progress_bar": "text", + "progress_kwargs": {"chunk_size": 10}, + "store_final_state": False, + "store_states": None, + "keep_runs_results": False, + "normalize_output": False, + "map": "serial", + "mpi_options": {}, + "num_cpus": None, + "bitgenerator": None, "method": "platen", "store_measurement": False, } @@ -795,7 +804,18 @@ class SMESolver(StochasticSolver): _avail_integrators = {} _open = True solver_options = { - **StochasticSolver.solver_options + "progress_bar": "text", + "progress_kwargs": {"chunk_size": 10}, + "store_final_state": False, + "store_states": None, + "keep_runs_results": False, + "normalize_output": False, + "map": "serial", + "mpi_options": {}, + "num_cpus": None, + "bitgenerator": None, + "method": "platen", + "store_measurement": False, } @@ -828,5 +848,16 @@ class SSESolver(StochasticSolver): _avail_integrators = {} _open = False solver_options = { - **StochasticSolver.solver_options + "progress_bar": "text", + "progress_kwargs": {"chunk_size": 10}, + "store_final_state": False, + "store_states": None, + "keep_runs_results": False, + "normalize_output": False, + "map": "serial", + "mpi_options": {}, + "num_cpus": None, + "bitgenerator": None, + "method": "platen", + "store_measurement": False, } From a9ed0fb48884eed45c15f9f3276cbf766ece745e Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 22 Jan 2024 16:49:37 -0500 Subject: [PATCH 179/247] Fix for scipy 1.12 in tests --- qutip/tests/solver/test_steadystate.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/qutip/tests/solver/test_steadystate.py b/qutip/tests/solver/test_steadystate.py index 74287a81b3..161e904e95 100644 --- a/qutip/tests/solver/test_steadystate.py +++ b/qutip/tests/solver/test_steadystate.py @@ -1,7 +1,9 @@ import numpy as np +import scipy import pytest import qutip import warnings +from packaging import version as pac_version @pytest.mark.parametrize(['method', 'kwargs'], [ @@ -36,6 +38,13 @@ def test_qubit(method, kwargs, dtype): sz = qutip.sigmaz().to(dtype) sm = qutip.destroy(2, dtype=dtype) + if ( + pac_version.parse(scipy.__version__) >= pac_version.parse("1.12") + and "tol" in kwargs + ): + # From scipy 1.12, the tol keyword is renamed to rtol + kwargs["rtol"] = kwargs.pop("tol") + H = 0.5 * 2 * np.pi * sz gamma1 = 0.05 @@ -94,6 +103,13 @@ def test_exact_solution_for_simple_methods(method, kwargs): def test_ho(method, kwargs): # thermal steadystate of an oscillator: compare numerics with analytical # formula + if ( + pac_version.parse(scipy.__version__) >= pac_version.parse("1.12") + and "tol" in kwargs + ): + # From scipy 1.12, the tol keyword is renamed to rtol + kwargs["rtol"] = kwargs.pop("tol") + a = qutip.destroy(30) H = 0.5 * 2 * np.pi * a.dag() * a gamma1 = 0.05 @@ -130,6 +146,13 @@ def test_ho(method, kwargs): pytest.param('iterative-bicgstab', {"atol": 1e-10, "tol": 1e-10}, id="iterative-bicgstab"), ]) def test_driven_cavity(method, kwargs): + if ( + pac_version.parse(scipy.__version__) >= pac_version.parse("1.12") + and "tol" in kwargs + ): + # From scipy 1.12, the tol keyword is renamed to rtol + kwargs["rtol"] = kwargs.pop("tol") + N = 30 Omega = 0.01 * 2 * np.pi Gamma = 0.05 From e600344148457a136fedb3a32eb5d0c3c9460660 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 22 Jan 2024 22:02:33 +0000 Subject: [PATCH 180/247] Bump pillow from 10.0.1 to 10.2.0 in /doc Bumps [pillow](https://github.com/python-pillow/Pillow) from 10.0.1 to 10.2.0. - [Release notes](https://github.com/python-pillow/Pillow/releases) - [Changelog](https://github.com/python-pillow/Pillow/blob/main/CHANGES.rst) - [Commits](https://github.com/python-pillow/Pillow/compare/10.0.1...10.2.0) --- updated-dependencies: - dependency-name: pillow dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- doc/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/requirements.txt b/doc/requirements.txt index aa58f0e06e..6cb3ffacf5 100644 --- a/doc/requirements.txt +++ b/doc/requirements.txt @@ -21,7 +21,7 @@ packaging==23.0 parso==0.8.3 pexpect==4.8.0 pickleshare==0.7.5 -Pillow==10.0.1 +Pillow==10.2.0 prompt-toolkit==3.0.38 ptyprocess==0.7.0 Pygments==2.15.0 From c1854ec4690fd7dc8c4d8b5ca73dc37f707bb49e Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Tue, 23 Jan 2024 14:16:10 +0900 Subject: [PATCH 181/247] Update github workflows to include mpi tests --- .github/workflows/build_documentation.yml | 1 + .github/workflows/tests.yml | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index b5c6eb9cf6..17d818be48 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -10,6 +10,7 @@ jobs: steps: - uses: actions/checkout@v3 + - uses: mpi4py/setup-mpi@v1 - uses: actions/setup-python@v4 name: Install Python diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f44aa7e8f2..0c07620686 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -115,7 +115,7 @@ jobs: # rather than in the GitHub Actions file directly, because bash gives us # a proper programming language to use. run: | - QUTIP_TARGET="tests,graphics,semidefinite,ipython" + QUTIP_TARGET="tests,graphics,semidefinite,ipython,extra" if [[ -z "${{ matrix.nocython }}" ]]; then QUTIP_TARGET="$QUTIP_TARGET,runtime_compilation" fi From 1b203cc0435f1ca70a8c4e8fa4186b34630f6a0c Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Tue, 23 Jan 2024 15:14:39 +0900 Subject: [PATCH 182/247] Fixed typo --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0c07620686..61de501929 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -115,7 +115,7 @@ jobs: # rather than in the GitHub Actions file directly, because bash gives us # a proper programming language to use. run: | - QUTIP_TARGET="tests,graphics,semidefinite,ipython,extra" + QUTIP_TARGET="tests,graphics,semidefinite,ipython,extras" if [[ -z "${{ matrix.nocython }}" ]]; then QUTIP_TARGET="$QUTIP_TARGET,runtime_compilation" fi From 030298b3ff259bd5432fdb281b17f54b664d9ca7 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Tue, 23 Jan 2024 15:18:02 +0900 Subject: [PATCH 183/247] Missing MPI setup in tests.yml --- .github/workflows/tests.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 61de501929..baca23a293 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -103,6 +103,7 @@ jobs: steps: - uses: actions/checkout@v3 + - uses: mpi4py/setup-mpi@v1 - uses: conda-incubator/setup-miniconda@v2 with: From 9301d66cdf8fd6ada8a6d1cfc6defa2114a33092 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 23 Jan 2024 13:45:23 -0500 Subject: [PATCH 184/247] Add error message to MemoryError --- qutip/core/data/csr.pyx | 23 +++++++++++++++++++---- qutip/core/data/dense.pyx | 30 +++++++++++++++++++++++++----- qutip/core/data/dia.pyx | 12 ++++++++++-- 3 files changed, 54 insertions(+), 11 deletions(-) diff --git a/qutip/core/data/csr.pyx b/qutip/core/data/csr.pyx index c7eed323c2..5bbce551cc 100644 --- a/qutip/core/data/csr.pyx +++ b/qutip/core/data/csr.pyx @@ -530,9 +530,21 @@ cpdef CSR empty(base.idxint rows, base.idxint cols, base.idxint size): PyDataMem_NEW(size * sizeof(base.idxint)) out.row_index =\ PyDataMem_NEW(row_size * sizeof(base.idxint)) - if not out.data: raise MemoryError() - if not out.col_index: raise MemoryError() - if not out.row_index: raise MemoryError() + if not out.data: + raise MemoryError( + f"Failed to allocate the `data` of a ({rows}, {cols}) " + f"CSR array of {size} max elements." + ) + if not out.col_index: + raise MemoryError( + f"Failed to allocate the `col_index` of a ({rows}, {cols}) " + f"CSR array of {size} max elements." + ) + if not out.row_index: + raise MemoryError( + f"Failed to allocate the `row_index` of a ({rows}, {cols}) " + f"CSR array of {size} max elements." + ) # Set the number of non-zero elements to 0. out.row_index[rows] = 0 return out @@ -604,7 +616,10 @@ cdef CSR from_coo_pointers( data_tmp = mem.PyMem_Malloc(nnz * sizeof(double complex)) cols_tmp = mem.PyMem_Malloc(nnz * sizeof(base.idxint)) if data_tmp == NULL or cols_tmp == NULL: - raise MemoryError + raise MemoryError( + f"Failed to allocate the memory needed for a ({n_rows}, {n_cols}) " + f"CSR array with {nnz} elements." + ) with nogil: memset(out.row_index, 0, (n_rows + 1) * sizeof(base.idxint)) for ptr_in in range(nnz): diff --git a/qutip/core/data/dense.pyx b/qutip/core/data/dense.pyx index 1fc7f35670..0db9034fa0 100644 --- a/qutip/core/data/dense.pyx +++ b/qutip/core/data/dense.pyx @@ -120,7 +120,11 @@ cdef class Dense(base.Data): cdef Dense out = Dense.__new__(Dense) cdef size_t size = self.shape[0]*self.shape[1]*sizeof(double complex) cdef double complex *ptr = PyDataMem_NEW(size) - if not ptr: raise MemoryError() + if not ptr: + raise MemoryError( + "Could not allocate memory to copy a " + f"({self.shape[0]}, {self.shape[1]}) Dense matrix." + ) memcpy(ptr, self.data, size) out.shape = self.shape out.data = ptr @@ -163,7 +167,11 @@ cdef class Dense(base.Data): """ cdef size_t size = self.shape[0]*self.shape[1]*sizeof(double complex) cdef double complex *ptr = PyDataMem_NEW(size) - if not ptr: raise MemoryError() + if not ptr: + raise MemoryError( + "Could not allocate memory to convert to a numpy array a " + f"({self.shape[0]}, {self.shape[1]}) Dense matrix." + ) memcpy(ptr, self.data, size) cdef object out =\ cnp.PyArray_SimpleNewFromData(2, [self.shape[0], self.shape[1]], @@ -246,7 +254,11 @@ cpdef Dense empty(base.idxint rows, base.idxint cols, bint fortran=True): cdef Dense out = Dense.__new__(Dense) out.shape = (rows, cols) out.data = PyDataMem_NEW(rows * cols * sizeof(double complex)) - if not out.data: raise MemoryError() + if not out.data: + raise MemoryError( + "Could not allocate memory to create an empty " + f"({rows}, {cols}) Dense matrix." + ) out._deallocate = True out.fortran = fortran return out @@ -267,7 +279,11 @@ cpdef Dense zeros(base.idxint rows, base.idxint cols, bint fortran=True): out.shape = (rows, cols) out.data =\ PyDataMem_NEW_ZEROED(rows * cols, sizeof(double complex)) - if not out.data: raise MemoryError() + if not out.data: + raise MemoryError( + "Could not allocate memory to create a zero " + f"({rows}, {cols}) Dense matrix." + ) out.fortran = fortran out._deallocate = True return out @@ -294,7 +310,11 @@ cpdef Dense from_csr(CSR matrix, bint fortran=False): PyDataMem_NEW_ZEROED(out.shape[0]*out.shape[1], sizeof(double complex)) ) - if not out.data: raise MemoryError() + if not out.data: + raise MemoryError( + "Could not allocate memory to create a " + f"({out.shape[0]}, {out.shape[1]}) Dense matrix from a CSR." + ) out.fortran = fortran out._deallocate = True cdef size_t row, ptr_in, ptr_out, row_stride, col_stride diff --git a/qutip/core/data/dia.pyx b/qutip/core/data/dia.pyx index 92d68b8f89..9414298402 100644 --- a/qutip/core/data/dia.pyx +++ b/qutip/core/data/dia.pyx @@ -271,8 +271,16 @@ cpdef Dia empty(base.idxint rows, base.idxint cols, base.idxint num_diag): PyDataMem_NEW(cols * num_diag * sizeof(double complex)) out.offsets =\ PyDataMem_NEW(num_diag * sizeof(base.idxint)) - if not out.data: raise MemoryError() - if not out.offsets: raise MemoryError() + if not out.data: + raise MemoryError( + f"Failed to allocate the `data` of a ({rows}, {cols}) " + f"Dia array of {num_diag} diagonals." + ) + if not out.offsets: + raise MemoryError( + f"Failed to allocate the `offsets` of a ({rows}, {cols}) " + f"Dia array of {num_diag} diagonals." + ) return out From b1d582b2d0cee852dfb3a92471d0205257c1cf03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Wed, 24 Jan 2024 09:51:14 -0500 Subject: [PATCH 185/247] Apply suggestions from code review Co-authored-by: Simon Cross --- doc/guide/guide-basics.rst | 12 ++++++------ doc/guide/guide-bloch.rst | 2 +- doc/guide/guide-control.rst | 11 +++++++---- 3 files changed, 14 insertions(+), 11 deletions(-) diff --git a/doc/guide/guide-basics.rst b/doc/guide/guide-basics.rst index 19b035903b..1d17cf0976 100644 --- a/doc/guide/guide-basics.rst +++ b/doc/guide/guide-basics.rst @@ -328,10 +328,10 @@ For the destruction operator above: The ``data`` attribute returns a Qutip diagonal matrix. ``Qobj`` instances store their data in Qutip matrix format. -In the core qutip module, the ``Dense``, ``CSR`` and ``Dia`` formats are available, but other module can add other formats. -For example, the qutip-jax module add ``Jax`` and ``JaxDia`` formats. +In the core qutip module, the ``Dense``, ``CSR`` and ``Dia`` formats are available, but other packages can add other formats. +For example, the ``qutip-jax`` module adds the ``Jax`` and ``JaxDia`` formats. One can always access the underlying matrix as a numpy array using :meth:`.Qobj.full`. -It is also possible to access the underlying data as is in a common format using :meth:`.Qobj.data_as`. +It is also possible to access the underlying data in a common format using :meth:`.Qobj.data_as`. .. doctest:: [basics] :options: +NORMALIZE_WHITESPACE @@ -357,9 +357,9 @@ Conversion between storage type is done using the :meth:`.Qobj.to` method. Note that :meth:`.Qobj.data_as` does not do the conversion. QuTiP will do conversion when needed to keep everything working in any format. -However these conversions could slow down the computations and it is recomented to keep to one family of format. -For example, core qutip ``Dense`` and ``CSR`` work well together and binary operation between these format is efficient. -However binary operations between ``Dense`` and ``Jax`` should be avoided since it is not clear whether the operation will be executed by Jax, (possibly on GPU) or numpy. +However these conversions could slow down computation and it is recommended to keep to one format family where possible. +For example, core QuTiP ``Dense`` and ``CSR`` work well together and binary operations between these formats is efficient. +However binary operations between ``Dense`` and ``Jax`` should be avoided since it is not always clear whether the operation will be executed by Jax (possibly on a GPU if present) or numpy. .. _basics-qobj-math: diff --git a/doc/guide/guide-bloch.rst b/doc/guide/guide-bloch.rst index 696b18c4e9..ad351a5f51 100644 --- a/doc/guide/guide-bloch.rst +++ b/doc/guide/guide-bloch.rst @@ -9,7 +9,7 @@ Plotting on the Bloch Sphere Introduction ============ -When studying the dynamics of a two-level system, it is often convenient to visualize the state of the system by plotting the state-vector or density matrix on the Bloch sphere. In QuTiP, we have created two different classes to allow for easy creation and manipulation of data sets, both vectors and data points, on the Bloch sphere. +When studying the dynamics of a two-level system, it is often convenient to visualize the state of the system by plotting the state-vector or density matrix on the Bloch sphere. In QuTiP, there is a class to allow for easy creation and manipulation of data sets, both vectors and data points, on the Bloch sphere. .. _bloch-class: diff --git a/doc/guide/guide-control.rst b/doc/guide/guide-control.rst index b5baf9b0d5..e0769cd592 100644 --- a/doc/guide/guide-control.rst +++ b/doc/guide/guide-control.rst @@ -191,7 +191,10 @@ algorithm. Optimal Quantum Control in QuTiP ================================ -The Quantum Control part of qutip has been moved to it's own project. -Previously available implementation is now located in the `qutip-qtrl `_ module. -A newer interface with upgraded capacities is also being developped in `qutip-qoc `_. -Please give these module a look. +The Quantum Control part of qutip has been moved to its own project. + +The previously available implementation is now located in the `qutip-qtrl `_ module. If the ``qutip-qtrl`` package is installed, it can also be imported under the name ``qutip.control`` to ease porting code developed for QuTiP 4 to QuTiP 5. + +A newer interface with upgraded capacities is being developped in `qutip-qoc `_. + +Please give these modules a try. From aeeb74fea93d90c8dd6196c2e3f8b80e47b5b256 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 24 Jan 2024 15:02:46 -0500 Subject: [PATCH 186/247] Add towncrier --- doc/changes/2306.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/2306.misc diff --git a/doc/changes/2306.misc b/doc/changes/2306.misc new file mode 100644 index 0000000000..eb9042599c --- /dev/null +++ b/doc/changes/2306.misc @@ -0,0 +1 @@ +Remove Bloch3D: redundant to Bloch \ No newline at end of file From 5355a893bcfe95ced0b9fe665d03fd063851f729 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 25 Jan 2024 13:59:22 -0500 Subject: [PATCH 187/247] Renew tests matrix --- .github/workflows/tests.yml | 74 +++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 40 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index f44aa7e8f2..dafc1295d2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -36,47 +36,43 @@ jobs: # the lack of a variable is _always_ false-y, and the defaults lack all # the special cases. include: - # Mac - # Mac has issues with MKL since september 2022. - - case-name: macos - os: macos-latest - python-version: "3.10" - condaforge: 1 - nomkl: 1 - - # Scipy 1.5 - - case-name: old SciPy + # Python 3.10, Scipy 1.8, numpy 1.23 + # Oldest version we have to support according to SPEC 0 + # https://scientific-python.org/specs/spec-0000/ + - case-name: Old setup os: ubuntu-latest - python-version: "3.8" - numpy-requirement: ">=1.20,<1.21" - scipy-requirement: ">=1.5,<1.6" + python-version: "3.10" + numpy-requirement: ">=1.23,<1.24" + scipy-requirement: ">=1.8,<1.9" condaforge: 1 oldcython: 1 pytest-extra-options: "-W ignore:dep_util:DeprecationWarning" - # No MKL runs. MKL is now the default for conda installations, but - # not necessarily for pip. - - case-name: no MKL + # Python 3.10, no mkl, no cython + - case-name: Python 3.10, no mkl os: ubuntu-latest - python-version: "3.9" - numpy-requirement: ">=1.20,<1.21" + python-version: "3.10" + condaforge: 1 nomkl: 1 - - # Builds without Cython at runtime. This is a core feature; - # everything should be able to run this. - - case-name: no Cython - os: ubuntu-latest - python-version: "3.8" nocython: 1 - # Python 3.10 and numpy 1.22 - # Use conda-forge to provide numpy 1.22 - - case-name: Python 3.10 - os: ubuntu-latest - python-version: "3.10" + # Mac + # Mac has issues with MKL since september 2022. + - case-name: macos + os: macos-latest + python-version: "3.11" condaforge: 1 - oldcython: 1 - pytest-extra-options: "-W ignore:dep_util:DeprecationWarning" + nomkl: 1 + + # Windows. Once all tests pass without special options needed, this + # can be moved to the main os list in the test matrix. All the tests + # that fail currently seem to do so because mcsolve uses + # multiprocessing under the hood. Windows does not support fork() + # well, which makes transfering objects to the child processes + # error prone. See, e.g., https://github.com/qutip/qutip/issues/1202 + - case-name: Windows + os: windows-latest + python-version: "3.11" # Python 3.11 and latest numpy # Use conda-forge to provide Python 3.11 and latest numpy @@ -91,15 +87,13 @@ jobs: conda-extra-pkgs: "suitesparse" # for compiling cvxopt pytest-extra-options: "-W ignore::DeprecationWarning:Cython.Tempita" - # Windows. Once all tests pass without special options needed, this - # can be moved to the main os list in the test matrix. All the tests - # that fail currently seem to do so because mcsolve uses - # multiprocessing under the hood. Windows does not support fork() - # well, which makes transfering objects to the child processes - # error prone. See, e.g., https://github.com/qutip/qutip/issues/1202 - - case-name: Windows Latest - os: windows-latest - python-version: "3.10" + # Python 3.12 and latest numpy + # Use conda-forge to provide Python 3.11 and latest numpy + - case-name: Python 3.12 + os: ubuntu-latest + python-version: "3.12" + condaforge: 1 + steps: - uses: actions/checkout@v3 From c46e17f17446fb929ff35afe2f4a6e1c07d85634 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 25 Jan 2024 14:05:47 -0500 Subject: [PATCH 188/247] Update build for python 3.10 to 3.12 --- .github/workflows/build.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 45b648fa35..4c2396502b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,7 +57,7 @@ jobs: # For the sdist we should be as conservative as possible with our # Python version. This should be the lowest supported version. This # means that no unsupported syntax can sneak through. - python-version: '3.8' + python-version: '3.10' - name: Install pip build run: | @@ -107,11 +107,11 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macos-latest] env: - # Set up wheels matrix. This is CPython 3.8--3.10 for all OS targets. - CIBW_BUILD: "cp3{8,9,10,11}-*" + # Set up wheels matrix. This is CPython 3.10--3.12 for all OS targets. + CIBW_BUILD: "cp3{10,11,12}-*" # Numpy and SciPy do not supply wheels for i686 or win32 for # Python 3.10+, so we skip those: - CIBW_SKIP: "*-musllinux* cp3{8,9,10,11}-manylinux_i686 cp3{8,9,10,11}-win32" + CIBW_SKIP: "*-musllinux* cp3{10,11,12}-manylinux_i686 cp3{10,11,12}-win32" OVERRIDE_VERSION: ${{ github.event.inputs.override_version }} steps: @@ -165,12 +165,12 @@ jobs: - uses: actions/setup-python@v4 name: Install Python with: - python-version: '3.8' + python-version: '3.10' - name: Verify this is not a dev version shell: bash run: | - python -m pip install wheels/*-cp38-cp38-manylinux*.whl + python -m pip install wheels/*-cp310-cp310-manylinux*.whl python -c 'import qutip; print(qutip.__version__); assert "dev" not in qutip.__version__; assert "+" not in qutip.__version__' # We built the zipfile for convenience distributing to Windows users on @@ -198,12 +198,12 @@ jobs: - uses: actions/setup-python@v4 name: Install Python with: - python-version: '3.8' + python-version: '3.10' - name: Verify this is not a dev version shell: bash run: | - python -m pip install wheels/*-cp38-cp38-manylinux*.whl + python -m pip install wheels/*-cp310-cp310-manylinux*.whl python -c 'import qutip; print(qutip.__version__); assert "dev" not in qutip.__version__; assert "+" not in qutip.__version__' # We built the zipfile for convenience distributing to Windows users on From c87f563f96540bf4e1c152c7919dc36d66eee772 Mon Sep 17 00:00:00 2001 From: DnMGalan Date: Thu, 25 Jan 2024 20:03:33 +0100 Subject: [PATCH 189/247] Add the possibility to customize point colors as in V4 --- qutip/bloch.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/qutip/bloch.py b/qutip/bloch.py index 0978ad48bd..84ceb83bd4 100644 --- a/qutip/bloch.py +++ b/qutip/bloch.py @@ -169,8 +169,10 @@ def __init__(self, fig=None, axes=None, view=None, figsize=None, # ---point options--- # List of colors for Bloch point markers, default = ['b','g','r','y'] self.point_default_color = ['b', 'r', 'g', '#CC6600'] + # Old variable used in V4 to customise the color of the points + self.point_color = None # List that stores the display colors for each set of points - self.point_color = [] + self.inner_point_color = [] # Size of point markers, default = 25 self.point_size = [25, 32, 35, 45] # Shape of point markers, default = ['o','^','d','s'] @@ -360,7 +362,7 @@ def add_points(self, points, meth='s', colors=None, alpha=1.0): self.point_style.append(meth) self.points.append(points) self.point_alpha.append(alpha) - self.point_color.append(colors) + self.inner_point_color.append(colors) def add_states(self, state, kind='vector', colors=None, alpha=1.0): """Add a state vector Qobj to Bloch sphere. @@ -799,12 +801,15 @@ def plot_points(self): s = self.point_size[np.mod(k, len(self.point_size))] marker = self.point_marker[np.mod(k, len(self.point_marker))] style = self.point_style[k] - if self.point_color[k] is not None: - color = self.point_color[k] + + if self.inner_point_color[k] is not None: + color = self.inner_point_color[k] + elif self.point_color is not None: + color = self.point_color elif self.point_style[k] in ['s', 'l']: - color = self.point_default_color[ + color = [self.point_default_color[ k % len(self.point_default_color) - ] + ]] elif self.point_style[k] == 'm': length = np.ceil(num_points/len(self.point_default_color)) color = np.tile(self.point_default_color, length.astype(int)) @@ -824,6 +829,7 @@ def plot_points(self): ) elif self.point_style[k] == 'l': + color = color[k % len(color)] self.axes.plot(np.real(points[1]), -np.real(points[0]), np.real(points[2]), From 699f7279163d29036cc874c7695cd8742171bf1a Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 25 Jan 2024 14:22:59 -0500 Subject: [PATCH 190/247] Update requirements --- .github/workflows/build.yml | 16 ++++++++-------- .github/workflows/tests.yml | 33 +++++++++++++++++++++++---------- pyproject.toml | 8 +------- requirements.txt | 4 ++-- setup.cfg | 8 ++++---- 5 files changed, 38 insertions(+), 31 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4c2396502b..c08e003ea1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -57,7 +57,7 @@ jobs: # For the sdist we should be as conservative as possible with our # Python version. This should be the lowest supported version. This # means that no unsupported syntax can sneak through. - python-version: '3.10' + python-version: '3.9' - name: Install pip build run: | @@ -107,8 +107,8 @@ jobs: matrix: os: [ubuntu-latest, windows-latest, macos-latest] env: - # Set up wheels matrix. This is CPython 3.10--3.12 for all OS targets. - CIBW_BUILD: "cp3{10,11,12}-*" + # Set up wheels matrix. This is CPython 3.9--3.12 for all OS targets. + CIBW_BUILD: "cp3{9,10,11,12}-*" # Numpy and SciPy do not supply wheels for i686 or win32 for # Python 3.10+, so we skip those: CIBW_SKIP: "*-musllinux* cp3{10,11,12}-manylinux_i686 cp3{10,11,12}-win32" @@ -121,7 +121,7 @@ jobs: name: Install Python with: # This is about the build environment, not the released wheel version. - python-version: '3.8' + python-version: '3.9' - name: Install cibuildwheel run: | @@ -165,12 +165,12 @@ jobs: - uses: actions/setup-python@v4 name: Install Python with: - python-version: '3.10' + python-version: '3.9' - name: Verify this is not a dev version shell: bash run: | - python -m pip install wheels/*-cp310-cp310-manylinux*.whl + python -m pip install wheels/*-cp39-cp39-manylinux*.whl python -c 'import qutip; print(qutip.__version__); assert "dev" not in qutip.__version__; assert "+" not in qutip.__version__' # We built the zipfile for convenience distributing to Windows users on @@ -198,12 +198,12 @@ jobs: - uses: actions/setup-python@v4 name: Install Python with: - python-version: '3.10' + python-version: '3.9' - name: Verify this is not a dev version shell: bash run: | - python -m pip install wheels/*-cp310-cp310-manylinux*.whl + python -m pip install wheels/*-cp39-cp39-manylinux*.whl python -c 'import qutip; print(qutip.__version__); assert "dev" not in qutip.__version__; assert "+" not in qutip.__version__' # We built the zipfile for convenience distributing to Windows users on diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index dafc1295d2..5fc826a276 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -27,34 +27,45 @@ jobs: os: [ubuntu-latest] # Test other versions of Python in special cases to avoid exploding the # matrix size; make sure to test all supported versions in some form. - python-version: ["3.9"] + python-version: ["3.11"] case-name: [defaults] - numpy-requirement: [">=1.20,<1.21"] + numpy-requirement: [">=1.22,<1.27"] + scipy-requirement: [">=1.8,<1.12"] coverage-requirement: ["==6.5"] # Extra special cases. In these, the new variable defined should always # be a truth-y value (hence 'nomkl: 1' rather than 'mkl: 0'), because # the lack of a variable is _always_ false-y, and the defaults lack all # the special cases. include: - # Python 3.10, Scipy 1.8, numpy 1.23 - # Oldest version we have to support according to SPEC 0 + # Python 3.9, Scipy 1.7, numpy 1.22 + # On more version than suggested by SPEC 0 # https://scientific-python.org/specs/spec-0000/ - case-name: Old setup os: ubuntu-latest - python-version: "3.10" - numpy-requirement: ">=1.23,<1.24" + python-version: "3.9" + numpy-requirement: ">=1.22,<1.23" scipy-requirement: ">=1.8,<1.9" condaforge: 1 oldcython: 1 pytest-extra-options: "-W ignore:dep_util:DeprecationWarning" - # Python 3.10, no mkl, no cython + # Python 3.10, no cython, oldist dependencies + - case-name: Python 3.10, no cython + os: ubuntu-latest + python-version: "3.10" + scipy-requirement: ">=1.9,<1.10" + numpy-requirement: ">=1.23,<1.24" + condaforge: 1 + nocython: 1 + + # Python 3.10, no mkl - case-name: Python 3.10, no mkl os: ubuntu-latest python-version: "3.10" + scipy-requirement: ">=1.10,<1.11" + numpy-requirement: ">=1.24,<1.25" condaforge: 1 nomkl: 1 - nocython: 1 # Mac # Mac has issues with MKL since september 2022. @@ -74,7 +85,7 @@ jobs: os: windows-latest python-version: "3.11" - # Python 3.11 and latest numpy + # Python 3.11 and recent numpy # Use conda-forge to provide Python 3.11 and latest numpy # Ignore deprecation of the cgi module in Python 3.11 that is # still imported by Cython.Tempita. This was addressed in @@ -84,8 +95,10 @@ jobs: os: ubuntu-latest python-version: "3.11" condaforge: 1 + numpy-requirement: ">=1.25,<1.26" + scipy-requirement: ">=1.11,<1.12" conda-extra-pkgs: "suitesparse" # for compiling cvxopt - pytest-extra-options: "-W ignore::DeprecationWarning:Cython.Tempita" + # pytest-extra-options: "-W ignore::DeprecationWarning:Cython.Tempita" # Python 3.12 and latest numpy # Use conda-forge to provide Python 3.11 and latest numpy diff --git a/pyproject.toml b/pyproject.toml index 230bbad6b3..896cd3f115 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ requires = [ # See https://numpy.org/doc/stable/user/depending_on_numpy.html for # the recommended way to build against numpy's C API: "oldest-supported-numpy", - "scipy>=1.0", + "scipy>=1.8", ] build-backend = "setuptools.build_meta" @@ -18,12 +18,6 @@ manylinux-i686-image = "manylinux2014" # Change in future version to "build" build-frontend = "pip" -[[tool.cibuildwheel.overrides]] -# NumPy and SciPy support manylinux2010 on CPython 3.6 and 3.7 -select = "cp3{6,7}-*" -manylinux-x86_64-image = "manylinux2010" -manylinux-i686-image = "manylinux2010" - [tool.towncrier] directory = "doc/changes" filename = "doc/changelog.rst" diff --git a/requirements.txt b/requirements.txt index 41d2c09a1b..9fc7beade5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ cython>=0.29.20 -numpy>=1.16.6 -scipy>=1.5 +numpy>=1.22 +scipy>=1.8 packaging diff --git a/setup.cfg b/setup.cfg index d5373c9c85..4863e1ec63 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,12 +31,12 @@ packages = find: include_package_data = True zip_safe = False install_requires = - numpy>=1.16.6 - scipy>=1.0 + numpy>=1.22,<1.26 + scipy>=1.8,<1.12 packaging setup_requires = - numpy>=1.13.3 - scipy>=1.0 + numpy>=1.22 + scipy>=1.8 cython>=0.29.20; python_version>='3.10' cython>=0.29.20,<3.0.3; python_version<='3.9' packaging From f36cc220f68adfb4e579229e8640b2c1db17acfe Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 25 Jan 2024 15:17:03 -0500 Subject: [PATCH 191/247] installable with oldest supported numpy --- setup.cfg | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/setup.cfg b/setup.cfg index 4863e1ec63..1491a2cbb2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -31,11 +31,11 @@ packages = find: include_package_data = True zip_safe = False install_requires = - numpy>=1.22,<1.26 + numpy>=1.22 scipy>=1.8,<1.12 packaging setup_requires = - numpy>=1.22 + numpy>=1.19 scipy>=1.8 cython>=0.29.20; python_version>='3.10' cython>=0.29.20,<3.0.3; python_version<='3.9' From 1afbd10a1e3c8c78fc282946cb97dfa63c6bbedc Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 25 Jan 2024 16:42:50 -0500 Subject: [PATCH 192/247] filter dateutil deprecation --- .github/workflows/tests.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5fc826a276..b835a3f5b0 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -50,7 +50,7 @@ jobs: pytest-extra-options: "-W ignore:dep_util:DeprecationWarning" # Python 3.10, no cython, oldist dependencies - - case-name: Python 3.10, no cython + - case-name: no cython os: ubuntu-latest python-version: "3.10" scipy-requirement: ">=1.9,<1.10" @@ -59,7 +59,7 @@ jobs: nocython: 1 # Python 3.10, no mkl - - case-name: Python 3.10, no mkl + - case-name: no mkl os: ubuntu-latest python-version: "3.10" scipy-requirement: ">=1.10,<1.11" @@ -98,7 +98,6 @@ jobs: numpy-requirement: ">=1.25,<1.26" scipy-requirement: ">=1.11,<1.12" conda-extra-pkgs: "suitesparse" # for compiling cvxopt - # pytest-extra-options: "-W ignore::DeprecationWarning:Cython.Tempita" # Python 3.12 and latest numpy # Use conda-forge to provide Python 3.11 and latest numpy @@ -106,6 +105,7 @@ jobs: os: ubuntu-latest python-version: "3.12" condaforge: 1 + pytest-extra-options: "-W ignore:datetime:DeprecationWarning" steps: From ac6f7fb3d368a0d4abcced776704adca65a4a02e Mon Sep 17 00:00:00 2001 From: DnMGalan Date: Fri, 26 Jan 2024 12:24:17 +0100 Subject: [PATCH 193/247] Fix point plot behavior for 'l' style and add Towncrier entry. --- doc/changes/1974.bugfix | 1 + qutip/bloch.py | 7 +++---- 2 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 doc/changes/1974.bugfix diff --git a/doc/changes/1974.bugfix b/doc/changes/1974.bugfix new file mode 100644 index 0000000000..f7b521a0bf --- /dev/null +++ b/doc/changes/1974.bugfix @@ -0,0 +1 @@ +Add the possibility to customize point colors as in V4 and fix point plot behavior for 'l' style \ No newline at end of file diff --git a/qutip/bloch.py b/qutip/bloch.py index 84ceb83bd4..236493ba04 100644 --- a/qutip/bloch.py +++ b/qutip/bloch.py @@ -794,7 +794,6 @@ def plot_points(self): dist = np.linalg.norm(points, axis=0) if not np.allclose(dist, dist[0], rtol=1e-12): indperm = np.argsort(dist) - points = points[:, indperm] else: indperm = np.arange(num_points) @@ -817,9 +816,9 @@ def plot_points(self): color = list(color) if self.point_style[k] in ['s', 'm']: - self.axes.scatter(np.real(points[1]), - -np.real(points[0]), - np.real(points[2]), + self.axes.scatter(np.real(points[1][indperm]), + -np.real(points[0][indperm]), + np.real(points[2][indperm]), s=s, marker=marker, color=color, From 9123786721a44c284f363c29f8bca77901d9dd1a Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 26 Jan 2024 10:31:28 -0500 Subject: [PATCH 194/247] Use old cython for old scipy --- .github/workflows/tests.yml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index b835a3f5b0..71382bad8c 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -43,29 +43,30 @@ jobs: - case-name: Old setup os: ubuntu-latest python-version: "3.9" - numpy-requirement: ">=1.22,<1.23" scipy-requirement: ">=1.8,<1.9" + numpy-requirement: ">=1.22,<1.23" condaforge: 1 oldcython: 1 pytest-extra-options: "-W ignore:dep_util:DeprecationWarning" - # Python 3.10, no cython, oldist dependencies - - case-name: no cython + # Python 3.10, no mkl, scipy 1.9, numpy 1.23 + # Scipy 1.10 did not support cython 3.0 yet. + - case-name: no mkl os: ubuntu-latest python-version: "3.10" scipy-requirement: ">=1.9,<1.10" numpy-requirement: ">=1.23,<1.24" condaforge: 1 - nocython: 1 + oldcython: 1 + nomkl: 1 - # Python 3.10, no mkl + # Python 3.10, no cython, scipy 1.10, numpy 1.24 - case-name: no mkl os: ubuntu-latest python-version: "3.10" scipy-requirement: ">=1.10,<1.11" numpy-requirement: ">=1.24,<1.25" - condaforge: 1 - nomkl: 1 + nocython: 1 # Mac # Mac has issues with MKL since september 2022. From a8c6dfdeadb67e5526f9bc4764fc6609a3a18d64 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 26 Jan 2024 11:13:11 -0500 Subject: [PATCH 195/247] Fix filter warnings --- .github/workflows/tests.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 71382bad8c..19b1e79fb8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -50,7 +50,8 @@ jobs: pytest-extra-options: "-W ignore:dep_util:DeprecationWarning" # Python 3.10, no mkl, scipy 1.9, numpy 1.23 - # Scipy 1.10 did not support cython 3.0 yet. + # Scipy 1.9 did not support cython 3.0 yet. + # cython#17234 - case-name: no mkl os: ubuntu-latest python-version: "3.10" @@ -59,9 +60,10 @@ jobs: condaforge: 1 oldcython: 1 nomkl: 1 + pytest-extra-options: "-W ignore:dep_util:DeprecationWarning" # Python 3.10, no cython, scipy 1.10, numpy 1.24 - - case-name: no mkl + - case-name: no cython os: ubuntu-latest python-version: "3.10" scipy-requirement: ">=1.10,<1.11" From d4c441f97703d10c070b2cfac49a6998c05bc651 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 26 Jan 2024 12:03:21 -0500 Subject: [PATCH 196/247] Reorder matrix --- .github/workflows/tests.yml | 44 +++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 19b1e79fb8..4ee25f99e2 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -29,8 +29,8 @@ jobs: # matrix size; make sure to test all supported versions in some form. python-version: ["3.11"] case-name: [defaults] - numpy-requirement: [">=1.22,<1.27"] - scipy-requirement: [">=1.8,<1.12"] + numpy-requirement: [">=1.22"] + scipy-requirement: [">=1.8"] coverage-requirement: ["==6.5"] # Extra special cases. In these, the new variable defined should always # be a truth-y value (hence 'nomkl: 1' rather than 'mkl: 0'), because @@ -40,6 +40,7 @@ jobs: # Python 3.9, Scipy 1.7, numpy 1.22 # On more version than suggested by SPEC 0 # https://scientific-python.org/specs/spec-0000/ + # There are deprecation warnings when using cython 0.29.X - case-name: Old setup os: ubuntu-latest python-version: "3.9" @@ -70,24 +71,6 @@ jobs: numpy-requirement: ">=1.24,<1.25" nocython: 1 - # Mac - # Mac has issues with MKL since september 2022. - - case-name: macos - os: macos-latest - python-version: "3.11" - condaforge: 1 - nomkl: 1 - - # Windows. Once all tests pass without special options needed, this - # can be moved to the main os list in the test matrix. All the tests - # that fail currently seem to do so because mcsolve uses - # multiprocessing under the hood. Windows does not support fork() - # well, which makes transfering objects to the child processes - # error prone. See, e.g., https://github.com/qutip/qutip/issues/1202 - - case-name: Windows - os: windows-latest - python-version: "3.11" - # Python 3.11 and recent numpy # Use conda-forge to provide Python 3.11 and latest numpy # Ignore deprecation of the cgi module in Python 3.11 that is @@ -98,8 +81,8 @@ jobs: os: ubuntu-latest python-version: "3.11" condaforge: 1 - numpy-requirement: ">=1.25,<1.26" scipy-requirement: ">=1.11,<1.12" + numpy-requirement: ">=1.25,<1.26" conda-extra-pkgs: "suitesparse" # for compiling cvxopt # Python 3.12 and latest numpy @@ -107,9 +90,28 @@ jobs: - case-name: Python 3.12 os: ubuntu-latest python-version: "3.12" + scipy-requirement: ">=1.12,<1.13" + numpy-requirement: ">=1.26,<1.27" condaforge: 1 pytest-extra-options: "-W ignore:datetime:DeprecationWarning" + # Mac + # Mac has issues with MKL since september 2022. + - case-name: macos + os: macos-latest + python-version: "3.11" + condaforge: 1 + nomkl: 1 + + # Windows. Once all tests pass without special options needed, this + # can be moved to the main os list in the test matrix. All the tests + # that fail currently seem to do so because mcsolve uses + # multiprocessing under the hood. Windows does not support fork() + # well, which makes transfering objects to the child processes + # error prone. See, e.g., https://github.com/qutip/qutip/issues/1202 + - case-name: Windows + os: windows-latest + python-version: "3.11" steps: - uses: actions/checkout@v3 From ca8f8c2a23a37b9120a60a9d8bc176075953b5b1 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 29 Jan 2024 14:25:31 -0500 Subject: [PATCH 197/247] Ensure tests can pass without ipython and matplotlib --- qutip/tests/test_animation.py | 5 +++-- qutip/tests/test_ipynbtools.py | 3 ++- qutip/tests/test_visualization.py | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/qutip/tests/test_animation.py b/qutip/tests/test_animation.py index 676e7473a4..0433a38608 100644 --- a/qutip/tests/test_animation.py +++ b/qutip/tests/test_animation.py @@ -1,10 +1,11 @@ import pytest import qutip import numpy as np -import matplotlib as mpl -import matplotlib.pyplot as plt from scipy.special import sph_harm +mpl = pytest.importorskip("matplotlib") +plt = pytest.importorskip("matplotlib.pyplot") + def test_result_state(): H = qutip.rand_dm(2) tlist = np.linspace(0, 3*np.pi, 2) diff --git a/qutip/tests/test_ipynbtools.py b/qutip/tests/test_ipynbtools.py index 2b1c76559a..2227e0c784 100644 --- a/qutip/tests/test_ipynbtools.py +++ b/qutip/tests/test_ipynbtools.py @@ -1,6 +1,7 @@ -from qutip.ipynbtools import version_table import pytest +pytest.importorskip("IPython") +from qutip.ipynbtools import version_table @pytest.mark.parametrize('verbose', [False, True]) def test_version_table(verbose): diff --git a/qutip/tests/test_visualization.py b/qutip/tests/test_visualization.py index 2d2901e1be..ebf92b4d11 100644 --- a/qutip/tests/test_visualization.py +++ b/qutip/tests/test_visualization.py @@ -1,10 +1,10 @@ import pytest import qutip import numpy as np -import matplotlib as mpl -import matplotlib.pyplot as plt from scipy.special import sph_harm +mpl = pytest.importorskip("matplotlib") +plt = pytest.importorskip("matplotlib.pyplot") def test_cyclic(): qutip.settings.colorblind_safe = True From 99124bcffad9913c85719cdec2a552e11548bd05 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 29 Jan 2024 14:34:03 -0500 Subject: [PATCH 198/247] Add towncrier --- doc/changes/2311.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/2311.misc diff --git a/doc/changes/2311.misc b/doc/changes/2311.misc new file mode 100644 index 0000000000..d5712b0c02 --- /dev/null +++ b/doc/changes/2311.misc @@ -0,0 +1 @@ +Allow tests to run without matplotlib and ipython. \ No newline at end of file From 6bb7a93b07048a9e4b462fde84117627e4ea0eb7 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 30 Jan 2024 09:38:57 -0500 Subject: [PATCH 199/247] Increase small step over default dt. --- qutip/tests/solver/test_stochastic.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/qutip/tests/solver/test_stochastic.py b/qutip/tests/solver/test_stochastic.py index ebbf194daf..f5dac26af2 100644 --- a/qutip/tests/solver/test_stochastic.py +++ b/qutip/tests/solver/test_stochastic.py @@ -348,23 +348,23 @@ def func(t, A, W): def test_deprecation_warnings(): with pytest.warns(FutureWarning, match=r'map_func'): - ssesolve(qeye(2), basis(2), [0, 1e-5], [qeye(2)], map_func=None) + ssesolve(qeye(2), basis(2), [0, 0.01], [qeye(2)], map_func=None) with pytest.warns(FutureWarning, match=r'progress_bar'): - ssesolve(qeye(2), basis(2), [0, 1e-5], [qeye(2)], progress_bar=None) + ssesolve(qeye(2), basis(2), [0, 0.01], [qeye(2)], progress_bar=None) with pytest.warns(FutureWarning, match=r'nsubsteps'): - ssesolve(qeye(2), basis(2), [0, 1e-5], [qeye(2)], nsubsteps=None) + ssesolve(qeye(2), basis(2), [0, 0.01], [qeye(2)], nsubsteps=None) with pytest.warns(FutureWarning, match=r'map_func'): - ssesolve(qeye(2), basis(2), [0, 1e-5], [qeye(2)], map_func=None) + ssesolve(qeye(2), basis(2), [0, 0.01], [qeye(2)], map_func=None) with pytest.warns(FutureWarning, match=r'store_all_expect'): - ssesolve(qeye(2), basis(2), [0, 1e-5], [qeye(2)], store_all_expect=1) + ssesolve(qeye(2), basis(2), [0, 0.01], [qeye(2)], store_all_expect=1) with pytest.warns(FutureWarning, match=r'store_measurement'): - ssesolve(qeye(2), basis(2), [0, 1e-5], [qeye(2)], store_measurement=1) + ssesolve(qeye(2), basis(2), [0, 0.01], [qeye(2)], store_measurement=1) with pytest.raises(TypeError) as err: - ssesolve(qeye(2), basis(2), [0, 1e-5], [qeye(2)], m_ops=1) + ssesolve(qeye(2), basis(2), [0, 0.01], [qeye(2)], m_ops=1) assert '"m_ops" and "dW_factors"' in str(err.value) From ebb730506af0079093ca4dcb318881af7ed69556 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 30 Jan 2024 10:05:49 -0500 Subject: [PATCH 200/247] Add small step warnings --- qutip/solver/sode/sode.py | 12 +++++++++--- qutip/tests/solver/test_stochastic.py | 4 ++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index 33ffaae96a..63c283a774 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -1,4 +1,5 @@ import numpy as np +import warnings from . import _sode from ..integrator.integrator import Integrator from ..stochastic import StochasticSolver, SMESolver @@ -135,12 +136,17 @@ def __init__(self, rhs, options): def integrate(self, t, copy=True): delta_t = t - self.t + dt = self.options["dt"] if delta_t < 0: - raise ValueError("Stochastic integration time") - elif delta_t == 0: + raise ValueError("Integration time, can't be negative.") + elif delta_t < 0.5 * dt: + warnings.warn( + f"Step under minimum step ({dt}), skipped.", + RuntimeWarning + ) return self.t, self.state, np.zeros(self.N_dw) - dt = self.options["dt"] + N, extra = np.divmod(delta_t, dt) N = int(N) if extra > 0.5 * dt: diff --git a/qutip/tests/solver/test_stochastic.py b/qutip/tests/solver/test_stochastic.py index f5dac26af2..da11402b96 100644 --- a/qutip/tests/solver/test_stochastic.py +++ b/qutip/tests/solver/test_stochastic.py @@ -368,3 +368,7 @@ def test_deprecation_warnings(): with pytest.raises(TypeError) as err: ssesolve(qeye(2), basis(2), [0, 0.01], [qeye(2)], m_ops=1) assert '"m_ops" and "dW_factors"' in str(err.value) + +def test_small_step_warnings(): + with pytest.warns(RuntimeWarning, match=r'under minimum'): + ssesolve(qeye(2), basis(2), [0, 0.0000001], [qeye(2)]) From 9e00d1fd59e5e143b963da62f1592a3487e73339 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 30 Jan 2024 11:11:21 -0500 Subject: [PATCH 201/247] Add towncrier --- doc/changes/2313.misc | 1 + qutip/solver/sode/rouchon.py | 11 ++++++++--- qutip/solver/sode/sode.py | 1 - qutip/tests/solver/test_stochastic.py | 9 +++++++-- 4 files changed, 16 insertions(+), 6 deletions(-) create mode 100644 doc/changes/2313.misc diff --git a/doc/changes/2313.misc b/doc/changes/2313.misc new file mode 100644 index 0000000000..8020b81549 --- /dev/null +++ b/doc/changes/2313.misc @@ -0,0 +1 @@ +Add too small step warnings in fixed dt SODE solver \ No newline at end of file diff --git a/qutip/solver/sode/rouchon.py b/qutip/solver/sode/rouchon.py index a00d8e41de..61c30c0996 100644 --- a/qutip/solver/sode/rouchon.py +++ b/qutip/solver/sode/rouchon.py @@ -1,4 +1,5 @@ import numpy as np +import warnings from qutip import unstack_columns, stack_columns from qutip.core import data as _data from ..stochastic import StochasticSolver @@ -97,12 +98,16 @@ def set_state(self, t, state0, generator): def integrate(self, t, copy=True): delta_t = (t - self.t) + dt = self.options["dt"] if delta_t < 0: raise ValueError("Stochastic integration need increasing times") - elif delta_t == 0: - return self.t, self.state, np.zeros() + elif delta_t < 0.5 * dt: + warnings.warn( + f"Step under minimum step ({dt}), skipped.", + RuntimeWarning + ) + return self.t, self.state, np.zeros(len(self.sc_ops)) - dt = self.options["dt"] N, extra = np.divmod(delta_t, dt) N = int(N) if extra > 0.5 * dt: diff --git a/qutip/solver/sode/sode.py b/qutip/solver/sode/sode.py index 63c283a774..e43e8f5ac1 100644 --- a/qutip/solver/sode/sode.py +++ b/qutip/solver/sode/sode.py @@ -146,7 +146,6 @@ def integrate(self, t, copy=True): ) return self.t, self.state, np.zeros(self.N_dw) - N, extra = np.divmod(delta_t, dt) N = int(N) if extra > 0.5 * dt: diff --git a/qutip/tests/solver/test_stochastic.py b/qutip/tests/solver/test_stochastic.py index da11402b96..dc04b966f1 100644 --- a/qutip/tests/solver/test_stochastic.py +++ b/qutip/tests/solver/test_stochastic.py @@ -369,6 +369,11 @@ def test_deprecation_warnings(): ssesolve(qeye(2), basis(2), [0, 0.01], [qeye(2)], m_ops=1) assert '"m_ops" and "dW_factors"' in str(err.value) -def test_small_step_warnings(): + +@pytest.mark.parametrize("method", ["euler", "rouchon"]) +def test_small_step_warnings(method): with pytest.warns(RuntimeWarning, match=r'under minimum'): - ssesolve(qeye(2), basis(2), [0, 0.0000001], [qeye(2)]) + smesolve( + qeye(2), basis(2), [0, 0.0000001], [qeye(2)], + options={"method": method} + ) From 74f148a426620cca67fd0fa8a3ce6248d197e867 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Thu, 1 Feb 2024 18:41:28 +0900 Subject: [PATCH 202/247] Added documentation regarding default value of 'num_cpus' for 'mpi_pmap', changed tests to not use default value --- qutip/solver/parallel.py | 21 +++++++++++++++++++++ qutip/tests/solver/test_parallel.py | 15 ++++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index fd0956c125..16d4413370 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -11,6 +11,7 @@ import time import threading import concurrent.futures +import warnings from qutip.ui.progressbar import progress_bars from qutip.settings import available_cpu_count @@ -432,6 +433,12 @@ def mpi_pmap(task, values, task_args=None, task_kwargs=None, processes. For more information, consult the documentation of mpi4py and the mpi4py.MPIPoolExecutor class. + Note: in keeping consistent with the API of `parallel_map`, the parameter + determining the number of requested worker processes is called `num_cpus`. + The value of `map_kw['num_cpus']` is passed to the MPIPoolExecutor as its + `max_workers` argument. Its default value is the number of logical CPUs + (i.e., threads), which might be unsuitable for MPI applications. + Parameters ---------- task : a Python function @@ -472,11 +479,25 @@ def mpi_pmap(task, values, task_args=None, task_kwargs=None, """ from mpi4py.futures import MPIPoolExecutor + + # If the provided num_cpus is None, we use the default value instead (and + # emit a warning). We thus intentionally make it impossible to call + # MPIPoolExecutor(max_workers=None, ...) + # in which case mpi4py would determine a default value. + # The default value provided by mpi4py would be better suited, but mpi4py + # provides no public API to access the actual number of workers that is + # used in this case, which we need. + worker_number_provided = (map_kw is not None) and ('num_cpus' in map_kw) + map_kw = _read_map_kw(map_kw) timeout = map_kw.pop('timeout') num_workers = map_kw.pop('num_cpus') fail_fast = map_kw.pop('fail_fast') + if not worker_number_provided: + warnings.warn(f'mpi_pmap was called without specifying the number of ' + f'worker processes, using the default {num_workers}') + def setup_executor(): return MPIPoolExecutor(max_workers=num_workers, **map_kw) diff --git a/qutip/tests/solver/test_parallel.py b/qutip/tests/solver/test_parallel.py index b7836983ee..a666ab8456 100644 --- a/qutip/tests/solver/test_parallel.py +++ b/qutip/tests/solver/test_parallel.py @@ -95,13 +95,16 @@ def func(i): pytest.param(serial_map, id='serial_map'), ]) def test_map_pass_error(map): + kwargs = {} if map is loky_pmap: pytest.importorskip("loky") if map is mpi_pmap: pytest.importorskip("mpi4py") + # do not use default value for num_cpus for mpi_pmap + kwargs = {'map_kw': {'num_cpus': 1}} with pytest.raises(CustomException) as err: - map(func, range(10)) + map(func, range(10), **kwargs) assert "Error in subprocess" in str(err.value) @@ -112,13 +115,16 @@ def test_map_pass_error(map): pytest.param(serial_map, id='serial_map'), ]) def test_map_store_error(map): + map_kw = {"fail_fast": False} if map is loky_pmap: pytest.importorskip("loky") if map is mpi_pmap: pytest.importorskip("mpi4py") + # do not use default value for num_cpus for mpi_pmap + map_kw.update({'num_cpus': 1}) with pytest.raises(MapExceptions) as err: - map(func, range(10), map_kw={"fail_fast": False}) + map(func, range(10), map_kw=map_kw) map_error = err.value assert "iterations failed" in str(map_error) for iter, error in map_error.errors.items(): @@ -139,10 +145,13 @@ def test_map_store_error(map): pytest.param(serial_map, id='serial_map'), ]) def test_map_early_end(map): + kwargs = {} if map is loky_pmap: pytest.importorskip("loky") if map is mpi_pmap: pytest.importorskip("mpi4py") + # do not use default value for num_cpus for mpi_pmap + kwargs = {'map_kw': {'num_cpus': 1}} results = [] @@ -150,6 +159,6 @@ def reduce_func(result): results.append(result) return 5 - len(results) - map(_func1, range(100), reduce_func=reduce_func) + map(_func1, range(100), reduce_func=reduce_func, **kwargs) assert len(results) < 100 From 3b31f632c473ec8c41c562d872d05e130fbb842c Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Thu, 1 Feb 2024 19:05:02 +0900 Subject: [PATCH 203/247] Update workflows: remove mpi4py from extras, install it with conda on one of the test runs --- .github/workflows/build_documentation.yml | 1 - .github/workflows/tests.yml | 6 ++++-- setup.cfg | 1 - 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index 17d818be48..b5c6eb9cf6 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -10,7 +10,6 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: mpi4py/setup-mpi@v1 - uses: actions/setup-python@v4 name: Install Python diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5087eb1d23..a2395568c7 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -94,6 +94,10 @@ jobs: numpy-requirement: ">=1.26,<1.27" condaforge: 1 pytest-extra-options: "-W ignore:datetime:DeprecationWarning" + conda-extra-pkgs: "mpi4py" + # Enough to include mpi4py in one of the test runs + # Install it with conda (uses openmpi) instead of pip (uses mpich) + # because of issues with mpich # Mac # Mac has issues with MKL since september 2022. @@ -115,8 +119,6 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: mpi4py/setup-mpi@v1 - - uses: conda-incubator/setup-miniconda@v2 with: auto-update-conda: true diff --git a/setup.cfg b/setup.cfg index f2184414a0..1491a2cbb2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -60,7 +60,6 @@ ipython = ipython extras = loky - mpi4py tqdm ; This uses ConfigParser's string interpolation to include all the above ; dependencies into one single target, convenient for testing full builds. From a2d37418e1211c89384a8a00f6fd63d2d3294115 Mon Sep 17 00:00:00 2001 From: DnMGalan Date: Thu, 1 Feb 2024 20:31:40 +0100 Subject: [PATCH 204/247] Rename the variable inner_point_color to _inner_point_color --- qutip/bloch.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qutip/bloch.py b/qutip/bloch.py index 236493ba04..5a918ca311 100644 --- a/qutip/bloch.py +++ b/qutip/bloch.py @@ -172,7 +172,7 @@ def __init__(self, fig=None, axes=None, view=None, figsize=None, # Old variable used in V4 to customise the color of the points self.point_color = None # List that stores the display colors for each set of points - self.inner_point_color = [] + self._inner_point_color = [] # Size of point markers, default = 25 self.point_size = [25, 32, 35, 45] # Shape of point markers, default = ['o','^','d','s'] @@ -362,7 +362,7 @@ def add_points(self, points, meth='s', colors=None, alpha=1.0): self.point_style.append(meth) self.points.append(points) self.point_alpha.append(alpha) - self.inner_point_color.append(colors) + self._inner_point_color.append(colors) def add_states(self, state, kind='vector', colors=None, alpha=1.0): """Add a state vector Qobj to Bloch sphere. @@ -801,8 +801,8 @@ def plot_points(self): marker = self.point_marker[np.mod(k, len(self.point_marker))] style = self.point_style[k] - if self.inner_point_color[k] is not None: - color = self.inner_point_color[k] + if self._inner_point_color[k] is not None: + color = self._inner_point_color[k] elif self.point_color is not None: color = self.point_color elif self.point_style[k] in ['s', 'l']: From 3e58a4b249179cbffed366c488077e87b251e5b7 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Fri, 2 Feb 2024 13:22:45 +0900 Subject: [PATCH 205/247] mpi_pmap: document interaction with QUTIP_NUM_PROCESSES --- qutip/solver/parallel.py | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index 16d4413370..0eca5cac99 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -436,8 +436,11 @@ def mpi_pmap(task, values, task_args=None, task_kwargs=None, Note: in keeping consistent with the API of `parallel_map`, the parameter determining the number of requested worker processes is called `num_cpus`. The value of `map_kw['num_cpus']` is passed to the MPIPoolExecutor as its - `max_workers` argument. Its default value is the number of logical CPUs - (i.e., threads), which might be unsuitable for MPI applications. + `max_workers` argument. + If this parameter is not provided, the environment variable + `QUTIP_NUM_PROCESSES` is used instead. If this environment variable is not + set either, QuTiP will use default values that might be unsuitable for MPI + applications. Parameters ---------- @@ -480,14 +483,15 @@ def mpi_pmap(task, values, task_args=None, task_kwargs=None, from mpi4py.futures import MPIPoolExecutor - # If the provided num_cpus is None, we use the default value instead (and - # emit a warning). We thus intentionally make it impossible to call + # If the provided num_cpus is None, we use the default value instead. + # We thus intentionally make it impossible to call # MPIPoolExecutor(max_workers=None, ...) - # in which case mpi4py would determine a default value. - # The default value provided by mpi4py would be better suited, but mpi4py - # provides no public API to access the actual number of workers that is - # used in this case, which we need. - worker_number_provided = (map_kw is not None) and ('num_cpus' in map_kw) + # in which case mpi4py would determine a default value. That would be + # useful, but unfortunately mpi4py provides no public API to access the + # actual number of workers that is used in that case, which we would need. + worker_number_provided = ( + ((map_kw is not None) and ('num_cpus' in map_kw)) + or 'QUTIP_NUM_PROCESSES' in os.environ) map_kw = _read_map_kw(map_kw) timeout = map_kw.pop('timeout') From e0bf5d3a255c0a6545b6b2f8bc8fc59b7625ae72 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Fri, 2 Feb 2024 13:26:12 +0900 Subject: [PATCH 206/247] Fix test workflow with MPI --- .github/workflows/tests.yml | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index a2395568c7..c046f02a4e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -94,10 +94,9 @@ jobs: numpy-requirement: ">=1.26,<1.27" condaforge: 1 pytest-extra-options: "-W ignore:datetime:DeprecationWarning" - conda-extra-pkgs: "mpi4py" - # Enough to include mpi4py in one of the test runs - # Install it with conda (uses openmpi) instead of pip (uses mpich) - # because of issues with mpich + # Install mpi4py to test mpi_pmap + # Should be enough to include this in one of the runs + includempi: 1 # Mac # Mac has issues with MKL since september 2022. @@ -150,6 +149,10 @@ jobs: if [[ -n "${{ matrix.conda-extra-pkgs }}" ]]; then conda install "${{ matrix.conda-extra-pkgs }}" fi + if [[ "${{ matrix.includempi }}" ]]; then + # Use openmpi because mpich causes problems. Note, environment variable names change in v5 + conda install "openmpi<5" mpi4py + fi python -m pip install -e .[$QUTIP_TARGET] python -m pip install "coverage${{ matrix.coverage-requirement }}" python -m pip install pytest-cov coveralls pytest-fail-slow @@ -181,6 +184,12 @@ jobs: # truly being executed. export QUTIP_NUM_PROCESSES=2 fi + if [[ "${{ matrix.includempi }}" ]]; then + # By default, the max. number of allowed worker processes in openmpi is + # (number of physical cpu cores) - 1. + # We only have 2 physical cores, but we want to test mpi_pmap with 2 workers. + export OMPI_MCA_rmaps_base_oversubscribe=true + fi pytest -Werror --strict-config --strict-markers --fail-slow=300 --durations=0 --durations-min=1.0 --verbosity=1 --cov=qutip --cov-report= --color=yes ${{ matrix.pytest-extra-options }} qutip/tests # Above flags are: # -Werror From e4fc3ac0e19492af6a83eff62a8d9f035f10a194 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 2 Feb 2024 13:22:57 -0500 Subject: [PATCH 207/247] Focus on RIKEN and UdeS in support section --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 376e25b3a8..dec8085f8e 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,11 @@ Support [![Powered by NumFOCUS](https://img.shields.io/badge/powered%20by-NumFOCUS-orange.svg?style=flat&colorA=E1523D&colorB=007D8A)](https://numfocus.org) We are proud to be affiliated with [Unitary Fund](https://unitary.fund) and [numFOCUS](https://numfocus.org). -QuTiP development is supported by [Nori's lab](https://dml.riken.jp/) at RIKEN, by the University of Sherbrooke, and by Aberystwyth University, [among other supporting organizations](https://qutip.org/#supporting-organizations). + +[Nori's lab](https://dml.riken.jp/) at RIKEN and [Blais' lab](https://www.physique.usherbrooke.ca/blais/) at the University of Sherbrooke +have been providing developers to work on QuTiP. + +We also thank Google for supporting us by financing GSoC student to work on the QuTiP as well as [other supporting organizations](https://qutip.org/#supporting-organizations) that have been supporting QuTiP over the years. Installation From 5c55923d2afcdf7b20ba35ba9e65be785b198224 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 2 Feb 2024 14:31:47 -0500 Subject: [PATCH 208/247] IQ instead of UdeS --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index dec8085f8e..98fab16f38 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Support We are proud to be affiliated with [Unitary Fund](https://unitary.fund) and [numFOCUS](https://numfocus.org). -[Nori's lab](https://dml.riken.jp/) at RIKEN and [Blais' lab](https://www.physique.usherbrooke.ca/blais/) at the University of Sherbrooke +[Nori's lab](https://dml.riken.jp/) at RIKEN and [Blais' lab](https://www.physique.usherbrooke.ca/blais/) at the Institut Quantique have been providing developers to work on QuTiP. We also thank Google for supporting us by financing GSoC student to work on the QuTiP as well as [other supporting organizations](https://qutip.org/#supporting-organizations) that have been supporting QuTiP over the years. From 54f2d08ea7506251559c404354493eebfc9a8ac7 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Tue, 6 Feb 2024 12:08:06 +0900 Subject: [PATCH 209/247] Remove bitgenerator and mpi_options from solver function docstrings, formatting --- qutip/solver/mcsolve.py | 24 +++++++++--------------- qutip/solver/nm_mcsolve.py | 20 +++++++------------- qutip/solver/stochastic.py | 31 +++++++++++-------------------- 3 files changed, 27 insertions(+), 48 deletions(-) diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index 72b049553c..e18f3bb97c 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -73,11 +73,11 @@ def mcsolve(H, state, tlist, c_ops=(), e_ops=None, ntraj=500, *, - | atol, rtol : float | Absolute and relative tolerance of the ODE integrator. - | nsteps : int - | Maximum number of (internally defined) steps allowed in one ``tlist`` - step. + | Maximum number of (internally defined) steps allowed in one + ``tlist`` step. - | max_step : float - | Maximum length of one internal step. When using pulses, it should be - less than half the width of the thinnest pulse. + | Maximum length of one internal step. When using pulses, it should + be less than half the width of the thinnest pulse. - | keep_runs_results : bool, [False] | Whether to store results from all trajectories or just store the averages. @@ -85,17 +85,9 @@ def mcsolve(H, state, tlist, c_ops=(), e_ops=None, ntraj=500, *, | How to run the trajectories. "parallel" uses the multiprocessing module to run in parallel while "loky" and "mpi" use the "loky" and "mpi4py" modules to do so. - - | mpi_options : dict - | Only applies if map is "mpi". This dictionary will be passed as - keyword arguments to the `mpi4py.futures.MPIPoolExecutor` - constructor. Note that the `max_workers` argument is provided - separately through the `num_cpus` option. - | num_cpus : int | Number of cpus to use when running in parallel. ``None`` detect the number of available cpus. - - | bitgenerator : {None, "MT19937", "PCG64", "PCG64DXSM", ...} - Which of numpy.random's bitgenerator to use. With `None`, your - numpy version's default is used. - | norm_t_tol, norm_tol, norm_steps : float, float, int | Parameters used to find the collapse location. ``norm_t_tol`` and ``norm_tol`` are the tolerance in time and norm respectively. @@ -108,7 +100,9 @@ def mcsolve(H, state, tlist, c_ops=(), e_ops=None, ntraj=500, *, | Whether to use the improved sampling algorithm from Abdelhafez et al. PRA (2019) - Additional options may be available depending on the selected + Additional options are listed under + `options <./classes.html#qutip.solver.mcsolve.MCSolver.options>`__. + More options may be available depending on the selected differential equation integration method, see `Integrator <./classes.html#classes-ode>`_. @@ -592,8 +586,8 @@ def options(self): progress_bar: str {'text', 'enhanced', 'tqdm', ''}, default: "text" How to present the solver progress. - 'tqdm' uses the python module of the same name and raise an error if - not installed. Empty string or False will disable the bar. + 'tqdm' uses the python module of the same name and raise an error + if not installed. Empty string or False will disable the bar. progress_kwargs: dict, default: {"chunk_size":10} Arguments to pass to the progress_bar. Qutip's bars use diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index 31e7cbd4a4..69c7e397a5 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -94,11 +94,11 @@ def nm_mcsolve(H, state, tlist, ops_and_rates=(), e_ops=None, ntraj=500, *, - | atol, rtol : float | Absolute and relative tolerance of the ODE integrator. - | nsteps : int - | Maximum number of (internally defined) steps allowed in one ``tlist`` - step. + | Maximum number of (internally defined) steps allowed in one + ``tlist`` step. - | max_step : float - | Maximum length of one internal step. When using pulses, it should be - less than half the width of the thinnest pulse. + | Maximum length of one internal step. When using pulses, it should + be less than half the width of the thinnest pulse. - | keep_runs_results : bool, [False] | Whether to store results from all trajectories or just store the averages. @@ -106,17 +106,9 @@ def nm_mcsolve(H, state, tlist, ops_and_rates=(), e_ops=None, ntraj=500, *, | How to run the trajectories. "parallel" uses the multiprocessing module to run in parallel while "loky" and "mpi" use the "loky" and "mpi4py" modules to do so. - - | mpi_options : dict - | Only applies if map is "mpi". This dictionary will be passed as - keyword arguments to the `mpi4py.futures.MPIPoolExecutor` - constructor. Note that the `max_workers` argument is provided - separately through the `num_cpus` option. - | num_cpus : int | Number of cpus to use when running in parallel. ``None`` detect the number of available cpus. - - | bitgenerator : {None, "MT19937", "PCG64", "PCG64DXSM", ...} - Which of numpy.random's bitgenerator to use. With `None`, your - numpy version's default is used. - | norm_t_tol, norm_tol, norm_steps : float, float, int | Parameters used to find the collapse location. ``norm_t_tol`` and ``norm_tol`` are the tolerance in time and norm respectively. @@ -135,7 +127,9 @@ def nm_mcsolve(H, state, tlist, ops_and_rates=(), e_ops=None, ntraj=500, *, integration of the martingale. Note that the 'improved_sampling' option is not currently supported. - Additional options may be available depending on the selected + Additional options are listed under `options + <./classes.html#qutip.solver.nm_mcsolve.NonMarkovianMCSolver.options>`__. + More options may be available depending on the selected differential equation integration method, see `Integrator <./classes.html#classes-ode>`_. diff --git a/qutip/solver/stochastic.py b/qutip/solver/stochastic.py index 0fcf362afe..b2f81e57df 100644 --- a/qutip/solver/stochastic.py +++ b/qutip/solver/stochastic.py @@ -9,6 +9,7 @@ from .solver_base import _solver_deprecation from ._feedback import _QobjFeedback, _DataFeedback, _WeinerFeedback + class StochasticTrajResult(Result): def _post_init(self, m_ops=(), dw_factor=(), heterodyne=False): super()._post_init() @@ -336,23 +337,18 @@ def smesolve( | How to run the trajectories. "parallel" uses the multiprocessing module to run in parallel while "loky" and "mpi" use the "loky" and "mpi4py" modules to do so. - - | mpi_options : dict - | Only applies if map is "mpi". This dictionary will be passed as - keyword arguments to the `mpi4py.futures.MPIPoolExecutor` - constructor. Note that the `max_workers` argument is provided - separately through the `num_cpus` option. - | num_cpus : NoneType, int | Number of cpus to use when running in parallel. ``None`` detect the number of available cpus. - - | bitgenerator : {None, "MT19937", "PCG64", "PCG64DXSM", ...} - Which of numpy.random's bitgenerator to use. With `None`, your - numpy version's default is used. - | dt : float | The finite steps lenght for the Stochastic integration method. Default change depending on the integrator. - Other options could be supported depending on the integration method, - see `SIntegrator <./classes.html#classes-sode>`_. + Additional options are listed under + `options <./classes.html#qutip.solver.stochastic.SMESolver.options>`__. + More options may be available depending on the selected + differential equation integration method, see + `SIntegrator <./classes.html#classes-sode>`_. Returns ------- @@ -464,23 +460,18 @@ def ssesolve( | How to run the trajectories. "parallel" uses the multiprocessing module to run in parallel while "loky" and "mpi" use the "loky" and "mpi4py" modules to do so. - - | mpi_options : dict - | Only applies if map is "mpi". This dictionary will be passed as - keyword arguments to the `mpi4py.futures.MPIPoolExecutor` - constructor. Note that the `max_workers` argument is provided - separately through the `num_cpus` option. - | num_cpus : NoneType, int | Number of cpus to use when running in parallel. ``None`` detect the number of available cpus. - - | bitgenerator : {None, "MT19937", "PCG64", "PCG64DXSM", ...} - Which of numpy.random's bitgenerator to use. With `None`, your - numpy version's default is used. - | dt : float | The finite steps lenght for the Stochastic integration method. Default change depending on the integrator. - Other options could be supported depending on the integration method, - see `SIntegrator <./classes.html#classes-sode>`_. + Additional options are listed under + `options <./classes.html#qutip.solver.stochastic.SSESolver.options>`__. + More options may be available depending on the selected + differential equation integration method, see + `SIntegrator <./classes.html#classes-sode>`_. Returns ------- From 7e5ba81941e246d749bd065a0f57d9ee2012b96a Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Tue, 6 Feb 2024 12:15:56 +0900 Subject: [PATCH 210/247] Moved initialization of map_kw to parallel module --- qutip/solver/multitraj.py | 7 ++----- qutip/solver/parallel.py | 13 ++++++++++++- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/qutip/solver/multitraj.py b/qutip/solver/multitraj.py index 4b600434df..3ca7930a96 100644 --- a/qutip/solver/multitraj.py +++ b/qutip/solver/multitraj.py @@ -1,5 +1,5 @@ from .result import Result, MultiTrajResult -from .parallel import _get_map, mpi_pmap +from .parallel import _get_map from time import time from .solver_base import Solver from ..core import QobjEvo @@ -143,10 +143,7 @@ def _initialize_run(self, state, ntraj=1, args=None, e_ops=(), ) result.add_end_condition(ntraj, target_tol) - map_func = _get_map[self.options['map']] - map_kw = {} - if map_func == mpi_pmap: - map_kw.update(self.options['mpi_options']) + map_func, map_kw = _get_map(self.options) map_kw.update({ 'timeout': timeout, 'num_cpus': self.options['num_cpus'], diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index 0eca5cac99..d1043a51a2 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -522,7 +522,7 @@ def shutdown_executor(executor, _): ) -_get_map = { +_maps = { "parallel_map": parallel_map, "parallel": parallel_map, "serial_map": serial_map, @@ -530,3 +530,14 @@ def shutdown_executor(executor, _): "loky": loky_pmap, "mpi": mpi_pmap } + + +def _get_map(options): + map_func = _get_map[options['map']] + + if map_func == mpi_pmap: + map_kw = options['mpi_options'] + else: + map_kw = {} + + return map_func, map_kw From 53f4582a8e382ff2e2939dec2a64eea665cb8632 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Tue, 6 Feb 2024 12:23:40 +0900 Subject: [PATCH 211/247] Added mpi4py to setup.cfg again --- .github/workflows/build_documentation.yml | 3 +++ setup.cfg | 3 +++ 2 files changed, 6 insertions(+) diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index b5c6eb9cf6..9e952b0a30 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -10,6 +10,9 @@ jobs: steps: - uses: actions/checkout@v3 + - uses: mpi4py/setup-mpi@v1 + with: + mpi: 'openmpi' - uses: actions/setup-python@v4 name: Install Python diff --git a/setup.cfg b/setup.cfg index 1491a2cbb2..8a3bf41c79 100644 --- a/setup.cfg +++ b/setup.cfg @@ -61,6 +61,8 @@ ipython = extras = loky tqdm +mpi = + mpi4py ; This uses ConfigParser's string interpolation to include all the above ; dependencies into one single target, convenient for testing full builds. full = @@ -70,3 +72,4 @@ full = %(tests)s %(ipython)s %(extras)s + %(mpi)s From 8b58b587ff9e93c47b536a45aa75791490ff28c2 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Tue, 6 Feb 2024 13:50:38 +0900 Subject: [PATCH 212/247] Typo --- qutip/solver/parallel.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/solver/parallel.py b/qutip/solver/parallel.py index d1043a51a2..425a317f21 100644 --- a/qutip/solver/parallel.py +++ b/qutip/solver/parallel.py @@ -533,7 +533,7 @@ def shutdown_executor(executor, _): def _get_map(options): - map_func = _get_map[options['map']] + map_func = _maps[options['map']] if map_func == mpi_pmap: map_kw = options['mpi_options'] From 02f6f9b382c60b77d85d4f1184aff6408645e4be Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Tue, 6 Feb 2024 14:28:19 +0900 Subject: [PATCH 213/247] Docstring formatting mistakes --- qutip/solver/nm_mcsolve.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutip/solver/nm_mcsolve.py b/qutip/solver/nm_mcsolve.py index 69c7e397a5..6c064bb975 100644 --- a/qutip/solver/nm_mcsolve.py +++ b/qutip/solver/nm_mcsolve.py @@ -95,7 +95,7 @@ def nm_mcsolve(H, state, tlist, ops_and_rates=(), e_ops=None, ntraj=500, *, | Absolute and relative tolerance of the ODE integrator. - | nsteps : int | Maximum number of (internally defined) steps allowed in one - ``tlist`` step. + ``tlist`` step. - | max_step : float | Maximum length of one internal step. When using pulses, it should be less than half the width of the thinnest pulse. @@ -123,7 +123,7 @@ def nm_mcsolve(H, state, tlist, ops_and_rates=(), e_ops=None, ntraj=500, *, additional Lindblad operator is added automatically (with zero rate). - | martingale_quad_limit : float or int - An upper bound on the number of subintervals used in the adaptive + | An upper bound on the number of subintervals used in the adaptive integration of the martingale. Note that the 'improved_sampling' option is not currently supported. From d550e7c82e8e4e399765649dfe03703151b620e2 Mon Sep 17 00:00:00 2001 From: Matt Date: Tue, 6 Feb 2024 09:47:52 +0000 Subject: [PATCH 214/247] Only pre-compute density matrix if not stoing individual trajectories --- doc/changes/2303.feature | 1 + qutip/solver/heom/bofin_solvers.py | 14 +- qutip/solver/mcsolve.py | 5 +- qutip/solver/result.py | 346 +++++++++++++++++++---------- 4 files changed, 242 insertions(+), 124 deletions(-) create mode 100644 doc/changes/2303.feature diff --git a/doc/changes/2303.feature b/doc/changes/2303.feature new file mode 100644 index 0000000000..db293b995f --- /dev/null +++ b/doc/changes/2303.feature @@ -0,0 +1 @@ +Only pre-compute density matricies if keep_runs_results is False \ No newline at end of file diff --git a/qutip/solver/heom/bofin_solvers.py b/qutip/solver/heom/bofin_solvers.py index bb49890be2..23b82f629f 100644 --- a/qutip/solver/heom/bofin_solvers.py +++ b/qutip/solver/heom/bofin_solvers.py @@ -385,7 +385,7 @@ def _post_init(self): self.store_ados = self.options["store_ados"] if self.store_ados: - self.final_ado_state = None + self._final_ado_state = None self.ado_states = [] def _e_op_func(self, e_op): @@ -407,9 +407,17 @@ def _store_state(self, t, ado_state): self.ado_states.append(ado_state) def _store_final_state(self, t, ado_state): - self.final_state = ado_state.rho + self._final_state = ado_state.rho if self.store_ados: - self.final_ado_state = ado_state + self._final_ado_state = ado_state + + @property + def final_ado_state(self): + if self._final_ado_state is not None: + return self._final_state + if self.ado_states: + return self.ado_states[-1] + return None def heomsolve( diff --git a/qutip/solver/mcsolve.py b/qutip/solver/mcsolve.py index a55b1a094c..fb60f5fdd5 100644 --- a/qutip/solver/mcsolve.py +++ b/qutip/solver/mcsolve.py @@ -167,6 +167,7 @@ class _MCSystem(_MTSystem): """ Container for the operators of the solver. """ + def __init__(self, rhs, c_ops, n_ops): self.rhs = rhs self.c_ops = c_ops @@ -247,8 +248,8 @@ def set_state(self, t, state0, generator, self.target_norm = 0.0 else: self.target_norm = ( - self._generator.random() * (1 - jump_prob_floor) - + jump_prob_floor + self._generator.random() * (1 - jump_prob_floor) + + jump_prob_floor ) self._integrator.set_state(t, state0) self._is_set = True diff --git a/qutip/solver/result.py b/qutip/solver/result.py index b1d1bf2bbd..0778a5cfd9 100644 --- a/qutip/solver/result.py +++ b/qutip/solver/result.py @@ -1,9 +1,17 @@ """ Class for solve function results""" + +from typing import TypedDict import numpy as np from ..core import Qobj, QobjEvo, expect, isket, ket2dm, qzero_like -__all__ = ["Result", "MultiTrajResult", "McResult", "NmmcResult", - "McTrajectoryResult", "McResultImprovedSampling"] +__all__ = [ + "Result", + "MultiTrajResult", + "McResult", + "NmmcResult", + "McTrajectoryResult", + "McResultImprovedSampling", +] class _QobjExpectEop: @@ -16,6 +24,7 @@ class _QobjExpectEop: op : :obj:`.Qobj` The expectation value operator. """ + def __init__(self, op): self.op = op @@ -46,6 +55,7 @@ class ExpectOp: op : object The original object used to define the e_op operation. """ + def __init__(self, op, f, append): self.op = op self._f = f @@ -70,6 +80,7 @@ class _BaseResult: """ Common method for all ``Result``. """ + def __init__(self, options, *, solver=None, stats=None): self.solver = solver if stats is None: @@ -81,12 +92,12 @@ def __init__(self, options, *, solver=None, stats=None): # make sure not to store a reference to the solver options_copy = options.copy() - if hasattr(options_copy, '_feedback'): + if hasattr(options_copy, "_feedback"): options_copy._feedback = None self.options = options_copy def _e_ops_to_dict(self, e_ops): - """ Convert the supplied e_ops to a dictionary of Eop instances. """ + """Convert the supplied e_ops to a dictionary of Eop instances.""" if e_ops is None: e_ops = {} elif isinstance(e_ops, (list, tuple)): @@ -118,6 +129,11 @@ def add_processor(self, f, requires_copy=False): self._state_processors_require_copy |= requires_copy +class ResultOptions(TypedDict): + store_states: bool + store_final_state: bool + + class Result(_BaseResult): """ Base class for storing solver results. @@ -199,7 +215,18 @@ class Result(_BaseResult): options : dict The options for this result class. """ - def __init__(self, e_ops, options, *, solver=None, stats=None, **kw): + + options: ResultOptions + + def __init__( + self, + e_ops, + options: ResultOptions, + *, + solver=None, + stats=None, + **kw, + ): super().__init__(options, solver=solver, stats=stats) raw_ops = self._e_ops_to_dict(e_ops) self.e_data = {k: [] for k in raw_ops} @@ -211,7 +238,7 @@ def __init__(self, e_ops, options, *, solver=None, stats=None, **kw): self.times = [] self.states = [] - self.final_state = None + self._final_state = None self._post_init(**kw) @@ -243,29 +270,29 @@ def _post_init(self): Sub-class ``.post_init()`` implementation may take additional keyword arguments if required. """ - store_states = self.options['store_states'] - store_final_state = self.options['store_final_state'] - - if store_states is None: - store_states = len(self.e_ops) == 0 + store_states = self.options["store_states"] + store_states = store_states or ( + len(self.e_ops) == 0 and store_states is None + ) if store_states: self.add_processor(self._store_state, requires_copy=True) - if store_states or store_final_state: + store_final_state = self.options["store_final_state"] + if store_final_state and not store_states: self.add_processor(self._store_final_state, requires_copy=True) def _store_state(self, t, state): - """ Processor that stores a state in ``.states``. """ + """Processor that stores a state in ``.states``.""" self.states.append(state) def _store_final_state(self, t, state): - """ Processor that writes the state to ``.final_state``. """ - self.final_state = state + """Processor that writes the state to ``._final_state``.""" + self._final_state = state def _pre_copy(self, state): - """ Return a copy of the state. Sub-classes may override this to - copy a state in different manner or to skip making a copy - altogether if a copy is not necessary. + """Return a copy of the state. Sub-classes may override this to + copy a state in different manner or to skip making a copy + altogether if a copy is not necessary. """ return state.copy() @@ -311,10 +338,7 @@ def __repr__(self): ] if self.stats: lines.append(" Solver stats:") - lines.extend( - f" {k}: {v!r}" - for k, v in self.stats.items() - ) + lines.extend(f" {k}: {v!r}" for k, v in self.stats.items()) if self.times: lines.append( f" Time interval: [{self.times[0]}, {self.times[-1]}]" @@ -334,6 +358,20 @@ def __repr__(self): def expect(self): return [np.array(e_op) for e_op in self.e_data.values()] + @property + def final_state(self): + if self._final_state is not None: + return self._final_state + if self.states: + return self.states[-1] + return None + + +class MultiTrajResultOptions(TypedDict): + store_states: bool + store_final_state: bool + keep_runs_results: bool + class MultiTrajResult(_BaseResult): """ @@ -455,7 +493,18 @@ class MultiTrajResult(_BaseResult): options : :obj:`~SolverResultsOptions` The options for this result class. """ - def __init__(self, e_ops, options, *, solver=None, stats=None, **kw): + + options: MultiTrajResultOptions + + def __init__( + self, + e_ops, + options: MultiTrajResultOptions, + *, + solver=None, + stats=None, + **kw, + ): super().__init__(options, solver=solver, stats=stats) self._raw_ops = self._e_ops_to_dict(e_ops) @@ -471,15 +520,29 @@ def __init__(self, e_ops, options, *, solver=None, stats=None, **kw): self._target_tols = None self.average_e_data = {} - self.e_data = {} self.std_e_data = {} self.runs_e_data = {} self._post_init(**kw) + @property + def _store_average_density_matricies(self) -> bool: + return ( + self.options["store_states"] + or (self.options["store_states"] is None and self._raw_ops == {}) + ) and not self.options["keep_runs_results"] + + @property + def _store_final_density_matrix(self) -> bool: + return ( + self.options["store_final_state"] + and not self._store_average_density_matricies + and not self.options["keep_runs_results"] + ) + @staticmethod def _to_dm(state): - if state.type == 'ket': + if state.type == "ket": state = state.proj() return state @@ -489,10 +552,12 @@ def _add_first_traj(self, trajectory): """ self.times = trajectory.times - if trajectory.states: - self._sum_states = [qzero_like(self._to_dm(state)) - for state in trajectory.states] - if trajectory.final_state: + if trajectory.states and self._store_average_density_matricies: + self._sum_states = [ + qzero_like(self._to_dm(state)) for state in trajectory.states + ] + + if trajectory.final_state and self._store_final_density_matrix: state = trajectory.final_state self._sum_final_states = qzero_like(self._to_dm(state)) @@ -507,13 +572,10 @@ def _add_first_traj(self, trajectory): self.average_e_data = { k: list(avg_expect) - for k, avg_expect - in zip(self._raw_ops, self._sum_expect) + for k, avg_expect in zip(self._raw_ops, self._sum_expect) } - self.e_data = self.average_e_data - if self.options['keep_runs_results']: + if self.options["keep_runs_results"]: self.runs_e_data = {k: [] for k in self._raw_ops} - self.e_data = self.runs_e_data def _store_trajectory(self, trajectory): self.trajectories.append(trajectory) @@ -521,8 +583,7 @@ def _store_trajectory(self, trajectory): def _reduce_states(self, trajectory): self._sum_states = [ accu + self._to_dm(state) - for accu, state - in zip(self._sum_states, trajectory.states) + for accu, state in zip(self._sum_states, trajectory.states) ] def _reduce_final_state(self, trajectory): @@ -569,7 +630,7 @@ def _fixed_end(self): """ ntraj_left = self._target_ntraj - self.num_trajectories if ntraj_left == 0: - self.stats['end_condition'] = 'ntraj reached' + self.stats["end_condition"] = "ntraj reached" return ntraj_left def _average_computer(self): @@ -586,40 +647,36 @@ def _target_tolerance_end(self): if self.num_trajectories <= 1: return np.inf avg, avg2 = self._average_computer() - target = np.array([ - atol + rtol * mean - for mean, (atol, rtol) - in zip(avg, self._target_tols) - ]) + target = np.array( + [ + atol + rtol * mean + for mean, (atol, rtol) in zip(avg, self._target_tols) + ] + ) target_ntraj = np.max((avg2 - abs(avg) ** 2) / target**2 + 1) self._estimated_ntraj = min(target_ntraj, self._target_ntraj) if (self._estimated_ntraj - self.num_trajectories) <= 0: - self.stats['end_condition'] = 'target tolerance reached' + self.stats["end_condition"] = "target tolerance reached" return self._estimated_ntraj - self.num_trajectories def _post_init(self): self.num_trajectories = 0 self._target_ntraj = None - store_states = self.options['store_states'] - store_final_state = self.options['store_final_state'] - store_traj = self.options['keep_runs_results'] - self.add_processor(self._increment_traj) - if store_traj: + store_trajectory = self.options["keep_runs_results"] + if store_trajectory: self.add_processor(self._store_trajectory) - if store_states is None: - store_states = len(self._raw_ops) == 0 - if store_states: + if self._store_average_density_matricies: self.add_processor(self._reduce_states) - if store_states or store_final_state: + if self._store_final_density_matrix: self.add_processor(self._reduce_final_state) if self._raw_ops: self.add_processor(self._reduce_expect) self._early_finish_check = self._no_end - self.stats['end_condition'] = 'unknown' + self.stats["end_condition"] = "unknown" def add(self, trajectory_info): """ @@ -676,7 +733,7 @@ def add_end_condition(self, ntraj, target_tol=None): Error estimation is done with jackknife resampling. """ self._target_ntraj = ntraj - self.stats['end_condition'] = 'timeout' + self.stats["end_condition"] = "timeout" if target_tol is None: self._early_finish_check = self._fixed_end @@ -691,14 +748,16 @@ def add_end_condition(self, ntraj, target_tol=None): targets = np.array(target_tol) if targets.ndim == 0: - self._target_tols = np.array([(target_tol, 0.)] * num_e_ops) + self._target_tols = np.array([(target_tol, 0.0)] * num_e_ops) elif targets.shape == (2,): self._target_tols = np.ones((num_e_ops, 2)) * targets elif targets.shape == (num_e_ops, 2): self._target_tols = targets else: - raise ValueError("target_tol must be a number, a pair of (atol, " - "rtol) or a list of (atol, rtol) for each e_ops") + raise ValueError( + "target_tol must be a number, a pair of (atol, " + "rtol) or a list of (atol, rtol) for each e_ops" + ) self._early_finish_check = self._target_tolerance_end @@ -718,8 +777,18 @@ def average_states(self): States averages as density matrices. """ if self._sum_states is None: - return None - return [final / self.num_trajectories for final in self._sum_states] + if not (self.trajectories and self.trajectories[0].states): + return None + self._sum_states = [ + qzero_like(self._to_dm(state)) + for state in self.trajectories[0].states + ] + for trajectory in self.trajectories: + self._reduce_states(trajectory) + + return [ + final / self.num_trajectories for final in self._sum_states + ] @property def states(self): @@ -744,6 +813,8 @@ def average_final_state(self): Last states of each trajectories averaged into a density matrix. """ if self._sum_final_states is None: + if self.average_states is not None: + return self.average_states[-1] return None return self._sum_final_states / self.num_trajectories @@ -770,6 +841,10 @@ def runs_expect(self): def expect(self): return [np.array(val) for val in self.e_data.values()] + @property + def e_data(self): + return self.runs_e_data or self.average_e_data + def steady_state(self, N=0): """ Average the states of the last ``N`` times of every runs as a density @@ -796,16 +871,13 @@ def __repr__(self): ] if self.stats: lines.append(" Solver stats:") - lines.extend( - f" {k}: {v!r}" - for k, v in self.stats.items() - ) + lines.extend(f" {k}: {v!r}" for k, v in self.stats.items()) if self.times: lines.append( f" Time interval: [{self.times[0]}, {self.times[-1]}]" f" ({len(self.times)} steps)" ) - lines.append(f" Number of e_ops: {len(self.e_ops)}") + lines.append(f" Number of e_ops: {len(self.e_data)}") if self.states: lines.append(" States saved.") elif self.final_state is not None: @@ -827,23 +899,30 @@ def __add__(self, other): raise ValueError("Shared `e_ops` is required to merge results") if self.times != other.times: raise ValueError("Shared `times` are is required to merge results") - new = self.__class__(self._raw_ops, self.options, - solver=self.solver, stats=self.stats) + new = self.__class__( + self._raw_ops, self.options, solver=self.solver, stats=self.stats + ) if self.trajectories and other.trajectories: new.trajectories = self.trajectories + other.trajectories new.num_trajectories = self.num_trajectories + other.num_trajectories new.times = self.times new.seeds = self.seeds + other.seeds - if self._sum_states is not None and other._sum_states is not None: - new._sum_states = self._sum_states + other._sum_states + if ( + self._sum_states is not None + and other._sum_states is not None + ): + new._sum_states = ( + self._sum_states + other._sum_states + ) if ( self._sum_final_states is not None and other._sum_final_states is not None ): new._sum_final_states = ( - self._sum_final_states + other._sum_final_states + self._sum_final_states + + other._sum_final_states ) new._target_tols = None @@ -854,23 +933,21 @@ def __add__(self, other): for i, k in enumerate(self._raw_ops): new._sum_expect.append(self._sum_expect[i] + other._sum_expect[i]) - new._sum2_expect.append(self._sum2_expect[i] - + other._sum2_expect[i]) + new._sum2_expect.append( + self._sum2_expect[i] + other._sum2_expect[i] + ) avg = new._sum_expect[i] / new.num_trajectories avg2 = new._sum2_expect[i] / new.num_trajectories new.average_e_data[k] = list(avg) - new.e_data = new.average_e_data - new.std_e_data[k] = np.sqrt(np.abs(avg2 - np.abs(avg**2))) - if new.trajectories: + if self.runs_e_data and other.runs_e_data: new.runs_e_data[k] = self.runs_e_data[k] + other.runs_e_data[k] - new.e_data = new.runs_e_data new.stats["run time"] += other.stats["run time"] - new.stats['end_condition'] = "Merged results" + new.stats["end_condition"] = "Merged results" return new @@ -881,8 +958,9 @@ class McTrajectoryResult(Result): """ def __init__(self, e_ops, options, *args, **kwargs): - super().__init__(e_ops, {**options, "normalize_output": False}, - *args, **kwargs) + super().__init__( + e_ops, {**options, "normalize_output": False}, *args, **kwargs + ) class McResult(MultiTrajResult): @@ -922,6 +1000,7 @@ class McResult(MultiTrajResult): For each runs, a list of every collapse as a tuple of the time it happened and the corresponding ``c_ops`` index. """ + # Collapse are only produced by mcsolve. def _add_collapse(self, trajectory): @@ -941,7 +1020,7 @@ def col_times(self): out = [] for col_ in self.collapse: col = list(zip(*col_)) - col = ([] if len(col) == 0 else col[0]) + col = [] if len(col) == 0 else col[0] out.append(col) return out @@ -953,7 +1032,7 @@ def col_which(self): out = [] for col_ in self.collapse: col = list(zip(*col_)) - col = ([] if len(col) == 0 else col[1]) + col = [] if len(col) == 0 else col[1] out.append(col) return out @@ -968,7 +1047,9 @@ def photocurrent(self): for t, which in collapses: cols[which].append(t) mesurement = [ - np.histogram(cols[i], tlist)[0] / np.diff(tlist) / self.num_trajectories + np.histogram(cols[i], tlist)[0] + / np.diff(tlist) + / self.num_trajectories for i in range(self.num_c_ops) ] return mesurement @@ -984,10 +1065,12 @@ def runs_photocurrent(self): cols = [[] for _ in range(self.num_c_ops)] for t, which in collapses: cols[which].append(t) - measurements.append([ - np.histogram(cols[i], tlist)[0] / np.diff(tlist) - for i in range(self.num_c_ops) - ]) + measurements.append( + [ + np.histogram(cols[i], tlist)[0] / np.diff(tlist) + for i in range(self.num_c_ops) + ] + ) return measurements @@ -998,6 +1081,7 @@ class McResultImprovedSampling(McResult, MultiTrajResult): using the improved sampling algorithm, which samples the no-jump trajectory first and then only samples jump trajectories afterwards. """ + def __init__(self, e_ops, options, **kw): MultiTrajResult.__init__(self, e_ops=e_ops, options=options, **kw) self._sum_expect_no_jump = None @@ -1016,14 +1100,16 @@ def _reduce_states(self, trajectory): if self.num_trajectories == 1: self._sum_states_no_jump = [ accu + self._to_dm(state) - for accu, state - in zip(self._sum_states_no_jump, trajectory.states) + for accu, state in zip( + self._sum_states_no_jump, trajectory.states + ) ] else: self._sum_states_jump = [ accu + self._to_dm(state) - for accu, state - in zip(self._sum_states_jump, trajectory.states) + for accu, state in zip( + self._sum_states_jump, trajectory.states + ) ] def _reduce_final_state(self, trajectory): @@ -1040,13 +1126,15 @@ def _average_computer(self): def _add_first_traj(self, trajectory): super()._add_first_traj(trajectory) - if trajectory.states: + if trajectory.states and self._store_average_density_matricies: del self._sum_states - self._sum_states_no_jump = [qzero_like(self._to_dm(state)) - for state in trajectory.states] - self._sum_states_jump = [qzero_like(self._to_dm(state)) - for state in trajectory.states] - if trajectory.final_state: + self._sum_states_no_jump = [ + qzero_like(self._to_dm(state)) for state in trajectory.states + ] + self._sum_states_jump = [ + qzero_like(self._to_dm(state)) for state in trajectory.states + ] + if trajectory.final_state and self._store_final_density_matrix: state = trajectory.final_state del self._sum_final_states self._sum_final_states_no_jump = qzero_like(self._to_dm(state)) @@ -1063,10 +1151,12 @@ def _add_first_traj(self, trajectory): self._sum2_expect_no_jump = [ np.zeros_like(expect) for expect in trajectory.expect ] - self._sum_expect_jump = [np.zeros_like(expect) - for expect in trajectory.expect] - self._sum2_expect_jump = [np.zeros_like(expect) - for expect in trajectory.expect] + self._sum_expect_jump = [ + np.zeros_like(expect) for expect in trajectory.expect + ] + self._sum2_expect_jump = [ + np.zeros_like(expect) for expect in trajectory.expect + ] del self._sum_expect del self._sum2_expect @@ -1088,12 +1178,12 @@ def _reduce_expect(self, trajectory): else: self._sum_expect_jump[i] += expect_traj * (1 - p) self._sum2_expect_jump[i] += expect_traj**2 * (1 - p) - avg = (self._sum_expect_no_jump[i] - + self._sum_expect_jump[i] - / (self.num_trajectories - 1)) - avg2 = (self._sum2_expect_no_jump[i] - + self._sum2_expect_jump[i] - / (self.num_trajectories - 1)) + avg = self._sum_expect_no_jump[i] + ( + self._sum_expect_jump[i] / (self.num_trajectories - 1) + ) + avg2 = self._sum2_expect_no_jump[i] + ( + self._sum2_expect_jump[i] / (self.num_trajectories - 1) + ) self.average_e_data[k] = list(avg) @@ -1110,12 +1200,28 @@ def average_states(self): States averages as density matrices. """ if self._sum_states_no_jump is None: - return None + if not (self.trajectories and self.trajectories[0].states): + return None + self._sum_states_no_jump = [ + qzero_like(self._to_dm(state)) + for state in self.trajectories[0].states + ] + self._sum_states_jump = [ + qzero_like(self._to_dm(state)) + for state in self.trajectories[0].states + ] + self.num_trajectories = 0 + for trajectory in self.trajectories: + self.num_trajectories += 1 + self._reduce_states(trajectory) p = self.no_jump_prob - return [p * final_no_jump - + (1 - p) * final_jump / (self.num_trajectories - 1) - for final_no_jump, final_jump in - zip(self._sum_states_no_jump, self._sum_states_jump)] + return [ + p * final_no_jump + + (1 - p) * final_jump / (self.num_trajectories - 1) + for final_no_jump, final_jump in zip( + self._sum_states_no_jump, self._sum_states_jump + ) + ] @property def average_final_state(self): @@ -1123,11 +1229,11 @@ def average_final_state(self): Last states of each trajectory averaged into a density matrix. """ if self._sum_final_states_no_jump is None: - return None + if self.average_states is not None: + return self.average_states[-1] p = self.no_jump_prob - return ( - p * self._sum_final_states_no_jump - + (1 - p) * self._sum_final_states_jump + return p * self._sum_final_states_no_jump + ( + ((1 - p) * self._sum_final_states_jump) / (self.num_trajectories - 1) ) @@ -1145,8 +1251,10 @@ def photocurrent(self): for t, which in collapses: cols[which].append(t) mesurement = [ - (1 - self.no_jump_prob) / (self.num_trajectories - 1) * - np.histogram(cols[i], tlist)[0] / np.diff(tlist) + (1 - self.no_jump_prob) + / (self.num_trajectories - 1) + * np.histogram(cols[i], tlist)[0] + / np.diff(tlist) for i in range(self.num_c_ops) ] return mesurement @@ -1241,15 +1349,15 @@ def _add_first_traj(self, trajectory): def _add_trace(self, trajectory): new_trace = np.array(trajectory.trace) self._sum_trace += new_trace - self._sum2_trace += np.abs(new_trace)**2 + self._sum2_trace += np.abs(new_trace) ** 2 avg = self._sum_trace / self.num_trajectories avg2 = self._sum2_trace / self.num_trajectories self.average_trace = avg - self.std_trace = np.sqrt(np.abs(avg2 - np.abs(avg)**2)) + self.std_trace = np.sqrt(np.abs(avg2 - np.abs(avg) ** 2)) - if self.options['keep_runs_results']: + if self.options["keep_runs_results"]: self.runs_trace.append(trajectory.trace) @property From 5143be062679bc8570749361fc851dfc6cb002fa Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 6 Feb 2024 16:38:58 -0500 Subject: [PATCH 215/247] Remove mpi from full and update versions --- doc/rtd-environment.yml | 6 +++--- setup.cfg | 1 - 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/doc/rtd-environment.yml b/doc/rtd-environment.yml index dd4e9f26ca..7cafb0adc0 100644 --- a/doc/rtd-environment.yml +++ b/doc/rtd-environment.yml @@ -8,7 +8,7 @@ dependencies: - certifi==2022.12.7 - chardet==4.0.0 - cycler==0.10.0 -- Cython==0.29.33 +- Cython==3.0.8 - decorator==5.1.1 - docutils==0.18.1 - idna==3.4 @@ -19,7 +19,7 @@ dependencies: - kiwisolver==1.4.4 - MarkupSafe==2.1.2 - matplotlib==3.7.1 -- numpy==1.24.2 +- numpy==1.25.2 - numpydoc==1.5.0 - packaging==23.0 - parso==0.8.3 @@ -33,7 +33,7 @@ dependencies: - python-dateutil==2.8.2 - pytz==2023.3 - requests==2.28.2 -- scipy==1.10.1 +- scipy==1.11.4 - six==1.16.0 - snowballstemmer==2.2.0 - Sphinx==6.1.3 diff --git a/setup.cfg b/setup.cfg index 8a3bf41c79..ecb3cc0ee4 100644 --- a/setup.cfg +++ b/setup.cfg @@ -72,4 +72,3 @@ full = %(tests)s %(ipython)s %(extras)s - %(mpi)s From 7b0cca5e2a7ddf1d69b3df0426d402cf84c59eb7 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Wed, 7 Feb 2024 11:27:47 +0900 Subject: [PATCH 216/247] Remove setup-mpi step from build documentation workflow --- .github/workflows/build_documentation.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/workflows/build_documentation.yml b/.github/workflows/build_documentation.yml index 9e952b0a30..b5c6eb9cf6 100644 --- a/.github/workflows/build_documentation.yml +++ b/.github/workflows/build_documentation.yml @@ -10,9 +10,6 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: mpi4py/setup-mpi@v1 - with: - mpi: 'openmpi' - uses: actions/setup-python@v4 name: Install Python From 1316bd6f2e8243af7e1c88bb8fa43d0ee8245d57 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 8 Feb 2024 15:26:31 -0500 Subject: [PATCH 217/247] Ensure final dtype in Qobj creation functions. --- qutip/core/operators.py | 9 ++++++--- qutip/core/states.py | 33 ++++++++++++++++++++++++--------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/qutip/core/operators.py b/qutip/core/operators.py index 2ef1f3ff03..0b3a99f266 100644 --- a/qutip/core/operators.py +++ b/qutip/core/operators.py @@ -588,6 +588,7 @@ def _f_op(n_sites, site, action, dtype=None): oper : qobj Qobj for destruction operator. """ + dtype = dtype or settings.core["default_dtype"] or _data.CSR # get `tensor` and sigma z objects from .tensor import tensor s_z = 2 * jmat(0.5, 'z', dtype=dtype) @@ -614,7 +615,7 @@ def _f_op(n_sites, site, action, dtype=None): eye = identity(2, dtype=dtype) opers = [s_z] * site + [operator] + [eye] * (n_sites - site - 1) - return tensor(opers) + return tensor(opers).to(dtype) def _implicit_tensor_dimensions(dimensions): @@ -798,10 +799,11 @@ def position(N, offset=0, *, dtype=None): oper : qobj Position operator as Qobj. """ + dtype = dtype or settings.core["default_dtype"] or _data.Dia a = destroy(N, offset=offset, dtype=dtype) position = np.sqrt(0.5) * (a + a.dag()) position.isherm = True - return position + return position.to(dtype) def momentum(N, offset=0, *, dtype=None): @@ -826,10 +828,11 @@ def momentum(N, offset=0, *, dtype=None): oper : qobj Momentum operator as Qobj. """ + dtype = dtype or settings.core["default_dtype"] or _data.Dia a = destroy(N, offset=offset, dtype=dtype) momentum = -1j * np.sqrt(0.5) * (a - a.dag()) momentum.isherm = True - return momentum + return momentum.to(dtype) def num(N, offset=0, *, dtype=None): diff --git a/qutip/core/states.py b/qutip/core/states.py index 79bc89529a..a5139b46f9 100644 --- a/qutip/core/states.py +++ b/qutip/core/states.py @@ -295,7 +295,9 @@ def coherent_dm(N, alpha, offset=0, method='operator', *, dtype=None): """ dtype = dtype or settings.core["default_dtype"] or _data.Dense - return coherent(N, alpha, offset=offset, method=method, dtype=dtype).proj() + return coherent( + N, alpha, offset=offset, method=method, dtype=dtype + ).proj().to(dtype) def fock_dm(dimensions, n=None, offset=None, *, dtype=None): @@ -340,7 +342,7 @@ def fock_dm(dimensions, n=None, offset=None, *, dtype=None): """ dtype = dtype or settings.core["default_dtype"] or _data.Dia - return basis(dimensions, n, offset=offset, dtype=dtype).proj() + return basis(dimensions, n, offset=offset, dtype=dtype).proj().to(dtype) def fock(dimensions, n=None, offset=None, *, dtype=None): @@ -550,8 +552,10 @@ def projection(N, n, m, offset=None, *, dtype=None): Requested projection operator. """ dtype = dtype or settings.core["default_dtype"] or _data.CSR - return basis(N, n, offset=offset, dtype=dtype) @ \ - basis(N, m, offset=offset, dtype=dtype).dag() + return ( + basis(N, n, offset=offset, dtype=dtype) @ \ + basis(N, m, offset=offset, dtype=dtype).dag() + ).to(dtype) def qstate(string, *, dtype=None): @@ -1154,10 +1158,15 @@ def triplet_states(*, dtype=None): trip_states : list 2 particle triplet states """ + dtype = dtype or settings.core["default_dtype"] or _data.Dense return [ basis([2, 2], [1, 1], dtype=dtype), - np.sqrt(0.5) * (basis([2, 2], [0, 1], dtype=dtype) + - basis([2, 2], [1, 0], dtype=dtype)), + ( + np.sqrt(0.5) * ( + basis([2, 2], [0, 1], dtype=dtype) + + basis([2, 2], [1, 0], dtype=dtype) + ) + ).to(dtype), basis([2, 2], [0, 0], dtype=dtype), ] @@ -1181,12 +1190,13 @@ def w_state(N=3, *, dtype=None): W : :obj:`.Qobj` N-qubit W-state """ + dtype = dtype or settings.core["default_dtype"] or _data.Dense inds = np.zeros(N, dtype=int) inds[0] = 1 state = basis([2]*N, list(inds), dtype=dtype) for kk in range(1, N): state += basis([2]*N, list(np.roll(inds, kk)), dtype=dtype) - return np.sqrt(1 / N) * state + return (np.sqrt(1 / N) * state).to(dtype) def ghz_state(N=3, *, dtype=None): @@ -1208,5 +1218,10 @@ def ghz_state(N=3, *, dtype=None): G : qobj N-qubit GHZ-state """ - return np.sqrt(0.5) * (basis([2]*N, [0]*N, dtype=dtype) + - basis([2]*N, [1]*N, dtype=dtype)) + dtype = dtype or settings.core["default_dtype"] or _data.Dense + return ( + np.sqrt(0.5) * ( + basis([2]*N, [0]*N, dtype=dtype) + + basis([2]*N, [1]*N, dtype=dtype) + ) + ).to(dtype) From 42fe13305c1ae12353ba8a720321ba2ef82f63e2 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 13 Feb 2024 11:11:06 -0500 Subject: [PATCH 218/247] Reword --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 98fab16f38..af98f064bf 100644 --- a/README.md +++ b/README.md @@ -49,8 +49,8 @@ Support We are proud to be affiliated with [Unitary Fund](https://unitary.fund) and [numFOCUS](https://numfocus.org). -[Nori's lab](https://dml.riken.jp/) at RIKEN and [Blais' lab](https://www.physique.usherbrooke.ca/blais/) at the Institut Quantique -have been providing developers to work on QuTiP. +We are grateful for [Nori's lab](https://dml.riken.jp/) at RIKEN and [Blais' lab](https://www.physique.usherbrooke.ca/blais/) at the Institut Quantique +for providing developer positions to work on QuTiP. We also thank Google for supporting us by financing GSoC student to work on the QuTiP as well as [other supporting organizations](https://qutip.org/#supporting-organizations) that have been supporting QuTiP over the years. From 05cdbb0aae8f6d2d6894c5ce60e93ef785c8f491 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Tue, 13 Feb 2024 14:37:58 -0500 Subject: [PATCH 219/247] Update README.md Co-authored-by: Simon Cross --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index af98f064bf..ba6dbf7c6c 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ We are proud to be affiliated with [Unitary Fund](https://unitary.fund) and [num We are grateful for [Nori's lab](https://dml.riken.jp/) at RIKEN and [Blais' lab](https://www.physique.usherbrooke.ca/blais/) at the Institut Quantique for providing developer positions to work on QuTiP. -We also thank Google for supporting us by financing GSoC student to work on the QuTiP as well as [other supporting organizations](https://qutip.org/#supporting-organizations) that have been supporting QuTiP over the years. +We also thank Google for supporting us by financing GSoC students to work on the QuTiP as well as [other supporting organizations](https://qutip.org/#supporting-organizations) that have been supporting QuTiP over the years. Installation From 77def926e817c4fe58ee23f9855e87e949ca2f6f Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 14 Feb 2024 10:43:54 -0500 Subject: [PATCH 220/247] Add dtype to Qobj(Evo) and use it in ..._like functions --- qutip/core/cy/_element.pyx | 12 ++++++++++++ qutip/core/cy/qobjevo.pyx | 15 +++++++++++++++ qutip/core/operators.py | 15 ++++++--------- qutip/core/qobj.py | 4 ++++ qutip/tests/core/test_operators.py | 4 ++++ qutip/tests/core/test_qobj.py | 6 ++++++ qutip/tests/core/test_qobjevo.py | 16 ++++++++++++++++ 7 files changed, 63 insertions(+), 9 deletions(-) diff --git a/qutip/core/cy/_element.pyx b/qutip/core/cy/_element.pyx index cfe3154742..d77f3e6dfd 100644 --- a/qutip/core/cy/_element.pyx +++ b/qutip/core/cy/_element.pyx @@ -267,6 +267,10 @@ cdef class _BaseElement: return new.qobj(t) * new.coeff(t) return self.qobj(t) * self.coeff(t) + @property + def dtype(self): + return None + cdef class _ConstantElement(_BaseElement): """ @@ -313,6 +317,10 @@ cdef class _ConstantElement(_BaseElement): def __call__(self, t, args=None): return self._qobj + @property + def dtype(self): + return type(self._data) + cdef class _EvoElement(_BaseElement): """ @@ -370,6 +378,10 @@ cdef class _EvoElement(_BaseElement): self._coefficient.replace_arguments(args) ) + @property + def dtype(self): + return type(self._data) + cdef class _FuncElement(_BaseElement): """ diff --git a/qutip/core/cy/qobjevo.pyx b/qutip/core/cy/qobjevo.pyx index c2b4d5d3ae..f508189c31 100644 --- a/qutip/core/cy/qobjevo.pyx +++ b/qutip/core/cy/qobjevo.pyx @@ -947,6 +947,21 @@ cdef class QobjEvo: """Indicates if the system represents a operator-bra state.""" return self._dims.type == 'operator-bra' + @property + def dtype(self): + """ + Type of the data layers of the QobjEvo. + When different data layers are used, we return the type of the sum of + the parts. + """ + part_types = [part.dtype for part in self.elements] + if ( + part_types[0] is not None + and all(part == part_types[0] for part in part_types) + ): + return part_types[0] + return self(0).dtype + ########################################################################### # operation methods # ########################################################################### diff --git a/qutip/core/operators.py b/qutip/core/operators.py index 2ef1f3ff03..c89e170959 100644 --- a/qutip/core/operators.py +++ b/qutip/core/operators.py @@ -691,11 +691,9 @@ def qzero_like(qobj): Zero operator Qobj. """ - from .cy.qobjevo import QobjEvo - if isinstance(qobj, QobjEvo): - qobj = qobj(0) + return Qobj( - _data.zeros_like(qobj.data), dims=qobj._dims, + _data.zeros[qobj.dtype](*qobj.shape), dims=qobj._dims, isherm=True, isunitary=False, copy=False ) @@ -767,12 +765,11 @@ def qeye_like(qobj): Identity operator Qobj. """ - from .cy.qobjevo import QobjEvo - if isinstance(qobj, QobjEvo): - qobj = qobj(0) + if qobj.shape[0] != qobj.shape[1]: + raise ValueError(f"Cannot build a {qobj.shape} identity matrix.") return Qobj( - _data.identity_like(qobj.data), dims=qobj._dims, - isherm=True, isunitary=True, copy=False + _data.identity[qobj.dtype](qobj.shape[0]), dims=qobj._dims, + isherm=True, isunitary=False, copy=False ) diff --git a/qutip/core/qobj.py b/qutip/core/qobj.py index bdc72ca67e..2b4dcda24f 100644 --- a/qutip/core/qobj.py +++ b/qutip/core/qobj.py @@ -330,6 +330,10 @@ def superrep(self, super_rep): def data(self): return self._data + @property + def dtype(self): + return type(self._data) + @data.setter def data(self, data): if not isinstance(data, _data.Data): diff --git a/qutip/tests/core/test_operators.py b/qutip/tests/core/test_operators.py index 46fa020481..77eeb70ff3 100644 --- a/qutip/tests/core/test_operators.py +++ b/qutip/tests/core/test_operators.py @@ -320,10 +320,12 @@ def test_qeye_like(dims, superrep, dtype): expected = qutip.qeye(dims, dtype=dtype) expected.superrep = superrep assert new == expected + assert new.dtype is qutip.data.to.parse(dtype) opevo = qutip.QobjEvo(op) new = qutip.qeye_like(op) assert new == expected + assert new.dtype is qutip.data.to.parse(dtype) @pytest.mark.parametrize(["dims", "superrep"], [ @@ -340,10 +342,12 @@ def test_qzero_like(dims, superrep, dtype): expected = qutip.qzero(dims, dtype=dtype) expected.superrep = superrep assert new == expected + assert new.dtype is qutip.data.to.parse(dtype) opevo = qutip.QobjEvo(op) new = qutip.qzero_like(op) assert new == expected + assert new.dtype is qutip.data.to.parse(dtype) @pytest.mark.parametrize('n_sites', [2, 3, 4, 5]) diff --git a/qutip/tests/core/test_qobj.py b/qutip/tests/core/test_qobj.py index 9ad09fe6fa..7aa7dc0b3b 100644 --- a/qutip/tests/core/test_qobj.py +++ b/qutip/tests/core/test_qobj.py @@ -1262,3 +1262,9 @@ def test_data_as(): with pytest.raises(ValueError) as err: qobj.data_as("ndarray") assert "dia_matrix" in str(err.value) + + +@pytest.mark.parametrize('dtype', ["CSR", "Dense"]) +def test_qobj_dtype(dtype): + obj = qutip.qeye(2, dtype=dtype) + assert obj.dtype == qutip.data.to.parse(dtype) \ No newline at end of file diff --git a/qutip/tests/core/test_qobjevo.py b/qutip/tests/core/test_qobjevo.py index 9fc8454d15..6fb6211d64 100644 --- a/qutip/tests/core/test_qobjevo.py +++ b/qutip/tests/core/test_qobjevo.py @@ -6,6 +6,7 @@ rand_herm, rand_ket, liouvillian, basis, spre, spost, to_choi, expect, rand_ket, rand_dm, operator_to_vector, SESolver, MESolver ) +import qutip.core.data as _data import numpy as np from numpy.testing import assert_allclose @@ -623,3 +624,18 @@ def test_feedback_super(): checker.state = rand_dm(4) checker.state.dims = [[[2],[2]], [[2],[2]]] qevo.matmul_data(0, checker.state.data) + + +@pytest.mark.parametrize('dtype', ["CSR", "Dense"]) +def test_qobjevo_dtype(dtype): + obj = QobjEvo([qeye(2, dtype=dtype), [num(2, dtype=dtype), lambda t: t]]) + assert obj.dtype == _data.to.parse(dtype) + + obj = QobjEvo(lambda t: qeye(2, dtype=dtype)) + assert obj.dtype == _data.to.parse(dtype) + + +def test_qobjevo_mixed(): + obj = QobjEvo([qeye(2, dtype="CSR"), [num(2, dtype="Dense"), lambda t: t]]) + # We test that the output dtype is a know type: accepted by `to.parse`. + _data.to.parse(obj.dtype) \ No newline at end of file From 88e95ceaef3d0ee5e4531c73650f52aa51bd658a Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 14 Feb 2024 11:11:33 -0500 Subject: [PATCH 221/247] Improve error message --- qutip/core/data/constant.py | 4 ++-- qutip/core/operators.py | 4 +++- qutip/tests/core/test_operators.py | 7 +++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/qutip/core/data/constant.py b/qutip/core/data/constant.py index 8061bb8832..52f9f50bfd 100644 --- a/qutip/core/data/constant.py +++ b/qutip/core/data/constant.py @@ -96,7 +96,7 @@ def identity_like_data(data, /): Create an identity matrix of the same type and shape. """ if not data.shape[0] == data.shape[1]: - raise ValueError("Can't create and identity like a non square matrix.") + raise ValueError("Can't create an identity matrix like a non square matrix.") return identity[type(data)](data.shape[0]) @@ -105,7 +105,7 @@ def identity_like_dense(data, /): Create an identity matrix of the same type and shape. """ if not data.shape[0] == data.shape[1]: - raise ValueError("Can't create and identity like a non square matrix.") + raise ValueError("Can't create an identity matrix like a non square matrix.") return dense.identity(data.shape[0], fortran=data.fortran) diff --git a/qutip/core/operators.py b/qutip/core/operators.py index c89e170959..5ab8521e01 100644 --- a/qutip/core/operators.py +++ b/qutip/core/operators.py @@ -766,7 +766,9 @@ def qeye_like(qobj): """ if qobj.shape[0] != qobj.shape[1]: - raise ValueError(f"Cannot build a {qobj.shape} identity matrix.") + raise ValueError( + "Can't create an identity matrix like a non square matrix." + ) return Qobj( _data.identity[qobj.dtype](qobj.shape[0]), dims=qobj._dims, isherm=True, isunitary=False, copy=False diff --git a/qutip/tests/core/test_operators.py b/qutip/tests/core/test_operators.py index 77eeb70ff3..625385def3 100644 --- a/qutip/tests/core/test_operators.py +++ b/qutip/tests/core/test_operators.py @@ -328,6 +328,13 @@ def test_qeye_like(dims, superrep, dtype): assert new.dtype is qutip.data.to.parse(dtype) +def test_qeye_like_error(): + with pytest.raises(ValueError) as err: + qutip.qeye_like(qutip.basis(3)) + + assert "non square matrix" in str(err.value) + + @pytest.mark.parametrize(["dims", "superrep"], [ pytest.param([2], None, id="simple"), pytest.param([2, 3], None, id="tensor"), From e8f36e2d274296f26298814d6dd7539939b41a56 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 14 Feb 2024 11:33:51 -0500 Subject: [PATCH 222/247] Add towncrier --- doc/changes/2325.misc | 1 + qutip/core/data/constant.py | 8 ++++++-- 2 files changed, 7 insertions(+), 2 deletions(-) create mode 100644 doc/changes/2325.misc diff --git a/doc/changes/2325.misc b/doc/changes/2325.misc new file mode 100644 index 0000000000..9b64dc7253 --- /dev/null +++ b/doc/changes/2325.misc @@ -0,0 +1 @@ +Add `dtype` to `Qobj` and `QobjEvo` \ No newline at end of file diff --git a/qutip/core/data/constant.py b/qutip/core/data/constant.py index 52f9f50bfd..d6264c5f89 100644 --- a/qutip/core/data/constant.py +++ b/qutip/core/data/constant.py @@ -96,7 +96,9 @@ def identity_like_data(data, /): Create an identity matrix of the same type and shape. """ if not data.shape[0] == data.shape[1]: - raise ValueError("Can't create an identity matrix like a non square matrix.") + raise ValueError( + "Can't create an identity matrix like a non square matrix." + ) return identity[type(data)](data.shape[0]) @@ -105,7 +107,9 @@ def identity_like_dense(data, /): Create an identity matrix of the same type and shape. """ if not data.shape[0] == data.shape[1]: - raise ValueError("Can't create an identity matrix like a non square matrix.") + raise ValueError( + "Can't create an identity matrix like a non square matrix." + ) return dense.identity(data.shape[0], fortran=data.fortran) From fa3a0732a6b818ddccc5de6d6c88d30f28e84a2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Thu, 15 Feb 2024 08:38:05 -0500 Subject: [PATCH 223/247] Update qutip/core/operators.py --- qutip/core/operators.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/core/operators.py b/qutip/core/operators.py index 5ab8521e01..a220286758 100644 --- a/qutip/core/operators.py +++ b/qutip/core/operators.py @@ -771,7 +771,7 @@ def qeye_like(qobj): ) return Qobj( _data.identity[qobj.dtype](qobj.shape[0]), dims=qobj._dims, - isherm=True, isunitary=False, copy=False + isherm=True, isunitary=True, copy=False ) From 398027402827bd5f5c1a0a6dd998611bc2518f06 Mon Sep 17 00:00:00 2001 From: Paul Menczel Date: Wed, 21 Feb 2024 15:34:45 +0900 Subject: [PATCH 224/247] Qobj minor fixes --- qutip/core/qobj.py | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/qutip/core/qobj.py b/qutip/core/qobj.py index 2b4dcda24f..c050143ecd 100644 --- a/qutip/core/qobj.py +++ b/qutip/core/qobj.py @@ -147,12 +147,10 @@ class Qobj: Parameters ---------- - inpt: array_like + inpt: array_like, data object or :obj:`.Qobj` Data for vector/matrix representation of the quantum object. dims: list Dimensions of object used for tensor products. - type: {'bra', 'ket', 'oper', 'operator-ket', 'operator-bra', 'super'} - The type of quantum object to be represented. shape: list Shape of underlying data structure (matrix shape). copy: bool @@ -162,8 +160,12 @@ class Qobj: Attributes ---------- - data : array_like - Sparse matrix characterizing the quantum object. + data : object + The data object storing the vector / matrix representation of the + `Qobj`. + dtype : type + The data-layer type used for storing the data. The possible types are + described in `Qobj.to <./classes.html#qutip.core.qobj.Qobj.to>`__. dims : list List of dimensions keeping track of the tensor structure. shape : list @@ -173,7 +175,8 @@ class Qobj: 'operator-bra', or 'super'. superrep : str Representation used if `type` is 'super'. One of 'super' - (Liouville form) or 'choi' (Choi matrix with tr = dimension). + (Liouville form), 'choi' (Choi matrix with tr = dimension), + or 'chi' (chi-matrix representation). isherm : bool Indicates if quantum object represents Hermitian operator. isunitary : bool @@ -211,10 +214,16 @@ class Qobj: Create copy of Qobj conj() Conjugate of quantum object. + contract() + Contract subspaces of the tensor structure which are 1D. cosm() Cosine of quantum object. dag() Adjoint (dagger) of quantum object. + data_as(format, copy) + Vector / matrix representation of quantum object. + diag() + Diagonal elements of quantum object. dnorm() Diamond norm of quantum operator. dual_chan() @@ -232,10 +241,14 @@ class Qobj: object. inv() Return a Qobj corresponding to the matrix inverse of the operator. + logm() + Matrix logarithm of quantum operator. matrix_element(bra, ket) Returns the matrix element of operator between `bra` and `ket` vectors. norm(norm='tr', sparse=False, tol=0, maxiter=100000) Returns norm of a ket or an operator. + overlap(other) + Overlap between two state vectors or two operators. permute(order) Returns composite qobj with indices reordered. proj() @@ -243,6 +256,8 @@ class Qobj: ptrace(sel) Returns quantum object for selected dimensions after performing partial trace. + purity() + Calculates the purity of a quantum object. sinm() Sine of quantum object. sqrtm() @@ -340,7 +355,7 @@ def data(self, data): raise TypeError('Qobj data must be a data-layer format.') if self._dims.shape != data.shape: raise ValueError('Provided data do not match the dimensions: ' + - f"{dims.shape} vs {self._data.shape}") + f"{self._dims.shape} vs {data.shape}") self._data = data def to(self, data_type): @@ -904,7 +919,6 @@ def sqrtm(self, sparse=False, tol=0, maxiter=100000): else: evals, evecs = _data.eigs(self.data, isherm=self._isherm) - numevals = len(evals) dV = _data.diag([np.sqrt(evals, dtype=complex)], 0) if self.isherm: spDv = _data.matmul(dV, evecs.conj().transpose()) From 0f68d9078116997556e9052f764536325a7e171d Mon Sep 17 00:00:00 2001 From: Neill Lambert Date: Sat, 24 Feb 2024 15:22:05 +0900 Subject: [PATCH 225/247] Fix steady-state errors When returning the system state two errors crept into v5 that were not in v47; the ordering of the state was not specified as Fortran ordering, and a conjugate was used instead of an adjoint in ensuring hermiticity of states. Both are fixed, and a small test is added. --- qutip/solver/heom/bofin_solvers.py | 7 +++--- qutip/tests/solver/heom/test_bofin_solvers.py | 24 +++++++++++++++++-- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/qutip/solver/heom/bofin_solvers.py b/qutip/solver/heom/bofin_solvers.py index 23b82f629f..9fdf90a54d 100644 --- a/qutip/solver/heom/bofin_solvers.py +++ b/qutip/solver/heom/bofin_solvers.py @@ -965,9 +965,10 @@ def steady_state( else: L = L.tocsc() solution = spsolve(L, b_mat) - - data = _data.Dense(solution[:n ** 2].reshape((n, n))) - data = _data.mul(_data.add(data, data.conj()), 0.5) + + data = _data.Dense(solution[:n ** 2].reshape((n, n), order = 'F')) + data = _data.mul(_data.add(data, data.adjoint()), 0.5) + steady_state = Qobj(data, dims=self._sys_dims) solution = solution.reshape((self._n_ados, n, n)) diff --git a/qutip/tests/solver/heom/test_bofin_solvers.py b/qutip/tests/solver/heom/test_bofin_solvers.py index ad6e5f2297..be43122c55 100644 --- a/qutip/tests/solver/heom/test_bofin_solvers.py +++ b/qutip/tests/solver/heom/test_bofin_solvers.py @@ -8,8 +8,8 @@ from scipy.integrate import quad from qutip import ( - basis, destroy, expect, liouvillian, qeye, sigmax, sigmaz, - tensor, Qobj, QobjEvo + basis, destroy, expect, liouvillian, qeye, sigmax, sigmaz, sigmay, + tensor, Qobj, QobjEvo, fidelity ) from qutip.core import data as _data from qutip.solver.heom.bofin_baths import ( @@ -740,6 +740,26 @@ def test_pure_dephasing_model_bosonic_bath( assert rho_final == ado_state.extract(0) else: assert_raises_steady_state_time_dependent(hsolver) + + + def test_steady_state_bosonic_bath( + self, atol=1e-3 + ): + H_sys = 0.25 * sigmaz() + 0.5 * sigmay() + + bath = DrudeLorentzBath(sigmaz(), lam=0.025, gamma=0.05, T=1/0.95, Nk=2) + options = {"nsteps": 15000, "store_states": True} + hsolver = HEOMSolver(H_sys, bath, 5, options=options) + + tlist = np.linspace(0, 500, 21) + rho0 = basis(2, 0) * basis(2, 0).dag() + + result = hsolver.run(rho0, tlist) + rho_final, ado_state = hsolver.steady_state() + fid = fidelity(rho_final, result.states[-1]) + np.testing.assert_allclose(fid, 1.0, atol=atol) + + @pytest.mark.parametrize(['terminator'], [ pytest.param(True, id="terminator"), From 33157272c0810514ac1058b354b041656ce28f28 Mon Sep 17 00:00:00 2001 From: Neill Lambert Date: Sat, 24 Feb 2024 15:34:36 +0900 Subject: [PATCH 226/247] Pep8 fixes Just some small pep8 fixes. --- qutip/solver/heom/bofin_solvers.py | 5 ++--- qutip/tests/solver/heom/test_bofin_solvers.py | 10 ++++------ 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/qutip/solver/heom/bofin_solvers.py b/qutip/solver/heom/bofin_solvers.py index 9fdf90a54d..1061c1c492 100644 --- a/qutip/solver/heom/bofin_solvers.py +++ b/qutip/solver/heom/bofin_solvers.py @@ -965,10 +965,9 @@ def steady_state( else: L = L.tocsc() solution = spsolve(L, b_mat) - - data = _data.Dense(solution[:n ** 2].reshape((n, n), order = 'F')) + + data = _data.Dense(solution[:n ** 2].reshape((n, n), order='F')) data = _data.mul(_data.add(data, data.adjoint()), 0.5) - steady_state = Qobj(data, dims=self._sys_dims) solution = solution.reshape((self._n_ados, n, n)) diff --git a/qutip/tests/solver/heom/test_bofin_solvers.py b/qutip/tests/solver/heom/test_bofin_solvers.py index be43122c55..b704b015db 100644 --- a/qutip/tests/solver/heom/test_bofin_solvers.py +++ b/qutip/tests/solver/heom/test_bofin_solvers.py @@ -740,14 +740,14 @@ def test_pure_dephasing_model_bosonic_bath( assert rho_final == ado_state.extract(0) else: assert_raises_steady_state_time_dependent(hsolver) - - + def test_steady_state_bosonic_bath( self, atol=1e-3 ): - H_sys = 0.25 * sigmaz() + 0.5 * sigmay() + H_sys = 0.25 * sigmaz() + 0.5 * sigmay() - bath = DrudeLorentzBath(sigmaz(), lam=0.025, gamma=0.05, T=1/0.95, Nk=2) + bath = DrudeLorentzBath(sigmaz(), lam=0.025, + gamma=0.05, T=1/0.95, Nk=2) options = {"nsteps": 15000, "store_states": True} hsolver = HEOMSolver(H_sys, bath, 5, options=options) @@ -758,8 +758,6 @@ def test_steady_state_bosonic_bath( rho_final, ado_state = hsolver.steady_state() fid = fidelity(rho_final, result.states[-1]) np.testing.assert_allclose(fid, 1.0, atol=atol) - - @pytest.mark.parametrize(['terminator'], [ pytest.param(True, id="terminator"), From 93177ff4776bfacd6b1d142ff2b9bdc9cc039fbc Mon Sep 17 00:00:00 2001 From: Neill Lambert Date: Sat, 24 Feb 2024 15:55:18 +0900 Subject: [PATCH 227/247] Add towncrier entry Add towncrier entry. --- doc/changes/2333.bugfix | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/2333.bugfix diff --git a/doc/changes/2333.bugfix b/doc/changes/2333.bugfix new file mode 100644 index 0000000000..c8a1bda03b --- /dev/null +++ b/doc/changes/2333.bugfix @@ -0,0 +1 @@ +Fixed two problems with the steady_state() solver in the HEOM method. \ No newline at end of file From 45804e37d2000c157dc494f39f1fb35cb5dffa4f Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 26 Feb 2024 09:07:58 -0500 Subject: [PATCH 228/247] Fix typos in `expect` documentation --- doc/changes/2331.misc | 1 + qutip/core/expect.py | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 doc/changes/2331.misc diff --git a/doc/changes/2331.misc b/doc/changes/2331.misc new file mode 100644 index 0000000000..4231d540fd --- /dev/null +++ b/doc/changes/2331.misc @@ -0,0 +1 @@ +Fix typos in `expect` documentation diff --git a/qutip/core/expect.py b/qutip/core/expect.py index 9051c53f72..bb72fccd95 100644 --- a/qutip/core/expect.py +++ b/qutip/core/expect.py @@ -16,7 +16,7 @@ def expect(oper, state): Parameters ---------- oper : qobj/array-like - A single or a `list` or operators for expectation value. + A single or a `list` of operators for expectation value. state : qobj/array-like A single or a `list` of quantum states or density matrices. @@ -25,8 +25,8 @@ def expect(oper, state): ------- expt : float/complex/array-like Expectation value. ``real`` if ``oper`` is Hermitian, ``complex`` - otherwise. A (nested) array of expectaction values of state or operator - are arrays. + otherwise. A (nested) array of expectaction values if ``state`` or + ``oper`` are arrays. Examples -------- From e90ddcb49a1b0190546372639894a2ef9cf0150a Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 27 Feb 2024 14:10:56 -0500 Subject: [PATCH 229/247] Fix bloch.clear breaking point_color --- qutip/bloch.py | 2 +- qutip/tests/test_bloch.py | 17 +++++++++++++++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/qutip/bloch.py b/qutip/bloch.py index 5a918ca311..f64b56c6a9 100644 --- a/qutip/bloch.py +++ b/qutip/bloch.py @@ -803,7 +803,7 @@ def plot_points(self): if self._inner_point_color[k] is not None: color = self._inner_point_color[k] - elif self.point_color is not None: + elif self.point_color not in [None, []]: color = self.point_color elif self.point_style[k] in ['s', 'l']: color = [self.point_default_color[ diff --git a/qutip/tests/test_bloch.py b/qutip/tests/test_bloch.py index f503302606..c1faafdf3d 100644 --- a/qutip/tests/test_bloch.py +++ b/qutip/tests/test_bloch.py @@ -470,6 +470,23 @@ def test_vector_errors_color_length(self, vectors, colors): "size as the number of vectors. ") assert str(err.value) == err_msg + @check_pngs_equal + def test_clear(self, fig_test=None, fig_ref=None): + b = Bloch(fig=fig_test) + b.add_vectors([0, 1, 0]) + b.add_points([1, 0, 0]) + b.vector_color = ["g"] + b.point_color = ["g"] + b.clear() + b.add_vectors([0, 0, 1]) + b.add_points([0, 0, -1]) + b.make_sphere() + + b2 = Bloch(fig=fig_ref) + b2.add_vectors([0, 0, 1]) + b2.add_points([0, 0, -1]) + b2.make_sphere() + def test_repr_svg(): pytest.importorskip("matplotlib") From f9fc0972d68208ea64d63fcded553f1beed08723 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Wed, 28 Feb 2024 09:42:38 -0500 Subject: [PATCH 230/247] Increase test reliability --- qutip/tests/solver/test_floquet.py | 2 +- qutip/tests/test_random.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/qutip/tests/solver/test_floquet.py b/qutip/tests/solver/test_floquet.py index 679a3cba80..3c990d0c0f 100644 --- a/qutip/tests/solver/test_floquet.py +++ b/qutip/tests/solver/test_floquet.py @@ -51,7 +51,7 @@ def testFloquetBasis(self): states = sesolve(H, psi0, tlist).states for t, state in zip(tlist, states): from_floquet = floquet_basis.from_floquet_basis(floquet_psi0, t) - assert state.overlap(from_floquet) == pytest.approx(1., abs=5e-5) + assert state.overlap(from_floquet) == pytest.approx(1., abs=8e-5) def testFloquetUnitary(self): N = 10 diff --git a/qutip/tests/test_random.py b/qutip/tests/test_random.py index 8d6ff441e8..085e940448 100644 --- a/qutip/tests/test_random.py +++ b/qutip/tests/test_random.py @@ -222,7 +222,7 @@ def test_rand_super(dimensions, dtype, superrep): """ random_qobj = rand_super(dimensions, dtype=dtype, superrep=superrep) assert random_qobj.issuper - with CoreOptions(atol=1e-9): + with CoreOptions(atol=2e-9): assert random_qobj.iscptp assert random_qobj.superrep == superrep _assert_metadata(random_qobj, dimensions, dtype, super=True) From b9e783c9f983c4da5657d375c08e12fa45249064 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Thu, 29 Feb 2024 12:55:48 -0500 Subject: [PATCH 231/247] Fix whitespace typos added in the conflict resolution commit. --- qutip/visualization.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/qutip/visualization.py b/qutip/visualization.py index d0f55e36a7..cc37c20ba8 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -1161,7 +1161,7 @@ def plot_wigner(rho, xvec=None, yvec=None, method='clenshaw', parfor : bool {False, True} Flag for parallel calculation. See the documentation for qutip.wigner for details. - + projection: str {'2d', '3d'}, default: '2d' Specify whether the Wigner function is to be plotted as a contour graph ('2d') or surface plot ('3d'). @@ -1175,7 +1175,7 @@ def plot_wigner(rho, xvec=None, yvec=None, method='clenshaw', fig : a matplotlib Figure instance, optional The Figure canvas in which the plot will be drawn. - + ax : a matplotlib axes instance, optional The axes context in which the plot will be drawn. @@ -1193,7 +1193,6 @@ def plot_wigner(rho, xvec=None, yvec=None, method='clenshaw', if not isinstance(rho, list): rhos = [rho] - else: rhos = rho From ed0784920e7960e8e04171902213e09403f874e6 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Thu, 29 Feb 2024 17:15:53 -0500 Subject: [PATCH 232/247] Makes measurement support degenerate states --- qutip/__init__.py | 2 +- qutip/measurement.py | 136 +++++++++++++++++++++++--------- qutip/tests/test_measurement.py | 35 ++++---- 3 files changed, 117 insertions(+), 56 deletions(-) diff --git a/qutip/__init__.py b/qutip/__init__.py index 19f373c8b1..3fe38033a1 100644 --- a/qutip/__init__.py +++ b/qutip/__init__.py @@ -50,7 +50,7 @@ from .partial_transpose import * from .continuous_variables import * from .distributions import * - +from . import measurement # utilities from .utilities import * diff --git a/qutip/measurement.py b/qutip/measurement.py index da90387dd0..a745b92dca 100644 --- a/qutip/measurement.py +++ b/qutip/measurement.py @@ -13,7 +13,7 @@ import numpy as np -from . import Qobj, expect, identity, tensor +from . import Qobj, expect, identity, tensor, settings def _verify_input(op, state): @@ -36,7 +36,7 @@ def _verify_input(op, state): raise ValueError("state must be a ket or a density matrix") -def _measurement_statistics_povm_ket(state, ops): +def _measurement_statistics_povm_ket(state, ops, tol=None): r""" Returns measurement statistics (resultant states and probabilities) for a measurements specified by a set of positive operator valued @@ -51,6 +51,9 @@ def _measurement_statistics_povm_ket(state, ops): List of measurement operators :math:`M_i` (specifying a POVM such that :math:`E_i = M_i^\dagger M_i`). + tol : float, optional + Smallest value for the probabilities. Smaller probabilities will be + rounded to ``0``. Returns ------- @@ -65,19 +68,23 @@ def _measurement_statistics_povm_ket(state, ops): """ probabilities = [] collapsed_states = [] + if tol is None: + tol = settings.core["atol"] for i, op in enumerate(ops): - p = np.absolute((state.dag() * op.dag() * op * state)) - probabilities.append(p) - if p != 0: - collapsed_states.append((op * state) / np.sqrt(p)) + psi = op * state + p = np.absolute(psi.overlap(psi)) + if p >= tol: + collapsed_states.append(psi / np.sqrt(p)) + probabilities.append(p) else: collapsed_states.append(None) + probabilities.append(0.) return collapsed_states, probabilities -def _measurement_statistics_povm_dm(density_mat, ops): +def _measurement_statistics_povm_dm(density_mat, ops, tol=None): r""" Returns measurement statistics (resultant states and probabilities) for a measurements specified by a set of positive operator valued @@ -92,6 +99,10 @@ def _measurement_statistics_povm_dm(density_mat, ops): List of measurement operators :math:`M_i` (specifying a POVM s.t. :mathm:`E_i = M_i^\dagger M_i`) + tol : float, optional + Smallest value for the probabilities. Smaller probabilities will be + rounded to ``0``. + Returns ------- collapsed_states : list of :class:`.Qobj` @@ -105,20 +116,24 @@ def _measurement_statistics_povm_dm(density_mat, ops): """ probabilities = [] collapsed_states = [] + if tol is None: + tol = settings.core["atol"] for i, op in enumerate(ops): st = op * density_mat * op.dag() p = st.tr() - probabilities.append(p) - if p != 0: + if p >= tol: collapsed_states.append(st/p) + probabilities.append(p) else: collapsed_states.append(None) + probabilities.append(0.) + return collapsed_states, probabilities -def measurement_statistics_povm(state, ops): +def measurement_statistics_povm(state, ops, tol=None): r""" Returns measurement statistics (resultant states and probabilities) for a measurement specified by a set of positive operator valued measurements on @@ -137,6 +152,10 @@ def measurement_statistics_povm(state, ops): projectors (s.t. :math:`E_i = M_i^\dagger = M_i`) 3. kets (transformed to projectors) + tol : float, optional + Smallest value for the probabilities. Smaller probabilities will be + rounded to ``0``. Default is qutip's core settings' ``atol``. + Returns ------- collapsed_states : list of :class:`.Qobj` @@ -160,12 +179,12 @@ def measurement_statistics_povm(state, ops): raise ValueError("measurement operators must sum to identity") if state.isket: - return _measurement_statistics_povm_ket(state, ops) + return _measurement_statistics_povm_ket(state, ops, tol) else: - return _measurement_statistics_povm_dm(state, ops) + return _measurement_statistics_povm_dm(state, ops, tol) -def measurement_statistics_observable(state, op): +def measurement_statistics_observable(state, op, tol=None): """ Return the measurement eigenvalues, eigenstates (or projectors) and measurement probabilities for the given state and measurement operator. @@ -178,33 +197,57 @@ def measurement_statistics_observable(state, op): op : :class:`.Qobj` The measurement operator. + tol : float, optional + Smallest value for the probabilities. + Default is qutip's core settings' ``atol``. + Returns ------- eigenvalues: list of float The list of eigenvalues of the measurement operator. - eigenstates_or_projectors: list of :class:`.Qobj` - If the state was a ket, return the eigenstates of the measurement - operator. Otherwise return the projectors onto the eigenstates. + projectors: list of :class:`.Qobj` + Return the projectors onto the eigenstates. probabilities: list of float The probability of measuring the state as being in the corresponding eigenstate (and the measurement result being the corresponding eigenvalue). """ + probabilities = [] + values = [] + projectors = [] _verify_input(op, state) + if tol is None: + tol = settings.core["atol"] eigenvalues, eigenstates = op.eigenstates() - if state.isket: - probabilities = [abs(e.overlap(state))**2 for e in eigenstates] - return eigenvalues, eigenstates, probabilities - else: - projectors = [e.proj() for e in eigenstates] - probabilities = [expect(v, state) for v in projectors] - return eigenvalues, projectors, probabilities + # Detect groups of eigenvalues within atol of each other. + # A group will be [True] + [False] * N + groups = np.append(True, np.diff(eigenvalues) >= tol) + + present_group = [] + for i in range(len(eigenvalues)-1, -1, -1): + present_group.append(i) + if not groups[i]: + continue + projector = 0 + for j in present_group: + projector += eigenstates[j].proj() + probability = expect(projector, state) -def measure_observable(state, op): + if probability >= tol: + probabilities.append(probability) + values.append(np.mean(eigenvalues[present_group])) + projectors.append(projector) + + present_group = [] + + return values[::-1], projectors[::-1], probabilities[::-1] + + +def measure_observable(state, op, tol=None): """ Perform a measurement specified by an operator on the given state. @@ -221,6 +264,10 @@ def measure_observable(state, op): op : :class:`.Qobj` The measurement operator. + tol : float, optional + Smallest value for the probabilities. + Default is qutip's core settings' ``atol``. + Returns ------- measured_value : float @@ -269,19 +316,17 @@ def measure_observable(state, op): The measurement result is the same, but the new state is returned as a density matrix. """ - eigenvalues, eigenstates_or_projectors, probabilities = ( - measurement_statistics_observable(state, op)) - i = np.random.choice(range(len(eigenvalues)), p=probabilities) + eigenvalues, projectors, probabilities = ( + measurement_statistics_observable(state, op, tol)) + i = np.random.choice(len(eigenvalues), p=probabilities) if state.isket: - eigenstates = eigenstates_or_projectors - state = eigenstates[i] + state = (projectors[i] * state) / probabilities[i]**0.5 else: - projectors = eigenstates_or_projectors state = (projectors[i] * state * projectors[i]) / probabilities[i] return eigenvalues[i], state -def measure_povm(state, ops): +def measure_povm(state, ops, tol=None): r""" Perform a measurement specified by list of POVMs. @@ -303,6 +348,10 @@ def measure_povm(state, ops): :math:`E_i = M_i^\dagger = M_i`) 3. kets (transformed to projectors) + tol : float, optional + Smallest value for the probabilities. + Default is qutip's core settings' ``atol``. + Returns ------- index : float @@ -311,13 +360,14 @@ def measure_povm(state, ops): state : :class:`.Qobj` The new state (a ket if a ket was given, otherwise a density matrix). """ - collapsed_states, probabilities = measurement_statistics_povm(state, ops) - index = np.random.choice(range(len(collapsed_states)), p=probabilities) + collapsed_states, probabilities = ( + measurement_statistics_povm(state, ops, tol)) + index = np.random.choice(len(collapsed_states), p=probabilities) state = collapsed_states[index] return index, state -def measurement_statistics(state, ops): +def measurement_statistics(state, ops, tol=None): r""" A dispatch method that provides measurement statistics handling both observable style measurements and projector style measurements(POVMs and @@ -342,14 +392,18 @@ def measurement_statistics(state, ops): 2. projection operators if ops correspond to projectors (s.t. :math:`E_i = M_i^\dagger = M_i`) 3. kets (transformed to projectors) + + tol : float, optional + Smallest value for the probabilities. + Default is qutip's core settings' ``atol``. """ if isinstance(ops, list): - return measurement_statistics_povm(state, ops) + return measurement_statistics_povm(state, ops, tol) else: - return measurement_statistics_observable(state, ops) + return measurement_statistics_observable(state, ops, tol) -def measure(state, ops): +def measure(state, ops, tol=None): r""" A dispatch method that provides measurement results handling both observable style measurements and projector style measurements (POVMs and @@ -374,8 +428,12 @@ def measure(state, ops): 2. projection operators if ops correspond to projectors (s.t. :math:`E_i = M_i^\dagger = M_i`) 3. kets (transformed to projectors) + + tol : float, optional + Smallest value for the probabilities. + Default is qutip's core settings' ``atol``. """ if isinstance(ops, list): - return measure_povm(state, ops) + return measure_povm(state, ops, tol) else: - return measure_observable(state, ops) + return measure_observable(state, ops, tol) diff --git a/qutip/tests/test_measurement.py b/qutip/tests/test_measurement.py index 6bccd1a35c..44b57ce28f 100644 --- a/qutip/tests/test_measurement.py +++ b/qutip/tests/test_measurement.py @@ -3,8 +3,8 @@ import pytest from math import sqrt from qutip import ( - Qobj, basis, ket2dm, sigmax, sigmay, sigmaz, identity, tensor, - rand_ket, + Qobj, basis, ket2dm, sigmax, sigmay, sigmaz, identity, num, tensor, + rand_ket ) from qutip.measurement import ( measure_povm, measurement_statistics_povm, measure_observable, @@ -70,8 +70,6 @@ def _equivalent(left, right, tol=1e-8): @pytest.mark.parametrize(["op", "state", "pairs", "probabilities"], [ - pytest.param(sigmaz(), basis(2, 0), SIGMAZ, [0, 1], id="sigmaz_ket"), - pytest.param(sigmaz(), basis(2, 0).proj(), SIGMAZ, [0, 1], id="sigmaz_dm"), pytest.param(sigmax(), basis(2, 0), SIGMAX, [0.5, 0.5], id="sigmax_ket"), pytest.param(sigmax(), basis(2, 0).proj(), SIGMAX, [0.5, 0.5], id="sigmax_dm"), @@ -82,19 +80,24 @@ def _equivalent(left, right, tol=1e-8): def test_measurement_statistics_observable(op, state, pairs, probabilities): """ measurement_statistics_observable: observables on basis states. """ - evs, ess_or_projs, probs = measurement_statistics_observable(state, op) - np.testing.assert_almost_equal(evs, pairs.eigenvalues) - if state.isket: - ess = ess_or_projs - assert len(ess) == len(pairs.eigenstates) - for a, b in zip(ess, pairs.eigenstates): - assert _equivalent(a, b) - else: - projs = ess_or_projs - assert len(projs) == len(pairs.projectors) - for a, b in zip(projs, pairs.projectors): - assert _equivalent(a, b) + evs, projs, probs = measurement_statistics_observable(state, op) + assert len(projs) == len(probabilities) np.testing.assert_almost_equal(probs, probabilities) + for a, b in zip(projs, pairs.projectors): + assert _equivalent(a, b) + + +def test_measurement_statistics_observable_degenerate(): + """ measurement_statistics_observable: observables on basis states. """ + + state = basis(2, 1) & (basis(2, 0) + basis(2, 1)).unit() + op = sigmaz() & identity(2) + expected_projector = num(2) & identity(2) + evs, projs, probs = measurement_statistics_observable(state, op) + assert len(probs) == 1 + np.testing.assert_almost_equal(probs, [1.]) + np.testing.assert_almost_equal(evs, [-1.]) + assert _equivalent(projs[0], expected_projector) @pytest.mark.parametrize(["ops", "state", "final_states", "probabilities"], [ From b1e7fafab7d07410b26bbffc6c5ca2d03927030c Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 1 Mar 2024 08:10:51 -0500 Subject: [PATCH 233/247] Simplify --- qutip/measurement.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qutip/measurement.py b/qutip/measurement.py index a745b92dca..5b29c40836 100644 --- a/qutip/measurement.py +++ b/qutip/measurement.py @@ -223,11 +223,11 @@ def measurement_statistics_observable(state, op, tol=None): eigenvalues, eigenstates = op.eigenstates() # Detect groups of eigenvalues within atol of each other. - # A group will be [True] + [False] * N - groups = np.append(True, np.diff(eigenvalues) >= tol) + # A group will be [False] * N + [True] + groups = np.append(np.diff(eigenvalues) >= tol, True) present_group = [] - for i in range(len(eigenvalues)-1, -1, -1): + for i in range(len(eigenvalues)): present_group.append(i) if not groups[i]: continue @@ -244,7 +244,7 @@ def measurement_statistics_observable(state, op, tol=None): present_group = [] - return values[::-1], projectors[::-1], probabilities[::-1] + return values, projectors, probabilities def measure_observable(state, op, tol=None): From 891b7c3b14c10b92724bd86b26b5acc7d9b224f5 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 1 Mar 2024 08:12:52 -0500 Subject: [PATCH 234/247] Add towncrier entry --- doc/changes/2342.misc | 1 + 1 file changed, 1 insertion(+) create mode 100644 doc/changes/2342.misc diff --git a/doc/changes/2342.misc b/doc/changes/2342.misc new file mode 100644 index 0000000000..1163fc45c7 --- /dev/null +++ b/doc/changes/2342.misc @@ -0,0 +1 @@ +Allow measurement functions to support degenerate operators. From 7894e7e3a2f2a78259d1cce1efa1718b04256c9f Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 1 Mar 2024 08:45:16 -0500 Subject: [PATCH 235/247] Improve flatten docstring --- qutip/core/dimensions.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index 805d4b2b30..e51babe3c0 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -17,9 +17,13 @@ def flatten(l): """Flattens a list of lists to the first level. - Given a list containing a mix of scalars and lists, - flattens down to a list of the scalars within the original - list. + Given a list containing a mix of scalars and lists or a dimension object, + flattens it down to a list of the scalars within the original list. + + Parameters + ---------- + l : scalar, list, Space, Dimension + Object to flatten. Examples -------- @@ -27,6 +31,11 @@ def flatten(l): >>> flatten([[[0], 1], 2]) # doctest: +SKIP [0, 1, 2] + Notes + ----- + Any scalar will be returned wrapped in a list: ``flaten(1) == [1]``. + Interable are not seen as list, a tuple is a scalar for the purpose of this + function. """ if isinstance(l, (Space, Dimensions)): l = l.as_list() From f7a01bb4615dc1aeb0c88c2ebb96dac93a5c384c Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 1 Mar 2024 11:11:04 -0500 Subject: [PATCH 236/247] rm _implicit_tensor_dimensions --- qutip/core/operators.py | 49 ++++++++---------------------- qutip/tests/core/test_operators.py | 2 +- 2 files changed, 13 insertions(+), 38 deletions(-) diff --git a/qutip/core/operators.py b/qutip/core/operators.py index d662e1ab50..8210785511 100644 --- a/qutip/core/operators.py +++ b/qutip/core/operators.py @@ -618,35 +618,6 @@ def _f_op(n_sites, site, action, dtype=None): return tensor(opers).to(dtype) -def _implicit_tensor_dimensions(dimensions): - """ - Total flattened size and operator dimensions for operator creation routines - that automatically perform tensor products. - - Parameters - ---------- - dimensions : (int) or (list of int) or (list of list of int) - First dimension of an operator which can create an implicit tensor - product. If the type is `int`, it is promoted first to `[dimensions]`. - From there, it should be one of the two-elements `dims` parameter of a - `qutip.Qobj` representing an `oper` or `super`, with possible tensor - products. - - Returns - ------- - size : int - Dimension of backing matrix required to represent operator. - dimensions : list - Dimension list in the form required by ``Qobj`` creation. - """ - if not isinstance(dimensions, (list, Space)): - dimensions = [dimensions] - flat = flatten(dimensions) - if not all(isinstance(x, numbers.Integral) and x >= 0 for x in flat): - raise ValueError("All dimensions must be integers >= 0") - return np.prod(flat), [dimensions, dimensions] - - def qzero(dimensions, dims_right=None, *, dtype=None): """ Zero operator. @@ -674,14 +645,17 @@ def qzero(dimensions, dims_right=None, *, dtype=None): """ dtype = dtype or settings.core["default_dtype"] or _data.CSR - size, dimensions = _implicit_tensor_dimensions(dimensions) + dimensions = Space(dimensions) + size = dimensions.size + dims = [dimensions, dimensions] if dims_right is not None: - size_right, dims_right = _implicit_tensor_dimensions(dims_right) - dimensions = [dimensions[0], dims_right[1]] + dims_right = Space(dims_right) + dims = [dims[0], dims_right] + size_right = dims_right.size else: size_right = size # A sparse matrix with no data is equal to a zero matrix. - return Qobj(_data.zeros[dtype](size, size_right), dims=dimensions, + return Qobj(_data.zeros[dtype](size, size_right), dims=dims, isherm=True, isunitary=False, copy=False) @@ -750,8 +724,8 @@ def qeye(dimensions, *, dtype=None): """ dtype = dtype or settings.core["default_dtype"] or _data.Dia - size, dimensions = _implicit_tensor_dimensions(dimensions) - return Qobj(_data.identity[dtype](size), dims=dimensions, + dimensions = Space(dimensions) + return Qobj(_data.identity[dtype](dimensions.size), dims=[dimensions]*2, isherm=True, isunitary=True, copy=False) @@ -1167,13 +1141,14 @@ def qft(dimensions, *, dtype="dense"): Quantum Fourier transform operator. """ - N2, dimensions = _implicit_tensor_dimensions(dimensions) + dimensions = Space(dimensions) + N2 = dimensions.size phase = 2.0j * np.pi / N2 arr = np.arange(N2) L, M = np.meshgrid(arr, arr) data = np.exp(phase * (L * M)) / np.sqrt(N2) - return Qobj(data, dims=dimensions).to(dtype) + return Qobj(data, dims=[dimensions]*2).to(dtype) def swap(N, M, *, dtype=None): diff --git a/qutip/tests/core/test_operators.py b/qutip/tests/core/test_operators.py index f9db90245a..28e0923613 100644 --- a/qutip/tests/core/test_operators.py +++ b/qutip/tests/core/test_operators.py @@ -107,7 +107,7 @@ def test_diagonal_operators(oper_func, diag, offset, args): @pytest.mark.parametrize(['function', 'message'], [ - (qutip.qeye, "All dimensions must be integers >= 0"), + (qutip.qeye, "Dimensions must be integers > 0"), (qutip.destroy, "Hilbert space dimension must be integer value"), (qutip.create, "Hilbert space dimension must be integer value"), ], ids=["qeye", "destroy", "create"]) From 8f930a0815b2f376020b8f5518919288cc8e289d Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 1 Mar 2024 11:12:52 -0500 Subject: [PATCH 237/247] clear set to none --- qutip/bloch.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutip/bloch.py b/qutip/bloch.py index f64b56c6a9..6a7e38a687 100644 --- a/qutip/bloch.py +++ b/qutip/bloch.py @@ -310,7 +310,7 @@ def clear(self): self.vector_alpha = [] self.annotations = [] self.vector_color = [] - self.point_color = [] + self.point_color = None self._lines = [] self._arcs = [] @@ -803,7 +803,7 @@ def plot_points(self): if self._inner_point_color[k] is not None: color = self._inner_point_color[k] - elif self.point_color not in [None, []]: + elif self.point_color is not None: color = self.point_color elif self.point_style[k] in ['s', 'l']: color = [self.point_default_color[ From 2e9c02595ab5b29318ada1e375a2ebb81d4891d5 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 1 Mar 2024 12:06:47 -0500 Subject: [PATCH 238/247] Add in new args in animation_wigner --- qutip/animation.py | 22 ++++++++++++++++++---- qutip/visualization.py | 15 +++++++-------- 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/qutip/animation.py b/qutip/animation.py index 61cd5de7ed..0f19485585 100644 --- a/qutip/animation.py +++ b/qutip/animation.py @@ -9,6 +9,7 @@ plot_fock_distribution, plot_wigner, plot_spin_distribution, plot_qubism, plot_schmidt) from .solver import Result +from numpy import sqrt def _result_state(obj): @@ -346,9 +347,9 @@ def anim_fock_distribution(rhos, fock_numbers=None, color="green", return fig, ani -def anim_wigner(rhos, xvec=None, yvec=None, method='clenshaw', - projection='2d', *, cmap=None, colorbar=False, - fig=None, ax=None): +def anim_wigner(rhos, xvec=None, yvec=None, method='clenshaw', projection='2d', + g=sqrt(2), sparse=False, parfor=False, *, + cmap=None, colorbar=False, fig=None, ax=None): """ Animation of the Wigner function for a density matrix (or ket) that describes an oscillator mode. @@ -373,6 +374,18 @@ def anim_wigner(rhos, xvec=None, yvec=None, method='clenshaw', Specify whether the Wigner function is to be plotted as a contour graph ('2d') or surface plot ('3d'). + g : float + Scaling factor for `a = 0.5 * g * (x + iy)`, default `g = sqrt(2)`. + See the documentation for qutip.wigner for details. + + sparse : bool {False, True} + Flag for sparse format. + See the documentation for qutip.wigner for details. + + parfor : bool {False, True} + Flag for parallel calculation. + See the documentation for qutip.wigner for details. + cmap : a matplotlib cmap instance, optional The colormap. @@ -395,7 +408,8 @@ def anim_wigner(rhos, xvec=None, yvec=None, method='clenshaw', rhos = _result_state(rhos) - fig, ani = plot_wigner(rhos, xvec, yvec, method, projection, + fig, ani = plot_wigner(rhos, xvec, yvec, method=method, g=g, sparse=sparse, + parfor=parfor, projection=projection, cmap=cmap, colorbar=colorbar, fig=fig, ax=ax) return fig, ani diff --git a/qutip/visualization.py b/qutip/visualization.py index cc37c20ba8..b5f10f7dcf 100644 --- a/qutip/visualization.py +++ b/qutip/visualization.py @@ -1126,10 +1126,9 @@ def plot_fock_distribution(rho, fock_numbers=None, color="green", return fig, output -def plot_wigner(rho, xvec=None, yvec=None, method='clenshaw', - g=sqrt(2), sparse=False, parfor=False, - projection='2d', *, cmap=None, colorbar=False, - fig=None, ax=None): +def plot_wigner(rho, xvec=None, yvec=None, method='clenshaw', projection='2d', + g=sqrt(2), sparse=False, parfor=False, *, + cmap=None, colorbar=False, fig=None, ax=None): """ Plot the the Wigner function for a density matrix (or ket) that describes an oscillator mode. @@ -1150,6 +1149,10 @@ def plot_wigner(rho, xvec=None, yvec=None, method='clenshaw', The method used for calculating the wigner function. See the documentation for qutip.wigner for details. + projection: str {'2d', '3d'}, default: '2d' + Specify whether the Wigner function is to be plotted as a + contour graph ('2d') or surface plot ('3d'). + g : float Scaling factor for `a = 0.5 * g * (x + iy)`, default `g = sqrt(2)`. See the documentation for qutip.wigner for details. @@ -1162,10 +1165,6 @@ def plot_wigner(rho, xvec=None, yvec=None, method='clenshaw', Flag for parallel calculation. See the documentation for qutip.wigner for details. - projection: str {'2d', '3d'}, default: '2d' - Specify whether the Wigner function is to be plotted as a - contour graph ('2d') or surface plot ('3d'). - cmap : a matplotlib cmap instance, optional The colormap. From f2236e6382ac5364cea520123564bba9f6c03754 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Fri, 1 Mar 2024 17:01:05 -0500 Subject: [PATCH 239/247] Apply suggestions from code review Co-authored-by: Simon Cross --- qutip/core/dimensions.py | 4 ++-- qutip/core/operators.py | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/qutip/core/dimensions.py b/qutip/core/dimensions.py index e51babe3c0..f19e3b2dee 100644 --- a/qutip/core/dimensions.py +++ b/qutip/core/dimensions.py @@ -34,8 +34,8 @@ def flatten(l): Notes ----- Any scalar will be returned wrapped in a list: ``flaten(1) == [1]``. - Interable are not seen as list, a tuple is a scalar for the purpose of this - function. + A non-list iterable will not be treated as a list by flatten. For example, flatten would treat a tuple + as a scalar. """ if isinstance(l, (Space, Dimensions)): l = l.as_list() diff --git a/qutip/core/operators.py b/qutip/core/operators.py index 8210785511..86e220b2c3 100644 --- a/qutip/core/operators.py +++ b/qutip/core/operators.py @@ -645,17 +645,17 @@ def qzero(dimensions, dims_right=None, *, dtype=None): """ dtype = dtype or settings.core["default_dtype"] or _data.CSR - dimensions = Space(dimensions) - size = dimensions.size - dims = [dimensions, dimensions] - if dims_right is not None: + dims_left = Space(dimensions) + size_left = dimensions.size + if dims_right is None: + dims_right = dims_left + size_right = size_left + else: dims_right = Space(dims_right) - dims = [dims[0], dims_right] size_right = dims_right.size - else: - size_right = size + dims = [dims_left, dims_right] # A sparse matrix with no data is equal to a zero matrix. - return Qobj(_data.zeros[dtype](size, size_right), dims=dims, + return Qobj(_data.zeros[dtype](size_left, size_right), dims=dims, isherm=True, isunitary=False, copy=False) From f2baffdd7cf9a8e7cee0f8716e293628f3cb9f78 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Fri, 1 Mar 2024 17:14:47 -0500 Subject: [PATCH 240/247] Fix qzero, github fix --- qutip/core/operators.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/qutip/core/operators.py b/qutip/core/operators.py index 86e220b2c3..6eaac9d342 100644 --- a/qutip/core/operators.py +++ b/qutip/core/operators.py @@ -646,8 +646,8 @@ def qzero(dimensions, dims_right=None, *, dtype=None): """ dtype = dtype or settings.core["default_dtype"] or _data.CSR dims_left = Space(dimensions) - size_left = dimensions.size - if dims_right is None: + size_left = dims_left.size + if dims_right is None: dims_right = dims_left size_right = size_left else: From 3838290ee61b961f5f7078c60ced8791fe884306 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 4 Mar 2024 09:46:42 -0500 Subject: [PATCH 241/247] Update changelog for v5b1 --- doc/changelog.rst | 109 +++++++++++++++++++++++++++++++++++++++ doc/changes/1974.bugfix | 1 - doc/changes/1996.feature | 1 - doc/changes/2057.feature | 1 - doc/changes/2210.feature | 1 - doc/changes/2234.bugfix | 1 - doc/changes/2257.misc | 1 - doc/changes/2262.bugfix | 1 - doc/changes/2269.bugfix | 1 - doc/changes/2271.misc | 1 - doc/changes/2272.bugfix | 1 - doc/changes/2280.bugfix | 1 - doc/changes/2284.misc | 1 - doc/changes/2288.bugfix | 1 - doc/changes/2296.feature | 1 - doc/changes/2303.feature | 1 - doc/changes/2306.misc | 1 - doc/changes/2311.misc | 1 - doc/changes/2313.misc | 1 - doc/changes/2325.misc | 1 - doc/changes/2331.misc | 1 - doc/changes/2333.bugfix | 1 - doc/changes/2342.misc | 1 - 23 files changed, 109 insertions(+), 22 deletions(-) delete mode 100644 doc/changes/1974.bugfix delete mode 100644 doc/changes/1996.feature delete mode 100644 doc/changes/2057.feature delete mode 100644 doc/changes/2210.feature delete mode 100644 doc/changes/2234.bugfix delete mode 100644 doc/changes/2257.misc delete mode 100644 doc/changes/2262.bugfix delete mode 100644 doc/changes/2269.bugfix delete mode 100644 doc/changes/2271.misc delete mode 100644 doc/changes/2272.bugfix delete mode 100644 doc/changes/2280.bugfix delete mode 100644 doc/changes/2284.misc delete mode 100644 doc/changes/2288.bugfix delete mode 100644 doc/changes/2296.feature delete mode 100644 doc/changes/2303.feature delete mode 100644 doc/changes/2306.misc delete mode 100644 doc/changes/2311.misc delete mode 100644 doc/changes/2313.misc delete mode 100644 doc/changes/2325.misc delete mode 100644 doc/changes/2331.misc delete mode 100644 doc/changes/2333.bugfix delete mode 100644 doc/changes/2342.misc diff --git a/doc/changelog.rst b/doc/changelog.rst index ca2cc79f61..d50c150932 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -6,6 +6,46 @@ Change Log .. towncrier release notes start +QuTiP 5.0.0b1 (2024-03-04) +========================== + +Features +-------- + +- Create a Dimension class (#1996) +- Add arguments of plot_wigner() and plot_wigner_fock_distribution() to specify parameters for wigner(). (#2057, by Kosuke Mizuno) +- Restore feedback to solvers (#2210) +- Added mpi_pmap, which uses the mpi4py module to run computations in parallel through the MPI interface. (#2296, by Paul) +- Only pre-compute density matricies if keep_runs_results is False (#2303, by Matt Ord) + + +Bug Fixes +--------- + +- Add the possibility to customize point colors as in V4 and fix point plot behavior for 'l' style (#1974, by Daniel Moreno Galán) +- Disabled broken "improved sampling" for `nm_mcsolve`. (#2234, by Paul) +- Fixed result objects storing a reference to the solver through options._feedback. (#2262, by Paul) +- Fixed simdiag not returning orthonormal eigenvectors. (#2269, by Sola85) +- Fix LaTeX display of Qobj state in Jupyter cell outputs (#2272, by Edward Thomas) +- Improved behavior of `parallel_map` and `loky_pmap` in the case of timeouts, errors or keyboard interrupts (#2280, by Paul) +- Ignore deprecation warnings from cython 0.29.X in tests. (#2288) +- Fixed two problems with the steady_state() solver in the HEOM method. (#2333) + + +Miscellaneous +------------- + +- Improve fidelity doc-string (#2257) +- Improve documentation in guide/dynamics (#2271) +- Rework `kraus_to_choi` making it faster (#2284, by Bogdan Reznychenko) +- Remove Bloch3D: redundant to Bloch (#2306) +- Allow tests to run without matplotlib and ipython. (#2311) +- Add too small step warnings in fixed dt SODE solver (#2313) +- Add `dtype` to `Qobj` and `QobjEvo` (#2325) +- Fix typos in `expect` documentation (#2331, by gabbence95) +- Allow measurement functions to support degenerate operators. (#2342) + + QuTiP 5.0.0a2 (2023-09-06) ========================== @@ -435,6 +475,75 @@ Feature removals - The ``~/.qutip/qutiprc`` config file is no longer supported. It contained settings for the OpenMP support. +QuTiP 4.7.5 (2024-01-29) +======================== + +Patch release for QuTiP 4.7. It adds support for SciPy 1.12. + +Bug Fixes +--------- + +- Remove use of scipy. in parallel.py, incompatible with scipy==1.12 (#2305 by Evan McKinney) + + +QuTiP 4.7.4 (2024-01-15) +======================== + +Bug Fixes +--------- + +- Adapt to deprecation from matplotlib 3.8 (#2243, reported by Bogdan Reznychenko) +- Fix name of temp files for removal after use. (#2251, reported by Qile Su) +- Avoid integer overflow in Qobj creation. (#2252, reported by KianHwee-Lim) +- Ignore DeprecationWarning from pyximport (#2287) +- Add partial support and tests for python 3.12. (#2294) + + +Miscellaneous +------------- + +- Rework `choi_to_kraus`, making it rely on an eigenstates solver that can choose `eigh` if the Choi matrix is Hermitian, as it is more numerically stable. (#2276, by Bogdan Reznychenko) +- Rework `kraus_to_choi`, making it faster (#2283, by Bogdan Reznychenko and Rafael Haenel) + + +QuTiP 4.7.3 (2023-08-22) +======================== + +Bug Fixes +--------- + +- Non-oper qobj + scalar raise an error. (#2208 reported by vikramkashyap) +- Fixed issue where `extract_states` did not preserve hermiticity. + Fixed issue where `rand_herm` did not set the private attribute _isherm to True. (#2214 by AGaliciaMartinez) +- ssesolve average states to density matrices (#2216 reported by BenjaminDAnjou) + + +Miscellaneous +------------- + +- Exclude cython 3.0.0 from requirement (#2204) +- Run in no cython mode with cython >=3.0.0 (#2207) + + +QuTiP 4.7.2 (2023-06-28) +======================== + +This is a bugfix release for QuTiP 4.7.X. It adds support for +numpy 1.25 and scipy 1.11. + +Bug Fixes +--------- +- Fix setting of sso.m_ops in heterodyne smesolver and passing through of sc_ops to photocurrent solver. (#2081 by Bogdan Reznychenko and Simon Cross) +- Update calls to SciPy eigvalsh and eigsh to pass the range of eigenvalues to return using ``subset_by_index=``. (#2081 by Simon Cross) +- Fixed bug where some matrices were wrongly found to be hermitian. (#2082 by AGaliciaMartinez) + +Miscellaneous +------------- +- Fixed typo in stochastic.py (#2049, by eltociear) +- `ptrace` always return density matrix (#2185, issue by udevd) +- `mesolve` can support mixed callable and Qobj for `e_ops` (#2184 issue by balopat) + + Version 4.7.1 (December 11, 2022) +++++++++++++++++++++++++++++++++ diff --git a/doc/changes/1974.bugfix b/doc/changes/1974.bugfix deleted file mode 100644 index f7b521a0bf..0000000000 --- a/doc/changes/1974.bugfix +++ /dev/null @@ -1 +0,0 @@ -Add the possibility to customize point colors as in V4 and fix point plot behavior for 'l' style \ No newline at end of file diff --git a/doc/changes/1996.feature b/doc/changes/1996.feature deleted file mode 100644 index 0daabffbed..0000000000 --- a/doc/changes/1996.feature +++ /dev/null @@ -1 +0,0 @@ -Create a Dimension class diff --git a/doc/changes/2057.feature b/doc/changes/2057.feature deleted file mode 100644 index 27bdf6eb1a..0000000000 --- a/doc/changes/2057.feature +++ /dev/null @@ -1 +0,0 @@ -Add arguments of plot_wigner() and plot_wigner_fock_distribution() to specify parameters for wigner(). (#2057) \ No newline at end of file diff --git a/doc/changes/2210.feature b/doc/changes/2210.feature deleted file mode 100644 index 99dba0c718..0000000000 --- a/doc/changes/2210.feature +++ /dev/null @@ -1 +0,0 @@ -Restore feedback to solvers diff --git a/doc/changes/2234.bugfix b/doc/changes/2234.bugfix deleted file mode 100644 index f9e0bb5ffe..0000000000 --- a/doc/changes/2234.bugfix +++ /dev/null @@ -1 +0,0 @@ -Disabled broken "improved sampling" for `nm_mcsolve`. \ No newline at end of file diff --git a/doc/changes/2257.misc b/doc/changes/2257.misc deleted file mode 100644 index 7d75604039..0000000000 --- a/doc/changes/2257.misc +++ /dev/null @@ -1 +0,0 @@ -Improve fidelity doc-string diff --git a/doc/changes/2262.bugfix b/doc/changes/2262.bugfix deleted file mode 100644 index 7413a5f513..0000000000 --- a/doc/changes/2262.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixed result objects storing a reference to the solver through options._feedback. \ No newline at end of file diff --git a/doc/changes/2269.bugfix b/doc/changes/2269.bugfix deleted file mode 100644 index 48c2b06fa5..0000000000 --- a/doc/changes/2269.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixed simdiag not returning orthonormal eigenvectors. \ No newline at end of file diff --git a/doc/changes/2271.misc b/doc/changes/2271.misc deleted file mode 100644 index 42e4934c3c..0000000000 --- a/doc/changes/2271.misc +++ /dev/null @@ -1 +0,0 @@ -Improve documentation in guide/dynamics diff --git a/doc/changes/2272.bugfix b/doc/changes/2272.bugfix deleted file mode 100644 index 05d74ec7cc..0000000000 --- a/doc/changes/2272.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fix LaTeX display of Qobj state in Jupyter cell outputs \ No newline at end of file diff --git a/doc/changes/2280.bugfix b/doc/changes/2280.bugfix deleted file mode 100644 index 9d54bab013..0000000000 --- a/doc/changes/2280.bugfix +++ /dev/null @@ -1 +0,0 @@ -Improved behavior of `parallel_map` and `loky_pmap` in the case of timeouts, errors or keyboard interrupts \ No newline at end of file diff --git a/doc/changes/2284.misc b/doc/changes/2284.misc deleted file mode 100644 index 8d4ffffcdc..0000000000 --- a/doc/changes/2284.misc +++ /dev/null @@ -1 +0,0 @@ -Rework `kraus_to_choi` making it faster \ No newline at end of file diff --git a/doc/changes/2288.bugfix b/doc/changes/2288.bugfix deleted file mode 100644 index f0dd3bc183..0000000000 --- a/doc/changes/2288.bugfix +++ /dev/null @@ -1 +0,0 @@ -Ignore deprecation warnings from cython 0.29.X in tests. \ No newline at end of file diff --git a/doc/changes/2296.feature b/doc/changes/2296.feature deleted file mode 100644 index 8fed655652..0000000000 --- a/doc/changes/2296.feature +++ /dev/null @@ -1 +0,0 @@ -Added mpi_pmap, which uses the mpi4py module to run computations in parallel through the MPI interface. \ No newline at end of file diff --git a/doc/changes/2303.feature b/doc/changes/2303.feature deleted file mode 100644 index db293b995f..0000000000 --- a/doc/changes/2303.feature +++ /dev/null @@ -1 +0,0 @@ -Only pre-compute density matricies if keep_runs_results is False \ No newline at end of file diff --git a/doc/changes/2306.misc b/doc/changes/2306.misc deleted file mode 100644 index eb9042599c..0000000000 --- a/doc/changes/2306.misc +++ /dev/null @@ -1 +0,0 @@ -Remove Bloch3D: redundant to Bloch \ No newline at end of file diff --git a/doc/changes/2311.misc b/doc/changes/2311.misc deleted file mode 100644 index d5712b0c02..0000000000 --- a/doc/changes/2311.misc +++ /dev/null @@ -1 +0,0 @@ -Allow tests to run without matplotlib and ipython. \ No newline at end of file diff --git a/doc/changes/2313.misc b/doc/changes/2313.misc deleted file mode 100644 index 8020b81549..0000000000 --- a/doc/changes/2313.misc +++ /dev/null @@ -1 +0,0 @@ -Add too small step warnings in fixed dt SODE solver \ No newline at end of file diff --git a/doc/changes/2325.misc b/doc/changes/2325.misc deleted file mode 100644 index 9b64dc7253..0000000000 --- a/doc/changes/2325.misc +++ /dev/null @@ -1 +0,0 @@ -Add `dtype` to `Qobj` and `QobjEvo` \ No newline at end of file diff --git a/doc/changes/2331.misc b/doc/changes/2331.misc deleted file mode 100644 index 4231d540fd..0000000000 --- a/doc/changes/2331.misc +++ /dev/null @@ -1 +0,0 @@ -Fix typos in `expect` documentation diff --git a/doc/changes/2333.bugfix b/doc/changes/2333.bugfix deleted file mode 100644 index c8a1bda03b..0000000000 --- a/doc/changes/2333.bugfix +++ /dev/null @@ -1 +0,0 @@ -Fixed two problems with the steady_state() solver in the HEOM method. \ No newline at end of file diff --git a/doc/changes/2342.misc b/doc/changes/2342.misc deleted file mode 100644 index 1163fc45c7..0000000000 --- a/doc/changes/2342.misc +++ /dev/null @@ -1 +0,0 @@ -Allow measurement functions to support degenerate operators. From c096c793fbd55d5944d59d799cf805bbdd729d21 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Mon, 4 Mar 2024 11:49:41 -0500 Subject: [PATCH 242/247] Improve test tol for 1/600 to 1/100000 failure --- qutip/tests/solver/test_mcsolve.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qutip/tests/solver/test_mcsolve.py b/qutip/tests/solver/test_mcsolve.py index dd7dcceaf8..717fc74fc1 100644 --- a/qutip/tests/solver/test_mcsolve.py +++ b/qutip/tests/solver/test_mcsolve.py @@ -411,7 +411,7 @@ def test_super_H(improved_sampling): target_tol=0.1, options={'map': 'serial', "improved_sampling": improved_sampling}) - np.testing.assert_allclose(mc_expected.expect[0], mc.expect[0], atol=0.5) + np.testing.assert_allclose(mc_expected.expect[0], mc.expect[0], atol=0.65) def test_MCSolver_run(): From 9f63e6ef6567f216a29a40c772114dd030b82a1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Eric=20Gigu=C3=A8re?= Date: Mon, 4 Mar 2024 17:51:58 -0500 Subject: [PATCH 243/247] Update doc/changelog.rst Co-authored-by: Simon Cross --- doc/changelog.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/changelog.rst b/doc/changelog.rst index d50c150932..300129c547 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -16,7 +16,7 @@ Features - Add arguments of plot_wigner() and plot_wigner_fock_distribution() to specify parameters for wigner(). (#2057, by Kosuke Mizuno) - Restore feedback to solvers (#2210) - Added mpi_pmap, which uses the mpi4py module to run computations in parallel through the MPI interface. (#2296, by Paul) -- Only pre-compute density matricies if keep_runs_results is False (#2303, by Matt Ord) +- Only pre-compute density matrices if keep_runs_results is False (#2303, by Matt Ord) Bug Fixes From 15e49d15e5f58a65774bbaff46fc1a4784f1752a Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 5 Mar 2024 08:34:12 -0500 Subject: [PATCH 244/247] Update version --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index d51bd13201..a799a6e466 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -5.0.0a2 +5.0.0b1 From 73423b85acc5f2ae4cfbf5f3b884751e6b2273e2 Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 5 Mar 2024 08:41:39 -0500 Subject: [PATCH 245/247] Update classifiers --- setup.cfg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.cfg b/setup.cfg index ecb3cc0ee4..90c93f7b07 100644 --- a/setup.cfg +++ b/setup.cfg @@ -12,7 +12,7 @@ project_urls = Documentation = https://qutip.org/docs/latest/ Source Code = https://github.com/qutip/qutip classifiers = - Development Status :: 2 - Pre-Alpha + Development Status :: 4 - Beta Intended Audience :: Science/Research License :: OSI Approved :: BSD License Programming Language :: Python From 553ae786a31ba984172b7223b0a0175e86b356cc Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 5 Mar 2024 14:32:04 -0500 Subject: [PATCH 246/247] Add last minute merge to changelog --- doc/changelog.rst | 1 + doc/changes/2289.misc | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 doc/changes/2289.misc diff --git a/doc/changelog.rst b/doc/changelog.rst index 300129c547..a916a657c5 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -37,6 +37,7 @@ Miscellaneous - Improve fidelity doc-string (#2257) - Improve documentation in guide/dynamics (#2271) +- Improve states and operator parameters documentation. (#2289) - Rework `kraus_to_choi` making it faster (#2284, by Bogdan Reznychenko) - Remove Bloch3D: redundant to Bloch (#2306) - Allow tests to run without matplotlib and ipython. (#2311) diff --git a/doc/changes/2289.misc b/doc/changes/2289.misc deleted file mode 100644 index 182a215512..0000000000 --- a/doc/changes/2289.misc +++ /dev/null @@ -1 +0,0 @@ -Improve states and operator parameters documentation. \ No newline at end of file From 3299f539d3cbeaccb67090e8fa5fdb33b10febaa Mon Sep 17 00:00:00 2001 From: Eric Giguere Date: Tue, 5 Mar 2024 16:10:12 -0500 Subject: [PATCH 247/247] Skip wheels for i686 and win32 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c08e003ea1..ea101cb160 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -111,7 +111,7 @@ jobs: CIBW_BUILD: "cp3{9,10,11,12}-*" # Numpy and SciPy do not supply wheels for i686 or win32 for # Python 3.10+, so we skip those: - CIBW_SKIP: "*-musllinux* cp3{10,11,12}-manylinux_i686 cp3{10,11,12}-win32" + CIBW_SKIP: "*-musllinux* *-manylinux_i686 *-win32" OVERRIDE_VERSION: ${{ github.event.inputs.override_version }} steps: