Coverage Report

Created: 2026-09-14 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/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 */