use std::{
f64::consts::TAU,
fmt,
fmt::{Display, Formatter},
};
use bio_files::{AtomGeneric, ResidueGeneric, ResidueType};
use lin_alg::f64::{Quaternion, Vec3, calc_dihedral_angle, calc_dihedral_angle_v2};
use na_seq::{
AminoAcid, AtomTypeInRes,
Element::{Carbon, Hydrogen, Nitrogen, Oxygen, Sulfur},
};
use crate::{
ParamError,
add_hydrogens::{
DigitMap,
bond_vecs::{
LEN_C_H, LEN_CALPHA_H, LEN_N_H, LEN_O_H, LEN_S_H, PLANAR3_A, PLANAR3_B, PLANAR3_C,
TETRA_A, TETRA_B, TETRA_C, TETRA_D,
},
h_type_in_res_sidechain,
sidechain::Sidechain,
},
};
const PLANAR_ANGLE_THRESH: f64 = 2.00;
const SP2_PLANAR_ANGLE: f64 = TAU / 3.;
struct BondError {}
#[derive(Debug, Clone, Default)]
pub struct Dihedral {
pub ω: Option<f64>,
pub φ: Option<f64>,
pub ψ: Option<f64>,
pub sidechain: Sidechain,
}
impl Display for Dihedral {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
let mut result = String::new();
if let Some(ω) = self.ω {
result = format!(" ω: {:.2}τ", ω / TAU) + " " + &result;
}
if let Some(φ) = self.φ {
result += &format!(" φ: {:.2}τ", φ / TAU);
}
if let Some(ψ) = self.ψ {
result += &format!(" ψ: {:.2}τ", ψ / TAU);
}
write!(f, "{result}")?;
Ok(())
}
}
pub fn tetra_legs(leg_a: Vec3, leg_b: Vec3, leg_c: Vec3) -> Vec3 {
(-(leg_a + leg_b + leg_c)).to_normalized()
}
pub fn tetra_atoms(atom_center: Vec3, atom_a: Vec3, atom_b: Vec3, atom_c: Vec3) -> Vec3 {
let avg = (atom_a + atom_b + atom_c) / 3.;
(avg - atom_center).to_normalized()
}
fn tetra_atoms_2(center: Vec3, atom_0: Vec3, atom_1: Vec3, len: f64) -> (Vec3, Vec3) {
let bond_0 = (atom_0 - center).to_normalized();
let bond_1 = (center - atom_1).to_normalized();
let rotator_a = Quaternion::from_unit_vecs(TETRA_A, bond_0);
let tetra_b_rotated = rotator_a.rotate_vec(unsafe { TETRA_B });
let dihedral = calc_dihedral_angle(bond_0, tetra_b_rotated, bond_1);
let rotator_b = Quaternion::from_axis_angle(bond_0, -dihedral);
let rotator = rotator_b * rotator_a;
unsafe {
(
center + rotator.rotate_vec(TETRA_C) * len,
center + rotator.rotate_vec(TETRA_D) * len,
)
}
}
fn planar_posit(posit_center: Vec3, bond_0: Vec3, bond_1: Vec3, len: f64) -> Vec3 {
let bond_0_unit = bond_0.to_normalized();
let n_plane_normal = bond_0_unit.cross(bond_1).to_normalized();
let rotator = Quaternion::from_axis_angle(n_plane_normal, SP2_PLANAR_ANGLE);
posit_center + rotator.rotate_vec(-bond_0_unit) * len
}
fn find_bonded_atoms<'a>(
atom: &'a AtomGeneric,
atoms: &[&'a AtomGeneric],
atom_i: usize,
) -> Vec<(usize, &'a AtomGeneric)> {
const BONDED_LEN_THRESH: f64 = 1.85;
atoms
.iter()
.enumerate()
.filter(|(j, a)| {
atom_i != *j
&& (a.posit - atom.posit).magnitude() < BONDED_LEN_THRESH
&& a.element != Hydrogen
})
.map(|(j, a)| (j, *a))
.collect()
}
fn get_prev_bonds(
atom: &AtomGeneric,
atoms: &[&AtomGeneric],
atom_i: usize,
atom_next: (usize, &AtomGeneric),
) -> Result<(Vec3, Vec3), BondError> {
let bonded_to_next: Vec<(usize, &AtomGeneric)> =
find_bonded_atoms(atom_next.1, atoms, atom_next.0)
.into_iter()
.filter(|a| a.0 != atom_i)
.collect();
if bonded_to_next.is_empty() {
return Err(BondError {});
}
let atom_2_after = bonded_to_next[0].1;
let this_to_next = (atom_next.1.posit - atom.posit).to_normalized();
let next_to_2after = (atom_next.1.posit - atom_2_after.posit).to_normalized();
Ok((this_to_next, next_to_2after))
}
fn add_h_sc_het(
hydrogens: &mut Vec<AtomGeneric>,
atoms: &[&AtomGeneric],
h_default: &AtomGeneric,
residues: &[ResidueGeneric],
digit_map: &DigitMap,
) -> Result<(), ParamError> {
let h_default_sc = AtomGeneric {
..h_default.clone()
};
for (i, atom) in atoms.iter().enumerate() {
let Some(tir) = &atom.type_in_res else {
continue;
};
if matches!(
tir,
AtomTypeInRes::CA | AtomTypeInRes::C | AtomTypeInRes::N | AtomTypeInRes::O
) {
continue;
}
let mut aa = None;
for res in residues {
if res.atom_sns.contains(&atom.serial_number)
&& let ResidueType::AminoAcid(a) = &res.res_type
{
aa = Some(*a);
break;
}
}
let atoms_bonded = find_bonded_atoms(atom, atoms, i);
let Some(parent_tir) = atom.type_in_res.as_ref() else {
return Err(ParamError::new(&format!(
"Missing parent type in res when adding H: {atom}"
)));
};
match atom.element {
Carbon => {
match atoms_bonded.len() {
1 => unsafe {
let (bond_prev, bond_back2) =
match get_prev_bonds(atom, atoms, i, atoms_bonded[0]) {
Ok(v) => v,
Err(_) => {
continue;
}
};
let rotator_a = Quaternion::from_unit_vecs(TETRA_A, bond_prev);
let tetra_rotated = rotator_a.rotate_vec(TETRA_B);
let dihedral = calc_dihedral_angle(bond_prev, tetra_rotated, bond_back2);
let rotator_b =
Quaternion::from_axis_angle(bond_prev, -dihedral + TAU / 6.);
let rotator = rotator_b * rotator_a;
for (i, tetra_bond) in [TETRA_B, TETRA_C, TETRA_D].into_iter().enumerate() {
let at = h_type_in_res_sidechain(i, parent_tir, aa, digit_map)?;
let Some(at) = at else { continue };
hydrogens.push(AtomGeneric {
posit: atom.posit + rotator.rotate_vec(tetra_bond) * LEN_C_H,
type_in_res: Some(at),
hetero: aa.is_none(),
..h_default_sc.clone()
});
}
},
2 => {
let mut planar = false;
let mut exemption = false;
if let Some(a) = aa {
if (a == AminoAcid::Trp && *parent_tir == AtomTypeInRes::CD1)
|| (a == AminoAcid::His && *parent_tir == AtomTypeInRes::CD2)
{
exemption = true;
}
}
if atoms_bonded[0].1.element == Nitrogen
&& atoms_bonded[1].1.element == Nitrogen
|| exemption
{
planar = true;
} else {
let bond_next = (atoms_bonded[1].1.posit - atom.posit).to_normalized();
let bond_prev = (atoms_bonded[0].1.posit - atom.posit).to_normalized();
let angle = bond_next.dot(bond_prev).acos();
if angle > PLANAR_ANGLE_THRESH {
planar = true;
}
}
if planar {
let bond_0 = atom.posit - atoms_bonded[0].1.posit;
let bond_1 = atoms_bonded[1].1.posit - atom.posit;
let at = h_type_in_res_sidechain(0, parent_tir, aa, digit_map)?;
let Some(at) = at else { continue };
hydrogens.push(AtomGeneric {
posit: planar_posit(atom.posit, bond_0, bond_1, LEN_C_H),
type_in_res: Some(at),
hetero: aa.is_none(),
..h_default_sc.clone()
});
continue;
}
let (h_0, h_1) = tetra_atoms_2(
atom.posit,
atoms_bonded[0].1.posit,
atoms_bonded[1].1.posit,
LEN_C_H,
);
for (i, posit) in [h_0, h_1].into_iter().enumerate() {
let at = h_type_in_res_sidechain(i, parent_tir, aa, digit_map)?;
let Some(at) = at else { continue };
hydrogens.push(AtomGeneric {
posit,
type_in_res: Some(at),
hetero: aa.is_none(),
..h_default_sc.clone()
});
}
}
3 => {
if atoms_bonded[0].1.element == Nitrogen
&& atoms_bonded[1].1.element == Nitrogen
&& atoms_bonded[2].1.element == Nitrogen
{
continue;
}
if let Some(aa) = aa
&& aa == AminoAcid::Trp
&& matches!(parent_tir, AtomTypeInRes::CD2 | AtomTypeInRes::CE2)
{
continue;
}
let bond_next = (atoms_bonded[1].1.posit - atom.posit).to_normalized();
let bond_prev = (atoms_bonded[0].1.posit - atom.posit).to_normalized();
let angle = (bond_next.dot(bond_prev)).acos();
if angle > PLANAR_ANGLE_THRESH {
continue;
}
let at = h_type_in_res_sidechain(0, parent_tir, aa, digit_map)?;
let Some(at) = at else { continue };
hydrogens.push(AtomGeneric {
posit: atom.posit
- tetra_atoms(
atom.posit,
atoms_bonded[0].1.posit,
atoms_bonded[1].1.posit,
atoms_bonded[2].1.posit,
) * LEN_CALPHA_H,
type_in_res: Some(at),
hetero: aa.is_none(),
..h_default_sc.clone()
});
}
_ => (),
}
}
Nitrogen => {
match atoms_bonded.len() {
1 => unsafe {
let (bond_prev, bond_back2) =
match get_prev_bonds(atom, atoms, i, atoms_bonded[0]) {
Ok(v) => v,
Err(_) => {
eprintln!("Error: Could not find prev bonds on Amine");
continue;
}
};
let depth = match parent_tir {
AtomTypeInRes::NZ => 'Z',
AtomTypeInRes::NH1 | AtomTypeInRes::NH2 => 'H',
AtomTypeInRes::NE | AtomTypeInRes::NE1 | AtomTypeInRes::NE2 => 'E',
AtomTypeInRes::ND1 | AtomTypeInRes::ND2 => 'D',
_ => '\0',
};
let n_h = aa
.and_then(|a| digit_map.get(&a))
.and_then(|m| m.get(&depth))
.map(|d| d.len())
.unwrap_or(2);
if n_h >= 3 {
let rotator_a = Quaternion::from_unit_vecs(TETRA_A, bond_prev);
let tetra_rotated = rotator_a.rotate_vec(TETRA_B);
let dihedral =
calc_dihedral_angle(bond_prev, tetra_rotated, bond_back2);
let rotator_b = Quaternion::from_axis_angle(bond_prev, -dihedral);
let rotator = rotator_b * rotator_a;
for (i, tetra_bond) in
[TETRA_B, TETRA_C, TETRA_D].into_iter().enumerate()
{
let at = h_type_in_res_sidechain(i, parent_tir, aa, digit_map)?;
let Some(at) = at else { continue };
hydrogens.push(AtomGeneric {
posit: atom.posit + rotator.rotate_vec(tetra_bond) * LEN_N_H,
type_in_res: Some(at),
hetero: aa.is_none(),
..h_default_sc.clone()
});
}
} else {
let rotator_a = Quaternion::from_unit_vecs(PLANAR3_A, bond_prev);
let planar_3_rotated = rotator_a.rotate_vec(PLANAR3_B);
let dihedral =
calc_dihedral_angle(bond_prev, planar_3_rotated, bond_back2);
let rotator_b = Quaternion::from_axis_angle(bond_prev, -dihedral);
let rotator = rotator_b * rotator_a;
for (i, planar_bond) in [PLANAR3_B, PLANAR3_C].into_iter().enumerate() {
let at = h_type_in_res_sidechain(i, parent_tir, aa, digit_map)?;
let Some(at) = at else { continue };
hydrogens.push(AtomGeneric {
posit: atom.posit + rotator.rotate_vec(planar_bond) * LEN_N_H,
type_in_res: Some(at),
hetero: aa.is_none(),
..h_default_sc.clone()
});
}
}
},
2 => {
let bond_0 = atom.posit - atoms_bonded[0].1.posit;
let bond_1 = atoms_bonded[1].1.posit - atom.posit;
let at = h_type_in_res_sidechain(0, parent_tir, aa, digit_map)?;
let Some(at) = at else { continue };
hydrogens.push(AtomGeneric {
posit: planar_posit(atom.posit, bond_0, bond_1, LEN_N_H),
type_in_res: Some(at),
hetero: aa.is_none(),
..h_default_sc.clone()
});
}
_ => (),
}
}
Oxygen => {
if atoms_bonded.len() == 1 {
let (bond_prev, bond_back2) =
match get_prev_bonds(atom, atoms, i, atoms_bonded[0]) {
Ok(v) => v,
Err(_) => {
eprintln!("Error: Could not find prev bonds on Hydroxyl");
continue;
}
};
let bond_prev_non_norm = atoms_bonded[0].1.posit - atom.posit;
if aa.is_none() && bond_prev_non_norm.magnitude() < 1.30 {
continue;
}
let rotator_a = Quaternion::from_unit_vecs(TETRA_A, bond_prev);
let tetra_rotated = rotator_a.rotate_vec(unsafe { TETRA_B });
let dihedral = calc_dihedral_angle(bond_prev, tetra_rotated, bond_back2);
let rotator_b = Quaternion::from_axis_angle(bond_prev, -dihedral + TAU / 6.);
let rotator = rotator_b * rotator_a;
let at = h_type_in_res_sidechain(0, parent_tir, aa, digit_map)?;
let Some(at) = at else { continue };
hydrogens.push(AtomGeneric {
posit: atom.posit + rotator.rotate_vec(unsafe { TETRA_B }) * LEN_O_H,
type_in_res: Some(at),
hetero: aa.is_none(),
..h_default_sc.clone()
});
}
}
Sulfur => {
if atoms_bonded.len() == 1 {
let (bond_prev, bond_back2) =
match get_prev_bonds(atom, atoms, i, atoms_bonded[0]) {
Ok(v) => v,
Err(_) => {
eprintln!("Error: Could not find prev bonds on Thiol");
continue;
}
};
let at = h_type_in_res_sidechain(0, parent_tir, aa, digit_map)?;
let Some(at) = at else { continue };
let rotator_a = Quaternion::from_unit_vecs(TETRA_A, bond_prev);
let tetra_rotated = rotator_a.rotate_vec(unsafe { TETRA_B });
let dihedral = calc_dihedral_angle(bond_prev, tetra_rotated, bond_back2);
let rotator_b = Quaternion::from_axis_angle(bond_prev, -dihedral + TAU / 6.);
let rotator = rotator_b * rotator_a;
hydrogens.push(AtomGeneric {
posit: atom.posit + rotator.rotate_vec(unsafe { TETRA_B }) * LEN_S_H,
type_in_res: Some(at),
hetero: aa.is_none(),
..h_default_sc.clone()
});
}
}
_ => {}
}
}
Ok(())
}
fn handle_backbone(
hydrogens: &mut Vec<AtomGeneric>,
atoms: &[&AtomGeneric],
posits_sc: &[Vec3],
prev_cp_ca: Option<(Vec3, Vec3)>,
next_n: Option<Vec3>,
h_default: &AtomGeneric,
aa: &AminoAcid,
) -> Result<(Dihedral, Option<(Vec3, Vec3)>), ParamError> {
let mut dihedral = Dihedral::default();
let mut n_posit = None;
let mut c_alpha_posit = None;
let mut c_p_posit = None;
for atom in atoms {
let Some(tir) = &atom.type_in_res else {
continue;
};
match tir {
AtomTypeInRes::N => {
n_posit = Some(atom.posit);
}
AtomTypeInRes::CA => {
c_alpha_posit = Some(atom.posit);
}
AtomTypeInRes::C => {
c_p_posit = Some(atom.posit);
}
_ => (),
}
}
let (Some(c_alpha_posit), Some(c_p_posit), Some(n_posit)) = (c_alpha_posit, c_p_posit, n_posit)
else {
eprintln!("Error: Missing backbone atoms in coords.");
return Ok((dihedral, None));
};
let bond_ca_n = c_alpha_posit - n_posit;
let bond_cp_ca = c_p_posit - c_alpha_posit;
if let Some((cp_prev, ca_prev)) = prev_cp_ca {
let bond_n_cp_prev = n_posit - cp_prev;
dihedral.φ = Some(calc_dihedral_angle_v2(&(
cp_prev,
n_posit,
c_alpha_posit,
c_p_posit,
)));
dihedral.ω = Some(calc_dihedral_angle_v2(&(
ca_prev,
cp_prev,
n_posit,
c_alpha_posit,
)));
if dihedral.ω.unwrap().is_nan() {
println!("NAN: prev_cp: {cp_prev} prev_ca: {ca_prev}\n")
}
if aa != &AminoAcid::Pro {
hydrogens.push(AtomGeneric {
posit: planar_posit(n_posit, bond_n_cp_prev, bond_ca_n, LEN_N_H),
type_in_res: Some(AtomTypeInRes::H("H".to_string())),
..h_default.clone()
});
}
}
if let Some(n_next) = next_n {
dihedral.ψ = Some(calc_dihedral_angle_v2(&(
n_posit,
c_alpha_posit,
c_p_posit,
n_next,
)));
}
if posits_sc.is_empty() {
let (h_0, h_1) = tetra_atoms_2(c_alpha_posit, c_p_posit, n_posit, LEN_CALPHA_H);
hydrogens.push(AtomGeneric {
posit: h_0,
type_in_res: Some(AtomTypeInRes::H("HA2".to_string())),
..h_default.clone()
});
hydrogens.push(AtomGeneric {
posit: h_1,
type_in_res: Some(AtomTypeInRes::H("HA3".to_string())),
..h_default.clone()
});
return Ok((dihedral, Some((c_p_posit, c_alpha_posit))));
}
let mut closest = (posits_sc[0] - c_alpha_posit).magnitude();
let mut closest_sc = posits_sc[0];
for pos in posits_sc {
let dist = (*pos - c_alpha_posit).magnitude();
if dist < closest {
closest = dist;
closest_sc = *pos;
}
}
let bond_ca_sidechain = c_alpha_posit - closest_sc;
let posit_ha = c_alpha_posit
+ tetra_legs(
-bond_ca_n.to_normalized(),
bond_cp_ca.to_normalized(),
-bond_ca_sidechain.to_normalized(),
) * LEN_CALPHA_H;
hydrogens.push(AtomGeneric {
posit: posit_ha,
type_in_res: Some(AtomTypeInRes::H("HA".to_string())),
..h_default.clone()
});
Ok((dihedral, Some((c_p_posit, c_alpha_posit))))
}
pub fn aa_data_from_coords(
atoms: &[&AtomGeneric],
residues: &[ResidueGeneric],
residue_type: &ResidueType,
prev_cp_ca: Option<(Vec3, Vec3)>,
next_n: Option<Vec3>,
digit_map: &DigitMap,
) -> Result<(Dihedral, Vec<AtomGeneric>, Option<(Vec3, Vec3)>), ParamError> {
let h_default = AtomGeneric {
element: Hydrogen,
hetero: false,
..Default::default()
};
let mut hydrogens = Vec::new();
let mut dihedral = Dihedral::default();
let mut this_cp_ca = None;
if let ResidueType::AminoAcid(aa) = residue_type {
let mut posits_sc = Vec::new();
for atom_sc in atoms {
let Some(tir) = &atom_sc.type_in_res else {
continue;
};
let sc = !matches!(
tir,
AtomTypeInRes::C | AtomTypeInRes::CA | AtomTypeInRes::N | AtomTypeInRes::O
);
if sc && atom_sc.element == Carbon {
posits_sc.push(atom_sc.posit);
}
}
(dihedral, this_cp_ca) = handle_backbone(
&mut hydrogens,
atoms,
&posits_sc,
prev_cp_ca,
next_n,
&h_default,
aa,
)?;
}
add_h_sc_het(&mut hydrogens, atoms, &h_default, residues, digit_map)?;
Ok((dihedral, hydrogens, this_cp_ca))
}