0

I am trying to define a function which will approximate pi in python using one of Euler's methods. His formula is as follows: formula

My code so far is this:

def pi_euler1(n):
    numerator = list(range(2 , n))
    for i in numerator:
        j = 2
        while i * j <= numerator[-1]:
            if i * j in numerator:
                numerator.remove(i * j)
            j += 1
    for k in numerator:
        if (k + 1) % 4 == 0:
            denominator = k + 1
        else:
            denominator = k - 1
    #Because all primes are odd, both numbers inbetween them are divisible by 2,
    #and by extension 1 of the 2 numbers is divisible by 4
    term = numerator / denominator

I know this is wrong, and also incomplete. I'm just not quite sure what the TypeError that I mentioned earlier actually means. I'm just quite stuck with it, I want to create a list of the terms and then find their products. Am I on the right lines?

Update: I have worked ways around this, fixing the clearly obvious errors that were prevalent thanks to msconi and Johanc, now with the following code:

import math
def pi_euler1(n):
    numerator = list(range(2 , 13 + math.ceil(n*(math.log(n)+math.log(math.log(n))))))
    denominator=[]
    for i in numerator:
        j = 2
        while i * j <= numerator[-1]:
            if (i * j) in numerator:
                numerator.remove(i * j)
            j += 1
    numerator.remove(2)
        for k in numerator:
            if (k + 1) % 4 == 0:
                denominator.append(k+1)
            else:
                denominator.append(k-1)
        a=1
        for i in range(n):
            a *= numerator[i] / denominator[i]
        return 4*a

This seems to work, when I tried to plot a graph of the errors from pi in a semilogy axes scale, I was getting a domain error, but i needed to change the upper bound of the range to n+1 because log(0) is undefined. Thank you guys

TomGG98
  • 3
  • 2

2 Answers2

0

In term = numerator / denominator you are dividing a list by a number, which doesn't make sense. Divide k by the denominator in the loop in order to use the numerator element for each of the equation's factors one by one. Then you could multiply them repeatedly to the term term *= i / denominator, which you initialize in the beginning as term = 1.

Another issue is the first loop, which won't give you the first n prime numbers. For example, for n=3, list(range(2 , n)) = [2]. Therefore, the only prime you will get is 2.

mcsoini
  • 6,280
  • 2
  • 15
  • 38
  • Thank you, I've done all you suggested and changed the range of primes to n+1 so as to include n, I've also added a third for loop to iterate i/denominator as you said. My output for pi_euler(10) was 0.04486... and larger numbers just get very small float outputs, I'm going to fiddle around with it see where I've gone wrong. Thank you though, I now have the premise of how it works, I think – TomGG98 Dec 10 '19 at 21:57
  • changing the range of primes to n+1 won't be enough, since in the general case you don't know what your largest prime will be (e.g. the 3rd prime is 5, not included in n+1=4). Better: start with an empty list, find the next primes, and keep appending until the list has length `n`. It should be easy to find efficient prime generating algorithms. – mcsoini Dec 10 '19 at 22:04
0

Here is the code with some small modifications to get it working:

import math
def pi_euler1(n):
    lim = n * n + 4
    numerator = list(range(3, lim, 2))
    for i in numerator:
        j = 3
        while i * j <= numerator[-1]:
            if i * j in numerator:
                numerator.remove(i * j)
            j += 2
    euler_product = 1
    for k in numerator[:n]:
        if (k + 1) % 4 == 0:
            denominator = k + 1
        else:
            denominator = k - 1
        factor = k / denominator
        euler_product *= factor
    return euler_product * 4

print(pi_euler1(3))
print(pi_euler1(10000))
print(math.pi)

Output:

3.28125
3.148427801913721
3.141592653589793

Remarks:

  • You only want the odd primes, so you can start with a list of odd numbers.
  • j can start with 3 and increment in steps of 2. In fact, j can start at i because all the multiples of i smaller than i*i are already removed earlier.
  • In general it is very bad practise to remove elements from the list over which you are iterating. See e.g. this post. Internally, Python uses an index into the list over which it iterates. Coincidently, this is not a problem in this specific case, because only numbers larger than the current are removed.
  • Also, removing elements from a very long list is very slow, as each time the complete list needs to be moved to fill the gap. Therefore, it is better to work with two separate lists.
  • You didn't calculate the resulting product, nor did you return it.
  • As you notice, this formula converges very slowly.
  • As mentioned in the comments, the previous version interpreted n as the limit for highest prime, while in fact n should be the number of primes. I adapted the code to rectify that. In the above version with a crude limit; the version below tries a tighter approximation for the limit.

Here is a reworked version, without removing from the list you're iterating. Instead of removing elements, it just marks them. This is much faster, so a larger n can be used in a reasonable time:

import math
def pi_euler_v3(n):
    if n < 3:
        lim = 6
    else:
        lim = n*n
        while lim / math.log(lim) / 2 > n:
            lim //= 2
    print(n, lim)

    numerator = list(range(3, lim, 2))
    odd_primes = []
    for i in numerator:
        if i is not None:
            odd_primes.append(i)
            if len(odd_primes) >= n:
                break
            j = i
            while i * j < lim:
                numerator[(i*j-3) // 2] = None
                j += 2
    if len(odd_primes) != n:
       print(f"Wrong limit calculation, only {len(odd_primes)} primes instead of {n}")
    euler_product = 1
    for k in odd_primes:
        denominator = k + 1 if k % 4 == 3 else k - 1
        euler_product *= k / denominator
    return euler_product * 4

print(pi_euler_v2(100000))
print(math.pi)

Output:

3.141752253548891
3.141592653589793
JohanC
  • 71,591
  • 8
  • 33
  • 66
  • If you start from `numerator = list(range(3, n, 2))` you won't get the list of the `n` first primes, which is how the problem is defined. – mcsoini Dec 10 '19 at 22:19
  • "...which computes the value of the first n terms in the product..." – mcsoini Dec 10 '19 at 22:22
  • Indeed, this is a different interpretation of `n`. The solution would be to multiply n with some large enough number to get the array limit. – JohanC Dec 10 '19 at 22:23
  • I would rather build the prime list incrementally. but I'm not an expert on prime algorithms. starting from a list with random length doesn't sound like a good general approach, imo – mcsoini Dec 10 '19 at 22:24
  • Indeed, building the list in chunks the additional advantage to need much less memory. You only need memory for a fixed chunk length, together with memory to store the `n` primes. The new version above tries to calculate a good limit. – JohanC Dec 10 '19 at 23:11
  • Thank you for the comments, and thank you very much for your answer, would it not be more efficient to work out the complexity for the number of primes in a range 1,n. And change the range accordingly? I think it's something like ```math.ceil(n*(math.log(n)+math.log(math.log(n)))``` – TomGG98 Dec 11 '19 at 15:26
  • Feel free to use a better way to calculate the limit. The only simple formula I found was `n < lim / log(lim)`, but that doesn't have an easy inverse. So I made a crude approximation, which could be refined if needed. – JohanC Dec 11 '19 at 15:33