1

In my situation, the objective function is a numerical process contains a root finding process for an equation by bisection method. With certain set of parameters, the equation does not have root for a intermediate variable. I thought making the bisection root finding routine return None can solve such problem. As the object function with a set of date being regressed by scipy.optimize.curve_fit with p0 separate by this situation in between, error is then stop the process.

To study this case, a simplified case is shown.

import numpy as np

#Define object function:
def f(x,a1,a2):
    if a1 < 0:
        return None
    elif a2 < 0:
        return np.inf
    else:
        return a1 * x**2 + a2

#Making data:
x = np.linspace(-5,5,10)
i = 0
y = np.empty_like(x)
for xi in x:
    y[i] = f(xi,1,1)
    i += 1

import scipy.optimize as sp

para,pvoc = sp.curve_fit(f,x,y,p0=(-1,1))
#TypeError: unsupported operand type(s) for -: 'NoneType' and 'float'

para,pvoc = sp.curve_fit(f,x,y,p0=(1,-1))
#RuntimeError: Optimal parameters not found: Number of calls to function has reached maxfev = 600.

I also tried inf, and it is obviously not working. What should I return to continue the curve_fit process? Imagine it is trying to converge, what happen does the curve_fit do when it meets such situation.

Additional thinking: I tried the try...except... to ignore the error and also simulate a case that the p0 is in a solvable range, but will pass the unsolvable segment to the true fit.

import numpy as np

def f(x,a1,a2):
    if a1 < 0:
        return None
    elif a1 < 2:
        return a1 * x**2 + a2
    elif a2 < 0:
        return np.inf
    else:
        return a1 * x**2 + a2

def ff(x,a1,a2):
    output = f(x,a1,a2)
    if output == None:
        return 0
    else:
        return output

x = np.linspace(-5,5,10)
i = 0
y = np.empty_like(x)
for xi in x:
    y[i] = f(xi,1,1)
    i += 1


import scipy.optimize as sp

#para,pvoc = sp.curve_fit(f,x,y,p0=(-1,1))
#TypeError: unsupported operand type(s) for -: 'NoneType' and 'float':
#para,pvoc = sp.curve_fit(f,x,y,p0=(1,-1))

try:
    para,pvoc = sp.curve_fit(f,x,y,p0=(-3,1))
except TypeError:
    pass

Obviously error was met during converging and had been reported and was excepted. What should I do to continue the curve_fit with the original converging direction? Even I can make concession, how can I tell the curve_fit to return the last attempt to the a1?

On the other hand, I tried put this try... except... in the object function to return 0 when there is the error. The result is then as I expect:

para,pvoc = sp.curve_fit(ff,x,y,p0=(-3,1))

#OptimizeWarning: Covariance of the parameters could not be estimated
  category=OptimizeWarning)
Mr. T
  • 11,960
  • 10
  • 32
  • 54
Zhidong Li
  • 61
  • 1
  • 9
  • One possibility is to try the "brick wall" method, where the function returns a very large value - and therefore a very large error - when a condition occurs, rather than returning np.inf or None. This is somewhat crude, yet has the practical advantage of being easily coded and easily tested. – James Phillips May 28 '18 at 11:42
  • I don't know if I need to open a new question. I just realised. This phenomenon is probably caused by: the independent variable of the object function is actually one of the dependent variable of the experiments where the datas come from. As a result of that, there are some data points scatter into a place that is not solvable by the model with the current parameters while converging, are now put into curfit as independent variables. In fact, changing the model to fit such condition is impossible as one input lead to multiple output. So the original question is actually not precise. – Zhidong Li May 29 '18 at 10:02

1 Answers1

0

I think you want to take a different approach. That is, you have written your objective function to return None or Inf to signal when the value for a1 or a2 is out of bounds: a1<0 and a2<0 are not acceptable values for the objective function.

If that is a correct interpretation of what you are trying to do, it would be better to place bounds on both a1 and a2 so that the objective function never gets those values at all. To do that with curve_fit you would need to create a tuple of arrays for lower and upper bounds, with an order matching your p0, so

pout, pcov = sp.curve_fit(f, x, y, p0=(1, 1), bounds=([0, 0], [np.inpf, np.inf])

As an aside: I have no idea why you're using a starting value for a1 that is < 0, and so out of bounds. That seems like you're asking for trouble.

For an even better experience setting bounds on fitting parameters, you might consider using the lmfit, which would allow you to write:

import numpy as np
from lmfit import Model

def f(x, a1, a2):
    return a1 * x**2 + a2

fmod = Model(f)

params = fmod.make_params(a1=1, a2=0.5)
params['a1'].min = 0
params['a2'].min = 0

x = np.linspace(-5, 5, 10)

np.random.seed(0)
y = f(x, 1, 1) + np.random.normal(size=len(x), scale=0.02)

result = fmod.fit(y, params, x=x)
print(result.fit_report())

which will print out

[[Model]]
    Model(f)
[[Fit Statistics]]
    # fitting method   = leastsq
    # function evals   = 13
    # data points      = 10
    # variables        = 2
    chi-square         = 0.00374066
    reduced chi-square = 4.6758e-04
    Akaike info crit   = -74.9107853
    Bayesian info crit = -74.3056151
[[Variables]]
    a1:  0.99998038 +/- 7.6225e-04 (0.08%) (init = 1)
    a2:  1.01496025 +/- 0.01034565 (1.02%) (init = 0.5)
[[Correlations]] (unreported correlations are < 0.100)
    C(a1, a2) = -0.750

Hope that helps.

M Newville
  • 7,486
  • 2
  • 16
  • 29
  • Are you a contributor to the lmfit package you are recommending? If so you should disclose this in your answer. – James Phillips May 29 '18 at 00:01
  • I am the main author of lmfit. I have contributed to other open source projects as well. My identity is not secret. I do not use an alias. On SO, I have answered many questions about curve-fitting, python, and related topics. It appears my answers are not to everyone's liking, though I've never heard a complaint from an actual questioner -- only in comments. I can certainly see why people get burnt out with the inherently competitive nature of SO, and am perfectly happy to take this as an opportunity to devote much less energy answering questions here. Cheers! – M Newville May 29 '18 at 02:28
  • Thank you for your answer. In fact, the original object function is a theoretical analysis of real life problem that, if the parameter set is within a certain range, the behaviour would be unrealistic or undefined. It often shows that this range is unknown. But anyway, the bounds are unknown in the cases of datas. So far, I did manage to get the parameters if the p0 is within the range when fortunate. I am thinking make the curvefit in a function by trying different p0, but it takes extremely long. And the model will get further developed, datas varies, I hope this process is stable. – Zhidong Li May 29 '18 at 08:24