I need to compute this formula:
It is an approximation of this integral
but it doesn't matter, actually I just want to compute the value of Figure 1 with PYTHON, that's what the topic concerns.
K, alpha and sigma are fixed values within a single computation, usually:
0 <= k <= 99;
alpha = 3;
sigma = 2.
Below is how I am trying to compute such summation in python:
import decimal
from scipy.special import binom
def residual_time_mean(alpha, sigma=2, k=1):
prev_prec = decimal.getcontext().prec
D = decimal.Decimal
decimal.getcontext().prec = 128
a = float(alpha)
s = float(sigma)
sum1 = 0.0
sum2 = 0.0
sumD1 = D(0.0)
sumD2 = D(0.0)
for i in range(1, k + 1):
sum1 += binom(k, i) * ((-1) ** (i + 1)) * (s / ((a - 1) * i - 1.0))
sum2 += binom(k, i) * ((-1) ** (i + 1)) * s / ((a - 1) * i - 1.0)
sumD1 += D(binom(k, i)) * (D(-1.0) ** (D(i) + D(1.0))) * (D(s) / ((D(a) - D(1.0)) * D(i) - D(1.0)))
sumD2 += D(binom(k, i)) * (D(-1.0) ** (D(i) + D(1.0))) * D(s) / ((D(a) - D(1.0)) * D(i) - D(1.0))
decimal.getcontext().prec = prev_prec
return sum1, sum2, float(sumD1), float(sumD2)
Running
for k in [0, 1, 2, 4, 8, 20, 50, 99]:
print("k={} -> {}".format(k, residual_time_mean(3, 2, k)))
the outcome is:
k=0 -> (0.0, 0.0, 0.0, 0.0)
k=1 -> (2.0, 2.0, 2.0, 2.0)
k=2 -> (3.3333333333333335, 3.3333333333333335, 3.3333333333333335, 3.3333333333333335)
k=4 -> (5.314285714285714, 5.314285714285714, 5.314285714285714, 5.314285714285714)
k=8 -> (8.184304584304588, 8.184304584304583, 8.184304584304584, 8.184304584304584)
k=20 -> (13.952692275798238, 13.952692275795965, 13.95269227579524, 13.95269227579524)
k=50 -> (23.134878809207617, 23.13390225415814, 23.134078892910786, 23.134078892910786)
k=99 -> (265412075330.96634, 179529505602.9507, 17667813427.20196, 17667813427.20196)
You can see that starting from k=8 the results are different.
Making a multiplication before a division leads results of sum1 and sum2 to diverge a lot for k=99 for instance.
sum1 += binom(k, i) * ((-1) ** (i + 1)) * (s / ((a - 1) * i - 1.0))
sum2 += binom(k, i) * ((-1) ** (i + 1)) * s / ((a - 1) * i - 1.0)
With decimal this problem doesn't occur but the result is not correct at all.
Computing the summation on WolframAlpha
for k = 99
(Here is the link for the computation on WolframAlpha). It gives 33.3159488(...) while for my python function it is 17667813427.20196. I trust WolframAlpha since it makes something like symbolic computation, indeed it also returns the real value in form of a fraction.
for other k
Approximation problems (e.g. the value computed by Wolfram is different from the one computed in python by an order of magnitude of 10^0 or more) starts occurring from k~=60.
Moreover computing the integral (Figure 2) with scipy.integrate leads to similar approximation errors.
The question:
Do you have any suggestion to handle this computation? Increasing decimal precision doesn't seem to be helpful.
I've discovered the problem by myself:
Executing scipy.special.binom(99,50) gives
5.044567227278209e+28
while calculating binomial (99,50) on WolframAlpha gives
5.0445672272782096667406248628e+28
There is an absolute difference with an order of magnitude of 10^12.
That's why, for sure, results of python function are unreliable for high values of k. So I need to change how the binomial is computed.
I don't understand why you involve a numpy function here, and why you are converting to float objects. Really, for this formula, if your inputs are always integers, then simply stick with int and fractions.Fraction and your answers will always be exact. It is easy enough to implement your own binom function:
In [8]: def binom(n, k):
...: return (
...: factorial(n)
...: // (factorial(k)*factorial(n-k))
...: )
...:
Note, I used integer division: //. And finally, your summation:
In [9]: from fractions import Fraction
...: def F(k, a, s):
...: result = Fraction(0, 1)
...: for i in range(1, k+1):
...: b = binom(k, i)*pow(-1, i+1)
...: x = Fraction(s, (a-1)*i - 1)
...: result += b*x
...: return result
...:
And the results:
In [10]: F(99, 3, 2)
Out[10]: Fraction(47372953498165579239913571130715220654368322523193013011418, 1421930192463933435386372127473055337225260516259631545875)
Which seems correct based on wolfram-alpha...
Note, if say, alpha can be a non-integer, you could use decimal.Decimal for arbitary-precision floating point operations:
In [17]: from decimal import Decimal
...: def F(k, a, s):
...: result = Decimal('0')
...: for i in range(1, k+1):
...: b = binom(k, i)*pow(-1, i+1)
...: x = Decimal(s) / Decimal((a-1)*i - 1)
...: result += b*x
...: return result
...:
In [18]: F(99, 3, 2)
Out[18]: Decimal('33.72169506311642881389682714')
Let's up the precision:
In [20]: import decimal
In [21]: decimal.getcontext().prec
Out[21]: 28
In [22]: decimal.getcontext().prec = 100
In [23]: F(99, 3, 2)
Out[23]: Decimal('33.31594880623309576443774363783112352607484321721989160481537847749994248174570647797323789728798446')
Related
I am currently to new to sympy and I am trying to reproduce the Mathematica example in the attached image in Python. My attempt is written below but it returns an empty list
import sympy
m , n, D_star, a, j = sympy.symbols('m , n, D_star, a, j')
s1 = sympy.Sum(a**(j-1),(j, 1, m-1))
rhs = 6 * sympy.sqrt((D_star * (1 + a)*(n - 1))/2)
expand_expr = sympy.solve(s1 - rhs, m)
temp = sympy.lambdify((a, n, D_star), expand_expr, 'numpy')
n = 100
a = 1.2
D_star = 2.0
ms = temp(1.2, 100, 2.0)
ms
# what I get is an empty list []
# expected answer using Mma FindRoot function is 17.0652
Adding .doit() to expand the sum seems to help. It gives Piecewise((m - 1, Eq(a, 1)), ((a - a**m)/(1 - a), True))/a for the sum in s1.
from sympy import symbols, Eq, Sum, sqrt, solve, lambdify
m, n, j, a, D_star = symbols('m n j a D_star')
s1 = Sum(a**(j - 1), (j, 1, m - 1)).doit()
rhs = 6 * sqrt((D_star * (1 + a) * (n - 1)) / 2)
expand_expr = solve(Eq(s1, rhs), m)
temp = lambdify((a, n, D_star), expand_expr, 'numpy')
n = 100
a = 1.2
D_star = 2.0
ms = temp(1.2, 100, 2.0)
This gives for expand_expr:
[Piecewise((log(a*(3*sqrt(2)*a*sqrt(D_star*(a*n - a + n - 1)) - 3*sqrt(2)*sqrt(D_star*(a*n - a + n - 1)) + 1))/log(a), Ne(a, 1)), (nan, True)),
Piecewise((3*sqrt(2)*a*sqrt(D_star*(a*n - a + n - 1)) + 1, Eq(a, 1)), (nan, True))]
which separates into a != 1 and a == 1.
The result of ms gives [array(17.06524172), array(nan)], again in a bit awkward way to separate a hypothetical a == 1.
I have to compute {sin, cos}(arctan2(x)) on a regular basis. These expressions have much cheaper equivalents, namely
import numpy as np
np.random.seed(0)
a, b = np.random.rand(2)
print(np.cos(np.arctan2(b, a)))
print(a / np.sqrt(a ** 2 + b ** 2))
# or a / np.hypot(a, b)
print()
print(np.sin(np.arctan2(b, a)))
print(b / np.hypot(a, b))
0.6087819565465009
0.6087819565465009
0.7933375885355579
0.793337588535558
Unfortunately, for the important border case a == b == 0.0, the alternative yields and error and gives nan. I'd like to avoid both.
Is there a specialized function that computes {sin, cos}(arctan2(x)) or perhaps another more appropriate expression? Like the above, it needs to work for scalar and vector inputs.
One possible trick to get around this issue is to just add a small epsilon to the values when you are dividing by zero.
import numpy as np
np.random.seed(0)
a = np.random.rand(10)
b = np.random.rand(10)
a[0] = 0
b[0] = 0
eps = 1e-9
p1 = np.cos(np.arctan2(b, a))
p2 = (a+eps) / ((np.sqrt(a ** 2 + b ** 2))+eps)
print(np.allclose(p1, p2))
p1 = np.sin(np.arctan2(b, a))
p2 = b / (np.hypot(a, b)+eps)
print(np.allclose(p1, p2))
In the following code I have implemented Simpsons Rule in Python. I have attempted to plot the absolute error as a function of n for a suitable range of integer values n. I know that the exact result should be 1-cos(pi/2). However my graph doesn't seem to be correct. How can I fix my code to get the correct output? there were two loops and I don't think I implemented my graph coding correctly
def simpson(f, a, b, n):
"""Approximates the definite integral of f from a to b by the composite Simpson's rule, using n subintervals (with n even)"""
h = (b - a) / (n)
s = f(a) + f(b)
diffs = {}
for i in range(1, n, 2):
s += 4 * f(a + i * h)
for i in range(2, n-1, 2):
s += 2 * f(a + i * h)
r = s
exact = 1 - cos(pi/2)
diff = abs(r - exact)
diffs[n] = diff
ordered = sorted(diffs.items())
x,y = zip(*ordered)
plt.autoscale()
plt.loglog(x,y)
plt.xlabel("Intervals")
plt.ylabel("Error")
plt.show()
return s * h / 3
simpson(lambda x: sin(x), 0.0, pi/2, 100)
Your simpson method should just calculate the integral for a single value of n (as it does), but creating the plot for many values of n should be outside that method. as:
from math import pi, cos, sin
from matplotlib import pyplot as plt
def simpson(f, a, b, n):
"""Approximates the definite integral of f from a to b by the composite Simpson's rule, using 2n subintervals """
h = (b - a) / (2*n)
s = f(a) + f(b)
for i in range(1, 2*n, 2):
s += 4 * f(a + i * h)
for i in range(2, 2*n-1, 2):
s += 2 * f(a + i * h)
return s * h / 3
diffs = {}
exact = 1 - cos(pi/2)
for n in range(1, 100):
result = simpson(lambda x: sin(x), 0.0, pi/2, n)
diffs[2*n] = abs(exact - result) # use 2*n or n here, your choice.
ordered = sorted(diffs.items())
x,y = zip(*ordered)
plt.autoscale()
plt.loglog(x,y)
plt.xlabel("Intervals")
plt.ylabel("Error")
plt.show()
Starting with:
a,b=np.ogrid[0:n+1:1,0:n+1:1]
B=np.exp(1j*(np.pi/3)*np.abs(a-b))
B[z,b] = np.exp(1j * (np.pi/3) * np.abs(z - b +x))
B[a,z] = np.exp(1j * (np.pi/3) * np.abs(a - z +x))
B[diag,diag]=1-1j/np.sqrt(3)
this produces an n*n grid that acts as a matrix.
n is just a number chosen to represent the indices, i.e. an a*b matrix where a and b both go up to n.
Where z is a constant I choose to replace a row and column with the B[z,b] and B[a,z] formulas. (Essentially the same formula but with a small number added to the np.abs(a-b))
The diagonal of the matrix is given by the bottom line:
B[diag,diag]=1-1j/np.sqrt(3)
where,
diag=np.arange(n+1)
I would like to repeat this code 50 times where the only thing that changes is x so I will end up with 50 versions of the B np.ogrid. x is a randomly generated number between -0.8 and 0.8 each time.
x=np.random.uniform(-0.8,0.8)
I want to generate 50 versions of B with random values of x each time and take a geometric average of the 50 versions of B using the definition:
def geo_mean(y):
y = np.asarray(y)
return np.prod(y ** (1.0 / y.shape[0]), axis=-1)
I have tried to set B as a function of some index and then use a for _ in range(): loop, this doesn't work. Aside from copy and pasting the block 50 times and denoting each one as B1, B2, B3 etc; I can't think of another way of working this out.
EDIT:
I'm now using part of a given solution in order to show clearly what I am looking for:
#A matrix with 50 random values between -0.8 and 0.8 to be used in the loop
X=np.random.uniform(-0.8,0.8, (50,1))
#constructing the base array before modification by random x values in position z
a,b = np.ogrid[0:n+1:1,0:n+1:1]
B = np.exp(1j * ( np.pi / 3) * np.abs( a - b ))
B[diag,diag] = 1 - 1j / np.sqrt(3)
#list to store all modified arrays
randomarrays = []
for i in range( 0,50 ):
#copy array and modify it
Bnew = np.copy( B )
Bnew[z, b] = np.exp( 1j * ( np.pi / 3 ) * np.abs(z - b + X[i]))
Bnew[a, z] = np.exp( 1j * ( np.pi / 3 ) * np.abs(a - z + X[i]))
randomarrays.append(Bnew)
Bstack = np.dstack(randomarrays)
#calculate the geometric mean value along the axis that was the row in 2D arrays
B0 = geo_mean(Bstack)
From this example, every iteration of i uses the same value of X, I can't seem to get a way to get each new loop of i to use the next value in the matrix X. I am unsure of the ++ action in python, I know it does not work in python, I just don't know how to use the python equivalent. I want a loop to use a value of X, then the next loop to use the next value and so on and so forth so I can dstack all the matrices at the end and find a geo_mean for each element in the stacked matrices.
One pedestrian way would be to use a list comprehension or generator expression:
>>> def f(n, z, x):
... diag = np.arange(n+1)
... a,b=np.ogrid[0:n+1:1,0:n+1:1]
... B=np.exp(1j*(np.pi/3)*np.abs(a-b))
... B[z,b] = np.exp(1j * (np.pi/3) * np.abs(z - b +x))
... B[a,z] = np.exp(1j * (np.pi/3) * np.abs(a - z +x))
... B[diag,diag]=1-1j/np.sqrt(3)
... return B
...
>>> X = np.random.uniform(-0.8, 0.8, (10,))
>>> np.prod((*map(np.power, map(f, 10*(4,), 10*(2,), X), 10 * (1/10,)),), axis=0)
But in your concrete example we can do much better than that;
using the identity exp(a) x exp(b) = exp(a + b) we can convert the geometric mean after exponentiation to an arithmetic mean before exponentition. A bit of care is required because of the multivaluedness of the complex n-th root which occurs in the geometric mean. In the code below we normalize the angles occurring to range -pi, pi so as to always hit the same branch as the n-th root.
Please also note that the geo_mean function you provide is definitely wrong. It fails the basic sanity check that taking the average of copies of the same thing should return the same thing. I've provided a better version. It is still not perfect, but I think there actually is no perfect solution, because of the nonuniqueness of the complex root.
Because of this I recommend taking the average before exponentiating. As long as your random spread is less than pi this allows a well-defined averaging procedure with an average that is actually close to the samples
import numpy as np
def f(n, z, X, do_it_pps_way=True):
X = np.asanyarray(X)
diag = np.arange(n+1)
a,b=np.ogrid[0:n+1:1,0:n+1:1]
B=np.exp(1j*(np.pi/3)*np.abs(a-b))
X = X.reshape(-1,1,1)
if do_it_pps_way:
zbx = np.mean(np.abs(z-b+X), axis=0)
azx = np.mean(np.abs(a-z+X), axis=0)
else:
zbx = np.mean((np.abs(z-b+X)+3) % 6 - 3, axis=0)
azx = np.mean((np.abs(a-z+X)+3) % 6 - 3, axis=0)
B[z,b] = np.exp(1j * (np.pi/3) * zbx)
B[a,z] = np.exp(1j * (np.pi/3) * azx)
B[diag,diag]=1-1j/np.sqrt(3)
return B
def geo_mean(y):
y = np.asarray(y)
dim = len(y.shape)
y = np.atleast_2d(y)
v = np.prod(y, axis=0) ** (1.0 / y.shape[0])
return v[0] if dim == 1 else v
def geo_mean_correct(y):
y = np.asarray(y)
return np.prod(y ** (1.0 / y.shape[0]), axis=0)
# demo that orig geo_mean is wrong
B = np.exp(1j * np.random.random((5, 5)))
# the mean of four times the same thing should be the same thing:
if not np.allclose(B, geo_mean([B, B, B, B])):
print('geo_mean failed')
if np.allclose(B, geo_mean_correct([B, B, B, B])):
print('but geo_mean_correct works')
n, z, m = 10, 3, 50
X = np.random.uniform(-0.8, 0.8, (m,))
B0 = f(n, z, X, do_it_pps_way=False)
B1 = np.prod((*map(np.power, map(f, m*(n,), m*(z,), X), m * (1/m,)),), axis=0)
B2 = geo_mean_correct([f(n, z, x) for x in X])
# This is the recommended way:
B_recommended = f(n, z, X, do_it_pps_way=True)
print()
print(np.allclose(B1, B0))
print(np.allclose(B2, B1))
I think you should rely more on numpy functionality, when approaching your problem. Not a numpy expert myself, so there is surely room for improvement:
from scipy.stats import gmean
n = 2
z = 1
a = np.arange(n + 1).reshape(1, n + 1)
#constructing the base array before modification by random x values in position z
B = np.exp(1j * (np.pi / 3) * np.abs(a - a.T))
B[a, a] = 1 - 1j / np.sqrt(3)
#list to store all modified arrays
random_arrays = []
for _ in range(50):
#generate random x value
x=np.random.uniform(-0.8, 0.8)
#copy array and modify it
B_new = np.copy(B)
B_new[z, a] = np.exp(1j * (np.pi / 3) * np.abs(z - a + x))
B_new[a, z] = np.exp(1j * (np.pi / 3) * np.abs(a - z + x))
random_arrays.append(B_new)
#store all B arrays as a 3D array
B_stack = np.stack(random_arrays)
#calculate the geometric mean value along the axis that was the row in 2D arrays
geom_mean_for_rows = gmean(B_stack, axis = 2)
It uses the geometric mean function from scipy.stats module to have a vectorised approach for this calculation.
I'm trying to speed up this python function:
def twoFreq_orig(z, source_z, num, den, matrix, e):
Z1, Z2 = np.meshgrid(source_z, np.conj(z))
Z1 **= num
Z2 **= den - 1
M = (e ** ((num + den - 2) / 2.0)) * Z1 * Z2
return np.sum(matrix * M, 1)
where z and source_z are np.ndarray (1d, dtype=np.complex128), num and den are np.ndarray (2d, dtype=np.float64), matrix is a np.ndarray (2d, dtype=np.complex128) and e is a np.float64.
I don't have much experience with Numba, but after reading some tutorials, I came up with this implementation:
#nb.jit(nb.f8[:](nb.c16[:], nb.c16[:], nb.f8[:, :], nb.f8[:, :], nb.c16[:, :], nb.f8))
def twoFreq(z, source_z, num, den, matrix, e):
N1, N2 = len(z), len(source_z)
out = np.zeros(N1)
for r in xrange(N1):
tmp = 0
for c in xrange(N2):
n, d = num[r, c], den[r, c] - 1
z1 = source_z[c] ** n
z2 = z[r] ** d
tmp += matrix[r, c] * e ** ((n + d - 1) / 2.0) * z1 * z2
out[r] = tmp
return out
Unfortunatelly, instead of a speedup, the Numba implementation is several times slower than the original. I can't figure out how to properly use Numba. Any Numba gurus out there than can give me a hand?
Actually I don't think there is much you can do to speedup your numba function without having some more insights into the properties of your arrays (is there some mathematical tricks to get some calculations done more quickly).
But I noticed one error: you didn't conjugate your array in the numba version for example and I edited some lines to make it more streamline (some of which might only be taste). I've included comments on the appropriate places:
#nb.njit
def twoFreq(z, source_z, num, den, matrix, e):
#Replace z with conjugate of z (otherwise the result is wrong!)
z = np.conj(z)
# Size instead of len() don't know if it actually makes a difference but it's cleaner
N1, N2 = z.size, source_z.size
# Must be zeros_like otherwise you create a float array where you want a complex one
out = np.zeros_like(z)
# I'm using python 3 so you need to replace this by xrange later
for r in range(N1):
for c in range(N2):
n, d = num[r, c], den[r, c] - 1
z1 = source_z[c] ** n
z2 = z[r] ** d
# Multiply with 0.5 instead of dividing by 2
# Work on the out array directly instead of a tmp variable
out[r] += matrix[r, c] * e ** ((n + d - 1) * 0.5) * z1 * z2
return out
def twoFreq_orig(z, source_z, num, den, matrix, e):
Z1, Z2 = np.meshgrid(source_z, np.conj(z))
Z1 **= num
Z2 **= den - 1
M = (e ** ((num + den - 2) / 2.0)) * Z1 * Z2
return np.sum(matrix * M, 1)
numb = 1000
z = np.random.uniform(0,1,numb) + 1j*np.random.uniform(0,1,numb)
source_z = np.random.uniform(0,10,numb) + 1j*np.random.uniform(0,1,numb)
num = np.random.uniform(0,1,(numb,numb))
den = np.random.uniform(0,1,(numb,numb))
matrix = np.random.uniform(0,1,(numb,numb)) + 1j*np.random.uniform(0,1,(numb, numb))
e = 5.5
# This failed for your initial version:
np.testing.assert_array_almost_equal(twoFreq(z, source_z, num, den, matrix, e),
twoFreq_orig(z, source_z, num, den, matrix, e))
And the runtimes on my computer were:
%timeit twoFreq(z, source_z, num, den, matrix, e)
1 loop, best of 3: 246 ms per loop
%timeit twoFreq_orig(z, source_z, num, den, matrix, e)
1 loop, best of 3: 344 ms per loop
It's approximatly 30% faster than your numpy-solution. But I think the numpy solution could be made a bit faster with clever usage of broadcasting. But nevertheless, most of the speedup I got was from omitting the signature: notice that you probably use C-contiguous arrays but you have given an arbitary ordering (so numba might be a bit slower depending on the computer architecture). Probably by defining c16[::-1] you'll get the same speed but generally just let numba infer the type, it will probably be as fast as it can be. Exception: You want different precision inputs for each variable (for example you want z to be complex128 and complex64)
You will get an amazing speedup when your numpy solution runs out of memory (because your numpy solution is vectorized it will need much more RAM!) With numb = 5000 the numba version was approximatly 3x faster than the numpy one.
EDIT:
With clever broadcasting I mean that
np.conj(z[:,None]**(den-1)) * source_z[None, :]**(num)
is equal to
z1, z2 = np.meshgrid(source_z, np.conj(z))
z1**(num) * z2**(den-1)
but with the first variant you only have the power operation on numb elements whereas you have a (numb, numb) shaped array so you perform much more "power" operations than necessary (even though I guess for small arrays the result is probably mostly cached and not very expensive)
The version for numpy without mgrid (which produces the same result) looks like this:
def twoFreq_orig2(z, source_z, num, den, matrix, e):
z1z2 = source_z[None,:]**(num) * np.conj(z)[:, None]**(den-1)
M = (e ** ((num + den - 2) / 2.0)) * z1z2
return np.sum(matrix * M, 1)