Coverage for  / dolfinx-env / lib / python3.12 / site-packages / io4dolfinx / backends / adios2 / backend.py: 93%

381 statements  

« prev     ^ index     » next       coverage.py v7.13.4, created at 2026-02-26 18:16 +0000

1import warnings 

2from pathlib import Path 

3from typing import Any 

4 

5from mpi4py import MPI 

6 

7import adios2 

8import dolfinx 

9import numpy as np 

10import numpy.typing as npt 

11 

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

13from ...utils import check_file_exists, compute_local_range 

14from .. import FileMode, ReadMode 

15from .helpers import ( 

16 ADIOSFile, 

17 adios_to_numpy_dtype, 

18 check_variable_exists, 

19 read_adjacency_list, 

20 read_array, 

21 resolve_adios_scope, 

22) 

23 

24adios2 = resolve_adios_scope(adios2) 

25 

26read_mode = ReadMode.parallel 

27 

28 

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

30 """Get default arguements (sets engine to BP4).""" 

31 args = arguments or {} 

32 if "engine" not in args.keys(): 

33 args["engine"] = "BP4" 

34 if "legacy" not in args.keys(): 

35 args["legacy"] = False # Only used for legacy HDF5 meshtags 

36 return args 

37 

38 

39def convert_file_mode(mode: FileMode) -> adios2.Mode: # type: ignore[override] 

40 match mode: 

41 case FileMode.append: 

42 return adios2.Mode.Append 

43 case FileMode.write: 

44 return adios2.Mode.Write 

45 case FileMode.read: 

46 return adios2.Mode.Read 

47 case _: 

48 raise NotImplementedError(f"FileMode {mode} not implemented.") 

49 

50 

51def write_attributes( 

52 filename: Path | str, 

53 comm: MPI.Intracomm, 

54 name: str, 

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

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

57): 

58 """Write attributes to file using ADIOS2. 

59 

60 Args: 

61 filename: Path to file to write to 

62 comm: MPI communicator used in storage 

63 name: Name of the attributes 

64 attributes: Dictionary of attributes to write to file 

65 engine: ADIOS2 engine to use 

66 """ 

67 

68 adios = adios2.ADIOS(comm) 

69 backend_args = get_default_backend_args(backend_args) 

70 

71 with ADIOSFile( 

72 adios=adios, 

73 filename=filename, 

74 mode=adios2.Mode.Append, 

75 io_name="AttributeWriter", 

76 engine=backend_args["engine"], 

77 ) as adios_file: 

78 adios_file.file.BeginStep() 

79 

80 for k, v in attributes.items(): 

81 adios_file.io.DefineAttribute(f"{name}_{k}", v) 

82 

83 adios_file.file.PerformPuts() 

84 adios_file.file.EndStep() 

85 

86 

87def read_attributes( 

88 filename: Path | str, 

89 comm: MPI.Intracomm, 

90 name: str, 

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

92) -> dict[str, np.ndarray]: 

93 """Read attributes from file using ADIOS2. 

94 

95 Args: 

96 filename: Path to file to read from 

97 comm: MPI communicator used in storage 

98 name: Name of the attributes 

99 engine: ADIOS2 engine to use 

100 Returns: 

101 The attributes 

102 """ 

103 check_file_exists(filename) 

104 adios = adios2.ADIOS(comm) 

105 backend_args = get_default_backend_args(backend_args) 

106 with ADIOSFile( 

107 adios=adios, 

108 filename=filename, 

109 mode=adios2.Mode.Read, 

110 engine=backend_args["engine"], 

111 io_name="AttributesReader", 

112 ) as adios_file: 

113 adios_file.file.BeginStep() 

114 attributes = {} 

115 for k in adios_file.io.AvailableAttributes().keys(): 

116 if k.startswith(f"{name}_"): 

117 a = adios_file.io.InquireAttribute(k) 

118 attributes[k[len(name) + 1 :]] = a.Data() 

119 adios_file.file.EndStep() 

120 return attributes 

121 

122 

