Python C extension to compute the permanent.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

89 lines
3.1KB

  1. /* Computes the permanent, given a numpy array */
  2. #define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
  3. #include <Python.h>
  4. #include <numpy/arrayobject.h>
  5. // Array access macros.
  6. #define SM(x0, x1) (*(npy_complex128*)((PyArray_DATA(submatrix) + \
  7. (x0) * PyArray_STRIDES(submatrix)[0] + \
  8. (x1) * PyArray_STRIDES(submatrix)[1])))
  9. #define SM_shape(x0) (int) PyArray_DIM(submatrix, x0)
  10. int countbits(unsigned int n)
  11. {
  12. int q=n;
  13. q = (q & 0x5555555555555555) + ((q & 0xAAAAAAAAAAAAAAAA) >> 1);
  14. q = (q & 0x3333333333333333) + ((q & 0xCCCCCCCCCCCCCCCC) >> 2);
  15. q = (q & 0x0F0F0F0F0F0F0F0F) + ((q & 0xF0F0F0F0F0F0F0F0) >> 4);
  16. q = (q & 0x00FF00FF00FF00FF) + ((q & 0xFF00FF00FF00FF00) >> 8);
  17. q = (q & 0x0000FFFF0000FFFF) + ((q & 0xFFFF0000FFFF0000) >> 16);
  18. q = (q & 0x00000000FFFFFFFF) + ((q & 0xFFFFFFFF00000000) >> 32); // This last & isq't strictly qecessary.
  19. return q;
  20. }
  21. int bitparity (unsigned int n) { return 1 - (countbits(n) & 1)*2; }
  22. // Complex numbers
  23. static const npy_complex128 complex_one = {.real=1, .imag=0};
  24. static const npy_complex128 complex_zero = {.real=0, .imag=0};
  25. static npy_complex128 complex_add(npy_complex128 a, npy_complex128 b) {
  26. npy_complex128 x;
  27. x.real = a.real+b.real;
  28. x.imag = a.imag+b.imag;
  29. return x;
  30. }
  31. static npy_complex128 complex_prod(npy_complex128 a, npy_complex128 b) {
  32. npy_complex128 x;
  33. x.real = a.real*b.real - a.imag*b.imag;
  34. x.imag = a.imag*b.real + a.real*b.imag;
  35. return x;
  36. }
  37. // Boilerplate
  38. static PyObject *permanent(PyObject *self, PyObject *args); // Forward function declaration
  39. static PyMethodDef methods[] = { // Method list
  40. { "permanent", permanent, METH_VARARGS, "Compute the permanent"},
  41. { NULL, NULL, 0, NULL } /* Sentinel */
  42. };
  43. PyMODINIT_FUNC initpermanent(void) { // Module initialization
  44. (void) Py_InitModule("permanent", methods);
  45. import_array();
  46. }
  47. // Ryser's algorithm
  48. static npy_complex128 perm_ryser(PyArrayObject *submatrix) {
  49. int n = (int) PyArray_DIM(submatrix, 0);
  50. npy_complex128 rowsum, rowsumprod;
  51. npy_complex128 perm = complex_zero;
  52. int exp = 1 << n;
  53. int i, y, z;
  54. // Iterate over exponentially many index strings
  55. for (i=0; i<exp; ++i) {
  56. rowsumprod = complex_one;
  57. for (y=0; y<n; ++y) { // Rows
  58. rowsum = complex_zero;
  59. for (z=0; z<n; ++z) { // Columns
  60. if ((i & (1 << z)) != 0) { rowsum = complex_add(rowsum, SM(z,y)); }
  61. }
  62. rowsumprod = complex_prod(rowsumprod, rowsum);
  63. }
  64. int sign = bitparity(i);
  65. perm.real+=sign*rowsumprod.real; perm.imag+=sign*rowsumprod.imag;
  66. }
  67. if (n%2 == 1) {perm.real*=-1; perm.imag*=-1;}
  68. return perm;
  69. }
  70. // This is a wrapper which chooses the optimal permanent function
  71. static PyObject *permanent(PyObject *self, PyObject *args) {
  72. // Parse the input
  73. PyArrayObject *submatrix;
  74. if (!PyArg_ParseTuple(args, "O!", &PyArray_Type, &submatrix)) {return NULL;}
  75. // Compute the permanent
  76. npy_complex128 p = perm_ryser(submatrix);
  77. return PyComplex_FromDoubles(p.real, p.imag);
  78. }