Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Correction of erroneous copy of pre-split tags to new split meshes #34

Merged
merged 20 commits into from
Oct 14, 2024
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
53fd564
First correction now allowing for the copy of fields from the origina…
alexbenedicto Jul 24, 2024
4615f63
Module import paths update
alexbenedicto Jul 29, 2024
0f8c2ba
Fields are now copied correctly for fracture_mesh.
alexbenedicto Aug 1, 2024
4cee143
Test case added to check the __copy_fields feature.
alexbenedicto Aug 1, 2024
6759376
Invalid packages import for geos-mesh, impacting documentation (#32)
alexbenedicto Aug 5, 2024
a392c4b
Correction for yapf valid format.
alexbenedicto Aug 13, 2024
9cf4007
Had to make a workaround to avoid a weird yapf formatting suggestion.
alexbenedicto Aug 13, 2024
b8bc706
Mistake correction
alexbenedicto Aug 13, 2024
c64a6ba
First correction now allowing for the copy of fields from the origina…
alexbenedicto Jul 24, 2024
5343cf6
Module import paths update
alexbenedicto Jul 29, 2024
29f01a3
Fields are now copied correctly for fracture_mesh.
alexbenedicto Aug 1, 2024
269b09b
Test case added to check the __copy_fields feature.
alexbenedicto Aug 1, 2024
779ae68
Correction for yapf valid format.
alexbenedicto Aug 13, 2024
a6f1d68
Had to make a workaround to avoid a weird yapf formatting suggestion.
alexbenedicto Aug 13, 2024
2179463
Mistake correction
alexbenedicto Aug 13, 2024
278adc9
Merge branch 'origin/benedicto/bugfix/issue#31_pre_split_tags' of htt…
alexbenedicto Aug 14, 2024
8495595
First part of review corrections
alexbenedicto Sep 12, 2024
47b6380
Second part correction of reviews
alexbenedicto Sep 12, 2024
a4ff5e5
Last part of correction review
alexbenedicto Sep 13, 2024
b763288
Updated method when linking old cells to new cells ids to copy correc…
alexbenedicto Sep 20, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -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
Expand Down
3 changes: 1 addition & 2 deletions geos-mesh/src/geos/mesh/conversion/main.py
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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()
Expand Down
1 change: 1 addition & 0 deletions geos-mesh/src/geos/mesh/doctor/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# Empty
2 changes: 1 addition & 1 deletion geos-mesh/src/geos/mesh/doctor/checks/check_fractures.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
vtkXMLMultiBlockDataReader, )
from vtkmodules.util.numpy_support import (
vtk_to_numpy, )
from vtk_utils import (
from .vtk_utils import (
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

absolute imports are the recommended import style per the PEP 8 styling guide

vtk_iter, )


Expand Down
156 changes: 137 additions & 19 deletions geos-mesh/src/geos/mesh/doctor/checks/generate_fractures.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from collections import defaultdict
from dataclasses import dataclass
from sys import exit
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

never use exit in a function. return an error code like -1 or an exception

import logging
from typing import (
Collection,
Expand All @@ -19,6 +20,7 @@
import networkx
import numpy

from vtk import vtkDataArray
from vtkmodules.vtkCommonCore import (
vtkIdList,
vtkPoints,
Expand Down Expand Up @@ -252,45 +254,156 @@ def __call__( self, index: int ) -> int:
return result


def __copy_fields( old_mesh: vtkUnstructuredGrid, new_mesh: vtkUnstructuredGrid,
collocated_nodes: Sequence[ int ] ) -> None:
def cells_points_coordinates( mesh: vtkUnstructuredGrid ) -> dict[ int, list[ tuple[ float ] ] ]:
"""Map each cell id to a list of its points coordinates.

Args:
mesh (vtkUnstructuredGrid): An unstructured mesh.

Returns:
dict[int, list[tuple[float]]]: {cell_id1: [(pt0_x, pt0_y, pt0_z), ...], ..., cell_idN: [...]}
for a mesh containing N cells.
"""
Copies the fields from the old mesh to the new one.
Point data will be duplicated for collocated nodes.
:param old_mesh: The mesh before the split.
:param new_mesh: The mesh after the split. Will receive the fields in place.
:param collocated_nodes: New index to old index.
:return: None
cells_nodes_coordinates: dict[ int, list[ tuple[ float ] ] ] = {}
for i in range( mesh.GetNumberOfCells() ):
cell: vtkCell = mesh.GetCell( i )
cells_nodes_coordinates[ i ] = []
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

list() is more comprehensive for empty lists than square brackets.

cell_points = cell.GetPoints()
for v in range( cell.GetNumberOfPoints() ):
node_coordinates: tuple[ float ] = cell_points.GetPoint( v )
truncated_coordinates: list[ float ] = [ float( '%.3f' % ( coord ) ) for coord in node_coordinates ]
cells_nodes_coordinates[ i ].append( tuple( truncated_coordinates ) )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't get the idea. You get a tuple, put it in a list via float cutting and the back to tuple ? btw python float has double precision on almost all platforms. There is not need to cast them

return cells_nodes_coordinates


def link_new_cells_id_with_old_cells_id( old_mesh: vtkUnstructuredGrid,
new_mesh: vtkUnstructuredGrid ) -> dict[ int, int ]:
"""After mapping each cell id to a list of its points coordinates for the old and new mesh,
it is possible to determine the link between old and new cell id by coordinates position.

Args:
old_mesh (vtkUnstructuredGrid): An unstructured mesh before splitting.
new_mesh (vtkUnstructuredGrid): An unstructured mesh after splitting the old_mesh.

Returns:
dict[int, int]: { new_cell_id: old_cell_id }
"""
new_cell_ids_with_old_cell_ids: dict[ int, int ] = {}
cpc_old_mesh: dict[ int, list[ tuple[ float ] ] ] = cells_points_coordinates( old_mesh )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is possible to have a TypeAlias for this one. It would make it more human-friendly

cpc_new_mesh: dict[ int, list[ tuple[ float ] ] ] = cells_points_coordinates( new_mesh )
for new_cell_id, new_coords in cpc_new_mesh.items():
for old_cell_id, old_coords in cpc_old_mesh.items():
if all( elem in new_coords for elem in old_coords ):
new_cell_ids_with_old_cell_ids[ new_cell_id ] = old_cell_id
break
return new_cell_ids_with_old_cell_ids


def __copy_cell_data( old_mesh: vtkUnstructuredGrid, new_mesh: vtkUnstructuredGrid, is_fracture_mesh: bool = False ):
"""Copy the cell data arrays from the old mesh to the new mesh.
If the new mesh is a fracture_mesh, the fracture_mesh has less cells than the old mesh.
Therefore, copying the entire old cell arrays to the new one will assignate invalid
indexing of the values of these arrays.
So here, we consider the new indexing of cells to add an array with the valid size of elements
and correct indexes.

Args:
old_mesh (vtkUnstructuredGrid): An unstructured mesh before splitting.
new_mesh (vtkUnstructuredGrid): An unstructured mesh after splitting the old_mesh.
is_fracture_mesh (bool, optional): True if dealing with a new_mesh being the fracture mesh.
Defaults to False.
"""
# Copying the cell data.
# The cells are the same, just their nodes support have changed.
if is_fracture_mesh:
new_to_old_cells: dict[ int, int ] = link_new_cells_id_with_old_cells_id( old_mesh, new_mesh )
input_cell_data = old_mesh.GetCellData()
for i in range( input_cell_data.GetNumberOfArrays() ):
input_array = input_cell_data.GetArray( i )
input_array: vtkDataArray = input_cell_data.GetArray( i )
logging.info( f"Copying cell field \"{input_array.GetName()}\"." )
new_mesh.GetCellData().AddArray( input_array )
if is_fracture_mesh:
array_name: str = input_array.GetName()
old_values: list = vtk_to_numpy( input_array ).tolist()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why tolist ? It is possible to iterate over the numpy array directly

useful_values: list = []
for old_cell_id in new_to_old_cells.values():
useful_values.append( old_values[ old_cell_id ] )
useful_input_array = numpy_to_vtk( numpy.array( useful_values ) )
useful_input_array.SetName( array_name )
new_mesh.GetCellData().AddArray( useful_input_array )
else:
new_mesh.GetCellData().AddArray( input_array )


# TODO consider the fracture_mesh case ?
def __copy_field_data( old_mesh: vtkUnstructuredGrid, new_mesh: vtkUnstructuredGrid ):
"""Copy the field data arrays from the old mesh to the new mesh.
Does not consider the fracture_mesh case.

Args:
old_mesh (vtkUnstructuredGrid): An unstructured mesh before splitting.
new_mesh (vtkUnstructuredGrid): An unstructured mesh after splitting the old_mesh.
"""
# Copying field data. This data is a priori not related to geometry.
input_field_data = old_mesh.GetFieldData()
for i in range( input_field_data.GetNumberOfArrays() ):
input_array = input_field_data.GetArray( i )
logging.info( f"Copying field data \"{input_array.GetName()}\"." )
new_mesh.GetFieldData().AddArray( input_array )


# TODO consider the fracture_mesh case ?
def __copy_point_data( old_mesh: vtkUnstructuredGrid, new_mesh: vtkUnstructuredGrid,
collocated_nodes: Sequence[ int ] ):
"""Copy the point data arrays from the old mesh to the new mesh.
Does not consider the fracture_mesh case.

Args:
old_mesh (vtkUnstructuredGrid): An unstructured mesh before splitting.
new_mesh (vtkUnstructuredGrid): An unstructured mesh after splitting the old_mesh.
"""
# Copying the point data.
input_point_data = old_mesh.GetPointData()
for i in range( input_point_data.GetNumberOfArrays() ):
input_array = input_point_data.GetArray( i )
logging.info( f"Copying point field \"{input_array.GetName()}\"" )
logging.info( f"Copying point field \"{input_array.GetName()}\"." )
tmp = input_array.NewInstance()
tmp.SetName( input_array.GetName() )
tmp.SetNumberOfComponents( input_array.GetNumberOfComponents() )
tmp.SetNumberOfTuples( new_mesh.GetNumberOfPoints() )
for p in range( tmp.GetNumberOfTuples() ):
tmp.SetTuple( p, input_array.GetTuple( collocated_nodes[ p ] ) )
collocated_nodes_p = collocated_nodes[ p ]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could use deepCopy() (memcpy) instead of iterating and copying

if isinstance( collocated_nodes_p, numpy.integer ):
tmp.SetTuple( p, input_array.GetTuple( collocated_nodes_p ) )
else: # when using collocated_nodes from fracture mesh, it ia an array
tmp.SetTuple( p, input_array.GetTuple( collocated_nodes_p[ 0 ] ) )
new_mesh.GetPointData().AddArray( tmp )


def __copy_fields( old_mesh: vtkUnstructuredGrid,
new_mesh: vtkUnstructuredGrid,
collocated_nodes: Sequence[ int ],
is_fracture_mesh: bool = False ) -> None:
"""
Copies the fields from the old mesh to the new one.
Point data will be duplicated for collocated nodes.
:param old_mesh: The mesh before the split.
:param new_mesh: The mesh after the split. Will receive the fields in place.
:param collocated_nodes: New index to old index.
:param is_fracture_mesh: True when copying fields for a fracture mesh, False instead.
:return: None
"""
# Mesh cannot contains global ids before splitting.
if vtk_utils.has_invalid_field( old_mesh, [ "GLOBAL_IDS_POINTS", "GLOBAL_IDS_CELLS" ] ):
logging.critical( f"The mesh cannot contain global ids for neither cells nor points." )
logging.critical( f"The correct procedure is to split the mesh and then generate global" +
"ids for new split meshes. Dying ..." )
exit( 1 )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as before


__copy_cell_data( old_mesh, new_mesh, is_fracture_mesh )
__copy_field_data( old_mesh, new_mesh )
__copy_point_data( old_mesh, new_mesh, collocated_nodes )


def __perform_split( old_mesh: vtkUnstructuredGrid,
cell_to_node_mapping: Mapping[ int, Mapping[ int, int ] ] ) -> vtkUnstructuredGrid:
"""
Expand Down Expand Up @@ -366,7 +479,7 @@ def __perform_split( old_mesh: vtkUnstructuredGrid,
return new_mesh


def __generate_fracture_mesh( mesh_points: vtkPoints, fracture_info: FractureInfo,
def __generate_fracture_mesh( old_mesh: vtkUnstructuredGrid, fracture_info: FractureInfo,
cell_to_node_mapping: Mapping[ int, Mapping[ int, int ] ] ) -> vtkUnstructuredGrid:
"""
Generates the mesh of the fracture.
Expand All @@ -377,6 +490,7 @@ def __generate_fracture_mesh( mesh_points: vtkPoints, fracture_info: FractureInf
"""
logging.info( "Generating the meshes" )

mesh_points: vtkPoints = old_mesh.GetPoints()
is_node_duplicated = numpy.zeros( mesh_points.GetNumberOfPoints(), dtype=bool ) # defaults to False
for node_mapping in cell_to_node_mapping.values():
for i, o in node_mapping.items():
Expand All @@ -399,11 +513,12 @@ def __generate_fracture_mesh( mesh_points: vtkPoints, fracture_info: FractureInf
# for dfns in discarded_face_nodes:
# tmp.append(", ".join(map(str, dfns)))
msg: str = "(" + '), ('.join( map( lambda dfns: ", ".join( map( str, dfns ) ), discarded_face_nodes ) ) + ")"
# logging.info(f"The {len(tmp)} faces made of nodes ({'), ('.join(tmp)}) were/was discarded from the fracture mesh because none of their/its nodes were duplicated.")
# print(f"The {len(tmp)} faces made of nodes ({'), ('.join(tmp)}) were/was discarded from the fracture mesh because none of their/its nodes were duplicated.")
print(
f"The faces made of nodes [{msg}] were/was discarded from the fracture mesh because none of their/its nodes were duplicated."
)
# logging.info(f"The {len(tmp)} faces made of nodes ({'), ('.join(tmp)}) were/was discarded"
# + "from the fracture mesh because none of their/its nodes were duplicated.")
# print(f"The {len(tmp)} faces made of nodes ({'), ('.join(tmp)}) were/was discarded"
# + "from the fracture mesh because none of their/its nodes were duplicated.")
print( f"The faces made of nodes [{msg}] were/was discarded" +
"from the fracture mesh because none of their/its nodes were duplicated." )

fracture_nodes_tmp = numpy.ones( mesh_points.GetNumberOfPoints(), dtype=int ) * -1
for ns in face_nodes:
Expand Down Expand Up @@ -448,6 +563,9 @@ def __generate_fracture_mesh( mesh_points: vtkPoints, fracture_info: FractureInf
if polygons.GetNumberOfCells() > 0:
fracture_mesh.SetCells( [ VTK_POLYGON ] * polygons.GetNumberOfCells(), polygons )
fracture_mesh.GetPointData().AddArray( array )

__copy_fields( old_mesh, fracture_mesh, collocated_nodes, True )

return fracture_mesh


Expand All @@ -458,7 +576,7 @@ def __split_mesh_on_fracture( mesh: vtkUnstructuredGrid,
cell_to_node_mapping: Mapping[ int, Mapping[ int, int ] ] = __identify_split( mesh.GetNumberOfPoints(),
cell_to_cell, fracture.node_to_cells )
output_mesh: vtkUnstructuredGrid = __perform_split( mesh, cell_to_node_mapping )
fractured_mesh: vtkUnstructuredGrid = __generate_fracture_mesh( mesh.GetPoints(), fracture, cell_to_node_mapping )
fractured_mesh: vtkUnstructuredGrid = __generate_fracture_mesh( mesh, fracture, cell_to_node_mapping )
return output_mesh, fractured_mesh


Expand Down
35 changes: 35 additions & 0 deletions geos-mesh/src/geos/mesh/doctor/checks/vtk_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,38 @@ def vtk_iter( l ) -> Iterator[ Any ]:
yield l.GetCellType( i )


def has_invalid_field( mesh: vtkUnstructuredGrid, invalid_fields: list[ str ] ) -> bool:
"""Checks if a mesh contains at least a data arrays within its cell, field or point data
having a certain name. If so, returns True, else False.

Args:
mesh (vtkUnstructuredGrid): An unstructured mesh.
invalid_fields (list[str]): Field name of an array in any data from the data.

Returns:
bool: True if one field found, else False.
"""
# Check the cell data fields
cell_data = mesh.GetCellData()
for i in range( cell_data.GetNumberOfArrays() ):
if cell_data.GetArrayName( i ) in invalid_fields:
logging.error( f"The mesh contains an invalid cell field name '{cell_data.GetArrayName( i )}'." )
return True
# Check the field data fields
field_data = mesh.GetFieldData()
for i in range( field_data.GetNumberOfArrays() ):
if field_data.GetArrayName( i ) in invalid_fields:
logging.error( f"The mesh contains an invalid field name '{field_data.GetArrayName( i )}'." )
return True
# Check the point data fields
point_data = mesh.GetPointData()
for i in range( point_data.GetNumberOfArrays() ):
if point_data.GetArrayName( i ) in invalid_fields:
logging.error( f"The mesh contains an invalid point field name '{point_data.GetArrayName( i )}'." )
return True
return False


def __read_vtk( vtk_input_file: str ) -> Optional[ vtkUnstructuredGrid ]:
reader = vtkUnstructuredGridReader()
logging.info( f"Testing file format \"{vtk_input_file}\" using legacy format reader..." )
Expand Down Expand Up @@ -83,6 +115,9 @@ 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.
"""
if not os.path.exists( vtk_input_file ):
logging.critical( f"Invalid file path. COuld not read \"{vtk_input_file}\". Dying..." )
sys.exit( 1 )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here. Think exceptions. exit is a bit extreme

file_extension = os.path.splitext( vtk_input_file )[ -1 ]
extension_to_reader = { ".vtk": __read_vtk, ".vtu": __read_vtu }
# Testing first the reader that should match
Expand Down
Loading
Loading