Simpson's Rule Takes Forever to Run in Python - python

I've written the following function for estimating the definite integral of a function with Simpson's Rule:
def fnInt(func, a, b):
if callable(func) and type(a) in [float] and type(b) in [float]:
if a > b:
return -1 * fnInt(func, b, a)
else:
y1 = nDeriv(func)
y2 = nDeriv(y1)
y3 = nDeriv(y2)
y4 = nDeriv(y3)
f = lambda t: abs(y4(t))
k = f(max(f, a, b))
n = ((1 / 0.00001) * k * (b - a) ** 5 / 180) ** 0.25
if n > 0:
n = math.ceil(n) if math.ceil(n) % 2 == 0 else math.ceil(n) + 1
else:
n = 2
x = (b - a) / n
ans = 0
for i in range(int((n - 4) / 2 + 1)):
ans += (x / 3) * (4 * func(a + x * (2 * i + 1)) + 2 * func(a + x * (2 * i + 2)))
ans += (x / 3) * (func(a) + 4 * func(a + x * (n - 1)) + func(b))
return ans
else:
raise TypeError('Data Type Error')
It seems, however, that whenever I try to use this function, it takes forever to produce an output. Is there a way that I can rewrite this code in order to take up less time?

As one of the comments mentioned, profiling the code will show you the slowdowns. Perhaps nDeriv is slow. If you don't have a profiling tool, you can put time() calls around each section of code and print the results. More info here: Measure time elapsed in Python?
So, if the slowdown ends up being in your for loop, here are a few things you can try:
Python might be computing the loop condition every iteration:
for i in range(int((n - 4) / 2 + 1)):
calculate int((n - 4) / 2 + 1) once before the loop.
Don't recalculate stuff inside the loops that doesn't change. For example, x / 3 is going to be recalculated every loop iteration, but it never changes. Do it before the loop starts.
Likewise, you're doing 2 * i twice every loop iteration.
Addition is faster than multiplication. The func arguments could be re-written as:
xi = x * i
a1 = a + xi + xi + x
a2 = a1 + x
and then taking it a step further, you could also re-do xi as an accumulator. That is, start with x = 0, then every iteration simply x += x
This is probably obvious, but if func() is difficult to calculate, this function will be exponentially slow.
Python may be doing a lot of simpler optimizations for you, so these may not help, but just wanted to share some ideas.

Related

vectorizing a double for loop

This is a performance question. I am trying to optimize the following double for loop. Here is a MWE
import numpy as np
from timeit import default_timer as tm
# L1 and L2 will range from 0 to 3 typically, sometimes up to 5
# all of the following are dummy values but match correct `type`
L1, L2, x1, x2, fac = 2, 3, 2.0, 4.5, 2.3
saved_values = np.random.uniform(high=75.0, size=[max(L1,L2) + 1, max(L1,L2) + 1])
facts = np.random.uniform(high=65.0, size=[L1 + L2 + 1])
val = 0
start = tm()
for i in range(L1+1):
sf = saved_values[L1][i] * x1 ** (L1 - i)
for j in range(L2 + 1):
m = i + j
if m % 2 == 0:
num = sf * facts[m] / (2 * fac) ** (m / 2)
val += saved_values[L2][j] * x1 ** (L1 - j) * num
end = tm()
time = end-start
print("Long way: time taken was {} and value is {}".format(time, val))
My idea for a solution is to take out the if m % 2 == 0: statement and then calculate all i and j combinations i.e., a matrix, which I should be able to vectorize, and then use something like np.where() to add up all of the elements meeting the requirement of if m % 2 == 0: where m= i+j.
Even if this is not faster than the explicit for loops, it should be vectorized because in reality I will be sending arrays to a function containing the double for loops, so being able to do that part vectorized, should get me the speed gains I am after, even if vectorizing this double for loop does not.
I am stuck spinning my wheels right now on how to broadcast, but account for the sf factor as well as the m factor in the inner loop.

How to optimize a algorithm that uses loops to find a stable value for a variable

