1 /*
2  * PCMSolver, an API for the Polarizable Continuum Model
3  * Copyright (C) 2020 Roberto Di Remigio, Luca Frediani and contributors.
4  *
5  * This file is part of PCMSolver.
6  *
7  * PCMSolver is free software: you can redistribute it and/or modify
8  * it under the terms of the GNU Lesser General Public License as published by
9  * the Free Software Foundation, either version 3 of the License, or
10  * (at your option) any later version.
11  *
12  * PCMSolver is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with PCMSolver.  If not, see <http://www.gnu.org/licenses/>.
19  *
20  * For information on the complete list of contributors to the
21  * PCMSolver API, see: <http://pcmsolver.readthedocs.io/>
22  */
23 
24 #include <sys/types.h>
25 #include "Meddle.hpp"
26 #include "PCMInput.h"
27 #include "pcmsolver.h"
28 
29 #include <string>
30 #include <vector>
31 
32 #include "Config.hpp"
33 
34 #include <Eigen/Core>
35 
36 #include "bi_operators/BIOperatorData.hpp"
37 #include "bi_operators/BoundaryIntegralOperator.hpp"
38 #include "cavity/Cavity.hpp"
39 #include "cavity/CavityData.hpp"
40 #include "green/Green.hpp"
41 #include "green/GreenData.hpp"
42 #include "mmfq/FQOhno.hpp"
43 #include "solver/Solver.hpp"
44 #include "solver/SolverData.hpp"
45 
46 #include "Citation.hpp"
47 #include "VersionInfo.hpp"
48 #include "utils/Atom.hpp"
49 #include "utils/Factory.hpp"
50 #include "utils/Solvent.hpp"
51 #include "utils/Sphere.hpp"
52 #include "utils/cnpy.hpp"
53 
54 #ifndef AS_TYPE
55 #define AS_TYPE(Type, Obj) reinterpret_cast<Type *>(Obj)
56 #endif
57 
58 #ifndef AS_CTYPE
59 #define AS_CTYPE(Type, Obj) reinterpret_cast<const Type *>(Obj)
60 #endif
61 
pcmsolver_new(pcmsolver_reader_t input_reading,int nr_nuclei,double charges[],double coordinates[],int symmetry_info[],PCMInput * host_input,HostWriter writer)62 pcmsolver_context_t * pcmsolver_new(pcmsolver_reader_t input_reading,
63                                     int nr_nuclei,
64                                     double charges[],
65                                     double coordinates[],
66                                     int symmetry_info[],
67                                     PCMInput * host_input,
68                                     HostWriter writer) {
69   return pcmsolver_new_v1112(input_reading,
70                              nr_nuclei,
71                              charges,
72                              coordinates,
73                              symmetry_info,
74                              "@pcmsolver.inp",
75                              host_input,
76                              writer);
77 }
78 
pcmsolver_new_v1112(pcmsolver_reader_t input_reading,int nr_nuclei,double charges[],double coordinates[],int symmetry_info[],const char * parsed_fname,PCMInput * host_input,HostWriter writer)79 pcmsolver_context_t * pcmsolver_new_v1112(pcmsolver_reader_t input_reading,
80                                           int nr_nuclei,
81                                           double charges[],
82                                           double coordinates[],
83                                           int symmetry_info[],
84                                           const char * parsed_fname,
85                                           PCMInput * host_input,
86                                           HostWriter writer) {
87   if (input_reading) {
88     // Use host_input data structured as passed from host
89     return AS_TYPE(
90         pcmsolver_context_t,
91         new pcm::Meddle(
92             nr_nuclei, charges, coordinates, symmetry_info, *host_input, writer));
93   } else {
94     // Use the parsed PCMSolver input file parsed_fname, as found on disk
95     return AS_TYPE(
96         pcmsolver_context_t,
97         new pcm::Meddle(
98             nr_nuclei, charges, coordinates, symmetry_info, writer, parsed_fname));
99   }
100 }
101 
pcmsolver_new_read_host(int nr_nuclei,double charges[],double coordinates[],int symmetry_info[],HostWriter writer)102 pcmsolver_context_t * pcmsolver_new_read_host(int nr_nuclei,
103                                               double charges[],
104                                               double coordinates[],
105                                               int symmetry_info[],
106                                               HostWriter writer) {
107   return AS_TYPE(
108       pcmsolver_context_t,
109       new pcm::Meddle(nr_nuclei, charges, coordinates, symmetry_info, writer));
110 }
111 
pcmsolver_default_input()112 PCMInput pcmsolver_default_input() {
113   PCMInput host_input;
114 
115   // These parameters would be set by the host input reading
116   // Length and area parameters are all assumed to be in Angstrom,
117   // the module will convert to Bohr internally
118   strcpy(host_input.cavity_type, "gepol");
119   host_input.patch_level = 2;
120   host_input.coarsity = 0.5;
121   host_input.area = 0.2;
122   host_input.min_distance = 0.1;
123   host_input.der_order = 4;
124   host_input.scaling = true;
125   strcpy(host_input.radii_set, "bondi");
126   strcpy(host_input.restart_name, "cavity.npz");
127   host_input.min_radius = 100.0;
128 
129   strcpy(host_input.solver_type, "iefpcm");
130   strcpy(host_input.solvent, "water");
131   strcpy(host_input.equation_type, "secondkind");
132   host_input.correction = 0.0;
133   host_input.probe_radius = 1.0;
134 
135   strcpy(host_input.inside_type, "vacuum");
136   host_input.outside_epsilon = 1.0;
137   strcpy(host_input.outside_type, "uniformdielectric");
138 
139   return host_input;
140 }
141 
142 namespace pcm {
CTORBody()143 void Meddle::CTORBody() {
144   // Write PCMSolver output header
145   infoStream_ << "~~~~~~~~~~ PCMSolver ~~~~~~~~~~\n";
146   infoStream_ << "Using CODATA " << input_.CODATAyear() << " set of constants."
147               << std::endl;
148   infoStream_ << "Input parsing done " << input_.providedBy() << std::endl;
149 
150   if (input_.isFQ()) { /* MMFQ calculation */
151     hasFQ_ = true;
152     TIMER_ON("Meddle::initMMFQ");
153     initMMFQ();
154     TIMER_OFF("Meddle::initMMFQ");
155   } else { /* Pure PCM calculation */
156     TIMER_ON("Meddle::initCavity");
157     initCavity();
158     TIMER_OFF("Meddle::initCavity");
159 
160     TIMER_ON("Meddle::initStaticSolver");
161     initStaticSolver();
162     TIMER_OFF("Meddle::initStaticSolver");
163 
164     if (input_.isDynamic()) {
165       TIMER_ON("Meddle::initDynamicSolver");
166       initDynamicSolver();
167       TIMER_OFF("Meddle::initDynamicSolver");
168     }
169   }
170 }
171 
Meddle(const Input & input,const HostWriter & write)172 Meddle::Meddle(const Input & input, const HostWriter & write)
173     : hostWriter_(write),
174       input_(input),
175       host_input_(pcmsolver_default_input()),
176       cavity_(nullptr),
177       K_0_(nullptr),
178       K_d_(nullptr),
179       FQ_(nullptr),
180       hasDynamic_(false),
181       hasFQ_(false) {
182   input_.initMolecule();
183   CTORBody();
184 }
185 
Meddle(const std::string & inputFileName,const HostWriter & write)186 Meddle::Meddle(const std::string & inputFileName, const HostWriter & write)
187     : hostWriter_(write),
188       input_(Input(inputFileName)),
189       host_input_(pcmsolver_default_input()),
190       cavity_(nullptr),
191       K_0_(nullptr),
192       K_d_(nullptr),
193       FQ_(nullptr),
194       hasDynamic_(false),
195       hasFQ_(false) {
196   input_.initMolecule();
197   CTORBody();
198 }
199 
Meddle(int nr_nuclei,double charges[],double coordinates[],int symmetry_info[],const HostWriter & write,const std::string & inputFileName)200 Meddle::Meddle(int nr_nuclei,
201                double charges[],
202                double coordinates[],
203                int symmetry_info[],
204                const HostWriter & write,
205                const std::string & inputFileName)
206     : hostWriter_(write),
207       input_(Input(inputFileName.empty() ? "@pcmsolver.inp" : inputFileName)),
208       host_input_(pcmsolver_default_input()),
209       cavity_(nullptr),
210       K_0_(nullptr),
211       K_d_(nullptr),
212       FQ_(nullptr),
213       hasDynamic_(false),
214       hasFQ_(false) {
215   TIMER_ON("Meddle::initInput");
216   initInput(nr_nuclei, charges, coordinates, symmetry_info);
217   TIMER_OFF("Meddle::initInput");
218 
219   CTORBody();
220 }
221 
Meddle(int nr_nuclei,double charges[],double coordinates[],int symmetry_info[],const PCMInput & host_input,const HostWriter & write)222 Meddle::Meddle(int nr_nuclei,
223                double charges[],
224                double coordinates[],
225                int symmetry_info[],
226                const PCMInput & host_input,
227                const HostWriter & write)
228     : hostWriter_(write),
229       input_(Input(host_input)),
230       host_input_(host_input),
231       cavity_(nullptr),
232       K_0_(nullptr),
233       K_d_(nullptr),
234       FQ_(nullptr),
235       hasDynamic_(false),
236       hasFQ_(false) {
237   TIMER_ON("Meddle::initInput");
238   initInput(nr_nuclei, charges, coordinates, symmetry_info);
239   TIMER_OFF("Meddle::initInput");
240 
241   CTORBody();
242 }
243 
Meddle(int nr_nuclei,double charges[],double coordinates[],int symmetry_info[],const HostWriter & write)244 Meddle::Meddle(int nr_nuclei,
245                double charges[],
246                double coordinates[],
247                int symmetry_info[],
248                const HostWriter & write)
249     : hostWriter_(write),
250       input_(Input(pcmsolver_default_input())),
251       host_input_(pcmsolver_default_input()),
252       cavity_(nullptr),
253       K_0_(nullptr),
254       K_d_(nullptr),
255       FQ_(nullptr),
256       hasDynamic_(false),
257       hasFQ_(false) {
258   // This one does a deferred initialization:
259   // The CTORBody function is not called at this point, but during refresh
260   TIMER_ON("Meddle::initInput");
261   initInput(nr_nuclei, charges, coordinates, symmetry_info, true);
262   TIMER_OFF("Meddle::initInput");
263 }
264 } // namespace pcm
265 
pcmsolver_delete(pcmsolver_context_t * context)266 void pcmsolver_delete(pcmsolver_context_t * context) {
267   if (!context)
268     return;
269   delete AS_TYPE(pcm::Meddle, context);
270 }
~Meddle()271 pcm::Meddle::~Meddle() {
272   if (hasFQ_) { /* MMFQ calculation */
273     delete FQ_;
274   } else { /* Pure PCM calculation */
275     delete cavity_;
276     delete K_0_;
277     if (hasDynamic_)
278       delete K_d_;
279   }
280 }
281 
pcmsolver_get_cavity_size(pcmsolver_context_t * context)282 PCMSolverIndex pcmsolver_get_cavity_size(pcmsolver_context_t * context) {
283   return (AS_CTYPE(pcm::Meddle, context)->getCavitySize());
284 }
getCavitySize() const285 PCMSolverIndex pcm::Meddle::getCavitySize() const { return std::get<0>(size_); }
286 
pcmsolver_get_irreducible_cavity_size(pcmsolver_context_t * context)287 PCMSolverIndex pcmsolver_get_irreducible_cavity_size(pcmsolver_context_t * context) {
288   return (AS_CTYPE(pcm::Meddle, context)->getIrreducibleCavitySize());
289 }
getIrreducibleCavitySize() const290 PCMSolverIndex pcm::Meddle::getIrreducibleCavitySize() const {
291   return std::get<1>(size_);
292 }
293 
pcmsolver_get_centers(pcmsolver_context_t * context,double centers[])294 void pcmsolver_get_centers(pcmsolver_context_t * context, double centers[]) {
295   TIMER_ON("pcmsolver_get_centers");
296   AS_CTYPE(pcm::Meddle, context)->getCenters(centers);
297   TIMER_OFF("pcmsolver_get_centers");
298 }
getCenters(double centers[]) const299 void pcm::Meddle::getCenters(double centers[]) const {
300   TIMER_ON("Meddle::getCenters");
301   if (hasFQ_) { /* MMFQ calculation */
302     PCMSolverIndex size = input_.fragments().sites.cols();
303     Eigen::Map<Eigen::Matrix3Xd>(centers, 3, size) = input_.fragments().sites;
304   } else { /* Pure PCM calculation */
305     Eigen::Map<Eigen::Matrix3Xd>(centers, 3, cavity_->size()) =
306         cavity_->elementCenter();
307   }
308   TIMER_OFF("Meddle::getCenters");
309 }
310 
pcmsolver_get_center(pcmsolver_context_t * context,int its,double center[])311 void pcmsolver_get_center(pcmsolver_context_t * context, int its, double center[]) {
312   AS_CTYPE(pcm::Meddle, context)->getCenter(its, center);
313 }
getCenter(int its,double center[]) const314 void pcm::Meddle::getCenter(int its, double center[]) const {
315   if (hasFQ_) { /* MMFQ calculation */
316     Eigen::Map<Eigen::Matrix3Xd>(center, 3, 1) =
317         input_.fragments().sites.col(its - 1);
318   } else { /* Pure PCM calculation */
319     Eigen::Map<Eigen::Vector3d>(center, 3, 1) = cavity_->elementCenter(its - 1);
320   }
321 }
322 
pcmsolver_get_areas(pcmsolver_context_t * context,double areas[])323 void pcmsolver_get_areas(pcmsolver_context_t * context, double areas[]) {
324   AS_CTYPE(pcm::Meddle, context)->getAreas(areas);
325 }
getAreas(double areas[]) const326 void pcm::Meddle::getAreas(double areas[]) const {
327   Eigen::Map<Eigen::VectorXd>(areas, cavity_->size(), 1) = cavity_->elementArea();
328 }
329 
pcmsolver_compute_polarization_energy(pcmsolver_context_t * context,const char * mep_name,const char * asc_name)330 double pcmsolver_compute_polarization_energy(pcmsolver_context_t * context,
331                                              const char * mep_name,
332                                              const char * asc_name) {
333   return (
334       AS_CTYPE(pcm::Meddle, context)
335           ->computePolarizationEnergy(std::string(mep_name), std::string(asc_name)));
336 }
computePolarizationEnergy(const std::string & mep_name,const std::string & asc_name) const337 double pcm::Meddle::computePolarizationEnergy(const std::string & mep_name,
338                                               const std::string & asc_name) const {
339   double energy = 0.0;
340   if (hasFQ_) { /* MMFQ calculation */
341     // Dot product of MEP + Electronegativities times Fluctuating charges
342     energy = (functions_.at(mep_name) + input_.fragments().chi)
343                  .dot(functions_.at(asc_name));
344     if (input_.isNonPolarizable()) { /* HACK Nonpolarizable doesn't need 1/2 */
345       energy *= 2.0;
346     }
347   } else { /* Pure PCM calculation */
348     // Dot product of MEP and ASC surface function
349     energy = functions_.at(mep_name).dot(functions_.at(asc_name));
350   }
351   return (energy / 2.0);
352 }
353 
pcmsolver_get_asc_dipole(pcmsolver_context_t * context,const char * asc_name,double dipole[])354 double pcmsolver_get_asc_dipole(pcmsolver_context_t * context,
355                                 const char * asc_name,
356                                 double dipole[]) {
357   return (
358       AS_CTYPE(pcm::Meddle, context)->getASCDipole(std::string(asc_name), dipole));
359 }
getASCDipole(const std::string & asc_name,double dipole[]) const360 double pcm::Meddle::getASCDipole(const std::string & asc_name,
361                                  double dipole[]) const {
362   Eigen::Vector3d asc_dipole = cavity_->elementCenter() * functions_.at(asc_name);
363   // Bind to host-allocated array
364   Eigen::Map<Eigen::Vector3d>(dipole, 3, 1) = asc_dipole;
365   return asc_dipole.norm();
366 }
367 
pcmsolver_compute_asc(pcmsolver_context_t * context,const char * mep_name,const char * asc_name,int irrep)368 void pcmsolver_compute_asc(pcmsolver_context_t * context,
369                            const char * mep_name,
370                            const char * asc_name,
371                            int irrep) {
372   TIMER_ON("pcmsolver_compute_asc");
373   AS_TYPE(pcm::Meddle, context)
374       ->computeASC(std::string(mep_name), std::string(asc_name), irrep);
375   TIMER_OFF("pcmsolver_compute_asc");
376 }
computeASC(const std::string & mep_name,const std::string & asc_name,int irrep)377 void pcm::Meddle::computeASC(const std::string & mep_name,
378                              const std::string & asc_name,
379                              int irrep) {
380   // Get the proper iterators
381   SurfaceFunctionMapConstIter iter_pot = functions_.find(mep_name);
382   Eigen::VectorXd asc = Eigen::VectorXd::Zero(iter_pot->second.size());
383   if (hasFQ_) {                      /* MMFQ calculation */
384     if (input_.isNonPolarizable()) { /* HACK We store point charges in the eta vector
385                                       */
386       asc = input_.fragments().eta;
387     } else {
388       asc = FQ_->computeCharge(iter_pot->second);
389     }
390   } else { /* Pure PCM calculation */
391     asc = K_0_->computeCharge(iter_pot->second, irrep);
392     // Renormalize
393     asc /= double(cavity_->pointGroup().nrIrrep());
394   }
395   // Insert it into the map
396   if (functions_.count(asc_name) == 1) { // Key in map already
397     functions_[asc_name] = asc;
398   } else { // Create key-value pair
399     functions_.insert(std::make_pair(asc_name, asc));
400   }
401 }
402 
pcmsolver_compute_response_asc(pcmsolver_context_t * context,const char * mep_name,const char * asc_name,int irrep)403 void pcmsolver_compute_response_asc(pcmsolver_context_t * context,
404                                     const char * mep_name,
405                                     const char * asc_name,
406                                     int irrep) {
407   TIMER_ON("pcmsolver_compute_response_asc");
408   AS_TYPE(pcm::Meddle, context)
409       ->computeResponseASC(std::string(mep_name), std::string(asc_name), irrep);
410   TIMER_OFF("pcmsolver_compute_response_asc");
411 }
computeResponseASC(const std::string & mep_name,const std::string & asc_name,int irrep)412 void pcm::Meddle::computeResponseASC(const std::string & mep_name,
413                                      const std::string & asc_name,
414                                      int irrep) {
415   // Get the proper iterators
416   SurfaceFunctionMapConstIter iter_pot = functions_.find(mep_name);
417   Eigen::VectorXd asc = Eigen::VectorXd::Zero(iter_pot->second.size());
418   if (hasFQ_) {                       /* MMFQ calculation */
419     if (!input_.isNonPolarizable()) { /* HACK Can we do it more cleanly/clearly? */
420       // Do NOT add classical (electronegativities) contributions to RHS
421       asc = FQ_->computeCharge(iter_pot->second, false);
422     }
423   } else { /* Pure PCM calculation */
424     if (hasDynamic_) {
425       asc = K_d_->computeCharge(iter_pot->second, irrep);
426     } else {
427       asc = K_0_->computeCharge(iter_pot->second, irrep);
428     }
429     // Renormalize
430     asc /= double(cavity_->pointGroup().nrIrrep());
431   }
432   if (functions_.count(asc_name) == 1) { // Key in map already
433     functions_[asc_name] = asc;
434   } else { // Create key-value pair
435     functions_.insert(std::make_pair(asc_name, asc));
436   }
437 }
438 
pcmsolver_get_surface_function(pcmsolver_context_t * context,PCMSolverIndex size,double values[],const char * name)439 void pcmsolver_get_surface_function(pcmsolver_context_t * context,
440                                     PCMSolverIndex size,
441                                     double values[],
442                                     const char * name) {
443   TIMER_ON("pcmsolver_get_surface_function");
444   AS_CTYPE(pcm::Meddle, context)
445       ->getSurfaceFunction(size, values, std::string(name));
446   TIMER_OFF("pcmsolver_get_surface_function");
447 }
getSurfaceFunction(PCMSolverIndex size,double values[],const std::string & name) const448 void pcm::Meddle::getSurfaceFunction(PCMSolverIndex size,
449                                      double values[],
450                                      const std::string & name) const {
451   if (std::get<0>(size_) != size)
452     PCMSOLVER_ERROR("The " + name + " SurfaceFunction is bigger than the cavity!");
453 
454   SurfaceFunctionMapConstIter iter = functions_.find(name);
455   if (iter == functions_.end())
456     PCMSOLVER_ERROR("The " + name + " SurfaceFunction does not exist.");
457 
458   Eigen::Map<Eigen::VectorXd>(values, size, 1) = iter->second;
459 }
460 
pcmsolver_set_surface_function(pcmsolver_context_t * context,PCMSolverIndex size,double values[],const char * name)461 void pcmsolver_set_surface_function(pcmsolver_context_t * context,
462                                     PCMSolverIndex size,
463                                     double values[],
464                                     const char * name) {
465   TIMER_ON("pcmsolver_set_surface_function");
466   AS_TYPE(pcm::Meddle, context)->setSurfaceFunction(size, values, std::string(name));
467   TIMER_OFF("pcmsolver_set_surface_function");
468 }
setSurfaceFunction(PCMSolverIndex size,double values[],const std::string & name)469 void pcm::Meddle::setSurfaceFunction(PCMSolverIndex size,
470                                      double values[],
471                                      const std::string & name) {
472   if (std::get<0>(size_) != size)
473     PCMSOLVER_ERROR("The " + name + " SurfaceFunction is bigger than the cavity!");
474 
475   Eigen::VectorXd func = Eigen::Map<Eigen::VectorXd>(values, size, 1);
476   if (functions_.count(name) == 1) { // Key in map already
477     functions_[name] = func;
478   } else {
479     functions_.insert(std::make_pair(name, func));
480   }
481 }
482 
pcmsolver_print_surface_function(pcmsolver_context_t * context,const char * name)483 void pcmsolver_print_surface_function(pcmsolver_context_t * context,
484                                       const char * name) {
485   AS_CTYPE(pcm::Meddle, context)->printSurfaceFunction(std::string(name));
486 }
printSurfaceFunction(const std::string & name) const487 void pcm::Meddle::printSurfaceFunction(const std::string & name) const {
488   if (functions_.count(name) == 1) { // Key in map already
489     std::ostringstream print_sf;
490     Eigen::IOFormat fmt(Eigen::FullPrecision);
491     print_sf << functions_.at(name).format(fmt) << std::endl;
492     hostWriter_(print_sf);
493   } else {
494     PCMSOLVER_ERROR("You are trying to print a nonexistent SurfaceFunction!");
495   }
496 }
497 
pcmsolver_save_surface_functions(pcmsolver_context_t * context)498 void pcmsolver_save_surface_functions(pcmsolver_context_t * context) {
499   AS_CTYPE(pcm::Meddle, context)->saveSurfaceFunctions();
500 }
saveSurfaceFunctions() const501 void pcm::Meddle::saveSurfaceFunctions() const {
502   hostWriter_("\nDumping surface functions to .npy files");
503   for (auto sf_pair : functions_) {
504     cnpy::custom::npy_save(sf_pair.first + ".npy", sf_pair.second);
505   }
506 }
507 
pcmsolver_save_surface_function(pcmsolver_context_t * context,const char * name)508 void pcmsolver_save_surface_function(pcmsolver_context_t * context,
509                                      const char * name) {
510   AS_CTYPE(pcm::Meddle, context)->saveSurfaceFunction(std::string(name));
511 }
saveSurfaceFunction(const std::string & name) const512 void pcm::Meddle::saveSurfaceFunction(const std::string & name) const {
513   SurfaceFunctionMapConstIter it = functions_.find(name);
514   cnpy::custom::npy_save(name + ".npy", it->second);
515 }
516 
pcmsolver_load_surface_function(pcmsolver_context_t * context,const char * name)517 void pcmsolver_load_surface_function(pcmsolver_context_t * context,
518                                      const char * name) {
519   AS_TYPE(pcm::Meddle, context)->loadSurfaceFunction(std::string(name));
520 }
loadSurfaceFunction(const std::string & name)521 void pcm::Meddle::loadSurfaceFunction(const std::string & name) {
522   hostWriter_("\nLoading surface function " + name + " from .npy file");
523   Eigen::VectorXd values = cnpy::custom::npy_load<double>(name + ".npy");
524   if (values.size() != std::get<0>(size_))
525     PCMSOLVER_ERROR("The loaded " + name +
526                     " surface function is bigger than the cavity!");
527   // Append to global map
528   if (functions_.count(name) == 1) { // Key in map already
529     functions_[name] = values;
530   } else {
531     functions_.insert(std::make_pair(name, values));
532   }
533 }
534 
pcmsolver_write_timings(pcmsolver_context_t * context)535 void pcmsolver_write_timings(pcmsolver_context_t * context) {
536   AS_CTYPE(pcm::Meddle, context)->writeTimings();
537 }
writeTimings() const538 void pcm::Meddle::writeTimings() const { TIMER_DONE("pcmsolver.timer.dat"); }
539 
pcmsolver_refresh(pcmsolver_context_t * context)540 void pcmsolver_refresh(pcmsolver_context_t * context) {
541   AS_TYPE(pcm::Meddle, context)->refresh();
542 }
refresh()543 void pcm::Meddle::refresh() {
544   // Gather info to refresh Molecule
545   // FIXME I need to refresh it because scaling of radii is done there -.-
546   Symmetry pg = molecule().pointGroup();
547   size_t nuclei = molecule().nAtoms();
548   Eigen::VectorXd chgs = molecule().charges();
549   Eigen::MatrixXd cnts = molecule().geometry();
550   // Refresh input_
551   input_ = Input(host_input_);
552   // Refresh Molecule
553   input_.molecule(detail::initMolecule(input_, pg, nuclei, chgs, cnts, false));
554   CTORBody();
555 }
556 
pcmsolver_set_bool_option(pcmsolver_context_t * context,const char * parameter,bool value)557 void pcmsolver_set_bool_option(pcmsolver_context_t * context,
558                                const char * parameter,
559                                bool value) {
560   AS_TYPE(pcm::Meddle, context)->setInputOption(std::string(parameter), value);
561 }
setInputOption(std::string parameter,bool value)562 void pcm::Meddle::setInputOption(std::string parameter, bool value) {
563   detail::PCMInputFields p = detail::string_to_enum(parameter);
564   switch (p) {
565     case detail::PCMInputFields::scaling:
566       host_input_.scaling = value;
567       break;
568     default:
569       std::ostringstream errmsg;
570       errmsg << "Unknown parameter " << parameter << std::endl;
571       errmsg << " See http://pcmsolver.readthedocs.org/en/latest/users/input.html"
572              << std::endl;
573       PCMSOLVER_ERROR(errmsg.str());
574   }
575 }
576 
pcmsolver_set_int_option(pcmsolver_context_t * context,const char * parameter,int value)577 void pcmsolver_set_int_option(pcmsolver_context_t * context,
578                               const char * parameter,
579                               int value) {
580   AS_TYPE(pcm::Meddle, context)->setInputOption(std::string(parameter), value);
581 }
setInputOption(std::string parameter,int value)582 void pcm::Meddle::setInputOption(std::string parameter, int value) {
583   detail::PCMInputFields p = detail::string_to_enum(parameter);
584   switch (p) {
585     case detail::PCMInputFields::patch_level:
586       host_input_.patch_level = value;
587       break;
588     case detail::PCMInputFields::der_order:
589       host_input_.der_order = value;
590       break;
591     default:
592       std::ostringstream errmsg;
593       errmsg << "Unknown parameter " << parameter << std::endl;
594       errmsg << " See http://pcmsolver.readthedocs.org/en/latest/users/input.html"
595              << std::endl;
596       PCMSOLVER_ERROR(errmsg.str());
597   }
598 }
599 
pcmsolver_set_double_option(pcmsolver_context_t * context,const char * parameter,double value)600 void pcmsolver_set_double_option(pcmsolver_context_t * context,
601                                  const char * parameter,
602                                  double value) {
603   AS_TYPE(pcm::Meddle, context)->setInputOption(std::string(parameter), value);
604 }
setInputOption(std::string parameter,double value)605 void pcm::Meddle::setInputOption(std::string parameter, double value) {
606   detail::PCMInputFields p = detail::string_to_enum(parameter);
607   switch (p) {
608     case detail::PCMInputFields::coarsity:
609       host_input_.coarsity = value;
610       break;
611     case detail::PCMInputFields::area:
612       host_input_.area = value;
613       break;
614     case detail::PCMInputFields::min_distance:
615       host_input_.min_distance = value;
616       break;
617     case detail::PCMInputFields::min_radius:
618       host_input_.min_radius = value;
619       break;
620     case detail::PCMInputFields::correction:
621       host_input_.correction = value;
622       break;
623     case detail::PCMInputFields::probe_radius:
624       host_input_.probe_radius = value;
625       break;
626     case detail::PCMInputFields::outside_epsilon:
627       host_input_.outside_epsilon = value;
628       break;
629     default:
630       std::ostringstream errmsg;
631       errmsg << "Unknown parameter " << parameter << std::endl;
632       errmsg << " See http://pcmsolver.readthedocs.org/en/latest/users/input.html"
633              << std::endl;
634       PCMSOLVER_ERROR(errmsg.str());
635   }
636 }
637 
pcmsolver_set_string_option(pcmsolver_context_t * context,const char * parameter,const char * value)638 void pcmsolver_set_string_option(pcmsolver_context_t * context,
639                                  const char * parameter,
640                                  const char * value) {
641   AS_TYPE(pcm::Meddle, context)
642       ->setInputOption(std::string(parameter), std::string(value));
643 }
setInputOption(std::string parameter,std::string value)644 void pcm::Meddle::setInputOption(std::string parameter, std::string value) {
645   detail::PCMInputFields p = detail::string_to_enum(parameter);
646   switch (p) {
647     case detail::PCMInputFields::cavity_type:
648       strcpy(host_input_.cavity_type, value.c_str());
649       break;
650     case detail::PCMInputFields::radii_set:
651       strcpy(host_input_.radii_set, value.c_str());
652       break;
653     case detail::PCMInputFields::restart_name:
654       strcpy(host_input_.restart_name, value.c_str());
655       break;
656     case detail::PCMInputFields::solver_type:
657       strcpy(host_input_.solver_type, value.c_str());
658       break;
659     case detail::PCMInputFields::solvent:
660       strcpy(host_input_.solvent, value.c_str());
661       break;
662     case detail::PCMInputFields::equation_type:
663       strcpy(host_input_.equation_type, value.c_str());
664       break;
665     case detail::PCMInputFields::inside_type:
666       strcpy(host_input_.inside_type, value.c_str());
667       break;
668     case detail::PCMInputFields::outside_type:
669       strcpy(host_input_.outside_type, value.c_str());
670       break;
671     default:
672       std::ostringstream errmsg;
673       errmsg << "Unknown parameter " << parameter << std::endl;
674       errmsg << " See http://pcmsolver.readthedocs.org/en/latest/users/input.html"
675              << std::endl;
676       PCMSOLVER_ERROR(errmsg.str());
677   }
678 }
679 
pcmsolver_print(pcmsolver_context_t * context)680 void pcmsolver_print(pcmsolver_context_t * context) {
681   AS_CTYPE(pcm::Meddle, context)->printInfo();
682 }
printInfo() const683 void pcm::Meddle::printInfo() const { hostWriter_(infoStream_); }
684 
pcmsolver_citation(HostWriter writer)685 void pcmsolver_citation(HostWriter writer) { writer(citation_message().c_str()); }
686 
printCitation() const687 std::string pcm::Meddle::printCitation() const { return citation_message(); }
688 
pcmsolver_is_compatible_library(void)689 bool pcmsolver_is_compatible_library(void) {
690   unsigned int major = (pcm::pcmsolver_get_version() >> 16);
691   return (major == PROJECT_VERSION_MAJOR);
692 }
pcmsolver_get_version(void)693 unsigned int pcm::pcmsolver_get_version(void) { return PCMSOLVER_VERSION; }
694 
695 namespace pcm {
molecule() const696 Molecule Meddle::molecule() const { return input_.molecule(); }
697 
getCenters() const698 Eigen::Matrix3Xd Meddle::getCenters() const { return cavity_->elementCenter(); }
699 
initInput(int nr_nuclei,double charges[],double coordinates[],int symmetry_info[],bool deferred_init)700 void Meddle::initInput(int nr_nuclei,
701                        double charges[],
702                        double coordinates[],
703                        int symmetry_info[],
704                        bool deferred_init) {
705   // Position and charges of atomic centers
706   Eigen::VectorXd chg = Eigen::Map<Eigen::VectorXd>(charges, nr_nuclei, 1);
707   Eigen::Matrix3Xd centers = Eigen::Map<Eigen::Matrix3Xd>(coordinates, 3, nr_nuclei);
708 
709   if (input_.mode() != "EXPLICIT") {
710     auto pg = Symmetry(
711         symmetry_info[0], symmetry_info[1], symmetry_info[2], symmetry_info[3]);
712     input_.molecule(
713         detail::initMolecule(input_, pg, nr_nuclei, chg, centers, deferred_init));
714   }
715 }
716 
initCavity()717 void Meddle::initCavity() {
718   cavity_ = cavity::bootstrapFactory().create(input_.cavityParams().cavityType,
719                                               input_.cavityParams());
720   size_ = std::make_tuple(cavity_->size(), cavity_->irreducible_size());
721   cavity_->saveCavity();
722 
723   infoStream_ << "========== Cavity " << std::endl;
724   infoStream_ << "Atomic radii set: " << input_.radiiSetName() << std::endl;
725   infoStream_ << *cavity_;
726 }
727 
initStaticSolver()728 void Meddle::initStaticSolver() {
729   IGreensFunction * gf_i = green::bootstrapFactory().create(
730       input_.insideGreenParams().greensFunctionType, input_.insideGreenParams());
731   IGreensFunction * gf_o = green::bootstrapFactory().create(
732       input_.outsideStaticGreenParams().greensFunctionType,
733       input_.outsideStaticGreenParams());
734 
735   K_0_ = solver::bootstrapFactory().create(input_.solverParams().solverType,
736                                            input_.solverParams());
737 
738   IBoundaryIntegralOperator * biop = bi_operators::bootstrapFactory().create(
739       input_.integratorParams().integratorType, input_.integratorParams());
740   K_0_->buildSystemMatrix(*cavity_, *gf_i, *gf_o, *biop);
741   delete biop;
742 
743   // Perform Gauss' theorem check for nuclear charges
744   if (!hasFQ_)
745     GaussCheck();
746 
747   infoStream_ << "========== Static solver " << std::endl;
748   infoStream_ << *K_0_ << std::endl;
749   mediumInfo(gf_i, gf_o);
750   delete gf_o;
751   delete gf_i;
752 }
753 
initDynamicSolver()754 void Meddle::initDynamicSolver() {
755   IGreensFunction * gf_i = green::bootstrapFactory().create(
756       input_.insideGreenParams().greensFunctionType, input_.insideGreenParams());
757   IGreensFunction * gf_o = green::bootstrapFactory().create(
758       input_.outsideDynamicGreenParams().greensFunctionType,
759       input_.outsideDynamicGreenParams());
760 
761   K_d_ = solver::bootstrapFactory().create(input_.solverParams().solverType,
762                                            input_.solverParams());
763 
764   IBoundaryIntegralOperator * biop = bi_operators::bootstrapFactory().create(
765       input_.integratorParams().integratorType, input_.integratorParams());
766   K_d_->buildSystemMatrix(*cavity_, *gf_i, *gf_o, *biop);
767   hasDynamic_ = true;
768   delete biop;
769 
770   infoStream_ << "========== Dynamic solver " << std::endl;
771   infoStream_ << *K_d_ << std::endl;
772   mediumInfo(gf_i, gf_o);
773   delete gf_o;
774   delete gf_i;
775 }
776 
initMMFQ()777 void Meddle::initMMFQ() {
778   FQ_ = new mmfq::FQOhno(input_.fragments(), input_.isNonPolarizable());
779   size_ = std::make_tuple(input_.fragments().sites.cols(),
780                           input_.fragments().sites.cols());
781   hasFQ_ = true;
782 
783   infoStream_ << "========== MMFQ solver " << std::endl;
784   infoStream_ << *FQ_ << std::endl;
785 }
786 
mediumInfo(IGreensFunction * gf_i,IGreensFunction * gf_o)787 void Meddle::mediumInfo(IGreensFunction * gf_i, IGreensFunction * gf_o) {
788   using utils::Solvent;
789   infoStream_ << "============ Medium " << std::endl;
790   if (input_.fromSolvent()) {
791     infoStream_ << "Medium initialized from solvent built-in data." << std::endl;
792     Solvent solvent = input_.solvent();
793     infoStream_ << solvent << std::endl;
794   }
795   std::stringstream tmp;
796   tmp << ".... Inside " << std::endl;
797   tmp << *gf_i << std::endl;
798   tmp << ".... Outside " << std::endl;
799   tmp << *gf_o;
800   infoStream_ << tmp.str() << std::endl;
801 }
802 
GaussCheck() const803 void Meddle::GaussCheck() const {
804   Eigen::VectorXd nuclear_mep = computeMEP(input_.molecule(), cavity_->elements());
805   Eigen::VectorXd nuclear_asc = K_0_->computeCharge(nuclear_mep);
806   double total_nuclear_asc = nuclear_asc.sum() * cavity_->pointGroup().nrIrrep();
807   double gauss_nuclear_asc = GaussEstimate(input_.molecule().charges(),
808                                            input_.outsideStaticGreenParams().epsilon,
809                                            input_.correction());
810   double abs_rel_diff =
811       std::abs((total_nuclear_asc - gauss_nuclear_asc) / gauss_nuclear_asc);
812   std::stringstream tmp;
813   if (!utils::isZero(abs_rel_diff, 1.0e-2)) {
814     std::ostringstream errmsg;
815     errmsg
816         << "Absolute value of the relative difference between the Gauss' theorem ("
817         << gauss_nuclear_asc << ") ";
818     errmsg << "and computed (" << total_nuclear_asc << ") values ";
819     errmsg << "of the total nuclear ASC higher than threshold (" << abs_rel_diff
820            << ")." << std::endl;
821     errmsg << "Consider changing the average area of the cavity finite elements."
822            << std::endl;
823     errmsg
824         << "Please report this issue: https://github.com/PCMSolver/pcmsolver/issues"
825         << std::endl;
826     PCMSOLVER_ERROR(errmsg.str());
827   }
828 }
829 
830 namespace detail {
initMolecule(const Input & inp,const Symmetry & pg,int nuclei,const Eigen::VectorXd & charges,const Eigen::Matrix3Xd & centers,bool deferred_init)831 Molecule initMolecule(const Input & inp,
832                       const Symmetry & pg,
833                       int nuclei,
834                       const Eigen::VectorXd & charges,
835                       const Eigen::Matrix3Xd & centers,
836                       bool deferred_init) {
837   bool scaling = inp.scaling();
838   std::string set = inp.radiiSet();
839   std::vector<Atom> radiiSet;
840   std::vector<Atom> atoms;
841   // FIXME Code duplication in function initMolecule in interface/Input.cpp
842   tie(ignore, radiiSet) = utils::bootstrapRadiiSet().create(set);
843   std::vector<Sphere> spheres;
844   atoms.reserve(nuclei);
845   spheres.reserve(nuclei);
846   for (int i = 0; i < charges.size(); ++i) {
847     int index = int(charges(i)) - 1;
848     atoms.push_back(radiiSet[index]);
849     if (scaling)
850       atoms[i].radiusScaling = 1.2;
851     // Convert to Bohr and multiply by scaling factor (alpha)
852     double radius = atoms[i].radius * angstromToBohr() * atoms[i].radiusScaling;
853     spheres.push_back(Sphere(centers.col(i), radius));
854   }
855   Eigen::VectorXd masses = Eigen::VectorXd::Zero(nuclei);
856   for (int i = 0; i < masses.size(); ++i) {
857     masses(i) = atoms[i].mass;
858   }
859   // Based on the creation mode (Implicit or Atoms)
860   // the spheres list might need postprocessing
861   if (inp.mode() == "ATOMS") {
862     initSpheresAtoms(inp, centers, spheres);
863   }
864 
865   // OK, now get molecule
866   Molecule molecule(nuclei, charges, masses, centers, atoms, spheres, pg);
867   // Check that all atoms have a radius attached
868   std::vector<Atom>::const_iterator res =
869       std::find_if(atoms.begin(), atoms.end(), invalid);
870   if (res != atoms.end() && !deferred_init) {
871     std::ostringstream errmsg;
872     errmsg << "In the molecule:\n" << molecule << std::endl;
873     errmsg << "Some atoms do not have a radius attached." << std::endl;
874     errmsg << "Please specify a radius for all atoms!" << std::endl;
875     errmsg << " See http://pcmsolver.readthedocs.org/en/latest/users/input.html"
876            << std::endl;
877     PCMSOLVER_ERROR(errmsg.str());
878   }
879   return molecule;
880 }
881 
initSpheresAtoms(const Input & inp,const Eigen::Matrix3Xd & sphereCenter_,std::vector<Sphere> & spheres_)882 void initSpheresAtoms(const Input & inp,
883                       const Eigen::Matrix3Xd & sphereCenter_,
884                       std::vector<Sphere> & spheres_) {
885   // Loop over the atomsInput array to get which atoms will have a user-given radius
886   for (size_t i = 0; i < inp.atoms().size(); ++i) {
887     size_t index =
888         inp.atoms(i) - 1; // -1 to go from human readable to machine readable
889     // Put the new Sphere in place of the implicit-generated one
890     spheres_[index] = Sphere(sphereCenter_.col(index), inp.radii(i));
891   }
892 }
893 
print(const PCMInput & inp)894 void print(const PCMInput & inp) {
895   std::cout << "cavity type " << std::string(inp.cavity_type) << std::endl;
896   std::cout << "patch level " << inp.patch_level << std::endl;
897   std::cout << "coarsity " << inp.coarsity << std::endl;
898   std::cout << "area " << inp.area << std::endl;
899   std::cout << "min distance " << inp.min_distance << std::endl;
900   std::cout << "der order " << inp.der_order << std::endl;
901   std::cout << "scaling " << inp.scaling << std::endl;
902   std::cout << "radii set " << std::string(inp.radii_set) << std::endl;
903   std::cout << "restart name " << std::string(inp.restart_name) << std::endl;
904   std::cout << "min radius " << inp.min_radius << std::endl;
905   std::cout << "solver type " << std::string(inp.solver_type) << std::endl;
906   std::cout << "solvent " << std::string(inp.solvent) << std::endl;
907   std::cout << "equation type " << std::string(inp.equation_type) << std::endl;
908   std::cout << "correction " << inp.correction << std::endl;
909   std::cout << "probe_radius " << inp.probe_radius << std::endl;
910   std::cout << "inside type " << std::string(inp.inside_type) << std::endl;
911   std::cout << "outside type " << std::string(inp.outside_type) << std::endl;
912   std::cout << "epsilon outside " << inp.outside_epsilon << std::endl;
913 }
914 } // namespace detail
915 } // namespace pcm
916