PVTU — VTK XML parallel unstructured grid (.pvtu)
The index of a partitioned dataset: it declares the arrays every piece holds and names one .vtu piece per part, so a mesh split across ranks opens in ParaView as one dataset (v15.0.0). This is the on-disk face of partition: partition → .pvtu → read-merge returns the original mesh, up to point ordering. See the VTK XML file formats documentation and .pvtp for the .vtp twin.
| Format name | pvtu |
| Extensions | .pvtu |
| Read / Write | ✓ / ✓ |
| Extra dependencies | — (zlib for compressed pieces, as VTU) |
Reading & writing
import meshioplusplus
mesh = meshioplusplus.read("case.vtu")
labels = meshioplusplus.partition_labels(mesh, 4) # one Int64 array per cell block
mesh.cell_data["partition:part"] = labels
meshioplusplus.pvtu.write("case.pvtu", mesh, # carved by partition:part
binary=True, # base64-encode each piece's arrays instead of writing text
compression="zlib", # "zlib", "lz4", "zstd", or None
header_type=None, # "UInt32" (default) or "UInt64"
part_key="partition:part", # the integer cell_data array to carve by
)
merged = meshioplusplus.pvtu.read("case.pvtu") # every piece merged, one region each
second = meshioplusplus.pvtu.read("case.pvtu", piece=1) # or one piece alonemeshioplusplus.read/write dispatch on the extension, so .pvtu needs no explicit format name, and read(..., piece=k) works through the generic call (see selective reads).
To write the pieces partition returns — with their halo layers — hand them over as a list:
pieces = meshioplusplus.partition(mesh, 4, ghost_layers=1)
meshioplusplus.pvtu.write_pieces("case.pvtu", pieces)From the command line, a partition output path ending in .pvtu (or -o pvtu) with no {part} token writes one index over every part rather than one file per part:
meshioplusplus partition -n 4 --ghost-layers 1 case.vtu case.pvtuFile structure
Writing case.pvtu creates a sibling directory named after the index's own stem, holding one piece per part, numbered with zero padding (the width {step} patterns use: at least four digits):
case.pvtu
case/
case_0000.vtu
case_0001.vtu
case_0002.vtu
case_0003.vtu<?xml version="1.0"?>
<VTKFile type="PUnstructuredGrid" version="1.0" byte_order="LittleEndian">
<PUnstructuredGrid GhostLevel="1">
<PPointData>
<PDataArray type="Float64" Name="u"/>
<PDataArray type="UInt8" Name="vtkGhostType"/>
</PPointData>
<PCellData>
<PDataArray type="Float64" Name="c"/>
<PDataArray type="Int64" Name="partition:ghost"/>
<PDataArray type="UInt8" Name="vtkGhostType"/>
</PCellData>
<PPoints>
<PDataArray type="Float64" Name="Points" NumberOfComponents="3"/>
</PPoints>
<Piece Source="case/case_0000.vtu"/>
<Piece Source="case/case_0001.vtu"/>
</PUnstructuredGrid>
</VTKFile>The index carries no geometry and no data, only the declarations of the arrays (PPoints, PPointData, PCellData: name, type, component count) and one <Piece Source=> per part. Each piece is a standalone, independently readable .vtu file. Source= is always relative, always written with forward slashes, and XML-escaped.
The mesh side of the deal
writecarves the mesh by the integercell_dataarray named bypart_key(defaultpartition:part, whatpartition_labelsandpartition --labels-onlyproduce): one piece per part id0..max, each pruned to the points its own cells reference, so a point on a part boundary is duplicated into every piece that touches it. A part that owns no cells is still written as an (empty) piece, because skipping it would renumber the parts after it. Without the array the whole mesh is one piece. A non-integer or negative part array is refused.write_piecesis the primitive under it: already-carved pieces go in as they are. This is howpartition's output is written with its halo layers, since a single mesh cannot express a cell that is a ghost of several parts.- Every piece must declare identical arrays — name, type and component count, for
Points, point data and cell data. That is checked before anything is created, so a refusal leaves no directory and no half-written index; the error names the array, the piece and both declarations. A piece with no cells at all may omit its cell arrays (an idle rank allocated none). readparses the index, reads every piece with the best available.vtu/.vtpreader, and combines them withmerge()usingweld=false. Each piece becomes oneRegionKind::Cellregion namedpiece_0,piece_1, …. A point on a part boundary therefore appears once per piece;cleanwithweld=truefuses them:
merged = meshioplusplus.read("case.pvtu")
whole = meshioplusplus.clean(merged, weld=True) # the original mesh, up to point orderingpiece=kkeeps only piecek(negative counts from the end; out of range names the piece count) and attaches no region. A file with a single piece reads as that piece, with no region.- Piece paths are resolved against the index's own directory, so a partitioned case can be moved as a folder. A path with spaces,
&or other XML-escaped characters is read as written. An absolute path written on another machine that does not exist here is an error naming the attribute and the path — it is deliberately not searched for elsewhere, because a fallback could read the wrong file. - A
.pvtunames.vtupieces (and, leniently,.vtp). It does not nest: another.pvtuas a piece is refused by name, so no cycle is expressible. The layer above is.pvd, which may name.pvtufiles.
Ghost cells
partition(..., ghost_layers=N) grows each piece by N layers of neighbouring parts' cells and tags them partition:ghost (0 = owned, L = reached at layer L). Writing translates that into VTK's own vocabulary:
vtkGhostTypeon cells (UInt8):0for an owned cell,DUPLICATECELL(1) for a cell in any halo layer.vtkGhostTypeon points:DUPLICATEPOINT(1) when no owned cell of the piece uses the point, else0.GhostLevel="N"on the index, the deepest layer present.partition:ghostitself is written too, as an ordinaryInt64cell array:vtkGhostTypecollapses layer 2 onto layer 1, so keeping both makes meshio++ → meshio++ exact, and ParaView simply ignores the extra array.
An array you already named vtkGhostType is passed through with all of VTK's bits (REFINEDCELL = 8 and HIDDENCELL = 32 included) and nothing is fabricated when there is neither. It is always written as UInt8 on disk, whatever dtype the mesh holds it in (the NATIVE and KRATOS mesh backends widen integers to Int64): ParaView ignores a vtkGhostType array of any other type. The caller's meshes are never modified.
On read the ghost cells are kept by default — a reader must not silently discard data, and keeping is what makes read → write round-trip. ghosts="drop" (on meshioplusplus.read and the format's own read alike) removes every cell with any vtkGhostType bit set, and the points only those cells used, from each piece before merging, and then removes the ghost arrays:
kept = meshioplusplus.pvtu.read("case.pvtu") # more cells than the original: the halo is real data
whole = meshioplusplus.pvtu.read("case.pvtu", ghosts="drop") # the partition of unity againMerging does not deduplicate ghost cells by itself; ghosts="drop" is the precise tool for that.
The choice reaches every surface, each in its own idiom: meshioplusplus.read(path, ghosts="drop"), convert --drop-ghosts on both CLIs, the MCP ghosts argument, ReadOptions::mGhosts = GhostPolicy::Drop in C++, mio_read_opts.drop_ghosts in C, drop_ghosts=.true. in Fortran, ReadOptions(; drop_ghosts=true) in Julia, mio_read(drop_ghosts = TRUE) in R and readMeshSelective(path, { dropGhosts: true }) in WASM. It is honoured by .pvtu, .pvtp and .pvd and ignored by every other reader, like lenient: a file with no halo is already the answer it asks for. Adding it to ReadOptions is what took the C++ ABI to 15; sizeof(ReadOptions) did not move (the member sits in tail padding), which the ABI history records.
Data mapping
| meshio++ | PVTU |
|---|---|
points | duplicated into every piece that references them |
point_data | copied onto every piece and declared under PPointData |
cell_data | each piece gets its own cells' slice, declared under PCellData |
partition:part | the carving key; kept as an ordinary array in each piece |
partition:ghost | kept, and translated to vtkGhostType + GhostLevel |
field_data | dataset-global: written in <FieldData> on the grid of every piece, and read back as the union across pieces (the first piece carrying a name wins), never namespaced 0:name, 1:name |
| named cell regions | recovered as piece_<i> on read, not round-tripped on write |
Quirks & limitations
- Field data is one value per dataset, repeated per piece. Every piece carries the same
<FieldData>and the reader takes the union rather thanmerge()'s renaming, so aTimeValueevery piece repeats staysTimeValue; a name only some pieces carry is kept. See VTU for the element's layout. - The Python reference handles rectangular cell blocks only when carving, deriving ghost arrays or dropping ghosts; a polygon or polyhedron block raises
NotImplementedErrorthere, and the C++ core (the normal path) handles them. - Regions are not round-tripped symmetrically:
writedoes not look at existing regions, whilereadalways attaches one per piece. - The index's own
byte_orderandheader_typeattributes are not used for reading: each piece's own header is authoritative, so an index and its pieces may disagree. - ParaView itself is driven by the tests (
tests/python/test_pvd_paraview.py, throughpvpython, skipped where it is absent): version 6.1.1 opens a ghosted.pvtu, recognises everyvtkGhostTypeflag we write as a ghost cell, reads the pieces and the dataset's field data. Alongside it, VTK's ownvtkXMLPUnstructuredGridReader/vtkXMLPPolyDataReaderread every file either engine writes, and both engines read whatvtkXMLPUnstructuredGridWriterwrites.
Notes
The pieces reuse the exact .vtu writer/reader every other VTK XML piece file uses — compression="zlib|lz4|zstd" works here exactly as it does for .vtu. The pure-Python reference (meshioplusplus.pvtu._pvtu, over meshioplusplus._pvtk_index) delegates each piece to the public meshioplusplus.vtu/vtp readers/writers, so pieces still get C++ acceleration when the core is present; both engines are tested against each other and against VTK.
See also
.pvtp— the same index over.vtppieces..pvd— a time-indexed collection, whose entries may be.pvtufiles.- VTU — what every piece actually is; VTM — the index that carves by cell block instead of by part.
- partition — the operation whose output this is, halo layers included.
- merge — the operation the reader is built on; selective reads —
piece=.