Source code for strongcoca.response.time_domain
import logging
import numpy as np
from .base import BaseResponse
from ..types import Array
from ..utilities import ClassFormatter
from .utilities import fourier_transform
from ..units import au_to_eA, eA_to_au, au_to_fs, fs_to_au
from .utilities import Broadening
logger = logging.getLogger(__name__)
[docs]
class TimeDomainResponse(BaseResponse):
"""Objects of this class hold different representations of the response
functions for a time domain system.
Parameters
----------
times
Time grid of {t} equally spaced time values
units are fs by default, optionally atomic units (see :attr:`units`).
dipole_moments
Dipole moment tensors at the time values, shape {t}x{3}x{3}
units are eÅ by default, optionally atomic units (see :attr:`units`).
broadening
artificial broadening applied to the response
units
`fseA` to specify times in fs and dipole_moments in eÅ
or `au` to specify inputs in atomic units.
This parameter determines whether conversion should be performed during
initialization and has no effect on instance methods and variables.
name
Name of response.
Examples
--------
The following snippet illustrates how a time domain response object can be
constructed. Here, artificial dipole moments are used. In practice dipole
moments would usually be imported from, e.g., TDDFT calculations:
>>> import numpy as np
>>> from strongcoca.response import TimeDomainResponse
>>> from strongcoca.response.utilities import LorentzianBroadening
>>>
>>> t = np.linspace(0, 10)
>>> dm = np.ones((t.shape[0], 3, 3))
>>> broadening = LorentzianBroadening(0.1)
>>> response = TimeDomainResponse(t, dm, broadening, name='Eats')
>>> print(response)
-------------------- TimeDomainResponse --------------------
name : Eats
times (fs) : [ 0. 0.20408 0.40816 ...
9.59184 9.79592 10. ]
dipole_moments (eÅ) : [[[1. 1. 1.]
[1. 1. 1.]
[1. 1. 1.]]
...
"""
def __init__(self,
times: np.ndarray,
dipole_moments: np.ndarray,
broadening: Broadening,
units: str = 'fseA',
name: str = 'TimeDomain') -> None:
logger.debug(f'Entering {self.__class__.__name__}.__init__')
super().__init__(broadening=broadening, pbc=False, name=name)
if times.ndim != 1:
raise ValueError('times array must be one dimensional')
if dipole_moments.shape != (times.shape[0], 3, 3):
raise ValueError('dipole_moments array has the wrong shape')
times = times.copy()
dipole_moments = dipole_moments.copy()
if units == 'fseA':
times = times * fs_to_au # TODO Possibly simplify to *= fs_to_au after resolving #57
dipole_moments = dipole_moments * eA_to_au
elif units != 'au':
raise ValueError(f"units has to be 'fseA' or 'au', not '{units}'")
self._time_t = times
dt_t = self._time_t[1:] - self._time_t[:-1]
self._dt = dt_t[0]
if not np.allclose(dt_t, self._dt):
raise ValueError('times array need to be equally spaced')
self._dm_tvv = dipole_moments
def __str__(self) -> str:
fmt = ClassFormatter(self, pad=20)
fmt.append_class_name()
fmt.append_attr('name')
fmt.append_attr('times', unit='fs')
fmt.append_attr('dipole_moments', unit='eÅ')
fmt.append_attr('broadening')
formula = self.atoms.get_chemical_formula() if self.atoms is not None else None
fmt.append_attr('atoms', formula)
return fmt.to_string()
@property
def times(self) -> np.ndarray:
"""Times at which dipole moments are provided."""
return self._time_t * au_to_fs # type: ignore
@property
def dipole_moments(self) -> np.ndarray:
"""Dipole moment tensors in the time domain."""
return self._dm_tvv * au_to_eA # type: ignore
def _get_dynamic_polarizability(self, frequencies: Array) -> np.ndarray:
freq_w = np.asarray(frequencies)
dm_wvv = fourier_transform(self._time_t, self._dm_tvv, freq_w,
broadening=self._broadening)
return dm_wvv
def _get_dynamic_polarizability_imaginary_frequency(
self, frequencies: Array) -> np.ndarray:
freq_w = np.asarray(frequencies)
dm_wvv = fourier_transform(self._time_t, self._dm_tvv, freq_w,
freq_axis='imag')
return dm_wvv