6

What algorithm can I use to find the set of all positive integer values of n1, n2, ... ,n7 for which the the following inequalities holds true.

97n1 + 89n2 + 42n3 + 20n4 + 16n5 + 11n6 + 2n7 - 185 > 0
-98n1 - 90n2 - 43n3 - 21n4 - 17n5 - 12n6 - 3n7 + 205 > 0
n1 >= 0, n2 >= 0, n3 >=0. n4 >=0, n5 >=0, n6 >=0, n7 >= 0

For example one set n1= 2, n2 = n3 = ... = n7 =0 makes the inequality true. How do I find out all other set of values? The similar question has been posted in M.SE.

ADDED:: I need to generalize the solution for n variables (might be large). What procedure can I apply? For another particular case n=8

97n1 + 89n2 + 42n3 + 20n4 + 16n5 + 11n6 + 6n7 + 2n8 - 185 > 0
-98n1 - 90n2 - 43n3 - 21n4 - 17n5 - 12n6  - 7 - 3n8 + 205 > 0
n1 >= 0, n2 >= 0, n3 >=0. n4 >=0, n5 >=0, n6 >=0, n7 >= 0, n8 >= 0

Python takes forever. Wolfram Mathematica reveals that there are 4015 solutions in less than minute.

Length[Solve[{97 n1 + 89 n2 + 42 n3 + 20 n4 + 16 n5 + 11 n6 + 6 n7 + 
     2 n8 - 185 > 0, 
   -98 n1 - 90 n2 - 43 n3 - 21 n4 - 17 n5 - 12 n6 - 7 n7 - 3 n8 + 
     205 > 0,
   n1 >= 0, n2 >= 0, n3 >= 0, n4 >= 0, n5 >= 0, n6 >= 0, n7 >= 0, 
   n8 >= 0}, {n1, n2, n3, n4, n5, n6, n7, n8}, Integers]]
Community
  • 1
  • 1
S L
  • 14,262
  • 17
  • 77
  • 116
  • Are the numbers after the `n` the exponents? Is this a linear inequality? – Thomas Matthews Mar 30 '16 at 23:49
  • What results were there when you searched the internet using "c++ fortran inequality algorithm"? – Thomas Matthews Mar 30 '16 at 23:50
  • You *could* invent your own algorithm by writing down the steps required to solve the inequalities. – Thomas Matthews Mar 30 '16 at 23:51
  • 3
    The problem of finding whether there is at least one integer solution to a set of linear inequalities is NP complete. "Feasible solutions" "Integer linear programming" are helpful search terms, but, as the NP-completeness suggests, you're not going to get easy answers. – Paul Hankin Mar 31 '16 at 00:14
  • @ThomasMatthews no the solutions is linear. It is about finding the all particular solutions of the matrix equation AX=B whose elements are greater than zero. I'll google for inequaity algorithm and see. – S L Mar 31 '16 at 04:11
  • @SantoshLinkha I think my solution is the best you can do for this problem. I understand that you'll want to wait for possibly better solutions to be submitted, but it would be nice to get your comments on what I've offered. – molecule Apr 05 '16 at 01:53

6 Answers6

8

Reti43 has the right idea, but there's a quick recursive solution that works with less restrictive assumptions about your inequalities.

