Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to find all possible values of four variables when squared sum to N?

A^2+B^2+C^2+D^2 = N Given an integer N, print out all possible combinations of integer values of ABCD which solve the equation.

I am guessing we can do better than brute force.

like image 280
Usman Ismail Avatar asked Jul 31 '12 03:07

Usman Ismail


3 Answers

Naive brute force would be something like:

n = 3200724;
lim = sqrt (n) + 1;
for (a = 0; a <= lim; a++)
    for (b = 0; b <= lim; b++)
        for (c = 0; c <= lim; c++)
            for (d = 0; d <= lim; d++)
                if (a * a + b * b + c * c + d * d == n)
                    printf ("%d %d %d %d\n", a, b, c, d);

Unfortunately, this will result in over a trillion loops, not overly efficient.

You can actually do substantially better than that by discounting huge numbers of impossibilities at each level, with something like:

#include <stdio.h>

int main(int argc, char *argv[]) {
    int n = atoi (argv[1]);
    int a, b, c, d, na, nb, nc, nd;
    int count = 0;

    for (a = 0, na = n; a * a <= na; a++) {
        for (b = 0, nb = na - a * a; b * b <= nb; b++) {
            for (c = 0, nc = nb - b * b; c * c <= nc; c++) {
                for (d = 0, nd = nc - c * c; d * d <= nd; d++) {
                    if (d * d == nd) {
                        printf ("%d %d %d %d\n", a, b, c, d);
                        count++;
                    }
                    tot++;
                }
            }
        }
    }

    printf ("Found %d solutions\n", count);

    return 0;
}

It's still brute force, but not quite as brutish inasmuch as it understands when to stop each level of looping as early as possible.

On my (relatively) modest box, that takes under a second (a) to get all solutions for numbers up to 50,000. Beyond that, it starts taking more time:

     n          time taken
----------      ----------
   100,000            3.7s
 1,000,000       6m, 18.7s

For n = ten million, it had been going about an hour and a half before I killed it.

So, I would say brute force is perfectly acceptable up to a point. Beyond that, more mathematical solutions would be needed.


For even more efficiency, you could only check those solutions where d >= c >= b >= a. That's because you could then build up all the solutions from those combinations into permutations (with potential duplicate removal where the values of two or more of a, b, c, or d are identical).

In addition, the body of the d loop doesn't need to check every value of d, just the last possible one.

Getting the results for 1,000,000 in that case takes under ten seconds rather than over six minutes:

   0    0    0 1000
   0    0  280  960
   0    0  352  936
   0    0  600  800
   0   24  640  768
   :    :    :    :
 424  512  512  544
 428  460  500  596
 432  440  480  624
 436  476  532  548
 444  468  468  604
 448  464  520  560
 452  452  476  604
 452  484  484  572
 500  500  500  500
Found 1302 solutions

real   0m9.517s
user   0m9.505s
sys    0m0.012s

That code follows:

#include <stdio.h>

int main(int argc, char *argv[]) {
    int n = atoi (argv[1]);
    int a, b, c, d, na, nb, nc, nd;
    int count = 0;

    for (a = 0, na = n; a * a <= na; a++) {
        for (b = a, nb = na - a * a; b * b <= nb; b++) {
            for (c = b, nc = nb - b * b; c * c <= nc; c++) {
                for (d = c, nd = nc - c * c; d * d < nd; d++);
                if (d * d == nd) {
                    printf ("%4d %4d %4d %4d\n", a, b, c, d);
                    count++;
                }
            }
        }
    }

    printf ("Found %d solutions\n", count);

    return 0;
}

And, as per a suggestion by DSM, the d loop can disappear altogether (since there's only one possible value of d (discounting negative numbers) and it can be calculated), which brings the one million case down to two seconds for me, and the ten million case to a far more manageable 68 seconds.

That version is as follows:

#include <stdio.h>
#include <math.h>

int main(int argc, char *argv[]) {
    int n = atoi (argv[1]);
    int a, b, c, d, na, nb, nc, nd;
    int count = 0;

    for (a = 0, na = n; a * a <= na; a++) {
        for (b = a, nb = na - a * a; b * b <= nb; b++) {
            for (c = b, nc = nb - b * b; c * c <= nc; c++) {
                nd = nc - c * c;
                d = sqrt (nd);
                if (d * d == nd) {
                    printf ("%d %d %d %d\n", a, b, c, d);
                    count++;
                }
            }
        }
    }

    printf ("Found %d solutions\n", count);

    return 0;
}

(a): All timings are done with the inner printf commented out so that I/O doesn't skew the figures.

like image 92
paxdiablo Avatar answered Sep 24 '22 09:09

paxdiablo


The Wikipedia page has some interesting background information, but Lagrange's four-square theorem (or, more correctly, Bachet's Theorem - Lagrange only proved it) doesn't really go into detail on how to find said squares.

As I said in my comment, the solution is going to be nontrivial. This paper discusses the solvability of four-square sums. The paper alleges that:

There is no convenient algorithm (beyond the simple one mentioned in the second paragraph of this paper) for finding additional solutions that are indicated by the calculation of representations, but perhaps this will streamline the search by giving an idea of what kinds of solutions do and do not exist.

There are a few other interesting facts related to this topic. There exist other theorems that state that every integer can be written as a sum of four particular multiples of squares. For example, every integer can be written as N = a^2 + 2b^2 + 4c^2 + 14d^2. There are 54 cases like this that are true for all integers, and Ramanujan provided the complete list in the year 1917.

