Using einsum makes a lot of sense in your case; but you can do it quite easily by hand. The trick is to make the arrays broadcast against each other. This means that they modify them so that each array changes independently along its own axis. Then multiply them together, letting numpy take care of the broadcast; and then summed along the last (rightmost) axis.
>>> x = numpy.arange(2 * 4).reshape(2, 4) >>> y = numpy.arange(3 * 4).reshape(3, 4) >>> z = numpy.arange(4 * 4).reshape(4, 4) >>> (x.reshape(2, 1, 1, 4) * ... y.reshape(1, 3, 1, 4) * ... z.reshape(1, 1, 4, 4)).sum(axis=3) array([[[ 36, 92, 148, 204], [ 92, 244, 396, 548], [ 148, 396, 644, 892]], [[ 92, 244, 396, 548], [ 244, 748, 1252, 1756], [ 396, 1252, 2108, 2964]]])
You can make this more generalized by using slice notation, the value of newaxis (which is None , so it will work with None below), and the fact that sum takes the values ββof the negative axis (with -1 , denoting the last, -2 , denoting the next and the last), etc.). Thus, you do not need to know the original shape of the arrays; as long as their last axes are compatible, this will broadcast the first three together:
>>> (x[:, numpy.newaxis, numpy.newaxis, :] * ... y[numpy.newaxis, :, numpy.newaxis, :] * ... z[numpy.newaxis, numpy.newaxis, :, :]).sum(axis=-1) array([[[ 36, 92, 148, 204], [ 92, 244, 396, 548], [ 148, 396, 644, 892]], [[ 92, 244, 396, 548], [ 244, 748, 1252, 1756], [ 396, 1252, 2108, 2964]]])