Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Stokes macroelements #84

Merged
merged 44 commits into from
Oct 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
fe12e99
Alfeld-Sorokina triangular macroelement
pbrubeck Sep 13, 2024
dba9051
Integral moment dofs
pbrubeck Sep 13, 2024
84482ac
Fix Bell
pbrubeck Sep 13, 2024
f63e8b1
Reduced Alfeld-Sorokina dofs
pbrubeck Sep 14, 2024
2747d0a
AW: constructor with lowest degree as default
pbrubeck Sep 14, 2024
abad5ef
test C0DivPolynomialSet
pbrubeck Sep 14, 2024
7479673
Test HCT Stokes complex
pbrubeck Sep 19, 2024
53742aa
Add the Christiansen-Hu macroelement
pbrubeck Sep 19, 2024
f98bfb4
Christiansen-Hu hierarchical edge DOF
pbrubeck Sep 20, 2024
9a9a265
cleanup functional.py
pbrubeck Sep 22, 2024
1fa4f22
PowellSabinSplit has AlfeldSplit as parent_complex
pbrubeck Sep 23, 2024
72d543e
do the extendended element thing
pbrubeck Sep 23, 2024
d3a5f82
Rename minimal Stokes 1-form to ArnoldQin
pbrubeck Sep 24, 2024
8faa06b
add Bernardi-Raugel
pbrubeck Sep 24, 2024
a69033a
Alfeld-Sorokina in 3D
pbrubeck Sep 24, 2024
4e25055
Fix BR dofs
pbrubeck Sep 25, 2024
a2f302e
clean up functionals.py
pbrubeck Sep 26, 2024
53abf66
Construct Worsey-Farin via PowellSabinSplit with codim=2
pbrubeck Sep 26, 2024
ed707e3
2D/3D minimal Stokes macroelements reusing the Bernardi-Raugel dual set
pbrubeck Sep 26, 2024
ec68c9f
JM: fix scaling
pbrubeck Sep 27, 2024
7afec14
Construct extended AQ space by rotating face bubbles
pbrubeck Sep 27, 2024
f1bfd97
simplify the AQ space
pbrubeck Sep 27, 2024
1c032d6
Clean up PowellSabin
pbrubeck Sep 28, 2024
1887bee
AlfeldSplit and WorseyFarinSplit subclass PowellSabinSplit
pbrubeck Sep 28, 2024
4816e8b
Correctly recover the scalar bubble
pbrubeck Sep 30, 2024
e6cfe0b
Fix super
pbrubeck Sep 30, 2024
2d702c4
Powell-Sabin: Preserve the numbering of the unsplit entities
pbrubeck Sep 30, 2024
fa90a91
Add worsey-farin Lagrange variant
pbrubeck Sep 30, 2024
8057738
address review comments
pbrubeck Oct 1, 2024
849b510
Add Guzman-Neilan
pbrubeck Oct 1, 2024
8db041d
Add Guzman-Neilan
pbrubeck Oct 2, 2024
ddae5ac
address review comments
pbrubeck Oct 2, 2024
7169971
Merge branch 'pbrubeck/alfeld-sorokina' into pbrubeck/guzman-neilan
pbrubeck Oct 2, 2024
83e6575
cleanup
pbrubeck Oct 2, 2024
ae8af4d
Use bubbles from the paper
pbrubeck Oct 3, 2024
086456b
cleanup bubbles
pbrubeck Oct 3, 2024
380e423
cleanup
pbrubeck Oct 3, 2024
098c72e
RestrictedElement: preserve ordering
pbrubeck Oct 3, 2024
37dcf4c
Test the GN space
pbrubeck Oct 3, 2024
6a68f30
Merge branch 'pbrubeck/alfeld-sorokina' of github.com:firedrakeprojec…
pbrubeck Oct 3, 2024
bde8583
AlfeldSorokina: add variant="integral"
pbrubeck Oct 4, 2024
6b4243c
Allow Christiansen-Hu in 2D
pbrubeck Oct 5, 2024
9d30b1d
BR Bubbles, and non-Piola mapped Arnold-Qin
pbrubeck Oct 6, 2024
480563f
simplify AQ
pbrubeck Oct 6, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions FIAT/P0.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def __init__(self, ref_el):
entity_ids[dim][entity] = [entity] if dim == sd else []
entity_permutations[dim][entity] = perms