For more information, see Modular Forms. This is not easy to understand unless you have some background in number theory. If you could generalize Ramanujan's 54 forms, you may have an easier time with this. With that said, in the first paper I cite, there is a small snippet which discusses an algorithm that may find every solution (even though I find it a bit hard to follow):

For example, it was reported in 1911 that the calculator Gottfried Ruckle was asked to reduce N = 15663 as a sum of four squares. He produced a solution of 125^2 + 6^2 + 1^2 + 1^2 in 8 seconds, followed immediately by 125^2 + 5^2 + 3^2 + 2^2. A more difficult problem (reflected by a first term that is farther from the original number, with correspondingly larger later terms) took 56 seconds: 11399 = 105^2 + 15^2 + 8^2 + 5^2. In general, the strategy is to begin by setting the first term to be the largest square below N and try to represent the smaller remainder as a sum of three squares. Then the first term is set to the next largest square below N, and so forth. Over time a lightning calculator would become familiar with expressing small numbers as sums of squares, which would speed up the process.

(Emphasis mine.)

The algorithm is described as being recursive, but it could easily be implemented iteratively.

like image 34
David Titarenco Avatar answered Sep 25 '22 09:09

David Titarenco


It seems as though all integers can be made by such a combination:

0 = 0^2 + 0^2 + 0^2 + 0^2
1 = 1^2 + 0^2 + 0^2 + 0^2
2 = 1^2 + 1^2 + 0^2 + 0^2
3 = 1^2 + 1^2 + 1^2 + 0^2
4 = 2^2 + 0^2 + 0^2 + 0^2, 1^2 + 1^2 + 1^2 + 1^2 + 1^2
5 = 2^2 + 1^2 + 0^2 + 0^2
6 = 2^2 + 1^2 + 1^2 + 0^2
7 = 2^2 + 1^2 + 1^2 + 1^2
8 = 2^2 + 2^2 + 0^2 + 0^2
9 = 3^2 + 0^2 + 0^2 + 0^2, 2^2 + 2^2 + 1^2 + 0^2
10 = 3^2 + 1^2 + 0^2 + 0^2, 2^2 + 2^2 + 1^2 + 1^2
11 = 3^2 + 1^2 + 1^2 + 0^2
12 = 3^2 + 1^2 + 1^2 + 1^2, 2^2 + 2^2 + 2^2 + 0^2
.
.
.

and so forth

As I did some initial working in my head, I thought that it would be only the perfect squares that had more than 1 possible solution. However after listing them out it seems to me there is no obvious order to them. However, I thought of an algorithm I think is most appropriate for this situation:

The important thing is to use a 4-tuple (a, b, c, d). In any given 4-tuple which is a solution to a^2 + b^2 + c^2 + d^2 = n, we will set ourselves a constraint that a is always the largest of the 4, b is next, and so on and so forth like:

a >= b >= c >= d

Also note that a^2 cannot be less than n/4, otherwise the sum of the squares will have to be less than n.

Then the algorithm is:

1a. Obtain floor(square_root(n)) # this is the maximum value of a - call it max_a
1b. Obtain the first value of a such that a^2 >= n/4 - call it min_a
2. For a in a range (min_a, max_a)

At this point we have selected a particular a, and are now looking at bridging the gap from a^2 to n - i.e. (n - a^2)

3. Repeat steps 1a through 2 to select a value of b. This time instead of finding
floor(square_root(n)) we find floor(square_root(n - a^2))

and so on and so forth. So the entire algorithm would look something like:

1a. Obtain floor(square_root(n)) # this is the maximum value of a - call it max_a
1b. Obtain the first value of a such that a^2 >= n/4 - call it min_a
2. For a in a range (min_a, max_a)
3a. Obtain floor(square_root(n - a^2))
3b. Obtain the first value of b such that b^2 >= (n - a^2)/3
4. For b in a range (min_b, max_b)
5a. Obtain floor(square_root(n - a^2 - b^2))
5b. Obtain the first value of b such that b^2 >= (n - a^2 - b^2)/2
6. For c in a range (min_c, max_c)
7. We now look at (n - a^2 - b^2 - c^2). If its square root is an integer, this is d.
Otherwise, this tuple will not form a solution

At steps 3b and 5b I use (n - a^2)/3, (n - a^2 - b^2)/2. We divide by 3 or 2, respectively, because of the number of values in the tuple not yet 'fixed'.

An example:

doing this on n = 12:

1a. max_a = 3
1b. min_a = 2
2. for a in range(2, 3):
    use a = 2
3a. we now look at (12 - 2^2) = 8
max_b = 2
3b. min_b = 2
4. b must be 2
5a. we now look at (12 - 2^2 - 2^2) = 4
max_c = 2
5b. min_c = 2
6. c must be 2
7. (n - a^2 - b^2 - c^2) = 0, hence d = 0
so a possible tuple is (2, 2, 2, 0)

2. use a = 3
3a. we now look at (12 - 3^2) = 3
max_b = 1
3b. min_b = 1
4. b must be 1
5a. we now look at (12 - 3^2 - 1^2) = 2
max_c = 1
5b. min_c = 1
6. c must be 1
7. (n - a^2 - b^2 - c^2) = 1, hence d = 1
so a possible tuple is (3, 1, 1, 1)

These are the only two possible tuples - hey presto!

like image 42
nebffa Avatar answered Sep 22 '22 09:09

nebffa