Skip to content

Support hierarchical graphs in the HeteroData representation - #713

Open
prajwal-tech07 wants to merge 11 commits into
mllam:mainfrom
prajwal-tech07:issue-385/heterodata-hierarchical
Open

Support hierarchical graphs in the HeteroData representation#713
prajwal-tech07 wants to merge 11 commits into
mllam:mainfrom
prajwal-tech07:issue-385/heterodata-hierarchical

Conversation

@prajwal-tech07

Copy link
Copy Markdown
Contributor

Second step of the HeteroData migration (#385), extending it to hierarchical (multi-level) graphs and the HiLAM / HiLAMParallel models.

Builds on #711 (the flat first step) — that PR's commits are included here, so the change specific to this PR is the last commit. Happy to rebase once #711 is merged.

  • graph_dict_to_heterodata / heterodata_to_graph_dict now handle hierarchical graphs. Each mesh level is a distinct node type (mesh_0mesh_{L-1}), since levels have different node counts and their own embedders/GNNs in the model. g2m/m2g connect the grid to the bottom level, intra-level edges are ("mesh_i","to","mesh_i"), and inter-level edges are ("mesh_i","up","mesh_{i+1}") and ("mesh_{i+1}","down","mesh_i") for each of the L-1 level pairs.
  • Threads use_heterodata through BaseHiGraphModel, HiLAM and HiLAMParallel; BaseGraphModel now builds and consumes the HeteroData for hierarchical graphs as well (the previous NotImplementedError guard is removed).
  • The round-trip through HeteroData is exact, so the model receives identical tensors and builds/trains identically either way, and existing checkpoints remain compatible.

Verified on a real 3-level graph (729/81/9 mesh nodes): the expected per-level node types and all nine edge types are present, with m2m having L entries and mesh_up/mesh_down having L-1.

Issue Link

Part of #385 (hierarchical follow-up to #711).

Type of change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 💥 Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • 📖 Documentation (Addition or improvements to documentation)

Checklist before requesting a review

  • My branch is up-to-date with the target branch - if not update your fork with the changes from the target branch (use pull with --rebase option if possible).
  • I have performed a self-review of my code
  • For any new/modified functions/classes I have added docstrings that clearly describe its purpose, expected inputs and returned values
  • I have placed in-line comments to clarify the intent of any hard-to-understand passages of my code
  • I have updated the README to cover introduced code changes
  • I have added tests that prove my fix is effective or that my feature works
  • I have given the PR a name that clearly describes the change, written in imperative form (context).
  • I have requested a reviewer and an assignee (assignee is responsible for merging). This applies only if you have write access to the repo, otherwise feel free to tag a maintainer to add a reviewer and assignee.

Checklist for reviewers

Each PR comes with its own improvements and flaws. The reviewer should check the following:

  • the code is readable
  • the code is well tested
  • the code is documented (including return types and parameters)
  • the code is easy to maintain

Author checklist after completed review

  • I have added a line to the CHANGELOG describing this change, in a section
    reflecting type of change (add section where missing):
    • added: when you have added new functionality
    • changed: when default behaviour of the code has been changed
    • fixes: when your contribution fixes a bug
    • maintenance: when your contribution is relates to repo maintenance, e.g. CI/CD or documentation

Checklist for assignee

  • PR is up to date with the base branch
  • the tests pass
  • (if the PR is not just maintenance/bugfix) the PR is assigned to the next milestone. If it is not, propose it for a future milestone.
  • author has added an entry to the changelog (and designated the change as added, changed, fixed or maintenance)
  • Once the PR is ready to be merged, squash commits and merge the PR.

First step of the HeteroData migration (issue mllam#385), scoped to flat
(non-hierarchical) graphs.

- Add neural_lam.utils.heterodata with graph_dict_to_heterodata and its
  inverse heterodata_to_graph_dict. Grid nodes are the "data" node type and
  mesh nodes the "hidden" node type, following the convention used in the
  reference implementations linked from the issue (leifdenby/weatherduck,
  matschreiner/equicast); g2m/m2m/m2g become typed edges. The grid node
  count is taken from the datastore rather than inferred from edge indices,
  and the edge-feature width is read dynamically (3 for 2D, 4 for 3D).
- Wire into BaseGraphModel behind a use_heterodata flag (default False):
  when enabled the graph is represented as a HeteroData object and the
  model's graph tensors are taken from it. The extracted tensors are
  identical to the dict ones, so the model builds and trains identically
  either way, and existing checkpoints remain compatible.
- Thread the flag through GraphLAM.
- Tests: unit tests for the conversion (structure, exact round-trip,
  variable edge-feature width, datastore-provided grid count, hierarchical
  rejection) and a GraphLAM equivalence test asserting identical parameters,
  graph buffers and forward output with the flag on and off.
Rename the HeteroData node types from data/hidden to grid/mesh, matching
the naming used throughout the rest of neural-lam (grid_static_features,
mesh_static_features, g2m/m2m/m2g). The names remain module-level constants;
the reference implementations in issue mllam#385 use data/hidden, noted in the
module docstring.
Extend the GraphLAM equivalence test to run a few real optimizer steps on
both the dict-based and HeteroData-based models and assert identical
per-step losses and identical weights afterwards, covering the issue mllam#385
requirement to carry out training with both datastructures and confirm it
proceeds identically.
Rename heterodata_to_graph_dict to graph_tensors_from_heterodata and
document it as how the model obtains its graph tensors when the graph is
represented as a HeteroData: every tensor is looked up on the typed
node/edge stores of the object, so the HeteroData is the source of the
graph data the model uses. The tensors are unchanged, so the model still
builds and trains identically.

Also add the changelog entry for this change.
Extend the HeteroData migration (mllam#385) to hierarchical (multi-level) graphs
and the HiLAM / HiLAMParallel models.

- graph_dict_to_heterodata / heterodata_to_graph_dict now handle
  hierarchical graphs: each mesh level is a distinct node type
  (mesh_0 .. mesh_{L-1}), g2m/m2g connect the grid to the bottom level,
  intra-level edges are (mesh_i, to, mesh_i) and inter-level edges are
  (mesh_i, up, mesh_{i+1}) and (mesh_{i+1}, down, mesh_i) for each of the
  L-1 level pairs. The round-trip through HeteroData is exact, so the model
  gets identical tensors.
- Thread use_heterodata through BaseHiGraphModel, HiLAM and HiLAMParallel;
  BaseGraphModel now builds/consumes the HeteroData for hierarchical graphs
  too (the previous NotImplementedError guard is removed).
- Tests: hierarchical builder structure + exact round-trip unit tests, and a
  HiLAM equivalence test asserting identical parameters, per-level graph
  buffers, forward output and training with and without the HeteroData
  datastructure.
@prajwal-tech07
prajwal-tech07 force-pushed the issue-385/heterodata-hierarchical branch from 49d1f6e to 5908777 Compare July 25, 2026 13:58
Main moved the graph loading and buffer registration out of BaseGraphModel
into utils.load_and_register_graph (mllam#648), so the HeteroData wiring moves
there with it. It is now applied wherever a module loads a graph through
that helper, which includes the new Graph-EFM step predictors, and the grid
node count is read from the datastore directly.
The HeteroData wiring now lives in utils.load_and_register_graph, so pass
the hierarchical flag through to the conversion helpers there instead of
building the object in BaseGraphModel.
The check moved out of BaseGraphModel together with the rest of the graph
loading, so test it where it now lives: loading a hierarchical graph with
use_heterodata raises NotImplementedError.
This PR adds hierarchical support, so the rejection test from the flat
branch becomes its positive counterpart: loading a hierarchical graph with
use_heterodata builds a per-level HeteroData object whose levels match the
tensors registered on the module.
@prajwal-tech07

Copy link
Copy Markdown
Contributor Author

@leifdenby @joeloskarsson review request for this one too - it stacks on #711, so that's the one to look at first. Same post-#648 merge, CI green, hierarchical re-verified live (3 levels, grid/mesh_0/mesh_1/mesh_2, 9 edge types).

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