Obtaining an equilibrated initial trajectory

This is the first file to run in the alanine dipeptide TPS example. This teaches you how to:

  • set up an engine using OpenMM

  • set up states using MDTraj-based collective variables

  • obtain a initial trajectory using high temperature MD

  • equilibrate by using shooting moves until the first decorrelated trajectory

We assume at this point that you are familiar with the basic concepts of OPS. If you find this file confusing, we recommend working through the toy model examples.

[1]:
from __future__ import print_function
%matplotlib inline
import matplotlib.pyplot as plt
import openpathsampling as paths

import openpathsampling.engines.openmm as omm
from simtk.openmm import app
import simtk.openmm as mm
import simtk.unit as unit
from openmmtools.integrators import VVVRIntegrator

import mdtraj as md

import numpy as np

import os
initial_pdb = os.path.join("..", "resources", "AD_initial_frame.pdb")

Setting up high-temperature engine

Now we set things up for the OpenMM simulation. We will need a openmm.System object and an openmm.Integrator object.

To learn more about OpenMM, read the OpenMM documentation. The code we use here is based on output from the convenient web-based OpenMM builder.

[2]:
# this cell is all OpenMM specific
forcefield = app.ForceField('amber96.xml', 'tip3p.xml')
pdb = app.PDBFile(initial_pdb)
system = forcefield.createSystem(
    pdb.topology,
    nonbondedMethod=app.PME,
    nonbondedCutoff=1.0*unit.nanometers,
    constraints=app.HBonds,
    rigidWater=True,
    ewaldErrorTolerance=0.0005
)
hi_T_integrator = VVVRIntegrator(
    500*unit.kelvin,
    1.0/unit.picoseconds,
    2.0*unit.femtoseconds)
hi_T_integrator.setConstraintTolerance(0.00001)

The storage file will need a template snapshot. In addition, the OPS OpenMM-based Engine has a few properties and options that are set by these dictionaries.

[3]:
template = omm.snapshot_from_pdb(initial_pdb)
openmm_properties = {'OpenCLPrecision': 'mixed'}
engine_options = {
    'n_steps_per_frame': 10,
    'n_frames_max': 20000
}
[4]:
hi_T_engine = omm.Engine(
    template.topology,
    system,
    hi_T_integrator,
    openmm_properties=openmm_properties,
    options=engine_options
)
hi_T_engine.name = '500K'
[5]:
hi_T_engine.current_snapshot = template
hi_T_engine.minimize()

Defining states

First we define the CVs using the md.compute_dihedrals function. Then we define our states using PeriodicCVDefinedVolume (since our CVs are periodic.)

[6]:
# define the CVs
psi = paths.MDTrajFunctionCV(
    "psi", md.compute_dihedrals,
    topology=template.topology, indices=[[6,8,14,16]]
).enable_diskcache()
phi = paths.MDTrajFunctionCV(
    "phi", md.compute_dihedrals,
    topology=template.topology, indices=[[4,6,8,14]]
).enable_diskcache()
[7]:
# define the states
deg = 180.0/np.pi
C_7eq = (
    paths.PeriodicCVDefinedVolume(phi, lambda_min=-180/deg, lambda_max=0/deg,
                                  period_min=-np.pi, period_max=np.pi) &
    paths.PeriodicCVDefinedVolume(psi, lambda_min=100/deg, lambda_max=200/deg,
                                  period_min=-np.pi, period_max=np.pi)
).named("C_7eq")
# similarly, without bothering with the labels:
alpha_R = (
    paths.PeriodicCVDefinedVolume(phi, -180/deg, 0/deg, -np.pi, np.pi) &
    paths.PeriodicCVDefinedVolume(psi, -100/deg, 0/deg, -np.pi, np.pi)
).named("alpha_R")

Getting a first trajectory

We’ll use the VisitAllStatesEnsemble to create a high-temperature trajectory that visits all of our states. Here we only have 2 states, but this approach generalizes to multiple states.

[8]:
visit_all = paths.VisitAllStatesEnsemble([C_7eq, alpha_R])
trajectory = hi_T_engine.generate(hi_T_engine.current_snapshot, [visit_all.can_append])
Ran 2143 frames. Found states [alpha_R,C_7eq]. Looking for [].

Plotting the trajectory

