HiveBrain v1.2.0
Get Started
← Back to all entries
patternpythonMinor

In matrix of sub-matrices, select diagonal indices of off-diagonal submatrices

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
indicesdiagonalsubmatricessubmatricesselectmatrixoff

Problem

Let's say I have a square matrix of size (nm) x (nm) that is composed of n x n sub-matrices, with each submatrix being a square of size m x m.

I want to select the diagonal indices of the off-diagonal submatrices. This is the code that I have so far, but I would like to do this without the calls to itertools.permutations if possible.

n_submatrix = 3
submatrix_size = 2
my_matrix = np.zeros([n_submatrix * submatrix_size,
                      n_submatrix * submatrix_size])

for submatrix_index in itertools.permutations(range(n_submatrix),2):
    my_slice0 = slice(submatrix_index[0]*submatrix_size, (submatrix_index[0]+1)*submatrix_size)
    my_slice1 = slice(submatrix_index[1]*submatrix_size, (submatrix_index[1]+1)*submatrix_size)
    np.fill_diagonal(my_matrix[my_slice0,my_slice1],1)

my_matrix

#array([[ 0.,  0.,  1.,  0.,  1.,  0.],
#       [ 0.,  0.,  0.,  1.,  0.,  1.],
#       [ 1.,  0.,  0.,  0.,  1.,  0.],
#       [ 0.,  1.,  0.,  0.,  0.,  1.],
#       [ 1.,  0.,  1.,  0.,  0.,  0.],
#       [ 0.,  1.,  0.,  1.,  0.,  0.]])

Solution

I'm sure there are other ways, but in the meantime, how about this:

my_matrix = np.tile(np.eye(submatrix_size), (n_submatrix, n_submatrix))
np.fill_diagonal(my_matrix, 0)


Then again, the concatenation of matrixes might not be particularly
efficient, so instead I'd rather compute the target indexes directly,
fill them, and possibly clear out the diagonal again (all destructive on
a previously allocated matrix):

cells = n_submatrix * submatrix_size
my_matrix = np.zeros((cells, cells))

for x in range(n_submatrix):
    for y in range(submatrix_size):
        r = range(y, n_submatrix * submatrix_size, submatrix_size)
        my_matrix[x * submatrix_size + y, r] = 1

np.fill_diagonal(my_matrix, 0)


That is still easily explained: we step first by number of submatrixes
(because that's where the pattern repeats), then for each row we set all
indexes in that row at once, offset by the current step in the
submatrix.

Again, that seems less efficient than calculating indexes at once. I
ended up with the following:

cells = n_submatrix * submatrix_size
my_matrix = np.zeros((cells, cells))

for y in range(submatrix_size):
    r = range(y, n_submatrix * submatrix_size, submatrix_size)
    my_matrix[np.ix_(r, r)] = 1

np.fill_diagonal(my_matrix, 0)


To be fair, the documentation for numpy.ix_ and
indexing
isn't particularly intuitive, but basically I was looking for a way to
compress the notion of "for each of these rows, select all the indexes
in that row" and then setting them all at once. Now there is only one
loop for each row in the submatrix and I'm out of ideas.

There might be a clever formula to generate all of the indexes in one go
(by calculating them modulo some number) for all the diagonals though.

Btw. for your code I'd suggest extracting expressions as variables (like
cells above) and possibly a bit more succinct names, but that's just
me.

Code Snippets

my_matrix = np.tile(np.eye(submatrix_size), (n_submatrix, n_submatrix))
np.fill_diagonal(my_matrix, 0)
cells = n_submatrix * submatrix_size
my_matrix = np.zeros((cells, cells))

for x in range(n_submatrix):
    for y in range(submatrix_size):
        r = range(y, n_submatrix * submatrix_size, submatrix_size)
        my_matrix[x * submatrix_size + y, r] = 1

np.fill_diagonal(my_matrix, 0)
cells = n_submatrix * submatrix_size
my_matrix = np.zeros((cells, cells))

for y in range(submatrix_size):
    r = range(y, n_submatrix * submatrix_size, submatrix_size)
    my_matrix[np.ix_(r, r)] = 1

np.fill_diagonal(my_matrix, 0)

Context

StackExchange Code Review Q#121801, answer score: 2

Revisions (0)

No revisions yet.