def find_root_bisect(function, x0, x1, eps=1e-5, max_iter=10000, debug=False):
assert x0 < x1
assert function(x0) < 0, "need monotonous growing function + suitable x values"
assert function(x1) > 0, "need monotonous growing function + suitable x values"
iter_count = 0
while x1 - x0 > eps and iter_count < max_iter:
x_mid = (x0 + x1) / 2
f_mid = function(x_mid)
if debug:
print("{:4d} x_mid={:+.5e} f(x_mid)={:+.5e}".format(iter_count, x_mid, f_mid))
if f_mid < 0:
x0 = x_mid
else:
x1 = x_mid
iter_count += 1
if x1 - x0 > eps: # iteration failed
return None
return (x0 + x1) / 2.0
def x_squared_minus_2(x):
return x * x - 2
print(find_root_bisect(x_squared_minus_2, 0, 2, debug=True))
Python also knows "anoymous functions", so called "lambda" functions. The are intended to be passed around, and are restricted compared to regular functions (eg no "for", "if", ..):
Syntax is lambda <ARGS>: <RETURN_VALUE>
:
print(find_root_bisect(lambda x: x**2 - 2, 0, 2))
print(lambda a, b: a + b)
print((lambda a, b: a + b)(1, 2))
def find_root_newton(function, derivative, x0, eps=1e-5, max_iter=10, debug=False):
iter_count = 0
f0 = function(x0)
while abs(f0) > eps and iter_count < max_iter:
if debug:
print("{:4d} x0={:+.5e} f(x0)={:+.5e}".format(iter_count, x0, f0))
x0 = x0 - f0 / derivative(x0)
iter_count += 1
f0 = function(x0)
if abs(function(x0)) > eps: # iteration failed
return None
return x0
def x_squared_minus_2(x):
return x * x - 2
def two_x(x):
return 2 * x
print(find_root_newton(x_squared_minus_2, two_x, 10, debug=True))
print(find_root_newton(lambda x: x*x - 2, lambda x: 2*x, 10, debug=True))
import random
import math
def montecarlo_pi(n_points):
hits = 0
for i in range(n_points):
x = random.random()
y = random.random()
if math.hypot(x, y) <= 1:
hits += 1
return hits / n_points * 4
for n_points in (10, 100, 1000, 10000, 100000, 1000000):
pi_approx = montecarlo_pi(n_points)
err = abs(math.pi - pi_approx)
print("{:7d} {:.8f} {:.5e}".format(n_points, pi_approx, err))
def leibnitz_pi(n_summands):
sum_ = 0.0
sign = 1
for i in range(n_summands):
sum_ += sign / (2 * i + 1)
sign = -sign
return sum_ * 4
for n_summands in (10, 100, 1000, 10000, 100000):
pi_approx = leibnitz_pi(n_summands)
err = abs(math.pi - pi_approx)
print("{:6d} {:.8f} {:.5e}".format(n_summands, pi_approx, err))
We use $$ \sum_1^n \frac{1}{n^4} \to \frac{\pi^4}{90} $$
def leibnitz_bessel(n_summands):
sum_ = 0.0
for i in range(1, n_summands):
sum_ += 1 / (i ** 4)
return (90 * sum_) ** .25
for n_summands in (10, 100, 1000, 10000, 100000):
pi_approx = leibnitz_bessel(n_summands)
err = abs(math.pi - pi_approx)
print("{:6d} {:.8f} {:.5e}".format(n_summands, pi_approx, err))