SSAGES  0.1
A MetaDynamics Package
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Groups Pages
BasisFunc.cpp
1 
21 #include "BasisFunc.h"
22 #include "Validator/ObjectRequirement.h"
23 #include "Drivers/DriverException.h"
24 #include "CVs/CVManager.h"
25 #include "Snapshot.h"
26 #include <cmath>
27 #include <iostream>
28 #include <iomanip>
29 
30 using namespace Json;
31 
32 namespace SSAGES
33 {
34 
35  // Pre-simulation hook.
36  void Basis::PreSimulation(Snapshot* snapshot, const CVManager& cvmanager)
37  {
38  auto cvs = cvmanager.GetCVs(cvmask_);
39  // For print statements and file I/O, the walker IDs are used
40  mpiid_ = snapshot->GetWalkerID();
41 
42  // Make sure the iteration index is set correctly
43  iteration_ = 0;
44 
45  // There are a few error messages / checks that are in place with
46  // defining CVs and grids
47  if(hist_->GetDimension() != cvs.size())
48  {
49  std::cerr<<"::::::::::::::::::::::::::::::::::::::::::::::::::::::"<<std::endl;
50  std::cerr<<"ERROR: Histogram dimensions doesn't match number of CVS."<<std::endl;
51  std::cerr<<"Exiting on node ["<<mpiid_<<"]"<<std::endl;
52  std::cerr<<"::::::::::::::::::::::::::::::::::::::::::::::::::::::"<<std::endl;
53  MPI_Abort(world_, EXIT_FAILURE);
54  }
55  else if(cvs.size() != polyords_.size())
56  {
57  std::cout<<cvs.size()<<std::endl;
58  std::cout<<polyords_.size()<<std::endl;
59  std::cout<<"::::::::::::::::::::::::::::::::::::::::::::::::::::::"<<std::endl;
60  std::cout<<"WARNING: The number of polynomial orders is not the same"<<std::endl;
61  std::cout<<"as the number of CVs"<<std::endl;
62  std::cout<<"The simulation will take the first defined input"<<std::endl;
63  std::cout<<"as the same for all CVs. ["<<polyords_[0]<<"]"<<std::endl;
64  std::cout<<"::::::::::::::::::::::::::::::::::::::::::::::::::::::"<<std::endl;
65 
67  polyords_.resize(cvs.size());
69  for(size_t i = 0; i < cvs.size(); ++i)
70  {
71  polyords_[i] = polyords_[0];
72  }
73  }
74 
75  // This is to check for non-periodic bounds. It comes into play in the update bias function
76  bounds_ = true;
77 
78  size_t coeff_size = 1;
79 
80  for(size_t i = 0; i < cvs.size(); ++i)
81  {
82  coeff_size *= polyords_[i]+1;
83  }
84 
85  derivatives_.resize(cvs.size());
86  unbias_.resize(hist_->size(),0);
87  coeff_arr_.resize(coeff_size,0);
88 
89  std::vector<int> idx(cvs.size(), 0);
90  std::vector<int> jdx(cvs.size(), 0);
91  Map temp_map(idx,0.0);
92 
93  // Initialize the mapping for the hist function
94  for (int &val : *hist_) {
95  val = 0;
96  }
97 
98  //Initialize the mapping for the coeff function
99  for(size_t i = 0; i < coeff_size; ++i)
100  {
101  for(size_t j = 0; j < jdx.size(); ++j)
102  {
103  if(jdx[j] > 0 && jdx[j] % (polyords_[j]+1) == 0)
104  {
105  if(j != cvs.size() - 1)
106  {
107  jdx[j+1]++;
108  jdx[j] = 0;
109  }
110  }
111  temp_map.map[j] = jdx[j];
112  temp_map.value = 0;
113  }
114  coeff_.push_back(temp_map);
115  coeff_[i].value = coeff_arr_[i];
116  jdx[0]++;
117  }
118 
119  //Initialize the look-up table.
120  BasisInit(cvs);
121  }
122 
123  // Post-integration hook.
124  void Basis::PostIntegration(Snapshot* snapshot, const CVManager& cvmanager)
125  {
126  auto cvs = cvmanager.GetCVs(cvmask_);
127  std::vector<double> x(cvs.size(),0);
128 
129  /*The binned cv space is updated at every step
130  *After a certain number of steps has been passed, the system updates a
131  *bias projection based on the visited histogram states
132  */
133  for(size_t i = 0; i < cvs.size(); ++i)
134  {
135  x[i] = cvs[i]->GetValue();
136  }
137 
138  // The histogram is updated based on the index
139  hist_->at(x) += 1;
140 
141  // Update the basis projection after a predefined number of steps
142  if(snapshot->GetIteration() % cyclefreq_ == 0) {
143  double beta;
144  beta = 1.0 / (snapshot->GetTemperature() * snapshot->GetKb());
145 
146  // For systems with poorly defined temperature (ie: 1 particle) the
147  // user needs to define their own temperature. This is a hack that
148  // will be removed in future versions.
149 
150  if(snapshot->GetTemperature() == 0)
151  {
152  beta = temperature_;
153  if(temperature_ == 0)
154  {
155  std::cout<<std::endl;
156  std::cerr<<"::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"<<std::endl;
157  std::cerr<<"ERROR: Input temperature needs to be defined for this simulation"<<std::endl;
158  std::cerr<<"Exiting on node ["<<mpiid_<<"]"<<std::endl;
159  std::cout<<"::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"<<std::endl;
160  exit(EXIT_FAILURE);
161  }
162  }
163  iteration_+= 1;
164  UpdateBias(cvs,beta);
165  std::cout<<"Node: ["<<mpiid_<<"]"<<std::setw(10)<<"\tSweep: "<<iteration_<<std::endl;
166  }
167 
168  // This calculates the bias force based on the existing basis projection.
169  CalcBiasForce(cvs);
170 
171  // Take each CV and add its biased forces to the atoms using the chain rule
172  auto& forces = snapshot->GetForces();
173  auto& virial = snapshot->GetVirial();
174  for(size_t i = 0; i < cvs.size(); ++i)
175  {
176  auto& grad = cvs[i]->GetGradient();
177  auto& boxgrad = cvs[i]->GetBoxGradient();
178 
179  /* Update the forces in snapshot by adding in the force bias from each
180  *CV to each atom based on the gradient of the CV.
181  */
182  for (size_t j = 0; j < forces.size(); ++j)
183  forces[j] += derivatives_[i]*grad[j];
184 
185  virial -= derivatives_[i]*boxgrad;
186  }
187  }
188 
189  // Post-simulation hook.
190  void Basis::PostSimulation(Snapshot*, const CVManager&)
191  {
192  std::cout<<"Run has finished"<<std::endl;
193  }
194 
195  /* The basis set is initialized through the recursive definition.
196  *Currently, SSAGES only supports Legendre polyonmials for basis projections
197  */
198  void Basis::BasisInit(const CVList& cvs)
199  {
200  for( size_t k = 0; k < cvs.size(); k++)
201  {
202  size_t ncoeff = polyords_[k]+1;
203  int nbins = hist_->GetNumPoints(k);
204 
205  std::vector<double> dervs(nbins*ncoeff,0);
206  std::vector<double> vals(nbins*ncoeff,0);
207  std::vector<double> x(nbins,0);
208 
209  /*As the values for Legendre polynomials can be defined recursively, \
210  *both the derivatives and values are defined at the same time,
211  */
212  for (int i = 0; i < nbins; ++i)
213  {
214  x[i] = (2.0*i + 1.0)/nbins - 1.0;
215  vals[i] = 1.0;
216  dervs[i] = 0.0;
217  }
218 
219  for (int i = 0; i < nbins; ++i)
220  {
221  vals[i+nbins] = x[i];
222  dervs[i+nbins] = 1.0;
223  }
224 
225  for (size_t j = 2; j < ncoeff; j++)
226  {
227  for (int i = 0; i < nbins; i++)
228  {
229  //Evaluate the values of the Legendre polynomial at each bin
230  vals[i+j*nbins] = ( ( 2.0*j - 1.0 ) * x[i] * vals[i+(j-1)*nbins]
231  - (j - 1.0) * vals[i+(j-2)*nbins] ) / j;
232 
233  //Evaluate the derivatives of the Legendre polynomial at each bin
234  dervs[i+j*nbins] = ( ( 2.0*j - 1.0 ) * ( vals[i+(j-1)*nbins] + x[i] * dervs[i+(j-1)*nbins] )
235  - (j - 1.0) * dervs[i+(j-2)*nbins] ) / j;
236  }
237  }
238  BasisLUT TempLUT(vals,dervs);
239  LUT_.push_back(TempLUT);
240  }
241  }
242 
243  // Update the coefficients/bias projection
244  void Basis::UpdateBias(const CVList& cvs, const double beta)
245  {
246  std::vector<double> x(cvs.size(), 0);
247  std::vector<double> coeffTemp(coeff_.size(), 0);
248  double sum = 0.0;
249  double bias = 0.0;
250  double basis = 1.0;
251 
252  // For multiple walkers, the struct is unpacked
253  Histogram<int> histlocal(*hist_);
254 
255  // Summed between all walkers
256  MPI_Allreduce(histlocal.data(), hist_->data(), hist_->size(), MPI_INT, MPI_SUM, world_);
257 
258  // Construct the biased histogram
259  size_t i = 0;
260  for (Histogram<int>::iterator it2 = hist_->begin(); it2 != hist_->end(); ++it2, ++i)
261  {
262  if (it2.isUnderOverflowBin()) {
263  --i;
264  continue;
265  }
266 
267  // This is to make sure that the CV projects across the entire surface
268  if (*it2 == 0) { *it2 = 1; }
269 
270  // The loop builds the previous basis projection for each bin of the histogram
271  for(size_t k = 1; k < coeff_.size(); ++k)
272  {
273  auto& coeff = coeff_[k];
274  for(size_t l = 0; l < cvs.size(); ++l)
275  {
276  // The previous bias is only calculated after each sweep has happened
277  int nbins = hist_->GetNumPoints(l);
278  basis *= LUT_[l].values[it2.index(l) + coeff.map[l]*nbins];
279  }
280  bias += coeff.value*basis;
281  basis = 1.0;
282  }
283 
284  /* The evaluation of the biased histogram which projects the histogram to the
285  * current bias of CV space.
286  */
287  unbias_[i] += (*it2) * exp(bias) * weight_ / (double)(cyclefreq_);
288  bias = 0.0;
289 
290  }
291 
292  // The coefficients and histograms are reset after evaluating the biased histogram values
293  for(size_t i = 0; i < coeff_.size(); ++i)
294  {
295  coeffTemp[i] = coeff_[i].value;
296  coeff_[i].value = 0.0;
297  }
298 
299  // Reset histogram
300  for (int &val : *hist_) {
301  val = 0;
302  }
303 
304  // The loop that evaluates the new coefficients by integrating the CV space
305  for(size_t i = 1; i < coeff_.size(); ++i)
306  {
307  auto& coeff = coeff_[i];
308 
309  // The method uses a standard integration with trap rule weights
310  size_t j = 0;
311  for(Histogram<int>::iterator it2 = hist_->begin(); it2 != hist_->end(); ++it2, ++j)
312  {
313  if (it2.isUnderOverflowBin()) {
314  --j;
315  continue;
316  }
317 
318  double weight = std::pow(2.0,cvs.size());
319 
320  // This adds in a trap-rule type weighting which lowers error significantly at the boundaries
321  for(size_t k = 0; k < cvs.size(); ++k)
322  {
323  if( it2.index(k) == 0 ||
324  it2.index(k) == hist_->GetNumPoints(k)-1)
325  weight /= 2.0;
326  }
327 
328  /*The numerical integration of the biased histogram across the entirety of CV space
329  *All calculations include the normalization as well
330  */
331  for(size_t l = 0; l < cvs.size(); l++)
332  {
333  int nbins = hist_->GetNumPoints(l);
334  basis *= LUT_[l].values[it2.index(l) + coeff.map[l]*nbins] / nbins;
335  basis *= 2.0 * coeff.map[l] + 1.0;
336  }
337  coeff.value += basis * log(unbias_[j]) * weight/std::pow(2.0,cvs.size());
338  basis = 1.0;
339  }
340  coeffTemp[i] -= coeff.value;
341  coeff_arr_[i] = coeff.value;
342  sum += coeffTemp[i]*coeffTemp[i];
343  }
344 
345  if(world_.rank() == 0)
346  // Write coeff at this step, but only one walker
347  PrintBias(cvs,beta);
348 
349  // The convergence tolerance and whether the user wants to exit are incorporated here
350  if(sum < tol_)
351  {
352  std::cout<<"System has converged"<<std::endl;
353  if(converge_exit_)
354  {
355  std::cout<<"User has elected to exit. System is now exiting"<<std::endl;
356  exit(EXIT_SUCCESS);
357  }
358  }
359  }
360 
361  /*The coefficients are printed out for the purpose of saving the free energy space
362  *Additionally, the current basis projection is printed so that the user can view
363  *the current free energy space
364  */
365  void Basis::PrintBias(const CVList& cvs, const double beta)
366  {
367  std::vector<double> bias(hist_->size(), 0);
368  std::vector<double> x(cvs.size(), 0);
369  double temp = 1.0;
370 
371  /* Since the coefficients are the only piece that needs to be
372  *updated, the bias is only evaluated when printing
373  */
374  size_t i = 0;
375  for(Histogram<int>::iterator it = hist_->begin(); it != hist_->end(); ++it, ++i)
376  {
377  if (it.isUnderOverflowBin()) {
378  --i;
379  continue;
380  }
381 
382  for(size_t j = 1; j < coeff_.size(); ++j)
383  {
384  for(size_t k = 0; k < cvs.size(); ++k)
385  {
386  int nbins = hist_->GetNumPoints(k);
387  temp *= LUT_[k].values[it.index(k) + coeff_[j].map[k] * nbins];
388  }
389  bias[i] += coeff_[j].value*temp;
390  temp = 1.0;
391  }
392  }
393 
394  // The filenames will have a standard name, with a user-defined suffix
395  std::string filename1 = "basis"+bnme_+".out";
396  std::string filename2 = "coeff"+cnme_+".out";
397 
398  basisout_.precision(5);
399  coeffout_.precision(5);
400  basisout_.open(filename1.c_str());
401  coeffout_.open(filename2.c_str());
402 
403  // The CV values, PMF projection, PMF, and biased histogram are output for the user
404  coeffout_ << iteration_ <<std::endl;
405  basisout_ << "CV Values" << std::setw(35*cvs.size()) << "Basis Set Bias" << std::setw(35) << "PMF Estimate" << std::setw(35) << "Biased Histogram" << std::endl;
406 
407  size_t j = 0;
408  for(Histogram<int>::iterator it = hist_->begin(); it != hist_->end(); ++it, ++j)
409  {
410  if (it.isUnderOverflowBin()) {
411  --j;
412  continue;
413  }
414 
415  for(size_t k = 0; k < cvs.size(); ++k)
416  {
417  // Evaluate the CV values for printing purposes
418  basisout_ << it.coordinate(k) << std::setw(35);
419  }
420  basisout_ << -bias[j] << std::setw(35);
421  if(unbias_[j])
422  basisout_ << -log(unbias_[j]) / beta << std::setw(35);
423  else
424  basisout_ << "0" << std::setw(35);
425  basisout_ << unbias_[j];
426  basisout_ << std::endl;
427  }
428 
429  for(size_t k = 0; k < coeff_.size(); ++k)
430  {
431  coeffout_ <<coeff_[k].value << std::endl;
432  }
433 
434  basisout_ << std::endl;
435  basisout_.close();
436  coeffout_.close();
437  }
438 
439  // The forces are calculated by chain rule, first the derivatives of the basis set, then in the PostIntegration function, the derivative of the CV is evaluated
440  void Basis::CalcBiasForce(const CVList& cvs)
441  {
442  // Reset derivatives
443  std::fill(derivatives_.begin(), derivatives_.end(), 0);
444  std::vector<double> x(cvs.size(),0);
445 
446  double temp = 1.0;
447 
448  //This is calculating the derivatives for the bias force
449  for (size_t j = 0; j < cvs.size(); ++j)
450  {
451  x[j] = cvs[j]->GetValue();
452  double min = hist_->GetLower(j);
453  double max = hist_->GetUpper(j);
454 
455  if(!hist_->GetPeriodic(j))
456  {
457  // In order to prevent the index for the histogram from going out of bounds a check is in place
458  if(x[j] > max && bounds_)
459  {
460  std::cout<<"::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"<<std::endl;
461  std::cout<<"WARNING: CV is above the maximum boundary."<<std::endl;
462  std::cout<<"Statistics will not be gathered during this interval"<<std::endl;
463  std::cout<<"::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"<<std::endl;
464  bounds_ = false;
465  }
466  else if(x[j] < min && bounds_)
467  {
468  std::cout<<"::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"<<std::endl;
469  std::cout<<"WARNING: CV is below the minimum boundary."<<std::endl;
470  std::cout<<"Statistics will not be gathered during this interval"<<std::endl;
471  std::cout<<"::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"<<std::endl;
472  bounds_ = false;
473  }
474  else if(x[j] < max && x[j] > min && !bounds_)
475  {
476  std::cout<<"::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"<<std::endl;
477  std::cout<<"CV has returned in between bounds. Run is resuming"<<std::endl;
478  std::cout<<"::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::"<<std::endl;
479  bounds_ = true;
480  }
481  }
482  }
483 
484  // Only apply soft wall potential in the event that it has left the boundaries
485  if(bounds_)
486  {
487  for (size_t i = 1; i < coeff_.size(); ++i)
488  {
489  for (size_t j = 0; j < cvs.size(); ++j)
490  {
491  temp = 1.0;
492  for (size_t k = 0; k < cvs.size(); ++k)
493  {
494  int nbins = hist_->GetNumPoints(k);
495  temp *= j == k ? LUT_[k].derivs[hist_->GetIndices(x)[k] + coeff_[i].map[k]*nbins] * 2.0 / (hist_->GetUpper(j) - hist_->GetLower(j))
496  : LUT_[k].values[hist_->GetIndices(x)[k] + coeff_[i].map[k]*nbins];
497  }
498  derivatives_[j] -= coeff_[i].value * temp;
499  }
500  }
501  }
502 
503  // This is where the wall potentials are going to be thrown into the method if the system is not a periodic CV
504  for(size_t j = 0; j < cvs.size(); ++j)
505  {
506  // Are these used?
507  // double min = hist_->GetLower(j);
508  // double max = hist_->GetUpper(j);
509 
510  if(!hist_->GetPeriodic(j))
511  {
512  if(x[j] > boundUp_[j])
513  derivatives_[j] -= restraint_[j] * (x[j] - boundUp_[j]);
514  else if(x[j] < boundLow_[j])
515  derivatives_[j] -= restraint_[j] * (x[j] - boundLow_[j]);
516  }
517  }
518  }
519 
521  Basis* Basis::Build(const Json::Value& json,
522  const MPI_Comm& world,
523  const MPI_Comm& comm,
524  const std::string& path)
525  {
526  ObjectRequirement validator;
527  Value schema;
528  Reader reader;
529 
530  reader.parse(JsonSchema::BFSMethod, schema);
531  validator.Parse(schema, path);
532 
533  //Validate Inputs
534  validator.Validate(json, path);
535  if(validator.HasErrors())
536  throw BuildException(validator.GetErrors());
537 
538  std::vector<unsigned int> coefsCV(0);
539  for(auto& coefs : json["CV_coefficients"])
540  coefsCV.push_back(coefs.asInt());
541 
542  std::vector<double> restrCV(0);
543  for(auto& restr : json["CV_restraint_spring_constants"])
544  restrCV.push_back(restr.asDouble());
545 
546  std::vector<double> boundLow(0);
547  for(auto& bndl : json["CV_restraint_minimums"])
548  boundLow.push_back(bndl.asDouble());
549 
550  std::vector<double> boundUp(0);
551  for(auto& bndu : json["CV_restraint_maximums"])
552  boundUp.push_back(bndu.asDouble());
553 
554  auto cyclefreq = json.get("cycle_frequency", 100000).asInt();
555  auto freq = json.get("frequency", 1).asInt();
556  auto wght = json.get("weight", 1.0).asDouble();
557  auto bnme = json.get("basis_filename", "").asString();
558  auto cnme = json.get("coeff_filename", "").asString();
559  auto temp = json.get("temperature", 0.0).asDouble();
560  auto tol = json.get("tolerance", 1e-6).asDouble();
561  auto conv = json.get("convergence_exit", false).asBool();
562 
564  json.get("grid", Json::Value()) );
565 
566  auto* m = new Basis(world, comm, hist, coefsCV, restrCV, boundUp, boundLow,
567  cyclefreq, freq, bnme, cnme, temp, tol, wght,
568  conv);
569 
570  if(json.isMember("iteration"))
571  m->SetIteration(json.get("iteration",0).asInt());
572 
573  if(json.isMember("coefficients") && json.isMember("bias hist"))
574  {
575  std::vector<double> coeff;
576  std::vector<double> unbias;
577 
578  for(auto& c : json["coefficients"])
579  coeff.push_back(c.asDouble());
580 
581  for(auto& u : json["bias hist"])
582  unbias.push_back(u.asDouble());
583 
584  m->SetBasis(coeff, unbias);
585  }
586 
587  return m;
588  }
589 }
unsigned GetWalkerID() const
Get walker ID.
Definition: Snapshot.h:193
bool HasErrors()
Check if errors have occured.
Definition: Requirement.h:86
Collective variable manager.
Definition: CVManager.h:40
int GetIteration() const
Get the current iteration.
Definition: Snapshot.h:103
const Matrix3 & GetVirial() const
Get box virial.
Definition: Snapshot.h:133
std::vector< CollectiveVariable * > CVList
List of Collective Variables.
Definition: types.h:51
Class containing a snapshot of the current simulation in time.
Definition: Snapshot.h:43
Basis Function Sampling Algorithm.
Definition: BasisFunc.h:89
virtual void Parse(Value json, const std::string &path) override
Parse JSON value to generate Requirement(s).
Map for histogram and coefficients.
Definition: BasisFunc.h:37
Exception to be thrown when building the Driver fails.
std::vector< std::string > GetErrors()
Get list of error messages.
Definition: Requirement.h:92
double GetTemperature() const
Get current temperature.
Definition: Snapshot.h:115
std::vector< CollectiveVariable * > GetCVs(const std::vector< uint > &mask=std::vector< uint >()) const
Get CV iterator.
Definition: CVManager.h:80
Look-up table for basis functions.
Definition: BasisFunc.h:63
Requirements on an object.
double GetKb() const
Get system Kb.
Definition: Snapshot.h:163
iterator begin()
Return iterator at first bin of histogram.
Definition: Histogram.h:549
T * data()
Get pointer to the internal data storage vector.
Definition: GridBase.h:262
const std::vector< Vector3 > & GetForces() const
Access the per-particle forces.
Definition: Snapshot.h:362
virtual void Validate(const Value &json, const std::string &path) override
Validate JSON value.