Быстрый код, чтобы определить, имеют ли какие-либо два подмножества столбцов одинаковую сумму

Для заданных n и m я перебираю все n на m частичных циркулирующих матриц с записями, которые либо 0 или 1. Я хочу найти, существует ли такая матрица, в которой нет двух подмножеств столбцов, дающих одинаковую сумму. Здесь, когда мы добавляем столбцы, мы просто делаем это поэлементно. В моем текущем коде используется программирование в ограничениях с помощью ortools. Однако это не так быстро, как хотелось бы. Для n = 7 и m = 12 он занимает более 3 минут, а для n = 10, m = 18 он не завершается, хотя необходимо учитывать только 2 ^ 18 = 262144 различных матрицы. Вот мой код.

from scipy.linalg import circulant
import numpy as np
import itertools
from ortools.constraint_solver import pywrapcp as cs

n = 7
m = 12

def isdetecting(matrix):
    X = np.array([solver.IntVar(values) for i in range(matrix.shape[1])])
    X1 = X.tolist()
    for row in matrix:
        x = X[row].tolist()
        solver.Add(solver.Sum(x) == 0)
    db = solver.Phase(X1, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT)
    solver.NewSearch(db)
    count = 0
    while (solver.NextSolution() and count < 2):
        solution = [x.Value() for x in X1]
        count += 1
    solver.EndSearch()
    if (count < 2):
        return True

values = [-1,0,1]
solver = cs.Solver("scip")

for row in itertools.product([0,1],repeat = m):
    M = np.array(circulant(row)[0:n], dtype=bool)
    if isdetecting(M):
        print M.astype(int)
        break

Можно ли решить эту проблему достаточно быстро, чтобы можно было решить n = 10, m = 18?


person eleanora    schedule 04.03.2014    source источник
comment
Я бы переключился на язык с более быстрым компилятором и применил бы грубую силу, немного подергивая. Вычисления различимости 2 ^ 18 элементов с 2 ^ 18 64-битными целыми числами кажутся мне выполнимыми.   -  person David Eisenstat    schedule 04.03.2014


Ответы (2)


Одна из проблем заключается в том, что вы объявляете переменную «решателя» глобально, и это, кажется, сбивает с толку инструменты or, чтобы повторно использовать ее много раз. Когда он перемещается внутрь «isdetecting», тогда проблема (7,12) решается намного быстрее, примерно за 7 секунд (по сравнению с 2:51 минутами для исходной модели). Однако я не проверял его на предмет более серьезной проблемы.

Кроме того, было бы неплохо протестировать разные метки (вместо solver.INT_VAR_DEFAULT и solver.INT_VALUE_DEFAULT), хотя двоичное значение, как правило, не очень чувствительно к различным меткам. Смотрите код для другой маркировки.

def isdetecting(matrix):
   solver = cs.Solver("scip") # <----
   X = np.array([solver.IntVar(values) for i in range(matrix.shape[1])])
   X1 = X.tolist()
   for row in matrix:
       x = X[row].tolist()
       solver.Add(solver.Sum(x) == 0)
   # db = solver.Phase(X1, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT)
   db = solver.Phase(X1, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_CENTER_VALUE)    
   solver.NewSearch(db)
   count = 0
   while (solver.NextSolution() and count < 2):
       solution = [x.Value() for x in X1]
       count += 1
   solver.EndSearch()
   if (count < 2):
       print "FOUND"
       return True

Изменить: вот ограничения для удаления решений all-0, как указано в комментариях. Насколько я знаю, это требует отдельного списка. Теперь это занимает немного больше времени (10,4 с против 7 с).

X1Abs = [solver.IntVar(values, 'X1Abs[%i]' % i) for i in range(X1_len)]
for i in range(X1_len):
    solver.Add(X1Abs[i] == abs(X1[i])) 
