use super::params::ReaxffAtomParams;
use super::taper::taper_function;
#[derive(Debug, Clone, Copy)]
pub struct BondOrder {
pub total: f64,
pub sigma: f64,
pub pi: f64,
pub pipi: f64,
pub distance: f64,
}
pub fn compute_bond_orders(
positions_flat: &[f64],
atom_params: &[ReaxffAtomParams],
cutoff: f64,
) -> Vec<Vec<BondOrder>> {
let n = atom_params.len();
let mut bo = vec![
vec![
BondOrder {
total: 0.0,
sigma: 0.0,
pi: 0.0,
pipi: 0.0,
distance: 0.0
};
n
];
n
];
for i in 0..n {
let xi = positions_flat[3 * i];
let yi = positions_flat[3 * i + 1];
let zi = positions_flat[3 * i + 2];
for j in (i + 1)..n {
let dx = xi - positions_flat[3 * j];
let dy = yi - positions_flat[3 * j + 1];
let dz = zi - positions_flat[3 * j + 2];
let r = (dx * dx + dy * dy + dz * dz).sqrt();
if r > cutoff || r < 0.01 {
continue;
}
let tap = taper_function(r, cutoff);
let pi = &atom_params[i];
let pj = &atom_params[j];
let r0_sigma = (pi.r_sigma + pj.r_sigma) / 2.0;
let r0_pi = (pi.r_pi + pj.r_pi) / 2.0;
let r0_pipi = (pi.r_pipi + pj.r_pipi) / 2.0;
let bo_sigma = if r0_sigma > 0.01 {
(pi.p_bo1 * (r / r0_sigma).powf(pi.p_bo2)).exp()
} else {
0.0
};
let bo_pi = if r0_pi > 0.01 {
(pi.p_bo3 * (r / r0_pi).powf(pi.p_bo4)).exp()
} else {
0.0
};
let bo_pipi = if r0_pipi > 0.01 {
(pi.p_bo5 * (r / r0_pipi).powf(pi.p_bo6)).exp()
} else {
0.0
};
let total = (bo_sigma + bo_pi + bo_pipi) * tap;
let entry = BondOrder {
total,
sigma: bo_sigma * tap,
pi: bo_pi * tap,
pipi: bo_pipi * tap,
distance: r,
};
bo[i][j] = entry;
bo[j][i] = entry;
}
}
apply_overcorrection(&mut bo, atom_params);
bo
}
fn apply_overcorrection(bo: &mut [Vec<BondOrder>], atom_params: &[ReaxffAtomParams]) {
let n = atom_params.len();
let mut valence_total: Vec<f64> = vec![0.0; n];
for i in 0..n {
for j in 0..n {
if i != j {
valence_total[i] += bo[i][j].total;
}
}
}
for i in 0..n {
let val_i = atom_params[i].valence;
let delta_i = valence_total[i] - val_i;
if delta_i > 0.0 {
let correction = 1.0 / (1.0 + delta_i.exp());
for j in 0..n {
if i != j && bo[i][j].total > 1e-6 {
let val_j = atom_params[j].valence;
let delta_j = valence_total[j] - val_j;
let corr_j = if delta_j > 0.0 {
1.0 / (1.0 + delta_j.exp())
} else {
1.0
};
let scale = correction * corr_j;
bo[i][j].total *= scale;
bo[i][j].sigma *= scale;
bo[i][j].pi *= scale;
bo[i][j].pipi *= scale;
}
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::forcefield::reaxff::params::ReaxffParams;
#[test]
fn bond_order_h2_is_near_one() {
let params = ReaxffParams::default_chon();
let positions = [0.0, 0.0, 0.0, 0.74, 0.0, 0.0];
let atom_params = vec![
params.atom_params[params.element_index(1).unwrap()].clone(),
params.atom_params[params.element_index(1).unwrap()].clone(),
];
let bo_matrix = compute_bond_orders(&positions, &atom_params, 10.0);
let bo = bo_matrix[0][1].total; assert!(
bo > 0.3 && bo < 2.0,
"H-H bond order at 0.74Å should be roughly 1, got {bo}"
);
}
#[test]
fn bond_order_decreases_with_distance() {
let params = ReaxffParams::default_chon();
let atom_params = vec![
params.atom_params[params.element_index(6).unwrap()].clone(),
params.atom_params[params.element_index(6).unwrap()].clone(),
];
let pos_close = [0.0, 0.0, 0.0, 1.54, 0.0, 0.0]; let pos_far = [0.0, 0.0, 0.0, 3.0, 0.0, 0.0]; let bo_close = compute_bond_orders(&pos_close, &atom_params, 10.0);
let bo_far = compute_bond_orders(&pos_far, &atom_params, 10.0);
assert!(
bo_close[0][1].total > bo_far[0][1].total,
"closer atoms should have higher bond order: close={} vs far={}",
bo_close[0][1].total,
bo_far[0][1].total,
);
}
#[test]
fn bond_order_sigma_pi_components() {
let params = ReaxffParams::default_chon();
let atom_params = vec![
params.atom_params[params.element_index(6).unwrap()].clone(),
params.atom_params[params.element_index(6).unwrap()].clone(),
];
let positions = [0.0, 0.0, 0.0, 1.54, 0.0, 0.0];
let bo_matrix = compute_bond_orders(&positions, &atom_params, 10.0);
let bo = bo_matrix[0][1];
assert!(bo.sigma >= 0.0);
assert!(bo.pi >= 0.0);
assert!(bo.pipi >= 0.0);
assert!(bo.total >= 0.0);
}
}