How to broadcast over only certain function arguments?

I have a function that takes fixed input arrays and variable scalars to produce a scalar return value. I would like to use the broadcast capability of Julia to turn the variable scalars into arrays, and have it return an array of the same size. Is there a straightforward way to get the broadcast syntax f.() to interpret the input arguments correctly (not attempting to broadcast over the fixed arrays)? Or is there some other clever way using map or broadcast?

For now, I will initialize the return array and just use a for loop.

2 Likes

You can turn one of the arguments into a tuple to consider it as a scalar:

For example (if I understood your question correctly):

f(x,y) = sum(x) + y
f([1,2,3], 4) #-> 10
f([1,2,3], 5) #-> 11

f.([1,2,3], [4,5])    # -> error

f.(([1,2,3],), [4,5]) # -> [10, 11]
                      # this works because ([1,2,3],) is now considered
                      # to be scalar and is broadcasted

Note how in the second case, the array argument [1,2,3] has been turned into a 1-element tuple containing the actual array argument: ([1,2,3],)

It would also have been possible to use Ref([1,2,3]) to achieve the same effect:

f.(Ref([1,2,3]), [4,5]) #-> [10, 11]
9 Likes

Also see this (ongoing) discussion:

1 Like

Wrapping an argument in Ref(x) also works and is a common idiom for this.

13 Likes

If you want to override the broadcasting behaviour for a particular function, it’s possible by adding a new method to the broadcasted function. However, I’m not sure it’s a particularly advisable thing to do since your function will behave differently to most other functions.

julia> _f(x,y) = sum(x) + y
_f (generic function with 1 method)

julia> f(x, y) = _f(x,y)
f (generic function with 1 method)

julia> Broadcast.broadcasted(::typeof(f), x, y) = broadcast(_f, Ref(x), y)

julia> f.([1,2,3], [4,5])
2-element Array{Int64,1}:
 10
 11
2 Likes

The tuple-wrapping trick seems to work with Julia v0.7. I didn’t have success with the Ref() trick - will need to experiment further.

Thanks!

It is not a “trick”, but part of the semantics of broadcasting. If it does not work, that is a bug, so please provide a self-contained MWE that demonstrates this.

This doesn’t seem to work, as of Julia 1.7. Has it been replaced/deprecated?

It should work. Can you provide a minimal example?

f(x,y)=2*x+3*y

then

julia> f.(([1,2]),[3,4,5])
ERROR: DimensionMismatch("arrays could not be broadcast to a common size; got a dimension with lengths 2 and 3")
Stacktrace:
 [1] _bcs1
   @ ./broadcast.jl:516 [inlined]
 [2] _bcs
   @ ./broadcast.jl:510 [inlined]
 [3] broadcast_shape
   @ ./broadcast.jl:504 [inlined]
 [4] combine_axes
   @ ./broadcast.jl:499 [inlined]
 [5] instantiate
   @ ./broadcast.jl:281 [inlined]
 [6] materialize(bc::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1}, Nothing, typeof(f), Tuple{Vector{Int64}, Vector{Int64}}})
   @ Base.Broadcast ./broadcast.jl:860
 [7] top-level scope
   @ REPL[32]:1
 [8] top-level scope
   @ ~/.julia/packages/CUDA/0IDh2/src/initialization.jl:52

Hi Sorry - I tried Ref() again and it worked
[sorry - I thought I had tried it before, but something must have been different]

Thanks

Ah, OK.

It’s Ref(1,2) that doesn’t work here, which had confused me.

@compleat - You missed a trailing comma in your first argument above:

julia> ([1,2])
2-element Vector{Int64}:
 1
 2

julia> ([1,2],)
([1, 2],)

julia> typeof(ans)
Tuple{Vector{Int64}}

Surrounding an item with parentheses doesn’t make it a tuple.

2 Likes

Hi, thanks so much. [How did I miss that? I never knew]