master-thesis/python/richard_hops/bcf.py
2021-10-18 18:40:50 +02:00

464 lines
13 KiB
Python

import numpy as np
from scipy.special import gamma as gamma_func
# from scipy.special import zeta as zeta_func
from scipy.integrate import quad
from collections import namedtuple
from functools import partial
# import mpmath
import warnings
import traceback
import mpmath
# import mypath
# import convenientMath as cm
# import persistentdata as pd
# import binfootprint as bf
# import fcSpline
import logging
log = logging.getLogger(__name__)
#############################################################
#
# parameter types
#
#############################################################
def zeta_func(x, q):
return np.complex128(mpmath.zeta(x, q))
obcf_param_type = namedtuple(
typename="obcf_param_type", field_names=["s", "eta", "gamma"]
)
obcf_param_KAST_ANKERHOLD_type = namedtuple(
typename="obcf_param_KAST_ANKERHOLD_type", field_names=["s", "alpha", "omega_c"]
)
#############################################################
#
# class definitiona
#
#############################################################
class OSD(object):
r"""ohmic spectral density
J(w) = eta w^s exp(-w / gamma)
"""
def __init__(self, s, eta, gamma, normed=False, mp=False):
"""
represents an super / sub Ohmic spectral density function
normed: if set True, scaled eta, such that int_0^infty J(w) = 1
mp: if set True, use mpmath for evaluation
this class implements set_state and get_state and is suitable for binfootprint
make sure, that s, eta, and gamma are regular float objects
"""
# the "args" attributes are use for get_state and set_state
self.s_arg = s
self.gamma_arg = gamma
self.normed_arg = normed
self.mp_arg = mp
if normed:
self.eta_arg = None
else:
self.eta_arg = eta
# non non math version
if not mp:
self._exp = np.exp
self._gamma_func = gamma_func
self.s = s
self.gamma = gamma
self.eta = eta
# mpmath version
else:
self._exp = mpmath.exp
self._gamma_func = mpmath.gamma
self.s = mpmath.mpf(s)
self.gamma = mpmath.mpf(gamma)
self.eta = mpmath.mpf(eta)
if normed:
self.eta = self.eta / self.integral()
self.c = self.eta * self._gamma_func(self.s)
def __call__(self, w):
if isinstance(w, np.ndarray):
res = np.empty_like(w)
idx_l0 = np.where(w < 0)
res[idx_l0] = 0
idx_ge0 = np.where(w >= 0)
res[idx_ge0] = (
self.eta * w[idx_ge0] ** self.s * self._exp(-w[idx_ge0] / self.gamma)
)
return res
else:
if w < 0:
return 0
else:
return self.eta * w ** self.s * self._exp(-w / self.gamma)
def maximum_at(self):
return self.s * self.gamma
def maximum_val(self):
return self(self.maximum_at())
def integral(self):
return self.eta * self.gamma ** (self.s + 1) * self._gamma_func(self.s + 1)
def reorganization_energy(self):
"""
int_0^infity J(w) / w
"""
return self.eta * self.gamma ** self.s * self._gamma_func(self.s)
def shifted_bath_influence(self, t):
"""
int_0^infity J(w) / w exp(-i w t) = eta [ gamma/(1+i gamma t) ]**s * Gamma(s)
"""
return self.c * (self.gamma / (1 + 1j * self.gamma * t)) ** self.s
def __str__(self):
return (
"J(w) = eta w^s exp(-w / w_c)\n"
+ "eta = {}\n".format(self.eta)
+ "s = {}\n".format(self.s)
+ "w_c = {}\n".format(self.gamma)
)
def __getstate__(self):
return self.s_arg, self.eta_arg, self.gamma_arg, self.normed_arg, self.mp_arg
def __setstate__(self, state):
self.__init__(*state)
class MakeItReal(object):
def __init__(self, func):
self.func = func
def __call__(self, x):
return np.real(self.func(x))
def __bfkey__(self):
return self.func
class OBCF(object):
r"""ohmic bath correlation functions (BCF)
consider ohmic spectral density
J(w) = eta w^s exp(-w / gamma)
general BCF
alpha(tau) = 1/pi int_0^infty d w J(w) exp(-i w tau)
-> ohmic BCF
alpha(tau) = eta/pi gamma^(s+1) int_0^infty d x x^s exp(-x) exp(-i x gamma t)
= eta/pi gamma^(s+1) (1 + i gamma t)^(-s-1) gamma_func(s+1)
"""
def __init__(self, s, eta, gamma, normed=False):
self.s = float(s)
self.gamma = float(gamma)
if normed:
self.eta = None
self._c1 = 1 / np.pi
else:
self.eta = eta
self._c1 = self.eta * gamma_func(s + 1) * gamma ** (s + 1) / np.pi
def __call__(self, tau):
# with warnings.catch_warnings():
# warnings.filterwarnings('error')
# try:
# a1 = 1 + 1j*self.gamma * tau
# a2 = a1**(-(self.s+1))
# return self._c1 * a2
# except:
# traceback.print_stack()
# print("gamma", self.gamma)
# print("tau", tau)
# print('a1', a1)
# print('a2', a2)
# raise
return self._c1 * (1 + 1j * self.gamma * tau) ** (-(self.s + 1))
def div1(self, tau):
return (
-self._c1
* (self.s + 1)
* (1 + 1j * self.gamma * tau) ** (-(self.s + 2))
* 1j
* self.gamma
)
def __getstate__(self):
return self.s, self.eta, self.gamma
def __setstate__(self, state):
eta = state[1]
if eta is None:
self.__init__(*state, normed=True)
else:
self.__init__(*state, normed=False)
def __eq__(self, other):
return (
(self.s == other.s)
and (self.eta == other.eta)
and (self.gamma == other.gamma)
)
def __repr__(self):
s = "\nbcf with J(w) = eta w^s exp(-w / gamma)"
s += "\ns :{}".format(self.s)
s += "\neta :{}".format(self.eta)
s += "\ngamma:{}".format(self.gamma)
return s
class OBCF_FT(object):
def __init__(self, s, eta, gamma, beta):
self.s_p1 = s + 1
self.eta = eta
self.gamma = gamma
self.beta = beta
self._c = self.eta / self.beta ** (self.s_p1) * gamma_func(self.s_p1) / np.pi
self._beta_gamma = self.beta * self.gamma
def __call__(self, tau):
if isinstance(tau, np.ndarray):
res = np.empty(shape=tau.shape, dtype=np.complex128)
res_flat = res.flat
tau_flat = tau.flat
for i, ti in enumerate(tau_flat):
zf = zeta_func(
self.s_p1,
(1 + self._beta_gamma + 1j * self.gamma * ti) / self._beta_gamma,
)
res_flat[i] = self._c * (
(self._beta_gamma / (1 + 1j * self.gamma * ti)) ** self.s_p1
+ zf
+ np.conj(zf)
)
return res
else:
zf = zeta_func(
self.s_p1,
(1 + self._beta_gamma + 1j * self.gamma * tau) / self._beta_gamma,
)
return self._c * (
(self._beta_gamma / (1 + 1j * self.gamma * tau)) ** self.s_p1
+ zf
+ np.conj(zf)
)
def __bfkey__(self):
return self.s_p1, self.eta, self.gamma, self.beta
class OBCF_FT_mp(object):
def __init__(self, s, eta, gamma, beta):
self.s_p1 = mpmath.mpf(s) + 1
self.eta = mpmath.mpf(eta)
self.gamma = mpmath.mpf(gamma)
self.beta = mpmath.mpf(beta)
self._c = (
self.eta / self.beta ** (self.s_p1) * mpmath.gamma(self.s_p1) / mpmath.pi
)
self._beta_gamma = self.beta * self.gamma
def __call__(self, tau):
zf = mpmath.zeta(
self.s_p1, (1 + self._beta_gamma + 1j * self.gamma * tau) / self._beta_gamma
)
return self._c * (
(self._beta_gamma / (1 + 1j * self.gamma * tau)) ** self.s_p1
+ zf
+ mpmath.conj(zf)
)
class OFTCorr(object):
def __init__(self, s, eta, gamma, beta):
self.s_p1 = s + 1
self.eta = eta
self.gamma = gamma
self.beta = beta
self._c = self.eta / self.beta ** (self.s_p1) * gamma_func(self.s_p1) / np.pi
self._beta_gamma = self.beta * self.gamma
def __call__(self, tau):
if isinstance(tau, np.ndarray):
res = np.empty(shape=tau.shape, dtype=np.complex128)
res_flat = res.flat
tau_flat = tau.flat
for i, ti in enumerate(tau_flat):
res_flat[i] = self._c * zeta_func(
self.s_p1,
(1 + self._beta_gamma + 1j * self.gamma * ti) / self._beta_gamma,
)
return res
else:
return self._c * zeta_func(
self.s_p1,
(1 + self._beta_gamma + 1j * self.gamma * tau) / self._beta_gamma,
)
def __bfkey__(self):
return self.s_p1, self.eta, self.gamma, self.beta
def BOSE_distribution_single(x):
"""calc (exp(x)-1)^-1"""
if x < 0:
raise ValueError("x < 0")
elif x > 40:
return np.exp(-x)
else:
return 1 / np.expm1(x)
def BOSE_distribution(x):
try:
return np.asarray([BOSE_distribution_single(xi) for xi in x])
except:
return BOSE_distribution_single(x)
class OFTDens(object):
def __init__(self, s, eta, gamma, beta):
self.osd = OSD(s, eta, gamma)
self.beta = beta
def __call__(self, w):
return BOSE_distribution(self.beta * w) * self.osd(w)
def __bfkey__(self):
return self.osd, self.beta
class BCF_aprx(object):
r"""approximation of the bath correlation function using exponentials
alpha(tau) = sum_i=1^n g_i exp(-omega_i tau)
"""
def __init__(self, g, omega):
g = np.asarray(g)
omega = np.asarray(omega)
self._n = g.shape
assert self._n == omega.shape
self.g = g
self.omega = omega
def __call__(self, tau):
r"""return alpha(tau) = sum_i=1^n g_i exp(-w_i tau) for arbitrary shape of tau"""
try:
s_tau = tau.shape
dims_tau = len(s_tau)
tau_ = tau.reshape((1,) + s_tau)
g_ = self.g.reshape(self._n + dims_tau * (1,))
omega_ = self.omega.reshape(self._n + dims_tau * (1,))
res = np.empty(shape=s_tau, dtype=np.complex128)
idx_pos = np.where(tau >= 0)
res[idx_pos] = np.sum(g_ * np.exp(-omega_ * tau_[(0,) + idx_pos]), axis=0)
idx_neg = np.where(tau < 0)
res[idx_neg] = np.conj(
np.sum(g_ * np.exp(-omega_ * np.abs(tau_[(0,) + idx_neg])), axis=0)
)
except Exception as e:
if tau >= 0:
res = np.sum(self.g * np.exp(-self.omega * tau))
else:
res = np.sum(self.g * np.exp(self.omega * tau)).conj()
return res
def get_SD(self, t_max, n):
"""
t0, t1, t2 ... ,tn=t_max
al(t0), al(t1), al(t2), ... al(t n-1) + al(tn), al(t n+1) = al(t n-1)^ast, ... al(t 2n-1) = al(-1)^ast
idx: 0 ... n-1 n .. -1
-> dt = t_max / n-1
"""
t, dt = np.linspace(0, t_max, n, retstep=True)
alpha_t = self.__call__(t)
alpha_t = np.hstack((alpha_t[:-1], np.conj(alpha_t[:0:-1])))
N = 2 * (n - 1)
t_, dt_ = np.linspace(0, 2 * t_max, N, endpoint=False, retstep=True)
assert dt == dt_
assert len(t_) == len(alpha_t)
fft_alpha = np.fft.ifft(alpha_t) * dt * N
dw = 2 * np.pi / N / dt
freq = np.hstack((np.arange(N // 2), -np.arange(1, N // 2 + 1)[::-1])) * dw
return 0.5 * np.fft.ifftshift(np.real(fft_alpha)), np.fft.ifftshift(freq)
def get_SD_analyt(self, w):
sd = np.asarray(
[
np.sum(
(
self.g.real * self.omega.real
- self.g.imag * (wi - self.omega.imag)
)
/ (self.omega.real ** 2 + (wi - self.omega.imag) ** 2)
)
for wi in w
]
)
return sd
def ft(self, om):
if isinstance(om, np.ndarray):
res = np.empty(shape=om.shape, dtype=np.complex128)
res_flat_view = res.flat
om_flat = om.flatten().reshape(-1, 1)
res_flat_view[:] = np.sum(
self.g
* 2
* self.omega
/ ((om_flat - 1j * self.omega) * (om_flat + 1j * self.omega)),
axis=1,
)
return res
else:
return (
self.g
* 2
* self.omega
/ ((om - 1j * self.omega) * (om + 1j * self.omega))
)
def n(self):
return self._n[0]