Skip to content
This repository has been archived by the owner on Feb 21, 2022. It is now read-only.

Mimetic Spectral Elements #29

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 5 additions & 0 deletions FIAT/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@
from FIAT.mixed import MixedElement # noqa: F401
from FIAT.restricted import RestrictedElement # noqa: F401
from FIAT.quadrature_element import QuadratureElement # noqa: F401
from FIAT.extended_gauss_legendre import ExtendedGaussLegendre
from FIAT.edge import EdgeGaussLobattoLegendre, EdgeExtendedGaussLegendre

# Important functionality
from FIAT.quadrature import make_quadrature # noqa: F401
Expand Down Expand Up @@ -66,6 +68,9 @@
"Lagrange": Lagrange,
"Gauss-Lobatto-Legendre": GaussLobattoLegendre,
"Gauss-Legendre": GaussLegendre,
"Extended-Gauss-Legendre": ExtendedGaussLegendre,
"Gauss-Lobatto-Legendre Edge": EdgeGaussLobattoLegendre,
"Extended-Gauss-Legendre Edge": EdgeExtendedGaussLegendre,
"Gauss-Radau": GaussRadau,
"Morley": Morley,
"Nedelec 1st kind H(curl)": Nedelec,
Expand Down
176 changes: 176 additions & 0 deletions FIAT/edge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
# Copyright (C) 2019 Chris Eldred (Inria Grenoble Rhone-Alpes) and Werner Bauer (Inria Rennes)
#
# This file is part of FIAT.
#
# FIAT is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# FIAT is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are only insert SPDX header lines since #33.

#

import sympy
import numpy as np
from FIAT.finite_element import FiniteElement
from FIAT.dual_set import make_entity_closure_ids
from FIAT.polynomial_set import mis
from FIAT import quadrature

x = sympy.symbols('x')


def _lagrange_poly(i, xi):
'''Returns the Langrange polynomial P_i(x) of pts xi
which interpolates the values (0,0,..1,..,0) where 1 is at the ith support point
:param i: non-zero location
:param xi: set of N support points
'''
index = list(range(len(xi)))
index.pop(i)
return sympy.prod([(x-xi[j][0])/(xi[i][0]-xi[j][0]) for j in index])


def _lagrange_basis(spts):
symbas = []
for i in range(len(spts)):
symbas.append(_lagrange_poly(i, spts))
return symbas


def _create_compatible_l2_basis(cg_symbas):
ncg_basis = len(cg_symbas)
symbas = []
for i in range(1, ncg_basis):
basis = 0
for j in range(i):
basis = basis + sympy.diff(-cg_symbas[j])
symbas.append(basis)
return symbas


class _EdgeElement(FiniteElement):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are all these names above starting with an underscore? Are they meant to be module private?

My personal opinion is further development and refactoring may make you want to use function in other module, even though those functions were (originally) intended to be private. Such private -> public change then either introduces inconsistency (if the name is not changed), or introduces unnecessary name change which pollutes the diff at review.

Most of the time one just imports what one needs anyway, and in those cases when from package.module import * is meaningful, one can set the list of exported names through the __all__ variable.


def __new__(cls, ref_el, degree):
dim = ref_el.get_spatial_dimension()
if dim == 1:
self = super().__new__(cls)
return self
else:
raise IndexError("only intervals supported for _IntervalElement")

def tabulate(self, order, points, entity=None):

if entity is None:
entity = (self.ref_el.get_spatial_dimension(), 0)
entity_dim, entity_id = entity
dim = self.ref_el.get_spatial_dimension()

transform = self.ref_el.get_entity_transform(entity_dim, entity_id)
points = list(map(transform, points))

phivals = {}
for o in range(order + 1):
alphas = mis(dim, o)
for alpha in alphas:
try:
basis = self.basis[alpha]
except KeyError:
basis = sympy.diff(self.basis[(0,)], x)
self.basis[alpha] = basis
T = np.zeros((len(basis), len(points)))
for i in range(len(points)):
subs = {x: points[i][0]}
for j, f in enumerate(basis):
T[j, i] = f.evalf(subs=subs)
phivals[alpha] = T

return phivals

def degree(self):
return self._degree

