Skip to content

Commit 105f916

Browse files
committed
Laplacian of trace kernel for internal dofs TNT quadrilateral
According to https://github.com/mscroggs/symfem/blob/main/symfem/elements/tnt.py, we need to multiply by the primary bubble function, and take the laplacian. To this end tabulation which also generates the derivatives is used.
1 parent 9ee46f0 commit 105f916

File tree

1 file changed

+67
-35
lines changed

1 file changed

+67
-35
lines changed

python/demo/demo_tnt-elements.py

Lines changed: 67 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@
6060
#
6161
# We begin by defining a basis of the polynomial space spanned by the
6262
# TNT element, which is defined in terms of the orthogonal Legendre
63-
# polynomials on the cell. For a degree 1 element, the polynomial set
63+
# polynomials on the cell. For a degree 2 element (here, we use the
64+
# superdegree rather than the conventional subdegree to allign with
65+
# the definition of other elements), the polynomial set
6466
# contains $1$, $y$, $y^2$, $x$, $xy$, $xy^2$, $x^2$, and $x^2y$, which
6567
# are the first 8 polynomials in the degree 2 set of polynomials on a
6668
# quadrilateral. We create an $8 \times 9$ matrix (number of dofs by
@@ -146,7 +148,7 @@
146148
# We now create the element. Using the Basix UFL interface, we can wrap
147149
# this element so that it can be used with FFCx/DOLFINx.
148150

