use nalgebra::{DMatrix, Vector3};
pub fn calculate_dihedral_angle(
idx1: usize,
idx2: usize,
idx3: usize,
idx4: usize,
coords: &DMatrix<f32>,
) -> f32 {
let dim = coords.ncols();
assert!(dim >= 3, "Coordinates must have at least 3 dimensions");
let p1 = Vector3::new(coords[(idx1, 0)], coords[(idx1, 1)], coords[(idx1, 2)]);
let p2 = Vector3::new(coords[(idx2, 0)], coords[(idx2, 1)], coords[(idx2, 2)]);
let p3 = Vector3::new(coords[(idx3, 0)], coords[(idx3, 1)], coords[(idx3, 2)]);
let p4 = Vector3::new(coords[(idx4, 0)], coords[(idx4, 1)], coords[(idx4, 2)]);
let b1 = p2 - p1;
let b2 = p3 - p2;
let b3 = p4 - p3;
let n1 = b1.cross(&b2);
let n2 = b2.cross(&b3);
let n1_unit = n1.normalize();
let n2_unit = n2.normalize();
if n1.norm() < 1e-6 || n2.norm() < 1e-6 {
return 0.0;
}
let m = n1_unit.cross(&b2.normalize());
let x = n1_unit.dot(&n2_unit);
let y = m.dot(&n2_unit);
y.atan2(x)
}
pub fn calculate_torsion_penalty(
current_angle_rad: f32,
preferred_angle_rad: f32,
force_constant: f32,
) -> f32 {
force_constant * (1.0 - (current_angle_rad - preferred_angle_rad).cos())
}
#[cfg(test)]
mod tests {
use super::*;
use std::f32::consts::PI;
#[test]
fn test_dihedral_angle_trans() {
let mut coords = DMatrix::from_element(4, 3, 0.0);
coords.set_row(0, &nalgebra::RowVector3::new(0.0, 1.0, 0.0));
coords.set_row(1, &nalgebra::RowVector3::new(0.0, 0.0, 0.0));
coords.set_row(2, &nalgebra::RowVector3::new(1.0, 0.0, 0.0));
coords.set_row(3, &nalgebra::RowVector3::new(1.0, -1.0, 0.0));
let angle = calculate_dihedral_angle(0, 1, 2, 3, &coords);
let angle_deg = angle.to_degrees();
assert!((angle_deg.abs() - 180.0).abs() < 1e-4);
}
#[test]
fn test_dihedral_angle_gauche() {
let mut coords = DMatrix::from_element(4, 3, 0.0);
coords.set_row(0, &nalgebra::RowVector3::new(0.0, 1.0, 0.0));
coords.set_row(1, &nalgebra::RowVector3::new(0.0, 0.0, 0.0));
coords.set_row(2, &nalgebra::RowVector3::new(1.0, 0.0, 0.0));
coords.set_row(3, &nalgebra::RowVector3::new(1.0, 0.0, 1.0));
let angle = calculate_dihedral_angle(0, 1, 2, 3, &coords);
let angle_deg = angle.to_degrees();
assert!((angle_deg - -90.0).abs() < 1e-4); }
#[test]
fn test_torsion_penalty() {
let penalty = calculate_torsion_penalty(PI, PI, 10.0);
assert!((penalty - 0.0).abs() < 1e-5);
let penalty2 = calculate_torsion_penalty(PI, 0.0, 10.0);
assert!((penalty2 - 20.0).abs() < 1e-5);
}
}