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