Coverage for /dolfinx-env/lib/python3.14/site-packages/io4dolfinx/backends/med/backend.py: 96%

161 statements  

« prev     ^ index     » next       coverage.py v7.17.0, created at 2026-10-23 13:26 +0000

1""" 

2MED interface to io4dolfinx. 

3 

4MED is the HDF5 based mesh format of Salome and Code_Aster. See 

5https://docs.salome-platform.org/latest/dev/MEDCoupling/developer/med-file.html 

6 

7Families and groups, in DOLFINx terms: 

8 

9- Every entity stores one *family* id (``MAI/<type>/FAM`` for elements, negative; 

10 ``NOE/FAM`` for nodes, positive; ``0`` for none). Per element type, these ids are 

11 the values of a :class:`dolfinx.mesh.MeshTags`, with family 0 meaning untagged. 

12- ``FAS/<mesh>`` maps each family id to the named *groups* it belongs to, which 

13 ``MeshTags`` has no equivalent of. 

14- A family is one combination of groups, so overlapping groups (an entity in both 

15 ``inlet`` and ``wall``) get a family of their own. A group is the union of its 

16 families, read as a separate ``MeshTags`` with value 1. 

17 

18Element family ids are negated when read, as integral subdomain ids in DOLFINx/FFCx 

19must be non-negative. 

20 

21SPDX License identifier: MIT 

22 

23Copyright: Jørgen S. Dokken and Simula Research Laboratory 

24""" 

25 

26from pathlib import Path 

27from typing import Any, Literal 

28 

29from mpi4py import MPI 

30 

31import basix 

32import dolfinx 

33import h5py 

34import numpy as np 

35import numpy.typing as npt 

36 

37from ...structures import ArrayData, FunctionData, MeshData, MeshTagsData, ReadMeshData 

38from ...utils import check_file_exists, compute_local_range 

39from .. import DEFAULT_MESH_NAME, FileMode, ReadMode, get_mesh_name 

40from ..h5py.backend import h5pyfile, read_component_major_rows 

41 

42read_mode = ReadMode.parallel 

43 

44# MED element type -> (DOLFINx cell type, degree) 

45_med_cell_types = { 

46 "PO1": (dolfinx.mesh.CellType.point, 1), 

47 "SE2": (dolfinx.mesh.CellType.interval, 1), 

48 "SE3": (dolfinx.mesh.CellType.interval, 2), 

49 "TR3": (dolfinx.mesh.CellType.triangle, 1), 

50 "TR6": (dolfinx.mesh.CellType.triangle, 2), 

51 "TR7": (dolfinx.mesh.CellType.triangle, 2), 

52 "QU4": (dolfinx.mesh.CellType.quadrilateral, 1), 

53 "QU8": (dolfinx.mesh.CellType.quadrilateral, 2), 

54 "QU9": (dolfinx.mesh.CellType.quadrilateral, 2), 

55 "TE4": (dolfinx.mesh.CellType.tetrahedron, 1), 

56 "T10": (dolfinx.mesh.CellType.tetrahedron, 2), 

57 "PY5": (dolfinx.mesh.CellType.pyramid, 1), 

58 "P13": (dolfinx.mesh.CellType.pyramid, 2), 

59 "PE6": (dolfinx.mesh.CellType.prism, 1), 

60 "P15": (dolfinx.mesh.CellType.prism, 2), 

61 "P18": (dolfinx.mesh.CellType.prism, 2), 

62 "HE8": (dolfinx.mesh.CellType.hexahedron, 1), 

63 "H20": (dolfinx.mesh.CellType.hexahedron, 2), 

64 "H27": (dolfinx.mesh.CellType.hexahedron, 2), 

65} 

66 

67# Node permutation ``p`` with ``dolfinx[i] = med[p[i]]`` for each supported MED element 

68# type. Derived from MEDCoupling's (LGPL) MED cell model: the nodes it adds in 

69# ``MEDCouplingUMesh.convertLinearCellsToQuadratic`` and the face centres of 

70# ``buildDescendingConnectivity`` (for ``P18``), matched to the Basix reference points. 

71# ``test_med_permutations_match_medcoupling`` checks the table when medcoupling is 

