f(a, b, c, d)

Let’s say we want to explore the behavior of f in some range of parameters.

How can we vectorize the function and iterate over all possible combinations? Is building a Cartesian product the best way? We expect this to be very inefficient, like $O(n^2)$ with respect to $O(n)$ inefficient in memory, at least. Let’s say the parameters have a definite number of samples (len(x) or np.shape(x) in pyhon, with x a parameter vector). We change the axis of the numpy vectors, making them multi-dimensional vectors (or, respecting the notion of dimension in algebra, multi-axial):

a_vec = a[:, None, None, None]

Therefore the samples on each axis will be

(10, 1, 1, 1)
(1,  2, 1, 1)
(1,  1, 4, 1)
(1,  1, 1, 8)

By calling the function foo(a_vec, b_vec, c_vec, d_vec), we get a

res = foo(a_vec, b_vec, c_vec, d_vec)

and

np.shape(res)

will give (10, 2, 4, 8). The vectorization is crucially dependent on the number of items in each axis of the multi-axis vectors. For example, we expect that only two values are allowed in a column: 1 or a fixed N

(..., 1, ...)
(..., 1, ...)
(..., N, ...)
(..., 1, ...)
(..., N, ...)

The vectorization routine is therefore allowed to broadcast.

Example script

import numpy as np
def f(x, y, z): return x+y+z
print("\nCheck the normal operation")
a = np.random.randint(0, 100, (20, 10, 1))
b = np.random.randint(0, 100, (32))
c = np.random.randint(0, 100, (20, 1, 32))
print("a   : ", np.shape(a))
print("b   : ", np.shape(b[None, None, :]))
print("c   : ", np.shape(c))
res = f(a, b[None, None, :], c)
print("res : ", np.shape(res))

print("\nNow we generate a vectorization (broadcasting) error")
b = np.random.randint(0, 100, (31))
res = f(a, b[None, None, :], c)