Bisection method for finding different valued roots in Python - python

I am trying to implement the bisection method for finding different valued roots. Lets say I have y = 2x + 1, what value of x will give me y = 1.
I am not sure how to phrase the if statement in the while loop. This just converges on 'a' as the answer.
def f(x):
return 2*x + 1
def bisection_method(f, a, b, tol):
if f(a) > 1 and f(b) > 1:
print("No root found.")
else:
iter = 0
while (b - a)/2.0 > tol:
print(a, b, (b-a)/2)
midpoint = (a + b)/2.0
if f(a) < 1:
b = midpoint
else:
a = midpoint
iter += 1
return midpoint, iter
x, its = bisection_method(f, -10, 10, 0.000001)
print(x, its)

To fix your code you sahouls change if to
...
if f(midpoint) > 1:
b = midpoint
else:
a = midpoint
...
But this will only work for increasing functions f, ie that cross 1 from below to above. To handle decreasing functions as well you need to modify the code in a couple of places. Yo ubasically want to find a subinterval on which f switches from being above 1 to below 1, or vice versa
def bisection_method(f, a, b, tol):
if (f(a) - 1) *(f(b) - 1) > 0: # change here
print("No root found.")
else:
iter = 0
while (b - a)/2.0 > tol:
# print(a, b, (b-a)/2)
midpoint = (a + b)/2.0
if (f(a) -1)*(f(midpoint)-1) <= 0: # change here
b = midpoint
else:
a = midpoint
iter += 1
return midpoint, iter

Related

minimal absolute value of the difference between A[i] and B[i] (array A is strictly increasing, array B is strictly decreasing)

Given two sequences A and B of the same length: one is strictly increasing, the other is strictly decreasing.
It is required to find an index i such that the absolute value of the difference between A[i] and B[i] is minimal. If there are several such indices, the answer is the smallest of them. The input sequences are standard Python arrays. It is guaranteed that they are of the same length. Efficiency requirements: Asymptotic complexity: no more than the power of the logarithm of the length of the input sequences.
I have implemented index lookup using the golden section method, but I am confused by the use of floating point arithmetic. Is it possible to somehow improve this algorithm so as not to use it, or can you come up with a more concise solution?
import random
import math
def peak(A,B):
def f(x):
return abs(A[x]-B[x])
phi_inv = 1 / ((math.sqrt(5) + 1) / 2)
def cal_x1(left,right):
return right - (round((right-left) * phi_inv))
def cal_x2(left,right):
return left + (round((right-left) * phi_inv))
left, right = 0, len(A)-1
x1, x2 = cal_x1(left, right), cal_x2(left,right)
while x1 < x2:
if f(x1) > f(x2):
left = x1
x1 = x2
x2 = cal_x1(x1,right)
else:
right = x2
x2 = x1
x1 = cal_x2(left,x2)
if x1 > 1 and f(x1-2) <= f(x1-1): return x1-2
if x1+2 < len(A) and f(x1+2) < f(x1+1): return x1+2
if x1 > 0 and f(x1-1) <= f(x1): return x1-1
if x1+1 < len(A) and f(x1+1) < f(x1): return x1+1
return x1
#value check
def make_arr(inv):
x = set()
while len(x) != 1000:
x.add(random.randint(-10000,10000))
x = sorted(list(x),reverse = inv)
return x
x = make_arr(0)
y = make_arr(1)
needle = 1000000
c = 0
for i in range(1000):
if abs(x[i]-y[i]) < needle:
c = i
needle = abs(x[i]-y[i])
print(c)
print(peak(x,y))
Approach
The poster asks about alternative, simpler solutions to posted code.
The problem is a variant of Leetcode Problem 852, where the goal is to find the peak index in a moutain array. We convert to a peak, rather than min, by computing the negative of the abolute difference. Our aproach is to modify this Python solution to the Leetcode problem.
Code
def binary_search(x, y):
''' Mod of https://walkccc.me/LeetCode/problems/0852/ to use function'''
def f(m):
' Absoute value of difference at index m of two arrays '
return -abs(x[m] - y[m]) # Make negative so we are looking for a peak
# peak using binary search
l = 0
r = len(arr) - 1
while l < r:
m = (l + r) // 2
if f(m) < f(m + 1): # check if increasing
l = m + 1
else:
r = m # was decreasing
return l
Test
def linear_search(A, B):
' Linear Search Method '
values = [abs(ai-bi) for ai, bi in zip(A, B)]
return values.index(min(values)) # linear search
def make_arr(inv):
random.seed(10) # added so we can repeat with the same data
x = set()
while len(x) != 1000:
x.add(random.randint(-10000,10000))
x = sorted(list(x),reverse = inv)
return x
# Create data
x = make_arr(0)
y = make_arr(1)
# Run search methods
print(f'Linear Search Solution {linear_search(x, y)}')
print(f'Golden Section Search Solution {peak(x, y)}') # posted code
print(f'Binary Search Solution {binary_search(x, y)}')
Output
Linear Search Solution 499
Golden Section Search Solution 499
Binary Search Solution 499

