PolyFEM
Loading...
Searching...
No Matches
RBFWithLinear.cpp
Go to the documentation of this file.
1
2#include "RBFWithLinear.hpp"
3
6
7#include <igl/Timer.h>
8
9#include <Eigen/Dense>
10
11#include <iostream>
12#include <fstream>
13#include <array>
15
16using namespace polyfem;
17using namespace polyfem::basis;
18using namespace polyfem::quadrature;
19
20namespace
21{
22
23 // Harmonic kernel
24 double kernel(const bool is_volume, const double r)
25 {
26 if (r < 1e-8)
27 {
28 return 0;
29 }
30
31 if (is_volume)
32 {
33 return 1 / r;
34 }
35 else
36 {
37 return log(r);
38 }
39 }
40
41 double kernel_prime(const bool is_volume, const double r)
42 {
43 if (r < 1e-8)
44 {
45 return 0;
46 }
47
48 if (is_volume)
49 {
50 return -1 / (r * r);
51 }
52 else
53 {
54 return 1 / r;
55 }
56 }
57
58} // anonymous namespace
59
61
63 const Eigen::MatrixXd &centers,
64 const Eigen::MatrixXd &samples,
65 const Eigen::MatrixXd &local_basis_integral,
66 const Quadrature &quadr,
67 Eigen::MatrixXd &rhs,
68 bool with_constraints)
69 : centers_(centers)
70{
71 compute_weights(samples, local_basis_integral, quadr, rhs, with_constraints);
72}
73
74// -----------------------------------------------------------------------------
75
76void RBFWithLinear::basis(const int local_index, const Eigen::MatrixXd &samples, Eigen::MatrixXd &val) const
77{
78 Eigen::MatrixXd tmp;
79 bases_values(samples, tmp);
80 val = tmp.col(local_index);
81}
82
83// -----------------------------------------------------------------------------
84
85void RBFWithLinear::grad(const int local_index, const Eigen::MatrixXd &samples, Eigen::MatrixXd &val) const
86{
87 Eigen::MatrixXd tmp;
88 const int dim = centers_.cols();
89 val.resize(samples.rows(), dim);
90 for (int d = 0; d < dim; ++d)
91 {
92 bases_grads(d, samples, tmp);
93 val.col(d) = tmp.col(local_index);
94 }
95}
96
98
99void RBFWithLinear::bases_values(const Eigen::MatrixXd &samples, Eigen::MatrixXd &val) const
100{
101 // Compute A
102 Eigen::MatrixXd A;
103 compute_kernels_matrix(samples, A);
104
105 // Multiply by the weights
106 val = A * weights_;
107}
108
109// -----------------------------------------------------------------------------
110
111void RBFWithLinear::bases_grads(const int axis, const Eigen::MatrixXd &samples, Eigen::MatrixXd &val) const
112{
113 const int num_kernels = centers_.rows();
114 const int dim = centers_.cols();
115
116 // Compute ∇xA
117 Eigen::MatrixXd A_prime(samples.rows(), num_kernels + 1 + dim);
118 A_prime.setZero();
119
120 for (int j = 0; j < num_kernels; ++j)
121 {
122 A_prime.col(j) = (samples.rowwise() - centers_.row(j)).rowwise().norm().unaryExpr([this](double x) { return kernel_prime(is_volume(), x) / x; });
123 A_prime.col(j) = (samples.col(axis).array() - centers_(j, axis)) * A_prime.col(j).array();
124 }
125 // Linear terms
126 A_prime.middleCols(num_kernels + 1 + axis, 1).setOnes();
127
128 // Apply weights
129 val = A_prime * weights_;
130}
131
133
134void RBFWithLinear::compute_kernels_matrix(const Eigen::MatrixXd &samples, Eigen::MatrixXd &A) const
135{
136 // Compute A
137 const int num_kernels = centers_.rows();
138 const int dim = centers_.cols();
139
140 A.resize(samples.rows(), num_kernels + 1 + dim);
141 for (int j = 0; j < num_kernels; ++j)
142 {
143 A.col(j) = (samples.rowwise() - centers_.row(j)).rowwise().norm().unaryExpr([this](double x) { return kernel(is_volume(), x); });
144 }
145 A.col(num_kernels).setOnes(); // constant term
146 A.rightCols(dim) = samples; // linear terms
147}
148
149// -----------------------------------------------------------------------------
150
152 const int num_bases,
153 const Quadrature &quadr,
154 const Eigen::MatrixXd &local_basis_integral,
155 Eigen::MatrixXd &L,
156 Eigen::MatrixXd &t) const
157{
158 const int num_kernels = centers_.rows();
159 const int dim = centers_.cols();
160
161 // Compute KI
162 Eigen::MatrixXd KI(num_kernels, dim);
163 for (int j = 0; j < num_kernels; ++j)
164 {
165 // ∫∇x(φj)(p) = Σ_q (xq - xk) * 1/r * h'(r) * wq
166 // - xq is the x coordinate of the q-th quadrature point
167 // - wq is the q-th quadrature weight
168 // - r is the distance from pq to the kernel center
169 // - h is the RBFWithLinear RBF kernel (scalar function)
170 const Eigen::MatrixXd drdp = quadr.points.rowwise() - centers_.row(j);
171 const Eigen::VectorXd r = drdp.rowwise().norm();
172 KI.row(j) = (drdp.array().colwise() * (quadr.weights.array() * r.unaryExpr([this](double x) { return kernel_prime(is_volume(), x); }).array() / r.array())).colwise().sum();
173 }
174 KI /= quadr.weights.sum();
175
176 // Compute L
177 L.resize(num_kernels + dim + 1, num_kernels + 1);
178 L.setZero();
179 L.diagonal().setOnes();
180 L.block(num_kernels + 1, 0, dim, num_kernels) = -KI.transpose();
181 // std::cout << L.bottomRightCorner(10, 10) << std::endl;
182
183 // Compute t
184 t.resize(num_kernels + 1 + dim, num_bases);
185 t.setZero();
186 t.bottomRows(dim) = local_basis_integral.transpose().topRows(dim) / quadr.weights.sum();
187}
188
189// -----------------------------------------------------------------------------
190
191void RBFWithLinear::compute_weights(const Eigen::MatrixXd &samples,
192 const Eigen::MatrixXd &local_basis_integral, const Quadrature &quadr,
193 Eigen::MatrixXd &rhs, bool with_constraints)
194{
195 logger().trace("#kernel centers: {}", centers_.rows());
196 logger().trace("#collocation points: {}", samples.rows());
197 logger().trace("#quadrature points: {}", quadr.weights.size());
198 logger().trace("#non-vanishing bases: {}", rhs.cols());
199
200 if (!with_constraints)
201 {
202 // Compute A
203 Eigen::MatrixXd A;
204 compute_kernels_matrix(samples, A);
205
206 // Solve the system
207 const int num_kernels = centers_.rows();
208 logger().trace("-- Solving system of size {}x{}", num_kernels, num_kernels);
209 weights_ = (A.transpose() * A).ldlt().solve(A.transpose() * rhs);
210 logger().trace("-- Solved!");
211
212 return;
213 }
214
215 // For each basis function f that is nonzero on the element E, we want to
216 // solve the least square system A w = rhs, where:
217 // ┏ ┓
218 // ┃ φj(pi) ... 1 xi yi ┃
219 // A = ┃ ┊ ┊ ┊ ┊ ┃ ∊ ℝ^{#S x (#K+1+dim)}
220 // ┃ ┊ ┊ ┊ ┊ ┃
221 // ┗ ┛
222 // ┏ ┓^⊤
223 // w = ┃ wj ... a00 a10 a01 ┃ ∊ ℝ^{#K+1+dim}
224 // ┗ ┛
225 // - A is the RBF kernels evaluated over the collocation points (#S)
226 // - b is the expected value of the basis sampled on the boundary (#S)
227 // - w is the weight of the kernels defining the basis
228 // - pi = (xi, yi) is the i-th collocation point
229 //
230 // Moreover, we want to impose a constraint on the gradients of the kernels
231 // so that the integral of the gradients over the polytope must be equal to
232 // the value specified in the argument `local_basis_integral` (#K)
233 //
234 // Let `lb` be the precomputed expected value of ∫f over the rest of the mesh.
235 // We write down the constraint as:
236 //
237 // ∫_{p ∊ E} Σ_j wj ∇x(φj)(p) + ∇x(a^⊤·p + c) dp = lb (1)
238 // (1) ⇔ ∫_{p ∊ E} Σ_j wj ∇x(φj)(p) + ax dp = lb
239 // ⇔ lb - Σ_j wj ∫_{p ∊ E} ∇x(φj)(p) dp = ax Vol(E)
240 //
241 // We now have a relationship w = Lv + t, where the weights (and esp. the
242 // linear terms in the weight vector w), are expressed as an affine
243 // combination of unknowns v = [wj ... c] ∊ ℝ^{#K+1} and a translation t
244 //
245 // After solving the new least square system A L v = rhs - A t, we can retrieve
246 // w = L v
247
248 //
249 // ┏ ┓^⊤
250 // t = ┃ 0 ┈ ┈ 0 0 lbx lby ┃ / Vol(E) ∊ ℝ^{#K+1+dim}
251 // ┗ ┛
252 //
253 // ┏ ┓
254 // ┃ 1 ┃
255 // ┃ 1 ┃
256 // ┃ · ┃
257 // L = ┃ · ┃ ∊ ℝ^{ (#K+1+dim) x (#K+1}) }
258 // ┃ 1 ┃
259 // ┃ Lx_j ┈ 0 ┃
260 // ┃ Ly_j ┈ 0 ┃
261 // ┗ ┛
262 // Where Lx_j = -∫∇xφj / Vol(E) = -∫_{p ∊ E} ∇x(φj)(p) / Vol(E) is integrated numerically
263 //
264
265 const int num_bases = rhs.cols();
266
267 // Compute A
268 Eigen::MatrixXd A;
269 compute_kernels_matrix(samples, A);
270
271 // Compute L and t
272 // Note that t is stored into `weights_` for memory efficiency reasons
273 Eigen::MatrixXd L;
274 compute_constraints_matrix(num_bases, quadr, local_basis_integral, L, weights_);
275
276 // Compute b = rhs - A t
277 rhs -= A * weights_;
278
279 // Solve the system
280 logger().trace("-- Solving system of size {}x{}", L.cols(), L.cols());
281 weights_ += L * (L.transpose() * A.transpose() * A * L).ldlt().solve(L.transpose() * A.transpose() * rhs);
282 logger().trace("-- Solved!");
283
284 // std::cout << weights_.bottomRows(10) << std::endl;
285
286 // Eigen::MatrixXd M, x, dx;
287 // grad(0, quadr.points, M);
288 // for (int d = 0; d < dim; ++d) {
289 // basis(0, quadr.points, x);
290 // auto asd = quadr.points;
291 // asd.col(d).array() += 1e-7;
292 // basis(0, asd, dx);
293 // std::cout << (dx - x) / 1e-7 - M.col(d) << std::endl;
294 // std::cout << (M.col(d).array() * quadr.weights.array()).sum() - local_basis_integral(0, d) << std::endl;
295 // }
296}
double val
Definition Assembler.cpp:86
int x
void compute_weights(const Eigen::MatrixXd &collocation_points, const Eigen::MatrixXd &local_basis_integral, const quadrature::Quadrature &quadr, Eigen::MatrixXd &rhs, bool with_constraints)
void bases_values(const Eigen::MatrixXd &samples, Eigen::MatrixXd &val) const
Batch evaluates the RBF + polynomials on a set of sample points.
void compute_constraints_matrix(const int num_bases, const quadrature::Quadrature &quadr, const Eigen::MatrixXd &local_basis_integral, Eigen::MatrixXd &L, Eigen::MatrixXd &t) const
RBFWithLinear(const Eigen::MatrixXd &centers, const Eigen::MatrixXd &collocation_points, const Eigen::MatrixXd &local_basis_integral, const quadrature::Quadrature &quadr, Eigen::MatrixXd &rhs, bool with_constraints=true)
Initialize RBF functions over a polytope element.
void compute_kernels_matrix(const Eigen::MatrixXd &samples, Eigen::MatrixXd &A) const
void grad(const int local_index, const Eigen::MatrixXd &uv, Eigen::MatrixXd &val) const
Evaluates the gradient of one RBF function over a list of coordinates.
void basis(const int local_index, const Eigen::MatrixXd &uv, Eigen::MatrixXd &val) const
Evaluates one RBF function over a list of coordinates.
void bases_grads(const int axis, const Eigen::MatrixXd &samples, Eigen::MatrixXd &val) const
Batch evaluates the gradient of the RBF + polynomials on a set of sample points.
spdlog::logger & logger()
Retrieves the current logger.
Definition Logger.cpp:42