Le défi est d'écrire le code le plus rapide possible pour calculer le permanent d'une matrice .
Le permanent d'une matrice n
-by- = ( ) est défini commen
A
a
i,j
S_n
Représente ici l'ensemble de toutes les permutations de [1, n]
.
À titre d'exemple (du wiki):
Dans cette question, les matrices sont toutes carrées et n'auront que les valeurs -1
et 1
en elles.
Exemples
Contribution:
[[ 1 -1 -1 1]
[-1 -1 -1 1]
[-1 1 -1 1]
[ 1 -1 -1 1]]
Permanent:
-4
Contribution:
[[-1 -1 -1 -1]
[-1 1 -1 -1]
[ 1 -1 -1 -1]
[ 1 -1 1 -1]]
Permanent:
0
Contribution:
[[ 1 -1 1 -1 -1 -1 -1 -1]
[-1 -1 1 1 -1 1 1 -1]
[ 1 -1 -1 -1 -1 1 1 1]
[-1 -1 -1 1 -1 1 1 1]
[ 1 -1 -1 1 1 1 1 -1]
[-1 1 -1 1 -1 1 1 -1]
[ 1 -1 1 -1 1 -1 1 -1]
[-1 -1 1 -1 1 1 1 1]]
Permanent:
192
Contribution:
[[1, -1, 1, -1, -1, 1, 1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, 1, 1, -1],
[1, -1, 1, 1, 1, 1, 1, -1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, 1, -1],
[-1, -1, 1, 1, 1, -1, -1, -1, -1, 1, -1, 1, 1, 1, -1, -1, -1, 1, -1, -1],
[-1, -1, -1, 1, 1, -1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, -1],
[-1, 1, 1, 1, -1, 1, 1, 1, -1, -1, -1, 1, -1, 1, -1, 1, 1, 1, 1, 1],
[1, -1, 1, 1, -1, -1, 1, -1, 1, 1, 1, 1, -1, 1, 1, -1, 1, -1, -1, -1],
[1, -1, -1, 1, -1, -1, -1, 1, -1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1],
[1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1],
[1, -1, -1, -1, -1, -1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1],
[-1, -1, 1, -1, 1, -1, 1, 1, -1, 1, -1, 1, 1, 1, 1, 1, 1, -1, 1, 1],
[-1, -1, -1, -1, -1, -1, -1, 1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1],
[1, 1, -1, -1, -1, 1, 1, -1, -1, 1, -1, 1, 1, -1, 1, 1, 1, 1, 1, 1],
[-1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1, -1, -1, 1, -1, 1],
[1, 1, -1, -1, -1, 1, -1, 1, -1, -1, -1, -1, 1, -1, 1, 1, -1, 1, -1, 1],
[1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1, 1, -1, 1, 1, 1, -1, 1, 1],
[1, -1, -1, 1, -1, -1, -1, -1, 1, -1, -1, 1, 1, -1, 1, -1, -1, -1, -1, -1],
[-1, 1, 1, 1, -1, 1, 1, -1, -1, 1, 1, 1, -1, -1, 1, 1, -1, -1, 1, 1],
[1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1, 1, 1, -1, 1, 1, -1, 1, -1, 1],
[1, 1, 1, -1, -1, -1, 1, -1, -1, 1, 1, -1, -1, -1, 1, -1, -1, -1, -1, 1],
[-1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, -1, 1, 1, -1, 1, -1, -1]]
Permanent:
1021509632
La tâche
Vous devez écrire du code qui, donné n
par une n
matrice, sort son permanent.
Comme je devrai tester votre code, il serait utile que vous me donniez un moyen simple de donner une matrice en entrée à votre code, par exemple en lisant la norme dans.
Soyez averti que le permanent peut être grand (la matrice tous les 1 est le cas extrême).
Scores et égalités
Je vais tester votre code sur des matrices aléatoires + -1 de taille croissante et arrêter la première fois que votre code prend plus d'une minute sur mon ordinateur. Les matrices de notation seront cohérentes pour toutes les soumissions afin d'assurer l'équité.
Si deux personnes obtiennent le même score, le gagnant est celui qui est le plus rapide pour cette valeur de n
. Si ceux-ci sont à moins d'une seconde l'un de l'autre, c'est celui affiché en premier.
Langues et bibliothèques
Vous pouvez utiliser n'importe quelle langue et bibliothèque disponibles que vous aimez, mais aucune fonction préexistante pour calculer le permanent. Dans la mesure du possible, il serait bon de pouvoir exécuter votre code, veuillez donc inclure une explication complète sur la façon d'exécuter / compiler votre code sous Linux si possible. »
Implémentations de référence
Il existe déjà une question de codegolf avec beaucoup de code dans différentes langues pour calculer le permanent pour les petites matrices. Mathematica et Maple ont également des implémentations permanentes si vous pouvez y accéder.
Ma machine Les horaires seront exécutés sur ma machine 64 bits. Il s'agit d'une installation Ubuntu standard avec 8 Go de RAM, processeur AMD FX-8350 à huit cœurs et Radeon HD 4250. Cela signifie également que je dois pouvoir exécuter votre code.
Informations de bas niveau sur ma machine
cat /proc/cpuinfo/|grep flags
donne
drapeaux: fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ht syscall nx mmxext fxsr_opt pdpe1gb rdtscp lm constant_tsc rep_good nopl sst fm scl sp sf f16c lahf_lm cmp_legacy svm extapic cr8_legacy abm sse4a misalignsse 3dnowprefetch osvw ibs xop skinit wdt lwp fma4 tce nodeid_msr tbm topoext perfctr_core perfctr_nb cpb hw_pstate vmmccl ns
Je vais poser une question de suivi multilingue étroitement liée qui ne souffre pas du gros problème d'Int afin que les amateurs de Scala , Nim , Julia , Rust , Bash puissent également montrer leurs langues.
Classement
- n = 33 (45 secondes. 64 secondes pour n = 34). Ton Hospel en C ++ avec g ++ 5.4.0.
- n = 32 (32 secondes). Dennis en C avec gcc 5.4.0 utilisant les drapeaux gcc de Ton Hospel.
- n = 31 (54 secondes). Christian Sievers à Haskell
- n = 31 (60 secondes). primo en rpython
- n = 30 (26 secondes). ezrast à Rust
- n = 28 (49 secondes). xnor avec Python + pypy 5.4.1
- n = 22 (25 secondes). Shebang avec Python + pypy 5.4.1
Remarque . Dans la pratique, les horaires de Dennis et Ton Hospel varient beaucoup pour des raisons mystérieuses. Par exemple, ils semblent être plus rapides après avoir chargé un navigateur Web! Les horaires cités sont les plus rapides de tous les tests que j'ai effectués.
la source
Réponses:
gcc C ++ n ≈ 36 (57 secondes sur mon système)
Utilise la formule Glynn avec un code Gray pour les mises à jour si toutes les sommes des colonnes sont paires, sinon utilise la méthode Ryser. Fileté et vectorisé. Optimisé pour AVX, ne vous attendez donc pas à grand-chose sur les processeurs plus anciens. Ne vous embêtez pas
n>=35
pour une matrice avec seulement + 1 même si votre système est assez rapide car l'accumulateur 128 bits signé va déborder. Pour les matrices aléatoires, vous n'atteindrez probablement pas le débordement. Pourn>=37
les multiplicateurs internes commenceront à déborder pour une1/-1
matrice tout . N'utilisez donc ce programme que pourn<=36
.Donnez simplement les éléments de la matrice sur STDIN séparés par tout type d'espace blanc
permanent.cpp
:la source
2 << (n-1)
à la fin, ce qui signifie que mon accumulateur int128 a débordé bien avant ce point.C99, n ≈ 33 (35 secondes)
L'entrée est actuellement un peu lourde; il est pris avec des lignes comme arguments de ligne de commande, où chaque entrée est représentée par son signe, c'est-à-dire que + indique un 1 et - indique un -1 .
Essai
la source
popcnt
). Si cela vous fait gagner du temps, le prochain gros obstacle est le type entier. Pour les matrices générées aléatoirement, le permanent est relativement petit. Si je peux trouver un moyen facile de calculer une limite avant de faire le calcul réel, je pourrais envelopper le tout dans un grand conditionnel.Python 2, n ≈ 28
Utilise la formule Glynn avec un code Gray pour les mises à jour. Fonctionne jusqu'à
n=23
dans une minute sur ma machine. On peut sûrement mieux implémenter cela dans un langage plus rapide et avec de meilleures structures de données. Cela n'utilise pas que la matrice a une valeur de ± 1.Une implémentation de formule Ryser est très similaire, sommant tous les vecteurs 0/1 de coefficients plutôt que les vecteurs ± 1. Cela prend environ deux fois plus de temps que la formule de Glynn, car il ajoute tous les 2 ^ n de ces vecteurs, tandis que Glynn divise par deux l'utilisation de la symétrie uniquement pour ceux commençant par
+1
.la source
pypy
cela a pu calculer facilementn=28
en 44,6 secondes. Le système de Lembik semble être assez comparable au mien en vitesse, sinon un peu plus vite.Haskell, n = 31 (54s)
Avec beaucoup de précieuses contributions de @Angs: utilisez
Vector
, utilisez des produits de court-circuit, regardez n impair.Mes premières tentatives de parallélisme à Haskell. Vous pouvez voir de nombreuses étapes d'optimisation dans l'historique des révisions. Étonnamment, il s'agissait principalement de très petits changements. Le code est basé sur la formule de la section "Formule de Balasubramanian-Bax / Franklin-Glynn" dans l'article Wikipedia sur le calcul du permanent .
p
calcule le permanent. Il est appelé viapt
qui transforme la matrice d'une manière qui est toujours valide, mais particulièrement utile pour les matrices que nous obtenons ici.Compilez avec
ghc -O2 -threaded -fllvm -feager-blackholing -o <name> <name>.hs
. Pour exécuter avec parallélisation, lui donner paramètres d' exécution comme ceci:./<name> +RTS -N
. L'entrée provient de stdin avec des listes imbriquées séparées par des virgules entre crochets comme[[1,2],[3,4]]
dans le dernier exemple (les retours à la ligne sont autorisés partout).la source
Data.Vector
. Les changements ont changé à l' exclusion des types de fonction: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
). Cela ne m'a donné que ~ 10%. Modification du code pour que les vecteurs ne contiennent queInt
s. Ce n'est pas grave car ils ne sont ajoutés, les grands nombres proviennent de la multiplication. Ensuite, c'était ~ 20%. J'avais essayé le même changement avec l'ancien code, mais à ce moment-là, cela le ralentissait. J'ai réessayé car cela permet d'utiliser des vecteurs non boxés , ce qui m'a beaucoup aidé!x p _ m _ = m * (sum $ V.foldM' (\a b -> if b==0 then Nothing else Just $ a*fromIntegral b) 1 p)
- produit sous forme de pli monadique où 0 est un cas spécial. Semble être bénéfique le plus souvent.Transversable
(je vois que vous ne changez pas plusproduct
tôt n'était pas une erreur ...) pour ghc par exemple de Debian stable. Il utilise la forme de l'entrée, mais cela semble bien: nous ne nous appuyons pas sur elle, nous l'optimisons uniquement. Rend le timing beaucoup plus excitant: ma matrice aléatoire 30x30 est légèrement plus rapide que 29x29, mais 31x31 prend alors 4x. - Que INLINE ne semble pas fonctionner pour moi. AFAIK, il est ignoré pour les fonctions récursives.product
mais j'ai oublié. Il semble que seules les longueurs égales contiennent des zérosp
, donc pour les longueurs impaires, nous devrions utiliser le produit normal au lieu du court-circuitage pour tirer le meilleur parti des deux mondes.Rouille + extprim
Ce Ryser simple avec implémentation de code Gray prend environ
6590 secondes pour exécuter n = 31 sur mon ordinateur portable.J'imagine que votre machine arrivera dans bien moins de 60 ans.J'utilise extprim 1.1.1 pouri128
.Je n'ai jamais utilisé Rust et je n'ai aucune idée de ce que je fais. Aucune option de compilation autre que quoi que ce soit
cargo build --release
. Les commentaires / suggestions / optimisations sont appréciés.L'invocation est identique au programme de Dennis.
la source
git clone https://gitlab.com/ezrast/permanent.git; cd permanent; cargo build --release
si vous voulez être sûr d'avoir la même configuration que moi. Le fret gérera les dépendances. Le binaire entretarget/release
.Mathematica, n ≈ 20
En utilisant la
Timing
commande, une matrice 20x20 nécessite environ 48 secondes sur mon système. Ce n'est pas exactement aussi efficace que l'autre car il repose sur le fait que le permanent peut être trouvé comme le coefficient du produit des polymômes de chaque ligne de la matrice. Une multiplication polynomiale efficace est effectuée en créant les listes de coefficients et en effectuant une convolution à l'aide deListConvolve
. Cela nécessite environ O (2 n n 2 ) de temps en supposant que la convolution est effectuée en utilisant une transformée de Fourier rapide ou similaire qui nécessite un temps de O ( n log n ).la source
Python 2, n = 22 [Référence]
Ceci est la mise en œuvre de «référence» que j'ai partagée avec Lembik hier, il lui manque de le faire
n=23
quelques secondes sur sa machine, sur ma machine il le fait en 52 secondes environ. Pour atteindre ces vitesses, vous devez exécuter ceci via PyPy.La première fonction calcule le permanent similaire à la façon dont le déterminant pourrait être calculé, en parcourant chaque sous-matrice jusqu'à ce que vous vous retrouvez avec un 2x2 auquel vous pouvez appliquer la règle de base. C'est incroyablement lent .
La deuxième fonction est celle qui implémente la fonction Ryser (la deuxième équation répertoriée dans Wikipedia). L'ensemble
S
est essentiellement la puissance des nombres{1,...,n}
(variables_list
dans le code).la source
RPython 5.4.1, n ≈ 32 (37 secondes)
Pour compiler, téléchargez la source PyPy la plus récente et exécutez ce qui suit:
L'exécutable résultant sera nommé
matrix-permanent-c
ou similaire dans le répertoire de travail actuel.Depuis PyPy 5.0, les primitives de threading de RPython sont beaucoup moins primitives qu'auparavant. Les threads nouvellement créés nécessitent le GIL, qui est plus ou moins inutile pour les calculs parallèles. Je l'ai utilisé à la
fork
place, donc cela peut ne pas fonctionner comme prévu sous Windows,bien que je n'aie pas testé lacompilation (unresolved external symbol _fork
).L'exécutable accepte jusqu'à deux paramètres de ligne de commande. Le premier est le nombre de threads, le deuxième paramètre facultatif est
n
. S'il est fourni, une matrice aléatoire sera générée, sinon elle sera lue depuis stdin. Chaque ligne doit être séparée par un retour à la ligne (sans retour à la ligne) et chaque espace de valeur séparé. Le troisième exemple d'entrée serait donné comme suit:Exemple d'utilisation
Méthode
J'ai utilisé la formule Balasubramanian-Bax / Franklin-Glynn , avec une complexité d'exécution de O (2 n n) . Cependant, au lieu d'itérer le δ dans l'ordre des codes gris, j'ai plutôt remplacé la multiplication de lignes vectorielles par une seule opération xor (mappage (1, -1) → (0, 1)). La somme vectorielle peut également être trouvée en une seule opération, en prenant n moins le double du popcount.
la source
Raquette 84 octets
La fonction simple suivante fonctionne pour les matrices plus petites mais se bloque sur ma machine pour les matrices plus grandes:
Non golfé:
Le code peut facilement être modifié pour un nombre inégal de lignes et de colonnes.
Essai:
Sortie:
Comme je l'ai mentionné ci-dessus, cela dépend des tests suivants:
la source