Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 1 addition & 6 deletions .github/workflows/pytest.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,5 @@ jobs:
uv pip install pytest pytest-cov pytest-benchmark hypothesis --system
uv pip freeze --system

- name: Run tests with -Werror
if: matrix.python-version != '3.14'
- name: Run tests
run: pytest --cov=pyerrors -vv -Werror

- name: Run tests without -Werror for python 3.14
if: matrix.python-version == '3.14'
run: pytest --cov=pyerrors -vv
4 changes: 2 additions & 2 deletions pyerrors/fits.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@ def prepare_hat_matrix():

try:
hess = hessian(chisqfunc)(fitp)
except TypeError:
except (TypeError, ValueError, np.linalg.LinAlgError):
raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None

len_y = len(y_f)
Expand Down Expand Up @@ -722,7 +722,7 @@ def odr_chisquare(p):
fitp = out.beta
try:
hess = hessian(odr_chisquare)(np.concatenate((fitp, out.xplusd.ravel())))
except TypeError:
except (TypeError, ValueError, np.linalg.LinAlgError):
raise Exception("It is required to use autograd.numpy instead of numpy within fit functions, see the documentation for details.") from None

def odr_chisquare_compact_x(d):
Expand Down
4 changes: 2 additions & 2 deletions pyerrors/roots.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ def root_func(x, d):
root = scipy.optimize.fsolve(func, guess, d_val)

