NaN Handling in Engines
Developers of DynamicsEngine
subclasses have several options for
how their engines should behave if they encounter a NaN
.
How to deal with errors in engines¶
Imports¶
import openpathsampling as paths
import openpathsampling.engines.openmm as dyn_omm
import openpathsampling.engines as dyn
from simtk.openmm import app
import simtk.openmm as mm
import simtk.unit as unit
import mdtraj as md
import numpy as np
Setting up the 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.
# this cell is all OpenMM specific
forcefield = app.ForceField('amber96.xml', 'tip3p.xml')
pdb = app.PDBFile("../resources/AD_initial_frame.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 = mm.LangevinIntegrator(
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.
template = dyn_omm.snapshot_from_pdb("../resources/AD_initial_frame.pdb")
openmm_properties = {'OpenCLPrecision': 'mixed'}
engine_options = {
'n_frames_max': 2000,
'nsteps_per_frame': 10
}
engine = dyn_omm.Engine(
template.topology,
system,
hi_T_integrator,
openmm_properties=openmm_properties,
options=engine_options
).named('500K')
engine.initialize('OpenCL')
Defining states¶
We define stupid non-existant states which we can never hit. Good grounds to generate nan or too long trajectories.
volA = paths.EmptyVolume()
volB = paths.EmptyVolume()
init_traj_ensemble = paths.AllOutXEnsemble(volA) | paths.AllOutXEnsemble(volB)
Create a bad snapshot
nan_causing_template = template.copy()
kinetics = template.kinetics.copy()
# this is crude but does the trick
kinetics.velocities = kinetics.velocities.copy()
kinetics.velocities[0] = \
(np.zeros(template.velocities.shape[1]) + 1000000.) * \
unit.nanometers / unit.picoseconds
nan_causing_template.kinetics = kinetics
# generate trajectory that includes frame in both states
try:
trajectory = engine.generate(nan_causing_template, [init_traj_ensemble.can_append])
except dyn.EngineNaNError as e:
print 'we got NaNs, oh no.'
print 'last valid trajectory was of length %d' % len(e.last_trajectory)
except dyn.EngineMaxLengthError as e:
print 'we ran into max length.'
print 'last valid trajectory was of length %d' % len(e.last_trajectory)
Now we will make a long trajectory
engine.options['n_frames_max'] = 10
engine.options['on_max_length'] = 'fail'
# generate trajectory that includes frame in both states
try:
trajectory = engine.generate(template, [init_traj_ensemble.can_append])
except dyn.EngineNaNError as e:
print 'we got NaNs, oh no.'
print 'last valid trajectory was of length %d' % len(e.last_trajectory)
except dyn.EngineMaxLengthError as e:
print 'we ran into max length.'
print 'last valid trajectory was of length %d' % len(e.last_trajectory)
What, if that happens inside of a simulation?
mover = paths.ForwardShootMover(
ensemble=init_traj_ensemble,
selector=paths.UniformSelector(),
engine=engine)
Should run indefinitely and hit the max frames of 10.
init_sampleset = paths.SampleSet([paths.Sample(
trajectory=paths.Trajectory([template] * 5),
replica=0,
ensemble = init_traj_ensemble
)])
Run the PathMover and check the change
change = mover.move(init_sampleset)
assert(isinstance(change, paths.movechange.RejectedMaxLengthSampleMoveChange))
assert(not change.accepted)
change.samples[0].details.__dict__.get('stopping_reason')
Let's try again what happens when nan is encountered
init_sampleset = paths.SampleSet([paths.Sample(
trajectory=paths.Trajectory([nan_causing_template] * 5),
replica=0,
ensemble = init_traj_ensemble
)])
change = mover.move(init_sampleset)
assert(isinstance(change, paths.movechange.RejectedNaNSampleMoveChange))
assert(not change.accepted)
change.samples[0].details.__dict__.get('stopping_reason')
Change the behaviour of the engine to ignore nans. This is really not advised, because not all platforms support this. CPU
will always throw an nan
error and
engine.options['on_nan'] = 'ignore'
engine.options
change = mover.move(init_sampleset)