pond-ts 0.53.1 → 0.55.0

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +1175 -4
  2. package/README.md +25 -21
  3. package/dist/batch/aggregate-columns.d.ts +46 -7
  4. package/dist/batch/aggregate-columns.js +458 -105
  5. package/dist/batch/operators/arrow-types.d.ts +25 -0
  6. package/dist/batch/operators/arrow-types.js +180 -0
  7. package/dist/batch/operators/cumulative.js +82 -0
  8. package/dist/batch/operators/diff-rate.js +32 -0
  9. package/dist/batch/operators/flat-keys.d.ts +32 -0
  10. package/dist/batch/operators/flat-keys.js +103 -0
  11. package/dist/batch/operators/from-arrow.d.ts +96 -12
  12. package/dist/batch/operators/from-arrow.js +363 -23
  13. package/dist/batch/operators/ingest-columns.d.ts +15 -2
  14. package/dist/batch/operators/ingest-columns.js +211 -23
  15. package/dist/batch/operators/numeric-io.d.ts +114 -0
  16. package/dist/batch/operators/numeric-io.js +126 -0
  17. package/dist/batch/operators/to-arrow.d.ts +131 -0
  18. package/dist/batch/operators/to-arrow.js +169 -0
  19. package/dist/batch/operators/to-columns.d.ts +50 -0
  20. package/dist/batch/operators/to-columns.js +72 -0
  21. package/dist/batch/operators/value-rows.d.ts +58 -0
  22. package/dist/batch/operators/value-rows.js +164 -0
  23. package/dist/batch/time-series.d.ts +162 -17
  24. package/dist/batch/time-series.js +378 -51
  25. package/dist/batch/validate.d.ts +18 -0
  26. package/dist/batch/validate.js +18 -1
  27. package/dist/batch/value-series.d.ts +189 -11
  28. package/dist/batch/value-series.js +231 -16
  29. package/dist/index.d.ts +4 -2
  30. package/dist/reducers/avg.js +91 -4
  31. package/dist/reducers/blocked.d.ts +99 -0
  32. package/dist/reducers/blocked.js +178 -0
  33. package/dist/reducers/count.js +41 -0
  34. package/dist/reducers/max.js +57 -46
  35. package/dist/reducers/median.js +4 -1
  36. package/dist/reducers/min.js +57 -46
  37. package/dist/reducers/percentile.d.ts +25 -7
  38. package/dist/reducers/percentile.js +157 -27
  39. package/dist/reducers/stdev.js +68 -61
  40. package/dist/reducers/sum.js +68 -41
  41. package/dist/reducers/types.d.ts +31 -0
  42. package/dist/schema/index.d.ts +2 -1
  43. package/dist/schema/json.d.ts +65 -1
  44. package/dist/schema/value-io.d.ts +118 -0
  45. package/dist/schema/value-io.js +2 -0
  46. package/package.json +1 -1
package/README.md CHANGED
@@ -152,27 +152,31 @@ const points = series.sample({ reservoir: { size: 500 } }).toRows();
152
152
 
153
153
  ## Performance
154
154
 
