Задача состоит в том, чтобы написать максимально быстрый код для вычисления матрицы Хафниана .
Hafnian симметричной 2n
матрицы с размерностью 2n
матрицы A
определяются следующим образом:
Здесь S 2n представляет множество всех перестановок целых чисел от 1
до 2n
, то есть [1, 2n]
.
Ссылка на википедию также дает другую формулу, которая может быть интересна (и даже более быстрые методы существуют, если вы загляните дальше в сеть). На той же вики-странице говорится о матрицах смежности, но ваш код должен работать и для других матриц. Можно предположить, что все значения будут целыми числами, но не все они будут положительными.
Существует также более быстрый алгоритм, но его трудно понять. и Кристиан Сиверс был первым, кто реализовал это (в Хаскеле).
В этом вопросе все матрицы квадратные и симметричные с четной размерностью.
Ссылочная реализация (обратите внимание, что это самый медленный из возможных методов).
Вот пример кода Python от мистера Xcoder.
from itertools import permutations
from math import factorial
def hafnian(matrix):
my_sum = 0
n = len(matrix) // 2
for sigma in permutations(range(n*2)):
prod = 1
for j in range(n):
prod *= matrix[sigma[2*j]][sigma[2*j+1]]
my_sum += prod
return my_sum / (factorial(n) * 2 ** n)
print(hafnian([[-1, 1, 1, -1, 0, 0, 1, -1], [1, 0, 1, 0, -1, 0, -1, -1], [1, 1, -1, 1, -1, -1, 0, -1], [-1, 0, 1, -1, -1, 1, -1, 0], [0, -1, -1, -1, -1, 0, 0, -1], [0, 0, -1, 1, 0, 0, 1, 1], [1, -1, 0, -1, 0, 1, 1, 0], [-1, -1, -1, 0, -1, 1, 0, 1]]))
4
M = [[1, 1, 0, 0, 0, 0, 0, 1, 0, 0], [1, 1, -1, 0, -1, 1, 1, 1, 0, -1], [0, -1, -1, -1, 0, -1, -1, 0, -1, 1], [0, 0, -1, 1, -1, 1, -1, 0, 1, -1], [0, -1, 0, -1, -1, -1, -1, 1, -1, 1], [0, 1, -1, 1, -1, 1, -1, -1, 1, -1], [0, 1, -1, -1, -1, -1, 1, 0, 0, 0], [1, 1, 0, 0, 1, -1, 0, 1, 1, -1], [0, 0, -1, 1, -1, 1, 0, 1, 1, 1], [0, -1, 1, -1, 1, -1, 0, -1, 1, 1]]
print(hafnian(M))
-13
M = [[-1, 0, -1, -1, 0, -1, 0, 1, -1, 0, 0, 0], [0, 0, 0, 0, 0, -1, 0, 1, -1, -1, -1, -1], [-1, 0, 0, 1, 0, 0, 0, 1, -1, 1, -1, 0], [-1, 0, 1, -1, 1, -1, -1, -1, 0, -1, -1, -1], [0, 0, 0, 1, 0, 0, 0, 0, 0, 1, -1, 0], [-1, -1, 0, -1, 0, 0, 1, 1, 1, 1, 1, 0], [0, 0, 0, -1, 0, 1, 1, -1, -1, 0, 1, 0], [1, 1, 1, -1, 0, 1, -1, 1, -1, -1, -1, -1], [-1, -1, -1, 0, 0, 1, -1, -1, -1, 1, -1, 0], [0, -1, 1, -1, 1, 1, 0, -1, 1, -1, 1, 1], [0, -1, -1, -1, -1, 1, 1, -1, -1, 1, 0, -1], [0, -1, 0, -1, 0, 0, 0, -1, 0, 1, -1, 1]]
print(hafnian(M))
13
M = [[-1, 1, 0, 1, 0, -1, 0, 0, -1, 1, -1, 1, 0, -1], [1, -1, 1, -1, 1, 1, -1, 0, -1, 1, 1, 0, 0, -1], [0, 1, 1, 1, -1, 1, -1, -1, 0, 0, -1, 0, -1, -1], [1, -1, 1, -1, 1, 0, 1, 1, -1, -1, 0, 0, 1, 1], [0, 1, -1, 1, 0, 1, 0, 1, -1, -1, 1, 1, 0, -1], [-1, 1, 1, 0, 1, 1, -1, 0, 1, -1, -1, -1, 1, -1], [0, -1, -1, 1, 0, -1, -1, -1, 0, 1, -1, 0, 1, -1], [0, 0, -1, 1, 1, 0, -1, 0, 0, -1, 0, 0, 0, 1], [-1, -1, 0, -1, -1, 1, 0, 0, 1, 1, 0, 1, -1, 0], [1, 1, 0, -1, -1, -1, 1, -1, 1, 1, 1, 0, 1, 0], [-1, 1, -1, 0, 1, -1, -1, 0, 0, 1, -1, 0, -1, 0], [1, 0, 0, 0, 1, -1, 0, 0, 1, 0, 0, 1, 1, 1], [0, 0, -1, 1, 0, 1, 1, 0, -1, 1, -1, 1, 1, -1], [-1, -1, -1, 1, -1, -1, -1, 1, 0, 0, 0, 1, -1, -1]]
print(hafnian(M))
83
Задание
Вы должны написать код, который с 2n
помощью 2n
матрицы выводит свой гафнианский.
Поскольку мне нужно будет протестировать ваш код, было бы полезно, если бы вы могли дать мне простой способ дать матрицу в качестве входных данных для вашего кода, например, путем чтения из стандартного в. Я протестирую ваш код в случайно выбранных матрицах с элементами выбран из {-1, 0, 1}. Цель такого тестирования состоит в том, чтобы уменьшить вероятность того, что гафнян будет очень большой ценностью.
В идеале ваш код сможет читать в матрицах точно так же, как я это делал в примерах в этом вопросе, прямо из стандартного. Это означает, что ввод будет выглядеть, [[1,-1],[-1,-1]]
например. Если вы хотите использовать другой формат ввода, пожалуйста, спросите, и я сделаю все возможное, чтобы приспособиться.
Счета и связи
Я протестирую ваш код на случайных матрицах увеличивающегося размера и остановлюсь, когда ваш код займет больше 1 минуты на моем компьютере. Матрицы оценки будут согласованы для всех представленных документов, чтобы обеспечить справедливость.
Если два человека получают одинаковый результат, то победителем считается тот, кто быстрее всех получит это значение n
. Если они находятся в пределах 1 секунды друг от друга, то это тот, который размещен первым.
Языки и библиотеки
Вы можете использовать любой доступный язык и библиотеки, которые вам нравятся, но без ранее существовавшей функции для вычисления гафнианского. Там, где это возможно, было бы хорошо иметь возможность запускать ваш код, поэтому, пожалуйста, включите полное объяснение того, как запускать / компилировать ваш код в Linux, если это возможно.
Моя машина Время будет работать на моей 64-битной машине. Это стандартная установка Ubuntu с 8 ГБ ОЗУ, восьмиъядерным процессором AMD FX-8350 и Radeon HD 4250. Это также означает, что мне нужно иметь возможность запускать ваш код.
Звоните для ответов на нескольких языках
Было бы здорово получить ответы на своем любимом супер-быстром языке программирования. Для начала, как насчет Фортрана , Нима и Ржавчины ?
Leaderboard
- 52 миль с использованием C ++ . 30 секунд.
- 50 с использованием СПП C . 50 секунд
- 46 от Christian Sievers с использованием Haskell . 40 секунд
- 40 миль, используя Python 2 + pypy . 41 секунда
- 34 по ngn, используя Python 3 + pypy . 29 секунд
- 28 от Денниса с использованием Python 3 . 35 секунд (Pypy медленнее)
Ответы:
Haskell
Это реализует вариант Алгоритма 2 Андреаса Бьёрклунда: Подсчет идеальных совпадений так же быстро, как и Райзера .
Компиляция с использованием
ghc
параметров времени компиляции-O3 -threaded
и использования параметров времени выполнения+RTS -N
для распараллеливания. Принимает ввод от стандартного ввода.источник
parallel
иvector
должны быть установлены?Python 3
Попробуйте онлайн!
источник
C ++ (gcc)
Попробуйте онлайн! (13 с для n = 24)
На основе более быстрой реализации Python в моем другом посте. Отредактируйте вторую строку
#define S 3
на вашем 8-ядерном компьютере и скомпилируйте сg++ -pthread -march=native -O2 -ftree-vectorize
.Делит работу пополам, поэтому стоимость
S
должна бытьlog2(#threads)
. Типы могут быть легко изменены междуint
,long
,float
иdouble
путем изменения значения#define TYPE
.источник
tr -d \ < matrix52.txt > matrix52s.txt
Python 3
Это вычисляет haf (A) как запомненную сумму (A [i] [j] * haf (A без строк и столбцов i и j)).
источник
С
Еще один след из статьи Андреаса Бьёрклунда , которую намного легче понять, если вы посмотрите на код Хаскелла Кристиана Сиверса . Для первых нескольких уровней рекурсии она распределяет циклические потоки по доступным процессорам. Последний уровень рекурсии, на который приходится половина вызовов, оптимизируется вручную.
Компиляция с:
gcc -O3 -pthread -march=native
; спасибо @Dennis за ускорение в 2 разаn = 24 в 24 с на TIO
Алгоритм:
Симметричная матрица хранится в нижнем левом треугольном виде. Индексы треугольника
i,j
соответствуют линейному индексу,T(max(i,j))+min(i,j)
гдеT
есть макрос дляi*(i-1)/2
. Матричные элементы являются полиномами степениn
. Многочлен представляется в виде массива коэффициентов, упорядоченных от постоянного члена (p[0]
) до коэффициента x n (p[n]
). Начальные значения матрицы -1,0,1 сначала преобразуются в полиномы const.Мы выполняем рекурсивный шаг с двумя аргументами: полуматрица (то есть треугольник)
a
полиномов и отдельный полиномp
(в статье упоминается как бета). Мы уменьшаемm
проблему размера (изначальноm=2*n
) рекурсивно до двух проблем размераm-2
и возвращаем разность их hafnians. Один из них - использовать то же самоеa
без последних двух строк, и то же самоеp
. Другой - использовать треугольникb[i][j] = a[i][j] + shmul(a[m-1][i],a[m-2][j]) + shmul(a[m-1][j],a[m-2][i])
(гдеshmul
операция многократного смещения для многочленов - это как полиномиальное произведение, как обычно, дополнительно умноженное на переменную "x"; степени, превышающие x ^ n, игнорируются) и отдельный многочленq = p + shmul(p,a[m-1][m-2])
. Когда рекурсия поражает размер-0a
, мы возвращаем главный коэффициент р:p[n]
.Операция сдвига и умножения реализована в функции
f(x,y,z)
. Он модифицируетz
на месте. Грубо говоря, это такz += shmul(x,y)
. Это, кажется, самая важная часть производительности.После завершения рекурсии нам нужно исправить знак результата, умножив его на (-1) n .
источник
-march=native
будет иметь большое значение здесь. По крайней мере, на TIO, это почти вдвое сокращает время на стене.питон
Это довольно прямая, эталонная реализация алгоритма 2 из упомянутой статьи . Единственными изменениями были только сохранение текущего значения B , отбрасывание значений β путем обновления только g, когда i ∈ X , и усеченное полиномиальное умножение только путем вычисления значений до степени n .
Попробуйте онлайн!
Вот более быстрая версия с некоторыми простыми оптимизациями.
Попробуйте онлайн!
Для дополнительного удовольствия, вот справочная реализация в J.
источник
pmul
, использоватьfor j in range(n-i):
и избегатьif
j != k
наj < k
. В другом случае он копирует подматрицу, чего можно избежать, когда мы обрабатываем и удаляем последние два вместо первых двух строк и столбцов. И когда он вычисляет с,x={1,2,4}
а потом сx={1,2,4,6}
этим, он повторяет свои вычисления доi=5
. Я заменил структуру двух внешних циклов, сначала включив цикл,i
а затем рекурсивно предполагаяi in X
иi not in X
. - Кстати, было бы интересно посмотреть на рост необходимого времени по сравнению с другими более медленными программами.октава
Это в основном копия записи Денниса , но оптимизированная для Octave. Основная оптимизация выполняется путем использования полной входной матрицы (и ее транспонирования) и рекурсии с использованием только индексов матриц, а не путем создания сокращенных матриц.
Основным преимуществом является уменьшенное копирование матриц. Хотя Octave не имеет различий между указателями / ссылками и значениями и функционально выполняет только передачу по значению, за кулисами это другая история. Там используется копирование при записи (ленивая копия). Это означает, что для кода
a=1;b=a;b=b+1
переменнаяb
копируется в новое место только в последнем операторе, когда она изменяется. Посколькуmatin
иmatransp
никогда не изменяются, они никогда не будут скопированы. Недостатком является то, что функция тратит больше времени на вычисление правильных показателей. Возможно, мне придется попробовать разные варианты между числовыми и логическими показателями, чтобы оптимизировать это.Важное примечание: матрица ввода должна быть
int32
! Сохраните функцию в файле с именемhaf.m
Пример тестового скрипта:
Я попробовал это, используя TIO и MATLAB (я фактически никогда не устанавливал Octave). Я думаю, заставить его работать так же просто, как
sudo apt-get install octave
. Командаoctave
загрузит графический интерфейс Octave. Если это будет сложнее, чем этот, я буду удалять этот ответ, пока не предоставлю более подробные инструкции по установке.источник
Недавно Андреас Бьорклунд, Брайжеш Гупт и я опубликовали новый алгоритм для гафнианов сложных матриц: https://arxiv.org/pdf/1805.12498.pdf . Для матрицы n \ times n она масштабируется как n ^ 3 2 ^ {n / 2}.
Если я правильно понимаю оригинальный алгоритм Андреаса из https://arxiv.org/pdf/1107.4466.pdf, он масштабируется как n ^ 4 2 ^ {n / 2} или n ^ 3 log (n) 2 ^ {n / 2} если вы использовали преобразования Фурье для полиномиальных умножений.
Наш алгоритм специально разработан для сложных матриц, поэтому он не будет таким быстрым, как разработанные здесь для {-1,0,1} матриц. Интересно, однако, можно ли использовать некоторые приемы, которые вы использовали для улучшения нашей реализации? Также, если людям интересно, я хотел бы увидеть, как их реализация делает, когда даны комплексные числа вместо целых чисел. Наконец, любые комментарии, критика, улучшения, ошибки, улучшения приветствуются в нашем репозитории https://github.com/XanaduAI/hafnian/
Ура!
источник