► |  | Math Library

1 | Constants and Elementary Functions
2 | Flonums
3 | Special Functions
4 | Number Theory
5 | Arbitrary-Precision Floating-Point Numbers (Bigfloats)
6 | Arrays
7 | Matrices and Linear Algebra
8 | Statistics Functions
9 | Probability Distributions
10 | Stuff That Doesn’t Belong Anywhere Else

▼ | 6 | Arrays

6.1 | Quick Start
6.2 | Definitions
6.3 | Broadcasting
6.4 | Slicing
6.5 | Nonstrict Arrays
6.6 | Types, Predicates and Accessors
6.7 | Construction
6.8 | Conversion
6.9 | Comprehensions and Sequences
6.10 | Pointwise Operations
6.11 | Indexing and Slicing
6.12 | Transformations
6.13 | Folds, Reductions and Expansions
6.14 | Other Array Operations
6.15 | Subtypes
6.16 | Strictness

► | 6.3 | Broadcasting

6.3.1 | Broadcasting Rules
6.3.2 | Broadcasting Control

On this page:

6.3.1`` Broadcasting Rules
6.3.2`` Broadcasting Control

top ← prev up next →

#### 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 Zero One))
> (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.
top ← prev up next →