use std::collections::HashMap;
use bio_files::{AtomGeneric, ChainGeneric, ResidueGeneric};
use na_seq::{AminoAcid, AminoAcidGeneral, AminoAcidProtenationVariant, AtomTypeInRes};
use crate::{
ParamError,
add_hydrogens::{
add_hydrogens_2::{Dihedral, aa_data_from_coords},
bond_vecs::init_local_bond_vecs,
ph::{PKA_TYR, his_choice, standard_allowed_at_ph, variant_allowed_at_ph},
},
params::{ProtFfChargeMap, ProtFfChargeMapSet},
};
pub(crate) mod add_hydrogens_2;
pub mod bond_vecs;
mod ph;
mod sidechain;
pub type DigitMap = HashMap<AminoAcid, HashMap<char, Vec<u8>>>;
fn validate_h_atom_type(
depth: char,
digit: u8,
aa: AminoAcid,
digit_map: &DigitMap,
) -> Result<bool, ParamError> {
let data = digit_map.get(&aa).ok_or_else(|| {
ParamError::new(&format!(
"No parm19_data entry for amino acid {:?}",
AminoAcidGeneral::Standard(aa)
))
})?;
let data_this_depth = data.get(&depth).ok_or_else(|| {
ParamError::new(&format!(
"No parm19_data entry for amino acid (Depth) {:?}",
AminoAcidGeneral::Standard(aa)
))
})?;
if data_this_depth.contains(&digit) {
return Ok(true);
}
Ok(false)
}
fn make_h_digit_map(ff_map: &ProtFfChargeMap, ph: f32) -> DigitMap {
let mut result: DigitMap = HashMap::new();
let his_selected = his_choice(ph);
for (&aa_gen, params) in ff_map {
let allowed = match aa_gen {
AminoAcidGeneral::Standard(aa) => standard_allowed_at_ph(aa, ph),
AminoAcidGeneral::Variant(v) => {
if matches!(
v,
AminoAcidProtenationVariant::Hid
| AminoAcidProtenationVariant::Hie
| AminoAcidProtenationVariant::Hip
) {
matches!(his_selected, Some(sel) if sel == v)
} else {
variant_allowed_at_ph(v, ph)
}
}
};
if !allowed {
continue;
}
let mut per_heavy: HashMap<char, Vec<u8>> = HashMap::new();
for cp in params {
let tir = &cp.type_in_res;
match tir {
AtomTypeInRes::H(name) => {
let mut chars = name.chars();
chars.next();
let designator = match chars.next() {
Some(c) if c.is_ascii_alphabetic() => c,
_ => continue, };
let digits: String = chars.filter(|c| c.is_ascii_digit()).collect();
if digits.is_empty() {
per_heavy.entry(designator).or_default().push(0);
} else {
let num: u8 = digits.parse().unwrap();
per_heavy.entry(designator).or_default().push(num);
}
}
_ => (),
}
}
if per_heavy.is_empty() {
continue;
}
let aa = match aa_gen {
AminoAcidGeneral::Standard(a) => a,
AminoAcidGeneral::Variant(av) => av.get_standard().unwrap(), };
for v in per_heavy.values_mut() {
v.sort_unstable();
v.dedup();
}
if let Some(existing) = result.get_mut(&aa) {
for (designator, mut digits) in per_heavy {
existing.entry(designator).or_default().append(&mut digits);
}
for v in existing.values_mut() {
v.sort_unstable();
v.dedup();
}
} else {
result.insert(aa, per_heavy);
}
}
if ph > PKA_TYR {
if let Some(tyr_map) = result.get_mut(&AminoAcid::Tyr) {
tyr_map.remove(&'H');
}
}
result
}
pub(crate) fn h_type_in_res_sidechain(
h_num_this_parent: usize,
parent_tir: &AtomTypeInRes,
aa: Option<AminoAcid>, h_digit_map: &DigitMap,
) -> Result<Option<AtomTypeInRes>, ParamError> {
let Some(aa) = aa else {
let val = match parent_tir {
AtomTypeInRes::Hetero(name_parent) => {
let mut chars = name_parent.chars();
let elem = chars.next().unwrap(); let rest: String = chars.collect();
if elem == 'C' && rest.chars().all(|c| c.is_ascii_digit()) {
let idx = h_num_this_parent + 1;
format!("H{}{}", rest, idx)
} else {
format!("H{}", name_parent)
}
}
_ => {
return Err(ParamError::new(
"Error assigning H type: Non-hetero parent, but missing AA.",
));
}
};
return Ok(Some(AtomTypeInRes::Hetero(val)));
};
let depth = match parent_tir {
AtomTypeInRes::CB => 'B',
AtomTypeInRes::CD | AtomTypeInRes::CD1 | AtomTypeInRes::CD2 => 'D',
AtomTypeInRes::CE | AtomTypeInRes::CE1 | AtomTypeInRes::CE2 | AtomTypeInRes::CE3 => 'E',
AtomTypeInRes::CG | AtomTypeInRes::CG1 | AtomTypeInRes::CG2 => 'G',
AtomTypeInRes::CH2 | AtomTypeInRes::CH3 => 'H',
AtomTypeInRes::CZ | AtomTypeInRes::CZ1 | AtomTypeInRes::CZ2 | AtomTypeInRes::CZ3 => 'Z',
AtomTypeInRes::OD1 | AtomTypeInRes::OD2 => 'D',
AtomTypeInRes::OG | AtomTypeInRes::OG1 | AtomTypeInRes::OG2 => 'G',
AtomTypeInRes::OH => 'H',
AtomTypeInRes::OE1 | AtomTypeInRes::OE2 => 'E',
AtomTypeInRes::ND1 | AtomTypeInRes::ND2 => 'D',
AtomTypeInRes::NH1 | AtomTypeInRes::NH2 => 'H',
AtomTypeInRes::NE | AtomTypeInRes::NE1 | AtomTypeInRes::NE2 => 'E',
AtomTypeInRes::NZ => 'Z',
AtomTypeInRes::SE => 'E',
AtomTypeInRes::SG => 'G',
AtomTypeInRes::OXT => 'X', _ => {
return Err(ParamError::new(&format!(
"Invalid parent type in res on H assignment. AA: {aa}. {parent_tir:?}",
)));
}
};
match aa {
AminoAcid::Thr => {
if *parent_tir == AtomTypeInRes::CG2 {
let digit = h_num_this_parent + 21;
return Ok(Some(AtomTypeInRes::H(format!("HG{digit}"))));
}
}
AminoAcid::Arg => {
if *parent_tir == AtomTypeInRes::NH2 {
let digit = h_num_this_parent + 21;
return Ok(Some(AtomTypeInRes::H(format!("HH{digit}"))));
}
}
AminoAcid::Phe => match parent_tir {
AtomTypeInRes::CD2 => {
let digit = h_num_this_parent + 2;
return Ok(Some(AtomTypeInRes::H(format!("HD{digit}"))));
}
AtomTypeInRes::CE2 => {
let digit = h_num_this_parent + 2;
return Ok(Some(AtomTypeInRes::H(format!("HE{digit}"))));
}
_ => (),
},
AminoAcid::Leu => {
if *parent_tir == AtomTypeInRes::CD2 {
let digit = h_num_this_parent + 21;
return Ok(Some(AtomTypeInRes::H(format!("HD{digit}"))));
}
}
AminoAcid::Ile => {
if *parent_tir == AtomTypeInRes::CG2 {
let digit = h_num_this_parent + 21;
return Ok(Some(AtomTypeInRes::H(format!("HG{digit}"))));
}
}
_ => (),
}
let Some(digits_this_aa) = h_digit_map.get(&aa) else {
return Err(ParamError::new(&format!(
"Missing AA {aa} in digits map, which has {:?}",
h_digit_map.keys()
)));
};
let Some(digits) = digits_this_aa.get(&depth) else {
return Ok(None);
};
let digit = {
let suffix_digit = format!("{:?}", parent_tir)
.chars()
.rev()
.find(|c| c.is_ascii_digit())
.and_then(|c| c.to_digit(10))
.map(|d| d as usize);
if let Some(sd) = suffix_digit {
if let Some(pos) = digits.iter().position(|&d| d == sd as u8) {
&digits[pos] } else if digits.iter().all(|&d| d < 10) {
return Ok(None);
} else {
digits
.get(h_num_this_parent)
.unwrap_or_else(|| &digits[digits.len() - 1])
}
} else {
digits
.get(h_num_this_parent)
.unwrap_or_else(|| &digits[digits.len() - 1])
}
};
let val = if *digit == 0 {
format!("H{depth}") } else {
format!("H{depth}{digit}")
};
let result = AtomTypeInRes::H(val);
if !validate_h_atom_type(depth, *digit, aa, h_digit_map)? {
return Err(ParamError::new(&format!(
"Invalid H type: {result} on {aa}. Parent: {parent_tir}"
)));
}
Ok(Some(result))
}
pub fn populate_hydrogens_dihedrals(
atoms: &mut Vec<AtomGeneric>,
residues: &mut [ResidueGeneric],
chains: &mut [ChainGeneric],
ff_map: &ProtFfChargeMapSet,
ph: f32,
) -> Result<Vec<Dihedral>, ParamError> {
init_local_bond_vecs();
let mut index_map = HashMap::new();
for (i, atom) in atoms.iter().enumerate() {
index_map.insert(atom.serial_number, i);
}
let mut dihedrals = Vec::with_capacity(residues.len());
let mut prev_cp_ca = None;
let res_len = residues.len();
let res_clone = residues.to_owned();
let digit_map = make_h_digit_map(&ff_map.internal, ph);
let mut highest_sn = 0;
for atom in atoms.iter() {
if atom.serial_number > highest_sn {
highest_sn = atom.serial_number;
}
}
let mut next_sn = highest_sn + 1;
for (res_i, res) in residues.iter_mut().enumerate() {
let mut n_next_pos = None;
if res_i < res_len - 1 {
let res_next = &res_clone[res_i + 1];
let n_next = res_next.atom_sns.iter().find(|i| {
if let Some(&idx) = index_map.get(*i) {
matches!(&atoms[idx].type_in_res, Some(tir) if *tir == AtomTypeInRes::N)
} else {
false
}
});
if let Some(n_next) = n_next {
if let Some(&idx) = index_map.get(n_next) {
n_next_pos = Some(atoms[idx].posit);
}
}
}
let atoms_this_res: Vec<&_> = res
.atom_sns
.iter()
.filter_map(|i| index_map.get(i).map(|&idx| &atoms[idx]))
.collect();
let (dihedral, h_added_this_res, this_cp_ca) = aa_data_from_coords(
&atoms_this_res,
&res_clone,
&res.res_type,
prev_cp_ca,
n_next_pos,
&digit_map,
)?;
let mut chain_i = 0;
if !atoms_this_res.is_empty() {
for (i, chain) in chains.iter().enumerate() {
if chain.atom_sns.contains(&atoms_this_res[0].serial_number) {
chain_i = i;
break;
}
}
}
for mut h in h_added_this_res {
h.serial_number = next_sn;
atoms.push(h);
index_map.insert(next_sn, atoms.len() - 1);
res.atom_sns.push(next_sn);
chains[chain_i].atom_sns.push(next_sn);
next_sn += 1;
}
prev_cp_ca = this_cp_ca;
dihedrals.push(dihedral);
}
Ok(dihedrals)
}