Source code for sarkas.potentials.egs

r"""
Module for handling Exact Gradient corrected Screened (EGS) Potential.

Potential
*********

The exact-gradient screened (EGS) potential introduces new parameters that can be easily calculated from initial inputs.
Density gradient corrections to the free energy functional lead to the first parameter, :math:`\nu`,

.. math::
   \nu = - \frac{3\lambda}{\pi^{3/2}}  \frac{4\pi \bar{e}^2 \beta }{\Lambda_{e}} \frac{d}{d\eta} \mathcal I_{-1/2}(\eta),

where :math:`\lambda` is a correction factor; :math:`\lambda = 1/9` for the true gradient corrected Thomas-Fermi model
and :math:`\lambda = 1` for the traditional von Weissaecker model, :math:`\mathcal I_{-1/2}[\eta_0]` is the
Fermi Integral of order :math:`-1/2`, and :math:`\Lambda_e` is the de Broglie wavelength of the electrons.

In the case :math:`\nu < 1` the EGS potential takes the form

.. math::
   U_{ab}(r) = \frac{Z_a Z_b \bar{e}^2 }{2r}\left [ ( 1+ \alpha ) e^{-r/\lambda_-} + ( 1 - \alpha) e^{-r/\lambda_+} \right ],

with

.. math::
   \lambda_\pm^2 = \frac{\nu \lambda_{\textrm{TF}}^2}{2b \pm 2b\sqrt{1 - \nu}}, \quad \alpha = \frac{b}{\sqrt{b - \nu}},

where the parameter :math:`b` arises from exchange-correlation contributions, see below.\n
On the other hand :math:`\nu > 1`, the pair potential has the form

.. math::
   U_{ab}(r) = \frac{Z_a Z_b \bar{e}^2}{r}\left [ \cos(r/\gamma_-) + \alpha' \sin(r/\gamma_-) \right ] e^{-r/\gamma_+}

with

.. math::
   \gamma_\pm^2 = \frac{\nu\lambda_{\textrm{TF}}^2}{\sqrt{\nu} \pm b}, \quad \alpha' = \frac{b}{\sqrt{\nu - b}}.

Neglect of exchange-correlational effects leads to :math:`b = 1` otherwise

.. math::
   b = 1 - \frac{2}{8} \frac{1}{k_{\textrm{F}}^2 \lambda_{\textrm{TF}}^2 }  \left [ h\left ( \Theta \right ) - 2 \Theta h'(\Theta) \right ]

where :math:`k_{\textrm{F}}` is the Fermi wavenumber and :math:`\Theta = (\beta E_{\textrm{F}})^{-1}` is the electron
degeneracy parameter` calculated from the Fermi energy.

.. math::
   h \left ( \Theta \right) = \frac{N(\Theta)}{D(\Theta)}\tanh \left( \Theta^{-1} \right ),

.. math::
   N(\Theta) = 1 + 2.8343\Theta^2 - 0.2151\Theta^3 + 5.2759\Theta^4,

.. math::
   D \left ( \Theta \right ) = 1 + 3.9431\Theta^2 + 7.9138\Theta^4.

Force Error
***********

The EGS potential is always smaller than pure Yukawa. Therefore the force error is chosen to be the same as Yukawa's

.. math::

    \Delta F = \frac{q^2}{4 \pi \epsilon_0} \sqrt{\frac{2 \pi n}{\lambda_{-}}}e^{-r_c/\lambda_-}

This overestimates it, but it doesn't matter.

Potential Attributes
********************

The elements of :attr:`sarkas.potentials.core.Potential.pot_matrix` are

if :attr:`sarkas.core.Parameters.nu` less than 1:

.. code-block::

    pot_matrix[0] = q_iq_j/4pi eps0
    pot_matrix[1] = nu
    pot_matrix[2] = 1 + alpha
    pot_matrix[3] = 1 - alpha
    pot_matrix[4] = 1.0 / lambda_minus
    pot_matrix[5] = 1.0 / lambda_plus

else

.. code-block::

    pot_matrix[0] = q_iq_j/4pi eps0
    pot_matrix[1] = nu
    pot_matrix[2] = 1.0
    pot_matrix[3] = alpha prime
    pot_matrix[4] = 1.0 / gamma_minus
    pot_matrix[5] = 1.0 / gamma_plus

"""
from numba import jit
from numba.core.types import float64, UniTuple
from numpy import cos, cosh, exp, pi, sin, sqrt, tanh, zeros