[9]:
# create a network so we can use its ensemble to obtain an initial trajectory
# use all-to-all because initial traj can be A->B or B->A; will be reversed
tmp_network = paths.TPSNetwork.from_states_all_to_all([C_7eq, alpha_R])
[10]:
# take the subtrajectory matching the ensemble (only one ensemble, only one subtraj)
subtrajectories = []
for ens in tmp_network.analysis_ensembles:
    subtrajectories += ens.split(trajectory)
print(subtrajectories)
[Trajectory[33]]
[11]:
plt.plot(phi(trajectory), psi(trajectory), 'k.')
plt.plot(phi(subtrajectories[0]), psi(subtrajectories[0]), 'r')
[11]:
[<matplotlib.lines.Line2D at 0x151a20509ad0>]
../../_images/examples_alanine_dipeptide_tps_AD_tps_1_trajectory_18_1.png

Setting up the normal temperature engine

We’ll create another engine that uses a 300K integrator, and equilibrate to a 300K path from the 500K path.

[12]:
integrator = VVVRIntegrator(
    300*unit.kelvin,
    1.0/unit.picoseconds,
    2.0*unit.femtoseconds
)
integrator.setConstraintTolerance(0.00001)
engine = omm.Engine(
    template.topology,
    system,
    integrator,
    openmm_properties=openmm_properties,
    options=engine_options
).named('300K')

Using TPS to equilibrate

This is, again, a simple path sampling setup. We use the same TPSNetwork we’ll use later, and only shooting moves. One the initial conditions are correctly set up, we run one step at a time until the initial trajectory is decorrelated.

This setup of a path sampler always consists of defining a network and a move_scheme. See toy model notebooks for further discussion.

[13]:
network = paths.TPSNetwork(initial_states=C_7eq,
                           final_states=alpha_R).named('tps_network')
scheme = paths.OneWayShootingMoveScheme(network,
                                        selector=paths.UniformSelector(),
                                        engine=engine).named('equil_scheme')

The move scheme’s initial_conditions_from_trajectories method will take whatever input you give it and try to generate valid initial conditions. This includes looking for subtrajectories that satisfy the ensembles you’re sampling, as well as reversing the trajectory. If it succeeds, you’ll see the message “No missing ensembles. No extra ensembles.” It’s also a good practice to use scheme.assert_initial_conditions to ensure that your initial conditions are valid. This will halt a script if the initial conditions are not valid.

[14]:
# make subtrajectories into initial conditions (trajectories become a sampleset)
initial_conditions = scheme.initial_conditions_from_trajectories(subtrajectories)
No missing ensembles.
No extra ensembles.
[15]:
# check that initial conditions are valid and complete (raise AssertionError otherwise)
scheme.assert_initial_conditions(initial_conditions)

We can create a StepVisualizer2D, which plays the simulation as it is running. It requires CVs for the \(x\) direction and the \(y\) direction, as well as bounds of the plot. You can set the background attribute to an existing matplotlib Figure, and the simulation will plot on top of that. See the toy model MSTIS example for an example of that.

This isn’t necessary, and isn’t even a useful for long simulations (which typically won’t be in an interactive notebook), but it can be fun!

[16]:
visualizer = paths.StepVisualizer2D(network, phi, psi, xlim=[-3.14, 3.14],
                                    ylim=[-3.14, 3.14])
[17]:
sampler = paths.PathSampling(storage=paths.Storage("ad_tps_equil.nc", "w", template),
                             move_scheme=scheme,
                             sample_set=initial_conditions)
sampler.live_visualizer = visualizer
[18]:
# initially, these trajectories are correlated (actually, identical)
# once decorrelated, we have a (somewhat) reasonable 300K trajectory
initial_conditions[0].trajectory.is_correlated(sampler.sample_set[0].trajectory)
[18]:
True
[19]:
sampler.run_until_decorrelated()
../../_images/examples_alanine_dipeptide_tps_AD_tps_1_trajectory_30_0.png
Step 6: All trajectories decorrelated!
[20]:
# now we have decorrelated: no frames are in common
initial_conditions[0].trajectory.is_correlated(sampler.sample_set[0].trajectory)
[20]:
False
[21]:
# run an extra 10 to decorrelate a little futher
sampler.run(10)
../../_images/examples_alanine_dipeptide_tps_AD_tps_1_trajectory_32_0.png
DONE! Completed 15 Monte Carlo cycles.
[22]:
sampler.storage.close()

From here, you can either extend this to a longer trajectory for the fixed length TPS in the AD_tps_1b_trajectory.ipynb notebook, or go straight to flexible length TPS in the AD_tps_2a_run_flex.ipynb notebook.