72# installed. 

73_med_to_dolfinx = { 

74 "SE2": [0, 1], 

75 "SE3": [0, 1, 2], 

76 "TR3": [0, 1, 2], 

77 "TR6": [0, 1, 2, 4, 5, 3], 

78 "QU4": [0, 1, 3, 2], 

79 "QU8": [0, 1, 3, 2, 4, 7, 5, 6], 

80 "QU9": [0, 1, 3, 2, 4, 7, 5, 6, 8], 

81 "TE4": [0, 1, 2, 3], 

82 "T10": [0, 1, 2, 3, 9, 8, 5, 7, 6, 4], 

83 "PY5": [0, 1, 3, 2, 4], 

84 "PE6": [0, 1, 2, 3, 4, 5], 

85 "P18": [0, 1, 2, 3, 4, 5, 6, 8, 12, 7, 13, 14, 9, 11, 10, 15, 17, 16], 

86 "HE8": [0, 1, 3, 2, 4, 5, 7, 6], 

87 "H20": [0, 1, 3, 2, 4, 5, 7, 6, 8, 11, 16, 9, 17, 10, 19, 18, 12, 15, 13, 14], 

88 "H27": [0, 1, 3, 2, 4, 5, 7, 6, 8, 11, 16, 9, 17, 10, 19, 18, 12, 15, 13, 14] 

89 + [20, 21, 24, 22, 23, 25, 26], 

90} 

91 

92# Layouts with the nodal serendipity coordinate element 

93_med_serendipity = ("QU8", "H20") 

94 

95 

96def geometry_layout(med_type: str) -> tuple[dolfinx.mesh.CellType, int, bool]: 

97 """Get the coordinate element layout of a MED element type. 

98 

99 Args: 

100 med_type: MED element type, e.g. ``"T10"`` 

101 

102 Returns: 

103 Tuple containing: 

104 - The DOLFINx cell type 

105 - The degree of the coordinate element 

106 - True if the coordinate element is the nodal serendipity element 

107 (``QU8``, ``H20``), False if it is Lagrange 

108 """ 

109 cell_type, degree = _med_cell_types[med_type] 

110 return cell_type, degree, med_type in _med_serendipity 

111 

112 

113def get_mesh_step_and_families( 

114 h5file: h5py.File, 

115 filename: Path | str, 

116 backend_args: dict[str, Any] | None, 

117 time: str | float | None, 

118) -> tuple[h5py.Group, h5py.Group]: 

119 """Get the groups holding the nodes, connectivity and families of a mesh. 

120 

121 In DOLFINx terms, ``NOE`` (*nœuds*) holds the geometry nodes, i.e. the coordinates 

122 in ``mesh.geometry.x``, and ``MAI`` (*mailles*) the connectivity: one block per 

123 element type, listing the nodes of each cell, facet, edge or point element. 

124 

125 A time-dependent mesh has one group per computation step, named by its step and 

126 iteration numbers so that the names sort in time order. A step only stores data 

127 that changed, and links to the earlier step for the rest. 

128 

129 Args: 

130 h5file: The MED file 

131 filename: Path to the file, used in error messages 

132 backend_args: Arguments to backend. ``"name"`` selects the mesh; if it is not 

133 given and the file holds a single mesh, that mesh is used. 

134 time: Time of the step to read. Ignored for a mesh with a single step, and 

135 ``None`` selects the first step. 

136 

137 Returns: 

138 Tuple containing: 

139 - The step group, holding ``NOE`` and ``MAI`` 

140 - The families of the mesh (``FAS/<mesh>``) 

141 """ 

142 if "ENS_MAA" not in h5file.keys(): 

143 raise ValueError(f"Could not find a set of meshes (ENS_MAA) in {filename}.") 

144 meshes = sorted(h5file["ENS_MAA"].keys()) 

145 name = get_mesh_name(backend_args) 

146 if name not in meshes: 

147 if name == DEFAULT_MESH_NAME and len(meshes) == 1: 

148 name = meshes[0] 

149 else: 

150 raise KeyError(f"Could not find mesh '{name}' in {filename}. Available: {meshes}") 