from ..utilities.exceptions import AlgorithmError
from ..utilities.fdints import fdm3h
from ..utilities.maths import force_error_analytic_lcl


[docs]def update_params(potential, params): """ Assign potential dependent simulation's parameters. Parameters ---------- potential : :class:`sarkas.potentials.core.Potential` Class handling potential form. params : :class:`sarkas.core.Parameters` Simulation's parameters. Raises ------ `~sarkas.utilities.exceptions.AlgorithmError` If the chosen algorithm is pppm. """ # lambda factor : 1 = von Weizsaecker, 1/9 = Thomas-Fermi if not hasattr(potential, "lmbda"): potential.lmbda = 1.0 / 9.0 # eq. (14) of Ref. [1]_ potential.nu = 3.0 / pi**1.5 * potential.electron_landau_length / potential.electron_deBroglie_wavelength dIdeta = -3.0 / 2.0 * fdm3h(potential.electron_dimensionless_chemical_potential) potential.nu *= potential.lmbda * dIdeta # Degeneracy Parameter theta = potential.electron_degeneracy_parameter if 0.1 <= theta <= 12: # Regime of validity of the following approximation Perrot et al. Phys Rev A 302619 (1984) # eq. (33) of Ref. [1]_ Ntheta = 1.0 + 2.8343 * theta**2 - 0.2151 * theta**3 + 5.2759 * theta**4 # eq. (34) of Ref. [1]_ Dtheta = 1.0 + 3.9431 * theta**2 + 7.9138 * theta**4 # eq. (32) of Ref. [1]_ h = Ntheta / Dtheta * tanh(1.0 / theta) # grad h(x) gradh = -(Ntheta / Dtheta) / cosh(1 / theta) ** 2 / (theta**2) - tanh( # derivative of tanh(1/x) 1.0 / theta ) * ( Ntheta * (7.8862 * theta + 31.6552 * theta**3) / Dtheta**2 # derivative of 1/Dtheta + (5.6686 * theta - 0.6453 * theta**2 + 21.1036 * theta**3) / Dtheta ) # derivative of Ntheta # eq.(31) of Ref. [1]_ b = 1.0 - 2.0 / (8.0 * (potential.electron_Fermi_wavenumber * potential.electron_TF_wavelength) ** 2) * ( h - 2.0 * theta * gradh ) else: b = 1.0 potential.b = b # Monotonic decay if potential.nu <= 1: # eq. (29) of Ref. [1]_ potential.lambda_p = potential.electron_TF_wavelength * sqrt( potential.nu / (2.0 * b + 2.0 * sqrt(b**2 - potential.nu)) ) potential.lambda_m = potential.electron_TF_wavelength * sqrt( potential.nu / (2.0 * b - 2.0 * sqrt(b**2 - potential.nu)) ) potential.alpha = b / sqrt(b - potential.nu) # Oscillatory behavior if potential.nu > 1: # eq. (29) of Ref. [1]_ potential.gamma_m = potential.electron_TF_wavelength * sqrt(potential.nu / (sqrt(potential.nu) - b)) potential.gamma_p = potential.electron_TF_wavelength * sqrt(potential.nu / (sqrt(potential.nu) + b)) potential.alphap = b / sqrt(potential.nu - b) potential.matrix = zeros((7, potential.num_species, potential.num_species)) potential.matrix[1, :, :] = potential.nu for i, q1 in enumerate(potential.species_charges): for j, q2 in enumerate(potential.species_charges): if potential.nu <= 1: potential.matrix[0, i, j] = q1 * q2 / (2.0 * potential.fourpie0) potential.matrix[2, i, j] = 1.0 + potential.alpha potential.matrix[3, i, j] = 1.0 - potential.alpha potential.matrix[4, i, j] = 1.0 / potential.lambda_m potential.matrix[5, i, j] = 1.0 / potential.lambda_p if potential.nu > 1: potential.matrix[0, i, j] = q1 * q2 / potential.fourpie0 potential.matrix[2, i, j] = 1.0 potential.matrix[3, i, j] = potential.alphap potential.matrix[4, i, j] = 1.0 / potential.gamma_m potential.matrix[5, i, j] = 1.0 / potential.gamma_p potential.matrix[6, :, :] = potential.a_rs if potential.method == "pppm": raise AlgorithmError("pppm algorithm not implemented yet.") potential.force = egs_force # EGS is always smaller than pure Yukawa. # Therefore the force error is chosen to be the same as Yukawa's. # This overestimates it, but it doesn't matter. # The rescaling constant is sqrt ( na^4 ) = sqrt( 3 a/(4pi) ) potential.force_error = force_error_analytic_lcl( potential.type, potential.rc, potential.matrix, sqrt(3.0 * potential.a_ws / (4.0 * pi)) )
@jit(UniTuple(float64, 2)(float64, float64[:]), nopython=True) def egs_force(r_in, pot_matrix): """ Numba'd function to calculate the potential and force between particles using the EGS Potential. Parameters ---------- r_in : float Particles' distance. pot_matrix : array EGS potential parameters. \n Shape = (6, :attr:`sarkas.core.Parameters.num_species`, :attr:`sarkas.core.Parameters.num_species`) Returns ------- U : float Potential. fr : float Force. Examples -------- >>> from numpy import array, pi >>> from scipy.constants import epsilon_0 >>> r = 2.0 >>> alpha = 1.3616 >>> lambda_p = 1.778757e-09 >>> lambda_m = 4.546000e-09 >>> charge = 1.440961e-09 >>> c_const = charge**2/( 4.0 * pi * epsilon_0) >>> pot_mat = array([c_const * 0.5, 1.0 + alpha, 1.0 - alpha, 1.0/lambda_m, 1.0 / lambda_p, 1.0e-14]) >>> egs_force(r, pot_mat) (-0.9067719924627385, 270184640.33105946) """ rs = pot_matrix[6] # Branchless programming r = r_in * (r_in >= rs) + rs * (r_in < rs) # nu = pot_matrix[1] if pot_matrix[1] <= 1.0: temp1 = pot_matrix[2] * exp(-r * pot_matrix[4]) temp2 = pot_matrix[3] * exp(-r * pot_matrix[5]) # Potential U = (temp1 + temp2) * pot_matrix[0] / r # Force fr = U / r + pot_matrix[0] * (temp1 * pot_matrix[4] + temp2 * pot_matrix[5]) / r else: coskr = cos(r * pot_matrix[4]) sinkr = sin(r * pot_matrix[4]) expkr = pot_matrix[0] * exp(-r * pot_matrix[5]) U = (coskr + pot_matrix[3] * sinkr) * expkr / r fr = U / r # derivative of 1/r fr += U * pot_matrix[5] # derivative of exp fr += pot_matrix[4] * (sinkr - pot_matrix[3] * coskr) * expkr / r return U, fr
[docs]def pretty_print_info(potential): """ Print potential specific parameters in a user-friendly way. Parameters ---------- potential : :class:`sarkas.potentials.core.Potential` Class handling potential form. """ # print('electron temperature = {:1.4e} [K] = {:1.4e} eV'.format( # potential.electron_temperature, # potential.electron_temperature / potential.eV2K)) print(f"kappa = {potential.a_ws / potential.screening_length:.4f}") print(f"SGA Correction factor: lmbda = {potential.lmbda:.4f}") # print('lambda_TF = {:1.4e} '.format(potential.electron_TF_wavelength), end='') # print("[cm]" if potential.units == "cgs" else "[m]") print(f"nu = {potential.nu:.4f}") if potential.nu < 1: print("Exponential decay:") print(f"lambda_p = {potential.lambda_p:.6e} ", end="") print("[cm]" if potential.units == "cgs" else "[m]") print(f"lambda_m = {potential.lambda_m:.6e} ", end="") print("[cm]" if potential.units == "cgs" else "[m]") print(f"alpha = {potential.alpha:.4f}") # print('Theta = {:1.4e}'.format(potential.electron_degeneracy_parameter)) print(f"b = {potential.b:.4f}") else: print("Oscillatory potential:") print(f"gamma_p = {potential.gamma_p:.6e} ", end="") print("[cm]" if potential.units == "cgs" else "[m]") print(f"gamma_m = {potential.gamma_m:.6e} ", end="") print("[cm]" if potential.units == "cgs" else "[m]") print(f"alpha = {potential.alphap:.4f}") print(f"b = {potential.b:.4f}") print(f"Gamma_eff = {potential.coupling_constant:.2f}")