Skip to content

Commit 5899a4b

Browse files
authored
Enhance the fracture generation feature of mesh_doctor (#2793)
- Complete rewriting of the fracture generation algorithm. There should be not limitation anymore, and it supports `VTK_POLYHEDRON` thanks to @IsaacJu-debug 's investigations. - Efforts were paid to validate the code using `mypy` and type hints. Not perfect yet, but still a good improvement.
1 parent e098823 commit 5899a4b

22 files changed

+1028
-544
lines changed
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
from dataclasses import dataclass
2+
import logging
3+
4+
from typing import (
5+
Collection,
6+
FrozenSet,
7+
Iterable,
8+
Sequence,
9+
Set,
10+
Tuple,
11+
)
12+
13+
from tqdm import tqdm
14+
import numpy
15+
16+
from vtkmodules.vtkCommonDataModel import (
17+
vtkUnstructuredGrid,
18+
vtkCell,
19+
)
20+
from vtkmodules.vtkCommonCore import (
21+
vtkPoints,
22+
)
23+
from vtkmodules.vtkIOXML import (
24+
vtkXMLMultiBlockDataReader,
25+
)
26+
from vtkmodules.util.numpy_support import (
27+
vtk_to_numpy,
28+
)
29+
from vtk_utils import (
30+
vtk_iter,
31+
)
32+
33+
34+
@dataclass(frozen=True)
35+
class Options:
36+
tolerance: float
37+
matrix_name: str
38+
fracture_name: str
39+
collocated_nodes_field_name: str
40+
41+
42+
@dataclass(frozen=True)
43+
class Result:
44+
# First index is the local index of the fracture mesh.
45+
# Second is the local index of the matrix mesh.
46+
# Third is the global index in the matrix mesh.
47+
errors: Sequence[tuple[int, int, int]]
48+
49+
50+
def __read_multiblock(vtk_input_file: str, matrix_name: str, fracture_name: str) -> Tuple[vtkUnstructuredGrid, vtkUnstructuredGrid]:
51+
reader = vtkXMLMultiBlockDataReader()
52+
reader.SetFileName(vtk_input_file)
53+
reader.Update()
54+
multi_block = reader.GetOutput()
55+
for b in range(multi_block.GetNumberOfBlocks()):
56+
block_name: str = multi_block.GetMetaData(b).Get(multi_block.NAME())
57+
if block_name == matrix_name:
58+
matrix: vtkUnstructuredGrid = multi_block.GetBlock(b)
59+
if block_name == fracture_name:
60+
fracture: vtkUnstructuredGrid = multi_block.GetBlock(b)
61+
assert matrix and fracture
62+
return matrix, fracture
63+
64+
65+
def format_collocated_nodes(fracture_mesh: vtkUnstructuredGrid) -> Sequence[Iterable[int]]:
66+
"""
67+
Extract the collocated nodes information from the mesh and formats it in a python way.
68+
:param fracture_mesh: The mesh of the fracture (with 2d cells).
69+
:return: An iterable over all the buckets of collocated nodes.
70+
"""
71+
collocated_nodes: numpy.ndarray = vtk_to_numpy(fracture_mesh.GetPointData().GetArray("collocated_nodes"))
72+
if len(collocated_nodes.shape) == 1:
73+
collocated_nodes: numpy.ndarray = collocated_nodes.reshape((collocated_nodes.shape[0], 1))
74+
generator = (tuple(sorted(bucket[bucket > -1])) for bucket in collocated_nodes)
75+
return tuple(generator)
76+
77+
78+
def __check_collocated_nodes_positions(matrix_points: Sequence[Tuple[float, float, float]],
79+
fracture_points: Sequence[Tuple[float, float, float]],
80+
g2l: Sequence[int],
81+
collocated_nodes: Iterable[Iterable[int]]) -> Collection[Tuple[int, Iterable[int], Iterable[Tuple[float, float, float]]]]:
82+
issues = []
83+
for li, bucket in enumerate(collocated_nodes):
84+
matrix_nodes = (fracture_points[li], ) + tuple(map(lambda gi: matrix_points[g2l[gi]], bucket))
85+
m = numpy.array(matrix_nodes)
86+
rank: int = numpy.linalg.matrix_rank(m)
87+
if rank > 1:
88+
issues.append((li, bucket, tuple(map(lambda gi: matrix_points[g2l[gi]], bucket))))
89+
return issues
90+
91+
92+
def my_iter(ccc):
93+
car, cdr = ccc[0], ccc[1:]
94+
for i in car:
95+
if cdr:
96+
for j in my_iter(cdr):
97+
yield i, *j
98+
else:
99+
yield (i, )
100+
101+
102+
def __check_neighbors(matrix: vtkUnstructuredGrid,
103+
fracture: vtkUnstructuredGrid,
104+
g2l: Sequence[int],
105+
collocated_nodes: Sequence[Iterable[int]]):
106+
fracture_nodes: Set[int] = set()
107+
for bucket in collocated_nodes:
108+
for gi in bucket:
109+
fracture_nodes.add(g2l[gi])
110+
# For each face of each cell,
111+
# if all the points of the face are "made" of collocated nodes,
112+
# then this is a fracture face.
113+
fracture_faces: Set[FrozenSet[int]] = set()
114+
for c in range(matrix.GetNumberOfCells()):
115+
cell: vtkCell = matrix.GetCell(c)
116+
for f in range(cell.GetNumberOfFaces()):
117+
face: vtkCell = cell.GetFace(f)
118+
point_ids = frozenset(vtk_iter(face.GetPointIds()))
119+
if point_ids <= fracture_nodes:
120+
fracture_faces.add(point_ids)
121+
# Finding the cells
122+
for c in tqdm(range(fracture.GetNumberOfCells()), desc="Finding neighbor cell pairs"):
123+
cell: vtkCell = fracture.GetCell(c)
124+
cns: Set[FrozenSet[int]] = set() # subset of collocated_nodes
125+
point_ids = frozenset(vtk_iter(cell.GetPointIds()))
126+
for point_id in point_ids:
127+
bucket = collocated_nodes[point_id]
128+
local_bucket = frozenset(map(g2l.__getitem__, bucket))
129+
cns.add(local_bucket)
130+
found = 0
131+
tmp = tuple(map(tuple, cns))
132+
for node_combinations in my_iter(tmp):
133+
f = frozenset(node_combinations)
134+
if f in fracture_faces:
135+
found += 1
136+
if found != 2:
137+
logging.warning(f"Something went wrong since we should have found 2 fractures faces (we found {found}) for collocated nodes {cns}.")
138+
139+
140+
def __check(vtk_input_file: str, options: Options) -> Result:
141+
matrix, fracture = __read_multiblock(vtk_input_file, options.matrix_name, options.fracture_name)
142+
matrix_points: vtkPoints = matrix.GetPoints()
143+
fracture_points: vtkPoints = fracture.GetPoints()
144+
145+
collocated_nodes: Sequence[Iterable[int]] = format_collocated_nodes(fracture)
146+
assert matrix.GetPointData().GetGlobalIds() and matrix.GetCellData().GetGlobalIds() and \
147+
fracture.GetPointData().GetGlobalIds() and fracture.GetCellData().GetGlobalIds()
148+
149+
point_ids = vtk_to_numpy(matrix.GetPointData().GetGlobalIds())
150+
g2l = numpy.ones(len(point_ids), dtype=int) * -1
151+
for loc, glo in enumerate(point_ids):
152+
g2l[glo] = loc
153+
g2l.flags.writeable = False
154+
155+
issues = __check_collocated_nodes_positions(vtk_to_numpy(matrix.GetPoints().GetData()),
156+
vtk_to_numpy(fracture.GetPoints().GetData()),
157+
g2l,
158+
collocated_nodes)
159+
assert len(issues) == 0
160+
161+
__check_neighbors(matrix, fracture, g2l, collocated_nodes)
162+
163+
errors = []
164+
for i, duplicates in enumerate(collocated_nodes):
165+
for duplicate in filter(lambda i: i > -1, duplicates):
166+
p0 = matrix_points.GetPoint(g2l[duplicate])
167+
p1 = fracture_points.GetPoint(i)
168+
if numpy.linalg.norm(numpy.array(p1) - numpy.array(p0)) > options.tolerance:
169+
errors.append((i, g2l[duplicate], duplicate))
170+
return Result(errors=errors)
171+
172+
173+
def check(vtk_input_file: str, options: Options) -> Result:
174+
try:
175+
return __check(vtk_input_file, options)
176+
except BaseException as e:
177+
logging.error(e)
178+
return Result(errors=())

geosx_mesh_doctor/checks/collocated_nodes.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
from collections import defaultdict
22
from dataclasses import dataclass
33
import logging
4-
from typing import List
5-
4+
from typing import (
5+
Collection,
6+
Iterable,
7+
)
68
import numpy
79

810
from vtkmodules.vtkCommonCore import (
@@ -22,8 +24,8 @@ class Options:
2224

2325
@dataclass(frozen=True)
2426
class Result:
25-
nodes_buckets: List[List[int]] # Each bucket contains the duplicated node indices.
26-
wrong_support_elements: List[int] # Element indices with support node indices appearing more than once.
27+
nodes_buckets: Iterable[Iterable[int]] # Each bucket contains the duplicated node indices.
28+
wrong_support_elements: Collection[int] # Element indices with support node indices appearing more than once.
2729

2830

2931
def __check(mesh, options: Options) -> Result:

geosx_mesh_doctor/checks/element_volumes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def __check(mesh, options: Options) -> Result:
5656
mq.SetWedgeQualityMeasureToVolume()
5757
SUPPORTED_TYPES.append(VTK_WEDGE)
5858
else:
59-
logging.debug("Your \"pyvtk\" version does not bring pyramid not wedge support with vtkMeshQuality. Using the fallback solution.")
59+
logging.warning("Your \"pyvtk\" version does not bring pyramid nor wedge support with vtkMeshQuality. Using the fallback solution.")
6060

6161
mq.SetInputData(mesh)
6262
mq.Update()

geosx_mesh_doctor/checks/fix_elements_orderings.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
from dataclasses import dataclass
22
import logging
3-
from typing import List, Dict, Set
3+
from typing import (
4+
List,
5+
Dict,
6+
Set,
7+
FrozenSet,
8+
)
49

510
from vtkmodules.vtkCommonCore import (
611
vtkIdList,
@@ -22,21 +27,25 @@ class Options:
2227
@dataclass(frozen=True)
2328
class Result:
2429
output: str
25-
unchanged_cell_types: Set[int]
30+
unchanged_cell_types: FrozenSet[int]
2631

2732

28-
def __check(mesh, options: Options):
33+
def __check(mesh, options: Options) -> Result:
34+
# The vtk cell type is an int and will be the key of the following mapping,
35+
# that will point to the relevant permutation.
2936
cell_type_to_ordering: Dict[int, List[int]] = options.cell_type_to_ordering
30-
unchanged_cell_types = []
31-
output_mesh = mesh.NewInstance() # keeping the same instance type.
37+
unchanged_cell_types: Set[int] = set() # For logging purpose
38+
39+
# Preparing the output mesh by first keeping the same instance type.
40+
output_mesh = mesh.NewInstance()
3241
output_mesh.CopyStructure(mesh)
3342
output_mesh.CopyAttributes(mesh)
3443

3544
# `output_mesh` now contains a full copy of the input mesh.
3645
# We'll now modify the support nodes orderings in place if needed.
3746
cells = output_mesh.GetCells()
3847
for cell_idx in range(output_mesh.GetNumberOfCells()):
39-
cell_type = output_mesh.GetCell(cell_idx).GetCellType()
48+
cell_type: int = output_mesh.GetCell(cell_idx).GetCellType()
4049
new_ordering = cell_type_to_ordering.get(cell_type)
4150
if new_ordering:
4251
support_point_ids = vtkIdList()
@@ -46,10 +55,10 @@ def __check(mesh, options: Options):
4655
new_support_point_ids.append(support_point_ids.GetId(new_ordering[i]))
4756
cells.ReplaceCellAtId(cell_idx, to_vtk_id_list(new_support_point_ids))
4857
else:
49-
unchanged_cell_types.insert(cell_type)
58+
unchanged_cell_types.add(cell_type)
5059
is_written_error = vtk_utils.write_mesh(output_mesh, options.vtk_output)
5160
return Result(output=options.vtk_output.output if not is_written_error else "",
52-
unchanged_cell_types=set(unchanged_cell_types))
61+
unchanged_cell_types=frozenset(unchanged_cell_types))
5362

5463

5564
def check(vtk_input_file: str, options: Options) -> Result:

geosx_mesh_doctor/checks/generate_cube.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,9 +54,9 @@ class Options:
5454

5555
@dataclass(frozen=True)
5656
class XYZ:
57-
x: numpy.array
58-
y: numpy.array
59-
z: numpy.array
57+
x: numpy.ndarray
58+
y: numpy.ndarray
59+
z: numpy.ndarray
6060

6161

6262
def build_rectilinear_blocks_mesh(xyzs: Iterable[XYZ]) -> vtkUnstructuredGrid:

0 commit comments

Comments
 (0)