def solve(smin, smax, coef1, coef2):
    """
    Return a list of lists of non-negative integers `n` that satisfy
    the inequalities,

    sum([coef1[i] * n[i] for i in range(len(coef1)]) > smin
    sum([coef2[i] * n[i] for i in range(len(coef1)]) < smax

    where coef1 and coef2 are equal-length lists of positive integers.
    """
    if smax < 0:
        return []

    n_max = ((smax-1) // coef2[0])
    solutions = []
    if len(coef1) > 1:
        for n0 in range(n_max + 1):
            for solution in solve(smin - n0 * coef1[0],
                                  smax - n0 * coef2[0], 
                                  coef1[1:], coef2[1:]):
                solutions.append([n0] + solution)
    else:
        n_min = max(0, (smin // coef1[0]) + 1)
        for n0 in range(n_min, n_max + 1):
            if n0 * coef1[0] > smin and n0 * coef2[0] < smax:
                solutions.append([n0])
    return solutions

You'd apply this to your original problem like this,

smin, coef1 = 185, (97, 89, 42, 20, 16, 11, 2)
smax, coef2 = 205, (98, 90, 43, 21, 17, 12, 3)
solns7 = solve(smin, smax, coef1, coef2)
len(solns7)
1013

and to the longer problem like this,

smin, coef1 = 185, (97, 89, 42, 20, 16, 11, 6, 2)
smax, coef2 = 205, (98, 90, 43, 21, 17, 12, 7, 3)
solns8 = solve(smin, smax, coef1, coef2)
len(solns8)
4015

On my Macbook, both of these cases are solved in milliseconds. This should scale reasonably well to slightly larger problems, but fundamentally, it's O(2^N) in the number of coefficients N. How well it actually scales depends on how large the additional coefficients are - the more large coefficients (compared to smax-smin), the fewer possible solutions and the faster it'll run.

Updated: From the discussion on the linked M.SE post, I see that the relationship between the two inequalities here is part of the structure of the problem. Given that, a slightly simpler solution can be given. The code below also includes a couple of additional optimizations, which speed up the solution for the 8-variable case from 88 milliseconds to 34 milliseconds on my laptop. I've tried this on examples with as many as 22 variables and gotten the results in less than a minute, but it's never going to be practical for hundreds of variables.

def solve(smin, smax, coef):
    """
    Return a list of lists of non-negative integers `n` that satisfy
    the inequalities,

    sum([coef[i] * n[i] for i in range(len(coef)]) > smin
    sum([(coef[i]+1) * n[i] for i in range(len(coef)]) < smax

    where coef is a list of positive integer coefficients, ordered
    from highest to lowest.
    """
    if smax <= smin:
        return []
    if smin < 0 and smax <= coef[-1]+1:
        return [[0] * len(coef)]

    c0 = coef[0]
    c1 = c0 + 1
    n_max = ((smax-1) // c1)
    solutions = []
    if len(coef) > 1:
        for n0 in range(n_max + 1):
            for solution in solve(smin - n0 * c0,
                                  smax - n0 * c1, 
                                  coef[1:]):
                solutions.append([n0] + solution)
    else:
        n_min = max(0, (smin // c0) + 1)
        for n0 in range(n_min, n_max + 1):
            solutions.append([n0])
    return solutions

You'd apply it to the 8-variable example like this,

solutions = solve(185, 205, (97, 89, 42, 20, 16, 11, 6, 2))
len(solutions)
4015

This solution directly enumerates the lattice points in the bounded region. Since you want all of these solutions, the time it takes to get them is going to be proportional (at best) to the number of bound lattice points, which grows exponentially with the number of dimensions (variables).

Community
  • 1
  • 1
molecule
  • 534
  • 2
  • 9
  • 2
    Beautiful solution. It is indeed true that it scales with the number of coefficients and how large smin and smax are. But solving for the smaller cases lightningly fast, you only help yourself for the bigger cases. :) – Reti43 Apr 02 '16 at 18:58
  • I just edited the updated solve() function to correct an error in the early-termination conditions. – molecule Apr 03 '16 at 20:23
  • I edited the docstring for the updated solve() function, explaining that the input coefficients were expected to be ordered. (This is assumed by the second early-termination condition.) – molecule Apr 03 '16 at 22:53
3

In both examples you gave I noticed the same pattern. For example, for the first case if you add the two equations together, you'll get

-n1 - n2 - n3 - n4 - n5 - n6 - n7 + 20 > 0

which can be rearranged to

n1 + n2 + n3 + n4 + n5 + n6 + n7 < 20

This is a nice bounded equation that you can brute force. Specifically, you can iterate for n1 from 0 to 19, for n2 from 0 to 19-n1, etc. A possible solution of this would be (0, 0, 0, 0, 0, 0, 0), but we notice that this doesn't satisfy our original equation. So, just generate all possible values for (n1, n2, ..., n7) and keep only those which satisfy your equation. Hardcoding all this results to

def find_solutions(N):
    sols = []
    for n1 in xrange(N):
        for n2 in xrange(N-n1):
            for n3 in xrange(N-n1-n2):
                for n4 in xrange(N-n1-n2-n3):
                    for n5 in xrange(N-n1-n2-n3-n4):
                        for n6 in xrange(N-n1-n2-n3-n4-n5):
                            for n7 in xrange(N-n1-n2-n3-n4-n5-n6):
                                if (97*n1 + 89*n2 + 42*n3 + 20*n4 + 16*n5 + 11*n6 + 2*n7 - 185 > 0 and
                                    -98*n1 - 90*n2 - 43*n3 - 21*n4 - 17*n5 - 12*n6 - 3*n7 + 205 > 0):
                                    sols.append((n1, n2, n3, n4, n5, n6, n7))
return sols

find_solutions(20) finds all 1013 solutions in 0.6 seconds. Similarly, for the second case it finds all 4015 solutions in 2.3 seconds. Now, this isn't easy to generalise at all, but it shows that with a smart approach, Python or any other language, doesn't have to be slow.

On the other hand, recursion allows us to generalise this for any number of nested loops but at a price of running a bit slower.

def find_solutions(N, coeffs, depth=0, variables=None, subtotal=None, solutions=None):
    if variables is None:
        solutions = []
        subtotal = [0 for _ in xrange(len(coeffs[0]))]
        variables = [0 for _ in xrange(len(coeffs[0])-1)]
    if depth == len(coeffs[0])-2:
        for v in xrange(N-sum(variables[:depth])):
            conditions = all(
                subtotal[i]+coeffs[i][depth]*v > coeffs[i][-1]
                for i in xrange(len(coeffs))
            )
            if conditions:
                variables[depth] = v
                solutions.append(tuple(variables))
    else:
        for v in xrange(N-sum(variables[:depth])):
            variables[depth] = v
            total = [subtotal[i]+coeffs[i][depth]*v for i in xrange(len(coeffs))]
            find_solutions(N, coeffs, depth+1, variables, total, solutions)
    if depth == 0:
        return solutions

To run this, generate the coefficients for each equation and pass them to the function. Keep in mind the sign for the constants is inverted!

coeffs = [
    [97, 89, 42, 20, 16, 11, 2, 185],
    [-98, -90, -43, -21, -17, -12, -3, -205]
]
solutions = find_solutions(20, coeffs)
print(len(solutions))

This one finishes the n=7 case in 1.6 seconds and the n=8 case in 5.8. I'll look into any possible optimisations if you expect your n to get very large, but for the time being it looks satisfactory.

The remaining question is whether the sum of your equations will always simplify to something like n1 + n2 + ... nn < N. There is a simple solution around that if that's not the case, but I opted not to prematurely overgeneralise the code beyond the examples you had provided us.

Finally, I imagine the same approach could be implemented in Java or C# and it'd probably be even faster. I wouldn't mind doing that if your general cases are expected to take much longer to solve.

Reti43
  • 9,656
  • 3
  • 28
  • 44
2

There are 1013 solutions, but I do not know the most efficient way to solve this.

Looking at the second inequality, 17 * n5 cannot be more than 205 (otherwise the entire left hand side could not be positive). This leads to n5 <= 12. By computing a similar bound for each of the other variables, you can reduce the problem to one that can be solved quickly using nested loops.

This Java code prints out all the solutions.

for (int n1 = 0; n1 <= 2; n1++) {
    for (int n2 = 0; n2 <= 2; n2++) {
        for (int n3 = 0; n3 <= 4; n3++) {
            for (int n4 = 0; n4 <= 9; n4++) {
                for (int n5 = 0; n5 <= 12; n5++) {
                    for (int n6 = 0; n6 <= 17; n6++) {
                        for (int n7 = 0; n7 <= 68; n7++) {
                            if (97 * n1 + 89 * n2 + 42 * n3 + 20 * n4 + 16 * n5 + 11 * n6 + 2 * n7 - 185 > 0
                                && -98 * n1 - 90 * n2 - 43 * n3 - 21 * n4 - 17 * n5 - 12 * n6 - 3 * n7 + 205 > 0) {
                                System.out.println(Arrays.asList(n1, n2, n3, n4, n5, n6, n7));
                            }
                        }
                    }
                }
            }
        }
    }
}

It is possible to make this more efficient by stopping each loop as soon as the sum of the first few terms of

98n1 + 90n2 + 43n3 + 21n4 + 17n5 + 12n6 + 3n7

reaches 205.

For example, the n4 loop can be replaced with

for (int n4 = 0; 98 * n1 + 90 * n2 + 43 * n3 + 21 * n4 < 205; n4++)

If you change all 7 loops in this way, all 1013 solutions can be found extremely quickly.

Paul Boddington
  • 37,127
  • 10
  • 65
  • 116
  • As I feared, the solution is ugly. Perhaps I should take cartesian product of all ranges of n's – S L Mar 31 '16 at 04:13
  • Once you have set n1, the constant becomes 205-98n1, so the bounds of n2 to be explored can be reduced... – aka.nice Mar 31 '16 at 08:41
  • That's the point I make at the end. There are other ways to improve this too. E.g. if you add the 2 original inequalities, you get that the sum of the values can't exceed 20. – Paul Boddington Mar 31 '16 at 10:30
  • Can we generalize for other values of n's? For example, I don't know how many loops are going to be there? I know there exists an algorithm in Wolfram Mathematica. `Solve[{97 n1 + 89 n2 + 42 n3 + 20 n4 + 16 n5 + 11 n6 + 6 n7 + 2 n8 - 185 > 0, -98 n1 - 90 n2 - 43 n3 - 21 n4 - 17 n5 - 12 n6 - 7 n7 - 3 n8 + 205 > 0, n1 >= 0, n2 >= 0, n3 >= 0, n4 >= 0, n5 >= 0, n6 >= 0, n7 >= 0, n8 >= 0}, {n1, n2, n3, n4, n5, n6, n7, n8}, Integers]` – S L Apr 01 '16 at 18:51
  • When you don't know the number of loops, the usual solution is recursion, so as long as you have just 2 inequalities and the +/- signs are the same as in your question my answer can be generalised. As for how Wolfram does the general case, I have no idea I'm afraid, but the simplex algorithm is related. – Paul Boddington Apr 02 '16 at 09:00
1

Seems to be something like linear programming with integer solutions. I think there some algorithms are already implemented. Try consider Linear Programming Tool/Libraries for Java.

Community
  • 1
  • 1
Shear Plane
  • 124
  • 8
1

(up to 13 integers only) Here is ugly brute-force with gpgpu(opencl) on 1600 cores finding 1013(7 ints) solutions in 9.3 milliseconds including array download time from gpu to cpu memory:

Edit: Corrected n1,n2,n3 as they were 1,20,400 instead of 20,20,20 limited.

__kernel void solver1(__global __write_only float * subCount){
                        int threadId=get_global_id(0);
                        int ctr=0;
                        int n1=threadId/400;
                        int n2=(threadId/20)%20;
                        int n3=threadId%20;
                        for(int n4=0;n4<=20;n4++) 
                           for(int n5=0;n5<=20;n5++)
                               for(int n6=0;n6<=20;n6++)
                                  for(int n7=0;n7<=20;n7++)
                                      if (
                      (97*n1 + 89*n2 + 42*n3 + 20*n4 + 16*n5 + 11*n6 + 2*n7 - 185 > 0) && 
                      (-98*n1 - 90*n2 - 43*n3 - 21*n4 - 17*n5 - 12*n6 - 3*n7 + 205 > 0))
                                   {ctr++;}
                    subCount[threadId]=ctr;

}

then something like subCount.Sum() gives number of solutions.(in microseconds)

global work size = 8000(threads) (getting n4 into this would make it 160000 and increase performance)

local work size= 160 (inefficient on my machine, making it power-of-2 is optimal)

This is just a string to be sent to gpu for compiling. You just add extra loops(or just threadId relations) in the string like n8, n9 and alter "if" body to sum them.

Edit: Adding 1 extra integer to equations increased solution time to 101 milliseconds even with more optimizations(finds 4015 solutions).

  __kernel void solver2(__global __write_only float * subCount){
                        int threadId=get_global_id(0);
                        int ctr=0;
                        int n1=threadId/160000;     int c1n1=97*n1; int c2n1=-98*n1;
                        int n2=(threadId/8000)%20;  int c1n2=89*n2; int c2n2=- 90*n2 ;
                        int n3=(threadId/400)%20;   int c1n3=42*n3; int c2n3=- 43*n3 ;
                        int n4=(threadId/20)%20;    int c1n4=20*n4 ;int c2n4=- 21*n4 ;
                        int n5=threadId%20;         int c1n5=16*n5 ;int c2n5=- 17*n5 ;
                        int t1=c1n1+c1n2+c1n3+c1n4+c1n5;
                        int t2=c2n1+c2n2+c2n3+c2n4+c2n5;
                               for(int n6=0;n6<=20;n6++)
                                  for(int n7=0;n7<=20;n7++)
                                    for(int n8=0;n8<=20;n8++)
                                        if(t1+ 11*n6 + 2*n7+6*n8 > 185  &&  t2 - 12*n6 - 3*n7-7*n8 > -205)
                                                ctr++;
                        subCount[threadId]=ctr;

                }

work group size = 256 global work size = 3200000 threads

Edit: 9-integer version with just another for-loop found 14k solutions for c1=3 and c2=-4 in 1581 milliseconds. Changing upper bound from 20 to 19 for n6 through n9 gave same result in 1310 milliseconds.

Edit: Adding some from Reti43's and Molecule's answers, 9 integer version took 14 milliseconds (but still inefficient for first 5 integers) using 3.2M threads.

__kernel void solver3(__global __write_only float * subCount){
                    int threadId=get_global_id(0);
                    int ctr=0;
                    int n1=threadId/160000;     int c1n1=97*n1; int c2n1=-98*n1;
                    int n2=(threadId/8000)%20;  int c1n2=89*n2; int c2n2=- 90*n2 ;
                    int n3=(threadId/400)%20;   int c1n3=42*n3; int c2n3=- 43*n3 ;
                    int n4=(threadId/20)%20;    int c1n4=20*n4 ;int c2n4=- 21*n4 ;
                    int n5=threadId%20;         int c1n5=16*n5 ;int c2n5=- 17*n5 ;
                    int t1=c1n1+c1n2+c1n3+c1n4+c1n5;
                    int t2=c2n1+c2n2+c2n3+c2n4+c2n5;
                    int m=max( max( max( max(n1,n2),n3),n4),n5);
                           for(int n6=0;n6<=20-m;n6++)
                              for(int n7=0;n7<=20-m-n6;n7++)
                                for(int n8=0;n8<=20-m-n6-n7;n8++)
                                    for(int n9=0;n9<=20-m-n6-n7-n8;n9++)
                                        if(t1+ 11*n6 + 2*n7+6*n8 +3*n9> 185  &&  t2 - 12*n6 - 3*n7-7*n8-4*n9 > -205)
                                            ctr++;
                    subCount[threadId]=ctr;

            }
  • 10 ints: 46k solutions, 35ms (c1=7,c2=-8)

  • 11 ints: 140k solutions,103ms (c1=1,c2=-2)

  • 12 ints: 383k solutions,274ms (c1=5,c2=-6)

  • probably be too slow at 15 ints because of hardware limits. Wouldnt even compile for 20 ints.

    note: m=max( max( max( max(n1,n2),n3),n4),n5) is not optimal but m=n1+n2+n3+n4+n5 is.

I will try Monte Carlo method to overcome hardware resource bottlenecks now. Maybe there is convergence to solution number somehow.

Community
  • 1
  • 1
huseyin tugrul buyukisik
  • 11,469
  • 4
  • 45
  • 97
1

pseudocode:

for each inequation:
    find all real roots of the equivalent equation, i.e. the zero-crossings
    for each interval between two adjacent roots:
        pick any number strictly inside the interval
        evaluate the polynomial in that point
            if the evaluated polimonial is positive:
                add every integer in the interval to the list of solutions to that inequation
                (treat the open-ended intervals outside the extreme roots as special cases, they may contain infinite solutions)
find the integers that are in all the lists of solutions to the individual equations
Emilio M Bumachar
  • 2,532
  • 3
  • 26
  • 30