use serde::{Deserialize, Serialize};
const EANG_TO_DEBYE: f64 = 4.80321;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DipoleResult {
pub vector: [f64; 3],
pub magnitude: f64,
pub unit: String,
pub nuclear_dipole: Option<[f64; 3]>,
pub electronic_dipole: Option<[f64; 3]>,
}
pub fn compute_dipole(mulliken_charges: &[f64], positions: &[[f64; 3]]) -> DipoleResult {
let n = mulliken_charges.len();
let mut mu = [0.0, 0.0, 0.0];
for i in 0..n {
let q = mulliken_charges[i];
mu[0] += q * positions[i][0];
mu[1] += q * positions[i][1];
mu[2] += q * positions[i][2];
}
mu[0] *= EANG_TO_DEBYE;
mu[1] *= EANG_TO_DEBYE;
mu[2] *= EANG_TO_DEBYE;
let magnitude = (mu[0] * mu[0] + mu[1] * mu[1] + mu[2] * mu[2]).sqrt();
DipoleResult {
vector: mu,
magnitude,
unit: "Debye".to_string(),
nuclear_dipole: None,
electronic_dipole: None,
}
}
pub fn compute_dipole_from_eht(
elements: &[u8],
positions: &[[f64; 3]],
coefficients: &[Vec<f64>],
n_electrons: usize,
) -> DipoleResult {
let basis = crate::eht::basis::build_basis(elements, positions);
let overlap = crate::eht::build_overlap_matrix(&basis);
let charges =
crate::population::mulliken_charges(elements, &basis, &overlap, coefficients, n_electrons);
let mut mu_nuc = [0.0; 3];
let mut mu_el = [0.0; 3];
for (i, &z) in elements.iter().enumerate() {
let z_val = crate::population::population::valence_electrons(z);
let pop = z_val - charges[i]; for k in 0..3 {
mu_nuc[k] += z_val * positions[i][k];
mu_el[k] -= pop * positions[i][k];
}
}
for k in 0..3 {
mu_nuc[k] *= EANG_TO_DEBYE;
mu_el[k] *= EANG_TO_DEBYE;
}
let mut result = compute_dipole(&charges, positions);
result.nuclear_dipole = Some(mu_nuc);
result.electronic_dipole = Some(mu_el);
result
}
#[cfg(test)]
mod tests {
use super::*;
use crate::eht::solve_eht;
#[test]
fn test_h2_zero_dipole() {
let elems = vec![1u8, 1];
let pos = vec![[0.0, 0.0, 0.0], [0.74, 0.0, 0.0]];
let result = solve_eht(&elems, &pos, None).unwrap();
let dipole =
compute_dipole_from_eht(&elems, &pos, &result.coefficients, result.n_electrons);
assert!(
dipole.magnitude < 0.1,
"H₂ should be nonpolar, got {:.3} D",
dipole.magnitude
);
}
#[test]
fn test_water_nonzero_dipole() {
let elems = vec![8u8, 1, 1];
let pos = vec![[0.0, 0.0, 0.0], [0.757, 0.586, 0.0], [-0.757, 0.586, 0.0]];
let result = solve_eht(&elems, &pos, None).unwrap();
let dipole =
compute_dipole_from_eht(&elems, &pos, &result.coefficients, result.n_electrons);
assert!(
dipole.magnitude > 0.1,
"Water should be polar, got {:.3} D",
dipole.magnitude
);
}
#[test]
fn test_dipole_vector_components() {
let elems = vec![8u8, 1, 1];
let pos = vec![[0.0, 0.0, 0.0], [0.757, 0.586, 0.0], [-0.757, 0.586, 0.0]];
let result = solve_eht(&elems, &pos, None).unwrap();
let dipole =
compute_dipole_from_eht(&elems, &pos, &result.coefficients, result.n_electrons);
assert!(
dipole.vector[2].abs() < 0.1,
"z-component should be ~0 for planar molecule"
);
assert!(
dipole.vector[0].abs() < 0.1,
"x-component should be ~0 for symmetric water"
);
}
#[test]
fn test_dipole_units_debye() {
let charges = vec![0.5, -0.5];
let pos = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]];
let dipole = compute_dipole(&charges, &pos);
assert_eq!(dipole.unit, "Debye");
assert!(
(dipole.magnitude - 2.4016).abs() < 0.01,
"Expected ~2.4 D, got {:.3}",
dipole.magnitude
);
}
}