cardiac-geometriesx 0.1.2__py3-none-any.whl → 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of cardiac-geometriesx might be problematic. Click here for more details.

@@ -40,8 +40,8 @@ def biv_ellipsoid(
40
40
  b_epi_rv: float = 2.5,
41
41
  c_epi_rv: float = 2.0,
42
42
  create_fibers: bool = False,
43
- fiber_angle_endo: float = -60,
44
- fiber_angle_epi: float = +60,
43
+ fiber_angle_endo: float = 60,
44
+ fiber_angle_epi: float = -60,
45
45
  fiber_space: str = "P_1",
46
46
  verbose: bool = False,
47
47
  ) -> Geometry:
@@ -92,9 +92,9 @@ def biv_ellipsoid(
92
92
  create_fibers : bool, optional
93
93
  If True create analytic fibers, by default False
94
94
  fiber_angle_endo : float, optional
95
- Angle for the endocardium, by default -60
95
+ Angle for the endocardium, by default 60
96
96
  fiber_angle_epi : float, optional
97
- Angle for the epicardium, by default +60
97
+ Angle for the epicardium, by default -60
98
98
  fiber_space : str, optional
99
99
  Function space for fibers of the form family_degree, by default "P_1"
100
100
  verbose : bool, optional
@@ -102,7 +102,7 @@ def biv_ellipsoid(
102
102
 
103
103
  Returns
104
104
  -------
105
- Geometry
105
+ cardiac_geometries.geometry.Geometry
106
106
  A Geometry with the mesh, markers, markers functions and fibers.
107
107
 
108
108
  """
@@ -178,7 +178,30 @@ def biv_ellipsoid(
178
178
  json.dump(geometry.markers, f, default=utils.json_serial)
179
179
  comm.barrier()
180
180
  if create_fibers:
181
- raise NotImplementedError("Fibers not implemented yet for biv ellipsoid.")
181
+ try:
182
+ import ldrb
183
+ except ImportError:
184
+ msg = (
185
+ "To create fibers you need to install the ldrb package "
186
+ "which you can install with pip install fenicsx-ldrb"
187
+ )
188
+ raise ImportError(msg)
189
+
190
+ system = ldrb.dolfinx_ldrb(
191
+ mesh=geometry.mesh,
192
+ ffun=geometry.ffun,
193
+ markers=geometry.markers,
194
+ alpha_endo_lv=fiber_angle_endo,
195
+ alpha_epi_lv=fiber_angle_epi,
196
+ beta_endo_lv=0,
197
+ beta_epi_lv=0,
198
+ fiber_space=fiber_space,
199
+ )
200
+ from .fibers.utils import save_microstructure
201
+
202
+ save_microstructure(
203
+ mesh=geometry.mesh, functions=(system.f0, system.s0, system.n0), outdir=outdir
204
+ )
182
205
  # from .fibers._biv_ellipsoid import create_biv_fibers
183
206
 
184
207
  # create_biv_fibers(
@@ -222,8 +245,8 @@ def biv_ellipsoid_torso(
222
245
  b_epi_rv: float = 2.5,
223
246
  c_epi_rv: float = 2.0,
224
247
  create_fibers: bool = False,
225
- fiber_angle_endo: float = -60,
226
- fiber_angle_epi: float = +60,
248
+ fiber_angle_endo: float = 60,
249
+ fiber_angle_epi: float = -60,
227
250
  fiber_space: str = "P_1",
228
251
  verbose: bool = False,
229
252
  ) -> Geometry:
@@ -286,9 +309,9 @@ def biv_ellipsoid_torso(
286
309
  create_fibers : bool, optional
287
310
  If True create analytic fibers, by default False
288
311
  fiber_angle_endo : float, optional
289
- Angle for the endocardium, by default -60
312
+ Angle for the endocardium, by default 60
290
313
  fiber_angle_epi : float, optional
291
- Angle for the epicardium, by default +60
314
+ Angle for the epicardium, by default -60
292
315
  fiber_space : str, optional
293
316
  Function space for fibers of the form family_degree, by default "P_1"
294
317
  verbose : bool, optional
@@ -296,7 +319,7 @@ def biv_ellipsoid_torso(
296
319
 
297
320
  Returns
298
321
  -------
299
- Geometry
322
+ cardiac_geometries.geometry.Geometry
300
323
  A Geometry with the mesh, markers, markers functions and fibers.
301
324
 
302
325
  """
@@ -417,8 +440,8 @@ def lv_ellipsoid(
417
440
  mu_apex_epi: float = -math.pi,
418
441
  mu_base_epi: float = -math.acos(5 / 20),
419
442
  create_fibers: bool = False,
420
- fiber_angle_endo: float = -60,
421
- fiber_angle_epi: float = +60,
443
+ fiber_angle_endo: float = 60,
444
+ fiber_angle_epi: float = -60,
422
445
  fiber_space: str = "P_1",
423
446
  aha: bool = True,
424
447
  verbose: bool = False,
@@ -450,9 +473,9 @@ def lv_ellipsoid(
450
473
  create_fibers : bool, optional
451
474
  If True create analytic fibers, by default False
452
475
  fiber_angle_endo : float, optional
453
- Angle for the endocardium, by default -60
476
+ Angle for the endocardium, by default 60
454
477
  fiber_angle_epi : float, optional
455
- Angle for the epicardium, by default +60
478
+ Angle for the epicardium, by default -60
456
479
  fiber_space : str, optional
457
480
  Function space for fibers of the form family_degree, by default "P_1"
458
481
  aha : bool, optional
@@ -462,7 +485,7 @@ def lv_ellipsoid(
462
485
 
463
486
  Returns
464
487
  -------
465
- Geometry
488
+ cardiac_geometries.geometry.Geometry
466
489
  A Geometry with the mesh, markers, markers functions and fibers.
467
490
 
468
491
  """
@@ -569,8 +592,8 @@ def slab(
569
592
  lz: float = 3.0,
570
593
  dx: float = 1.0,
571
594
  create_fibers: bool = True,
572
- fiber_angle_endo: float = -60,
573
- fiber_angle_epi: float = +60,
595
+ fiber_angle_endo: float = 60,
596
+ fiber_angle_epi: float = -60,
574
597
  fiber_space: str = "P_1",
575
598
  verbose: bool = False,
576
599
  ) -> Geometry:
@@ -591,9 +614,9 @@ def slab(
591
614
  create_fibers : bool, optional
592
615
  If True create analytic fibers, by default True
593
616
  fiber_angle_endo : float, optional
594
- Angle for the endocardium, by default -60
617
+ Angle for the endocardium, by default 60
595
618
  fiber_angle_epi : float, optional
596
- Angle for the epicardium, by default +60
619
+ Angle for the epicardium, by default -60
597
620
  fiber_space : str, optional
598
621
  Function space for fibers of the form family_degree, by default "P_1"
599
622
  verbose : bool, optional
@@ -601,7 +624,7 @@ def slab(
601
624
 
602
625
  Returns
603
626
  -------
604
- Geometry
627
+ cardiac_geometries.geometry.Geometry
605
628
  A Geometry with the mesh, markers, markers functions and fibers.
606
629
 
607
630
  """
@@ -701,7 +724,7 @@ def slab_in_bath(
701
724
 
702
725
  Returns
703
726
  -------
704
- Geometry
727
+ cardiac_geometries.geometry.Geometry
705
728
  A Geometry with the mesh, markers, markers functions and fibers.
706
729
 
707
730
  """
@@ -1,4 +1,5 @@
1
1
  import tempfile
2
+ import typing
2
3
  from enum import Enum
3
4
  from pathlib import Path
4
5
  from typing import Iterable, NamedTuple
@@ -12,13 +13,307 @@ from structlog import get_logger
12
13
 
13
14
  logger = get_logger()
14
15
 
16
+ quads = ("Quadrature", "Q", "Quad", "quadrature", "q", "quad")
17
+ QUADNR = 42
15
18
 
16
- def element2array(el: basix.finite_element.FiniteElement) -> np.ndarray:
17
- return np.array(
18
- [int(el.family), int(el.cell_type), int(el.degree), int(el.discontinuous)],
19
- dtype=np.uint8,
19
+
20
+ class GMshModel(NamedTuple):
21
+ mesh: dolfinx.mesh.Mesh
22
+ cell_tags: dolfinx.mesh.MeshTags
23
+ facet_tags: dolfinx.mesh.MeshTags
24
+ edge_tags: dolfinx.mesh.MeshTags
25
+ vertex_tags: dolfinx.mesh.MeshTags
26
+
27
+
28
+ # copied from https://github.com/FEniCS/dolfinx/blob/main/python/dolfinx/io/gmshio.py
29
+ def model_to_mesh(
30
+ model,
31
+ comm: MPI.Comm,
32
+ rank: int,
33
+ gdim: int = 3,
34
+ partitioner: typing.Optional[
35
+ typing.Callable[
36
+ [MPI.Comm, int, int, dolfinx.cpp.graph.AdjacencyList_int32],
37
+ dolfinx.cpp.graph.AdjacencyList_int32,
38
+ ]
39
+ ] = None,
40
+ dtype=dolfinx.default_real_type,
41
+ ) -> GMshModel:
42
+ """Create a Mesh from a Gmsh model.
43
+
44
+ Creates a :class:`dolfinx.mesh.Mesh` from the physical entities of
45
+ the highest topological dimension in the Gmsh model. In parallel,
46
+ the gmsh model is processed on one MPI rank, and the
47
+ :class:`dolfinx.mesh.Mesh` is distributed across ranks.
48
+
49
+ Args:
50
+ model: Gmsh model.
51
+ comm: MPI communicator to use for mesh creation.
52
+ rank: MPI rank that the Gmsh model is initialized on.
53
+ gdim: Geometrical dimension of the mesh.
54
+ partitioner: Function that computes the parallel
55
+ distribution of cells across MPI ranks.
56
+
57
+ Returns:
58
+ A tuple containing the :class:`dolfinx.mesh.Mesh` and
59
+ :class:`dolfinx.mesh.MeshTags` for cells, facets, edges and
60
+ vertices.
61
+
62
+
63
+ Note:
64
+ For performance, this function should only be called once for
65
+ large problems. For re-use, it is recommended to save the mesh
66
+ and corresponding tags using :class:`dolfinxio.XDMFFile` after
67
+ creation for efficient access.
68
+
69
+ """
70
+ if comm.rank == rank:
71
+ assert model is not None, "Gmsh model is None on rank responsible for mesh creation."
72
+ # Get mesh geometry and mesh topology for each element
73
+ x = dolfinx.io.gmshio.extract_geometry(model)
74
+ topologies = dolfinx.io.gmshio.extract_topology_and_markers(model)
75
+
76
+ # Extract Gmsh cell id, dimension of cell and number of nodes to
77
+ # cell for each
78
+ num_cell_types = len(topologies.keys())
79
+ cell_information = dict()
80
+ cell_dimensions = np.zeros(num_cell_types, dtype=np.int32)
81
+ for i, element in enumerate(topologies.keys()):
82
+ _, dim, _, num_nodes, _, _ = model.mesh.getElementProperties(element)
83
+ cell_information[i] = {"id": element, "dim": dim, "num_nodes": num_nodes}
84
+ cell_dimensions[i] = dim
85
+
86
+ # Sort elements by ascending dimension
87
+ perm_sort = np.argsort(cell_dimensions)
88
+
89
+ # Broadcast cell type data and geometric dimension
90
+ cell_id = cell_information[perm_sort[-1]]["id"]
91
+ tdim = cell_information[perm_sort[-1]]["dim"]
92
+ num_nodes = cell_information[perm_sort[-1]]["num_nodes"]
93
+ cell_id, num_nodes = comm.bcast([cell_id, num_nodes], root=rank)
94
+
95
+ # Check for facet data and broadcast relevant info if True
96
+ has_facet_data = False
97
+ if tdim - 1 in cell_dimensions:
98
+ has_facet_data = True
99
+ has_edge_data = False
100
+ if tdim - 2 in cell_dimensions:
101
+ has_edge_data = True
102
+ has_vertex_data = False
103
+ if tdim - 3 in cell_dimensions:
104
+ has_vertex_data = True
105
+
106
+ has_facet_data = comm.bcast(has_facet_data, root=rank)
107
+ if has_facet_data:
108
+ num_facet_nodes = comm.bcast(cell_information[perm_sort[-2]]["num_nodes"], root=rank)
109
+ gmsh_facet_id = cell_information[perm_sort[-2]]["id"]
110
+ marked_facets = np.asarray(topologies[gmsh_facet_id]["topology"], dtype=np.int64)
111
+ facet_values = np.asarray(topologies[gmsh_facet_id]["cell_data"], dtype=np.int32)
112
+
113
+ has_edge_data = comm.bcast(has_edge_data, root=rank)
114
+ if has_edge_data:
115
+ num_edge_nodes = comm.bcast(cell_information[perm_sort[-3]]["num_nodes"], root=rank)
116
+ gmsh_edge_id = cell_information[perm_sort[-3]]["id"]
117
+ marked_edges = np.asarray(topologies[gmsh_edge_id]["topology"], dtype=np.int64)
118
+ edge_values = np.asarray(topologies[gmsh_edge_id]["cell_data"], dtype=np.int32)
119
+
120
+ has_vertex_data = comm.bcast(has_vertex_data, root=rank)
121
+ if has_vertex_data:
122
+ num_vertex_nodes = comm.bcast(cell_information[perm_sort[-4]]["num_nodes"], root=rank)
123
+ gmsh_vertex_id = cell_information[perm_sort[-4]]["id"]
124
+ marked_vertices = np.asarray(topologies[gmsh_vertex_id]["topology"], dtype=np.int64)
125
+ vertex_values = np.asarray(topologies[gmsh_vertex_id]["cell_data"], dtype=np.int32)
126
+
127
+ cells = np.asarray(topologies[cell_id]["topology"], dtype=np.int64)
128
+ cell_values = np.asarray(topologies[cell_id]["cell_data"], dtype=np.int32)
129
+ else:
130
+ cell_id, num_nodes = comm.bcast([None, None], root=rank)
131
+ cells, x = np.empty([0, num_nodes], dtype=np.int32), np.empty([0, gdim], dtype=dtype)
132
+ cell_values = np.empty((0,), dtype=np.int32)
133
+
134
+ has_facet_data = comm.bcast(None, root=rank)
135
+ if has_facet_data:
136
+ num_facet_nodes = comm.bcast(None, root=rank)
137
+ marked_facets = np.empty((0, num_facet_nodes), dtype=np.int32)
138
+ facet_values = np.empty((0,), dtype=np.int32)
139
+
140
+ has_edge_data = comm.bcast(None, root=rank)
141
+ if has_edge_data:
142
+ num_edge_nodes = comm.bcast(None, root=rank)
143
+ marked_edges = np.empty((0, num_edge_nodes), dtype=np.int32)
144
+ edge_values = np.empty((0,), dtype=np.int32)
145
+
146
+ has_vertex_data = comm.bcast(None, root=rank)
147
+ if has_vertex_data:
148
+ num_vertex_nodes = comm.bcast(None, root=rank)
149
+ marked_vertices = np.empty((0, num_vertex_nodes), dtype=np.int32)
150
+ vertex_values = np.empty((0,), dtype=np.int32)
151
+
152
+ # Create distributed mesh
153
+ ufl_domain = dolfinx.io.gmshio.ufl_mesh(cell_id, gdim, dtype=dtype)
154
+ gmsh_cell_perm = dolfinx.io.gmshio.cell_perm_array(
155
+ dolfinx.cpp.mesh.to_type(str(ufl_domain.ufl_cell())), num_nodes
156
+ )
157
+ cells = cells[:, gmsh_cell_perm].copy()
158
+ mesh = dolfinx.mesh.create_mesh(
159
+ comm, cells, x[:, :gdim].astype(dtype, copy=False), ufl_domain, partitioner
20
160
  )
21
161
 
162
+ # Create MeshTags for cells
163
+ local_entities, local_values = dolfinx.io.utils.distribute_entity_data(
164
+ mesh._cpp_object, mesh.topology.dim, cells, cell_values
165
+ )
166
+ mesh.topology.create_connectivity(mesh.topology.dim, 0)
167
+ adj = dolfinx.cpp.graph.AdjacencyList_int32(local_entities)
168
+ ct = dolfinx.mesh.meshtags_from_entities(
169
+ mesh, mesh.topology.dim, adj, local_values.astype(np.int32, copy=False)
170
+ )
171
+ ct.name = "Cell tags"
172
+
173
+ # Create MeshTags for facets
174
+ topology = mesh.topology
175
+ tdim = topology.dim
176
+ if has_facet_data:
177
+ # Permute facets from MSH to DOLFINx ordering
178
+ # FIXME: This does not work for prism meshes
179
+ if (
180
+ topology.cell_type == dolfinx.mesh.CellType.prism
181
+ or topology.cell_type == dolfinx.mesh.CellType.pyramid
182
+ ):
183
+ raise RuntimeError(f"Unsupported cell type {topology.cell_type}")
184
+
185
+ facet_type = dolfinx.cpp.mesh.cell_entity_type(
186
+ dolfinx.cpp.mesh.to_type(str(ufl_domain.ufl_cell())), tdim - 1, 0
187
+ )
188
+ gmsh_facet_perm = dolfinx.io.gmshio.cell_perm_array(facet_type, num_facet_nodes)
189
+ marked_facets = marked_facets[:, gmsh_facet_perm]
190
+
191
+ local_entities, local_values = dolfinx.io.utils.distribute_entity_data(
192
+ mesh._cpp_object, tdim - 1, marked_facets, facet_values
193
+ )
194
+ mesh.topology.create_connectivity(topology.dim - 1, tdim)
195
+ adj = dolfinx.cpp.graph.AdjacencyList_int32(local_entities)
196
+ ft = dolfinx.io.gmshio.meshtags_from_entities(
197
+ mesh, tdim - 1, adj, local_values.astype(np.int32, copy=False)
198
+ )
199
+ ft.name = "Facet tags"
200
+ else:
201
+ ft = dolfinx.mesh.meshtags(
202
+ mesh, tdim - 1, np.empty(0, dtype=np.int32), np.empty(0, dtype=np.int32)
203
+ )
204
+
205
+ if has_edge_data:
206
+ # Permute edges from MSH to DOLFINx ordering
207
+ edge_type = dolfinx.cpp.mesh.cell_entity_type(
208
+ dolfinx.cpp.mesh.to_type(str(ufl_domain.ufl_cell())), tdim - 2, 0
209
+ )
210
+ gmsh_edge_perm = dolfinx.io.gmshio.cell_perm_array(edge_type, num_edge_nodes)
211
+ marked_edges = marked_edges[:, gmsh_edge_perm]
212
+
213
+ local_entities, local_values = dolfinx.io.utils.distribute_entity_data(
214
+ mesh._cpp_object, tdim - 2, marked_edges, edge_values
215
+ )
216
+ mesh.topology.create_connectivity(topology.dim - 2, tdim)
217
+ adj = dolfinx.cpp.graph.AdjacencyList_int32(local_entities)
218
+ et = dolfinx.io.gmshio.meshtags_from_entities(
219
+ mesh, tdim - 2, adj, local_values.astype(np.int32, copy=False)
220
+ )
221
+ et.name = "Edge tags"
222
+ else:
223
+ et = dolfinx.mesh.meshtags(
224
+ mesh, tdim - 2, np.empty(0, dtype=np.int32), np.empty(0, dtype=np.int32)
225
+ )
226
+
227
+ if has_vertex_data:
228
+ # Permute vertices from MSH to DOLFINx ordering
229
+ vertex_type = dolfinx.cpp.mesh.cell_entity_type(
230
+ dolfinx.cpp.mesh.to_type(str(ufl_domain.ufl_cell())), tdim - 3, 0
231
+ )
232
+ gmsh_vertex_perm = dolfinx.io.gmshio.cell_perm_array(vertex_type, num_vertex_nodes)
233
+ marked_vertices = marked_vertices[:, gmsh_vertex_perm]
234
+
235
+ local_entities, local_values = dolfinx.io.utils.distribute_entity_data(
236
+ mesh._cpp_object, tdim - 3, marked_vertices, vertex_values
237
+ )
238
+ mesh.topology.create_connectivity(topology.dim - 3, tdim)
239
+ adj = dolfinx.cpp.graph.AdjacencyList_int32(local_entities)
240
+ vt = dolfinx.io.gmshio.meshtags_from_entities(
241
+ mesh, tdim - 3, adj, local_values.astype(np.int32, copy=False)
242
+ )
243
+ vt.name = "Vertex tags"
244
+ else:
245
+ vt = dolfinx.mesh.meshtags(
246
+ mesh, tdim - 3, np.empty(0, dtype=np.int32), np.empty(0, dtype=np.int32)
247
+ )
248
+
249
+ return GMshModel(mesh, ct, ft, et, vt)
250
+
251
+
252
+ def parse_element(space_string: str, mesh: dolfinx.mesh.Mesh, dim: int) -> basix.ufl._ElementBase:
253
+ """
254
+ Parse a string representation of a basix element family
255
+ """
256
+
257
+ family_str, degree_str = space_string.split("_")
258
+ kwargs = {"degree": int(degree_str), "cell": mesh.ufl_cell().cellname()}
259
+ if dim > 1:
260
+ if family_str in quads:
261
+ kwargs["value_shape"] = (dim,)
262
+ else:
263
+ kwargs["shape"] = (dim,)
264
+
265
+ if family_str in ["Lagrange", "P", "CG"]:
266
+ el = basix.ufl.element(family=basix.ElementFamily.P, discontinuous=False, **kwargs)
267
+ elif family_str in ["Discontinuous Lagrange", "DG", "dP"]:
268
+ el = basix.ufl.element(family=basix.ElementFamily.P, discontinuous=True, **kwargs)
269
+
270
+ elif family_str in quads:
271
+ el = basix.ufl.quadrature_element(scheme="default", **kwargs)
272
+ else:
273
+ families = list(basix.ElementFamily.__members__.keys())
274
+ msg = f"Unknown element family: {family_str!r}, available families: {families}"
275
+ raise ValueError(msg)
276
+ return el
277
+
278
+
279
+ def space_from_string(
280
+ space_string: str, mesh: dolfinx.mesh.Mesh, dim: int
281
+ ) -> dolfinx.fem.functionspace:
282
+ """
283
+ Constructed a finite elements space from a string
284
+ representation of the space
285
+
286
+ Arguments
287
+ ---------
288
+ space_string : str
289
+ A string on the form {family}_{degree} which
290
+ determines the space. Example 'Lagrange_1'.
291
+ mesh : df.Mesh
292
+ The mesh
293
+ dim : int
294
+ 1 for scalar space, 3 for vector space.
295
+ """
296
+ el = parse_element(space_string, mesh, dim)
297
+ return dolfinx.fem.functionspace(mesh, el)
298
+
299
+
300
+ def element2array(el: basix.ufl._BlockedElement) -> np.ndarray:
301
+ if el.family_name in quads:
302
+ return np.array(
303
+ [QUADNR, int(el.cell_type), int(el.degree), 0],
304
+ dtype=np.uint8,
305
+ )
306
+ else:
307
+ return np.array(
308
+ [
309
+ int(el.basix_element.family),
310
+ int(el.cell_type),
311
+ int(el.degree),
312
+ int(el.basix_element.discontinuous),
313
+ ],
314
+ dtype=np.uint8,
315
+ )
316
+
22
317
 
23
318
  def number2Enum(num: int, enum: Iterable) -> Enum:
24
319
  for e in enum:
@@ -28,18 +323,24 @@ def number2Enum(num: int, enum: Iterable) -> Enum:
28
323
 
29
324
 
30
325
  def array2element(arr: np.ndarray) -> basix.finite_element.FiniteElement:
31
- family = number2Enum(arr[0], basix.ElementFamily)
32
326
  cell_type = number2Enum(arr[1], basix.CellType)
33
327
  degree = int(arr[2])
34
328
  discontinuous = bool(arr[3])
35
- # TODO: Shape is hardcoded to (3,) for now, but this should also be stored
36
- return basix.ufl.element(
37
- family=family,
38
- cell=cell_type,
39
- degree=degree,
40
- discontinuous=discontinuous,
41
- shape=(3,),
42
- )
329
+ if arr[0] == QUADNR:
330
+ return basix.ufl.quadrature_element(
331
+ scheme="default", degree=degree, value_shape=(3,), cell=cell_type
332
+ )
333
+ else:
334
+ family = number2Enum(arr[0], basix.ElementFamily)
335
+
336
+ # TODO: Shape is hardcoded to (3,) for now, but this should also be stored
337
+ return basix.ufl.element(
338
+ family=family,
339
+ cell=cell_type,
340
+ degree=degree,
341
+ discontinuous=discontinuous,
342
+ shape=(3,),
343
+ )
43
344
 
44
345
 
45
346
  def handle_mesh_name(mesh_name: str = "") -> Path:
@@ -69,13 +370,23 @@ class GMshGeometry(NamedTuple):
69
370
 
70
371
  def read_mesh(
71
372
  comm, filename: str | Path
72
- ) -> tuple[dolfinx.mesh.Mesh, dolfinx.mesh.MeshTags | None, dolfinx.mesh.MeshTags | None]:
373
+ ) -> tuple[dolfinx.mesh.Mesh, dict[str, dolfinx.mesh.MeshTags]]:
374
+ tags = {}
73
375
  with dolfinx.io.XDMFFile(comm, filename, "r") as xdmf:
74
376
  mesh = xdmf.read_mesh(name="Mesh")
75
- cfun = xdmf.read_meshtags(mesh, name="Cell tags")
76
- mesh.topology.create_connectivity(2, 3)
77
- ffun = xdmf.read_meshtags(mesh, name="Facet tags")
78
- return mesh, cfun, ffun
377
+ for var, name, dim in [
378
+ ("cfun", "Cell tags", mesh.topology.dim),
379
+ ("ffun", "Facet tags", mesh.topology.dim - 1),
380
+ ("efun", "Edge tags", mesh.topology.dim - 2),
381
+ ("vfun", "Vertex tags", mesh.topology.dim - 3),
382
+ ]:
383
+ mesh.topology.create_connectivity(dim, mesh.topology.dim)
384
+ try:
385
+ tags[var] = xdmf.read_meshtags(mesh, name=name)
386
+ except RuntimeError:
387
+ continue
388
+
389
+ return mesh, tags
79
390
 
80
391
 
81
392
  def gmsh2dolfin(comm: MPI.Intracomm, msh_file, rank: int = 0) -> GMshGeometry:
@@ -90,33 +401,40 @@ def gmsh2dolfin(comm: MPI.Intracomm, msh_file, rank: int = 0) -> GMshGeometry:
90
401
  gmsh.initialize()
91
402
  gmsh.model.add("Mesh from file")
92
403
  gmsh.merge(str(msh_file))
93
- mesh, ct, ft = dolfinx.io.gmshio.model_to_mesh(gmsh.model, comm, 0)
404
+ mesh, ct, ft, et, vt = model_to_mesh(gmsh.model, comm, 0)
94
405
  markers = {
95
406
  gmsh.model.getPhysicalName(*v): tuple(reversed(v))
96
407
  for v in gmsh.model.getPhysicalGroups()
97
408
  }
98
409
  gmsh.finalize()
99
410
  else:
100
- mesh, ct, ft = dolfinx.io.gmshio.model_to_mesh(gmsh.model, comm, 0)
411
+ mesh, ct, ft, et, vt = model_to_mesh(gmsh.model, comm, 0)
101
412
  markers = {}
102
413
  mesh.name = "Mesh"
103
414
  ct.name = "Cell tags"
104
415
  ft.name = "Facet tags"
416
+ et.name = "Edge tags"
417
+ vt.name = "Vertex tags"
105
418
 
106
419
  markers = comm.bcast(markers, root=rank)
107
420
 
108
421
  # Save tags to xdmf
109
422
  with dolfinx.io.XDMFFile(comm, outdir / "mesh.xdmf", "w") as xdmf:
110
423
  xdmf.write_mesh(mesh)
111
- mesh.topology.create_connectivity(2, 3)
112
424
  xdmf.write_meshtags(
113
425
  ct, mesh.geometry, geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{mesh.name}']/Geometry"
114
426
  )
427
+ mesh.topology.create_connectivity(2, 3)
115
428
  xdmf.write_meshtags(
116
429
  ft, mesh.geometry, geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{mesh.name}']/Geometry"
117
430
  )
431
+ mesh.topology.create_connectivity(1, 3)
432
+ xdmf.write_meshtags(
433
+ et, mesh.geometry, geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{mesh.name}']/Geometry"
434
+ )
435
+ mesh.topology.create_connectivity(0, 3)
436
+ xdmf.write_meshtags(
437
+ vt, mesh.geometry, geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{mesh.name}']/Geometry"
438
+ )
118
439
 
119
- vfun = None
120
- efun = None
121
-
122
- return GMshGeometry(mesh, ct, ft, efun, vfun, markers)
440
+ return GMshGeometry(mesh, ct, ft, et, vt, markers)
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Computational Physiology at Simula Research Laboratory
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.