solver.Add(solver.Sum(X1Abs) > 0)       
person hakank    schedule 04.03.2014
comment
Спасибо! На самом деле мой код также странен тем, что мне не нужны все нулевые решения, но поскольку я не понимаю, как их исключить, я проверяю, есть ли 2 или более решений. Это уродливый прием, и он может даже ускорить код, чтобы избавиться от него. - person eleanora; 04.03.2014
comment
(Надеюсь, я вас правильно понял.) Попробуйте добавить это ограничение, которое суммирует все значения в матрице: solver.Add (solver.Sum (X.flat) ›0) Затем решение отображается через 0,3 с. - person hakank; 04.03.2014
comment
Переменные в решении могут быть -1, 0 или 1, так что это будет сумма абсолютных значений. Это можно сделать? - person eleanora; 04.03.2014
comment
Почему смена этикеток ускоряет процесс ?? (Я только что протестировал это, и это действительно так.) - person eleanora; 04.03.2014
comment
@ user2179021: Я не уверен, но я думаю, что ускорение связано с тем, что or-tools предполагает, что Solver работает с одной проблемой. При глобальном использовании может возникнуть некоторая путаница с ограничениями (и, возможно, с проблемами памяти). Кроме того, каждый раз, когда вы закрывали решатель с помощью EndSearch, но не открывали его раньше, и это, очевидно, не очень хорошо. Это имеет для вас смысл? - person hakank; 04.03.2014
comment
На самом деле это фиктивное ограничение. - person David Eisenstat; 04.03.2014
comment
@hakank Извините, я имел в виду, почему использование db = solver.Phase (X1, solver.CHOOSE_FIRST_UNBOUND, solver.ASSIGN_CENTER_VALUE) ускоряет его по сравнению с db = solver.Phase (X1, solver.INT_VAR_DEFAULT, solver.INT_VALUE_DEFAULT)? - person eleanora; 04.03.2014
comment
@ user2179021 Обычно разные метки по-разному сокращают дерево поиска (что часто очень заметно для больших доменов). Хотя в данном конкретном случае я не могу подробно объяснить, почему это быстрее. Я должен сказать, что я часто пробую разные ярлыки, просто чтобы посмотреть, что из этого получится. - person hakank; 04.03.2014
comment
@ user2179021 Я добавил ограничение abs () в основной ответ. - person hakank; 04.03.2014
comment
@hakank Спасибо. Использование абсолютных значений, как ни странно, кажется медленнее, чем мой взлом. Может быть, есть более эффективный способ сделать это. (Я полагаю, что X1_len должно быть len (x1).) - person eleanora; 04.03.2014
comment
@DavidEisenstat Какое ограничение является фиктивным? - person eleanora; 04.03.2014
comment
@ user2179021 Да, я имел ввиду len (X1). Возможно, для решения этой проблемы можно было бы использовать ограничение AllDifferent, которое могло бы ускорить работу, поскольку оно довольно мощное. Что именно вы подразумеваете под отсутствием двух подмножеств столбцов, которые дают одинаковую сумму; это среди всех возможных 2 ^ m-1 подмножеств столбцов? - person hakank; 04.03.2014
comment
@hakank Это действительно так. Конечно, вы можете получить примеры, запустив мой код :) - person eleanora; 04.03.2014
comment
Ваш улучшенный код (без абс) занял 3 часа 10 на 18. Спасибо! - person eleanora; 05.03.2014
comment
Вы знаете, как выбрать другой решатель MIP (при условии, что он используется)? У меня установлен gurobi, который обычно работает очень быстро. - person eleanora; 05.03.2014
comment
@ user2179021 Для использования решателя MIP в or-tools см. этот пример: code.google.com/p/or-tools/source/browse/trunk/examples/python/. В том же каталоге есть и другие примеры (ищите _mip.py). Я еще не тестировал Gurobi с помощью or-tools / python. - person hakank; 05.03.2014

Примерно это то, что я имел в виду. Я бы оценил время работы параметров командной строки 10 18 на моей машине менее 8 часов.

public class Search {
    public static void main(String[] args) {
        int n = Integer.parseInt(args[0]);
        int m = Integer.parseInt(args[1]);
        int row = search(n, m);
        if (row >= 0) {
            printRow(m, row);
        }
    }

    private static int search(int n, int m) {
        if (n < 0 || m < n || m >= 31 || powOverflows(m + 1, n)) {
            throw new IllegalArgumentException();
        }
        long[] column = new long[m];
        long[] sums = new long[1 << m];
        int row = 1 << m;
        while (row-- > 0) {
            System.err.println(row);
            for (int j = 0; j < m; j++) {
                column[j] = 0;
                for (int i = 0; i < n; i++) {
                    column[j] = (column[j] * (m + 1)) + ((row >> ((i + j) % m)) & 1);
                }
            }
            for (int subset = 0; subset < (1 << m); subset++) {
                long sum = 0;
                for (int j = 0; j < m; j++) {
                    if (((subset >> j) & 1) == 1) {
                        sum += column[j];
                    }
                }
                sums[subset] = sum;
            }
            java.util.Arrays.sort(sums);
            boolean duplicate = false;
            for (int k = 1; k < (1 << m); k++) {
                if (sums[k - 1] == sums[k]) {
                    duplicate = true;
                    break;
                }
            }
            if (!duplicate) {
                break;
            }
        }
        return row;
    }

    private static boolean powOverflows(long b, int e) {
        if (b <= 0 || e < 0) {
            throw new IllegalArgumentException();
        }
        if (e == 0) {
            return false;
        }
        long max = Long.MAX_VALUE;
        while (e > 1) {
            if (b > Integer.MAX_VALUE) {
                return true;
            }
            if ((e & 1) == 1) {
                max /= b;
            }
            b *= b;
            e >>= 1;
        }
        return b > max;
    }

    private static void printRow(int m, int row) {
        for (int j = 0; j < m; j++) {
            System.out.print((row >> j) & 1);
        }
        System.out.println();
    }
}
person David Eisenstat    schedule 04.03.2014
comment
Интересно, можно ли ускорить метод грубой силы. Скажем, мы исправляем первую половину столбцов и перебираем только вторую половину. Нет необходимости повторно вычислять, есть ли два подмножества с одинаковой суммой полностью в пределах первой половины. Интересно, может ли эта идея сэкономить много перерасчетов. Это похоже на правдоподобное ускорение? - person eleanora; 04.03.2014
comment
@ user2179021 Я думал о чем-то в этом роде. Проблема в том, что, если мы эвристически предположим, что сталкивающиеся подмножества редки и равномерно распределены случайным образом, полностью 3/4 из них будет включать последний столбец, поэтому не похоже, что нас ждет огромный выигрыш. - person David Eisenstat; 04.03.2014