Skip to main content

sci_form/dispersion/
params.rs

1//! D4 Parameter Infrastructure — Core
2//!
3//! Atomic reference data, coordination numbers, and dynamic C6/C8.
4
5/// Per-element D4 parameters.
6#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
7pub struct D4Params {
8    /// Reference C6 coefficient (Hartree·Bohr^6).
9    pub c6_ref: f64,
10    /// Reference C8 coefficient (Hartree·Bohr^8).
11    pub c8_ref: f64,
12    /// Static dipole polarizability α₀ (Bohr³).
13    pub alpha0: f64,
14    /// Covalent radius for CN (Bohr).
15    pub r_cov: f64,
16}
17
18/// Get D4 reference parameters by atomic number.
19pub fn get_d4_params(z: u8) -> D4Params {
20    match z {
21        1 => D4Params {
22            c6_ref: 6.50,
23            c8_ref: 94.6,
24            alpha0: 4.50,
25            r_cov: 0.60,
26        },
27        5 => D4Params {
28            c6_ref: 99.5,
29            c8_ref: 3430.0,
30            alpha0: 21.0,
31            r_cov: 1.61,
32        },
33        6 => D4Params {
34            c6_ref: 46.6,
35            c8_ref: 1315.0,
36            alpha0: 12.0,
37            r_cov: 1.46,
38        },
39        7 => D4Params {
40            c6_ref: 24.2,
41            c8_ref: 560.0,
42            alpha0: 7.40,
43            r_cov: 1.42,
44        },
45        8 => D4Params {
46            c6_ref: 15.6,
47            c8_ref: 300.0,
48            alpha0: 5.40,
49            r_cov: 1.38,
50        },
51        9 => D4Params {
52            c6_ref: 9.52,
53            c8_ref: 160.0,
54            alpha0: 3.80,
55            r_cov: 1.34,
56        },
57        14 => D4Params {
58            c6_ref: 305.0,
59            c8_ref: 15700.0,
60            alpha0: 37.0,
61            r_cov: 2.21,
62        },
63        15 => D4Params {
64            c6_ref: 185.0,
65            c8_ref: 8200.0,
66            alpha0: 25.0,
67            r_cov: 2.08,
68        },
69        16 => D4Params {
70            c6_ref: 134.0,
71            c8_ref: 5470.0,
72            alpha0: 19.6,
73            r_cov: 1.96,
74        },
75        17 => D4Params {
76            c6_ref: 94.6,
77            c8_ref: 3430.0,
78            alpha0: 15.0,
79            r_cov: 1.88,
80        },
81        35 => D4Params {
82            c6_ref: 162.0,
83            c8_ref: 6800.0,
84            alpha0: 21.0,
85            r_cov: 2.16,
86        },
87        53 => D4Params {
88            c6_ref: 385.0,
89            c8_ref: 20500.0,
90            alpha0: 35.0,
91            r_cov: 2.51,
92        },
93        22 => D4Params {
94            c6_ref: 379.0,
95            c8_ref: 17100.0,
96            alpha0: 45.0,
97            r_cov: 2.61,
98        },
99        26 => D4Params {
100            c6_ref: 202.0,
101            c8_ref: 7900.0,
102            alpha0: 30.0,
103            r_cov: 2.45,
104        },
105        29 => D4Params {
106            c6_ref: 171.0,
107            c8_ref: 6100.0,
108            alpha0: 24.0,
109            r_cov: 2.49,
110        },
111        30 => D4Params {
112            c6_ref: 207.0,
113            c8_ref: 8000.0,
114            alpha0: 28.0,
115            r_cov: 2.45,
116        },
117        // --- Additional main-group elements ---
118        2 => D4Params {
119            c6_ref: 1.46,
120            c8_ref: 14.1,
121            alpha0: 1.38,
122            r_cov: 0.46,
123        }, // He
124        3 => D4Params {
125            c6_ref: 1387.0,
126            c8_ref: 127800.0,
127            alpha0: 164.0,
128            r_cov: 2.49,
129        }, // Li
130        4 => D4Params {
131            c6_ref: 214.0,
132            c8_ref: 12100.0,
133            alpha0: 38.0,
134            r_cov: 1.89,
135        }, // Be
136        10 => D4Params {
137            c6_ref: 6.38,
138            c8_ref: 76.0,
139            alpha0: 2.67,
140            r_cov: 1.10,
141        }, // Ne
142        11 => D4Params {
143            c6_ref: 1556.0,
144            c8_ref: 165500.0,
145            alpha0: 163.0,
146            r_cov: 2.99,
147        }, // Na
148        12 => D4Params {
149            c6_ref: 626.0,
150            c8_ref: 46400.0,
151            alpha0: 71.0,
152            r_cov: 2.74,
153        }, // Mg
154        13 => D4Params {
155            c6_ref: 528.0,
156            c8_ref: 35300.0,
157            alpha0: 60.0,
158            r_cov: 2.38,
159        }, // Al
160        18 => D4Params {
161            c6_ref: 64.3,
162            c8_ref: 2450.0,
163            alpha0: 11.1,
164            r_cov: 1.74,
165        }, // Ar
166        19 => D4Params {
167            c6_ref: 3897.0,
168            c8_ref: 536300.0,
169            alpha0: 290.0,
170            r_cov: 3.59,
171        }, // K
172        20 => D4Params {
173            c6_ref: 2190.0,
174            c8_ref: 246200.0,
175            alpha0: 160.0,
176            r_cov: 3.31,
177        }, // Ca
178        // --- 3d transition metals (remaining) ---
179        21 => D4Params {
180            c6_ref: 571.0,
181            c8_ref: 30500.0,
182            alpha0: 55.0,
183            r_cov: 2.76,
184        }, // Sc
185        23 => D4Params {
186            c6_ref: 335.0,
187            c8_ref: 14800.0,
188            alpha0: 40.0,
189            r_cov: 2.53,
190        }, // V
191        24 => D4Params {
192            c6_ref: 276.0,
193            c8_ref: 11400.0,
194            alpha0: 35.0,
195            r_cov: 2.49,
196        }, // Cr
197        25 => D4Params {
198            c6_ref: 245.0,
199            c8_ref: 9700.0,
200            alpha0: 33.0,
201            r_cov: 2.49,
202        }, // Mn
203        27 => D4Params {
204            c6_ref: 175.0,
205            c8_ref: 6500.0,
206            alpha0: 27.0,
207            r_cov: 2.38,
208        }, // Co
209        28 => D4Params {
210            c6_ref: 155.0,
211            c8_ref: 5500.0,
212            alpha0: 24.0,
213            r_cov: 2.34,
214        }, // Ni
215        // --- 4d transition metals ---
216        39 => D4Params {
217            c6_ref: 700.0,
218            c8_ref: 42000.0,
219            alpha0: 65.0,
220            r_cov: 2.95,
221        }, // Y
222        40 => D4Params {
223            c6_ref: 540.0,
224            c8_ref: 29000.0,
225            alpha0: 55.0,
226            r_cov: 2.80,
227        }, // Zr
228        42 => D4Params {
229            c6_ref: 340.0,
230            c8_ref: 15000.0,
231            alpha0: 40.0,
232            r_cov: 2.61,
233        }, // Mo
234        44 => D4Params {
235            c6_ref: 252.0,
236            c8_ref: 10200.0,
237            alpha0: 33.0,
238            r_cov: 2.53,
239        }, // Ru
240        45 => D4Params {
241            c6_ref: 220.0,
242            c8_ref: 8500.0,
243            alpha0: 29.0,
244            r_cov: 2.49,
245        }, // Rh
246        46 => D4Params {
247            c6_ref: 205.0,
248            c8_ref: 7600.0,
249            alpha0: 26.0,
250            r_cov: 2.49,
251        }, // Pd
252        47 => D4Params {
253            c6_ref: 253.0,
254            c8_ref: 10200.0,
255            alpha0: 33.0,
256            r_cov: 2.72,
257        }, // Ag
258        48 => D4Params {
259            c6_ref: 323.0,
260            c8_ref: 14500.0,
261            alpha0: 46.0,
262            r_cov: 2.76,
263        }, // Cd
264        // --- 5d transition metals ---
265        72 => D4Params {
266            c6_ref: 485.0,
267            c8_ref: 24000.0,
268            alpha0: 50.0,
269            r_cov: 2.80,
270        }, // Hf
271        74 => D4Params {
272            c6_ref: 375.0,
273            c8_ref: 16500.0,
274            alpha0: 42.0,
275            r_cov: 2.68,
276        }, // W
277        76 => D4Params {
278            c6_ref: 280.0,
279            c8_ref: 11500.0,
280            alpha0: 34.0,
281            r_cov: 2.53,
282        }, // Os
283        77 => D4Params {
284            c6_ref: 245.0,
285            c8_ref: 9500.0,
286            alpha0: 30.0,
287            r_cov: 2.53,
288        }, // Ir
289        78 => D4Params {
290            c6_ref: 225.0,
291            c8_ref: 8500.0,
292            alpha0: 28.0,
293            r_cov: 2.53,
294        }, // Pt
295        79 => D4Params {
296            c6_ref: 255.0,
297            c8_ref: 10500.0,
298            alpha0: 36.0,
299            r_cov: 2.57,
300        }, // Au
301        80 => D4Params {
302            c6_ref: 305.0,
303            c8_ref: 13400.0,
304            alpha0: 34.0,
305            r_cov: 2.53,
306        }, // Hg
307        // --- Period 4-5 main group ---
308        31 => D4Params {
309            c6_ref: 498.0,
310            c8_ref: 30600.0,
311            alpha0: 50.0,
312            r_cov: 2.34,
313        }, // Ga
314        32 => D4Params {
315            c6_ref: 354.0,
316            c8_ref: 18600.0,
317            alpha0: 40.0,
318            r_cov: 2.30,
319        }, // Ge
320        33 => D4Params {
321            c6_ref: 246.0,
322            c8_ref: 11400.0,
323            alpha0: 30.0,
324            r_cov: 2.23,
325        }, // As
326        34 => D4Params {
327            c6_ref: 210.0,
328            c8_ref: 9100.0,
329            alpha0: 26.0,
330            r_cov: 2.19,
331        }, // Se
332        49 => D4Params {
333            c6_ref: 700.0,
334            c8_ref: 47000.0,
335            alpha0: 65.0,
336            r_cov: 2.53,
337        }, // In
338        50 => D4Params {
339            c6_ref: 530.0,
340            c8_ref: 32000.0,
341            alpha0: 53.0,
342            r_cov: 2.53,
343        }, // Sn
344        51 => D4Params {
345            c6_ref: 405.0,
346            c8_ref: 21000.0,
347            alpha0: 43.0,
348            r_cov: 2.49,
349        }, // Sb
350        52 => D4Params {
351            c6_ref: 345.0,
352            c8_ref: 16500.0,
353            alpha0: 38.0,
354            r_cov: 2.49,
355        }, // Te
356        _ => D4Params {
357            c6_ref: 50.0,
358            c8_ref: 1500.0,
359            alpha0: 12.0,
360            r_cov: 1.80,
361        },
362    }
363}
364
365/// Compute D4 fractional coordination number.
366pub fn d4_coordination_number(elements: &[u8], positions: &[[f64; 3]]) -> Vec<f64> {
367    let n = elements.len();
368    let bohr_to_ang = 0.529177;
369    let mut cn = vec![0.0; n];
370
371    for i in 0..n {
372        let pi = get_d4_params(elements[i]);
373        for j in (i + 1)..n {
374            let pj = get_d4_params(elements[j]);
375            let r_cov_ij = (pi.r_cov + pj.r_cov) * bohr_to_ang;
376
377            let dx = positions[i][0] - positions[j][0];
378            let dy = positions[i][1] - positions[j][1];
379            let dz = positions[i][2] - positions[j][2];
380            let r_ij = (dx * dx + dy * dy + dz * dz).sqrt();
381
382            if r_ij < 1e-10 {
383                continue;
384            }
385
386            let f = 1.0 / (1.0 + (-16.0 * (r_cov_ij / r_ij - 1.0)).exp());
387            cn[i] += f;
388            cn[j] += f;
389        }
390    }
391
392    cn
393}
394
395/// Get C6 reference for a pair using the London formula.
396pub fn get_c6_reference(z_a: u8, z_b: u8) -> f64 {
397    let pa = get_d4_params(z_a);
398    let pb = get_d4_params(z_b);
399    let sum_alpha = pa.alpha0 + pb.alpha0;
400    if sum_alpha < 1e-15 {
401        return 0.0;
402    }
403    1.5 * pa.alpha0 * pb.alpha0 / sum_alpha
404}
405
406/// Get dynamic C6 adjusted by coordination number.
407pub fn dynamic_c6(z_a: u8, z_b: u8, cn_a: f64, cn_b: f64) -> f64 {
408    let c6_ref = get_c6_reference(z_a, z_b);
409    let cn_ref_a = expected_cn(z_a);
410    let cn_ref_b = expected_cn(z_b);
411
412    let w_a = (-4.0 * (cn_a / cn_ref_a - 1.0).powi(2)).exp();
413    let w_b = (-4.0 * (cn_b / cn_ref_b - 1.0).powi(2)).exp();
414
415    c6_ref * w_a * w_b
416}
417
418fn expected_cn(z: u8) -> f64 {
419    match z {
420        1 => 1.0,
421        2 => 0.0,                // He
422        3 | 11 | 19 => 1.0,      // Li, Na, K
423        4 | 12 | 20 => 2.0,      // Be, Mg, Ca
424        5 | 13 => 3.0,           // B, Al
425        6 | 14 | 32 => 4.0,      // C, Si, Ge
426        7 | 15 | 33 => 3.0,      // N, P, As
427        8 | 16 | 34 | 52 => 2.0, // O, S, Se, Te
428        9 | 17 | 35 | 53 => 1.0, // F, Cl, Br, I
429        10 | 18 => 0.0,          // Ne, Ar
430        // 3d TMs
431        21 => 6.0, // Sc
432        22 => 6.0, // Ti
433        23 => 6.0, // V
434        24 => 6.0, // Cr
435        25 => 6.0, // Mn
436        26 => 6.0, // Fe
437        27 => 6.0, // Co
438        28 => 4.0, // Ni
439        29 => 4.0, // Cu
440        30 => 4.0, // Zn
441        // 4d TMs
442        39 => 6.0,
443        40 => 6.0,
444        42 => 6.0,
445        44 => 6.0,
446        45 => 6.0,
447        46 => 4.0,
448        47 => 4.0,
449        48 => 4.0,
450        // 5d TMs
451        72 => 6.0,
452        74 => 6.0,
453        76 => 6.0,
454        77 => 6.0,
455        78 => 4.0,
456        79 => 4.0,
457        80 => 4.0,
458        // Main group 4-5
459        31 | 49 => 3.0, // Ga, In
460        50 | 51 => 4.0, // Sn, Sb
461        _ => 4.0,
462    }
463}
464
465/// Compute C8 from C6 using the recursion relation.
466pub fn c8_from_c6(c6: f64, z_a: u8, z_b: u8) -> f64 {
467    let pa = get_d4_params(z_a);
468    let pb = get_d4_params(z_b);
469    let qa = if pa.c6_ref > 1e-10 {
470        (pa.c8_ref / pa.c6_ref).sqrt()
471    } else {
472        5.0
473    };
474    let qb = if pb.c6_ref > 1e-10 {
475        (pb.c8_ref / pb.c6_ref).sqrt()
476    } else {
477        5.0
478    };
479    3.0 * c6 * (qa * qb).sqrt()
480}
481
482#[cfg(test)]
483mod tests {
484    use super::*;
485
486    #[test]
487    fn test_d4_cn_methane() {
488        let elements = [6, 1, 1, 1, 1];
489        let pos = [
490            [0.0, 0.0, 0.0],
491            [0.63, 0.63, 0.63],
492            [-0.63, -0.63, 0.63],
493            [-0.63, 0.63, -0.63],
494            [0.63, -0.63, -0.63],
495        ];
496        let cn = d4_coordination_number(&elements, &pos);
497        assert!(cn[0] > 1.0 && cn[0] < 5.0, "C CN = {}", cn[0]);
498        assert!(cn[1] > 0.3 && cn[1] < 1.5, "H CN = {}", cn[1]);
499    }
500
501    #[test]
502    fn test_c6_reference_positive() {
503        for &z in &[1u8, 6, 7, 8, 16] {
504            let c6 = get_c6_reference(z, z);
505            assert!(c6 > 0.0, "C6({},{}) = {}", z, z, c6);
506        }
507    }
508
509    #[test]
510    fn test_c8_from_c6_positive() {
511        let c6 = get_c6_reference(6, 6);
512        let c8 = c8_from_c6(c6, 6, 6);
513        assert!(c8 > c6, "C8 should be larger than C6");
514    }
515}