Skip to content
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

Refactor integrator framework to reduce coupling #3390

Merged
merged 22 commits into from
Jan 16, 2020

Conversation

jngrad
Copy link
Member

@jngrad jngrad commented Dec 24, 2019

Fixes #3271

Description of changes:

  • represent core integrators as individual Python classes to reduce coupling in the script interface
  • use an IntegratorHandle Python class to store the currently active integrator
  • make the steepest descent algorithm a fully functional integrator and remove a side effect in steepest_descent() that reverted the integ_switch value after integration
  • move the integrator logic from espressomd.minimize_energy.MinimizeEnergy to espressomd.integrate.SteepestDescent
  • keep espressomd.minimize_energy.MinimizeEnergy as a wrapper for backward compatibility
  • API change:
    • no API change for integrators
    • API change for espressomd.minimize_energy.MinimizeEnergy: now a stateless free function that takes an espresso system as argument, renamed to steepest_descent

Split Integrator class in multiple classes managed by an
IntegratorHandle class. Cleanup integrator documentation.
Remove code duplication by having all the integrator logic in a
single class SteepestDescent. The MinimizeEnergy class is now a
wrapper to setup and run the steepest descent integrator.

API change: it is now necessary to restore the original integrator
after energy minimization using `system.minimize_energy.disable()`.
Give steepest descent the same core interface as other integrators.
Remove the code logic in `steepest_descent()` that saved then
restored the previous value of the `integ_switch` flag (this is
now achieved by the `MinimizeEnergy` Python class, thus making
`Integrator` Python classes free of side effects).
@review-notebook-app
Copy link

Check out this pull request on  ReviewNB

You'll be able to see Jupyter notebook diff and discuss changes. Powered by ReviewNB.

@codecov
Copy link

codecov bot commented Dec 24, 2019

Codecov Report

❗ No coverage uploaded for pull request base (python@a5e10d8). Click here to learn what that means.
The diff coverage is n/a.

Impacted file tree graph

@@           Coverage Diff            @@
##             python   #3390   +/-   ##
========================================
  Coverage          ?     86%           
========================================
  Files             ?     538           
  Lines             ?   25299           
  Branches          ?       0           
========================================
  Hits              ?   21818           
  Misses            ?    3481           
  Partials          ?       0

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a5e10d8...830c76b. Read the comment docs.

@jngrad
Copy link
Member Author

jngrad commented Dec 24, 2019

Feel free to comment on this WIP. Possible talking points:

  • better abstraction of the integrator framework and naming convention
  • although MinimizeEnergy won't hinder further refactoring of the integrator framework anymore, it could be removed to reduce coupling in the script interface and make the API change non-silent

@fweik fweik self-assigned this Dec 27, 2019
@fweik
Copy link
Contributor

fweik commented Dec 27, 2019

I'll have a read today and will give you some feedback.

