diff --git a/FIAT/macro.py b/FIAT/macro.py index 77ab8938..18b95114 100644 --- a/FIAT/macro.py +++ b/FIAT/macro.py @@ -1,5 +1,6 @@ from itertools import chain, combinations +from functools import cache import numpy from FIAT import expansions, polynomial_set @@ -195,6 +196,7 @@ def get_parent_complex(self): return self._parent_complex +@cache class IsoSplit(SplitSimplicialComplex): """Splits simplex into the simplicial complex obtained by connecting points on a regular lattice. @@ -300,6 +302,7 @@ def construct_subcomplex(self, dimension): return PowellSabinSplit(subcomplex, dimension=self.split_dimension) +@cache class AlfeldSplit(PowellSabinSplit): """Splits a simplicial complex by connecting cell vertices to their barycenter. diff --git a/finat/__init__.py b/finat/__init__.py index d4ba0eb1..0bf386c7 100644 --- a/finat/__init__.py +++ b/finat/__init__.py @@ -1,109 +1,44 @@ -from .fiat_elements import Bernstein -from .fiat_elements import Bubble, CrouzeixRaviart, DiscontinuousTaylor -from .fiat_elements import Lagrange, DiscontinuousLagrange, Real -from .fiat_elements import DPC, Serendipity, BrezziDouglasMariniCubeEdge, BrezziDouglasMariniCubeFace -from .fiat_elements import TrimmedSerendipityFace, TrimmedSerendipityEdge -from .fiat_elements import TrimmedSerendipityDiv -from .fiat_elements import TrimmedSerendipityCurl -from .fiat_elements import BrezziDouglasMarini, BrezziDouglasFortinMarini -from .fiat_elements import Nedelec, NedelecSecondKind, RaviartThomas -from .fiat_elements import HellanHerrmannJohnson, Regge -from .fiat_elements import GopalakrishnanLedererSchoberlFirstKind -from .fiat_elements import GopalakrishnanLedererSchoberlSecondKind -from .fiat_elements import FacetBubble -from .fiat_elements import KongMulderVeldhuizen +from .fiat_elements import (Bernstein, Bubble, BrezziDouglasFortinMarini, # noqa: F401 + BrezziDouglasMarini, BrezziDouglasMariniCubeEdge, # noqa: F401 + BrezziDouglasMariniCubeFace, CrouzeixRaviart, # noqa: F401 + DiscontinuousLagrange, DiscontinuousTaylor, DPC, # noqa: F401 + FacetBubble, GopalakrishnanLedererSchoberlFirstKind, # noqa: F401 + GopalakrishnanLedererSchoberlSecondKind, HellanHerrmannJohnson, # noqa: F401 + KongMulderVeldhuizen, Lagrange, Real, Serendipity, # noqa: F401 + TrimmedSerendipityCurl, TrimmedSerendipityDiv, # noqa: F401 + TrimmedSerendipityEdge, TrimmedSerendipityFace, # noqa: F401 + Nedelec, NedelecSecondKind, RaviartThomas, Regge) # noqa: F401 -from .argyris import Argyris -from .aw import ArnoldWinther -from .aw import ArnoldWintherNC -from .hz import HuZhang -from .bell import Bell -from .bernardi_raugel import BernardiRaugel, BernardiRaugelBubble -from .hct import HsiehCloughTocher, ReducedHsiehCloughTocher -from .arnold_qin import ArnoldQin, ReducedArnoldQin -from .christiansen_hu import ChristiansenHu -from .alfeld_sorokina import AlfeldSorokina -from .guzman_neilan import GuzmanNeilanFirstKindH1, GuzmanNeilanSecondKindH1, GuzmanNeilanBubble, GuzmanNeilanH1div -from .powell_sabin import QuadraticPowellSabin6, QuadraticPowellSabin12 -from .hermite import Hermite -from .johnson_mercier import JohnsonMercier -from .mtw import MardalTaiWinther -from .morley import Morley -from .trace import HDivTrace -from .direct_serendipity import DirectSerendipity +from .argyris import Argyris # noqa: F401 +from .aw import ArnoldWinther, ArnoldWintherNC # noqa: F401 +from .hz import HuZhang # noqa: F401 +from .bell import Bell # noqa: F401 +from .bernardi_raugel import BernardiRaugel, BernardiRaugelBubble # noqa: F401 +from .hct import HsiehCloughTocher, ReducedHsiehCloughTocher # noqa: F401 +from .arnold_qin import ArnoldQin, ReducedArnoldQin # noqa: F401 +from .christiansen_hu import ChristiansenHu # noqa: F401 +from .alfeld_sorokina import AlfeldSorokina # noqa: F401 +from .guzman_neilan import GuzmanNeilanFirstKindH1, GuzmanNeilanSecondKindH1, GuzmanNeilanBubble, GuzmanNeilanH1div # noqa: F401 +from .powell_sabin import QuadraticPowellSabin6, QuadraticPowellSabin12 # noqa: F401 +from .hermite import Hermite # noqa: F401 +from .johnson_mercier import JohnsonMercier # noqa: F401 +from .mtw import MardalTaiWinther # noqa: F401 +from .morley import Morley # noqa: F401 +from .trace import HDivTrace # noqa: F401 +from .direct_serendipity import DirectSerendipity # noqa: F401 from .spectral import GaussLobattoLegendre, GaussLegendre, Legendre, IntegratedLegendre, FDMLagrange, FDMQuadrature, FDMDiscontinuousLagrange, FDMBrokenH1, FDMBrokenL2, FDMHermite # noqa: F401 -from .tensorfiniteelement import TensorFiniteElement # noqa: F401 -from .tensor_product import TensorProductElement # noqa: F401 -from .cube import FlattenedDimensions # noqa: F401 -from .discontinuous import DiscontinuousElement # noqa: F401 -from .enriched import EnrichedElement # noqa: F401 -from .hdivcurl import HCurlElement, HDivElement # noqa: F401 -from .mixed import MixedElement # noqa: F401 -from .nodal_enriched import NodalEnrichedElement # noqa: 401 +from .tensorfiniteelement import TensorFiniteElement # noqa: F401 +from .tensor_product import TensorProductElement # noqa: F401 +from .cube import FlattenedDimensions # noqa: F401 +from .discontinuous import DiscontinuousElement # noqa: F401 +from .enriched import EnrichedElement # noqa: F401 +from .hdivcurl import HCurlElement, HDivElement # noqa: F401 +from .mixed import MixedElement # noqa: F401 +from .nodal_enriched import NodalEnrichedElement # noqa: F401 from .quadrature_element import QuadratureElement, make_quadrature_element # noqa: F401 -from .restricted import RestrictedElement # noqa: F401 -from .runtime_tabulated import RuntimeTabulated # noqa: F401 -from . import quadrature # noqa: F401 -from . import cell_tools # noqa: F401 - -# List of supported elements and mapping to element classes -supported_elements = {"Argyris": Argyris, - "Bell": Bell, - "Bernardi-Raugel": BernardiRaugel, - "Bernardi-Raugel Bubble": BernardiRaugelBubble, - "Bernstein": Bernstein, - "Brezzi-Douglas-Fortin-Marini": BrezziDouglasFortinMarini, - "Brezzi-Douglas-Marini Cube Face": BrezziDouglasMariniCubeFace, - "Brezzi-Douglas-Marini": BrezziDouglasMarini, - "Brezzi-Douglas-Marini Cube Edge": BrezziDouglasMariniCubeEdge, - "Bubble": Bubble, - "FacetBubble": FacetBubble, - "Crouzeix-Raviart": CrouzeixRaviart, - "Direct Serendipity": DirectSerendipity, - "Discontinuous Lagrange": DiscontinuousLagrange, - "Discontinuous Lagrange L2": DiscontinuousLagrange, - "Discontinuous Taylor": DiscontinuousTaylor, - "Discontinuous Raviart-Thomas": lambda *args, **kwargs: DiscontinuousElement(RaviartThomas(*args, **kwargs)), - "DPC": DPC, - "DPC L2": DPC, - "Hermite": Hermite, - "Hsieh-Clough-Tocher": HsiehCloughTocher, - "Reduced-Hsieh-Clough-Tocher": ReducedHsiehCloughTocher, - "QuadraticPowellSabin6": QuadraticPowellSabin6, - "QuadraticPowellSabin12": QuadraticPowellSabin12, - "Alfeld-Sorokina": AlfeldSorokina, - "Arnold-Qin": ArnoldQin, - "Reduced-Arnold-Qin": ReducedArnoldQin, - "Christiansen-Hu": ChristiansenHu, - "Guzman-Neilan 1st kind H1": GuzmanNeilanFirstKindH1, - "Guzman-Neilan 2nd kind H1": GuzmanNeilanSecondKindH1, - "Guzman-Neilan H1(div)": GuzmanNeilanH1div, - "Guzman-Neilan Bubble": GuzmanNeilanBubble, - "Johnson-Mercier": JohnsonMercier, - "Lagrange": Lagrange, - "Kong-Mulder-Veldhuizen": KongMulderVeldhuizen, - "Gauss-Lobatto-Legendre": GaussLobattoLegendre, - "Gauss-Legendre": GaussLegendre, - "Gauss-Legendre L2": GaussLegendre, - "Legendre": Legendre, - "Integrated Legendre": IntegratedLegendre, - "Morley": Morley, - "Nedelec 1st kind H(curl)": Nedelec, - "Nedelec 2nd kind H(curl)": NedelecSecondKind, - "Raviart-Thomas": RaviartThomas, - "Real": Real, - "S": Serendipity, - "SminusF": TrimmedSerendipityFace, - "SminusDiv": TrimmedSerendipityDiv, - "SminusE": TrimmedSerendipityEdge, - "SminusCurl": TrimmedSerendipityCurl, - "Regge": Regge, - "HDiv Trace": HDivTrace, - "Hellan-Herrmann-Johnson": HellanHerrmannJohnson, - "Gopalakrishnan-Lederer-Schoberl 1st kind": GopalakrishnanLedererSchoberlFirstKind, - "Gopalakrishnan-Lederer-Schoberl 2nd kind": GopalakrishnanLedererSchoberlSecondKind, - "Conforming Arnold-Winther": ArnoldWinther, - "Nonconforming Arnold-Winther": ArnoldWintherNC, - "Hu-Zhang": HuZhang, - "Mardal-Tai-Winther": MardalTaiWinther} +from .restricted import RestrictedElement # noqa: F401 +from .runtime_tabulated import RuntimeTabulated # noqa: F401 +from . import quadrature # noqa: F401 +from . import cell_tools # noqa: F401 +from . import element_factory # noqa: F401 diff --git a/finat/element_factory.py b/finat/element_factory.py new file mode 100644 index 00000000..317fa7a0 --- /dev/null +++ b/finat/element_factory.py @@ -0,0 +1,368 @@ +# This file was modified from FFC +# (http://bitbucket.org/fenics-project/ffc), copyright notice +# reproduced below. +# +# Copyright (C) 2009-2013 Kristian B. Oelgaard and Anders Logg +# +# This file is part of FFC. +# +# FFC 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. +# +# FFC 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 FFC. If not, see . + +import weakref +from functools import singledispatch, cache + +import finat +import finat.ufl +import ufl + +from FIAT import ufc_cell + +__all__ = ("as_fiat_cell", "create_base_element", + "create_element", "supported_elements") + + +# List of supported elements and mapping to element classes +supported_elements = {"Argyris": finat.Argyris, + "Bell": finat.Bell, + "Bernardi-Raugel": finat.BernardiRaugel, + "Bernardi-Raugel Bubble": finat.BernardiRaugelBubble, + "Bernstein": finat.Bernstein, + "Brezzi-Douglas-Fortin-Marini": finat.BrezziDouglasFortinMarini, + "Brezzi-Douglas-Marini Cube Face": finat.BrezziDouglasMariniCubeFace, + "Brezzi-Douglas-Marini": finat.BrezziDouglasMarini, + "Brezzi-Douglas-Marini Cube Edge": finat.BrezziDouglasMariniCubeEdge, + "Bubble": finat.Bubble, + "FacetBubble": finat.FacetBubble, + "Crouzeix-Raviart": finat.CrouzeixRaviart, + "Direct Serendipity": finat.DirectSerendipity, + "Discontinuous Lagrange": finat.DiscontinuousLagrange, + "Discontinuous Lagrange L2": finat.DiscontinuousLagrange, + "Discontinuous Taylor": finat.DiscontinuousTaylor, + "Discontinuous Raviart-Thomas": lambda *args, **kwargs: finat.DiscontinuousElement(finat.RaviartThomas(*args, **kwargs)), + "DPC": finat.DPC, + "DPC L2": finat.DPC, + "Hermite": finat.Hermite, + "Hsieh-Clough-Tocher": finat.HsiehCloughTocher, + "Reduced-Hsieh-Clough-Tocher": finat.ReducedHsiehCloughTocher, + "QuadraticPowellSabin6": finat.QuadraticPowellSabin6, + "QuadraticPowellSabin12": finat.QuadraticPowellSabin12, + "Alfeld-Sorokina": finat.AlfeldSorokina, + "Arnold-Qin": finat.ArnoldQin, + "Reduced-Arnold-Qin": finat.ReducedArnoldQin, + "Christiansen-Hu": finat.ChristiansenHu, + "Guzman-Neilan 1st kind H1": finat.GuzmanNeilanFirstKindH1, + "Guzman-Neilan 2nd kind H1": finat.GuzmanNeilanSecondKindH1, + "Guzman-Neilan H1(div)": finat.GuzmanNeilanH1div, + "Guzman-Neilan Bubble": finat.GuzmanNeilanBubble, + "Johnson-Mercier": finat.JohnsonMercier, + "Lagrange": finat.Lagrange, + "Kong-Mulder-Veldhuizen": finat.KongMulderVeldhuizen, + "Gauss-Lobatto-Legendre": finat.GaussLobattoLegendre, + "Gauss-Legendre": finat.GaussLegendre, + "Gauss-Legendre L2": finat.GaussLegendre, + "Legendre": finat.Legendre, + "Integrated Legendre": finat.IntegratedLegendre, + "Morley": finat.Morley, + "Nedelec 1st kind H(curl)": finat.Nedelec, + "Nedelec 2nd kind H(curl)": finat.NedelecSecondKind, + "Raviart-Thomas": finat.RaviartThomas, + "Real": finat.Real, + "S": finat.Serendipity, + "SminusF": finat.TrimmedSerendipityFace, + "SminusDiv": finat.TrimmedSerendipityDiv, + "SminusE": finat.TrimmedSerendipityEdge, + "SminusCurl": finat.TrimmedSerendipityCurl, + "Regge": finat.Regge, + "HDiv Trace": finat.HDivTrace, + "Hellan-Herrmann-Johnson": finat.HellanHerrmannJohnson, + "Gopalakrishnan-Lederer-Schoberl 1st kind": finat.GopalakrishnanLedererSchoberlFirstKind, + "Gopalakrishnan-Lederer-Schoberl 2nd kind": finat.GopalakrishnanLedererSchoberlSecondKind, + "Conforming Arnold-Winther": finat.ArnoldWinther, + "Nonconforming Arnold-Winther": finat.ArnoldWintherNC, + "Hu-Zhang": finat.HuZhang, + "Mardal-Tai-Winther": finat.MardalTaiWinther, + # These require special treatment + "Q": None, + "DQ": None, + "DQ L2": None, + "RTCE": None, + "RTCF": None, + "NCE": None, + "NCF": None, + } +"""A :class:`.dict` mapping UFL element family names to their +FInAT-equivalent constructors. If the value is ``None``, the UFL +element is supported, but must be handled specially because it doesn't +have a direct FInAT equivalent.""" + + +@cache +def as_fiat_cell(cell): + """Convert a ufl cell to a FIAT cell. + + :arg cell: the :class:`ufl.Cell` to convert.""" + if not isinstance(cell, ufl.AbstractCell): + raise ValueError("Expecting a UFL Cell") + return ufc_cell(cell) + + +@singledispatch +def convert(element, **kwargs): + """Handler for converting UFL elements to FInAT elements. + + :arg element: The UFL element to convert. + + Do not use this function directly, instead call + :func:`create_element`.""" + if element.family() in supported_elements: + raise ValueError("Element %s supported, but no handler provided" % element) + raise ValueError("Unsupported element type %s" % type(element)) + + +cg_interval_variants = { + "fdm": finat.FDMLagrange, + "fdm_ipdg": finat.FDMLagrange, + "fdm_quadrature": finat.FDMQuadrature, + "fdm_broken": finat.FDMBrokenH1, + "fdm_hermite": finat.FDMHermite, +} + + +dg_interval_variants = { + "fdm": finat.FDMDiscontinuousLagrange, + "fdm_quadrature": finat.FDMDiscontinuousLagrange, + "fdm_ipdg": lambda *args: finat.DiscontinuousElement(finat.FDMLagrange(*args)), + "fdm_broken": finat.FDMBrokenL2, +} + + +# Base finite elements first +@convert.register(finat.ufl.FiniteElement) +def convert_finiteelement(element, **kwargs): + cell = as_fiat_cell(element.cell) + if element.family() == "Quadrature": + degree = element.degree() + scheme = element.quadrature_scheme() + if degree is None or scheme is None: + raise ValueError("Quadrature scheme and degree must be specified!") + + return finat.make_quadrature_element(cell, degree, scheme), set() + lmbda = supported_elements[element.family()] + if element.family() == "Real" and element.cell.cellname() in {"quadrilateral", "hexahedron"}: + lmbda = None + element = finat.ufl.FiniteElement("DQ", element.cell, 0) + if lmbda is None: + if element.cell.cellname() == "quadrilateral": + # Handle quadrilateral short names like RTCF and RTCE. + element = element.reconstruct(cell=quadrilateral_tpc) + elif element.cell.cellname() == "hexahedron": + # Handle hexahedron short names like NCF and NCE. + element = element.reconstruct(cell=hexahedron_tpc) + else: + raise ValueError("%s is supported, but handled incorrectly" % + element.family()) + finat_elem, deps = _create_element(element, **kwargs) + return finat.FlattenedDimensions(finat_elem), deps + + finat_kwargs = {} + kind = element.variant() + if kind is None: + kind = 'spectral' # default variant + + if element.family() == "Lagrange": + if kind == 'spectral': + lmbda = finat.GaussLobattoLegendre + elif element.cell.cellname() == "interval" and kind in cg_interval_variants: + lmbda = cg_interval_variants[kind] + elif any(map(kind.startswith, ['integral', 'demkowicz', 'fdm'])): + lmbda = finat.IntegratedLegendre + finat_kwargs["variant"] = kind + elif kind in ['mgd', 'feec', 'qb', 'mse']: + degree = element.degree() + shift_axes = kwargs["shift_axes"] + restriction = kwargs["restriction"] + deps = {"shift_axes", "restriction"} + return finat.RuntimeTabulated(cell, degree, variant=kind, shift_axes=shift_axes, restriction=restriction), deps + else: + # Let FIAT handle the general case + lmbda = finat.Lagrange + finat_kwargs["variant"] = kind + + elif element.family() in ["Discontinuous Lagrange", "Discontinuous Lagrange L2"]: + if kind == 'spectral': + lmbda = finat.GaussLegendre + elif element.cell.cellname() == "interval" and kind in dg_interval_variants: + lmbda = dg_interval_variants[kind] + elif any(map(kind.startswith, ['integral', 'demkowicz', 'fdm'])): + lmbda = finat.Legendre + finat_kwargs["variant"] = kind + elif kind in ['mgd', 'feec', 'qb', 'mse']: + degree = element.degree() + shift_axes = kwargs["shift_axes"] + restriction = kwargs["restriction"] + deps = {"shift_axes", "restriction"} + return finat.RuntimeTabulated(cell, degree, variant=kind, shift_axes=shift_axes, restriction=restriction, continuous=False), deps + else: + # Let FIAT handle the general case + lmbda = finat.DiscontinuousLagrange + finat_kwargs["variant"] = kind + + elif element.variant() is not None: + finat_kwargs["variant"] = element.variant() + + return lmbda(cell, element.degree(), **finat_kwargs), set() + + +# Element modifiers and compound element types +@convert.register(finat.ufl.BrokenElement) +def convert_brokenelement(element, **kwargs): + finat_elem, deps = _create_element(element._element, **kwargs) + return finat.DiscontinuousElement(finat_elem), deps + + +@convert.register(finat.ufl.EnrichedElement) +def convert_enrichedelement(element, **kwargs): + elements, deps = zip(*[_create_element(elem, **kwargs) + for elem in element._elements]) + return finat.EnrichedElement(elements), set.union(*deps) + + +@convert.register(finat.ufl.NodalEnrichedElement) +def convert_nodalenrichedelement(element, **kwargs): + elements, deps = zip(*[_create_element(elem, **kwargs) + for elem in element._elements]) + return finat.NodalEnrichedElement(elements), set.union(*deps) + + +@convert.register(finat.ufl.MixedElement) +def convert_mixedelement(element, **kwargs): + elements, deps = zip(*[_create_element(elem, **kwargs) + for elem in element.sub_elements]) + return finat.MixedElement(elements), set.union(*deps) + + +@convert.register(finat.ufl.VectorElement) +@convert.register(finat.ufl.TensorElement) +def convert_tensorelement(element, **kwargs): + inner_elem, deps = _create_element(element.sub_elements[0], **kwargs) + shape = element.reference_value_shape + shape = shape[:len(shape) - len(inner_elem.value_shape)] + shape_innermost = kwargs["shape_innermost"] + return (finat.TensorFiniteElement(inner_elem, shape, not shape_innermost), + deps | {"shape_innermost"}) + + +@convert.register(finat.ufl.TensorProductElement) +def convert_tensorproductelement(element, **kwargs): + cell = element.cell + if type(cell) is not ufl.TensorProductCell: + raise ValueError("TensorProductElement not on TensorProductCell?") + shift_axes = kwargs["shift_axes"] + dim_offset = 0 + elements = [] + deps = set() + for elem in element.sub_elements: + kwargs["shift_axes"] = shift_axes + dim_offset + dim_offset += elem.cell.topological_dimension() + finat_elem, ds = _create_element(elem, **kwargs) + elements.append(finat_elem) + deps.update(ds) + return finat.TensorProductElement(elements), deps + + +@convert.register(finat.ufl.HDivElement) +def convert_hdivelement(element, **kwargs): + finat_elem, deps = _create_element(element._element, **kwargs) + return finat.HDivElement(finat_elem), deps + + +@convert.register(finat.ufl.HCurlElement) +def convert_hcurlelement(element, **kwargs): + finat_elem, deps = _create_element(element._element, **kwargs) + return finat.HCurlElement(finat_elem), deps + + +@convert.register(finat.ufl.WithMapping) +def convert_withmapping(element, **kwargs): + return _create_element(element.wrapee, **kwargs) + + +@convert.register(finat.ufl.RestrictedElement) +def convert_restrictedelement(element, **kwargs): + finat_elem, deps = _create_element(element._element, **kwargs) + return finat.RestrictedElement(finat_elem, element.restriction_domain()), deps + + +hexahedron_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval, ufl.interval) +quadrilateral_tpc = ufl.TensorProductCell(ufl.interval, ufl.interval) +_cache = weakref.WeakKeyDictionary() + + +def create_element(ufl_element, shape_innermost=True, shift_axes=0, restriction=None): + """Create a FInAT element (suitable for tabulating with) given a UFL element. + + :arg ufl_element: The UFL element to create a FInAT element from. + :arg shape_innermost: Vector/tensor indices come after basis function indices + :arg restriction: cell restriction in interior facet integrals + (only for runtime tabulated elements) + """ + finat_element, deps = _create_element(ufl_element, + shape_innermost=shape_innermost, + shift_axes=shift_axes, + restriction=restriction) + return finat_element + + +def _create_element(ufl_element, **kwargs): + """A caching wrapper around :py:func:`convert`. + + Takes a UFL element and an unspecified set of parameter options, + and returns the converted element with the set of keyword names + that were relevant for conversion. + """ + # Look up conversion in cache + try: + cache = _cache[ufl_element] + except KeyError: + _cache[ufl_element] = {} + cache = _cache[ufl_element] + + for key, finat_element in cache.items(): + # Cache hit if all relevant parameter values match. + if all(kwargs[param] == value for param, value in key): + return finat_element, set(param for param, value in key) + + # Convert if cache miss + if ufl_element.cell is None: + raise ValueError("Don't know how to build element when cell is not given") + + finat_element, deps = convert(ufl_element, **kwargs) + + # Store conversion in cache + key = frozenset((param, kwargs[param]) for param in deps) + cache[key] = finat_element + + # Forward result + return finat_element, deps + + +def create_base_element(ufl_element, **kwargs): + """Create a "scalar" base FInAT element given a UFL element. + Takes a UFL element and an unspecified set of parameter options, + and returns the converted element. + """ + finat_element = create_element(ufl_element, **kwargs) + if isinstance(finat_element, finat.TensorFiniteElement): + finat_element = finat_element.base_element + return finat_element