151 mesh = h5file["ENS_MAA"][name] 

152 # Mesh type 0 is unstructured, 1 is a structured grid without connectivity 

153 if mesh.attrs.get("TYP", 0) != 0: 

154 raise NotImplementedError(f"Structured MED meshes are not supported ('{name}').") 

155 if "FAS" not in h5file.keys() or name not in h5file["FAS"].keys(): 

156 raise ValueError(f"Could not find the families (FAS) of mesh '{name}' in {filename}.") 

157 

158 if "NOE" in mesh.keys(): 

159 step = mesh 

160 else: 

161 steps = [mesh[key] for key in sorted(mesh.keys())] 

162 if len(steps) == 1 or time is None: 

163 step = steps[0] 

164 else: 

165 times = np.array([float(s.attrs["PDT"]) for s in steps]) 

166 match = np.flatnonzero(np.isclose(times, float(time))) 

167 if len(match) != 1: 

168 raise KeyError( 

169 f"Expected one step of mesh '{name}' at time {time} in {filename}, found " 

170 f"{len(match)}. Available times: {times.tolist()}" 

171 ) 

172 step = steps[match[0]] 

173 if unsupported := sorted(set(step["MAI"].keys()) - _med_cell_types.keys()): 

174 raise NotImplementedError(f"MED element types {unsupported} are not supported.") 

175 return step, h5file["FAS"][name] 

176 

177 

178def read_entities( 

179 element_blocks: h5py.Group, dim: int, comm: MPI.Comm 

180) -> tuple[str, list[int], npt.NDArray[np.int64]] | None: 

181 """Read the entities of one topological dimension, distributed over all processes. 

182 

183 Args: 

184 element_blocks: The group holding one connectivity block per element type 

185 (``MAI`` in the MED file) 

186 dim: Topological dimension of the entities 

187 comm: MPI communicator to distribute the entities over 

188 

189 Returns: 

190 ``None`` if the mesh has no entities of dimension ``dim``, otherwise a tuple 

191 containing: 

192 - The MED element type of the entities, e.g. ``"TR3"`` 

193 - The range ``[start, end)`` of entities read on this process 

194 - Their connectivity as global input node indices in DOLFINx node order, 

195 of shape ``(end - start, num_nodes_per_entity)`` 

196 """ 

197 blocks = sorted( 

198 key 

199 for key in element_blocks.keys() 

200 if dolfinx.mesh.cell_dim(_med_cell_types[key][0]) == dim 

201 ) 

202 if len(blocks) == 0: 

203 return None 

204 if len(blocks) > 1: 

205 raise NotImplementedError( 

206 f"Mixed element types {blocks} of dimension {dim} are not supported." 

207 ) 

208 med_type = blocks[0] 

209 block = element_blocks[med_type] 

210 

211 # Read a contiguous chunk of the entities on each process 

212 num_entities_global = num_entities(block) 

213 local_range = compute_local_range(comm, num_entities_global) 

214 nodes = read_component_major_rows(block["NOD"], num_entities_global, local_range) 

215 

216 # MED node indices are 1-based 

217 connectivity = nodes.astype(np.int64) - 1 

218 

219 # Permute the nodes of each entity from MED to DOLFINx ordering 

220 if med_type not in _med_to_dolfinx: 

221 raise NotImplementedError( 

222 f"MED element type {med_type} has no Lagrange or nodal serendipity layout." 

223 ) 

224 perm = _med_to_dolfinx[med_type] 

225 if connectivity.shape[1] != len(perm): 

226 raise ValueError(f"Expected {len(perm)} nodes per {med_type}, got {connectivity.shape[1]}.") 

227 return med_type, local_range, connectivity[:, perm] 

228 

229 

230def read_entity_families(block: h5py.Group, local_range: list[int]) -> npt.NDArray[np.int32]: 

231 """Read the family id of each entity of one MED element type on this process. 

232 

233 Each entity has exactly one family, so this is a tag value per entity. Which 

234 groups a family belongs to is read with :func:`read_family_groups`. 

235 

236 Args: 

237 block: Connectivity block of the element type (``MAI/<med_type>``) 

238 local_range: Range ``[start, end)`` of entities to read on this process 

239 

240 Returns: 

241 Family ids of shape ``(end - start,)``. A block without ``FAM`` has every 

242 entity in family 0, i.e. in no group. 

243 """ 

