Python 2 с использованием pypy и pp: n = 15 за 3 минуты
Также просто грубая сила. Интересно видеть, что я почти получаю ту же скорость, что и курои нэко с C ++. Мой код может достигать n = 12около 5 минут. И я запускаю его только на одном виртуальном ядре.
изменить: уменьшить пространство поиска в n
Я заметил, что циклическое вектор A*из Aпроизводит одни и то же число , как и вероятности ( то же номера) в качестве исходного вектора , Aкогда я перебирать B. Например , вектор (1, 1, 0, 1, 0, 0)имеет ту же вероятность , как каждый из векторов (1, 0, 1, 0, 0, 1), (0, 1, 0, 0, 1, 1), (1, 0, 0, 1, 1, 0), (0, 0, 1, 1, 0, 1)и (0, 1, 1, 0, 1, 0)при выборе случайного образом B. Поэтому мне не нужно перебирать каждый из этих 6 векторов, а только около 1 и заменять count[i] += 1на count[i] += cycle_number.
Это уменьшает сложность от Theta(n) = 6^nдо Theta(n) = 6^n / n. Поэтому n = 13это примерно в 13 раз быстрее, чем моя предыдущая версия. Он рассчитывается n = 13примерно за 2 минуты 20 секунд. Потому n = 14что это все еще слишком медленно. Это займет около 13 минут.
редактировать 2: многоядерное программирование
Не очень доволен следующим улучшением. Я решил также попытаться выполнить мою программу на нескольких ядрах. На моих 2 + 2 ядрах я теперь могу вычислить n = 14примерно за 7 минут. Только улучшение в 2 раза.
Код доступен в этом репозитории github: Ссылка . Многоядерное программирование делает его немного уродливым.
редактировать 3: Сокращение пространства поиска для Aвекторов и Bвекторов
Я заметил ту же зеркальную симметрию для векторов, Aчто и Курой Неко. Все еще не уверен, почему это работает (и если это работает для каждогоn ).
Сокращение пространства поиска для Bвекторов немного умнее. Я заменил генерацию векторов ( itertools.product) собственной функцией. По сути, я начинаю с пустого списка и помещаю его в стек. Пока стек не пуст, я удаляю список, если он не имеет той же длины, что nи я, я генерирую 3 других списка (добавляя -1, 0, 1) и помещая их в стек. У меня такой же список, как nи у меня, я могу оценить суммы.
Теперь, когда я сам генерирую векторы, я могу фильтровать их в зависимости от того, смогу ли я достичь суммы = 0 или нет. Например, если мой вектор Aесть (1, 1, 1, 0, 0), и мой вектор Bвыглядит (1, 1, ?, ?, ?), я знаю, что я не могу заполнить ?значениями, так что A*B = 0. Поэтому мне не нужно перебирать все эти 6 векторов Bформы (1, 1, ?, ?, ?).
Мы можем улучшить это, если проигнорируем значения для 1. Как отмечалось в вопросе, значения для i = 1представляют собой последовательность A081671 . Есть много способов рассчитать их. Я выбираю простое повторение a(n) = (4*(2*n-1)*a(n-1) - 12*(n-1)*a(n-2)) / n. Так как мы можем вычислить i = 1практически за короткое время, мы можем отфильтровать больше векторов B. Например A = (0, 1, 0, 1, 1)и B = (1, -1, ?, ?, ?). Мы можем игнорировать векторы, где первый ? = 1, потому что A * cycled(B) > 0, для всех этих векторов. Я надеюсь, что вы можете следовать. Это, наверное, не лучший пример.
С этим я могу рассчитать n = 15за 6 минут.
редактировать 4:
Быстро реализовал отличную идею Куроя Неко, которая гласит, что Bи -Bдает те же результаты. Ускорение х2. Реализация - это только быстрый взлом. n = 15через 3 минуты.
Код:
Для полного кода посетите Github . Следующий код является только представлением основных функций. Я пропустил импорт, многоядерное программирование, печать результатов, ...
count = [0] * n
count[0] = oeis_A081671(n)
#generating all important vector A
visited = set(); todo = dict()
for A in product((0, 1), repeat=n):
if A not in visited:
# generate all vectors, which have the same probability
# mirrored and cycled vectors
same_probability_set = set()
for i in range(n):
tmp = [A[(i+j) % n] for j in range(n)]
same_probability_set.add(tuple(tmp))
same_probability_set.add(tuple(tmp[::-1]))
visited.update(same_probability_set)
todo[A] = len(same_probability_set)
# for each vector A, create all possible vectors B
stack = []
for A, cycled_count in dict_A.iteritems():
ones = [sum(A[i:]) for i in range(n)] + [0]
# + [0], so that later ones[n] doesn't throw a exception
stack.append(([0] * n, 0, 0, 0, False))
while stack:
B, index, sum1, sum2, used_negative = stack.pop()
if index < n:
# fill vector B[index] in all possible ways,
# so that it's still possible to reach 0.
if used_negative:
for v in (-1, 0, 1):
sum1_new = sum1 + v * A[index]
sum2_new = sum2 + v * A[index - 1 if index else n - 1]
if abs(sum1_new) <= ones[index+1]:
if abs(sum2_new) <= ones[index] - A[n-1]:
C = B[:]
C[index] = v
stack.append((C, index + 1, sum1_new, sum2_new, True))
else:
for v in (0, 1):
sum1_new = sum1 + v * A[index]
sum2_new = sum2 + v * A[index - 1 if index else n - 1]
if abs(sum1_new) <= ones[index+1]:
if abs(sum2_new) <= ones[index] - A[n-1]:
C = B[:]
C[index] = v
stack.append((C, index + 1, sum1_new, sum2_new, v == 1))
else:
# B is complete, calculate the sums
count[1] += cycled_count # we know that the sum = 0 for i = 1
for i in range(2, n):
sum_prod = 0
for j in range(n-i):
sum_prod += A[j] * B[i+j]
for j in range(i):
sum_prod += A[n-i+j] * B[j]
if sum_prod:
break
else:
if used_negative:
count[i] += 2*cycled_count
else:
count[i] += cycled_count
Использование:
Вы должны установить Pypy (для Python 2 !!!). Модуль параллельного Python не портирован для Python 3. Затем вам необходимо установить модуль параллельного Python pp-1.6.4.zip . Распакуйте его cdв папку и позвоните pypy setup.py install.
Тогда вы можете вызвать мою программу с
pypy you-do-the-math.py 15
Он автоматически определит количество процессоров. После завершения программы могут быть сообщения об ошибках, просто игнорируйте их. n = 16должно быть возможно на вашей машине.
Выход:
Calculation for n = 15 took 2:50 minutes
1 83940771168 / 470184984576 17.85%
2 17379109692 / 470184984576 3.70%
3 3805906050 / 470184984576 0.81%
4 887959110 / 470184984576 0.19%
5 223260870 / 470184984576 0.05%
6 67664580 / 470184984576 0.01%
7 30019950 / 470184984576 0.01%
8 20720730 / 470184984576 0.00%
9 18352740 / 470184984576 0.00%
10 17730480 / 470184984576 0.00%
11 17566920 / 470184984576 0.00%
12 17521470 / 470184984576 0.00%
13 17510280 / 470184984576 0.00%
14 17507100 / 470184984576 0.00%
15 17506680 / 470184984576 0.00%
Заметки и идеи:
- У меня процессор i7-4600m с 2 ядрами и 4 потоками. Неважно, если я использую 2 или 4 темы. Загрузка процессора составляет 50% с 2 потоками и 100% с 4 потоками, но это все равно занимает столько же времени. Я не знаю почему. Я проверил, что каждый поток имеет только половину объема данных, когда есть 4 потока, проверил результаты, ...
- Я использую много списков. Python не очень эффективен при хранении, мне приходится копировать много списков, поэтому я подумал об использовании целого числа вместо этого. Я мог бы использовать биты 00 (для 0) и 11 (для 1) в векторе A, и биты 10 (для -1), 00 (для 0) и 01 (для 1) в векторе B. Для произведения из A и B, мне нужно будет только рассчитать
A & Bи сосчитать блоки 01 и 10. Циклирование может быть выполнено с помощью смещения вектора и использования масок ... Я на самом деле все это реализовал, вы можете найти это в некоторых моих старых коммитах на Github. Но оказалось, что медленнее, чем со списками. Я думаю, Pypy действительно оптимизирует операции со списком.