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 "Molecule.hpp"
25 
26 #include <algorithm>
27 #include <cmath>
28 #include <iomanip>
29 #include <iostream>
30 #include <string>
31 #include <vector>
32 
33 #include "Config.hpp"
34 
35 #include <Eigen/Core>
36 #include <Eigen/Eigenvalues>
37 
38 #include "Atom.hpp"
39 #include "MathUtils.hpp"
40 #include "Symmetry.hpp"
41 #include "cavity/Element.hpp"
42 
43 namespace pcm {
44 using cavity::Element;
45 using utils::hermitivitize;
46 
Molecule(int nat,const Eigen::VectorXd & chg,const Eigen::VectorXd & m,const Eigen::Matrix3Xd & geo,const std::vector<Atom> & at,const std::vector<Sphere> & sph)47 Molecule::Molecule(int nat,
48                    const Eigen::VectorXd & chg,
49                    const Eigen::VectorXd & m,
50                    const Eigen::Matrix3Xd & geo,
51                    const std::vector<Atom> & at,
52                    const std::vector<Sphere> & sph)
53     : nAtoms_(nat),
54       charges_(chg),
55       masses_(m),
56       geometry_(geo),
57       atoms_(at),
58       spheres_(sph) {
59   rotor_ = findRotorType();
60 }
61 
Molecule(int nat,const Eigen::VectorXd & chg,const Eigen::VectorXd & m,const Eigen::Matrix3Xd & geo,const std::vector<Atom> & at,const std::vector<Sphere> & sph,int nr_gen,std::array<int,3> gen)62 Molecule::Molecule(int nat,
63                    const Eigen::VectorXd & chg,
64                    const Eigen::VectorXd & m,
65                    const Eigen::Matrix3Xd & geo,
66                    const std::vector<Atom> & at,
67                    const std::vector<Sphere> & sph,
68                    int nr_gen,
69                    std::array<int, 3> gen)
70     : nAtoms_(nat),
71       charges_(chg),
72       masses_(m),
73       geometry_(geo),
74       atoms_(at),
75       spheres_(sph),
76       pointGroup_(nr_gen, gen) {
77   rotor_ = findRotorType();
78 }
79 
Molecule(int nat,const Eigen::VectorXd & chg,const Eigen::VectorXd & m,const Eigen::Matrix3Xd & geo,const std::vector<Atom> & at,const std::vector<Sphere> & sph,const Symmetry & pg)80 Molecule::Molecule(int nat,
81                    const Eigen::VectorXd & chg,
82                    const Eigen::VectorXd & m,
83                    const Eigen::Matrix3Xd & geo,
84                    const std::vector<Atom> & at,
85                    const std::vector<Sphere> & sph,
86                    const Symmetry & pg)
87     : nAtoms_(nat),
88       charges_(chg),
89       masses_(m),
90       geometry_(geo),
91       atoms_(at),
92       spheres_(sph),
93       pointGroup_(pg) {
94   rotor_ = findRotorType();
95 }
96 
Molecule(const std::vector<Sphere> & sph)97 Molecule::Molecule(const std::vector<Sphere> & sph)
98     : nAtoms_(sph.size()), spheres_(sph) {
99   charges_ = Eigen::VectorXd::Ones(nAtoms_);
100   masses_.resize(nAtoms_);
101   geometry_.resize(Eigen::NoChange, nAtoms_);
102   for (size_t i = 0; i < nAtoms_; ++i) {
103     masses_(i) = spheres_[i].radius;
104     geometry_.col(i) = spheres_[i].center;
105     double charge = charges_(i);
106     double mass = masses_(i);
107     atoms_.push_back(Atom("Dummy", "Du", charge, mass, mass, geometry_.col(i)));
108   }
109   rotor_ = findRotorType();
110 }
111 
Molecule(const Molecule & other)112 Molecule::Molecule(const Molecule & other) { *this = other; }
113 
centerOfMass()114 Eigen::Vector3d Molecule::centerOfMass() {
115   Eigen::Vector3d com;
116   com << 0.0, 0.0, 0.0;
117   for (size_t i = 0; i < nAtoms_; ++i) {
118     com += masses_(i) * atoms_[i].position;
119   }
120   com *= 1.0 / masses_.sum();
121   return com;
122 }
123 
inertiaTensor()124 Eigen::Matrix3d Molecule::inertiaTensor() {
125   Eigen::Matrix3d inertia = Eigen::Matrix3d::Zero();
126 
127   for (size_t i = 0; i < nAtoms_; ++i) {
128     // Diagonal
129     inertia(0, 0) += masses_(i) * (geometry_(1, i) * geometry_(1, i) +
130                                    geometry_(2, i) * geometry_(2, i));
131     inertia(1, 1) += masses_(i) * (geometry_(0, i) * geometry_(0, i) +
132                                    geometry_(2, i) * geometry_(2, i));
133     inertia(2, 2) += masses_(i) * (geometry_(0, i) * geometry_(0, i) +
134                                    geometry_(1, i) * geometry_(1, i));
135 
136     // Off-diagonal
137     inertia(0, 1) -= masses_(i) * (geometry_(0, i) * geometry_(1, i));
138     inertia(0, 2) -= masses_(i) * (geometry_(0, i) * geometry_(2, i));
139     inertia(1, 2) -= masses_(i) * (geometry_(1, i) * geometry_(2, i));
140   }
141   // Now symmetrize
142   hermitivitize(inertia);
143 
144   // Check elements for a numerical zero and make it a hard zero
145   for (int i = 0; i < 3; ++i) {
146     for (int j = 0; j < 3; ++j) {
147       if (fabs(inertia(i, j)) < 1.0e-14) {
148         inertia(i, j) = 0.0;
149       }
150     }
151   }
152 
153   return inertia;
154 }
155 
findRotorType()156 rotorType Molecule::findRotorType() {
157   rotorType type;
158   if (nAtoms_ == 1) {
159     type = rtAtom;
160   } else {
161     // Get inertia tensor
162     Eigen::Matrix3d inertia = inertiaTensor();
163     // Diagonalize inertia tensor V^t * I * V
164     Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> eigenSolver(inertia);
165     if (eigenSolver.info() != Eigen::Success)
166       abort();
167     // Determine the degeneracy of the eigenvalues.
168     int deg = 0;
169     double tmp, abs, rel;
170     for (int i = 0; i < 2; ++i) {
171       for (int j = i + 1; j < 3 && deg < 2; ++j) { // Check i and j != i
172         abs = fabs(eigenSolver.eigenvalues()[i] - eigenSolver.eigenvalues()[j]);
173         tmp = eigenSolver.eigenvalues()[j]; // Because the eigenvalues are already in
174                                             // ascending order.
175         if (abs > 1.0e-14) {
176           rel = abs / tmp;
177         } else {
178           rel = 0.0;
179         }
180         if (rel < 1.0e-8) {
181           ++deg;
182         }
183       }
184     }
185     // Get the rotor type based on the degeneracy.
186     if (eigenSolver.eigenvalues()[0] == 0.0) {
187       type = rtLinear;
188     } else if (deg == 2) {
189       type = rtSpherical;
190     } else if (deg == 1) { // We do not distinguish between prolate and oblate.
191       type = rtSymmetric;
192     } else {
193       type = rtAsymmetric;
194     }
195   }
196 
197   return type;
198 }
199 
translate(const Eigen::Vector3d & translationVector)200 void Molecule::translate(const Eigen::Vector3d & translationVector) {
201   // Translate the geometry_ matrix and update the geometric data in atoms_.
202   for (size_t i = 0; i < nAtoms_; ++i) {
203     geometry_.col(i) -= translationVector;
204     Eigen::Vector3d tmp = geometry_.col(i);
205     atoms_[i].position = tmp;
206   }
207 }
208 
moveToCOM()209 void Molecule::moveToCOM() {
210   Eigen::Vector3d com = centerOfMass();
211   this->translate(com);
212 }
213 
rotate(const Eigen::Matrix3d & rotationMatrix)214 void Molecule::rotate(const Eigen::Matrix3d & rotationMatrix) {
215   // Rotate the geometry_ matrix and update the geometric data in atoms_.
216   geometry_ *=
217       rotationMatrix; // The power of Eigen: geometry_ = geometry_ * rotationMatrix;
218   for (size_t i = 0; i < nAtoms_; ++i) {
219     Eigen::Vector3d tmp = geometry_.col(i);
220     atoms_[i].position = tmp;
221   }
222 }
223 
moveToPAF()224 void Molecule::moveToPAF() {
225   Eigen::Matrix3d inertia = inertiaTensor();
226   // Diagonalize inertia tensor V^t * I * V
227   Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> eigenSolver(inertia);
228   if (eigenSolver.info() != Eigen::Success)
229     abort();
230   // Rotate to Principal Axes Frame
231   this->rotate(eigenSolver.eigenvectors());
232   std::cout << eigenSolver.eigenvalues() << std::endl;
233 }
234 
operator =(const Molecule & other)235 Molecule & Molecule::operator=(const Molecule & other) {
236   // Self assignment is bad
237   if (this == &other)
238     return *this;
239 
240   nAtoms_ = other.nAtoms_;
241   charges_ = other.charges_;
242   masses_ = other.masses_;
243   geometry_ = other.geometry_;
244   atoms_ = other.atoms_;
245   spheres_ = other.spheres_;
246   rotor_ = other.rotor_;
247   pointGroup_ = other.pointGroup_;
248 
249   return *this;
250 }
251 
operator <<(std::ostream & os,const Molecule & m)252 std::ostream & operator<<(std::ostream & os, const Molecule & m) {
253   if (m.nAtoms_ != 0) {
254     Eigen::IOFormat CleanFmt(6, Eigen::DontAlignCols, " ", "\n", "", "");
255     os << "                 Geometry (in Angstrom)" << std::endl;
256     os << "   Center            X             Y             Z     \n";
257     os << "------------   ------------  ------------  ------------\n";
258     for (size_t i = 0; i < m.nAtoms_; ++i) {
259       os << std::setw(12) << m.atoms_[i].symbol;
260       os << (m.geometry_.col(i).transpose() * bohrToAngstrom()).format(CleanFmt);
261       os << std::endl;
262     }
263     os << "Rotor type: " << rotorTypeList[m.rotor_];
264   } else {
265     os << "  No atoms in this molecule!" << std::endl;
266   }
267 
268   return os;
269 }
270 
computeMEP(const Molecule & mol,const std::vector<Element> & el)271 Eigen::VectorXd computeMEP(const Molecule & mol, const std::vector<Element> & el) {
272   Eigen::VectorXd mep = Eigen::VectorXd::Zero(el.size());
273   for (size_t i = 0; i < mol.nAtoms(); ++i) {
274     for (size_t j = 0; j < el.size(); ++j) {
275       double dist = (mol.geometry().col(i) - el[j].center()).norm();
276       mep(j) += mol.charges(i) / dist;
277     }
278   }
279   return mep;
280 }
281 
computeMEP(const Molecule & mol,const Eigen::Matrix3Xd & grid)282 Eigen::VectorXd computeMEP(const Molecule & mol, const Eigen::Matrix3Xd & grid) {
283   Eigen::VectorXd mep = Eigen::VectorXd::Zero(grid.cols());
284   for (size_t i = 0; i < mol.nAtoms(); ++i) {
285     for (int j = 0; j < grid.cols(); ++j) {
286       double dist = (mol.geometry().col(i) - grid.col(j)).norm();
287       mep(j) += mol.charges(i) / dist;
288     }
289   }
290   return mep;
291 }
292 
computeMEP(const std::vector<Element> & el,double charge,const Eigen::Vector3d & origin)293 Eigen::VectorXd computeMEP(const std::vector<Element> & el,
294                            double charge,
295                            const Eigen::Vector3d & origin) {
296   Eigen::VectorXd mep = Eigen::VectorXd::Zero(el.size());
297   for (size_t i = 0; i < el.size(); ++i) {
298     double dist = (origin - el[i].center()).norm();
299     mep(i) += charge / dist;
300   }
301   return mep;
302 }
303 
GaussEstimate(const Eigen::VectorXd & charges,double permittivity,double correction)304 double GaussEstimate(const Eigen::VectorXd & charges,
305                      double permittivity,
306                      double correction) {
307   return (-charges.sum() * (permittivity - 1) / (permittivity + correction));
308 }
309 } // namespace pcm
310