Skip to main content

lig_and_protein/
lig_and_protein.rs

1//! Taken from molchanica. Shows how to bind application-specific data structures to
2//! this library's API.
3
4use std::{path::Path, time::Instant};
5
6use bio_files::{MmCif, Sdf, create_bonds, md_params::ForceFieldParams};
7use dynamics::{
8    BarostatCfg, ComputationDevice, FfMolType, HydrogenConstraint, Integrator, MdConfig, MdState,
9    MolDynamics, ParamError, SimBoxInit,
10    params::{FfParamSet, prepare_peptide_mmcif},
11    snapshot::{Snapshot, SnapshotHandlers},
12};
13use lin_alg::f64::Vec3;
14
15// Å. Static atoms must be at least this close to a dynamic atom at the start of MD to be counted.
16// Set this wide to take into account motion.
17const STATIC_ATOM_DIST_THRESH: f64 = 8.;
18
19/// Perform MD on the ligand, with nearby protein (receptor) atoms, from the docking setup as static
20/// non-bonded contributors. (Vdw and coulomb)
21pub fn build_dynamics(
22    dev: &ComputationDevice,
23    ligs: Vec<&mut Sdf>,
24    peptide: &MmCif,
25    param_set: &FfParamSet,
26    cfg: &MdConfig,
27    n_steps: u32,
28    dt: f32,
29) -> Result<MdState, ParamError> {
30    println!("Setting up dynamics...");
31
32    let mut mols = Vec::new();
33
34    for lig in &ligs {
35        mols.push(MolDynamics {
36            ff_mol_type: FfMolType::SmallOrganic,
37            atoms: lig.atoms.clone(),
38            atom_posits: None,
39            atom_init_velocities: None,
40            bonds: lig.bonds.clone(),
41            // Adj list is created atomatically, but can be passed from a cache.
42            adjacency_list: None,
43            static_: false,
44            mol_specific_params: None,
45            bonded_only: false,
46            // Or: ..Default::default()
47        });
48    }
49
50    // A demonstration of only selecting peptide atoms near a ligand.
51    // We assume hetero atoms are ligands, solvent etc, and are not part of the protein.
52    let atoms: Vec<_> = peptide
53        .atoms
54        .iter()
55        .filter(|a| {
56            let mut closest_dist = f64::MAX;
57            for lig in &ligs {
58                // You should probably use a special position set vs the native atom positions.
59                for a in &lig.atoms {
60                    let posit = a.posit;
61                    let dist = (posit - a.posit).magnitude();
62                    if dist < closest_dist {
63                        closest_dist = dist;
64                    }
65                }
66            }
67
68            !a.hetero && closest_dist < STATIC_ATOM_DIST_THRESH
69        })
70        .map(|a| a.clone())
71        .collect();
72
73    let bonds = create_bonds(&atoms);
74
75    mols.push(MolDynamics {
76        ff_mol_type: FfMolType::Peptide,
77        atoms,
78        bonds,
79        static_: true,
80        ..Default::default()
81    });
82
83    // See also: `MolDynamics::from_sdf()`, `::from_mol2()`, and `::from_amber_geostd("CPB")`
84
85    println!("Initializing MD state...");
86    let (mut md_state, _) = MdState::new(dev, cfg, &mols, param_set)?;
87    println!("Done.");
88
89    let start = Instant::now();
90
91    for _ in 0..n_steps {
92        md_state.step(dev, dt, None);
93    }
94
95    let elapsed = start.elapsed();
96    println!("MD complete in {:.2} s", elapsed.as_secs());
97
98    change_snapshot(ligs, &md_state.snapshots[0]);
99
100    Ok(md_state)
101}
102
103// todo: This is so annoying. &[T] vs [&T].
104/// Set atom positions for molecules involve in dynamics to that of a snapshot.
105pub fn change_snapshot(ligs: Vec<&mut Sdf>, snapshot: &Snapshot) {
106    // todo: Handle peptide too!
107
108    // todo: QC this logic.
109
110    // Unflatten.
111    let mut start_i_this_mol = 0;
112
113    for lig in ligs {
114        // todo: A/R, for your system that manages atom positions.
115        let mut atom_posits = vec![Vec3::new_zero(); lig.atoms.len()];
116
117        for (i_snap, posit) in snapshot.atom_posits.iter().enumerate() {
118            if i_snap < start_i_this_mol || i_snap >= atom_posits.len() + start_i_this_mol {
119                continue;
120            }
121            atom_posits[i_snap - start_i_this_mol] = (*posit).into();
122        }
123
124        start_i_this_mol += atom_posits.len();
125    }
126}
127
128fn main() {
129    let dev = ComputationDevice::Cpu;
130    let param_set = FfParamSet::new_amber().unwrap();
131
132    let mut protein = MmCif::load(Path::new("1c8k.cif")).unwrap();
133    // let mut mol = Mol2::load(Path::new("CPB.mol2")).unwrap();
134    let mut mol = Sdf::load(Path::new("123.sdf")).unwrap();
135    // Optional; the library infers FRCMOD overrides on its own.
136    let _mol_specific = ForceFieldParams::load_frcmod(Path::new("CPB.frcmod")).unwrap();
137
138    // Or, instead of loading atoms and mol-specific params separately:
139    // let (mol, lig_specific) = load_prmtop("my_mol.prmtop");
140
141    // Add Hydrogens, force field type, and partial charge to atoms in the protein; these usually aren't
142    // included from RSCB PDB. You can also call `populate_hydrogens_dihedrals()`, and
143    // `populate_peptide_ff_and_q() separately. Add bonds.
144    let (_bonds, _dihedrals) = prepare_peptide_mmcif(
145        &mut protein,
146        &param_set.peptide_ff_q_map.as_ref().unwrap(),
147        7.0,
148    )
149    .unwrap();
150
151    // A variant of that function called `prepare_peptide` takes separate atom, residue, and chain
152    // lists, for flexibility.
153
154    let cfg = MdConfig {
155        // Defaults to Langevin middle.
156        integrator: Integrator::VerletVelocity { thermostat: None },
157        // If enabled, zero the drift in center of mass of the system.
158        zero_com_drift: true,
159        // Kelvin. Defaults to 310 K.
160        temp_target: 310.,
161        // Bar (Pa/100). Defaults to 1 bar.
162        barostat_cfg: Some(BarostatCfg {
163            pressure_target: 1.,
164            ..Default::default()
165        }),
166        // Allows constraining Hydrogens to be rigid with their bonded atom, using SHAKE and RATTLE
167        // algorithms. This allows for higher time steps.
168        hydrogen_constraint: HydrogenConstraint::Linear { order: 4, iter: 1 },
169        // Deafults to in-memory, every step
170        snapshot_handlers: SnapshotHandlers {
171            memory: Some(1),
172            dcd: Some(10),
173            ..Default::default()
174        },
175        // Or sim_box: SimBoxInit::Fixed((Vec3::new(-10., -10., -10.), Vec3::new(10., 10., 10.)),
176        sim_box: SimBoxInit::Pad(10.),
177        ..Default::default()
178    };
179
180    let _md = build_dynamics(&dev, vec![&mut mol], &protein, &param_set, &cfg, 100, 0.001);
181}