OpenMD 3.2
Molecular Dynamics in the Open
Loading...
Searching...
No Matches
CubicSpline.cpp
1/*
2 * Copyright (c) 2004-present, The University of Notre Dame. All rights
3 * reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright notice,
9 * this list of conditions and the following disclaimer.
10 *
11 * 2. Redistributions in binary form must reproduce the above copyright notice,
12 * this list of conditions and the following disclaimer in the documentation
13 * and/or other materials provided with the distribution.
14 *
15 * 3. Neither the name of the copyright holder nor the names of its
16 * contributors may be used to endorse or promote products derived from
17 * this software without specific prior written permission.
18 *
19 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
23 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29 * POSSIBILITY OF SUCH DAMAGE.
30 *
31 * SUPPORT OPEN SCIENCE! If you use OpenMD or its source code in your
32 * research, please cite the following paper when you publish your work:
33 *
34 * [1] Drisko et al., J. Open Source Softw. 9, 7004 (2024).
35 *
36 * Good starting points for code and simulation methodology are:
37 *
38 * [2] Meineke, et al., J. Comp. Chem. 26, 252-271 (2005).
39 * [3] Fennell & Gezelter, J. Chem. Phys. 124, 234104 (2006).
40 * [4] Sun, Lin & Gezelter, J. Chem. Phys. 128, 234107 (2008).
41 * [5] Vardeman, Stocker & Gezelter, J. Chem. Theory Comput. 7, 834 (2011).
42 * [6] Kuang & Gezelter, Mol. Phys., 110, 691-701 (2012).
43 * [7] Lamichhane, Gezelter & Newman, J. Chem. Phys. 141, 134109 (2014).
44 * [8] Bhattarai, Newman & Gezelter, Phys. Rev. B 99, 094106 (2019).
45 * [9] Drisko & Gezelter, J. Chem. Theory Comput. 20, 4986-4997 (2024).
46 */
47
48#include "math/CubicSpline.hpp"
49
50#include <algorithm>
51#include <cassert>
52#include <cmath>
53#include <cstdio>
54#include <numeric>
55
56namespace OpenMD {
57
58 CubicSpline::CubicSpline() : isUniform(true), generated(false) {
59 x_.clear();
60 y_.clear();
61 }
62
63 void CubicSpline::addPoint(const RealType xp, const RealType yp) {
64 x_.push_back(xp);
65 y_.push_back(yp);
66 }
67
68 void CubicSpline::addPoints(const std::vector<RealType>& xps,
69 const std::vector<RealType>& yps) {
70 assert(xps.size() == yps.size());
71
72 for (unsigned int i = 0; i < xps.size(); i++) {
73 x_.push_back(xps[i]);
74 y_.push_back(yps[i]);
75 }
76 }
77
78 void CubicSpline::generate() {
79 // Calculate coefficients defining a smooth cubic interpolatory spline.
80 //
81 // class values constructed:
82 // n = number of data_ points.
83 // x_ = vector of independent variable values
84 // y_ = vector of dependent variable values
85 // b = vector of S'(x_[i]) values.
86 // c = vector of S"(x_[i])/2 values.
87 // d = vector of S'''(x_[i]+)/6 values (i < n).
88 // Local variables:
89
90 RealType fp1, fpn, p;
91 RealType h(0.0);
92
93 // make sure the sizes match
94
95 int n = x_.size();
96 b.resize(n);
97 c.resize(n);
98 d.resize(n);
99
100 // make sure we are monotonically increasing in x:
101
102 bool sorted = true;
103
104 for (int i = 1; i < n; i++) {
105 if ((x_[i] - x_[i - 1]) <= 0.0) sorted = false;
106 }
107
108 // sort if necessary
109
110 if (!sorted) {
111 std::vector<int> p = sort_permutation(x_);
112 x_ = apply_permutation(x_, p);
113 y_ = apply_permutation(y_, p);
114 }
115
116 // Calculate coefficients for the tridiagonal system: store
117 // sub-diagonal in B, diagonal in D, difference quotient in C.
118
119 b[0] = x_[1] - x_[0];
120 c[0] = (y_[1] - y_[0]) / b[0];
121
122 if (n == 2) {
123 // Assume the derivatives at both endpoints are zero. Another
124 // assumption could be made to have a linear interpolant between
125 // the two points. In that case, the b coefficients below would be
126 // (y_[1] - y_[0]) / (x_[1] - x_[0])
127 // and the c and d coefficients would both be zero.
128 b[0] = 0.0;
129 c[0] = -3.0 * pow((y_[1] - y_[0]) / (x_[1] - x_[0]), 2);
130 d[0] = -2.0 * pow((y_[1] - y_[0]) / (x_[1] - x_[0]), 3);
131 b[1] = b[0];
132 c[1] = 0.0;
133 d[1] = 0.0;
134 dx = 1.0 / (x_[1] - x_[0]);
135 isUniform = true;
136 generated = true;
137 return;
138 }
139
140 d[0] = 2.0 * b[0];
141
142 for (int i = 1; i < n - 1; i++) {
143 b[i] = x_[i + 1] - x_[i];
144 if (fabs(b[i] - b[0]) / b[0] > 1.0e-5) isUniform = false;
145 c[i] = (y_[i + 1] - y_[i]) / b[i];
146 d[i] = 2.0 * (b[i] + b[i - 1]);
147 }
148
149 d[n - 1] = 2.0 * b[n - 2];
150
151 // Calculate estimates for the end slopes using polynomials
152 // that interpolate the data_ nearest the end.
153
154 fp1 = c[0] - b[0] * (c[1] - c[0]) / (b[0] + b[1]);
155 if (n > 3)
156 fp1 = fp1 +
157 b[0] *
158 ((b[0] + b[1]) * (c[2] - c[1]) / (b[1] + b[2]) - c[1] + c[0]) /
159 (x_[3] - x_[0]);
160
161 fpn = c[n - 2] + b[n - 2] * (c[n - 2] - c[n - 3]) / (b[n - 3] + b[n - 2]);
162
163 if (n > 3)
164 fpn = fpn + b[n - 2] *
165 (c[n - 2] - c[n - 3] -
166 (b[n - 3] + b[n - 2]) * (c[n - 3] - c[n - 4]) /
167 (b[n - 3] + b[n - 4])) /
168 (x_[n - 1] - x_[n - 4]);
169
170 // Calculate the right hand side and store it in C.
171
172 c[n - 1] = 3.0 * (fpn - c[n - 2]);
173 for (int i = n - 2; i > 0; i--)
174 c[i] = 3.0 * (c[i] - c[i - 1]);
175 c[0] = 3.0 * (c[0] - fp1);
176
177 // Solve the tridiagonal system.
178
179 for (int k = 1; k < n; k++) {
180 p = b[k - 1] / d[k - 1];
181 d[k] = d[k] - p * b[k - 1];
182 c[k] = c[k] - p * c[k - 1];
183 }
184
185 c[n - 1] = c[n - 1] / d[n - 1];
186
187 for (int k = n - 2; k >= 0; k--)
188 c[k] = (c[k] - b[k] * c[k + 1]) / d[k];
189
190 // Calculate the coefficients defining the spline.
191
192 for (int i = 0; i < n - 1; i++) {
193 h = x_[i + 1] - x_[i];
194 d[i] = (c[i + 1] - c[i]) / (3.0 * h);
195 b[i] = (y_[i + 1] - y_[i]) / h - h * (c[i] + h * d[i]);
196 }
197
198 b[n - 1] = b[n - 2] + h * (2.0 * c[n - 2] + h * 3.0 * d[n - 2]);
199
200 if (isUniform) dx = 1.0 / (x_[1] - x_[0]);
201
202 generated = true;
203 return;
204 }
205
206 RealType CubicSpline::getValueAt(const RealType& t) {
207 if (!generated) generate();
208
209 int n = x_.size();
210 int j;
211 RealType dt;
212
213 if (isUniform) {
214 j = int((t - x_[0]) * dx);
215 } else {
216 j = n - 1;
217 for (int i = 0; i < n; i++) {
218 if (t < x_[i]) {
219 j = i - 1;
220 break;
221 }
222 }
223 }
224
225 j = std::clamp(j, 0, n - 1);
226
227 dt = t - x_[j];
228 return y_[j] + dt * (b[j] + dt * (c[j] + dt * d[j]));
229 }
230
231 void CubicSpline::getValueAt(const RealType& t, RealType& v) {
232 if (!generated) generate();
233
234 int n = x_.size();
235 int j;
236 RealType dt;
237
238 if (isUniform) {
239 j = int((t - x_[0]) * dx);
240 } else {
241 j = n - 1;
242 for (int i = 0; i < n; i++) {
243 if (t < x_[i]) {
244 j = i - 1;
245 break;
246 }
247 }
248 }
249
250 j = std::clamp(j, 0, n - 1);
251
252 dt = t - x_[j];
253 v = y_[j] + dt * (b[j] + dt * (c[j] + dt * d[j]));
254 }
255
256 std::pair<RealType, RealType> CubicSpline::getLimits() {
257 if (!generated) generate();
258 return make_pair(x_.front(), x_.back());
259 }
260
261 RealType CubicSpline::getSpacing() {
262 if (!generated) generate();
263 assert(isUniform);
264 if (isUniform)
265 return 1.0 / dx;
266 else
267 return 0.0;
268 }
269
270 void CubicSpline::getValueAndDerivativeAt(const RealType& t, RealType& v,
271 RealType& dv) {
272 if (!generated) generate();
273
274 int n = x_.size();
275 int j;
276 RealType dt;
277
278 if (isUniform) {
279 j = int((t - x_[0]) * dx);
280 } else {
281 j = n - 1;
282 for (int i = 0; i < n; i++) {
283 if (t < x_[i]) {
284 j = i - 1;
285 break;
286 }
287 }
288 }
289
290 j = std::clamp(j, 0, n - 1);
291
292 dt = t - x_[j];
293 v = y_[j] + dt * (b[j] + dt * (c[j] + dt * d[j]));
294 dv = b[j] + dt * (2.0 * c[j] + 3.0 * dt * d[j]);
295 }
296
297 std::vector<int> CubicSpline::sort_permutation(
298 const std::vector<double>& v) const {
299 std::vector<int> p(v.size());
300
301 std::iota(p.begin(), p.end(), 0);
302 std::sort(p.begin(), p.end(), [&v](int a, int b) { return (v[a] < v[b]); });
303
304 return p;
305 }
306
307 std::vector<RealType> CubicSpline::apply_permutation(
308 const std::vector<RealType>& v, const std::vector<int>& p) const {
309 std::size_t n = p.size();
310 std::vector<RealType> sorted_vec(n);
311
312 for (std::size_t i = 0; i < n; ++i) {
313 sorted_vec[i] = v[p[i]];
314 }
315
316 return sorted_vec;
317 }
318} // namespace OpenMD
This basic Periodic Table class was originally taken from the data.cpp file in OpenBabel.