def get_nodal_basis(self):
raise NotImplementedError("get_nodal_basis not implemented for _IntervalElement")

def get_dual_set(self):
raise NotImplementedError("get_dual_set is not implemented for _IntervalElement")

def get_coeffs(self):
raise NotImplementedError("get_coeffs not implemented for _IntervalElement")

def entity_dofs(self):
"""Return the map of topological entities to degrees of
freedom for the finite element."""
return self.entity_ids

def entity_closure_dofs(self):
"""Return the map of topological entities to degrees of
freedom on the closure of those entities for the finite element."""
return self.entity_closure_ids

def value_shape(self):
return ()

def dmats(self):
raise NotImplementedError

def get_num_members(self, arg):
raise NotImplementedError

def space_dimension(self):
return len(self.basis[(0,)])

def __init__(self, ref_el, degree):

dim = ref_el.get_spatial_dimension()
topology = ref_el.get_topology()

if not dim == 1:
raise IndexError("only intervals supported for DMSE")

formdegree = 1

# This is general code to build empty entity_ids
entity_ids = {}
for dim in range(dim+1):
entity_ids[dim] = {}
for entity in sorted(topology[dim]):
entity_ids[dim][entity] = []

# The only dofs for DMSE are with the interval!
entity_ids[dim][0] = list(range(degree + 1))

# Build the basis
# This is a dictionary basis[o] that gives the "basis" functions for derivative order o, where o=0 is just the basis itself
# This is filled as needed for o > 0 by tabulate

self.entity_ids = entity_ids
self.entity_closure_ids = make_entity_closure_ids(ref_el, entity_ids)
self._degree = degree

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


class EdgeGaussLobattoLegendre(_EdgeElement):
def __init__(self, ref_el, degree):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some docstring would be nice.

super(EdgeGaussLobattoLegendre, self).__init__(ref_el, degree)
cont_pts = quadrature.GaussLobattoLegendreQuadratureLineRule(ref_el, degree+2).pts
cont_basis = _lagrange_basis(cont_pts)
basis = _create_compatible_l2_basis(cont_basis)
self.basis = {(0,): sympy.Array(basis)}


class EdgeExtendedGaussLegendre(_EdgeElement):
def __init__(self, ref_el, degree):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

docstring missing

super(EdgeExtendedGaussLegendre, self).__init__(ref_el, degree)
cont_pts = quadrature.ExtendedGaussLegendreQuadratureLineRule(ref_el, degree+2).pts
cont_basis = _lagrange_basis(cont_pts)
basis = _create_compatible_l2_basis(cont_basis)
self.basis = {(0,): sympy.Array(basis)}
44 changes: 44 additions & 0 deletions FIAT/extended_gauss_legendre.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Copyright (C) 2019 Chris Eldred (Inria Grenoble Rhone-Alpes) and Werner Bauer (Inria Rennes)
#
# This file is part of FIAT.
#
# FIAT is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# FIAT is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SPDX header line

#
# Written by Chris Eldred (Inria Grenoble Rhone-Alpes) and Werner Bauer (Inria Rennes) 2019

from FIAT import finite_element, polynomial_set, dual_set, functional, quadrature
from FIAT.reference_element import LINE


class ExtendedGaussLegendreDualSet(dual_set.DualSet):
"""The dual basis for 1D continuous elements with nodes at the
Extended-Gauss-Legendre points."""
def __init__(self, ref_el, degree):
entity_ids = {0: {0: [0], 1: [degree]},
1: {0: list(range(1, degree))}}
lr = quadrature.ExtendedGaussLegendreQuadratureLineRule(ref_el, degree+1)
nodes = [functional.PointEvaluation(ref_el, x) for x in lr.pts]

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


class ExtendedGaussLegendre(finite_element.CiarletElement):
"""1D continuous element with nodes at the Extended-Gauss-Legendre points."""
def __init__(self, ref_el, degree):
if ref_el.shape != LINE:
raise ValueError("Extended-Gauss-Legendre elements are only defined in one dimension.")
poly_set = polynomial_set.ONPolynomialSet(ref_el, degree)
dual = ExtendedGaussLegendreDualSet(ref_el, degree)
formdegree = 0 # 0-form
super(ExtendedGaussLegendre, self).__init__(poly_set, dual, degree, formdegree)
35 changes: 34 additions & 1 deletion FIAT/quadrature.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ def __init__(self, ref_el, m):