244 if "FAM" in block.keys(): 

245 return block["FAM"][slice(*local_range)].astype(np.int32) 

246 return np.zeros(local_range[1] - local_range[0], dtype=np.int32) 

247 

248 

249def num_entities(block: h5py.Group) -> int: 

250 """Get the global number of entities of one MED element type. 

251 

252 Args: 

253 block: Connectivity block of the element type (``MAI/<med_type>``) 

254 

255 Returns: 

256 The number of entities, stored as the ``NBR`` attribute of the connectivity 

257 """ 

258 return int(block["NOD"].attrs["NBR"]) 

259 

260 

261def read_family_groups( 

262 families: h5py.Group, kind: Literal["ELEME", "NOEUD"] 

263) -> dict[int, list[str]]: 

264 """Read which named groups each family of a mesh belongs to. 

265 

266 A family is one combination of groups, so one family can belong to several 

267 groups. The family of each entity is read with :func:`read_entity_families`. 

268 

269 Args: 

270 families: The families of the mesh (``FAS/<mesh>``) 

271 kind: ``"ELEME"`` (*éléments*) for element families, ``"NOEUD"`` (*nœuds*) 

272 for node families 

273 

274 Returns: 

275 Map from family id to the names of its groups. Empty if the mesh has no 

276 groups of this kind, in which case MED does not write ``kind``. 

277 """ 

278 family_groups: dict[int, list[str]] = {} 

279 if kind not in families.keys(): 

280 return family_groups 

281 for family in families[kind].values(): 

282 groups = [] 

283 if "GRO" in family.keys(): 

284 for row in family["GRO"]["NOM"][()]: 

285 raw = np.asarray(row).astype(np.uint8).tobytes() 

286 groups.append(raw.split(b"\x00")[0].decode().rstrip()) 

287 family_groups[int(family.attrs["NUM"])] = groups 

288 return family_groups 

289 

290 

291def read_groups( 

292 filename: Path | str, comm: MPI.Comm, backend_args: dict[str, Any] | None = None 

293) -> dict[str, list[int]]: 

294 """Read the element groups of a MED mesh and the families that make them up. 

295 

296 Use this to find the group names accepted by :func:`read_meshtags_data`, or to 

297 decode the (negated) family ids returned for ``"cell"`` and ``"facet"``. 

298 

299 Args: 

300 filename: Path to file 

301 comm: MPI communicator used in storage 

302 backend_args: Arguments to backend, ``"name"`` selects the mesh 

303 

304 Returns: 

305 Map from group name to the negated family ids that belong to the group 

306 """ 

307 check_file_exists(filename) 

308 with h5pyfile(filename, "r", comm=comm) as h5file: 

309 _, families = get_mesh_step_and_families(h5file, filename, backend_args, None) 

310 family_groups = read_family_groups(families, "ELEME") 

311 groups: dict[str, list[int]] = {} 

312 for family, names in sorted(family_groups.items(), reverse=True): 

313 for group in names: 

314 groups.setdefault(group, []).append(-family) 

315 return groups 

316 

317 

318def get_default_backend_args(arguments: dict[str, Any] | None) -> dict[str, Any]: 

319 args = arguments or {} 

320 return args 

321 

322 

323def write_attributes( 

324 filename: Path | str, 

325 comm: MPI.Comm, 

326 name: str, 

327 attributes: dict[str, np.ndarray], 

328 backend_args: dict[str, Any] | None = None, 

329): 

330 """Write attributes to file. 

331 

332 Args: 

333 filename: Path to file to write to 

334 comm: MPI communicator used in storage 

335 name: Name of the attributes 

336 attributes: Dictionary of attributes to write to file 

337 backend_args: Arguments to backend 

338 """ 

339 raise NotImplementedError("The MED backend cannot write attributes.") 

340 

341 

