LehrFEM++ 1.0.0
A simple Finite Element Library for teaching
hierarchic_fe.h
1#ifndef LF_USCALFE_HP_FE_H_
2#define LF_USCALFE_HP_FE_H_
3
12#define _USE_MATH_DEFINES
13
14#include <lf/assemble/assemble.h>
15#include <lf/mesh/mesh.h>
16#include <lf/quad/quad.h>
17
18#include <cmath>
19#include <memory>
20#include <vector>
21
22#include "fe_point.h"
23#include "scalar_reference_finite_element.h"
24
25namespace lf::fe {
26
41double legendre(unsigned n, double x, double t = 1);
42
58double ilegendre(unsigned n, double x, double t = 1);
59
69double ilegendre_dx(unsigned n, double x, double t = 1);
70
84double ilegendre_dt(unsigned n, double x, double t = 1);
85
99double legendre_dx(unsigned n, double x, double t = 1);
100
123double jacobi(unsigned n, double alpha, double beta, double x);
124
132double jacobi(unsigned n, double alpha, double x);
133
155double ijacobi(unsigned n, double alpha, double x);
156
167double ijacobi_dx(unsigned n, double alpha, double x);
168
181double jacobi_dx(unsigned n, double alpha, double x);
182
221template <typename SCALAR>
223 public:
226 FeHierarchicSegment &operator=(const FeHierarchicSegment &) = default;
227 FeHierarchicSegment &operator=(FeHierarchicSegment &&) noexcept = default;
228 ~FeHierarchicSegment() override = default;
229
230 FeHierarchicSegment(unsigned degree, const quad::QuadRuleCache &qr_cache)
232 degree_(degree),
233 qr_dual_(&qr_cache.Get(RefEl(), 2 * (degree - 1))) {}
234
235 [[nodiscard]] lf::base::RefEl RefEl() const override {
237 }
238
239 [[nodiscard]] unsigned Degree() const override { return degree_; }
240
245 [[nodiscard]] lf::base::size_type NumRefShapeFunctions() const override {
246 return degree_ + 1;
247 }
248
255 dim_t codim) const override {
256 LF_ASSERT_MSG(codim >= 0 && codim <= 1, "codim out of range.");
257 return codim == 0 ? degree_ - 1 : 2;
258 }
259
260 // clang-format off
266 // clang-format on
268 dim_t codim, sub_idx_t subidx) const override {
269 LF_ASSERT_MSG((codim == 0 && subidx == 0) || (codim == 1 && subidx < 2),
270 "codim/subidx out of range.");
271 return NumRefShapeFunctions(codim);
272 }
273
274 [[nodiscard]] Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic>
275 EvalReferenceShapeFunctions(const Eigen::MatrixXd &refcoords) const override {
276 LF_ASSERT_MSG(refcoords.rows() == 1, "refcoords must be a row vector");
277 Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> result(
278 NumRefShapeFunctions(), refcoords.cols());
279 // Get the shape functions associated with the vertices
280 result.row(0) =
281 refcoords.unaryExpr([&](double x) -> SCALAR { return 1 - x; });
282 result.row(1) = refcoords;
283 // Get the shape functions associated with the interior of the segment
284 for (int i = 0; i < degree_ - 1; ++i) {
285 result.row(i + 2) = refcoords.unaryExpr(
286 [&](double x) -> SCALAR { return ilegendre(i + 2, x); });
287 }
288 return result;
289 }
290
291 [[nodiscard]] Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic>
293 const Eigen::MatrixXd &refcoords) const override {
294 LF_ASSERT_MSG(refcoords.rows() == 1, "refcoords must be a row vector");
295 Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> result(
296 NumRefShapeFunctions(), refcoords.cols());
297 // Get the gradient of the shape functions associated with the vertices
298 result.row(0) =
299 refcoords.unaryExpr([&](double /*x*/) -> SCALAR { return -1; });
300 result.row(1) =
301 refcoords.unaryExpr([&](double /*x*/) -> SCALAR { return 1; });
302 // Get the shape functions associated with the interior of the segment
303 for (int i = 0; i < degree_ - 1; ++i) {
304 result.row(i + 2) = refcoords.unaryExpr(
305 [&](double x) -> SCALAR { return ilegendre_dx(i + 2, x); });
306 }
307 return result;
308 }
309
315 [[nodiscard]] Eigen::MatrixXd EvaluationNodes() const override {
316 // First two nodes are vertices of the segment
317 Eigen::MatrixXd nodes(1, NumEvaluationNodes());
318 nodes(0, 0) = 0;
319 nodes(0, 1) = 1;
320 // The other nodes are quadrature points
321 nodes.block(0, 2, 1, qr_dual_->NumPoints()) = qr_dual_->Points();
322 return nodes;
323 }
324
328 [[nodiscard]] lf::base::size_type NumEvaluationNodes() const override {
329 return qr_dual_->NumPoints() + 2;
330 }
331
345 [[nodiscard]] Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> NodalValuesToDofs(
346 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> &nodevals) const override {
347 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> dofs(NumRefShapeFunctions());
348 // Compute the first and second basis function coefficients
349 dofs[0] = nodevals[0];
350 dofs[1] = nodevals[1];
351 // Compute the other basis function coefficients
352 for (lf::base::size_type i = 2; i < NumRefShapeFunctions(); ++i) {
353 const SCALAR P0 = ilegendre_dx(i, 0);
354 const SCALAR P1 = ilegendre_dx(i, 1);
355 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> psidd =
356 qr_dual_->Points().unaryExpr(
357 [&](double x) -> SCALAR { return legendre_dx(i - 1, x); });
358 // Evaluate the integral from the dual basis
359 const SCALAR integ =
360 (qr_dual_->Weights().transpose().array() * psidd.array() *
361 nodevals.tail(qr_dual_->NumPoints()).array())
362 .sum();
363 // Compute the basis function coefficient
364 dofs[i] = (P1 * nodevals[1] - P0 * nodevals[0] - integ) * (2 * i - 1);
365 }
366 return dofs;
367 }
368
369 private:
370 unsigned degree_;
372};
373
449template <typename SCALAR>
450class FeHierarchicTria final : public ScalarReferenceFiniteElement<SCALAR> {
451 public:
453 FeHierarchicTria(FeHierarchicTria &&) noexcept = default;
454 FeHierarchicTria &operator=(const FeHierarchicTria &) = default;
455 FeHierarchicTria &operator=(FeHierarchicTria &&) noexcept = default;
456 ~FeHierarchicTria() override = default;
457
458 FeHierarchicTria(unsigned interior_degree,
459 std::array<unsigned, 3> edge_degrees,
460 const quad::QuadRuleCache &qr_cache,
461 nonstd::span<const lf::mesh::Orientation> rel_orient)
463 interior_degree_(interior_degree),
464 edge_degrees_(edge_degrees),
466 {&qr_cache.Get(base::RefEl::kSegment(), 2 * (edge_degrees_[0] - 1)),
467 &qr_cache.Get(base::RefEl::kSegment(), 2 * (edge_degrees_[1] - 1)),
468 &qr_cache.Get(base::RefEl::kSegment(),
469 2 * (edge_degrees_[2] - 1))}),
471 2 * (interior_degree_ - 1))),
472 rel_orient_(rel_orient) {
473 LF_ASSERT_MSG(interior_degree_ >= 0, "illegal interior degree.");
474 LF_ASSERT_MSG(edge_degrees_[0] >= 0, "illegal degree for edge 0");
475 LF_ASSERT_MSG(edge_degrees_[1] >= 0, "illegal degree for edge 1");
476 LF_ASSERT_MSG(edge_degrees_[2] >= 0, "illegal degree for edge 2");
477 }
478
479 [[nodiscard]] lf::base::RefEl RefEl() const override {
480 return lf::base::RefEl::kTria();
481 }
482
483 [[nodiscard]] unsigned Degree() const override {
484 return std::max({interior_degree_, edge_degrees_[0], edge_degrees_[1],
485 edge_degrees_[2], 1U});
486 }
487
492 [[nodiscard]] lf::base::size_type NumRefShapeFunctions() const override {
495 }
496
503 dim_t codim) const override {
504 switch (codim) {
505 case 0:
506 if (interior_degree_ <= 2) {
507 return 0;
508 } else {
509 return (interior_degree_ - 2) * (interior_degree_ - 1) / 2;
510 }
511 case 1:
512 LF_VERIFY_MSG(
513 edge_degrees_[0] == edge_degrees_[1] &&
515 "You cannot call this method with codim=1 if the edges of the "
516 "triangle have a differing number of shape functions.");
517 return edge_degrees_[0] - 1;
518 case 2:
519 return 1;
520 default:
521 LF_ASSERT_MSG(false, "Illegal codim " << codim);
522 // Silence compiler warnings
523 return 0;
524 }
525 }
526
527 // clang-format off
533 // clang-format on
535 dim_t codim, sub_idx_t subidx) const override {
536 switch (codim) {
537 case 0:
538 LF_ASSERT_MSG(subidx == 0, "illegal codim and subidx.");
539 return NumRefShapeFunctions(0);
540 case 1:
541 LF_ASSERT_MSG(subidx >= 0 && subidx <= 2,
542 "illegal codim/subidx combination.");
543 return edge_degrees_[subidx] - 1;
544 case 2:
545 LF_ASSERT_MSG(subidx >= 0 && subidx <= 2,
546 "illegal codim/subidx combination.");
547 return 1;
548 default:
549 LF_VERIFY_MSG(false, "Illegal codim " << codim);
550 // Silence compiler warnings
551 return 0;
552 }
553 }
554
555 [[nodiscard]] Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic>
556 EvalReferenceShapeFunctions(const Eigen::MatrixXd &refcoords) const override {
557 Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> result(
558 NumRefShapeFunctions(), refcoords.cols());
559 // Compute the barycentric coordinate functions
560 const Eigen::RowVectorXd l1 = Eigen::RowVectorXd::Ones(refcoords.cols()) -
561 refcoords.row(0) - refcoords.row(1);
562 const Eigen::RowVectorXd l2 = refcoords.row(0);
563 const Eigen::RowVectorXd l3 = refcoords.row(1);
564 // Get the basis functions associated with the vertices
565 result.row(0) = l1;
566 result.row(1) = l2;
567 result.row(2) = l3;
568 int current_dof = 3; // counter for the next row in the result.
569
570 // Get the basis functions associated with the first edge
571 for (unsigned i = 0; i < edge_degrees_[0] - 1; ++i) {
573 // L_{i+2}(\lambda_2 ; \lambda_1+\lambda_2)
574 for (long j = 0; j < refcoords.cols(); ++j) {
575 result(3 + i, j) = ilegendre(i + 2, l2[j], l1[j] + l2[j]);
576 }
577 } else {
578 // L_{i+2}(\lambda_1 ; \lambda_1+\lambda_2)
579 for (long j = 0; j < refcoords.cols(); ++j) {
580 result(edge_degrees_[0] + 1 - i, j) =
581 ilegendre(i + 2, l1[j], l1[j] + l2[j]);
582 }
583 }
584 }
585 current_dof += edge_degrees_[0] - 1;
586
587 // Get the basis functions associated with the second edge
588 for (unsigned i = 0; i < edge_degrees_[1] - 1; ++i) {
590 // L_{i+2}(\lambda_3 ; \lambda_2+\lambda_3)
591 for (long j = 0; j < refcoords.cols(); ++j) {
592 result(current_dof + i, j) = ilegendre(i + 2, l3[j], l2[j] + l3[j]);
593 }
594 } else {
595 // L_{i+2}(\lambda_2 ; \lambda_2+\lambda_3)
596 for (long j = 0; j < refcoords.cols(); ++j) {
597 result(current_dof + edge_degrees_[1] - 2 - i, j) =
598 ilegendre(i + 2, l2[j], l2[j] + l3[j]);
599 }
600 }
601 }
602 current_dof += edge_degrees_[1] - 1;
603
604 // Get the basis functions associated with the third edge
605 for (unsigned i = 0; i < edge_degrees_[2] - 1; ++i) {
607 // L_{i+2}(\lambda_1 ; \lambda_3+\lambda_1)
608 for (long j = 0; j < refcoords.cols(); ++j) {
609 result(current_dof + i, j) = ilegendre(i + 2, l1[j], l3[j] + l1[j]);
610 }
611 } else {
612 // L_{i+2}(\lambda_3 ; \lambda_3+\lambda_1)
613 for (long j = 0; j < refcoords.cols(); ++j) {
614 result(current_dof + edge_degrees_[2] - 2 - i, j) =
615 ilegendre(i + 2, l3[j], l3[j] + l1[j]);
616 }
617 }
618 }
619 current_dof += edge_degrees_[2] - 1;
620
621 // Get the basis functions associated with the interior of the triangle
622 if (interior_degree_ > 2) {
623 // i is the degree of the edge function
624 for (unsigned i = 0; i < interior_degree_ - 2; ++i) {
625 // value of the edge function (agrees with the values computed above,
626 // but since the degrees of the edge and interior are not linked at all,
627 // we cannot make any assumptions.
628 Eigen::Array<SCALAR, 1, Eigen::Dynamic> edge(refcoords.cols());
629 for (Eigen::Index j = 0; j < refcoords.cols(); ++j) {
631 edge(j) = ilegendre(i + 2, l2[j], l1[j] + l2[j]);
632 } else {
633 edge(j) = ilegendre(i + 2, l1[j], l1[j] + l2[j]);
634 }
635 }
636
637 // j is the degree of the blending
638 // polynomial
639 for (unsigned j = 0; j < interior_degree_ - i - 2; ++j) {
641 // L_{i+2}(\lambda_3 ; \lambda_2+\lambda_3) *
642 // P_{j+1}^{2i+4}(\lambda_1)
643 result.row(current_dof++) =
644 (edge * l3.array().unaryExpr([&](double x) -> SCALAR {
645 return ijacobi(j + 1, 2 * i + 4, x);
646 })).matrix();
647 } else {
648 // L_{i+2}(\lambda_2 ; \lambda_2+\lambda_3) *
649 // P_{j+1}^{2i+4}(\lambda_1)
650 result.row(current_dof++) =
651 (edge * l3.array().unaryExpr([&](double x) -> SCALAR {
652 return ijacobi(j + 1, 2 * i + 4, x);
653 })).matrix();
654 }
655 }
656 }
657 }
658 LF_ASSERT_MSG(current_dof == result.rows(),
659 "Something's wrong, not all rows have been filled.");
660 return result;
661 }
662
663 [[nodiscard]] Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic>
665 const Eigen::MatrixXd &refcoords) const override {
666 Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> result(
667 NumRefShapeFunctions(), 2 * refcoords.cols());
668 // Compute the barycentric coordinate functions
669 const Eigen::RowVectorXd l1 = Eigen::RowVectorXd::Ones(refcoords.cols()) -
670 refcoords.row(0) - refcoords.row(1);
671 const Eigen::RowVectorXd l2 = refcoords.row(0);
672 const Eigen::RowVectorXd l3 = refcoords.row(1);
673 // Comput the gradients of the barycentrc coordinate functions
674 const Eigen::RowVectorXd l1_dx =
675 Eigen::RowVectorXd::Constant(refcoords.cols(), -1);
676 const Eigen::RowVectorXd l1_dy =
677 Eigen::RowVectorXd::Constant(refcoords.cols(), -1);
678 const Eigen::RowVectorXd l2_dx =
679 Eigen::RowVectorXd::Constant(refcoords.cols(), 1);
680 const Eigen::RowVectorXd l2_dy =
681 Eigen::RowVectorXd::Constant(refcoords.cols(), 0);
682 const Eigen::RowVectorXd l3_dx =
683 Eigen::RowVectorXd::Constant(refcoords.cols(), 0);
684 const Eigen::RowVectorXd l3_dy =
685 Eigen::RowVectorXd::Constant(refcoords.cols(), 1);
686 // Iterate over all refcoords
687 for (Eigen::Index i = 0; i < refcoords.cols(); ++i) {
688 int current_dof = 3;
689 // Get the gradient of the basis functions associated with the vertices
690 result(0, 2 * i + 0) = l1_dx[i];
691 result(0, 2 * i + 1) = l1_dy[i];
692 result(1, 2 * i + 0) = l2_dx[i];
693 result(1, 2 * i + 1) = l2_dy[i];
694 result(2, 2 * i + 0) = l3_dx[i];
695 result(2, 2 * i + 1) = l3_dy[i];
696 // Get the gradient of the basis functions associated with the first edge
697 for (int j = 0; j < edge_degrees_[0] - 1; ++j) {
699 result(current_dof + j, 2 * i + 0) =
700 ilegendre_dx(j + 2, l2[i], l1[i] + l2[i]) * l2_dx[i] +
701 ilegendre_dt(j + 2, l2[i], l1[i] + l2[i]) * (l1_dx[i] + l2_dx[i]);
702 result(current_dof + j, 2 * i + 1) =
703 ilegendre_dx(j + 2, l2[i], l1[i] + l2[i]) * l2_dy[i] +
704 ilegendre_dt(j + 2, l2[i], l1[i] + l2[i]) * (l1_dy[i] + l2_dy[i]);
705 } else {
706 result(current_dof + edge_degrees_[0] - 2 - j, 2 * i + 0) =
707 ilegendre_dx(j + 2, l1[i], l1[i] + l2[i]) * l1_dx[i] +
708 ilegendre_dt(j + 2, l1[i], l1[i] + l2[i]) * (l1_dx[i] + l2_dx[i]);
709 result(current_dof + edge_degrees_[0] - 2 - j, 2 * i + 1) =
710 ilegendre_dx(j + 2, l1[i], l1[i] + l2[i]) * l1_dy[i] +
711 ilegendre_dt(j + 2, l1[i], l1[i] + l2[i]) * (l1_dy[i] + l2_dy[i]);
712 }
713 }
714 current_dof += edge_degrees_[0] - 1;
715
716 // Get the gradient of the basis functions associated with the second edge
717 for (int j = 0; j < edge_degrees_[1] - 1; ++j) {
719 result(current_dof + j, 2 * i + 0) =
720 ilegendre_dx(j + 2, l3[i], l2[i] + l3[i]) * l3_dx[i] +
721 ilegendre_dt(j + 2, l3[i], l2[i] + l3[i]) * (l2_dx[i] + l3_dx[i]);
722 result(current_dof + j, 2 * i + 1) =
723 ilegendre_dx(j + 2, l3[i], l2[i] + l3[i]) * l3_dy[i] +
724 ilegendre_dt(j + 2, l3[i], l2[i] + l3[i]) * (l2_dy[i] + l3_dy[i]);
725 } else {
726 result(current_dof + edge_degrees_[1] - 2 - j, 2 * i + 0) =
727 ilegendre_dx(j + 2, l2[i], l2[i] + l3[i]) * l2_dx[i] +
728 ilegendre_dt(j + 2, l2[i], l2[i] + l3[i]) * (l2_dx[i] + l3_dx[i]);
729 result(current_dof + edge_degrees_[1] - 2 - j, 2 * i + 1) =
730 ilegendre_dx(j + 2, l2[i], l2[i] + l3[i]) * l2_dy[i] +
731 ilegendre_dt(j + 2, l2[i], l2[i] + l3[i]) * (l2_dy[i] + l3_dy[i]);
732 }
733 }
734 current_dof += edge_degrees_[1] - 1;
735
736 // Get the gradient of the basis functions associated with the third edge
737 for (int j = 0; j < edge_degrees_[2] - 1; ++j) {
739 result(current_dof + j, 2 * i + 0) =
740 ilegendre_dx(j + 2, l1[i], l3[i] + l1[i]) * l1_dx[i] +
741 ilegendre_dt(j + 2, l1[i], l3[i] + l1[i]) * (l3_dx[i] + l1_dx[i]);
742 result(current_dof + j, 2 * i + 1) =
743 ilegendre_dx(j + 2, l1[i], l3[i] + l1[i]) * l1_dy[i] +
744 ilegendre_dt(j + 2, l1[i], l3[i] + l1[i]) * (l3_dy[i] + l1_dy[i]);
745 } else {
746 result(current_dof + edge_degrees_[2] - 2 - j, 2 * i + 0) =
747 ilegendre_dx(j + 2, l3[i], l3[i] + l1[i]) * l3_dx[i] +
748 ilegendre_dt(j + 2, l3[i], l3[i] + l1[i]) * (l3_dx[i] + l1_dx[i]);
749 result(current_dof + edge_degrees_[2] - 2 - j, 2 * i + 1) =
750 ilegendre_dx(j + 2, l3[i], l3[i] + l1[i]) * l3_dy[i] +
751 ilegendre_dt(j + 2, l3[i], l3[i] + l1[i]) * (l3_dy[i] + l1_dy[i]);
752 }
753 }
754 current_dof += edge_degrees_[2] - 1;
755
756 // Get the gradient of the basis functions associated with the interior of
757 // the triangle
758 if (interior_degree_ > 2) {
759 for (unsigned j = 0; j < interior_degree_ - 2; ++j) {
760 SCALAR edge_eval;
761 SCALAR edge_dx;
762 SCALAR edge_dy;
764 edge_eval = ilegendre(j + 2, l2[i], l1[i] + l2[i]);
765 edge_dx = ilegendre_dx(j + 2, l2[i], l1[i] + l2[i]) * l2_dx[i] +
766 ilegendre_dt(j + 2, l2[i], l1[i] + l2[i]) *
767 (l1_dx[i] + l2_dx[i]);
768 edge_dy = ilegendre_dx(j + 2, l2[i], l1[i] + l2[i]) * l2_dy[i] +
769 ilegendre_dt(j + 2, l2[i], l1[i] + l2[i]) *
770 (l1_dy[i] + l2_dy[i]);
771 } else {
772 edge_eval = ilegendre(j + 2, l1[i], l1[i] + l2[i]);
773 edge_dx = ilegendre_dx(j + 2, l1[i], l1[i] + l2[i]) * l1_dx[i] +
774 ilegendre_dt(j + 2, l1[i], l1[i] + l2[i]) *
775 (l1_dx[i] + l2_dx[i]);
776 edge_dy = ilegendre_dx(j + 2, l1[i], l1[i] + l2[i]) * l1_dy[i] +
777 ilegendre_dt(j + 2, l1[i], l1[i] + l2[i]) *
778 (l1_dy[i] + l2_dy[i]);
779 }
780 for (unsigned k = 0; k < interior_degree_ - j - 2; ++k) {
781 SCALAR jackinte = ijacobi(k + 1, 2 * j + 4, l3[i]);
782 SCALAR jackeval = ijacobi_dx(k + 1, 2 * j + 4, l3[i]);
783 result(current_dof, 2 * i + 0) =
784 jackinte * edge_dx + edge_eval * jackeval * l3_dx[i];
785 result(current_dof++, 2 * i + 1) =
786 jackinte * edge_dy + edge_eval * jackeval * l3_dy[i];
787 }
788 }
789 }
790 LF_ASSERT_MSG(current_dof == NumRefShapeFunctions(),
791 "internal error, not all rows have been filled.");
792 }
793 return result;
794 }
795
802 [[nodiscard]] Eigen::MatrixXd EvaluationNodes() const override {
803 const auto Ns0 = edge_degrees_[0] > 1 ? qr_dual_edge_[0]->NumPoints() : 0;
804 const auto Ns1 = edge_degrees_[1] > 1 ? qr_dual_edge_[1]->NumPoints() : 0;
805 const auto Ns2 = edge_degrees_[2] > 1 ? qr_dual_edge_[2]->NumPoints() : 0;
807 Eigen::MatrixXd nodes(2, 3 + Ns0 + Ns1 + Ns2 + Nt);
808 // Add the vertices
809 nodes(0, 0) = 0;
810 nodes(1, 0) = 0;
811 nodes(0, 1) = 1;
812 nodes(1, 1) = 0;
813 nodes(0, 2) = 0;
814 nodes(1, 2) = 1;
815 // Add the quadrature points on the first edge
816 if (Ns0 > 0) {
818 nodes.block(0, 3, 1, Ns0) = qr_dual_edge_[0]->Points();
819 } else {
820 nodes.block(0, 3, 1, Ns0) =
821 Eigen::RowVectorXd::Ones(Ns0) - qr_dual_edge_[0]->Points();
822 }
823 nodes.block(1, 3, 1, Ns0).setZero();
824 }
825 // Add the quadrature points on the second edge
826 if (Ns1 > 0) {
828 nodes.block(0, 3 + Ns0, 1, Ns1) =
829 Eigen::RowVectorXd::Ones(Ns1) - qr_dual_edge_[1]->Points();
830 nodes.block(1, 3 + Ns0, 1, Ns1) = qr_dual_edge_[1]->Points();
831 } else {
832 nodes.block(0, 3 + Ns0, 1, Ns1) = qr_dual_edge_[1]->Points();
833 nodes.block(1, 3 + Ns0, 1, Ns1) =
834 Eigen::RowVectorXd::Ones(Ns1) - qr_dual_edge_[1]->Points();
835 }
836 }
837 // Add the quadrature points on the third edge
838 if (Ns2 > 0) {
839 nodes.block(0, 3 + Ns0 + Ns1, 1, Ns2).setZero();
841 nodes.block(1, 3 + Ns0 + Ns1, 1, Ns2) =
842 Eigen::RowVectorXd::Ones(Ns2) - qr_dual_edge_[2]->Points();
843 } else {
844 nodes.block(1, 3 + Ns0 + Ns1, 1, Ns2) = qr_dual_edge_[2]->Points();
845 }
846 }
847 if (Nt > 0) {
848 // Add the quadrature points for the interior
849 nodes.block(0, 3 + Ns0 + Ns1 + Ns2, 2, Nt) = qr_dual_tria_->Points();
850 }
851 return nodes;
852 }
853
857 [[nodiscard]] lf::base::size_type NumEvaluationNodes() const override {
858 const auto Ns0 = edge_degrees_[0] > 1 ? qr_dual_edge_[0]->NumPoints() : 0;
859 const auto Ns1 = edge_degrees_[1] > 1 ? qr_dual_edge_[1]->NumPoints() : 0;
860 const auto Ns2 = edge_degrees_[2] > 1 ? qr_dual_edge_[2]->NumPoints() : 0;
862 return 3 + Ns0 + Ns1 + Ns2 + Nt;
863 }
864
865 [[nodiscard]] Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> NodalValuesToDofs(
866 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> &nodevals) const override {
867 const auto d0 = edge_degrees_[0] - 1;
868 const auto d1 = edge_degrees_[1] - 1;
869 const auto d2 = edge_degrees_[2] - 1;
870
871 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> dofs(NumRefShapeFunctions());
872 // Compute the basis function coefficients of the vertex basis functions
873 dofs.segment(0, 3) = NodalValuesToVertexDofs(nodevals);
874 // Compute the basis function coefficients on the edges
875 dofs.segment(3, d0 + d1 + d2) = NodalValuesToEdgeDofs(nodevals);
876 // Compute the basis function coefficients for the face bubbles
877 dofs.segment(3 + d0 + d1 + d2, NumRefShapeFunctions(0)) =
878 NodalValuesToFaceDofs(nodevals);
879 // We need to orthogonalize the face bubble dual basis w.r.t. the
880 // dual basis on the vertices and edges
881 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic>
882 basis_functions = EvalReferenceShapeFunctions(EvaluationNodes());
883 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> boundary_function =
884 dofs.segment(0, 3 + d0 + d1 + d2) *
885 basis_functions.block(0, 0, 3 + d0 + d1 + d2, basis_functions.cols());
886 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> boundary_face_dofs =
887 NodalValuesToFaceDofs(boundary_function);
888 dofs.segment(3 + d0 + d1 + d2, NumRefShapeFunctions(0)) -=
889 boundary_face_dofs;
890 return dofs;
891 }
892
893 private:
894 unsigned interior_degree_; // degree for inner shape functions
895 std::array<unsigned, 3> edge_degrees_; // degree of the edges.
896 std::array<const quad::QuadRule *, 3>
897 qr_dual_edge_; // Quadrature rules for the edges.
900
901 /*
902 * @brief Compute the DOFs of the vertex functions from some
903 * given nodal evaluations
904 */
905 [[nodiscard]] Eigen::Matrix<SCALAR, 1, Eigen::Dynamic>
907 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> &nodevals) const {
908 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> dofs(3);
909 dofs[0] = nodevals[0];
910 dofs[1] = nodevals[1];
911 dofs[2] = nodevals[2];
912 return dofs;
913 }
914
915 /*
916 * @brief Compute the DOFs of the edge functions from some
917 * given nodal evaluations
918 */
919 [[nodiscard]] Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> NodalValuesToEdgeDofs(
920 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> &nodevals) const {
921 const auto Ns0 = edge_degrees_[0] > 1 ? qr_dual_edge_[0]->NumPoints() : 0;
922 const auto Ns1 = edge_degrees_[1] > 1 ? qr_dual_edge_[1]->NumPoints() : 0;
923 const auto Ns2 = edge_degrees_[2] > 1 ? qr_dual_edge_[2]->NumPoints() : 0;
925 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> dofs(
926 edge_degrees_[0] + edge_degrees_[1] + edge_degrees_[2] - 3);
927 // Compute the basis function coefficients on the edges
928 // by applying the dual basis of the segment
929
930 // first edge:
931 for (base::size_type i = 2; i < edge_degrees_[0] + 1; ++i) {
932 const SCALAR P0 = ilegendre_dx(i, 0);
933 const SCALAR P1 = ilegendre_dx(i, 1);
934 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> psidd =
935 qr_dual_edge_[0]->Points().unaryExpr(
936 [&](double x) -> SCALAR { return legendre_dx(i - 1, x); });
937
938 const SCALAR integ1 = (qr_dual_edge_[0]->Weights().transpose().array() *
939 psidd.array() * nodevals.segment(3, Ns0).array())
940 .sum();
942 dofs[1 + i - 3] =
943 (P1 * nodevals[1] - P0 * nodevals[0] - integ1) * (2 * i - 1);
944 } else {
945 dofs[4 - i + (edge_degrees_[0] - 1) - 3] =
946 (P1 * nodevals[0] - P0 * nodevals[1] - integ1) * (2 * i - 1);
947 }
948 }
949
950 // Compute the basis function coefficients for the second edge
951 for (base::size_type i = 2; i < edge_degrees_[1] + 1; ++i) {
952 const SCALAR P0 = ilegendre_dx(i, 0);
953 const SCALAR P1 = ilegendre_dx(i, 1);
954 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> psidd =
955 qr_dual_edge_[1]->Points().unaryExpr(
956 [&](double x) -> SCALAR { return legendre_dx(i - 1, x); });
957
958 const SCALAR integ2 =
959 (qr_dual_edge_[1]->Weights().transpose().array() * psidd.array() *
960 nodevals.segment(3 + Ns0, Ns1).array())
961 .sum();
963 dofs[1 + i + (edge_degrees_[0] - 1) - 3] =
964 (P1 * nodevals[2] - P0 * nodevals[1] - integ2) * (2 * i - 1);
965 } else {
966 dofs[4 - i + (edge_degrees_[0] + edge_degrees_[1] - 2) - 3] =
967 (P1 * nodevals[1] - P0 * nodevals[2] - integ2) * (2 * i - 1);
968 }
969 }
970
971 // Compute the basis function coefficients for the second edge
972 for (base::size_type i = 2; i < edge_degrees_[2] + 1; ++i) {
973 const SCALAR P0 = ilegendre_dx(i, 0);
974 const SCALAR P1 = ilegendre_dx(i, 1);
975 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> psidd =
976 qr_dual_edge_[2]->Points().unaryExpr(
977 [&](double x) -> SCALAR { return legendre_dx(i - 1, x); });
978
979 const SCALAR integ3 =
980 (qr_dual_edge_[2]->Weights().transpose().array() * psidd.array() *
981 nodevals.segment(3 + Ns0 + Ns1, Ns2).array())
982 .sum();
984 dofs[1 + i + (edge_degrees_[0] + edge_degrees_[1] - 2) - 3] =
985 (P1 * nodevals[0] - P0 * nodevals[2] - integ3) * (2 * i - 1);
986 } else {
987 dofs[4 - i +
988 (edge_degrees_[0] + edge_degrees_[1] + edge_degrees_[2] - 3) - 3] =
989 (P1 * nodevals[2] - P0 * nodevals[0] - integ3) * (2 * i - 1);
990 }
991 }
992
993 return dofs;
994 }
995
996 /*
997 * @brief Compute the non-orthogonalized DOFs of the face bubbles from some
998 * given nodal evaluations
999 */
1000 [[nodiscard]] Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> NodalValuesToFaceDofs(
1001 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> &nodevals) const {
1002 const auto Ns0 = edge_degrees_[0] > 1 ? qr_dual_edge_[0]->NumPoints() : 0;
1003 const auto Ns1 = edge_degrees_[1] > 1 ? qr_dual_edge_[1]->NumPoints() : 0;
1004 const auto Ns2 = edge_degrees_[2] > 1 ? qr_dual_edge_[2]->NumPoints() : 0;
1005 const lf::base::size_type Ns = Ns0 + Ns1 + Ns2;
1007 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> dofs(NumRefShapeFunctions(0));
1008 if (interior_degree_ > 2) {
1009 unsigned idx = 0;
1010 const Eigen::Matrix<double, 1, Eigen::Dynamic> xnorm =
1011 (qr_dual_tria_->Points().row(0).array() /
1012 qr_dual_tria_->Points().row(1).array().unaryExpr([&](double y) {
1013 return 1 - y;
1014 })).matrix();
1015 // We need to flip the local coordinates in case the
1016 // relative orientation is negative
1017 const Eigen::Matrix<double, 1, Eigen::Dynamic> xnorm_adj =
1019 ? xnorm
1020 : Eigen::RowVectorXd::Ones(Nt) - xnorm;
1021 const Eigen::Matrix<double, 1, Eigen::Dynamic> y =
1022 qr_dual_tria_->Points().row(1);
1023 // i is the degree of the edge function
1024 for (unsigned i = 0; i < interior_degree_ - 2; ++i) {
1025 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> ypow =
1026 y.array()
1027 .unaryExpr(
1028 [&](double y) -> SCALAR { return std::pow(1 - y, i); })
1029 .matrix();
1030 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> ypowp1 =
1031 y.array()
1032 .unaryExpr(
1033 [&](double y) -> SCALAR { return std::pow(1 - y, i + 1); })
1034 .matrix();
1035 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> psidd =
1036 xnorm_adj.unaryExpr(
1037 [&](double x) -> SCALAR { return legendre_dx(i + 1, x); });
1038 // j is the degree of the blending Jacobi polynomial
1039 for (unsigned j = 0; j < interior_degree_ - i - 2; ++j) {
1040 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> jacdd =
1041 qr_dual_tria_->Points().row(1).unaryExpr([&](double y) -> SCALAR {
1042 return jacobi_dx(j, 2 * i + 4, y);
1043 });
1044 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> jacd =
1045 qr_dual_tria_->Points().row(1).unaryExpr([&](double y) -> SCALAR {
1046 return ijacobi_dx(j + 1, 2 * i + 4, y);
1047 });
1048 dofs[idx] =
1049 (qr_dual_tria_->Weights().transpose().array() *
1050 nodevals.block(0, 3 + Ns, 1, Nt).array() * psidd.array() *
1051 (ypowp1.array() * jacdd.array() -
1052 (2 * i + 4) * ypow.array() * jacd.array()))
1053 .sum();
1054 dofs[idx] *=
1055 2 * i + 3; // Normalization factor for the Legendre polynomial
1056 dofs[idx] *= 2 * j + 2 * i +
1057 5; // Normalization factor for the Jacobi Polynomial
1058 ++idx;
1059 }
1060 }
1061 }
1062 return dofs;
1063 }
1064};
1065
1088template <typename SCALAR>
1090 public:
1092 FeHierarchicQuad(FeHierarchicQuad &&) noexcept = default;
1093 FeHierarchicQuad &operator=(const FeHierarchicQuad &) = default;
1094 FeHierarchicQuad &operator=(FeHierarchicQuad &&) noexcept = default;
1095 ~FeHierarchicQuad() override = default;
1096
1097 FeHierarchicQuad(unsigned interior_degree,
1098 std::array<unsigned, 4> edge_degrees,
1099 const quad::QuadRuleCache &qr_cache,
1100 nonstd::span<const lf::mesh::Orientation> rel_orient)
1101 : ScalarReferenceFiniteElement<SCALAR>(),
1102 interior_degree_(interior_degree),
1103 edge_degrees_(edge_degrees),
1105 2 * (edge_degrees_[0] - 1)),
1106 &qr_cache.Get(lf::base::RefEl::kSegment(),
1107 2 * (edge_degrees_[1] - 1)),
1108 &qr_cache.Get(lf::base::RefEl::kSegment(),
1109 2 * (edge_degrees_[2] - 1)),
1110 &qr_cache.Get(lf::base::RefEl::kSegment(),
1111 2 * (edge_degrees_[3] - 1))}),
1112 rel_orient_(rel_orient),
1115 qr_cache) {
1116 LF_ASSERT_MSG(interior_degree_ >= 0, "illegal interior degree.");
1117 LF_ASSERT_MSG(edge_degrees_[0] >= 0, "illegal degree for edge 0");
1118 LF_ASSERT_MSG(edge_degrees_[1] >= 0, "illegal degree for edge 1");
1119 LF_ASSERT_MSG(edge_degrees_[2] >= 0, "illegal degree for edge 2");
1120 LF_ASSERT_MSG(edge_degrees_[3] >= 0, "illegal degree for edge 3");
1121 }
1122
1123 [[nodiscard]] lf::base::RefEl RefEl() const override {
1124 return lf::base::RefEl::kQuad();
1125 }
1126
1127 [[nodiscard]] unsigned Degree() const override {
1128 return std::max({interior_degree_, edge_degrees_[0], edge_degrees_[1],
1130 }
1131
1136 [[nodiscard]] lf::base::size_type NumRefShapeFunctions() const override {
1137 return 4 + NumRefShapeFunctions(0) + NumRefShapeFunctions(1, 0) +
1140 }
1141
1148 dim_t codim) const override {
1149 switch (codim) {
1150 case 0:
1151 return (interior_degree_ - 1) * (interior_degree_ - 1);
1152 case 1:
1153 LF_VERIFY_MSG(
1154 edge_degrees_[0] == edge_degrees_[1] &&
1155 edge_degrees_[0] == edge_degrees_[2] &&
1157 "You cannot call this method with codim=1 if the edges of the "
1158 "quad have a differing number of shape functions.");
1159 return edge_degrees_[0] - 1;
1160 case 2:
1161 return 1;
1162 default:
1163 LF_ASSERT_MSG(false, "Illegal codim " << codim);
1164 // Silence compiler warnings
1165 return 0;
1166 }
1167 }
1168
1169 // clang-format off
1175 // clang-format on
1177 dim_t codim, sub_idx_t subidx) const override {
1178 switch (codim) {
1179 case 0:
1180 LF_ASSERT_MSG(subidx == 0, "illegal codim and subidex.");
1181 return NumRefShapeFunctions(0);
1182 case 1:
1183 LF_ASSERT_MSG(subidx >= 0 && subidx < 4, "illegal codim and subidx.");
1184 return edge_degrees_[subidx] - 1;
1185 case 2:
1186 LF_ASSERT_MSG(subidx >= 0 && subidx < 4, "illegal codim and subidx.");
1187 return 1;
1188 default:
1189 LF_VERIFY_MSG(false, "Illegal codim " << codim);
1190 // Silence compiler warnings
1191 return 0;
1192 }
1193 }
1194
1195 [[nodiscard]] Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic>
1196 EvalReferenceShapeFunctions(const Eigen::MatrixXd &refcoords) const override {
1197 Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> result(
1198 NumRefShapeFunctions(), refcoords.cols());
1199 // Compute the 1D shape functions at the x and y coordinates
1200 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1d_x =
1201 fe1d_.EvalReferenceShapeFunctions(refcoords.row(0));
1202 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1d_y =
1203 fe1d_.EvalReferenceShapeFunctions(refcoords.row(1));
1204 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1df_x =
1205 fe1d_.EvalReferenceShapeFunctions(
1206 Eigen::RowVectorXd::Constant(refcoords.cols(), 1) -
1207 refcoords.row(0));
1208 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1df_y =
1209 fe1d_.EvalReferenceShapeFunctions(
1210 Eigen::RowVectorXd::Constant(refcoords.cols(), 1) -
1211 refcoords.row(1));
1212 // Get the basis functions associated with the vertices
1213 result.row(0) = (sf1d_x.row(0).array() * sf1d_y.row(0).array()).matrix();
1214 result.row(1) = (sf1d_x.row(1).array() * sf1d_y.row(0).array()).matrix();
1215 result.row(2) = (sf1d_x.row(1).array() * sf1d_y.row(1).array()).matrix();
1216 result.row(3) = (sf1d_x.row(0).array() * sf1d_y.row(1).array()).matrix();
1217
1218 int current_dof = 4;
1219
1220 // Get the basis functions associated with the first edge
1221 // as a tensor product of 1D basis functions
1222 for (int i = 0; i < edge_degrees_[0] - 1; ++i) {
1224 result.row(current_dof + i) =
1225 (sf1d_x.row(2 + i).array() * sf1d_y.row(0).array()).matrix();
1226 } else {
1227 result.row(current_dof - 2 + edge_degrees_[0] - i) =
1228 (sf1df_x.row(2 + i).array() * sf1d_y.row(0).array()).matrix();
1229 }
1230 }
1231 current_dof += edge_degrees_[0] - 1;
1232 // Get the basis functions associated with the second edge
1233 // as a tensor product of 1D basis functions
1234 for (int i = 0; i < edge_degrees_[1] - 1; ++i) {
1236 result.row(current_dof + i) =
1237 (sf1d_x.row(1).array() * sf1d_y.row(2 + i).array()).matrix();
1238 } else {
1239 result.row(current_dof - 2 + edge_degrees_[1] - i) =
1240 (sf1d_x.row(1).array() * sf1df_y.row(2 + i).array()).matrix();
1241 }
1242 }
1243 current_dof += edge_degrees_[1] - 1;
1244 // Get the basis functions associated with the third edge
1245 // as a tensor product of 1D basis functions
1246 for (int i = 0; i < edge_degrees_[2] - 1; ++i) {
1248 result.row(current_dof + i) =
1249 (sf1df_x.row(2 + i).array() * sf1d_y.row(1).array()).matrix();
1250 } else {
1251 result.row(current_dof + edge_degrees_[2] - 2 - i) =
1252 (sf1d_x.row(2 + i).array() * sf1d_y.row(1).array()).matrix();
1253 }
1254 }
1255 current_dof += edge_degrees_[2] - 1;
1256 // Get the basis functions associated with the fourth edge
1257 // as a tensor product of 1D basis functions
1258 for (int i = 0; i < edge_degrees_[3] - 1; ++i) {
1260 result.row(current_dof + i) =
1261 (sf1d_x.row(0).array() * sf1df_y.row(2 + i).array()).matrix();
1262 } else {
1263 result.row(current_dof + edge_degrees_[3] - 2 - i) =
1264 (sf1d_x.row(0).array() * sf1d_y.row(2 + i).array()).matrix();
1265 }
1266 }
1267 current_dof += edge_degrees_[3] - 1;
1268 // Get the basis functions associated with the interior of the quad
1269 // as a tensor product of 1D basis functions
1270 for (int i = 0; i < interior_degree_ - 1; ++i) {
1271 for (int j = 0; j < interior_degree_ - 1; ++j) {
1272 result.row(current_dof++) =
1273 (sf1d_x.row(j + 2).array() * sf1d_y.row(i + 2).array()).matrix();
1274 }
1275 }
1276 LF_ASSERT_MSG(current_dof == result.rows(),
1277 "Not all rows have been filled.");
1278 return result;
1279 }
1280
1281 [[nodiscard]] Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic>
1283 const Eigen::MatrixXd &refcoords) const override {
1284 Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> result(
1285 NumRefShapeFunctions(), 2 * refcoords.cols());
1286 // Compute the gradient of the 1D shape functions at the x and y coordinates
1287 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1d_x =
1288 fe1d_.EvalReferenceShapeFunctions(refcoords.row(0));
1289 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1d_y =
1290 fe1d_.EvalReferenceShapeFunctions(refcoords.row(1));
1291 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1d_dx =
1292 fe1d_.GradientsReferenceShapeFunctions(refcoords.row(0));
1293 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1d_dy =
1294 fe1d_.GradientsReferenceShapeFunctions(refcoords.row(1));
1295 // Compute the gradient of the flipped 1D shape functions at the x and y
1296 // coordinates
1297 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1df_x =
1298 fe1d_.EvalReferenceShapeFunctions(
1299 Eigen::RowVectorXd::Constant(refcoords.cols(), 1) -
1300 refcoords.row(0));
1301 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1df_y =
1302 fe1d_.EvalReferenceShapeFunctions(
1303 Eigen::RowVectorXd::Constant(refcoords.cols(), 1) -
1304 refcoords.row(1));
1305 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1df_dx =
1306 fe1d_.GradientsReferenceShapeFunctions(
1307 Eigen::RowVectorXd::Constant(refcoords.cols(), 1) -
1308 refcoords.row(0));
1309 const Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> sf1df_dy =
1310 fe1d_.GradientsReferenceShapeFunctions(
1311 Eigen::RowVectorXd::Constant(refcoords.cols(), 1) -
1312 refcoords.row(1));
1313 for (Eigen::Index i = 0; i < refcoords.cols(); ++i) {
1314 // Get the gradient of the basis functions associated with the vertices
1315 result(0, 2 * i + 0) = sf1d_dx(0, i) * sf1d_y(0, i);
1316 result(0, 2 * i + 1) = sf1d_x(0, i) * sf1d_dy(0, i);
1317 result(1, 2 * i + 0) = sf1d_dx(1, i) * sf1d_y(0, i);
1318 result(1, 2 * i + 1) = sf1d_x(1, i) * sf1d_dy(0, i);
1319 result(2, 2 * i + 0) = sf1d_dx(1, i) * sf1d_y(1, i);
1320 result(2, 2 * i + 1) = sf1d_x(1, i) * sf1d_dy(1, i);
1321 result(3, 2 * i + 0) = sf1d_dx(0, i) * sf1d_y(1, i);
1322 result(3, 2 * i + 1) = sf1d_x(0, i) * sf1d_dy(1, i);
1323
1324 int current_dof = 4;
1325 // Get the basis functions associated with the first edge
1326 for (int j = 0; j < edge_degrees_[0] - 1; ++j) {
1328 result(current_dof + j, 2 * i + 0) = sf1d_dx(2 + j, i) * sf1d_y(0, i);
1329 result(current_dof + j, 2 * i + 1) = sf1d_x(2 + j, i) * sf1d_dy(0, i);
1330 } else {
1331 result(current_dof + edge_degrees_[0] - 2 - j, 2 * i + 0) =
1332 -sf1df_dx(2 + j, i) * sf1d_y(0, i);
1333 result(current_dof + edge_degrees_[0] - 2 - j, 2 * i + 1) =
1334 sf1df_x(2 + j, i) * sf1d_dy(0, i);
1335 }
1336 }
1337 current_dof += edge_degrees_[0] - 1;
1338 // Get the basis functions associated with the second edge
1339 for (int j = 0; j < edge_degrees_[1] - 1; ++j) {
1341 result(current_dof + j, 2 * i + 0) = sf1d_dx(1, i) * sf1d_y(2 + j, i);
1342 result(current_dof + j, 2 * i + 1) = sf1d_x(1, i) * sf1d_dy(2 + j, i);
1343 } else {
1344 result(current_dof + edge_degrees_[1] - 2 - j, 2 * i + 0) =
1345 sf1d_dx(1, i) * sf1df_y(2 + j, i);
1346 result(current_dof + edge_degrees_[1] - 2 - j, 2 * i + 1) =
1347 sf1d_x(1, i) * -sf1df_dy(2 + j, i);
1348 }
1349 }
1350 current_dof += edge_degrees_[1] - 1;
1351 // Get the basis functions associated with the third edge
1352 for (int j = 0; j < edge_degrees_[2] - 1; ++j) {
1354 result(current_dof + j, 2 * i + 0) =
1355 -sf1df_dx(2 + j, i) * sf1d_y(1, i);
1356 result(current_dof + j, 2 * i + 1) =
1357 sf1df_x(2 + j, i) * sf1d_dy(1, i);
1358 } else {
1359 result(current_dof + edge_degrees_[2] - 2 - j, 2 * i + 0) =
1360 sf1d_dx(2 + j, i) * sf1d_y(1, i);
1361 result(current_dof + edge_degrees_[2] - 2 - j, 2 * i + 1) =
1362 sf1d_x(2 + j, i) * sf1d_dy(1, i);
1363 }
1364 }
1365 current_dof += edge_degrees_[2] - 1;
1366 // Get the basis functions associated with the fourth edge
1367 for (int j = 0; j < edge_degrees_[3] - 1; ++j) {
1369 result(current_dof + j, 2 * i + 0) =
1370 sf1d_dx(0, i) * sf1df_y(2 + j, i);
1371 result(current_dof + j, 2 * i + 1) =
1372 sf1d_x(0, i) * -sf1df_dy(2 + j, i);
1373 } else {
1374 result(current_dof + edge_degrees_[3] - 2 - j, 2 * i + 0) =
1375 sf1d_dx(0, i) * sf1d_y(2 + j, i);
1376 result(current_dof + edge_degrees_[3] - 2 - j, 2 * i + 1) =
1377 sf1d_x(0, i) * sf1d_dy(2 + j, i);
1378 }
1379 }
1380 current_dof += edge_degrees_[3] - 1;
1381 // Get the basis functions associated with the interior of the quad
1382 for (int j = 0; j < interior_degree_ - 1; ++j) {
1383 for (int k = 0; k < interior_degree_ - 1; ++k) {
1384 result(current_dof, 2 * i + 0) = sf1d_dx(k + 2, i) * sf1d_y(j + 2, i);
1385 result(current_dof++, 2 * i + 1) =
1386 sf1d_x(k + 2, i) * sf1d_dy(j + 2, i);
1387 }
1388 }
1389 }
1390 return result;
1391 }
1392
1399 [[nodiscard]] Eigen::MatrixXd EvaluationNodes() const override {
1400 const base::size_type Ne0 =
1401 edge_degrees_[0] > 1 ? qr_dual_edge_[0]->NumPoints() : 0;
1402 const base::size_type Ne1 =
1403 edge_degrees_[1] > 1 ? qr_dual_edge_[1]->NumPoints() : 0;
1404 const base::size_type Ne2 =
1405 edge_degrees_[2] > 1 ? qr_dual_edge_[2]->NumPoints() : 0;
1406 const base::size_type Ne3 =
1407 edge_degrees_[3] > 1 ? qr_dual_edge_[3]->NumPoints() : 0;
1408 auto Nq = interior_degree_ > 1 ? fe1d_.NumEvaluationNodes() : 0;
1409
1410 Eigen::MatrixXd nodes(2, 4 + Ne0 + Ne1 + Ne2 + Ne3 + Nq * Nq);
1411 // Add the vertices
1412 nodes(0, 0) = 0;
1413 nodes(1, 0) = 0;
1414 nodes(0, 1) = 1;
1415 nodes(1, 1) = 0;
1416 nodes(0, 2) = 1;
1417 nodes(1, 2) = 1;
1418 nodes(0, 3) = 0;
1419 nodes(1, 3) = 1;
1420 if (edge_degrees_[0] > 1) {
1421 // Add the quadrature points on the first edge
1423 nodes.block(0, 4, 1, Ne0) = qr_dual_edge_[0]->Points();
1424 } else {
1425 nodes.block(0, 4, 1, Ne0) =
1426 Eigen::RowVectorXd::Ones(Ne0) - qr_dual_edge_[0]->Points();
1427 }
1428 nodes.block(1, 4, 1, Ne0).setZero();
1429 }
1430
1431 if (edge_degrees_[1] > 1) {
1432 // Add the quadrature points on the second edge
1433 nodes.block(0, 4 + Ne0, 1, Ne1).setOnes();
1435 nodes.block(1, 4 + Ne0, 1, Ne1) = qr_dual_edge_[1]->Points();
1436 } else {
1437 nodes.block(1, 4 + Ne0, 1, Ne1) =
1438 Eigen::RowVectorXd::Ones(Ne1) - qr_dual_edge_[1]->Points();
1439 }
1440 }
1441 if (edge_degrees_[2] > 1) {
1442 // Add the quadrature points on the third edge
1444 nodes.block(0, 4 + Ne0 + Ne1, 1, Ne2) =
1445 Eigen::RowVectorXd::Ones(Ne2) - qr_dual_edge_[2]->Points();
1446 } else {
1447 nodes.block(0, 4 + Ne0 + Ne1, 1, Ne2) = qr_dual_edge_[2]->Points();
1448 }
1449 nodes.block(1, 4 + Ne0 + Ne1, 1, Ne2).setOnes();
1450 }
1451 if (edge_degrees_[3] > 1) {
1452 // Add the quadrature points on the fourth edge
1453 nodes.block(0, 4 + Ne0 + Ne1 + Ne2, 1, Ne3).setZero();
1455 nodes.block(1, 4 + Ne0 + Ne1 + Ne2, 1, Ne3) =
1456 Eigen::RowVectorXd::Ones(Ne3) - qr_dual_edge_[3]->Points();
1457 } else {
1458 nodes.block(1, 4 + Ne0 + Ne1 + Ne2, 1, Ne3) =
1459 qr_dual_edge_[3]->Points();
1460 }
1461 }
1462 if (interior_degree_ > 1) {
1463 // Add the quadrature points for the face
1464 auto points = fe1d_.EvaluationNodes();
1465 for (lf::base::size_type i = 0; i < Nq; ++i) {
1466 nodes.block(0, 4 + Ne0 + Ne1 + Ne2 + Ne3 + i * Nq, 1, Nq) = points;
1467 nodes.block(1, 4 + Ne0 + Ne1 + Ne2 + Ne3 + i * Nq, 1, Nq)
1468 .setConstant(points(0, i));
1469 }
1470 }
1471 return nodes;
1472 }
1473
1477 [[nodiscard]] lf::base::size_type NumEvaluationNodes() const override {
1478 const base::size_type Ne0 =
1479 edge_degrees_[0] > 1 ? qr_dual_edge_[0]->NumPoints() : 0;
1480 const base::size_type Ne1 =
1481 edge_degrees_[1] > 1 ? qr_dual_edge_[1]->NumPoints() : 0;
1482 const base::size_type Ne2 =
1483 edge_degrees_[2] > 1 ? qr_dual_edge_[2]->NumPoints() : 0;
1484 const base::size_type Ne3 =
1485 edge_degrees_[3] > 1 ? qr_dual_edge_[3]->NumPoints() : 0;
1486 const lf::base::size_type Nq =
1487 interior_degree_ > 1 ? fe1d_.NumEvaluationNodes() : 0;
1488 return 4 + Ne0 + Ne1 + Ne2 + Ne3 + Nq * Nq;
1489 }
1490
1491 [[nodiscard]] Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> NodalValuesToDofs(
1492 const Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> &nodevals) const override {
1493 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> dofs(NumRefShapeFunctions());
1494 const base::size_type Ne0 =
1495 edge_degrees_[0] > 1 ? qr_dual_edge_[0]->NumPoints() : 0;
1496 const base::size_type Ne1 =
1497 edge_degrees_[1] > 1 ? qr_dual_edge_[1]->NumPoints() : 0;
1498 const base::size_type Ne2 =
1499 edge_degrees_[2] > 1 ? qr_dual_edge_[2]->NumPoints() : 0;
1500 const base::size_type Ne3 =
1501 edge_degrees_[3] > 1 ? qr_dual_edge_[3]->NumPoints() : 0;
1502
1503 // Compute the basis function coefficients of the vertex basis functions
1504 dofs[0] = nodevals[0];
1505 dofs[1] = nodevals[1];
1506 dofs[2] = nodevals[2];
1507 dofs[3] = nodevals[3];
1508 // Compute the basis function coefficients on the edges
1509 // by applying the dual basis of the segment
1510 for (lf::base::size_type i = 2; i < Degree() + 1; ++i) {
1511 const SCALAR P0 = ilegendre_dx(i, 0);
1512 const SCALAR P1 = ilegendre_dx(i, 1);
1513
1514 // Compute the basis function coefficients for the first edge
1515 if (i <= edge_degrees_[0]) {
1516 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> psidd =
1517 qr_dual_edge_[0]->Points().unaryExpr(
1518 [&](double x) -> SCALAR { return legendre_dx(i - 1, x); });
1519 const SCALAR integ1 = (qr_dual_edge_[0]->Weights().transpose().array() *
1520 psidd.array() * nodevals.segment(4, Ne0).array())
1521 .sum();
1523 dofs[2 + i] =
1524 (P1 * nodevals[1] - P0 * nodevals[0] - integ1) * (2 * i - 1);
1525 } else {
1526 dofs[5 - i + (edge_degrees_[0] - 1)] =
1527 (P1 * nodevals[0] - P0 * nodevals[1] - integ1) * (2 * i - 1);
1528 }
1529 }
1530 if (i <= edge_degrees_[1]) {
1531 // Compute the basis function coefficients for the second edge
1532 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> psidd =
1533 qr_dual_edge_[1]->Points().unaryExpr(
1534 [&](double x) -> SCALAR { return legendre_dx(i - 1, x); });
1535 const SCALAR integ2 =
1536 (qr_dual_edge_[1]->Weights().transpose().array() * psidd.array() *
1537 nodevals.segment(4 + Ne0, Ne1).array())
1538 .sum();
1540 dofs[2 + i + (edge_degrees_[0] - 1)] =
1541 (P1 * nodevals[2] - P0 * nodevals[1] - integ2) * (2 * i - 1);
1542 } else {
1543 dofs[5 - i + (edge_degrees_[0] + edge_degrees_[1] - 2)] =
1544 (P1 * nodevals[1] - P0 * nodevals[2] - integ2) * (2 * i - 1);
1545 }
1546 }
1547 if (i <= edge_degrees_[2]) {
1548 // Compute the basis function coefficients for the third edge
1549 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> psidd =
1550 qr_dual_edge_[2]->Points().unaryExpr(
1551 [&](double x) -> SCALAR { return legendre_dx(i - 1, x); });
1552 const SCALAR integ3 =
1553 (qr_dual_edge_[2]->Weights().transpose().array() * psidd.array() *
1554 nodevals.segment(4 + Ne0 + Ne1, Ne2).array())
1555 .sum();
1557 dofs[2 + i + (edge_degrees_[0] + edge_degrees_[1] - 2)] =
1558 (P1 * nodevals[3] - P0 * nodevals[2] - integ3) * (2 * i - 1);
1559 } else {
1560 dofs[5 - i +
1561 (edge_degrees_[0] + edge_degrees_[1] + edge_degrees_[2] - 3)] =
1562 (P1 * nodevals[2] - P0 * nodevals[3] - integ3) * (2 * i - 1);
1563 }
1564 }
1565 if (i <= edge_degrees_[3]) {
1566 // Compute the basis function coefficients for the fourth edge
1567 Eigen::Matrix<SCALAR, 1, Eigen::Dynamic> psidd =
1568 qr_dual_edge_[3]->Points().unaryExpr(
1569 [&](double x) -> SCALAR { return legendre_dx(i - 1, x); });
1570 const SCALAR integ4 =
1571 (qr_dual_edge_[3]->Weights().transpose().array() * psidd.array() *
1572 nodevals.segment(4 + Ne0 + Ne1 + Ne2, Ne3).array())
1573 .sum();
1575 dofs[2 + i +
1576 (edge_degrees_[0] + edge_degrees_[1] + edge_degrees_[2] - 3)] =
1577 (P1 * nodevals[0] - P0 * nodevals[3] - integ4) * (2 * i - 1);
1578 } else {
1579 dofs[5 - i +
1581 edge_degrees_[3] - 4)] =
1582 (P1 * nodevals[3] - P0 * nodevals[0] - integ4) * (2 * i - 1);
1583 }
1584 }
1585 }
1586 if (interior_degree_ > 1) {
1587 // Compute the basis function coefficients for the face bubbles
1588 auto Nq = fe1d_.NumEvaluationNodes();
1589 Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> dof_temp(
1590 Nq, interior_degree_ - 1);
1591 for (unsigned i = 0; i < Nq; ++i) {
1592 dof_temp.row(i) = fe1d_
1593 .NodalValuesToDofs(nodevals.segment(
1594 4 + Ne0 + Ne1 + Ne2 + Ne3 + Nq * i, Nq))
1595 .segment(2, interior_degree_ - 1);
1596 }
1597 Eigen::Matrix<SCALAR, Eigen::Dynamic, Eigen::Dynamic> dof_temp2(
1599 for (unsigned i = 0; i < interior_degree_ - 1; ++i) {
1600 dofs.segment(edge_degrees_[0] + edge_degrees_[1] + edge_degrees_[2] +
1601 edge_degrees_[3] + i * (interior_degree_ - 1),
1602 interior_degree_ - 1) = dof_temp2.row(i) =
1603 fe1d_.NodalValuesToDofs(dof_temp.col(i))
1604 .segment(2, interior_degree_ - 1);
1605 }
1606 dofs.segment(edge_degrees_[0] + edge_degrees_[1] + edge_degrees_[2] +
1607 edge_degrees_[3],
1608 (interior_degree_ - 1) * (interior_degree_ - 1)) =
1609 Eigen::Map<Eigen::Matrix<SCALAR, 1, Eigen::Dynamic>>(
1610 dof_temp2.data(), 1,
1611 (interior_degree_ - 1) * (interior_degree_ - 1));
1612 }
1613 return dofs;
1614 }
1615
1616 private:
1618 std::array<unsigned, 4> edge_degrees_;
1619 std::array<const quad::QuadRule *, 4> qr_dual_edge_;
1621 fe1d_; // degree = max(interior_degree_, edge_degrees_)
1623};
1624
1625} // end namespace lf::fe
1626
1627#endif // LF_USCALFE_HP_FE_H_
Represents a reference element with all its properties.
Definition: ref_el.h:106
static constexpr RefEl kSegment()
Returns the (1-dimensional) reference segment.
Definition: ref_el.h:150
static constexpr RefEl kTria()
Returns the reference triangle.
Definition: ref_el.h:158
static constexpr RefEl kQuad()
Returns the reference quadrilateral.
Definition: ref_el.h:166
Hierarchic Finite Elements of arbitrary degree on quadrilaterals.
FeHierarchicQuad(const FeHierarchicQuad &)=default
lf::base::size_type NumRefShapeFunctions() const override
The local shape functions.
nonstd::span< const lf::mesh::Orientation > rel_orient_
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_
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.
nonstd::span< const lf::mesh::Orientation > rel_orient_
const lf::quad::QuadRule * qr_dual_tria_
std::array< const quad::QuadRule *, 3 > qr_dual_edge_
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: base.h:24
Collects data structures and algorithms designed for scalar finite element methods primarily meant fo...
Definition: fe.h:47
double ilegendre_dt(unsigned n, double x, double t)
Computes .
double ilegendre_dx(unsigned n, double x, double t)
Computes .
lf::assemble::dim_t dim_t
Definition: fe.h:56
double ijacobi(unsigned n, double alpha, double x)
Evaluate the integral of the (n-1)-th degree Jacobi Polynomial for .
double jacobi_dx(unsigned n, double alpha, double x)
Computes the derivative of the n-th degree Jacobi Polynomial for .
double ilegendre(unsigned n, double x, double t)
computes the integral of the (n-1)-th degree scaled Legendre Polynomial
lf::base::sub_idx_t sub_idx_t
Definition: fe.h:60
double legendre_dx(unsigned n, double x, double t)
Computes the derivative of the n-th degree scaled Legendre polynomial.
double jacobi(unsigned n, double alpha, double beta, double x)
Computes the n-th degree shifted Jacobi polynomial.
double legendre(unsigned n, double x, double t)
computes the n-th degree scaled Legendre Polynomial
double ijacobi_dx(unsigned n, double alpha, double x)
Computes the derivative of the n-th integrated scaled Jacobi polynomial.
Definition: assemble.h:30
Definition: span.h:584