Skip to main content

test_interface/
test_interface.rs

1//! Integration tests for dftd4 interface.
2//!
3//! However, perhaps some functions can be captured in documentation.
4//! So this file is here.
5#![allow(clippy::excessive_precision)]
6
7use approx::assert_abs_diff_eq;
8use dftd4::prelude::*;
9
10fn test_rational_damping_noargs() {
11    // Check constructor of damping parameters for insufficient arguments
12    assert!(DFTD4RationalDampingParamBuilder::default().build().is_err());
13
14    let builder = DFTD4RationalDampingParamBuilder::default().a1(0.4).a2(5.0);
15    assert!(builder.build().is_err_and(|e| match &e {
16        DFTD4Error::BuilderError(f) => f.field_name() == "s8",
17        _ => false,
18    }));
19
20    let builder = DFTD4RationalDampingParamBuilder::default().s8(1.0).a2(5.0);
21    assert!(builder.build().is_err_and(|e| match &e {
22        DFTD4Error::BuilderError(f) => f.field_name() == "a1",
23        _ => false,
24    }));
25
26    let builder = DFTD4RationalDampingParamBuilder::default().s8(1.0).a1(0.4);
27    assert!(builder.build().is_err_and(|e| match &e {
28        DFTD4Error::BuilderError(f) => f.field_name() == "a2",
29        _ => false,
30    }));
31}
32
33fn test_structure() {
34    // Check if the molecular structure data is working as expected
35    let numbers = vec![6, 7, 6, 7, 6, 6, 6, 8, 7, 6, 8, 7, 6, 6, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
36    #[rustfmt::skip]
37    let positions = vec![
38         2.02799738646442,  0.09231312124713, -0.14310895950963,
39         4.75011007621000,  0.02373496014051, -0.14324124033844,
40         6.33434307654413,  2.07098865582721, -0.14235306905930,
41         8.72860718071825,  1.38002919517619, -0.14265542523943,
42         8.65318821103610, -1.19324866489847, -0.14231527453678,
43         6.23857175648671, -2.08353643730276, -0.14218299370797,
44         5.63266886875962, -4.69950321056008, -0.13940509630299,
45         3.44931709749015, -5.48092386085491, -0.14318454855466,
46         7.77508917214346, -6.24427872938674, -0.13107140408805,
47        10.30229550927022, -5.39739796609292, -0.13672168520430,
48        12.07410272485492, -6.91573621641911, -0.13666499342053,
49        10.70038521493902, -2.79078533715849, -0.14148379504141,
50        13.24597858727017, -1.76969072232377, -0.14218299370797,
51         7.40891694074004, -8.95905928176407, -0.11636933482904,
52         1.38702118184179,  2.05575746325296, -0.14178615122154,
53         1.34622199478497, -0.86356704498496,  1.55590600570783,
54         1.34624089204623, -0.86133716815647, -1.84340893849267,
55         5.65596919189118,  4.00172183859480, -0.14131371969009,
56        14.67430918222276, -3.26230980007732, -0.14344911021228,
57        13.50897177220290, -0.60815166181684,  1.54898960808727,
58        13.50780014200488, -0.60614855212345, -1.83214617078268,
59         5.41408424778406, -9.49239668625902, -0.11022772492007,
60         8.31919801555568, -9.74947502841788,  1.56539243085954,
61         8.31511620712388, -9.76854236502758, -1.79108242206824,
62    ];
63
64    // Constructor should raise an error for nuclear fusion input
65    assert!(DFTD4Structure::new_f(&numbers, &vec![0.0; 72], None, None, None)
66        .is_err_and(|e| e.to_string().contains("Too close interatomic distances found")));
67
68    // The Rust struct should protect from garbage input like this
69    assert!(DFTD4Structure::new_f(&[1, 1, 1], &positions, None, None, None)
70        .is_err_and(|e| e.to_string().contains("Invalid dimension for positions")));
71
72    // Also check for sane coordinate input
73    assert!(DFTD4Structure::new_f(&numbers, &[0.0; 7], None, None, None)
74        .is_err_and(|e| e.to_string().contains("Invalid dimension for positions")));
75
76    // Construct real molecule
77    let mut mol = DFTD4Structure::new(&numbers, &positions, None, None, None);
78
79    // Try to update a structure with mismatched coordinates
80    assert!(mol
81        .update_f(&[0.0; 7], None)
82        .is_err_and(|e| e.to_string().contains("Invalid dimension for positions")));
83
84    // Try to add a mismatched lattice
85    assert!(mol
86        .update_f(&positions, Some(&[0.0; 7]))
87        .is_err_and(|e| e.to_string().contains("Invalid dimension for lattice")));
88
89    // Try to update a structure with nuclear fusion coordinates
90    assert!(mol
91        .update_f(&[0.0; 72], None)
92        .is_err_and(|e| e.to_string().contains("Too close interatomic distances found"),));
93}
94
95fn test_blypd4() {
96    // Use BLYP-D4 for a mindless molecule
97    let thr = 1.0e-7;
98
99    let numbers = vec![1, 1, 6, 5, 1, 15, 8, 17, 13, 15, 5, 1, 9, 15, 1, 15];
100    #[rustfmt::skip]
101    let positions = vec![
102         2.79274810283778,  3.82998228828316, -2.79287054959216,
103        -1.43447454186833,  0.43418729987882,  5.53854345129809,
104        -3.26268343665218, -2.50644032426151, -1.56631149351046,
105         2.14548759959147, -0.88798018953965, -2.24592534506187,
106        -4.30233097423181, -3.93631518670031, -0.48930754109119,
107         0.06107643564880, -3.82467931731366, -2.22333344469482,
108         0.41168550401858,  0.58105573172764,  5.56854609916143,
109         4.41363836635653,  3.92515871809283,  2.57961724984000,
110         1.33707758998700,  1.40194471661647,  1.97530004949523,
111         3.08342709834868,  1.72520024666801, -4.42666116106828,
112        -3.02346932078505,  0.04438199934191, -0.27636197425010,
113         1.11508390868455, -0.97617412809198,  6.25462847718180,
114         0.61938955433011,  2.17903547389232, -6.21279842416963,
115        -2.67491681346835,  3.00175899761859,  1.05038813614845,
116        -4.13181080289514, -2.34226739863660, -3.44356159392859,
117         2.85007173009739, -2.64884892757600,  0.71010806424206,
118    ];
119
120    let model = DFTD4Model::new(&numbers, &positions, None, None, None);
121
122    let param = DFTD4Param::load_rational_damping("blyp", true);
123    let res = model.get_dispersion(&param, false);
124
125    assert_abs_diff_eq!(res.energy, -0.06991716314879085, epsilon = thr);
126
127    let res = model.get_dispersion(&param, true);
128
129    assert_abs_diff_eq!(res.energy, -0.06991716314879085, epsilon = thr);
130}
131
132#[cfg(feature = "api-v4_0")]
133fn test_tpssd4s() {
134    // Use TPSS-D4S for a mindless molecule
135    let thr = 1.0e-7;
136
137    let numbers = vec![1, 1, 6, 5, 1, 15, 8, 17, 13, 15, 5, 1, 9, 15, 1, 15];
138    #[rustfmt::skip]
139    let positions = vec![
140         2.79274810283778,  3.82998228828316, -2.79287054959216,
141        -1.43447454186833,  0.43418729987882,  5.53854345129809,
142        -3.26268343665218, -2.50644032426151, -1.56631149351046,
143         2.14548759959147, -0.88798018953965, -2.24592534506187,
144        -4.30233097423181, -3.93631518670031, -0.48930754109119,
145         0.06107643564880, -3.82467931731366, -2.22333344469482,
146         0.41168550401858,  0.58105573172764,  5.56854609916143,
147         4.41363836635653,  3.92515871809283,  2.57961724984000,
148         1.33707758998700,  1.40194471661647,  1.97530004949523,
149         3.08342709834868,  1.72520024666801, -4.42666116106828,
150        -3.02346932078505,  0.04438199934191, -0.27636197425010,
151         1.11508390868455, -0.97617412809198,  6.25462847718180,
152         0.61938955433011,  2.17903547389232, -6.21279842416963,
153        -2.67491681346835,  3.00175899761859,  1.05038813614845,
154        -4.13181080289514, -2.34226739863660, -3.44356159392859,
155         2.85007173009739, -2.64884892757600,  0.71010806424206,
156    ];
157
158    let model = DFTD4Model::new_d4s(&numbers, &positions, None, None, None);
159
160    let param = DFTD4Param::load_rational_damping("tpss", true);
161    let res = model.get_dispersion(&param, false);
162
163    assert_abs_diff_eq!(res.energy, -0.046233140236052253, epsilon = thr);
164
165    let res = model.get_dispersion(&param, true);
166
167    assert_abs_diff_eq!(res.energy, -0.046233140236052253, epsilon = thr);
168}
169
170fn test_pbed4() {
171    // Use PBE-D4 for a mindless molecule
172    let thr = 1.0e-7;
173
174    let numbers = vec![1, 9, 15, 13, 1, 1, 13, 5, 3, 15, 8, 1, 1, 5, 16, 1];
175    #[rustfmt::skip]
176    let positions = vec![
177        -2.14132037405479, -1.34402701877044, -2.32492500904728,
178         4.46671289205392, -2.04800110524830,  0.44422406067087,
179        -4.92212517643478, -1.73734240529793,  0.96890323821450,
180        -1.30966093045696, -0.52977363497805,  3.44453452239668,
181        -4.34208759006189, -4.30470270977329,  0.39887431726215,
182         0.61788392767516,  2.62484136683297, -3.28228926932647,
183         4.23562873444840, -1.68839322682951, -3.53824299552792,
184         2.23130060612446,  1.93579813100155, -1.80384647554323,
185        -2.32285463652832,  2.90603947535842, -1.39684847191937,
186         2.34557941578250,  2.86074312333371,  1.82827238641666,
187        -3.66431367659153, -0.42910188232667, -1.81957402856634,
188        -0.34927881505446, -1.75988134003940,  5.98017466326572,
189         0.29500802281217, -2.00226104143537,  0.53023447931897,
190         2.10449364205058, -0.56741404446633,  0.30975625014335,
191        -1.59355304432499,  3.69176153150419,  2.87878226787916,
192         4.34858700256050,  2.39171478113440, -2.61802993563738,
193    ];
194
195    let model = DFTD4Model::new(&numbers, &positions, None, None, None);
196
197    let param = DFTD4Param::load_rational_damping("pbe", true);
198    let res = model.get_dispersion(&param, false);
199
200    assert_abs_diff_eq!(res.energy, -0.028415184156428127, epsilon = thr);
201
202    let res = model.get_dispersion(&param, true);
203
204    assert_abs_diff_eq!(res.energy, -0.028415184156428127, epsilon = thr);
205}
206
207#[cfg(feature = "api-v3_1")]
208fn test_r2scan3c() {
209    // Use r2SCAN-3c for a mindless molecule (custom D4 model)
210    let thr = 1.0e-8;
211
212    let numbers = vec![1, 9, 15, 13, 1, 1, 13, 5, 3, 15, 8, 1, 1, 5, 16, 1];
213    #[rustfmt::skip]
214    let positions = vec![
215        -2.14132037405479, -1.34402701877044, -2.32492500904728,
216         4.46671289205392, -2.04800110524830,  0.44422406067087,
217        -4.92212517643478, -1.73734240529793,  0.96890323821450,
218        -1.30966093045696, -0.52977363497805,  3.44453452239668,
219        -4.34208759006189, -4.30470270977329,  0.39887431726215,
220         0.61788392767516,  2.62484136683297, -3.28228926932647,
221         4.23562873444840, -1.68839322682951, -3.53824299552792,
222         2.23130060612446,  1.93579813100155, -1.80384647554323,
223        -2.32285463652832,  2.90603947535842, -1.39684847191937,
224         2.34557941578250,  2.86074312333371,  1.82827238641666,
225        -3.66431367659153, -0.42910188232667, -1.81957402856634,
226        -0.34927881505446, -1.75988134003940,  5.98017466326572,
227         0.29500802281217, -2.00226104143537,  0.53023447931897,
228         2.10449364205058, -0.56741404446633,  0.30975625014335,
229        -1.59355304432499,  3.69176153150419,  2.87878226787916,
230         4.34858700256050,  2.39171478113440, -2.61802993563738,
231    ];
232
233    // Python test uses custom D4 model with ga=2.0, gc=1.0
234    let model = DFTD4Model::custom_d4(&numbers, &positions, 2.0, 1.0, 6.0, None, None, None);
235
236    let param = DFTD4RationalDampingParamBuilder::default()
237        .s8(0.0)
238        .a1(0.42)
239        .a2(5.65)
240        .s9(2.0)
241        .build()
242        .unwrap()
243        .new_param();
244    let res = model.get_dispersion(&param, false);
245
246    assert_abs_diff_eq!(res.energy, -0.008016697276824889, epsilon = thr);
247
248    let res = model.get_dispersion(&param, true);
249
250    assert_abs_diff_eq!(res.energy, -0.008016697276824889, epsilon = thr);
251}
252
253#[cfg(feature = "api-v3_2")]
254fn test_pair_resolved() {
255    // Calculate pairwise resolved dispersion energy for a molecule
256    let thr = 1.0e-8;
257
258    let numbers = vec![16, 16, 16, 16, 16, 16, 16, 16];
259    #[rustfmt::skip]
260    let positions = vec![
261        -4.15128787379191,  1.71951973863958, -0.93066267097296,
262        -4.15128787379191, -1.71951973863958,  0.93066267097296,
263        -1.71951973863958, -4.15128787379191, -0.93066267097296,
264         1.71951973863958, -4.15128787379191,  0.93066267097296,
265         4.15128787379191, -1.71951973863958, -0.93066267097296,
266         4.15128787379191,  1.71951973863958,  0.93066267097296,
267         1.71951973863958,  4.15128787379191, -0.93066267097296,
268        -1.71951973863958,  4.15128787379191,  0.93066267097296,
269    ];
270    #[rustfmt::skip]
271    let pair_disp2 = vec![
272        -0.00000000e-0, -5.80599854e-4, -4.74689854e-4, -2.11149449e-4, -1.63163128e-4, -2.11149449e-4, -4.74689854e-4, -5.80599854e-4,
273        -5.80599854e-4, -0.00000000e-0, -5.80599854e-4, -4.74689854e-4, -2.11149449e-4, -1.63163128e-4, -2.11149449e-4, -4.74689854e-4,
274        -4.74689854e-4, -5.80599854e-4, -0.00000000e-0, -5.80599854e-4, -4.74689854e-4, -2.11149449e-4, -1.63163128e-4, -2.11149449e-4,
275        -2.11149449e-4, -4.74689854e-4, -5.80599854e-4, -0.00000000e-0, -5.80599854e-4, -4.74689854e-4, -2.11149449e-4, -1.63163128e-4,
276        -1.63163128e-4, -2.11149449e-4, -4.74689854e-4, -5.80599854e-4, -0.00000000e-0, -5.80599854e-4, -4.74689854e-4, -2.11149449e-4,
277        -2.11149449e-4, -1.63163128e-4, -2.11149449e-4, -4.74689854e-4, -5.80599854e-4, -0.00000000e-0, -5.80599854e-4, -4.74689854e-4,
278        -4.74689854e-4, -2.11149449e-4, -1.63163128e-4, -2.11149449e-4, -4.74689854e-4, -5.80599854e-4, -0.00000000e-0, -5.80599854e-4,
279        -5.80599854e-4, -4.74689854e-4, -2.11149449e-4, -1.63163128e-4, -2.11149449e-4, -4.74689854e-4, -5.80599854e-4, -0.00000000e-0,
280    ];
281    #[rustfmt::skip]
282    let pair_disp3 = vec![
283        0.00000000e-0, 3.39353850e-7, 8.74462839e-7, 1.17634100e-6, 9.86937310e-7, 1.17634100e-6, 8.74462839e-7, 3.39353850e-7,
284        3.39353850e-7, 0.00000000e-0, 3.39353850e-7, 8.74462839e-7, 1.17634100e-6, 9.86937310e-7, 1.17634100e-6, 8.74462839e-7,
285        8.74462839e-7, 3.39353850e-7, 0.00000000e-0, 3.39353850e-7, 8.74462839e-7, 1.17634100e-6, 9.86937310e-7, 1.17634100e-6,
286        1.17634100e-6, 8.74462839e-7, 3.39353850e-7, 0.00000000e-0, 3.39353850e-7, 8.74462839e-7, 1.17634100e-6, 9.86937310e-7,
287        9.86937310e-7, 1.17634100e-6, 8.74462839e-7, 3.39353850e-7, 0.00000000e-0, 3.39353850e-7, 8.74462839e-7, 1.17634100e-6,
288        1.17634100e-6, 9.86937310e-7, 1.17634100e-6, 8.74462839e-7, 3.39353850e-7, 0.00000000e-0, 3.39353850e-7, 8.74462839e-7,
289        8.74462839e-7, 1.17634100e-6, 9.86937310e-7, 1.17634100e-6, 8.74462839e-7, 3.39353850e-7, 0.00000000e-0, 3.39353850e-7,
290        3.39353850e-7, 8.74462839e-7, 1.17634100e-6, 9.86937310e-7, 1.17634100e-6, 8.74462839e-7, 3.39353850e-7, 0.00000000e-0,
291    ];
292
293    let model = DFTD4Model::new(&numbers, &positions, None, None, None);
294
295    let param = DFTD4RationalDampingParamBuilder::default()
296        .s8(1.20065498)
297        .a1(0.40085597)
298        .a2(5.02928789)
299        .build()
300        .unwrap()
301        .new_param();
302    let res = model.get_pairwise_dispersion(&param);
303
304    for (a, b) in res.pair_energy2.iter().zip(pair_disp2.iter()) {
305        assert_abs_diff_eq!(a, b, epsilon = thr);
306    }
307
308    for (a, b) in res.pair_energy3.iter().zip(pair_disp3.iter()) {
309        assert_abs_diff_eq!(a, b, epsilon = thr);
310    }
311}
312
313#[cfg(feature = "api-v3_1")]
314fn test_properties() {
315    // Calculate dispersion related properties for a molecule
316    let thr = 1.0e-7;
317
318    let numbers = vec![7, 7, 7, 7, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1];
319    #[rustfmt::skip]
320    let positions = vec![
321        1.97420621099560, 1.97415497783241, 1.97424596974304,
322        6.82182427659395, 2.87346383480995, 7.72099517560089,
323        7.72104957181201, 6.82177051521773, 2.87336561318016,
324        2.87343220660781, 7.72108897828386, 6.82187093171878,
325        3.51863272100286, 2.63865333484548, 1.00652979981286,
326        2.63877594964754, 1.00647313885594, 3.51882748086447,
327        1.00639728563189, 3.51850454450845, 2.63869202592387,
328        8.36624975982697, 2.20896711017229, 8.68870955681018,
329        7.48639684558259, 3.84114715917956, 6.17640982573725,
330        5.85401675167715, 1.32911569888797, 7.05654606696031,
331        7.05646299938990, 5.85409590282274, 1.32879923864813,
332        8.68882633853582, 8.36611541129785, 2.20894120662207,
333        3.84121223226912, 6.17673669892998, 7.48629723649480,
334        1.32897854262127, 7.05658604099926, 5.85414031368096,
335        2.20884896069885, 8.68875820985799, 8.36643568423387,
336        6.17659142004652, 7.48627051643848, 3.84109594690835,
337    ];
338    #[rustfmt::skip]
339    let lattice = vec![
340        9.69523775911749, 0.0, 0.0,
341        0.0, 9.69523775911749, 0.0,
342        0.0, 0.0, 9.69523775911749,
343    ];
344    #[rustfmt::skip]
345    let cn = [
346        2.5775156287150218,
347        2.5775155620078536,
348        2.5775157938667150,
349        2.5775157704485387,
350        0.8591731475439074,
351        0.8591680526841657,
352        0.8591744284869478,
353        0.8591732359038715,
354        0.8591678667769283,
355        0.8591744593270527,
356        0.8591684383407867,
357        0.8591754863625011,
358        0.8591751690053771,
359        0.8591719636497469,
360        0.8591686377934131,
361        0.8591718691634261,
362    ];
363    #[rustfmt::skip]
364    let charges = [
365        -0.86974543285813199,
366        -0.86974501326130316,
367        -0.86974658178316333,
368        -0.86974643466661739,
369         0.28992010784471012,
370         0.28989847135893759,
371         0.28992738637932841,
372         0.28992042841052362,
373         0.28989746859382759,
374         0.28992753735979965,
375         0.28990032127437559,
376         0.28993196014690176,
377         0.28993044356403513,
378         0.28991421277784613,
379         0.28990119160907674,
380         0.28991393324985348,
381    ];
382    #[rustfmt::skip]
383    let alpha = [
384        9.9853045768095097,
385        9.9853025181287016,
386        9.9853102162086920,
387        9.9853094944097140,
388        1.3513315505023076,
389        1.3513939255952379,
390        1.3513106323935196,
391        1.3513306244831862,
392        1.3513968091133417,
393        1.3513101978580895,
394        1.3513885997538553,
395        1.3512974505020905,
396        1.3513018164322617,
397        1.3513485145877058,
398        1.3513860914395939,
399        1.3513493246534483,
400    ];
401
402    let model = DFTD4Model::new(&numbers, &positions, None, Some(&lattice), None);
403
404    let res = model.get_properties();
405
406    for (a, b) in res.cn.iter().zip(cn.iter()) {
407        assert_abs_diff_eq!(a, b, epsilon = thr);
408    }
409
410    for (a, b) in res.charges.iter().zip(charges.iter()) {
411        assert_abs_diff_eq!(a, b, epsilon = thr);
412    }
413
414    for (a, b) in res.alpha.iter().zip(alpha.iter()) {
415        assert_abs_diff_eq!(a, b, epsilon = thr);
416    }
417}
418
419fn test_error_model() {
420    // Test loading an unknown method produces an error
421    let param = DFTD4Param::load_rational_damping_f("UNKNOWN_METHOD", false);
422    assert!(param.is_err_and(|e| e.to_string().contains("not known")));
423}
424
425#[test]
426fn test() {
427    test_rational_damping_noargs();
428    test_structure();
429    test_blypd4();
430    #[cfg(feature = "api-v4_0")]
431    test_tpssd4s();
432    test_pbed4();
433    #[cfg(feature = "api-v3_1")]
434    test_r2scan3c();
435    #[cfg(feature = "api-v3_2")]
436    test_pair_resolved();
437    #[cfg(feature = "api-v3_1")]
438    test_properties();
439    test_error_model();
440}
441
442fn main() {
443    test_rational_damping_noargs();
444    test_structure();
445    test_blypd4();
446    #[cfg(feature = "api-v4_0")]
447    test_tpssd4s();
448    test_pbed4();
449    #[cfg(feature = "api-v3_1")]
450    test_r2scan3c();
451    #[cfg(feature = "api-v3_2")]
452    test_pair_resolved();
453    #[cfg(feature = "api-v3_1")]
454    test_properties();
455    test_error_model();
456}