How to find root of a function accepting error on f(x) instead of x?

Viewed 127

I have a function like e. g.:

amountOfCalls = 0

def myFunc(x):
    global amountOfCalls
    amountOfCalls+=1
    y = x ** 3 + x -5
    return y

and want to find the root of it (i. e.: find the value of x where f(x) == 0).

If I use one of scipy optimizers like ridder, I can set only the relative or absolute tolerance of x but not of f(x).

Is there any possibility/alternative, where I could tell the optimizer to stop when abs(f(x)) has become smaller than e.g. 0.2 (as an absolute tolerance)?

Remark: As my function is in reality a black-box function, I have to stick to an optimizing approach (so analytical alternatives unfortunately do not work).

2 Answers

You could use the scipy.optimize.root for achieving this as described here.

You could use any of the mentioned methods on the above-mentioned link. I am choosing broyden1. In this method, you can set fatol to 0.2 as it is the absolute tolerance for the residual.

from scipy.optimize import root

amountOfCalls = 0

def myFunc(x):
    global amountOfCalls
    amountOfCalls+=1
    y = x ** 3 + x -5
    return y

x0 = 3
res = root(myFunc, x0, method='broyden1', options={'disp':True, 'fatol':0.2})
print(res)

The above code will terminate when myFunc(x) < 0.2.

If you want your condition to be explicitly abs(f(x)) < 0.2, then you could use the callback function as it is called at each iteration. You can then check at each iteration, if the condition is met and raise an Error to stop the root-finding method as follows:

from scipy.optimize import root

class Error(Exception):
    pass

class AlmostReachedZeroError(Error):
    pass

class rootFinder():

    def __init__(self):
        self.amountOfCalls = 0

    def fun(self, x0):
        y = x0 ** 3 + x0 - 5
        return y

    def callback(self, x, f):
        # callback to terminate if condition is met
        self.amountOfCalls+=1

        if abs(self.fun(x[0])) < 0.2:
            print("Current x: ", x[0], "Current fval: ", f[0],
                  "Iterations: ", self.amountOfCalls)
            raise AlmostReachedZeroError("Terminating optimization: limit reached")

    def find_root(self):
        x0 = 3
        opt = root(self.fun, x0, method='broyden1', callback=self.callback,
                   options={'disp':True, 'ftol':0.2})
        return opt

rf = rootFinder()
res = rf.find_root()
print(res)

The above code will terminate the root-finding method if abs(f(x)) < 0.2, and will produce an Error. Also, the current x value, the current fval, and the iterations that the method took will be printed.

I might be misunderstanding your question, but this should do the trick:

    global amountOfCalls
    amountOfCalls+=1
    y = x ** 3 + x -5
    if y == 0:
        return x
    return y
Related