avoiding einsum because it depends on numpy 1.6+

also, it's potentially more opaque to read