# -*- coding: utf-8 -*
r"""
The :class:`SmoothFunction` class and its methods are defined hereafter.
It is used as the base class of two more specific smooth functions:
* the :class:`TanhSmoothFunction` class,
* and the :class:`ErfSmoothFunction` class.
"""
from abc import abstractmethod, ABCMeta
import numpy as np
from scipy.special import erf
[docs]class SmoothFunction(metaclass=ABCMeta):
r"""
.. note:: This is an abstract class.
Smooth functions are used when a smooth complex scaling is applied
to the potential. The aim of this class is to easily update the
values of the smooth functions (and its derivatives) when the
Refection-Free Complex Absorbing Potentials and the Virial operator
are defined.
"""
PARAMETERS = ["x0", "lbda"]
def __init__(self, x0, lbda, c0=1, cp=1, cm=-1, grid=None):
r"""
The smooth functions :math:`q` used here are of the form:
.. math::
q(x) = c_0 + c_+ r(\lambda (x - x_0)) - c_- r(\lambda (x + x_0))
The smooth function should be 0 on a large part of the
:math:`[-x_0, x_0]` range of the grid, while tending to 1 at
both infinities.
The initialization of a smooth function requires the value of
:math:`x_0` and of the parameter :math:`\lambda`, indicating
how smoothly the function goes from 0 to 1 (the larger, the
sharper).
Parameters
----------
x0: float
Inflection point.
lbda: float
Sharpness parameter (the function is smoother for
smaller values).
c0: float
Constant term :math:`c_0` of the smooth function defintion
(default to 1).
cp: float
Constant term :math:`c_+` of the smooth function defintion
(default to 1).
cm: float
Constant term :math:`c_-` of the smooth function defintion
(default to -1).
numpy array
Discretization grid of the smooth function (optional).
Raises
------
ValueError
If ``x0`` or ``lbda`` are not strictly positive.
"""
# Check the value of x0 and lbda before initializing the
# corresponding attributes.
if x0 <= 0:
raise ValueError("The inflection point must be positive.")
if lbda <= 0:
raise ValueError("The sharpness parameter must be positive.")
self._x0 = x0
self._lbda = lbda
# Set initial values for the smooth function
self._c0 = c0
self._cp = cp
self._cm = cm
# Use the grid setter
if grid is not None:
self.grid = np.array(grid)
else:
self.grid = grid
@property
def x0(self):
r"""
Returns
-------
float
Inflection point.
"""
return self._x0
@property
def lbda(self):
r"""
Returns
-------
float
Sharpness parameter.
"""
return self._lbda
@property
def grid(self):
r"""
Returns
-------
numpy array
Discretization grid of the smooth function.
"""
return self._grid
@grid.setter
def grid(self, new_grid):
r"""
Setter of the :attr:`grid` attribute.
Parameters
----------
new_grid: numpy array
New discretization grid.
"""
if self._to_be_updated(new_grid):
self._grid = new_grid
self._update_all_values()
[docs] def _to_be_updated(self, new_grid):
r"""
Returns
-------
bool
``True`` if the grid has to be updated.
"""
return not (hasattr(self, "grid") and np.array_equal(self.grid, new_grid))
[docs] def _update_all_values(self):
r"""
Update the values of the test functions and all of its
derivatives.
"""
grid = self.grid
lbda = self.lbda
if grid is not None:
cp = self._cp
cm = self._cm
grid_p = lbda * (grid - self.x0)
grid_m = lbda * (grid + self.x0)
r_p = self._get_r_values(grid_p)
r_m = self._get_r_values(grid_m)
r_dx_p = self._get_r_dx_values(grid_p)
r_dx_m = self._get_r_dx_values(grid_m)
r_dx2_p = self._get_r_dx2_values(grid_p)
r_dx2_m = self._get_r_dx2_values(grid_m)
r_dx3_p = self._get_r_dx3_values(grid_p)
r_dx3_m = self._get_r_dx3_values(grid_m)
self._values = self._c0 + (cp * r_p + cm * r_m) / 2
self._dx_values = lbda * (cp * r_dx_p + cm * r_dx_m) / 2
self._dx2_values = lbda ** 2 * (cp * r_dx2_p + cm * r_dx2_m) / 2
self._dx3_values = lbda ** 3 * (cp * r_dx3_p + cm * r_dx3_m) / 2
self._dxi_values = {}
self._dx_dxi_values = {}
self._dxi_values["x0"] = -lbda * (cp * r_dx_p - cm * r_dx_m) / 2
self._dx_dxi_values["x0"] = -lbda ** 2 * (cp * r_dx2_p - cm * r_dx2_m) / 2
self._dxi_values["lbda"] = (cp * grid_p * r_dx_p + cm * grid_m * r_dx_m) / (
2 * lbda
)
self._dx_dxi_values["lbda"] = (
cp * (grid_p * r_dx2_p + r_dx_p) + cm * (grid_m * r_dx2_m + r_dx_m)
) / 2
else:
self._values = None
self._dx_values = None
self._dx2_values = None
self._dx3_values = None
self._dxi_values = {param: None for param in self.PARAMETERS}
self._dx_dxi_values = {param: None for param in self.PARAMETERS}
@property
def values(self):
r"""
Returns
-------
numpy array
Values of the smooth function.
"""
return self._values
@property
def dx_values(self):
r"""
Returns
-------
numpy array
Values of the first derivative of the smooth function.
"""
return self._dx_values
@property
def dx2_values(self):
r"""
Returns
-------
numpy array
Values of the second derivative of the smooth
function.
"""
return self._dx2_values
@property
def dx3_values(self):
r"""
Returns
-------
numpy array
Values of the third derivative of the smooth function.
"""
return self._dx3_values
@property
def dxi_values(self):
r"""
Returns
-------
numpy array
Values of the first derivative of the smooth function
with respect to both parameters :math:`x_0` and
:math:`\lambda`.
"""
return self._dxi_values
@property
def dx_dxi_values(self):
r"""
Returns
-------
numpy array
Values of the first derivative with respect to both
parameters :math:`x_0` and :math:`\lambda` of the first
derivative of the smooth function.
"""
return self._dx_dxi_values
[docs] @abstractmethod
def _get_r_values(self, grid): # pragma: no cover
r"""
.. note:: This is an asbtract method.
Parameters
----------
grid: numpy array
Discretization grid.
Returns
-------
numpy array
Values of the function :math:`r`, evaluated on a
given set of grid points.
"""
pass
[docs] @abstractmethod
def _get_r_dx_values(self, grid): # pragma: no cover
r"""
.. note:: This is an asbtract method.
Parameters
----------
grid: numpy array
Discretization grid.
Returns
-------
Values of first derivative of the function :math:`r`,
evaluated on a given set of grid points.
"""
pass
[docs] @abstractmethod
def _get_r_dx2_values(self, grid): # pragma: no cover
r"""
.. note:: This is an asbtract method.
Parameters
----------
grid: numpy array
Discretization grid.
Returns
-------
Values of second derivative of the function :math:`r`,
evaluated on a given set of grid points.
"""
pass
[docs] @abstractmethod
def _get_r_dx3_values(self, grid): # pragma: no cover
r"""
.. note:: This is an asbtract method.
Parameters
----------
grid: numpy array
Discretization grid.
Returns
-------
Values of third derivative of the function :math:`r`,
evaluated on a given set of grid points.
"""
pass
[docs]class ErfSmoothFunction(SmoothFunction):
r"""
In this case, the function :math:`r` corresponds to the error
function :math:`\text{erf}`.
"""
[docs] def _get_r_values(self, grid):
r"""
Parameters
----------
grid: numpy array
Discretization grid.
Returns
-------
Values of the function :math:`\text{erf}`, evaluated on a
given set of grid points.
"""
return erf(grid)
[docs] def _get_r_dx_values(self, grid):
r"""
Parameters
----------
grid: numpy array
Discretization grid.
Returns
-------
Values of first derivative of the function
:math:`\text{erf}`, evaluated on a given set of grid points.
"""
return 2 / np.sqrt(np.pi) * np.exp(-grid ** 2)
[docs] def _get_r_dx2_values(self, grid):
r"""
Parameters
----------
grid: numpy array
Discretization grid.
Returns
-------
Values of second derivative of the function
:math:`\text{erf}`, evaluated on a given set of grid points.
"""
return -2 * grid * self._get_r_dx_values(grid)
[docs] def _get_r_dx3_values(self, grid):
r"""
Parameters
----------
grid: numpy array
Discretization grid.
Returns
-------
Values of third derivative of the function
:math:`\text{erf}`, evaluated on a given set of grid points.
"""
return -2 * (self._get_r_dx_values(grid) + grid * self._get_r_dx2_values(grid))
[docs]class TanhSmoothFunction(SmoothFunction):
r"""
In this case, the function :math:`r` corresponds to the hyperbolic
tangent :math:`\tanh`.
"""
[docs] def _get_r_values(self, grid):
r"""
Parameters
----------
grid: numpy array
Discretization grid.
Returns
-------
Values of the function :math:`\tanh`, evaluated on a
given set of grid points.
"""
return np.tanh(grid)
[docs] def _get_r_dx_values(self, grid):
r"""
Parameters
----------
grid: numpy array
Discretization grid.
Returns
-------
Values of first derivative of the function :math:`\tanh`,
evaluated on a given set of grid points.
"""
return 1 / np.cosh(grid) ** 2
[docs] def _get_r_dx2_values(self, grid):
r"""
Parameters
----------
grid: numpy array
Discretization grid.
Returns
-------
Values of second derivative of the function :math:`\tanh`,
evaluated on a given set of grid points.
"""
return -2 * np.tanh(grid) / np.cosh(grid) ** 2
[docs] def _get_r_dx3_values(self, grid):
r"""
Parameters
----------
grid: numpy array
Discretization grid.
Returns
-------
Values of third derivative of the function :math:`\tanh`,
evaluated on a given set of grid points.
"""
r = self._get_r_values(grid)
r_dx = self._get_r_dx_values(grid)
r_dx2 = self._get_r_dx2_values(grid)
return -2 * (r_dx ** 2 + r * r_dx2)