Subtract Vector from Vector of Vectors

I’m having a bit of a moment. What is wrong with

julia> x = [[1, 2, 3], [4, 5, 6]]
2-element Vector{Vector{Int64}}:
 [1, 2, 3]
 [4, 5, 6]
julia> x .- [1, 2, 3]
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(-), Tuple{Vector{Vector{Int64}}, Vector{Int64}}})
   @ Base.Broadcast ./broadcast.jl:860
 [7] top-level scope
   @ REPL[18]:1

? I would like to see

[[0, 0, 0], [3, 3, 3]]

but I’m obviously doing something silly.

The issue is that you have a 2-vector of 3-vectors and you try to elementwise subtract a 3-vector. Broadcasting semantics mean you’re trying to compute the 2-vector (of 3-vectors) minus a 3-vector. Since 2 != 3, this doesn’t make sense and you get the error.

What you’re looking for is to subtract the 3-vector from each of the 3-vectors in the 2-vector. This means you want to broadcast the 2-vector but not the 3-vector. You can use Ref (or a 1-element array, a 1-element tuple, or a few other constructs) to prevent broadcasting of a collection. When used, it broadcast over the 1-element Ref (or array or tuple…) instead of the inner contents. Like this:

[[1, 2, 3], [4, 5, 6]] .- Ref([1, 2, 3])
4 Likes

This works for subtraction, but it doesn’t for division. Understandably, because “division by a vector” is not exactly clear, but what I want to do now is elementwise division of vectors, e.g.,

julia> [1., 2.] ./ [3., 4.]
2-element Vector{Float64}:
 0.3333333333333333
 0.5

If I change the solution above to division instead, I get

julia> [[1, 2, 3], [4, 5, 6]] ./ Ref([1, 2, 3])
2-element Vector{Matrix{Float64}}:
 [0.07142857142857142 0.14285714285714285 0.21428571428571427; 0.14285714285714285 0.2857142857142857 0.42857142857142855; 0.21428571428571427 0.42857142857142855 0.6428571428571428]
 [0.2857142857142857 0.5714285714285714 0.8571428571428571; 0.3571428571428571 0.7142857142857142 1.0714285714285714; 0.42857142857142855 0.8571428571428571 1.2857142857142856]

which is not what I intend or expect. I can do instead

julia> x = [[1, 2, 3], [4, 5, 6]]
2-element Vector{Vector{Int64}}:
 [1, 2, 3]
 [4, 5, 6]

julia> [xx ./ [1, 2, 3] for xx in x]
2-element Vector{Vector{Float64}}:
 [1.0, 1.0, 1.0]
 [4.0, 2.5, 2.0]

to circumvent the issue, but this feels a little clunky. Is there a cleaner way, nearer the spirit of the above solution for division?

maybe

vdiv(a, b) = a ./ b
vdiv.(x, Ref([1,2,3]))

or

map(r -> r ./ [1, 2, 3], x)

I think the problem is that /(::Vector, ::Vector) is already defined but it doesn’t do element-wise division like you’re looking for

1 Like

There isn’t a fancy syntax for nested broadcasting (EDIT: See the following post for fancy syntax), but you can construct it using the broadcast function over broadcasted division ./ like this:

julia> broadcast(./, [[1, 2, 3], [4, 5, 6]], Ref([1, 2, 3]))
2-element Vector{Vector{Float64}}:
 [1.0, 1.0, 1.0]
 [4.0, 2.5, 2.0]

The subtraction solution above [[1, 2, 3], [4, 5, 6]] .- Ref([1, 2, 3]) is equivalent to broadcast(-, [[1, 2, 3], [4, 5, 6]], Ref([1, 2, 3])). It is also in-practice equivalent to broadcast(.-, [[1, 2, 3], [4, 5, 6]], Ref([1, 2, 3])), but only because - and .- are equivalent on pairs of vectors (unlike / and ./).

2 Likes

A somewhat simpler-looking way is

julia> (./).([[1, 2, 3], [4, 5, 6]], Ref([1, 2, 3]))
2-element Vector{Vector{Float64}}:
 [1.0, 1.0, 1.0]
 [4.0, 2.5, 2.0]

But yeah, convenient interface for deeper broadcasting would be nice…

3 Likes

For unlimited-depth broadcasting, it’s often easiest to create a function that broadcasts over itself. Something like

dotplus(a, b) = dotplus.(a, b)
dotplus(a::Number, b::Number) = +(a, b) # base case

x = fill(fill([1 3; 2 4],2),3)
y = [50 70; 60 80]

dotplus(x,Ref(y))
# 3-element Vector{Matrix{Matrix{Int64}}}:
#  [[51 53; 52 54] [71 73; 72 74]; [61 63; 62 64] [81 83; 82 84]]
#  [[51 53; 52 54] [71 73; 72 74]; [61 63; 62 64] [81 83; 82 84]]
#  [[51 53; 52 54] [71 73; 72 74]; [61 63; 62 64] [81 83; 82 84]]

dotplus(x,Ref(Ref(y)))
# 3-element Vector{Vector{Matrix{Int64}}}:
#  [[51 73; 62 84], [51 73; 62 84]]
#  [[51 73; 62 84], [51 73; 62 84]]
#  [[51 73; 62 84], [51 73; 62 84]]

although you may need to change things depending on how exactly you want each term passed down through the broadcasting.

Broadcasting to significant depth is not terribly common.

1 Like