# HyperTreeGrid support in VTKHDF

**URL:** https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150
**Category:** Development
**Tags:** proposal
**Created:** [January 14, 2025, 3:12pm UTC](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150 "2025-01-14T15:12:24Z")
**Posts on this page:** 20
**Page:** 1

<div class="post-metadata">

### Author: ![Louis\_Gombert](https://discourse.vtk.org/user_avatar/discourse.vtk.org/louis_gombert/32/7766_2.png) [@Louis\_Gombert](https://discourse.vtk.org/u/Louis_Gombert)
#### Post date: [January 14, 2025, 3:12pm UTC](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150/1 "2025-01-14T15:12:24Z")

</div>

The overlappingAMR data structure has been [recently added to the VTKHDF specification](https://discourse.vtk.org/t/overlapping-amr-support-in-vtkhdf/7868), now is time for its cousin, the Hyper Tree Grid (HTG for short) to be specified as well.

HTG is a compact and memory-efficient tree-based AMR data structure, that has been around in VTK for more than a decade. Many HTG-specific algorithms have been created, using a cursor mechanism coupled with tree iteration to browse through it. HTGs can be stored in the VTK XML format using the .htg extension. The parallel .phtg implementation is currently in a non-functionning state.

This post suggests a specification for the up-and-coming VTKHDF format for HTG, which will allow efficient distributed writing and reading, temporal support, and composite structures, distributed in a single or in multiple files.

The general structure of the file would look like this

```auto
GROUP "VTKHDF"
    ATTRIBUTE "Version" // Updated to (2,4)
    ATTRIBUTE "Type" // "HyperTreeGrid" In this case

    // HTG-specific properties
    ATTRIBUTE "BranchFactor" // 2 or 3, cell division factor
    ATTRIBUTE "Dimensions" // 3-Vector
    ATTRIBUTE "InterfaceInterceptsName" // String referencing a cell data array
    ATTRIBUTE "InterfaceNormalsName" // String referencing a cell data array
    ATTRIBUTE "TransposedRootIndexing" // Bool, true if the indexing mode of the grid is inverted

    // Grid point coordinates
    DATASET "XCoordinates"
    DATASET "YCoordinates"
    DATASET "ZCoordinates"

    // HTG Specific fields
    DATASET "NumberOfTrees" // One entry for each distributed part
    DATASET "DescriptorsSize" // One entry for each distributed part
    DATASET "DepthPerTree" // size = sum(NumberOfTrees), maximum depth for each tree
    DATASET "Descriptors" // Packed bit array, size = sum(DescriptorsSize)
    DATASET "NumberOfCellsPerDepth" // size = sum(DepthPerTree), number of cells for each depth of each tree
    DATASET "TreeIds" // size = sum(NumberOfTrees)
    DATASET "Mask" // Packed bit array, size = sum(NumberOfCellsPerDepth)

    GROUP "FieldData"
        ...
    GROUP "CellData"
        ...

```

If you’re aware of the .htg v2 format, this is mostly a mapping of its content, with the addition of a few offset fields to support efficient distributed reading.

Misc. Notes:

- “NumberOfTrees” has one value for each part of the distributed dataset, and is optional for non-distributed data or if the number of trees in each part is the same.
- “DescriptorsSize” is required for distributed datasets and used as an offset array in the “Descriptors” bit set. Without it, each rank would need to process all of the previous trees to calculate the read offset for descriptors.
- “TreeIds” must have all values 0-\>N, but in any order. This allows distributing trees across parts
- “Mask” is optional, and omitted if no cell is masked.
- “NumberOfCellsPerDepth” is renamed from “NumberOfVerticesPerDepth” in the XML format. It allows for faster reading when limiting reading depth. The reader can compute the number of cells to offset for a given tree using this value and “DepthPerTree”.
- There is no “PointData” for the HTG structure, because we only consider cells.
- For temporal, offsets for NumberOfTrees, Descriptors, NumberOfCellsPerDepth and NumberOfCells will be required in the “Steps” group, similarly to the other types of VTKHDF datasets. This way, the reader can easily pick up any time step without any pre-computation.

Any comment or suggestion is welcome!

cc @mwestphal @Charles_Gueunet @hakostra @lgivord

---

<div class="post-metadata">

### Author: ![mwestphal](https://discourse.vtk.org/user_avatar/discourse.vtk.org/mwestphal/32/19_2.png) [@mwestphal](https://discourse.vtk.org/u/mwestphal)
#### Post date: [January 14, 2025, 3:18pm UTC](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150/2 "2025-01-14T15:18:32Z")

</div>

Nice writeup.

> [@Louis\_Gombert](#):
>
> The parallel .phtg implementation is currently in a non-functionning state.

Could you clarify if distributed data is supported in this VTKHDF specification ?

---

<div class="post-metadata">

### Author: ![Louis\_Gombert](https://discourse.vtk.org/user_avatar/discourse.vtk.org/louis_gombert/32/7766_2.png) [@Louis\_Gombert](https://discourse.vtk.org/u/Louis_Gombert)
#### Post date: [January 14, 2025, 3:21pm UTC](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150/3 "2025-01-14T15:21:05Z")

</div>

It totally is. The sentence you quoted is about the current XML PHTG data format, which I could not get to work properly.

---

<div class="post-metadata">

### Author: ![hakostra](https://discourse.vtk.org/user_avatar/discourse.vtk.org/hakostra/32/2990_2.png) [@hakostra](https://discourse.vtk.org/u/hakostra)
#### Post date: [January 15, 2025, 9:14am UTC](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150/4 "2025-01-15T09:14:47Z")

</div>

There are two datasets in the proposal, `Descriptors` and `Mask` that are described as packed bit arrays.

My experience with packed bit arrays (in my interpretation a datatype a bool 0/1 value with 1 bit storage space, rounded up to the nearest byte in length) is that different languages/frameworks work differently.

Would the following Python example give the desired encoding?

```auto
import h5py as h5
import numpy as np

bitlist = np.zeros(100, dtype=np.uint8)

with h5.File('bitfield.h5', 'w') as fh:
    bitfield = np.packbits(bitlist)
    fh.create_dataset('bitfield', data=bitfield)

```

100 bool elements in the “bitlist” (in which 0 = false, any other value = true) occupy 12.5 bytes and the dataset in the file therefore ends up being 13 elements of `uint8` type.

Or is this not what you expect?

I know `vtkBitArray` is encoded such that each byte stores eight bool values, but not every programming language and framework has such a class/datatype natively. I think one of the great advantages of `vtkhdf` is that it is relatively easy to write custom writer functions/methods/classes/tools from applications in various languages (Python, C, C++, Fortran, …) without relying on the vtk library itself. The writing of these packed bit arrays needs to be possible without too much manual encoding work from all of these languages.

---

<div class="post-metadata">

### Author: ![Louis\_Gombert](https://discourse.vtk.org/user_avatar/discourse.vtk.org/louis_gombert/32/7766_2.png) [@Louis\_Gombert](https://discourse.vtk.org/u/Louis_Gombert)
#### Post date: [January 15, 2025, 9:31am UTC](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150/5 "2025-01-15T09:31:39Z")

</div>

Hey Håkon, thanks for your feedback. I did not share it but it is exactly what I’m doing in my test writer:

```auto
    descriptors = (
          # Tree 0
          1, # Depth 1
            0, 0, 1, 0, 0, 0, 0, 0, # Depth 2
          # Tree 1
          1, # Depth 1
            0, 1, 0, 0, 0, 0, 0, 0, # Depth 2
          # Tree 3 : refined
          1,
          # Tree 4 : not refined
    )
    packed_descriptors = np.packbits(descriptors)
    root.create_dataset("Descriptors", data=packed_descriptors)

```

After a quick survey of bit packing techniques, using uint8 seem like a safe bet.

---

<div class="post-metadata">

### Author: ![hakostra](https://discourse.vtk.org/user_avatar/discourse.vtk.org/hakostra/32/2990_2.png) [@hakostra](https://discourse.vtk.org/u/hakostra)
#### Post date: [January 16, 2025, 11:49am UTC](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150/6 "2025-01-16T11:49:19Z")

</div>

Seems like a reasonable approach. I asked because I the HDF5 library has some mention on bitfields in the manual:

[https://support.hdfgroup.org/documentation/hdf5/latest/\_h5\_t\_\_u\_g.html#subsubsec\_datatype\_other\_bitfield](https://support.hdfgroup.org/documentation/hdf5/latest/_h5_t__u_g.html#subsubsec_datatype_other_bitfield)

but I do not exactly know what features the library offer, and for instance what features are exposed and available from important packages like h5py.

I think your suggestion of manually packing the bitfield is sensible because you can achieve it easily in any language that allows bit-manipulation of integers.

---

<div class="post-metadata">

### Author: ![mstauffert](https://discourse.vtk.org/letter_avatar_proxy/v4/letter/m/5f8ce5/32.png) [@mstauffert](https://discourse.vtk.org/u/mstauffert)
#### Post date: [February 5, 2025, 3:26pm UTC](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150/7 "2025-02-05T15:26:52Z")

</div>

Thank you for this topic.

I have a question on dimension attribute, shouldn’t it be 1, 2 or 3 ?

```auto
ATTRIBUTE "Dimensions" // 3-Vector

```

We really rarely use 1 dimension but my point was just because I don’t get what 3-Vector means.

---

<div class="post-metadata">

### Author: ![Louis\_Gombert](https://discourse.vtk.org/user_avatar/discourse.vtk.org/louis_gombert/32/7766_2.png) [@Louis\_Gombert](https://discourse.vtk.org/u/Louis_Gombert)
#### Post date: [February 5, 2025, 4:23pm UTC](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150/8 "2025-02-05T16:23:15Z")

</div>

Hello Maxime, the `Dimensions` attribute corresponds to the number of trees in each direction, so you’re right, it can have 1, 2 or 3 components whether we’re describing a 1D, 2D or 3D HTG. It is not necessarily a 3 components vector as I wrote initially.

---

<div class="post-metadata">

### Author: ![mstauffert](https://discourse.vtk.org/letter_avatar_proxy/v4/letter/m/5f8ce5/32.png) [@mstauffert](https://discourse.vtk.org/u/mstauffert)
#### Post date: [February 12, 2025, 8:50am UTC](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150/9 "2025-02-12T08:50:19Z")

</div>

Hello Louis, thank you for the answer. I have another question: `NumberOfCellsPerDepth` shouldn’t be named `NumberOfCellsPerDepthPerTree` ? Even if it seems too long, it is more accurate no ?

---

<div class="post-metadata">

### Author: ![Louis\_Gombert](https://discourse.vtk.org/user_avatar/discourse.vtk.org/louis_gombert/32/7766_2.png) [@Louis\_Gombert](https://discourse.vtk.org/u/Louis_Gombert)
#### Post date: [February 12, 2025, 9:51am UTC](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150/10 "2025-02-12T09:51:57Z")

</div>

That’s right, `NumberOfCellsPerDepth` actually defines the number of Cells for each depth of each tree. I kept this name to mirror the name used in the XML HTG reader, but it can be changed to more accurately represent its content. `NumberOfCellsPerTreeDepth` sounds acceptable to you?

---

<div class="post-metadata">

### Author: ![mstauffert](https://discourse.vtk.org/letter_avatar_proxy/v4/letter/m/5f8ce5/32.png) [@mstauffert](https://discourse.vtk.org/u/mstauffert)
#### Post date: [February 12, 2025, 10:49am UTC](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150/11 "2025-02-12T10:49:04Z")

</div>

`NumberOfCellsPerTreeDepth` sounds better than what I proposed. Thank you for this HTG structure and answers. I look forward to use it when it will be available

---

<div class="post-metadata">

### Author: ![omdaniel](https://discourse.vtk.org/user_avatar/discourse.vtk.org/omdaniel/32/8538_2.png) [@omdaniel](https://discourse.vtk.org/u/omdaniel)
#### Post date: [February 13, 2025, 1:23pm UTC](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150/12 "2025-02-13T13:23:41Z")

</div>

Can this support something similar to ForceStaticMesh (ei ForceStaticTree) where a the cell attribute data can be changing in time with respect to a static tree such that visualization in VTK/paraview can optimize cycling through time steps without re-reading the underlying tree

---

<div class="post-metadata">

### Author: ![Louis\_Gombert](https://discourse.vtk.org/user_avatar/discourse.vtk.org/louis_gombert/32/7766_2.png) [@Louis\_Gombert](https://discourse.vtk.org/u/Louis_Gombert)
#### Post date: [February 13, 2025, 1:52pm UTC](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150/13 "2025-02-13T13:52:09Z")

</div>

Yes, static trees will also be supported, the same way static meshes work with the other data types supported by the VTKHDF specification; the fields in the `Steps` group specify a read offset for the other arrays for each time step, such as “Descriptors” in our case. If 2 time steps use the same read offset for an array, then the tree geometry can only be read once and reused. You can read more about time support in VTKHDF here: [VTK File Formats - VTK documentation](https://docs.vtk.org/en/latest/design_documents/VTKFileFormats.html#temporal-data)

---

<div class="post-metadata">

### Author: ![omdaniel](https://discourse.vtk.org/user_avatar/discourse.vtk.org/omdaniel/32/8538_2.png) [@omdaniel](https://discourse.vtk.org/u/omdaniel)
#### Post date: [February 14, 2025, 7:18pm UTC](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150/14 "2025-02-14T19:18:07Z")

</div>

Last April 2024 I asked about static geometry with time varying data here [HTG time thread](https://discourse.vtk.org/t/hypertreegrid-multicomponent-scalar-for-time/13750/3) @Charles_Gueunet commented at the time “You can force the static mesh on a dataset, but nothing will really use this information.” Are downsteam filters better able to use static topologies in VTK 9.4+ compared to 10 months ago.

---

<div class="post-metadata">

### Author: ![mwestphal](https://discourse.vtk.org/user_avatar/discourse.vtk.org/mwestphal/32/19_2.png) [@mwestphal](https://discourse.vtk.org/u/mwestphal)
#### Post date: [February 17, 2025, 8:57am UTC](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150/15 "2025-02-17T08:57:18Z")

</div>

Hi @omdaniel

VTKHDF natively support static mesh in the sense that same geometry will not be read when changing timesteps, which is already a good speedup source.

No HTG filter uses this information to optimize their processing, yet. This could be added in the future though.

---

<div class="post-metadata">

### Author: ![omdaniel](https://discourse.vtk.org/user_avatar/discourse.vtk.org/omdaniel/32/8538_2.png) [@omdaniel](https://discourse.vtk.org/u/omdaniel)
#### Post date: [April 23, 2025, 2:58pm UTC](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150/16 "2025-04-23T14:58:54Z")

</div>

@Louis_Gombert great work on the HTG integration into vtkHDF. I saw the merge request and wanted to know if you think it may be accepted before the feature freeze for vtk 9.5?

---

<div class="post-metadata">

### Author: ![Louis\_Gombert](https://discourse.vtk.org/user_avatar/discourse.vtk.org/louis_gombert/32/7766_2.png) [@Louis\_Gombert](https://discourse.vtk.org/u/Louis_Gombert)
#### Post date: [April 23, 2025, 3:01pm UTC](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150/17 "2025-04-23T15:01:03Z")

</div>

Yes that’s my goal, it should be in VTK 9.5 and ParaView 6.0. Thanks for checking it out!

---

<div class="post-metadata">

### Author: ![colive](https://discourse.vtk.org/letter_avatar_proxy/v4/letter/c/d07c76/32.png) [@colive](https://discourse.vtk.org/u/colive)
#### Post date: [June 4, 2025, 3:17pm UTC](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150/18 "2025-06-04T15:17:34Z")

</div>

Is it possible to get some guidance about how the distributed data will work w.r.t. duplicated trees between difference pieces? For example could one tree (e.g. ID 0) in one piece have descriptors like 1 0 0 0 1 where another piece has descriptors for the same tree (also ID 0) like 1 1 0 0 0 and if so how VTK interprets this? Or is is strictly necessary that the pieces are disjoint sets of trees?

---

<div class="post-metadata">

### Author: ![Louis\_Gombert](https://discourse.vtk.org/user_avatar/discourse.vtk.org/louis_gombert/32/7766_2.png) [@Louis\_Gombert](https://discourse.vtk.org/u/Louis_Gombert)
#### Post date: [June 5, 2025, 7:30am UTC](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150/19 "2025-06-05T07:30:34Z")

</div>

Tree Ids do not need to be globally unique across pieces. Descriptors are read separately for each piece.

See for example this file from the testing set:  
[multipiece\_htg.hdf](https://discourse.vtk.org/uploads/short-url/fWs8jtWSK6d76uwJGFr3YFy6lPe.hdf) (124.6 KB)

Each piece has 15 trees (NumberOfTrees field), and a descriptor of size 1. In this case, the descriptors are the same for each piece, because only 1 tree is refined to level 2 for each piece, but they could totally be different.

---

<div class="post-metadata">

### Author: ![colive](https://discourse.vtk.org/letter_avatar_proxy/v4/letter/c/d07c76/32.png) [@colive](https://discourse.vtk.org/u/colive)
#### Post date: [June 5, 2025, 11:41am UTC](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150/20 "2025-06-05T11:41:33Z")

</div>

I wrote a small test file/script to see how paraview (6.0 RC-1) interprets different descriptors. It seems that if you give conflicting descriptors for the same tree it only considers the last piece rather than some sort of union of the two. Maybe this is intentional but unfortunately my tree-AMR program does domain decomposition within a single tree and so I’m not sure I will be able to make use it of it if that is the case.

Since I can’t upload the file as a new user I’ll put the python code I generated it with here:

**EDIT:** The file is attached below. The two descriptor fields are “1 1100” and “1 0110” but if you load the file you may see that only a tree with the second piece’s descriptors is visible.

```auto
import h5py
import numpy as np

branch_factor = np.array(2, dtype='i8') # H5T_STD_I64LE scalar
dimensions = np.array([2, 2, 1], dtype='i8') # H5T_STD_I64LE shape=(3,)
transposed_root_indexing = np.array(0, dtype='i8') # H5T_STD_I64LE scalar

type_dtype = h5py.string_dtype(encoding='ascii', length=13)
type_value = np.array("HyperTreeGrid", dtype=type_dtype)
version = np.array([2, 4], dtype='i8') # H5T_STD_I64LE shape=(2,)

def pack_binary_string(bitstr: str) -> np.ndarray:
    bits = "".join(bitstr.split())
    nbits = len(bits)
    nbytes = (nbits + 7) // 8 
    arr = np.zeros(nbytes, dtype='u1')
    for i in range(nbytes):
        chunk = bits[i*8 : i*8 + 8]
        if len(chunk) < 8:
            chunk = chunk.ljust(8, "0")
        arr[i] = int(chunk, 2)

    return arr

descriptors = pack_binary_string("1 1100 000 1 0110 000")
descriptors_size = np.array([8,8], dtype='i8')
number_of_cells = np.array([9,9], dtype='i8')
depth_per_tree = np.array([3,3], dtype='i8')
number_of_cells_per_tree_depth = np.array([1,4,4,1,4,4], dtype='i8')
number_of_depths = np.array([3,3], dtype='i8')
number_of_trees = np.array([1,1], dtype='i8')
tree_ids = np.array([0,0],dtype='i8')
x_coordinates = np.array([0,1], dtype='f8')
y_coordinates = np.array([0,1], dtype='f8')
z_coordinates = np.array([0], dtype='f8')

# -----------------------------------------------------------------------------
# 2) Create the HDF5 file and reproduce the exact group/dataset structure
# -----------------------------------------------------------------------------
import os 

script_path = os.path.abspath( __file__ )
script_dir = os.path.dirname(script_path)
htg_path = os.path.join(script_dir, "multipiece_htg.hdf")

with h5py.File(htg_path, "w") as f:
    # 2.1 Create the top‐level VTKHDF group
    vtkgrp = f.create_group("VTKHDF")

    # 2.2 Write the five attributes onto "/VTKHDF"
    vtkgrp.attrs.create("BranchFactor", branch_factor, dtype='i8')
    vtkgrp.attrs.create("Dimensions", dimensions, dtype='i8')
    vtkgrp.attrs.create("TransposedRootIndexing", transposed_root_indexing, dtype='i8')
    vtkgrp.attrs.create("Type", type_value, dtype=type_dtype)
    vtkgrp.attrs.create("Version", version, dtype='i8')

    # 2.3 Create "/VTKHDF/CellData" and its "Depth" dataset
    celldata_grp = vtkgrp.create_group("CellData")

    # 2.4 Create all other datasets directly under "/VTKHDF"
    vtkgrp.create_dataset(
        "DepthPerTree",
        data=depth_per_tree,
        dtype='i8',
        shape=depth_per_tree.shape,
        maxshape=(None,)
    )

    vtkgrp.create_dataset(
        "Descriptors",
        data=descriptors,
        dtype='u1',
        shape=descriptors.shape,
        maxshape=(None,)
    )

    vtkgrp.create_dataset(
        "DescriptorsSize",
        data=descriptors_size,
        dtype='i8',
        shape=descriptors_size.shape,
        maxshape=(None,)
    )

    vtkgrp.create_dataset(
        "NumberOfCells",
        data=number_of_cells,
        dtype='i8',
        shape=number_of_cells.shape,
        maxshape=(None,)
    )

    vtkgrp.create_dataset(
        "NumberOfCellsPerTreeDepth",
        data=number_of_cells_per_tree_depth,
        dtype='i8',
        shape=number_of_cells_per_tree_depth.shape,
        maxshape=(None,)
    )

    vtkgrp.create_dataset(
        "NumberOfDepths",
        data=number_of_depths,
        dtype='i8',
        shape=number_of_depths.shape,
        maxshape=(None,)
    )

    vtkgrp.create_dataset(
        "NumberOfTrees",
        data=number_of_trees,
        dtype='i8',
        shape=number_of_trees.shape,
        maxshape=(None,)
    )

    vtkgrp.create_dataset(
        "TreeIds",
        data=tree_ids,
        dtype='i8',
        shape=tree_ids.shape,
        maxshape=(None,)
    )

    vtkgrp.create_dataset(
        "XCoordinates",
        data=x_coordinates,
        dtype='f8',
        shape=x_coordinates.shape,
        maxshape=(None,)
    )

    vtkgrp.create_dataset(
        "YCoordinates",
        data=y_coordinates,
        dtype='f8',
        shape=y_coordinates.shape,
        maxshape=(None,)
    )

    vtkgrp.create_dataset(
        "ZCoordinates",
        data=z_coordinates,
        dtype='f8',
        shape=z_coordinates.shape,
        maxshape=(None,)
    )

```

[multipiece\_htg.hdf](https://discourse.vtk.org/uploads/short-url/4HWEEcqNluzeJFpyJYjRTr9OHSw.hdf) (31.5 KB)

[Next page](https://discourse.vtk.org/t/hypertreegrid-support-in-vtkhdf/15150.md?page=2)
