Skip to content

Feat/tsfresh parity extractors - #21

Merged
wilswer merged 7 commits into
mainfrom
feat/tsfresh-parity-extractors
Oct 13, 2026
Merged

wilswer merged 7 commits into
mainfrom
feat/tsfresh-parity-extractors

Conversation

@wilswer

@wilswer wilswer commented Oct 13, 2026

Copy link
Copy Markdown
Owner

No description provided.

wilswer and others added 7 commits October 13, 2026 09:48
tsfresh_compare.py replaces minimal_bench.py, efficient_bench.py,
1brc/benchmark.py and the stale efficient.json. tsfresh now runs exactly
the calculators, parameters and attributes tsfx runs for the tier
(derived from .tsfx-config.toml, checked by feature count), both get the
same pre-sorted in-memory data with CSV parsing outside the timer,
tsfresh's n_jobs matches the Polars thread pool, and both get a warm-up
and repeated runs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The noise estimate selected the 10th percentile by sorting a len/20
window per ridge line, and every local maximum was compared with every
ridge line. Select the two order statistics instead, match by binary
search over the lines' sorted end columns (keeping np.argmin's
first-line tie-break), and end lines with one partition instead of
repeated Vec::remove. Results are bit-identical (snapshot and 11,857
number_cwt_peaks values on 1BRC and edge-case series).

On 50M 1BRC rows: number_cwt_peaks 10.9 s -> 2.6 s, Efficient tier
21.6 s -> 13.7 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
statsmodels 0.15 fits an AR(k) model with exactly as many observations
as parameters (0.14 raised ZeroDivisionError, which tsfresh turned into
NaN, or 0 for coeff = k). CI's fuzz job did not pin statsmodels, so it
picked up 0.15 and failed on ar_coefficient([1.0], k=0): tsfresh 1.0,
tsfx 0.0. Fit from 2k + 1 values on, and pin statsmodels==0.15.0 in the
fuzz job.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Bit-identical (snapshot plus ~12,900 series of 1BRC and edge-case data per
feature). Efficient tier on 100M 1BRC rows: 35.3 s -> 25.5 s.

- augmented_dickey_fuller: the exact-fit check computed the fit's scale
  (O(nobs * p) per candidate lag) for every fit. Compare against the cheap
  bound max|y| + sum_j |b_j| max|X_j| first; the exact scale is only
  needed when the residual is within twice the tolerance of that bound,
  so the decision is unchanged. The Jacobi SVD also sums its three dot
  products in one pass (same order). 16.6 s -> ~10 s.
- permutation_entropy: count patterns as base-`dimension` integers (same
  order as the rank sequences) instead of allocating a Vec per window;
  dimensions too large for u64 keep the Vec path. 3.7 s -> 2.1 s.
- change_quantiles: compute each (ql, qh, isabs) corridor's changes once
  and aggregate a copy (the median reorders it). 3.0 s -> 2.5 s.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With pyo3's abi3 feature, --find-interpreter builds only the abi3 wheel,
so 0.5.0 shipped no wheels for free-threaded CPython or PyPy (0.4.0 had
cp314t and pp311 on every Linux target) and those installs fell back to
the sdist. Name the interpreters instead: python3.11 (abi3), python3.14t
and pypy3.11. maturin has their configuration built in, so they need not
be installed in the build images (checked locally for both).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- _ar_designs left out the exactly determined designs (n = 2k + 1) that
  statsmodels 0.15 now fits, so their conditioning went unchecked: the
  thorough profile found [6519, 1, 0, 0, 1] (k = 2, condition number
  6e7), where statsmodels is off by 2e-9 of the largest coefficient
  (tsfx gives the exact 6518 to within 3e-12).
- NaN or +-inf inside an ADF regression design can make LAPACK's SVD
  loop forever in statsmodels' adfuller (autolag=None), which hung the
  thorough fuzz run. Only a non-finite last value, which enters no
  design, is still compared; the parity tests check tsfx's NaN for the
  others.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Householder QR in Ols::fit_prefixes sweeps the whole design once
per column; for ADF on long series (42 columns x 11k rows, 3.7 MB) that
was memory-bandwidth bound and most of ADF's time. Build X^T X and X^T y
in one pass over cache-sized row blocks, with column scaling, and use
the Cholesky factor, whose leading blocks serve every lag prefix as the
QR's did.

Not bit-identical: the QR path is still used, unchanged, when the
scaled condition number bound reaches 1e3, when the residual (a
difference, y'y - |c|^2) has an estimated relative error above 1e-12,
or when a fit could be exact. On 1BRC and edge-case series, ADF results
agree with the QR path's within 1e-12 relative with the same chosen
lags, AR coefficients within 2e-11 of the largest coefficient.

ADF alone on 100M 1BRC rows: 12.5 s -> 4.3 s. tsfresh comparison on
1BRC: 39x at 10M rows (1.3 s vs 52.3 s), 27x at 50M (7.1 s vs 192.7 s).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wilswer
wilswer merged commit dfdeb27 into main Oct 13, 2026
0 of 2 checks passed
@wilswer
wilswer deleted the feat/tsfresh-parity-extractors branch October 13, 2026 11:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant