source: src/Dynamics/ForceAnnealing.hpp@ d51e62

AutomationFragmentation_failures Candidate_v1.6.1 ChemicalSpaceEvaluator Exclude_Hydrogens_annealWithBondGraph ForceAnnealing_with_BondGraph ForceAnnealing_with_BondGraph_contraction-expansion Gui_displays_atomic_force_velocity PythonUI_with_named_parameters StoppableMakroAction TremoloParser_IncreasedPrecision
Last change on this file since d51e62 was 355915, checked in by Frederik Heber <frederik.heber@…>, 7 years ago

ForceAnnealing now calculates force on either side of bond in direction of bond.

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