feat: add icosahedral mesh layout with grid-to-mesh connectivity#76
feat: add icosahedral mesh layout with grid-to-mesh connectivity#76yuvraajnarula wants to merge 61 commits intomllam:mainfrom
Conversation
|
@yuvraajnarula Since we are collaborating on this PR, could you please invite me as a Collaborator to your fork of weather-model-graphs? This way, I can directly commit and push the trimesh icosahedral mesh generation script to the PR branch myself. It will make it much easier for both of us to iterate on the code without having to send files back and forth! |
|
@Raj-Taware Great point about the BallTree dependency. The 3D projection + KDTree approach is cleaner and matches what we're already doing for g2m connections. One question: |
Hey ! great work getting Yes the formula works as long as both the coordinates are projected on a unit sphere. If you want to generalise it the formula becomes But since in Btw when using arcsin i would recommend clipping the input to handle floating-point rounding errors. Sometimes KDTree might return a distance as something like 2.00000000000004 which will case np.arcsin(2.00000000000004/2) = np.arcsin(1.00000000000002) to throw a NaN warning. Hope this helps ! |
TrackingNote : Core icosahedral work now moved to dedicated issue #77. This PR focuses on connectivity primitives that will feed into that effort. |
…/yuvraajnarula/weather-model-graphs into feature/icosahedral-connectivity
… only left to fixed)
|
@mandeepsingh2007 Now that the PR is taking shape, wanted to check in – how does the implementation look from your end? Specifically curious if:
Your mesh work is the foundation here, so your feedback matters. Thanks again for collaborating on this! |
|
@yuvraajnarula thanks for putting all this together! The implementation is definitely taking great shape. To answer your points: Mesh Integration: The trimesh base generation looks smooth and is establishing the correct spherical topology as expected. g2m & Dispatch Logic: The current logic works perfectly for establishing our baseline. However, keeping Joel's recent Slack note about networkx bottlenecks in mind, we should note that scaling this to high-resolution global grids will eventually require us to shift toward fully vectorized bulk operations (to avoid iterative graph-building overhead). But for the scope of this initial PR, your current setup is spot on. Ready for review? I think it’s good to go! Keeping this PR tightly scoped to just establishing the lightweight icosahedral layout (without GraphCast dependencies) is the perfect first step. We can tackle the hierarchical scaling and tensor optimizations in subsequent issues. |
| dlat = source_pos_2d[0] - target_pos_2d[0] | ||
| dlon = source_pos_2d[1] - target_pos_2d[1] | ||
| dlon = (dlon + 180) % 360 - 180 | ||
| G_connect.edges[source_node, target_node]["vdiff"] = np.array([dlat, dlon]) |
There was a problem hiding this comment.
One thing worth flagging here. find_containing_triangle correctly computes and returns the barycentric weights, but they don't seem to be making it onto the edges in connect_nodes_across_graphs:
G_connect.edges[source_node, target_node]["vdiff"] = np.array([dlat, dlon])
# weights returned by find_containing_triangle not stored hereStoring them as a weights edge attribute would let to_pyg include them in the edge features alongside len and vdiff, which gives the model a geometric prior on each mesh node's contribution to a grid point rather than having to learn it purely from data. That's kind of the nice thing about containing_triangle over nearest_neighbours so would be good to preserve it.
G_connect.edges[source_node, target_node]["weights"] = barycentric_weight_for_this_nodeThat said, we could skip the weights entirely since the current LAM model operates on a learned weight basis anyway. But if we are going through the effort of computing them it seems like a shame not to pass them through to the model as well.
Might just be an oversight, wanted to flag it!
There was a problem hiding this comment.
@Joltsy10 Good flag
I'll add them alongside len and vdiff in the containing_triangle block. The model can then choose to use them or not, but at least the information isn't lost.
Appreciate you catching these edge cases.
There was a problem hiding this comment.
Great idea to add these weights (maybe as barycentric_weight) as a node attribute. If you have done that @yuvraajnarula should we close this conversation?
…ented the optimized function in containing_triangle case in base.py
|
Pushed two commits:
@Joltsy10 – your review comments on performance and weights are addressed. The containing_triangle path now uses spatial indexing and stores barycentric weights as edge attributes. @mandeepsingh2007 – mesh integration still solid? The optimizations shouldn't affect your generation logic. Next: Generic methods (nearest_neighbour, etc.) could also use 3D spatial indexing for icosahedral cases. I'm going to keep this PR focused on containing_triangle since that's the critical path. We can optimize the generic methods in a follow-up PR if needed. Thoughts? |
|
@yuvraajnarula Yes, the mesh integration is completely solid. The spatial indexing you added for the containing_triangle path sits downstream from the coordinate generation, so the underlying trimesh geometry remains perfectly intact and mathematically sound. I also 100% agree with your approach for the next steps. Keeping this PR strictly scoped to the containing_triangle critical path and the baseline icosahedral layout is the smartest move. It prevents scope creep and makes this much easier for the mentors to review. We should definitely freeze the scope here, get this foundation merged, and tackle the generic methods in subsequent PRs. This looks fantastic. From my end on the mesh side, this is ready to go! |
- Replace 2D lat/lon vdiff with 3D Cartesian in flat and hierarchical mesh edge creation - Apply same fix to G2M and M2G edges in connect_nodes_across_graphs - Add tests verifying 3D vdiff for icosahedral and 2D backward compatibility for rectilinear
@Joltsy10: taking inspiration from @joeloskarsson's work on global probalistic forecasting what was done there (https://github.com/mllam/neural-lam/blob/prob_model_global/create_global_grid_features.py#L66) was create a positional embedding (just |
Oh ye i was thinking of ways to embed the positional coordinates and I thought about the sin/cos embedding and that would make it 3D but I was thinking of computing it here in wmg, rather we could just pass the 2D lat/lon and then embed it later in neural lam so yeah just having the lat/lon as node attributes and then later embedding it in nueral lam seems good |
leifdenby
left a comment
There was a problem hiding this comment.
Left some feedback on the notebooks (good idea to separate into two!). Both a really great, just some suggestions for simplification and adjustment. Once you've revised the one on the internals I will give another read and that will help me understand what the code is trying to achieve, and I'll then read the implementation next.
Also, what do you think to putting the notebook about the internals in docs/internals/? That way we can over time have a collection of notebooks that describe the api functionality of weather-model-graphs separately from those that describe the internal implementation.
…_layout attribute
…/yuvraajnarula/weather-model-graphs into feature/icosahedral-connectivity
|
@leifdenby I'm opening a new issue and will track 3D visualization work there. Following the discussion in #20, the plan is:
Happy to iterate on the API there. Thanks for the guidance! |
|
Hi @yuvraajnarula @Raj-Taware @mandeepsingh2007, great progress on the icosahedral implementation. After reviewing the latest commits, I have a few concerns regarding the global consistency: 1.Coordinate System: While Lat/Lon is 'pragmatic', using 3D Cartesian coordinates for mesh_features would avoid the pole singularity issue which is critical for GNN stability in global models. 2.Efficiency: Have we considered using torch-cluster for the spatial indexing instead of scipy? This would keep the entire pipeline on the GPU and avoid costly CPU-GPU transfers during graph construction. 3.Hierarchical Links: Does the current mesh_layout logic pre-compute the edges between different icosahedral levels? This is vital for implementing multi-scale message passing (pooling/unpooling) later on. |
|
|
Thanks for the clarification! It makes sense to keep Lat/Lon for
compatibility with the validator spec, especially if the sin/cos embedding
handles the continuity on the model side. Also, I see your point about the
static nature of the graph; since it's a one-time generation, the CPU/GPU
handoff isn't a bottleneck. I'll take a closer look at the hierarchy in
#118 to understand how the multi-scale edges are structured.
|
|
@Prince637-boo To add to what @Joltsy10 already clarified: 1. Coordinate system : We opted for lat/lon in the graph to stay validator‑compliant. The neural‑lam side can apply sin/cos embeddings (as in the 2. Efficiency : Since graph generation is a one‑time offline step, scipy’s KDTree is sufficient for now. Keeping it CPU‑side avoids pulling in PyTorch GPU dependencies into WMG. If we ever need to generate graphs repeatedly (e.g., dynamic graphs), 3. Hierarchical links : Yes, the edges between levels are pre‑computed. The hierarchical layout in #118 visualises them clearly (intra‑level in blue, up/down in red/green). For multi‑scale message passing, the structure is there to use directly. Happy to answer any more questions! |
|
Thanks @yuvraajnarula and @Joltsy10 for the detailed 1.Coordinate system: Understood. Using sin/cos embeddings on the model side is a smart way to maintain validator compliance while solving the polar discontinuity. 2.Efficiency: That makes sense. Since the graph is static and pre-computed, the CPU overhead isn't a bottleneck for training. I agree that keeping WMG lightweight without forcing GPU dependencies is the right call for now. 3.Hierarchical links: I’ve been looking at the visualization in #118, and the concentric layout is a great improvement for clarity. It’s much easier to trace the up/down edges now. I'm currently diving deeper into the plot_3d.py logic. I'd love to help polish the global visualization further—perhaps by adding a coastline/map layer to give the icosahedral grid more geographical context. Looking forward to contributing! |
Describe your changes
This PR adds
mesh_layout='icosahedral'to support global graph generation in WMG, addressing the architectural gap identified in #71.Key additions:
create.mesh.layouts.icosahedral.py- Implements icosahedral mesh hierarchy usingtrimesh(co-developed with @mandeepsingh2007)prob_model_globalbranch (lines 224-242)/docs/icosahedral_global_graph.ipynbshowing end-to-end pipelineMotivation:
Currently, WMG's mesh generation assumes rectilinear grids, which breaks for global domains (Issue #71). This PR decouples mesh layout from connectivity by introducing:
mesh_layout='icosahedral'for quasi-uniform spherical samplingDependencies:
trimesh- Lightweight mesh generation (avoiding GraphCast dependency)numpy,scipy(already in WMG dependencies)Issue Link
#71
Type of change
Checklist before requesting a review
pullwith--rebaseoption if possible).Checklist for reviewers
Each PR comes with its own improvements and flaws. The reviewer should check the following:
Author checklist after completed review
reflecting type of change (add section where missing):
Checklist for assignee