1use crate::{pairwise::ShortRangeFunction, DebyeLength};
19
20#[cfg(feature = "serde")]
21use serde::{Deserialize, Deserializer, Serialize, Serializer};
22
23#[derive(Clone, Debug, PartialEq)]
28#[cfg_attr(
29 feature = "serde",
30 derive(Serialize, Deserialize),
31 serde(deny_unknown_fields)
32)]
33pub struct Plain {
34 cutoff: f64,
36 #[cfg_attr(
38 feature = "serde",
39 serde(
40 rename = "debye",
41 alias = "debye_length",
42 alias = "debyelength",
43 serialize_with = "serialize_reciprocal",
44 deserialize_with = "deserialize_reciprocal",
45 default
46 )
47 )]
48 kappa: Option<f64>,
49}
50
51#[cfg(feature = "serde")]
53fn serialize_reciprocal<S>(x: &Option<f64>, s: S) -> Result<S::Ok, S::Error>
54where
55 S: Serializer,
56{
57 match x.as_ref() {
58 Some(&value) => s.serialize_some(&f64::recip(value)),
59 None => s.serialize_none(),
60 }
61}
62
63#[cfg(feature = "serde")]
65fn deserialize_reciprocal<'de, D>(deserializer: D) -> Result<Option<f64>, D::Error>
66where
67 D: Deserializer<'de>,
68{
69 Ok(Option::deserialize(deserializer)?.map(f64::recip))
70}
71
72impl Default for Plain {
73 fn default() -> Self {
75 Self {
76 cutoff: f64::INFINITY,
77 kappa: None,
78 }
79 }
80}
81
82impl core::fmt::Display for Plain {
83 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
84 write!(f, "Plain Coulomb: 𝑟✂ = {:.1}", self.cutoff)?;
85 if let Some(debye_length) = self.kappa.map(f64::recip) {
86 write!(
87 f,
88 ", λᴰ = {:.1}, λᴰ/𝑟✂ = {:.1e}",
89 debye_length,
90 debye_length / self.cutoff
91 )?;
92 }
93 write!(f, " <{}>", Self::url())?;
94 Ok(())
95 }
96}
97
98impl Plain {
99 pub fn without_cutoff() -> Self {
101 Self::new(f64::INFINITY, None)
102 }
103 pub fn new(cutoff: f64, debye_length: Option<f64>) -> Self {
105 Self {
106 cutoff,
107 kappa: debye_length.map(f64::recip),
108 }
109 }
110 pub fn new_without_salt(cutoff: f64) -> Self {
112 Self::new(cutoff, None)
113 }
114
115 pub fn from_medium(cutoff: f64, medium: &crate::Medium) -> Self {
118 Self::new(cutoff, medium.debye_length())
119 }
120}
121
122impl crate::Cutoff for Plain {
123 #[inline]
124 fn cutoff(&self) -> f64 {
125 self.cutoff
126 }
127}
128
129impl DebyeLength for Plain {
130 #[inline]
131 fn kappa(&self) -> Option<f64> {
132 self.kappa
133 }
134 fn set_debye_length(&mut self, debye_length: Option<f64>) -> crate::Result<()> {
135 self.kappa = debye_length.map(f64::recip);
136 Ok(())
137 }
138}
139
140impl ShortRangeFunction for Plain {
141 fn url() -> &'static str {
142 "https://doi.org/msxd"
143 }
144
145 #[inline]
146 fn short_range_f0(&self, _q: f64) -> f64 {
147 1.0
148 }
149 #[inline]
150 fn short_range_f1(&self, _q: f64) -> f64 {
151 0.0
152 }
153 #[inline]
154 fn short_range_f2(&self, _q: f64) -> f64 {
155 0.0
156 }
157 #[inline]
158 fn short_range_f3(&self, _q: f64) -> f64 {
159 0.0
160 }
161}
162
163#[test]
164fn test_coulomb() {
165 use super::test_utils::{assert_vec3_eq, assert_vec_x_equals_norm, assert_vec_zero};
166 use crate::{NalgebraMatrix3 as Matrix3, NalgebraVector3 as Vector3};
167 use approx::assert_relative_eq;
168
169 use crate::pairwise::{MultipoleEnergy, MultipoleField, MultipoleForce, MultipolePotential};
170 let cutoff: f64 = 29.0;
171 let z1 = 2.0;
172 let z2 = 3.0;
173 let mu1 = Vector3::new(19.0, 7.0, 11.0);
174 let mu2 = Vector3::new(13.0, 17.0, 5.0);
175 let quad1 = Matrix3::new(3.0, 7.0, 8.0, 5.0, 9.0, 6.0, 2.0, 1.0, 4.0);
176 let _quad2 = Matrix3::zeros();
177 let r = Vector3::new(23.0, 0.0, 0.0);
178 let rq = Vector3::new(
179 5.75 * (6.0f64).sqrt(),
180 5.75 * (2.0f64).sqrt(),
181 11.5 * (2.0f64).sqrt(),
182 );
183 let rh = Vector3::new(1.0, 0.0, 0.0);
184
185 let pot = Plain::new(cutoff, None);
186 let eps = 1e-9;
187
188 assert_eq!(
189 pot.to_string(),
190 "Plain Coulomb: 𝑟✂ = 29.0 <https://doi.org/msxd>"
191 );
192
193 assert_eq!(pot.short_range_f0(0.5), 1.0);
195 assert_eq!(pot.short_range_f1(0.5), 0.0);
196 assert_eq!(pot.short_range_f2(0.5), 0.0);
197 assert_eq!(pot.short_range_f3(0.5), 0.0);
198
199 assert_eq!(pot.ion_potential(z1, cutoff + 1.0), 0.0);
201 assert_relative_eq!(
202 pot.ion_potential(z1, r.norm()),
203 0.08695652173913043,
204 epsilon = eps
205 );
206 assert_relative_eq!(
207 pot.dipole_potential(mu1, (cutoff + 1.0) * rh),
208 0.0,
209 epsilon = eps
210 );
211 assert_relative_eq!(
212 pot.dipole_potential(mu1, r),
213 0.035916824196597356,
214 epsilon = eps
215 );
216 assert_relative_eq!(
217 pot.quadrupole_potential(quad1, rq),
218 0.00093632817,
219 epsilon = eps
220 );
221
222 assert_vec_zero!(pot.ion_field(z1, (cutoff + 1.0) * rh), eps);
224 assert_vec_x_equals_norm!(pot.ion_field(z1, r), 0.003780718336, eps);
225 assert_vec_zero!(pot.dipole_field(mu1, (cutoff + 1.0) * rh), eps);
226 assert_vec3_eq!(
227 pot.dipole_field(mu1, r),
228 [0.003123202104, -0.0005753267034, -0.0009040848196],
229 eps
230 );
231 assert_vec3_eq!(
232 pot.quadrupole_field(quad1, r),
233 [-0.00003752130674, -0.00006432224013, -0.00005360186677],
234 eps
235 );
236
237 assert_relative_eq!(pot.ion_ion_energy(z1, z2, cutoff + 1.0), 0.0, epsilon = eps);
239 assert_relative_eq!(
240 pot.ion_ion_energy(z1, z2, r.norm()),
241 z1 * z2 / r.norm(),
242 epsilon = eps
243 );
244 assert_relative_eq!(
245 pot.ion_dipole_energy(z1, mu2, (cutoff + 1.0) * rh),
246 -0.0,
247 epsilon = eps
248 );
249 assert_relative_eq!(
250 pot.ion_dipole_energy(z1, mu2, r),
251 -0.04914933837,
252 epsilon = eps
253 );
254 assert_relative_eq!(
255 pot.dipole_dipole_energy(mu1, mu2, (cutoff + 1.0) * rh),
256 -0.0,
257 epsilon = eps
258 );
259 assert_relative_eq!(
260 pot.dipole_dipole_energy(mu1, mu2, r),
261 -0.02630064930,
262 epsilon = eps
263 );
264 assert_relative_eq!(
265 pot.ion_quadrupole_energy(z2, quad1, rq),
266 0.002808984511,
267 epsilon = eps
268 );
269
270 assert_vec_zero!(pot.ion_ion_force(z1, z2, (cutoff + 1.0) * rh), eps);
272 assert_vec_x_equals_norm!(pot.ion_ion_force(z1, z2, r), 0.01134215501, eps);
273 assert_vec_zero!(pot.ion_dipole_force(z2, mu1, (cutoff + 1.0) * rh), eps);
274 assert_vec3_eq!(
275 pot.ion_dipole_force(z2, mu1, r),
276 [0.009369606312, -0.001725980110, -0.002712254459],
277 eps
278 );
279 assert_vec_zero!(pot.dipole_dipole_force(mu1, mu2, (cutoff + 1.0) * rh), eps);
280 assert_vec3_eq!(
281 pot.dipole_dipole_force(mu1, mu2, r),
282 [0.003430519474, -0.004438234569, -0.002551448858],
283 eps
284 );
285
286 let pot = Plain::new(cutoff, Some(23.0));
288
289 assert_eq!(
290 pot.to_string(),
291 "Plain Coulomb: 𝑟✂ = 29.0, λᴰ = 23.0, λᴰ/𝑟✂ = 7.9e-1 <https://doi.org/msxd>"
292 );
293
294 assert_relative_eq!(pot.ion_potential(z1, cutoff + 1.0), 0.0, epsilon = eps);
295 assert_relative_eq!(
296 pot.ion_potential(z1, r.norm()),
297 0.03198951663,
298 epsilon = eps
299 );
300 assert_relative_eq!(
301 pot.dipole_potential(mu1, (cutoff + 1.0) * rh),
302 0.0,
303 epsilon = eps
304 );
305 assert_relative_eq!(pot.dipole_potential(mu1, r), 0.02642612243, epsilon = eps);
306
307 assert_vec_zero!(pot.ion_field(z1, (cutoff + 1.0) * rh), eps);
309 assert_vec_x_equals_norm!(pot.ion_field(z1, r), 0.002781697098, eps);
310 assert_vec_zero!(pot.dipole_field(mu1, (cutoff + 1.0) * rh), eps);
311
312 let field_scalar = pot.ion_field_scalar(z1, r.norm());
313 let field: Vector3 = pot.ion_field(z1, r).into();
314 assert_relative_eq!(field_scalar, field.norm(), epsilon = eps);
315
316 assert_vec3_eq!(
317 pot.dipole_field(mu1, r),
318 [0.002872404612, -0.0004233017324, -0.0006651884364],
319 eps
320 );
321
322 let pot = Plain::new(cutoff, None);
324 let alpha = 50.0;
325 let charge = 1.0;
326 let r_vec = Vector3::new(5.0, 0.0, 0.0);
327 let energy = pot.ion_induced_dipole_energy(charge, alpha, r_vec);
328 assert_relative_eq!(energy, -0.04, epsilon = eps);
329
330 let r4 = r_vec.norm_squared().powi(2);
332 assert_relative_eq!(energy, -0.5 * charge * charge * alpha / r4, epsilon = eps);
333
334 let pot = Plain::new(cutoff, Some(10.0));
336 let energy = pot.ion_induced_dipole_energy(charge, alpha, r_vec);
337 assert_relative_eq!(energy, -0.0331091497054298, epsilon = eps);
338}
339
340#[cfg(feature = "uom")]
341#[test]
342fn test_plain_si() {
343 use crate::{
344 pairwise::{MultipoleEnergySI, MultipoleFieldSI, MultipolePotentialSI},
345 units::*,
346 };
347 use approx::assert_relative_eq;
348 let eps = 1e-9; let pot = Plain::without_cutoff();
350 let z1 = ElectricCharge::new::<elementary_charge>(2.0);
351 let z2 = ElectricCharge::new::<elementary_charge>(3.0);
352 let r = Length::new::<nanometer>(2.3);
353 let energy = pot.ion_ion_energy(z1, z2, r);
354 assert_relative_eq!(
355 energy.get::<kilojoule_per_mole>(),
356 362.4403242896922,
357 epsilon = eps
358 );
359 let potential = pot.ion_potential(z1, r);
360 assert_relative_eq!(potential.get::<volt>(), 1.2521430850804929, epsilon = eps);
361
362 let field = pot.ion_field(z1, r);
363 assert_relative_eq!(
364 field.get::<volt_per_micrometer>(),
365 544.4100369915187,
366 epsilon = eps
367 );
368}