Perhaps you have a problem which is complicated enough that you need to add major new functionality to OpenPathSampling. In general, we suggest that you first see whether the functionality you want to add can be done using the tools introduced at the power user level, but if not, you can implement useful subclasses by following these instructions.
Many tools already exist to generate the trajectories that our path sampling methods are based on, and we see no reason to reimplement what has already been better implemented by others. In addition, the tools we provide in OPS are independent of the nature of the underlying trajectories.
Because of these to points, we interact with the simulation layer, which generates trajectories, using a very simple API.
To add support for a new dynamics engine, you must create a subclass of our
DynamicsEngine object. This subclass must implement the following features:
@propertycalledSubclass.current_snapshot: getting this returns the current state of the simulation in the form of anopenpathsampling.Snapshotobject. Setting initializes a simulation in the state given by theopenpathsampling.Snapshot.next_frame(): a function that returns the next saved frame from the simulationstop(): a function that tells the simulation to stop. For simulations where the frame generation is under direct control, this is often a no-op. However, if your simulation uses an external process to generate frames, it is likely that you'll continue generating frames after reaching the stopping point. This function is where you do the clean-up.- The type signature for
__init__must include argumentsfilename(for the netCDF output file),mode(which determines whether the file should be written or just read), andopts(a dictionary which contains all the options for the engine). Callingsuperwith these arguments will open the storage file and will also set all the options inoptsas attributes of the object.
The most obvious way to combine OPS with another engine is if that engine
has a convenient API such that you can, in one function call, obtain the
next frame. This is what we mean by "direct control." For a very simple
example of direct control, see our ToyDynamicsEngine. The OpenMMEngine
provides a much more powerful (and more complicated) interface with direct
control.
However, in some situations you can't exercise direct control over the simulation: it's quite possible that your dynamics engine doesn't provide a Python API. Such situations are more complicated; examples will be developed in the next version of OPS.
Path sampling approaches are fundamentally based on sampling path ensembles.
Before trying to create your own Ensemble subclass, we strongly recommend
you see if your goal can be met by creative usage of the path ensemble
classes we've already created. However, if that isn't possible, you can
always create a new subclass.
Such an object must be a subclass of Ensemble, and it must implement the
following methods:
__call__(trajectory): determine whether the given trajectory is in the ensemble.can_append(trajectory): determine whether appending to the given trajectory would make it impossible for further appending to be in the ensemble. Another way of thinking about this is to call this the logical "not" of the forward stop condition: stop propagating the trajectory forward ifcan_appendreturnsFalse.can_prepend(trajectory): as withcan_append, but in the direction of decreasing time.__not__(): return the set-logical "not" of the ensemble subclass. That is to say, all trajectories (except the zero-length trajectory) should either be inensemble_subclass(trajectory)orensemble_subclass.__not__(trajectory). (While not strictly necessary in version 1.0, implementation of__not__()is highly recommended for future compatibility.)
As with ensembles, we have very powerful facilities for creating new
PathMovers without resorting to creating new subclasses. However, if you
must create a new subclass, here's what you should understand about
PathMovers.
First, the fundamental object that goes into each move as input is a
SampleSet, which is (at heart) a list of Samples. Samples consist of a
trajectory, an identifier for that trajectory traj_id, an ensemble,
and further details about how that trajectory works. Typically, the move
takes as input a SampleSet with active samples (for example, one sample
for each ensemble in RETIS).
PathMovers can, in general, be split into two categories: "control"
and "change". "Change" PathMovers actually generate new Samples, whereas
"Control" PathMovers affect which other PathMovers are called. See the
power user discussion of PathMovers for more details: it is very important
to understand what is feasible there before trying to implement a new
PathMover with the more difficult techniques described here.
??? something about the MovePaths, and then the .move function ???
The PathSimulator API is by far the simplest. The PathSimulator is
basically what you would normally consider to be the "main" function. In
many cases, you're trying to sample a specific set of ensembles, and so that
PathSampling example is all you need. Since this ends up being the
outside loop, you can override it in any way you desire. However, we
recommend including the storage and engine in the initialization, and
calling the main function .run(). We usually give .run a parameter to
indicate when it should stop (number of steps to take, or maximum number of
failed attempts to allow, for example).