The code proposed by the OP can indeed made be more efficient, mainly by noting the fact that to form the sequence , with you do not have to compute at each step, but you can exploit the fact that , reusing the result of the previous step.

My proposed implementation is

import numpy as np

N = 3
M = 1

A = np.random.random((N, N))
B = np.random.random((N, M))

X = np.zeros((N ** 2, N * M))
rsl = slice(0, N)
X[rsl, :M] = B
for i in range(1, N):
    rsl_p, rsl = rsl, slice(i * N, (i + 1) * N)
    X[rsl, :M] = A @ X[rsl_p, :M]
    X[rsl, M : (i + 1) * M] = X[rsl_p, : i * M]

This question is more related to programming that computational science, so I add some general remarks, useful for a novice programmer.

  1. never blindly translate mathematical expressions into code: think algorithmically.

    X = np.linalg.matrix_power(A,i)@B 
    

    is much slower than

    X=B; 
    for j in range(i): X = A*X
    

    if

    (exercise: compute operation count for both code fragments).

  2. whenever possible reuse previous calculations.

  3. pre-allocate output matrices, and do not store intermediate results in temp. matrices

Answer from Stefano M on Stack Exchange
🌐
NumPy
numpy.org › doc › stable › reference › generated › numpy.block.html
numpy.block — NumPy v2.5 Manual
The most common use of this function is to build a block matrix: >>> import numpy as np >>> A = np.eye(2) * 2 >>> B = np.eye(3) * 3 >>> np.block([ ... [A, np.zeros((2, 3))], ... [np.ones((3, 2)), B ] ...
🌐
w3resource
w3resource.com › numpy › manipulation › block.php
NumPy: numpy.block() function - w3resource
April 24, 2026 - Numpy Array manipulation: numpy.block() is stack arrays in sequence vertically (row wise).
Discussions

Is there an efficient way to form this block matrix with numpy or scipy? - Computational Science Stack Exchange
What I am hoping to achieve is an efficient way to generate such a matrix that scales with $N$, and I am able to do it with np.block() and list but it just seems to be not efficient for me. More on scicomp.stackexchange.com
🌐 scicomp.stackexchange.com
August 13, 2019
Forming a particular (averaged) block matrix with numpy - Computational Science Stack Exchange
Say I have a set of $n \times n$ matrices $A_1, ..., A_m$ as numpy arrays. I'd like to create the block matrix defined below. I'm looking for a clean, elegant, and easy-to-interpret way of doing this in numpy. I tried this with np.block: More on scicomp.stackexchange.com
🌐 scicomp.stackexchange.com
python - Better way to create block matrices out of individual blocks in numpy? - Stack Overflow
CopyM=5;N=3; A11=np.random.rand(M,M); A12=np.random.rand(M,N); A21=np.random.rand(N,M); A22=np.random.rand(N,N); I am new to numpy and learning it. I want to create a block matrix in the following manner More on stackoverflow.com
🌐 stackoverflow.com
python - How do numpy block matrices work? - Stack Overflow
@NeilG: I see an np.block in the dev repository, but not in any current release. More on stackoverflow.com
🌐 stackoverflow.com
🌐
NumPy
numpy.org › devdocs › reference › generated › numpy.block.html
numpy.block — NumPy v2.6.dev0 Manual
The most common use of this function is to build a block matrix: >>> import numpy as np >>> A = np.eye(2) * 2 >>> B = np.eye(3) * 3 >>> np.block([ ... [A, np.zeros((2, 3))], ... [np.ones((3, 2)), B ] ...
🌐
NumPy
numpy.org › doc › 2.3 › reference › generated › numpy.block.html
numpy.block — NumPy v2.3 Manual
The most common use of this function is to build a block matrix: >>> import numpy as np >>> A = np.eye(2) * 2 >>> B = np.eye(3) * 3 >>> np.block([ ... [A, np.zeros((2, 3))], ... [np.ones((3, 2)), B ] ...
🌐
NumPy
numpy.org › doc › 2.1 › reference › generated › numpy.block.html
numpy.block — NumPy v2.1 Manual
Blocks can be of any dimension, but will not be broadcasted using the normal rules. Instead, leading axes of size 1 are inserted, to make block.ndim the same for all blocks. This is primarily useful for working with scalars, and means that code like np.block([v, 1]) is valid, where v.ndim == 1.
Find elsewhere
🌐
GitHub
github.com › bamos › block
GitHub - bamos/block: An intelligent block matrix library for numpy, PyTorch, and beyond. · GitHub
Block acts a lot like np.bmat and replaces: Any constant with an appropriately shaped block matrix filled with that constant. The string 'I' with an appropriately shaped identity matrix. The string '-I' with an appropriately shaped negated identity matrix. [Request more features.] Yes, block is meant to be a quick prototyping tool and there's probably a more efficient way to solve your system if it has a lot of zeros or identity elements.
Author: bamos
Top answer
1 of 2
8

The code proposed by the OP can indeed made be more efficient, mainly by noting the fact that to form the sequence , with you do not have to compute at each step, but you can exploit the fact that , reusing the result of the previous step.

My proposed implementation is

import numpy as np

N = 3
M = 1

A = np.random.random((N, N))
B = np.random.random((N, M))

X = np.zeros((N ** 2, N * M))
rsl = slice(0, N)
X[rsl, :M] = B
for i in range(1, N):
    rsl_p, rsl = rsl, slice(i * N, (i + 1) * N)
    X[rsl, :M] = A @ X[rsl_p, :M]
    X[rsl, M : (i + 1) * M] = X[rsl_p, : i * M]

This question is more related to programming that computational science, so I add some general remarks, useful for a novice programmer.

  1. never blindly translate mathematical expressions into code: think algorithmically.

    X = np.linalg.matrix_power(A,i)@B 
    

    is much slower than

    X=B; 
    for j in range(i): X = A*X
    

    if

    (exercise: compute operation count for both code fragments).

  2. whenever possible reuse previous calculations.

  3. pre-allocate output matrices, and do not store intermediate results in temp. matrices

2 of 2
2

If you need to explicitly construct the entire matrix, then Stefano M's answer is your best bet. If, however, you don't really need the whole matrix, but just need to be able to perform a matrix-vector-product (MVP), then you might want to consider the following approach. Based on your comments it's unclear to me if this helps you, and I realize this doesn't technically answer your question, but I thought I'd post it just in case.

Suppose you want to calculate the MVP:

where 's are size and known and we want to calculate 's which are size and unknown. This can be expressed block-by-block as

for . (The last step works if we assume .)

This should be much faster than using the full dense matrix.

Example Python code:

import numpy as np

def mvp(A,B,x):

    n=A.shape[0]
    m=B.shape[1]

    # assume that x is 1D array of size [n*m]

    y=np.zeros( (n*n) )

    y[:n] = B @ x[:m]
    for i in range(1,n):
        y[i*n:(i+1)*n] = B @ x[i*m:(i+1)*m] + A @ y[(i-1)*n:i*n]

    return y

N = 3
M = 1

A = np.random.random((N, N))
B = np.random.random((N, M))
x=np.random.random( (N*M) )

y=mvp(A,B,x)

🌐
TutorialsPoint
tutorialspoint.com › build-a-block-matrix-in-numpy
Build a block matrix in Numpy
February 17, 2022 - To build a block of matrix, use the numpy.block() method in Python Numpy. Blocks in the innermost lists are concatenated along the last dimension (-1), then these are concatenated along the secondlast dimension (-2), and so on until the outermost
🌐
w3resource
w3resource.com.cach3.com › numpy › manipulation › block.php.html
NumPy: block() function - w3resource
Numpy Array manipulation: block is stack arrays in sequence vertically (row wise).
🌐
SciPy
docs.scipy.org › doc › numpy-1.13.0 › reference › generated › numpy.block.html
numpy.block — NumPy v1.13 Manual
... Stack arrays in sequence ... array into a list of multiple sub-arrays vertically. ... When called with only scalars, np.block is equivalent to an ndarray call....
🌐
SciPy
docs.scipy.org › doc › scipy › reference › generated › scipy.linalg.block_diag.html
block_diag — SciPy v1.18.0 Manual
>>> import numpy as np >>> from scipy.linalg import block_diag >>> A = [[1, 0], ... [0, 1]] >>> B = [[3, 4, 5], ...
🌐
SciPy
docs.scipy.org › doc › numpy-1.14.0 › reference › generated › numpy.block.html
numpy.block — NumPy v1.14 Manual
... Stack arrays in sequence ... array into a list of multiple sub-arrays vertically. ... When called with only scalars, np.block is equivalent to an ndarray call....
🌐
Stack Overflow
stackoverflow.com › questions › 69558957 › numpy-create-block-matrices-from-object-type-numpy-arrays
python - Numpy create block matrices from object type numpy arrays - Stack Overflow
For 1d object dtype arrays, concatenate ... 1d object array as a sequence, a list. np.block can handle nested lists, basically by recursively combining the sublists and outer list....
🌐
Reddit
reddit.com › r/learnpython › efficiently divide numpy array into blocks sized y x y and apply function to each block
r/learnpython on Reddit: Efficiently divide numpy array into blocks sized Y x Y and apply function to each block
May 23, 2021 -

Hey,

I have the following problem: I have a 2D numpy array (256 x 256 shape) and I want to divide it into equally sized blocks (for example 8 x 8 elements blocks) and apply a custom function to each block (which should reflect changes made in this function onto the original array).

What I first tried was simply (I know basically one should never do this) looping with a double for loop over the array (with step size being the blocks shape) and applying the function on each block. This worked, but it was really, really slow and barely worked for smaller block sizes like 2 x 2.

Next, I used the following (which is basically the view_as_blocks function from skimage.util.shape.

from numpy.lib.stride_tricks import as_strided

def chunk(arr_in: np.ndarray, block_shape:tuple) -> np.ndarray:     
    block_shape = np.array(block_shape)
    arr_shape = np.array(arr_in.shape)
    new_shape = tuple(arr_shape // block_shape) + tuple(block_shape)
    new_strides = tuple(arr_in.strides * block_shape) + arr_in.strides
    arr_out = as_strided(arr_in, shape=new_shape, strides=new_strides)
    return arr_out

And then looped through the resulting blocks one by one and assembling it into a new array like this:

def my_main_fun() -> None:
    # the blocks   
    s = chunk(img, (chunk_size, chunk_size))
    # the new array
    t = np.zeros_like(img)
    for i in range(s.shape[0]):
        for j in range(s.shape[1]):
            o_i = i * chunk_size
            o_j = j * chunk_size 
            _, vals = myCustomFunction(s[i][j])
            t[o_i:o_i + chunk_size, o_j:o_j + chunk_size] = vals

This was faster, but it also takes quite a lot time (I assume due to the double for loop in the reassembling (second) function).

Now my question: How can I do this much more efficiently?

Note that its guaranteed that the block shape (which is always a square) is evenly divisible by the arrays shape and I only care about 2D arrays for now.

Many thanks in advance ;)