Source code for quantarhei.qm.liouvillespace.superoperator
"""Class representing superoperators
This class represents operators on the space of Hilbert space operators.
Usually, we refer to such operators as superoperators.
Class Details
-------------
"""
from __future__ import annotations
import copy as _copy_module
from typing import Any
# dependencies imports
import numpy
# quantarhei imports
from ... import COMPLEX, REAL
from ...core.managers import BasisManaged
from ...exceptions import QuantarheiError
from ...utils.types import BasisManagedComplexArray
[docs]
class SuperOperator(BasisManaged):
"""Class representing superoperators
Parameters
----------
dim : int
Dimension of the superoperator
data : array
Data of the superoperator
real : bool
Is this data real? False if they are complex
Examples
--------
Creation of an empty SuperOperator is allowed
>>> So = SuperOperator()
>>> print(So.dim is None)
True
But calling uninitialize data attribute raises an exception
>>> print(So.data is None) # doctest: +ELLIPSIS
Traceback (most recent call last):
...
AttributeError: 'SuperOperator' object has no attribute '_data'...
Creating with `dim` arguments creates a zero superoperator
>>> So = SuperOperator(dim=3)
>>> print(So.dim)
3
>>> print(So.data.shape)
(3, 3, 3, 3)
Creating superoperator from data checks, if the data object has the
correct shape
>>> data = numpy.zeros((3,3,3))
>>> So = SuperOperator(data=data)
Traceback (most recent call last):
...
quantarhei.exceptions.QuantarheiError: The data do not represent a superoperator
>>> data = numpy.zeros((3,1,3,2))
>>> So = SuperOperator(data=data)
Traceback (most recent call last):
...
quantarhei.exceptions.QuantarheiError: `data` has to be `square` four-dimensional matrix
"""
data: numpy.ndarray = BasisManagedComplexArray("data") # type: ignore[assignment]
_data: numpy.ndarray
name: str = ""
def __init__(
self, dim: int | None = None, data: Any = None, real: bool = False
) -> None:
# Set the currently used basis
cb = self.manager.get_current_basis()
self.set_current_basis(cb)
# unless it is the basis outside any context
if cb != 0:
self.manager.register_with_basis(cb, self)
self._data_initialized = False
self.dim = dim
if dim is not None:
if real:
self.data = numpy.zeros((dim, dim, dim, dim), dtype=REAL)
else:
self.data = numpy.zeros((dim, dim, dim, dim), dtype=COMPLEX)
elif data is not None:
self.data = data
if len(data.shape) != 4:
raise QuantarheiError("The data do not represent a superoperator")
Nd = data.shape[0]
if numpy.any(numpy.array(data.shape) - Nd):
raise QuantarheiError(
"`data` has to be `square` four-dimensional matrix"
)
self.dim = data.shape[0]
[docs]
def apply(self, oper: Any, target: Any = None, copy: bool = True) -> Any:
"""Applies superoperator to an operator
Parameters
----------
oper : Operator
Operator on which the present superoperator is applied
Examples
--------
>>> import quantarhei as qr
>>> import numpy
>>> op = qr.ReducedDensityMatrix(data=[[0.0, 1.0], [1.0, 0.0]])
Let's take a matrix A
>>> A = numpy.zeros((2,2))
>>> A[0,1] = 1.0
>>> A[1,0] = 1.0
and create a superoperator equivalent to matrix multiplication
.. math::
A\\rho = \\sum_{i}A_{ik}\\rho_{kj} = \\
\\sum_{kl}\\delta_{jl}A_{ik}\\rho_{kl} = \\
\\sum_{kl}D_{ijkl}\\rho_{kl}
D_{ijkl} = \\delta_{jl}A_{ik}
>>> data = numpy.zeros((2,2,2,2))
>>> for i in range(2):
... for j in range(2):
... for k in range(2):
... for l in range(2):
... if j == l:
... data[i,j,k,l] = A[i,k]
... else:
... data[i,j,k,l] = 0.0
>>> Dd = SuperOperator(data=data)
Now we can check that maultiplication and application of the
superoperator lead to the same result.
>>> B1 = Dd.apply(op)
>>> B2 = numpy.dot(A,op.data)
>>> numpy.allclose(B1.data, B2)
True
Every time you do this, new density matrix object is created. To avoid
this, we can apply the operation without copying the object.
>>> B3 = Dd.apply(op, copy=False)
>>> print(B3 == op)
True
`B3` is now a different reference to the same object as `op`. We can
check that the result of the operation is still correct:
>>> numpy.allclose(B3.data, B2)
True
"""
if copy:
oper_ven = _copy_module.copy(oper)
try:
# if oper is Quantarhei operator
oper_ven.data = numpy.tensordot(self.data, oper.data)
except AttributeError:
# if oper is numpy array
oper_ven = numpy.tensordot(self.data, oper)
return oper_ven
oper.data = numpy.tensordot(self.data, oper.data)
return oper
[docs]
def transform(self, SS: numpy.ndarray, inv: numpy.ndarray | None = None) -> None:
"""Transforms the superoperator to a new basis
Transformation of the superoperator follows similar rules
as the one of operators. An operar :math:`A` is transformed as
follows:
.. math::
A_{\\alpha\\beta} = \\sum_{ij}(s^{-1})_{\\alpha i}A_{ij}s_{j\\beta}
and
.. math::
A_{ij} = \\sum_{\\alpha\\beta} s_{i\\alpha} \\
A_{\\alpha\\beta}(s^{-1})_{\\beta j}
The transformation matrix :math:`s` is obtained by standard
diagonalization routines and its elements can be expressed in Dirac
notation as
.. math::
s_{i\\alpha} = \\langle i | \\alpha \\rangle
The inverse matrix :math:`s^{-1}` is obtained by transposition, because
.. math::
\\langle i | j \\rangle = \\delta_{ij} = \\
\\sum_{\\alpha} \\langle i | \\alpha \\rangle \\
\\langle \\alpha | j \\rangle
and we see that
.. math::
(s^{-1})_{\\alpha i} = s_{i \\alpha}.
Given an operator :math:`A` which is
a result of an action of the superoperator :math:`R` on operar
:math:`A` we can see that the transformation occurs as follows:
.. math::
A_{ij} = \\sum_{kl}R_{ijkl}B_{kl}
.. math::
A_{\\alpha\\beta} = \\
\\sum_{ij}(s^{-1})_{\\alpha i}A_{ij}s_{j\\beta} = \\
\\sum_{ijkl} (s^{-1})_{\\alpha i} s_{j\\beta} R_{ijkl} B_{kl}
Using the back transformation of the operator :math:`B` we obtaine
.. math::
A_{\\alpha\\beta} = \\
\\sum_{ij}(s^{-1})_{\\alpha i}A_{ij}s_{j\\beta} = \\
\\sum_{\\gamma\\delta} \\left [ \\right ] B_{\\gamma\\delta}
which translates into
.. math::
R_{\\alpha\\beta\\gamma\\delta} = \\
\\sum_{ijkl} s_{i \\alpha} \\
s_{j\\beta} R_{ijkl} \\
s_{k\\gamma}s_{l\\delta}
Parameters
----------
SS : float matrix
Transformation matrix
inv : float matrix, optional
Inverse of the transformation matrix
Examples
--------
"""
if self.manager.warn_about_basis_change:
print(f"\nQr >>> SuperOperator '{self.name}' changes basis")
#
# if inverse matrix not present, we create it
#
if inv is None:
S1 = numpy.linalg.inv(SS)
else:
S1 = inv
# dimension of the transformation matrix
dim = SS.shape[0]
#
# Dimension 4 means a single, time independent superoperator
#
if self._data.ndim == 4:
for c in range(dim):
for d in range(dim):
self._data[:, :, c, d] = numpy.dot(
S1, numpy.dot(self._data[:, :, c, d], SS)
)
for a in range(dim):
for b in range(dim):
self._data[a, b, :, :] = numpy.dot(
S1, numpy.dot(self._data[a, b, :, :], SS)
)
#
# Larger dimension means more superoperators or time dependence
#
else:
for tt in range(self._data.shape[0]):
for c in range(dim):
for d in range(dim):
self._data[tt, :, :, c, d] = numpy.dot(
S1, numpy.dot(self._data[tt, :, :, c, d], SS)
)
for a in range(dim):
for b in range(dim):
self._data[tt, a, b, :, :] = numpy.dot(
S1, numpy.dot(self._data[tt, a, b, :, :], SS)
)