import { max, min, pip, rep, matrixDiag, matrixTranspose, matrixAdd, matrixMultiply, matrixChol, matrixChol2inv, matrixSolve, variogramGaussian, variogramExponential, variogramSpherical, } from './utils'; // Train using gaussian processes with bayesian priors function train(t: number[], x: number[], y: number[], model: any, sigma2: any, alpha: number) { const variogram = { t, x, y, nugget: 0.0, range: 0.0, sill: 0.0, A: 1 / 3, n: 0, model: variogramExponential, K: [], M: [], }; switch (model) { case 'gaussian': variogram.model = variogramGaussian; break; case 'exponential': variogram.model = variogramExponential; break; case 'spherical': variogram.model = variogramSpherical; break; default: variogram.model = variogramExponential; } // Lag distance/semivariance let i; let j; let k; let l; let n = t.length; const distance = Array((n * n - n) / 2); for (i = 0, k = 0; i < n; i++) { for (j = 0; j < i; j++, k++) { distance[k] = Array(2); distance[k][0] = Math.pow( Math.pow(x[i] - x[j], 2) + Math.pow(y[i] - y[j], 2), 0.5, ); distance[k][1] = Math.abs(t[i] - t[j]); } } distance.sort((a, b) => a[0] - b[0]); variogram.range = distance[(n * n - n) / 2 - 1][0]; // Bin lag distance const lags = ((n * n - n) / 2) > 30 ? 30 : (n * n - n) / 2; const tolerance = variogram.range / lags; const lag = rep(0, lags); const semi = rep(0, lags); if (lags < 30) { for (l = 0; l < lags; l++) { lag[l] = distance[l][0]; semi[l] = distance[l][1]; } } else { for (i = 0, j = 0, k = 0, l = 0; i < lags && j < ((n * n - n) / 2); i++, k = 0) { while (distance[j][0] <= ((i + 1) * tolerance)) { lag[l] += distance[j][0]; semi[l] += distance[j][1]; j++; k++; if (j >= ((n * n - n) / 2)) break; } if (k > 0) { lag[l] /= k; semi[l] /= k; l++; } } if (l < 2) return variogram; // Error: Not enough points } // Feature transformation n = l; variogram.range = lag[n - 1] - lag[0]; const X = rep(1, 2 * n); const Y = Array(n); const { A } = variogram; for (i = 0; i < n; i++) { // eslint-disable-next-line default-case switch (model) { case 'gaussian': X[i * 2 + 1] = 1.0 - Math.exp(-(1.0 / A) * Math.pow(lag[i] / variogram.range, 2)); break; case 'exponential': X[i * 2 + 1] = 1.0 - Math.exp(-(1.0 / A) * lag[i] / variogram.range); break; case 'spherical': X[i * 2 + 1] = 1.5 * (lag[i] / variogram.range) - 0.5 * Math.pow(lag[i] / variogram.range, 3); break; } Y[i] = semi[i]; } // Least squares const Xt = matrixTranspose(X, n, 2); let Z = matrixMultiply(Xt, X, 2, n, 2); Z = matrixAdd(Z, matrixDiag(1 / alpha, 2), 2, 2); const cloneZ = Z.slice(0); if (matrixChol(Z, 2)) { matrixChol2inv(Z, 2); } else { matrixSolve(cloneZ, 2); Z = cloneZ; } const W = matrixMultiply(matrixMultiply(Z, Xt, 2, 2, n), Y, 2, n, 1); // Variogram parameters variogram.nugget = W[0]; variogram.sill = W[1] * variogram.range + variogram.nugget; variogram.n = x.length; // Gram matrix with prior n = x.length; const K = Array(n * n); for (i = 0; i < n; i++) { for (j = 0; j < i; j++) { K[i * n + j] = variogram.model(Math.pow(Math.pow(x[i] - x[j], 2) + Math.pow(y[i] - y[j], 2), 0.5), variogram.nugget, variogram.range, variogram.sill, variogram.A); K[j * n + i] = K[i * n + j]; } K[i * n + i] = variogram.model(0, variogram.nugget, variogram.range, variogram.sill, variogram.A); } // Inverse penalized Gram matrix projected to target vector let C = matrixAdd(K, matrixDiag(sigma2, n), n, n); const cloneC = C.slice(0); if (matrixChol(C, n)) { matrixChol2inv(C, n); } else { matrixSolve(cloneC, n); C = cloneC; } // Copy unprojected inverted matrix as K const K1 = C.slice(0); const M = matrixMultiply(C, t, n, n, 1); // @ts-ignore variogram.K = K1; // @ts-ignore variogram.M = M; return variogram; } // Model prediction function predict( x: number, y: number, variogram: { n: number; model: (arg0: number, arg1: any, arg2: any, arg3: any, arg4: any) => void; x: number[]; y: number[]; nugget: any; range: any; sill: any; A: any; M: number[]; }, ) { let i; const k = Array(variogram.n); for (i = 0; i < variogram.n; i++) { k[i] = variogram.model(Math.pow(Math.pow(x - variogram.x[i], 2) + Math.pow(y - variogram.y[i], 2), 0.5), variogram.nugget, variogram.range, variogram.sill, variogram.A); } return matrixMultiply(k, variogram.M, 1, variogram.n, 1)[0]; } function variance( x: number, y: number, variogram: { n: number; model: { (arg0: number, arg1: any, arg2: any, arg3: any, arg4: any): void; (arg0: number, arg1: any, arg2: any, arg3: any, arg4: any): number; }; x: number[]; y: number[]; nugget: any; range: any; sill: any; A: any; K: number[]; }, ) { let i; const k = Array(variogram.n); for (i = 0; i < variogram.n; i++) { k[i] = variogram.model( Math.pow(Math.pow(x - variogram.x[i], 2) + Math.pow(y - variogram.y[i], 2), 0.5), variogram.nugget, variogram.range, variogram.sill, variogram.A, ); } const val: number = matrixMultiply( matrixMultiply(k, variogram.K, 1, variogram.n, variogram.n), k, 1, variogram.n, 1)[0]; // @ts-ignore return variogram.model(0, variogram.nugget, variogram.range, variogram.sill, variogram.A) + val; } // Gridded matrices or contour paths function grid( polygons: number[][][], variogram: { t: number[]; n: number; model: (arg0: number, arg1: any, arg2: any, arg3: any, arg4: any) => void; x: number[]; y: number[]; nugget: any; range: any; sill: any; A: any; M: number[]; }, width: number, ) { let i; let j; let k; const n = polygons.length; if (n === 0) return; // Boundaries of polygons space const xlim = [polygons[0][0][0], polygons[0][0][0]]; const ylim = [polygons[0][0][1], polygons[0][0][1]]; for (i = 0; i < n; i++) { for (j = 0; j < polygons[i].length; j++) { // Vertices if (polygons[i][j][0] < xlim[0]) xlim[0] = polygons[i][j][0]; if (polygons[i][j][0] > xlim[1]) xlim[1] = polygons[i][j][0]; if (polygons[i][j][1] < ylim[0]) ylim[0] = polygons[i][j][1]; if (polygons[i][j][1] > ylim[1]) ylim[1] = polygons[i][j][1]; } } // Alloc for O(n^2) space let xtarget; let ytarget; const a = Array(2); const b = Array(2); const lxlim = Array(2); // Local dimensions const lylim = Array(2); // Local dimensions const x = Math.ceil((xlim[1] - xlim[0]) / width); const y = Math.ceil((ylim[1] - ylim[0]) / width); const A = Array(x + 1); for (i = 0; i <= x; i++) A[i] = Array(y + 1); for (i = 0; i < n; i++) { // Range for polygons[i] lxlim[0] = polygons[i][0][0]; lxlim[1] = lxlim[0]; lylim[0] = polygons[i][0][1]; lylim[1] = lylim[0]; for (j = 1; j < polygons[i].length; j++) { // Vertices if (polygons[i][j][0] < lxlim[0]) lxlim[0] = polygons[i][j][0]; if (polygons[i][j][0] > lxlim[1]) lxlim[1] = polygons[i][j][0]; if (polygons[i][j][1] < lylim[0]) lylim[0] = polygons[i][j][1]; if (polygons[i][j][1] > lylim[1]) lylim[1] = polygons[i][j][1]; } // Loop through polygon subspace a[0] = Math.floor(((lxlim[0] - ((lxlim[0] - xlim[0]) % width)) - xlim[0]) / width); a[1] = Math.ceil(((lxlim[1] - ((lxlim[1] - xlim[1]) % width)) - xlim[0]) / width); b[0] = Math.floor(((lylim[0] - ((lylim[0] - ylim[0]) % width)) - ylim[0]) / width); b[1] = Math.ceil(((lylim[1] - ((lylim[1] - ylim[1]) % width)) - ylim[0]) / width); for (j = a[0]; j <= a[1]; j++) { for (k = b[0]; k <= b[1]; k++) { xtarget = xlim[0] + j * width; ytarget = ylim[0] + k * width; if (pip(polygons[i], xtarget, ytarget)) { A[j][k] = predict(xtarget, ytarget, variogram); } } } } return { xlim, ylim, width, data: A, zlim: [min(variogram.t), max(variogram.t)], }; } // Plotting on the DOM function plot( canvas: HTMLCanvasElement, grid: { data: [][], xlim: number[], ylim: number[], width: number, zlim: any[], }, xlim: number[], ylim: number[], colors: any[], ) { // Clear screen const ctx = canvas.getContext('2d'); const { data, zlim, width } = grid; if (ctx) { ctx.clearRect(0, 0, canvas.width, canvas.height); // Starting boundaries const range = [xlim[1] - xlim[0], ylim[1] - ylim[0], zlim[1] - zlim[0]]; let i; let j; let x; let y; let z; const n = data.length; const m = data[0].length; // @ts-ignore const wx = Math.ceil(width * canvas.width / (xlim[1] - xlim[0])); // @ts-ignore const wy = Math.ceil(width * canvas.height / (ylim[1] - ylim[0])); for (i = 0; i < n; i++) { for (j = 0; j < m; j++) { if (data[i][j] === undefined) continue; x = canvas.width * (i * width + grid.xlim[0] - xlim[0]) / range[0]; y = canvas.height * (1 - (j * width + grid.ylim[0] - ylim[0]) / range[1]); z = (data[i][j] - zlim[0]) / range[2]; if (z < 0.0) z = 0.0; if (z > 1.0) z = 1.0; ctx.fillStyle = colors[Math.floor((colors.length - 1) * z)]; ctx.fillRect(Math.round(x - wx / 2), Math.round(y - wy / 2), wx, wy); } } } } export { train, predict, variance, grid, plot, max, min, pip, rep, matrixDiag, matrixTranspose, matrixAdd, matrixMultiply, matrixChol, matrixChol2inv, matrixSolve, variogramGaussian, variogramExponential, variogramSpherical, }; export default { train, predict, variance, grid, plot, max, min, pip, rep, matrixDiag, matrixTranspose, matrixAdd, matrixMultiply, matrixChol, matrixChol2inv, matrixSolve, variogramGaussian, variogramExponential, variogramSpherical, };