From 5406935c1f7831a5995becba56292dc01f4788db Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 29 Nov 2024 21:17:46 +0100 Subject: [PATCH 01/20] Start to wrap FiniteElement --- python/dolfinx/fem/__init__.py | 26 ++++++----- python/dolfinx/fem/element.py | 84 +++++++++++++++++++++++++++++++++- python/dolfinx/fem/function.py | 37 +++------------ 3 files changed, 103 insertions(+), 44 deletions(-) diff --git a/python/dolfinx/fem/__init__.py b/python/dolfinx/fem/__init__.py index 25b4f2f8fed..47d61cc87b4 100644 --- a/python/dolfinx/fem/__init__.py +++ b/python/dolfinx/fem/__init__.py @@ -32,7 +32,7 @@ locate_dofs_topological, ) from dolfinx.fem.dofmap import DofMap -from dolfinx.fem.element import CoordinateElement, coordinate_element +from dolfinx.fem.element import CoordinateElement, FiniteElement, coordinate_element, finite_element from dolfinx.fem.forms import ( Form, compile_form, @@ -163,16 +163,6 @@ def compute_integration_domains( __all__ = [ - "Constant", - "CoordinateElement", - "DirichletBC", - "DofMap", - "ElementMetaData", - "Expression", - "Form", - "Function", - "FunctionSpace", - "IntegralType", "apply_lifting", "assemble_matrix", "assemble_scalar", @@ -180,18 +170,30 @@ def compute_integration_domains( "bcs_by_block", "compile_form", "compute_integration_domains", + "Constant", "coordinate_element", + "CoordinateElement", "create_form", "create_interpolation_data", "create_matrix", "create_sparsity_pattern", "create_vector", "dirichletbc", + "DirichletBC", "discrete_gradient", + "DofMap", + "ElementMetaData", + "Expression", "extract_function_spaces", - "form", + "finite_element", + "FiniteElement", "form_cpp_class", + "form", + "Form", + "Function", "functionspace", + "FunctionSpace", + "IntegralType", "locate_dofs_geometrical", "locate_dofs_topological", "set_bc", diff --git a/python/dolfinx/fem/element.py b/python/dolfinx/fem/element.py index 7b2ae95d807..0fc78659780 100644 --- a/python/dolfinx/fem/element.py +++ b/python/dolfinx/fem/element.py @@ -1,4 +1,4 @@ -# Copyright (C) 2024 Garth N. Wells +# Copyright (C) 2024 Garth N. Wells and Paul T. Kühner # # This file is part of DOLFINx (https://www.fenicsproject.org) # @@ -12,6 +12,7 @@ import numpy.typing as npt import basix +import ufl from dolfinx import cpp as _cpp @@ -160,3 +161,84 @@ def _(e: basix.finite_element.FiniteElement): return CoordinateElement(_cpp.fem.CoordinateElement_float32(e._e)) except TypeError: return CoordinateElement(_cpp.fem.CoordinateElement_float64(e._e)) + + +class FiniteElement: + _cpp_object: typing.Union[_cpp.fem.FiniteElement_float32, _cpp.fem.FiniteElement_float64] + + def __init__(self, cpp_object): + self._cpp_object = cpp_object + + def __eq__(self, other): + return self._cpp_object == other._cpp_object + + @property + def dtype(self): + return self._cpp_object.dtype + + @property + def basix_element(self): + return self._cpp_object.basix_element + + @property + def num_sub_elements(self): + return self._cpp_object.num_sub_elements + + @property + def value_shape(self): + return self._cpp_object.value_shape + + @property + def interpolation_points(self): + return self._cpp_object.interpolation_points + + @property + def interpolation_ident(self): + return self._cpp_object.interpolation_ident + + @property + def space_dimension(self): + return self._cpp_object.space_dimension + + @property + def needs_dof_transformations(self): + return self._cpp_object.needs_dof_transformations + + @property + def signature(self): + return self._cpp_object.signature + + def T_apply(self, x, cell_permutations, dim): + self._cpp_object.T_apply(x, cell_permutations, dim) + + def Tt_apply(self, x, cell_permutations, dim): + self._cpp_object.Tt_apply(x, cell_permutations, dim) + + def Tt_inv_apply(self, x, cell_permutations, dim): + self._cpp_object.Tt_apply(x, cell_permutations, dim) + + +def finite_element( + cell_type: _cpp.mesh.CellType, + ufl_e: ufl.FiniteElementBase, + dtype: np.dtype, +) -> FiniteElement: + """Create a DOLFINx element from a basix.ufl element.""" + if np.issubdtype(dtype, np.float32): + CppElement = _cpp.fem.FiniteElement_float32 + elif np.issubdtype(dtype, np.float64): + CppElement = _cpp.fem.FiniteElement_float64 + else: + raise ValueError(f"Unsupported dtype: {dtype}") + + if ufl_e.is_mixed: + elements = [finite_element(cell_type, e, dtype) for e in ufl_e.sub_elements] + return CppElement(elements) + elif ufl_e.is_quadrature: + return CppElement( + cell_type, ufl_e.custom_quadrature()[0], ufl_e.reference_value_shape, ufl_e.is_symmetric + ) + else: + basix_e = ufl_e.basix_element._e + value_shape = ufl_e.reference_value_shape if ufl_e.block_size > 1 else None + return FiniteElement(CppElement(basix_e, value_shape, ufl_e.is_symmetric)) diff --git a/python/dolfinx/fem/function.py b/python/dolfinx/fem/function.py index 01123fc61bf..9465feded7a 100644 --- a/python/dolfinx/fem/function.py +++ b/python/dolfinx/fem/function.py @@ -18,6 +18,7 @@ from dolfinx import cpp as _cpp from dolfinx import default_scalar_type, jit, la from dolfinx.fem import dofmap +from dolfinx.fem.element import finite_element from dolfinx.geometry import PointOwnershipData if typing.TYPE_CHECKING: @@ -560,32 +561,6 @@ class ElementMetaData(typing.NamedTuple): symmetry: typing.Optional[bool] = None -def _create_dolfinx_element( - cell_type: _cpp.mesh.CellType, - ufl_e: ufl.FiniteElementBase, - dtype: np.dtype, -) -> typing.Union[_cpp.fem.FiniteElement_float32, _cpp.fem.FiniteElement_float64]: - """Create a DOLFINx element from a basix.ufl element.""" - if np.issubdtype(dtype, np.float32): - CppElement = _cpp.fem.FiniteElement_float32 - elif np.issubdtype(dtype, np.float64): - CppElement = _cpp.fem.FiniteElement_float64 - else: - raise ValueError(f"Unsupported dtype: {dtype}") - - if ufl_e.is_mixed: - elements = [_create_dolfinx_element(cell_type, e, dtype) for e in ufl_e.sub_elements] - return CppElement(elements) - elif ufl_e.is_quadrature: - return CppElement( - cell_type, ufl_e.custom_quadrature()[0], ufl_e.reference_value_shape, ufl_e.is_symmetric - ) - else: - basix_e = ufl_e.basix_element._e - value_shape = ufl_e.reference_value_shape if ufl_e.block_size > 1 else None - return CppElement(basix_e, value_shape, ufl_e.is_symmetric) - - def functionspace( mesh: Mesh, element: typing.Union[ufl.FiniteElementBase, ElementMetaData, tuple[str, int, tuple, bool]], @@ -614,18 +589,18 @@ def functionspace( raise ValueError("Non-matching UFL cell and mesh cell shapes.") # Create DOLFINx objects - cpp_element = _create_dolfinx_element(mesh.topology.cell_type, ufl_e, dtype) - cpp_dofmap = _cpp.fem.create_dofmap(mesh.comm, mesh.topology._cpp_object, cpp_element) + element = finite_element(mesh.topology.cell_type, ufl_e, dtype) + cpp_dofmap = _cpp.fem.create_dofmap(mesh.comm, mesh.topology._cpp_object, element._cpp_object) assert np.issubdtype( - mesh.geometry.x.dtype, cpp_element.dtype + mesh.geometry.x.dtype, element.dtype ), "Mesh and element dtype are not compatible." # Initialize the cpp.FunctionSpace try: - cppV = _cpp.fem.FunctionSpace_float64(mesh._cpp_object, cpp_element, cpp_dofmap) + cppV = _cpp.fem.FunctionSpace_float64(mesh._cpp_object, element._cpp_object, cpp_dofmap) except TypeError: - cppV = _cpp.fem.FunctionSpace_float32(mesh._cpp_object, cpp_element, cpp_dofmap) + cppV = _cpp.fem.FunctionSpace_float32(mesh._cpp_object, element._cpp_object, cpp_dofmap) return FunctionSpace(mesh, ufl_e, cppV) From 00fe7405dac3eac51f406557a8e75b18ccfe321a Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 29 Nov 2024 21:20:44 +0100 Subject: [PATCH 02/20] Ruff --- python/dolfinx/fem/__init__.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/python/dolfinx/fem/__init__.py b/python/dolfinx/fem/__init__.py index 47d61cc87b4..89921b842be 100644 --- a/python/dolfinx/fem/__init__.py +++ b/python/dolfinx/fem/__init__.py @@ -163,6 +163,17 @@ def compute_integration_domains( __all__ = [ + "Constant", + "CoordinateElement", + "DirichletBC", + "DofMap", + "ElementMetaData", + "Expression", + "FiniteElement", + "Form", + "Function", + "FunctionSpace", + "IntegralType", "apply_lifting", "assemble_matrix", "assemble_scalar", @@ -170,30 +181,19 @@ def compute_integration_domains( "bcs_by_block", "compile_form", "compute_integration_domains", - "Constant", "coordinate_element", - "CoordinateElement", "create_form", "create_interpolation_data", "create_matrix", "create_sparsity_pattern", "create_vector", "dirichletbc", - "DirichletBC", "discrete_gradient", - "DofMap", - "ElementMetaData", - "Expression", "extract_function_spaces", "finite_element", - "FiniteElement", - "form_cpp_class", "form", - "Form", - "Function", + "form_cpp_class", "functionspace", - "FunctionSpace", - "IntegralType", "locate_dofs_geometrical", "locate_dofs_topological", "set_bc", From d5b32f99d576b722f109f525400eefe6c966d0b2 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 29 Nov 2024 21:30:07 +0100 Subject: [PATCH 03/20] Ufl naming --- python/dolfinx/fem/element.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/python/dolfinx/fem/element.py b/python/dolfinx/fem/element.py index 0fc78659780..4ef721ebb59 100644 --- a/python/dolfinx/fem/element.py +++ b/python/dolfinx/fem/element.py @@ -14,6 +14,7 @@ import basix import ufl from dolfinx import cpp as _cpp +import ufl.finiteelement class CoordinateElement: @@ -220,7 +221,7 @@ def Tt_inv_apply(self, x, cell_permutations, dim): def finite_element( cell_type: _cpp.mesh.CellType, - ufl_e: ufl.FiniteElementBase, + ufl_e: ufl.finiteelement, dtype: np.dtype, ) -> FiniteElement: """Create a DOLFINx element from a basix.ufl element.""" From 3a3a565504d33b38a8d212f3ed494b765c1f45da Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 29 Nov 2024 21:32:15 +0100 Subject: [PATCH 04/20] More wrapping --- python/dolfinx/fem/element.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/python/dolfinx/fem/element.py b/python/dolfinx/fem/element.py index 4ef721ebb59..58191a968eb 100644 --- a/python/dolfinx/fem/element.py +++ b/python/dolfinx/fem/element.py @@ -233,11 +233,16 @@ def finite_element( raise ValueError(f"Unsupported dtype: {dtype}") if ufl_e.is_mixed: - elements = [finite_element(cell_type, e, dtype) for e in ufl_e.sub_elements] - return CppElement(elements) + elements = [finite_element(cell_type, e, dtype)._cpp_object for e in ufl_e.sub_elements] + return FiniteElement(CppElement(elements)) elif ufl_e.is_quadrature: - return CppElement( - cell_type, ufl_e.custom_quadrature()[0], ufl_e.reference_value_shape, ufl_e.is_symmetric + return FiniteElement( + CppElement( + cell_type, + ufl_e.custom_quadrature()[0], + ufl_e.reference_value_shape, + ufl_e.is_symmetric, + ) ) else: basix_e = ufl_e.basix_element._e From 891f9ab4f215fa3157119f40fbad24675d9aeb6b Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 29 Nov 2024 21:33:11 +0100 Subject: [PATCH 05/20] ruff --- python/dolfinx/fem/element.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/dolfinx/fem/element.py b/python/dolfinx/fem/element.py index 58191a968eb..1503d1c7617 100644 --- a/python/dolfinx/fem/element.py +++ b/python/dolfinx/fem/element.py @@ -13,8 +13,8 @@ import basix import ufl -from dolfinx import cpp as _cpp import ufl.finiteelement +from dolfinx import cpp as _cpp class CoordinateElement: From ccdae0ff3405f20565da1c8dddef71408d02bd2e Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 29 Nov 2024 21:58:45 +0100 Subject: [PATCH 06/20] last one? --- python/dolfinx/fem/function.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/python/dolfinx/fem/function.py b/python/dolfinx/fem/function.py index 9465feded7a..e74be1e741c 100644 --- a/python/dolfinx/fem/function.py +++ b/python/dolfinx/fem/function.py @@ -18,7 +18,7 @@ from dolfinx import cpp as _cpp from dolfinx import default_scalar_type, jit, la from dolfinx.fem import dofmap -from dolfinx.fem.element import finite_element +from dolfinx.fem.element import FiniteElement, finite_element from dolfinx.geometry import PointOwnershipData if typing.TYPE_CHECKING: @@ -721,11 +721,9 @@ def ufl_function_space(self) -> ufl.FunctionSpace: return self @property - def element( - self, - ) -> typing.Union[_cpp.fem.FiniteElement_float32, _cpp.fem.FiniteElement_float64]: + def element(self) -> FiniteElement: """Function space finite element.""" - return self._cpp_object.element # type: ignore + return FiniteElement(self._cpp_object.element) @property def dofmap(self) -> dofmap.DofMap: From 7790a9ec1215b3682f28c62f7b7f3fed1759a87d Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 29 Nov 2024 22:09:18 +0100 Subject: [PATCH 07/20] Misse done --- python/dolfinx/fem/function.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/dolfinx/fem/function.py b/python/dolfinx/fem/function.py index e74be1e741c..436298ba332 100644 --- a/python/dolfinx/fem/function.py +++ b/python/dolfinx/fem/function.py @@ -462,7 +462,7 @@ def _(e0: Expression): # u0 is callable assert callable(u0) x = _cpp.fem.interpolation_coords( - self._V.element, self._V.mesh.geometry._cpp_object, cells0 + self._V.element._cpp_object, self._V.mesh.geometry._cpp_object, cells0 ) self._cpp_object.interpolate(np.asarray(u0(x), dtype=self.dtype), cells0) # type: ignore From 625cd621fcc43f13e243bf797d583e0334f3cc47 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 29 Nov 2024 22:27:07 +0100 Subject: [PATCH 08/20] nomatching --- python/dolfinx/fem/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/python/dolfinx/fem/__init__.py b/python/dolfinx/fem/__init__.py index 89921b842be..ee93a481d68 100644 --- a/python/dolfinx/fem/__init__.py +++ b/python/dolfinx/fem/__init__.py @@ -91,7 +91,11 @@ def create_interpolation_data( """ return _PointOwnershipData( _create_interpolation_data( - V_to.mesh._cpp_object.geometry, V_to.element, V_from.mesh._cpp_object, cells, padding + V_to.mesh._cpp_object.geometry, + V_to.element._cpp_object, + V_from.mesh._cpp_object, + cells, + padding, ) ) From de6ba0af89203fdfb535bc8cc1b690a389b72858 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Fri, 29 Nov 2024 22:43:37 +0100 Subject: [PATCH 09/20] Add docstrings --- python/dolfinx/fem/element.py | 73 +++++++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/python/dolfinx/fem/element.py b/python/dolfinx/fem/element.py index 1503d1c7617..f8d78024f7b 100644 --- a/python/dolfinx/fem/element.py +++ b/python/dolfinx/fem/element.py @@ -168,6 +168,14 @@ class FiniteElement: _cpp_object: typing.Union[_cpp.fem.FiniteElement_float32, _cpp.fem.FiniteElement_float64] def __init__(self, cpp_object): + """Creates a Python wrapper for the exported finite element class. + + Note: + Do not use this constructor directly. Instead use :func:`finite_element`. + + Args: + The underlying cpp instance that this object will wrap. + """ self._cpp_object = cpp_object def __eq__(self, other): @@ -175,47 +183,112 @@ def __eq__(self, other): @property def dtype(self): + """Geometry type of the Mesh that the FunctionSpace is defined on.""" return self._cpp_object.dtype @property def basix_element(self): + """Return underlying Basix element (if it exists). + + Raises: + Runtime error if Basix element does not exist. + """ return self._cpp_object.basix_element @property def num_sub_elements(self): + """Number of sub elements (for a mixed or blocked element).""" return self._cpp_object.num_sub_elements @property def value_shape(self): + """Value shape of the finite element field. + + The value shape describes the shape of the finite element field, e.g. `{}` for a scalar, + `{2}` for a vector in 2D, `{3, 3}` for a rank-2 tensor in 3D, etc. + + Returns: + The value shape. + """ return self._cpp_object.value_shape @property def interpolation_points(self): + """Points on the reference cell at which an expression needs to be evaluated in order to + interpolate the expression in the finite element space. + + Note: + For Lagrange elements the points will just be the nodal positions. For other elements + the points will typically be the quadrature points used to evaluate moment degrees of + freedom. + + Returns: + Interpolation point coordinates on the reference cell, returning the (0) coordinates + data (row-major) storage and (1) the shape `(num_points, tdim)`. + """ return self._cpp_object.interpolation_points @property def interpolation_ident(self): + """Check if interpolation into the finite element space is an identity operation given the + evaluation on an expression at specific points, i.e. the degree-of-freedom are equal to + point evaluations. The function will return `true` for Lagrange elements. + + Returns: + True if interpolation is an identity operation""" return self._cpp_object.interpolation_ident @property def space_dimension(self): + """Dimension of the finite element function space (the number of degrees-of-freedom for the + element). + + For 'blocked' elements, this function returns the dimension of the full element rather than + the dimension of the base element. + + Returns: + Dimension of the finite element space. + """ return self._cpp_object.space_dimension @property def needs_dof_transformations(self): + """Check if DOF transformations are needed for this element. + + DOF transformations will be needed for elements which might not be continuous when two + neighbouring cells disagree on the orientation of a shared sub-entity, and when this cannot + be corrected for by permuting the DOF numbering in the dofmap. + + For example, Raviart-Thomas elements will need DOF transformations, as the neighbouring + cells may disagree on the orientation of a basis function, and this orientation cannot be + corrected for by permuting the DOF numbers on each cell. + + Returns: + True if DOF transformations are required. + """ return self._cpp_object.needs_dof_transformations @property def signature(self): + """String identifying the finite element. + + Returns: + Element signature + """ return self._cpp_object.signature def T_apply(self, x, cell_permutations, dim): + """Transform basis functions from the reference element ordering and orientation to the + globally consistent physical element ordering and orientation. + """ self._cpp_object.T_apply(x, cell_permutations, dim) def Tt_apply(self, x, cell_permutations, dim): + """Apply the transpose of the operator applied by T_apply().""" self._cpp_object.Tt_apply(x, cell_permutations, dim) def Tt_inv_apply(self, x, cell_permutations, dim): + """Apply the inverse transpose of the operator applied by T_apply().""" self._cpp_object.Tt_apply(x, cell_permutations, dim) From 22aa1cf124d3f2a11a26ac420ef14f8b454def1b Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 30 Nov 2024 11:37:49 +0100 Subject: [PATCH 10/20] Add tpye hints --- python/dolfinx/fem/element.py | 31 ++++++++++++++++++------------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/python/dolfinx/fem/element.py b/python/dolfinx/fem/element.py index f8d78024f7b..c6bbc7cc281 100644 --- a/python/dolfinx/fem/element.py +++ b/python/dolfinx/fem/element.py @@ -167,7 +167,10 @@ def _(e: basix.finite_element.FiniteElement): class FiniteElement: _cpp_object: typing.Union[_cpp.fem.FiniteElement_float32, _cpp.fem.FiniteElement_float64] - def __init__(self, cpp_object): + def __init__( + self, + cpp_object: typing.Union[_cpp.fem.FiniteElement_float32, _cpp.fem.FiniteElement_float64], + ): """Creates a Python wrapper for the exported finite element class. Note: @@ -182,12 +185,12 @@ def __eq__(self, other): return self._cpp_object == other._cpp_object @property - def dtype(self): + def dtype(self) -> np.dtype: """Geometry type of the Mesh that the FunctionSpace is defined on.""" return self._cpp_object.dtype @property - def basix_element(self): + def basix_element(self) -> basix.finite_element.FiniteElement: """Return underlying Basix element (if it exists). Raises: @@ -196,12 +199,12 @@ def basix_element(self): return self._cpp_object.basix_element @property - def num_sub_elements(self): + def num_sub_elements(self) -> int: """Number of sub elements (for a mixed or blocked element).""" return self._cpp_object.num_sub_elements @property - def value_shape(self): + def value_shape(self) -> npt.NDArray[np.integer]: """Value shape of the finite element field. The value shape describes the shape of the finite element field, e.g. `{}` for a scalar, @@ -213,7 +216,7 @@ def value_shape(self): return self._cpp_object.value_shape @property - def interpolation_points(self): + def interpolation_points(self) -> npt.NDArray[np.floating]: """Points on the reference cell at which an expression needs to be evaluated in order to interpolate the expression in the finite element space. @@ -229,7 +232,7 @@ def interpolation_points(self): return self._cpp_object.interpolation_points @property - def interpolation_ident(self): + def interpolation_ident(self) -> bool: """Check if interpolation into the finite element space is an identity operation given the evaluation on an expression at specific points, i.e. the degree-of-freedom are equal to point evaluations. The function will return `true` for Lagrange elements. @@ -239,7 +242,7 @@ def interpolation_ident(self): return self._cpp_object.interpolation_ident @property - def space_dimension(self): + def space_dimension(self) -> int: """Dimension of the finite element function space (the number of degrees-of-freedom for the element). @@ -252,7 +255,7 @@ def space_dimension(self): return self._cpp_object.space_dimension @property - def needs_dof_transformations(self): + def needs_dof_transformations(self) -> bool: """Check if DOF transformations are needed for this element. DOF transformations will be needed for elements which might not be continuous when two @@ -269,7 +272,7 @@ def needs_dof_transformations(self): return self._cpp_object.needs_dof_transformations @property - def signature(self): + def signature(self) -> str: """String identifying the finite element. Returns: @@ -277,17 +280,19 @@ def signature(self): """ return self._cpp_object.signature - def T_apply(self, x, cell_permutations, dim): + def T_apply(self, x: npt.NDArray[np.floating], cell_permutations: np.int32, dim: int) -> None: """Transform basis functions from the reference element ordering and orientation to the globally consistent physical element ordering and orientation. """ self._cpp_object.T_apply(x, cell_permutations, dim) - def Tt_apply(self, x, cell_permutations, dim): + def Tt_apply(self, x: npt.NDArray[np.floating], cell_permutations: np.int32, dim: int) -> None: """Apply the transpose of the operator applied by T_apply().""" self._cpp_object.Tt_apply(x, cell_permutations, dim) - def Tt_inv_apply(self, x, cell_permutations, dim): + def Tt_inv_apply( + self, x: npt.NDArray[np.floating], cell_permutations: np.int32, dim: int + ) -> None: """Apply the inverse transpose of the operator applied by T_apply().""" self._cpp_object.Tt_apply(x, cell_permutations, dim) From c1861993ddd454dd35633add5ace10110ae2ac57 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 30 Nov 2024 11:40:13 +0100 Subject: [PATCH 11/20] Remove returns for properties --- python/dolfinx/fem/element.py | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/python/dolfinx/fem/element.py b/python/dolfinx/fem/element.py index c6bbc7cc281..31ee820b942 100644 --- a/python/dolfinx/fem/element.py +++ b/python/dolfinx/fem/element.py @@ -191,7 +191,7 @@ def dtype(self) -> np.dtype: @property def basix_element(self) -> basix.finite_element.FiniteElement: - """Return underlying Basix element (if it exists). + """Return underlying Basix C++ element (if it exists). Raises: Runtime error if Basix element does not exist. @@ -209,9 +209,6 @@ def value_shape(self) -> npt.NDArray[np.integer]: The value shape describes the shape of the finite element field, e.g. `{}` for a scalar, `{2}` for a vector in 2D, `{3, 3}` for a rank-2 tensor in 3D, etc. - - Returns: - The value shape. """ return self._cpp_object.value_shape @@ -220,14 +217,13 @@ def interpolation_points(self) -> npt.NDArray[np.floating]: """Points on the reference cell at which an expression needs to be evaluated in order to interpolate the expression in the finite element space. + Interpolation point coordinates on the reference cell, returning the (0) coordinates data + (row-major) storage and (1) the shape `(num_points, tdim)`. + Note: For Lagrange elements the points will just be the nodal positions. For other elements the points will typically be the quadrature points used to evaluate moment degrees of freedom. - - Returns: - Interpolation point coordinates on the reference cell, returning the (0) coordinates - data (row-major) storage and (1) the shape `(num_points, tdim)`. """ return self._cpp_object.interpolation_points @@ -235,10 +231,7 @@ def interpolation_points(self) -> npt.NDArray[np.floating]: def interpolation_ident(self) -> bool: """Check if interpolation into the finite element space is an identity operation given the evaluation on an expression at specific points, i.e. the degree-of-freedom are equal to - point evaluations. The function will return `true` for Lagrange elements. - - Returns: - True if interpolation is an identity operation""" + point evaluations. The function will return `true` for Lagrange elements.""" return self._cpp_object.interpolation_ident @property @@ -248,9 +241,6 @@ def space_dimension(self) -> int: For 'blocked' elements, this function returns the dimension of the full element rather than the dimension of the base element. - - Returns: - Dimension of the finite element space. """ return self._cpp_object.space_dimension @@ -265,19 +255,12 @@ def needs_dof_transformations(self) -> bool: For example, Raviart-Thomas elements will need DOF transformations, as the neighbouring cells may disagree on the orientation of a basis function, and this orientation cannot be corrected for by permuting the DOF numbers on each cell. - - Returns: - True if DOF transformations are required. """ return self._cpp_object.needs_dof_transformations @property def signature(self) -> str: - """String identifying the finite element. - - Returns: - Element signature - """ + """String identifying the finite element.""" return self._cpp_object.signature def T_apply(self, x: npt.NDArray[np.floating], cell_permutations: np.int32, dim: int) -> None: From 765110b7f19d7722e66f46935202a3175d390799 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 30 Nov 2024 11:44:53 +0100 Subject: [PATCH 12/20] Add argument doc strings --- python/dolfinx/fem/element.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/python/dolfinx/fem/element.py b/python/dolfinx/fem/element.py index 31ee820b942..8ea8e23bb60 100644 --- a/python/dolfinx/fem/element.py +++ b/python/dolfinx/fem/element.py @@ -285,7 +285,13 @@ def finite_element( ufl_e: ufl.finiteelement, dtype: np.dtype, ) -> FiniteElement: - """Create a DOLFINx element from a basix.ufl element.""" + """Create a DOLFINx element from a basix.ufl element. + + Args: + cell_type: Element cell type, see `mesh.CellType` + ufl_e: UFL element, holding quadrature rule and other properties of the selected element. + dtype: Geometry type of the element. + """ if np.issubdtype(dtype, np.float32): CppElement = _cpp.fem.FiniteElement_float32 elif np.issubdtype(dtype, np.float64): From b7cea4b586f1782ab8f21c1137218fe5e4427dda Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sat, 30 Nov 2024 11:52:06 +0100 Subject: [PATCH 13/20] more document args --- python/dolfinx/fem/element.py | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/python/dolfinx/fem/element.py b/python/dolfinx/fem/element.py index 8ea8e23bb60..8254363578a 100644 --- a/python/dolfinx/fem/element.py +++ b/python/dolfinx/fem/element.py @@ -266,17 +266,46 @@ def signature(self) -> str: def T_apply(self, x: npt.NDArray[np.floating], cell_permutations: np.int32, dim: int) -> None: """Transform basis functions from the reference element ordering and orientation to the globally consistent physical element ordering and orientation. + + Args: + x: Data to transform (in place). The shape is `(m, n)`, where `m` is the number of + dgerees-of-freedom and the storage is row-major. + cell_permutations: Permutation data for the cell. + dim: Number of columns in `data`. + + Note: + Exposed for testing only, expect poor performance. """ self._cpp_object.T_apply(x, cell_permutations, dim) def Tt_apply(self, x: npt.NDArray[np.floating], cell_permutations: np.int32, dim: int) -> None: - """Apply the transpose of the operator applied by T_apply().""" + """Apply the transpose of the operator applied by T_apply(). + + Args: + x: Data to transform (in place). The shape is `(m, n)`, where `m` is the number of + dgerees-of-freedom and the storage is row-major. + cell_permutations: Permutation data for the cell. + dim: Number of columns in `data`. + + Note: + Exposed for testing only, expect poor performance. + """ self._cpp_object.Tt_apply(x, cell_permutations, dim) def Tt_inv_apply( self, x: npt.NDArray[np.floating], cell_permutations: np.int32, dim: int ) -> None: - """Apply the inverse transpose of the operator applied by T_apply().""" + """Apply the inverse transpose of the operator applied by T_apply(). + + Args: + x: Data to transform (in place). The shape is `(m, n)`, where `m` is the number of + dgerees-of-freedom and the storage is row-major. + cell_permutations: Permutation data for the cell. + dim: Number of columns in `data`. + + Note: + Exposed for testing only, expect poor performance. + """ self._cpp_object.Tt_apply(x, cell_permutations, dim) From df6f9ce0a7049300aa1b966a3e2d0ef1cb797595 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sun, 1 Dec 2024 16:35:27 +0100 Subject: [PATCH 14/20] Rename: dtype -> FiniteElement_dtype --- python/dolfinx/fem/element.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/python/dolfinx/fem/element.py b/python/dolfinx/fem/element.py index 8254363578a..dbe259e0ecf 100644 --- a/python/dolfinx/fem/element.py +++ b/python/dolfinx/fem/element.py @@ -312,24 +312,27 @@ def Tt_inv_apply( def finite_element( cell_type: _cpp.mesh.CellType, ufl_e: ufl.finiteelement, - dtype: np.dtype, + FiniteElement_dtype: np.dtype, ) -> FiniteElement: """Create a DOLFINx element from a basix.ufl element. Args: cell_type: Element cell type, see `mesh.CellType` ufl_e: UFL element, holding quadrature rule and other properties of the selected element. - dtype: Geometry type of the element. + FiniteElement_dtype: Geometry type of the element. """ - if np.issubdtype(dtype, np.float32): + if np.issubdtype(FiniteElement_dtype, np.float32): CppElement = _cpp.fem.FiniteElement_float32 - elif np.issubdtype(dtype, np.float64): + elif np.issubdtype(FiniteElement_dtype, np.float64): CppElement = _cpp.fem.FiniteElement_float64 else: - raise ValueError(f"Unsupported dtype: {dtype}") + raise ValueError(f"Unsupported dtype: {FiniteElement_dtype}") if ufl_e.is_mixed: - elements = [finite_element(cell_type, e, dtype)._cpp_object for e in ufl_e.sub_elements] + elements = [ + finite_element(cell_type, e, FiniteElement_dtype)._cpp_object + for e in ufl_e.sub_elements + ] return FiniteElement(CppElement(elements)) elif ufl_e.is_quadrature: return FiniteElement( From b2cb0d0e625f7ed176d22d94bcd2a75ed83d5f91 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sun, 1 Dec 2024 17:40:09 +0100 Subject: [PATCH 15/20] finite_element -> finiteelement --- python/dolfinx/fem/__init__.py | 4 ++-- python/dolfinx/fem/element.py | 6 +++--- python/dolfinx/fem/function.py | 4 ++-- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/python/dolfinx/fem/__init__.py b/python/dolfinx/fem/__init__.py index ee93a481d68..44ca560ffea 100644 --- a/python/dolfinx/fem/__init__.py +++ b/python/dolfinx/fem/__init__.py @@ -32,7 +32,7 @@ locate_dofs_topological, ) from dolfinx.fem.dofmap import DofMap -from dolfinx.fem.element import CoordinateElement, FiniteElement, coordinate_element, finite_element +from dolfinx.fem.element import CoordinateElement, FiniteElement, coordinate_element, finiteelement from dolfinx.fem.forms import ( Form, compile_form, @@ -194,7 +194,7 @@ def compute_integration_domains( "dirichletbc", "discrete_gradient", "extract_function_spaces", - "finite_element", + "finiteelement", "form", "form_cpp_class", "functionspace", diff --git a/python/dolfinx/fem/element.py b/python/dolfinx/fem/element.py index dbe259e0ecf..b02f458b55a 100644 --- a/python/dolfinx/fem/element.py +++ b/python/dolfinx/fem/element.py @@ -174,7 +174,7 @@ def __init__( """Creates a Python wrapper for the exported finite element class. Note: - Do not use this constructor directly. Instead use :func:`finite_element`. + Do not use this constructor directly. Instead use :func:`finiteelement`. Args: The underlying cpp instance that this object will wrap. @@ -309,7 +309,7 @@ def Tt_inv_apply( self._cpp_object.Tt_apply(x, cell_permutations, dim) -def finite_element( +def finiteelement( cell_type: _cpp.mesh.CellType, ufl_e: ufl.finiteelement, FiniteElement_dtype: np.dtype, @@ -330,7 +330,7 @@ def finite_element( if ufl_e.is_mixed: elements = [ - finite_element(cell_type, e, FiniteElement_dtype)._cpp_object + finiteelement(cell_type, e, FiniteElement_dtype)._cpp_object for e in ufl_e.sub_elements ] return FiniteElement(CppElement(elements)) diff --git a/python/dolfinx/fem/function.py b/python/dolfinx/fem/function.py index 436298ba332..43cb02f1bd9 100644 --- a/python/dolfinx/fem/function.py +++ b/python/dolfinx/fem/function.py @@ -18,7 +18,7 @@ from dolfinx import cpp as _cpp from dolfinx import default_scalar_type, jit, la from dolfinx.fem import dofmap -from dolfinx.fem.element import FiniteElement, finite_element +from dolfinx.fem.element import FiniteElement, finiteelement from dolfinx.geometry import PointOwnershipData if typing.TYPE_CHECKING: @@ -589,7 +589,7 @@ def functionspace( raise ValueError("Non-matching UFL cell and mesh cell shapes.") # Create DOLFINx objects - element = finite_element(mesh.topology.cell_type, ufl_e, dtype) + element = finiteelement(mesh.topology.cell_type, ufl_e, dtype) cpp_dofmap = _cpp.fem.create_dofmap(mesh.comm, mesh.topology._cpp_object, element._cpp_object) assert np.issubdtype( From a7239babed57bd1daccec9616d984a90c4b863df Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Sun, 1 Dec 2024 17:41:13 +0100 Subject: [PATCH 16/20] Ruff --- python/dolfinx/fem/element.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/python/dolfinx/fem/element.py b/python/dolfinx/fem/element.py index b02f458b55a..4212a07922a 100644 --- a/python/dolfinx/fem/element.py +++ b/python/dolfinx/fem/element.py @@ -330,8 +330,7 @@ def finiteelement( if ufl_e.is_mixed: elements = [ - finiteelement(cell_type, e, FiniteElement_dtype)._cpp_object - for e in ufl_e.sub_elements + finiteelement(cell_type, e, FiniteElement_dtype)._cpp_object for e in ufl_e.sub_elements ] return FiniteElement(CppElement(elements)) elif ufl_e.is_quadrature: From 46864683a94062c3731986a8cbd5322bfd402f54 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Mon, 2 Dec 2024 19:16:51 +0100 Subject: [PATCH 17/20] Apply suggestion --- python/dolfinx/fem/element.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/python/dolfinx/fem/element.py b/python/dolfinx/fem/element.py index 4212a07922a..cc15dfc4ab1 100644 --- a/python/dolfinx/fem/element.py +++ b/python/dolfinx/fem/element.py @@ -274,7 +274,8 @@ def T_apply(self, x: npt.NDArray[np.floating], cell_permutations: np.int32, dim: dim: Number of columns in `data`. Note: - Exposed for testing only, expect poor performance. + Exposed for testing. Function is not vectorised across multiple cells. Please see + `basix.numba_helpers` for performant versions. """ self._cpp_object.T_apply(x, cell_permutations, dim) @@ -288,7 +289,8 @@ def Tt_apply(self, x: npt.NDArray[np.floating], cell_permutations: np.int32, dim dim: Number of columns in `data`. Note: - Exposed for testing only, expect poor performance. + Exposed for testing. Function is not vectorised across multiple cells. Please see + `basix.numba_helpers` for performant versions. """ self._cpp_object.Tt_apply(x, cell_permutations, dim) @@ -304,7 +306,8 @@ def Tt_inv_apply( dim: Number of columns in `data`. Note: - Exposed for testing only, expect poor performance. + Exposed for testing. Function is not vectorised across multiple cells. Please see + `basix.numba_helpers` for performant versions. """ self._cpp_object.Tt_apply(x, cell_permutations, dim) From 02d03f27a31add5cc2f3a9b320739e8d54c661c7 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Tue, 3 Dec 2024 17:55:25 +0100 Subject: [PATCH 18/20] Switch to cached_propert for element access --- python/dolfinx/fem/function.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/dolfinx/fem/function.py b/python/dolfinx/fem/function.py index 43cb02f1bd9..44c1f740bb2 100644 --- a/python/dolfinx/fem/function.py +++ b/python/dolfinx/fem/function.py @@ -8,7 +8,7 @@ from __future__ import annotations import typing -from functools import singledispatch +from functools import cached_property, singledispatch import numpy as np import numpy.typing as npt @@ -720,7 +720,7 @@ def ufl_function_space(self) -> ufl.FunctionSpace: """UFL function space.""" return self - @property + @cached_property def element(self) -> FiniteElement: """Function space finite element.""" return FiniteElement(self._cpp_object.element) From f77dd430054978d8b877bda47b1206ff138f3e83 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Tue, 3 Dec 2024 22:23:01 +0100 Subject: [PATCH 19/20] FixreStructured text --- python/dolfinx/fem/element.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/python/dolfinx/fem/element.py b/python/dolfinx/fem/element.py index cc15dfc4ab1..2af7441021c 100644 --- a/python/dolfinx/fem/element.py +++ b/python/dolfinx/fem/element.py @@ -95,7 +95,7 @@ def pull_back( ``shape=(num_points, geometrical_dimension)``. cell_geometry: Physical coordinates describing the cell, shape ``(num_of_geometry_basis_functions, geometrical_dimension)`` - They can be created by accessing `geometry.x[geometry.dofmap.cell_dofs(i)]`, + They can be created by accessing ``geometry.x[geometry.dofmap.cell_dofs(i)]``, Returns: Reference coordinates of the physical points ``x``. @@ -174,7 +174,7 @@ def __init__( """Creates a Python wrapper for the exported finite element class. Note: - Do not use this constructor directly. Instead use :func:`finiteelement`. + Do not use this constructor directly. Instead use :func:``finiteelement``. Args: The underlying cpp instance that this object will wrap. @@ -207,8 +207,8 @@ def num_sub_elements(self) -> int: def value_shape(self) -> npt.NDArray[np.integer]: """Value shape of the finite element field. - The value shape describes the shape of the finite element field, e.g. `{}` for a scalar, - `{2}` for a vector in 2D, `{3, 3}` for a rank-2 tensor in 3D, etc. + The value shape describes the shape of the finite element field, e.g. ``{}`` for a scalar, + ``{2}`` for a vector in 2D, ``{3, 3}`` for a rank-2 tensor in 3D, etc. """ return self._cpp_object.value_shape @@ -218,7 +218,7 @@ def interpolation_points(self) -> npt.NDArray[np.floating]: interpolate the expression in the finite element space. Interpolation point coordinates on the reference cell, returning the (0) coordinates data - (row-major) storage and (1) the shape `(num_points, tdim)`. + (row-major) storage and (1) the shape ``(num_points, tdim)``. Note: For Lagrange elements the points will just be the nodal positions. For other elements @@ -268,10 +268,10 @@ def T_apply(self, x: npt.NDArray[np.floating], cell_permutations: np.int32, dim: globally consistent physical element ordering and orientation. Args: - x: Data to transform (in place). The shape is `(m, n)`, where `m` is the number of + x: Data to transform (in place). The shape is ``(m, n)``, where `m` is the number of dgerees-of-freedom and the storage is row-major. cell_permutations: Permutation data for the cell. - dim: Number of columns in `data`. + dim: Number of columns in ``data``. Note: Exposed for testing. Function is not vectorised across multiple cells. Please see @@ -283,7 +283,7 @@ def Tt_apply(self, x: npt.NDArray[np.floating], cell_permutations: np.int32, dim """Apply the transpose of the operator applied by T_apply(). Args: - x: Data to transform (in place). The shape is `(m, n)`, where `m` is the number of + x: Data to transform (in place). The shape is ``(m, n)``, where `m` is the number of dgerees-of-freedom and the storage is row-major. cell_permutations: Permutation data for the cell. dim: Number of columns in `data`. @@ -300,14 +300,14 @@ def Tt_inv_apply( """Apply the inverse transpose of the operator applied by T_apply(). Args: - x: Data to transform (in place). The shape is `(m, n)`, where `m` is the number of + x: Data to transform (in place). The shape is ``(m, n)``, where ``m`` is the number of dgerees-of-freedom and the storage is row-major. cell_permutations: Permutation data for the cell. dim: Number of columns in `data`. Note: Exposed for testing. Function is not vectorised across multiple cells. Please see - `basix.numba_helpers` for performant versions. + ``basix.numba_helpers`` for performant versions. """ self._cpp_object.Tt_apply(x, cell_permutations, dim) @@ -320,7 +320,7 @@ def finiteelement( """Create a DOLFINx element from a basix.ufl element. Args: - cell_type: Element cell type, see `mesh.CellType` + cell_type: Element cell type, see ``mesh.CellType`` ufl_e: UFL element, holding quadrature rule and other properties of the selected element. FiniteElement_dtype: Geometry type of the element. """ From 6f35a39c30a5546538f3bd4d3c2e4f8ac56e0977 Mon Sep 17 00:00:00 2001 From: schnellerhase <56360279+schnellerhase@users.noreply.github.com> Date: Tue, 3 Dec 2024 22:24:00 +0100 Subject: [PATCH 20/20] Fix return type descriptions --- python/dolfinx/fem/element.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/dolfinx/fem/element.py b/python/dolfinx/fem/element.py index 2af7441021c..885a806846f 100644 --- a/python/dolfinx/fem/element.py +++ b/python/dolfinx/fem/element.py @@ -217,8 +217,8 @@ def interpolation_points(self) -> npt.NDArray[np.floating]: """Points on the reference cell at which an expression needs to be evaluated in order to interpolate the expression in the finite element space. - Interpolation point coordinates on the reference cell, returning the (0) coordinates data - (row-major) storage and (1) the shape ``(num_points, tdim)``. + Interpolation point coordinates on the reference cell, returning the coordinates data + (row-major) storage with shape ``(num_points, tdim)``. Note: For Lagrange elements the points will just be the nodal positions. For other elements