Skip to content

Commit cba7bb2

Browse files
rckirbypbrubeck
andauthored
Rckirby/ps (#78)
* Add Powell-Sabin elements * Add test for PS akin to HCT test --------- Co-authored-by: Pablo Brubeck <brubeck@protonmail.com>
1 parent eb34beb commit cba7bb2

File tree

4 files changed

+188
-2
lines changed

4 files changed

+188
-2
lines changed

FIAT/__init__.py

+4
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131
from FIAT.morley import Morley
3232
from FIAT.nedelec import Nedelec
3333
from FIAT.nedelec_second_kind import NedelecSecondKind
34+
from FIAT.powell_sabin import QuadraticPowellSabin6
35+
from FIAT.powell_sabin import QuadraticPowellSabin12
3436
from FIAT.hierarchical import Legendre, IntegratedLegendre
3537
from FIAT.P0 import P0
3638
from FIAT.raviart_thomas import RaviartThomas
@@ -93,6 +95,8 @@
9395
"Regge": Regge,
9496
"EnrichedElement": EnrichedElement,
9597
"NodalEnrichedElement": NodalEnrichedElement,
98+
"QuadraticPowellSabin6": QuadraticPowellSabin6,
99+
"QuadraticPowellSabin12": QuadraticPowellSabin12,
96100
"TensorProductElement": TensorProductElement,
97101
"BrokenElement": DiscontinuousElement,
98102
"HDiv Trace": HDivTrace,

FIAT/macro.py

+44-2
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@
33

44
import numpy
55

6-
from FIAT.quadrature import FacetQuadratureRule, QuadratureRule
7-
from FIAT.reference_element import SimplicialComplex, lattice_iter, make_lattice
86
from FIAT import expansions, polynomial_set
7+
from FIAT.quadrature import FacetQuadratureRule, QuadratureRule
8+
from FIAT.reference_element import (TRIANGLE, SimplicialComplex, lattice_iter,
9+
make_lattice)
910

1011

1112
def bary_to_xy(verts, bary, result=None):
@@ -320,6 +321,47 @@ def construct_subcomplex(self, dimension):
320321
return PowellSabinSplit(ref_el)
321322

322323

324+
class PowellSabin12Split(SplitSimplicialComplex):
325+
"""Splits a triangle (only!) by connecting each vertex to the opposite
326+
edge midpoint and edge midpoints to each other.
327+
"""
328+
def __init__(self, ref_el):
329+
assert ref_el.get_shape() == TRIANGLE
330+
verts = ref_el.get_vertices()
331+
new_verts = list(verts)
332+
new_verts.extend(
333+
map(tuple, bary_to_xy(verts,
334+
[(1/3, 1/3, 1/3),
335+
(1/2, 1/2, 0),
336+
(1/2, 0, 1/2),
337+
(0, 1/2, 1/2),
338+
(1/2, 1/4, 1/4),
339+
(1/4, 1/2, 1/4),
340+
(1/4, 1/4, 1/2)])))
341+
342+
edges = [(0, 4), (0, 7), (0, 5),
343+
(1, 4), (1, 8), (1, 6),
344+
(2, 5), (2, 9), (2, 6),
345+
(3, 4), (3, 5), (3, 6), (3, 7), (3, 8), (3, 9),
346+
(4, 7), (4, 8), (5, 7), (5, 9), (6, 8), (6, 9)]
347+
348+
super(PowellSabin12Split, self).__init__(
349+
ref_el, tuple(new_verts), make_topology(2, len(new_verts), edges))
350+
351+
def construct_subcomplex(self, dimension):
352+
"""Constructs the reference subcomplex of the parent cell subentity
353+
specified by subcomplex dimension.
354+
"""
355+
if dimension == 2:
356+
return self
357+
elif dimension == 1:
358+
return AlfeldSplit(self.construct_subelement(1))
359+
elif dimension == 0:
360+
return self.construct_subelement(0)
361+
else:
362+
raise ValueError("Illegal dimension")
363+
364+
323365
class MacroQuadratureRule(QuadratureRule):
324366
"""Composite quadrature rule on parent facets that respects the splitting.
325367

FIAT/powell_sabin.py

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
# Copyright (C) 2024 Robert C. Kirby
2+
#
3+
# This file is part of FIAT (https://www.fenicsproject.org)
4+
#
5+
# SPDX-License-Identifier: LGPL-3.0-or-later
6+
#
7+
# Written by Robert C. Kirby (robert.c.kirby@gmail.com), 2024
8+
9+
from FIAT import dual_set, finite_element, macro, polynomial_set
10+
from FIAT.functional import (IntegralMomentOfNormalDerivative, PointDerivative,
11+
PointEvaluation)
12+
from FIAT.jacobi import eval_jacobi_batch
13+
from FIAT.quadrature_schemes import create_quadrature
14+
from FIAT.reference_element import TRIANGLE, ufc_simplex
15+
16+
17+
class QuadraticPowellSabin6DualSet(dual_set.DualSet):
18+
def __init__(self, ref_complex, degree=2):
19+
if degree != 2:
20+
raise ValueError("PS6 only defined for degree = 2")
21+
ref_el = ref_complex.get_parent()
22+
if ref_el.get_shape() != TRIANGLE:
23+
raise ValueError("PS6 only defined on triangles")
24+
top = ref_el.get_topology()
25+
verts = ref_el.get_vertices()
26+
sd = ref_el.get_spatial_dimension()
27+
entity_ids = {dim: {entity: [] for entity in sorted(top[dim])} for dim in sorted(top)}
28+
29+
# get first order jet at each vertex
30+
alphas = polynomial_set.mis(sd, 1)
31+
nodes = []
32+
33+
for v in sorted(top[0]):
34+
pt = verts[v]
35+
cur = len(nodes)
36+
nodes.append(PointEvaluation(ref_el, pt))
37+
nodes.extend(PointDerivative(ref_el, pt, alpha) for alpha in alphas)
38+
entity_ids[0][v].extend(range(cur, len(nodes)))
39+
40+
super(QuadraticPowellSabin6DualSet, self).__init__(
41+
nodes, ref_el, entity_ids)
42+
43+
44+
class QuadraticPowellSabin6(finite_element.CiarletElement):
45+
"""The PS6 macroelement is a C^1 quadratic macroelement defined
46+
on the 6-way Powell-Sabin split of a triangle.
47+
"""
48+
def __init__(self, ref_el, degree=2):
49+
if degree != 2:
50+
raise ValueError("PS6 only defined for degree = 2")
51+
ref_complex = macro.PowellSabinSplit(ref_el)
52+
dual = QuadraticPowellSabin6DualSet(ref_complex, degree)
53+
poly_set = macro.CkPolynomialSet(ref_complex, degree, order=1)
54+
55+
super(QuadraticPowellSabin6, self).__init__(poly_set, dual, degree)
56+
57+
58+
class QuadraticPowellSabin12DualSet(dual_set.DualSet):
59+
def __init__(self, ref_complex, degree=2):
60+
if degree != 2:
61+
raise ValueError("PS12 only defined for degree = 2")
62+
ref_el = ref_complex.get_parent()
63+
if ref_el.get_shape() != TRIANGLE:
64+
raise ValueError("PS12 only defined on triangles")
65+
top = ref_el.get_topology()
66+
verts = ref_el.get_vertices()
67+
sd = ref_el.get_spatial_dimension()
68+
entity_ids = {dim: {entity: [] for entity in sorted(top[dim])} for dim in sorted(top)}
69+
70+
# get first order jet at each vertex
71+
alphas = polynomial_set.mis(sd, 1)
72+
nodes = []
73+
74+
for v in sorted(top[0]):
75+
pt = verts[v]
76+
cur = len(nodes)
77+
nodes.append(PointEvaluation(ref_el, pt))
78+
nodes.extend(PointDerivative(ref_el, pt, alpha) for alpha in alphas)
79+
entity_ids[0][v].extend(range(cur, len(nodes)))
80+
81+
# integral moment of normal derivatives
82+
rline = macro.AlfeldSplit(ufc_simplex(1))
83+
Q = create_quadrature(rline, degree-1)
84+
qpts = Q.get_points()
85+
86+
x = 2.0*qpts - 1
87+
phis = eval_jacobi_batch(1, 1, 0, x)
88+
for e in sorted(top[1]):
89+
cur = len(nodes)
90+
nodes.extend(IntegralMomentOfNormalDerivative(ref_el, e, Q, phi) for phi in phis)
91+
entity_ids[1][e].extend(range(cur, len(nodes)))
92+
93+
super(QuadraticPowellSabin12DualSet, self).__init__(
94+
nodes, ref_el, entity_ids)
95+
96+
97+
class QuadraticPowellSabin12(finite_element.CiarletElement):
98+
"""The PS12 macroelement is a C^1 quadratic macroelement defined
99+
on the 12-way Powell-Sabin split of a triangle.
100+
"""
101+
def __init__(self, ref_el, degree=2):
102+
if degree != 2:
103+
raise ValueError("PS12 only defined for degree = 2")
104+
ref_complex = macro.PowellSabin12Split(ref_el)
105+
dual = QuadraticPowellSabin12DualSet(ref_complex, degree)
106+
poly_set = macro.CkPolynomialSet(ref_complex, degree, order=1)
107+
108+
super(QuadraticPowellSabin12, self).__init__(poly_set, dual, degree)

test/unit/test_powell_sabin.py

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import numpy
2+
import pytest
3+
from FIAT import QuadraticPowellSabin6 as PS6
4+
from FIAT import QuadraticPowellSabin12 as PS12
5+
from FIAT.functional import PointEvaluation
6+
from FIAT.reference_element import make_lattice, ufc_simplex
7+
8+
9+
@pytest.fixture
10+
def cell():
11+
return ufc_simplex(2)
12+
13+
14+
@pytest.mark.parametrize("el", (PS6, PS12))
15+
def test_powell_sabin_constant(cell, el):
16+
# Test that bfs associated with point evaluation sum up to 1
17+
fe = el(cell)
18+
19+
pts = make_lattice(cell.get_vertices(), 3)
20+
tab = fe.tabulate(2, pts)
21+
22+
coeffs = numpy.zeros((fe.space_dimension(),))
23+
nodes = fe.dual_basis()
24+
entity_dofs = fe.entity_dofs()
25+
for v in entity_dofs[0]:
26+
for k in entity_dofs[0][v]:
27+
if isinstance(nodes[k], PointEvaluation):
28+
coeffs[k] = 1.0
29+
30+
for alpha in tab:
31+
expected = 1 if sum(alpha) == 0 else 0
32+
assert numpy.allclose(coeffs @ tab[alpha], expected)

0 commit comments

Comments
 (0)