The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Quick Start

Gilles Colling

2026-11-02

Installation

install.packages("tulpaMesh")            # CRAN

install.packages("pak")                  # development version
pak::pak("gcol33/tulpaMesh")

Your first mesh

tulpaMesh takes point coordinates and returns a triangulated mesh with FEM matrices ready for SPDE models.

set.seed(42)
coords <- cbind(x = runif(100), y = runif(100))
mesh <- tulpa_mesh(coords)
mesh
#> tulpa_mesh:
#>   Vertices:   138 
#>   Triangles:  236 
#>   Edges:      373 
#>   ( 100  input + 38  generated)

The mesh extends slightly beyond the convex hull of your points (controlled by extend). Plot it:

plot(mesh, vertex_col = "steelblue", main = "Basic mesh")

Controlling mesh density

Use max_edge to add refinement points. The mesh generator places a hexagonal lattice of points at this spacing, producing near-equilateral triangles.

mesh_fine <- tulpa_mesh(coords, max_edge = 0.08)
mesh_fine
#> tulpa_mesh:
#>   Vertices:   336 
#>   Triangles:  595 
#>   Edges:      930 
#>   ( 100  input + 236  generated)
plot(mesh_fine, main = "Refined mesh (max_edge = 0.08)")

Getting FEM matrices

fem_matrices() returns the three sparse matrices needed for SPDE models:

fem <- fem_matrices(mesh_fine, obs_coords = coords)
dim(fem$C)
#> [1] 336 336
dim(fem$A)
#> [1] 100 336

# Verify key properties
all(Matrix::diag(fem$C) > 0)        # positive diagonal
#> [1] TRUE
max(abs(Matrix::rowSums(fem$G)))     # row sums ~ 0
#> [1] 7.882583e-15
range(Matrix::rowSums(fem$A))        # row sums = 1
#> [1] 1 1

For the SPDE Q-builder, you typically need the lumped (diagonal) mass matrix:

fem_l <- fem_matrices(mesh_fine, obs_coords = coords, lumped = TRUE)
Matrix::isDiagonal(fem_l$C0)
#> [1] TRUE

Using a formula interface

If your coordinates live in a data.frame, use a formula:

df <- data.frame(lon = runif(50), lat = runif(50), y = rnorm(50))
mesh_f <- tulpa_mesh(~ lon + lat, data = df)
mesh_f
#> tulpa_mesh:
#>   Vertices:   82 
#>   Triangles:  130 
#>   Edges:      211 
#>   ( 50  input + 32  generated)

Mesh quality

Check triangle quality with mesh_quality() and mesh_summary():

mesh_summary(mesh_fine)
#> Mesh quality summary:
#>   Triangles:      595
#>   Min angle:      1.4 / 40.2 / 60.0 (min / median / max)
#>   Max angle:      60.0 / 80.1 / 149.5
#>   Aspect ratio:   1.00 / 1.52 / 40.75
#>   Area:           0.0000 / 0.0023 / 0.0090
#>   WARNING: 16 triangles with min angle < 10 degrees
#>   WARNING: 8 triangles with min angle < 5 degrees (slivers)

Color triangles by minimum angle:

plot(mesh_fine, color = "quality", main = "Colored by minimum angle")

Ruppert refinement

For guaranteed minimum angles, use min_angle:

mesh_r <- tulpa_mesh(coords, min_angle = 25, max_edge = 0.15)
mesh_summary(mesh_r)
#> Mesh quality summary:
#>   Triangles:      697
#>   Min angle:      25.2 / 39.3 / 59.5 (min / median / max)
#>   Max angle:      60.6 / 76.9 / 128.4
#>   Aspect ratio:   1.02 / 1.55 / 2.32
#>   Area:           0.0000 / 0.0015 / 0.0159

Next steps

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.