What is the cleanest way to broadcast a multi-variate function over all input vectors

For example, I have a function f(x,y,z) and three vectors x, y, z. I’d like to compute all combinations and return a 3D array of values. What I can do is this: f.(x,y',reshape(z,1,1,:)). Is there a less cumbersome way, e.g., a macro, to do the same? Also the ' will conjugate if y were complex, which is not very clean.

1 Like

It’s not exactly what you asked for, but I think it’s more readable

using Base.Iterators

f(x, y, z) = x + y + z
f(x) = f(x...)

x, y, z = rand(4), rand(3), rand(2)
f.(product(x, y, z))
1 Like

I found the following solution through Tullio:

using Tullio
@tullio v[i,j,k]:=f(x[i],y[j],z[k])

Looks quite intuitive

3 Likes

Is the broadcasting dot needed here? I would not expect so.

1 Like

When you’re down to one multidimensional iterable, comprehension is neater than broadcasting and doesn’t require you to define the one-argument method to splat an input tuple: [f(i...) for i in product(x, y, z)].

Broadcasting really is just awkward for this case because the inputs’ shapes determine the output’s shape, so the straightforward usage is indeed reshaping the input vectors to extend along different axes. It’s fine to use other ways when they are neater; they’re all looping a method call over the same elements anyway.

2 Likes

One clean way to do this is with generator expressions

[f(x,y,z) for x in 1:3, y in 1:5, z in 1:7]

it has the added benefit of being lazy, so operations like sum don’t need to allocate the whole 3D array

sum(f(x,y,z) for x in 1:3, y in 1:5, z in 1:7)
3 Likes

What you wrote is a comprehension. The generator expressions have round brackets, right?

1 Like

Indeed, a stand alone generator expression has round brackets. I did a comprehension because OP wanted an array. It may not be the case in the internals, but I think of a comprehension as a collected generator.

1 Like