feat: optimization-course solver suite (8 methods), uncertainty helpers, noise-robustness example - #127
Conversation
New unfolding methods ported from the MIPT "Optimization Methods in Machine Learning" course (fall 2025): - projected gradient descent (unfold_pgd): orthant/box/simplex projections, Armijo backtracking (lecture 9 / HW 14) - Frank-Wolfe with away steps and exact line search (lecture 9 / HW 14) - mirror descent with entropy/l2/pnorm mirror maps (lecture 10 / HW 16) - consensus ADMM with L1 + total variation + nonnegativity, adaptive rho (lecture 11 / HW 18) - L-BFGS-B quasi-Newton with box bounds (lecture 7 / HW 10) - cyclic/randomized NNLS coordinate descent (lecture 15) - subgradient method with Polyak/diminishing steps (lecture 8 / HW 12) - extragradient on the robust saddle formulation (lecture 13 / HW 20) Supporting machinery: - 1-D line search: golden section / dichotomy / Brent (_line_search) - regularization selection by 1-D optimization (GCV / discrepancy / predictive criteria, Hutchinson trace estimate) - regularization_1d - NNLS duality-gap and KKT diagnostics (_dual_diagnostics) - Monte-Carlo variance reduction (antithetic + control variate, lecture 14) in _montecarlo, wired through run_unfolding Examples: - 52-optimization-course-methods.ipynb: solver comparison, duality certificates, regularization selection - 53-montecarlo-20-spectra.ipynb: 20 Monte-Carlo spectra per solver, variance-reduction comparison, doserate distribution 75 new tests in tests/test_optimization_methods.py (all green).
New example: examples/54-noise-robustness.ipynb
- noise sweep delta in {2, 5, 10, 20}% x 20 paired Gaussian realizations
shared by all solvers; 8 new solvers + MLEM/Landweber/CVXPY baselines
- bias-variance decomposition of the spectrum RMSE (normalized by the
solver's own noiseless solution) and the dose-rate error
- RMSE/bias/spread tables, degradation curves, spaghetti plots at 10%
- robust-radius tuning for the extragradient saddle formulation:
matching delta_rob to the true noise level minimizes the error
Fixed: default total-fluence estimate in Frank-Wolfe and entropy mirror
descent. The previous uniform-response heuristic mean(b)/mean(A)*n_bins
over-estimated the simplex level by ~100x on log-spaced GSF grids (215 vs
2.25 for ISO Cf-252), pinning both solvers to a wrong scale (dose rate
off by 2x / 144x). The default is now a data-driven NNLS estimate
(estimate_total_fluence helper in _matrix_utils); the old heuristic is
kept only as a fallback. Notebooks 52/53 re-executed with the fix.
75 optimization-method tests green; ruff clean.
✅ Deploy Preview for bssunfold ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
Not up to standards ⛔🔴 Issues
|
| Category | Results |
|---|---|
| ErrorProne | 1 high |
| Security | 1 medium |
🟢 Metrics 229 complexity · 39 duplication
Metric Results Complexity 229 Duplication 39
🟢 Coverage 92.81% diff coverage · +0.06% coverage variation
Metric Results Coverage variation ✅ +0.06% coverage variation (-1.00%) Diff coverage ✅ 92.81% diff coverage Coverage variation details
Coverable lines Covered lines Coverage Common ancestor commit (cc8d149) 13276 12219 92.04% Head commit (2157779) 14121 (+845) 13005 (+786) 92.10% (+0.06%) Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch:
<coverage of head commit> - <coverage of common ancestor commit>Diff coverage details
Coverable lines Covered lines Diff coverage Pull request (#127) 848 787 92.81% Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified:
<covered lines added or modified>/<coverable lines added or modified> * 100%
NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.
Resolve conflicts with the CUQIpy Bayesian unfolding integration (main): - detector.py: keep all 8 optimization-course solvers + unfold_cuqi - README.md: unified method table (cuqi = #80, optimization-course = #81-88, 88 methods total) and merged feature bullets - CHANGELOG.md: renumber our solver-comparison example 52 -> 55 (main's 52-cuqi-iaea took the number) - uv.lock: regenerated from the merged pyproject (adds cuqipy git source) Post-merge checks: py_compile OK, 75 optimization tests passed, ruff src/ tests/ clean (CI gate), smoke: pgd/FW sums and dose rates sane.
…xis tweaks, opencode.json)
Summary
Ports a suite of classic and modern numerical-optimization methods (from the MFTI "Optimization Methods" course) to the Bonner-sphere unfolding problem, adds uncertainty/diagnostics helpers, and ships three executed example notebooks including a noise-robustness study.
Branch:
feature/optimization-course-methods(2 commits, 26 files, +8189/-159)New solvers (
Detector.unfold_*)unfold_pgdunfold_frank_wolfeunfold_mirror_descentunfold_admmunfold_lbfgsbunfold_coordinate_descentunfold_subgradientunfold_extragradientHelper modules
_line_search.py— golden-section / dichotomy / Brent 1-D search (used by Morozov λ-tuning)regularization_1d.py—select_regularization_1dwith GCV / discrepancy / predictive criteria + Hutchinson trace estimation_dual_diagnostics.py—nnls_duality_gap,nnls_kkt_residuals(solution certificates)_montecarlo.py—monte_carlo_uncertaintywith variance reduction (antithetic / control variates)_matrix_utils.py—estimate_total_fluence(data-driven default for FW / entropy MD)Examples (all executed, 0 errors)
Fixed
Default total-fluence estimate in
unfold_frank_wolfeand entropyunfold_mirror_descent: the previous uniform-response heuristicmean(b)/mean(A)·n_binsover-estimated the simplex level by ~100x on log-spaced GSF grids (215 vs 2.25 for ISO Cf-252), pinning both solvers to a wrong scale. The default is now a data-driven NNLS estimate viaestimate_total_fluence; the old heuristic remains as an explicit fallback. Note: this changes results of default calls (previously mis-scaled) — flagged for review.Testing
tests/test_optimization_methods.py: 75 tests, all greenruffclean; notebooks re-executed with the fluence fixc5df2ff^(known pre-existing skips are optional-dependency related)