Skip to content

Add mesh_layout='triangular' for regular triangular mesh generation#92

Open
prajwal-tech07 wants to merge 7 commits intomllam:mainfrom
prajwal-tech07:feat/triangular-mesh-layout
Open

Add mesh_layout='triangular' for regular triangular mesh generation#92
prajwal-tech07 wants to merge 7 commits intomllam:mainfrom
prajwal-tech07:feat/triangular-mesh-layout

Conversation

@prajwal-tech07
Copy link
Copy Markdown

Describe your changes

Adds support for mesh_layout="triangular" in create_all_graph_components, using networkx.triangular_lattice_graph to produce an equilateral-triangle lattice with 6-connectivity (vs 8 for rectilinear). This provides more isotropic message passing for weather model graph neural networks.

New module: triangular.py (428 lines)

  • create_single_level_2d_triangular_mesh_primitive — coordinate creation using triangular_lattice_graph, scales/offsets positions to cover the coordinate domain
  • create_multirange_2d_triangular_mesh_primitives — multi-level triangular primitives at progressively coarser resolutions
  • create_single_level_2d_triangular_mesh_graph — convenience wrapper (primitive → directed)
  • create_flat_singlescale_triangular_mesh_graph — flat single-scale wrapper
  • create_flat_multiscale_from_triangular_coordinatesposition-based (KDTree) node merging for flat multiscale (replaces the index-based reshaping approach from rectilinear which is incompatible with triangular lattice node labeling)
  • create_flat_multiscale_triangular_mesh_graph — flat multiscale wrapper
  • create_hierarchical_triangular_mesh_graph — hierarchical wrapper

Dispatch in base.py:

  • All 3 m2m_connectivity modes (flat, hierarchical, flat_multiscale) now support mesh_layout="triangular"
  • Default connectivity pattern is "4-star" for triangular (all edges are cardinal; "4-star" == "8-star")
  • Added CRS geographic coordinate warning

Tests: 97 tests in test_triangular_mesh.py covering:

  • Primitive creation (node count, positions, adjacency_type, type attrs, edge cases)
  • Directed graph (bidirectional edges, len/vdiff attrs, 6-connectivity, degree checks)
  • Multirange primitives (levels, refinement factors, domain coverage)
  • Flat single-scale, flat multiscale, and hierarchical wrappers
  • Full integration through create_all_graph_components for all 3 modes
  • Error handling (missing params, spacing too large, unsupported layouts)
  • Numerical correctness (len symmetry, vdiff reciprocity, no NaN/Inf, scaling)
  • Rectilinear regression tests (all 3 modes still work)

This is part of the flexible graph construction effort for Issue #80.

No new dependencies — uses existing networkx, numpy, scipy.spatial.

Issue Link

closes #80

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 documentation 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)

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

Checklist for assignee

  • PR is up to date with the base branch
  • the tests pass
  • author has added an entry to the changelog (and designated the change as added, changed or fixed)
  • Once the PR is ready to be merged, squash commits and merge the PR.

…ivity architecture

Implement the mesh_layout parameter and refactor mesh graph creation into a
two-step process as discussed in mllam#78:

1. Coordinate creation (mesh_layout + mesh_layout_kwargs):
   - create_single_level_2d_mesh_coordinates() returns nx.Graph with spatial
     adjacency edges annotated as 'cardinal' or 'diagonal'
   - create_multirange_2d_mesh_coordinates() returns list of nx.Graph for
     multi-level meshes with interlevel_refinement_factor stored as graph attr

2. Connectivity creation (m2m_connectivity + m2m_connectivity_kwargs):
   - create_directed_mesh_graph() converts nx.Graph to nx.DiGraph based on
     pattern ('4-star' or '8-star')
   - create_flat_singlescale_from_coordinates() for flat single-scale
   - create_flat_multiscale_from_coordinates() with intra_level/inter_level
     sub-dicts for explicit connectivity control
   - create_hierarchical_from_coordinates() with intra_level/inter_level
     sub-dicts supporting 'nearest' pattern with k parameter

Parameter restructuring:
- grid_spacing replaces mesh_node_distance in mesh_layout_kwargs
- interlevel_refinement_factor replaces level_refinement_factor
- max_num_levels moves to mesh_layout_kwargs
- m2m_connectivity_kwargs restructured with intra_level/inter_level sub-dicts

Backward compatibility:
- Old-style kwargs (mesh_node_distance, level_refinement_factor, max_num_levels
  in m2m_connectivity_kwargs) are auto-migrated with deprecation warnings
- All existing wrapper functions preserved
- All existing tests pass unchanged

Archetype functions updated to use new parameter scheme:
- create_keisler_graph: pattern='8-star'
- create_graphcast_graph: intra_level=8-star, inter_level=coincident
- create_oskarsson_hierarchical_graph: intra_level=8-star, inter_level=nearest(k=1)

