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