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

382 statements  

« prev     ^ index     » next       coverage.py v7.14.0, created at 2026-05-12 11:21 +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 vals = np.array(data.values) 

449 values_var = adios_file.io.DefineVariable( 

450 data.name + "_values", 

451 vals, 

452 shape=[data.num_entities_global], 

453 start=[data.local_start], 

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

455 ) 

456 adios_file.file.Put(values_var, vals, adios2.Mode.Sync) 

457 

458 # Write meshtag dim 

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

460 adios_file.file.PerformPuts() 

461 adios_file.file.EndStep() 

462 

463 

464def read_meshtags_data( 

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

466) -> MeshTagsData: 

467 """Read mesh tags from file. 

468 

469 Args: 

470 filename: Path to file to read from 

471 comm: MPI communicator used in storage 

472 name: Name of the mesh tags to read 

473 backend_args: Arguments to backend 

474 

475 Returns: 

476 Internal data structure for the mesh tags read from file 

477 """ 

478 

479 adios = adios2.ADIOS(comm) 

480 backend_args = get_default_backend_args(backend_args) 

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

482 engine = backend_args["engine"] 

483 legacy = backend_args["legacy"] 

484 with ADIOSFile( 

485 adios=adios, 

486 filename=filename, 

487 mode=adios2.Mode.Read, 

488 engine=engine, 

489 io_name=io_name, 

490 ) as adios_file: 

491 if not legacy: 

492 # Get mesh cell type 

493 dim_attr_name = f"{name}_dim" 

494 step = 0 

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

496 adios_file.file.BeginStep() 

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

498 step = i 

499 break 

500 adios_file.file.EndStep() 

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

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

503 

504 m_dim = adios_file.io.InquireAttribute(dim_attr_name) 

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

506 

507 # Get mesh tags entites 

508 topology_name = f"{name}_topology" 

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

510 if i > step: 

511 adios_file.file.BeginStep() 

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

513 break 

514 adios_file.file.EndStep() 

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

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

517 

518 topology = adios_file.io.InquireVariable(topology_name) 

519 top_shape = topology.Shape() 

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

521 

522 topology.SetSelection( 

523 [ 

524 [topology_range[0], 0], 

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

526 ] 

527 ) 

528 mesh_entities = np.empty( 

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

530 ) 

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

532 

533 # Get mesh tags values 

534 values_name = f"{name}_values" 

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

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

537 

538 values = adios_file.io.InquireVariable(values_name) 

539 val_shape = values.Shape() 

540 assert val_shape[0] == top_shape[0] 

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

542 tag_values = np.empty( 

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

544 ) 

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

546 

547 adios_file.file.PerformGets() 

548 adios_file.file.EndStep() 

549 else: 

550 # Get mesh cell type 

551 dim_attr_name = f"{name}_dim" 

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

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

554 cell_type = adios_file.io.InquireAttribute(ct_key) 

555 else: 

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

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

558 

559 # Get mesh tags entites 

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

561 topology = adios_file.io.InquireVariable(top_key) 

562 else: 

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

564 

565 top_shape = topology.Shape() 

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

567 

568 topology.SetSelection( 

569 [ 

570 [topology_range[0], 0], 

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

572 ] 

573 ) 

574 mesh_entities = np.empty( 

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

576 ) 

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

578 

579 # Get mesh tags values 

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

581 values = adios_file.io.InquireVariable(val_key) 

582 else: 

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

584 

585 val_shape = values.Shape() 

586 assert val_shape[0] == top_shape[0] 

587 

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

589 tag_values = np.empty( 

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

591 ) 

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

593 

594 adios_file.file.PerformGets() 

595 adios_file.file.EndStep() 

596 

597 return MeshTagsData( 

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

599 ) 

600 

601 

602def read_dofmap( 

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

604) -> dolfinx.graph.AdjacencyList: 

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

606 

607 Args: 

608 filename: Path to file to read from 

609 comm: MPI communicator used in storage 

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

611 backend_args: Arguments to backend 

612 

613 Returns: 

614 Dofmap as an AdjacencyList 

615 """ 

616 backend_args = {} if backend_args is None else backend_args 

617 

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

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

620 xdofmap_path: str | None 

621 dofmap_path: str | None 

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

623 if legacy: 

624 dofmap_path = "Dofmap" 

625 else: 

626 dofmap_path = f"{name}_dofmap" 

627 

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

629 if legacy: 

630 xdofmap_path = "XDofmap" 

631 else: 

632 xdofmap_path = f"{name}_XDofmap" 

633 

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

635 

636 adios = adios2.ADIOS(comm) 

637 check_file_exists(filename) 

638 assert isinstance(xdofmap_path, str) 

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

640 

641 

642def read_dofs( 

643 filename: str | Path, 

644 comm: MPI.Intracomm, 

645 name: str, 

646 time: float, 

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

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

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

650 

651 Args: 

652 filename: Path to file to read from 

653 comm: MPI communicator used in storage 

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

655 time: Time stamp associated with the function to read 

656 backend_args: Arguments to backend 

657 

658 Returns: 

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

660 and the global starting point on the process. 

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

662 """ 

