diff --git a/src/config.jl b/src/config.jl index d561b4fd..579079a5 100644 --- a/src/config.jl +++ b/src/config.jl @@ -20,11 +20,50 @@ end Tag(::Nothing, ::Type{V}) where {V} = nothing - @inline function ≺(::Type{Tag{F1,V1}}, ::Type{Tag{F2,V2}}) where {F1,V1,F2,V2} tagcount(Tag{F1,V1}) < tagcount(Tag{F2,V2}) end +""" + HashTag{Hash} + +HashTag is similar to a Tag, but carries just a small UInt64 hash, +instead of the full type, which makes stacktraces / types easier to +read while still providing good resilience to perturbation confusion. +""" +struct HashTag{H} +end + +@generated function tagcount(::Type{HashTag{H}}) where {H} + :($(Threads.atomic_add!(TAGCOUNT, UInt(1)))) +end + +function HashTag(f::F, ::Type{V}) where {F,V} + H = if F <: Tuple + # no easy way to check Jacobian tag used with Hessians as multiple functions may be used + # see checktag(::Type{Tag{FT,VT}}, f::F, x::AbstractArray{V}) where {FT<:Tuple,VT,F,V} + nothing + else + hash(F) ⊻ hash(V) + end + tagcount(HashTag{H}) # trigger generated function + HashTag{H}() +end + +HashTag(::Nothing, ::Type{V}) where {V} = nothing + +@inline function ≺(::Type{HashTag{H1}}, ::Type{Tag{F2,V2}}) where {H1,F2,V2} + tagcount(HashTag{H1}) < tagcount(Tag{F2,V2}) +end + +@inline function ≺(::Type{Tag{F1,V1}}, ::Type{HashTag{H2}}) where {F1,V1,H2} + tagcount(Tag{F1,V1}) < tagcount(HashTag{H2}) +end + +@inline function ≺(::Type{HashTag{H1}}, ::Type{HashTag{H2}}) where {H1,H2} + tagcount(HashTag{H1}) < tagcount(HashTag{H2}) +end + struct InvalidTagException{E,O} <: Exception end @@ -36,13 +75,22 @@ checktag(::Type{Tag{FT,VT}}, f::F, x::AbstractArray{V}) where {FT,VT,F,V} = checktag(::Type{Tag{F,V}}, f::F, x::AbstractArray{V}) where {F,V} = true +# HashTag is a smaller tag, that only confirms the hash +function checktag(::Type{HashTag{HT}}, f::F, x::AbstractArray{V}) where {HT,F,V} + H = hash(F) ⊻ hash(V) + if HT == H || HT === nothing + true + else + throw(InvalidTagException{HashTag{H},HashTag{HT}}()) + end +end + # no easy way to check Jacobian tag used with Hessians as multiple functions may be used checktag(::Type{Tag{FT,VT}}, f::F, x::AbstractArray{V}) where {FT<:Tuple,VT,F,V} = true # custom tag: you're on your own. checktag(z, f, x) = true - ################## # AbstractConfig # ################## @@ -55,6 +103,21 @@ Base.eltype(cfg::AbstractConfig) = eltype(typeof(cfg)) @inline (chunksize(::AbstractConfig{N})::Int) where {N} = N +@inline function maketag(f, X; style::Union{Symbol,Nothing} = nothing) + if style === :hash + return HashTag(f, X) + elseif style === :type + return Tag(f, X) + elseif style === nothing + if VERSION ≥ v"1.11" + return HashTag(f, X) + else + return Tag(f, X) + end + end + error("unexpected tag style: $(style)") +end + #################### # DerivativeConfig # #################### @@ -108,9 +171,9 @@ vector `x`. The returned `GradientConfig` instance contains all the work buffers required by `ForwardDiff.gradient` and `ForwardDiff.gradient!`. -If `f` is `nothing` instead of the actual target function, then the returned instance can -be used with any target function. However, this will reduce ForwardDiff's ability to catch -and prevent perturbation confusion (see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). +If `f` or `tag` is `nothing`, then the returned instance can be used with any target function. +However, this will reduce ForwardDiff's ability to catch and prevent perturbation confusion +(see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). This constructor does not store/modify `x`. """ @@ -145,9 +208,9 @@ The returned `JacobianConfig` instance contains all the work buffers required by `ForwardDiff.jacobian` and `ForwardDiff.jacobian!` when the target function takes the form `f(x)`. -If `f` is `nothing` instead of the actual target function, then the returned instance can -be used with any target function. However, this will reduce ForwardDiff's ability to catch -and prevent perturbation confusion (see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). +If `f` or `tag` is `nothing`, then the returned instance can be used with any target function. +However, this will reduce ForwardDiff's ability to catch and prevent perturbation confusion +(see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). This constructor does not store/modify `x`. """ @@ -170,9 +233,9 @@ The returned `JacobianConfig` instance contains all the work buffers required by `ForwardDiff.jacobian` and `ForwardDiff.jacobian!` when the target function takes the form `f!(y, x)`. -If `f!` is `nothing` instead of the actual target function, then the returned instance can -be used with any target function. However, this will reduce ForwardDiff's ability to catch -and prevent perturbation confusion (see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). +If `f!` or `tag` is `nothing`, then the returned instance can be used with any target function. +However, this will reduce ForwardDiff's ability to catch and prevent perturbation confusion +(see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). This constructor does not store/modify `y` or `x`. """ @@ -212,9 +275,9 @@ configured for the case where the `result` argument is an `AbstractArray`. If it is a `DiffResult`, the `HessianConfig` should instead be constructed via `ForwardDiff.HessianConfig(f, result, x, chunk)`. -If `f` is `nothing` instead of the actual target function, then the returned instance can -be used with any target function. However, this will reduce ForwardDiff's ability to catch -and prevent perturbation confusion (see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). +If `f` or `tag` is `nothing`, then the returned instance can be used with any target function. +However, this will reduce ForwardDiff's ability to catch and prevent perturbation confusion +(see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). This constructor does not store/modify `x`. """ @@ -236,9 +299,9 @@ type/shape of the input vector `x`. The returned `HessianConfig` instance contains all the work buffers required by `ForwardDiff.hessian!` for the case where the `result` argument is an `DiffResult`. -If `f` is `nothing` instead of the actual target function, then the returned instance can -be used with any target function. However, this will reduce ForwardDiff's ability to catch -and prevent perturbation confusion (see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). +If `f` or `tag` is `nothing`, then the returned instance can be used with any target function. +However, this will reduce ForwardDiff's ability to catch and prevent perturbation confusion +(see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). This constructor does not store/modify `x`. """