Coverage for strongcoca / response / base.py: 100%
64 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-04-15 18:15 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-04-15 18:15 +0000
1import logging
2from abc import ABC, abstractmethod
3from typing import Optional
5import numpy as np
6from ase import Atoms
8from .utilities import Broadening
9from ..types import Array
10from ..units import au_to_eV, eV_to_au, au_to_eA
11from ..utilities import ClassFormatter
13logger = logging.getLogger(__name__)
16class BaseResponse(ABC):
18 """Objects of this class hold response function of, e.g., nanoparticles or
19 molecules, in several different representations. The latter include, e.g.,
20 the polarizability tensor in the real or imaginary frequency domain.
22 Parameters
23 ----------
24 broadening
25 Broadening of the response.
26 pbc
27 True if underlying system is periodic.
28 name
29 Name of response function.
30 """
32 def __init__(self,
33 broadening: Broadening,
34 pbc: bool = False,
35 name: str = 'BaseResponse') -> None:
36 logger.debug(f'Entering {self.__class__.__name__}.__init__')
37 self._broadening = broadening
38 self._pbc = pbc
39 self._name = name
40 self._atoms: Optional[Atoms] = None
42 def __str__(self) -> str:
43 """String representation."""
44 fmt = ClassFormatter(self)
45 fmt.append_class_name()
47 fmt.append_attr('name')
48 fmt.append_attr('pbc')
49 fmt.append_attr('atoms')
51 return fmt.to_string()
53 def _repr_html_(self) -> str:
54 """HTML representation for Jupyter notebooks."""
55 rows = [
56 f'<tr><td style="text-align: left;">name</td><td>{self._name}</td></tr>',
57 f'<tr><td style="text-align: left;">broadening</td><td>{self._broadening!r}</td></tr>',
58 f'<tr><td style="text-align: left;">pbc</td><td>{self._pbc}</td></tr>',
59 ]
60 return (
61 f'<h4>{self.__class__.__name__}</h4>'
62 '<table>'
63 '<thead><tr>'
64 '<th style="text-align: left;">field</th>'
65 '<th style="text-align: left;">value</th>'
66 '</tr></thead>'
67 '<tbody>' + ''.join(rows) + '</tbody>'
68 '</table>'
69 )
71 @property
72 def pbc(self) -> bool:
73 """True if underlying system is periodic."""
74 return self._pbc
76 @property
77 def broadening(self) -> Broadening:
78 """Broadening of the response."""
79 return self._broadening
81 @property
82 def name(self) -> str:
83 """Name of response function."""
84 return self._name
86 @property
87 def atoms(self) -> Optional[Atoms]:
88 """Atomic structure associated with this response function.
89 None if not available."""
90 return self._atoms
92 def _get_dipole_strength_function(self, frequencies: Array) -> np.ndarray:
93 """Returns the dipole strength function.
95 Parameters
96 ----------
97 frequencies
98 List of frequencies at which the dipole strength function is calculated;
99 in atomic units.
101 Returns
102 -------
103 Dipole strength function at the given frequencies; in atomic units.
104 """
105 freq_w = np.asarray(frequencies)
106 pol_wvv = self._get_dynamic_polarizability(frequencies)
107 # Take diagonal
108 pol_wv = pol_wvv[(Ellipsis, ) + np.diag_indices(3)]
109 osc_wv = 2 / np.pi * freq_w[:, np.newaxis] * pol_wv.imag
110 return osc_wv # type: ignore
112 def get_dipole_strength_function(self, frequencies: Array) -> np.ndarray:
113 """Returns the dipole strength function.
115 Parameters
116 ----------
117 frequencies
118 List of frequencies at which the dipole strength function is calculated;
119 in units of eV.
121 Returns
122 -------
123 Dipole strength function at the given frequencies; in units of 1/eV.
124 """
125 freq_w = np.asarray(frequencies) * eV_to_au
126 osc_wv = self._get_dipole_strength_function(freq_w)
127 osc_wv = osc_wv / au_to_eV
128 return osc_wv
130 @abstractmethod
131 def _get_dynamic_polarizability(self, frequencies: Array) -> np.ndarray:
132 """Returns the dynamic polarizability in the real frequency domain.
134 Parameters
135 ----------
136 frequencies
137 List of frequencies at which the polarizability is calculated; in atomic units.
139 Returns
140 -------
141 Dynamical polarizability tensor at the given frequencies; in atomic units.
142 """
143 raise NotImplementedError()
145 def get_dynamic_polarizability(self, frequencies: Array) -> np.ndarray:
146 """Returns the dynamic polarizability in the real frequency domain.
148 Parameters
149 ----------
150 frequencies
151 List of frequencies at which the polarizability is calculated; in units of eV.
153 Returns
154 -------
155 Dynamical polarizability tensor at the given frequencies; in units of (eÅ)**2/eV.
156 """
157 freq_w = np.asarray(frequencies) * eV_to_au
158 dm_wvv = self._get_dynamic_polarizability(freq_w)
159 dm_wvv = dm_wvv * au_to_eA**2 / au_to_eV
160 return dm_wvv
162 @abstractmethod
163 def _get_dynamic_polarizability_imaginary_frequency(
164 self, frequencies: Array) -> np.ndarray:
165 """Returns the dynamic polarizability in the imaginary frequency domain.
167 Parameters
168 ----------
169 frequencies
170 List of frequencies at which the polarizability is calculated; in atomic units.
172 Returns
173 -------
174 Dynamical polarizability tensor at the given imaginary frequencies; in atomic units.
175 """
176 raise NotImplementedError()
178 def get_dynamic_polarizability_imaginary_frequency(
179 self, frequencies: Array) -> np.ndarray:
180 """Returns the dynamic polarizability in the imaginary frequency domain.
182 Parameters
183 ----------
184 frequencies
185 List of frequencies at which the polarizability is calculated; in units of eV.
187 Returns
188 -------
189 Dynamical polarizability tensor at the given imaginary frequencies; in units of (eÅ)**2/eV.
190 """
191 freq_w = np.asarray(frequencies) * eV_to_au
192 dm_wvv = self._get_dynamic_polarizability_imaginary_frequency(freq_w)
193 dm_wvv = dm_wvv * au_to_eA**2 / au_to_eV
194 return dm_wvv