#[cfg(test)]
use crate::debye_length::DebyeLength;
use crate::math::erfc_x;
use crate::pairwise::{SelfEnergyPrefactors, ShortRangeFunction};
#[cfg(test)]
use approx::assert_relative_eq;
#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize};
#[derive(Debug, Clone, PartialEq)]
pub struct RealSpaceEwald {
cutoff: f64,
debye_length: Option<f64>,
eta: f64,
zeta: Option<f64>,
}
#[cfg(feature = "serde")]
impl Serialize for RealSpaceEwald {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
use serde::ser::SerializeStruct;
let mut s = serializer.serialize_struct("RealSpaceEwald", 3)?;
s.serialize_field("cutoff", &self.cutoff)?;
s.serialize_field("alpha", &self.alpha())?;
s.serialize_field("debye_length", &self.debye_length)?;
s.end()
}
}
#[cfg(feature = "serde")]
impl<'de> Deserialize<'de> for RealSpaceEwald {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(deny_unknown_fields)]
struct RealSpaceEwaldData {
cutoff: f64,
accuracy: f64,
debye_length: Option<f64>,
}
let RealSpaceEwaldData {
cutoff,
accuracy,
debye_length,
} = RealSpaceEwaldData::deserialize(deserializer)?;
Ok(RealSpaceEwald::new(cutoff, accuracy, debye_length))
}
}
impl core::fmt::Display for RealSpaceEwald {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"Real-space Ewald: πβ = {:.1}, π = {:.1}",
self.cutoff, self.eta,
)?;
if let Some(zeta) = self.zeta {
write!(f, ", π» = {:.1}", zeta)?;
}
write!(f, " <{}>", Self::url())?;
Ok(())
}
}
impl RealSpaceEwald {
const SQRT_PI: f64 = 1.7724538509055159;
pub fn new(cutoff: f64, accuracy: f64, debye_length: Option<f64>) -> Self {
let kappa = debye_length.map_or(0.0, |d| 1.0 / d);
let alpha = crate::reciprocal::parameters::ewald_alpha(cutoff, accuracy, kappa);
Self::from_alpha(cutoff, alpha, debye_length)
}
pub fn from_alpha(cutoff: f64, alpha: f64, debye_length: Option<f64>) -> Self {
Self {
cutoff,
debye_length,
eta: alpha * cutoff,
zeta: debye_length.map(|d| cutoff / d),
}
}
pub fn alpha(&self) -> f64 {
self.eta / self.cutoff
}
}
impl crate::Cutoff for RealSpaceEwald {
#[inline]
fn cutoff(&self) -> f64 {
self.cutoff
}
}
impl crate::DebyeLength for RealSpaceEwald {
#[inline]
fn kappa(&self) -> Option<f64> {
self.zeta.map(|z| z / self.cutoff)
}
fn set_debye_length(&mut self, debye_length: Option<f64>) -> crate::Result<()> {
self.zeta = debye_length.map(|d| self.cutoff / d);
Ok(())
}
}
impl ShortRangeFunction for RealSpaceEwald {
fn url() -> &'static str {
"https://doi.org/fcjts8"
}
#[inline]
fn short_range_f0(&self, q: f64) -> f64 {
if let Some(zeta) = self.zeta {
0.5 * (erfc_x(self.eta * q + zeta / (2.0 * self.eta)) * f64::exp(2.0 * zeta * q)
+ erfc_x(self.eta * q - zeta / (2.0 * self.eta)))
} else {
erfc_x(self.eta * q)
}
}
fn short_range_f1(&self, q: f64) -> f64 {
if let Some(zeta) = self.zeta {
let exp_c = f64::exp(-(self.eta * q - zeta / (2.0 * self.eta)).powi(2));
let erfc_c = erfc_x(self.eta * q + zeta / (2.0 * self.eta));
-2.0 * self.eta / Self::SQRT_PI * exp_c + zeta * erfc_c * f64::exp(2.0 * zeta * q)
} else {
-2.0 * self.eta / Self::SQRT_PI * f64::exp(-self.eta.powi(2) * q.powi(2))
}
}
fn short_range_f2(&self, q: f64) -> f64 {
if let Some(zeta) = self.zeta {
let exp_c = f64::exp(-(self.eta * q - zeta / (2.0 * self.eta)).powi(2));
let erfc_c = erfc_x(self.eta * q + zeta / (2.0 * self.eta));
4.0 * self.eta.powi(2) / Self::SQRT_PI * (self.eta * q - zeta / self.eta) * exp_c
+ 2.0 * zeta.powi(2) * erfc_c * f64::exp(2.0 * zeta * q)
} else {
4.0 * self.eta.powi(2) / Self::SQRT_PI
* (self.eta * q)
* f64::exp(-(self.eta * q).powi(2))
}
}
fn short_range_f3(&self, q: f64) -> f64 {
if let Some(zeta) = self.zeta {
let exp_c = f64::exp(-(self.eta * q - zeta / (2.0 * self.eta)).powi(2));
let erfc_c = erfc_x(self.eta * q + zeta / (2.0 * self.eta));
4.0 * self.eta.powi(3) / Self::SQRT_PI
* (1.0
- 2.0
* (self.eta * q - zeta / self.eta)
* (self.eta * q - zeta / (2.0 * self.eta))
- zeta.powi(2) / self.eta.powi(2))
* exp_c
+ 4.0 * zeta.powi(3) * erfc_c * f64::exp(2.0 * zeta * q)
} else {
4.0 * self.eta.powi(3) / Self::SQRT_PI
* (1.0 - 2.0 * (self.eta * q).powi(2))
* (-(self.eta * q).powi(2)).exp()
}
}
fn self_energy_prefactors(&self) -> SelfEnergyPrefactors {
let (c1, c2) = if let Some(zeta) = self.zeta {
let c1 = -self.eta / Self::SQRT_PI
* (f64::exp(-zeta.powi(2) / 4.0 / self.eta.powi(2))
- Self::SQRT_PI * zeta / (2.0 * self.eta) * erfc_x(zeta / (2.0 * self.eta)));
let c2 = -self.eta.powi(3) / Self::SQRT_PI * 2.0 / 3.0
* (Self::SQRT_PI * zeta.powi(3) / 4.0 / self.eta.powi(3)
* erfc_x(zeta / (2.0 * self.eta))
+ (1.0 - zeta.powi(2) / 2.0 / self.eta.powi(2))
* f64::exp(-zeta.powi(2) / 4.0 / self.eta.powi(2)));
(c1, c2)
} else {
let c1 = -self.eta / Self::SQRT_PI;
let c2 = -self.eta.powi(3) / Self::SQRT_PI * 2.0 / 3.0;
(c1, c2)
};
SelfEnergyPrefactors {
monopole: Some(c1),
dipole: Some(c2),
}
}
}
#[test]
fn test_ewald() {
use crate::pairwise::MultipoleEnergy;
let cutoff = 29.0;
let alpha = 0.1;
let pot = RealSpaceEwald::from_alpha(cutoff, alpha, None);
let eps = 1e-8;
assert_relative_eq!(
pot.self_energy(&[2.0], &[0.0]),
-0.2256758334,
epsilon = eps
);
assert_relative_eq!(
pot.self_energy(&[0.0], &[f64::sqrt(2.0)]),
-0.000752257778,
epsilon = eps
);
assert_relative_eq!(pot.alpha(), alpha, epsilon = eps);
assert_relative_eq!(pot.short_range_f0(0.5), 0.04030484067840161, epsilon = eps);
assert_relative_eq!(pot.short_range_f1(0.5), -0.39971358519150996, epsilon = eps);
assert_relative_eq!(pot.short_range_f2(0.5), 3.36159125, epsilon = eps);
assert_relative_eq!(pot.short_range_f3(0.5), -21.54779992186245, epsilon = eps);
let debye_length = 23.0;
let pot = RealSpaceEwald::from_alpha(cutoff, alpha, Some(debye_length));
let eps = 1e-7;
assert_relative_eq!(
pot.self_energy(&[2.0], &[0.0]),
-0.14930129209178544,
epsilon = eps
);
assert_relative_eq!(
pot.self_energy(&[0.0], &[f64::sqrt(2.0)]),
-0.0006704901976,
epsilon = eps
);
assert_relative_eq!(pot.kappa().unwrap(), 1.0 / 23.0, epsilon = eps);
assert_relative_eq!(pot.short_range_f0(0.5), 0.07306333588, epsilon = eps);
assert_relative_eq!(pot.short_range_f1(0.5), -0.6344413331247332, epsilon = eps);
assert_relative_eq!(pot.short_range_f2(0.5), 4.42313324197739, epsilon = eps);
assert_relative_eq!(pot.short_range_f3(0.5), -19.859372613319028, epsilon = eps);
assert_eq!(
pot.to_string(),
"Real-space Ewald: πβ = 29.0, π = 2.9, π» = 1.3 <https://doi.org/fcjts8>"
);
}