source: src/Dynamics/ForceAnnealing.hpp@ 6d7c73

ForceAnnealing_with_BondGraph_continued
Last change on this file since 6d7c73 was cb80d4, checked in by Frederik Heber <frederik.heber@…>, 8 years ago

Extracted calculation of weights per atom into BondVectors.

  • Property mode set to 100644
File size: 22.1 KB
Line 
1/*
2 * ForceAnnealing.hpp
3 *
4 * Created on: Aug 02, 2014
5 * Author: heber
6 */
7
8#ifndef FORCEANNEALING_HPP_
9#define FORCEANNEALING_HPP_
10
11// include config.h
12#ifdef HAVE_CONFIG_H
13#include <config.h>
14#endif
15
16#include <algorithm>
17#include <functional>
18#include <iterator>
19
20#include <boost/bind.hpp>
21
22#include "Atom/atom.hpp"
23#include "Atom/AtomSet.hpp"
24#include "CodePatterns/Assert.hpp"
25#include "CodePatterns/Info.hpp"
26#include "CodePatterns/Log.hpp"
27#include "CodePatterns/Verbose.hpp"
28#include "Descriptors/AtomIdDescriptor.hpp"
29#include "Dynamics/AtomicForceManipulator.hpp"
30#include "Dynamics/BondVectors.hpp"
31#include "Fragmentation/ForceMatrix.hpp"
32#include "Graph/BoostGraphCreator.hpp"
33#include "Graph/BoostGraphHelpers.hpp"
34#include "Graph/BreadthFirstSearchGatherer.hpp"
35#include "Helpers/helpers.hpp"
36#include "Helpers/defs.hpp"
37#include "LinearAlgebra/LinearSystemOfEquations.hpp"
38#include "LinearAlgebra/MatrixContent.hpp"
39#include "LinearAlgebra/Vector.hpp"
40#include "LinearAlgebra/VectorContent.hpp"
41#include "Thermostats/ThermoStatContainer.hpp"
42#include "Thermostats/Thermostat.hpp"
43#include "World.hpp"
44
45/** This class is the essential build block for performing structural optimization.
46 *
47 * Sadly, we have to use some static instances as so far values cannot be passed
48 * between actions. Hence, we need to store the current step and the adaptive-
49 * step width (we cannot perform a line search, as we have no control over the
50 * calculation of the forces).
51 *
52 * However, we do use the bond graph, i.e. if a single atom needs to be shifted
53 * to the left, then the whole molecule left of it is shifted, too. This is
54 * controlled by the \a max_distance parameter.
55 */
56template <class T>
57class ForceAnnealing : public AtomicForceManipulator<T>
58{
59public:
60 /** Constructor of class ForceAnnealing.
61 *
62 * \note We use a fixed delta t of 1.
63 *
64 * \param _atoms set of atoms to integrate
65 * \param _Deltat time step width in atomic units
66 * \param _IsAngstroem whether length units are in angstroem or bohr radii
67 * \param _maxSteps number of optimization steps to perform
68 * \param _max_distance up to this bond order is bond graph taken into account.
69 */
70 ForceAnnealing(
71 AtomSetMixin<T> &_atoms,
72 const double _Deltat,
73 bool _IsAngstroem,
74 const size_t _maxSteps,
75 const int _max_distance,
76 const double _damping_factor) :
77 AtomicForceManipulator<T>(_atoms, _Deltat, _IsAngstroem),
78 maxSteps(_maxSteps),
79 max_distance(_max_distance),
80 damping_factor(_damping_factor)
81 {}
82
83 /** Destructor of class ForceAnnealing.
84 *
85 */
86 ~ForceAnnealing()
87 {}
88
89 /** Performs Gradient optimization.
90 *
91 * We assume that forces have just been calculated.
92 *
93 *
94 * \param CurrentTimeStep current time step (i.e. \f$ t + \Delta t \f$ in the sense of the velocity verlet)
95 * \param offset offset in matrix file to the first force component
96 * \todo This is not yet checked if it is correctly working with DoConstrainedMD set >0.
97 */
98 void operator()(
99 const int _CurrentTimeStep,
100 const size_t _offset,
101 const bool _UseBondgraph)
102 {
103 // make sum of forces equal zero
104 AtomicForceManipulator<T>::correctForceMatrixForFixedCenterOfMass(_offset, _CurrentTimeStep);
105
106 // are we in initial step? Then set static entities
107 Vector maxComponents(zeroVec);
108 if (currentStep == 0) {
109 currentDeltat = AtomicForceManipulator<T>::Deltat;
110 currentStep = 1;
111 LOG(2, "DEBUG: Initial step, setting values, current step is #" << currentStep);
112
113 // always use atomic annealing on first step
114 anneal(_CurrentTimeStep, _offset, maxComponents);
115 } else {
116 ++currentStep;
117 LOG(2, "DEBUG: current step is #" << currentStep);
118
119 if (_UseBondgraph)
120 annealWithBondGraph(_CurrentTimeStep, _offset, maxComponents);
121 else
122 anneal(_CurrentTimeStep, _offset, maxComponents);
123 }
124
125 LOG(1, "STATUS: Largest remaining force components at step #"
126 << currentStep << " are " << maxComponents);
127
128 // are we in final step? Remember to reset static entities
129 if (currentStep == maxSteps) {
130 LOG(2, "DEBUG: Final step, resetting values");
131 reset();
132 }
133 }
134
135 /** Performs Gradient optimization on the atoms.
136 *
137 * We assume that forces have just been calculated.
138 *
139 * \param CurrentTimeStep current time step (i.e. \f$ t + \Delta t \f$ in the sense of the velocity verlet)
140 * \param offset offset in matrix file to the first force component
141 * \param maxComponents to be filled with maximum force component over all atoms
142 */
143 void anneal(
144 const int CurrentTimeStep,
145 const size_t offset,
146 Vector &maxComponents)
147 {
148 bool deltat_decreased = false;
149 for(typename AtomSetMixin<T>::iterator iter = AtomicForceManipulator<T>::atoms.begin();
150 iter != AtomicForceManipulator<T>::atoms.end(); ++iter) {
151 // atom's force vector gives steepest descent direction
152 const Vector oldPosition = (*iter)->getPositionAtStep(CurrentTimeStep-2 >= 0 ? CurrentTimeStep - 2 : 0);
153 const Vector currentPosition = (*iter)->getPosition();
154 const Vector oldGradient = (*iter)->getAtomicForceAtStep(CurrentTimeStep-2 >= 0 ? CurrentTimeStep - 2 : 0);
155 const Vector currentGradient = (*iter)->getAtomicForce();
156 LOG(4, "DEBUG: oldPosition for atom " << **iter << " is " << oldPosition);
157 LOG(4, "DEBUG: currentPosition for atom " << **iter << " is " << currentPosition);
158 LOG(4, "DEBUG: oldGradient for atom " << **iter << " is " << oldGradient);
159 LOG(4, "DEBUG: currentGradient for atom " << **iter << " is " << currentGradient);
160// LOG(4, "DEBUG: Force for atom " << **iter << " is " << currentGradient);
161
162 // we use Barzilai-Borwein update with position reversed to get descent
163 const Vector PositionDifference = currentPosition - oldPosition;
164 const Vector GradientDifference = (currentGradient - oldGradient);
165 double stepwidth = 0.;
166 if (GradientDifference.Norm() > MYEPSILON)
167 stepwidth = fabs(PositionDifference.ScalarProduct(GradientDifference))/
168 GradientDifference.NormSquared();
169 if (fabs(stepwidth) < 1e-10) {
170 // dont' warn in first step, deltat usage normal
171 if (currentStep != 1)
172 ELOG(1, "INFO: Barzilai-Borwein stepwidth is zero, using deltat " << currentDeltat << " instead.");
173 stepwidth = currentDeltat;
174 }
175 Vector PositionUpdate = stepwidth * currentGradient;
176 LOG(3, "DEBUG: Update would be " << stepwidth << "*" << currentGradient << " = " << PositionUpdate);
177
178 // extract largest components for showing progress of annealing
179 for(size_t i=0;i<NDIM;++i)
180 if (currentGradient[i] > maxComponents[i])
181 maxComponents[i] = currentGradient[i];
182
183 // steps may go back and forth again (updates are of same magnitude but
184 // have different sign: Check whether this is the case and one step with
185 // deltat to interrupt this sequence
186 if ((currentStep > 1) && (!PositionDifference.IsZero()))
187 if ((PositionUpdate.ScalarProduct(PositionDifference) < 0)
188 && (fabs(PositionUpdate.NormSquared()-PositionDifference.NormSquared()) < 1e-3)) {
189 // for convergence we want a null sequence here, too
190 if (!deltat_decreased) {
191 deltat_decreased = true;
192 currentDeltat = .5*currentDeltat;
193 }
194 LOG(2, "DEBUG: Upgrade in other direction: " << PositionUpdate
195 << " > " << PositionDifference
196 << ", using deltat: " << currentDeltat);
197 PositionUpdate = currentDeltat * currentGradient;
198 }
199
200 // finally set new values
201 (*iter)->setPosition(currentPosition + PositionUpdate);
202 }
203 }
204
205 /** Performs Gradient optimization on the bonds.
206 *
207 * We assume that forces have just been calculated. These forces are projected
208 * onto the bonds and these are annealed subsequently by moving atoms in the
209 * bond neighborhood on either side conjunctively.
210 *
211 *
212 * \param CurrentTimeStep current time step (i.e. \f$ t + \Delta t \f$ in the sense of the velocity verlet)
213 * \param offset offset in matrix file to the first force component
214 * \param maxComponents to be filled with maximum force component over all atoms
215 */
216 void annealWithBondGraph(
217 const int CurrentTimeStep,
218 const size_t offset,
219 Vector &maxComponents)
220 {
221 // get nodes on either side of selected bond via BFS discovery
222 BoostGraphCreator BGcreator;
223 BGcreator.createFromRange(
224 AtomicForceManipulator<T>::atoms.begin(),
225 AtomicForceManipulator<T>::atoms.end(),
226 AtomicForceManipulator<T>::atoms.size(),
227 BreadthFirstSearchGatherer::AlwaysTruePredicate);
228 BreadthFirstSearchGatherer NodeGatherer(BGcreator);
229
230 /// We assume that a force is local, i.e. a bond is too short yet and hence
231 /// the atom needs to be moved. However, all the adjacent (bound) atoms might
232 /// already be at the perfect distance. If we just move the atom alone, we ruin
233 /// all the other bonds. Hence, it would be sensible to move every atom found
234 /// through the bond graph in the direction of the force as well by the same
235 /// PositionUpdate. This is almost what we are going to do.
236
237 /// One issue is: If we need to shorten bond, then we use the PositionUpdate
238 /// also on the the other bond partner already. This is because it is in the
239 /// direction of the bond. Therefore, the update is actually performed twice on
240 /// each bond partner, i.e. the step size is twice as large as it should be.
241 /// This problem only occurs when bonds need to be shortened, not when they
242 /// need to be made longer (then the force vector is facing the other
243 /// direction than the bond vector).
244 /// As a remedy we need to average the force on either end of the bond and
245 /// check whether each gradient points inwards out or outwards with respect
246 /// to the bond and then shift accordingly.
247
248 /// One more issue is that the projection onto the bond directions does not
249 /// recover the gradient but may be larger as the bond directions are a
250 /// generating system and not a basis (e.g. 3 bonds on a plane where 2 would
251 /// suffice to span the plane). To this end, we need to account for the
252 /// overestimation and obtain a weighting for each bond.
253
254 // initialize helper class for bond vectors using bonds from range of atoms
255 BondVectors bv;
256 bv.setFromAtomRange< T >(
257 AtomicForceManipulator<T>::atoms.begin(),
258 AtomicForceManipulator<T>::atoms.end(),
259 currentStep);
260 const BondVectors::container_t &sorted_bonds = bv.getSorted();
261
262 // knowing the number of bonds in total, we can setup the storage for the
263 // projected forces
264 enum whichatom_t {
265 leftside=0,
266 rightside=1,
267 MAX_sides
268 };
269 std::vector< // time step
270 std::vector< // which bond side
271 std::vector<double> > // over all bonds
272 > projected_forces(2); // one for leftatoms, one for rightatoms (and for both time steps)
273 for (size_t i=0;i<2;++i) {
274 projected_forces[i].resize(MAX_sides);
275 for (size_t j=0;j<MAX_sides;++j)
276 projected_forces[i][j].resize(sorted_bonds.size(), 0.);
277 }
278
279 // for each atom we need to gather weights and then project the gradient
280 typedef std::map<atomId_t, BondVectors::weights_t > weights_per_atom_t;
281 std::vector<weights_per_atom_t> weights_per_atom(2);
282 for (size_t timestep = 0; timestep <= 1; ++timestep) {
283 // TODO: We have this extra step in between because of DoOutput copying
284 // change this making the code easier to understand
285 const size_t CurrentStep = CurrentTimeStep-2*timestep >= 0 ? CurrentTimeStep - 2*timestep : 0;
286
287 for(typename AtomSetMixin<T>::const_iterator iter = AtomicForceManipulator<T>::atoms.begin();
288 iter != AtomicForceManipulator<T>::atoms.end(); ++iter) {
289 const atom &walker = *(*iter);
290 const Vector &walkerGradient = walker.getAtomicForceAtStep(CurrentStep);
291 LOG(3, "DEBUG: Gradient of atom #" << walker.getId() << ", namely "
292 << walker << " is " << walkerGradient);
293
294 const BondList& ListOfBonds = walker.getListOfBonds();
295 if (walkerGradient.Norm() > MYEPSILON) {
296
297 // gather subset of BondVectors for the current atom
298 const std::vector<Vector> BondVectors =
299 bv.getAtomsBondVectorsAtStep(walker, CurrentStep);
300
301 // go through all its bonds and calculate what magnitude is represented
302 // by the others i.e. sum of scalar products against other bonds
303 const std::pair<weights_per_atom_t::iterator, bool> inserter =
304 weights_per_atom[timestep].insert(
305 std::make_pair(walker.getId(),
306 bv.getWeightsForAtomAtStep(walker, CurrentStep)) );
307 ASSERT( inserter.second,
308 "ForceAnnealing::operator() - weight map for atom "+toString(walker)
309 +" and time step "+toString(timestep)+" already filled?");
310 BondVectors::weights_t &weights = inserter.first->second;
311 ASSERT( weights.size() == ListOfBonds.size(),
312 "ForceAnnealing::operator() - number of weights "
313 +toString(weights.size())+" does not match number of bonds "
314 +toString(ListOfBonds.size())+", error in calculation?");
315
316 // projected gradient over all bonds and place in one of projected_forces
317 // using the obtained weights
318 {
319 BondVectors::weights_t::const_iterator weightiter = weights.begin();
320 std::vector<Vector>::const_iterator vectoriter = BondVectors.begin();
321 Vector forcesum_check;
322 for(BondList::const_iterator bonditer = ListOfBonds.begin();
323 bonditer != ListOfBonds.end(); ++bonditer, ++weightiter, ++vectoriter) {
324 const bond::ptr &current_bond = *bonditer;
325 const Vector &BondVector = *vectoriter;
326
327 std::vector<double> &forcelist = (&walker == current_bond->leftatom) ?
328 projected_forces[timestep][leftside] : projected_forces[timestep][rightside];
329 const size_t index = bv.getIndexForBond(current_bond);
330 ASSERT( index != (size_t)-1,
331 "ForceAnnealing() - could not find bond "+toString(*current_bond)
332 +" in bondvectors");
333 forcelist[index] = (*weightiter)*walkerGradient.ScalarProduct(BondVector);
334 LOG(4, "DEBUG: BondVector " << BondVector << " receives projected force of "
335 << forcelist[index]);
336 forcesum_check += forcelist[index] * BondVector;
337 }
338 ASSERT( weightiter == weights.end(),
339 "ForceAnnealing - weightiter is not at end when it should be.");
340 ASSERT( vectoriter == BondVectors.end(),
341 "ForceAnnealing - vectoriter is not at end when it should be.");
342 LOG(3, "DEBUG: sum of projected forces is " << forcesum_check);
343 }
344
345 } else {
346 LOG(2, "DEBUG: Gradient is " << walkerGradient << " less than "
347 << MYEPSILON << " for atom " << walker);
348 // note that projected_forces is initialized to full length and filled
349 // with zeros. Hence, nothing to do here
350 }
351 }
352 }
353
354 // step through each bond and shift the atoms
355 std::map<atomId_t, Vector> GatheredUpdates; //!< gathers all updates which are applied at the end
356
357 LOG(3, "DEBUG: current step is " << currentStep << ", given time step is " << CurrentTimeStep);
358 const BondVectors::mapped_t bondvectors = bv.getBondVectorsAtStep(currentStep);
359
360 for (BondVectors::container_t::const_iterator bondsiter = sorted_bonds.begin();
361 bondsiter != sorted_bonds.end(); ++bondsiter) {
362 const bond::ptr &current_bond = *bondsiter;
363 const size_t index = bv.getIndexForBond(current_bond);
364 const atom* bondatom[MAX_sides] = {
365 current_bond->leftatom,
366 current_bond->rightatom
367 };
368
369 // remove the edge
370#ifndef NDEBUG
371 const bool status =
372#endif
373 BGcreator.removeEdge(bondatom[leftside]->getId(), bondatom[rightside]->getId());
374 ASSERT( status, "ForceAnnealing() - edge to found bond is not present?");
375
376 // gather nodes for either atom
377 BoostGraphHelpers::Nodeset_t bondside_set[MAX_sides];
378 BreadthFirstSearchGatherer::distance_map_t distance_map[MAX_sides];
379 for (size_t side=leftside;side<MAX_sides;++side) {
380 bondside_set[side] = NodeGatherer(bondatom[side]->getId(), max_distance);
381 distance_map[side] = NodeGatherer.getDistances();
382 std::sort(bondside_set[side].begin(), bondside_set[side].end());
383 }
384
385 // re-add edge
386 BGcreator.addEdge(bondatom[leftside]->getId(), bondatom[rightside]->getId());
387
388 // do for both leftatom and rightatom of bond
389 for (size_t side = leftside; side < MAX_sides; ++side) {
390 const double &bondforce = projected_forces[0][side][index];
391 const double &oldbondforce = projected_forces[1][side][index];
392 const double bondforcedifference = fabs(bondforce - oldbondforce);
393 LOG(4, "DEBUG: bondforce for " << (side == leftside ? "left" : "right")
394 << " side of bond is " << bondforce);
395 LOG(4, "DEBUG: oldbondforce for " << (side == leftside ? "left" : "right")
396 << " side of bond is " << oldbondforce);
397 // if difference or bondforce itself is zero, do nothing
398 if ((fabs(bondforce) < MYEPSILON) || (fabs(bondforcedifference) < MYEPSILON))
399 continue;
400
401 // get BondVector to bond
402 const BondVectors::mapped_t::const_iterator bviter =
403 bondvectors.find(current_bond);
404 ASSERT( bviter != bondvectors.end(),
405 "ForceAnnealing() - cannot find current_bond ?");
406 ASSERT( fabs(bviter->second.Norm() -1.) < MYEPSILON,
407 "ForceAnnealing() - norm of BondVector is not one");
408 const Vector &BondVector = bviter->second;
409
410 // calculate gradient and position differences for stepwidth
411 const Vector currentGradient = bondforce * BondVector;
412 LOG(4, "DEBUG: current projected gradient for "
413 << (side == leftside ? "left" : "right") << " side of bond is " << currentGradient);
414 const Vector &oldPosition = bondatom[side]->getPositionAtStep(CurrentTimeStep-2 >= 0 ? CurrentTimeStep - 2 : 0);
415 const Vector &currentPosition = bondatom[side]->getPositionAtStep(currentStep);
416 const Vector PositionDifference = currentPosition - oldPosition;
417 LOG(4, "DEBUG: old position is " << oldPosition);
418 LOG(4, "DEBUG: current position is " << currentPosition);
419 LOG(4, "DEBUG: difference in position is " << PositionDifference);
420 LOG(4, "DEBUG: abs. difference in forces is " << bondforcedifference);
421 LOG(4, "DEBUG: bondvector is " << BondVector);
422
423 // calculate step width
424 double stepwidth =
425 fabs(PositionDifference.ScalarProduct(BondVector))/bondforcedifference;
426 if (fabs(stepwidth) < 1e-10) {
427 // dont' warn in first step, deltat usage normal
428 if (currentStep != 1)
429 ELOG(1, "INFO: Barzilai-Borwein stepwidth is zero, using deltat " << currentDeltat << " instead.");
430 stepwidth = currentDeltat;
431 }
432 Vector PositionUpdate = stepwidth * currentGradient;
433 LOG(3, "DEBUG: Update would be " << stepwidth << "*" << currentGradient << " = " << PositionUpdate);
434
435 // add PositionUpdate for all nodes in the bondside_set
436 for (BoostGraphHelpers::Nodeset_t::const_iterator setiter = bondside_set[side].begin();
437 setiter != bondside_set[side].end(); ++setiter) {
438 const BreadthFirstSearchGatherer::distance_map_t::const_iterator diter
439 = distance_map[side].find(*setiter);
440 ASSERT( diter != distance_map[side].end(),
441 "ForceAnnealing() - could not find distance to an atom.");
442 const double factor = pow(damping_factor, diter->second+1);
443 LOG(3, "DEBUG: Update for atom #" << *setiter << " will be "
444 << factor << "*" << PositionUpdate);
445 if (GatheredUpdates.count((*setiter))) {
446 GatheredUpdates[(*setiter)] += factor*PositionUpdate;
447 } else {
448 GatheredUpdates.insert(
449 std::make_pair(
450 (*setiter),
451 factor*PositionUpdate) );
452 }
453 }
454 }
455 }
456
457 for(typename AtomSetMixin<T>::iterator iter = AtomicForceManipulator<T>::atoms.begin();
458 iter != AtomicForceManipulator<T>::atoms.end(); ++iter) {
459 atom &walker = *(*iter);
460 // extract largest components for showing progress of annealing
461 const Vector currentGradient = walker.getAtomicForce();
462 for(size_t i=0;i<NDIM;++i)
463 if (currentGradient[i] > maxComponents[i])
464 maxComponents[i] = currentGradient[i];
465 }
466
467 // apply the gathered updates
468 for (std::map<atomId_t, Vector>::const_iterator iter = GatheredUpdates.begin();
469 iter != GatheredUpdates.end(); ++iter) {
470 const atomId_t &atomid = iter->first;
471 const Vector &update = iter->second;
472 atom* const walker = World::getInstance().getAtom(AtomById(atomid));
473 ASSERT( walker != NULL,
474 "ForceAnnealing() - walker with id "+toString(atomid)+" has suddenly disappeared.");
475 LOG(3, "DEBUG: Applying update " << update << " to atom #" << atomid
476 << ", namely " << *walker);
477 walker->setPosition( walker->getPosition() + update );
478 }
479 }
480
481 /** Reset function to unset static entities and artificial velocities.
482 *
483 */
484 void reset()
485 {
486 currentDeltat = 0.;
487 currentStep = 0;
488 }
489
490private:
491 //!> contains the current step in relation to maxsteps
492 static size_t currentStep;
493 //!> contains the maximum number of steps, determines initial and final step with currentStep
494 size_t maxSteps;
495 static double currentDeltat;
496 //!> minimum deltat for internal while loop (adaptive step width)
497 static double MinimumDeltat;
498 //!> contains the maximum bond graph distance up to which shifts of a single atom are spread
499 const int max_distance;
500 //!> the shifted is dampened by this factor with the power of the bond graph distance to the shift causing atom
501 const double damping_factor;
502};
503
504template <class T>
505double ForceAnnealing<T>::currentDeltat = 0.;
506template <class T>
507size_t ForceAnnealing<T>::currentStep = 0;
508template <class T>
509double ForceAnnealing<T>::MinimumDeltat = 1e-8;
510
511#endif /* FORCEANNEALING_HPP_ */
Note: See TracBrowser for help on using the repository browser.