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.
For global-scale species distribution models or climate fields, you
need a mesh on the sphere. tulpa_mesh_sphere() generates
one by recursive subdivision of an icosahedron.
globe <- tulpa_mesh_sphere(subdivisions = 3)
globe
#> tulpa_mesh (sphere, radius = 1 ):
#> Vertices: 642
#> Triangles: 1280
#> Edges: 1920Each subdivision level quadruples the triangle count:
| Level | Vertices | Triangles |
|---|---|---|
| 0 | 12 | 20 |
| 1 | 42 | 80 |
| 2 | 162 | 320 |
| 3 | 642 | 1280 |
| 4 | 2562 | 5120 |
All vertices lie exactly on the sphere surface:
The 3D FEM assembly uses the metric tensor on each triangle’s tangent plane:
Pass observation coordinates as (longitude, latitude) in degrees:
For spatio-temporal SPDE models, the temporal dimension needs its own
mesh. tulpa_mesh_1d() produces knots with tridiagonal FEM
matrices that combine with the spatial mesh via Kronecker products.
# Monthly observations over 5 years
times <- seq(2020, 2025, by = 1/12)
m1d <- tulpa_mesh_1d(times)
m1d
#> tulpa_mesh_1d:
#> Knots: 67
#> Range: [2019.75, 2025.25]
#> Intervals: 66The extension knots (controlled by n_extend) prevent
boundary effects:
range(times) # data range
#> [1] 2020 2025
range(m1d$knots) # mesh range (extended)
#> [1] 2019.75 2025.25Mass and stiffness matrices are tridiagonal and sparse:
# No extension for cleaner demonstration
m <- tulpa_mesh_1d(seq(0, 1, by = 0.1), n_extend = 0)
# Symmetric, positive definite mass
Matrix::isSymmetric(m$C)
#> [1] TRUE
all(Matrix::diag(m$C) > 0)
#> [1] TRUE
# Stiffness row sums = 0
max(abs(Matrix::rowSums(m$G)))
#> [1] 1.776357e-15
# Total mass = domain length
sum(m$C)
#> [1] 1The 1D mesh handles irregular intervals naturally:
# Denser sampling in summer, sparser in winter
times_irr <- c(
seq(2020, 2020.25, by = 1/52), # weekly Jan-Mar
seq(2020.25, 2020.75, by = 1/365), # daily Apr-Sep
seq(2020.75, 2021, by = 1/52) # weekly Oct-Dec
)
m_irr <- tulpa_mesh_1d(times_irr, n_extend = 0)
cat(m_irr$n, "knots from", length(times_irr), "unique time points\n")
#> 210 knots from 211 unique time pointsFor SPDE models along roads, rivers, or coastlines,
tulpa_mesh_graph() builds a 1D FEM mesh along network
edges.
# Simple river network: main channel + tributary
edges <- list(
cbind(x = seq(0, 10, by = 0.5), y = rep(0, 21)), # main channel
cbind(x = c(5, 5, 5, 5), y = c(0, 2, 4, 6)) # tributary
)
g <- tulpa_mesh_graph(edges)
g
#> tulpa_mesh_graph:
#> Vertices: 24
#> Segments: 23
#> Junctions: 1 Endpoints: 3Nodes where edges meet are automatically detected:
The graph FEM matrices are structurally identical to the 1D case but assembled across the full network:
library(sf)
#> Linking to GEOS 3.13.1, GDAL 3.11.4, PROJ 9.7.0; sf_use_s2() is TRUE
line1 <- st_linestring(cbind(c(0, 5, 10), c(0, 3, 0)))
line2 <- st_linestring(cbind(c(5, 5), c(3, 8)))
g_sf <- tulpa_mesh_graph(st_sfc(line1, line2), max_edge = 1)
g_sf
#> tulpa_mesh_graph:
#> Vertices: 18
#> Segments: 17
#> Junctions: 1 Endpoints: 3For fields where the range or variance changes across space,
fem_matrices_nonstationary() computes weighted FEM matrices
in C++:
set.seed(42)
mesh <- tulpa_mesh(cbind(runif(50), runif(50)), max_edge = 0.15)
n <- mesh$n_vertices
# Range decreases from left to right
kappa <- sqrt(8) / (2 - mesh$vertices[, 1]) # shorter range on the right
tau <- rep(1, n)
ns <- fem_matrices_nonstationary(mesh, kappa, tau)
names(ns)
#> [1] "Ck" "Gk" "Ct" "C" "G" "C0" "n_mesh"With constant kappa/tau, the weighted matrices are simply scaled versions of the standard ones:
For higher accuracy with fewer mesh nodes, use 6-node quadratic elements:
set.seed(42)
mesh <- tulpa_mesh(cbind(runif(30), runif(30)))
p2 <- fem_matrices_p2(mesh)
cat("P1 nodes:", mesh$n_vertices, "\n")
#> P1 nodes: 40
cat("P2 nodes:", p2$n_mesh, "(", p2$n_vertices, "vertices +",
p2$n_midpoints, "midpoints)\n")
#> P2 nodes: 147 ( 40 vertices + 107 midpoints)The total area is preserved:
For large meshes (>50K triangles), enable parallel assembly:
set.seed(42)
mesh_large <- tulpa_mesh(cbind(runif(500), runif(500)), max_edge = 0.03)
cat(mesh_large$n_triangles, "triangles\n")
#> 3670 triangles
fem_seq <- fem_matrices(mesh_large, parallel = FALSE)
fem_par <- fem_matrices(mesh_large, parallel = TRUE)
# Results are identical
max(abs(fem_seq$C - fem_par$C))
#> [1] 2.168404e-19These 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.