Skip to content

Add create_graph_with_wmg.py CLI using weather-model-graphs - #596

Open
prajwal-tech07 wants to merge 12 commits into
mllam:mainfrom
prajwal-tech07:issue-384/create-graph-with-wmg
Open

Add create_graph_with_wmg.py CLI using weather-model-graphs#596
prajwal-tech07 wants to merge 12 commits into
mllam:mainfrom
prajwal-tech07:issue-384/create-graph-with-wmg

Conversation

@prajwal-tech07

Copy link
Copy Markdown
Contributor

Describe your changes

Add a new CLI script create_graph_with_wmg.py that delegates graph creation
to weather-model-graphs (wmg)
and saves the output using wmg.save.to_neural_lam() in the tensor-on-disk
format expected by neural_lam.utils.load_graph().

This is the neural-lam side of the bridge described in #384. The wmg side
is mllam/weather-model-graphs#123,
which adds to_neural_lam() to wmg.save.

What this PR does:

  • Adds neural_lam/create_graph_with_wmg.py with support for all three wmg
    archetypes: keisler (flat single-scale), graphcast (flat multiscale),
    and hierarchical (Oskarsson)
  • Auto-computes mesh_node_distance from grid spacing when not specified
  • Reshapes datastore coordinates from (Nx, Ny, 2)(N, 2) for wmg
  • Adds a deprecation warning to the old create_graph.py CLI pointing users
    to the new script
  • Adds weather-model-graphs[pytorch]>=0.3.0 as a dependency
  • Relaxes torch-geometric pin from ==2.3.1 to >=2.5.3 (required by
    wmg's pytorch extra)
  • Registers create_graph_with_wmg as a console script entry point

Dependencies: Requires mllam/weather-model-graphs#123
to be merged and released first (adds wmg.save.to_neural_lam()).

Files changed (4 files, +305 −1):

File Change
neural_lam/create_graph_with_wmg.py New CLI script (+188 lines)
neural_lam/create_graph.py Added deprecation warning (+9 lines)
pyproject.toml Added wmg dep, relaxed torch-geometric, added script entry (+5 −1)
tests/test_graph_creation.py 12 new tests (9 wmg creation + 3 deprecation) (+103 lines)

Issue Link

Solves #384 (neural-lam side)

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.

@prajwal-tech07

Copy link
Copy Markdown
Contributor Author

Merged Leif's pyproject.toml fix from #1. The pip, cpu CI failure is expected since tool.uv.sources isn't read by pip — only the uv jobs will correctly resolve weather-model-graphs from the fork branch. The 3 cancelled checks (pip gpu, uv cpu, uv gpu) were just cascading from the pip,cpu failure. All 21 tests pass locally.

@leifdenby leifdenby left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good! A few suggestions for changes 🚀

Comment thread neural_lam/create_graph_with_wmg.py Outdated
}


def _estimate_mesh_node_distance(xy):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we could call this _estimate_grid_node_spacing? And then expose the grid-mesh-spacing ratio as a CLI arg that defaults to 3.0?

@prajwal-tech07 prajwal-tech07 Apr 14, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed _estimate_mesh_node_distance → _estimate_grid_node_spacing — it now returns only the average grid spacing. The ×3 multiplier is replaced by a new grid_mesh_ratio parameter (default 3.0) exposed as --grid_mesh_ratio on the CLI.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

looks great, but it would better to name it --grid_mesh_spacing_ratio I think - just be clear about what it is the ratio between :) what do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--grid_mesh_ratio--grid_mesh_spacing_ratio
Good call — renamed the CLI arg, function parameter, and docstrings throughout to make it clear it's the ratio between mesh-node and grid-node spacing.

Comment thread neural_lam/create_graph_with_wmg.py Outdated
Comment thread neural_lam/create_graph_with_wmg.py

@pytest.mark.parametrize("archetype", ["keisler", "graphcast", "hierarchical"])
@pytest.mark.parametrize("datastore_name", DATASTORES.keys())
def test_wmg_graph_creation(datastore_name, archetype):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is great, but we could actually use the testing that #323 introduces. I should try and get that finished so that we can merge both in together :)

