Source code for sarkas.potentials.moliere

r"""
Module for handling Moliere Potential.

Potential
*********

The Moliere Potential is defined as

.. math::
    U(r) = \frac{q_i q_j}{4 \pi \epsilon_0} \frac{1}{r} \sum_{\alpha} C_{\alpha} e^{- b_{\alpha} r}.

For more details see :cite:`Wilson1977`. Note that the parameters :math:`b` are not normalized by the Bohr radius.
They should be passed with the correct units [m] if mks or [cm] if cgs.

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

The force error is calculated from the Yukawa's formula with the smallest screening length.

.. math::

    \Delta F = \frac{q^2}{4 \pi \epsilon_0} \sqrt{2 \pi n b_{\textrm min} }e^{-b_{\textrm min} r_c},

This overestimates it, but it doesn't matter.

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

.. code-block:: python

    pot_matrix[0] = q_iq_je^2/(4 pi eps_0) Force factor between two particles.
    pot_matrix[1] = C_1
    pot_matrix[2] = C_2
    pot_matrix[3] = C_3
    pot_matrix[4] = b_1
    pot_matrix[5] = b_2
    pot_matrix[6] = b_3

"""
from numba import jit
from numba.core.types import float64, UniTuple
from numpy import array, exp, pi, sqrt, zeros

from ..utilities.maths import force_error_analytic_lcl


[docs]def update_params(potential): """ Assign potential dependent simulation's parameters. Parameters ---------- potential : :class:`sarkas.potentials.core.Potential` Class handling potential form. """ potential.screening_lengths = array(potential.screening_lengths) potential.screening_charges = array(potential.screening_charges) params_len = len(potential.screening_lengths) potential.matrix = zeros((2 * params_len + 1, potential.num_species, potential.num_species)) for i, q1 in enumerate(potential.species_charges): for j, q2 in enumerate(potential.species_charges): potential.matrix[0, i, j] = q1 * q2 / potential.fourpie0 potential.matrix[1 : params_len + 1, i, j] = potential.screening_charges potential.matrix[params_len + 1 :, i, j] = 1.0 / potential.screening_lengths potential.force = moliere_force # Use Yukawa force error formula with the smallest screening length. # This overestimates the Force error, 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[params_len + 1 :, :, :], sqrt(3.0 * potential.a_ws / (4.0 * pi)) )
@jit(UniTuple(float64, 2)(float64, float64[:]), nopython=True) def moliere_force(r, pot_matrix): """ Numba'd function to calculate the PP force between particles using the Moliere Potential. Parameters ---------- r : float Particles' distance. pot_matrix : numpy.ndarray Moliere potential parameters. \n Shape = (7, :attr:`sarkas.core.Parameters.num_species`, :attr:`sarkas.core.Parameters.num_species`) Returns ------- U : float Potential. force : float Force between two particles. Examples -------- >>> from scipy.constants import epsilon_0, pi, elementary_charge >>> from numpy import array, zeros >>> charge = 4.0 * elementary_charge # = 4e [C] mks units >>> coul_const = 1.0/ (4.0 * pi * epsilon_0) >>> screening_charges = array([0.5, -0.5, 1.0]) >>> screening_lengths = array([5.99988000e-11, 1.47732309e-11, 1.47732309e-11]) # [m] >>> params_len = len(screening_lengths) >>> pot_mat = zeros(2 * params_len + 1) >>> pot_mat[0] = coul_const * charge**2 >>> pot_mat[1: params_len + 1] = screening_charges.copy() >>> pot_mat[params_len + 1:] = 1./screening_lengths >>> r = 6.629755e-10 # [m] particles distance >>> moliere_force(r, pot_mat) (4.423663010052846e-23, 6.672438139145769e-14) """ U = 0.0 force = 0.0 for i in range(int(len(pot_matrix[:-1]) / 2)): factor1 = r * pot_matrix[i + 4] factor2 = pot_matrix[i + 1] / r U += factor2 * exp(-factor1) force += exp(-factor1) * factor2 * (1.0 / r + pot_matrix[i + 1]) force *= pot_matrix[0] U *= pot_matrix[0] return U, force