PolyFEM
Loading...
Searching...
No Matches
pyramid.py
Go to the documentation of this file.
1# -*- coding: utf-8 -*-
2import math
3import textwrap
4import numpy as np
5import sympy
6# https://github.com/yib0liu/legacy-quadpy commit 5213fed9155e3cf0e62900a7483ecf9abb23d48a
7import quadpy
8
9b = np.array([
10 [0,0,0],
11 [0,1,0],
12 [1,1,0],
13 [1,0,0],
14 [0,0,1]
15])
16
18 """
19 Integrate a function over the reference pyramid:
20 0 <= z <= 1
21 0 <= x <= (1 - z)
22 0 <= y <= (1 - z)
23 """
24 import sympy
25 x, y, z = sympy.symbols('x y z')
26
27 exact = sympy.integrate(
28 sympy.integrate(
29 sympy.integrate(f, (x,0, (1 - z))),
30 (y, 0, (1 - z)))
31 , (z, 0, 1)
32 )
33 return exact
34
35
36
37def integrate_approx(f, scheme):
38 """
39 Integrate numerically over the reference tetrahedron.
40 """
41 x, y, z = sympy.symbols('x y z')
42 res = 0
43 pts=scheme.transform(b)
44 weights=scheme.transform_w(b)
45 for i, w in enumerate(weights):
46 res += w * f.subs([(x, pts[i, 0]),
47 (y, pts[i, 1]), (z, pts[i, 2])])
48 return res
49
50
52 """
53 List all existing schemes for tetrahedra.
54 """
55 L = [quadpy.p3.felippa_1()] \
56 + [quadpy.p3.felippa_2()] \
57 + [quadpy.p3.felippa_3()] \
58 + [quadpy.p3.felippa_4()] \
59 + [quadpy.p3.felippa_5()] \
60 + [quadpy.p3.felippa_6()] \
61 + [quadpy.p3.felippa_7()] \
62 + [quadpy.p3.felippa_8()] \
63 + [quadpy.p3.felippa_9()] \
64
65 return L
66
67
69 """
70 Generate trivariate monomials up to given order.
71 """
72 monoms = []
73 x, y, z = sympy.symbols('x y z')
74 for i in range(0, order + 1):
75 for j in range(0, order + 1):
76 for k in range(0, order + 1):
77 if i + j + k <= order:
78 monoms.append(x**i * y**j * z**k)
79 return monoms
80
81
82def is_valid(scheme, tol=1e-6, relaxed=False):
83 """
84 A scheme is valid if:
85 1. weights sum to 1
86 2. weights positive
87 3. points inside reference pyramid:
88 0 <= z <= 1
89 |x|, |y| <= 1 - z
90 """
91 pts=scheme.transform(b)
92 w=scheme.transform_w(b)
93 print("sum w: ", sum(w))
94
95 x, y, z = pts[:, 0], pts[:, 1], pts[:, 2]
96 cond_z = (z >= 0 - tol) & (z <= 1 + tol)
97 cond_x = (x >= 0 - tol) & (x <= (1 - z) + tol)
98 cond_y = (y >= 0 - tol) & (y <= (1 - z) + tol)
99 cond_inside = cond_z & cond_x & cond_y
100
101 print("is valid ",(
102 math.isclose(np.sum(w), 0.3333333333333333, rel_tol=1e-10)
103 ,(relaxed or (w >= tol).all())
104 # , cond_z.all() and cond_x.all() and cond_y.all()
105 ,cond_inside.all()
106 ))
107
108 return (
109 math.isclose(np.sum(w), 0.3333333333333333, rel_tol=1e-10)
110 and (relaxed or (w >= tol).all())
111 # and cond_z.all() and cond_x.all() and cond_y.all()
112 and cond_inside.all()
113 )
114
115def pick_scheme(all_schemes, order, relaxed=False):
116 """
117 Picks the best scheme for a given polynomial degree, following this strategy:
118 - Tries all schemes with the same order as required.
119 - Eliminate schemes with weights that do not sum up to 1, or points that lie
120 outside the reference triangle.
121 - Among the remaining schemes, compare the integration error over all bivariate
122 monomials of inferior order.
123 - Keeps only the scheme with total integration error lower than a certain threshold.
124 - Among those, keep the one with fewer number of integration points.
125 - Finally, break ties using the integration error accumulated over the monomials.
126 """
127 monoms = generate_monomials(order)
128 print(monoms)
129 min_points = None
130 best_error = None
131 best_scheme = None
132 threshold = 1e-12 # Only accept quadrature rules with this much precision
133 for scheme in all_schemes:
134 if scheme.degree == order and is_valid(scheme, relaxed=relaxed):
135 N = len(scheme.weights)
136 ok = True
137 error = 0
138 for poly in monoms:
139 exact = integrate_exact(poly)
140 approx = integrate_approx(poly, scheme)
141 # print(abs(exact - approx), tol)
142 if not math.isclose(exact - approx, 0, abs_tol=threshold):
143 ok = False
144 error += abs(exact - approx)
145 if ok:
146 if (min_points is None) or N < min_points:
147 min_points = N
148 best_error = error
149 best_scheme = scheme
150 elif N == min_points and error < best_error:
151 best_error = error
152 best_scheme = scheme
153 return best_scheme, best_error
154
155def generate_cpp(selected_schemes):
156 """
157 Generate cpp code to fill points & weights.
158 """
159 res = []
160 for order, scheme in selected_schemes:
161 try:
162 name = scheme.name
163 except AttributeError:
164 name = scheme.__class__.__name__
165 points=scheme.transform(b)
166 weights=scheme.transform_w(b)
167 code = """\
168 case {order}: // {name}
169 points.resize({num_pts}, 3);
170 weights.resize({num_pts}, 1);
171 points << {points};
172 weights << {weights};
173 break;
174 """.format(order=order,
175 name=name,
176 num_pts=len(weights),
177 points=', '.join('{:.64g}'.format(w)
178 for w in points.flatten()),
179 weights=', '.join('{:.64g}'.format(w) for w in weights))
180 res.append(textwrap.dedent(code))
181 return ''.join(res)
182
183
184def main():
185 all_schemes = list_schemes()
186 selected = []
187 for order in [1,2,3,5]: # legacy-quadpy only support these
188 scheme, error = pick_scheme(all_schemes, order)
189 if scheme is None:
190 scheme, error = pick_scheme(all_schemes, order, relaxed=True)
191 # assert scheme is not None
192 if scheme is not None:
193 selected.append((order, scheme))
194 code = generate_cpp(selected)
195 with open('../auto_pyramid.ipp', 'w') as f:
196 f.write(code)
197 print("write")
198
199
200if __name__ == '__main__':
201 main()
generate_monomials(order)
Definition pyramid.py:68
generate_cpp(selected_schemes)
Definition pyramid.py:155
integrate_approx(f, scheme)
Definition pyramid.py:37
integrate_exact(f)
Definition pyramid.py:17
is_valid(scheme, tol=1e-6, relaxed=False)
Definition pyramid.py:82
list_schemes()
Definition pyramid.py:51
pick_scheme(all_schemes, order, relaxed=False)
Definition pyramid.py:115