class GaussLegendreQuadratureLineRule(QuadratureRule):
"""Produce the Gauss--Legendre quadrature rules on the interval using
"""Produce the Gauss-Legendre quadrature rules on the interval using
the implementation in numpy. This facilitates implementing
discontinuous spectral elements.

Expand All @@ -123,6 +123,39 @@ def __init__(self, ref_el, m):
QuadratureRule.__init__(self, ref_el, xs, ws)


class ExtendedGaussLegendreQuadratureLineRule(QuadratureRule):
"""Produce the Extended-Gauss-Legendre quadrature rules on the interval using
the implementation in numpy. This facilitates implementing
dual continuous mimetic spectral elements.

The quadrature rule uses m points for a degree of precision of 2(m-2)-3 = 2m - 7.
Thus, not recommend for actual use (but is useful to define the Extended-Gauss-Legendre elements)
"""
def __init__(self, ref_el, m):
if m < 3:
raise ValueError(
"Extended-Gauss-Legendre quadrature invalid for fewer than 3 points")

xs_ref, ws_ref = numpy.polynomial.legendre.leggauss(m-2)
A, b = reference_element.make_affine_mapping(((-1.,), (1.)),
ref_el.get_vertices())

mapping = lambda x: numpy.dot(A, x) + b

scale = numpy.linalg.det(A)

ws = list([scale * w for w in ws_ref])
xs = [tuple(mapping(x_ref)[0]) for x_ref in xs_ref]

xs.insert(0, (0.,))
xs.append((1.,))
# The integral of these basis functions over the interval is 0!
ws.insert(0, 0.)
ws.append(0.)

QuadratureRule.__init__(self, ref_el, tuple(xs), tuple(ws))


class RadauQuadratureLineRule(QuadratureRule):
"""Produce the Gauss--Radau quadrature rules on the interval using
an adaptation of Winkel's Matlab code.
Expand Down
72 changes: 72 additions & 0 deletions test/unit/test_edge.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Copyright (C) 2016 Imperial College London and others
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably incorrect copy-paste

#
# This file is part of FIAT.
#
# FIAT is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# FIAT is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SPDX header line

#
# Authors:
#
# Christopher Eldred

import pytest
import math
import sympy
import FIAT

x = sympy.symbols('x')


@pytest.mark.parametrize("degree", list(range(0, 7)))
def test_gll_edge_basis_values(degree):
"""Ensure that edge elements are histopolatory"""

s = FIAT.ufc_simplex(1)

fe = FIAT.EdgeGaussLobattoLegendre(s, degree)

basis = fe.basis[(0,)]
cont_pts = FIAT.quadrature.GaussLobattoLegendreQuadratureLineRule(s, degree + 2).pts

for i in range(len(cont_pts)-1):
for j in range(basis.shape[0]):
int_sub = sympy.integrate(basis[j], (x, cont_pts[i][0], cont_pts[i+1][0]))
if i == j:
assert(math.isclose(int_sub, 1.))
else:
assert(math.isclose(int_sub, 0., abs_tol=1e-9))


@pytest.mark.parametrize("degree", list(range(2, 7)))
def test_egl_edge_basis_values(degree):
"""Ensure that edge elements are histopolatory"""

s = FIAT.ufc_simplex(1)

fe = FIAT.EdgeExtendedGaussLegendre(s, degree)

basis = fe.basis[(0,)]
cont_pts = FIAT.quadrature.ExtendedGaussLegendreQuadratureLineRule(s, degree + 2).pts

for i in range(len(cont_pts)-1):
for j in range(basis.shape[0]):
int_sub = sympy.integrate(basis[j], (x, cont_pts[i][0], cont_pts[i+1][0]))
if i == j:
assert(math.isclose(int_sub, 1.))
else:
assert(math.isclose(int_sub, 0., abs_tol=1e-9))


if __name__ == '__main__':
import os
pytest.main(os.path.abspath(__file__))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We normally don't add these anymore at the bottom of test files.

Loading