Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Fastest way to expand the values of a numpy matrix in diagonal blocks

I'm searching for a fast way for resize the matrix in a special way, without using for-loops: I have a squared Matrix:

matrix = [[ 1, 2, 3, 4, 5],
          [ 6, 7, 8, 9,10],
          [11,12,13,14,15],
          [16,17,18,19,20],
          [21,22,23,24,25]]

and my purpose is to resize it 3 (or n) times, where the values are diagonal blocks in the matrix and other values are zeros:

goal_matrix = [[ 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0, 0],
               [ 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5, 0],
               [ 0, 0, 1, 0, 0, 2, 0, 0, 3, 0, 0, 4, 0, 0, 5],
               [ 6, 0, 0, 7, 0, 0, 8, 0, 0, 9, 0, 0,10, 0, 0],
               [ 0, 6, 0, 0, 7, 0, 0, 8, 0, 0, 9, 0, 0,10, 0],
               [ 0, 0, 6, 0, 0, 7, 0, 0, 8, 0, 0, 9, 0, 0,10],
               [11, 0, 0,12, 0, 0,13, 0, 0,14, 0, 0,15, 0, 0],
               [ 0,11, 0, 0,12, 0, 0,13, 0, 0,14, 0, 0,15, 0],
               [ 0, 0,11, 0, 0,12, 0, 0,13, 0, 0,14, 0, 0,15],
               [16, 0, 0,17, 0, 0,18, 0, 0,19, 0, 0,20, 0, 0],
               [ 0,16, 0, 0,17, 0, 0,18, 0, 0,19, 0, 0,20, 0],
               [ 0, 0,16, 0, 0,17, 0, 0,18, 0, 0,19, 0, 0,20],
               [21, 0, 0,22, 0, 0,23, 0, 0,24, 0, 0,25, 0, 0],
               [ 0,21, 0, 0,22, 0, 0,23, 0, 0,24, 0, 0,25, 0],
               [ 0, 0,21, 0, 0,22, 0, 0,23, 0, 0,24, 0, 0,25]]

It should do something like this question, but without unnecessary zero padding.
Is there any mapping, padding or resizing function for doing this in a fast way?

like image 327
Olivér Palotás Avatar asked Oct 18 '25 21:10

Olivér Palotás


1 Answers

IMO, it is inappropriate to reject the for loop blindly. Here I provide a solution without the for loop. When n is small, its performance is better than that of @MichaelSzczesny and @SalvatoreDanieleBianco solutions:

def mechanic(mat, n):
    ar = np.zeros((*mat.shape, n * n), mat.dtype)
    ar[..., ::n + 1] = mat[..., None]
    return ar.reshape(
        *mat.shape,
        n,
        n
    ).transpose(0, 3, 1, 2).reshape([s * n for s in mat.shape])

This solution obtains the expected output through a slice assignment, then transpose and reshape, but copies will occur in the last step of reshaping, making it inefficient when n is large.

After a simple test, I found that the solution that simply uses the for loop has the best performance:

def mechanic_for_loop(mat, n):
    ar = np.zeros([s * n for s in mat.shape], mat.dtype)
    for i in range(n):
        ar[i::n, i::n] = mat
    return ar

Next is a benchmark test using perfplot. The test functions are as follows:

import numpy as np


def mechanic(mat, n):
    ar = np.zeros((*mat.shape, n * n), mat.dtype)
    ar[..., ::n + 1] = mat[..., None]
    return ar.reshape(
        *mat.shape,
        n,
        n
    ).transpose(0, 3, 1, 2).reshape([s * n for s in mat.shape])


def mechanic_for_loop(mat, n):
    ar = np.zeros([s * n for s in mat.shape], mat.dtype)
    for i in range(n):
        ar[i::n, i::n] = mat
    return ar


def michael_szczesny(mat, n):
    return np.einsum(
        'ij,kl->ikjl',
        mat,
        np.eye(n, dtype=mat.dtype)
    ).reshape([s * n for s in mat.shape])


def salvatore_daniele_bianco(mat, n):
    repeated_matrix = mat.repeat(n, axis=0).repeat(n, axis=1)
    col_ids, row_ids = np.meshgrid(
        np.arange(repeated_matrix.shape[0]),
        np.arange(repeated_matrix.shape[1])
    )
    repeated_matrix[(col_ids % n) - (row_ids % n) != 0] = 0
    return repeated_matrix


functions = [
    mechanic,
    mechanic_for_loop,
    michael_szczesny,
    salvatore_daniele_bianco
]

Resize times unchanged, array size changes:

if __name__ == '__main__':
    from itertools import accumulate, repeat
    from operator import mul
    from perfplot import bench

    bench(
        functions,
        list(accumulate(repeat(2, 11), mul)),
        lambda n: (np.arange(n * n).reshape(n, n), 5),
        xlabel='ar.shape[0]'
    ).show()

Output: enter link description here

Resize times changes, array size unchanged:

if __name__ == '__main__':
    from itertools import accumulate, repeat
    from operator import mul
    from perfplot import bench

    ar = np.arange(25).reshape(5, 5)
    bench(
        functions,
        list(accumulate(repeat(2, 11), mul)),
        lambda n: (ar, n),
        xlabel='resize times'
    ).show()

Output: enter link description here



like image 78
Mechanic Pig Avatar answered Oct 20 '25 10:10

Mechanic Pig



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!