Broadcasting with in-place changes

Hi all,

I am learning broadcasting in julia. I would like to adapt one vector in-place by multiplying it with the contents of another vector, without creating a copy. I tried this:

function mymultiply!(a,b)
    a*=b
    return ;
end

a=[1,2,3,4]
b=[10,100,1000,10000]
mymultiply!.(a,b)
a

But this does not adapt a. Is this not passing through a pointer and is a new allocation made? How do I avoid this and keep it in-place?

a .*= b

I get the following error when trying that:

ERROR: MethodError: no method matching copyto!(::Int64, ::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{0}, Tuple{}, typeof(*), Tuple{Int64, Int64}})
Closest candidates are:
  copyto!(::AbstractArray, ::Base.Broadcast.Broadcasted{<:Base.Broadcast.AbstractArrayStyle{0}}) at broadcast.jl:916
  copyto!(::AbstractArray, ::Base.Broadcast.Broadcasted) at broadcast.jl:913
  copyto!(::AbstractArray, ::Any) at abstractarray.jl:898
Stacktrace:
 [1] materialize!
   @ .\broadcast.jl:871 [inlined]
 [2] materialize!
   @ .\broadcast.jl:868 [inlined]
 [3] mymultiply!(a::Int64, b::Int64)
   @ Main project.jl:88
 [4] _broadcast_getindex_evalf
   @ .\broadcast.jl:670 [inlined]
 [5] _broadcast_getindex
   @ .\broadcast.jl:643 [inlined]
 [6] getindex
   @ .\broadcast.jl:597 [inlined]
 [7] copy
   @ .\broadcast.jl:899 [inlined]
 [8] materialize(bc::Base.Broadcast.Broadcasted{Base.Broadcast.DefaultArrayStyle{1}, Nothing, typeof(mymultiply!), Tuple{Vector{Int64}, Vector{Int64}}})
   @ Base.Broadcast .\broadcast.jl:860
 [9] top-level scope
   @ project.jl:94

Not sure what you did, but:

julia> a=[1,2,3,4];

julia> b=[10,100,1000,10000];

julia> a .*= b;

julia> a
4-element Vector{Int64}:
    10
   200
  3000
 40000

Oh yes thanks that works indeed! I tried adapting my function like this:

function mymultiply!(a,b)
    a.*=b
    return ;
end

Which resulted in the error.

The suggestion a.* =b does work for something simple like multiplication, but if I want to do something more complicated for which I’d like to write my own function, could I adapt mymultiply to obtain the same behavior as a.* =b?

That works fine too:

julia> a=[1,2,3,4]; b=[10,100,1000,10000];

julia> function mymultiply!(a,b)
           a.*=b
           return ;
       end
mymultiply! (generic function with 1 method)

julia> mymultiply!(a,b)

julia> a
4-element Vector{Int64}:
    10
   200
  3000
 40000

I think what’s confusing you may be that you are also trying to call mymultiply!.(a,b) with a dot. To use broadcasting, you generally want to do one but not both of:

  1. Write a function f(x,y) that takes arrays as arguments. Inside f(x,y), use as many broadcast operations as you want. You can even modify x or y in-place if you want (in which case it is conventional to name f! with a !).
  2. Write a function f(x,y) that takes scalarsas arguments. Call it on arrays withf.(a,b), and assign the results in-place with (for example) a .= f.(a,b)`.

Do one or the other! For example, the 2nd strategy in this case would be:

function mymultiply(a,b)
    return a * b
end
a .= mymultiply.(a,b) # apply to arrays a,b, assigning result in-place to a

(You can’t do a .*= b in a function that takes scalars as arguments, because scalars are immutable. And a += b is equivalent to a = a + b which does not mutate the argument a.)

1 Like

That’s clear, thanks so much! In the second case, does this mean I am temporarily allocating memory to store a*b before I assign it to a?

1 Like

No. a .= mymultiply.(a,b) is exactly equivalent to broadcast!(mymultiply, a, a,b), which is conceptually equivalent to:

for i in 1:length(a)
    a[i] = mymultiply(a[i], b[i])
end

which doesn’t allocate any arrays.

1 Like

Perfect, that resolves it all, thank you!