use sci_form::eht::{
self, evaluate_orbital_on_grid, marching_cubes, solve_eht, EhtResult, IsosurfaceMesh,
};
fn h2_molecule() -> (Vec<u8>, Vec<[f64; 3]>) {
let elements = vec![1, 1];
let positions = vec![[0.0, 0.0, 0.0], [0.74, 0.0, 0.0]];
(elements, positions)
}
fn water_molecule() -> (Vec<u8>, Vec<[f64; 3]>) {
let elements = vec![8, 1, 1];
let positions = vec![[0.0, 0.0, 0.0], [0.757, 0.586, 0.0], [-0.757, 0.586, 0.0]];
(elements, positions)
}
fn methane_molecule() -> (Vec<u8>, Vec<[f64; 3]>) {
let elements = vec![6, 1, 1, 1, 1];
let positions = vec![
[0.0, 0.0, 0.0],
[0.629, 0.629, 0.629],
[-0.629, -0.629, 0.629],
[-0.629, 0.629, -0.629],
[0.629, -0.629, -0.629],
];
(elements, positions)
}
#[test]
fn test_h2_full_pipeline() {
let (elems, pos) = h2_molecule();
let result = solve_eht(&elems, &pos, None).expect("H₂ EHT should succeed");
assert_eq!(result.energies.len(), 2, "H₂ should have 2 MOs");
assert!(
result.homo_energy < result.lumo_energy,
"HOMO ({}) should be below LUMO ({})",
result.homo_energy,
result.lumo_energy
);
assert!(
result.gap > 0.0,
"H₂ gap should be positive: {}",
result.gap
);
assert_eq!(result.homo_index, 0);
assert_eq!(result.lumo_index, 1);
}
#[test]
fn test_water_full_pipeline() {
let (elems, pos) = water_molecule();
let result = solve_eht(&elems, &pos, None).expect("H₂O EHT should succeed");
assert_eq!(result.energies.len(), 6, "H₂O should have 6 MOs");
assert_eq!(result.n_electrons, 8);
assert_eq!(result.homo_index, 3); assert_eq!(result.lumo_index, 4);
for i in 1..result.energies.len() {
assert!(
result.energies[i] >= result.energies[i - 1],
"Energies not sorted: {} > {} at index {}",
result.energies[i - 1],
result.energies[i],
i
);
}
}
#[test]
fn test_methane_full_pipeline() {
let (elems, pos) = methane_molecule();
let result = solve_eht(&elems, &pos, None).expect("CH₄ EHT should succeed");
assert_eq!(result.energies.len(), 8, "CH₄ should have 8 MOs");
assert_eq!(result.n_electrons, 8);
assert_eq!(result.homo_index, 3);
assert_eq!(result.lumo_index, 4);
assert!(
result.energies[0] < -10.0,
"Lowest MO should be deep bonding: {}",
result.energies[0]
);
}
#[test]
fn test_h2_orbital_to_mesh() {
let (elems, pos) = h2_molecule();
let result = solve_eht(&elems, &pos, None).unwrap();
let basis = eht::basis::build_basis(&elems, &pos);
let grid = evaluate_orbital_on_grid(&basis, &result.coefficients, 0, &pos, 0.2, 3.0);
assert!(!grid.values.is_empty(), "Grid should have values");
assert_eq!(
grid.values.len(),
grid.dims[0] * grid.dims[1] * grid.dims[2],
"Grid size mismatch"
);
let max_val = grid
.values
.iter()
.cloned()
.fold(f32::NEG_INFINITY, f32::max);
let min_val = grid.values.iter().cloned().fold(f32::INFINITY, f32::min);
let max_abs = max_val.abs().max(min_val.abs());
assert!(
max_abs > 1e-6,
"Bonding orbital should have nonzero density, max_abs={}",
max_abs
);
let iso = if max_val.abs() > min_val.abs() {
max_val * 0.5
} else {
min_val * 0.5
};
let mesh = marching_cubes(&grid, iso);
assert!(
mesh.num_triangles > 0,
"Bonding orbital isosurface should produce triangles"
);
assert_eq!(mesh.vertices.len(), mesh.normals.len());
assert_eq!(mesh.indices.len(), mesh.num_triangles * 3);
}
#[test]
fn test_water_homo_visualization() {
let (elems, pos) = water_molecule();
let result = solve_eht(&elems, &pos, None).unwrap();
let basis = eht::basis::build_basis(&elems, &pos);
let homo = result.homo_index;
let grid = evaluate_orbital_on_grid(&basis, &result.coefficients, homo, &pos, 0.25, 3.0);
let has_positive = grid.values.iter().any(|&v| v > 0.01);
let has_negative = grid.values.iter().any(|&v| v < -0.01);
assert!(
has_positive || has_negative,
"HOMO should have significant orbital density"
);
let pos_mesh = marching_cubes(&grid, 0.02);
let neg_mesh = marching_cubes(&grid, -0.02);
assert!(
pos_mesh.num_triangles > 0 || neg_mesh.num_triangles > 0,
"HOMO should produce at least one isosurface lobe"
);
}
#[test]
fn test_eht_result_serialization() {
let (elems, pos) = h2_molecule();
let result = solve_eht(&elems, &pos, None).unwrap();
let json = serde_json::to_string(&result).expect("EhtResult should serialize");
let deser: EhtResult = serde_json::from_str(&json).expect("EhtResult should deserialize");
assert_eq!(result.energies.len(), deser.energies.len());
assert_eq!(result.n_electrons, deser.n_electrons);
assert_eq!(result.homo_index, deser.homo_index);
assert_eq!(result.lumo_index, deser.lumo_index);
}
#[test]
fn test_mesh_serialization() {
let (elems, pos) = h2_molecule();
let result = solve_eht(&elems, &pos, None).unwrap();
let basis = eht::basis::build_basis(&elems, &pos);
let grid = evaluate_orbital_on_grid(&basis, &result.coefficients, 0, &pos, 0.3, 3.0);
let mesh = marching_cubes(&grid, 0.02);
let json = serde_json::to_string(&mesh).expect("IsosurfaceMesh should serialize");
let deser: IsosurfaceMesh =
serde_json::from_str(&json).expect("IsosurfaceMesh should deserialize");
assert_eq!(mesh.num_triangles, deser.num_triangles);
assert_eq!(mesh.vertices.len(), deser.vertices.len());
}
#[test]
fn test_single_atom_eht() {
let elems = vec![6];
let pos = vec![[0.0, 0.0, 0.0]];
let result = solve_eht(&elems, &pos, None).expect("Single atom should succeed");
assert_eq!(result.energies.len(), 4);
assert_eq!(result.n_electrons, 4); }
#[test]
fn test_unsupported_element_error() {
let elems = vec![2];
let pos = vec![[0.0, 0.0, 0.0]];
let result = solve_eht(&elems, &pos, None);
assert!(result.is_err(), "Unsupported element should return error");
}