How to make abs() , sqrt() (from sympy) compare with value?

I am try to check the intervals over which the function is increasing and the intervals where it is decreasing (The intervals only check in (-oo;0) or (0;+oo) or Real number). Of course my code only serve in simple function such as:
(x^(-5)) ; (x^3 + x) ; (x^4 + 3(x^2) - 1)
The problem here I must input all this function: (sqrt(abs(x))) ; (1/(abs(x)))
When I input all that, I realize when the code run to compare calculation --> It cannot compare and It's error
Example: - Input: (sympy.sqrt(sympy.abs(x)))(I comment in my code where the error take from) - Ouput: Error
import sympy
import math
import inspect
import matplotlib.pyplot as plt
import numpy as np
def check_inc_des(f, x_min=-100, x_max=100, n_sample=100):
x = sympy.symbols('x')
expr = f(x)
limit_expr = sympy.limit(expr, x, 0)
if limit_expr == -math.inf or limit_expr == math.inf:
for k in np.linspace(x_min, -0.5, n_sample):
a = sympy.diff(f(x))
a = a.subs(sympy.Symbol("x"), k)
if a < 0:
b = "The function is decreasing in the interval (-oo;0)"
break
if a > 0:
b = "The function is increasing in the interval (-oo;0)"
break
for k in np.linspace(0.5, x_max, n_sample):
a = sympy.diff(f(x))
a = a.subs(sympy.Symbol("x"), k)
if a > 0:
d = "The function is increasing in the interval (0;+oo)"
break
if a < 0:
d = "The function is decreasing in the interval (0;+oo)"
break
return b + "\n" +" "+ d
else:
for k in np.linspace(x_min, -0.5, n_sample):
a = sympy.diff(f(x))
a = a.subs(sympy.Symbol("x"), k)
if a < 0: #<--------------------------------------------------The error take from here
b = "The function is decreasing in the interval (-oo;0)"
break
if a > 0:
b = "The function is increasing in the interval (-oo;0)"
break
for k in np.linspace(0.5, x_max, n_sample):
h = sympy.diff(f(x))
h = h.subs(sympy.Symbol("x"), k)
if h > 0:
d = "The function is increasing in the interval (0;+oo)"
break
if h < 0:
d = "The function is decreasing in the interval (0;+oo)"
break
if a < 0 and h < 0:
return "The function is decreasing in the Real number"
if a > 0 and h > 0:
return "The function is increasing in the Real number"
return b + "\n"+" "+ d
def do_ex_3(fa):
func_lst = [fa]
for func in func_lst:
print(check_inc_des(func))
return
def f_3x_a(x):
return x**(-5) #<------------------------------------------------------Where I input
do_ex_3(f_3x_a)
My code upper take example for x^(-5): - Input: (x^(-5)) - Ouput: The function is decreasing in the interval (-oo;0)
The function is decreasing in the interval (0;+oo) Thanks for support!

A Normal Distribution Calculator