# Error propagation as detailed in arXiv:1809.01289
dx = jacobian(func)(root[0], d_val)
try:
dx = jacobian(func)(root[0], d_val)
da = jacobian(lambda u, v: func(v, u))(d_val, root[0])
except TypeError:
except (TypeError, ValueError, np.linalg.LinAlgError):
raise Exception("It is required to use autograd.numpy instead of numpy within root functions, see the documentation for details.") from None
deriv = - da / dx
res = derived_observable(lambda x, **kwargs: (x[0] + np.finfo(np.float64).eps) / (np.array(d).reshape(-1)[0].value + np.finfo(np.float64).eps) * root[0],
Expand Down
58 changes: 52 additions & 6 deletions tests/fits_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -698,13 +698,27 @@ def func(a, x):
y = a[0] * np.exp(-a[1] * x)
return y

with pytest.raises(Exception):
pe.least_squares(x, oy, func)
def func_autograd(a, x):
return a[0] * anp.exp(-a[1] * x)

# Since autograd 1.9.0 plain numpy ufuncs are dispatched to the autograd
# wrappers (ArrayBox.__array_ufunc__), so a function using numpy.exp now
# yields exactly the same result as one using autograd.numpy.exp.
for r_np, r_ag in zip(pe.least_squares(x, oy, func), pe.least_squares(x, oy, func_autograd)):
assert r_np == r_ag
for r_np, r_ag in zip(pe.total_least_squares(oy, oy, func), pe.total_least_squares(oy, oy, func_autograd)):
assert r_np == r_ag

# A function that genuinely cannot be traced by autograd must still raise a
# clear error pointing the user to autograd.numpy.
def func_invalid(a, x):
return np.array(a[0] * np.exp(-a[1] * x), dtype=np.float64)

pe.least_squares(x, oy, func, num_grad=True)
with pytest.raises(Exception):
pe.least_squares(x, oy, func_invalid)

with pytest.raises(Exception):
pe.total_least_squares(oy, oy, func)
pe.total_least_squares(oy, oy, func_invalid)


def test_invalid_fit_function():
Expand Down Expand Up @@ -818,16 +832,32 @@ def func_a(a,x):
def func_b(a,x):
return a[0]*np.exp(a[2]*x)

def func_a_autograd(a,x):
return a[0]*anp.exp(a[1]*x)

def func_b_autograd(a,x):
return a[0]*anp.exp(a[2]*x)

funcs = {'a':func_a, 'b':func_b}
funcs_autograd = {'a':func_a_autograd, 'b':func_b_autograd}
xs = {'a':xvals_a, 'b':xvals_b}
ys = {'a':[pe.Obs([np.random.normal(item, item*1.5, 1000)],['ensemble1']) for item in func_exp1(xvals_a)],
'b':[pe.Obs([np.random.normal(item, item*1.4, 1000)],['ensemble1']) for item in func_exp2(xvals_b)]}

for key in funcs.keys():
[item.gamma_method() for item in ys[key]]

# Since autograd 1.9.0 plain numpy ufuncs are dispatched to the autograd
# wrappers, so the fit using numpy now matches the one using autograd.numpy.
for r_np, r_ag in zip(pe.least_squares(xs, ys, funcs), pe.least_squares(xs, ys, funcs_autograd)):
assert r_np == r_ag

# A function that genuinely cannot be traced by autograd must still raise.
def func_a_invalid(a, x):
return np.array(a[0] * np.exp(a[1] * x), dtype=np.float64)

with pytest.raises(Exception):
pe.least_squares(xs, ys, funcs)
pe.least_squares(xs, ys, {'a': func_a_invalid, 'b': func_b})

pe.least_squares(xs, ys, funcs, num_grad=True)

Expand Down Expand Up @@ -930,16 +960,32 @@ def func_a(a,x):
def func_b(a,x):
return a[0]*np.exp(a[2]*x)

def func_a_autograd(a,x):
return a[0]*anp.exp(a[1]*x)

def func_b_autograd(a,x):
return a[0]*anp.exp(a[2]*x)

funcs = {'a':func_a, 'b':func_b}
funcs_autograd = {'a':func_a_autograd, 'b':func_b_autograd}
xs = {'a':xvals_a, 'b':xvals_b}
ys = {'a':[pe.Obs([np.random.normal(item, item*1.5, 1000)],['ensemble1']) for item in func_exp1(xvals_a)],
'b':[pe.Obs([np.random.normal(item, item*1.4, 1000)],['ensemble1']) for item in func_exp2(xvals_b)]}

for key in funcs.keys():
[item.gamma_method() for item in ys[key]]

# Since autograd 1.9.0 plain numpy ufuncs are dispatched to the autograd
# wrappers, so the fit using numpy now matches the one using autograd.numpy.
for r_np, r_ag in zip(pe.least_squares(xs, ys, funcs), pe.least_squares(xs, ys, funcs_autograd)):
assert r_np == r_ag

# A function that genuinely cannot be traced by autograd must still raise.
def func_a_invalid(a, x):
return np.array(a[0] * np.exp(a[1] * x), dtype=np.float64)

with pytest.raises(Exception):
pe.least_squares(xs, ys, funcs)
pe.least_squares(xs, ys, {'a': func_a_invalid, 'b': func_b})

pe.least_squares(xs, ys, funcs, num_grad=True)

Expand Down
15 changes: 14 additions & 1 deletion tests/roots_test.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import numpy as np
import autograd.numpy as anp
import pyerrors as pe
import pytest

Expand Down Expand Up @@ -37,11 +38,23 @@ def test_root_no_autograd():
def root_function(x, d):
return x - np.log(np.exp(d))

def root_function_autograd(x, d):
return x - anp.log(anp.exp(d))

value = np.random.normal(0, 100)
my_obs = pe.pseudo_Obs(value, 0.1, 't')

# Since autograd 1.9.0 plain numpy ufuncs are dispatched to the autograd
# wrappers, so a root function using numpy now yields the same result as
# one using autograd.numpy.
assert pe.roots.find_root(my_obs, root_function) == pe.roots.find_root(my_obs, root_function_autograd)

# A function that genuinely cannot be traced by autograd must still raise.
def root_invalid(x, d):
return x - np.float64(d)

with pytest.raises(Exception):
my_root = pe.roots.find_root(my_obs, root_function)
pe.roots.find_root(my_obs, root_invalid)


def test_root_multi_parameter():
Expand Down
Loading