From b9761a523330c72bdd07b4b6a1c6d14cac53b65c Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Tue, 16 Jul 2024 11:50:30 -0700 Subject: [PATCH 01/15] Bug fix where packages would not be found in the src of geos-mesh, hence leading to error when creating documentation --- geos-mesh/pyproject.toml | 3 +++ geos-mesh/src/geos/mesh/doctor/__init__.py | 1 + 2 files changed, 4 insertions(+) create mode 100644 geos-mesh/src/geos/mesh/doctor/__init__.py diff --git a/geos-mesh/pyproject.toml b/geos-mesh/pyproject.toml index a15ceba0..cc9c0812 100644 --- a/geos-mesh/pyproject.toml +++ b/geos-mesh/pyproject.toml @@ -51,3 +51,6 @@ warn_unused_configs = true ignore_missing_imports = true allow_redefinition = true plugins = "numpy.typing.mypy_plugin" + +[tool.setuptools.packages.find] +where = ["src"] \ No newline at end of file diff --git a/geos-mesh/src/geos/mesh/doctor/__init__.py b/geos-mesh/src/geos/mesh/doctor/__init__.py new file mode 100644 index 00000000..b1cfe267 --- /dev/null +++ b/geos-mesh/src/geos/mesh/doctor/__init__.py @@ -0,0 +1 @@ +# Empty \ No newline at end of file From 8b2819935e6415e978c9f41bf76378893d0a3d2c Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Tue, 23 Jul 2024 11:50:40 -0700 Subject: [PATCH 02/15] Invalid import module path for abaqus_converter --- geos-mesh/src/geos/mesh/conversion/main.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/geos-mesh/src/geos/mesh/conversion/main.py b/geos-mesh/src/geos/mesh/conversion/main.py index 90b048fe..9579f323 100644 --- a/geos-mesh/src/geos/mesh/conversion/main.py +++ b/geos-mesh/src/geos/mesh/conversion/main.py @@ -1,6 +1,7 @@ import argparse import logging import sys +from geos.mesh.conversion import abaqus_converter def build_abaqus_converter_input_parser() -> argparse.ArgumentParser: @@ -25,8 +26,6 @@ def main() -> None: output (str): Output mesh file name -v/--verbose (flag): Increase verbosity level """ - from geosx_mesh_tools import abaqus_converter - # Parse the user arguments parser = build_abaqus_converter_input_parser() args = parser.parse_args() From 6713bb3e1be1d1b010382b85272fb1753067b038 Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Tue, 23 Jul 2024 11:52:49 -0700 Subject: [PATCH 03/15] Argparse module not able to access packages when creating the documentation. Argparse filename used instead. --- docs/geos-ats.rst | 2 +- docs/geos-mesh.rst | 24 ++++++++++++------------ docs/geos-xml-tools.rst | 8 ++++---- geos-mesh/pyproject.toml | 3 --- 4 files changed, 17 insertions(+), 20 deletions(-) diff --git a/docs/geos-ats.rst b/docs/geos-ats.rst index 2327707d..02cae084 100644 --- a/docs/geos-ats.rst +++ b/docs/geos-ats.rst @@ -13,7 +13,7 @@ run_geos_ats Primary entry point for running integrated tests. .. argparse:: - :module: geos.ats.command_line_parsers + :filename: ..\geos-ats\src\geos\ats\command_line_parsers.py :func: build_command_line_parser :prog: run_geos_ats diff --git a/docs/geos-mesh.rst b/docs/geos-mesh.rst index 8582f106..ea960059 100644 --- a/docs/geos-mesh.rst +++ b/docs/geos-mesh.rst @@ -16,13 +16,13 @@ Modules To list all the modules available through ``mesh-doctor``, you can simply use the ``--help`` option, which will list all available modules as well as a quick summary. .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py --help - :cwd: ../geos-mesh + :cwd: ..\geos-mesh Then, if you are interested in a specific module, you can ask for its documentation using the ``mesh-doctor module_name --help`` pattern. For example .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py collocated_nodes --help - :cwd: ../geos-mesh + :cwd: ..\geos-mesh ``mesh-doctor`` loads its module dynamically. If a module can't be loaded, ``mesh-doctor`` will proceed and try to load other modules. @@ -45,7 +45,7 @@ Displays the neighboring nodes that are closer to each other than a prescribed t It is not uncommon to define multiple nodes for the exact same position, which will typically be an issue for ``geos`` and should be fixed. .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py collocated_nodes --help - :cwd: ../geos-mesh + :cwd: ..\geos-mesh ``element_volumes`` """"""""""""""""""" @@ -54,7 +54,7 @@ Computes the volumes of all the cells and displays the ones that are below a pre Cells with negative volumes will typically be an issue for ``geos`` and should be fixed. .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py element_volumes --help - :cwd: ../geos-mesh + :cwd: ..\geos-mesh ``fix_elements_orderings`` """""""""""""""""""""""""" @@ -64,7 +64,7 @@ The ``fix_elements_orderings`` module can rearrange the nodes of given types of This can be convenient if you cannot regenerate the mesh. .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py fix_elements_orderings --help - :cwd: ../geos-mesh + :cwd: ..\geos-mesh ``generate_cube`` """"""""""""""""" @@ -74,7 +74,7 @@ It can also generate fields with simple values. This tool can also be useful to generate a trial mesh that will later be refined or customized. .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py generate_cube --help - :cwd: ../geos-mesh + :cwd: ..\geos-mesh ``generate_fractures`` """""""""""""""""""""" @@ -83,7 +83,7 @@ For a conformal fracture to be defined in a mesh, ``geos`` requires the mesh to The ``generate_fractures`` module will split the mesh and generate the multi-block ``vtk`` files. .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py generate_fractures --help - :cwd: ../geos-mesh + :cwd: ..\geos-mesh ``generate_global_ids`` """"""""""""""""""""""" @@ -92,7 +92,7 @@ When running ``geos`` in parallel, `global ids` can be used to refer to data acr The ``generate_global_ids`` can generate `global ids` for the imported ``vtk`` mesh. .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py generate_global_ids --help - :cwd: ../geos-mesh + :cwd: ..\geos-mesh ``non_conformal`` """"""""""""""""" @@ -103,7 +103,7 @@ The angle between two faces can also be precribed. This module can be a bit time consuming. .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py non_conformal --help - :cwd: ../geos-mesh + :cwd: ..\geos-mesh ``self_intersecting_elements`` """""""""""""""""""""""""""""" @@ -112,7 +112,7 @@ Some meshes can have cells that auto-intersect. This module will display the elements that have faces intersecting. .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py self_intersecting_elements --help - :cwd: ../geos-mesh + :cwd: ..\geos-mesh ``supported_elements`` """""""""""""""""""""" @@ -126,7 +126,7 @@ The ``supported_elements`` check will validate that no unsupported element is in It will also verify that the ``VTK_POLYHEDRON`` cells can effectively get converted into a supported type of element. .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py supported_elements --help - :cwd: ../geos-mesh + :cwd: ..\geos-mesh @@ -144,7 +144,7 @@ convert_abaqus Compile an xml file with advanced features into a single file that can be read by GEOS. .. argparse:: - :module: geos.mesh.conversion.main + :filename: ..\geos-mesh\src\geos\mesh\conversion\main.py :func: build_abaqus_converter_input_parser :prog: convert_abaqus diff --git a/docs/geos-xml-tools.rst b/docs/geos-xml-tools.rst index 72ef2a13..c6b0951d 100644 --- a/docs/geos-xml-tools.rst +++ b/docs/geos-xml-tools.rst @@ -15,7 +15,7 @@ convert_abaqus Convert an abaqus format mesh file to gmsh or vtk format. .. argparse:: - :module: geos.xml_tools.command_line_parsers + :filename: ..\geos-xml-tools\src\geos\xml_tools\command_line_parsers.py :func: build_preprocessor_input_parser :prog: preprocess_xml @@ -26,7 +26,7 @@ format_xml Formats an xml file. .. argparse:: - :module: geos.xml_tools.command_line_parsers + :filename: ..\geos-xml-tools\src\geos\xml_tools\command_line_parsers.py :func: build_xml_formatter_input_parser :prog: format_xml @@ -37,7 +37,7 @@ check_xml_attribute_coverage Checks xml attribute coverage for files in the GEOS repository. .. argparse:: - :module: geos.xml_tools.command_line_parsers + :filename: ..\geos-xml-tools\src\geos\xml_tools\command_line_parsers.py :func: build_attribute_coverage_input_parser :prog: check_xml_attribute_coverage @@ -48,7 +48,7 @@ check_xml_redundancy Checks for redundant attribute definitions in an xml file, such as those that duplicate the default value. .. argparse:: - :module: geos.xml_tools.command_line_parsers + :filename: ..\geos-xml-tools\src\geos\xml_tools\command_line_parsers.py :func: build_xml_redundancy_input_parser :prog: check_xml_redundancy diff --git a/geos-mesh/pyproject.toml b/geos-mesh/pyproject.toml index cc9c0812..a15ceba0 100644 --- a/geos-mesh/pyproject.toml +++ b/geos-mesh/pyproject.toml @@ -51,6 +51,3 @@ warn_unused_configs = true ignore_missing_imports = true allow_redefinition = true plugins = "numpy.typing.mypy_plugin" - -[tool.setuptools.packages.find] -where = ["src"] \ No newline at end of file From f6c103c5b513d6ca0dbc0623eab690d3c1a02e0d Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Tue, 23 Jul 2024 14:42:59 -0700 Subject: [PATCH 04/15] Updating sphinx and sphinx-argparse solved the issue. --- docs/geos-ats.rst | 2 +- docs/geos-mesh.rst | 2 +- docs/geos-xml-tools.rst | 8 ++++---- docs/requirements.txt | 3 ++- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/geos-ats.rst b/docs/geos-ats.rst index 02cae084..2327707d 100644 --- a/docs/geos-ats.rst +++ b/docs/geos-ats.rst @@ -13,7 +13,7 @@ run_geos_ats Primary entry point for running integrated tests. .. argparse:: - :filename: ..\geos-ats\src\geos\ats\command_line_parsers.py + :module: geos.ats.command_line_parsers :func: build_command_line_parser :prog: run_geos_ats diff --git a/docs/geos-mesh.rst b/docs/geos-mesh.rst index ea960059..b8a094bc 100644 --- a/docs/geos-mesh.rst +++ b/docs/geos-mesh.rst @@ -144,7 +144,7 @@ convert_abaqus Compile an xml file with advanced features into a single file that can be read by GEOS. .. argparse:: - :filename: ..\geos-mesh\src\geos\mesh\conversion\main.py + :module: geos.mesh.conversion.main :func: build_abaqus_converter_input_parser :prog: convert_abaqus diff --git a/docs/geos-xml-tools.rst b/docs/geos-xml-tools.rst index c6b0951d..72ef2a13 100644 --- a/docs/geos-xml-tools.rst +++ b/docs/geos-xml-tools.rst @@ -15,7 +15,7 @@ convert_abaqus Convert an abaqus format mesh file to gmsh or vtk format. .. argparse:: - :filename: ..\geos-xml-tools\src\geos\xml_tools\command_line_parsers.py + :module: geos.xml_tools.command_line_parsers :func: build_preprocessor_input_parser :prog: preprocess_xml @@ -26,7 +26,7 @@ format_xml Formats an xml file. .. argparse:: - :filename: ..\geos-xml-tools\src\geos\xml_tools\command_line_parsers.py + :module: geos.xml_tools.command_line_parsers :func: build_xml_formatter_input_parser :prog: format_xml @@ -37,7 +37,7 @@ check_xml_attribute_coverage Checks xml attribute coverage for files in the GEOS repository. .. argparse:: - :filename: ..\geos-xml-tools\src\geos\xml_tools\command_line_parsers.py + :module: geos.xml_tools.command_line_parsers :func: build_attribute_coverage_input_parser :prog: check_xml_attribute_coverage @@ -48,7 +48,7 @@ check_xml_redundancy Checks for redundant attribute definitions in an xml file, such as those that duplicate the default value. .. argparse:: - :filename: ..\geos-xml-tools\src\geos\xml_tools\command_line_parsers.py + :module: geos.xml_tools.command_line_parsers :func: build_xml_redundancy_input_parser :prog: check_xml_redundancy diff --git a/docs/requirements.txt b/docs/requirements.txt index 8a23448b..6ecdf80c 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -1,5 +1,6 @@ +sphinx >= 7.4.7 sphinx_rtd_theme -sphinx-argparse +sphinx-argparse >= 0.5.2 sphinx-design # Running CLI programs and capture outputs sphinxcontrib-programoutput>=0.17 From 97c6cc07afbbefc6a5840f09132b2fb3f808008b Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Mon, 29 Jul 2024 08:49:44 -0700 Subject: [PATCH 05/15] Back to Linux convention for paths --- docs/geos-mesh.rst | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/geos-mesh.rst b/docs/geos-mesh.rst index b8a094bc..8582f106 100644 --- a/docs/geos-mesh.rst +++ b/docs/geos-mesh.rst @@ -16,13 +16,13 @@ Modules To list all the modules available through ``mesh-doctor``, you can simply use the ``--help`` option, which will list all available modules as well as a quick summary. .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py --help - :cwd: ..\geos-mesh + :cwd: ../geos-mesh Then, if you are interested in a specific module, you can ask for its documentation using the ``mesh-doctor module_name --help`` pattern. For example .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py collocated_nodes --help - :cwd: ..\geos-mesh + :cwd: ../geos-mesh ``mesh-doctor`` loads its module dynamically. If a module can't be loaded, ``mesh-doctor`` will proceed and try to load other modules. @@ -45,7 +45,7 @@ Displays the neighboring nodes that are closer to each other than a prescribed t It is not uncommon to define multiple nodes for the exact same position, which will typically be an issue for ``geos`` and should be fixed. .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py collocated_nodes --help - :cwd: ..\geos-mesh + :cwd: ../geos-mesh ``element_volumes`` """"""""""""""""""" @@ -54,7 +54,7 @@ Computes the volumes of all the cells and displays the ones that are below a pre Cells with negative volumes will typically be an issue for ``geos`` and should be fixed. .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py element_volumes --help - :cwd: ..\geos-mesh + :cwd: ../geos-mesh ``fix_elements_orderings`` """""""""""""""""""""""""" @@ -64,7 +64,7 @@ The ``fix_elements_orderings`` module can rearrange the nodes of given types of This can be convenient if you cannot regenerate the mesh. .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py fix_elements_orderings --help - :cwd: ..\geos-mesh + :cwd: ../geos-mesh ``generate_cube`` """"""""""""""""" @@ -74,7 +74,7 @@ It can also generate fields with simple values. This tool can also be useful to generate a trial mesh that will later be refined or customized. .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py generate_cube --help - :cwd: ..\geos-mesh + :cwd: ../geos-mesh ``generate_fractures`` """""""""""""""""""""" @@ -83,7 +83,7 @@ For a conformal fracture to be defined in a mesh, ``geos`` requires the mesh to The ``generate_fractures`` module will split the mesh and generate the multi-block ``vtk`` files. .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py generate_fractures --help - :cwd: ..\geos-mesh + :cwd: ../geos-mesh ``generate_global_ids`` """"""""""""""""""""""" @@ -92,7 +92,7 @@ When running ``geos`` in parallel, `global ids` can be used to refer to data acr The ``generate_global_ids`` can generate `global ids` for the imported ``vtk`` mesh. .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py generate_global_ids --help - :cwd: ..\geos-mesh + :cwd: ../geos-mesh ``non_conformal`` """"""""""""""""" @@ -103,7 +103,7 @@ The angle between two faces can also be precribed. This module can be a bit time consuming. .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py non_conformal --help - :cwd: ..\geos-mesh + :cwd: ../geos-mesh ``self_intersecting_elements`` """""""""""""""""""""""""""""" @@ -112,7 +112,7 @@ Some meshes can have cells that auto-intersect. This module will display the elements that have faces intersecting. .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py self_intersecting_elements --help - :cwd: ..\geos-mesh + :cwd: ../geos-mesh ``supported_elements`` """""""""""""""""""""" @@ -126,7 +126,7 @@ The ``supported_elements`` check will validate that no unsupported element is in It will also verify that the ``VTK_POLYHEDRON`` cells can effectively get converted into a supported type of element. .. command-output:: python src/geos/mesh/doctor/mesh_doctor.py supported_elements --help - :cwd: ..\geos-mesh + :cwd: ../geos-mesh From 5a2df8461751485eb927679a36602c89729ffb1b Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Thu, 22 Aug 2024 13:47:30 -0700 Subject: [PATCH 06/15] New reordering taking into account the volumes as a criteria for reordering now + better reordering stats output --- .../doctor/checks/fix_elements_orderings.py | 243 +++++++++++++++--- .../parsing/fix_elements_orderings_parsing.py | 35 ++- 2 files changed, 240 insertions(+), 38 deletions(-) diff --git a/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py b/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py index eb603b4e..da258319 100644 --- a/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py +++ b/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py @@ -1,5 +1,8 @@ from dataclasses import dataclass +from sys import exit +from os import path, access, W_OK import logging +import numpy as np from typing import ( List, Dict, @@ -7,8 +10,19 @@ FrozenSet, ) -from vtkmodules.vtkCommonCore import ( - vtkIdList, ) +from vtkmodules.vtkCommonCore import vtkIdList +from vtkmodules.vtkCommonDataModel import vtkCell +from vtk import vtkMeshQuality +from vtkmodules.vtkCommonDataModel import ( + vtkDataSet, + VTK_HEXAHEDRON, + VTK_TETRA, + VTK_PYRAMID, + VTK_WEDGE, + VTK_VOXEL, + VTK_PENTAGONAL_PRISM, + VTK_HEXAGONAL_PRISM, +) from . import vtk_utils from .vtk_utils import ( @@ -21,43 +35,210 @@ class Options: vtk_output: VtkOutput cell_type_to_ordering: Dict[ int, List[ int ] ] + volume_to_reorder: str @dataclass( frozen=True ) class Result: output: str - unchanged_cell_types: FrozenSet[ int ] + reordering_stats: dict[ str, list[ int ] ] + + +cell_type_names: dict[ int, str ] = { + VTK_HEXAHEDRON: "Hexahedron", + VTK_TETRA: "Tetra", + VTK_PYRAMID: "Pyramid", + VTK_WEDGE: "Wedge", + VTK_VOXEL: "Voxel", + VTK_PENTAGONAL_PRISM: "Pentagonal prism", + VTK_HEXAGONAL_PRISM: "Pentagonal prism", +} + + +def plan_normal_from_3_points( p1: tuple[ float ], p2: tuple[ float ], p3: tuple[ float ] ) -> np.array: + """Calculate the normal vector of the plan created by 3 points. + + Args: + p1 (tuple[ float ]): ( point1_x, point1_y, point1_z ) + p2 (tuple[ float ]): ( point2_x, point2_y, point2_z ) + p3 (tuple[ float ]): ( point3_x, point3_y, point3_z ) + + Returns: + np.array: Normal vector of the plan composed of those 3 points. + """ + try: + for point in [ p1, p2, p3 ]: + assert len( point ) == 3 + except Exception as e: + logging.error( "Points entered are not tuple of 3D coordinates. Dying ..." ) + exit( 1 ) + point1, point2, point3 = [ np.array( p ) for p in [ p1, p2, p3 ] ] + vector1: np.array = point3 - point1 + vector2: np.array = point2 - point1 + plan_normal: np.array = np.cross( vector1, vector2 ) + return plan_normal + + +def get_cell_nodes_coordinates( cell: vtkCell ) -> list[ tuple[ float ] ]: + """Returns the tuple of coordinates for every node of a vtkCell element. + + Args: + cell (vtkCell): A vtkCell. + + Returns: + list[ tuple[ float ] ]: [ ( node0_x, node0_y, node0_z ), ..., ( nodeN_x, nodeN_y, nodeN_z ) ] + """ + nodes_coordinates: list[ tuple[ float ] ] = [] + for v in cell.GetNumberOfPoints(): + nodes_coordinates.append( cell.GetPoints().GetPoint( v ) ) + return nodes_coordinates + + +def get_sign_of_cell_volume( cell: vtkCell ) -> int: + """For a vtkCell, tells if a volume is positive, negative or null based on vtk cell convention for nodes ordering. + DISCLAIMER: only works for VTK_TETRA, VTK_PYRAMID, VTK_WEDGE, VTK_HEXAHEDRON, + VTK_PENTAGONAL_PRISM, VTK_HEXAGONAL_PRISM + + Args: + cell (vtkCell): A vtkCell. + + Returns: + int: 1 if volume > 0.0, -1 elif volume < 0.0, else 0. + """ + cell_type_volume_calculation: dict[ int, any ] = { VTK_TETRA: vtkMeshQuality.TetVolume, + VTK_PYRAMID: vtkMeshQuality.PyramidVolume, + VTK_WEDGE: vtkMeshQuality.WedgeVolume, + VTK_HEXAHEDRON: vtkMeshQuality.HexVolume } + cell_type: int = cell.GetCellType() + if cell_type in cell_type_volume_calculation: + volume: float = cell_type_volume_calculation[ cell_type ]( cell ) + return int( volume / abs(volume) ) if volume != 0 else 0 # needs to return -1, 0 or 1 + elif cell_type in [ VTK_VOXEL, VTK_PENTAGONAL_PRISM, VTK_HEXAGONAL_PRISM ]: + points: list[ tuple[float] ] = get_cell_nodes_coordinates( cell ) + midpoint: int = len( points ) // 2 + plan1_nodes, plan2_nodes = points[ :midpoint ], points[ midpoint: ] + normal_plan1: np.array = plan_normal_from_3_points(plan1_nodes[0], plan1_nodes[1], plan1_nodes[2]) + normal_plan2: np.array = plan_normal_from_3_points(plan2_nodes[0], plan2_nodes[1], plan2_nodes[2]) + dot_product: float = np.dot( normal_plan1, normal_plan2 ) + return int( dot_product / abs(dot_product) ) if dot_product != 0 else 0 # needs to return -1, 0 or 1 + else: + logging.error( f"Invalid cell type number '{cell_type}' encountered. Dying ..." ) + exit( 1 ) + + +def is_cell_to_reorder( cell: vtkCell, volume_to_reorder: str ) -> bool: + """Check if a vtkCell needs to be reordered. + + Args: + cell (vtkCell): A vtkCell. + volume_to_reorder (str): Condition imposed in Options.volume_to_reorder + + Returns: + bool: True if cell needs to be reordered + """ + if volume_to_reorder == "all": + return True + sign_of_cell_volume: int = get_sign_of_cell_volume( cell ) + if volume_to_reorder == "positive" and sign_of_cell_volume == 1: + return True + elif volume_to_reorder == "negative" and sign_of_cell_volume == -1: + return True + return False + + +def get_all_cells_type( mesh: vtkDataSet ) -> np.array: + """Gets the cell type for every cell of a mesh. + + Args: + mesh (vtk grid): A vtk grid. + + Returns: + np.array: ( [ cell0_type, cell1_type, ..., cellN_type ] ) + """ + number_cells: int = mesh.GetNumberOfCells() + all_cells_type: np.array = np.zeros( ( number_cells, 1 ), dtype=int ) + for cell_id in range( number_cells ): + all_cells_type = mesh.GetCellType( cell_id ) + return all_cells_type + + +def get_cell_ids_to_check( all_cells_type: np.array, options: Options ) -> dict[ int, np.array ]: + """For every cell type, returns a numpy array with only the indexes of the cell elements that have the same + type as specified in options. So if the cell type to check was tetrahedron, only the indexes of all + tetrahedrons in the mesh are returned. + + Args: + all_cells_type (np.array): Array of cell types for every cell of a mesh. + options (Options): Options defined by the user. + + Returns: + dict[ int, np.array ]: Indexes in ascending order of every cell being of a certain type. + """ + cell_ids_to_check: dict[ int, np.array ] = {} + for cell_type in options.cell_type_to_ordering.keys(): + sub_array = np.where( all_cells_type == cell_type )[ 0 ] + cell_ids_to_check[ cell_type ] = sub_array + all_cells_type = all_cells_type[ all_cells_type != cell_type ] + return cell_ids_to_check + + +def reorder_nodes_to_new_mesh( old_mesh: vtkDataSet, options: Options ) -> tuple: + """From an old mesh, creates a new one where certain cell nodes are reordered to obtain a correct volume. + + Args: + old_mesh (vtkDataSet): An unstructured grid needing nodes to be reordered. + options (Options): Options defined by the user. + + Returns: + tuple: ( vtkDataSet, reordering_stats ) + """ + # a new mesh with the same data is created from the old mesh + new_mesh: vtkDataSet = old_mesh.NewInstance() + new_mesh.CopyStructure( old_mesh ) + new_mesh.CopyAttributes( old_mesh ) + cells = new_mesh.GetCells() + # this part will get the cell ids that need to be verified and if invalid, to have their nodes be reodered + all_cells_type: np.array = get_all_cells_type( new_mesh ) + cell_ids_to_check: dict[ int, np.array ] = get_cell_ids_to_check( old_mesh, options ) + unique_cell_types, total_per_cell_types = np.unique( all_cells_type, return_counts=True ) + unique_cell_types, total_per_cell_types = unique_cell_types.tolist(), total_per_cell_types.tolist() + unique_cell_types_names: list[ str ] = [ cell_type_names[ i ] for i in unique_cell_types ] + reordering_stats: dict[ str, list[ any ] ] = { + "Types reordered": [], + "Number of cells reordered": [], + "Types non reordered": unique_cell_types_names, + "Number of cells non reordered": total_per_cell_types + } + for cell_type, cell_ids in cell_ids_to_check: + counter_cells_reordered: int = 0 + for cell_id in cell_ids: + cell: vtkCell = new_mesh.GetCell( cell_id ) + if is_cell_to_reorder( cell, options.volume_to_reorder ): + support_point_ids = vtkIdList() + cells.GetCellAtId( cell_id, support_point_ids ) + new_support_point_ids = [] + node_ordering: list[ int ] = options.cell_type_to_ordering[ cell_type ] + for i in range( len( node_ordering ) ): + new_support_point_ids.append( support_point_ids.GetId( node_ordering[ i ] ) ) + cells.ReplaceCellAtId( cell_id, to_vtk_id_list( new_support_point_ids ) ) + counter_cells_reordered += 1 + # for reordering_stats + cell_type_name: str = cell_type_names[ cell_type ] + if counter_cells_reordered > 0: + reordering_stats[ "Types reordered" ].append( cell_type_name ) + reordering_stats[ "Number of cells reordered" ].append( counter_cells_reordered ) + cell_type_position: int = reordering_stats[ "Types non reordered" ].index( cell_type_name ) + reordering_stats[ "Number of cells non reordered" ][ cell_type_position ] -= counter_cells_reordered + if reordering_stats[ "Number of cells non reordered" ][ cell_type_position ] == 0: + reordering_stats[ "Types reordered" ].pop( cell_type_position ) + reordering_stats[ "Number of cells non reordered" ].pop( cell_type_position ) + return ( new_mesh, reordering_stats ) def __check( mesh, options: Options ) -> Result: - # The vtk cell type is an int and will be the key of the following mapping, - # that will point to the relevant permutation. - cell_type_to_ordering: Dict[ int, List[ int ] ] = options.cell_type_to_ordering - unchanged_cell_types: Set[ int ] = set() # For logging purpose - - # Preparing the output mesh by first keeping the same instance type. - output_mesh = mesh.NewInstance() - output_mesh.CopyStructure( mesh ) - output_mesh.CopyAttributes( mesh ) - - # `output_mesh` now contains a full copy of the input mesh. - # We'll now modify the support nodes orderings in place if needed. - cells = output_mesh.GetCells() - for cell_idx in range( output_mesh.GetNumberOfCells() ): - cell_type: int = output_mesh.GetCell( cell_idx ).GetCellType() - new_ordering = cell_type_to_ordering.get( cell_type ) - if new_ordering: - support_point_ids = vtkIdList() - cells.GetCellAtId( cell_idx, support_point_ids ) - new_support_point_ids = [] - for i, v in enumerate( new_ordering ): - new_support_point_ids.append( support_point_ids.GetId( new_ordering[ i ] ) ) - cells.ReplaceCellAtId( cell_idx, to_vtk_id_list( new_support_point_ids ) ) - else: - unchanged_cell_types.add( cell_type ) - is_written_error = vtk_utils.write_mesh( output_mesh, options.vtk_output ) - return Result( output=options.vtk_output.output if not is_written_error else "", - unchanged_cell_types=frozenset( unchanged_cell_types ) ) + output_mesh, reordering_stats = reorder_nodes_to_new_mesh( mesh, options ) + vtk_utils.write_mesh( output_mesh, options.vtk_output ) + return Result( output=options.vtk_output.output, reordering_stats=reordering_stats ) def check( vtk_input_file: str, options: Options ) -> Result: diff --git a/geos-mesh/src/geos/mesh/doctor/parsing/fix_elements_orderings_parsing.py b/geos-mesh/src/geos/mesh/doctor/parsing/fix_elements_orderings_parsing.py index 71fb3a51..8fdaf63d 100644 --- a/geos-mesh/src/geos/mesh/doctor/parsing/fix_elements_orderings_parsing.py +++ b/geos-mesh/src/geos/mesh/doctor/parsing/fix_elements_orderings_parsing.py @@ -10,9 +10,7 @@ VTK_VOXEL, VTK_WEDGE, ) - from geos.mesh.doctor.checks.fix_elements_orderings import Options, Result - from . import vtk_output_parsing, FIX_ELEMENTS_ORDERINGS __CELL_TYPE_MAPPING = { @@ -35,6 +33,10 @@ VTK_WEDGE: 6, } +__VOLUME_TO_REORDER = "volume_to_reorder" +__VOLUME_TO_REORDER_DEFAULT = "all" +__VOLUME_TO_REORDER_CHOICES = [ "all", "positive", "negative" ] + def fill_subparser( subparsers ) -> None: p = subparsers.add_parser( FIX_ELEMENTS_ORDERINGS, help="Reorders the support nodes for the given cell types." ) @@ -47,6 +49,15 @@ def fill_subparser( subparsers ) -> None: default=None, required=False, help=f"[list of integers]: node permutation for \"{key}\"." ) + p.add_argument( '--' + __VOLUME_TO_REORDER, + type=str, + metavar=__VOLUME_TO_REORDER_DEFAULT, + default=__VOLUME_TO_REORDER_DEFAULT, + choices=__VOLUME_TO_REORDER_CHOICES, + required=False, + help= "[str]: Select which element volume is invalid and needs reordering." + + "'all' will allow reordering of nodes for every element, regarding of their volume." + + "'positive' or 'negative' will only reorder the element with the corresponding volume." ) vtk_output_parsing.fill_vtk_output_subparser( p ) @@ -67,15 +78,25 @@ def convert( parsed_options ) -> Options: raise ValueError( err_msg ) cell_type_to_ordering[ vtk_key ] = tmp vtk_output = vtk_output_parsing.convert( parsed_options ) - return Options( vtk_output=vtk_output, cell_type_to_ordering=cell_type_to_ordering ) + volume_to_reorder: str = parsed_options[ __VOLUME_TO_REORDER ] + if volume_to_reorder.lower() not in __VOLUME_TO_REORDER_CHOICES: + raise ValueError( f"Please use one of these options for --volume_to_reorder: {__VOLUME_TO_REORDER_CHOICES}." ) + return Options( vtk_output=vtk_output, cell_type_to_ordering=cell_type_to_ordering, + volume_to_reorder=volume_to_reorder ) def display_results( options: Options, result: Result ): if result.output: logging.info( f"New mesh was written to file '{result.output}'" ) - if result.unchanged_cell_types: - logging.info( f"Those vtk types were not reordered: [{', '.join(map(str, result.unchanged_cell_types))}]." ) - else: - logging.info( "All the cells of the mesh were reordered." ) else: logging.info( "No output file was written." ) + logging.info( f"Number of cells reordered :" ) + logging.info( f"\tCellType\tNumber" ) + for i in range( result.reordering_stats[ "Types reordered" ] ): + logging.info( f"\t{result.reordering_stats[ "Types reordered" ][ i ]}" + + f"\t{result.reordering_stats[ "Number of cells reordered" ][ i ]}" ) + logging.info( f"Number of cells non reordered :" ) + logging.info( f"\tCellType\tNumber" ) + for i in range( result.reordering_stats[ "Types non reordered" ] ): + logging.info( f"\t{result.reordering_stats[ "Types non reordered" ][ i ]}" + + f"\t{result.reordering_stats[ "Number of cells non reordered" ][ i ]}" ) From 1c5251c753e56ac101b255de99c452cbbf903ba1 Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Thu, 22 Aug 2024 14:41:07 -0700 Subject: [PATCH 07/15] Better handling of filepaths when reading and/or creating a new one. --- .../doctor/checks/fix_elements_orderings.py | 4 +- .../src/geos/mesh/doctor/checks/vtk_utils.py | 54 +++++++++++++++---- 2 files changed, 47 insertions(+), 11 deletions(-) diff --git a/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py b/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py index da258319..4aff8527 100644 --- a/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py +++ b/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py @@ -186,7 +186,7 @@ def reorder_nodes_to_new_mesh( old_mesh: vtkDataSet, options: Options ) -> tuple """From an old mesh, creates a new one where certain cell nodes are reordered to obtain a correct volume. Args: - old_mesh (vtkDataSet): An unstructured grid needing nodes to be reordered. + old_mesh (vtkDataSet): A vtk grid needing nodes to be reordered. options (Options): Options defined by the user. Returns: @@ -230,7 +230,7 @@ def reorder_nodes_to_new_mesh( old_mesh: vtkDataSet, options: Options ) -> tuple cell_type_position: int = reordering_stats[ "Types non reordered" ].index( cell_type_name ) reordering_stats[ "Number of cells non reordered" ][ cell_type_position ] -= counter_cells_reordered if reordering_stats[ "Number of cells non reordered" ][ cell_type_position ] == 0: - reordering_stats[ "Types reordered" ].pop( cell_type_position ) + reordering_stats[ "Types non reordered" ].pop( cell_type_position ) reordering_stats[ "Number of cells non reordered" ].pop( cell_type_position ) return ( new_mesh, reordering_stats ) diff --git a/geos-mesh/src/geos/mesh/doctor/checks/vtk_utils.py b/geos-mesh/src/geos/mesh/doctor/checks/vtk_utils.py index 9beb3758..c27872ac 100644 --- a/geos-mesh/src/geos/mesh/doctor/checks/vtk_utils.py +++ b/geos-mesh/src/geos/mesh/doctor/checks/vtk_utils.py @@ -1,7 +1,7 @@ from dataclasses import dataclass -import os.path +from os import path, access, R_OK, W_OK import logging -import sys +from sys import exit from typing import ( Any, Iterator, @@ -50,6 +50,39 @@ def vtk_iter( l ) -> Iterator[ Any ]: yield l.GetCellType( i ) +# Inspired from https://stackoverflow.com/a/78870363 +def is_filepath_valid( filepath: str, is_input=True ) -> bool: + """Checks if a filepath can be used to read or to create a new file. + + Args: + filepath (str): A filepath. + is_input (bool, optional): If the filepath is used to read a file, use True. + If the filepath is used to create a new file, use False. Defaults to True. + + Returns: + bool: False if invalid, True instead. + """ + dirname = path.dirname( filepath ) + if not path.isdir( dirname ): + logging.error( f"The directory '{dirname}' specified does not exist." ) + return False + if is_input: + if not access( dirname, R_OK ): + logging.error( f"You do not have rights to read in directory '{dirname}' specified for the file." ) + return False + if not path.exists( filepath ): + logging.error( f"The file specified does not exist." ) + return False + else: + if not access( dirname, W_OK ): + logging.error( f"You do not have rights to write in directory '{dirname}' specified for the file." ) + return False + if path.exists( filepath ): + logging.error( f"A file with the same name already exists. No over-writing possible." ) + return False + return True + + def __read_vtk( vtk_input_file: str ) -> Optional[ vtkUnstructuredGrid ]: reader = vtkUnstructuredGridReader() logging.info( f"Testing file format \"{vtk_input_file}\" using legacy format reader..." ) @@ -83,7 +116,10 @@ def read_mesh( vtk_input_file: str ) -> vtkUnstructuredGrid: If first guess does not work, eventually all the others reader available will be tested. :return: A unstructured grid. """ - file_extension = os.path.splitext( vtk_input_file )[ -1 ] + if not is_filepath_valid( vtk_input_file, True ): + logging.error( f"Invalid filepath to read. Dying ..." ) + exit( 1 ) + file_extension = path.splitext( vtk_input_file )[ -1 ] extension_to_reader = { ".vtk": __read_vtk, ".vtu": __read_vtu } # Testing first the reader that should match if file_extension in extension_to_reader: @@ -97,7 +133,7 @@ def read_mesh( vtk_input_file: str ) -> vtkUnstructuredGrid: return output_mesh # No reader did work. Dying. logging.critical( f"Could not find the appropriate VTK reader for file \"{vtk_input_file}\". Dying..." ) - sys.exit( 1 ) + exit( 1 ) def __write_vtk( mesh: vtkUnstructuredGrid, output: str ) -> int: @@ -125,10 +161,10 @@ def write_mesh( mesh: vtkUnstructuredGrid, vtk_output: VtkOutput ) -> int: :param vtk_output: Where to write. The file extension will be used to select the VTK file format. :return: 0 in case of success. """ - if os.path.exists( vtk_output.output ): - logging.error( f"File \"{vtk_output.output}\" already exists, nothing done." ) - return 1 - file_extension = os.path.splitext( vtk_output.output )[ -1 ] + if not is_filepath_valid( vtk_output.output, False ): + logging.error( f"Invalid filepath to write. Dying ..." ) + exit( 1 ) + file_extension = path.splitext( vtk_output.output )[ -1 ] if file_extension == ".vtk": success_code = __write_vtk( mesh, vtk_output.output ) elif file_extension == ".vtu": @@ -136,5 +172,5 @@ def write_mesh( mesh: vtkUnstructuredGrid, vtk_output: VtkOutput ) -> int: else: # No writer found did work. Dying. logging.critical( f"Could not find the appropriate VTK writer for extension \"{file_extension}\". Dying..." ) - sys.exit( 1 ) + exit( 1 ) return 0 if success_code else 2 # the Write member function return 1 in case of success, 0 otherwise. From d4f2100c178fccf39adc566eae84406c2a0eafa6 Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Fri, 30 Aug 2024 12:10:41 -0700 Subject: [PATCH 08/15] Correction for certain methods --- .../doctor/checks/fix_elements_orderings.py | 165 +++++------------- 1 file changed, 48 insertions(+), 117 deletions(-) diff --git a/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py b/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py index 4aff8527..9f2e4479 100644 --- a/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py +++ b/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py @@ -1,34 +1,22 @@ from dataclasses import dataclass -from sys import exit -from os import path, access, W_OK import logging import numpy as np from typing import ( List, Dict, - Set, - FrozenSet, ) - +from vtk import vtkCellSizeFilter from vtkmodules.vtkCommonCore import vtkIdList -from vtkmodules.vtkCommonDataModel import vtkCell -from vtk import vtkMeshQuality -from vtkmodules.vtkCommonDataModel import ( - vtkDataSet, - VTK_HEXAHEDRON, - VTK_TETRA, - VTK_PYRAMID, - VTK_WEDGE, - VTK_VOXEL, - VTK_PENTAGONAL_PRISM, - VTK_HEXAGONAL_PRISM, -) - -from . import vtk_utils -from .vtk_utils import ( - to_vtk_id_list, - VtkOutput, -) +from vtkmodules.util.numpy_support import vtk_to_numpy +from vtkmodules.vtkCommonDataModel import ( vtkDataSet, + VTK_HEXAHEDRON, + VTK_TETRA, + VTK_PYRAMID, + VTK_WEDGE, + VTK_VOXEL, + VTK_PENTAGONAL_PRISM, + VTK_HEXAGONAL_PRISM ) +from .vtk_utils import VtkOutput, to_vtk_id_list, write_mesh, read_mesh @dataclass( frozen=True ) @@ -44,93 +32,36 @@ class Result: reordering_stats: dict[ str, list[ int ] ] -cell_type_names: dict[ int, str ] = { - VTK_HEXAHEDRON: "Hexahedron", - VTK_TETRA: "Tetra", - VTK_PYRAMID: "Pyramid", - VTK_WEDGE: "Wedge", - VTK_VOXEL: "Voxel", - VTK_PENTAGONAL_PRISM: "Pentagonal prism", - VTK_HEXAGONAL_PRISM: "Pentagonal prism", -} +cell_type_names: dict[ int, str ] = { VTK_HEXAHEDRON: "Hexahedron", + VTK_TETRA: "Tetra", + VTK_PYRAMID: "Pyramid", + VTK_WEDGE: "Wedge", + VTK_VOXEL: "Voxel", + VTK_PENTAGONAL_PRISM: "Prism5", + VTK_HEXAGONAL_PRISM: "Prism6" } -def plan_normal_from_3_points( p1: tuple[ float ], p2: tuple[ float ], p3: tuple[ float ] ) -> np.array: - """Calculate the normal vector of the plan created by 3 points. +def compute_mesh_cells_volume( mesh: vtkDataSet ) -> np.array: + """Generates a volume array that was calculated on all cells of a mesh. Args: - p1 (tuple[ float ]): ( point1_x, point1_y, point1_z ) - p2 (tuple[ float ]): ( point2_x, point2_y, point2_z ) - p3 (tuple[ float ]): ( point3_x, point3_y, point3_z ) + mesh (vtkDataSet): A vtk grid. Returns: - np.array: Normal vector of the plan composed of those 3 points. + np.array: Volume for every cell of a mesh. """ - try: - for point in [ p1, p2, p3 ]: - assert len( point ) == 3 - except Exception as e: - logging.error( "Points entered are not tuple of 3D coordinates. Dying ..." ) - exit( 1 ) - point1, point2, point3 = [ np.array( p ) for p in [ p1, p2, p3 ] ] - vector1: np.array = point3 - point1 - vector2: np.array = point2 - point1 - plan_normal: np.array = np.cross( vector1, vector2 ) - return plan_normal - - -def get_cell_nodes_coordinates( cell: vtkCell ) -> list[ tuple[ float ] ]: - """Returns the tuple of coordinates for every node of a vtkCell element. + cell_size_filter = vtkCellSizeFilter() + cell_size_filter.SetInputData( mesh ) + cell_size_filter.SetComputeVolume(True) + cell_size_filter.Update() + return vtk_to_numpy( cell_size_filter.GetOutput().GetCellData().GetArray("Volume") ) - Args: - cell (vtkCell): A vtkCell. - - Returns: - list[ tuple[ float ] ]: [ ( node0_x, node0_y, node0_z ), ..., ( nodeN_x, nodeN_y, nodeN_z ) ] - """ - nodes_coordinates: list[ tuple[ float ] ] = [] - for v in cell.GetNumberOfPoints(): - nodes_coordinates.append( cell.GetPoints().GetPoint( v ) ) - return nodes_coordinates - -def get_sign_of_cell_volume( cell: vtkCell ) -> int: - """For a vtkCell, tells if a volume is positive, negative or null based on vtk cell convention for nodes ordering. - DISCLAIMER: only works for VTK_TETRA, VTK_PYRAMID, VTK_WEDGE, VTK_HEXAHEDRON, - VTK_PENTAGONAL_PRISM, VTK_HEXAGONAL_PRISM +def is_cell_to_reorder( cell_volume: float, volume_to_reorder: str ) -> bool: + """Check if the volume of vtkCell qualifies the cell to be reordered. Args: - cell (vtkCell): A vtkCell. - - Returns: - int: 1 if volume > 0.0, -1 elif volume < 0.0, else 0. - """ - cell_type_volume_calculation: dict[ int, any ] = { VTK_TETRA: vtkMeshQuality.TetVolume, - VTK_PYRAMID: vtkMeshQuality.PyramidVolume, - VTK_WEDGE: vtkMeshQuality.WedgeVolume, - VTK_HEXAHEDRON: vtkMeshQuality.HexVolume } - cell_type: int = cell.GetCellType() - if cell_type in cell_type_volume_calculation: - volume: float = cell_type_volume_calculation[ cell_type ]( cell ) - return int( volume / abs(volume) ) if volume != 0 else 0 # needs to return -1, 0 or 1 - elif cell_type in [ VTK_VOXEL, VTK_PENTAGONAL_PRISM, VTK_HEXAGONAL_PRISM ]: - points: list[ tuple[float] ] = get_cell_nodes_coordinates( cell ) - midpoint: int = len( points ) // 2 - plan1_nodes, plan2_nodes = points[ :midpoint ], points[ midpoint: ] - normal_plan1: np.array = plan_normal_from_3_points(plan1_nodes[0], plan1_nodes[1], plan1_nodes[2]) - normal_plan2: np.array = plan_normal_from_3_points(plan2_nodes[0], plan2_nodes[1], plan2_nodes[2]) - dot_product: float = np.dot( normal_plan1, normal_plan2 ) - return int( dot_product / abs(dot_product) ) if dot_product != 0 else 0 # needs to return -1, 0 or 1 - else: - logging.error( f"Invalid cell type number '{cell_type}' encountered. Dying ..." ) - exit( 1 ) - - -def is_cell_to_reorder( cell: vtkCell, volume_to_reorder: str ) -> bool: - """Check if a vtkCell needs to be reordered. - - Args: - cell (vtkCell): A vtkCell. + cell_volume (float): The volume of a vtkCell. volume_to_reorder (str): Condition imposed in Options.volume_to_reorder Returns: @@ -138,7 +69,9 @@ def is_cell_to_reorder( cell: vtkCell, volume_to_reorder: str ) -> bool: """ if volume_to_reorder == "all": return True - sign_of_cell_volume: int = get_sign_of_cell_volume( cell ) + if cell_volume == 0.0: + return True + sign_of_cell_volume: int = int( cell_volume / abs(cell_volume) ) if volume_to_reorder == "positive" and sign_of_cell_volume == 1: return True elif volume_to_reorder == "negative" and sign_of_cell_volume == -1: @@ -156,9 +89,9 @@ def get_all_cells_type( mesh: vtkDataSet ) -> np.array: np.array: ( [ cell0_type, cell1_type, ..., cellN_type ] ) """ number_cells: int = mesh.GetNumberOfCells() - all_cells_type: np.array = np.zeros( ( number_cells, 1 ), dtype=int ) + all_cells_type: np.array = np.ones( number_cells, dtype=int ) for cell_id in range( number_cells ): - all_cells_type = mesh.GetCellType( cell_id ) + all_cells_type[ cell_id ] = mesh.GetCellType( cell_id ) return all_cells_type @@ -175,10 +108,11 @@ def get_cell_ids_to_check( all_cells_type: np.array, options: Options ) -> dict[ dict[ int, np.array ]: Indexes in ascending order of every cell being of a certain type. """ cell_ids_to_check: dict[ int, np.array ] = {} + available_types: list[ int ] = np.unique( all_cells_type ).tolist() for cell_type in options.cell_type_to_ordering.keys(): - sub_array = np.where( all_cells_type == cell_type )[ 0 ] - cell_ids_to_check[ cell_type ] = sub_array - all_cells_type = all_cells_type[ all_cells_type != cell_type ] + if cell_type in available_types: + sub_array = np.where( all_cells_type == cell_type )[ 0 ] + cell_ids_to_check[ cell_type ] = sub_array return cell_ids_to_check @@ -198,22 +132,21 @@ def reorder_nodes_to_new_mesh( old_mesh: vtkDataSet, options: Options ) -> tuple new_mesh.CopyAttributes( old_mesh ) cells = new_mesh.GetCells() # this part will get the cell ids that need to be verified and if invalid, to have their nodes be reodered + all_cells_volume: np.array = compute_mesh_cells_volume( new_mesh ) all_cells_type: np.array = get_all_cells_type( new_mesh ) - cell_ids_to_check: dict[ int, np.array ] = get_cell_ids_to_check( old_mesh, options ) + cell_ids_to_check: dict[ int, np.array ] = get_cell_ids_to_check( all_cells_type, options ) unique_cell_types, total_per_cell_types = np.unique( all_cells_type, return_counts=True ) unique_cell_types, total_per_cell_types = unique_cell_types.tolist(), total_per_cell_types.tolist() - unique_cell_types_names: list[ str ] = [ cell_type_names[ i ] for i in unique_cell_types ] reordering_stats: dict[ str, list[ any ] ] = { "Types reordered": [], "Number of cells reordered": [], - "Types non reordered": unique_cell_types_names, + "Types non reordered": unique_cell_types, "Number of cells non reordered": total_per_cell_types } - for cell_type, cell_ids in cell_ids_to_check: + for cell_type, cell_ids in cell_ids_to_check.items(): counter_cells_reordered: int = 0 for cell_id in cell_ids: - cell: vtkCell = new_mesh.GetCell( cell_id ) - if is_cell_to_reorder( cell, options.volume_to_reorder ): + if is_cell_to_reorder( float( all_cells_volume[ cell_id ] ), options.volume_to_reorder ): support_point_ids = vtkIdList() cells.GetCellAtId( cell_id, support_point_ids ) new_support_point_ids = [] @@ -222,12 +155,10 @@ def reorder_nodes_to_new_mesh( old_mesh: vtkDataSet, options: Options ) -> tuple new_support_point_ids.append( support_point_ids.GetId( node_ordering[ i ] ) ) cells.ReplaceCellAtId( cell_id, to_vtk_id_list( new_support_point_ids ) ) counter_cells_reordered += 1 - # for reordering_stats - cell_type_name: str = cell_type_names[ cell_type ] if counter_cells_reordered > 0: - reordering_stats[ "Types reordered" ].append( cell_type_name ) + reordering_stats[ "Types reordered" ].append( cell_type ) reordering_stats[ "Number of cells reordered" ].append( counter_cells_reordered ) - cell_type_position: int = reordering_stats[ "Types non reordered" ].index( cell_type_name ) + cell_type_position: int = reordering_stats[ "Types non reordered" ].index( cell_type ) reordering_stats[ "Number of cells non reordered" ][ cell_type_position ] -= counter_cells_reordered if reordering_stats[ "Number of cells non reordered" ][ cell_type_position ] == 0: reordering_stats[ "Types non reordered" ].pop( cell_type_position ) @@ -237,10 +168,10 @@ def reorder_nodes_to_new_mesh( old_mesh: vtkDataSet, options: Options ) -> tuple def __check( mesh, options: Options ) -> Result: output_mesh, reordering_stats = reorder_nodes_to_new_mesh( mesh, options ) - vtk_utils.write_mesh( output_mesh, options.vtk_output ) + write_mesh( output_mesh, options.vtk_output ) return Result( output=options.vtk_output.output, reordering_stats=reordering_stats ) def check( vtk_input_file: str, options: Options ) -> Result: - mesh = vtk_utils.read_mesh( vtk_input_file ) + mesh = read_mesh( vtk_input_file ) return __check( mesh, options ) From 953e89058524f06f1926e8f8c8959828e2c797b1 Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Fri, 30 Aug 2024 12:10:51 -0700 Subject: [PATCH 09/15] Test file added --- .../tests/test_fix_elements_orderings.py | 990 ++++++++++++++++++ 1 file changed, 990 insertions(+) create mode 100644 geos-mesh/tests/test_fix_elements_orderings.py diff --git a/geos-mesh/tests/test_fix_elements_orderings.py b/geos-mesh/tests/test_fix_elements_orderings.py new file mode 100644 index 00000000..e93a75a8 --- /dev/null +++ b/geos-mesh/tests/test_fix_elements_orderings.py @@ -0,0 +1,990 @@ +import numpy as np +import logging +from geos.mesh.doctor.checks import fix_elements_orderings as feo +from geos.mesh.doctor.checks.generate_cube import Options, __build +from geos.mesh.doctor.checks.vtk_utils import VtkOutput, to_vtk_id_list +from geos.mesh.doctor.checks.fix_elements_orderings import Options as opt +from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints +from vtkmodules.vtkCommonDataModel import ( vtkDataSet, + vtkUnstructuredGrid, + vtkCellArray, + vtkHexahedron, + vtkTetra, + vtkPyramid, + vtkVoxel, + vtkWedge, + vtkPentagonalPrism, + vtkHexagonalPrism, + VTK_HEXAHEDRON, + VTK_TETRA, + VTK_PYRAMID, + VTK_WEDGE, + VTK_VOXEL, + VTK_PENTAGONAL_PRISM, + VTK_HEXAGONAL_PRISM ) + + +def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int ] ): + """Utility function to reorder the nodes of one cell for test purposes. + + Args: + mesh (vtkDataSet): A vtk grid. + cell_id (int): Cell id to identify the cell which will be modified. + node_ordering (list[ int ]): Nodes id ordering to construct a cell. + """ + if mesh.GetCell( cell_id ).GetNumberOfPoints() != len( node_ordering ): + raise ValueError( f"The cell to reorder needs to have '{mesh.GetCell( cell_id ).GetNumberOfPoints()}'" + + "nodes in reordering." ) + cells = mesh.GetCells() + support_point_ids = vtkIdList() + cells.GetCellAtId( cell_id, support_point_ids ) + new_support_point_ids = [] + node_ordering: list[ int ] = node_ordering + for i in range( len( node_ordering ) ): + new_support_point_ids.append( support_point_ids.GetId( node_ordering[ i ] ) ) + cells.ReplaceCellAtId( cell_id, to_vtk_id_list( new_support_point_ids ) ) + + +""" +Dict used to apply false nodes orderings for test purposes +""" +to_change_order: dict[ int, list[ int ] ] = { VTK_HEXAHEDRON: [ 0, 3, 2, 1, 4, 5, 6, 7 ], + VTK_TETRA: [ 0, 2, 1, 3 ], + VTK_PYRAMID: [ 0, 3, 2, 1, 4 ], + VTK_WEDGE: [ 0, 2, 1, 3, 4, 5 ], + VTK_VOXEL: [ 7, 6, 5, 4, 3, 2, 1, 0 ], + VTK_PENTAGONAL_PRISM: [ 0, 4, 3, 2, 1, 5, 6, 7, 8, 9 ], + VTK_HEXAGONAL_PRISM: [ 0, 1, 4, 2, 3, 5, 11, 10, 9, 8, 7, 6 ] } +to_change_order = dict( sorted( to_change_order.items() ) ) + +""" +1 Hexahedron: no invalid ordering +""" +out: VtkOutput = VtkOutput( "test", False ) +options_one_hex: Options = Options( vtk_output=out, + generate_cells_global_ids=False, + generate_points_global_ids=False, + xs=np.array( [ 0.0, 1.0] ), + ys=np.array( [ 0.0, 1.0] ), + zs=np.array( [ 0.0, 1.0 ] ), + nxs=[ 1 ], + nys=[ 1 ], + nzs=[ 1 ], + fields=[] ) +one_hex: vtkDataSet = __build( options_one_hex ) + +""" +4 Hexahedrons: no invalid ordering +""" +out: VtkOutput = VtkOutput( "test", False ) +options_hexahedrons_grid: Options = Options( vtk_output=out, + generate_cells_global_ids=False, + generate_points_global_ids=False, + xs=np.array( [ 0.0, 1.0, 2.0 ] ), + ys=np.array( [ 0.0, 1.0, 2.0 ] ), + zs=np.array( [ 0.0, 1.0 ] ), + nxs=[ 1, 1 ], + nys=[ 1, 1 ], + nzs=[ 1 ], + fields=[] ) +hexahedrons_grid: vtkDataSet = __build( options_hexahedrons_grid ) + +""" +4 Hexahedrons: 2 Hexahedrons with invalid ordering +""" +hexahedrons_grid_invalid: vtkDataSet = __build( options_hexahedrons_grid ) +for i in range( 2 ): + reorder_cell_nodes( hexahedrons_grid_invalid, i * 2 + 1, to_change_order[ VTK_HEXAHEDRON ] ) + +""" +4 tetrahedrons +""" +points_tetras: vtkPoints = vtkPoints() +points_tetras_coords: list[ tuple[ float ] ] = [ (0.0, 0.0, 0.0), + (1.0, 0.0, 0.0), + (1.0, 1.0, 0.0), + (0.0, 1.0, 0.0), + (0.0, 0.0, 1.0), + (1.0, 0.0, 1.0), + (1.0, 1.0, 1.0), + (0.0, 1.0, 1.0) ] +for point_tetra in points_tetras_coords: + points_tetras.InsertNextPoint( point_tetra ) + +tetra1: vtkTetra = vtkTetra() +tetra1.GetPointIds().SetId( 0, 3 ) +tetra1.GetPointIds().SetId( 1, 0 ) +tetra1.GetPointIds().SetId( 2, 1 ) +tetra1.GetPointIds().SetId( 3, 4 ) + +tetra2: vtkTetra = vtkTetra() +tetra2.GetPointIds().SetId( 0, 6 ) +tetra2.GetPointIds().SetId( 1, 5 ) +tetra2.GetPointIds().SetId( 2, 4 ) +tetra2.GetPointIds().SetId( 3, 1 ) + +tetra3: vtkTetra = vtkTetra() +tetra3.GetPointIds().SetId( 0, 1 ) +tetra3.GetPointIds().SetId( 1, 2 ) +tetra3.GetPointIds().SetId( 2, 3 ) +tetra3.GetPointIds().SetId( 3, 6 ) + +tetra4: vtkTetra = vtkTetra() +tetra4.GetPointIds().SetId( 0, 4 ) +tetra4.GetPointIds().SetId( 1, 7 ) +tetra4.GetPointIds().SetId( 2, 6 ) +tetra4.GetPointIds().SetId( 3, 3 ) + +tetras_cells: vtkCellArray = vtkCellArray() +tetras_cells.InsertNextCell( tetra1 ) +tetras_cells.InsertNextCell( tetra2 ) +tetras_cells.InsertNextCell( tetra3 ) +tetras_cells.InsertNextCell( tetra4 ) + +tetras_grid: vtkUnstructuredGrid = vtkUnstructuredGrid() +tetras_grid.SetPoints( points_tetras ) +tetras_grid.SetCells( VTK_TETRA, tetras_cells ) + +# one of every other wedge has invalid ordering +tetras_grid_invalid = vtkUnstructuredGrid() +tetras_grid_invalid.DeepCopy( tetras_grid ) +for i in range( 2 ): + reorder_cell_nodes( tetras_grid_invalid, i * 2 + 1, to_change_order[ VTK_TETRA ] ) + +""" +4 pyramids +""" +points_pyramids: vtkPoints = vtkPoints() +points_pyramids_coords: list[ tuple[ float ] ] = [ (0.0, 0.0, 0.0), + (1.0, 0.0, 0.0), + (1.0, 1.0, 0.0), + (0.0, 1.0, 0.0), + (0.5, 0.5, 1.0), + (2.0, 0.0, 0.0), + (3.0, 0.0, 0.0), + (3.0, 1.0, 0.0), + (2.0, 1.0, 0.0), + (2.5, 0.5, 1.0), + (0.0, 2.0, 0.0), + (1.0, 2.0, 0.0), + (1.0, 3.0, 0.0), + (0.0, 3.0, 0.0), + (0.5, 2.5, 1.0), + (2.0, 2.0, 0.0), + (3.0, 2.0, 0.0), + (3.0, 3.0, 0.0), + (2.0, 3.0, 0.0), + (2.5, 2.5, 1.0) ] +for point_pyramid in points_pyramids_coords: + points_pyramids.InsertNextPoint( point_pyramid ) + +pyramid1: vtkPyramid = vtkPyramid() +pyramid1.GetPointIds().SetId( 0, 0 ) +pyramid1.GetPointIds().SetId( 1, 1 ) +pyramid1.GetPointIds().SetId( 2, 2 ) +pyramid1.GetPointIds().SetId( 3, 3 ) +pyramid1.GetPointIds().SetId( 4, 4 ) + +pyramid2: vtkPyramid = vtkPyramid() +pyramid2.GetPointIds().SetId( 0, 5 ) +pyramid2.GetPointIds().SetId( 1, 6 ) +pyramid2.GetPointIds().SetId( 2, 7 ) +pyramid2.GetPointIds().SetId( 3, 8 ) +pyramid2.GetPointIds().SetId( 4, 9 ) + +pyramid3: vtkPyramid = vtkPyramid() +pyramid3.GetPointIds().SetId( 0, 10 ) +pyramid3.GetPointIds().SetId( 1, 11 ) +pyramid3.GetPointIds().SetId( 2, 12 ) +pyramid3.GetPointIds().SetId( 3, 13 ) +pyramid3.GetPointIds().SetId( 4, 14 ) + +pyramid4: vtkPyramid = vtkPyramid() +pyramid4.GetPointIds().SetId( 0, 15 ) +pyramid4.GetPointIds().SetId( 1, 16 ) +pyramid4.GetPointIds().SetId( 2, 17 ) +pyramid4.GetPointIds().SetId( 3, 18 ) +pyramid4.GetPointIds().SetId( 4, 19 ) + +pyramids_cells: vtkCellArray = vtkCellArray() +pyramids_cells.InsertNextCell( pyramid1 ) +pyramids_cells.InsertNextCell( pyramid2 ) +pyramids_cells.InsertNextCell( pyramid3 ) +pyramids_cells.InsertNextCell( pyramid4 ) + +pyramids_grid: vtkUnstructuredGrid = vtkUnstructuredGrid() +pyramids_grid.SetPoints( points_pyramids ) +pyramids_grid.SetCells( VTK_PYRAMID, pyramids_cells ) + +# one of every other wedge has invalid ordering +pyramids_grid_invalid = vtkUnstructuredGrid() +pyramids_grid_invalid.DeepCopy( pyramids_grid ) +for i in range( 2 ): + reorder_cell_nodes( pyramids_grid_invalid, i * 2 + 1, to_change_order[ VTK_PYRAMID ] ) + + +""" +4 voxels +""" +points_voxels: vtkPoints = vtkPoints() +points_voxels_coords: list[ tuple[ float ] ] = [ (0.0, 0.0, 0.0), + (1.0, 0.0, 0.0), + (1.0, 1.0, 0.0), + (0.0, 1.0, 0.0), + (0.0, 0.0, 1.0), + (1.0, 0.0, 1.0), + (1.0, 1.0, 1.0), + (0.0, 1.0, 1.0), + (2.0, 0.0, 0.0), + (3.0, 0.0, 0.0), + (3.0, 1.0, 0.0), + (2.0, 1.0, 0.0), + (2.0, 0.0, 1.0), + (3.0, 0.0, 1.0), + (3.0, 1.0, 1.0), + (2.0, 1.0, 1.0), + (0.0, 2.0, 0.0), + (1.0, 2.0, 0.0), + (1.0, 3.0, 0.0), + (0.0, 3.0, 0.0), + (0.0, 2.0, 1.0), + (1.0, 2.0, 1.0), + (1.0, 3.0, 1.0), + (0.0, 3.0, 1.0), + (2.0, 2.0, 0.0), + (3.0, 2.0, 0.0), + (3.0, 3.0, 0.0), + (2.0, 3.0, 0.0), + (2.0, 2.0, 1.0), + (3.0, 2.0, 1.0), + (3.0, 3.0, 1.0), + (2.0, 3.0, 1.0) ] +for point_voxel in points_voxels_coords: + points_voxels.InsertNextPoint( point_voxel ) + +voxel1: vtkVoxel = vtkVoxel() +voxel1.GetPointIds().SetId( 0, 0 ) +voxel1.GetPointIds().SetId( 1, 1 ) +voxel1.GetPointIds().SetId( 2, 2 ) +voxel1.GetPointIds().SetId( 3, 3 ) +voxel1.GetPointIds().SetId( 4, 4 ) +voxel1.GetPointIds().SetId( 5, 5 ) +voxel1.GetPointIds().SetId( 6, 6 ) +voxel1.GetPointIds().SetId( 7, 7 ) + +voxel2: vtkVoxel = vtkVoxel() +voxel2.GetPointIds().SetId( 0, 8 ) +voxel2.GetPointIds().SetId( 1, 9 ) +voxel2.GetPointIds().SetId( 2, 10 ) +voxel2.GetPointIds().SetId( 3, 11 ) +voxel2.GetPointIds().SetId( 4, 12 ) +voxel2.GetPointIds().SetId( 5, 13 ) +voxel2.GetPointIds().SetId( 6, 14 ) +voxel2.GetPointIds().SetId( 7, 15 ) + +voxel3: vtkVoxel = vtkVoxel() +voxel3.GetPointIds().SetId( 0, 16 ) +voxel3.GetPointIds().SetId( 1, 17 ) +voxel3.GetPointIds().SetId( 2, 18 ) +voxel3.GetPointIds().SetId( 3, 19 ) +voxel3.GetPointIds().SetId( 4, 20 ) +voxel3.GetPointIds().SetId( 5, 21 ) +voxel3.GetPointIds().SetId( 6, 22 ) +voxel3.GetPointIds().SetId( 7, 23 ) + +voxel4: vtkVoxel = vtkVoxel() +voxel4.GetPointIds().SetId( 0, 24 ) +voxel4.GetPointIds().SetId( 1, 25 ) +voxel4.GetPointIds().SetId( 2, 26 ) +voxel4.GetPointIds().SetId( 3, 27 ) +voxel4.GetPointIds().SetId( 4, 28 ) +voxel4.GetPointIds().SetId( 5, 29 ) +voxel4.GetPointIds().SetId( 6, 30 ) +voxel4.GetPointIds().SetId( 7, 31 ) + +voxels_cells: vtkCellArray = vtkCellArray() +voxels_cells.InsertNextCell( voxel1 ) +voxels_cells.InsertNextCell( voxel2 ) +voxels_cells.InsertNextCell( voxel3 ) +voxels_cells.InsertNextCell( voxel4 ) + +voxels_grid: vtkUnstructuredGrid = vtkUnstructuredGrid() +voxels_grid.SetPoints( points_voxels ) +voxels_grid.SetCells( VTK_VOXEL, voxels_cells ) + +# one of every other wedge has invalid ordering +voxels_grid_invalid = vtkUnstructuredGrid() +voxels_grid_invalid.DeepCopy( voxels_grid ) +for i in range( 2 ): + reorder_cell_nodes( voxels_grid_invalid, i * 2 + 1, to_change_order[ VTK_VOXEL ] ) + + +""" +4 wedges +""" +points_wedges: vtkPoints = vtkPoints() +points_wedges_coords: list[ tuple[ float ] ] = [ (0.5, 0.0, 0.0), + (1.5, 0.0, 0.0), + (2.5, 0.0, 0.0), + (0.0, 1.0, 0.0), + (1.0, 1.0, 0.0), + (2.0, 1.0, 0.0), + (0.5, 0.0, 1.0), + (1.5, 0.0, 1.0), + (2.5, 0.0, 1.0), + (0.0, 1.0, 1.0), + (1.0, 1.0, 1.0), + (2.0, 1.0, 1.0) ] +for point_wedge in points_wedges_coords: + points_wedges.InsertNextPoint( point_wedge ) + +wedge1: vtkWedge = vtkWedge() +wedge1.GetPointIds().SetId( 0, 9 ) +wedge1.GetPointIds().SetId( 1, 6 ) +wedge1.GetPointIds().SetId( 2, 10 ) +wedge1.GetPointIds().SetId( 3, 3 ) +wedge1.GetPointIds().SetId( 4, 0 ) +wedge1.GetPointIds().SetId( 5, 4 ) + +wedge2: vtkWedge = vtkWedge() +wedge2.GetPointIds().SetId( 0, 7 ) +wedge2.GetPointIds().SetId( 1, 10 ) +wedge2.GetPointIds().SetId( 2, 6 ) +wedge2.GetPointIds().SetId( 3, 1 ) +wedge2.GetPointIds().SetId( 4, 4 ) +wedge2.GetPointIds().SetId( 5, 0 ) + +wedge3: vtkWedge = vtkWedge() +wedge3.GetPointIds().SetId( 0, 10 ) +wedge3.GetPointIds().SetId( 1, 7 ) +wedge3.GetPointIds().SetId( 2, 11 ) +wedge3.GetPointIds().SetId( 3, 4 ) +wedge3.GetPointIds().SetId( 4, 1 ) +wedge3.GetPointIds().SetId( 5, 5 ) + +wedge4: vtkWedge = vtkWedge() +wedge4.GetPointIds().SetId( 0, 8 ) +wedge4.GetPointIds().SetId( 1, 11 ) +wedge4.GetPointIds().SetId( 2, 7 ) +wedge4.GetPointIds().SetId( 3, 2 ) +wedge4.GetPointIds().SetId( 4, 5 ) +wedge4.GetPointIds().SetId( 5, 1 ) + +wedges_cells: vtkCellArray = vtkCellArray() +wedges_cells.InsertNextCell( wedge1 ) +wedges_cells.InsertNextCell( wedge2 ) +wedges_cells.InsertNextCell( wedge3 ) +wedges_cells.InsertNextCell( wedge4 ) + +wedges_grid = vtkUnstructuredGrid() +wedges_grid.SetPoints( points_wedges ) +wedges_grid.SetCells( VTK_WEDGE, wedges_cells ) + +# one of every other wedge has invalid ordering +wedges_grid_invalid = vtkUnstructuredGrid() +wedges_grid_invalid.DeepCopy( wedges_grid ) +for i in range( 2 ): + reorder_cell_nodes( wedges_grid_invalid, i * 2 + 1, to_change_order[ VTK_WEDGE ] ) + + +""" +4 pentagonal prisms +""" +points_penta_prisms: vtkPoints = vtkPoints() +points_penta_prisms_coords: list[ tuple[ float ] ] = [ (0.0, 0.0, 0.0), + (1.0, 0.0, 0.0), + (1.5, 0.5, 0.0), + (0.5, 1.0, 0.0), + (-0.5, 0.5, 0.0), + (0.0, 0.0, 1.0), + (1.0, 0.0, 1.0), + (1.5, 0.5, 1.0), + (0.5, 1.0, 1.0), + (-0.5, 0.5, 1.0), + (2.0, 0.0, 0.0), + (3.0, 0.0, 0.0), + (3.5, 0.5, 0.0), + (2.5, 1.0, 0.0), + (1.5, 0.5, 0.0), + (2.0, 0.0, 1.0), + (3.0, 0.0, 1.0), + (3.5, 0.5, 1.0), + (2.5, 1.0, 1.0), + (1.5, 0.5, 1.0), + (0.0, 2.0, 0.0), + (1.0, 2.0, 0.0), + (1.5, 2.5, 0.0), + (0.5, 3.0, 0.0), + (-0.5, 2.5, 0.0), + (0.0, 2.0, 1.0), + (1.0, 2.0, 1.0), + (1.5, 2.5, 1.0), + (0.5, 3.0, 1.0), + (-0.5, 2.5, 1.0), + (2.0, 2.0, 0.0), + (3.0, 2.0, 0.0), + (3.5, 2.5, 0.0), + (2.5, 3.0, 0.0), + (1.5, 2.5, 0.0), + (2.0, 2.0, 1.0), + (3.0, 2.0, 1.0), + (3.5, 2.5, 1.0), + (2.5, 3.0, 1.0), + (1.5, 2.5, 1.0) ] +for point_penta_prism in points_penta_prisms_coords: + points_penta_prisms.InsertNextPoint( point_penta_prism ) + +penta_prism1: vtkPentagonalPrism = vtkPentagonalPrism() +penta_prism1.GetPointIds().SetId( 0, 0 ) +penta_prism1.GetPointIds().SetId( 1, 1 ) +penta_prism1.GetPointIds().SetId( 2, 2 ) +penta_prism1.GetPointIds().SetId( 3, 3 ) +penta_prism1.GetPointIds().SetId( 4, 4 ) +penta_prism1.GetPointIds().SetId( 5, 5 ) +penta_prism1.GetPointIds().SetId( 6, 6 ) +penta_prism1.GetPointIds().SetId( 7, 7 ) +penta_prism1.GetPointIds().SetId( 8, 8 ) +penta_prism1.GetPointIds().SetId( 9, 9 ) + +penta_prism2: vtkPentagonalPrism = vtkPentagonalPrism() +penta_prism2.GetPointIds().SetId( 0, 10 ) +penta_prism2.GetPointIds().SetId( 1, 11 ) +penta_prism2.GetPointIds().SetId( 2, 12 ) +penta_prism2.GetPointIds().SetId( 3, 13 ) +penta_prism2.GetPointIds().SetId( 4, 14 ) +penta_prism2.GetPointIds().SetId( 5, 15 ) +penta_prism2.GetPointIds().SetId( 6, 16 ) +penta_prism2.GetPointIds().SetId( 7, 17 ) +penta_prism2.GetPointIds().SetId( 8, 18 ) +penta_prism2.GetPointIds().SetId( 9, 19 ) + +penta_prism3: vtkPentagonalPrism = vtkPentagonalPrism() +penta_prism3.GetPointIds().SetId( 0, 20 ) +penta_prism3.GetPointIds().SetId( 1, 21 ) +penta_prism3.GetPointIds().SetId( 2, 22 ) +penta_prism3.GetPointIds().SetId( 3, 23 ) +penta_prism3.GetPointIds().SetId( 4, 24 ) +penta_prism3.GetPointIds().SetId( 5, 25 ) +penta_prism3.GetPointIds().SetId( 6, 26 ) +penta_prism3.GetPointIds().SetId( 7, 27 ) +penta_prism3.GetPointIds().SetId( 8, 28 ) +penta_prism3.GetPointIds().SetId( 9, 29 ) + +penta_prism4: vtkPentagonalPrism = vtkPentagonalPrism() +penta_prism4.GetPointIds().SetId( 0, 30 ) +penta_prism4.GetPointIds().SetId( 1, 31 ) +penta_prism4.GetPointIds().SetId( 2, 32 ) +penta_prism4.GetPointIds().SetId( 3, 33 ) +penta_prism4.GetPointIds().SetId( 4, 34 ) +penta_prism4.GetPointIds().SetId( 5, 35 ) +penta_prism4.GetPointIds().SetId( 6, 36 ) +penta_prism4.GetPointIds().SetId( 7, 37 ) +penta_prism4.GetPointIds().SetId( 8, 38 ) +penta_prism4.GetPointIds().SetId( 9, 39 ) + +penta_prism_cells = vtkCellArray() +penta_prism_cells.InsertNextCell( penta_prism1 ) +penta_prism_cells.InsertNextCell( penta_prism2 ) +penta_prism_cells.InsertNextCell( penta_prism3 ) +penta_prism_cells.InsertNextCell( penta_prism4 ) + +penta_prism_grid = vtkUnstructuredGrid() +penta_prism_grid.SetPoints( points_penta_prisms ) +penta_prism_grid.SetCells( VTK_PENTAGONAL_PRISM, penta_prism_cells ) + +# one of every other pentagonal prism has invalid ordering +penta_prism_grid_invalid = vtkUnstructuredGrid() +penta_prism_grid_invalid.DeepCopy( penta_prism_grid ) +for i in range( 2 ): + reorder_cell_nodes( penta_prism_grid_invalid, i * 2 + 1, to_change_order[ VTK_PENTAGONAL_PRISM ] ) + + +""" +4 hexagonal prisms +""" +points_hexa_prisms: vtkPoints = vtkPoints() +points_hexa_prisms_coords: list[ tuple[ float ] ] = [ (0.0, 0.0, 0.0), + (1.0, 0.0, 0.0), + (1.5, 0.5, 0.0), + (1.0, 1.0, 0.0), + (0.0, 1.0, 0.0), + (-0.5, 0.5, 0.0), + (0.0, 0.0, 1.0), + (1.0, 0.0, 1.0), + (1.5, 0.5, 1.0), + (1.0, 1.0, 1.0), + (0.0, 1.0, 1.0), + (-0.5, 0.5, 1.0), + (2.0, 0.0, 0.0), + (3.0, 0.0, 0.0), + (3.5, 0.5, 0.0), + (3.0, 1.0, 0.0), + (2.0, 1.0, 0.0), + (1.5, 0.5, 0.0), + (2.0, 0.0, 1.0), + (3.0, 0.0, 1.0), + (3.5, 0.5, 1.0), + (3.0, 1.0, 1.0), + (2.0, 1.0, 1.0), + (1.5, 0.5, 1.0), + (0.0, 2.0, 0.0), + (1.0, 2.0, 0.0), + (1.5, 2.5, 0.0), + (1.0, 3.0, 0.0), + (0.0, 3.0, 0.0), + (-0.5, 2.5, 0.0), + (0.0, 2.0, 1.0), + (1.0, 2.0, 1.0), + (1.5, 2.5, 1.0), + (1.0, 3.0, 1.0), + (0.0, 3.0, 1.0), + (-0.5, 2.5, 1.0), + (2.0, 2.0, 0.0), + (3.0, 2.0, 0.0), + (3.5, 2.5, 0.0), + (3.0, 3.0, 0.0), + (2.0, 3.0, 0.0), + (1.5, 2.5, 0.0), + (2.0, 2.0, 1.0), + (3.0, 2.0, 1.0), + (3.5, 2.5, 1.0), + (3.0, 3.0, 1.0), + (2.0, 3.0, 1.0), + (1.5, 2.5, 1.0) ] +for point_hexa_prism in points_hexa_prisms_coords: + points_hexa_prisms.InsertNextPoint( point_hexa_prism ) + +hexa_prism1: vtkHexagonalPrism = vtkHexagonalPrism() +for i in range(12): + hexa_prism1.GetPointIds().SetId( i, i ) + +hexa_prism2: vtkHexagonalPrism = vtkHexagonalPrism() +for i in range(12): + hexa_prism2.GetPointIds().SetId( i, i + 12 ) + +hexa_prism3: vtkHexagonalPrism = vtkHexagonalPrism() +for i in range(12): + hexa_prism3.GetPointIds().SetId( i, i + 24 ) + +hexa_prism4: vtkHexagonalPrism = vtkHexagonalPrism() +for i in range(12): + hexa_prism4.GetPointIds().SetId( i, i + 36 ) + +hexa_prism_cells = vtkCellArray() +hexa_prism_cells.InsertNextCell( hexa_prism1 ) +hexa_prism_cells.InsertNextCell( hexa_prism2 ) +hexa_prism_cells.InsertNextCell( hexa_prism3 ) +hexa_prism_cells.InsertNextCell( hexa_prism4 ) + +hexa_prism_grid = vtkUnstructuredGrid() +hexa_prism_grid.SetPoints( points_hexa_prisms ) +hexa_prism_grid.SetCells( VTK_HEXAGONAL_PRISM, hexa_prism_cells ) + +# one of every other hexagonal prism has invalid ordering +hexa_prism_grid_invalid = vtkUnstructuredGrid() +hexa_prism_grid_invalid.DeepCopy( hexa_prism_grid ) +for i in range( 2 ): + reorder_cell_nodes( hexa_prism_grid_invalid, i * 2 + 1, to_change_order[ VTK_HEXAGONAL_PRISM ] ) + +""" +2 hexahedrons, 2 tetrahedrons, 2 wedges, 2 pyramids, 2 voxels, 2 pentagonal prisms and 2 hexagonal prisms +""" +points_mix: vtkPoints = vtkPoints() +points_mix_coords: list[ tuple[ float ] ] = [ ( 0.0, 0.0, 0.0 ), + ( 1.0, 0.0, 0.0 ), + ( 2.0, 0.0, 0.0 ), + ( 2.5, -0.5, 0.0 ), + ( 3.0, 0.0, 0.0 ), + ( 3.5, -0.5, 0.0 ), + ( 4.0, 0.0, 0.0 ), + ( 4.5, -0.5, 0.0 ), + ( 5.0, 0.0, 0.0 ), + ( 5.5, -0.5, 0.0 ), + ( 6.0, 0.5, 0.0 ), + ( 0.0, 1.0, 0.0 ), + ( 1.0, 1.0, 0.0 ), + ( 2.0, 1.0, 0.0 ), + ( 2.5, 1.5, 0.0 ), + ( 3.0, 1.0, 0.0 ), + ( 4.0, 1.0, 0.0 ), + ( 5.0, 1.0, 0.0 ), + ( 5.5, 1.5, 0.0 ), + ( 0.0, 0.0, 1.0 ), + ( 1.0, 0.0, 1.0 ), + ( 2.0, 0.0, 1.0 ), + ( 2.5, -0.5, 1.0 ), + ( 3.0, 0.0, 1.0 ), + ( 3.5, -0.5, 1.0 ), + ( 4.0, 0.0, 1.0 ), + ( 4.5, -0.5, 1.0 ), + ( 5.0, 0.0, 1.0 ), + ( 5.5, -0.5, 1.0 ), + ( 6.0, 0.5, 1.0 ), + ( 0.0, 1.0, 1.0 ), + ( 1.0, 1.0, 1.0 ), + ( 2.0, 1.0, 1.0 ), + ( 2.5, 1.5, 1.0 ), + ( 3.0, 1.0, 1.0 ), + ( 4.0, 1.0, 1.0 ), + ( 5.0, 1.0, 1.0 ), + ( 5.5, 1.5, 1.0 ), + ( 0.5, 0.5, 2.0 ), + ( 0.5, 1.5, 2.0 ), + ( 1.5, 0.5, 2.0 ), + ( 1.5, 1.5, 2.0 ), + ( 2.0, 0.0, 2.0 ), + ( 2.5, -0.5, 2.0 ), + ( 3.0, 0.0, 2.0 ), + ( 3.0, 1.0, 2.0 ), + ( 2.5, 1.5, 2.0 ), + ( 2.0, 1.0, 2.0 ), + ( 5.0, 0.0, 2.0 ), + ( 5.5, -0.5, 2.0 ), + ( 6.0, 0.5, 2.0 ), + ( 5.5, 1.5, 2.0 ), + ( 5.0, 1.0, 2.0 ) ] +for point_mix in points_mix_coords: + points_mix.InsertNextPoint( point_mix ) + +mix_hex1: vtkHexahedron = vtkHexahedron() +mix_hex1.GetPointIds().SetId( 0, 0 ) +mix_hex1.GetPointIds().SetId( 1, 1 ) +mix_hex1.GetPointIds().SetId( 2, 12 ) +mix_hex1.GetPointIds().SetId( 3, 11 ) +mix_hex1.GetPointIds().SetId( 4, 19 ) +mix_hex1.GetPointIds().SetId( 5, 20 ) +mix_hex1.GetPointIds().SetId( 6, 31 ) +mix_hex1.GetPointIds().SetId( 7, 30 ) + +mix_hex2: vtkHexahedron = vtkHexahedron() +mix_hex2.GetPointIds().SetId( 0, 1 ) +mix_hex2.GetPointIds().SetId( 1, 2 ) +mix_hex2.GetPointIds().SetId( 2, 13 ) +mix_hex2.GetPointIds().SetId( 3, 12 ) +mix_hex2.GetPointIds().SetId( 4, 20 ) +mix_hex2.GetPointIds().SetId( 5, 21 ) +mix_hex2.GetPointIds().SetId( 6, 32 ) +mix_hex2.GetPointIds().SetId( 7, 31 ) + +mix_pyram1: vtkPyramid = vtkPyramid() +mix_pyram1.GetPointIds().SetId( 0, 19 ) +mix_pyram1.GetPointIds().SetId( 1, 20 ) +mix_pyram1.GetPointIds().SetId( 2, 31 ) +mix_pyram1.GetPointIds().SetId( 3, 30 ) +mix_pyram1.GetPointIds().SetId( 4, 38 ) + +mix_pyram2: vtkPyramid = vtkPyramid() +mix_pyram2.GetPointIds().SetId( 0, 20 ) +mix_pyram2.GetPointIds().SetId( 1, 21 ) +mix_pyram2.GetPointIds().SetId( 2, 32 ) +mix_pyram2.GetPointIds().SetId( 3, 31 ) +mix_pyram2.GetPointIds().SetId( 4, 40 ) + +mix_tetra1: vtkTetra = vtkTetra() +mix_tetra1.GetPointIds().SetId( 0, 31 ) +mix_tetra1.GetPointIds().SetId( 1, 39 ) +mix_tetra1.GetPointIds().SetId( 2, 30 ) +mix_tetra1.GetPointIds().SetId( 3, 38 ) + +mix_tetra2: vtkTetra = vtkTetra() +mix_tetra2.GetPointIds().SetId( 0, 32 ) +mix_tetra2.GetPointIds().SetId( 1, 41 ) +mix_tetra2.GetPointIds().SetId( 2, 31 ) +mix_tetra2.GetPointIds().SetId( 3, 40 ) + +mix_hex_prism1: vtkHexagonalPrism = vtkHexagonalPrism() +mix_hex_prism1.GetPointIds().SetId( 0, 2 ) +mix_hex_prism1.GetPointIds().SetId( 1, 3 ) +mix_hex_prism1.GetPointIds().SetId( 2, 4 ) +mix_hex_prism1.GetPointIds().SetId( 3, 15 ) +mix_hex_prism1.GetPointIds().SetId( 4, 14 ) +mix_hex_prism1.GetPointIds().SetId( 5, 13 ) +mix_hex_prism1.GetPointIds().SetId( 6, 21 ) +mix_hex_prism1.GetPointIds().SetId( 7, 22 ) +mix_hex_prism1.GetPointIds().SetId( 8, 23 ) +mix_hex_prism1.GetPointIds().SetId( 9, 34 ) +mix_hex_prism1.GetPointIds().SetId( 10, 33 ) +mix_hex_prism1.GetPointIds().SetId( 11, 32 ) + +mix_hex_prism2: vtkHexagonalPrism = vtkHexagonalPrism() +mix_hex_prism2.GetPointIds().SetId( 0, 21 ) +mix_hex_prism2.GetPointIds().SetId( 1, 22 ) +mix_hex_prism2.GetPointIds().SetId( 2, 23 ) +mix_hex_prism2.GetPointIds().SetId( 3, 34 ) +mix_hex_prism2.GetPointIds().SetId( 4, 33 ) +mix_hex_prism2.GetPointIds().SetId( 5, 32 ) +mix_hex_prism2.GetPointIds().SetId( 6, 42 ) +mix_hex_prism2.GetPointIds().SetId( 7, 43 ) +mix_hex_prism2.GetPointIds().SetId( 8, 44 ) +mix_hex_prism2.GetPointIds().SetId( 9, 45 ) +mix_hex_prism2.GetPointIds().SetId( 10, 46 ) +mix_hex_prism2.GetPointIds().SetId( 11, 47 ) + +mix_voxel1: vtkVoxel = vtkVoxel() +mix_voxel1.GetPointIds().SetId( 0, 4 ) +mix_voxel1.GetPointIds().SetId( 1, 6 ) +mix_voxel1.GetPointIds().SetId( 2, 15 ) +mix_voxel1.GetPointIds().SetId( 3, 16 ) +mix_voxel1.GetPointIds().SetId( 4, 23 ) +mix_voxel1.GetPointIds().SetId( 5, 25 ) +mix_voxel1.GetPointIds().SetId( 6, 34 ) +mix_voxel1.GetPointIds().SetId( 7, 35 ) + +mix_voxel2: vtkVoxel = vtkVoxel() +mix_voxel2.GetPointIds().SetId( 0, 6 ) +mix_voxel2.GetPointIds().SetId( 1, 8 ) +mix_voxel2.GetPointIds().SetId( 2, 16 ) +mix_voxel2.GetPointIds().SetId( 3, 17 ) +mix_voxel2.GetPointIds().SetId( 4, 25 ) +mix_voxel2.GetPointIds().SetId( 5, 27 ) +mix_voxel2.GetPointIds().SetId( 6, 35 ) +mix_voxel2.GetPointIds().SetId( 7, 36 ) + +mix_wedge1: vtkWedge = vtkWedge() +mix_wedge1.GetPointIds().SetId( 0, 23 ) +mix_wedge1.GetPointIds().SetId( 1, 24 ) +mix_wedge1.GetPointIds().SetId( 2, 25 ) +mix_wedge1.GetPointIds().SetId( 3, 4 ) +mix_wedge1.GetPointIds().SetId( 4, 5 ) +mix_wedge1.GetPointIds().SetId( 5, 6 ) + +mix_wedge2: vtkWedge = vtkWedge() +mix_wedge2.GetPointIds().SetId( 0, 25 ) +mix_wedge2.GetPointIds().SetId( 1, 26 ) +mix_wedge2.GetPointIds().SetId( 2, 27 ) +mix_wedge2.GetPointIds().SetId( 3, 6 ) +mix_wedge2.GetPointIds().SetId( 4, 7 ) +mix_wedge2.GetPointIds().SetId( 5, 8 ) + +mix_penta_prism1: vtkPentagonalPrism = vtkPentagonalPrism() +mix_penta_prism1.GetPointIds().SetId( 0, 8 ) +mix_penta_prism1.GetPointIds().SetId( 1, 9 ) +mix_penta_prism1.GetPointIds().SetId( 2, 10 ) +mix_penta_prism1.GetPointIds().SetId( 3, 18 ) +mix_penta_prism1.GetPointIds().SetId( 4, 17 ) +mix_penta_prism1.GetPointIds().SetId( 5, 27 ) +mix_penta_prism1.GetPointIds().SetId( 6, 28 ) +mix_penta_prism1.GetPointIds().SetId( 7, 29 ) +mix_penta_prism1.GetPointIds().SetId( 8, 37 ) +mix_penta_prism1.GetPointIds().SetId( 9, 36 ) + +mix_penta_prism2: vtkPentagonalPrism = vtkPentagonalPrism() +mix_penta_prism2.GetPointIds().SetId( 0, 27 ) +mix_penta_prism2.GetPointIds().SetId( 1, 28 ) +mix_penta_prism2.GetPointIds().SetId( 2, 29 ) +mix_penta_prism2.GetPointIds().SetId( 3, 37 ) +mix_penta_prism2.GetPointIds().SetId( 4, 36 ) +mix_penta_prism2.GetPointIds().SetId( 5, 48 ) +mix_penta_prism2.GetPointIds().SetId( 6, 49 ) +mix_penta_prism2.GetPointIds().SetId( 7, 50 ) +mix_penta_prism2.GetPointIds().SetId( 8, 51 ) +mix_penta_prism2.GetPointIds().SetId( 9, 52 ) + +# this mix grid has only valid cell volumes +mix_grid = vtkUnstructuredGrid() +mix_grid.SetPoints( points_mix ) +all_cell_types_mix_grid = [ VTK_HEXAHEDRON, VTK_HEXAHEDRON, VTK_PYRAMID, VTK_PYRAMID, VTK_TETRA, VTK_TETRA, + VTK_HEXAGONAL_PRISM, VTK_HEXAGONAL_PRISM, VTK_VOXEL, VTK_VOXEL, VTK_WEDGE, VTK_WEDGE, + VTK_PENTAGONAL_PRISM, VTK_PENTAGONAL_PRISM ] +all_cells_mix_grid = [ mix_hex1, mix_hex2, mix_pyram1, mix_pyram2, mix_tetra1, mix_tetra2, + mix_hex_prism1, mix_hex_prism2, mix_voxel1, mix_voxel2, mix_wedge1, mix_wedge2, + mix_penta_prism1, mix_penta_prism2 ] +for cell_type, cell in zip( all_cell_types_mix_grid, all_cells_mix_grid ): + mix_grid.InsertNextCell( cell_type, cell.GetPointIds() ) + +# this mix grid has one invalid cell for each different element type +mix_grid_invalid = vtkUnstructuredGrid() +mix_grid_invalid.DeepCopy(mix_grid) +for i in range( len( all_cell_types_mix_grid ) // 2 ): + reorder_cell_nodes( mix_grid_invalid, i * 2 + 1, to_change_order[ all_cell_types_mix_grid[ i * 2 + 1 ] ] ) + + +class TestClass: + + def test_reorder_cell_nodes( self ): + expected_nodes_coords: list[ tuple[ float] ] = [ ( 0.0, 0.0, 0.0 ), + ( 1.0, 0.0, 0.0 ), + ( 1.0, 1.0, 0.0 ), + ( 0.0, 1.0, 0.0 ), + ( 0.0, 0.0, 1.0 ), + ( 1.0, 0.0, 1.0 ), + ( 1.0, 1.0, 1.0 ), + ( 0.0, 1.0, 1.0 ) ] + for i in range( one_hex.GetCell( 0 ).GetNumberOfPoints() ): + assert one_hex.GetCell( 0 ).GetPoints().GetPoint( i ) == expected_nodes_coords[ i ] + + # reorder the cell to make it invalid + reorder_cell_nodes( one_hex, 0, to_change_order[ VTK_HEXAHEDRON ] ) + expected_nodes_coords_modified = [ ( 0.0, 0.0, 0.0 ), + ( 0.0, 1.0, 0.0 ), + ( 1.0, 1.0, 0.0 ), + ( 1.0, 0.0, 0.0 ), + ( 0.0, 0.0, 1.0 ), + ( 1.0, 0.0, 1.0 ), + ( 1.0, 1.0, 1.0 ), + ( 0.0, 1.0, 1.0 ) ] + for i in range( one_hex.GetCell( 0 ).GetNumberOfPoints() ): + assert one_hex.GetCell( 0 ).GetPoints().GetPoint( i ) == expected_nodes_coords_modified[ i ] + + # reorder the cell again to make it valid again + reorder_cell_nodes( one_hex, 0, to_change_order[ VTK_HEXAHEDRON ] ) + expected_nodes_coords_modified2 = [ ( 0.0, 0.0, 0.0 ), + ( 1.0, 0.0, 0.0 ), + ( 1.0, 1.0, 0.0 ), + ( 0.0, 1.0, 0.0 ), + ( 0.0, 0.0, 1.0 ), + ( 1.0, 0.0, 1.0 ), + ( 1.0, 1.0, 1.0 ), + ( 0.0, 1.0, 1.0 ) ] + for i in range( one_hex.GetCell( 0 ).GetNumberOfPoints() ): + assert one_hex.GetCell( 0 ).GetPoints().GetPoint( i ) == expected_nodes_coords_modified2[ i ] + + def test_compute_mesh_cells_volume( self ): + grid_volumes = { hexahedrons_grid: [ 1.0, 1.0, 1.0, 1.0 ], + hexahedrons_grid_invalid: [ 1.0, -0.333, 1.0, -0.333 ], + tetras_grid: [ 0.167, 0.167, 0.167, 0.167 ], + tetras_grid_invalid: [ 0.167, -0.167, 0.167, -0.167 ], + pyramids_grid: [ 0.333, 0.333, 0.333, 0.333 ], + pyramids_grid_invalid: [ 0.333, -0.333, 0.333, -0.333 ], + voxels_grid: [ 1.0, 1.0, 1.0, 1.0 ], + voxels_grid_invalid: [ 1.0, 1.0, 1.0, 1.0 ], + wedges_grid: [ 0.5, 0.5, 0.5, 0.5 ], + wedges_grid_invalid: [ 0.5, -0.167, 0.5, -0.167 ], + penta_prism_grid: [ 1.25, 1.25, 1.25, 1.25 ], + penta_prism_grid_invalid: [ 1.25, -0.083, 1.25, -0.083 ], + hexa_prism_grid: [ 1.5, 1.5, 1.5, 1.5 ], + hexa_prism_grid_invalid: [ 1.5, -0.333, 1.5, -0.333 ], + mix_grid: [ 1.0, 1.0, 0.333, 0.333, 0.167, 0.167, 1.5, + 1.5, 1.0, 1.0, 0.25, 0.25, 1.25, 1.25 ], + mix_grid_invalid: [ 1.0, -0.333, 0.333, -0.333, 0.167, -0.167, 1.5, + -0.333, 1.0, 1.0, 0.25, -0.083, 1.25, -0.083 ] } + for grid, volumes_expected in grid_volumes.items(): + volumes_computed = feo.compute_mesh_cells_volume( grid ) + for i in range( len( volumes_computed ) ): + assert round( float( volumes_computed[ i ] ), 3 ) == volumes_expected[ i ] + + def test_is_cell_to_reorder( self ): + grid_needs_ordering = { hexahedrons_grid: [ False ] * 4, + hexahedrons_grid_invalid: [ i % 2 != 0 for i in range( 4 ) ], + tetras_grid: [ False ] * 4, + tetras_grid_invalid: [ i % 2 != 0 for i in range( 4 ) ], + pyramids_grid: [ False ] * 4, + pyramids_grid_invalid: [ i % 2 != 0 for i in range( 4 ) ], + voxels_grid: [ False ] * 4, + voxels_grid_invalid: [ False ] * 4, + wedges_grid: [ False ] * 4, + wedges_grid_invalid: [ i % 2 != 0 for i in range( 4 ) ], + penta_prism_grid: [ False ] * 4, + penta_prism_grid_invalid: [ i % 2 != 0 for i in range( 4 ) ], + hexa_prism_grid: [ False ] * 4, + hexa_prism_grid_invalid: [ i % 2 != 0 for i in range( 4 ) ], + mix_grid: [ False ] * 14, + mix_grid_invalid: [ i % 2 != 0 for i in range( 14 ) ] } + grid_needs_ordering[ mix_grid_invalid ][ 9 ] = False + for grid, needs_ordering in grid_needs_ordering.items(): + volumes = feo.compute_mesh_cells_volume( grid ) + for i in range( len( volumes ) ): + assert feo.is_cell_to_reorder( volumes[ i ], "negative" ) == needs_ordering[ i ] + assert feo.is_cell_to_reorder( volumes[ i ], "positive" ) != needs_ordering[ i ] + assert feo.is_cell_to_reorder( volumes[ i ], "all" ) == True + + def test_get_all_cells_type( self ): + assert feo.get_all_cells_type( hexahedrons_grid ).tolist() == [ 12, 12, 12, 12 ] + assert feo.get_all_cells_type( hexahedrons_grid_invalid ).tolist() == [ 12, 12, 12, 12 ] + assert feo.get_all_cells_type( tetras_grid ).tolist() == [ 10, 10, 10, 10 ] + assert feo.get_all_cells_type( tetras_grid_invalid ).tolist() == [ 10, 10, 10, 10 ] + assert feo.get_all_cells_type( pyramids_grid ).tolist() == [ 14, 14, 14, 14 ] + assert feo.get_all_cells_type( pyramids_grid_invalid ).tolist() == [ 14, 14, 14, 14 ] + assert feo.get_all_cells_type( voxels_grid ).tolist() == [ 11, 11, 11, 11 ] + assert feo.get_all_cells_type( voxels_grid_invalid ).tolist() == [ 11, 11, 11, 11 ] + assert feo.get_all_cells_type( wedges_grid ).tolist() == [ 13, 13, 13, 13 ] + assert feo.get_all_cells_type( wedges_grid_invalid ).tolist() == [ 13, 13, 13, 13 ] + assert feo.get_all_cells_type( penta_prism_grid ).tolist() == [ 15, 15, 15, 15 ] + assert feo.get_all_cells_type( penta_prism_grid_invalid ).tolist() == [ 15, 15, 15, 15 ] + assert feo.get_all_cells_type( hexa_prism_grid ).tolist() == [ 16, 16, 16, 16 ] + assert feo.get_all_cells_type( hexa_prism_grid_invalid ).tolist() == [ 16, 16, 16, 16 ] + assert feo.get_all_cells_type( mix_grid ).tolist() == [ 12, 12, 14, 14, 10, 10, 16, + 16, 11, 11, 13, 13, 15, 15 ] + assert feo.get_all_cells_type( mix_grid_invalid ).tolist() == [ 12, 12, 14, 14, 10, 10, 16, + 16, 11, 11, 13, 13, 15, 15 ] + + def test_get_cell_ids_to_check( self ): + options = opt( out, to_change_order, "negative" ) + # single element grids + grid_cell_type = { hexahedrons_grid: VTK_HEXAHEDRON, + hexahedrons_grid_invalid: VTK_HEXAHEDRON, + tetras_grid: VTK_TETRA, + tetras_grid_invalid: VTK_TETRA, + pyramids_grid: VTK_PYRAMID, + pyramids_grid_invalid: VTK_PYRAMID, + voxels_grid: VTK_VOXEL, + voxels_grid_invalid: VTK_VOXEL, + wedges_grid: VTK_WEDGE, + wedges_grid_invalid: VTK_WEDGE, + penta_prism_grid: VTK_PENTAGONAL_PRISM, + penta_prism_grid_invalid: VTK_PENTAGONAL_PRISM, + hexa_prism_grid: VTK_HEXAGONAL_PRISM, + hexa_prism_grid_invalid: VTK_HEXAGONAL_PRISM } + for grid, cell_type in grid_cell_type.items(): + all_cells_type = feo.get_all_cells_type( grid ) + result = feo.get_cell_ids_to_check( all_cells_type, options ) + expected = { cell_type: np.array([ 0, 1, 2, 3 ]) } + for cell_type_result, cell_type_expected in zip( result.keys(), expected.keys() ): + assert cell_type_result == cell_type_expected + for cell_indexes_result, cell_indexes_expected in zip( result.values(), expected.values() ): + assert np.array_equal( cell_indexes_result, cell_indexes_expected ) + + # mix elements grid + all_cells_type_mix = feo.get_all_cells_type( mix_grid ) + result = feo.get_cell_ids_to_check( all_cells_type_mix, options ) + result = dict( sorted( result.items() ) ) + expected = { 12: np.array( [ 0, 1 ] ), 14: np.array( [ 2, 3 ] ), 10: np.array( [ 4, 5 ] ), + 16: np.array( [ 6, 7 ] ), 11: np.array( [ 8, 9 ] ), 13: np.array( [ 10, 11 ] ), + 15: np.array( [ 12, 13 ] ) } + expected = dict( sorted( expected.items() ) ) + for cell_type_result, cell_type_expected in zip( result.keys(), expected.keys() ): + assert cell_type_result == cell_type_expected + for cell_indexes_result, cell_indexes_expected in zip( result.values(), expected.values() ): + assert np.array_equal( cell_indexes_result, cell_indexes_expected ) + + def test_reorder_nodes_to_new_mesh( self ): + options = opt( out, to_change_order, "negative" ) + # single element grids except voxels because volume is always positive + grid_cell_type = { hexahedrons_grid_invalid: VTK_HEXAHEDRON, + tetras_grid_invalid: VTK_TETRA, + pyramids_grid_invalid: VTK_PYRAMID, + wedges_grid_invalid: VTK_WEDGE, + penta_prism_grid_invalid: VTK_PENTAGONAL_PRISM, + hexa_prism_grid_invalid: VTK_HEXAGONAL_PRISM } + for grid, cell_type in grid_cell_type.items(): + new_invalid = vtkUnstructuredGrid() + new_invalid.DeepCopy( grid ) + not_use_invalid, reorder_stats = feo.reorder_nodes_to_new_mesh( new_invalid, options ) + expected = { "Types reordered": [ cell_type ], + "Number of cells reordered": [ 2 ], + "Types non reordered": [ cell_type ], + "Number of cells non reordered": [ 2 ] } + for prop in expected.keys(): + assert reorder_stats[ prop ] == expected[ prop ] + + # voxel elements grid + voxels_invalid = vtkUnstructuredGrid() + voxels_invalid.DeepCopy( voxels_grid_invalid ) + not_use_invalid, voxels_stats = feo.reorder_nodes_to_new_mesh( voxels_invalid, options ) + expected = { "Types reordered": [], + "Number of cells reordered": [], + "Types non reordered": [ 11 ], + "Number of cells non reordered": [ 4 ] } + for prop in expected.keys(): + assert voxels_stats[ prop ] == expected[ prop ] + + # mix elements grid + mix_invalid = vtkUnstructuredGrid() + mix_invalid.DeepCopy( mix_grid_invalid ) + not_use_invalid, mix_stats = feo.reorder_nodes_to_new_mesh( mix_invalid, options ) + expected = { "Types reordered": [ 10, 12, 13, 14, 15, 16 ], + "Number of cells reordered": [ 1, 1, 1, 1, 1, 1 ], + "Types non reordered": [ 10, 11, 12, 13, 14, 15, 16 ], + "Number of cells non reordered": [ 1, 2, 1, 1, 1, 1, 1 ] } + for prop in expected.keys(): + assert mix_stats[ prop ] == expected[ prop ] \ No newline at end of file From 16578d0039127a5ddb43a1ac73d0a6f6fc31eed3 Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Fri, 30 Aug 2024 13:58:01 -0700 Subject: [PATCH 10/15] yapf formatting --- .../doctor/checks/fix_elements_orderings.py | 32 +- .../tests/test_fix_elements_orderings.py | 566 ++++++------------ 2 files changed, 213 insertions(+), 385 deletions(-) diff --git a/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py b/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py index 9f2e4479..6b7971b6 100644 --- a/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py +++ b/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py @@ -8,14 +8,8 @@ from vtk import vtkCellSizeFilter from vtkmodules.vtkCommonCore import vtkIdList from vtkmodules.util.numpy_support import vtk_to_numpy -from vtkmodules.vtkCommonDataModel import ( vtkDataSet, - VTK_HEXAHEDRON, - VTK_TETRA, - VTK_PYRAMID, - VTK_WEDGE, - VTK_VOXEL, - VTK_PENTAGONAL_PRISM, - VTK_HEXAGONAL_PRISM ) +from vtkmodules.vtkCommonDataModel import ( vtkDataSet, VTK_HEXAHEDRON, VTK_TETRA, VTK_PYRAMID, VTK_WEDGE, VTK_VOXEL, + VTK_PENTAGONAL_PRISM, VTK_HEXAGONAL_PRISM ) from .vtk_utils import VtkOutput, to_vtk_id_list, write_mesh, read_mesh @@ -32,13 +26,15 @@ class Result: reordering_stats: dict[ str, list[ int ] ] -cell_type_names: dict[ int, str ] = { VTK_HEXAHEDRON: "Hexahedron", - VTK_TETRA: "Tetra", - VTK_PYRAMID: "Pyramid", - VTK_WEDGE: "Wedge", - VTK_VOXEL: "Voxel", - VTK_PENTAGONAL_PRISM: "Prism5", - VTK_HEXAGONAL_PRISM: "Prism6" } +cell_type_names: dict[ int, str ] = { + VTK_HEXAHEDRON: "Hexahedron", + VTK_TETRA: "Tetra", + VTK_PYRAMID: "Pyramid", + VTK_WEDGE: "Wedge", + VTK_VOXEL: "Voxel", + VTK_PENTAGONAL_PRISM: "Prism5", + VTK_HEXAGONAL_PRISM: "Prism6" +} def compute_mesh_cells_volume( mesh: vtkDataSet ) -> np.array: @@ -52,9 +48,9 @@ def compute_mesh_cells_volume( mesh: vtkDataSet ) -> np.array: """ cell_size_filter = vtkCellSizeFilter() cell_size_filter.SetInputData( mesh ) - cell_size_filter.SetComputeVolume(True) + cell_size_filter.SetComputeVolume( True ) cell_size_filter.Update() - return vtk_to_numpy( cell_size_filter.GetOutput().GetCellData().GetArray("Volume") ) + return vtk_to_numpy( cell_size_filter.GetOutput().GetCellData().GetArray( "Volume" ) ) def is_cell_to_reorder( cell_volume: float, volume_to_reorder: str ) -> bool: @@ -71,7 +67,7 @@ def is_cell_to_reorder( cell_volume: float, volume_to_reorder: str ) -> bool: return True if cell_volume == 0.0: return True - sign_of_cell_volume: int = int( cell_volume / abs(cell_volume) ) + sign_of_cell_volume: int = int( cell_volume / abs( cell_volume ) ) if volume_to_reorder == "positive" and sign_of_cell_volume == 1: return True elif volume_to_reorder == "negative" and sign_of_cell_volume == -1: diff --git a/geos-mesh/tests/test_fix_elements_orderings.py b/geos-mesh/tests/test_fix_elements_orderings.py index e93a75a8..47ec2a04 100644 --- a/geos-mesh/tests/test_fix_elements_orderings.py +++ b/geos-mesh/tests/test_fix_elements_orderings.py @@ -5,23 +5,10 @@ from geos.mesh.doctor.checks.vtk_utils import VtkOutput, to_vtk_id_list from geos.mesh.doctor.checks.fix_elements_orderings import Options as opt from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints -from vtkmodules.vtkCommonDataModel import ( vtkDataSet, - vtkUnstructuredGrid, - vtkCellArray, - vtkHexahedron, - vtkTetra, - vtkPyramid, - vtkVoxel, - vtkWedge, - vtkPentagonalPrism, - vtkHexagonalPrism, - VTK_HEXAHEDRON, - VTK_TETRA, - VTK_PYRAMID, - VTK_WEDGE, - VTK_VOXEL, - VTK_PENTAGONAL_PRISM, - VTK_HEXAGONAL_PRISM ) +from vtkmodules.vtkCommonDataModel import ( vtkDataSet, vtkUnstructuredGrid, vtkCellArray, vtkHexahedron, vtkTetra, + vtkPyramid, vtkVoxel, vtkWedge, vtkPentagonalPrism, vtkHexagonalPrism, + VTK_HEXAHEDRON, VTK_TETRA, VTK_PYRAMID, VTK_WEDGE, VTK_VOXEL, + VTK_PENTAGONAL_PRISM, VTK_HEXAGONAL_PRISM ) def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int ] ): @@ -33,8 +20,8 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int node_ordering (list[ int ]): Nodes id ordering to construct a cell. """ if mesh.GetCell( cell_id ).GetNumberOfPoints() != len( node_ordering ): - raise ValueError( f"The cell to reorder needs to have '{mesh.GetCell( cell_id ).GetNumberOfPoints()}'" - + "nodes in reordering." ) + raise ValueError( f"The cell to reorder needs to have '{mesh.GetCell( cell_id ).GetNumberOfPoints()}'" + + "nodes in reordering." ) cells = mesh.GetCells() support_point_ids = vtkIdList() cells.GetCellAtId( cell_id, support_point_ids ) @@ -48,15 +35,16 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int """ Dict used to apply false nodes orderings for test purposes """ -to_change_order: dict[ int, list[ int ] ] = { VTK_HEXAHEDRON: [ 0, 3, 2, 1, 4, 5, 6, 7 ], - VTK_TETRA: [ 0, 2, 1, 3 ], - VTK_PYRAMID: [ 0, 3, 2, 1, 4 ], - VTK_WEDGE: [ 0, 2, 1, 3, 4, 5 ], - VTK_VOXEL: [ 7, 6, 5, 4, 3, 2, 1, 0 ], - VTK_PENTAGONAL_PRISM: [ 0, 4, 3, 2, 1, 5, 6, 7, 8, 9 ], - VTK_HEXAGONAL_PRISM: [ 0, 1, 4, 2, 3, 5, 11, 10, 9, 8, 7, 6 ] } +to_change_order: dict[ int, list[ int ] ] = { + VTK_HEXAHEDRON: [ 0, 3, 2, 1, 4, 5, 6, 7 ], + VTK_TETRA: [ 0, 2, 1, 3 ], + VTK_PYRAMID: [ 0, 3, 2, 1, 4 ], + VTK_WEDGE: [ 0, 2, 1, 3, 4, 5 ], + VTK_VOXEL: [ 7, 6, 5, 4, 3, 2, 1, 0 ], + VTK_PENTAGONAL_PRISM: [ 0, 4, 3, 2, 1, 5, 6, 7, 8, 9 ], + VTK_HEXAGONAL_PRISM: [ 0, 1, 4, 2, 3, 5, 11, 10, 9, 8, 7, 6 ] +} to_change_order = dict( sorted( to_change_order.items() ) ) - """ 1 Hexahedron: no invalid ordering """ @@ -64,15 +52,14 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int options_one_hex: Options = Options( vtk_output=out, generate_cells_global_ids=False, generate_points_global_ids=False, - xs=np.array( [ 0.0, 1.0] ), - ys=np.array( [ 0.0, 1.0] ), + xs=np.array( [ 0.0, 1.0 ] ), + ys=np.array( [ 0.0, 1.0 ] ), zs=np.array( [ 0.0, 1.0 ] ), nxs=[ 1 ], nys=[ 1 ], nzs=[ 1 ], fields=[] ) one_hex: vtkDataSet = __build( options_one_hex ) - """ 4 Hexahedrons: no invalid ordering """ @@ -88,26 +75,19 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int nzs=[ 1 ], fields=[] ) hexahedrons_grid: vtkDataSet = __build( options_hexahedrons_grid ) - """ 4 Hexahedrons: 2 Hexahedrons with invalid ordering """ hexahedrons_grid_invalid: vtkDataSet = __build( options_hexahedrons_grid ) for i in range( 2 ): reorder_cell_nodes( hexahedrons_grid_invalid, i * 2 + 1, to_change_order[ VTK_HEXAHEDRON ] ) - """ 4 tetrahedrons """ points_tetras: vtkPoints = vtkPoints() -points_tetras_coords: list[ tuple[ float ] ] = [ (0.0, 0.0, 0.0), - (1.0, 0.0, 0.0), - (1.0, 1.0, 0.0), - (0.0, 1.0, 0.0), - (0.0, 0.0, 1.0), - (1.0, 0.0, 1.0), - (1.0, 1.0, 1.0), - (0.0, 1.0, 1.0) ] +points_tetras_coords: list[ tuple[ float ] ] = [ ( 0.0, 0.0, 0.0 ), ( 1.0, 0.0, 0.0 ), ( 1.0, 1.0, 0.0 ), + ( 0.0, 1.0, 0.0 ), ( 0.0, 0.0, 1.0 ), ( 1.0, 0.0, 1.0 ), + ( 1.0, 1.0, 1.0 ), ( 0.0, 1.0, 1.0 ) ] for point_tetra in points_tetras_coords: points_tetras.InsertNextPoint( point_tetra ) @@ -150,31 +130,17 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int tetras_grid_invalid.DeepCopy( tetras_grid ) for i in range( 2 ): reorder_cell_nodes( tetras_grid_invalid, i * 2 + 1, to_change_order[ VTK_TETRA ] ) - """ 4 pyramids """ points_pyramids: vtkPoints = vtkPoints() -points_pyramids_coords: list[ tuple[ float ] ] = [ (0.0, 0.0, 0.0), - (1.0, 0.0, 0.0), - (1.0, 1.0, 0.0), - (0.0, 1.0, 0.0), - (0.5, 0.5, 1.0), - (2.0, 0.0, 0.0), - (3.0, 0.0, 0.0), - (3.0, 1.0, 0.0), - (2.0, 1.0, 0.0), - (2.5, 0.5, 1.0), - (0.0, 2.0, 0.0), - (1.0, 2.0, 0.0), - (1.0, 3.0, 0.0), - (0.0, 3.0, 0.0), - (0.5, 2.5, 1.0), - (2.0, 2.0, 0.0), - (3.0, 2.0, 0.0), - (3.0, 3.0, 0.0), - (2.0, 3.0, 0.0), - (2.5, 2.5, 1.0) ] +points_pyramids_coords: list[ tuple[ float ] ] = [ ( 0.0, 0.0, 0.0 ), ( 1.0, 0.0, 0.0 ), ( 1.0, 1.0, 0.0 ), + ( 0.0, 1.0, 0.0 ), ( 0.5, 0.5, 1.0 ), ( 2.0, 0.0, 0.0 ), + ( 3.0, 0.0, 0.0 ), ( 3.0, 1.0, 0.0 ), ( 2.0, 1.0, 0.0 ), + ( 2.5, 0.5, 1.0 ), ( 0.0, 2.0, 0.0 ), ( 1.0, 2.0, 0.0 ), + ( 1.0, 3.0, 0.0 ), ( 0.0, 3.0, 0.0 ), ( 0.5, 2.5, 1.0 ), + ( 2.0, 2.0, 0.0 ), ( 3.0, 2.0, 0.0 ), ( 3.0, 3.0, 0.0 ), + ( 2.0, 3.0, 0.0 ), ( 2.5, 2.5, 1.0 ) ] for point_pyramid in points_pyramids_coords: points_pyramids.InsertNextPoint( point_pyramid ) @@ -221,44 +187,21 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int pyramids_grid_invalid.DeepCopy( pyramids_grid ) for i in range( 2 ): reorder_cell_nodes( pyramids_grid_invalid, i * 2 + 1, to_change_order[ VTK_PYRAMID ] ) - - """ 4 voxels """ points_voxels: vtkPoints = vtkPoints() -points_voxels_coords: list[ tuple[ float ] ] = [ (0.0, 0.0, 0.0), - (1.0, 0.0, 0.0), - (1.0, 1.0, 0.0), - (0.0, 1.0, 0.0), - (0.0, 0.0, 1.0), - (1.0, 0.0, 1.0), - (1.0, 1.0, 1.0), - (0.0, 1.0, 1.0), - (2.0, 0.0, 0.0), - (3.0, 0.0, 0.0), - (3.0, 1.0, 0.0), - (2.0, 1.0, 0.0), - (2.0, 0.0, 1.0), - (3.0, 0.0, 1.0), - (3.0, 1.0, 1.0), - (2.0, 1.0, 1.0), - (0.0, 2.0, 0.0), - (1.0, 2.0, 0.0), - (1.0, 3.0, 0.0), - (0.0, 3.0, 0.0), - (0.0, 2.0, 1.0), - (1.0, 2.0, 1.0), - (1.0, 3.0, 1.0), - (0.0, 3.0, 1.0), - (2.0, 2.0, 0.0), - (3.0, 2.0, 0.0), - (3.0, 3.0, 0.0), - (2.0, 3.0, 0.0), - (2.0, 2.0, 1.0), - (3.0, 2.0, 1.0), - (3.0, 3.0, 1.0), - (2.0, 3.0, 1.0) ] +points_voxels_coords: list[ tuple[ float ] ] = [ ( 0.0, 0.0, 0.0 ), ( 1.0, 0.0, 0.0 ), ( 1.0, 1.0, 0.0 ), + ( 0.0, 1.0, 0.0 ), ( 0.0, 0.0, 1.0 ), ( 1.0, 0.0, 1.0 ), + ( 1.0, 1.0, 1.0 ), ( 0.0, 1.0, 1.0 ), ( 2.0, 0.0, 0.0 ), + ( 3.0, 0.0, 0.0 ), ( 3.0, 1.0, 0.0 ), ( 2.0, 1.0, 0.0 ), + ( 2.0, 0.0, 1.0 ), ( 3.0, 0.0, 1.0 ), ( 3.0, 1.0, 1.0 ), + ( 2.0, 1.0, 1.0 ), ( 0.0, 2.0, 0.0 ), ( 1.0, 2.0, 0.0 ), + ( 1.0, 3.0, 0.0 ), ( 0.0, 3.0, 0.0 ), ( 0.0, 2.0, 1.0 ), + ( 1.0, 2.0, 1.0 ), ( 1.0, 3.0, 1.0 ), ( 0.0, 3.0, 1.0 ), + ( 2.0, 2.0, 0.0 ), ( 3.0, 2.0, 0.0 ), ( 3.0, 3.0, 0.0 ), + ( 2.0, 3.0, 0.0 ), ( 2.0, 2.0, 1.0 ), ( 3.0, 2.0, 1.0 ), + ( 3.0, 3.0, 1.0 ), ( 2.0, 3.0, 1.0 ) ] for point_voxel in points_voxels_coords: points_voxels.InsertNextPoint( point_voxel ) @@ -317,24 +260,14 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int voxels_grid_invalid.DeepCopy( voxels_grid ) for i in range( 2 ): reorder_cell_nodes( voxels_grid_invalid, i * 2 + 1, to_change_order[ VTK_VOXEL ] ) - - """ 4 wedges """ points_wedges: vtkPoints = vtkPoints() -points_wedges_coords: list[ tuple[ float ] ] = [ (0.5, 0.0, 0.0), - (1.5, 0.0, 0.0), - (2.5, 0.0, 0.0), - (0.0, 1.0, 0.0), - (1.0, 1.0, 0.0), - (2.0, 1.0, 0.0), - (0.5, 0.0, 1.0), - (1.5, 0.0, 1.0), - (2.5, 0.0, 1.0), - (0.0, 1.0, 1.0), - (1.0, 1.0, 1.0), - (2.0, 1.0, 1.0) ] +points_wedges_coords: list[ tuple[ float ] ] = [ ( 0.5, 0.0, 0.0 ), ( 1.5, 0.0, 0.0 ), ( 2.5, 0.0, 0.0 ), + ( 0.0, 1.0, 0.0 ), ( 1.0, 1.0, 0.0 ), ( 2.0, 1.0, 0.0 ), + ( 0.5, 0.0, 1.0 ), ( 1.5, 0.0, 1.0 ), ( 2.5, 0.0, 1.0 ), + ( 0.0, 1.0, 1.0 ), ( 1.0, 1.0, 1.0 ), ( 2.0, 1.0, 1.0 ) ] for point_wedge in points_wedges_coords: points_wedges.InsertNextPoint( point_wedge ) @@ -385,52 +318,24 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int wedges_grid_invalid.DeepCopy( wedges_grid ) for i in range( 2 ): reorder_cell_nodes( wedges_grid_invalid, i * 2 + 1, to_change_order[ VTK_WEDGE ] ) - - """ 4 pentagonal prisms """ points_penta_prisms: vtkPoints = vtkPoints() -points_penta_prisms_coords: list[ tuple[ float ] ] = [ (0.0, 0.0, 0.0), - (1.0, 0.0, 0.0), - (1.5, 0.5, 0.0), - (0.5, 1.0, 0.0), - (-0.5, 0.5, 0.0), - (0.0, 0.0, 1.0), - (1.0, 0.0, 1.0), - (1.5, 0.5, 1.0), - (0.5, 1.0, 1.0), - (-0.5, 0.5, 1.0), - (2.0, 0.0, 0.0), - (3.0, 0.0, 0.0), - (3.5, 0.5, 0.0), - (2.5, 1.0, 0.0), - (1.5, 0.5, 0.0), - (2.0, 0.0, 1.0), - (3.0, 0.0, 1.0), - (3.5, 0.5, 1.0), - (2.5, 1.0, 1.0), - (1.5, 0.5, 1.0), - (0.0, 2.0, 0.0), - (1.0, 2.0, 0.0), - (1.5, 2.5, 0.0), - (0.5, 3.0, 0.0), - (-0.5, 2.5, 0.0), - (0.0, 2.0, 1.0), - (1.0, 2.0, 1.0), - (1.5, 2.5, 1.0), - (0.5, 3.0, 1.0), - (-0.5, 2.5, 1.0), - (2.0, 2.0, 0.0), - (3.0, 2.0, 0.0), - (3.5, 2.5, 0.0), - (2.5, 3.0, 0.0), - (1.5, 2.5, 0.0), - (2.0, 2.0, 1.0), - (3.0, 2.0, 1.0), - (3.5, 2.5, 1.0), - (2.5, 3.0, 1.0), - (1.5, 2.5, 1.0) ] +points_penta_prisms_coords: list[ tuple[ float ] ] = [ ( 0.0, 0.0, 0.0 ), ( 1.0, 0.0, 0.0 ), ( 1.5, 0.5, 0.0 ), + ( 0.5, 1.0, 0.0 ), ( -0.5, 0.5, 0.0 ), ( 0.0, 0.0, 1.0 ), + ( 1.0, 0.0, 1.0 ), ( 1.5, 0.5, 1.0 ), ( 0.5, 1.0, 1.0 ), + ( -0.5, 0.5, 1.0 ), ( 2.0, 0.0, 0.0 ), ( 3.0, 0.0, 0.0 ), + ( 3.5, 0.5, 0.0 ), ( 2.5, 1.0, 0.0 ), ( 1.5, 0.5, 0.0 ), + ( 2.0, 0.0, 1.0 ), ( 3.0, 0.0, 1.0 ), ( 3.5, 0.5, 1.0 ), + ( 2.5, 1.0, 1.0 ), ( 1.5, 0.5, 1.0 ), ( 0.0, 2.0, 0.0 ), + ( 1.0, 2.0, 0.0 ), ( 1.5, 2.5, 0.0 ), ( 0.5, 3.0, 0.0 ), + ( -0.5, 2.5, 0.0 ), ( 0.0, 2.0, 1.0 ), ( 1.0, 2.0, 1.0 ), + ( 1.5, 2.5, 1.0 ), ( 0.5, 3.0, 1.0 ), ( -0.5, 2.5, 1.0 ), + ( 2.0, 2.0, 0.0 ), ( 3.0, 2.0, 0.0 ), ( 3.5, 2.5, 0.0 ), + ( 2.5, 3.0, 0.0 ), ( 1.5, 2.5, 0.0 ), ( 2.0, 2.0, 1.0 ), + ( 3.0, 2.0, 1.0 ), ( 3.5, 2.5, 1.0 ), ( 2.5, 3.0, 1.0 ), + ( 1.5, 2.5, 1.0 ) ] for point_penta_prism in points_penta_prisms_coords: points_penta_prisms.InsertNextPoint( point_penta_prism ) @@ -497,77 +402,43 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int penta_prism_grid_invalid.DeepCopy( penta_prism_grid ) for i in range( 2 ): reorder_cell_nodes( penta_prism_grid_invalid, i * 2 + 1, to_change_order[ VTK_PENTAGONAL_PRISM ] ) - - """ 4 hexagonal prisms """ points_hexa_prisms: vtkPoints = vtkPoints() -points_hexa_prisms_coords: list[ tuple[ float ] ] = [ (0.0, 0.0, 0.0), - (1.0, 0.0, 0.0), - (1.5, 0.5, 0.0), - (1.0, 1.0, 0.0), - (0.0, 1.0, 0.0), - (-0.5, 0.5, 0.0), - (0.0, 0.0, 1.0), - (1.0, 0.0, 1.0), - (1.5, 0.5, 1.0), - (1.0, 1.0, 1.0), - (0.0, 1.0, 1.0), - (-0.5, 0.5, 1.0), - (2.0, 0.0, 0.0), - (3.0, 0.0, 0.0), - (3.5, 0.5, 0.0), - (3.0, 1.0, 0.0), - (2.0, 1.0, 0.0), - (1.5, 0.5, 0.0), - (2.0, 0.0, 1.0), - (3.0, 0.0, 1.0), - (3.5, 0.5, 1.0), - (3.0, 1.0, 1.0), - (2.0, 1.0, 1.0), - (1.5, 0.5, 1.0), - (0.0, 2.0, 0.0), - (1.0, 2.0, 0.0), - (1.5, 2.5, 0.0), - (1.0, 3.0, 0.0), - (0.0, 3.0, 0.0), - (-0.5, 2.5, 0.0), - (0.0, 2.0, 1.0), - (1.0, 2.0, 1.0), - (1.5, 2.5, 1.0), - (1.0, 3.0, 1.0), - (0.0, 3.0, 1.0), - (-0.5, 2.5, 1.0), - (2.0, 2.0, 0.0), - (3.0, 2.0, 0.0), - (3.5, 2.5, 0.0), - (3.0, 3.0, 0.0), - (2.0, 3.0, 0.0), - (1.5, 2.5, 0.0), - (2.0, 2.0, 1.0), - (3.0, 2.0, 1.0), - (3.5, 2.5, 1.0), - (3.0, 3.0, 1.0), - (2.0, 3.0, 1.0), - (1.5, 2.5, 1.0) ] +points_hexa_prisms_coords: list[ tuple[ float ] ] = [ ( 0.0, 0.0, 0.0 ), ( 1.0, 0.0, 0.0 ), ( 1.5, 0.5, 0.0 ), + ( 1.0, 1.0, 0.0 ), ( 0.0, 1.0, 0.0 ), ( -0.5, 0.5, 0.0 ), + ( 0.0, 0.0, 1.0 ), ( 1.0, 0.0, 1.0 ), ( 1.5, 0.5, 1.0 ), + ( 1.0, 1.0, 1.0 ), ( 0.0, 1.0, 1.0 ), ( -0.5, 0.5, 1.0 ), + ( 2.0, 0.0, 0.0 ), ( 3.0, 0.0, 0.0 ), ( 3.5, 0.5, 0.0 ), + ( 3.0, 1.0, 0.0 ), ( 2.0, 1.0, 0.0 ), ( 1.5, 0.5, 0.0 ), + ( 2.0, 0.0, 1.0 ), ( 3.0, 0.0, 1.0 ), ( 3.5, 0.5, 1.0 ), + ( 3.0, 1.0, 1.0 ), ( 2.0, 1.0, 1.0 ), ( 1.5, 0.5, 1.0 ), + ( 0.0, 2.0, 0.0 ), ( 1.0, 2.0, 0.0 ), ( 1.5, 2.5, 0.0 ), + ( 1.0, 3.0, 0.0 ), ( 0.0, 3.0, 0.0 ), ( -0.5, 2.5, 0.0 ), + ( 0.0, 2.0, 1.0 ), ( 1.0, 2.0, 1.0 ), ( 1.5, 2.5, 1.0 ), + ( 1.0, 3.0, 1.0 ), ( 0.0, 3.0, 1.0 ), ( -0.5, 2.5, 1.0 ), + ( 2.0, 2.0, 0.0 ), ( 3.0, 2.0, 0.0 ), ( 3.5, 2.5, 0.0 ), + ( 3.0, 3.0, 0.0 ), ( 2.0, 3.0, 0.0 ), ( 1.5, 2.5, 0.0 ), + ( 2.0, 2.0, 1.0 ), ( 3.0, 2.0, 1.0 ), ( 3.5, 2.5, 1.0 ), + ( 3.0, 3.0, 1.0 ), ( 2.0, 3.0, 1.0 ), ( 1.5, 2.5, 1.0 ) ] for point_hexa_prism in points_hexa_prisms_coords: points_hexa_prisms.InsertNextPoint( point_hexa_prism ) hexa_prism1: vtkHexagonalPrism = vtkHexagonalPrism() -for i in range(12): +for i in range( 12 ): hexa_prism1.GetPointIds().SetId( i, i ) hexa_prism2: vtkHexagonalPrism = vtkHexagonalPrism() -for i in range(12): +for i in range( 12 ): hexa_prism2.GetPointIds().SetId( i, i + 12 ) hexa_prism3: vtkHexagonalPrism = vtkHexagonalPrism() -for i in range(12): +for i in range( 12 ): hexa_prism3.GetPointIds().SetId( i, i + 24 ) hexa_prism4: vtkHexagonalPrism = vtkHexagonalPrism() -for i in range(12): +for i in range( 12 ): hexa_prism4.GetPointIds().SetId( i, i + 36 ) hexa_prism_cells = vtkCellArray() @@ -585,64 +456,21 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int hexa_prism_grid_invalid.DeepCopy( hexa_prism_grid ) for i in range( 2 ): reorder_cell_nodes( hexa_prism_grid_invalid, i * 2 + 1, to_change_order[ VTK_HEXAGONAL_PRISM ] ) - """ 2 hexahedrons, 2 tetrahedrons, 2 wedges, 2 pyramids, 2 voxels, 2 pentagonal prisms and 2 hexagonal prisms """ points_mix: vtkPoints = vtkPoints() -points_mix_coords: list[ tuple[ float ] ] = [ ( 0.0, 0.0, 0.0 ), - ( 1.0, 0.0, 0.0 ), - ( 2.0, 0.0, 0.0 ), - ( 2.5, -0.5, 0.0 ), - ( 3.0, 0.0, 0.0 ), - ( 3.5, -0.5, 0.0 ), - ( 4.0, 0.0, 0.0 ), - ( 4.5, -0.5, 0.0 ), - ( 5.0, 0.0, 0.0 ), - ( 5.5, -0.5, 0.0 ), - ( 6.0, 0.5, 0.0 ), - ( 0.0, 1.0, 0.0 ), - ( 1.0, 1.0, 0.0 ), - ( 2.0, 1.0, 0.0 ), - ( 2.5, 1.5, 0.0 ), - ( 3.0, 1.0, 0.0 ), - ( 4.0, 1.0, 0.0 ), - ( 5.0, 1.0, 0.0 ), - ( 5.5, 1.5, 0.0 ), - ( 0.0, 0.0, 1.0 ), - ( 1.0, 0.0, 1.0 ), - ( 2.0, 0.0, 1.0 ), - ( 2.5, -0.5, 1.0 ), - ( 3.0, 0.0, 1.0 ), - ( 3.5, -0.5, 1.0 ), - ( 4.0, 0.0, 1.0 ), - ( 4.5, -0.5, 1.0 ), - ( 5.0, 0.0, 1.0 ), - ( 5.5, -0.5, 1.0 ), - ( 6.0, 0.5, 1.0 ), - ( 0.0, 1.0, 1.0 ), - ( 1.0, 1.0, 1.0 ), - ( 2.0, 1.0, 1.0 ), - ( 2.5, 1.5, 1.0 ), - ( 3.0, 1.0, 1.0 ), - ( 4.0, 1.0, 1.0 ), - ( 5.0, 1.0, 1.0 ), - ( 5.5, 1.5, 1.0 ), - ( 0.5, 0.5, 2.0 ), - ( 0.5, 1.5, 2.0 ), - ( 1.5, 0.5, 2.0 ), - ( 1.5, 1.5, 2.0 ), - ( 2.0, 0.0, 2.0 ), - ( 2.5, -0.5, 2.0 ), - ( 3.0, 0.0, 2.0 ), - ( 3.0, 1.0, 2.0 ), - ( 2.5, 1.5, 2.0 ), - ( 2.0, 1.0, 2.0 ), - ( 5.0, 0.0, 2.0 ), - ( 5.5, -0.5, 2.0 ), - ( 6.0, 0.5, 2.0 ), - ( 5.5, 1.5, 2.0 ), - ( 5.0, 1.0, 2.0 ) ] +points_mix_coords: list[ tuple[ float ] ] = [ + ( 0.0, 0.0, 0.0 ), ( 1.0, 0.0, 0.0 ), ( 2.0, 0.0, 0.0 ), ( 2.5, -0.5, 0.0 ), ( 3.0, 0.0, 0.0 ), ( 3.5, -0.5, 0.0 ), + ( 4.0, 0.0, 0.0 ), ( 4.5, -0.5, 0.0 ), ( 5.0, 0.0, 0.0 ), ( 5.5, -0.5, 0.0 ), ( 6.0, 0.5, 0.0 ), ( 0.0, 1.0, 0.0 ), + ( 1.0, 1.0, 0.0 ), ( 2.0, 1.0, 0.0 ), ( 2.5, 1.5, 0.0 ), ( 3.0, 1.0, 0.0 ), ( 4.0, 1.0, 0.0 ), ( 5.0, 1.0, 0.0 ), + ( 5.5, 1.5, 0.0 ), ( 0.0, 0.0, 1.0 ), ( 1.0, 0.0, 1.0 ), ( 2.0, 0.0, 1.0 ), ( 2.5, -0.5, 1.0 ), ( 3.0, 0.0, 1.0 ), + ( 3.5, -0.5, 1.0 ), ( 4.0, 0.0, 1.0 ), ( 4.5, -0.5, 1.0 ), ( 5.0, 0.0, 1.0 ), ( 5.5, -0.5, 1.0 ), ( 6.0, 0.5, 1.0 ), + ( 0.0, 1.0, 1.0 ), ( 1.0, 1.0, 1.0 ), ( 2.0, 1.0, 1.0 ), ( 2.5, 1.5, 1.0 ), ( 3.0, 1.0, 1.0 ), ( 4.0, 1.0, 1.0 ), + ( 5.0, 1.0, 1.0 ), ( 5.5, 1.5, 1.0 ), ( 0.5, 0.5, 2.0 ), ( 0.5, 1.5, 2.0 ), ( 1.5, 0.5, 2.0 ), ( 1.5, 1.5, 2.0 ), + ( 2.0, 0.0, 2.0 ), ( 2.5, -0.5, 2.0 ), ( 3.0, 0.0, 2.0 ), ( 3.0, 1.0, 2.0 ), ( 2.5, 1.5, 2.0 ), ( 2.0, 1.0, 2.0 ), + ( 5.0, 0.0, 2.0 ), ( 5.5, -0.5, 2.0 ), ( 6.0, 0.5, 2.0 ), ( 5.5, 1.5, 2.0 ), ( 5.0, 1.0, 2.0 ) +] for point_mix in points_mix_coords: points_mix.InsertNextPoint( point_mix ) @@ -783,18 +611,20 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int # this mix grid has only valid cell volumes mix_grid = vtkUnstructuredGrid() mix_grid.SetPoints( points_mix ) -all_cell_types_mix_grid = [ VTK_HEXAHEDRON, VTK_HEXAHEDRON, VTK_PYRAMID, VTK_PYRAMID, VTK_TETRA, VTK_TETRA, - VTK_HEXAGONAL_PRISM, VTK_HEXAGONAL_PRISM, VTK_VOXEL, VTK_VOXEL, VTK_WEDGE, VTK_WEDGE, - VTK_PENTAGONAL_PRISM, VTK_PENTAGONAL_PRISM ] -all_cells_mix_grid = [ mix_hex1, mix_hex2, mix_pyram1, mix_pyram2, mix_tetra1, mix_tetra2, - mix_hex_prism1, mix_hex_prism2, mix_voxel1, mix_voxel2, mix_wedge1, mix_wedge2, - mix_penta_prism1, mix_penta_prism2 ] +all_cell_types_mix_grid = [ + VTK_HEXAHEDRON, VTK_HEXAHEDRON, VTK_PYRAMID, VTK_PYRAMID, VTK_TETRA, VTK_TETRA, VTK_HEXAGONAL_PRISM, + VTK_HEXAGONAL_PRISM, VTK_VOXEL, VTK_VOXEL, VTK_WEDGE, VTK_WEDGE, VTK_PENTAGONAL_PRISM, VTK_PENTAGONAL_PRISM +] +all_cells_mix_grid = [ + mix_hex1, mix_hex2, mix_pyram1, mix_pyram2, mix_tetra1, mix_tetra2, mix_hex_prism1, mix_hex_prism2, mix_voxel1, + mix_voxel2, mix_wedge1, mix_wedge2, mix_penta_prism1, mix_penta_prism2 +] for cell_type, cell in zip( all_cell_types_mix_grid, all_cells_mix_grid ): mix_grid.InsertNextCell( cell_type, cell.GetPointIds() ) # this mix grid has one invalid cell for each different element type mix_grid_invalid = vtkUnstructuredGrid() -mix_grid_invalid.DeepCopy(mix_grid) +mix_grid_invalid.DeepCopy( mix_grid ) for i in range( len( all_cell_types_mix_grid ) // 2 ): reorder_cell_nodes( mix_grid_invalid, i * 2 + 1, to_change_order[ all_cell_types_mix_grid[ i * 2 + 1 ] ] ) @@ -802,84 +632,70 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int class TestClass: def test_reorder_cell_nodes( self ): - expected_nodes_coords: list[ tuple[ float] ] = [ ( 0.0, 0.0, 0.0 ), - ( 1.0, 0.0, 0.0 ), - ( 1.0, 1.0, 0.0 ), - ( 0.0, 1.0, 0.0 ), - ( 0.0, 0.0, 1.0 ), - ( 1.0, 0.0, 1.0 ), - ( 1.0, 1.0, 1.0 ), - ( 0.0, 1.0, 1.0 ) ] + expected_nodes_coords: list[ tuple[ float ] ] = [ ( 0.0, 0.0, 0.0 ), ( 1.0, 0.0, 0.0 ), ( 1.0, 1.0, 0.0 ), + ( 0.0, 1.0, 0.0 ), ( 0.0, 0.0, 1.0 ), ( 1.0, 0.0, 1.0 ), + ( 1.0, 1.0, 1.0 ), ( 0.0, 1.0, 1.0 ) ] for i in range( one_hex.GetCell( 0 ).GetNumberOfPoints() ): assert one_hex.GetCell( 0 ).GetPoints().GetPoint( i ) == expected_nodes_coords[ i ] # reorder the cell to make it invalid reorder_cell_nodes( one_hex, 0, to_change_order[ VTK_HEXAHEDRON ] ) - expected_nodes_coords_modified = [ ( 0.0, 0.0, 0.0 ), - ( 0.0, 1.0, 0.0 ), - ( 1.0, 1.0, 0.0 ), - ( 1.0, 0.0, 0.0 ), - ( 0.0, 0.0, 1.0 ), - ( 1.0, 0.0, 1.0 ), - ( 1.0, 1.0, 1.0 ), - ( 0.0, 1.0, 1.0 ) ] + expected_nodes_coords_modified = [ ( 0.0, 0.0, 0.0 ), ( 0.0, 1.0, 0.0 ), ( 1.0, 1.0, 0.0 ), ( 1.0, 0.0, 0.0 ), + ( 0.0, 0.0, 1.0 ), ( 1.0, 0.0, 1.0 ), ( 1.0, 1.0, 1.0 ), ( 0.0, 1.0, 1.0 ) ] for i in range( one_hex.GetCell( 0 ).GetNumberOfPoints() ): assert one_hex.GetCell( 0 ).GetPoints().GetPoint( i ) == expected_nodes_coords_modified[ i ] # reorder the cell again to make it valid again reorder_cell_nodes( one_hex, 0, to_change_order[ VTK_HEXAHEDRON ] ) - expected_nodes_coords_modified2 = [ ( 0.0, 0.0, 0.0 ), - ( 1.0, 0.0, 0.0 ), - ( 1.0, 1.0, 0.0 ), - ( 0.0, 1.0, 0.0 ), - ( 0.0, 0.0, 1.0 ), - ( 1.0, 0.0, 1.0 ), - ( 1.0, 1.0, 1.0 ), - ( 0.0, 1.0, 1.0 ) ] + expected_nodes_coords_modified2 = [ ( 0.0, 0.0, 0.0 ), ( 1.0, 0.0, 0.0 ), ( 1.0, 1.0, 0.0 ), ( 0.0, 1.0, 0.0 ), + ( 0.0, 0.0, 1.0 ), ( 1.0, 0.0, 1.0 ), ( 1.0, 1.0, 1.0 ), ( 0.0, 1.0, 1.0 ) ] for i in range( one_hex.GetCell( 0 ).GetNumberOfPoints() ): assert one_hex.GetCell( 0 ).GetPoints().GetPoint( i ) == expected_nodes_coords_modified2[ i ] def test_compute_mesh_cells_volume( self ): - grid_volumes = { hexahedrons_grid: [ 1.0, 1.0, 1.0, 1.0 ], - hexahedrons_grid_invalid: [ 1.0, -0.333, 1.0, -0.333 ], - tetras_grid: [ 0.167, 0.167, 0.167, 0.167 ], - tetras_grid_invalid: [ 0.167, -0.167, 0.167, -0.167 ], - pyramids_grid: [ 0.333, 0.333, 0.333, 0.333 ], - pyramids_grid_invalid: [ 0.333, -0.333, 0.333, -0.333 ], - voxels_grid: [ 1.0, 1.0, 1.0, 1.0 ], - voxels_grid_invalid: [ 1.0, 1.0, 1.0, 1.0 ], - wedges_grid: [ 0.5, 0.5, 0.5, 0.5 ], - wedges_grid_invalid: [ 0.5, -0.167, 0.5, -0.167 ], - penta_prism_grid: [ 1.25, 1.25, 1.25, 1.25 ], - penta_prism_grid_invalid: [ 1.25, -0.083, 1.25, -0.083 ], - hexa_prism_grid: [ 1.5, 1.5, 1.5, 1.5 ], - hexa_prism_grid_invalid: [ 1.5, -0.333, 1.5, -0.333 ], - mix_grid: [ 1.0, 1.0, 0.333, 0.333, 0.167, 0.167, 1.5, - 1.5, 1.0, 1.0, 0.25, 0.25, 1.25, 1.25 ], - mix_grid_invalid: [ 1.0, -0.333, 0.333, -0.333, 0.167, -0.167, 1.5, - -0.333, 1.0, 1.0, 0.25, -0.083, 1.25, -0.083 ] } + grid_volumes = { + hexahedrons_grid: [ 1.0, 1.0, 1.0, 1.0 ], + hexahedrons_grid_invalid: [ 1.0, -0.333, 1.0, -0.333 ], + tetras_grid: [ 0.167, 0.167, 0.167, 0.167 ], + tetras_grid_invalid: [ 0.167, -0.167, 0.167, -0.167 ], + pyramids_grid: [ 0.333, 0.333, 0.333, 0.333 ], + pyramids_grid_invalid: [ 0.333, -0.333, 0.333, -0.333 ], + voxels_grid: [ 1.0, 1.0, 1.0, 1.0 ], + voxels_grid_invalid: [ 1.0, 1.0, 1.0, 1.0 ], + wedges_grid: [ 0.5, 0.5, 0.5, 0.5 ], + wedges_grid_invalid: [ 0.5, -0.167, 0.5, -0.167 ], + penta_prism_grid: [ 1.25, 1.25, 1.25, 1.25 ], + penta_prism_grid_invalid: [ 1.25, -0.083, 1.25, -0.083 ], + hexa_prism_grid: [ 1.5, 1.5, 1.5, 1.5 ], + hexa_prism_grid_invalid: [ 1.5, -0.333, 1.5, -0.333 ], + mix_grid: [ 1.0, 1.0, 0.333, 0.333, 0.167, 0.167, 1.5, 1.5, 1.0, 1.0, 0.25, 0.25, 1.25, 1.25 ], + mix_grid_invalid: + [ 1.0, -0.333, 0.333, -0.333, 0.167, -0.167, 1.5, -0.333, 1.0, 1.0, 0.25, -0.083, 1.25, -0.083 ] + } for grid, volumes_expected in grid_volumes.items(): volumes_computed = feo.compute_mesh_cells_volume( grid ) for i in range( len( volumes_computed ) ): assert round( float( volumes_computed[ i ] ), 3 ) == volumes_expected[ i ] def test_is_cell_to_reorder( self ): - grid_needs_ordering = { hexahedrons_grid: [ False ] * 4, - hexahedrons_grid_invalid: [ i % 2 != 0 for i in range( 4 ) ], - tetras_grid: [ False ] * 4, - tetras_grid_invalid: [ i % 2 != 0 for i in range( 4 ) ], - pyramids_grid: [ False ] * 4, - pyramids_grid_invalid: [ i % 2 != 0 for i in range( 4 ) ], - voxels_grid: [ False ] * 4, - voxels_grid_invalid: [ False ] * 4, - wedges_grid: [ False ] * 4, - wedges_grid_invalid: [ i % 2 != 0 for i in range( 4 ) ], - penta_prism_grid: [ False ] * 4, - penta_prism_grid_invalid: [ i % 2 != 0 for i in range( 4 ) ], - hexa_prism_grid: [ False ] * 4, - hexa_prism_grid_invalid: [ i % 2 != 0 for i in range( 4 ) ], - mix_grid: [ False ] * 14, - mix_grid_invalid: [ i % 2 != 0 for i in range( 14 ) ] } + grid_needs_ordering = { + hexahedrons_grid: [ False ] * 4, + hexahedrons_grid_invalid: [ i % 2 != 0 for i in range( 4 ) ], + tetras_grid: [ False ] * 4, + tetras_grid_invalid: [ i % 2 != 0 for i in range( 4 ) ], + pyramids_grid: [ False ] * 4, + pyramids_grid_invalid: [ i % 2 != 0 for i in range( 4 ) ], + voxels_grid: [ False ] * 4, + voxels_grid_invalid: [ False ] * 4, + wedges_grid: [ False ] * 4, + wedges_grid_invalid: [ i % 2 != 0 for i in range( 4 ) ], + penta_prism_grid: [ False ] * 4, + penta_prism_grid_invalid: [ i % 2 != 0 for i in range( 4 ) ], + hexa_prism_grid: [ False ] * 4, + hexa_prism_grid_invalid: [ i % 2 != 0 for i in range( 4 ) ], + mix_grid: [ False ] * 14, + mix_grid_invalid: [ i % 2 != 0 for i in range( 14 ) ] + } grid_needs_ordering[ mix_grid_invalid ][ 9 ] = False for grid, needs_ordering in grid_needs_ordering.items(): volumes = feo.compute_mesh_cells_volume( grid ) @@ -903,32 +719,34 @@ def test_get_all_cells_type( self ): assert feo.get_all_cells_type( penta_prism_grid_invalid ).tolist() == [ 15, 15, 15, 15 ] assert feo.get_all_cells_type( hexa_prism_grid ).tolist() == [ 16, 16, 16, 16 ] assert feo.get_all_cells_type( hexa_prism_grid_invalid ).tolist() == [ 16, 16, 16, 16 ] - assert feo.get_all_cells_type( mix_grid ).tolist() == [ 12, 12, 14, 14, 10, 10, 16, - 16, 11, 11, 13, 13, 15, 15 ] - assert feo.get_all_cells_type( mix_grid_invalid ).tolist() == [ 12, 12, 14, 14, 10, 10, 16, - 16, 11, 11, 13, 13, 15, 15 ] + assert feo.get_all_cells_type( mix_grid ).tolist() == [ 12, 12, 14, 14, 10, 10, 16, 16, 11, 11, 13, 13, 15, 15 ] + assert feo.get_all_cells_type( mix_grid_invalid ).tolist() == [ + 12, 12, 14, 14, 10, 10, 16, 16, 11, 11, 13, 13, 15, 15 + ] def test_get_cell_ids_to_check( self ): options = opt( out, to_change_order, "negative" ) # single element grids - grid_cell_type = { hexahedrons_grid: VTK_HEXAHEDRON, - hexahedrons_grid_invalid: VTK_HEXAHEDRON, - tetras_grid: VTK_TETRA, - tetras_grid_invalid: VTK_TETRA, - pyramids_grid: VTK_PYRAMID, - pyramids_grid_invalid: VTK_PYRAMID, - voxels_grid: VTK_VOXEL, - voxels_grid_invalid: VTK_VOXEL, - wedges_grid: VTK_WEDGE, - wedges_grid_invalid: VTK_WEDGE, - penta_prism_grid: VTK_PENTAGONAL_PRISM, - penta_prism_grid_invalid: VTK_PENTAGONAL_PRISM, - hexa_prism_grid: VTK_HEXAGONAL_PRISM, - hexa_prism_grid_invalid: VTK_HEXAGONAL_PRISM } + grid_cell_type = { + hexahedrons_grid: VTK_HEXAHEDRON, + hexahedrons_grid_invalid: VTK_HEXAHEDRON, + tetras_grid: VTK_TETRA, + tetras_grid_invalid: VTK_TETRA, + pyramids_grid: VTK_PYRAMID, + pyramids_grid_invalid: VTK_PYRAMID, + voxels_grid: VTK_VOXEL, + voxels_grid_invalid: VTK_VOXEL, + wedges_grid: VTK_WEDGE, + wedges_grid_invalid: VTK_WEDGE, + penta_prism_grid: VTK_PENTAGONAL_PRISM, + penta_prism_grid_invalid: VTK_PENTAGONAL_PRISM, + hexa_prism_grid: VTK_HEXAGONAL_PRISM, + hexa_prism_grid_invalid: VTK_HEXAGONAL_PRISM + } for grid, cell_type in grid_cell_type.items(): all_cells_type = feo.get_all_cells_type( grid ) result = feo.get_cell_ids_to_check( all_cells_type, options ) - expected = { cell_type: np.array([ 0, 1, 2, 3 ]) } + expected = { cell_type: np.array( [ 0, 1, 2, 3 ] ) } for cell_type_result, cell_type_expected in zip( result.keys(), expected.keys() ): assert cell_type_result == cell_type_expected for cell_indexes_result, cell_indexes_expected in zip( result.values(), expected.values() ): @@ -937,11 +755,17 @@ def test_get_cell_ids_to_check( self ): # mix elements grid all_cells_type_mix = feo.get_all_cells_type( mix_grid ) result = feo.get_cell_ids_to_check( all_cells_type_mix, options ) - result = dict( sorted( result.items() ) ) - expected = { 12: np.array( [ 0, 1 ] ), 14: np.array( [ 2, 3 ] ), 10: np.array( [ 4, 5 ] ), - 16: np.array( [ 6, 7 ] ), 11: np.array( [ 8, 9 ] ), 13: np.array( [ 10, 11 ] ), - 15: np.array( [ 12, 13 ] ) } - expected = dict( sorted( expected.items() ) ) + result = dict( sorted( result.items() ) ) + expected = { + 12: np.array( [ 0, 1 ] ), + 14: np.array( [ 2, 3 ] ), + 10: np.array( [ 4, 5 ] ), + 16: np.array( [ 6, 7 ] ), + 11: np.array( [ 8, 9 ] ), + 13: np.array( [ 10, 11 ] ), + 15: np.array( [ 12, 13 ] ) + } + expected = dict( sorted( expected.items() ) ) for cell_type_result, cell_type_expected in zip( result.keys(), expected.keys() ): assert cell_type_result == cell_type_expected for cell_indexes_result, cell_indexes_expected in zip( result.values(), expected.values() ): @@ -950,20 +774,24 @@ def test_get_cell_ids_to_check( self ): def test_reorder_nodes_to_new_mesh( self ): options = opt( out, to_change_order, "negative" ) # single element grids except voxels because volume is always positive - grid_cell_type = { hexahedrons_grid_invalid: VTK_HEXAHEDRON, - tetras_grid_invalid: VTK_TETRA, - pyramids_grid_invalid: VTK_PYRAMID, - wedges_grid_invalid: VTK_WEDGE, - penta_prism_grid_invalid: VTK_PENTAGONAL_PRISM, - hexa_prism_grid_invalid: VTK_HEXAGONAL_PRISM } + grid_cell_type = { + hexahedrons_grid_invalid: VTK_HEXAHEDRON, + tetras_grid_invalid: VTK_TETRA, + pyramids_grid_invalid: VTK_PYRAMID, + wedges_grid_invalid: VTK_WEDGE, + penta_prism_grid_invalid: VTK_PENTAGONAL_PRISM, + hexa_prism_grid_invalid: VTK_HEXAGONAL_PRISM + } for grid, cell_type in grid_cell_type.items(): new_invalid = vtkUnstructuredGrid() new_invalid.DeepCopy( grid ) not_use_invalid, reorder_stats = feo.reorder_nodes_to_new_mesh( new_invalid, options ) - expected = { "Types reordered": [ cell_type ], - "Number of cells reordered": [ 2 ], - "Types non reordered": [ cell_type ], - "Number of cells non reordered": [ 2 ] } + expected = { + "Types reordered": [ cell_type ], + "Number of cells reordered": [ 2 ], + "Types non reordered": [ cell_type ], + "Number of cells non reordered": [ 2 ] + } for prop in expected.keys(): assert reorder_stats[ prop ] == expected[ prop ] @@ -971,10 +799,12 @@ def test_reorder_nodes_to_new_mesh( self ): voxels_invalid = vtkUnstructuredGrid() voxels_invalid.DeepCopy( voxels_grid_invalid ) not_use_invalid, voxels_stats = feo.reorder_nodes_to_new_mesh( voxels_invalid, options ) - expected = { "Types reordered": [], - "Number of cells reordered": [], - "Types non reordered": [ 11 ], - "Number of cells non reordered": [ 4 ] } + expected = { + "Types reordered": [], + "Number of cells reordered": [], + "Types non reordered": [ 11 ], + "Number of cells non reordered": [ 4 ] + } for prop in expected.keys(): assert voxels_stats[ prop ] == expected[ prop ] @@ -982,9 +812,11 @@ def test_reorder_nodes_to_new_mesh( self ): mix_invalid = vtkUnstructuredGrid() mix_invalid.DeepCopy( mix_grid_invalid ) not_use_invalid, mix_stats = feo.reorder_nodes_to_new_mesh( mix_invalid, options ) - expected = { "Types reordered": [ 10, 12, 13, 14, 15, 16 ], - "Number of cells reordered": [ 1, 1, 1, 1, 1, 1 ], - "Types non reordered": [ 10, 11, 12, 13, 14, 15, 16 ], - "Number of cells non reordered": [ 1, 2, 1, 1, 1, 1, 1 ] } + expected = { + "Types reordered": [ 10, 12, 13, 14, 15, 16 ], + "Number of cells reordered": [ 1, 1, 1, 1, 1, 1 ], + "Types non reordered": [ 10, 11, 12, 13, 14, 15, 16 ], + "Number of cells non reordered": [ 1, 2, 1, 1, 1, 1, 1 ] + } for prop in expected.keys(): - assert mix_stats[ prop ] == expected[ prop ] \ No newline at end of file + assert mix_stats[ prop ] == expected[ prop ] From 7ea0e42dfa6e30ce393a86d0c417f394e42ff420 Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Thu, 5 Sep 2024 11:32:59 -0700 Subject: [PATCH 11/15] Voxels cannot be used as input for geos mesh --- .../doctor/checks/fix_elements_orderings.py | 22 +++++++++++++------ .../parsing/fix_elements_orderings_parsing.py | 19 ++++++++-------- 2 files changed, 24 insertions(+), 17 deletions(-) diff --git a/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py b/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py index 6b7971b6..115645a8 100644 --- a/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py +++ b/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py @@ -1,5 +1,4 @@ from dataclasses import dataclass -import logging import numpy as np from typing import ( List, @@ -37,6 +36,10 @@ class Result: } +# Knowing the calculation of cell volumes in vtk was discussed there: https://github.com/GEOS-DEV/GEOS/issues/2253 +# Here, we do not need to have the exact volumes matching the simulation softwares results +# because we are mostly interested in knowing the sign of the volume for the rest of the workflow. +# Therefore, there is no need to use vtkMeshQuality for specific cell types when vtkCellSizeFilter works with all types. def compute_mesh_cells_volume( mesh: vtkDataSet ) -> np.array: """Generates a volume array that was calculated on all cells of a mesh. @@ -122,17 +125,22 @@ def reorder_nodes_to_new_mesh( old_mesh: vtkDataSet, options: Options ) -> tuple Returns: tuple: ( vtkDataSet, reordering_stats ) """ + # gets all the cell types found in the mesh + all_cells_type: np.array = get_all_cells_type( old_mesh ) + unique_cell_types, total_per_cell_types = np.unique( all_cells_type, return_counts=True ) + unique_cell_types, total_per_cell_types = unique_cell_types.tolist(), total_per_cell_types.tolist() + # voxel elements are not accepted as input for GEOS mesh, so no need to perform reordering + if VTK_VOXEL in unique_cell_types: + raise ValueError( "Voxel elements were found in the grid. This element cannot be used in GEOS. Dying ..." ) + # volumes and cell ids that have the element type suggested for reordering are collected + all_cells_volume: np.array = compute_mesh_cells_volume( old_mesh ) + cell_ids_to_check: dict[ int, np.array ] = get_cell_ids_to_check( all_cells_type, options ) # a new mesh with the same data is created from the old mesh new_mesh: vtkDataSet = old_mesh.NewInstance() new_mesh.CopyStructure( old_mesh ) new_mesh.CopyAttributes( old_mesh ) cells = new_mesh.GetCells() - # this part will get the cell ids that need to be verified and if invalid, to have their nodes be reodered - all_cells_volume: np.array = compute_mesh_cells_volume( new_mesh ) - all_cells_type: np.array = get_all_cells_type( new_mesh ) - cell_ids_to_check: dict[ int, np.array ] = get_cell_ids_to_check( all_cells_type, options ) - unique_cell_types, total_per_cell_types = np.unique( all_cells_type, return_counts=True ) - unique_cell_types, total_per_cell_types = unique_cell_types.tolist(), total_per_cell_types.tolist() + # Statistics on how many cells have or have not been reordered reordering_stats: dict[ str, list[ any ] ] = { "Types reordered": [], "Number of cells reordered": [], diff --git a/geos-mesh/src/geos/mesh/doctor/parsing/fix_elements_orderings_parsing.py b/geos-mesh/src/geos/mesh/doctor/parsing/fix_elements_orderings_parsing.py index 8fdaf63d..f9eb085f 100644 --- a/geos-mesh/src/geos/mesh/doctor/parsing/fix_elements_orderings_parsing.py +++ b/geos-mesh/src/geos/mesh/doctor/parsing/fix_elements_orderings_parsing.py @@ -19,7 +19,6 @@ "Prism6": VTK_HEXAGONAL_PRISM, "Pyramid": VTK_PYRAMID, "Tetrahedron": VTK_TETRA, - "Voxel": VTK_VOXEL, "Wedge": VTK_WEDGE, } @@ -54,7 +53,7 @@ def fill_subparser( subparsers ) -> None: metavar=__VOLUME_TO_REORDER_DEFAULT, default=__VOLUME_TO_REORDER_DEFAULT, choices=__VOLUME_TO_REORDER_CHOICES, - required=False, + required=True, help= "[str]: Select which element volume is invalid and needs reordering." + "'all' will allow reordering of nodes for every element, regarding of their volume." + "'positive' or 'negative' will only reorder the element with the corresponding volume." ) @@ -90,13 +89,13 @@ def display_results( options: Options, result: Result ): logging.info( f"New mesh was written to file '{result.output}'" ) else: logging.info( "No output file was written." ) - logging.info( f"Number of cells reordered :" ) + logging.info( f"Number of cells reordered:" ) logging.info( f"\tCellType\tNumber" ) - for i in range( result.reordering_stats[ "Types reordered" ] ): - logging.info( f"\t{result.reordering_stats[ "Types reordered" ][ i ]}" - + f"\t{result.reordering_stats[ "Number of cells reordered" ][ i ]}" ) - logging.info( f"Number of cells non reordered :" ) + for i in range( len( result.reordering_stats[ "Types reordered" ] ) ): + logging.info( f"\t{result.reordering_stats[ "Types reordered" ][ i ]}" + + f"\t\t{result.reordering_stats[ "Number of cells reordered" ][ i ]}" ) + logging.info( f"Number of cells non reordered:" ) logging.info( f"\tCellType\tNumber" ) - for i in range( result.reordering_stats[ "Types non reordered" ] ): - logging.info( f"\t{result.reordering_stats[ "Types non reordered" ][ i ]}" - + f"\t{result.reordering_stats[ "Number of cells non reordered" ][ i ]}" ) + for i in range( len( result.reordering_stats[ "Types non reordered" ] ) ): + logging.info( f"\t{result.reordering_stats[ "Types non reordered" ][ i ]}" + + f"\t\t{result.reordering_stats[ "Number of cells non reordered" ][ i ]}" ) From 62799e190b7b6e140fb2733fe999b2130228c51b Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Thu, 5 Sep 2024 11:33:51 -0700 Subject: [PATCH 12/15] Test execution of command line execution and voxels prohibition taken into account --- geos-mesh/src/geos/mesh/doctor/mesh_doctor.py | 12 +- .../tests/test_fix_elements_orderings.py | 132 ++++++++++++------ 2 files changed, 92 insertions(+), 52 deletions(-) diff --git a/geos-mesh/src/geos/mesh/doctor/mesh_doctor.py b/geos-mesh/src/geos/mesh/doctor/mesh_doctor.py index ea1bfe8a..1311145f 100644 --- a/geos-mesh/src/geos/mesh/doctor/mesh_doctor.py +++ b/geos-mesh/src/geos/mesh/doctor/mesh_doctor.py @@ -1,17 +1,17 @@ import sys +import logging +from geos.mesh.doctor.parsing import CheckHelper +from geos.mesh.doctor.parsing.cli_parsing import parse_and_set_verbosity +import geos.mesh.doctor.register as register +min_python_version = ( 3, 7 ) try: - min_python_version = ( 3, 7 ) assert sys.version_info >= min_python_version except AssertionError as e: print( f"Please update python to at least version {'.'.join(map(str, min_python_version))}." ) sys.exit( 1 ) -import logging - -from geos.mesh.doctor.parsing import CheckHelper -from geos.mesh.doctor.parsing.cli_parsing import parse_and_set_verbosity -import geos.mesh.doctor.register as register +MESH_DOCTOR_FILEPATH = __file__ def main(): diff --git a/geos-mesh/tests/test_fix_elements_orderings.py b/geos-mesh/tests/test_fix_elements_orderings.py index 47ec2a04..34e26298 100644 --- a/geos-mesh/tests/test_fix_elements_orderings.py +++ b/geos-mesh/tests/test_fix_elements_orderings.py @@ -1,8 +1,13 @@ -import numpy as np +import os +import re +import pytest import logging +import subprocess +import numpy as np +from geos.mesh.doctor.mesh_doctor import MESH_DOCTOR_FILEPATH from geos.mesh.doctor.checks import fix_elements_orderings as feo from geos.mesh.doctor.checks.generate_cube import Options, __build -from geos.mesh.doctor.checks.vtk_utils import VtkOutput, to_vtk_id_list +from geos.mesh.doctor.checks.vtk_utils import VtkOutput, to_vtk_id_list, write_mesh from geos.mesh.doctor.checks.fix_elements_orderings import Options as opt from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints from vtkmodules.vtkCommonDataModel import ( vtkDataSet, vtkUnstructuredGrid, vtkCellArray, vtkHexahedron, vtkTetra, @@ -32,6 +37,14 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int cells.ReplaceCellAtId( cell_id, to_vtk_id_list( new_support_point_ids ) ) +""" +For creation of output test meshes +""" +current_file_path: str = __file__ +dir_name: str = os.path.dirname( current_file_path ) +filepath_non_ordered_mesh: str = os.path.join( dir_name, "to_reorder_mesh.vtu" ) +filepath_reordered_mesh: str = os.path.join( dir_name, "reordered_mesh.vtu" ) +test_file: VtkOutput = VtkOutput( filepath_non_ordered_mesh, True ) """ Dict used to apply false nodes orderings for test purposes """ @@ -188,7 +201,7 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int for i in range( 2 ): reorder_cell_nodes( pyramids_grid_invalid, i * 2 + 1, to_change_order[ VTK_PYRAMID ] ) """ -4 voxels +4 voxels: this type of element cannot be used in GEOS, we just test that the feature rejects them """ points_voxels: vtkPoints = vtkPoints() points_voxels_coords: list[ tuple[ float ] ] = [ ( 0.0, 0.0, 0.0 ), ( 1.0, 0.0, 0.0 ), ( 1.0, 1.0, 0.0 ), @@ -494,6 +507,26 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int mix_hex2.GetPointIds().SetId( 6, 32 ) mix_hex2.GetPointIds().SetId( 7, 31 ) +mix_hex3: vtkHexahedron = vtkHexahedron() +mix_hex3.GetPointIds().SetId( 0, 4 ) +mix_hex3.GetPointIds().SetId( 1, 6 ) +mix_hex3.GetPointIds().SetId( 2, 16 ) +mix_hex3.GetPointIds().SetId( 3, 15 ) +mix_hex3.GetPointIds().SetId( 4, 23 ) +mix_hex3.GetPointIds().SetId( 5, 25 ) +mix_hex3.GetPointIds().SetId( 6, 35 ) +mix_hex3.GetPointIds().SetId( 7, 34 ) + +mix_hex4: vtkHexahedron = vtkHexahedron() +mix_hex4.GetPointIds().SetId( 0, 6 ) +mix_hex4.GetPointIds().SetId( 1, 8 ) +mix_hex4.GetPointIds().SetId( 2, 17 ) +mix_hex4.GetPointIds().SetId( 3, 16 ) +mix_hex4.GetPointIds().SetId( 4, 25 ) +mix_hex4.GetPointIds().SetId( 5, 27 ) +mix_hex4.GetPointIds().SetId( 6, 36 ) +mix_hex4.GetPointIds().SetId( 7, 35 ) + mix_pyram1: vtkPyramid = vtkPyramid() mix_pyram1.GetPointIds().SetId( 0, 19 ) mix_pyram1.GetPointIds().SetId( 1, 20 ) @@ -548,26 +581,6 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int mix_hex_prism2.GetPointIds().SetId( 10, 46 ) mix_hex_prism2.GetPointIds().SetId( 11, 47 ) -mix_voxel1: vtkVoxel = vtkVoxel() -mix_voxel1.GetPointIds().SetId( 0, 4 ) -mix_voxel1.GetPointIds().SetId( 1, 6 ) -mix_voxel1.GetPointIds().SetId( 2, 15 ) -mix_voxel1.GetPointIds().SetId( 3, 16 ) -mix_voxel1.GetPointIds().SetId( 4, 23 ) -mix_voxel1.GetPointIds().SetId( 5, 25 ) -mix_voxel1.GetPointIds().SetId( 6, 34 ) -mix_voxel1.GetPointIds().SetId( 7, 35 ) - -mix_voxel2: vtkVoxel = vtkVoxel() -mix_voxel2.GetPointIds().SetId( 0, 6 ) -mix_voxel2.GetPointIds().SetId( 1, 8 ) -mix_voxel2.GetPointIds().SetId( 2, 16 ) -mix_voxel2.GetPointIds().SetId( 3, 17 ) -mix_voxel2.GetPointIds().SetId( 4, 25 ) -mix_voxel2.GetPointIds().SetId( 5, 27 ) -mix_voxel2.GetPointIds().SetId( 6, 35 ) -mix_voxel2.GetPointIds().SetId( 7, 36 ) - mix_wedge1: vtkWedge = vtkWedge() mix_wedge1.GetPointIds().SetId( 0, 23 ) mix_wedge1.GetPointIds().SetId( 1, 24 ) @@ -613,11 +626,12 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int mix_grid.SetPoints( points_mix ) all_cell_types_mix_grid = [ VTK_HEXAHEDRON, VTK_HEXAHEDRON, VTK_PYRAMID, VTK_PYRAMID, VTK_TETRA, VTK_TETRA, VTK_HEXAGONAL_PRISM, - VTK_HEXAGONAL_PRISM, VTK_VOXEL, VTK_VOXEL, VTK_WEDGE, VTK_WEDGE, VTK_PENTAGONAL_PRISM, VTK_PENTAGONAL_PRISM + VTK_HEXAGONAL_PRISM, VTK_HEXAHEDRON, VTK_HEXAHEDRON, VTK_WEDGE, VTK_WEDGE, VTK_PENTAGONAL_PRISM, + VTK_PENTAGONAL_PRISM ] all_cells_mix_grid = [ - mix_hex1, mix_hex2, mix_pyram1, mix_pyram2, mix_tetra1, mix_tetra2, mix_hex_prism1, mix_hex_prism2, mix_voxel1, - mix_voxel2, mix_wedge1, mix_wedge2, mix_penta_prism1, mix_penta_prism2 + mix_hex1, mix_hex2, mix_pyram1, mix_pyram2, mix_tetra1, mix_tetra2, mix_hex_prism1, mix_hex_prism2, mix_hex3, + mix_hex4, mix_wedge1, mix_wedge2, mix_penta_prism1, mix_penta_prism2 ] for cell_type, cell in zip( all_cell_types_mix_grid, all_cells_mix_grid ): mix_grid.InsertNextCell( cell_type, cell.GetPointIds() ) @@ -670,7 +684,7 @@ def test_compute_mesh_cells_volume( self ): hexa_prism_grid_invalid: [ 1.5, -0.333, 1.5, -0.333 ], mix_grid: [ 1.0, 1.0, 0.333, 0.333, 0.167, 0.167, 1.5, 1.5, 1.0, 1.0, 0.25, 0.25, 1.25, 1.25 ], mix_grid_invalid: - [ 1.0, -0.333, 0.333, -0.333, 0.167, -0.167, 1.5, -0.333, 1.0, 1.0, 0.25, -0.083, 1.25, -0.083 ] + [ 1.0, -0.333, 0.333, -0.333, 0.167, -0.167, 1.5, -0.333, 1.0, -0.333, 0.25, -0.083, 1.25, -0.083 ] } for grid, volumes_expected in grid_volumes.items(): volumes_computed = feo.compute_mesh_cells_volume( grid ) @@ -696,7 +710,6 @@ def test_is_cell_to_reorder( self ): mix_grid: [ False ] * 14, mix_grid_invalid: [ i % 2 != 0 for i in range( 14 ) ] } - grid_needs_ordering[ mix_grid_invalid ][ 9 ] = False for grid, needs_ordering in grid_needs_ordering.items(): volumes = feo.compute_mesh_cells_volume( grid ) for i in range( len( volumes ) ): @@ -719,9 +732,9 @@ def test_get_all_cells_type( self ): assert feo.get_all_cells_type( penta_prism_grid_invalid ).tolist() == [ 15, 15, 15, 15 ] assert feo.get_all_cells_type( hexa_prism_grid ).tolist() == [ 16, 16, 16, 16 ] assert feo.get_all_cells_type( hexa_prism_grid_invalid ).tolist() == [ 16, 16, 16, 16 ] - assert feo.get_all_cells_type( mix_grid ).tolist() == [ 12, 12, 14, 14, 10, 10, 16, 16, 11, 11, 13, 13, 15, 15 ] + assert feo.get_all_cells_type( mix_grid ).tolist() == [ 12, 12, 14, 14, 10, 10, 16, 16, 12, 12, 13, 13, 15, 15 ] assert feo.get_all_cells_type( mix_grid_invalid ).tolist() == [ - 12, 12, 14, 14, 10, 10, 16, 16, 11, 11, 13, 13, 15, 15 + 12, 12, 14, 14, 10, 10, 16, 16, 12, 12, 13, 13, 15, 15 ] def test_get_cell_ids_to_check( self ): @@ -757,11 +770,10 @@ def test_get_cell_ids_to_check( self ): result = feo.get_cell_ids_to_check( all_cells_type_mix, options ) result = dict( sorted( result.items() ) ) expected = { - 12: np.array( [ 0, 1 ] ), + 12: np.array( [ 0, 1, 8, 9 ] ), 14: np.array( [ 2, 3 ] ), 10: np.array( [ 4, 5 ] ), 16: np.array( [ 6, 7 ] ), - 11: np.array( [ 8, 9 ] ), 13: np.array( [ 10, 11 ] ), 15: np.array( [ 12, 13 ] ) } @@ -773,7 +785,7 @@ def test_get_cell_ids_to_check( self ): def test_reorder_nodes_to_new_mesh( self ): options = opt( out, to_change_order, "negative" ) - # single element grids except voxels because volume is always positive + # single element grids except voxels because it is an invalid cell type for GEOS grid_cell_type = { hexahedrons_grid_invalid: VTK_HEXAHEDRON, tetras_grid_invalid: VTK_TETRA, @@ -795,18 +807,12 @@ def test_reorder_nodes_to_new_mesh( self ): for prop in expected.keys(): assert reorder_stats[ prop ] == expected[ prop ] - # voxel elements grid + # voxel elements grid to check if raise ValueError was correctly called voxels_invalid = vtkUnstructuredGrid() voxels_invalid.DeepCopy( voxels_grid_invalid ) - not_use_invalid, voxels_stats = feo.reorder_nodes_to_new_mesh( voxels_invalid, options ) - expected = { - "Types reordered": [], - "Number of cells reordered": [], - "Types non reordered": [ 11 ], - "Number of cells non reordered": [ 4 ] - } - for prop in expected.keys(): - assert voxels_stats[ prop ] == expected[ prop ] + expected_error: str = "Voxel elements were found in the grid. This element cannot be used in GEOS. Dying ..." + with pytest.raises( ValueError, match=expected_error ): + not_use_invalid, reorder_stats = feo.reorder_nodes_to_new_mesh( voxels_invalid, options ) # mix elements grid mix_invalid = vtkUnstructuredGrid() @@ -814,9 +820,43 @@ def test_reorder_nodes_to_new_mesh( self ): not_use_invalid, mix_stats = feo.reorder_nodes_to_new_mesh( mix_invalid, options ) expected = { "Types reordered": [ 10, 12, 13, 14, 15, 16 ], - "Number of cells reordered": [ 1, 1, 1, 1, 1, 1 ], - "Types non reordered": [ 10, 11, 12, 13, 14, 15, 16 ], - "Number of cells non reordered": [ 1, 2, 1, 1, 1, 1, 1 ] + "Number of cells reordered": [ 1, 2, 1, 1, 1, 1 ], + "Types non reordered": [ 10, 12, 13, 14, 15, 16 ], + "Number of cells non reordered": [ 1, 2, 1, 1, 1, 1 ] } for prop in expected.keys(): assert mix_stats[ prop ] == expected[ prop ] + + def test_fix_elements_orderings_execution( self ): + # for mix_grid_invalid mesh, checks that reordered mesh was created and that reoredring_stats are valid + write_mesh( mix_grid_invalid, test_file ) + command = [ + "python", MESH_DOCTOR_FILEPATH, "-v", "-i", test_file.output, "fix_elements_orderings", "--Hexahedron", + str( to_change_order[ VTK_HEXAHEDRON ] ).replace( "[", "" ).replace( "]", "" ), "--Tetrahedron", + str( to_change_order[ VTK_TETRA ] ).replace( "[", "" ).replace( "]", "" ), "--Pyramid", + str( to_change_order[ VTK_PYRAMID ] ).replace( "[", "" ).replace( "]", "" ), "--Wedge", + str( to_change_order[ VTK_WEDGE ] ).replace( "[", "" ).replace( "]", "" ), "--Wedge", + str( to_change_order[ VTK_WEDGE ] ).replace( "[", "" ).replace( "]", "" ), "--Prism5", + str( to_change_order[ VTK_PENTAGONAL_PRISM ] ).replace( "[", "" ).replace( "]", "" ), "--Prism6", + str( to_change_order[ VTK_HEXAGONAL_PRISM ] ).replace( "[", "" ).replace( "]", "" ), "--volume_to_reorder", + "negative", "--data-mode", "binary", "--output", filepath_reordered_mesh + ] + try: + result = subprocess.run( command, shell=True, stderr=subprocess.PIPE, universal_newlines=True ) + stderr = result.stderr + assert result.returncode == 0 + os.remove( filepath_reordered_mesh ) + raw_stderr = r"{}".format( stderr ) + pattern = r"\[.*?\]\[.*?\] (.*)" + matches = re.findall( pattern, raw_stderr ) + no_log = "\n".join( matches ) + reordering_stats: str = no_log[ no_log.index( "Number of cells reordered" ): ] + expected_stats: str = ( "Number of cells reordered:\n" + "\tCellType\tNumber\n" + "\t12\t\t2\n" + + "\t15\t\t1\n" + "\t16\t\t1\n" + "\t14\t\t1\n" + "\t10\t\t1\n" + "\t13\t\t1\n" + + "Number of cells non reordered:\n" + "\tCellType\tNumber\n" + "\t10\t\t1\n" + + "\t12\t\t2\n" + "\t13\t\t1\n" + "\t14\t\t1\n" + "\t15\t\t1\n" + "\t16\t\t1" ) + assert reordering_stats == expected_stats + except Exception as e: + logging.error( "Invalid command input. Test has failed." ) + logging.error( e ) + os.remove( test_file.output ) From 4e5ea4d025090c65ee851e157ea645840a7406c2 Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Wed, 11 Sep 2024 09:54:58 -0700 Subject: [PATCH 13/15] Parsing improvementsand errors when using invalid cell elements added --- .../doctor/checks/fix_elements_orderings.py | 154 ++++++------ .../parsing/fix_elements_orderings_parsing.py | 75 +++--- .../tests/test_fix_elements_orderings.py | 219 ++++++++---------- 3 files changed, 194 insertions(+), 254 deletions(-) diff --git a/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py b/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py index 115645a8..bc693a99 100644 --- a/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py +++ b/geos-mesh/src/geos/mesh/doctor/checks/fix_elements_orderings.py @@ -1,13 +1,10 @@ from dataclasses import dataclass import numpy as np -from typing import ( - List, - Dict, -) +import logging from vtk import vtkCellSizeFilter from vtkmodules.vtkCommonCore import vtkIdList from vtkmodules.util.numpy_support import vtk_to_numpy -from vtkmodules.vtkCommonDataModel import ( vtkDataSet, VTK_HEXAHEDRON, VTK_TETRA, VTK_PYRAMID, VTK_WEDGE, VTK_VOXEL, +from vtkmodules.vtkCommonDataModel import ( vtkDataSet, VTK_HEXAHEDRON, VTK_TETRA, VTK_PYRAMID, VTK_WEDGE, VTK_PENTAGONAL_PRISM, VTK_HEXAGONAL_PRISM ) from .vtk_utils import VtkOutput, to_vtk_id_list, write_mesh, read_mesh @@ -15,7 +12,7 @@ @dataclass( frozen=True ) class Options: vtk_output: VtkOutput - cell_type_to_ordering: Dict[ int, List[ int ] ] + cell_name_to_ordering: dict[ str, list[ int ] ] volume_to_reorder: str @@ -25,15 +22,17 @@ class Result: reordering_stats: dict[ str, list[ int ] ] -cell_type_names: dict[ int, str ] = { - VTK_HEXAHEDRON: "Hexahedron", - VTK_TETRA: "Tetra", - VTK_PYRAMID: "Pyramid", - VTK_WEDGE: "Wedge", - VTK_VOXEL: "Voxel", - VTK_PENTAGONAL_PRISM: "Prism5", - VTK_HEXAGONAL_PRISM: "Prism6" +GEOS_ACCEPTED_TYPES = [ VTK_HEXAHEDRON, VTK_TETRA, VTK_PYRAMID, VTK_WEDGE, VTK_PENTAGONAL_PRISM, VTK_HEXAGONAL_PRISM ] +# the number of different nodes that needs to be entered in parsing when dealing with a specific vtk element +NAME_TO_VTK_TYPE = { + "Hexahedron": VTK_HEXAHEDRON, + "Tetrahedron": VTK_TETRA, + "Pyramid": VTK_PYRAMID, + "Wedge": VTK_WEDGE, + "Prism5": VTK_PENTAGONAL_PRISM, + "Prism6": VTK_HEXAGONAL_PRISM } +VTK_TYPE_TO_NAME = { vtk_type: name for name, vtk_type in NAME_TO_VTK_TYPE.items() } # Knowing the calculation of cell volumes in vtk was discussed there: https://github.com/GEOS-DEV/GEOS/issues/2253 @@ -56,63 +55,52 @@ def compute_mesh_cells_volume( mesh: vtkDataSet ) -> np.array: return vtk_to_numpy( cell_size_filter.GetOutput().GetCellData().GetArray( "Volume" ) ) -def is_cell_to_reorder( cell_volume: float, volume_to_reorder: str ) -> bool: - """Check if the volume of vtkCell qualifies the cell to be reordered. +def get_cell_types_and_number( mesh: vtkDataSet ) -> tuple[ list[ int ] ]: + """Gets the cell type for every cell of a mesh and the amount for each cell type. Args: - cell_volume (float): The volume of a vtkCell. - volume_to_reorder (str): Condition imposed in Options.volume_to_reorder - - Returns: - bool: True if cell needs to be reordered - """ - if volume_to_reorder == "all": - return True - if cell_volume == 0.0: - return True - sign_of_cell_volume: int = int( cell_volume / abs( cell_volume ) ) - if volume_to_reorder == "positive" and sign_of_cell_volume == 1: - return True - elif volume_to_reorder == "negative" and sign_of_cell_volume == -1: - return True - return False - + mesh (vtkDataSet): A vtk grid. -def get_all_cells_type( mesh: vtkDataSet ) -> np.array: - """Gets the cell type for every cell of a mesh. - - Args: - mesh (vtk grid): A vtk grid. + Raises: + ValueError: "Invalid type '{cell_type}' for GEOS is in the mesh. Dying ..." Returns: - np.array: ( [ cell0_type, cell1_type, ..., cellN_type ] ) + tuple[ list[ int ] ]: ( unique_cell_types, total_per_cell_types ) """ number_cells: int = mesh.GetNumberOfCells() all_cells_type: np.array = np.ones( number_cells, dtype=int ) for cell_id in range( number_cells ): all_cells_type[ cell_id ] = mesh.GetCellType( cell_id ) - return all_cells_type + unique_cell_types, total_per_cell_types = np.unique( all_cells_type, return_counts=True ) + unique_cell_types, total_per_cell_types = unique_cell_types.tolist(), total_per_cell_types.tolist() + for cell_type in unique_cell_types: + if cell_type not in GEOS_ACCEPTED_TYPES: + err_msg: str = f"Invalid type '{cell_type}' for GEOS is in the mesh. Dying ..." + logging.error( err_msg ) + raise ValueError( err_msg ) + return ( unique_cell_types, total_per_cell_types ) -def get_cell_ids_to_check( all_cells_type: np.array, options: Options ) -> dict[ int, np.array ]: - """For every cell type, returns a numpy array with only the indexes of the cell elements that have the same - type as specified in options. So if the cell type to check was tetrahedron, only the indexes of all - tetrahedrons in the mesh are returned. +def is_cell_to_reorder( cell_volume: str, options: Options ) -> bool: + """Check if the volume of vtkCell qualifies the cell to be reordered. Args: - all_cells_type (np.array): Array of cell types for every cell of a mesh. + cell_volume (float): The volume of a vtkCell. options (Options): Options defined by the user. Returns: - dict[ int, np.array ]: Indexes in ascending order of every cell being of a certain type. + bool: True if cell needs to be reordered """ - cell_ids_to_check: dict[ int, np.array ] = {} - available_types: list[ int ] = np.unique( all_cells_type ).tolist() - for cell_type in options.cell_type_to_ordering.keys(): - if cell_type in available_types: - sub_array = np.where( all_cells_type == cell_type )[ 0 ] - cell_ids_to_check[ cell_type ] = sub_array - return cell_ids_to_check + if options.volume_to_reorder == "all": + return True + if cell_volume == 0.0: + return True + sign_of_cell_volume: int = int( cell_volume / abs( cell_volume ) ) + if options.volume_to_reorder == "positive" and sign_of_cell_volume == 1: + return True + elif options.volume_to_reorder == "negative" and sign_of_cell_volume == -1: + return True + return False def reorder_nodes_to_new_mesh( old_mesh: vtkDataSet, options: Options ) -> tuple: @@ -125,16 +113,13 @@ def reorder_nodes_to_new_mesh( old_mesh: vtkDataSet, options: Options ) -> tuple Returns: tuple: ( vtkDataSet, reordering_stats ) """ - # gets all the cell types found in the mesh - all_cells_type: np.array = get_all_cells_type( old_mesh ) - unique_cell_types, total_per_cell_types = np.unique( all_cells_type, return_counts=True ) - unique_cell_types, total_per_cell_types = unique_cell_types.tolist(), total_per_cell_types.tolist() - # voxel elements are not accepted as input for GEOS mesh, so no need to perform reordering - if VTK_VOXEL in unique_cell_types: - raise ValueError( "Voxel elements were found in the grid. This element cannot be used in GEOS. Dying ..." ) - # volumes and cell ids that have the element type suggested for reordering are collected + unique_cell_types, total_per_cell_types = get_cell_types_and_number( old_mesh ) + unique_cell_names: list[ str ] = [ VTK_TYPE_TO_NAME[ vtk_type ] for vtk_type in unique_cell_types ] + names_with_totals: dict[ str, int ] = { n: v for n, v in zip( unique_cell_names, total_per_cell_types ) } + # sorted dict allow for sorted output of reordering_stats + names_with_totals_sorted: dict[ str, int ] = dict( sorted( names_with_totals.items() ) ) + useful_VTK_TYPEs: list[ int ] = [ NAME_TO_VTK_TYPE[ name ] for name in options.cell_name_to_ordering.keys() ] all_cells_volume: np.array = compute_mesh_cells_volume( old_mesh ) - cell_ids_to_check: dict[ int, np.array ] = get_cell_ids_to_check( all_cells_type, options ) # a new mesh with the same data is created from the old mesh new_mesh: vtkDataSet = old_mesh.NewInstance() new_mesh.CopyStructure( old_mesh ) @@ -142,31 +127,38 @@ def reorder_nodes_to_new_mesh( old_mesh: vtkDataSet, options: Options ) -> tuple cells = new_mesh.GetCells() # Statistics on how many cells have or have not been reordered reordering_stats: dict[ str, list[ any ] ] = { - "Types reordered": [], - "Number of cells reordered": [], - "Types non reordered": unique_cell_types, - "Number of cells non reordered": total_per_cell_types + "Types reordered": list(), + "Number of cells reordered": list(), + "Types non reordered": list( names_with_totals_sorted.keys() ), + "Number of cells non reordered": list( names_with_totals_sorted.values() ) } - for cell_type, cell_ids in cell_ids_to_check.items(): - counter_cells_reordered: int = 0 - for cell_id in cell_ids: - if is_cell_to_reorder( float( all_cells_volume[ cell_id ] ), options.volume_to_reorder ): + counter_cells_reordered: dict[ str, int ] = { name: 0 for name in options.cell_name_to_ordering.keys() } + # sorted dict allow for sorted output of reordering_stats + ounter_cells_reordered_sorted: dict[ str, int ] = dict( sorted( counter_cells_reordered.items() ) ) + # Reordering operations + for cell_id in range( new_mesh.GetNumberOfCells() ): + vtk_type: int = new_mesh.GetCellType( cell_id ) + if vtk_type in useful_VTK_TYPEs: + if is_cell_to_reorder( float( all_cells_volume[ cell_id ] ), options ): + cell_name: str = VTK_TYPE_TO_NAME[ vtk_type ] support_point_ids = vtkIdList() cells.GetCellAtId( cell_id, support_point_ids ) - new_support_point_ids = [] - node_ordering: list[ int ] = options.cell_type_to_ordering[ cell_type ] + new_support_point_ids: list[ int ] = list() + node_ordering: list[ int ] = options.cell_name_to_ordering[ cell_name ] for i in range( len( node_ordering ) ): new_support_point_ids.append( support_point_ids.GetId( node_ordering[ i ] ) ) cells.ReplaceCellAtId( cell_id, to_vtk_id_list( new_support_point_ids ) ) - counter_cells_reordered += 1 - if counter_cells_reordered > 0: - reordering_stats[ "Types reordered" ].append( cell_type ) - reordering_stats[ "Number of cells reordered" ].append( counter_cells_reordered ) - cell_type_position: int = reordering_stats[ "Types non reordered" ].index( cell_type ) - reordering_stats[ "Number of cells non reordered" ][ cell_type_position ] -= counter_cells_reordered - if reordering_stats[ "Number of cells non reordered" ][ cell_type_position ] == 0: - reordering_stats[ "Types non reordered" ].pop( cell_type_position ) - reordering_stats[ "Number of cells non reordered" ].pop( cell_type_position ) + ounter_cells_reordered_sorted[ cell_name ] += 1 + # Calculation of stats + for cell_name_reordered, amount in ounter_cells_reordered_sorted.items(): + if amount > 0: + reordering_stats[ "Types reordered" ].append( cell_name_reordered ) + reordering_stats[ "Number of cells reordered" ].append( amount ) + index_non_reordered: int = reordering_stats[ "Types non reordered" ].index( cell_name_reordered ) + reordering_stats[ "Number of cells non reordered" ][ index_non_reordered ] -= amount + if reordering_stats[ "Number of cells non reordered" ][ index_non_reordered ] == 0: + reordering_stats[ "Types non reordered" ].pop( index_non_reordered ) + reordering_stats[ "Number of cells non reordered" ].pop( index_non_reordered ) return ( new_mesh, reordering_stats ) diff --git a/geos-mesh/src/geos/mesh/doctor/parsing/fix_elements_orderings_parsing.py b/geos-mesh/src/geos/mesh/doctor/parsing/fix_elements_orderings_parsing.py index f9eb085f..37c1519a 100644 --- a/geos-mesh/src/geos/mesh/doctor/parsing/fix_elements_orderings_parsing.py +++ b/geos-mesh/src/geos/mesh/doctor/parsing/fix_elements_orderings_parsing.py @@ -1,62 +1,42 @@ import logging import random - -from vtkmodules.vtkCommonDataModel import ( - VTK_HEXAGONAL_PRISM, - VTK_HEXAHEDRON, - VTK_PENTAGONAL_PRISM, - VTK_PYRAMID, - VTK_TETRA, - VTK_VOXEL, - VTK_WEDGE, -) from geos.mesh.doctor.checks.fix_elements_orderings import Options, Result from . import vtk_output_parsing, FIX_ELEMENTS_ORDERINGS -__CELL_TYPE_MAPPING = { - "Hexahedron": VTK_HEXAHEDRON, - "Prism5": VTK_PENTAGONAL_PRISM, - "Prism6": VTK_HEXAGONAL_PRISM, - "Pyramid": VTK_PYRAMID, - "Tetrahedron": VTK_TETRA, - "Wedge": VTK_WEDGE, -} - -__CELL_TYPE_SUPPORT_SIZE = { - VTK_HEXAHEDRON: 8, - VTK_PENTAGONAL_PRISM: 10, - VTK_HEXAGONAL_PRISM: 12, - VTK_PYRAMID: 5, - VTK_TETRA: 4, - VTK_VOXEL: 8, - VTK_WEDGE: 6, +__CELL_NAME_WITH_NUMBER_NODES = { + "Tetrahedron": 4, + "Pyramid": 5, + "Wedge": 6, + "Hexahedron": 8, + "Prism5": 10, + "Prism6": 12 } __VOLUME_TO_REORDER = "volume_to_reorder" __VOLUME_TO_REORDER_DEFAULT = "all" -__VOLUME_TO_REORDER_CHOICES = [ "all", "positive", "negative" ] +__VOLUME_TO_REORDER_CHOICES = [ "all", "positive", "negative" ] def fill_subparser( subparsers ) -> None: p = subparsers.add_parser( FIX_ELEMENTS_ORDERINGS, help="Reorders the support nodes for the given cell types." ) - for key, vtk_key in __CELL_TYPE_MAPPING.items(): - tmp = list( range( __CELL_TYPE_SUPPORT_SIZE[ vtk_key ] ) ) + for element_name, size in __CELL_NAME_WITH_NUMBER_NODES.items(): + tmp = list( range( size ) ) random.Random( 4 ).shuffle( tmp ) - p.add_argument( '--' + key, + p.add_argument( '--' + element_name, type=str, metavar=",".join( map( str, tmp ) ), default=None, required=False, - help=f"[list of integers]: node permutation for \"{key}\"." ) + help=f"[list of integers]: node permutation for \"{element_name}\"." ) p.add_argument( '--' + __VOLUME_TO_REORDER, type=str, metavar=__VOLUME_TO_REORDER_DEFAULT, default=__VOLUME_TO_REORDER_DEFAULT, choices=__VOLUME_TO_REORDER_CHOICES, required=True, - help= "[str]: Select which element volume is invalid and needs reordering." - + "'all' will allow reordering of nodes for every element, regarding of their volume." - + "'positive' or 'negative' will only reorder the element with the corresponding volume." ) + help="[str]: Select which element volume is invalid and needs reordering." + + "'all' will allow reordering of nodes for every element, regarding of their volume." + + "'positive' or 'negative' will only reorder the element with the corresponding volume." ) vtk_output_parsing.fill_vtk_output_subparser( p ) @@ -66,21 +46,22 @@ def convert( parsed_options ) -> Options: :param options_str: Parsed cli options. :return: Options instance. """ - cell_type_to_ordering = {} - for key, vtk_key in __CELL_TYPE_MAPPING.items(): - raw_mapping = parsed_options[ key ] + cell_name_to_ordering: dict[ str, list[ int ] ] = {} + for element_name, size in __CELL_NAME_WITH_NUMBER_NODES.items(): + raw_mapping = parsed_options[ element_name ] if raw_mapping: - tmp = tuple( map( int, raw_mapping.split( "," ) ) ) - if not set( tmp ) == set( range( __CELL_TYPE_SUPPORT_SIZE[ vtk_key ] ) ): - err_msg = f"Permutation {raw_mapping} for type {key} is not valid." + nodes_ordering = tuple( map( int, raw_mapping.split( "," ) ) ) + if not set( nodes_ordering ) == set( range( size ) ): + err_msg: str = f"Permutation {raw_mapping} for type {element_name} is not valid." logging.error( err_msg ) raise ValueError( err_msg ) - cell_type_to_ordering[ vtk_key ] = tmp + cell_name_to_ordering[ element_name ] = nodes_ordering vtk_output = vtk_output_parsing.convert( parsed_options ) volume_to_reorder: str = parsed_options[ __VOLUME_TO_REORDER ] if volume_to_reorder.lower() not in __VOLUME_TO_REORDER_CHOICES: raise ValueError( f"Please use one of these options for --volume_to_reorder: {__VOLUME_TO_REORDER_CHOICES}." ) - return Options( vtk_output=vtk_output, cell_type_to_ordering=cell_type_to_ordering, + return Options( vtk_output=vtk_output, + cell_name_to_ordering=cell_name_to_ordering, volume_to_reorder=volume_to_reorder ) @@ -92,10 +73,10 @@ def display_results( options: Options, result: Result ): logging.info( f"Number of cells reordered:" ) logging.info( f"\tCellType\tNumber" ) for i in range( len( result.reordering_stats[ "Types reordered" ] ) ): - logging.info( f"\t{result.reordering_stats[ "Types reordered" ][ i ]}" + - f"\t\t{result.reordering_stats[ "Number of cells reordered" ][ i ]}" ) + logging.info( f"\t{result.reordering_stats[ 'Types reordered' ][ i ]}" + + f"\t\t{result.reordering_stats[ 'Number of cells reordered' ][ i ]}" ) logging.info( f"Number of cells non reordered:" ) logging.info( f"\tCellType\tNumber" ) for i in range( len( result.reordering_stats[ "Types non reordered" ] ) ): - logging.info( f"\t{result.reordering_stats[ "Types non reordered" ][ i ]}" + - f"\t\t{result.reordering_stats[ "Number of cells non reordered" ][ i ]}" ) + logging.info( f"\t{result.reordering_stats[ 'Types non reordered' ][ i ]}" + + f"\t\t{result.reordering_stats[ 'Number of cells non reordered' ][ i ]}" ) diff --git a/geos-mesh/tests/test_fix_elements_orderings.py b/geos-mesh/tests/test_fix_elements_orderings.py index 34e26298..b6152043 100644 --- a/geos-mesh/tests/test_fix_elements_orderings.py +++ b/geos-mesh/tests/test_fix_elements_orderings.py @@ -7,13 +7,28 @@ from geos.mesh.doctor.mesh_doctor import MESH_DOCTOR_FILEPATH from geos.mesh.doctor.checks import fix_elements_orderings as feo from geos.mesh.doctor.checks.generate_cube import Options, __build -from geos.mesh.doctor.checks.vtk_utils import VtkOutput, to_vtk_id_list, write_mesh -from geos.mesh.doctor.checks.fix_elements_orderings import Options as opt +from geos.mesh.doctor.checks.vtk_utils import ( VtkOutput, to_vtk_id_list, write_mesh ) +from geos.mesh.doctor.checks.fix_elements_orderings import Options as opt, VTK_TYPE_TO_NAME from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints -from vtkmodules.vtkCommonDataModel import ( vtkDataSet, vtkUnstructuredGrid, vtkCellArray, vtkHexahedron, vtkTetra, - vtkPyramid, vtkVoxel, vtkWedge, vtkPentagonalPrism, vtkHexagonalPrism, - VTK_HEXAHEDRON, VTK_TETRA, VTK_PYRAMID, VTK_WEDGE, VTK_VOXEL, - VTK_PENTAGONAL_PRISM, VTK_HEXAGONAL_PRISM ) +from vtkmodules.vtkCommonDataModel import ( + vtkDataSet, + vtkUnstructuredGrid, + vtkCellArray, + vtkHexahedron, + vtkTetra, + vtkPyramid, + vtkVoxel, + vtkWedge, + vtkPentagonalPrism, + vtkHexagonalPrism, + VTK_HEXAHEDRON, + VTK_TETRA, + VTK_PYRAMID, + VTK_WEDGE, + VTK_VOXEL, + VTK_PENTAGONAL_PRISM, + VTK_HEXAGONAL_PRISM +) def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int ] ): @@ -49,13 +64,12 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int Dict used to apply false nodes orderings for test purposes """ to_change_order: dict[ int, list[ int ] ] = { - VTK_HEXAHEDRON: [ 0, 3, 2, 1, 4, 5, 6, 7 ], - VTK_TETRA: [ 0, 2, 1, 3 ], - VTK_PYRAMID: [ 0, 3, 2, 1, 4 ], - VTK_WEDGE: [ 0, 2, 1, 3, 4, 5 ], - VTK_VOXEL: [ 7, 6, 5, 4, 3, 2, 1, 0 ], - VTK_PENTAGONAL_PRISM: [ 0, 4, 3, 2, 1, 5, 6, 7, 8, 9 ], - VTK_HEXAGONAL_PRISM: [ 0, 1, 4, 2, 3, 5, 11, 10, 9, 8, 7, 6 ] + "Hexahedron": [ 0, 3, 2, 1, 4, 5, 6, 7 ], + "Tetrahedron": [ 0, 2, 1, 3 ], + "Pyramid": [ 0, 3, 2, 1, 4 ], + "Wedge": [ 0, 2, 1, 3, 4, 5 ], + "Prism5": [ 0, 4, 3, 2, 1, 5, 6, 7, 8, 9 ], + "Prism6": [ 0, 1, 4, 2, 3, 5, 11, 10, 9, 8, 7, 6 ] } to_change_order = dict( sorted( to_change_order.items() ) ) """ @@ -93,7 +107,7 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int """ hexahedrons_grid_invalid: vtkDataSet = __build( options_hexahedrons_grid ) for i in range( 2 ): - reorder_cell_nodes( hexahedrons_grid_invalid, i * 2 + 1, to_change_order[ VTK_HEXAHEDRON ] ) + reorder_cell_nodes( hexahedrons_grid_invalid, i * 2 + 1, to_change_order[ "Hexahedron" ] ) """ 4 tetrahedrons """ @@ -142,7 +156,7 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int tetras_grid_invalid = vtkUnstructuredGrid() tetras_grid_invalid.DeepCopy( tetras_grid ) for i in range( 2 ): - reorder_cell_nodes( tetras_grid_invalid, i * 2 + 1, to_change_order[ VTK_TETRA ] ) + reorder_cell_nodes( tetras_grid_invalid, i * 2 + 1, to_change_order[ "Tetrahedron" ] ) """ 4 pyramids """ @@ -199,7 +213,7 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int pyramids_grid_invalid = vtkUnstructuredGrid() pyramids_grid_invalid.DeepCopy( pyramids_grid ) for i in range( 2 ): - reorder_cell_nodes( pyramids_grid_invalid, i * 2 + 1, to_change_order[ VTK_PYRAMID ] ) + reorder_cell_nodes( pyramids_grid_invalid, i * 2 + 1, to_change_order[ "Pyramid" ] ) """ 4 voxels: this type of element cannot be used in GEOS, we just test that the feature rejects them """ @@ -267,12 +281,6 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int voxels_grid: vtkUnstructuredGrid = vtkUnstructuredGrid() voxels_grid.SetPoints( points_voxels ) voxels_grid.SetCells( VTK_VOXEL, voxels_cells ) - -# one of every other wedge has invalid ordering -voxels_grid_invalid = vtkUnstructuredGrid() -voxels_grid_invalid.DeepCopy( voxels_grid ) -for i in range( 2 ): - reorder_cell_nodes( voxels_grid_invalid, i * 2 + 1, to_change_order[ VTK_VOXEL ] ) """ 4 wedges """ @@ -330,7 +338,7 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int wedges_grid_invalid = vtkUnstructuredGrid() wedges_grid_invalid.DeepCopy( wedges_grid ) for i in range( 2 ): - reorder_cell_nodes( wedges_grid_invalid, i * 2 + 1, to_change_order[ VTK_WEDGE ] ) + reorder_cell_nodes( wedges_grid_invalid, i * 2 + 1, to_change_order[ "Wedge" ] ) """ 4 pentagonal prisms """ @@ -414,7 +422,7 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int penta_prism_grid_invalid = vtkUnstructuredGrid() penta_prism_grid_invalid.DeepCopy( penta_prism_grid ) for i in range( 2 ): - reorder_cell_nodes( penta_prism_grid_invalid, i * 2 + 1, to_change_order[ VTK_PENTAGONAL_PRISM ] ) + reorder_cell_nodes( penta_prism_grid_invalid, i * 2 + 1, to_change_order[ "Prism5" ] ) """ 4 hexagonal prisms """ @@ -468,7 +476,7 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int hexa_prism_grid_invalid = vtkUnstructuredGrid() hexa_prism_grid_invalid.DeepCopy( hexa_prism_grid ) for i in range( 2 ): - reorder_cell_nodes( hexa_prism_grid_invalid, i * 2 + 1, to_change_order[ VTK_HEXAGONAL_PRISM ] ) + reorder_cell_nodes( hexa_prism_grid_invalid, i * 2 + 1, to_change_order[ "Prism6" ] ) """ 2 hexahedrons, 2 tetrahedrons, 2 wedges, 2 pyramids, 2 voxels, 2 pentagonal prisms and 2 hexagonal prisms """ @@ -629,6 +637,10 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int VTK_HEXAGONAL_PRISM, VTK_HEXAHEDRON, VTK_HEXAHEDRON, VTK_WEDGE, VTK_WEDGE, VTK_PENTAGONAL_PRISM, VTK_PENTAGONAL_PRISM ] +all_cell_names_mix_grid = [ + "Hexahedron", "Hexahedron", "Pyramid", "Pyramid", "Tetrahedron", "Tetrahedron", "Prism6", "Prism6", "Hexahedron", + "Hexahedron", "Wedge", "Wedge", "Prism5", "Prism5" +] all_cells_mix_grid = [ mix_hex1, mix_hex2, mix_pyram1, mix_pyram2, mix_tetra1, mix_tetra2, mix_hex_prism1, mix_hex_prism2, mix_hex3, mix_hex4, mix_wedge1, mix_wedge2, mix_penta_prism1, mix_penta_prism2 @@ -640,7 +652,7 @@ def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int mix_grid_invalid = vtkUnstructuredGrid() mix_grid_invalid.DeepCopy( mix_grid ) for i in range( len( all_cell_types_mix_grid ) // 2 ): - reorder_cell_nodes( mix_grid_invalid, i * 2 + 1, to_change_order[ all_cell_types_mix_grid[ i * 2 + 1 ] ] ) + reorder_cell_nodes( mix_grid_invalid, i * 2 + 1, to_change_order[ all_cell_names_mix_grid[ i * 2 + 1 ] ] ) class TestClass: @@ -653,14 +665,14 @@ def test_reorder_cell_nodes( self ): assert one_hex.GetCell( 0 ).GetPoints().GetPoint( i ) == expected_nodes_coords[ i ] # reorder the cell to make it invalid - reorder_cell_nodes( one_hex, 0, to_change_order[ VTK_HEXAHEDRON ] ) + reorder_cell_nodes( one_hex, 0, to_change_order[ "Hexahedron" ] ) expected_nodes_coords_modified = [ ( 0.0, 0.0, 0.0 ), ( 0.0, 1.0, 0.0 ), ( 1.0, 1.0, 0.0 ), ( 1.0, 0.0, 0.0 ), ( 0.0, 0.0, 1.0 ), ( 1.0, 0.0, 1.0 ), ( 1.0, 1.0, 1.0 ), ( 0.0, 1.0, 1.0 ) ] for i in range( one_hex.GetCell( 0 ).GetNumberOfPoints() ): assert one_hex.GetCell( 0 ).GetPoints().GetPoint( i ) == expected_nodes_coords_modified[ i ] # reorder the cell again to make it valid again - reorder_cell_nodes( one_hex, 0, to_change_order[ VTK_HEXAHEDRON ] ) + reorder_cell_nodes( one_hex, 0, to_change_order[ "Hexahedron" ] ) expected_nodes_coords_modified2 = [ ( 0.0, 0.0, 0.0 ), ( 1.0, 0.0, 0.0 ), ( 1.0, 1.0, 0.0 ), ( 0.0, 1.0, 0.0 ), ( 0.0, 0.0, 1.0 ), ( 1.0, 0.0, 1.0 ), ( 1.0, 1.0, 1.0 ), ( 0.0, 1.0, 1.0 ) ] for i in range( one_hex.GetCell( 0 ).GetNumberOfPoints() ): @@ -675,7 +687,6 @@ def test_compute_mesh_cells_volume( self ): pyramids_grid: [ 0.333, 0.333, 0.333, 0.333 ], pyramids_grid_invalid: [ 0.333, -0.333, 0.333, -0.333 ], voxels_grid: [ 1.0, 1.0, 1.0, 1.0 ], - voxels_grid_invalid: [ 1.0, 1.0, 1.0, 1.0 ], wedges_grid: [ 0.5, 0.5, 0.5, 0.5 ], wedges_grid_invalid: [ 0.5, -0.167, 0.5, -0.167 ], penta_prism_grid: [ 1.25, 1.25, 1.25, 1.25 ], @@ -699,8 +710,6 @@ def test_is_cell_to_reorder( self ): tetras_grid_invalid: [ i % 2 != 0 for i in range( 4 ) ], pyramids_grid: [ False ] * 4, pyramids_grid_invalid: [ i % 2 != 0 for i in range( 4 ) ], - voxels_grid: [ False ] * 4, - voxels_grid_invalid: [ False ] * 4, wedges_grid: [ False ] * 4, wedges_grid_invalid: [ i % 2 != 0 for i in range( 4 ) ], penta_prism_grid: [ False ] * 4, @@ -713,75 +722,35 @@ def test_is_cell_to_reorder( self ): for grid, needs_ordering in grid_needs_ordering.items(): volumes = feo.compute_mesh_cells_volume( grid ) for i in range( len( volumes ) ): - assert feo.is_cell_to_reorder( volumes[ i ], "negative" ) == needs_ordering[ i ] - assert feo.is_cell_to_reorder( volumes[ i ], "positive" ) != needs_ordering[ i ] - assert feo.is_cell_to_reorder( volumes[ i ], "all" ) == True - - def test_get_all_cells_type( self ): - assert feo.get_all_cells_type( hexahedrons_grid ).tolist() == [ 12, 12, 12, 12 ] - assert feo.get_all_cells_type( hexahedrons_grid_invalid ).tolist() == [ 12, 12, 12, 12 ] - assert feo.get_all_cells_type( tetras_grid ).tolist() == [ 10, 10, 10, 10 ] - assert feo.get_all_cells_type( tetras_grid_invalid ).tolist() == [ 10, 10, 10, 10 ] - assert feo.get_all_cells_type( pyramids_grid ).tolist() == [ 14, 14, 14, 14 ] - assert feo.get_all_cells_type( pyramids_grid_invalid ).tolist() == [ 14, 14, 14, 14 ] - assert feo.get_all_cells_type( voxels_grid ).tolist() == [ 11, 11, 11, 11 ] - assert feo.get_all_cells_type( voxels_grid_invalid ).tolist() == [ 11, 11, 11, 11 ] - assert feo.get_all_cells_type( wedges_grid ).tolist() == [ 13, 13, 13, 13 ] - assert feo.get_all_cells_type( wedges_grid_invalid ).tolist() == [ 13, 13, 13, 13 ] - assert feo.get_all_cells_type( penta_prism_grid ).tolist() == [ 15, 15, 15, 15 ] - assert feo.get_all_cells_type( penta_prism_grid_invalid ).tolist() == [ 15, 15, 15, 15 ] - assert feo.get_all_cells_type( hexa_prism_grid ).tolist() == [ 16, 16, 16, 16 ] - assert feo.get_all_cells_type( hexa_prism_grid_invalid ).tolist() == [ 16, 16, 16, 16 ] - assert feo.get_all_cells_type( mix_grid ).tolist() == [ 12, 12, 14, 14, 10, 10, 16, 16, 12, 12, 13, 13, 15, 15 ] - assert feo.get_all_cells_type( mix_grid_invalid ).tolist() == [ - 12, 12, 14, 14, 10, 10, 16, 16, 12, 12, 13, 13, 15, 15 - ] - - def test_get_cell_ids_to_check( self ): - options = opt( out, to_change_order, "negative" ) - # single element grids - grid_cell_type = { - hexahedrons_grid: VTK_HEXAHEDRON, - hexahedrons_grid_invalid: VTK_HEXAHEDRON, - tetras_grid: VTK_TETRA, - tetras_grid_invalid: VTK_TETRA, - pyramids_grid: VTK_PYRAMID, - pyramids_grid_invalid: VTK_PYRAMID, - voxels_grid: VTK_VOXEL, - voxels_grid_invalid: VTK_VOXEL, - wedges_grid: VTK_WEDGE, - wedges_grid_invalid: VTK_WEDGE, - penta_prism_grid: VTK_PENTAGONAL_PRISM, - penta_prism_grid_invalid: VTK_PENTAGONAL_PRISM, - hexa_prism_grid: VTK_HEXAGONAL_PRISM, - hexa_prism_grid_invalid: VTK_HEXAGONAL_PRISM - } - for grid, cell_type in grid_cell_type.items(): - all_cells_type = feo.get_all_cells_type( grid ) - result = feo.get_cell_ids_to_check( all_cells_type, options ) - expected = { cell_type: np.array( [ 0, 1, 2, 3 ] ) } - for cell_type_result, cell_type_expected in zip( result.keys(), expected.keys() ): - assert cell_type_result == cell_type_expected - for cell_indexes_result, cell_indexes_expected in zip( result.values(), expected.values() ): - assert np.array_equal( cell_indexes_result, cell_indexes_expected ) - - # mix elements grid - all_cells_type_mix = feo.get_all_cells_type( mix_grid ) - result = feo.get_cell_ids_to_check( all_cells_type_mix, options ) - result = dict( sorted( result.items() ) ) - expected = { - 12: np.array( [ 0, 1, 8, 9 ] ), - 14: np.array( [ 2, 3 ] ), - 10: np.array( [ 4, 5 ] ), - 16: np.array( [ 6, 7 ] ), - 13: np.array( [ 10, 11 ] ), - 15: np.array( [ 12, 13 ] ) - } - expected = dict( sorted( expected.items() ) ) - for cell_type_result, cell_type_expected in zip( result.keys(), expected.keys() ): - assert cell_type_result == cell_type_expected - for cell_indexes_result, cell_indexes_expected in zip( result.values(), expected.values() ): - assert np.array_equal( cell_indexes_result, cell_indexes_expected ) + options = opt( out, to_change_order, "negative" ) + assert feo.is_cell_to_reorder( volumes[ i ], options ) == needs_ordering[ i ] + options = opt( out, to_change_order, "positive" ) + assert feo.is_cell_to_reorder( volumes[ i ], options ) != needs_ordering[ i ] + options = opt( out, to_change_order, "all" ) + assert feo.is_cell_to_reorder( volumes[ i ], options ) == True + + def test_get_cell_types_and_number( self ): + assert feo.get_cell_types_and_number( hexahedrons_grid ) == ( [ VTK_HEXAHEDRON ], [ 4 ] ) + assert feo.get_cell_types_and_number( hexahedrons_grid_invalid ) == ( [ VTK_HEXAHEDRON ], [ 4 ] ) + assert feo.get_cell_types_and_number( tetras_grid ) == ( [ VTK_TETRA ], [ 4 ] ) + assert feo.get_cell_types_and_number( tetras_grid_invalid ) == ( [ VTK_TETRA ], [ 4 ] ) + assert feo.get_cell_types_and_number( pyramids_grid ) == ( [ VTK_PYRAMID ], [ 4 ] ) + assert feo.get_cell_types_and_number( pyramids_grid_invalid ) == ( [ VTK_PYRAMID ], [ 4 ] ) + assert feo.get_cell_types_and_number( wedges_grid ) == ( [ VTK_WEDGE ], [ 4 ] ) + assert feo.get_cell_types_and_number( wedges_grid_invalid ) == ( [ VTK_WEDGE ], [ 4 ] ) + assert feo.get_cell_types_and_number( penta_prism_grid ) == ( [ VTK_PENTAGONAL_PRISM ], [ 4 ] ) + assert feo.get_cell_types_and_number( penta_prism_grid_invalid ) == ( [ VTK_PENTAGONAL_PRISM ], [ 4 ] ) + assert feo.get_cell_types_and_number( hexa_prism_grid ) == ( [ VTK_HEXAGONAL_PRISM ], [ 4 ] ) + assert feo.get_cell_types_and_number( hexa_prism_grid_invalid ) == ( [ VTK_HEXAGONAL_PRISM ], [ 4 ] ) + assert feo.get_cell_types_and_number( mix_grid ) == ( [ + VTK_TETRA, VTK_HEXAHEDRON, VTK_WEDGE, VTK_PYRAMID, VTK_PENTAGONAL_PRISM, VTK_HEXAGONAL_PRISM + ], [ 2, 4, 2, 2, 2, 2 ] ) + assert feo.get_cell_types_and_number( mix_grid_invalid ) == ( [ + VTK_TETRA, VTK_HEXAHEDRON, VTK_WEDGE, VTK_PYRAMID, VTK_PENTAGONAL_PRISM, VTK_HEXAGONAL_PRISM + ], [ 2, 4, 2, 2, 2, 2 ] ) + expected_error: str = f"Invalid type '11' for GEOS is in the mesh. Dying ..." + with pytest.raises( ValueError, match=expected_error ): + feo.get_cell_types_and_number( voxels_grid ) def test_reorder_nodes_to_new_mesh( self ): options = opt( out, to_change_order, "negative" ) @@ -799,30 +768,23 @@ def test_reorder_nodes_to_new_mesh( self ): new_invalid.DeepCopy( grid ) not_use_invalid, reorder_stats = feo.reorder_nodes_to_new_mesh( new_invalid, options ) expected = { - "Types reordered": [ cell_type ], + "Types reordered": [ VTK_TYPE_TO_NAME[ cell_type ] ], "Number of cells reordered": [ 2 ], - "Types non reordered": [ cell_type ], + "Types non reordered": [ VTK_TYPE_TO_NAME[ cell_type ] ], "Number of cells non reordered": [ 2 ] } for prop in expected.keys(): assert reorder_stats[ prop ] == expected[ prop ] - # voxel elements grid to check if raise ValueError was correctly called - voxels_invalid = vtkUnstructuredGrid() - voxels_invalid.DeepCopy( voxels_grid_invalid ) - expected_error: str = "Voxel elements were found in the grid. This element cannot be used in GEOS. Dying ..." - with pytest.raises( ValueError, match=expected_error ): - not_use_invalid, reorder_stats = feo.reorder_nodes_to_new_mesh( voxels_invalid, options ) - # mix elements grid mix_invalid = vtkUnstructuredGrid() mix_invalid.DeepCopy( mix_grid_invalid ) not_use_invalid, mix_stats = feo.reorder_nodes_to_new_mesh( mix_invalid, options ) expected = { - "Types reordered": [ 10, 12, 13, 14, 15, 16 ], - "Number of cells reordered": [ 1, 2, 1, 1, 1, 1 ], - "Types non reordered": [ 10, 12, 13, 14, 15, 16 ], - "Number of cells non reordered": [ 1, 2, 1, 1, 1, 1 ] + "Types reordered": [ VTK_TYPE_TO_NAME[ cell_type ] for cell_type in [ 12, 15, 16, 14, 10, 13 ] ], + "Number of cells reordered": [ 2, 1, 1, 1, 1, 1 ], + "Types non reordered": [ VTK_TYPE_TO_NAME[ cell_type ] for cell_type in [ 12, 15, 16, 14, 10, 13 ] ], + "Number of cells non reordered": [ 2, 1, 1, 1, 1, 1 ] } for prop in expected.keys(): assert mix_stats[ prop ] == expected[ prop ] @@ -830,33 +792,38 @@ def test_reorder_nodes_to_new_mesh( self ): def test_fix_elements_orderings_execution( self ): # for mix_grid_invalid mesh, checks that reordered mesh was created and that reoredring_stats are valid write_mesh( mix_grid_invalid, test_file ) + invalidTest = False command = [ "python", MESH_DOCTOR_FILEPATH, "-v", "-i", test_file.output, "fix_elements_orderings", "--Hexahedron", - str( to_change_order[ VTK_HEXAHEDRON ] ).replace( "[", "" ).replace( "]", "" ), "--Tetrahedron", - str( to_change_order[ VTK_TETRA ] ).replace( "[", "" ).replace( "]", "" ), "--Pyramid", - str( to_change_order[ VTK_PYRAMID ] ).replace( "[", "" ).replace( "]", "" ), "--Wedge", - str( to_change_order[ VTK_WEDGE ] ).replace( "[", "" ).replace( "]", "" ), "--Wedge", - str( to_change_order[ VTK_WEDGE ] ).replace( "[", "" ).replace( "]", "" ), "--Prism5", - str( to_change_order[ VTK_PENTAGONAL_PRISM ] ).replace( "[", "" ).replace( "]", "" ), "--Prism6", - str( to_change_order[ VTK_HEXAGONAL_PRISM ] ).replace( "[", "" ).replace( "]", "" ), "--volume_to_reorder", - "negative", "--data-mode", "binary", "--output", filepath_reordered_mesh + str( to_change_order[ "Hexahedron" ] ).replace( "[", "" ).replace( "]", "" ), "--Tetrahedron", + str( to_change_order[ "Tetrahedron" ] ).replace( "[", "" ).replace( "]", "" ), "--Pyramid", + str( to_change_order[ "Pyramid" ] ).replace( "[", "" ).replace( "]", "" ), "--Wedge", + str( to_change_order[ "Wedge" ] ).replace( "[", "" ).replace( "]", "" ), "--Prism5", + str( to_change_order[ "Prism5" ] ).replace( "[", "" ).replace( "]", "" ), "--Prism6", + str( to_change_order[ "Prism6" ] ).replace( "[", "" ).replace( "]", "" ), "--volume_to_reorder", "negative", + "--data-mode", "binary", "--output", filepath_reordered_mesh ] try: result = subprocess.run( command, shell=True, stderr=subprocess.PIPE, universal_newlines=True ) + os.remove( filepath_reordered_mesh ) stderr = result.stderr assert result.returncode == 0 - os.remove( filepath_reordered_mesh ) raw_stderr = r"{}".format( stderr ) pattern = r"\[.*?\]\[.*?\] (.*)" matches = re.findall( pattern, raw_stderr ) no_log = "\n".join( matches ) reordering_stats: str = no_log[ no_log.index( "Number of cells reordered" ): ] - expected_stats: str = ( "Number of cells reordered:\n" + "\tCellType\tNumber\n" + "\t12\t\t2\n" + - "\t15\t\t1\n" + "\t16\t\t1\n" + "\t14\t\t1\n" + "\t10\t\t1\n" + "\t13\t\t1\n" + - "Number of cells non reordered:\n" + "\tCellType\tNumber\n" + "\t10\t\t1\n" + - "\t12\t\t2\n" + "\t13\t\t1\n" + "\t14\t\t1\n" + "\t15\t\t1\n" + "\t16\t\t1" ) + expected_stats: str = ( "Number of cells reordered:\n" + "\tCellType\tNumber\n" + f"\tHexahedron\t\t2\n" + + f"\tPrism5\t\t1\n" + f"\tPrism6\t\t1\n" + f"\tPyramid\t\t1\n" + + f"\tTetrahedron\t\t1\n" + f"\tWedge\t\t1\n" + "Number of cells non reordered:\n" + + "\tCellType\tNumber\n" + f"\tHexahedron\t\t2\n" + f"\tPrism5\t\t1\n" + + f"\tPrism6\t\t1\n" + f"\tPyramid\t\t1\n" + f"\tTetrahedron\t\t1\n" + + f"\tWedge\t\t1" ) assert reordering_stats == expected_stats except Exception as e: logging.error( "Invalid command input. Test has failed." ) logging.error( e ) + invalidTest = True os.remove( test_file.output ) + if invalidTest: + raise ValueError( "test_fix_elements_orderings_execution has failed." ) From d476bdfb83ee8efbb7305606c3e74bc352f87b54 Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Mon, 16 Sep 2024 11:53:02 -0700 Subject: [PATCH 14/15] Documentation update --- docs/geos-mesh.rst | 220 +++++++++++++++--- .../parsing/fix_elements_orderings_parsing.py | 7 +- 2 files changed, 196 insertions(+), 31 deletions(-) diff --git a/docs/geos-mesh.rst b/docs/geos-mesh.rst index 8582f106..739d4936 100644 --- a/docs/geos-mesh.rst +++ b/docs/geos-mesh.rst @@ -15,14 +15,59 @@ Modules To list all the modules available through ``mesh-doctor``, you can simply use the ``--help`` option, which will list all available modules as well as a quick summary. -.. command-output:: python src/geos/mesh/doctor/mesh_doctor.py --help - :cwd: ../geos-mesh +.. code-block:: + + $ python src/geos/mesh/doctor/mesh_doctor.py --help + usage: mesh_doctor.py [-h] [-v] [-q] -i VTK_MESH_FILE + {collocated_nodes,element_volumes,fix_elements_orderings,generate_cube,generate_fractures,generate_global_ids,non_conformal,self_intersecting_elements,supported_elements} + ... + + Inspects meshes for GEOSX. + + positional arguments: + {collocated_nodes,element_volumes,fix_elements_orderings,generate_cube,generate_fractures,generate_global_ids,non_conformal,self_intersecting_elements,supported_elements} + Modules + collocated_nodes + Checks if nodes are collocated. + element_volumes + Checks if the volumes of the elements are greater than "min". + fix_elements_orderings + Reorders the support nodes for the given cell types. + generate_cube + Generate a cube and its fields. + generate_fractures + Splits the mesh to generate the faults and fractures. [EXPERIMENTAL] + generate_global_ids + Adds globals ids for points and cells. + non_conformal + Detects non conformal elements. [EXPERIMENTAL] + self_intersecting_elements + Checks if the faces of the elements are self intersecting. + supported_elements + Check that all the elements of the mesh are supported by GEOSX. + + options: + -h, --help + show this help message and exit + -v Use -v 'INFO', -vv for 'DEBUG'. Defaults to 'WARNING'. + -q Use -q to reduce the verbosity of the output. + -i VTK_MESH_FILE, --vtk-input-file VTK_MESH_FILE + + Note that checks are dynamically loaded. + An option may be missing because of an unloaded module. + Increase verbosity (-v, -vv) to get full information. Then, if you are interested in a specific module, you can ask for its documentation using the ``mesh-doctor module_name --help`` pattern. For example -.. command-output:: python src/geos/mesh/doctor/mesh_doctor.py collocated_nodes --help - :cwd: ../geos-mesh +.. code-block:: + + $ python src/geos/mesh/doctor/mesh_doctor.py collocated_nodes --help + usage: mesh_doctor.py collocated_nodes [-h] --tolerance TOLERANCE + + options: + -h, --help show this help message and exit + --tolerance TOLERANCE [float]: The absolute distance between two nodes for them to be considered collocated. ``mesh-doctor`` loads its module dynamically. If a module can't be loaded, ``mesh-doctor`` will proceed and try to load other modules. @@ -44,8 +89,14 @@ Here is a list and brief description of all the modules available. Displays the neighboring nodes that are closer to each other than a prescribed threshold. It is not uncommon to define multiple nodes for the exact same position, which will typically be an issue for ``geos`` and should be fixed. -.. command-output:: python src/geos/mesh/doctor/mesh_doctor.py collocated_nodes --help - :cwd: ../geos-mesh +.. code-block:: + + $ python src/geos/mesh/doctor/mesh_doctor.py collocated_nodes --help + usage: mesh_doctor.py collocated_nodes [-h] --tolerance TOLERANCE + + options: + -h, --help show this help message and exit + --tolerance TOLERANCE [float]: The absolute distance between two nodes for them to be considered collocated. ``element_volumes`` """"""""""""""""""" @@ -53,8 +104,14 @@ It is not uncommon to define multiple nodes for the exact same position, which w Computes the volumes of all the cells and displays the ones that are below a prescribed threshold. Cells with negative volumes will typically be an issue for ``geos`` and should be fixed. -.. command-output:: python src/geos/mesh/doctor/mesh_doctor.py element_volumes --help - :cwd: ../geos-mesh +.. code-block:: + + $ python src/geos/mesh/doctor/mesh_doctor.py element_volumes --help + usage: mesh_doctor.py element_volumes [-h] --min 0.0 + + options: + -h, --help show this help message and exit + --min 0.0 [float]: The minimum acceptable volume. Defaults to 0.0. ``fix_elements_orderings`` """""""""""""""""""""""""" @@ -63,8 +120,38 @@ It sometimes happens that an exported mesh does not abide by the ``vtk`` orderin The ``fix_elements_orderings`` module can rearrange the nodes of given types of elements. This can be convenient if you cannot regenerate the mesh. -.. command-output:: python src/geos/mesh/doctor/mesh_doctor.py fix_elements_orderings --help - :cwd: ../geos-mesh +.. code-block:: + + $ python src/geos/mesh/doctor/mesh_doctor.py fix_elements_orderings --help + usage: mesh_doctor.py fix_elements_orderings [-h] [--Tetrahedron 2,0,3,1] [--Pyramid 3,4,0,2,1] [--Wedge 3,5,4,0,2,1] + [--Hexahedron 1,6,5,4,7,0,2,3] [--Prism5 8,2,0,7,6,9,5,1,4,3] [--Prism6 11,2,8,10,5,0,9,7,6,1,4,3] + --volume_to_reorder all,positive,negative --output OUTPUT [--data-mode binary, ascii] + + options: + -h, --help show this help message and exit + --Tetrahedron 2,0,3,1 [list of integers]: node permutation for "Tetrahedron". + --Pyramid 3,4,0,2,1 [list of integers]: node permutation for "Pyramid". + --Wedge 3,5,4,0,2,1 [list of integers]: node permutation for "Wedge". + --Hexahedron 1,6,5,4,7,0,2,3 + [list of integers]: node permutation for "Hexahedron". + --Prism5 8,2,0,7,6,9,5,1,4,3 + [list of integers]: node permutation for "Prism5". + --Prism6 11,2,8,10,5,0,9,7,6,1,4,3 + [list of integers]: node permutation for "Prism6". + --volume_to_reorder all,positive,negative + [str]: Select which element volume is invalid and needs reordering. 'all' will allow reordering of nodes for every element, regarding of their volume. + 'positive' or 'negative' will only reorder the element with the corresponding volume. + --output OUTPUT [string]: The vtk output file destination. + --data-mode binary, ascii [string]: For ".vtu" output format, the data mode can be binary or ascii. Defaults to binary. + +For example, assume that you have a mesh that contains 3 different cell types like Hexahedrons, Pyramids and Tetrahedrons. +After checking ``element_volumes``, you found that all your Hexahedrons and half of your Tetrahedrons have a negative volume. +To correct that, assuming you know the correct node ordering to correct these volumes, you can use this command: + +.. code-block:: + + $ python src/geos/mesh/doctor/mesh_doctor.py -i /path/to/your/mesh/file fix_elements_orderings --Hexahedron 1,6,5,4,7,0,2,3 + --Tetrahedron 2,0,3,1 --volume_to_reorder negative --output /new/path/for/your/new/mesh/reordered/file --data-mode binary ``generate_cube`` """"""""""""""""" @@ -73,8 +160,30 @@ This module conveniently generates cubic meshes in ``vtk``. It can also generate fields with simple values. This tool can also be useful to generate a trial mesh that will later be refined or customized. -.. command-output:: python src/geos/mesh/doctor/mesh_doctor.py generate_cube --help - :cwd: ../geos-mesh +.. code-block:: + + $ python src/geos/mesh/doctor/mesh_doctor.py generate_cube --help + usage: mesh_doctor.py generate_cube [-h] [--x 0:1.5:3] [--y 0:5:10] [--z 0:1] [--nx 2:2] [--ny 1:1] [--nz 4] + [--fields name:support:dim [name:support:dim ...]] [--cells] [--no-cells] + [--points] [--no-points] --output OUTPUT [--data-mode binary, ascii] + + options: + -h, --help show this help message and exit + --x 0:1.5:3 [list of floats]: X coordinates of the points. + --y 0:5:10 [list of floats]: Y coordinates of the points. + --z 0:1 [list of floats]: Z coordinates of the points. + --nx 2:2 [list of integers]: Number of elements in the X direction. + --ny 1:1 [list of integers]: Number of elements in the Y direction. + --nz 4 [list of integers]: Number of elements in the Z direction. + --fields name:support:dim + [name:support:dim ...]: Create fields on CELLS or POINTS, with given dimension (typically 1 or 3). + --cells [bool]: Generate global ids for cells. Defaults to true. + --no-cells [bool]: Don't generate global ids for cells. + --points [bool]: Generate global ids for points. Defaults to true. + --no-points [bool]: Don't generate global ids for points. + --output OUTPUT [string]: The vtk output file destination. + --data-mode binary, ascii + [string]: For ".vtu" output format, the data mode can be binary or ascii. Defaults to binary. ``generate_fractures`` """""""""""""""""""""" @@ -82,8 +191,31 @@ This tool can also be useful to generate a trial mesh that will later be refined For a conformal fracture to be defined in a mesh, ``geos`` requires the mesh to be split at the faces where the fracture gets across the mesh. The ``generate_fractures`` module will split the mesh and generate the multi-block ``vtk`` files. -.. command-output:: python src/geos/mesh/doctor/mesh_doctor.py generate_fractures --help - :cwd: ../geos-mesh +.. code-block:: + + $ python src/geos/mesh/doctor/mesh_doctor.py generate_fractures --help + usage: mesh_doctor.py generate_fractures [-h] --policy field, internal_surfaces [--name NAME] [--values VALUES] + --output OUTPUT [--data-mode binary, ascii] --fracture-output + FRACTURE_OUTPUT [--fracture-data-mode binary, ascii] + + options: + -h, --help show this help message and exit + --policy field, internal_surfaces + [string]: The criterion to define the surfaces that will be changed into fracture zones. + Possible values are "field, internal_surfaces" + --name NAME [string]: If the "field" policy is selected, defines which field will be considered to + define the fractures. If the "internal_surfaces" policy is selected, defines the name of + the attribute will be considered to identify the fractures. + --values VALUES [list of comma separated integers]: If the "field" policy is selected, which changes of + the field will be considered as a fracture. If the "internal_surfaces" policy is + selected, list of the fracture attributes. + --output OUTPUT [string]: The vtk output file destination. + --data-mode binary, ascii + [string]: For ".vtu" output format, the data mode can be binary or ascii. Defaults to binary. + --fracture-output FRACTURE_OUTPUT + [string]: The vtk output file destination. + --fracture-data-mode binary, ascii + [string]: For ".vtu" output format, the data mode can be binary or ascii. Defaults to binary. ``generate_global_ids`` """"""""""""""""""""""" @@ -91,8 +223,21 @@ The ``generate_fractures`` module will split the mesh and generate the multi-blo When running ``geos`` in parallel, `global ids` can be used to refer to data across multiple ranks. The ``generate_global_ids`` can generate `global ids` for the imported ``vtk`` mesh. -.. command-output:: python src/geos/mesh/doctor/mesh_doctor.py generate_global_ids --help - :cwd: ../geos-mesh +.. code-block:: + + $ python src/geos/mesh/doctor/mesh_doctor.py generate_global_ids --help + usage: mesh_doctor.py generate_global_ids [-h] [--cells] [--no-cells] [--points] [--no-points] --output OUTPUT + [--data-mode binary, ascii] + + options: + -h, --help show this help message and exit + --cells [bool]: Generate global ids for cells. Defaults to true. + --no-cells [bool]: Don't generate global ids for cells. + --points [bool]: Generate global ids for points. Defaults to true. + --no-points [bool]: Don't generate global ids for points. + --output OUTPUT [string]: The vtk output file destination. + --data-mode binary, ascii + [string]: For ".vtu" output format, the data mode can be binary or ascii. Defaults to binary. ``non_conformal`` """"""""""""""""" @@ -102,8 +247,19 @@ This module will detect elements which are close enough (there's a user defined The angle between two faces can also be precribed. This module can be a bit time consuming. -.. command-output:: python src/geos/mesh/doctor/mesh_doctor.py non_conformal --help - :cwd: ../geos-mesh +.. code-block:: + + $ python src/geos/mesh/doctor/mesh_doctor.py non_conformal --help + usage: mesh_doctor.py non_conformal [-h] [--angle_tolerance 10.0] [--point_tolerance POINT_TOLERANCE] + [--face_tolerance FACE_TOLERANCE] + + options: + -h, --help show this help message and exit + --angle_tolerance 10.0 [float]: angle tolerance in degrees. Defaults to 10.0 + --point_tolerance POINT_TOLERANCE + [float]: tolerance for two points to be considered collocated. + --face_tolerance FACE_TOLERANCE + [float]: tolerance for two faces to be considered "touching". ``self_intersecting_elements`` """""""""""""""""""""""""""""" @@ -111,8 +267,15 @@ This module can be a bit time consuming. Some meshes can have cells that auto-intersect. This module will display the elements that have faces intersecting. -.. command-output:: python src/geos/mesh/doctor/mesh_doctor.py self_intersecting_elements --help - :cwd: ../geos-mesh +.. code-block:: + + $ python src/geos/mesh/doctor/mesh_doctor.py self_intersecting_elements --help + usage: mesh_doctor.py self_intersecting_elements [-h] [--min 2.220446049250313e-16] + + options: + -h, --help show this help message and exit + --min 2.220446049250313e-16 + [float]: The tolerance in the computation. Defaults to your machine precision 2.220446049250313e-16. ``supported_elements`` """""""""""""""""""""" @@ -125,8 +288,15 @@ But also prismes up to 11 faces. The ``supported_elements`` check will validate that no unsupported element is included in the input mesh. It will also verify that the ``VTK_POLYHEDRON`` cells can effectively get converted into a supported type of element. -.. command-output:: python src/geos/mesh/doctor/mesh_doctor.py supported_elements --help - :cwd: ../geos-mesh +.. code-block:: + + $ python src/geos/mesh/doctor/mesh_doctor.py supported_elements --help + usage: mesh_doctor.py supported_elements [-h] [--chunck_size 1] [--nproc 8] + + options: + -h, --help show this help message and exit + --chunck_size 1 [int]: Defaults chunk size for parallel processing to 1 + --nproc 8 [int]: Number of threads used for parallel processing. Defaults to your CPU count 8. @@ -179,8 +349,4 @@ API ^^^ .. automodule:: geos.mesh.conversion.abaqus_converter - :members: - - - - + :members: \ No newline at end of file diff --git a/geos-mesh/src/geos/mesh/doctor/parsing/fix_elements_orderings_parsing.py b/geos-mesh/src/geos/mesh/doctor/parsing/fix_elements_orderings_parsing.py index 37c1519a..641a9505 100644 --- a/geos-mesh/src/geos/mesh/doctor/parsing/fix_elements_orderings_parsing.py +++ b/geos-mesh/src/geos/mesh/doctor/parsing/fix_elements_orderings_parsing.py @@ -30,13 +30,12 @@ def fill_subparser( subparsers ) -> None: help=f"[list of integers]: node permutation for \"{element_name}\"." ) p.add_argument( '--' + __VOLUME_TO_REORDER, type=str, - metavar=__VOLUME_TO_REORDER_DEFAULT, default=__VOLUME_TO_REORDER_DEFAULT, - choices=__VOLUME_TO_REORDER_CHOICES, + metavar=",".join( map( str, __VOLUME_TO_REORDER_CHOICES ) ), required=True, help="[str]: Select which element volume is invalid and needs reordering." + - "'all' will allow reordering of nodes for every element, regarding of their volume." + - "'positive' or 'negative' will only reorder the element with the corresponding volume." ) + " 'all' will allow reordering of nodes for every element, regarding of their volume." + + " 'positive' or 'negative' will only reorder the element with the corresponding volume." ) vtk_output_parsing.fill_vtk_output_subparser( p ) From 270f9b8af9d8924d253aa9b8615953a1c074a27f Mon Sep 17 00:00:00 2001 From: alexbenedicto Date: Mon, 16 Sep 2024 15:22:07 -0700 Subject: [PATCH 15/15] yapf formatting --- .../tests/test_fix_elements_orderings.py | 23 ++++--------------- 1 file changed, 4 insertions(+), 19 deletions(-) diff --git a/geos-mesh/tests/test_fix_elements_orderings.py b/geos-mesh/tests/test_fix_elements_orderings.py index b6152043..7df2c079 100644 --- a/geos-mesh/tests/test_fix_elements_orderings.py +++ b/geos-mesh/tests/test_fix_elements_orderings.py @@ -10,25 +10,10 @@ from geos.mesh.doctor.checks.vtk_utils import ( VtkOutput, to_vtk_id_list, write_mesh ) from geos.mesh.doctor.checks.fix_elements_orderings import Options as opt, VTK_TYPE_TO_NAME from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints -from vtkmodules.vtkCommonDataModel import ( - vtkDataSet, - vtkUnstructuredGrid, - vtkCellArray, - vtkHexahedron, - vtkTetra, - vtkPyramid, - vtkVoxel, - vtkWedge, - vtkPentagonalPrism, - vtkHexagonalPrism, - VTK_HEXAHEDRON, - VTK_TETRA, - VTK_PYRAMID, - VTK_WEDGE, - VTK_VOXEL, - VTK_PENTAGONAL_PRISM, - VTK_HEXAGONAL_PRISM -) +from vtkmodules.vtkCommonDataModel import ( vtkDataSet, vtkUnstructuredGrid, vtkCellArray, vtkHexahedron, vtkTetra, + vtkPyramid, vtkVoxel, vtkWedge, vtkPentagonalPrism, vtkHexagonalPrism, + VTK_HEXAHEDRON, VTK_TETRA, VTK_PYRAMID, VTK_WEDGE, VTK_VOXEL, + VTK_PENTAGONAL_PRISM, VTK_HEXAGONAL_PRISM ) def reorder_cell_nodes( mesh: vtkDataSet, cell_id: int, node_ordering: list[ int ] ):