import logging
import numpy as np
from scipy.integrate import trapezoid
from .base import BaseResponse
from .dielectric import DielectricFunction
from ..types import Array
from ..utilities import ClassFormatter
from ..units import au_to_A, A_to_au
from .utilities import NoArtificialBroadening
logger = logging.getLogger(__name__)
[docs]
class MieGansResponse(BaseResponse):
r"""Objects of this class hold representations of the Mie-Gans response of a
ellipsoid in vacuum.
For an ellipsoid with semi-axes :math:`a_x, a_y, a_z` in the Cartesian directions,
the Mie-Gans polarizability is diagonal
`[Sihvola, J. Nanomater. 2007, 045090] <https://doi.org/10.1155/2007/45090>`_
.. math::
\alpha_{\mu\mu}(\omega) = \varepsilon_0 V
\frac{\varepsilon_r(\omega) - 1}{1 + N_\mu (\varepsilon_r(\omega) - 1)}
where :math:`N_\mu` are depolarization factors
.. math::
N_\mu = \frac{a_x a_y a_z}{2} \int_0^\infty
\frac{\mathrm{d}s}{(s+a_\mu^2)\sqrt{(s+a_x^2)(s+a_y^2)(s+a_z^2)}}.
It is useful to keep in mind that in Hartree atomic units the vacuum permitivity
.. math::
\varepsilon_0 = \frac{1}{4\pi}.
Thus the expression is simplified
.. math::
\alpha_{\mu\mu}(\omega) = \frac{a_x a_y a_z}{3}
\frac{\varepsilon_r(\omega) - 1}{1 + N_\mu (\varepsilon_r(\omega) - 1)}
Parameters
----------
semiaxes
Semi-axes of the ellipsoid in Cartesian coordinates in units of Å; shape {3}.
Optionally radius of sphere in units of Å; shape {1}.
dielectric_function
Dielectric function, which can have an analytic form or be sampled on a frequency grid.
name
Name of response.
Raises
------
TypeError
If :attr:`semiaxes` is not scalar or three-dimensional array.
ValueError
If any component of :attr:`semiaxes` is zero or smaller.
Examples
--------
The following snippet illustrates how a Mie-Gans response object can be constructed.
Here, the analytic Drude dielectric function of the free-electron gas is used.
Instead, a numeric dielectric function can be used with the help of
:func:`~strongcoca.response.read_dielec_function_file`.
>>> import numpy as np
>>> from strongcoca.response import MieGansResponse, DrudeDielectricFunction
>>>
>>> semiaxes = 20
>>> omega_p = 3
>>> gamma = 0.5
>>> eps = DrudeDielectricFunction(omega_p, gamma)
>>> response = MieGansResponse(semiaxes, eps, name='Leaves')
>>> print(response)
--------------------- MieGansResponse ----------------------
name : Leaves
semiaxes (Å) : [20. 20. 20.]
depol. factors : [0.33335 0.33335 0.33335]
Dielectric function : DrudeDielectricFunction
Different shapes of the ellipsoid can be set by supplying three cartesian semiaxes. Here a
sphere, a prolate ellipsoid and an oblate ellipsoid are created.
>>> response = MieGansResponse([30, 30, 30], eps, name='Sphere')
>>> print(response)
--------------------- MieGansResponse ----------------------
name : Sphere
semiaxes (Å) : [30. 30. 30.]
depol. factors : [0.33335 0.33335 0.33335]
Dielectric function : DrudeDielectricFunction
>>> response = MieGansResponse([25, 25, 35], eps, name='Prolate ellipsoid')
>>> print(response)
--------------------- MieGansResponse ----------------------
name : Prolate ellipsoid
semiaxes (Å) : [25. 25. 35.]
depol. factors : [0.37561 0.37561 0.24881]
Dielectric function : DrudeDielectricFunction
>>> response = MieGansResponse([30, 30, 20], eps, name='Oblate ellipsoid')
>>> print(response)
--------------------- MieGansResponse ----------------------
name : Oblate ellipsoid
semiaxes (Å) : [30. 30. 20.]
depol. factors : [0.27705 0.27705 0.44592]
Dielectric function : DrudeDielectricFunction
"""
def __init__(self,
semiaxes: Array,
dielectric_function: DielectricFunction,
name: str = 'MieGansResponse') -> None:
logger.debug(f'Entering {self.__class__.__name__}.__init__')
super().__init__(pbc=False, broadening=NoArtificialBroadening(), name=name)
self._set_semiaxes(semiaxes)
self._factor_v = self._calc_depolarization_factors()
self._eps = dielectric_function
def __str__(self) -> str:
fmt = ClassFormatter(self, pad=20)
fmt.append_class_name()
fmt.append_attr('name')
fmt.append_attr('semiaxes', unit='Å')
fmt.append_attr('depol. factors', self.depolarization_factors)
fmt.append_attr('Dielectric function', self.dielectric_function.name)
return fmt.to_string()
@property
def semiaxes(self) -> np.ndarray:
"""Semi-axes of the ellipsoid in Cartesian coordinates in units of Å."""
return self._a_v * au_to_A # type: ignore
@property
def depolarization_factors(self) -> np.ndarray:
"""Depolarization factors."""
return self._factor_v
@property
def dielectric_function(self) -> DielectricFunction:
"""Dielectric function of the Mie-Gans ellipsoid."""
return self._eps
def _get_dynamic_polarizability(
self, frequencies: Array) -> np.ndarray:
freq_w = np.asarray(frequencies)
eps_w = self._eps._eval_at(freq_w)
epsm_w = np.ones_like(eps_w)
deps_w = eps_w - epsm_w
depolarization_factors = self.depolarization_factors
dm_vw = deps_w / (epsm_w + depolarization_factors[:, np.newaxis] * deps_w)
dm_vw *= 1 / 3 * np.prod(self._a_v)
# Transform into (wvv) matrix where the dm_wvv[i, :, :] is a diagonal matrix for any i
dm_wvv = np.einsum('xn,xy->nxy', dm_vw, np.eye(3), optimize=True)
return dm_wvv # type: ignore
def _get_dynamic_polarizability_imaginary_frequency(
self, frequencies: Array) -> np.ndarray:
raise NotImplementedError()
def _set_semiaxes(self, val: Array) -> None:
self._a_v = np.asarray(val, dtype=float) * A_to_au
if self._a_v.shape in [(), (1,)]:
self._a_v = self._a_v * np.ones(3)
elif self._a_v.shape != (3,):
raise TypeError('semiaxes must be scalar or three-dimensional array, '
f'has shape {self._a_v.shape}')
if not np.all(self._a_v > 0):
raise ValueError('All components of semiaxes must be strictly positive')
def _calc_depolarization_factors(self, mstep: int = 100, mmax: int = 100) -> np.ndarray:
"""Calculates the depolarization factors.
See https://doi.org/10.1155/2007/45090 Eq. (6).
"""
ds = np.min(self.semiaxes**2) / float(mstep)
smax = np.max(self.semiaxes**2) * mmax
s_t = np.arange(0, smax, ds)
sa2_vt = s_t + self.semiaxes[:, np.newaxis]**2
y_vt = 1.0 / (sa2_vt * np.sqrt(np.prod(sa2_vt, axis=0)))
# Integrate
depolarization_factors = trapezoid(y_vt, dx=ds, axis=-1)
# Add remainder
depolarization_factors += 2.0 / 3.0 * s_t[-1]**(-3.0 / 2.0)
depolarization_factors *= np.prod(self.semiaxes) / 2.0
return depolarization_factors # type: ignore