Add spring shooting to OPS core - #850
Conversation
dwhswenson
left a comment
There was a problem hiding this comment.
Overall, my main concerns are about things that seem to behave differently for spring shooting (especially around the selector) than for other shooting methods. If overridden functions have significantly different meanings/call signatures, maybe they're best given a different name? (e.g., ._biases). You can raise NotImplementedError if something is impossible to calculate for spring shooting.
From my own interests in playing with spring shooting, my biggest concern is that it looks to me like the .probability() method will return nonsense.
Also, I've been trying to think if there's another way around some of the cyclic dependency issues you'd had (IIRC, the issue was that you needed to share information between fwd and bkwd selectors, but then storage wouldn't see a DAG, and therefore couldn't recreate). Would something like this work?
class SpringShootingSelector(StorableNamedObject):
def __init__(self, k_spring, delta_max):
self.forward_selector = ForwardSpringShootingSelector(k_spring, delta_max)
self.backward_selector = BackwardSpringShootingSelector(k_spring, delta_max)
self.forward_selector._parent = self
self.backward_selector._parent = self
self.initial_trajectory = ...
...
class SpringShootingMover(...):
def __init__(self, selector, ensemble):
super().__init__(selector, ensemble)
self.movers = [
ForwardSpringShootingMover(selector.forward_selector, ensemble),
BackwardSpringShootingMover(selector.backward_selector, ensemble),
]
...(Obviously needing to fill in a lot of blanks.) I think that gets around the storage issue. Think of it as "recreate, then initialize," where the initialization is just telling where the parent is (where the joint storage of last selected snapshot is.) Note that if you do a custom from_dict for SpringShootingSelector, you'd need to do this in there.
In fact, that might even make it dangerously ease to (incorrectly) use spring shooting in combination with other path movers (by making the code more compatible, even if it wouldn't be scientifically compatible).
Another thing to consider would be to check the sanity of the input trajectory (to prevent foolishness by the user). The previous shooting snapshot must be a part of the input trajectory (and must have the right index; which side you count from depends on which direction the last shot was), right?
| from functools import reduce | ||
|
|
||
|
|
||
| class SpringShootingSelector(paths.ShootingPointSelector): |
There was a problem hiding this comment.
General concern on SpringShootingSelector: it looks to me like the inherited methods f and probability would give nonsense (raise an error for probability?) See:
openpathsampling/openpathsampling/shooting.py
Lines 12 to 29 in d2dab42
Giving a correct answer for probability, in particular, would be very useful.
| spring shooting simulation. It uses a biased potential in the shape of | ||
| min(1, e^(-k*i)) for a forward shooting move and min(1, e^(k*i)) for a | ||
| backwards shooting move, where i is a frame number in the range | ||
| [-delta_max, delta_max] where 0 is the last accepted shooting frame index. |
There was a problem hiding this comment.
This could do with a slightly clearer explanation. Maybe something like `where i is in the range [-delta_max, delta_max] and represents a shift (in frames) relative to the last shooting frame index.
| else: | ||
| self.k_spring = k_spring | ||
|
|
||
| # Initiate the class variable |
There was a problem hiding this comment.
instance variables, not class variables!
| raise RuntimeError("Sum of the biases changed") | ||
|
|
||
| @staticmethod | ||
| def _biases(delta_max, k_spring): |
There was a problem hiding this comment.
this is a weird override of super's _biases method, see
openpathsampling/openpathsampling/shooting.py
Lines 36 to 41 in d2dab42
| @@ -0,0 +1,346 @@ | |||
| from nose.tools import (assert_equal, assert_false, raises, assert_is, | |||
| assert_is_instance) | |||
There was a problem hiding this comment.
General comment on tests: we're moving toward pure pytest, so it might be good to rewrite this for pytest now (rather than later). That should be pretty straightfoward: replace assert_* with plain Python assert statements, and use with pytest.raises(Exception): blocks for the exception testing.
We support nose-based tests simply because we haven't gotten around to rewriting all our old tests, but that's on the to-do list (likely as a "good first issue" for someone just starting to get familiar with OPS).
| from openpathsampling.pathmovers.spring_shooting import (SpringShootingSelector, SpringMover, | ||
| ForwardSpringMover, BackwardSpringMover, | ||
| SpringShootingMover, SpringShootingStrategy, | ||
| SpringShootingMoveScheme) |
There was a problem hiding this comment.
clean up style on this import
…or' variable from SpringShootingStrategy
|
@dwhswenson This one should be ready for another round of comments |
So I looked into it and it seems that happens when we select the "last" index as a shooting point (in my new test it was Now, I am not too sure what to do with this one (or index I will keep it as the only reference for this edge case mentioned in the paper is:
And in the paper the Do you have another view on what is the "correct" implementation? (as you are in the acknowledgement for `critically reading' that paper) |
… step indices if we select an illegal frame
|
After a high bandwidth discussion between me and @dwhswenson, the algorithm is slightly altered . It now does not allow for frames in the state to be selected anymore, and now uses these indices to ensure 0-md step shots for illegal shooting points (so it selects index I also updated the tests to reflect this behaviour |
Codecov Report
@@ Coverage Diff @@
## master #850 +/- ##
==========================================
+ Coverage 80.25% 80.58% +0.33%
==========================================
Files 136 138 +2
Lines 14449 14671 +222
==========================================
+ Hits 11596 11823 +227
+ Misses 2853 2848 -5
Continue to review full report at Codecov.
|
|
Reading through the papers that cite OPS following #967 , I noticed that there is one that uses spring shooting with OPS. @dwhswenson do you know if they used this code/E-CAM code or if they implemented their own? (Was just wondering, thought it was cool to see this/similar algorithm being used) |
|
@sroet I don't know, but I would assume it was your E-CAM code, since Christoph may have known about that. That work was part of a thesis that came out in 2019, so I doubt it used this PR. EDIT: Thesis contains the line "In all cases, the TPS simulation was done using Spring Shooting module of OPS," so that's almost definitely your E-CAM module. https://is.muni.cz/th/ndj99/PhDThesis_janos.pdf |
dwhswenson
left a comment
There was a problem hiding this comment.
Looks good. A few very minor changes.
I'm a little uncomfortable with the use of SpringShootingSelector.acceptable_snapshot as a way to transfer information between methods. However, I'm not sure how easy it would be to change that, so no need to (unless you see another approach).
Co-authored-by: David W.H. Swenson <dwhs@hyperblazer.net>
Yeah, it is not ideal. Keep in mind that this is just a backstop to correctly count the rejections, if this is not done correctly this just turns into an identity move (as the trial-trajectory should always be identical to the input trajectory if an illegal frame is selected). The only issue this might lead to (other than a misrepresentation of the number of rejected paths) is less efficient sampling due to that (illegal) point being picked as the anchor point for the next try. None of these outcomes should actually break detailed balance. I can make it underscored, or name mangled (double underscored) to indicate that users should not mess with it, if that would help to make it more comfortable? |
Yeah, probably adding an underscore would help. How reusable is that class likely to be in other use cases? It might be possible to add methods that aren't part of the standard selector API that give a different return value. So In the regular shooting mover, the selector is a separate class because different selectors are interchangeable. That isn't true with spring shooting -- here there's a bit of separation of concerns, but it's doing more to just mimic the standard selectors for the purpose of familiarity. Anyway, that's a bigger change that doesn't need to be done now. Just documenting the thought. For now, just add the underscore, please. |
|
@dwhswenson thanks for the review, all your comments should be handled now :) |
dwhswenson
left a comment
There was a problem hiding this comment.
LGTM. In the future, we might change the example to only show the first 50 or so moves in the path trees, but will merge this now!

This adds the
spring shootingalgorithm as described by Brotzakis and Bolhuis to the core of OPS. It has been developed previously on an E-CAM 2020 workshop.