Skip to content

Commit 2ad1538

Browse files
authored
Merge pull request #2 from HugoMVale/odr_fit
flip arg order in fcn signature
2 parents 5eb0349 + ae233c6 commit 2ad1538

10 files changed

Lines changed: 69 additions & 64 deletions

File tree

README.md

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,27 +35,26 @@ pip install odrpack
3535
The following example demonstrates a simple use of the package. For more comprehensive examples and explanations, please refer to the [documentation](https://hugomvale.github.io/odrpack-python/) pages.
3636

3737
```py
38-
from odrpack import odr
38+
from odrpack import odr_fit
3939
import numpy as np
4040

41-
x = np.array([0.982, 1.998, 4.978, 6.01])
42-
y = np.array([2.7, 7.4, 148.0, 403.0])
41+
xdata = np.array([0.982, 1.998, 4.978, 6.01])
42+
ydata = np.array([2.7, 7.4, 148.0, 403.0])
4343

4444
beta0 = np.array([2.0, 0.5])
45-
lower = np.array([0.0, 0.0])
46-
upper = np.array([10.0, 0.9])
45+
bounds = (np.array([0.0, 0.0]), np.array([10.0, 0.9]))
4746

4847
def f(beta: np.ndarray, x: np.ndarray) -> np.ndarray:
4948
"Model function."
5049
return beta[0] * np.exp(beta[1]*x)
5150

52-
sol = odr(f, beta0, y, x, lower=lower, upper=upper, iprint=1001)
51+
sol = odr_fit(f, xdata, ydata, beta0, bounds=bounds)
5352

5453
print("beta:", sol.beta)
5554
print("delta:", sol.delta)
5655
```
5756

5857
```sh
59-
beta: [1.63337057 0.9 ]
60-
delta: [-0.36885787 -0.31272733 0.02928942 0.11031791]
58+
beta: [1.63336897 0.9 ]
59+
delta: [-0.36885696 -0.31272648 0.02929022 0.11031872]
6160
```

docs/examples/explicit-model.ipynb

Lines changed: 13 additions & 11 deletions
Large diffs are not rendered by default.

docs/examples/implicit-model.ipynb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@
8888
"metadata": {},
8989
"outputs": [],
9090
"source": [
91-
"def f(beta: np.ndarray, X: np.ndarray) -> np.ndarray:\n",
91+
"def f(X: np.ndarray, beta: np.ndarray) -> np.ndarray:\n",
9292
" x0, y0, a, b, theta = beta\n",
9393
" x, y = X\n",
9494
" return ((x - x0)*np.cos(theta) + (y - y0)*np.sin(theta))**2 / a**2 \\\n",
@@ -140,7 +140,7 @@
140140
},
141141
{
142142
"cell_type": "code",
143-
"execution_count": null,
143+
"execution_count": 6,
144144
"metadata": {},
145145
"outputs": [],
146146
"source": [

src/odrpack/__odrpack.py.cpp

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ int odr_wrapper(int n,
147147
try {
148148
// Evaluate model function
149149
if (*ideval % 10 > 0) {
150-
auto f_object = fcn_f_holder(beta_ndarray, xplusd_ndarray);
150+
auto f_object = fcn_f_holder(xplusd_ndarray, beta_ndarray);
151151
auto f_ndarray = nb::cast<nb::ndarray<const double, nb::c_contig>>(f_object);
152152
auto f_ndarray_ptr = f_ndarray.data();
153153
for (auto i = 0; i < (*q) * (*n); i++) {
@@ -157,7 +157,7 @@ int odr_wrapper(int n,
157157

158158
// Model partial derivatives wrt `beta`
159159
if ((*ideval / 10) % 10 != 0) {
160-
auto fjacb_object = fcn_fjacb_holder(beta_ndarray, xplusd_ndarray);
160+
auto fjacb_object = fcn_fjacb_holder(xplusd_ndarray, beta_ndarray);
161161
auto fjacb_ndarray = nb::cast<nb::ndarray<const double, nb::c_contig>>(fjacb_object);
162162
auto fjacb_ndarray_ptr = fjacb_ndarray.data();
163163
for (auto i = 0; i < (*q) * (*npar) * (*n); i++) {
@@ -167,7 +167,7 @@ int odr_wrapper(int n,
167167

168168
// Model partial derivatives wrt `delta`
169169
if ((*ideval / 100) % 10 != 0) {
170-
auto fjacd_object = fcn_fjacd_holder(beta_ndarray, xplusd_ndarray);
170+
auto fjacd_object = fcn_fjacd_holder(xplusd_ndarray, beta_ndarray);
171171
auto fjacd_ndarray = nb::cast<nb::ndarray<const double, nb::c_contig>>(fjacd_object);
172172
auto fjacd_ndarray_ptr = fjacd_ndarray.data();
173173
for (auto i = 0; i < (*q) * (*npar) * (*n); i++) {
@@ -261,13 +261,13 @@ ldstpd : int
261261
ldscld : int
262262
Leading dimension of the `scld` array, must be in `{1, n}`.
263263
f : Callable
264-
User-supplied function for evaluating the model, `f(beta, x)`.
264+
User-supplied function for evaluating the model, `f(x, beta)`.
265265
fjacb : Callable
266266
User-supplied function for evaluating the Jacobian w.r.t. `beta`,
267-
`fjacb(beta, x)`.
267+
`fjacb(x, beta)`.
268268
fjacd : Callable
269269
User-supplied function for evaluating the Jacobian w.r.t. `delta`,
270-
`fjacd(beta, x)`.
270+
`fjacd(x, beta)`.
271271
beta : np.ndarray[float64]
272272
Array of function parameters with shape `(npar)`.
273273
y : np.ndarray[float64]

src/odrpack/__odrpack.pyi

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -79,13 +79,13 @@ def odr(n: int, m: int, q: int, npar: int, ldwe: int, ld2we: int, ldwd: int, ld2
7979
ldscld : int
8080
Leading dimension of the `scld` array, must be in `{1, n}`.
8181
f : Callable
82-
User-supplied function for evaluating the model, `f(beta, x)`.
82+
User-supplied function for evaluating the model, `f(x, beta)`.
8383
fjacb : Callable
8484
User-supplied function for evaluating the Jacobian w.r.t. `beta`,
85-
`fjacb(beta, x)`.
85+
`fjacb(x, beta)`.
8686
fjacd : Callable
8787
User-supplied function for evaluating the Jacobian w.r.t. `delta`,
88-
`fjacd(beta, x)`.
88+
`fjacd(x, beta)`.
8989
beta : np.ndarray[float64]
9090
Array of function parameters with shape `(npar)`.
9191
y : np.ndarray[float64]

src/odrpack/odr_fortran.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -253,7 +253,7 @@ def odr(f: Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.float
253253
... return beta[0] * np.exp(beta[1]*x)
254254
>>> sol = odr(f, beta0, y, x, lower=lower, upper=upper, iprint=0)
255255
>>> sol.beta
256-
array([1.63337057, 0.9 ])
256+
array([1.63336897, 0.9 ])
257257
"""
258258

259259
# Future deprecation warning
@@ -434,29 +434,33 @@ def odr(f: Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.float
434434

435435
# Check model function and jacobians
436436
f0 = f(beta0, x)
437+
def f_(x, beta): return f(beta, x)
438+
437439
if f0.shape != y.shape:
438440
raise ValueError(
439441
"Function `f` must return an array with the same shape as `y`.")
440442

441-
def fdummy(beta, x): return np.array([np.nan]) # will never be called
443+
def fdummy(x, beta): return np.array([np.nan]) # will never be called
442444

443445
if has_jac and fjacb is not None:
444446
fjacb0 = fjacb(beta0, x)
447+
def fjacb_(x, beta): return fjacb(beta, x)
445448
if fjacb0.shape[-1] != n or fjacb0.size != n*npar*q:
446449
raise ValueError(
447450
"Function `fjacb` must return an array with shape `(n, npar, q)` or compatible.")
448451
elif not has_jac and fjacb is None:
449-
fjacb = fdummy
452+
fjacb_ = fdummy
450453
else:
451454
raise ValueError("Inconsistent arguments for `job` and `fjacb`.")
452455

453456
if has_jac and fjacd is not None:
454457
fjacd0 = fjacd(beta0, x)
458+
def fjacd_(x, beta): return fjacd(beta, x)
455459
if fjacd0.shape[-1] != n or fjacd0.size != n*m*q:
456460
raise ValueError(
457461
"Function `fjacd` must return an array with shape `(n, m, q)` or compatible.")
458462
elif not has_jac and fjacd is None:
459-
fjacd = fdummy
463+
fjacd_ = fdummy
460464
else:
461465
raise ValueError("Inconsistent arguments for `job` and `fjacd`.")
462466

@@ -483,7 +487,7 @@ def fdummy(beta, x): return np.array([np.nan]) # will never be called
483487
ldwd=ldwd, ld2wd=ld2wd,
484488
ldifx=ldifx,
485489
ldstpd=ldstpd, ldscld=ldscld,
486-
f=f, fjacb=fjacb, fjacd=fjacd,
490+
f=f_, fjacb=fjacb_, fjacd=fjacd_,
487491
beta=beta, y=y, x=x,
488492
delta=delta,
489493
we=we, wd=wd, ifixb=ifixb, ifixx=ifixx,

src/odrpack/odr_scipy.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ def odr_fit(f: Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.f
4848
Parameters
4949
----------
5050
f : Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]]
51-
Function to be fitted, with the signature `f(beta, x)`. It must return
51+
Function to be fitted, with the signature `f(x, beta)`. It must return
5252
an array with the same shape as `y`.
5353
xdata : NDArray[np.float64]
5454
Array of shape `(n,)` or `(m, n)` containing the observed values of the
@@ -117,13 +117,13 @@ def odr_fit(f: Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.f
117117
all `xdata` values are automatically treated as fixed.
118118
jac_beta : Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]] | None
119119
Jacobian of the function to be fitted with respect to `beta`, with the
120-
signature `jac_beta(beta, x)`. It must return an array with shape
120+
signature `jac_beta(x, beta)`. It must return an array with shape
121121
`(n, npar, q)` or a compatible shape. By default, the Jacobian is
122122
approximated numerically using the finite difference scheme specified
123123
by `diff_scheme`.
124124
jac_x : Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.float64]] | None
125125
Jacobian of the function to be fitted with respect to `x`, with the
126-
signature `jac_x(beta, x)`. It must return an array with shape
126+
signature `jac_x(x, beta)`. It must return an array with shape
127127
`(n, m, q)` or a compatible shape. By default, the Jacobian is approximated
128128
numerically using the finite difference scheme specified by `diff_scheme`.
129129
delta0 : NDArray[np.float64] | None
@@ -229,7 +229,7 @@ def odr_fit(f: Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.f
229229
>>> ydata = np.array([2.7, 7.4, 148.0, 403.0])
230230
>>> beta0 = np.array([2., 0.5])
231231
>>> bounds = (np.array([0., 0.]), np.array([10., 0.9]))
232-
>>> def f(beta: np.ndarray, x: np.ndarray) -> np.ndarray:
232+
>>> def f(x: np.ndarray, beta: np.ndarray) -> np.ndarray:
233233
... return beta[0] * np.exp(beta[1]*x)
234234
>>> sol = odr_fit(f, xdata, ydata, beta0, bounds=bounds)
235235
>>> sol.beta
@@ -409,25 +409,25 @@ def odr_fit(f: Callable[[NDArray[np.float64], NDArray[np.float64]], NDArray[np.f
409409
ld2we = 1
410410

411411
# Check model function
412-
f0 = f(beta0, xdata)
412+
f0 = f(xdata, beta0)
413413
if f0.shape != ydata.shape:
414414
raise ValueError(
415415
"Function `f` must return an array with the same shape as `ydata`.")
416416

417417
# Check model jacobians
418-
def fdummy(beta, x): return np.array([np.nan]) # will never be called
418+
def fdummy(x, beta): return np.array([np.nan]) # will never be called
419419

420420
if jac_beta is None and jac_x is None:
421421
has_jac = False
422422
jac_beta = fdummy
423423
jac_x = fdummy
424424
elif jac_beta is not None and jac_x is not None:
425425
has_jac = True
426-
jac0_beta = jac_beta(beta0, xdata)
426+
jac0_beta = jac_beta(xdata, beta0)
427427
if jac0_beta.shape[-1] != n or jac0_beta.size != n*npar*q:
428428
raise ValueError(
429429
"Function `jac_beta` must return an array with shape `(n, npar, q)` or compatible.")
430-
jac0_x = jac_x(beta0, xdata)
430+
jac0_x = jac_x(xdata, beta0)
431431
if jac0_x.shape[-1] != n or jac0_x.size != n*m*q:
432432
raise ValueError(
433433
"Function `jac_x` must return an array with shape `(n, m, q)` or compatible.")

tests/test_bindings.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,19 +42,19 @@ def test_dimension_consistency():
4242
def test_odr():
4343
"example5 from odrpack"
4444

45-
def f(beta: np.ndarray, x: np.ndarray) -> np.ndarray:
45+
def f(x: np.ndarray, beta: np.ndarray) -> np.ndarray:
4646
return beta[0] * np.exp(beta[1]*x)
4747

48-
def fjacb(beta: np.ndarray, x: np.ndarray) -> np.ndarray:
48+
def fjacb(x: np.ndarray, beta: np.ndarray) -> np.ndarray:
4949
jac = np.zeros((beta.size, x.size))
5050
jac[0, :] = np.exp(beta[1]*x)
5151
jac[1, :] = beta[0]*x*np.exp(beta[1]*x)
5252
return jac
5353

54-
def fjacd(beta: np.ndarray, x: np.ndarray) -> np.ndarray:
54+
def fjacd(x: np.ndarray, beta: np.ndarray) -> np.ndarray:
5555
return beta[0] * beta[1] * np.exp(beta[1]*x)
5656

57-
def fdummy(beta: np.ndarray, x: np.ndarray) -> np.ndarray:
57+
def fdummy(x: np.ndarray, beta: np.ndarray) -> np.ndarray:
5858
return np.array([42.0])
5959

6060
beta0 = np.array([2., 0.5])
@@ -77,7 +77,7 @@ def fdummy(beta: np.ndarray, x: np.ndarray) -> np.ndarray:
7777
f, fdummy, fdummy, beta, y, x, delta,
7878
lower=lower, upper=upper, job=0)
7979
assert info == 1
80-
np.allclose(beta, beta_ref)
80+
assert np.allclose(beta, beta_ref)
8181

8282
# solution with jacobian
8383
beta = beta0.copy()

tests/test_multiprocessing.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,27 +10,27 @@
1010
# %% Functions need to be defined outside the test function
1111

1212

13-
def f1(beta: np.ndarray, x: np.ndarray) -> np.ndarray:
13+
def f1(x: np.ndarray, beta: np.ndarray) -> np.ndarray:
1414
return beta[0] + beta[1] * x + beta[2] * x**2 + beta[3] * x**3
1515

1616

1717
beta1 = np.array([1, -2., 0.1, -0.1])
1818
x1 = np.linspace(-10., 10., 21, dtype=np.float64)
19-
y1 = f1(beta1, x1)
19+
y1 = f1(x1, beta1)
2020

2121

22-
def f2(beta: np.ndarray, x: np.ndarray) -> np.ndarray:
22+
def f2(x: np.ndarray, beta: np.ndarray) -> np.ndarray:
2323
time.sleep(np.random.uniform(0, DELAY))
2424
return (beta[0] * x[0, :])**3 + x[1, :]**beta[1]
2525

2626

2727
beta2 = np.array([2., 2.])
2828
x2 = np.linspace(-10., 10., 41, dtype=np.float64)
2929
x2 = np.vstack((x2, 10+x2/2))
30-
y2 = f2(beta2, x2)
30+
y2 = f2(x2, beta2)
3131

3232

33-
def f3(beta: np.ndarray, x: np.ndarray) -> np.ndarray:
33+
def f3(x: np.ndarray, beta: np.ndarray) -> np.ndarray:
3434
time.sleep(np.random.uniform(0, DELAY))
3535
y = np.zeros((2, x.shape[-1]))
3636
y[0, :] = (beta[0] * x[0, :])**3 + x[1, :]**beta[1] + np.exp(x[2, :]/2)
@@ -41,7 +41,7 @@ def f3(beta: np.ndarray, x: np.ndarray) -> np.ndarray:
4141
beta3 = np.array([1., 2., 3.])
4242
x3 = np.linspace(-1., 1., 31, dtype=np.float64)
4343
x3 = np.vstack((x3, np.exp(x3), x3**2))
44-
y3 = f3(beta3, x3)
44+
y3 = f3(x3, beta3)
4545

4646
case1 = (f1, x1, y1, np.ones_like(beta1))
4747
case2 = (f2, x2, y2, np.ones_like(beta2))

tests/test_odr_fit.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,12 @@ def add_noise(array, noise, seed):
1919
@pytest.fixture
2020
def case1():
2121
# m=1, q=1
22-
def f(beta: np.ndarray, x: np.ndarray) -> np.ndarray:
22+
def f(x: np.ndarray, beta: np.ndarray) -> np.ndarray:
2323
return beta[0] + beta[1] * x + beta[2] * x**2 + beta[3] * x**3
2424

2525
beta_star = np.array([1, -2., 0.1, -0.1])
2626
x = np.linspace(-10., 10., 21)
27-
y = f(beta_star, x)
27+
y = f(x, beta_star)
2828

2929
x = add_noise(x, 5e-2, SEED)
3030
y = add_noise(y, 10e-2, SEED)
@@ -35,13 +35,13 @@ def f(beta: np.ndarray, x: np.ndarray) -> np.ndarray:
3535
@pytest.fixture
3636
def case2():
3737
# m=2, q=1
38-
def f(beta: np.ndarray, x: np.ndarray) -> np.ndarray:
38+
def f(x: np.ndarray, beta: np.ndarray) -> np.ndarray:
3939
return (beta[0] * x[0, :])**3 + x[1, :]**beta[1]
4040

4141
beta_star = np.array([2., 2.])
4242
x1 = np.linspace(-10., 10., 41)
4343
x = np.vstack((x1, 10+x1/2))
44-
y = f(beta_star, x)
44+
y = f(x, beta_star)
4545

4646
x = add_noise(x, 5e-2, SEED)
4747
y = add_noise(y, 10e-2, SEED)
@@ -52,7 +52,7 @@ def f(beta: np.ndarray, x: np.ndarray) -> np.ndarray:
5252
@pytest.fixture
5353
def case3():
5454
# m=3, q=2
55-
def f(beta: np.ndarray, x: np.ndarray) -> np.ndarray:
55+
def f(x: np.ndarray, beta: np.ndarray) -> np.ndarray:
5656
y = np.zeros((2, x.shape[-1]))
5757
y[0, :] = (beta[0] * x[0, :])**3 + x[1, :]**beta[1] + np.exp(x[2, :]/2)
5858
y[1, :] = (beta[2] * x[0, :])**2 + x[1, :]**beta[1]
@@ -61,7 +61,7 @@ def f(beta: np.ndarray, x: np.ndarray) -> np.ndarray:
6161
beta_star = np.array([1., 2., 3.])
6262
x1 = np.linspace(-1., 1., 31)
6363
x = np.vstack((x1, np.exp(x1), x1**2))
64-
y = f(beta_star, x)
64+
y = f(x, beta_star)
6565

6666
x = add_noise(x, 5e-2, SEED)
6767
y = add_noise(y, 10e-2, SEED)
@@ -460,16 +460,16 @@ def test_jacobians():
460460
beta0 = np.array([2., 0.5])
461461
bounds = (np.array([0., 0.]), np.array([10., 0.9]))
462462

463-
def f(beta: np.ndarray, x: np.ndarray) -> np.ndarray:
463+
def f(x: np.ndarray, beta: np.ndarray) -> np.ndarray:
464464
return beta[0] * np.exp(beta[1]*x)
465465

466-
def jac_beta(beta: np.ndarray, x: np.ndarray) -> np.ndarray:
466+
def jac_beta(x: np.ndarray, beta: np.ndarray) -> np.ndarray:
467467
jac = np.zeros((beta.size, x.size))
468468
jac[0, :] = np.exp(beta[1]*x)
469469
jac[1, :] = beta[0]*x*np.exp(beta[1]*x)
470470
return jac
471471

472-
def jac_x(beta: np.ndarray, x: np.ndarray) -> np.ndarray:
472+
def jac_x(x: np.ndarray, beta: np.ndarray) -> np.ndarray:
473473
return beta[0] * beta[1] * np.exp(beta[1]*x)
474474

475475
beta_ref = np.array([1.63337602, 0.9])
@@ -539,7 +539,7 @@ def test_implicit_model():
539539
xdata = np.array(x).T
540540
ydata = np.full(xdata.shape[-1], 0.0)
541541

542-
def f(beta: np.ndarray, x: np.ndarray) -> np.ndarray:
542+
def f(x: np.ndarray, beta: np.ndarray) -> np.ndarray:
543543
v, h = x
544544
return beta[2]*(v-beta[0])**2 + 2*beta[3]*(v-beta[0])*(h-beta[1]) \
545545
+ beta[4]*(h-beta[1])**2 - 1

0 commit comments

Comments
 (0)