source: src/Dynamics/ForceAnnealing.hpp@ ab7bfa6

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

ForceAnnealing converts atomic forces to bond forces and optimize these.

  • the initial idea was using the bond graph (i.e. pushing also the adjacent atoms in order not to ruin the distance to them). However, this falls short when bonds need to be shortened - we update each bond partner twice.
  • Therefore, we convert the atomic force to a force per bond via an SVD and shift then both bond partners (and adjacent neighbors) at the same time.
  • Property mode set to 100644
File size: 14.3 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 "Atom/atom.hpp"
17#include "Atom/AtomSet.hpp"
18#include "CodePatterns/Assert.hpp"
19#include "CodePatterns/Info.hpp"
20#include "CodePatterns/Log.hpp"
21#include "CodePatterns/Verbose.hpp"
22#include "Descriptors/AtomIdDescriptor.hpp"
23#include "Dynamics/AtomicForceManipulator.hpp"
24#include "Fragmentation/ForceMatrix.hpp"
25#include "Graph/BoostGraphCreator.hpp"
26#include "Graph/BoostGraphHelpers.hpp"
27#include "Graph/BreadthFirstSearchGatherer.hpp"
28#include "Helpers/helpers.hpp"
29#include "Helpers/defs.hpp"
30#include "LinearAlgebra/LinearSystemOfEquations.hpp"
31#include "LinearAlgebra/MatrixContent.hpp"
32#include "LinearAlgebra/Vector.hpp"
33#include "LinearAlgebra/VectorContent.hpp"
34#include "Thermostats/ThermoStatContainer.hpp"
35#include "Thermostats/Thermostat.hpp"
36#include "World.hpp"
37
38/** This class is the essential build block for performing structural optimization.
39 *
40 * Sadly, we have to use some static instances as so far values cannot be passed
41 * between actions. Hence, we need to store the current step and the adaptive-
42 * step width (we cannot perform a line search, as we have no control over the
43 * calculation of the forces).
44 *
45 * However, we do use the bond graph, i.e. if a single atom needs to be shifted
46 * to the left, then the whole molecule left of it is shifted, too. This is
47 * controlled by the \a max_distance parameter.
48 */
49template <class T>
50class ForceAnnealing : public AtomicForceManipulator<T>
51{
52public:
53 /** Constructor of class ForceAnnealing.
54 *
55 * \note We use a fixed delta t of 1.
56 *
57 * \param _atoms set of atoms to integrate
58 * \param _Deltat time step width in atomic units
59 * \param _IsAngstroem whether length units are in angstroem or bohr radii
60 * \param _maxSteps number of optimization steps to perform
61 * \param _max_distance up to this bond order is bond graph taken into account.
62 */
63 ForceAnnealing(
64 AtomSetMixin<T> &_atoms,
65 bool _IsAngstroem,
66 const size_t _maxSteps,
67 const int _max_distance,
68 const double _damping_factor) :
69 AtomicForceManipulator<T>(_atoms, 1., _IsAngstroem),
70 maxSteps(_maxSteps),
71 max_distance(_max_distance),
72 damping_factor(_damping_factor)
73 {}
74 /** Destructor of class ForceAnnealing.
75 *
76 */
77 ~ForceAnnealing()
78 {}
79
80 /** Performs Gradient optimization.
81 *
82 * We assume that forces have just been calculated.
83 *
84 *
85 * \param NextStep current time step (i.e. \f$ t + \Delta t \f$ in the sense of the velocity verlet)
86 * \param offset offset in matrix file to the first force component
87 * \todo This is not yet checked if it is correctly working with DoConstrainedMD set >0.
88 */
89 void operator()(const int NextStep, const size_t offset)
90 {
91 // make sum of forces equal zero
92 AtomicForceManipulator<T>::correctForceMatrixForFixedCenterOfMass(offset,NextStep);
93
94 // are we in initial step? Then set static entities
95 if (currentStep == 0) {
96 currentDeltat = AtomicForceManipulator<T>::Deltat;
97 currentStep = 1;
98 LOG(2, "DEBUG: Initial step, setting values, current step is #" << currentStep);
99 } else {
100 ++currentStep;
101 LOG(2, "DEBUG: current step is #" << currentStep);
102 }
103
104 // get nodes on either side of selected bond via BFS discovery
105// std::vector<atomId_t> atomids;
106// for(typename AtomSetMixin<T>::iterator iter = AtomicForceManipulator<T>::atoms.begin();
107// iter != AtomicForceManipulator<T>::atoms.end(); ++iter) {
108// atomids.push_back((*iter)->getId());
109// }
110// ASSERT( atomids.size() == AtomicForceManipulator<T>::atoms.size(),
111// "ForceAnnealing() - could not gather all atomic ids?");
112 BoostGraphCreator BGcreator;
113 BGcreator.createFromRange(
114 AtomicForceManipulator<T>::atoms.begin(),
115 AtomicForceManipulator<T>::atoms.end(),
116 AtomicForceManipulator<T>::atoms.size(),
117 BreadthFirstSearchGatherer::AlwaysTruePredicate);
118 BreadthFirstSearchGatherer NodeGatherer(BGcreator);
119
120 /// We assume that a force is local, i.e. a bond is too short yet and hence
121 /// the atom needs to be moved. However, all the adjacent (bound) atoms might
122 /// already be at the perfect distance. If we just move the atom alone, we ruin
123 /// all the other bonds. Hence, it would be sensible to move every atom found
124 /// through the bond graph in the direction of the force as well by the same
125 /// PositionUpdate. This is almost what we are going to do.
126
127 /// One more issue is: If we need to shorten bond, then we use the PositionUpdate
128 /// also on the the other bond partner already. This is because it is in the
129 /// direction of the bond. Therefore, the update is actually performed twice on
130 /// each bond partner, i.e. the step size is twice as large as it should be.
131 /// This problem only occurs when bonds need to be shortened, not when they
132 /// need to be made longer (then the force vector is facing the other
133 /// direction than the bond vector).
134 /// As a remedy we need to know the forces "per bond" and not per atom.
135 /// In effect, the gradient is the error per atom. However, we need an
136 /// error per bond. To this end, we set up a matrix A that translate the
137 /// vector of bond energies into a vector of atomic force component and
138 /// then we simply solve the linear system (inverse problem) via an SVD
139 /// and use the bond gradients to get the PositionUpdate for both bond
140 /// partners at the same time when we go through all bonds.
141
142 // gather/enumerate all bonds
143 std::set<bond::ptr> allbonds;
144 std::vector<atomId_t> allatomids;
145 Vector maxComponents(zeroVec);
146 for(typename AtomSetMixin<T>::iterator iter = AtomicForceManipulator<T>::atoms.begin();
147 iter != AtomicForceManipulator<T>::atoms.end(); ++iter) {
148 const atom &walker = *(*iter);
149 allatomids.push_back(walker.getId());
150 const BondList& ListOfBonds = walker.getListOfBonds();
151 for(BondList::const_iterator bonditer = ListOfBonds.begin();
152 bonditer != ListOfBonds.end(); ++bonditer) {
153 const bond::ptr &current_bond = *bonditer;
154 allbonds.insert(current_bond);
155 }
156
157 // extract largest components for showing progress of annealing
158 const Vector currentGradient = (*iter)->getAtomicForce();
159 for(size_t i=0;i<NDIM;++i)
160 if (currentGradient[i] > maxComponents[i])
161 maxComponents[i] = currentGradient[i];
162
163 // reset force vector for next step except on final one
164 if (currentStep != maxSteps)
165 (*iter)->setAtomicForce(zeroVec);
166 }
167 std::sort(allatomids.begin(), allatomids.end());
168 // converting set back to vector is fastest when requiring sorted and unique,
169 // see https://stackoverflow.com/questions/1041620/whats-the-most-efficient-way-to-erase-duplicates-and-sort-a-vector
170 typedef std::vector<bond::ptr> bondvector_t;
171 bondvector_t bondvector( allbonds.begin(), allbonds.end() );
172
173 // setup matrix A given the enumerated bonds
174 LinearSystemOfEquations lseq(AtomicForceManipulator<T>::atoms.size(), bondvector.size());
175 for (size_t i = 0;i<bondvector.size();++i) {
176 const atom* bondatom[2] = {
177 bondvector[i]->leftatom,
178 bondvector[i]->rightatom
179 };
180 size_t index[2];
181 for (size_t n=0;n<2;++n) {
182 const std::pair<std::vector<atomId_t>::iterator, std::vector<atomId_t>::iterator> atomiditer =
183 std::equal_range(allatomids.begin(), allatomids.end(), bondatom[n]->getId());
184 index[n] = std::distance(allatomids.begin(), atomiditer.first);
185 (*lseq.A).at(index[0],index[1]) = 1.;
186 (*lseq.A).at(index[1],index[0]) = 1.;
187 }
188 }
189 lseq.SetSymmetric(true);
190
191 // for each component and for current and last time step
192 // solve Ax=y with given A and y being the vectorized atomic force
193 double *tmpforces = new double[bondvector.size()];
194 double *bondforces = new double[bondvector.size()];
195 double *oldbondforces = new double[bondvector.size()];
196 double *bondforceref[2] = {
197 bondforces,
198 oldbondforces
199 };
200 for (size_t n=0;n<bondvector.size();++n) {
201 bondforces[n] = 0.;
202 oldbondforces[n] = 0.;
203 }
204 for (size_t timestep = 0; timestep <= 1; ++timestep) {
205 for (size_t component = 0; component < NDIM; ++component) {
206 for(typename AtomSetMixin<T>::iterator iter = AtomicForceManipulator<T>::atoms.begin();
207 iter != AtomicForceManipulator<T>::atoms.end(); ++iter) {
208 const atom &walker = *(*iter);
209 const std::pair<std::vector<atomId_t>::iterator, std::vector<atomId_t>::iterator> atomiditer =
210 std::equal_range(allatomids.begin(), allatomids.end(), walker.getId());
211 const size_t i = std::distance(allatomids.begin(), atomiditer.first);
212 (*lseq.b).at(i) = timestep == 0 ?
213 walker.getAtomicForce()[component] :
214 walker.getAtomicForceAtStep(NextStep-2 >= 0 ? NextStep - 2 : 0)[component];
215 }
216 lseq.GetSolutionAsArray(tmpforces);
217 for (size_t i = 0;i<bondvector.size();++i)
218 bondforceref[timestep][i] += tmpforces[i];
219 }
220 }
221
222 // step through each bond and shift the atoms
223 std::map<atomId_t, Vector> GatheredUpdates; //!< gathers all updates which are applied at the end
224 for (size_t i = 0;i<bondvector.size();++i) {
225 const atom* bondatom[2] = {
226 bondvector[i]->leftatom,
227 bondvector[i]->rightatom};
228 const double &bondforce = bondforces[i];
229 const double &oldbondforce = oldbondforces[i];
230 const double bondforcedifference = (bondforce - oldbondforce);
231 Vector BondVector = (bondatom[0]->getPosition() - bondatom[1]->getPosition());
232 BondVector.Normalize();
233 double stepwidth = 0.;
234 for (size_t n=0;n<2;++n) {
235 const Vector oldPosition = bondatom[n]->getPositionAtStep(NextStep-2 >= 0 ? NextStep - 2 : 0);
236 const Vector currentPosition = bondatom[n]->getPosition();
237 stepwidth += fabs((currentPosition - oldPosition).ScalarProduct(BondVector))/bondforcedifference;
238 }
239 stepwidth = stepwidth/2;
240 Vector PositionUpdate = stepwidth * BondVector;
241 if (fabs(stepwidth) < 1e-10) {
242 // dont' warn in first step, deltat usage normal
243 if (currentStep != 1)
244 ELOG(1, "INFO: Barzilai-Borwein stepwidth is zero, using deltat " << currentDeltat << " instead.");
245 PositionUpdate = currentDeltat * BondVector;
246 }
247 LOG(3, "DEBUG: Update would be " << PositionUpdate);
248
249 // remove the edge
250#ifndef NDEBUG
251 const bool status =
252#endif
253 BGcreator.removeEdge(bondatom[0]->getId(), bondatom[1]->getId());
254 ASSERT( status, "ForceAnnealing() - edge to found bond is not present?");
255
256 // gather nodes for either atom
257 BoostGraphHelpers::Nodeset_t bondside_set[2];
258 BreadthFirstSearchGatherer::distance_map_t distance_map[2];
259 for (size_t n=0;n<2;++n) {
260 bondside_set[n] = NodeGatherer(bondatom[n]->getId(), max_distance);
261 distance_map[n] = NodeGatherer.getDistances();
262 std::sort(bondside_set[n].begin(), bondside_set[n].end());
263 }
264
265 // re-add edge
266 BGcreator.addEdge(bondatom[0]->getId(), bondatom[1]->getId());
267
268 // add PositionUpdate for all nodes in the bondside_set
269 for (size_t n=0;n<2;++n) {
270 for (BoostGraphHelpers::Nodeset_t::const_iterator setiter = bondside_set[n].begin();
271 setiter != bondside_set[n].end(); ++setiter) {
272 const BreadthFirstSearchGatherer::distance_map_t::const_iterator diter
273 = distance_map[n].find(*setiter);
274 ASSERT( diter != distance_map[n].end(),
275 "ForceAnnealing() - could not find distance to an atom.");
276 const double factor = pow(damping_factor, diter->second);
277 LOG(3, "DEBUG: Update for atom #" << *setiter << " will be "
278 << factor << "*" << PositionUpdate);
279 if (GatheredUpdates.count((*setiter))) {
280 GatheredUpdates[(*setiter)] += factor*PositionUpdate;
281 } else {
282 GatheredUpdates.insert(
283 std::make_pair(
284 (*setiter),
285 factor*PositionUpdate) );
286 }
287 }
288 // invert for other atom
289 PositionUpdate *= -1;
290 }
291 }
292
293 // apply the gathered updates
294 for (std::map<atomId_t, Vector>::const_iterator iter = GatheredUpdates.begin();
295 iter != GatheredUpdates.end(); ++iter) {
296 const atomId_t &atomid = iter->first;
297 const Vector &update = iter->second;
298 atom* const walker = World::getInstance().getAtom(AtomById(atomid));
299 ASSERT( walker != NULL,
300 "ForceAnnealing() - walker with id "+toString(atomid)+" has suddenly disappeared.");
301 walker->setPosition( walker->getPosition() + update );
302 walker->setAtomicVelocity(update);
303 LOG(3, "DEBUG: Applying update " << update << " to atom " << *walker);
304 }
305
306 LOG(1, "STATUS: Largest remaining force components at step #"
307 << currentStep << " are " << maxComponents);
308
309 // are we in final step? Remember to reset static entities
310 if (currentStep == maxSteps) {
311 LOG(2, "DEBUG: Final step, resetting values");
312 reset();
313 }
314 }
315
316 /** Reset function to unset static entities and artificial velocities.
317 *
318 */
319 void reset()
320 {
321 currentDeltat = 0.;
322 currentStep = 0;
323
324 // reset (artifical) velocities
325 for(typename AtomSetMixin<T>::iterator iter = AtomicForceManipulator<T>::atoms.begin();
326 iter != AtomicForceManipulator<T>::atoms.end(); ++iter)
327 (*iter)->setAtomicVelocity(zeroVec);
328 }
329
330private:
331 //!> contains the current step in relation to maxsteps
332 static size_t currentStep;
333 //!> contains the maximum number of steps, determines initial and final step with currentStep
334 size_t maxSteps;
335 static double currentDeltat;
336 //!> minimum deltat for internal while loop (adaptive step width)
337 static double MinimumDeltat;
338 //!> contains the maximum bond graph distance up to which shifts of a single atom are spread
339 const int max_distance;
340 //!> the shifted is dampened by this factor with the power of the bond graph distance to the shift causing atom
341 const double damping_factor;
342};
343
344template <class T>
345double ForceAnnealing<T>::currentDeltat = 0.;
346template <class T>
347size_t ForceAnnealing<T>::currentStep = 0;
348template <class T>
349double ForceAnnealing<T>::MinimumDeltat = 1e-8;
350
351#endif /* FORCEANNEALING_HPP_ */
Note: See TracBrowser for help on using the repository browser.