Skip to content

Enhance the fracture generation feature of mesh_doctor #2793

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

Merged
merged 32 commits into from
Dec 9, 2023
Merged
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
e97aa3e
cell_to_faces for fracture generation.
TotoGaz Jun 24, 2023
1b81668
First more or less working new version of the fracture generation.
TotoGaz Oct 17, 2023
6d9e148
Little improvement of the testing.
TotoGaz Oct 17, 2023
130bc4d
remove commented code
TotoGaz Oct 17, 2023
3c87eca
More tests
TotoGaz Oct 18, 2023
77c7c88
Another test
TotoGaz Oct 18, 2023
c5805e4
Comments
TotoGaz Oct 19, 2023
84121d1
Add copy field
TotoGaz Oct 19, 2023
8dac541
first version of check_fractures.py
TotoGaz Oct 24, 2023
bc25c93
Merge branch 'develop' into TotoGaz/feature/meshDoctorFracture
TotoGaz Oct 30, 2023
5418a69
Replace _2 files and add polyhedron support.
TotoGaz Oct 31, 2023
bb9adb4
Discard fracture element with no duplicated node.
TotoGaz Nov 1, 2023
7ad903e
Merge branch 'develop' into TotoGaz/feature/meshDoctorFracture
TotoGaz Nov 16, 2023
4336a1d
Merge branch 'develop' into TotoGaz/feature/meshDoctorFracture
TotoGaz Nov 28, 2023
774431d
Attempts to use mypy
TotoGaz Nov 28, 2023
e4deac9
Attempts to use mypy, again
TotoGaz Nov 28, 2023
1229b7f
Merge branch 'develop' into TotoGaz/feature/meshDoctorFracture
TotoGaz Nov 29, 2023
561ab13
Parsing mistake: int instead of float
TotoGaz Nov 29, 2023
7a6f9a9
cleaning + mypy, and comments.
TotoGaz Nov 30, 2023
d55f8ef
Merge branch 'develop' into TotoGaz/feature/meshDoctorFracture
TotoGaz Nov 30, 2023
79aaca7
Using Mesh doctor generate fractures with internal surfaces already d…
AntoineMazuyer Dec 4, 2023
fdc0caf
Small reorgs, using an enum to distinguish between fracture descripti…
TotoGaz Dec 5, 2023
8468d4e
Add a unit tests for internal fracture description
TotoGaz Dec 5, 2023
32b4267
Use a generator instead of clumsy maps, filters...
TotoGaz Dec 5, 2023
618ce7f
Use a dataclass instead of a tuple
TotoGaz Dec 5, 2023
26f155f
Use a dataclass instead of a tuple
TotoGaz Dec 5, 2023
f1a3159
Add CLI tests + remove useless split_on_domain_boundary option.
TotoGaz Dec 5, 2023
215ca8f
Little refactoring
TotoGaz Dec 5, 2023
0f3eaa2
Merge branch 'develop' into TotoGaz/feature/meshDoctorFracture
TotoGaz Dec 5, 2023
22b29f2
Merge branch 'develop' into TotoGaz/feature/meshDoctorFracture
TotoGaz Dec 6, 2023
400b74e
Merge branch 'develop' into TotoGaz/feature/meshDoctorFracture
TotoGaz Dec 6, 2023
ddae8f7
Merge branch 'develop' into TotoGaz/feature/meshDoctorFracture
TotoGaz Dec 9, 2023
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
from dataclasses import dataclass
import logging

from typing import (
Collection,
FrozenSet,
Iterable,
Sequence,
Set,
Tuple,
)

from tqdm import tqdm
import numpy

from vtkmodules.vtkCommonDataModel import (
vtkUnstructuredGrid,
vtkCell,
)
from vtkmodules.vtkCommonCore import (
vtkPoints,
)
from vtkmodules.vtkIOXML import (
vtkXMLMultiBlockDataReader,
)
from vtkmodules.util.numpy_support import (
vtk_to_numpy,
)
from vtk_utils import (
vtk_iter,
)


@dataclass(frozen=True)
class Options:
tolerance: float
matrix_name: str
fracture_name: str
collocated_nodes_field_name: str


@dataclass(frozen=True)
class Result:
# First index is the local index of the fracture mesh.
# Second is the local index of the matrix mesh.
# Third is the global index in the matrix mesh.
errors: Sequence[tuple[int, int, int]]


