Coverage for strongcoca/response/time_domain_casida.py: 100%
43 statements
« prev ^ index » next coverage.py v7.10.6, created at 2025-10-26 18:44 +0000
« prev ^ index » next coverage.py v7.10.6, created at 2025-10-26 18:44 +0000
1import logging
2from typing import Optional, Tuple
3import numpy as np
4from . import CasidaResponse, TimeDomainResponse
5from ..units import fs_to_au
8logger = logging.getLogger(__name__)
11def _check_propagation_parameters(eigenvals: np.ndarray,
12 dt: Optional[float] = None,
13 n_steps: Optional[int] = None) -> Tuple[float, int]:
14 """Checks propagation parameters based on eigenvalues.
16 Parameters
17 ----------
18 dt
19 Time step in atomic units.
21 The time step is used to propagate the Casida system and
22 generate the dipole tensor in the time domain; by default the time
23 step is set based on the highest frequency of the eigenvalue spectrum.
24 n_steps
25 Number of time steps to propagate the Casida system and generate the
26 dipole tensor in the time domain; by default the value is set
27 based on the lowest frequency of the eigenvalue spectrum.
29 Returns
30 -------
31 dt
32 Time step in atomic units (the same as given or recommended).
33 n_steps
34 Number of time steps (the same as given or recommended).
35 """
37 # determine suitable integration parameters and
38 # check sensibility of user-provided values (if applicable)
39 # - time step
40 dt_recommended = 1 / sorted(eigenvals)[-1]
41 logger.debug(f'dt_recommended: {dt_recommended} a.u.')
42 if dt is None:
43 dt = dt_recommended
44 else:
45 if dt > 2 * dt_recommended or dt < 0.5 * dt_recommended:
46 logger.warning(f'dt ({dt} a.u.) deviates strongly from the'
47 f' recommended value ({dt_recommended} a.u.).')
48 logger.debug(f'dt_actual: {dt} a.u.')
49 # - maximum time and number of time steps
50 t_max_recommended = 100 / sorted(eigenvals)[0]
51 logger.debug(f't_max_recommended: {t_max_recommended} a.u.')
52 if n_steps is None:
53 n_steps = int(t_max_recommended / dt)
54 else:
55 t_max = dt * (n_steps - 1)
56 if t_max < 0.5 * t_max_recommended or t_max > 2 * t_max_recommended:
57 logger.warning(f't_max ({t_max} a.u.) deviates strongly from the'
58 f' recommended value ({t_max_recommended} a.u.).')
59 logger.debug(f'n_steps: {n_steps}')
61 return dt, n_steps
64def build_time_domain_response_from_casida(
65 casida_response: CasidaResponse,
66 dt: Optional[float] = None,
67 n_steps: Optional[int] = None,
68 units: str = 'fs',
69 name: str = 'PropagatedCasida') -> TimeDomainResponse:
70 """Builds a :class:`~strongcoca.response.TimeDomainResponse` instance
71 from an existing Casida representation via time propagation.
73 Parameters
74 ----------
75 casida_response
76 Response in Casida representation.
77 dt
78 Time step; units are fs by default, optionally atomic units (see :attr:`units`).
79 n_steps
80 Number of time steps for which to propagate the equations of motion.
81 units
82 `fs` to specify dt in fs or `au` to specify `dt` in atomic units.
83 """
85 if units == 'fs':
86 if dt is not None:
87 dt = dt * fs_to_au
88 elif units != 'au':
89 raise ValueError(f"units has to be 'fs' or 'au', not '{units}'")
91 # TODO: The argument below should probably be just `energies`. Test this. See #41.
92 dt, n_steps = _check_propagation_parameters(
93 casida_response.excitations._energies**2, dt, n_steps)
95 # Matrices
96 D_n = casida_response._D_n
97 K_nn = casida_response._K_nn
98 mu_nv = casida_response._mu_nv
100 # Propagate
101 time_t = np.linspace(0, dt * (n_steps - 1), n_steps)
102 dm_tvv = np.zeros((n_steps, 3, 3))
104 # Integrate equations of motion
105 Re_rho_nv = 0 * mu_nv
106 Im_rho_nv = -mu_nv
107 for t in range(0, n_steps):
108 # Evaluate dipole moment
109 dm_tvv[t] = 2 * np.dot(Re_rho_nv.T, mu_nv)
111 # Symplectic propagator for real-time TDDFT
112 Im_rho_nv += dt * D_n[:, np.newaxis] * Re_rho_nv + 2 * dt * np.dot(K_nn, Re_rho_nv)
113 Re_rho_nv -= dt * D_n[:, np.newaxis] * Im_rho_nv
115 # Broadening
116 broadening = casida_response.broadening
117 return TimeDomainResponse(time_t, dm_tvv, broadening=broadening,
118 name=name, units='au')