Showing posts with label maths. Show all posts
Showing posts with label maths. Show all posts

Tuesday, September 10, 2019

How elusive was 42 as the sum of three cubes?

The big mathematical news September 9th 2019 was the final resolution of the decade old Diophantine equation.

As is often the case for super tough questions, the question itself seems completely trivial.
Can all the numbers from 1 to 100 be written as the sum of the cubes of three integers,
i^3 + j^3 + k^3?

Of course the key part of the question is integers, otherwise 0^3 + 0^3 + (cubic root of x)^3 will return x for any x we want!

The other subtlety is that integers can be negative, so each cube can be either positive or negative. So 92 can be written as 9^3 + (-8)^3 + (-5)^3.



This puzzle has been around for at least decades in this current form, but similar equations with integer solutions have been around at least since the third century!

Four our problem, solutions for almost all numbers 1 through 100 had been found, but two remained elusive 33 and 42.



And then 33 was found earlier this year:
   (8866128975287528)^3
+ (−8778405442862239)^3
+ (−2736111468807040)^3 = 33

Which left 42, but advances on the theory and on the computing side have cracked the last remaining elusive number:
   (80435758145817515) ^ 3
+ (-80538738812075974) ^ 3
+  (12602123297335631) ^ 3 = 42

Now I will describe man algorithm to generate all numbers 1 through 1000....
Just kidding.

I was actually curious of the elusiveness of these numbers 33 and 42. Mathematicians had to go really far to get the right numbers. Is that because numbers just weren't very likely to fall in the 1 to 100 range (very likely given how quickly cubes get!) or are certain numbers much more likely to get all the attention. in other words, as all combinations of three integers were tested, were all numbers 1 through 100 obtained in roughly equal proportions or did frequency concentrate around certain ranges?

