Skip to main content

gasteiger_rs/
lib.rs

1pub mod traits;
2pub mod parameters;
3pub mod solver;
4
5pub use traits::{GasteigerAtom, GasteigerBond};
6pub use solver::GasteigerSolver;
7
8#[cfg(test)]
9mod tests {
10    use super::*;
11
12    struct MockAtom {
13        name: &'static str,
14        element: usize,
15        formal_charge: f32,
16    }
17
18    impl GasteigerAtom for MockAtom {
19        fn atomic_number(&self) -> usize { self.element }
20        fn formal_charge(&self) -> f32 { self.formal_charge }
21    }
22
23    struct MockBond {
24        pair: (usize, usize),
25        order: f32,
26    }
27
28    impl GasteigerBond for MockBond {
29        fn atom_indices(&self) -> (usize, usize) { self.pair }
30        fn bond_order(&self) -> f32 { self.order }
31    }
32
33    fn print_charges(atoms: &[MockAtom], charges: &[f64]) {
34        println!("{:<5} {:<10} {:<15}", "Idx", "Element", "Partial Charge");
35        for (i, (atom, &charge)) in atoms.iter().zip(charges.iter()).enumerate() {
36            println!("{:<5} {:<10} {:<15.6}", i, atom.name, charge);
37        }
38        let total: f64 = charges.iter().sum();
39        println!("Total Charge: {:.6}", total);
40    }
41
42    #[test]
43    fn test_triple_bonds() {
44        let solver = GasteigerSolver::default();
45
46        // 1. Acetylene (HC#CH)
47        let atoms_ace = vec![
48            MockAtom { name: "C1", element: 6, formal_charge: 0.0 }, // Sp
49            MockAtom { name: "C2", element: 6, formal_charge: 0.0 }, // Sp
50            MockAtom { name: "H1", element: 1, formal_charge: 0.0 },
51            MockAtom { name: "H2", element: 1, formal_charge: 0.0 },
52        ];
53        let bonds_ace = vec![
54            MockBond { pair: (0, 1), order: 3.0 }, // Triple bond
55            MockBond { pair: (0, 2), order: 1.0 },
56            MockBond { pair: (1, 3), order: 1.0 },
57        ];
58        let charges_ace = solver.compute_charges(&atoms_ace, &bonds_ace);
59        println!("\n--- Acetylene (HC#CH) ---");
60        print_charges(&atoms_ace, &charges_ace);
61        assert!(charges_ace.iter().sum::<f64>().abs() < 1e-6);
62
63        // 2. Acetonitrile (CH3-C#N)
64        let atoms_nit = vec![
65            MockAtom { name: "C_me", element: 6, formal_charge: 0.0 }, // Sp3
66            MockAtom { name: "C_cn", element: 6, formal_charge: 0.0 }, // Sp
67            MockAtom { name: "N",    element: 7, formal_charge: 0.0 }, // Sp
68            MockAtom { name: "H1",   element: 1, formal_charge: 0.0 },
69            MockAtom { name: "H2",   element: 1, formal_charge: 0.0 },
70            MockAtom { name: "H3",   element: 1, formal_charge: 0.0 },
71        ];
72        let bonds_nit = vec![
73            MockBond { pair: (0, 1), order: 1.0 },
74            MockBond { pair: (1, 2), order: 3.0 }, // C#N
75            MockBond { pair: (0, 3), order: 1.0 },
76            MockBond { pair: (0, 4), order: 1.0 },
77            MockBond { pair: (0, 5), order: 1.0 },
78        ];
79        let charges_nit = solver.compute_charges(&atoms_nit, &bonds_nit);
80        println!("\n--- Acetonitrile (CH3-CN) ---");
81        print_charges(&atoms_nit, &charges_nit);
82        assert!(charges_nit.iter().sum::<f64>().abs() < 1e-6);
83    }
84
85    #[test]
86    fn test_ethene_and_benzene() {
87        let solver = GasteigerSolver::default();
88
89        let atoms_ethene = vec![
90            MockAtom { name: "C1", element: 6, formal_charge: 0.0 },
91            MockAtom { name: "C2", element: 6, formal_charge: 0.0 },
92            MockAtom { name: "H1", element: 1, formal_charge: 0.0 },
93            MockAtom { name: "H2", element: 1, formal_charge: 0.0 },
94            MockAtom { name: "H3", element: 1, formal_charge: 0.0 },
95            MockAtom { name: "H4", element: 1, formal_charge: 0.0 },
96        ];
97        let bonds_ethene = vec![
98            MockBond { pair: (0, 1), order: 2.0 },
99            MockBond { pair: (0, 2), order: 1.0 },
100            MockBond { pair: (0, 3), order: 1.0 },
101            MockBond { pair: (1, 4), order: 1.0 },
102            MockBond { pair: (1, 5), order: 1.0 },
103        ];
104        let charges_ethene = solver.compute_charges(&atoms_ethene, &bonds_ethene);
105        println!("\n--- Ethene (CH2=CH2) ---");
106        print_charges(&atoms_ethene, &charges_ethene);
107        assert!(charges_ethene.iter().sum::<f64>().abs() < 1e-6);
108
109        let mut atoms_benzene = Vec::new();
110        for _ in 0..6 { atoms_benzene.push(MockAtom { name: "C", element: 6, formal_charge: 0.0 }); }
111        for _ in 0..6 { atoms_benzene.push(MockAtom { name: "H", element: 1, formal_charge: 0.0 }); }
112        
113        let mut bonds_benzene = Vec::new();
114        for i in 0..6 {
115            bonds_benzene.push(MockBond { pair: (i, (i + 1) % 6), order: 1.5 });
116            bonds_benzene.push(MockBond { pair: (i, i + 6), order: 1.0 });
117        }
118        let charges_benzene = solver.compute_charges(&atoms_benzene, &bonds_benzene);
119        println!("\n--- Benzene (C6H6) ---");
120        print_charges(&atoms_benzene, &charges_benzene);
121        assert!(charges_benzene.iter().sum::<f64>().abs() < 1e-6);
122    }
123
124    #[test]
125    fn test_sulfur_compounds() {
126        let solver = GasteigerSolver::default();
127        let atoms = vec![
128            MockAtom { name: "S", element: 16, formal_charge: 0.0 },
129            MockAtom { name: "C1", element: 6, formal_charge: 0.0 },
130            MockAtom { name: "C2", element: 6, formal_charge: 0.0 },
131        ];
132        let bonds = vec![
133            MockBond { pair: (0, 1), order: 1.0 },
134            MockBond { pair: (0, 2), order: 1.0 },
135        ];
136        let charges = solver.compute_charges(&atoms, &bonds);
137        println!("\n--- Dimethyl Sulfide (CH3-S-CH3) ---");
138        print_charges(&atoms, &charges);
139
140        let atoms_thio = vec![
141            MockAtom { name: "S", element: 16, formal_charge: 0.0 },
142            MockAtom { name: "C", element: 6, formal_charge: 0.0 },
143        ];
144        let bonds_thio = vec![
145            MockBond { pair: (0, 1), order: 2.0 },
146        ];
147        let charges_thio = solver.compute_charges(&atoms_thio, &bonds_thio);
148        println!("\n--- Thioformaldehyde (CH2=S) ---");
149        print_charges(&atoms_thio, &charges_thio);
150        assert!(charges_thio[0] < 0.0);
151    }
152
153    #[test]
154    fn test_unknown_element_handling() {
155        let atoms = vec![
156            MockAtom { name: "C", element: 6, formal_charge: 0.0 },
157            MockAtom { name: "H1", element: 1, formal_charge: 0.0 },
158            MockAtom { name: "Pd", element: 46, formal_charge: 0.0 },
159        ];
160        let bonds = vec![
161            MockBond { pair: (0, 1), order: 1.0 },
162            MockBond { pair: (0, 2), order: 1.0 },
163        ];
164        let solver = GasteigerSolver::default();
165        let charges = solver.compute_charges(&atoms, &bonds);
166        println!("\n--- Unknown Element Test (C-H + C-Pd) ---");
167        print_charges(&atoms, &charges);
168        assert!(charges[2].abs() < 1e-10);
169    }
170
171    #[test]
172    fn test_methane_charges() {
173        let atoms = vec![
174            MockAtom { name: "C", element: 6, formal_charge: 0.0 },
175            MockAtom { name: "H1", element: 1, formal_charge: 0.0 },
176            MockAtom { name: "H2", element: 1, formal_charge: 0.0 },
177            MockAtom { name: "H3", element: 1, formal_charge: 0.0 },
178            MockAtom { name: "H4", element: 1, formal_charge: 0.0 },
179        ];
180        let bonds = vec![
181            MockBond { pair: (0, 1), order: 1.0 },
182            MockBond { pair: (0, 2), order: 1.0 },
183            MockBond { pair: (0, 3), order: 1.0 },
184            MockBond { pair: (0, 4), order: 1.0 },
185        ];
186        let solver = GasteigerSolver::default();
187        let charges = solver.compute_charges(&atoms, &bonds);
188        println!("\n--- Methane (CH4) ---");
189        print_charges(&atoms, &charges);
190        assert!(charges[0] < 0.0);
191    }
192
193    #[test]
194    fn test_water_charges() {
195        let atoms = vec![
196            MockAtom { name: "O", element: 8, formal_charge: 0.0 },
197            MockAtom { name: "H1", element: 1, formal_charge: 0.0 },
198            MockAtom { name: "H2", element: 1, formal_charge: 0.0 },
199        ];
200        let bonds = vec![
201            MockBond { pair: (0, 1), order: 1.0 },
202            MockBond { pair: (0, 2), order: 1.0 },
203        ];
204        let solver = GasteigerSolver::default();
205        let charges = solver.compute_charges(&atoms, &bonds);
206        println!("\n--- Water (H2O) ---");
207        print_charges(&atoms, &charges);
208        assert!(charges[0] < 0.0);
209    }
210
211    #[test]
212    fn test_multi_molecule_system() {
213        let atoms = vec![
214            MockAtom { name: "C", element: 6, formal_charge: 0.0 },
215            MockAtom { name: "H1", element: 1, formal_charge: 0.0 },
216            MockAtom { name: "H2", element: 1, formal_charge: 0.0 },
217            MockAtom { name: "H3", element: 1, formal_charge: 0.0 },
218            MockAtom { name: "H4", element: 1, formal_charge: 0.0 },
219            MockAtom { name: "O", element: 8, formal_charge: 0.0 },
220            MockAtom { name: "Hw1", element: 1, formal_charge: 0.0 },
221            MockAtom { name: "Hw2", element: 1, formal_charge: 0.0 },
222        ];
223        let bonds = vec![
224            MockBond { pair: (0, 1), order: 1.0 },
225            MockBond { pair: (0, 2), order: 1.0 },
226            MockBond { pair: (0, 3), order: 1.0 },
227            MockBond { pair: (0, 4), order: 1.0 },
228            MockBond { pair: (5, 6), order: 1.0 },
229            MockBond { pair: (5, 7), order: 1.0 },
230        ];
231        let solver = GasteigerSolver::default();
232        let charges = solver.compute_charges(&atoms, &bonds);
233        println!("\n--- Multi-molecule (CH4 + H2O) ---");
234        print_charges(&atoms, &charges);
235        let methane_sum: f64 = charges[0..5].iter().sum();
236        let water_sum: f64 = charges[5..8].iter().sum();
237        assert!(methane_sum.abs() < 1e-6);
238        assert!(water_sum.abs() < 1e-6);
239    }
240
241    #[test]
242    fn test_ion_charge_conservation() {
243        let atoms = vec![
244            MockAtom { name: "N+", element: 7, formal_charge: 1.0 },
245            MockAtom { name: "H1", element: 1, formal_charge: 0.0 },
246            MockAtom { name: "H2", element: 1, formal_charge: 0.0 },
247            MockAtom { name: "H3", element: 1, formal_charge: 0.0 },
248            MockAtom { name: "H4", element: 1, formal_charge: 0.0 },
249        ];
250        let bonds = vec![
251            MockBond { pair: (0, 1), order: 1.0 },
252            MockBond { pair: (0, 2), order: 1.0 },
253            MockBond { pair: (0, 3), order: 1.0 },
254            MockBond { pair: (0, 4), order: 1.0 },
255        ];
256        let solver = GasteigerSolver::default();
257        let charges = solver.compute_charges(&atoms, &bonds);
258        println!("\n--- Ammonium Ion (NH4+) ---");
259        print_charges(&atoms, &charges);
260        let total_charge: f64 = charges.iter().sum();
261        assert!((total_charge - 1.0).abs() < 1e-6);
262    }
263}