Skip to content

Commit c37764b

Browse files
authored
Improvements in mesh_doctor (#2274)
- Check and fixes for wrong support nodes ordering. - Checks and fixes polyhedron with normals not directed inward. (Not exposed through the CLI though). - Checks if the mesh contains "non-conformal" elements (elements are one in front of the other with non matching nodes). - Checks that all elements are supported by geos, including that polyhedra can be converted into supported elements. - Generates a cube in vtk. - The registration of checks has also been improved. And many other code improvements.
1 parent af2532c commit c37764b

34 files changed

+2841
-640
lines changed

geosx_mesh_doctor/checks/element_volumes.py

Lines changed: 48 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
1-
from dataclasses import dataclass
21
import logging
2+
from dataclasses import dataclass
33
from typing import List, Tuple
44
import uuid
55

6+
from vtkmodules.vtkCommonDataModel import (
7+
VTK_HEXAHEDRON,
8+
VTK_PYRAMID,
9+
VTK_TETRA,
10+
VTK_WEDGE,
11+
)
612
from vtkmodules.vtkFiltersVerdict import (
7-
vtkCellSizeFilter
13+
vtkCellSizeFilter,
14+
vtkMeshQuality,
815
)
9-
from vtk.util.numpy_support import (
16+
from vtkmodules.util.numpy_support import (
1017
vtk_to_numpy,
1118
)
1219

@@ -25,28 +32,48 @@ class Result:
2532

2633

2734
def __check(mesh, options: Options) -> Result:
28-
f = vtkCellSizeFilter()
35+
cs = vtkCellSizeFilter()
2936

30-
f.ComputeAreaOff()
31-
f.ComputeLengthOff()
32-
f.ComputeSumOff()
33-
f.ComputeVertexCountOff()
34-
f.ComputeVolumeOn()
37+
cs.ComputeAreaOff()
38+
cs.ComputeLengthOff()
39+
cs.ComputeSumOff()
40+
cs.ComputeVertexCountOff()
41+
cs.ComputeVolumeOn()
3542
volume_array_name = "__MESH_DOCTOR_VOLUME-" + str(uuid.uuid4()) # Making the name unique
36-
f.SetVolumeArrayName(volume_array_name)
37-
38-
f.SetInputData(mesh)
39-
f.Update()
40-
output = f.GetOutput()
41-
cd = output.GetCellData()
42-
for i in range(cd.GetNumberOfArrays()):
43-
if cd.GetArrayName(i) == volume_array_name:
44-
volume = vtk_to_numpy(cd.GetArray(i))
43+
cs.SetVolumeArrayName(volume_array_name)
44+
45+
cs.SetInputData(mesh)
46+
cs.Update()
47+
48+
mq = vtkMeshQuality()
49+
SUPPORTED_TYPES = [VTK_HEXAHEDRON, VTK_TETRA]
50+
51+
mq.SetTetQualityMeasureToVolume()
52+
mq.SetHexQualityMeasureToVolume()
53+
if hasattr(mq, "SetPyramidQualityMeasureToVolume"): # This feature is quite recent
54+
mq.SetPyramidQualityMeasureToVolume()
55+
SUPPORTED_TYPES.append(VTK_PYRAMID)
56+
mq.SetWedgeQualityMeasureToVolume()
57+
SUPPORTED_TYPES.append(VTK_WEDGE)
58+
else:
59+
logging.debug("Your \"pyvtk\" version does not bring pyramid not wedge support with vtkMeshQuality. Using the fallback solution.")
60+
61+
mq.SetInputData(mesh)
62+
mq.Update()
63+
64+
volume = cs.GetOutput().GetCellData().GetArray(volume_array_name)
65+
quality = mq.GetOutput().GetCellData().GetArray("Quality") # Name is imposed by vtk.
66+
4567
assert volume is not None
68+
assert quality is not None
69+
volume = vtk_to_numpy(volume)
70+
quality = vtk_to_numpy(quality)
4671
small_volumes: List[Tuple[int, float]] = []
47-
for i, v in enumerate(volume):
48-
if v < options.min_volume:
49-
small_volumes.append((i, v))
72+
for i, pack in enumerate(zip(volume, quality)):
73+
v, q = pack
74+
vol = q if mesh.GetCellType(i) in SUPPORTED_TYPES else v
75+
if vol < options.min_volume:
76+
small_volumes.append((i, vol))
5077
return Result(element_volumes=small_volumes)
5178

5279

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from dataclasses import dataclass
2+
import logging
3+
from typing import List, Dict, Set
4+
5+
from vtkmodules.vtkCommonCore import (
6+
vtkIdList,
7+
)
8+
9+
from . import vtk_utils
10+
from .vtk_utils import (
11+
to_vtk_id_list,
12+
VtkOutput,
13+
)
14+
15+
16+
@dataclass(frozen=True)
17+
class Options:
18+
vtk_output: VtkOutput
19+
cell_type_to_ordering: Dict[int, List[int]]
20+
21+
22+
@dataclass(frozen=True)
23+
class Result:
24+
output: str
25+
unchanged_cell_types: Set[int]
26+
27+
28+
def __check(mesh, options: Options):
29+
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.
32+
output_mesh.CopyStructure(mesh)
33+
output_mesh.CopyAttributes(mesh)
34+
35+
# `output_mesh` now contains a full copy of the input mesh.
36+
# We'll now modify the support nodes orderings in place if needed.
37+
cells = output_mesh.GetCells()
38+
for cell_idx in range(output_mesh.GetNumberOfCells()):
39+
cell_type = output_mesh.GetCell(cell_idx).GetCellType()
40+
new_ordering = cell_type_to_ordering.get(cell_type)
41+
if new_ordering:
42+
support_point_ids = vtkIdList()
43+
cells.GetCellAtId(cell_idx, support_point_ids)
44+
new_support_point_ids = []
45+
for i, v in enumerate(new_ordering):
46+
new_support_point_ids.append(support_point_ids.GetId(new_ordering[i]))
47+
cells.ReplaceCellAtId(cell_idx, to_vtk_id_list(new_support_point_ids))
48+
else:
49+
unchanged_cell_types.insert(cell_type)
50+
is_written_error = vtk_utils.write_mesh(output_mesh, options.vtk_output)
51+
return Result(output=options.vtk_output.output if not is_written_error else "",
52+
unchanged_cell_types=set(unchanged_cell_types))
53+
54+
55+
def check(vtk_input_file: str, options: Options) -> Result:
56+
mesh = vtk_utils.read_mesh(vtk_input_file)
57+
return __check(mesh, options)
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
from dataclasses import dataclass
2+
import logging
3+
from typing import Sequence, Iterable
4+
5+
import numpy
6+
7+
from vtkmodules.vtkCommonCore import (
8+
vtkPoints,
9+
)
10+
from vtkmodules.vtkCommonDataModel import (
11+
VTK_HEXAHEDRON,
12+
vtkCellArray,
13+
vtkHexahedron,
14+
vtkRectilinearGrid,
15+
vtkUnstructuredGrid,
16+
)
17+
from vtkmodules.util.numpy_support import (
18+
numpy_to_vtk,
19+
)
20+
21+
from . import vtk_utils
22+
from .vtk_utils import (
23+
VtkOutput,
24+
)
25+
26+
from .generate_global_ids import __build_global_ids
27+
28+
29+
@dataclass(frozen=True)
30+
class Result:
31+
info: str
32+
33+
34+
@dataclass(frozen=True)
35+
class FieldInfo:
36+
name: str
37+
dimension: int
38+
support: str
39+
40+
41+
@dataclass(frozen=True)
42+
class Options:
43+
vtk_output: VtkOutput
44+
generate_cells_global_ids: bool
45+
generate_points_global_ids: bool
46+
xs: Sequence[float]
47+
ys: Sequence[float]
48+
zs: Sequence[float]
49+
nxs: Sequence[int]
50+
nys: Sequence[int]
51+
nzs: Sequence[int]
52+
fields: Iterable[FieldInfo]
53+
54+
55+
@dataclass(frozen=True)
56+
class XYZ:
57+
x: numpy.array
58+
y: numpy.array
59+
z: numpy.array
60+
61+
62+
def build_rectilinear_blocks_mesh(xyzs: Iterable[XYZ]) -> vtkUnstructuredGrid:
63+
"""
64+
Builds an unstructured vtk grid from the `xyzs` blocks. Kind of InternalMeshGenerator.
65+
:param xyzs: The blocks.
66+
:return: The unstructured mesh, even if it's topologically structured.
67+
"""
68+
rgs = []
69+
for xyz in xyzs:
70+
rg = vtkRectilinearGrid()
71+
rg.SetDimensions(len(xyz.x), len(xyz.y), len(xyz.z))
72+
rg.SetXCoordinates(numpy_to_vtk(xyz.x))
73+
rg.SetYCoordinates(numpy_to_vtk(xyz.y))
74+
rg.SetZCoordinates(numpy_to_vtk(xyz.z))
75+
rgs.append(rg)
76+
77+
num_points = sum(map(lambda r: r.GetNumberOfPoints(), rgs))
78+
num_cells = sum(map(lambda r: r.GetNumberOfCells(), rgs))
79+
80+
points = vtkPoints()
81+
points.Allocate(num_points)
82+
for rg in rgs:
83+
for i in range(rg.GetNumberOfPoints()):
84+
points.InsertNextPoint(rg.GetPoint(i))
85+
86+
cell_types = [VTK_HEXAHEDRON] * num_cells
87+
cells = vtkCellArray()
88+
cells.AllocateExact(num_cells, num_cells * 8)
89+
90+
m = (0, 1, 3, 2, 4, 5, 7, 6) # VTK_VOXEL and VTK_HEXAHEDRON do not share the same ordering.
91+
offset = 0
92+
for rg in rgs:
93+
for i in range(rg.GetNumberOfCells()):
94+
c = rg.GetCell(i)
95+
new_cell = vtkHexahedron()
96+
for j in range(8):
97+
new_cell.GetPointIds().SetId(j, offset + c.GetPointId(m[j]))
98+
cells.InsertNextCell(new_cell)
99+
offset += rg.GetNumberOfPoints()
100+
101+
mesh = vtkUnstructuredGrid()
102+
mesh.SetPoints(points)
103+
mesh.SetCells(cell_types, cells)
104+
105+
return mesh
106+
107+
108+
def __add_fields(mesh: vtkUnstructuredGrid, fields: Iterable[FieldInfo]) -> vtkUnstructuredGrid:
109+
for field_info in fields:
110+
if field_info.support == "CELLS":
111+
data = mesh.GetCellData()
112+
n = mesh.GetNumberOfCells()
113+
elif field_info.support == "POINTS":
114+
data = mesh.GetPointData()
115+
n = mesh.GetNumberOfPoints()
116+
array = numpy.ones((n, field_info.dimension), dtype=float)
117+
vtk_array = numpy_to_vtk(array)
118+
vtk_array.SetName(field_info.name)
119+
data.AddArray(vtk_array)
120+
return mesh
121+
122+
123+
def __build(options: Options):
124+
def build_coordinates(positions, num_elements):
125+
result = []
126+
it = zip(zip(positions, positions[1:]), num_elements)
127+
try:
128+
coords, n = next(it)
129+
while True:
130+
start, stop = coords
131+
end_point = False
132+
tmp = numpy.linspace(start=start, stop=stop, num=n+end_point, endpoint=end_point)
133+
coords, n = next(it)
134+
result.append(tmp)
135+
except StopIteration:
136+
end_point = True
137+
tmp = numpy.linspace(start=start, stop=stop, num=n+end_point, endpoint=end_point)
138+
result.append(tmp)
139+
return numpy.concatenate(result)
140+
x = build_coordinates(options.xs, options.nxs)
141+
y = build_coordinates(options.ys, options.nys)
142+
z = build_coordinates(options.zs, options.nzs)
143+
cube = build_rectilinear_blocks_mesh((XYZ(x, y, z),))
144+
cube = __add_fields(cube, options.fields)
145+
__build_global_ids(cube, options.generate_cells_global_ids, options.generate_points_global_ids)
146+
return cube
147+
148+
149+
def __check(options: Options) -> Result:
150+
output_mesh = __build(options)
151+
vtk_utils.write_mesh(output_mesh, options.vtk_output)
152+
return Result(info=f"Mesh was written to {options.vtk_output.output}")
153+
154+
155+
def check(vtk_input_file: str, options: Options) -> Result:
156+
try:
157+
return __check(options)
158+
except BaseException as e:
159+
logging.error(e)
160+
return Result(info="Something went wrong.")

0 commit comments

Comments
 (0)