Python bindings for the xdmf crate, built with pyo3/maturin: write meshes with time-series data as XDMF files, for ParaView or VisIt to read.
pip install xdmfOne abi3 wheel per platform (Linux x86_64/aarch64, macOS x86_64/arm64, Windows x86_64) covers
every Python >= 3.9, and carries its own HDF5 -- xdmf.is_hdf5_enabled() says whether a given
build has it.
To build from the repository instead, which links a system HDF5:
pip install ./pythonPass the mesh and the data as numpy arrays. The bindings borrow them rather than copy them, and
write each one at the dtype you pass (float64, float32, uint64, uint32, int64, int32),
as in Rust:
import numpy as np
import xdmf
writer = xdmf.TimeSeriesWriter("xdmf_writing", xdmf.DataStorage.Hdf5SingleFile)
# define 3 points and 2 cells (a line and a triangle)
coords = np.array([0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 1.0, 0.0])
connectivity = np.array([0, 1, 0, 2, 1], dtype=np.uint32) # line (0,1) and triangle (0,2,1)
cell_types = [xdmf.CellType.Edge, xdmf.CellType.Triangle]
# write the mesh, then the data for 10 time steps
with writer.write_mesh(coords, connectivity, cell_types) as data_writer:
for step in range(10):
point_values = np.full(9, float(step))
data_writer.write_time_step(
str(step),
point_data=[("point_data", xdmf.DataAttribute.VECTOR, point_values)],
cell_data=[("cell_data", xdmf.DataAttribute.SCALAR, np.array([0.0, 1.0]))],
)write_mesh_with_submeshes splits the mesh into named blocks, which ParaView lists in its
Multi-block Inspector (View -> Multi-block Inspector) and shows or hides one at a time. Each
(name, cells) pair names the cells belonging to one block, as a range, a numpy integer array or
a sequence of int:
writer = xdmf.TimeSeriesWriter("xdmf_blocks", xdmf.DataStorage.Hdf5SingleFile)
with writer.write_mesh_with_submeshes(
coords, connectivity, cell_types, [("line", range(0, 1)), ("triangle", [1])]
) as data_writer:
# the data still covers the whole mesh, and each block gets its share
data_writer.write_time_step(
"0.0",
point_data=[("point_data", xdmf.DataAttribute.VECTOR, np.zeros(9))],
cell_data=[("material", xdmf.DataAttribute.SCALAR, np.array([10.0, 20.0]))],
)Give a block of consecutive cells as range(start, stop) to save storage space.
Blocks may overlap, every cell has to belong to at least one, and no two may share a name.
tests/test_writer.py has many more examples, including every failure case.
- Points and vector data may also be shaped
(N, 3): a C-contiguous(N, 3)array is the same memory as the flat one, so it needs noreshape. Points are the one array whose shape is checked, so the transposed(3, N)layout raises instead of passing as interleaved coordinates. cell_typesalso takes a numpy array of cell type codes, in any integer dtype. Those codes are theCellTypevalues, i.e. the XDMF topology types and not the VTK cell codes: a hexahedron is 9 here and 12 in VTK. (The node ordering within a cell does follow VTK.)- The file name is a
stror anyos.PathLike, andwriter.file_namehands back thepathlib.Pathit writes. - A time step is all-or-nothing, as in Rust: one rejected attribute drops the whole step, heavy data and all, and leaves the time free again.
- The data writer is a context manager, so the
withblock closes the HDF5 file instead of the garbage collector.close()does the same. - Writes release the GIL, so other threads keep running and threads writing their own file write in parallel. The arrays are borrowed, so no thread may modify one mid-write.
- A rejected
write_meshleaves the writer usable, so fix the dtype or shape and retry. Only a successful call consumes it, as in Rust. DataStorage,DataAttributeandCellTypeare frozen, comparable and hashable, so they work asdictkeys and in sets.- Type stubs (
xdmf.pyi) ship with the package.