kassonlab / gmxapi

(outdated) fork of https://gitlab.com/gromacs/gromacs
http://gmxapi.org/
Other
52 stars 13 forks source link

Stop condition hook #62

Open eirrgang opened 6 years ago

eirrgang commented 6 years ago

By default, a simulation has stop conditions based on the preconfigured nsteps, as well as the OS SIGINT and other implementation details.

Workflow level control logic needs to be able to tell a simulation to stop, say, after convergence of some property is detected.

The current plan is to make the types of interaction between MD elements and other elements more explicit than a mere dependency relationship, such as by naming elements in keyword parameters provided to the MD element, using keywords that signify the type of connection (e.g. restraint, stop). These relationships will result in distinct binding operations as the execution graph is instantiated. For immediate test functionality, though, prototype implementation will follow the previous automatic binding model, which temporarily bloats the Restraint interface.

Further discussion is needed regarding the interaction of the predefined nsteps and additional stop conditions. A big consideration is to try to make sure the behavior is what a user would intuitively expect. Should the nsteps stop condition be explicitly represented? Always? Or only if additional stop conditions are supplied? E.g. What if a simulation reaches nsteps without reaching the convergence condition? (Note, a convergence condition does not have to be a stop condition to trigger some data event if the user wants the simulation to run nsteps.)

eirrgang commented 6 years ago

Also ref https://github.com/kassonlab/gromacs-gmxapi/pull/15 and https://github.com/kassonlab/sample_restraint/pull/18

eirrgang commented 6 years ago

The API hook can be provided to the C++ plugin as a function pointer in the Resources object. At the higher level, the MD element params will look like

`params`: {
    `input`:{
        `restraint`: ["potential1.interface.restraint", "potential2.interface.restraint"],
        `stop`: ["my_stop_condition.ostream"]
    }
}

where "my_stop_condition" is an element for the gmxapi.logical_and operation, with params equal to

`input`: ["potential1.ostream.stop", "potential2.ostream.stop"]

This naming scheme is approximate. Until we decide otherwise, I'm going to allow a default behavior that the final component of the source port can be implicit if it matches the name of a provided input port or is the only source port for the source data type. Similarly, I think the source type could be implicit if there is only one type that makes sense for the sink port, but I'm going to leave it explicit for now.

Naming, hierarchical namespaces, and name semantics should be discussed further in the context of how we handle repeated graph executions versus sequences of data events.

The high level / user interface will hide the naming complexity with the exception that handles to final results of an element object will explicitly be named "output" while other interfaces can be implicit where it is least confusing for them to be so. The explicit name "output" is akin to the TensorFlow read_value() method on Variable objects, specifying that the data must be pull from the result of an operation that has previously been specified, except that using the output attribute of an element makes it more explicit which operation we are dependent on. E.g.

>>> my_stop_condition = gmx.logical_and(potential1.stop, potential2.stop)
>>> md = gmx.workflow.from_tpr([tpr_filename, tpr_filename], restraint=[potential1, potential2], stop=[my_stop_condition], override_nsteps=True)
>>> potential3 = myplugin.new_restraint(alpha=potential1.output.alpha)
>>> # or
>>> # potential3 = myplugin.new_restraint(params=potential1.output.params)
eirrgang commented 6 years ago

Implementation details: the signal does not necessarily go directly to the simulation, but we can make the interaction look the same whether or not it goes through a gmxapi Boolean logic operation first.

When the graph is built, the element provides the Director for the downstream element with the name of an output and a function object bound to the method for attaching a functor to trigger the data event. The resources for an executing node, then, have a mapping of their named outputs to function objects that they can call as appropriate and output manager objects will dispatch to the appropriate subscriber-provided function objects.

Another option for graph execution is that a call is made by the session manager to each edge rather than to each node. We can optionally allow the node itself to be explicitly evaluated, potentially precalculating what is needed for the edge or even triggering the edge API call, if edge API calls are made with a unique tag for the data state (i.e. timestep).