deal.II version GIT relicensing-6834-g5b78e6bcdf 2026-10-01 11:20: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
fe_q_base.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) 2013 - 2026 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
23
24#include <deal.II/fe/fe_dgp.h>
25#include <deal.II/fe/fe_dgq.h>
32#include <deal.II/fe/fe_tools.h>
34
35#include <memory>
36#include <sstream>
37#include <vector>
38
40
41
42namespace internal
43{
44 namespace FE_Q_Base
45 {
46 namespace
47 {
48 // in get_restriction_matrix() and get_prolongation_matrix(), want to undo
49 // tensorization on inner loops for performance reasons. this clears a
50 // dim-array
51 template <int dim>
52 void
53 zero_indices(unsigned int (&indices)[dim])
54 {
55 for (unsigned int d = 0; d < dim; ++d)
56 indices[d] = 0;
57 }
58
59
60
61 // in get_restriction_matrix() and get_prolongation_matrix(), want to undo
62 // tensorization on inner loops for performance reasons. this increments
63 // tensor product indices
64 template <int dim>
65 void
66 increment_indices(unsigned int (&indices)[dim], const unsigned int dofs1d)
67 {
68 ++indices[0];
69 for (unsigned int d = 0; d < dim - 1; ++d)
70 if (indices[d] == dofs1d)
71 {
72 indices[d] = 0;
73 indices[d + 1]++;
74 }
75 }
76 } // namespace
77 } // namespace FE_Q_Base
78} // namespace internal
79
80
81
86template <int xdim, int xspacedim>
87struct FE_Q_Base<xdim, xspacedim>::Implementation
88{
93 template <int spacedim>
94 static void
95 initialize_constraints(const std::vector<Point<1>> &,
97 {
98 // no constraints in 1d
99 }
100
101
102 template <int spacedim>
103 static void
104 initialize_constraints(const std::vector<Point<1>> & /*points*/,
106 {
107 const unsigned int dim = 2;
108
109 unsigned int q_deg = fe.degree;
110 if (dynamic_cast<const TensorProductPolynomialsBubbles<dim> *>(
111 &fe.get_poly_space()) != nullptr)
112 q_deg = fe.degree - 1;
113
114 // restricted to each face, the traces of the shape functions is an
115 // element of P_{k} (in 2d), or Q_{k} (in 3d), where k is the degree of
116 // the element. from this, we interpolate between mother and cell face.
117
118 // the interpolation process works as follows: on each subface, we want
119 // that finite element solutions from both sides coincide. i.e. if a and b
120 // are expansion coefficients for the shape functions from both sides, we
121 // seek a relation between a and b such that
122 // sum_j a_j phi^c_j(x) == sum_j b_j phi_j(x)
123 // for all points x on the interface. here, phi^c_j are the shape
124 // functions on the small cell on one side of the face, and phi_j those on
125 // the big cell on the other side. To get this relation, it suffices to
126 // look at a sufficient number of points for which this has to hold. if
127 // there are n functions, then we need n evaluation points, and we choose
128 // them equidistantly.
129 //
130 // we obtain the matrix system
131 // A a == B b
132 // where
133 // A_ij = phi^c_j(x_i)
134 // B_ij = phi_j(x_i)
135 // and the relation we are looking for is
136 // a = A^-1 B b
137 //
138 // for the special case of Lagrange interpolation polynomials, A_ij
139 // reduces to delta_ij, and
140 // a_i = B_ij b_j
141 // Hence, interface_constraints(i,j)=B_ij.
142 //
143 // for the general case, where we don't have Lagrange interpolation
144 // polynomials, this is a little more complicated. Then we would evaluate
145 // at a number of points and invert the interpolation matrix A.
146 //
147 // Note that we build up these matrices for all subfaces at once, rather
148 // than considering them separately. the reason is that we finally will
149 // want to have them in this order inside deal.II
150
151 // In the following the points x_i are constructed in following order
152 // (n=degree-1)
153 // *----------*---------*
154 // 1..n 0 n+1..2n
155 // i.e. first the midpoint of the line, then the support points on subface
156 // 0 and on subface 1
157 std::vector<Point<dim - 1>> constraint_points;
158 // Add midpoint
159 constraint_points.emplace_back(0.5);
160
161 // subface 0
162 for (unsigned int i = 1; i < q_deg; ++i)
163 constraint_points.push_back(
165 Point<dim - 1>(i / double(q_deg)), 0));
166 // subface 1
167 for (unsigned int i = 1; i < q_deg; ++i)
168 constraint_points.push_back(
170 Point<dim - 1>(i / double(q_deg)), 1));
171
172 // Now construct relation between destination (child) and source (mother)
173 // dofs.
174
175 fe.interface_constraints.TableBase<2, double>::reinit(
177
178 // use that the element evaluates to 1 at index 0 and along the line at
179 // zero
180 const std::vector<unsigned int> &index_map_inverse =
182 const std::vector<unsigned int> face_index_map =
185 fe.poly_space->compute_value(index_map_inverse[0], Point<dim>()) -
186 1.) < 1e-14,
188
189 for (unsigned int i = 0; i < constraint_points.size(); ++i)
190 for (unsigned int j = 0; j < q_deg + 1; ++j)
191 {
192 Point<dim> p;
193 p[0] = constraint_points[i][0];
194 fe.interface_constraints(i, face_index_map[j]) =
195 fe.poly_space->compute_value(index_map_inverse[j], p);
196
197 // if the value is small up to round-off, then simply set it to zero
198 // to avoid unwanted fill-in of the constraint matrices (which would
199 // then increase the number of other DoFs a constrained DoF would
200 // couple to)
201 if (std::fabs(fe.interface_constraints(i, face_index_map[j])) < 1e-13)
202 fe.interface_constraints(i, face_index_map[j]) = 0;
203 }
204 }
205
206
207 template <int spacedim>
208 static void
209 initialize_constraints(const std::vector<Point<1>> & /*points*/,
211 {
212 const unsigned int dim = 3;
213
214 unsigned int q_deg = fe.degree;
215 if (dynamic_cast<const TensorProductPolynomialsBubbles<dim> *>(
216 &fe.get_poly_space()) != nullptr)
217 q_deg = fe.degree - 1;
218
219 // For a detailed documentation of the interpolation see the
220 // FE_Q_Base<2>::initialize_constraints function.
221
222 // In the following the points x_i are constructed in the order as
223 // described in the documentation of the FiniteElement class (fe_base.h),
224 // i.e.
225 // *--15--4--16--*
226 // | | |
227 // 10 19 6 20 12
228 // | | |
229 // 1--7---0--8---2
230 // | | |
231 // 9 17 5 18 11
232 // | | |
233 // *--13--3--14--*
234 std::vector<Point<dim - 1>> constraint_points;
235
236 // Add midpoint
237 constraint_points.emplace_back(0.5, 0.5);
238
239 // Add midpoints of lines of "mother-face"
240 constraint_points.emplace_back(0, 0.5);
241 constraint_points.emplace_back(1, 0.5);
242 constraint_points.emplace_back(0.5, 0);
243 constraint_points.emplace_back(0.5, 1);
244
245 if (q_deg > 1)
246 {
247 const unsigned int n = q_deg - 1;
248 const double step = 1. / q_deg;
249 std::vector<Point<1>> line_support_points(n);
250 for (unsigned int i = 0; i < n; ++i)
251 line_support_points[i][0] = (i + 1) * step;
252 const Quadrature<1> qline(line_support_points);
253
254 // Add nodes of lines interior in the "mother-face"
255 auto get_points = [&](const unsigned int face_no,
256 const unsigned int subface_no) {
258 ReferenceCells::get_hypercube<2>(),
259 qline,
260 face_no,
261 subface_no,
264 .get_points();
265 };
266
267 // line 5: use line 9
268 for (const Point<dim - 1> &p : get_points(0, 0))
269 constraint_points.push_back(p + Point<dim - 1>(0.5, 0));
270 // line 6: use line 10
271 for (const Point<dim - 1> &p : get_points(0, 1))
272 constraint_points.push_back(p + Point<dim - 1>(0.5, 0));
273 // line 7: use line 13
274 for (const Point<dim - 1> &p : get_points(2, 0))
275 constraint_points.push_back(p + Point<dim - 1>(0, 0.5));
276 // line 8: use line 14
277 for (const Point<dim - 1> &p : get_points(2, 1))
278 constraint_points.push_back(p + Point<dim - 1>(0, 0.5));
279
280 // DoFs on bordering lines lines 9-16
281 for (unsigned int face = 0;
282 face < GeometryInfo<dim - 1>::faces_per_cell;
283 ++face)
284 for (unsigned int subface = 0;
285 subface < GeometryInfo<dim - 1>::max_children_per_face;
286 ++subface)
287 {
288 const auto p_line = get_points(face, subface);
289 constraint_points.insert(constraint_points.end(),
290 p_line.begin(),
291 p_line.end());
292 }
293
294 // Create constraints for interior nodes
295 std::vector<Point<dim - 1>> inner_points(n * n);
296 for (unsigned int i = 0, iy = 1; iy <= n; ++iy)
297 for (unsigned int ix = 1; ix <= n; ++ix)
298 inner_points[i++] = Point<dim - 1>(ix * step, iy * step);
299
300 // at the moment do this for isotropic face refinement only
301 for (unsigned int child = 0;
302 child < GeometryInfo<dim - 1>::max_children_per_cell;
303 ++child)
304 for (const auto &inner_point : inner_points)
305 constraint_points.push_back(
307 child));
308 }
309
310 // Now construct relation between destination (child) and source (mother)
311 // dofs.
312 const unsigned int pnts = (q_deg + 1) * (q_deg + 1);
313
314 // use that the element evaluates to 1 at index 0 and along the line at
315 // zero
316 const std::vector<unsigned int> &index_map_inverse =
318 const std::vector<unsigned int> face_index_map =
321 fe.poly_space->compute_value(index_map_inverse[0], Point<dim>()) -
322 1.) < 1e-14,
324
325 fe.interface_constraints.TableBase<2, double>::reinit(
327
328 for (unsigned int i = 0; i < constraint_points.size(); ++i)
329 {
330 const double interval = static_cast<double>(q_deg * 2);
331 bool mirror[dim - 1];
332 Point<dim> constraint_point;
333
334 // Eliminate FP errors in constraint points. Due to their origin, they
335 // must all be fractions of the unit interval. If we have polynomial
336 // degree 4, the refined element has 8 intervals. Hence the
337 // coordinates must be 0, 0.125, 0.25, 0.375 etc. Now the coordinates
338 // of the constraint points will be multiplied by the inverse of the
339 // interval size (in the example by 8). After that the coordinates
340 // must be integral numbers. Hence a normal truncation is performed
341 // and the coordinates will be scaled back. The equal treatment of all
342 // coordinates should eliminate any FP errors.
343 for (unsigned int k = 0; k < dim - 1; ++k)
344 {
345 const int coord_int =
346 static_cast<int>(constraint_points[i][k] * interval + 0.25);
347 constraint_point[k] = 1. * coord_int / interval;
348
349 // The following lines of code should eliminate the problems with
350 // the constraints object which appeared for P>=4. The
351 // AffineConstraints class complained about different constraints
352 // for the same entry: Actually, this
353 // difference could be attributed to FP errors, as it was in the
354 // range of 1.0e-16. These errors originate in the loss of
355 // symmetry in the FP approximation of the shape-functions.
356 // Considering a 3rd order shape function in 1d, we have
357 // N0(x)=N3(1-x) and N1(x)=N2(1-x). For higher order polynomials
358 // the FP approximations of the shape functions do not satisfy
359 // these equations any more! Thus in the following code
360 // everything is computed in the interval x \in [0..0.5], which is
361 // sufficient to express all values that could come out from a
362 // computation of any shape function in the full interval
363 // [0..1]. If x > 0.5 the computation is done for 1-x with the
364 // shape function N_{p-n} instead of N_n. Hence symmetry is
365 // preserved and everything works fine...
366 //
367 // For a different explanation of the problem, see the discussion
368 // in the FiniteElement class for constraint matrices in 3d.
369 mirror[k] = (constraint_point[k] > 0.5);
370 if (mirror[k])
371 constraint_point[k] = 1.0 - constraint_point[k];
372 }
373
374 for (unsigned int j = 0; j < pnts; ++j)
375 {
376 unsigned int indices[2] = {j % (q_deg + 1), j / (q_deg + 1)};
377
378 for (unsigned int k = 0; k < 2; ++k)
379 if (mirror[k])
380 indices[k] = q_deg - indices[k];
381
382 const unsigned int new_index =
383 indices[1] * (q_deg + 1) + indices[0];
384
385 fe.interface_constraints(i, face_index_map[j]) =
386 fe.poly_space->compute_value(index_map_inverse[new_index],
387 constraint_point);
388
389 // if the value is small up to round-off, then simply set it to
390 // zero to avoid unwanted fill-in of the constraint matrices
391 // (which would then increase the number of other DoFs a
392 // constrained DoF would couple to)
393 if (std::fabs(fe.interface_constraints(i, face_index_map[j])) <
394 1e-13)
395 fe.interface_constraints(i, face_index_map[j]) = 0;
396 }
397 }
398 }
399};
400
401#ifndef DOXYGEN
402
403template <int dim, int spacedim>
405 const ScalarPolynomialsBase<dim> &poly_space,
406 const FiniteElementData<dim> &fe_data,
407 const std::vector<bool> &restriction_is_additive_flags)
408 : FE_Poly<dim, spacedim>(
409 poly_space,
410 fe_data,
411 restriction_is_additive_flags,
412 std::vector<ComponentMask>(1, ComponentMask(std::vector<bool>(1, true))))
413 , q_degree(dynamic_cast<const TensorProductPolynomialsBubbles<dim> *>(
414 &poly_space) != nullptr ?
415 this->degree - 1 :
416 this->degree)
417{}
418
419
420
421template <int dim, int spacedim>
422void
423FE_Q_Base<dim, spacedim>::initialize(const std::vector<Point<1>> &points)
424{
425 Assert(points[0][0] == 0,
426 ExcMessage("The first support point has to be zero."));
427 Assert(points.back()[0] == 1,
428 ExcMessage("The last support point has to be one."));
429
430 // distinguish q/q_dg0 case: need to be flexible enough to allow more
431 // degrees of freedom than there are FE_Q degrees of freedom for derived
432 // class FE_Q_DG0 that otherwise shares 95% of the code.
433 const unsigned int q_dofs_per_cell =
434 Utilities::fixed_power<dim>(q_degree + 1);
435 Assert(q_dofs_per_cell == this->n_dofs_per_cell() ||
436 q_dofs_per_cell + 1 == this->n_dofs_per_cell() ||
437 q_dofs_per_cell + dim == this->n_dofs_per_cell(),
439
440 [this, q_dofs_per_cell]() {
441 std::vector<unsigned int> renumber =
442 FETools::hierarchic_to_lexicographic_numbering<dim>(q_degree);
443 for (unsigned int i = q_dofs_per_cell; i < this->n_dofs_per_cell(); ++i)
444 renumber.push_back(i);
445 auto *tensor_poly_space_ptr =
446 dynamic_cast<TensorProductPolynomials<dim> *>(this->poly_space.get());
447 if (tensor_poly_space_ptr != nullptr)
448 {
449 tensor_poly_space_ptr->set_numbering(renumber);
450 return;
451 }
452 auto *tensor_piecewise_poly_space_ptr = dynamic_cast<
454 *>(this->poly_space.get());
455 if (tensor_piecewise_poly_space_ptr != nullptr)
456 {
457 tensor_piecewise_poly_space_ptr->set_numbering(renumber);
458 return;
459 }
460 auto *tensor_bubbles_poly_space_ptr =
462 this->poly_space.get());
463 if (tensor_bubbles_poly_space_ptr != nullptr)
464 {
465 tensor_bubbles_poly_space_ptr->set_numbering(renumber);
466 return;
467 }
468 auto *tensor_const_poly_space_ptr =
470 this->poly_space.get());
471 if (tensor_const_poly_space_ptr != nullptr)
472 {
473 tensor_const_poly_space_ptr->set_numbering(renumber);
474 return;
475 }
477 }();
478
479 // Finally fill in support points on cell and face and initialize
480 // constraints. All of this can happen in parallel
482 tasks += Threads::new_task([&]() { initialize_unit_support_points(points); });
483 tasks +=
485 tasks += Threads::new_task([&]() { initialize_constraints(points); });
486 tasks +=
488 tasks.join_all();
489
490 // do not initialize embedding and restriction here. these matrices are
491 // initialized on demand in get_restriction_matrix and
492 // get_prolongation_matrix
493}
494
495
496
497template <int dim, int spacedim>
498void
500 const FiniteElement<dim, spacedim> &x_source_fe,
501 FullMatrix<double> &interpolation_matrix) const
502{
503 // go through the list of elements we can interpolate from
504 if (const FE_Q_Base<dim, spacedim> *source_fe =
505 dynamic_cast<const FE_Q_Base<dim, spacedim> *>(&x_source_fe))
506 {
507 // ok, source is a Q element, so we will be able to do the work
508 Assert(interpolation_matrix.m() == this->n_dofs_per_cell(),
509 ExcDimensionMismatch(interpolation_matrix.m(),
510 this->n_dofs_per_cell()));
511 Assert(interpolation_matrix.n() == x_source_fe.n_dofs_per_cell(),
512 ExcDimensionMismatch(interpolation_matrix.m(),
513 x_source_fe.n_dofs_per_cell()));
514
515 // only evaluate Q dofs
516 const unsigned int q_dofs_per_cell =
517 Utilities::fixed_power<dim>(q_degree + 1);
518 const unsigned int source_q_dofs_per_cell =
519 Utilities::fixed_power<dim>(source_fe->degree + 1);
520
521 // evaluation is simply done by evaluating the other FE's basis functions
522 // on the unit support points (FE_Q has the property that the cell
523 // interpolation matrix is a unit matrix, so no need to evaluate it and
524 // invert it)
525 for (unsigned int j = 0; j < q_dofs_per_cell; ++j)
526 {
527 // read in a point on this cell and evaluate the shape functions there
528 const Point<dim> p = this->unit_support_points[j];
529
530 // FE_Q element evaluates to 1 in unit support point and to zero in
531 // all other points by construction
532 Assert(std::abs(this->poly_space->compute_value(j, p) - 1.) < 1e-13,
534
535 for (unsigned int i = 0; i < source_q_dofs_per_cell; ++i)
536 interpolation_matrix(j, i) =
537 source_fe->poly_space->compute_value(i, p);
538 }
539
540 // for FE_Q_DG0, add one last row of identity
541 if (q_dofs_per_cell < this->n_dofs_per_cell())
542 {
543 AssertDimension(source_q_dofs_per_cell + 1,
544 source_fe->n_dofs_per_cell());
545 for (unsigned int i = 0; i < source_q_dofs_per_cell; ++i)
546 interpolation_matrix(q_dofs_per_cell, i) = 0.;
547 for (unsigned int j = 0; j < q_dofs_per_cell; ++j)
548 interpolation_matrix(j, source_q_dofs_per_cell) = 0.;
549 interpolation_matrix(q_dofs_per_cell, source_q_dofs_per_cell) = 1.;
550 }
551
552 // cut off very small values
553 const double eps = 2e-13 * q_degree * dim;
554 for (unsigned int i = 0; i < this->n_dofs_per_cell(); ++i)
555 for (unsigned int j = 0; j < source_fe->n_dofs_per_cell(); ++j)
556 if (std::fabs(interpolation_matrix(i, j)) < eps)
557 interpolation_matrix(i, j) = 0.;
558
559 if constexpr (running_in_debug_mode())
560 {
561 // make sure that the row sum of each of the matrices is 1 at this
562 // point. this must be so since the shape functions sum up to 1
563 for (unsigned int i = 0; i < this->n_dofs_per_cell(); ++i)
564 {
565 double sum = 0.;
566 for (unsigned int j = 0; j < source_fe->n_dofs_per_cell(); ++j)
567 sum += interpolation_matrix(i, j);
568
569 Assert(std::fabs(sum - 1) < eps, ExcInternalError());
570 }
571 }
572 }
573 else if (dynamic_cast<const FE_Nothing<dim> *>(&x_source_fe))
574 {
575 // the element we want to interpolate from is an FE_Nothing. this
576 // element represents a function that is constant zero and has no
577 // degrees of freedom, so the interpolation is simply a multiplication
578 // with a n_dofs x 0 matrix. there is nothing to do here
579
580 // we would like to verify that the number of rows and columns of
581 // the matrix equals this->n_dofs_per_cell() and zero. unfortunately,
582 // whenever we do FullMatrix::reinit(m,0), it sets both rows and
583 // columns to zero, instead of m and zero. thus, only test the
584 // number of columns
585 Assert(interpolation_matrix.n() == x_source_fe.n_dofs_per_cell(),
586 ExcDimensionMismatch(interpolation_matrix.m(),
587 x_source_fe.n_dofs_per_cell()));
588 }
589 else
591 false,
592 (typename FiniteElement<dim,
594}
595
596
597
598template <int dim, int spacedim>
599void
601 const FiniteElement<dim, spacedim> &source_fe,
602 FullMatrix<double> &interpolation_matrix,
603 const unsigned int face_no) const
604{
607 interpolation_matrix,
608 face_no);
609}
610
611
612
613template <int dim, int spacedim>
614void
616 const FiniteElement<dim, spacedim> &source_fe,
617 const unsigned int subface,
618 FullMatrix<double> &interpolation_matrix,
619 const unsigned int face_no) const
620{
621 Assert(interpolation_matrix.m() == source_fe.n_dofs_per_face(face_no),
622 ExcDimensionMismatch(interpolation_matrix.m(),
623 source_fe.n_dofs_per_face(face_no)));
624
625 Assert(source_fe.n_components() == this->n_components(),
626 ExcDimensionMismatch(source_fe.n_components(), this->n_components()));
627
628 if ((source_fe.has_face_support_points(face_no)) &&
629 (source_fe.n_dofs_per_face(face_no) > 0))
630 {
631 // have this test in here since a table of size 2x0 reports its size as
632 // 0x0
633 Assert(interpolation_matrix.n() == this->n_dofs_per_face(face_no),
634 ExcDimensionMismatch(interpolation_matrix.n(),
635 this->n_dofs_per_face(face_no)));
636
637 // Make sure that the element for which the DoFs should be constrained
638 // is the one with the higher polynomial degree. Actually the procedure
639 // will work also if this assertion is not satisfied. But the matrices
640 // produced in that case might lead to problems in the hp-procedures,
641 // which use this method.
642 Assert(
643 this->n_dofs_per_face(face_no) <= source_fe.n_dofs_per_face(face_no),
644 (typename FiniteElement<dim,
646
647 // generate a point on this cell and evaluate the shape functions there
648 const Quadrature<dim - 1> quad_face_support(
649 source_fe.get_unit_face_support_points(face_no));
650
651 // Rule of thumb for FP accuracy, that can be expected for a given
652 // polynomial degree. This value is used to cut off values close to
653 // zero.
654 const double eps = 2e-13 * this->q_degree * std::max(dim - 1, 1);
655
656 // compute the interpolation matrix by simply taking the value at the
657 // support points.
658 // TODO: Verify that all faces are the same with respect to
659 // these support points. Furthermore, check if something has to
660 // be done for the face orientation flag in 3d.
661 const Quadrature<dim> subface_quadrature =
664 this->reference_cell(),
665 quad_face_support,
666 0,
668 QProjector<dim>::project_to_subface(
669 this->reference_cell(),
670 quad_face_support,
671 0,
672 subface,
675 for (unsigned int i = 0; i < source_fe.n_dofs_per_face(face_no); ++i)
676 {
677 const Point<dim> &p = subface_quadrature.point(i);
678
679 for (unsigned int j = 0; j < this->n_dofs_per_face(face_no); ++j)
680 {
681 double matrix_entry =
682 this->shape_value(this->face_to_cell_index(j, 0), p);
683
684 // Correct the interpolated value. I.e. if it is close to 1 or
685 // 0, make it exactly 1 or 0. Unfortunately, this is required to
686 // avoid problems with higher order elements.
687 if (std::fabs(matrix_entry - 1.0) < eps)
688 matrix_entry = 1.0;
689 if (std::fabs(matrix_entry) < eps)
690 matrix_entry = 0.0;
691
692 interpolation_matrix(i, j) = matrix_entry;
693 }
694 }
695
696 if constexpr (running_in_debug_mode())
697 {
698 // make sure that the row sum of each of the matrices is 1 at this
699 // point. this must be so since the shape functions sum up to 1
700 for (unsigned int j = 0; j < source_fe.n_dofs_per_face(face_no); ++j)
701 {
702 double sum = 0.;
703
704 for (unsigned int i = 0; i < this->n_dofs_per_face(face_no); ++i)
705 sum += interpolation_matrix(j, i);
706
707 Assert(std::fabs(sum - 1) < eps, ExcInternalError());
708 }
709 }
710 }
711 else if (dynamic_cast<const FE_Nothing<dim> *>(&source_fe) != nullptr)
712 {
713 // nothing to do here, the FE_Nothing has no degrees of freedom anyway
714 }
715 else
717 false,
718 (typename FiniteElement<dim,
720}
721
722
723
724template <int dim, int spacedim>
725bool
727{
728 return true;
729}
730
731
732
733template <int dim, int spacedim>
734std::vector<std::pair<unsigned int, unsigned int>>
736 const FiniteElement<dim, spacedim> &fe_other) const
737{
738 if (dynamic_cast<const FE_Q_Base<dim, spacedim> *>(&fe_other) != nullptr)
739 {
740 // there should be exactly one single DoF of each FE at a vertex, and they
741 // should have identical value
742 return {{0U, 0U}};
743 }
744 else if ((dynamic_cast<const FE_SimplexP<dim, spacedim> *>(&fe_other) !=
745 nullptr) ||
746 (dynamic_cast<const FE_PyramidP<dim, spacedim> *>(&fe_other) !=
747 nullptr) ||
748 (dynamic_cast<const FE_WedgeP<dim, spacedim> *>(&fe_other) !=
749 nullptr))
750 {
751 // there should be exactly one single DoF of each FE at a vertex, and they
752 // should have identical value
753 return {{0U, 0U}};
754 }
755 else if (dynamic_cast<const FE_Hermite<dim, spacedim> *>(&fe_other) !=
756 nullptr)
757 {
758 // FE_Hermite will usually have several degrees of freedom on
759 // each vertex, however only the first one will actually
760 // correspond to the shape value at the vertex, meaning it's
761 // the only one of interest for FE_Q_Base
762 return {{0U, 0U}};
763 }
764 else if (dynamic_cast<const FE_Nothing<dim> *>(&fe_other) != nullptr)
765 {
766 // the FE_Nothing has no degrees of freedom, so there are no
767 // equivalencies to be recorded
768 return {};
769 }
770 else if (fe_other.n_unique_faces() == 1 && fe_other.n_dofs_per_face(0) == 0)
771 {
772 // if the other element has no elements on faces at all,
773 // then it would be impossible to enforce any kind of
774 // continuity even if we knew exactly what kind of element
775 // we have -- simply because the other element declares
776 // that it is discontinuous because it has no DoFs on
777 // its faces. in that case, just state that we have no
778 // constraints to declare
779 return {};
780 }
781 else
782 {
784 return {};
785 }
786}
787
788
789
790template <int dim, int spacedim>
791std::vector<std::pair<unsigned int, unsigned int>>
793 const FiniteElement<dim, spacedim> &fe_other) const
794{
795 // we can presently only compute these identities if both FEs are FE_Qs or
796 // if the other one is an FE_Nothing
797 if (const FE_Q_Base<dim, spacedim> *fe_q_other =
798 dynamic_cast<const FE_Q_Base<dim, spacedim> *>(&fe_other))
799 {
800 // dofs are located along lines, so two dofs are identical if they are
801 // located at identical positions. if we had only equidistant points, we
802 // could simply check for similarity like (i+1)*q == (j+1)*p, but we
803 // might have other support points (e.g. Gauss-Lobatto
804 // points). Therefore, read the points in unit_support_points for the
805 // first coordinate direction. We take the lexicographic ordering of the
806 // points in the first direction (i.e., x-direction), which we access
807 // between index 1 and p-1 (index 0 and p are vertex dofs).
808 const unsigned int p = this->degree;
809 const unsigned int q = fe_q_other->degree;
810
811 std::vector<std::pair<unsigned int, unsigned int>> identities;
812
813 const std::vector<unsigned int> &index_map_inverse =
815 const std::vector<unsigned int> &index_map_inverse_other =
816 fe_q_other->get_poly_space_numbering_inverse();
817
818 for (unsigned int i = 0; i < p - 1; ++i)
819 for (unsigned int j = 0; j < q - 1; ++j)
820 if (std::fabs(
821 this->unit_support_points[index_map_inverse[i + 1]][0] -
822 fe_q_other->unit_support_points[index_map_inverse_other[j + 1]]
823 [0]) < 1e-14)
824 identities.emplace_back(i, j);
825
826 return identities;
827 }
828 else if ((dynamic_cast<const FE_SimplexP<dim, spacedim> *>(&fe_other)) ||
829 (dynamic_cast<const FE_PyramidP<dim, spacedim> *>(&fe_other)) ||
830 (dynamic_cast<const FE_WedgeP<dim, spacedim> *>(&fe_other)))
831 {
832 std::vector<std::pair<unsigned int, unsigned int>> identities;
833 // check if the support points are the same location on the line
834 // to avoid rescaling for pyramids use the support points on the faces
835 const auto &face_support_points = this->get_unit_face_support_points(0);
836 const auto &face_support_points_other =
838
839 // now just compare the DoFs on the line going from [0,0] to [1,0]
840 // for a triangular face that is the first line
841 // for a quad face that is the third line
842 // adjust the offsets accordingly
843 // face number 0 of the hex is a quad
844 // in 2d we need just to account for the vertices
845 const unsigned int offset =
846 dim == 2 ? this->reference_cell().face_reference_cell(0).n_vertices() :
847 this->reference_cell().face_reference_cell(0).n_vertices() +
848 2 * this->n_dofs_per_line();
849
850 const unsigned int offset_other =
851 fe_other.reference_cell().face_reference_cell(0).is_simplex() ?
852 fe_other.reference_cell().face_reference_cell(0).n_vertices() :
853 fe_other.reference_cell().face_reference_cell(0).n_vertices() +
854 2 * fe_other.n_dofs_per_line();
855
856 // now get the identities
857 for (unsigned int i = 0; i < this->degree - 1; ++i)
858 for (unsigned int j = 0; j < fe_other.degree - 1; ++j)
859 if (face_support_points[i + offset].distance(
860 face_support_points_other[j + offset_other]) < 1e-14)
861 identities.emplace_back(i, j);
862
863 return identities;
864 }
865 else if (dynamic_cast<const FE_Nothing<dim> *>(&fe_other) != nullptr)
866 {
867 // the FE_Nothing has no degrees of freedom, so there are no
868 // equivalencies to be recorded
869 return {};
870 }
871 else if (fe_other.n_unique_faces() == 1 && fe_other.n_dofs_per_face(0) == 0)
872 {
873 // if the other element has no elements on faces at all,
874 // then it would be impossible to enforce any kind of
875 // continuity even if we knew exactly what kind of element
876 // we have -- simply because the other element declares
877 // that it is discontinuous because it has no DoFs on
878 // its faces. in that case, just state that we have no
879 // constraints to declare
880 return {};
881 }
882 else
883 {
885 return {};
886 }
887}
888
889
890
891template <int dim, int spacedim>
892std::vector<std::pair<unsigned int, unsigned int>>
894 const FiniteElement<dim, spacedim> &fe_other,
895 const unsigned int) const
896{
897 // we can presently only compute these identities if both FEs are FE_Qs or
898 // if the other one is an FE_Nothing
899 if (const FE_Q_Base<dim, spacedim> *fe_q_other =
900 dynamic_cast<const FE_Q_Base<dim, spacedim> *>(&fe_other))
901 {
902 // this works exactly like the line case above, except that now we have
903 // to have two indices i1, i2 and j1, j2 to characterize the dofs on the
904 // face of each of the finite elements. since they are ordered
905 // lexicographically along the first line and we have a tensor product,
906 // the rest is rather straightforward
907 const unsigned int p = this->degree;
908 const unsigned int q = fe_q_other->degree;
909
910 std::vector<std::pair<unsigned int, unsigned int>> identities;
911
912 const std::vector<unsigned int> &index_map_inverse =
914 const std::vector<unsigned int> &index_map_inverse_other =
915 fe_q_other->get_poly_space_numbering_inverse();
916
917 for (unsigned int i1 = 0; i1 < p - 1; ++i1)
918 for (unsigned int i2 = 0; i2 < p - 1; ++i2)
919 for (unsigned int j1 = 0; j1 < q - 1; ++j1)
920 for (unsigned int j2 = 0; j2 < q - 1; ++j2)
921 if ((std::fabs(
922 this->unit_support_points[index_map_inverse[i1 + 1]][0] -
923 fe_q_other
924 ->unit_support_points[index_map_inverse_other[j1 + 1]]
925 [0]) < 1e-14) &&
926 (std::fabs(
927 this->unit_support_points[index_map_inverse[i2 + 1]][0] -
928 fe_q_other
929 ->unit_support_points[index_map_inverse_other[j2 + 1]]
930 [0]) < 1e-14))
931 identities.emplace_back(i1 * (p - 1) + i2, j1 * (q - 1) + j2);
932
933 return identities;
934 }
935 else if ((dynamic_cast<const FE_PyramidP<dim> *>(&fe_other) != nullptr) ||
936 (dynamic_cast<const FE_WedgeP<dim> *>(&fe_other) != nullptr))
937 {
938 const unsigned int face_no_neighbor =
939 (dynamic_cast<const FE_PyramidP<dim> *>(&fe_other) != nullptr) ? 0 : 2;
940
941 std::vector<std::pair<unsigned int, unsigned int>> identities;
942
943 // compare the face support points
944 const auto &face_support_points = this->get_unit_face_support_points(0);
945 const auto &face_support_points_other =
946 fe_other.get_unit_face_support_points(face_no_neighbor);
947
948 // get the offsets to skip vertices and lines
949 const auto face_reference_cell =
950 this->reference_cell().face_reference_cell(0);
951 Assert(face_reference_cell ==
952 fe_other.reference_cell().face_reference_cell(face_no_neighbor),
954
955 const auto offset =
956 face_reference_cell.n_vertices() +
957 face_reference_cell.n_lines() * this->n_dofs_per_line();
958
959 const auto offset_other =
960 face_reference_cell.n_vertices() +
961 face_reference_cell.n_lines() * fe_other.n_dofs_per_line();
962
963 // now compare the points
964 for (unsigned int i = 0; i < this->n_dofs_per_quad(0); ++i)
965 for (unsigned int j = 0; j < fe_other.n_dofs_per_quad(face_no_neighbor);
966 ++j)
967 if (face_support_points[i + offset].distance(
968 face_support_points_other[j + offset_other]) < 1e-14)
969 identities.emplace_back(i, j);
970 return identities;
971 }
972 else if (dynamic_cast<const FE_Nothing<dim> *>(&fe_other) != nullptr)
973 {
974 // the FE_Nothing has no degrees of freedom, so there are no
975 // equivalencies to be recorded
976 return std::vector<std::pair<unsigned int, unsigned int>>();
977 }
978 else if (fe_other.n_unique_faces() == 1 && fe_other.n_dofs_per_face(0) == 0)
979 {
980 // if the other element has no elements on faces at all,
981 // then it would be impossible to enforce any kind of
982 // continuity even if we knew exactly what kind of element
983 // we have -- simply because the other element declares
984 // that it is discontinuous because it has no DoFs on
985 // its faces. in that case, just state that we have no
986 // constraints to declare
987 return std::vector<std::pair<unsigned int, unsigned int>>();
988 }
989 else
990 {
992 return std::vector<std::pair<unsigned int, unsigned int>>();
993 }
994}
995
996
997
998//---------------------------------------------------------------------------
999// Auxiliary functions
1000//---------------------------------------------------------------------------
1001
1002
1003
1004template <int dim, int spacedim>
1005void
1007 const std::vector<Point<1>> &points)
1008{
1009 const std::vector<unsigned int> &index_map_inverse =
1011
1012 // We can compute the support points by computing the tensor
1013 // product of the 1d set of points. We could do this by hand, but it's
1014 // easier to just re-use functionality that's already been implemented
1015 // for quadrature formulas.
1016 const Quadrature<1> support_1d(points);
1017 const Quadrature<dim> support_quadrature(support_1d); // NOLINT
1018
1019 // The only thing we have to do is reorder the points from tensor
1020 // product order to the order in which we enumerate DoFs on cells
1021 this->unit_support_points.resize(support_quadrature.size());
1022 for (unsigned int k = 0; k < support_quadrature.size(); ++k)
1023 this->unit_support_points[index_map_inverse[k]] =
1024 support_quadrature.point(k);
1025}
1026
1027
1028
1029template <int dim, int spacedim>
1030void
1032 const std::vector<Point<1>> &points)
1033{
1034 // TODO: the implementation makes the assumption that all faces have the
1035 // same number of dofs
1036 AssertDimension(this->n_unique_faces(), 1);
1037 const unsigned int face_no = 0;
1038
1039 this->unit_face_support_points[face_no].resize(
1040 Utilities::fixed_power<dim - 1>(q_degree + 1));
1041
1042 // In 1d, there is only one 0-dimensional support point, so there is nothing
1043 // more to be done.
1044 if (dim == 1)
1045 return;
1046
1047 // find renumbering of faces and assign from values of quadrature
1048 const std::vector<unsigned int> face_index_map =
1050
1051 // We can compute the support points by computing the tensor
1052 // product of the 1d set of points. We could do this by hand, but it's
1053 // easier to just re-use functionality that's already been implemented
1054 // for quadrature formulas.
1055 const Quadrature<1> support_1d(points);
1056 const Quadrature<dim - 1> support_quadrature(support_1d); // NOLINT
1057
1058 // The only thing we have to do is reorder the points from tensor
1059 // product order to the order in which we enumerate DoFs on cells
1060 this->unit_face_support_points[face_no].resize(support_quadrature.size());
1061 for (unsigned int k = 0; k < support_quadrature.size(); ++k)
1062 this->unit_face_support_points[face_no][face_index_map[k]] =
1063 support_quadrature.point(k);
1064}
1065
1066
1067
1068template <int dim, int spacedim>
1069void
1071{
1072 // initialize reordering of line dofs
1073 for (unsigned int i = 0; i < this->n_dofs_per_line(); ++i)
1075 this->n_dofs_per_line() - 1 - i - i;
1076
1077 // for 1d and 2d we can skip adjust_quad_dof_index_for_face_orientation_table
1078 if (dim < 3)
1079 return;
1080
1081 // TODO: the implementation makes the assumption that all faces have the
1082 // same number of dofs
1083 AssertDimension(this->n_unique_faces(), 1);
1084 const unsigned int face_no = 0;
1085
1086 Assert(
1088 this->reference_cell().n_face_orientations(face_no) *
1089 this->n_dofs_per_quad(face_no),
1091
1092 const unsigned int n = q_degree - 1;
1093 Assert(n * n == this->n_dofs_per_quad(face_no), ExcInternalError());
1094
1095 // the dofs on a face are connected to a n x n matrix. for example, for
1096 // degree==4 we have the following dofs on a quad
1097
1098 // ___________
1099 // | |
1100 // | 6 7 8 |
1101 // | |
1102 // | 3 4 5 |
1103 // | |
1104 // | 0 1 2 |
1105 // |___________|
1106 //
1107 // we have dof_no=i+n*j with index i in x-direction and index j in
1108 // y-direction running from 0 to n-1. to extract i and j we can use
1109 // i=dof_no%n and j=dof_no/n. i and j can then be used to construct the
1110 // rotated and mirrored numbers.
1111
1112
1113 for (unsigned int local = 0; local < this->n_dofs_per_quad(face_no); ++local)
1114 // face support points are in lexicographic ordering with x running
1115 // fastest. invert that (y running fastest)
1116 {
1117 unsigned int i = local % n, j = local / n;
1118
1119 // face_orientation=false, face_flip=false, face_rotation=false
1121 local, internal::combined_face_orientation(false, false, false)) =
1122 j + i * n - local;
1123 // face_orientation=false, face_flip=false, face_rotation=true
1125 local, internal::combined_face_orientation(false, true, false)) =
1126 i + (n - 1 - j) * n - local;
1127 // face_orientation=false, face_flip=true, face_rotation=false
1129 local, internal::combined_face_orientation(false, false, true)) =
1130 (n - 1 - j) + (n - 1 - i) * n - local;
1131 // face_orientation=false, face_flip=true, face_rotation=true
1133 local, internal::combined_face_orientation(false, true, true)) =
1134 (n - 1 - i) + j * n - local;
1135 // face_orientation=true, face_flip=false, face_rotation=false
1137 local, internal::combined_face_orientation(true, false, false)) = 0;
1138 // face_orientation=true, face_flip=false, face_rotation=true
1140 local, internal::combined_face_orientation(true, true, false)) =
1141 j + (n - 1 - i) * n - local;
1142 // face_orientation=true, face_flip=true, face_rotation=false
1144 local, internal::combined_face_orientation(true, false, true)) =
1145 (n - 1 - i) + (n - 1 - j) * n - local;
1146 // face_orientation=true, face_flip=true, face_rotation=true
1148 local, internal::combined_face_orientation(true, true, true)) =
1149 (n - 1 - j) + i * n - local;
1150 }
1151}
1152
1153
1154
1155template <int dim, int spacedim>
1156unsigned int
1158 const unsigned int face_index,
1159 const unsigned int face,
1160 const types::geometric_orientation combined_orientation) const
1161{
1162 return FETools::face_to_cell_index(*this,
1163 face_index,
1164 face,
1165 combined_orientation);
1166}
1167
1168
1169
1170template <int dim, int spacedim>
1171std::vector<unsigned int>
1173{
1174 using FEQ = FE_Q_Base<dim, spacedim>;
1175 AssertThrow(degree > 0, typename FEQ::ExcFEQCannotHaveDegree0());
1176 std::vector<unsigned int> dpo(dim + 1, 1U);
1177 for (unsigned int i = 1; i < dpo.size(); ++i)
1178 dpo[i] = dpo[i - 1] * (degree - 1);
1179 return dpo;
1180}
1181
1182
1183
1184template <int dim, int spacedim>
1185void
1187 const std::vector<Point<1>> &points)
1188{
1190}
1191
1192
1193
1194template <int dim, int spacedim>
1195const FullMatrix<double> &
1197 const unsigned int child,
1198 const RefinementCase<dim> &refinement_case) const
1199{
1200 AssertIndexRange(refinement_case,
1202 Assert(refinement_case != RefinementCase<dim>::no_refinement,
1203 ExcMessage(
1204 "Prolongation matrices are only available for refined cells!"));
1205 AssertIndexRange(child, this->reference_cell().n_children(refinement_case));
1206
1207 // initialization upon first request
1208 if (this->prolongation[refinement_case - 1][child].n() == 0)
1209 {
1210 std::scoped_lock lock(prolongation_matrix_mutex);
1211
1212 // if matrix got updated while waiting for the lock
1213 if (this->prolongation[refinement_case - 1][child].n() ==
1214 this->n_dofs_per_cell())
1215 return this->prolongation[refinement_case - 1][child];
1216
1217 // distinguish q/q_dg0 case: only treat Q dofs first
1218 const unsigned int q_dofs_per_cell =
1219 Utilities::fixed_power<dim>(q_degree + 1);
1220
1221 // compute the interpolation matrices in much the same way as we do for
1222 // the constraints. it's actually simpler here, since we don't have this
1223 // weird renumbering stuff going on. The trick is again that we the
1224 // interpolation matrix is formed by a permutation of the indices of the
1225 // cell matrix. The value eps is used a threshold to decide when certain
1226 // evaluations of the Lagrange polynomials are zero or one.
1227 const double eps = 1e-15 * q_degree * dim;
1228
1229 if constexpr (running_in_debug_mode())
1230 {
1231 // in DEBUG mode, check that the evaluation of support points in the
1232 // current numbering gives the identity operation
1233 for (unsigned int i = 0; i < q_dofs_per_cell; ++i)
1234 {
1235 Assert(std::fabs(1. - this->poly_space->compute_value(
1236 i, this->unit_support_points[i])) < eps,
1238 "The Lagrange polynomial does not evaluate "
1239 "to one or zero in a nodal point. "
1240 "This typically indicates that the "
1241 "polynomial interpolation is "
1242 "ill-conditioned such that round-off "
1243 "prevents the sum to be one."));
1244 for (unsigned int j = 0; j < q_dofs_per_cell; ++j)
1245 if (j != i)
1246 Assert(std::fabs(this->poly_space->compute_value(
1247 i, this->unit_support_points[j])) < eps,
1249 "The Lagrange polynomial does not evaluate "
1250 "to one or zero in a nodal point. "
1251 "This typically indicates that the "
1252 "polynomial interpolation is "
1253 "ill-conditioned such that round-off "
1254 "prevents the sum to be one."));
1255 }
1256 }
1257
1258 // to efficiently evaluate the polynomial at the subcell, make use of
1259 // the tensor product structure of this element and only evaluate 1d
1260 // information from the polynomial. This makes the cost of this function
1261 // almost negligible also for high order elements
1262 const unsigned int dofs1d = q_degree + 1;
1263 std::vector<Table<2, double>> subcell_evaluations(
1264 dim, Table<2, double>(dofs1d, dofs1d));
1265
1266 const std::vector<unsigned int> &index_map_inverse =
1268
1269 // helper value: step size how to walk through diagonal and how many
1270 // points we have left apart from the first dimension
1271 unsigned int step_size_diag = 0;
1272 {
1273 unsigned int factor = 1;
1274 for (unsigned int d = 0; d < dim; ++d)
1275 {
1276 step_size_diag += factor;
1277 factor *= dofs1d;
1278 }
1279 }
1280
1281 FullMatrix<double> prolongate(this->n_dofs_per_cell(),
1282 this->n_dofs_per_cell());
1283
1284 // go through the points in diagonal to capture variation in all
1285 // directions simultaneously
1286 for (unsigned int j = 0; j < dofs1d; ++j)
1287 {
1288 const unsigned int diag_comp = index_map_inverse[j * step_size_diag];
1289 const Point<dim> p_subcell = this->unit_support_points[diag_comp];
1290 const Point<dim> p_cell =
1292 child,
1293 refinement_case);
1294 for (unsigned int i = 0; i < dofs1d; ++i)
1295 for (unsigned int d = 0; d < dim; ++d)
1296 {
1297 // evaluate along line where only x is different from zero
1299 point[0] = p_cell[d];
1300 const double cell_value =
1301 this->poly_space->compute_value(index_map_inverse[i], point);
1302
1303 // cut off values that are too small. note that we have here
1304 // Lagrange interpolation functions, so they should be zero at
1305 // almost all points, and one at the others, at least on the
1306 // subcells. so set them to their exact values
1307 //
1308 // the actual cut-off value is somewhat fuzzy, but it works
1309 // for 2e-13*degree*dim (see above), which is kind of
1310 // reasonable given that we compute the values of the
1311 // polynomials via an degree-step recursion and then multiply
1312 // the 1d-values. this gives us a linear growth in degree*dim,
1313 // times a small constant.
1314 //
1315 // the embedding matrix is given by applying the inverse of
1316 // the subcell matrix on the cell_interpolation matrix. since
1317 // the subcell matrix is actually only a permutation vector,
1318 // all we need to do is to switch the rows we write the data
1319 // into. moreover, cut off very small values here
1320 if (std::fabs(cell_value) < eps)
1321 subcell_evaluations[d](j, i) = 0;
1322 else
1323 subcell_evaluations[d](j, i) = cell_value;
1324 }
1325 }
1326
1327 // now expand from 1d info. block innermost dimension (x_0) in order to
1328 // avoid difficult checks at innermost loop
1329 unsigned int j_indices[dim];
1330 internal::FE_Q_Base::zero_indices<dim>(j_indices);
1331 for (unsigned int j = 0; j < q_dofs_per_cell; j += dofs1d)
1332 {
1333 unsigned int i_indices[dim];
1334 internal::FE_Q_Base::zero_indices<dim>(i_indices);
1335 for (unsigned int i = 0; i < q_dofs_per_cell; i += dofs1d)
1336 {
1337 double val_extra_dim = 1.;
1338 for (unsigned int d = 1; d < dim; ++d)
1339 val_extra_dim *=
1340 subcell_evaluations[d](j_indices[d - 1], i_indices[d - 1]);
1341
1342 // innermost sum where we actually compute. the same as
1343 // prolongate(j,i) = this->poly_space->compute_value (i, p_cell)
1344 for (unsigned int jj = 0; jj < dofs1d; ++jj)
1345 {
1346 const unsigned int j_ind = index_map_inverse[j + jj];
1347 for (unsigned int ii = 0; ii < dofs1d; ++ii)
1348 prolongate(j_ind, index_map_inverse[i + ii]) =
1349 val_extra_dim * subcell_evaluations[0](jj, ii);
1350 }
1351
1352 // update indices that denote the tensor product position. a bit
1353 // fuzzy and therefore not done for innermost x_0 direction
1354 internal::FE_Q_Base::increment_indices<dim>(i_indices, dofs1d);
1355 }
1356 Assert(i_indices[dim - 1] == 1, ExcInternalError());
1357 internal::FE_Q_Base::increment_indices<dim>(j_indices, dofs1d);
1358 }
1359
1360 // the discontinuous node is simply mapped on the discontinuous node on
1361 // the child element
1362 if (q_dofs_per_cell < this->n_dofs_per_cell())
1363 prolongate(q_dofs_per_cell, q_dofs_per_cell) = 1.;
1364
1365 // and make sure that the row sum is 1. this must be so since for this
1366 // element, the shape functions add up to one
1367 if constexpr (running_in_debug_mode())
1368 {
1369 for (unsigned int row = 0; row < this->n_dofs_per_cell(); ++row)
1370 {
1371 double sum = 0;
1372 for (unsigned int col = 0; col < this->n_dofs_per_cell(); ++col)
1373 sum += prolongate(row, col);
1374 Assert(std::fabs(sum - 1.) <
1375 std::max(eps,
1376 5e-16 * std::sqrt(this->n_dofs_per_cell())),
1377 ExcInternalError("The entries in a row of the local "
1378 "prolongation matrix do not add to one. "
1379 "This typically indicates that the "
1380 "polynomial interpolation is "
1381 "ill-conditioned such that round-off "
1382 "prevents the sum to be one."));
1383 }
1384 }
1385
1386 // move result into place
1387 const_cast<FullMatrix<double> &>(
1388 this->prolongation[refinement_case - 1][child]) = std::move(prolongate);
1389 }
1390
1391 // finally return the matrix
1392 return this->prolongation[refinement_case - 1][child];
1393}
1394
1395
1396
1397template <int dim, int spacedim>
1398const FullMatrix<double> &
1400 const unsigned int child,
1401 const RefinementCase<dim> &refinement_case) const
1402{
1403 AssertIndexRange(refinement_case,
1405 Assert(refinement_case != RefinementCase<dim>::no_refinement,
1406 ExcMessage(
1407 "Restriction matrices are only available for refined cells!"));
1408 AssertIndexRange(child, this->reference_cell().n_children(refinement_case));
1409
1410 // initialization upon first request
1411 if (this->restriction[refinement_case - 1][child].n() == 0)
1412 {
1413 std::scoped_lock lock(restriction_matrix_mutex);
1414
1415 // if matrix got updated while waiting for the lock...
1416 if (this->restriction[refinement_case - 1][child].n() ==
1417 this->n_dofs_per_cell())
1418 return this->restriction[refinement_case - 1][child];
1419
1420 FullMatrix<double> my_restriction(this->n_dofs_per_cell(),
1421 this->n_dofs_per_cell());
1422 // distinguish q/q_dg0 case
1423 const unsigned int q_dofs_per_cell =
1424 Utilities::fixed_power<dim>(q_degree + 1);
1425
1426 // for Lagrange interpolation polynomials based on equidistant points,
1427 // construction of the restriction matrices is relatively simple. the
1428 // reason is that in this case the interpolation points on the mother
1429 // cell are always also interpolation points for some shape function on
1430 // one or the other child.
1431 //
1432 // in the general case with non-equidistant points, we need to actually
1433 // do an interpolation. thus, we take the interpolation points on the
1434 // mother cell and evaluate the shape functions of the child cell on
1435 // those points. it does not hurt in the equidistant case because then
1436 // simple one shape function evaluates to one and the others to zero.
1437 //
1438 // this element is non-additive in all its degrees of freedom by
1439 // default, which requires care in downstream use. fortunately, even the
1440 // interpolation on non-equidistant points is invariant under the
1441 // assumption that whenever a row makes a non-zero contribution to the
1442 // mother's residual, the correct value is interpolated.
1443
1444 const double eps = 1e-15 * q_degree * dim;
1445 const std::vector<unsigned int> &index_map_inverse =
1447
1448 const unsigned int dofs1d = q_degree + 1;
1449 std::vector<Tensor<1, dim>> evaluations1d(dofs1d);
1450
1451 my_restriction.reinit(this->n_dofs_per_cell(), this->n_dofs_per_cell());
1452
1453 for (unsigned int i = 0; i < q_dofs_per_cell; ++i)
1454 {
1455 unsigned int mother_dof = index_map_inverse[i];
1456 const Point<dim> p_cell = this->unit_support_points[mother_dof];
1457
1458 // check whether this interpolation point is inside this child cell
1459 const Point<dim> p_subcell =
1461 child,
1462 refinement_case);
1464 {
1465 // same logic as in initialize_embedding to evaluate the
1466 // polynomial faster than from the tensor product: since we
1467 // evaluate all polynomials, it is much faster to just compute
1468 // the 1d values for all polynomials before and then get the
1469 // dim-data.
1470 for (unsigned int j = 0; j < dofs1d; ++j)
1471 for (unsigned int d = 0; d < dim; ++d)
1472 {
1474 point[0] = p_subcell[d];
1475 evaluations1d[j][d] =
1476 this->poly_space->compute_value(index_map_inverse[j],
1477 point);
1478 }
1479 unsigned int j_indices[dim];
1480 internal::FE_Q_Base::zero_indices<dim>(j_indices);
1481 double sum_check = 0;
1482 for (unsigned int j = 0; j < q_dofs_per_cell; j += dofs1d)
1483 {
1484 double val_extra_dim = 1.;
1485 for (unsigned int d = 1; d < dim; ++d)
1486 val_extra_dim *= evaluations1d[j_indices[d - 1]][d];
1487 for (unsigned int jj = 0; jj < dofs1d; ++jj)
1488 {
1489 // find the child shape function(s) corresponding to
1490 // this point. Usually this is just one function;
1491 // however, when we use FE_Q on arbitrary nodes a parent
1492 // support point might not be a child support point, and
1493 // then we will get more than one nonzero value per
1494 // row. Still, the values should sum up to 1
1495 const double val = val_extra_dim * evaluations1d[jj][0];
1496 const unsigned int child_dof = index_map_inverse[j + jj];
1497 if (std::fabs(val - 1.) < eps)
1498 my_restriction(mother_dof, child_dof) = 1.;
1499 else if (std::fabs(val) > eps)
1500 my_restriction(mother_dof, child_dof) = val;
1501 sum_check += val;
1502 }
1503 internal::FE_Q_Base::increment_indices<dim>(j_indices,
1504 dofs1d);
1505 }
1506 (void)sum_check;
1507 Assert(std::fabs(sum_check - 1.0) <
1508 std::max(eps,
1509 5e-16 * std::sqrt(this->n_dofs_per_cell())),
1510 ExcInternalError("The entries in a row of the local "
1511 "restriction matrix do not add to one. "
1512 "This typically indicates that the "
1513 "polynomial interpolation is "
1514 "ill-conditioned such that round-off "
1515 "prevents the sum to be one."));
1516 }
1517
1518 // part for FE_Q_DG0
1519 if (q_dofs_per_cell < this->n_dofs_per_cell())
1520 my_restriction(this->n_dofs_per_cell() - 1,
1521 this->n_dofs_per_cell() - 1) =
1522 1. / this->reference_cell().n_children(
1523 RefinementCase<dim>(refinement_case));
1524 }
1525
1526 // move result into place
1527 const_cast<FullMatrix<double> &>(
1528 this->restriction[refinement_case - 1][child]) =
1529 std::move(my_restriction);
1530 }
1531
1532 return this->restriction[refinement_case - 1][child];
1533}
1534
1535
1536
1537//---------------------------------------------------------------------------
1538// Data field initialization
1539//---------------------------------------------------------------------------
1540
1541
1542template <int dim, int spacedim>
1543bool
1545 const unsigned int shape_index,
1546 const unsigned int face_index) const
1547{
1548 AssertIndexRange(shape_index, this->n_dofs_per_cell());
1550
1551 // in 1d, things are simple. since there is only one degree of freedom per
1552 // vertex in this class, the first is on vertex 0 (==face 0 in some sense),
1553 // the second on face 1:
1554 if (dim == 1)
1555 return (((shape_index == 0) && (face_index == 0)) ||
1556 ((shape_index == 1) && (face_index == 1)));
1557
1558 // first, special-case interior shape functions, since they have no support
1559 // no-where on the boundary
1560 if (((dim == 2) &&
1561 (shape_index >= this->get_first_quad_index(0 /*first quad*/))) ||
1562 ((dim == 3) && (shape_index >= this->get_first_hex_index())))
1563 return false;
1564
1565 // let's see whether this is a vertex
1566 if (shape_index < this->get_first_line_index())
1567 {
1568 // for Q elements, there is one dof per vertex, so
1569 // shape_index==vertex_number. check whether this vertex is on the given
1570 // face. thus, for each face, give a list of vertices
1571 const unsigned int vertex_no = shape_index;
1574
1575 for (unsigned int v = 0; v < GeometryInfo<dim>::vertices_per_face; ++v)
1576 if (GeometryInfo<dim>::face_to_cell_vertices(face_index, v) ==
1577 vertex_no)
1578 return true;
1579
1580 return false;
1581 }
1582 else if (shape_index < this->get_first_quad_index(0 /*first quad*/))
1583 // ok, dof is on a line
1584 {
1585 const unsigned int line_index =
1586 (shape_index - this->get_first_line_index()) / this->n_dofs_per_line();
1589
1590 // in 2d, the line is the face, so get the line index
1591 if constexpr (dim == 2)
1592 return (line_index == face_index);
1593 else if constexpr (dim == 3)
1594 {
1595 // see whether the given line is on the given face.
1596 for (unsigned int l = 0; l < GeometryInfo<3>::lines_per_face; ++l)
1597 if (GeometryInfo<3>::face_to_cell_lines(face_index, l) ==
1598 line_index)
1599 return true;
1600
1601 return false;
1602 }
1603 else
1605 }
1606 else if (shape_index < this->get_first_hex_index())
1607 // dof is on a quad
1608 {
1609 const unsigned int quad_index =
1610 (shape_index - this->get_first_quad_index(0)) /
1611 this->n_dofs_per_quad(face_index); // this won't work
1613
1614 // in 2d, cell bubble are zero on all faces. but we have treated this
1615 // case above already
1616 Assert(dim != 2, ExcInternalError());
1617
1618 // in 3d, quad_index=face_index
1619 if (dim == 3)
1620 return (quad_index == face_index);
1621 else
1623 }
1624 else
1625 // dof on hex
1626 {
1627 // can only happen in 3d, but this case has already been covered above
1629 return false;
1630 }
1631
1632 // we should not have gotten here
1634 return false;
1635}
1636
1637
1638
1639template <int dim, int spacedim>
1640std::pair<Table<2, bool>, std::vector<unsigned int>>
1642{
1643 Table<2, bool> constant_modes(1, this->n_dofs_per_cell());
1644 // We here just care for the constant mode due to the polynomial space
1645 // without any enrichments
1646 // As there may be more constant modes derived classes may to implement this
1647 // themselves. An example for this is FE_Q_DG0.
1648 for (unsigned int i = 0; i < Utilities::fixed_power<dim>(q_degree + 1); ++i)
1649 constant_modes(0, i) = true;
1650 return std::pair<Table<2, bool>, std::vector<unsigned int>>(
1651 constant_modes, std::vector<unsigned int>(1, 0));
1652}
1653
1654#endif
1655
1656// explicit instantiations
1657#include "fe/fe_q_base.inst"
1658
std::vector< unsigned int > get_poly_space_numbering_inverse() const
const ScalarPolynomialsBase< dim > & get_poly_space() const
virtual double shape_value(const unsigned int i, const Point< dim > &p) const override
const std::unique_ptr< ScalarPolynomialsBase< dim > > poly_space
Definition fe_poly.h:530
Threads::Mutex prolongation_matrix_mutex
Definition fe_q_base.h:332
void initialize_unit_face_support_points(const std::vector< Point< 1 > > &points)
virtual bool hp_constraints_are_implemented() const override
virtual std::vector< std::pair< unsigned int, unsigned int > > hp_line_dof_identities(const FiniteElement< dim, spacedim > &fe_other) const override
virtual void get_interpolation_matrix(const FiniteElement< dim, spacedim > &source, FullMatrix< double > &matrix) const override
Threads::Mutex restriction_matrix_mutex
Definition fe_q_base.h:331
virtual std::vector< std::pair< unsigned int, unsigned int > > hp_quad_dof_identities(const FiniteElement< dim, spacedim > &fe_other, const unsigned int face_no=0) const override
const unsigned int q_degree
Definition fe_q_base.h:339
virtual std::pair< Table< 2, bool >, std::vector< unsigned int > > get_constant_modes() const override
void initialize(const std::vector< Point< 1 > > &support_points_1d)
void initialize_unit_support_points(const std::vector< Point< 1 > > &points)
virtual void get_subface_interpolation_matrix(const FiniteElement< dim, spacedim > &source, const unsigned int subface, FullMatrix< double > &matrix, const unsigned int face_no=0) const override
virtual void get_face_interpolation_matrix(const FiniteElement< dim, spacedim > &source, FullMatrix< double > &matrix, const unsigned int face_no=0) const override
static std::vector< unsigned int > get_dpo_vector(const unsigned int degree)
void initialize_constraints(const std::vector< Point< 1 > > &points)
void initialize_dof_index_permutations()
virtual const FullMatrix< double > & get_restriction_matrix(const unsigned int child, const RefinementCase< dim > &refinement_case=RefinementCase< dim >::isotropic_refinement) const override
virtual const FullMatrix< double > & get_prolongation_matrix(const unsigned int child, const RefinementCase< dim > &refinement_case=RefinementCase< dim >::isotropic_refinement) const override
virtual bool has_support_on_face(const unsigned int shape_index, const unsigned int face_index) const override
virtual unsigned int face_to_cell_index(const unsigned int face_dof_index, const unsigned int face, const types::geometric_orientation combined_orientation=numbers::default_geometric_orientation) const override
virtual std::vector< std::pair< unsigned int, unsigned int > > hp_vertex_dof_identities(const FiniteElement< dim, spacedim > &fe_other) const override
FE_Q_Base(const ScalarPolynomialsBase< dim > &poly_space, const FiniteElementData< dim > &fe_data, const std::vector< bool > &restriction_is_additive_flags)
unsigned int get_first_line_index() const
const unsigned int degree
Definition fe_data.h:450
unsigned int n_dofs_per_cell() const
unsigned int n_dofs_per_line() const
unsigned int get_first_quad_index(const unsigned int quad_no=0) const
unsigned int n_dofs_per_face(unsigned int face_no=0, unsigned int child=0) const
unsigned int n_components() const
unsigned int n_unique_faces() const
unsigned int n_dofs_per_quad(unsigned int face_no=0) const
ReferenceCell< dim > reference_cell() const
unsigned int get_first_hex_index() const
std::vector< std::vector< Point< dim - 1 > > > unit_face_support_points
Definition fe.h:2592
bool has_face_support_points(const unsigned int face_no=0) const
std::vector< std::vector< FullMatrix< double > > > restriction
Definition fe.h:2547
std::vector< Table< 2, int > > adjust_quad_dof_index_for_face_orientation_table
Definition fe.h:2621
const std::vector< Point< dim - 1 > > & get_unit_face_support_points(const unsigned int face_no=0) const
std::vector< int > adjust_line_dof_index_for_line_orientation_table
Definition fe.h:2634
TableIndices< 2 > interface_constraints_size() const
std::vector< Point< dim > > unit_support_points
Definition fe.h:2585
FullMatrix< double > interface_constraints
Definition fe.h:2573
std::vector< std::vector< FullMatrix< double > > > prolongation
Definition fe.h:2561
size_type n() const
size_type m() const
Definition point.h:111
Class which transforms dim - 1-dimensional quadrature rules to dim-dimensional face quadratures.
Definition qprojector.h:68
static Quadrature< dim > project_to_face(const ReferenceCell< dim > &reference_cell, const SubQuadrature &quadrature, const unsigned int face_no, const types::geometric_orientation combined_orientation)
static Quadrature< dim > project_to_subface(const ReferenceCell< dim > &reference_cell, const SubQuadrature &quadrature, const unsigned int face_no, const unsigned int subface_no, const types::geometric_orientation combined_orientation, const RefinementCase< dim - 1 > &ref_case)
const Point< dim > & point(const unsigned int i) const
void set_numbering(const std::vector< unsigned int > &renumber)
void set_numbering(const std::vector< unsigned int > &renumber)
void set_numbering(const std::vector< unsigned int > &renumber)
#define DEAL_II_NAMESPACE_OPEN
Definition config.h:38
constexpr bool running_in_debug_mode()
Definition config.h:76
#define DEAL_II_NAMESPACE_CLOSE
Definition config.h:39
#define DEAL_II_ASSERT_UNREACHABLE()
#define DEAL_II_NOT_IMPLEMENTED()
#define Assert(cond, exc)
#define AssertDimension(dim1, dim2)
#define AssertIndexRange(index, range)
static ::ExceptionBase & ExcInternalError()
static ::ExceptionBase & ExcDimensionMismatch(std::size_t arg1, std::size_t arg2)
static ::ExceptionBase & ExcInterpolationNotImplemented()
static ::ExceptionBase & ExcMessage(std::string arg1)
#define AssertThrow(cond, exc)
Task< RT > new_task(const std::function< RT()> &function)
unsigned int face_to_cell_index(const FiniteElement< dim, spacedim > &fe, const unsigned int face_dof_index, const unsigned int face_no, const types::geometric_orientation combined_orientation)
std::vector< unsigned int > lexicographic_to_hierarchic_numbering(unsigned int degree)
constexpr char U
Point< spacedim > point(const gp_Pnt &p, const double tolerance=1e-10)
Definition utilities.cc:210
SymmetricTensor< 2, dim, Number > e(const Tensor< 2, dim, Number > &F)
Tensor< 2, dim, Number > l(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
SymmetricTensor< 2, dim, Number > d(const Tensor< 2, dim, Number > &F, const Tensor< 2, dim, Number > &dF_dt)
T sum(const T &t, const MPI_Comm mpi_communicator)
types::geometric_orientation combined_face_orientation(const bool face_orientation, const bool face_rotation, const bool face_flip)
constexpr unsigned int invalid_unsigned_int
Definition types.h:228
constexpr types::geometric_orientation default_geometric_orientation
Definition types.h:342
STL namespace.
::VectorizedArray< Number, width > max(const ::VectorizedArray< Number, width > &, const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > sqrt(const ::VectorizedArray< Number, width > &)
::VectorizedArray< Number, width > abs(const ::VectorizedArray< Number, width > &)
std::uint8_t geometric_orientation
Definition types.h:38
static void initialize_constraints(const std::vector< Point< 1 > > &, FE_Q_Base< 3, spacedim > &fe)
Definition fe_q_base.cc:209
static void initialize_constraints(const std::vector< Point< 1 > > &, FE_Q_Base< 1, spacedim > &)
Definition fe_q_base.cc:95
static void initialize_constraints(const std::vector< Point< 1 > > &, FE_Q_Base< 2, spacedim > &fe)
Definition fe_q_base.cc:104
static Point< dim > cell_to_child_coordinates(const Point< dim > &p, const unsigned int child_index, const RefinementCase< dim > refine_case=RefinementCase< dim >::isotropic_refinement)
static Point< dim > child_to_cell_coordinates(const Point< dim > &p, const unsigned int child_index, const RefinementCase< dim > refine_case=RefinementCase< dim >::isotropic_refinement)