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.

92 lines
3.0KB

  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. // Complex numbers
  11. static const npy_complex128 complex_one = {.real=1, .imag=0};
  12. static const npy_complex128 complex_zero = {.real=0, .imag=0};
  13. static npy_complex128 complex_add(npy_complex128 a, npy_complex128 b) {
  14. npy_complex128 x;
  15. x.real = a.real+b.real;
  16. x.imag = a.imag+b.imag;
  17. return x;
  18. }
  19. static npy_complex128 complex_prod(npy_complex128 a, npy_complex128 b) {
  20. npy_complex128 x;
  21. x.real = a.real*b.real+a.imag*b.imag;
  22. x.imag = a.real*b.imag+a.imag*b.real;
  23. return x;
  24. }
  25. // Boilerplate
  26. static PyObject *permanent(PyObject *self, PyObject *args); // Forward function declaration
  27. static PyMethodDef methods[] = { // Method list
  28. { "permanent", permanent, METH_VARARGS, "Compute the permanent"},
  29. { NULL, NULL, 0, NULL } /* Sentinel */
  30. };
  31. PyMODINIT_FUNC initpermanent(void) { // Module initialization
  32. (void) Py_InitModule("permanent", methods);
  33. import_array();
  34. }
  35. inline int* dec2binarr(long n, int dim)
  36. {
  37. // note: res[dim] will save the sum res[0]+...+res[dim-1]
  38. int* res = (int*)calloc(dim + 1, sizeof(int));
  39. int pos = dim - 1;
  40. // note: this will crash if dim < log_2(n)...
  41. while (n > 0)
  42. {
  43. res[pos] = n % 2;
  44. res[dim] += res[pos];
  45. n = n / 2; // integer division
  46. pos--;
  47. }
  48. return res;
  49. }
  50. // Ryser's algorithm
  51. static npy_complex128 perm_ryser(PyArrayObject *submatrix) {
  52. int n = (int) PyArray_DIM(submatrix, 0);
  53. int i = 0; int z = 0; int y = 0;
  54. npy_complex128 sum, prod;
  55. npy_complex128 perm = complex_zero;
  56. int exp = 1 << n;
  57. // Iterate over exponentially many index strings
  58. for (i=0; i<exp; ++i) {
  59. prod = complex_one;
  60. for (y=0; y<n; ++y) { // Rows
  61. sum = complex_zero;
  62. for (z=0; z<n; ++z) { // Columns
  63. if ((i && (1 << z)) != 0) {
  64. sum = complex_add(sum, SM(z,y));
  65. }
  66. }
  67. prod = complex_prod(prod, sum);
  68. }
  69. if (i%2 == 1) {prod.real*=-1; prod.imag*=-1;}
  70. perm = complex_add(perm, prod);
  71. }
  72. if (i%2 == 1) {perm.real*=-1; perm.imag*=-1;}
  73. return perm;
  74. }
  75. // This is a wrapper which chooses the optimal permanent function
  76. static PyObject *permanent(PyObject *self, PyObject *args) {
  77. // Parse the input
  78. PyArrayObject *submatrix;
  79. if (!PyArg_ParseTuple(args, "O!", &PyArray_Type, &submatrix)) {return NULL;}
  80. // Compute the permanent
  81. npy_complex128 p = perm_ryser(submatrix);
  82. return PyComplex_FromDoubles(p.real, p.imag);
  83. }