Secant Method

import math
import numpy as np
from matplotlib import pyplot as plt

Let $g(x)$ be a function given by

$$ \begin{equation*} e^{x^2 + 1} - 2.718 + 0.16969 x - 4.07799 x^2 + 3.3653 x^3 - 4.1279 x^4 \end{equation*} $$

To find maximum of $g(x)$, we need to find roots of $g^{\prime}(x)$, so a can define a function gprime(x)

To find roots and confirm if it is a maxima, we need gdoubleprime(x), as $g(x)$ is at maximum if $g^{\prime\prime}(x)$ is negative

def g(x):
    return np.exp(x**2 + 1) - 2.718 + 0.16969*x - 4.07799*x**2 + 3.3653 * x**3 - \
              4.1279 * x**4

def gprime(x):
    return np.exp(x**2 + 1) * 2 * x + 0.16969 - 2.0 * 4.07799 * x + 3 * 3.3653 * x**2 \
              - 4 * 4.1279 * x**3

def gdoubleprime(x):
    return 2 * (2 * x + 1) * np.exp(x**2 + 1) - 2.0 * 4.07799 + 3 * 2 * 3.3653 * x \
               - 4 * 3 * 4.1279 * x**2

Plot the function over the unit interval

x = np.linspace(0.0, 1.0, 100)
plt.plot(x, g(x))
plt.grid(True)

Sketch of function g(x)

Newton method

def newton_step(x, step_num):
    val = x - gprime(x) / gdoubleprime(x)
    step_num += 1
    print(f"Step Num: {step_num}, x: {val}")
    return val, step_num

Secant method

def secant_step(x1, x2, step_num, f):
    step_num += 1
    val = x2 - f(x2) * (x2 - x1) / (f(x2) - f(x1))
    print(f"Step Num: {step_num}, x: {val}")
    return x2, val, step_num

Use secant method here because we can bound it.

def calculate_first_max(x1, x2, nmax = 20):
    step_num = 0
    for i in range(nmax):
        x1, x2, step_num = secant_step(x1, x2, step_num, gprime)
    return x2

def calculate_second_max():
    step_num = 0
    x1 = 0.
    x2 = 0.7
    for i in range(6):
        x1, x2, step_num = secant_step(x1, x2, step_num, gprime)
    return x2
print("Calculating x for first maximum in x -> [0, 1], in interval [0, 0.2]:")
max_x_1 = calculate_first_max(0, 0.2, 19)
print("First maximum value: ", g(max_x_1))
Calculating x for first maximum in x -> [0, 1], in interval [0, 0.2]:
Step Num: 1, x: 0.14900380590331655
Step Num: 2, x: -0.08821605866212776
Step Num: 3, x: 0.12814220849590097
Step Num: 4, x: 0.11341387156881519
Step Num: 5, x: 0.0806834268432286
Step Num: 6, x: 0.09017014204250429
Step Num: 7, x: 0.08892692738129448
Step Num: 8, x: 0.08886390723627205
Step Num: 9, x: 0.08886440328278541
Step Num: 10, x: 0.08886440309559314
Step Num: 11, x: 0.08886440309559253
Step Num: 12, x: 0.0888644030955926
Step Num: 13, x: 0.08886440309559303
Step Num: 14, x: 0.08886440309559265
Step Num: 15, x: 0.08886440309559258
Step Num: 16, x: 0.08886440309559261
Step Num: 17, x: 0.0888644030955926
Step Num: 18, x: 0.08886440309559261
Step Num: 19, x: 0.08886440309559261
First maximum value:  0.006812940563426053
print("Calculating x for second maximum in x -> [0, 1], in interval [0.6, 0.7]:")
max_x_2 = calculate_second_max(0.6, 0.7, 6)
print("Second maximum value: ", g(max_x_2))
Calculating x for second maximum in x -> [0, 1], in interval [0.6, 0.7]:
Step Num: 1, x: 0.6307415305490232
Step Num: 2, x: 0.6353034667646523
Step Num: 3, x: 0.6358951723781623
Step Num: 4, x: 0.6358866382102938
Step Num: 5, x: 0.6358866518459403
Step Num: 6, x: 0.6358866518462564
Second maximum value:  0.004225969342977254
plt.plot(x, g(x))
plt.plot(max_x_1, g(max_x_1), '*')
plt.plot(max_x_2, g(max_x_2), 'o')
plt.grid(True)

Sketch of function g(x) with local maxima