We’ve had various discussions (including in the planning meeting) about ForceProvider public APIs. It sounds like there are also some EU project mandates that may contribute. But I wanted to start a thread to solicit desired features for such APIs. No promises about delivering all of these, but if we don’t know what people want it’s much harder to give it.
ForceProvider API seems to be fine for the usecases covered by the current API, which I would say are:
- Forces acting on a small number of atoms : anything that one would expect takes a few index groups in the MDP, and does stuff between them
- Forces that are non-pairwise/are purely a property of the atom itself (e.g. electric field), and thus sidestep the O(N^2) of pairwise interaction
However, I don’t think it works for:
- Forces that are a full pairwise potential (between all atoms) - will need implementing some equivalent to interaction list directly in the force provider for better than naive scaling
-
Forces that need per-atom parameters (that are not the charge): anything one would expect to be specified in the topology. Possible to have auxilliary file, but they will basically be
top
file with a different syntax - Forces that behaves like bonded interactions between more than a few atoms - also mostly because of the input file format constraint
The return of tabulated potential will help here - it might be an opportunity to integrate them with ForceProvider (FP computes the tables during simulation setup according to own formula?). The modified LJ that was presented at a recent weekly meeting would be a typical case.
Two other remarks:
Perhaps a good way to ensure everything necessary is provided by the interface, might be to dogfood it and implement, e.g., a not-completly-a-toy Coulomb solver in them.
The choice of an end-to-end computeForce
method in the interface is necessarily limiting. Maybe an additional interface that ask to provide a force kernel vaguely like f(x1, x2, f1*, f2*)
, or f(x, interactionList, forceBuffer)
that is then called through the exisiting short-range force infrastructure is an idea? It doesn’t need to be as optimized as “normal” forces - just provide a better scaling than the monolithic computeForce
does.
There are two main topics here, I think.
- How does an external package extend a gromacs installation by providing a ForceProvider?
- What is the maintainable interface that we need to provide for accessing ForceProviderInput and ForceProviderOutput?
The first question is mostly technical.
The second question involves some requirements gathering. I think that’s what Peter is trying to initiate.
We can get a relatively quick start on providing free functions that accept an opaque ForceProviderInput
or ForceProviderOutput
and return a simple iterator or handle. We may be able to make a stable ABI or at least a stable API for the objects themselves at some point, but that is harder and I don’t think that should be the first priority.
Similarly, I think it would be helpful to clearly distinguish between functionality that is currently available to an IForceProvider from functionality that we would like in the future.
In the simplest case, the implementer of IForceProvider.calculateForces()
needs to be able to get forward input and output iterators to read positions and write forces. Questions:
- Is simple forward iteration enough or do we need random access?
- How much type info do we need, and in what way?
- is it sufficient to have a forward iterator of the global atom indices in the current PP domain?
- what topology information do we need at the
calculateForces()
, versus what can we require the ForceProvider to obtain independently or to retain from initialization?
A first dog-fooding example would be the original electric field module. Another would be the IRestraintPotential.
See also:
- issue #4596 and
- notes at Modular interfaces 2023 · Wiki · GROMACS / GROMACS · GitLab
We spent a lot of time thinking about these questions in the development of NB-LIB and determined that there are basically 3 functions needed for a “regular” force calculation interface.
- A constructor or similar that takes info such as topology info and global particle indices.
- A function which needs to be called after domain decomposition and partitioning which takes the local atom indices that are current for the PP domain.
- A calculateForces() method that takes some combo of positions, velocities, and forces (though if forces can be output directly then velocities could in principle be force calculator local) and optionally returns energies.
In the case of listed forces or other type of forces that might be felt by atoms not in the same PP domain perhaps the best solution would be to have a category of “special domains” that in principle can contain only a few atoms. This would be a performance optimization so it should be taken into account how to design for this problem, but I think it need not actually be solved by any initial design.
When it comes to having more types of PP interactions, this is actually conceptually straightforward but potentially hard to implement in a performant way. Conceptually, an LJ force is just a linear combination of 2 terms, and you just generate a matrix of interactions for all the particle types with all the other particle types and then expand this matrix to a list of interaction pairs for each actual particle within the cutoff. I hope from this description it is clear that expanding the number of terms in the particle interactions from 2 to n is conceptually straightforward. The real issue is making sure that you have your neighbor list sizes optimized based on the total number of interactions that any particle feels with any other. I think implementing a feature like this in a force provider interface would not be ideal, and instead work on making the number of interactions in a PP interaction more flexible would make the most sense. Perhaps others would feel very differently though.
I agree.
Thanks, Joe. Just for my understanding (in addition to the technical scope of the interface), you’re saying that you think generalized particle-particle interactions are much more complex, at least from a performance perspective, than interactions that are either sparse particle-particle or external field-type interactions along the lines of what the current internal provide? And the ForceProvider functionality currently doesn’t “replace” the pair-force kernels. It would be cool to unify them (and awesome if nblib/etc could provide a way to do that), but that seems more complex for near-term, unless you’ve worked on that already?
Non-bonded (NB) particle-particle interactions are complex because you have to calculate neighbors and only compute those interactions that fall within the cutoff distance. In principle NB interactions could be put behind an interface similar to the one I outlined above, and so could be made “just another” force provider. I think this is a reasonable goal in some sense. However, the actual technical work to make this feasible is quite significant.
Basically you would need to separate the methods for partitioning from the code that computes the NB interactions. I think this would open up a lot of opportunities for performance engineering, and would suggest that a space filling curve would work for the case of short range NB interactions and long range electrostatic interactions (here you would also need to implement and tune an FMM based LR electrostatics).
I would also point out that even if the separation were made to allow short range NB forces to be behind a force provider interface, they would still not be “just another” force type. This is because short range NB is the largest part of the computation and there is thus good reason to structure the data layouts to accommodate NB forces. In principle it would be nice to say that every force provider can decide to store its data however it wants. For force providers that only compute forces between a few particles, this might be fine. For any forces that act on the majority of particles however there is a trade-off that must be evaluated globally, finding a data layout that is optimized for the most compute intensive parts and good-enough for the rest.
Having all force calculations using a similar interface which is also separated from the partitioning could make it easier to test different layouts, so it is a laudable goal. It is also daunting because many parts of gromacs are extremely intricately connected.
Does this answer you question all Peter?
Yes, thanks. My reaction would be that this is a great roadmap for long-term improvements and as you say " good reason to structure the data layouts to accommodate NB forces". But in the interests of getting a good force provider API out in a feasible timescale probably best to say general NB interactions are scoped as long-term rather than near-term targets? (perfect is the enemy of good, etc.)
probably best to say general NB interactions are scoped as long-term rather than near-term targets?
Yes, exactly.