csaps-js 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -84,43 +84,70 @@ var PPoly = class {
84
84
  }
85
85
  return lo;
86
86
  }
87
- /** Evaluate component `n` at scalar `xq` with derivative order `nu`. */
88
- evalScalar(n, xq, nu, extrapolate) {
89
- const i = this.findPiece(xq, extrapolate);
90
- if (i < 0) return NaN;
91
- const s = xq - this.breaks[i];
92
- const order = this.order;
93
- const pieces = this.pieces;
94
- const ndim = this.ndim;
95
- let res = 0;
96
- for (let k = 0; k < order; k++) {
97
- const e = order - 1 - k;
98
- if (e < nu) continue;
99
- let f = 1;
100
- for (let t = 0; t < nu; t++) f *= e - t;
101
- const coef = this.c[(k * pieces + i) * ndim + n];
102
- res += coef * f * Math.pow(s, e - nu);
103
- }
104
- return res;
105
- }
106
87
  /**
107
- * Evaluate all components at every point in `xs`.
88
+ * Evaluate all components at every point in `xs` (Horner's method).
89
+ *
90
+ * The piece lookup is done once per evaluation point and reused across all
91
+ * components, and the polynomial is evaluated with Horner's scheme rather
92
+ * than `Math.pow` — both matter for multivariate and N-D gridded data where
93
+ * `ndim` is large.
108
94
  *
109
95
  * @returns Flat row-major array with logical shape `(ndim, xs.length)`:
110
96
  * `out[n * xs.length + q]`.
111
97
  */
112
98
  evalAll(xs, nu, extrapolate) {
99
+ const { order, pieces, ndim, breaks, c } = this;
100
+ const deg = order - 1;
113
101
  const L = xs.length;
114
- const out = new Float64Array(this.ndim * L);
102
+ const out = new Float64Array(ndim * L);
103
+ if (nu > deg) return out;
104
+ const effDeg = deg - nu;
105
+ const kstep = pieces * ndim;
106
+ let mult = null;
107
+ if (nu !== 0) {
108
+ mult = new Float64Array(effDeg + 1);
109
+ for (let k = 0; k <= effDeg; k++) mult[k] = fallingFactorial(deg - k, nu);
110
+ }
115
111
  for (let q = 0; q < L; q++) {
116
112
  const xq = xs[q];
117
- for (let n = 0; n < this.ndim; n++) {
118
- out[n * L + q] = this.evalScalar(n, xq, nu, extrapolate);
113
+ const i = this.findPiece(xq, extrapolate);
114
+ if (i < 0) {
115
+ for (let n = 0; n < ndim; n++) out[n * L + q] = NaN;
116
+ continue;
117
+ }
118
+ const s = xq - breaks[i];
119
+ const start = i * ndim;
120
+ if (nu === 0) {
121
+ for (let n = 0; n < ndim; n++) {
122
+ let off = start + n;
123
+ let res = c[off];
124
+ for (let k = 1; k <= deg; k++) {
125
+ off += kstep;
126
+ res = res * s + c[off];
127
+ }
128
+ out[n * L + q] = res;
129
+ }
130
+ } else {
131
+ const m = mult;
132
+ for (let n = 0; n < ndim; n++) {
133
+ let off = start + n;
134
+ let res = c[off] * m[0];
135
+ for (let k = 1; k <= effDeg; k++) {
136
+ off += kstep;
137
+ res = res * s + c[off] * m[k];
138
+ }
139
+ out[n * L + q] = res;
140
+ }
119
141
  }
120
142
  }
121
143
  return out;
122
144
  }
123
145
  };
146
+ function fallingFactorial(base, nu) {
147
+ let f = 1;
148
+ for (let t = 0; t < nu; t++) f *= base - t;
149
+ return f;
150
+ }
124
151
 
125
152
  // src/umv.ts
