I just released my paper - On the Infinity of Twin Primes and other K-tuples - and thought it would be of interest here how I used Ruby|Crystal to compute the data presented in the paper.
https://www.academia.edu/41024027/On…other_K-tuples
https://www.scribd.com/document/4364…other-K-tuples
I used Ruby version 2.7.0-preview2 (final due Christmas 2019, as usual) to write my Ruby scripts. It has the nice Enumerable#tally method, which made life really simple for me. I also used my Rubygem primes-utils to generate the primes (which I have a working Crystal version of, but haven’t released yet).
So after installing primes-utis I can do oneliners as below to find all the prime gaps within any range p to p*p, as long as p isn’t too large.
require 'primes/utils'
2.7.0-preview1 :037 > n = 5; n.primes(n*n).each_cons(2).map{ |a,b| b-a }.tally.sort
=> [[2, 3], [4, 3]]
2.7.0-preview1 :038 > n = 7; n.primes(n*n).each_cons(2).map{ |a,b| b-a }.tally.sort
=> [[2, 4], [4, 5], [6, 2]]
2.7.0-preview1 :039 > n = 11; n.primes(n*n).each_cons(2).map{ |a,b| b-a }.tally.sort
=> [[2, 8], [4, 9], [6, 7], [8, 1]]
2.7.0-preview1 :040 > n = 13; n.primes(n*n).each_cons(2).map{ |a,b| b-a }.tally.sort
=> [[2, 9], [4, 11], [6, 10], [8, 1], [10, 1], [14, 1]]
2.7.0-preview1 :041 > n = 17; n.primes(n*n).each_cons(2).map{ |a,b| b-a }.tally.sort
=> [[2, 16], [4, 14], [6, 17], [8, 1], [10, 3], [12, 2], [14, 1]]
2.7.0-preview1 :042 > n = 19; n.primes(n*n).each_cons(2).map{ |a,b| b-a }.tally.sort
=> [[2, 17], [4, 17], [6, 19], [8, 1], [10, 5], [12, 2], [14, 3]]
2.7.0-preview1 :043 > n = 23; n.primes(n*n).each_cons(2).map{ |a,b| b-a }.tally.sort
=> [[2, 21], [4, 23], [6, 25], [8, 7], [10, 7], [12, 4], [14, 3]]
2.7.0-preview1 :044 > n = 29; n.primes(n*n).each_cons(2).map{ |a,b| b-a }.tally.sort
=> [[2, 29], [4, 30], [6, 38], [8, 12], [10, 15], [12, 7], [14, 4], [18, 1]]
But to process the gaps for very large p, I had to get more creative to get past large memory usage, and time. So I first created a Ruby version to count residue gaps where I directly computed the residues in order, and then computed|counted the gap size between all consecutive residues. When I got it to work correctly, I translated it to Crystal for speed. Here’s the Crystal version.
require "big"
def gaps(p)
h = Hash(UInt64, UInt64).new(0)
if p == 2; h[2] = 1; return h end
primes = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
modpg = primes[0..primes.index(p)].product(1u64)
rescnt = primes[0..primes.index(p)].map{ |p| p-1 }.product(1u64)
puts "modp#{p} = #{modpg}"
puts "rescnt = #{rescnt}"
pc, inc = 5u64, 2
#r0 = second = [primes.index(p)+1..primes.index(p)+1]
#first = primes[primes.index(p)+2..primes.index(p)+2]
#second, first = primes[primes.index(p)+1..primes.index(p)+2]
#r0 = second
first = 43u64
second = r0 = 41u64
limit = modpg >> 1
while pc < limit
if pc.gcd(modpg) == 1
first = pc
gap = first - second
h[gap] += 1
second = first
print "\r #{(first * 100)/limit}%"
end
pc += inc; inc = inc ^ 0b110
end
puts
h.delete(0)
h = h.to_a.map{ |k,v| (k == 2 || k == 4) ? [k,2*v+1] : [k,2*v]}.to_h
if h[r0 - 1] == nil; h[r0 - 1] = 2 else h[r0 - 1] += 2 end
h.to_a.sort_by { |key, value| key }
end
puts gaps 37
A problem with this version is the commented out section creates compiler errors because it squawks raising nil errors for the index values I’m trying to get. So I just hard code the values in so I can run it and get my results.
This routine is what I use to generate the data in Fig 3. in the paper.
I’m currently running this Crystal script to generate all the residue gaps for P37. I started it on a 2011 Lenovo laptop with an I5, 4 core cpu, and 6 GB of ram. But since this version is single threaded the extra cores don’t help me. My real work computer is a System76, I7 - 8 threads laptop, which I have to do all my other stuff with.
I began running it on Sunday, November 10, 2019 at 10:45 am. At time of writing (atow), Saturday, November 23, 4:35 pm, it’s now 60%+ done, clocking in at about 1% every 5+ hours, with estimated finish in 21/22 days, or by November 30, 2019
I can go faster by splitting the residues range into segments and doing them in parallel. I’d like to try to do this in Crystal to give me an incentive to learn its new parallel programming powers (PPP), and produce the data for the next larger generator P41, which has 40 times more residues than P37 {(41 - 1) for p = 41.}
If anyone wants to play with it please let me know.
Any suggestions to speed it up, fix the indexing issue, and make the code more efficient, are welcome.