@prajwal-tech07 prajwal-tech07 Apr 14, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, the tests are working well as-is! Happy to adapt them to the testing infrastructure from #323 once that's ready — makes total sense to merge them together. Let me know if there's anything I can do to help with #323!

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, how about we say that depending on whether I finish #323 soon then we either a) merge this in with the tests have you have them implemented already or b) merge #323 in first and then adapting the testing here to use the testing being introduced in #323?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like a great plan — either way works for me. Happy to adapt the tests to #323's infrastructure once it lands, or merge as-is if this PR is ready

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can remove these tests now, no? Maybe we should instead just call the validator from #323 here?

Comment thread pyproject.toml Outdated
"plotly>=5.15.0",
"torch>=2.3.0",
"torch-geometric==2.3.1",
"torch-geometric>=2.5.3",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is this a necessary change?

@prajwal-tech07 prajwal-tech07 Apr 14, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is necessary. The weather-model-graphs[pytorch] extra requires torch-geometric>=2.5.3 in its own dependencies, so we need to allow at least that version here too.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, in that case I think we maybe should reduce required version number on the weather-model-graphs side, since all we are doing with pytorch-geometric is using it to convert the networkx.DiGraph objects to torch.Tensor objects, and we already do that in the current create_graphs.py code in neural-lam with the older version. So how about we change weather-model-graphs to require torch-geometric==2.3.1 so the two are in sync? Then we don't have to change anything on the neural-lam side

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right — wmg only uses from_networkx which works fine with 2.3.1. I've also pushed a commit to the wmg PR branch (prajwal-tech07/weather-model-graphs@d0c693d) pinning it to ==2.3.1 there too, so both repos stay in sync.

Comment thread pyproject.toml

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will make a separate PR for this. I think this is some general CI maintenance that should be merged in before this PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted — happy to revert the CI changes from this branch if you'd prefer to handle them in a separate PR.

@leifdenby leifdenby left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread README.md
Comment thread pyproject.toml
@sadamov sadamov linked an issue Apr 15, 2026 that may be closed by this pull request

@leifdenby leifdenby left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, the CLI interface looks great now, as do the readme instructions and install works great (just tried locally on my laptop too).

