use elasticrab::{Atom, NormalModes, Params};
const DATA: &str = concat!(env!("CARGO_MANIFEST_DIR"), "/tests/data");
fn element_mass(symbol: &str) -> f64 {
match symbol {
"C" => 12.011,
"N" => 14.007,
"O" => 15.999,
"S" => 32.06,
other => panic!("unexpected element {other}"),
}
}
fn read_atoms_and_residue_blocks(path: &str) -> (Vec<Atom>, Vec<usize>) {
let text = std::fs::read_to_string(path).unwrap();
let mut atoms = Vec::new();
let mut blocks = Vec::new();
for line in text.lines().filter(|l| l.starts_with("ATOM")) {
let f = |a: usize, b: usize| line[a..b].trim().parse::<f64>().unwrap();
atoms.push(Atom {
position: [f(30, 38), f(38, 46), f(46, 54)],
mass: element_mass(line[76..78].trim()),
});
blocks.push(line[22..26].trim().parse().unwrap());
}
(atoms, blocks)
}
fn read_reference_freqs(path: &str) -> Vec<f64> {
std::fs::read_to_string(path)
.unwrap()
.lines()
.filter(|l| !l.starts_with('#'))
.map(|l| l.trim().parse().unwrap())
.collect()
}
#[test]
fn mass_weighted_rtb_matches_nolb() {
let (atoms, blocks) = read_atoms_and_residue_blocks(&format!("{DATA}/crambin_heavy.pdb"));
assert_eq!(atoms.len(), 327);
let mut params = Params::default();
params.cutoff = 5.0;
params.mass_weighted = true;
let modes = NormalModes::with_blocks(&atoms, &blocks, ¶ms).unwrap();
assert_eq!(modes.len(), 276);
let eigenvalues = modes.eigenvalues();
let zeros = eigenvalues.iter().filter(|&&v| v.abs() < 1e-9).count();
assert_eq!(zeros, 6);
let ours: Vec<f64> = eigenvalues
.iter()
.filter(|&&v| v > 1e-9)
.take(10)
.map(|v| v.sqrt())
.collect();
let nolb = read_reference_freqs(&format!("{DATA}/nolb_crambin_freqs.txt"));
let scale = ours[0] / nolb[0];
for (k, (o, n)) in ours.iter().zip(&nolb).enumerate() {
let ratio = o / n;
assert!(
(ratio / scale - 1.0).abs() < 1e-3,
"mode {k}: ratio {ratio} deviates from global scale {scale}"
);
}
}
#[test]
fn disconnected_atom_dropped_matches_nolb() {
let (atoms, blocks) =
read_atoms_and_residue_blocks(&format!("{DATA}/crambin_heavy_isolated.pdb"));
assert_eq!(atoms.len(), 328);
let mut params = Params::default();
params.cutoff = 5.0;
params.mass_weighted = true;
let modes = NormalModes::with_blocks(&atoms, &blocks, ¶ms).unwrap();
assert_eq!(modes.disconnected(), &[327]);
assert_eq!(modes.len(), 276);
let eigenvalues = modes.eigenvalues();
let zeros = eigenvalues.iter().filter(|&&v| v.abs() < 1e-9).count();
assert_eq!(zeros, 6);
let ours: Vec<f64> = eigenvalues
.iter()
.filter(|&&v| v > 1e-9)
.take(10)
.map(|v| v.sqrt())
.collect();
let nolb = read_reference_freqs(&format!("{DATA}/nolb_crambin_freqs.txt"));
let scale = ours[0] / nolb[0];
for (k, (o, n)) in ours.iter().zip(&nolb).enumerate() {
let ratio = o / n;
assert!(
(ratio / scale - 1.0).abs() < 1e-3,
"mode {k}: ratio {ratio} deviates from global scale {scale}"
);
}
}