Skip to content

Commit

Permalink
FitResult.cost: return float instead of list, correct values for half…
Browse files Browse the repository at this point in the history
…-factor in scipy.optimize.least_squares (#81, #80)
  • Loading branch information
luisfabib committed Mar 7, 2021
1 parent 530a59c commit e140021
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 5 deletions.
4 changes: 2 additions & 2 deletions deerlab/fitparamodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ def lsqresiduals(p):
sol = least_squares(lsqresiduals ,par0, bounds=(lb,ub), max_nfev=int(maxiter), ftol=tol, method='dogbox')
sols.append(sol)
parfits.append(sol.x)
fvals.append(sol.cost)
fvals.append(2*sol.cost) # least_squares uses 0.5*sum(residual**2)

# Find global minimum from multiple runs
globmin = np.argmin(fvals)
Expand Down Expand Up @@ -255,7 +255,7 @@ def lsqresiduals(p):
if Nsignals==1:
stats = stats[0]
scales = scales[0]

fvals = fvals[0]
# Get plot function
plotfcn = lambda: _plot(Vsubsets,V,Vfit)

Expand Down
3 changes: 2 additions & 1 deletion deerlab/snlls.py
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ def ResidualsFcn(p):
sol = least_squares(ResidualsFcn, par0, bounds=(lb, ub), max_nfev=int(nonlin_maxiter), ftol=nonlin_tol)
nonlinfits.append(sol.x)
linfits.append(linfit)
fvals.append(sol.cost)
fvals.append(2*sol.cost) # least_squares uses 0.5*sum(residual**2)
sols.append(sol)
# Find global minimum from multiple runs
globmin = np.argmin(fvals)
Expand Down Expand Up @@ -418,6 +418,7 @@ def ci(coverage,ptype='full'):
stats.append(goodness_of_fit(y[subset], yfit[subset], Ndof))
if len(stats) == 1:
stats = stats[0]
fvals = fvals[0]

# Display function
plotfcn = lambda: _plot(subsets,y,yfit)
Expand Down
15 changes: 15 additions & 0 deletions test/test_fitmultimodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,3 +254,18 @@ def test_globalfit_scales():
assert max(abs(np.asarray(scales)/np.asarray(fit.scale) - 1)) < 1e-2
#=======================================================================

def test_cost_value():
#=======================================================================
"Check that the fit of a multi-Gauss model works"

r = np.linspace(2,6,300)
t = np.linspace(-0.5,6,500)
K = dipolarkernel(t,r)
parin = [4, 0.05, 0.4, 4, 0.4, 0.4, 3, 0.15, 0.2]
P = dd_gauss3(r,parin)
V = K@P

fit = fitmultimodel(V,K,r,dd_gauss,3,'aicc', uqanalysis=False)

assert isinstance(fit.cost,float) and np.round(fit.cost/np.sum(fit.residuals**2),5)==1
#=======================================================================
22 changes: 22 additions & 0 deletions test/test_fitparamodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -224,4 +224,26 @@ def Vmodel(par):
fit = fitparamodel([V1,V2],Vmodel,par0,lb,ub,weights=[1,1])

assert max(abs(np.asarray(scales)/np.asarray(fit.scale) - 1)) < 1e-2
#============================================================


def test_cost_value():
#============================================================
"Check that the cost value is properly returned"

t = np.linspace(0,3,300)
r = np.linspace(2,5,200)
par = np.array([3,0.2])
P = dd_gauss(r,par)

K = dipolarkernel(t,r)
V = K@P

par0 = [5, 0.5]
lb = [1, 0.1]
ub = [20, 1]
model = lambda p: K@dd_gauss(r,p)
fit = fitparamodel(V,model,par0,lb,ub)

assert isinstance(fit.cost,float) and np.round(fit.cost/np.sum(fit.residuals**2),5)==1
#============================================================
15 changes: 15 additions & 0 deletions test/test_fitregmodel.py
Original file line number Diff line number Diff line change
Expand Up @@ -390,3 +390,18 @@ def test_globalfit_scales():

assert max(abs(np.asarray(scales)/np.asarray(fit.scale) - 1)) < 1e-2
#============================================================

def test_cost_value():
#============================================================
"Check that the cost value is properly returned"

np.random.seed(1)
t = np.linspace(-2,4,300)
r = np.linspace(2,6,100)
P = dd_gauss(r,[3,0.2])
K = dipolarkernel(t,r)
V = K@P + whitegaussnoise(t,0.01)
fit = fitregmodel(V,K,r)

assert isinstance(fit.cost,float) and np.round(fit.cost/np.sum(fit.residuals**2),5)==1
#============================================================
20 changes: 19 additions & 1 deletion test/test_fitsignal.py
Original file line number Diff line number Diff line change
Expand Up @@ -508,4 +508,22 @@ def test_V_scale_regularized():
fit = fitsignal(V,t,r,'P',None,None,uqanalysis=False)

assert max(abs(1 - V/fit.V)) < 1e-4
# ======================================================================
# ======================================================================

def test_cost_value():
# ======================================================================
"Check that starts values can be correctly specified"

t = np.linspace(0,5,100)
r = np.linspace(2,6,150)
P = dd_gauss(r,[4.5, 0.25])

Bmodel = lambda t: bg_exp(t,0.4)
K = dipolarkernel(t,r,0.4,Bmodel)
V = K@P

fit = fitsignal(V,t,r,'P',bg_exp,ex_4pdeer,uqanalysis=False)

assert isinstance(fit.cost,float) and np.round(fit.cost/np.sum(fit.residuals**2),5)==1
# ======================================================================

31 changes: 30 additions & 1 deletion test/test_snlls.py
Original file line number Diff line number Diff line change
Expand Up @@ -352,4 +352,33 @@ def test_reg_huber():
"Check SNNLS using Tikhonov regularization works"

assert_reg_type(regtype='huber')
#=======================================================================
#=======================================================================


def test_cost_value():
#============================================================
"Check that the cost value is properly returned"

# Prepare test data
r = np.linspace(1,8,80)
t = np.linspace(0,4,200)
lam = 0.25
K = dipolarkernel(t,r,lam)
parin = [3.5, 0.4, 0.6, 4.5, 0.5, 0.4]
P = dd_gauss2(r,parin)
V = K@P

# Non-linear parameters
# nlpar = [lam]
nlpar0 = 0.2
lb = 0
ub = 1
# Linear parameters: non-negativity
lbl = np.zeros(len(r))
ubl = np.full(len(r), np.inf)
# Separable LSQ fit

fit = snlls(V,lambda lam: dipolarkernel(t,r,lam),nlpar0,lb,ub,lbl,ubl)

assert isinstance(fit.cost,float) and np.round(fit.cost/np.sum(fit.residuals**2),5)==1
#============================================================

0 comments on commit e140021

Please sign in to comment.