I think we should also change the tests that use graph creation (i.e. the training examples, they all use the create_graph_from_datastore() function in https://github.com/mllam/neural-lam/blob/main/neural_lam/create_graph.py#L540), but I think we should use the new one that you have created that creates a graph with wmg instead.

@prajwal-tech07

Copy link
Copy Markdown
Contributor Author

Thanks for the suggestion, @leifdenby! Done in 76c7ed7.

All test files that used the old create_graph_from_datastore() from neural_lam.create_graph have been migrated to use the new wmg-based version from neural_lam.create_graph_with_wmg:

  • test_datasets.py, test_clamping.py, test_plotting.py, test_training.py: Switched to create_graph_from_datastore(archetype="keisler") (equivalent to the previous n_max_levels=1 flat graph).
  • test_plot_graph.py: Switched to wmg-based graph creation with keisler and hierarchical archetypes. Removed the multiscale (graphcast) parametrization from this test since the graphcast archetype produces multi-level m2m edges without up/down edges, which isn't compatible with utils.load_graph() (it infers hierarchical=True when n_levels > 1 and then tries to load mesh_up_edge_index.pt). Graphcast graph creation is already separately tested in test_graph_creation.py where all 3 archetypes pass.

All tests pass locally (55/55 in the modified files, 72/72 in the full suite.

@leifdenby

leifdenby commented Apr 15, 2026

Copy link
Copy Markdown
Member

I just completed a 200 epoch training (on a DGX Spark machine) on the DANRA test dataset using a keisler graph created with your new create_graph_with_wmg script and the training loss decreases as expected 🥳

W B Chart 15 4 2026, 15 39 26

I ran the following commands:

uv run python -m neural_lam.create_graph_with_wmg --config_path tests/datastore_examples/mdp/danra_100m_winds/config.yaml --archetype keisler
uv run python -m neural_lam.train_model --config_path tests/datastore_examples/mdp/danra_100m_winds/config.yaml

So I will give this a final review, but I think this is nearly ready to merge :)

@prajwal-tech07

Copy link
Copy Markdown
Contributor Author

Hi @leifdenby! Implementation is complete. create_graph_from_datastore() in neural_lam/create_graph_with_wmg.py is working and tested , all 21 graph creation tests pass across 3 datastores × 3 archetypes (keisler, graphcast, hierarchical), including the deprecation warning test. The full training suite (6/6) also passes. Ready for your review!

@prajwal-tech07

Copy link
Copy Markdown
Contributor Author

Rebased onto main (now includes the merged graph-storage spec/validator from #323). Resolved conflicts, and added the end-to-end validation test from the merge plan: test_wmg_graph_creation now builds a graph via create_graph_with_wmg and validates it on disk with docs/validate_graph.py, asserting it passes the spec + metainfo.yaml version. Locally green (graph creation, datasets, plotting, and full training all pass) and uv.lock regenerated for the wmg #123-branch pin. Addressed in 5055f4d.

@prajwal-tech07
prajwal-tech07 force-pushed the issue-384/create-graph-with-wmg branch from 5055f4d to 7063f99 Compare July 28, 2026 15:44
@prajwal-tech07

Copy link
Copy Markdown
Contributor Author

Rebased onto latest main and switched to the released weather-model-graphs 0.4.0 (now that #123 is merged and v0.4.0 is on PyPI), completing step 3 of the merge plan.

Changes since the last review:

Verification: the full test suite passes locally against the released 0.4.0 - 259 passed, including graph creation for all three archetypes, the validation test, and a full training run. pre-commit is clean.

One note on the dependency: I deliberately did not use the [pytorch] extra. wmg 0.4.0's [pytorch] extra requires torch-geometric>=2.5.3, which is unsatisfiable against neural-lam's torch-geometric==2.3.1 pin. It isn't needed here - save/neural_lam/torch_tensors.py doesn't import torch-geometric (save/base.py only imports it optionally via HAS_PYG), and neural-lam already provides torch/torch-geometric itself. Let me know if you'd rather pin ==0.4.0 exactly.

Your earlier review is still marked as changes requested - I believe everything from it is addressed now (_estimate_grid_node_spacing + --grid_mesh_spacing_ratio, stacked=True, the return_components comment, the README section, and CI left for your separate PR), so this should be ready for a re-review.

@prajwal-tech07
prajwal-tech07 requested a review from leifdenby July 28, 2026 15:55
prajwal-tech07 and others added 4 commits August 6, 2026 22:54
Update pyproject.toml to install weather-model-graphs from the
issue-384/to-neural-lam branch of the fork, so that CI and reviewers
can test the neural-lam side against the unreleased to_neural_lam()
changes before wmg PR mllam#123 is merged.

Will revert to a versioned PyPI dependency once PR mllam#123 is released.
…sh_ratio, use stacked=True, add return_components comment, add README entry
Migrated all test files that used the old create_graph_from_datastore()
from neural_lam.create_graph to use the new wmg-based version from
neural_lam.create_graph_with_wmg instead.

Changes:
- test_datasets.py: Use wmg create_graph_from_datastore with archetype='keisler'
- test_clamping.py: Use wmg create_graph_from_datastore with archetype='keisler'
- test_plotting.py: Use wmg create_graph_from_datastore with archetype='keisler'
- test_training.py: Use wmg create_graph_from_datastore with archetype='keisler'
- test_plot_graph.py: Use wmg create_graph_from_datastore with keisler and
  hierarchical archetypes. Removed multiscale (graphcast) parametrization
  since the graphcast archetype produces multi-level m2m edges without
  up/down edges, which is not yet compatible with utils.load_graph().
  Graphcast graph creation is separately tested in test_graph_creation.py.
- test_graph_creation: validate create_graph_with_wmg output on disk with the
  graph-storage validator (docs/validate_graph.py from mllam#323) and assert the
  metainfo.yaml spec_version -- the end-to-end format contract for the bridge
- create_graph_with_wmg: add module docstring (interrogate 100% coverage)
- pyproject: merge the weather-model-graphs pin into a single [tool.uv.sources]
  table (rebase left two declarations, which is invalid TOML)
- uv.lock: regenerate so 'uv sync --locked' matches the wmg PR mllam#123 branch pin
weather-model-graphs 0.4.0 is released on PyPI and includes
save.to_torch_tensors_on_disk, so the temporary git dependency on the
PR mllam#123 branch is no longer needed.

- pyproject: bump to weather-model-graphs>=0.4.0 and drop the
  [tool.uv.sources] git pin
- uv.lock: regenerate so it resolves 0.4.0 from PyPI

The [pytorch] extra is intentionally not used: it requires
torch-geometric>=2.5.3, which conflicts with neural-lam's
torch-geometric==2.3.1 pin. It is not needed here, since neural-lam
already provides torch/torch-geometric and the tensor-on-disk save path
does not import torch-geometric.
@prajwal-tech07
prajwal-tech07 force-pushed the issue-384/create-graph-with-wmg branch from 7063f99 to bba00b3 Compare August 6, 2026 17:38
@prajwal-tech07

Copy link
Copy Markdown
Contributor Author

@leifdenby gentle ping on this one. It's green and mergeable - the bump to weather-model-graphs>=0.4.0 and the regenerated uv.lock went in on 28 July, so it now resolves 0.4.0 straight from PyPI with no fork refs.

Going by the merge order we agreed: spec #323 merged, the validation test is on this branch, wmg #123 merged and v0.4.0 released - this PR is the last remaining step. The points from your April review are all addressed. Is there anything else you'd like changed before it can go in?

``"hierarchical"``.
mesh_node_distance : float or None
Distance between created mesh nodes (in coordinate units). If None,
automatically estimated as ``grid_mesh_spacing_ratio * grid_spacing``.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is grid_spacing here? I think it is the grid node spacing, but we should state here that we automatically try to estimate this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's the grid node spacing - roughly the distance between neighbouring grid points, estimated from the datastore coordinates when mesh_node_distance isn't given. Agreed the docstring should say that outright instead of leaving it to be inferred.

I'll fold this in together with the rename once we've settled the terminology in the other thread, since the wording depends on whether we land on "distance" or "resolution". Same goes for the log line you suggested.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let's go with distance here like you suggest :)


if mesh_node_distance is None:
grid_spacing = _estimate_grid_node_spacing(xy)
mesh_node_distance = grid_spacing * grid_mesh_spacing_ratio

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should log here that because mesh_node_distance wasn't provided we estimate the grid-node spacing and use that. Maybe something like mesh_node_distance not provided so estimating grid-node spacing, with value estimated to be xx.xx, resulting mesh-node spacing will be yy.yy - what do you think?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes Leif, this should be logged. It's the one number in the command the user didn't pick, so it shouldn't be silent.

One thing on the wording. Your version prints the estimated grid spacing and the resulting mesh spacing, but not the ratio between them, and the ratio is what someone would reach for if the number looks wrong. I'd rather the line explained itself:

mesh_node_distance not given; estimated grid node spacing 12.50 x grid_mesh_spacing_ratio 3.0 -> mesh_node_distance 37.50
(parameter name rather than the CLI flag, since create_graph_from_datastore gets called directly from the tests too)

The other reason I want it visible: the estimate is rougher than "estimated" suggests. It's sqrt(x_range * y_range / N), so it returns the geometric mean of dx and dy, which on an anisotropic grid lands between the two rather than on either. And since np.ptp measures n-1 intervals across n points, it comes out low by sqrt((nx-1)(ny-1)/(nx*ny)). I measured -0.2% on 500x500, -1% on 100x100, -10% on 10x10. Doesn't matter on a real domain, very visible on a small test one, which is exactly when someone is squinting at a graph wondering why it looks off.

The file has no logger yet, so I'd add loguru at info to match create_graph.py. Happy to make it a warning instead.
Wording depends on the naming thread, by the way. If we go with "distance" this becomes estimated grid node distance.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sounds great! Let's include the ratio as well, good idea

help="Graph archetype to create",
)
parser.add_argument(
"--mesh_node_distance",

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it is a bit confusing that we use both term "spacing" and "distance" for the mesh/grid resolution. I appreciate that wmg uses "distance", so maybe should use the same here? "resolution" would probably be an even better term. What do you think?

@prajwal-tech07 prajwal-tech07 Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, Leif - that's me mixing the two, not wmg. I had a look and there's no convention to protect either way: "spacing" doesn't appear anywhere in the codebase outside this file, and "resolution" only turns up in a comment in create_graph.py and a cartopy resolution="50m" kwarg. So we can just pick one.

I'd go with "distance". wmg's parameter is mesh_node_distance and we pass it straight through, so anything else means the flag and the thing it sets end up with different names for the same quantity. And "resolution" in a weather context usually runs the other way - higher resolution is a smaller number - so mesh_resolution=5 could be read as 5 units apart or 5x finer, where a distance of 5 can only mean one thing.

That would give mesh_node_distance (unchanged), _estimate_grid_node_distance, and grid_spacing -> grid_node_distance.

One thing I'd like your view on: --grid_mesh_spacing_ratio reads as grid:mesh, but the code is mesh_node_distance = grid_spacing * ratio, so it's mesh:grid. At the default of 3.0 the mesh nodes end up 3x further apart, not 3x closer. If we're renaming anyway, --mesh_grid_distance_ratio would match, though I don't know if churning a CLI flag is worth it.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd go with "distance". wmg's parameter is mesh_node_distance and we pass it straight through, so anything else means the flag and the thing it sets end up with different names for the same quantity. And "resolution" in a weather context usually runs the other way - higher resolution is a smaller number - so mesh_resolution=5 could be read as 5 units apart or 5x finer, where a distance of 5 can only mean one thing.

I agree - good point!

One thing I'd like your view on: --grid_mesh_spacing_ratio reads as grid:mesh, but the code is mesh_node_distance = grid_spacing * ratio, so it's mesh:grid. At the default of 3.0 the mesh nodes end up 3x further apart, not 3x closer. If we're renaming anyway, --mesh_grid_distance_ratio would match, though I don't know if churning a CLI flag is worth it.

Very well caught! Nice, yes rename the arg


@pytest.mark.parametrize("archetype", ["keisler", "graphcast", "hierarchical"])
@pytest.mark.parametrize("datastore_name", DATASTORES.keys())
def test_wmg_graph_creation(datastore_name, archetype):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can remove these tests now, no? Maybe we should instead just call the validator from #323 here?

Comment thread tests/test_plot_graph.py
if graph_name == "hierarchical":
hierarchical = True
n_max_levels = 3
elif graph_name == "multiscale":

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did you remove the multiscale option?

@prajwal-tech07 prajwal-tech07 Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I removed it because at the time it genuinely didn't work. Back then to_torch_tensors_on_disk split non-hierarchical m2m by the level edge attribute, so graphcast came out as a multi-level list. load_graph infers hierarchical = len(m2m_edge_index) > 1, so it read a flat graph as hierarchical and went looking for mesh_up_*.pt files that aren't there.

Worth being explicit that this is GC-LAM from the README's three paper graphs - it should absolutely be covered here, especially in a PR that deprecates create_graph.py.

That's no longer true. When I conformed the function to the storage spec from #323, non-hierarchical graphs started being written as a single merged m2m level, and that's what's in 0.4.0. The note I left in the fixture is just stale - I should have updated it then.

Checked against the released 0.4.0:

keisler m2m levels: 1 (8320 edges) mesh_up: no load_graph -> hierarchical=False
graphcast m2m levels: 1 (6096 edges) mesh_up: no load_graph -> hierarchical=False
hierarchical m2m levels: 3 (5512/544/40) mesh_up: yes load_graph -> hierarchical=True

So it loads fine now, and I've put multiscale back as the graphcast archetype. All three are green (6 passed).

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perfect!

Comment thread CHANGELOG.md Outdated
### Added
- Add latent encoder/decoder modules and the `GraphEFM` (hierarchical) / `GraphEFMMultiScale` (flat) step predictors for the Graph-EFM ensemble forecasting model. [\#648](https://github.com/mllam/neural-lam/pull/648) @Sir-Sloth-The-Lazy

- Add `neural_lam.create_graph_with_wmg` CLI which builds graphs with

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is a bit verbose, can you shorten it to just a single sentence?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, shortened to a single sentence.

@leifdenby

Copy link
Copy Markdown
Member

One note on the dependency: I deliberately did not use the [pytorch] extra. wmg 0.4.0's [pytorch] extra requires torch-geometric>=2.5.3, which is unsatisfiable against neural-lam's torch-geometric==2.3.1 pin

Ah, I hadn't realised this. There won't be anything in weather-model-graphs that actually needs a more recent version. But maybe we can just make neural-lam require torch-geometric>=2.3.1 instead? That way we can keep moving without having to release a new revision of weather-model-graphs with a more lax requirement

Restore the multiscale (GC-LAM) case in the plot_graph fixture, using the
graphcast archetype. It was dropped when non-hierarchical m2m edges were
still written split by level, which load_graph misread as hierarchical.
Non-hierarchical graphs are written as a single merged m2m level since
to_torch_tensors_on_disk was conformed to the graph storage spec, so all
three paper graph types load again.

Shorten the create_graph_with_wmg changelog entry to a single sentence.
Drop the hand-rolled file presence, spec version, container type and
shape assertions from test_wmg_graph_creation; the validator from mllam#323
already covers all of them, and pinning edge features to exactly 3 was
stricter than the spec, which allows 3 or 4.

Keep an explicit check that mesh_up/mesh_down files are present exactly
when the archetype is hierarchical. The validator infers hierarchy from
the graph contents, so it cannot tell whether the requested archetype
was honoured.
@prajwal-tech07

prajwal-tech07 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

On tests/test_graph_creation.py:

we can remove these tests now, no? Maybe we should instead just call the validator from #323 here?

The validator's actually already in there - it runs right after the graph is built, and that's what the report.has_fails() assert is. Agreed the hand-rolled checks around it are redundant though, so I've removed them.

I checked what would be lost first, by corrupting a valid graph and confirming the validator still fails: edge features 3 -> 2 columns, mesh features 2 -> 1, edge_index transposed to [E, 2], the m2m list replaced by a bare tensor, and a deleted mesh_features.pt. It catches all five. The only thing the manual block asserted that the validator doesn't is edge features being exactly 3 - and since the spec allows {3, 4}, that check was stricter than it should have been anyway.

One thing I did keep: the validator infers whether a graph is hierarchical from its contents, so it can't tell whether the archetype that was requested was honoured - a hierarchical run that silently produced a flat graph would still pass. So there's now a single assert that the mesh_up/mesh_down files exist exactly when the archetype is hierarchical. That's an addition rather than something you asked for, so happy to drop it too if you'd rather the test were purely the validator call.

@prajwal-tech07

prajwal-tech07 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

But maybe we can just make neural-lam require torch-geometric>=2.3.1 instead?

I tried it. It works, but it's a bigger change than it looks, so I'd rather not fold it into this PR.
The conflict is real, uv won't resolve the extra against the 2.3.1 pin. Loosening to >=2.3.1 does resolve, but look at what it resolves to: torch-geometric 2.3.1 -> 2.8.0.post1. That isn't optional either. The extra needs >=2.5.3, so taking it means leaving 2.3.1 behind.

There's also a trap if we do it separately. Loosening the pin without the extra looks like a no-op, because uv lock keeps an already-locked version when it still satisfies the constraint. The lockfile stays on 2.3.1 and CI stays green. Then the next uv lock --upgrade jumps to 2.8.0. I'd rather that happened in a PR where someone's watching for it. 2.3.1 is from April 2023, 2.8.0 came out last month, five minor versions apart, and InteractionNet subclasses pyg.nn.MessagePassing, so it needs testing rather than just a green resolve.

Either way this PR doesn't need the extra. save/neural_lam/torch_tensors.py imports only torch, and save/base.py keeps its torch_geometric import behind the HAS_PYG guard, so plain weather-model-graphs>=0.4.0 covers everything create_graph_with_wmg uses, and neural-lam brings its own torch and torch-geometric anyway. So I'd leave this as it is and do the bump separately. Can open an issue for it if you want.

@leifdenby

Copy link
Copy Markdown
Member

Either way this PR doesn't need the extra. save/neural_lam/torch_tensors.py imports only torch, and save/base.py keeps its torch_geometric import behind the HAS_PYG guard, so plain weather-model-graphs>=0.4.0 covers everything create_graph_with_wmg uses, and neural-lam brings its own torch and torch-geometric anyway. So I'd leave this as it is and do the bump separately. Can open an issue for it if you want.

Ah yes, that's because we have pytorch and optional dependency in weather-model-graphs. Ok, yes if you could make an issue for weather-model-graphs to say we should relax the min version of pytorch required. And we should put that on the roadmap for the next release of weather-model-graphs I think

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Supporting tensor-on-disk-format from weather-model-graphs

3 participants