Source code for gpaw.response.frequencies

from __future__ import annotations

import numbers
from typing import Any

import numpy as np
from ase.units import Ha
from gpaw.typing import ArrayLike1D


[docs]class FrequencyDescriptor: def __init__(self, omega_w: ArrayLike1D): """Frequency grid descriptor. Parameters ---------- omega_w: Frequency grid in Hartree units. """ self.omega_w = np.asarray(omega_w).copy() def __len__(self): return len(self.omega_w) def __repr__(self): emin = self.omega_w[0] * Ha emax = self.omega_w[-1] * Ha return (f'{self.__class__.__name__}' f'(from {emin:.3f} to {emax:.3f} eV, {len(self)} points)')
[docs] @staticmethod def from_array_or_dict(input: dict[str, Any] | ArrayLike1D ) -> FrequencyDescriptor: """Create frequency-grid descriptor. In case *input* is a list on frequencies (in eV) a :class:`FrequencyGridDescriptor` instance is returned. Othervise a :class:`NonLinearFrequencyDescriptor` instance is returned. >>> from ase.units import Ha >>> params = dict(type='nonlinear', ... domega0=0.1, ... omega2=10, ... omegamax=50) >>> wd = FrequencyDescriptor.from_array_or_dict(params) >>> wd.omega_w[0:2] * Ha array([0. , 0.10041594]) """ if isinstance(input, dict): assert input['type'] == 'nonlinear' domega0 = input.get('domega0') omega2 = input.get('omega2') omegamax = input['omegamax'] return NonLinearFrequencyDescriptor( (0.1 if domega0 is None else domega0) / Ha, (10.0 if omega2 is None else omega2) / Ha, omegamax / Ha) return FrequencyGridDescriptor(np.asarray(input) / Ha)
class ComplexFrequencyDescriptor: def __init__(self, hz_z: ArrayLike1D): """Construct the complex frequency descriptor. Parameters ---------- hz_z: Array of complex frequencies (in units of Hartree) """ # Use a copy of the input array hz_z = np.array(hz_z) assert hz_z.dtype == complex self.hz_z = hz_z def __len__(self): return len(self.hz_z) def almost_eq(self, zd): if len(zd) != len(self): return False return np.allclose(self.hz_z, zd.hz_z) @staticmethod def from_array(frequencies: ArrayLike1D): """Create a ComplexFrequencyDescriptor from frequencies in eV.""" return ComplexFrequencyDescriptor(np.asarray(frequencies) / Ha) @property def upper_half_plane(self): """All frequencies reside in the upper half complex frequency plane?""" return np.all(self.hz_z.imag > 0.) @property def horizontal_contour(self): """Do all frequency point lie on a horizontal contour?""" return np.ptp(self.hz_z.imag) < 1.e-6 @property def omega_w(self): """Real part of the frequencies.""" assert self.horizontal_contour, \ 'It only makes sense to index the frequencies by their real part '\ 'if they reside on a horizontal contour.' return self.hz_z.real
[docs]class FrequencyGridDescriptor(FrequencyDescriptor):
[docs] def get_index_range(self, lim1_m, lim2_m): """Get index range. """ i0_m = np.zeros(len(lim1_m), int) i1_m = np.zeros(len(lim2_m), int) for m, (lim1, lim2) in enumerate(zip(lim1_m, lim2_m)): i_x = np.logical_and(lim1 <= self.omega_w, lim2 >= self.omega_w) if i_x.any(): inds = np.argwhere(i_x) i0_m[m] = inds.min() i1_m[m] = inds.max() + 1 return i0_m, i1_m
[docs]class NonLinearFrequencyDescriptor(FrequencyDescriptor): def __init__(self, domega0: float, omega2: float, omegamax: float): """Non-linear frequency grid. Units are Hartree. See :ref:`frequency grid`. Parameters ---------- domega0: Frequency grid spacing for non-linear frequency grid at omega = 0. omega2: Frequency at which the non-linear frequency grid has doubled the spacing. omegamax: The upper frequency bound for the non-linear frequency grid. """ beta = (2**0.5 - 1) * domega0 / omega2 wmax = int(omegamax / (domega0 + beta * omegamax)) w = np.arange(wmax + 2) # + 2 is for buffer omega_w = w * domega0 / (1 - beta * w) super().__init__(omega_w) self.domega0 = domega0 self.omega2 = omega2 self.omegamax = omegamax self.omegamin = 0 self.beta = beta self.wmax = wmax self.omega_w = omega_w self.wmax = wmax
[docs] def get_floor_index(self, o_m, safe=True): """Get closest index rounding down.""" beta = self.beta w_m = (o_m / (self.domega0 + beta * o_m)).astype(int) if safe: if isinstance(w_m, np.ndarray): w_m[w_m >= self.wmax] = self.wmax - 1 elif isinstance(w_m, numbers.Integral): if w_m >= self.wmax: w_m = self.wmax - 1 else: raise TypeError return w_m
def get_index_range(self, omega1_m, omega2_m): omega1_m = omega1_m.copy() omega2_m = omega2_m.copy() omega1_m[omega1_m < 0] = 0 omega2_m[omega2_m < 0] = 0 w1_m = self.get_floor_index(omega1_m) w2_m = self.get_floor_index(omega2_m) o1_m = self.omega_w[w1_m] o2_m = self.omega_w[w2_m] w1_m[o1_m < omega1_m] += 1 w2_m[o2_m < omega2_m] += 1 return w1_m, w2_m