342def read_attributes( 

343 filename: Path | str, 

344 comm: MPI.Comm, 

345 name: str, 

346 backend_args: dict[str, Any] | None = None, 

347) -> dict[str, Any]: 

348 """Read attributes from file. 

349 

350 Args: 

351 filename: Path to file to read from 

352 comm: MPI communicator used in storage 

353 name: Name of the attributes 

354 backend_args: Arguments to backend 

355 Returns: 

356 The attributes 

357 """ 

358 raise NotImplementedError("The MED backend cannot read attributes.") 

359 

360 

361def snapshot_checkpoint( 

362 filename: Path | str, 

363 mode: FileMode, 

364 u: dolfinx.fem.Function, 

365 backend_args: dict[str, Any] | None, 

366): 

367 """Create a snapshot checkpoint of a dolfinx function. 

368 

369 Args: 

370 filename: Path to file to read from 

371 mode: File-mode to store the function 

372 u: dolfinx function to create a snapshot checkpoint for 

373 backend_args: Arguments to backend 

374 """ 

375 raise NotImplementedError("The MED backend cannot make checkpoints.") 

376 

377 

378def read_hdf5_array( 

379 comm: MPI.Comm, 

380 filename: Path | str, 

381 group: str, 

382 backend_args: dict[str, Any] | None, 

383) -> tuple[np.ndarray, int]: 

384 """Read an array from an HDF5 file. 

385 

386 Args: 

387 comm: MPI communicator used in storage 

388 filename: Path to file to read from 

389 group: Group in HDF5 file where array is stored 

390 backend_args: Arguments to backend 

391 

392 Returns: 

393 Tuple containing: 

394 - Numpy array read from file 

395 - Global starting point on the process. 

396 Process 0 has [0, M), process 1 [M, N), process 2 [N, O) etc. 

397 """ 

398 raise NotImplementedError("The MED backend cannot read HDF5 arrays.") 

399 

400 

401def read_timestamps( 

402 filename: Path | str, 

403 comm: MPI.Comm, 

404 function_name: str, 

405 backend_args: dict[str, Any] | None = None, 

406) -> npt.NDArray[np.float64 | str]: # type: ignore[type-var] 

407 """Read time-stamps from a checkpoint file. 

408 

409 Args: 

410 filename: Path to file 

411 comm: MPI communicator 

412 function_name: Name of the function to read time-stamps for 

413 backend_args: Arguments for backend, for instance file type. 

414 

415 Returns: 

416 The time-stamps 

417 """ 

418 raise NotImplementedError("The MED backend cannot read timestamps.") 

419 

420 

421def write_mesh( 

422 filename: Path | str, 

423 comm: MPI.Comm, 

424 mesh: MeshData, 

425 backend_args: dict[str, Any] | None = None, 

426 mode: FileMode = FileMode.write, 

427 time: float = 0.0, 

428): 

429 """Write a mesh to file. 

430 

431 Args: 

432 filename: Path to file to write to. 

433 mesh: Internal data structure for the mesh data to save to file 

434 comm: MPI communicator used in storage 

435 backend_args: Arguments to backend 

436 mode: Mode to use (write or append) 

437 time: Time stamp 

438 """ 

439 raise NotImplementedError("The MED backend cannot write meshes.") 

440 

441 

442def read_mesh_data( 

443 filename: Path | str, 

444 comm: MPI.Comm, 

445 time: str | float | None = 0.0, 

446 read_from_partition: bool = False, 

447 backend_args: dict[str, Any] | None = None, 

448) -> ReadMeshData: 

449 """Read mesh data from a MED file. 

450 

451 Only cells of the highest topological dimension are read; these must all be of 

452 the same type. Lagrange layouts up to degree 2 (e.g. ``TR6``, ``T10``, ``H27``) and 

453 the serendipity ``QU8`` and ``H20`` are supported. Node and cell data is read in 

454 contiguous chunks on each process. 

455 

456 Args: 

457 filename: Path to input file 

458 comm: The MPI communciator to distribute the mesh over 

459 time: Time of the step to read from a time-dependent mesh. Ignored for a mesh 

460 with a single step, and ``None`` selects the first step. 

461 read_from_partition: Read mesh with partition from file (not supported) 

462 backend_args: Arguments to backend, ``"name"`` selects the mesh 

463 Returns: 

464 The mesh topology, geometry, UFL domain and partition function 

465 """ 

