Skip to content

VTK algorithms take AMR hierarchies: whole where Tack does, else box by box - #341

Merged
berkgeveci merged 4 commits into
Kitware:mainfrom
berkgeveci:vis/vtk-algorithms-amr
Nov 5, 2026
Merged

berkgeveci merged 4 commits into
Kitware:mainfrom
berkgeveci:vis/vtk-algorithms-amr

Conversation

@berkgeveci

Copy link
Copy Markdown
Collaborator

Stacked on #340 and #301 (their commits show here until they merge; this merges after them). The sixth step of the VTK algorithms plan (#302): AMR.

Before this, a vtkOverlappingAMR given to any wrapper gave an empty hierarchy and no error: VTK makes a hierarchy a subclass of vtkPartitionedDataSetCollection, so it went down the tree path into an output of the hierarchy's type that could not hold a collection.

Now a hierarchy is one Tack AMRDataSet, its boxes marked with what a finer box covers (REFINEDCELL) and what a sibling holds:

  • Taken whole where the Tack function takes a hierarchy, declared with vtkOverlappingAMR as the port's input type: Probe (as its source: each point at its finest box), Statistics, Entropy, Integrate, Voxelize, GhostCells, and the new AverageDown (td.average_down on a copy of the field, the input left as it was). Each region counts once. GhostCells and AverageDown give a vtkOverlappingAMR of the input's boxes (with metadata of its own); the grown blocks go out with ghost layers.
  • Box by box for every other wrapper, as Tack's filters run on AMR (the AMR proposal): each level's boxes as the pieces of a partitioned dataset, the covered cells left out by their marks. A field filter gives the hierarchy back with its arrays; one that makes a dataset a vtkPartitionedDataSetCollection with a partitioned dataset per level (level0, ...), contours cracked between levels as documented.
  • Refused: GenerateGlobalIds, Streamlines, Advect (box by box: ids repeated per level, paths ending at a finer box), and a hierarchy on any other port.

The cache: a hierarchy's Tack conversion is kept on its vtkOverlappingAMRMetaData, which VTK shares between a hierarchy and its shallow copies (the pythonic call returns one), valid for its blocks' arrays by address and modification time (the hierarchy's own modification time does not follow its blocks'). So the grown hierarchy from GhostCells reaches the next AMR filter in a pipeline or as returned; converting it again would refuse its ghost layers. Over MPI ranks the decision to convert again is agreed on every rank, since converting is collective.

vtk_to_dataset(..., world=): a hierarchy spread over ranks is converted together, in VTK's world by default. Before, each rank marked its boxes as if its own were the whole hierarchy.