def __read_multiblock(vtk_input_file: str, matrix_name: str, fracture_name: str) -> Tuple[vtkUnstructuredGrid, vtkUnstructuredGrid]:
reader = vtkXMLMultiBlockDataReader()
reader.SetFileName(vtk_input_file)
reader.Update()
multi_block = reader.GetOutput()
for b in range(multi_block.GetNumberOfBlocks()):
block_name: str = multi_block.GetMetaData(b).Get(multi_block.NAME())
if block_name == matrix_name:
matrix: vtkUnstructuredGrid = multi_block.GetBlock(b)
if block_name == fracture_name:
fracture: vtkUnstructuredGrid = multi_block.GetBlock(b)
assert matrix and fracture
return matrix, fracture


def format_collocated_nodes(fracture_mesh: vtkUnstructuredGrid) -> Sequence[Iterable[int]]:
"""
Extract the collocated nodes information from the mesh and formats it in a python way.
:param fracture_mesh: The mesh of the fracture (with 2d cells).
:return: An iterable over all the buckets of collocated nodes.
"""
collocated_nodes: numpy.ndarray = vtk_to_numpy(fracture_mesh.GetPointData().GetArray("collocated_nodes"))
if len(collocated_nodes.shape) == 1:
collocated_nodes: numpy.ndarray = collocated_nodes.reshape((collocated_nodes.shape[0], 1))
return tuple(map(lambda bucket: tuple(sorted(filter(lambda i: i != -1, bucket))), collocated_nodes))
Copy link
Contributor

Choose a reason for hiding this comment

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

This is completely fine (and maybe a bit faster?), but I find list comprehension+slicing to be much more readable/pythonic than lambdas+maps+filters:

tuple([tuple(sorted(bucket[bucket != -1])) for bucket in collected_nodes])

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You are right, it's far better!



def __check_collocated_nodes_positions(matrix_points: Sequence[Tuple[float, float, float]],
fracture_points: Sequence[Tuple[float, float, float]],
g2l: Sequence[int],
collocated_nodes: Iterable[Iterable[int]]) -> Collection[Tuple[int, Iterable[int], Iterable[Tuple[float, float, float]]]]:
issues = []
for li, bucket in enumerate(collocated_nodes):
matrix_nodes = (fracture_points[li], ) + tuple(map(lambda gi: matrix_points[g2l[gi]], bucket))
m = numpy.array(matrix_nodes)
rank: int = numpy.linalg.matrix_rank(m)
if rank > 1:
issues.append((li, bucket, tuple(map(lambda gi: matrix_points[g2l[gi]], bucket))))
return issues


def my_iter(ccc):
car, cdr = ccc[0], ccc[1:]
for i in car:
if cdr:
for j in my_iter(cdr):
yield i, *j
else:
yield (i, )


def __check_neighbors(matrix: vtkUnstructuredGrid,
fracture: vtkUnstructuredGrid,
g2l: Sequence[int],
collocated_nodes: Sequence[Iterable[int]]):
fracture_nodes: Set[int] = set()
for bucket in collocated_nodes:
for gi in bucket:
fracture_nodes.add(g2l[gi])
# For each face of each cell,
# if all the points of the face are "made" of collocated nodes,
# then this is a fracture face.
fracture_faces: Set[FrozenSet[int]] = set()
for c in range(matrix.GetNumberOfCells()):
cell: vtkCell = matrix.GetCell(c)
for f in range(cell.GetNumberOfFaces()):
face: vtkCell = cell.GetFace(f)
point_ids = frozenset(vtk_iter(face.GetPointIds()))
if point_ids <= fracture_nodes:
fracture_faces.add(point_ids)
# Finding the cells
for c in tqdm(range(fracture.GetNumberOfCells()), desc="Finding neighbor cell pairs"):
cell: vtkCell = fracture.GetCell(c)
cns: Set[FrozenSet[int]] = set() # subset of collocated_nodes
point_ids = frozenset(vtk_iter(cell.GetPointIds()))
for point_id in point_ids:
bucket = collocated_nodes[point_id]
local_bucket = frozenset(map(g2l.__getitem__, bucket))
cns.add(local_bucket)
found = 0
tmp = tuple(map(tuple, cns))
for node_combinations in my_iter(tmp):
f = frozenset(node_combinations)
if f in fracture_faces:
found += 1
if found != 2:
logging.warning(f"Something went wrong since we should have found 2 fractures faces (we found {found}) for collocated nodes {cns}.")