466 if read_from_partition: 

467 raise NotImplementedError("The MED backend cannot read partitioning data.") 

468 check_file_exists(filename) 

469 with h5pyfile(filename, "r", comm=comm) as h5file: 

470 step, _ = get_mesh_step_and_families(h5file, filename, backend_args, time) 

471 coo = step["NOE"]["COO"] 

472 num_nodes = int(coo.attrs["NBR"]) 

473 x = read_component_major_rows(coo, num_nodes, compute_local_range(comm, num_nodes)) 

474 

475 element_blocks = step["MAI"] 

476 tdim = max(dolfinx.mesh.cell_dim(_med_cell_types[key][0]) for key in element_blocks.keys()) 

477 entities = read_entities(element_blocks, tdim, comm) 

478 assert entities is not None 

479 med_type, _, cells = entities 

480 cell_type, degree, serendipity = geometry_layout(med_type) 

481 return ReadMeshData( 

482 cells=cells, 

483 cell_type=dolfinx.mesh.to_string(cell_type), 

484 x=x, 

485 lvar=int(basix.LagrangeVariant.equispaced), 

486 degree=degree, 

487 partition_graph=None, 

488 serendipity=serendipity, 

489 ) 

490 

491 

492def write_meshtags( 

493 filename: str | Path, 

494 comm: MPI.Comm, 

495 data: MeshTagsData, 

496 backend_args: dict[str, Any] | None = None, 

497): 

498 """Write mesh tags to file. 

499 

500 Args: 

501 filename: Path to file to write to 

502 comm: MPI communicator used in storage 

503 data: Internal data structure for the mesh tags to save to file 

504 backend_args: Arguments to backend 

505 """ 

506 raise NotImplementedError("The MED backend cannot write meshtags.") 

507 

508 

509def read_meshtags_data( 

510 filename: str | Path, 

511 comm: MPI.Comm, 

512 name: str, 

513 backend_args: dict[str, Any] | None = None, 

514) -> MeshTagsData: 

515 """Read mesh tags from a MED file. 

516 

517 Args: 

518 filename: Path to file to read from 

519 comm: MPI communicator used in storage 

520 name: ``"cell"`` or ``"facet"`` for the entities of the highest topological 

521 dimension or one below, otherwise the name of a MED group (see 

522 :func:`read_groups`) 

523 backend_args: Arguments to backend, ``"name"`` selects the mesh and ``"time"`` the 

524 step of a time-dependent mesh (the first step if not given) 

525 

526 Returns: 

527 Internal data structure for the mesh tags read from file. For ``"cell"`` and 

528 ``"facet"``, each entity is tagged with its negated MED family id, and entities 

529 without a family (id 0) are skipped. For a group, its entities are tagged with 

530 ``1``. 

531 """ 

532 check_file_exists(filename) 

533 with h5pyfile(filename, "r", comm=comm) as h5file: 

534 time = None if backend_args is None else backend_args.get("time") 

535 step, families = get_mesh_step_and_families(h5file, filename, backend_args, time) 

536 element_blocks = step["MAI"] 

537 keys = sorted(element_blocks.keys()) 

538 tdim = max(dolfinx.mesh.cell_dim(_med_cell_types[key][0]) for key in keys) 

539 group_families: list[int] | None = None 

540 if name in ("cell", "facet"): 

541 dim = tdim if name == "cell" else tdim - 1 

542 else: 

543 family_groups = read_family_groups(families, "ELEME") 

544 group_families = [f for f, groups in family_groups.items() if name in groups] 

545 if len(group_families) == 0: 

546 nodes = read_family_groups(families, "NOEUD") 

547 if any(name in groups for groups in nodes.values()): 

548 raise NotImplementedError(f"Group '{name}' only holds nodes.") 

549 available = sorted({g for groups in family_groups.values() for g in groups}) 

550 raise KeyError( 

551 f"Could not find group '{name}' in {filename}. Available: {available}" 

552 ) 

553 

