Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ jobs:
HYPOTHESIS_PROFILE: ci
run: |
# bit-exact parity is checked against these versions
uv pip install --system hypothesis tsfresh==0.21.2 numpy==2.4.4 pandas==3.0.2 scipy==1.17.1
uv pip install --system hypothesis tsfresh==0.21.2 numpy==2.4.4 pandas==3.0.2 scipy==1.17.1 statsmodels==0.15.0
pytest -m fuzz

linux:
Expand All @@ -75,7 +75,12 @@ jobs:
CFLAGS_aarch64_unknown_linux_gnu: "-D__ARM_ARCH=8"
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --find-interpreter
# One abi3 wheel for CPython >= 3.11, plus version-specific wheels
# for the interpreters abi3 does not cover (free-threaded CPython,
# PyPy). --find-interpreter builds only the abi3 wheel. maturin
# knows these interpreters' configuration, so they need not be
# installed in the build image
args: --release --out dist -i python3.11 -i python3.14t -i pypy3.11
sccache: false
manylinux: auto
- name: Upload wheels
Expand Down Expand Up @@ -108,7 +113,8 @@ jobs:
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.platform.target }}
args: --release --out dist --find-interpreter
# As for manylinux: abi3 plus free-threaded and PyPy wheels
args: --release --out dist -i python3.11 -i python3.14t -i pypy3.11
sccache: false
manylinux: musllinux_1_2
- name: Upload wheels
Expand Down
55 changes: 55 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,15 @@ All notable changes to TSFX are documented here.

### Changed

- **Fair TSFresh benchmark.** `python_benchmarks/tsfresh_compare.py`
replaces `minimal_bench.py`, `efficient_bench.py`, `1brc/benchmark.py` and
`efficient.json`. TSFresh now runs exactly the calculators and parameters
TSFX runs for the tier (derived from `.tsfx-config.toml` and checked by
feature count), both libraries 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. Previously TSFX's
time included reading the CSV, the stocks series were not sorted by date
for TSFX, and the feature sets differed.
- **Many more output columns by default.** The `Efficient` tier now gives
about 750 columns per value column (was about 155), 400 of them from
`fft_coefficient`. Existing columns keep their names, but the new ones
Expand Down Expand Up @@ -63,6 +72,52 @@ All notable changes to TSFX are documented here.
scaled to the series or fit) rather than bit for bit, since tsfx does not
reproduce numpy's FFT, LAPACK and SIMD `log` arithmetic.

### Fixed

- **Linux wheels for free-threaded CPython 3.14 and PyPy 3.11 again.** 0.5.0
shipped only the abi3 wheels, which those interpreters cannot use, so
installing there built tsfx from source (with a Rust toolchain).
- **`ar_coefficient` matches statsmodels 0.15.** A series of exactly
2k + 1 values (as many observations as AR(k) parameters) now gives the
exact fit, as tsfresh does with statsmodels 0.15, instead of NaN (or 0 for
`coeff = k`), which matched statsmodels 0.14. For example, a single value
with `k = 0` gives that value. The tsfresh fuzz tests in CI now pin
`statsmodels==0.15.0`.

### Performance

- **`number_cwt_peaks` is much faster on long series,** with the same
results bit for bit: on 50M rows of 1BRC data (series of about 5,600
values) it takes 2.6 s instead of 10.9 s. Its noise estimate
now selects the 10th percentile of each window instead of sorting it, and
ridge lines are matched by binary search instead of comparing every pair.
The `Efficient` tier on 50M rows of 1BRC data takes 13.0 s instead of
21.9 s.
- **`augmented_dickey_fuller`, `permutation_entropy` and
`change_quantiles` are faster,** with the same results bit for bit.
Together they make the `Efficient` tier on 100M rows of 1BRC data take
25.5 s instead of 35.3 s (−28%), the features alone measured as follows:
- `augmented_dickey_fuller` 16.6 → about 10 s: the check for an exact fit
first compares against a cheap upper bound of the fit's scale, and only
computes the exact scale (a pass over every row and regressor) when the
residual is small enough to matter.
- `permutation_entropy` 3.7 → 2.1 s: each window's pattern is counted as
one integer instead of an allocated list.
- `change_quantiles` 3.0 → 2.5 s: each corridor's changes are computed
once and shared by its aggregations.
- **`augmented_dickey_fuller` and `ar_coefficient` solve well-conditioned
regressions from the Gram matrix.** A Householder QR sweeps the whole
design once per regressor and was limited by memory bandwidth on long
series; the Gram matrix needs one pass. On 100M rows of 1BRC data,
`augmented_dickey_fuller` alone takes 4.4 s instead of 12.6 s. **Not bit
for bit:** results agree with the previous ones to within about
1e-10 (ADF test statistics and p-values within 1e-12 relative on
1BRC and edge-case series, the same chosen lags, AR coefficients within
2e-11 of the fit's largest coefficient). Ill-conditioned,
near-exact and exactly fitting regressions still use the QR decomposition,
so their results, including the deviations from tsfresh documented for
exact fits, are unchanged.

## 0.5.0 (2026-10-12)

### Changed
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ crate-type = ["lib", "cdylib"]
serde = { version = "1", features = ["derive"] }
toml = "1.1"
# abi3: one wheel per platform for every CPython >= 3.11, including future
# versions (free-threaded builds, which have no stable ABI, still get
# version-specific wheels)
# versions. Free-threaded CPython and PyPy have no stable ABI: on Linux, CI
# builds version-specific wheels for them (see the -i flags in CI.yml)
pyo3 = { version = "0.29", features = ["abi3-py311"] }
chrono = "0.4.42"
csv = "1.3"
Expand Down
22 changes: 17 additions & 5 deletions python_benchmarks/1brc/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# 1 Billion Row Benchmark

To do perform the benchmark you must first generate data (if you are in the root
To perform the benchmark you must first generate data (if you are in the root
directory):

```bash
Expand All @@ -9,14 +9,26 @@ python python_benchmarks/1brc/generate_data.py N

where `N` is a number (1_000_000_000 for the proper 1 billion row challenge).

Then run benchmark like so:
Then compare TSFX against TSFresh like so:

```bash
python python_benchmarks/1brc/benchmark.py
python python_benchmarks/tsfresh_compare.py --dataset 1brc --rows 10_000_000
```

If `tsfx` runs out of memory you can specify how many threads polars should use,
by setting the environment variable:
`--rows` limits how many measurements are read (default 10 million, `0` for
all). For scale, on an 80-core machine with the `efficient` tier (8,859
stations, best of two timed runs):

| rows | TSFX | TSFresh |
| ---- | ------ | ------- |
| 10M | 1.3 s | 52.3 s |
| 50M | 7.1 s | 192.7 s |

The full billion rows has not been measured. See
the docstring of `tsfresh_compare.py` for how the comparison is kept fair.

Both libraries use as many threads (TSFresh: processes) as Polars does. To
limit them, for example if you run out of memory, set:

```bash
export POLARS_MAX_THREADS=16
Expand Down
72 changes: 0 additions & 72 deletions python_benchmarks/1brc/benchmark.py

This file was deleted.

173 changes: 0 additions & 173 deletions python_benchmarks/efficient.json

This file was deleted.

Loading
Loading