Skip to content

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

@prajwal-tech07

Description

@prajwal-tech07

Summary

Add support for mesh_layout="triangular" in create_all_graph_components(), creating regular triangular meshes using networkx.triangular_lattice_graph. This mirrors the existing rectilinear mesh functions with zero new dependencies.

Motivation

A regular triangular mesh has several advantages over rectilinear:

  • 6-connectivity (vs 8 for rectilinear with diagonals) — more isotropic message passing
  • Better approximation of circular receptive fields — equilateral triangles tile more uniformly than squares + diagonals
  • Natural stepping stone toward icosahedral meshes on a sphere
  • Relevant for LAM models — some regional NWP grids use triangular meshes

As discussed in #71, mesh_layout="triangular" means "uniform equilateral triangles in the coordinates of the provided CRS" — a regular planar grid, not Delaunay triangulation of irregular points.

Proposed API

# Flat triangular mesh
graph = create_all_graph_components(
    coords=xy,
    m2m_connectivity="flat",
    m2m_connectivity_kwargs=dict(mesh_node_distance=3.0),
    mesh_layout="triangular",
    ...
)

# Hierarchical triangular mesh
graph = create_all_graph_components(
    coords=xy,
    m2m_connectivity="hierarchical",
    m2m_connectivity_kwargs=dict(mesh_node_distance=3.0, max_num_levels=3),
    mesh_layout="triangular",
    ...
)

Implementation: Mirrored Function Pairs

The triangular layout mirrors the existing rectilinear functions exactly:

Rectilinear (existing) Triangular (new)
create_single_level_2d_mesh_graph uses networkx.grid_2d_graph (8-connectivity) create_single_level_2d_triangular_mesh_graph uses networkx.triangular_lattice_graph (6-connectivity)
create_flat_singlescale_mesh_graph(xy, mesh_node_distance) create_flat_singlescale_triangular_mesh_graph(xy, mesh_node_distance)
create_flat_multiscale_mesh_graph(xy, mesh_node_distance, ...) create_flat_multiscale_triangular_mesh_graph(xy, mesh_node_distance, ...)

Core function: create_single_level_2d_triangular_mesh_graph

def create_single_level_2d_triangular_mesh_graph(xy, nx, ny):
    """
    Create directed graph with triangular lattice topology.
    Uses networkx.triangular_lattice_graph which produces offset rows
    with equilateral triangle side length 1 and row spacing sqrt(3)/2.
    """
    xm, xM = np.amin(xy[:, 0]), np.amax(xy[:, 0])
    ym, yM = np.amin(xy[:, 1]), np.amax(xy[:, 1])

    dx = (xM - xm) / nx
    dy = (yM - ym) / ny

    g = networkx.triangular_lattice_graph(ny, nx, with_positions=True)

    # Scale and offset positions to cover the domain
    for node in g.nodes:
        pos = np.array(g.nodes[node]["pos"])
        # Scale from unit triangles to domain
        g.nodes[node]["pos"] = np.array([
            xm + dx/2 + pos[0] * dx,
            ym + dy/2 + pos[1] * dy
        ])
        g.nodes[node]["type"] = "mesh"

    # Convert to DiGraph with bidirectional edges
    dg = networkx.DiGraph(g)
    for u, v in g.edges():
        d = np.sqrt(np.sum((dg.nodes[u]["pos"] - dg.nodes[v]["pos"]) ** 2))
        dg.edges[u, v]["len"] = d
        dg.edges[u, v]["vdiff"] = dg.nodes[u]["pos"] - dg.nodes[v]["pos"]
        dg.add_edge(v, u)
        dg.edges[v, u]["len"] = d
        dg.edges[v, u]["vdiff"] = dg.nodes[v]["pos"] - dg.nodes[u]["pos"]

    return dg

mesh_node_distance interface
Same as rectilinear. For triangular, mesh_node_distance controls the side length:

nx = int(range_x / mesh_node_distance)
ny = int(range_y / (mesh_node_distance * np.sqrt(3) / 2))

CRS Warning

If graph_crs.is_geographic == True and mesh_layout="triangular", emit a warning:

if graph_crs is not None and graph_crs.is_geographic and mesh_layout == "triangular":
    warnings.warn(
        "mesh_layout='triangular' produces non-uniform physical spacing in "
        "geographic coordinates. Consider mesh_layout='icosahedral' for "
        "uniform coverage on a sphere."
    )

Depends On

  • mesh_layout argument introduction issue

Related

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions