Skip to content

Commit 043e2f3

Browse files
committed
Add vtkhdf reader
1 parent 8feda4d commit 043e2f3

8 files changed

Lines changed: 169 additions & 48 deletions

File tree

src/adios4dolfinx/backends/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ def read_mesh_data(
151151
self,
152152
filename: Path | str,
153153
comm: MPI.Intracomm,
154-
time: float | None,
154+
time: str | float | None,
155155
read_from_partition: bool,
156156
backend_args: dict[str, Any] | None,
157157
) -> ReadMeshData:

src/adios4dolfinx/backends/adios2/backend.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ def write_mesh(
275275
def read_mesh_data(
276276
filename: Path | str,
277277
comm: MPI.Intracomm,
278-
time: float | None = 0.0,
278+
time: str | float | None = 0.0,
279279
read_from_partition: bool = False,
280280
backend_args: dict[str, Any] | None = None,
281281
) -> ReadMeshData:

src/adios4dolfinx/backends/h5py/backend.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@ def write_mesh(
239239
def read_mesh_data(
240240
filename: Path | str,
241241
comm: MPI.Intracomm,
242-
time: float | None = 0.0,
242+
time: str | float | None = 0.0,
243243
read_from_partition: bool = False,
244244
backend_args: dict[str, Any] | None = None,
245245
) -> ReadMeshData:

src/adios4dolfinx/backends/pyvista/backend.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ def get_default_backend_args(arguments: dict[str, Any] | None) -> dict[str, Any]
109109
def read_mesh_data(
110110
filename: Path | str,
111111
comm: MPI.Intracomm,
112-
time: float | None,
112+
time: str | float | None,
113113
read_from_partition: bool,
114114
backend_args: dict[str, Any] | None,
115115
) -> ReadMeshData:

src/adios4dolfinx/backends/vtkhdf/backend.py

Lines changed: 101 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -89,20 +89,71 @@ def read_mesh_data(
8989

9090
with h5pyfile(filename, "r", comm=comm) as h5file:
9191
hdf = h5file["VTKHDF"]
92-
num_cells_global = hdf["Types"].size
9392

94-
local_cell_range = compute_local_range(comm, num_cells_global)
95-
cell_types_local = hdf["Types"][local_cell_range[0] : local_cell_range[1]]
93+
if time is None:
94+
num_cells_global = hdf["Types"].size
95+
local_cell_range = compute_local_range(comm, num_cells_global)
96+
cell_types_local = hdf["Types"][slice(*local_cell_range)]
97+
98+
num_points_global = hdf["NumberOfPoints"][0]
99+
local_point_range = compute_local_range(comm, num_points_global)
100+
points_local = hdf["Points"][slice(*local_point_range)]
101+
102+
# Connectivity read
103+
offsets = hdf["Offsets"]
104+
local_connectivity_offset = offsets[local_cell_range[0] : local_cell_range[1] + 1]
105+
topology = hdf["Connectivity"][
106+
local_connectivity_offset[0] : local_connectivity_offset[-1]
107+
]
108+
offset = local_connectivity_offset - local_connectivity_offset[0]
109+
else:
110+
if "Steps" not in hdf.keys():
111+
raise RuntimeError(f"No timestepping information found in {filename}.")
112+
stamps = hdf["Steps"]["Values"][:]
113+
pos = np.flatnonzero(np.isclose(stamps, time))
114+
if len(pos) == 0:
115+
raise RuntimeError(f"Could not find mesh at t={time} in {filename}.")
116+
elif len(pos) > 1:
117+
raise RuntimeError(f"Multiple time steps for mesh at t={time} in {filename}")
96118

97-
num_points_global = hdf["NumberOfPoints"][0]
98-
local_point_range = compute_local_range(comm, num_points_global)
99-
points_local = hdf["Points"][local_point_range[0] : local_point_range[1]]
119+
# Get number of points
120+
point_node = hdf["Points"]
121+
step_start = hdf["Steps"]["PointOffsets"][pos[0]]
100122

101-
# Connectivity read
102-
offsets = hdf["Offsets"]
103-
local_connectivity_offset = offsets[local_cell_range[0] : local_cell_range[1] + 1]
104-
topology = hdf["Connectivity"][local_connectivity_offset[0] : local_connectivity_offset[-1]]
105-
offset = local_connectivity_offset - local_connectivity_offset[0]
123+
# NOTE: currently, it doesn't seem like we follow:
124+
# https://docs.vtk.org/en/latest/vtk_file_formats/vtkhdf_file_format/vtkhdf_specifications.html#temporal-unstructuredgrid-and-polydata
125+
# As only one num_points is stored irregardless of time steps added.
126+
if hdf["NumberOfPoints"].shape[0] != len(stamps):
127+
num_pts = hdf["NumberOfPoints"][0]
128+
else:
129+
num_pts = hdf["NumberOfPoints"][pos[0]]
130+
lr = compute_local_range(comm, num_pts)
131+
points_local = point_node[step_start + lr[0] : step_start + lr[1]]
132+
133+
# Get cell-types in step
134+
cell_start = hdf["Steps"]["CellOffsets"][pos[0]]
135+
if hdf["NumberOfCells"].shape[0] != len(stamps):
136+
num_cells = hdf["NumberOfCells"][0]
137+
else:
138+
num_cells = hdf["NumberOfCells"][pos[0]]
139+
local_cell_range = compute_local_range(comm, num_cells)
140+
cell_types_local = hdf["Types"][
141+
cell_start + local_cell_range[0] : cell_start + local_cell_range[1]
142+
]
143+
144+
# Get connectivity in step
145+
connectivity_start = hdf["Steps"]["ConnectivityIdOffsets"][pos[0]]
146+
# Connectivity read
147+
offsets = hdf["Offsets"]
148+
local_connectivity_offset = offsets[
149+
connectivity_start + local_cell_range[0] : connectivity_start
150+
+ local_cell_range[1]
151+
+ 1
152+
]
153+
topology = hdf["Connectivity"][
154+
local_connectivity_offset[0] : local_connectivity_offset[-1]
155+
]
156+
offset = local_connectivity_offset - local_connectivity_offset[0]
106157

107158
# NOTE: Currently we limit ourselfs to a single celltype, as it makes life easier,
108159
# other things have to change in `MeshReadData` to support this.
@@ -172,13 +223,15 @@ def read_point_data(
172223
raise RuntimeError(f"Could not find {name}(t={time}) in {filename}.")
173224
elif len(pos) > 1:
174225
raise RuntimeError(f"Multiple time steps for {name}(t={time}) in {filename}")
175-
pd_offsets = hdf["Steps"]["PointDataOffsets"][name][:]
176-
pd_offsets = np.hstack([pd_offsets, [func_node.shape[0]]])
177-
178-
step_start = pd_offsets[pos[0]]
179-
step_end = pd_offsets[pos[0] + 1]
180-
global_size = step_end - step_start
181-
lr = compute_local_range(comm, global_size)
226+
step_start = hdf["Steps"]["PointDataOffsets"][name][pos[0]]
227+
# NOTE: currently, it doesn't seem like we follow:
228+
# https://docs.vtk.org/en/latest/vtk_file_formats/vtkhdf_file_format/vtkhdf_specifications.html#temporal-unstructuredgrid-and-polydata
229+
# As only one num_points is stored irregardless of time steps added.
230+
if hdf["NumberOfPoints"].shape[0] != len(stamps):
231+
num_pts = hdf["NumberOfPoints"][0]
232+
else:
233+
num_pts = hdf["NumberOfPoints"][pos[0]]
234+
lr = compute_local_range(comm, num_pts)
182235
return func_node[step_start + lr[0] : step_start + lr[1]], lr[0]
183236

184237

@@ -197,12 +250,37 @@ def read_cell_data(
197250
raise RuntimeError(f"No cell data found in {filename}.")
198251
cell_data = hdf["CellData"]
199252
assert cell_data is not None
253+
if name not in cell_data.keys():
254+
raise ValueError(f"No cell data with name {name} in {filename}")
200255
cell_data_node = cell_data[name]
201-
cell_data_shape = cell_data_node.shape
202-
num_cells_global = hdf["Types"].size
203-
assert num_cells_global == cell_data_shape[0]
204-
local_cell_range = compute_local_range(comm, num_cells_global)
205-
data = cell_data_node[slice(*local_cell_range)]
256+
assert cell_data_node is not None
257+
if time is None:
258+
cell_data_shape = cell_data_node.shape
259+
num_cells_global = hdf["Types"].size
260+
assert num_cells_global == cell_data_shape[0]
261+
local_cell_range = compute_local_range(comm, num_cells_global)
262+
data = cell_data_node[slice(*local_cell_range)]
263+
else:
264+
if "Steps" not in hdf.keys():
265+
raise RuntimeError(f"No timestepping information found in {filename}.")
266+
stamps = hdf["Steps"]["Values"][:]
267+
pos = np.flatnonzero(np.isclose(stamps, time))
268+
if len(pos) == 0:
269+
raise RuntimeError(f"Could not find {name}(t={time}) in {filename}.")
270+
elif len(pos) > 1:
271+
raise RuntimeError(f"Multiple time steps for {name}(t={time}) in {filename}")
272+
cd_start = hdf["Steps"]["CellDataOffsets"][name][pos[0]]
273+
274+
# NOTE: currently, it doesn't seem like we follow:
275+
# https://docs.vtk.org/en/latest/vtk_file_formats/vtkhdf_file_format/vtkhdf_specifications.html#temporal-unstructuredgrid-and-polydata
276+
# As only one num_points is stored irregardless of time steps added.
277+
if hdf["NumberOfCells"].shape[0] != len(stamps):
278+
number_of_cells = hdf["NumberOfCells"][0]
279+
else:
280+
number_of_cells = hdf["NumberOfCells"][pos[0]]
281+
lr = compute_local_range(comm, number_of_cells)
282+
data = cell_data_node[cd_start + lr[0] : cd_start + lr[1]]
283+
206284
# NOTE: THis could be optimized by hand-coding some communication in
207285
# `read_cell_data` on the frontend side
208286
md = read_mesh_data(filename, comm, time=time, read_from_partition=False, backend_args=None)

src/adios4dolfinx/backends/xdmf/backend.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ def get_default_backend_args(arguments: dict[str, Any] | None) -> dict[str, Any]
9393
def read_mesh_data(
9494
filename: Path | str,
9595
comm: MPI.Intracomm,
96-
time: float | None,
96+
time: str | float | None,
9797
read_from_partition: bool,
9898
backend_args: dict[str, Any] | None,
9999
) -> ReadMeshData:
@@ -109,6 +109,7 @@ def read_mesh_data(
109109
Returns:
110110
Internal data structure for the mesh data read from file
111111
"""
112+
assert read_from_partition == False
112113
check_file_exists(filename)
113114
with dolfinx.io.XDMFFile(comm, filename, "r") as file:
114115
cell_shape, cell_degree = file.read_cell_type()

src/adios4dolfinx/checkpointing.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -378,7 +378,7 @@ def read_mesh(
378378
filename: Path | str,
379379
comm: MPI.Intracomm,
380380
ghost_mode: dolfinx.mesh.GhostMode = dolfinx.mesh.GhostMode.shared_facet,
381-
time: float = 0.0,
381+
time: float | str | None = None,
382382
read_from_partition: bool = False,
383383
backend_args: dict[str, Any] | None = None,
384384
backend: str = "adios2",

tests/test_vtkhdf.py

Lines changed: 61 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import numpy as np
44
import pytest
55
from dolfinx.fem import Function
6-
from dolfinx.io.vtkhdf import write_mesh, write_point_data
7-
from dolfinx.mesh import create_unit_cube
6+
from dolfinx.io.vtkhdf import write_cell_data, write_mesh, write_point_data
7+
from dolfinx.mesh import CellType, compute_midpoints, create_unit_cube
88

99
import adios4dolfinx
1010

@@ -17,18 +17,19 @@ def g(x, t):
1717
return x[0], 2 * x[1], -x[2] * t
1818

1919

20+
@pytest.mark.parametrize("cell_type", [CellType.hexahedron, CellType.tetrahedron])
2021
@pytest.mark.parametrize("dtype", [np.float32, np.float64])
21-
def test_write_point_data(dtype, tmp_path):
22+
def test_write_point_data(dtype, tmp_path, cell_type):
2223
comm = MPI.COMM_WORLD
2324
tmp_path = comm.bcast(tmp_path, root=0)
2425
comm.barrier()
2526

2627
# Write temporal data
27-
mesh = create_unit_cube(comm, 5, 5, 5, dtype=dtype)
28-
filename = tmp_path / "point_data.vtkhdf"
28+
mesh = create_unit_cube(comm, 5, 5, 5, dtype=dtype, cell_type=cell_type)
29+
filename = tmp_path / f"point_data_{cell_type.name}.vtkhdf"
2930
write_mesh(str(filename), mesh)
3031

31-
t = np.linspace(0, 1.2, 25)
32+
t = np.linspace(0.1, 1.2, 25)
3233
num_nodes_local = mesh.geometry.index_map().size_local
3334
for ti in t:
3435
point_data = f(mesh.geometry.x.T[:, :num_nodes_local], ti)
@@ -44,7 +45,7 @@ def test_write_point_data(dtype, tmp_path):
4445
filename=filename, name="u", mesh=grid, time=tj, backend="vtkhdf"
4546
)
4647
v_ref = Function(u.function_space, dtype=u.x.array.dtype)
47-
atol = 5 * np.finfo(u.x.array.dtype).eps
48+
atol = 10 * np.finfo(u.x.array.dtype).eps
4849
v_ref.interpolate(lambda x: f(x, tj))
4950
np.testing.assert_allclose(u.x.array, v_ref.x.array, atol=atol)
5051

@@ -63,20 +64,61 @@ def test_write_point_data(dtype, tmp_path):
6364
filename=blocked_file, name="u", mesh=grid, time=tk, backend="vtkhdf"
6465
)
6566
v_ref = Function(u.function_space, dtype=u.x.array.dtype)
66-
atol = 5 * np.finfo(u.x.array.dtype).eps
67+
atol = 10 * np.finfo(u.x.array.dtype).eps
6768
v_ref.interpolate(lambda x: g(x, tk))
6869
np.testing.assert_allclose(u.x.array, v_ref.x.array, atol=atol)
6970

7071

71-
# @pytest.mark.parametrize("dtype", [np.float32, np.float64])
72-
# def test_write_cell_data(dtype, tmp_path):
73-
# comm = MPI.COMM_WORLD
74-
# tmp_path = comm.bcast(tmp_path, root=0)
75-
# comm.barrier()
72+
@pytest.mark.parametrize("cell_type", [CellType.hexahedron, CellType.tetrahedron])
73+
@pytest.mark.parametrize("dtype", [np.float32, np.float64])
74+
def test_write_cell_data(dtype, tmp_path, cell_type):
75+
comm = MPI.COMM_WORLD
76+
tmp_path = comm.bcast(tmp_path, root=0)
77+
comm.barrier()
78+
79+
# Write temporal data
80+
mesh = create_unit_cube(comm, 5, 5, 5, dtype=dtype, cell_type=cell_type)
81+
filename = tmp_path / f"cell_data_{cell_type.name}.vtkhdf"
82+
write_mesh(str(filename), mesh)
83+
84+
t = np.linspace(0.1, 1.2, 25)
85+
num_cells_local = mesh.topology.index_map(mesh.topology.dim).size_local
86+
midpoints = compute_midpoints(
87+
mesh, mesh.topology.dim, np.arange(num_cells_local, dtype=np.int32)
88+
)
89+
for ti in t:
90+
cell_data = f(midpoints.T, ti)
91+
write_cell_data(str(filename), mesh, cell_data, float(ti))
92+
comm.barrier()
7693

77-
# mesh = create_unit_cube(MPI.COMM_WORLD, 5, 5, 5, dtype=dtype, cell_type=CellType.hexahedron)
78-
# filename = "cell_data.vtkhdf"
79-
# write_mesh(filename, mesh)
80-
# cell_data = np.arange(mesh.topology.index_map(2).size_local * width)
81-
# for j in range(3):
82-
# write_cell_data(filename, mesh, cell_data, float(j))
94+
grid = adios4dolfinx.read_mesh(filename=filename, comm=comm, backend="vtkhdf")
95+
# Since we shuffle time we need to shuffle in the same way on each process
96+
np.random.shuffle(t)
97+
t = comm.bcast(t, root=0)
98+
for tj in t:
99+
u = adios4dolfinx.read_cell_data(
100+
filename=filename, name="u", mesh=grid, time=tj, backend="vtkhdf"
101+
)
102+
v_ref = Function(u.function_space, dtype=u.x.array.dtype)
103+
atol = 10 * np.finfo(u.x.array.dtype).eps
104+
v_ref.interpolate(lambda x: f(x, tj))
105+
np.testing.assert_allclose(u.x.array, v_ref.x.array, atol=atol)
106+
107+
# Test blocked data as well (with shuffled input timestep)
108+
blocked_file = filename.with_stem(filename.stem + "_blocked")
109+
write_mesh(str(blocked_file), mesh)
110+
for tj in t:
111+
cell_data = np.asarray(g(midpoints.T, tj)).T.flatten()
112+
write_cell_data(str(blocked_file), mesh, cell_data, float(tj))
113+
comm.barrier()
114+
115+
np.random.shuffle(t)
116+
t = comm.bcast(t, root=0)
117+
for tk in t:
118+
u = adios4dolfinx.read_cell_data(
119+
filename=blocked_file, name="u", mesh=grid, time=tk, backend="vtkhdf"
120+
)
121+
v_ref = Function(u.function_space, dtype=u.x.array.dtype)
122+
atol = 10 * np.finfo(u.x.array.dtype).eps
123+
v_ref.interpolate(lambda x: g(x, tk))
124+
np.testing.assert_allclose(u.x.array, v_ref.x.array, atol=atol)

0 commit comments

Comments
 (0)