>> Lets step back for a bit. There are several types of choices that we want to be able to parameterize (pairwise) restraints by: - The choice of pairs (all pairs, bipartite pairs, nearby pairs etc.) This is clearly the job of the restraint and each type should probably be a different type of restraint. - Choice of the attributes being scored for the pairs. Is it the L2 distance? The L1? The X distance? What else? Direction of difference vector? This probably should also be in the restraint, but perhaps not. - Choice of the shape of the scoring function. This is mostly orthogonal to the other choices and so should probably be a class that is a parameter to the Restraint. In shape, I would include steepness of a harmonic. - Choice of the minimum for the scoring function. This is only a valid choice for some types of ScoreFunc (statistical potentials for example, tend to have it already in the function). As a result either we need two sorts of restraints, or some ScoreFuncGenerators need to ignore this (ick). I suspect that any ScoreFunc for which shifting based on the particles makes sense, we are shifting the minimum.
My suggestion would be: - A set of SingleVariableFunctions which are created some way or another and take a float and produce the score and the derivative. These include - Harmonic - Linear - HarmonicLowerBound - etc. - A set of ParticlePairScoreFunctions. These take a pair of particles and a DerivativeAccumulator and compute a score and the derivatives. These include - DistanceScoreFunction which takes a SingleVariableFunction and computes the L2 distance and computes its energy - SphereDistanceScoreFunction which takes a SingleVariableFunction and a name for a radius attribute and computes the energy (reducing the distance by the radius before passing it to the ScoreFunction - A set of restraints which take lists of particles. In general, there is no reason to do as the restraints currently do and create a large number of sub restraints. These only make the code longer and take more memory. Instead, the Restraints can simply call the ScoreFunc or ParticlePairScoreFunction as needed. The restraints include: - AllPairsRestraint which is paramaterized by a ParticlePairScoreFunction and calls it for all pairs - NearbyPairsRestraint which uses the mythical non-bonded list and a ParticlePairScoreFunction - BondedPairsRestraint which uses the mythical bonded list and directly uses the Harmonic SingleVariableFunction. - SphericalRestraint (or whatever I called it) which takes the SingleVariableFunction directly and computes distance to a fixed point
It think those three classes of classes allow us to cover everything with only a few types and keep everything fairly simple. We don't actually need to shift the scoring functions as you can always shift the distance or whatever you are scoring. Loosening or tightening of restraints can occur by changing the object stored in the restraint since there is always only one. For example, if you want to loosen the exclusion volume restraint, you change the HarmonicLowerBound you passed to the DistanceScoreFunction you passed to the NearbyPairsRestraint. Since you set them all up, and they are never duplicated, you can easily modify them.
Each of the Restraints listed is about 15 lines of broiler plate and 15-20 lines of interesting code (judging from my ExclusionVolume and BondRestraints).
The ParticlePairScoreFunctions would, among other things, subsume the non-trivial code from the current DistanceRestraint making it more easily usable.
SingleVariableFunctions would be as simple as ScoreFuncs (a line or two of interesting code).
>> More substantially, it is not clear to me that we shouldn't have it >> directly evaluate the score from the particles and skip the function >> object creation. Creating the ScoreFunc doesn't cache anything useful >> and just adds another layer of indirection. Internally they can use >> ScoreFuncs if they want. > > I'm not sure what you mean here - could you expand a little? It > seems to > me that they wouldn't be able to "directly evaluate the score from the > particles" because that's the restraint's job. And a harmonic scoring > function shouldn't care about the particles anyway - it could be > acting > on a distance (two particles), a dihedral (four particles) etc. But > perhaps I'm missing part of your idea here. In the proposal you have, from what I can tell, you create a generator which takes (perhaps implicitly) the name of the attribute(s) you are restraining. The restraint then uses the generator to create a bunch of ScoreFuncs one per pair of particles. I just realized that there are two options for what happens next:
What you probably actually meant was that the Generator processes the pair of particles and then returns a ScoreFunc (as it currently is) which is at some later time passed the distance. I don't really like that as the knowledge of what exactly is being restrained is spread in two places--part in the score function generation (or the choice of ScoreFunc generator) and part in the Restraint which has to generate the distance. I don't like this and don't see that we end up with a very general mechanism
What I had assumed you intended to have happen was, creating the ScoreFunc just involves copying data from the generator into a new object and storing the Particle*s. The created scorefunc then doesn't take any arguments other than the DerivativeAccumulator on evaluate. If this is what was intended, I would suggest that instead, the (now no longer a) generator could take the two particles and simply return the energy (and set the derivatives). You have the same amount of external control, and fewer objects are created. We then can easily create Restraints based around various types of pairs of particles (nearby ones, far away ones, bipartite ones etc.) Not sure if it is interesting but at least it is a clear separation of what goes in the Restraint (choice of pairs) and what goes in the PairScoreFunc (score an a pair of particles).
A couple comments:
I suspect that we ultimately will not want the compound restraints to create a (large) set of DistanceRestraints rather than just evaluate things on the fly. So decoupling the ScoreFunc generation and evaluation is not necessarily useful.
In addition, despite Bret's lengthy restraints, new Restraints which do various things over all pairs or all nearby pairs of points should only take a few lines of code (my ExclusionVolume and Bond restraints are each <15 lines of interesting code and another 15 or so of broiler plate), so I don't think a complicated and brittle mechanism to make more general in some way is a good use of time. Something simple like scaling and shifting of ScoreFuncs should be enough.