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 "Element.hpp"
25 
26 #include <cmath>
27 #include <vector>
28 
29 #include "Config.hpp"
30 
31 #include <Eigen/Core>
32 #include <Eigen/Geometry>
33 #include <Eigen/LU>
34 
35 #include "utils/MathUtils.hpp"
36 #include "utils/Sphere.hpp"
37 
38 namespace pcm {
39 namespace cavity {
spherical_polygon(Eigen::Vector3d & t_,Eigen::Vector3d & b_,std::vector<double> & theta,std::vector<double> & phi,std::vector<double> & phinumb,std::vector<int> & numb) const40 void Element::spherical_polygon(Eigen::Vector3d & t_,
41                                 Eigen::Vector3d & b_,
42                                 std::vector<double> & theta,
43                                 std::vector<double> & phi,
44                                 std::vector<double> & phinumb,
45                                 std::vector<int> & numb) const {
46   Eigen::Vector3d sph_center = sphere_.center;
47   double radius = sphere_.radius;
48   // Calculate the azimuthal and polar angles for the tessera vertices:
49   // we use the normal, tangent and bitangent as a local reference frame
50   for (int i = 0; i < nVertices_; ++i) {
51     Eigen::Vector3d vertex_normal = vertices_.col(i) - sph_center;
52     // The cosine of the polar angle is given as the dot product of the normal at the
53     // vertex and the
54     // normal at the tessera center: R\cos\theta
55     double cos_theta = vertex_normal.dot(normal_) / radius;
56     if (cos_theta >= 1.0)
57       cos_theta = 1.0;
58     if (cos_theta <= -1.0)
59       cos_theta = -1.0;
60     theta[i] = std::acos(cos_theta);
61     // The cosine of the azimuthal angle is given as the dot product of the normal at
62     // the vertex and the
63     // tangent at the tessera center divided by the sine of the polar angle:
64     // R\sin\theta\cos\phi
65     double cos_phi = vertex_normal.dot(t_) / (radius * std::sin(theta[i]));
66     if (cos_phi >= 1.0)
67       cos_phi = 1.0;
68     if (cos_phi <= -1.0)
69       cos_phi = -1.0;
70     phi[i] = std::acos(cos_phi);
71     // The sine of the azimuthal angle is given as the dot product of the normal at
72     // the vertex and the
73     // bitangent at the tessera center divided by the sine of the polar angle:
74     // R\sin\theta\sin\phi
75     double sin_phi = vertex_normal.dot(b_) / (radius * std::sin(theta[i]));
76     if (sin_phi <= 0.0)
77       phi[i] = 2 * M_PI - phi[i];
78   }
79   for (int i = 1; i < nVertices_; ++i) {
80     phi[i] = phi[i] - phi[0];
81     if (phi[i] < 0.0)
82       phi[i] = 2 * M_PI + phi[i];
83   }
84   // Rewrite tangent as linear combination of original tangent and bitangent
85   // then recalculate bitangent so that it's orthogonal to the tangent
86   t_ = t_ * std::cos(phi[0]) + b_ * std::sin(phi[0]);
87   b_ = normal_.cross(t_);
88   // Populate numb and phinumb arrays
89   phi[0] = 0.0;
90   numb[0] = 0;
91   numb[1] = 1;
92   phinumb[0] = phi[0];
93   phinumb[1] = phi[1];
94   for (int i = 2; i < nVertices_; ++i) { // This loop is 2-based
95     for (int j = 1; j < i; ++j) {        // This loop is 1-based
96       if (phi[i] < phinumb[j]) {
97         for (int k = 0; k < (i - j); ++k) {
98           numb[i - k] = numb[i - k - 1];
99           phinumb[i - k] = phinumb[i - k - 1];
100         }
101         numb[j] = i;
102         phinumb[j] = phi[i];
103         goto jump;
104       }
105     }
106     numb[i] = i;
107     phinumb[i] = phi[i];
108   jump:; // Do nothing...
109   }
110   numb[nVertices_] = numb[0];
111   phinumb[nVertices_] = 2 * M_PI;
112 }
113 
114 namespace detail {
tangent_and_bitangent(const Eigen::Vector3d & n_,Eigen::Vector3d & t_,Eigen::Vector3d & b_)115 void tangent_and_bitangent(const Eigen::Vector3d & n_,
116                            Eigen::Vector3d & t_,
117                            Eigen::Vector3d & b_) {
118   double rmin = 0.99;
119   double n0 = n_(0), n1 = n_(1), n2 = n_(2);
120   if (std::abs(n0) <= rmin) {
121     rmin = std::abs(n0);
122     t_(0) = 0.0;
123     t_(1) = -n2 / std::sqrt(1.0 - std::pow(n0, 2));
124     t_(2) = n1 / std::sqrt(1.0 - std::pow(n0, 2));
125   }
126   if (std::abs(n1) <= rmin) {
127     rmin = std::abs(n1);
128     t_(0) = n2 / std::sqrt(1.0 - std::pow(n1, 2));
129     t_(1) = 0.0;
130     t_(2) = -n0 / std::sqrt(1.0 - std::pow(n1, 2));
131   }
132   if (std::abs(n2) <= rmin) {
133     rmin = std::abs(n2);
134     t_(0) = n1 / std::sqrt(1.0 - std::pow(n2, 2));
135     t_(1) = -n0 / std::sqrt(1.0 - std::pow(n2, 2));
136     t_(2) = 0.0;
137   }
138   b_ = n_.cross(t_);
139   // Check that the calculated Frenet-Serret frame is left-handed (levogiro)
140   // by checking that the determinant of the matrix whose columns are the normal,
141   // tangent and bitangent vectors has determinant 1 (the system is orthonormal!)
142   Eigen::Matrix3d M;
143   M.col(0) = n_;
144   M.col(1) = t_;
145   M.col(2) = b_;
146   if (utils::sign(M.determinant()) != 1) {
147     PCMSOLVER_ERROR("Frenet-Serret local frame is not left-handed!");
148   }
149 }
150 } // namespace detail
151 } // namespace cavity
152 } // namespace pcm
153