I have a case where a variable (a, in this case) is calculated at each loop iteration and stops where the increment of value between two iterations is small enough.
I would like to know of a general way to find the value for that variable in this kind of case, without having to do that "convergence" work using loops.
There I would like to know if the solution is to put everything in equations, or if some tools exist to tackle that.
a = 10
b = 10
diff = 1
while diff > .1:
old_a = a
a += b
diff = 1 - (old_a/a)
print(diff)
The present code produces:
0.5
0.33333333333333337
0.25
0.19999999999999996
0.16666666666666663
0.1428571428571429
0.125
0.11111111111111116
0.09999999999999998
Therefore, it takes 9 iterations to find a relative difference of the value of a between two iterations inferior to 10%.
You have
a_n = a_0 + n * b
and try to find where
1 - (a_(n-1) / a_n)
= 1 - (a_0 + (n--1)*b) / ( a_0 + n * b)
= 1 - (a_0 + n*b -b) / (a_0 + n*b)
= 1 - 1 + b / (a_0 + n*b)
= b / (a_0 + n * b)
< 0.1
That is the same as
(a_0 / b) + n * b / b
= (a_0 / b) + n
> 10
(because 0.1 = 1 / 10 and 1/x > 1/y <=> y > x if x,y != 0)
Since you metion in the comments that your actual problem is more complex: If finding a closed form solution like above is not feasible, look at this wikipedia page about fixed point iteration, which is exactly the kind of problem you try to solve.

Trying to find the T(n) function and complexity in this bit of Python

def wum(aList):
a = 7
b = 5
n = len(aList)
for i in range(n):
for j in range(n):
a = i * a
b = j * j
w = i * j
v = i + w
x = v * v
for k in range(n):
w = a * k + 23
v = b * b
a = w + v
I got T(n) = 2n + 6n^2 complexity O(n^2), does that seem right? Help!
I always find it a bit difficult to give an exact value for T(n) since it’s hard to define what 1 means there. But assuming that each of those assignments is 1 (regardless of what kind of calculation happens), then the total T(n) would be as following: n * (6n + 2) + 3.
But in big-O notation, that is O(n²), yes. You can easily see that since you have two nested loop levels, both over n.
Btw. your function is probably an example from your instructor or something, but it’s really a bad example. You can easily modify the logic to be in linear and yield the same results:
a = 7
b = 5
n = len(aList)
for i in range(n):
a *= i ** n # `a` is multiplicated `n` times by (constant) `i`
b = (n - 1) ** 2 # at the end of the loop, `j` is `(n - 1)`
v = i + (i * b) # at the end of the loop, `w` is `i * b`
x = v * v
w = a * (n - 1) + 23 # at the end of the loop, `k` is `(n - 1)`
v = b ** 2 # `b` (and as such `v`) is never changed in the loop
a = w + v
And since nothing of that uses any value of the list, you could actually make these calculations in constant time too (I’ll leave that for your exercise ;) ).
And finally, you could argue that since the function does not return anything, and also does not mutate the input list, the function is a big NO-OP, and as such can be replaced by a function that does nothing:
def wum(aList):
pass

Trapezoidal rule in Python

