use crate::coords::Coords3D;
use crate::etkdg_knowledge::{classify_atom_type, default_torsion_preference, get_torsion_preference, AtomType};
use chematic_core::{AtomIdx, Molecule};
use crate::prng::Prng;
pub fn generate_coords_etkdg(mol: &Molecule) -> Coords3D {
generate_coords_etkdg_with_noise(mol, 0.0)
}
pub fn generate_coords_etkdg_with_noise(mol: &Molecule, noise_sigma_deg: f64) -> Coords3D {
let mut coords = super::dg::generate_coords(mol);
if mol.atom_count() < 4 {
return coords;
}
let mut prng = Prng::new();
apply_torsion_preferences_with_noise(mol, &mut coords, noise_sigma_deg, &mut prng);
let constraints = super::constraints::build_constraints(mol);
coords = super::constraints::satisfy_constraints(&coords, mol, &constraints, 3);
snap_amide_torsions(mol, &mut coords);
coords
}
fn apply_torsion_preferences_with_noise(
mol: &Molecule,
coords: &mut Coords3D,
noise_sigma_deg: f64,
prng: &mut Prng,
) {
let n = mol.atom_count();
let mut applied = std::collections::HashSet::new();
for b in 0..n {
for c in 0..n {
if b == c {
continue;
}
let b_idx = AtomIdx(b as u32);
let c_idx = AtomIdx(c as u32);
if mol.bond_between(b_idx, c_idx).is_none() {
continue;
}
let b_neighbors: Vec<usize> = mol
.neighbors(b_idx)
.filter(|(nb, _)| nb.0 as usize != c)
.map(|(nb, _)| nb.0 as usize)
.collect();
let c_neighbors: Vec<usize> = mol
.neighbors(c_idx)
.filter(|(nb, _)| nb.0 as usize != b)
.map(|(nb, _)| nb.0 as usize)
.collect();
for &a in &b_neighbors {
for &d in &c_neighbors {
let key = (a.min(d), a.max(d), b.min(c), b.max(c));
if applied.contains(&key) {
continue;
}
let a_idx = AtomIdx(a as u32);
let d_idx = AtomIdx(d as u32);
let current =
super::mol_transforms::get_dihedral(coords, a_idx, b_idx, c_idx, d_idx);
if current.is_none() {
continue;
}
let current_deg = current.unwrap() * 180.0 / std::f64::consts::PI;
let preference = get_torsion_preference(mol, a_idx, b_idx, c_idx, d_idx)
.unwrap_or_else(default_torsion_preference);
let scale = bond_flexibility_scale(mol, b_idx, c_idx);
let noise = if noise_sigma_deg > 0.0 && scale > 0.0 {
prng.gaussian_f64() * noise_sigma_deg * scale
} else {
0.0
};
let raw = preference.angle_deg + noise;
let target_deg = ((raw + 180.0).rem_euclid(360.0)) - 180.0;
let diff = {
let d = (target_deg - current_deg).rem_euclid(360.0);
if d > 180.0 { d - 360.0 } else { d }
};
if diff.abs() > 20.0 {
let target_rad = target_deg * std::f64::consts::PI / 180.0;
*coords = super::mol_transforms::set_dihedral(
coords, mol, a_idx, b_idx, c_idx, d_idx, target_rad,
);
applied.insert(key);
}
}
}
}
}
}
fn snap_amide_torsions(mol: &Molecule, coords: &mut super::coords::Coords3D) {
use super::mol_transforms::{get_dihedral, set_dihedral};
let n = mol.atom_count();
for b in 0..n {
let b_idx = AtomIdx(b as u32);
let b_atom = mol.atom(b_idx);
if b_atom.element.atomic_number() != 7 || b_atom.aromatic {
continue;
}
let c_idx = mol
.neighbors(b_idx)
.find(|(nb, _)| classify_atom_type(mol, *nb) == AtomType::CCarbonyl)
.map(|(nb, _)| nb);
let Some(c_idx) = c_idx else { continue };
let b_neighbors: Vec<AtomIdx> = mol
.neighbors(b_idx)
.filter(|(nb, _)| *nb != c_idx)
.map(|(nb, _)| nb)
.collect();
let c_neighbors: Vec<AtomIdx> = mol
.neighbors(c_idx)
.filter(|(nb, _)| *nb != b_idx)
.map(|(nb, _)| nb)
.collect();
'snap: for &a_idx in &b_neighbors {
for &d_idx in &c_neighbors {
let Some(omega_rad) = get_dihedral(coords, a_idx, b_idx, c_idx, d_idx) else {
continue;
};
let omega_deg = omega_rad.to_degrees();
let to_0 = omega_deg.abs();
let to_180 = (omega_deg.abs() - 180.0).abs();
let min_dist = to_0.min(to_180);
if min_dist > 30.0 {
let target_deg = if to_0 < to_180 { 0.0_f64 } else { 180.0_f64 };
*coords = set_dihedral(
coords, mol, a_idx, b_idx, c_idx, d_idx,
target_deg.to_radians(),
);
}
break 'snap;
}
}
}
}
fn bond_flexibility_scale(mol: &Molecule, b_idx: AtomIdx, c_idx: AtomIdx) -> f64 {
let b = classify_atom_type(mol, b_idx);
let c = classify_atom_type(mol, c_idx);
if let Some((_, bond)) = mol.bond_between(b_idx, c_idx) {
match bond.order {
chematic_core::BondOrder::Double
| chematic_core::BondOrder::Triple
| chematic_core::BondOrder::Aromatic => return 0.0,
_ => {}
}
}
match (b, c) {
(AtomType::NSp2, AtomType::CCarbonyl) | (AtomType::CCarbonyl, AtomType::NSp2) => 0.20,
(AtomType::CAromatic, AtomType::CAromatic)
| (AtomType::NAromatic, AtomType::CAromatic)
| (AtomType::CAromatic, AtomType::NAromatic)
| (AtomType::OAromatic, AtomType::CAromatic)
| (AtomType::CAromatic, AtomType::OAromatic)
| (AtomType::SAromatic, AtomType::CAromatic)
| (AtomType::CAromatic, AtomType::SAromatic) => 0.50,
(AtomType::CSp2Alkene, AtomType::CCarbonyl)
| (AtomType::CCarbonyl, AtomType::CSp2Alkene)
| (AtomType::CSp2Alkene, AtomType::NSp2)
| (AtomType::NSp2, AtomType::CSp2Alkene)
| (AtomType::CSp2Alkene, AtomType::CAromatic)
| (AtomType::CAromatic, AtomType::CSp2Alkene) => 0.30,
(AtomType::CAromatic, AtomType::CSp3) | (AtomType::CSp3, AtomType::CAromatic) => 0.70,
_ => 1.0,
}
}