Coverage for  / dolfinx-env / lib / python3.12 / site-packages / io4dolfinx / checkpointing.py: 97%

206 statements  

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

1# Copyright (C) 2023-2026 Jørgen Schartum Dokken 

2# 

3# This file is part of io4dolfinx 

4# 

5# SPDX-License-Identifier: MIT 

6 

7from __future__ import annotations 

8 

9import typing 

10from pathlib import Path 

11from typing import Any 

12 

13from mpi4py import MPI 

14 

15import basix 

16import dolfinx 

17import numpy as np 

18import numpy.typing as npt 

19import ufl 

20from packaging.version import Version 

21 

22from .backends import FileMode, ReadMode, get_backend 

23from .comm_helpers import ( 

24 send_and_recv_cell_perm, 

25 send_dofmap_and_recv_values, 

26 send_dofs_and_recv_values, 

27) 

28from .readers import create_geometry_function_space 

29from .structures import ArrayData, FunctionData, MeshTagsData 

30from .utils import ( 

31 check_file_exists, 

32 compute_dofmap_pos, 

33 compute_local_range, 

34 index_owner, 

35 unroll_dofmap, 

36 unroll_insert_position, 

37) 

38from .writers import prepare_meshdata_for_storage 

39from .writers import write_function as _internal_function_writer 

40from .writers import write_mesh as _internal_mesh_writer 

41 

42__all__ = [ 

43 "read_mesh", 

44 "write_function", 

45 "read_function", 

46 "write_mesh", 

47 "read_meshtags", 

48 "write_meshtags", 

49 "read_attributes", 

50 "write_attributes", 

51] 

52 

53 

54def write_attributes( 

55 filename: Path | str, 

56 comm: MPI.Intracomm, 

57 name: str, 

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

59 backend_args: dict[str, typing.Any] | None = None, 

60 backend: str = "adios2", 

61): 

62 """Write attributes to file. 

63 

64 Args: 

65 filename: Path to file to write to 

66 comm: MPI communicator used in storage 

67 name: Name of the attributes 

68 attributes: Dictionary of attributes to write to file 

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

70 backend: What backend to use for writing. 

71 """ 

72 backend_cls = get_backend(backend) 

73 backend_args = backend_cls.get_default_backend_args(backend_args) 

74 backend_cls.write_attributes(filename, comm, name, attributes, backend_args) 

75 

76 

77def read_attributes( 

78 filename: Path | str, 

79 comm: MPI.Intracomm, 

80 name: str, 

81 backend_args: dict[str, typing.Any] | None = None, 

82 backend: str = "adios2", 

83) -> dict[str, typing.Any]: 

84 """Read attributes from file. 

85 

86 Args: 

87 filename: Path to file to read from 

88 comm: MPI communicator used in storage 

89 name: Name of the attributes 

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

91 backend: What backend to use for writing. 

92 Returns: 

93 The attributes 

94 """ 

95 backend_cls = get_backend(backend) 

96 backend_args = backend_cls.get_default_backend_args(backend_args) 

97 return backend_cls.read_attributes(filename, comm, name, backend_args) 

98 

99 

