Some Approximation Techniques in Python: Numerical Search Methods and Their Applications
Note
Simple Exhaustive Square Roots
We count and then square the result to compare to the absolute value of the number to check.
Testing the Prototype
simple_square_root(x)Count, square and check
Note
Step-by-Step Process:
ans = 0ans² < |x|ans by 1ans² ≥ |x|ans² exactly equals |x|Key Logic: Exhaustive search: Tests every integer sequentially.
If ans² == |x| then the root is found. Cool!
Binary outcome: Either finds exact root or reports failure
simple_square_root(x)Example: For x = 25,
tests: 0^2, 1^2, 2^2, 3^2, 4^2, 5^2 = 25 ✓
Be careful!
Example: For x = 26,
tests: 0^2, 1^2, 2^2, 3^2, 4^2, 5^2, 6^2 != 26
(We just passed the correct value!!)
Simple Exhaustive Cube Roots (with same method)
Testing the Prototype
simple_cube_root(x)Count, cube and check
Note
Step-by-Step Process:
(Same as before)
ans = 0ans^3 < |x|ans by 1ans^3 ≥ |x|ans^3 exactly equals |x|Key Logic: Exhaustive search: Tests every integer sequentially.
If ans^3 == |x| then the root is found. Nifty!
Binary outcome: Either finds exact root or reports failure
simple_cube_root(x)Example: For x = 8,
tests: 0^3, 1^3, 2^3 = 8 ✓
Be careful!
For x = 9, tests: 0^3, 1^3, 2^3, 3^3 != 9
(We just passed the correct value!!)
Exhaustive \(n^{th}\) root
simple_n_root(x)
Add print statements to see steps.
def exhaustive_sqrt(x, epsilon=0.01):
"""
Find square root using exhaustive enumeration
"""
step = epsilon
num_guesses = 0
ans = 0.0
print(f"Finding square root of {x}")
while abs(ans**2 - x) >= epsilon and ans*ans <= x:
ans += step
num_guesses += 1
print(f"Number of guesses: {num_guesses}")
if abs(ans**2 - x) >= epsilon:
print(f"Failed to find square root of {x}")
return None
else:
print(f"Square root of {x} is approximately {ans}")
return ansexhaustive_sqrt_flowchart(x)Finding square root of 25
Number of guesses: 500
Square root of 25 is approximately 4.999999999999938
4.999999999999938
Finding square root of 26
Number of guesses: 510
Square root of 26 is approximately 5.099999999999936
5.099999999999936
Note
Algorithm Steps:
ans = 0.0epsilon each iterationans² is close enough to xKey Variables:
step: How much to increment each guessnum_guesses: Performance counterans: Current approximationLoop Condition Explained:
abs(ans**2 - x) >= epsilon: Not accurate enough yetans*ans <= x: Haven’t exceeded target (prevents infinite loop)Why It Works:
These functions use “exhaustive enumeration” over integers:
Key Insight: The algorithm design assumes the answer is an integer!
Remember this slide?
What if I need an exact value for a root for a non-perfect integers?
Important
Note
Note:
For approximating non-perfect roots, we need:
Next: We’ll explore these approximation techniques!
Key Concepts:
Like, Why Approximation?
Note
Exhaustive
However, Newtons Method
Whoops, here’s the right slide.
(Issac Newton)def newtons_sqrt(n:float, guess:float = 1.0) -> float:
while abs(n - guess*guess) > .0001:
print(f"n = {n}, guess = {guess}")
print(f" abs(n - guess*guess) = {abs(n - guess*guess)}")
guess = guess - (guess*guess - n)/(2*guess)
print(f" guess = guess - (guess*guess - n)/(2*guess) = {guess}\n")
return guessTest using debug print statements …
my_num = 25
result = newtons_sqrt(my_num)
print(f"Verification:\n {result}^2 = ")
print(f"{result**2} is {result**2 == result**2}")n = 25, guess = 1.0
abs(n - guess*guess) = 24.0
guess = guess - (guess*guess - n)/(2*guess) = 13.0
n = 25, guess = 13.0
abs(n - guess*guess) = 144.0
guess = guess - (guess*guess - n)/(2*guess) = 7.461538461538462
n = 25, guess = 7.461538461538462
abs(n - guess*guess) = 30.674556213017752
guess = guess - (guess*guess - n)/(2*guess) = 5.406026962727994
n = 25, guess = 5.406026962727994
abs(n - guess*guess) = 4.22512752174206
guess = guess - (guess*guess - n)/(2*guess) = 5.015247601944898
n = 25, guess = 5.015247601944898
abs(n - guess*guess) = 0.15270850881405096
guess = guess - (guess*guess - n)/(2*guess) = 5.000023178253949
n = 25, guess = 5.000023178253949
abs(n - guess*guess) = 0.00023178307672111487
guess = guess - (guess*guess - n)/(2*guess) = 5.000000000053723
Verification:
5.000000000053723^2 =
25.00000000053723 is True
So, how does this thing work?
def newtons_cube_root(n:float, guess:float = 1.0) -> float:
while abs(n - guess*guess*guess) > .0001:
print(f"n = {n}, guess = {guess}")
print(f" abs(n - guess^3) = {abs(n - guess*guess*guess)}")
guess = guess - (guess*guess*guess - n)/(3*(guess*guess))
print(f" guess = guess - (guess*guess*guess - n)/(3*(guess*guess)) = {guess}\n")
return guessNote
We are getting increasingly closer the actual value. which is determined by the tolerance level (and hence the number of iterations) as specified here by the algorithm’s parameters.
Testing the Prototype
n = 8, guess = 1.0
abs(n - guess^3) = 7.0
guess = guess - (guess*guess*guess - n)/(3*(guess*guess)) = 3.3333333333333335
n = 8, guess = 3.3333333333333335
abs(n - guess^3) = 29.037037037037045
guess = guess - (guess*guess*guess - n)/(3*(guess*guess)) = 2.462222222222222
n = 8, guess = 2.462222222222222
abs(n - guess^3) = 6.92731645541838
guess = guess - (guess*guess*guess - n)/(3*(guess*guess)) = 2.081341247671579
n = 8, guess = 2.081341247671579
abs(n - guess^3) = 1.0163315496105625
guess = guess - (guess*guess*guess - n)/(3*(guess*guess)) = 2.003137499141287
n = 8, guess = 2.003137499141287
abs(n - guess^3) = 0.03770908398584538
guess = guess - (guess*guess*guess - n)/(3*(guess*guess)) = 2.000004911675504
2.000004911675504
Testing the Prototype
n = 27, guess = 1.0
abs(n - guess^3) = 26.0
guess = guess - (guess*guess*guess - n)/(3*(guess*guess)) = 9.666666666666666
n = 27, guess = 9.666666666666666
abs(n - guess^3) = 876.2962962962961
guess = guess - (guess*guess*guess - n)/(3*(guess*guess)) = 6.540758356453956
n = 27, guess = 6.540758356453956
abs(n - guess^3) = 252.82358364070478
guess = guess - (guess*guess*guess - n)/(3*(guess*guess)) = 4.570876778578707
n = 27, guess = 4.570876778578707
abs(n - guess^3) = 68.49893783892396
guess = guess - (guess*guess*guess - n)/(3*(guess*guess)) = 3.4780192333867963
n = 27, guess = 3.4780192333867963
abs(n - guess^3) = 15.07226932492673
guess = guess - (guess*guess*guess - n)/(3*(guess*guess)) = 3.0626891086275365
n = 27, guess = 3.0626891086275365
abs(n - guess^3) = 1.7282216154620045
guess = guess - (guess*guess*guess - n)/(3*(guess*guess)) = 3.001274406506175
n = 27, guess = 3.001274406506175
abs(n - guess^3) = 0.03442359474399126
guess = guess - (guess*guess*guess - n)/(3*(guess*guess)) = 3.0000005410641766
3.0000005410641766
Testing the Prototype
n = 31, guess = 1.0
abs(n - guess^3) = 30.0
guess = guess - (guess*guess*guess - n)/(3*(guess*guess)) = 11.0
n = 31, guess = 11.0
abs(n - guess^3) = 1300.0
guess = guess - (guess*guess*guess - n)/(3*(guess*guess)) = 7.418732782369146
n = 31, guess = 7.418732782369146
abs(n - guess^3) = 377.30921842166106
guess = guess - (guess*guess*guess - n)/(3*(guess*guess)) = 5.133572303084083
n = 31, guess = 5.133572303084083
abs(n - guess^3) = 104.28792927185404
guess = guess - (guess*guess*guess - n)/(3*(guess*guess)) = 3.814485354880487
n = 31, guess = 3.814485354880487
abs(n - guess^3) = 24.501900623588178
guess = guess - (guess*guess*guess - n)/(3*(guess*guess)) = 3.2531703966218446
n = 31, guess = 3.2531703966218446
abs(n - guess^3) = 3.4286849761153846
guess = guess - (guess*guess*guess - n)/(3*(guess*guess)) = 3.1451781175927085
n = 31, guess = 3.1451781175927085
abs(n - guess^3) = 0.11255922102655447
guess = guess - (guess*guess*guess - n)/(3*(guess*guess)) = 3.1413852355779674
n = 31, guess = 3.1413852355779674
abs(n - guess^3) = 0.00013568459873170013
guess = guess - (guess*guess*guess - n)/(3*(guess*guess)) = 3.14138065239808
3.14138065239808
Note
Parameters:
Returns:
Important
Mathematical formula:
def newtons_nth_root(n: int, value: float, guess: float = 1.0) -> float:
"""
Find the nth root of a value using Newton's method.
"""
if n <= 0:
raise ValueError("n must be a positive integer")
if value < 0 and n % 2 == 0:
raise ValueError("Cannot find even root of negative number")
tolerance = 0.0001
while abs(guess**n - value) > tolerance:
print(f"n = {n}, value = {value}, guess = {guess}")
print(f" abs(guess^n - value) = abs({guess}^{n} - {value}) = {abs(guess**n - value)}")
# Newton's method formula: guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = guess - (guess**n - value) / (n * guess**(n-1))
print(f" guess_new = guess - (guess^n - value)/(n * guess^(n-1))")
print(f" guess_new = {guess} - ({guess}^{n} - {value})/({n} * {guess}^{n-1}) = {guess_new}\n")
guess = guess_new
return guessTesting the Prototype (1)
print("Square root of 16 (n=2, value=16):")
result = newtons_nth_root(2, 16)
print(f"Result: {result}")
print(f"Verification: {result}^2 = {result**2}\n")Square root of 16 (n=2, value=16):
n = 2, value = 16, guess = 1.0
abs(guess^n - value) = abs(1.0^2 - 16) = 15.0
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 1.0 - (1.0^2 - 16)/(2 * 1.0^1) = 8.5
n = 2, value = 16, guess = 8.5
abs(guess^n - value) = abs(8.5^2 - 16) = 56.25
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 8.5 - (8.5^2 - 16)/(2 * 8.5^1) = 5.1911764705882355
n = 2, value = 16, guess = 5.1911764705882355
abs(guess^n - value) = abs(5.1911764705882355^2 - 16) = 10.94831314878893
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 5.1911764705882355 - (5.1911764705882355^2 - 16)/(2 * 5.1911764705882355^1) = 4.136664722546242
n = 2, value = 16, guess = 4.136664722546242
abs(guess^n - value) = abs(4.136664722546242^2 - 16) = 1.1119950267585779
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 4.136664722546242 - (4.136664722546242^2 - 16)/(2 * 4.136664722546242^1) = 4.002257524798522
n = 2, value = 16, guess = 4.002257524798522
abs(guess^n - value) = abs(4.002257524798522^2 - 16) = 0.018065294806394405
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 4.002257524798522 - (4.002257524798522^2 - 16)/(2 * 4.002257524798522^1) = 4.000000636692939
Result: 4.000000636692939
Verification: 4.000000636692939^2 = 16.00000509354392
Testing the Prototype (2)
print("Cube root of 27 (n=3, value=27):")
result = newtons_nth_root(3, 27)
print(f"Result: {result}")
print(f"Verification: {result}^3 = {result**3}\n")Cube root of 27 (n=3, value=27):
n = 3, value = 27, guess = 1.0
abs(guess^n - value) = abs(1.0^3 - 27) = 26.0
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 1.0 - (1.0^3 - 27)/(3 * 1.0^2) = 9.666666666666666
n = 3, value = 27, guess = 9.666666666666666
abs(guess^n - value) = abs(9.666666666666666^3 - 27) = 876.2962962962961
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 9.666666666666666 - (9.666666666666666^3 - 27)/(3 * 9.666666666666666^2) = 6.540758356453956
n = 3, value = 27, guess = 6.540758356453956
abs(guess^n - value) = abs(6.540758356453956^3 - 27) = 252.82358364070478
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 6.540758356453956 - (6.540758356453956^3 - 27)/(3 * 6.540758356453956^2) = 4.570876778578707
n = 3, value = 27, guess = 4.570876778578707
abs(guess^n - value) = abs(4.570876778578707^3 - 27) = 68.49893783892396
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 4.570876778578707 - (4.570876778578707^3 - 27)/(3 * 4.570876778578707^2) = 3.4780192333867963
n = 3, value = 27, guess = 3.4780192333867963
abs(guess^n - value) = abs(3.4780192333867963^3 - 27) = 15.07226932492673
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 3.4780192333867963 - (3.4780192333867963^3 - 27)/(3 * 3.4780192333867963^2) = 3.0626891086275365
n = 3, value = 27, guess = 3.0626891086275365
abs(guess^n - value) = abs(3.0626891086275365^3 - 27) = 1.728221615462001
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 3.0626891086275365 - (3.0626891086275365^3 - 27)/(3 * 3.0626891086275365^2) = 3.0012744065061754
n = 3, value = 27, guess = 3.0012744065061754
abs(guess^n - value) = abs(3.0012744065061754^3 - 27) = 0.03442359474400192
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 3.0012744065061754 - (3.0012744065061754^3 - 27)/(3 * 3.0012744065061754^2) = 3.0000005410641766
Result: 3.0000005410641766
Verification: 3.0000005410641766^3 = 27.000014608735402
Testing the Prototype (3)
print("Fourth root of 81 (n=4, value=81):")
result = newtons_nth_root(4, 81)
print(f"Result: {result}")
print(f"Verification: {result}^4 = {result**4}\n")Fourth root of 81 (n=4, value=81):
n = 4, value = 81, guess = 1.0
abs(guess^n - value) = abs(1.0^4 - 81) = 80.0
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 1.0 - (1.0^4 - 81)/(4 * 1.0^3) = 21.0
n = 4, value = 81, guess = 21.0
abs(guess^n - value) = abs(21.0^4 - 81) = 194400.0
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 21.0 - (21.0^4 - 81)/(4 * 21.0^3) = 15.752186588921283
n = 4, value = 81, guess = 15.752186588921283
abs(guess^n - value) = abs(15.752186588921283^4 - 81) = 61488.18289808421
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 15.752186588921283 - (15.752186588921283^4 - 81)/(4 * 15.752186588921283^3) = 11.81932080918686
n = 4, value = 81, guess = 11.81932080918686
abs(guess^n - value) = abs(11.81932080918686^4 - 81) = 19434.068636062897
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 11.81932080918686 - (11.81932080918686^4 - 81)/(4 * 11.81932080918686^3) = 8.876755039613878
n = 4, value = 81, guess = 8.876755039613878
abs(guess^n - value) = abs(8.876755039613878^4 - 81) = 6127.932543617901
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 8.876755039613878 - (8.876755039613878^4 - 81)/(4 * 8.876755039613878^3) = 6.686517196520891
n = 4, value = 81, guess = 6.686517196520891
abs(guess^n - value) = abs(6.686517196520891^4 - 81) = 1917.9404828939596
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 6.686517196520891 - (6.686517196520891^4 - 81)/(4 * 6.686517196520891^3) = 5.082624768192105
n = 4, value = 81, guess = 5.082624768192105
abs(guess^n - value) = abs(5.082624768192105^4 - 81) = 586.3477398915912
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 5.082624768192105 - (5.082624768192105^4 - 81)/(4 * 5.082624768192105^3) = 3.966195743486564
n = 4, value = 81, guess = 3.966195743486564
abs(guess^n - value) = abs(3.966195743486564^4 - 81) = 166.45519543819964
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 3.966195743486564 - (3.966195743486564^4 - 81)/(4 * 3.966195743486564^3) = 3.2992124877307853
n = 4, value = 81, guess = 3.2992124877307853
abs(guess^n - value) = abs(3.2992124877307853^4 - 81) = 37.478937202150505
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 3.2992124877307853 - (3.2992124877307853^4 - 81)/(4 * 3.2992124877307853^3) = 3.0382990701981023
n = 4, value = 81, guess = 3.0382990701981023
abs(guess^n - value) = abs(3.0382990701981023^4 - 81) = 4.216184080510672
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 3.0382990701981023 - (3.0382990701981023^4 - 81)/(4 * 3.0382990701981023^3) = 3.000718098021805
n = 4, value = 81, guess = 3.000718098021805
abs(guess^n - value) = abs(3.000718098021805^4 - 81) = 0.07758243669628939
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 3.000718098021805 - (3.000718098021805^4 - 81)/(4 * 3.000718098021805^3) = 3.000000257729561
Result: 3.000000257729561
Verification: 3.000000257729561^4 = 81.00002783479616
Testing the Prototype ((4)
print("4. Square root of 10 (n=2, value=10):")
result = newtons_nth_root(2, 10)
print(f"Result: {result}")
print(f"Verification: {result}^2 = {result**2}\n")4. Square root of 10 (n=2, value=10):
n = 2, value = 10, guess = 1.0
abs(guess^n - value) = abs(1.0^2 - 10) = 9.0
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 1.0 - (1.0^2 - 10)/(2 * 1.0^1) = 5.5
n = 2, value = 10, guess = 5.5
abs(guess^n - value) = abs(5.5^2 - 10) = 20.25
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 5.5 - (5.5^2 - 10)/(2 * 5.5^1) = 3.659090909090909
n = 2, value = 10, guess = 3.659090909090909
abs(guess^n - value) = abs(3.659090909090909^2 - 10) = 3.3889462809917354
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 3.659090909090909 - (3.659090909090909^2 - 10)/(2 * 3.659090909090909^1) = 3.196005081874647
n = 2, value = 10, guess = 3.196005081874647
abs(guess^n - value) = abs(3.196005081874647^2 - 10) = 0.21444848336856914
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 3.196005081874647 - (3.196005081874647^2 - 10)/(2 * 3.196005081874647^1) = 3.1624556228038903
n = 2, value = 10, guess = 3.1624556228038903
abs(guess^n - value) = abs(3.1624556228038903^2 - 10) = 0.0011255662039406644
guess_new = guess - (guess^n - value)/(n * guess^(n-1))
guess_new = 3.1624556228038903 - (3.1624556228038903^2 - 10)/(2 * 3.1624556228038903^1) = 3.162277665175675
Result: 3.162277665175675
Verification: 3.162277665175675^2 = 10.000000031668918
Can you modify the newtons_cube_root() algorithm to find the forth root newtons_4_root()
Hint: Use the general case to create the code.
Solve each challenge with a short Python function and a quick test.
Task: Write is_perfect_square(n) that returns True if n is a perfect square and False otherwise.
Constraints: Use exhaustive enumeration (counting up).
Task: Write approx_sqrt(x, epsilon=0.001) using exhaustive enumeration with a step size of epsilon.
Return the approximation and the number of guesses.
Task: Write newtons_sqrt_steps(x, guess=1.0, tol=1e-4) that returns 1) the approximation and 2) how many iterations it took.
Task: Write int_cube_root(n) that returns the integer cube root if it exists, otherwise returns None.
Use exhaustive enumeration only.
Task: Write compare_methods(x) that prints two lines:
exhaustive approximation (epsilon = 0.001) and the values of the guesses for each iteration of “simple_square_root(x)”guesses for each iteration “newtons_sqrt()”Which algorithm do you suppose is better? Why?
Below are sample solutions with short explanations.
Idea: Count up until k^2 is at least n, then compare.
Idea: Step by epsilon and stop when the square is close enough.
def approx_sqrt(x: float, epsilon: float = 0.001):
if x < 0:
return None, 0
step = epsilon
ans = 0.0
guesses = 0
while abs(ans * ans - x) >= epsilon and ans * ans <= x:
ans += step
guesses += 1
if abs(ans * ans - x) >= epsilon:
return None, guesses
return ans, guesses
print(approx_sqrt(25))
print(approx_sqrt(26))(5.000000000000004, 5000)
(5.0990000000000375, 5099)
Idea: Track iterations while updating with Newton’s method.
def newtons_sqrt_steps(x: float, guess: float = 1.0, tol: float = 1e-4):
if x < 0:
return None, 0
steps = 0
while abs(x - guess * guess) > tol:
guess = guess - (guess * guess - x) / (2 * guess)
steps += 1
return guess, steps
print(newtons_sqrt_steps(25))
print(newtons_sqrt_steps(26))(5.000000000053723, 6)
(5.099019513684701, 6)
Idea: Count up until k^3 reaches or passes |n|, then check.
Idea: One way to determine “better-ness”; Try running both algorithms and report results with effort.
def compare_methods(x: float, epsilon: float = 0.001, tol: float = 1e-4):
approx, guesses = approx_sqrt(x, epsilon)
newton, steps = newtons_sqrt_steps(x, tol=tol)
print(f"Exhaustive: approx={approx}, guesses={guesses}")
print(f"Newton: approx={newton}, steps={steps}")
compare_methods(26)Exhaustive: approx=5.0990000000000375, guesses=5099
Newton: approx=5.099019513684701, steps=6