From 30c163f051fc3b373bb75a003d055e429be3d0c8 Mon Sep 17 00:00:00 2001 From: Marcus Mohr <marcus.mohr@lmu.de> Date: Mon, 2 Dec 2024 18:01:25 +0100 Subject: [PATCH] First unsuccessful approach to split up function_space_impls.py --- .../n1e1_space_impl.py | 141 +++ .../p0_space_impl.py | 89 ++ .../p1_space_impl.py | 304 +++++++ .../p2_space_impl.py | 344 ++++++++ .../function_space_impls.py | 812 +----------------- 5 files changed, 883 insertions(+), 807 deletions(-) create mode 100644 hog/operator_generation/function_space_implementations/n1e1_space_impl.py create mode 100644 hog/operator_generation/function_space_implementations/p0_space_impl.py create mode 100644 hog/operator_generation/function_space_implementations/p1_space_impl.py create mode 100644 hog/operator_generation/function_space_implementations/p2_space_impl.py diff --git a/hog/operator_generation/function_space_implementations/n1e1_space_impl.py b/hog/operator_generation/function_space_implementations/n1e1_space_impl.py new file mode 100644 index 0000000..c0cdd5a --- /dev/null +++ b/hog/operator_generation/function_space_implementations/n1e1_space_impl.py @@ -0,0 +1,141 @@ +# HyTeG Operator Generator +# Copyright (C) 2024 HyTeG Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + + +import hog.operator_generation.function_space_impls + + +class N1E1FunctionSpaceImpl(FunctionSpaceImpl): + def __init__( + self, + fe_space: FunctionSpace, + name: str, + type_descriptor: HOGType, + is_pointer: bool = False, + ): + super().__init__(fe_space, name, type_descriptor, is_pointer) + self.field = self._create_field(name) + + def pre_communication(self, dim: int) -> str: + if dim == 2: + return "" + else: + return ( + f"{self._deref()}.communicate< Face, Cell >( level );\n" + f"{self._deref()}.communicate< Edge, Cell >( level );" + ) + + def zero_halos(self, dim: int) -> str: + if dim == 2: + return "" + else: + return f"edgedof::macrocell::setBoundaryToZero( level, cell, {self._deref()}.getDoFs()->getCellDataID() );" + + def post_communication( + self, dim: int, params: str, transform_basis: bool = True + ) -> str: + if dim == 2: + return "" + else: + dofs = "" + if not transform_basis: + dofs = "getDoFs()->" + return ( + f"{self._deref()}.{dofs}communicateAdditively< Cell, Face >( {params} );\n" + f"{self._deref()}.{dofs}communicateAdditively< Cell, Edge >( {params} );" + ) + + def pointer_retrieval(self, dim: int) -> str: + """C++ code for retrieving pointers to the numerical data stored in the macro primitives `face` (2d) or `cell` (3d).""" + Macro = {2: "Face", 3: "Cell"}[dim] + macro = {2: "face", 3: "cell"}[dim] + + return f"{self.type_descriptor.pystencils_type}* _data_{self.name} = {macro}.getData( {self._deref()}.getDoFs()->get{Macro}DataID() )->getPointer( level );" + + def invert_elementwise(self, dim: int) -> str: + return f"{self._deref()}.getDoFs()->invertElementwise( level );" + + def local_dofs( + self, + geometry: ElementGeometry, + element_index: Tuple[int, int, int], + element_type: Union[FaceType, CellType], + indexing_info: IndexingInfo, + element_vertex_ordering: List[int], + ) -> List[Field.Access]: + vertex_dof_indices = micro_element_to_vertex_indices( + geometry, element_type, element_index + ) + + vertex_dof_indices = [vertex_dof_indices[i] for i in element_vertex_ordering] + + edge_dof_indices = micro_vertex_to_edge_indices(geometry, vertex_dof_indices) + edge_array_indices = [ + dof_idx.array_index(geometry, indexing_info) for dof_idx in edge_dof_indices + ] + + return [self.field.absolute_access((idx,), (0,)) for idx in edge_array_indices] + + def func_type_string(self) -> str: + return f"n1e1::N1E1VectorFunction< {self.type_descriptor.pystencils_type} >" + + def includes(self) -> Set[str]: + return { + "hyteg/n1e1functionspace/N1E1VectorFunction.hpp", + "hyteg/n1e1functionspace/N1E1MacroCell.hpp", + } + + def dof_transformation( + self, + geometry: ElementGeometry, + element_index: Tuple[int, int, int], + element_type: Union[FaceType, CellType], + element_vertex_ordering: List[int], + ) -> Tuple[CustomCodeNode, sp.MatrixBase]: + if element_vertex_ordering != [0, 1, 2, 3]: + raise HOGException( + "Element vertex re-ordering not supported for Nédélec elements (yet)." + ) + + Macro = {2: "Face", 3: "Cell"}[geometry.dimensions] + macro = {2: "face", 3: "cell"}[geometry.dimensions] + name = "basisTransformation" + n_dofs = self.fe_space.num_dofs(geometry) + + # NOTE: Types are added manually because pystencils won't add types to accessed/ + # defined symbols of custom code nodes. As a result the symbols + # in the matrix will be typed but not their definition, leading to + # undefined symbols. + # NOTE: The type is `real_t` (not `self._type_descriptor.pystencils_type`) + # because this function is implemented manually in HyTeG with + # this signature. Passing `np.float64` is not ideal (if `real_t != + # double`) but it makes sure that casts are inserted if necessary + # (though some might be superfluous). + symbols = [ + ps.TypedSymbol(f"{name}.diagonal()({i})", np.float64) for i in range(n_dofs) + ] + return ( + CustomCodeNode( + f"const Eigen::DiagonalMatrix< real_t, {n_dofs} > {name} = " + f"n1e1::macro{macro}::basisTransformation( level, {macro}, {{{element_index[0]}, {element_index[1]}, {element_index[2]}}}, {macro}dof::{Macro}Type::{element_type.name} );", + [ + ps.TypedSymbol("level", "const uint_t"), + ps.TypedSymbol(f"{macro}", f"const {Macro}&"), + ], + symbols, + ), + sp.diag(*symbols), + ) diff --git a/hog/operator_generation/function_space_implementations/p0_space_impl.py b/hog/operator_generation/function_space_implementations/p0_space_impl.py new file mode 100644 index 0000000..15180b1 --- /dev/null +++ b/hog/operator_generation/function_space_implementations/p0_space_impl.py @@ -0,0 +1,89 @@ +# HyTeG Operator Generator +# Copyright (C) 2024 HyTeG Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +import hog.operator_generation.function_space_impls + + +class P0FunctionSpaceImpl(FunctionSpaceImpl): + def __init__( + self, + fe_space: FunctionSpace, + name: str, + type_descriptor: HOGType, + is_pointer: bool = False, + ): + super().__init__(fe_space, name, type_descriptor, is_pointer) + self.field = self._create_field(name) + + def pre_communication(self, dim: int) -> str: + return "" + + def zero_halos(self, dim: int) -> str: + return "" + + def post_communication( + self, dim: int, params: str, transform_basis: bool = True + ) -> str: + return "" + + def pointer_retrieval(self, dim: int) -> str: + """C++ code for retrieving pointers to the numerical data stored in the macro primitives `face` (2d) or `cell` (3d).""" + Macro = {2: "Face", 3: "Cell"}[dim] + macro = {2: "face", 3: "cell"}[dim] + + return f"{self.type_descriptor.pystencils_type}* _data_{self.name} = {self._deref()}.getDGFunction()->volumeDoFFunction()->dofMemory( it.first, level );" + + def invert_elementwise(self, dim: int) -> str: + return f"{self._deref()}.invertElementwise( level );" + + def local_dofs( + self, + geometry: ElementGeometry, + element_index: Tuple[int, int, int], + element_type: Union[FaceType, CellType], + indexing_info: IndexingInfo, + element_vertex_ordering: List[int], + ) -> List[Field.Access]: + volume_dof_indices = micro_element_to_volume_indices( + element_type, element_index, 1, VolumeDoFMemoryLayout.SoA + ) + + vertex_array_indices = [ + dof_idx.array_index(geometry, indexing_info) + for dof_idx in volume_dof_indices + ] + + return [ + self.field.absolute_access((idx,), (0,)) for idx in vertex_array_indices + ] + + def func_type_string(self) -> str: + return f"P0Function< {self.type_descriptor.pystencils_type} >" + + def includes(self) -> Set[str]: + return {f"hyteg/p0functionspace/P0Function.hpp"} + + def dof_transformation( + self, + geometry: ElementGeometry, + element_index: Tuple[int, int, int], + element_type: Union[FaceType, CellType], + element_vertex_ordering: List[int], + ) -> Tuple[CustomCodeNode, sp.MatrixBase]: + return ( + CustomCodeNode("", [], []), + sp.Identity(self.fe_space.num_dofs(geometry)), + ) diff --git a/hog/operator_generation/function_space_implementations/p1_space_impl.py b/hog/operator_generation/function_space_implementations/p1_space_impl.py new file mode 100644 index 0000000..cb851e6 --- /dev/null +++ b/hog/operator_generation/function_space_implementations/p1_space_impl.py @@ -0,0 +1,304 @@ +# HyTeG Operator Generator +# Copyright (C) 2024 HyTeG Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + +import hog.operator_generation.function_space_impls + + +class P1FunctionSpaceImpl(FunctionSpaceImpl): + def __init__( + self, + fe_space: FunctionSpace, + name: str, + type_descriptor: HOGType, + is_pointer: bool = False, + ): + super().__init__(fe_space, name, type_descriptor, is_pointer) + self.field = self._create_field(name) + + def pre_communication(self, dim: int) -> str: + if dim == 2: + return f"communication::syncFunctionBetweenPrimitives( {self.name}, level, communication::syncDirection_t::LOW2HIGH );" + else: + return ( + f"{self._deref()}.communicate< Face, Cell >( level );\n" + f"{self._deref()}.communicate< Edge, Cell >( level );\n" + f"{self._deref()}.communicate< Vertex, Cell >( level );" + ) + + def zero_halos(self, dim: int) -> str: + if dim == 2: + return ( + f"for ( const auto& idx : vertexdof::macroface::Iterator( level ) ) {{\n" + f" if ( vertexdof::macroface::isVertexOnBoundary( level, idx ) ) {{\n" + f" auto arrayIdx = vertexdof::macroface::index( level, idx.x(), idx.y() );\n" + f" _data_{self.name}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" + f" }}\n" + f"}}" + ) + else: + return ( + f"for ( const auto& idx : vertexdof::macrocell::Iterator( level ) ) {{\n" + f" if ( !vertexdof::macrocell::isOnCellFace( idx, level ).empty() ) {{\n" + f" auto arrayIdx = vertexdof::macrocell::index( level, idx.x(), idx.y(), idx.z() );\n" + f" _data_{self.name}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" + f" }}\n" + f"}}" + ) + + def post_communication( + self, dim: int, params: str, transform_basis: bool = True + ) -> str: + if dim == 2: + return ( + f"{self._deref()}.communicateAdditively < Face, Edge > ( {params} );\n" + f"{self._deref()}.communicateAdditively < Face, Vertex > ( {params} );" + ) + else: + return ( + f"{self._deref()}.communicateAdditively< Cell, Face >( {params} );\n" + f"{self._deref()}.communicateAdditively< Cell, Edge >( {params} );\n" + f"{self._deref()}.communicateAdditively< Cell, Vertex >( {params} );" + ) + + def pointer_retrieval(self, dim: int) -> str: + """C++ code for retrieving pointers to the numerical data stored in the macro primitives `face` (2d) or `cell` (3d).""" + Macro = {2: "Face", 3: "Cell"}[dim] + macro = {2: "face", 3: "cell"}[dim] + + return f"{self.type_descriptor.pystencils_type}* _data_{self.name} = {macro}.getData( {self._deref()}.get{Macro}DataID() )->getPointer( level );" + + def invert_elementwise(self, dim: int) -> str: + return f"{self._deref()}.invertElementwise( level );" + + def local_dofs( + self, + geometry: ElementGeometry, + element_index: Tuple[int, int, int], + element_type: Union[FaceType, CellType], + indexing_info: IndexingInfo, + element_vertex_ordering: List[int], + ) -> List[Field.Access]: + vertex_dof_indices = micro_element_to_vertex_indices( + geometry, element_type, element_index + ) + + vertex_dof_indices = [vertex_dof_indices[i] for i in element_vertex_ordering] + + vertex_array_indices = [ + dof_idx.array_index(geometry, indexing_info) + for dof_idx in vertex_dof_indices + ] + + return [ + self.field.absolute_access((idx,), (0,)) for idx in vertex_array_indices + ] + + def func_type_string(self) -> str: + return f"P1Function< {self.type_descriptor.pystencils_type} >" + + def includes(self) -> Set[str]: + return {f"hyteg/p1functionspace/P1Function.hpp"} + + def dof_transformation( + self, + geometry: ElementGeometry, + element_index: Tuple[int, int, int], + element_type: Union[FaceType, CellType], + element_vertex_ordering: List[int], + ) -> Tuple[CustomCodeNode, sp.MatrixBase]: + return ( + CustomCodeNode("", [], []), + sp.Identity(self.fe_space.num_dofs(geometry)), + ) + + +class P1VectorFunctionSpaceImpl(FunctionSpaceImpl): + def __init__( + self, + fe_space: FunctionSpace, + name: str, + type_descriptor: HOGType, + is_pointer: bool = False, + ): + super().__init__(fe_space, name, type_descriptor, is_pointer) + self.fields: Dict[int, Field] = {} + + def _field_name(self, component: int) -> str: + return self.name + f"_{component}" + + def _raw_pointer_name(self, component: int) -> str: + return f"_data_" + self._field_name(component) + + def pre_communication(self, dim: int) -> str: + if dim == 2: + return f"communication::syncVectorFunctionBetweenPrimitives( {self.name}, level, communication::syncDirection_t::LOW2HIGH );" + else: + ret_str = "" + for i in range(dim): + ret_str += ( + f"{self._deref()}[{i}].communicate< Face, Cell >( level );\n" + f"{self._deref()}[{i}].communicate< Edge, Cell >( level );\n" + f"{self._deref()}[{i}].communicate< Vertex, Cell >( level );" + ) + return ret_str + + def zero_halos(self, dim: int) -> str: + if dim == 2: + return ( + f"for ( const auto& idx : vertexdof::macroface::Iterator( level ) ) {{\n" + f" if ( vertexdof::macroface::isVertexOnBoundary( level, idx ) ) {{\n" + f" auto arrayIdx = vertexdof::macroface::index( level, idx.x(), idx.y() );\n" + f" {self._raw_pointer_name(0)}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" + f" {self._raw_pointer_name(1)}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" + f" }}\n" + f"}}" + ) + else: + return ( + f"for ( const auto& idx : vertexdof::macrocell::Iterator( level ) ) {{\n" + f" if ( !vertexdof::macrocell::isOnCellFace( idx, level ).empty() ) {{\n" + f" auto arrayIdx = vertexdof::macrocell::index( level, idx.x(), idx.y(), idx.z() );\n" + f" {self._raw_pointer_name(0)}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" + f" {self._raw_pointer_name(1)}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" + f" {self._raw_pointer_name(2)}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" + f" }}\n" + f"}}" + ) + + def post_communication( + self, dim: int, params: str, transform_basis: bool = True + ) -> str: + if dim == 2: + ret_str = "" + for i in range(dim): + ret_str += ( + f"{self._deref()}[{i}].communicateAdditively < Face, Edge > ( {params} );\n" + f"{self._deref()}[{i}].communicateAdditively < Face, Vertex > ( {params} );\n" + ) + return ret_str + else: + ret_str = "" + for i in range(dim): + ret_str += ( + f"{self._deref()}[{i}].communicateAdditively< Cell, Face >( {params} );\n" + f"{self._deref()}[{i}].communicateAdditively< Cell, Edge >( {params} );\n" + f"{self._deref()}[{i}].communicateAdditively< Cell, Vertex >( {params} );" + ) + return ret_str + + def pointer_retrieval(self, dim: int) -> str: + """C++ code for retrieving pointers to the numerical data stored in the macro primitives `face` (2d) or `cell` (3d).""" + Macro = {2: "Face", 3: "Cell"}[dim] + macro = {2: "face", 3: "cell"}[dim] + + ret_str = "" + for i in range(dim): + ret_str += f"{self.type_descriptor.pystencils_type}* {self._raw_pointer_name(i)} = {macro}.getData( {self._deref()}[{i}].get{Macro}DataID() )->getPointer( level );\n" + return ret_str + + def invert_elementwise(self, dim: int) -> str: + ret_str = "" + for i in range(dim): + ret_str += f"{self._deref()}[{i}].invertElementwise( level );\n" + return ret_str + + def local_dofs( + self, + geometry: ElementGeometry, + element_index: Tuple[int, int, int], + element_type: Union[FaceType, CellType], + indexing_info: IndexingInfo, + element_vertex_ordering: List[int], + ) -> List[Field.Access]: + """ + Returns the element-local DoFs (field accesses) in a list (i.e., linearized). + + The order here has to match that of the function space implementation. + TODO: This is a little concerning since that order has to match in two very different parts of this + implementation. Here, and in the TensorialVectorFunctionSpace. Maybe we can define this order in a single + location. + We return them in "SoA" order: first all DoFs corresponding to the function with the first component != 0, + then all DoFs corresponding to the function with the second component != 0 etc. + + For instance, in 2D we get the DoFs corresponding to the 6 shape functions in the following order: + + First component != 0 + + list[0]: ⎡-x_ref_0 - x_ref_1 + 1⎤ + ⎢ ⎥ + ⎣ 0 ⎦ + + list[1]: ⎡x_ref_0⎤ + ⎢ ⎥ + ⎣ 0 ⎦ + + list[2]: ⎡x_ref_1⎤ + ⎢ ⎥ + ⎣ 0 ⎦ + + Second component != 0 + + list[3]: ⎡ 0 ⎤ + ⎢ ⎥ + ⎣-x_ref_0 - x_ref_1 + 1⎦ + + list[4]: ⎡ 0 ⎤ + ⎢ ⎥ + ⎣x_ref_0⎦ + + list[5]: ⎡ 0 ⎤ + ⎢ ⎥ + ⎣x_ref_1⎦ + """ + + for c in range(geometry.dimensions): + if c not in self.fields: + self.fields[c] = self._create_field(self._field_name(c)) + + vertex_dof_indices = micro_element_to_vertex_indices( + geometry, element_type, element_index + ) + + vertex_dof_indices = [vertex_dof_indices[i] for i in element_vertex_ordering] + + vertex_array_indices = [ + dof_idx.array_index(geometry, indexing_info) + for dof_idx in vertex_dof_indices + ] + + return [ + self.fields[c].absolute_access((idx,), (0,)) + for c in range(geometry.dimensions) + for idx in vertex_array_indices + ] + + def func_type_string(self) -> str: + return f"P1VectorFunction< {self.type_descriptor.pystencils_type} >" + + def includes(self) -> Set[str]: + return {f"hyteg/p1functionspace/P1VectorFunction.hpp"} + + def dof_transformation( + self, + geometry: ElementGeometry, + element_index: Tuple[int, int, int], + element_type: Union[FaceType, CellType], + element_vertex_ordering: List[int], + ) -> Tuple[CustomCodeNode, sp.MatrixBase]: + return ( + CustomCodeNode("", [], []), + sp.Identity(self.fe_space.num_dofs(geometry)), + ) diff --git a/hog/operator_generation/function_space_implementations/p2_space_impl.py b/hog/operator_generation/function_space_implementations/p2_space_impl.py new file mode 100644 index 0000000..f56bca5 --- /dev/null +++ b/hog/operator_generation/function_space_implementations/p2_space_impl.py @@ -0,0 +1,344 @@ +# HyTeG Operator Generator +# Copyright (C) 2024 HyTeG Team +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <https://www.gnu.org/licenses/>. + + +import hog.operator_generation.function_space_impls + + +class P2FunctionSpaceImpl(FunctionSpaceImpl): + def __init__( + self, + fe_space: FunctionSpace, + name: str, + type_descriptor: HOGType, + is_pointer: bool = False, + ): + super().__init__(fe_space, name, type_descriptor, is_pointer) + self.f_vertex = self._create_field(name + "Vertex") + self.f_edge = self._create_field(name + "Edge") + + def pre_communication(self, dim: int) -> str: + if dim == 2: + return f"communication::syncFunctionBetweenPrimitives( {self.name}, level, communication::syncDirection_t::LOW2HIGH );" + else: + return ( + f"{self._deref()}.communicate< Face, Cell >( level );\n" + f"{self._deref()}.communicate< Edge, Cell >( level );\n" + f"{self._deref()}.communicate< Vertex, Cell >( level );" + ) + + def zero_halos(self, dim: int) -> str: + if dim == 2: + return ( + f"for ( const auto& idx : vertexdof::macroface::Iterator( level ) )\n" + f"{{\n" + f" if ( vertexdof::macroface::isVertexOnBoundary( level, idx ) )\n" + f" {{\n" + f" auto arrayIdx = vertexdof::macroface::index( level, idx.x(), idx.y() );\n" + f" _data_{self.name }Vertex[arrayIdx] = walberla::numeric_cast< {self.type_descriptor.pystencils_type} >( 0 );\n" + f" }}\n" + f"}}\n" + f"for ( const auto& idx : edgedof::macroface::Iterator( level ) )\n" + f"{{\n" + f" for ( const auto& orientation : edgedof::faceLocalEdgeDoFOrientations )\n" + f" {{\n" + f" if ( !edgedof::macroface::isInnerEdgeDoF( level, idx, orientation ) )\n" + f" {{\n" + f" auto arrayIdx = edgedof::macroface::index( level, idx.x(), idx.y(), orientation );\n" + f" _data_{self.name }Edge[arrayIdx] = walberla::numeric_cast< {self.type_descriptor.pystencils_type} >( 0 );\n" + f" }}\n" + f" }}\n" + f"}}" + ) + else: + return ( + f"for ( const auto& idx : vertexdof::macrocell::Iterator( level ) )\n" + f"{{\n" + f"if ( !vertexdof::macrocell::isOnCellFace( idx, level ).empty() )\n" + f" {{\n" + f" auto arrayIdx = vertexdof::macrocell::index( level, idx.x(), idx.y(), idx.z() );\n" + f" _data_{self.name}Vertex[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" + f" }}\n" + f"}}\n" + f"edgedof::macrocell::setBoundaryToZero( level, cell, {self._deref()}.getEdgeDoFFunction().getCellDataID() );" + ) + + def post_communication( + self, dim: int, params: str, transform_basis: bool = True + ) -> str: + if dim == 2: + return ( + f"{self._deref()}.getVertexDoFFunction().communicateAdditively< Face, Edge >( {params} );\n" + f"{self._deref()}.getVertexDoFFunction().communicateAdditively< Face, Vertex >( {params} );\n" + f"{self._deref()}.getEdgeDoFFunction().communicateAdditively< Face, Edge >( {params} );" + ) + else: + return ( + f"{self._deref()}.getVertexDoFFunction().communicateAdditively< Cell, Face >( {params} );\n" + f"{self._deref()}.getVertexDoFFunction().communicateAdditively< Cell, Edge >( {params} );\n" + f"{self._deref()}.getVertexDoFFunction().communicateAdditively< Cell, Vertex >( {params} );\n" + f"{self._deref()}.getEdgeDoFFunction().communicateAdditively< Cell, Face >( {params} );\n" + f"{self._deref()}.getEdgeDoFFunction().communicateAdditively< Cell, Edge >( {params} );" + ) + + def pointer_retrieval(self, dim: int) -> str: + """C++ code for retrieving pointers to the numerical data stored in the macro primitives `face` (2d) or `cell` (3d).""" + Macro = {2: "Face", 3: "Cell"}[dim] + macro = {2: "face", 3: "cell"}[dim] + + return ( + f"{self.type_descriptor.pystencils_type}* _data_{self.name}Vertex = {macro}.getData( {self._deref()}.getVertexDoFFunction().get{Macro}DataID() )->getPointer( level );\n" + f"{self.type_descriptor.pystencils_type}* _data_{self.name}Edge = {macro}.getData( {self._deref()}.getEdgeDoFFunction().get{Macro}DataID() )->getPointer( level );" + ) + + def invert_elementwise(self, dim: int) -> str: + return f"{self._deref()}.invertElementwise( level );" + + def local_dofs( + self, + geometry: ElementGeometry, + element_index: Tuple[int, int, int], + element_type: Union[FaceType, CellType], + indexing_info: IndexingInfo, + element_vertex_ordering: List[int], + ) -> List[Field.Access]: + vertex_dof_indices = micro_element_to_vertex_indices( + geometry, element_type, element_index + ) + + vertex_dof_indices = [vertex_dof_indices[i] for i in element_vertex_ordering] + + edge_dof_indices = micro_vertex_to_edge_indices(geometry, vertex_dof_indices) + + vrtx_array_idcs = [ + dof_idx.array_index(geometry, indexing_info) + for dof_idx in vertex_dof_indices + ] + edge_array_idcs = [ + dof_idx.array_index(geometry, indexing_info) for dof_idx in edge_dof_indices + ] + + # NOTE: The order of DoFs must match the order of shape functions + # defined in the FunctionSpace. Holds within the individual lists + # and overall. + return [ + self.f_vertex.absolute_access((idx,), (0,)) for idx in vrtx_array_idcs + ] + [self.f_edge.absolute_access((idx,), (0,)) for idx in edge_array_idcs] + + def func_type_string(self) -> str: + return f"P2Function< {self.type_descriptor.pystencils_type} >" + + def includes(self) -> Set[str]: + return {f"hyteg/p2functionspace/P2Function.hpp"} + + def dof_transformation( + self, + geometry: ElementGeometry, + element_index: Tuple[int, int, int], + element_type: Union[FaceType, CellType], + element_vertex_ordering: List[int], + ) -> Tuple[CustomCodeNode, sp.MatrixBase]: + return ( + CustomCodeNode("", [], []), + sp.Identity(self.fe_space.num_dofs(geometry)), + ) + + +class P2VectorFunctionSpaceImpl(FunctionSpaceImpl): + def __init__( + self, + fe_space: FunctionSpace, + name: str, + type_descriptor: HOGType, + is_pointer: bool = False, + ): + super().__init__(fe_space, name, type_descriptor, is_pointer) + self.fields: Dict[Tuple[str, int], Field] = {} + + def _field_name(self, component: int, dof_type: str) -> str: + """dof_type should either be 'vertex' or 'edge'""" + return self.name + f"_{dof_type}_{component}" + + def _raw_pointer_name(self, component: int, dof_type: str) -> str: + """dof_type should either be 'vertex' or 'edge'""" + return f"_data_" + self._field_name(component, dof_type) + + def pre_communication(self, dim: int) -> str: + if dim == 2: + return f"communication::syncVectorFunctionBetweenPrimitives( {self.name}, level, communication::syncDirection_t::LOW2HIGH );" + else: + ret_str = "" + for i in range(dim): + ret_str += ( + f"{self._deref()}[{i}].communicate< Face, Cell >( level );\n" + f"{self._deref()}[{i}].communicate< Edge, Cell >( level );\n" + f"{self._deref()}[{i}].communicate< Vertex, Cell >( level );" + ) + return ret_str + + def zero_halos(self, dim: int) -> str: + if dim == 2: + return ( + f"for ( const auto& idx : vertexdof::macroface::Iterator( level ) )\n" + f"{{\n" + f" if ( vertexdof::macroface::isVertexOnBoundary( level, idx ) )\n" + f" {{\n" + f" auto arrayIdx = vertexdof::macroface::index( level, idx.x(), idx.y() );\n" + f" {self._raw_pointer_name(0, 'vertex')}[arrayIdx] = walberla::numeric_cast< {self.type_descriptor.pystencils_type} >( 0 );\n" + f" {self._raw_pointer_name(1, 'vertex')}[arrayIdx] = walberla::numeric_cast< {self.type_descriptor.pystencils_type} >( 0 );\n" + f" }}\n" + f"}}\n" + f"for ( const auto& idx : edgedof::macroface::Iterator( level ) )\n" + f"{{\n" + f" for ( const auto& orientation : edgedof::faceLocalEdgeDoFOrientations )\n" + f" {{\n" + f" if ( !edgedof::macroface::isInnerEdgeDoF( level, idx, orientation ) )\n" + f" {{\n" + f" auto arrayIdx = edgedof::macroface::index( level, idx.x(), idx.y(), orientation );\n" + f" {self._raw_pointer_name(0, 'edge')}[arrayIdx] = walberla::numeric_cast< {self.type_descriptor.pystencils_type} >( 0 );\n" + f" {self._raw_pointer_name(1, 'edge')}[arrayIdx] = walberla::numeric_cast< {self.type_descriptor.pystencils_type} >( 0 );\n" + f" }}\n" + f" }}\n" + f"}}" + ) + else: + return ( + f"for ( const auto& idx : vertexdof::macrocell::Iterator( level ) )\n" + f"{{\n" + f"if ( !vertexdof::macrocell::isOnCellFace( idx, level ).empty() )\n" + f" {{\n" + f" auto arrayIdx = vertexdof::macrocell::index( level, idx.x(), idx.y(), idx.z() );\n" + f" {self._raw_pointer_name(0, 'vertex')}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" + f" {self._raw_pointer_name(1, 'vertex')}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" + f" {self._raw_pointer_name(2, 'vertex')}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" + f" }}\n" + f"}}\n" + f"edgedof::macrocell::setBoundaryToZero( level, cell, {self._deref()}[0].getEdgeDoFFunction().getCellDataID() );\n" + f"edgedof::macrocell::setBoundaryToZero( level, cell, {self._deref()}[1].getEdgeDoFFunction().getCellDataID() );\n" + f"edgedof::macrocell::setBoundaryToZero( level, cell, {self._deref()}[2].getEdgeDoFFunction().getCellDataID() );\n" + ) + + def post_communication( + self, dim: int, params: str, transform_basis: bool = True + ) -> str: + if dim == 2: + ret_str = "" + for i in range(dim): + ret_str += ( + f"{self._deref()}[{i}].getVertexDoFFunction().communicateAdditively< Face, Edge >( {params} );\n" + f"{self._deref()}[{i}].getVertexDoFFunction().communicateAdditively< Face, Vertex >( {params} );\n" + f"{self._deref()}[{i}].getEdgeDoFFunction().communicateAdditively< Face, Edge >( {params} );" + ) + return ret_str + else: + ret_str = "" + for i in range(dim): + ret_str += ( + f"{self._deref()}[{i}].getVertexDoFFunction().communicateAdditively< Cell, Face >( {params} );\n" + f"{self._deref()}[{i}].getVertexDoFFunction().communicateAdditively< Cell, Edge >( {params} );\n" + f"{self._deref()}[{i}].getVertexDoFFunction().communicateAdditively< Cell, Vertex >( {params} );\n" + f"{self._deref()}[{i}].getEdgeDoFFunction().communicateAdditively< Cell, Face >( {params} );\n" + f"{self._deref()}[{i}].getEdgeDoFFunction().communicateAdditively< Cell, Edge >( {params} );" + ) + return ret_str + + def pointer_retrieval(self, dim: int) -> str: + """C++ code for retrieving pointers to the numerical data stored in the macro primitives `face` (2d) or `cell` (3d).""" + Macro = {2: "Face", 3: "Cell"}[dim] + macro = {2: "face", 3: "cell"}[dim] + + ret_str = "" + for i in range(dim): + ret_str += f"{self.type_descriptor.pystencils_type}* {self._raw_pointer_name(i, 'vertex')} = {macro}.getData( {self._deref()}[{i}].getVertexDoFFunction().get{Macro}DataID() )->getPointer( level );\n" + ret_str += f"{self.type_descriptor.pystencils_type}* {self._raw_pointer_name(i, 'edge')} = {macro}.getData( {self._deref()}[{i}].getEdgeDoFFunction().get{Macro}DataID() )->getPointer( level );\n" + return ret_str + + def invert_elementwise(self, dim: int) -> str: + ret_str = "" + for i in range(dim): + ret_str += f"{self._deref()}[{i}].invertElementwise( level );\n" + return ret_str + + def local_dofs( + self, + geometry: ElementGeometry, + element_index: Tuple[int, int, int], + element_type: Union[FaceType, CellType], + indexing_info: IndexingInfo, + element_vertex_ordering: List[int], + ) -> List[Field.Access]: + """ + Returns the element-local DoFs (field accesses) in a list (i.e., linearized). + + See P1VectorFunctionSpaceImpl::local_dofs() for details. + """ + + for dt in ["vertex", "edge"]: + for c in range(geometry.dimensions): + if (dt, c) not in self.fields: + self.fields[(dt, c)] = self._create_field(self._field_name(c, dt)) + + vertex_dof_indices = micro_element_to_vertex_indices( + geometry, element_type, element_index + ) + + vertex_dof_indices = [vertex_dof_indices[i] for i in element_vertex_ordering] + + edge_dof_indices = micro_vertex_to_edge_indices(geometry, vertex_dof_indices) + + vertex_array_indices = [ + dof_idx.array_index(geometry, indexing_info) + for dof_idx in vertex_dof_indices + ] + + edge_array_indices = [ + dof_idx.array_index(geometry, indexing_info) for dof_idx in edge_dof_indices + ] + + loc_dofs = [] + + for c in range(geometry.dimensions): + loc_dofs += [ + self.fields[("vertex", c)].absolute_access((idx,), (0,)) + for idx in vertex_array_indices + ] + loc_dofs += [ + self.fields[("edge", c)].absolute_access((idx,), (0,)) + for idx in edge_array_indices + ] + + return loc_dofs + + def func_type_string(self) -> str: + return f"P2VectorFunction< {self.type_descriptor.pystencils_type} >" + + def includes(self) -> Set[str]: + return { + f"hyteg/p2functionspace/P2VectorFunction.hpp", + f"hyteg/p2functionspace/P2Function.hpp", + } + + def dof_transformation( + self, + geometry: ElementGeometry, + element_index: Tuple[int, int, int], + element_type: Union[FaceType, CellType], + element_vertex_ordering: List[int], + ) -> Tuple[CustomCodeNode, sp.MatrixBase]: + return ( + CustomCodeNode("", [], []), + sp.Identity(self.fe_space.num_dofs(geometry)), + ) diff --git a/hog/operator_generation/function_space_impls.py b/hog/operator_generation/function_space_impls.py index 956001b..67ebd43 100644 --- a/hog/operator_generation/function_space_impls.py +++ b/hog/operator_generation/function_space_impls.py @@ -95,6 +95,11 @@ class FunctionSpaceImpl(ABC): """ impl_class: type + import hog.operator_generation.function_space_implementations.p0_space_impl + import hog.operator_generation.function_space_implementations.p1_space_impl + import hog.operator_generation.function_space_implementations.p2_space_impl + import hog.operator_generation.function_space_implementations.n1e1_space_impl + if isinstance(func_space, LagrangianFunctionSpace): if func_space.degree == 2: impl_class = P2FunctionSpaceImpl @@ -244,810 +249,3 @@ class FunctionSpaceImpl(ABC): return f"(*{self.name})" else: return self.name - -class P0FunctionSpaceImpl(FunctionSpaceImpl): - def __init__( - self, - fe_space: FunctionSpace, - name: str, - type_descriptor: HOGType, - is_pointer: bool = False, - ): - super().__init__(fe_space, name, type_descriptor, is_pointer) - self.field = self._create_field(name) - - def pre_communication(self, dim: int) -> str: - return "" - - def zero_halos(self, dim: int) -> str: - return "" - - def post_communication( - self, dim: int, params: str, transform_basis: bool = True - ) -> str: - return "" - - def pointer_retrieval(self, dim: int) -> str: - """C++ code for retrieving pointers to the numerical data stored in the macro primitives `face` (2d) or `cell` (3d).""" - Macro = {2: "Face", 3: "Cell"}[dim] - macro = {2: "face", 3: "cell"}[dim] - - return f"{self.type_descriptor.pystencils_type}* _data_{self.name} = {self._deref()}.getDGFunction()->volumeDoFFunction()->dofMemory( it.first, level );" - - def invert_elementwise(self, dim: int) -> str: - return f"{self._deref()}.invertElementwise( level );" - - def local_dofs( - self, - geometry: ElementGeometry, - element_index: Tuple[int, int, int], - element_type: Union[FaceType, CellType], - indexing_info: IndexingInfo, - element_vertex_ordering: List[int], - ) -> List[Field.Access]: - volume_dof_indices = micro_element_to_volume_indices( - element_type, element_index, 1, VolumeDoFMemoryLayout.SoA - ) - - vertex_array_indices = [ - dof_idx.array_index(geometry, indexing_info) - for dof_idx in volume_dof_indices - ] - - return [ - self.field.absolute_access((idx,), (0,)) for idx in vertex_array_indices - ] - - def func_type_string(self) -> str: - return f"P0Function< {self.type_descriptor.pystencils_type} >" - - def includes(self) -> Set[str]: - return {f"hyteg/p0functionspace/P0Function.hpp"} - - def dof_transformation( - self, - geometry: ElementGeometry, - element_index: Tuple[int, int, int], - element_type: Union[FaceType, CellType], - element_vertex_ordering: List[int], - ) -> Tuple[CustomCodeNode, sp.MatrixBase]: - return ( - CustomCodeNode("", [], []), - sp.Identity(self.fe_space.num_dofs(geometry)), - ) - -class P1FunctionSpaceImpl(FunctionSpaceImpl): - def __init__( - self, - fe_space: FunctionSpace, - name: str, - type_descriptor: HOGType, - is_pointer: bool = False, - ): - super().__init__(fe_space, name, type_descriptor, is_pointer) - self.field = self._create_field(name) - - def pre_communication(self, dim: int) -> str: - if dim == 2: - return f"communication::syncFunctionBetweenPrimitives( {self.name}, level, communication::syncDirection_t::LOW2HIGH );" - else: - return ( - f"{self._deref()}.communicate< Face, Cell >( level );\n" - f"{self._deref()}.communicate< Edge, Cell >( level );\n" - f"{self._deref()}.communicate< Vertex, Cell >( level );" - ) - - def zero_halos(self, dim: int) -> str: - if dim == 2: - return ( - f"for ( const auto& idx : vertexdof::macroface::Iterator( level ) ) {{\n" - f" if ( vertexdof::macroface::isVertexOnBoundary( level, idx ) ) {{\n" - f" auto arrayIdx = vertexdof::macroface::index( level, idx.x(), idx.y() );\n" - f" _data_{self.name}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" - f" }}\n" - f"}}" - ) - else: - return ( - f"for ( const auto& idx : vertexdof::macrocell::Iterator( level ) ) {{\n" - f" if ( !vertexdof::macrocell::isOnCellFace( idx, level ).empty() ) {{\n" - f" auto arrayIdx = vertexdof::macrocell::index( level, idx.x(), idx.y(), idx.z() );\n" - f" _data_{self.name}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" - f" }}\n" - f"}}" - ) - - def post_communication( - self, dim: int, params: str, transform_basis: bool = True - ) -> str: - if dim == 2: - return ( - f"{self._deref()}.communicateAdditively < Face, Edge > ( {params} );\n" - f"{self._deref()}.communicateAdditively < Face, Vertex > ( {params} );" - ) - else: - return ( - f"{self._deref()}.communicateAdditively< Cell, Face >( {params} );\n" - f"{self._deref()}.communicateAdditively< Cell, Edge >( {params} );\n" - f"{self._deref()}.communicateAdditively< Cell, Vertex >( {params} );" - ) - - def pointer_retrieval(self, dim: int) -> str: - """C++ code for retrieving pointers to the numerical data stored in the macro primitives `face` (2d) or `cell` (3d).""" - Macro = {2: "Face", 3: "Cell"}[dim] - macro = {2: "face", 3: "cell"}[dim] - - return f"{self.type_descriptor.pystencils_type}* _data_{self.name} = {macro}.getData( {self._deref()}.get{Macro}DataID() )->getPointer( level );" - - def invert_elementwise(self, dim: int) -> str: - return f"{self._deref()}.invertElementwise( level );" - - def local_dofs( - self, - geometry: ElementGeometry, - element_index: Tuple[int, int, int], - element_type: Union[FaceType, CellType], - indexing_info: IndexingInfo, - element_vertex_ordering: List[int], - ) -> List[Field.Access]: - vertex_dof_indices = micro_element_to_vertex_indices( - geometry, element_type, element_index - ) - - vertex_dof_indices = [vertex_dof_indices[i] for i in element_vertex_ordering] - - vertex_array_indices = [ - dof_idx.array_index(geometry, indexing_info) - for dof_idx in vertex_dof_indices - ] - - return [ - self.field.absolute_access((idx,), (0,)) for idx in vertex_array_indices - ] - - def func_type_string(self) -> str: - return f"P1Function< {self.type_descriptor.pystencils_type} >" - - def includes(self) -> Set[str]: - return {f"hyteg/p1functionspace/P1Function.hpp"} - - def dof_transformation( - self, - geometry: ElementGeometry, - element_index: Tuple[int, int, int], - element_type: Union[FaceType, CellType], - element_vertex_ordering: List[int], - ) -> Tuple[CustomCodeNode, sp.MatrixBase]: - return ( - CustomCodeNode("", [], []), - sp.Identity(self.fe_space.num_dofs(geometry)), - ) - - -class P2FunctionSpaceImpl(FunctionSpaceImpl): - def __init__( - self, - fe_space: FunctionSpace, - name: str, - type_descriptor: HOGType, - is_pointer: bool = False, - ): - super().__init__(fe_space, name, type_descriptor, is_pointer) - self.f_vertex = self._create_field(name + "Vertex") - self.f_edge = self._create_field(name + "Edge") - - def pre_communication(self, dim: int) -> str: - if dim == 2: - return f"communication::syncFunctionBetweenPrimitives( {self.name}, level, communication::syncDirection_t::LOW2HIGH );" - else: - return ( - f"{self._deref()}.communicate< Face, Cell >( level );\n" - f"{self._deref()}.communicate< Edge, Cell >( level );\n" - f"{self._deref()}.communicate< Vertex, Cell >( level );" - ) - - def zero_halos(self, dim: int) -> str: - if dim == 2: - return ( - f"for ( const auto& idx : vertexdof::macroface::Iterator( level ) )\n" - f"{{\n" - f" if ( vertexdof::macroface::isVertexOnBoundary( level, idx ) )\n" - f" {{\n" - f" auto arrayIdx = vertexdof::macroface::index( level, idx.x(), idx.y() );\n" - f" _data_{self.name }Vertex[arrayIdx] = walberla::numeric_cast< {self.type_descriptor.pystencils_type} >( 0 );\n" - f" }}\n" - f"}}\n" - f"for ( const auto& idx : edgedof::macroface::Iterator( level ) )\n" - f"{{\n" - f" for ( const auto& orientation : edgedof::faceLocalEdgeDoFOrientations )\n" - f" {{\n" - f" if ( !edgedof::macroface::isInnerEdgeDoF( level, idx, orientation ) )\n" - f" {{\n" - f" auto arrayIdx = edgedof::macroface::index( level, idx.x(), idx.y(), orientation );\n" - f" _data_{self.name }Edge[arrayIdx] = walberla::numeric_cast< {self.type_descriptor.pystencils_type} >( 0 );\n" - f" }}\n" - f" }}\n" - f"}}" - ) - else: - return ( - f"for ( const auto& idx : vertexdof::macrocell::Iterator( level ) )\n" - f"{{\n" - f"if ( !vertexdof::macrocell::isOnCellFace( idx, level ).empty() )\n" - f" {{\n" - f" auto arrayIdx = vertexdof::macrocell::index( level, idx.x(), idx.y(), idx.z() );\n" - f" _data_{self.name}Vertex[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" - f" }}\n" - f"}}\n" - f"edgedof::macrocell::setBoundaryToZero( level, cell, {self._deref()}.getEdgeDoFFunction().getCellDataID() );" - ) - - def post_communication( - self, dim: int, params: str, transform_basis: bool = True - ) -> str: - if dim == 2: - return ( - f"{self._deref()}.getVertexDoFFunction().communicateAdditively< Face, Edge >( {params} );\n" - f"{self._deref()}.getVertexDoFFunction().communicateAdditively< Face, Vertex >( {params} );\n" - f"{self._deref()}.getEdgeDoFFunction().communicateAdditively< Face, Edge >( {params} );" - ) - else: - return ( - f"{self._deref()}.getVertexDoFFunction().communicateAdditively< Cell, Face >( {params} );\n" - f"{self._deref()}.getVertexDoFFunction().communicateAdditively< Cell, Edge >( {params} );\n" - f"{self._deref()}.getVertexDoFFunction().communicateAdditively< Cell, Vertex >( {params} );\n" - f"{self._deref()}.getEdgeDoFFunction().communicateAdditively< Cell, Face >( {params} );\n" - f"{self._deref()}.getEdgeDoFFunction().communicateAdditively< Cell, Edge >( {params} );" - ) - - def pointer_retrieval(self, dim: int) -> str: - """C++ code for retrieving pointers to the numerical data stored in the macro primitives `face` (2d) or `cell` (3d).""" - Macro = {2: "Face", 3: "Cell"}[dim] - macro = {2: "face", 3: "cell"}[dim] - - return ( - f"{self.type_descriptor.pystencils_type}* _data_{self.name}Vertex = {macro}.getData( {self._deref()}.getVertexDoFFunction().get{Macro}DataID() )->getPointer( level );\n" - f"{self.type_descriptor.pystencils_type}* _data_{self.name}Edge = {macro}.getData( {self._deref()}.getEdgeDoFFunction().get{Macro}DataID() )->getPointer( level );" - ) - - def invert_elementwise(self, dim: int) -> str: - return f"{self._deref()}.invertElementwise( level );" - - def local_dofs( - self, - geometry: ElementGeometry, - element_index: Tuple[int, int, int], - element_type: Union[FaceType, CellType], - indexing_info: IndexingInfo, - element_vertex_ordering: List[int], - ) -> List[Field.Access]: - vertex_dof_indices = micro_element_to_vertex_indices( - geometry, element_type, element_index - ) - - vertex_dof_indices = [vertex_dof_indices[i] for i in element_vertex_ordering] - - edge_dof_indices = micro_vertex_to_edge_indices(geometry, vertex_dof_indices) - - vrtx_array_idcs = [ - dof_idx.array_index(geometry, indexing_info) - for dof_idx in vertex_dof_indices - ] - edge_array_idcs = [ - dof_idx.array_index(geometry, indexing_info) for dof_idx in edge_dof_indices - ] - - # NOTE: The order of DoFs must match the order of shape functions - # defined in the FunctionSpace. Holds within the individual lists - # and overall. - return [ - self.f_vertex.absolute_access((idx,), (0,)) for idx in vrtx_array_idcs - ] + [self.f_edge.absolute_access((idx,), (0,)) for idx in edge_array_idcs] - - def func_type_string(self) -> str: - return f"P2Function< {self.type_descriptor.pystencils_type} >" - - def includes(self) -> Set[str]: - return {f"hyteg/p2functionspace/P2Function.hpp"} - - def dof_transformation( - self, - geometry: ElementGeometry, - element_index: Tuple[int, int, int], - element_type: Union[FaceType, CellType], - element_vertex_ordering: List[int], - ) -> Tuple[CustomCodeNode, sp.MatrixBase]: - return ( - CustomCodeNode("", [], []), - sp.Identity(self.fe_space.num_dofs(geometry)), - ) - - -class P1VectorFunctionSpaceImpl(FunctionSpaceImpl): - def __init__( - self, - fe_space: FunctionSpace, - name: str, - type_descriptor: HOGType, - is_pointer: bool = False, - ): - super().__init__(fe_space, name, type_descriptor, is_pointer) - self.fields: Dict[int, Field] = {} - - def _field_name(self, component: int) -> str: - return self.name + f"_{component}" - - def _raw_pointer_name(self, component: int) -> str: - return f"_data_" + self._field_name(component) - - def pre_communication(self, dim: int) -> str: - if dim == 2: - return f"communication::syncVectorFunctionBetweenPrimitives( {self.name}, level, communication::syncDirection_t::LOW2HIGH );" - else: - ret_str = "" - for i in range(dim): - ret_str += ( - f"{self._deref()}[{i}].communicate< Face, Cell >( level );\n" - f"{self._deref()}[{i}].communicate< Edge, Cell >( level );\n" - f"{self._deref()}[{i}].communicate< Vertex, Cell >( level );" - ) - return ret_str - - def zero_halos(self, dim: int) -> str: - if dim == 2: - return ( - f"for ( const auto& idx : vertexdof::macroface::Iterator( level ) ) {{\n" - f" if ( vertexdof::macroface::isVertexOnBoundary( level, idx ) ) {{\n" - f" auto arrayIdx = vertexdof::macroface::index( level, idx.x(), idx.y() );\n" - f" {self._raw_pointer_name(0)}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" - f" {self._raw_pointer_name(1)}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" - f" }}\n" - f"}}" - ) - else: - return ( - f"for ( const auto& idx : vertexdof::macrocell::Iterator( level ) ) {{\n" - f" if ( !vertexdof::macrocell::isOnCellFace( idx, level ).empty() ) {{\n" - f" auto arrayIdx = vertexdof::macrocell::index( level, idx.x(), idx.y(), idx.z() );\n" - f" {self._raw_pointer_name(0)}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" - f" {self._raw_pointer_name(1)}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" - f" {self._raw_pointer_name(2)}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" - f" }}\n" - f"}}" - ) - - def post_communication( - self, dim: int, params: str, transform_basis: bool = True - ) -> str: - if dim == 2: - ret_str = "" - for i in range(dim): - ret_str += ( - f"{self._deref()}[{i}].communicateAdditively < Face, Edge > ( {params} );\n" - f"{self._deref()}[{i}].communicateAdditively < Face, Vertex > ( {params} );\n" - ) - return ret_str - else: - ret_str = "" - for i in range(dim): - ret_str += ( - f"{self._deref()}[{i}].communicateAdditively< Cell, Face >( {params} );\n" - f"{self._deref()}[{i}].communicateAdditively< Cell, Edge >( {params} );\n" - f"{self._deref()}[{i}].communicateAdditively< Cell, Vertex >( {params} );" - ) - return ret_str - - def pointer_retrieval(self, dim: int) -> str: - """C++ code for retrieving pointers to the numerical data stored in the macro primitives `face` (2d) or `cell` (3d).""" - Macro = {2: "Face", 3: "Cell"}[dim] - macro = {2: "face", 3: "cell"}[dim] - - ret_str = "" - for i in range(dim): - ret_str += f"{self.type_descriptor.pystencils_type}* {self._raw_pointer_name(i)} = {macro}.getData( {self._deref()}[{i}].get{Macro}DataID() )->getPointer( level );\n" - return ret_str - - def invert_elementwise(self, dim: int) -> str: - ret_str = "" - for i in range(dim): - ret_str += f"{self._deref()}[{i}].invertElementwise( level );\n" - return ret_str - - def local_dofs( - self, - geometry: ElementGeometry, - element_index: Tuple[int, int, int], - element_type: Union[FaceType, CellType], - indexing_info: IndexingInfo, - element_vertex_ordering: List[int], - ) -> List[Field.Access]: - """ - Returns the element-local DoFs (field accesses) in a list (i.e., linearized). - - The order here has to match that of the function space implementation. - TODO: This is a little concerning since that order has to match in two very different parts of this - implementation. Here, and in the TensorialVectorFunctionSpace. Maybe we can define this order in a single - location. - We return them in "SoA" order: first all DoFs corresponding to the function with the first component != 0, - then all DoFs corresponding to the function with the second component != 0 etc. - - For instance, in 2D we get the DoFs corresponding to the 6 shape functions in the following order: - - First component != 0 - - list[0]: ⎡-x_ref_0 - x_ref_1 + 1⎤ - ⎢ ⎥ - ⎣ 0 ⎦ - - list[1]: ⎡x_ref_0⎤ - ⎢ ⎥ - ⎣ 0 ⎦ - - list[2]: ⎡x_ref_1⎤ - ⎢ ⎥ - ⎣ 0 ⎦ - - Second component != 0 - - list[3]: ⎡ 0 ⎤ - ⎢ ⎥ - ⎣-x_ref_0 - x_ref_1 + 1⎦ - - list[4]: ⎡ 0 ⎤ - ⎢ ⎥ - ⎣x_ref_0⎦ - - list[5]: ⎡ 0 ⎤ - ⎢ ⎥ - ⎣x_ref_1⎦ - """ - - for c in range(geometry.dimensions): - if c not in self.fields: - self.fields[c] = self._create_field(self._field_name(c)) - - vertex_dof_indices = micro_element_to_vertex_indices( - geometry, element_type, element_index - ) - - vertex_dof_indices = [vertex_dof_indices[i] for i in element_vertex_ordering] - - vertex_array_indices = [ - dof_idx.array_index(geometry, indexing_info) - for dof_idx in vertex_dof_indices - ] - - return [ - self.fields[c].absolute_access((idx,), (0,)) - for c in range(geometry.dimensions) - for idx in vertex_array_indices - ] - - def func_type_string(self) -> str: - return f"P1VectorFunction< {self.type_descriptor.pystencils_type} >" - - def includes(self) -> Set[str]: - return {f"hyteg/p1functionspace/P1VectorFunction.hpp"} - - def dof_transformation( - self, - geometry: ElementGeometry, - element_index: Tuple[int, int, int], - element_type: Union[FaceType, CellType], - element_vertex_ordering: List[int], - ) -> Tuple[CustomCodeNode, sp.MatrixBase]: - return ( - CustomCodeNode("", [], []), - sp.Identity(self.fe_space.num_dofs(geometry)), - ) - - -class P2VectorFunctionSpaceImpl(FunctionSpaceImpl): - def __init__( - self, - fe_space: FunctionSpace, - name: str, - type_descriptor: HOGType, - is_pointer: bool = False, - ): - super().__init__(fe_space, name, type_descriptor, is_pointer) - self.fields: Dict[Tuple[str, int], Field] = {} - - def _field_name(self, component: int, dof_type: str) -> str: - """dof_type should either be 'vertex' or 'edge'""" - return self.name + f"_{dof_type}_{component}" - - def _raw_pointer_name(self, component: int, dof_type: str) -> str: - """dof_type should either be 'vertex' or 'edge'""" - return f"_data_" + self._field_name(component, dof_type) - - def pre_communication(self, dim: int) -> str: - if dim == 2: - return f"communication::syncVectorFunctionBetweenPrimitives( {self.name}, level, communication::syncDirection_t::LOW2HIGH );" - else: - ret_str = "" - for i in range(dim): - ret_str += ( - f"{self._deref()}[{i}].communicate< Face, Cell >( level );\n" - f"{self._deref()}[{i}].communicate< Edge, Cell >( level );\n" - f"{self._deref()}[{i}].communicate< Vertex, Cell >( level );" - ) - return ret_str - - def zero_halos(self, dim: int) -> str: - if dim == 2: - return ( - f"for ( const auto& idx : vertexdof::macroface::Iterator( level ) )\n" - f"{{\n" - f" if ( vertexdof::macroface::isVertexOnBoundary( level, idx ) )\n" - f" {{\n" - f" auto arrayIdx = vertexdof::macroface::index( level, idx.x(), idx.y() );\n" - f" {self._raw_pointer_name(0, 'vertex')}[arrayIdx] = walberla::numeric_cast< {self.type_descriptor.pystencils_type} >( 0 );\n" - f" {self._raw_pointer_name(1, 'vertex')}[arrayIdx] = walberla::numeric_cast< {self.type_descriptor.pystencils_type} >( 0 );\n" - f" }}\n" - f"}}\n" - f"for ( const auto& idx : edgedof::macroface::Iterator( level ) )\n" - f"{{\n" - f" for ( const auto& orientation : edgedof::faceLocalEdgeDoFOrientations )\n" - f" {{\n" - f" if ( !edgedof::macroface::isInnerEdgeDoF( level, idx, orientation ) )\n" - f" {{\n" - f" auto arrayIdx = edgedof::macroface::index( level, idx.x(), idx.y(), orientation );\n" - f" {self._raw_pointer_name(0, 'edge')}[arrayIdx] = walberla::numeric_cast< {self.type_descriptor.pystencils_type} >( 0 );\n" - f" {self._raw_pointer_name(1, 'edge')}[arrayIdx] = walberla::numeric_cast< {self.type_descriptor.pystencils_type} >( 0 );\n" - f" }}\n" - f" }}\n" - f"}}" - ) - else: - return ( - f"for ( const auto& idx : vertexdof::macrocell::Iterator( level ) )\n" - f"{{\n" - f"if ( !vertexdof::macrocell::isOnCellFace( idx, level ).empty() )\n" - f" {{\n" - f" auto arrayIdx = vertexdof::macrocell::index( level, idx.x(), idx.y(), idx.z() );\n" - f" {self._raw_pointer_name(0, 'vertex')}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" - f" {self._raw_pointer_name(1, 'vertex')}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" - f" {self._raw_pointer_name(2, 'vertex')}[arrayIdx] = {self.type_descriptor.pystencils_type}( 0 );\n" - f" }}\n" - f"}}\n" - f"edgedof::macrocell::setBoundaryToZero( level, cell, {self._deref()}[0].getEdgeDoFFunction().getCellDataID() );\n" - f"edgedof::macrocell::setBoundaryToZero( level, cell, {self._deref()}[1].getEdgeDoFFunction().getCellDataID() );\n" - f"edgedof::macrocell::setBoundaryToZero( level, cell, {self._deref()}[2].getEdgeDoFFunction().getCellDataID() );\n" - ) - - def post_communication( - self, dim: int, params: str, transform_basis: bool = True - ) -> str: - if dim == 2: - ret_str = "" - for i in range(dim): - ret_str += ( - f"{self._deref()}[{i}].getVertexDoFFunction().communicateAdditively< Face, Edge >( {params} );\n" - f"{self._deref()}[{i}].getVertexDoFFunction().communicateAdditively< Face, Vertex >( {params} );\n" - f"{self._deref()}[{i}].getEdgeDoFFunction().communicateAdditively< Face, Edge >( {params} );" - ) - return ret_str - else: - ret_str = "" - for i in range(dim): - ret_str += ( - f"{self._deref()}[{i}].getVertexDoFFunction().communicateAdditively< Cell, Face >( {params} );\n" - f"{self._deref()}[{i}].getVertexDoFFunction().communicateAdditively< Cell, Edge >( {params} );\n" - f"{self._deref()}[{i}].getVertexDoFFunction().communicateAdditively< Cell, Vertex >( {params} );\n" - f"{self._deref()}[{i}].getEdgeDoFFunction().communicateAdditively< Cell, Face >( {params} );\n" - f"{self._deref()}[{i}].getEdgeDoFFunction().communicateAdditively< Cell, Edge >( {params} );" - ) - return ret_str - - def pointer_retrieval(self, dim: int) -> str: - """C++ code for retrieving pointers to the numerical data stored in the macro primitives `face` (2d) or `cell` (3d).""" - Macro = {2: "Face", 3: "Cell"}[dim] - macro = {2: "face", 3: "cell"}[dim] - - ret_str = "" - for i in range(dim): - ret_str += f"{self.type_descriptor.pystencils_type}* {self._raw_pointer_name(i, 'vertex')} = {macro}.getData( {self._deref()}[{i}].getVertexDoFFunction().get{Macro}DataID() )->getPointer( level );\n" - ret_str += f"{self.type_descriptor.pystencils_type}* {self._raw_pointer_name(i, 'edge')} = {macro}.getData( {self._deref()}[{i}].getEdgeDoFFunction().get{Macro}DataID() )->getPointer( level );\n" - return ret_str - - def invert_elementwise(self, dim: int) -> str: - ret_str = "" - for i in range(dim): - ret_str += f"{self._deref()}[{i}].invertElementwise( level );\n" - return ret_str - - def local_dofs( - self, - geometry: ElementGeometry, - element_index: Tuple[int, int, int], - element_type: Union[FaceType, CellType], - indexing_info: IndexingInfo, - element_vertex_ordering: List[int], - ) -> List[Field.Access]: - """ - Returns the element-local DoFs (field accesses) in a list (i.e., linearized). - - See P1VectorFunctionSpaceImpl::local_dofs() for details. - """ - - for dt in ["vertex", "edge"]: - for c in range(geometry.dimensions): - if (dt, c) not in self.fields: - self.fields[(dt, c)] = self._create_field(self._field_name(c, dt)) - - vertex_dof_indices = micro_element_to_vertex_indices( - geometry, element_type, element_index - ) - - vertex_dof_indices = [vertex_dof_indices[i] for i in element_vertex_ordering] - - edge_dof_indices = micro_vertex_to_edge_indices(geometry, vertex_dof_indices) - - vertex_array_indices = [ - dof_idx.array_index(geometry, indexing_info) - for dof_idx in vertex_dof_indices - ] - - edge_array_indices = [ - dof_idx.array_index(geometry, indexing_info) for dof_idx in edge_dof_indices - ] - - loc_dofs = [] - - for c in range(geometry.dimensions): - loc_dofs += [ - self.fields[("vertex", c)].absolute_access((idx,), (0,)) - for idx in vertex_array_indices - ] - loc_dofs += [ - self.fields[("edge", c)].absolute_access((idx,), (0,)) - for idx in edge_array_indices - ] - - return loc_dofs - - def func_type_string(self) -> str: - return f"P2VectorFunction< {self.type_descriptor.pystencils_type} >" - - def includes(self) -> Set[str]: - return { - f"hyteg/p2functionspace/P2VectorFunction.hpp", - f"hyteg/p2functionspace/P2Function.hpp", - } - - def dof_transformation( - self, - geometry: ElementGeometry, - element_index: Tuple[int, int, int], - element_type: Union[FaceType, CellType], - element_vertex_ordering: List[int], - ) -> Tuple[CustomCodeNode, sp.MatrixBase]: - return ( - CustomCodeNode("", [], []), - sp.Identity(self.fe_space.num_dofs(geometry)), - ) - - -class N1E1FunctionSpaceImpl(FunctionSpaceImpl): - def __init__( - self, - fe_space: FunctionSpace, - name: str, - type_descriptor: HOGType, - is_pointer: bool = False, - ): - super().__init__(fe_space, name, type_descriptor, is_pointer) - self.field = self._create_field(name) - - def pre_communication(self, dim: int) -> str: - if dim == 2: - return "" - else: - return ( - f"{self._deref()}.communicate< Face, Cell >( level );\n" - f"{self._deref()}.communicate< Edge, Cell >( level );" - ) - - def zero_halos(self, dim: int) -> str: - if dim == 2: - return "" - else: - return f"edgedof::macrocell::setBoundaryToZero( level, cell, {self._deref()}.getDoFs()->getCellDataID() );" - - def post_communication( - self, dim: int, params: str, transform_basis: bool = True - ) -> str: - if dim == 2: - return "" - else: - dofs = "" - if not transform_basis: - dofs = "getDoFs()->" - return ( - f"{self._deref()}.{dofs}communicateAdditively< Cell, Face >( {params} );\n" - f"{self._deref()}.{dofs}communicateAdditively< Cell, Edge >( {params} );" - ) - - def pointer_retrieval(self, dim: int) -> str: - """C++ code for retrieving pointers to the numerical data stored in the macro primitives `face` (2d) or `cell` (3d).""" - Macro = {2: "Face", 3: "Cell"}[dim] - macro = {2: "face", 3: "cell"}[dim] - - return f"{self.type_descriptor.pystencils_type}* _data_{self.name} = {macro}.getData( {self._deref()}.getDoFs()->get{Macro}DataID() )->getPointer( level );" - - def invert_elementwise(self, dim: int) -> str: - return f"{self._deref()}.getDoFs()->invertElementwise( level );" - - def local_dofs( - self, - geometry: ElementGeometry, - element_index: Tuple[int, int, int], - element_type: Union[FaceType, CellType], - indexing_info: IndexingInfo, - element_vertex_ordering: List[int], - ) -> List[Field.Access]: - vertex_dof_indices = micro_element_to_vertex_indices( - geometry, element_type, element_index - ) - - vertex_dof_indices = [vertex_dof_indices[i] for i in element_vertex_ordering] - - edge_dof_indices = micro_vertex_to_edge_indices(geometry, vertex_dof_indices) - edge_array_indices = [ - dof_idx.array_index(geometry, indexing_info) for dof_idx in edge_dof_indices - ] - - return [self.field.absolute_access((idx,), (0,)) for idx in edge_array_indices] - - def func_type_string(self) -> str: - return f"n1e1::N1E1VectorFunction< {self.type_descriptor.pystencils_type} >" - - def includes(self) -> Set[str]: - return { - "hyteg/n1e1functionspace/N1E1VectorFunction.hpp", - "hyteg/n1e1functionspace/N1E1MacroCell.hpp", - } - - def dof_transformation( - self, - geometry: ElementGeometry, - element_index: Tuple[int, int, int], - element_type: Union[FaceType, CellType], - element_vertex_ordering: List[int], - ) -> Tuple[CustomCodeNode, sp.MatrixBase]: - - if element_vertex_ordering != [0, 1, 2, 3]: - raise HOGException( - "Element vertex re-ordering not supported for Nédélec elements (yet)." - ) - - Macro = {2: "Face", 3: "Cell"}[geometry.dimensions] - macro = {2: "face", 3: "cell"}[geometry.dimensions] - name = "basisTransformation" - n_dofs = self.fe_space.num_dofs(geometry) - - # NOTE: Types are added manually because pystencils won't add types to accessed/ - # defined symbols of custom code nodes. As a result the symbols - # in the matrix will be typed but not their definition, leading to - # undefined symbols. - # NOTE: The type is `real_t` (not `self._type_descriptor.pystencils_type`) - # because this function is implemented manually in HyTeG with - # this signature. Passing `np.float64` is not ideal (if `real_t != - # double`) but it makes sure that casts are inserted if necessary - # (though some might be superfluous). - symbols = [ - ps.TypedSymbol(f"{name}.diagonal()({i})", np.float64) for i in range(n_dofs) - ] - return ( - CustomCodeNode( - f"const Eigen::DiagonalMatrix< real_t, {n_dofs} > {name} = " - f"n1e1::macro{macro}::basisTransformation( level, {macro}, {{{element_index[0]}, {element_index[1]}, {element_index[2]}}}, {macro}dof::{Macro}Type::{element_type.name} );", - [ - ps.TypedSymbol("level", "const uint_t"), - ps.TypedSymbol(f"{macro}", f"const {Macro}&"), - ], - symbols, - ), - sp.diag(*symbols), - ) -- GitLab