Skip to content

Low-risk correctness fixes: multi-peak param_bounds, crop(None), window index bound + test hardening#27

Merged
gchure merged 3 commits into
mainfrom
fix/param-bounds-and-robustness
Jun 22, 2026
Merged

Low-risk correctness fixes: multi-peak param_bounds, crop(None), window index bound + test hardening#27
gchure merged 3 commits into
mainfrom
fix/param-bounds-and-robustness

Conversation

@gchure

@gchure gchure commented Jun 22, 2026

Copy link
Copy Markdown
Member

Summary

Three small, self-contained correctness/robustness fixes that change no behavior for valid input, plus hardening of the test suite's exception checks.

Fixes

I: multi-peak param_bounds validated the wrong peak's guess

In deconvolve_peaks, the global param_bounds feasibility check indexed the per-window initial-guess list p0 with a positive index (p0[key_inds[p]]), which always pointed at the first peak's parameters. For a window containing more than one peak, this validated the wrong peak's guess against the supplied bounds — risking a spurious ValueError or a missed one. The adjacent peak-specific block already uses the correct negative-index map paridx.

Fix: use p0[paridx[p]] so each peak is checked against its own guess; remove the now-dead key_inds. Single-peak windows are unaffected (the indices coincide), so existing test_generic_param_bounding / test_bounding are unchanged.

II: crop() with no/None window gave a cryptic error

crop() / crop(None) fell through to len(time_window) and raised TypeError: object of type 'NoneType' has no len().

Fix: add a leading guard that raises a clear ValueError instructing the caller to pass [start, end].

III: off-by-one in window-range index filter

_assign_windows filtered candidate indices with <= len(norm_int), admitting one out-of-range index. Harmless today (the value is only used via .isin) but incorrect.

Fix: use < len(norm_int). No observable output change.

Test hardening

Several exception tests used the try/except: assert True pattern with no assert False after the call, so they passed even when no exception (or the wrong one) was raised. Converted these to pytest.raises(...) intest_peak_fitting, test_deconvolve_peaks, and test_crop, and added regression tests for the multi-peak bound check and the crop(None) guard.

Risk

Low. No behavior change for valid input; the only newly-raised error (the crop(None) guard) replaces an existing crash with a clearer message.

Three scoped fixes plus test hardening:

* deconvolve_peaks: the global `param_bounds` initial-guess check used a
  positive index into the per-window `p0` list, which always referred to
  the first peak's parameters. For windows with >1 peak this validated the
  wrong peak's guess. Use the existing negative-index map `paridx` so each
  peak is checked against its own guess; remove the now-dead `key_inds`.

* crop: passing no/`None` time window raised an opaque
  `TypeError: object of type 'NoneType' has no len()`. Guard it with a
  clear ValueError.

* _assign_windows: the window-range index filter used `<= len(norm_int)`,
  admitting one out-of-range index; use `<`.

Test hardening: convert vacuous `try/except: assert True` blocks (which
pass even when no exception is raised) to `pytest.raises` in
test_peak_fitting, test_deconvolve_peaks, and test_crop; add regression
tests for the multi-peak bound check and the crop(None) guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@codecov

codecov Bot commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 97.12%. Comparing base (391f9ee) to head (74ff25d).

Additional details and impacted files
@@           Coverage Diff           @@
##             main      #27   +/-   ##
=======================================
  Coverage   97.12%   97.12%           
=======================================
  Files           3        3           
  Lines         556      557    +1     
=======================================
+ Hits          540      541    +1     
  Misses         16       16           

☔ View full report in Codecov by Harness.
📢 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.

gchure and others added 2 commits June 21, 2026 20:37
- Hoist `import scipy.stats` to module level instead of inside
  test_multipeak_param_bounds_validated_per_peak, matching the other
  top-level imports in the suite.
- Pin the crop(None)/crop() assertions to the guard's specific message
  via `match='must be provided as a list'`, so a future refactor can't
  satisfy them with an unrelated ValueError (e.g. the length-2 check).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
crop(None) now raises ValueError, but the docstring still claimed
"If None, the entire time range ... will be considered" — behavior that
was never actually implemented (the old code crashed with TypeError).
Document time_window as required.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@gchure gchure merged commit 026596d into main Jun 22, 2026
5 checks passed
gchure added a commit that referenced this pull request Jun 26, 2026
Resolves a conflict in tests/test_chromatogram.py where both branches
appended new tests after test_generic_param_bounding: main (via #27) added
test_multipeak_param_bounds_validated_per_peak, while this branch added the
deeper-correctness regression tests. Kept both. Also dropped the now-redundant
local `import scipy.stats` in the _skewnorm_signal helper, since #27 added that
import at module level.

quant.py merged cleanly: #27's per-peak `paridx` bound check and this branch's
location clamp + pre-fit safety net coexist. Full suite: 23 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.

1 participant