feat(solver): surface bi-exponential fit outcome (FitMode)#162
Merged
Conversation
The bi-exponential fit could silently fall back to slow-only, produce a
degenerate result (no positive slow amplitude — a noise/flat free kernel), or
return the empty sentinel, and the caller had to infer this from beta_fast==0
or an infinite residual. Make the outcome explicit and carry it through the
whole stack so a suspect kernel is reported, not guessed.
Rust (biexp_fit.rs): add enum FitMode { TwoComponent, SlowOnly, Degenerate,
Empty }; add a fit_mode field to BiexpResult, classified once on the returned
result (residual !finite -> Empty; beta<=0 -> Degenerate; else two/slow by
fast-component presence). Intermediate candidates carry a placeholder.
FFI: WASM indeca_fit_biexponential serializes fit_mode (variant name) in its
result object; PyO3 py_indeca_fit_biexponential appends it as a string to the
return tuple (now 8-tuple); Python BiexpFitResult gains a fit_mode field.
App (CaDecon): worker parses fitMode into KernelResult; iteration-manager
counts Degenerate/Empty subset fits per iteration (degenerateSubsets /
totalSubsetFits on the snapshot); KernelDisplay shows a vermillion
"N/K fits degenerate" warning when the displayed kernel rests on untrustworthy
subset fits.
Tests: Rust (Empty on empty input, Degenerate on flat kernel, usable on a clean
biexp); Python test_fit_mode covers all three via the public wrapper. Verified
in-browser that a clean run shows no warning and no console errors. All suites
green (131 Rust, 32 Python, 289 TS); fmt/clippy clean on both feature sets.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
fit_biexponential now returns an 8-tuple (added fit_mode); update the pre-existing tuple-unpacking test that hard-coded 7. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This was referenced Jul 8, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The bi-exponential fit could silently:
0.02/0.4, residual = ∞) on empty input.The caller had to infer which by inspecting
beta_fast == 0or an infinite residual. That's the second silent-fallback defensibility gap flagged in the config/numerical inventory (after the FFI NaN guard).Change
Make the fit outcome explicit and carry it through the whole stack.
Rust (
biexp_fit.rs) — addenum FitMode { TwoComponent, SlowOnly, Degenerate, Empty }and afit_modefield onBiexpResult, classified once on the returned result:residualnot finite →Emptybeta <= 0→DegenerateTwoComponent/SlowOnlyby fast-component presence.FFI — WASM
indeca_fit_biexponentialserializesfit_mode(variant name) in its result object; PyO3py_indeca_fit_biexponentialappends it as a string (return is now an 8-tuple); PythonBiexpFitResultgains afit_modefield.App (CaDecon) — the worker parses
fitModeintoKernelResult;iteration-managercountsDegenerate/Emptysubset fits per iteration (degenerateSubsets/totalSubsetFitson the snapshot); KernelDisplay shows a vermillion⚠ N/K fits degeneratewarning when the displayed kernel rests on untrustworthy subset fits.Tests
Emptyon empty input,Degenerateon a flat kernel, usable mode on a clean biexp.test_fit_mode.pycovers all three via the publicfit_biexponentialwrapper.cargo fmt/clippyclean on both feature sets; typecheck clean; WASM builds.Series
Config/numerical-hygiene series. Done: FFI NaN guard (#161), fit-mode status (this). Remaining (lower priority): expose result-changing tuning constants through config; de-dup + name the fast-grid bounds (latent drift hazard) + resolve the two
biexp_fit.rsTODOs.🤖 Generated with Claude Code