Skip to main content

rustures

Fast, memory-aware offline change-point detection for Python — powered by Rust.

Python PyPI Rust PyO3 License Status

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] rustures is 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-K dynamic 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_constant
  • pw_linear
  • pw_normal
  • pw_wavy

Evaluation metrics:

  • hausdorff
  • precision_recall
  • rand_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-K and penalized problems;
  • black-box parity fixtures generated from pinned ruptures behaviour;
  • 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

rustures-0.1.1.tar.gz (167.4 kB view details)

Uploaded Source

Built Distributions

If you're not sure about the file name format, learn more about wheel file names.

rustures-0.1.1-cp310-abi3-win_amd64.whl (505.2 kB view details)

Uploaded CPython 3.10+Windows x86-64

rustures-0.1.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (575.2 kB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ x86-64

rustures-0.1.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl (512.1 kB view details)

Uploaded CPython 3.10+manylinux: glibc 2.17+ ARM64

rustures-0.1.1-cp310-abi3-macosx_11_0_arm64.whl (495.6 kB view details)

Uploaded CPython 3.10+macOS 11.0+ ARM64

rustures-0.1.1-cp310-abi3-macosx_10_12_x86_64.whl (541.4 kB view details)

Uploaded CPython 3.10+macOS 10.12+ x86-64

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

Hashes for rustures-0.1.1.tar.gz
Algorithm Hash digest
SHA256 cc7b35b5753d785034a9ed6d221427ac4a2f73de77b457c49f7d10387b8053aa
MD5 6a683bb69d4f3396f563742d7c2efbe7
BLAKE2b-256 165d2dafe037bef17948598d45815db36a4fc49aeae447ccd023a346404b24a0

See more details on using hashes here.

Provenance

The following attestation bundles were made for rustures-0.1.1.tar.gz:

Publisher: release.yml on denrew88/rustures

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

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

Hashes for rustures-0.1.1-cp310-abi3-win_amd64.whl
Algorithm Hash digest
SHA256 e373132d17a4fb1a27f8a85838ebd77b5e4ae059c9351df37759d453cbb14df6
MD5 19a3a0373ca82f6b32127541c3aeaa3d
BLAKE2b-256 789c58b42d51a7493293310998a8399934980ac75f7c31c6d8b151e0c86b25f0

See more details on using hashes here.

Provenance

The following attestation bundles were made for rustures-0.1.1-cp310-abi3-win_amd64.whl:

Publisher: release.yml on denrew88/rustures

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rustures-0.1.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.

File metadata

File hashes

Hashes for rustures-0.1.1-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Algorithm Hash digest
SHA256 540b2f811453663e48e3cc0e8f5410bcc01702fc45d5bdae129677814acf5bea
MD5 c8201008636228301be65142d252b3d5
BLAKE2b-256 65f7f14855ef08ebd33542c2bca272721c8f041653332256dc847f2bd5d1352d

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rustures-0.1.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl.

File metadata

File hashes

Hashes for rustures-0.1.1-cp310-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Algorithm Hash digest
SHA256 86bfc943d668c43543920d5e70898f8b02c0026c15792722b798674d0452db1d
MD5 cb5f54fb28c2021568150ea7e6ff43f1
BLAKE2b-256 f629870b0c5912910597ff9e0be8e58856f4744d4687494e5c81c3d386150778

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rustures-0.1.1-cp310-abi3-macosx_11_0_arm64.whl.

File metadata

File hashes

Hashes for rustures-0.1.1-cp310-abi3-macosx_11_0_arm64.whl
Algorithm Hash digest
SHA256 4f9f6352371bb5c977671e47902e588e3104396111b05a487e8513c53cf927e5
MD5 c678e504dbd77d4c541687c9878e2a63
BLAKE2b-256 d83bd649ab3f6e92c7f1bd1161d9274c6c7e3dbf7aecf3e1ceb3e356c60694b9

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file rustures-0.1.1-cp310-abi3-macosx_10_12_x86_64.whl.

File metadata

File hashes

Hashes for rustures-0.1.1-cp310-abi3-macosx_10_12_x86_64.whl
Algorithm Hash digest
SHA256 b2c041cad308a533cbb9cd17c05ec6cc50ee3a68591ab9b0273e8d362f0afc5a
MD5 f4fb7a9547e0e0dc889c946445f48c0d
BLAKE2b-256 1686e5ecf253af6a1a3347471e078c2ccac4da4af3b3a4e0c76367a45f9dd830

See more details on using hashes here.

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

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Release history Release notifications | RSS feed

0.2.0

6 files

This release

0.1.1 This release

6 files

0.1.0

6 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page