As for more readable Python alternatives, indeed, the ‘np.tile’ isn’t strictly required, as Python will add a (1,n) matrix and a (n,1) matrix into an (n,n) matrix by default (I would be happier with an error message here, though).
There are many alternative ways to take advantage of this fact, for example what you have suggested
A = a[:, np.newaxis]
M = np.exp(1j*k*np.sqrt(A**2 + A.T**2))
Note that A is a (n,1) matrix here, which is not quite obvious at first sight. Equivalently, my new preferred alternative is resorting to two reshapes n = len(a)
M = np.exp(1j*k*sqrt(a.reshape(n,1)**2 + a.reshape(1,n)**2)
which is more explicit about the shapes of the arrays involved. I have updated the post to better reflect this discussion.In some more complex manual-vectorization cases I have encountered, the np.tile cannot be dropped, and the code looks a lot like my original posting.
Being able to resort to loops, and not even having to think about this manual-vectorization issues is a big plus, if you need to do this very often in your code.
Regards