so im trying to make a program to solve various normal distribution questions with pure python (no modules other than math) to 4 decimal places only for A Levels, and there is this problem that occurs in the function get_z_less_than_a_equal(0.75):. Apparently, without the assert statement in the except clause, the variables all get messed up, and change. The error, i'm catching is the recursion error. Anyways, if there is an easier and more efficient way to do things, it'd be appreciated.
import math
mean = 0
standard_dev = 1
percentage_points = {0.5000: 0.0000, 0.4000: 0.2533, 0.3000: 0.5244, 0.2000: 0.8416, 0.1000: 1.2816, 0.0500: 1.6440, 0.0250: 1.9600, 0.0100: 2.3263, 0.0050: 2.5758, 0.0010: 3.0902, 0.0005: 3.2905}
def get_z_less_than(x):
"""
P(Z < x)
"""
return round(0.5 * (1 + math.erf((x - mean)/math.sqrt(2 * standard_dev**2))), 4)
def get_z_greater_than(x):
"""
P(Z > x)
"""
return round(1 - get_z_less_than(x), 4)
def get_z_in_range(lower_bound, upper_bound):
"""
P(lower_bound < Z < upper_bound)
"""
return round(get_z_less_than(upper_bound) - get_z_less_than(lower_bound), 4)
def get_z_less_than_a_equal(x):
"""
P(Z < a) = x
acquires a, given x
"""
# first trial: brute forcing
for i in range(401):
a = i/100
p = get_z_less_than(a)
if x == p:
return a
elif p > x:
break
# second trial: using symmetry
try:
res = -get_z_less_than_a_equal(1 - x)
except:
# third trial: using estimation
assert a, "error"
prev = get_z_less_than(a-0.01)
p = get_z_less_than(a)
if abs(x - prev) > abs(x - p):
res = a
else:
res = a - 0.01
return res
def get_z_greater_than_a_equal(x):
"""
P(Z > a) = x
"""
if x in percentage_points:
return percentage_points[x]
else:
return get_z_less_than_a_equal(1-x)
print(get_z_in_range(-1.20, 1.40))
print(get_z_less_than_a_equal(0.7517))
print(get_z_greater_than_a_equal(0.1000))
print(get_z_greater_than_a_equal(0.0322))
print(get_z_less_than_a_equal(0.1075))
print(get_z_less_than_a_equal(0.75))
Since python3.8, the statistics module in the standard library has a NormalDist class, so we could use that to implement our functions "with pure python" or at least for testing:
import math
from statistics import NormalDist
normal_dist = NormalDist(mu=0, sigma=1)
for i in range(-2000, 2000):
test_val = i / 1000
assert get_z_less_than(test_val) == round(normal_dist.cdf(test_val), 4)
Doesn't throw an error, so that part probably works fine
Your get_z_less_than_a_equal seems to be the equivalent of NormalDist.inv_cdf
There are very efficient ways to compute it accurately using the inverse of the error function (see Wikipedia and Python implementation), but we don't have that in the standard library
Since you only care about the first few digits and get_z_less_than is monotonic, we can use a simple bisection method to find our solution
Newton's method would be much faster, and not too hard to implement since we know that the derivative of the cdf is just the pdf, but still probably more complex than what we need
def get_z_less_than_a_equal(x):
"""
P(Z < a) = x
acquires a, given x
"""
if x <= 0.0 or x >= 1.0:
raise ValueError("x must be >0.0 and <1.0")
min_res, max_res = -10, 10
while max_res - min_res > 1e-7:
mid = (max_res + min_res) / 2
if get_z_less_than(mid) < x:
min_res = mid
else:
max_res = mid
return round((max_res + min_res) / 2, 4)
Let's test this:
for i in range(1, 2000):
test_val = i / 2000
left_val = get_z_less_than_a_equal(test_val)
right_val = round(normal_dist.inv_cdf(test_val), 4)
assert left_val == right_val, f"{left_val} != {right_val}"
# AssertionError: -3.3201 != -3.2905
We see that we are losing some precision, that's because the error introduced by get_z_less_than (which rounds to 4 digits) gets propagated and amplified when we use it to estimate its inverse (see Wikipedia - error propagation for details)
So let's add a "digits" parameter to get_z_less_than and change our functions slightly:
def get_z_less_than(x, digits=4):
"""
P(Z < x)
"""
res = 0.5 * (1 + math.erf((x - mean) / math.sqrt(2 * standard_dev ** 2)))
return round(res, digits)
def get_z_less_than_a_equal(x, digits=4):
"""
P(Z < a) = x
acquires a, given x
"""
if x <= 0.0 or x >= 1.0:
raise ValueError("x must be >0.0 and <1.0")
min_res, max_res = -10, 10
while max_res - min_res > 10 ** -(digits * 2):
mid = (max_res + min_res) / 2
if get_z_less_than(mid, digits * 2) < x:
min_res = mid
else:
max_res = mid
return round((max_res + min_res) / 2, digits)
And now we can try the same test again and see it passes

LCM using recursive?

