#![allow(non_snake_case)]
#![allow(confusable_idents)]
mod add_hydrogens;
mod barostat;
mod bonded;
mod bonded_forces;
mod config;
mod forces;
pub mod integrate;
mod neighbors;
mod non_bonded;
pub mod params;
pub mod partial_charge_inference;
mod prep;
#[cfg(target_arch = "x86_64")]
mod simd;
pub mod snapshot;
mod solvent;
mod thermostat;
mod util;
mod com_zero;
#[cfg(feature = "cuda")]
mod gpu_interface;
pub mod minimize_energy;
pub mod alchemical;
pub mod param_inference;
mod sa_surface;
#[cfg(test)]
mod tests;
#[cfg(feature = "cuda")]
use std::sync::Arc;
use std::{
collections::HashSet,
fmt,
fmt::{Display, Formatter},
io,
path::Path,
};
pub use add_hydrogens::{
add_hydrogens_2::Dihedral,
bond_vecs::{find_planar_posit, find_tetra_posit_final, find_tetra_posits},
populate_hydrogens_dihedrals,
};
use barostat::SimBox;
#[cfg(feature = "encode")]
use bincode::{Decode, Encode};
use bio_files::{
AtomGeneric, BondGeneric, Sdf, dcd::DcdTrajectory, md_params::ForceFieldParams, mol2::Mol2,
};
pub use bonded::{LINCS_ITER_DEFAULT, LINCS_ORDER_DEFAULT, SHAKE_TOL_DEFAULT};
pub use config::MdConfig;
#[cfg(feature = "cuda")]
use cudarc::driver::{CudaFunction, CudaStream};
use ewald::PmeRecip;
pub use integrate::Integrator;
#[allow(unused)]
#[cfg(any(target_arch = "x86", target_arch = "x86_64"))]
use lin_alg::f32::{Vec3x8, Vec3x16, f32x8, f32x16};
use lin_alg::{f32::Vec3, f64::Vec3 as Vec3F64};
use na_seq::Element;
use neighbors::NeighborsNb;
pub use prep::{HydrogenConstraint, merge_params};
pub use solvent::{
ForcesOnWaterMol, Solvent,
init::{WATER_TEMPLATE_60A, WaterInitTemplate},
};
#[cfg(feature = "cuda")]
use crate::gpu_interface::{ForcesPositsGpu, PerNeighborGpu};
use crate::{
barostat::Barostat,
non_bonded::{CHARGE_UNIT_SCALER, LjTables, NonBondedPair},
params::{FfParamSet, ForceFieldParamsIndexed},
snapshot::Snapshot,
solvent::{WaterMol, WaterMolx8, WaterMolx16},
util::ComputationTimeSums,
};
pub use crate::{
barostat::{BarostatCfg, PRESSURE_DEFAULT, TAU_PRESSURE_DEFAULT},
thermostat::{LANGEVIN_GAMMA_DEFAULT, TAU_TEMP_DEFAULT},
};
#[cfg(feature = "cuda")]
const PTX: &str = include_str!("../dynamics.ptx");
const KCAL_TO_NATIVE: f32 = 418.4;
const NATIVE_TO_KCAL: f32 = 1. / KCAL_TO_NATIVE;
const CENTER_SIMBOX_RATIO: usize = 30;
const SPME_RATIO: usize = 2;
const COMPUTATION_TIME_RATIO: usize = 20;
#[derive(Debug, Clone, Default)]
pub enum ComputationDevice {
#[default]
Cpu,
#[cfg(feature = "cuda")]
Gpu(Arc<CudaStream>),
}
#[derive(Clone, Debug)]
pub struct ParamError {
pub descrip: String,
}
impl ParamError {
pub fn new(descrip: &str) -> Self {
Self {
descrip: descrip.to_owned(),
}
}
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum FfMolType {
Peptide,
SmallOrganic,
Dna,
Rna,
Lipid,
Carbohydrate,
}
#[derive(Clone, Debug)]
pub struct MolDynamics {
pub ff_mol_type: FfMolType,
pub atoms: Vec<AtomGeneric>,
pub atom_posits: Option<Vec<Vec3F64>>,
pub atom_init_velocities: Option<Vec<Vec3>>,
pub bonds: Vec<BondGeneric>,
pub adjacency_list: Option<Vec<Vec<usize>>>,
pub static_: bool,
pub mol_specific_params: Option<ForceFieldParams>,
pub bonded_only: bool,
}
impl Default for MolDynamics {
fn default() -> Self {
Self {
ff_mol_type: FfMolType::SmallOrganic,
atoms: Vec::new(),
atom_posits: None,
atom_init_velocities: None,
bonds: Vec::new(),
adjacency_list: None,
static_: false,
mol_specific_params: None,
bonded_only: false,
}
}
}
impl MolDynamics {
pub fn from_mol2(mol: &Mol2, mol_specific_params: Option<ForceFieldParams>) -> Self {
Self {
ff_mol_type: FfMolType::SmallOrganic,
atoms: mol.atoms.clone(),
atom_posits: None,
atom_init_velocities: None,
bonds: mol.bonds.clone(),
adjacency_list: None,
static_: false,
mol_specific_params,
bonded_only: false,
}
}
pub fn from_sdf(mol: &Sdf, mol_specific_params: Option<ForceFieldParams>) -> Self {
Self {
ff_mol_type: FfMolType::SmallOrganic,
atoms: mol.atoms.clone(),
atom_posits: None,
atom_init_velocities: None,
bonds: mol.bonds.clone(),
adjacency_list: None,
static_: false,
mol_specific_params,
bonded_only: false,
}
}
pub fn from_amber_geostd(ident: &str) -> io::Result<Self> {
let data = bio_apis::amber_geostd::load_mol_files(ident)
.map_err(|e| io::Error::other(format!("Error loading data: {e:?}")))?;
let mol = Mol2::new(&data.mol2)?;
let params = ForceFieldParams::from_frcmod(&data.frcmod.unwrap())?;
Ok(Self {
ff_mol_type: FfMolType::SmallOrganic,
atoms: mol.atoms,
atom_posits: None,
atom_init_velocities: None,
bonds: mol.bonds,
adjacency_list: None,
static_: false,
mol_specific_params: Some(params),
bonded_only: false,
})
}
}
#[derive(Clone, Debug, Default)]
pub struct AtomDynamics {
pub serial_number: u32,
pub static_: bool,
pub bonded_only: bool,
pub force_field_type: String,
pub element: Element,
pub posit: Vec3,
pub vel: Vec3,
pub accel: Vec3,
pub force: Vec3,
pub mass: f32,
pub partial_charge: f32,
pub lj_sigma: f32,
pub lj_eps: f32,
}
impl Display for AtomDynamics {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"Atom {}: {}, {}. ff: {}, q: {}",
self.serial_number,
self.element.to_letter(),
self.posit,
self.force_field_type,
self.partial_charge,
)?;
if self.static_ {
write!(f, ", Static")?;
}
Ok(())
}
}
impl AtomDynamics {
pub fn new(
atom: &AtomGeneric,
atom_posits: &[Vec3],
i: usize,
static_: bool,
bonded_only: bool,
) -> Result<Self, ParamError> {
let ff_type = match &atom.force_field_type {
Some(ff_type) => ff_type.clone(),
None => {
return Err(ParamError::new(&format!(
"Atom missing FF type; can't run dynamics: {:?}",
atom
)));
}
};
let partial_charge = match atom.partial_charge {
Some(p) => p * CHARGE_UNIT_SCALER,
None => return Err(ParamError::new("Missing partial charge on atom {i}")),
};
Ok(Self {
serial_number: atom.serial_number,
static_,
bonded_only,
element: atom.element,
posit: atom_posits[i],
force_field_type: ff_type,
partial_charge,
..Default::default()
})
}
pub(crate) fn assign_data_from_params(
&mut self,
ff_params: &ForceFieldParamsIndexed,
i: usize,
) {
self.mass = ff_params.mass[&i].mass;
self.lj_sigma = ff_params.lennard_jones[&i].sigma;
self.lj_eps = ff_params.lennard_jones[&i].eps;
}
}
#[allow(unused)]
#[cfg(target_arch = "x86_64")]
#[derive(Clone, Debug)]
pub(crate) struct AtomDynamicsx8 {
pub serial_number: [u32; 8],
pub static_: [bool; 8],
pub bonded_only: [bool; 8],
pub force_field_type: [String; 8],
pub element: [Element; 8],
pub posit: Vec3x8,
pub vel: Vec3x8,
pub accel: Vec3x8,
pub mass: f32x8,
pub partial_charge: f32x8,
pub lj_sigma: f32x8,
pub lj_eps: f32x8,
}
#[allow(unused)]
#[cfg(target_arch = "x86_64")]
#[derive(Clone, Debug)]
pub(crate) struct AtomDynamicsx16 {
pub serial_number: [u32; 16],
pub static_: [bool; 16],
pub bonded_only: [bool; 16],
pub force_field_type: [String; 16],
pub element: [Element; 16],
pub posit: Vec3x16,
pub vel: Vec3x16,
pub accel: Vec3x16,
pub mass: f32x16,
pub partial_charge: f32x16,
pub lj_sigma: f32x16,
pub lj_eps: f32x16,
}
#[cfg_attr(feature = "encode", derive(Encode, Decode))]
#[derive(Debug, Clone, PartialEq)]
pub enum SimBoxInit {
Pad(f32),
Fixed((Vec3, Vec3)),
}
impl SimBoxInit {
pub fn new_cube(side_len: f32) -> Self {
let l = side_len / 2.;
Self::Fixed((Vec3::new(-l, -l, -l), Vec3::new(l, l, l)))
}
}
impl Default for SimBoxInit {
fn default() -> Self {
Self::Pad(12.)
}
}
#[derive(Clone, Default, Debug, PartialEq)]
#[cfg_attr(feature = "encode", derive(Encode, Decode))]
pub struct MdOverrides {
pub skip_water_relaxation: bool,
pub bonded_disabled: bool,
pub coulomb_disabled: bool,
pub lj_disabled: bool,
pub long_range_recip_disabled: bool,
pub snapshots_during_equilibration: bool,
pub snapshots_during_energy_min: bool,
}
#[derive(Default)]
pub struct MdState {
pub cfg: MdConfig,
pub atoms: Vec<AtomDynamics>,
#[allow(unused)]
#[cfg(target_arch = "x86_64")]
pub(crate) atoms_x8: Vec<AtomDynamicsx8>,
#[allow(unused)]
#[cfg(target_arch = "x86_64")]
pub(crate) atoms_x16: Vec<AtomDynamicsx16>,
pub water: Vec<WaterMol>,
#[allow(unused)]
#[cfg(target_arch = "x86_64")]
pub(crate) water_x8: Vec<WaterMolx8>,
#[allow(unused)]
#[cfg(target_arch = "x86_64")]
pub(crate) water_x16: Vec<WaterMolx16>,
pub adjacency_list: Vec<Vec<usize>>,
pub(crate) force_field_params: ForceFieldParamsIndexed,
pub time: f64,
pub step_count: usize, pub snapshots: Vec<Snapshot>,
pub cell: SimBox,
pub neighbors_nb: NeighborsNb,
nb_pairs: Vec<NonBondedPair>,
barostat: Barostat,
pairs_excluded_12_13: HashSet<(usize, usize)>,
pairs_14_scaled: HashSet<(usize, usize)>,
lj_tables: LjTables,
pub water_pme_sites_forces: Vec<[Vec3F64; 3]>, pme_recip: Option<PmeRecip>,
pub kinetic_energy: f64,
pub potential_energy: f64,
pub potential_energy_nonbonded: f64,
pub potential_energy_bonded: f64,
pub potential_energy_between_mols: Vec<f64>,
snapshot_queue_for_dcd: Vec<Snapshot>,
snapshot_queue_for_trr: Vec<Snapshot>,
snapshot_queue_for_xtc: Vec<Snapshot>,
#[cfg(feature = "cuda")]
gpu_kernel: Option<CudaFunction>, #[cfg(feature = "cuda")]
gpu_kernel_zero_f32: Option<CudaFunction>,
#[cfg(feature = "cuda")]
gpu_kernel_zero_f64: Option<CudaFunction>,
#[cfg(feature = "cuda")]
forces_posits_gpu: Option<ForcesPositsGpu>,
#[cfg(feature = "cuda")]
per_neighbor_gpu: Option<PerNeighborGpu>,
pub neighbor_rebuild_count: usize,
mass_accel_factor: Vec<f32>,
pub computation_time: ComputationTimeSums,
spme_force_prev: Option<(Vec<Vec3>, f64, f64)>,
_num_static_atoms: usize,
thermo_dof: usize,
pub mol_start_indices: Vec<usize>,
solvent_only_sim_at_init: bool,
pub alch_mol_idx: Option<usize>,
pub lambda: f64,
pub(crate) run_index: usize,
}
impl Display for MdState {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(
f,
"MdState. # Snapshots: {}. # steps: {} Current time: {}. # of dynamic atoms: {}. # Water mols: {}",
self.snapshots.len(),
self.step_count,
self.time,
self.atoms.len(),
self.water.len()
)
}
}
pub(crate) fn split2_mut<T>(v: &mut [T], i: usize, j: usize) -> (&mut T, &mut T) {
assert!(i != j);
let (low, high) = if i < j { (i, j) } else { (j, i) };
let (left, right) = v.split_at_mut(high);
(&mut left[low], &mut right[0])
}
fn split3_mut<T>(v: &mut [T], i: usize, j: usize, k: usize) -> (&mut T, &mut T, &mut T) {
let len = v.len();
assert!(i < len && j < len && k < len, "index out of bounds");
assert!(i != j && i != k && j != k, "indices must be distinct");
let ptr = v.as_mut_ptr();
unsafe {
let a = &mut *ptr.add(i);
let b = &mut *ptr.add(j);
let c = &mut *ptr.add(k);
(a, b, c)
}
}
pub(crate) fn split4_mut<T>(
slice: &mut [T],
i0: usize,
i1: usize,
i2: usize,
i3: usize,
) -> (&mut T, &mut T, &mut T, &mut T) {
let len = slice.len();
assert!(
i0 < len && i1 < len && i2 < len && i3 < len,
"index out of bounds"
);
assert!(
i0 != i1 && i0 != i2 && i0 != i3 && i1 != i2 && i1 != i3 && i2 != i3,
"indices must be pair-wise distinct"
);
unsafe {
let base = slice.as_mut_ptr();
(
&mut *base.add(i0),
&mut *base.add(i1),
&mut *base.add(i2),
&mut *base.add(i3),
)
}
}
pub fn compute_energy_snapshot(
dev: &ComputationDevice,
mols: &[MolDynamics],
param_set: &FfParamSet,
) -> Result<Snapshot, ParamError> {
let cfg = MdConfig {
integrator: Integrator::VerletVelocity { thermostat: None },
hydrogen_constraint: HydrogenConstraint::Flexible,
max_init_relaxation_iters: None,
solvent: Solvent::None,
..Default::default()
};
let mut md_state = MdState::new(dev, &cfg, mols, param_set)?;
let dt = 0.001;
md_state.step(dev, dt, None);
if md_state.snapshots.is_empty() {
return Err(ParamError {
descrip: String::from("Snapshots empty on energy compuptation"),
});
}
Ok(md_state.snapshots[0].clone())
}