I'm trying to setup a Multi State Transition Interface Sampling (MSTIS) simulation to study the ligand binding of my enzyme using OPS and GROMACS. My enzyme acts on small sugar chains and I would like to simulate which sugar unit it bound most often (roughly depicted in the picture below). I tried to run a Transition Path Sampling (TPS) simulation, but so far I was not able to obtain a decorrelated path:
According to my understanding so far, TIS might work better here since I think that this transition has a quite small probability. And if I include the other bound states as well, I'm hoping to get a good correlation between the transition frequencies here and those I observe in the lab.
I docked my substrate in two ways, once with the second (AaAA) and once with the third (AAaA) sugar unit (short A) bound to the active site (the sugar at the active site is indicated by a lower case a). Using gromacs, I created an initial trajectory for both pulling them away from the active site.
I load these trajectories as described by dwhswenson here. As the collective variables (CVs), I chose the distance of one atom in the corresponding sugar unit to the metal ion in my active center (dist_AAaA & dist_AaAA).
Below a distance of 0.25, I consider the ligand to be bound, while with a distance greater than 1.5 (for both distances), I consider the ligand to be unbound. Since the distance is almost never smaller than 0.2 and the two distances influence each other, the two states (bound_AAaA & bound_AaAA) are quite small in the plot shown above. The dotted lines should indicate the interfaces.
So far, my code looks like this:
import openpathsampling as paths
from openpathsampling.engines import gromacs as ops_gmx
import mdtraj as md
import numpy as np
# define the options for the MD simulation
options = {
'gmx_executable': 'gmx -nobackup ',
'snapshot_timestep': 0.02,
'n_frames_max': 200,
'grompp_args': '-n index.ndx',
'mdrun_args': '-nb gpu -nt 6'
}
# define the file names
trr_file_AAaA = "md_pull_AAaA_1st.trr"
trr_file_AaAA = "md_pull_AaAA_1st.trr"
pdb_file = "npt_AAaA_conf2.pdb"
top_file = "topol_AAaA+AaAA.top"
# setup the engine
TIS_engine = ops_gmx.Engine(gro=pdb_file,
mdp="TPS.mdp",
top=top_file,
options=options,
base_dir=".",
prefix="TIS").named("TIS_engine")
engine_setup = TIS_engine.current_snapshot
# define the CVs for all 3 states
dist_AAaA = paths.MDTrajFunctionCV(
"dist_AAaA", md.compute_distances, engine_setup.topology,
atom_pairs=[[3136,3180]], periodic=False)
dist_AaAA = paths.MDTrajFunctionCV(
"dist_AaAA", md.compute_distances, engine_setup.topology,
atom_pairs=[[3136,3207]], periodic=False)
dist_unbound = paths.MDTrajFunctionCV(
"dist_unbound", md.compute_distances, engine_setup.topology,
atom_pairs=[[3136,3192]], periodic=False)
# define the states
bound_AAaA = (paths.CVDefinedVolume(
dist_AAaA, 0, 0.25)).named("bound_AAaA")
bound_AaAA = (paths.CVDefinedVolume(
dist_AaAA, 0, 0.25)).named("bound_AaAA")
unbound = (paths.CVDefinedVolume(
dist_unbound, 1.5, 2)).named("unbound")
# define the interfaces
bound_AAaA_interface = paths.VolumeInterfaceSet(
dist_AAaA, 0, [0.25, 0.35, 0.45, 0.55])
bound_AaAA_interface = paths.VolumeInterfaceSet(
dist_AaAA, 0, [0.25, 0.35, 0.45, 0.55])
unbound_interface = paths.VolumeInterfaceSet(
dist_unbound, 2, [1.5, 1.3, 1.1, 0.9, 0.8, 0.7, 0.6])
# import the two inital trajectories
# calculate the number of frames in both input trajectories
n_frames_AAaA = len(md.load(trr_file_AAaA, top=pdb_file))
n_frames_AaAA = len(md.load(trr_file_AaAA, top=pdb_file))
# external_traj uses externally-stored snapshots
init_traj_AAaA = paths.Trajectory(
[TIS_engine.read_frame_from_file(trr_file_AAaA, num)
for num in range(n_frames_AAaA)])
init_traj_AaAA = paths.Trajectory(
[TIS_engine.read_frame_from_file(trr_file_AaAA, num)
for num in range(n_frames_AaAA)])
# create two networks to obtain the initial trajectories
tps_network_for_init_traj_AAaA = paths.TPSNetwork.from_states_all_to_all(
[bound_AAaA, unbound])
tps_network_for_init_traj_AaAA = paths.TPSNetwork.from_states_all_to_all(
[bound_AaAA, unbound])
# take the subtrajectories matching the ensemble
subtrajectories = []
for ens in tps_network_for_init_traj_AAaA.analysis_ensembles:
subtrajectories += ens.split(init_traj_AAaA)
for ens in tps_network_for_init_traj_AaAA.analysis_ensembles:
subtrajectories += ens.split(init_traj_AaAA)
# create a TIS network for the sampling itself
mstis_network = paths.MSTISNetwork(
[(bound_AAaA, bound_AAaA_interface),
(bound_AaAA, bound_AaAA_interface),
(unbound, unbound_interface)])
# define a move scheme for the TIS simulation
scheme = paths.OneWayShootingMoveScheme(mstis_network,
selector=paths.UniformSelector(),
engine=TIS_engine)
# make subtrajectories into initial conditions (trajectories become a sampleset)
initial_conditions = scheme.initial_conditions_from_trajectories(subtrajectories)
# check that initial conditions are valid and complete
# (raise AssertionError otherwise)
scheme.assert_initial_conditions(initial_conditions)
# setup the sampler
sampler = paths.PathSampling(
storage=paths.Storage("AAaA_AaAA_TIS.nc", "w", engine_setup),
move_scheme=scheme, sample_set=initial_conditions)
# sampler.run_until_decorrelated()
# run only 100 simulations for testing purposes
sampler.run(100)
sampler.storage.close()
The code runs without any error, but I'm not sure if the setup is correct since my resulting path tree looks like this:
As you may have noticed, I didn't setup the unbound state as described above since a certain CV is needed for the interface setup (unbound_interface = paths.VolumeInterfaceSet(CV,...)
). Here I used the distance to another atom in the center of my ligand.
I get how you would setup the unbound state depending on the two distances with
unbound = (paths.CVDefinedVolume(dist_AAaA, 1.5, 2) &
paths.CVDefinedVolume(dist_AaAA, 1.5, 2)).named("unbound")
but I don't know how to incorporate that into the interface setup. Preferably I would like to not use an unbound state at all since (at least from my understanding) it's not a stable state and I'm mainly interested in the transition towards to two bound states crossing the outer interface first and finally ending in the stable bound state. But I'm not sure if you could set it up like this. Maybe someone could comment on this.
Furthermore, I'm not sure if the rest of the setup is correct. I did not define an ms_outers
for the paths.MSTISNetwork
as I'm not sure what this would be in my case or how to correctly define it. Maybe this could be used to remove the unbound state?
And I would like to specify that I'm only interested in the transitions from the unbound to either of the bound states but not in the transition from one bound state to another bound state. So something similar to this:
tps_network = paths.TPSNetwork([unbound, bound_AAaA],[unbound, bound_AaAA])
I hope to get some help on these main issues, but if you have any comment regarding this setup or a link to a related paper this would be highly appreciated as well.
initial_states
andfinal_states
. So I think the setup you want isTPSNetwork(initial_states=[unbound], final_states=[bound_AaAA, bound_AAaA])
-- that is, always start unbound, and allow it to end in eitherAaAA
orAAaA
(of course, you can swap initial/final if you'd rather think of it from bound to unbound; that makes no difference to OPS.) $\endgroup$