1use super::basis::{build_sto3g_basis, ANG_TO_BOHR};
7use super::cis::{compute_cis, CisResult};
8use super::d3::compute_d3_energy;
9use super::fock::nuclear_repulsion;
10use super::gcp::compute_gcp;
11use super::integrals::compute_eris;
12use super::nuclear::compute_nuclear_matrix;
13use super::overlap_kin::{compute_kinetic_matrix, compute_overlap_matrix};
14use super::scf::{solve_scf, ScfConfig};
15use super::srb::compute_srb;
16use serde::{Deserialize, Serialize};
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct HfConfig {
21 pub max_scf_iter: usize,
23 pub diis_size: usize,
25 pub n_cis_states: usize,
27 pub corrections: bool,
29}
30
31impl Default for HfConfig {
32 fn default() -> Self {
33 HfConfig {
34 max_scf_iter: 100,
35 diis_size: 6,
36 n_cis_states: 5,
37 corrections: true,
38 }
39 }
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
44pub struct Hf3cResult {
45 pub energy: f64,
47 pub hf_energy: f64,
49 pub nuclear_repulsion: f64,
51 pub d3_energy: f64,
53 pub gcp_energy: f64,
55 pub srb_energy: f64,
57 pub orbital_energies: Vec<f64>,
59 pub scf_iterations: usize,
61 pub converged: bool,
63 pub cis: Option<CisResult>,
65}
66
67pub fn solve_hf3c(
69 elements: &[u8],
70 positions: &[[f64; 3]],
71 config: &HfConfig,
72) -> Result<Hf3cResult, String> {
73 if elements.len() != positions.len() {
74 return Err("elements/positions length mismatch".to_string());
75 }
76 if elements.is_empty() {
77 return Err("empty molecule".to_string());
78 }
79
80 let pos_bohr: Vec<[f64; 3]> = positions
82 .iter()
83 .map(|p| [p[0] * ANG_TO_BOHR, p[1] * ANG_TO_BOHR, p[2] * ANG_TO_BOHR])
84 .collect();
85
86 let basis = build_sto3g_basis(elements, positions);
88 let n_basis = basis.n_basis();
89
90 let n_electrons: usize = elements.iter().map(|&z| z as usize).sum();
92
93 let s_mat = compute_overlap_matrix(&basis);
95 let t_mat = compute_kinetic_matrix(&basis);
96 let v_mat = compute_nuclear_matrix(&basis, elements, &pos_bohr);
97 let h_core = &t_mat + &v_mat;
98
99 let eris = compute_eris(&basis);
101
102 let scf_config = ScfConfig {
104 max_iter: config.max_scf_iter,
105 diis_size: config.diis_size,
106 ..ScfConfig::default()
107 };
108 let scf_result = solve_scf(&h_core, &s_mat, &eris, n_electrons, &scf_config);
109
110 let e_nuc = nuclear_repulsion(elements, &pos_bohr);
112
113 let (d3_e, gcp_e, srb_e) = if config.corrections {
115 (
116 compute_d3_energy(elements, &pos_bohr).energy,
117 compute_gcp(elements, &pos_bohr),
118 compute_srb(elements, &pos_bohr),
119 )
120 } else {
121 (0.0, 0.0, 0.0)
122 };
123
124 let total = scf_result.energy + e_nuc + d3_e + gcp_e + srb_e;
125
126 let cis = if config.n_cis_states > 0 && scf_result.converged {
128 let n_occ = n_electrons / 2;
129 Some(compute_cis(
130 &scf_result.orbital_energies,
131 &scf_result.coefficients,
132 &eris,
133 n_basis,
134 n_occ,
135 config.n_cis_states,
136 ))
137 } else {
138 None
139 };
140
141 Ok(Hf3cResult {
142 energy: total,
143 hf_energy: scf_result.energy + e_nuc,
144 nuclear_repulsion: e_nuc,
145 d3_energy: d3_e,
146 gcp_energy: gcp_e,
147 srb_energy: srb_e,
148 orbital_energies: scf_result.orbital_energies,
149 scf_iterations: scf_result.iterations,
150 converged: scf_result.converged,
151 cis,
152 })
153}
154
155#[cfg(feature = "parallel")]
157pub fn solve_hf3c_batch(
158 molecules: &[(&[u8], &[[f64; 3]])],
159 config: &HfConfig,
160) -> Vec<Result<Hf3cResult, String>> {
161 use rayon::prelude::*;
162 molecules
163 .par_iter()
164 .map(|(els, pos)| solve_hf3c(els, pos, config))
165 .collect()
166}
167
168#[cfg(not(feature = "parallel"))]
170pub fn solve_hf3c_batch(
171 molecules: &[(&[u8], &[[f64; 3]])],
172 config: &HfConfig,
173) -> Vec<Result<Hf3cResult, String>> {
174 molecules
175 .iter()
176 .map(|(els, pos)| solve_hf3c(els, pos, config))
177 .collect()
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183
184 #[test]
185 fn test_h2_hf3c() {
186 let elements = [1u8, 1];
187 let positions = [[0.0, 0.0, 0.0], [0.0, 0.0, 0.74]];
188 let config = HfConfig {
189 n_cis_states: 0,
190 ..Default::default()
191 };
192 let result = solve_hf3c(&elements, &positions, &config).unwrap();
193 assert!(result.energy.is_finite(), "Energy should be finite");
194 assert!(result.energy < 0.0, "H2 total energy should be negative");
195 }
196
197 #[test]
198 fn test_water_hf3c() {
199 let elements = [8u8, 1, 1];
200 let positions = [
201 [0.0, 0.0, 0.117],
202 [0.0, 0.757, -0.469],
203 [0.0, -0.757, -0.469],
204 ];
205 let result = solve_hf3c(&elements, &positions, &HfConfig::default()).unwrap();
206 assert!(result.energy.is_finite());
207 assert!(result.orbital_energies.len() == 7); }
209}