123def read_timestamps( 

124 filename: Path | str, 

125 comm: MPI.Intracomm, 

126 function_name: str, 

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

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

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

130 

131 Args: 

132 comm: MPI communicator 

133 filename: Path to file 

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

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

136 backend: What backend to use for writing. 

137 Returns: 

138 The time-stamps 

139 """ 

140 check_file_exists(filename) 

141 

142 adios = adios2.ADIOS(comm) 

143 backend_args = get_default_backend_args(backend_args) 

144 with ADIOSFile( 

145 adios=adios, 

146 filename=filename, 

147 mode=adios2.Mode.Read, 

148 engine=backend_args["engine"], 

149 io_name="TimestepReader", 

150 ) as adios_file: 

151 time_name = f"{function_name}_time" 

152 time_stamps = [] 

153 for _ in range(adios_file.file.Steps()): 

154 adios_file.file.BeginStep() 

155 if time_name in adios_file.io.AvailableVariables().keys(): 

156 arr = adios_file.io.InquireVariable(time_name) 

157 time_shape = arr.Shape() 

158 arr.SetSelection([[0], [time_shape[0]]]) 

159 times = np.empty( 

160 time_shape[0], 

161 dtype=adios_to_numpy_dtype[arr.Type()], 

162 ) 

163 adios_file.file.Get(arr, times, adios2.Mode.Sync) 

164 time_stamps.append(times[0]) 

165 adios_file.file.EndStep() 

166 

167 return np.array(time_stamps) 

168 

169 

170def write_mesh( 

171 filename: Path | str, 

172 comm: MPI.Intracomm, 

173 mesh: MeshData, 

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

175 mode: FileMode = FileMode.write, 

176 time: float = 0.0, 

177): 

178 """Write a mesh to file using ADIOS2. 

179 

180 Args: 

181 comm: MPI communicator used in storage 

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

183 filename: Path to file to write to 

184 backend_args: File mode and potentially the io-name. 

185 mode: Mode to use (write or append) 

186 time: Time stamp 

187 """ 

188 backend_args = get_default_backend_args(backend_args) 

189 if "io_name" not in backend_args.keys(): 

190 backend_args["io_name"] = "MeshWriter" 

191 

192 mode = convert_file_mode(mode) 

193 gdim = mesh.local_geometry.shape[1] 

194 adios = adios2.ADIOS(comm) 

195 with ADIOSFile( 

196 adios=adios, 

197 filename=filename, 

198 mode=mode, 

199 comm=comm, 

200 engine=backend_args["engine"], 

201 io_name=backend_args["io_name"], 

202 ) as adios_file: 

203 adios_file.file.BeginStep() 

204 # Write geometry 

205 pointvar = adios_file.io.DefineVariable( 

206 "Points", 

207 mesh.local_geometry, 

208 shape=[mesh.num_nodes_global, gdim], 

209 start=[mesh.local_geometry_pos[0], 0], 

210 count=[mesh.local_geometry_pos[1] - mesh.local_geometry_pos[0], gdim], 

211 ) 

212 adios_file.file.Put(pointvar, mesh.local_geometry, adios2.Mode.Sync) 

213 

214 if mode == adios2.Mode.Write: 

215 adios_file.io.DefineAttribute("CellType", mesh.cell_type) 

216 adios_file.io.DefineAttribute("Degree", np.array([mesh.degree], dtype=np.int32)) 

217 adios_file.io.DefineAttribute( 

218 "LagrangeVariant", np.array([mesh.lagrange_variant], dtype=np.int32) 

219 ) 

220 # Write topology (on;y on first write as topology is constant) 

221 num_dofs_per_cell = mesh.local_topology.shape[1] 

222 dvar = adios_file.io.DefineVariable( 

223 "Topology", 

224 mesh.local_topology, 

225 shape=[mesh.num_cells_global, num_dofs_per_cell], 

226 start=[mesh.local_topology_pos[0], 0], 

227 count=[ 

228 mesh.local_topology_pos[1] - mesh.local_topology_pos[0], 

229 num_dofs_per_cell, 

230 ], 

231 ) 

232 adios_file.file.Put(dvar, mesh.local_topology) 

233 

234 # Add partitioning data 

235 if mesh.store_partition: 

236 assert mesh.partition_range is not None 

237 par_data = adios_file.io.DefineVariable( 

238 "PartitioningData", 

239 mesh.ownership_array, 

240 shape=[mesh.partition_global], 

241 start=[mesh.partition_range[0]], 

242 count=[ 

243 mesh.partition_range[1] - mesh.partition_range[0], 

244 ], 

245 ) 

246 adios_file.file.Put(par_data, mesh.ownership_array) 

247 assert mesh.ownership_offset is not None 

248 par_offset = adios_file.io.DefineVariable( 

249 "PartitioningOffset", 

250 mesh.ownership_offset, 

251 shape=[mesh.num_cells_global + 1], 

252 start=[mesh.local_topology_pos[0]], 

253 count=[mesh.local_topology_pos[1] - mesh.local_topology_pos[0] + 1], 

254 ) 

255 adios_file.file.Put(par_offset, mesh.ownership_offset) 

256 assert mesh.partition_processes is not None 

257 adios_file.io.DefineAttribute( 

258 "PartitionProcesses", np.array([mesh.partition_processes], dtype=np.int32) 

259 ) 

260 if mode == adios2.Mode.Append and mesh.store_partition: 

261 warnings.warn("Partitioning data is not written in append mode") 

262 

263 # Add time step to file 

264 t_arr = np.array([time], dtype=np.float64) 

265 time_var = adios_file.io.DefineVariable( 

266 "MeshTime", 

267 t_arr, 

268 shape=[1], 

269 start=[0], 

270 count=[1 if comm.rank == 0 else 0], 

271 ) 

272 adios_file.file.Put(time_var, t_arr) 

273 

274 adios_file.file.PerformPuts() 

275 adios_file.file.EndStep() 

276 

277 

278def read_mesh_data( 

279 filename: Path | str, 

280 comm: MPI.Intracomm, 

281 time: str | float | None = 0.0, 

282 read_from_partition: bool = False, 

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

284) -> ReadMeshData: 

285 """Read an ADIOS2 mesh data for use with DOLFINx. 

286 

287 Args: 

288 filename: Path to input file 

289 comm: The MPI communciator to distribute the mesh over 

290 engine: ADIOS engine to use for reading (BP4, BP5 or HDF5) 

291 time: Time stamp associated with mesh 

292 legacy: If checkpoint was made prior to time-dependent mesh-writer set to True 

293 read_from_partition: Read mesh with partition from file 

294 Returns: 

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

296 """ 

297 

298 adios = adios2.ADIOS(comm) 

299 backend_args = get_default_backend_args(backend_args) 

300 legacy = backend_args.get("legacy", False) 

301 io_name = backend_args.get("io_name", "MeshReader") 

302 engine = backend_args["engine"] 

303 with ADIOSFile( 

304 adios=adios, 

305 filename=filename, 

306 mode=adios2.Mode.Read, 

307 engine=engine, 

308 io_name=io_name, 

309 ) as adios_file: 

310 # Get time independent mesh variables (mesh topology and cell type info) first 

311 adios_file.file.BeginStep() 

312 # Get mesh topology (distributed) 

313 if "Topology" not in adios_file.io.AvailableVariables().keys(): 

314 raise KeyError(f"Mesh topology not found at Topology in {filename}") 

315 topology = adios_file.io.InquireVariable("Topology") 

316 shape = topology.Shape() 

317 local_range = compute_local_range(comm, shape[0]) 

318 topology.SetSelection([[local_range[0], 0], [local_range[1] - local_range[0], shape[1]]]) 

319 mesh_topology = np.empty((local_range[1] - local_range[0], shape[1]), dtype=np.int64) 

320 adios_file.file.Get(topology, mesh_topology, adios2.Mode.Deferred) 

321 

322 # Check validity of partitioning information 

323 if read_from_partition: 

324 if "PartitionProcesses" not in adios_file.io.AvailableAttributes().keys(): 

325 raise KeyError(f"Partitioning information not found in {filename}") 

326 par_num_procs = adios_file.io.InquireAttribute("PartitionProcesses") 

327 num_procs = par_num_procs.Data()[0] 

328 if num_procs != comm.size: 

329 raise ValueError(f"Number of processes in file ({num_procs})!=({comm.size=})") 

330 

331 # Get mesh cell type 

332 if "CellType" not in adios_file.io.AvailableAttributes().keys(): 

333 raise KeyError(f"Mesh cell type not found at CellType in {filename}") 

334 celltype = adios_file.io.InquireAttribute("CellType") 

335 cell_type = celltype.DataString()[0] 

336 

337 # Get basix info 

338 if "LagrangeVariant" not in adios_file.io.AvailableAttributes().keys(): 

339 raise KeyError(f"Mesh LagrangeVariant not found in {filename}") 

340 lvar = adios_file.io.InquireAttribute("LagrangeVariant").Data()[0] 

341 if "Degree" not in adios_file.io.AvailableAttributes().keys(): 

342 raise KeyError(f"Mesh degree not found in {filename}") 

343 degree = adios_file.io.InquireAttribute("Degree").Data()[0] 

344 

345 if not legacy: 

346 time_name = "MeshTime" 

347 for i in range(adios_file.file.Steps()): 

348 if i > 0: 

349 adios_file.file.BeginStep() 

350 if time_name in adios_file.io.AvailableVariables().keys(): 

351 arr = adios_file.io.InquireVariable(time_name) 

352 time_shape = arr.Shape() 

353 arr.SetSelection([[0], [time_shape[0]]]) 

354 times = np.empty(time_shape[0], dtype=adios_to_numpy_dtype[arr.Type()]) 

355 adios_file.file.Get(arr, times, adios2.Mode.Sync) 

356 if times[0] == time: 

357 break 

358 if i == adios_file.file.Steps() - 1: 

359 raise KeyError( 

360 f"No data associated with {time_name}={time} found in {filename}" 

361 ) 

362 

363 adios_file.file.EndStep() 

364 

365 if time_name not in adios_file.io.AvailableVariables().keys(): 

366 raise KeyError(f"No data associated with {time_name}={time} found in {filename}") 

367 

368 # Get mesh geometry 

369 if "Points" not in adios_file.io.AvailableVariables().keys(): 

370 raise KeyError(f"Mesh coordinates not found at Points in {filename}") 

371 geometry = adios_file.io.InquireVariable("Points") 

372 x_shape = geometry.Shape() 

373 geometry_range = compute_local_range(comm, x_shape[0]) 

374 geometry.SetSelection( 

375 [ 

376 [geometry_range[0], 0], 

377 [geometry_range[1] - geometry_range[0], x_shape[1]], 

378 ] 

379 ) 

380 mesh_geometry = np.empty( 

381 (geometry_range[1] - geometry_range[0], x_shape[1]), 

382 dtype=adios_to_numpy_dtype[geometry.Type()], 

383 ) 

384 adios_file.file.Get(geometry, mesh_geometry, adios2.Mode.Deferred) 

385 adios_file.file.PerformGets() 

386 adios_file.file.EndStep() 

387 

388 if read_from_partition: 

389 partition_graph = read_adjacency_list( 

390 adios, 

391 comm, 

392 filename, 

393 "PartitioningData", 

394 "PartitioningOffset", 

395 backend_args["engine"], 

396 ) 

397 else: 

398 partition_graph = None 

399 

400 return ReadMeshData( 

401 cells=mesh_topology, 

402 cell_type=cell_type, 

403 x=mesh_geometry, 

404 degree=degree, 

405 lvar=lvar, 

406 partition_graph=partition_graph, 

407 ) 

408 

409 

410def write_meshtags( 

411 filename: str | Path, 

412 comm: MPI.Intracomm, 

413 data: MeshTagsData, 

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

415): 

416 """Write mesh tags to file. 

417 

418 Args: 

419 filename: Path to file to write to 

420 comm: MPI communicator used in storage 

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

422 backend_args: Arguments to backend 

423 """ 

424 backend_args = {} if backend_args is None else backend_args 

425 io_name = backend_args.get("io_name", "MeshTagWriter") 

426 engine = backend_args.get("engine", "BP4") 

427 adios = adios2.ADIOS(comm) 

428 with ADIOSFile( 

429 adios=adios, 

430 filename=filename, 

431 mode=adios2.Mode.Append, 

432 engine=engine, 

433 io_name=io_name, 

434 ) as adios_file: 

435 adios_file.file.BeginStep() 

436 

437 # Write meshtag topology 

438 topology_var = adios_file.io.DefineVariable( 

439 data.name + "_topology", 

440 data.indices, 

441 shape=[data.num_entities_global, data.num_dofs_per_entity], 

442 start=[data.local_start, 0], 

443 count=[len(data.indices), data.num_dofs_per_entity], 

444 ) 

445 adios_file.file.Put(topology_var, data.indices, adios2.Mode.Sync) 

446 

447 # Write meshtag values 

448 values_var = adios_file.io.DefineVariable( 

449 data.name + "_values", 

450 data.values, 

451 shape=[data.num_entities_global], 

452 start=[data.local_start], 

453 count=[len(data.indices)], 

454 ) 

455 adios_file.file.Put(values_var, data.values, adios2.Mode.Sync) 

456 

457 # Write meshtag dim 

458 adios_file.io.DefineAttribute(data.name + "_dim", np.array([data.dim], dtype=np.uint8)) 

459 adios_file.file.PerformPuts() 

460 adios_file.file.EndStep() 

461 

462 

463def read_meshtags_data( 

464 filename: str | Path, comm: MPI.Intracomm, name: str, backend_args: dict[str, Any] | None = None 

465) -> MeshTagsData: 

466 """Read mesh tags from file. 

467 

468 Args: 

469 filename: Path to file to read from 

470 comm: MPI communicator used in storage 

471 name: Name of the mesh tags to read 

472 backend_args: Arguments to backend 

473 

474 Returns: 

475 Internal data structure for the mesh tags read from file 

476 """ 

477 

478 adios = adios2.ADIOS(comm) 

479 backend_args = get_default_backend_args(backend_args) 

480 io_name = backend_args.get("io_name", "MeshTagsReader") 

481 engine = backend_args["engine"] 

482 legacy = backend_args["legacy"] 

483 with ADIOSFile( 

484 adios=adios, 

485 filename=filename, 

486 mode=adios2.Mode.Read, 

487 engine=engine, 

488 io_name=io_name, 

489 ) as adios_file: 

490 if not legacy: 

491 # Get mesh cell type 

492 dim_attr_name = f"{name}_dim" 

493 step = 0 

494 for i in range(adios_file.file.Steps()): 

495 adios_file.file.BeginStep() 

496 if dim_attr_name in adios_file.io.AvailableAttributes().keys(): 

497 step = i 

498 break 

499 adios_file.file.EndStep() 

500 if dim_attr_name not in adios_file.io.AvailableAttributes().keys(): 

501 raise KeyError(f"{dim_attr_name} not found in {filename}") 

502 

503 m_dim = adios_file.io.InquireAttribute(dim_attr_name) 

504 dim = int(m_dim.Data()[0]) 

505 

506 # Get mesh tags entites 

507 topology_name = f"{name}_topology" 

508 for i in range(step, adios_file.file.Steps()): 

509 if i > step: 

510 adios_file.file.BeginStep() 

511 if topology_name in adios_file.io.AvailableVariables().keys(): 

512 break 

513 adios_file.file.EndStep() 

514 if topology_name not in adios_file.io.AvailableVariables().keys(): 

515 raise KeyError(f"{topology_name} not found in {filename}") 

516 

517 topology = adios_file.io.InquireVariable(topology_name) 

518 top_shape = topology.Shape() 

519 topology_range = compute_local_range(comm, top_shape[0]) 

520 

521 topology.SetSelection( 

522 [ 

523 [topology_range[0], 0], 

524 [topology_range[1] - topology_range[0], top_shape[1]], 

525 ] 

526 ) 

527 mesh_entities = np.empty( 

528 (topology_range[1] - topology_range[0], top_shape[1]), dtype=np.int64 

529 ) 

530 adios_file.file.Get(topology, mesh_entities, adios2.Mode.Deferred) 

531 

532 # Get mesh tags values 

533 values_name = f"{name}_values" 

534 if values_name not in adios_file.io.AvailableVariables().keys(): 

535 raise KeyError(f"{values_name} not found") 

536 

537 values = adios_file.io.InquireVariable(values_name) 

538 val_shape = values.Shape() 

539 assert val_shape[0] == top_shape[0] 

540 values.SetSelection([[topology_range[0]], [topology_range[1] - topology_range[0]]]) 

541 tag_values = np.empty( 

542 (topology_range[1] - topology_range[0]), dtype=values.Type().strip("_t") 

543 ) 

544 adios_file.file.Get(values, tag_values, adios2.Mode.Deferred) 

545 

546 adios_file.file.PerformGets() 

547 adios_file.file.EndStep() 

548 else: 

549 # Get mesh cell type 

550 dim_attr_name = f"{name}_dim" 

551 assert adios_file.file.Steps() == 0 

552 if (ct_key := f"/{name}/topology/celltype") in adios_file.io.AvailableAttributes(): 

553 cell_type = adios_file.io.InquireAttribute(ct_key) 

554 else: 

555 raise ValueError(f"Celltype not found for meshtags {name} in {filename}.") 

556 dim = dolfinx.mesh.cell_dim(dolfinx.mesh.to_type(cell_type.DataString()[0])) 

557 

558 # Get mesh tags entites 

559 if (top_key := f"/{name}/topology") in adios_file.io.AvailableVariables(): 

560 topology = adios_file.io.InquireVariable(top_key) 

561 else: 

562 raise ValueError(f"Topology not found for meshtags {name} in {filename}.") 

563 

564 top_shape = topology.Shape() 

565 topology_range = compute_local_range(comm, top_shape[0]) 

566 

567 topology.SetSelection( 

568 [ 

569 [topology_range[0], 0], 

570 [topology_range[1] - topology_range[0], top_shape[1]], 

571 ] 

572 ) 

573 mesh_entities = np.empty( 

574 (topology_range[1] - topology_range[0], top_shape[1]), dtype=np.int64 

575 ) 

576 adios_file.file.Get(topology, mesh_entities, adios2.Mode.Deferred) 

577 

578 # Get mesh tags values 

579 if (val_key := f"/{name}/values") in adios_file.io.AvailableVariables(): 

580 values = adios_file.io.InquireVariable(val_key) 

581 else: 

582 raise ValueError(f"Values not found for meshtags {name} in {filename}.") 

583 

584 val_shape = values.Shape() 

585 assert val_shape[0] == top_shape[0] 

586 

587 values.SetSelection([[topology_range[0]], [topology_range[1] - topology_range[0]]]) 

588 tag_values = np.empty( 

589 (topology_range[1] - topology_range[0]), dtype=values.Type().strip("_t") 

590 ) 

591 adios_file.file.Get(values, tag_values, adios2.Mode.Deferred) 

592 

593 adios_file.file.PerformGets() 

594 adios_file.file.EndStep() 

595 

596 return MeshTagsData( 

597 name=name, values=tag_values.astype(np.int32), indices=mesh_entities, dim=dim 

598 ) 

599 

600 

601def read_dofmap( 

602 filename: str | Path, comm: MPI.Intracomm, name: str, backend_args: dict[str, Any] | None = None 

603) -> dolfinx.graph.AdjacencyList: 

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

605 

606 Args: 

607 filename: Path to file to read from 

608 comm: MPI communicator used in storage 

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

610 backend_args: Arguments to backend 

611 

612 Returns: 

613 Dofmap as an AdjacencyList 

614 """ 

615 backend_args = {} if backend_args is None else backend_args 

616 

617 # Handles legacy io4dolfinx files, modern files, and custom location of dofmap. 

618 legacy = backend_args.get("legacy", False) 

619 xdofmap_path: str | None 

620 dofmap_path: str | None 

621 if (dofmap_path := backend_args.get("dofmap", None)) is None: 

622 if legacy: 

623 dofmap_path = "Dofmap" 

624 else: 

625 dofmap_path = f"{name}_dofmap" 

626 

627 if (xdofmap_path := backend_args.get("offsets", None)) is None: 

628 if legacy: 

629 xdofmap_path = "XDofmap" 

630 else: 

631 xdofmap_path = f"{name}_XDofmap" 

632 

633 engine = backend_args.get("engine", "BP4") 

634 

635 adios = adios2.ADIOS(comm) 

636 check_file_exists(filename) 

637 assert isinstance(xdofmap_path, str) 

638 return read_adjacency_list(adios, comm, filename, dofmap_path, xdofmap_path, engine=engine) 

639 

640 

641def read_dofs( 

642 filename: str | Path, 

643 comm: MPI.Intracomm, 

644 name: str, 

645 time: float, 

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

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

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

649 

650 Args: 

651 filename: Path to file to read from 

652 comm: MPI communicator used in storage 

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

654 time: Time stamp associated with the function to read 

655 backend_args: Arguments to backend 

656 

657 Returns: 

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

659 and the global starting point on the process. 

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

661 """ 

662 

663 backend_args = {} if backend_args is None else backend_args 

664 legacy = backend_args.get("legacy", False) 

665 engine = backend_args.get("engine", "BP4") 

666 io_name = backend_args.get("io_name", f"{name}_FunctionReader") 

667 # Check that file contains the function to read 

668 adios = adios2.ADIOS(comm) 

669 check_file_exists(filename) 

670 

671 if not legacy: 

672 with ADIOSFile( 

673 adios=adios, 

674 filename=filename, 

675 mode=adios2.Mode.Read, 

676 engine=engine, 

677 io_name=io_name, 

678 ) as adios_file: 

679 variables = set( 

680 sorted( 

681 map( 

682 lambda x: x.split("_time")[0], 

683 filter(lambda x: x.endswith("_time"), adios_file.io.AvailableVariables()), 

684 ) 

685 ) 

686 ) 

687 if name not in variables: 

688 raise KeyError(f"{name} not found in {filename}. Did you mean one of {variables}?") 

689 

690 if legacy: 

691 array_path = "Values" 

692 else: 

693 array_path = f"{name}_values" 

694 

695 time_name = f"{name}_time" 

696 return read_array(adios, filename, array_path, engine, comm, time, time_name, legacy=legacy) 

697 

698 

699def read_cell_perms( 

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

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

702 """ 

703 Read cell permutation from file with given communicator, 

704 Split in continuous chunks based on number of cells in the mesh (global). 

705 

706 Args: 

707 adios: The ADIOS instance 

708 comm: The MPI communicator used to read the data 

709 filename: Path to input file 

710 variable: Name of cell-permutation variable 

711 num_cells_global: Number of cells in the mesh (global) 

712 engine: Type of ADIOS engine to use for reading data 

713 

714 Returns: 

715 Cell-permutations local to the process 

716 

717 .. note:: 

718 No MPI communication is done during this call 

719 """ 

720 adios = adios2.ADIOS(comm) 

721 check_file_exists(filename) 

722 

723 # Open ADIOS engine 

724 backend_args = {} if backend_args is None else backend_args 

725 engine = backend_args.get("engine", "BP4") 

726 

727 cell_perms, _ = read_array( 

728 adios, filename, "CellPermutations", engine=engine, comm=comm, legacy=True 

729 ) 

730 

731 return cell_perms.astype(np.uint32) 

732 

733 

734def read_hdf5_array( 

735 comm: MPI.Intracomm, 

736 filename: Path | str, 

737 group: str, 

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

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

740 adios = adios2.ADIOS(comm) 

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

742 

743 Args: 

744 comm: MPI communicator used in storage 

745 filename: Path to file to read from 

746 group: Group in HDF5 file where array is stored 

747 backend_args: Arguments to backend 

748 

749 Returns: 

750 Tuple containing: 

751 - Numpy array read from file 

752 - Global starting point on the process. 

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

754 """ 

755 return read_array(adios, filename, group, engine="HDF5", comm=comm, legacy=True) 

756 

757 

758def write_function( 

759 filename: Path, 

760 comm: MPI.Intracomm, 

761 u: FunctionData, 

762 time: float = 0.0, 

763 mode: FileMode = FileMode.append, 

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

765): 

766 """ 

767 Write a function to file using ADIOS2 

768 

769 Args: 

770 comm: MPI communicator used in storage 

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

772 filename: Path to file to write to 

773 engine: ADIOS2 engine to use 

774 mode: ADIOS2 mode to use (write or append) 

775 time: Time stamp associated with function 

776 io_name: Internal name used for the ADIOS IO object 

777 """ 

778 adios_mode = convert_file_mode(mode) 

779 backend_args = get_default_backend_args(backend_args) 

780 engine = backend_args["engine"] 

781 io_name = backend_args.get("io_name", "{name}_writer") 

782 

783 adios = adios2.ADIOS(comm) 

784 cell_permutations_exists = False 

785 dofmap_exists = False 

786 XDofmap_exists = False 

787 if mode == adios2.Mode.Append: 

788 cell_permutations_exists = check_variable_exists( 

789 adios, filename, "CellPermutations", engine=engine 

790 ) 

791 dofmap_exists = check_variable_exists(adios, filename, f"{u.name}_dofmap", engine=engine) 

792 XDofmap_exists = check_variable_exists(adios, filename, f"{u.name}_XDofmap", engine=engine) 

793 

794 with ADIOSFile( 

795 adios=adios, filename=filename, mode=adios_mode, engine=engine, io_name=io_name, comm=comm 

796 ) as adios_file: 

797 adios_file.file.BeginStep() 

798 

799 if not cell_permutations_exists: 

800 # Add mesh permutations 

801 pvar = adios_file.io.DefineVariable( 

802 "CellPermutations", 

803 u.cell_permutations, 

804 shape=[u.num_cells_global], 

805 start=[u.local_cell_range[0]], 

806 count=[u.local_cell_range[1] - u.local_cell_range[0]], 

807 ) 

808 adios_file.file.Put(pvar, u.cell_permutations) 

809 

810 if not dofmap_exists: 

811 # Add dofmap 

812 dofmap_var = adios_file.io.DefineVariable( 

813 f"{u.name}_dofmap", 

814 u.dofmap_array, 

815 shape=[u.global_dofs_in_dofmap], 

816 start=[u.dofmap_range[0]], 

817 count=[u.dofmap_range[1] - u.dofmap_range[0]], 

818 ) 

819 adios_file.file.Put(dofmap_var, u.dofmap_array) 

820 

821 if not XDofmap_exists: 

822 # Add XDofmap 

823 xdofmap_var = adios_file.io.DefineVariable( 

824 f"{u.name}_XDofmap", 

825 u.dofmap_offsets, 

826 shape=[u.num_cells_global + 1], 

827 start=[u.local_cell_range[0]], 

828 count=[u.local_cell_range[1] - u.local_cell_range[0] + 1], 

829 ) 

830 adios_file.file.Put(xdofmap_var, u.dofmap_offsets) 

831 

832 val_var = adios_file.io.DefineVariable( 

833 f"{u.name}_values", 

834 u.values, 

835 shape=[u.num_dofs_global], 

836 start=[u.dof_range[0]], 

837 count=[u.dof_range[1] - u.dof_range[0]], 

838 ) 

839 adios_file.file.Put(val_var, u.values) 

840 

841 # Add time step to file 

842 t_arr = np.array([time], dtype=np.float64) 

843 time_var = adios_file.io.DefineVariable( 

844 f"{u.name}_time", 

845 t_arr, 

846 shape=[1], 

847 start=[0], 

848 count=[1 if comm.rank == 0 else 0], 

849 ) 

850 adios_file.file.Put(time_var, t_arr) 

851 adios_file.file.PerformPuts() 

852 adios_file.file.EndStep() 

853 

854 

855def read_legacy_mesh( 

856 filename: Path | str, comm: MPI.Intracomm, group: str 

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

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

859 legacy DOLFIN HDF5-file. 

860 

861 Args: 

862 filename: Path to file to read from 

863 comm: MPI communicator used in storage 

864 group: Group in HDF5 file where mesh is stored 

865 

866 Returns: 

867 Tuple containing: 

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

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

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

871 """ 

872 # Create ADIOS2 reader 

873 adios = adios2.ADIOS(comm) 

874 with ADIOSFile( 

875 adios=adios, 

876 filename=filename, 

877 mode=adios2.Mode.Read, 

878 io_name="Mesh reader", 

879 engine="HDF5", 

880 ) as adios_file: 

881 # Get mesh topology (distributed) 

882 if f"{group}/topology" not in adios_file.io.AvailableVariables().keys(): 

883 raise KeyError(f"Mesh topology not found at '{group}/topology'") 

884 topology = adios_file.io.InquireVariable(f"{group}/topology") 

885 shape = topology.Shape() 

886 local_range = compute_local_range(comm, shape[0]) 

887 topology.SetSelection([[local_range[0], 0], [local_range[1] - local_range[0], shape[1]]]) 

888 

889 mesh_topology = np.empty( 

890 (local_range[1] - local_range[0], shape[1]), 

891 dtype=topology.Type().strip("_t"), 

892 ) 

893 adios_file.file.Get(topology, mesh_topology, adios2.Mode.Sync) 

894 

895 # Get mesh cell type 

896 cell_type = None 

897 if f"{group}/topology/celltype" in adios_file.io.AvailableAttributes().keys(): 

898 celltype = adios_file.io.InquireAttribute(f"{group}/topology/celltype") 

899 cell_type = celltype.DataString()[0] 

900 

901 # Get mesh geometry 

902 

903 for geometry_key in [f"{group}/geometry", f"{group}/coordinates"]: 

904 if geometry_key in adios_file.io.AvailableVariables().keys(): 

905 break 

906 else: 

907 raise KeyError( 

908 f"Mesh coordinates not found at '{group}/coordinates' or '{group}/geometry'" 

909 ) 

910 

911 geometry = adios_file.io.InquireVariable(geometry_key) 

912 shape = geometry.Shape() 

913 local_range = compute_local_range(comm, shape[0]) 

914 geometry.SetSelection([[local_range[0], 0], [local_range[1] - local_range[0], shape[1]]]) 

915 mesh_geometry = np.empty( 

916 (local_range[1] - local_range[0], shape[1]), 

917 dtype=adios_to_numpy_dtype[geometry.Type()], 

918 ) 

919 adios_file.file.Get(geometry, mesh_geometry, adios2.Mode.Sync) 

920 

921 return mesh_topology, mesh_geometry, cell_type 

922 

923 

924def snapshot_checkpoint( 

925 filename: Path | str, 

926 mode: FileMode, 

927 u: dolfinx.fem.Function, 

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

929): 

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

931 

932 Args: 

933 filename: Path to file to read from 

934 mode: File-mode to store the function 

935 u: dolfinx function to create a snapshot checkpoint for 

936 backend_args: Arguments to backend 

937 """ 

938 adios_mode = convert_file_mode(mode) 

939 adios = adios2.ADIOS(u.function_space.mesh.comm) 

940 backend_args = {} if backend_args is None else backend_args 

941 io_name = backend_args.get("io_name", "SnapshotCheckPoint") 

942 engine = backend_args.get("engine", "BP4") 

943 with ADIOSFile( 

944 adios=adios, 

945 filename=filename, 

946 mode=adios_mode, 

947 io_name=io_name, 

948 engine=engine, 

949 ) as adios_file: 

950 if adios_mode == adios2.Mode.Write: 

951 dofmap = u.function_space.dofmap 

952 num_dofs_local = dofmap.index_map.size_local * dofmap.index_map_bs 

953 local_dofs = u.x.array[:num_dofs_local].copy() 

954 

955 # Write to file 

956 adios_file.file.BeginStep() 

957 dofs = adios_file.io.DefineVariable("dofs", local_dofs, count=[num_dofs_local]) 

958 adios_file.file.Put(dofs, local_dofs, adios2.Mode.Sync) 

959 adios_file.file.EndStep() 

960 elif adios_mode == adios2.Mode.Read: 

961 adios_file.file.BeginStep() 

962 in_variable = adios_file.io.InquireVariable("dofs") 

963 in_variable.SetBlockSelection(u.function_space.mesh.comm.rank) 

964 adios_file.file.Get(in_variable, u.x.array, adios2.Mode.Sync) 

965 adios_file.file.EndStep() 

966 u.x.scatter_forward() 

967 else: 

968 raise NotImplementedError(f"Mode {mode} is not implemented for snapshot checkpoint") 

969 

970 

971def read_function_names( 

972 filename: Path | str, comm: MPI.Intracomm, backend_args: dict[str, Any] | None 

973) -> list[str]: 

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

975 

976 Args: 

977 filename: Path to file 

978 comm: MPI communicator to launch IO on. 

979 backend_args: Arguments to backend 

980 

981 Returns: 

982 A list of function names. 

983 """ 

984 raise NotImplementedError("Reading function names are not implemented with ADIOS2") 

985 

986 

987def read_point_data( 

988 filename: Path | str, 

989 name: str, 

990 comm: MPI.Intracomm, 

991 time: float | str | None, 

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

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

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

995 

996 Args: 

997 filename: Path to file 

998 name: Name of point data 

999 comm: Communicator to launch IO on. 

1000 time: The time stamp 

1001 backend_args: The backend arguments 

1002 

1003 Returns: 

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

1005 """ 

1006 raise NotImplementedError("The ADIOS2 backend cannot read point data.") 

1007 

1008 

1009def read_cell_data( 

1010 filename: Path | str, 

1011 name: str, 

1012 comm: MPI.Intracomm, 

1013 time: str | float | None, 

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

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

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

1017 

1018 Args: 

1019 filename: Path to file 

1020 name: Name of point data 

1021 comm: Communicator to launch IO on. 

1022 time: The time stamp 

1023 backend_args: The backend arguments 

1024 Returns: 

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

1026 vertex indices of the cells, dofs the degrees of 

1027 freedom within that cell. 

1028 """ 

1029 raise NotImplementedError("The ADIOS2 backend does not support reading cell data.") 

1030 

1031 

1032def write_data( 

1033 filename: Path | str, 

1034 array_data: ArrayData, 

1035 comm: MPI.Intracomm, 

1036 time: str | float | None, 

1037 mode: FileMode, 

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

1039): 

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

1041 

1042 

1043 Args: 

1044 filename: Path to file 

1045 array_data: Data to write to file 

1046 comm: MPI communicator to open the file with. 

1047 time: Time stamp 

1048 mode: Append or write 

1049 backend_args: The backend arguments 

1050 """ 

1051 raise NotImplementedError("ADIOS2 has not implemented this yet")