1
- # Copyright (C) 2022 Jørgen S. Dokken
1
+ # Copyright (C) 2022-2025 Jørgen S. Dokken, Henrik N. T. Finsberg and Paul T. Kühner
2
2
#
3
3
# This file is part of DOLFINx (https://www.fenicsproject.org)
4
4
#
@@ -78,9 +78,9 @@ class MeshData(typing.NamedTuple):
78
78
Args:
79
79
mesh: Mesh.
80
80
cell_tags: MeshTags for cells.
81
- facet_tags: MeshTags for facets.
82
- edge_tags : MeshTags for edges .
83
- vertex_tags : MeshTags for vertices .
81
+ facet_tags: MeshTags for facets (codim 1) .
82
+ ridge_tags : MeshTags for ridges (codim 2) .
83
+ peak_tags : MeshTags for peaks (codim 3) .
84
84
physical_groups: Physical groups in the mesh, where the key
85
85
is the physical name and the value is a tuple with the
86
86
dimension and tag.
@@ -89,8 +89,8 @@ class MeshData(typing.NamedTuple):
89
89
mesh : Mesh
90
90
cell_tags : typing .Optional [MeshTags ]
91
91
facet_tags : typing .Optional [MeshTags ]
92
- edge_tags : typing .Optional [MeshTags ]
93
- vertex_tags : typing .Optional [MeshTags ]
92
+ ridge_tags : typing .Optional [MeshTags ]
93
+ peak_tags : typing .Optional [MeshTags ]
94
94
physical_groups : dict [str , tuple [int , int ]]
95
95
96
96
@@ -184,7 +184,13 @@ def extract_topology_and_markers(
184
184
# one cell-type,
185
185
# i.e. facets of prisms and pyramid meshes are not supported
186
186
(entity_types , entity_tags , entity_topologies ) = model .mesh .getElements (dim , tag = entity )
187
- assert len (entity_types ) == 1
187
+
188
+ if len (entity_types ) > 1 :
189
+ raise RuntimeError (
190
+ f"Unsupported mesh with multiple cell types { entity_types } for entity { entity } "
191
+ )
192
+ elif len (entity_types ) == 0 :
193
+ continue
188
194
189
195
# Determine number of local nodes per element to create the
190
196
# topology of the elements
@@ -277,8 +283,10 @@ def model_to_mesh(
277
283
distribution of cells across MPI ranks.
278
284
279
285
Returns:
280
- MeshData with mesh, cell tags, facet tags, edge tags,
281
- vertex tags and physical groups.
286
+ MeshData with mesh and tags of corresponding entities by codimension.
287
+ Codimension 0 is the cell tags, codimension 1 is the facet tags,
288
+ codimension 2 is the ridge tags and codimension 3 is the peak tags
289
+ as well as a lookup table from the physical groups by name to integer.
282
290
283
291
Note:
284
292
For performance, this function should only be called once for
@@ -292,158 +300,107 @@ def model_to_mesh(
292
300
x = extract_geometry (model )
293
301
topologies , physical_groups = extract_topology_and_markers (model )
294
302
295
- # Extract Gmsh cell id, dimension of cell and number of nodes to
296
- # cell for each
297
- num_cell_types = len (topologies .keys ())
298
- cell_information = dict ()
299
- cell_dimensions = np .zeros (num_cell_types , dtype = np .int32 )
303
+ # Extract Gmsh entity (cell) id, topological dimension and number of nodes
304
+ # which is used to create an appropriate coordinate element, and seperate
305
+ # higher topological entities from lower topological entities (e.g. facets,
306
+ # ridges and peaks).
307
+ num_unique_entities = len (topologies .keys ())
308
+ element_ids = np .zeros (num_unique_entities , dtype = np .int32 )
309
+ entity_tdim = np .zeros (num_unique_entities , dtype = np .int32 )
310
+ num_nodes_per_element = np .zeros (num_unique_entities , dtype = np .int32 )
300
311
for i , element in enumerate (topologies .keys ()):
301
312
_ , dim , _ , num_nodes , _ , _ = model .mesh .getElementProperties (element )
302
- cell_information [i ] = {"id" : element , "dim" : dim , "num_nodes" : num_nodes }
303
- cell_dimensions [i ] = dim
304
-
305
- # Sort elements by ascending dimension
306
- perm_sort = np .argsort (cell_dimensions )
307
-
308
- # Broadcast cell type data and geometric dimension
309
- cell_id = cell_information [perm_sort [- 1 ]]["id" ]
310
- tdim = cell_information [perm_sort [- 1 ]]["dim" ]
311
- num_nodes = cell_information [perm_sort [- 1 ]]["num_nodes" ]
312
- cell_id , num_nodes = comm .bcast ([cell_id , num_nodes ], root = rank )
313
-
314
- # Check for facet, edge and vertex data and broadcast relevant info if True
315
- has_facet_data = (tdim - 1 ) in cell_dimensions
316
- has_edge_data = (tdim - 2 ) in cell_dimensions
317
- has_vertex_data = (tdim - 3 ) in cell_dimensions
318
-
319
- has_facet_data = comm .bcast (has_facet_data , root = rank )
320
- if has_facet_data :
321
- num_facet_nodes = comm .bcast (cell_information [perm_sort [- 2 ]]["num_nodes" ], root = rank )
322
- gmsh_facet_id = cell_information [perm_sort [- 2 ]]["id" ]
323
- marked_facets = np .asarray (topologies [gmsh_facet_id ]["topology" ], dtype = np .int64 )
324
- facet_values = np .asarray (topologies [gmsh_facet_id ]["cell_data" ], dtype = np .int32 )
325
-
326
- has_edge_data = comm .bcast (has_edge_data , root = rank )
327
- if has_edge_data :
328
- num_edge_nodes = comm .bcast (cell_information [perm_sort [- 3 ]]["num_nodes" ], root = rank )
329
- gmsh_edge_id = cell_information [perm_sort [- 3 ]]["id" ]
330
- marked_edges = np .asarray (topologies [gmsh_edge_id ]["topology" ], dtype = np .int64 )
331
- edge_values = np .asarray (topologies [gmsh_edge_id ]["cell_data" ], dtype = np .int32 )
332
-
333
- has_vertex_data = comm .bcast (has_vertex_data , root = rank )
334
- if has_vertex_data :
335
- num_vertex_nodes = comm .bcast (cell_information [perm_sort [- 4 ]]["num_nodes" ], root = rank )
336
- gmsh_vertex_id = cell_information [perm_sort [- 4 ]]["id" ]
337
- marked_vertices = np .asarray (topologies [gmsh_vertex_id ]["topology" ], dtype = np .int64 )
338
- vertex_values = np .asarray (topologies [gmsh_vertex_id ]["cell_data" ], dtype = np .int32 )
339
-
340
- cells = np .asarray (topologies [cell_id ]["topology" ], dtype = np .int64 )
341
- cell_values = np .asarray (topologies [cell_id ]["cell_data" ], dtype = np .int32 )
342
- physical_groups = comm .bcast (physical_groups , root = rank )
343
- else :
344
- cell_id , num_nodes = comm .bcast ([None , None ], root = rank )
345
- cells , x = np .empty ([0 , num_nodes ], dtype = np .int32 ), np .empty ([0 , gdim ], dtype = dtype )
346
- cell_values = np .empty ((0 ,), dtype = np .int32 )
347
-
348
- has_facet_data = comm .bcast (None , root = rank )
349
- if has_facet_data :
350
- num_facet_nodes = comm .bcast (None , root = rank )
351
- marked_facets = np .empty ((0 , num_facet_nodes ), dtype = np .int32 )
352
- facet_values = np .empty ((0 ,), dtype = np .int32 )
353
-
354
- has_edge_data = comm .bcast (None , root = rank )
355
- if has_edge_data :
356
- num_edge_nodes = comm .bcast (None , root = rank )
357
- marked_edges = np .empty ((0 , num_edge_nodes ), dtype = np .int32 )
358
- edge_values = np .empty ((0 ,), dtype = np .int32 )
359
-
360
- has_vertex_data = comm .bcast (None , root = rank )
361
- if has_vertex_data :
362
- num_vertex_nodes = comm .bcast (None , root = rank )
363
- marked_vertices = np .empty ((0 , num_vertex_nodes ), dtype = np .int32 )
364
- vertex_values = np .empty ((0 ,), dtype = np .int32 )
365
-
366
- physical_groups = comm .bcast (None , root = rank )
313
+ element_ids [i ] = element
314
+ entity_tdim [i ] = dim
315
+ num_nodes_per_element [i ] = num_nodes
367
316
368
- # Create distributed mesh
369
- ufl_domain = ufl_mesh (cell_id , gdim , dtype = dtype )
317
+ # Broadcast information to all other ranks
318
+ entity_tdim , element_ids , num_nodes_per_element = comm .bcast (
319
+ (entity_tdim , element_ids , num_nodes_per_element ), root = rank
320
+ )
321
+ else :
322
+ entity_tdim , element_ids , num_nodes_per_element = comm .bcast ((None , None , None ), root = rank )
323
+
324
+ # Sort elements by descending dimension
325
+ assert len (np .unique (entity_tdim )) == len (entity_tdim )
326
+ perm_sort = np .argsort (entity_tdim )[::- 1 ]
327
+
328
+ # Extract position of the highest topological entity and its topological dimension
329
+ cell_position = perm_sort [0 ]
330
+ tdim = int (entity_tdim [cell_position ])
331
+
332
+ # Extract entity -> node connectivity for all cells and sub-entities marked in the GMSH model
333
+ meshtags : dict [int , tuple [npt .NDArray [np .int32 ], npt .NDArray [np .int32 ]]] = {}
334
+ for position in perm_sort :
335
+ codim = tdim - entity_tdim [position ]
336
+ if comm .rank == rank :
337
+ gmsh_entity_id = element_ids [position ]
338
+ marked_entities = np .asarray (topologies [gmsh_entity_id ]["topology" ], dtype = np .int64 )
339
+ entity_values = np .asarray (topologies [gmsh_entity_id ]["cell_data" ], dtype = np .int32 )
340
+ meshtags [codim ] = (marked_entities , entity_values )
341
+ else :
342
+ # Any other process than input rank does not have any entities
343
+ marked_entities = np .empty ((0 , num_nodes_per_element [position ]), dtype = np .int32 )
344
+ entity_values = np .empty ((0 ,), dtype = np .int32 )
345
+ meshtags [codim ] = (marked_entities , entity_values )
346
+
347
+ # Create a UFL Mesh object for the GMSH element with the highest topoligcal dimension
348
+ ufl_domain = ufl_mesh (element_ids [cell_position ], gdim , dtype = dtype )
349
+
350
+ # Get cell->node connectivity and permute to FEniCS ordering
351
+ num_nodes = num_nodes_per_element [cell_position ]
370
352
gmsh_cell_perm = cell_perm_array (_cpp .mesh .to_type (str (ufl_domain .ufl_cell ())), num_nodes )
371
- cells = cells [:, gmsh_cell_perm ].copy ()
372
- mesh = create_mesh (comm , cells , x [:, :gdim ].astype (dtype , copy = False ), ufl_domain , partitioner )
353
+ cell_connectivity = meshtags [0 ][0 ][:, gmsh_cell_perm ].copy ()
373
354
374
- # Create MeshTags for cells
375
- local_entities , local_values = distribute_entity_data (
376
- mesh , mesh .topology .dim , cells , cell_values
355
+ # Create a distributed mesh, where mesh nodes are only destributed from the input rank
356
+ if comm .rank != rank :
357
+ x = np .empty ([0 , gdim ], dtype = dtype ) # No nodes on other than root rank
358
+ mesh = create_mesh (
359
+ comm , cell_connectivity , x [:, :gdim ].astype (dtype , copy = False ), ufl_domain , partitioner
377
360
)
378
- mesh .topology .create_connectivity (mesh .topology .dim , 0 )
379
- adj = adjacencylist (local_entities )
380
- ct = meshtags_from_entities (
381
- mesh , mesh .topology .dim , adj , local_values .astype (np .int32 , copy = False )
361
+ assert tdim == mesh .topology .dim , (
362
+ f"{ mesh .topology .dim = } does not match Gmsh model dimension { tdim } "
382
363
)
383
- ct .name = "Cell tags"
384
364
385
- # Create MeshTags for facets
365
+ # Create MeshTags for all sub entities
386
366
topology = mesh .topology
387
- tdim = topology .dim
388
- if has_facet_data :
389
- # Permute facets from MSH to DOLFINx ordering
390
- # FIXME: This does not work for prism meshes
391
- if topology .cell_type == CellType .prism or topology .cell_type == CellType .pyramid :
392
- raise RuntimeError (f"Unsupported cell type { topology .cell_type } " )
393
-
394
- facet_type = _cpp .mesh .cell_entity_type (
395
- _cpp .mesh .to_type (str (ufl_domain .ufl_cell ())), tdim - 1 , 0
396
- )
397
- gmsh_facet_perm = cell_perm_array (facet_type , num_facet_nodes )
398
- marked_facets = marked_facets [:, gmsh_facet_perm ]
399
-
400
- local_entities , local_values = distribute_entity_data (
401
- mesh , tdim - 1 , marked_facets , facet_values
402
- )
403
- mesh .topology .create_connectivity (topology .dim - 1 , tdim )
404
- adj = adjacencylist (local_entities )
405
- ft = meshtags_from_entities (mesh , tdim - 1 , adj , local_values .astype (np .int32 , copy = False ))
406
- ft .name = "Facet tags"
407
- else :
408
- ft = None
409
-
410
- if has_edge_data :
411
- # Permute edges from MSH to DOLFINx ordering
412
- edge_type = _cpp .mesh .cell_entity_type (
413
- _cpp .mesh .to_type (str (ufl_domain .ufl_cell ())), tdim - 2 , 0
414
- )
415
- gmsh_edge_perm = cell_perm_array (edge_type , num_edge_nodes )
416
- marked_edges = marked_edges [:, gmsh_edge_perm ]
417
-
367
+ codim_to_name = {0 : "cell" , 1 : "facet" , 2 : "ridge" , 3 : "peak" }
368
+ dolfinx_meshtags : dict [str , typing .Optional [MeshTags ]] = {}
369
+ for codim in [0 , 1 , 2 , 3 ]:
370
+ key = f"{ codim_to_name [codim ]} _tags"
371
+ if (
372
+ codim == 1 and topology .cell_type == CellType .prism
373
+ ) or topology .cell_type == CellType .pyramid :
374
+ raise RuntimeError (f"Unsupported facet tag for type { topology .cell_type } " )
375
+
376
+ meshtag_data = meshtags .get (codim , None )
377
+ if meshtag_data is None :
378
+ dolfinx_meshtags [key ] = None
379
+ continue
380
+
381
+ # Distribute entity data [[e0_v0, e0_v1, ...], [e1_v0, e1_v1, ...], ...]
382
+ # which is made in global input indices to local indices on the
383
+ # owning process
384
+ (marked_entities , entity_values ) = meshtag_data
418
385
local_entities , local_values = distribute_entity_data (
419
- mesh , tdim - 2 , marked_edges , edge_values
386
+ mesh , tdim - codim , marked_entities , entity_values
420
387
)
421
- mesh .topology .create_connectivity (topology .dim - 2 , tdim )
388
+ # Create MeshTags object from the local entities
389
+ mesh .topology .create_connectivity (tdim - codim , tdim )
422
390
adj = adjacencylist (local_entities )
423
- et = meshtags_from_entities (mesh , tdim - 2 , adj , local_values .astype (np .int32 , copy = False ))
424
- et .name = "Edge tags"
425
- else :
426
- et = None
427
-
428
- if has_vertex_data :
429
- # Permute vertices from MSH to DOLFINx ordering
430
- vertex_type = _cpp .mesh .cell_entity_type (
431
- _cpp .mesh .to_type (str (ufl_domain .ufl_cell ())), tdim - 3 , 0
391
+ et = meshtags_from_entities (
392
+ mesh , tdim - codim , adj , local_values .astype (np .int32 , copy = False )
432
393
)
433
- gmsh_vertex_perm = cell_perm_array ( vertex_type , num_vertex_nodes )
434
- marked_vertices = marked_vertices [:, gmsh_vertex_perm ]
394
+ et . name = key
395
+ dolfinx_meshtags [ key ] = et
435
396
436
- local_entities , local_values = distribute_entity_data (
437
- mesh , tdim - 3 , marked_vertices , vertex_values
438
- )
439
- mesh .topology .create_connectivity (topology .dim - 3 , tdim )
440
- adj = adjacencylist (local_entities )
441
- vt = meshtags_from_entities (mesh , tdim - 3 , adj , local_values .astype (np .int32 , copy = False ))
442
- vt .name = "Vertex tags"
397
+ # Broadcast physical groups (string to integer mapping) to all ranks
398
+ if comm .rank == rank :
399
+ physical_groups = comm .bcast (physical_groups , root = rank )
443
400
else :
444
- vt = None
401
+ physical_groups = comm . bcast ( None , root = rank )
445
402
446
- return MeshData (mesh , ct , ft , et , vt , physical_groups )
403
+ return MeshData (mesh , physical_groups = physical_groups , ** dolfinx_meshtags )
447
404
448
405
449
406
def read_from_msh (
0 commit comments