Twin primes¶

Introduction¶

A positive integer $n$ is called prime if the only positive integers that divide $n$ are $1$ and itself. For example, $7$ is prime since it is only divisible by $1$ and $7$, while $8$ is not prime since it is divisble by $2$ and $4$. Prime numbers play a key role in the study of integers, and thus are of great interest to mathematicians. For example, mathematicians have long studied the density and distribution of prime numbers. Note that, aside from the pair of primes ($2, 3$), no other prime numbers are adjacent (since $2$ is the only even prime number). That is, no other pair of prime numbers differ by $1$. In this report, we are interested in studying pairs of prime numbers that differ by $2$, which we will call twin primes.

Finding prime numbers¶

In order to find twin primes, it will first be useful to be able to find prime numbers. To that end, we will introduce a function is_prime that takes in an integer n and returns True if n is prime, and False otherwise.

... more explanation on how is_prime works, including an explanation about the sqrt(n) optimization that was discussed in class...

In [1]:
# Import the square root function
from math import sqrt

def is_prime(n):
    # Check for divisors between 2 and sqrt(n)
    for d in range(2,int(sqrt(n))+1):
        # Check if d divides n
        if n % d == 0:
            return False
    return True

We now test the use of is_prime on some known examples of prime and non-prime numbers:

In [2]:
is_prime(7)
Out[2]:
True
In [3]:
is_prime(8)
Out[3]:
False

We see that the function correctly identifies 7 as prime and 8 as not prime.

We can use the is_prime function to easily generate lists of prime numbers. To do so, we will define a function get_primes that takes in an integer N and returns a list containing all primes n such that n < N.

In [4]:
def get_primes(N):
    # Initialize an empty list of twin primes
    primes = []
    
    # Check if n is prime for n = 2, 3, ..., N-1 
    for n in range(2,N):
        if is_prime(n):
            # If n is prime, add it to the list
            primes.append(n)
    
    return primes

We can now use this function to find prime numbers.

In [5]:
N = 30
primes = get_primes(N)

print('Prime numbers less than {}:'.format(N))
print(primes)
Prime numbers less than 30:
[2, 3, 5, 7, 11, 13, 17, 19, 23, 29]

Finding twin primes¶

Now that we can use Python to identify prime numbers, we are ready to search for false primes. We will first define a function is_twin_prime that takes in an integer n and returns True if the pair (n,n+2) are twin primes, and False otherwise. This function will make use of the earlier defined is_prime function.

In [6]:
def is_twin_prime(n):
    # Check whether both n and n+2 are prime
    if is_prime(n) and is_prime(n+2):
        return True
    else:
        return False

Once again, we will test our is_twin_prime function on some simple cases. For example, the pair $(5,7)$ are twin primes since both $5$ and $7$ are prime. This we can confirm by calling is_twin_prime(5).

In [7]:
is_twin_prime(5)
Out[7]:
True

On the other hand, the pair $(7,9)$ are not twin primes since $9$ is not a prime number.

In [8]:
is_twin_prime(7)
Out[8]:
False

From these examples, our is_twin_prime function appears to be working correctly. We are now ready to search for twin primes. To do so, we will define a function get_twin_primes that takes in an integer N and returns a list containing all twin primes (n,n+2) such that n < N.

In [9]:
def get_twin_primes(N):
    # Initialize an empty list of twin primes
    twin_primes = []
    
    # Check if (n,n+2) are twin primes for n = 2, 3, ..., N-1 
    for n in range(2,N):
        if is_twin_prime(n):
            # If (n,n+2) are twin primes, add them to the list
            twin_primes.append((n,n+2))
    
    return twin_primes

We can now use this function to find twin primes.

In [10]:
twin_primes = get_twin_primes(10000)

We now check a portion of the twin_primes list to see the first 10 twin primes:

In [11]:
num_to_show = 10
print('The first {} twin primes:'.format(num_to_show))
print()
print('  n | n+2')
print('---------')
for (n,m) in twin_primes[:num_to_show]:
    print('{0:>3} | {1:>3}'.format(n,m))
The first 10 twin primes:

  n | n+2
---------
  3 |   5
  5 |   7
 11 |  13
 17 |  19
 29 |  31
 41 |  43
 59 |  61
 71 |  73
101 | 103
107 | 109

Density of twin primes¶

Now that we are able to generate lists of twin primes, we can start to study their properties. For example, what is the density of twin prime numbers? We can explore this by counting the number of twin primes less than a maximum N for several different choices of N, which we do below.

We first will define a list Nlist of N values. For each value N, we will generate a list of all twin primes less than N, and store this list of twin primes inside another list called twin_primes_lists. For later convenience, we will simultaneously generate and store lists of all primes (whether prime or not) less than N for each N.

In [12]:
# Define list of N values, which will give an upper bound
# on the twin primes that will be sought
Nlist = [10**i for i in range(2,7)]
# Initialize an empty list that will store lists of twin primes
twin_primes_lists = []
# Initialize an empty list that will store lists of primes
primes_lists = []

