tag:blogger.com,1999:blog-8353416089501405552014-10-03T09:06:45.934+02:00Numerical Recipes<p>"Come the (computer) revolution, all persons found guilty of such criminal behavior will be summarily executed, and their programs won’t be!"</p>
from <i>Numerical Recipes in C</i>Jaimehttp://www.blogger.com/profile/02831887559711602902noreply@blogger.comBlogger9125tag:blogger.com,1999:blog-835341608950140555.post-28731788694060018152009-04-30T18:10:00.004+02:002009-04-30T18:26:47.804+02:00Go West Young Man!I have had to find it out the hard way, but blogger sucks when it comes to math blogging. It wouldn't be all that bad, were it not because on the other side of the road things are soooo very much easier. And while I will miss google analytics, the native support for latex and syntax highlighting make up for it more than enough. So I'm moving this blog to wordpress...<br /><br />You can find the new blog <a href="http://numericalrecipes.wordpress.com/">here</a>, and suscribe to the RSS feed <a href="http://numericalrecipes.wordpress.com/feed/">here</a>.<br /><br />For starters I have redone the much improvable first post of the series on the FFT, which I have split in two, <a href="http://numericalrecipes.wordpress.com/2009/04/30/the-discrete-fourier-transform/">one on the DFT</a>, <a href="http://numericalrecipes.wordpress.com/2009/04/30/the-radix-2-cooley-tukey-fft-algorithm-with-decimation-in-time/">the other starting discussion on the FFT</a>, plus an unconnected post on <a href="http://numericalrecipes.wordpress.com/2009/04/30/computing-the-greatest-common-divisor/">computing the GCD</a>...<br /><br />I will be moving all the content in this blog over to the new one, but have no intention of deleting anything from this one.Jaimehttp://www.blogger.com/profile/02831887559711602902noreply@blogger.com3tag:blogger.com,1999:blog-835341608950140555.post-252483954293281902009-04-16T22:30:00.025+02:002009-04-19T01:22:27.911+02:00The Cooley-Tukey FFT AlgorithmI'm currently a little fed up with <a href="http://numericalrecipes.blogspot.com/search/label/number%20theory">number theory</a>, so its time to change topics completely. Specially since the post on <a href="http://numericalrecipes.blogspot.com/2009/04/naive-integer-factorization.html">basic integer factorization</a> completes what I believe is a sufficient toolkit to tackle a very cool subject: the <a href="http://en.wikipedia.org/wiki/Fast_Fourier_transform">fast Fourier transform (FFT)</a>.<br /><br />I have some mixed feelings about how does the Fourier transform qualify for the "uncomplicated complexity" rule I imposed on myself when starting this blog. There certainly is a lot of very elaborate math behind Fourier analysis, but I think that if you skip all the mathematical subtleties and dive head first into a "the Fourier transform gives you the frequencies present in the signal" type of intuitive description, the journey from the naive implementation of the <a href="http://en.wikipedia.org/wiki/Discrete_Fourier_transform">discrete Fourier transform (DFT)</a> to the many flavors of the FFT is a very enjoyable one, which requires only a limited amount of complication, but gentle doses of complexity. Which is exactly what I'm after...<br /><br />So I will take the definition of the DFT for granted, and concentrate on the algorithms to compute it efficiently. There will have to be a few more leaps of faith, but I will try to mark them clearly, so you can whisper "amen" and keep going without a second thought. Although things may unfold differently, I think there is material for about half a dozen posts. Towards the end I may devote some time to optimizing the code, with things such as avoiding recursion, or doing the calculations in place, but most of my efforts will be devoted to optimizing the math behind the code, not the code itself. So the resulting code will probably be suboptimal: if you spot a potential improvement your comments are welcome and encouraged.<br /><br /><span style="font-weight: bold;">The Discrete Fourier Transform</span><br />Without further explanation, we will begin by writing down the analytical expression of the DFT,<br /><img alt="tex: \displaystyle X_k = \sum_{n=0}^{N-1}{x_n e^{-2\pi i k n / N}}," /><br />and of its corresponding inverse transform,<br /><img alt="tex: \displaystyle x_n = \sum_{k=0}^{N-1}{X_k e^{2\pi i k n / N}}." /><br />With python's built-in support for complex arithmetic, there really isn't much mistery in turning these two formulas into two python functions, or as I have chosen, one with an <code>inverse</code> switch:<br /><br /><pre class="brush: py"><br />from __future__ import division<br />import math<br />import time<br /><br />def dft(x, inverse = False, verbose = False) :<br /> t = time.clock()<br /> N = len(x)<br /> inv = -1 if not inverse else 1<br /> X =[0] * N<br /> for k in xrange(N) :<br /> for n in xrange(N) :<br /> X[k] += x[n] * math.e**(inv * 2j * math.pi * k * n / N)<br /> if inverse :<br /> X[k] /= N<br /> t = time.clock() - t<br /> if verbose :<br /> print "Computed","an inverse" if inverse else "a","DFT of size",N,<br /> print "in",t,"sec."<br /> return X<br /></pre><br />Of course, having two nested loops of the size of the input means that computation time will grow with N<sup>2</sup>, which is extremely inefficient.<br /><br /><span style="font-weight: bold;">The Radix-2 Cooley-Tukey Algorithm with Decimation in Time</span><br />How can that be improved? If the size of the input is even, we can write N = 2·M and it is possible to split the N element summation in the previous formulas into two M element ones, one over n = 2·m, another over n = 2·m + 1. By doing so, we arrive at<br /><img alt="tex: \displaystyle X_k = \sum_{m=0}^{M-1}{x_{2m} e^{-2\pi i \frac{m k}{M} }} + e^{-2\pi i\frac{k}{N}} \sum_{m=0}^{M-1}{x_{2m+1} e^{-2\pi i\frac{m k}{M}}}" /><br />for the direct transform, and<br /><img style="width: 400px;" alt="tex: \displaystyle x_n = \frac{1}{2} \left(\frac{1}{M} \sum_{m=0}^{M-1}{X_{2m} e^{2\pi i \frac{m n}{M} }} + e^{2\pi i\frac{n}{N}}\frac{1}{M} \sum_{m=0}^{M-1}{X_{2m+1} e^{2\pi i\frac{m n}{M}}}\right)" /><br />for the inverse.<br /><br />If you look carefully at this formulas, you'll notice we have just split our size N transform, be it direct or inverse, into two others of half the size, one over even indexes, the other over odd ones. The factor multiplying the second partial transform is known as a <a href="http://en.wikipedia.org/wiki/Twiddle_factor">twiddle factor</a>, and introduces a little overhead to the total timing, but overall we have just managed to cut the total time in half. And if the input size happens to be a power of two, we need not stop here, for as long as it is divisible by two, we can repeat the process iteratively, leading to a time of N log N.<br /><br />To implement this recursive approach we need to take care of a few more details, though. First, if you look at the previous formulas, variable k in the direct transform, or n in the inverse, runs all the way to N = 2·M, rather than to M, as one would expect in the standard DFT. By taking advantage of the fact that the exponential function is periodic with period 2πi, it is quite easy to show that, for k (or n) larger than M, the resulting value will be the same as the one obtained for k-M (or n-M).<br /><br />The other open question is at what point to stop the recursion. The simplest approach is to keep going until the input size is odd, and then call the naive DFT function. If the size happens to be a power of two, this call will not happen until the input size comes down to one. Since the DFT of a single sample signal is the same signal unchanged, that is a wasteful way of coding. But since it clarifies the logic behind the algorithm, I will leave those optimizations for a later stage.<br /><br />With this caveats in mind, this FFT algorithm can be coded in python as follows:<br /><br /><pre class="brush: py"><br />from __future__ import division<br />import math<br />import time<br /><br />def fft_CT(x, inverse = False, verbose = False) :<br /> t = time.clock()<br /> N = len(x)<br /> inv = -1 if not inverse else 1<br /> if N % 2 :<br /> return dft(x, inverse, False)<br /> x_e = x[::2]<br /> x_o = x[1::2]<br /> X_e = fft_CT(x_e, inverse, False)<br /> X_o = fft_CT(x_o, inverse, False)<br /> X = []<br /> M = N // 2<br /> for k in range(M) :<br /> X += [X_e[k] + X_o[k] * math.e ** (inv * 2j * math.pi * k / N)]<br /> for k in range(M,N) :<br /> X += [X_e[k-M] - X_o[k-M] * math.e ** (inv * 2j * math.pi * (k-M) / N)]<br /> if inverse :<br /> X = [j/2 for j in X]<br /> t = time.clock() - t<br /> if verbose :<br /> print "Computed","an inverse" if inverse else "a","CT FFT of size",N,<br /> print "in",t,"sec."<br /> return X<br /></pre><br />The speed gain with this approach, restricted for now to power of two sizes, are tremendous even for moderately large input sizes. For instance, a 2<sup>20</sup> (~10<sup>6</sup>) item input can be processed with the FFT approach in a couple of minutes, while the projected duration using the naive DFT will be more like a couple of months...Jaimehttp://www.blogger.com/profile/02831887559711602902noreply@blogger.com7tag:blogger.com,1999:blog-835341608950140555.post-26941943367650898292009-04-09T00:20:00.008+02:002009-04-16T22:50:35.791+02:00Naive Integer FactorizationAfter three posts (<a href="http://numericalrecipes.blogspot.com/2009/03/prime-numbers_04.html">1</a>, <a href="http://numericalrecipes.blogspot.com/2009/03/prime-numbers-2-sieving-of-erathostenes.html">2</a>, <a href="http://numericalrecipes.blogspot.com/2009/03/prime-numbers-3-wheel-factorization.html">3</a>) on calculating prime numbers, it is probably worth putting that knowledge to a more useful task. As we will see in a near future, <a href="http://en.wikipedia.org/wiki/Integer_factorization">integer factorization</a>, i.e. breaking down a (composite) number into its prime factors is one such task. In purity, factoring a number n is simply decomposing it as the product of two smaller non-trivial, i.e. different from 1 and n itself, divisors. But by repeatedly factoring the divisors one will eventually come up with a unique set of primes which, when multiplied together, render the original number, or so says the <a href="http://en.wikipedia.org/wiki/Fundamental_theorem_of_arithmetic">fundamental theorem of arithmetic</a>... The point is, we will consider factorization a synonym of prime decomposition, be it formally correct or not.<br /><br />There are some very sophisticated methods to factor very large numbers, but they use a lot of extremely complex math, so I doubt they will ever find their way onto this blog. So we are going to be left with the naive, straightforward approach as our only option, although I will try to give it an efficiency boost. What is this naive approach? Trial division, of course: given a number n, we know that its smallest factor will be smaller than the square root of n, so we can simply try and see if any of those numbers divide it. No, I will not try to code that yet... If you have read the entries on determining prime numbers, it should come as no surprise that we really do not need to do trial division by all numbers smaller than the square root of n, but only by the primes within. This is a consequence of the fact that, if a composite number divides n, then each of the prime factors of that composite number will also divide n. According to the <a href="http://en.wikipedia.org/wiki/Prime_number_theorem">prime number theorem</a> the number of primes below x is asymptotic to x / log x. So by limiting our trials to prime numbers we can reduce the number of tests from n<sup>1/2</sup> to something around 2 n<sup>1/2</sup> / log n. If we rescue the <code>primeListSofE</code> function from <a href="http://numericalrecipes.blogspot.com/2009/03/prime-numbers-2-sieving-of-erathostenes.html">the post on the sieve of Erathostenes</a>, a python implementation of naive factorization could look something like this...<br /><pre class="brush: py"><br />from time import clock<br /><br />def factor(n, verbose = False) :<br /> """Returns all prime factors of n, using trial division by prime<br /> numbers only. Returns a list of (possibly repeating) prime factors<br /> """<br /> t = clock()<br /> ret =[]<br /> nn = n<br /> maxFactor = int(n**0.5)<br /> primes = primeListSofE(maxFactor, verbose)<br /> for p in primes :<br /> while nn % p == 0 :<br /> nn //= p<br /> ret += [p]<br /> if nn == 1 :<br /> break<br /> if nn != 1 :<br /> ret += [nn]<br /> t = clock() - t<br /> if verbose :<br /> print "Calculated factors of",n,"in",t,"sec."<br /> return ret<br /></pre><br />While this function will be about as good as we can make it for numbers which are the product of two large prime factors, it will be terribly inefficient for most numbers. Consider, as an extreme example, that we are trying to factor 2<sup>55</sup> ~ 3.6·10<sup>16</sup>. We would first calculate all primes up to 1.9·10<sup>8</sup>, a challenging feat in itself with our available tools, only to find out that we only needed the first of those primes, i.e. 2. Taking into account that 50% of all numbers are divisible by 2, 33% are divisible by 3, 20% are divisible by 5... it doesn't seem wise to disregard the potential time savings. What we can do to profit from this is to do the trial division checks at the same time as we determine the prime numbers, updating the largest prime to test on the fly. This has to be done in two stages, the first while we sieve up to n<sup>1/4</sup>, the second while we search the rest of the sieve up to n<sup>1/2</sup> searching for more primes. The following Franken-code has been written mostly by cut-and-paste from <code>primeListSofE</code> and <code>factor</code>, which hopefully hasn't affected its readability much:<br /><pre class="brush: py"><br />from time import clock<br /><br />def factorAndSieve(n, verbose = False) :<br /> """Returns all prime factors of n, using trial division while sieving<br /> for primes. Returns a list of (possibly repeating) prime factors<br /> """<br /> t = clock()<br /> ret =[]<br /> nn = n<br /> while nn % 2 == 0 : # remove 2's first, as 2 is not in sieve<br /> nn //= 2<br /> ret += [2]<br /> maxFactor = int(nn**0.5)<br /> maxI = (maxFactor-3) // 2<br /> maxP = int(maxFactor**0.5)<br /> sieve = [True for j in xrange(maxI+1)]<br /> i = 0<br /> for p in xrange(3, maxP+1,2) : # we then sieve as far as needed<br /> if p > maxP :<br /> break<br /> i = (p-3) // 2<br /> if sieve[i] :<br /> while nn % p == 0 :<br /> nn //= p<br /> ret += [p]<br /> maxFactor = int(nn**0.5)<br /> maxI = (maxFactor-3) // 2<br /> maxP = int(maxFactor**0.5)<br /> if nn == 1 :<br /> break<br /> else :<br /> i2 = (p*p - 3) // 2<br /> for k in xrange(i2, maxI+1, p) :<br /> sieve[k] = False<br /> index = i<br /> for i in xrange(index, maxI+1) : # and inspect the rest of the sieve<br /> if i > maxI :<br /> break<br /> if sieve[i] :<br /> p = 2*i + 3<br /> while nn % p == 0 :<br /> nn //= p<br /> ret += [p]<br /> maxFactor = int(nn**0.5)<br /> maxI = (maxFactor-3) // 2<br /> maxP = int(maxFactor**0.5)<br /> if nn == 1 :<br /> break<br /> if nn != 1 :<br /> ret += [nn]<br /> t = clock() - t<br /> if verbose :<br /> print "Calculated factors of",n,"in",t,"sec."<br /> print "Stopped trial division at",2*i+3,"instead of",int(n**0.5)<br /> return ret <br /></pre><br />This new code will very often be much faster than the other one, but at times it will be just about as slow as in the other case, or even slower, since the mixing of both codes introduces some inefficiencies. The most extreme examples of such cases would be a prime number, or the square of a prime number on one side, and a power of 2 on the other one.<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://3.bp.blogspot.com/_47tBuP0my9o/Sd4W2kjwj3I/AAAAAAAAAIA/T1lCIBL2qtg/s1600-h/factorization1.png"><img style="cursor: pointer; width: 400px; height: 247px;" src="http://3.bp.blogspot.com/_47tBuP0my9o/Sd4W2kjwj3I/AAAAAAAAAIA/T1lCIBL2qtg/s400/factorization1.png" alt="" id="BLOGGER_PHOTO_ID_5322716936380911474" border="0" /></a><br /><br />The graph above plots times to calculate the factors of numbers between 10<sup>6</sup> and 10<sup>6</sup> + 100. Prime numbers in this interval stick out as the red dots among the blue ones: 10<sup>6</sup> +3, +33, the <a href="http://en.wikipedia.org/wiki/Twin_prime">twin primes</a> +37 and +39, +81 and +99. And numbers with many small prime factors populate the bottom of the red cloud.<br /><br />If the above graph is not enough to convince you of the benefits of the second approach, maybe this timings for very large numbers will:<br /><pre><br />>>> factor(10**15+37,True)<br />Calculated primes to 31622776 in 6.760 sec.<br />Calculated factors of 1000000000000037 in 8.466 sec.<br />[1000000000000037L]<br />>>> factorAndSieve(10**15+37,True)<br />Calculated factors of 1000000000000037 in 8.666 sec.<br />Stopped trial division at 31622775 instead of 31622776<br />[1000000000000037L]<br /><br />>>> factor(2**55,True)<br />Calculated primes to 189812531 in 42.811 sec.<br />Calculated factors of 36028797018963968 in 43.261 sec.<br />[2, ..., 2]<br />>>> factorAndSieve(2**55,True)<br />Calculated factors of 36028797018963968 in 8.632e-05 sec.<br />Stopped trial division at 3 instead of 189812531<br />[2, ..., 2]<br /></pre>Jaimehttp://www.blogger.com/profile/02831887559711602902noreply@blogger.com0tag:blogger.com,1999:blog-835341608950140555.post-69848604355369707472009-03-24T02:30:00.000+01:002009-03-24T02:42:20.034+01:00Prime Numbers 3. Wheel FactorizationIn an <a href="http://numericalrecipes.blogspot.com/2009/03/prime-numbers-2-sieving-of-erathostenes.html">earlier post</a>, I described the <a href="http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes">sieve of Eratosthenes</a>, a very fast algorithm to calculate all primes below a given limit, n. At least when programming it in python, speed is not an issue at all with this algorithm, because we start having memory problems much earlier, due to the fact that we need to keep in memory a list of size n...<br /><br />This is specially problematic with python, because although all we need is a list of booleans (is prime / is not prime), which could fit in a bit each, I'm afraid python assigns several bytes to each. How many of them is probably machine dependent, but I'm almost certain it is at least 4 of them. So it is very likely that we could improve memory usage by a factor of 32, maybe more, if we could get around this. There are <a href="http://pypi.python.org/pypi/bitarray/">not-so-standard libraries</a> that do just that, but I've sworn not to use them, and one day I may expose my failed attempts at using very long ints as bit arrays to the world, but python is definitely not the best language to mess around with low level optimizations, so I'm going to leave this line of thinking aside for the time being.<br /><br />I also presented a very naive optimization, which simply discards all even numbers, which we already know are not prime, and thus cuts in half the memory requirements. More formally, what was being done there was to <a href="http://en.wikipedia.org/wiki/Partition_of_a_set">partition</a> all numbers into two groups: those of the form 2·n, and those of the form 2·n+1, and then discard the former, since no prime of that form exists, and keep the latter only. This line of thinking is much more promising, as it leads to a very neat way of further reducing the memory required, known as <a href="http://en.wikipedia.org/wiki/Wheel_factorization">wheel factorization</a>.<br /><br />For some odd reason that escapes my shallow understanding, the standard description of this algorithm places much emphasis on setting all numbers in circles, and then eliminating what it calls spokes. I personally don't see any benefit of such a mental image, specially since the number are going to end up arranged in a rectangular array. So for the sake of clarity, I will not honor tradition, and spare you the circles and the spokes.<br /><br />We begin by choosing a first few prime numbers. Say the first three: 2, 3 and 5. We multiply them together to get M = 30. We are now going to partition the set of all numbers into M groups, of the form M·m + k<sub>i</sub>, where the k<sub>i</sub> are the numbers 1 through M, both inclusive. Most of these subsets happen to hold no prime number, and thus need not be considered at all. To find out which of them, remember that M is the product of several prime numbers, M = p<sub>1</sub>·p<sub>2</sub> ... p<sub>n</sub>. Therefore, if k<sub>i</sub> is a multiple of any of those prime numbers, i.e. k<sub>i</sub> = p<sub>j</sub>·k'<sub>i</sub>, we can always extract the common factor p<sub>j</sub> from both summands in M·m + k<sub>i</sub>, which means that all numbers in that subset are composite. These subsets, which we discard, are very closely related to the spokes I was telling you about a while ago.<br /><br />If M = 30, of the 30 potential k<sub>i</sub> we are left with just 8 (1, 7, 11, 13, 17, 19, 23 and 29), so we have cut memory required to about 27% of the straightforward implementation of the sieve of Eratosthenes. If we use the first 4 primes, we then have M = 210, and of the 210 potential k<sub>i</sub> we only need to keep 48. This improves memory use, which now is roughly 23% of the original. But you can probably see a <a href="http://en.wikipedia.org/wiki/Diminishing_returns">law of diminishing returns</a> at wortk here: the improvement in memory use gets smaller and smaller with every prime you add to the calculation of M. You can find some more data regarding this at the end of <a href="http://primes.utm.edu/glossary/page.php?sort=WheelFactorization">this link</a>.<br /><br />Of course we are still going to have to sieve each of our subsets, in much the same way we did with the sieve of Eratothenes. But sieving all multiples of a prime p when you have several subsets to consider is anything but trivial. Lets see how to tackle it...<br /><br />The question to answer is, what integer values of m fulfill the equation M·m + k<sub>i</sub> = p·n? Using <a href="http://en.wikipedia.org/wiki/Modular_arithmetic">modular arithmetic</a>, this equation is equivalent to finding M·m = -k<sub>i</sub> (mod p). This requires calculating M<sup>-1</sup> (mod p), the <a href="http://en.wikipedia.org/wiki/Modular_multiplicative_inverse">modular multiplicative inverse</a> of M modulo p, a subject already treated in <a href="http://numericalrecipes.blogspot.com/2009/03/modular-multiplicative-inverse.html">a previous post</a>. Given that, we can simply write m<sub>i</sub> = M<sup>-1</sup>·(-k<sub>i</sub>) (mod p), and therefore determine all multiples of p in the subset of k<sub>i</sub> as m = p·j + m<sub>i</sub>.<br /><br />Last, but not least, we want to start sieving at the smallest multiple of p in each subset that is larger than or equal to p<sup>2</sup>, and so we want to increment m<sub>i</sub> with p·j<sub>i</sub>, where j<sub>i</sub> >= (p<sup>2</sup>-k<sub>i</sub>- M·m<sub>i</sub>) / (m·p).<br /><br />Lets try to put all this ideas together in a python script. To make it work, you will need the <code>primeListSofE</code> function from <a href="http://numericalrecipes.blogspot.com/2009/03/prime-numbers-2-sieving-of-erathostenes.html">this post</a>, as well as the <code>extEuclideanAlg</code> and <code>modInvEuclid</code> ones from <a href="http://numericalrecipes.blogspot.com/2009/03/modular-multiplicative-inverse.html">this other post</a>...<br /><br /><pre class="brush: py"><br />def primeListWF(n, m, verbose = True) :<br /> """Returns a list of prime numbers smaller than or equal to n,<br /> using wheel factorization, with a wheel size of the product of<br /> all primes smaller than or equal to m<br /> """<br /> t = time()<br /> # We use the standard sieve of Eratosthenes first...<br /> primes = primeListSofE(m)<br /> M = 1<br /> for p in primes :<br /> M *= p<br /> if verbose :<br /> print "Size of factorization wheel is",M<br /> # We now sieve out all multiples of these primes,including<br /> # the primes themselves, to get the k[i]<br /> sieve = [True] * M<br /> for p in primes :<br /> sieve[p-1] = False<br /> for j in xrange(p*p,M+1,p) :<br /> sieve[j-1] = False<br /> k = [j+1 for j in range(M) if sieve[j]]<br /> if verbose :<br /> print "Memory use is only",len(k),"/",M,"=",len(k)/M<br /> N = n<br /> if N %M :<br /> N += M<br /> N //= M<br /> maxP = int(sqrt(M*N))<br /> if verbose:<br /> print "Primes will be calculated up to",M*N,"not",n<br /><br /> sieve = [[True]*N for j in range(len(k))]<br /> sieve[0][0] = False # Take care of the non primality of 1<br /> for row in range(N) :<br /> baseVal = M * row<br /> for subset in range(len(k)) :<br /> if sieve[subset][row] :<br /> p = baseVal + k[subset]<br /> primes += [p]<br /> if p > maxP :<br /> continue<br /> # Sieve all multiples of p...<br /> invMp = modInvEuclid(M,p)<br /> for i in range(len(k)) :<br /> mi = invMp * (-k[i] % p)<br /> mi %= p<br /> # ...starting at p**2<br /> ji = (p*p - k[i] - M*mi)<br /> if ji % (M*p) :<br /> ji += M*p<br /> ji //= M*p<br /> mi+= ji*p<br /><br /> for r in range(mi,N,p) :<br /> sieve[j][r] = False<br /> if verbose :<br /> print "Calculated primes to",n,"in",time()-t,"sec."<br /> <br /> return primes<br /></pre><br /><br />You can play around with this algorithm and look at the time required. It probably depends on the value of n, but there seems to be a sweet spot for speed somewhere around m = 7 (and thus M = 210) or m = 11 (M = 2310). This being a general purpose wheel factorization algorithm, it has speeds that are faster than the unoptimized sieve of Eratosthenes code, but it cannot beat the optimized code, which in fact is a particular case of wheel factorization, for m = M = 2.<br /><br />On the other hand, using m = 7 I have managed to compute all primes up to 10<sup>8</sup>, a feat unreachable for the previous code, at least without finding a workaround the <code>MemoryError</code> they produce. 10<sup>9</sup> is still out of reach, though...Jaimehttp://www.blogger.com/profile/02831887559711602902noreply@blogger.com0tag:blogger.com,1999:blog-835341608950140555.post-45486059903580515632009-03-20T14:01:00.010+01:002009-03-22T21:52:13.829+01:00Modular Multiplicative InverseNow that we are all proficient with <a href="http://en.wikipedia.org/wiki/Modular_exponentiation">modular exponentiation</a> thanks to my <a href="http://numericalrecipes.blogspot.com/2009/03/modular-exponentiation.html">previous post</a>, it is time to tackle a more complicated issue, still in the realm of <a href="http://en.wikipedia.org/wiki/Modular_arithmetic">modular arithmetic</a>.<br />While addition, subtraction and multiplication are "compatible with the congruence relation" introduced by modular arithmetic, the same doesn't happen with division. This means that solving a simple equation such as a·x = 1 (mod m) is anything but trivial. In fact, determining x = a<sup>-1</sup> (mod m) that fulfills that equation is the whole object of this post...<br /><br />There is, as always, the brute force approach, which I always like to visit first, so that I can later congratulate myself on how much the efficiency has improved with my deep insight. In this particular case, brute force means trying every number between 1 and m-1, and see if the m-modulo of its product with a is 1. :<br /><pre class="brush: py"><br />def modInv1(a,m) :<br /> """Computes the modular multiplicative inverse of a modulo m,<br /> using brute force<br /> """<br /> a %= m<br /> for x in range(1,m) :<br /> if a*x%m == 1 :<br /> return x<br /> return None<br /></pre><br />Do notice that the possibility of no multiplicative inverse existing is contemplated in the code. This will actually be the case if a and m have any common factors, i.e. if they are not <a href="http://en.wikipedia.org/wiki/Coprime">coprime</a>. So the previous code could serve as a moron's approach to determining if two numbers are relatively prime. Not that I recommend it, though, as it should be clear that the code I have just produced will be very inefficient for large values of m.<br /><br /><span style="font-weight: bold;">The Extended Euclidean Algorithm</span><br />As stated, we are after a number x that fulfills a·x = 1 (mod m). This can of course be written as well as a·x = 1 + m·y, which rearranges neatly into a·x - m·y = 1. Since x and y need not be positive, we can write it as well in the standard form, a·x + m·y = 1. If a and m are coprime, this is a particular instance of a·x + b·y = gcd(a,b), where gcd(a,b) is the <a href="http://en.wikipedia.org/wiki/Greatest_common_divisor">greatest common divisor</a> of a and b. This equatiin is known as <a href="http://en.wikipedia.org/wiki/B%C3%A9zout%27s_identity">Bézout's identity</a>, and can be efficiently solved using the <a href="http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm">extended Euclidean algorithm</a>. To find a solution to our particular equation, this algorithm would proceed as follows:<br /><ol><li>Rewrite the equation as a<sub>n</sub>·x<sub>n</sub> + m<sub>n</sub>·y<sub>n</sub> = 1, starting with n = 0 </li><li>Compute the quotient, q<sub>n</sub>, and the remainder, r<sub>n</sub>, of a<sub>n</sub> divided by m<sub>n</sub>, and rewrite the original equation as (m<sub>n</sub>·q<sub>n</sub> + r<sub>n</sub>)·x<sub>n</sub> + m<sub>n</sub>·y<sub>n</sub> =1<br /></li><li>Rearrange the terms in the equation to get m<sub>n</sub>·(q<sub>n</sub>·x<sub>n</sub>+y<sub>n</sub>) + r<sub>n</sub>·x<sub>n</sub> = 1<br /></li><li>Do the changes of variables<br /><ul><li>x<sub>n+1</sub> = q<sub>n</sub>·x<sub>n</sub>+y<sub>n</sub>,<br /></li><li>y<sub>n+1</sub> = x<sub>n</sub>,<br /></li><li>a<sub>n+1</sub> = m<sub>n,</sub> and<br /></li><li>m<sub>n+1</sub> = r<sub>n</sub><br /></li></ul></li><li>Repeat the process, until a<sub>n</sub> = 1, and m<sub>n</sub> = 0.<br /></li><li>Such equations has the trivial solution x<sub>n</sub> = 1, y<sub>n</sub> = 0.<br /></li><li>Work your way back to x<sub>0</sub> and y<sub>0</sub>, noting that<br /><ul><li>x<sub>n-1</sub> = y<sub>n</sub>,<br /></li><li>y<sub>n-1</sub> = x<sub>n</sub> - q<sub>n-1</sub>·y<sub>n</sub><br /></li></ul></li><li>x<sub>0</sub> is the number we are after.<br /></li></ol>There is a certain component of faith in believing that, eventually, a<sub>n</sub> = 1 and m<sub>n</sub> = 0. It is in fact not always the case, as if a and m are not coprime, then the values we will arrive at are m<sub>n</sub> = 0, but a<sub>n</sub> = gcd(a,m). As a matter of fact, the gcd is normally computed using the <a href="http://en.wikipedia.org/wiki/Euclidean_algorithm">Euclidean algorithm</a>, dropping the adjective extended, which basically does the same as above, but without the back-tracking of the x<sub>n</sub>'s and the y<sub>n</sub>'s. With all of this in mind, we can write two functions, one calling the other, as follows:<br /><pre class="brush: py"><br />def extEuclideanAlg(a, b) :<br /> """Computes a solution to a x + b y = gcd(a,b), as well as gcd(a,b)<br /> """<br /> if b == 0 :<br /> return 1,0,a<br /> else :<br /> x, y, gcd = extEuclideanAlg(b, a % b)<br /> return y, x - y * (a // b),gcd<br /><br />def modInvEuclid(a,m) :<br /> """Computes the modular multiplicative inverse of a modulo m,<br /> using the extended Euclidean algorithm<br /> """<br /> x,y,gcd = extEuclideanAlg(a,m)<br /> if gcd == 1 :<br /> return x % m<br /> else :<br /> return None<br /></pre><br /><br />The gains in speed are tremendous with this new approach. Again, write your own testing function as an exercise, but mine spits out the following results...<br /><code><br />>>> testModInv(1234,5,10000)<br />Got 4 using method 1 in 0.0160000324249 sec.<br />Got 4 using Euclidean algorithm in 0.0309998989105 sec.<br /><br />>>> testModInv(1234,56789,10000)<br />Got 31800 using method 1 in 59.4000101089 sec.<br />Got 31800 using Euclidean algorithm in 0.047000169754 sec.<br /></code><br />So while brute force is good enough, or even the best choice, for very small values of m, as soon as it grows, the benefits of the more elaborate algorithm are all too obvious. According to wikipedia, the algorithm we have coded has complexity O(log(m)<sup>2</sup>), i.e. time required to compute the modular multiplicative inverse is proportional to log(m)<sup>2</sup>.Jaimehttp://www.blogger.com/profile/02831887559711602902noreply@blogger.com1tag:blogger.com,1999:blog-835341608950140555.post-41007173550614560492009-03-20T12:08:00.013+01:002009-03-25T18:22:08.757+01:00Modular ExponentiationI have a post cooking on <a href="http://en.wikipedia.org/wiki/Wheel_factorization">wheel factorization</a>, yet another sieving algorithm for the computation of all primes below a given number n. It turns out that while implementing it, one comes across a number of not so obvious preliminary questions. It is a rule of this blog not to resort to any non standard library, and I feel it would be breaking the spirit of that rule to post code which hasn't been thoroughly explained in advance. So in order not to make that soon-to-be post on prime numbers longer than anyone could bear, I find myself in need of covering a couple of preliminaries, and so here comes this thrilling post on <a href="http://en.wikipedia.org/wiki/Modular_exponentiation">modular exponentiation</a>...<br /><br />What we are after is determining a<sup>b</sup> (mod m), or to put it in plain English, the remainder of a to the power of b, divided by m. Doesn't seem much, does it? We could just go ahead and write:<br /><pre class="brush: py"><br />def modExp1(a, b, m) :<br /> """Computes a to the power b, modulo m, directly<br /> """<br /> return a**b % m<br /></pre><br />The problems begin if a and b are both big. Python's integers don't overflow, and we all love it for it, but the memory juggling it has to do to accommodate a huge number in memory does come at a speed cost. Specially if m is small, it definitely seems a little overkill to keep a zillion digit number in memory, given that the answer will be smaller than m...<br /><br />We can, and will, use <a href="http://en.wikipedia.org/wiki/Modular_arithmetic">modular arithmetic</a> to our advantage. See, wikipedia says that "the congruence relation (introduced by modular arithmetic) on the integers is compatible with the multiplication operation of the ring of integers," and if it's on the internet it has to be true, right? Translated to plain English, the idea is that if we want to take the product of two numbers, a and b, modulo m, we can first multiply them, and then take the m-modulo of the result, or we can take the m-modulo of a and/or b, prior to multiplication, and the m-modulo of the result will still be the right answer. So the least a conscious programmer should do is to rewrite the previous function as:<br /><pre class="brush: py"><br />def modExp2(a, b, m) :<br /> """Computes a to the power b, modulo m, a little less directly<br /> """<br /> return (a%m)**b % m<br /></pre><br />Of course, if b is large enough, you may still find yourself in the realm of zillion digit numbers, which is a place we don't really want to go. It turns out then that it may be a good option to remember that exponentiation is just repeated multiplication. If we multiply a by itself b times, and take the m-modulo of the result, the larger number we will ever have in memory will be m<sup>2</sup>. It doesn't really seem right to put a loop of b iterations into the code, since what we are worried about are large b's, but lets do it anyway:<br /><pre class="brush: py"><br />def modExp3(a, b, m) :<br /> """Computes a to the power b, modulo m, by iterative multiplication<br /> """<br /> ret = 1<br /> a %= m<br /> while b :<br /> ret *= a<br /> ret %= m<br /> b -= 1<br /> return ret<br /></pre><br />The only reason why this last function deserves a place here, is because it helps introduce the really cool algorithm that this whole post really is about, <a href="http://en.wikipedia.org/wiki/Binary_exponentiation">binary exponentiation</a>. In <a href="http://www.johndcook.com/blog/2008/12/10/fast-exponentiation/">John D. Cook's blog</a> there is a great explanation on how it could be implemented iteratively, based on the binary representation of the exponent, but I'll go for a recursive version, which is essentially the same, but which I find easier for my brain's wiring...<br /><br />So what we are about to do is, when asked to compute a<sup>b</sup>, check if b is even, in which case we will compute it as the square of a<sup>b/2</sup>. If b is odd, we will compute it as a a<sup>b-1</sup>. If b is 0, we will of course return 1. If we also throw in all the modulo m stuff I have been discussing previously, we finally arrive at THE function to do modular exponentiation:<br /><pre class="brush: py"><br />def modExp(a, b, m) :<br /> """Computes a to the power b, modulo m, using binary exponentiation<br /> """<br /> a %= m<br /> ret = None<br /> <br /> if b == 0 :<br /> ret = 1<br /> elif b%2 :<br /> ret = a * modExp(a,b-1,m)<br /> else :<br /> ret = modExp(a,b//2,m)<br /> ret *= ret<br /> <br /> return ret%m<br /></pre><br />Now that we have all four versions of the same function written, we can do a little testing, I will leave writing the testing code as an exercise for the reader (I've been wanting to say this since my first year of university...), but when computing 1234 to the power 5678, modulo 90, a 1000 times with each function, these is the performance I get:<br /><code><br />>>> testModExp(1234,5678,90,1000)<br />Got 46 using method 1 in 3.73399996758 sec.<br />Got 46 using method 2 in 0.546999931335 sec.<br />Got 46 using method 3 in 1.25 sec.<br />Got 46 using THE method in 0.0150001049042 sec.<br /></code><br />Results which really don't require much commenting...<br /><br /><span style="font-weight:bold;">EDIT:</span> There is some very interesting information in the comments: python comes with a built-in <code>pow<code> function, which can take two or three arguments, the third being, precisely, the modulo. It probably implements an algorithm similar to what has been presented, but is more than an order of magnitude faster.Jaimehttp://www.blogger.com/profile/02831887559711602902noreply@blogger.com2tag:blogger.com,1999:blog-835341608950140555.post-36115953978544703852009-03-16T00:12:00.016+01:002009-03-21T08:43:29.743+01:00Prime Numbers 2. The Sieve of ErathostenesThe problem with the algorithms described in the <a href="http://numericalrecipes.blogspot.com/2009/03/prime-numbers_04.html">previous post</a> lies on the very large amount of redundant testing that goes on. Because once we have checked the primality of a certain number p, we know that 2p, 3p, 4p, ..., np are composites. So what is the point of checking those numbers at all? When you follow this line of thinking to the very end, it turns out you don't have to do any trial division at all! The resulting algorithm is attributed to <a href="http://en.wikipedia.org/wiki/Eratosthenes">Erathostenes</a>, and it employs a technique, known as <a href="http://en.wikipedia.org/wiki/Sieve_theory">sieving</a>, which may come in handy in many other problems. But enough for a presentation, lets get going with the <a href="http://en.wikipedia.org/wiki/Sieve_of_Eratosthenes">Sieve of Erathostenes</a>.<br /><br />All the information we need to begin with, is that 2 is the first prime. This means, following the previous approach, that no other even number can be prime. To keep track of this information, we begin by writing a list of all the numbers between 2 and the desired limit, say N:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://2.bp.blogspot.com/_47tBuP0my9o/ScEwu3RTIyI/AAAAAAAAAHI/zBdrGZgQ0Z0/s1600-h/sieving1.png"><img style="cursor: pointer; width: 400px; height: 19px;" src="http://2.bp.blogspot.com/_47tBuP0my9o/ScEwu3RTIyI/AAAAAAAAAHI/zBdrGZgQ0Z0/s400/sieving1.png" alt="" id="BLOGGER_PHOTO_ID_5314582616942519074" border="0" /></a><br /><br />We now draw a circle around 2, because it is prime, and then strike out all even numbers, i.e. all multiples of 2:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_47tBuP0my9o/ScEx_oQWp2I/AAAAAAAAAHg/WUq50_6K0aM/s1600-h/sieving2.png"><img style="cursor: pointer; width: 400px; height: 34px;" src="http://4.bp.blogspot.com/_47tBuP0my9o/ScEx_oQWp2I/AAAAAAAAAHg/WUq50_6K0aM/s400/sieving2.png" alt="" id="BLOGGER_PHOTO_ID_5314584004481427298" border="0" /></a><br /><br />Now, there is a good and a bad way to strike out all multiples of a number from a list. The wrong way is to do trial division to find out if it is a multiple. The right way is to use the fact that two consecutive multiples of a number differ by that exact number. Writing it out in python, you can check the tremendous gain for yourself:<br /><pre class="brush: py"><br />from time import time<br /><br />def strikeOut(mul, n) :<br /><br /># note that list index i corresponds to number i+2<br />sieve = [True for j in xrange(2,n+1)]<br /><br /># The bad way to sieve<br />t = time()<br />for j in xrange(2,n+1) :<br /> if j % mul == 0 :<br /> sieve[j-2] = False<br />t = time() - t<br />print "Sieved (bad!!!) all multiples of",mul,"below",n,"in",t,"sec."<br /><br />sieve = [True for j in xrange(2,n+1)]<br /><br /># The good way to sieve<br />t = time()<br />for j in xrange(mul,n+1,mul) :<br /> sieve[j-2] = False<br />t = time() - t<br />print "Sieved (good!!!) all multiples of",mul,"below",n,"in",t,"sec."<br /></pre><br />No, I'm not taking you, dear reader, for an idiot: you can find sieving algorithms on fist pages of google searches that do sieving this very wrong way...<br /><br />But lets get back to primes. We had our list, with 2 circled and all its multiples crossed out. The first unmarked number we come across is 3. Just a coincidence that 3 is a prime number? Not really: it it is unmarked because it is not a multiple of any smaller number. Or to put it another way: it is only divisible by 1 and itself, which is hte very definition of prime... So we circle 3 and cross out all its multiples:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://1.bp.blogspot.com/_47tBuP0my9o/ScEx0khjENI/AAAAAAAAAHY/ib6-anRapmA/s1600-h/sieving3.png"><img style="cursor: pointer; width: 400px; height: 39px;" src="http://1.bp.blogspot.com/_47tBuP0my9o/ScEx0khjENI/AAAAAAAAAHY/ib6-anRapmA/s400/sieving3.png" alt="" id="BLOGGER_PHOTO_ID_5314583814501241042" border="0" /></a><br /><br />I've underlined the multiples of three that where already crossed out. The first number which gets crossed out for the first time is 9. Will it be just a coincidence that 9 = 3<sup>2</sup>? Of course not! We have already crossed out all multiples of numbers smaller than 3, and 3<sup>2</sup> is the first multiple of 3 which is not a multiple of any smaller number.<br /><br />So we keep going down our list, and find 5 to be the next unmarked number in the list, which of course is prime, so we circle it, and then cross out all of its multiples. But as we saw before, rather than starting at 10, and crossing out the already crossed out 10, 15 and 20, we proceed directly to 5<sup>2</sup>, that is 25.<br /><br />This goes on until we've searched the whole list, circling numbers and crossing out its multiples. But since we start crossing out at the square of the new found prime, we only need to do the crossing out for primes smaller than the square root of N: once we get there, any uncrossed number up to N will be prime. This is what the beginning of our list will look like once we are through:<br /><br /><a onblur="try {parent.deselectBloggerImageGracefully();} catch(e) {}" href="http://4.bp.blogspot.com/_47tBuP0my9o/ScE1UJG8qGI/AAAAAAAAAHo/PcMD2_SQ2Yc/s1600-h/sieving4.png"><img style="cursor: pointer; width: 400px; height: 41px;" src="http://4.bp.blogspot.com/_47tBuP0my9o/ScE1UJG8qGI/AAAAAAAAAHo/PcMD2_SQ2Yc/s400/sieving4.png" alt="" id="BLOGGER_PHOTO_ID_5314587655432611938" border="0" /></a><br /><br />So lets try and write our very own sieve of Erathostenes in python, with all this square and square root tricks pointed out above...<br /><pre class="brush: py"><br />from time import time<br />from math import sqrt<br /><br />def primeListSofE(n, verbose = True) :<br /> t = time()<br /> sieve = [True for j in xrange(2,n+1)]<br /> for j in xrange(2,int(sqrt(n))+1) :<br /> i = j-2<br /> if sieve[i]:<br /> for k in range(j*j,n+1,j) :<br /> sieve[k-2] = False<br /><br /> ret = [j for j in xrange(2,n+1) if sieve[j-2]]<br /><br /> if verbose :<br /> print "Calculated primes to",n,"in",time()-t,"sec."<br /><br /> return ret<br /></pre><br />Play around with it and once more feel the thrill of speed. On my computer it does all primes below 1,000,000 almost 5 times faster than the best trial division approach from the previous post. But the real beauty is in how the time now scales with n, because you can no longer find a suitable fit of the form n<sup>q</sup>. We haven't simply reduced the size of the exponent, but are treading uncharted territory, as it turns out that the time required to calculate all primes below using this sieve requires (n.log(n))(log(log(n))) time. Actually, for the first 10<sup>10</sup> numbers, this is basically indistinguishable from linear time, i.e proportional to n.<br /><br />This speed does come at a memory cost, though: you need to store an array of size n. And while the algorithm could produce all primes below 10<sup>10</sup> in just a few minutes, the program will very likely crash giving an out of memory error. Some memory saving can be done by not storing even numbers at all. Without boring you with the details, below is the python function I use to compute primes at work, which does 10<sup>7</sup> almost three times faster than the previous one, and uses half the memory, but still crashes when asked for all primes below 10<sup>9</sup>...<br /><br /><pre class="brush: py"><br />from time import time<br />from math import sqrt<br /><br />def primeListSofE(n, verbose = False) :<br /> """Returns a list of prime numbers smaller than or equal to n,<br /> using an optimized sieve of Erathostenes algorithm.<br /> """<br /> t = time()<br /> maxI = (n-3) // 2<br /> maxP = int(sqrt(n))<br /> sieve = [True for j in xrange(maxI+1)]<br /> for p in xrange(3,maxP+1,2) :<br /> i = (p-3) // 2<br /> if sieve[i] :<br /> i2 = (p*p-3) // 2<br /> for k in xrange(i2,maxI+1,p) :<br /> sieve[k] = False<br /> ret = [2]+[2*j+3 for j in xrange(len(sieve)) if sieve[j]]<br /> if verbose :<br /> print "Calculated primes to",n,"in",time()-t,"sec."<br /> return ret<br /></pre><br /><br />There are slightly faster ways of sieving, and we could <a href="http://en.wikipedia.org/wiki/Memoization">memoize</a> precalculated primes, to speed up repeated calls to the function. But this is probably enough for today's post...Jaimehttp://www.blogger.com/profile/02831887559711602902noreply@blogger.com4tag:blogger.com,1999:blog-835341608950140555.post-76313065345935384852009-03-05T14:50:00.004+01:002009-03-20T17:56:53.860+01:00Prime Numbers 1. The Wrong WayA <a href="http://en.wikipedia.org/wiki/Prime_number">prime number</a> is a natural number larger than 1 which is only divisible by 1 and itself. Simple, isn't it? <a href="http://en.wikipedia.org/wiki/Prime_number">Euclid</a> already <a href="http://www-users.cs.york.ac.uk/%7Esusan/cyc/p/primeprf.htm">proved</a> quite a while ago that there are infinitely many of them, so lets try and write a function that returns all primes below a given number, using the most straightforward approach. Whta would that be? Well, there is nothing less sophisticated than try and divide each number by every integer larger than 1 and smaller than the number itself, and if we never get a zero remainder then the number is prime. Putting it in python:<br /><pre class="brush: py"><br />from time import time<br /><br />def primeList1(n, verbose = True) :<br />t = time()<br />ret = []<br />for j in range(2,n+1) :<br /> jIsPrime = True<br /> for k in range(2,j) :<br /> if j % k == 0 :<br /> jIsPrime = False<br /> break<br /> if jIsPrime :<br /> ret += [j]<br />if verbose :<br /> print "Calculated primes to",n,"in",time()-t,"sec."<br /><br />return ret<br /></pre><br />Do notice the feeble attempt at code optimization, with the <code>break</code> statement that ends the loop as soon as a divisor is found... If you try it out, it will work fine. Use it to calculate all prime numbers below 1,000, and it will probably give you an instant answer. 10,000 delays a second or two, nothing too worrying. If you try it with 100,000, you'll have time to take your coffee break before it finishes, and adding any more zeroes will require leaving your script running overnight... Not too surprising really, since to determine that, for example, 9973 is indeed a prime number, our program is having to do trial division by every number between 2 and 9972, both inclusive. All in all, the time required grows with n<sup>2</sup>.<br /><br />There are several very simple improvements that can speed things up dramatically, and which require a little, but just a little, mathematical insight...<br /><br />First, we don't need to test every number between 2 and 9972 to determine that 9973 is prime; we can stop after the largest integer smaller than the square-root of 9973, i.e. 99. Why? Well, if a number is divisible by a number larger than its square-root, it will also be divisible by the quotient of that division, which is necesaarily smaller than the square-root. And since we have already tried (and failed) to divide by those smaller numbers, there is no need to keep going...<br /><br />The resulting code is very similar to the previous one:<br /><pre class="brush: py"><br />from time import time<br />from math import sqrt<br /><br />def primeList2(n, verbose = True) :<br />t = time()<br />ret = []<br />for j in range(2,n+1) :<br /> jIsPrime = True<br /> kMax = int(sqrt(j))<br /> for k in range(2,kMax+1) :<br /> if j % k == 0 :<br /> jIsPrime = False<br /> break<br /> if jIsPrime :<br /> ret += [j]<br /><br />if verbose :<br /> print "Calculated primes to",n,"in",time()-t,"sec."<br /><br />return ret<br /></pre><br /><br />Do try it, and feel the thrill of the new speed of the algorithm. If you are after all primes below 10,000, this new function will probably get them for you... a hundred times faster!!! If you try it for larger numbers, you will find even better speed increases, because the time required by our algorithm now grows with n<sup>1.4</sup>.<br /><br />But we are still doing a lot of superfluous checking. Think again of 9973. Once we have tried to divide it by 2 and failed, there is no point in checking with 4, 6, 8 or any other multiple of 2. So yes, we could double the speed of our algorithm by checking only odd numbers, but lets spend a little more time thinking before going back to coding... Because, after failing with 2, once we turn our attention to 3 and again fail, what's the point in trying 6, 9, 12 or any other multiple of 3? If you keep the reasoning going, it becomes obvious that you only need to do trial division by prime numbers, since by the time we test whether a composite number divides 9973, we have already tested all its prime factors, and if those have failed, this one will as well.<br /><br />Now wait a minute! Isn't finding whether a number is prime or not precisely what we are after? How are we going to figure out what numbers to do trial division with? Well, we already saw in our first optimization that our division trials only need to go up to the square root of the number being tested, so we only need to know the first prime number, 2, and afterwards we can use the list of prime numbers that we are building to generate the additional items... The code to do that would look something like this:<br /><br /><pre class="brush: py"><br />from time import time<br />from math import sqrt<br /><br />def primeList3(n, verbose = True) :<br /> t = time()<br /> ret = []<br /> for j in range(2,n+1) :<br /> jIsPrime = True<br /> kMax = sqrt(j)<br /> for k in ret :<br /> if k > kMax :<br /> break<br /> if j % k == 0 :<br /> jIsPrime = False<br /> break<br /> if jIsPrime :<br /> ret += [j]<br /><br /> if verbose :<br /> print "Calculated primes to",n,"in",time()-t,"sec."<br /><br /> return ret<br /></pre><br /><br />If you test this code you'll surely notice the new speed increase. This last optimization doesn't make it a hundred times faster, but on my computer the new code is 3.5 faster than the previous one when calculating all primes below 100,000 and 5.5 times faster when calculating all primes below 1,000,000. And again, what really makes the new code more efficient is not that 4x increase in speed, but the fact that, the larger our input number gets, the better the new code is. It seems that now time required grows with n<sup>1.25</sup>...<br /><br />THis last version holds the fundamental ideas of really fast versions, i.e. skipping multiples of primes, but still relies on the slow ans tie consuming operation of trial division. Getting rid of it is the next optimization, which we will see in the next post.Jaimehttp://www.blogger.com/profile/02831887559711602902noreply@blogger.com4tag:blogger.com,1999:blog-835341608950140555.post-70905474234206505192009-03-04T18:36:00.013+01:002009-03-15T23:45:51.148+01:00Excusatio Non PetitaThe year probably was 1994, so I guess its understandable that I don't exactly recall how did I come across a copy of <a href="http://www.amazon.com/Numerical-Recipes-C-Scientific-Computing/dp/0521431085/ref=sr_1_9?ie=UTF8&s=books&qid=1236206282&sr=8-9">Numerical Recipes in C: The Art of Scientific Computing</a>. But while skimming through the pages, I eventually got to the part on evaluating polynomials, and read something that struck me very deeply. The paragraph that described what I now know is <a href="http://en.wikipedia.org/wiki/Horner_scheme">Horner's scheme</a> read as follows:<br /><blockquote>We assume that you know enough never to evaluate a polynomial this way:<br /><br />p=c[0]+c[1]*x+c[2]*x*x+c[3]*x*x*x +c[4]*x*x*x*x;<br /><br />or (even worse!),<br /><br />p=c[0]+c[1]*x+c[2]*pow(x,2.0)+c[3]*pow(x,3.0)+c[4]*pow(x,4.0);<br /><br />Come the (computer) revolution, all persons found guilty of such criminal behavior will be summarily executed, and their programs won’t be! It is a matter of taste, however, whether to write<br /><br />p=c[0]+x*(c[1]+x*(c[2]+x*(c[3]+x*c[4])));<br /><br />or<br /><br />p=(((c[4]*x+c[3])*x+c[2])*x+c[1])*x+c[0];</blockquote><br />Boy was I guilty! Who would have ever thought there was another way of evaluating a polynomial than the obvious one? And that it would be so much more elegant and efficient?<br /><br />Would be nice if it was just a polynomial thing, but of course it isn't, and the more complicated the math you are trying to get your computer to do, the larger the chance you are doing it terribly bad. And although much has been written about algorithms, the gospel doesn't seem to be spreading too swiftly: as I am writing this, the first match for a search of "prime numbers python " that google returns is <a href="http://www.gidforums.com/t-9440.html" target="_blank">this</a>. And he is <a href="http://pthree.org/2007/09/05/prime-numbers-in-python/" target="_blank">not alone</a>...<br /><br />So what am I in for? The idea is to bridge the gap between straightforward brute force (a sin I have been as guilty as anyone else of) and the elegant simplicity of efficient code. Hopefully, this will allow me to escape the guillotine when the Jacobins take over...<br /><br />There are some simple rules that I intend to follow, at least until I decide not to:<br /><ol><br /> <li><strong>Uncomplicated complexity.</strong> The math content will be kept reasonably simple. Or maybe simple isn't the word, as things may get utterly complex. But I will try to stay out of unnecessary complications, so a high school diploma should be all you need to come along on the ride...</li><br /> <li><strong>Plain python.</strong> Sure, C++ is a zillion times faster, but python takes care of most gory details, and makes it easier to concentrate on the algorithm. Plus, it's so cool and trendy!</li><br /> <li><strong>No libraries</strong>. <a href="http://www.sagemath.org/" target="_blank">Sage</a>, <a href="http://www.scipy.org/" target="_blank">SciPy</a>, <a href="http://pari.math.u-bordeaux.fr/" target="_blank">PARI</a>, <a href="http://www.qhull.org/" target="_blank">qhull</a>, <a href="http://www.fftw.org/" target="_blank">FFTW</a>... whatever it is you are trying to achieve computationally, there is a faster way than replicating the code you find in this blog. And interesting as that may be, it would take a lot of the charm of writing this blog, if it turned into a description of somebody else's API. So yes, we will even code our own FFTs!</li><br /></ol>Jaimehttp://www.blogger.com/profile/02831887559711602902noreply@blogger.com5