Skip to content

Added L1Potts class#344

Merged
deepcharles merged 2 commits into
deepcharles:masterfrom
mstorath:feature-l1potts
May 26, 2026
Merged

Added L1Potts class#344
deepcharles merged 2 commits into
deepcharles:masterfrom
mstorath:feature-l1potts

Conversation

@mstorath

@mstorath mstorath commented Apr 4, 2025

Copy link
Copy Markdown
Contributor

Summary

Adds L1Potts, an exact O(KN) solver for the (weighted) L1 Potts model
penalized piecewise-constant estimation under L1 data fidelity, robust to
heavy-tailed noise and outliers:

min_u  γ · #{i : u_i ≠ u_{i+1}}  +  Σ_i w_i · |f_i - u_i|

This complements Pelt(model="l1") with a far faster algorithm specialized
for this functional. On a 5000-sample noisy 1D signal: ~20× faster than
Pelt(model="l1", min_size=1, jump=1)
, with identical functional value
to machine precision.

Algorithm

Implements Algorithm 1 of Storath, Weinmann & Unser (2017):

  • Theorem 2 of the paper: the optimum lies in V = unique(signal),
    because the weighted L1 median is always a data value. So the search
    space is reduced from ℝ^N to V^N.
  • Viterbi-style DP over (level, sample) pairs, with the
    Felzenszwalb-Huttenlocher trick for the Potts penalty (O(K) per
    column instead of O(K²)).
  • Time complexity O(K·N), where K is the number of distinct values in the
    signal.

Forward pass matches the paper exactly. Backtracking uses explicit parent
pointers (uint8 matrix + prev-argmin vector)
instead of the paper's
in-place float64 subtraction trick — mathematically equivalent, ~8× less
memory.

Currently 1D-only and penalty-only mode (no n_bkps / epsilon); the
algorithm is exact, so min_size/jump are fixed at 1.

What's included

  • src/ruptures/detection/l1potts.py — solver + input validation
  • tests/test_l1potts.py — 71 tests covering correctness, weights,
    hardening, edge cases:
    • functional-value parity with Pelt+CostL1 over multiple seeds
    • replication-equivalence for integer weights, weight ratios up to
      10000:1
    • positive homogeneity (scaling weights and γ by the same α) over 7
      orders of magnitude
    • reversal symmetry of the functional
    • rejection of NaN/Inf, non-numeric dtypes, empty signals,
      predict-before-fit
    • SyntaxWarning regression net (subprocess + compile())
  • docs/user-guide/detection/l1potts.md — user guide page
  • docs/code-reference/detection/l1potts-reference.md — autodoc stub
  • mkdocs.yml — nav entries
  • Top-level export rpt.L1Potts

Disclosure

Implementation prepared with assistance from Claude (Anthropic) acting as a
coding agent. The algorithm is the cited authors' (SWU2017); the agent's
contributions were the parent-pointer backtracking variant (memory
optimization), input hardening, and the test suite.

@edelweiss611428

Copy link
Copy Markdown

Hi could you submit a feature request to https://github.com/edelweiss611428/rupturesRcpp @mstorath

I may implement this later.

mstorath added a commit to mstorath/ruptures that referenced this pull request May 5, 2026
Fixes pre-commit.ci failure on PR deepcharles#344. v1.7.5's hook manifest used
language: python_venv, which was removed in pre-commit 4.0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@mstorath mstorath force-pushed the feature-l1potts branch from 4336ef6 to 62bb2f8 Compare May 5, 2026 10:25
@deepcharles

Copy link
Copy Markdown
Owner

This looks good! Thanks Martin!

Before I merge, can you add the changes on the main branch (git rebase)?

mstorath and others added 2 commits May 24, 2026 08:13
Penalized change point detection for piecewise constant signals with L1 data fidelity

    The L1 Potts model is defined as follows:

    \min_{u} \sum_{i=1}^N w_i |f_i - u_i| + \gamma \sum_{i=1}^{N-1} \mathbb{I}(u_i \neq u_{i+1})

    The algorithm implemented is described in the paper
    the paper
    Storath, Weinmann, Unser.
    Jump-penalized least absolute values estimation of scalar or circle-valued signals,
    Information and Inference, 2017

The method is significantly faster than PELT for the L1 jump penalized problem.
Rewrite the L1Potts implementation to match Algorithm 1 of
Storath, Weinmann & Unser (2017) explicitly. The forward DP is identical
to the paper; backtracking uses explicit parent pointers (uint8 matrix +
prev-argmin vector) instead of the paper's in-place float64 subtraction
trick. Mathematically equivalent, ~8x less memory.

Hardening: reject non-numeric dtypes, NaN/Inf in signal or weights,
empty signal, and predict-before-fit; clear ValueError/RuntimeError
messages instead of cryptic IndexError/AttributeError. Defensive copies
of signal and weights shield the solver from caller mutation.

Tests (tests/test_l1potts.py, 71 cases):
- functional-value parity with Pelt+CostL1 over 5 random seeds
- replication-equivalence for integer weights, including weight ratios
  up to 10000:1
- positive homogeneity over 7 orders of magnitude in alpha
- reversal symmetry of the functional
- multi-pen independence on a single fit
- edge cases: n=1, K=1, n=2, shape (N,1), pen=+inf, pen=NaN, empty
  signal, complex/object/bool dtypes, list/tuple/read-only inputs
- SyntaxWarning regression net via subprocess + compile()

Docs: user-guide page (docs/user-guide/detection/l1potts.md) with usage
example and reference, code-reference autodoc stub, mkdocs nav entries.
Export L1Potts at top level (rpt.L1Potts).

Performance: on N=5000 noisy 1D signal, ~20x faster than
Pelt(model="l1", min_size=1, jump=1) with identical functional value.

Implementation prepared with assistance from Claude (Anthropic) acting
as a coding agent. The algorithm is the cited authors'; the agent's
contributions were the parent-pointer backtracking variant, input
hardening, and the test suite.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented May 26, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.00000% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 98.70%. Comparing base (6e3bb72) to head (0b81e9b).
⚠️ Report is 1 commits behind head on master.

Files with missing lines Patch % Lines
src/ruptures/detection/l1potts.py 97.95% 2 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #344      +/-   ##
==========================================
- Coverage   98.77%   98.70%   -0.08%     
==========================================
  Files          40       41       +1     
  Lines         982     1081      +99     
==========================================
+ Hits          970     1067      +97     
- Misses         12       14       +2     
Flag Coverage Δ
unittests 98.70% <98.00%> (-0.08%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@deepcharles deepcharles self-requested a review May 26, 2026 14:38
@deepcharles deepcharles merged commit ee1c8ff into deepcharles:master May 26, 2026
18 of 20 checks passed
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.

3 participants