1use crate::debye_length::DebyeLength;
19use crate::pairwise::{SelfEnergyPrefactors, ShortRangeFunction};
20use num_integer::binomial;
21#[cfg(feature = "serde")]
22use serde::{Deserialize, Deserializer, Serialize};
23
24#[derive(Debug, Clone, PartialEq)]
69#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
70struct Screening {
71 #[cfg_attr(feature = "serde", serde(alias = "κ"))]
73 pub kappa: f64,
74 pub reduced_kappa: f64,
76 pub reduced_kappa_squared: f64,
77 pub yukawa_denom: f64,
78}
79
80impl Screening {
81 fn new(debye_length: f64, cutoff: f64) -> Self {
82 let reduced_kappa = cutoff / debye_length;
83 Screening {
84 kappa: 1.0 / debye_length,
85 reduced_kappa,
86 reduced_kappa_squared: reduced_kappa.powi(2),
87 yukawa_denom: 1.0 / (1.0 - (2.0 * reduced_kappa).exp()),
88 }
89 }
90}
91
92#[derive(Debug, Clone, PartialEq)]
93#[cfg_attr(feature = "serde", derive(Serialize))]
94pub struct Poisson<const C: i32, const D: i32> {
96 cutoff: f64,
98 #[cfg_attr(feature = "serde", serde(alias = "debyelength"))]
100 debye_length: Option<f64>,
101 #[cfg_attr(feature = "serde", serde(skip))]
103 _has_dipolar_selfenergy: bool,
104 #[cfg_attr(feature = "serde", serde(skip))]
105 binom_cdc: f64,
106 #[cfg_attr(feature = "serde", serde(skip))]
107 screening: Option<Screening>,
108}
109
110#[cfg(feature = "serde")]
111impl<'de, const C: i32, const D: i32> Deserialize<'de> for Poisson<C, D> {
112 fn deserialize<DES>(deserializer: DES) -> Result<Self, DES::Error>
113 where
114 DES: Deserializer<'de>,
115 {
116 #[derive(Deserialize)]
117 #[serde(deny_unknown_fields)]
118 struct PoissonData {
119 cutoff: f64,
120 debye_length: Option<f64>,
121 }
122
123 let PoissonData {
124 cutoff,
125 debye_length,
126 } = PoissonData::deserialize(deserializer)?;
127 Ok(Poisson::new(cutoff, debye_length))
128 }
129}
130
131pub type _Plain = Poisson<1, -1>;
133
134pub type Yukawa = Poisson<1, 1>;
138
139pub type UndampedWolf = Poisson<1, 0>;
141
142pub type Kale = Poisson<1, 2>;
144
145pub type McCann = Poisson<1, 3>;
147
148pub type UndampedFukuda = Poisson<2, 1>;
150
151pub type Markland = Poisson<2, 2>;
153
154pub type Stenqvist = Poisson<3, 3>;
156
157pub type Fanourgakis = Poisson<4, 3>;
159
160impl<const C: i32, const D: i32> Poisson<C, D> {
161 pub fn new(cutoff: f64, debye_length: Option<f64>) -> Self {
163 if C < 1 {
164 panic!("`C` must be larger than zero");
165 }
166 if D < -1 && D != -C {
167 panic!("If `D` is less than negative one, then it has to equal negative `C`");
168 }
169 if D == 0 && C != 1 {
170 panic!("If `D` is zero, then `C` has to equal one ");
171 }
172
173 let _has_dipolar_selfenergy = C >= 2;
174
175 let screening = debye_length.map(|d| Screening::new(d, cutoff));
176
177 let binom_cdc = if screening.is_some() || D != -C {
178 f64::from(binomial(C + D, C) * D)
179 } else {
180 0.0
181 };
182
183 Self {
184 cutoff,
185 debye_length,
186 _has_dipolar_selfenergy,
187 binom_cdc,
188 screening,
189 }
190 }
191}
192
193impl<const C: i32, const D: i32> crate::Cutoff for Poisson<C, D> {
194 fn cutoff(&self) -> f64 {
195 self.cutoff
196 }
197}
198
199impl<const C: i32, const D: i32> crate::DebyeLength for Poisson<C, D> {
200 #[inline]
201 fn kappa(&self) -> Option<f64> {
202 self.screening.as_ref().map(|s| s.kappa)
203 }
204 fn set_debye_length(&mut self, debye_length: Option<f64>) -> crate::Result<()> {
205 self.screening = debye_length.map(|d| Screening::new(d, self.cutoff));
206 Ok(())
207 }
208}
209
210impl<const C: i32, const D: i32> ShortRangeFunction for Poisson<C, D> {
211 fn url() -> &'static str {
212 match (C, D) {
213 (1, -1) => "https://doi.org/msxd", (1, 0) => "https://doi.org/10.1063/1.478738", (1, 1) => "https://doi.org/10/fp959p", (1, 2) => "https://doi.org/10/csh8bg", (1, 3) => "https://doi.org/10.1021/ct300961", (2, 1) => "https://doi.org/10.1063/1.3582791", (2, 2) => "https://doi.org/dbpbts", (3, 3) => "https://doi.org/10/c5fr", (4, 3) => "https://doi.org/10.1063/1.3216520", _ => "https://doi.org/c5fr", }
224 }
225
226 fn short_range_f0(&self, q: f64) -> f64 {
227 if D == -C {
229 return 1.0;
230 }
231 let qp: f64 = self.screening.as_ref().map_or(q, |s| {
232 (1.0 - (2.0 * s.reduced_kappa * q).exp()) * s.yukawa_denom
233 });
234
235 if D == 0 && C == 1 {
236 return 1.0 - qp;
237 }
238
239 let sum: f64 = (0..C)
241 .map(|c| (binomial(D - 1 + c, c) * (C - c)) as f64 / f64::from(C) * qp.powi(c))
242 .sum();
243 (1.0 - qp).powi(D + 1) * sum
244 }
245 fn short_range_f1(&self, q: f64) -> f64 {
246 if D == -C {
247 return 0.0;
248 }
249 if D == 0 && C == 1 {
250 return 0.0;
251 }
252 let (qp, dqpdq) = if let Some(s) = &self.screening {
253 let exp2kq = (2.0 * s.reduced_kappa * q).exp();
254 let qp = (1.0 - exp2kq) * s.yukawa_denom;
255 let dqpdq = -2.0 * s.reduced_kappa * exp2kq * s.yukawa_denom;
256 (qp, dqpdq)
257 } else {
258 (q, 1.0)
259 };
260 let mut sum1 = 1.0;
261 let mut sum2 = 0.0;
262
263 for c in 1..C {
264 let factor = (binomial(D - 1 + c, c) * (C - c)) as f64 / C as f64;
265 sum1 += factor * qp.powi(c);
266 sum2 += factor * c as f64 * qp.powi(c - 1);
267 }
268 let dsdqp = -f64::from(D + 1) * (1.0 - qp).powi(D) * sum1 + (1.0 - qp).powi(D + 1) * sum2;
269 dsdqp * dqpdq
270 }
271
272 fn short_range_f2(&self, q: f64) -> f64 {
273 if D == -C {
274 return 0.0;
275 }
276 if D == 0 && C == 1 {
277 return 0.0;
278 }
279
280 let (qp, dqpdq, d2qpdq2, dsdqp) = if let Some(s) = &self.screening {
281 let qp = (1.0 - (2.0 * s.reduced_kappa * q).exp()) * s.yukawa_denom;
282 let dqpdq = -2.0 * s.reduced_kappa * (2.0 * s.reduced_kappa * q).exp() * s.yukawa_denom;
283 let d2qpdq2 =
284 -4.0 * s.reduced_kappa_squared * (2.0 * s.reduced_kappa * q).exp() * s.yukawa_denom;
285 let mut tmp1 = 1.0;
286 let mut tmp2 = 0.0;
287 for c in 1..C {
288 let b = binomial(D - 1 + c, c) as f64 * (C - c) as f64;
289 tmp1 += b / C as f64 * qp.powi(c);
290 tmp2 += b * c as f64 / C as f64 * qp.powi(c - 1);
291 }
292 let dsdqp =
293 -f64::from(D + 1) * (1.0 - qp).powi(D) * tmp1 + (1.0 - qp).powi(D + 1) * tmp2;
294 (qp, dqpdq, d2qpdq2, dsdqp)
295 } else {
296 (q, 1.0, 0.0, 0.0)
297 };
298 let d2sdqp2 = self.binom_cdc * (1.0 - qp).powi(D - 1) * qp.powi(C - 1);
299 d2sdqp2 * dqpdq * dqpdq + dsdqp * d2qpdq2
300 }
301
302 fn short_range_f3(&self, q: f64) -> f64 {
303 if D == -C {
304 return 0.0;
305 }
306 if D == 0 && C == 1 {
307 return 0.0;
308 }
309
310 let (qp, dqpdq, d2qpdq2, d3qpdq3, d2sdqp2, dsdqp) = if let Some(s) = &self.screening {
311 let qp = (1.0 - (2.0 * s.reduced_kappa * q).exp()) * s.yukawa_denom;
312 let dqpdq = -2.0 * s.reduced_kappa * (2.0 * s.reduced_kappa * q).exp() * s.yukawa_denom;
313 let d2qpdq2 =
314 -4.0 * s.reduced_kappa_squared * (2.0 * s.reduced_kappa * q).exp() * s.yukawa_denom;
315 let d3qpdq3 = -8.0
316 * s.reduced_kappa_squared
317 * s.reduced_kappa
318 * (2.0 * s.reduced_kappa * q).exp()
319 * s.yukawa_denom;
320 let d2sdqp2 = self.binom_cdc * (1.0 - qp).powi(D - 1) * qp.powi(C - 1);
321 let mut tmp1 = 1.0;
322 let mut tmp2 = 0.0;
323 for c in 1..C {
324 tmp1 += (binomial(D - 1 + c, c) * (C - c)) as f64 / C as f64 * qp.powi(c);
325 tmp2 += (binomial(D - 1 + c, c) * (C - c)) as f64 / C as f64
326 * c as f64
327 * qp.powi(c - 1);
328 }
329 let dsdqp =
330 -f64::from(D + 1) * (1.0 - qp).powi(D) * tmp1 + (1.0 - qp).powi(D + 1) * tmp2;
331 (qp, dqpdq, d2qpdq2, d3qpdq3, d2sdqp2, dsdqp)
332 } else {
333 (q, 1.0, 0.0, 0.0, 0.0, 0.0)
334 };
335 let d3sdqp3 = self.binom_cdc
336 * (1.0 - qp).powi(D - 2)
337 * qp.powi(C - 2)
338 * ((2.0 - C as f64 - D as f64) * qp + C as f64 - 1.0);
339 d3sdqp3 * dqpdq * dqpdq * dqpdq + 3.0 * d2sdqp2 * dqpdq * d2qpdq2 + dsdqp * d3qpdq3
340 }
341
342 fn self_energy_prefactors(&self) -> SelfEnergyPrefactors {
343 let mut c1: f64 = -0.5 * (C + D) as f64 / C as f64;
344 if let Some(s) = &self.screening {
345 c1 = c1 * -2.0 * s.reduced_kappa * s.yukawa_denom;
346 }
347 SelfEnergyPrefactors {
348 monopole: Some(c1),
349 dipole: None,
350 }
351 }
352}
353
354impl<const C: i32, const D: i32> core::fmt::Display for Poisson<C, D> {
355 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
356 write!(
357 f,
358 "Poisson: 𝐶 = {}, 𝐷 = {}, 𝑟✂ = {:.1} Å",
359 C, D, self.cutoff
360 )?;
361 if let Some(debye_length) = self.kappa().map(f64::recip) {
362 write!(f, ", λᴰ = {:.1} Å", debye_length)?;
363 }
364 write!(f, " <{}>", Self::url())?;
365 Ok(())
366 }
367}
368
369#[test]
370fn test_poisson() {
371 use super::test_utils::{assert_vec3_eq, assert_vec_x_equals_norm, assert_vec_zero};
372 use crate::{
373 pairwise::{MultipoleEnergy, MultipoleField, MultipoleForce, MultipolePotential},
374 NalgebraMatrix3 as Matrix3, NalgebraVector3 as Vector3,
375 };
376 use approx::assert_relative_eq;
377 let cutoff = 29.0;
378 let pot = Stenqvist::new(cutoff, None);
379 let eps = 1e-9; assert_relative_eq!(pot.short_range_f0(0.5), 0.15625, epsilon = eps);
383 assert_relative_eq!(pot.short_range_f1(0.5), -1.0, epsilon = eps);
384 assert_relative_eq!(pot.short_range_f2(0.5), 3.75, epsilon = eps);
385 assert_relative_eq!(pot.short_range_f3(0.5), 0.0, epsilon = eps);
386 assert_relative_eq!(pot.short_range_f3(0.6), -5.76, epsilon = eps);
387 assert_relative_eq!(pot.short_range_f0(1.0), 0.0, epsilon = eps);
388 assert_relative_eq!(pot.short_range_f1(1.0), 0.0, epsilon = eps);
389 assert_relative_eq!(pot.short_range_f2(1.0), 0.0, epsilon = eps);
390 assert_relative_eq!(pot.short_range_f3(1.0), 0.0, epsilon = eps);
391 assert_relative_eq!(pot.short_range_f0(0.0), 1.0, epsilon = eps);
392 assert_relative_eq!(pot.short_range_f1(0.0), -2.0, epsilon = eps);
393 assert_relative_eq!(pot.short_range_f2(0.0), 0.0, epsilon = eps);
394 assert_relative_eq!(pot.short_range_f3(0.0), 0.0, epsilon = eps);
395
396 let pot = Stenqvist::new(cutoff, Some(23.0));
397 approx::assert_relative_eq!(
398 pot.self_energy(&[2.0], &[0.0]),
399 -0.03037721287,
400 epsilon = eps
401 );
402
403 assert_eq!(
404 pot.to_string(),
405 "Poisson: 𝐶 = 3, 𝐷 = 3, 𝑟✂ = 29.0 Å, λᴰ = 23.0 Å <https://doi.org/10/c5fr>"
406 );
407
408 let pot = Fanourgakis::new(cutoff, None);
410 assert_relative_eq!(pot.short_range_f0(0.5), 0.19921875, epsilon = eps);
411 assert_relative_eq!(pot.short_range_f1(0.5), -1.1484375, epsilon = eps);
412 assert_relative_eq!(pot.short_range_f2(0.5), 3.28125, epsilon = eps);
413 assert_relative_eq!(pot.short_range_f3(0.5), 6.5625, epsilon = eps);
414
415 assert_eq!(
416 pot.to_string(),
417 "Poisson: 𝐶 = 4, 𝐷 = 3, 𝑟✂ = 29.0 Å <https://doi.org/10.1063/1.3216520>"
418 );
419
420 let pot = Poisson::<4, 3>::new(cutoff, None);
421 let z1 = 2.0;
422 let z2 = 3.0;
423 let r = Vector3::new(23.0, 0.0, 0.0); let rq = Vector3::new(
425 5.75 * 6.0_f64.sqrt(),
426 5.75 * 2.0_f64.sqrt(),
427 11.5 * 2.0_f64.sqrt(),
428 );
429 let rh = r.normalize();
430 let mu1 = Vector3::new(19.0, 7.0, 11.0);
431 let mu2 = Vector3::new(13.0, 17.0, 5.0);
432 let quad1 = Matrix3::from_row_slice(&[3.0, 7.0, 8.0, 5.0, 9.0, 6.0, 2.0, 1.0, 4.0]);
436 let quad2 = Matrix3::zeros();
437 assert_relative_eq!(quad1.trace(), 16.0, epsilon = eps);
438
439 assert_relative_eq!(pot.ion_potential(z1, cutoff), 0.0, epsilon = eps);
441 assert_relative_eq!(
442 pot.ion_potential(z1, r.norm()),
443 0.0009430652121,
444 epsilon = eps
445 );
446 assert_relative_eq!(
447 pot.dipole_potential(mu1, rh.scale(cutoff)),
448 0.0,
449 epsilon = eps
450 );
451 assert_relative_eq!(pot.dipole_potential(mu1, r), 0.005750206554, epsilon = eps);
452 assert_relative_eq!(
453 pot.quadrupole_potential(quad1, rq),
454 0.000899228165,
455 epsilon = eps
456 );
457 assert_relative_eq!(
458 pot.quadrupole_potential(quad1, rq.scale(cutoff / 23.0)),
459 0.0,
460 epsilon = eps
461 );
462
463 assert_vec_zero!(pot.ion_field(z1, rh.scale(cutoff)), eps);
465 assert_vec_x_equals_norm!(pot.ion_field(z1, r), 0.0006052849004, eps);
466 assert_vec_zero!(pot.dipole_field(mu1, rh.scale(cutoff)), eps);
467
468 let ion_field_scalar = pot.ion_field_scalar(z1, r.norm());
469 let e_ion: Vector3 = pot.ion_field(z1, r).into();
470 assert_relative_eq!(ion_field_scalar, e_ion.norm(), epsilon = eps);
471
472 assert_vec3_eq!(
473 pot.dipole_field(mu1, r),
474 [0.002702513754, -0.00009210857180, -0.0001447420414],
475 eps
476 );
477 assert_vec3_eq!(
478 pot.quadrupole_field(quad1, r),
479 [0.00001919309993, -0.00004053806958, -0.00003378172465],
480 eps
481 );
482
483 assert_relative_eq!(pot.ion_ion_energy(z1, z2, cutoff), 0.0, epsilon = eps);
485 assert_relative_eq!(
486 pot.ion_ion_energy(z1, z2, r.norm()),
487 0.002829195636,
488 epsilon = eps
489 );
490 assert_relative_eq!(
491 pot.ion_dipole_energy(z1, mu2, rh.scale(cutoff)),
492 0.0,
493 epsilon = eps
494 );
495 assert_relative_eq!(
496 pot.ion_dipole_energy(z1, mu2, r),
497 -0.007868703705,
498 epsilon = eps
499 );
500 assert_relative_eq!(
501 pot.ion_dipole_energy(z2, mu1, -r),
502 0.01725061966,
503 epsilon = eps
504 );
505 assert_relative_eq!(
506 pot.dipole_dipole_energy(mu1, mu2, rh.scale(cutoff)),
507 0.0,
508 epsilon = eps
509 );
510 assert_relative_eq!(
511 pot.dipole_dipole_energy(mu1, mu2, r),
512 -0.03284312288,
513 epsilon = eps
514 );
515 assert_relative_eq!(
516 pot.ion_quadrupole_energy(z2, quad1, rq.scale(cutoff / 23.0 * 29.0)),
517 0.0,
518 epsilon = eps
519 );
520 assert_relative_eq!(
521 pot.ion_quadrupole_energy(z2, quad1, rq),
522 0.002697684495,
523 epsilon = eps
524 );
525 assert_relative_eq!(
526 pot.ion_quadrupole_energy(z1, quad2, -rq.scale(cutoff / 23.0 * 29.0)),
527 0.0,
528 epsilon = eps
529 );
530 assert_relative_eq!(
531 pot.ion_quadrupole_energy(z1, quad2, -rq),
532 0.0,
533 epsilon = eps
534 );
535
536 assert_vec_zero!(
538 pot.ion_ion_force(z1, z2, Vector3::new(cutoff, 0.0, 0.0)),
539 eps
540 );
541 assert_vec_x_equals_norm!(pot.ion_ion_force(z1, z2, r), 0.001815854701, eps);
542 assert_vec_zero!(pot.ion_dipole_force(z2, mu1, rh.scale(cutoff)), eps);
543 assert_vec3_eq!(
544 pot.ion_dipole_force(z2, mu1, r),
545 [0.008107541263, -0.0002763257154, -0.0004342261242],
546 eps
547 );
548 assert_vec3_eq!(
549 pot.ion_dipole_force(z1, mu2, -r),
550 [0.003698176716, -0.0004473844916, -0.0001315836740],
551 eps
552 );
553 assert_vec_zero!(pot.dipole_dipole_force(mu1, mu2, rh.scale(cutoff)), eps);
554 assert_vec3_eq!(
555 pot.dipole_dipole_force(mu1, mu2, r),
556 [0.009216400961, -0.002797126801, -0.001608010094],
557 eps
558 );
559
560 let debye_length = 23.0;
562 let pot = Poisson::<3, 3>::new(cutoff, Some(debye_length));
563
564 assert_relative_eq!(pot.short_range_f0(0.5), 0.5673222086324718, epsilon = eps);
566 assert_relative_eq!(pot.short_range_f1(0.5), -1.4373727619264975, epsilon = eps);
567 assert_relative_eq!(pot.short_range_f2(0.5), -2.552012309527445, epsilon = eps);
568 assert_relative_eq!(pot.short_range_f3(0.5), 4.384434366606605, epsilon = eps);
569
570 assert_relative_eq!(pot.ion_potential(z1, cutoff), 0.0, epsilon = eps);
572 assert_relative_eq!(
573 pot.ion_potential(z1, r.norm()),
574 0.003344219306,
575 epsilon = eps
576 );
577 assert_relative_eq!(
578 pot.dipole_potential(mu1, rh.scale(cutoff)),
579 0.0,
580 epsilon = eps
581 );
582 assert_relative_eq!(pot.dipole_potential(mu1, r), 0.01614089171, epsilon = eps);
583 assert_relative_eq!(
584 pot.quadrupole_potential(quad1, rq),
585 0.0016294707475,
586 epsilon = eps
587 );
588 assert_relative_eq!(
589 pot.quadrupole_potential(quad1, rq.scale(cutoff / 23.0 * 29.0)),
590 0.0,
591 epsilon = eps
592 );
593
594 assert_vec_zero!(pot.ion_field(z1, r.scale(cutoff)), eps);
596 assert_vec_x_equals_norm!(pot.ion_field(z1, r), 0.001699041230, eps);
597 assert_vec_zero!(pot.dipole_field(mu1, r.scale(cutoff)), eps);
598
599 let ion_field_scalar = pot.ion_field_scalar(z1, r.norm());
600 let ion_field: Vector3 = pot.ion_field(z1, r).into();
601 assert_relative_eq!(ion_field_scalar, ion_field.norm(), epsilon = eps);
602
603 assert_vec3_eq!(
604 pot.dipole_field(mu1, r),
605 [0.004956265485, -0.0002585497523, -0.0004062924688],
606 eps
607 );
608 assert_vec3_eq!(
609 pot.quadrupole_field(quad1, r),
610 [-0.00005233355205, -0.00007768480608, -0.00006473733856],
611 eps
612 );
613
614 assert_relative_eq!(pot.ion_ion_energy(z1, z2, cutoff), 0.0, epsilon = eps);
616 assert_relative_eq!(
617 pot.ion_ion_energy(z1, z2, r.norm()),
618 0.01003265793,
619 epsilon = eps
620 );
621 assert_relative_eq!(
622 pot.ion_dipole_energy(z1, mu2, r.scale(cutoff)),
623 0.0,
624 epsilon = eps
625 );
626 assert_relative_eq!(
627 pot.ion_dipole_energy(z1, mu2, r),
628 -0.02208753604,
629 epsilon = eps
630 );
631 assert_relative_eq!(
632 pot.ion_dipole_energy(z2, mu1, -r),
633 0.04842267505,
634 epsilon = eps
635 );
636 assert_relative_eq!(
637 pot.dipole_dipole_energy(mu1, mu2, r.scale(cutoff)),
638 0.0,
639 epsilon = eps
640 );
641 assert_relative_eq!(
642 pot.dipole_dipole_energy(mu1, mu2, r),
643 -0.05800464321,
644 epsilon = eps
645 );
646 assert_relative_eq!(
647 pot.ion_quadrupole_energy(z2, quad1, rq.scale(cutoff / 23.0 * 29.0)),
648 0.0,
649 epsilon = eps
650 );
651 assert_relative_eq!(
652 pot.ion_quadrupole_energy(z2, quad1, rq),
653 0.004888412229,
654 epsilon = eps
655 );
656
657 assert_vec_zero!(pot.ion_ion_force(z1, z2, r.scale(cutoff)), eps);
659 assert_vec_x_equals_norm!(pot.ion_ion_force(z1, z2, r), 0.005097123689, eps);
660 assert_vec_zero!(pot.ion_dipole_force(z2, mu1, r.scale(cutoff)), eps);
661 assert_vec3_eq!(
662 pot.ion_dipole_force(z2, mu1, r),
663 [0.01486879646, -0.0007756492577, -0.001218877402],
664 eps
665 );
666 assert_vec3_eq!(
667 pot.ion_dipole_force(z1, mu2, -r),
668 [0.006782258035, -0.001255813082, -0.0003693567885],
669 eps
670 );
671 assert_vec_zero!(pot.dipole_dipole_force(mu1, mu2, r.scale(cutoff)), eps);
672 assert_vec3_eq!(
673 pot.dipole_dipole_force(mu1, mu2, r),
674 [0.002987655323, -0.005360251624, -0.003081497314],
675 eps
676 );
677}