PolyFEM
Loading...
Searching...
No Matches
pyramid_duffy.py
Go to the documentation of this file.
1import numpy as np
2import sympy
3
5 """Exact integral of f over the reference pyramid: 0<=z<=1, 0<=x,y<=1-z."""
6 x, y, z = sympy.symbols('x y z')
7 return sympy.integrate(
8 sympy.integrate(
9 sympy.integrate(f, (x, 0, 1 - z)),
10 (y, 0, 1 - z)),
11 (z, 0, 1))
12
14 """
15 Build a pyramid quadrature rule via the Duffy transform:
16 x = xi*(1-zeta), y = eta*(1-zeta), z = zeta
17 J = (1-zeta)^2
18 A tensor product of n 1D Gauss-Legendre points (n = ceil((order+1)/2))
19 integrates the pulled-back polynomial stiffness integrand exactly for
20 pyramid basis degree p = order//2.
21
22 Returns (pts, weights) on the reference pyramid [0<=x,y<=1-z, 0<=z<=1].
23 Weights sum to 1/3.
24 """
25 n = (order + 4) // 2 # ceil((order+3)/2): exact for degree (order+2) in zeta
26
27 # 1D Gauss-Legendre on [-1,1]
28 t, w = np.polynomial.legendre.leggauss(n)
29
30 # Map to [0,1]: xi = (t+1)/2, w_1d = w/2
31 xi1d = (t + 1.0) / 2.0
32 w1d = w / 2.0
33
34 pts = []
35 weights = []
36 for i in range(n): # xi
37 for j in range(n): # eta
38 for k in range(n): # zeta
39 xi, eta, zeta = xi1d[i], xi1d[j], xi1d[k]
40 x = xi * (1.0 - zeta)
41 y = eta * (1.0 - zeta)
42 z = zeta
43 w_total = w1d[i] * w1d[j] * w1d[k] * (1.0 - zeta)**2
44 pts.append([x, y, z])
45 weights.append(w_total)
46
47 pts = np.array(pts)
48 weights = np.array(weights)
49 return pts, weights
50
51
52def verify_duffy(max_order=8):
53 """
54 Verify that duffy_quadrature integrates all monomials x^i y^j z^k
55 (i+j+k <= order) exactly over the reference pyramid.
56 """
57 import sympy
58 x, y, z = sympy.symbols('x y z')
59
60 for order in range(1, max_order + 1):
61 pts, weights = duffy_quadrature(order)
62 max_err = 0.0
63 for i in range(order + 1):
64 for j in range(order + 1):
65 for k in range(order + 1):
66 if i + j + k > order:
67 continue
68 # exact integral of x^i y^j z^k over pyramid
69 f = x**i * y**j * z**k
70 exact = float(integrate_exact(f))
71 approx = float(np.sum(weights * pts[:,0]**i
72 * pts[:,1]**j
73 * pts[:,2]**k))
74 max_err = max(max_err, abs(exact - approx))
75 print(f"order={order}, n={(order+2)//2}^3={(((order+2)//2)**3)} pts, "
76 f"max monomial error={max_err:.2e}, "
77 f"weight sum={weights.sum():.10f} (should be {1/3:.10f})")
78
79
80# Also verify against the rational pyramid basis integrands:
82 """
83 Verify Duffy integrates the actual pyramid rational basis products exactly.
84 Tests xy/(1-z), x^2*y/(1-z)^2, etc. that appear in stiffness integrals.
85 """
86 import sympy
87 x, y, z = sympy.symbols('x y z')
88
89 for p in range(1, max_p + 1):
90 order = 2 * p # stiffness integrand degree
91 pts, weights = duffy_quadrature(order)
92 max_err = 0.0
93 # Test basis-like rational monomials (x/(1-z))^a (y/(1-z))^b (1-z)^c
94 for a in range(p + 1):
95 for b in range(p + 1):
96 for c in range(p + 1):
97 # rational function on pyramid: x^a y^b / (1-z)^(a+b-c)
98 # typical stiffness term after two gradient evaluations
99 f = x**a * y**b / (1 - z)**(a + b - c) if (a+b-c) >= 0 else x**a * y**b * (1-z)**(c-a-b)
100 try:
101 exact = float(integrate_exact(f))
102 except Exception:
103 continue
104 pts_x, pts_y, pts_z = pts[:,0], pts[:,1], pts[:,2]
105 if a + b - c >= 0:
106 approx = float(np.sum(weights * pts_x**a * pts_y**b
107 / (1 - pts_z)**(a+b-c)))
108 else:
109 approx = float(np.sum(weights * pts_x**a * pts_y**b
110 * (1 - pts_z)**(c-a-b)))
111 max_err = max(max_err, abs(exact - approx))
112 print(f"p={p}, order={order}, max rational error={max_err:.2e}")
113
114if __name__ == "__main__":
115 print("=== Monomial verification ===")
116 verify_duffy(max_order=8)
117 print()
118 print("=== Rational basis verification ===")
duffy_quadrature(order)
verify_duffy(max_order=8)
verify_duffy_rational(max_p=4)