mirror of
https://github.com/vale981/master-thesis
synced 2025-03-11 13:46:39 -04:00
424 lines
13 KiB
Python
424 lines
13 KiB
Python
|
"""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
|
|||
|
from typing import Callable, Union
|
|||
|
import numpy.typing as npt
|
|||
|
import lmfit
|
|||
|
from . import util
|
|||
|
import functools
|
|||
|
from numpy.polynomial import Polynomial
|
|||
|
|
|||
|
|
|||
|
@dataclass
|
|||
|
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.
|
|||
|
|
|||
|
:param t_max: the maximum simulation time
|
|||
|
:param function: the BCF as python function, will be set to the
|
|||
|
exponential expansion if the BCF coefficients are given.
|
|||
|
|
|||
|
:param num_terms: the number of terms of the expansion of the
|
|||
|
BCF
|
|||
|
:param precision: the precision in the sampling for the fit,
|
|||
|
``t_max/precision`` points will be used
|
|||
|
:param exponents: the exponential factors in the BCF expansion
|
|||
|
:param factors: the pre-factors in the BCF expansion
|
|||
|
|
|||
|
:attribute approx:
|
|||
|
"""
|
|||
|
|
|||
|
#: the maximum simulation time
|
|||
|
t_max: float
|
|||
|
function: Union[Callable[[npt.ArrayLike], np.ndarray], None] = None
|
|||
|
num_terms: Union[int, None] = None
|
|||
|
resolution: Union[float, None] = None
|
|||
|
factors: Union[np.ndarray, None] = None
|
|||
|
exponents: Union[np.ndarray, None] = None
|
|||
|
|
|||
|
#: the BCF as exponential expansion
|
|||
|
approx: Callable[[npt.ArrayLike], np.ndarray] = field(init=False)
|
|||
|
|
|||
|
def __post_init__(self):
|
|||
|
if self.function is not None:
|
|||
|
if self.num_terms is None or self.resolution is None:
|
|||
|
raise ValueError(
|
|||
|
"Either give the function, the number of terms and the resolution or the coefficients."
|
|||
|
)
|
|||
|
|
|||
|
self.exponents, self.factors = util.fit_α(
|
|||
|
self.function,
|
|||
|
self.num_terms,
|
|||
|
self.t_max,
|
|||
|
int(self.t_max / self.resolution),
|
|||
|
)
|
|||
|
|
|||
|
else:
|
|||
|
if self.factors is None or self.exponents is None:
|
|||
|
raise ValueError(
|
|||
|
"Either give the function and number of terms or the coefficients."
|
|||
|
)
|
|||
|
|
|||
|
if self.factors.size != self.exponents.size:
|
|||
|
raise ValueError(
|
|||
|
"Factors and exponents have to have the same dimension."
|
|||
|
)
|
|||
|
|
|||
|
self.num_terms = self.factors.size
|
|||
|
|
|||
|
self.approx = functools.partial(util.α_apprx, G=self.factors, W=self.exponents)
|
|||
|
|
|||
|
if self.function is None:
|
|||
|
self.function = self.approx
|
|||
|
|
|||
|
|
|||
|
@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)
|
|||
|
:param α: the BCF
|
|||
|
|
|||
|
|
|||
|
: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
|
|||
|
α: BCF
|
|||
|
|
|||
|
t_max: float = field(init=False)
|
|||
|
|
|||
|
# BCF coefficients
|
|||
|
W: np.ndarray = field(init=False)
|
|||
|
G: np.ndarray = field(init=False)
|
|||
|
|
|||
|
def __post_init__(self):
|
|||
|
self.t_max = self.α.t_max
|
|||
|
|
|||
|
self.W = self.α.exponents
|
|||
|
self.G = self.α.factors * (self.η ** 2)
|
|||
|
|
|||
|
|
|||
|
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 -G ((z + γ)s + δc)
|
|||
|
roots_f = -(γ + δ * c / s)
|
|||
|
|
|||
|
# 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.Ω
|
|||
|
* s_c
|
|||
|
* util.poly_real(
|
|||
|
Polynomial.fromroots(
|
|||
|
np.concatenate(
|
|||
|
(
|
|||
|
[root_f],
|
|||
|
util.except_element(roots_z, i),
|
|||
|
util.except_element(roots_z, i).conj(),
|
|||
|
)
|
|||
|
)
|
|||
|
)
|
|||
|
)
|
|||
|
for root_f, G_c, γ_c, δ_c, s_c, c_c, i in zip(
|
|||
|
roots_f, G_abs, γ, δ, s, c, range(len(c))
|
|||
|
)
|
|||
|
]
|
|||
|
|
|||
|
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.
|
|||
|
|
|||
|
Calling it with a time argument returns the whole matrix.
|
|||
|
|
|||
|
: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)
|
|||
|
)
|
|||
|
|
|||
|
|
|||
|
@dataclass
|
|||
|
class FlowParams:
|
|||
|
r"""
|
|||
|
A parameter object to hold the global parameters for the flow algorithm.
|
|||
|
|
|||
|
:param system: the system parameters, see :any:`SystemParams`
|
|||
|
:param α_0_dot: the zero temperature BCF time derivative
|
|||
|
:param n: the excitation number of the initial state of the system :math:`|n\rangle`
|
|||
|
|
|||
|
:attr L: the exponential factors in the BCF derivative
|
|||
|
expansion :math:`\dot{\alpha}_0=\sum_k P_k e^{-L_k \cdot t}`
|
|||
|
|
|||
|
:attr P: the pre-factors in the BCF derivative expansion
|
|||
|
:attr q_s_0: the expectation value :math:`\langle q(0)^2\rangle`
|
|||
|
:attr p_s_0: the expectation value :math:`\langle q(0)^2\rangle`
|
|||
|
:attr qp_0: the expectation value :math:`\langle q(0)p(0)\rangle`
|
|||
|
:attr prop: the propagator matrix :math:`G(t)`, see :any:`Propagator`
|
|||
|
:attr B_coef: the pre-factors of the exponential sum of :math:`B=G_{12}=\sum_k B_k e^{-C_k \cdot t}`
|
|||
|
|
|||
|
- note the minus sign
|
|||
|
:attr C: the exponents of the exponential sum of ``B``
|
|||
|
:attr A_coef: the pre-factors of the exponential sum of :math:`A=G_{11}=\sum_k A_k e^{-C_k \cdot t}`
|
|||
|
"""
|
|||
|
|
|||
|
system: InitVar[SystemParams]
|
|||
|
α_0_dot: BCF
|
|||
|
n: int
|
|||
|
|
|||
|
# BCF derivative coefficients
|
|||
|
L: np.ndarray = field(init=False)
|
|||
|
P: np.ndarray = field(init=False)
|
|||
|
|
|||
|
# initial state
|
|||
|
q_s_0: float = field(init=False)
|
|||
|
p_s_0: float = field(init=False)
|
|||
|
qp_0: complex = field(init=False)
|
|||
|
|
|||
|
# propagator
|
|||
|
prop: Propagator = field(init=False)
|
|||
|
B_coef: np.ndarray = field(init=False)
|
|||
|
C: np.ndarray = field(init=False)
|
|||
|
A_coef: np.ndarray = field(init=False)
|
|||
|
|
|||
|
def __post_init__(self, system):
|
|||
|
self.L = self.α_0_dot.exponents
|
|||
|
self.P = self.α_0_dot.factors * (system.η ** 2)
|
|||
|
|
|||
|
self.q_s_0 = 1 + 2 * self.n
|
|||
|
self.p_s_0 = 1 + 2 * self.n
|
|||
|
self.qp_0 = 1j
|
|||
|
|
|||
|
self.C, self.B_coef = calculate_coefficients(system)
|
|||
|
self.A_coef = self.B_coef * self.C
|
|||
|
|
|||
|
self.B_coef = system.Ω * self.B_coef
|
|||
|
self.C = -self.C # mind the extra -
|
|||
|
|
|||
|
self.prop = Propagator(system)
|
|||
|
|
|||
|
|
|||
|
def get_flow(
|
|||
|
sys: SystemParams, flow: FlowParams
|
|||
|
) -> Callable[[npt.ArrayLike], np.ndarray]:
|
|||
|
r"""Get the flow :math:`\frac{d}{dt}\langle H_B\rangle` as a function of time."""
|
|||
|
|
|||
|
# TODO: Think about the too bloated ``FlowParams`` class and this ugly function
|
|||
|
A = flow.prop[0, 0]
|
|||
|
B = flow.prop[0, 1]
|
|||
|
Bk = flow.B_coef #
|
|||
|
Ak = flow.A_coef #
|
|||
|
Pk = flow.P #
|
|||
|
Lk = flow.L #
|
|||
|
Ck = flow.C #
|
|||
|
Wk = sys.W #
|
|||
|
Gk = sys.G #
|
|||
|
Γ1 = (Bk[:, None] * Pk[None, :]) / (Lk[None, :] - Ck[:, None])
|
|||
|
Γ2 = (Bk[:, None] * Gk[None, :]) / (Ck[:, None] - Wk[None, :])
|
|||
|
Γ3 = (Bk[:, None] * Gk.conj()[None, :]) / (Ck[:, None] + Wk.conj()[None, :])
|
|||
|
ΓA = (Ak[:, None] * Pk[None, :]) / (Lk[None, :] - Ck[:, None])
|
|||
|
|
|||
|
def A_conv(t):
|
|||
|
t = np.asarray(t)
|
|||
|
result = np.zeros_like(t, dtype="complex128")
|
|||
|
|
|||
|
# print(np.sort(Ck.imag.flatten()))
|
|||
|
|
|||
|
for (n, m) in itertools.product(range(len(Ak)), range(len(Pk))):
|
|||
|
result += ΓA[n, m] * (np.exp(-Ck[n] * t) - np.exp(-Lk[m] * t))
|
|||
|
|
|||
|
return result
|
|||
|
|
|||
|
def B_conv(t):
|
|||
|
t = np.asarray(t)
|
|||
|
result = np.zeros_like(t, dtype="complex128")
|
|||
|
for (n, m) in itertools.product(range(len(Bk)), range(len(Pk))):
|
|||
|
result += Γ1[n, m] * (np.exp(-Ck[n] * t) - np.exp(-Lk[m] * t))
|
|||
|
|
|||
|
return result
|
|||
|
|
|||
|
def flow_conv_free(t):
|
|||
|
a, b = A(t), B(t)
|
|||
|
ac, bc = A_conv(t), B_conv(t)
|
|||
|
# print(ac, bc)
|
|||
|
return (
|
|||
|
-1
|
|||
|
/ 2
|
|||
|
* (
|
|||
|
flow.q_s_0 * a * ac
|
|||
|
+ flow.p_s_0 * b * bc
|
|||
|
+ flow.qp_0 * a * bc
|
|||
|
+ flow.qp_0.conjugate() * b * ac
|
|||
|
).imag
|
|||
|
)
|
|||
|
|
|||
|
def conv_part(t):
|
|||
|
t = np.asarray(t)
|
|||
|
result = np.zeros_like(t, dtype="float")
|
|||
|
for (m, k, n, l) in itertools.product(
|
|||
|
range(len(Bk)), range(len(Pk)), range(len(Bk)), range(len(Gk))
|
|||
|
):
|
|||
|
g_1_2 = (
|
|||
|
Γ1[m, k]
|
|||
|
* Γ2[n, l]
|
|||
|
* (
|
|||
|
(1 - np.exp(-(Ck[m] + Wk[l]) * t)) / (Ck[m] + Wk[l])
|
|||
|
+ (np.exp(-(Ck[m] + Ck[n]) * t) - 1) / (Ck[m] + Ck[n])
|
|||
|
+ (np.exp(-(Lk[k] + Wk[l]) * t) - 1) / (Lk[k] + Wk[l])
|
|||
|
+ (1 - np.exp(-(Lk[k] + Ck[n]) * t)) / (Lk[k] + Ck[n])
|
|||
|
)
|
|||
|
)
|
|||
|
|
|||
|
g_1_3 = (
|
|||
|
Γ1[m, k]
|
|||
|
* Γ3[n, l]
|
|||
|
* (
|
|||
|
(1 - np.exp(-(Ck[m] + Ck[n]) * t)) / (Ck[m] + Ck[n])
|
|||
|
- (1 - np.exp(-(Lk[k] + Ck[n]) * t)) / (Lk[k] + Ck[n])
|
|||
|
- (
|
|||
|
np.exp(-(Ck[n] + Wk[l].conj() * t))
|
|||
|
- np.exp(-(Ck[m] + Ck[n]) * t)
|
|||
|
)
|
|||
|
/ (Ck[m] - Wk[l].conj())
|
|||
|
+ (
|
|||
|
np.exp(-(Ck[n] + Wk[l].conj() * t))
|
|||
|
- np.exp(-(Lk[k] + Ck[n]) * t)
|
|||
|
)
|
|||
|
/ (Lk[k] - Wk[l].conj())
|
|||
|
)
|
|||
|
)
|
|||
|
|
|||
|
result += -1 / 2 * (g_1_2.imag + g_1_3.imag)
|
|||
|
|
|||
|
return result
|
|||
|
|
|||
|
def final_flow(t: npt.ArrayLike) -> np.ndarray:
|
|||
|
return flow_conv_free(t) + conv_part(t)
|
|||
|
|
|||
|
return final_flow
|