Loading...
Searching...
No Matches
rbfspline.C
1/*
2 * This file is part of the Multivariate Splines library.
3 * Copyright (C) 2012 Bjarne Grimstad (bjarne.grimstad@gmail.com)
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8*/
9
10
11#include "rbfspline.h"
12#include "linearsolvers.h"
13#include <Eigen/Eigen>
14#include "IOstreams.H"
15#include "IOmanip.H"
16
17namespace SPLINTER
18{
19
20RBFSpline::RBFSpline(const DataTable& samples, RadialBasisFunctionType type,
21 double e)
22 : RBFSpline(samples, type, false)
23{
24}
25
26RBFSpline::RBFSpline(const DataTable& samples, RadialBasisFunctionType type,
27 DenseMatrix w, double e)
28 : samples(samples),
29 normalized(false),
30 precondition(false),
31 dim(samples.getNumVariables()),
32 numSamples(samples.getNumSamples())
33{
34 if (type == RadialBasisFunctionType::THIN_PLATE_SPLINE)
35 {
36 fn = std::shared_ptr<RadialBasisFunction>(new ThinPlateSpline());
37 fn->e = e;
38 }
39 else if (type == RadialBasisFunctionType::MULTIQUADRIC)
40 {
41 fn = std::shared_ptr<RadialBasisFunction>(new Multiquadric());
42 }
43 else if (type == RadialBasisFunctionType::INVERSE_QUADRIC)
44 {
45 fn = std::shared_ptr<RadialBasisFunction>(new InverseQuadric());
46 }
47 else if (type == RadialBasisFunctionType::INVERSE_MULTIQUADRIC)
48 {
49 fn = std::shared_ptr<RadialBasisFunction>(new InverseMultiquadric());
50 }
51 else if (type == RadialBasisFunctionType::GAUSSIAN)
52 {
53 fn = std::shared_ptr<RadialBasisFunction>(new Gaussian());
54 fn->e = e;
55 }
56 else if (type == RadialBasisFunctionType::LINEAR)
57 {
58 fn = std::shared_ptr<RadialBasisFunction>(new Linear());
59 }
60 else if (type == RadialBasisFunctionType::CUBIC)
61 {
62 fn = std::shared_ptr<RadialBasisFunction>(new Cubic());
63 }
64 else if (type == RadialBasisFunctionType::QUINTIC)
65 {
66 fn = std::shared_ptr<RadialBasisFunction>(new Quintic());
67 }
68 else
69 {
70 fn = std::shared_ptr<RadialBasisFunction>(new ThinPlateSpline());
71 }
72
73 weights = w;
74}
75
76RBFSpline::RBFSpline(const DataTable& samples, RadialBasisFunctionType type,
77 bool normalized, double e)
78 : samples(samples),
79 normalized(normalized),
80 precondition(false),
81 dim(samples.getNumVariables()),
82 numSamples(samples.getNumSamples())
83{
84 if (type == RadialBasisFunctionType::THIN_PLATE_SPLINE)
85 {
86 fn = std::shared_ptr<RadialBasisFunction>(new ThinPlateSpline());
87 }
88 else if (type == RadialBasisFunctionType::MULTIQUADRIC)
89 {
90 fn = std::shared_ptr<RadialBasisFunction>(new Multiquadric());
91 }
92 else if (type == RadialBasisFunctionType::INVERSE_QUADRIC)
93 {
94 fn = std::shared_ptr<RadialBasisFunction>(new InverseQuadric());
95 }
96 else if (type == RadialBasisFunctionType::INVERSE_MULTIQUADRIC)
97 {
98 fn = std::shared_ptr<RadialBasisFunction>(new InverseMultiquadric());
99 }
100 else if (type == RadialBasisFunctionType::GAUSSIAN)
101 {
102 fn = std::shared_ptr<RadialBasisFunction>(new Gaussian());
103 fn->e = e;
104 }
105 else if (type == RadialBasisFunctionType::LINEAR)
106 {
107 fn = std::shared_ptr<RadialBasisFunction>(new Linear());
108 }
109 else if (type == RadialBasisFunctionType::CUBIC)
110 {
111 fn = std::shared_ptr<RadialBasisFunction>(new Cubic());
112 }
113 else if (type == RadialBasisFunctionType::QUINTIC)
114 {
115 fn = std::shared_ptr<RadialBasisFunction>(new Quintic());
116 }
117 else
118 {
119 fn = std::shared_ptr<RadialBasisFunction>(new ThinPlateSpline());
120 }
121
122 /* Want to solve the linear system A*w = b,
123 * where w is the vector of weights.
124 * NOTE: the system is dense and by default badly conditioned.
125 * It should be solved by a specialized solver such as GMRES
126 * with preconditioning (e.g. ACBF) as in matlab.
127 * NOTE: Consider trying the Łukaszyk–Karmowski metric (for two variables)
128 */
129 //SparseMatrix A(numSamples,numSamples);
130 //A.reserve(numSamples*numSamples);
131 DenseMatrix A;
132 A.setZero(numSamples, numSamples);
133 DenseMatrix b;
134 b.setZero(numSamples, 1);
135 int i = 0;
136
137 for (auto it1 = samples.cbegin(); it1 != samples.cend(); ++it1, ++i)
138 {
139 double sum = 0;
140 int j = 0;
141
142 for (auto it2 = samples.cbegin(); it2 != samples.cend(); ++it2, ++j)
143 {
144 double val = fn->eval(dist(* it1, * it2));
145
146 if (val != 0)
147 {
148 //A.insert(i,j) = val;
149 A(i, j) = val;
150 sum += val;
151 }
152 }
153
154 double y = it1->getY();
155
156 if (normalized)
157 {
158 b(i) = sum * y;
159 }
160 else
161 {
162 b(i) = y;
163 }
164 }
165
166 //A.makeCompressed();
167
168 if (precondition)
169 {
170 // Calcualte precondition matrix P
171 DenseMatrix P = computePreconditionMatrix();
172 // Preconditioned A and b
173 DenseMatrix Ap = P * A;
174 DenseMatrix bp = P * b;
175 A = Ap;
176 b = bp;
177 }
178
179#ifndef NDEBUG
180 Foam::Info << "Computing RBF weights using dense solver." << Foam::endl;
181 Foam::Info << "The radius of the RBF is equal to " << e << Foam::endl;
182#endif // NDEBUG
183 // SVD analysis
184 // Eigen::JacobiSVD<DenseMatrix> svd(A, Eigen::ComputeThinU | Eigen::ComputeThinV);
185 // auto svals = svd.singularValues();
186 // double svalmax = svals(0);
187 // double svalmin = svals(svals.rows() - 1);
188 // double rcondnum = (svalmax <= 0.0 || svalmin <= 0.0) ? 0.0 : svalmin / svalmax;
189 // #ifndef NDEBUG
190 // Info << "The reciprocal of the condition number is: " << rcondnum <<
191 // endl;
192 // Info << "Largest/smallest singular value: " << svalmax << " / " << svalmin
193 // << endl;
194 // #endif // NDEBUG
195 // // Solve for weights
196 // weights = svd.solve(b);
197 weights = A.colPivHouseholderQr().solve(b);
198#ifndef NDEBUG
199 // Compute error. If it is used later on, move this statement above the NDEBUG
200 double err = (A * weights - b).norm() / b.norm();
201 Foam::Info << "Error: " << Foam::setprecision(10) << err << Foam::endl;
202#endif // NDEBUG
203 // // Alternative solver
204 // DenseQR s;
205 // bool success = s.solve(A,b,weights);
206 // assert(success);
207 // NOTE: Tried using experimental GMRES solver in Eigen, but it did not work very well.
208}
209
210double RBFSpline::eval(DenseVector x) const
211{
212 std::vector<double> y;
213
214 for (int i = 0; i < x.rows(); i++)
215 {
216 y.push_back(x(i));
217 }
218
219 return eval(y);
220}
221
222double RBFSpline::eval(std::vector<double> x) const
223{
224 assert(x.size() == dim);
225 double fval, sum = 0, sumw = 0;
226 int i = 0;
227
228 for (auto it = samples.cbegin(); it != samples.cend(); ++it, ++i)
229 {
230 fval = fn->eval(dist(x, it->getX()));
231 sumw += weights(i) * fval;
232 sum += fval;
233 }
234
235 return normalized ? sumw / sum : sumw;
236}
237
238/*
239 * TODO: test for errors
240 */
241//DenseMatrix RBFSpline::evalJacobian(DenseVector x) const
242//{
243// std::vector<double> x_vec;
244// for(unsigned int i = 0; i<x.size(); i++)
245// x_vec.push_back(x(i));
246
247// DenseMatrix jac;
248// jac.setZero(1,dim);
249
250// for(unsigned int i = 0; i < dim; i++)
251// {
252// double sumw = 0;
253// double sumw_d = 0;
254// double sum = 0;
255// double sum_d = 0;
256
257// int j = 0;
258// for(auto it = samples.cbegin(); it != samples.cend(); ++it, ++j)
259// {
260// // Sample
261// auto s_vec = it->getX();
262
263// // Distance from sample
264// double r = dist(x_vec, s_vec);
265// double ri = x_vec.at(i) - s_vec.at(i);
266
267// // Evaluate RBF and its derivative at r
268// double f = fn->eval(r);
269// double dfdr = fn->evalDerivative(r);
270
271// sum += f;
272// sumw += weights(j)*f;
273
274// // TODO: check if this assumption is correct
275// if(r != 0)
276// {
277// sum_d += dfdr*ri/r;
278// sumw_d += weights(j)*dfdr*ri/r;
279// }
280// }
281
282// if(normalized)
283// jac(i) = (sum*sumw_d - sum_d*sumw)/(sum*sum);
284// else
285// jac(i) = sumw_d;
286// }
287// return jac;
288//}
289
290/*
291 * Calculate precondition matrix
292 */
293DenseMatrix RBFSpline::computePreconditionMatrix() const
294{
295 DenseMatrix P;
296 P.setZero(numSamples, numSamples);
297 // Calculate precondition matrix P based on
298 // purely local approximate cardinal basis functions (ACBF)
299 int sigma = std::max(1.0,
300 std::floor(0.1 * numSamples)); // Local points to consider
301 int i = 0;
302
303 for (auto it1 = samples.cbegin(); it1 != samples.cend(); ++it1, ++i)
304 {
305 Point p1(it1->getX());
306 // Shift data using p1 as origin
307 std::vector<Point> shifted_points;
308 int j = 0;
309
310 for (auto it2 = samples.cbegin(); it2 != samples.cend(); ++it2, ++j)
311 {
312 Point p2(it2->getX());
313 Point p3(p2 - p1);
314 p3.setIndex(j); // store index with point
315 shifted_points.push_back(p3);
316 }
317
318 std::sort(shifted_points.begin(), shifted_points.end());
319 // Find sigma closest points to p1
320 std::vector<Point> points;
321 std::vector<int> indices;
322
323 for (int j = 0; j < sigma; j++)
324 {
325 Point p(shifted_points.at(j));
326 indices.push_back(p.getIndex());
327 Point p2 = p + p1;
328 p2.setIndex(p.getIndex());
329 points.push_back(
330 p2); // The resulting point has a different index than that of p
331 // cout << p.getIndex() << "/" << p1.getIndex() << "/" << p2.getIndex() << endl;
332 // assert(p.getIndex() == p2.getIndex());
333 }
334
335 // Add some points far away from p1 and preferably scattered around the domain boundary
336 for (int k = 0; k < 1; k++)
337 {
338 Point p(shifted_points.at(shifted_points.size() - 1 - k));
339 indices.push_back(p.getIndex());
340 Point p2 = p + p1;
341 p2.setIndex(p.getIndex());
342 points.push_back(p2);
343 }
344
345 // Build and solve linear system
346 int m = points.size();
347 DenseMatrix e;
348 e.setZero(m, 1);
349 e(0, 0) = 1;
350 DenseMatrix B;
351 B.setZero(m, m);
352 DenseMatrix w;
353 w.setZero(m, 1);
354 assert(points.front().getIndex() == i);
355
356 for (int k1 = 0; k1 < m; k1++)
357 {
358 for (int k2 = 0; k2 < m; k2++)
359 {
360 Point p = points.at(k1) - points.at(k2);
361 B(k1, k2) = fn->eval(p.dist());
362 }
363 }
364
365 // DenseQR s;
366 // bool success = s.solve(B,e,w);
367 // assert(success);
368 Eigen::JacobiSVD<DenseMatrix> svd(B, Eigen::ComputeThinU | Eigen::ComputeThinV);
369 w = svd.solve(e);
370 //assert(svd.info() == Eigen::Success);
371
372 for (unsigned int j = 0; j < numSamples; j++)
373 {
374 auto it = find(indices.begin(), indices.end(), j);
375
376 if (it != indices.end())
377 {
378 int k = it - indices.begin();
379 P(i, j) = w(k, 0);
380 //cout << "j/k/g " << j << "/" << k << "/" << points.at(k).getIndex() << endl;
381 assert(points.at(k).getIndex() == j);
382 }
383 }
384 }
385
386 return P;
387}
388
389/*
390 * Computes Euclidean distance ||x-y||
391 */
392double RBFSpline::dist(std::vector<double> x, std::vector<double> y) const
393{
394 assert(x.size() == y.size());
395 double sum = 0.0;
396
397 for (unsigned int i = 0; i < x.size(); i++)
398 {
399 sum += (x.at(i) - y.at(i)) * (x.at(i) - y.at(i));
400 }
401
402 return std::sqrt(sum);
403}
404
405/*
406 * Computes Euclidean distance ||x-y||
407 */
408double RBFSpline::dist(DataPoint x, DataPoint y) const
409{
410 return dist(x.getX(), y.getX());
411}
412
413bool RBFSpline::dist_sort(DataPoint x, DataPoint y) const
414{
415 std::vector<double> zeros(x.getDimX(), 0);
416 DataPoint origin(zeros, 0.0);
417 double x_dist = dist(x, origin);
418 double y_dist = dist(y, origin);
419 return (x_dist < y_dist);
420}
421
422} // namespace MultivariateSplines