OpenLB 1.8.1
Loading...
Searching...
No Matches
freeSurfaceHelpers.hh
Go to the documentation of this file.
1/* This file is part of the OpenLB library
2 *
3 * Copyright (C) 2021 Claudius Holeksa
4 * 2024-2025 Danial Khazaeipoul
5 * E-mail contact: info@openlb.net
6 * The most recent release of OpenLB can be downloaded at
7 * <http://www.openlb.net/>
8 *
9 * This program is free software; you can redistribute it and/or
10 * modify it under the terms of the GNU General Public License
11 * as published by the Free Software Foundation; either version 2
12 * of the License, or (at your option) any later version.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public
20 * License along with this program; if not, write to the Free
21 * Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
22 * Boston, MA 02110-1301, USA.
23*/
24
25#include <atomic>
26
27namespace olb {
28
29namespace FreeSurface {
30
31template<typename T, typename DESCRIPTOR>
40
41template <typename CELL>
42bool isCellType(CELL& cell, const FreeSurface::Type& type) {
43 return cell.template getField<FreeSurface::CELL_TYPE>() == type;
44}
45
46template <typename CELL>
47void setCellType(CELL& cell, const FreeSurface::Type& type) {
48 cell.template setField<FreeSurface::CELL_TYPE>(type);
49}
50
51template <typename CELL>
52bool hasNeighbour(CELL& cell, const FreeSurface::Type& type) {
53 using DESCRIPTOR = typename CELL::descriptor_t;
54
55 for (int iPop = 1; iPop < DESCRIPTOR::q; ++iPop) {
56 auto nbrCell = cell.neighbor(descriptors::c<DESCRIPTOR>(iPop));
57 if (isCellType(nbrCell, type)) { return true; }
58 }
59
60 return false;
61}
62
63template <typename CELL>
64bool hasCellFlags(CELL& cell, const FreeSurface::Flags& flags) {
65 return static_cast<bool>(cell.template getField<FreeSurface::CELL_FLAGS>() & flags);
66}
67
68template <typename CELL>
69void setCellFlags(CELL& cell, const FreeSurface::Flags& flags) {
70 cell.template setField<FreeSurface::CELL_FLAGS>(flags);
71}
72
73template <typename CELL>
74bool hasNeighbourFlags(CELL& cell, const FreeSurface::Flags& flags) {
75 using DESCRIPTOR = typename CELL::descriptor_t;
76
77 for (int iPop = 1; iPop < DESCRIPTOR::q; ++iPop) {
78 auto nbrCell = cell.neighbor(descriptors::c<DESCRIPTOR>(iPop));
79 if (hasCellFlags(nbrCell, flags)) { return true; }
80 }
81
82 return false;
83}
84
85template <typename CELL>
86NeighbourInfo getNeighbourInfo(CELL& cell) {
87 NeighbourInfo info{};
88 using DESCRIPTOR = typename CELL::descriptor_t;
89
90 for (int iPop = 1; iPop < DESCRIPTOR::q; ++iPop) {
91 auto nbrCell = cell.neighbor(descriptors::c<DESCRIPTOR>(iPop));
92
93 if (isCellType(nbrCell, FreeSurface::Type::Gas)) {
94 info.has_gas_neighbours = true;
95 }
96 else if (isCellType(nbrCell, FreeSurface::Type::Fluid)) {
97 info.has_fluid_neighbours = true;
98 }
99 else if (isCellType(nbrCell, FreeSurface::Type::Interface)) {
100 ++info.interface_neighbours;
101 }
102 }
103
104 return info;
105}
106
107template <typename CELL, typename V>
108bool isHealthyInterface(CELL& cell) {
109 using DESCRIPTOR = typename CELL::descriptor_t;
110 bool has_fluid_neighbours = false;
111 bool has_gas_neighbours = false;
112
113 if (!isCellType(cell, FreeSurface::Type::Interface)) { return false; }
114
115 for (int iPop = 1; iPop < DESCRIPTOR::q; ++iPop) {
116 auto nbrCell = cell.neighbor(descriptors::c<DESCRIPTOR>(iPop));
117 if (isCellType(nbrCell, FreeSurface::Type::Gas)) {
118 has_gas_neighbours = true;
119 if (has_fluid_neighbours) { return true; }
120 }
121 else if (isCellType(nbrCell, FreeSurface::Type::Fluid)) {
122 has_fluid_neighbours = true;
123 if (has_gas_neighbours) { return true; }
124 }
125 }
126
127 return false;
128}
129
130template <typename CELL, typename V>
131void computeMassExcessWeights
132(
133 CELL& cell, const Vector<V, CELL::descriptor_t::d>& normal,
134 const bool& enableAllInterfaces, Vector<V, CELL::descriptor_t::q>& weights
135) {
136 using DESCRIPTOR = typename CELL::descriptor_t;
137
138 for (int iPop = 1; iPop < DESCRIPTOR::q; ++iPop) {
139 auto direction = descriptors::c<DESCRIPTOR>(iPop);
140 auto nbrCell = cell.neighbor(direction);
141
143 if constexpr (DESCRIPTOR::d == 3) {
144 ei = {V(direction[0]), V(direction[1]), V(direction[2])};
145 }
146 else { ei = {V(direction[0]), V(direction[1])}; }
147
148 if (isCellType(nbrCell, FreeSurface::Type::Interface) && hasCellFlags(nbrCell, FreeSurface::Flags::None)) {
149 const V nDotDirection = util::dotProduct(normal, ei);
150
151 if (hasCellFlags(cell, FreeSurface::Flags::ToFluid)) {
152 // This cell was converted from interface to fluid, so the normal vector is used as is
153 weights[iPop] = nDotDirection > V(0) ? nDotDirection : V(0);
154 }
155 else if (hasCellFlags(cell, FreeSurface::Flags::ToGas)) {
156 // This cell was converted from interface to gas, so the normal vector is inverted
157 weights[iPop] = nDotDirection < V(0) ? -nDotDirection : V(0);
158 }
159 }
160 else if (hasCellFlags(nbrCell, FreeSurface::Flags::NewInterface) && enableAllInterfaces) {
161 const V nDotDirection = util::dotProduct(normal, ei);
162
163 if (hasCellFlags(cell, FreeSurface::Flags::ToFluid)) {
164 // This cell was converted from interface to fluid, so the normal vector is used as is
165 weights[iPop] = nDotDirection > V(0) ? nDotDirection : V(0);
166 }
167 else if (hasCellFlags(cell, FreeSurface::Flags::ToGas)) {
168 // This cell was converted from interface to gas, so the normal vector is inverted
169 weights[iPop] = nDotDirection < V(0) ? -nDotDirection : V(0);
170 }
171 }
172 else {
173 // If the neighbour is not an interface cell, the weight is set to zero
174 weights[iPop] = V(0);
175 }
176 }
177}
178
179template <typename CELL, typename V>
180V getClampedEpsilon(const CELL& cell) {
181
182 V epsilon = cell.template getField<FreeSurface::EPSILON>();
183 return util::max(V(0), util::min(V(1), epsilon));
184}
185
186template <typename CELL, typename V>
187V getSmoothEpsilon(CELL& cell) {
188 using DESCRIPTOR = typename CELL::descriptor_t;
189
190 // K8 kernel of Williams et al.
191 // Avoid uisng expensive std::pow to boost performance
192 auto kernel = [](auto radius, const auto& direction) {
193 auto norm = norm_squared(direction);
194 if (norm < radius * radius) {
195 auto tmp = decltype(radius)(1) - norm / (radius * radius);
196 return tmp * tmp * tmp * tmp;
197 } else { return decltype(radius)(0); }
198 };
199
200 // Kernel support radius is fixed at 2.0
201 V radius = V(2);
202 V denominator = V(0);
203 V tmp = V(0);
204
205 for (int iPop = 1; iPop < DESCRIPTOR::q; ++iPop) {
206 auto nbrCell = cell.neighbor(descriptors::c<DESCRIPTOR>(iPop));
207 const auto direction = descriptors::c<DESCRIPTOR>(iPop);
208 tmp += kernel(radius, direction) * nbrCell.template getField<FreeSurface::EPSILON>();
209 if (norm(direction) < radius) { denominator += kernel(radius, direction); }
210 }
211
212 return tmp / denominator;
213}
214
215template <typename CELL, typename V>
216V getClampedSmoothEpsilon(const CELL& cell) {
217
218 V epsilon = getSmoothEpsilon(cell);
219 return util::max(V(0), util::min(V(1), epsilon));
220}
221
222template <typename CELL, typename V>
223Vector<V, CELL::descriptor_t::d> computeInterfaceNormal(CELL& cell) {
224
225 // Compute interface normal using Parker-Young approximation
227 if constexpr (CELL::descriptor_t::d == 3) {
228 auto eps_minusY = getClampedEpsilon(cell.neighbor({0, -1, 0}));
229 auto eps_plusY = getClampedEpsilon(cell.neighbor({0, 1, 0}));
230 auto eps_minusX = getClampedEpsilon(cell.neighbor({-1, 0, 0}));
231 auto eps_plusX = getClampedEpsilon(cell.neighbor({1, 0, 0}));
232 auto eps_minusYminusX = getClampedEpsilon(cell.neighbor({-1, -1, 0}));
233 auto eps_minusYplusX = getClampedEpsilon(cell.neighbor({1, -1, 0}));
234 auto eps_plusYminusX = getClampedEpsilon(cell.neighbor({-1, 1, 0}));
235 auto eps_plusYplusX = getClampedEpsilon(cell.neighbor({1, 1, 0}));
236 auto eps_minusZ = getClampedEpsilon(cell.neighbor({0, 0, -1}));
237 auto eps_plusZ = getClampedEpsilon(cell.neighbor({0, 0, 1}));
238 auto eps_minusZminusY = getClampedEpsilon(cell.neighbor({0, -1, -1}));
239 auto eps_minusZplusY = getClampedEpsilon(cell.neighbor({0, 1, -1}));
240 auto eps_minusZminusX = getClampedEpsilon(cell.neighbor({-1, 0, -1}));
241 auto eps_minusZplusX = getClampedEpsilon(cell.neighbor({1, 0, -1}));
242 auto eps_plusZminusY = getClampedEpsilon(cell.neighbor({0, -1, 1}));
243 auto eps_plusZplusY = getClampedEpsilon(cell.neighbor({0, 1, 1}));
244 auto eps_plusZminusX = getClampedEpsilon(cell.neighbor({-1, 0, 1}));
245 auto eps_plusZplusX = getClampedEpsilon(cell.neighbor({1, 0, 1}));
246 auto eps_minusZminusYminusX = getClampedEpsilon(cell.neighbor({-1, -1, -1}));
247 auto eps_minusZplusYminusX = getClampedEpsilon(cell.neighbor({-1, 1, -1}));
248 auto eps_minusZminusYplusX = getClampedEpsilon(cell.neighbor({1, -1, -1}));
249 auto eps_minusZplusYplusX = getClampedEpsilon(cell.neighbor({1, 1, -1}));
250 auto eps_plusZminusYminusX = getClampedEpsilon(cell.neighbor({-1, -1, 1}));
251 auto eps_plusZplusYminusX = getClampedEpsilon(cell.neighbor({-1, 1, 1}));
252 auto eps_plusZminusYplusX = getClampedEpsilon(cell.neighbor({1, -1, 1}));
253 auto eps_plusZplusYplusX = getClampedEpsilon(cell.neighbor({1, 1, 1}));
254
255 // Rearrange arithmetic operations to minimize the number of cancellation events, i.e., positive and negative terms.
256 normal[0] = V(4) * (eps_minusX - eps_plusX);
257 normal[0] += V(2) * ((eps_minusYminusX + eps_minusZminusX + eps_plusYminusX + eps_plusZminusX) - (eps_plusYplusX + eps_plusZplusX + eps_minusYplusX + eps_minusZplusX));
258 normal[0] += V(1) * ((eps_minusZminusYminusX + eps_plusZminusYminusX + eps_minusZplusYminusX + eps_plusZplusYminusX) - (eps_plusZplusYplusX + eps_minusZplusYplusX + eps_plusZminusYplusX + eps_minusZminusYplusX));
259
260 normal[1] = V(4) * (eps_minusY - eps_plusY);
261 normal[1] += V(2) * ((eps_minusYminusX + eps_minusZminusY + eps_minusYplusX + eps_plusZminusY) - (eps_plusYplusX + eps_plusZplusY + eps_plusYminusX + eps_minusZplusY));
262 normal[1] += V(1) * ((eps_minusZminusYminusX + eps_plusZminusYminusX + eps_plusZminusYplusX + eps_minusZminusYplusX) - (eps_plusZplusYplusX + eps_minusZplusYplusX + eps_minusZplusYminusX + eps_plusZplusYminusX));
263
264 normal[2] = V(4) * (eps_minusZ - eps_plusZ);
265 normal[2] += V(2) * ((eps_minusZminusX + eps_minusZminusY + eps_minusZplusX + eps_minusZplusY) - (eps_plusZplusX + eps_plusZplusY + eps_plusZminusX + eps_plusZminusY));
266 normal[2] += V(1) * ((eps_minusZminusYminusX + eps_minusZplusYplusX + eps_minusZplusYminusX + eps_minusZminusYplusX) - (eps_plusZplusYplusX + eps_plusZminusYminusX + eps_plusZminusYplusX + eps_plusZplusYminusX));
267
268 return normal;
269 }
270 else {
271 auto eps_minusY = getClampedEpsilon(cell.neighbor({0, -1}));
272 auto eps_plusY = getClampedEpsilon(cell.neighbor({0, 1}));
273 auto eps_minusX = getClampedEpsilon(cell.neighbor({-1, 0}));
274 auto eps_plusX = getClampedEpsilon(cell.neighbor({1, 0}));
275 auto eps_minusYminusX = getClampedEpsilon(cell.neighbor({-1, -1}));
276 auto eps_minusYplusX = getClampedEpsilon(cell.neighbor({1, -1}));
277 auto eps_plusYminusX = getClampedEpsilon(cell.neighbor({-1, 1}));
278 auto eps_plusYplusX = getClampedEpsilon(cell.neighbor({1, 1}));
279
280 // Rearrange arithmetic operations to minimize the number of cancellation events, i.e., positive and negative terms.
281 normal[0] = V(2) * (eps_minusX - eps_plusX);
282 normal[0] += V(1) * ((eps_minusYminusX + eps_plusYminusX) - (eps_plusYplusX + eps_minusYplusX));
283
284 normal[1] = V(2) * (eps_minusY - eps_plusY);
285 normal[1] += V(1) * ((eps_minusYminusX + eps_minusYplusX) - (eps_plusYplusX + eps_plusYminusX));
286
287 return normal;
288 }
289}
290
291template<typename T, int N>
292void iterativeRefinement
293(
294 const std::array<T, N * N>& A, const std::array<T, N * N>& M,
295 std::array<T, N>& x, const std::array<T, N>& b, const int Nsol, int maxIter
296) {
297 std::array<T, N> residual{};
298 for (int iter = 0; iter < maxIter; ++iter) {
299 // Compute the residual, i.e., r = b - A * x
300 for (int i = 0; i < Nsol; ++i) {
301 T sum = T(0);
302 for (int j = 0; j < Nsol; ++j) { sum += A[i * N + j] * x[j]; }
303 residual[i] = b[i] - sum;
304 }
305
306 // Forward substitution
307 std::array<T, N> dx = residual;
308 for (int i = 0; i < Nsol; ++i) {
309 for (int k = 0; k < i; ++k) { dx[i] -= M[i * N + k] * dx[k]; }
310 }
311
312 // Backward substitution
313 for (int i = Nsol - 1; i >= 0; --i) {
314 for (int k = i + 1; k < Nsol; ++k) { dx[i] -= M[i * N + k] * dx[k]; }
315 dx[i] /= M[i * N + i];
316 }
317
318 // Update the solution, i.e., x = x + dx
319 for (int i = 0; i < Nsol; ++i) { x[i] += dx[i]; }
320 }
321}
322
323template<typename T, int N>
324void rowPivotingLUSolver(std::array<T, N * N>& M, std::array<T, N>& x, std::array<T, N>& b, const int Nsol) {
325 // Add Tikhonov Regularization to the diagonal elements of M
326 for (int i = 0; i < Nsol; ++i) { M[i * N + i] += T(0); }
327
328 // Make a copy of the original coefficient matrix.
329 std::array<T, N * N> Prev_M = M;
330
331 // Permutation vector to record row swaps.
332 std::array<int, N> row_perm{};
333 for (int i = 0; i < N; ++i) { row_perm[i] = i; }
334
335 // LU decomposition method with row pivoting.
336 for (int i = 0; i < Nsol; ++i) {
337 int pivotRow = i;
338 T maxValue = std::abs(M[i * N + i]);
339
340 for (int j = i + 1; j < Nsol; ++j) {
341 T value = std::abs(M[j * N + i]);
342 if (value > maxValue) {
343 maxValue = value;
344 pivotRow = j;
345 }
346 }
347
348 if (pivotRow != i) {
349 for (int k = 0; k < Nsol; ++k) { std::swap(M[i * N + k], M[pivotRow * N + k]); }
350 std::swap(row_perm[i], row_perm[pivotRow]);
351 std::swap(b[i], b[pivotRow]);
352 }
353
354 for (int j = i + 1; j < Nsol; ++j) {
355 M[j * N + i] /= M[i * N + i];
356 for (int k = i + 1; k < Nsol; ++k) { M[j * N + k] -= M[j * N + i] * M[i * N + k]; }
357 }
358 }
359
360 // Copy the right-hand side array into x array
361 for (int i = 0; i < Nsol; ++i) { x[i] = b[i]; }
362
363 // Forward substitution step, i.e., solve L * x = b in place.
364 for (int i = 0; i < Nsol; ++i) {
365 for (int j = 0; j < i; ++j) { x[i] -= M[i * N + j] * x[j]; }
366 }
367
368 // Backward substitution step, i.e., solve U * x = b in place.
369 for (int i = Nsol - 1; i >= 0; --i) {
370 for (int j = i + 1; j < Nsol; ++j) { x[i] -= M[i * N + j] * x[j]; }
371 x[i] /= M[i * N + i];
372 }
373
374 // Perform iterative refinement of the solution.
375 std::array<T, N * N> tmp = Prev_M;
376 for (int i = 0; i < Nsol; ++i) {
377 for (int j = 0; j < Nsol; ++j) { Prev_M[i * N + j] = tmp[row_perm[i] * N + j]; }
378 }
379 iterativeRefinement<T, N>(Prev_M, M, x, b, Nsol);
380}
381
382template<typename T, int N>
383void completePivotingLUSolver(std::array<T, N * N>& M, std::array<T, N>& x, std::array<T, N>& b, const int Nsol) {
384 // Add Tikhonov Regularization to the diagonal elements of M
385 for (int i = 0; i < Nsol; ++i) { M[i * N + i] += T(0); }
386
387 // Permutation vectors to record row and columns swaps.
388 std::array<int, N> row_perm{}, col_perm{};
389 for (int i = 0; i < N; ++i) {
390 row_perm[i] = i;
391 col_perm[i] = i;
392 }
393
394 // LU decomposition method with complete pivoting.
395 for (int i = 0; i < Nsol; ++i) {
396 int pivotRow = i, pivotCol = i;
397 T maxValue = std::abs(M[i * N + i]);
398
399 for (int r = i; r < Nsol; ++r) {
400 for (int c = i; c < Nsol; ++c) {
401 T value = std::abs(M[r * N + c]);
402 if (value > maxValue) {
403 maxValue = value;
404 pivotRow = r;
405 pivotCol = c;
406 }
407 }
408 }
409
410 if (pivotRow != i) {
411 for (int k = 0; k < Nsol; ++k) { std::swap(M[i * N + k], M[pivotRow * N + k]); }
412 std::swap(row_perm[i], row_perm[pivotRow]);
413 std::swap(b[i], b[pivotRow]);
414 }
415
416 if (pivotCol != i) {
417 for (int k = 0; k < Nsol; ++k) { std::swap(M[k * N + i], M[k * N + pivotCol]); }
418 std::swap(col_perm[i], col_perm[pivotCol]);
419 }
420
421 for (int j = i + 1; j < Nsol; ++j) {
422 M[j * N + i] /= M[i * N + i];
423 for (int k = i + 1; k < Nsol; ++k) { M[j * N + k] -= M[j * N + i] * M[i * N + k]; }
424 }
425 }
426
427 // Copy the right-hand side array into x array
428 for (int i = 0; i < Nsol; ++i) { x[i] = b[i]; }
429
430 // Forward substitution step, i.e., solve L * x = b in place.
431 for (int i = 0; i < Nsol; ++i) {
432 for (int j = 0; j < i; ++j) { x[i] -= M[i * N + j] * x[j]; }
433 }
434
435 // Backward substitution step, i.e., solve U * x = b in place.
436 for (int i = Nsol - 1; i >= 0; --i) {
437 for (int j = i + 1; j < Nsol; ++j) { x[i] -= M[i * N + j] * x[j]; }
438 x[i] /= M[i * N + i];
439 }
440
441 // Reconstruct original x by inverting the permutation, i.e.,
442 // for each index i, the original index is given by col_perm[i].
443 std::array<T, N> tmp = x;
444 for (int i = 0; i < Nsol; ++i) { x[col_perm[i]] = tmp[i]; }
445}
446
447template<typename T, int N>
448void columnPivotingQRSolver(std::array<T, N * N>& M, std::array<T, N>& x, std::array<T, N>& b, const int Nsol) {
449 // Permutation array to record column swaps.
450 std::array<int, N> col_perm{};
451 for (int i = 0; i < N; ++i) { col_perm[i] = i; }
452
453 // Pre-computed column norm array, i.e., L2-normalization of each column.
454 std::array<T, N> col_norm{}, col_norm_tmp{};
455 for (int i = 0; i < Nsol; ++i) {
456 T norm = T(0);
457 for (int j = 0; j < Nsol; ++j) { norm += M[j * N + i] * M[j * N + i]; }
458 col_norm[i] = util::sqrt(norm);
459 col_norm_tmp[i] = col_norm[i];
460 }
461
462 // QR decomposition method with column pivoting.
463 for (int i = 0; i < Nsol; ++i) {
464 int pivotCol = i;
465 T maxValue = col_norm[i];
466
467 for (int j = i + 1; j < Nsol; ++j) {
468 if (col_norm[j] > maxValue) {
469 maxValue = col_norm[j];
470 pivotCol = j;
471 }
472 }
473
474 if (pivotCol != i) {
475 for (int k = 0; k < Nsol; ++k) { std::swap(M[k * N + i], M[k * N + pivotCol]); }
476 std::swap(col_perm[i], col_perm[pivotCol]);
477 std::swap(col_norm[i], col_norm[pivotCol]);
478 std::swap(col_norm_tmp[i], col_norm_tmp[pivotCol]);
479 }
480
481 // Compute the L2-norm of the i-th column, i.e., range is from row (i) to (Nsol - 1),
482 // and the reflection factor (r)
483 T r = T(0);
484 {
485 T norm = T(0);
486 for (int k = i; k < Nsol; ++k) { norm += M[k * N + i] * M[k * N + i]; }
488 r = M[i * N + i] >= T(0) ? -norm : norm;
489 }
490
491 // Prepare and normalize the Householder array for the i-th column.
492 std::array<T, N> v{};
493 {
494 v[0] = M[i * N + i] - r;
495 for (int k = i + 1; k < Nsol; ++k) { v[k - i] = M[k * N + i]; }
496
497 T norm = T(0);
498 for (int k = 0; k < Nsol - i; ++k) { norm += v[k] * v[k]; }
500
501 // Otherwise, the Householder array is already zero and no reflection is applied.
502 if (norm != T(0)) {
503 for (int k = 0; k < Nsol - i; ++k) { v[k] /= norm; }
504 }
505 }
506
507 // Apply the Householder reflection to the remaining columns.
508 for (int k = i; k < Nsol; ++k) {
509 T dot_product = T(0);
510 for (int j = 0; j < Nsol - i; ++j) { dot_product += v[j] * M[(j + i) * N + k]; }
511
512 dot_product *= T(2);
513 for (int j = 0; j < Nsol - i; ++j) { M[(j + i) * N + k] -= v[j] * dot_product; }
514 }
515
516 // Apply the Householder reflection to the right-hand side array.
517 {
518 T dot_product = T(0);
519 for (int k = 0; k < Nsol - i; ++k) { dot_product += v[k] * b[k + i]; }
520
521 dot_product *= T(2);
522 for (int k = 0; k < Nsol - i; ++k) { b[k + i] -= v[k] * dot_product; }
523 }
524
525 // Update the column norm array, i.e., adapted from LAPACK’s DGEQP3 routine.
526 for (int k = i + 1; k < Nsol; ++k) {
527 T tmp = col_norm[k] * col_norm[k] - M[i * N + k] * M[i * N + k];
528
529 // Recompute L2-norm from scratch, if tmp becomes negative or it drops below
530 // a specified threshold, e.g., T(0.1)
531 if (tmp <= T(0) || util::sqrt(tmp) < T(0.1) * col_norm_tmp[k]) {
532 T norm = T(0);
533 for (int j = i + 1; j < Nsol; ++j) { norm += M[j * N + k] * M[j * N + k]; }
534 col_norm[k] = util::sqrt(norm);
535 col_norm_tmp[k] = col_norm[k];
536 }
537 else {
538 col_norm[k] = util::sqrt(tmp);
539 }
540 }
541
542 // For the i-th column, replace diagonal elements with (r) and the non-diagonal elements with zero.
543 M[i * N + i] = r;
544 for (int k = i + 1; k < Nsol; ++k) { M[k * N + i] = T(0); }
545 }
546
547 // Backward substitution step, i.e., solve R*x = Qáµ€*b in place.
548 for (int i = Nsol - 1; i >= 0; --i) {
549 T sum = T(0);
550 for (int j = i + 1; j < Nsol; ++j) { sum += M[i * N + j] * x[j]; }
551 x[i] = (b[i] - sum) / M[i * N + i];
552 }
553
554 // Reconstruct x by inverting the permutation, i.e., for each
555 // index i, the original index is given by col_perm[i].
556 std::array<T, N> tmp = x;
557 for (int i = 0; i < Nsol; ++i) { x[col_perm[i]] = tmp[i]; }
558}
559
560// Offset helper: optimized PLIC algorithm taken from Moritz Lehmann FluidX3D
561template<typename T>
562T plicCubeReduced(const T& volume, const Vector<T, 3>& n) {
563 const T n1 = n[0], n2 = n[1], n3 = n[2];
564 const T n12 = n1 + n2, n3V = n3 * volume;
565
566 // (I) Case 5
567 if (n12 <= T(2) * n3V) { return n3V + T(0.5) * n12; }
568
569 // (II) After case 5, check n2 > 0 is true
570 const T sqn1 = util::sqr(n1), n26 = T(6) * n2, v1 = sqn1 / n26;
571
572 // (III) Case 2
573 if (v1 <= n3V && n3V < v1 + T(0.5) * (n2 - n1)) {
574 return T(0.5) * (n1 + util::sqrt(sqn1 + T(8) * n2 * (n3V - v1)));
575 }
576
577 // (IV) Case 1
578 const T V6 = n1 * n26 * n3V;
579 if (n3V < v1) { return std::cbrt(V6); }
580
581 // (V) After case 2, check n1 > 0 is true
582 const T v3 = n3 < n12 ? (util::sqr(n3) * (T(3) * n12 - n3) + sqn1 * (n1 - T(3) * n3) + util::sqr(n2) * (n2 - T(3) * n3)) / (n1 * n26) : T(0.5) * n12;
583 const T sqn12 = sqn1 + util::sqr(n2), V6cbn12 = V6 - util::cube(n1) - util::cube(n2);
584 const bool case34 = n3V < v3; // true: case (3), false: case (4)
585 const T a = case34 ? V6cbn12 : T(0.5) * (V6cbn12 - util::cube(n3));
586 const T b = case34 ? sqn12 : T(0.5) * (sqn12 + util::sqr(n3));
587 const T c = case34 ? n12 : T(0.5);
588 const T t = util::sqrt(util::sqr(c) - b);
589 static constexpr T oneThird = T(1) / T(3);
590
591 return c - T(2) * t * util::sin(oneThird * util::asin((util::cube(c) - T(0.5) * a - T(1.5) * b * c) / util::cube(t)));
592}
593
594// Offset computer: optimized PLIC algorithm taken from Moritz Lehmann FluidX3D
595template<typename T>
596T plicCube(const T& volume, const Vector<T, 3>& n) {
597 // Unit cube-plane intersection: using volume and normal vector to compute the offset
598 const T ax = util::abs(n[0]), ay = util::abs(n[1]), az = util::abs(n[2]);
599
600 // Eliminate symmetry cases, and normalize n using L1 norm
601 const T V = T(0.5) - util::abs(volume - T(0.5)), l = ax + ay + az;
602 const T n1 = util::min(util::min(ax, ay), az) / l;
603 const T n3 = util::max(util::max(ax, ay), az) / l;
604 const T n2 = std::fdim(T(1), n1 + n3); // ensure n2 >= 0
605 const T d = plicCubeReduced<T>(V, {n1, n2, n3});
606
607 return l * std::copysign(T(0.5) - d, volume - T(0.5));
608}
609
610// Volume computer: optimized inverse PLIC algorithm taken from Moritz Lehmann FluidX3D
611template<typename T>
612T plicCubeInverse(const T& d0, const Vector<T, 3>& n) {
613 // Eliminate majority of cases due to symmetry
614 const T n1 = util::min(util::min(util::abs(n[0]), util::abs(n[1])), util::abs(n[2]));
615 const T n3 = util::max(util::max(util::abs(n[0]), util::abs(n[1])), util::abs(n[2]));
616 const T n2 = util::abs(n[0]) - n1 + util::abs(n[1]) + util::abs(n[2]) - n3;
617
618 // Compute PLIC using reduced symmetry, shifting origin from (0.0, 0.0, 0.0) to (0.5, 0.5, 0.5)
619 const T d = T(0.5) * (n1 + n2 + n3) - util::abs(d0);
620
621 T V = T(0);
622 if (util::min(n1 + n2, n3) <= d && d <= n3) {
623 // (I) Case 5
624 V = (d - T(0.5) * (n1 + n2)) / n3;
625 }
626 else if (d < n1) {
627 // (II) Case 1
628 V = util::cube(d) / (T(6) * n1 * n2 * n3);
629 }
630 else if (d <= n2) {
631 // (III) Case 2
632 V = (T(3) * d * (d - n1) + util::sqr(n1)) / (T(6) * n2 * n3);
633 }
634 else {
635 // (IV) Case 3 or 4
636 V = (util::cube(d) - util::cube(d - n1) - util::cube(d - n2) - util::cube(std::fdim(d, n3))) / (T(6) * n1 * n2 * n3);
637 }
638
639 return std::copysign(T(0.5) - V, d0) + V(0.5);
640}
641
642// Compute 2D interface curvature using Moritz Lehmann's FluidX3D algorithm
643template <typename CELL, typename V>
644V computeCurvaturePLIC2D(CELL& cell) {
645 using DESCRIPTOR = typename CELL::descriptor_t;
646
647 // Setup a new coordinate system where bz is perpendicular to the surface, while bx and by are
648 // tangent to the surface.
649 const auto normal = computeInterfaceNormal(cell);
650 Vector<V, 3> by = {normal[0], normal[1], V(0)};
651 if (norm_squared(by) < zeroThreshold<V>()) { return V(0); }
652 by = util::normalize(by);
653
654 const Vector<V, 3> rn{V(0), V(0), V(1)};
655 const Vector<V, 3> bx = crossProduct(by, rn);
656
657 // Number of healthy interface neighbours
658 std::uint8_t nbrInterfaces = std::uint8_t(0);
659
660 // Compute z-offset of center point using PLIC cube
661 const V centerOffset = plicCube<V>(getClampedEpsilon(cell), by);
662
663 // Number of neighbouring interface points, i.e., less than or equal to 8 (neighbours) - 1 (liquid) - 1 (gas).
664 std::array<Vector<V, DESCRIPTOR::d>, 6> points;
665
666 for (int iPop = 1; iPop < DESCRIPTOR::q; ++iPop) {
667 auto nbrCell = cell.neighbor(descriptors::c<DESCRIPTOR>(iPop));
668
669 // Check if the neighbour is an interface cell or has a gas neighbour
670 if (!isCellType(nbrCell, FreeSurface::Type::Interface) || !hasNeighbour(nbrCell, FreeSurface::Type::Gas)) {
671 continue;
672 }
673
674 // Here it is assumed that neighbor normal vector is the same as center normal vector
675 const auto direction = descriptors::c<DESCRIPTOR>(iPop);
676 const Vector<V, 3> ei = {V(direction[0]), V(direction[1]), V(0)};
677 const V offset = plicCube<V>(getClampedEpsilon(nbrCell), by) - centerOffset;
678
679 // Transform coordinate system into (x, f(x)) and apply PLIC offset
680 points[nbrInterfaces++] = {util::dotProduct(ei, bx), util::dotProduct(ei, by) + offset};
681 }
682
683 // Prepare the linear equation system, to be initialized with zeros
684 std::array<V, 4> M{};
685 std::array<V, 2> solution{};
686 std::array<V, 2> b{};
687
688 for (std::uint8_t i = 0; i < nbrInterfaces; ++i) {
689 const V x = points[i][0];
690 const V y = points[i][1];
691 const V x2 = util::sqr(x);
692 const V x3 = util::sqr(x) * x;
693
694 M[0] += x2 * x2;
695 M[1] += x3;
696 M[3] += x2;
697
698 b[0] += x2 * y;
699 b[1] += x * y;
700 }
701
702 // Fill the lower triangle of the symmetric matrix
703 M[2] = M[1];
704
705 // Solve the linear system of equations, compile-time selection.
706 if constexpr (solver == SolverType::rowPivotingLU) {
707 if (nbrInterfaces >= 2) {
708 rowPivotingLUSolver<V, 2>(M, solution, b, 2);
709 } else {
710 rowPivotingLUSolver<V, 2>(M, solution, b, util::min(2, nbrInterfaces));
711 }
712 }
713 else if constexpr (solver == SolverType::completePivotingLU) {
714 if (nbrInterfaces >= 2) {
715 completePivotingLUSolver<V, 2>(M, solution, b, 2);
716 } else {
717 completePivotingLUSolver<V, 2>(M, solution, b, util::min(2, nbrInterfaces));
718 }
719 }
720 else if constexpr (solver == SolverType::columnPivotingQR) {
721 if (nbrInterfaces >= 2) {
722 columnPivotingQRSolver<V, 2>(M, solution, b, 2);
723 } else {
724 columnPivotingQRSolver<V, 2>(M, solution, b, util::min(2, nbrInterfaces));
725 }
726 }
727 else {
728 // Fall back to default solver
729 if (nbrInterfaces >= 2) {
730 completePivotingLUSolver<V, 2>(M, solution, b, 2);
731 } else {
732 completePivotingLUSolver<V, 2>(M, solution, b, util::min(2, nbrInterfaces));
733 }
734 }
735
736 const V A = solution[0], H = solution[1];
737 const V curvature = V(2) * A * util::cube(V(1) / util::sqrt(H * H + V(1)));
738 return util::max(V(-1), util::min(V(1), curvature));
739}
740
741// Compute 3D interface curvature using Moritz Lehmann's FluidX3D algorithm
742template <typename CELL, typename V>
743V computeCurvaturePLIC3D(CELL& cell) {
744 using DESCRIPTOR = typename CELL::descriptor_t;
745
746 // Setup a new coordinate system where bz is perpendicular to the surface, while bx and by are
747 // tangent to the surface.
748 auto bz = computeInterfaceNormal(cell);
749 if (norm_squared(bz) < zeroThreshold<V>()) { return V(0); }
750 bz = util::normalize(bz);
751
752 // A random normalized vector that is just by random chance not collinear with bz
753 // const Vector<V, DESCRIPTOR::d> rn{V(0.56270900), V(0.32704452), V(0.75921047)};
754 const Vector<V, DESCRIPTOR::d> rn{V(0.5402151198529208), V(0.2765640977634561), V(0.7947829415070379)};
755
756 // Normalization is necessary here because bz and rn are not perpendicular
758 const Vector<V, DESCRIPTOR::d> bx = crossProduct(by, bz);
759
760 // Number of healthy interface neighbours
761 std::uint8_t nbrInterfaces = std::uint8_t(0);
762
763 // Compute z-offset of center point using PLIC cube
764 const V centerOffset = plicCube<V>(getClampedEpsilon(cell), bz);
765
766 // Number of neighbouring interface points, i.e., less than or equal to 26 (neighbours) - 1 (liquid) - 1 (gas).
767 std::array<Vector<V, DESCRIPTOR::d>, 24> points;
768
769 for (int iPop = 1; iPop < DESCRIPTOR::q; ++iPop) {
770 auto nbrCell = cell.neighbor(descriptors::c<DESCRIPTOR>(iPop));
771
772 // Check if the neighbour is an interface cell or has a gas neighbour
773 if (!isCellType(nbrCell, FreeSurface::Type::Interface) || !hasNeighbour(nbrCell, FreeSurface::Type::Gas)) {
774 continue;
775 }
776
777 // Here it is assumed that neighbor normal vector is the same as center normal vector
778 const auto direction = descriptors::c<DESCRIPTOR>(iPop);
779 const Vector<V, DESCRIPTOR::d> ei = {V(direction[0]), V(direction[1]), V(direction[2])};
780 const V offset = plicCube<V>(getClampedEpsilon(nbrCell), bz) - centerOffset;
781
782 // Transform coordinate system into (x, y, f(x,y)) and apply PLIC offset
783 points[nbrInterfaces++] = {util::dotProduct(ei, bx), util::dotProduct(ei, by), util::dotProduct(ei, bz) + offset};
784 }
785
786 // Prepare the linear equation system, to be initialized with zeros
787 std::array<V, 25> M{};
788 std::array<V, 5> solution{};
789 std::array<V, 5> b{};
790
791 for (std::uint8_t i = 0; i < nbrInterfaces; ++i) {
792 const V x = points[i][0];
793 const V y = points[i][1];
794 const V z = points[i][2];
795 const V x2 = util::sqr(x);
796 const V y2 = util::sqr(y);
797 const V x3 = x2 * x;
798 const V y3 = y2 * y;
799
800 M[0] += x2 * x2;
801 M[1] += x2 * y2;
802 M[2] += x3 * y;
803 M[3] += x3;
804 M[4] += x2 * y;
805 M[6] += y2 * y2;
806 M[7] += x * y3;
807 M[8] += x * y2;
808 M[9] += y3;
809 M[12] += x2 * y2;
810 M[13] += x2 * y;
811 M[14] += x * y2;
812 M[18] += x2;
813 M[19] += x * y;
814 M[24] += y2;
815
816 b[0] += x2 * z;
817 b[1] += y2 * z;
818 b[2] += x * y * z;
819 b[3] += x * z;
820 b[4] += y * z;
821 }
822
823 // Fill the lower triangle of the symmetric matrix
824 for (std::uint8_t i = 1; i < 5; ++i) {
825 for (std::uint8_t j = 0; j < i; ++j) {
826 M[i * 5 + j] = M[j * 5 + i];
827 }
828 }
829
830 // Solve the linear system of equations, compile-time selection.
831 if constexpr (solver == SolverType::rowPivotingLU) {
832 if (nbrInterfaces >= 5) {
833 rowPivotingLUSolver<V, 5>(M, solution, b, 5);
834 } else {
835 rowPivotingLUSolver<V, 5>(M, solution, b, util::min(5, nbrInterfaces));
836 }
837 }
838 else if constexpr (solver == SolverType::completePivotingLU) {
839 if (nbrInterfaces >= 5) {
840 completePivotingLUSolver<V, 5>(M, solution, b, 5);
841 } else {
842 completePivotingLUSolver<V, 5>(M, solution, b, util::min(5, nbrInterfaces));
843 }
844 }
845 else if constexpr (solver == SolverType::columnPivotingQR) {
846 if (nbrInterfaces >= 5) {
847 columnPivotingQRSolver<V, 5>(M, solution, b, 5);
848 } else {
849 columnPivotingQRSolver<V, 5>(M, solution, b, util::min(5, nbrInterfaces));
850 }
851 }
852 else {
853 // Fall back to default solver
854 if (nbrInterfaces >= 5) {
855 completePivotingLUSolver<V, 5>(M, solution, b, 5);
856 } else {
857 completePivotingLUSolver<V, 5>(M, solution, b, util::min(5, nbrInterfaces));
858 }
859 }
860
861 const V A = solution[0], B = solution[1], C = solution[2], H = solution[3], I = solution[4];
862 const V curvature = (A * (I * I + V(1)) + B * (H * H + V(1)) - C * H * I) * util::cube(V(1) / util::sqrt(H * H + I * I + V(1)));
863 return util::max(V(-1), util::min(V(1), curvature));
864}
865
866template <typename CELL, typename V>
867V computeCurvatureFDM3D(CELL& cell) {
868 using DESCRIPTOR = typename CELL::descriptor_t;
869
870 const auto normal = computeInterfaceNormal(cell);
871 if (norm_squared(normal) < zeroThreshold<V>()) { return V(0); }
872
873 const std::uint32_t gaussianMultipliers [DESCRIPTOR::q] =
874 {
875 std::uint32_t(8),
876 std::uint32_t(4), std::uint32_t(4), std::uint32_t(4),
877 std::uint32_t(2), std::uint32_t(2), std::uint32_t(2),
878 std::uint32_t(2), std::uint32_t(2), std::uint32_t(2),
879 std::uint32_t(1), std::uint32_t(1), std::uint32_t(1), std::uint32_t(1),
880 std::uint32_t(4), std::uint32_t(4), std::uint32_t(4),
881 std::uint32_t(2), std::uint32_t(2), std::uint32_t(2),
882 std::uint32_t(2), std::uint32_t(2), std::uint32_t(2),
883 std::uint32_t(1), std::uint32_t(1), std::uint32_t(1), std::uint32_t(1)
884 };
885
886 V curvature = V(0);
887 V weightSum = V(0);
888
889 for (int iPop = 1; iPop < DESCRIPTOR::q; ++iPop) {
890 auto direction = descriptors::c<DESCRIPTOR>(iPop);
891 auto nbrCell = cell.neighbor(direction);
892
893 // Check if the neighbour is an interface cell or has a gas neighbour
894 if (!isCellType(nbrCell, FreeSurface::Type::Interface) || !hasNeighbour(nbrCell, FreeSurface::Type::Gas)) {
895 continue;
896 }
897
898 const Vector<V, DESCRIPTOR::d> ei = {V(direction[0]), V(direction[1]), V(direction[2])};
899 const auto nbrNormal = util::normalize(computeInterfaceNormal(nbrCell));
900 const V weight = gaussianMultipliers[iPop];
901 weightSum += weight;
902 curvature += weight * util::dotProduct(ei, nbrNormal);
903 }
904
905 curvature /= weightSum;
906 return util::max(V(-1), util::min(V(1), curvature));
907}
908
909template <typename CELL, typename V>
910V computeCurvature(CELL& cell) {
911 using DESCRIPTOR = typename CELL::descriptor_t;
912
913 if constexpr (DESCRIPTOR::d == 2) {
914 return computeCurvaturePLIC2D(cell);
915 } else if (DESCRIPTOR::d == 3) {
916 return computeCurvaturePLIC3D(cell);
917 }
918
919 return V(0);
920}
921
922} // namespace FreeSurface
923
924} // namespace olb
Super class maintaining block lattices for a cuboid decomposition.
void executePostProcessors(STAGE stage=STAGE())
Executes post processors for STAGE.
Plain old scalar vector.
constexpr SolverType solver
void initialize(SuperLattice< T, DESCRIPTOR > &sLattice)
constexpr T zeroThreshold()
constexpr int c(unsigned iPop, unsigned iDim) any_platform
Definition functions.h:83
ADf< T, DIM > abs(const ADf< T, DIM > &a)
Definition aDiff.h:1019
Expr sqrt(Expr x)
Definition expr.cpp:225
any_platform T sqr(T arg) any_platform
Definition util.h:132
Expr min(Expr a, Expr b)
Definition expr.cpp:249
Expr max(Expr a, Expr b)
Definition expr.cpp:245
any_platform T cube(T arg)
Definition util.h:138
ADf< T, DIM > sin(const ADf< T, DIM > &a)
Definition aDiff.h:569
T dotProduct(const Vector< T, D > &a, const Vector< T, D > &b)
dot product
ADf< T, DIM > asin(const ADf< T, DIM > &a)
Definition aDiff.h:596
Vector< T, D > normalize(const Vector< T, D > &a)
Top level namespace for all of OpenLB.
constexpr T norm_squared(const ScalarVector< T, D, IMPL > &a) any_platform
Squared euclidean vector norm.
auto crossProduct(const ScalarVector< T, D, IMPL > &a, const ScalarVector< T, D, IMPL_ > &b) any_platform
Definition vector.h:274
constexpr T norm(const ScalarVector< T, D, IMPL > &a) any_platform
Euclidean vector norm.