100def read_timestamps( 

101 filename: Path | str, 

102 comm: MPI.Intracomm, 

103 function_name: str, 

104 backend_args: dict[str, typing.Any] | None = None, 

105 backend: str = "adios2", 

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

107 """ 

108 Read time-stamps from a checkpoint file. 

109 

110 Args: 

111 comm: MPI communicator 

112 filename: Path to file 

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

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

115 backend: What backend to use for writing. 

116 Returns: 

117 The time-stamps 

118 """ 

119 check_file_exists(filename) 

120 backend_cls = get_backend(backend) 

121 backend_args = backend_cls.get_default_backend_args(backend_args) 

122 return backend_cls.read_timestamps(filename, comm, function_name, backend_args) 

123 

124 

125def write_meshtags( 

126 filename: Path | str, 

127 mesh: dolfinx.mesh.Mesh, 

128 meshtags: dolfinx.mesh.MeshTags, 

129 meshtag_name: typing.Optional[str] = None, 

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

131 backend: str = "adios2", 

132): 

133 """ 

134 Write meshtags associated with input mesh to file. 

135 

136 .. note:: 

137 For this checkpoint to work, the mesh must be written to file 

138 using :func:`write_mesh` before calling this function. 

139 

140 Args: 

141 filename: Path to save meshtags (with file-extension) 

142 mesh: The mesh associated with the meshtags 

143 meshtags: The meshtags to write to file 

144 meshtag_name: Name of the meshtag. If None, the meshtag name is used. 

145 backend_args: Option to IO backend. 

146 backend: IO backend 

147 """ 

148 

149 # Extract data from meshtags (convert to global geometry node indices for each entity) 

150 tag_entities = meshtags.indices 

151 dim = meshtags.dim 

152 num_tag_entities_local = mesh.topology.index_map(dim).size_local 

153 local_tag_entities = tag_entities[tag_entities < num_tag_entities_local] 

154 local_values = meshtags.values[: len(local_tag_entities)] 

155 

156 num_saved_tag_entities = len(local_tag_entities) 

157 local_start = mesh.comm.exscan(num_saved_tag_entities, op=MPI.SUM) 

158 local_start = local_start if mesh.comm.rank != 0 else 0 

159 global_num_tag_entities = mesh.comm.allreduce(num_saved_tag_entities, op=MPI.SUM) 

160 dof_layout = mesh.geometry.cmap.create_dof_layout() 

161 if hasattr(dof_layout, "num_entity_closure_dofs"): 

162 num_dofs_per_entity = dof_layout.num_entity_closure_dofs(dim) 

163 else: 

164 num_dofs_per_entity = len(dof_layout.entity_closure_dofs(dim, 0)) 

165 

166 entities_to_geometry = dolfinx.cpp.mesh.entities_to_geometry( 

167 mesh._cpp_object, dim, local_tag_entities, False 

168 ) 

169 

170 indices = ( 

171 mesh.geometry.index_map() 

172 .local_to_global(entities_to_geometry.reshape(-1)) 

173 .reshape(entities_to_geometry.shape) 

174 ) 

175 name = meshtag_name or meshtags.name 

176 

177 tag_ct = dolfinx.cpp.mesh.cell_entity_type(mesh.topology.cell_type, dim, 0).name 

178 tag_data = MeshTagsData( 

179 values=local_values, 

180 num_entities_global=global_num_tag_entities, 

181 num_dofs_per_entity=num_dofs_per_entity, 

182 indices=indices, 

183 name=name, 

184 local_start=local_start, 

185 dim=meshtags.dim, 

186 cell_type=tag_ct, 

187 ) 

188 

189 # Get backend and default arguments 

190 backend_cls = get_backend(backend) 

191 backend_args = backend_cls.get_default_backend_args(backend_args) 

192 return backend_cls.write_meshtags(filename, mesh.comm, tag_data, backend_args=backend_args) 

193 

194 

195def read_meshtags( 

196 filename: Path | str, 

197 mesh: dolfinx.mesh.Mesh, 

198 meshtag_name: str, 

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

200 backend: str = "adios2", 

201) -> dolfinx.mesh.MeshTags: 

202 """ 

203 Read meshtags from file and return a :class:`dolfinx.mesh.MeshTags` object. 

204 

205 Args: 

206 filename: Path to meshtags file (with file-extension) 

207 mesh: The mesh associated with the meshtags 

208 meshtag_name: The name of the meshtag to read 

209 engine: Adios2 Engine 

210 Returns: 

211 The meshtags 

212 """ 

213 check_file_exists(filename) 

214 backend_cls = get_backend(backend) 

215 backend_args = backend_cls.get_default_backend_args(backend_args) 

216 data = backend_cls.read_meshtags_data(filename, mesh.comm, meshtag_name, backend_args) 

217 

218 local_entities, local_values = dolfinx.io.distribute_entity_data( 

219 mesh, int(data.dim), data.indices, data.values 

220 ) 

221 mesh.topology.create_connectivity(data.dim, 0) 

222 mesh.topology.create_connectivity(data.dim, mesh.topology.dim) 

223 

224 adj = dolfinx.graph.adjacencylist(local_entities) 

225 

226 local_values = np.array(local_values, dtype=np.int32) 

227 

228 mt = dolfinx.mesh.meshtags_from_entities(mesh, int(data.dim), adj, local_values) 

229 mt.name = meshtag_name 

230 return mt 

231 

232 

233def read_function( 

234 filename: Path | str, 

235 u: dolfinx.fem.Function, 

236 time: float = 0.0, 

237 name: str | None = None, 

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

239 backend: str = "adios2", 

240): 

241 """ 

242 Read checkpoint from file and fill it into `u`. 

243 

244 Args: 

245 filename: Path to checkpoint 

246 u: Function to fill 

247 time: Time-stamp associated with checkpoint 

248 name: If not provided, `u.name` is used to search through the input file for the function 

249 """ 

250 check_file_exists(filename) 

251 

252 mesh = u.function_space.mesh 

253 comm = mesh.comm 

254 if name is None: 

255 name = u.name 

256 

257 # ----------------------Step 1--------------------------------- 

258 # Compute index of input cells and get cell permutation 

259 num_owned_cells = mesh.topology.index_map(mesh.topology.dim).size_local 

260 input_cells = mesh.topology.original_cell_index[:num_owned_cells] 

261 mesh.topology.create_entity_permutations() 

262 cell_perm = mesh.topology.get_cell_permutation_info()[:num_owned_cells] 

263 

264 # Compute mesh->input communicator 

265 # 1.1 Compute mesh->input communicator 

266 backend_cls = get_backend(backend) 

267 owners: npt.NDArray[np.int32] 

268 if backend_cls.read_mode == ReadMode.serial: 

269 owners = np.zeros(input_cells, dtype=np.int32) 

270 elif backend_cls.read_mode == ReadMode.parallel: 

271 num_cells_global = mesh.topology.index_map(mesh.topology.dim).size_global 

272 owners = index_owner(mesh.comm, input_cells, num_cells_global) 

273 else: 

274 raise NotImplementedError(f"{backend_cls.read_mode} not implemented") 

275 # -------------------Step 2------------------------------------ 

276 # Send and receive global cell index and cell perm 

277 inc_cells, inc_perms = send_and_recv_cell_perm(input_cells, cell_perm, owners, mesh.comm) 

278 

279 # -------------------Step 3----------------------------------- 

280 # Read dofmap from file and compute dof owners 

281 check_file_exists(filename) 

282 backend_cls = get_backend(backend) 

283 backend_args = backend_cls.get_default_backend_args(backend_args) 

284 

285 input_dofmap = backend_cls.read_dofmap(filename, comm, name, backend_args) 

286 

287 # Compute owner of dofs in dofmap 

288 dof_owner: npt.NDArray[np.int32] 

289 if backend_cls.read_mode == ReadMode.serial: 

290 dof_owner = np.zeros(len(input_dofmap.array), dtype=np.int32) 

291 elif backend_cls.read_mode == ReadMode.parallel: 

292 num_dofs_global = ( 

293 u.function_space.dofmap.index_map.size_global * u.function_space.dofmap.index_map_bs 

294 ) 

295 dof_owner = index_owner(comm, input_dofmap.array.astype(np.int64), num_dofs_global) 

296 else: 

297 raise NotImplementedError(f"{backend_cls.read_mode} not implemented") 

298 

299 # --------------------Step 4----------------------------------- 

300 # Read array from file and communicate them to input dofmap process 

301 input_array, starting_pos = backend_cls.read_dofs(filename, comm, name, time, backend_args) 

302 

303 recv_array = send_dofs_and_recv_values( 

304 input_dofmap.array.astype(np.int64), dof_owner, comm, input_array, starting_pos 

305 ) 

306 

307 # -------------------Step 5-------------------------------------- 

308 # Invert permutation of input data based on input perm 

309 # Then apply current permutation to the local data 

310 element = u.function_space.element 

311 if element.needs_dof_transformations: 

312 bs = u.function_space.dofmap.bs 

313 

314 # Read input cell permutations on dofmap process 

315 local_input_range = compute_local_range(comm, num_cells_global) 

316 input_local_cell_index = inc_cells - local_input_range[0] 

317 input_perms = backend_cls.read_cell_perms(comm, filename, backend_args) 

318 

319 # Start by sorting data array by cell permutation 

320 num_dofs_per_cell = input_dofmap.offsets[1:] - input_dofmap.offsets[:-1] 

321 assert np.allclose(num_dofs_per_cell, num_dofs_per_cell[0]) 

322 

323 # Sort dofmap by input local cell index 

324 input_perms_sorted = input_perms[input_local_cell_index] 

325 unrolled_dofmap_position = unroll_insert_position( 

326 input_local_cell_index, num_dofs_per_cell[0] 

327 ) 

328 dofmap_sorted_by_input = recv_array[unrolled_dofmap_position] 

329 

330 # First invert input data to reference element then transform to current mesh 

331 element.Tt_apply(dofmap_sorted_by_input, input_perms_sorted, bs) 

332 element.Tt_inv_apply(dofmap_sorted_by_input, inc_perms, bs) 

333 # Compute invert permutation 

334 inverted_perm = np.empty_like(unrolled_dofmap_position) 

335 inverted_perm[unrolled_dofmap_position] = np.arange( 

336 len(unrolled_dofmap_position), dtype=inverted_perm.dtype 

337 ) 

338 recv_array = dofmap_sorted_by_input[inverted_perm] 

339 

340 # ------------------Step 6---------------------------------------- 

341 # For each dof owned by a process, find the local position in the dofmap. 

342 V = u.function_space 

343 local_cells, dof_pos = compute_dofmap_pos(V) 

344 input_cells = V.mesh.topology.original_cell_index[local_cells] 

345 num_cells_global = V.mesh.topology.index_map(V.mesh.topology.dim).size_global 

346 

347 if backend_cls.read_mode == ReadMode.serial: 

348 owners = np.zeros(len(input_cells), dtype=np.int32) 

349 elif backend_cls.read_mode == ReadMode.parallel: 

350 owners = index_owner(V.mesh.comm, input_cells, num_cells_global) 

351 else: 

352 raise NotImplementedError(f"{backend_cls.read_mode} not implemented") 

353 

354 unique_owners, owner_count = np.unique(owners, return_counts=True) 

355 # FIXME: In C++ use NBX to find neighbourhood 

356 sub_comm = V.mesh.comm.Create_dist_graph( 

357 [V.mesh.comm.rank], [len(unique_owners)], unique_owners, reorder=False 

358 ) 

359 source, dest, _ = sub_comm.Get_dist_neighbors() 

360 sub_comm.Free() 

361 

362 owned_values = send_dofmap_and_recv_values( 

363 comm, 

364 np.asarray(source, dtype=np.int32), 

365 np.asarray(dest, dtype=np.int32), 

366 owners, 

367 owner_count.astype(np.int32), 

368 input_cells, 

369 dof_pos, 

370 num_cells_global, 

371 recv_array, 

372 input_dofmap.offsets, 

373 ) 

374 u.x.array[: len(owned_values)] = owned_values 

375 u.x.scatter_forward() 

376 

377 

378def read_mesh( 

379 filename: Path | str, 

380 comm: MPI.Intracomm, 

381 ghost_mode: dolfinx.mesh.GhostMode = dolfinx.mesh.GhostMode.shared_facet, 

382 time: float | str | None = 0.0, 

383 read_from_partition: bool = False, 

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

385 backend: str = "adios2", 

386 max_facet_to_cell_links: int = 2, 

387) -> dolfinx.mesh.Mesh: 

388 """ 

389 Read an ADIOS2 mesh into DOLFINx. 

390 

391 Args: 

392 filename: Path to input file 

393 comm: The MPI communciator to distribute the mesh over 

394 ghost_mode: Ghost mode to use for mesh. If `read_from_partition` 

395 is set to `True` this option is ignored. 

396 time: Time stamp associated with mesh 

397 read_from_partition: Read mesh with partition from file 

398 backend_args: List of arguments to reader backend 

399 max_facet_to_cell_links: Maximum number of cells a facet 

400 can be connected to. 

401 Returns: 

402 The distributed mesh 

403 """ 

404 # Read in data in a distributed fashin 

405 check_file_exists(filename) 

406 backend_cls = get_backend(backend) 

407 backend_args = backend_cls.get_default_backend_args(backend_args) 

408 

409 # Let each backend handle what should be default behavior when reading mesh 

410 # with or without time stamp. 

411 dist_in_data = backend_cls.read_mesh_data( 

412 filename, 

413 comm, 

414 time=time, 

415 read_from_partition=read_from_partition, 

416 backend_args=backend_args, 

417 ) 

418 

419 # Create DOLFINx mesh 

420 element = basix.ufl.element( 

421 basix.ElementFamily.P, 

422 dist_in_data.cell_type, 

423 dist_in_data.degree, 

424 basix.LagrangeVariant(int(dist_in_data.lvar)), 

425 shape=(dist_in_data.x.shape[1],), 

426 dtype=dist_in_data.x.dtype, 

427 ) 

428 domain = ufl.Mesh(element) 

429 if (partition_graph := dist_in_data.partition_graph) is not None: 

430 

431 def partitioner(comm: MPI.Intracomm, n, m, topo): 

432 assert len(topo[0]) % (len(partition_graph.offsets) - 1) == 0 

433 if Version(dolfinx.__version__) > Version("0.9.0"): 

434 return partition_graph._cpp_object 

435 else: 

436 return partition_graph 

437 else: 

438 try: 

439 partitioner = dolfinx.cpp.mesh.create_cell_partitioner( 

440 ghost_mode, max_facet_to_cell_links=max_facet_to_cell_links 

441 ) 

442 except TypeError: 

443 partitioner = dolfinx.cpp.mesh.create_cell_partitioner(ghost_mode) 

444 

445 # Should change to the commented code below when we require python 

446 # minimum version to be >=3.12 see https://github.com/python/cpython/pull/116198 

447 # import inspect 

448 # sig = inspect.signature(dolfinx.mesh.create_cell_partitioner) 

449 # part_kwargs = {} 

450 # if "max_facet_to_cell_links" in list(sig.parameters.keys()): 

451 # part_kwargs["max_facet_to_cell_links"] = max_facet_to_cell_links 

452 # partitioner = dolfinx.cpp.mesh.create_cell_partitioner(ghost_mode, **part_kwargs) 

453 

454 return dolfinx.mesh.create_mesh( 

455 comm, 

456 cells=dist_in_data.cells, 

457 x=dist_in_data.x, 

458 e=domain, 

459 partitioner=partitioner, 

460 ) 

461 

462 

463def write_mesh( 

464 filename: Path, 

465 mesh: dolfinx.mesh.Mesh, 

466 mode: FileMode = FileMode.write, 

467 time: float = 0.0, 

468 store_partition_info: bool = False, 

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

470 backend: str = "adios2", 

471): 

472 """ 

473 Write a mesh to file. 

474 

475 Args: 

476 filename: Path to save mesh (without file-extension) 

477 mesh: The mesh to write to file 

478 

479 store_partition_info: Store mesh partitioning (including ghosting) to file 

480 """ 

481 mesh_data = prepare_meshdata_for_storage(mesh=mesh, store_partition_info=store_partition_info) 

482 

483 _internal_mesh_writer( 

484 filename, 

485 mesh.comm, 

486 mesh_data=mesh_data, 

487 time=time, 

488 backend_args=backend_args, 

489 backend=backend, 

490 mode=mode, 

491 ) 

492 

493 

494def write_function( 

495 filename: Path | str, 

496 u: dolfinx.fem.Function, 

497 time: float = 0.0, 

498 mode: FileMode = FileMode.append, 

499 name: str | None = None, 

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

501 backend: str = "adios2", 

502): 

503 """ 

504 Write function checkpoint to file. 

505 

506 Args: 

507 u: Function to write to file 

508 time: Time-stamp for simulation 

509 filename: Path to write to 

510 mode: Write or append. 

511 name: Name of function to write. If None, the name of the function is used. 

512 backend_args: Arguments to the IO backend. 

513 backend: The backend to use 

514 """ 

515 dofmap = u.function_space.dofmap 

516 values = u.x.array 

517 mesh = u.function_space.mesh 

518 comm = mesh.comm 

519 mesh.topology.create_entity_permutations() 

520 cell_perm = mesh.topology.get_cell_permutation_info() 

521 num_cells_local = mesh.topology.index_map(mesh.topology.dim).size_local 

522 local_cell_range = mesh.topology.index_map(mesh.topology.dim).local_range 

523 num_cells_global = mesh.topology.index_map(mesh.topology.dim).size_global 

524 

525 # Convert local dofmap into global_dofmap 

526 dmap = dofmap.list 

527 num_dofs_per_cell = dmap.shape[1] 

528 dofmap_bs = dofmap.bs 

529 num_dofs_local_dmap = num_cells_local * num_dofs_per_cell * dofmap_bs 

530 index_map_bs = dofmap.index_map_bs 

531 

532 # Unroll dofmap for block size 

533 unrolled_dofmap = unroll_dofmap(dofmap.list[:num_cells_local, :], dofmap_bs) 

534 dmap_loc = (unrolled_dofmap // index_map_bs).reshape(-1) 

535 dmap_rem = (unrolled_dofmap % index_map_bs).reshape(-1) 

536 

537 # Convert imap index to global index 

538 imap_global = dofmap.index_map.local_to_global(dmap_loc) 

539 dofmap_global = imap_global * index_map_bs + dmap_rem 

540 dofmap_imap = dolfinx.common.IndexMap(mesh.comm, num_dofs_local_dmap) 

541 

542 # Compute dofmap offsets 

543 local_dofmap_offsets = np.arange(num_cells_local + 1, dtype=np.int64) 

544 local_dofmap_offsets[:] *= num_dofs_per_cell * dofmap_bs 

545 local_dofmap_offsets += dofmap_imap.local_range[0] 

546 

547 num_dofs_global = dofmap.index_map.size_global * dofmap.index_map_bs 

548 local_dof_range = np.asarray(dofmap.index_map.local_range) * dofmap.index_map_bs 

549 num_dofs_local = local_dof_range[1] - local_dof_range[0] 

550 

551 # Create internal data structure for function data to write to file 

552 function_data = FunctionData( 

553 cell_permutations=cell_perm[:num_cells_local].copy(), 

554 local_cell_range=local_cell_range, 

555 num_cells_global=num_cells_global, 

556 dofmap_array=dofmap_global, 

557 dofmap_offsets=local_dofmap_offsets, 

558 dofmap_range=dofmap_imap.local_range, 

559 global_dofs_in_dofmap=dofmap_imap.size_global, 

560 values=values[:num_dofs_local].copy(), 

561 dof_range=local_dof_range, 

562 num_dofs_global=num_dofs_global, 

563 name=name or u.name, 

564 ) 

565 # Write to file 

566 fname = Path(filename) 

567 _internal_function_writer( 

568 fname, comm, function_data, time, backend_args=backend_args, backend=backend, mode=mode 

569 ) 

570 

571 

572def read_function_names( 

573 filename: Path | str, 

574 comm: MPI.Intracomm, 

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

576 backend: str = "h5py", 

577) -> list[str]: 

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

579 

580 Args: 

581 filename: Path to file 

582 comm: MPI communicator to launch IO on. 

583 backend_args: Arguments to backend 

584 

585 Returns: 

586 A list of function names. 

587 """ 

588 backend_cls = get_backend(backend) 

589 return backend_cls.read_function_names(filename, comm, backend_args=backend_args) 

590 

591 

592def write_point_data( 

593 filename: Path | str, 

594 u: dolfinx.fem.Function, 

595 time: str | float | None, 

596 mode: FileMode, 

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

598 backend: str = "vtkhdf", 

599): 

600 """Write function to file by interpolating into geometry nodes. 

601 

602 

603 Args: 

604 filename: Path to file 

605 u: The function to store 

606 time: Time stamp 

607 mode: Append or write 

608 backend_args: The backend arguments 

609 backend: Which backend to use. 

610 """ 

611 V = create_geometry_function_space(u.function_space.mesh, int(np.prod(u.ufl_shape))) 

612 v_out = dolfinx.fem.Function(V, name=u.name, dtype=u.x.array.dtype) 

613 v_out.interpolate(u) 

614 comm = v_out.function_space.mesh.comm 

615 data_shape = (V.dofmap.index_map.size_global, V.dofmap.index_map_bs) 

616 local_range = V.dofmap.index_map.local_range 

617 num_dofs_local = V.dofmap.index_map.size_local 

618 data = v_out.x.array.reshape(-1, V.dofmap.index_map_bs)[:num_dofs_local] 

619 ad = ArrayData( 

620 name=v_out.name, values=data, global_shape=data_shape, local_range=local_range, type="Point" 

621 ) 

622 backend_cls = get_backend(backend) 

623 return backend_cls.write_data( 

624 filename, comm=comm, mode=mode, time=time, array_data=ad, backend_args=backend_args 

625 ) 

626 

627 

628def write_cell_data( 

629 filename: Path | str, 

630 u: dolfinx.fem.Function, 

631 time: str | float | None, 

632 mode: FileMode, 

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

634 backend: str = "vtkhdf", 

635): 

636 """Write function to file by interpolating into cell midpoints. 

637 

638 

639 Args: 

640 filename: Path to file 

641 point_data: Data to write to file 

642 time: Time stamp 

643 mode: Append or write 

644 backend_args: The backend arguments 

645 """ 

646 V = dolfinx.fem.functionspace(u.function_space.mesh, ("DG", 0, u.ufl_shape)) 

647 v_out = dolfinx.fem.Function(V, name=u.name, dtype=u.x.array.dtype) 

648 v_out.interpolate(u) 

649 comm = v_out.function_space.mesh.comm 

650 data_shape = (V.dofmap.index_map.size_global, V.dofmap.index_map_bs) 

651 local_range = V.dofmap.index_map.local_range 

652 num_dofs_local = V.dofmap.index_map.size_local 

653 data = v_out.x.array.reshape(-1, V.dofmap.index_map_bs)[:num_dofs_local] 

654 

655 backend_cls = get_backend(backend) 

656 ad = ArrayData( 

657 name=v_out.name, values=data, global_shape=data_shape, local_range=local_range, type="Cell" 

658 ) 

659 backend_cls = get_backend(backend) 

660 return backend_cls.write_data( 

661 filename, comm=comm, mode=mode, time=time, array_data=ad, backend_args=backend_args 

662 )