155
- pond-ts is faster on **every** comparable operation, with no regressions —
156
- a **~17x** geometric-mean speedup across the measurable ops, plus a handful
157
- of transforms (`select` / `rename`) that are **effectively instant** (O(1)
158
- column rebinds, below the timer's resolution). The advantage grows with data
159
- size.
160
-
161
- | Category | Speedup (N=16k) | Notes |
162
- | ----------------- | ---------------------------------------------------- | --------------------------------------------- |
163
- | **Rate** | ~120x | Single columnar walk vs Pipeline |
164
- | **Fill** | 77–87x | Single columnar pass vs Pipeline per strategy |
165
- | **Aggregation** | 57–82x | O(N+B) bucketing vs O(N×B) Pipeline |
166
- | **Statistics** | 18–80x | Typed-array reduce vs ImmutableJS iteration |
167
- | **Alignment** | 42x | Forward cursor vs repeated binary search |
168
- | **Construction** | 13x | Columnar intake vs ImmutableJS wrapping |
169
- | **Chained** | 8x | Derived constructors vs per-step Pipeline |
170
- | **Transforms** | `select`/`rename` instant; `collapse` 30x; `map` ~4x | Column reshapes vs Pipeline |
171
- | **Event access** | 6x | Array indexing vs ImmutableJS `get()` |
172
- | **Serialization** | 4x | Lightweight columnar representation |
173
-
174
- See the [full benchmark results](website/docs/reference/benchmarks.mdx)
175
- for detailed numbers. Run locally:
155
+ Measured against three reference points (snapshot 2026-07-30; full tables,
156
+ methodology, and every losing number in the
157
+ [benchmark reference](website/docs/reference/benchmarks.mdx)):
158
+
159
+ - **vs pondjs** (the predecessor): faster on all 54 measurable shared
160
+ operations — geometric mean **20.7×**, `aggregate` up to 453×,
161
+ `median` 157×, and `select` / `rename` effectively instant (O(1)
162
+ column rebinds).
163
+ - **vs pandas** (500k-bar workload): roughly **even** — ahead on `ema`,
164
+ `mean`, `median` / `percentile`; behind ~1.1–1.9× on the rolling
165
+ studies. A five-study strategy pass runs 1.28× slower than pandas'
166
+ Cython kernels.
167
+ - **vs polars, single-threaded**: **ahead on composite studies**
168
+ (`bollinger` 0.53×, strategy stack 0.85×, `ema` 0.32× — lower is
169
+ pond-ts faster), behind 4–9× on whole-column reductions (the SIMD gap;
170
+ half-closed already by blocked summation) and on raw ingest, where
171
+ pond-ts front-loads validation the dataframe engines defer.
172
+ - **vs polars on all 10 cores**: behind ~3.5× on the strategy stack —
173
+ pond-ts has no parallelism today; a measured 2.42× worker-thread path
174
+ is on the roadmap (`[PND-PROCPAR]`).
175
+
176
+ The honest one-line version: the rewrite beat its predecessor by an order
177
+ of magnitude, holds its own per-core against the native engines on the
178
+ composite queries that dominate real workloads, and knows exactly where
179
+ it is behind. Run locally:
176
180
 
177
181
  ```sh
178
182
  npm run build && node packages/core/bench/vs-pondjs.cjs
@@ -1,5 +1,6 @@
1
- import type { Column } from '../columnar/index.js';
2
- import { Float64Column } from '../columnar/index.js';
1
+ import type { Column, ColumnSchema } from '../columnar/index.js';
2
+ import { ColumnarStore, Float64Column } from '../columnar/index.js';
3
+ import type { Interval } from '../core/interval.js';
3
4
  import type { AggregateMap, AggregateOutputMap, AggregateReducer, RollingAlignment, ScalarKind, SeriesSchema } from '../schema/index.js';
4
5
  /**
5
6
  * Normalised column spec used by both batch and live aggregation paths.
@@ -35,10 +36,48 @@ export type AggregateColumnSpec = {
35
36
  * same `mapping` shape produces the same schema.
36
37
  */
37
38
  export declare function normalizeAggregateColumns<S extends SeriesSchema>(schema: S, mapping: AggregateMap<S> | AggregateOutputMap<S>): AggregateColumnSpec[];
38
- export declare function tryAggregateColumnarTimeKeyed<B extends {
39
- begin(): number;
40
- end(): number;
41
- }>(begins: Float64Array, getColumn: (name: string) => Column | undefined, buckets: ReadonlyArray<B>, columns: ReadonlyArray<AggregateColumnSpec>): Array<ReadonlyArray<unknown>> | null;
39
+ /**
40
+ * **Columnar output for time-keyed `aggregate()` — [PND-IVLCOL].**
41
+ *
42
+ * Same gate and same bucket walk as the reduction fast path above, but
43
+ * the result is assembled as a `ColumnarStore` instead of a row array.
44
+ *
45
+ * **Why this exists.** `aggregate`'s output is interval-keyed, and the
46
+ * only construction door for an interval-keyed series used to be row
47
+ * intake — so the columnar fast path computed each bucket's answer in
48
+ * typed arrays, boxed it into a frozen `[Interval, …]` row, and then
49
+ * `new TimeSeries({ rows })` walked those rows straight back into
50
+ * columns. Measured on a 1M-row series at 16,667 buckets × 4 columns,
51
+ * that round trip cost **2.52 ms of a 5.01 ms call** — more than the
52
+ * reduction it was packaging. Writing the columns directly costs
53
+ * **0.09 ms** (spike: `spikes/columnar-wasm/bench/interval-columnar.mjs`).
54
+ *
55
+ * **Behaviour is identical, deliberately.** Trusted construction skips
56
+ * row intake, so every check row intake performed is performed here:
57
+ *
58
+ * - Cell kinds go through the *same* `assertCellKind` (shared, not
59
+ * copied), so a reducer that overflows to `Infinity` still raises the
60
+ * same `ValidationError` with the same message rather than silently
61
+ * landing a non-finite cell in a column flagged `allFinite`.
62
+ * - Numeric columns are stamped `allFinite: true` on the same
63
+ * justification the row path uses — every surviving cell was
64
+ * finite-checked above. Dropping that flag would be safe but would
65
+ * quietly deoptimise every downstream reduction of an aggregate
66
+ * result.
67
+ * - Missing cells (an empty bucket, or an all-missing one) set no
68
+ * validity bit, so they read back as `undefined` — **not** `NaN`. That
69
+ * is the sentinel decision this change forces, resolved in favour of
70
+ * preserving current behaviour exactly.
71
+ *
72
+ * The one row-intake check deliberately skipped is the non-decreasing
73
+ * key scan: `BoundedSequence` already validates its intervals as sorted,
74
+ * non-overlapping, and positive-duration at construction, so re-deriving
75
+ * it per bucket would be checking the same fact twice.
76
+ *
77
+ * Returns `null` for the same disqualifying mappings as before; the
78
+ * caller falls back to the unchanged row path.
79
+ */
80
+ export declare function tryAggregateColumnarStore(begins: Float64Array, getColumn: (name: string) => Column | undefined, buckets: ReadonlyArray<Interval>, columns: ReadonlyArray<AggregateColumnSpec>, resultSchema: ColumnSchema): ColumnarStore | null;
42
81
  /**
43
82
  * **Columnar fast path for count-window `rolling()`** — the N-bar window
44
83
  * financial studies compose on (SMA / Bollinger / rolling stats). When every
@@ -57,7 +96,7 @@ export declare function tryAggregateColumnarTimeKeyed<B extends {
57
96
  * qualify: a custom-function reducer, a non-`'number'` output kind
58
97
  * (`unique` / `samples` / `keep` over a non-number source / an explicit
59
98
  * `kind` override), or a non-numeric / chunked / missing source column.
60
- * All-or-nothing per call, matching {@link tryAggregateColumnarTimeKeyed}.
99
+ * All-or-nothing per call, matching {@link tryAggregateColumnarStore}.
61
100
  *
62
101
  * Window shape replicates the generic count sweep exactly: rows are the unit
63
102
  * (`count` is a bar count — no equal-key grouping), `lo` / `hi` are monotonic