cardiac-geometriesx 0.1.1__py3-none-any.whl → 0.1.3__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.

cardiac_geometries/cli.py CHANGED
@@ -8,8 +8,10 @@ from . import mesh
8
8
 
9
9
  meta = metadata("cardiac-geometriesx")
10
10
  __version__ = meta["Version"]
11
- __author__ = meta["Author"]
11
+ __author__ = meta["Author-email"]
12
12
  __license__ = meta["License"]
13
+ __email__ = meta["Author-email"]
14
+ __program_name__ = meta["Name"]
13
15
 
14
16
 
15
17
  @click.group()
@@ -131,9 +131,11 @@ def create_microstructure(
131
131
  )
132
132
 
133
133
  if outdir is not None:
134
- with dolfinx.io.XDMFFile(mesh.comm, Path(outdir) / "laplace.xdmf", "w") as file:
135
- file.write_mesh(mesh)
136
- file.write_function(t)
134
+ with dolfinx.io.VTXWriter(
135
+ mesh.comm, Path(outdir) / "laplace.bp", [t], engine="BP4"
136
+ ) as file:
137
+ file.write(0.0)
138
+
137
139
  system = compute_system(
138
140
  t,
139
141
  function_space=function_space,
@@ -134,9 +134,10 @@ def create_microstructure(
134
134
  function_space=function_space,
135
135
  )
136
136
  if outdir is not None:
137
- with dolfinx.io.XDMFFile(mesh.comm, Path(outdir) / "laplace.xdmf", "w") as file:
138
- file.write_mesh(mesh)
139
- file.write_function(t)
137
+ with dolfinx.io.VTXWriter(
138
+ mesh.comm, Path(outdir) / "laplace.bp", [t], engine="BP4"
139
+ ) as file:
140
+ file.write(0.0)
140
141
 
141
142
  system = compute_system(
142
143
  t,
@@ -20,9 +20,11 @@ def save_microstructure(
20
20
  from ..utils import element2array
21
21
 
22
22
  # Save for paraview visualization
23
- with dolfinx.io.VTXWriter(mesh.comm, Path(outdir) / "microstructure-viz.bp", functions, engine="BP4") as file:
23
+ with dolfinx.io.VTXWriter(
24
+ mesh.comm, Path(outdir) / "microstructure-viz.bp", functions, engine="BP4"
25
+ ) as file:
24
26
  file.write(0.0)
25
-
27
+
26
28
  # Save with proper function space
27
29
  filename = Path(outdir) / "microstructure.bp"
28
30
  for function in functions:
@@ -12,12 +12,14 @@ import numpy as np
12
12
  from . import utils
13
13
 
14
14
 
15
- @dataclass#(frozen=True, slots=True)
15
+ @dataclass # (frozen=True, slots=True)
16
16
  class Geometry:
17
17
  mesh: dolfinx.mesh.Mesh
18
- markers: dict[str, tuple[str, str]] = field(default_factory=dict)
19
- ffun: dolfinx.mesh.MeshTags | None = None
18
+ markers: dict[str, tuple[int, int]] = field(default_factory=dict)
20
19
  cfun: dolfinx.mesh.MeshTags | None = None
20
+ ffun: dolfinx.mesh.MeshTags | None = None
21
+ efun: dolfinx.mesh.MeshTags | None = None
22
+ vfun: dolfinx.mesh.MeshTags | None = None
21
23
  f0: dolfinx.fem.Function | None = None
22
24
  s0: dolfinx.fem.Function | None = None
23
25
  n0: dolfinx.fem.Function | None = None
@@ -25,8 +27,7 @@ class Geometry:
25
27
  def save(self, path: str | Path) -> None:
26
28
  path = Path(path)
27
29
 
28
- if path.exists() and self.mesh.comm.rank == 0:
29
- shutil.rmtree(path)
30
+ shutil.rmtree(path, ignore_errors=True)
30
31
  self.mesh.comm.barrier()
31
32
  adios4dolfinx.write_mesh(mesh=self.mesh, filename=path)
32
33
 
@@ -37,11 +38,33 @@ class Geometry:
37
38
  attributes={k: np.array(v, dtype=np.uint8) for k, v in self.markers.items()},
38
39
  )
39
40
 
41
+ if self.cfun is not None:
42
+ adios4dolfinx.write_meshtags(
43
+ meshtags=self.cfun,
44
+ mesh=self.mesh,
45
+ filename=path,
46
+ meshtag_name="Cell tags",
47
+ )
40
48
  if self.ffun is not None:
41
49
  adios4dolfinx.write_meshtags(
42
50
  meshtags=self.ffun,
43
51
  mesh=self.mesh,
44
52
  filename=path,
53
+ meshtag_name="Facet tags",
54
+ )
55
+ if self.efun is not None:
56
+ adios4dolfinx.write_meshtags(
57
+ meshtags=self.efun,
58
+ mesh=self.mesh,
59
+ filename=path,
60
+ meshtag_name="Edge tags",
61
+ )
62
+ if self.vfun is not None:
63
+ adios4dolfinx.write_meshtags(
64
+ meshtags=self.vfun,
65
+ mesh=self.mesh,
66
+ filename=path,
67
+ meshtag_name="Vertex tags",
45
68
  )
46
69
 
47
70
  if self.f0 is not None:
@@ -60,26 +83,49 @@ class Geometry:
60
83
  if self.n0 is not None:
61
84
  adios4dolfinx.write_function(u=self.n0, filename=path, name="n0")
62
85
 
86
+ self.mesh.comm.barrier()
87
+
63
88
  @classmethod
64
89
  def from_file(cls, comm: MPI.Intracomm, path: str | Path) -> "Geometry":
65
90
  path = Path(path)
91
+
66
92
  mesh = adios4dolfinx.read_mesh(comm=comm, filename=path)
67
93
  markers = adios4dolfinx.read_attributes(comm=comm, filename=path, name="markers")
68
- ffun = adios4dolfinx.read_meshtags(mesh=mesh, meshtag_name="Facet tags", filename=path)
94
+ tags = {}
95
+ for name, meshtag_name in (
96
+ ("cfun", "Cell tags"),
97
+ ("ffun", "Facet tags"),
98
+ ("efun", "Edge tags"),
99
+ ("vfun", "Vertex tags"),
100
+ ):
101
+ try:
102
+ tags[name] = adios4dolfinx.read_meshtags(
103
+ mesh=mesh, meshtag_name=meshtag_name, filename=path
104
+ )
105
+ except KeyError:
106
+ tags[name] = None
107
+
108
+ functions = {}
69
109
  function_space = adios4dolfinx.read_attributes(
70
110
  comm=comm, filename=path, name="function_space"
71
111
  )
72
- element = utils.array2element(function_space["f0"])
73
- # Assume same function space for all functions
74
- V = dolfinx.fem.functionspace(mesh, element)
75
- f0 = dolfinx.fem.Function(V, name="f0")
76
- s0 = dolfinx.fem.Function(V, name="s0")
77
- n0 = dolfinx.fem.Function(V, name="n0")
78
-
79
- adios4dolfinx.read_function(u=f0, filename=path, name="f0")
80
- adios4dolfinx.read_function(u=s0, filename=path, name="s0")
81
- adios4dolfinx.read_function(u=n0, filename=path, name="n0")
82
- return cls(mesh=mesh, markers=markers, ffun=ffun, f0=f0, s0=s0, n0=n0)
112
+ for name, el in function_space.items():
113
+ element = utils.array2element(el)
114
+ V = dolfinx.fem.functionspace(mesh, element)
115
+ f = dolfinx.fem.Function(V, name=name)
116
+ try:
117
+ adios4dolfinx.read_function(u=f, filename=path, name=name)
118
+ except KeyError:
119
+ continue
120
+ else:
121
+ functions[name] = f
122
+
123
+ return cls(
124
+ mesh=mesh,
125
+ markers=markers,
126
+ **functions,
127
+ **tags,
128
+ )
83
129
 
84
130
  @classmethod
85
131
  def from_folder(cls, comm: MPI.Intracomm, folder: str | Path) -> "Geometry":
@@ -87,7 +133,7 @@ class Geometry:
87
133
 
88
134
  # Read mesh
89
135
  if (folder / "mesh.xdmf").exists():
90
- mesh, cfun, ffun = utils.read_mesh(comm=comm, filename=folder / "mesh.xdmf")
136
+ mesh, tags = utils.read_mesh(comm=comm, filename=folder / "mesh.xdmf")
91
137
  else:
92
138
  raise ValueError("No mesh file found")
93
139
 
@@ -101,22 +147,26 @@ class Geometry:
101
147
  else:
102
148
  markers = {}
103
149
 
150
+ functions = {}
104
151
  microstructure_path = folder / "microstructure.bp"
105
152
  if microstructure_path.exists():
106
153
  function_space = adios4dolfinx.read_attributes(
107
154
  comm=MPI.COMM_WORLD, filename=microstructure_path, name="function_space"
108
155
  )
109
- # Assume same function space for all functions
110
- element = utils.array2element(function_space["f0"])
111
- V = dolfinx.fem.functionspace(mesh, element)
112
- f0 = dolfinx.fem.Function(V, name="f0")
113
- s0 = dolfinx.fem.Function(V, name="s0")
114
- n0 = dolfinx.fem.Function(V, name="n0")
115
-
116
- adios4dolfinx.read_function(u=f0, filename=microstructure_path, name="f0")
117
- adios4dolfinx.read_function(u=s0, filename=microstructure_path, name="s0")
118
- adios4dolfinx.read_function(u=n0, filename=microstructure_path, name="n0")
119
- else:
120
- f0 = s0 = n0 = None
121
-
122
- return cls(mesh=mesh, ffun=ffun, cfun=cfun, markers=markers, f0=f0, s0=s0, n0=n0)
156
+ for name, el in function_space.items():
157
+ element = utils.array2element(el)
158
+ V = dolfinx.fem.functionspace(mesh, element)
159
+ f = dolfinx.fem.Function(V, name=name)
160
+ try:
161
+ adios4dolfinx.read_function(u=f, filename=microstructure_path, name=name)
162
+ except KeyError:
163
+ continue
164
+ else:
165
+ functions[name] = f
166
+
167
+ return cls(
168
+ mesh=mesh,
169
+ markers=markers,
170
+ **functions,
171
+ **tags,
172
+ )
@@ -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
@@ -13,6 +14,238 @@ from structlog import get_logger
13
14
  logger = get_logger()
14
15
 
15
16
 
17
+ class GMshModel(NamedTuple):
18
+ mesh: dolfinx.mesh.Mesh
19
+ cell_tags: dolfinx.mesh.MeshTags
20
+ facet_tags: dolfinx.mesh.MeshTags
21
+ edge_tags: dolfinx.mesh.MeshTags
22
+ vertex_tags: dolfinx.mesh.MeshTags
23
+
24
+
25
+ # copied from https://github.com/FEniCS/dolfinx/blob/main/python/dolfinx/io/gmshio.py
26
+ def model_to_mesh(
27
+ model,
28
+ comm: MPI.Comm,
29
+ rank: int,
30
+ gdim: int = 3,
31
+ partitioner: typing.Optional[
32
+ typing.Callable[
33
+ [MPI.Comm, int, int, dolfinx.cpp.graph.AdjacencyList_int32],
34
+ dolfinx.cpp.graph.AdjacencyList_int32,
35
+ ]
36
+ ] = None,
37
+ dtype=dolfinx.default_real_type,
38
+ ) -> GMshModel:
39
+ """Create a Mesh from a Gmsh model.
40
+
41
+ Creates a :class:`dolfinx.mesh.Mesh` from the physical entities of
42
+ the highest topological dimension in the Gmsh model. In parallel,
43
+ the gmsh model is processed on one MPI rank, and the
44
+ :class:`dolfinx.mesh.Mesh` is distributed across ranks.
45
+
46
+ Args:
47
+ model: Gmsh model.
48
+ comm: MPI communicator to use for mesh creation.
49
+ rank: MPI rank that the Gmsh model is initialized on.
50
+ gdim: Geometrical dimension of the mesh.
51
+ partitioner: Function that computes the parallel
52
+ distribution of cells across MPI ranks.
53
+
54
+ Returns:
55
+ A tuple containing the :class:`dolfinx.mesh.Mesh` and
56
+ :class:`dolfinx.mesh.MeshTags` for cells, facets, edges and
57
+ vertices.
58
+
59
+
60
+ Note:
61
+ For performance, this function should only be called once for
62
+ large problems. For re-use, it is recommended to save the mesh
63
+ and corresponding tags using :class:`dolfinxio.XDMFFile` after
64
+ creation for efficient access.
65
+
66
+ """
67
+ if comm.rank == rank:
68
+ assert model is not None, "Gmsh model is None on rank responsible for mesh creation."
69
+ # Get mesh geometry and mesh topology for each element
70
+ x = dolfinx.io.gmshio.extract_geometry(model)
71
+ topologies = dolfinx.io.gmshio.extract_topology_and_markers(model)
72
+
73
+ # Extract Gmsh cell id, dimension of cell and number of nodes to
74
+ # cell for each
75
+ num_cell_types = len(topologies.keys())
76
+ cell_information = dict()
77
+ cell_dimensions = np.zeros(num_cell_types, dtype=np.int32)
78
+ for i, element in enumerate(topologies.keys()):
79
+ _, dim, _, num_nodes, _, _ = model.mesh.getElementProperties(element)
80
+ cell_information[i] = {"id": element, "dim": dim, "num_nodes": num_nodes}
81
+ cell_dimensions[i] = dim
82
+
83
+ # Sort elements by ascending dimension
84
+ perm_sort = np.argsort(cell_dimensions)
85
+
86
+ # Broadcast cell type data and geometric dimension
87
+ cell_id = cell_information[perm_sort[-1]]["id"]
88
+ tdim = cell_information[perm_sort[-1]]["dim"]
89
+ num_nodes = cell_information[perm_sort[-1]]["num_nodes"]
90
+ cell_id, num_nodes = comm.bcast([cell_id, num_nodes], root=rank)
91
+
92
+ # Check for facet data and broadcast relevant info if True
93
+ has_facet_data = False
94
+ if tdim - 1 in cell_dimensions:
95
+ has_facet_data = True
96
+ has_edge_data = False
97
+ if tdim - 2 in cell_dimensions:
98
+ has_edge_data = True
99
+ has_vertex_data = False
100
+ if tdim - 3 in cell_dimensions:
101
+ has_vertex_data = True
102
+
103
+ has_facet_data = comm.bcast(has_facet_data, root=rank)
104
+ if has_facet_data:
105
+ num_facet_nodes = comm.bcast(cell_information[perm_sort[-2]]["num_nodes"], root=rank)
106
+ gmsh_facet_id = cell_information[perm_sort[-2]]["id"]
107
+ marked_facets = np.asarray(topologies[gmsh_facet_id]["topology"], dtype=np.int64)
108
+ facet_values = np.asarray(topologies[gmsh_facet_id]["cell_data"], dtype=np.int32)
109
+
110
+ has_edge_data = comm.bcast(has_edge_data, root=rank)
111
+ if has_edge_data:
112
+ num_edge_nodes = comm.bcast(cell_information[perm_sort[-3]]["num_nodes"], root=rank)
113
+ gmsh_edge_id = cell_information[perm_sort[-3]]["id"]
114
+ marked_edges = np.asarray(topologies[gmsh_edge_id]["topology"], dtype=np.int64)
115
+ edge_values = np.asarray(topologies[gmsh_edge_id]["cell_data"], dtype=np.int32)
116
+
117
+ has_vertex_data = comm.bcast(has_vertex_data, root=rank)
118
+ if has_vertex_data:
119
+ num_vertex_nodes = comm.bcast(cell_information[perm_sort[-4]]["num_nodes"], root=rank)
120
+ gmsh_vertex_id = cell_information[perm_sort[-4]]["id"]
121
+ marked_vertices = np.asarray(topologies[gmsh_vertex_id]["topology"], dtype=np.int64)
122
+ vertex_values = np.asarray(topologies[gmsh_vertex_id]["cell_data"], dtype=np.int32)
123
+
124
+ cells = np.asarray(topologies[cell_id]["topology"], dtype=np.int64)
125
+ cell_values = np.asarray(topologies[cell_id]["cell_data"], dtype=np.int32)
126
+ else:
127
+ cell_id, num_nodes = comm.bcast([None, None], root=rank)
128
+ cells, x = np.empty([0, num_nodes], dtype=np.int32), np.empty([0, gdim], dtype=dtype)
129
+ cell_values = np.empty((0,), dtype=np.int32)
130
+
131
+ has_facet_data = comm.bcast(None, root=rank)
132
+ if has_facet_data:
133
+ num_facet_nodes = comm.bcast(None, root=rank)
134
+ marked_facets = np.empty((0, num_facet_nodes), dtype=np.int32)
135
+ facet_values = np.empty((0,), dtype=np.int32)
136
+
137
+ has_edge_data = comm.bcast(None, root=rank)
138
+ if has_edge_data:
139
+ num_edge_nodes = comm.bcast(None, root=rank)
140
+ marked_edges = np.empty((0, num_edge_nodes), dtype=np.int32)
141
+ edge_values = np.empty((0,), dtype=np.int32)
142
+
143
+ has_vertex_data = comm.bcast(None, root=rank)
144
+ if has_vertex_data:
145
+ num_vertex_nodes = comm.bcast(None, root=rank)
146
+ marked_vertices = np.empty((0, num_vertex_nodes), dtype=np.int32)
147
+ vertex_values = np.empty((0,), dtype=np.int32)
148
+
149
+ # Create distributed mesh
150
+ ufl_domain = dolfinx.io.gmshio.ufl_mesh(cell_id, gdim, dtype=dtype)
151
+ gmsh_cell_perm = dolfinx.io.gmshio.cell_perm_array(
152
+ dolfinx.cpp.mesh.to_type(str(ufl_domain.ufl_cell())), num_nodes
153
+ )
154
+ cells = cells[:, gmsh_cell_perm].copy()
155
+ mesh = dolfinx.mesh.create_mesh(
156
+ comm, cells, x[:, :gdim].astype(dtype, copy=False), ufl_domain, partitioner
157
+ )
158
+
159
+ # Create MeshTags for cells
160
+ local_entities, local_values = dolfinx.io.utils.distribute_entity_data(
161
+ mesh._cpp_object, mesh.topology.dim, cells, cell_values
162
+ )
163
+ mesh.topology.create_connectivity(mesh.topology.dim, 0)
164
+ adj = dolfinx.cpp.graph.AdjacencyList_int32(local_entities)
165
+ ct = dolfinx.mesh.meshtags_from_entities(
166
+ mesh, mesh.topology.dim, adj, local_values.astype(np.int32, copy=False)
167
+ )
168
+ ct.name = "Cell tags"
169
+
170
+ # Create MeshTags for facets
171
+ topology = mesh.topology
172
+ tdim = topology.dim
173
+ if has_facet_data:
174
+ # Permute facets from MSH to DOLFINx ordering
175
+ # FIXME: This does not work for prism meshes
176
+ if (
177
+ topology.cell_type == dolfinx.mesh.CellType.prism
178
+ or topology.cell_type == dolfinx.mesh.CellType.pyramid
179
+ ):
180
+ raise RuntimeError(f"Unsupported cell type {topology.cell_type}")
181
+
182
+ facet_type = dolfinx.cpp.mesh.cell_entity_type(
183
+ dolfinx.cpp.mesh.to_type(str(ufl_domain.ufl_cell())), tdim - 1, 0
184
+ )
185
+ gmsh_facet_perm = dolfinx.io.gmshio.cell_perm_array(facet_type, num_facet_nodes)
186
+ marked_facets = marked_facets[:, gmsh_facet_perm]
187
+
188
+ local_entities, local_values = dolfinx.io.utils.distribute_entity_data(
189
+ mesh._cpp_object, tdim - 1, marked_facets, facet_values
190
+ )
191
+ mesh.topology.create_connectivity(topology.dim - 1, tdim)
192
+ adj = dolfinx.cpp.graph.AdjacencyList_int32(local_entities)
193
+ ft = dolfinx.io.gmshio.meshtags_from_entities(
194
+ mesh, tdim - 1, adj, local_values.astype(np.int32, copy=False)
195
+ )
196
+ ft.name = "Facet tags"
197
+ else:
198
+ ft = dolfinx.mesh.meshtags(
199
+ mesh, tdim - 1, np.empty(0, dtype=np.int32), np.empty(0, dtype=np.int32)
200
+ )
201
+
202
+ if has_edge_data:
203
+ # Permute edges from MSH to DOLFINx ordering
204
+ edge_type = dolfinx.cpp.mesh.cell_entity_type(
205
+ dolfinx.cpp.mesh.to_type(str(ufl_domain.ufl_cell())), tdim - 2, 0
206
+ )
207
+ gmsh_edge_perm = dolfinx.io.gmshio.cell_perm_array(edge_type, num_edge_nodes)
208
+ marked_edges = marked_edges[:, gmsh_edge_perm]
209
+
210
+ local_entities, local_values = dolfinx.io.utils.distribute_entity_data(
211
+ mesh._cpp_object, tdim - 2, marked_edges, edge_values
212
+ )
213
+ mesh.topology.create_connectivity(topology.dim - 2, tdim)
214
+ adj = dolfinx.cpp.graph.AdjacencyList_int32(local_entities)
215
+ et = dolfinx.io.gmshio.meshtags_from_entities(
216
+ mesh, tdim - 2, adj, local_values.astype(np.int32, copy=False)
217
+ )
218
+ et.name = "Edge tags"
219
+ else:
220
+ et = dolfinx.mesh.meshtags(
221
+ mesh, tdim - 2, np.empty(0, dtype=np.int32), np.empty(0, dtype=np.int32)
222
+ )
223
+
224
+ if has_vertex_data:
225
+ # Permute vertices from MSH to DOLFINx ordering
226
+ vertex_type = dolfinx.cpp.mesh.cell_entity_type(
227
+ dolfinx.cpp.mesh.to_type(str(ufl_domain.ufl_cell())), tdim - 3, 0
228
+ )
229
+ gmsh_vertex_perm = dolfinx.io.gmshio.cell_perm_array(vertex_type, num_vertex_nodes)
230
+ marked_vertices = marked_vertices[:, gmsh_vertex_perm]
231
+
232
+ local_entities, local_values = dolfinx.io.utils.distribute_entity_data(
233
+ mesh._cpp_object, tdim - 3, marked_vertices, vertex_values
234
+ )
235
+ mesh.topology.create_connectivity(topology.dim - 3, tdim)
236
+ adj = dolfinx.cpp.graph.AdjacencyList_int32(local_entities)
237
+ vt = dolfinx.io.gmshio.meshtags_from_entities(
238
+ mesh, tdim - 3, adj, local_values.astype(np.int32, copy=False)
239
+ )
240
+ vt.name = "Vertex tags"
241
+ else:
242
+ vt = dolfinx.mesh.meshtags(
243
+ mesh, tdim - 3, np.empty(0, dtype=np.int32), np.empty(0, dtype=np.int32)
244
+ )
245
+
246
+ return GMshModel(mesh, ct, ft, et, vt)
247
+
248
+
16
249
  def element2array(el: basix.finite_element.FiniteElement) -> np.ndarray:
17
250
  return np.array(
18
251
  [int(el.family), int(el.cell_type), int(el.degree), int(el.discontinuous)],
@@ -69,13 +302,23 @@ class GMshGeometry(NamedTuple):
69
302
 
70
303
  def read_mesh(
71
304
  comm, filename: str | Path
72
- ) -> tuple[dolfinx.mesh.Mesh, dolfinx.mesh.MeshTags | None, dolfinx.mesh.MeshTags | None]:
305
+ ) -> tuple[dolfinx.mesh.Mesh, dict[str, dolfinx.mesh.MeshTags]]:
306
+ tags = {}
73
307
  with dolfinx.io.XDMFFile(comm, filename, "r") as xdmf:
74
308
  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
309
+ for var, name, dim in [
310
+ ("cfun", "Cell tags", mesh.topology.dim),
311
+ ("ffun", "Facet tags", mesh.topology.dim - 1),
312
+ ("efun", "Edge tags", mesh.topology.dim - 2),
313
+ ("vfun", "Vertex tags", mesh.topology.dim - 3),
314
+ ]:
315
+ mesh.topology.create_connectivity(dim, mesh.topology.dim)
316
+ try:
317
+ tags[var] = xdmf.read_meshtags(mesh, name=name)
318
+ except RuntimeError:
319
+ continue
320
+
321
+ return mesh, tags
79
322
 
80
323
 
81
324
  def gmsh2dolfin(comm: MPI.Intracomm, msh_file, rank: int = 0) -> GMshGeometry:
@@ -90,33 +333,40 @@ def gmsh2dolfin(comm: MPI.Intracomm, msh_file, rank: int = 0) -> GMshGeometry:
90
333
  gmsh.initialize()
91
334
  gmsh.model.add("Mesh from file")
92
335
  gmsh.merge(str(msh_file))
93
- mesh, ct, ft = dolfinx.io.gmshio.model_to_mesh(gmsh.model, comm, 0)
336
+ mesh, ct, ft, et, vt = model_to_mesh(gmsh.model, comm, 0)
94
337
  markers = {
95
338
  gmsh.model.getPhysicalName(*v): tuple(reversed(v))
96
339
  for v in gmsh.model.getPhysicalGroups()
97
340
  }
98
341
  gmsh.finalize()
99
342
  else:
100
- mesh, ct, ft = dolfinx.io.gmshio.model_to_mesh(gmsh.model, comm, 0)
343
+ mesh, ct, ft, et, vt = model_to_mesh(gmsh.model, comm, 0)
101
344
  markers = {}
102
345
  mesh.name = "Mesh"
103
346
  ct.name = "Cell tags"
104
347
  ft.name = "Facet tags"
348
+ et.name = "Edge tags"
349
+ vt.name = "Vertex tags"
105
350
 
106
351
  markers = comm.bcast(markers, root=rank)
107
352
 
108
353
  # Save tags to xdmf
109
354
  with dolfinx.io.XDMFFile(comm, outdir / "mesh.xdmf", "w") as xdmf:
110
355
  xdmf.write_mesh(mesh)
111
- mesh.topology.create_connectivity(2, 3)
112
356
  xdmf.write_meshtags(
113
357
  ct, mesh.geometry, geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{mesh.name}']/Geometry"
114
358
  )
359
+ mesh.topology.create_connectivity(2, 3)
115
360
  xdmf.write_meshtags(
116
361
  ft, mesh.geometry, geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{mesh.name}']/Geometry"
117
362
  )
363
+ mesh.topology.create_connectivity(1, 3)
364
+ xdmf.write_meshtags(
365
+ et, mesh.geometry, geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{mesh.name}']/Geometry"
366
+ )
367
+ mesh.topology.create_connectivity(0, 3)
368
+ xdmf.write_meshtags(
369
+ vt, mesh.geometry, geometry_xpath=f"/Xdmf/Domain/Grid[@Name='{mesh.name}']/Geometry"
370
+ )
118
371
 
119
- vfun = None
120
- efun = None
121
-
122
- return GMshGeometry(mesh, ct, ft, efun, vfun, markers)
372
+ 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.
@@ -0,0 +1,87 @@
1
+ Metadata-Version: 2.1
2
+ Name: cardiac-geometriesx
3
+ Version: 0.1.3
4
+ Summary: A python library for cardiac geometries
5
+ Author-email: Henrik Finsberg <henriknf@simula.no>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/finsberg/cardiac-geometriesx
8
+ Keywords: cardiac,geometry
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3 :: Only
12
+ Requires-Python: >=3.8
13
+ Description-Content-Type: text/markdown
14
+ License-File: LICENSE
15
+ Requires-Dist: fenics-dolfinx >=0.8.0
16
+ Requires-Dist: structlog
17
+ Requires-Dist: cardiac-geometries-core
18
+ Requires-Dist: rich-click
19
+ Requires-Dist: adios4dolfinx
20
+ Provides-Extra: dev
21
+ Requires-Dist: bump-my-version ; extra == 'dev'
22
+ Requires-Dist: ipython ; extra == 'dev'
23
+ Requires-Dist: pdbpp ; extra == 'dev'
24
+ Requires-Dist: pre-commit ; extra == 'dev'
25
+ Requires-Dist: twine ; extra == 'dev'
26
+ Requires-Dist: wheel ; extra == 'dev'
27
+ Provides-Extra: docs
28
+ Requires-Dist: jupyter-book ; extra == 'docs'
29
+ Requires-Dist: jupytext ; extra == 'docs'
30
+ Requires-Dist: jupyter ; extra == 'docs'
31
+ Requires-Dist: pyvista[all] >=0.43.0 ; extra == 'docs'
32
+ Requires-Dist: trame-vuetify ; extra == 'docs'
33
+ Requires-Dist: ipywidgets ; extra == 'docs'
34
+ Requires-Dist: fenicsx-ldrb ; extra == 'docs'
35
+ Provides-Extra: test
36
+ Requires-Dist: pre-commit ; extra == 'test'
37
+ Requires-Dist: pytest ; extra == 'test'
38
+ Requires-Dist: pytest-cov ; extra == 'test'
39
+
40
+ ![_](docs/_static/logo.png)
41
+
42
+ [![Create and publish a Docker image](https://github.com/ComputationalPhysiology/cardiac-geometriesx/actions/workflows/docker-image.yml/badge.svg)](https://github.com/ComputationalPhysiology/cardiac-geometriesx/actions/workflows/docker-image.yml)
43
+ [![Test package](https://github.com/ComputationalPhysiology/cardiac-geometriesx/actions/workflows/test.yml/badge.svg)](https://github.com/ComputationalPhysiology/cardiac-geometriesx/actions/workflows/test.yml)
44
+ [![Test package MPI](https://github.com/ComputationalPhysiology/cardiac-geometriesx/actions/workflows/test-mpi.yml/badge.svg)](https://github.com/ComputationalPhysiology/cardiac-geometriesx/actions/workflows/test-mpi.yml)
45
+ [![Pre-commit](https://github.com/ComputationalPhysiology/cardiac-geometriesx/actions/workflows/pre-commit.yml/badge.svg)](https://github.com/ComputationalPhysiology/cardiac-geometriesx/actions/workflows/pre-commit.yml)
46
+ [![PyPI version](https://badge.fury.io/py/cardiac-geometriesx.svg)](https://badge.fury.io/py/cardiac-geometriesx)
47
+
48
+ # Cardiac geometries
49
+
50
+ Cardiac geometries is a software package built on top of [`cariac-geometries-core`](https://github.com/ComputationalPhysiology/cardiac-geometries-core) that adds support for creating idealized cardiac geometries for dolfinx.
51
+
52
+ There are two ways you can use `cardiac-geomtries`, either using the command line interface, e.g
53
+ ```
54
+ geox lv-ellipsoid --create-fibers lv-mesh --fiber-space P_2
55
+ ```
56
+
57
+ or using the python API e.g
58
+ ```python
59
+ geo = cardiac_geometries.mesh.lv_ellipsoid(outdir="lv-mesh", create_fibers=True, fiber_space="P_2")
60
+ ```
61
+
62
+ ## Install
63
+
64
+ To install the package you can use `pip`
65
+ ```
66
+ python3 -m pip install cardiac-geometriesx
67
+ ```
68
+ however, this assumes that you already have `dolfinx` pre-installed. You can also use the provided docker image e.g
69
+ ```
70
+ docker pull ghcr.io/computationalphysiology/cardiac-geometriesx:latest
71
+ ```
72
+ To start a new container interactive you can do
73
+ ```
74
+ docker run --name geox -v $PWD:/home/shared -w /home/shared -it ghcr.io/computationalphysiology/cardiac-geometriesx:latest
75
+ ```
76
+ or if you just want to create a mesh and exit you can run the command line interface directly e.g
77
+ ```
78
+ docker run --rm -v $PWD:/home/shared -w /home/shared -it ghcr.io/computationalphysiology/cardiac-geometriesx:latest geox lv-ellipsoid --create-fibers lv-mesh --fiber-space P_2
79
+ ```
80
+
81
+ ## Authors
82
+ Henrik Finsberg (henriknf@simula.no)
83
+
84
+ ## License
85
+ MIT
86
+
87
+ ## Contributing
@@ -0,0 +1,15 @@
1
+ cardiac_geometries/__init__.py,sha256=2W_ywAeLjyRk5MqSPAodHa4UN2lOnW1h8tmGLQ3gaJ0,150
2
+ cardiac_geometries/cli.py,sha256=hOv1muw33MY1GedXnIR1xvh488lWIzRDq29LajK6PY0,19394
3
+ cardiac_geometries/geometry.py,sha256=UWOasa3l7BM1tdr3au5Bu0hPnOe1TyoP8KMMrwtJjyM,5650
4
+ cardiac_geometries/mesh.py,sha256=JSm69BTS7WRUldwaQ9-Gnjq_KbX66FTnl2ouFKRtFhA,26825
5
+ cardiac_geometries/utils.py,sha256=vO9Ov385JBfODH-3exVVgK9xKvAoNLThi_QyiM2w7iE,14312
6
+ cardiac_geometries/fibers/__init__.py,sha256=WpRrn9Iakl-3m8IGtFkqP0LXGjw5EZHZ8Eg9JCnCdrg,137
7
+ cardiac_geometries/fibers/lv_ellipsoid.py,sha256=jdRek3v_V2a9IQHqH7P-lK0JBwa1dUNpBBmABzKGje4,3690
8
+ cardiac_geometries/fibers/slab.py,sha256=h_iVs_e1F9LJaXJOzam336f0LcTAXXBvApsFZGZSnZs,3861
9
+ cardiac_geometries/fibers/utils.py,sha256=hMTAKy32ow10tpNE1-WuvCYk7PVXO1qL0dHPa3LjecQ,2434
10
+ cardiac_geometriesx-0.1.3.dist-info/LICENSE,sha256=lo5K2rJPZOSv6luutGHbzzi3IpXNaB9E2UWq60qvNx0,1111
11
+ cardiac_geometriesx-0.1.3.dist-info/METADATA,sha256=M9ntsIWoEz5VILHZa2nWKMDjvdM25_7QsV9kvvFDFWI,3871
12
+ cardiac_geometriesx-0.1.3.dist-info/WHEEL,sha256=y4mX-SOX4fYIkonsAGA5N0Oy-8_gI4FXw5HNI1xqvWg,91
13
+ cardiac_geometriesx-0.1.3.dist-info/entry_points.txt,sha256=xOBnlc6W-H9oCDYLNz3kpki26OmpfYSoFSrmi_4V-Ec,52
14
+ cardiac_geometriesx-0.1.3.dist-info/top_level.txt,sha256=J0gQxkWR2my5Vf7Qt8buDY8ZOjYdVfIweVunCGXWKNE,19
15
+ cardiac_geometriesx-0.1.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (70.1.0)
2
+ Generator: setuptools (70.2.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,51 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: cardiac-geometriesx
3
- Version: 0.1.1
4
- Summary: A python library for cardiac geometries
5
- Author-email: Henrik Finsberg <henriknf@simula.no>
6
- License: MIT
7
- Project-URL: Homepage, https://github.com/finsberg/cardiac-geometriesx
8
- Keywords: cardiac,geometry
9
- Classifier: License :: OSI Approved :: MIT License
10
- Classifier: Programming Language :: Python :: 3
11
- Classifier: Programming Language :: Python :: 3 :: Only
12
- Requires-Python: >=3.8
13
- Description-Content-Type: text/markdown
14
- Requires-Dist: fenics-dolfinx >=0.8.0
15
- Requires-Dist: structlog
16
- Requires-Dist: cardiac-geometries-core
17
- Requires-Dist: rich-click
18
- Requires-Dist: adios4dolfinx
19
- Provides-Extra: dev
20
- Requires-Dist: bump-my-version ; extra == 'dev'
21
- Requires-Dist: ipython ; extra == 'dev'
22
- Requires-Dist: pdbpp ; extra == 'dev'
23
- Requires-Dist: pre-commit ; extra == 'dev'
24
- Requires-Dist: twine ; extra == 'dev'
25
- Requires-Dist: wheel ; extra == 'dev'
26
- Provides-Extra: docs
27
- Provides-Extra: test
28
- Requires-Dist: pre-commit ; extra == 'test'
29
- Requires-Dist: pytest ; extra == 'test'
30
- Requires-Dist: pytest-cov ; extra == 'test'
31
-
32
-
33
- # Cardiac geometries
34
-
35
- Cardiac geometries for `dolfinx` (targeting v0.8).
36
-
37
- Install
38
- ```
39
- python3 -m pip install cardiac-geometriesx
40
- ```
41
-
42
- Example usage
43
- ```
44
- geox lv-ellipsoid --create-fibers lv-mesh
45
- ```
46
-
47
- ## Authors
48
- Henrik Finsberg (henriknf@simula.no)
49
-
50
- ## License
51
- MIT
@@ -1,14 +0,0 @@
1
- cardiac_geometries/__init__.py,sha256=2W_ywAeLjyRk5MqSPAodHa4UN2lOnW1h8tmGLQ3gaJ0,150
2
- cardiac_geometries/cli.py,sha256=2-99xmcBltdotzxp4aDZLrh46gyA5U_PZc6lHen7LIM,19323
3
- cardiac_geometries/geometry.py,sha256=rhtq219q06ta1ZS_WIyqfjr00rN1L099bfX_Fwhe29g,4656
4
- cardiac_geometries/mesh.py,sha256=cJAcwdDaYmO8MlfocaxvV6pkBcfEWybMSBAaycr6q3s,25987
5
- cardiac_geometries/utils.py,sha256=-fH13vimCYrirDZubQdxRpX-7I9iCFsOJ8Xo1NL5Nvc,3596
6
- cardiac_geometries/fibers/__init__.py,sha256=WpRrn9Iakl-3m8IGtFkqP0LXGjw5EZHZ8Eg9JCnCdrg,137
7
- cardiac_geometries/fibers/lv_ellipsoid.py,sha256=zcCTcst08-xcG5c2j8rPLFp0cgNjXt1Q0mVGhs5r5Ps,3695
8
- cardiac_geometries/fibers/slab.py,sha256=-EWG4cHjtFYo4VyoFfVnsbHhfdf8qKWphYimJ1jB56o,3867
9
- cardiac_geometries/fibers/utils.py,sha256=K_C_G1az_SnwBAeKvvvZN4V4M_6z0DAAqOsV7M3T5aI,2428
10
- cardiac_geometriesx-0.1.1.dist-info/METADATA,sha256=w_cEg_5ZBV2Ptl9OxH2VN4Psll0wjlLU0WTR4GIaB8o,1349
11
- cardiac_geometriesx-0.1.1.dist-info/WHEEL,sha256=cpQTJ5IWu9CdaPViMhC9YzF8gZuS5-vlfoFihTBC86A,91
12
- cardiac_geometriesx-0.1.1.dist-info/entry_points.txt,sha256=xOBnlc6W-H9oCDYLNz3kpki26OmpfYSoFSrmi_4V-Ec,52
13
- cardiac_geometriesx-0.1.1.dist-info/top_level.txt,sha256=J0gQxkWR2my5Vf7Qt8buDY8ZOjYdVfIweVunCGXWKNE,19
14
- cardiac_geometriesx-0.1.1.dist-info/RECORD,,