Skip to content

Commit 51c74e1

Browse files
Create multiple fractures with generate_fractures (#46)
* First attempt at outputting all fracture meshes. Ok when no more than 1 intersection with 2 faults. * Corrected first commit, now works for multiple fractures at a same time. * docs update * Allows for flexibility in data mode for fractures * yapf formatting * Better docs readability * Corrected tests with new formalism * TypeAlias cannot be used in prod machines because of old python version * yapf formatting
1 parent a88a093 commit 51c74e1

File tree

4 files changed

+145
-67
lines changed

4 files changed

+145
-67
lines changed

docs/geos-mesh.rst

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -185,27 +185,26 @@ The ``generate_fractures`` module will split the mesh and generate the multi-blo
185185
.. code-block::
186186
187187
$ python src/geos/mesh/doctor/mesh_doctor.py generate_fractures --help
188-
usage: mesh_doctor.py generate_fractures [-h] --policy field, internal_surfaces [--name NAME] [--values VALUES]
189-
--output OUTPUT [--data-mode binary, ascii] --fracture-output
190-
FRACTURE_OUTPUT [--fracture-data-mode binary, ascii]
188+
usage: mesh_doctor.py generate_fractures [-h] --policy field, internal_surfaces [--name NAME] [--values VALUES] --output OUTPUT
189+
[--data-mode binary, ascii] [--fractures_output_dir FRACTURES_OUTPUT_DIR]
191190
192191
options:
193192
-h, --help show this help message and exit
194193
--policy field, internal_surfaces
195-
[string]: The criterion to define the surfaces that will be changed into fracture zones.
196-
Possible values are "field, internal_surfaces"
197-
--name NAME [string]: If the "field" policy is selected, defines which field will be considered to
198-
define the fractures. If the "internal_surfaces" policy is selected, defines the name of
199-
the attribute will be considered to identify the fractures.
200-
--values VALUES [list of comma separated integers]: If the "field" policy is selected, which changes of
201-
the field will be considered as a fracture. If the "internal_surfaces" policy is
202-
selected, list of the fracture attributes.
194+
[string]: The criterion to define the surfaces that will be changed into fracture zones. Possible values are "field, internal_surfaces"
195+
--name NAME [string]: If the "field" policy is selected, defines which field will be considered to define the fractures.
196+
If the "internal_surfaces" policy is selected, defines the name of the attribute will be considered to identify the fractures.
197+
--values VALUES [list of comma separated integers]: If the "field" policy is selected, which changes of the field will be considered as a fracture.
198+
If the "internal_surfaces" policy is selected, list of the fracture attributes.
199+
You can create multiple fractures by separating the values with ':' like shown in this example.
200+
--values 10,12:13,14,16,18:22 will create 3 fractures identified respectively with the values (10,12), (13,14,16,18) and (22).
201+
If no ':' is found, all values specified will be assumed to create only 1 single fracture.
203202
--output OUTPUT [string]: The vtk output file destination.
204203
--data-mode binary, ascii
205204
[string]: For ".vtu" output format, the data mode can be binary or ascii. Defaults to binary.
206-
--fracture-output FRACTURE_OUTPUT
207-
[string]: The vtk output file destination.
208-
--fracture-data-mode binary, ascii
205+
--fractures_output_dir FRACTURES_OUTPUT_DIR
206+
[string]: The output directory for the fractures meshes that will be generated from the mesh.
207+
--fractures_data_mode FRACTURES_DATA_MODE
209208
[string]: For ".vtu" output format, the data mode can be binary or ascii. Defaults to binary.
210209
211210
``generate_global_ids``

geos-mesh/src/geos/mesh/doctor/checks/generate_fractures.py

Lines changed: 37 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from dataclasses import dataclass
66
from enum import Enum
77
from tqdm import tqdm
8-
from typing import Collection, Iterable, Mapping, Optional, Sequence, TypeAlias
8+
from typing import Collection, Iterable, Mapping, Optional, Sequence
99
from vtk import vtkDataArray
1010
from vtkmodules.vtkCommonCore import vtkIdList, vtkPoints
1111
from vtkmodules.vtkCommonDataModel import ( vtkCell, vtkCellArray, vtkPolygon, vtkUnstructuredGrid, VTK_POLYGON,
@@ -16,11 +16,12 @@
1616
has_invalid_field )
1717
from geos.mesh.doctor.checks.vtk_polyhedron import FaceStream
1818
"""
19-
TypeAliases used in this file
19+
TypeAliases cannot be used with Python 3.9. A simple assignment like described there will be used:
20+
https://docs.python.org/3/library/typing.html#typing.TypeAlias:~:text=through%20simple%20assignment%3A-,Vector%20%3D%20list%5Bfloat%5D,-Or%20marked%20with
2021
"""
21-
IDMapping: TypeAlias = Mapping[ int, int ]
22-
CellsPointsCoords: TypeAlias = dict[ int, list[ tuple[ float ] ] ]
23-
Coordinates3D: TypeAlias = tuple[ float ]
22+
IDMapping = Mapping[ int, int ]
23+
CellsPointsCoords = dict[ int, list[ tuple[ float ] ] ]
24+
Coordinates3D = tuple[ float ]
2425

2526

2627
class FracturePolicy( Enum ):
@@ -32,9 +33,10 @@ class FracturePolicy( Enum ):
3233
class Options:
3334
policy: FracturePolicy
3435
field: str
35-
field_values: frozenset[ int ]
36-
vtk_output: VtkOutput
37-
vtk_fracture_output: VtkOutput
36+
field_values_combined: frozenset[ int ]
37+
field_values_per_fracture: list[ frozenset[ int ] ]
38+
mesh_VtkOutput: VtkOutput
39+
all_fractures_VtkOutput: list[ VtkOutput ]
3840

3941

4042
@dataclass( frozen=True )
@@ -127,9 +129,15 @@ def __build_fracture_info_from_internal_surfaces( mesh: vtkUnstructuredGrid, f:
127129
return FractureInfo( node_to_cells=node_to_cells, face_nodes=face_nodes )
128130

129131

130-
def build_fracture_info( mesh: vtkUnstructuredGrid, options: Options ) -> FractureInfo:
132+
def build_fracture_info( mesh: vtkUnstructuredGrid,
133+
options: Options,
134+
combined_fractures: bool,
135+
fracture_id: int = 0 ) -> FractureInfo:
131136
field = options.field
132-
field_values = options.field_values
137+
if combined_fractures:
138+
field_values = options.field_values_combined
139+
else:
140+
field_values = options.field_values_per_fracture[ fracture_id ]
133141
cell_data = mesh.GetCellData()
134142
if cell_data.HasArray( field ):
135143
f = vtk_to_numpy( cell_data.GetArray( field ) )
@@ -538,21 +546,30 @@ def __generate_fracture_mesh( old_mesh: vtkUnstructuredGrid, fracture_info: Frac
538546
return fracture_mesh
539547

540548

541-
def __split_mesh_on_fracture( mesh: vtkUnstructuredGrid,
542-
options: Options ) -> tuple[ vtkUnstructuredGrid, vtkUnstructuredGrid ]:
543-
fracture: FractureInfo = build_fracture_info( mesh, options )
544-
cell_to_cell: networkx.Graph = build_cell_to_cell_graph( mesh, fracture )
549+
def __split_mesh_on_fractures( mesh: vtkUnstructuredGrid,
550+
options: Options ) -> tuple[ vtkUnstructuredGrid, list[ vtkUnstructuredGrid ] ]:
551+
all_fracture_infos: list[ FractureInfo ] = list()
552+
for fracture_id in range( len( options.field_values_per_fracture ) ):
553+
fracture_info: FractureInfo = build_fracture_info( mesh, options, False, fracture_id )
554+
all_fracture_infos.append( fracture_info )
555+
combined_fractures: FractureInfo = build_fracture_info( mesh, options, True )
556+
cell_to_cell: networkx.Graph = build_cell_to_cell_graph( mesh, combined_fractures )
545557
cell_to_node_mapping: Mapping[ int, IDMapping ] = __identify_split( mesh.GetNumberOfPoints(), cell_to_cell,
546-
fracture.node_to_cells )
558+
combined_fractures.node_to_cells )
547559
output_mesh: vtkUnstructuredGrid = __perform_split( mesh, cell_to_node_mapping )
548-
fractured_mesh: vtkUnstructuredGrid = __generate_fracture_mesh( mesh, fracture, cell_to_node_mapping )
549-
return output_mesh, fractured_mesh
560+
fracture_meshes: list[ vtkUnstructuredGrid ] = list()
561+
for fracture_info_separated in all_fracture_infos:
562+
fracture_mesh: vtkUnstructuredGrid = __generate_fracture_mesh( mesh, fracture_info_separated,
563+
cell_to_node_mapping )
564+
fracture_meshes.append( fracture_mesh )
565+
return ( output_mesh, fracture_meshes )
550566

551567

552568
def __check( mesh, options: Options ) -> Result:
553-
output_mesh, fracture_mesh = __split_mesh_on_fracture( mesh, options )
554-
write_mesh( output_mesh, options.vtk_output )
555-
write_mesh( fracture_mesh, options.vtk_fracture_output )
569+
output_mesh, fracture_meshes = __split_mesh_on_fractures( mesh, options )
570+
write_mesh( output_mesh, options.mesh_VtkOutput )
571+
for i, fracture_mesh in enumerate( fracture_meshes ):
572+
write_mesh( fracture_mesh, options.all_fractures_VtkOutput[ i ] )
556573
# TODO provide statistics about what was actually performed (size of the fracture, number of split nodes...).
557574
return Result( info="OK" )
558575

Lines changed: 73 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import logging
2-
2+
import os
33
from geos.mesh.doctor.checks.generate_fractures import Options, Result, FracturePolicy
4-
4+
from geos.mesh.doctor.checks.vtk_utils import VtkOutput
55
from . import vtk_output_parsing, GENERATE_FRACTURES
66

77
__POLICY = "policy"
@@ -12,7 +12,8 @@
1212
__FIELD_NAME = "name"
1313
__FIELD_VALUES = "values"
1414

15-
__FRACTURE_PREFIX = "fracture"
15+
__FRACTURES_OUTPUT_DIR = "fractures_output_dir"
16+
__FRACTURES_DATA_MODE = "fractures_data_mode"
1617

1718

1819
def convert_to_fracture_policy( s: str ) -> FracturePolicy:
@@ -30,8 +31,7 @@ def convert_to_fracture_policy( s: str ) -> FracturePolicy:
3031

3132

3233
def fill_subparser( subparsers ) -> None:
33-
p = subparsers.add_parser( GENERATE_FRACTURES,
34-
help="Splits the mesh to generate the faults and fractures. [EXPERIMENTAL]" )
34+
p = subparsers.add_parser( GENERATE_FRACTURES, help="Splits the mesh to generate the faults and fractures." )
3535
p.add_argument( '--' + __POLICY,
3636
type=convert_to_fracture_policy,
3737
metavar=", ".join( __POLICIES ),
@@ -43,30 +43,86 @@ def fill_subparser( subparsers ) -> None:
4343
type=str,
4444
help=
4545
f"[string]: If the \"{__FIELD_POLICY}\" {__POLICY} is selected, defines which field will be considered to define the fractures. "
46-
f"If the \"{__INTERNAL_SURFACES_POLICY}\" {__POLICY} is selected, defines the name of the attribute will be considered to identify the fractures. "
46+
f"If the \"{__INTERNAL_SURFACES_POLICY}\" {__POLICY} is selected, defines the name of the attribute will be considered to identify the fractures."
4747
)
4848
p.add_argument(
4949
'--' + __FIELD_VALUES,
5050
type=str,
5151
help=
52-
f"[list of comma separated integers]: If the \"{__FIELD_POLICY}\" {__POLICY} is selected, which changes of the field will be considered as a fracture. If the \"{__INTERNAL_SURFACES_POLICY}\" {__POLICY} is selected, list of the fracture attributes."
53-
)
52+
f"[list of comma separated integers]: If the \"{__FIELD_POLICY}\" {__POLICY} is selected, which changes of the field will be considered "
53+
f"as a fracture. If the \"{__INTERNAL_SURFACES_POLICY}\" {__POLICY} is selected, list of the fracture attributes. "
54+
f"You can create multiple fractures by separating the values with ':' like shown in this example. "
55+
f"--{__FIELD_VALUES} 10,12:13,14,16,18:22 will create 3 fractures identified respectively with the values (10,12), (13,14,16,18) and (22). "
56+
f"If no ':' is found, all values specified will be assumed to create only 1 single fracture." )
5457
vtk_output_parsing.fill_vtk_output_subparser( p )
55-
vtk_output_parsing.fill_vtk_output_subparser( p, prefix=__FRACTURE_PREFIX )
58+
p.add_argument(
59+
'--' + __FRACTURES_OUTPUT_DIR,
60+
type=str,
61+
help=f"[string]: The output directory for the fractures meshes that will be generated from the mesh." )
62+
p.add_argument(
63+
'--' + __FRACTURES_DATA_MODE,
64+
type=str,
65+
help=f'[string]: For ".vtu" output format, the data mode can be binary or ascii. Defaults to binary.' )
5666

5767

5868
def convert( parsed_options ) -> Options:
59-
policy = parsed_options[ __POLICY ]
60-
field = parsed_options[ __FIELD_NAME ]
61-
field_values = frozenset( map( int, parsed_options[ __FIELD_VALUES ].split( "," ) ) )
62-
vtk_output = vtk_output_parsing.convert( parsed_options )
63-
vtk_fracture_output = vtk_output_parsing.convert( parsed_options, prefix=__FRACTURE_PREFIX )
69+
policy: str = parsed_options[ __POLICY ]
70+
field: str = parsed_options[ __FIELD_NAME ]
71+
all_values: str = parsed_options[ __FIELD_VALUES ]
72+
if not are_values_parsable( all_values ):
73+
raise ValueError(
74+
f"When entering --{__FIELD_VALUES}, respect this given format example:\n--{__FIELD_VALUES} " +
75+
"10,12:13,14,16,18:22 to create 3 fractures identified with respectively the values (10,12), (13,14,16,18) and (22)."
76+
)
77+
all_values_no_separator: str = all_values.replace( ":", "," )
78+
field_values_combined: frozenset[ int ] = frozenset( map( int, all_values_no_separator.split( "," ) ) )
79+
mesh_vtk_output = vtk_output_parsing.convert( parsed_options )
80+
# create the different fractures
81+
per_fracture: list[ str ] = all_values.split( ":" )
82+
field_values_per_fracture: list[ frozenset[ int ] ] = [
83+
frozenset( map( int, fracture.split( "," ) ) ) for fracture in per_fracture
84+
]
85+
fracture_names: list[ str ] = [ "fracture_" + frac.replace( ",", "_" ) + ".vtu" for frac in per_fracture ]
86+
fractures_output_dir: str = parsed_options[ __FRACTURES_OUTPUT_DIR ]
87+
fractures_data_mode: str = parsed_options[ __FRACTURES_DATA_MODE ]
88+
all_fractures_VtkOutput: list[ VtkOutput ] = build_all_fractures_VtkOutput( fractures_output_dir,
89+
fractures_data_mode, mesh_vtk_output,
90+
fracture_names )
6491
return Options( policy=policy,
6592
field=field,
66-
field_values=field_values,
67-
vtk_output=vtk_output,
68-
vtk_fracture_output=vtk_fracture_output )
93+
field_values_combined=field_values_combined,
94+
field_values_per_fracture=field_values_per_fracture,
95+
mesh_VtkOutput=mesh_vtk_output,
96+
all_fractures_VtkOutput=all_fractures_VtkOutput )
6997

