Halving the Interval#
Minimum found in slow but steady method#
Just as with root finding we can apply interval halving to find extrema. Within the method the testing is changed, since the evaluated function seeks to find values of the same sign (positive for minima and negative for maxima when the function is above the x-axis). In general solutions in the first quadrant, where x and y are positive, is of more interest than in the other quadrants.
The main difference between this and root finder is at the conditional statement:
# root finding
if Fa*F1 < 0:
b = x1
Fb = F1
else:
a = x1
Fa = F1
# minimum finding
if Fa < Fb:
b = x1
Fb = F1
else:
a = x1
Fa = F1
The result of finding the minimum is as follows:
+------+-----+-----------+-----------+-------------+-------------+------------+-----------+
| step | a | b | x1 | f(a) | f(b) | f(x1) | emax |
+------+-----+-----------+-----------+-------------+-------------+------------+-----------+
| 0 | 1 | 5 | 3.0 | 106.2831853 | 177.0796327 | 89.8820011 | 2.0 |
| 1 | 1 | 3.0 | 2.0 | 106.2831853 | 89.8820011 | 75.1327412 | 1.0 |
| 2 | 2.0 | 3.0 | 2.5 | 75.1327412 | 89.8820011 | 79.2699082 | 0.5 |
| 3 | 2.0 | 2.5 | 2.25 | 75.1327412 | 79.2699082 | 76.2530701 | 0.25 |
| 4 | 2.0 | 2.25 | 2.125 | 75.1327412 | 76.2530701 | 75.4313322 | 0.125 |
| 5 | 2.0 | 2.125 | 2.0625 | 75.1327412 | 75.4313322 | 75.2129297 | 0.0625 |
| 6 | 2.0 | 2.0625 | 2.03125 | 75.1327412 | 75.2129297 | 75.1550445 | 0.03125 |
| 7 | 2.0 | 2.03125 | 2.015625 | 75.1327412 | 75.1550445 | 75.1393774 | 0.015625 |
| 8 | 2.0 | 2.015625 | 2.0078125 | 75.1327412 | 75.1393774 | 75.1349217 | 0.0078125 |
| 9 | 2.0 | 2.0078125 | 2.0039062 | 75.1327412 | 75.1349217 | 75.133546 | 0.0039062 |
| 10 | 2.0 | 2.0039062 | 2.0019531 | 75.1327412 | 75.133546 | 75.1330721 | 0.0019531 |
| 11 | 2.0 | 2.0019531 | 2.0009766 | 75.1327412 | 75.1330721 | 75.1328888 | 0.0009766 |
| 12 | 2.0 | 2.0009766 | 2.0004883 | 75.1327412 | 75.1328888 | 75.1328105 | 0.0004883 |
| 13 | 2.0 | 2.0004883 | 2.0002441 | 75.1327412 | 75.1328105 | 75.1327748 | 0.0002441 |
| 14 | 2.0 | 2.0002441 | 2.0001221 | 75.1327412 | 75.1327748 | 75.1327577 | 0.0001221 |
| 15 | 2.0 | 2.0001221 | 2.000061 | 75.1327412 | 75.1327577 | 75.1327494 | 6.1e-05 |
+------+-----+-----------+-----------+-------------+-------------+------------+-----------+
Minimum found : at 2.0000305 value 75.1327494 in 15 steps
The root was found in 15 iterations, but it did not change the lower limit for most of the computation.
Show/Hide Code my_half.py
# halving intervals
from numpy import linspace, pi
from prettytable import PrettyTable
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme()
f = lambda x: 2 * (pi*x**2 + 50/x) # (x-4)**2 #
x = PrettyTable(['step', 'a', 'b', 'x1', 'f(a)', 'f(b)', 'f(x1)', 'emax'])
a = 1 # 1 # 2
b = 5 # 4 # 5
Fa = f(a)
Fb = f(b)
step = 0
xp = linspace(a,b)
while abs(b - a)/2 >= 5e-5:
x1=(a+b)/2
F1 = f(x1)
# check whether the limits bracket the minimum
if Fa >= F1 and Fb >= F1:
pass
elif x1 <= a + Fa * (b - a)/(Fb - Fa):
pass
else:
linear = a + Fa * (b - a)/(Fb - Fa)
print('Limits not bracketing minimum, lower {}, calculated {}, upper {}'.format(a,x1,b))
print(f'Values lower {Fa:.4f}, calculated {F1:.4f}, upper {Fb:.4f}')
print(f'Linear position {linear:.4f}')
break
x.add_row([step, round(a,7), round(b,7), round(x1,7), round(Fa,7), round(Fb,7), round(F1,7), round(abs(b - a)/2,7)])
if Fa < Fb:
b = x1
Fb = F1
else:
a = x1
Fa = F1
step += 1
if step > 19:
break
print(x)
print(f'Minimum found : at {(a+b)/2:.7f} value {F1:.7f} in {step-1} steps')
plt.plot(xp, f(xp), label='function')
plt.scatter((a+b)/2, F1 , marker='P', color='r', label='minimum')
plt.text((a+b)/2-0.38, F1+5, (round((a+b)/2,2), round(F1,4)))
plt.xlabel(r'$x$')
plt.ylabel(r'$f(x)$')
plt.title('Optimisation by Interval Halving \n on function $2\cdot(\pi\cdot r^2 + 50/r)$')
plt.legend()
plt.show()
#plt.savefig('../../figures/half_opt.png')
In the next script an extra level of testing is used to avoid the computations sticking on a limit:
+------+-----------+-----------+-----------+------------+-----------+
| step | a | x2 | b | f(x2) | tol |
+------+-----------+-----------+-----------+------------+-----------+
| 0 | 1 | 3.0 | 5 | 89.8820011 | 2.0 |
| 1 | 1 | 2.0 | 3.0 | 75.1327412 | 1.0 |
| 2 | 1.5 | 2.0 | 2.5 | 75.1327412 | 0.5 |
| 3 | 1.75 | 2.0 | 2.25 | 75.1327412 | 0.25 |
| 4 | 1.875 | 2.0 | 2.125 | 75.1327412 | 0.125 |
| 5 | 1.9375 | 2.0 | 2.0625 | 75.1327412 | 0.0625 |
| 6 | 1.96875 | 2.0 | 2.03125 | 75.1327412 | 0.03125 |
| 7 | 1.984375 | 2.0 | 2.015625 | 75.1327412 | 0.015625 |
| 8 | 1.9921875 | 2.0 | 2.0078125 | 75.1327412 | 0.0078125 |
| 9 | 1.9921875 | 1.9960938 | 2.0 | 75.1325097 | 0.0039062 |
| 10 | 1.9941406 | 1.9960938 | 1.9980469 | 75.1325097 | 0.0019531 |
| 11 | 1.9951172 | 1.9960938 | 1.9970703 | 75.1325097 | 0.0009766 |
| 12 | 1.9960938 | 1.996582 | 1.9970703 | 75.1325072 | 0.0004883 |
| 13 | 1.9963379 | 1.996582 | 1.9968262 | 75.1325072 | 0.0002441 |
| 14 | 1.9963379 | 1.99646 | 1.996582 | 75.132507 | 0.0001221 |
| 15 | 1.9963989 | 1.99646 | 1.996521 | 75.132507 | 6.1e-05 |
| 16 | 1.9964294 | 1.99646 | 1.9964905 | 75.132507 | 3.05e-05 |
+------+-----------+-----------+-----------+------------+-----------+
Minimum found : at 1.9964600 value 75.1325070 in 16 steps
There was no discernable improvement, but the computations were not stuck on a single limit and should be better for other functions.
Show/Hide Code half_cylinder_rev.py
# https://arxiv.org/ftp/arxiv/papers/1903/1903.07117.pdf
# find minimum area given volume of a cylinder
'''
V = π r² h
A = 2 π r h + 2 π r²
A = 2 π r (h + r) = 2 π r (r + V / π r²) = 2 π r² + 2 V / r
'''
from math import pi
from prettytable import PrettyTable
f = lambda r: 2 * (pi*r**2 + 50/r) # (r-4)**2 #
x = PrettyTable(['step', 'a', 'x1', 'x2', 'b', 'f(x1)', 'f(x2)', 'emax'])
a = 1 # 2
b = 5 # 5.0
x2 = (a + b)/2
F2 = f(x2)
step = 0
while abs(b - a)/2 >= 5e-5:
x1 = (a + x2)/2
F1 = f(x1)
x.add_row([step, round(a,7), round(x1,7), round(x2,7), round(b,7), round(F1,7), round(F2,7), round(abs(b - a)/2,7)])
if F1 <= F2:
b = x2
x2 = x1
F2 = F1
else:
x3 = (x2 + b)/2
F3 = f(x3)
if F2 <= F3:
a = x1
b = x3
else:
a = x2
x2 = x3
F2 = F3
step += 1
if step > 19:
break
print(x)
print(f'Minimum found : at {(a+b)/2:.7f} value {F1:.7f} in {step} steps')