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