use crate::{pairwise::ShortRangeFunction, DebyeLength};
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(Serialize, Deserialize),
serde(deny_unknown_fields)
)]
pub struct Plain {
cutoff: f64,
#[cfg_attr(
feature = "serde",
serde(
rename = "debye",
alias = "debye_length",
alias = "debyelength",
serialize_with = "serialize_reciprocal",
deserialize_with = "deserialize_reciprocal",
default
)
)]
kappa: Option<f64>,
}
#[cfg(feature = "serde")]
fn serialize_reciprocal<S>(x: &Option<f64>, s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match x.as_ref() {
Some(&value) => s.serialize_some(&f64::recip(value)),
None => s.serialize_none(),
}
}
#[cfg(feature = "serde")]
fn deserialize_reciprocal<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
where
D: Deserializer<'de>,
{
Ok(Option::deserialize(deserializer)?.map(f64::recip))
}
impl Default for Plain {
fn default() -> Self {
Self {
cutoff: f64::INFINITY,
kappa: None,
}
}
}
impl core::fmt::Display for Plain {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Plain Coulomb: 𝑟✂ = {:.1}", self.cutoff)?;
if let Some(debye_length) = self.kappa.map(f64::recip) {
write!(
f,
", λᴰ = {:.1}, λᴰ/𝑟✂ = {:.1e}",
debye_length,
debye_length / self.cutoff
)?;
}
write!(f, " <{}>", Self::url())?;
Ok(())
}
}
impl Plain {
pub fn without_cutoff() -> Self {
Self::new(f64::INFINITY, None)
}
pub fn new(cutoff: f64, debye_length: Option<f64>) -> Self {
Self {
cutoff,
kappa: debye_length.map(f64::recip),
}
}
pub fn new_without_salt(cutoff: f64) -> Self {
Self::new(cutoff, None)
}
pub fn from_medium(cutoff: f64, medium: &crate::Medium) -> Self {
Self::new(cutoff, medium.debye_length())
}
}
impl crate::Cutoff for Plain {
#[inline]
fn cutoff(&self) -> f64 {
self.cutoff
}
}
impl DebyeLength for Plain {
#[inline]
fn kappa(&self) -> Option<f64> {
self.kappa
}
fn set_debye_length(&mut self, debye_length: Option<f64>) -> crate::Result<()> {
self.kappa = debye_length.map(f64::recip);
Ok(())
}
}
impl ShortRangeFunction for Plain {
fn url() -> &'static str {
"https://doi.org/msxd"
}
#[inline]
fn short_range_f0(&self, _q: f64) -> f64 {
1.0
}
#[inline]
fn short_range_f1(&self, _q: f64) -> f64 {
0.0
}
#[inline]
fn short_range_f2(&self, _q: f64) -> f64 {
0.0
}
#[inline]
fn short_range_f3(&self, _q: f64) -> f64 {
0.0
}
}
#[test]
fn test_coulomb() {
use super::test_utils::{assert_vec3_eq, assert_vec_x_equals_norm, assert_vec_zero};
use crate::{NalgebraMatrix3 as Matrix3, NalgebraVector3 as Vector3};
use approx::assert_relative_eq;
use crate::pairwise::{MultipoleEnergy, MultipoleField, MultipoleForce, MultipolePotential};
let cutoff: f64 = 29.0;
let z1 = 2.0;
let z2 = 3.0;
let mu1 = Vector3::new(19.0, 7.0, 11.0);
let mu2 = Vector3::new(13.0, 17.0, 5.0);
let quad1 = Matrix3::new(3.0, 7.0, 8.0, 5.0, 9.0, 6.0, 2.0, 1.0, 4.0);
let _quad2 = Matrix3::zeros();
let r = Vector3::new(23.0, 0.0, 0.0);
let rq = Vector3::new(
5.75 * (6.0f64).sqrt(),
5.75 * (2.0f64).sqrt(),
11.5 * (2.0f64).sqrt(),
);
let rh = Vector3::new(1.0, 0.0, 0.0);
let pot = Plain::new(cutoff, None);
let eps = 1e-9;
assert_eq!(
pot.to_string(),
"Plain Coulomb: 𝑟✂ = 29.0 <https://doi.org/msxd>"
);
assert_eq!(pot.short_range_f0(0.5), 1.0);
assert_eq!(pot.short_range_f1(0.5), 0.0);
assert_eq!(pot.short_range_f2(0.5), 0.0);
assert_eq!(pot.short_range_f3(0.5), 0.0);
assert_eq!(pot.ion_potential(z1, cutoff + 1.0), 0.0);
assert_relative_eq!(
pot.ion_potential(z1, r.norm()),
0.08695652173913043,
epsilon = eps
);
assert_relative_eq!(
pot.dipole_potential(mu1, (cutoff + 1.0) * rh),
0.0,
epsilon = eps
);
assert_relative_eq!(
pot.dipole_potential(mu1, r),
0.035916824196597356,
epsilon = eps
);
assert_relative_eq!(
pot.quadrupole_potential(quad1, rq),
0.00093632817,
epsilon = eps
);
assert_vec_zero!(pot.ion_field(z1, (cutoff + 1.0) * rh), eps);
assert_vec_x_equals_norm!(pot.ion_field(z1, r), 0.003780718336, eps);
assert_vec_zero!(pot.dipole_field(mu1, (cutoff + 1.0) * rh), eps);
assert_vec3_eq!(
pot.dipole_field(mu1, r),
[0.003123202104, -0.0005753267034, -0.0009040848196],
eps
);
assert_vec3_eq!(
pot.quadrupole_field(quad1, r),
[-0.00003752130674, -0.00006432224013, -0.00005360186677],
eps
);
assert_relative_eq!(pot.ion_ion_energy(z1, z2, cutoff + 1.0), 0.0, epsilon = eps);
assert_relative_eq!(
pot.ion_ion_energy(z1, z2, r.norm()),
z1 * z2 / r.norm(),
epsilon = eps
);
assert_relative_eq!(
pot.ion_dipole_energy(z1, mu2, (cutoff + 1.0) * rh),
-0.0,
epsilon = eps
);
assert_relative_eq!(
pot.ion_dipole_energy(z1, mu2, r),
-0.04914933837,
epsilon = eps
);
assert_relative_eq!(
pot.dipole_dipole_energy(mu1, mu2, (cutoff + 1.0) * rh),
-0.0,
epsilon = eps
);
assert_relative_eq!(
pot.dipole_dipole_energy(mu1, mu2, r),
-0.02630064930,
epsilon = eps
);
assert_relative_eq!(
pot.ion_quadrupole_energy(z2, quad1, rq),
0.002808984511,
epsilon = eps
);
assert_vec_zero!(pot.ion_ion_force(z1, z2, (cutoff + 1.0) * rh), eps);
assert_vec_x_equals_norm!(pot.ion_ion_force(z1, z2, r), 0.01134215501, eps);
assert_vec_zero!(pot.ion_dipole_force(z2, mu1, (cutoff + 1.0) * rh), eps);
assert_vec3_eq!(
pot.ion_dipole_force(z2, mu1, r),
[0.009369606312, -0.001725980110, -0.002712254459],
eps
);
assert_vec_zero!(pot.dipole_dipole_force(mu1, mu2, (cutoff + 1.0) * rh), eps);
assert_vec3_eq!(
pot.dipole_dipole_force(mu1, mu2, r),
[0.003430519474, -0.004438234569, -0.002551448858],
eps
);
let pot = Plain::new(cutoff, Some(23.0));
assert_eq!(
pot.to_string(),
"Plain Coulomb: 𝑟✂ = 29.0, λᴰ = 23.0, λᴰ/𝑟✂ = 7.9e-1 <https://doi.org/msxd>"
);
assert_relative_eq!(pot.ion_potential(z1, cutoff + 1.0), 0.0, epsilon = eps);
assert_relative_eq!(
pot.ion_potential(z1, r.norm()),
0.03198951663,
epsilon = eps
);
assert_relative_eq!(
pot.dipole_potential(mu1, (cutoff + 1.0) * rh),
0.0,
epsilon = eps
);
assert_relative_eq!(pot.dipole_potential(mu1, r), 0.02642612243, epsilon = eps);
assert_vec_zero!(pot.ion_field(z1, (cutoff + 1.0) * rh), eps);
assert_vec_x_equals_norm!(pot.ion_field(z1, r), 0.002781697098, eps);
assert_vec_zero!(pot.dipole_field(mu1, (cutoff + 1.0) * rh), eps);
let field_scalar = pot.ion_field_scalar(z1, r.norm());
let field: Vector3 = pot.ion_field(z1, r).into();
assert_relative_eq!(field_scalar, field.norm(), epsilon = eps);
assert_vec3_eq!(
pot.dipole_field(mu1, r),
[0.002872404612, -0.0004233017324, -0.0006651884364],
eps
);
let pot = Plain::new(cutoff, None);
let alpha = 50.0;
let charge = 1.0;
let r_vec = Vector3::new(5.0, 0.0, 0.0);
let energy = pot.ion_induced_dipole_energy(charge, alpha, r_vec);
assert_relative_eq!(energy, -0.04, epsilon = eps);
let r4 = r_vec.norm_squared().powi(2);
assert_relative_eq!(energy, -0.5 * charge * charge * alpha / r4, epsilon = eps);
let pot = Plain::new(cutoff, Some(10.0));
let energy = pot.ion_induced_dipole_energy(charge, alpha, r_vec);
assert_relative_eq!(energy, -0.0331091497054298, epsilon = eps);
}
#[cfg(feature = "uom")]
#[test]
fn test_plain_si() {
use crate::{
pairwise::{MultipoleEnergySI, MultipoleFieldSI, MultipolePotentialSI},
units::*,
};
use approx::assert_relative_eq;
let eps = 1e-9; let pot = Plain::without_cutoff();
let z1 = ElectricCharge::new::<elementary_charge>(2.0);
let z2 = ElectricCharge::new::<elementary_charge>(3.0);
let r = Length::new::<nanometer>(2.3);
let energy = pot.ion_ion_energy(z1, z2, r);
assert_relative_eq!(
energy.get::<kilojoule_per_mole>(),
362.4403242896922,
epsilon = eps
);
let potential = pot.ion_potential(z1, r);
assert_relative_eq!(potential.get::<volt>(), 1.2521430850804929, epsilon = eps);
let field = pot.ion_field(z1, r);
assert_relative_eq!(
field.get::<volt_per_micrometer>(),
544.4100369915187,
epsilon = eps
);
}