-
Notifications
You must be signed in to change notification settings - Fork 80
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add SamplerState interface to OpenMM CustomCVForce #362
Conversation
This PR gives the `SamplerState` a regular way to access the CustomCVForce variables and values which may appear in a `Context`.
This does not make OpenMMTools require OpenMM 7.3 yet, it should gracefully trap the existence, or lack thereof, of OpenMM 7.3 |
Are there any objections to me dropping these two lines from the https://github.com/choderalab/openmmtools/blob/master/setup.py#L174-L175 |
No problems here! |
Implementation wise, how does this look? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good in general, but I think there are a couple of things that we should change before merging this.
openmmtools/states.py
Outdated
self._collective_variables = collective_variables if collective_variables else None | ||
|
||
@property | ||
def _test_positions_valid(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe rename this as _are_positions_valid
, which I believe is the most common name convention for these.
openmmtools/states.py
Outdated
self._kinetic_energy = openmm_state.getKineticEnergy() | ||
self._set_collective_variables(context_state) | ||
|
||
def _set_collective_variables(self, context_state): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you call this maybe _read_collective_variables
to be consistent with _read_context_state
?
openmmtools/states.py
Outdated
@@ -1843,7 +1867,7 @@ def update_from_context(self, context_state): | |||
""" | |||
self._read_context_state(context_state, check_consistency=True) | |||
|
|||
def apply_to_context(self, context, ignore_velocities=False): | |||
def apply_to_context(self, context, ignore_velocities=False, ignore_collective_variables=False): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was remembering incorrectly and I thought there was an ignore_velocities
in update_from_context()
on top of apply_to_context
. I wouldn't have an ignore_collective_variables
argument here anyway. This function task is to write the context so it shouldn't also read information from it, and collective variables like the energies, are not written.
I'm now thinking we should also add optional ignore_positions
, ignore_velocities
, ignore_energies
, and ignore_collective_variables
argument in update_from_context()
, similarly to OpenMM's Context.getState()
to allow users to optimize reading off the GPU. We'll definitely need the ignore_collective_variables
in any case.
openmmtools/states.py
Outdated
except AttributeError: | ||
pass | ||
# Trap no variables found (empty dict), return None | ||
self._collective_variables = collective_variables if collective_variables else None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would convert the defaultdict
to a normal dict
either here or in the SamplerState.collective_variable
property since the user expects that type.
openmmtools/states.py
Outdated
The object to read. This only works with Context's for now, | ||
but in the future, this may support OpenMM State objects as well. | ||
""" | ||
if isinstance(context_state, openmm.State): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should raise an exception if the user passes an openmm.State
object and get_collective_variables=True
. Otherwise, this will fail silently and end up with a value for SamplerState.collective_variables
indistinguishable from having a System
with no collective variables.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't really like this idea. This can lead the user to be mandated to use the get_collective_variables=False
in every call to this function when the collective variables are less common.
SamplerState.collective_variables indistinguishable from having a System with no collective variables.
Yes, though I thought that was partially the point? If a user never supplies the information about a given state, then we can't use that. I don't like the idea of the users being required to now track collective variables, which are an optional construct in of themselves.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The user wouldn't have to set get_collective_variable=False
in every call as long as he passes an actual Context
object. Passing the State
object is there exclusively for optimization (i.e. if I already have one around I don't have to read the info off the Context
again).
In any case, I think avoiding silent failing would be worth forcing the users to add the extra option, and the default behavior should be the safest. With this implementation, for example, all our metropolized MCMCMove
s are currently failing silently and not retrieving CV variables that should actually be available after apply
has been performed.
# sampler_state.positions[5:8] = [[2.0, 2.0, 2.0], [2.0, 2.0, 2.0], [2.0, 2.0, 2.0]] * pos_unit | ||
# assert sampler_state.positions.has_changed | ||
# assert np.all(old_unitless_positions[5:8] != sampler_state._unitless_positions[5:8]) | ||
if isinstance(sampler_state._positions._value, np.ndarray): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for re-activating these tests! I forgot about this.
Okay! Changes made, let me know what you think. |
Looks great, thanks for the changes! With the current implementation now, the integrator We'll have to change the implementation of sampler_state.update_from_context(context_state, ignore_collective_variables=True)
sampler_state.update_from_context(context, ignore_positions=True, ignore_velocities=True, ignore_energy=True) With repex, I think this would save us |
To put that into context, with the |
I'm not sure I follow what you would prefer I do here, nor where I need to make the change |
Sorry, that was unclear. I'd prefer adding the rest of the sampler_state.update_from_context(context_state, ignore_collective_variables=True)
sampler_state.update_from_context(context, ignore_positions=True, ignore_velocities=True, ignore_energy=True) to avoid degrading the performance of the integrator moves. |
Okay, but would you want all those to default to True? |
I think the safer default is probably |
Having a thought. Would it be more clear to use inclusive keywords instead of exclusive? e.g. |
That sounds clearer to me! |
Me and Levi just finished talking about this. I thought of the |
openmmtools/states.py
Outdated
if ignore_collective_variables: | ||
if isinstance(context_state, openmm.State): | ||
# Invalidate if State is passed in | ||
self._collective_variables = None |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure we want to do this. I'm worried about the fact that these two snippets:
sampler_state.update_from_context(context_state, ignore_collective_variables=True)
sampler_state.update_from_context(context, ignore_positions=True, ignore_velocities=True)
and
sampler_state.update_from_context(context, ignore_positions=True, ignore_velocities=True)
sampler_state.update_from_context(context_state, ignore_collective_variables=True)
will result in two different states. I think this mistake would be hard to find and it's easy to miss this behavior in the docs. I think it's safer to have always ignore_collective_variables
leave the CV as it is because if the user is using the ignore_X
attributes it means that he's updating everything by steps.
Hmm. I understand now that you were probably worried about the fact that the user won't be able to invalidate a particular attribute during the manual update. If this is the case, I'd rather have the properties setters allow to set (exclusively) the None
value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because we support both State
and Context
objects, which now cause a loss of information, we have to do something to handle it. I don't want the user to by default be able to put the SamplerState
into something inconsistent.
I understand now that you were probably worried about the fact that the user won't be able to invalidate a particular attribute during the manual update.
When would this be?
I'd rather have the properties setters allow to set (exclusively) the None value.
I can change that, but lets figure out all the use cases first.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't want the user to by default be able to put the SamplerState into something inconsistent.
This is already possible if the user does
sampler_state.update_from_context(context, ignore_collective_variables)
and context
is an actual Context
object. We must assume that users know what they're doing when they are requesting only partial information to optimize the communication with the GPU, but I'm worried an inconsistent behavior of the input arguments would trick also more advanced users (me included).
The collective variables should now be consistent with the other |
Thanks! Feel free to merge when tests pass. |
We keep having SegFaults during the |
Nope T.T I gave up after a couple of hours of trying different things. Last time that happened the errors magically disappeared with a new OpenMM version. I'll try to figure out what's going on separately anyway. Locally, all those tests pass here. |
All the tests pass locally for me as well |
This PR gives the
SamplerState
a regular way to access the CustomCVForce variables and values which may appear in aContext
.