-
Notifications
You must be signed in to change notification settings - Fork 21
[Fix] Migrate to odrpack because of scipy.odr deprecation #279
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from 5 commits
46d3268
18a70fa
23d54f1
1689dc9
5084894
92d5b59
8b3b1f3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,7 +3,7 @@ | |
| import matplotlib.pyplot as plt | ||
| import math | ||
| import scipy.optimize | ||
| from scipy.odr import ODR, Model, RealData | ||
| from odrpack import odr_fit | ||
| from scipy.linalg import cholesky | ||
| from scipy.stats import norm | ||
| import iminuit | ||
|
|
@@ -397,11 +397,21 @@ def func(a, x): | |
| y = a[0] * anp.exp(-a[1] * x) | ||
| return y | ||
|
|
||
| data = RealData([o.value for o in ox], [o.value for o in oy], sx=[o.dvalue for o in ox], sy=[o.dvalue for o in oy]) | ||
| model = Model(func) | ||
| odr = ODR(data, model, [0, 0], partol=np.finfo(np.float64).eps) | ||
| odr.set_job(fit_type=0, deriv=1) | ||
| output = odr.run() | ||
| # odrpack expects f(x, beta), but pyerrors convention is f(beta, x) | ||
| def wrapped_func(x, beta): | ||
| return func(beta, x) | ||
|
|
||
| output = odr_fit( | ||
| wrapped_func, | ||
| np.array([o.value for o in ox]), | ||
| np.array([o.value for o in oy]), | ||
| beta0=np.array([0.0, 0.0]), | ||
| weight_x=1.0 / np.array([o.dvalue for o in ox]) ** 2, | ||
| weight_y=1.0 / np.array([o.dvalue for o in oy]) ** 2, | ||
| partol=np.finfo(np.float64).eps, | ||
| task='explicit-ODR', | ||
| diff_scheme='central' | ||
| ) | ||
|
|
||
| out = pe.total_least_squares(ox, oy, func, expected_chisquare=True) | ||
| beta = out.fit_parameters | ||
|
|
@@ -458,6 +468,19 @@ def func(a, x): | |
| 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 = [] | ||
|
|
@@ -1431,11 +1454,11 @@ def func(a, x): | |
| global print_output, beta0 | ||
| print_output = 1 | ||
| if 'initial_guess' in kwargs: | ||
| beta0 = kwargs.get('initial_guess') | ||
| beta0 = np.asarray(kwargs.get('initial_guess'), dtype=np.float64) | ||
| if len(beta0) != n_parms: | ||
| raise Exception('Initial guess does not have the correct length.') | ||
| else: | ||
| beta0 = np.arange(n_parms) | ||
| beta0 = np.arange(n_parms, dtype=np.float64) | ||
|
|
||
| if len(x) != len(y): | ||
| raise Exception('x and y have to have the same length') | ||
|
|
@@ -1463,23 +1486,45 @@ def do_the_fit(obs, **kwargs): | |
|
|
||
| xerr = kwargs.get('xerr') | ||
|
|
||
| # odrpack expects f(x, beta), but pyerrors convention is f(beta, x) | ||
| def wrapped_func(x, beta): | ||
| return func(beta, x) | ||
|
|
||
| if length == len(obs): | ||
| assert 'x_constants' in kwargs | ||
| data = RealData(kwargs.get('x_constants'), obs, sy=yerr) | ||
| fit_type = 2 | ||
| x_data = np.asarray(kwargs.get('x_constants')) | ||
| y_data = np.asarray(obs) | ||
| # Ordinary least squares (no x errors) | ||
|
Comment on lines
+1501
to
+1503
|
||
| output = odr_fit( | ||
| wrapped_func, | ||
| x_data, | ||
| y_data, | ||
| beta0=beta0, | ||
| weight_y=1.0 / np.asarray(yerr) ** 2, | ||
| partol=np.finfo(np.float64).eps, | ||
| task='explicit-OLS', | ||
| diff_scheme='central' | ||
| ) | ||
| elif length == len(obs) // 2: | ||
| data = RealData(obs[:length], obs[length:], sx=xerr, sy=yerr) | ||
| fit_type = 0 | ||
| x_data = np.asarray(obs[:length]) | ||
| y_data = np.asarray(obs[length:]) | ||
| # ODR with x errors | ||
|
Comment on lines
+1515
to
+1517
|
||
| output = odr_fit( | ||
| wrapped_func, | ||
| x_data, | ||
| y_data, | ||
| beta0=beta0, | ||
| weight_x=1.0 / np.asarray(xerr) ** 2, | ||
| weight_y=1.0 / np.asarray(yerr) ** 2, | ||
| partol=np.finfo(np.float64).eps, | ||
| task='explicit-ODR', | ||
| diff_scheme='central' | ||
| ) | ||
| else: | ||
| raise Exception('x and y do not fit together.') | ||
|
|
||
| model = Model(func) | ||
|
|
||
| odr = ODR(data, model, beta0, partol=np.finfo(np.float64).eps) | ||
| odr.set_job(fit_type=fit_type, deriv=1) | ||
| output = odr.run() | ||
| if print_output and not silent: | ||
| print(*output.stopreason) | ||
| print(output.stopreason) | ||
| print('chisquare/d.o.f.:', output.res_var) | ||
| print_output = 0 | ||
| beta0 = output.beta | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The implementation now uses
odrpack.odr_fit, buttotal_least_squaresstill documents itself as being based on SciPy ODR (see this function’s docstring “Notes” section) and the package-level docs inpyerrors/__init__.pystill reference SciPy’s ODR (and even point readers tofits.least_squares). Please update the documentation to reflect the newodrpackbackend and referencefits.total_least_squareswhere appropriate, so users aren’t directed to deprecated/incorrect APIs.