554 # Find the blocks holding entities of the group. FAS does not record which 

555 # element type a family is used on, and each process only reads its chunk 

556 # of every block, so the reduction is needed for all processes to agree on 

557 # the blocks and dimension before the collective meshtag creation. 

558 in_block = np.zeros(len(keys), dtype=np.int32) 

559 for i, key in enumerate(keys): 

560 r = compute_local_range(comm, num_entities(element_blocks[key])) 

561 in_block[i] = np.isin( 

562 read_entity_families(element_blocks[key], r), group_families 

563 ).any() 

564 comm.Allreduce(MPI.IN_PLACE, in_block, op=MPI.MAX) 

565 blocks = [key for key, found in zip(keys, in_block) if found] 

566 dims = {dolfinx.mesh.cell_dim(_med_cell_types[key][0]) for key in blocks} 

567 if len(dims) != 1: 

568 raise RuntimeError( 

569 f"Group '{name}' must hold entities of a single dimension, got {dims}." 

570 ) 

571 dim = dims.pop() 

572 

573 entities = read_entities(element_blocks, dim, comm) 

574 if entities is None: 

575 # No entities of this dimension; read_meshtags sets the number of nodes 

576 return MeshTagsData( 

577 name=name, 

578 values=np.zeros(0, dtype=np.int32), 

579 indices=np.zeros((0, 0), dtype=np.int64), 

580 dim=dim, 

581 ) 

582 med_type, local_range, connectivity = entities 

583 family = read_entity_families(element_blocks[med_type], local_range) 

584 

585 if group_families is None: 

586 keep = family != 0 

587 values = -family[keep] 

588 else: 

589 keep = np.isin(family, group_families) 

590 values = np.ones(np.count_nonzero(keep), dtype=np.int32) 

591 return MeshTagsData(name=name, values=values, indices=connectivity[keep], dim=dim) 

592 

593 

594def read_dofmap( 

595 filename: str | Path, 

596 comm: MPI.Comm, 

597 name: str, 

598 backend_args: dict[str, Any] | None, 

599) -> dolfinx.graph.AdjacencyList: 

600 """Read the dofmap of a function with a given name. 

601 

602 Args: 

603 filename: Path to file to read from 

604 comm: MPI communicator used in storage 

605 name: Name of the function to read the dofmap for 

606 backend_args: Arguments to backend 

607 

608 Returns: 

609 Dofmap as an :class:`dolfinx.graph.AdjacencyList` 

610 """ 

611 raise NotImplementedError("The MED backend cannot read dofmap.") 

612 

613 

614def read_dofs( 

615 filename: str | Path, 

616 comm: MPI.Comm, 

617 name: str, 

618 time: float, 

619 backend_args: dict[str, Any] | None, 

620) -> tuple[npt.NDArray[np.float32 | np.float64 | np.complex64 | np.complex128], int]: 

621 """Read the dofs (values) of a function with a given name from a given timestep. 

622 

623 Args: 

624 filename: Path to file to read from 

625 comm: MPI communicator used in storage 

626 name: Name of the function to read the dofs for 

627 time: Time stamp associated with the function to read 

628 backend_args: Arguments to backend 

629 

630 Returns: 

631 Contiguous sequence of degrees of freedom (with respect to input data) 

632 and the global starting point on the process. 

633 Process 0 has [0, M), process 1 [M, N), process 2 [N, O) etc. 

634 """ 

635 raise NotImplementedError("The MED backend cannot read dofs.") 

636 

637 

638def read_cell_perms( 

639 comm: MPI.Comm, filename: Path | str, backend_args: dict[str, Any] | None 

640) -> npt.NDArray[np.uint32]: 

641 """ 

642 Read cell permutation from file with given communicator, 

643 Split in continuous chunks based on number of cells in the input data. 

644 

645 Args: 

646 comm: MPI communicator used in storage 

647 filename: Path to file to read from 

648 backend_args: Arguments to backend 

649 

650 Returns: 

651 Contiguous sequence of permutations (with respect to input data) 

652 Process 0 has [0, M), process 1 [M, N), process 2 [N, O) etc. 

653 """ 

654 raise NotImplementedError("The MED backend cannot read cell perms.") 

