import numpy as _np
from .constants import *
from .core import *
from . import backends
from typing import Union
import warnings
###########################################################
# GLOBAL WRAPPER FUNCTIONS FOR BACKEND SPECIFIC METHODS
###########################################################
############# UTILITY FUNCTIONS #############
[docs]
def tidyup(arg, method = None, dims = None, **kwargs):
""" Transforms an input argument to a quantum object data type of an available backend. The data types of the individual elements of the quantum object are NOT altered by this routine.
:param arg: Input object.
:param str method: Backend to which type conversion should be performed. If omitted, the target backend is determined via the data type of the input argument.
:param list dims: Specifies dims attribute of the output quantum object. If dims == None but the input argument is already a quantum object with valid dims, the old dims are retained.
:param **kwargs:
See below
:Keyword Arguments:
* *atol* (``float``) --
Values smaller than atol are set to 0. Only used in 'scipy' and 'qutip' backends.
:return: Quantum object data type.
"""
# Set target dimensions.
if hasattr(arg, "dims") and dims is None:
tdims = arg.dims
else:
tdims = dims
# Type conversion.
curr_method = backends.get_backend(arg)
# If explicitly asked for conversion to another backend.
if method is not None and curr_method is not method:
targ = getattr(getattr(backends,curr_method), 'data')(arg)
targ = getattr(getattr(backends, method), 'tidyup')(targ, dims = tdims, *kwargs)
# If desired backend None or same as current.
else:
targ = getattr(getattr(backends,curr_method), 'tidyup')(arg, dims = tdims, *kwargs)
return targ
[docs]
def data(arg):
""" Returns data of a quantum object as a numpy.ndarray.
:param arg: Quantum object.
:return: Numpy.ndarray holding data of input quantum object.
"""
backend = backends.get_backend(arg)
return getattr(getattr(backends,backend), 'data')(arg)
def changeelement(matrix, ind1 : int, ind2 : int, newvalue : Union[int, float, complex, list, tuple, _np.ndarray]):
"""
Sets a single element or a block of elements in an operator or superoperator with a series of values.
:param matrix: Input operator/superoperator that will be modified.
:param ind1: Index specifying starting row for data change.
:param ind2: Index specifying starting column for changed data.
:param newvalue: Values to be inserted.
:return: Updated operator/superoperator.
"""
backend = backends.get_backend(matrix)
matrix = tidyup(matrix, method = backend)
return getattr(getattr(backends,backend), 'changeelement')(matrix, ind1, ind2, newvalue)
############# QUANTUM OBJECT ATTRIBUTES #############
def dag(op):
""" Dagger(adjoint) of a quantum object.
:param: Quantum object.
:return: Dagger(adjoint)."""
backend = backends.get_backend(op)
op = tidyup(op, method = backend)
return op.dag()
def conj(op):
""" Conjugate of a quantum object.
:param: Quantum object.
:return: Conjugate."""
backend = backends.get_backend(op)
op = tidyup(op, method = backend)
return op.conj()
def trans(op):
""" Transpose of a quantum object.
:param: Quantum object.
:return: Transpose."""
backend = backends.get_backend(op)
op = tidyup(op, method = backend)
return op.trans()
def tr(op):
"""Trace of a quantum object.
:param: Quantum object.
:return: Trace.
"""
backend = backends.get_backend(op)
op = tidyup(op, method = backend)
return op.tr()
def diag(op):
""" Diagonal of a quantum object.
:param: Quantum object.
:return: Diagonal of the quantum object, returned as a numpy.ndarray."""
backend = backends.get_backend(op)
op = tidyup(op, method = backend)
return op.diag()
def unit(op):
""" Normalizes a quantum object.
:param: Quantum object.
:return: Normalized quantum object."""
backend = backends.get_backend(op)
op = tidyup(op, method = backend)
return op.unit()
def transform(op,U):
""" Coordinate Transformation of a quantum object.
:param: Quantum object.
:U: Transformation matrix.
:return: Transformed quantum object."""
backend = backends.get_backend(op)
op = tidyup(op, method = backend)
return op.transform(U)
def expm(H):
""" Matrix exponential of a quantum object.
:param: Quantum object.
:return: Matrix exponential."""
backend = backends.get_backend(H)
H = tidyup(H, method = backend)
return H.expm()
def ptrace(op, sel , dims=None):
"""Partial trace of a quantum object.
:param op: Operator.
:param sel: Separable subsystem that is kept after the partial trace.
:return: Partial trace.
"""
backend = backends.get_backend(op)
if backend != "qutip" and isoper(op) is False:
raise ValueError("For all backends except qutip, partial trace currently only works on operators.")
op = tidyup(op, dims = dims)
return op.ptrace(sel)
def eigenstates(op, isherm=True):
"""Eigenstates of a quantum object.
:param op: Operator.
:param isherm: If True, the operator is assumed to be Hermitian.
:return: eigenvalues, eigenvectors.
"""
backend = backends.get_backend(op)
op = tidyup(op, method = backend)
if backend == 'numpy':
return op.eigenstates(isherm)
else:
return op.eigenstates()
def permute(op, order : list):
""" Permutes members of a quantum object.
:param op: Operator.
:return: The operator with permuted members.
"""
backend = backends.get_backend(op)
op = tidyup(op, method = backend)
return op.permute(order)
###################### Math / Generic #######################
[docs]
def expect(oper,state):
""" Expectation value for an operator-state pair.
:param oper: Operator.
:param state: State.
:return: Expectation value.
"""
backend = backends.get_backend(state)
return getattr(getattr(backends,backend), 'expect')(oper,state)
[docs]
def tensor(operators : list):
""" Tensor product of a series of operators.
:param operators: A list of operators
:return: Tensor product of the operators.
"""
backend = backends.get_backend(operators[0])
for idx, item in enumerate(operators):
operators[idx] = tidyup(item, method = backend)
return getattr(getattr(backends,backend), 'tensor')(operators)
[docs]
def directsum(*args):
""" Direct sum of a series of operators. Empty, square operators may also be specified as
integers.
:param *args: A series of operators, provided as a single list or multiple arguments.
:return: Direct product of the operators.
"""
backend = backends.get_backend(operator)
return getattr(getattr(backends,backend), 'directsum')(*args)
[docs]
def ddrop(operator, idx : Union[int, list]):
""" Drops dimension(s) from an operator (i.e. a quantum object).
:param operator: A quantum object.
:param idx: The dimension that will be dropped.
:return: The operator without the dropped dimensions.
"""
backend = backends.get_backend(operator)
return getattr(getattr(backends,backend), 'ddrop')(operator, idx)
[docs]
def dpermute(operator, order : list):
""" Permutes dimensions of an operator.
:param operator: A quantum object.
:param order: A list specifying the new ordering of dimensions.
:return: The operator with permuted dimensions.
"""
backend = backends.get_backend(operator)
return getattr(getattr(backends,backend), 'dpermute')(operator, order)
[docs]
def block_diagonal(L_list):
""" Constructs a block-diagonal matrix from a list of operators.
:param list L_list: A list of operators.
:return: A block diagonal matrix, each block is an entry of L_list.
"""
backend = backends.get_backend(L_list[0])
L_list = [tidyup(L, method = backend) for L in L_list]
return getattr(getattr(backends,backend), 'block_diagonal')(L_list)
###################### HILBERT SPACE ########################
[docs]
def isbra(state):
""" Indicates if the quantum object is a bra.
:param state: Quantum object.
:return bool:
"""
backend = backends.get_backend(state)
return getattr(getattr(backends,backend), 'isbra')(state)
[docs]
def isket(state):
""" Indicates if the quantum object is a ket.
:param state: Quantum object.
:return bool:
"""
backend = backends.get_backend(state)
return getattr(getattr(backends,backend), 'isket')(state)
[docs]
def isoper(state):
""" Indicates if the quantum object is an operator.
:param state: Quantum object.
:return bool:
"""
backend = backends.get_backend(state)
return getattr(getattr(backends,backend), 'isoper')(state)
[docs]
def ket2dm(ket):
""" Constructs an density matrix from a state vector with an outer product.
:param ket: A fock state vector (ket or bra)
:return: Density matrix.
"""
backend = backends.get_backend(ket)
ket = tidyup(ket, method = backend)
return getattr(getattr(backends,backend), 'ket2dm')(ket)
[docs]
def dm2ket(dm, tol = 1e-6):
""" Constructs a state vector (ket) from a density matrix. Does only work for pure states and normalized density matrices.
:param dm: A density matrix.
:parm tol: Tolerance used when probing whether the input density matrix is a pure state and normalized.
:return: State vector (ket).
"""
backend = backends.get_backend(dm)
dm = tidyup(dm, method = backend)
return getattr(getattr(backends,backend), 'dm2ket')(dm,tol)
[docs]
def ket(N : int, m : int ,method='qutip'):
""" Pure state vector (ket) for the mth basis state of an N-dimensional Hilbert space.
:param int N: Number of fock states in Hilbert space.
:param int m: The index of the desired state, ranging from 0-(N-1)
:param str method: The desired backend. Can be 'numpy', 'qutip','sparse' or 'sympy'.
:return: The pure state vector (ket)
"""
return getattr(getattr(backends,method), 'ket')(N,m)
[docs]
def bra(N : int, m : int, method= 'qutip'):
""" Pure state vector (bra) for the mth basis state of an N-dimensional Hilbert space.
:param int N: Number of fock states in Hilbert space.
:param int m: The index of the desired state, ranging from 0-(N-1)
:param str method: The desired backend. Can be 'numpy', 'qutip','sparse' or 'sympy'.
:return: The pure state vector (bra)
"""
return getattr(getattr(backends,method), 'bra')(N, m)
[docs]
def jmat(j , op_spec : str, method='qutip'):
"""Spin operator for a spin j.
:param j: A non-negative integer or half-integer specifying the spin
:param str op_spec: A string specifying the desired operator, can be 'x', 'y', 'z','+','-'
:return: The spin operator.
"""
return getattr(getattr(backends,method), 'jmat')(j,op_spec)
[docs]
def identity(N : int , dims = None , method ='qutip'):
""" Identiy Matrix of an N-dimensional Hilbert space.
:param int N: Number of Fock states in Hilbert space.
:param list dims: Structure of the Hilbert space, if None or omitted dims are set as [N].
:param str method: The desired backend. Can be 'numpy', 'qutip','sparse' or 'sympy'.
:return: The identity matrix.
"""
return getattr(getattr(backends,method), 'identity')(N,dims)
[docs]
def diags(v , k : int = 0, method='qutip', dims = None):
""" Constructs an operator from a diagonal or extracts a diagonal.
:param v: A sequence of elements to be placed along the selected diagonal or a quantum object from which diagonal is extracted.
:param int k: The selected diagonal
:param list dims: Structure of the Hilbert space, if None or omitted dims are set as [N].
:param str method: The desired backend. Can be 'numpy', 'qutip','sparse' or 'sympy'.
:return: Resulting matrix.
"""
# If diags was called to extract a diagonal, use native backend.
if len(_np.shape(v)) == 2:
backend = backends.get_backend(v)
if backend != method:
warnings.warn("Input data type suggests other backend than specified method that will be ignored.")
method = backend
return getattr(getattr(backends, method), 'diags')(v, k, dims)
################ Liouville Space ###########################ß
def dm2vec(op):
""" Constructs a liouville space vector from a Hilbert space operator.
:param op: An operator in Hilbert space.
:return: State vector in Liouville space.
"""
backend = backends.get_backend(op)
op = tidyup(op, method = backend)
return getattr(getattr(backends,backend), 'dm2vec')(op)
def vec2dm(vec):
""" Constructs Hilbert space operator from a liouville space vector.
:param op: State vector in Liouville space.An operator in Hilbert space.
:return: An operator in Hilbert space.
"""
backend = backends.get_backend(vec)
vec = tidyup(vec, method = backend)
return getattr(getattr(backends,backend), 'vec2dm')(vec)
[docs]
def issuper(state):
""" Indicates if the quantum object is a superoperator.
:param state: Quantum object.
:return bool:
"""
backend = backends.get_backend(state)
return getattr(getattr(backends,backend), 'issuper')(state)
[docs]
def spost(op):
""" Computes superoperator by post-multiplication by op.
:param op: Operator.
:return: Superoperator 1 \\otimes op. """
backend = backends.get_backend(op)
op = tidyup(op, method = backend)
return getattr(getattr(backends,backend), 'spost')(op)
[docs]
def spre(op):
""" Computes superoperator by pre-multiplication by op.
:param op: Operator.
:return: Superoperator op.T \\otimes 1."""
backend = backends.get_backend(op)
op = tidyup(op, method = backend)
return getattr(getattr(backends,backend), 'spre')(op)
[docs]
def lindbladian(a, b=None):
""" Lindbladian superoperator for a pair of collapse operators a,b, or a single collapse operator if only a is being provided.
:param a: Collapse operator.
:param b: Collapse operator, None per default.
:return: Lindbladian superoperator. """
backend = backends.get_backend(a)
a = tidyup(a, method = backend)
if b is not None:
b = tidyup(b, method = backend)
return getattr(getattr(backends,backend), 'lindbladian')(a, b)
[docs]
def liouvillian(H, c_ops : list):
"""Liouvillian superoperator from a hamiltonian and a list of collapse operators.
:param H: Hamiltonian operator.
:param list c_ops: List of collapse operators.
:return: Liouville superoperator.
"""
if H is not None:
backend = backends.get_backend(H)
H = tidyup(H, method = backend)
else:
try:
backend = backends.get_backend(c_ops[0])
except:
raise ValueError("If no Hamiltonian is provided, list of collapse operators cannot be empty.")
for idx, item in enumerate(c_ops):
c_ops[idx] = tidyup(item, method = backend)
return getattr(getattr(backends,backend), 'liouvillian')(H,c_ops)
[docs]
def applySuperoperator(L,rho):
"""Apply superoperator to a state.
:param L: Superoperator (Liouvillian).
:param rho: State (density matrix).
:return: Propagator.
"""
backend = backends.get_backend(rho)
return getattr(getattr(backends,backend), 'applySuperoperator')(L,rho)
################ Fokker Planck Space #########################
[docs]
def fp2dm(v,is_hilbert,dims=None,weights=None):
"""Converts a state vector in the Fokker-Planck space to a density matrix.
In the Fokker-Planck space, the state vector is a vector of concatenated state vectors in Hilber space (pure quantum states) or vectorized density matrices (mixed quantum states).
:param v: State vector in the Fokker-Planck space.
:param bool is_hilbert: If True, the state vector is defined in the Hilbert space. If False, the state vector is defined in the Liouville space.
:param list dims: dims to set. If None, dims are set as the Hilbert / Liouville part.
:param weights: Weights for the density matrix. If None, the weights are set as 1.
"""
backend = backends.get_backend(v)
v = tidyup(v, method = backend)
return getattr(getattr(backends,backend), 'fp2dm')(v,is_hilbert,dims,weights)
[docs]
def dm2fp(rho,dof,try_ket=False):
"""Copies a density matrix to a state vector in the Fokker-Planck space.
In the Fokker-Planck space, the state vector is a vector of concatenated state vectors in Hilbert space (pure quantum states) or vectorized density matrices (mixed quantum states).
:param rho: Density matrix or state vector
:param int dof: Number of degrees of freedom. This is the number of copies.
:param bool try_ket: If True, the function tries to convert the density matrix to a state vector in Hilbert space.
"""
backend = backends.get_backend(rho)
rho = tidyup(rho, method = backend)
return getattr(getattr(backends,backend), 'dm2fp')(rho,dof,try_ket)
def ket2fp(ket,dof):
"""Copies a state vector to a state vector in the Fokker-Planck space.
In the Fokker-Planck space, the state vector is a vector of concatenated state vectors in Hilbert space (pure quantum states) or vectorized density matrices (mixed quantum states).
:param ket: State vector
:param int dof: Number of degrees of freedom. This is the number of copies.
"""
backend = backends.get_backend(ket)
ket = tidyup(ket, method = backend)
return getattr(getattr(backends,backend), 'ket2fp')(ket,dof)