master-thesis/python/energy_flow_proper/hopsflow/gaussflow.py

432 lines
14 KiB
Python
Raw Normal View History

2021-11-25 20:14:52 +01:00
"""Calculate the energy flow into the bath for a simple gaussian
Quantum Brownian Motion model.
This is done analytically for a BCF that is a sum of exponentials.
"""
import numpy as np
import itertools
from dataclasses import dataclass, field, InitVar
2021-11-26 14:02:13 +01:00
from typing import Callable, Union, Optional
2021-11-25 20:14:52 +01:00
import numpy.typing as npt
import lmfit
from . import util
import functools
from numpy.polynomial import Polynomial
class BCF:
r"""A parameter object to hold information about a BCF.
The BCFs will be expanded into a sum of exponentials like
:math:`\alpha(\tau) = \sum_k G_k \cdot \exp(-W_k\cdot\tau)`.
You can either give the BCFs as parameter or the coefficients.
If you give the BCFs, the fit will be performed automatically.
2021-11-26 14:02:13 +01:00
Calling this object will call the wrapped BCF function.
2021-11-25 20:14:52 +01:00
2021-11-26 14:02:13 +01:00
:param resolution: the precision in the sampling for the fit, ``t_max/precision``
points will be used
:param num_terms: the number of terms of the expansion of the BCF expansion
2021-11-25 20:14:52 +01:00
"""
2021-11-26 14:02:13 +01:00
def __init__(
self,
t_max: float,
function: Optional[Callable[[np.ndarray], np.ndarray]] = None,
num_terms: Optional[int] = None,
resolution: Optional[float] = None,
factors: Optional[np.ndarray] = None,
exponents: Optional[np.ndarray] = None,
):
#: the maximum simulation time
self.t_max = t_max
if function is not None:
#: the BCF as python function, will be set to the exponential
#: expansion if the BCF coefficients are given.
self.function = function
if num_terms is None or resolution is None:
2021-11-25 20:14:52 +01:00
raise ValueError(
"Either give the function, the number of terms and the resolution or the coefficients."
)
2021-11-26 14:02:13 +01:00
_exponents, _factors = util.fit_α(
2021-11-25 20:14:52 +01:00
self.function,
2021-11-26 14:02:13 +01:00
num_terms,
2021-11-25 20:14:52 +01:00
self.t_max,
2021-11-26 14:02:13 +01:00
int(self.t_max / resolution),
2021-11-25 20:14:52 +01:00
)
2021-11-26 14:02:13 +01:00
#: the factors in the BCF expansion
self.factors = _factors
#: the exponents in the BCF expansion
self.exponents = _exponents
2021-11-25 20:14:52 +01:00
else:
2021-11-26 14:02:13 +01:00
if factors is None or exponents is None:
2021-11-25 20:14:52 +01:00
raise ValueError(
"Either give the function and number of terms or the coefficients."
)
2021-11-26 14:02:13 +01:00
assert factors is not None
assert exponents is not None
self.factors = factors
self.exponents = factors
2021-11-25 20:14:52 +01:00
if self.factors.size != self.exponents.size:
raise ValueError(
"Factors and exponents have to have the same dimension."
)
2021-11-26 14:02:13 +01:00
self.function = self.approx
2021-11-25 20:14:52 +01:00
2021-11-26 14:02:13 +01:00
def approx(self, t: np.ndarray) -> np.ndarray:
"""The BCF as exponential expansion."""
return util.α_apprx(t, self.factors, self.exponents)
2021-11-25 20:14:52 +01:00
2021-11-26 14:02:13 +01:00
def __call__(self, t: np.ndarray):
return self.function(t)
2021-11-25 20:14:52 +01:00
@dataclass
class SystemParams:
r"""A parameter object to hold information about the physical
system and the global parameters for the algorithm.
:param Ω: the system hamiltonian energy scale, :math:`H_s =
\Omega (p^2 + q^2)`
:param η: the coupling strength (in essence a prefactor to the BCF)
2021-12-01 18:01:42 +01:00
:param α_0: the zero temperature BCF
2021-11-25 20:14:52 +01:00
:attr t_max: the maximum simulation time, will be copied from :attr:`α`
:attr W: the exponential factors in the BCF expansion
:attr G: the pre-factors in the BCF expansion
"""
Ω: float
η: float
2021-12-01 18:01:42 +01:00
α_0: BCF
2021-11-25 20:14:52 +01:00
t_max: float = field(init=False)
# BCF coefficients
W: np.ndarray = field(init=False)
G: np.ndarray = field(init=False)
def __post_init__(self):
2021-12-01 18:01:42 +01:00
self.t_max = self.α_0.t_max
2021-11-25 20:14:52 +01:00
2021-12-01 18:01:42 +01:00
self.W = self.α_0.exponents
self.G = self.α_0.factors * self.η
2021-11-25 20:14:52 +01:00
def construct_polynomials(sys: SystemParams) -> tuple[Polynomial, Polynomial]:
r"""
Construct the polynomials required to find the coefficients
and exponents of the solution.
:param sys: a parameter object with the system parameters
:returns: a list of polynomials
- :math:`f_0(z) = \prod_k (z-z_k) (z-z_k^{\ast})`
- :math:`p(z) = p_1(z) + \sum_n q_n(z)` where
- :math:`p_1(z) = (z^2 + \Omega^2)\prod_k (z-z_k)(z-z_k^{\ast})`
- :math:`q_n(z)=\Omega f_n(z) \prod_{k\neq n}(z-z_k) (z-z_k^{\ast})`
"""
# we begin by calculating all the constants we'll need
φ = -np.angle(sys.G)
G_abs = np.abs(sys.G)
γ, δ = sys.W.real, sys.W.imag
s, c = np.sin(φ), np.cos(φ)
# the roots of the denominator of the laplace transform of α
roots_z = -sys.W
# now the polynomials
f_0 = util.poly_real(
Polynomial.fromroots(np.concatenate((roots_z, roots_z.conj())))
)
p_1 = Polynomial([sys.Ω ** 2, 0, 1]) * f_0
q = [
-G_c
* sys.Ω
* Polynomial((δ_c * c_c + γ_c * s_c, s_c))
2021-11-25 20:14:52 +01:00
* util.poly_real(
Polynomial.fromroots(
np.concatenate(
(
util.except_element(roots_z, i),
util.except_element(roots_z, i).conj(),
)
)
)
)
for G_c, γ_c, δ_c, s_c, c_c, i in zip(G_abs, γ, δ, s, c, range(len(c)))
2021-11-25 20:14:52 +01:00
]
p = p_1 + sum(q)
return f_0, p
def calculate_coefficients(sys: SystemParams) -> tuple[np.ndarray, np.ndarray]:
r"""
Calculates the coefficients required to construct the
propagator matrix :math:`G`.
:param sys: a parameter object with the system parameters
:returns: the exponents and the residuals which play a role as pre-factors
"""
f_0, p = construct_polynomials(sys)
master_roots = p.roots()
if np.unique(master_roots).size != master_roots.size:
raise RuntimeError(
"""The roots of the polynomial are not unique.
You can try to alter the number of terms in the expansion of the BCF."""
)
resiquals = f_0(master_roots) / p.deriv()(master_roots)
return master_roots, resiquals
def _expand_t(f):
def wrapped(self, t):
t = np.expand_dims(np.asarray(t), axis=0)
return f(self, t)
return wrapped
class Propagator:
"""The propagator matrix :math:`G` for the system.
You can get inidividual matrix elements as functions of time
through indexing this object.
2021-11-26 14:26:49 +01:00
Calling it with a time argument returns the whole matrix as
array of shape ``(time, 2, 2)``.
2021-11-25 20:14:52 +01:00
:param sys: a parameter object with the system parameters
"""
def __init__(self, sys: SystemParams):
self.params = sys
self._roots, self._res = calculate_coefficients(sys)
self._roots_exp = np.expand_dims(self._roots, axis=1)
self._res_exp = np.expand_dims(self._res, axis=1)
self. = sys.Ω
self._res_times_roots = self._res_exp * self._roots_exp
self._res_times_roots_squared = self._res_times_roots * self._roots_exp
self._elements = np.array([[self.el_11, self.el_12], [self.el_21, self.el_22]])
pass
@_expand_t
def el_11(self, t):
return (self._res_times_roots * np.exp(t * self._roots_exp)).real.sum(axis=0)
@_expand_t
def el_12(self, t):
return self. * (self._res_exp * np.exp(t * self._roots_exp)).real.sum(axis=0)
@_expand_t
def el_21(self, t):
return (self._res_times_roots_squared * np.exp(t * self._roots_exp)).real.sum(
axis=0
) / self.
def el_22(self, t):
return self.el_11(t)
def __getitem__(self, indices) -> Callable[[npt.ArrayLike], np.ndarray]:
"""Get an individual matrix elment as function."""
return self._elements[indices]
@_expand_t
def __call__(self, t) -> np.ndarray:
"""Get the propagator as array of shape ``(time, 2, 2)``."""
g_12 = self._res_exp * np.exp(t * self._roots_exp)
diag = self._roots_exp * g_12
g_21 = self._roots_exp * diag / self.
return (
np.array([[diag, g_12 * self.], [g_21, diag]])
.real.sum(axis=2)
.swapaxes(0, 2)
.swapaxes(1, 2)
)
2021-11-26 14:26:49 +01:00
class Flow:
2021-11-25 20:14:52 +01:00
r"""
2021-11-26 14:26:49 +01:00
A collection of methods to calculate the time derivative
of the bath energy expectation value :math:`\frac{d}{dt}\langle H_B\rangle`
which can be retrieved as a function of time by calling this object.
2021-11-25 20:14:52 +01:00
:param system: the system parameters, see :any:`SystemParams`
2021-12-01 18:01:42 +01:00
:param α: the (finite temperature) BCF
2021-11-25 20:14:52 +01:00
:param α_0_dot: the zero temperature BCF time derivative
:param n: the excitation number of the initial state of the system :math:`|n\rangle`
2021-11-26 14:26:49 +01:00
"""
2021-11-25 20:14:52 +01:00
2021-12-01 18:01:42 +01:00
def __init__(self, system: SystemParams, α: BCF, α_0_dot: BCF, n: int):
2021-11-26 14:26:49 +01:00
#: the exponential factors in the BCF derivative
#: expansion :math:`\dot{\alpha}_0=\sum_k P_k e^{-L_k \cdot t}`
self.L = α_0_dot.exponents
2021-11-25 20:14:52 +01:00
2021-11-26 14:26:49 +01:00
#: the pre-factors in the BCF derivative expansion
2021-11-30 14:22:06 +01:00
self.P = α_0_dot.factors * system.η
2021-11-25 20:14:52 +01:00
2021-11-26 14:26:49 +01:00
self.C, self.B = calculate_coefficients(system)
2021-11-25 20:14:52 +01:00
2021-11-26 14:26:49 +01:00
#: the pre-factors of the exponential sum of :math:`A=G_{11}=\sum_k A_k e^{-C_k \cdot t}`
self.A = self.B * self.C
2021-11-25 20:14:52 +01:00
2021-11-26 14:26:49 +01:00
#: the exponents of the exponential sum of :math:`B=G_{12}=\sum_k B_k e^{-C_k \cdot t}`
#:
#: - note the minus sign
self.C = -self.C # mind the extra -
2021-11-25 20:14:52 +01:00
2021-11-26 14:26:49 +01:00
#: the pre-factors of the exponential sum of ``B``
self.B = system.Ω * self.B
2021-11-25 20:14:52 +01:00
2021-11-26 14:26:49 +01:00
#: the exponential factors in the BCF
#: expansion :math:`\alpha=\sum_k G_k e^{-W_k \cdot t}`
2021-12-01 18:01:42 +01:00
self.W = α.exponents
2021-11-25 20:14:52 +01:00
2021-11-26 14:26:49 +01:00
#: the pre-factors factors in the BCF expansion
2021-12-01 18:01:42 +01:00
self.G = α.factors
2021-11-25 20:14:52 +01:00
2021-11-26 14:26:49 +01:00
#: the expectation value :math:`\langle q(0)^2\rangle`
self.q_s_0 = 1 + 2 * n
2021-11-25 20:14:52 +01:00
2021-11-26 14:26:49 +01:00
#: the expectation value :math:`\langle q(0)^2\rangle`
self.p_s_0 = 1 + 2 * n
2021-11-25 20:14:52 +01:00
2021-11-26 14:26:49 +01:00
#: the expectation value :math:`\langle q(0)p(0)\rangle`
self.qp_0 = 1j
#: the propagator matrix :math:`G(t)`, see :any:`Propagator`
2021-11-25 20:14:52 +01:00
self.prop = Propagator(system)
2021-11-26 14:26:49 +01:00
#: the coefficient matrix :math:`\Gamma^1`
self.Γ1 = (self.B[:, None] * self.P[None, :]) / (
self.L[None, :] - self.C[:, None]
)
2021-11-25 20:14:52 +01:00
2021-11-26 14:26:49 +01:00
#: the coefficient matrix :math:`\Gamma^2`
self.Γ2 = (self.B[:, None] * self.G[None, :]) / (
self.C[:, None] - self.W[None, :]
)
2021-11-25 20:14:52 +01:00
2021-11-26 14:26:49 +01:00
#: the coefficient matrix :math:`\Gamma^3`
self.Γ3 = (self.B[:, None] * self.G.conj()[None, :]) / (
self.C[:, None] + self.W.conj()[None, :]
)
#: the coefficient matrix :math:`\Gamma^A`
self.ΓA = (self.A[:, None] * self.P[None, :]) / (
self.L[None, :] - self.C[:, None]
)
2021-11-25 20:14:52 +01:00
2021-11-26 14:26:49 +01:00
def A_conv(self, t: npt.ArrayLike):
r"""The integral :math:`\int_0^t A(s)\dot{\alpha}_0(t-s)ds`."""
result = np.zeros_like(t, dtype="complex128")
for (n, m) in itertools.product(range(len(self.A)), range(len(self.P))):
result += self.ΓA[n, m] * (np.exp(-self.C[n] * t) - np.exp(-self.L[m] * t))
2021-11-25 20:14:52 +01:00
return result
2021-11-26 14:26:49 +01:00
def B_conv(self, t: npt.ArrayLike):
r"""The integral :math:`\int_0^t B(s)\dot{\alpha}_0(t-s)ds`."""
2021-11-25 20:14:52 +01:00
result = np.zeros_like(t, dtype="complex128")
2021-11-26 14:26:49 +01:00
for (n, m) in itertools.product(range(len(self.B)), range(len(self.P))):
result += self.Γ1[n, m] * (np.exp(-self.C[n] * t) - np.exp(-self.L[m] * t))
2021-11-25 20:14:52 +01:00
return result
2021-11-26 14:26:49 +01:00
def flow_nontherm(self, t: npt.ArrayLike):
r"""The part of the flow that **does not** involve :math:`\alpha`."""
a, b = self.prop[0, 0](t), self.prop[0, 1](t)
ac, bc = self.A_conv(t), self.B_conv(t)
2021-11-25 20:14:52 +01:00
return (
-1
/ 2
* (
2021-11-26 14:26:49 +01:00
self.q_s_0 * a * ac
+ self.p_s_0 * b * bc
+ self.qp_0 * a * bc
+ self.qp_0.conjugate() * b * ac
2021-11-25 20:14:52 +01:00
).imag
)
2021-11-26 14:26:49 +01:00
def flow_therm(self, t: npt.ArrayLike):
r"""The part of the flow that **does** involve :math:`\alpha`."""
2021-11-25 20:14:52 +01:00
t = np.asarray(t)
result = np.zeros_like(t, dtype="float")
2021-11-26 14:26:49 +01:00
2021-11-25 20:14:52 +01:00
for (m, k, n, l) in itertools.product(
2021-11-26 14:26:49 +01:00
range(len(self.B)),
range(len(self.P)),
range(len(self.B)),
range(len(self.G)),
2021-11-25 20:14:52 +01:00
):
g_1_2 = (
2021-11-26 14:26:49 +01:00
self.Γ1[m, k]
* self.Γ2[n, l]
2021-11-25 20:14:52 +01:00
* (
2021-11-26 14:26:49 +01:00
(1 - np.exp(-(self.C[m] + self.W[l]) * t)) / (self.C[m] + self.W[l])
+ (np.exp(-(self.C[m] + self.C[n]) * t) - 1)
/ (self.C[m] + self.C[n])
+ (np.exp(-(self.L[k] + self.W[l]) * t) - 1)
/ (self.L[k] + self.W[l])
+ (1 - np.exp(-(self.L[k] + self.C[n]) * t))
/ (self.L[k] + self.C[n])
2021-11-25 20:14:52 +01:00
)
2021-11-26 14:26:49 +01:00
).imag
2021-11-25 20:14:52 +01:00
g_1_3 = (
2021-11-26 14:26:49 +01:00
self.Γ1[m, k]
* self.Γ3[n, l]
2021-11-25 20:14:52 +01:00
* (
2021-11-26 14:26:49 +01:00
(1 - np.exp(-(self.C[m] + self.C[n]) * t)) / (self.C[m] + self.C[n])
- (1 - np.exp(-(self.L[k] + self.C[n]) * t))
/ (self.L[k] + self.C[n])
2021-11-25 20:14:52 +01:00
- (
2021-11-26 14:26:49 +01:00
np.exp(-(self.C[n] + self.W[l].conj() * t))
- np.exp(-(self.C[m] + self.C[n]) * t)
2021-11-25 20:14:52 +01:00
)
2021-11-26 14:26:49 +01:00
/ (self.C[m] - self.W[l].conj())
2021-11-25 20:14:52 +01:00
+ (
2021-11-26 14:26:49 +01:00
np.exp(-(self.C[n] + self.W[l].conj() * t))
- np.exp(-(self.L[k] + self.C[n]) * t)
2021-11-25 20:14:52 +01:00
)
2021-11-26 14:26:49 +01:00
/ (self.L[k] - self.W[l].conj())
2021-11-25 20:14:52 +01:00
)
2021-11-26 14:26:49 +01:00
).imag
2021-11-25 20:14:52 +01:00
2021-11-26 14:26:49 +01:00
result += -1 / 2 * (g_1_2 + g_1_3)
2021-11-25 20:14:52 +01:00
return result
2021-11-26 14:26:49 +01:00
def __call__(self, t: npt.ArrayLike) -> np.ndarray:
r"""
The flow. Time derivative of the bath energy
expectation value :math:`\frac{d}{dt}\langle H_B\rangle`.
"""
2021-11-25 20:14:52 +01:00
2021-11-26 14:26:49 +01:00
return self.flow_nontherm(t) + self.flow_therm(t)