rustures
Fast, memory-aware offline change-point detection for Python — powered by Rust.
Exact dynamic programming, kernel methods, robust costs, custom Python costs,
and familiar fit / predict APIs in one native extension.
Quick start · Algorithms · Custom costs · Build · Tutorial
rustures finds points where the statistical behaviour of a sequence changes:
its mean, distribution, trend, autoregressive dynamics, or kernel representation.
The search algorithms and built-in cost functions run in Rust while the public API
stays in Python.
The project is inspired by the excellent
ruptures ecosystem, but it is an
independent implementation rather than a complete drop-in replacement.
[!IMPORTANT]
rusturesis currently pre-alpha. The API is usable and heavily tested, but public APIs and compatibility guarantees may still change between releases.
Why rustures?
- A native core without a Python loop in the hot path. Built-in costs and detectors execute in optimized Rust.
- Exact and approximate search strategies. Use fixed-
Kdynamic programming, penalized optimal partitioning, kernel CPD, or faster greedy detectors. - Memory is part of the API. Dynp and full-Gram kernel backends reject oversized jobs before allocating their main tables.
- Kernel CPD without mandatory quadratic storage. The default fused backend is exact and does not materialize a full Gram matrix.
- Custom Python costs when the built-ins are not enough. Dynp and Pelt accept scalar callbacks and optional endpoint-batched callbacks.
- Multivariate input is first-class. Most costs accept an
(n_samples, n_features)NumPy array; scalar signals may remain one-dimensional. - Python-safe failure boundaries. Invalid data, allocation limits, numerical failures, and unwinding Rust panics become catchable Python exceptions.
- Reproducible validation. Deterministic generators, metrics, exhaustive small oracles, parity fixtures, and raw benchmark artifacts live in the repository.
Quick start
import rustures as rpt
# Deterministic piecewise-constant data and its true breakpoints.
signal, truth = rpt.pw_constant(
n_samples=600,
n_features=2,
n_bkps=3,
noise_std=0.7,
seed=42,
)
# Penalized exact segmentation.
prediction = rpt.Pelt(
model="l2",
min_size=10,
jump=1,
).fit_predict(signal, pen=12.0)
precision, recall = rpt.precision_recall(truth, prediction, margin=10)
print("truth: ", truth)
print("prediction:", prediction)
print(f"precision={precision:.3f}, recall={recall:.3f}")
Breakpoints use the half-open interval convention and always include the terminal
sample. A result such as [120, 360, 600] represents segments [0, 120),
[120, 360), and [360, 600).
Choosing an algorithm
| You know… | Start with | What it does |
|---|---|---|
The number of changes K |
Dynp |
Exact fixed-K dynamic programming |
| A penalty per additional change | Pelt |
Exact penalized optimal partitioning; uses pruning only when the cost proves it is valid |
| The change may be nonlinear or distributional | KernelCPD |
Exact linear, RBF, or cosine kernel segmentation |
| You need a fast exploratory result | Binseg |
Recursive binary segmentation |
| You prefer merge-based segmentation | BottomUp |
Starts small and merges neighbouring segments |
| Changes should be found from a local score | Window |
Window discrepancy with deterministic peak selection |
| A scalar signal is piecewise constant with robust L1 loss | L1Potts |
Weighted scalar L1-Potts optimization |
Fixed number of changes
algo = rpt.Dynp(model="normal", min_size=8, jump=2).fit(signal)
print("workspace bytes:", algo.estimated_memory_bytes(n_bkps=3))
breakpoints = algo.predict(n_bkps=3)
Dynp defaults to a 512 MiB prediction-workspace limit. Override it explicitly when you know the process budget:
algo = rpt.Dynp(
model="l2",
jump=1,
max_memory_bytes=256 * 1024 * 1024,
).fit(signal)
# Raises MemoryError before allocating DP states if the limit would be exceeded.
breakpoints = algo.predict(n_bkps=32)
Kernel change-point detection
kernel_algo = rpt.KernelCPD(
kernel="rbf",
gamma_policy="sampled",
gamma_samples=10_000,
seed=42,
backend="fused",
min_size=5,
jump=1,
)
breakpoints = kernel_algo.fit_predict(signal, n_bkps=3)
Available kernels are "linear", "rbf", and "cosine".
| Backend | Exact? | Main storage behaviour |
|---|---|---|
fused |
Yes | Default fixed-K implementation; no full Gram matrix |
streaming |
Yes | Computes kernel contributions without retaining a full Gram table |
full |
Yes | Stores a full Gram prefix for repeated constant-time segment-cost queries |
The full backend has its own 512 MiB default limit through max_gram_bytes.
Cost models
The following model strings work with the general-purpose detectors:
| Model | Detects changes in… | Notes |
|---|---|---|
l2 |
Mean | Fast prefix sums; scalar or multivariate |
l1 |
Median / robust location | Component-wise median absolute deviation |
rank |
Distribution | Global ranks with tie handling |
normal |
Gaussian mean and covariance | Regularized covariance log-determinant |
linear |
Regression relationship | First column is the response; remaining columns are predictors |
ar |
Autoregressive dynamics | Default order is 4 |
clinear |
Continuous piecewise-linear trend | Endpoint interpolation cost |
mahalanobis |
Metric-weighted scatter | Exposed as CostMahalanobis(metric=...) / CostMl |
Standalone cost objects expose fit, error(start, end), and sum_of_costs:
cost = rpt.CostL2().fit(signal)
segment_cost = cost.error(100, 220)
partition_cost = cost.sum_of_costs([100, 220, len(signal)])
Custom Python costs
Dynp and Pelt accept any object with this protocol:
class CustomCost:
min_size: int
def fit(self, signal): ...
def error(self, start: int, end: int) -> float: ...
# Optional: calculate several segments ending at the same endpoint.
def error_many(self, starts, ends): ...
For example, a Bernoulli negative log-likelihood cost can be written as:
import numpy as np
import rustures as rpt
class BernoulliCost:
min_size = 1
def fit(self, signal):
values = np.asarray(signal, dtype=np.float64).reshape(-1)
if not np.all((values == 0.0) | (values == 1.0)):
raise ValueError("BernoulliCost expects only 0 and 1")
self.values = values
self.prefix = np.r_[0.0, np.cumsum(values)]
return self
def error(self, start, end):
length = end - start
ones = self.prefix[end] - self.prefix[start]
p = ones / length
if p == 0.0 or p == 1.0:
return 0.0
return -(ones * np.log(p) + (length - ones) * np.log1p(-p))
binary_signal = np.r_[np.zeros(80), np.ones(60), np.zeros(90)]
breakpoints = rpt.Dynp(
custom_cost=BernoulliCost(),
min_size=10,
jump=1,
).fit_predict(binary_signal, n_bkps=2)
error_many avoids one Python call per segment candidate. The adapter retains only
one endpoint batch, so it does not silently build an O(n²) Python cost table.
Exceptions raised by a custom cost preserve their Python type, message, and
traceback. Custom Pelt uses the exact unpruned path because arbitrary user costs do
not automatically satisfy the PELT pruning inequality.
Included utilities
Deterministic signal generators:
pw_constantpw_linearpw_normalpw_wavy
Evaluation metrics:
hausdorffprecision_recallrand_index
All generators require an explicit seed and return (signal, breakpoints).
Performance snapshot
The latest local comparison used Windows 11 x86-64, Python 3.11, a release abi3
wheel, N=400, min_size=5, jump=5, and the median of five alternating runs.
Times include a fresh Dynp.fit_predict call.
| Model | K | rustures | pinned ruptures | Relative speed |
|---|---|---|---|---|
| Linear | 1 | 1.868 ms | 4.526 ms | 2.42× |
| Linear | 4 | 1.936 ms | 115.019 ms | 59.42× |
| Linear | 8 | 2.035 ms | 136.428 ms | 67.04× |
| AR | 1 | 3.738 ms | 6.039 ms | 1.62× |
| AR | 4 | 4.428 ms | 155.526 ms | 35.12× |
| AR | 8 | 4.274 ms | 194.458 ms | 45.50× |
These are machine- and workload-specific results, not universal guarantees. Raw
data is available in
artifacts/validation/phase9-final-regression-benchmark.json,
and the benchmark driver is
benchmarks/benchmark_phase7_costs.py.
Correctness and safety
The repository currently checks correctness through several independent layers:
- exhaustive enumeration for small fixed-
Kand penalized problems; - black-box parity fixtures generated from pinned
rupturesbehaviour; - full-Gram, streaming, fused, scalar, and AVX2 backend parity tests;
- deterministic tie-breaking tests;
- finite-input, overflow, singular, collinear, constant, and large-offset cases;
- 2,528 Linear/AR fast-path comparisons against scalar SVD Dynp and Pelt;
- Python exception and Rust panic-boundary process-survival tests.
The AVX2 path uses runtime CPU detection. CPUs without AVX2 automatically use the scalar implementation instead of failing at import time.
Installation
Current compatibility
- Python 3.10 or newer is declared through
abi3-py310. - NumPy 1.23 or newer is required.
- Binary wheels are published for Linux x86-64 and ARM64, Windows x86-64, and macOS Intel and Apple Silicon.
- The current wheels target GIL-enabled CPython. They do not target 32-bit Python, PyPy, free-threaded CPython, or native Windows ARM64.
Install a published wheel from PyPI:
python -m pip install rustures
Build from source
Prerequisites: Python 3.10+, Rust 1.83+, and a working native compiler toolchain.
git clone https://github.com/denrew88/rustures.git
cd rustures
python -m venv .venv
Activate the environment:
Windows PowerShell: .venv\Scripts\Activate.ps1
Linux/macOS: source .venv/bin/activate
Build and install an editable release extension:
python -m pip install --upgrade pip
python -m pip install "maturin>=1.14,<2.0" "numpy>=1.23"
python -m maturin develop --release
Or create a wheel:
python -m maturin build --release
The wheel is written to target/wheels/.
Development
# Rust unit, oracle, and parity tests
cargo test
# Formatting and linting
cargo fmt -- --check
cargo clippy --all-targets --all-features -- -D warnings
# Python wheel tests after installing a built wheel
python -m pip install pytest
python -m pytest tests/python/test_wheel.py -q
# Longer Linear/AR regression matrix
cargo test --release --test regression_mass -- --ignored --nocapture
Tutorial notebooks are available in English and Korean.
Project status
Implemented today:
- Dynp, Pelt, Binseg, BottomUp, Window, KernelCPD, and L1Potts
- eight general-purpose cost models
- linear, RBF, and cosine kernels with three exact backends
- custom Python costs for Dynp and Pelt
- multivariate signal handling, metrics, and deterministic datasets
- typed Python errors, panic isolation, memory preflight, and type hints
Major work still planned:
- approximate low-rank kernel backends;
- broader profiling across CPU architectures and feature dimensions;
- additional interpreter and architecture coverage as the API matures.
License
Licensed under either of
at your option.
The Rust dependencies compiled into the wheel, their selected license options,
copyright notices, and full license texts are recorded in
THIRD-PARTY-LICENSES. The report is generated from
Cargo.lock for the verified Windows x86-64 target with:
cargo install --locked --features cli cargo-about
cargo about generate --locked --fail -c about.toml -o THIRD-PARTY-LICENSES about.hbs
Built for people who want Python ergonomics, Rust execution, and explicit correctness contracts in offline change-point detection.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distributions
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file rustures-0.1.1.tar.gz.
File metadata
- Download URL: rustures-0.1.1.tar.gz
- Upload date:
- Size: 167.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
cc7b35b5753d785034a9ed6d221427ac4a2f73de77b457c49f7d10387b8053aa
|
|
| MD5 |
6a683bb69d4f3396f563742d7c2efbe7
|
|
| BLAKE2b-256 |
165d2dafe037bef17948598d45815db36a4fc49aeae447ccd023a346404b24a0
|
Provenance
The following attestation bundles were made for rustures-0.1.1.tar.gz:
Publisher:
release.yml on denrew88/rustures
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rustures-0.1.1.tar.gz -
Subject digest:
cc7b35b5753d785034a9ed6d221427ac4a2f73de77b457c49f7d10387b8053aa - Sigstore transparency entry: 2685998005
- Sigstore integration time:
-
Permalink:
denrew88/rustures@17e992786eb1c85a8ee8de5180df71ee988cc91d -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/denrew88
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@17e992786eb1c85a8ee8de5180df71ee988cc91d -
Trigger Event:
push
-
Statement type:
File details
Details for the file rustures-0.1.1-cp310-abi3-win_amd64.whl.
File metadata
- Download URL: rustures-0.1.1-cp310-abi3-win_amd64.whl
- Upload date:
- Size: 505.2 kB
- Tags: CPython 3.10+, Windows x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e373132d17a4fb1a27f8a85838ebd77b5e4ae059c9351df37759d453cbb14df6
|
|
| MD5 |
19a3a0373ca82f6b32127541c3aeaa3d
|
|
| BLAKE2b-256 |
789c58b42d51a7493293310998a8399934980ac75f7c31c6d8b151e0c86b25f0
|
Provenance
The following attestation bundles were made for rustures-0.1.1-cp310-abi3-win_amd64.whl:
Publisher:
release.yml on denrew88/rustures
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rustures-0.1.1-cp310-abi3-win_amd64.whl -
Subject digest:
e373132d17a4fb1a27f8a85838ebd77b5e4ae059c9351df37759d453cbb14df6 - Sigstore transparency entry: 2685998663
- Sigstore integration time:
-
Permalink:
denrew88/rustures@17e992786eb1c85a8ee8de5180df71ee988cc91d -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/denrew88
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@17e992786eb1c85a8ee8de5180df71ee988cc91d -
Trigger Event:
push
-
Statement type:
File details
Details for the file rustures-0.1.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.
File metadata
- Download URL: rustures-0.1.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
- Upload date:
- Size: 575.2 kB
- Tags: CPython 3.10+, manylinux: glibc 2.17+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
540b2f811453663e48e3cc0e8f5410bcc01702fc45d5bdae129677814acf5bea
|
|
| MD5 |
c8201008636228301be65142d252b3d5
|
|
| BLAKE2b-256 |
65f7f14855ef08ebd33542c2bca272721c8f041653332256dc847f2bd5d1352d
|
Provenance
The following attestation bundles were made for rustures-0.1.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl:
Publisher:
release.yml on denrew88/rustures
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rustures-0.1.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl -
Subject digest:
540b2f811453663e48e3cc0e8f5410bcc01702fc45d5bdae129677814acf5bea - Sigstore transparency entry: 2686000346
- Sigstore integration time:
-
Permalink:
denrew88/rustures@17e992786eb1c85a8ee8de5180df71ee988cc91d -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/denrew88
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@17e992786eb1c85a8ee8de5180df71ee988cc91d -
Trigger Event:
push
-
Statement type:
File details
Details for the file rustures-0.1.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.
File metadata
- Download URL: rustures-0.1.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
- Upload date:
- Size: 512.1 kB
- Tags: CPython 3.10+, manylinux: glibc 2.17+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
86bfc943d668c43543920d5e70898f8b02c0026c15792722b798674d0452db1d
|
|
| MD5 |
cb5f54fb28c2021568150ea7e6ff43f1
|
|
| BLAKE2b-256 |
f629870b0c5912910597ff9e0be8e58856f4744d4687494e5c81c3d386150778
|
Provenance
The following attestation bundles were made for rustures-0.1.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl:
Publisher:
release.yml on denrew88/rustures
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rustures-0.1.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl -
Subject digest:
86bfc943d668c43543920d5e70898f8b02c0026c15792722b798674d0452db1d - Sigstore transparency entry: 2685999255
- Sigstore integration time:
-
Permalink:
denrew88/rustures@17e992786eb1c85a8ee8de5180df71ee988cc91d -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/denrew88
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@17e992786eb1c85a8ee8de5180df71ee988cc91d -
Trigger Event:
push
-
Statement type:
File details
Details for the file rustures-0.1.1-cp310-abi3-macosx_11_0_arm64.whl.
File metadata
- Download URL: rustures-0.1.1-cp310-abi3-macosx_11_0_arm64.whl
- Upload date:
- Size: 495.6 kB
- Tags: CPython 3.10+, macOS 11.0+ ARM64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
4f9f6352371bb5c977671e47902e588e3104396111b05a487e8513c53cf927e5
|
|
| MD5 |
c678e504dbd77d4c541687c9878e2a63
|
|
| BLAKE2b-256 |
d83bd649ab3f6e92c7f1bd1161d9274c6c7e3dbf7aecf3e1ceb3e356c60694b9
|
Provenance
The following attestation bundles were made for rustures-0.1.1-cp310-abi3-macosx_11_0_arm64.whl:
Publisher:
release.yml on denrew88/rustures
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rustures-0.1.1-cp310-abi3-macosx_11_0_arm64.whl -
Subject digest:
4f9f6352371bb5c977671e47902e588e3104396111b05a487e8513c53cf927e5 - Sigstore transparency entry: 2686001119
- Sigstore integration time:
-
Permalink:
denrew88/rustures@17e992786eb1c85a8ee8de5180df71ee988cc91d -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/denrew88
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@17e992786eb1c85a8ee8de5180df71ee988cc91d -
Trigger Event:
push
-
Statement type:
File details
Details for the file rustures-0.1.1-cp310-abi3-macosx_10_12_x86_64.whl.
File metadata
- Download URL: rustures-0.1.1-cp310-abi3-macosx_10_12_x86_64.whl
- Upload date:
- Size: 541.4 kB
- Tags: CPython 3.10+, macOS 10.12+ x86-64
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b2c041cad308a533cbb9cd17c05ec6cc50ee3a68591ab9b0273e8d362f0afc5a
|
|
| MD5 |
f4fb7a9547e0e0dc889c946445f48c0d
|
|
| BLAKE2b-256 |
1686e5ecf253af6a1a3347471e078c2ccac4da4af3b3a4e0c76367a45f9dd830
|
Provenance
The following attestation bundles were made for rustures-0.1.1-cp310-abi3-macosx_10_12_x86_64.whl:
Publisher:
release.yml on denrew88/rustures
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
rustures-0.1.1-cp310-abi3-macosx_10_12_x86_64.whl -
Subject digest:
b2c041cad308a533cbb9cd17c05ec6cc50ee3a68591ab9b0273e8d362f0afc5a - Sigstore transparency entry: 2686001276
- Sigstore integration time:
-
Permalink:
denrew88/rustures@17e992786eb1c85a8ee8de5180df71ee988cc91d -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/denrew88
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@17e992786eb1c85a8ee8de5180df71ee988cc91d -
Trigger Event:
push
-
Statement type: