use crate::mozyme::lewis::construct_lewis_structure;
use crate::mozyme::types::LewisStructure;
use crate::types::MolecularBatch;
#[derive(Debug, Clone, PartialEq)]
pub struct Am1BccResult {
pub initial_charges: Vec<f64>,
pub bond_charge_corrections: Vec<f64>,
pub bcc_charges: Vec<f64>,
pub total_charge: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Hybridization {
Sp3,
Sp2,
Sp,
Aromatic,
Terminal,
}
fn determine_atom_hybridization(
atom_idx: usize,
z: u8,
structure: &LewisStructure,
is_aromatic_atom: &[bool],
) -> Hybridization {
if is_aromatic_atom[atom_idx] {
return Hybridization::Aromatic;
}
let coord = structure.coordination_numbers[atom_idx];
if coord <= 1 {
return Hybridization::Terminal;
}
match z {
6 => {
if coord >= 4 {
Hybridization::Sp3
} else if coord == 3 {
Hybridization::Sp2
} else {
Hybridization::Sp
}
}
7 => {
if coord >= 3 {
let is_amide = structure.bonds.iter().any(|b| {
if b.atom1 == atom_idx || b.atom2 == atom_idx {
let other = if b.atom1 == atom_idx {
b.atom2
} else {
b.atom1
};
structure
.bonds
.iter()
.any(|b2| (b2.atom1 == other || b2.atom2 == other) && b2.order == 2)
} else {
false
}
});
if is_amide {
Hybridization::Sp2
} else {
Hybridization::Sp3
}
} else if coord == 2 {
Hybridization::Sp2
} else {
Hybridization::Sp
}
}
8 => {
if coord >= 2 {
Hybridization::Sp3
} else {
Hybridization::Sp2
}
}
14..=16 => {
if coord >= 4 {
Hybridization::Sp3
} else {
Hybridization::Sp2
}
}
_ => Hybridization::Sp3,
}
}
fn detect_aromatic_atoms(batch: &MolecularBatch, structure: &LewisStructure) -> Vec<bool> {
let natoms = batch.natoms;
let mut is_aromatic = vec![false; natoms];
let mut adj = vec![Vec::new(); natoms];
for bond in &structure.bonds {
adj[bond.atom1].push(bond.atom2);
adj[bond.atom2].push(bond.atom1);
}
for a0 in 0..natoms {
if batch.atomic_numbers[a0] != 6 && batch.atomic_numbers[a0] != 7 {
continue;
}
for &a1 in &adj[a0] {
if a1 <= a0 {
continue;
}
for &a2 in &adj[a1] {
if a2 == a0 {
continue;
}
for &a3 in &adj[a2] {
if a3 == a1 || a3 == a0 {
continue;
}
for &a4 in &adj[a3] {
if a4 == a2 || a4 == a1 || a4 == a0 {
continue;
}
for &a5 in &adj[a4] {
if a5 == a3 || a5 == a2 || a5 == a1 {
continue;
}
if adj[a5].contains(&a0) {
let ring = [a0, a1, a2, a3, a4, a5];
let all_sp2 = ring
.iter()
.all(|&at| structure.coordination_numbers[at] <= 3);
if all_sp2 {
for &at in &ring {
is_aromatic[at] = true;
}
}
}
}
}
}
}
}
}
is_aromatic
}
fn lookup_bcc_delta(
z_i: u8,
hyb_i: Hybridization,
z_j: u8,
hyb_j: Hybridization,
bond_order: usize,
) -> f64 {
if z_i == 6 && z_j == 1 {
return match hyb_i {
Hybridization::Sp3 => -0.0487, Hybridization::Aromatic => -0.0407, Hybridization::Sp2 => -0.0435, Hybridization::Sp => -0.0768, _ => -0.0487,
};
}
if z_i == 1 && z_j == 6 {
return -lookup_bcc_delta(z_j, hyb_j, z_i, hyb_i, bond_order);
}
if z_i == 6 && z_j == 6 {
if hyb_i == Hybridization::Sp3 && hyb_j == Hybridization::Aromatic {
return -0.0150; }
if hyb_i == Hybridization::Aromatic && hyb_j == Hybridization::Sp3 {
return 0.0150;
}
if hyb_i == Hybridization::Sp3 && hyb_j == Hybridization::Sp2 {
return -0.0120;
}
if hyb_i == Hybridization::Sp2 && hyb_j == Hybridization::Sp3 {
return 0.0120;
}
if hyb_i == Hybridization::Sp3 && hyb_j == Hybridization::Sp {
return -0.0250;
}
if hyb_i == Hybridization::Sp && hyb_j == Hybridization::Sp3 {
return 0.0250;
}
return 0.0000;
}
if z_i == 6 && z_j == 8 {
if bond_order == 2 {
return 0.1340; }
return match hyb_i {
Hybridization::Aromatic => 0.0650, _ => 0.0750, };
}
if z_i == 8 && z_j == 6 {
return -lookup_bcc_delta(z_j, hyb_j, z_i, hyb_i, bond_order);
}
if z_i == 6 && z_j == 7 {
if bond_order == 3 {
return 0.1320; }
if bond_order == 2 {
return 0.0920; }
return match hyb_i {
Hybridization::Aromatic => 0.0380, _ => 0.0450, };
}
if z_i == 7 && z_j == 6 {
return -lookup_bcc_delta(z_j, hyb_j, z_i, hyb_i, bond_order);
}
if z_i == 6 && (z_j == 9 || z_j == 17 || z_j == 35 || z_j == 53) {
let is_ar = hyb_i == Hybridization::Aromatic;
return match z_j {
9 => {
if is_ar {
0.1250
} else {
0.1420
}
} 17 => {
if is_ar {
0.0680
} else {
0.0820
}
} 35 => {
if is_ar {
0.0510
} else {
0.0610
}
} 53 => {
if is_ar {
0.0350
} else {
0.0420
}
} _ => 0.0500,
};
}
if (z_i == 9 || z_i == 17 || z_i == 35 || z_i == 53) && z_j == 6 {
return -lookup_bcc_delta(z_j, hyb_j, z_i, hyb_i, bond_order);
}
if z_i == 8 && z_j == 1 {
return -0.0620; }
if z_i == 1 && z_j == 8 {
return 0.0620;
}
if z_i == 7 && z_j == 1 {
return -0.0460; }
if z_i == 1 && z_j == 7 {
return 0.0460;
}
if z_i == 16 && z_j == 1 {
return -0.0210; }
if z_i == 1 && z_j == 16 {
return 0.0210;
}
if z_i == 6 && z_j == 16 {
if bond_order == 2 {
return 0.0550; }
return 0.0220; }
if z_i == 16 && z_j == 6 {
return -lookup_bcc_delta(z_j, hyb_j, z_i, hyb_i, bond_order);
}
if z_i == 7 && z_j == 8 {
if bond_order == 2 {
return 0.1100; }
return 0.0600; }
if z_i == 8 && z_j == 7 {
return -lookup_bcc_delta(z_j, hyb_j, z_i, hyb_i, bond_order);
}
if z_i == 15 && z_j == 8 {
return 0.1200; }
if z_i == 8 && z_j == 15 {
return -0.1200;
}
if z_i == 16 && z_j == 8 {
return 0.1150; }
if z_i == 8 && z_j == 16 {
return -0.1150;
}
0.0
}
pub fn compute_am1_bcc_charges(
batch: &MolecularBatch,
initial_charges: &[f64],
) -> Result<Am1BccResult, String> {
let natoms = batch.natoms;
if initial_charges.len() != natoms {
return Err(format!(
"Mismatch between initial charges length ({}) and number of atoms ({})",
initial_charges.len(),
natoms
));
}
let structure = construct_lewis_structure(batch);
let is_aromatic = detect_aromatic_atoms(batch, &structure);
let mut hybridizations = Vec::with_capacity(natoms);
for i in 0..natoms {
let z = batch.atomic_numbers[i];
let hyb = determine_atom_hybridization(i, z, &structure, &is_aromatic);
hybridizations.push(hyb);
}
let mut delta_charges = vec![0.0; natoms];
for bond in &structure.bonds {
let a1 = bond.atom1;
let a2 = bond.atom2;
let z1 = batch.atomic_numbers[a1];
let z2 = batch.atomic_numbers[a2];
let hyb1 = hybridizations[a1];
let hyb2 = hybridizations[a2];
let delta = lookup_bcc_delta(z1, hyb1, z2, hyb2, bond.order);
delta_charges[a1] += delta;
delta_charges[a2] -= delta;
}
let mut bcc_charges = Vec::with_capacity(natoms);
let mut total_charge = 0.0;
for i in 0..natoms {
let final_q = initial_charges[i] + delta_charges[i];
bcc_charges.push(final_q);
total_charge += final_q;
}
Ok(Am1BccResult {
initial_charges: initial_charges.to_vec(),
bond_charge_corrections: delta_charges,
bcc_charges,
total_charge,
})
}