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 "GePolCavity.hpp"
26
27 #include <algorithm>
28 #include <cctype>
29 #include <fstream>
30 #include <iomanip>
31 #include <iostream>
32 #include <sstream>
33 #include <string>
34 #include <vector>
35
36 #include <unistd.h>
37
38 #include "Config.hpp"
39
40 #include <Eigen/Core>
41
42 #include "CavityData.hpp"
43 #include "utils/MathUtils.hpp"
44 #include "utils/Sphere.hpp"
45 #include "utils/Symmetry.hpp"
46
47 namespace pcm {
48 namespace cavity {
GePolCavity(const Molecule & molec,double a,double pr,double minR,const std::string & suffix)49 GePolCavity::GePolCavity(const Molecule & molec,
50 double a,
51 double pr,
52 double minR,
53 const std::string & suffix)
54 : ICavity(molec), averageArea(a), probeRadius(pr), minimalRadius(minR) {
55 TIMER_ON("GePolCavity::build from Molecule object");
56 build(suffix, 50000, 1000, 100000);
57 TIMER_OFF("GePolCavity::build from Molecule object");
58 }
59
GePolCavity(const Sphere & sph,double a,double pr,double minR,const std::string & suffix)60 GePolCavity::GePolCavity(const Sphere & sph,
61 double a,
62 double pr,
63 double minR,
64 const std::string & suffix)
65 : ICavity(sph), averageArea(a), probeRadius(pr), minimalRadius(minR) {
66 TIMER_ON("GePolCavity::build from single sphere");
67 build(suffix, 50000, 1000, 100000);
68 TIMER_OFF("GePolCavity::build from single sphere");
69 }
70
GePolCavity(const std::vector<Sphere> & sph,double a,double pr,double minR,const std::string & suffix)71 GePolCavity::GePolCavity(const std::vector<Sphere> & sph,
72 double a,
73 double pr,
74 double minR,
75 const std::string & suffix)
76 : ICavity(sph), averageArea(a), probeRadius(pr), minimalRadius(minR) {
77 TIMER_ON("GePolCavity::build from list of spheres");
78 build(suffix, 50000, 1000, 100000);
79 TIMER_OFF("GePolCavity::build from list of spheres");
80 }
81
build(const std::string & suffix,int maxts,int maxsph,int maxvert)82 void GePolCavity::build(const std::string & suffix,
83 int maxts,
84 int maxsph,
85 int maxvert) {
86
87 // This is a wrapper for the generatecavity_cpp function defined in the Fortran
88 // code PEDRA.
89 // Here we allocate the necessary arrays to be passed to PEDRA, in particular we
90 // allow
91 // for the insertion of additional spheres as in the most general formulation of
92 // the
93 // GePol algorithm.
94
95 double * xtscor = new double[maxts];
96 double * ytscor = new double[maxts];
97 double * ztscor = new double[maxts];
98 double * ar = new double[maxts];
99 double * xsphcor = new double[maxts];
100 double * ysphcor = new double[maxts];
101 double * zsphcor = new double[maxts];
102 double * rsph = new double[maxts];
103 int * nvert = new int[maxts];
104 double * vert = new double[30 * maxts];
105 double * centr = new double[30 * maxts];
106 int * isphe = new int[maxts];
107
108 // Clean-up possible heap-crap
109 std::fill_n(xtscor, maxts, 0.0);
110 std::fill_n(ytscor, maxts, 0.0);
111 std::fill_n(ztscor, maxts, 0.0);
112 std::fill_n(ar, maxts, 0.0);
113 std::fill_n(xsphcor, maxts, 0.0);
114 std::fill_n(ysphcor, maxts, 0.0);
115 std::fill_n(zsphcor, maxts, 0.0);
116 std::fill_n(rsph, maxts, 0.0);
117 std::fill_n(nvert, maxts, 0);
118 std::fill_n(vert, 30 * maxts, 0.0);
119 std::fill_n(centr, 30 * maxts, 0.0);
120 std::fill_n(isphe, maxts, 0);
121
122 int nts = 0;
123 int ntsirr = 0;
124
125 // If there's an overflow in the number of spheres PEDRA will die.
126 // The maximum number of spheres in PEDRA is set to 200 (primitive+additional)
127 // so the integer here declared is just to have enough space C++ side to pass
128 // everything back.
129 int maxAddedSpheres = 200;
130
131 // Allocate vectors of size equal to nSpheres + maxAddedSpheres where
132 // maxAddedSpheres is the
133 // maximum number of spheres we allow the algorithm to add to our original set.
134 // If this number is exceeded, then the algorithm crashes (should look into
135 // this...)
136 // After the cavity is generated we will update ALL the class data members, both
137 // related
138 // to spheres and finite elements so that the cavity is fully formed.
139
140 Eigen::VectorXd xv = Eigen::VectorXd::Zero(nSpheres_ + maxAddedSpheres);
141 Eigen::VectorXd yv = Eigen::VectorXd::Zero(nSpheres_ + maxAddedSpheres);
142 Eigen::VectorXd zv = Eigen::VectorXd::Zero(nSpheres_ + maxAddedSpheres);
143 Eigen::VectorXd radii_scratch =
144 Eigen::VectorXd::Zero(nSpheres_ + maxAddedSpheres); // Not to be confused with
145 // the data member
146 // inherited from ICavity!!!
147
148 for (int i = 0; i < nSpheres_; ++i) {
149 for (int j = 0; j < 3; ++j) {
150 xv(i) = sphereCenter_(0, i);
151 yv(i) = sphereCenter_(1, i);
152 zv(i) = sphereCenter_(2, i);
153 }
154 radii_scratch(i) = sphereRadius_(i);
155 }
156
157 double * xe = xv.data();
158 double * ye = yv.data();
159 double * ze = zv.data();
160
161 double * rin = radii_scratch.data();
162 double * mass = new double[molecule_.nAtoms()];
163 for (size_t i = 0; i < molecule_.nAtoms(); ++i) {
164 mass[i] = molecule_.masses(i);
165 }
166
167 addedSpheres = 0;
168 // Number of generators and generators of the point group
169 int nr_gen = molecule_.pointGroup().nrGenerators();
170 int gen1 = molecule_.pointGroup().generators(0);
171 int gen2 = molecule_.pointGroup().generators(1);
172 int gen3 = molecule_.pointGroup().generators(2);
173
174 std::stringstream pedra;
175 pedra << "PEDRA.OUT_" << suffix << "_" << getpid();
176 int len_f_pedra = std::strlen(pedra.str().c_str());
177 // Go PEDRA, Go!
178 TIMER_ON("GePolCavity::generatecavity_cpp");
179 generatecavity_cpp(&maxts,
180 &maxsph,
181 &maxvert,
182 xtscor,
183 ytscor,
184 ztscor,
185 ar,
186 xsphcor,
187 ysphcor,
188 zsphcor,
189 rsph,
190 &nts,
191 &ntsirr,
192 &nSpheres_,
193 &addedSpheres,
194 xe,
195 ye,
196 ze,
197 rin,
198 mass,
199 &averageArea,
200 &probeRadius,
201 &minimalRadius,
202 &nr_gen,
203 &gen1,
204 &gen2,
205 &gen3,
206 nvert,
207 vert,
208 centr,
209 isphe,
210 pedra.str().c_str(),
211 &len_f_pedra);
212 TIMER_OFF("GePolCavity::generatecavity_cpp");
213
214 // The "intensive" part of updating the spheres related class data members will be
215 // of course
216 // executed iff addedSpheres != 0
217 if (addedSpheres != 0) {
218 // Save the number of original spheres
219 int orig = nSpheres_;
220 // Update the nSpheres
221 nSpheres_ += addedSpheres;
222 // Resize sphereRadius and sphereCenter...
223 sphereRadius_.resize(nSpheres_);
224 sphereCenter_.resize(Eigen::NoChange, nSpheres_);
225 // Transfer radii and centers.
226 // Eigen has no push_back function, so we need to traverse all the spheres...
227 sphereRadius_ = radii_scratch.head(nSpheres_);
228 for (int i = 0; i < nSpheres_; ++i) {
229 sphereCenter_(0, i) = xv(i);
230 sphereCenter_(1, i) = yv(i);
231 sphereCenter_(2, i) = zv(i);
232 }
233 // Now grow the vector<Sphere> containing the list of spheres
234 for (int i = orig; i < nSpheres_; ++i) {
235 spheres_.push_back(Sphere(sphereCenter_.col(i), sphereRadius_(i)));
236 }
237 }
238
239 // Now take care of updating the rest of the cavity info.
240 // We prune the list of tesserae coming out of PEDRA to remove the ones that
241 // have area smaller than 1.0e-4 AU^2 these finite elements can breake
242 // symmetric positive-definiteness of the S matrix.
243 Eigen::MatrixXd centers = Eigen::MatrixXd::Zero(3, nts);
244 Eigen::MatrixXd sphereCenters = Eigen::MatrixXd::Zero(3, nts);
245 Eigen::VectorXd areas = Eigen::VectorXd::Zero(nts);
246 Eigen::VectorXd radii = Eigen::VectorXd::Zero(nts);
247 // Filtering array
248 Eigen::Matrix<bool, 1, Eigen::Dynamic> filter =
249 Eigen::Matrix<bool, 1, Eigen::Dynamic>::Zero(nts);
250 PCMSolverIndex retval = 0;
251 for (PCMSolverIndex i = 0; i < nts; ++i) {
252 if (std::abs(ar[i]) >= 1.0e-04) {
253 retval += 1;
254 centers.col(i) =
255 (Eigen::Vector3d() << xtscor[i], ytscor[i], ztscor[i]).finished();
256 sphereCenters.col(i) =
257 (Eigen::Vector3d() << xsphcor[i], ysphcor[i], zsphcor[i]).finished();
258 areas(i) = ar[i];
259 filter(i) = true;
260 radii(i) = rsph[i];
261 }
262 }
263 PCMSOLVER_ASSERT(filter.count() == retval);
264 // Resize data members and fill them up starting from the pruned arrays
265 // We first build a mask array, for the indices of the nonzero elements
266 elementCenter_ = utils::prune_zero_columns(centers, filter);
267 elementSphereCenter_ = utils::prune_zero_columns(sphereCenters, filter);
268 elementArea_ = utils::prune_vector(areas, filter);
269 elementRadius_ = utils::prune_vector(radii, filter);
270 nElements_ = retval;
271 pruned_ = nts - nElements_;
272 nIrrElements_ =
273 static_cast<PCMSolverIndex>(nElements_ / molecule_.pointGroup().nrIrrep());
274
275 // Check that no points are overlapping exactly
276 // Do not perform float comparisons column by column.
277 // Instead form differences between columns and evaluate if they differ
278 // from zero by more than a fixed threshold.
279 // The indices of the equal elements are gathered in a std::pair and saved into a
280 // std::vector
281 double threshold = 1.0e-12;
282 std::vector<std::pair<PCMSolverIndex, PCMSolverIndex> > equal_elements;
283 for (PCMSolverIndex i = 0; i < nElements_; ++i) {
284 for (PCMSolverIndex j = i + 1; j < nElements_; ++j) {
285 Eigen::Vector3d difference = elementCenter_.col(i) - elementCenter_.col(j);
286 if (difference.isZero(threshold)) {
287 equal_elements.push_back(std::make_pair(i, j));
288 }
289 }
290 }
291 if (equal_elements.size() != 0) {
292 // Not sure that printing the list of pairs is actually of any help...
293 std::string list_of_pairs;
294 for (PCMSolverIndex i = 0; i < equal_elements.size(); ++i) {
295 list_of_pairs += "(" + std::to_string(equal_elements[i].first) + ", " +
296 std::to_string(equal_elements[i].second) + ")\n";
297 }
298 // Prepare the error message:
299 std::string message = std::to_string(equal_elements.size()) +
300 " cavity finite element centers overlap exactly!\n" +
301 list_of_pairs;
302 PCMSOLVER_ERROR(message);
303 }
304 // Calculate normal vectors
305 elementNormal_.resize(Eigen::NoChange, nElements_);
306 elementNormal_ = elementCenter_ - elementSphereCenter_;
307 for (PCMSolverIndex i = 0; i < nElements_; ++i) {
308 elementNormal_.col(i) /= elementNormal_.col(i).norm();
309 }
310
311 // Fill elements_ vector
312 for (PCMSolverIndex i = 0; i < nElements_; ++i) {
313 PCMSolverIndex i_off = i + 1;
314 bool irr = false;
315 // PEDRA puts the irreducible tesserae first
316 if (i < nIrrElements_)
317 irr = true;
318 Sphere sph(elementSphereCenter_.col(i), elementRadius_(i));
319 int nv = nvert[i];
320 int isph = isphe[i]; // Back to C++ indexing starting from 0
321 Eigen::Matrix3Xd vertices, arcs;
322 vertices.resize(Eigen::NoChange, nv);
323 arcs.resize(Eigen::NoChange, nv);
324 // Populate vertices and arcs
325 for (int j = 0; j < nv; ++j) {
326 PCMSolverIndex j_off = (j + 1) * nElements_ - 1;
327 for (PCMSolverIndex k = 0; k < 3; ++k) {
328 PCMSolverIndex k_off = (k + 1) * nElements_ * nv;
329 PCMSolverIndex offset = i_off + j_off + k_off;
330 vertices(k, j) = vert[offset];
331 arcs(k, j) = centr[offset];
332 }
333 }
334 elements_.push_back(Element(nv,
335 isph,
336 elementArea_(i),
337 elementCenter_.col(i),
338 elementNormal_.col(i),
339 irr,
340 sph,
341 vertices,
342 arcs));
343 }
344
345 // Clean-up
346 delete[] xtscor;
347 delete[] ytscor;
348 delete[] ztscor;
349 delete[] ar;
350 delete[] xsphcor;
351 delete[] ysphcor;
352 delete[] zsphcor;
353 delete[] rsph;
354 delete[] nvert;
355 delete[] vert;
356 delete[] centr;
357 delete[] mass;
358 delete[] isphe;
359
360 built = true;
361
362 writeOFF(suffix);
363 }
364
writeOFF(const std::string & suffix)365 void GePolCavity::writeOFF(const std::string & suffix) {
366 std::stringstream off;
367 off << "cavity.off_" << suffix << "_" << getpid();
368
369 std::ofstream fout;
370 fout.open(off.str().c_str());
371
372 int numv = 0;
373 for (PCMSolverIndex i = 0; i < nElements_; ++i) {
374 numv += elements_[i].nVertices();
375 }
376 fout << "COFF" << std::endl;
377 fout << numv << " " << nElements_ << " " << numv << std::endl;
378
379 int k = 0;
380 double c1, c2, c3;
381 Eigen::MatrixXi ivts = Eigen::MatrixXi::Zero(nElements_, 10);
382 for (PCMSolverIndex i = 0; i < nElements_; ++i) {
383 if (i == 0)
384 fout << "# Sphere number " << elements_[i].iSphere() << std::endl;
385 c1 = 1.0;
386 c2 = 1.0;
387 c3 = 1.0;
388 for (int j = 0; j < elements_[i].nVertices(); ++j) {
389 ivts(i, j) = k;
390 k = k + 1;
391 fout << std::fixed << std::left << std::setfill('0') << std::setprecision(14)
392 << elements_[i].vertices()(0, j) << " "
393 << elements_[i].vertices()(1, j) << " "
394 << elements_[i].vertices()(2, j) << " " << std::fixed << std::left
395 << std::setfill('0') << std::setprecision(4) << c1 << " " << c2
396 << " " << c3 << " " << 0.75 << " "
397 << " # Tess " << (i + 1) << std::endl;
398 }
399 }
400 for (PCMSolverIndex i = 0; i < nElements_; ++i) {
401 fout << elements_[i].nVertices() << " ";
402 for (int j = 0; j < elements_[i].nVertices(); ++j) {
403 fout << ivts(i, j) << " ";
404 }
405 fout << std::endl;
406 }
407
408 fout.close();
409 }
410
printCavity(std::ostream & os)411 std::ostream & GePolCavity::printCavity(std::ostream & os) {
412 os << "Cavity type: GePol" << std::endl;
413 os << "Average tesserae area = " << averageArea * bohr2ToAngstrom2() << " Ang^2"
414 << std::endl;
415 os << "Solvent probe radius = " << probeRadius * bohrToAngstrom() << " Ang"
416 << std::endl;
417 os << "Number of spheres = " << nSpheres_
418 << " [initial = " << nSpheres_ - addedSpheres << "; added = " << addedSpheres
419 << "]" << std::endl;
420 os << "Number of finite elements = " << nElements_ << " (" << pruned_ << " pruned)"
421 << std::endl;
422 os << "Number of irreducible finite elements = " << nIrrElements_ << std::endl;
423 os << "============ Spheres list (in Angstrom)" << std::endl;
424 os << " Sphere on Radius Alpha X Y Z \n";
425 os << "-------- ---- -------- ------- ----------- ----------- -----------\n";
426 // Print original set of spheres
427 int original = nSpheres_ - addedSpheres;
428 Eigen::IOFormat CleanFmt(6, Eigen::DontAlignCols, " ", "\n", "", "");
429 for (int i = 0; i < original; ++i) {
430 os << std::setw(4) << i + 1;
431 os << " " << molecule_.atoms()[i].symbol << " ";
432 os << std::fixed << std::setprecision(4)
433 << molecule_.spheres()[i].radius * bohrToAngstrom();
434 os << std::fixed << std::setprecision(2) << " "
435 << molecule_.atoms()[i].radiusScaling << " ";
436 os << (molecule_.spheres()[i].center.transpose() * bohrToAngstrom())
437 .format(CleanFmt);
438 os << std::endl;
439 }
440 // Print added spheres
441 for (int j = 0; j < addedSpheres; ++j) {
442 int idx = original + j;
443 os << std::setw(4) << idx + 1;
444 os << " Du ";
445 os << std::fixed << std::setprecision(4)
446 << sphereRadius_(idx) * bohrToAngstrom();
447 os << std::fixed << std::setprecision(2) << " 1.00";
448 os << (sphereCenter_.col(idx).transpose() * bohrToAngstrom()).format(CleanFmt);
449 os << std::endl;
450 }
451 return os;
452 }
453
createGePolCavity(const CavityData & data)454 ICavity * createGePolCavity(const CavityData & data) {
455 return new GePolCavity(
456 data.molecule, data.area, data.probeRadius, data.minimalRadius);
457 }
458 } // namespace cavity
459 } // namespace pcm
460