149-
tnt_degree1 = basix.ufl.custom_element(
151+
tnt_degree2 = basix.ufl.custom_element(
150152
basix.CellType.quadrilateral,
151153
[],
152154
wcoeffs,
@@ -168,23 +170,26 @@
168170

169171

170172
def create_tnt_quad(degree):
171-
assert degree > 1
173+
assert degree > 0
172174
# Polyset
173-
ndofs = (degree + 1) ** 2 + 4
174-
npoly = (degree + 2) ** 2
175+
ndofs = 4*degree + max(degree-2,0)**2
176+
npoly = (degree + 1) ** 2
175177

176178
wcoeffs = np.zeros((ndofs, npoly))
177179

178180
dof_n = 0
179-
for i in range(degree + 1):
180-
for j in range(degree + 1):
181-
wcoeffs[dof_n, i * (degree + 2) + j] = 1
181+
for i in range(degree):
182+
for j in range(degree):
183+
wcoeffs[dof_n, i * (degree + 1) + j] = 1
182184
dof_n += 1
183185

184-
for i, j in [(degree + 1, 1), (degree + 1, 0), (1, degree + 1), (0, degree + 1)]:
185-
wcoeffs[dof_n, i * (degree + 2) + j] = 1
186+
for i, j in [(degree, 1), (degree, 0), (0, degree)]:
187+
wcoeffs[dof_n, i * (degree + 1) + j] = 1
186188
dof_n += 1
187189

190+
if degree > 1:
191+
wcoeffs[dof_n, 2 * degree + 1] = 1
192+
188193
# Interpolation
189194
geometry = basix.geometry(basix.CellType.quadrilateral)
190195
topology = basix.topology(basix.CellType.quadrilateral)
@@ -197,31 +202,44 @@ def create_tnt_quad(degree):
197202
M[0].append(np.array([[[[1.0]]]]))
198203

199204
# Edges
200-
pts, wts = basix.make_quadrature(basix.CellType.interval, 2 * degree)
201-
poly = basix.tabulate_polynomials(
202-
basix.PolynomialType.legendre, basix.CellType.interval, degree - 1, pts
203-
)
204-
edge_ndofs = poly.shape[0]
205-
for e in topology[1]:
206-
v0 = geometry[e[0]]
207-
v1 = geometry[e[1]]
208-
edge_pts = np.array([v0 + p * (v1 - v0) for p in pts])
209-
x[1].append(edge_pts)
210-
211-
mat = np.zeros((edge_ndofs, 1, len(pts), 1))
212-
for i in range(edge_ndofs):
213-
mat[i, 0, :, 0] = wts[:] * poly[i, :]
214-
M[1].append(mat)
205+
if degree < 2:
206+
for _ in topology[1]:
207+
x[1].append(np.zeros([0, 2]))
208+
M[1].append(np.zeros([0, 1, 0, 1]))
209+
else:
210+
pts, wts = basix.make_quadrature(basix.CellType.interval, 2 * degree - 2)
211+
poly = basix.tabulate_polynomials(
212+
basix.PolynomialType.legendre, basix.CellType.interval, degree - 2, pts
213+
)
214+
edge_ndofs = poly.shape[0]
215+
for e in topology[1]:
216+
v0 = geometry[e[0]]
217+
v1 = geometry[e[1]]
218+
edge_pts = np.array([v0 + p * (v1 - v0) for p in pts])
219+
x[1].append(edge_pts)
220+
221+
mat = np.zeros((edge_ndofs, 1, len(pts), 1))
222+
for i in range(edge_ndofs):
223+
mat[i, 0, :, 0] = wts[:] * poly[i, :]
224+
M[1].append(mat)
215225

216226
# Interior
217-
if degree == 1:
227+
if degree < 3:
218228
x[2].append(np.zeros([0, 2]))
219229
M[2].append(np.zeros([0, 1, 0, 1]))
220230
else:
221-
pts, wts = basix.make_quadrature(basix.CellType.quadrilateral, 2 * degree - 1)
222-
poly = basix.tabulate_polynomials(
223-
basix.PolynomialType.legendre, basix.CellType.quadrilateral, degree - 2, pts
231+
pts, wts = basix.make_quadrature(basix.CellType.quadrilateral, 2 * degree - 2)
232+
u = pts[:, 0]
233+
v = pts[:, 1]
234+
pol_set = basix.polynomials.tabulate_polynomial_set(
235+
basix.CellType.quadrilateral, basix.PolynomialType.legendre, degree - 3, 2, pts
224236
)
237+
# this assumes the conventional [0 to 1][0 to 1] domain of the reference element,
238+
# and takes the Laplacian of (1-u)*u*(1-v)*v*pol_set[0],
239+
# cf https://github.com/mscroggs/symfem/blob/main/symfem/elements/tnt.py
240+
poly = (pol_set[5]+pol_set[3])*(u-1)*u*(v-1)*v+ \
241+
2*(pol_set[2]*(u-1)*u*(2*v-1)+pol_set[1]*(v-1)*v*(2*u-1)+ \
242+
pol_set[0]*((u-1)*u+(v-1)*v))
225243
face_ndofs = poly.shape[0]
226244
x[2].append(pts)
227245
mat = np.zeros((face_ndofs, 1, len(pts), 1))
@@ -239,8 +257,8 @@ def create_tnt_quad(degree):
239257
basix.MapType.identity,
240258
basix.SobolevSpace.H1,
241259
False,
260+
max(degree - 1, 1),
242261
degree,
243-
degree + 1,
244262
dtype=default_real_type,
245263
)
246264

@@ -296,12 +314,12 @@ def poisson_error(V: fem.FunctionSpace):
296314
tnt_degrees = []
297315
tnt_errors = []
298316

299-
V = fem.functionspace(msh, tnt_degree1)
317+
V = fem.functionspace(msh, tnt_degree2)
300318
tnt_degrees.append(2)
301319
tnt_ndofs.append(V.dofmap.index_map.size_global)
302320
tnt_errors.append(poisson_error(V))
303321
print(f"TNT degree 2 error: {tnt_errors[-1]}")
304-
for degree in range(2, 9):
322+
for degree in range(1, 9):
305323
V = fem.functionspace(msh, create_tnt_quad(degree))
306324
tnt_degrees.append(degree + 1)
307325
tnt_ndofs.append(V.dofmap.index_map.size_global)
@@ -317,6 +335,17 @@ def poisson_error(V: fem.FunctionSpace):
317335
q_ndofs.append(V.dofmap.index_map.size_global)
318336
q_errors.append(poisson_error(V))
319337
print(f"Q degree {degree} error: {q_errors[-1]}")
338+
339+
s_ndofs = []
340+
s_degrees = []
341+
s_errors = []
342+
for degree in range(1, 9):
343+
V = fem.functionspace(msh, ("S", degree))
344+
s_degrees.append(degree)
345+
s_ndofs.append(V.dofmap.index_map.size_global)
346+
s_errors.append(poisson_error(V))
347+
print(f"S degree {degree} error: {s_errors[-1]}")
348+
320349
# -
321350

322351
# We now plot the data that we have obtained. First we plot the error
@@ -326,27 +355,30 @@ def poisson_error(V: fem.FunctionSpace):
326355
if MPI.COMM_WORLD.rank == 0: # Only plot on one rank
327356
plt.plot(q_degrees, q_errors, "bo-")
328357
plt.plot(tnt_degrees, tnt_errors, "gs-")
358+
plt.plot(s_degrees, s_errors, "rD-")
329359
plt.yscale("log")
330360
plt.xlabel("Polynomial degree")
331361
plt.ylabel("Error")
332-
plt.legend(["Q", "TNT"])
362+
plt.legend(["Q", "TNT", "S"])
333363
plt.savefig("demo_tnt-elements_degrees_vs_error.png")
334364
plt.clf()
335365

336366
# ![](demo_tnt-elements_degrees_vs_error.png)
337367
#
338368
# A key advantage of TNT elements is that for a given degree, they span
339-
# a smaller polynomial space than Q elements. This can be observed in
369+
# a smaller polynomial space than Q tensorproduct elements. This can be observed in
340370
# the following diagram, where we plot the error against the square root
341371
# of the number of DOFs (providing a measure of cell size in 2D)
372+
# S serendipity element perform even better here.
342373

343374
if MPI.COMM_WORLD.rank == 0: # Only plot on one rank
344375
plt.plot(np.sqrt(q_ndofs), q_errors, "bo-")
345376
plt.plot(np.sqrt(tnt_ndofs), tnt_errors, "gs-")
377+
plt.plot(np.sqrt(s_ndofs), s_errors, "rD-")
346378
plt.yscale("log")
347379
plt.xlabel("Square root of number of DOFs")
348380
plt.ylabel("Error")
349-
plt.legend(["Q", "TNT"])
381+
plt.legend(["Q", "TNT", "S"])
350382
plt.savefig("demo_tnt-elements_ndofs_vs_error.png")
351383
plt.clf()
352384

0 commit comments

Comments
 (0)