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