forked from FEniCS/fiat
-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement a traceless H(curl div) element (#69)
* TracelessTensorPolynomialSet * Add GLS H(curl div) element
- Loading branch information
Showing
7 changed files
with
234 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
from FIAT import finite_element, dual_set, polynomial_set, expansions | ||
from FIAT.functional import TensorBidirectionalIntegralMoment as BidirectionalMoment | ||
from FIAT.quadrature_schemes import create_quadrature | ||
from FIAT.quadrature import FacetQuadratureRule | ||
from FIAT.restricted import RestrictedElement | ||
|
||
|
||
class GLSDual(dual_set.DualSet): | ||
def __init__(self, ref_el, degree): | ||
sd = ref_el.get_spatial_dimension() | ||
top = ref_el.get_topology() | ||
nodes = [] | ||
entity_ids = {dim: {entity: [] for entity in sorted(top[dim])} for dim in sorted(top)} | ||
|
||
# Face dofs: moments of normal-tangential components against a basis for Pk | ||
ref_facet = ref_el.construct_subelement(sd-1) | ||
Qref = create_quadrature(ref_facet, 2*degree) | ||
P = polynomial_set.ONPolynomialSet(ref_facet, degree) | ||
phis = P.tabulate(Qref.get_points())[(0,) * (sd-1)] | ||
for f in sorted(top[sd-1]): | ||
cur = len(nodes) | ||
Q = FacetQuadratureRule(ref_el, sd-1, f, Qref) | ||
Jdet = Q.jacobian_determinant() | ||
normal = ref_el.compute_scaled_normal(f) | ||
tangents = ref_el.compute_tangents(sd-1, f) | ||
n = normal / Jdet | ||
nodes.extend(BidirectionalMoment(ref_el, t, n, Q, phi) | ||
for phi in phis for t in tangents) | ||
entity_ids[sd-1][f].extend(range(cur, len(nodes))) | ||
|
||
# Interior dofs: moments of normal-tangential components against a basis for P_{k-1} | ||
if degree > 0: | ||
cur = len(nodes) | ||
Q = create_quadrature(ref_el, 2*degree-1) | ||
P = polynomial_set.ONPolynomialSet(ref_el, degree-1, scale="L2 piola") | ||
phis = P.tabulate(Q.get_points())[(0,) * sd] | ||
for f in sorted(top[sd-1]): | ||
n = ref_el.compute_scaled_normal(f) | ||
tangents = ref_el.compute_tangents(sd-1, f) | ||
nodes.extend(BidirectionalMoment(ref_el, t, n, Q, phi) | ||
for phi in phis for t in tangents) | ||
entity_ids[sd][0].extend(range(cur, len(nodes))) | ||
|
||
super().__init__(nodes, ref_el, entity_ids) | ||
|
||
|
||
class GopalakrishnanLedererSchoberlSecondKind(finite_element.CiarletElement): | ||
"""The GLS element used for the Mass-Conserving mixed Stress (MCS) | ||
formulation for Stokes flow with weakly imposed stress symmetry. | ||
GLS^2(k) is the space of trace-free polynomials of degree k with | ||
continuous normal-tangential components. | ||
Reference: https://doi.org/10.1137/19M1248960 | ||
Notes | ||
----- | ||
This element does not include the bubbles required for inf-sup stability of | ||
the weak symmetry constraint. | ||
""" | ||
def __init__(self, ref_el, degree): | ||
poly_set = polynomial_set.TracelessTensorPolynomialSet(ref_el, degree) | ||
dual = GLSDual(ref_el, degree) | ||
sd = ref_el.get_spatial_dimension() | ||
formdegree = (1, sd-1) | ||
mapping = "covariant contravariant piola" | ||
super().__init__(poly_set, dual, degree, formdegree, mapping=mapping) | ||
|
||
|
||
def GopalakrishnanLedererSchoberlFirstKind(ref_el, degree): | ||
"""The GLS element used for the Mass-Conserving mixed Stress (MCS) | ||
formulation for Stokes flow. | ||
GLS^1(k) is the space of trace-free polynomials of degree k with | ||
continuous normal-tangential components of degree k-1. | ||
Reference: https://doi.org/10.1093/imanum/drz022 | ||
""" | ||
fe = GopalakrishnanLedererSchoberlSecondKind(ref_el, degree) | ||
entity_dofs = fe.entity_dofs() | ||
sd = ref_el.get_spatial_dimension() | ||
dimPkm1 = (sd-1)*expansions.polynomial_dimension(ref_el.construct_subelement(sd-1), degree-1) | ||
indices = [] | ||
for f in entity_dofs[sd-1]: | ||
indices.extend(entity_dofs[sd-1][f][:dimPkm1]) | ||
indices.extend(entity_dofs[sd][0]) | ||
return RestrictedElement(fe, indices=indices) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import pytest | ||
import numpy | ||
|
||
from FIAT import (GopalakrishnanLedererSchoberlFirstKind, | ||
GopalakrishnanLedererSchoberlSecondKind) | ||
from FIAT.reference_element import ufc_simplex | ||
from FIAT.expansions import polynomial_dimension | ||
from FIAT.polynomial_set import ONPolynomialSet | ||
from FIAT.quadrature_schemes import create_quadrature | ||
from FIAT.quadrature import FacetQuadratureRule | ||
|
||
|
||
@pytest.fixture(params=("T", "S")) | ||
def cell(request): | ||
dim = {"I": 1, "T": 2, "S": 3}[request.param] | ||
return ufc_simplex(dim) | ||
|
||
|
||
@pytest.mark.parametrize("degree", (1, 2, 3)) | ||
@pytest.mark.parametrize("kind", (1, 2)) | ||
def test_gls_bubbles(kind, cell, degree): | ||
if kind == 1: | ||
element = GopalakrishnanLedererSchoberlFirstKind | ||
else: | ||
element = GopalakrishnanLedererSchoberlSecondKind | ||
fe = element(cell, degree) | ||
sd = cell.get_spatial_dimension() | ||
facet_el = cell.construct_subelement(sd-1) | ||
poly_set = fe.get_nodal_basis() | ||
|
||
# test dimension of constrained space | ||
dimPkm1 = polynomial_dimension(facet_el, degree-1) | ||
dimPkp1 = polynomial_dimension(facet_el, degree+1) | ||
dimPk = polynomial_dimension(facet_el, degree) | ||
if kind == 1: | ||
constraints = dimPk - dimPkm1 | ||
else: | ||
constraints = 0 | ||
expected = (sd**2-1)*(polynomial_dimension(cell, degree) - constraints) | ||
assert poly_set.get_num_members() == expected | ||
|
||
# test dimension of the bubbles | ||
entity_dofs = fe.entity_dofs() | ||
bubbles = poly_set.take(entity_dofs[sd][0]) | ||
expected = (sd**2-1)*polynomial_dimension(cell, degree-1) | ||
assert bubbles.get_num_members() == expected | ||
|
||
top = cell.get_topology() | ||
Qref = create_quadrature(facet_el, 2*degree+1) | ||
Pk = ONPolynomialSet(facet_el, degree+1) | ||
if kind == 1: | ||
start, stop = dimPkm1, dimPkp1 | ||
else: | ||
start, stop = dimPk, dimPkp1 | ||
PkH = Pk.take(list(range(start, stop))) | ||
PkH_at_qpts = PkH.tabulate(Qref.get_points())[(0,)*(sd-1)] | ||
weights = numpy.transpose(numpy.multiply(PkH_at_qpts, Qref.get_weights())) | ||
for facet in top[sd-1]: | ||
n = cell.compute_scaled_normal(facet) | ||
rts = cell.compute_tangents(sd-1, facet) | ||
Q = FacetQuadratureRule(cell, sd-1, facet, Qref) | ||
qpts, qwts = Q.get_points(), Q.get_weights() | ||
|
||
# test the degree of normal-tangential components | ||
phi_at_pts = fe.tabulate(0, qpts)[(0,) * sd] | ||
for t in rts: | ||
nt = numpy.outer(t, n) | ||
phi_nt = numpy.tensordot(nt, phi_at_pts, axes=((0, 1), (1, 2))) | ||
assert numpy.allclose(numpy.dot(phi_nt, weights), 0) | ||
|
||
# test the support of the normal-tangential bubble | ||
phi_at_pts = bubbles.tabulate(qpts)[(0,) * sd] | ||
for t in rts: | ||
nt = numpy.outer(t, n) | ||
phi_nt = numpy.tensordot(nt, phi_at_pts, axes=((0, 1), (1, 2))) | ||
norms = numpy.dot(phi_nt**2, qwts) | ||
assert numpy.allclose(norms, 0) |