use std::{collections::HashMap, f32::consts::PI, io, io::ErrorKind};
use bio_files::{
AtomGeneric,
md_params::{AngleBendingParams, BondStretchingParams, DihedralParams, ForceFieldParams},
};
use crate::param_inference::{
frcmod_missing_params::MissingParams,
parmchk_parse::{AtCor, Corr, ParmChk, load_atcor},
};
const WILDCARD_PENALTY: f32 = 50.0;
pub fn assign_missing_params(
atoms: &[AtomGeneric],
adj_list: &[Vec<usize>],
gaff2: &ForceFieldParams,
) -> io::Result<ForceFieldParams> {
let mut result = ForceFieldParams::default();
let params_missing = MissingParams::new(atoms, adj_list, gaff2)?;
let atcor = load_atcor()?;
let parmchk = ParmChk::new()?;
let mut eq_map = HashMap::new();
eq_map.insert("cs".to_owned(), "c");
eq_map.insert("o2".to_owned(), "o");
eq_map.insert("nj".to_owned(), "n");
eq_map.insert("sq".to_owned(), "ss");
eq_map.insert("c6".to_owned(), "c3");
eq_map.insert("c5".to_owned(), "c3");
eq_map.insert("ns".to_owned(), "n");
eq_map.insert("n7".to_owned(), "n3");
eq_map.insert("n8".to_owned(), "n3");
eq_map.insert("n9".to_owned(), "n3");
eq_map.insert("nv".to_owned(), "nh");
eq_map.insert("pb".to_owned(), "p2");
for (t0, t1) in ¶ms_missing.bond {
let key = (t0.to_owned(), t1.to_owned());
if let Some(params) = find_bond_alts(gaff2, &parmchk, &atcor, &eq_map, &key) {
result.bond.insert(key, params);
} else {
return Err(io::Error::new(
ErrorKind::InvalidData,
format!("Unable to find a bondparam for {t0} {t1}"),
));
}
}
for (t0, t1, t2) in ¶ms_missing.angle {
let key = (t0.to_owned(), t1.to_owned(), t2.to_owned());
if let Some(params) = find_angle_alts(gaff2, &parmchk, &atcor, &eq_map, &key) {
result.angle.insert(key, params);
} else {
return Err(io::Error::new(
ErrorKind::InvalidData,
format!("Unable to find a valence angle param for {t0} {t1} {t2}"),
));
}
}
for (t0, t1, t2, t3) in ¶ms_missing.dihedral {
let key = (t0.to_owned(), t1.to_owned(), t2.to_owned(), t3.to_owned());
if let Some(params) = find_dihedral_alts(gaff2, &parmchk, &atcor, &eq_map, &key, true) {
result.dihedral.insert(key, params);
} else {
return Err(io::Error::new(
ErrorKind::InvalidData,
format!("Unable to find a dihedral param for {t0} {t1} {t2} {t3}"),
));
}
}
for (t0, t1, t2, t3) in ¶ms_missing.improper {
let key = (t0.to_owned(), t1.to_owned(), t2.to_owned(), t3.to_owned());
if let Some(params) = find_dihedral_alts(gaff2, &parmchk, &atcor, &eq_map, &key, false) {
result.improper.insert(key, params);
}
}
Ok(result)
}
fn find_bond_alts(
gaff2: &ForceFieldParams,
parmchk: &ParmChk,
atcor: &AtCor,
eq_map: &HashMap<String, &str>,
key: &(String, String),
) -> Option<BondStretchingParams> {
let key0 = match atcor.get(&key.0) {
Some(v) => v.1[0].clone(),
None => key.0.clone(),
};
let key1 = match atcor.get(&key.1) {
Some(v) => v.1[0].clone(),
None => key.1.clone(),
};
let key0 = match eq_map.get(&key0) {
Some(v) => *v,
None => &key0,
};
let key1 = match eq_map.get(&key1) {
Some(v) => *v,
None => &key1,
};
let corr_0_candidates: Vec<_> = parmchk.corr.iter().filter(|c| c.base == key0).collect();
let corr_1_candidates: Vec<_> = parmchk.corr.iter().filter(|c| c.base == key1).collect();
if let Some(param) = gaff2.get_bond(&(key0.to_owned(), key1.to_owned()), false) {
return Some(param.clone());
}
for c in &corr_0_candidates {
if let Some(param) = gaff2.get_bond(&(c.other.to_owned(), key1.to_owned()), false) {
return Some(param.clone());
}
}
for c in &corr_1_candidates {
if let Some(param) = gaff2.get_bond(&(key0.to_owned(), c.other.to_owned()), false) {
return Some(param.to_owned());
}
}
for c0 in &corr_0_candidates {
let c0 = match eq_map.get(&c0.other) {
Some(v) => *v,
None => &c0.other,
};
for c1 in &corr_1_candidates {
let c1 = match eq_map.get(&c1.other) {
Some(v) => *v,
None => &c1.other,
};
if let Some(param) = gaff2.get_bond(&(c0.to_owned(), c1.to_owned()), false) {
return Some(param.to_owned());
}
}
}
None
}
fn find_angle_alts(
gaff2: &ForceFieldParams,
parmchk: &ParmChk,
atcor: &AtCor,
eq_map: &HashMap<String, &str>,
key: &(String, String, String),
) -> Option<AngleBendingParams> {
let key0 = match atcor.get(&key.0) {
Some(v) => v.1[0].clone(),
None => key.0.clone(),
};
let key1 = match atcor.get(&key.1) {
Some(v) => v.1[0].clone(),
None => key.1.clone(),
};
let key2 = match atcor.get(&key.2) {
Some(v) => v.1[0].clone(),
None => key.2.clone(),
};
let key0 = match eq_map.get(&key0) {
Some(v) => *v,
None => &key0,
};
let key1 = match eq_map.get(&key1) {
Some(v) => *v,
None => &key1,
};
let key2 = match eq_map.get(&key2) {
Some(v) => *v,
None => &key2,
};
let corr_0_candidates: Vec<_> = parmchk.corr.iter().filter(|c| c.base == key0).collect();
let corr_1_candidates: Vec<_> = parmchk.corr.iter().filter(|c| c.base == key1).collect();
let corr_2_candidates: Vec<_> = parmchk.corr.iter().filter(|c| c.base == key2).collect();
if let Some(param) =
gaff2.get_valence_angle(&(key0.to_owned(), key1.to_owned(), key2.to_owned()), false)
{
return Some(param.clone());
}
for c in &corr_0_candidates {
if let Some(param) = gaff2.get_valence_angle(
&(c.other.to_owned(), key1.to_owned(), key2.to_owned()),
false,
) {
return Some(param.clone());
}
}
for c in &corr_1_candidates {
if let Some(param) = gaff2.get_valence_angle(
&(key0.to_owned(), c.other.to_owned(), key2.to_owned()),
false,
) {
return Some(param.to_owned());
}
}
for c in &corr_2_candidates {
if let Some(param) = gaff2.get_valence_angle(
&(key0.to_owned(), key1.to_owned(), c.other.to_owned()),
false,
) {
return Some(param.to_owned());
}
}
for c0 in &corr_0_candidates {
let c0 = match eq_map.get(&c0.other) {
Some(v) => *v,
None => &c0.other,
};
for c1 in &corr_1_candidates {
let c1 = match eq_map.get(&c1.other) {
Some(v) => *v,
None => &c1.other,
};
if let Some(param) =
gaff2.get_valence_angle(&(c0.to_owned(), key1.to_owned(), c1.to_owned()), false)
{
return Some(param.to_owned());
}
}
}
None
}
fn find_dihedral_alts(
gaff2: &ForceFieldParams,
parmchk: &ParmChk,
_atcor: &AtCor,
eq_map: &HashMap<String, &str>,
key: &(String, String, String, String),
proper: bool,
) -> Option<Vec<DihedralParams>> {
let key0 = key.0.clone();
let key1 = key.1.clone();
let key2 = key.2.clone();
let key3 = key.3.clone();
let key0 = match eq_map.get(&key0) {
Some(v) => *v,
None => &key0,
};
let key1 = match eq_map.get(&key1) {
Some(v) => *v,
None => &key1,
};
let key2 = match eq_map.get(&key2) {
Some(v) => *v,
None => &key2,
};
let key3 = match eq_map.get(&key3) {
Some(v) => *v,
None => &key3,
};
let corr_0_candidates: Vec<_> = parmchk.corr.iter().filter(|c| c.base == key0).collect();
let corr_1_candidates: Vec<_> = parmchk.corr.iter().filter(|c| c.base == key1).collect();
let corr_2_candidates: Vec<_> = parmchk.corr.iter().filter(|c| c.base == key2).collect();
let corr_3_candidates: Vec<_> = parmchk.corr.iter().filter(|c| c.base == key3).collect();
if let Some(param) = gaff2.get_dihedral(
&(
key0.to_owned(),
key1.to_owned(),
key2.to_owned(),
key3.to_owned(),
),
proper,
false,
) {
return Some(param.clone());
}
let best_no_wc = dihedral_inner(
key0,
key1,
key2,
key3,
&corr_0_candidates,
&corr_1_candidates,
&corr_2_candidates,
&corr_3_candidates,
gaff2,
parmchk,
eq_map,
proper,
false,
);
let best_wc = dihedral_inner(
key0,
key1,
key2,
key3,
&corr_0_candidates,
&corr_1_candidates,
&corr_2_candidates,
&corr_3_candidates,
gaff2,
parmchk,
eq_map,
proper,
true,
);
let _improper_default = DihedralParams {
atom_types: (
"X".to_string(),
"X".to_string(),
"X".to_string(),
"X".to_string(),
),
divider: 1,
barrier_height: 1.1,
phase: PI,
periodicity: 2,
comment: Some("Using the default value".to_owned()),
};
let best = match (best_no_wc, best_wc) {
(Some(b), None) => Some(b),
(None, Some(b)) => Some(b),
(Some(b1), Some(b2)) => {
if b1.0 <= b2.0 { Some(b1) } else { Some(b2) }
}
(None, None) => None,
};
if proper {
return best.map(|(_, params)| params);
}
if let Some((_, p)) = best {
return Some(p);
}
let key_for_default = (
key.0.as_str(),
key.1.as_str(),
key.2.as_str(),
key.3.as_str(),
);
if is_carbonyl_improper_candidate(key_for_default) {
let improper_carbonyl = DihedralParams {
atom_types: ("X".into(), "X".into(), "c".into(), "o".into()),
divider: 1,
barrier_height: 10.5,
phase: PI,
periodicity: 2,
comment: Some("Using general improper torsional angle X- X- c- o".to_owned()),
};
return Some(vec![improper_carbonyl]);
}
if is_default_improper_candidate(key_for_default) {
let improper_default = DihedralParams {
atom_types: ("X".into(), "X".into(), "X".into(), "X".into()),
divider: 1,
barrier_height: 1.1,
phase: PI,
periodicity: 2,
comment: Some("Using the default value".to_owned()),
};
return Some(vec![improper_default]);
}
None
}
fn is_carbonyl_improper_candidate(key: (&str, &str, &str, &str)) -> bool {
let (a, b, c, d) = key;
let ts = [a, b, c, d];
let has_c = ts.contains(&"c");
let has_o = ts.contains(&"o");
if !(has_c && has_o) {
return false;
}
let heavy_count = ts.iter().filter(|t| !is_hydrogen_ff_type(t)).count();
if heavy_count < 3 {
return false;
}
let sp2_like_count = ts.iter().filter(|t| is_sp2_like_ff_type(t)).count();
if sp2_like_count < 2 {
return false;
}
true
}
#[allow(clippy::too_many_arguments)]
fn dihedral_inner(
key0: &str,
key1: &str,
key2: &str,
key3: &str,
corr_0_candidates: &[&Corr],
corr_1_candidates: &[&Corr],
corr_2_candidates: &[&Corr],
corr_3_candidates: &[&Corr],
gaff2: &ForceFieldParams,
parmchk: &ParmChk,
eq_map: &HashMap<String, &str>,
proper: bool,
wildcard_allowed: bool,
) -> Option<(f32, Vec<DihedralParams>)> {
let mut best: Option<(f32, Vec<DihedralParams>)> = None;
let mut consider = |a: &str, b: &str, c: &str, d: &str| {
if let Some(params) = gaff2.get_dihedral(
&(a.to_owned(), b.to_owned(), c.to_owned(), d.to_owned()),
proper,
wildcard_allowed,
) {
let (gaff_a, gaff_b, gaff_c, gaff_d) = ¶ms[0].atom_types;
let wildcard_pen = if proper {
(gaff_a == "X") as u8 as f32 * WILDCARD_PENALTY
+ (gaff_d == "X") as u8 as f32 * WILDCARD_PENALTY
} else {
if gaff_a == "X" && gaff_b == "X" && gaff_c == "X" && gaff_d == "X" {
WILDCARD_PENALTY
} else {
0.0
}
};
let penalty = if proper {
parmchk.dihe_outer_penalty(key0, a)
+ parmchk.dihe_inner_penalty(key1, b)
+ parmchk.dihe_inner_penalty(key2, c)
+ parmchk.dihe_outer_penalty(key3, d)
+ wildcard_pen
} else {
parmchk.improper_penalty(key1, b) + wildcard_pen
};
match &mut best {
None => best = Some((penalty, params.clone())),
Some((best_p, best_params)) => {
if penalty < *best_p {
*best_p = penalty;
*best_params = params.clone();
}
}
}
}
};
if wildcard_allowed {
consider(key0, key1, key2, key3);
}
for c in corr_0_candidates {
consider(&c.other, key1, key2, key3);
}
for c in corr_1_candidates {
consider(key0, &c.other, key2, key3);
}
for c in corr_2_candidates {
consider(key0, key1, &c.other, key3);
}
for c in corr_3_candidates {
consider(key0, key1, key2, &c.other);
}
for c0 in corr_0_candidates {
let c0 = match eq_map.get(&c0.other) {
Some(v) => *v,
None => &c0.other,
};
for c1 in corr_1_candidates {
let c1 = match eq_map.get(&c1.other) {
Some(v) => *v,
None => &c1.other,
};
consider(c0, key1, key2, c1);
}
}
best
}
fn is_sp2_like_ff_type(t: &str) -> bool {
matches!(
t,
"c" | "c2" | "ca" | "cc" | "cd" | "ce" | "c6" |
"n1" | "n2" | "na" | "nb" | "nc" | "nd" | "ne" | "ns" |
"o" | "os" | "oh" | "op" | "oq" | "s" | "so" | "sx" | "sy"
)
}
fn is_hydrogen_ff_type(t: &str) -> bool {
matches!(
t,
"h1" | "h2" | "h3" | "ha" | "hc" | "hn" | "ho" | "hs" | "hp"
)
}
fn is_default_improper_candidate(key: (&str, &str, &str, &str)) -> bool {
let (a, b, c, d) = key;
let center = c;
if !is_sp2_like_ff_type(center) {
return false;
}
let neighbors = [a, b, d];
let heavy_neighbors = neighbors.iter().filter(|t| !is_hydrogen_ff_type(t)).count();
heavy_neighbors >= 2
}