pub struct OrbitalApproachInfo {
pub direction: [f64; 3],
pub homo_energy: f64,
pub lumo_energy: f64,
pub interaction_type: OrbitalInteraction,
}
#[derive(Debug, Clone, Copy)]
pub enum OrbitalInteraction {
HomoLumo,
LumoHomo,
ChargeControlled,
}
impl std::fmt::Display for OrbitalInteraction {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::HomoLumo => write!(f, "HOMO→LUMO"),
Self::LumoHomo => write!(f, "LUMO→HOMO"),
Self::ChargeControlled => write!(f, "charge-controlled"),
}
}
}
pub fn compute_orbital_approach_direction(
elements_a: &[u8],
coords_a: &[f64],
elements_b: &[u8],
coords_b: &[f64],
) -> Option<OrbitalApproachInfo> {
let pos_a: Vec<[f64; 3]> = coords_a.chunks(3).map(|c| [c[0], c[1], c[2]]).collect();
let pos_b: Vec<[f64; 3]> = coords_b.chunks(3).map(|c| [c[0], c[1], c[2]]).collect();
let eht_a = crate::eht::solve_eht(elements_a, &pos_a, Some(1.75)).ok()?;
let eht_b = crate::eht::solve_eht(elements_b, &pos_b, Some(1.75)).ok()?;
let _n_a = elements_a.len();
let _n_b = elements_b.len();
let n_occ_a = eht_a.n_electrons / 2;
let n_occ_b = eht_b.n_electrons / 2;
if n_occ_a == 0 || n_occ_b == 0 {
return None;
}
let homo_a_e = eht_a.homo_energy;
let lumo_a_e = eht_a.lumo_energy;
let homo_b_e = eht_b.homo_energy;
let lumo_b_e = eht_b.lumo_energy;
let (nuc_homo_e, elec_lumo_e, interaction) =
if (homo_a_e - lumo_b_e).abs() < (homo_b_e - lumo_a_e).abs() {
(homo_a_e, lumo_b_e, OrbitalInteraction::HomoLumo)
} else {
(homo_b_e, lumo_a_e, OrbitalInteraction::LumoHomo)
};
let n_basis_a = eht_a.coefficients.len();
let n_basis_b = eht_b.coefficients.len();
let orbital_centroid_a = compute_orbital_centroid(
&eht_a.coefficients,
eht_a.homo_index,
n_basis_a,
elements_a,
coords_a,
);
let orbital_centroid_b = compute_orbital_centroid(
&eht_b.coefficients,
eht_b.lumo_index,
n_basis_b,
elements_b,
coords_b,
);
let dx = orbital_centroid_b[0] - orbital_centroid_a[0];
let dy = orbital_centroid_b[1] - orbital_centroid_a[1];
let dz = orbital_centroid_b[2] - orbital_centroid_a[2];
let len = (dx * dx + dy * dy + dz * dz).sqrt();
if len < 1e-10 {
let com_a = com_flat(coords_a);
let com_b = com_flat(coords_b);
let dx2 = com_b[0] - com_a[0];
let dy2 = com_b[1] - com_a[1];
let dz2 = com_b[2] - com_a[2];
let l2 = (dx2 * dx2 + dy2 * dy2 + dz2 * dz2).sqrt().max(1e-12);
return Some(OrbitalApproachInfo {
direction: [dx2 / l2, dy2 / l2, dz2 / l2],
homo_energy: nuc_homo_e,
lumo_energy: elec_lumo_e,
interaction_type: interaction,
});
}
Some(OrbitalApproachInfo {
direction: [dx / len, dy / len, dz / len],
homo_energy: nuc_homo_e,
lumo_energy: elec_lumo_e,
interaction_type: interaction,
})
}
fn compute_orbital_centroid(
coefficients: &[Vec<f64>],
orbital_idx: usize,
n_basis: usize,
elements: &[u8],
coords: &[f64],
) -> [f64; 3] {
let n_atoms = elements.len();
let mut basis_to_atom = Vec::with_capacity(n_basis);
for (i, &z) in elements.iter().enumerate() {
let n_bf = basis_functions_for_element(z);
for _ in 0..n_bf {
basis_to_atom.push(i);
}
}
while basis_to_atom.len() < n_basis {
basis_to_atom.push(n_atoms.saturating_sub(1));
}
let mut centroid = [0.0f64; 3];
let mut total_wt = 0.0f64;
for mu in 0..n_basis.min(basis_to_atom.len()) {
if mu >= coefficients.len() || orbital_idx >= coefficients[mu].len() {
continue;
}
let c = coefficients[mu][orbital_idx];
let w = c * c;
let atom = basis_to_atom[mu];
if atom < n_atoms {
centroid[0] += w * coords[atom * 3];
centroid[1] += w * coords[atom * 3 + 1];
centroid[2] += w * coords[atom * 3 + 2];
total_wt += w;
}
}
if total_wt > 1e-14 {
centroid[0] /= total_wt;
centroid[1] /= total_wt;
centroid[2] /= total_wt;
}
centroid
}
fn basis_functions_for_element(z: u8) -> usize {
match z {
1 => 1, 2 => 1, 3..=4 => 5, 5..=10 => 5, 11..=18 => 9, 19..=36 => 13, 37..=54 => 18, _ => 18, }
}
fn com_flat(coords: &[f64]) -> [f64; 3] {
let n = coords.len() / 3;
if n == 0 {
return [0.0; 3];
}
let mut c = [0.0; 3];
for i in 0..n {
c[0] += coords[i * 3];
c[1] += coords[i * 3 + 1];
c[2] += coords[i * 3 + 2];
}
let nf = n as f64;
[c[0] / nf, c[1] / nf, c[2] / nf]
}