for N in Nlist:
    # for each N in Nlist, find all twin primes less than N
    twin_primes = get_twin_primes(N)
    # store the list of twin primes inside twin_primes_lists
    twin_primes_lists.append(twin_primes)
    
    # for each N in Nlist, find all primes less than N
    primes = get_primes(N)
    # store the list of primes inside primes_lists
    primes_lists.append(primes)

Now that we have generated this data, let us look at the density of twin primes over each subset. That is, we will count how many integers less than N are twin primes. We can easily do this by dividing the length of each list of twin primes by the corresponding maximum N.

In [13]:
for N, twin_primes in zip(Nlist, twin_primes_lists):
    # For each maximum N and corresponding list of twin primes less than N,
    # divide the length of the list by N to calculate the density of twin primes
    ratio = len(twin_primes) / N
    print('{0:5.2f}% of integers less than {1:>7} are twin primes.'.format(100*ratio,N))
 8.00% of integers less than     100 are twin primes.
 3.50% of integers less than    1000 are twin primes.
 2.05% of integers less than   10000 are twin primes.
 1.22% of integers less than  100000 are twin primes.
 0.82% of integers less than 1000000 are twin primes.

It appears that the density of twin primes decreases as N increases. This might suggest that there are only finitely many twin primes, although that need not be the case. For comparison, we can also check the density of prime numbers (whether twin prime or not).

In [14]:
for N, primes in zip(Nlist, primes_lists):
    # For each maximum N and corresponding list of primes less than N,
    # divide the length of the list by N to calculate the density of primes
    ratio = len(primes) / N
    print('{0:5.2f}% of integers less than {1:>7} are primes.'.format(100*ratio,N))
25.00% of integers less than     100 are primes.
16.80% of integers less than    1000 are primes.
12.29% of integers less than   10000 are primes.
 9.59% of integers less than  100000 are primes.
 7.85% of integers less than 1000000 are primes.

It appears that the density of the prime numbers also decreases. On the other hand, it is a well-known fact that there are infinitely many prime numbers. With this in mind, we should not assume that there are only finitely many twin primes. Answering this question is beyond the scope of this report.

Finally, for completeness, we compare look at the density of twin primes within the set of prime numbers less than N for each N in our list Nlist.

In [15]:
for N, primes, twin_primes in zip(Nlist, primes_lists, twin_primes_lists):
    # For each maximum N and corresponding list of twin primes less than N,
    # divide the length of the list by N to calculate the density of twin primes
    ratio = len(twin_primes) / len(primes)
    print('{0:5.2f}% of primes less than {1:>7} are twin primes.'.format(100*ratio,N))
32.00% of primes less than     100 are twin primes.
20.83% of primes less than    1000 are twin primes.
16.68% of primes less than   10000 are twin primes.
12.76% of primes less than  100000 are twin primes.
10.41% of primes less than 1000000 are twin primes.

From this, we see that the density of twin primes diminishes even within the diminishingly dense subset of prime numbers.

Triplet primes¶

We have found many examples of twin primes. Are there are any triplet primes? That is, can we find triples of integers $(n, n+2, n+4)$ such that $n$, $n+2$, and $n+4$ are all prime? The integers $(3,5,7)$ (with $n=3$) form triplet primes as an immediate example. Are there any others?

It is straight-forward to modify our existing functions to search for such triplets. We start by defining a function is_triplet_prime which takes in an integer n and returns True if (n, n+2, n+4) is a triplet prime, and False otherwise.

In [16]:
def is_triplet_prime(n):
    # Check whether n, n+2, and n+4 are all prime
    if is_prime(n) and is_prime(n+2) and is_prime(n+4):
        return True
    else:
        return False

We can now search for triplet primes. We will start by searching through all positive integers n less than 10000 to see if (n,n+2,n+4) are triplet primes.

In [17]:
for n in range(2,10000):
    if is_triplet_prime(n):
        print('({},{},{}) is a triplet prime.'.format(n,n+2,n+4))
(3,5,7) is a triplet prime.

From this seach, we find only the one example, $(3,5,7)$. This suggests that there may not be any others, although it is not concrete proof. On the other hand, we can argue mathematically that these are the only possible triplet primes.

Suppose $(n,n+2,n+4)$ are triplet primes. Then $n$, $n+2$, and $n+4$ must all be odd numbers (since $2$ is the only even prime). On the other hand, at least one of $n$, $n+2$, and $n+4$ must be divisible by $3$. To see why, note that $n$, $n+2$, and $n+4$ all have different remainders after division by $3$. But the only possible remainders are $0$, $1$, and $2$. Thus one of them has remainder $0$ and is divisible by $3$. Since all are prime, this number must be exactly $3$. Finally, since $n=1$ is not a prime number, it must be the case that $n=3$, so that the triplet primes are $(3,5,7)$.

Other neighboring primes¶

Instead of generalizing twin primes to triplet primes (which did not yield interesting results), we can instead look for other prime neighbors. For example, can we find primes that differ by $4$, $6$, $8$, etc? Note that we do not need to bother searching for primes that differ by an odd number, since all primes (aside from $n=2$) are odd.

