'''Newton's method for arbitrary differentiable functions.

Input: 
    fun ... function
    dfun ... derivative of the function
    x0 ... starting value
    tol ... desired accuracy of f(xi): computation stops if |f(xi)| <= tol
    maxk ... maximal number of iterations; program terminates after maxk iterations

Output: 
    root xi and sequence of approximations X

Typical calls of program: 
    
Example 1: 
    from python08_ex9 import newton
    import numpy as np 
    newton(np.sin,np.cos,2.0)  # newton(fun, dfun, x0, tol, maxk)

Example 2:
    from python08_ex9 import newton
    from python08_examplefun import f1, df1 
    newton(f1,df1,2.0)     # newton(fun, dfun, x0, tol, maxk)

Example 3:
    from python08_ex9 import newton
    from python08_examplefun import f2, df2 
    newton(f2,df2,2.0)    # newton(fun, dfun, x0, tol, maxk)

'''

def newton(fun, dfun, x0 = 0, tol = 1e-12, maxk = 100):
    
    x = x0
    X = [x] 
    
    for k in range(maxk):
        x -= fun(x)/dfun(x)
        X += [x]
       
        '''If the error is small enough, we stop'''
        if abs(fun(x)) < tol:
           break
    xi = x

    print('root = ', xi)
    print('convercence history = ', X)