Tested:

  • The whole-hierarchy wrappers against td.statistics/entropy/integrate/probe/voxelize/ghost_cells/average_down on VTK's Gaussian pulse; the domain's volume.
  • Box by box: Elevation per block and its Tack hierarchy kept; Threshold per level against td.threshold of the marked boxes; a Project >> Contour pipeline; Probe at a hierarchy's points.
  • The cache through shallow copies, a modified array, an added array and a replaced block; a simulated second device.
  • 3 MPI ranks, the blocks dealt round the ranks (world given, and from VTK's controller on a local master build): Statistics, GhostCells >> Statistics, Integrate, AverageDown and Threshold box by box against the whole.

VTK and AMR tests: 245 passed on the 9.7.1 wheel, 261 on a local VTK master; 3 MPI ranks on both; quick tier 3047 passed; lint and the guide's AMR example clean.

🤖 Generated with Claude Code

…ped at the next use

The cache of Tack datasets per VTK dataset drops an entry from a DeleteEvent
observer, a function only the VTK object referred to. When Python's collector
freed such a dataset in a reference cycle made after it was read (a failing
test's traceback holding its frames), it took the observer for garbage too and
cleared its globals and closure first; the deletion then called it, and Python
segfaulted (_PyEval_EvalFrameDefault from vtkPythonCommand::Execute, from
vtkObject::ObjectFinalize, from the collector). VTK 9.7.1 and master both; a
VTK-only reproduction is VTK bugs 19 in the vault.

The observers are now kept in a module-level dict until they fire, and do no
more than note the address; the cache drops the entry, and the observer, at its
next use, outside whatever freed the dataset. The regression test makes the
cycle after the dataset was read, as the crash needs; it segfaulted before.
… the input

The summaries reduce everything they are given -- every dataset of a partitioned
dataset or a tree, on every MPI rank (the run's world) -- to one answer, as VTK's
vtkDescriptiveStatistics and vtkIntegrateAttributes reduce composite data; every
rank gets it.

- Statistics: a vtkTable, a row per field and a vector field's components one by
  one (ExtractComponent), columns field, where, component, td.statistics' values
  under Tack's names, and the unit.
- Entropy: the same rows, with the entropy in bits over `bins` bins.
- Integrate: vtkIntegrateAttributes' output, one vertex at the measure-weighted
  centroid (the integral of position over the measure), each point field's
  integral as point data and each cell field's as cell data, the measure as
  Length, Area or Volume by the cells' dimension.
- Fields: `fields`, SetInputArrayToProcess, or every field.

Held to td.statistics and td.entropy called directly, Integrate to
vtkIntegrateAttributes (centroid, volume, a point and a vector cell field), pieces
and trees to the whole (each shared point counted once where GhostCells or
GenerateGlobalIds mark its owner), a surface's measure to its area, and 3 MPI
ranks to the whole.
…per, no observer

The cache of Tack datasets per VTK dataset kept entries by address and dropped
them from a DeleteEvent observer, which needed a registry to keep the observers
reachable (VTK bugs 19: the collector cleared one as garbage, then the deletion
called it) and deferred drops. The entry is now an attribute of the dataset's
Python wrapper: VTK keeps a wrapper's attributes while its C++ object lives, on
every later wrapper of it (GetInputData's in the next filter), and frees them with
the object, so an entry lives exactly as long as its dataset with no observer.

Measured (yavin, the wheel and a VTK master build): on master a wrapper costs
0.6 us however many objects keep attributes (10,000), and Contour over 1,000 and
5,000 boxes runs as fast with the attribute as with the observer (951 against 943
ms, 4,803 against 4,817); VTK 9.7.1 sweeps kept attributes at every wrapper (50 us
with 10,000), which it does for the attributes the pythonic API gives every wrapper
anyway, and the two caches cost the same there too.

An entry is stale when the dataset's modification time or time step differs from
when it was cached, as before. The comment says what that sees: Modified() (an
array's, the points'), arrays added or removed; not values written in place without
it, nor InsertNextCell, which VTK's own pipeline would not run again for either.
Tests: the entry outlives the wrapper and goes with the dataset, goes in the
collector's cycle (the crash's case), and is stale after Modified() or a new time
step but not after a write without it.
…by box

A vtkOverlappingAMR is one Tack AMRDataSet. Probe (as its source), Statistics,
Entropy, Integrate, Voxelize, GhostCells and the new AverageDown take it whole,
declaring vtkOverlappingAMR as an input type; every other wrapper runs box by
box, each level's boxes as pieces with the covered cells left out, a field
filter giving the hierarchy back and one that makes a dataset a partitioned
collection per level. GenerateGlobalIds, Streamlines and Advect refuse one.
Before, a hierarchy (a vtkPartitionedDataSetCollection subclass) went down the
tree path into an empty output with no error.

A hierarchy's conversion is kept on its metadata, which shallow copies share,
keyed by its blocks' arrays, so GhostCells' grown hierarchy reaches the next
filter. vtk_to_dataset takes world= for a hierarchy spread over MPI ranks.
@berkgeveci
berkgeveci merged commit 557cf6c into Kitware:main Nov 5, 2026
31 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant