2

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).

Muhammad Mohsin Khan
  • 1,444
  • 7
  • 16
  • 23
1018307
  • 111
  • 1
  • 7

2 Answers2

2

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.

Muhammad Mohsin Khan
  • 1,444
  • 7
  • 16
  • 23
  • 1
    Very nice, thank you. I think that even your first code example is usable for my initial question, when we wrap `myFunc()` with another function that delivers `abs(myFunc())`. But the hint for the callback is very helpful indeed! – 1018307 Jan 19 '22 at 16:53
  • Yes, the first approach is much simpler but with _callback_, you can do much more. – Muhammad Mohsin Khan Jan 19 '22 at 17:11
0

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
Ayush
  • 86
  • 7
  • 1
    Yes, it seems to me that you misunderstood the question. The function will always return y. The question is just how to stop the optimizing algorithm once `f(x)` is close enough to `0`. – 1018307 Jan 18 '22 at 06:06