126
153
  function computeSmooth(traceR, traceQtw) {
@@ -152,9 +179,8 @@ function normalizeSmooth(x, w, smooth) {
152
179
  const s = smooth == null ? 0.5 : smooth;
153
180
  return s / (s + (1 - s) * k);
154
181
  }
155
- function makeSpline(x, y2d, w, smooth, normalizedsmooth) {
182
+ function makeSpline(x, y, N, w, smooth, normalizedsmooth) {
156
183
  const M = x.length;
157
- const N = y2d.length;
158
184
  const dx = new Float64Array(M - 1);
159
185
  for (let i = 0; i < M - 1; i++) {
160
186
  dx[i] = x[i + 1] - x[i];
@@ -162,18 +188,18 @@ function makeSpline(x, y2d, w, smooth, normalizedsmooth) {
162
188
  throw new Error("Items of 'xdata' must satisfy x1 < x2 < ... < xN.");
163
189
  }
164
190
  }
165
- const dydx = new Array(N);
191
+ const md = M - 1;
192
+ const dydx = new Float64Array(N * md);
166
193
  for (let n = 0; n < N; n++) {
167
- const row = new Float64Array(M - 1);
168
- const yn = y2d[n];
169
- for (let i = 0; i < M - 1; i++) row[i] = (yn[i + 1] - yn[i]) / dx[i];
170
- dydx[n] = row;
194
+ const yb = n * M;
195
+ const db = n * md;
196
+ for (let i = 0; i < md; i++) dydx[db + i] = (y[yb + i + 1] - y[yb + i]) / dx[i];
171
197
  }
172
198
  if (M === 2) {
173
199
  const c2 = new Float64Array(2 * 1 * N);
174
200
  for (let n = 0; n < N; n++) {
175
- c2[0 * N + n] = dydx[n][0];
176
- c2[1 * N + n] = y2d[n][0];
201
+ c2[0 * N + n] = dydx[n * md];
202
+ c2[1 * N + n] = y[n * M];
177
203
  }
178
204
  return { pp: new PPoly(c2, Float64Array.from(x), 2, 1, N), smooth: 1 };
179
205
  }
@@ -221,44 +247,63 @@ function makeSpline(x, y2d, w, smooth, normalizedsmooth) {
221
247
  }
222
248
  const B = new Float64Array(m * N);
223
249
  for (let i = 0; i < m; i++) {
224
- for (let n = 0; n < N; n++) B[i * N + n] = dydx[n][i + 1] - dydx[n][i];
250
+ for (let n = 0; n < N; n++) {
251
+ const db = n * md + i;
252
+ B[i * N + n] = dydx[db + 1] - dydx[db];
253
+ }
225
254
  }
226
255
  const U = ldltBandSolve(m, 2, lower, B, N);
227
- const uAt = (r, n) => r === 0 || r === M - 1 ? 0 : U[(r - 1) * N + n];
256
+ const uFull = new Float64Array(M * N);
257
+ for (let i = 1; i < M - 1; i++) {
258
+ const src = (i - 1) * N;
259
+ const dst = i * N;
260
+ for (let n = 0; n < N; n++) uFull[dst + n] = U[src + n];
261
+ }
228
262
  const d1 = new Float64Array((M - 1) * N);
229
263
  for (let i = 0; i < M - 1; i++) {
230
264
  const inv = dr[i];
231
- for (let n = 0; n < N; n++) d1[i * N + n] = (uAt(i + 1, n) - uAt(i, n)) * inv;
265
+ const a = i * N;
266
+ const b = a + N;
267
+ for (let n = 0; n < N; n++) d1[a + n] = (uFull[b + n] - uFull[a + n]) * inv;
232
268
  }
233
- const d1At = (r, n) => r === 0 || r === M ? 0 : d1[(r - 1) * N + n];
234
269
  const d2 = new Float64Array(M * N);
235
- for (let i = 0; i < M; i++) {
236
- for (let n = 0; n < N; n++) d2[i * N + n] = d1At(i + 1, n) - d1At(i, n);
270
+ const lastD1 = (M - 2) * N;
271
+ const lastD2 = (M - 1) * N;
272
+ for (let n = 0; n < N; n++) {
273
+ d2[n] = d1[n];
274
+ d2[lastD2 + n] = -d1[lastD1 + n];
275
+ }
276
+ for (let i = 1; i < M - 1; i++) {
277
+ const di = i * N;
278
+ const dim1 = di - N;
279
+ for (let n = 0; n < N; n++) d2[di + n] = d1[di + n] - d1[dim1 + n];
237
280
  }
238
281
  const yi = new Float64Array(M * N);
239
282
  for (let i = 0; i < M; i++) {
240
283
  const f = pp6 * iw[i];
241
- for (let n = 0; n < N; n++) yi[i * N + n] = y2d[n][i] - f * d2[i * N + n];
284
+ const row = i * N;
285
+ for (let n = 0; n < N; n++) yi[row + n] = y[n * M + i] - f * d2[row + n];
242
286
  }
243
- const puAt = (r, n) => r === 0 || r === M - 1 ? 0 : p * U[(r - 1) * N + n];
244
287
  const pieces = M - 1;
245
288
  const c = new Float64Array(4 * pieces * N);
289
+ const o1 = pieces * N;
290
+ const o2 = 2 * pieces * N;
291
+ const o3 = 3 * pieces * N;
246
292
  for (let i = 0; i < pieces; i++) {
247
293
  const h = dx[i];
248
294
  const invh = dr[i];
295
+ const ri = i * N;
296
+ const ri1 = ri + N;
249
297
  for (let n = 0; n < N; n++) {
250
- const pu0 = puAt(i, n);
251
- const pu1 = puAt(i + 1, n);
252
- const yi0 = yi[i * N + n];
253
- const yi1 = yi[(i + 1) * N + n];
254
- const c1 = (pu1 - pu0) * invh;
255
- const c2 = 3 * pu0;
256
- const c3 = (yi1 - yi0) * invh - h * (2 * pu0 + pu1);
257
- const c4 = yi0;
258
- c[(0 * pieces + i) * N + n] = c1;
259
- c[(1 * pieces + i) * N + n] = c2;
260
- c[(2 * pieces + i) * N + n] = c3;
261
- c[(3 * pieces + i) * N + n] = c4;
298
+ const pu0 = p * uFull[ri + n];
299
+ const pu1 = p * uFull[ri1 + n];
300
+ const yi0 = yi[ri + n];
301
+ const yi1 = yi[ri1 + n];
302
+ const ci = ri + n;
303
+ c[ci] = (pu1 - pu0) * invh;
304
+ c[o1 + ci] = 3 * pu0;
305
+ c[o2 + ci] = (yi1 - yi0) * invh - h * (2 * pu0 + pu1);
306
+ c[o3 + ci] = yi0;
262
307
  }
263
308
  }
264
309
  return { pp: new PPoly(c, Float64Array.from(x), 4, pieces, N), smooth: p };
@@ -270,36 +315,44 @@ function prepareUnivariate(xdata, ydata, weights, axis) {
270
315
  const x = toFloat64(xdata);
271
316
  const M = x.length;
272
317
  if (x.length < 2) throw new Error("'xdata' must contain at least 2 data points.");
273
- let y2d;
318
+ let y;
319
+ let N;
274
320
  let isVector;
275
321
  let transposed = false;
276
322
  const first = ydata[0];
277
323
  if (Array.isArray(first) || first instanceof Float64Array) {
278
- const y = ydata;
279
- const R = y.length;
280
- const C = y[0].length;
324
+ const yin = ydata;
325
+ const R = yin.length;
326
+ const C = yin[0].length;
281
327
  const ndimY = 2;
282
328
  const ax = axis < 0 ? ndimY + axis : axis;
283
329
  if (ax === 1) {
284
330
  if (C !== M) throw new Error(`'ydata' shape[${ax}] (${C}) must equal 'xdata' size (${M}).`);
285
- y2d = y.map((row) => Array.from(row));
331
+ N = R;
332
+ y = new Float64Array(N * M);
333
+ for (let n = 0; n < N; n++) {
334
+ const row = yin[n];
335
+ const base = n * M;
336
+ for (let i = 0; i < M; i++) y[base + i] = row[i];
337
+ }
286
338
  } else if (ax === 0) {
287
339
  if (R !== M) throw new Error(`'ydata' shape[${ax}] (${R}) must equal 'xdata' size (${M}).`);
288
340
  transposed = true;
289
- y2d = new Array(C);
290
- for (let n = 0; n < C; n++) {
291
- const row = new Array(M);
292
- for (let i = 0; i < M; i++) row[i] = y[i][n];
293
- y2d[n] = row;
341
+ N = C;
342
+ y = new Float64Array(N * M);
343
+ for (let i = 0; i < M; i++) {
344
+ const row = yin[i];
345
+ for (let n = 0; n < N; n++) y[n * M + i] = row[n];
294
346
  }
295
347
  } else {
296
348
  throw new Error(`Unsupported axis ${axis} for 2-D ydata.`);
297
349
  }
298
350
  isVector = false;
299
351
  } else {
300
- const y = Array.from(ydata);
301
- if (y.length !== M) throw new Error(`'ydata' size (${y.length}) must equal 'xdata' size (${M}).`);
302
- y2d = [y];
352
+ const yin = ydata;
353
+ if (yin.length !== M) throw new Error(`'ydata' size (${yin.length}) must equal 'xdata' size (${M}).`);
354
+ N = 1;
355
+ y = toFloat64(yin);
303
356
  isVector = true;
304
357
  }
305
358
  let w;
@@ -309,7 +362,6 @@ function prepareUnivariate(xdata, ydata, weights, axis) {
309
362
  w = toFloat64(weights);
310
363
  if (w.length !== M) throw new Error("Weights vector size must equal xdata size.");
311
364
  }
312
- const N = y2d.length;
313
365
  const restore = (flat, L) => {
314
366
  if (isVector) {
315
367
  const out2 = new Array(L);
@@ -333,13 +385,13 @@ function prepareUnivariate(xdata, ydata, weights, axis) {
333
385
  }
334
386
  return out;
335
387
  };
336
- return { x, y2d, w, restore };
388
+ return { x, y, N, w, restore };
337
389
  }
338
390
  var CubicSmoothingSpline = class {
339
391
  constructor(xdata, ydata, options = {}) {
340
392
  const { weights, smooth = null, axis = -1, normalizedsmooth = false } = options;
341
393
  const prep = prepareUnivariate(xdata, ydata, weights, axis);
342
- const res = makeSpline(prep.x, prep.y2d, prep.w, smooth ?? null, normalizedsmooth);
394
+ const res = makeSpline(prep.x, prep.y, prep.N, prep.w, smooth ?? null, normalizedsmooth);
343
395
  this.pp = res.pp;
344
396
  this.smooth = res.smooth;
345
397
  this.restore = prep.restore;
@@ -389,17 +441,19 @@ var NdArray = class _NdArray {
389
441
  const d = this.shape.length;
390
442
  const newShape = perm.map((axis) => this.shape[axis]);
391
443
  const oldStrides = cStrides(this.shape);
392
- const permutedStrides = perm.map((axis) => oldStrides[axis]);
444
+ const ps = perm.map((axis) => oldStrides[axis]);
393
445
  const total = this.data.length;
446
+ const src = this.data;
394
447
  const out = new Float64Array(total);
395
448
  const idx = new Array(d).fill(0);
449
+ let off = 0;
396
450
  for (let pos = 0; pos < total; pos++) {
397
- let off = 0;
398
- for (let a = 0; a < d; a++) off += idx[a] * permutedStrides[a];
399
- out[pos] = this.data[off];
451
+ out[pos] = src[off];
400
452
  for (let a = d - 1; a >= 0; a--) {
453
+ off += ps[a];
401
454
  if (++idx[a] < newShape[a]) break;
402
455
  idx[a] = 0;
456
+ off -= ps[a] * newShape[a];
403
457
  }
404
458
  }
405
459
  return new _NdArray(out, newShape);
@@ -481,15 +535,8 @@ function buildGrid(xs, yNd, weights, smooth, normalizedsmooth) {
481
535
  const lastSize = shape[shape.length - 1];
482
536
  const total = coeffs.data.length;
483
537
  const Nrest = total / lastSize;
484
- const y2d = new Array(Nrest);
485
- for (let n = 0; n < Nrest; n++) {
486
- const row = new Array(lastSize);
487
- const base = n * lastSize;
488
- for (let col = 0; col < lastSize; col++) row[col] = coeffs.data[base + col];
489
- y2d[n] = row;
490
- }
491
538
  const w = weights[i] ?? new Float64Array(lastSize).fill(1);
492
- const res = makeSpline(xs[i], y2d, w, smooth[i], normalizedsmooth);
539
+ const res = makeSpline(xs[i], coeffs.data, Nrest, w, smooth[i], normalizedsmooth);
493
540
  const order = res.pp.order;
494
541
  const pcs = res.pp.pieces;
495
542
  orders[i] = order;