1 /*
2  * Copyright 2009-2021 The VOTCA Development Team (http://www.votca.org)
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  *
16  */
17 
18 // Standard includes
19 #include <cmath>
20 #include <cstdio>
21 #include <fstream>
22 #include <iostream>
23 #include <memory>
24 #include <sstream>
25 
26 // VOTCA includes
27 #include <votca/tools/cubicspline.h>
28 #include <votca/tools/linalg.h>
29 #include <votca/tools/table.h>
30 
31 // Local VOTCA includes
32 #include "votca/csg/beadlist.h"
33 #include "votca/csg/nblistgrid.h"
34 #include "votca/csg/nblistgrid_3body.h"
35 
36 // Local private includes
37 #include "csg_fmatch.h"
38 
39 int main(int argc, char **argv) {
40   CGForceMatching app;
41   return app.Exec(argc, argv);
42 }
43 
44 void CGForceMatching::Initialize(void) {
45   CsgApplication::Initialize();
46   AddProgramOptions()("options", boost::program_options::value<string>(),
47                       "  options file for coarse graining")(
48       "trj-force", boost::program_options::value<string>(),
49       "  coarse-grained trajectory containing forces of already known "
50       "interactions");
51 }
52 
53 bool CGForceMatching::EvaluateOptions() {
54   CsgApplication::EvaluateOptions();
55   CheckRequired("trj", "no trajectory file specified");
56   CheckRequired("options", "need to specify options file");
57   LoadOptions(OptionsMap()["options"].as<string>());
58 
59   has_existing_forces_ = false;
60   if (OptionsMap().count("trj-force")) {
61     has_existing_forces_ = true;
62   }
63   return true;
64 }
65 
66 void CGForceMatching::BeginEvaluate(Topology *top, Topology *) {
67   // set counters to zero value:
68   nblocks_ = 0;
69   line_cntr_ = col_cntr_ = 0;
70 
71   // Number of CG beads in topology
72   nbeads_ = top->BeadCount();
73   // Set frame counter to zero
74   frame_counter_ = 0;
75 
76   // accuracy for evaluating the difference in bead positions (default 1e-5)
77   dist_ = 1e-5;
78   if (options_.exists("cg.fmatch.dist")) {
79     dist_ = options_.get("cg.fmatch.dist").as<double>();
80   }
81 
82   // read  nframes_ from input file
83   nframes_ = options_.get("cg.fmatch.frames_per_block").as<votca::Index>();
84   // read  constr_least_sq_ from input file
85   constr_least_sq_ = options_.get("cg.fmatch.constrainedLS").as<bool>();
86 
87   // initializing bonded interactions
88   for (votca::tools::Property *prop : bonded_) {
89     // add spline to container
90     splines_.emplace_back(splines_.size(), true, col_cntr_, prop);
91     // adjust initial Eigen::Matrix3d dimensions:
92     line_cntr_ += splines_.back().num_gridpoints;
93     col_cntr_ += 2 * splines_.back().num_gridpoints;
94     // if periodic potential, one additional constraint has to be taken into
95     // account -> 1 additional line in matrix
96     if (splines_.back().periodic != 0) {
97       line_cntr_ += 1;
98     }
99   }
100 
101   // initializing non-bonded interactions
102   for (votca::tools::Property *prop : nonbonded_) {
103     // add spline to container
104     splines_.emplace_back(splines_.size(), false, col_cntr_, prop);
105     // adjust initial Eigen::Matrix3d dimensions:
106     // number of constraints/restraints
107     line_cntr_ += splines_.back().num_gridpoints;
108     // number of coefficients
109     col_cntr_ += 2 * splines_.back().num_gridpoints;
110 
111     // preliminary: use also spline functions for the threebody interaction. So
112     // far only angular interaction implemented
113   }
114 
115   cout << "\nYou are using VOTCA!\n";
116   cout << "\nhey, somebody wants to forcematch!\n";
117 
118   // now initialize  A_,  b_,  x_ and probably  B_constr_
119   // depending on least-squares algorithm used
120   if (constr_least_sq_) {  // Constrained Least Squares
121 
122     cout << "\nUsing constrained Least Squares!\n " << endl;
123 
124     // assign  least_sq_offset_
125     least_sq_offset_ = 0;
126 
127     // resize and clear  B_constr_
128     B_constr_ = Eigen::MatrixXd::Zero(line_cntr_, col_cntr_);
129 
130     // resize Eigen::Matrix3d  A_
131     A_ = Eigen::MatrixXd::Zero(3 * nbeads_ * nframes_, col_cntr_);
132     // resize vector  b_
133     b_ = Eigen::VectorXd::Zero(3 * nbeads_ * nframes_);
134 
135     // in case of constrained least squares smoothing conditions
136     // are assigned to Eigen::Matrix3d  B_constr_
137     FmatchAssignSmoothCondsToMatrix(B_constr_);
138   } else {  // Simple Least Squares
139 
140     cout << "\nUsing simple Least Squares! " << endl;
141     // assign  least_sq_offset_
142     least_sq_offset_ = line_cntr_;
143 
144     // resize Eigen::Matrix3d  A_
145     A_ = Eigen::MatrixXd::Zero(line_cntr_ + 3 * nbeads_ * nframes_, col_cntr_);
146     // resize vector  b_
147     b_ = Eigen::VectorXd::Zero(line_cntr_ + 3 * nbeads_ * nframes_);
148 
149     // in case of simple least squares smoothing conditions
150     // are assigned to Eigen::Matrix3d  A_
151     FmatchAssignSmoothCondsToMatrix(A_);
152     // clear  b_ (only necessary in simple least squares)
153     b_.setZero();
154   }
155   // resize and clear  x_
156   x_ = Eigen::VectorXd::Zero(col_cntr_);
157 
158   if (has_existing_forces_) {
159     top_force_.CopyTopologyData(top);
160     trjreader_force_ =
161         TrjReaderFactory().Create(OptionsMap()["trj-force"].as<string>());
162     if (trjreader_force_ == nullptr) {
163       throw runtime_error(string("input format not supported: ") +
164                           OptionsMap()["trj-force"].as<string>());
165     }
166     // open the trajectory
167     trjreader_force_->Open(OptionsMap()["trj-force"].as<string>());
168     // read in first frame
169     trjreader_force_->FirstFrame(top_force_);
170   }
171 }
172 
173 CGForceMatching::SplineInfo::SplineInfo(votca::Index index,
174                                         bool bonded_interaction,
175                                         votca::Index matr_pos_col,
176                                         votca::tools::Property *options) {
177   // initialize standard data
178   splineIndex = index;
179   options_ = options;
180   splineName = options->get("name").value();
181   bonded = bonded_interaction;
182   // in general natural boundary conditions are used for splines (default is no)
183   periodic = 0;
184   // check if non-bonded 3-body interaction or not (default is no)
185   threebody = 0;
186   // initialize additional parameters for threebody interactions
187   //(values of Molinero water potential)
188   a = 0.37;      //(0.37 nm)
189   sigma = 1;     //(dimensionless)
190   gamma = 0.12;  //(0.12 nm = 1.2 Ang)
191 
192   // get non-bonded information
193   if (!bonded) {
194     // check if option threebody exists
195     if (options->exists("threebody")) {
196       threebody = options->get("threebody").as<bool>();
197     }
198     // check if threebody interaction or not
199     if (threebody) {
200       type1 = options->get("type1").value();
201       type2 = options->get("type2").value();
202       type3 = options->get("type3").value();
203       // read in additional parameters for threebody interactions
204       if (options->exists("fmatch.a")) {
205         a = options->get("fmatch.a").as<double>();
206       }
207       if (options->exists("fmatch.sigma")) {
208         sigma = options->get("fmatch.sigma").as<double>();
209       }
210       if (options->exists("fmatch.gamma")) {
211         gamma = options->get("fmatch.gamma").as<double>();
212       }
213     }
214     // if not threebody only read in the two bead types
215     if (!threebody) {
216       type1 = options->get("type1").value();
217       type2 = options->get("type2").value();
218     }
219   }
220   if (bonded) {
221     // check if option periodic exists
222     if (options->exists("fmatch.periodic")) {
223       periodic = options->get("fmatch.periodic").as<bool>();
224       // set cubic spline Spline boundary conditions to periodic
225       Spline.setBCInt(1);
226     }
227   }
228   std::cout << "a: " << a << " ,sigma: " << sigma << " ,gamma: " << gamma
229             << std::endl;
230 
231   // initialize the grid
232   double grid_min = options->get("fmatch.min").as<double>();
233   double grid_max = options->get("fmatch.max").as<double>();
234   double grid_step = options->get("fmatch.step").as<double>();
235 
236   // GenerateGrid returns number of grid points. We subtract 1 to get
237   // the number of spline functions
238   num_gridpoints = Spline.GenerateGrid(grid_min, grid_max, grid_step);
239   num_splinefun = num_gridpoints - 1;
240 
241   cout << "Number of spline functions for the interaction " << splineName << ":"
242        << num_splinefun << endl;
243 
244   matr_pos = matr_pos_col;
245 
246   // initialize grid for block averaging
247   dx_out = options->get("fmatch.out_step").as<double>();
248   // number of output grid points
249   num_outgrid = 1 + (votca::Index)((grid_max - grid_min) / dx_out);
250   result = Eigen::VectorXd::Zero(num_outgrid);
251   error = Eigen::VectorXd::Zero(num_outgrid);
252   resSum = Eigen::VectorXd::Zero(num_outgrid);
253   resSum2 = Eigen::VectorXd::Zero(num_outgrid);
254 
255   // Only if threebody interaction, the derivatives are explicitly calculated
256   if (threebody) {
257     resultDer = Eigen::VectorXd::Zero(num_outgrid);
258     errorDer = Eigen::VectorXd::Zero(num_outgrid);
259     resSumDer = Eigen::VectorXd::Zero(num_outgrid);
260     resSumDer2 = Eigen::VectorXd::Zero(num_outgrid);
261   }
262 
263   block_res_f = Eigen::VectorXd::Zero(num_outgrid);
264   block_res_f2 = Eigen::VectorXd::Zero(num_outgrid);
265 }
266 
267 void CGForceMatching::EndEvaluate() {
268   // sanity check
269   if (nblocks_ == 0) {
270     cerr << "\nERROR in CGForceMatching::EndEvaluate - No blocks have been "
271             "processed so far"
272          << endl;
273     cerr << "It might be that you are using trajectory, which is smaller than "
274             "needed for one block"
275          << endl;
276     cerr << "Check your input!" << endl;
277     exit(-1);
278   }
279 
280   cout << "\nWe are done, thank you very much!" << endl;
281   if (has_existing_forces_) {
282     trjreader_force_->Close();
283   }
284 }
285 
286 void CGForceMatching::WriteOutFiles() {
287   string file_extension = ".force";
288   string file_extension_pot = ".pot";
289   string file_name;
290   string file_nameDer;
291   votca::tools::Table force_tab;
292   votca::tools::Table force_tabDer;
293 
294   // table with error column
295   force_tab.SetHasYErr(true);
296   force_tabDer.SetHasYErr(true);
297 
298   for (SplineInfo &spline : splines_) {
299     // construct meaningful outfile name
300     file_name = spline.splineName;
301 
302     // resize table
303     force_tab.resize(spline.num_outgrid);
304 
305     // If not threebody, the result represents the force
306     if (!(spline.threebody)) {
307       file_name = file_name + file_extension;
308       // print output file names on stdout
309       cout << "Updating file: " << file_name << endl;
310     }
311 
312     // If threebody interaction, the result represents the potential and (-1)
313     // the derivative represents the force Only then, the derivatives are
314     // explicitly calculated
315     if (spline.threebody) {
316       file_name = file_name + file_extension_pot;
317       file_nameDer = spline.splineName;
318       file_nameDer = file_nameDer + file_extension;
319 
320       force_tabDer.resize(spline.num_outgrid);
321       // print output file names on stdout
322       cout << "Updating files: " << file_name << " and: " << file_nameDer
323            << endl;
324     }
325 
326     spline.result = (spline.resSum).array() / nblocks_;
327     spline.error = (((spline.resSum2).array() / nblocks_ -
328                      (spline.result).array().abs2()) /
329                     nblocks_)
330                        .abs()
331                        .sqrt();
332 
333     if (spline.threebody) {
334       spline.resultDer = (spline.resSumDer).array() / nblocks_;
335       spline.errorDer = (((spline.resSumDer2).array() / nblocks_ -
336                           (spline.resultDer).array().abs2()) /
337                          nblocks_)
338                             .abs()
339                             .sqrt();
340     }
341 
342     // first output point = first grid point
343     double out_x = spline.Spline.getGridPoint(0);
344     // loop over output grid
345     for (votca::Index i = 0; i < spline.num_outgrid; i++) {
346 
347       // If not threebody the result is (-1) the force
348       if (!(spline.threebody)) {
349         // put point, result, flag and accuracy at point out_x into the table
350         force_tab.set(i, out_x, (-1.0) * spline.result[i], 'i',
351                       spline.error[i]);
352       }
353 
354       // If threebody interaction, force_tab represents the potential (-1) which
355       // is the Antiderivative of the force Only if threebody interaction, the
356       // derivatives are explicitly calculated
357       if (spline.threebody) {
358         // put point, result, flag and accuracy at point out_x into the table
359         force_tab.set(i, out_x, (+1.0) * spline.result[i], 'i',
360                       spline.error[i]);
361         force_tabDer.set(i, out_x, (-1.0) * spline.resultDer[i], 'i',
362                          spline.errorDer[i]);
363       }
364 
365       // update out_x for the next iteration
366       out_x += spline.dx_out;
367     }
368     // save table in the file
369     force_tab.Save(file_name);
370 
371     // clear the table for the next spline
372     force_tab.clear();
373 
374     // Only if threebody interaction, the derivatives are explicitly calculated
375     if (spline.threebody) {
376       force_tabDer.Save(file_nameDer);
377       // clear the table for the next spline
378       force_tabDer.clear();
379     }
380   }
381 }
382 
383 void CGForceMatching::EvalConfiguration(Topology *conf, Topology *) {
384   if (conf->BeadCount() == 0) {
385     throw std::runtime_error(
386         "CG Topology has 0 beads, check your mapping file!");
387   }
388   if (has_existing_forces_) {
389     if (conf->BeadCount() != top_force_.BeadCount()) {
390       throw std::runtime_error(
391           "number of beads in topology and force topology does not match");
392     }
393     for (votca::Index i = 0; i < conf->BeadCount(); ++i) {
394       conf->getBead(i)->F() -= top_force_.getBead(i)->getF();
395       Eigen::Vector3d d =
396           conf->getBead(i)->getPos() - top_force_.getBead(i)->getPos();
397       if (d.norm() > dist_) {  // default is 1e-5, otherwise it can be a too
398                                // strict criterion
399         throw std::runtime_error(
400             "One or more bead positions in mapped and reference force "
401             "trajectory differ by more than 1e-5");
402       }
403     }
404   }
405 
406   for (SplineInfo &sinfo : splines_) {
407     if (sinfo.bonded) {
408       EvalBonded(conf, &sinfo);
409     } else {
410       if (sinfo.threebody) {
411         EvalNonbonded_Threebody(conf, &sinfo);
412       } else {
413         EvalNonbonded(conf, &sinfo);
414       }
415     }
416   }
417 
418   // loop for the forces vector:
419   // hack, change the Has functions..
420   if (conf->getBead(0)->HasF()) {
421     for (votca::Index iatom = 0; iatom < nbeads_; ++iatom) {
422       const Eigen::Vector3d &Force = conf->getBead(iatom)->getF();
423       b_(least_sq_offset_ + 3 * nbeads_ * frame_counter_ + iatom) = Force.x();
424       b_(least_sq_offset_ + 3 * nbeads_ * frame_counter_ + nbeads_ + iatom) =
425           Force.y();
426       b_(least_sq_offset_ + 3 * nbeads_ * frame_counter_ + 2 * nbeads_ +
427          iatom) = Force.z();
428     }
429   } else {
430     throw std::runtime_error(
431         "\nERROR in csg_fmatch::EvalConfiguration - No forces in "
432         "configuration!");
433   }
434   // update the frame counter
435   frame_counter_ += 1;
436 
437   if (frame_counter_ % nframes_ == 0) {  // at this point we processed  nframes_
438                                          // frames, which is enough for one
439                                          // block
440                                          // update block counter
441     nblocks_++;
442     // solve FM equations and accumulate the result
443     FmatchAccumulateData();
444     // print status information
445     cout << "\nBlock No" << nblocks_ << " done!" << endl;
446     // write results to output files
447     WriteOutFiles();
448 
449     // we must count frames from zero again for the next block
450     frame_counter_ = 0;
451     if (constr_least_sq_) {  // Constrained Least Squares
452       // Matrices should be cleaned after each block is evaluated
453       A_.setZero();
454       b_.setZero();
455       // clear and assign smoothing conditions to  B_constr_
456       FmatchAssignSmoothCondsToMatrix(B_constr_);
457     } else {  // Simple Least Squares
458       // Matrices should be cleaned after each block is evaluated
459       // clear and assign smoothing conditions to  A_
460       FmatchAssignSmoothCondsToMatrix(A_);
461       b_.setZero();
462     }
463   }
464   if (has_existing_forces_) {
465     trjreader_force_->NextFrame(top_force_);
466   }
467 }
468 
469 void CGForceMatching::FmatchAccumulateData() {
470   if (constr_least_sq_) {  // Constrained Least Squares
471                            // Solving linear equations system
472     x_ = votca::tools::linalg_constrained_qrsolve(A_, b_, B_constr_);
473   } else {  // Simple Least Squares
474 
475     Eigen::HouseholderQR<Eigen::MatrixXd> dec(A_);
476     x_ = dec.solve(b_);
477     Eigen::VectorXd residual = b_ - A_ * x_;
478     // calculate FM residual - quality of FM
479     // FM residual is calculated in (kJ/(mol*nm))^2
480     double fm_resid = residual.cwiseAbs2().sum();
481 
482     fm_resid /= (double)(3 * nbeads_ * frame_counter_);
483 
484     cout << endl;
485     cout << "#### Force matching residual ####" << endl;
486     cout << "     Chi_2[(kJ/(mol*nm))^2] = " << fm_resid << endl;
487     cout << "#################################" << endl;
488     cout << endl;
489   }
490 
491   for (SplineInfo &sinfo : splines_) {
492     votca::Index mp = sinfo.matr_pos;
493     votca::Index ngp = sinfo.num_gridpoints;
494 
495     //  x_ contains results for all splines. Here we cut the results for one
496     // spline
497 
498     sinfo.block_res_f = x_.segment(mp, ngp);
499     sinfo.block_res_f2 = x_.segment(mp + ngp, ngp);
500 
501     // result cut before is assigned to the corresponding spline
502     sinfo.Spline.setSplineData(sinfo.block_res_f, sinfo.block_res_f2);
503 
504     // first output point = first grid point
505     double out_x = sinfo.Spline.getGridPoint(0);
506 
507     // point in the middle of the output grid for printing debug information
508     votca::Index grid_point_debug = sinfo.num_outgrid / 2;
509 
510     // loop over output grid
511     for (votca::Index i = 0; i < sinfo.num_outgrid; i++) {
512       // update resSum (add result of a particular block)
513       sinfo.resSum[i] += sinfo.Spline.Calculate(out_x);
514       // update resSum2 (add result of a particular block)
515       sinfo.resSum2[i] +=
516           sinfo.Spline.Calculate(out_x) * sinfo.Spline.Calculate(out_x);
517 
518       // Only if threebody interaction, the derivatives are explicitly
519       // calculated
520       if (sinfo.threebody) {
521         sinfo.resSumDer[i] += sinfo.Spline.CalculateDerivative(out_x);
522         // update resSumDer2 (add result of a particular block)
523         sinfo.resSumDer2[i] += sinfo.Spline.CalculateDerivative(out_x) *
524                                sinfo.Spline.CalculateDerivative(out_x);
525       }
526 
527       // print useful debug information
528       if (i == grid_point_debug) {
529         cout << "This should be a number: " << sinfo.Spline.Calculate(out_x)
530              << " " << endl;
531       }
532 
533       // output point for the next iteration
534       out_x += sinfo.dx_out;
535     }
536   }
537 }
538 
539 void CGForceMatching::FmatchAssignSmoothCondsToMatrix(Eigen::MatrixXd &Matrix) {
540   // This function assigns Spline smoothing conditions to the Matrix.
541   // For the simple least squares the function is used for Eigen::Matrix3d  A_
542   // For constrained least squares - for Eigen::Matrix3d  B_constr_
543 
544   Matrix.setZero();
545   votca::Index line_tmp = 0;
546   votca::Index col_tmp = 0;
547 
548   for (SplineInfo &sinfo : splines_) {
549 
550     sinfo.Spline.AddBCToFitMatrix(Matrix, line_tmp, col_tmp);
551     // if periodic potential, one additional constraint has to be taken into
552     // account!
553     if (sinfo.periodic != 0) {
554       sinfo.Spline.AddBCSumZeroToFitMatrix(Matrix, line_tmp, col_tmp);
555       // update counter
556       line_tmp += 1;
557     }
558     // update counters
559     votca::Index sfnum = sinfo.num_splinefun;
560     line_tmp += sfnum + 1;
561     col_tmp += 2 * (sfnum + 1);
562   }
563 }
564 
565 void CGForceMatching::LoadOptions(const string &file) {
566   options_.LoadFromXML(file);
567   bonded_ = options_.Select("cg.bonded");
568   nonbonded_ = options_.Select("cg.non-bonded");
569 }
570 
571 void CGForceMatching::EvalBonded(Topology *conf, SplineInfo *sinfo) {
572 
573   std::vector<Interaction *> interList =
574       conf->InteractionsInGroup(sinfo->splineName);
575 
576   for (Interaction *inter : interList) {
577 
578     votca::Index beads_in_int = inter->BeadCount();  // 2 for bonds, 3 for
579                                                      // angles, 4 for dihedrals
580 
581     votca::tools::CubicSpline &SP = sinfo->Spline;
582 
583     votca::Index mpos = sinfo->matr_pos;
584 
585     double var = inter->EvaluateVar(*conf);  // value of bond, angle,
586                                              // or dihedral
587 
588     for (votca::Index loop = 0; loop < beads_in_int; loop++) {
589       votca::Index ii = inter->getBeadId(loop);
590       Eigen::Vector3d gradient = inter->Grad(*conf, loop);
591 
592       SP.AddToFitMatrix(A_, var,
593                         least_sq_offset_ + 3 * nbeads_ * frame_counter_ + ii,
594                         mpos, -gradient.x());
595       SP.AddToFitMatrix(
596           A_, var,
597           least_sq_offset_ + 3 * nbeads_ * frame_counter_ + nbeads_ + ii, mpos,
598           -gradient.y());
599       SP.AddToFitMatrix(
600           A_, var,
601           least_sq_offset_ + 3 * nbeads_ * frame_counter_ + 2 * nbeads_ + ii,
602           mpos, -gradient.z());
603     }
604   }
605 }
606 
607 void CGForceMatching::EvalNonbonded(Topology *conf, SplineInfo *sinfo) {
608 
609   // generate the neighbour list
610   std::unique_ptr<NBList> nb;
611 
612   bool gridsearch = false;
613 
614   if (options_.exists("cg.nbsearch")) {
615     if (options_.get("cg.nbsearch").as<string>() == "grid") {
616       gridsearch = true;
617     } else if (options_.get("cg.nbsearch").as<string>() == "simple") {
618       gridsearch = false;
619     } else {
620       throw std::runtime_error("cg.nbsearch invalid, can be grid or simple");
621     }
622   }
623   if (gridsearch) {
624     nb = std::make_unique<NBListGrid>();
625   } else {
626     nb = std::make_unique<NBList>();
627   }
628 
629   nb->setCutoff(
630       sinfo->options_->get("fmatch.max").as<double>());  // implement different
631                                                          // cutoffs for
632                                                          // different
633                                                          // interactions!
634 
635   // generate the bead lists
636   BeadList beads1, beads2;
637   beads1.Generate(*conf, sinfo->type1);
638   beads2.Generate(*conf, sinfo->type2);
639 
640   // is it same types or different types?
641   if (sinfo->type1 == sinfo->type2) {
642     nb->Generate(beads1, true);
643   } else {
644     nb->Generate(beads1, beads2, true);
645   }
646 
647   for (BeadPair *pair : *nb) {
648     votca::Index iatom = pair->first()->getId();
649     votca::Index jatom = pair->second()->getId();
650     double var = pair->dist();
651     Eigen::Vector3d gradient = pair->r();
652     gradient.normalize();
653 
654     votca::tools::CubicSpline &SP = sinfo->Spline;
655 
656     votca::Index mpos = sinfo->matr_pos;
657 
658     // add iatom
659     SP.AddToFitMatrix(A_, var,
660                       least_sq_offset_ + 3 * nbeads_ * frame_counter_ + iatom,
661                       mpos, gradient.x());
662     SP.AddToFitMatrix(
663         A_, var,
664         least_sq_offset_ + 3 * nbeads_ * frame_counter_ + nbeads_ + iatom, mpos,
665         gradient.y());
666     SP.AddToFitMatrix(
667         A_, var,
668         least_sq_offset_ + 3 * nbeads_ * frame_counter_ + 2 * nbeads_ + iatom,
669         mpos, gradient.z());
670 
671     // add jatom
672     SP.AddToFitMatrix(A_, var,
673                       least_sq_offset_ + 3 * nbeads_ * frame_counter_ + jatom,
674                       mpos, -gradient.x());
675     SP.AddToFitMatrix(
676         A_, var,
677         least_sq_offset_ + 3 * nbeads_ * frame_counter_ + nbeads_ + jatom, mpos,
678         -gradient.y());
679     SP.AddToFitMatrix(
680         A_, var,
681         least_sq_offset_ + 3 * nbeads_ * frame_counter_ + 2 * nbeads_ + jatom,
682         mpos, -gradient.z());
683   }
684 }
685 
686 void CGForceMatching::EvalNonbonded_Threebody(Topology *conf,
687                                               SplineInfo *sinfo) {
688   // so far option gridsearch ignored. Only simple search
689 
690   // generate the neighbour list
691   std::unique_ptr<NBList_3Body> nb;
692 
693   bool gridsearch = false;
694 
695   if (options_.exists("cg.nbsearch")) {
696     if (options_.get("cg.nbsearch").as<string>() == "grid") {
697       gridsearch = true;
698     } else if (options_.get("cg.nbsearch").as<string>() == "simple") {
699       gridsearch = false;
700     } else {
701       throw std::runtime_error("cg.nbsearch invalid, can be grid or simple");
702     }
703   }
704   if (gridsearch) {
705     nb = std::make_unique<NBListGrid_3Body>();
706   } else {
707     nb = std::make_unique<NBList_3Body>();
708   }
709 
710   nb->setCutoff(sinfo->a);  // implement different cutoffs for different
711                             // interactions!
712   // Here, a is the distance between two beads of a triple, where the 3-body
713   // interaction is zero
714 
715   // generate the bead lists
716   BeadList beads1, beads2, beads3;
717   beads1.Generate(*conf, sinfo->type1);
718   beads2.Generate(*conf, sinfo->type2);
719   beads3.Generate(*conf, sinfo->type3);
720 
721   // check if type1 and type2 are the same
722   if (sinfo->type1 == sinfo->type2) {
723     // if all three types are the same
724     if (sinfo->type2 == sinfo->type3) {
725       nb->Generate(beads1, true);
726     }
727     // if type2 and type3 are different, use the Generate function for 2 bead
728     // types
729     if (sinfo->type2 != sinfo->type3) {
730       nb->Generate(beads1, beads3, true);
731     }
732   }
733   // if type1 != type2
734   if (sinfo->type1 != sinfo->type2) {
735     // if the last two types are the same, use Generate function with them as
736     // the first two bead types Neighborlist_3body is constructed in a way that
737     // the two equal bead types have two be the first 2 types
738     if (sinfo->type2 == sinfo->type3) {
739       nb->Generate(beads1, beads2, true);
740     }
741     if (sinfo->type2 != sinfo->type3) {
742       // type1 = type3 !=type2
743       if (sinfo->type1 == sinfo->type3) {
744         nb->Generate(beads2, beads1, true);
745       }
746       // type1 != type2 != type3
747       if (sinfo->type1 != sinfo->type3) {
748         nb->Generate(beads1, beads2, beads3, true);
749       }
750     }
751   }
752 
753   for (BeadTriple *triple : *nb) {
754     votca::Index iatom = triple->bead1()->getId();
755     votca::Index jatom = triple->bead2()->getId();
756     votca::Index katom = triple->bead3()->getId();
757     double distij = triple->dist12();
758     double distik = triple->dist13();
759     Eigen::Vector3d rij = triple->r12();
760     Eigen::Vector3d rik = triple->r13();
761 
762     double gamma_sigma = (sinfo->gamma) * (sinfo->sigma);
763     double denomij = (distij - (sinfo->a) * (sinfo->sigma));
764     double denomik = (distik - (sinfo->a) * (sinfo->sigma));
765     double expij = std::exp(gamma_sigma / denomij);
766     double expik = std::exp(gamma_sigma / denomik);
767 
768     votca::tools::CubicSpline &SP = sinfo->Spline;
769 
770     votca::Index mpos = sinfo->matr_pos;
771 
772     double var =
773         std::acos(rij.dot(rik) / sqrt(rij.squaredNorm() * rik.squaredNorm()));
774 
775     double acos_prime =
776         1.0 / (sqrt(1 - std::pow(rij.dot(rik), 2) /
777                             (distij * distik * distij * distik)));
778 
779     Eigen::Vector3d gradient1 =
780         acos_prime *
781         ((rij + rik) / (distij * distik) -
782          rij.dot(rik) * (rik.squaredNorm() * rij + rij.squaredNorm() * rik) /
783              (distij * distij * distij * distik * distik * distik)) *
784         expij * expik;
785     Eigen::Vector3d gradient2 =
786         ((rij / distij) * (gamma_sigma / (denomij * denomij)) +
787          (rik / distik) * (gamma_sigma / (denomik * denomik))) *
788         expij * expik;
789 
790     // add iatom
791     SP.AddToFitMatrix(A_, var,
792                       least_sq_offset_ + 3 * nbeads_ * frame_counter_ + iatom,
793                       mpos, -gradient1.x(), -gradient2.x());
794     SP.AddToFitMatrix(
795         A_, var,
796         least_sq_offset_ + 3 * nbeads_ * frame_counter_ + nbeads_ + iatom, mpos,
797         -gradient1.y(), -gradient2.y());
798     SP.AddToFitMatrix(
799         A_, var,
800         least_sq_offset_ + 3 * nbeads_ * frame_counter_ + 2 * nbeads_ + iatom,
801         mpos, -gradient1.z(), -gradient2.z());
802 
803     // evaluate gradient1 and gradient2 for jatom:
804     gradient1 = acos_prime *
805                 (-rik / (distij * distik) +
806                  rij.dot(rik) * rij / (distik * distij * distij * distij)) *
807                 expij * expik;
808     // gradient2
809     gradient2 = ((rij / distij) * (-1.0 * gamma_sigma / (denomij * denomij))) *
810                 expij * expik;
811 
812     // add jatom
813     SP.AddToFitMatrix(A_, var,
814                       least_sq_offset_ + 3 * nbeads_ * frame_counter_ + jatom,
815                       mpos, -gradient1.x(), -gradient2.x());
816     SP.AddToFitMatrix(
817         A_, var,
818         least_sq_offset_ + 3 * nbeads_ * frame_counter_ + nbeads_ + jatom, mpos,
819         -gradient1.y(), -gradient2.y());
820     SP.AddToFitMatrix(
821         A_, var,
822         least_sq_offset_ + 3 * nbeads_ * frame_counter_ + 2 * nbeads_ + jatom,
823         mpos, -gradient1.z(), -gradient2.z());
824 
825     // evaluate gradient1 and gradient2 for katom:
826     gradient1 = acos_prime *
827                 (-rij / (distij * distik) +
828                  rij.dot(rik) * rik / (distij * distik * distik * distik)) *
829                 expij * expik;
830     // gradient2
831     gradient2 = ((rik / distik) * (-1.0 * gamma_sigma / (denomik * denomik))) *
832                 expij * expik;
833 
834     // add katom
835     SP.AddToFitMatrix(A_, var,
836                       least_sq_offset_ + 3 * nbeads_ * frame_counter_ + katom,
837                       mpos, -gradient1.x(), -gradient2.x());
838     SP.AddToFitMatrix(
839         A_, var,
840         least_sq_offset_ + 3 * nbeads_ * frame_counter_ + nbeads_ + katom, mpos,
841         -gradient1.y(), -gradient2.y());
842     SP.AddToFitMatrix(
843         A_, var,
844         least_sq_offset_ + 3 * nbeads_ * frame_counter_ + 2 * nbeads_ + katom,
845         mpos, -gradient1.z(), -gradient2.z());
846   }
847 }
848