Skip to content

Commit

Permalink
Merge pull request #14 from firedrakeproject/Cyrus-serendipity-branch
Browse files Browse the repository at this point in the history
Serendipity element
  • Loading branch information
dham committed Apr 29, 2019
2 parents 0964801 + be04e50 commit 58658a6
Show file tree
Hide file tree
Showing 6 changed files with 261 additions and 6 deletions.
3 changes: 3 additions & 0 deletions AUTHORS
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,6 @@ Contributors:

Ivan Yashchuk
email: [email protected]

Cyrus Cheng
email: [email protected]
2 changes: 2 additions & 0 deletions FIAT/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from FIAT.discontinuous_lagrange import DiscontinuousLagrange
from FIAT.discontinuous_taylor import DiscontinuousTaylor
from FIAT.discontinuous_raviart_thomas import DiscontinuousRaviartThomas
from FIAT.serendipity import Serendipity
from FIAT.discontinuous_pc import DPC
from FIAT.hermite import CubicHermite
from FIAT.lagrange import Lagrange
Expand Down Expand Up @@ -56,6 +57,7 @@
"FacetBubble": FacetBubble,
"Crouzeix-Raviart": CrouzeixRaviart,
"Discontinuous Lagrange": DiscontinuousLagrange,
"S": Serendipity,
"DPC": DPC,
"Discontinuous Taylor": DiscontinuousTaylor,
"Discontinuous Raviart-Thomas": DiscontinuousRaviartThomas,
Expand Down
12 changes: 8 additions & 4 deletions FIAT/discontinuous_pc.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Copyright (C) 2018 Cyrus Cheng (Imperial College London)
#
# This file is part of FIAT.
#
# FIAT is free software: you can redistribute it and/or modify
Expand All @@ -12,6 +14,8 @@
#
# You should have received a copy of the GNU Lesser General Public License
# along with FIAT. If not, see <http://www.gnu.org/licenses/>.
#
# Modified by David A. Ham ([email protected]), 2018

from FIAT import finite_element, polynomial_set, dual_set, functional
from FIAT.reference_element import (Point,
Expand Down Expand Up @@ -61,14 +65,14 @@ def __init__(self, ref_el, degree):
# Vertices of the reference element.
v_hypercube = ref_el.get_vertices()
# For the mapping, first two vertices are unchanged in all dimensions.
v_ = list(v_hypercube[:2])
v_ = [v_hypercube[0], v_hypercube[int(-0.5*len(v_hypercube))]]

# For dimension 1 upwards,
# take the next vertex and map it to the midpoint of the edge/face it belongs to, and shares
# with no other points.
for d in range(1, ref_el.get_dimension()):
v_.append(tuple(np.asarray(v_hypercube[d+1] +
np.average(np.asarray(v_hypercube[2**d:2**(d+1)]), axis=0))))
v_.append(tuple(np.asarray(v_hypercube[ref_el.get_dimension() - d] +
np.average(np.asarray(v_hypercube[::2]), axis=0))))
A, b = make_affine_mapping(v_simplex, tuple(v_)) # Make affine mapping to be used later.

# make nodes by getting points
Expand All @@ -91,7 +95,7 @@ def __init__(self, ref_el, degree):
entity_ids[dim][entity] = []

entity_ids[dim][0] = list(range(len(nodes)))
super(DPCDualSet, self).__init__(nodes, hypercube_simplex_map[ref_el], entity_ids)
super(DPCDualSet, self).__init__(nodes, ref_el, entity_ids)


class HigherOrderDPC(finite_element.CiarletElement):
Expand Down
16 changes: 16 additions & 0 deletions FIAT/dual_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,19 @@ def to_riesz(self, poly_set):
self.mat[i][:] = self.nodes[i].to_riesz(poly_set)

return self.mat


def make_entity_closure_ids(ref_el, entity_ids):
entity_closure_ids = {}
for dim, entities in ref_el.sub_entities.items():
entity_closure_ids[dim] = {}

for e, sub_entities in entities.items():
ids = []

for d, se in sub_entities:
ids += entity_ids[d][se]
ids.sort()
entity_closure_ids[d][e] = ids

return entity_closure_ids
230 changes: 230 additions & 0 deletions FIAT/serendipity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
# Copyright (C) 2019 Cyrus Cheng (Imperial College London)
#
# 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/>.
#
# Modified by David A. Ham ([email protected]), 2019

from sympy import symbols, legendre, Array, diff
import numpy as np
from FIAT.finite_element import FiniteElement
from FIAT.lagrange import Lagrange
from FIAT.dual_set import make_entity_closure_ids
from FIAT.polynomial_set import mis

x, y, z = symbols('x y z')
variables = (x, y, z)
leg = legendre


def tr(n):
if n <= 1:
return 0
else:
return int((n-3)*(n-2)/2)


class Serendipity(FiniteElement):

def __new__(cls, ref_el, degree):
dim = ref_el.get_spatial_dimension()
if dim == 1:
return Lagrange(ref_el, degree)
elif dim == 0:
raise IndexError("reference element cannot be dimension 0")
else:
self = super().__new__(cls)
return self

def __init__(self, ref_el, degree):

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

x, y, z = symbols('x y z')
verts = ref_el.get_vertices()

dx = ((verts[-1][0] - x)/(verts[-1][0] - verts[0][0]), (x - verts[0][0])/(verts[-1][0] - verts[0][0]))
dy = ((verts[-1][1] - y)/(verts[-1][1] - verts[0][1]), (y - verts[0][1])/(verts[-1][1] - verts[0][1]))
x_mid = 2*x-(verts[-1][0] + verts[0][0])
y_mid = 2*y-(verts[-1][1] + verts[0][1])
try:
dz = ((verts[-1][2] - z)/(verts[-1][2] - verts[0][2]), (z - verts[0][2])/(verts[-1][2] - verts[0][2]))
z_mid = 2*z-(verts[-1][2] + verts[0][2])
except IndexError:
dz = None
z_mid = None

VL = v_lambda_0(dim, dx, dy, dz)
EL = []
FL = []
IL = []
s_list = []
entity_ids = {}
cur = 0

for top_dim, entities in topology.items():
entity_ids[top_dim] = {}
for entity in entities:
entity_ids[top_dim][entity] = []

for j in sorted(topology[0]):
entity_ids[0][j] = [cur]
cur = cur + 1

EL += e_lambda_0(degree, dim, dx, dy, dz, x_mid, y_mid, z_mid)

for j in sorted(topology[1]):
entity_ids[1][j] = list(range(cur, cur + degree - 1))
cur = cur + degree - 1

FL += f_lambda_0(degree, dim, dx, dy, dz, x_mid, y_mid, z_mid)

for j in sorted(topology[2]):
entity_ids[2][j] = list(range(cur, cur + tr(degree)))
cur = cur + tr(degree)

if dim == 3:
IL += i_lambda_0(degree, dx, dy, dz, x_mid, y_mid, z_mid)

entity_ids[3] = {}
entity_ids[3][0] = list(range(cur, cur + len(IL)))

s_list = VL + EL + FL + IL
assert len(s_list) == cur
formdegree = 0

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

self.basis = {(0,)*dim: Array(s_list)}
self.entity_ids = entity_ids
self.entity_closure_ids = make_entity_closure_ids(ref_el, entity_ids)
self._degree = degree

def degree(self):
return self._degree + 1

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

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

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

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

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

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

phivals = {}
dim = self.ref_el.get_spatial_dimension()
if dim <= 1:
raise NotImplementedError('no tabulate method for serendipity elements of dimension 1 or less.')
if dim >= 4:
raise NotImplementedError('tabulate does not support higher dimensions than 3.')
for o in range(order + 1):
alphas = mis(dim, o)
for alpha in alphas:
try:
poly = self.basis[alpha]
except KeyError:
poly = diff(self.basis[(0,)*dim], *zip(variables, alpha))
self.basis[alpha] = poly
T = np.zeros((len(poly), len(points)))
for i in range(len(points)):
subs = {v: points[i][k] for k, v in enumerate(variables[:dim])}
for j, f in enumerate(poly):
T[j, i] = f.evalf(subs=subs)
phivals[alpha] = T

return phivals

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,)*self.ref_el.get_spatial_dimension()])


