# RPython 5.4.1, n ≈ 32 (37 seconds) <!-- language-all: lang-python --> from rpython.rlib.rtime import time from rpython.rlib.rarithmetic import r_int, r_uint from rpython.rlib.rrandom import Random from rpython.rlib.rposix import pipe, close, read, write, fork, waitpid from rpython.rlib.rbigint import rbigint from math import log, ceil from struct import pack bitsize = len(pack('l', 1)) * 8 - 1 bitcounts = bytearray([0]) for i in range(16): b = bytearray([j+1 for j in bitcounts]) bitcounts += b def bitcount(n): bits = 0 while n: bits += bitcounts[n & 65535] n >>= 16 return bits def main(argv): if len(argv) < 2: write(2, 'Usage: %s NUM_THREADS [N]'%argv[0]) return 1 threads = int(argv[1]) if len(argv) > 2: n = int(argv[2]) rnd = Random(r_uint(time()*1000)) m = [] for i in range(n): row = [] for j in range(n): row.append(1 - r_int(rnd.genrand32() & 2)) m.append(row) else: m = [] strm = "" while True: buf = read(0, 4096) if len(buf) == 0: break strm += buf rows = strm.split("\n") for row in rows: r = [] for val in row.split(' '): r.append(int(val)) m.append(r) n = len(m) a = [] for row in m: val = 0 for v in row: val = (val << 1) | -(v >> 1) a.append(val) batches = int(ceil(n * log(n) / (bitsize * log(2)))) pids = [] handles = [] total = rbigint.fromint(0) for i in range(threads): r, w = pipe() pid = fork() if pid: close(w) pids.append(pid) handles.append(r) else: close(r) total = run(n, a, i, threads, batches) write(w, total.str()) close(w) return 0 for pid in pids: waitpid(pid, 0) for handle in handles: strval = read(handle, 256) total = total.add(rbigint.fromdecimalstr(strval)) close(handle) print total.rshift(n-1).str() return 0 def run(n, a, mynum, threads, batches): start = (1 << n-1) * mynum / threads end = (1 << n-1) * (mynum+1) / threads dtotal = rbigint.fromint(0) for delta in range(start, end): pdelta = rbigint.fromint(1 - ((bitcount(delta) & 1) << 1)) for i in range(batches): pbatch = 1 for j in range(i, n, batches): pbatch *= n - (bitcount(delta ^ a[j]) << 1) pdelta = pdelta.int_mul(pbatch) dtotal = dtotal.add(pdelta) return dtotal def target(*args): return main To compile, [download](http://pypy.org/download.html) the most recent PyPy source, and execute the following: pypy /path/to/pypy-src/rpython/bin/rpython matrix-permanent.py The resulting executable will be named `matrix-permanent-c` or similiar in the current working directory. As of PyPy 5.0, RPython's threading primitives are a lot less primitive than they used to be. Newly spawned threads require the GIL, which is more or less useless for parallel computations. I've used `fork` instead, so it may not work as expected on Windows, <s>although I haven't tested</s> fails to compile (`unresolved external symbol _fork`). The executable accepts up to two command line parameters. The first is the number of threads, the second optional parameter is `n`. If it is provided, a random matrix will be generated, otherwise it will be read from stdin. Each row must be newline separated (without a trailing newline), and each value space separated. The third example input would be given as: 1 -1 1 -1 -1 1 1 1 -1 -1 -1 -1 1 1 1 1 -1 1 1 -1 1 -1 1 1 1 1 1 -1 1 -1 -1 1 1 1 -1 -1 1 1 1 -1 -1 -1 1 1 1 -1 -1 -1 -1 1 -1 1 1 1 -1 -1 -1 1 -1 -1 -1 -1 -1 1 1 -1 1 1 1 1 1 1 -1 -1 -1 -1 -1 -1 1 -1 -1 1 1 1 -1 1 1 1 -1 -1 -1 1 -1 1 -1 1 1 1 1 1 1 -1 1 1 -1 -1 1 -1 1 1 1 1 -1 1 1 -1 1 -1 -1 -1 1 -1 -1 1 -1 -1 -1 1 -1 1 1 1 1 -1 -1 -1 1 1 1 -1 1 -1 -1 1 -1 1 1 -1 1 1 1 -1 1 -1 1 1 1 -1 1 1 1 -1 -1 -1 -1 -1 1 1 1 -1 -1 -1 -1 -1 1 1 -1 1 1 -1 -1 -1 1 -1 1 -1 1 1 -1 1 -1 1 1 1 1 1 1 -1 1 1 -1 -1 -1 -1 -1 -1 -1 1 -1 -1 -1 -1 1 1 1 1 -1 -1 -1 -1 1 1 -1 -1 -1 1 1 -1 -1 1 -1 1 1 -1 1 1 1 1 1 1 -1 1 1 -1 -1 -1 -1 -1 1 1 1 1 -1 -1 -1 -1 -1 1 -1 1 1 1 -1 -1 -1 1 -1 1 -1 -1 -1 -1 1 -1 1 1 -1 1 -1 1 1 1 1 1 1 -1 -1 -1 1 1 1 -1 1 -1 1 1 1 -1 1 1 1 -1 -1 1 -1 -1 -1 -1 1 -1 -1 1 1 -1 1 -1 -1 -1 -1 -1 -1 1 1 1 -1 1 1 -1 -1 1 1 1 -1 -1 1 1 -1 -1 1 1 1 1 -1 -1 1 1 -1 1 1 -1 1 1 1 -1 1 1 -1 1 -1 1 1 1 1 -1 -1 -1 1 -1 -1 1 1 -1 -1 -1 1 -1 -1 -1 -1 1 -1 1 1 1 -1 -1 -1 -1 -1 -1 -1 1 1 -1 1 1 -1 1 -1 -1 --- **Sample Usage** $ time ./matrix-permanent-c 8 30 8395059644858368 real 0m8.582s user 1m8.656s sys 0m0.000s --- **Method** I've used the [Balasubramanian-Bax/Franklin-Glynn formula](https://en.wikipedia.org/wiki/Computing_the_permanent#Balasubramanian-Bax.2FFranklin-Glynn_formula), with a runtime complexity of _O(2<sup>n</sup>n)_. However, instead of iterating the _δ_ in grey code order, I've instead replaced vector-row multiplication with a single xor operation (mapping (1, -1) → (0, 1)). The vector sum can likewise be found in a single operation, by taking n minus twice the popcount.