Задача состоит в том, чтобы написать максимально быстрый код для вычисления перманента матрицы .
Перманент n
-by- n
matrix A
= ( a
i,j
) определяется как
Здесь S_n
представляет множество всех перестановок [1, n]
.
Как пример (из вики):
В этом вопросе матрицы все квадратные и будут иметь только значения -1
и 1
в них.
Примеры
Входные данные:
[[ 1 -1 -1 1]
[-1 -1 -1 1]
[-1 1 -1 1]
[ 1 -1 -1 1]]
Постоянный:
-4
Входные данные:
[[-1 -1 -1 -1]
[-1 1 -1 -1]
[ 1 -1 -1 -1]
[ 1 -1 1 -1]]
Постоянный:
0
Входные данные:
[[ 1 -1 1 -1 -1 -1 -1 -1]
[-1 -1 1 1 -1 1 1 -1]
[ 1 -1 -1 -1 -1 1 1 1]
[-1 -1 -1 1 -1 1 1 1]
[ 1 -1 -1 1 1 1 1 -1]
[-1 1 -1 1 -1 1 1 -1]
[ 1 -1 1 -1 1 -1 1 -1]
[-1 -1 1 -1 1 1 1 1]]
Постоянный:
192
Входные данные:
[[1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1],
[1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1],
[-1, -1, 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, 1, -1, -1],
[-1, -1, -1, 1, 1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1],
[-1, 1, 1, 1, -1, 1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1],
[1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, -1, -1, -1],
[1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1],
[1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1],
[1, -1, -1, -1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1],
[-1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, 1],
[-1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1],
[1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, 1, 1],
[-1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, -1, 1, -1, 1],
[1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, 1],
[1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1],
[1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1],
[-1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1],
[1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, -1, 1],
[1, 1, 1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, 1, -1, -1, -1, -1, 1],
[-1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1]]
Постоянный:
1021509632
Задание
Вы должны написать код, который с n
помощью n
матрицы выводит его постоянный.
Поскольку мне нужно будет протестировать ваш код, было бы полезно, если бы вы могли дать мне простой способ дать матрицу в качестве входных данных для вашего кода, например, путем чтения из стандартного в.
Имейте в виду, что перманент может быть большим (матрица all 1s - крайний случай).
Счета и связи
Я протестирую ваш код на случайных + 1 матрицах увеличивающегося размера и остановлюсь, когда ваш код займет больше 1 минуты на моем компьютере. Матрицы оценки будут согласованы для всех представленных документов, чтобы обеспечить справедливость.
Если два человека получают одинаковый результат, то победителем считается тот, кто быстрее всех получит это значение n
. Если они находятся в пределах 1 секунды друг от друга, то это тот, который размещен первым.
Языки и библиотеки
Вы можете использовать любой доступный язык и библиотеки, которые вам нравятся, но не можете использовать предварительно существующую функцию для вычисления перманента. Там, где это возможно, было бы хорошо иметь возможность запускать ваш код, поэтому, пожалуйста, включите полное объяснение того, как запускать / компилировать ваш код в Linux, если это возможно.
Реализованные реализации
Уже есть вопрос о Codegolf с большим количеством кода на разных языках для вычисления перманента для маленьких матриц. Mathematica и Maple также имеют постоянные реализации, если вы можете получить к ним доступ.
Моя машина Время будет работать на моей 64-битной машине. Это стандартная установка Ubuntu с 8 ГБ ОЗУ, восьмиъядерным процессором AMD FX-8350 и Radeon HD 4250. Это также означает, что мне нужно иметь возможность запускать ваш код.
Низкоуровневая информация о моей машине
cat /proc/cpuinfo/|grep flags
дает
флаги: FPU VME-де-псевдоэфедрин TSC MSR пае MCE CX8 APIC Сентябрь MTRR PGE MCA CMOV погладить pse36 clflush MMX fxsr сс sse2 ХТЫ системного вызов ого mmxext fxsr_opt pdpe1gb rdtscp ого constant_tsc rep_good nopl nonstop_tsc extd_apicid aperfmperf ПНИ PCLMULQDQ монитор SSSE3 FMA CX16 sse4_1 sse4_2 POPCNT АЕС XSAVE AVX f16c lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a неправильное выравнивание
Я задам тесно связанный вопрос о мультиязычности, который не страдает от большой проблемы с Int, поэтому любители Scala , Nim , Julia , Rust , Bash также могут похвастаться своими языками.
Таблица лидеров
- n = 33 (45 секунд. 64 секунды для n = 34). Тон Хоспел в C ++ с g ++ 5.4.0.
- n = 32 (32 секунды). Деннис в C с gcc 5.4.0 использует флаги gcc Тон Хоспел.
- n = 31 (54 секунды). Кристиан Сиверс в Хаскеле
- n = 31 (60 секунд). примы в rpython
- n = 30 (26 секунд). Езраст в Русте
- n = 28 (49 секунд). xnor с Python + pypy 5.4.1
- n = 22 (25 секунд). Шебанг с Python + pypy 5.4.1
Примечание . На практике время для Денниса и Тона Хоспела сильно различается по таинственным причинам. Например, они кажутся быстрее после того, как я загрузил веб-браузер! Указанные сроки являются самыми быстрыми во всех тестах, которые я проводил.
источник
Ответы:
gcc C ++ n ≈ 36 (57 секунд в моей системе)
Использует формулу Глинна с кодом Грея для обновлений, если все суммы столбцов являются четными, в противном случае используется метод Райзера. Резьбовое и векторизованное. Оптимизирован для AVX, поэтому не ожидайте многого от старых процессоров. Не беспокойтесь
n>=35
о матрице, имеющей только + 1, даже если ваша система достаточно быстрая, так как подписанный 128-битный аккумулятор переполнится. Для случайных матриц вы, вероятно, не попадете в переполнение. Дляn>=37
внутренних множителей начнет перетекать для всех1/-1
матрицы. Так что используйте только эту программу дляn<=36
.Просто укажите матричные элементы на STDIN, разделенные пробелами любого вида.
permanent.cpp
:источник
2 << (n-1)
сократить в конце, что означает, что мой аккумулятор int128 переполнился задолго до этой точки.C99, n ≈ 33 (35 секунд)
Ввод в настоящее время немного громоздкий; он берется со строками в качестве аргументов командной строки, где каждая запись представлена своим знаком, то есть + указывает на 1, а - указывает на -1 .
Тестовый забег
источник
popcnt
). Если это экономит время, следующим большим препятствием является целочисленный тип. Для случайно сгенерированных матриц перманент сравнительно мал. Если я смогу найти простой способ вычислить границу до выполнения фактического вычисления, я мог бы обернуть все это в большое условное выражение.Python 2, n ≈ 28
Использует формулу Глинна с кодом Грея для обновлений. Бежит за
n=23
минуту на моей машине. Можно, конечно, лучше реализовать это на более быстром языке и с лучшими структурами данных. Это не использует, что матрица ± 1-значная.Реализация формулы Райзера очень похожа, суммируя по всем 0/1 векторам коэффициентов, а не ± 1-векторов. Это занимает примерно вдвое больше, чем формула Глинна, потому что добавляет ко всем 2 ^ n таких векторов, тогда как половины Глинна используют симметрию только для тех, которые начинаются с
+1
.источник
pypy
этим удалось легко рассчитатьn=28
за 44,6 секунды. Система Лембика, похоже, довольно сравнима с моей по скорости, если не чуть быстрее.Haskell, n = 31 (54 с)
С помощью бесценного вклада @Angs: используйте
Vector
, используйте продукты короткого замыкания, посмотрите на нечетные n.Мои первые попытки параллелизма в Хаскеле. Вы можете увидеть множество шагов по оптимизации в истории изменений. Удивительно, но это были в основном очень маленькие изменения. Код основан на формуле в разделе «Формула Баласубрамана-Бакса / Франклина-Глинна» в статье Википедии о вычислении перманента .
p
вычисляет перманент Он вызывается с помощьюpt
которого преобразует матрицу способом, который всегда действителен, но особенно полезен для матриц, которые мы здесь получаем.Компилировать с
ghc -O2 -threaded -fllvm -feager-blackholing -o <name> <name>.hs
. Для запуска с параллелизмом, дайте ему время выполнения таких параметров , как это:./<name> +RTS -N
. Ввод осуществляется из стандартного ввода со списками, разделенными запятыми, в скобках,[[1,2],[3,4]]
как в предыдущем примере (переводы строк разрешены везде).источник
Data.Vector
. Изменения исключая изменены типы функций:import qualified Data.Vector as V
,x (V.zipWith(-) p v) vs (-m) c' )
,p (v:vs) = x (foldl (V.zipWith (+)) v vs) (map (V.map (2*)) vs) 1 11
,main = getContents >>= print . p . map V.fromList . read
V.product
). Это только дало мне ~ 10%. Изменен код, так что векторы содержат толькоInt
s. Это нормально, потому что они только добавляются, большие числа приходят из умножения. Тогда это было ~ 20%. Я попробовал то же самое изменение со старым кодом, но в то время это замедлило его. Я попробовал еще раз, потому что это позволяет использовать неупакованные векторы, которые очень помогли!x p _ m _ = m * (sum $ V.foldM' (\a b -> if b==0 then Nothing else Just $ a*fromIntegral b) 1 p)
- продукт в виде монадического сгиба, где 0 - это особый случай. Кажется, чаще всего полезно.Transversable
(я вижу, что вашаproduct
неизменная еда не была ошибкой ...) для ghc из, например, Debian stable. Он использует форму ввода, но это выглядит хорошо: мы не полагаемся на это, а только оптимизируем его. Делает выбор времени намного более захватывающим: моя случайная матрица 30x30 немного быстрее, чем 29x29, но затем 31x31 занимает 4 раза. - Кажется, что INLINE не работает для меня. AFAIK это игнорируется для рекурсивных функций.product
но забыл. Кажется, что только четные длины имеют нулиp
, поэтому для нечетной длины мы должны использовать обычный продукт вместо короткого замыкания, чтобы получить лучшее из обоих миров.Руст + Экстрим
Эта простая реализация кода Ryser с Греем занимает около
6590 секунд для запуска n = 31 на моем ноутбуке.Я полагаю, ваша машина доберется до 60 лет.Я использую Extprim 1.1.1 дляi128
.Я никогда не использовал Rust и понятия не имею, что я делаю. Никаких опций компилятора, кроме того, что
cargo build --release
делает. Комментарии / предложения / оптимизации приветствуются.Вызов идентичен программе Денниса.
источник
git clone https://gitlab.com/ezrast/permanent.git; cd permanent; cargo build --release
если хотите быть уверены, что у меня такая же настройка, как и у меня. Cargo будет обрабатывать зависимости. Двоичный код входитtarget/release
.Mathematica, n ≈ 20
Используя
Timing
команду, матрица 20x20 требует около 48 секунд в моей системе. Это не так эффективно, как другие, поскольку оно основано на том факте, что перманент можно найти как коэффициент произведения многочленов из каждой строки матрицы. Эффективное полиномиальное умножение выполняется путем создания списков коэффициентов и выполнения свертки с использованиемListConvolve
. Это требует времени O (2 n n 2 ), предполагая, что свертка выполняется с использованием быстрого преобразования Фурье или аналогичного, который требует времени O ( n log n ).источник
Python 2, n = 22 [ссылка]
Это «эталонная» реализация, которой я поделился с Лембиком вчера.
n=23
на своей машине несколько секунд, на моей машине он делает это за 52 секунды. Для достижения этих скоростей вам нужно запустить это через PyPy.Первая функция вычисляет перманент аналогично тому, как можно рассчитать определитель, обходя каждую подматрицу, пока у вас не останется 2x2, к которому вы можете применить основное правило. Это невероятно медленно .
Вторая функция - это функция, реализующая функцию Райзера (второе уравнение приведено в Википедии). Набор
S
по сути является набором чисел{1,...,n}
(переменнаяs_list
в коде).источник
RPython 5.4.1, n ≈ 32 (37 секунд)
Для компиляции скачайте самый последний исходный код PyPy и выполните следующее:
Полученный исполняемый файл будет назван
matrix-permanent-c
или похож в текущем рабочем каталоге.Начиная с PyPy 5.0 потоковые примитивы RPython намного менее примитивны, чем раньше. Вновь порожденные потоки требуют GIL, который более или менее бесполезен для параллельных вычислений. Я использовал
fork
вместо этого, поэтому он может работать не так, как ожидалось, в Windows,хотя я не проверял,не может скомпилировать (unresolved external symbol _fork
).Исполняемый файл принимает до двух параметров командной строки. Первый - это количество потоков, второй необязательный параметр
n
. Если он указан, будет сгенерирована случайная матрица, в противном случае она будет считана из стандартного ввода. Каждая строка должна быть разделена символом новой строки (без завершающего символа новой строки), и каждый интервал значений должен быть разделен. Третий пример ввода будет дан как:Образец использования
метод
Я использовал формулу Баласубрамана-Бакса / Франклина-Глинна со сложностью времени выполнения O (2 n n) . Однако вместо того, чтобы перебирать δ в порядке кода Грея, я заменил умножение вектора на строку одной операцией xor (отображение (1, -1) → (0, 1)). Векторная сумма также может быть найдена в одной операции, взяв n минус в два раза больше попконта.
источник
Ракетка 84 байта
Следующая простая функция работает для меньших матриц, но зависает на моей машине для больших матриц:
Ungolfed:
Код может быть легко изменен для неравного количества строк и столбцов.
Тестирование:
Выход:
Как я упоминал выше, зависает при тестировании следующее:
источник