7098

7199
def display_results( options: Options, result: Result ):
72100
pass
101+
102+
103+
def are_values_parsable( values: str ) -> bool:
104+
if not all( character.isdigit() or character in { ':', ',' } for character in values ):
105+
return False
106+
if values.startswith( ":" ) or values.startswith( "," ):
107+
return False
108+
if values.endswith( ":" ) or values.endswith( "," ):
109+
return False
110+
return True
111+
112+
113+
def build_all_fractures_VtkOutput( fracture_output_dir: str, fractures_data_mode: str, mesh_vtk_output: VtkOutput,
114+
fracture_names: list[ str ] ) -> list[ VtkOutput ]:
115+
if not os.path.exists( fracture_output_dir ):
116+
raise ValueError( f"The --{__FRACTURES_OUTPUT_DIR} given directory does not exist." )
117+
118+
if not os.access( fracture_output_dir, os.W_OK ):
119+
raise ValueError( f"The --{__FRACTURES_OUTPUT_DIR} given directory is not writable." )
120+
121+
output_name = os.path.basename( mesh_vtk_output.output )
122+
splitted_name_without_expension: list[ str ] = output_name.split( "." )[ :-1 ]
123+
name_without_extension: str = '_'.join( splitted_name_without_expension ) + "_"
124+
all_fractures_VtkOuput: list[ VtkOutput ] = list()
125+
for fracture_name in fracture_names:
126+
fracture_path = os.path.join( fracture_output_dir, name_without_extension + fracture_name )
127+
all_fractures_VtkOuput.append( VtkOutput( fracture_path, fractures_data_mode ) )
128+
return all_fractures_VtkOuput

0 commit comments

Comments
 (0)