| 1 | /*
 | 
|---|
| 2 |  * Project: MoleCuilder
 | 
|---|
| 3 |  * Description: creates and alters molecular systems
 | 
|---|
| 4 |  * Copyright (C)  2010 University of Bonn. All rights reserved.
 | 
|---|
| 5 |  * Please see the LICENSE file or "Copyright notice" in builder.cpp for details.
 | 
|---|
| 6 |  */
 | 
|---|
| 7 | 
 | 
|---|
| 8 | /**
 | 
|---|
| 9 |  * \file potentials.dox
 | 
|---|
| 10 |  *
 | 
|---|
| 11 |  * Created on: Nov 28, 2012
 | 
|---|
| 12 |  *    Author: heber
 | 
|---|
| 13 |  */
 | 
|---|
| 14 | 
 | 
|---|
| 15 | /** \page potentials Empirical Potentials and FunctionModels
 | 
|---|
| 16 |  *
 | 
|---|
| 17 |  * On this page we explain what is meant with the Potentials sub folder.
 | 
|---|
| 18 |  *
 | 
|---|
| 19 |  * First, we are based on fragmenting a molecular system, i.e. dissecting its
 | 
|---|
| 20 |  * bond structure into connected subgraphs, calculating the energies of the
 | 
|---|
| 21 |  * fragments (ab-initio) and summing up to a good approximation of the total
 | 
|---|
| 22 |  * energy of the whole system, \sa fragmentation.
 | 
|---|
| 23 |  * Second, having calculated these energies, there quickly comes up the idea
 | 
|---|
| 24 |  * that one actually calculates quite similar systems all time and if one could
 | 
|---|
| 25 |  * not cache results in an intelligent (i.e. interpolating) fashion ...
 | 
|---|
| 26 |  *
 | 
|---|
| 27 |  * That's where so-called empirical potentials come into play. They are
 | 
|---|
| 28 |  * functions depending on a number of "fitted" parameters and the variable
 | 
|---|
| 29 |  * distances within a molecular fragment (i.e. the bond lengths) in order to
 | 
|---|
| 30 |  * give a value for the total energy without the need to solve a complex
 | 
|---|
| 31 |  * ab-initio model (essentially, not solving the electronic Schrödinger equation
 | 
|---|
| 32 |  * anymore). And they are accompanied by a specific binding model that
 | 
|---|
| 33 |  * represents what kind of many-body force is represented by the potential.
 | 
|---|
| 34 |  *
 | 
|---|
| 35 |  * Empirical potentials have been thought of by fellows such as Lennard-Jones,
 | 
|---|
| 36 |  * Morse, Tersoff, Stillinger and Weber, etc. And in their honor, most of the
 | 
|---|
| 37 |  * potential forms are named after its inventor. Hence, we speak e.g. of a
 | 
|---|
| 38 |  * Lennard-Jones potential.
 | 
|---|
| 39 |  *
 | 
|---|
| 40 |  * So, what we have to do in order to cache results is the following procedure:
 | 
|---|
| 41 |  * -# gather similar fragments
 | 
|---|
| 42 |  * -# perform a fit procedure to obtain the parameters for the empirical
 | 
|---|
| 43 |  *    potential
 | 
|---|
| 44 |  * -# evaluate the potential instead of an ab-initio calculation
 | 
|---|
| 45 |  *
 | 
|---|
| 46 |  * However, we need more: What are similar fragments? How do we perform the
 | 
|---|
| 47 |  * fitting procedure? And as the potentials are mathematical functions, what
 | 
|---|
| 48 |  * arguments do they depend on?
 | 
|---|
| 49 |  *
 | 
|---|
| 50 |  * Similar fragments are those that share the same bond graph, i.e. they have
 | 
|---|
| 51 |  * the same number of nodes and the same number of edges. And each edge is
 | 
|---|
| 52 |  * between the same two elements.
 | 
|---|
| 53 |  *
 | 
|---|
| 54 |  * The fitting procedure works by looking at a training set, i.e. a list of
 | 
|---|
| 55 |  * elements where each contains an energy and a number of arguments, namely
 | 
|---|
| 56 |  * pair-wise distances. The error is then the difference between the energies
 | 
|---|
| 57 |  * in the set and all the energy values that we obtain when we feed the
 | 
|---|
| 58 |  * arguments into the fitted potentials. This error is minimized in the
 | 
|---|
| 59 |  * euclidian norm, i.e. least squares regression. But other norms might be
 | 
|---|
| 60 |  * possible in the future, too.
 | 
|---|
| 61 |  * 
 | 
|---|
| 62 |  * And the pair-wise distances, we mentioned are the arguments.
 | 
|---|
| 63 |  *
 | 
|---|
| 64 |  * The terms, that we use, model the classes that are implemented:
 | 
|---|
| 65 |  * -# EmpiricalPotential: Contains the interface to a function that can be
 | 
|---|
| 66 |  *    evaluated given a number of arguments_t, i.e. distances. Also, one might
 | 
|---|
| 67 |  *    want to evaluate derivatives.
 | 
|---|
| 68 |  * -# FunctionModel: Is a function that can be fitted, i.e. it depends on a
 | 
|---|
| 69 |  *    set of internal parameters that can be set and got.
 | 
|---|
| 70 |  * -# argument_t: The Argument stores not only the distance but also the index
 | 
|---|
| 71 |  *    pair of the associated atoms and also their charges, to let the potential
 | 
|---|
| 72 |  *    check on validity.
 | 
|---|
| 73 |  * -# SerializablePotential: Eventually, one wants to store to or parse from
 | 
|---|
| 74 |  *    a file all potential parameters. This functionality is encoded in this
 | 
|---|
| 75 |  *    class.
 | 
|---|
| 76 |  * -# HomologyGraph: "Similar" fragments in our case have to have the same bond
 | 
|---|
| 77 |  *    graph. It is stored in the HomologyGraph that acts as representative.
 | 
|---|
| 78 |  * -# HomologyContainer: This container combines, in a ultimap fashion, all
 | 
|---|
| 79 |  *    similar fragments with their energies together, with the HomologyGraph
 | 
|---|
| 80 |  *    as their "key".
 | 
|---|
| 81 |  * -# TrainingData: Here, we combine InputVector and OutputVector that contain
 | 
|---|
| 82 |  *    the set of distances required for the FunctionModel (e.g. only a single
 | 
|---|
| 83 |  *    distance/argument for a pair potential, three for an angle potential,
 | 
|---|
| 84 |  *    etc.) and also the expected OutputVector, i.e. the energy of the specific
 | 
|---|
| 85 |  *    configuration in our case. This in combination with the FunctionModel is
 | 
|---|
| 86 |  *    the basis for the non-linear regression used for the fitting procedure.
 | 
|---|
| 87 |  * -# Extractors: These set of functions yield the set of distances from a
 | 
|---|
| 88 |  *    given fragment that is stored in the HomologyContainer.
 | 
|---|
| 89 |  * -# FunctionApproximation: Contains the interface to the levmar package where
 | 
|---|
| 90 |  *    the Levenberg-Marquardt (Newton + Trust region) algorithm is used to
 | 
|---|
| 91 |  *    perform the fit.
 | 
|---|
| 92 |  *
 | 
|---|
| 93 |  * \section potentials-fit-potential-action What happens in FitPotentialAction.
 | 
|---|
| 94 |  *
 | 
|---|
| 95 |  *  First, charges and a potential type is used from the given options. This 
 | 
|---|
| 96 |  *  is used to instantiate EmpiricalPotentials via the PotentialFactory, stored
 | 
|---|
| 97 |  *  within the PotentialRegistry. This is the available set of potentials
 | 
|---|
| 98 |  *  (without requiring any knowledge as to the nature of the fragment employed
 | 
|---|
| 99 |  *  in fitting).
 | 
|---|
| 100 |  *
 | 
|---|
| 101 |  *  Second, the given fragment is used to get a suitable HomologyGraph from
 | 
|---|
| 102 |  *  the World's HomologyContainer. This is given to a CompoundPotential, that in
 | 
|---|
| 103 |  *  turn browses through the PotentialRegistry, picking out those
 | 
|---|
| 104 |  *  EmpiricalPotential's that match with a subset of the FragmentNode's of the
 | 
|---|
| 105 |  *  given graph. These are stored as a list of FunctionModel's within the
 | 
|---|
| 106 |  *  CompoundPotential instance. Here comes the specific fragment into play,
 | 
|---|
| 107 |  *  picking a subset from the available potentials.
 | 
|---|
| 108 |  *
 | 
|---|
| 109 |  *  Third, we need to setup the training data. For this we need vectors of input
 | 
|---|
| 110 |  *  and output data that are obtained from the HomologyContainer with the
 | 
|---|
| 111 |  *  HomologyGraph as key. The output vector in our case is simply a number
 | 
|---|
| 112 |  *  (although the interface allows for more). The input vector is the set of
 | 
|---|
| 113 |  *  distances. In order to pre-process the input data for the specific model
 | 
|---|
| 114 |  *  a filter is required in the TrainingData's constructor. The purpose of the
 | 
|---|
| 115 |  *  filter is to pick out the subset of distance arguments for each model one
 | 
|---|
| 116 |  *  after the other and concatenate them to a list. On evaluation of the model
 | 
|---|
| 117 |  *  this concatenated list of distances is given to the model and it may easily
 | 
|---|
| 118 |  *  dissect the list and hand over each contained potential its subset of
 | 
|---|
| 119 |  *  arguments. See Extractors for more information.
 | 
|---|
| 120 |  *
 | 
|---|
| 121 |  *  Afterwards, training may commence: The goal is to find a set of parameters
 | 
|---|
| 122 |  *  for the model such that it as good as possible reproduces the output vector
 | 
|---|
| 123 |  *  for a given input vector. This non-linear regression is contained in the
 | 
|---|
| 124 |  *  levmar package and its functionality is wrapped in the FunctionApproximation
 | 
|---|
| 125 |  *  class. An instance is initialized with both the gathered training data and
 | 
|---|
| 126 |  *  the model containing a list of potentials. See
 | 
|---|
| 127 |  *  [FunctionApproximation-details] for more details.
 | 
|---|
| 128 |  *
 | 
|---|
| 129 |  *  During the fitting procedure, first the derivatives of the model is checked
 | 
|---|
| 130 |  *  for consistency, then the model is initialized with a sensible guess of
 | 
|---|
| 131 |  *  starting parameters, and afterwards the Levenberg-Marquardt algorithm
 | 
|---|
| 132 |  *  commences that makes numerous calls to evaluate the model and its derivative
 | 
|---|
| 133 |  *  to find the minimum in the L2-norm.
 | 
|---|
| 134 |  *
 | 
|---|
| 135 |  *  This is done more than once as high-dimensional regression is sensitive to
 | 
|---|
| 136 |  *  the starting values as there are possible numerous local minima. The lowest
 | 
|---|
| 137 |  *  of the found minima is taken, either via a given threshold or the best of a
 | 
|---|
| 138 |  *  given number of attempts.
 | 
|---|
| 139 |  *
 | 
|---|
| 140 |  *  Eventually, these parameters of the best model are streamed via
 | 
|---|
| 141 |  *  PotentialSerializer back into a potential file. Each EmpiricalPotential in
 | 
|---|
| 142 |  *  the CompoundPotential making up the whole model is also a
 | 
|---|
| 143 |  *  SerializablePotential. Hence, each in turn writes a single line with its
 | 
|---|
| 144 |  *  respective subset of parameters and particle types, describing this
 | 
|---|
| 145 |  *  particular fit function.
 | 
|---|
| 146 |  *
 | 
|---|
| 147 |  * \section potentials-function-evaluation How does the model evaluation work
 | 
|---|
| 148 |  *
 | 
|---|
| 149 |  *  We now come to the question of how the model and its derivative are actually
 | 
|---|
| 150 |  *  evaluated. We have an input vector from the training data and we have the
 | 
|---|
| 151 |  *  model with a specific set of parameters.
 | 
|---|
| 152 |  *
 | 
|---|
| 153 |  *  FunctionModel is just an abstract interface that is implemented by the
 | 
|---|
| 154 |  *  potential functions, such as CompoundPotential, that combines multiple
 | 
|---|
| 155 |  *  potentials into a single function for fitting, or PairPotential_Harmonic,
 | 
|---|
| 156 |  *  that is a specific fit function for harmonic bonds.
 | 
|---|
| 157 |  *
 | 
|---|
| 158 |  *  The main issue with the evaluation is picking the right set of distances from
 | 
|---|
| 159 |  *  ones given in the input vector and feed it to each potential contained in
 | 
|---|
| 160 |  *  CompoundPotential. 
 | 
|---|
| 161 |  *
 | 
|---|
| 162 |  *  Initially, the HomologyGraph only contains a list of configurations of a
 | 
|---|
| 163 |  *  specific fragments (i.e. the position of each atom in the fragment) and an
 | 
|---|
| 164 |  *  energy value. These first have to be converted into distances.
 | 
|---|
| 165 |  *
 | 
|---|
| 166 |  *  These distances are prepared by the TrainingData instantiation, i.e. a 
 | 
|---|
| 167 |  *  fragment with all its atomic positions has already been converted to the
 | 
|---|
| 168 |  *  set of all pair-wise interatomic distances.
 | 
|---|
| 169 |  * 
 | 
|---|
| 170 |  * \section potentials-distance-picking How does the distance picking work
 | 
|---|
| 171 |  *
 | 
|---|
| 172 |  *  Given a set of pair-wise distances, how do we pick the subset of distances
 | 
|---|
| 173 |  *  needed by a particular potential.
 | 
|---|
| 174 |  *  
 | 
|---|
| 175 |  *  Let's make an example first: Imagine a water molecule, i.e. one oxygen and
 | 
|---|
| 176 |  *  and two hydrogen atoms with two O-H bonds. Naturally, we obtain three pair-
 | 
|---|
| 177 |  *  wise distances, OH1, OH2 and H1H2. Now, we would like to fit a Morse
 | 
|---|
| 178 |  *  potential that just depends on a single interatomic distance. We would like
 | 
|---|
| 179 |  *  it to represents the O-H bond energy. Hence, the distance picker, namely
 | 
|---|
| 180 |  *  the Extractor function, needs to pick any subset of distance that contains
 | 
|---|
| 181 |  *  a unique single O-H distance. In effect, it needs to return a list 
 | 
|---|
| 182 |  *  containing OH1 and OH2 as the Morse potential needs to represent both bond
 | 
|---|
| 183 |  *  energies together.
 | 
|---|
| 184 |  *  
 | 
|---|
| 185 |  *  Now, this is really still quite simple as long as the potential only 
 | 
|---|
| 186 |  *  depends on a single distance. However, what if we continue and look at a
 | 
|---|
| 187 |  *  angle potential, requiring three atoms, i.e. H-O-H?
 | 
|---|
| 188 |  *  
 | 
|---|
| 189 |  *  Or even more complicated: Imagine an ethane molecule (C2H6) and we would
 | 
|---|
| 190 |  *  to represent the H-C-C angular interaction by a harmonic angle potential.
 | 
|---|
| 191 |  *  Now, there are multiple of these at the same time, namely six angular
 | 
|---|
| 192 |  *  interactions.
 | 
|---|
| 193 |  *  
 | 
|---|
| 194 |  *  What have to do is look for subgraphs inside a graph. Each potential comes
 | 
|---|
| 195 |  *  with a small graph that represents the binding structure, in our terms 
 | 
|---|
| 196 |  *  the bond model, that we expect. And we need to find the all matching
 | 
|---|
| 197 |  *  subgraphs in the whole graph being the fragment itself. Then, for each
 | 
|---|
| 198 |  *  subgraph the potential tells us in what order the pair-wise distances
 | 
|---|
| 199 |  *  associated with the subgraph are required to be. All of these subset of
 | 
|---|
| 200 |  *  distances are eventually concatenated and fed into the model on evaluation.
 | 
|---|
| 201 |  *  
 | 
|---|
| 202 |  * \section potentials-howto-use Howto use the potentials
 | 
|---|
| 203 |  *
 | 
|---|
| 204 |  *  We just give a brief run-down in terms of code on how to use the potentials.
 | 
|---|
| 205 |  *  Here, we just describe what to do in order to perform the fitting. This is
 | 
|---|
| 206 |  *  basically what is implemented in FragmentationFitPotentialAction.
 | 
|---|
| 207 |  *
 | 
|---|
| 208 |  *  \code
 | 
|---|
| 209 |  *  // we need the homology container and the representative graph we want to
 | 
|---|
| 210 |  *  // fit to.
 | 
|---|
| 211 |  *  HomologyContainer homologies;
 | 
|---|
| 212 |  *  const HomologyGraph graph = getSomeGraph(homologies);
 | 
|---|
| 213 |  *  Fragment::atomicnumbers_t h2o;
 | 
|---|
| 214 |  *  h2o += 8,1,1;
 | 
|---|
| 215 |  *  // TrainingData needs so called Extractors to get the required distances
 | 
|---|
| 216 |  *  // from the stored fragment. These functions are bound via boost::bind.
 | 
|---|
| 217 |  *  TrainingData AngleData(
 | 
|---|
| 218 |  *      boost::bind(&Extractors::gatherDistancesFromFragment,
 | 
|---|
| 219 |  *          boost::bind(&Fragment::getPositions, _1),
 | 
|---|
| 220 |  *          boost::bind(&Fragment::getAtomicNumbers, _1),
 | 
|---|
| 221 |  *          boost::cref(h2o),
 | 
|---|
| 222 |  *          _2)
 | 
|---|
| 223 |  *      );
 | 
|---|
| 224 |  *  // now we extract the distances and energies and store them
 | 
|---|
| 225 |  *  AngleData(homologies.getHomologousGraphs(graph));
 | 
|---|
| 226 |  *  // give ParticleTypes of this potential to make it unique
 | 
|---|
| 227 |  *  PairPotential_Angle::ParticleTypes_t types =
 | 
|---|
| 228 |  *        boost::assign::list_of<PairPotential_Angle::ParticleType_t>
 | 
|---|
| 229 |  *        (8)(1)(1)
 | 
|---|
| 230 |  *      ;
 | 
|---|
| 231 |  *  PairPotential_Angle angle(types);
 | 
|---|
| 232 |  *  // give initial parameter
 | 
|---|
| 233 |  *  FunctionModel::parameters_t params(PairPotential_Angle::MAXPARAMS, 0.);
 | 
|---|
| 234 |  *  // ... set some potential-specific initial parameters in params struct
 | 
|---|
| 235 |  *  angle.setParameters(params);
 | 
|---|
| 236 |  *
 | 
|---|
| 237 |  *  // use the potential as a FunctionModel along with prepared TrainingData
 | 
|---|
| 238 |  *  FunctionModel &model = angle;
 | 
|---|
| 239 |  *  FunctionApproximation approximator(AngleData, model);
 | 
|---|
| 240 |  *  approximator(FunctionApproximation::ParameterDerivative);
 | 
|---|
| 241 |  *
 | 
|---|
| 242 |  *  // obtain resulting parameters and check remaining L_2 and L_max error
 | 
|---|
| 243 |  *  angleparams = model.getParameters();
 | 
|---|
| 244 |  *  LOG(1, "INFO: L2sum = " << AngleData(model)
 | 
|---|
| 245 |  *      << ", LMax = " << AngleData(model) << ".");
 | 
|---|
| 246 |  *  \endcode
 | 
|---|
| 247 |  *
 | 
|---|
| 248 |  *  The evaluation of the fitted potential is then trivial, e.g.
 | 
|---|
| 249 |  *  \code
 | 
|---|
| 250 |  *  // constructed someplace
 | 
|---|
| 251 |  *  PairPotential_Angle angle(...);
 | 
|---|
| 252 |  *
 | 
|---|
| 253 |  *  // evaluate
 | 
|---|
| 254 |  *  FunctionModel::arguments_t args;
 | 
|---|
| 255 |  *  // .. initialise args to the desired distances
 | 
|---|
| 256 |  *  const double value = angle(args)[0]; // output is a vector!
 | 
|---|
| 257 |  *  \endcode
 | 
|---|
| 258 |  *
 | 
|---|
| 259 |  * \section potentials-stability-of-fit note in stability of fit
 | 
|---|
| 260 |  *
 | 
|---|
| 261 |  *  As we always start from random initial parameters (within a certain sensible
 | 
|---|
| 262 |  *  range at least), the non-linear fit does not always converge. Note that the
 | 
|---|
| 263 |  *  random values are drawn from the defined distribution and the uniform distribution
 | 
|---|
| 264 |  *  engine is obtained from the currently set, see \ref randomnumbers. Hence, you
 | 
|---|
| 265 |  *  can manipulate both in order to get different results or to set the seed such that
 | 
|---|
| 266 |  *  some "randomly" drawn value always work well (e.g. for testing). 
 | 
|---|
| 267 |  *  
 | 
|---|
| 268 |  *  In any case, the FragmentationFitPotentialAction has the option 
 | 
|---|
| 269 |  *  "take-best-of" to allow for multiple fits where the best (in terms of l2 error) 
 | 
|---|
| 270 |  *  is taken eventually. Furthermore, you can use the "set-threshold" option to
 | 
|---|
| 271 |  *  repeat the fit procedure until the L2 error has dropped below the given
 | 
|---|
| 272 |  *  threshold.
 | 
|---|
| 273 |  *
 | 
|---|
| 274 |  * \section potentials-howto-add Howto add new potentials
 | 
|---|
| 275 |  *
 | 
|---|
| 276 |  *  Adding a new potential requires the following:
 | 
|---|
| 277 |  *  -# Add the new modules to Potentials/Specifics
 | 
|---|
| 278 |  *  -# Add a unit test on the potential in Potentials/Specifics/unittests
 | 
|---|
| 279 |  *  -# Give the potential a type name and add it to PotentialTypes.def. Note
 | 
|---|
| 280 |  *     that the name must not contain white space.
 | 
|---|
| 281 |  *  -# Add the potential name as case to PotentialFactory such that it knows
 | 
|---|
| 282 |  *     how to instantiate your new potential when requested.
 | 
|---|
| 283 |  *  -# Remember to use the the RandomNumberGenerator for getting random starting
 | 
|---|
| 284 |  *     values!
 | 
|---|
| 285 |  *
 | 
|---|
| 286 |  * PotentialTypes.def contains a boost::preprocessor sequence of all
 | 
|---|
| 287 |  * potential names. PotentialFactory uses this sequence to build its enum to
 | 
|---|
| 288 |  * type map and inverse which the user sees when specifying the potential to
 | 
|---|
| 289 |  * fit via PotentialTypeValidator.
 | 
|---|
| 290 |  *
 | 
|---|
| 291 |  *
 | 
|---|
| 292 |  * \date 2017-05-14
 | 
|---|
| 293 |  */
 | 
|---|