diff --git a/.github/workflows/dolfin-tests.yml b/.github/workflows/dolfin-tests.yml index aa88c1692..dad2f9a42 100644 --- a/.github/workflows/dolfin-tests.yml +++ b/.github/workflows/dolfin-tests.yml @@ -51,7 +51,7 @@ jobs: if: github.event_name != 'workflow_dispatch' run: | python3 -m pip install git+https://github.com/FEniCS/ufl.git - python3 -m pip install git+https://github.com/FEniCS/ffcx.git + python3 -m pip install git+https://github.com/FEniCS/ffcx.git@mscroggs/embedded-degree - name: Install FEniCS Python components if: github.event_name == 'workflow_dispatch' run: | @@ -64,7 +64,7 @@ jobs: with: path: ./dolfinx repository: FEniCS/dolfinx - ref: main + ref: mscroggs/embedded-degree - name: Get DOLFINx if: github.event_name == 'workflow_dispatch' uses: actions/checkout@v3 diff --git a/.github/workflows/ffcx-tests.yml b/.github/workflows/ffcx-tests.yml index a5b0d3444..01accdeed 100644 --- a/.github/workflows/ffcx-tests.yml +++ b/.github/workflows/ffcx-tests.yml @@ -54,7 +54,7 @@ jobs: with: path: ./ffcx repository: FEniCS/ffcx - ref: main + ref: mscroggs/embedded-degree - name: Get FFCx source (specified branch) if: github.event_name == 'workflow_dispatch' uses: actions/checkout@v3 diff --git a/cpp/basix/docs.h b/cpp/basix/docs.h index 0ffe007b8..11ef561f6 100644 --- a/cpp/basix/docs.h +++ b/cpp/basix/docs.h @@ -407,15 +407,14 @@ Create a custom finite element map_type: The type of map to be used to map values from the reference to a cell sobolev_space: The underlying Sobolev space for the element discontinuous: Indicates whether or not this is the discontinuous version of the element - highest_complete_degree: The highest degree n such that a Lagrange (or vector Lagrange) element of degree n is a subspace of this element - highest_degree: The degree of a polynomial in this element's polyset + embedded_subdegree: The highest degree n such that a Lagrange (or vector Lagrange) element of degree n is a subspace of this element + embedded_superdegree: The degree of a polynomial in this element's polyset poly_type: The type of polyset to use for this element Returns: A custom finite element )"; - const std::string create_element__family_cell_degree_lvariant_dvariant_discontinuous_dof_ordering = R"( diff --git a/cpp/basix/finite-element.cpp b/cpp/basix/finite-element.cpp index 1c8da3a65..9cdd2ade4 100644 --- a/cpp/basix/finite-element.cpp +++ b/cpp/basix/finite-element.cpp @@ -421,11 +421,12 @@ FiniteElement basix::create_custom_element( const std::array>, 4>& x, const std::array>, 4>& M, int interpolation_nderivs, maps::type map_type, - sobolev::space sobolev_space, bool discontinuous, - int highest_complete_degree, int highest_degree, polyset::type poly_type) + sobolev::space sobolev_space, bool discontinuous, int embedded_subdegree, + int embedded_superdegree, polyset::type poly_type) { // Check that inputs are valid - const std::size_t psize = polyset::dim(cell_type, poly_type, highest_degree); + const std::size_t psize + = polyset::dim(cell_type, poly_type, embedded_superdegree); const std::size_t value_size = std::reduce( value_shape.begin(), value_shape.end(), 1, std::multiplies{}); const std::size_t deriv_count @@ -501,7 +502,7 @@ FiniteElement basix::create_custom_element( auto [dualmatrix, dualshape] = compute_dual_matrix(cell_type, poly_type, wcoeffs_ortho, x, M, - highest_degree, interpolation_nderivs); + embedded_superdegree, interpolation_nderivs); if (math::is_singular(mdspan_t(dualmatrix.data(), dualshape))) { throw std::runtime_error( @@ -509,9 +510,9 @@ FiniteElement basix::create_custom_element( } return basix::FiniteElement( - element::family::custom, cell_type, poly_type, highest_degree, + element::family::custom, cell_type, poly_type, embedded_superdegree, value_shape, wcoeffs_ortho, x, M, interpolation_nderivs, map_type, - sobolev_space, discontinuous, highest_complete_degree, highest_degree, + sobolev_space, discontinuous, embedded_subdegree, embedded_superdegree, element::lagrange_variant::unset, element::dpc_variant::unset); } //----------------------------------------------------------------------------- @@ -539,9 +540,9 @@ FiniteElement::FiniteElement( const std::array>, 4>& x, const std::array>, 4>& M, int interpolation_nderivs, maps::type map_type, - sobolev::space sobolev_space, bool discontinuous, - int highest_complete_degree, int highest_degree, - element::lagrange_variant lvariant, element::dpc_variant dvariant, + sobolev::space sobolev_space, bool discontinuous, int embedded_subdegree, + int embedded_superdegree, element::lagrange_variant lvariant, + element::dpc_variant dvariant, std::vector>, std::vector>> tensor_factors, std::vector dof_ordering) @@ -550,11 +551,11 @@ FiniteElement::FiniteElement( _cell_subentity_types(cell::subentity_types(cell_type)), _family(family), _lagrange_variant(lvariant), _dpc_variant(dvariant), _degree(degree), _interpolation_nderivs(interpolation_nderivs), - _highest_degree(highest_degree), - _highest_complete_degree(highest_complete_degree), - _value_shape(value_shape), _map_type(map_type), - _sobolev_space(sobolev_space), _discontinuous(discontinuous), - _tensor_factors(tensor_factors), _dof_ordering(dof_ordering) + _embedded_superdegree(embedded_superdegree), + _embedded_subdegree(embedded_subdegree), _value_shape(value_shape), + _map_type(map_type), _sobolev_space(sobolev_space), + _discontinuous(discontinuous), _tensor_factors(tensor_factors), + _dof_ordering(dof_ordering) { // Check that discontinuous elements only have DOFs on interior if (discontinuous) @@ -577,8 +578,9 @@ FiniteElement::FiniteElement( wcoeffs_b.begin()); _wcoeffs = {wcoeffs_b, {wcoeffs.extent(0), wcoeffs.extent(1)}}; - _dual_matrix = compute_dual_matrix(cell_type, poly_type, wcoeffs, x, M, - highest_degree, interpolation_nderivs); + _dual_matrix + = compute_dual_matrix(cell_type, poly_type, wcoeffs, x, M, + embedded_superdegree, interpolation_nderivs); // Copy x for (std::size_t i = 0; i < x.size(); ++i) @@ -647,7 +649,7 @@ FiniteElement::FiniteElement( _entity_transformations = doftransforms::compute_entity_transformations( cell_type, x, M, mdspan_t(_coeffs.first.data(), _coeffs.second), - highest_degree, value_size, map_type, poly_type); + embedded_superdegree, value_size, map_type, poly_type); const std::size_t nderivs = polyset::nderivs(cell_type, interpolation_nderivs); @@ -978,9 +980,9 @@ bool FiniteElement::operator==(const FiniteElement& e) const and map_type() == e.map_type() and sobolev_space() == e.sobolev_space() and value_shape() == e.value_shape() - and highest_degree() == e.highest_degree() - and highest_complete_degree() == e.highest_complete_degree() - and coeff_equal and entity_dofs() == e.entity_dofs(); + and embedded_superdegree() == e.embedded_superdegree() + and embedded_subdegree() == e.embedded_subdegree() and coeff_equal + and entity_dofs() == e.entity_dofs(); } else { @@ -1026,12 +1028,13 @@ void FiniteElement::tabulate(int nd, impl::mdspan_t x, } const std::size_t psize - = polyset::dim(_cell_type, _poly_type, _highest_degree); + = polyset::dim(_cell_type, _poly_type, _embedded_superdegree); const std::array bsize = {(std::size_t)polyset::nderivs(_cell_type, nd), psize, x.extent(0)}; std::vector basis_b(bsize[0] * bsize[1] * bsize[2]); mdspan_t basis(basis_b.data(), bsize); - polyset::tabulate(basis, _cell_type, _poly_type, _highest_degree, nd, x); + polyset::tabulate(basis, _cell_type, _poly_type, _embedded_superdegree, nd, + x); const int vs = std::accumulate(_value_shape.begin(), _value_shape.end(), 1, std::multiplies{}); diff --git a/cpp/basix/finite-element.h b/cpp/basix/finite-element.h index c03e1a379..32ffa753f 100644 --- a/cpp/basix/finite-element.h +++ b/cpp/basix/finite-element.h @@ -304,10 +304,10 @@ class FiniteElement /// element /// @param[in] discontinuous Indicates whether or not this is the /// discontinuous version of the element - /// @param[in] highest_complete_degree The highest degree n such that + /// @param[in] embedded_subdegree The highest degree n such that /// a Lagrange (or vector Lagrange) element of degree n is a subspace /// of this element - /// @param[in] highest_degree The highest degree n such that at least + /// @param[in] embedded_superdegree The highest degree n such that at least /// one polynomial of degree n is included in this element's /// polymonial set /// @param[in] lvariant The Lagrange variant of the element @@ -323,9 +323,9 @@ class FiniteElement const std::array>, 4>& x, const std::array>, 4>& M, int interpolation_nderivs, maps::type map_type, - sobolev::space sobolev_space, bool discontinuous, - int highest_complete_degree, int highest_degree, - element::lagrange_variant lvariant, element::dpc_variant dvariant, + sobolev::space sobolev_space, bool discontinuous, int embedded_subdegree, + int embedded_superdegree, element::lagrange_variant lvariant, + element::dpc_variant dvariant, std::vector, std::vector>> tensor_factors = {}, @@ -498,12 +498,12 @@ class FiniteElement /// element is contained in a Lagrange (or vector Lagrange) element of /// degree `n`. /// @return Polynomial degree - int highest_degree() const { return _highest_degree; } + int embedded_superdegree() const { return _embedded_superdegree; } /// Highest degree `n` such that a Lagrange (or vector Lagrange) /// element of degree n is a subspace of this element. /// @return Polynomial degree - int highest_complete_degree() const { return _highest_complete_degree; } + int embedded_subdegree() const { return _embedded_subdegree; } /// The element value tensor shape, e.g. returning {} for scalars, {3} /// for vectors in 3D, {2, 2} for a rank-2 tensor in 2D. @@ -1182,10 +1182,10 @@ class FiniteElement int _interpolation_nderivs; // Highest degree polynomial in element's polyset - int _highest_degree; + int _embedded_superdegree; // Highest degree space that is a subspace of element's polyset - int _highest_complete_degree; + int _embedded_subdegree; // Value shape std::vector _value_shape; @@ -1314,10 +1314,10 @@ class FiniteElement /// @param[in] sobolev_space The underlying Sobolev space for the element /// @param[in] discontinuous Indicates whether or not this is the /// discontinuous version of the element -/// @param[in] highest_complete_degree The highest degree n such that a +/// @param[in] embedded_subdegree The highest degree n such that a /// Lagrange (or vector Lagrange) element of degree n is a subspace of this /// element -/// @param[in] highest_degree The degree of a polynomial in this element's +/// @param[in] embedded_superdegree The degree of a polynomial in this element's /// polyset /// @param[in] poly_type The type of polyset to use for this element /// @return A custom finite element @@ -1328,8 +1328,8 @@ FiniteElement create_custom_element( const std::array>, 4>& x, const std::array>, 4>& M, int interpolation_nderivs, maps::type map_type, - sobolev::space sobolev_space, bool discontinuous, - int highest_complete_degree, int highest_degree, polyset::type poly_type); + sobolev::space sobolev_space, bool discontinuous, int embedded_subdegree, + int embedded_superdegree, polyset::type poly_type); /// Create an element using a given Lagrange variant and a given DPC variant /// @param[in] family The element family diff --git a/cpp/docs.template b/cpp/docs.template index b110f118f..2856aec46 100644 --- a/cpp/docs.template +++ b/cpp/docs.template @@ -228,24 +228,24 @@ Returns: )"; {{DOCTYPE}} create_custom_element = R"( -{{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, highest_complete_degree, highest_degree, poly_type) > doc}} +{{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, embedded_subdegree, embedded_superdegree, poly_type) > doc}} Args: - {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, highest_complete_degree, highest_degree, poly_type) > param > cell_type}} - {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, highest_complete_degree, highest_degree, poly_type) > param > value_shape}} - {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, highest_complete_degree, highest_degree, poly_type) > param > wcoeffs}} - {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, highest_complete_degree, highest_degree, poly_type) > param > x}} - {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, highest_complete_degree, highest_degree, poly_type) > param > M}} - {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, highest_complete_degree, highest_degree, poly_type) > param > interpolation_nderivs}} - {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, highest_complete_degree, highest_degree, poly_type) > param > map_type}} - {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, highest_complete_degree, highest_degree, poly_type) > param > sobolev_space}} - {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, highest_complete_degree, highest_degree, poly_type) > param > discontinuous}} - {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, highest_complete_degree, highest_degree, poly_type) > param > highest_complete_degree}} - {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, highest_complete_degree, highest_degree, poly_type) > param > highest_degree}} - {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, highest_complete_degree, highest_degree, poly_type) > param > poly_type}} + {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, embedded_subdegree, embedded_superdegree, poly_type) > param > cell_type}} + {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, embedded_subdegree, embedded_superdegree, poly_type) > param > value_shape}} + {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, embedded_subdegree, embedded_superdegree, poly_type) > param > wcoeffs}} + {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, embedded_subdegree, embedded_superdegree, poly_type) > param > x}} + {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, embedded_subdegree, embedded_superdegree, poly_type) > param > M}} + {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, embedded_subdegree, embedded_superdegree, poly_type) > param > interpolation_nderivs}} + {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, embedded_subdegree, embedded_superdegree, poly_type) > param > map_type}} + {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, embedded_subdegree, embedded_superdegree, poly_type) > param > sobolev_space}} + {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, embedded_subdegree, embedded_superdegree, poly_type) > param > discontinuous}} + {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, embedded_subdegree, embedded_superdegree, poly_type) > param > embedded_subdegree}} + {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, embedded_subdegree, embedded_superdegree, poly_type) > param > embedded_superdegree}} + {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, embedded_subdegree, embedded_superdegree, poly_type) > param > poly_type}} Returns: - {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, highest_complete_degree, highest_degree, poly_type) > return}} + {{finite-element.h > create_custom_element(cell_type, value_shape, wcoeffs, x, M, interpolation_nderivs, map_type, sobolev_space, discontinuous, embedded_subdegree, embedded_superdegree, poly_type) > return}} )"; diff --git a/python/basix/_basixcpp.pyi b/python/basix/_basixcpp.pyi index 88136c7bb..be3b5d85a 100644 --- a/python/basix/_basixcpp.pyi +++ b/python/basix/_basixcpp.pyi @@ -137,9 +137,9 @@ class FiniteElement: @property def has_tensor_product_factorisation(self) -> bool: ... @property - def highest_complete_degree(self) -> int: ... + def embedded_subdegree(self) -> int: ... @property - def highest_degree(self) -> int: ... + def embedded_superdegree(self) -> int: ... @property def interpolation_is_identity(self) -> bool: ... @property diff --git a/python/basix/ufl.py b/python/basix/ufl.py index 2e59be307..7a708ccbb 100644 --- a/python/basix/ufl.py +++ b/python/basix/ufl.py @@ -138,7 +138,7 @@ def pullback(self) -> _AbstractPullback: """Return the pullback for this element.""" return self._pullback - @property + @_abstractproperty def embedded_superdegree(self) -> int: """Return the degree of the minimum degree Lagrange space that spans this element. @@ -151,9 +151,8 @@ def embedded_superdegree(self) -> int: space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 Lagrange space includes the degree 2 polynomial xy. """ - return self.highest_degree - @property + @_abstractproperty def embedded_subdegree(self) -> int: """Return the degree of the maximum degree Lagrange space that is spanned by this element. @@ -166,7 +165,6 @@ def embedded_subdegree(self) -> int: space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 Lagrange space includes the degree 2 polynomial xy. """ - return self.highest_complete_degree @property def cell(self) -> _ufl.Cell: @@ -285,14 +283,6 @@ def discontinuous(self) -> bool: def map_type(self) -> _basix.MapType: """The Basix map type.""" - @_abstractproperty - def highest_complete_degree(self) -> int: - """The highest complete degree of the element.""" - - @_abstractproperty - def highest_degree(self) -> int: - """The highest degree of the element.""" - @_abstractproperty def polyset_type(self) -> _basix.PolysetType: """The polyset type of the element.""" @@ -538,14 +528,34 @@ def map_type(self) -> _basix.MapType: return self.element.map_type @property - def highest_complete_degree(self) -> int: - """The highest complete degree of the element.""" - return self.element.highest_complete_degree + def embedded_superdegree(self) -> int: + """Return the degree of the minimum degree Lagrange space that spans this element. + + This returns the degree of the lowest degree Lagrange space such that the polynomial + space of the Lagrange space is a superspace of this element's polynomial space. If this + element contains basis functions that are not in any Lagrange space, this function should + return None. + + Note that on a simplex cells, the polynomial space of Lagrange space is a complete polynomial + space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 + Lagrange space includes the degree 2 polynomial xy. + """ + return self.element.embedded_superdegree @property - def highest_degree(self) -> int: - """The highest degree of the element.""" - return self.element.highest_degree + def embedded_subdegree(self) -> int: + """Return the degree of the maximum degree Lagrange space that is spanned by this element. + + This returns the degree of the highest degree Lagrange space such that the polynomial + space of the Lagrange space is a subspace of this element's polynomial space. If this + element's polynomial space does not include the constant function, this function should + return -1. + + Note that on a simplex cells, the polynomial space of Lagrange space is a complete polynomial + space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 + Lagrange space includes the degree 2 polynomial xy. + """ + return self.element.embedded_subdegree @property def polyset_type(self) -> _basix.PolysetType: @@ -752,14 +762,34 @@ def map_type(self) -> _basix.MapType: raise NotImplementedError() @property - def highest_complete_degree(self) -> int: - """The highest complete degree of the element.""" - return self.element.highest_complete_degree + def embedded_superdegree(self) -> int: + """Return the degree of the minimum degree Lagrange space that spans this element. + + This returns the degree of the lowest degree Lagrange space such that the polynomial + space of the Lagrange space is a superspace of this element's polynomial space. If this + element contains basis functions that are not in any Lagrange space, this function should + return None. + + Note that on a simplex cells, the polynomial space of Lagrange space is a complete polynomial + space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 + Lagrange space includes the degree 2 polynomial xy. + """ + return self.element.embedded_superdegree @property - def highest_degree(self) -> int: - """The highest degree of the element.""" - return self.element.highest_degree + def embedded_subdegree(self) -> int: + """Return the degree of the maximum degree Lagrange space that is spanned by this element. + + This returns the degree of the highest degree Lagrange space such that the polynomial + space of the Lagrange space is a subspace of this element's polynomial space. If this + element's polynomial space does not include the constant function, this function should + return -1. + + Note that on a simplex cells, the polynomial space of Lagrange space is a complete polynomial + space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 + Lagrange space includes the degree 2 polynomial xy. + """ + return self.element.embedded_subdegree class _MixedElement(_ElementBase): @@ -858,13 +888,33 @@ def get_component_element(self, flat_component: int) -> _typing.Tuple[_ElementBa return e, irange[component_element_index] + offset, stride @property - def highest_degree(self) -> int: - """The highest degree of the element.""" - return max(e.highest_degree for e in self._sub_elements) + def embedded_superdegree(self) -> int: + """Return the degree of the minimum degree Lagrange space that spans this element. + + This returns the degree of the lowest degree Lagrange space such that the polynomial + space of the Lagrange space is a superspace of this element's polynomial space. If this + element contains basis functions that are not in any Lagrange space, this function should + return None. + + Note that on a simplex cells, the polynomial space of Lagrange space is a complete polynomial + space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 + Lagrange space includes the degree 2 polynomial xy. + """ + return max(e.embedded_superdegree for e in self._sub_elements) @property - def highest_complete_degree(self) -> int: - """The highest degree of the element.""" + def embedded_subdegree(self) -> int: + """Return the degree of the maximum degree Lagrange space that is spanned by this element. + + This returns the degree of the highest degree Lagrange space such that the polynomial + space of the Lagrange space is a subspace of this element's polynomial space. If this + element's polynomial space does not include the constant function, this function should + return -1. + + Note that on a simplex cells, the polynomial space of Lagrange space is a complete polynomial + space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 + Lagrange space includes the degree 2 polynomial xy. + """ raise NotImplementedError() @property @@ -1224,14 +1274,34 @@ def map_type(self) -> _basix.MapType: return self.sub_element.map_type @property - def highest_complete_degree(self) -> int: - """The highest complete degree of the element.""" - return self.sub_element.highest_complete_degree + def embedded_superdegree(self) -> int: + """Return the degree of the minimum degree Lagrange space that spans this element. + + This returns the degree of the lowest degree Lagrange space such that the polynomial + space of the Lagrange space is a superspace of this element's polynomial space. If this + element contains basis functions that are not in any Lagrange space, this function should + return None. + + Note that on a simplex cells, the polynomial space of Lagrange space is a complete polynomial + space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 + Lagrange space includes the degree 2 polynomial xy. + """ + return self.sub_element.embedded_superdegree @property - def highest_degree(self) -> int: - """The highest degree of the element.""" - return self.sub_element.highest_degree + def embedded_subdegree(self) -> int: + """Return the degree of the maximum degree Lagrange space that is spanned by this element. + + This returns the degree of the highest degree Lagrange space such that the polynomial + space of the Lagrange space is a subspace of this element's polynomial space. If this + element's polynomial space does not include the constant function, this function should + return -1. + + Note that on a simplex cells, the polynomial space of Lagrange space is a complete polynomial + space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 + Lagrange space includes the degree 2 polynomial xy. + """ + return self.sub_element.embedded_subdegree @property def polyset_type(self) -> _basix.PolysetType: @@ -1455,13 +1525,33 @@ def has_custom_quadrature(self) -> bool: return True @property - def highest_complete_degree(self) -> int: - """The highest complete degree of the element.""" + def embedded_superdegree(self) -> int: + """Return the degree of the minimum degree Lagrange space that spans this element. + + This returns the degree of the lowest degree Lagrange space such that the polynomial + space of the Lagrange space is a superspace of this element's polynomial space. If this + element contains basis functions that are not in any Lagrange space, this function should + return None. + + Note that on a simplex cells, the polynomial space of Lagrange space is a complete polynomial + space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 + Lagrange space includes the degree 2 polynomial xy. + """ raise NotImplementedError() @property - def highest_degree(self) -> int: - """The highest degree of the element.""" + def embedded_subdegree(self) -> int: + """Return the degree of the maximum degree Lagrange space that is spanned by this element. + + This returns the degree of the highest degree Lagrange space such that the polynomial + space of the Lagrange space is a subspace of this element's polynomial space. If this + element's polynomial space does not include the constant function, this function should + return -1. + + Note that on a simplex cells, the polynomial space of Lagrange space is a complete polynomial + space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 + Lagrange space includes the degree 2 polynomial xy. + """ raise NotImplementedError() @@ -1533,13 +1623,33 @@ def dim(self) -> int: return 0 @property - def highest_degree(self) -> int: - """The highest degree of the element.""" + def embedded_superdegree(self) -> int: + """Return the degree of the minimum degree Lagrange space that spans this element. + + This returns the degree of the lowest degree Lagrange space such that the polynomial + space of the Lagrange space is a superspace of this element's polynomial space. If this + element contains basis functions that are not in any Lagrange space, this function should + return None. + + Note that on a simplex cells, the polynomial space of Lagrange space is a complete polynomial + space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 + Lagrange space includes the degree 2 polynomial xy. + """ return 0 @property - def highest_complete_degree(self) -> int: - """The highest complete degree of the element.""" + def embedded_subdegree(self) -> int: + """Return the degree of the maximum degree Lagrange space that is spanned by this element. + + This returns the degree of the highest degree Lagrange space such that the polynomial + space of the Lagrange space is a subspace of this element's polynomial space. If this + element's polynomial space does not include the constant function, this function should + return -1. + + Note that on a simplex cells, the polynomial space of Lagrange space is a complete polynomial + space, but on other cells this is not true. For example, on quadrilateral cells, the degree 1 + Lagrange space includes the degree 2 polynomial xy. + """ return 0 @property @@ -1648,7 +1758,7 @@ def _compute_signature(element: _basix.finite_element.FiniteElement) -> str: """ assert element.family == _basix.ElementFamily.custom signature = (f"{element.cell_type.__name__}, {element.value_shape}, {element.map_type.__name__}, " - f"{element.discontinuous}, {element.highest_complete_degree}, {element.highest_degree}, ") + f"{element.discontinuous}, {element.embedded_subdegree}, {element.embedded_superdegree}, ") data = ",".join([f"{i}" for row in element.wcoeffs for i in row]) data += "__" for entity in element.x: @@ -1770,8 +1880,8 @@ def enriched_element(elements: _typing.List[_ElementBase], if e.map_type != map_type: raise ValueError("Enriched elements on different map types not supported.") - hcd = min(e.highest_complete_degree for e in elements) - hd = max(e.highest_degree for e in elements) + hcd = min(e.embedded_subdegree for e in elements) + hd = max(e.embedded_superdegree for e in elements) ss = _basix.sobolev_spaces.intersection([e.basix_sobolev_space for e in elements]) discontinuous = True for e in elements: @@ -1810,7 +1920,7 @@ def enriched_element(elements: _typing.List[_ElementBase], row = 0 for e in elements: wcoeffs[row: row + e.dim, :] = _basix.polynomials.reshape_coefficients( - _basix.PolynomialType.legendre, ct, e._wcoeffs, vsize, e.highest_degree, hd) + _basix.PolynomialType.legendre, ct, e._wcoeffs, vsize, e.embedded_superdegree, hd) row += e.dim return custom_element(ct, list(vshape), wcoeffs, x, M, nderivs, @@ -1821,7 +1931,7 @@ def custom_element(cell_type: _basix.CellType, value_shape: _typing.Union[_typin wcoeffs: _npt.NDArray[_np.float64], x: _typing.List[_typing.List[_npt.NDArray[_np.float64]]], M: _typing.List[_typing.List[_npt.NDArray[_np.float64]]], interpolation_nderivs: int, map_type: _basix.MapType, sobolev_space: _basix.SobolevSpace, discontinuous: bool, - highest_complete_degree: int, highest_degree: int, + embedded_subdegree: int, embedded_superdegree: int, polyset_type: _basix.PolysetType = _basix.PolysetType.standard, gdim: _typing.Optional[int] = None) -> _ElementBase: """Create a UFL compatible custom Basix element. @@ -1844,10 +1954,10 @@ def custom_element(cell_type: _basix.CellType, value_shape: _typing.Union[_typin sobolev_space: Underlying Sobolev space for the element. discontinuous: Indicates whether or not this is the discontinuous version of the element. - highest_complete_degree: The highest degree ``n`` such that a + embedded_subdegree: The highest degree ``n`` such that a Lagrange (or vector Lagrange) element of degree ``n`` is a subspace of this element. - highest_degree: The degree of a polynomial in this element's + embedded_superdegree: The highest degree of a polynomial in this element's polyset. polyset_type: Polyset type for the element. gdim: Geometric dimension. If not set the geometric dimension is @@ -1867,8 +1977,8 @@ def custom_element(cell_type: _basix.CellType, value_shape: _typing.Union[_typin map_type, sobolev_space, discontinuous, - highest_complete_degree, - highest_degree, + embedded_subdegree, + embedded_superdegree, polyset_type ) return _BasixElement(e, gdim=gdim) diff --git a/python/wrapper.cpp b/python/wrapper.cpp index 0732ffd4c..9d9dcd9f3 100644 --- a/python/wrapper.cpp +++ b/python/wrapper.cpp @@ -357,9 +357,10 @@ NB_MODULE(_basixcpp, m) basix::docstring::FiniteElement__get_tensor_product_representation .c_str()) .def_prop_ro("degree", &FiniteElement::degree) - .def_prop_ro("highest_degree", &FiniteElement::highest_degree) - .def_prop_ro("highest_complete_degree", - &FiniteElement::highest_complete_degree) + .def_prop_ro("embedded_superdegree", + &FiniteElement::embedded_superdegree) + .def_prop_ro("embedded_subdegree", + &FiniteElement::embedded_subdegree) .def_prop_ro("cell_type", &FiniteElement::cell_type) .def_prop_ro("polyset_type", &FiniteElement::polyset_type) .def_prop_ro("dim", &FiniteElement::dim) @@ -556,7 +557,7 @@ NB_MODULE(_basixcpp, m) M, int interpolation_nderivs, maps::type map_type, sobolev::space sobolev_space, bool discontinuous, - int highest_complete_degree, int highest_degree, + int embedded_subdegree, int embedded_superdegree, polyset::type poly_type) -> FiniteElement { if (x.size() != 4) @@ -590,7 +591,7 @@ NB_MODULE(_basixcpp, m) mdspan_t(wcoeffs.data(), wcoeffs.shape(0), wcoeffs.shape(1)), _x, _M, interpolation_nderivs, map_type, sobolev_space, - discontinuous, highest_complete_degree, highest_degree, poly_type); + discontinuous, embedded_subdegree, embedded_superdegree, poly_type); }, basix::docstring::create_custom_element.c_str()); diff --git a/test/test_interpolation_between_elements.py b/test/test_interpolation_between_elements.py index aa634bebe..045868907 100644 --- a/test/test_interpolation_between_elements.py +++ b/test/test_interpolation_between_elements.py @@ -136,7 +136,7 @@ def test_degree_bounds(cell_type, degree, element_type, element_args): tab = element.tabulate(0, points)[0] # Test that this element's basis functions are contained in Lagrange - # space with degree element.highest_degree + # space with degree element.embedded_superdegree coeffs = np.random.rand(element.dim) values = np.array([tab[:, :, i] @ coeffs for i in range(element.value_size)]) @@ -145,9 +145,9 @@ def test_degree_bounds(cell_type, degree, element_type, element_args): elif element.polyset_type == basix.PolysetType.macroedge: p_family = basix.ElementFamily.iso - if element.highest_degree >= 0: + if element.embedded_superdegree >= 0: # The element being tested should be a subset of this Lagrange space - lagrange = basix.create_element(p_family, cell_type, element.highest_degree, + lagrange = basix.create_element(p_family, cell_type, element.embedded_superdegree, basix.LagrangeVariant.equispaced, discontinuous=True) lagrange_coeffs = basix.compute_interpolation_operator(element, lagrange) @ coeffs lagrange_tab = lagrange.tabulate(0, points)[0] @@ -156,10 +156,10 @@ def test_degree_bounds(cell_type, degree, element_type, element_args): assert np.allclose(values, lagrange_values) - if element.highest_degree >= 1: + if element.embedded_superdegree >= 1: # The element being tested should be NOT a subset of this # Lagrange space - lagrange = basix.create_element(p_family, cell_type, element.highest_degree - 1, + lagrange = basix.create_element(p_family, cell_type, element.embedded_superdegree - 1, basix.LagrangeVariant.equispaced, discontinuous=True) lagrange_coeffs = basix.compute_interpolation_operator(element, lagrange) @ coeffs lagrange_tab = lagrange.tabulate(0, points)[0] @@ -169,12 +169,12 @@ def test_degree_bounds(cell_type, degree, element_type, element_args): assert not np.allclose(values, lagrange_values) # Test that the basis functions of Lagrange space with degree - # element.highest_complete_degree are contained in this space + # element.embedded_subdegree are contained in this space - if element.highest_complete_degree >= 0: + if element.embedded_subdegree >= 0: # This Lagrange space should be a subset to the element being # tested - lagrange = basix.create_element(p_family, cell_type, element.highest_complete_degree, + lagrange = basix.create_element(p_family, cell_type, element.embedded_subdegree, basix.LagrangeVariant.equispaced, discontinuous=True) lagrange_coeffs = np.random.rand(lagrange.dim * element.value_size) lagrange_tab = lagrange.tabulate(0, points)[0] @@ -186,15 +186,15 @@ def test_degree_bounds(cell_type, degree, element_type, element_args): assert np.allclose(values, lagrange_values) if element.polyset_type == basix.PolysetType.macroedge: - if cell_type == basix.CellType.triangle and element.highest_complete_degree + 1 > 2: + if cell_type == basix.CellType.triangle and element.embedded_subdegree + 1 > 2: pytest.xfail("Cannot run test with macro polyset on a triangle with degree > 2") - if cell_type == basix.CellType.tetrahedron and element.highest_complete_degree + 1 > 1: + if cell_type == basix.CellType.tetrahedron and element.embedded_subdegree + 1 > 1: pytest.xfail("Cannot run test with macro polyset on a tetrahedron with degree > 1") - if element.highest_complete_degree >= -1: + if element.embedded_subdegree >= -1: # This Lagrange space should NOT be a subset to the element # being tested - lagrange = basix.create_element(p_family, cell_type, element.highest_complete_degree + 1, + lagrange = basix.create_element(p_family, cell_type, element.embedded_subdegree + 1, basix.LagrangeVariant.equispaced, discontinuous=True) lagrange_coeffs = np.random.rand(lagrange.dim * element.value_size) lagrange_tab = lagrange.tabulate(0, points)[0]