655 

656 

657def write_function( 

658 filename: str | Path, 

659 comm: MPI.Comm, 

660 u: FunctionData, 

661 time: float, 

662 mode: FileMode, 

663 backend_args: dict[str, Any] | None = None, 

664): 

665 """Write a function to file. 

666 

667 Args: 

668 filename: Path to file to write to 

669 comm: MPI communicator used in storage 

670 u: Internal data structure for the function data to save to file 

671 time: Time stamp associated with function 

672 mode: File-mode to store the function 

673 backend_args: Arguments to backend 

674 """ 

675 raise NotImplementedError("The MED backend cannot write functions.") 

676 

677 

678def read_legacy_mesh( 

679 filename: Path | str, comm: MPI.Comm, group: str 

680) -> tuple[npt.NDArray[np.int64], npt.NDArray[np.floating], str | None]: 

681 """Read in the mesh topology, geometry and (optionally) cell type from a 

682 legacy DOLFIN HDF5-file. 

683 

684 Args: 

685 filename: Path to file to read from 

686 comm: MPI communicator used in storage 

687 group: Group in HDF5 file where mesh is stored 

688 

689 Returns: 

690 Tuple containing: 

691 - Topology as a (num_cells, num_vertices_per_cell) array of global vertex indices 

692 - Geometry as a (num_vertices, geometric_dimension) array of vertex coordinates 

693 - Cell type as a string (e.g. "tetrahedron") or None if not found 

694 """ 

695 raise NotImplementedError("The MED backend cannot read legacy meshes.") 

696 

697 

698def read_point_data( 

699 filename: Path | str, 

700 name: str, 

701 comm: MPI.Comm, 

702 time: float | str | None, 

703 backend_args: dict[str, Any] | None, 

704) -> tuple[np.ndarray, int]: 

705 """Read data from the nodes of a mesh. 

706 

707 Args: 

708 filename: Path to file 

709 name: Name of point data 

710 comm: Communicator to launch IO on. 

711 time: The time stamp 

712 backend_args: The backend arguments 

713 

714 Returns: 

715 Data local to process (contiguous, no mpi comm) and local start range 

716 """ 

717 raise NotImplementedError("The MED backend cannot read point data.") 

718 

719 

720def read_cell_data( 

721 filename: Path | str, 

722 name: str, 

723 comm: MPI.Comm, 

724 time: str | float | None, 

725 backend_args: dict[str, Any] | None, 

726) -> tuple[npt.NDArray[np.int64], np.ndarray]: 

727 """Read data from the cells of a mesh. 

728 

729 Args: 

730 filename: Path to file 

731 name: Name of point data 

732 comm: Communicator to launch IO on. 

733 time: The time stamp 

734 backend_args: The backend arguments 

735 Returns: 

736 A tuple (topology, dofs) where topology contains the 

737 vertex indices of the cells, dofs the degrees of 

738 freedom within that cell. 

739 """ 

740 raise NotImplementedError("The MED backend cannot read cell data.") 

741 

742 

743def read_function_names( 

744 filename: Path | str, comm: MPI.Comm, backend_args: dict[str, Any] | None 

745) -> list[str]: 

746 """Read all function names from a file. 

747 

748 Args: 

749 filename: Path to file 

750 comm: MPI communicator to launch IO on. 

751 backend_args: Arguments to backend 

752 

753 Returns: 

754 A list of function names. 

755 """ 

756 raise NotImplementedError("The MED backend cannot read function names.") 

757 

758 

759def write_data( 

760 filename: Path | str, 

761 array_data: ArrayData, 

762 comm: MPI.Comm, 

763 time: str | float | None, 

764 mode: FileMode, 

765 backend_args: dict[str, Any] | None, 

766): 

767 """Write a 2D-array to file (distributed across proceses with MPI). 

768 

769 Args: 

770 filename: Path to file 

771 array_data: Data to write to file 

772 comm: MPI communicator to open the file with 

773 time: Time-stamp for data. 

774 mode: Append or write 

775 backend_args: The backend arguments 

776 """ 

777 raise NotImplementedError("The MED backend cannot write data.")