/src/MapServer/src/kriging_nngp.h
Line | Count | Source |
1 | | /****************************************************************************** |
2 | | * |
3 | | * Project: MapServer |
4 | | * Purpose: Nearest-Neighbor Gaussian Process (NNGP) prediction core. |
5 | | * Pure C, depends only on <math.h>/<stdlib.h>/<string.h>; no |
6 | | * MapServer types. Compiled into a single translation unit |
7 | | * (kriging.c) and exercised independently by the unit test, so the |
8 | | * production code and the test run the identical math. |
9 | | * |
10 | | * Author: Hermes L. Herrera and the MapServer team. |
11 | | * |
12 | | ****************************************************************************** |
13 | | * Copyright (c) 2024 Regents of the University of Minnesota. |
14 | | * |
15 | | * Permission is hereby granted, free of charge, to any person obtaining a |
16 | | * copy of this software and associated documentation files (the "Software"), |
17 | | * to deal in the Software without restriction, including without limitation |
18 | | * the rights to use, copy, modify, merge, publish, distribute, sublicense, |
19 | | * and/or sell copies of the Software, and to permit persons to whom the |
20 | | * Software is furnished to do so, subject to the following conditions: |
21 | | * |
22 | | * The above copyright notice and this permission notice shall be included in |
23 | | * all copies of this Software or works derived from this Software. |
24 | | * |
25 | | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS |
26 | | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
27 | | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
28 | | * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
29 | | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
30 | | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
31 | | * DEALINGS IN THE SOFTWARE. |
32 | | *****************************************************************************/ |
33 | | |
34 | | #ifndef KRIGING_NNGP_H |
35 | | #define KRIGING_NNGP_H |
36 | | |
37 | | #include <math.h> |
38 | | #include <stdlib.h> |
39 | | #include <string.h> |
40 | | |
41 | | /* Covariance families. RANGE is the *practical* range: the lag at which the |
42 | | * correlation has decayed to ~0.05 (exp/gauss are scaled so this holds). */ |
43 | | enum { |
44 | | KR_EXPONENTIAL = 0, /* Matern nu=1/2 */ |
45 | | KR_GAUSSIAN = 1, /* squared-exponential */ |
46 | | KR_SPHERICAL = 2 |
47 | | }; |
48 | | |
49 | | /* Kriging flavor for the local solve. */ |
50 | | enum { KR_ORDINARY = 0, KR_SIMPLE = 1 }; |
51 | | |
52 | | /* Stationary covariance C(h); sigma2 = partial sill, a = practical range. |
53 | | * The exp/gauss forms carry a factor of 3 (exp(-3) ~= 0.05) so correlation has |
54 | | * decayed to ~5% at h = a -- i.e. "range" means the same thing for every model, |
55 | | * including the spherical one which reaches its sill exactly at h = a. */ |
56 | 0 | static double kr_cov(int model, double h, double sigma2, double a) { |
57 | 0 | double r; |
58 | 0 | if (a <= 0.0) |
59 | 0 | a = 1.0; |
60 | 0 | r = h / a; /* normalized lag h/a */ |
61 | 0 | switch (model) { |
62 | 0 | case KR_GAUSSIAN: /* sigma2 * exp(-3 r^2) */ |
63 | 0 | return sigma2 * exp(-3.0 * r * r); |
64 | 0 | case KR_SPHERICAL: /* sigma2 * (1 - 1.5 r + 0.5 r^3), 0 for h >= a */ |
65 | 0 | return h >= a ? 0.0 : sigma2 * (1.0 - 1.5 * r + 0.5 * r * r * r); |
66 | 0 | case KR_EXPONENTIAL: /* sigma2 * exp(-3 r) */ |
67 | 0 | default: |
68 | 0 | return sigma2 * exp(-3.0 * r); |
69 | 0 | } |
70 | 0 | } |
71 | | |
72 | | /* Dense LU with partial pivoting. Solves A x = b (row-major, n x n) in place; |
73 | | * the solution is written back into b. Returns 0 on success, -1 if singular. |
74 | | * The ordinary-kriging system is symmetric *indefinite* (a saddle point), so a |
75 | | * pivoted LU is used rather than Cholesky. n is tiny (<= neighbors + 1). */ |
76 | 0 | static int kr_solve(double *A, double *b, int n) { |
77 | 0 | int i, j, k; |
78 | 0 | for (k = 0; k < n; k++) { /* eliminate column k */ |
79 | | /* partial pivoting: choose the largest-magnitude entry in column k as the |
80 | | * pivot, which keeps the elimination numerically stable */ |
81 | 0 | int p = k; |
82 | 0 | double maxv = fabs(A[k * n + k]); |
83 | 0 | for (i = k + 1; i < n; i++) { |
84 | 0 | double v = fabs(A[i * n + k]); |
85 | 0 | if (v > maxv) { |
86 | 0 | maxv = v; |
87 | 0 | p = i; |
88 | 0 | } |
89 | 0 | } |
90 | 0 | if (maxv < 1e-300) |
91 | 0 | return -1; /* singular */ |
92 | 0 | if (p != k) { /* bring the pivot row up to row k (in A and the rhs b) */ |
93 | 0 | for (j = 0; j < n; j++) { |
94 | 0 | double t = A[p * n + j]; |
95 | 0 | A[p * n + j] = A[k * n + j]; |
96 | 0 | A[k * n + j] = t; |
97 | 0 | } |
98 | 0 | double tb = b[p]; |
99 | 0 | b[p] = b[k]; |
100 | 0 | b[k] = tb; |
101 | 0 | } |
102 | 0 | double piv = A[k * n + k]; |
103 | | /* subtract the right multiple of row k from each row below so column k goes |
104 | | * to zero there, carrying the same combination through the rhs */ |
105 | 0 | for (i = k + 1; i < n; i++) { |
106 | 0 | double f = A[i * n + k] / piv; |
107 | 0 | A[i * n + k] = 0.0; |
108 | 0 | for (j = k + 1; j < n; j++) |
109 | 0 | A[i * n + j] -= f * A[k * n + j]; |
110 | 0 | b[i] -= f * b[k]; |
111 | 0 | } |
112 | 0 | } |
113 | | /* back-substitution: A is now upper-triangular, so solve from the last row |
114 | | * upward; the solution overwrites b */ |
115 | 0 | for (i = n - 1; i >= 0; i--) { |
116 | 0 | double s = b[i]; |
117 | 0 | for (j = i + 1; j < n; j++) |
118 | 0 | s -= A[i * n + j] * b[j]; |
119 | 0 | b[i] = s / A[i * n + i]; |
120 | 0 | } |
121 | 0 | return 0; |
122 | 0 | } |
123 | | |
124 | | /* Bounded "m nearest" accumulator: holds the m smallest squared distances seen |
125 | | * so far, ascending. m is tiny, so a shifting insertion sort is cheaper than a |
126 | | * heap and keeps the current worst (d2[count-1]) at hand for the query cutoff. |
127 | | */ |
128 | | typedef struct { |
129 | | int *idx; |
130 | | double *d2; |
131 | | int count, cap; |
132 | | } kr_knn; |
133 | | |
134 | 0 | static void kr_knn_insert(kr_knn *k, int id, double d2) { |
135 | 0 | int i; |
136 | 0 | if (k->count < k->cap) { |
137 | 0 | i = k->count++; |
138 | 0 | } else if (d2 < k->d2[k->cap - 1]) { |
139 | 0 | i = k->cap - 1; /* full: this is nearer than the worst kept, so evict it */ |
140 | 0 | } else { |
141 | 0 | return; /* full and no closer than the worst kept -> nothing to do */ |
142 | 0 | } |
143 | | /* insertion sort: slide the larger kept entries up one slot until d2's place |
144 | | * is open, then drop it in -- the array stays sorted ascending */ |
145 | 0 | for (; i > 0 && k->d2[i - 1] > d2; i--) { |
146 | 0 | k->d2[i] = k->d2[i - 1]; |
147 | 0 | k->idx[i] = k->idx[i - 1]; |
148 | 0 | } |
149 | 0 | k->d2[i] = d2; |
150 | 0 | k->idx[i] = id; |
151 | 0 | } |
152 | | |
153 | | /* Uniform-grid spatial index over the samples (in pixel coordinates), packed |
154 | | * CSR-style so a neighbour query only scans the cells around the target. */ |
155 | | typedef struct { |
156 | | int nx, ny; |
157 | | double minx, miny, cell, invcell; |
158 | | int *start; /* CSR offsets, length nx*ny + 1 */ |
159 | | int *items; /* sample indices bucketed by cell, length n */ |
160 | | } kr_grid; |
161 | | |
162 | | static int kr_grid_build(kr_grid *g, const double *sx, const double *sy, |
163 | 0 | int n) { |
164 | 0 | int i, nc; |
165 | 0 | double mnx, mxx, mny, mxy, w, h, cell; |
166 | 0 | int *cur; |
167 | 0 | memset(g, 0, sizeof(*g)); |
168 | 0 | if (n <= 0) |
169 | 0 | return -1; |
170 | | |
171 | | /* bounding box of the sample cloud -> sets the grid's origin and extent */ |
172 | 0 | mnx = mxx = sx[0]; |
173 | 0 | mny = mxy = sy[0]; |
174 | 0 | for (i = 1; i < n; i++) { |
175 | 0 | if (sx[i] < mnx) |
176 | 0 | mnx = sx[i]; |
177 | 0 | if (sx[i] > mxx) |
178 | 0 | mxx = sx[i]; |
179 | 0 | if (sy[i] < mny) |
180 | 0 | mny = sy[i]; |
181 | 0 | if (sy[i] > mxy) |
182 | 0 | mxy = sy[i]; |
183 | 0 | } |
184 | 0 | w = mxx - mnx; |
185 | 0 | h = mxy - mny; |
186 | 0 | if (w <= 0) |
187 | 0 | w = 1; |
188 | 0 | if (h <= 0) |
189 | 0 | h = 1; |
190 | 0 | cell = sqrt((w * h) / (double)n); /* ~1 sample / cell */ |
191 | 0 | if (!(cell > 0)) |
192 | 0 | cell = 1; |
193 | 0 | g->minx = mnx; |
194 | 0 | g->miny = mny; |
195 | 0 | g->cell = cell; |
196 | 0 | g->invcell = 1.0 / cell; |
197 | 0 | g->nx = (int)(w * g->invcell) + 1; /* grid width in cells */ |
198 | 0 | g->ny = (int)(h * g->invcell) + 1; /* grid height in cells */ |
199 | 0 | nc = g->nx * g->ny; |
200 | | /* start[]: per-cell CSR offsets; items[]: sample ids grouped by cell; |
201 | | * cur[]: scratch write cursors used only while scattering below */ |
202 | 0 | g->start = (int *)calloc((size_t)nc + 1, sizeof(int)); |
203 | 0 | g->items = (int *)malloc((size_t)n * sizeof(int)); |
204 | 0 | cur = (int *)malloc((size_t)nc * sizeof(int)); |
205 | 0 | if (!g->start || !g->items || !cur) { |
206 | 0 | free(g->start); |
207 | 0 | free(g->items); |
208 | 0 | free(cur); |
209 | 0 | return -1; |
210 | 0 | } |
211 | | /* counting sort into per-cell buckets (CSR): tally each cell's count, prefix- |
212 | | * sum the counts into start offsets, then scatter the sample ids into place. |
213 | | */ |
214 | 0 | for (i = 0; i < n; i++) { |
215 | | /* which cell does sample i fall in? clamp to the grid's edge cells */ |
216 | 0 | int cx = (int)((sx[i] - mnx) * g->invcell); |
217 | 0 | int cy = (int)((sy[i] - mny) * g->invcell); |
218 | 0 | if (cx < 0) |
219 | 0 | cx = 0; |
220 | 0 | else if (cx >= g->nx) |
221 | 0 | cx = g->nx - 1; |
222 | 0 | if (cy < 0) |
223 | 0 | cy = 0; |
224 | 0 | else if (cy >= g->ny) |
225 | 0 | cy = g->ny - 1; |
226 | 0 | g->start[cy * g->nx + cx + |
227 | 0 | 1]++; /* tally into the next cell's offset slot */ |
228 | 0 | } |
229 | 0 | for (i = 0; i < nc; i++) /* counts -> CSR start offsets */ |
230 | 0 | g->start[i + 1] += g->start[i]; |
231 | 0 | memcpy(cur, g->start, (size_t)nc * sizeof(int)); /* per-cell write cursors */ |
232 | 0 | for (i = 0; i < n; i++) { |
233 | 0 | int cx = (int)((sx[i] - mnx) * g->invcell); |
234 | 0 | int cy = (int)((sy[i] - mny) * g->invcell); |
235 | 0 | if (cx < 0) |
236 | 0 | cx = 0; |
237 | 0 | else if (cx >= g->nx) |
238 | 0 | cx = g->nx - 1; |
239 | 0 | if (cy < 0) |
240 | 0 | cy = 0; |
241 | 0 | else if (cy >= g->ny) |
242 | 0 | cy = g->ny - 1; |
243 | 0 | g->items[cur[cy * g->nx + cx]++] = i; /* place sample i, advance cursor */ |
244 | 0 | } |
245 | 0 | free(cur); |
246 | 0 | return 0; |
247 | 0 | } |
248 | | |
249 | 0 | static void kr_grid_free(kr_grid *g) { |
250 | 0 | free(g->start); |
251 | 0 | free(g->items); |
252 | 0 | g->start = NULL; |
253 | 0 | g->items = NULL; |
254 | 0 | } |
255 | | |
256 | | /* Exact m-nearest-neighbor query: expand square rings of cells until the ring |
257 | | * distance proves no closer sample can remain unexamined. */ |
258 | | static void kr_grid_knn(const kr_grid *g, const double *sx, const double *sy, |
259 | 0 | double qx, double qy, int m, kr_knn *kn) { |
260 | 0 | int r, maxr, hcx, hcy; |
261 | 0 | kn->count = 0; |
262 | | /* home cell: the grid cell containing the query, clamped into range */ |
263 | 0 | hcx = (int)((qx - g->minx) * g->invcell); |
264 | 0 | hcy = (int)((qy - g->miny) * g->invcell); |
265 | 0 | if (hcx < 0) |
266 | 0 | hcx = 0; |
267 | 0 | else if (hcx >= g->nx) |
268 | 0 | hcx = g->nx - 1; |
269 | 0 | if (hcy < 0) |
270 | 0 | hcy = 0; |
271 | 0 | else if (hcy >= g->ny) |
272 | 0 | hcy = g->ny - 1; |
273 | 0 | maxr = g->nx + g->ny; /* an upper bound on rings: spans the whole grid */ |
274 | | /* Grow a square "ring" of cells outward from the query's home cell. Ring r is |
275 | | * the border at Chebyshev (chessboard) distance r: the box [hcx-r, hcx+r] x |
276 | | * [hcy-r, hcy+r], with its interior skipped -- earlier rings already covered |
277 | | * it -- so each cell is scanned exactly once across all rings. */ |
278 | 0 | for (r = 0; r <= maxr; r++) { |
279 | 0 | int cy, cx, x0 = hcx - r, x1 = hcx + r, y0 = hcy - r, y1 = hcy + r; |
280 | 0 | for (cy = y0; cy <= y1; cy++) { |
281 | 0 | if (cy < 0 || cy >= g->ny) |
282 | 0 | continue; /* row off the grid */ |
283 | 0 | for (cx = x0; cx <= x1; cx++) { |
284 | 0 | int ci, t; |
285 | 0 | if (cx < 0 || cx >= g->nx) |
286 | 0 | continue; /* column off the grid */ |
287 | 0 | if (r > 0 && cx > x0 && cx < x1 && cy > y0 && cy < y1) |
288 | 0 | continue; /* interior cell already visited on an earlier ring */ |
289 | 0 | ci = cy * g->nx + cx; |
290 | | /* test every sample bucketed in this cell against the query */ |
291 | 0 | for (t = g->start[ci]; t < g->start[ci + 1]; t++) { |
292 | 0 | int id = g->items[t]; |
293 | 0 | double dx = sx[id] - qx, dy = sy[id] - qy; |
294 | 0 | kr_knn_insert(kn, id, dx * dx + dy * dy); /* ranked by squared dist */ |
295 | 0 | } |
296 | 0 | } |
297 | 0 | } |
298 | | /* rings 0..r are fully scanned, so any sample not yet seen sits in a cell |
299 | | * at ring >= r+1 -- at least r*cell from the query. once that lower bound |
300 | | * meets the current m-th distance no nearer sample can remain, so we stop |
301 | | * with the *exact* m nearest rather than an approximation. */ |
302 | 0 | if (kn->count >= m) { |
303 | 0 | double bound = (double)r * g->cell; |
304 | 0 | if (bound * bound >= kn->d2[kn->count - 1]) |
305 | 0 | break; |
306 | 0 | } |
307 | 0 | } |
308 | 0 | } |
309 | | |
310 | | /* Local kriging / NNGP predictive distribution at (qx,qy) from the m samples in |
311 | | * nb[]. Writes the predictive mean and (latent) variance. Scratch buffers must |
312 | | * hold (m+1)*(m+1) doubles (A) and (m+1) doubles (k0, w). Returns 0, or -1 if |
313 | | * the local system is singular (caller should leave the pixel as no-data). */ |
314 | | static int kr_predict(const double *sx, const double *sy, const double *sz, |
315 | | const int *nb, int m, double qx, double qy, int model, |
316 | | int ktype, double sigma2, double range, double nugget, |
317 | | double *A, double *k0, double *w, double *mean_out, |
318 | 0 | double *var_out) { |
319 | 0 | int M = |
320 | 0 | (ktype == KR_ORDINARY) ? m + 1 : m; /* ordinary adds one bordered row */ |
321 | 0 | int i, j; |
322 | 0 | double mean = 0.0, quad = 0.0, var; |
323 | 0 | if (m <= 0) |
324 | 0 | return -1; |
325 | | /* A <- K, the covariance matrix among the m neighbours; k0 <- the covariance |
326 | | * of each neighbour with the target pixel. */ |
327 | 0 | for (i = 0; i < m; i++) { |
328 | 0 | double xi = sx[nb[i]], yi = sy[nb[i]]; |
329 | 0 | for (j = 0; j < m; j++) { |
330 | 0 | double dx = xi - sx[nb[j]], dy = yi - sy[nb[j]]; |
331 | 0 | A[i * M + j] = kr_cov(model, sqrt(dx * dx + dy * dy), sigma2, range); |
332 | 0 | } |
333 | 0 | A[i * M + i] += |
334 | 0 | nugget; /* nugget on the diagonal => smoothing / stability */ |
335 | 0 | { |
336 | 0 | double dx = xi - qx, dy = yi - qy; |
337 | 0 | k0[i] = kr_cov(model, sqrt(dx * dx + dy * dy), sigma2, range); |
338 | 0 | } |
339 | 0 | } |
340 | 0 | if (ktype == KR_ORDINARY) { |
341 | | /* Unknown local mean: border K with the unbiasedness constraint (weights |
342 | | * must sum to 1). The extra unknown w[m] is the Lagrange multiplier, so the |
343 | | * system becomes [K 1; 1' 0] [w; mu] = [k0; 1]. */ |
344 | 0 | for (i = 0; i < m; i++) { |
345 | 0 | A[i * M + m] = 1.0; |
346 | 0 | A[m * M + i] = 1.0; |
347 | 0 | } |
348 | 0 | A[m * M + m] = 0.0; |
349 | 0 | k0[m] = 1.0; |
350 | 0 | } |
351 | 0 | memcpy(w, k0, |
352 | 0 | (size_t)M * sizeof(double)); /* solve in w; keep k0 for the variance */ |
353 | 0 | if (kr_solve(A, w, M) != 0) |
354 | 0 | return -1; |
355 | | /* predictor z*(x0) = sum_i w_i z_i ; quad = sum_i w_i C(x0, x_i) */ |
356 | 0 | for (i = 0; i < m; i++) { |
357 | 0 | mean += w[i] * sz[nb[i]]; |
358 | 0 | quad += w[i] * k0[i]; |
359 | 0 | } |
360 | | /* kriging variance = sigma2 - sum_i w_i C(x0, x_i), less the Lagrange term mu |
361 | | * (= w[m]) for ordinary kriging; clamp round-off below zero. */ |
362 | 0 | var = sigma2 - quad - (ktype == KR_ORDINARY ? w[m] : 0.0); |
363 | 0 | if (var < 0.0) |
364 | 0 | var = 0.0; |
365 | 0 | *mean_out = mean; |
366 | 0 | *var_out = var; |
367 | 0 | return 0; |
368 | 0 | } |
369 | | |
370 | | #endif /* KRIGING_NNGP_H */ |