PolyFEM
Loading...
Searching...
No Matches
PyramidQuadrature.cpp
Go to the documentation of this file.
2
3#include <vector>
4#include <cassert>
5#include <cmath>
6#include <iostream>
7
8namespace polyfem
9{
10 namespace quadrature
11 {
12 namespace
13 {
14 void get_weight_and_points(const int order, Eigen::MatrixXd &points, Eigen::VectorXd &weights)
15 {
16 switch (order)
17 {
19
20 default:
21 assert(false);
22 };
23 }
24 } // namespace
25
29
30 void PyramidQuadrature::get_quadrature(const int order, Quadrature &quad)
31 {
32 if (order <= 5)
33 {
34 // Tabulated Felippa rules — exact for pyramid rational function space,
35 // available for orders 1–5 (up to 27 quadrature points).
36 get_weight_and_points(order, quad.points, quad.weights);
37 assert(quad.weights.minCoeff() > 0 && "Felippa quadrature weight non-positive");
38 assert(fabs(quad.weights.sum() - 1.0 / 3.0) < 1e-12);
39 assert(quad.points.minCoeff() >= 0 && quad.points.maxCoeff() <= 1);
40 assert(quad.points.rows() == quad.weights.size());
41 return;
42 }
43
44 // For order > 5: Duffy transform + tensor-product Gauss-Legendre on [0,1]^3.
45 //
46 // Map: x = xi*(1-zeta), y = eta*(1-zeta), z = zeta, J = (1-zeta)^2
47 //
48 // Under this map, (x/(1-z))^a (y/(1-z))^b (1-z)^c becomes
49 // xi^a eta^b (1-zeta)^(c+2) — a pure polynomial. A monomial x^a y^b z^c
50 // (degree d = a+b+c) becomes xi^a eta^b zeta^c (1-zeta)^(a+b+2), which has
51 // zeta-degree d+2. An n-pt GL rule is exact for degree 2n-1, so we need
52 // n >= ceil((d+3)/2) => n = (order + 4) / 2 (integer division).
53
54 const int n = (order + 4) / 2; // 1D Gauss-Legendre points per direction
55
56 // 1D Gauss-Legendre nodes & weights on [-1,1], then mapped to [0,1].
57 // Use Eigen's built-in: nodes t in [-1,1], weights w, map t_01=(t+1)/2, w_01=w/2.
58 Eigen::VectorXd t(n), w(n);
59 // Compute via eigenvalue method (Golub-Welsch).
60 {
61 // Tridiagonal symmetric Jacobi matrix for Gauss-Legendre.
62 Eigen::MatrixXd J = Eigen::MatrixXd::Zero(n, n);
63 for (int i = 1; i < n; ++i)
64 {
65 const double b = i / std::sqrt(4.0 * i * i - 1.0);
66 J(i - 1, i) = b;
67 J(i, i - 1) = b;
68 }
69 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(J);
70 t = es.eigenvalues(); // nodes on [-1,1]
71 w = 2.0 * es.eigenvectors().row(0).cwiseAbs2().transpose(); // weights
72 }
73 // Map to [0,1]
74 const Eigen::VectorXd xi1d = (t.array() + 1.0) * 0.5;
75 const Eigen::VectorXd w1d = w * 0.5;
76
77 const int nq = n * n * n;
78 quad.points.resize(nq, 3);
79 quad.weights.resize(nq);
80
81 int idx = 0;
82 for (int i = 0; i < n; ++i) // xi direction
83 for (int j = 0; j < n; ++j) // eta direction
84 for (int k = 0; k < n; ++k) // zeta direction
85 {
86 const double xi = xi1d(i);
87 const double eta = xi1d(j);
88 const double zeta = xi1d(k);
89 const double one_minus_zeta = 1.0 - zeta;
90 // Duffy Jacobian = (1-zeta)^2 must be strictly positive;
91 // GL nodes never reach the endpoint zeta=1 (the apex singularity).
92 assert(one_minus_zeta > 0 && "Duffy Jacobian degenerate: quadrature point at pyramid apex");
93 quad.points(idx, 0) = xi * one_minus_zeta; // x
94 quad.points(idx, 1) = eta * one_minus_zeta; // y
95 quad.points(idx, 2) = zeta; // z
96 quad.weights(idx) = w1d(i) * w1d(j) * w1d(k) * one_minus_zeta * one_minus_zeta;
97 ++idx;
98 }
99
100 assert(quad.weights.minCoeff() > 0 && "Duffy quadrature weight non-positive");
101 assert(fabs(quad.weights.sum() - 1.0 / 3.0) < 1e-10);
102 assert(quad.points.minCoeff() >= -1e-14);
103 assert(quad.points.rows() == quad.weights.size());
104 }
105 } // namespace quadrature
106} // namespace polyfem
Quadrature quadrature
void get_quadrature(const int order, Quadrature &quad)