I'm trying to implement the trapezoidal rule in Python 2.7.2. I've written the following function:
def trapezoidal(f, a, b, n):
h = float(b - a) / n
s = 0.0
s += h * f(a)
for i in range(1, n):
s += 2.0 * h * f(a + i*h)
s += h * f(b)
return s
However, f(lambda x:x**2, 5, 10, 100) returns 583.333 (it's supposed to return 291.667), so clearly there is something wrong with my script. I can't spot it though.
You are off by a factor of two. Indeed, the Trapezoidal Rule as taught in math class would use an increment like
s += h * (f(a + i*h) + f(a + (i-1)*h))/2.0
(f(a + i*h) + f(a + (i-1)*h))/2.0 is averaging the height of the function at two adjacent points on the grid.
Since every two adjacent trapezoids have a common edge, the formula above requires evaluating the function twice as often as necessary.
A more efficient implementation (closer to what you posted), would combine common terms from adjacent iterations of the for-loop:
f(a + i*h)/2.0 + f(a + i*h)/2.0 = f(a + i*h)
to arrive at:
def trapezoidal(f, a, b, n):
h = float(b - a) / n
s = 0.0
s += f(a)/2.0
for i in range(1, n):
s += f(a + i*h)
s += f(b)/2.0
return s * h
print( trapezoidal(lambda x:x**2, 5, 10, 100))
which yields
291.66875
The trapezoidal rule has a big /2 fraction (each term is (f(i) + f(i+1))/2, not f(i) + f(i+1)), which you've left out of your code.
You've used the common optimization that treats the first and last pair specially so you can use 2 * f(i) instead of calculating f(i) twice (once as f(j+1) and once as f(i)), so you have to add the / 2 to the loop step and to the special first and last steps:
s += h * f(a) / 2.0
for i in range(1, n):
s += 2.0 * h * f(a + i*h) / 2.0
s += h * f(b) / 2.0
You can obviously simplify the loop step by replacing the 2.0 * … / 2.0 with just ….
However, even more simply, you can just divide the whole thing by 2 at the end, changing nothing but this line:
return s / 2.0

writing multiple data calculated from a function in the same file

I am trying to write a data calculated from this function in a file. But the function is called number of times. Say there are 9 numbers in another file and this function will calculate the root for each of those 9 numbers. These 9 roots from this function should be written in the same file. But the way I have done it here will write calculated root in the file but the next one will replace this in the file. There are other mathematical functions that are carried out for each of those 9 numbers before this function is called therefore the functions are called again and again separately.Is it possible to write them all in the same file? Thank you.
def Newton(poly, start):
""" Newton's method for finding the roots of a polynomial."""
x = start
poly_diff = poly_differentiate(poly)
n = 1
counter = 0
r_i = 0
cFile = open("curve.dat", "w")
while True:
if (n >= 0) and (n < 1):
break
x_n = x - (float(poly_substitute(poly, x)) / poly_substitute(poly_diff, x))
if x_n == x:
break
x = x_n # this is the u value corresponding to the given time
n -= 1
counter += 1
x = str(x)
cFile.write('\n' + x + '\n')
if r_i:
print "t(u) = ", (x, counter)
else:
print "t(u) = ", x
cFile.close
After following the suggestions I got I changed the code to the following:
def Newton(poly, start):
""" Newton's method for finding the roots of a polynomial."""
x = start
poly_diff = poly_differentiate(poly)
n = 1
counter = 0
while True:
if (n >= 0) and (n < 1):
break
x_n = x - (float(poly_substitute(poly, x)) / poly_substitute(poly_diff, x))
if x_n == x:
break
x = x_n # this is the u value corresponding to the given time
n -= 1
counter += 1
yield x
Bezier(x)
def Bezier(u_value) :
""" Calculating sampling points using rational bezier curve equation"""
u = u_value
p_u = math.pow(1 - u, 3) * 0.7 + 3 * u * math.pow(1 - u, 2) * 0.23 \
+ 3 * (1 - u) * math.pow(u, 2) * 0.1 + math.pow(u, 3) * 0.52
p_u = p_u * w
d = math.pow(1 - u, 3) * w + 3 * u * w * math.pow(1 - u, 2) + 3 * (1 - u) *\
w * math.pow(u, 2) + math.pow(u, 3) * w
p_u = p_u / d
yield p_u
plist = list (p_u)
print plist
I followed the same thing in the Bezier() function but plist is not created as it doesn't print anything. Please help. Thank you.
Your function does two things: It calculates the roots of a polynomial, and it writes the result to an output file. Functions should ideally do one thing.
So, try breaking this up into a function that receives a polynomial and returns a list containing the roots, and then just write that list to a file in one step.
The simplest way to modify your function would be to replace the lines
x = str(x)
cFile.write('\n' + x + '\n')
with
yield x
Then you can call your function like this:
roots = list(Newton(polynomial, start))
To understand this, read about generators. To write the resulting list to a file, you can use this code:
with open("curve.dat", "w") as output_file:
output_file.write("\n".join(str(x) for x in roots)
While I'm not completely understanding what you are asking I think the answer can be boiled down to:
Open the file in append mode, not in write mode. So instead of
cFile = open("curve.dat", "w")
do
cFile = open("curve.dat", "a")
why use yield in Bezier, it doesn't return multiple values, so you can change:
yield p_u
plist = list (p_u)
print plist
to:
print list(p_u)
return p_u

Categories