[多维非标量索引]需要实现的方法 | | 简短描述 |
---|
strides(A) | | 返回每个维度中相邻元素之间的内存距离(以内存元素数量的形式)组成的元组。如果 A 是 AbstractArray{T,0} ,这应该返回空元组。 | Base.unsafe_convert(::Type{Ptr{T}}, A) | | 返回数组的本地内存地址。 | 可选方法 | 默认定义 | 简短描述 | stride(A, i::Int) | strides(A)[i] | 返回维度 i(译注:原文为 k)上相邻元素之间的内存距离(以内存元素数量的形式)。 |
Strided 数组是 AbstractArray 的子类型,其条目以固定步长储存在内存中。如果数组的元素类型与 BLAS 兼容,则 strided 数组可以利用 BLAS 和 LAPACK 例程来实现更高效的线性代数例程。用户定义的 strided 数组的典型示例是把标准 Array 用附加结构进行封装的数组。 警告:如果底层存储实际上不是 strided,则不要实现这些方法,因为这可能导致错误的结果或段错误。 下面是一些示例,用来演示哪些数组类型是 strided 数组,哪些不是: 1:5 # not strided (there is no storage associated with this array.)
Vector(1:5) # is strided with strides (1,)
A = [1 5; 2 6; 3 7; 4 8] # is strided with strides (1,4)
V = view(A, 1:2, :) # is strided with strides (1,4)
V = view(A, 1:2:3, 1:2) # is strided with strides (2,4)
V = view(A, [1,2,4], :) # is not strided, as the spacing between rows is not fixed.
[自定义广播]需要实现的方法 | 简短描述 |
---|
Base.BroadcastStyle(::Type{SrcType}) = SrcStyle() | SrcType 的广播行为 | Base.similar(bc::Broadcasted{DestStyle}, ::Type{ElType}) | 输出容器的分配 | 可选方法 | | Base.BroadcastStyle(::Style1, ::Style2) = Style12() | 混合广播风格的优先级规则 | Base.axes(x) | 用于广播的 x 的索引的声明(默认为 axes(x) ) | Base.broadcastable(x) | 将 x 转换为一个具有 axes 且支持索引的对象 | 绕过默认机制 | | Base.copy(bc::Broadcasted{DestStyle}) | broadcast 的自定义实现 | Base.copyto!(dest, bc::Broadcasted{DestStyle}) | 专门针对 DestStyle 的自定义 broadcast! 实现 | Base.copyto!(dest::DestType, bc::Broadcasted{Nothing}) | 专门针对 DestStyle 的自定义 broadcast! 实现 | Base.Broadcast.broadcasted(f, args...) | 覆盖融合表达式中的默认惰性行为 | Base.Broadcast.instantiate(bc::Broadcasted{DestStyle}) | 覆盖惰性广播的 axes 的计算 |
广播可由 broadcast 或 broadcast! 的显式调用、或者像 A .+ b 或 f.(x, y) 这样的「点」操作隐式触发。任何具有 axes 且支持索引的对象都可作为参数参与广播,默认情况下,广播结果储存在 Array 中。这个基本框架可通过三个主要方式扩展: - 确保所有参数都支持广播
- 为给定参数集选择合适的输出数组
- 为给定参数集选择高效的实现
不是所有类型都支持 axes 和索引,但许多类型便于支持广播。Base.broadcastable 函数会在每个广播参数上调用,它能返回与广播参数不同的支持 axes 和索引的对象。默认情况下,对于所有 AbstractArray 和 Number 来说这是 identity 函数——因为它们已经支持 axes 和索引了。少数其它类型(包括但不限于类型本身、函数、像 missing 和 nothing 这样的特殊单态类型以及日期)为了能被广播,Base.broadcastable 会返回封装在 Ref 的参数来充当 0 维「标量」。自定义类型可以类似地指定 Base.broadcastable 来定义其形状,但是它们应当遵循 collect(Base.broadcastable(x)) == collect(x) 的约定。一个值得注意的例外是 AbstractString ;字符串是个特例,为了能被广播其表现为标量,尽管它们是其字符的可迭代集合(详见 [字符串](@id man-strings))。 接下来的两个步骤(选择输出数组和实现)依赖于如何确定给定参数集的唯一解。广播必须接受其参数的所有不同类型,并把它们折叠到一个输出数组和实现。广播称此唯一解为「风格」。每个可广播对象都有自己的首选风格,并使用类似于类型提升的系统将这些风格组合成一个唯一解——「目标风格」。 广播风格抽象类型 Base.BroadcastStyle 派生了所有的广播风格。其在用作函数时有两种可能的形式,分别为一元形式(单参数)和二元形式。使用一元形式表明你打算实现特定的广播行为和/或输出类型,并且不希望依赖于默认的回退 Broadcast.DefaultArrayStyle 。 为了覆盖这些默认值,你可以为对象自定义 BroadcastStyle : struct MyStyle <: Broadcast.BroadcastStyle end
Base.BroadcastStyle(::Type{<:MyType}) = MyStyle()
在某些情况下,无需定义 MyStyle 也许很方便,在这些情况下,你可以利用一个通用的广播封装器: Base.BroadcastStyle(::Type{<:MyType}) = Broadcast.Style{MyType}() 可用于任意类型。
如果 MyType 是一个 AbstractArray ,首选是 Base.BroadcastStyle(::Type{<:MyType}) = Broadcast.ArrayStyle{MyType}() 。 对于只支持某个具体维度的 AbstractArrays ,请创建 Broadcast.AbstractArrayStyle{N} 的子类型(请参阅下文)。
当你的广播操作涉及多个参数,各个广播风格将合并,来确定唯一一个 DestStyle 以控制输出容器的类型。有关更多详细信息,请参阅[下文](http://127.0.0.5/@ref writing-binary-broadcasting-rules)。 选择合适的输出数组每个广播操作都会计算广播风格以便支持派发和专门化。结果数组的实际分配由 similar 处理,其使用 Broadcasted 对象作为其第一个参数。 Base.similar(bc::Broadcasted{DestStyle}, ::Type{ElType})
回退定义是 similar(bc::Broadcasted{DefaultArrayStyle{N}}, ::Type{ElType}) where {N,ElType} =
similar(Array{ElType}, axes(bc))
但是,如果需要,你可以专门化任何或所有这些参数。最后的参数 bc 是(还可能是融合的)广播操作的惰性表示,即 Broadcasted 对象。出于这些目的,该封装器中最重要的字段是 f 和 args ,分别描述函数和参数列表。请注意,参数列表可以——并且经常——包含其它嵌套的 Broadcasted 封装器。 举个完整的例子,假设你创建了类型 ArrayAndChar ,该类型存储一个数组和单个字符: struct ArrayAndChar{T,N} <: AbstractArray{T,N}
data::Array{T,N}
char::Char
end
Base.size(A::ArrayAndChar) = size(A.data)
Base.getindex(A::ArrayAndChar{T,N}, inds::Vararg{Int,N}) where {T,N} = A.data[inds...]
Base.setindex!(A::ArrayAndChar{T,N}, val, inds::Vararg{Int,N}) where {T,N} = A.data[inds...] = val
Base.showarg(io::IO, A::ArrayAndChar, toplevel) = print(io, typeof(A), " with char '", A.char, "'")
# output
你可能想要保留「元数据」char 。为此,我们首先定义 Base.BroadcastStyle(::Type{<:ArrayAndChar}) = Broadcast.ArrayStyle{ArrayAndChar}()
# output
这意味着我们还必须定义相应的 similar 方法: function Base.similar(bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{ArrayAndChar}}, ::Type{ElType}) where ElType
# Scan the inputs for the ArrayAndChar:
A = find_aac(bc)
# Use the char field of A to create the output
ArrayAndChar(similar(Array{ElType}, axes(bc)), A.char)
end
"`A = find_aac(As)` returns the first ArrayAndChar among the arguments."
find_aac(bc::Base.Broadcast.Broadcasted) = find_aac(bc.args)
find_aac(args::Tuple) = find_aac(find_aac(args[1]), Base.tail(args))
find_aac(x) = x
find_aac(a::ArrayAndChar, rest) = a
find_aac(::Any, rest) = find_aac(rest)
# output
find_aac (generic function with 5 methods)
在这些定义中,可以得到以下行为: julia> a = ArrayAndChar([1 2; 3 4], 'x')
2×2 ArrayAndChar{Int64,2} with char 'x':
1 2
3 4
julia> a .+ 1
2×2 ArrayAndChar{Int64,2} with char 'x':
2 3
4 5
julia> a .+ [5,10]
2×2 ArrayAndChar{Int64,2} with char 'x':
6 7
13 14
[使用自定义实现扩展广播](@id extending-in-place-broadcast)一般来说,广播操作由一个惰性 Broadcasted 容器表示,该容器保存要应用的函数及其参数。这些参数可能本身是嵌套得更深的 Broadcasted 容器,并一起形成了一个待求值的大型表达式树。嵌套的 Broadcasted 容器树可由隐式的点语法直接构造;例如,5 .+ 2.*x 由 Broadcasted(+, 5, Broadcasted(*, 2, x)) 暂时表示。这对于用户是不可见的,因为它是通过调用 copy 立即实现的,但是此容器为自定义类型的作者提供了广播可扩展性的基础。然后,内置的广播机制将根据参数确定结果的类型和大小,为它分配内存,并最终通过默认的 copyto!(::AbstractArray, ::Broadcasted) 方法将 Broadcasted 对象复制到其中。内置的回退 broadcast 和 broadcast! 方法类似地构造操作的暂时 Broadcasted 表示,因此它们共享相同的代码路径。这便允许自定义的数组实现通过提供它们自己的专门化 copyto! 来定义和优化广播。这再次由计算后的广播风格确定。此广播风格在广播操作中非常重要,以至于它被存储为 Broadcasted 类型的第一个类型参数,且允许派发和专门化。 对于某些类型,跨越层层嵌套的广播的「融合」操作无法实现,或者无法更高效地逐步完成。在这种情况下,你可能需要或者想要求值 x .* (x .+ 1) ,就好像该式已被编写成 broadcast(*, x, broadcast(+, x, 1)) ,其中内部广播操作会在处理外部广播操作前进行求值。这种直接的操作以有点间接的方式得到直接支持;Julia 不会直接构造 Broadcasted 对象,而会将 待融合的表达式 x .* (x .+ 1) 降低为 Broadcast.broadcasted(*, x, Broadcast.broadcasted(+, x, 1)) 。现在,默认情况下,broadcasted 只会调用 Broadcasted 构造函数来创建待融合表达式树的惰性表示,但是你可以选择为函数和参数的特定组合覆盖它。 举个例子,内置的 AbstractRange 对象使用此机制优化广播表达式的片段,这些表达式片段可以只根据 start、step 和 length(或 stop)直接进行求值,而无需计算每个元素。与所有其它机制一样,broadcasted 也会计算并暴露其参数的组合广播风格,所以你可以为广播风格、函数和参数的任意组合专门化 broadcasted(::DestStyle, f, args...) ,而不是专门化 broadcasted(f, args...) 。 例如,以下定义支持 range 的负运算: broadcasted(::DefaultArrayStyle{1}, ::typeof(-), r::OrdinalRange) = range(-first(r), step=-step(r), length=length(r))
[扩展 in-place 广播](@id extending-in-place-broadcast)In-place 广播可通过定义合适的 copyto!(dest, bc::Broadcasted) 方法来支持。由于你可能想要专门化 dest 或 bc 的特定子类型,为了避免包之间的歧义,我们建议采用以下约定。 如果你想要专门化特定的广播风格 DestStyle ,请为其定义一个方法 copyto!(dest, bc::Broadcasted{DestStyle})
你可选择使用此形式,如果使用,你还可以专门化 dest 的类型。 如果你想专门化目标类型 DestType 而不专门化 DestStyle ,那么你应该定义一个带有以下签名的方法: copyto!(dest::DestType, bc::Broadcasted{Nothing})
这利用了 copyto! 的回退实现,它将该封装器转换为一个 Broadcasted{Nothing} 对象。因此,专门化 DestType 的方法优先级低于专门化 DestStyle 的方法。 同样,你可以使用 copy(::Broadcasted) 方法完全覆盖 out-of-place 广播。 使用 Broadcasted 对象当然,为了实现这样的 copy 或 copyto! 方法,你必须使用 Broadcasted 封装器来计算每个元素。这主要有两种方式: [编写二元广播规则](@id writing-binary-broadcasting-rules)广播风格的优先级规则由二元 BroadcastStyle 调用定义: Base.BroadcastStyle(::Style1, ::Style2) = Style12()
其中,Style12 是你要为输出所选择的 BroadcastStyle ,所涉及的参数具有 Style1 及 Style2 。例如, Base.BroadcastStyle(::Broadcast.Style{Tuple}, ::Broadcast.AbstractArrayStyle{0}) = Broadcast.Style{Tuple}()
表示 Tuple 「胜过」零维数组(输出容器将是元组)。值得注意的是,你不需要(也不应该)为此调用的两个参数顺序下定义;无论用户提供的以何种顺序提供参数,定义一个就够了。 对于 AbstractArray 类型,定义 BroadcastStyle 将取代回退选择 Broadcast.DefaultArrayStyle 。DefaultArrayStyle 及其抽象超类型 AbstractArrayStyle 将维度存储为类型参数,以支持具有固定维度需求的特定数组类型。 由于以下方法,DefaultArrayStyle 「输给」任何其它已定义的 AbstractArrayStyle : BroadcastStyle(a::AbstractArrayStyle{Any}, ::DefaultArrayStyle) = a
BroadcastStyle(a::AbstractArrayStyle{N}, ::DefaultArrayStyle{N}) where N = a
BroadcastStyle(a::AbstractArrayStyle{M}, ::DefaultArrayStyle{N}) where {M,N} =
typeof(a)(_max(Val(M),Val(N)))
除非你想要为两个或多个非 DefaultArrayStyle 的类型建立优先级,否则不需要编写二元 BroadcastStyle 规则。 如果你的数组类型确实有固定的维度需求,那么你应该定义一个 AbstractArrayStyle 的子类型。例如,稀疏数组的代码中有以下定义: struct SparseVecStyle <: Broadcast.AbstractArrayStyle{1} end
struct SparseMatStyle <: Broadcast.AbstractArrayStyle{2} end
Base.BroadcastStyle(::Type{<:SparseVector}) = SparseVecStyle()
Base.BroadcastStyle(::Type{<:SparseMatrixCSC}) = SparseMatStyle()
每当你定义一个 AbstractArrayStyle 的子类型,你还需要定义用于组合维度的规则,这通过为你的广播风格创建带有一个 Val(N) 参数的构造函数。例如: SparseVecStyle(::Val{0}) = SparseVecStyle()
SparseVecStyle(::Val{1}) = SparseVecStyle()
SparseVecStyle(::Val{2}) = SparseMatStyle()
SparseVecStyle(::Val{N}) where N = Broadcast.DefaultArrayStyle{N}()
这些规则表明 SparseVecStyle 与 0 维或 1 维数组的组合会产生另一个 SparseVecStyle ,与 2 维数组的组合会产生 SparseMatStyle ,而与维度更高的数组则回退到任意维密集矩阵的框架中。这些规则允许广播为产生一维或二维输出的操作保持其稀疏表示,但为任何其它维度生成 Array 。 |
发布评论