LehrFEM++ 1.0.0
A simple Finite Element Library for teaching
Loading...
Searching...
No Matches
hierarchic_fe.h
1#ifndef LF_USCALFE_HP_FE_H_
2#define LF_USCALFE_HP_FE_H_
3
12#include <lf/assemble/assemble.h>
13#include <lf/mesh/mesh.h>
14#include <lf/quad/quad.h>
15
16#include <cmath>
17#include <memory>
18#include <vector>
19
20#include "fe_point.h"
21#include "scalar_reference_finite_element.h"
22
23namespace lf::fe {
24
25// clang-format off
40// clang-format on
41double Legendre(unsigned n, double x, double t = 1);
42
43// clang-format off
59// clang-format on
60double ILegendre(unsigned n, double x, double t = 1);
61
62// clang-format off
72// clang-format on
73double ILegendreDx(unsigned n, double x, double t = 1);
74
75// clang-format off
89// clang-format on
90double ILegendreDt(unsigned n, double x, double t = 1);
91
92// clang-format off
106// clang-format on
107double LegendreDx(unsigned n, double x, double t = 1);
108
109// clang-format off
132// clang-format on
133double Jacobi(unsigned n, double alpha, double beta, double x);
134
135// clang-format off
143// clang-format on
144double Jacobi(unsigned n, double alpha, double x);
145
146// clang-format off
168// clang-format on
169double IJacobi(unsigned n, double alpha, double x);
170
171// clang-format off
182// clang-format on
183double IJacobiDx(unsigned n, double alpha, double x);
184
185// clang-format off
198// clang-format on
199double JacobiDx(unsigned n, double alpha, double x);
200
201// clang-format off
240// clang-format on
241template <typename SCALAR>
243 public:
246 FeHierarchicSegment &operator=(const FeHierarchicSegment &) = default;
247 FeHierarchicSegment &operator=(FeHierarchicSegment &&) noexcept = default;
248 ~FeHierarchicSegment() override = default;
249
250 FeHierarchicSegment(unsigned degree, const quad::QuadRuleCache &qr_cache)
252 degree_(degree),
253 qr_dual_(&qr_cache.Get(RefEl(), 2 * (degree - 1))) {}
254
255 [[nodiscard]] lf::base::RefEl RefEl() const override {
257 }
258
259 [[nodiscard]] unsigned Degree() const override { return degree_; }
260
265 [[nodiscard]] lf::base::size_type NumRefShapeFunctions() const override {
266 return degree_ + 1;
267 }
268
275 dim_t codim) const override {
276 LF_ASSERT_MSG(codim >= 0 && codim <= 1, "codim out of range.");
277 return codim == 0 ? degree_ - 1 : 2;
278 }
279
280 // clang-format off
286 // clang-format on
288 dim_t codim, sub_idx_t subidx) const override {
289 LF_ASSERT_MSG((codim == 0 && subidx == 0) || (codim == 1 && subidx < 2),
290 "codim/subidx out of range.");
291 return NumRefShapeFunctions(codim);
292 }
293
294 [[nodiscard]] Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic>
295 EvalReferenceShapeFunctions(const Eigen::MatrixXd &refcoords) const override {
296 LF_ASSERT_MSG(refcoords.rows() == 1, "refcoords must be a row vector");
297 Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> result(
298 NumRefShapeFunctions(), refcoords.cols());
299 // Get the shape functions associated with the vertices
300 result.row(0) =
301 refcoords.unaryExpr([&](double x) -> SCALAR { return 1 - x; });
302 result.row(1) = refcoords;
303 // Get the shape functions associated with the interior of the segment
304 for (int i = 0; i < degree_ - 1; ++i) {
305 result.row(i + 2) = refcoords.unaryExpr(
306 [&](double x) -> SCALAR { return ILegendre(i + 2, x); });
307 }
308 return result;
309 }
310
311 [[nodiscard]] Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic>
313 const Eigen::MatrixXd &refcoords) const override {
314 LF_ASSERT_MSG(refcoords.rows() == 1, "refcoords must be a row vector");
315 Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> result(
316 NumRefShapeFunctions(), refcoords.cols());
317 // Get the gradient of the shape functions associated with the vertices
318 result.row(0) =
319 refcoords.unaryExpr([&](double /*x*/) -> SCALAR { return -1; });
320 result.row(1) =
321 refcoords.unaryExpr([&](double /*x*/) -> SCALAR { return 1; });
322 // Get the shape functions associated with the interior of the segment
323 for (int i = 0; i < degree_ - 1; ++i) {
324 result.row(i + 2) = refcoords.unaryExpr(
325 [&](double x) -> SCALAR { return ILegendreDx(i + 2, x); });
326 }
327 return result;
328 }
329
335 [[nodiscard]] Eigen::MatrixXd EvaluationNodes() const override {
336 // First two nodes are vertices of the segment
337 Eigen::MatrixXd nodes(1, NumEvaluationNodes());
338 nodes(0, 0) = 0;
339 nodes(0, 1) = 1;
340 // The other nodes are quadrature points
341 nodes.block(0, 2, 1, qr_dual_->NumPoints()) = qr_dual_->Points();
342 return nodes;
343 }
344
348 [[nodiscard]] lf::base::size_type NumEvaluationNodes() const override {
349 return qr_dual_->NumPoints() + 2;
350 }
351
352 // clang-format off
366 // clang-format on
367 [[nodiscard]] Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> NodalValuesToDofs(
368 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> &nodevals) const override {
369 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> dofs(NumRefShapeFunctions());
370 // Compute the first and second basis function coefficients
371 dofs[0] = nodevals[0];
372 dofs[1] = nodevals[1];
373 // Compute the other basis function coefficients
374 for (lf::base::size_type i = 2; i < NumRefShapeFunctions(); ++i) {
375 const SCALAR P0 = ILegendreDx(i, 0);
376 const SCALAR P1 = ILegendreDx(i, 1);
377 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> psidd =
378 qr_dual_->Points().unaryExpr(
379 [&](double x) -> SCALAR { return LegendreDx(i - 1, x); });
380 // Evaluate the integral from the dual basis
381 const SCALAR integ =
382 (qr_dual_->Weights().transpose().array() * psidd.array() *
383 nodevals.tail(qr_dual_->NumPoints()).array())
384 .sum();
385 // Compute the basis function coefficient
386 dofs[i] = (P1 * nodevals[1] - P0 * nodevals[0] - integ) * (2 * i - 1);
387 }
388 return dofs;
389 }
390
391 private:
392 unsigned degree_;
394};
395
396// clang-format off
472// clang-format on
473template <typename SCALAR>
474class FeHierarchicTria final : public ScalarReferenceFiniteElement<SCALAR> {
475 public:
477 FeHierarchicTria(FeHierarchicTria &&) noexcept = default;
478 FeHierarchicTria &operator=(const FeHierarchicTria &) = default;
479 FeHierarchicTria &operator=(FeHierarchicTria &&) noexcept = default;
480 ~FeHierarchicTria() override = default;
481
482 FeHierarchicTria(unsigned interior_degree,
483 std::array<unsigned, 3> edge_degrees,
484 const quad::QuadRuleCache &qr_cache,
485 std::span<const lf::mesh::Orientation> rel_orient)
487 interior_degree_(interior_degree),
488 edge_degrees_(edge_degrees),
490 {&qr_cache.Get(base::RefEl::kSegment(), 2 * (edge_degrees_[0] - 1)),
491 &qr_cache.Get(base::RefEl::kSegment(), 2 * (edge_degrees_[1] - 1)),
492 &qr_cache.Get(base::RefEl::kSegment(),
493 2 * (edge_degrees_[2] - 1))}),
495 2 * (interior_degree_ - 1))),
496 rel_orient_(rel_orient) {
497 LF_ASSERT_MSG(interior_degree_ >= 0, "illegal interior degree.");
498 LF_ASSERT_MSG(edge_degrees_[0] >= 0, "illegal degree for edge 0");
499 LF_ASSERT_MSG(edge_degrees_[1] >= 0, "illegal degree for edge 1");
500 LF_ASSERT_MSG(edge_degrees_[2] >= 0, "illegal degree for edge 2");
501 }
502
503 [[nodiscard]] lf::base::RefEl RefEl() const override {
504 return lf::base::RefEl::kTria();
505 }
506
507 [[nodiscard]] unsigned Degree() const override {
508 return std::max({interior_degree_, edge_degrees_[0], edge_degrees_[1],
509 edge_degrees_[2], 1U});
510 }
511
516 [[nodiscard]] lf::base::size_type NumRefShapeFunctions() const override {
519 }
520
527 dim_t codim) const override {
528 switch (codim) {
529 case 0:
530 if (interior_degree_ <= 2) {
531 return 0;
532 } else {
533 return (interior_degree_ - 2) * (interior_degree_ - 1) / 2;
534 }
535 case 1:
536 LF_VERIFY_MSG(
537 edge_degrees_[0] == edge_degrees_[1] &&
539 "You cannot call this method with codim=1 if the edges of the "
540 "triangle have a differing number of shape functions.");
541 return edge_degrees_[0] - 1;
542 case 2:
543 return 1;
544 default:
545 LF_ASSERT_MSG(false, "Illegal codim " << codim);
546 // Silence compiler warnings
547 return 0;
548 }
549 }
550
551 // clang-format off
557 // clang-format on
559 dim_t codim, sub_idx_t subidx) const override {
560 switch (codim) {
561 case 0:
562 LF_ASSERT_MSG(subidx == 0, "illegal codim and subidx.");
563 return NumRefShapeFunctions(0);
564 case 1:
565 LF_ASSERT_MSG(subidx >= 0 && subidx <= 2,
566 "illegal codim/subidx combination.");
567 return edge_degrees_[subidx] - 1;
568 case 2:
569 LF_ASSERT_MSG(subidx >= 0 && subidx <= 2,
570 "illegal codim/subidx combination.");
571 return 1;
572 default:
573 LF_VERIFY_MSG(false, "Illegal codim " << codim);
574 // Silence compiler warnings
575 return 0;
576 }
577 }
578
579 [[nodiscard]] Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic>
580 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
581 EvalReferenceShapeFunctions(const Eigen::MatrixXd &refcoords) const override {
582 Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> result(
583 NumRefShapeFunctions(), refcoords.cols());
584 // Compute the barycentric coordinate functions
585 const Eigen::RowVectorXd l1 = Eigen::RowVectorXd::Ones(refcoords.cols()) -
586 refcoords.row(0) - refcoords.row(1);
587 const Eigen::RowVectorXd l2 = refcoords.row(0);
588 const Eigen::RowVectorXd l3 = refcoords.row(1);
589 // Get the basis functions associated with the vertices
590 result.row(0) = l1;
591 result.row(1) = l2;
592 result.row(2) = l3;
593 int current_dof = 3; // counter for the next row in the result.
594
595 // Get the basis functions associated with the first edge
596 for (unsigned i = 0; i < edge_degrees_[0] - 1; ++i) {
598 // L_{i+2}(\lambda_2 ; \lambda_1+\lambda_2)
599 for (long j = 0; j < refcoords.cols(); ++j) {
600 result(3 + i, j) = ILegendre(i + 2, l2[j], l1[j] + l2[j]);
601 }
602 } else {
603 // L_{i+2}(\lambda_1 ; \lambda_1+\lambda_2)
604 for (long j = 0; j < refcoords.cols(); ++j) {
605 result(edge_degrees_[0] + 1 - i, j) =
606 ILegendre(i + 2, l1[j], l1[j] + l2[j]);
607 }
608 }
609 }
610 current_dof += edge_degrees_[0] - 1;
611
612 // Get the basis functions associated with the second edge
613 for (unsigned i = 0; i < edge_degrees_[1] - 1; ++i) {
615 // L_{i+2}(\lambda_3 ; \lambda_2+\lambda_3)
616 for (long j = 0; j < refcoords.cols(); ++j) {
617 result(current_dof + i, j) = ILegendre(i + 2, l3[j], l2[j] + l3[j]);
618 }
619 } else {
620 // L_{i+2}(\lambda_2 ; \lambda_2+\lambda_3)
621 for (long j = 0; j < refcoords.cols(); ++j) {
622 result(current_dof + edge_degrees_[1] - 2 - i, j) =
623 ILegendre(i + 2, l2[j], l2[j] + l3[j]);
624 }
625 }
626 }
627 current_dof += edge_degrees_[1] - 1;
628
629 // Get the basis functions associated with the third edge
630 for (unsigned i = 0; i < edge_degrees_[2] - 1; ++i) {
632 // L_{i+2}(\lambda_1 ; \lambda_3+\lambda_1)
633 for (long j = 0; j < refcoords.cols(); ++j) {
634 result(current_dof + i, j) = ILegendre(i + 2, l1[j], l3[j] + l1[j]);
635 }
636 } else {
637 // L_{i+2}(\lambda_3 ; \lambda_3+\lambda_1)
638 for (long j = 0; j < refcoords.cols(); ++j) {
639 result(current_dof + edge_degrees_[2] - 2 - i, j) =
640 ILegendre(i + 2, l3[j], l3[j] + l1[j]);
641 }
642 }
643 }
644 current_dof += edge_degrees_[2] - 1;
645
646 // Get the basis functions associated with the interior of the triangle
647 if (interior_degree_ > 2) {
648 // i is the degree of the edge function
649 for (unsigned i = 0; i < interior_degree_ - 2; ++i) {
650 // value of the edge function (agrees with the values computed above,
651 // but since the degrees of the edge and interior are not linked at all,
652 // we cannot make any assumptions.
653 Eigen::Array<SCALAR, 1, Eigen::Dynamic> edge(refcoords.cols());
654 for (Eigen::Index j = 0; j < refcoords.cols(); ++j) {
656 edge(j) = ILegendre(i + 2, l2[j], l1[j] + l2[j]);
657 } else {
658 edge(j) = ILegendre(i + 2, l1[j], l1[j] + l2[j]);
659 }
660 }
661
662 // j is the degree of the blending
663 // polynomial
664 for (unsigned j = 0; j < interior_degree_ - i - 2; ++j) {
666 // L_{i+2}(\lambda_3 ; \lambda_2+\lambda_3) *
667 // P_{j+1}^{2i+4}(\lambda_1)
668 result.row(current_dof++) =
669 (edge * l3.array().unaryExpr([&](double x) -> SCALAR {
670 return IJacobi(j + 1, 2 * i + 4, x);
671 })).matrix();
672 } else {
673 // L_{i+2}(\lambda_2 ; \lambda_2+\lambda_3) *
674 // P_{j+1}^{2i+4}(\lambda_1)
675 result.row(current_dof++) =
676 (edge * l3.array().unaryExpr([&](double x) -> SCALAR {
677 return IJacobi(j + 1, 2 * i + 4, x);
678 })).matrix();
679 }
680 }
681 }
682 }
683 LF_ASSERT_MSG(current_dof == result.rows(),
684 "Something's wrong, not all rows have been filled.");
685 return result;
686 }
687
688 [[nodiscard]] Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic>
689 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
691 const Eigen::MatrixXd &refcoords) const override {
692 Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> result(
693 NumRefShapeFunctions(), 2 * refcoords.cols());
694 // Compute the barycentric coordinate functions
695 const Eigen::RowVectorXd l1 = Eigen::RowVectorXd::Ones(refcoords.cols()) -
696 refcoords.row(0) - refcoords.row(1);
697 const Eigen::RowVectorXd l2 = refcoords.row(0);
698 const Eigen::RowVectorXd l3 = refcoords.row(1);
699 // Comput the gradients of the barycentrc coordinate functions
700 const Eigen::RowVectorXd l1_dx =
701 Eigen::RowVectorXd::Constant(refcoords.cols(), -1);
702 const Eigen::RowVectorXd l1_dy =
703 Eigen::RowVectorXd::Constant(refcoords.cols(), -1);
704 const Eigen::RowVectorXd l2_dx =
705 Eigen::RowVectorXd::Constant(refcoords.cols(), 1);
706 const Eigen::RowVectorXd l2_dy =
707 Eigen::RowVectorXd::Constant(refcoords.cols(), 0);
708 const Eigen::RowVectorXd l3_dx =
709 Eigen::RowVectorXd::Constant(refcoords.cols(), 0);
710 const Eigen::RowVectorXd l3_dy =
711 Eigen::RowVectorXd::Constant(refcoords.cols(), 1);
712 // Iterate over all refcoords
713 for (Eigen::Index i = 0; i < refcoords.cols(); ++i) {
714 int current_dof = 3;
715 // Get the gradient of the basis functions associated with the vertices
716 result(0, 2 * i + 0) = l1_dx[i];
717 result(0, 2 * i + 1) = l1_dy[i];
718 result(1, 2 * i + 0) = l2_dx[i];
719 result(1, 2 * i + 1) = l2_dy[i];
720 result(2, 2 * i + 0) = l3_dx[i];
721 result(2, 2 * i + 1) = l3_dy[i];
722 // Get the gradient of the basis functions associated with the first edge
723 for (int j = 0; j < edge_degrees_[0] - 1; ++j) {
725 result(current_dof + j, 2 * i + 0) =
726 ILegendreDx(j + 2, l2[i], l1[i] + l2[i]) * l2_dx[i] +
727 ILegendreDt(j + 2, l2[i], l1[i] + l2[i]) * (l1_dx[i] + l2_dx[i]);
728 result(current_dof + j, 2 * i + 1) =
729 ILegendreDx(j + 2, l2[i], l1[i] + l2[i]) * l2_dy[i] +
730 ILegendreDt(j + 2, l2[i], l1[i] + l2[i]) * (l1_dy[i] + l2_dy[i]);
731 } else {
732 result(current_dof + edge_degrees_[0] - 2 - j, 2 * i + 0) =
733 ILegendreDx(j + 2, l1[i], l1[i] + l2[i]) * l1_dx[i] +
734 ILegendreDt(j + 2, l1[i], l1[i] + l2[i]) * (l1_dx[i] + l2_dx[i]);
735 result(current_dof + edge_degrees_[0] - 2 - j, 2 * i + 1) =
736 ILegendreDx(j + 2, l1[i], l1[i] + l2[i]) * l1_dy[i] +
737 ILegendreDt(j + 2, l1[i], l1[i] + l2[i]) * (l1_dy[i] + l2_dy[i]);
738 }
739 }
740 current_dof += edge_degrees_[0] - 1;
741
742 // Get the gradient of the basis functions associated with the second edge
743 for (int j = 0; j < edge_degrees_[1] - 1; ++j) {
745 result(current_dof + j, 2 * i + 0) =
746 ILegendreDx(j + 2, l3[i], l2[i] + l3[i]) * l3_dx[i] +
747 ILegendreDt(j + 2, l3[i], l2[i] + l3[i]) * (l2_dx[i] + l3_dx[i]);
748 result(current_dof + j, 2 * i + 1) =
749 ILegendreDx(j + 2, l3[i], l2[i] + l3[i]) * l3_dy[i] +
750 ILegendreDt(j + 2, l3[i], l2[i] + l3[i]) * (l2_dy[i] + l3_dy[i]);
751 } else {
752 result(current_dof + edge_degrees_[1] - 2 - j, 2 * i + 0) =
753 ILegendreDx(j + 2, l2[i], l2[i] + l3[i]) * l2_dx[i] +
754 ILegendreDt(j + 2, l2[i], l2[i] + l3[i]) * (l2_dx[i] + l3_dx[i]);
755 result(current_dof + edge_degrees_[1] - 2 - j, 2 * i + 1) =
756 ILegendreDx(j + 2, l2[i], l2[i] + l3[i]) * l2_dy[i] +
757 ILegendreDt(j + 2, l2[i], l2[i] + l3[i]) * (l2_dy[i] + l3_dy[i]);
758 }
759 }
760 current_dof += edge_degrees_[1] - 1;
761
762 // Get the gradient of the basis functions associated with the third edge
763 for (int j = 0; j < edge_degrees_[2] - 1; ++j) {
765 result(current_dof + j, 2 * i + 0) =
766 ILegendreDx(j + 2, l1[i], l3[i] + l1[i]) * l1_dx[i] +
767 ILegendreDt(j + 2, l1[i], l3[i] + l1[i]) * (l3_dx[i] + l1_dx[i]);
768 result(current_dof + j, 2 * i + 1) =
769 ILegendreDx(j + 2, l1[i], l3[i] + l1[i]) * l1_dy[i] +
770 ILegendreDt(j + 2, l1[i], l3[i] + l1[i]) * (l3_dy[i] + l1_dy[i]);
771 } else {
772 result(current_dof + edge_degrees_[2] - 2 - j, 2 * i + 0) =
773 ILegendreDx(j + 2, l3[i], l3[i] + l1[i]) * l3_dx[i] +
774 ILegendreDt(j + 2, l3[i], l3[i] + l1[i]) * (l3_dx[i] + l1_dx[i]);
775 result(current_dof + edge_degrees_[2] - 2 - j, 2 * i + 1) =
776 ILegendreDx(j + 2, l3[i], l3[i] + l1[i]) * l3_dy[i] +
777 ILegendreDt(j + 2, l3[i], l3[i] + l1[i]) * (l3_dy[i] + l1_dy[i]);
778 }
779 }
780 current_dof += edge_degrees_[2] - 1;
781
782 // Get the gradient of the basis functions associated with the interior of
783 // the triangle
784 if (interior_degree_ > 2) {
785 for (unsigned j = 0; j < interior_degree_ - 2; ++j) {
786 SCALAR edge_eval;
787 SCALAR edge_dx;
788 SCALAR edge_dy;
790 edge_eval = ILegendre(j + 2, l2[i], l1[i] + l2[i]);
791 edge_dx = ILegendreDx(j + 2, l2[i], l1[i] + l2[i]) * l2_dx[i] +
792 ILegendreDt(j + 2, l2[i], l1[i] + l2[i]) *
793 (l1_dx[i] + l2_dx[i]);
794 edge_dy = ILegendreDx(j + 2, l2[i], l1[i] + l2[i]) * l2_dy[i] +
795 ILegendreDt(j + 2, l2[i], l1[i] + l2[i]) *
796 (l1_dy[i] + l2_dy[i]);
797 } else {
798 edge_eval = ILegendre(j + 2, l1[i], l1[i] + l2[i]);
799 edge_dx = ILegendreDx(j + 2, l1[i], l1[i] + l2[i]) * l1_dx[i] +
800 ILegendreDt(j + 2, l1[i], l1[i] + l2[i]) *
801 (l1_dx[i] + l2_dx[i]);
802 edge_dy = ILegendreDx(j + 2, l1[i], l1[i] + l2[i]) * l1_dy[i] +
803 ILegendreDt(j + 2, l1[i], l1[i] + l2[i]) *
804 (l1_dy[i] + l2_dy[i]);
805 }
806 for (unsigned k = 0; k < interior_degree_ - j - 2; ++k) {
807 SCALAR jackinte = IJacobi(k + 1, 2 * j + 4, l3[i]);
808 SCALAR jackeval = IJacobiDx(k + 1, 2 * j + 4, l3[i]);
809 result(current_dof, 2 * i + 0) =
810 jackinte * edge_dx + edge_eval * jackeval * l3_dx[i];
811 result(current_dof++, 2 * i + 1) =
812 jackinte * edge_dy + edge_eval * jackeval * l3_dy[i];
813 }
814 }
815 }
816 LF_ASSERT_MSG(current_dof == NumRefShapeFunctions(),
817 "internal error, not all rows have been filled.");
818 }
819 return result;
820 }
821
828 [[nodiscard]] Eigen::MatrixXd EvaluationNodes() const override {
829 const auto Ns0 = edge_degrees_[0] > 1 ? qr_dual_edge_[0]->NumPoints() : 0;
830 const auto Ns1 = edge_degrees_[1] > 1 ? qr_dual_edge_[1]->NumPoints() : 0;
831 const auto Ns2 = edge_degrees_[2] > 1 ? qr_dual_edge_[2]->NumPoints() : 0;
833 Eigen::MatrixXd nodes(2, 3 + Ns0 + Ns1 + Ns2 + Nt);
834 // Add the vertices
835 nodes(0, 0) = 0;
836 nodes(1, 0) = 0;
837 nodes(0, 1) = 1;
838 nodes(1, 1) = 0;
839 nodes(0, 2) = 0;
840 nodes(1, 2) = 1;
841 // Add the quadrature points on the first edge
842 if (Ns0 > 0) {
844 nodes.block(0, 3, 1, Ns0) = qr_dual_edge_[0]->Points();
845 } else {
846 nodes.block(0, 3, 1, Ns0) =
847 Eigen::RowVectorXd::Ones(Ns0) - qr_dual_edge_[0]->Points();
848 }
849 nodes.block(1, 3, 1, Ns0).setZero();
850 }
851 // Add the quadrature points on the second edge
852 if (Ns1 > 0) {
854 nodes.block(0, 3 + Ns0, 1, Ns1) =
855 Eigen::RowVectorXd::Ones(Ns1) - qr_dual_edge_[1]->Points();
856 nodes.block(1, 3 + Ns0, 1, Ns1) = qr_dual_edge_[1]->Points();
857 } else {
858 nodes.block(0, 3 + Ns0, 1, Ns1) = qr_dual_edge_[1]->Points();
859 nodes.block(1, 3 + Ns0, 1, Ns1) =
860 Eigen::RowVectorXd::Ones(Ns1) - qr_dual_edge_[1]->Points();
861 }
862 }
863 // Add the quadrature points on the third edge
864 if (Ns2 > 0) {
865 nodes.block(0, 3 + Ns0 + Ns1, 1, Ns2).setZero();
867 nodes.block(1, 3 + Ns0 + Ns1, 1, Ns2) =
868 Eigen::RowVectorXd::Ones(Ns2) - qr_dual_edge_[2]->Points();
869 } else {
870 nodes.block(1, 3 + Ns0 + Ns1, 1, Ns2) = qr_dual_edge_[2]->Points();
871 }
872 }
873 if (Nt > 0) {
874 // Add the quadrature points for the interior
875 nodes.block(0, 3 + Ns0 + Ns1 + Ns2, 2, Nt) = qr_dual_tria_->Points();
876 }
877 return nodes;
878 }
879
883 [[nodiscard]] lf::base::size_type NumEvaluationNodes() const override {
884 const auto Ns0 = edge_degrees_[0] > 1 ? qr_dual_edge_[0]->NumPoints() : 0;
885 const auto Ns1 = edge_degrees_[1] > 1 ? qr_dual_edge_[1]->NumPoints() : 0;
886 const auto Ns2 = edge_degrees_[2] > 1 ? qr_dual_edge_[2]->NumPoints() : 0;
888 return 3 + Ns0 + Ns1 + Ns2 + Nt;
889 }
890
891 [[nodiscard]] Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> NodalValuesToDofs(
892 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> &nodevals) const override {
893 const auto d0 = edge_degrees_[0] - 1;
894 const auto d1 = edge_degrees_[1] - 1;
895 const auto d2 = edge_degrees_[2] - 1;
896
897 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> dofs(NumRefShapeFunctions());
898 // Compute the basis function coefficients of the vertex basis functions
899 dofs.segment(0, 3) = NodalValuesToVertexDofs(nodevals);
900 // Compute the basis function coefficients on the edges
901 dofs.segment(3, d0 + d1 + d2) = NodalValuesToEdgeDofs(nodevals);
902 // Compute the basis function coefficients for the face bubbles
903 dofs.segment(3 + d0 + d1 + d2, NumRefShapeFunctions(0)) =
904 NodalValuesToFaceDofs(nodevals);
905 // We need to orthogonalize the face bubble dual basis w.r.t. the
906 // dual basis on the vertices and edges
907 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic>
908 basis_functions = EvalReferenceShapeFunctions(EvaluationNodes());
909 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> boundary_function =
910 dofs.segment(0, 3 + d0 + d1 + d2) *
911 basis_functions.block(0, 0, 3 + d0 + d1 + d2, basis_functions.cols());
912 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> boundary_face_dofs =
913 NodalValuesToFaceDofs(boundary_function);
914 dofs.segment(3 + d0 + d1 + d2, NumRefShapeFunctions(0)) -=
915 boundary_face_dofs;
916 return dofs;
917 }
918
919 private:
920 unsigned interior_degree_; // degree for inner shape functions
921 std::array<unsigned, 3> edge_degrees_; // degree of the edges.
922 std::array<const quad::QuadRule *, 3>
923 qr_dual_edge_; // Quadrature rules for the edges.
925 std::span<const lf::mesh::Orientation> rel_orient_;
926
927 /*
928 * @brief Compute the DOFs of the vertex functions from some
929 * given nodal evaluations
930 */
931 [[nodiscard]] Eigen::Matrix<SCALAR, 1, Eigen::Dynamic>
933 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> &nodevals) const {
934 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> dofs(3);
935 dofs[0] = nodevals[0];
936 dofs[1] = nodevals[1];
937 dofs[2] = nodevals[2];
938 return dofs;
939 }
940
941 /*
942 * @brief Compute the DOFs of the edge functions from some
943 * given nodal evaluations
944 */
945 [[nodiscard]] Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> NodalValuesToEdgeDofs(
946 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> &nodevals) const {
947 const auto Ns0 = edge_degrees_[0] > 1 ? qr_dual_edge_[0]->NumPoints() : 0;
948 const auto Ns1 = edge_degrees_[1] > 1 ? qr_dual_edge_[1]->NumPoints() : 0;
949 const auto Ns2 = edge_degrees_[2] > 1 ? qr_dual_edge_[2]->NumPoints() : 0;
951 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> dofs(
952 edge_degrees_[0] + edge_degrees_[1] + edge_degrees_[2] - 3);
953 // Compute the basis function coefficients on the edges
954 // by applying the dual basis of the segment
955
956 // first edge:
957 for (base::size_type i = 2; i < edge_degrees_[0] + 1; ++i) {
958 const SCALAR P0 = ILegendreDx(i, 0);
959 const SCALAR P1 = ILegendreDx(i, 1);
960 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> psidd =
961 qr_dual_edge_[0]->Points().unaryExpr(
962 [&](double x) -> SCALAR { return LegendreDx(i - 1, x); });
963
964 const SCALAR integ1 = (qr_dual_edge_[0]->Weights().transpose().array() *
965 psidd.array() * nodevals.segment(3, Ns0).array())
966 .sum();
968 dofs[1 + i - 3] =
969 (P1 * nodevals[1] - P0 * nodevals[0] - integ1) * (2 * i - 1);
970 } else {
971 dofs[4 - i + (edge_degrees_[0] - 1) - 3] =
972 (P1 * nodevals[0] - P0 * nodevals[1] - integ1) * (2 * i - 1);
973 }
974 }
975
976 // Compute the basis function coefficients for the second edge
977 for (base::size_type i = 2; i < edge_degrees_[1] + 1; ++i) {
978 const SCALAR P0 = ILegendreDx(i, 0);
979 const SCALAR P1 = ILegendreDx(i, 1);
980 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> psidd =
981 qr_dual_edge_[1]->Points().unaryExpr(
982 [&](double x) -> SCALAR { return LegendreDx(i - 1, x); });
983
984 const SCALAR integ2 =
985 (qr_dual_edge_[1]->Weights().transpose().array() * psidd.array() *
986 nodevals.segment(3 + Ns0, Ns1).array())
987 .sum();
989 dofs[1 + i + (edge_degrees_[0] - 1) - 3] =
990 (P1 * nodevals[2] - P0 * nodevals[1] - integ2) * (2 * i - 1);
991 } else {
992 dofs[4 - i + (edge_degrees_[0] + edge_degrees_[1] - 2) - 3] =
993 (P1 * nodevals[1] - P0 * nodevals[2] - integ2) * (2 * i - 1);
994 }
995 }
996
997 // Compute the basis function coefficients for the second edge
998 for (base::size_type i = 2; i < edge_degrees_[2] + 1; ++i) {
999 const SCALAR P0 = ILegendreDx(i, 0);
1000 const SCALAR P1 = ILegendreDx(i, 1);
1001 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> psidd =
1002 qr_dual_edge_[2]->Points().unaryExpr(
1003 [&](double x) -> SCALAR { return LegendreDx(i - 1, x); });
1004
1005 const SCALAR integ3 =
1006 (qr_dual_edge_[2]->Weights().transpose().array() * psidd.array() *
1007 nodevals.segment(3 + Ns0 + Ns1, Ns2).array())
1008 .sum();
1010 dofs[1 + i + (edge_degrees_[0] + edge_degrees_[1] - 2) - 3] =
1011 (P1 * nodevals[0] - P0 * nodevals[2] - integ3) * (2 * i - 1);
1012 } else {
1013 dofs[4 - i +
1014 (edge_degrees_[0] + edge_degrees_[1] + edge_degrees_[2] - 3) - 3] =
1015 (P1 * nodevals[2] - P0 * nodevals[0] - integ3) * (2 * i - 1);
1016 }
1017 }
1018
1019 return dofs;
1020 }
1021
1022 /*
1023 * @brief Compute the non-orthogonalized DOFs of the face bubbles from some
1024 * given nodal evaluations
1025 */
1026 [[nodiscard]] Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> NodalValuesToFaceDofs(
1027 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> &nodevals) const {
1028 const auto Ns0 = edge_degrees_[0] > 1 ? qr_dual_edge_[0]->NumPoints() : 0;
1029 const auto Ns1 = edge_degrees_[1] > 1 ? qr_dual_edge_[1]->NumPoints() : 0;
1030 const auto Ns2 = edge_degrees_[2] > 1 ? qr_dual_edge_[2]->NumPoints() : 0;
1031 const lf::base::size_type Ns = Ns0 + Ns1 + Ns2;
1033 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> dofs(NumRefShapeFunctions(0));
1034 if (interior_degree_ > 2) {
1035 unsigned idx = 0;
1036 const Eigen::Matrix<double, 1, Eigen::Dynamic> xnorm =
1037 (qr_dual_tria_->Points().row(0).array() /
1038 qr_dual_tria_->Points().row(1).array().unaryExpr([&](double y) {
1039 return 1 - y;
1040 })).matrix();
1041 // We need to flip the local coordinates in case the
1042 // relative orientation is negative
1043 const Eigen::Matrix<double, 1, Eigen::Dynamic> xnorm_adj =
1045 ? xnorm
1046 : Eigen::RowVectorXd::Ones(Nt) - xnorm;
1047 const Eigen::Matrix<double, 1, Eigen::Dynamic> y =
1048 qr_dual_tria_->Points().row(1);
1049 // i is the degree of the edge function
1050 for (unsigned i = 0; i < interior_degree_ - 2; ++i) {
1051 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> ypow =
1052 y.array()
1053 .unaryExpr(
1054 [&](double y) -> SCALAR { return std::pow(1 - y, i); })
1055 .matrix();
1056 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> ypowp1 =
1057 y.array()
1058 .unaryExpr(
1059 [&](double y) -> SCALAR { return std::pow(1 - y, i + 1); })
1060 .matrix();
1061 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> psidd =
1062 xnorm_adj.unaryExpr(
1063 [&](double x) -> SCALAR { return LegendreDx(i + 1, x); });
1064 // j is the degree of the blending Jacobi polynomial
1065 for (unsigned j = 0; j < interior_degree_ - i - 2; ++j) {
1066 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> jacdd =
1067 qr_dual_tria_->Points().row(1).unaryExpr([&](double y) -> SCALAR {
1068 return JacobiDx(j, 2 * i + 4, y);
1069 });
1070 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> jacd =
1071 qr_dual_tria_->Points().row(1).unaryExpr([&](double y) -> SCALAR {
1072 return IJacobiDx(j + 1, 2 * i + 4, y);
1073 });
1074 dofs[idx] =
1075 (qr_dual_tria_->Weights().transpose().array() *
1076 nodevals.block(0, 3 + Ns, 1, Nt).array() * psidd.array() *
1077 (ypowp1.array() * jacdd.array() -
1078 (2 * i + 4) * ypow.array() * jacd.array()))
1079 .sum();
1080 dofs[idx] *=
1081 2 * i + 3; // Normalization factor for the Legendre polynomial
1082 dofs[idx] *= 2 * j + 2 * i +
1083 5; // Normalization factor for the Jacobi Polynomial
1084 ++idx;
1085 }
1086 }
1087 }
1088 return dofs;
1089 }
1090};
1091
1092// clang-format off
1139// clang-format on
1140template <typename SCALAR>
1142 public:
1144 FeHierarchicQuad(FeHierarchicQuad &&) noexcept = default;
1145 FeHierarchicQuad &operator=(const FeHierarchicQuad &) = default;
1146 FeHierarchicQuad &operator=(FeHierarchicQuad &&) noexcept = default;
1147 ~FeHierarchicQuad() override = default;
1148
1149 FeHierarchicQuad(unsigned interior_degree,
1150 std::array<unsigned, 4> edge_degrees,
1151 const quad::QuadRuleCache &qr_cache,
1152 std::span<const lf::mesh::Orientation> rel_orient)
1153 : ScalarReferenceFiniteElement<SCALAR>(),
1154 interior_degree_(interior_degree),
1155 edge_degrees_(edge_degrees),
1157 2 * (edge_degrees_[0] - 1)),
1158 &qr_cache.Get(lf::base::RefEl::kSegment(),
1159 2 * (edge_degrees_[1] - 1)),
1160 &qr_cache.Get(lf::base::RefEl::kSegment(),
1161 2 * (edge_degrees_[2] - 1)),
1162 &qr_cache.Get(lf::base::RefEl::kSegment(),
1163 2 * (edge_degrees_[3] - 1))}),
1164 rel_orient_(rel_orient),
1167 qr_cache) {
1168 LF_ASSERT_MSG(interior_degree_ >= 0, "illegal interior degree.");
1169 LF_ASSERT_MSG(edge_degrees_[0] >= 0, "illegal degree for edge 0");
1170 LF_ASSERT_MSG(edge_degrees_[1] >= 0, "illegal degree for edge 1");
1171 LF_ASSERT_MSG(edge_degrees_[2] >= 0, "illegal degree for edge 2");
1172 LF_ASSERT_MSG(edge_degrees_[3] >= 0, "illegal degree for edge 3");
1173 }
1174
1175 [[nodiscard]] lf::base::RefEl RefEl() const override {
1176 return lf::base::RefEl::kQuad();
1177 }
1178
1179 [[nodiscard]] unsigned Degree() const override {
1180 return std::max({interior_degree_, edge_degrees_[0], edge_degrees_[1],
1182 }
1183
1188 [[nodiscard]] lf::base::size_type NumRefShapeFunctions() const override {
1189 return 4 + NumRefShapeFunctions(0) + NumRefShapeFunctions(1, 0) +
1192 }
1193
1200 dim_t codim) const override {
1201 switch (codim) {
1202 case 0:
1203 return (interior_degree_ - 1) * (interior_degree_ - 1);
1204 case 1:
1205 LF_VERIFY_MSG(
1206 edge_degrees_[0] == edge_degrees_[1] &&
1207 edge_degrees_[0] == edge_degrees_[2] &&
1209 "You cannot call this method with codim=1 if the edges of the "
1210 "quad have a differing number of shape functions.");
1211 return edge_degrees_[0] - 1;
1212 case 2:
1213 return 1;
1214 default:
1215 LF_ASSERT_MSG(false, "Illegal codim " << codim);
1216 // Silence compiler warnings
1217 return 0;
1218 }
1219 }
1220
1221 // clang-format off
1227 // clang-format on
1229 dim_t codim, sub_idx_t subidx) const override {
1230 switch (codim) {
1231 case 0:
1232 LF_ASSERT_MSG(subidx == 0, "illegal codim and subidex.");
1233 return NumRefShapeFunctions(0);
1234 case 1:
1235 LF_ASSERT_MSG(subidx >= 0 && subidx < 4, "illegal codim and subidx.");
1236 return edge_degrees_[subidx] - 1;
1237 case 2:
1238 LF_ASSERT_MSG(subidx >= 0 && subidx < 4, "illegal codim and subidx.");
1239 return 1;
1240 default:
1241 LF_VERIFY_MSG(false, "Illegal codim " << codim);
1242 // Silence compiler warnings
1243 return 0;
1244 }
1245 }
1246
1247 [[nodiscard]] Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic>
1248 EvalReferenceShapeFunctions(const Eigen::MatrixXd &refcoords) const override {
1249 Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> result(
1250 NumRefShapeFunctions(), refcoords.cols());
1251 // Compute the 1D shape functions at the x and y coordinates
1252 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1d_x =
1253 fe1d_.EvalReferenceShapeFunctions(refcoords.row(0));
1254 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1d_y =
1255 fe1d_.EvalReferenceShapeFunctions(refcoords.row(1));
1256 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1df_x =
1257 fe1d_.EvalReferenceShapeFunctions(
1258 Eigen::RowVectorXd::Constant(refcoords.cols(), 1) -
1259 refcoords.row(0));
1260 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1df_y =
1261 fe1d_.EvalReferenceShapeFunctions(
1262 Eigen::RowVectorXd::Constant(refcoords.cols(), 1) -
1263 refcoords.row(1));
1264 // Get the basis functions associated with the vertices
1265 result.row(0) = (sf1d_x.row(0).array() * sf1d_y.row(0).array()).matrix();
1266 result.row(1) = (sf1d_x.row(1).array() * sf1d_y.row(0).array()).matrix();
1267 result.row(2) = (sf1d_x.row(1).array() * sf1d_y.row(1).array()).matrix();
1268 result.row(3) = (sf1d_x.row(0).array() * sf1d_y.row(1).array()).matrix();
1269
1270 int current_dof = 4;
1271
1272 // Get the basis functions associated with the first edge
1273 // as a tensor product of 1D basis functions
1274 for (int i = 0; i < edge_degrees_[0] - 1; ++i) {
1276 result.row(current_dof + i) =
1277 (sf1d_x.row(2 + i).array() * sf1d_y.row(0).array()).matrix();
1278 } else {
1279 result.row(current_dof - 2 + edge_degrees_[0] - i) =
1280 (sf1df_x.row(2 + i).array() * sf1d_y.row(0).array()).matrix();
1281 }
1282 }
1283 current_dof += edge_degrees_[0] - 1;
1284 // Get the basis functions associated with the second edge
1285 // as a tensor product of 1D basis functions
1286 for (int i = 0; i < edge_degrees_[1] - 1; ++i) {
1288 result.row(current_dof + i) =
1289 (sf1d_x.row(1).array() * sf1d_y.row(2 + i).array()).matrix();
1290 } else {
1291 result.row(current_dof - 2 + edge_degrees_[1] - i) =
1292 (sf1d_x.row(1).array() * sf1df_y.row(2 + i).array()).matrix();
1293 }
1294 }
1295 current_dof += edge_degrees_[1] - 1;
1296 // Get the basis functions associated with the third edge
1297 // as a tensor product of 1D basis functions
1298 for (int i = 0; i < edge_degrees_[2] - 1; ++i) {
1300 result.row(current_dof + i) =
1301 (sf1df_x.row(2 + i).array() * sf1d_y.row(1).array()).matrix();
1302 } else {
1303 result.row(current_dof + edge_degrees_[2] - 2 - i) =
1304 (sf1d_x.row(2 + i).array() * sf1d_y.row(1).array()).matrix();
1305 }
1306 }
1307 current_dof += edge_degrees_[2] - 1;
1308 // Get the basis functions associated with the fourth edge
1309 // as a tensor product of 1D basis functions
1310 for (int i = 0; i < edge_degrees_[3] - 1; ++i) {
1312 result.row(current_dof + i) =
1313 (sf1d_x.row(0).array() * sf1df_y.row(2 + i).array()).matrix();
1314 } else {
1315 result.row(current_dof + edge_degrees_[3] - 2 - i) =
1316 (sf1d_x.row(0).array() * sf1d_y.row(2 + i).array()).matrix();
1317 }
1318 }
1319 current_dof += edge_degrees_[3] - 1;
1320 // Get the basis functions associated with the interior of the quad
1321 // as a tensor product of 1D basis functions
1322 for (int i = 0; i < interior_degree_ - 1; ++i) {
1323 for (int j = 0; j < interior_degree_ - 1; ++j) {
1324 result.row(current_dof++) =
1325 (sf1d_x.row(j + 2).array() * sf1d_y.row(i + 2).array()).matrix();
1326 }
1327 }
1328 LF_ASSERT_MSG(current_dof == result.rows(),
1329 "Not all rows have been filled.");
1330 return result;
1331 }
1332
1333 [[nodiscard]] Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic>
1334 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
1336 const Eigen::MatrixXd &refcoords) const override {
1337 Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> result(
1338 NumRefShapeFunctions(), 2 * refcoords.cols());
1339 // Compute the gradient of the 1D shape functions at the x and y coordinates
1340 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1d_x =
1341 fe1d_.EvalReferenceShapeFunctions(refcoords.row(0));
1342 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1d_y =
1343 fe1d_.EvalReferenceShapeFunctions(refcoords.row(1));
1344 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1d_dx =
1345 fe1d_.GradientsReferenceShapeFunctions(refcoords.row(0));
1346 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1d_dy =
1347 fe1d_.GradientsReferenceShapeFunctions(refcoords.row(1));
1348 // Compute the gradient of the flipped 1D shape functions at the x and y
1349 // coordinates
1350 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1df_x =
1351 fe1d_.EvalReferenceShapeFunctions(
1352 Eigen::RowVectorXd::Constant(refcoords.cols(), 1) -
1353 refcoords.row(0));
1354 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1df_y =
1355 fe1d_.EvalReferenceShapeFunctions(
1356 Eigen::RowVectorXd::Constant(refcoords.cols(), 1) -
1357 refcoords.row(1));
1358 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1df_dx =
1359 fe1d_.GradientsReferenceShapeFunctions(
1360 Eigen::RowVectorXd::Constant(refcoords.cols(), 1) -
1361 refcoords.row(0));
1362 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1df_dy =
1363 fe1d_.GradientsReferenceShapeFunctions(
1364 Eigen::RowVectorXd::Constant(refcoords.cols(), 1) -
1365 refcoords.row(1));
1366 for (Eigen::Index i = 0; i < refcoords.cols(); ++i) {
1367 // Get the gradient of the basis functions associated with the vertices
1368 result(0, 2 * i + 0) = sf1d_dx(0, i) * sf1d_y(0, i);
1369 result(0, 2 * i + 1) = sf1d_x(0, i) * sf1d_dy(0, i);
1370 result(1, 2 * i + 0) = sf1d_dx(1, i) * sf1d_y(0, i);
1371 result(1, 2 * i + 1) = sf1d_x(1, i) * sf1d_dy(0, i);
1372 result(2, 2 * i + 0) = sf1d_dx(1, i) * sf1d_y(1, i);
1373 result(2, 2 * i + 1) = sf1d_x(1, i) * sf1d_dy(1, i);
1374 result(3, 2 * i + 0) = sf1d_dx(0, i) * sf1d_y(1, i);
1375 result(3, 2 * i + 1) = sf1d_x(0, i) * sf1d_dy(1, i);
1376
1377 int current_dof = 4;
1378 // Get the basis functions associated with the first edge
1379 for (int j = 0; j < edge_degrees_[0] - 1; ++j) {
1381 result(current_dof + j, 2 * i + 0) = sf1d_dx(2 + j, i) * sf1d_y(0, i);
1382 result(current_dof + j, 2 * i + 1) = sf1d_x(2 + j, i) * sf1d_dy(0, i);
1383 } else {
1384 result(current_dof + edge_degrees_[0] - 2 - j, 2 * i + 0) =
1385 -sf1df_dx(2 + j, i) * sf1d_y(0, i);
1386 result(current_dof + edge_degrees_[0] - 2 - j, 2 * i + 1) =
1387 sf1df_x(2 + j, i) * sf1d_dy(0, i);
1388 }
1389 }
1390 current_dof += edge_degrees_[0] - 1;
1391 // Get the basis functions associated with the second edge
1392 for (int j = 0; j < edge_degrees_[1] - 1; ++j) {
1394 result(current_dof + j, 2 * i + 0) = sf1d_dx(1, i) * sf1d_y(2 + j, i);
1395 result(current_dof + j, 2 * i + 1) = sf1d_x(1, i) * sf1d_dy(2 + j, i);
1396 } else {
1397 result(current_dof + edge_degrees_[1] - 2 - j, 2 * i + 0) =
1398 sf1d_dx(1, i) * sf1df_y(2 + j, i);
1399 result(current_dof + edge_degrees_[1] - 2 - j, 2 * i + 1) =
1400 sf1d_x(1, i) * -sf1df_dy(2 + j, i);
1401 }
1402 }
1403 current_dof += edge_degrees_[1] - 1;
1404 // Get the basis functions associated with the third edge
1405 for (int j = 0; j < edge_degrees_[2] - 1; ++j) {
1407 result(current_dof + j, 2 * i + 0) =
1408 -sf1df_dx(2 + j, i) * sf1d_y(1, i);
1409 result(current_dof + j, 2 * i + 1) =
1410 sf1df_x(2 + j, i) * sf1d_dy(1, i);
1411 } else {
1412 result(current_dof + edge_degrees_[2] - 2 - j, 2 * i + 0) =
1413 sf1d_dx(2 + j, i) * sf1d_y(1, i);
1414 result(current_dof + edge_degrees_[2] - 2 - j, 2 * i + 1) =
1415 sf1d_x(2 + j, i) * sf1d_dy(1, i);
1416 }
1417 }
1418 current_dof += edge_degrees_[2] - 1;
1419 // Get the basis functions associated with the fourth edge
1420 for (int j = 0; j < edge_degrees_[3] - 1; ++j) {
1422 result(current_dof + j, 2 * i + 0) =
1423 sf1d_dx(0, i) * sf1df_y(2 + j, i);
1424 result(current_dof + j, 2 * i + 1) =
1425 sf1d_x(0, i) * -sf1df_dy(2 + j, i);
1426 } else {
1427 result(current_dof + edge_degrees_[3] - 2 - j, 2 * i + 0) =
1428 sf1d_dx(0, i) * sf1d_y(2 + j, i);
1429 result(current_dof + edge_degrees_[3] - 2 - j, 2 * i + 1) =
1430 sf1d_x(0, i) * sf1d_dy(2 + j, i);
1431 }
1432 }
1433 current_dof += edge_degrees_[3] - 1;
1434 // Get the basis functions associated with the interior of the quad
1435 for (int j = 0; j < interior_degree_ - 1; ++j) {
1436 for (int k = 0; k < interior_degree_ - 1; ++k) {
1437 result(current_dof, 2 * i + 0) = sf1d_dx(k + 2, i) * sf1d_y(j + 2, i);
1438 result(current_dof++, 2 * i + 1) =
1439 sf1d_x(k + 2, i) * sf1d_dy(j + 2, i);
1440 }
1441 }
1442 }
1443 return result;
1444 }
1445
1452 [[nodiscard]] Eigen::MatrixXd EvaluationNodes() const override {
1453 const base::size_type Ne0 =
1454 edge_degrees_[0] > 1 ? qr_dual_edge_[0]->NumPoints() : 0;
1455 const base::size_type Ne1 =
1456 edge_degrees_[1] > 1 ? qr_dual_edge_[1]->NumPoints() : 0;
1457 const base::size_type Ne2 =
1458 edge_degrees_[2] > 1 ? qr_dual_edge_[2]->NumPoints() : 0;
1459 const base::size_type Ne3 =
1460 edge_degrees_[3] > 1 ? qr_dual_edge_[3]->NumPoints() : 0;
1461 auto Nq = interior_degree_ > 1 ? fe1d_.NumEvaluationNodes() : 0;
1462
1463 Eigen::MatrixXd nodes(2, 4 + Ne0 + Ne1 + Ne2 + Ne3 + Nq * Nq);
1464 // Add the vertices
1465 nodes(0, 0) = 0;
1466 nodes(1, 0) = 0;
1467 nodes(0, 1) = 1;
1468 nodes(1, 1) = 0;
1469 nodes(0, 2) = 1;
1470 nodes(1, 2) = 1;
1471 nodes(0, 3) = 0;
1472 nodes(1, 3) = 1;
1473 if (edge_degrees_[0] > 1) {
1474 // Add the quadrature points on the first edge
1476 nodes.block(0, 4, 1, Ne0) = qr_dual_edge_[0]->Points();
1477 } else {
1478 nodes.block(0, 4, 1, Ne0) =
1479 Eigen::RowVectorXd::Ones(Ne0) - qr_dual_edge_[0]->Points();
1480 }
1481 nodes.block(1, 4, 1, Ne0).setZero();
1482 }
1483
1484 if (edge_degrees_[1] > 1) {
1485 // Add the quadrature points on the second edge
1486 nodes.block(0, 4 + Ne0, 1, Ne1).setOnes();
1488 nodes.block(1, 4 + Ne0, 1, Ne1) = qr_dual_edge_[1]->Points();
1489 } else {
1490 nodes.block(1, 4 + Ne0, 1, Ne1) =
1491 Eigen::RowVectorXd::Ones(Ne1) - qr_dual_edge_[1]->Points();
1492 }
1493 }
1494 if (edge_degrees_[2] > 1) {
1495 // Add the quadrature points on the third edge
1497 nodes.block(0, 4 + Ne0 + Ne1, 1, Ne2) =
1498 Eigen::RowVectorXd::Ones(Ne2) - qr_dual_edge_[2]->Points();
1499 } else {
1500 nodes.block(0, 4 + Ne0 + Ne1, 1, Ne2) = qr_dual_edge_[2]->Points();
1501 }
1502 nodes.block(1, 4 + Ne0 + Ne1, 1, Ne2).setOnes();
1503 }
1504 if (edge_degrees_[3] > 1) {
1505 // Add the quadrature points on the fourth edge
1506 nodes.block(0, 4 + Ne0 + Ne1 + Ne2, 1, Ne3).setZero();
1508 nodes.block(1, 4 + Ne0 + Ne1 + Ne2, 1, Ne3) =
1509 Eigen::RowVectorXd::Ones(Ne3) - qr_dual_edge_[3]->Points();
1510 } else {
1511 nodes.block(1, 4 + Ne0 + Ne1 + Ne2, 1, Ne3) =
1512 qr_dual_edge_[3]->Points();
1513 }
1514 }
1515 if (interior_degree_ > 1) {
1516 // Add the quadrature points for the face
1517 auto points = fe1d_.EvaluationNodes();
1518 for (lf::base::size_type i = 0; i < Nq; ++i) {
1519 nodes.block(0, 4 + Ne0 + Ne1 + Ne2 + Ne3 + i * Nq, 1, Nq) = points;
1520 nodes.block(1, 4 + Ne0 + Ne1 + Ne2 + Ne3 + i * Nq, 1, Nq)
1521 .setConstant(points(0, i));
1522 }
1523 }
1524 return nodes;
1525 }
1526
1530 [[nodiscard]] lf::base::size_type NumEvaluationNodes() const override {
1531 const base::size_type Ne0 =
1532 edge_degrees_[0] > 1 ? qr_dual_edge_[0]->NumPoints() : 0;
1533 const base::size_type Ne1 =
1534 edge_degrees_[1] > 1 ? qr_dual_edge_[1]->NumPoints() : 0;
1535 const base::size_type Ne2 =
1536 edge_degrees_[2] > 1 ? qr_dual_edge_[2]->NumPoints() : 0;
1537 const base::size_type Ne3 =
1538 edge_degrees_[3] > 1 ? qr_dual_edge_[3]->NumPoints() : 0;
1539 const lf::base::size_type Nq =
1540 interior_degree_ > 1 ? fe1d_.NumEvaluationNodes() : 0;
1541 return 4 + Ne0 + Ne1 + Ne2 + Ne3 + Nq * Nq;
1542 }
1543
1544 // NOLINTNEXTLINE(readability-function-cognitive-complexity)
1545 [[nodiscard]] Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> NodalValuesToDofs(
1546 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> &nodevals) const override {
1547 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> dofs(NumRefShapeFunctions());
1548 const base::size_type Ne0 =
1549 edge_degrees_[0] > 1 ? qr_dual_edge_[0]->NumPoints() : 0;
1550 const base::size_type Ne1 =
1551 edge_degrees_[1] > 1 ? qr_dual_edge_[1]->NumPoints() : 0;
1552 const base::size_type Ne2 =
1553 edge_degrees_[2] > 1 ? qr_dual_edge_[2]->NumPoints() : 0;
1554 const base::size_type Ne3 =
1555 edge_degrees_[3] > 1 ? qr_dual_edge_[3]->NumPoints() : 0;
1556
1557 // Compute the basis function coefficients of the vertex basis functions
1558 dofs[0] = nodevals[0];
1559 dofs[1] = nodevals[1];
1560 dofs[2] = nodevals[2];
1561 dofs[3] = nodevals[3];
1562 // Compute the basis function coefficients on the edges
1563 // by applying the dual basis of the segment
1564 for (lf::base::size_type i = 2; i < Degree() + 1; ++i) {
1565 const SCALAR P0 = ILegendreDx(i, 0);
1566 const SCALAR P1 = ILegendreDx(i, 1);
1567
1568 // Compute the basis function coefficients for the first edge
1569 if (i <= edge_degrees_[0]) {
1570 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> psidd =
1571 qr_dual_edge_[0]->Points().unaryExpr(
1572 [&](double x) -> SCALAR { return LegendreDx(i - 1, x); });
1573 const SCALAR integ1 = (qr_dual_edge_[0]->Weights().transpose().array() *
1574 psidd.array() * nodevals.segment(4, Ne0).array())
1575 .sum();
1577 dofs[2 + i] =
1578 (P1 * nodevals[1] - P0 * nodevals[0] - integ1) * (2 * i - 1);
1579 } else {
1580 dofs[5 - i + (edge_degrees_[0] - 1)] =
1581 (P1 * nodevals[0] - P0 * nodevals[1] - integ1) * (2 * i - 1);
1582 }
1583 }
1584 if (i <= edge_degrees_[1]) {
1585 // Compute the basis function coefficients for the second edge
1586 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> psidd =
1587 qr_dual_edge_[1]->Points().unaryExpr(
1588 [&](double x) -> SCALAR { return LegendreDx(i - 1, x); });
1589 const SCALAR integ2 =
1590 (qr_dual_edge_[1]->Weights().transpose().array() * psidd.array() *
1591 nodevals.segment(4 + Ne0, Ne1).array())
1592 .sum();
1594 dofs[2 + i + (edge_degrees_[0] - 1)] =
1595 (P1 * nodevals[2] - P0 * nodevals[1] - integ2) * (2 * i - 1);
1596 } else {
1597 dofs[5 - i + (edge_degrees_[0] + edge_degrees_[1] - 2)] =
1598 (P1 * nodevals[1] - P0 * nodevals[2] - integ2) * (2 * i - 1);
1599 }
1600 }
1601 if (i <= edge_degrees_[2]) {
1602 // Compute the basis function coefficients for the third edge
1603 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> psidd =
1604 qr_dual_edge_[2]->Points().unaryExpr(
1605 [&](double x) -> SCALAR { return LegendreDx(i - 1, x); });
1606 const SCALAR integ3 =
1607 (qr_dual_edge_[2]->Weights().transpose().array() * psidd.array() *
1608 nodevals.segment(4 + Ne0 + Ne1, Ne2).array())
1609 .sum();
1611 dofs[2 + i + (edge_degrees_[0] + edge_degrees_[1] - 2)] =
1612 (P1 * nodevals[3] - P0 * nodevals[2] - integ3) * (2 * i - 1);
1613 } else {
1614 dofs[5 - i +
1615 (edge_degrees_[0] + edge_degrees_[1] + edge_degrees_[2] - 3)] =
1616 (P1 * nodevals[2] - P0 * nodevals[3] - integ3) * (2 * i - 1);
1617 }
1618 }
1619 if (i <= edge_degrees_[3]) {
1620 // Compute the basis function coefficients for the fourth edge
1621 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> psidd =
1622 qr_dual_edge_[3]->Points().unaryExpr(
1623 [&](double x) -> SCALAR { return LegendreDx(i - 1, x); });
1624 const SCALAR integ4 =
1625 (qr_dual_edge_[3]->Weights().transpose().array() * psidd.array() *
1626 nodevals.segment(4 + Ne0 + Ne1 + Ne2, Ne3).array())
1627 .sum();
1629 dofs[2 + i +
1630 (edge_degrees_[0] + edge_degrees_[1] + edge_degrees_[2] - 3)] =
1631 (P1 * nodevals[0] - P0 * nodevals[3] - integ4) * (2 * i - 1);
1632 } else {
1633 dofs[5 - i +
1635 edge_degrees_[3] - 4)] =
1636 (P1 * nodevals[3] - P0 * nodevals[0] - integ4) * (2 * i - 1);
1637 }
1638 }
1639 }
1640 if (interior_degree_ > 1) {
1641 // Compute the basis function coefficients for the face bubbles
1642 auto Nq = fe1d_.NumEvaluationNodes();
1643 Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> dof_temp(
1644 Nq, interior_degree_ - 1);
1645 for (unsigned i = 0; i < Nq; ++i) {
1646 dof_temp.row(i) = fe1d_
1647 .NodalValuesToDofs(nodevals.segment(
1648 4 + Ne0 + Ne1 + Ne2 + Ne3 + Nq * i, Nq))
1649 .segment(2, interior_degree_ - 1);
1650 }
1651 Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> dof_temp2(
1653 for (unsigned i = 0; i < interior_degree_ - 1; ++i) {
1654 dofs.segment(edge_degrees_[0] + edge_degrees_[1] + edge_degrees_[2] +
1655 edge_degrees_[3] + i * (interior_degree_ - 1),
1656 interior_degree_ - 1) = dof_temp2.row(i) =
1657 fe1d_.NodalValuesToDofs(dof_temp.col(i))
1658 .segment(2, interior_degree_ - 1);
1659 }
1660 dofs.segment(edge_degrees_[0] + edge_degrees_[1] + edge_degrees_[2] +
1661 edge_degrees_[3],
1662 (interior_degree_ - 1) * (interior_degree_ - 1)) =
1663 Eigen::Map<Eigen::Matrix<SCALAR, 1, Eigen::Dynamic>>(
1664 dof_temp2.data(), 1,
1665 (interior_degree_ - 1) * (interior_degree_ - 1));
1666 }
1667 return dofs;
1668 }
1669
1670 private:
1672 std::array<unsigned, 4> edge_degrees_;
1673 std::array<const quad::QuadRule *, 4> qr_dual_edge_;
1675 fe1d_; // degree = max(interior_degree_, edge_degrees_)
1676 std::span<const lf::mesh::Orientation> rel_orient_;
1677};
1678
1679} // end namespace lf::fe
1680
1681#endif // LF_USCALFE_HP_FE_H_
Represents a reference element with all its properties.
Definition ref_el.h:109
static constexpr RefEl kSegment()
Returns the (1-dimensional) reference segment.
Definition ref_el.h:153
static constexpr RefEl kTria()
Returns the reference triangle.
Definition ref_el.h:161
static constexpr RefEl kQuad()
Returns the reference quadrilateral.
Definition ref_el.h:169
Hierarchic Finite Elements of arbitrary degree on quadrilaterals.
FeHierarchicQuad(const FeHierarchicQuad &)=default
lf::base::size_type NumRefShapeFunctions() const override
The local shape functions.
FeHierarchicQuad(FeHierarchicQuad &&) noexcept=default
FeHierarchicSegment< SCALAR > fe1d_
lf::base::size_type NumRefShapeFunctions(dim_t codim) const override
One shape function for each vertex, p-1 shape functions on the edges and (p-1)^2 shape functions on t...
Eigen::Matrix< SCALAR, Eigen::Dynamic, Eigen::Dynamic > EvalReferenceShapeFunctions(const Eigen::MatrixXd &refcoords) const override
Evaluation of all reference shape functions in a number of points.
Eigen::MatrixXd EvaluationNodes() const override
Evaluation nodes are the vertices, the points of a quadrature rule on each edge and the points of a q...
lf::base::RefEl RefEl() const override
Tells the type of reference cell underlying the parametric finite element.
lf::base::size_type NumRefShapeFunctions(dim_t codim, sub_idx_t subidx) const override
One shape function for each vertex, p-1 shape functions on the edges and (p-1)^2 shape functions on t...
Eigen::Matrix< SCALAR, Eigen::Dynamic, Eigen::Dynamic > GradientsReferenceShapeFunctions(const Eigen::MatrixXd &refcoords) const override
Computation of the gradients of all reference shape functions in a number of points.
unsigned Degree() const override
Request the maximal polynomial degree of the basis functions in this finite element.
lf::base::size_type NumEvaluationNodes() const override
Tell the number of evaluation (interpolation) nodes.
std::array< unsigned, 4 > edge_degrees_
std::array< const quad::QuadRule *, 4 > qr_dual_edge_
std::span< const lf::mesh::Orientation > rel_orient_
Eigen::Matrix< SCALAR, 1, Eigen::Dynamic > NodalValuesToDofs(const Eigen::Matrix< SCALAR, 1, Eigen::Dynamic > &nodevals) const override
Computes the linear combination of reference shape functions matching function values at evaluation n...
Hierarchic Finite Elements of arbitrary degree on segments.
lf::base::RefEl RefEl() const override
Tells the type of reference cell underlying the parametric finite element.
Eigen::Matrix< SCALAR, Eigen::Dynamic, Eigen::Dynamic > GradientsReferenceShapeFunctions(const Eigen::MatrixXd &refcoords) const override
Computation of the gradients of all reference shape functions in a number of points.
lf::base::size_type NumRefShapeFunctions() const override
The local shape functions.
lf::base::size_type NumRefShapeFunctions(dim_t codim, sub_idx_t subidx) const override
One shape function for each vertex, p-1 shape functions for the segment.
lf::base::size_type NumRefShapeFunctions(dim_t codim) const override
One shape function for each vertex, p-1 shape functions for the segment.
const lf::quad::QuadRule * qr_dual_
FeHierarchicSegment(const FeHierarchicSegment &)=default
FeHierarchicSegment(FeHierarchicSegment &&) noexcept=default
Eigen::Matrix< SCALAR, 1, Eigen::Dynamic > NodalValuesToDofs(const Eigen::Matrix< SCALAR, 1, Eigen::Dynamic > &nodevals) const override
Maps function evaluations to basis function coefficients.
lf::base::size_type NumEvaluationNodes() const override
Tell the number of evaluation (interpolation) nodes.
unsigned Degree() const override
Request the maximal polynomial degree of the basis functions in this finite element.
Eigen::MatrixXd EvaluationNodes() const override
Evaluation nodes are the endpoints of the segment and the Chebyshev nodes of degree p-1 on the segmen...
Eigen::Matrix< SCALAR, Eigen::Dynamic, Eigen::Dynamic > EvalReferenceShapeFunctions(const Eigen::MatrixXd &refcoords) const override
Evaluation of all reference shape functions in a number of points.
Hierarchic Finite Elements of arbitrary degree on triangles.
lf::base::RefEl RefEl() const override
Tells the type of reference cell underlying the parametric finite element.
Eigen::Matrix< SCALAR, Eigen::Dynamic, Eigen::Dynamic > EvalReferenceShapeFunctions(const Eigen::MatrixXd &refcoords) const override
Evaluation of all reference shape functions in a number of points.
const lf::quad::QuadRule * qr_dual_tria_
std::array< const quad::QuadRule *, 3 > qr_dual_edge_
std::span< const lf::mesh::Orientation > rel_orient_
Eigen::Matrix< SCALAR, 1, Eigen::Dynamic > NodalValuesToEdgeDofs(const Eigen::Matrix< SCALAR, 1, Eigen::Dynamic > &nodevals) const
FeHierarchicTria(const FeHierarchicTria &)=default
Eigen::MatrixXd EvaluationNodes() const override
Evaluation nodes are the vertices, the points of a Gauss quadrature rule for each edge and the points...
Eigen::Matrix< SCALAR, 1, Eigen::Dynamic > NodalValuesToFaceDofs(const Eigen::Matrix< SCALAR, 1, Eigen::Dynamic > &nodevals) const
Eigen::Matrix< SCALAR, 1, Eigen::Dynamic > NodalValuesToDofs(const Eigen::Matrix< SCALAR, 1, Eigen::Dynamic > &nodevals) const override
Computes the linear combination of reference shape functions matching function values at evaluation n...
lf::base::size_type NumRefShapeFunctions(dim_t codim, sub_idx_t subidx) const override
One shape function for each vertex, p-1 shape functions on the edges and max(0, (p-2)*(p-1)/2) shape ...
lf::base::size_type NumRefShapeFunctions() const override
The local shape functions.
lf::base::size_type NumRefShapeFunctions(dim_t codim) const override
One shape function for each vertex, p-1 shape functions on the edges and max(0, (p-2)*(p-1)/2) shape ...
Eigen::Matrix< SCALAR, Eigen::Dynamic, Eigen::Dynamic > GradientsReferenceShapeFunctions(const Eigen::MatrixXd &refcoords) const override
Computation of the gradients of all reference shape functions in a number of points.
unsigned Degree() const override
Request the maximal polynomial degree of the basis functions in this finite element.
Eigen::Matrix< SCALAR, 1, Eigen::Dynamic > NodalValuesToVertexDofs(const Eigen::Matrix< SCALAR, 1, Eigen::Dynamic > &nodevals) const
FeHierarchicTria(FeHierarchicTria &&) noexcept=default
lf::base::size_type NumEvaluationNodes() const override
Tell the number of evaluation (interpolation) nodes.
std::array< unsigned, 3 > edge_degrees_
Interface class for parametric scalar valued finite elements.
Represents a Quadrature Rule over one of the Reference Elements.
Definition quad_rule.h:58
const Eigen::VectorXd & Weights() const
All quadrature weights as a vector.
Definition quad_rule.h:152
const Eigen::MatrixXd & Points() const
All quadrature points as column vectors.
Definition quad_rule.h:145
base::size_type NumPoints() const
Return the total number of quadrature points (num of columns of points/weights)
Definition quad_rule.h:158
unsigned int size_type
general type for variables related to size of arrays
Definition types.h:20
Collects data structures and algorithms designed for scalar finite element methods primarily meant fo...
Definition fe.h:47
double Legendre(unsigned n, double x, double t)
computes the n-th degree scaled Legendre Polynomial
double ILegendreDx(unsigned n, double x, double t)
Computes .
double IJacobiDx(unsigned n, double alpha, double x)
Computes the derivative of the n-th integrated scaled Jacobi polynomial.
double ILegendre(unsigned n, double x, double t)
computes the integral of the (n-1)-th degree scaled Legendre Polynomial
double Jacobi(unsigned n, double alpha, double beta, double x)
Computes the n-th degree shifted Jacobi polynomial.
lf::assemble::dim_t dim_t
Definition fe.h:56
lf::base::sub_idx_t sub_idx_t
Definition fe.h:60
double IJacobi(unsigned n, double alpha, double x)
Evaluate the integral of the (n-1)-th degree Jacobi Polynomial for .
double JacobiDx(unsigned n, double alpha, double x)
Computes the derivative of the n-th degree Jacobi Polynomial for .
double ILegendreDt(unsigned n, double x, double t)
Computes .
double LegendreDx(unsigned n, double x, double t)
Computes the derivative of the n-th degree scaled Legendre polynomial.
Definition assemble.h:31