找到两个线性等式成立的整数集

find the set of integers for which two linear equalities holds true

我可以使用什么算法来找到 n1, n2, ... ,n7 的所有正整数值的集合,满足以下不等式。

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

例如一组 n1= 2, n2 = n3 = ... = n7 =0 使不等式成立。我如何找出所有其他值集?类似问题已发布在 M.SE.

ADDED:: 我需要概括 n 个变量(可能很大)的解决方案。我可以申请什么程序?对于 另一个 特殊情况 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 需要很长时间。 Wolfram Mathematica 显示在不到一分钟内有 4015 个解决方案。

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]]

有 1013 个解决方案,但我不知道最有效的解决方法。

看第二个不等式,17 * n5不能大于205(否则整个左边不能为正)。这导致 n5 <= 12。通过为每个其他变量计算类似的界限,您可以将问题简化为可以使用嵌套循环快速解决的问题。

此 Java 代码打印出所有解决方案。

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));
                            }
                        }
                    }
                }
            }
        }
    }
}

只要

的前几项之和停止每个循环,就可以提高效率
98n1 + 90n2 + 43n3 + 21n4 + 17n5 + 12n6 + 3n7

达到 205。

例如,n4循环可以替换为

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

如果你把7个循环都改成这样,那么1013个解都可以非常快的找到。

好像是整数解的线性规划。我认为已经实施了一些算法。 尝试考虑 Linear Programming Tool/Libraries for Java.

在你给出的两个例子中,我注意到了相同的模式。例如,对于第一种情况,如果将两个方程相加,您将得到

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

可以重新排列为

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

这是一个很好的有界方程,您可以对其进行暴力破解。具体来说,您可以从 0 到 19 迭代 n1,从 0 到 19-n1 迭代 n2,等等。一个可能的解决方案是 (0, 0, 0, 0, 0, 0 , 0), 但我们注意到这不满足我们原来的方程。因此,只需为 (n1, n2, ..., n7) 生成所有可能的值,并只保留满足您的等式的值。将所有这些结果硬编码为

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) 在 0.6 秒内找到所有 1013 个解决方案。同样,对于第二种情况,它在 2.3 秒内找到所有 4015 个解决方案。现在,这根本不容易概括,但它表明,使用聪明的方法,Python 或任何其他语言,不必很慢。

另一方面,递归允许我们将其推广到任意数量的嵌套循环,但代价是 运行ning 有点慢。

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

为了运行这个,生成每个方程的系数并将它们传递给函数。请记住常量的符号是倒转的!

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))

这一个在 1.6 秒内完成了 n=7 个案例,在 5.8 秒内完成了 n=8 个案例。如果您希望 n 变得非常大,我会研究任何可能的优化,但目前看起来令人满意。

剩下的问题是你的方程式之和是否总是会简化为 n1 + n2 + ... nn < N 之类的东西。如果不是这种情况,有一个简单的解决方案,但我选择不过早地过度概括代码,超出您提供给我们的示例。

最后,我想可以在 Java 或 C# 中实现相同的方法,而且速度可能会更快。如果您的一般案例预计需要更长的时间才能解决,我不介意这样做。

Reti43 的想法是正确的,但是有一个快速的递归解决方案,可以在对不等式的限制较少的假设下工作。

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

您可以像这样将其应用于您的原始问题,

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

对于这样更长的问题,

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

在我的 Macbook 上,这两种情况都在几毫秒内解决了。这应该可以很好地扩展到稍大的问题,但从根本上说,它在系数 N 的数量上是 O(2^N)。它实际扩展的好坏取决于附加系数的大小——更大的系数(与 smax- 相比) smin),可能的解决方案越少,它就会越快运行。

更新:从链接M.SE post上的讨论,我看出这里两个不等式之间的关系是问题结构的一部分。鉴于此,可以给出一个稍微简单的解决方案。下面的代码还包括一些额外的优化,这些优化在我的笔记本电脑上将 8 变量情况的解决方案从 88 毫秒加速到 34 毫秒。我已经在包含多达 22 个变量的示例中尝试过此操作,并在不到一分钟的时间内得到了结果,但它永远不会适用于数百个变量。

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

您可以像这样将其应用于 8 变量示例,

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

这个方案直接枚举了有界区域内的格点。由于您需要所有这些解决方案,因此获得它们所需的时间将(最多)与绑定格点的数量成正比,绑定格点的数量随维度(变量)的数量呈指数增长。

(最多 13 个整数) 这是在 1600 个内核上使用 gpgpu(opencl) 的丑陋蛮力,在 9.3 毫秒内找到 1013(7 ints) 个解决方案,包括从 gpu 到 cpu 内存的数组下载时间:

编辑: 更正了 n1、n2、n3,因为它们是 1,20,400,而不是 20,20,20。

__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;

}

然后 subCount.Sum() 给出解决方案的数量。(以微秒为单位)

global work size = 8000(threads)(将 n4 加入其中将使它达到 160000 并提高性能)

本地工作大小= 160(在我的机器上效率低下,使其成为 2 的幂是最优的)

这只是一个要发送到gpu 进行编译的字符串。您只需在字符串中添加额外的循环(或只是 threadId 关系),如 n8、n9 并更改“if”正文以对它们求和。

编辑: 将 1 个额外的整数添加到方程中,即使进行更多优化(找到 4015 个解),求解时间也会增加到 101 毫秒。

  __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;

                }

工作组规模 = 256 全球工作规模 = 3200000 个线程

编辑: 仅含另一个 for 循环的 9 整数版本在 1581 毫秒内找到了 c1=3 和 c2=-4 的 14k 解。将 n6 到 n9 的上限从 20 更改为 19 在 1310 毫秒内给出了相同的结果。

编辑: 添加一些来自 Reti43Molecule 的答案,9整数版本使用 3.2M 线程花费 14 毫秒(但对于前 5 个整数仍然效率低下)。

__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 个整数:46k 解,35ms (c1=7,c2=-8)

  • 11 个整数:140k 解,103ms (c1=1,c2=-2)

  • 12 个整数:383k 解,274ms (c1=5,c2=-6)

  • 由于硬件限制,15 个整数可能太慢了。甚至不会编译 20 个整数。

    注意: m=max( max( max( max(n1,n2),n3),n4),n5) 不是最优的,但 m=n1+n2+n3+n4+n5 是。

我现在会尝试Monte Carlo方法来克服硬件资源瓶颈。也许以某种方式收敛到解决方案编号。

伪代码:

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