Here is my code:
def lcm(a, b):
if b == 0:
return a
return a * b / lcm(a, b)
print lcm(5,3)
This is what I could manage so far, any idea on how to find the LCM (least common multiple) of two numbers using recursive and one function?
We have lcm(a, b) * gcd(a, b) = a * b. So we can write the following equation:
lcm(a, b) = a; if a % b == 0
lcm(a, b) ; if a % b != 0
= a * b / gcd(a, b)
= a * b / gcd(b, a % b)
= a * b / (b * (a % b) / lcm(b, a % b))
= a / (a % b) * lcm(b, a % b)
And translate to Python, we have:
def lcm(a, b):
t = a % b
if t == 0: return a
return a * lcm(b, t) / t
Edit: I didn't read the recursive / one function bit in your question cause I'm dumb. Incorporated now.
The lcm isn't a * b / lcm(a, b), it's a * b / gcd(a, b) (greatest common divisor).
So the cleanest way to do this is:
def gcd(x, y):
while y:
x, y = y, x % y
return x
def lcm(x, y):
return x * y / gcd(x, y)
If you are limited to recursion only (e.g. for an exam) then this doesn't have to be efficient, so you might as well just recursively count up until you find the lowest number that both x and y divide into:
def lcm(x, y, counter=1):
if (counter%x == 0 and counter%y == 0):
return counter
return lcm(x, y, counter+1)
That just increases counter until counter%x == 0 and counter%y == 0 is true, which is the LCM. Don't try it on large numbers though, you'll just get a stack overflow.
As stated in the other answers here lcm = a*b / gcd(a, b)but then you will need to define another function gcd(a, b) for it.
Since you needed only 1 function with recursion, maybe this piece of code will do.
N.B. : This function has one extra parameter c which should be always passed as 1 while calling it outside the function only :
def lcm(a, b, c):
d = c
m = min(a, b)
while m > 1 :
if a%m == 0 and b%m == 0 :
d*=m
return lcm(int(a/m), int(b/m), d)
else:
m-= 1
d*= a*b
return d
Using the mathematical relationship that the product of two numbers is equal to the product of the Greatest Common Divisor and the Least Common Multiplier of those two numbers: A * B = GCD(A,B) * LCM(A,B)
def gcd(a,b):
if a % b == 0: return b
return gcd(b, a % b)
def lcm(a, b):
return ((a*b) // gcd(a,b))
The first function is recursive and it's used to find the Greatest Common Divisor, it has cost O(log(n)).
This should do:
# Python Program to find the L.C.M. of two input number
# define a function
def lcm(x, y):
"""This function takes two
integers and returns the L.C.M."""
# choose the greater number
if x > y:
greater = x
else:
greater = y
while True:
if((greater % x == 0) and (greater % y == 0)):
lcm = greater
break
greater += 1
return lcm
# take input from the user
num1 = int(input("Enter first number: "))
num2 = int(input("Enter second number: "))
print("The L.C.M. of", num1,"and", num2,"is", lcm(num1, num2))
I created my own easy programme.
def lcm(greater,a,b):
# while(True):
if (greater % a == 0 and greater % b == 0):
lcm1 = greater
return lcm1
else:
lcm1=lcm(greater + 1,a,b)
return lcm1
a=int(input(" Enter 1st number :"))
b=int(input(" Enter 2nd number :"))
if(a>b):
greater=a
else:
greater=b
print(lcm(greater,a,b))

How to find a root for a mathematical function using Intermediate value theorem?

Accroding to the Intermediate value theorem for a given function F(x),
I'm supposed to write a function, which gets a mathematical function, two numbers a and b , and an error range, and it gives as an output the number x for which the functions' value for it is close to 0 up to epsilon.
Examples:
>>> find_root(lambda x : x - 1 , -10, 10)
1.0009765625
>>> find_root(lambda x : x**2 , -10, 10)
>>> #returned None
This is the code that I wrote so far, I think I'm on the right way, but I can't figure out what to loop over, I don't get the correct answer with this code.
So what should I fix in it?
def find_root(f, a, b, EPS=0.001):
if (f(a)*f(b))<0:
for i in range(a,b):
if f(i)<EPS:
return (i)
else:
return (None)
Use dichotomy :
def find_root(f, a, b, EPS=0.0001):
if f(a)==0 : return a
if f(b)==0 : return b
if f(a)*f(b)>0 : return None
c=(a+b)/2
while(abs(f(c))>EPS) :
if f(a)*f(c)<0 : b=c
else : a=c
c=(a+b)/2
return c
The simplest solution is this:
def find_root(f, a, b, EPS=0.001):
#assuming a < b
x = a
while x <= b:
if abs(f(x)) < EPS:
return x
else:
x += EPS
Result:
>>>find_root(lambda x: x-1, -10, 10)
0.9999999999998982
>>>find_root(lambda x: x-1, -10, -2)
None
As you know, your procedure cannot find the root if the initial values are both positive or both negative.
Here is a suggestion of how to implement it using a binary search, in order to accelerate the process:
def find_root(f, a, b, EPS=0.001):
fa = f(a)
fb = f(b)
if fa*fb > 0: # both positive or both negative
return None
while abs(fa) > EPS and abs(fb) > EPS:
c = (a+b)/2.0
fc = f(c)
if fa*fc >= 0:
a = c
fa = fc
else:
b = c
fb = fc
if abs(fa) <= EPS:
return a
else:
return b
The return value of find_root(lambda x : x-1, -10, 10) is 1.0009765625.

Categories