def __check(vtk_input_file: str, options: Options) -> Result:
matrix, fracture = __read_multiblock(vtk_input_file, options.matrix_name, options.fracture_name)
matrix_points: vtkPoints = matrix.GetPoints()
fracture_points: vtkPoints = fracture.GetPoints()

collocated_nodes: Sequence[Iterable[int]] = format_collocated_nodes(fracture)
assert matrix.GetPointData().GetGlobalIds() and matrix.GetCellData().GetGlobalIds() and \
fracture.GetPointData().GetGlobalIds() and fracture.GetCellData().GetGlobalIds()

point_ids = vtk_to_numpy(matrix.GetPointData().GetGlobalIds())
g2l = numpy.ones(len(point_ids), dtype=int) * -1
for loc, glo in enumerate(point_ids):
g2l[glo] = loc
g2l.flags.writeable = False

issues = __check_collocated_nodes_positions(vtk_to_numpy(matrix.GetPoints().GetData()),
vtk_to_numpy(fracture.GetPoints().GetData()),
g2l,
collocated_nodes)
assert len(issues) == 0

__check_neighbors(matrix, fracture, g2l, collocated_nodes)

errors = []
for i, duplicates in enumerate(collocated_nodes):
for duplicate in filter(lambda i: i > -1, duplicates):
p0 = matrix_points.GetPoint(g2l[duplicate])
p1 = fracture_points.GetPoint(i)
if numpy.linalg.norm(numpy.array(p1) - numpy.array(p0)) > options.tolerance:
errors.append((i, g2l[duplicate], duplicate))
return Result(errors=errors)


def check(vtk_input_file: str, options: Options) -> Result:
try:
return __check(vtk_input_file, options)
except BaseException as e:
logging.error(e)
return Result(errors=())
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from collections import defaultdict
from dataclasses import dataclass
import logging
from typing import List

from typing import (
Collection,
Iterable,
)
import numpy

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

@dataclass(frozen=True)
class Result:
nodes_buckets: List[List[int]] # Each bucket contains the duplicated node indices.
wrong_support_elements: List[int] # Element indices with support node indices appearing more than once.
nodes_buckets: Iterable[Iterable[int]] # Each bucket contains the duplicated node indices.
wrong_support_elements: Collection[int] # Element indices with support node indices appearing more than once.


def __check(mesh, options: Options) -> Result:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ def __check(mesh, options: Options) -> Result:
mq.SetWedgeQualityMeasureToVolume()
SUPPORTED_TYPES.append(VTK_WEDGE)
else:
logging.debug("Your \"pyvtk\" version does not bring pyramid not wedge support with vtkMeshQuality. Using the fallback solution.")
logging.warning("Your \"pyvtk\" version does not bring pyramid nor wedge support with vtkMeshQuality. Using the fallback solution.")

mq.SetInputData(mesh)
mq.Update()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
from dataclasses import dataclass
import logging
from typing import List, Dict, Set
from typing import (
List,
Dict,
Set,
FrozenSet,
)

from vtkmodules.vtkCommonCore import (
vtkIdList,
Expand All @@ -22,21 +27,25 @@ class Options:
@dataclass(frozen=True)
class Result:
output: str
unchanged_cell_types: Set[int]
unchanged_cell_types: FrozenSet[int]


def __check(mesh, options: Options):
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 = []
output_mesh = mesh.NewInstance() # keeping the same instance type.
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 = output_mesh.GetCell(cell_idx).GetCellType()
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()
Expand All @@ -46,10 +55,10 @@ def __check(mesh, options: Options):
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.insert(cell_type)
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=set(unchanged_cell_types))
unchanged_cell_types=frozenset(unchanged_cell_types))


def check(vtk_input_file: str, options: Options) -> Result:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,9 @@ class Options:

@dataclass(frozen=True)
class XYZ:
x: numpy.array
y: numpy.array
z: numpy.array
x: numpy.ndarray
y: numpy.ndarray
z: numpy.ndarray


def build_rectilinear_blocks_mesh(xyzs: Iterable[XYZ]) -> vtkUnstructuredGrid:
Expand Down
Loading