1use super::backend_report::IsosurfaceReport;
8use super::marching_cubes::{marching_cubes_cpu, smooth_mesh_normals, McOutput};
9use super::orbital_grid::{evaluate_orbital_with_report, GridParams};
10use crate::scf::basis::BasisSet;
11use nalgebra::DMatrix;
12
13#[derive(Debug, Clone)]
15pub struct IsosurfaceConfig {
16 pub spacing: f64,
18 pub padding: f64,
20 pub isovalue: f64,
22 pub both_lobes: bool,
24 pub smooth_normals: bool,
26}
27
28impl Default for IsosurfaceConfig {
29 fn default() -> Self {
30 Self {
31 spacing: 0.3,
32 padding: 5.0,
33 isovalue: 0.02,
34 both_lobes: true,
35 smooth_normals: true,
36 }
37 }
38}
39
40#[derive(Debug, Clone)]
42pub struct OrbitalIsosurface {
43 pub positive_lobe: McOutput,
44 pub negative_lobe: McOutput,
45 pub orbital_index: usize,
46 pub isovalue: f64,
47 pub total_triangles: usize,
48}
49
50pub fn generate_orbital_isosurface(
55 basis: &BasisSet,
56 mo_coefficients: &DMatrix<f64>,
57 orbital_index: usize,
58 positions: &[[f64; 3]],
59 config: &IsosurfaceConfig,
60) -> (OrbitalIsosurface, IsosurfaceReport) {
61 let params = GridParams::from_molecule(positions, config.spacing, config.padding);
62
63 let (grid, grid_report) =
64 evaluate_orbital_with_report(basis, mo_coefficients, orbital_index, ¶ms);
65
66 let mut positive = marching_cubes_cpu(&grid.values, &grid.params, config.isovalue);
67
68 let mut negative = if config.both_lobes {
69 let neg_values: Vec<f64> = grid.values.iter().map(|v| -v).collect();
70 marching_cubes_cpu(&neg_values, &grid.params, config.isovalue)
71 } else {
72 McOutput {
73 vertices: Vec::new(),
74 normals: Vec::new(),
75 indices: Vec::new(),
76 n_triangles: 0,
77 }
78 };
79
80 if config.smooth_normals {
81 smooth_mesh_normals(&mut positive);
82 if config.both_lobes {
83 smooth_mesh_normals(&mut negative);
84 }
85 }
86
87 let total = positive.n_triangles + negative.n_triangles;
88
89 let iso = OrbitalIsosurface {
90 positive_lobe: positive,
91 negative_lobe: negative,
92 orbital_index,
93 isovalue: config.isovalue,
94 total_triangles: total,
95 };
96
97 let report = IsosurfaceReport {
98 grid_backend: grid_report.backend,
99 grid_used_gpu: grid_report.used_gpu,
100 n_triangles: total,
101 isovalue: config.isovalue,
102 };
103
104 (iso, report)
105}
106
107pub fn generate_homo_isosurface(
109 basis: &BasisSet,
110 mo_coefficients: &DMatrix<f64>,
111 n_electrons: usize,
112 positions: &[[f64; 3]],
113 config: &IsosurfaceConfig,
114) -> (OrbitalIsosurface, IsosurfaceReport) {
115 let n_occ = n_electrons / 2;
116 let homo_index = if n_occ > 0 { n_occ - 1 } else { 0 };
117 generate_orbital_isosurface(basis, mo_coefficients, homo_index, positions, config)
118}
119
120pub fn generate_lumo_isosurface(
122 basis: &BasisSet,
123 mo_coefficients: &DMatrix<f64>,
124 n_electrons: usize,
125 positions: &[[f64; 3]],
126 config: &IsosurfaceConfig,
127) -> (OrbitalIsosurface, IsosurfaceReport) {
128 let lumo_index = n_electrons / 2;
129 generate_orbital_isosurface(basis, mo_coefficients, lumo_index, positions, config)
130}
131
132pub fn estimate_mesh_complexity(
134 positions: &[[f64; 3]],
135 spacing: f64,
136 padding: f64,
137) -> (usize, usize) {
138 let params = GridParams::from_molecule(positions, spacing, padding);
139 let n_voxels = (params.dimensions[0].saturating_sub(1))
140 * (params.dimensions[1].saturating_sub(1))
141 * (params.dimensions[2].saturating_sub(1));
142 let est_triangles = n_voxels / 20;
144 (est_triangles, est_triangles * 3)
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use crate::scf::basis::BasisSet;
151
152 #[test]
153 fn test_isosurface_config_default() {
154 let config = IsosurfaceConfig::default();
155 assert!(config.spacing > 0.0);
156 assert!(config.isovalue > 0.0);
157 assert!(config.both_lobes);
158 assert!(config.smooth_normals);
159 }
160
161 #[test]
162 fn test_estimate_mesh_complexity() {
163 let positions = vec![[0.0, 0.0, 0.0], [3.0, 0.0, 0.0]];
164 let (tri, vert) = estimate_mesh_complexity(&positions, 0.5, 3.0);
165 assert!(tri > 0);
166 assert_eq!(vert, tri * 3);
167 }
168
169 #[test]
170 fn test_generate_orbital_isosurface_h2() {
171 let elements = [1u8, 1];
172 let positions = [[0.0, 0.0, 0.0], [0.0, 0.0, 1.4]];
173 let basis = BasisSet::sto3g(&elements, &positions);
174
175 let n = basis.n_basis;
176 let mut coeffs = DMatrix::zeros(n, n);
177 let c = 1.0 / (2.0f64).sqrt();
178 coeffs[(0, 0)] = c;
179 if n > 1 {
180 coeffs[(1, 0)] = c;
181 }
182
183 let config = IsosurfaceConfig {
184 spacing: 0.4,
185 padding: 3.0,
186 isovalue: 0.005,
187 both_lobes: true,
188 smooth_normals: false,
189 };
190
191 let (iso, report) = generate_orbital_isosurface(&basis, &coeffs, 0, &positions, &config);
192 assert!(!report.grid_backend.is_empty());
193 assert!((report.isovalue - 0.005).abs() < 1e-10);
194 assert_eq!(iso.orbital_index, 0);
195 }
197
198 #[test]
199 fn test_generate_homo_lumo() {
200 let elements = [1u8, 1];
201 let positions = [[0.0, 0.0, 0.0], [0.0, 0.0, 1.4]];
202 let basis = BasisSet::sto3g(&elements, &positions);
203
204 let n = basis.n_basis;
205 let mut coeffs = DMatrix::zeros(n, n);
206 coeffs[(0, 0)] = 1.0 / (2.0f64).sqrt();
207 if n > 1 {
208 coeffs[(1, 0)] = 1.0 / (2.0f64).sqrt();
209 coeffs[(0, 1)] = 1.0 / (2.0f64).sqrt();
210 coeffs[(1, 1)] = -1.0 / (2.0f64).sqrt();
211 }
212
213 let config = IsosurfaceConfig {
214 spacing: 0.8,
215 padding: 2.0,
216 isovalue: 0.01,
217 both_lobes: false,
218 smooth_normals: false,
219 };
220
221 let (homo, _) = generate_homo_isosurface(&basis, &coeffs, 2, &positions, &config);
222 assert_eq!(homo.orbital_index, 0); let (lumo, _) = generate_lumo_isosurface(&basis, &coeffs, 2, &positions, &config);
225 assert_eq!(lumo.orbital_index, 1); }
227}