The Nazca Primes
The Nazca Lines are considered one of the most intriguing
mysteries of ancient societies. They consist of several drawings of gigantic
images, mostly animals and images resembling humans, created by the Nazca
culture, around 650 AD. The images are so big that they only make sense when
observed from extremely high altitudes:
“The Astronaut”, in the Nazca Desert (source: Bing Images)
The purpose of the hundreds of such drawings remains a
mystery. Scientists and anthropologists continue their studies and excavations
in order to solve this mystery. But in August 2007, a group of Israeli and
Iranian anthropologists made a very surreal discovery when excavating the caves
of the Nazca Desert in southern Peru.
Excavations of the caves in the Nazca Desert,
August 2007 (source: Bing Images)
Amongst several new (albeit small) drawings found in some of
the caves in that region, one particular drawing caught the eye of the
researches: first, there was a sequence of a 3x3 numbers written in one stone,
and the sequence was:
With the following text right below the numbers:
“ويجب أن يكون هناك دائما يعبي. الأرقام، ومجموع الأرقام. ويجب
أن يكون هناك دائما يعبي. الأرقام. مجموع الأرقام. في كل الاتجاهات. قد وتتكون من
الكون بالضبط 482139064 تركيبات. يعبي. يجب أن تكون دائما!”
Whose translation to English is the following:
“And there
shall always be primes. The numbers, the sum of the numbers. And there shall
always be primes. The numbers. The sum of the numbers. In all directions. And
may the universe be composed of exactly 482,139,064 combinations. Primes. They
shall always be!”
For the past several years scientists have been trying to
decipher the meaning of these numbers as well as the text associated with them.
Earlier this year a new article entitled “The Prime Stone – Unveiling the
Meaning of the Nazca Numbers” (by Ioran Olecram) was published in the journal Matematica which provides a fascinating
explanation to this particular drawing. First, the 3x3 matrix of course
consists of prime numbers only (this was known before). Moreover, if you add up
the elements of any row, any column or any diagonal, the result… is also a
prime number! For instance, add up the second row and the resulting number
would be: 5+13+19 = 37, which is a prime number! Now, sum up the elements in
the diagonal: 3+13+7 = 23, another prime number! It is unclear how such an old
and obscure civilization would have such an advanced knowledge of mathematics.
It is unclear also how they managed to put together not only the matrix of
these numbers, but also how they knew the properties of prime numbers, since it
is believed that such concepts were only known and developed in far-away parts
of the planet. The final mystery to be solved was the phrase:
“And may the
universe be composed of exactly 482,139,064 combinations”
What is the meaning of the number 482,139,064? It turns out
that, if you take all the prime numbers from 0 to 100 (which were the basic
numbers used at that time) and you try to create 3x3 matrix of such prime
numbers in a way to honor the properties that:
Prime Stone Properties
a) All
the numbers in the matrix are prime numbers
b) All
the numbers in the matrix are unique
c) The
sum of all the elements in any row is a prime number
d) The
sum of all the elements in any column is a prime number
e) The
sum of all the elements in any of the two diagonals is a prime number
You end up… with… amazingly… and astonishingly… exactly…
(how the heck did they do that?!)… 482,139,064 different combinations!!!
There is a theory that the Nazca civilization was colonized
by extra-terrestrial beings. Certainly the findings of the “Prime Stone” paper
will reenergize and heat up this conversation again.
In order to prove/disprove these findings, you need to write
an algorithm that takes as input three parameters:
-
N: the dimensions of the matrix (NxN)
-
L: the lower-bound of the prime numbers
-
U: the upper-bound of prime numbers
And returns the number of different combinations of the
“Prime Stone” (based on the above properties) using primes in the [L, U] range.
For example: for N = 3, L = 35 and U = 71:
PrimeStone.exe 3 35
71
Output should be:
Total solutions
found: 936
One of the solutions, for instance, is this one:
71 67 59
47 43 41
61 53 37
And if you can… run against N = 3, L = 0 and U = 100, and
see if you get the same result as the Israeli/Iranian team of scientists!
Solution
Since the problem is asking for all the combinations, it
should be clear that we’re looking for a brute-force approach (unless the Nazca
folks knew of a better, faster way). So the approach should be backtracking.
But the search space can be pruned, and that’s when the tradeoff space vs. time
come into play. There are certain techniques that can be used to speed up the
search:
1) Calculate
and cache the list of prime numbers between [L, U]. You can even use the Sieve
of Eratosthenes to find these primes.
2) Keep
a tab of the current sum of the elements for each row, column and diagonal.
Hence you’ll need an extra 2*N + 2 space (array of int) to store this data.
3) Also,
keep a tab of the current count of elements for each row, column and diagonal
(again, another 2*N + 2 space).
4) Using
the info in {1,2,3}, at each point in the recursive iteration, check whether
the column, or row, or diagonal has been filled completely, and if so, check
whether the sum is a prime number or not, and if it isn’t, skip this iteration.
This is accomplished by the multiple if conditions inside the main loop – here
is an example:
if
(count[n + c] >= n && !mapOfPrimes[sum[n + c]])
In
the example above the code is checking on the spot that the col has not reached
its capacity yet, and if it did, that the current sum is a prime number.
The
backtracking strategy uses a linear counter to access the position in the
matrix – notice how we can get the column and row numbers based on this linear
counter:
int r =
currentPosition / n;
int c =
currentPosition % n;
Also,
another minor trick is to randomize the position of the elements in the list of
prime numbers. This is particularly useful when trying to find any solution for
large input values. The randomization trick is done by adding a non-deterministic
if clause:
if (rd.Next(0, 10) < rd.Next(0, 5))
{
primeNumbers.AddLast(i);
}
else
{
primeNumbers.AddFirst(i);
}
And
that’s it. Code is below. Turns out that the Nazca people were right. Before
looking at the code, however, there is one extra interesting fact about this
problem: it turns out that there is no solution for this problem if N is an
even number. The proof for N > 2 and N even is easy: there is only one even
prime number (2), and since there are at least 4 rows, one of the rows won’t
contain a 2, so the row will contain only odd numbers. If you add up an even
number of terms of odd numbers, you will end up with an even number, which
won’t be the number 2, hence not a prime number! The proof for the case when N
= 2 is a very ingenious one, but unfortunately this post would become a little
too long if it was copied/pasted here (that's actually a lie. I don't know if there is a solution for N = 2). Now the code:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
namespace PrimeStone
{
class Program
{
static void Main(string[]
args)
{
int
n = 0;
int
initNumber = 0;
int
endNumber = 0;
bool
printStones = false;
if
(args.Length != 4 ||
!Int32.TryParse(args[0],
out n) ||
!Int32.TryParse(args[1],
out initNumber) ||
!Int32.TryParse(args[2],
out endNumber) ||
(!args[3].Equals("p") && !args[3].Equals("-p")))
{
Console.WriteLine("Usage: PrimeStones.exe <n> <init
number> <end number> <p or -p>");
Console.WriteLine("Limit
the numbers to small ones... Use p for printing the stones or -p for supressing
them");
return;
}
printStones = args[3].Equals("p");
FindPrimeStones(n, initNumber, endNumber,
printStones);
}
static void FindPrimeStones(int
n, int initNumber, int
endNumber, bool printStones)
{
int
numberSolutionsFound = 0;
if
(n > 1)
{
bool[]
mapOfPrimes = GenerateMapOfPrimes(endNumber * n);
LinkedList<int> primeNumbers = new
LinkedList<int>();
for(int i=0;i<mapOfPrimes.Length;i++)
{
if
(i>=initNumber && i<=endNumber && mapOfPrimes[i])
{
if (rd.Next(0, 10) < rd.Next(0, 5))
{
primeNumbers.AddLast(i);
}
else
{
primeNumbers.AddFirst(i);
}
}
}
int[]
used = new int[endNumber
+ 1];
int[]
sum = new int[2
* n + 2];
int[]
count = new int[2
* n + 2];
int[,] board = new int[n, n];
FindPrimeStonesInternal(n,
0,
ref primeNumbers,
ref numberSolutionsFound,
ref board,
ref sum,
ref count,
ref mapOfPrimes,
ref used,
printStones);
}
Console.WriteLine("Total solutions found: {0}",
numberSolutionsFound);
}
static void FindPrimeStonesInternal(int
n,
int
currentPosition,
ref LinkedList<int> primeNumbers,
ref int
solutionsFound,
ref int[,] board,
ref
int[] sum,
ref int[] count,
ref bool[]
mapOfPrimes,
ref int[] used,
bool
printStones)
{
//Base
if
(currentPosition >= n * n)
{
solutionsFound++;
if
(printStones)
{
Print(solutionsFound,
board, n);
}
return;
}
//Induction
(backtracking)
int
r = currentPosition / n;
int
c = currentPosition % n;
foreach(int candidate in
primeNumbers)
{
if
(used[candidate] == 0)
{
//Check
row:
sum[r] += candidate;
count[r]++;
if
(count[r] >= n && !mapOfPrimes[sum[r]])
{
sum[r] -= candidate;
count[r]--;
continue;
}
//Check
col:
sum[n + c] += candidate;
count[n + c]++;
if
(count[n + c] >= n && !mapOfPrimes[sum[n + c]])
{
sum[n + c] -=
candidate;
count[n + c]--;
sum[r] -= candidate;
count[r]--;
continue;
}
//Check
diag1
if
(r == c)
{
sum[2 * n] +=
candidate;
count[2 * n]++;
if (count[2 * n] >= n && !mapOfPrimes[sum[2 * n]])
{
sum[2 * n] -=
candidate;
count[2 * n]--;
sum[n + c] -=
candidate;
count[n + c]--;
sum[r] -=
candidate;
count[r]--;
continue;
}
}
//Check
diag2
if
(r + c == n - 1)
{
sum[2 * n + 1] +=
candidate;
count[2 * n + 1]++;
if (count[2 * n + 1] >= n && !mapOfPrimes[sum[2 * n +
1]])
{
sum[2 * n + 1] -=
candidate;
count[2 * n + 1]--;
if (r == c)
{
sum[2 * n] -=
candidate;
count[2 * n]--;
}
sum[n + c] -=
candidate;
count[n + c]--;
sum[r] -=
candidate;
count[r]--;
continue;
}
}
//Now
call recursively
used[candidate] = 1;
board[r, c] = candidate;
FindPrimeStonesInternal(n,
currentPosition + 1,
ref primeNumbers,
ref solutionsFound,
ref board,
ref sum,
ref count,
ref mapOfPrimes,
ref used,
printStones);
used[candidate] = 0;
//Row
sum[r] -= candidate;
count[r]--;
//Col
sum[n + c] -= candidate;
count[n + c]--;
//Diag1
if
(r == c)
{
sum[2 * n] -=
candidate;
count[2 * n]--;
}
//Diag2
if (r + c ==
n - 1)
{
sum[2 * n + 1] -=
candidate;
count[2 * n + 1]--;
}
}
}
}
static void Print(int
solutionNumber, int[,] board, int n)
{
Console.WriteLine("Solution number #{0}", solutionNumber);
for
(int r = 0; r < n; r++)
{
for
(int c = 0; c < n; c++)
{
Console.Write("{0} ", board[r,c]);
}
Console.WriteLine();
}
Console.WriteLine();
}
static bool[] GenerateMapOfPrimes(int
n)
{
if
(n <= 0)
{
return
null;
}
bool[]
mapOfPrimes = new bool[n];
for(int i=0;i<n;i++)
{
mapOfPrimes[i] = true;
}
//Special
cases
mapOfPrimes[0] = false;
if
(n > 1)
{
mapOfPrimes[1] = false;
}
int
boundary = (int)Math.Sqrt(n)
+ 1;
for
(int i = 2; i <= boundary; i++)
{
for
(int j = i; i * j < n; j++)
{
mapOfPrimes[i * j] = false;
}
}
return
mapOfPrimes;
}
}
}
Comments
Post a Comment