ryujin 2.1.1 revision bfc84abcd32241a454001e0cb9cf52f3c8021f70
transfinite_interpolation.template.h
Go to the documentation of this file.
1//
2// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception or LGPL-2.1-or-later
3// Copyright (C) 2007 - 2022 by Martin Kronbichler
4// Copyright (C) 2008 - 2022 by David Wells
5// Copyright (C) 2020 - 2023 by the ryujin authors
6//
7
8#pragma once
9
11
12#include <boost/container/small_vector.hpp>
13
14#include <deal.II/base/table.h>
15
16namespace ryujin
17{
18
19 namespace internal
20 {
21 static constexpr double invalid_pull_back_coordinate = 20.0;
22 }
23
24
25 template <int dim, int spacedim>
27 spacedim>::TransfiniteInterpolationManifold()
28 : level_coarse(-1)
29 {
30 AssertThrow(dim > 1, ExcNotImplemented());
31 }
32
33
34 template <int dim, int spacedim>
35 std::unique_ptr<Manifold<dim, spacedim>>
37 {
39 if (triangulation.n_levels() != 0)
40 ptr->initialize(triangulation, *chart_manifold);
41 return std::unique_ptr<Manifold<dim, spacedim>>(ptr);
42 }
43
44
45 template <int dim, int spacedim>
47 const Triangulation<dim, spacedim> &external_triangulation,
48 const Manifold<dim, spacedim> &chart_manifold)
49 {
50 this->triangulation.clear();
51 this->triangulation.copy_triangulation(external_triangulation);
52
53 this->chart_manifold = chart_manifold.clone();
54
55 level_coarse = triangulation.last()->level();
56 coarse_cell_is_flat.resize(triangulation.n_cells(level_coarse), false);
57 typename Triangulation<dim, spacedim>::active_cell_iterator
58 cell = triangulation.begin(level_coarse),
59 endc = triangulation.end(level_coarse);
60 for (; cell != endc; ++cell) {
61 bool cell_is_flat = true;
62 for (unsigned int l = 0; l < GeometryInfo<dim>::lines_per_cell; ++l)
63 if (cell->line(l)->manifold_id() != cell->manifold_id() &&
64 cell->line(l)->manifold_id() != numbers::flat_manifold_id)
65 cell_is_flat = false;
66 if (dim > 2)
67 for (unsigned int q = 0; q < GeometryInfo<dim>::quads_per_cell; ++q)
68 if (cell->quad(q)->manifold_id() != cell->manifold_id() &&
69 cell->quad(q)->manifold_id() != numbers::flat_manifold_id)
70 cell_is_flat = false;
71 AssertIndexRange(static_cast<unsigned int>(cell->index()),
72 coarse_cell_is_flat.size());
73 coarse_cell_is_flat[cell->index()] = cell_is_flat;
74 }
75 }
76
77
78 namespace
79 {
80 // version for 1D
81 template <typename AccessorType>
82 Point<AccessorType::space_dimension>
83 compute_transfinite_interpolation(const AccessorType &cell,
84 const Point<1> &chart_point,
85 const bool /*cell_is_flat*/)
86 {
87 return cell.vertex(0) * (1. - chart_point[0]) +
88 cell.vertex(1) * chart_point[0];
89 }
90
91 // version for 2D
92 template <typename AccessorType>
93 Point<AccessorType::space_dimension>
94 compute_transfinite_interpolation(const AccessorType &cell,
95 const Point<2> &chart_point,
96 const bool cell_is_flat)
97 {
98 const unsigned int dim = AccessorType::dimension;
99 const unsigned int spacedim = AccessorType::space_dimension;
100 const types::manifold_id my_manifold_id = cell.manifold_id();
101 const Triangulation<dim, spacedim> &tria = cell.get_triangulation();
102
103 // formula see wikipedia
104 // https://en.wikipedia.org/wiki/Transfinite_interpolation
105 // S(u,v) = (1-v)c_1(u)+v c_3(u) + (1-u)c_2(v) + u c_4(v) -
106 // [(1-u)(1-v)P_0 + u(1-v) P_1 + (1-u)v P_2 + uv P_3]
107 const std::array<Point<spacedim>, 4> vertices{
108 {cell.vertex(0), cell.vertex(1), cell.vertex(2), cell.vertex(3)}};
109
110 // this evaluates all bilinear shape functions because we need them
111 // repeatedly. we will update this values in the complicated case with
112 // curved lines below
113 std::array<double, 4> weights_vertices{
114 {(1. - chart_point[0]) * (1. - chart_point[1]),
115 chart_point[0] * (1. - chart_point[1]),
116 (1. - chart_point[0]) * chart_point[1],
117 chart_point[0] * chart_point[1]}};
118
119 Point<spacedim> new_point;
120 if (cell_is_flat)
121 for (const unsigned int v : GeometryInfo<2>::vertex_indices())
122 new_point += weights_vertices[v] * vertices[v];
123 else {
124 // The second line in the formula tells us to subtract the
125 // contribution of the vertices. If a line employs the same manifold
126 // as the cell, we can merge the weights of the line with the weights
127 // of the vertex with a negative sign while going through the faces
128 // (this is a bit artificial in 2D but it becomes clear in 3D where we
129 // avoid looking at the faces' orientation and other complications).
130
131 // add the contribution from the lines around the cell (first line in
132 // formula)
133 std::array<double, GeometryInfo<2>::vertices_per_face> weights;
134 std::array<Point<spacedim>, GeometryInfo<2>::vertices_per_face> points;
135 // note that the views are immutable, but the arrays are not
136 const auto weights_view =
137 make_array_view(weights.begin(), weights.end());
138 const auto points_view = make_array_view(points.begin(), points.end());
139
140 for (unsigned int line = 0; line < GeometryInfo<2>::lines_per_cell;
141 ++line) {
142 const double my_weight =
143 (line % 2) ? chart_point[line / 2] : 1 - chart_point[line / 2];
144 const double line_point = chart_point[1 - line / 2];
145
146 // Same manifold or invalid id which will go back to the same
147 // class -> contribution should be added for the final point,
148 // which means that we subtract the current weight from the
149 // negative weight applied to the vertex
150 const types::manifold_id line_manifold_id =
151 cell.line(line)->manifold_id();
152 if (line_manifold_id == my_manifold_id ||
153 line_manifold_id == numbers::flat_manifold_id) {
154 weights_vertices[GeometryInfo<2>::line_to_cell_vertices(line, 0)] -=
155 my_weight * (1. - line_point);
156 weights_vertices[GeometryInfo<2>::line_to_cell_vertices(line, 1)] -=
157 my_weight * line_point;
158 } else {
159 points[0] =
160 vertices[GeometryInfo<2>::line_to_cell_vertices(line, 0)];
161 points[1] =
162 vertices[GeometryInfo<2>::line_to_cell_vertices(line, 1)];
163 weights[0] = 1. - line_point;
164 weights[1] = line_point;
165 new_point +=
166 my_weight * tria.get_manifold(line_manifold_id)
167 .get_new_point(points_view, weights_view);
168 }
169 }
170
171 // subtract contribution from the vertices (second line in formula)
172 for (const unsigned int v : GeometryInfo<2>::vertex_indices())
173 new_point -= weights_vertices[v] * vertices[v];
174 }
175
176 return new_point;
177 }
178
179 // this is replicated from GeometryInfo::face_to_cell_vertices since we need
180 // it very often in compute_transfinite_interpolation and the function is
181 // performance critical
182 static constexpr unsigned int face_to_cell_vertices_3d[6][4] = {
183 {0, 2, 4, 6},
184 {1, 3, 5, 7},
185 {0, 4, 1, 5},
186 {2, 6, 3, 7},
187 {0, 1, 2, 3},
188 {4, 5, 6, 7}};
189
190 // this is replicated from GeometryInfo::face_to_cell_lines since we need it
191 // very often in compute_transfinite_interpolation and the function is
192 // performance critical
193 static constexpr unsigned int face_to_cell_lines_3d[6][4] = {{8, 10, 0, 4},
194 {9, 11, 1, 5},
195 {2, 6, 8, 9},
196 {3, 7, 10, 11},
197 {0, 1, 2, 3},
198 {4, 5, 6, 7}};
199
200 // version for 3D
201 template <typename AccessorType>
202 Point<AccessorType::space_dimension>
203 compute_transfinite_interpolation(const AccessorType &cell,
204 const Point<3> &chart_point,
205 const bool cell_is_flat)
206 {
207 const unsigned int dim = AccessorType::dimension;
208 const unsigned int spacedim = AccessorType::space_dimension;
209 const types::manifold_id my_manifold_id = cell.manifold_id();
210 const Triangulation<dim, spacedim> &tria = cell.get_triangulation();
211
212 // Same approach as in 2D, but adding the faces, subtracting the edges,
213 // and adding the vertices
214 const std::array<Point<spacedim>, 8> vertices{{cell.vertex(0),
215 cell.vertex(1),
216 cell.vertex(2),
217 cell.vertex(3),
218 cell.vertex(4),
219 cell.vertex(5),
220 cell.vertex(6),
221 cell.vertex(7)}};
222
223 // store the components of the linear shape functions because we need them
224 // repeatedly. we allow for 10 such shape functions to wrap around the
225 // first four once again for easier face access.
226 double linear_shapes[10];
227 for (unsigned int d = 0; d < 3; ++d) {
228 linear_shapes[2 * d] = 1. - chart_point[d];
229 linear_shapes[2 * d + 1] = chart_point[d];
230 }
231
232 // wrap linear shape functions around for access in face loop
233 for (unsigned int d = 6; d < 10; ++d)
234 linear_shapes[d] = linear_shapes[d - 6];
235
236 std::array<double, 8> weights_vertices;
237 for (unsigned int i2 = 0, v = 0; i2 < 2; ++i2)
238 for (unsigned int i1 = 0; i1 < 2; ++i1)
239 for (unsigned int i0 = 0; i0 < 2; ++i0, ++v)
240 weights_vertices[v] =
241 (linear_shapes[4 + i2] * linear_shapes[2 + i1]) *
242 linear_shapes[i0];
243
244 Point<spacedim> new_point;
245 if (cell_is_flat)
246 for (unsigned int v = 0; v < 8; ++v)
247 new_point += weights_vertices[v] * vertices[v];
248 else {
249 // identify the weights for the lines to be accumulated (vertex
250 // weights are set outside and coincide with the flat manifold case)
251
252 std::array<double, GeometryInfo<3>::lines_per_cell> weights_lines;
253 std::fill(weights_lines.begin(), weights_lines.end(), 0.0);
254
255 // start with the contributions of the faces
256 std::array<double, GeometryInfo<2>::vertices_per_cell> weights;
257 std::array<Point<spacedim>, GeometryInfo<2>::vertices_per_cell> points;
258 // note that the views are immutable, but the arrays are not
259 const auto weights_view =
260 make_array_view(weights.begin(), weights.end());
261 const auto points_view = make_array_view(points.begin(), points.end());
262
263 for (const unsigned int face : GeometryInfo<3>::face_indices()) {
264 const double my_weight = linear_shapes[face];
265 const unsigned int face_even = face - face % 2;
266
267 if (std::abs(my_weight) < 1e-13)
268 continue;
269
270 // same manifold or invalid id which will go back to the same class
271 // -> face will interpolate from the surrounding lines and vertices
272 const types::manifold_id face_manifold_id =
273 cell.face(face)->manifold_id();
274 if (face_manifold_id == my_manifold_id ||
275 face_manifold_id == numbers::flat_manifold_id) {
276 for (unsigned int line = 0; line < GeometryInfo<2>::lines_per_cell;
277 ++line) {
278 const double line_weight = linear_shapes[face_even + 2 + line];
279 weights_lines[face_to_cell_lines_3d[face][line]] +=
280 my_weight * line_weight;
281 }
282 // as to the indices inside linear_shapes: we use the index
283 // wrapped around at 2*d, ensuring the correct orientation of
284 // the face's coordinate system with respect to the
285 // lexicographic indices
286 weights_vertices[face_to_cell_vertices_3d[face][0]] -=
287 linear_shapes[face_even + 2] *
288 (linear_shapes[face_even + 4] * my_weight);
289 weights_vertices[face_to_cell_vertices_3d[face][1]] -=
290 linear_shapes[face_even + 3] *
291 (linear_shapes[face_even + 4] * my_weight);
292 weights_vertices[face_to_cell_vertices_3d[face][2]] -=
293 linear_shapes[face_even + 2] *
294 (linear_shapes[face_even + 5] * my_weight);
295 weights_vertices[face_to_cell_vertices_3d[face][3]] -=
296 linear_shapes[face_even + 3] *
297 (linear_shapes[face_even + 5] * my_weight);
298 } else {
299 for (const unsigned int v : GeometryInfo<2>::vertex_indices())
300 points[v] = vertices[face_to_cell_vertices_3d[face][v]];
301 weights[0] =
302 linear_shapes[face_even + 2] * linear_shapes[face_even + 4];
303 weights[1] =
304 linear_shapes[face_even + 3] * linear_shapes[face_even + 4];
305 weights[2] =
306 linear_shapes[face_even + 2] * linear_shapes[face_even + 5];
307 weights[3] =
308 linear_shapes[face_even + 3] * linear_shapes[face_even + 5];
309 new_point +=
310 my_weight * tria.get_manifold(face_manifold_id)
311 .get_new_point(points_view, weights_view);
312 }
313 }
314
315 // next subtract the contributions of the lines
316 const auto weights_view_line =
317 make_array_view(weights.begin(), weights.begin() + 2);
318 const auto points_view_line =
319 make_array_view(points.begin(), points.begin() + 2);
320 for (unsigned int line = 0; line < GeometryInfo<3>::lines_per_cell;
321 ++line) {
322 const double line_point =
323 (line < 8 ? chart_point[1 - (line % 4) / 2] : chart_point[2]);
324 double my_weight = 0.;
325 if (line < 8)
326 my_weight = linear_shapes[line % 4] * linear_shapes[4 + line / 4];
327 else {
328 const unsigned int subline = line - 8;
329 my_weight =
330 linear_shapes[subline % 2] * linear_shapes[2 + subline / 2];
331 }
332 my_weight -= weights_lines[line];
333
334 if (std::abs(my_weight) < 1e-13)
335 continue;
336
337 const types::manifold_id line_manifold_id =
338 cell.line(line)->manifold_id();
339 if (line_manifold_id == my_manifold_id ||
340 line_manifold_id == numbers::flat_manifold_id) {
341 weights_vertices[GeometryInfo<3>::line_to_cell_vertices(line, 0)] -=
342 my_weight * (1. - line_point);
343 weights_vertices[GeometryInfo<3>::line_to_cell_vertices(line, 1)] -=
344 my_weight * (line_point);
345 } else {
346 points[0] =
347 vertices[GeometryInfo<3>::line_to_cell_vertices(line, 0)];
348 points[1] =
349 vertices[GeometryInfo<3>::line_to_cell_vertices(line, 1)];
350 weights[0] = 1. - line_point;
351 weights[1] = line_point;
352 new_point -= my_weight * tria.get_manifold(line_manifold_id)
353 .get_new_point(points_view_line,
354 weights_view_line);
355 }
356 }
357
358 // finally add the contribution of the
359 for (const unsigned int v : GeometryInfo<dim>::vertex_indices())
360 new_point += weights_vertices[v] * vertices[v];
361 }
362 return new_point;
363 }
364 } // namespace
365
366
367 template <int dim, int spacedim>
368 Point<spacedim> TransfiniteInterpolationManifold<dim, spacedim>::push_forward(
369 const typename Triangulation<dim, spacedim>::cell_iterator &cell,
370 const Point<dim> &chart_point) const
371 {
372 AssertDimension(cell->level(), level_coarse);
373
374 // check that the point is in the unit cell which is the current chart
375 // Tolerance 5e-4 chosen that the method also works with manifolds
376 // that have some discretization error like SphericalManifold
377 Assert(GeometryInfo<dim>::is_inside_unit_cell(chart_point, 5e-4),
378 ExcMessage("chart_point is not in unit interval"));
379
380 return compute_transfinite_interpolation(
381 *cell, chart_point, coarse_cell_is_flat[cell->index()]);
382 }
383
384
385 template <int dim, int spacedim>
386 DerivativeForm<1, dim, spacedim>
387 TransfiniteInterpolationManifold<dim, spacedim>::push_forward_gradient(
388 const typename Triangulation<dim, spacedim>::cell_iterator &cell,
389 const Point<dim> &chart_point,
390 const Point<spacedim> &pushed_forward_chart_point) const
391 {
392 // compute the derivative with the help of finite differences
393 DerivativeForm<1, dim, spacedim> grad;
394 for (unsigned int d = 0; d < dim; ++d) {
395 Point<dim> modified = chart_point;
396 const double step = chart_point[d] > 0.5 ? -1e-8 : 1e-8;
397
398 // avoid checking outside of the unit interval
399 modified[d] += step;
400 Tensor<1, spacedim> difference =
401 compute_transfinite_interpolation(
402 *cell, modified, coarse_cell_is_flat[cell->index()]) -
403 pushed_forward_chart_point;
404 for (unsigned int e = 0; e < spacedim; ++e)
405 grad[e][d] = difference[e] / step;
406 }
407 return grad;
408 }
409
410
411 template <int dim, int spacedim>
412 Point<dim> TransfiniteInterpolationManifold<dim, spacedim>::pull_back(
413 const typename Triangulation<dim, spacedim>::cell_iterator &cell,
414 const Point<spacedim> &point,
415 const Point<dim> &initial_guess) const
416 {
417 Point<dim> outside;
418 for (unsigned int d = 0; d < dim; ++d)
420
421 // project the user-given input to unit cell
422 Point<dim> chart_point =
423 GeometryInfo<dim>::project_to_unit_cell(initial_guess);
424
425 // run quasi-Newton iteration with a combination of finite differences for
426 // the exact Jacobian and "Broyden's good method". As opposed to the various
427 // mapping implementations, this class does not throw exception upon failure
428 // as those are relatively expensive and failure occurs quite regularly in
429 // the implementation of the compute_chart_points method.
430 Tensor<1, spacedim> residual =
431 point - compute_transfinite_interpolation(
432 *cell, chart_point, coarse_cell_is_flat[cell->index()]);
433 const double tolerance =
434 1e-21 * Utilities::fixed_power<2>(cell->diameter());
435 double residual_norm_square = residual.norm_square();
436 DerivativeForm<1, dim, spacedim> inv_grad;
437 bool must_recompute_jacobian = true;
438 for (unsigned int i = 0; i < 100; ++i) {
439 if (residual_norm_square < tolerance) {
440 // do a final update of the point with the last available Jacobian
441 // information. The residual is close to zero due to the check
442 // above, but me might improve some of the last digits by a final
443 // Newton-like step with step length 1
444 Tensor<1, dim> update;
445 for (unsigned int d = 0; d < spacedim; ++d)
446 for (unsigned int e = 0; e < dim; ++e)
447 update[e] += inv_grad[d][e] * residual[d];
448 return chart_point + update;
449 }
450
451 // every 9 iterations, including the first time around, we create an
452 // approximation of the Jacobian with finite differences. Broyden's
453 // method usually does not need more than 5-8 iterations, but sometimes
454 // we might have had a bad initial guess and then we can accelerate
455 // convergence considerably with getting the actual Jacobian rather than
456 // using secant-like methods (one gradient calculation in 3D costs as
457 // much as 3 more iterations). this usually happens close to convergence
458 // and one more step with the finite-differenced Jacobian leads to
459 // convergence. however, we should not make the update too close to
460 // termination either because of cancellation effects an finite
461 // difference accuracy.
462 if (must_recompute_jacobian ||
463 (residual_norm_square > 1e4 * tolerance && i % 7 == 0)) {
464 // if the determinant is zero or negative, the mapping is either not
465 // invertible or already has inverted and we are outside the valid
466 // chart region. Note that the Jacobian here represents the
467 // derivative of the forward map and should have a positive
468 // determinant since we use properly oriented meshes.
469 DerivativeForm<1, dim, spacedim> grad = push_forward_gradient(
470 cell, chart_point, Point<spacedim>(point - residual));
471 if (grad.determinant() <= 0.0)
472 return outside;
473 inv_grad = grad.covariant_form();
474 must_recompute_jacobian = false;
475 }
476 Tensor<1, dim> update;
477 for (unsigned int d = 0; d < spacedim; ++d)
478 for (unsigned int e = 0; e < dim; ++e)
479 update[e] += inv_grad[d][e] * residual[d];
480
481 // Line search, accept step if the residual has decreased
482 double alpha = 1.;
483
484 // check if point is inside 1.2 times the unit cell to avoid
485 // hitting points very far away from valid ones in the manifolds
486 while (!GeometryInfo<dim>::is_inside_unit_cell(
487 chart_point + alpha * update, 0.2) &&
488 alpha > 1e-7)
489 alpha *= 0.5;
490
491 const Tensor<1, spacedim> old_residual = residual;
492 while (alpha > 1e-4) {
493 Point<dim> guess = chart_point + alpha * update;
494 const Tensor<1, dim> residual_guess =
495 point - compute_transfinite_interpolation(
496 *cell, guess, coarse_cell_is_flat[cell->index()]);
497 const double residual_norm_new = residual_guess.norm_square();
498 if (residual_norm_new < residual_norm_square) {
499 residual_norm_square = residual_norm_new;
500 chart_point += alpha * update;
501 residual = residual_guess;
502 break;
503 } else
504 alpha *= 0.5;
505 }
506 // If alpha got very small, it is likely due to a bad Jacobian
507 // approximation with Broyden's method (relatively far away from the
508 // zero), which can be corrected by the outer loop when a Newton update
509 // is recomputed. The second case is when we either have roundoff errors
510 // and cannot further improve the approximation or the Jacobian is
511 // actually bad and we should fail as early as possible. Since we cannot
512 // really distinguish the two, we must continue here in any case.
513 if (alpha <= 1e-4) {
514 // If we just recomputed the Jacobian by finite differences, we must
515 // stop. If the reached tolerance was sufficiently small (less than
516 // the square root of the tolerance), we return the best estimate,
517 // else we return the invalid point.
518 if (must_recompute_jacobian == true) {
519 return residual_norm_square < std::sqrt(tolerance) ? chart_point
520 : outside;
521 } else
522 must_recompute_jacobian = true;
523 }
524
525 // update the inverse Jacobian with "Broyden's good method" and
526 // Sherman-Morrison formula for the update of the inverse, see
527 // https://en.wikipedia.org/wiki/Broyden%27s_method
528 // J^{-1}_n = J^{-1}_{n-1} + (delta x_n - J^{-1}_{n-1} delta f_n) /
529 // (delta x_n^T J_{-1}_{n-1} delta f_n) delta x_n^T J^{-1}_{n-1}
530
531 // switch sign in residual as compared to the formula above because we
532 // use a negative definition of the residual with respect to the
533 // Jacobian
534 const Tensor<1, spacedim> delta_f = old_residual - residual;
535
536 Tensor<1, dim> Jinv_deltaf;
537 for (unsigned int d = 0; d < spacedim; ++d)
538 for (unsigned int e = 0; e < dim; ++e)
539 Jinv_deltaf[e] += inv_grad[d][e] * delta_f[d];
540
541 const Tensor<1, dim> delta_x = alpha * update;
542
543 // prevent division by zero. This number should be scale-invariant
544 // because Jinv_deltaf carries no units and x is in reference
545 // coordinates.
546 if (std::abs(delta_x * Jinv_deltaf) > 0.1 * tolerance &&
547 !must_recompute_jacobian) {
548 const Tensor<1, dim> factor =
549 (delta_x - Jinv_deltaf) / (delta_x * Jinv_deltaf);
550 Tensor<1, spacedim> jac_update;
551 for (unsigned int d = 0; d < spacedim; ++d)
552 for (unsigned int e = 0; e < dim; ++e)
553 jac_update[d] += delta_x[e] * inv_grad[d][e];
554 for (unsigned int d = 0; d < spacedim; ++d)
555 for (unsigned int e = 0; e < dim; ++e)
556 inv_grad[d][e] += factor[e] * jac_update[d];
557 }
558 }
559 return outside;
560 }
561
562
563 template <int dim, int spacedim>
564 std::array<unsigned int, 20> TransfiniteInterpolationManifold<dim, spacedim>::
565 get_possible_cells_around_points(
566 const ArrayView<const Point<spacedim>> &points) const
567 {
568 // The methods to identify cells around points in GridTools are all written
569 // for the active cells, but we are here looking at some cells at the coarse
570 // level.
571
572 // FIXME IMPROVE
573
574 Assert(triangulation.n_levels() != 0, ExcNotInitialized());
575 Assert(triangulation.begin_active()->level() == level_coarse,
576 ExcInternalError());
577
578 // This computes the distance of the surrounding points transformed to the
579 // unit cell from the unit cell.
580 auto cell = triangulation.begin(level_coarse);
581 const auto endc = triangulation.end(level_coarse);
582 boost::container::small_vector<std::pair<double, unsigned int>, 200>
583 distances_and_cells;
584 for (; cell != endc; ++cell) {
585 /* FIXME: Remove workaround - ignore certain cells. */
586 if (cell->material_id() == 42)
587 continue;
588
589 std::array<Point<spacedim>, GeometryInfo<dim>::vertices_per_cell>
590 vertices;
591 for (const unsigned int vertex_n : GeometryInfo<dim>::vertex_indices()) {
592 vertices[vertex_n] = cell->vertex(vertex_n);
593 }
594
595 // cheap check: if any of the points is not inside a circle around the
596 // center of the loop, we can skip the expensive part below (this assumes
597 // that the manifold does not deform the grid too much)
598 Point<spacedim> center;
599 for (const unsigned int v : GeometryInfo<dim>::vertex_indices())
600 center += vertices[v];
601 center *= 1. / GeometryInfo<dim>::vertices_per_cell;
602 double radius_square = 0.;
603 for (const unsigned int v : GeometryInfo<dim>::vertex_indices())
604 radius_square =
605 std::max(radius_square, (center - vertices[v]).norm_square());
606 bool inside_circle = true;
607 for (unsigned int i = 0; i < points.size(); ++i)
608 if ((center - points[i]).norm_square() > radius_square * 1.5) {
609 inside_circle = false;
610 break;
611 }
612 if (inside_circle == false)
613 continue;
614
615 // slightly more expensive search
616 double current_distance = 0;
617 for (unsigned int i = 0; i < points.size(); ++i) {
618 Point<dim> point =
619 cell->real_to_unit_cell_affine_approximation(points[i]);
620 current_distance += GeometryInfo<dim>::distance_to_unit_cell(point);
621 }
622 distances_and_cells.push_back(
623 std::make_pair(current_distance, cell->index()));
624 }
625 // no coarse cell could be found -> transformation failed
626 AssertThrow(distances_and_cells.size() > 0,
627 (typename Mapping<dim, spacedim>::ExcTransformationFailed()));
628 std::sort(distances_and_cells.begin(), distances_and_cells.end());
629 std::array<unsigned int, 20> cells;
630 cells.fill(numbers::invalid_unsigned_int);
631 for (unsigned int i = 0; i < distances_and_cells.size() && i < cells.size();
632 ++i)
633 cells[i] = distances_and_cells[i].second;
634
635 return cells;
636 }
637
638
639 template <int dim, int spacedim>
640 typename Triangulation<dim, spacedim>::cell_iterator
641 TransfiniteInterpolationManifold<dim, spacedim>::compute_chart_points(
642 const ArrayView<const Point<spacedim>> &surrounding_points,
643 ArrayView<Point<dim>> chart_points) const
644 {
645 Assert(surrounding_points.size() == chart_points.size(),
646 ExcMessage("The chart points array view must be as large as the "
647 "surrounding points array view."));
648
649 std::array<unsigned int, 20> nearby_cells =
650 get_possible_cells_around_points(surrounding_points);
651
652 // This function is nearly always called to place new points on a cell or
653 // cell face. In this case, the general structure of the surrounding points
654 // is known (i.e., if there are eight surrounding points, then they will
655 // almost surely be either eight points around a quadrilateral or the eight
656 // vertices of a cube). Hence, making this assumption, we use two
657 // optimizations (one for structdim == 2 and one for structdim == 3) that
658 // guess the locations of some of the chart points more efficiently than the
659 // affine map approximation. The affine map approximation is used whenever
660 // we don't have a cheaper guess available.
661
662 // Function that can guess the location of a chart point by assuming that
663 // the eight surrounding points are points on a two-dimensional object
664 // (either a cell in 2D or the face of a hexahedron in 3D), arranged like
665 //
666 // 2 - 7 - 3
667 // | |
668 // 4 5
669 // | |
670 // 0 - 6 - 1
671 //
672 // This function assumes that the first three chart points have been
673 // computed since there is no effective way to guess them.
674 auto guess_chart_point_structdim_2 =
675 [&](const unsigned int i) -> Point<dim> {
676 Assert(
677 surrounding_points.size() == 8 && 2 < i && i < 8,
678 ExcMessage("This function assumes that there are eight surrounding "
679 "points around a two-dimensional object. It also assumes "
680 "that the first three chart points have already been "
681 "computed."));
682 switch (i) {
683 case 0:
684 case 1:
685 case 2:
686 Assert(false, ExcInternalError());
687 break;
688 case 3:
689 return chart_points[1] + (chart_points[2] - chart_points[0]);
690 case 4:
691 return 0.5 * (chart_points[0] + chart_points[2]);
692 case 5:
693 return 0.5 * (chart_points[1] + chart_points[3]);
694 case 6:
695 return 0.5 * (chart_points[0] + chart_points[1]);
696 case 7:
697 return 0.5 * (chart_points[2] + chart_points[3]);
698 default:
699 Assert(false, ExcInternalError());
700 }
701
702 return Point<dim>();
703 };
704
705 // Function that can guess the location of a chart point by assuming that
706 // the eight surrounding points form the vertices of a hexahedron, arranged
707 // like
708 //
709 // 6-------7
710 // /| /|
711 // / / |
712 // / | / |
713 // 4-------5 |
714 // | 2- -|- -3
715 // | / | /
716 // | | /
717 // |/ |/
718 // 0-------1
719 //
720 // (where vertex 2 is the back left vertex) we can estimate where chart
721 // points 5 - 7 are by computing the height (in chart coordinates) as c4 -
722 // c0 and then adding that onto the appropriate bottom vertex.
723 //
724 // This function assumes that the first five chart points have been computed
725 // since there is no effective way to guess them.
726 auto guess_chart_point_structdim_3 =
727 [&](const unsigned int i) -> Point<dim> {
728 Assert(
729 surrounding_points.size() == 8 && 4 < i && i < 8,
730 ExcMessage("This function assumes that there are eight surrounding "
731 "points around a three-dimensional object. It also "
732 "assumes that the first five chart points have already "
733 "been computed."));
734 return chart_points[i - 4] + (chart_points[4] - chart_points[0]);
735 };
736
737 // Check if we can use the two chart point shortcuts above before we start:
738 bool use_structdim_2_guesses = false;
739 bool use_structdim_3_guesses = false;
740 // note that in the structdim 2 case: 0 - 6 and 2 - 7 should be roughly
741 // parallel, while in the structdim 3 case, 0 - 6 and 2 - 7 should be
742 // roughly orthogonal. Use the angle between these two vectors to figure out
743 // if we should turn on either structdim optimization.
744 if (surrounding_points.size() == 8) {
745 const Tensor<1, spacedim> v06 =
746 surrounding_points[6] - surrounding_points[0];
747 const Tensor<1, spacedim> v27 =
748 surrounding_points[7] - surrounding_points[2];
749
750 // note that we can save a call to sqrt() by rearranging
751 const double cosine = scalar_product(v06, v27) /
752 std::sqrt(v06.norm_square() * v27.norm_square());
753 if (0.707 < cosine)
754 // the angle is less than pi/4, so these vectors are roughly parallel:
755 // enable the structdim 2 optimization
756 use_structdim_2_guesses = true;
757 else if (spacedim == 3)
758 // otherwise these vectors are roughly orthogonal: enable the
759 // structdim 3 optimization if we are in 3D
760 use_structdim_3_guesses = true;
761 }
762 // we should enable at most one of the optimizations
763 Assert((!use_structdim_2_guesses && !use_structdim_3_guesses) ||
764 (use_structdim_2_guesses ^ use_structdim_3_guesses),
765 ExcInternalError());
766
767
768 auto compute_chart_point =
769 [&](const typename Triangulation<dim, spacedim>::cell_iterator &cell,
770 const unsigned int point_index) {
771 Point<dim> guess;
772 // an optimization: keep track of whether or not we used the affine
773 // approximation so that we don't call pull_back with the same
774 // initial guess twice (i.e., if pull_back fails the first time,
775 // don't try again with the same function arguments).
776 bool used_affine_approximation = false;
777 // if we have already computed three points, we can guess the fourth
778 // to be the missing corner point of a rectangle
779 if (point_index == 3 && surrounding_points.size() >= 8)
780 guess = chart_points[1] + (chart_points[2] - chart_points[0]);
781 else if (use_structdim_2_guesses && 3 < point_index)
782 guess = guess_chart_point_structdim_2(point_index);
783 else if (use_structdim_3_guesses && 4 < point_index)
784 guess = guess_chart_point_structdim_3(point_index);
785 else if (dim == 3 && point_index > 7 &&
786 surrounding_points.size() == 26) {
787 if (point_index < 20)
788 guess =
789 0.5 * (chart_points[GeometryInfo<dim>::line_to_cell_vertices(
790 point_index - 8, 0)] +
791 chart_points[GeometryInfo<dim>::line_to_cell_vertices(
792 point_index - 8, 1)]);
793 else
794 guess =
795 0.25 * (chart_points[GeometryInfo<dim>::face_to_cell_vertices(
796 point_index - 20, 0)] +
797 chart_points[GeometryInfo<dim>::face_to_cell_vertices(
798 point_index - 20, 1)] +
799 chart_points[GeometryInfo<dim>::face_to_cell_vertices(
800 point_index - 20, 2)] +
801 chart_points[GeometryInfo<dim>::face_to_cell_vertices(
802 point_index - 20, 3)]);
803 } else {
804 guess = cell->real_to_unit_cell_affine_approximation(
805 surrounding_points[point_index]);
806 used_affine_approximation = true;
807 }
808 chart_points[point_index] =
809 pull_back(cell, surrounding_points[point_index], guess);
810
811 // the initial guess may not have been good enough: if applicable,
812 // try again with the affine approximation (which is more accurate
813 // than the cheap methods used above)
814 if (chart_points[point_index][0] ==
816 !used_affine_approximation) {
817 guess = cell->real_to_unit_cell_affine_approximation(
818 surrounding_points[point_index]);
819 chart_points[point_index] =
820 pull_back(cell, surrounding_points[point_index], guess);
821 }
822
823 if (chart_points[point_index][0] ==
825 for (unsigned int d = 0; d < dim; ++d)
826 guess[d] = 0.5;
827 chart_points[point_index] =
828 pull_back(cell, surrounding_points[point_index], guess);
829 }
830 };
831
832 // check whether all points are inside the unit cell of the current chart
833 for (unsigned int c = 0; c < nearby_cells.size(); ++c) {
834 typename Triangulation<dim, spacedim>::cell_iterator cell(
835 &triangulation, level_coarse, nearby_cells[c]);
836 bool inside_unit_cell = true;
837 for (unsigned int i = 0; i < surrounding_points.size(); ++i) {
838 compute_chart_point(cell, i);
839
840 // Tolerance 5e-4 chosen that the method also works with manifolds
841 // that have some discretization error like SphericalManifold
842 if (GeometryInfo<dim>::is_inside_unit_cell(chart_points[i], 5e-4) ==
843 false) {
844 inside_unit_cell = false;
845 break;
846 }
847 }
848 if (inside_unit_cell == true) {
849 return cell;
850 }
851
852 // if we did not find a point and this was the last valid cell (the next
853 // iterate being the end of the array or an invalid tag), we must stop
854 if (c == nearby_cells.size() - 1 ||
855 nearby_cells[c + 1] == numbers::invalid_unsigned_int) {
856 // generate additional information to help debugging why we did not
857 // get a point
858 std::ostringstream message;
859 for (unsigned int b = 0; b <= c; ++b) {
860 typename Triangulation<dim, spacedim>::cell_iterator cell(
861 &triangulation, level_coarse, nearby_cells[b]);
862 message << "Looking at cell " << cell->id()
863 << " with vertices: " << std::endl;
864 for (const unsigned int v : GeometryInfo<dim>::vertex_indices())
865 message << std::setprecision(16) << " " << cell->vertex(v)
866 << " ";
867 message << std::endl;
868 message << "Transformation to chart coordinates: " << std::endl;
869 for (unsigned int i = 0; i < surrounding_points.size(); ++i) {
870 compute_chart_point(cell, i);
871 message << std::setprecision(16) << surrounding_points[i] << " -> "
872 << chart_points[i] << std::endl;
873 }
874 }
875
876 AssertThrow(false,
877 (typename Mapping<dim, spacedim>::ExcTransformationFailed(
878 message.str())));
879 }
880 }
881
882 // a valid inversion should have returned a point above. an invalid
883 // inversion should have triggered the assertion, so we should never end up
884 // here
885 Assert(false, ExcInternalError());
886 return typename Triangulation<dim, spacedim>::cell_iterator();
887 }
888
889
890 template <int dim, int spacedim>
891 Point<spacedim>
893 const ArrayView<const Point<spacedim>> &surrounding_points,
894 const ArrayView<const double> &weights) const
895 {
896 boost::container::small_vector<Point<dim>, 100> chart_points(
897 surrounding_points.size());
898 ArrayView<Point<dim>> chart_points_view =
899 make_array_view(chart_points.begin(), chart_points.end());
900 const auto cell =
901 compute_chart_points(surrounding_points, chart_points_view);
902
903 const Point<dim> p_chart =
904 chart_manifold->get_new_point(chart_points_view, weights);
905
906 return push_forward(cell, p_chart);
907 }
908
909
910 template <int dim, int spacedim>
912 const ArrayView<const Point<spacedim>> &surrounding_points,
913 const Table<2, double> &weights,
914 ArrayView<Point<spacedim>> new_points) const
915 {
916 Assert(weights.size(0) > 0, ExcEmptyObject());
917 AssertDimension(surrounding_points.size(), weights.size(1));
918
919 boost::container::small_vector<Point<dim>, 100> chart_points(
920 surrounding_points.size());
921 ArrayView<Point<dim>> chart_points_view =
922 make_array_view(chart_points.begin(), chart_points.end());
923 const auto cell =
924 compute_chart_points(surrounding_points, chart_points_view);
925
926 boost::container::small_vector<Point<dim>, 100> new_points_on_chart(
927 weights.size(0));
928 chart_manifold->get_new_points(chart_points_view,
929 weights,
930 make_array_view(new_points_on_chart.begin(),
931 new_points_on_chart.end()));
932
933 for (unsigned int row = 0; row < weights.size(0); ++row)
934 new_points[row] = push_forward(cell, new_points_on_chart[row]);
935 }
936
937} // namespace ryujin
Point< spacedim > get_new_point(const ArrayView< const Point< spacedim > > &surrounding_points, const ArrayView< const double > &weights) const override
std::unique_ptr< Manifold< dim, spacedim > > clone() const override
void get_new_points(const ArrayView< const Point< spacedim > > &surrounding_points, const Table< 2, double > &weights, ArrayView< Point< spacedim > > new_points) const override
void initialize(const Triangulation< dim, spacedim > &triangulation, const Manifold< dim, spacedim > &chart_manifold=FlatManifold< dim >())
void step(Triangulation< dim, dim > &, const double, const double, const double, const double)
Definition: geometry_step.h:23
static constexpr double invalid_pull_back_coordinate