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 fuse element infrastructure #127

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 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
8 changes: 4 additions & 4 deletions FIAT/nedelec.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ def NedelecSpace2D(ref_el, degree):
raise Exception("NedelecSpace2D requires 2d reference element")

k = degree - 1
vec_Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1, (sd,))
vec_Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1, (sd,), scale="orthonormal")

dimPkp1 = expansions.polynomial_dimension(ref_el, k + 1)
dimPk = expansions.polynomial_dimension(ref_el, k)
Expand All @@ -32,7 +32,7 @@ def NedelecSpace2D(ref_el, degree):
for i in range(sd))))
vec_Pk_from_Pkp1 = vec_Pkp1.take(vec_Pk_indices)

Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1)
Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1, scale="orthonormal")
PkH = Pkp1.take(list(range(dimPkm1, dimPk)))

Q = create_quadrature(ref_el, 2 * (k + 1))
Expand All @@ -43,8 +43,8 @@ def NedelecSpace2D(ref_el, degree):

CrossX = numpy.dot(numpy.array([[0.0, 1.0], [-1.0, 0.0]]), Qpts.T)
PkHCrossX_at_Qpts = PkH_at_Qpts[:, None, :] * CrossX[None, :, :]
PkHCrossX_coeffs = numpy.dot(numpy.multiply(PkHCrossX_at_Qpts, Qwts), Pkp1_at_Qpts.T)

PkHCrossX_coeffs = numpy.dot(numpy.multiply(PkHCrossX_at_Qpts, Qwts),
Pkp1_at_Qpts.T)
PkHcrossX = polynomial_set.PolynomialSet(ref_el,
k + 1,
k + 1,
Expand Down
42 changes: 39 additions & 3 deletions FIAT/polynomial_set.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,15 +176,51 @@ def polynomial_set_union_normalized(A, B):
not contain any of the same members of the set, as we construct a
span via SVD.
"""
new_coeffs = numpy.concatenate((A.coeffs, B.coeffs), axis=0)
assert A.get_reference_element() == B.get_reference_element()
new_coeffs = construct_new_coeffs(A.get_reference_element(), A, B)

deg = max(A.get_degree(), B.get_degree())
em_deg = max(A.get_embedded_degree(), B.get_embedded_degree())
coeffs = spanning_basis(new_coeffs)
return PolynomialSet(A.get_reference_element(),
A.get_degree(),
A.get_embedded_degree(),
deg,
em_deg,
A.get_expansion_set(),
coeffs)


def construct_new_coeffs(ref_el, A, B):
# Constructs new coefficients for the set union of A and B
# If A and B do not have the same degree the smaller one
# is extended to match the larger

sd = ref_el.get_spatial_dimension()
if A.get_embedded_degree() != B.get_embedded_degree():

Choose a reason for hiding this comment

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

We need to check also for the continuity of the expansion sets. We throw a ValueError if they are different. This logic would only work for discontinuous expansion sets, and break if the expansion set continuity is "C1".

Suggested change
if A.get_embedded_degree() != B.get_embedded_degree():
if A.get_embedded_degree() != B.get_embedded_degree() and continuity is None:

higher = A if A.degree > B.degree else B
lower = B if A.degree > B.degree else A

try:
sd = lower.get_shape()[0]
except IndexError:
sd = 1

embedded_coeffs = []
diff = higher.coeffs.shape[-1] - lower.coeffs.shape[-1]
for coeff in lower.coeffs:
if sd > 1:
new_coeff = []
for row in coeff:
new_coeff.append(numpy.append(row, [0 for i in range(diff)]))
embedded_coeffs.append(new_coeff)
else:
embedded_coeffs.append(numpy.append(coeff, [0 for i in range(diff)]))
embedded_coeffs = numpy.array(embedded_coeffs)
new_coeffs = numpy.array(list(embedded_coeffs) + list(higher.coeffs))
else:

Choose a reason for hiding this comment

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

this case works if A and B have the same continuity and degree.

Copy link
Author

Choose a reason for hiding this comment

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

I guess then this function still needs to handle the case where A and B are not discontinuous but don't have the same degree. I haven't encountered this yet but will add it as a note in the function.

new_coeffs = numpy.array(list(A.coeffs) + list(B.coeffs))
indiamai marked this conversation as resolved.
Show resolved Hide resolved
return new_coeffs


class ONSymTensorPolynomialSet(PolynomialSet):
"""Constructs an orthonormal basis for symmetric-tensor-valued
polynomials on a reference element.
Expand Down
6 changes: 3 additions & 3 deletions FIAT/raviart_thomas.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def RTSpace(ref_el, degree):
sd = ref_el.get_spatial_dimension()

k = degree - 1
vec_Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1, (sd,))
vec_Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1, (sd,), scale="orthonormal")

dimPkp1 = expansions.polynomial_dimension(ref_el, k + 1)
dimPk = expansions.polynomial_dimension(ref_el, k)
Expand All @@ -30,7 +30,7 @@ def RTSpace(ref_el, degree):
for i in range(sd))))
vec_Pk_from_Pkp1 = vec_Pkp1.take(vec_Pk_indices)

Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1)
Pkp1 = polynomial_set.ONPolynomialSet(ref_el, k + 1, scale="orthonormal")
PkH = Pkp1.take(list(range(dimPkm1, dimPk)))

