Source code for strongcoca.response.time_domain_casida
import logging
from typing import Optional, Tuple
import numpy as np
from . import CasidaResponse, TimeDomainResponse
from ..units import fs_to_au
logger = logging.getLogger(__name__)
def _check_propagation_parameters(eigenvals: np.ndarray,
dt: Optional[float] = None,
n_steps: Optional[int] = None) -> Tuple[float, int]:
"""Checks propagation parameters based on eigenvalues.
Parameters
----------
dt
Time step in atomic units.
The time step is used to propagate the Casida system and
generate the dipole tensor in the time domain; by default the time
step is set based on the highest frequency of the eigenvalue spectrum.
n_steps
Number of time steps to propagate the Casida system and generate the
dipole tensor in the time domain; by default the value is set
based on the lowest frequency of the eigenvalue spectrum.
Returns
-------
dt
Time step in atomic units (the same as given or recommended).
n_steps
Number of time steps (the same as given or recommended).
"""
# determine suitable integration parameters and
# check sensibility of user-provided values (if applicable)
# - time step
dt_recommended = 1 / sorted(eigenvals)[-1]
logger.debug(f'dt_recommended: {dt_recommended} a.u.')
if dt is None:
dt = dt_recommended
else:
if dt > 2 * dt_recommended or dt < 0.5 * dt_recommended:
logger.warning(f'dt ({dt} a.u.) deviates strongly from the'
f' recommended value ({dt_recommended} a.u.).')
logger.debug(f'dt_actual: {dt} a.u.')
# - maximum time and number of time steps
t_max_recommended = 100 / sorted(eigenvals)[0]
logger.debug(f't_max_recommended: {t_max_recommended} a.u.')
if n_steps is None:
n_steps = int(t_max_recommended / dt)
else:
t_max = dt * (n_steps - 1)
if t_max < 0.5 * t_max_recommended or t_max > 2 * t_max_recommended:
logger.warning(f't_max ({t_max} a.u.) deviates strongly from the'
f' recommended value ({t_max_recommended} a.u.).')
logger.debug(f'n_steps: {n_steps}')
return dt, n_steps
[docs]
def build_time_domain_response_from_casida(
casida_response: CasidaResponse,
dt: Optional[float] = None,
n_steps: Optional[int] = None,
units: str = 'fs',
name: str = 'PropagatedCasida') -> TimeDomainResponse:
"""Builds a :class:`~strongcoca.response.TimeDomainResponse` instance
from an existing Casida representation via time propagation.
Parameters
----------
casida_response
Response in Casida representation.
dt
Time step; units are fs by default, optionally atomic units (see :attr:`units`).
n_steps
Number of time steps for which to propagate the equations of motion.
units
`fs` to specify dt in fs or `au` to specify `dt` in atomic units.
"""
if units == 'fs':
if dt is not None:
dt = dt * fs_to_au
elif units != 'au':
raise ValueError(f"units has to be 'fs' or 'au', not '{units}'")
# TODO: The argument below should probably be just `energies`. Test this. See #41.
dt, n_steps = _check_propagation_parameters(
casida_response.excitations._energies**2, dt, n_steps)
# Matrices
D_n = casida_response._D_n
K_nn = casida_response._K_nn
mu_nv = casida_response._mu_nv
# Propagate
time_t = np.linspace(0, dt * (n_steps - 1), n_steps)
dm_tvv = np.zeros((n_steps, 3, 3))
# Integrate equations of motion
Re_rho_nv = 0 * mu_nv
Im_rho_nv = -mu_nv
for t in range(0, n_steps):
# Evaluate dipole moment
dm_tvv[t] = 2 * np.dot(Re_rho_nv.T, mu_nv)
# Symplectic propagator for real-time TDDFT
Im_rho_nv += dt * D_n[:, np.newaxis] * Re_rho_nv + 2 * dt * np.dot(K_nn, Re_rho_nv)
Re_rho_nv -= dt * D_n[:, np.newaxis] * Im_rho_nv
# Broadening
broadening = casida_response.broadening
return TimeDomainResponse(time_t, dm_tvv, broadening=broadening,
name=name, units='au')