deal.II version GIT relicensing-6842-g793a97d2aa 2026-10-02 14:00:01+00:00
\(\newcommand{\dealvcentcolon}{\mathrel{\mathop{:}}}\) \(\newcommand{\dealcoloneq}{\dealvcentcolon\mathrel{\mkern-1.2mu}=}\) \(\newcommand{\jump}[1]{\left[\!\left[ #1 \right]\!\right]}\) \(\newcommand{\average}[1]{\left\{\!\left\{ #1 \right\}\!\right\}}\)
Loading...
Searching...
No Matches
manifold_lib.cc
Go to the documentation of this file.
1// -----------------------------------------------------------------------------
2//
3// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception OR LGPL-2.1-or-later
4// Copyright (C) 2014 - 2025 by the deal.II authors
5//
6// This file is part of the deal.II library.
7//
8// Detailed license information governing the source code and contributions
9// can be found in LICENSE.md and CONTRIBUTING.md at the top level directory.
10//
11// -----------------------------------------------------------------------------
12
13#include <deal.II/base/table.h>
14#include <deal.II/base/tensor.h>
15
16#include <deal.II/fe/mapping.h>
18
20#include <deal.II/grid/tria.h>
23
24#include <deal.II/lac/vector.h>
25
27
28#include <boost/container/small_vector.hpp>
29
30#include <cmath>
31#include <limits>
32#include <memory>
33
35
36
37namespace internal
38{
39 // The pull_back function fails regularly in the compute_chart_points
40 // method, and, instead of throwing an exception, returns a point outside
41 // the unit cell. The individual coordinates of that point are given by the
42 // value below.
43 static constexpr double invalid_pull_back_coordinate = 20.0;
44
45 // Rotate a given unit vector u around the axis dir
46 // where the angle is given by the length of dir.
47 // This is the exponential map for a sphere.
50 {
51 const double theta = dir.norm();
52 if (theta < 1.e-10)
53 {
54 return u;
55 }
56 else
57 {
58 const Tensor<1, 3> dirUnit = dir / theta;
59 const Tensor<1, 3> tmp =
60 std::cos(theta) * u + std::sin(theta) * dirUnit;
61 return tmp / tmp.norm();
62 }
63 }
64
65 // Returns the direction to go from v to u
66 // projected to the plane perpendicular to the unit vector v.
67 // This one is more stable when u and v are nearly equal.
70 {
71 Tensor<1, 3> ans = u - v;
72 ans -= (ans * v) * v;
73 return ans; // ans = (u-v) - ((u-v)*v)*v
74 }
75
76 // helper function to compute a vector orthogonal to a given one.
77 // does nothing unless spacedim == 3.
78 template <int spacedim>
81 bool /*normalize*/ = false)
82 {
83 return {};
84 }
85
87 compute_normal(const Tensor<1, 3> &vector, bool normalize = false)
88 {
89 Assert(vector.norm_square() != 0.,
90 ExcMessage("The direction parameter must not be zero!"));
91 Point<3> normal;
92 if (std::abs(vector[0]) >= std::abs(vector[1]) &&
93 std::abs(vector[0]) >= std::abs(vector[2]))
94 {
95 normal[1] = -1.;
96 normal[2] = -1.;
97 normal[0] = (vector[1] + vector[2]) / vector[0];
98 }
99 else if (std::abs(vector[1]) >= std::abs(vector[0]) &&
100 std::abs(vector[1]) >= std::abs(vector[2]))
101 {
102 normal[0] = -1.;
103 normal[2] = -1.;
104 normal[1] = (vector[0] + vector[2]) / vector[1];
105 }
106 else
107 {
108 normal[0] = -1.;
109 normal[1] = -1.;
110 normal[2] = (vector[0] + vector[1]) / vector[2];
111 }
112 if (normalize)
113 normal /= normal.norm();
114 return normal;
115 }
116} // namespace internal
117
118
119
120// ============================================================
121// PolarManifold
122// ============================================================
123
125template <int dim, int spacedim>
127 : ChartManifold<dim, spacedim, spacedim>(
128 PolarManifold<dim, spacedim>::get_periodicity())
129 , center(center)
130 , p_center(center)
131{}
133
134
135
136template <int dim, int spacedim>
137std::unique_ptr<Manifold<dim, spacedim>>
139{
140 return std::make_unique<PolarManifold<dim, spacedim>>(*this);
141}
142
143
144
145template <int dim, int spacedim>
148{
149 Tensor<1, spacedim> periodicity;
150 // In two dimensions, theta is periodic.
151 // In three dimensions things are a little more complicated, since the only
152 // variable that is truly periodic is phi, while theta should be bounded
153 // between 0 and pi. There is currently no way to enforce this, so here we
154 // only fix periodicity for the last variable, corresponding to theta in 2d
155 // and phi in 3d.
156 periodicity[spacedim - 1] = 2 * numbers::PI;
157 return periodicity;
158}
159
160
161
162template <int dim, int spacedim>
165 const Point<spacedim> &spherical_point) const
166{
167 Assert(spherical_point[0] >= 0.0,
168 ExcMessage("Negative radius for given point."));
169 const double rho = spherical_point[0];
170 const double theta = spherical_point[1];
171
173 if (rho > 1e-10)
174 switch (spacedim)
175 {
176 case 2:
177 p[0] = rho * std::cos(theta);
178 p[1] = rho * std::sin(theta);
179 break;
180 case 3:
181 {
182 const double phi = spherical_point[2];
183 p[0] = rho * std::sin(theta) * std::cos(phi);
184 p[1] = rho * std::sin(theta) * std::sin(phi);
185 p[2] = rho * std::cos(theta);
186 break;
187 }
188 default:
190 }
191 return p + p_center;
192}
193
194
195
196template <int dim, int spacedim>
199 const Point<spacedim> &space_point) const
200{
201 const Tensor<1, spacedim> R = space_point - p_center;
202 const double rho = R.norm();
203
205 p[0] = rho;
206
207 switch (spacedim)
208 {
209 case 2:
210 {
211 p[1] = std::atan2(R[1], R[0]);
212 if (p[1] < 0)
213 p[1] += 2 * numbers::PI;
214 break;
215 }
216
217 case 3:
218 {
219 const double z = R[2];
220 p[2] = std::atan2(R[1], R[0]); // phi
221 if (p[2] < 0)
222 p[2] += 2 * numbers::PI; // phi is periodic
223 p[1] = std::atan2(std::sqrt(R[0] * R[0] + R[1] * R[1]), z); // theta
224 break;
225 }
226
227 default:
229 }
230 return p;
231}
232
233
234
235template <int dim, int spacedim>
238 const Point<spacedim> &spherical_point) const
239{
240 Assert(spherical_point[0] >= 0.0,
241 ExcMessage("Negative radius for given point."));
242 const double rho = spherical_point[0];
243 const double theta = spherical_point[1];
244
246 if (rho > 1e-10)
247 switch (spacedim)
248 {
249 case 2:
250 {
251 DX[0][0] = std::cos(theta);
252 DX[0][1] = -rho * std::sin(theta);
253 DX[1][0] = std::sin(theta);
254 DX[1][1] = rho * std::cos(theta);
255 break;
256 }
257
258 case 3:
259 {
260 const double phi = spherical_point[2];
261 DX[0][0] = std::sin(theta) * std::cos(phi);
262 DX[0][1] = rho * std::cos(theta) * std::cos(phi);
263 DX[0][2] = -rho * std::sin(theta) * std::sin(phi);
264
265 DX[1][0] = std::sin(theta) * std::sin(phi);
266 DX[1][1] = rho * std::cos(theta) * std::sin(phi);
267 DX[1][2] = rho * std::sin(theta) * std::cos(phi);
268
269 DX[2][0] = std::cos(theta);
270 DX[2][1] = -rho * std::sin(theta);
271 DX[2][2] = 0;
272 break;
273 }
274
275 default:
277 }
278 return DX;
279}
280
281
282
283namespace
284{
285 template <int dim, int spacedim>
286 bool
287 spherical_face_is_horizontal(
289 const Point<spacedim> &manifold_center)
290 {
291 // We test whether a face is horizontal by checking that the vertices
292 // all have roughly the same distance from the center: If the
293 // maximum deviation for the distances from the vertices to the
294 // center is less than 1.e-5 of the distance between vertices (as
295 // measured by the minimum distance from any of the other vertices
296 // to the first vertex), then we call this a horizontal face.
297 constexpr unsigned int n_vertices =
299 std::array<double, n_vertices> sqr_distances_to_center;
300 std::array<double, n_vertices - 1> sqr_distances_to_first_vertex;
301 sqr_distances_to_center[0] =
302 (face->vertex(0) - manifold_center).norm_square();
303 for (unsigned int i = 1; i < n_vertices; ++i)
304 {
305 sqr_distances_to_center[i] =
306 (face->vertex(i) - manifold_center).norm_square();
307 sqr_distances_to_first_vertex[i - 1] =
308 (face->vertex(i) - face->vertex(0)).norm_square();
309 }
310 const auto minmax_sqr_distance =
311 std::minmax_element(sqr_distances_to_center.begin(),
312 sqr_distances_to_center.end());
313 const auto min_sqr_distance_to_first_vertex =
314 std::min_element(sqr_distances_to_first_vertex.begin(),
315 sqr_distances_to_first_vertex.end());
316
317 return (*minmax_sqr_distance.second - *minmax_sqr_distance.first <
318 1.e-10 * *min_sqr_distance_to_first_vertex);
319 }
320} // namespace
321
322
323
324template <int dim, int spacedim>
328 const Point<spacedim> &p) const
329{
330 // Let us first test whether we are on a "horizontal" face
331 // (tangential to the sphere). In this case, the normal vector is
332 // easy to compute since it is proportional to the vector from the
333 // center to the point 'p'.
334 if (spherical_face_is_horizontal<dim, spacedim>(face, p_center))
335 {
336 // So, if this is a "horizontal" face, then just compute the normal
337 // vector as the one from the center to the point 'p', adequately
338 // scaled.
339 const Tensor<1, spacedim> unnormalized_spherical_normal = p - p_center;
340 const Tensor<1, spacedim> normalized_spherical_normal =
341 unnormalized_spherical_normal / unnormalized_spherical_normal.norm();
342 return normalized_spherical_normal;
343 }
344 else
345 // If it is not a horizontal face, just use the machinery of the
346 // base class.
348
349 return Tensor<1, spacedim>();
350}
351
352
353
354// ============================================================
355// SphericalManifold
356// ============================================================
357
359template <int dim, int spacedim>
361 const Point<spacedim> center)
362 : center(center)
363 , p_center(center)
364 , polar_manifold(center)
365{}
367
368
369
370template <int dim, int spacedim>
371std::unique_ptr<Manifold<dim, spacedim>>
373{
374 return std::make_unique<SphericalManifold<dim, spacedim>>(*this);
375}
376
377
378
379template <int dim, int spacedim>
382 const Point<spacedim> &p1,
383 const Point<spacedim> &p2,
384 const double w) const
385{
386 const double tol = 1e-10;
387
388 if ((p1 - p2).norm_square() < tol * tol || std::abs(w) < tol)
389 return p1;
390 else if (std::abs(w - 1.0) < tol)
391 return p2;
392
393 // If the points are one dimensional then there is no need for anything but
394 // a linear combination.
395 if (spacedim == 1)
396 return Point<spacedim>(w * p2 + (1 - w) * p1);
397
398 const Tensor<1, spacedim> v1 = p1 - p_center;
399 const Tensor<1, spacedim> v2 = p2 - p_center;
400 const double r1 = v1.norm();
401 const double r2 = v2.norm();
402
403 Assert(r1 > tol && r2 > tol,
404 ExcMessage("p1 and p2 cannot coincide with the center."));
405
406 const Tensor<1, spacedim> e1 = v1 / r1;
407 const Tensor<1, spacedim> e2 = v2 / r2;
408
409 // Find the cosine of the angle gamma described by v1 and v2.
410 const double cosgamma = e1 * e2;
411
412 // Points are collinear with the center (allow for 8*eps as a tolerance)
413 if (cosgamma < -1 + 8. * std::numeric_limits<double>::epsilon())
414 return p_center;
415
416 // Points are along a line, in which case e1 and e2 are essentially the same.
417 if (cosgamma > 1 - 8. * std::numeric_limits<double>::epsilon())
418 return Point<spacedim>(p_center + w * v2 + (1 - w) * v1);
419
420 // Find the angle sigma that corresponds to arclength equal to w. acos
421 // should never be undefined because we have ruled out the two special cases
422 // above.
423 const double sigma = w * std::acos(cosgamma);
424
425 // Normal to v1 in the plane described by v1,v2,and the origin.
426 // Since p1 and p2 do not coincide n is not zero and well defined.
427 Tensor<1, spacedim> n = v2 - (v2 * e1) * e1;
428 const double n_norm = n.norm();
429 Assert(n_norm > 0,
430 ExcInternalError("n should be different from the null vector. "
431 "Probably, this means v1==v2 or v2==0."));
432
433 n /= n_norm;
434
435 // Find the point Q along O,v1 such that
436 // P1,V,P2 has measure sigma.
437 const Tensor<1, spacedim> P = std::cos(sigma) * e1 + std::sin(sigma) * n;
438
439 // Project this point on the manifold.
440 return Point<spacedim>(p_center + (w * r2 + (1.0 - w) * r1) * P);
441}
442
443
444
445template <int dim, int spacedim>
448 const Point<spacedim> &p1,
449 const Point<spacedim> &p2) const
450{
451 const double tol = 1e-10;
452
453 Assert(p1 != p2, ExcMessage("p1 and p2 should not concide."));
454
455 const Tensor<1, spacedim> v1 = p1 - p_center;
456 const Tensor<1, spacedim> v2 = p2 - p_center;
457 const double r1 = v1.norm();
458 const double r2 = v2.norm();
459
460 Assert(r1 > tol, ExcMessage("p1 cannot coincide with the center."));
461
462 Assert(r2 > tol, ExcMessage("p2 cannot coincide with the center."));
463
464 const Tensor<1, spacedim> e1 = v1 / r1;
465 const Tensor<1, spacedim> e2 = v2 / r2;
466
467 // Find the cosine of the angle gamma described by v1 and v2.
468 const double cosgamma = e1 * e2;
469
470 Assert(cosgamma > -1 + 8. * std::numeric_limits<double>::epsilon(),
471 ExcMessage("p1 and p2 cannot lie on the same diameter and be opposite "
472 "respect to the center."));
473
474 if (cosgamma > 1 - 8. * std::numeric_limits<double>::epsilon())
475 return v2 - v1;
476
477 // Normal to v1 in the plane described by v1,v2,and the origin.
478 // Since p1 and p2 do not coincide n is not zero and well defined.
479 Tensor<1, spacedim> n = v2 - (v2 * e1) * e1;
480 const double n_norm = n.norm();
481 Assert(n_norm > 0,
482 ExcInternalError("n should be different from the null vector. "
483 "Probably, this means v1==v2 or v2==0."));
484
485 n /= n_norm;
486
487 // this is the derivative of the geodesic in get_intermediate_point
488 // derived with respect to w and inserting w=0.
489 const double gamma = std::acos(cosgamma);
490 return (r2 - r1) * e1 + r1 * gamma * n;
491}
492
493
494
495template <int dim, int spacedim>
499 const Point<spacedim> &p) const
500{
501 // Let us first test whether we are on a "horizontal" face
502 // (tangential to the sphere). In this case, the normal vector is
503 // easy to compute since it is proportional to the vector from the
504 // center to the point 'p'.
505 if (spherical_face_is_horizontal<dim, spacedim>(face, p_center))
506 {
507 // So, if this is a "horizontal" face, then just compute the normal
508 // vector as the one from the center to the point 'p', adequately
509 // scaled.
510 const Tensor<1, spacedim> unnormalized_spherical_normal = p - p_center;
511 const Tensor<1, spacedim> normalized_spherical_normal =
512 unnormalized_spherical_normal / unnormalized_spherical_normal.norm();
513 return normalized_spherical_normal;
514 }
515 else
516 // If it is not a horizontal face, just use the machinery of the
517 // base class.
519
520 return Tensor<1, spacedim>();
521}
522
523
524
525template <>
526void
533
534
535
536template <>
537void
544
545
546
547template <int dim, int spacedim>
548void
551 typename Manifold<dim, spacedim>::FaceVertexNormals &face_vertex_normals)
552 const
553{
554 // Let us first test whether we are on a "horizontal" face
555 // (tangential to the sphere). In this case, the normal vector is
556 // easy to compute since it is proportional to the vector from the
557 // center to the point 'p'.
558 if (spherical_face_is_horizontal<dim, spacedim>(face, p_center))
559 {
560 // So, if this is a "horizontal" face, then just compute the normal
561 // vector as the one from the center to the point 'p', adequately
562 // scaled.
563 for (unsigned int vertex = 0;
564 vertex < GeometryInfo<spacedim>::vertices_per_face;
565 ++vertex)
566 face_vertex_normals[vertex] = face->vertex(vertex) - p_center;
567 }
568 else
569 Manifold<dim, spacedim>::get_normals_at_vertices(face, face_vertex_normals);
570}
571
572
573
574template <int dim, int spacedim>
575void
577 const ArrayView<const Point<spacedim>> &surrounding_points,
578 const Table<2, double> &weights,
579 ArrayView<Point<spacedim>> new_points) const
580{
581 AssertDimension(new_points.size(), weights.size(0));
582 AssertDimension(surrounding_points.size(), weights.size(1));
583
584 do_get_new_points(surrounding_points, make_array_view(weights), new_points);
585
586 return;
587}
588
589
590
591template <int dim, int spacedim>
594 const ArrayView<const Point<spacedim>> &vertices,
595 const ArrayView<const double> &weights) const
596{
597 // To avoid duplicating all of the logic in get_new_points, simply call it
598 // for one position.
599 Point<spacedim> new_point;
600 do_get_new_points(vertices,
601 weights,
602 make_array_view(&new_point, &new_point + 1));
603
604 return new_point;
605}
606
607
608
609namespace internal
610{
612 {
613 namespace
614 {
615 template <int spacedim>
617 do_get_new_point(
618 const ArrayView<const Tensor<1, spacedim>> & /*directions*/,
619 const ArrayView<const double> & /*distances*/,
620 const ArrayView<const double> & /*weights*/,
621 const Point<spacedim> & /*candidate_point*/)
622 {
624 return {};
625 }
626
627 template <>
629 do_get_new_point(const ArrayView<const Tensor<1, 3>> &directions,
630 const ArrayView<const double> &distances,
631 const ArrayView<const double> &weights,
632 const Point<3> &candidate_point)
633 {
634 AssertDimension(directions.size(), distances.size());
635 AssertDimension(directions.size(), weights.size());
636
637 Point<3> candidate = candidate_point;
638 const unsigned int n_merged_points = directions.size();
639 const double tolerance = 1e-10;
640 const int max_iterations = 10;
641
642 {
643 // If the candidate happens to coincide with a normalized
644 // direction, we return it. Otherwise, the Hessian would be singular.
645 for (unsigned int i = 0; i < n_merged_points; ++i)
646 {
647 const double squared_distance =
648 (candidate - directions[i]).norm_square();
649 if (squared_distance < tolerance * tolerance)
650 return candidate;
651 }
652
653 // check if we only have two points now, in which case we can use the
654 // get_intermediate_point function
655 if (n_merged_points == 2)
656 {
657 static const ::SphericalManifold<3, 3> unit_manifold;
658 Assert(std::abs(weights[0] + weights[1] - 1.0) < 1e-13,
659 ExcMessage("Weights do not sum up to 1"));
660 const Point<3> intermediate =
661 unit_manifold.get_intermediate_point(Point<3>(directions[0]),
662 Point<3>(directions[1]),
663 weights[1]);
664 return intermediate;
665 }
666
667 Tensor<1, 3> vPerp;
668 Tensor<2, 2> Hessian;
670 Tensor<1, 2> gradlocal;
671
672 // On success we exit the loop early.
673 // Otherwise, we just take the result after max_iterations steps.
674 for (unsigned int i = 0; i < max_iterations; ++i)
675 {
676 // Step 2a: Find new descent direction
677
678 // Get local basis for the estimate candidate
679 const Tensor<1, 3> Clocalx = internal::compute_normal(candidate);
680 const Tensor<1, 3> Clocaly = cross_product_3d(candidate, Clocalx);
681
682 // For each vertices vector, compute the tangent vector from
683 // candidate towards the vertices vector -- its length is the
684 // spherical length from candidate to the vertices vector. Then
685 // compute its contribution to the Hessian.
686 gradient = 0.;
687 Hessian = 0.;
688 for (unsigned int i = 0; i < n_merged_points; ++i)
689 if (std::abs(weights[i]) > 1.e-15)
690 {
691 vPerp =
692 internal::projected_direction(directions[i], candidate);
693 const double sinthetaSq = vPerp.norm_square();
694 const double sintheta = std::sqrt(sinthetaSq);
695 if (sintheta < tolerance)
696 {
697 Hessian[0][0] += weights[i];
698 Hessian[1][1] += weights[i];
699 }
700 else
701 {
702 const double costheta = (directions[i]) * candidate;
703 const double theta = std::atan2(sintheta, costheta);
704 const double sincthetaInv = theta / sintheta;
705
706 const double cosphi = vPerp * Clocalx;
707 const double sinphi = vPerp * Clocaly;
708
709 gradlocal[0] = cosphi;
710 gradlocal[1] = sinphi;
711 gradient += (weights[i] * sincthetaInv) * gradlocal;
712
713 const double wt = weights[i] / sinthetaSq;
714 const double sinphiSq = sinphi * sinphi;
715 const double cosphiSq = cosphi * cosphi;
716 const double tt = sincthetaInv * costheta;
717 const double offdiag =
718 cosphi * sinphi * wt * (1.0 - tt);
719 Hessian[0][0] += wt * (cosphiSq + tt * sinphiSq);
720 Hessian[0][1] += offdiag;
721 Hessian[1][0] += offdiag;
722 Hessian[1][1] += wt * (sinphiSq + tt * cosphiSq);
723 }
724 }
725
726 Assert(determinant(Hessian) > tolerance, ExcInternalError());
727
728 const Tensor<2, 2> inverse_Hessian = invert(Hessian);
729
730 const Tensor<1, 2> xDisplocal = inverse_Hessian * gradient;
731 const Tensor<1, 3> xDisp =
732 xDisplocal[0] * Clocalx + xDisplocal[1] * Clocaly;
733
734 // Step 2b: rotate candidate in direction xDisp for a new
735 // candidate.
736 const Point<3> candidateOld = candidate;
737 candidate =
738 Point<3>(internal::apply_exponential_map(candidate, xDisp));
739
740 // Step 2c: return the new candidate if we didn't move
741 if ((candidate - candidateOld).norm_square() <
742 tolerance * tolerance)
743 break;
744 }
745 }
746 return candidate;
747 }
748 } // namespace
749 } // namespace SphericalManifold
750} // namespace internal
751
752
753
754template <int dim, int spacedim>
755void
757 const ArrayView<const Point<spacedim>> &surrounding_points,
758 const ArrayView<const double> &weights,
759 ArrayView<Point<spacedim>> new_points) const
760{
761 AssertDimension(weights.size(),
762 new_points.size() * surrounding_points.size());
763 const unsigned int weight_rows = new_points.size();
764 const unsigned int weight_columns = surrounding_points.size();
765
766 if (surrounding_points.size() == 2)
767 {
768 for (unsigned int row = 0; row < weight_rows; ++row)
769 new_points[row] =
771 surrounding_points[0],
772 surrounding_points[1],
773 weights[row * weight_columns + 1]);
774 return;
775 }
776
777 boost::container::small_vector<std::pair<double, Tensor<1, spacedim>>, 100>
778 new_candidates(new_points.size());
779 boost::container::small_vector<Tensor<1, spacedim>, 100> directions(
780 surrounding_points.size(), Point<spacedim>());
781 boost::container::small_vector<double, 100> distances(
782 surrounding_points.size(), 0.0);
783 double max_distance = 0.;
784 for (unsigned int i = 0; i < surrounding_points.size(); ++i)
785 {
786 directions[i] = surrounding_points[i] - p_center;
787 distances[i] = directions[i].norm();
788
789 if (distances[i] != 0.)
790 directions[i] /= distances[i];
791 else
792 Assert(false,
793 ExcMessage("One of the vertices coincides with the center. "
794 "This is not allowed!"));
795
796 // Check if an estimate is good enough,
797 // this is often the case for sufficiently refined meshes.
798 for (unsigned int k = 0; k < i; ++k)
799 {
800 const double squared_distance =
801 (directions[i] - directions[k]).norm_square();
802 max_distance = std::max(max_distance, squared_distance);
803 }
804 }
805
806 // Step 1: Check for some special cases, create simple linear guesses
807 // otherwise.
808 const double tolerance = 1e-10;
809 boost::container::small_vector<bool, 100> accurate_point_was_found(
810 new_points.size(), false);
811 const ArrayView<const Tensor<1, spacedim>> array_directions =
812 make_array_view(directions.begin(), directions.end());
813 const ArrayView<const double> array_distances =
814 make_array_view(distances.begin(), distances.end());
815 for (unsigned int row = 0; row < weight_rows; ++row)
816 {
817 new_candidates[row] =
818 guess_new_point(array_directions,
819 array_distances,
820 ArrayView<const double>(&weights[row * weight_columns],
821 weight_columns));
822
823 // If the candidate is the center, mark it as found to avoid entering
824 // the Newton iteration in step 2, which would crash.
825 if (new_candidates[row].first == 0.0)
826 {
827 new_points[row] = p_center;
828 accurate_point_was_found[row] = true;
829 continue;
830 }
831
832 // If not in 3d, just use the implementation from PolarManifold
833 // after we verified that the candidate is not the center.
834 if (spacedim < 3)
835 new_points[row] = polar_manifold.get_new_point(
836 surrounding_points,
837 ArrayView<const double>(&weights[row * weight_columns],
838 weight_columns));
839 }
840
841 // In this case, we treated the case that the candidate is the center and
842 // obtained the new locations from the PolarManifold object otherwise.
843 if constexpr (spacedim < 3)
844 return;
845 else
846 {
847 // If all the points are close to each other, we expect the estimate to
848 // be good enough. This tolerance was chosen such that the first iteration
849 // for a at least three time refined HyperShell mesh with radii .5 and 1.
850 // doesn't already succeed.
851 if (max_distance < 2e-2)
852 {
853 for (unsigned int row = 0; row < weight_rows; ++row)
854 new_points[row] =
855 p_center + new_candidates[row].first * new_candidates[row].second;
856
857 return;
858 }
859
860 // Step 2:
861 // Do more expensive Newton-style iterations to improve the estimate.
862
863 // Search for duplicate directions and merge them to minimize the cost of
864 // the get_new_point function call below.
865 boost::container::small_vector<double, 1000> merged_weights(
866 weights.size());
867 boost::container::small_vector<Tensor<1, spacedim>, 100>
868 merged_directions(surrounding_points.size(), Point<spacedim>());
869 boost::container::small_vector<double, 100> merged_distances(
870 surrounding_points.size(), 0.0);
871
872 unsigned int n_unique_directions = 0;
873 for (unsigned int i = 0; i < surrounding_points.size(); ++i)
874 {
875 bool found_duplicate = false;
876
877 // This inner loop is of @f$O(N^2)@f$ complexity, but
878 // surrounding_points.size() is usually at most 8 points large.
879 for (unsigned int j = 0; j < n_unique_directions; ++j)
880 {
881 const double squared_distance =
882 (directions[i] - directions[j]).norm_square();
883 if (!found_duplicate && squared_distance < 1e-28)
884 {
885 found_duplicate = true;
886 for (unsigned int row = 0; row < weight_rows; ++row)
887 merged_weights[row * weight_columns + j] +=
888 weights[row * weight_columns + i];
889 }
890 }
891
892 if (found_duplicate == false)
893 {
894 merged_directions[n_unique_directions] = directions[i];
895 merged_distances[n_unique_directions] = distances[i];
896 for (unsigned int row = 0; row < weight_rows; ++row)
897 merged_weights[row * weight_columns + n_unique_directions] =
898 weights[row * weight_columns + i];
899
900 ++n_unique_directions;
901 }
902 }
903
904 // Search for duplicate weight rows and merge them to minimize the cost of
905 // the get_new_point function call below.
906 boost::container::small_vector<unsigned int, 100> merged_weights_index(
907 new_points.size(), numbers::invalid_unsigned_int);
908 for (unsigned int row = 0; row < weight_rows; ++row)
909 {
910 for (unsigned int existing_row = 0; existing_row < row;
911 ++existing_row)
912 {
913 bool identical_weights = true;
914
915 for (unsigned int weight_index = 0;
916 weight_index < n_unique_directions;
917 ++weight_index)
918 if (std::abs(
919 merged_weights[row * weight_columns + weight_index] -
920 merged_weights[existing_row * weight_columns +
921 weight_index]) > tolerance)
922 {
923 identical_weights = false;
924 break;
925 }
926
927 if (identical_weights)
928 {
929 merged_weights_index[row] = existing_row;
930 break;
931 }
932 }
933 }
934
935 // Note that we only use the n_unique_directions first entries in the
936 // ArrayView
937 const ArrayView<const Tensor<1, spacedim>> array_merged_directions =
938 make_array_view(merged_directions.begin(),
939 merged_directions.begin() + n_unique_directions);
940 const ArrayView<const double> array_merged_distances =
941 make_array_view(merged_distances.begin(),
942 merged_distances.begin() + n_unique_directions);
943
944 for (unsigned int row = 0; row < weight_rows; ++row)
945 if (!accurate_point_was_found[row])
946 {
947 if (merged_weights_index[row] == numbers::invalid_unsigned_int)
948 {
949 const ArrayView<const double> array_merged_weights(
950 &merged_weights[row * weight_columns], n_unique_directions);
951 new_candidates[row].second =
952 internal::SphericalManifold::do_get_new_point(
953 array_merged_directions,
954 array_merged_distances,
955 array_merged_weights,
956 Point<spacedim>(new_candidates[row].second));
957 }
958 else
959 new_candidates[row].second =
960 new_candidates[merged_weights_index[row]].second;
961
962 new_points[row] =
963 p_center + new_candidates[row].first * new_candidates[row].second;
964 }
965 }
966}
967
968
969
970template <int dim, int spacedim>
971std::pair<double, Tensor<1, spacedim>>
973 const ArrayView<const Tensor<1, spacedim>> &directions,
974 const ArrayView<const double> &distances,
975 const ArrayView<const double> &weights) const
976{
977 const double tolerance = 1e-10;
978 double rho = 0.;
979 Tensor<1, spacedim> candidate;
980
981 // Perform a simple average ...
982 double total_weights = 0.;
983 for (unsigned int i = 0; i < directions.size(); ++i)
984 {
985 // if one weight is one, return its direction
986 if (std::abs(1 - weights[i]) < tolerance)
987 return std::make_pair(distances[i], directions[i]);
988
989 rho += distances[i] * weights[i];
990 candidate += directions[i] * weights[i];
991 total_weights += weights[i];
992 }
993
994 // ... and normalize if the candidate is different from the origin.
995 const double norm = candidate.norm();
996 if (norm == 0.)
997 return std::make_pair(0.0, Point<spacedim>());
998 candidate /= norm;
999 rho /= total_weights;
1000
1001 return std::make_pair(rho, candidate);
1002}
1003
1004
1005
1006// ============================================================
1007// CylindricalManifold
1008// ============================================================
1009template <int dim, int spacedim>
1011 const double tolerance)
1012 : CylindricalManifold<dim, spacedim>(Point<spacedim>::unit_vector(axis),
1013 Point<spacedim>(),
1014 tolerance)
1015{
1016 // do not use static_assert to make dimension-independent programming
1017 // easier.
1018 Assert(spacedim == 3,
1019 ExcMessage("CylindricalManifold can only be used for spacedim==3!"));
1020}
1021
1022
1023
1024template <int dim, int spacedim>
1026 const Tensor<1, spacedim> &direction,
1027 const Point<spacedim> &point_on_axis,
1028 const double tolerance)
1029 : ChartManifold<dim, spacedim, 3>(Tensor<1, 3>({0, 2. * numbers::PI, 0}))
1030 , normal_direction(internal::compute_normal(direction, true))
1031 , direction(direction / direction.norm())
1032 , point_on_axis(point_on_axis)
1033 , tolerance(tolerance)
1034 , dxn(cross_product_3d(this->direction, normal_direction))
1035{
1036 // do not use static_assert to make dimension-independent programming
1037 // easier.
1038 Assert(spacedim == 3,
1039 ExcMessage("CylindricalManifold can only be used for spacedim==3!"));
1040}
1041
1042
1043
1044template <int dim, int spacedim>
1045std::unique_ptr<Manifold<dim, spacedim>>
1047{
1048 return std::make_unique<CylindricalManifold<dim, spacedim>>(*this);
1049}
1050
1051
1052
1053template <int dim, int spacedim>
1056 const ArrayView<const Point<spacedim>> &surrounding_points,
1057 const ArrayView<const double> &weights) const
1058{
1059 Assert(spacedim == 3,
1060 ExcMessage("CylindricalManifold can only be used for spacedim==3!"));
1061
1062 // First check if the average in space lies on the axis.
1063 Point<spacedim> middle;
1064 double average_length = 0.;
1065 for (unsigned int i = 0; i < surrounding_points.size(); ++i)
1066 {
1067 middle += surrounding_points[i] * weights[i];
1068 average_length += surrounding_points[i].square() * weights[i];
1069 }
1070 middle -= point_on_axis;
1071 const double lambda = middle * direction;
1072
1073 if ((middle - direction * lambda).square() < tolerance * average_length)
1074 return point_on_axis + direction * lambda;
1075 else // If not, using the ChartManifold should yield valid results.
1076 return ChartManifold<dim, spacedim, 3>::get_new_point(surrounding_points,
1077 weights);
1078}
1079
1080
1081
1082template <int dim, int spacedim>
1085 const Point<spacedim> &space_point) const
1086{
1087 Assert(spacedim == 3,
1088 ExcMessage("CylindricalManifold can only be used for spacedim==3!"));
1089
1090 // First find the projection of the given point to the axis.
1091 const Tensor<1, spacedim> normalized_point = space_point - point_on_axis;
1092 const double lambda = normalized_point * direction;
1093 const Point<spacedim> projection = point_on_axis + direction * lambda;
1094 const Tensor<1, spacedim> p_diff = space_point - projection;
1095
1096 // Then compute the angle between the projection direction and
1097 // another vector orthogonal to the direction vector.
1098 const double phi = Physics::VectorRelations::signed_angle(normal_direction,
1099 p_diff,
1100 /*axis=*/direction);
1101
1102 // Return distance from the axis, angle and signed distance on the axis.
1103 return Point<3>(p_diff.norm(), phi, lambda);
1104}
1105
1106
1107
1108template <int dim, int spacedim>
1111 const Point<3> &chart_point) const
1112{
1113 Assert(spacedim == 3,
1114 ExcMessage("CylindricalManifold can only be used for spacedim==3!"));
1115
1116 // Rotate the orthogonal direction by the given angle
1117 const double sine_r = std::sin(chart_point[1]) * chart_point[0];
1118 const double cosine_r = std::cos(chart_point[1]) * chart_point[0];
1119 const Tensor<1, spacedim> intermediate =
1120 normal_direction * cosine_r + dxn * sine_r;
1121
1122 // Finally, put everything together.
1123 return point_on_axis + direction * chart_point[2] + intermediate;
1124}
1125
1126
1127
1128template <int dim, int spacedim>
1131 const Point<3> &chart_point) const
1132{
1133 Assert(spacedim == 3,
1134 ExcMessage("CylindricalManifold can only be used for spacedim==3!"));
1135
1136 Tensor<2, 3> derivatives;
1137
1138 // Rotate the orthogonal direction by the given angle
1139 const double sine = std::sin(chart_point[1]);
1140 const double cosine = std::cos(chart_point[1]);
1141 const Tensor<1, spacedim> intermediate =
1142 normal_direction * cosine + dxn * sine;
1143
1144 // avoid compiler warnings
1145 constexpr int s0 = 0 % spacedim;
1146 constexpr int s1 = 1 % spacedim;
1147 constexpr int s2 = 2 % spacedim;
1148
1149 // derivative w.r.t the radius
1150 derivatives[s0][s0] = intermediate[s0];
1151 derivatives[s1][s0] = intermediate[s1];
1152 derivatives[s2][s0] = intermediate[s2];
1153
1154 // derivatives w.r.t the angle
1155 derivatives[s0][s1] = -normal_direction[s0] * sine + dxn[s0] * cosine;
1156 derivatives[s1][s1] = -normal_direction[s1] * sine + dxn[s1] * cosine;
1157 derivatives[s2][s1] = -normal_direction[s2] * sine + dxn[s2] * cosine;
1158
1159 // derivatives w.r.t the direction of the axis
1160 derivatives[s0][s2] = direction[s0];
1161 derivatives[s1][s2] = direction[s1];
1162 derivatives[s2][s2] = direction[s2];
1163
1164 return derivatives;
1165}
1166
1167
1168
1169namespace
1170{
1171 template <int dim>
1173 check_and_normalize(const Tensor<1, dim> &t)
1174 {
1175 const double norm = t.norm();
1176 Assert(norm > 0.0, ExcMessage("The major axis must have a positive norm."));
1177 return t / norm;
1178 }
1179} // namespace
1180
1181
1182
1183// ============================================================
1184// EllipticalManifold
1185// ============================================================
1186template <int dim, int spacedim>
1188 const Point<spacedim> &center,
1189 const Tensor<1, spacedim> &major_axis_direction,
1190 const double eccentricity)
1191 : ChartManifold<dim, spacedim, spacedim>(
1192 EllipticalManifold<dim, spacedim>::get_periodicity())
1193 , direction(check_and_normalize(major_axis_direction))
1194 , center(center)
1195 , eccentricity(eccentricity)
1196 , cosh_u(1.0 / eccentricity)
1197 , sinh_u(std::sqrt(cosh_u * cosh_u - 1.0))
1198{
1199 // Throws an exception if dim!=2 || spacedim!=2.
1200 Assert(dim == 2 && spacedim == 2, ExcNotImplemented());
1201 // Throws an exception if eccentricity is not in range.
1202 Assert(std::signbit(cosh_u * cosh_u - 1.0) == false,
1203 ExcMessage(
1204 "Invalid eccentricity: It must satisfy 0 < eccentricity < 1."));
1205}
1206
1207
1208
1209template <int dim, int spacedim>
1210std::unique_ptr<Manifold<dim, spacedim>>
1212{
1213 return std::make_unique<EllipticalManifold<dim, spacedim>>(*this);
1214}
1215
1216
1217
1218template <int dim, int spacedim>
1221{
1222 Tensor<1, spacedim> periodicity;
1223 // The second elliptical coordinate is periodic, while the first is not.
1224 // Enforce periodicity on the last variable.
1225 periodicity[spacedim - 1] = 2.0 * numbers::PI;
1226 return periodicity;
1227}
1228
1229
1230
1231template <int dim, int spacedim>
1238
1239
1240
1241template <>
1244{
1245 const double cs = std::cos(chart_point[1]);
1246 const double sn = std::sin(chart_point[1]);
1247 // Coordinates in the reference frame (i.e. major axis direction is
1248 // x-axis)
1249 const double x = chart_point[0] * cosh_u * cs;
1250 const double y = chart_point[0] * sinh_u * sn;
1251 // Rotate them according to the major axis direction
1252 const Point<2> p(direction[0] * x - direction[1] * y,
1253 direction[1] * x + direction[0] * y);
1254 return p + center;
1255}
1256
1257
1258
1259template <int dim, int spacedim>
1266
1267
1268
1269template <>
1272{
1273 // Moving space_point in the reference coordinate system.
1274 const double x0 = space_point[0] - center[0];
1275 const double y0 = space_point[1] - center[1];
1276 const double x = direction[0] * x0 + direction[1] * y0;
1277 const double y = -direction[1] * x0 + direction[0] * y0;
1278 const double pt0 =
1279 std::sqrt((x * x) / (cosh_u * cosh_u) + (y * y) / (sinh_u * sinh_u));
1280 // If the radius is exactly zero, the point coincides with the origin.
1281 if (pt0 == 0.0)
1282 {
1283 return center;
1284 }
1285 double cos_eta = x / (pt0 * cosh_u);
1286 if (cos_eta < -1.0)
1287 {
1288 cos_eta = -1.0;
1289 }
1290 if (cos_eta > 1.0)
1291 {
1292 cos_eta = 1.0;
1293 }
1294 const double eta = std::acos(cos_eta);
1295 const double pt1 = (std::signbit(y) ? 2.0 * numbers::PI - eta : eta);
1296 return {pt0, pt1};
1297}
1298
1299
1300
1301template <int dim, int spacedim>
1309
1310
1311
1312template <>
1315 const Point<2> &chart_point) const
1316{
1317 const double cs = std::cos(chart_point[1]);
1318 const double sn = std::sin(chart_point[1]);
1319 Tensor<2, 2> dX;
1320 dX[0][0] = cosh_u * cs;
1321 dX[0][1] = -chart_point[0] * cosh_u * sn;
1322 dX[1][0] = sinh_u * sn;
1323 dX[1][1] = chart_point[0] * sinh_u * cs;
1324
1325 // rotate according to the major axis direction
1327 {{+direction[0], -direction[1]}, {direction[1], direction[0]}}};
1328
1329 return rot * dX;
1330}
1331
1332
1333
1334// ============================================================
1335// FunctionManifold
1336// ============================================================
1337template <int dim, int spacedim, int chartdim>
1339 const Function<chartdim> &push_forward_function,
1340 const Function<spacedim> &pull_back_function,
1341 const Tensor<1, chartdim> &periodicity,
1342 const double tolerance)
1343 : ChartManifold<dim, spacedim, chartdim>(periodicity)
1344 , const_map()
1345 , push_forward_function(&push_forward_function)
1346 , pull_back_function(&pull_back_function)
1347 , tolerance(tolerance)
1348 , owns_pointers(false)
1349 , finite_difference_step(0)
1350{
1351 AssertDimension(push_forward_function.n_components, spacedim);
1352 AssertDimension(pull_back_function.n_components, chartdim);
1353}
1354
1355
1356
1357template <int dim, int spacedim, int chartdim>
1359 std::unique_ptr<Function<chartdim>> push_forward,
1360 std::unique_ptr<Function<spacedim>> pull_back,
1361 const Tensor<1, chartdim> &periodicity,
1362 const double tolerance)
1363 : ChartManifold<dim, spacedim, chartdim>(periodicity)
1364 , const_map()
1365 , push_forward_function(push_forward.release())
1366 , pull_back_function(pull_back.release())
1367 , tolerance(tolerance)
1368 , owns_pointers(true)
1369 , finite_difference_step(0)
1370{
1371 AssertDimension(push_forward_function->n_components, spacedim);
1372 AssertDimension(pull_back_function->n_components, chartdim);
1373}
1374
1375
1376
1377template <int dim, int spacedim, int chartdim>
1379 const std::string push_forward_expression,
1380 const std::string pull_back_expression,
1381 const Tensor<1, chartdim> &periodicity,
1382 const typename FunctionParser<spacedim>::ConstMap const_map,
1383 const std::string chart_vars,
1384 const std::string space_vars,
1385 const double tolerance,
1386 const double h)
1387 : ChartManifold<dim, spacedim, chartdim>(periodicity)
1388 , const_map(const_map)
1389 , tolerance(tolerance)
1390 , owns_pointers(true)
1391 , push_forward_expression(push_forward_expression)
1392 , pull_back_expression(pull_back_expression)
1393 , chart_vars(chart_vars)
1394 , space_vars(space_vars)
1395 , finite_difference_step(h)
1396{
1397 FunctionParser<chartdim> *pf = new FunctionParser<chartdim>(spacedim, 0.0, h);
1398 FunctionParser<spacedim> *pb = new FunctionParser<spacedim>(chartdim, 0.0, h);
1402 pull_back_function = pb;
1403}
1404
1405
1406
1407template <int dim, int spacedim, int chartdim>
1409{
1410 if (owns_pointers == true)
1411 {
1412 const Function<chartdim> *pf = push_forward_function;
1413 push_forward_function = nullptr;
1414 delete pf;
1415
1416 const Function<spacedim> *pb = pull_back_function;
1417 pull_back_function = nullptr;
1418 delete pb;
1419 }
1420}
1421
1422
1423
1424template <int dim, int spacedim, int chartdim>
1425std::unique_ptr<Manifold<dim, spacedim>>
1427{
1428 // This manifold can be constructed either by providing an expression for the
1429 // push forward and the pull back charts, or by providing two Function
1430 // objects. In the first case, the push_forward and pull_back functions are
1431 // created internally in FunctionManifold, and destroyed when this object is
1432 // deleted. In the second case, the function objects are destroyed if they
1433 // are passed as pointers upon construction.
1434 // We need to make sure that our cloned object is constructed in the
1435 // same way this class was constructed, and that its internal Function
1436 // pointers point either to the same Function objects used to construct this
1437 // function or that the newly generated manifold creates internally the
1438 // push_forward and pull_back functions using the same expressions that were
1439 // used to construct this class.
1440 if (!(push_forward_expression.empty() && pull_back_expression.empty()))
1441 {
1442 return std::make_unique<FunctionManifold<dim, spacedim, chartdim>>(
1443 push_forward_expression,
1444 pull_back_expression,
1445 this->get_periodicity(),
1446 const_map,
1447 chart_vars,
1448 space_vars,
1449 tolerance,
1450 finite_difference_step);
1451 }
1452 else
1453 {
1454 return std::make_unique<FunctionManifold<dim, spacedim, chartdim>>(
1455 *push_forward_function,
1456 *pull_back_function,
1457 this->get_periodicity(),
1458 tolerance);
1459 }
1460}
1461
1462
1463
1464template <int dim, int spacedim, int chartdim>
1467 const Point<chartdim> &chart_point) const
1468{
1469 Vector<double> pf(spacedim);
1470 Point<spacedim> result;
1471 push_forward_function->vector_value(chart_point, pf);
1472 for (unsigned int i = 0; i < spacedim; ++i)
1473 result[i] = pf[i];
1474
1475 if constexpr (running_in_debug_mode())
1476 {
1477 Vector<double> pb(chartdim);
1478 pull_back_function->vector_value(result, pb);
1479 for (unsigned int i = 0; i < chartdim; ++i)
1480 Assert(
1481 (chart_point.norm() > tolerance &&
1482 (std::abs(pb[i] - chart_point[i]) <
1483 tolerance * chart_point.norm())) ||
1484 (std::abs(pb[i] - chart_point[i]) < tolerance),
1485 ExcMessage(
1486 "The push forward is not the inverse of the pull back! Bailing out."));
1487 }
1488
1489 return result;
1490}
1491
1492
1493
1494template <int dim, int spacedim, int chartdim>
1497 const Point<chartdim> &chart_point) const
1498{
1500 for (unsigned int i = 0; i < spacedim; ++i)
1501 {
1502 const auto gradient = push_forward_function->gradient(chart_point, i);
1503 for (unsigned int j = 0; j < chartdim; ++j)
1504 DF[i][j] = gradient[j];
1505 }
1506 return DF;
1507}
1508
1509
1510
1511template <int dim, int spacedim, int chartdim>
1514 const Point<spacedim> &space_point) const
1515{
1516 Vector<double> pb(chartdim);
1517 Point<chartdim> result;
1518 pull_back_function->vector_value(space_point, pb);
1519 for (unsigned int i = 0; i < chartdim; ++i)
1520 result[i] = pb[i];
1521 return result;
1522}
1523
1524
1525
1526// ============================================================
1527// TorusManifold
1528// ============================================================
1529template <int dim>
1532{
1533 double x = p[0];
1534 double z = p[1];
1535 double y = p[2];
1536 double phi = std::atan2(y, x);
1537 double theta = std::atan2(z, std::sqrt(x * x + y * y) - centerline_radius);
1538 double w =
1539 std::sqrt(Utilities::fixed_power<2>(y - std::sin(phi) * centerline_radius) +
1540 Utilities::fixed_power<2>(x - std::cos(phi) * centerline_radius) +
1541 z * z) /
1542 inner_radius;
1543 return {phi, theta, w};
1544}
1545
1546
1547
1548template <int dim>
1551{
1552 double phi = chart_point[0];
1553 double theta = chart_point[1];
1554 double w = chart_point[2];
1555
1556 return {std::cos(phi) * centerline_radius +
1557 inner_radius * w * std::cos(theta) * std::cos(phi),
1558 inner_radius * w * std::sin(theta),
1559 std::sin(phi) * centerline_radius +
1560 inner_radius * w * std::cos(theta) * std::sin(phi)};
1561}
1562
1563
1564
1565template <int dim>
1566TorusManifold<dim>::TorusManifold(const double centerline_radius,
1567 const double inner_radius)
1568 : ChartManifold<dim, 3, 3>(Point<3>(2 * numbers::PI, 2 * numbers::PI, 0.0))
1569 , centerline_radius(centerline_radius)
1570 , inner_radius(inner_radius)
1571{
1573 ExcMessage("The centerline radius must be greater than the "
1574 "inner radius."));
1575 Assert(inner_radius > 0.0, ExcMessage("The inner radius must be positive."));
1576}
1577
1578
1579
1580template <int dim>
1581std::unique_ptr<Manifold<dim, 3>>
1583{
1584 return std::make_unique<TorusManifold<dim>>(centerline_radius, inner_radius);
1585}
1586
1587
1588
1589template <int dim>
1592{
1594
1595 double phi = chart_point[0];
1596 double theta = chart_point[1];
1597 double w = chart_point[2];
1598
1599 DX[0][0] = -std::sin(phi) * centerline_radius -
1600 inner_radius * w * std::cos(theta) * std::sin(phi);
1601 DX[0][1] = -inner_radius * w * std::sin(theta) * std::cos(phi);
1602 DX[0][2] = inner_radius * std::cos(theta) * std::cos(phi);
1603
1604 DX[1][0] = 0;
1605 DX[1][1] = inner_radius * w * std::cos(theta);
1606 DX[1][2] = inner_radius * std::sin(theta);
1607
1608 DX[2][0] = std::cos(phi) * centerline_radius +
1609 inner_radius * w * std::cos(theta) * std::cos(phi);
1610 DX[2][1] = -inner_radius * w * std::sin(theta) * std::sin(phi);
1611 DX[2][2] = inner_radius * std::cos(theta) * std::sin(phi);
1612
1613 return DX;
1614}
1615
1616
1617
1618// ============================================================
1619// TransfiniteInterpolationManifold
1620// ============================================================
1621template <int dim, int spacedim>
1624 : triangulation(nullptr)
1625 , level_coarse(-1)
1626{
1627 AssertThrow(dim > 1, ExcNotImplemented());
1628}
1629
1630
1631
1632template <int dim, int spacedim>
1634 spacedim>::~TransfiniteInterpolationManifold()
1635{
1636 if (clear_signal.connected())
1637 clear_signal.disconnect();
1638}
1639
1640
1641
1642template <int dim, int spacedim>
1643std::unique_ptr<Manifold<dim, spacedim>>
1645{
1647 if (triangulation)
1648 ptr->initialize(*triangulation);
1649 return std::unique_ptr<Manifold<dim, spacedim>>(ptr);
1650}
1651
1652
1653
1654template <int dim, int spacedim>
1655void
1657 const Triangulation<dim, spacedim> &triangulation)
1658{
1659 this->triangulation = &triangulation;
1660 // In case the triangulation is cleared, remove the pointers by a signal:
1661 clear_signal.disconnect();
1662 clear_signal = triangulation.signals.clear.connect([&]() -> void {
1663 this->triangulation = nullptr;
1664 this->level_coarse = -1;
1665 });
1666 level_coarse = triangulation.last()->level();
1667 coarse_cell_is_flat.resize(triangulation.n_cells(level_coarse), false);
1668 quadratic_approximation.clear();
1669
1670 // In case of dim == spacedim we perform a quadratic approximation in
1671 // InverseQuadraticApproximation(), thus initialize the unit_points
1672 // vector with one subdivision to get 3^dim unit_points.
1673 //
1674 // In the co-dimension one case (meaning dim < spacedim) we have to fall
1675 // back to a simple GridTools::affine_cell_approximation<dim>() which
1676 // requires 2^dim points, instead. Thus, initialize the QIterated
1677 // quadrature with no subdivisions.
1678 const std::vector<Point<dim>> unit_points =
1679 QIterated<dim>(QTrapezoid<1>(), (dim == spacedim ? 2 : 1)).get_points();
1680 std::vector<Point<spacedim>> real_points(unit_points.size());
1681
1682 for (const auto &cell : triangulation.active_cell_iterators())
1683 {
1684 bool cell_is_flat = true;
1685 for (const auto l : cell->line_indices())
1686 if (cell->line(l)->manifold_id() != cell->manifold_id() &&
1687 cell->line(l)->manifold_id() != numbers::flat_manifold_id)
1688 cell_is_flat = false;
1689 if constexpr (dim > 2)
1690 for (const auto q : cell->face_indices())
1691 if (cell->quad(q)->manifold_id() != cell->manifold_id() &&
1692 cell->quad(q)->manifold_id() != numbers::flat_manifold_id)
1693 cell_is_flat = false;
1694 AssertIndexRange(static_cast<unsigned int>(cell->index()),
1695 coarse_cell_is_flat.size());
1696 coarse_cell_is_flat[cell->index()] = cell_is_flat;
1697
1698 // build quadratic interpolation
1699 for (unsigned int i = 0; i < unit_points.size(); ++i)
1700 real_points[i] = push_forward(cell, unit_points[i]);
1701 quadratic_approximation.emplace_back(real_points, unit_points);
1702 }
1703}
1704
1705
1706
1707namespace
1708{
1709 // version for 1d
1710 template <typename AccessorType>
1712 compute_transfinite_interpolation(const AccessorType &cell,
1713 const Point<1> &chart_point,
1714 const bool /*cell_is_flat*/)
1715 {
1716 return cell.vertex(0) * (1. - chart_point[0]) +
1717 cell.vertex(1) * chart_point[0];
1718 }
1719
1720 // version for 2d
1721 template <typename AccessorType>
1723 compute_transfinite_interpolation(const AccessorType &cell,
1724 const Point<2> &chart_point,
1725 const bool cell_is_flat)
1726 {
1727 const unsigned int dim = AccessorType::dimension;
1728 const unsigned int spacedim = AccessorType::space_dimension;
1729 const types::manifold_id my_manifold_id = cell.manifold_id();
1731
1732 // formula see wikipedia
1733 // https://en.wikipedia.org/wiki/Transfinite_interpolation
1734 // S(u,v) = (1-v)c_1(u)+v c_3(u) + (1-u)c_2(v) + u c_4(v) -
1735 // [(1-u)(1-v)P_0 + u(1-v) P_1 + (1-u)v P_2 + uv P_3]
1736 const std::array<Point<spacedim>, 4> vertices{
1737 {cell.vertex(0), cell.vertex(1), cell.vertex(2), cell.vertex(3)}};
1738
1739 // this evaluates all bilinear shape functions because we need them
1740 // repeatedly. we will update this values in the complicated case with
1741 // curved lines below
1742 std::array<double, 4> weights_vertices{
1743 {(1. - chart_point[0]) * (1. - chart_point[1]),
1744 chart_point[0] * (1. - chart_point[1]),
1745 (1. - chart_point[0]) * chart_point[1],
1746 chart_point[0] * chart_point[1]}};
1747
1748 Point<spacedim> new_point;
1749 if (cell_is_flat)
1750 for (const unsigned int v : GeometryInfo<2>::vertex_indices())
1751 new_point += weights_vertices[v] * vertices[v];
1752 else
1753 {
1754 // The second line in the formula tells us to subtract the
1755 // contribution of the vertices. If a line employs the same manifold
1756 // as the cell, we can merge the weights of the line with the weights
1757 // of the vertex with a negative sign while going through the faces
1758 // (this is a bit artificial in 2d but it becomes clear in 3d where we
1759 // avoid looking at the faces' orientation and other complications).
1760
1761 // add the contribution from the lines around the cell (first line in
1762 // formula)
1763 std::array<double, GeometryInfo<2>::vertices_per_face> weights;
1764 std::array<Point<spacedim>, GeometryInfo<2>::vertices_per_face> points;
1765 // note that the views are immutable, but the arrays are not
1766 const auto weights_view =
1767 make_array_view(weights.begin(), weights.end());
1768 const auto points_view = make_array_view(points.begin(), points.end());
1769
1770 for (unsigned int line = 0; line < GeometryInfo<2>::lines_per_cell;
1771 ++line)
1772 {
1773 const double my_weight =
1774 (line % 2) ? chart_point[line / 2] : 1 - chart_point[line / 2];
1775 const double line_point = chart_point[1 - line / 2];
1776
1777 // Same manifold or invalid id which will go back to the same
1778 // class -> contribution should be added for the final point,
1779 // which means that we subtract the current weight from the
1780 // negative weight applied to the vertex
1781 const types::manifold_id line_manifold_id =
1782 cell.line(line)->manifold_id();
1783 if (line_manifold_id == my_manifold_id ||
1784 line_manifold_id == numbers::flat_manifold_id)
1785 {
1786 weights_vertices[GeometryInfo<2>::line_to_cell_vertices(line,
1787 0)] -=
1788 my_weight * (1. - line_point);
1789 weights_vertices[GeometryInfo<2>::line_to_cell_vertices(line,
1790 1)] -=
1791 my_weight * line_point;
1792 }
1793 else
1794 {
1795 points[0] =
1796 vertices[GeometryInfo<2>::line_to_cell_vertices(line, 0)];
1797 points[1] =
1798 vertices[GeometryInfo<2>::line_to_cell_vertices(line, 1)];
1799 weights[0] = 1. - line_point;
1800 weights[1] = line_point;
1801 new_point +=
1802 my_weight * tria.get_manifold(line_manifold_id)
1803 .get_new_point(points_view, weights_view);
1804 }
1805 }
1806
1807 // subtract contribution from the vertices (second line in formula)
1808 for (const unsigned int v : GeometryInfo<2>::vertex_indices())
1809 new_point -= weights_vertices[v] * vertices[v];
1810 }
1811
1812 return new_point;
1813 }
1814
1815 // this is replicated from GeometryInfo::face_to_cell_vertices since we need
1816 // it very often in compute_transfinite_interpolation and the function is
1817 // performance critical
1818 static constexpr unsigned int face_to_cell_vertices_3d[6][4] = {{0, 2, 4, 6},
1819 {1, 3, 5, 7},
1820 {0, 4, 1, 5},
1821 {2, 6, 3, 7},
1822 {0, 1, 2, 3},
1823 {4, 5, 6, 7}};
1824
1825 // this is replicated from GeometryInfo::face_to_cell_lines since we need it
1826 // very often in compute_transfinite_interpolation and the function is
1827 // performance critical
1828 static constexpr unsigned int face_to_cell_lines_3d[6][4] = {{8, 10, 0, 4},
1829 {9, 11, 1, 5},
1830 {2, 6, 8, 9},
1831 {3, 7, 10, 11},
1832 {0, 1, 2, 3},
1833 {4, 5, 6, 7}};
1834
1835 // version for 3d
1836 template <typename AccessorType>
1838 compute_transfinite_interpolation(const AccessorType &cell,
1839 const Point<3> &chart_point,
1840 const bool cell_is_flat)
1841 {
1842 const unsigned int dim = AccessorType::dimension;
1843 const unsigned int spacedim = AccessorType::space_dimension;
1844 const types::manifold_id my_manifold_id = cell.manifold_id();
1846
1847 // Same approach as in 2d, but adding the faces, subtracting the edges, and
1848 // adding the vertices
1849 const std::array<Point<spacedim>, 8> vertices{{cell.vertex(0),
1850 cell.vertex(1),
1851 cell.vertex(2),
1852 cell.vertex(3),
1853 cell.vertex(4),
1854 cell.vertex(5),
1855 cell.vertex(6),
1856 cell.vertex(7)}};
1857
1858 // store the components of the linear shape functions because we need them
1859 // repeatedly. we allow for 10 such shape functions to wrap around the
1860 // first four once again for easier face access.
1861 double linear_shapes[10];
1862 for (unsigned int d = 0; d < 3; ++d)
1863 {
1864 linear_shapes[2 * d] = 1. - chart_point[d];
1865 linear_shapes[2 * d + 1] = chart_point[d];
1866 }
1867
1868 // wrap linear shape functions around for access in face loop
1869 for (unsigned int d = 6; d < 10; ++d)
1870 linear_shapes[d] = linear_shapes[d - 6];
1871
1872 std::array<double, 8> weights_vertices;
1873 for (unsigned int i2 = 0, v = 0; i2 < 2; ++i2)
1874 for (unsigned int i1 = 0; i1 < 2; ++i1)
1875 for (unsigned int i0 = 0; i0 < 2; ++i0, ++v)
1876 weights_vertices[v] =
1877 (linear_shapes[4 + i2] * linear_shapes[2 + i1]) * linear_shapes[i0];
1878
1879 Point<spacedim> new_point;
1880 if (cell_is_flat)
1881 for (unsigned int v = 0; v < 8; ++v)
1882 new_point += weights_vertices[v] * vertices[v];
1883 else
1884 {
1885 // identify the weights for the lines to be accumulated (vertex
1886 // weights are set outside and coincide with the flat manifold case)
1887
1888 std::array<double, GeometryInfo<3>::lines_per_cell> weights_lines;
1889 std::fill(weights_lines.begin(), weights_lines.end(), 0.0);
1890
1891 // start with the contributions of the faces
1892 std::array<double, GeometryInfo<2>::vertices_per_cell> weights;
1893 std::array<Point<spacedim>, GeometryInfo<2>::vertices_per_cell> points;
1894 // note that the views are immutable, but the arrays are not
1895 const auto weights_view =
1896 make_array_view(weights.begin(), weights.end());
1897 const auto points_view = make_array_view(points.begin(), points.end());
1898
1899 for (const unsigned int face : GeometryInfo<3>::face_indices())
1900 {
1901 const double my_weight = linear_shapes[face];
1902 const unsigned int face_even = face - face % 2;
1903
1904 if (std::abs(my_weight) < 1e-13)
1905 continue;
1906
1907 // same manifold or invalid id which will go back to the same class
1908 // -> face will interpolate from the surrounding lines and vertices
1909 const types::manifold_id face_manifold_id =
1910 cell.face(face)->manifold_id();
1911 if (face_manifold_id == my_manifold_id ||
1912 face_manifold_id == numbers::flat_manifold_id)
1913 {
1914 for (unsigned int line = 0;
1915 line < GeometryInfo<2>::lines_per_cell;
1916 ++line)
1917 {
1918 const double line_weight =
1919 linear_shapes[face_even + 2 + line];
1920 weights_lines[face_to_cell_lines_3d[face][line]] +=
1921 my_weight * line_weight;
1922 }
1923 // as to the indices inside linear_shapes: we use the index
1924 // wrapped around at 2*d, ensuring the correct orientation of
1925 // the face's coordinate system with respect to the
1926 // lexicographic indices
1927 weights_vertices[face_to_cell_vertices_3d[face][0]] -=
1928 linear_shapes[face_even + 2] *
1929 (linear_shapes[face_even + 4] * my_weight);
1930 weights_vertices[face_to_cell_vertices_3d[face][1]] -=
1931 linear_shapes[face_even + 3] *
1932 (linear_shapes[face_even + 4] * my_weight);
1933 weights_vertices[face_to_cell_vertices_3d[face][2]] -=
1934 linear_shapes[face_even + 2] *
1935 (linear_shapes[face_even + 5] * my_weight);
1936 weights_vertices[face_to_cell_vertices_3d[face][3]] -=
1937 linear_shapes[face_even + 3] *
1938 (linear_shapes[face_even + 5] * my_weight);
1939 }
1940 else
1941 {
1942 for (const unsigned int v : GeometryInfo<2>::vertex_indices())
1943 points[v] = vertices[face_to_cell_vertices_3d[face][v]];
1944 weights[0] =
1945 linear_shapes[face_even + 2] * linear_shapes[face_even + 4];
1946 weights[1] =
1947 linear_shapes[face_even + 3] * linear_shapes[face_even + 4];
1948 weights[2] =
1949 linear_shapes[face_even + 2] * linear_shapes[face_even + 5];
1950 weights[3] =
1951 linear_shapes[face_even + 3] * linear_shapes[face_even + 5];
1952 new_point +=
1953 my_weight * tria.get_manifold(face_manifold_id)
1954 .get_new_point(points_view, weights_view);
1955 }
1956 }
1957
1958 // next subtract the contributions of the lines
1959 const auto weights_view_line =
1960 make_array_view(weights.begin(), weights.begin() + 2);
1961 const auto points_view_line =
1962 make_array_view(points.begin(), points.begin() + 2);
1963 for (unsigned int line = 0; line < GeometryInfo<3>::lines_per_cell;
1964 ++line)
1965 {
1966 const double line_point =
1967 (line < 8 ? chart_point[1 - (line % 4) / 2] : chart_point[2]);
1968 double my_weight = 0.;
1969 if (line < 8)
1970 my_weight = linear_shapes[line % 4] * linear_shapes[4 + line / 4];
1971 else
1972 {
1973 const unsigned int subline = line - 8;
1974 my_weight =
1975 linear_shapes[subline % 2] * linear_shapes[2 + subline / 2];
1976 }
1977 my_weight -= weights_lines[line];
1978
1979 if (std::abs(my_weight) < 1e-13)
1980 continue;
1981
1982 const types::manifold_id line_manifold_id =
1983 cell.line(line)->manifold_id();
1984 if (line_manifold_id == my_manifold_id ||
1985 line_manifold_id == numbers::flat_manifold_id)
1986 {
1987 weights_vertices[GeometryInfo<3>::line_to_cell_vertices(line,
1988 0)] -=
1989 my_weight * (1. - line_point);
1990 weights_vertices[GeometryInfo<3>::line_to_cell_vertices(line,
1991 1)] -=
1992 my_weight * (line_point);
1993 }
1994 else
1995 {
1996 points[0] =
1997 vertices[GeometryInfo<3>::line_to_cell_vertices(line, 0)];
1998 points[1] =
1999 vertices[GeometryInfo<3>::line_to_cell_vertices(line, 1)];
2000 weights[0] = 1. - line_point;
2001 weights[1] = line_point;
2002 new_point -= my_weight * tria.get_manifold(line_manifold_id)
2003 .get_new_point(points_view_line,
2004 weights_view_line);
2005 }
2006 }
2007
2008 // finally add the contribution of the
2009 for (const unsigned int v : GeometryInfo<dim>::vertex_indices())
2010 new_point += weights_vertices[v] * vertices[v];
2011 }
2012 return new_point;
2013 }
2014} // namespace
2015
2016
2017
2018template <int dim, int spacedim>
2022 const Point<dim> &chart_point) const
2023{
2024 AssertDimension(cell->level(), level_coarse);
2025
2026 // check that the point is in the unit cell which is the current chart
2027 // Tolerance 5e-4 chosen that the method also works with manifolds
2028 // that have some discretization error like SphericalManifold
2030 ExcMessage("chart_point is not in unit interval"));
2031
2032 return compute_transfinite_interpolation(*cell,
2033 chart_point,
2034 coarse_cell_is_flat[cell->index()]);
2035}
2036
2037
2038
2039template <int dim, int spacedim>
2043 const Point<dim> &chart_point,
2044 const Point<spacedim> &pushed_forward_chart_point) const
2045{
2046 // compute the derivative with the help of finite differences
2048 for (unsigned int d = 0; d < dim; ++d)
2049 {
2050 Point<dim> modified = chart_point;
2051 const double step = chart_point[d] > 0.5 ? -1e-8 : 1e-8;
2052
2053 // avoid checking outside of the unit interval
2054 modified[d] += step;
2055 Tensor<1, spacedim> difference =
2056 compute_transfinite_interpolation(*cell,
2057 modified,
2058 coarse_cell_is_flat[cell->index()]) -
2059 pushed_forward_chart_point;
2060 for (unsigned int e = 0; e < spacedim; ++e)
2061 grad[e][d] = difference[e] / step;
2062 }
2063 return grad;
2064}
2065
2066
2067
2068template <int dim, int spacedim>
2072 const Point<spacedim> &point,
2073 const Point<dim> &initial_guess) const
2074{
2075 Point<dim> outside;
2076 for (unsigned int d = 0; d < dim; ++d)
2078
2079 // project the user-given input to unit cell
2080 Point<dim> chart_point = cell->reference_cell().closest_point(initial_guess);
2081
2082 // run quasi-Newton iteration with a combination of finite differences for
2083 // the exact Jacobian and "Broyden's good method". As opposed to the various
2084 // mapping implementations, this class does not throw exception upon failure
2085 // as those are relatively expensive and failure occurs quite regularly in
2086 // the implementation of the compute_chart_points method.
2087 Tensor<1, spacedim> residual =
2088 point -
2089 compute_transfinite_interpolation(*cell,
2090 chart_point,
2091 coarse_cell_is_flat[cell->index()]);
2092 const double tolerance = 1e-21 * Utilities::fixed_power<2>(cell->diameter());
2093 double residual_norm_square = residual.norm_square();
2095 bool must_recompute_jacobian = true;
2096 for (unsigned int i = 0; i < 100; ++i)
2097 {
2098 if (residual_norm_square < tolerance)
2099 {
2100 // do a final update of the point with the last available Jacobian
2101 // information. The residual is close to zero due to the check
2102 // above, but me might improve some of the last digits by a final
2103 // Newton-like step with step length 1
2104 Tensor<1, dim> update;
2105 for (unsigned int d = 0; d < spacedim; ++d)
2106 for (unsigned int e = 0; e < dim; ++e)
2107 update[e] += inv_grad[d][e] * residual[d];
2108 return chart_point + update;
2109 }
2110
2111 // every 9 iterations, including the first time around, we create an
2112 // approximation of the Jacobian with finite differences. Broyden's
2113 // method usually does not need more than 5-8 iterations, but sometimes
2114 // we might have had a bad initial guess and then we can accelerate
2115 // convergence considerably with getting the actual Jacobian rather than
2116 // using secant-like methods (one gradient calculation in 3d costs as
2117 // much as 3 more iterations). this usually happens close to convergence
2118 // and one more step with the finite-differenced Jacobian leads to
2119 // convergence
2120 if (must_recompute_jacobian || i % 9 == 0)
2121 {
2122 // if the determinant is zero or negative, the mapping is either not
2123 // invertible or already has inverted and we are outside the valid
2124 // chart region. Note that the Jacobian here represents the
2125 // derivative of the forward map and should have a positive
2126 // determinant since we use properly oriented meshes.
2128 push_forward_gradient(cell,
2129 chart_point,
2130 Point<spacedim>(point - residual));
2131 if (grad.determinant() <= 0.0)
2132 return outside;
2133 inv_grad = grad.covariant_form();
2134 must_recompute_jacobian = false;
2135 }
2136 Tensor<1, dim> update;
2137 for (unsigned int d = 0; d < spacedim; ++d)
2138 for (unsigned int e = 0; e < dim; ++e)
2139 update[e] += inv_grad[d][e] * residual[d];
2140
2141 // Line search, accept step if the residual has decreased
2142 double alpha = 1.;
2143
2144 // check if point is inside 1.2 times the unit cell to avoid
2145 // hitting points very far away from valid ones in the manifolds
2146 while (
2147 !GeometryInfo<dim>::is_inside_unit_cell(chart_point + alpha * update,
2148 0.2) &&
2149 alpha > 1e-7)
2150 alpha *= 0.5;
2151
2152 const Tensor<1, spacedim> old_residual = residual;
2153 while (alpha > 1e-4)
2154 {
2155 Point<dim> guess = chart_point + alpha * update;
2156 const Tensor<1, spacedim> residual_guess =
2157 point - compute_transfinite_interpolation(
2158 *cell, guess, coarse_cell_is_flat[cell->index()]);
2159 const double residual_norm_new = residual_guess.norm_square();
2160 if (residual_norm_new < residual_norm_square)
2161 {
2162 residual = residual_guess;
2163 residual_norm_square = residual_norm_new;
2164 chart_point += alpha * update;
2165 break;
2166 }
2167 else
2168 alpha *= 0.5;
2169 }
2170 // If alpha got very small, it is likely due to a bad Jacobian
2171 // approximation with Broyden's method (relatively far away from the
2172 // zero), which can be corrected by the outer loop when a Newton update
2173 // is recomputed. The second case is when the Jacobian is actually bad
2174 // and we should fail as early as possible. Since we cannot really
2175 // distinguish the two, we must continue here in any case.
2176 if (alpha <= 1e-4)
2177 must_recompute_jacobian = true;
2178
2179 // update the inverse Jacobian with "Broyden's good method" and
2180 // Sherman-Morrison formula for the update of the inverse, see
2181 // https://en.wikipedia.org/wiki/Broyden%27s_method
2182 // J^{-1}_n = J^{-1}_{n-1} + (delta x_n - J^{-1}_{n-1} delta f_n) /
2183 // (delta x_n^T J_{-1}_{n-1} delta f_n) delta x_n^T J^{-1}_{n-1}
2184
2185 // switch sign in residual as compared to the formula above because we
2186 // use a negative definition of the residual with respect to the
2187 // Jacobian
2188 const Tensor<1, spacedim> delta_f = old_residual - residual;
2189
2190 Tensor<1, dim> Jinv_deltaf;
2191 for (unsigned int d = 0; d < spacedim; ++d)
2192 for (unsigned int e = 0; e < dim; ++e)
2193 Jinv_deltaf[e] += inv_grad[d][e] * delta_f[d];
2194
2195 const Tensor<1, dim> delta_x = alpha * update;
2196
2197 // prevent division by zero. This number should be scale-invariant
2198 // because Jinv_deltaf carries no units and x is in reference
2199 // coordinates.
2200 if (std::abs(delta_x * Jinv_deltaf) > 1e-12 && !must_recompute_jacobian)
2201 {
2202 const Tensor<1, dim> factor =
2203 (delta_x - Jinv_deltaf) / (delta_x * Jinv_deltaf);
2204 Tensor<1, spacedim> jac_update;
2205 for (unsigned int d = 0; d < spacedim; ++d)
2206 for (unsigned int e = 0; e < dim; ++e)
2207 jac_update[d] += delta_x[e] * inv_grad[d][e];
2208 for (unsigned int d = 0; d < spacedim; ++d)
2209 for (unsigned int e = 0; e < dim; ++e)
2210 inv_grad[d][e] += factor[e] * jac_update[d];
2211 }
2212 }
2213 return outside;
2214}
2215
2216
2217
2218template <int dim, int spacedim>
2219std::array<unsigned int, 20>
2222 const ArrayView<const Point<spacedim>> &points) const
2223{
2224 // The methods to identify cells around points in GridTools are all written
2225 // for the active cells, but we are here looking at some cells at the coarse
2226 // level.
2227 Assert(triangulation != nullptr, ExcNotInitialized());
2228 Assert(triangulation->begin_active()->level() >= level_coarse,
2229 ExcMessage("The manifold was initialized with level " +
2230 std::to_string(level_coarse) + " but there are now" +
2231 "active cells on a lower level. Coarsening the mesh is " +
2232 "currently not supported"));
2233
2234 // This computes the distance of the surrounding points transformed to the
2235 // unit cell from the unit cell.
2237 triangulation->begin(
2238 level_coarse),
2239 endc =
2240 triangulation->end(
2241 level_coarse);
2242 boost::container::small_vector<std::pair<double, unsigned int>, 200>
2243 distances_and_cells;
2244 for (; cell != endc; ++cell)
2245 {
2246 // only consider cells where the current manifold is attached
2247 if (&cell->get_manifold() != this)
2248 continue;
2249
2250 std::array<Point<spacedim>, GeometryInfo<dim>::vertices_per_cell>
2251 vertices;
2252 for (const unsigned int vertex_n : GeometryInfo<dim>::vertex_indices())
2253 {
2254 vertices[vertex_n] = cell->vertex(vertex_n);
2255 }
2256
2257 // cheap check: if any of the points is not inside a circle around the
2258 // center of the loop, we can skip the expensive part below (this assumes
2259 // that the manifold does not deform the grid too much)
2260 Point<spacedim> center;
2261 for (const unsigned int v : GeometryInfo<dim>::vertex_indices())
2262 center += vertices[v];
2264 double radius_square = 0.;
2265 for (const unsigned int v : GeometryInfo<dim>::vertex_indices())
2266 radius_square =
2267 std::max(radius_square, (center - vertices[v]).norm_square());
2268 bool inside_circle = true;
2269 for (unsigned int i = 0; i < points.size(); ++i)
2270 if ((center - points[i]).norm_square() > radius_square * 1.5)
2271 {
2272 inside_circle = false;
2273 break;
2274 }
2275 if (inside_circle == false)
2276 continue;
2277
2278 // slightly more expensive search
2279 double current_distance = 0;
2280 for (unsigned int i = 0; i < points.size(); ++i)
2281 {
2282 Point<dim> point =
2283 quadratic_approximation[cell->index()].compute(points[i]);
2284 current_distance += GeometryInfo<dim>::distance_to_unit_cell(point);
2285 }
2286 distances_and_cells.push_back(
2287 std::make_pair(current_distance, cell->index()));
2288 }
2289 // no coarse cell could be found -> transformation failed
2290 AssertThrow(distances_and_cells.size() > 0,
2292 std::sort(distances_and_cells.begin(), distances_and_cells.end());
2293 std::array<unsigned int, 20> cells;
2295 for (unsigned int i = 0; i < distances_and_cells.size() && i < cells.size();
2296 ++i)
2297 cells[i] = distances_and_cells[i].second;
2298
2299 return cells;
2300}
2301
2302
2303
2304template <int dim, int spacedim>
2307 const ArrayView<const Point<spacedim>> &surrounding_points,
2308 ArrayView<Point<dim>> chart_points) const
2309{
2310 Assert(surrounding_points.size() == chart_points.size(),
2311 ExcMessage("The chart points array view must be as large as the "
2312 "surrounding points array view."));
2313
2314 std::array<unsigned int, 20> nearby_cells =
2315 get_possible_cells_around_points(surrounding_points);
2316
2317 // This function is nearly always called to place new points on a cell or
2318 // cell face. In this case, the general structure of the surrounding points
2319 // is known (i.e., if there are eight surrounding points, then they will
2320 // almost surely be either eight points around a quadrilateral or the eight
2321 // vertices of a cube). Hence, making this assumption, we use two
2322 // optimizations (one for structdim == 2 and one for structdim == 3) that
2323 // guess the locations of some of the chart points more efficiently than the
2324 // affine map approximation. The affine map approximation is used whenever
2325 // we don't have a cheaper guess available.
2326
2327 // Function that can guess the location of a chart point by assuming that
2328 // the eight surrounding points are points on a two-dimensional object
2329 // (either a cell in 2d or the face of a hexahedron in 3d), arranged like
2330 //
2331 // 2 - 7 - 3
2332 // | |
2333 // 4 5
2334 // | |
2335 // 0 - 6 - 1
2336 //
2337 // This function assumes that the first three chart points have been
2338 // computed since there is no effective way to guess them.
2339 auto guess_chart_point_structdim_2 = [&](const unsigned int i) -> Point<dim> {
2340 Assert(surrounding_points.size() == 8 && 2 < i && i < 8,
2341 ExcMessage("This function assumes that there are eight surrounding "
2342 "points around a two-dimensional object. It also assumes "
2343 "that the first three chart points have already been "
2344 "computed."));
2345 switch (i)
2346 {
2347 case 0:
2348 case 1:
2349 case 2:
2351 break;
2352 case 3:
2353 return chart_points[1] + (chart_points[2] - chart_points[0]);
2354 case 4:
2355 return 0.5 * (chart_points[0] + chart_points[2]);
2356 case 5:
2357 return 0.5 * (chart_points[1] + chart_points[3]);
2358 case 6:
2359 return 0.5 * (chart_points[0] + chart_points[1]);
2360 case 7:
2361 return 0.5 * (chart_points[2] + chart_points[3]);
2362 default:
2364 }
2365
2366 return {};
2367 };
2368
2369 // Function that can guess the location of a chart point by assuming that
2370 // the eight surrounding points form the vertices of a hexahedron, arranged
2371 // like
2372 //
2373 // 6-------7
2374 // /| /|
2375 // / / |
2376 // / | / |
2377 // 4-------5 |
2378 // | 2- -|- -3
2379 // | / | /
2380 // | | /
2381 // |/ |/
2382 // 0-------1
2383 //
2384 // (where vertex 2 is the back left vertex) we can estimate where chart
2385 // points 5 - 7 are by computing the height (in chart coordinates) as c4 -
2386 // c0 and then adding that onto the appropriate bottom vertex.
2387 //
2388 // This function assumes that the first five chart points have been computed
2389 // since there is no effective way to guess them.
2390 auto guess_chart_point_structdim_3 = [&](const unsigned int i) -> Point<dim> {
2391 Assert(surrounding_points.size() == 8 && 4 < i && i < 8,
2392 ExcMessage("This function assumes that there are eight surrounding "
2393 "points around a three-dimensional object. It also "
2394 "assumes that the first five chart points have already "
2395 "been computed."));
2396 return chart_points[i - 4] + (chart_points[4] - chart_points[0]);
2397 };
2398
2399 // Check if we can use the two chart point shortcuts above before we start:
2400 bool use_structdim_2_guesses = false;
2401 bool use_structdim_3_guesses = false;
2402 // note that in the structdim 2 case: 0 - 6 and 2 - 7 should be roughly
2403 // parallel, while in the structdim 3 case, 0 - 6 and 2 - 7 should be roughly
2404 // orthogonal. Use the angle between these two vectors to figure out if we
2405 // should turn on either structdim optimization.
2406 if (surrounding_points.size() == 8)
2407 {
2408 const Tensor<1, spacedim> v06 =
2409 surrounding_points[6] - surrounding_points[0];
2410 const Tensor<1, spacedim> v27 =
2411 surrounding_points[7] - surrounding_points[2];
2412
2413 // note that we can save a call to sqrt() by rearranging
2414 const double cosine = scalar_product(v06, v27) /
2415 std::sqrt(v06.norm_square() * v27.norm_square());
2416 if (0.707 < cosine)
2417 // the angle is less than pi/4, so these vectors are roughly parallel:
2418 // enable the structdim 2 optimization
2419 use_structdim_2_guesses = true;
2420 else if (spacedim == 3)
2421 // otherwise these vectors are roughly orthogonal: enable the
2422 // structdim 3 optimization if we are in 3d
2423 use_structdim_3_guesses = true;
2424 }
2425 // we should enable at most one of the optimizations
2426 Assert((!use_structdim_2_guesses && !use_structdim_3_guesses) ||
2427 (use_structdim_2_guesses ^ use_structdim_3_guesses),
2429
2430
2431
2432 auto compute_chart_point =
2433 [&](const typename Triangulation<dim, spacedim>::cell_iterator &cell,
2434 const unsigned int point_index) {
2435 Point<dim> guess;
2436 // an optimization: keep track of whether or not we used the quadratic
2437 // approximation so that we don't call pull_back with the same
2438 // initial guess twice (i.e., if pull_back fails the first time,
2439 // don't try again with the same function arguments).
2440 bool used_quadratic_approximation = false;
2441 // if we have already computed three points, we can guess the fourth
2442 // to be the missing corner point of a rectangle
2443 if (point_index == 3 && surrounding_points.size() >= 8)
2444 guess = chart_points[1] + (chart_points[2] - chart_points[0]);
2445 else if (use_structdim_2_guesses && 3 < point_index)
2446 guess = guess_chart_point_structdim_2(point_index);
2447 else if (use_structdim_3_guesses && 4 < point_index)
2448 guess = guess_chart_point_structdim_3(point_index);
2449 else if (dim == 3 && point_index > 7 && surrounding_points.size() == 26)
2450 {
2451 if (point_index < 20)
2452 guess =
2453 0.5 * (chart_points[GeometryInfo<dim>::line_to_cell_vertices(
2454 point_index - 8, 0)] +
2456 point_index - 8, 1)]);
2457 else
2458 guess =
2459 0.25 * (chart_points[GeometryInfo<dim>::face_to_cell_vertices(
2460 point_index - 20, 0)] +
2462 point_index - 20, 1)] +
2464 point_index - 20, 2)] +
2466 point_index - 20, 3)]);
2467 }
2468 else
2469 {
2470 guess = quadratic_approximation[cell->index()].compute(
2471 surrounding_points[point_index]);
2472 used_quadratic_approximation = true;
2473 }
2474 chart_points[point_index] =
2475 pull_back(cell, surrounding_points[point_index], guess);
2476
2477 // the initial guess may not have been good enough: if applicable,
2478 // try again with the affine approximation (which is more accurate
2479 // than the cheap methods used above)
2480 if (chart_points[point_index][0] ==
2482 !used_quadratic_approximation)
2483 {
2484 guess = quadratic_approximation[cell->index()].compute(
2485 surrounding_points[point_index]);
2486 chart_points[point_index] =
2487 pull_back(cell, surrounding_points[point_index], guess);
2488 }
2489
2490 if (chart_points[point_index][0] ==
2492 {
2493 for (unsigned int d = 0; d < dim; ++d)
2494 guess[d] = 0.5;
2495 chart_points[point_index] =
2496 pull_back(cell, surrounding_points[point_index], guess);
2497 }
2498 };
2499
2500 // check whether all points are inside the unit cell of the current chart
2501 for (unsigned int c = 0; c < nearby_cells.size(); ++c)
2502 {
2504 triangulation, level_coarse, nearby_cells[c]);
2505 bool inside_unit_cell = true;
2506 for (unsigned int i = 0; i < surrounding_points.size(); ++i)
2507 {
2508 compute_chart_point(cell, i);
2509
2510 // Tolerance 5e-4 chosen that the method also works with manifolds
2511 // that have some discretization error like SphericalManifold
2512 if (GeometryInfo<dim>::is_inside_unit_cell(chart_points[i], 5e-4) ==
2513 false)
2514 {
2515 inside_unit_cell = false;
2516 break;
2517 }
2518 }
2519 if (inside_unit_cell == true)
2520 {
2521 return cell;
2522 }
2523
2524 // if we did not find a point and this was the last valid cell (the next
2525 // iterate being the end of the array or an invalid tag), we must stop
2526 if (c == nearby_cells.size() - 1 ||
2527 nearby_cells[c + 1] == numbers::invalid_unsigned_int)
2528 {
2529 // generate additional information to help debugging why we did not
2530 // get a point
2531 std::ostringstream message;
2532 for (unsigned int b = 0; b <= c; ++b)
2533 {
2535 triangulation, level_coarse, nearby_cells[b]);
2536 message << "Looking at cell " << cell->id()
2537 << " with vertices: " << std::endl;
2538 for (const unsigned int v : GeometryInfo<dim>::vertex_indices())
2539 message << cell->vertex(v) << " ";
2540 message << std::endl;
2541 message << "Transformation to chart coordinates: " << std::endl;
2542 for (unsigned int i = 0; i < surrounding_points.size(); ++i)
2543 {
2544 compute_chart_point(cell, i);
2545 message << surrounding_points[i] << " -> " << chart_points[i]
2546 << std::endl;
2547 }
2548 }
2549
2550 AssertThrow(false,
2552 message.str())));
2553 }
2554 }
2555
2556 // a valid inversion should have returned a point above. an invalid
2557 // inversion should have triggered the assertion, so we should never end up
2558 // here
2561}
2562
2563
2564
2565template <int dim, int spacedim>
2568 const ArrayView<const Point<spacedim>> &surrounding_points,
2569 const ArrayView<const double> &weights) const
2570{
2571 boost::container::small_vector<Point<dim>, 100> chart_points(
2572 surrounding_points.size());
2573 ArrayView<Point<dim>> chart_points_view =
2574 make_array_view(chart_points.begin(), chart_points.end());
2575 const auto cell = compute_chart_points(surrounding_points, chart_points_view);
2576
2577 const Point<dim> p_chart =
2578 chart_manifold.get_new_point(chart_points_view, weights);
2579
2580 return push_forward(cell, p_chart);
2581}
2582
2583
2584
2585template <int dim, int spacedim>
2586void
2588 const ArrayView<const Point<spacedim>> &surrounding_points,
2589 const Table<2, double> &weights,
2590 ArrayView<Point<spacedim>> new_points) const
2591{
2592 Assert(weights.size(0) > 0, ExcEmptyObject());
2593 AssertDimension(surrounding_points.size(), weights.size(1));
2594
2595 boost::container::small_vector<Point<dim>, 100> chart_points(
2596 surrounding_points.size());
2597 ArrayView<Point<dim>> chart_points_view =
2598 make_array_view(chart_points.begin(), chart_points.end());
2599 const auto cell = compute_chart_points(surrounding_points, chart_points_view);
2600
2601 boost::container::small_vector<Point<dim>, 100> new_points_on_chart(
2602 weights.size(0));
2603 chart_manifold.get_new_points(chart_points_view,
2604 weights,
2605 make_array_view(new_points_on_chart.begin(),
2606 new_points_on_chart.end()));
2607
2608 for (unsigned int row = 0; row < weights.size(0); ++row)
2609 new_points[row] = push_forward(cell, new_points_on_chart[row]);
2610}
2611
2612
2613
2614// explicit instantiations
2615#include "grid/manifold_lib.inst"
2616
ArrayView< std::remove_reference_t< typename std::iterator_traits< Iterator >::reference >, MemorySpaceType > make_array_view(const Iterator begin, const Iterator end)
std::size_t size() const
Definition array_view.h:737
virtual Point< spacedim > get_new_point(const ArrayView< const Point< spacedim > > &surrounding_points, const ArrayView< const double > &weights) const override
Definition manifold.cc:1035
virtual Point< spacedim > push_forward(const Point< 3 > &chart_point) const override
virtual DerivativeForm< 1, 3, spacedim > push_forward_gradient(const Point< 3 > &chart_point) const override
virtual Point< spacedim > get_new_point(const ArrayView< const Point< spacedim > > &surrounding_points, const ArrayView< const double > &weights) const override
virtual std::unique_ptr< Manifold< dim, spacedim > > clone() const override
CylindricalManifold(const unsigned int axis=0, const double tolerance=1e-10)
virtual Point< 3 > pull_back(const Point< spacedim > &space_point) const override
Number determinant() const
DerivativeForm< 1, dim, spacedim, Number > covariant_form() const
virtual std::unique_ptr< Manifold< dim, spacedim > > clone() const override
virtual Point< spacedim > push_forward(const Point< spacedim > &chart_point) const override
EllipticalManifold(const Point< spacedim > &center, const Tensor< 1, spacedim > &major_axis_direction, const double eccentricity)
virtual DerivativeForm< 1, spacedim, spacedim > push_forward_gradient(const Point< spacedim > &chart_point) const override
virtual Point< spacedim > pull_back(const Point< spacedim > &space_point) const override
static Tensor< 1, spacedim > get_periodicity()
const double cosh_u
const FunctionParser< spacedim >::ConstMap const_map
const std::string chart_vars
const std::string pull_back_expression
virtual DerivativeForm< 1, chartdim, spacedim > push_forward_gradient(const Point< chartdim > &chart_point) const override
const std::string space_vars
virtual Point< spacedim > push_forward(const Point< chartdim > &chart_point) const override
FunctionManifold(const Function< chartdim > &push_forward_function, const Function< spacedim > &pull_back_function, const Tensor< 1, chartdim > &periodicity=Tensor< 1, chartdim >(), const double tolerance=1e-10)
virtual std::unique_ptr< Manifold< dim, spacedim > > clone() const override
const std::string push_forward_expression
virtual Point< chartdim > pull_back(const Point< spacedim > &space_point) const override
ObserverPointer< const Function< chartdim >, FunctionManifold< dim, spacedim, chartdim > > push_forward_function
ObserverPointer< const Function< spacedim >, FunctionManifold< dim, spacedim, chartdim > > pull_back_function
virtual ~FunctionManifold() override
virtual void initialize(const std::string &vars, const std::vector< std::string > &expressions, const ConstMap &constants, const bool time_dependent=false) override
std::map< std::string, double > ConstMap
virtual void get_normals_at_vertices(const typename Triangulation< dim, spacedim >::face_iterator &face, FaceVertexNormals &face_vertex_normals) const
std::array< Tensor< 1, spacedim >, GeometryInfo< dim >::vertices_per_face > FaceVertexNormals
Definition manifold.h:304
virtual Tensor< 1, spacedim > normal_vector(const typename Triangulation< dim, spacedim >::face_iterator &face, const Point< spacedim > &p) const
virtual Point< spacedim > get_new_point(const ArrayView< const Point< spacedim > > &surrounding_points, const ArrayView< const double > &weights) const
Abstract base class for mapping classes.
Definition mapping.h:318
Definition point.h:111
PolarManifold(const Point< spacedim > center=Point< spacedim >())
static Tensor< 1, spacedim > get_periodicity()
virtual std::unique_ptr< Manifold< dim, spacedim > > clone() const override
virtual DerivativeForm< 1, spacedim, spacedim > push_forward_gradient(const Point< spacedim > &chart_point) const override
virtual Tensor< 1, spacedim > normal_vector(const typename Triangulation< dim, spacedim >::face_iterator &face, const Point< spacedim > &p) const override
virtual Point< spacedim > push_forward(const Point< spacedim > &chart_point) const override
virtual Point< spacedim > pull_back(const Point< spacedim > &space_point) const override
virtual Point< spacedim > get_new_point(const ArrayView< const Point< spacedim > > &vertices, const ArrayView< const double > &weights) const override
virtual std::unique_ptr< Manifold< dim, spacedim > > clone() const override
virtual Point< spacedim > get_intermediate_point(const Point< spacedim > &p1, const Point< spacedim > &p2, const double w) const override
virtual void get_normals_at_vertices(const typename Triangulation< dim, spacedim >::face_iterator &face, typename Manifold< dim, spacedim >::FaceVertexNormals &face_vertex_normals) const override
std::pair< double, Tensor< 1, spacedim > > guess_new_point(const ArrayView< const Tensor< 1, spacedim > > &directions, const ArrayView< const double > &distances, const ArrayView< const double > &weights) const
virtual void get_new_points(const ArrayView< const Point< spacedim > > &surrounding_points, const Table< 2, double > &weights, ArrayView< Point< spacedim > > new_points) const override
SphericalManifold(const Point< spacedim > center=Point< spacedim >())
virtual Tensor< 1, spacedim > get_tangent_vector(const Point< spacedim > &x1, const Point< spacedim > &x2) const override
virtual Tensor< 1, spacedim > normal_vector(const typename Triangulation< dim, spacedim >::face_iterator &face, const Point< spacedim > &p) const override
void do_get_new_points(const ArrayView< const Point< spacedim > > &surrounding_points, const ArrayView< const double > &weights, ArrayView< Point< spacedim > > new_points) const
numbers::NumberTraits< Number >::real_type norm() const
constexpr numbers::NumberTraits< Number >::real_type norm_square() const
virtual Point< 3 > pull_back(const Point< 3 > &p) const override
double inner_radius
virtual DerivativeForm< 1, 3, 3 > push_forward_gradient(const Point< 3 > &chart_point) const override
virtual Point< 3 > push_forward(const Point< 3 > &chart_point) const override
double centerline_radius
TorusManifold(const double centerline_radius, const double inner_radius)
virtual std::unique_ptr< Manifold< dim, 3 > > clone() const override
DerivativeForm< 1, dim, spacedim > push_forward_gradient(const typename Triangulation< dim, spacedim >::cell_iterator &cell, const Point< dim > &chart_point, const Point< spacedim > &pushed_forward_chart_point) const
void initialize(const Triangulation< dim, spacedim > &triangulation)
Triangulation< dim, spacedim >::cell_iterator compute_chart_points(const ArrayView< const Point< spacedim > > &surrounding_points, ArrayView< Point< dim > > chart_points) const
std::array< unsigned int, 20 > get_possible_cells_around_points(const ArrayView< const Point< spacedim > > &surrounding_points) const
Point< dim > pull_back(const typename Triangulation< dim, spacedim >::cell_iterator &cell, const Point< spacedim > &p, const Point< dim > &initial_guess) const
virtual void get_new_points(const ArrayView< const Point< spacedim > > &surrounding_points, const Table< 2, double > &weights, ArrayView< Point< spacedim > > new_points) const override
virtual Point< spacedim > get_new_point(const ArrayView< const Point< spacedim > > &surrounding_points, const ArrayView< const double > &weights) const override
Point< spacedim > push_forward(const typename Triangulation< dim, spacedim >::cell_iterator &cell, const Point< dim > &chart_point) const
virtual std::unique_ptr< Manifold< dim, spacedim > > clone() const override
cell_iterator last() const
unsigned int n_cells() const
Triangulation< dim, spacedim > & get_triangulation()
Signals signals
Definition tria.h:2588
#define DEAL_II_NAMESPACE_OPEN
Definition config.h:38
#define DEAL_II_DISABLE_EXTRA_DIAGNOSTICS
Definition config.h:636
constexpr bool running_in_debug_mode()
Definition config.h:76
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:39
#define DEAL_II_ENABLE_EXTRA_DIAGNOSTICS
Definition config.h:680
#define DEAL_II_ASSERT_UNREACHABLE()
#define DEAL_II_NOT_IMPLEMENTED()
Point< 2 > second
Definition grid_out.cc:4640
Point< 2 > first
Definition grid_out.cc:4639
unsigned int vertex_indices[2]
const unsigned int v1
IteratorRange< active_cell_iterator > active_cell_iterators() const
static ::ExceptionBase & ExcNotImplemented()
static ::ExceptionBase & ExcEmptyObject()
#define Assert(cond, exc)
static ::ExceptionBase & ExcImpossibleInDim(int arg1)
#define AssertDimension(dim1, dim2)
#define AssertIndexRange(index, range)
static ::ExceptionBase & ExcInternalError()
static ::ExceptionBase & ExcNotInitialized()
static ::ExceptionBase & ExcMessage(std::string arg1)
#define AssertThrow(cond, exc)
TriaIterator< CellAccessor< dim, spacedim > > cell_iterator
Definition tria.h:1621
const Manifold< dim, spacedim > & get_manifold(const types::manifold_id number) const
SymmetricTensor< 2, dim, Number > d(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
Number signed_angle(const Tensor< 1, spacedim, Number > &a, const Tensor< 1, spacedim, Number > &b, const Tensor< 1, spacedim, Number > &axis)
Tensor< 1, 3 > projected_direction(const Tensor< 1, 3 > &u, const Tensor< 1, 3 > &v)
Tensor< 1, 3 > apply_exponential_map(const Tensor< 1, 3 > &u, const Tensor< 1, 3 > &dir)
static constexpr double invalid_pull_back_coordinate
Point< spacedim > compute_normal(const Tensor< 1, spacedim > &, bool=false)
constexpr double PI
Definition numbers.h:240
constexpr unsigned int invalid_unsigned_int
Definition types.h:228
constexpr types::manifold_id flat_manifold_id
Definition types.h:332
STL namespace.
::VectorizedArray< Number, width > max(const ::VectorizedArray< Number, width > &, const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > cos(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > sin(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > sqrt(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > abs(const ::VectorizedArray< Number, width > &)
inline ::VectorizedArray< Number, width > acos(const ::VectorizedArray< Number, width > &x)
static double distance_to_unit_cell(const Point< dim > &p)
static unsigned int face_to_cell_vertices(const unsigned int face, const unsigned int vertex, const bool face_orientation=true, const bool face_flip=false, const bool face_rotation=false)
static unsigned int line_to_cell_vertices(const unsigned int line, const unsigned int vertex)
static std_cxx20::ranges::iota_view< unsigned int, unsigned int > vertex_indices()
boost::signals2::signal< void()> clear
Definition tria.h:2449
constexpr Number determinant(const SymmetricTensor< 2, dim, Number > &)
constexpr SymmetricTensor< 2, dim, Number > invert(const SymmetricTensor< 2, dim, Number > &)