use std::collections::HashSet;
use anyhow::{bail, Context, Result};
use log::info;
use molar::prelude::*;
pub(crate) fn command_solvate(
file: &str,
outfile: &str,
solvent: &Option<String>,
exclude: &Option<String>,
) -> Result<()> {
info!("Loading solute from file '{file}'...");
let mut solute = System::from_file(file)?;
if solute.get_box().is_none() {
bail!("can't solvate a system without a periodic box");
}
let solvent_file = match solvent {
Some(s) => s.clone(),
None => {
let gmx_dir = std::env::var("GMXDATA").with_context(|| {
"GMXDATA env variable is not set, use explicit path to the solvent file"
})?;
format!("{gmx_dir}/top/spc216.gro")
}
};
info!("Loading solvent from file '{solvent_file}'...");
let mut solvent = System::from_file(&solvent_file)?;
if solvent.get_box().is_none() {
bail!("solvent lacks a periodic box");
}
if solvent.get_box().unwrap().is_triclinic() {
bail!("triclinic solvent boxes are not supported yet");
}
let solute_max_ext = solute.get_box().unwrap().get_lab_extents();
let solvent_ext = solvent.get_box().unwrap().get_box_extents();
let mut nbox = [0; 3];
for i in 0..=2 {
nbox[i] = (solute_max_ext[i] / solvent_ext[i]).ceil() as usize;
}
info!(
"Solute box extents [{},{},{}]",
solute_max_ext[0], solute_max_ext[1], solute_max_ext[2]
);
info!(
"Solvent box extents [{},{},{}]",
solvent_ext[0], solvent_ext[1], solvent_ext[2]
);
info!(
"Duplicating solvent box [{},{},{}]...",
nbox[0], nbox[1], nbox[2]
);
solvent.multiply_periodically(nbox)?;
info!("Removing solvent molecules outside the box...");
let mut inside_ind = vec![];
let b = solute.get_box().unwrap();
let all = solvent.select_all_bound();
'outer: for res in all.split_resindex_bound() {
for p in res.iter_pos() {
if !b.is_inside(p) {
continue 'outer;
}
}
inside_ind.extend(res.iter_index());
}
let inside_sel = solvent.select_bound(inside_ind)?;
info!("Searching for overlapping solvent molecules...");
let vdw1 = inside_sel.iter_atoms().map(|a| a.vdw()).collect();
let vdw2 = solute.iter_atoms().map(|a| a.vdw()).collect();
let local_overlap_ind: Vec<usize> = distance_search_double_vdw_pbc(
inside_sel.iter_pos(),
solute.iter_pos(),
&vdw1,
&vdw2,
b,
PBC_FULL,
);
info!("{} overlapping atoms", local_overlap_ind.len());
let mut resind_to_remove = HashSet::new();
for i in local_overlap_ind {
unsafe {
resind_to_remove.insert(inside_sel.get_atom_unchecked(i).resindex);
}
}
info!(
"{} overlapping water molecules to remove",
resind_to_remove.len()
);
let mut good_ind = vec![];
for res in inside_sel.split_resindex_bound() {
if !resind_to_remove.contains(&res.first_atom().resindex) {
good_ind.extend(res.iter_index());
}
}
info!("{} water atoms to keep", good_ind.len());
let good_sel = solvent.select_bound(good_ind)?;
solute.append(&good_sel)?;
if exclude.is_some() {
let sel_str = exclude.as_ref().unwrap();
let not_excl_sel = solute.select_bound(format!("not ({})", sel_str))?;
info!("Excluding atoms by selection '{}'", sel_str);
not_excl_sel.save(outfile)?;
} else {
solute.save(outfile)?;
}
Ok(())
}