Changeset e77580 for src/Dynamics


Ignore:
Timestamp:
Apr 10, 2018, 6:43:29 AM (7 years ago)
Author:
Frederik Heber <frederik.heber@…>
Branches:
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
Children:
355915
Parents:
d3e1d5
git-author:
Frederik Heber <frederik.heber@…> (05/30/17 20:31:38)
git-committer:
Frederik Heber <frederik.heber@…> (04/10/18 06:43:29)
Message:

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.
File:
1 edited

Legend:

Unmodified
Added
Removed
  • src/Dynamics/ForceAnnealing.hpp

    rd3e1d5 re77580  
    2828#include "Helpers/helpers.hpp"
    2929#include "Helpers/defs.hpp"
     30#include "LinearAlgebra/LinearSystemOfEquations.hpp"
     31#include "LinearAlgebra/MatrixContent.hpp"
    3032#include "LinearAlgebra/Vector.hpp"
     33#include "LinearAlgebra/VectorContent.hpp"
    3134#include "Thermostats/ThermoStatContainer.hpp"
    3235#include "Thermostats/Thermostat.hpp"
     
    232235    BreadthFirstSearchGatherer NodeGatherer(BGcreator);
    233236
    234     std::map<atomId_t, Vector> GatheredUpdates; //!< gathers all updates which are applied at the end
     237    /// We assume that a force is local, i.e. a bond is too short yet and hence
     238    /// the atom needs to be moved. However, all the adjacent (bound) atoms might
     239    /// already be at the perfect distance. If we just move the atom alone, we ruin
     240    /// all the other bonds. Hence, it would be sensible to move every atom found
     241    /// through the bond graph in the direction of the force as well by the same
     242    /// PositionUpdate. This is almost what we are going to do.
     243
     244    /// One more issue is: If we need to shorten bond, then we use the PositionUpdate
     245    /// also on the the other bond partner already. This is because it is in the
     246    /// direction of the bond. Therefore, the update is actually performed twice on
     247    /// each bond partner, i.e. the step size is twice as large as it should be.
     248    /// This problem only occurs when bonds need to be shortened, not when they
     249    /// need to be made longer (then the force vector is facing the other
     250    /// direction than the bond vector).
     251    /// As a remedy we need to know the forces "per bond" and not per atom.
     252    /// In effect, the gradient is the error per atom. However, we need an
     253    /// error per bond. To this end, we set up a matrix A that translate the
     254    /// vector of bond energies into a vector of atomic force component and
     255    /// then we simply solve the linear system (inverse problem) via an SVD
     256    /// and use the bond gradients to get the PositionUpdate for both bond
     257    /// partners at the same time when we go through all bonds.
     258
     259    // gather/enumerate all bonds
     260    std::set<bond::ptr> allbonds;
     261    std::vector<atomId_t> allatomids;
    235262    for(typename AtomSetMixin<T>::iterator iter = AtomicForceManipulator<T>::atoms.begin();
    236263        iter != AtomicForceManipulator<T>::atoms.end(); ++iter) {
    237       // atom's force vector gives steepest descent direction
    238       const Vector oldPosition = (*iter)->getPositionAtStep(CurrentTimeStep-1 >= 0 ? CurrentTimeStep - 1 : 0);
    239       const Vector currentPosition = (*iter)->getPositionAtStep(CurrentTimeStep);
    240       const Vector oldGradient = (*iter)->getAtomicForceAtStep(CurrentTimeStep-1 >= 0 ? CurrentTimeStep - 1 : 0);
    241       const Vector currentGradient = (*iter)->getAtomicForceAtStep(CurrentTimeStep);
    242       LOG(4, "DEBUG: Force for atom " << **iter << " is " << currentGradient);
    243 
    244       // we use Barzilai-Borwein update with position reversed to get descent
    245       const double stepwidth = getBarzilaiBorweinStepwidth(
    246           currentPosition - oldPosition, currentGradient - oldGradient);
    247       const Vector PositionUpdate = stepwidth * currentGradient;
    248       LOG(3, "DEBUG: Update would be " << stepwidth << "*" << currentGradient << " = " << PositionUpdate);
    249 
    250 //      // add update to central atom
    251 //      const atomId_t atomid = (*iter)->getId();
    252 //      if (GatheredUpdates.count(atomid)) {
    253 //        GatheredUpdates[atomid] += PositionUpdate;
    254 //      } else
    255 //        GatheredUpdates.insert( std::make_pair(atomid, PositionUpdate) );
    256 
    257       // We assume that a force is local, i.e. a bond is too short yet and hence
    258       // the atom needs to be moved. However, all the adjacent (bound) atoms might
    259       // already be at the perfect distance. If we just move the atom alone, we ruin
    260       // all the other bonds. Hence, it would be sensible to move every atom found
    261       // through the bond graph in the direction of the force as well by the same
    262       // PositionUpdate. This is just what we are going to do.
    263 
    264       /// get all nodes from bonds in the direction of the current force
    265 
    266       // remove edges facing in the wrong direction
    267       std::vector<bond::ptr> removed_bonds;
    268       const BondList& ListOfBonds = (*iter)->getListOfBonds();
     264      const atom &walker = *(*iter);
     265      allatomids.push_back(walker.getId());
     266      const BondList& ListOfBonds = walker.getListOfBonds();
    269267      for(BondList::const_iterator bonditer = ListOfBonds.begin();
    270268          bonditer != ListOfBonds.end(); ++bonditer) {
    271         const bond &current_bond = *(*bonditer);
    272         LOG(2, "DEBUG: Looking at bond " << current_bond);
    273         Vector BondVector = (*iter)->getPositionAtStep(CurrentTimeStep);
    274         BondVector -= ((*iter)->getId() == current_bond.rightatom->getId())
    275             ? current_bond.rightatom->getPositionAtStep(CurrentTimeStep) : current_bond.leftatom->getPositionAtStep(CurrentTimeStep);
    276         BondVector.Normalize();
    277         if (BondVector.ScalarProduct(currentGradient) < 0) {
    278           removed_bonds.push_back(*bonditer);
    279 #ifndef NDEBUG
    280           const bool status =
    281 #endif
    282               BGcreator.removeEdge(current_bond.leftatom->getId(), current_bond.rightatom->getId());
    283           ASSERT( status, "ForceAnnealing() - edge to found bond is not present?");
    284         }
    285       }
    286       BoostGraphHelpers::Nodeset_t bondside_set = NodeGatherer((*iter)->getId(), max_distance);
    287       const BreadthFirstSearchGatherer::distance_map_t &distance_map = NodeGatherer.getDistances();
    288       std::sort(bondside_set.begin(), bondside_set.end());
    289       // re-add those edges
    290       for (std::vector<bond::ptr>::const_iterator bonditer = removed_bonds.begin();
    291           bonditer != removed_bonds.end(); ++bonditer)
    292         BGcreator.addEdge((*bonditer)->leftatom->getId(), (*bonditer)->rightatom->getId());
    293 
    294       // apply PositionUpdate to all nodes in the bondside_set
    295       for (BoostGraphHelpers::Nodeset_t::const_iterator setiter = bondside_set.begin();
    296           setiter != bondside_set.end(); ++setiter) {
    297         const BreadthFirstSearchGatherer::distance_map_t::const_iterator diter
    298           = distance_map.find(*setiter);
    299         ASSERT( diter != distance_map.end(),
    300             "ForceAnnealing() - could not find distance to an atom.");
    301         const double factor = pow(damping_factor, diter->second);
    302         LOG(3, "DEBUG: Update for atom #" << *setiter << " will be "
    303             << factor << "*" << PositionUpdate);
    304         if (GatheredUpdates.count((*setiter))) {
    305           GatheredUpdates[(*setiter)] += factor*PositionUpdate;
    306         } else {
    307           GatheredUpdates.insert(
    308               std::make_pair(
    309                   (*setiter),
    310                   factor*PositionUpdate) );
    311         }
     269        const bond::ptr &current_bond = *bonditer;
     270        allbonds.insert(current_bond);
    312271      }
    313272
    314273      // extract largest components for showing progress of annealing
     274      const Vector currentGradient = (*iter)->getAtomicForce();
    315275      for(size_t i=0;i<NDIM;++i)
    316276        maxComponents[i] = std::max(maxComponents[i], fabs(currentGradient[i]));
    317     }
     277
     278      // reset force vector for next step except on final one
     279      if (currentStep != maxSteps)
     280        (*iter)->setAtomicForce(zeroVec);
     281    }
     282    std::sort(allatomids.begin(), allatomids.end());
     283    // converting set back to vector is fastest when requiring sorted and unique,
     284    // see https://stackoverflow.com/questions/1041620/whats-the-most-efficient-way-to-erase-duplicates-and-sort-a-vector
     285    typedef std::vector<bond::ptr> bondvector_t;
     286    bondvector_t bondvector( allbonds.begin(), allbonds.end() );
     287
     288    // setup matrix A given the enumerated bonds
     289    LinearSystemOfEquations lseq(AtomicForceManipulator<T>::atoms.size(), bondvector.size());
     290    for (size_t i = 0;i<bondvector.size();++i) {
     291      const atom* bondatom[2] = {
     292           bondvector[i]->leftatom,
     293           bondvector[i]->rightatom
     294      };
     295      size_t index[2];
     296      for (size_t n=0;n<2;++n) {
     297        const std::pair<std::vector<atomId_t>::iterator, std::vector<atomId_t>::iterator> atomiditer =
     298            std::equal_range(allatomids.begin(), allatomids.end(), bondatom[n]->getId());
     299        index[n] = std::distance(allatomids.begin(), atomiditer.first);
     300        (*lseq.A).at(index[0],index[1]) = 1.;
     301        (*lseq.A).at(index[1],index[0]) = 1.;
     302      }
     303    }
     304    lseq.SetSymmetric(true);
     305
     306    // for each component and for current and last time step
     307    // solve Ax=y with given A and y being the vectorized atomic force
     308    double *tmpforces = new double[bondvector.size()];
     309    double *bondforces = new double[bondvector.size()];
     310    double *oldbondforces = new double[bondvector.size()];
     311    double *bondforceref[2] = {
     312        bondforces,
     313        oldbondforces
     314    };
     315    for (size_t n=0;n<bondvector.size();++n) {
     316      bondforces[n] = 0.;
     317      oldbondforces[n] = 0.;
     318    }
     319    for (size_t timestep = 0; timestep <= 1; ++timestep) {
     320      for (size_t component = 0; component < NDIM; ++component) {
     321        for(typename AtomSetMixin<T>::iterator iter = AtomicForceManipulator<T>::atoms.begin();
     322            iter != AtomicForceManipulator<T>::atoms.end(); ++iter) {
     323          const atom &walker = *(*iter);
     324          const std::pair<std::vector<atomId_t>::iterator, std::vector<atomId_t>::iterator> atomiditer =
     325              std::equal_range(allatomids.begin(), allatomids.end(), walker.getId());
     326          const size_t i = std::distance(allatomids.begin(), atomiditer.first);
     327        (*lseq.b).at(i) = timestep == 0 ?
     328            walker.getAtomicForce()[component] :
     329            walker.getAtomicForceAtStep(CurrentTimeStep-2 >= 0 ? CurrentTimeStep - 2 : 0)[component];
     330      }
     331      lseq.GetSolutionAsArray(tmpforces);
     332      for (size_t i = 0;i<bondvector.size();++i)
     333        bondforceref[timestep][i] += tmpforces[i];
     334      }
     335    }
     336
     337    // step through each bond and shift the atoms
     338    std::map<atomId_t, Vector> GatheredUpdates; //!< gathers all updates which are applied at the end
     339    for (size_t i = 0;i<bondvector.size();++i) {
     340      const atom* bondatom[2] = {
     341           bondvector[i]->leftatom,
     342           bondvector[i]->rightatom};
     343      const double &bondforce = bondforces[i];
     344      const double &oldbondforce = oldbondforces[i];
     345      const double bondforcedifference = (bondforce - oldbondforce);
     346      Vector BondVector = (bondatom[0]->getPosition() - bondatom[1]->getPosition());
     347      BondVector.Normalize();
     348      double stepwidth = 0.;
     349      for (size_t n=0;n<2;++n) {
     350        const Vector oldPosition = bondatom[n]->getPositionAtStep(CurrentTimeStep-2 >= 0 ? CurrentTimeStep - 2 : 0);
     351        const Vector currentPosition = bondatom[n]->getPosition();
     352        stepwidth += fabs((currentPosition - oldPosition).ScalarProduct(BondVector))/bondforcedifference;
     353      }
     354      stepwidth = stepwidth/2;
     355      Vector PositionUpdate = stepwidth * BondVector;
     356      if (fabs(stepwidth) < 1e-10) {
     357        // dont' warn in first step, deltat usage normal
     358        if (currentStep != 1)
     359          ELOG(1, "INFO: Barzilai-Borwein stepwidth is zero, using deltat " << currentDeltat << " instead.");
     360        PositionUpdate = currentDeltat * BondVector;
     361      }
     362      LOG(3, "DEBUG: Update would be " << PositionUpdate);
     363
     364      // remove the edge
     365#ifndef NDEBUG
     366      const bool status =
     367#endif
     368          BGcreator.removeEdge(bondatom[0]->getId(), bondatom[1]->getId());
     369      ASSERT( status, "ForceAnnealing() - edge to found bond is not present?");
     370
     371      // gather nodes for either atom
     372      BoostGraphHelpers::Nodeset_t bondside_set[2];
     373      BreadthFirstSearchGatherer::distance_map_t distance_map[2];
     374      for (size_t n=0;n<2;++n) {
     375        bondside_set[n] = NodeGatherer(bondatom[n]->getId(), max_distance);
     376        distance_map[n] = NodeGatherer.getDistances();
     377        std::sort(bondside_set[n].begin(), bondside_set[n].end());
     378      }
     379
     380      // re-add edge
     381      BGcreator.addEdge(bondatom[0]->getId(), bondatom[1]->getId());
     382
     383      // add PositionUpdate for all nodes in the bondside_set
     384      for (size_t n=0;n<2;++n) {
     385        for (BoostGraphHelpers::Nodeset_t::const_iterator setiter = bondside_set[n].begin();
     386            setiter != bondside_set[n].end(); ++setiter) {
     387          const BreadthFirstSearchGatherer::distance_map_t::const_iterator diter
     388            = distance_map[n].find(*setiter);
     389          ASSERT( diter != distance_map[n].end(),
     390              "ForceAnnealing() - could not find distance to an atom.");
     391          const double factor = pow(damping_factor, diter->second);
     392          LOG(3, "DEBUG: Update for atom #" << *setiter << " will be "
     393              << factor << "*" << PositionUpdate);
     394          if (GatheredUpdates.count((*setiter))) {
     395            GatheredUpdates[(*setiter)] += factor*PositionUpdate;
     396          } else {
     397            GatheredUpdates.insert(
     398                std::make_pair(
     399                    (*setiter),
     400                    factor*PositionUpdate) );
     401          }
     402        }
     403        // invert for other atom
     404        PositionUpdate *= -1;
     405      }
     406    }
     407
    318408    // apply the gathered updates
    319409    for (std::map<atomId_t, Vector>::const_iterator iter = GatheredUpdates.begin();
Note: See TracChangeset for help on using the changeset viewer.