4

I have the following .pyx code:

import cython
@cython.boundscheck(False)
@cython.cdivision(True)
@cython.wraparound(False)
def f(m):
  cdef int n = len(m)/2
  cdef int j, k
  z = [[0]*(n+1) for _ in range(n*(2*n-1))]
  for j in range(1, 2*n):
    for k in range(j):
      z[j*(j-1)/2+k][0] = m[j][k]
  return solve(z, 2*n, 1, [1] + [0]*n, n)


cdef solve(b, int s, int w, g, int n):
  cdef complex h
  cdef int u,v,j,k
  if s == 0:
    return w*g[n]
  c = [b[(j+1)*(j+2)/2+k+2][:] for j in range(1, s-2) for k in range(j)]
  h = solve(c, s-2, -w, g, n)
  e = g[:]
  for u in range(n):
    for v in range(n-u):
      e[u+v+1] += g[u]*b[0][v]
  for j in range(1, s-2):
    for k in range(j):
      for u in range(n):
        for v in range(n-u):
          c[j*(j-1)/2+k][u+v+1] += b[(j+1)*(j+2)/2][u]*b[(k+1)*(k+2)/2+1][v] + b[(k+1)*(k+2)/2][u]*b[(j+1)*(j+2)/2+1][v]
  return h + solve(c, s-2, w, e, n)

I don't know how to declare the list of lists in cython to speed the code up.

For example, the variable m is a matrix represented as a list of lists of floating point numbers. The variable z is a list of lists of floating point numbers too. What should the line def f(m) look like for example?


Following the advice in the answer by @DavidW here is my latest version.

import cython
import numpy as np
def f(complex[:,:] m):
  cdef int n = len(m)/2
  cdef int j, k
  cdef complex[:,:] z = np.zeros((n*(2*n-1), n+1), dtype = complex)
  for j in range(1, 2*n):
    for k in range(j):
      z[j*(j-1)/2+k, 0] = m[j, k]
  return solve(z, 2*n, 1, [1] + [0]*n, n)


cdef solve(complex[:,:] b, int s, int w, g, int n):
  cdef complex h
  cdef int u,v,j,k
  cdef complex[:,:] c
  if s == 0:
    return w*g[n]
  c = [b[(j+1)*(j+2)/2+k+2][:] for j in range(1, s-2) for k in range(j)]
  print("c stats:", len(c), [len(c[i]) for i in len(c)]) 
  h = solve(c, s-2, -w, g, n)
  e = g[:]
  for u in range(n):
    for v in range(n-u):
      e[u+v+1] = e[u+v+1] + g[u]*b[0][v]
  for j in range(1, s-2):
    for k in range(j):
      for u in range(n):
        for v in range(n-u):
          c[j*(j-1)/2+k][u+v+1] = c[j*(j-1)/2+k][u+v+1] + b[(j+1)*(j+2)/2][u]*b[(k+1)*(k+2)/2+1][v] + b[(k+1)*(k+2)/2][u]*b[(j+1)*(j+2)/2+1][v]
  return h + solve(c, s-2, w, e, n)

The main problem now is how to declare c as it is currently a list of lists.

Simd
  • 19,447
  • 42
  • 136
  • 271

2 Answers2

7

A list-of-lists is not a structure that can get much speed-up from Cython. The structure you should use is a 2D typed memoryview:

def f(double[:,:] m):
    # ...

These are indexed as m[j,k] rather than m[j][k]. You can pass them any suitably shaped object that exposes the Python buffer protocol. Most frequency this will be a Numpy array.


You should also avoid using decorators like @cython.boundscheck(False) and @cython.wraparound(False) unless you understand what they do and have considered whether they're appropriate for your function. For your current version (where you're using lists) they actually do nothing and suggest you have copied them without understanding. They do speed up the indexing of memoryviews (at the cost of some safety).


Edit: In terms of initializing c you have two options.

  1. Initialize a numpy array with the list of lists. This probably isn't hugely quick (but if other steps are slower then it may not matter):

    c = np.array([b[(j+1)*(j+2)/2+k+2,:] for j in range(1, s-2) for k in range(j)], dtype=complex)
    # note that I've changed the indexing of b slightly
    
  2. Set up c with an appropriately sized np.zeros array. Swap the list comprehension for two loops. It isn't 100% obvious to me exactly what this translates to, but it's something like

    c = np.zeros("some size you'll have to work out",dtype=complex)
    for k in range(j):
        for j in range(1,s-2):
            c["some function of j and k",:] = b["some function of j and k",:] 
    

You'll also want to replace len(c) with c.shape[0], etc.

DavidW
  • 29,336
  • 6
  • 55
  • 86
  • Thanks. I will give it a go and report back. – Simd Apr 24 '18 at 11:19
  • I have made a little progress but am stuck on how to declare `c` now. – Simd Apr 24 '18 at 16:45
  • See edit. Option 1 is easy (but not optimized). Option 2 needs a little more thought than I can manage at the moment, but hopefully will give an idea – DavidW Apr 24 '18 at 17:07
  • Thanks. While I try to work that out, I wanted to define `g` which is a list of length n/2 + 1. I tried `cdef solve(complex[:,:] b, int s, int w, complex g[n/2+1], int n):` but it complains that `[1] + [0]*n` is not the right type (which is what g is set to when solve is first called). – Simd Apr 24 '18 at 20:39
  • You want `g[:]` to define it as a 1d array (this doesn't check the size though). You need to pass it a numpy array, not a list. Either make the numpy array from the list, or create one with `np.zeros` and set the first element to 1. – DavidW Apr 24 '18 at 21:10
  • Thank you. I tried `c = np.array([b[(j+1)*(j+2)/2+k+2,:] for j in range(1, s-2) for k in range(j)], dtype=complex)` but I get: `ValueError: Buffer has wrong number of dimensions (expected 2, got 1)` when I run the code. – Simd Apr 24 '18 at 21:26
  • I'm puzzled - this works fine for me (with a 5 by 6 input array). Try deleting the `dtype=complex` - it should be able to work it out anyway. Also try replacing `np.array` with `np.vstack`. But those are all guesses because it does work for me. – DavidW Apr 25 '18 at 06:36
  • This is what I get https://bpaste.net/show/5ec5b2cf5d11 (using python 2) – Simd Apr 25 '18 at 08:45
  • It looks like this is happening in some of the recursive calls to `solve` (I only tested a cut-down version with the top level call). I suspect it's actually trying to initialize an empty array, which may or may not be what you want. The simple solution would be to add `ndmin=2` to the call to `array` to force it to be at least 2D. I'd check that the output of this makes sense though (e.g. `print b.shape`) – DavidW Apr 25 '18 at 16:32
2

Like C:

cdef float z[100][100]

For more detail please refer to this link.

iRhonin
  • 383
  • 3
  • 14
  • 1
    Where does that go in the code? The hafnian function takes m as an argument so you can't put it within that function can you? – Simd Apr 22 '18 at 10:28
  • @eleanora you are right, I just wanted to give him an example. tnx. – iRhonin Apr 22 '18 at 10:34