Closes mllam#78
Add 46 new tests in test_mesh_layout.py covering:
- Coordinate creation (nx.Graph with adjacency_type annotations)
- Connectivity creation (4-star vs 8-star pattern filtering)
- New API via create_all_graph_components (flat, flat_multiscale, hierarchical)
- Backward compatibility with deprecation warnings
- Caller dict non-mutation safety
- Error handling (unsupported layouts, missing grid_spacing, etc.)
- Equivalence between archetype functions and new API

Also remove unused imports from base.py (old wrapper functions no longer
called directly from the dispatch logic).
… flat_multiscale kwargs

- Rename interlevel_refinement_factor → refinement_factor, max_num_levels → max_num_refinement_levels
- flat_multiscale uses simple pattern='8-star' (not intra_level/inter_level sub-dicts)
- Only hierarchical uses intra_level/inter_level sub-dicts
- Update archetypes and backward compat migration messages
- Add 36 comprehensive edge case tests (81 total mesh layout tests pass)
- Rename mesh.py -> coords.py to reflect coordinate/primitive focus
- Split mesh creation into two steps: primitive creation (nx.Graph) and
  directed connectivity creation (nx.DiGraph)
- Add create_single_level_2d_mesh_primitive and
  create_directed_mesh_graph as new public API
- Make mesh_layout a required parameter (no default) to force explicit choice
- Extract _migrate_deprecated_kwargs() as a separate module-private
  helper function for planned removal
- Use dict-based interface for intra_level/inter_level kwargs in
  hierarchical mesh creation
- Remove redundant default values in base.py, let each mesh kind
  handle its own defaults
- Add _check_required_graph_attributes() validation in flat.py
- Group method+kwargs arguments together in archetype functions
- Add comprehensive docstrings explaining 4-star/8-star patterns
- Preserve backward compatibility with deprecation warnings
- Update all tests to pass mesh_layout explicitly
- All 81 mesh layout tests passing, 177/179 full suite passing
  (2 pre-existing Windows PermissionError on temp PNG files)
…tion

Implements Issue mllam#80: adds triangular lattice mesh support using
networkx.triangular_lattice_graph, providing 6-connectivity (vs 8 for
rectilinear) for more isotropic message passing.

New module: triangular.py
- create_single_level_2d_triangular_mesh_primitive: coordinate creation
- create_multirange_2d_triangular_mesh_primitives: multi-level primitives
- create_single_level_2d_triangular_mesh_graph: convenience wrapper
- create_flat_singlescale_triangular_mesh_graph: flat single-scale
- create_flat_multiscale_from_triangular_coordinates: position-based
  node merging (KDTree) for flat multiscale, replacing index-based
  approach that only works with rectilinear grids
- create_flat_multiscale_triangular_mesh_graph: flat multiscale wrapper
- create_hierarchical_triangular_mesh_graph: hierarchical wrapper

Dispatch in base.py:
- All 3 m2m_connectivity modes (flat, hierarchical, flat_multiscale)
  now support mesh_layout='triangular'
- Default pattern is '4-star' for triangular (all edges are cardinal)
- CRS geographic coordinate warning added

Tests: 48 new tests covering primitives, directed graphs, multirange,
flat/hierarchical/flat_multiscale, integration, numerical correctness,
pattern equivalence (4-star==8-star), and rectilinear regressions.
Comprehensive edge cases added to every test class:
- Primitive: minimal lattice, large lattice, asymmetric nx/ny, offset domain,
  wide domain, numpy array type checks, no self-loops
- Directed graph: edge count = 2x undirected, no self-loops, pos preserved,
  interior node degree=12 (6-connectivity), minimal lattice directed
- Multirange: single level, refinement factor 2, domain coverage consistency,
  refinement factor preserved, all levels have edges
- Single-level wrapper: edge attrs, rectangular domain, minimal grid
- Flat singlescale: bidirectional edges, smaller spacing = more nodes,
  rectangular domain, no NaN, spacing-just-fits
- Flat multiscale: bidirectional, pos attrs, single-level fallback,
  refinement factor 2, no self-loops, more nodes than coarsest level
- Hierarchical: pos attrs, custom intra_level/inter_level configs,
  no self-loops, inter-level edge count, direction attrs (up/down)
- Integration: within_radius connectivity, nearest_neighbour (singular),
  missing mesh_node_spacing raises for all 3 modes, all components have
  nodes, large domain
- Numerical: no NaN/Inf, no zero-length edges, roughly uniform lengths,
  scaled domain = scaled lengths, positions are finite numpy arrays

Total: 97 tests (up from 48)
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.

[Feat] Add mesh_layout="triangular" for regular triangular mesh generation

3 participants