Source code for finat.ufl.finiteelement

"""This module defines the UFL finite element classes."""
# Copyright (C) 2008-2016 Martin Sandve Alnæs
#
# This file was originally part of UFL (https://www.fenicsproject.org)
#
# SPDX-License-Identifier:    LGPL-3.0-or-later
#
# Modified by Kristian B. Oelgaard
# Modified by Marie E. Rognes 2010, 2012
# Modified by Anders Logg 2014
# Modified by Massimiliano Leoni, 2016
# Modified by Matthew Scroggs, 2023

from ufl.cell import TensorProductCell, as_cell
from finat.ufl.elementlist import canonical_element_description, simplices
from finat.ufl.finiteelementbase import FiniteElementBase
from ufl.utils.formatting import istr


[docs]class FiniteElement(FiniteElementBase): """The basic finite element class for all simple finite elements.""" # TODO: Move these to base? __slots__ = ("_short_name", "_sobolev_space", "_mapping", "_variant", "_repr") def __new__(cls, family, cell=None, degree=None, form_degree=None, quad_scheme=None, variant=None): """Intercepts construction to expand CG, DG, RTCE and RTCF spaces on TensorProductCells.""" if cell is not None: cell = as_cell(cell) if isinstance(cell, TensorProductCell): # Delay import to avoid circular dependency at module load time from finat.ufl.enrichedelement import EnrichedElement from finat.ufl.hdivcurl import HCurlElement as HCurl from finat.ufl.hdivcurl import HDivElement as HDiv from finat.ufl.tensorproductelement import TensorProductElement family, short_name, degree, value_shape, reference_value_shape, sobolev_space, mapping = \ canonical_element_description(family, cell, degree, form_degree) if family in ["RTCF", "RTCE"]: cell_h, cell_v = cell.sub_cells() if cell_h.cellname() != "interval": raise ValueError(f"{family} is available on TensorProductCell(interval, interval) only.") if cell_v.cellname() != "interval": raise ValueError(f"{family} is available on TensorProductCell(interval, interval) only.") C_elt = FiniteElement("CG", "interval", degree, variant=variant) D_elt = FiniteElement("DG", "interval", degree - 1, variant=variant) CxD_elt = TensorProductElement(C_elt, D_elt, cell=cell) DxC_elt = TensorProductElement(D_elt, C_elt, cell=cell) if family == "RTCF": return EnrichedElement(HDiv(CxD_elt), HDiv(DxC_elt)) if family == "RTCE": return EnrichedElement(HCurl(CxD_elt), HCurl(DxC_elt)) elif family == "NCF": cell_h, cell_v = cell.sub_cells() if cell_h.cellname() != "quadrilateral": raise ValueError(f"{family} is available on TensorProductCell(quadrilateral, interval) only.") if cell_v.cellname() != "interval": raise ValueError(f"{family} is available on TensorProductCell(quadrilateral, interval) only.") Qc_elt = FiniteElement("RTCF", "quadrilateral", degree, variant=variant) Qd_elt = FiniteElement("DQ", "quadrilateral", degree - 1, variant=variant) Id_elt = FiniteElement("DG", "interval", degree - 1, variant=variant) Ic_elt = FiniteElement("CG", "interval", degree, variant=variant) return EnrichedElement(HDiv(TensorProductElement(Qc_elt, Id_elt, cell=cell)), HDiv(TensorProductElement(Qd_elt, Ic_elt, cell=cell))) elif family == "NCE": cell_h, cell_v = cell.sub_cells() if cell_h.cellname() != "quadrilateral": raise ValueError(f"{family} is available on TensorProductCell(quadrilateral, interval) only.") if cell_v.cellname() != "interval": raise ValueError(f"{family} is available on TensorProductCell(quadrilateral, interval) only.") Qc_elt = FiniteElement("Q", "quadrilateral", degree, variant=variant) Qd_elt = FiniteElement("RTCE", "quadrilateral", degree, variant=variant) Id_elt = FiniteElement("DG", "interval", degree - 1, variant=variant) Ic_elt = FiniteElement("CG", "interval", degree, variant=variant) return EnrichedElement(HCurl(TensorProductElement(Qc_elt, Id_elt, cell=cell)), HCurl(TensorProductElement(Qd_elt, Ic_elt, cell=cell))) elif family == "Q": return TensorProductElement(*[FiniteElement("CG", c, degree, variant=variant) for c in cell.sub_cells()], cell=cell) elif family == "DQ": def dq_family(cell): """Doc.""" return "DG" if cell.cellname() in simplices else "DQ" return TensorProductElement(*[FiniteElement(dq_family(c), c, degree, variant=variant) for c in cell.sub_cells()], cell=cell) elif family == "DQ L2": def dq_family_l2(cell): """Doc.""" return "DG L2" if cell.cellname() in simplices else "DQ L2" return TensorProductElement(*[FiniteElement(dq_family_l2(c), c, degree, variant=variant) for c in cell.sub_cells()], cell=cell) return super(FiniteElement, cls).__new__(cls) def __init__(self, family, cell=None, degree=None, form_degree=None, quad_scheme=None, variant=None): """Create finite element. Args: family: The finite element family cell: The geometric cell degree: The polynomial degree (optional) form_degree: The form degree (FEEC notation, used when field is viewed as k-form) quad_scheme: The quadrature scheme (optional) variant: Hint for the local basis function variant (optional) """ # Note: Unfortunately, dolfin sometimes passes None for # cell. Until this is fixed, allow it: if cell is not None: cell = as_cell(cell) ( family, short_name, degree, value_shape, reference_value_shape, sobolev_space, mapping ) = canonical_element_description(family, cell, degree, form_degree) # TODO: Move these to base? Might be better to instead # simplify base though. self._sobolev_space = sobolev_space self._mapping = mapping self._short_name = short_name or family self._variant = variant # Type check variant if variant is not None and not isinstance(variant, str): raise ValueError("Illegal variant: must be string or None") # Initialize element data FiniteElementBase.__init__(self, family, cell, degree, quad_scheme, value_shape, reference_value_shape) # Cache repr string qs = self.quadrature_scheme() if qs is None: quad_str = "" else: quad_str = ", quad_scheme=%s" % repr(qs) v = self.variant() if v is None: var_str = "" else: var_str = ", variant=%s" % repr(v) self._repr = "FiniteElement(%s, %s, %s%s%s)" % ( repr(self.family()), repr(self.cell), repr(self.degree()), quad_str, var_str) assert '"' not in self._repr def __repr__(self): """Format as string for evaluation as Python object.""" return self._repr def _is_globally_constant(self): """Doc.""" return self.family() == "Real" def _is_linear(self): """Doc.""" return self.family() == "Lagrange" and self.degree() == 1
[docs] def mapping(self): """Return the mapping type for this element .""" return self._mapping
@property def sobolev_space(self): """Return the underlying Sobolev space.""" return self._sobolev_space
[docs] def variant(self): """Return the variant used to initialise the element.""" return self._variant
[docs] def reconstruct(self, family=None, cell=None, degree=None, quad_scheme=None, variant=None): """Construct a new FiniteElement object with some properties replaced with new values.""" if family is None: family = self.family() if cell is None: cell = self.cell if degree is None: degree = self.degree() if quad_scheme is None: quad_scheme = self.quadrature_scheme() if variant is None: variant = self.variant() return FiniteElement(family, cell, degree, quad_scheme=quad_scheme, variant=variant)
def __str__(self): """Format as string for pretty printing.""" qs = self.quadrature_scheme() qs = "" if qs is None else "(%s)" % qs v = self.variant() v = "" if v is None else "(%s)" % v return "<%s%s%s%s on a %s>" % (self._short_name, istr(self.degree()), qs, v, self.cell)
[docs] def shortstr(self): """Format as string for pretty printing.""" return f"{self._short_name}{istr(self.degree())}({self.quadrature_scheme()},{istr(self.variant())})"
def __getnewargs__(self): """Return the arguments which pickle needs to recreate the object.""" return (self.family(), self.cell, self.degree(), None, self.quadrature_scheme(), self.variant()) @property def embedded_subdegree(self): """Return embedded subdegree.""" if isinstance(self.degree(), int): return self.degree() else: return min(self.degree()) @property def embedded_superdegree(self): """Return embedded superdegree.""" if isinstance(self.degree(), int): return self.degree() else: return max(self.degree())