We now write a function is_neighboring_prime that takes in integers n and k and returns True if both the pair (n,n+k) are prime and False otherwise.

In [18]:
def is_neighboring_prime(n,k=2):
    # Check whether both n and n+k are prime
    if is_prime(n) and is_prime(n+k):
        return True
    else:
        return False

As an example, $n=7$ and $n=11$ are neighboring primes with a difference of $k=4$. We will use this as a test scase for our is_neighboring_prime function.

In [19]:
is_neighboring_prime(7,4)
Out[19]:
True

We now define a function get_neighboring_primes that takes in integers N and k, and returns a list of all neighboring primes (n,n+k) with n < N.

In [20]:
def get_neighboring_primes(N,k=2):
    # Initialize an empty list of neighboring primes
    neighboring_primes = []
    
    # Check if (n,n+k) are neighboring primes for n = 2, 3, ..., N-1 
    for n in range(2,N):
        if is_neighboring_prime(n,k):
            # If (n,n+k) are neighboring primes, add them to the list
            neighboring_primes.append((n,n+k))
    
    return neighboring_primes

We will test this function by generating a list of neighboring primes $(n,n+k)$ with $n < 100$ and $k=6$.

In [21]:
N = 100
k = 6

neighboring_primes = get_neighboring_primes(N,k)

To check the output, we will now print out the first 10 neighboring primes with $k=6$.

In [22]:
num_to_show = 10
print('The first {0} neighboring primes with k={1}:'.format(num_to_show,k))
print()
print('  n | n+2')
print('---------')
for (n,m) in neighboring_primes[:num_to_show]:
    print('{0:>3} | {1:>3}'.format(n,m))
The first 10 neighboring primes with k=6:

  n | n+2
---------
  5 |  11
  7 |  13
 11 |  17
 13 |  19
 17 |  23
 23 |  29
 31 |  37
 37 |  43
 41 |  47
 47 |  53

Now that we have some tools in place, we can explore neighboring primes further. In particular, we may ask how many neighboring primes there are for various distance values $k$. To measure this, we will count the number of neighboring primes $(n,n+k)$ with $n < 100,000$ for varying distance values $k$.

In [23]:
# Set a maximum integer for the neighboring primes
# that will be sought
N = 100000

# Define a list of distance values k that will be used.
# Only even values of k will yield neighboring primes
klist = [2*i for i in range(1,10)]

# Initialize an empty list that will store lists of neighboring primes
neighboring_primes_lists = []

for k in klist:
    # for each k, find all neighboring primes (n,n+k) with n<N
    neighboring_primes = get_neighboring_primes(N,k)
    # store the list of neighboring primes in neighboring_primes_list
    neighboring_primes_lists.append(neighboring_primes)

Let us now count the number of neighboring primes for each value of $k$:

In [24]:
print('Count of neighboring primes (n,n+k) for varying k:')
print()
print(' k | count')
print('----------')
for k,neighboring_primes in zip(klist, neighboring_primes_lists):
    num_neighboring_primes = len(neighboring_primes)
    print('{0:>2} | {1:>5}'.format(k,num_neighboring_primes))
Count of neighboring primes (n,n+k) for varying k:

 k | count
----------
 2 |  1224
 4 |  1216
 6 |  2447
 8 |  1260
10 |  1624
12 |  2421
14 |  1488
16 |  1233
18 |  2477

From this data, we see that there are many neighboring primes for each value of $k$. Moreover, there are far more neighboring primes with a distance of $k=6$, $k=12$, and $k=18$ than the other distance values tested. This raises an interesting question: In general, are there more neighboring primes when $k$ a multiple of $6$ than when $k$ is not? While we could compile more data using Python, a more robust mathematical analysis may be required to truly answer this question. Such analysis is beyond the scope of this report.

We previously discovered that there is a unique triplet prime, $(3,5,7)$. Following our study of more general neighboring primes, we could revisit the idea of triplet primes and ask if there are triplet primes with different distance values $k$, that is prime triples $(n,n+k,n+2k)$. For example, $(5,11,17)$ and $(7,13,19)$ are triplet primes with distance $k=6$. For brevity, we leave such exploration out of this report, but encourage the reader to use the tools developed here to cary out their own exploration of this idea.

Conclusions¶

In this report, we have explored the idea of twin primes. We looked at the density of twin primes and found that they appear less and less dense as we search through more and more integers. Moreover, they become less dense even compared to subset of prime numbers as we search through larger and larger integers. It is not clear from our study whether or not there are infinitely many twin primes, which is an interesting question for future study. We have also looked into the idea of prime triplets, and found that there is a unique prime triplet $(3,5,7)$.

Finally, we looked at neighboring primes and found that many exist for many even distance values $k$. In particular, it seems that there are for more neighboring primes when $k$ is a multiple of $6$. Proving this statement is beyond the scope of this report, but is an interesting question for further study. In addition, we briefly considered the idea of triplet primes $(n,n+k,n+2k)$ with now varying distance values $k$, and leave further study to the reader.