I wrote a small script which explored this, simply generating all possible combinations of three integers (up to -1000 to 1000 in range, didn't feel like going all the way up to 80538738812075974...) and look at the distribution of i^3 + j^3 + k^3 in the 1 to 100 range.

With i,j,k in -10 to 10


With integers just in that small range, already 61 out of the 100 numbers are solved!
The distribution is far from being randomly uniform though! Two things jump out:

  • the spikes: These are perfectly normal! If a number is a perfect cube (i.e. 27), then there is an infinite number of solutions setting the two other values to opposite values: 27 = 3^3 = 3^3 + 0^3 + 0^3 = 3^3 + (1761)^3 + (-1761)^3... And you can add in all the permutations!
  • the clusters: it seems the solved numbers go in groups. Again, quite reasonable if we think about it. If a number can be generated as the sum of two cubes, then we can easily generate the one immediately before and after by adding either 1^3 or (-1)^3. We have an extra degree of freedom around the perfect cubes. Being able to generate consecutive solved numbers creates the clusters. There is a further discussion at the end of the post on how unexpected the number of clusters is.

Based on the spike comment, I will truncate at frequencies of 10 to focus on coverage:


With i,j,k in -100 to 100

Expanding the range ten-fold leads to 68 out of the 100 being solved:




With i,j,k in -1000 to 1000

Increasing the range ten-fold once again leads to a single additional number being solved: 51


We now have a better sense of the elusiveness of the solutions. If they aren't found with the range of the smaller integers, then it will get increasingly difficult to identify later, no wonder this has been such an open problem for so many years!

Just for fun, I looked at a new problem switching the power from 3 to 5, only 13 (compared to 69 for cubes) numbers were solved in the -1000 to 1000 range:


Again the clustering is pretty remarkable! While 42 is still unsolved, I'm glad to report a solution for 33: 0^5 + 1^5 + 2^5 !

And to close out this post, I wanted to return to the topic of the clusters. We saw that when exploring the -1000 to 1000 ranges for the integers we were able to cover 69 out of the 100 target numbers. But it didn't appear as though the 69 numbers were randomly distributed in 1-100 but grouped into clusters, more specifically into 13 clusters. We had a high-level explanation for this as consecutive numbers can easily be generated via k^3 with k = -1, 0 and 1. But the question remains, how non-random is this grouping into 13 clusters?

One approach is to sample without replacement 69 out of the 100 first integers, and see how many clusters are obtained. In order to count clusters, I used the rle() function which stands for run length encoding and basically describes a sequence. E.g. for 1,2,2,2,1,0,0 it will return that there is one 1, three 2s, one 1 and two 0s. From there it is easy to get the number of clusters.

I generated 10,000 samples of "solved" numbers between 1-100 and in each case computed the number of clusters obtained. Here is the associated histogram to which I've added the 13 we obtained in our case as a red vertical line:


While we'd typically expect to get about 20-25 clusters, only in extreme cases do we get a 15 or even a 14. Our 13 was not obtained a single time in all ten thousand simulations. This should settle the question of whether the low number of clusters comes as a surprise or not!



Friday, June 28, 2019

Solving Mathematical Challenges with R: #3 Sum of all grids

Welcome to our third challenge!

You can find more details on where these challenges come from and how/why we will be approaching them in the original posting.

Challenge #3

The original link to the challenge (in French) can be found here. The idea is to consider an empty three-by-three grid, and then place two 1s in any two of the nine cells:


The grid is the completed as follows:
  • pick any empty cell
  • fill it as the sum of all its neighbors (diagonals as well)
I've here detailed an example, the new cell is indicated at each step in red:



The challenge is now to find a way to complete the grid in such a manner as to obtain the greatest value in a cell. In the previous example, the maximum obtained was 30, can we beat it?

Power grid
I strongly encourage you to try out a few grids before continuing to read. Any guesses as to what the greatest obtainable value is?

As you try it out, you might realize that the middle cell is rather crucial, as it can contribute its value to every other cell, thus offering the following dilemma:
  • fill it too soon and it will be filled with a small value
  • fill it too late and it will not be able to distribute its value to a sufficient number of other cells
Gotta try them all!
In the spirit of our previous posts, let's try all possibilities and perform an exhaustive search.
The first thing we need to ask ourselves is the number of possible ways of filling up the square, and then generating all those combinations in R.
Ignoring how the cells are filled up, there are 9 possibilities to fill up the first cell (with a 1), 8 possibilities for the second (also a 1), 7 for the third (with the rule)... and 1 for the ninth final cell. Which amounts to 9*8*7*...*1 = 9! = 362880, piece of cake for R to handle.

Generating permutations
So we want to generate all these possibilities which are actually permutations of numbers 1 through 9, where each number corresponds to a cell. {7,1,5,2,4,9,3,8,6} would mean filling up cells 7 and 1 first with a 1, then filling up cell #5, then #2 and do on.
A naive method would be to use expand.grid() once again for all digits one through 9, which would also give unacceptable orders such as {7,1,5,2,1,3,6,8,9} because cell #1 is filled twice at different times! We could easily filter those out by checking if we have 9 unique values. But this would amount to generating all 9*9*9..*9=9^9>=387 million combinations and narrow down to just the 362K of interest. That seems like a lot of wasted resources and time!

Instead, I approached the problem from a recursive perspective (R does have a package combinat that does this type of work, but here and in general in all the posts I will try to only use basic R):

  • If I only have one value a, there is only a single permutation:{a}
  • If I only have two values a and b, I fix one of the values and look at all permutations I have of the left-over value, then append the value that was fixed:
    - fix a, all permutations of b are {b}, then append the a: {b,a}
    - fix b, all permutations of a are {a}, then append the b: {a,b}
    - the result is {a,b} and {b,a}
  • with three values, repeat the process now that we know all permutations of 2 values:
    - fix a, all permutations of b,c are {b,c} and {c,b}, then append the a: {b,c,a}, {c,b,a}
    - fix b, all permutations of a,c are {a,c} and {c,a}, then append the b: {a,c,b}, {c,a,b}
    - fix c, all permutations of a,b are {a,b} and {b,a}, then append the c: {a,b,c}, {b,a,c}

In R:
SmartPermutation <- function(v) {
  if (length(v) == 1) {
    return(as.data.table(v))
  } else {
    out <- rbindlist(lapply(v, function(e) {
      tmp <- SmartPermutation(setdiff(v, e))
      tmp[[ncol(tmp) + 1]] <- e
      return(tmp)
    }))
    return(out)
  }
}


Computing the score
The next and final step is to fill out the grid for each possible permutation.
This is done by defining a static mapping between cell ID and its neighbors:
We then go through each permutation, fill out a virtual grid (in our case it's not a 3-by-3 but a vector, which doesn't change anything as long as the relationships between neighbors is maintained) and track maximum value.

Neighbors <- function(i) {
  neighbor.list <-
    list(c(2, 4, 5), c(1, 3, 4, 5, 6), c(2, 5, 6),
         c(1, 2, 5, 7, 8), c(1, 2, 3, 4, 6, 7, 8, 9), c(2, 3, 5, 8, 9),
         c(4, 5, 8), c(4, 5, 6, 7, 9), c(5, 6, 8))
  return(neighbor.list[[i]])
}

CompleteGrid <- function(row) {
  grid <- rep(0, 9)
  grid[row[1 : 2]] <- 1
  for (i in 3 : 9) {
    grid[row[i]] <- sum(grid[Neighbors(row[i])])
  }
  return(max(grid))
}

Final answer
Based on our exhaustive search, we were able to identify the following solution yielding a max value of...57!



Little bonus #1: Distribution
The natural question is around the distribution of values? Were you also stuck around a max value in the forties? 57 seems really far off, but the distribution graph indicates that reaching values in the forties is already pretty good!




Little bonus #2: Middle cell
Do you remember our original trade-off dilemma? How early should the middle cell be filled out? In the optimal solution we see it was filled out exactly halfway through the grid.
To explore further, I also kept track for each permutation when the middle cell was filled out and what its filled value was. The optimal solution is shown in green, the middle cell being filled out in 5th position with a seven.


And a similar plot of overall max value against position in which the middle cell was filled:


It clearly illustrates the dilemma of filling neither too late nor too early: some very high scores can be obtained when the middle cell is filled as 4th or 6th cell, but absolute max of 57 will only be achieved when it is filled in 5th. Filling it in the first or last three positions will only guarantee a max of 40.