Speed up random matrix computation

math, numpy, performance, python, scipy

Solution

One way is to save some work from repeated calling of toeplitz() function by caching the indexes where the values are being put. The following code is ~ 30% faster than the original code. The rest of the performance is in the rank calculation... And I don't know whether there exists a faster rank calculation for toeplitz matrices with 0s and 1s.

(update) The code is actually ~ 4 times faster if you replace matrix_rank by scipy.linalg.det() == 0 (determinant is faster then rank calculation for small matrices)

import random
from scipy.linalg import toeplitz, det
import numpy as np,numpy.random

class si:
    #cache of info for toeplitz matrix construction
    indx = None
    l = None

def xtoeplitz(c,r):
    vals = np.concatenate((r[-1:0:-1], c))
    if si.indx is None or si.l != len(c):
        a, b = np.ogrid[0:len(c), len(r) - 1:-1:-1]
        si.indx = a + b
        si.l = len(c)
    # `indx` is a 2D array of indices into the 1D array `vals`, arranged so
    # that `vals[indx]` is the Toeplitz matrix.
    return vals[si.indx]

def doit():
    for n in xrange(1,25):
        rankzero = 0
        si.indx=None

        for repeats in xrange(5000):

            column = np.random.randint(0,2,n)
            #column=[random.choice([0,1]) for x in xrange(n)] # original code

            row = np.r_[column[0], np.random.randint(0,2,n-1)]
            #row=[column[0]]+[random.choice([0,1]) for x in xrange(n-1)] #origi

            matrix = xtoeplitz(column, row)
            #matrix=toeplitz(column,row) # original code

            #if  (np.linalg.matrix_rank(matrix) < n): # original code
            if  np.abs(det(matrix))<1e-4: # should be faster for small matrices
                rankzero += 1
        print n, (rankzero*1.0)/50000

Problem

I am creating random Toeplitz matrices to estimate the probability that they are invertible. My current code is ``` import random from scipy.linalg import toeplitz import numpy as np for n in xrange(1,25): rankzero = 0 for repeats in xrange(50000): column = [random.choice([0,1]) for x in xrange(n)] row = [column[0]]+[random.choice([0,1]) for x in xrange(n-1)] matrix = toeplitz(column, row) if (np.linalg.matrix_rank(matrix) < n): rankzero += 1 print n, (rankzero*1.0)/50000 ``` Can this be sped up? I would like to increase the value 50000 to get more accuracy but it is too slow to do so currently. Profiling using only `for n in xrange(10,14)` shows ``` 400000 9.482 0.000 9.482 0.000 {numpy.linalg.lapack_lite.dgesdd} 4400000 7.591 0.000 11.089 0.000 random.py:272(choice) 200000 6.836 0.000 10.903 0.000 index_tricks.py:144(__getitem__) 1 5.473 5.473 62.668 62.668 toeplitz.py:3(<module>) 800065 4.333 0.000 4.333 0.000 {numpy.core.multiarray.array} 200000 3.513 0.000 19.949 0.000 special_matrices.py:128(toeplitz) 200000 3.484 0.000 20.250 0.000 linalg.py:1194(svd) 6401273/6401237 2.421 0.000 2.421 0.000 {len} 200000 2.252 0.000 26.047 0.000 linalg.py:1417(matrix_rank) 4400000 1.863 0.000 1.863 0.000 {method 'random' of '_random.Random' objects} 2201015 1.240 0.000 1.240 0.000 {isinstance} [...] ```

Original source