use crate::graph::{Hybridization, Molecule};
use nalgebra::Vector3;
#[derive(Debug, Clone)]
pub struct M6Params {
pub v: [f32; 6],
pub s: [f32; 6],
}
impl Default for M6Params {
fn default() -> Self {
M6Params {
v: [0.0; 6],
s: [1.0; 6],
}
}
}
pub fn calc_torsion_energy_m6(
p1: &Vector3<f32>,
p2: &Vector3<f32>,
p3: &Vector3<f32>,
p4: &Vector3<f32>,
params: &M6Params,
) -> f32 {
let r1 = p1 - p2;
let r2 = p3 - p2;
let r3 = p2 - p3;
let r4 = p4 - p3;
let t1 = r1.cross(&r2);
let t2 = r3.cross(&r4);
let d1 = t1.norm();
let d2 = t2.norm();
if d1 < 1e-4 || d2 < 1e-4 {
return 0.0;
}
let n1 = t1 / d1;
let n2 = t2 / d2;
let mut cos_phi = n1.dot(&n2);
cos_phi = cos_phi.clamp(-1.0, 1.0);
let cos_phi2 = cos_phi * cos_phi;
let cos_phi3 = cos_phi * cos_phi2;
let cos_phi4 = cos_phi * cos_phi3;
let cos_phi5 = cos_phi * cos_phi4;
let cos_phi6 = cos_phi * cos_phi5;
let cos2_phi = 2.0 * cos_phi2 - 1.0;
let cos3_phi = 4.0 * cos_phi3 - 3.0 * cos_phi;
let cos4_phi = 8.0 * cos_phi4 - 8.0 * cos_phi2 + 1.0;
let cos5_phi = 16.0 * cos_phi5 - 20.0 * cos_phi3 + 5.0 * cos_phi;
let cos6_phi = 32.0 * cos_phi6 - 48.0 * cos_phi4 + 18.0 * cos_phi2 - 1.0;
let mut energy = 0.0;
energy += params.v[0] * (1.0 + params.s[0] * cos_phi);
energy += params.v[1] * (1.0 + params.s[1] * cos2_phi);
energy += params.v[2] * (1.0 + params.s[2] * cos3_phi);
energy += params.v[3] * (1.0 + params.s[3] * cos4_phi);
energy += params.v[4] * (1.0 + params.s[4] * cos5_phi);
energy += params.v[5] * (1.0 + params.s[5] * cos6_phi);
energy
}
pub fn calc_torsion_grad_m6(
p1: &Vector3<f32>,
p2: &Vector3<f32>,
p3: &Vector3<f32>,
p4: &Vector3<f32>,
params: &M6Params,
grad: &mut nalgebra::DMatrix<f32>,
idx1: usize,
idx2: usize,
idx3: usize,
idx4: usize,
) {
let r0 = p1 - p2;
let r1 = p3 - p2;
let r2 = -&r1;
let r3 = p4 - p3;
let mut t0 = r0.cross(&r1);
let d0 = t0.norm().max(1e-5);
t0 /= d0;
let mut t1 = r2.cross(&r3);
let d1 = t1.norm().max(1e-5);
t1 /= d1;
let cos_phi = t0.dot(&t1).clamp(-1.0, 1.0);
let sin_phi_sq = 1.0 - cos_phi * cos_phi;
let sin_phi = if sin_phi_sq > 0.0 {
sin_phi_sq.sqrt()
} else {
0.0
};
let cos_phi2 = cos_phi * cos_phi;
let cos_phi3 = cos_phi * cos_phi2;
let cos_phi4 = cos_phi * cos_phi3;
let cos_phi5 = cos_phi * cos_phi4;
let de_dphi = -params.v[0] * params.s[0] * sin_phi
- 2.0 * params.v[1] * params.s[1] * (2.0 * cos_phi * sin_phi)
- 3.0 * params.v[2] * params.s[2] * (4.0 * cos_phi2 * sin_phi - sin_phi)
- 4.0 * params.v[3] * params.s[3] * (8.0 * cos_phi3 * sin_phi - 4.0 * cos_phi * sin_phi)
- 5.0
* params.v[4]
* params.s[4]
* (16.0 * cos_phi4 * sin_phi - 12.0 * cos_phi2 * sin_phi + sin_phi)
- 6.0
* params.v[4]
* params.s[4]
* (32.0 * cos_phi5 * sin_phi - 32.0 * cos_phi3 * sin_phi + 6.0 * cos_phi * sin_phi);
let is_zero_sin = sin_phi < 1e-5;
let sin_term = -de_dphi
* if is_zero_sin {
1.0 / cos_phi
} else {
1.0 / sin_phi
};
let d_cos_dt = [
(t1.x - cos_phi * t0.x) / d0,
(t1.y - cos_phi * t0.y) / d0,
(t1.z - cos_phi * t0.z) / d0,
(t0.x - cos_phi * t1.x) / d1,
(t0.y - cos_phi * t1.y) / d1,
(t0.z - cos_phi * t1.z) / d1,
];
grad[(idx1, 0)] += sin_term * (d_cos_dt[2] * r1.y - d_cos_dt[1] * r1.z);
grad[(idx1, 1)] += sin_term * (d_cos_dt[0] * r1.z - d_cos_dt[2] * r1.x);
grad[(idx1, 2)] += sin_term * (d_cos_dt[1] * r1.x - d_cos_dt[0] * r1.y);
grad[(idx2, 0)] += sin_term
* (d_cos_dt[1] * (r1.z - r0.z)
+ d_cos_dt[2] * (r0.y - r1.y)
+ d_cos_dt[4] * (-r3.z)
+ d_cos_dt[5] * r3.y);
grad[(idx2, 1)] += sin_term
* (d_cos_dt[0] * (r0.z - r1.z)
+ d_cos_dt[2] * (r1.x - r0.x)
+ d_cos_dt[3] * r3.z
+ d_cos_dt[5] * (-r3.x));
grad[(idx2, 2)] += sin_term
* (d_cos_dt[0] * (r1.y - r0.y)
+ d_cos_dt[1] * (r0.x - r1.x)
+ d_cos_dt[3] * (-r3.y)
+ d_cos_dt[4] * r3.x);
grad[(idx3, 0)] += sin_term
* (d_cos_dt[1] * r0.z
+ d_cos_dt[2] * (-r0.y)
+ d_cos_dt[4] * (r3.z - r2.z)
+ d_cos_dt[5] * (r2.y - r3.y));
grad[(idx3, 1)] += sin_term
* (d_cos_dt[0] * (-r0.z)
+ d_cos_dt[2] * r0.x
+ d_cos_dt[3] * (r2.z - r3.z)
+ d_cos_dt[5] * (r3.x - r2.x));
grad[(idx3, 2)] += sin_term
* (d_cos_dt[0] * r0.y
+ d_cos_dt[1] * (-r0.x)
+ d_cos_dt[3] * (r3.y - r2.y)
+ d_cos_dt[4] * (r2.x - r3.x));
grad[(idx4, 0)] += sin_term * (d_cos_dt[4] * r2.z - d_cos_dt[5] * r2.y);
grad[(idx4, 1)] += sin_term * (d_cos_dt[5] * r2.x - d_cos_dt[3] * r2.z);
grad[(idx4, 2)] += sin_term * (d_cos_dt[3] * r2.y - d_cos_dt[4] * r2.x);
}
fn is_partial_double_bond(mol: &Molecule, n2_idx: usize, n3_idx: usize) -> bool {
let ni2 = petgraph::graph::NodeIndex::new(n2_idx);
let ni3 = petgraph::graph::NodeIndex::new(n3_idx);
if let Some(edge) = mol.graph.find_edge(ni2, ni3) {
if mol.graph[edge].order == crate::graph::BondOrder::Double {
return true;
}
if mol.graph[edge].order == crate::graph::BondOrder::Aromatic {
return true;
}
}
for ni in &[ni2, ni3] {
let other = if *ni == ni2 { ni3 } else { ni2 };
let atom = &mol.graph[*ni];
let other_atom = &mol.graph[other];
if (atom.element == 7 || atom.element == 8)
&& other_atom.hybridization == Hybridization::SP2
{
for neighbor in mol.graph.neighbors(other) {
if neighbor == *ni {
continue;
}
if let Some(edge) = mol.graph.find_edge(other, neighbor) {
if mol.graph[edge].order == crate::graph::BondOrder::Double {
let neigh_atom = &mol.graph[neighbor];
if neigh_atom.element == 8 || neigh_atom.element == 7 {
return true;
}
}
}
}
}
}
false
}
pub fn infer_etkdg_parameters(mol: &Molecule, n2_idx: usize, n3_idx: usize) -> M6Params {
let mut params = M6Params::default();
if is_partial_double_bond(mol, n2_idx, n3_idx) {
params.v[1] = 100.0;
params.s[1] = -1.0; return params;
}
let a2 = &mol.graph[petgraph::graph::NodeIndex::new(n2_idx)];
let a3 = &mol.graph[petgraph::graph::NodeIndex::new(n3_idx)];
let e2 = a2.element;
let e3 = a3.element;
let h2 = a2.hybridization;
let h3 = a3.hybridization;
if h2 == Hybridization::SP2 && h3 == Hybridization::SP2 {
params.v[1] = 10.0; params.s[1] = -1.0; }
else if (e2 == 6 && e3 == 8 && h2 == Hybridization::SP3)
|| (e2 == 8 && e3 == 6 && h3 == Hybridization::SP3)
{
params.v[2] = 4.0;
params.s[2] = 1.0;
}
else if h2 == Hybridization::SP3 && h3 == Hybridization::SP3 {
params.v[2] = 5.0; params.s[2] = 1.0;
params.v[0] = 2.0; params.s[0] = 1.0;
}
else {
params.v[2] = 3.0;
params.s[2] = 1.0;
}
params
}