int integrate_set_steepest_descent(const double f_max, const double gamma,
const int max_steps,
const double max_displacement) {
if (f_max < 0.0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

f_max == 0 should be allowed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it is allowed

@fweik
Copy link
Contributor

fweik commented Dec 27, 2019

Generally I don't think the minimize_energy thing should be a member of the system class. It's not part of its state, so it is confusing to put it there. For the class itself, I think the init and disable logic is unneeded and error-prone. I think it would be a better design to just have it set the the integrator at the beginning of the minimize method, and restore it at the end. This would make the object stateless, so it could be a free function. As such it is useful to keep for the future, because minimize and switch back is a common operation.

@fweik
Copy link
Contributor

fweik commented Dec 27, 2019

If the goal is to keep the current syntax for the integrators (set_...) the IntegratorHandle construction you proposed is the best we can get. So if this is the goal I'm fine with this. I think in general the integrators should not be part of the system, because this blurs the line between the physics and the MD method, (integration is something that is done to the system, not an inherent property) but I understand that there is no interest in such fundamental change at the moment.

I think the logical next step from here would be to put the core integrators into classes, akin to the
python classes derived from class Integrator.

@RudolfWeeber
Copy link
Contributor

Makes things much more clear on the Python side.
That's probably as far as we should take it, until we know what the implementation in the core will look like.

I think, the legacy Minimizeenergy interface can be removed. The 4.2 release will break the interface non-silently in a few places anyway.
Is there anything in the PR which should go into a bugfix release? If so, the old interface could be removed after cherry-picking.

On a different note, integrate_vv() is no longer responsible for Velocity-Verlet alone, so the _vv should probably be removed.

Is there a reason to manually allocate the memory for the steepest descent struct?

@fweik
Copy link
Contributor

fweik commented Dec 27, 2019

I think, the legacy Minimizeenergy interface can be removed.

As I was saying, this should be kept as a free function. That is a common think, and I can easily picture how forgetting to switch back the integrator after energy minimization can be a common error mode.

@RudolfWeeber I don't think that core refactoring is in scope for this PR, let's tackle this in the next step.

@jngrad
Copy link
Member Author

jngrad commented Dec 27, 2019

I think it would be a better design to just have it set the the integrator at the beginning of the minimize method, and restore it at the end. This would make the object stateless, so it could be a free function. As such it is useful to keep for the future, because minimize and switch back is a common operation.

Although I initially planned to remove completely the MinimizeEnergy class and replace it by system.integrator.set_steepest_descent everywhere, I realized we might have a need for some Minimization feature that would take as argument a lambda function to decide when to stop the minimization. This logic is currently coded manually in a few places to integrate until inter-particle distances become large enough (ex: nacl.py#L92-L99). This could be achieved by having an extra method minimize(until=lambda: system.analysis.min_dist() < max_sigma) in the SteepestDescent(Integrator) class. The refactored MinimizeEnergy class in this WIP already has a high level of coupling with IntegratorHandle, and that's a strong sign that MinimizeEnergy should be part of the integrator abstraction.

This strategy doesn't seem satisfactory from an abstraction perspective: minimization algorithms like steepest descent, simulated annealing or genetic algorithms don't have a well-defined notion of time (i.e. at each "integration" step, the time step is different for every particle in the system) and can't really be considered as integrators. It's unfortunate the steepest descent algorithm was initially developed as a pseudo-integrator, but until we find a need to decouple the integration loop (e.g. to support other minimization algorithms), we would probably be better off by removing the MinimizeEnergy feature altogether.

Designing the minimize_energy feature as a free function means it will have to change the current integrator and restore it after minimization, which is more-or-less what the refactored MinimizeEnergy is now doing, but in a stateless form. This would be one of the few features in espresso where a function takes a System object as argument. Should we discuss this in an espresso meeting or in a dev meeting?

If the goal is to keep the current syntax for the integrators (set_...) the IntegratorHandle construction you proposed is the best we can get. So if this is the goal I'm fine with this.

Yes, that's the main goal. In the future, we could think of a new syntax like system.integrator = espressomd.Integrator.VelocityVerlet(), or introducing a Propagator class that encapsulates an Integrator instance and provides a list of compatible thermostats.

Is there anything in the PR which should go into a bugfix release?

Not really. The bug mentioned in the issue was already shipped in 4.1.1, and the side-effects mentioned in the commit messages are only relevant to this refactoring.

so the _vv should probably be removed.

Good idea.

Is there a reason to manually allocate the memory for the steepest descent struct?

Probably not, I'll have a look.

I don't think that core refactoring is in scope for this PR, let's tackle this in the next step.

The changes proposed by Rudolf seems small enough to me.

@fweik
Copy link
Contributor

fweik commented Dec 27, 2019

Designing the minimize_energy feature as a free function means it will have to change the current integrator and restore it after minimization, which is more-or-less what the refactored MinimizeEnergy is now doing, but in a stateless form. This would be one of the few features in espresso where a function takes a System object as argument.

There are a few, e.g. the polymer function. Arguable we should have more of those. I find the logically more coherent, and e.g. it makes this high level code much more easily testable e.g. with a mock system class. Personally, I also don't think that what we currently do and do not have in the python API is a a very good indicator for what is and is not a good idea.

@jngrad
Copy link
Member Author

jngrad commented Dec 30, 2019

@fweik is 95549a9 what you have in mind?

The integration function does more than just velocity Verlet.
@fweik
Copy link
Contributor

fweik commented Dec 30, 2019

@jngrad yes, this looks good to me. Any other opinions on this? With this the high level logic of minimize_energy can be tested in python...

@RudolfWeeber
Copy link
Contributor

If we are keeping steepest descent as a free function (which I'd personally not do as per Pep8 reommendation to have peferrabley only one way to do a thing), the function should

  • the name should be/contain steepest_descent rather than minimize energy, as that is, what's done
  • the outcome (i.e., whether the target max force was reached) should be communicated, e.g., via return value.

@RudolfWeeber
Copy link
Contributor

I agree with the goal to split system state from stuff that modifies the system. Probably, the core needs to be done first, though.

@fweik
Copy link
Contributor

fweik commented Jan 3, 2020

It seems to me that having two ways to do it for now is just the price of not changing the whole API at once. I don't have strong opinions on the name, and returning available information to the user is always a good idea.

@jngrad
Copy link
Member Author

jngrad commented Jan 9, 2020

the outcome (i.e., whether the target max force was reached) should be communicated

This requires introducing a new global variable to store the state of the steepest descent, because steepest descent does not increment the simulation time and there is no simulation step counter.

@jngrad jngrad changed the title WIP: Refactor integrator framework to reduce coupling Refactor integrator framework to reduce coupling Jan 9, 2020
@fweik
Copy link
Contributor

fweik commented Jan 9, 2020

@jngrad why can't you just return the number of steps that were executed from the function?

@jngrad
Copy link
Member Author

jngrad commented Jan 10, 2020

@fweik this would require integrate() returning an int, steepest_descent() doing an MPI all_reduce, and writing a new mpi_call_all template that returns a value. I can have a look.

Also, we should make sure the return value of integrate() is only used for the steepest descent free function, and not in general for bookkeeping the total number of simulation steps, because steepest descent doesn't increment the simulation time. The canonical way of counting steps is:

step[0][0][0] = (int)std::round(sim_time / time_step);

@fweik
Copy link
Contributor

fweik commented Jan 10, 2020

You could always return the number of steps that are integrated. This also does not need communication, because this information is available on the head node, no?

Remove documentation of inexistent REQ_* MPI tags and update
instructions to add new callbacks. Clean up documentation of
functions used in callbacks. Remove references to the Tcl
setmd command.
Implement Communication::Result::Ignore and add a new operation
Communication::Result::MasterRank to read from the head node only.
In boost::mpi version 1.69 (default on CentOS/Fedora), reduction
functions taking a user-defined operation expect a struct with an
operator() method. Lambda functions are no longer allowed and
generate a compilation error: "error: use of deleted function
'<lambda(const int&, const int&)>::<lambda>()'".
Copy link
Contributor

@fweik fweik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM except for one small issue.

src/core/MpiCallbacks.hpp Outdated Show resolved Hide resolved
@fweik fweik added the automerge Merge with kodiak label Jan 16, 2020
@kodiakhq kodiakhq bot merged commit 125f831 into espressomd:python Jan 16, 2020
@jngrad jngrad deleted the fix-3271-minimize branch January 18, 2022 12:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Merge the two MinimizeEnergy interfaces
3 participants