Source code for finat.argyris
import numpy
from math import comb
import FIAT
from gem import Literal, ListTensor, partial_indexed
from finat.fiat_elements import ScalarFiatElement
from finat.physically_mapped import PhysicallyMappedElement, Citations
def _edge_transform(V, vorder, eorder, fiat_cell, coordinate_mapping, avg=False):
"""Basis transformation for integral edge moments.
:arg V: the transpose of the basis transformation.
:arg vorder: the jet order at vertices, matching the Jacobi weights in the
normal derivative moments on edges.
:arg eorder: the order of the normal derivative moments.
:arg fiat_cell: the reference triangle.
:arg coordinate_mapping: the coordinate mapping.
:kwarg avg: are we scaling integrals by dividing by the edge length?
"""
sd = fiat_cell.get_spatial_dimension()
J = coordinate_mapping.jacobian_at(fiat_cell.make_points(sd, 0, sd+1)[0])
rns = coordinate_mapping.reference_normals()
pts = coordinate_mapping.physical_tangents()
pns = coordinate_mapping.physical_normals()
pel = coordinate_mapping.physical_edge_lengths()
# number of DOFs per vertex/edge
voffset = comb(sd + vorder, vorder)
eoffset = 2 * eorder + 1
top = fiat_cell.get_topology()
for e in sorted(top[1]):
nhat = partial_indexed(rns, (e, ))
n = partial_indexed(pns, (e, ))
t = partial_indexed(pts, (e, ))
Bn = J @ nhat / pel[e]
Bnt = Bn @ t
Bnn = Bn @ n
if avg:
Bnn = Bnn * pel[e]
v0id, v1id = (v * voffset for v in top[1][e])
s0 = len(top[0]) * voffset + e * eoffset
for k in range(eorder+1):
s = s0 + k
# Jacobi polynomial at the endpoints
P1 = comb(k + vorder, k)
P0 = -(-1)**k * P1
V[s, s] = Bnn
V[s, v1id] = P1 * Bnt
V[s, v0id] = P0 * Bnt
if k > 0:
V[s, s + eorder] = -1 * Bnt
[docs]class Argyris(PhysicallyMappedElement, ScalarFiatElement):
def __init__(self, cell, degree=5, variant=None, avg=False):
if Citations is not None:
Citations().register("Argyris1968")
if variant is None:
variant = "integral"
if variant == "point" and degree != 5:
raise NotImplementedError("Degree must be 5 for 'point' variant of Argyris")
fiat_element = FIAT.Argyris(cell, degree, variant=variant)
self.variant = variant
self.avg = avg
super().__init__(fiat_element)
[docs] def basis_transformation(self, coordinate_mapping):
# Jacobian at barycenter
J = coordinate_mapping.jacobian_at([1/3, 1/3])
ndof = self.space_dimension()
V = numpy.eye(ndof, dtype=object)
for multiindex in numpy.ndindex(V.shape):
V[multiindex] = Literal(V[multiindex])
sd = self.cell.get_spatial_dimension()
top = self.cell.get_topology()
vorder = 2
voffset = comb(sd + vorder, vorder)
for v in sorted(top[0]):
s = voffset * v
for i in range(sd):
for j in range(sd):
V[s+1+i, s+1+j] = J[j, i]
V[s+3, s+3] = J[0, 0]*J[0, 0]
V[s+3, s+4] = 2*J[0, 0]*J[1, 0]
V[s+3, s+5] = J[1, 0]*J[1, 0]
V[s+4, s+3] = J[0, 0]*J[0, 1]
V[s+4, s+4] = J[0, 0]*J[1, 1] + J[1, 0]*J[0, 1]
V[s+4, s+5] = J[1, 0]*J[1, 1]
V[s+5, s+3] = J[0, 1]*J[0, 1]
V[s+5, s+4] = 2*J[0, 1]*J[1, 1]
V[s+5, s+5] = J[1, 1]*J[1, 1]
eorder = self.degree - 5
if self.variant == "integral":
_edge_transform(V, vorder, eorder, self.cell, coordinate_mapping, avg=self.avg)
else:
rns = coordinate_mapping.reference_normals()
pns = coordinate_mapping.physical_normals()
pts = coordinate_mapping.physical_tangents()
pel = coordinate_mapping.physical_edge_lengths()
for e in sorted(top[1]):
nhat = partial_indexed(rns, (e, ))
n = partial_indexed(pns, (e, ))
t = partial_indexed(pts, (e, ))
Bn = J @ nhat
Bnt = Bn @ t
Bnn = Bn @ n
s = len(top[0]) * voffset + e
v0id, v1id = (v * voffset for v in top[1][e])
V[s, s] = Bnn
# vertex points
V[s, v1id] = 15/8 * Bnt / pel[e]
V[s, v0id] = -1 * V[s, v1id]
# vertex derivatives
for i in range(sd):
V[s, v1id+1+i] = -7/16 * Bnt * t[i]
V[s, v0id+1+i] = V[s, v1id+1+i]
# second derivatives
tau = [t[0]*t[0], 2*t[0]*t[1], t[1]*t[1]]
for i in range(len(tau)):
V[s, v1id+3+i] = 1/32 * (pel[e] * Bnt * tau[i])
V[s, v0id+3+i] = -1 * V[s, v1id+3+i]
# Patch up conditioning
h = coordinate_mapping.cell_size()
for v in sorted(top[0]):
for k in range(sd):
V[:, voffset*v+1+k] *= 1 / h[v]
for k in range((sd+1)*sd//2):
V[:, voffset*v+3+k] *= 1 / (h[v]*h[v])
if self.variant == "point":
eoffset = 2 * eorder + 1
for e in sorted(top[1]):
v0, v1 = top[1][e]
s = len(top[0]) * voffset + e * eoffset
V[:, s:s+eorder+1] *= 2 / (h[v0] + h[v1])
return ListTensor(V.T)