meshcore (working name): first proof

One verb set for a raster (GRID) and a discrete global grid (CELL), with arrays joined to either through a keyed dimension. Prototype R package. Design notes, the mesh spec draft, the model zoo and the roadmap are in docs/.

# remotes::install_github("hypertidy/meshcore")
install.packages("meshcore", repos = c('https://hypertidy.r-universe.dev', 'https://cloud.r-project.org'))   # package name: meshcore

meshcore sits between wkpool (vertex pools and topology for any wk geometry; as_wkpool() hands one over) and model packages that add their own models on the same verbs: the silicate2 branch of silicate adds UGRID this way, and its methods make edges(), face_edge(), neighbours() and the array join work on flexible meshes too.

GRID and HEALPix CELL, same verbs, joined arrays

What it shows

library(meshcore)
tif  <- system.file("extdata", "field.tif", package = "meshcore")
zarr <- system.file("extdata", "field_healpix.zarr", package = "meshcore")

for (x in list(GRID(tif), CELL(zarr))) {
  cells(x); boundaries(x); vertices(x); edges(x); face_edge(x)
  neighbours(x); neighbours(x, "vertex"); parent(x); children(x)
}
join_array(GRID(tif),  read_array(tif))            # key computed from x, y
join_array(CELL(zarr), read_array(zarr, "field"))  # key = cell_ids on "cells"

Run Rscript inst/scripts/demo.R for the printed walk-through and figure.

The fast path: identity by construction

Every vertex has an integer lattice key computed from the cell id, so shared corners are never found by comparing coordinates.

Proposal for the identity ladder: a new top rung, lattice (identity by construction), above exact float. Models record rung = "lattice".

Checks

tests/testthat: 49 expectations, all passing.

Benchmark (bench/hex-fastpath.R, results in bench/hex-fastpath.csv), on the benchmark's own hex_grid(n) inputs:

input cells vertices edges fast path (vertices + edges) wkpool merge + edges
hex_1e3 1,000 2,128 3,127 0.002 s 0.02 s
hex_1e4 10,000 20,404 30,403 0.016 s 0.18 s
hex_1e5 100,000 201,280 301,279 0.20 s 2.3 s

Counts equal the silicate-vs-wkpool benchmark and wkpool on the same polygons, and the vertex coordinates agree with wkpool's pool to 1e-9. (wkpool timing includes establish_topology() from sf; wk 0.9.1 here.)

Vocabulary crosswalk

meshcore UGRID / uxarray / xugrid xdggs OGC API DGGS
.cell, cells() face, n_face, face_x/face_y cell_ids, cell_centers() zone, zone id
boundaries() face_node_connectivity cell_boundaries() zone geometry
vertices() (.vx) node, n_node
edges() edge_node_connectivity
face_edge() face_edge_connectivity
neighbours() face_face_connectivity neighbouring zones
parent()/children() zoom_to(level) parent zones, sub-zones
level level refinement level
keyed dimension dimension n_face dimension cells zone data

.vx, .vx0, .vx1 follow wkpool; as_wkpool() hands over an already-merged pool (.vx 1..n, lattice key kept as .key).

What the zoo still needs (found while building)

Layout

R/model.R       GRID(), CELL(), print
R/healpix.R     nested ids, centres, corner lattice keys
R/lattice.R     GRID and hexlattice keys
R/verbs.R       the verb set; generic edges/neighbours from boundaries
R/array.R       keyed_array(), read_array(), join_array()
R/wk.R          as_wk(), as_wkpool()
inst/extdata    field.tif, field_healpix.zarr (made by inst/scripts/make-data.py)
inst/scripts    make-data.py (data + healpy reference), demo.R, demo.png
bench           hex-fastpath.R and results

Built and tested with R 4.3.3, sf 1.0-15 (GDAL 3.8.4), wk 0.9.1, wkpool 0.3.0.9006 (hypertidy/wkpool main after PR #5).