PolyFEM
Loading...
Searching...
No Matches
triangle.py
Go to the documentation of this file.
1# -*- coding: utf-8 -*-
2
3import math
4import textwrap
5import numpy
6import sympy
7import quadpy
8
9
11 """
12 Integrate a function over the reference triangle (0,0), (1,0), (0,1).
13 """
14 x, y = sympy.symbols('x y')
15 exact = sympy.integrate(
16 sympy.integrate(f, (y, 0, 1 - x)),
17 (x, 0, 1)
18 )
19 return exact
20
21
22def integrate_approx(f, scheme):
23 """
24 Integrate numerically over the reference triangle.
25 """
26 x, y = sympy.symbols('x y')
27 vol = 0.5 # area of the reference triangle
28 res = 0
29 for i, w in enumerate(scheme.weights):
30 res += w * f.subs([(x, scheme.points[i, 0]), (y, scheme.points[i, 1])])
31 return vol * res
32
33
35 """
36 List all existing schemes for triangles.
37 """
38 L = [(quadpy.triangle.BerntsenEspelid(k), 1.0e-11) for k in range(1, 5)] \
39 + [(quadpy.triangle.Centroid(), 1.0e-14)] \
40 + [(quadpy.triangle.CoolsHaegemans(k), 1.0e-13) for k in [1]] \
41 + [(quadpy.triangle.Cubtri(), 1.0e-14)] \
42 + [(quadpy.triangle.Dunavant(k), 1.0e-12) for k in range(1, 21)] \
43 + [(quadpy.triangle.Gatermann(), 1.0e-12)] \
44 + [(quadpy.triangle.GrundmannMoeller(k), 1.0e-12) for k in range(10)] \
45 + [(quadpy.triangle.HammerMarloweStroud(k), 1.0e-14) for k in range(1, 6)] \
46 + [(quadpy.triangle.HammerStroud(k), 1.0e-14) for k in [2, 3]] \
47 + [(quadpy.triangle.Hillion(k), 1.0e-14) for k in range(1, 11)] \
48 + [(quadpy.triangle.LaursenGellert(key), 1.0e-13) for key in quadpy.triangle.LaursenGellert.keys] \
49 + [(quadpy.triangle.Lether(k), 1.0e-14) for k in range(1, 14)] \
50 + [(quadpy.triangle.LiuVinokur(k), 1.0e-14) for k in range(1, 14)] \
51 + [(quadpy.triangle.LynessJespersen(k), 1.0e-11) for k in range(1, 22)] \
52 + [(quadpy.triangle.NewtonCotesClosed(k), 1.0e-14) for k in range(1, 6)] \
53 + [(quadpy.triangle.NewtonCotesOpen(k), 1.0e-13) for k in range(6)] \
54 + [(quadpy.triangle.Papanicolopulos('fs', k), 1.0e-13) for k in range(9)] \
55 + [(quadpy.triangle.Papanicolopulos('rot', k), 1.0e-14)
56 # The first 8 schemes are flawed by round-off error
57 for k in range(8, 18)] \
58 + [(quadpy.triangle.SevenPoint(), 1.0e-14)] \
59 + [(quadpy.triangle.Strang(k), 1.0e-14) for k in range(1, 11)] \
60 + [(quadpy.triangle.Stroud(k), 1.0e-12) for k in ['T2 3-1', 'T2 5-1', 'T2 7-1']] \
61 + [(quadpy.triangle.TaylorWingateBos(k), 1.0e-12) for k in [1, 2, 4, 5, 8]] \
62 + [(quadpy.triangle.Triex(k), 1.0e-13) for k in [19, 28]] \
63 + [(quadpy.triangle.Vertex(), 1.0e-14)] \
64 + [(quadpy.triangle.VioreanuRokhlin(k), 1.0e-11) for k in range(20)] \
65 + [(quadpy.triangle.Walkington(k), 1.0e-14) for k in [1, 2, 3, 5, 'p5']] \
66 + [(quadpy.triangle.WandzuraXiao(k), 1.0e-14) for k in range(1, 7)] \
67 + [(quadpy.triangle.WilliamsShunnJameson(k), 1.0e-11) for k in range(1, 9)] \
68 + [(quadpy.triangle.WitherdenVincent(k), 1.0e-14) for k in [
69 1, 2, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20
70 ]] \
71 + [(quadpy.triangle.XiaoGimbutas(k), 1.0e-14) for k in range(1, 51)] \
72 + [(quadpy.triangle.ZhangCuiLiu(k), 1.0e-14) for k in [1, 2, 3]]
73
74 return L
75
76
78 """
79 Generate bivariate monomials up to given order.
80 """
81 monoms = []
82 x, y = sympy.symbols('x y')
83 for i in range(0, order + 1):
84 for j in range(0, order + 1):
85 if i + j <= order:
86 monoms.append(x**i * y**j)
87 return monoms
88
89
90def is_valid(scheme, tol=1e-6):
91 """
92 A scheme is valid if:
93 1. All its weights sums up to one;
94 2. All the weights are positive;
95 3. All its points are inside the reference triangle;
96 4. No point lie on an edge.
97 """
98 return math.isclose(numpy.sum(scheme.weights), 1.0, rel_tol=1e-10) \
99 and (scheme.weights >= tol).all() \
100 and (scheme.points >= 0).all() \
101 and (scheme.points[:, 0] + scheme.points[:, 1] <= 1 - tol).all() \
102 and (scheme.points[:, 0] >= tol).all() \
103 and (scheme.points[:, 1] >= tol).all()
104
105
106def pick_scheme(all_schemes, order):
107 """
108 Picks the best scheme for a given polynomial degree, following this strategy:
109 - Tries all schemes with the same order as required.
110 - Eliminate schemes with weights that do not sum up to 1, or points that lie
111 outside the reference triangle.
112 - Among the remaining schemes, compare the integration error over all bivariate
113 monomials of inferior order.
114 - Keeps only the scheme with total integration error lower than a certain threshold.
115 - Among those, keep the one with fewer number of integration points.
116 - Finally, break ties using the integration error accumulated over the monomials.
117 """
118 monoms = generate_monomials(order)
119 print(monoms)
120 min_points = None
121 best_error = None
122 best_scheme = None
123 threshold = 1e-12 # Only accept quadrature rules with this much precision
124 for scheme, __ in all_schemes:
125 if scheme.degree == order and is_valid(scheme):
126 N = len(scheme.weights)
127 ok = True
128 error = 0
129 for poly in monoms:
130 exact = integrate_exact(poly)
131 approx = integrate_approx(poly, scheme)
132 # print(abs(exact - approx), tol)
133 if not math.isclose(exact - approx, 0, abs_tol=threshold):
134 ok = False
135 error += abs(exact - approx)
136 if ok:
137 if (min_points is None) or N < min_points:
138 min_points = N
139 best_error = error
140 best_scheme = scheme
141 elif N == min_points and error < best_error:
142 best_error = error
143 best_scheme = scheme
144 return best_scheme
145
146
147def generate_cpp(selected_schemes):
148 """
149 Generate cpp code to fill points & weights.
150 """
151 res = []
152 for order, scheme in selected_schemes:
153 code = """\
154 case {order}: // {name}
155 points.resize({num_pts}, 2);
156 weights.resize({num_pts}, 1);
157 points << {points};
158 weights << {weights};
159 break;
160 """.format(order=order,
161 name=scheme.name,
162 num_pts=len(scheme.weights),
163 points=', '.join('{:.64g}'.format(w) for w in scheme.points.flatten()),
164 weights=', '.join('{:.64g}'.format(w) for w in scheme.weights))
165 res.append(textwrap.dedent(code))
166 return ''.join(res)
167
168
169def main():
170 all_schemes = list_schemes()
171 selected = []
172 for order in range(1, 16):
173 scheme = pick_scheme(all_schemes, order)
174 selected.append((order, scheme))
175 code = generate_cpp(selected)
176 # print(code)
177 with open('../auto_triangle.ipp', 'w') as f:
178 f.write(code)
179
180
181if __name__ == '__main__':
182 main()
is_valid(scheme, tol=1e-6)
Definition triangle.py:90
integrate_exact(f)
Definition triangle.py:10
pick_scheme(all_schemes, order)
Definition triangle.py:106
list_schemes()
Definition triangle.py:34
generate_cpp(selected_schemes)
Definition triangle.py:147
generate_monomials(order)
Definition triangle.py:77
integrate_approx(f, scheme)
Definition triangle.py:22