663 

664 backend_args = {} if backend_args is None else backend_args 

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

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

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

668 # Check that file contains the function to read 

669 adios = adios2.ADIOS(comm) 

670 check_file_exists(filename) 

671 

672 if not legacy: 

673 with ADIOSFile( 

674 adios=adios, 

675 filename=filename, 

676 mode=adios2.Mode.Read, 

677 engine=engine, 

678 io_name=io_name, 

679 ) as adios_file: 

680 variables = set( 

681 sorted( 

682 map( 

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

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

685 ) 

686 ) 

687 ) 

688 if name not in variables: 

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

690 

691 if legacy: 

692 array_path = "Values" 

693 else: 

694 array_path = f"{name}_values" 

695 

696 time_name = f"{name}_time" 

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

698 

699 

700def read_cell_perms( 

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

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

703 """ 

704 Read cell permutation from file with given communicator, 

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

706 

707 Args: 

708 adios: The ADIOS instance 

709 comm: The MPI communicator used to read the data 

710 filename: Path to input file 

711 variable: Name of cell-permutation variable 

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

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

714 

715 Returns: 

716 Cell-permutations local to the process 

717 

718 .. note:: 

719 No MPI communication is done during this call 

720 """ 

721 adios = adios2.ADIOS(comm) 

722 check_file_exists(filename) 

723 

724 # Open ADIOS engine 

725 backend_args = {} if backend_args is None else backend_args 

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

727 

728 cell_perms, _ = read_array( 

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

730 ) 

731 

732 return cell_perms.astype(np.uint32) 

733 

734 

735def read_hdf5_array( 

736 comm: MPI.Intracomm, 

737 filename: Path | str, 

738 group: str, 

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

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

741 adios = adios2.ADIOS(comm) 

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

743 

744 Args: 

745 comm: MPI communicator used in storage 

746 filename: Path to file to read from 

747 group: Group in HDF5 file where array is stored 

748 backend_args: Arguments to backend 

749 

750 Returns: 

751 Tuple containing: 

752 - Numpy array read from file 

753 - Global starting point on the process. 

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

755 """ 

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

757 

758 

759def write_function( 

760 filename: Path, 

761 comm: MPI.Intracomm, 

762 u: FunctionData, 

763 time: float = 0.0, 

764 mode: FileMode = FileMode.append, 

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

766): 

767 """ 

768 Write a function to file using ADIOS2 

769 

770 Args: 

771 comm: MPI communicator used in storage 

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

773 filename: Path to file to write to 

774 engine: ADIOS2 engine to use 

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

776 time: Time stamp associated with function 

777 io_name: Internal name used for the ADIOS IO object 

778 """ 

779 adios_mode = convert_file_mode(mode) 

780 backend_args = get_default_backend_args(backend_args) 

781 engine = backend_args["engine"] 

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

783 

784 adios = adios2.ADIOS(comm) 

785 cell_permutations_exists = False 

786 dofmap_exists = False 

787 XDofmap_exists = False 

788 if mode == adios2.Mode.Append: 

789 cell_permutations_exists = check_variable_exists( 

790 adios, filename, "CellPermutations", engine=engine 

791 ) 

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

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

794 

795 with ADIOSFile( 

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

797 ) as adios_file: 

798 adios_file.file.BeginStep() 

799 

800 if not cell_permutations_exists: 

801 # Add mesh permutations 

802 pvar = adios_file.io.DefineVariable( 

803 "CellPermutations", 

804 u.cell_permutations, 

805 shape=[u.num_cells_global], 

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

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

808 ) 

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

810 

811 if not dofmap_exists: 

812 # Add dofmap 

813 dofmap_var = adios_file.io.DefineVariable( 

814 f"{u.name}_dofmap", 

815 u.dofmap_array, 

816 shape=[u.global_dofs_in_dofmap], 

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

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

819 ) 

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

821 

822 if not XDofmap_exists: 

823 # Add XDofmap 

824 xdofmap_var = adios_file.io.DefineVariable( 

825 f"{u.name}_XDofmap", 

826 u.dofmap_offsets, 

827 shape=[u.num_cells_global + 1], 

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

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

830 ) 

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

832 

833 val_var = adios_file.io.DefineVariable( 

834 f"{u.name}_values", 

835 u.values, 

836 shape=[u.num_dofs_global], 

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

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

839 ) 

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

841 

842 # Add time step to file 

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

844 time_var = adios_file.io.DefineVariable( 

845 f"{u.name}_time", 

846 t_arr, 

847 shape=[1], 

848 start=[0], 

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

850 ) 

851 adios_file.file.Put(time_var, t_arr) 

852 adios_file.file.PerformPuts() 

853 adios_file.file.EndStep() 

854 

855 

856def read_legacy_mesh( 

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

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

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

860 legacy DOLFIN HDF5-file. 

861 

862 Args: 

863 filename: Path to file to read from 

864 comm: MPI communicator used in storage 

865 group: Group in HDF5 file where mesh is stored 

866 

867 Returns: 

868 Tuple containing: 

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

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

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

872 """ 

873 # Create ADIOS2 reader 

874 adios = adios2.ADIOS(comm) 

875 with ADIOSFile( 

876 adios=adios, 

877 filename=filename, 

878 mode=adios2.Mode.Read, 

879 io_name="Mesh reader", 

880 engine="HDF5", 

881 ) as adios_file: 

882 # Get mesh topology (distributed) 

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

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

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

886 shape = topology.Shape() 

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

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

889 

890 mesh_topology = np.empty( 

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

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

893 ) 

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

895 

896 # Get mesh cell type 

897 cell_type = None 

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

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

900 cell_type = celltype.DataString()[0] 

901 

902 # Get mesh geometry 

903 

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

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

906 break 

907 else: 

908 raise KeyError( 

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

910 ) 

911 

912 geometry = adios_file.io.InquireVariable(geometry_key) 

913 shape = geometry.Shape() 

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

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

916 mesh_geometry = np.empty( 

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

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

919 ) 

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

921 

922 return mesh_topology, mesh_geometry, cell_type 

923 

924 

925def snapshot_checkpoint( 

926 filename: Path | str, 

927 mode: FileMode, 

928 u: dolfinx.fem.Function, 

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

930): 

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

932 

933 Args: 

934 filename: Path to file to read from 

935 mode: File-mode to store the function 

936 u: dolfinx function to create a snapshot checkpoint for 

937 backend_args: Arguments to backend 

938 """ 

939 adios_mode = convert_file_mode(mode) 

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

941 backend_args = {} if backend_args is None else backend_args 

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

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

944 with ADIOSFile( 

945 adios=adios, 

946 filename=filename, 

947 mode=adios_mode, 

948 io_name=io_name, 

949 engine=engine, 

950 ) as adios_file: 

951 if adios_mode == adios2.Mode.Write: 

952 dofmap = u.function_space.dofmap 

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

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

955 

956 # Write to file 

957 adios_file.file.BeginStep() 

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

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

960 adios_file.file.EndStep() 

961 elif adios_mode == adios2.Mode.Read: 

962 adios_file.file.BeginStep() 

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

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

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

966 adios_file.file.EndStep() 

967 u.x.scatter_forward() 

968 else: 

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

970 

971 

972def read_function_names( 

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

974) -> list[str]: 

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

976 

977 Args: 

978 filename: Path to file 

979 comm: MPI communicator to launch IO on. 

980 backend_args: Arguments to backend 

981 

982 Returns: 

983 A list of function names. 

984 """ 

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

986 

987 

988def read_point_data( 

989 filename: Path | str, 

990 name: str, 

991 comm: MPI.Intracomm, 

992 time: float | str | None, 

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

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

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

996 

997 Args: 

998 filename: Path to file 

999 name: Name of point data 

1000 comm: Communicator to launch IO on. 

1001 time: The time stamp 

1002 backend_args: The backend arguments 

1003 

1004 Returns: 

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

1006 """ 

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

1008 

1009 

1010def read_cell_data( 

1011 filename: Path | str, 

1012 name: str, 

1013 comm: MPI.Intracomm, 

1014 time: str | float | None, 

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

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

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

1018 

1019 Args: 

1020 filename: Path to file 

1021 name: Name of point data 

1022 comm: Communicator to launch IO on. 

1023 time: The time stamp 

1024 backend_args: The backend arguments 

1025 Returns: 

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

1027 vertex indices of the cells, dofs the degrees of 

1028 freedom within that cell. 

1029 """ 

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

1031 

1032 

1033def write_data( 

1034 filename: Path | str, 

1035 array_data: ArrayData, 

1036 comm: MPI.Intracomm, 

1037 time: str | float | None, 

1038 mode: FileMode, 

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

1040): 

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

1042 

1043 

1044 Args: 

1045 filename: Path to file 

1046 array_data: Data to write to file 

1047 comm: MPI communicator to open the file with. 

1048 time: Time stamp 

1049 mode: Append or write 

1050 backend_args: The backend arguments 

1051 """ 

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