Where possible, using numpy functions and operators that operate on the whole arrays, and do the necessary broadcasting for you:
In [24]: A = np.array([1.0,2.0]); t = np.linspace(0.0, 1.0, 10)
In [25]: x = t[:,None] * A[None,:]
In [26]: x.shape
Out[26]: (10, 2)
In [27]: x[:3,:]
Out[27]:
array([[0. , 0. ],
[0.11111111, 0.22222222],
[0.22222222, 0.44444444]])
In [28]: np.sin(t)
Out[28]:
array([0. , 0.11088263, 0.22039774, 0.3271947 , 0.42995636,
0.52741539, 0.6183698 , 0.70169788, 0.77637192, 0.84147098])
If you have a function that only works with scalar inputs, you can use np.vectorize to feed it those values - from broadcasted arrays:
In [30]: def foo(A,t):
...: return t*A
...:
In [31]: f = np.vectorize(foo, otypes=[float])
In [32]: f(A[None,:], t[:,None])
Out[32]:
array([[0. , 0. ],
[0.11111111, 0.22222222],
[0.22222222, 0.44444444],
....
[1. , 2. ]])
vectorize is a convenience function; it does not promise speed. Compare its time with In[25]:
In [33]: timeit f(A[None,:], t[:,None])
41.3 µs ± 48.5 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [34]: timeit x = t[:,None] * A[None,:]
5.41 µs ± 8.2 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)
8x slower.
Or with the scalar math.sin function:
In [35]: import math
In [36]: g = np.vectorize(math.sin)
...
In [39]: timeit g(t)
39 µs ± 72.4 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
In [40]: timeit np.sin(t)
1.4 µs ± 2.72 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
Called iteratively math.sin is faster than np.sin, but np.sin is much faster when given a whole array.
The basic difference is that vectorize, like explicit loops is iterating in interpreted Python, and calling your function once for each output element. np.sin and array * is iterating, but in compiled code.
There are various ways of iterating in Python and numpy, but rarely do they give you more than a 2x speedup.
There are tools for moving calculations to compiled code, such as cython and numba. Search on those tags to get ideas on how to use them.
np.vectorizetakes a 'scalar' function, and feeds it values from arrays. But it does not promise speed. It's just a convenience for when you can't write the function to take full advantage of compiled code. This topic comes up quite often, with people trying to avoid nested loops or trying to implement numpy vectorization in a magical, easy, way.