On this page:
6.3.1 Broadcasting Rules
6.3.2 Broadcasting Control

6.3 Broadcasting

It is often useful to apply a pointwise operation to two or more arrays in a many-to-one manner. Library support for this, which math/array provides, is called broadcasting.

Creating a 6×6 identity matrix:
> (define diag (diagonal-array 2 6 1 0))
> (array-shape diag)

- : Indexes

'#(6 6)

> diag

- : (Array (U One Zero))

(array

 #[#[1 0 0 0 0 0]

   #[0 1 0 0 0 0]

   #[0 0 1 0 0 0]

   #[0 0 0 1 0 0]

   #[0 0 0 0 1 0]

   #[0 0 0 0 0 1]])

Multiplying each element by 10:
> (array-shape (array 10))

- : Indexes

'#()

> (array* diag (array 10))

- : (Array Index)

(array

 #[#[10 0 0 0 0 0]

   #[0 10 0 0 0 0]

   #[0 0 10 0 0 0]

   #[0 0 0 10 0 0]

   #[0 0 0 0 10 0]

   #[0 0 0 0 0 10]])

Adding (array #[0 1 2 3 4 5]) pointwise to every row:
> (array+ (array* diag (array 10))
          (array #[0 1 2 3 4 5]))

- : (Array Nonnegative-Fixnum)

(array

 #[#[10 1 2 3 4 5]

   #[0 11 2 3 4 5]

   #[0 1 12 3 4 5]

   #[0 1 2 13 4 5]

   #[0 1 2 3 14 5]

   #[0 1 2 3 4 15]])

6.3.1 Broadcasting Rules

Suppose we have two array shapes ds = (vector d0 d1 ...) and es = (vector e0 e1 ...). Broadcasting proceeds as follows:
  1. The shorter shape is padded on the left with 1 until it is the same length as the longer shape.

  2. For each axis k, dk and ek are compared. If dk = ek, the result axis is dk; if one axis is length 1, the result axis is the length of the other; otherwise fail.

  3. Both arrays’ axes are stretched by (conceptually) copying the rows of axes with length 1.

Example: Suppose we have an array drr with shape ds = #(4 1 3) and another array err with shape es = #(3 3). Following the rules:
  1. es is padded to get #(1 3 3).

  2. The result axis is derived from #(4 1 3) and #(1 3 3) to get #(4 3 3).

  3. drr’s second axis is stretched to length 3, and err’s new first axis (which is length 1 by rule 1) is stretched to length 4.

The same example, but more concrete:
> (define drr
    (array #[#[#["00" "01" "02"]]
             #[#["10" "11" "12"]]
             #[#["20" "21" "22"]]
             #[#["30" "31" "32"]]]))
> (array-shape drr)

- : Indexes

'#(4 1 3)

> (define err
    (array #[#["aa" "ab" "ac"]
             #["ba" "bb" "bc"]
             #["ca" "cb" "cc"]]))
> (array-shape err)

- : Indexes

'#(3 3)

> (define drr+err (array-map string-append drr err))
> (array-shape drr+err)

- : Indexes

'#(4 3 3)

> drr+err

- : (Array String)

(array

 #[#[#["00aa" "01ab" "02ac"]

     #["00ba" "01bb" "02bc"]

     #["00ca" "01cb" "02cc"]]

   #[#["10aa" "11ab" "12ac"]

     #["10ba" "11bb" "12bc"]

     #["10ca" "11cb" "12cc"]]

   #[#["20aa" "21ab" "22ac"]

     #["20ba" "21bb" "22bc"]

     #["20ca" "21cb" "22cc"]]

   #[#["30aa" "31ab" "32ac"]

     #["30ba" "31bb" "32bc"]

     #["30ca" "31cb" "32cc"]]])

Notice how the row #["00" "01" "02"] in drr is repeated in the result because drr’s second axis was stretched during broadcasting. Also, the column #[#["aa"] #["ba"] #["ca"]] in err is repeated because err’s first axis was stretched.

For the above example, array-map does this before operating on drr and err:
> (define ds (array-shape-broadcast (list (array-shape drr)
                                          (array-shape err))))
> ds

- : Indexes

'#(4 3 3)

> (array-broadcast drr ds)

- : (Array String)

(array

 #[#[#["00" "01" "02"]

     #["00" "01" "02"]

     #["00" "01" "02"]]

   #[#["10" "11" "12"]

     #["10" "11" "12"]

     #["10" "11" "12"]]

   #[#["20" "21" "22"]

     #["20" "21" "22"]

     #["20" "21" "22"]]

   #[#["30" "31" "32"]

     #["30" "31" "32"]

     #["30" "31" "32"]]])

> (array-broadcast err ds)

- : (Array String)

(array

 #[#[#["aa" "ab" "ac"]

     #["ba" "bb" "bc"]

     #["ca" "cb" "cc"]]

   #[#["aa" "ab" "ac"]

     #["ba" "bb" "bc"]

     #["ca" "cb" "cc"]]

   #[#["aa" "ab" "ac"]

     #["ba" "bb" "bc"]

     #["ca" "cb" "cc"]]

   #[#["aa" "ab" "ac"]

     #["ba" "bb" "bc"]

     #["ca" "cb" "cc"]]])

6.3.2 Broadcasting Control

The parameter array-broadcasting controls how pointwise operations broadcast arrays. Its default value is #t, which means that broadcasting proceeds as described in Broadcasting Rules. Another possible value is #f, which allows pointwise operations to succeed only if array shapes match exactly:
> (parameterize ([array-broadcasting #f])
    (array* (index-array #(3 3)) (array 10)))

array-shape-broadcast: incompatible array shapes

(array-broadcasting #f): '#(3 3), '#()

Another option is R-style permissive broadcasting, which allows pointwise operations to always succeed, by repeating shorter axes’ rows instead of repeating just singleton axes’ rows:
> (define arr10 (array-map number->string (index-array #(10))))
> (define arr3 (array-map number->string (index-array #(3))))
> arr10

- : (Array String)

(array #["0" "1" "2" "3" "4" "5" "6" "7" "8" "9"])

> arr3

- : (Array String)

(array #["0" "1" "2"])

> (array-map string-append arr10 (array #["+" "-"]) arr3)

array-shape-broadcast: incompatible array shapes

(array-broadcasting #t): '#(10), '#(2), '#(3)

> (parameterize ([array-broadcasting 'permissive])
    (array-map string-append arr10 (array #["+" "-"]) arr3))

- : (Array String)

(array #["0+0" "1-1" "2+2" "3-0" "4+1" "5-2" "6+0" "7-1" "8+2" "9-0"])

Notice that (array #["+" "-"]) was repeated five times, and that arr3 was repeated three full times and once partially.