33
44import numpy as np
55import pytest
6+ from scipy .odr import odr as odrscipy
67from scipy .optimize import curve_fit
78
89from odrpack import OdrStop , odr_fit
1112SEED = 1234567890
1213
1314
14- def add_noise (array , noise , seed ):
15+ def add_noise (array , noise , seed = SEED ):
1516 """Adds random noise to an array."""
1617 rng = np .random .default_rng (seed )
1718 return array * (1 + noise * rng .uniform (- 1 , 1 , size = array .shape ))
1819
1920
21+ def flipargs (f ):
22+ """Flips the order of the arguments of a function."""
23+ return lambda x , beta : f (beta , x )
24+
25+
2026@pytest .fixture
2127def case1 ():
22- # m=1, q=1
28+ "Made up test case with m=1, q=1"
2329 def f (x : np .ndarray , beta : np .ndarray ) -> np .ndarray :
2430 return beta [0 ] + beta [1 ] * x + beta [2 ] * x ** 2 + beta [3 ] * x ** 3
2531
2632 beta_star = np .array ([1 , - 2. , 0.1 , - 0.1 ])
2733 x = np .linspace (- 10. , 10. , 21 )
2834 y = f (x , beta_star )
2935
30- x = add_noise (x , 5e-2 , SEED )
31- y = add_noise (y , 10e-2 , SEED )
36+ x = add_noise (x , 5e-2 )
37+ y = add_noise (y , 10e-2 )
3238
3339 return {'xdata' : x , 'ydata' : y , 'f' : f , 'beta0' : np .zeros_like (beta_star )}
3440
3541
3642@pytest .fixture
3743def case2 ():
38- # m=2, q=1
44+ "Made up test with m=2, q=1"
3945 def f (x : np .ndarray , beta : np .ndarray ) -> np .ndarray :
4046 return (beta [0 ] * x [0 , :])** 3 + x [1 , :]** beta [1 ]
4147
@@ -44,15 +50,15 @@ def f(x: np.ndarray, beta: np.ndarray) -> np.ndarray:
4450 x = np .vstack ((x1 , 10 + x1 / 2 ))
4551 y = f (x , beta_star )
4652
47- x = add_noise (x , 5e-2 , SEED )
48- y = add_noise (y , 10e-2 , SEED )
53+ x = add_noise (x , 5e-2 )
54+ y = add_noise (y , 10e-2 )
4955
50- return {'xdata' : x , 'ydata' : y , 'f' : f , 'beta0' : np .array ([ 1. , 1. ] )}
56+ return {'xdata' : x , 'ydata' : y , 'f' : f , 'beta0' : np .ones_like ( beta_star )}
5157
5258
5359@pytest .fixture
5460def case3 ():
55- # m=3, q=2
61+ "Made up test case with m=3, q=2"
5662 def f (x : np .ndarray , beta : np .ndarray ) -> np .ndarray :
5763 y = np .zeros ((2 , x .shape [- 1 ]))
5864 y [0 , :] = (beta [0 ] * x [0 , :])** 3 + x [1 , :]** beta [1 ] + np .exp (x [2 , :]/ 2 )
@@ -64,10 +70,79 @@ def f(x: np.ndarray, beta: np.ndarray) -> np.ndarray:
6470 x = np .vstack ((x1 , np .exp (x1 ), x1 ** 2 ))
6571 y = f (x , beta_star )
6672
67- x = add_noise (x , 5e-2 , SEED )
68- y = add_noise (y , 10e-2 , SEED )
73+ x = add_noise (x , 5e-2 )
74+ y = add_noise (y , 10e-2 )
75+
76+ return {'xdata' : x , 'ydata' : y , 'f' : f , 'beta0' : np .full_like (beta_star , 5. )}
77+
78+
79+ @pytest .fixture
80+ def example2 ():
81+ "odrpack's example2"
82+ x = np .array ([[0.50 , - 0.12 ],
83+ [1.20 , - 0.60 ],
84+ [1.60 , - 1.00 ],
85+ [1.86 , - 1.40 ],
86+ [2.12 , - 2.54 ],
87+ [2.36 , - 3.36 ],
88+ [2.44 , - 4.00 ],
89+ [2.36 , - 4.75 ],
90+ [2.06 , - 5.25 ],
91+ [1.74 , - 5.64 ],
92+ [1.34 , - 5.97 ],
93+ [0.90 , - 6.32 ],
94+ [- 0.28 , - 6.44 ],
95+ [- 0.78 , - 6.44 ],
96+ [- 1.36 , - 6.41 ],
97+ [- 1.90 , - 6.25 ],
98+ [- 2.50 , - 5.88 ],
99+ [- 2.88 , - 5.50 ],
100+ [- 3.18 , - 5.24 ],
101+ [- 3.44 , - 4.86 ]]).T
102+ y = np .full (x .shape [- 1 ], 0.0 )
103+ beta0 = np .array ([- 1.0 , - 3.0 , 0.09 , 0.02 , 0.08 ])
104+
105+ beta_ref = np .array ([- 9.99380462E-01 ,
106+ - 2.93104890E+00 ,
107+ 8.75730642E-02 ,
108+ 1.62299601E-02 ,
109+ 7.97538109E-02 ])
110+
111+ def f (x : np .ndarray , beta : np .ndarray ) -> np .ndarray :
112+ v , h = x
113+ return beta [2 ]* (v - beta [0 ])** 2 + 2 * beta [3 ]* (v - beta [0 ])* (h - beta [1 ]) \
114+ + beta [4 ]* (h - beta [1 ])** 2 - 1
115+
116+ return {'xdata' : x , 'ydata' : y , 'f' : f , 'beta0' : beta0 ,
117+ 'beta_ref' : beta_ref }
118+
119+
120+ @pytest .fixture
121+ def example5 ():
122+ "odrpack's example5"
123+ x = np .array ([0.982 , 1.998 , 4.978 , 6.01 ])
124+ y = np .array ([2.7 , 7.4 , 148.0 , 403.0 ])
125+ beta0 = np .array ([2. , 0.5 ])
126+ bounds = (np .array ([0. , 0. ]), np .array ([10. , 0.9 ]))
127+
128+ beta_ref = np .array ([1.63337602 , 0.9 ])
129+ delta_ref = np .array ([- 0.36886137 , - 0.31273038 , 0.029287 , 0.11031505 ])
130+
131+ def f (x : np .ndarray , beta : np .ndarray ) -> np .ndarray :
132+ return beta [0 ] * np .exp (beta [1 ]* x )
133+
134+ def jac_beta (x : np .ndarray , beta : np .ndarray ) -> np .ndarray :
135+ jac = np .zeros ((beta .size , x .size ))
136+ jac [0 , :] = np .exp (beta [1 ]* x )
137+ jac [1 , :] = beta [0 ]* x * np .exp (beta [1 ]* x )
138+ return jac
139+
140+ def jac_x (x : np .ndarray , beta : np .ndarray ) -> np .ndarray :
141+ return beta [0 ] * beta [1 ] * np .exp (beta [1 ]* x )
69142
70- return {'xdata' : x , 'ydata' : y , 'f' : f , 'beta0' : np .array ([5. , 5. , 5. ])}
143+ return {'f' : f , 'xdata' : x , 'ydata' : y , 'beta0' : beta0 , 'bounds' : bounds ,
144+ 'jac_beta' : jac_beta , 'jac_x' : jac_x , 'beta_ref' : beta_ref ,
145+ 'delta_ref' : delta_ref }
71146
72147
73148def test_base_cases (case1 , case2 , case3 ):
@@ -246,7 +321,7 @@ def test_weight_x(case1, case3):
246321 sol = odr_fit (** case1 , weight_x = 1e10 )
247322 assert np .allclose (sol .delta , np .zeros_like (sol .delta ))
248323
249- # weight_x (n,) and m== 1
324+ # weight_x (n,) and m=1
250325 weight_x = np .ones_like (case1 ['xdata' ])
251326 fix = (4 , 7 )
252327 weight_x [fix ,] = 1e10
@@ -456,46 +531,35 @@ def test_rptfile_and_errfile(case1):
456531 os .remove (errfile )
457532
458533
459- def test_jacobians ():
460-
461- # model and data are from odrpack's example5
462- xdata = np .array ([0.982 , 1.998 , 4.978 , 6.01 ])
463- ydata = np .array ([2.7 , 7.4 , 148.0 , 403.0 ])
464- beta0 = np .array ([2. , 0.5 ])
465- bounds = (np .array ([0. , 0. ]), np .array ([10. , 0.9 ]))
466-
467- def f (x : np .ndarray , beta : np .ndarray ) -> np .ndarray :
468- return beta [0 ] * np .exp (beta [1 ]* x )
469-
470- def jac_beta (x : np .ndarray , beta : np .ndarray ) -> np .ndarray :
471- jac = np .zeros ((beta .size , x .size ))
472- jac [0 , :] = np .exp (beta [1 ]* x )
473- jac [1 , :] = beta [0 ]* x * np .exp (beta [1 ]* x )
474- return jac
534+ def test_jacobians (example5 ):
475535
476- def jac_x (x : np .ndarray , beta : np .ndarray ) -> np .ndarray :
477- return beta [0 ] * beta [1 ] * np .exp (beta [1 ]* x )
478-
479- beta_ref = np .array ([1.63337602 , 0.9 ])
480- delta_ref = np .array ([- 0.36886137 , - 0.31273038 , 0.029287 , 0.11031505 ])
536+ xdata = example5 ['xdata' ]
537+ ydata = example5 ['ydata' ]
538+ beta0 = example5 ['beta0' ]
539+ bounds = example5 ['bounds' ]
540+ f = example5 ['f' ]
541+ jac_beta = example5 ['jac_beta' ]
542+ jac_x = example5 ['jac_x' ]
543+ beta_ref = example5 ['beta_ref' ]
544+ delta_ref = example5 ['delta_ref' ]
481545
482546 # ODR without jacobian
483547 for diff_scheme in ['forward' , 'central' ]:
484548 sol = odr_fit (f , xdata , ydata , beta0 , bounds = bounds ,
485549 diff_scheme = diff_scheme )
486- assert np .allclose (sol .beta , beta_ref , rtol = 1e-4 )
487- assert np .allclose (sol .delta , delta_ref , rtol = 1e-3 )
550+ assert np .allclose (sol .beta , beta_ref , rtol = 1e-6 )
551+ assert np .allclose (sol .delta , delta_ref , rtol = 1e-5 )
488552
489553 # ODR with jacobian
490554 sol = odr_fit (f , xdata , ydata , beta0 , bounds = bounds ,
491555 jac_beta = jac_beta , jac_x = jac_x )
492- assert np .allclose (sol .beta , beta_ref , rtol = 1e-4 )
493- assert np .allclose (sol .delta , delta_ref , rtol = 1e-3 )
556+ assert np .allclose (sol .beta , beta_ref , rtol = 1e-6 )
557+ assert np .allclose (sol .delta , delta_ref , rtol = 1e-6 )
494558
495559 # OLS with jacobian
496560 sol1 = odr_fit (f , xdata , ydata , beta0 , weight_x = 1e100 )
497561 sol2 = odr_fit (f , xdata , ydata , beta0 , jac_beta = jac_beta , task = 'OLS' )
498- assert np .allclose (sol2 .beta , sol1 .beta , rtol = 1e-4 )
562+ assert np .allclose (sol2 .beta , sol1 .beta )
499563 assert np .allclose (sol2 .delta , np .zeros_like (xdata ))
500564
501565 # invalid f shape
@@ -525,49 +589,14 @@ def jac_x(x: np.ndarray, beta: np.ndarray) -> np.ndarray:
525589 _ = odr_fit (f , xdata , ydata , beta0 , diff_scheme = 'invalid' )
526590
527591
528- def test_implicit_model ():
592+ def test_implicit_model (example2 ):
529593
530- # model and data are from odrpack's example2
531- beta0 = np .array ([- 1.0 , - 3.0 , 0.09 , 0.02 , 0.08 ])
532- x = [[0.50 , - 0.12 ],
533- [1.20 , - 0.60 ],
534- [1.60 , - 1.00 ],
535- [1.86 , - 1.40 ],
536- [2.12 , - 2.54 ],
537- [2.36 , - 3.36 ],
538- [2.44 , - 4.00 ],
539- [2.36 , - 4.75 ],
540- [2.06 , - 5.25 ],
541- [1.74 , - 5.64 ],
542- [1.34 , - 5.97 ],
543- [0.90 , - 6.32 ],
544- [- 0.28 , - 6.44 ],
545- [- 0.78 , - 6.44 ],
546- [- 1.36 , - 6.41 ],
547- [- 1.90 , - 6.25 ],
548- [- 2.50 , - 5.88 ],
549- [- 2.88 , - 5.50 ],
550- [- 3.18 , - 5.24 ],
551- [- 3.44 , - 4.86 ]]
552- xdata = np .array (x ).T
553- ydata = np .full (xdata .shape [- 1 ], 0.0 )
594+ sol = odr_fit (example2 ['f' ], example2 ['xdata' ], example2 ['ydata' ],
595+ example2 ['beta0' ], task = 'implicit-ODR' )
596+ assert np .allclose (sol .beta , example2 ['beta_ref' ])
554597
555- def f (x : np .ndarray , beta : np .ndarray ) -> np .ndarray :
556- v , h = x
557- return beta [2 ]* (v - beta [0 ])** 2 + 2 * beta [3 ]* (v - beta [0 ])* (h - beta [1 ]) \
558- + beta [4 ]* (h - beta [1 ])** 2 - 1
559598
560- beta_ref = np .array ([- 9.99380462E-01 ,
561- - 2.93104890E+00 ,
562- 8.75730642E-02 ,
563- 1.62299601E-02 ,
564- 7.97538109E-02 ])
565-
566- sol = odr_fit (f , xdata , ydata , beta0 , task = 'implicit-ODR' )
567- assert np .allclose (sol .beta , beta_ref )
568-
569-
570- def test_ols (case1 ):
599+ def test_OLS (case1 ):
571600
572601 sol1 = odr_fit (** case1 , task = 'OLS' )
573602 sol2 = odr_fit (** case1 , weight_x = 1e100 )
@@ -577,7 +606,6 @@ def test_ols(case1):
577606
578607def test_exception_odrstop ():
579608
580- # model and data are from odrpack's example5
581609 xdata = np .array ([1.0 , 2.0 , 3.0 , 4.0 ])
582610 ydata = np .array ([1.0 , 2.0 , 3.0 , 4.0 ])
583611 beta0 = np .array ([1.0 , 1.0 ])
@@ -593,10 +621,35 @@ def f(x: np.ndarray, beta: np.ndarray) -> np.ndarray:
593621 assert sol .info == 51000
594622
595623
596- def test_compare_scipy (case1 ):
624+ def test_compare_scipy (case1 , case2 , case3 , example2 ):
597625
626+ # case1 // scipy.optimize.curve_fit
598627 sol1 = odr_fit (** case1 , task = 'OLS' )
599628 sol2 = curve_fit (lambda x , * b : case1 ['f' ](x , np .array (b )),
600629 case1 ['xdata' ], case1 ['ydata' ], case1 ['beta0' ])
601- print (sol2 )
602630 assert np .allclose (sol1 .beta , sol2 [0 ])
631+
632+ # case1,2,3 // scipy.odr.odr
633+ for case in [case3 ]:
634+ for subcase in range (2 ):
635+ if subcase == 0 :
636+ wd = np .random .uniform (0.1 , 1.0 )
637+ we = np .random .uniform (0.1 , 1.0 )
638+ rtol = 1e-4
639+ else :
640+ wd = np .random .rand (* case ['xdata' ].shape )
641+ we = np .random .rand (* case ['ydata' ].shape )
642+ rtol = 1e-3 # very tough problem!
643+
644+ sol1 = odr_fit (** case , weight_x = wd , weight_y = we )
645+ sol2 = odrscipy (flipargs (case ['f' ]),
646+ case ['beta0' ], case ['ydata' ], case ['xdata' ],
647+ wd = wd , we = we , full_output = True )
648+
649+ assert np .allclose (sol1 .beta , sol2 [0 ], rtol = 1e-5 )
650+
651+ assert np .all (np .max (we * abs (sol1 .eps - sol2 [3 ]['eps' ]), - 1 ) /
652+ (np .max (case ['ydata' ], - 1 ) - np .min (case ['ydata' ], - 1 )) < rtol )
653+
654+ assert np .all (np .max (wd * abs (sol1 .delta - sol2 [3 ]['delta' ]), - 1 ) /
655+ (np .max (case ['xdata' ], - 1 ) - np .min (case ['xdata' ], - 1 )) < rtol )
0 commit comments