Coverage for strongcoca/response/time_domain.py: 100%
54 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
3import numpy as np
5from .base import BaseResponse
6from ..types import Array
7from ..utilities import ClassFormatter
8from .utilities import fourier_transform
9from ..units import au_to_eA, eA_to_au, au_to_fs, fs_to_au
10from .utilities import Broadening
13logger = logging.getLogger(__name__)
16class TimeDomainResponse(BaseResponse):
17 """Objects of this class hold different representations of the response
18 functions for a time domain system.
20 Parameters
21 ----------
22 times
23 Time grid of {t} equally spaced time values
24 units are fs by default, optionally atomic units (see :attr:`units`).
25 dipole_moments
26 Dipole moment tensors at the time values, shape {t}x{3}x{3}
27 units are eÅ by default, optionally atomic units (see :attr:`units`).
28 broadening
29 artificial broadening applied to the response
30 units
31 `fseA` to specify times in fs and dipole_moments in eÅ
32 or `au` to specify inputs in atomic units.
34 This parameter determines whether conversion should be performed during
35 initialization and has no effect on instance methods and variables.
36 name
37 Name of response.
39 Examples
40 --------
42 The following snippet illustrates how a time domain response object can be
43 constructed. Here, artificial dipole moments are used. In practice dipole
44 moments would usually be imported from, e.g., TDDFT calculations:
46 >>> import numpy as np
47 >>> from strongcoca.response import TimeDomainResponse
48 >>> from strongcoca.response.utilities import LorentzianBroadening
49 >>>
50 >>> t = np.linspace(0, 10)
51 >>> dm = np.ones((t.shape[0], 3, 3))
52 >>> broadening = LorentzianBroadening(0.1)
53 >>> response = TimeDomainResponse(t, dm, broadening, name='Eats')
54 >>> print(response)
55 -------------------- TimeDomainResponse --------------------
56 name : Eats
57 times (fs) : [ 0. 0.20408 0.40816 ...
58 9.59184 9.79592 10. ]
59 dipole_moments (eÅ) : [[[1. 1. 1.]
60 [1. 1. 1.]
61 [1. 1. 1.]]
62 ...
63 """
64 def __init__(self,
65 times: np.ndarray,
66 dipole_moments: np.ndarray,
67 broadening: Broadening,
68 units: str = 'fseA',
69 name: str = 'TimeDomain') -> None:
70 logger.debug(f'Entering {self.__class__.__name__}.__init__')
71 super().__init__(broadening=broadening, pbc=False, name=name)
72 if times.ndim != 1:
73 raise ValueError('times array must be one dimensional')
74 if dipole_moments.shape != (times.shape[0], 3, 3):
75 raise ValueError('dipole_moments array has the wrong shape')
77 times = times.copy()
78 dipole_moments = dipole_moments.copy()
80 if units == 'fseA':
81 times = times * fs_to_au # TODO Possibly simplify to *= fs_to_au after resolving #57
82 dipole_moments = dipole_moments * eA_to_au
83 elif units != 'au':
84 raise ValueError(f"units has to be 'fseA' or 'au', not '{units}'")
86 self._time_t = times
87 dt_t = self._time_t[1:] - self._time_t[:-1]
88 self._dt = dt_t[0]
89 if not np.allclose(dt_t, self._dt):
90 raise ValueError('times array need to be equally spaced')
91 self._dm_tvv = dipole_moments
93 def __str__(self) -> str:
94 fmt = ClassFormatter(self, pad=20)
95 fmt.append_class_name()
97 fmt.append_attr('name')
99 fmt.append_attr('times', unit='fs')
100 fmt.append_attr('dipole_moments', unit='eÅ')
101 fmt.append_attr('broadening')
102 formula = self.atoms.get_chemical_formula() if self.atoms is not None else None
103 fmt.append_attr('atoms', formula)
105 return fmt.to_string()
107 @property
108 def times(self) -> np.ndarray:
109 """Times at which dipole moments are provided."""
110 return self._time_t * au_to_fs # type: ignore
112 @property
113 def dipole_moments(self) -> np.ndarray:
114 """Dipole moment tensors in the time domain."""
115 return self._dm_tvv * au_to_eA # type: ignore
117 def _get_dynamic_polarizability(self, frequencies: Array) -> np.ndarray:
118 freq_w = np.asarray(frequencies)
119 dm_wvv = fourier_transform(self._time_t, self._dm_tvv, freq_w,
120 broadening=self._broadening)
121 return dm_wvv
123 def _get_dynamic_polarizability_imaginary_frequency(
124 self, frequencies: Array) -> np.ndarray:
125 freq_w = np.asarray(frequencies)
126 dm_wvv = fourier_transform(self._time_t, self._dm_tvv, freq_w,
127 freq_axis='imag')
128 return dm_wvv