Q = create_quadrature(ref_el, 2 * (k + 1))
Expand All @@ -39,12 +39,12 @@ def RTSpace(ref_el, degree):
# have to work on this through "tabulate" interface
# first, tabulate PkH at quadrature points
PkH_at_Qpts = PkH.tabulate(Qpts)[(0,) * sd]

Pkp1_at_Qpts = Pkp1.tabulate(Qpts)[(0,) * sd]

x = Qpts.T
PkHx_at_Qpts = PkH_at_Qpts[:, None, :] * x[None, :, :]
PkHx_coeffs = numpy.dot(numpy.multiply(PkHx_at_Qpts, Qwts), Pkp1_at_Qpts.T)

PkHx = polynomial_set.PolynomialSet(ref_el,
k,
k + 1,
Expand Down
3 changes: 2 additions & 1 deletion finat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
KongMulderVeldhuizen, Lagrange, Real, Serendipity, # noqa: F401
TrimmedSerendipityCurl, TrimmedSerendipityDiv, # noqa: F401
TrimmedSerendipityEdge, TrimmedSerendipityFace, # noqa: F401
Nedelec, NedelecSecondKind, RaviartThomas, Regge) # noqa: F401
Nedelec, NedelecSecondKind, RaviartThomas, Regge, # noqa: F401
IndiaDefElement) # noqa: F401

from .argyris import Argyris # noqa: F401
from .aw import ArnoldWinther, ArnoldWintherNC # noqa: F401
Expand Down
8 changes: 8 additions & 0 deletions finat/element_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

import finat
import finat.ufl
from fuse.cells import CellComplexToUFL as FuseCell
import ufl

from FIAT import ufc_cell
Expand Down Expand Up @@ -112,6 +113,8 @@ def as_fiat_cell(cell):
:arg cell: the :class:`ufl.Cell` to convert."""
if not isinstance(cell, ufl.AbstractCell):
raise ValueError("Expecting a UFL Cell")
if isinstance(cell, FuseCell):
return cell.to_fiat()
return ufc_cell(cell)


Expand Down Expand Up @@ -302,6 +305,11 @@ def convert_restrictedelement(element, **kwargs):
return finat.RestrictedElement(finat_elem, element.restriction_domain()), deps


@convert.register(finat.ufl.FuseElement)
def convert_india_def(element, **kwargs):
return finat.fiat_elements.IndiaDefElement(element.triple), set()


hexahedron_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval, ufl.interval)
quadrilateral_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval)
_cache = weakref.WeakKeyDictionary()
Expand Down
5 changes: 5 additions & 0 deletions finat/fiat_elements.py
Original file line number Diff line number Diff line change
Expand Up @@ -480,3 +480,8 @@ def __init__(self, cell, degree, variant=None):
class NedelecSecondKind(VectorFiatElement):
def __init__(self, cell, degree, variant=None):
super().__init__(FIAT.NedelecSecondKind(cell, degree, variant=variant))


class IndiaDefElement(FiatElement):
def __init__(self, triple):
super(IndiaDefElement, self).__init__(triple.to_fiat())
1 change: 1 addition & 0 deletions finat/ufl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,4 @@
from finat.ufl.mixedelement import MixedElement, TensorElement, VectorElement # noqa: F401
from finat.ufl.restrictedelement import RestrictedElement # noqa: F401
from finat.ufl.tensorproductelement import TensorProductElement # noqa: F401
from finat.ufl.fuseelement import FuseElement # noqa: F401
45 changes: 45 additions & 0 deletions finat/ufl/fuseelement.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
"""Element."""
# -*- coding: utf-8 -*-
# Copyright (C) 2025 India Marsden
#
# SPDX-License-Identifier: LGPL-3.0-or-later

from finat.ufl.finiteelementbase import FiniteElementBase


class FuseElement(FiniteElementBase):
"""
A finite element defined using FUSE.

TODO: Need to deal with cases where value shape and reference value shape are different
"""

def __init__(self, triple, cell=None):
self.triple = triple
if not cell:
cell = self.triple.cell.to_ufl()

# this isn't really correct
degree = self.triple.spaces[0].degree()
super(FuseElement, self).__init__("IT", cell, degree, None, triple.get_value_shape())

def __repr__(self):
return "FiniteElement(%s, %s, (%s, %s, %s), %s)" % (
repr(self.triple.DOFGenerator), repr(self.triple.cell), repr(self.triple.spaces[0]), repr(self.triple.spaces[1]), repr(self.triple.spaces[2]), "X")

def __str__(self):
return "<Fuse%sElem on %s>" % (self.triple.spaces[0], self.triple.cell)

def mapping(self):
if str(self.sobolev_space) == "HCurl":
return "covariant Piola"
elif str(self.sobolev_space) == "HDiv":
return "contravariant Piola"
else:
return "identity"

def sobolev_space(self):
return self.triple.spaces[1]

def reconstruct(self, family=None, cell=None, degree=None, quad_scheme=None, variant=None):
return FuseElement(self.triple, cell=cell)
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ dependencies = [
"symengine",
"sympy",
"fenics-ufl @ git+https://github.com/firedrakeproject/ufl.git",
"fuse-element @ git+https://github.com/indiamai/fuse.git",
]
requires-python = ">=3.10"
authors = [
Expand Down
Loading