super(P0Dual, self).__init__(nodes, ref_el, entity_ids, entity_permutations)
rckirby marked this conversation as resolved.
Show resolved Hide resolved
super().__init__(nodes, ref_el, entity_ids, entity_permutations)


class P0(finite_element.CiarletElement):
Expand All @@ -49,4 +49,4 @@ def __init__(self, ref_el):
dual = P0Dual(ref_el)
degree = 0
formdegree = ref_el.get_spatial_dimension() # n-form
super(P0, self).__init__(poly_set, dual, degree, formdegree)
super().__init__(poly_set, dual, degree, formdegree)
4 changes: 2 additions & 2 deletions FIAT/Sminus.py
Original file line number Diff line number Diff line change
Expand Up @@ -497,7 +497,7 @@ def __init__(self, ref_el, degree):
self.basis = {(0, 0): Array(Sminus_list)}
else:
self.basis = {(0, 0, 0): Array(Sminus_list)}
super(TrimmedSerendipityEdge, self).__init__(ref_el=ref_el, degree=degree, mapping="covariant piola")
super().__init__(ref_el=ref_el, degree=degree, mapping="covariant piola")


class TrimmedSerendipityFace(TrimmedSerendipity):
Expand Down Expand Up @@ -526,4 +526,4 @@ def __init__(self, ref_el, degree):
Sminus_list = EL + FL
Sminus_list = [[-a[1], a[0]] for a in Sminus_list]
self.basis = {(0, 0): Array(Sminus_list)}
super(TrimmedSerendipityFace, self).__init__(ref_el=ref_el, degree=degree, mapping="contravariant piola")
super().__init__(ref_el=ref_el, degree=degree, mapping="contravariant piola")
12 changes: 6 additions & 6 deletions FIAT/SminusCurl.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,11 @@ def __init__(self, ref_el, degree, mapping):

entity_closure_ids = make_entity_closure_ids(flat_el, entity_ids)

super(TrimmedSerendipity, self).__init__(ref_el=ref_el,
dual=None,
order=degree,
formdegree=formdegree,
mapping=mapping)
super().__init__(ref_el=ref_el,
dual=None,
order=degree,
formdegree=formdegree,
mapping=mapping)

topology = ref_el.get_topology()
unflattening_map = compute_unflattening_map(topology)
Expand Down Expand Up @@ -231,7 +231,7 @@ def __init__(self, ref_el, degree):

Sminus_list = EL + FL
self.basis = {(0, 0): Array(Sminus_list)}
super(TrimmedSerendipityCurl, self).__init__(ref_el=ref_el, degree=degree, mapping="contravariant piola")
super().__init__(ref_el=ref_el, degree=degree, mapping="contravariant piola")


def e_lambda_1_3d(deg, dx, dy, dz, x_mid, y_mid, z_mid):
Expand Down
14 changes: 7 additions & 7 deletions FIAT/SminusDiv.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ def __init__(self, ref_el, degree, mapping):

entity_closure_ids = make_entity_closure_ids(flat_el, entity_ids)

super(TrimmedSerendipity, self).__init__(ref_el=ref_el,
dual=None,
order=degree,
formdegree=formdegree,
mapping=mapping)
super().__init__(ref_el=ref_el,
dual=None,
order=degree,
formdegree=formdegree,
mapping=mapping)