def v_lambda_0(dim, dx, dy, dz):

if dim == 2:
VL = [a*b for a in dx for b in dy]
else:
VL = [a*b*c for a in dx for b in dy for c in dz]

return VL


def e_lambda_0(i, dim, dx, dy, dz, x_mid, y_mid, z_mid):

if dim == 2:
EL = tuple([-leg(j, y_mid) * dy[0] * dy[1] * a for a in dx for j in range(i-1)] +
[-leg(j, x_mid) * dx[0] * dx[1] * b for b in dy for j in range(i-1)])
else:
EL = tuple([-leg(j, x_mid) * dx[0] * dx[1] * b * c for b in dy for c in dz for j in range(i-1)] +
[-leg(j, y_mid) * dy[0] * dy[1] * a * c for c in dz for a in dx for j in range(i-1)] +
[-leg(j, z_mid) * dz[0] * dz[1] * a * b for a in dx for b in dy for j in range(i-1)])

return EL


def f_lambda_0(i, dim, dx, dy, dz, x_mid, y_mid, z_mid):

if dim == 2:
FL = tuple([leg(j, x_mid) * leg(k-4-j, y_mid) * dx[0] * dx[1] * dy[0] * dy[1]
for k in range(4, i + 1) for j in range(k-3)])
else:
FL = tuple([leg(j, x_mid) * leg(k-4-j, y_mid) * dx[0] * dx[1] * dy[0] * dy[1] * c
for k in range(4, i + 1) for j in range(i-3) for c in dz] +
[leg(j, z_mid) * leg(k-4-j, x_mid) * dx[0] * dx[1] * dz[0] * dz[1] * b
for k in range(4, i + 1) for j in range(i-3) for b in dy] +
[leg(j, y_mid) * leg(k-4-j, z_mid) * dy[0] * dy[1] * dz[0] * dz[1] * a
for k in range(4, i + 1) for j in range(i-3) for a in dx])

return FL


def i_lambda_0(i, dx, dy, dz, x_mid, y_mid, z_mid):

assert i >= 6, 'invalid value for i'

IL = tuple([-leg(l-6-j, x_mid) * leg(j-k, y_mid) * leg(k, z_mid) *
dx[0] * dx[1] * dy[0] * dy[1] * dz[0] * dz[1]
for l in range(6, i + 1) for j in range(i-5) for k in range(j+1)])

return IL
4 changes: 2 additions & 2 deletions test/unit/test_discontinuous_pc.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,15 @@
#
# Authors:
#
# David Ham
# David Ham and Cyrus Cheng

import pytest
import numpy as np


@pytest.mark.parametrize("dim, degree", [(dim, degree)
for dim in range(1, 4)
for degree in range(4)])
for degree in range(6)])
def test_basis_values(dim, degree):
"""Ensure that integrating a simple monomial produces the expected results."""
from FIAT import ufc_cell, make_quadrature
Expand Down

0 comments on commit 58658a6

Please sign in to comment.