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