test_interface/
test_interface.rs

1//! This file is actually testing the interface of the library.
2//!
3//! However, perhaps some functions can be captured in documentation.
4//! So this file is here.
5
6use approx::assert_abs_diff_eq;
7use rstest::{fixture, rstest};
8
9use dftd3::prelude::*;
10
11// Fixtures
12#[fixture]
13fn numbers() -> Vec<usize> {
14    vec![1, 1, 6, 5, 1, 15, 8, 17, 13, 15, 5, 1, 9, 15, 1, 15]
15}
16
17#[fixture]
18fn positions() -> Vec<f64> {
19    #[rustfmt::skip]
20    let positions = vec![
21        // Coordinates in Bohr
22         2.79274810283778,  3.82998228828316, -2.79287054959216,
23        -1.43447454186833,  0.43418729987882,  5.53854345129809,
24        -3.26268343665218, -2.50644032426151, -1.56631149351046,
25         2.14548759959147, -0.88798018953965, -2.24592534506187,
26        -4.30233097423181, -3.93631518670031, -0.48930754109119,
27         0.06107643564880, -3.82467931731366, -2.22333344469482,
28         0.41168550401858,  0.58105573172764,  5.56854609916143,
29         4.41363836635653,  3.92515871809283,  2.57961724984000,
30         1.33707758998700,  1.40194471661647,  1.97530004949523,
31         3.08342709834868,  1.72520024666801, -4.42666116106828,
32        -3.02346932078505,  0.04438199934191, -0.27636197425010,
33         1.11508390868455, -0.97617412809198,  6.25462847718180,
34         0.61938955433011,  2.17903547389232, -6.21279842416963,
35        -2.67491681346835,  3.00175899761859,  1.05038813614845,
36        -4.13181080289514, -2.34226739863660, -3.44356159392859,
37         2.85007173009739, -2.64884892757600,  0.71010806424206,
38    ];
39    positions
40}
41
42#[fixture]
43fn model(numbers: Vec<usize>, positions: Vec<f64>) -> DFTD3Model {
44    DFTD3Model::new(&numbers, &positions, None, None)
45}
46
47// Tests for parameter constructors
48#[test]
49fn test_rational_damping_noargs() {
50    // Check constructor of damping parameters for insufficient arguments
51    assert!(DFTD3RationalDampingParamBuilder::default().build().is_err());
52
53    let builder = DFTD3RationalDampingParamBuilder::default().a1(0.4).a2(5.0);
54    assert!(builder.build().is_err());
55
56    let builder = DFTD3RationalDampingParamBuilder::default().s8(1.0).a2(5.0);
57    assert!(builder.build().is_err());
58
59    let builder = DFTD3RationalDampingParamBuilder::default().s8(1.0).a1(0.4);
60    assert!(builder.build().is_err());
61}
62
63#[test]
64fn test_zero_damping_noargs() {
65    // Check constructor of damping parameters for insufficient arguments
66    assert!(DFTD3ZeroDampingParamBuilder::default().build().is_err());
67
68    let builder = DFTD3ZeroDampingParamBuilder::default().rs6(1.2);
69    assert!(builder.build().is_err());
70
71    let builder = DFTD3ZeroDampingParamBuilder::default().s8(1.0);
72    assert!(builder.build().is_err());
73}
74
75#[test]
76fn test_modified_rational_damping_noargs() {
77    // Check constructor of damping parameters for insufficient arguments
78    assert!(DFTD3ModifiedRationalDampingParamBuilder::default().build().is_err());
79
80    let builder = DFTD3ModifiedRationalDampingParamBuilder::default().a1(0.4).a2(5.0);
81    assert!(builder.build().is_err());
82
83    let builder = DFTD3ModifiedRationalDampingParamBuilder::default().s8(1.0).a2(5.0);
84    assert!(builder.build().is_err());
85
86    let builder = DFTD3ModifiedRationalDampingParamBuilder::default().s8(1.0).a1(0.4);
87    assert!(builder.build().is_err());
88}
89
90#[test]
91fn test_modified_zero_damping_noargs() {
92    // Check constructor of damping parameters for insufficient arguments
93    assert!(DFTD3ModifiedZeroDampingParamBuilder::default().build().is_err());
94
95    let builder = DFTD3ModifiedZeroDampingParamBuilder::default().rs6(1.2).bet(1.0);
96    assert!(builder.build().is_err());
97
98    let builder = DFTD3ModifiedZeroDampingParamBuilder::default().s8(1.0).bet(1.0);
99    assert!(builder.build().is_err());
100
101    let builder = DFTD3ModifiedZeroDampingParamBuilder::default().s8(1.0).rs6(1.2);
102    assert!(builder.build().is_err());
103}
104
105#[test]
106fn test_optimized_power_damping_noargs() {
107    // Check constructor of damping parameters for insufficient arguments
108    assert!(DFTD3OptimizedPowerDampingParamBuilder::default().build().is_err());
109
110    let builder = DFTD3OptimizedPowerDampingParamBuilder::default().a1(0.3).a2(4.2).bet(1.0);
111    assert!(builder.build().is_err());
112
113    let builder = DFTD3OptimizedPowerDampingParamBuilder::default().s8(1.0).a2(4.2).bet(1.0);
114    assert!(builder.build().is_err());
115
116    let builder = DFTD3OptimizedPowerDampingParamBuilder::default().s8(1.0).a1(0.3).bet(1.0);
117    assert!(builder.build().is_err());
118
119    let builder = DFTD3OptimizedPowerDampingParamBuilder::default().s8(1.0).a1(0.3).a2(4.2);
120    assert!(builder.build().is_err());
121}
122
123// Structure tests
124#[rstest]
125fn test_structure(numbers: Vec<usize>, positions: Vec<f64>) {
126    // check if the molecular structure data is working as expected.
127
128    // Constructor should raise an error for nuclear fusion input
129    let zero_positions = vec![0.0; 24 * 3];
130    assert!(DFTD3Model::new_f(&numbers, &zero_positions, None, None).is_err());
131
132    // The Rust struct should protect from garbage input like this
133    let bad_numbers = vec![1, 1, 1];
134    assert!(DFTD3Model::new_f(&bad_numbers, &positions, None, None).is_err());
135
136    // Also check for sane coordinate input
137    let bad_positions = vec![0.0; 7];
138    assert!(DFTD3Model::new_f(&numbers, &bad_positions, None, None).is_err());
139
140    // Construct real molecule
141    let mut inst = DFTD3Model::new(&numbers, &positions, None, None);
142
143    // Try to update a structure with missmatched coordinates
144    let bad_update_positions = vec![0.0; 7];
145    assert!(inst.update_f(&bad_update_positions, None).is_err());
146
147    // Try to add a missmatched lattice
148    let bad_lattice = vec![0.0; 7];
149    assert!(inst.update_f(&positions, Some(&bad_lattice)).is_err());
150
151    // Try to update a structure with nuclear fusion coordinates
152    let zero_update_positions = vec![0.0; numbers.len() * 3];
153    assert!(inst.update_f(&zero_update_positions, None).is_err());
154}
155
156// D3 tests
157#[rstest]
158#[case(true, -0.029489232932494884)]
159#[case(false, -0.029589132634178342)]
160fn test_pbe0_d3_bj(model: DFTD3Model, #[case] atm: bool, #[case] expected: f64) {
161    let param = DFTD3RationalDampingParam::load_param("pbe0", atm);
162    let res = model.get_dispersion(&param, false);
163    assert_abs_diff_eq!(res.energy, expected, epsilon = 1e-8);
164}
165
166#[rstest]
167#[case(true, -0.022714272555175656)]
168#[case(false, -0.022814172019166058)]
169fn test_b3lyp_d3_zero(model: DFTD3Model, #[case] atm: bool, #[case] expected: f64) {
170    let param = DFTD3ZeroDampingParam::load_param("b3lyp", atm);
171    let res = model.get_dispersion(&param, false);
172    assert_abs_diff_eq!(res.energy, expected, epsilon = 1e-8);
173}
174
175#[rstest]
176#[case(true, -0.06327406660942464)]
177#[case(false, -0.06337396631110809)]
178fn test_pbe_d3_bjm(model: DFTD3Model, #[case] atm: bool, #[case] expected: f64) {
179    let param = DFTD3ModifiedRationalDampingParam::load_param("pbe", atm);
180    let res = model.get_dispersion(&param, false);
181    assert_abs_diff_eq!(res.energy, expected, epsilon = 1e-8);
182}
183
184#[rstest]
185#[case(true, -0.026013316869036292)]
186#[case(false, -0.026113216333026695)]
187fn test_bp_d3_zerom(model: DFTD3Model, #[case] atm: bool, #[case] expected: f64) {
188    let param = DFTD3ModifiedZeroDampingParam::load_param("bp", atm);
189    let res = model.get_dispersion(&param, false);
190    assert_abs_diff_eq!(res.energy, expected, epsilon = 1e-8);
191}
192
193#[rstest]
194#[case(true, -0.07681029606751344)]
195#[case(false, -0.07691018779028679)]
196fn test_b97d_d3_op(model: DFTD3Model, #[case] atm: bool, #[case] expected: f64) {
197    let param = DFTD3OptimizedPowerDampingParam::load_param("b97d", atm);
198    let res = model.get_dispersion(&param, false);
199    assert_abs_diff_eq!(res.energy, expected, epsilon = 1e-8);
200}
201
202// GCP tests
203#[rstest]
204#[cfg(feature = "gcp")]
205fn test_gcp_empty(numbers: Vec<usize>, positions: Vec<f64>) {
206    let gcp = DFTD3GCP::new(&numbers, &positions, None, None, "", "");
207    let res = gcp.get_counterpoise(false);
208    assert_abs_diff_eq!(res.energy, 0.0, epsilon = 1e-8);
209}
210
211#[rstest]
212#[cfg(feature = "gcp")]
213#[case("b973c", -0.07653225860427701)]
214#[case("pbeh3c", 0.04977771585466725)]
215fn test_gcp_3c(
216    numbers: Vec<usize>,
217    positions: Vec<f64>,
218    #[case] method: &str,
219    #[case] expected: f64,
220) {
221    let gcp = DFTD3GCP::new(&numbers, &positions, None, None, method, "");
222    let res = gcp.get_counterpoise(false);
223    assert_abs_diff_eq!(res.energy, expected, epsilon = 1e-8);
224}
225
226fn test_pair_resolved() {
227    let thr = 1.0e-8;
228
229    let numbers = vec![16, 16, 16, 16, 16, 16, 16, 16];
230    #[rustfmt::skip]
231    let positions = vec![
232        -4.15128787379191,  1.71951973863958, -0.93066267097296,
233        -4.15128787379191, -1.71951973863958,  0.93066267097296,
234        -1.71951973863958, -4.15128787379191, -0.93066267097296,
235         1.71951973863958, -4.15128787379191,  0.93066267097296,
236         4.15128787379191, -1.71951973863958, -0.93066267097296,
237         4.15128787379191,  1.71951973863958,  0.93066267097296,
238         1.71951973863958,  4.15128787379191, -0.93066267097296,
239        -1.71951973863958,  4.15128787379191,  0.93066267097296,
240    ];
241
242    #[rustfmt::skip]
243    let pair_disp2 = [
244        [-0.00000000, -0.00153111, -0.00108052, -0.00032865, -0.00023796, -0.00032865, -0.00108052, -0.00153111],
245        [-0.00153111, -0.00000000, -0.00153111, -0.00108052, -0.00032865, -0.00023796, -0.00032865, -0.00108052],
246        [-0.00108052, -0.00153111, -0.00000000, -0.00153111, -0.00108052, -0.00032865, -0.00023796, -0.00032865],
247        [-0.00032865, -0.00108052, -0.00153111, -0.00000000, -0.00153111, -0.00108052, -0.00032865, -0.00023796],
248        [-0.00023796, -0.00032865, -0.00108052, -0.00153111, -0.00000000, -0.00153111, -0.00108052, -0.00032865],
249        [-0.00032865, -0.00023796, -0.00032865, -0.00108052, -0.00153111, -0.00000000, -0.00153111, -0.00108052],
250        [-0.00108052, -0.00032865, -0.00023796, -0.00032865, -0.00108052, -0.00153111, -0.00000000, -0.00153111],
251        [-0.00153111, -0.00108052, -0.00032865, -0.00023796, -0.00032865, -0.00108052, -0.00153111, -0.00000000],
252    ];
253
254    #[rustfmt::skip]
255    let pair_disp3 = [
256        [0.00000000e-00, 1.08616452e-07, 2.91526483e-07, 3.95872130e-07, 3.18133443e-07, 3.95872130e-07, 2.91526483e-07, 1.08616452e-07],
257        [1.08616452e-07, 0.00000000e-00, 1.08616452e-07, 2.91526483e-07, 3.95872130e-07, 3.18133443e-07, 3.95872130e-07, 2.91526483e-07],
258        [2.91526483e-07, 1.08616452e-07, 0.00000000e-00, 1.08616452e-07, 2.91526483e-07, 3.95872130e-07, 3.18133443e-07, 3.95872130e-07],
259        [3.95872130e-07, 2.91526483e-07, 1.08616452e-07, 0.00000000e-00, 1.08616452e-07, 2.91526483e-07, 3.95872130e-07, 3.18133443e-07],
260        [3.18133443e-07, 3.95872130e-07, 2.91526483e-07, 1.08616452e-07, 0.00000000e-00, 1.08616452e-07, 2.91526483e-07, 3.95872130e-07],
261        [3.95872130e-07, 3.18133443e-07, 3.95872130e-07, 2.91526483e-07, 1.08616452e-07, 0.00000000e-00, 1.08616452e-07, 2.91526483e-07],
262        [2.91526483e-07, 3.95872130e-07, 3.18133443e-07, 3.95872130e-07, 2.91526483e-07, 1.08616452e-07, 0.00000000e-00, 1.08616452e-07],
263        [1.08616452e-07, 2.91526483e-07, 3.95872130e-07, 3.18133443e-07, 3.95872130e-07, 2.91526483e-07, 1.08616452e-07, 0.00000000e-00],
264    ];
265
266    let model = DFTD3Model::new(&numbers, &positions, None, None);
267    let param = DFTD3RationalDampingParamBuilder::default()
268        .a1(0.5545)
269        .s8(2.2609)
270        .a2(3.2297)
271        .build()
272        .unwrap()
273        .new_param();
274
275    let res = model.get_pairwise_dispersion(&param);
276
277    // Flatten the expected matrices for comparison
278    let expected_pairs2: Vec<f64> = pair_disp2.into_iter().flatten().collect();
279    let expected_pairs3: Vec<f64> = pair_disp3.into_iter().flatten().collect();
280
281    res.pair_energy2.iter().zip(expected_pairs2.iter()).for_each(|(x, y)| {
282        assert_abs_diff_eq!(x, y, epsilon = thr);
283    });
284    res.pair_energy3.iter().zip(expected_pairs3.iter()).for_each(|(x, y)| {
285        assert_abs_diff_eq!(x, y, epsilon = thr);
286    });
287}
288
289fn main() {
290    test_pair_resolved();
291}
292
293#[test]
294fn test() {
295    test_pair_resolved();
296}