From 18a70fad5308f359efaecb41f78e95af65ab2a23 Mon Sep 17 00:00:00 2001 From: Fabian Joswig Date: Thu, 19 Feb 2026 07:34:05 +0100 Subject: [PATCH] [Fix] Fix behaviour for rank deficient fits. Add test --- pyerrors/fits.py | 14 ++++++++++++-- tests/fits_test.py | 13 +++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) diff --git a/pyerrors/fits.py b/pyerrors/fits.py index 732f1521..98763b9d 100644 --- a/pyerrors/fits.py +++ b/pyerrors/fits.py @@ -567,7 +567,7 @@ def total_least_squares(x, y, func, silent=False, **kwargs): Notes ----- - Based on the orthogonal distance regression module of scipy. + Based on the odrpack orthogonal distance regression library. Returns ------- @@ -670,7 +670,17 @@ def total_least_squares(x, y, func, silent=False, **kwargs): print('Residual variance:', output.residual_variance) if not out.success: - raise Exception('The minimization procedure did not converge.') + # info % 5 gives the convergence status: 1=sum-of-sq, 2=param, 3=both + # If odrpack reports rank deficiency (e.g. vanishing chi-squared when + # n_obs == n_parms), convergence was still achieved – allow with a warning. + if out.info % 5 in [1, 2, 3]: + warnings.warn( + "ODR fit is rank deficient. This may indicate a vanishing " + "chi-squared (n_obs == n_parms). Results may be unreliable.", + RuntimeWarning + ) + else: + raise Exception('The minimization procedure did not converge.') m = x_f.size diff --git a/tests/fits_test.py b/tests/fits_test.py index c9138dcd..b89a3931 100644 --- a/tests/fits_test.py +++ b/tests/fits_test.py @@ -468,6 +468,19 @@ def test_total_least_squares(): assert((outc.fit_parameters[1] - betac[1]).is_zero()) +def test_total_least_squares_vanishing_chisquare(): + """Test that a saturated fit (n_obs == n_parms) works without exception.""" + def func(a, x): + return a[0] + a[1] * x + + x = [pe.pseudo_Obs(1.0, 0.1, 'x0'), pe.pseudo_Obs(2.0, 0.1, 'x1')] + y = [pe.pseudo_Obs(1.0, 0.1, 'y0'), pe.pseudo_Obs(2.0, 0.1, 'y1')] + + with pytest.warns(RuntimeWarning, match="rank deficient"): + out = pe.total_least_squares(x, y, func, silent=True) + assert len(out.fit_parameters) == 2 + + def test_odr_derivatives(): x = [] y = []