The challenge is to write the fastest code possible for computing the Hafnian of a matrix.
The Hafnian of a symmetric 2n
-by-2n
matrix A
is defined as:
$$\operatorname{haf}(A)=\frac{1}{n ! 2^n} \sum_{\sigma \in S_{2 n}} \prod_{j=1}^n A_{\sigma(2 j-1), \sigma(2 j)}$$
Here \$ S_{2 n} \$ represents the set of all permutations of the integers from 1
to 2n
, that is [1, 2n]
.
The wikipedia link also gives a different looking formula which may be of interest (and even faster methods exist if you look further on the web). The same wiki page talks about adjacency matrices but your code should work for other matrices as well. You can assume the values will all be integers but not that that they are all positive.
There is also a faster algorithm but it seems hard to understand. and Christian Sievers was the first to implement it (in Haskell).
In this question matrices are all square and symmetric with even dimension.
Reference implementation (note this is using the slowest possible method).
Here is some example python code from Mr. 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
The task
You should write code that, given an 2n
by 2n
matrix, outputs its Hafnian.
As I will need to test your code it would be helpful if you could give a simple way for me to give a matrix as input to your code, for example by reading from standard in. I will test your code in randomly chosen matrices with elements selected from {-1, 0, 1}. The purpose of testing like this is to reduce the chance the Hafnian will be a very large value.
Ideally your code will be able to read in matrices exactly as I have them in the examples in this question straight from standard in. That is the input would look like [[1,-1],[-1,-1]]
for example. If you want to use another input format, please ask and I will do my best to accommodate.
Scores and ties
I will test your code on random matrices of increasing size and stop the first time your code takes more than 1 minute on my computer. The scoring matrices will be consistent for all submissions in order to ensure fairness.
If two people get the same score then the winner is the one which is fastest for that value of n
. If those are within 1 second of each other then it is the one posted first.
Languages and libraries
You can use any available language and libraries you like but no pre-existing function to compute the Hafnian. Where feasible, it would be good to be able to run your code so please include a full explanation for how to run/compile your code in Linux if at all possible.`
My Machine The timings will be run on my 64-bit machine. This is a standard ubuntu install with 8GB RAM, AMD FX-8350 Eight-Core Processor and Radeon HD 4250. This also means I need to be able to run your code.
Call for answers in more languages
It would be great to get answers in your favorite super fast programming language. To start things off, how about fortran, nim and rust?
Leaderboard
- 52 by miles using C++. 30 seconds.
- 50 by ngn using C. 50 seconds.
- 46 by Christian Sievers using Haskell. 40 seconds.
- 40 by miles using Python 2 + pypy. 41 seconds.
- 34 by ngn using Python 3 + pypy. 29 seconds.
- 28 by Dennis using Python 3. 35 seconds. (Pypy is slower)