use super::backend_report::IsosurfaceReport;
use super::marching_cubes::{marching_cubes_cpu, smooth_mesh_normals, McOutput};
use super::orbital_grid::{evaluate_orbital_with_report, GridParams};
use crate::scf::basis::BasisSet;
use nalgebra::DMatrix;
#[derive(Debug, Clone)]
pub struct IsosurfaceConfig {
pub spacing: f64,
pub padding: f64,
pub isovalue: f64,
pub both_lobes: bool,
pub smooth_normals: bool,
}
impl Default for IsosurfaceConfig {
fn default() -> Self {
Self {
spacing: 0.3,
padding: 5.0,
isovalue: 0.02,
both_lobes: true,
smooth_normals: true,
}
}
}
#[derive(Debug, Clone)]
pub struct OrbitalIsosurface {
pub positive_lobe: McOutput,
pub negative_lobe: McOutput,
pub orbital_index: usize,
pub isovalue: f64,
pub total_triangles: usize,
}
pub fn generate_orbital_isosurface(
basis: &BasisSet,
mo_coefficients: &DMatrix<f64>,
orbital_index: usize,
positions: &[[f64; 3]],
config: &IsosurfaceConfig,
) -> (OrbitalIsosurface, IsosurfaceReport) {
let params = GridParams::from_molecule(positions, config.spacing, config.padding);
let (grid, grid_report) =
evaluate_orbital_with_report(basis, mo_coefficients, orbital_index, ¶ms);
let mut positive = marching_cubes_cpu(&grid.values, &grid.params, config.isovalue);
let mut negative = if config.both_lobes {
let neg_values: Vec<f64> = grid.values.iter().map(|v| -v).collect();
marching_cubes_cpu(&neg_values, &grid.params, config.isovalue)
} else {
McOutput {
vertices: Vec::new(),
normals: Vec::new(),
indices: Vec::new(),
n_triangles: 0,
}
};
if config.smooth_normals {
smooth_mesh_normals(&mut positive);
if config.both_lobes {
smooth_mesh_normals(&mut negative);
}
}
let total = positive.n_triangles + negative.n_triangles;
let iso = OrbitalIsosurface {
positive_lobe: positive,
negative_lobe: negative,
orbital_index,
isovalue: config.isovalue,
total_triangles: total,
};
let report = IsosurfaceReport {
grid_backend: grid_report.backend,
grid_used_gpu: grid_report.used_gpu,
n_triangles: total,
isovalue: config.isovalue,
};
(iso, report)
}
pub fn generate_homo_isosurface(
basis: &BasisSet,
mo_coefficients: &DMatrix<f64>,
n_electrons: usize,
positions: &[[f64; 3]],
config: &IsosurfaceConfig,
) -> (OrbitalIsosurface, IsosurfaceReport) {
let n_occ = n_electrons / 2;
let homo_index = if n_occ > 0 { n_occ - 1 } else { 0 };
generate_orbital_isosurface(basis, mo_coefficients, homo_index, positions, config)
}
pub fn generate_lumo_isosurface(
basis: &BasisSet,
mo_coefficients: &DMatrix<f64>,
n_electrons: usize,
positions: &[[f64; 3]],
config: &IsosurfaceConfig,
) -> (OrbitalIsosurface, IsosurfaceReport) {
let lumo_index = n_electrons / 2;
generate_orbital_isosurface(basis, mo_coefficients, lumo_index, positions, config)
}
pub fn estimate_mesh_complexity(
positions: &[[f64; 3]],
spacing: f64,
padding: f64,
) -> (usize, usize) {
let params = GridParams::from_molecule(positions, spacing, padding);
let n_voxels = (params.dimensions[0].saturating_sub(1))
* (params.dimensions[1].saturating_sub(1))
* (params.dimensions[2].saturating_sub(1));
let est_triangles = n_voxels / 20;
(est_triangles, est_triangles * 3)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::scf::basis::BasisSet;
#[test]
fn test_isosurface_config_default() {
let config = IsosurfaceConfig::default();
assert!(config.spacing > 0.0);
assert!(config.isovalue > 0.0);
assert!(config.both_lobes);
assert!(config.smooth_normals);
}
#[test]
fn test_estimate_mesh_complexity() {
let positions = vec![[0.0, 0.0, 0.0], [3.0, 0.0, 0.0]];
let (tri, vert) = estimate_mesh_complexity(&positions, 0.5, 3.0);
assert!(tri > 0);
assert_eq!(vert, tri * 3);
}
#[test]
fn test_generate_orbital_isosurface_h2() {
let elements = [1u8, 1];
let positions = [[0.0, 0.0, 0.0], [0.0, 0.0, 1.4]];
let basis = BasisSet::sto3g(&elements, &positions);
let n = basis.n_basis;
let mut coeffs = DMatrix::zeros(n, n);
let c = 1.0 / (2.0f64).sqrt();
coeffs[(0, 0)] = c;
if n > 1 {
coeffs[(1, 0)] = c;
}
let config = IsosurfaceConfig {
spacing: 0.4,
padding: 3.0,
isovalue: 0.005,
both_lobes: true,
smooth_normals: false,
};
let (iso, report) = generate_orbital_isosurface(&basis, &coeffs, 0, &positions, &config);
assert!(!report.grid_backend.is_empty());
assert!((report.isovalue - 0.005).abs() < 1e-10);
assert_eq!(iso.orbital_index, 0);
}
#[test]
fn test_generate_homo_lumo() {
let elements = [1u8, 1];
let positions = [[0.0, 0.0, 0.0], [0.0, 0.0, 1.4]];
let basis = BasisSet::sto3g(&elements, &positions);
let n = basis.n_basis;
let mut coeffs = DMatrix::zeros(n, n);
coeffs[(0, 0)] = 1.0 / (2.0f64).sqrt();
if n > 1 {
coeffs[(1, 0)] = 1.0 / (2.0f64).sqrt();
coeffs[(0, 1)] = 1.0 / (2.0f64).sqrt();
coeffs[(1, 1)] = -1.0 / (2.0f64).sqrt();
}
let config = IsosurfaceConfig {
spacing: 0.8,
padding: 2.0,
isovalue: 0.01,
both_lobes: false,
smooth_normals: false,
};
let (homo, _) = generate_homo_isosurface(&basis, &coeffs, 2, &positions, &config);
assert_eq!(homo.orbital_index, 0);
let (lumo, _) = generate_lumo_isosurface(&basis, &coeffs, 2, &positions, &config);
assert_eq!(lumo.orbital_index, 1); }
}