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-03-30

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:   113 
#>   Triangles:  211 
#>   Edges:      323

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:   287 
#>   Triangles:  0 
#>   Edges:      0
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] 287 287
dim(fem$A)
#> [1] 100 287

# Verify key properties
all(Matrix::diag(fem$C) > 0)        # positive diagonal
#> [1] FALSE
max(abs(Matrix::rowSums(fem$G)))     # row sums ~ 0
#> [1] 0
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:   60 
#>   Triangles:  108 
#>   Edges:      167

Mesh quality

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

mesh_summary(mesh_fine)
#> Mesh quality summary:
#>   Triangles:      0
#> Warning in min(q$min_angle): no non-missing arguments to min; returning Inf
#> Warning in max(q$min_angle): no non-missing arguments to max; returning -Inf
#>   Min angle:      Inf / NA / -Inf (min / median / max)
#> Warning in min(q$max_angle): no non-missing arguments to min; returning Inf
#> Warning in max(q$max_angle): no non-missing arguments to max; returning -Inf
#>   Max angle:      Inf / NA / -Inf
#> Warning in min(q$aspect_ratio): no non-missing arguments to min; returning Inf
#> Warning in max(q$aspect_ratio): no non-missing arguments to max; returning -Inf
#>   Aspect ratio:   Inf / NA / -Inf
#> Warning in min(q$area): no non-missing arguments to min; returning Inf
#> Warning in max(q$area): no non-missing arguments to max; returning -Inf
#>   Area:           Inf / NA / -Inf

Color triangles by minimum angle:

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

#> Warning in min(x): no non-missing arguments to min; returning Inf
#> Warning in max(x): no non-missing arguments to max; returning -Inf

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:      0
#> Warning in min(q$min_angle): no non-missing arguments to min; returning Inf
#> Warning in max(q$min_angle): no non-missing arguments to max; returning -Inf
#>   Min angle:      Inf / NA / -Inf (min / median / max)
#> Warning in min(q$max_angle): no non-missing arguments to min; returning Inf
#> Warning in max(q$max_angle): no non-missing arguments to max; returning -Inf
#>   Max angle:      Inf / NA / -Inf
#> Warning in min(q$aspect_ratio): no non-missing arguments to min; returning Inf
#> Warning in max(q$aspect_ratio): no non-missing arguments to max; returning -Inf
#>   Aspect ratio:   Inf / NA / -Inf
#> Warning in min(q$area): no non-missing arguments to min; returning Inf
#> Warning in max(q$area): no non-missing arguments to max; returning -Inf
#>   Area:           Inf / NA / -Inf

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.