topology = ref_el.get_topology()
unflattening_map = compute_unflattening_map(topology)
Expand Down Expand Up @@ -198,7 +198,7 @@ def __init__(self, ref_el, degree):
IL = ()
Sminus_list = FL + IL
self.basis = {(0, 0, 0): Array(Sminus_list)}
super(TrimmedSerendipityDiv, self).__init__(ref_el=ref_el, degree=degree, mapping="contravariant piola")
super().__init__(ref_el=ref_el, degree=degree, mapping="contravariant piola")

else:
# Put all 2 dimensional stuff here.
Expand All @@ -213,7 +213,7 @@ def __init__(self, ref_el, degree):
Sminus_list = EL + FL
Sminus_list = [[-a[1], a[0]] for a in Sminus_list]
self.basis = {(0, 0): Array(Sminus_list)}
super(TrimmedSerendipityDiv, self).__init__(ref_el=ref_el, degree=degree, mapping="contravariant piola")
super().__init__(ref_el=ref_el, degree=degree, mapping="contravariant piola")


def f_lambda_2_3d(degree, dx, dy, dz, x_mid, y_mid, z_mid):
Expand Down
15 changes: 12 additions & 3 deletions FIAT/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@
# Import finite element classes
from FIAT.finite_element import FiniteElement, CiarletElement # noqa: F401
from FIAT.argyris import Argyris
from FIAT.bernardi_raugel import BernardiRaugel
from FIAT.bernstein import Bernstein
from FIAT.bell import Bell
from FIAT.hct import HsiehCloughTocher
from FIAT.alfeld_sorokina import AlfeldSorokina
from FIAT.arnold_qin import ArnoldQin
from FIAT.guzman_neilan import GuzmanNeilan
from FIAT.christiansen_hu import ChristiansenHu
from FIAT.johnson_mercier import JohnsonMercier
from FIAT.brezzi_douglas_marini import BrezziDouglasMarini
from FIAT.Sminus import TrimmedSerendipityEdge # noqa: F401
Expand All @@ -31,8 +36,7 @@
from FIAT.morley import Morley
from FIAT.nedelec import Nedelec
from FIAT.nedelec_second_kind import NedelecSecondKind
from FIAT.powell_sabin import QuadraticPowellSabin6
from FIAT.powell_sabin import QuadraticPowellSabin12
from FIAT.powell_sabin import QuadraticPowellSabin6, QuadraticPowellSabin12
from FIAT.hierarchical import Legendre, IntegratedLegendre
from FIAT.P0 import P0
from FIAT.raviart_thomas import RaviartThomas
Expand Down Expand Up @@ -65,6 +69,7 @@
# List of supported elements and mapping to element classes
supported_elements = {"Argyris": Argyris,
"Bell": Bell,
"Bernardi-Raugel": BernardiRaugel,
"Bernstein": Bernstein,
"Brezzi-Douglas-Marini": BrezziDouglasMarini,
"Brezzi-Douglas-Fortin-Marini": BrezziDouglasFortinMarini,
Expand All @@ -79,7 +84,11 @@
"Discontinuous Taylor": DiscontinuousTaylor,
"Discontinuous Raviart-Thomas": DiscontinuousRaviartThomas,
"Hermite": CubicHermite,
"HsiehCloughTocher": HsiehCloughTocher,
"Hsieh-Clough-Tocher": HsiehCloughTocher,
"Alfeld-Sorokina": AlfeldSorokina,
"Arnold-Qin": ArnoldQin,
"Christiansen-Hu": ChristiansenHu,
"Guzman-Neilan": GuzmanNeilan,
"Johnson-Mercier": JohnsonMercier,
"Lagrange": Lagrange,
"Kong-Mulder-Veldhuizen": KongMulderVeldhuizen,
Expand Down
106 changes: 106 additions & 0 deletions FIAT/alfeld_sorokina.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Copyright (C) 2024 Pablo D. Brubeck
#
# This file is part of FIAT (https://www.fenicsproject.org)
#
# SPDX-License-Identifier: LGPL-3.0-or-later
#
# Written by Pablo D. Brubeck ([email protected]), 2024

from FIAT import finite_element, dual_set, polynomial_set
from FIAT.functional import ComponentPointEvaluation, PointDivergence, IntegralMoment
from FIAT.quadrature_schemes import create_quadrature
from FIAT.quadrature import FacetQuadratureRule
from FIAT.macro import CkPolynomialSet, AlfeldSplit

import numpy


def AlfeldSorokinaSpace(ref_el, degree):
"""Return a vector-valued C^0 PolynomialSet on an Alfeld split whose
divergence is also C^0. This works on any simplex and for all
polynomial degrees."""
ref_complex = AlfeldSplit(ref_el)
sd = ref_complex.get_spatial_dimension()
C0 = CkPolynomialSet(ref_complex, degree, order=0, shape=(sd,), variant="bubble")
expansion_set = C0.get_expansion_set()
num_members = C0.get_num_members()
coeffs = C0.get_coeffs()

facet_el = ref_complex.construct_subelement(sd-1)
phi = polynomial_set.ONPolynomialSet(facet_el, 0 if sd == 1 else degree-1)
Q = create_quadrature(facet_el, 2 * phi.degree)
qpts, qwts = Q.get_points(), Q.get_weights()
phi_at_qpts = phi.tabulate(qpts)[(0,) * (sd-1)]
weights = numpy.multiply(phi_at_qpts, qwts)

rows = []
for facet in ref_complex.get_interior_facets(sd-1):
n = ref_complex.compute_normal(facet)
jumps = expansion_set.tabulate_normal_jumps(degree, qpts, facet, order=1)
div_jump = n[:, None, None] * jumps[1][None, ...]
r = numpy.tensordot(div_jump, weights, axes=(-1, -1))
rows.append(r.reshape(num_members, -1).T)

if len(rows) > 0:
dual_mat = numpy.vstack(rows)
_, sig, vt = numpy.linalg.svd(dual_mat, full_matrices=True)
tol = sig[0] * 1E-10
num_sv = len([s for s in sig if abs(s) > tol])
coeffs = numpy.tensordot(vt[num_sv:], coeffs, axes=(-1, 0))
return polynomial_set.PolynomialSet(ref_complex, degree, degree, expansion_set, coeffs)


class AlfeldSorokinaDualSet(dual_set.DualSet):
def __init__(self, ref_el, degree, variant=None):
if degree != 2:
pbrubeck marked this conversation as resolved.
Show resolved Hide resolved
raise NotImplementedError("Alfeld-Sorokina only defined for degree = 2")
if variant is None:
variant = "integral"
if variant not in {"integral", "point"}:
raise ValueError(f"Invalid variant {variant}")

top = ref_el.get_topology()
sd = ref_el.get_spatial_dimension()
entity_ids = {dim: {entity: [] for entity in sorted(top[dim])} for dim in sorted(top)}

nodes = []
dims = (0, 1) if variant == "point" else (0,)
for dim in dims:
for entity in sorted(top[dim]):
cur = len(nodes)
pts = ref_el.make_points(dim, entity, degree)
if dim == 0:
pt, = pts
nodes.append(PointDivergence(ref_el, pt))
nodes.extend(ComponentPointEvaluation(ref_el, k, (sd,), pt)
for pt in pts for k in range(sd))
entity_ids[dim][entity].extend(range(cur, len(nodes)))

if variant == "integral":
# Edge moments against quadratic Legendre (mean-free bubble)
dim = 1
facet = ref_el.construct_subelement(dim)
Q = create_quadrature(facet, degree+dim+1)
f_at_qpts = facet.compute_bubble(Q.get_points())
f_at_qpts -= numpy.dot(f_at_qpts, Q.get_weights()) / facet.volume()
for entity in sorted(top[dim]):
cur = len(nodes)
Q_mapped = FacetQuadratureRule(ref_el, dim, entity, Q)
detJ = Q_mapped.jacobian_determinant()
phi = f_at_qpts / detJ
nodes.extend(IntegralMoment(ref_el, Q_mapped, phi, comp=k, shp=(sd,))
for k in range(sd))
entity_ids[dim][entity].extend(range(cur, len(nodes)))

super().__init__(nodes, ref_el, entity_ids)


class AlfeldSorokina(finite_element.CiarletElement):
pbrubeck marked this conversation as resolved.
Show resolved Hide resolved
"""The Alfeld-Sorokina C^0 quadratic macroelement with C^0 divergence. This element
belongs to a Stokes complex, and is paired with Lagrange(ref_el, 1, variant="alfeld")."""
def __init__(self, ref_el, degree=2):
dual = AlfeldSorokinaDualSet(ref_el, degree)
poly_set = AlfeldSorokinaSpace(ref_el, degree)
formdegree = ref_el.get_spatial_dimension() - 1 # (n-1)-form
super().__init__(poly_set, dual, degree, formdegree,
mapping="contravariant piola")
4 changes: 2 additions & 2 deletions FIAT/argyris.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ def __init__(self, ref_el, degree, variant, interpolant_deg):
entity_ids[2][0] = list(range(cur, len(nodes)))
else:
raise ValueError("Invalid variant for Argyris")
super(ArgyrisDualSet, self).__init__(nodes, ref_el, entity_ids)
super().__init__(nodes, ref_el, entity_ids)


class Argyris(finite_element.CiarletElement):
Expand All @@ -107,4 +107,4 @@ def __init__(self, ref_el, degree=5, variant=None):

poly_set = polynomial_set.ONPolynomialSet(ref_el, degree, variant="bubble")
dual = ArgyrisDualSet(ref_el, degree, variant, interpolant_deg)
super(Argyris, self).__init__(poly_set, dual, degree)
super().__init__(poly_set, dual, degree)
71 changes: 71 additions & 0 deletions FIAT/arnold_qin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# This file is part of FIAT (https://www.fenicsproject.org)
#
# SPDX-License-Identifier: LGPL-3.0-or-later
#
# Written by Pablo D. Brubeck ([email protected]), 2024

from FIAT import finite_element, polynomial_set
from FIAT.bernardi_raugel import BernardiRaugelDualSet
from FIAT.quadrature_schemes import create_quadrature
from FIAT.reference_element import TRIANGLE
from FIAT.macro import CkPolynomialSet
from FIAT.hct import HsiehCloughTocher

import numpy


def ArnoldQinSpace(ref_el, degree, reduced=False):
pbrubeck marked this conversation as resolved.
Show resolved Hide resolved
"""Return a basis for the Arnold-Qin space
curl(HCT-red) + P_0 x if reduced = True, and
curl(HCT) + P_0 x if reduced = False."""
if ref_el.get_shape() != TRIANGLE:
raise ValueError("Arnold-Qin only defined on triangles")
if degree != 2:
raise ValueError("Arnold-Qin only defined for degree = 2")
sd = ref_el.get_spatial_dimension()
HCT = HsiehCloughTocher(ref_el, degree+1, reduced=True)
ref_complex = HCT.get_reference_complex()
Q = create_quadrature(ref_complex, 2 * degree)
qpts, qwts = Q.get_points(), Q.get_weights()

x = qpts.T
bary = numpy.asarray(ref_el.make_points(sd, 0, sd+1))
P0x_at_qpts = x[None, :, :] - bary[:, :, None]

tab = HCT.tabulate(1, qpts)
curl_at_qpts = numpy.stack([tab[(0, 1)], -tab[(1, 0)]], axis=1)
if reduced:
curl_at_qpts = curl_at_qpts[:9]

C0 = CkPolynomialSet(ref_complex, degree, order=0, scale=1, variant="bubble")
C0_at_qpts = C0.tabulate(qpts)[(0,) * sd]
duals = numpy.multiply(C0_at_qpts, qwts)
M = numpy.dot(duals, C0_at_qpts.T)
duals = numpy.linalg.solve(M, duals)

# Remove the constant nullspace
pbrubeck marked this conversation as resolved.
Show resolved Hide resolved
ids = [0, 3, 6]
A = numpy.asarray([[1, 1, 1], [1, -1, 0], [0, -1, 1]])
phis = curl_at_qpts
phis[ids] = numpy.tensordot(A, phis[ids], axes=(-1, 0))
# Replace the constant nullspace with P_0 x
phis[0] = P0x_at_qpts
coeffs = numpy.tensordot(phis, duals, axes=(-1, -1))
return polynomial_set.PolynomialSet(ref_complex, degree, degree,
C0.get_expansion_set(), coeffs)


class ArnoldQin(finite_element.CiarletElement):
pbrubeck marked this conversation as resolved.
Show resolved Hide resolved
"""The Arnold-Qin C^0(Alfeld) quadratic macroelement with divergence in P0.
This element belongs to a Stokes complex, and is paired with unsplit DG0."""
def __init__(self, ref_el, degree=2, reduced=False):
poly_set = ArnoldQinSpace(ref_el, degree)
if reduced:
subdegree = 1
mapping = "contravariant piola"
else:
subdegree = degree
mapping = "affine"
dual = BernardiRaugelDualSet(ref_el, degree, subdegree)
formdegree = ref_el.get_spatial_dimension() - 1 # (n-1)-form
super().__init__(poly_set, dual, degree, formdegree, mapping=mapping)
17 changes: 8 additions & 9 deletions FIAT/arnold_winther.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@


class ArnoldWintherNCDual(DualSet):
def __init__(self, cell, degree):
def __init__(self, cell, degree=2):
if not degree == 2:
raise ValueError("Nonconforming Arnold-Winther elements are"
"only defined for degree 2.")
Expand Down Expand Up @@ -70,24 +70,23 @@ def __init__(self, cell, degree):
dof_ids[1][entity_id].append(dof_cur)
dof_cur += 1

super(ArnoldWintherNCDual, self).__init__(dofs, cell, dof_ids)
super().__init__(dofs, cell, dof_ids)


class ArnoldWintherNC(CiarletElement):
"""The definition of the nonconforming Arnold-Winther element.
"""
def __init__(self, cell, degree):
def __init__(self, cell, degree=2):
assert degree == 2, "Only defined for degree 2"
Ps = ONSymTensorPolynomialSet(cell, degree)
Ls = ArnoldWintherNCDual(cell, degree)
mapping = "double contravariant piola"

super(ArnoldWintherNC, self).__init__(Ps, Ls, degree,
mapping=mapping)
super().__init__(Ps, Ls, degree, mapping=mapping)


class ArnoldWintherDual(DualSet):
def __init__(self, cell, degree):
def __init__(self, cell, degree=3):
if not degree == 3:
raise ValueError("Arnold-Winther elements are"
"only defined for degree 3.")
Expand Down Expand Up @@ -152,15 +151,15 @@ def __init__(self, cell, degree):

dof_ids[2][0] += list(range(dof_cur, dof_cur+6))

super(ArnoldWintherDual, self).__init__(dofs, cell, dof_ids)
super().__init__(dofs, cell, dof_ids)


class ArnoldWinther(CiarletElement):
"""The definition of the conforming Arnold-Winther element.
"""
def __init__(self, cell, degree):
def __init__(self, cell, degree=3):
assert degree == 3, "Only defined for degree 3"
Ps = ONSymTensorPolynomialSet(cell, degree)
Ls = ArnoldWintherDual(cell, degree)
mapping = "double contravariant piola"
super(ArnoldWinther, self).__init__(Ps, Ls, degree, mapping=mapping)
super().__init__(Ps, Ls, degree, mapping=mapping)
Loading