From a515f5a30f8b1c81e9f04a317464cd96bfd1ebd4 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Wed, 23 Apr 2025 01:42:38 -0400 Subject: [PATCH 01/13] Add `SmallTag` type This is an alternative to `Tag` that provides largely the same functionality, but carries around only the hash of the function / array types instead of the full types themselves. This can make these types much less bulky to print and easier to visually scan for. --- src/config.jl | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/config.jl b/src/config.jl index d561b4fd..11442447 100644 --- a/src/config.jl +++ b/src/config.jl @@ -20,11 +20,46 @@ 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 +# SmallTag 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 SmallTag{H} +end + +@generated function tagcount(::Type{SmallTag{H}}) where {H} + :($(Threads.atomic_add!(TAGCOUNT, UInt(1)))) +end + +function SmallTag(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(SmallTag{H}) # trigger generated function + SmallTag{H}() +end + +SmallTag(::Nothing, ::Type{V}) where {V} = nothing + +@inline function ≺(::Type{SmallTag{H1}}, ::Type{Tag{F2,V2}}) where {H1,F2,V2} + tagcount(SmallTag{H1}) < tagcount(Tag{F2,V2}) +end + +@inline function ≺(::Type{Tag{F1,V1}}, ::Type{SmallTag{H2}}) where {F1,V1,H2} + tagcount(Tag{F1,V1}) < tagcount(SmallTag{H2}) +end + +@inline function ≺(::Type{SmallTag{H1}}, ::Type{SmallTag{H2}}) where {H1,H2} + tagcount(SmallTag{H1}) < tagcount(SmallTag{H2}) +end + struct InvalidTagException{E,O} <: Exception end @@ -36,13 +71,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 +# SmallTag is a smaller tag, that only confirms the hash +function checktag(::Type{SmallTag{HT}}, f::F, x::AbstractArray{V}) where {HT,F,V} + H = hash(F) ⊻ hash(V) + if HT == H || HT === nothing + true + else + throw(InvalidTagException{SmallTag{H},SmallTag{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 # ################## From 438fe5f149b918d466a643750285ba4ac5f7f780 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Wed, 23 Apr 2025 01:45:36 -0400 Subject: [PATCH 02/13] Support `tag = :small` in `Config(...)` constructors This provides a convenient interface to ask for a SmallTag. --- src/config.jl | 150 ++++++++++++++++++++++++++++++++++--------- test/GradientTest.jl | 14 +++- test/HessianTest.jl | 8 ++- test/JacobianTest.jl | 19 ++++-- 4 files changed, 148 insertions(+), 43 deletions(-) diff --git a/src/config.jl b/src/config.jl index 11442447..5190a19d 100644 --- a/src/config.jl +++ b/src/config.jl @@ -99,6 +99,18 @@ Base.eltype(cfg::AbstractConfig) = eltype(typeof(cfg)) @inline (chunksize(::AbstractConfig{N})::Int) where {N} = N +function maketag(kind::Union{Symbol,Nothing}, f, X) + if kind === :default + return Tag(f, X) + elseif kind === :small + return SmallTag(f, X) + elseif kind === nothing + return nothing + else + throw(ArgumentError("tag may be :default, :small, or nothing")) + end +end + #################### # DerivativeConfig # #################### @@ -108,7 +120,7 @@ struct DerivativeConfig{T,D} <: AbstractConfig{1} end """ - ForwardDiff.DerivativeConfig(f!, y::AbstractArray, x::Real) + ForwardDiff.DerivativeConfig(f!, y::AbstractArray, x::Real; tag::Union{Symbol,Nothing} = :default) Return a `DerivativeConfig` instance based on the type of `f!`, and the types/shapes of the output vector `y` and the input value `x`. @@ -121,12 +133,24 @@ If `f!` is `nothing` instead of the actual target function, then the returned in 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 `tag` is `:small`, a small hash-based tag is provided. This tracks perturbation confusion +with similar accuracy, but is much smaller when printing types. + This constructor does not store/modify `y` or `x`. """ +@inline function DerivativeConfig(f::F, + y::AbstractArray{Y}, + x::X; + tag::Union{Symbol,Nothing} = :default) where {F,X<:Real,Y<:Real} + # @inline ensures that, e.g., DerivativeConfig(...; tag = :small) will be well-inferred + T = @inline maketag(tag, f, X) + return @noinline DerivativeConfig(f,y,x,T) +end + function DerivativeConfig(f::F, y::AbstractArray{Y}, x::X, - tag::T = Tag(f, X)) where {F,X<:Real,Y<:Real,T} + tag::T) where {F,X<:Real,Y<:Real,T} duals = similar(y, Dual{T,Y,1}) return DerivativeConfig{T,typeof(duals)}(duals) end @@ -144,7 +168,7 @@ struct GradientConfig{T,V,N,D} <: AbstractConfig{N} end """ - ForwardDiff.GradientConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x)) + ForwardDiff.GradientConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x); tag::Union{Symbol,Nothing} = :default) Return a `GradientConfig` instance based on the type of `f` and type/shape of the input vector `x`. @@ -152,16 +176,28 @@ 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). + +If `tag` is `:small`, a small hash-based tag is provided. This tracks perturbation confusion +with similar accuracy, but is much smaller when printing types. This constructor does not store/modify `x`. """ +@inline function GradientConfig(f::F, + x::AbstractArray{V}, + c::Chunk{N} = Chunk(x); + tag::Union{Symbol,Nothing} = :default) where {F,V,N} + # @inline ensures that, e.g., GradientConfig(...; tag = :small) will be well-inferred + T = @inline maketag(tag, f, V) + return @noinline GradientConfig(f,x,c,T) +end + function GradientConfig(f::F, x::AbstractArray{V}, - ::Chunk{N} = Chunk(x), - ::T = Tag(f, V)) where {F,V,N,T} + ::Chunk{N}, + ::T) where {F,V,N,T} seeds = construct_seeds(Partials{N,V}) duals = similar(x, Dual{T,V,N}) return GradientConfig{T,V,N,typeof(duals)}(seeds, duals) @@ -180,7 +216,7 @@ struct JacobianConfig{T,V,N,D} <: AbstractConfig{N} end """ - ForwardDiff.JacobianConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x)) + ForwardDiff.JacobianConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x); tag::Union{Symbol,Nothing} = :default) Return a `JacobianConfig` instance based on the type of `f` and type/shape of the input vector `x`. @@ -189,23 +225,35 @@ 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). + +If `tag` is `:small`, a small hash-based tag is provided. This tracks perturbation confusion +with similar accuracy, but is much smaller when printing types. This constructor does not store/modify `x`. """ +@inline function JacobianConfig(f::F, + x::AbstractArray{V}, + c::Chunk{N} = Chunk(x); + tag::Union{Symbol,Nothing} = :default) where {F,V,N} + # @inline ensures that, e.g., JacobianConfig(...; tag = :small) will be well-inferred + T = @inline maketag(tag, f, V) + return @noinline JacobianConfig(f,x,c,T) +end + function JacobianConfig(f::F, x::AbstractArray{V}, - ::Chunk{N} = Chunk(x), - ::T = Tag(f, V)) where {F,V,N,T} + ::Chunk{N}, + ::T) where {F,V,N,T} seeds = construct_seeds(Partials{N,V}) duals = similar(x, Dual{T,V,N}) return JacobianConfig{T,V,N,typeof(duals)}(seeds, duals) end """ - ForwardDiff.JacobianConfig(f!, y::AbstractArray, x::AbstractArray, chunk::Chunk = Chunk(x)) + ForwardDiff.JacobianConfig(f!, y::AbstractArray, x::AbstractArray, chunk::Chunk = Chunk(x); tag::Union{Symbol,Nothing} = :default) Return a `JacobianConfig` instance based on the type of `f!`, and the types/shapes of the output vector `y` and the input vector `x`. @@ -214,17 +262,30 @@ 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). + +If `tag` is `:small`, a small hash-based tag is provided. This tracks perturbation confusion +with similar accuracy, but is much smaller when printing types. This constructor does not store/modify `y` or `x`. """ +@inline function JacobianConfig(f::F, + y::AbstractArray{Y}, + x::AbstractArray{X}, + c::Chunk{N} = Chunk(x); + tag::Union{Symbol,Nothing} = :default) where {F,Y,X,N} + # @inline ensures that, e.g., JacobianConfig(...; tag = :small) will be well-inferred + T = @inline maketag(tag, f, X) + return @noinline JacobianConfig(f,y,x,c,T) +end + function JacobianConfig(f::F, y::AbstractArray{Y}, x::AbstractArray{X}, - ::Chunk{N} = Chunk(x), - ::T = Tag(f, X)) where {F,Y,X,N,T} + ::Chunk{N}, + ::T) where {F,Y,X,N,T} seeds = construct_seeds(Partials{N,X}) yduals = similar(y, Dual{T,Y,N}) xduals = similar(x, Dual{T,X,N}) @@ -245,7 +306,7 @@ struct HessianConfig{T,V,N,DG,DJ} <: AbstractConfig{N} end """ - ForwardDiff.HessianConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x)) + ForwardDiff.HessianConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x); tag::Union{Symbol,Nothing} = :default) Return a `HessianConfig` instance based on the type of `f` and type/shape of the input vector `x`. @@ -256,23 +317,35 @@ 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). + +If `tag` is `:small`, a small hash-based tag is provided. This tracks perturbation confusion +with similar accuracy, but is much smaller when printing types. This constructor does not store/modify `x`. """ +@inline function HessianConfig(f::F, + x::AbstractArray{V}, + chunk::Chunk = Chunk(x); + tag::Union{Symbol,Nothing} = :default) where {F,V} + # @inline ensures that, e.g., HessianConfig(...; tag = :small) will be well-inferred + T = @inline maketag(tag, f, V) + return @noinline HessianConfig(f, x, chunk, T) +end + function HessianConfig(f::F, x::AbstractArray{V}, - chunk::Chunk = Chunk(x), - tag = Tag(f, V)) where {F,V} + chunk::Chunk, + tag) where {F,V} jacobian_config = JacobianConfig(f, x, chunk, tag) gradient_config = GradientConfig(f, jacobian_config.duals, chunk, tag) return HessianConfig(jacobian_config, gradient_config) end """ - ForwardDiff.HessianConfig(f, result::DiffResult, x::AbstractArray, chunk::Chunk = Chunk(x)) + ForwardDiff.HessianConfig(f, result::DiffResult, x::AbstractArray, chunk::Chunk = Chunk(x); tag::Union{Symbol,Nothing} = :default) Return a `HessianConfig` instance based on the type of `f`, types/storage in `result`, and type/shape of the input vector `x`. @@ -280,17 +353,30 @@ 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). + +If `tag` is `:small`, a small hash-based tag is provided. This tracks perturbation confusion +with similar accuracy, but is much smaller when printing types. This constructor does not store/modify `x`. """ +@inline function HessianConfig(f::F, + result::DiffResult, + x::AbstractArray{V}, + chunk::Chunk = Chunk(x); + tag::Union{Symbol,Nothing} = :default) where {F,V} + # @inline ensures that, e.g., HessianConfig(...; tag = :small) will be well-inferred + T = @inline maketag(tag, f, V) + return @noinline HessianConfig(f, result, x, chunk, T) +end + function HessianConfig(f::F, result::DiffResult, x::AbstractArray{V}, - chunk::Chunk = Chunk(x), - tag = Tag(f, V)) where {F,V} + chunk::Chunk, + tag) where {F,V} jacobian_config = JacobianConfig((f,gradient), DiffResults.gradient(result), x, chunk, tag) gradient_config = GradientConfig(f, jacobian_config.duals[2], chunk, tag) return HessianConfig(jacobian_config, gradient_config) diff --git a/test/GradientTest.jl b/test/GradientTest.jl index 4f46c167..d9c67a46 100644 --- a/test/GradientTest.jl +++ b/test/GradientTest.jl @@ -6,7 +6,7 @@ import NaNMath using Test using LinearAlgebra using ForwardDiff -using ForwardDiff: Dual, Tag +using ForwardDiff: Dual, maketag using StaticArrays using DiffTests @@ -21,7 +21,8 @@ x = [0.1, 0.2, 0.3] v = f(x) g = [-9.4, 15.6, 52.0] -@testset "Rosenbrock, chunk size = $c and tag = $(repr(tag))" for c in (1, 2, 3), tag in (nothing, Tag(f, eltype(x))) +@testset "Rosenbrock, chunk size = $c and tag = $(repr(maketag(tag, f, eltype(x))))" for c in (1, 2, 3), tag in (nothing, :default, :small) + tag = maketag(tag, f, eltype(x)) cfg = ForwardDiff.GradientConfig(f, x, ForwardDiff.Chunk{c}(), tag) @test eltype(cfg) == Dual{typeof(tag), eltype(x), c} @@ -60,7 +61,8 @@ cfgx = ForwardDiff.GradientConfig(sin, x) v = f(X) g = ForwardDiff.gradient(f, X) @test isapprox(g, Calculus.gradient(f, X), atol=FINITEDIFF_ERROR) - @testset "... with chunk size = $c and tag = $(repr(tag))" for c in CHUNK_SIZES, tag in (nothing, Tag(f, eltype(x))) + @testset "... with chunk size = $c and tag = $(repr(maketag(tag, f, eltype(x))))" for c in CHUNK_SIZES, tag in (nothing, :default, :small) + tag = maketag(tag, f, eltype(x)) cfg = ForwardDiff.GradientConfig(f, X, ForwardDiff.Chunk{c}(), tag) out = ForwardDiff.gradient(f, X, cfg) @@ -140,6 +142,12 @@ end # make sure this is not a source of type instability @inferred ForwardDiff.GradientConfig(f, sx) + if VERSION ≥ v"1.11" + # make sure that `GradientConfig(...; tag = compile-time-constant)` also + # infers well (requires that Base.hash(::Type) is foldable, which is true + # in Julia ≥ 1.11) + @inferred ((f, sx)->ForwardDiff.GradientConfig(f, sx; tag=:small))(f, sx) + end end @testset "exponential function at base zero" begin diff --git a/test/HessianTest.jl b/test/HessianTest.jl index 4c667e5e..68b6d103 100644 --- a/test/HessianTest.jl +++ b/test/HessianTest.jl @@ -5,7 +5,7 @@ import Calculus using Test using LinearAlgebra using ForwardDiff -using ForwardDiff: Dual, Tag +using ForwardDiff: Dual, maketag using StaticArrays using DiffTests @@ -23,7 +23,8 @@ h = [-66.0 -40.0 0.0; -40.0 130.0 -80.0; 0.0 -80.0 200.0] -@testset "running hardcoded test with chunk size = $c and tag = $(repr(tag))" for c in HESSIAN_CHUNK_SIZES, tag in (nothing, Tag((f,ForwardDiff.gradient), eltype(x))) +@testset "running hardcoded test with chunk size = $c and tag = $(repr(maketag(tag, (f, ForwardDiff.gradient), eltype(x))))" for c in HESSIAN_CHUNK_SIZES, tag in (nothing, :default, :small) + tag = maketag(tag, (f, ForwardDiff.gradient), eltype(x)) cfg = ForwardDiff.HessianConfig(f, x, ForwardDiff.Chunk{c}(), tag) resultcfg = ForwardDiff.HessianConfig(f, DiffResults.HessianResult(x), x, ForwardDiff.Chunk{c}(), tag) @@ -68,7 +69,8 @@ for f in DiffTests.VECTOR_TO_NUMBER_FUNCS h = ForwardDiff.hessian(f, X) # finite difference approximation error is really bad for Hessians... @test isapprox(h, Calculus.hessian(f, X), atol=0.02) - @testset "$f with chunk size = $c and tag = $(repr(tag))" for c in HESSIAN_CHUNK_SIZES, tag in (nothing, Tag((f,ForwardDiff.gradient), eltype(x))) + @testset "$f with chunk size = $c and tag = $(repr(maketag(tag, (f, ForwardDiff.gradient), eltype(x))))" for c in HESSIAN_CHUNK_SIZES, tag in (nothing, :default, :small) + tag = maketag(tag, (f, ForwardDiff.gradient), eltype(x)) cfg = ForwardDiff.HessianConfig(f, X, ForwardDiff.Chunk{c}(), tag) resultcfg = ForwardDiff.HessianConfig(f, DiffResults.HessianResult(X), X, ForwardDiff.Chunk{c}(), tag) diff --git a/test/JacobianTest.jl b/test/JacobianTest.jl index 1e52f7fa..5b319af2 100644 --- a/test/JacobianTest.jl +++ b/test/JacobianTest.jl @@ -4,7 +4,7 @@ import Calculus using Test using ForwardDiff -using ForwardDiff: Dual, Tag, JacobianConfig +using ForwardDiff: Dual, Tag, SmallTag, JacobianConfig, maketag using StaticArrays using DiffTests using LinearAlgebra @@ -31,8 +31,8 @@ j = [0.8242369704835132 0.4121184852417566 -10.933563142616123 0.169076696546684 0.084538348273342 -2.299173530851733 0.0 0.0 1.0] -for c in (1, 2, 3), tags in ((nothing, nothing), - (Tag(f, eltype(x)), Tag(f!, eltype(x)))) +for c in (1, 2, 3), tag in (nothing, :small, :default) + tags = (maketag(tag, f, eltype(x)), maketag(tag, f!, eltype(x))) println(" ...running hardcoded test with chunk size = $c and tag = $(repr(tags))") cfg = JacobianConfig(f, x, ForwardDiff.Chunk{c}(), tags[1]) ycfg = JacobianConfig(f!, fill(0.0, 4), x, ForwardDiff.Chunk{c}(), tags[2]) @@ -103,9 +103,11 @@ for f in DiffTests.ARRAY_TO_ARRAY_FUNCS v = f(X) j = ForwardDiff.jacobian(f, X) @test isapprox(j, Calculus.jacobian(x -> vec(f(x)), X, :forward), atol=1.3FINITEDIFF_ERROR) - @testset "$f with chunk size = $c and tag = $(repr(tag))" for c in CHUNK_SIZES, tag in (nothing, Tag) + @testset "$f with chunk size = $c and tag = $(repr(tag))" for c in CHUNK_SIZES, tag in (nothing, Tag, SmallTag) if tag == Tag tag = Tag(f, eltype(X)) + elseif tag == SmallTag + tag = SmallTag(f, eltype(X)) end cfg = JacobianConfig(f, X, ForwardDiff.Chunk{c}(), tag) @@ -128,7 +130,8 @@ for f! in DiffTests.INPLACE_ARRAY_TO_ARRAY_FUNCS f!(v, X) j = ForwardDiff.jacobian(f!, fill!(similar(Y), 0.0), X) @test isapprox(j, Calculus.jacobian(x -> (y = fill!(similar(Y), 0.0); f!(y, x); vec(y)), X, :forward), atol=FINITEDIFF_ERROR) - @testset "$(f!) with chunk size = $c and tag = $(repr(tag))" for c in CHUNK_SIZES, tag in (nothing, Tag(f!, eltype(X))) + @testset "$(f!) with chunk size = $c and tag = $(repr(maketag(tag, f!, eltype(X))))" for c in CHUNK_SIZES, tag in (nothing, :small, :default) + tag = maketag(tag, f!, eltype(X)) ycfg = JacobianConfig(f!, fill!(similar(Y), 0.0), X, ForwardDiff.Chunk{c}(), tag) y = fill!(similar(Y), 0.0) @@ -225,6 +228,12 @@ for T in (StaticArrays.SArray, StaticArrays.MArray) # make sure this is not a source of type instability @inferred ForwardDiff.JacobianConfig(f, sx) + if VERSION ≥ v"1.11" + # make sure that `JacobianConfig(...; tag = compile-time-constant)` also + # infers well (requires that Base.hash(::Type) is foldable, which is true + # in Julia ≥ 1.11) + @inferred ((f, sx)->ForwardDiff.JacobianConfig(f, sx; tag=:small))(f, sx) + end end ######### From 73e8b145dd6a2914bee69feb08e4af65fee0ab45 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Wed, 23 Apr 2025 03:08:11 -0400 Subject: [PATCH 03/13] Mark allocation tests as fixed on 1.10 Latest patch release seems to have tightened this up. --- test/AllocationsTest.jl | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/test/AllocationsTest.jl b/test/AllocationsTest.jl index ad1832d0..9b20fb60 100644 --- a/test/AllocationsTest.jl +++ b/test/AllocationsTest.jl @@ -24,20 +24,12 @@ convert_test_574() = convert(ForwardDiff.Dual{Nothing,ForwardDiff.Dual{Nothing,F index = 1 alloc = @allocated ForwardDiff.seed!(duals, x, index, seeds) alloc = @allocated ForwardDiff.seed!(duals, x, index, seeds) - if VERSION < v"1.9" || VERSION >= v"1.11" - @test alloc == 0 - else - @test_broken alloc == 0 - end + @test alloc == 0 index = 1 alloc = @allocated ForwardDiff.seed!(duals, x, index, seed) alloc = @allocated ForwardDiff.seed!(duals, x, index, seed) - if VERSION < v"1.9" || VERSION >= v"1.11" - @test alloc == 0 - else - @test_broken alloc == 0 - end + @test alloc == 0 alloc = @allocated convert_test_574() alloc = @allocated convert_test_574() From d88bf58768e127e9a97a642deadbcd45163662e1 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Wed, 23 Apr 2025 09:35:40 -0400 Subject: [PATCH 04/13] Fix pre-compilation on Julia 1.6 This old version of Julia doesn't have call-site `@inline` / `@noinline` so version-guard against this trick for now. --- src/config.jl | 54 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 12 deletions(-) diff --git a/src/config.jl b/src/config.jl index 5190a19d..4ec3c50f 100644 --- a/src/config.jl +++ b/src/config.jl @@ -143,8 +143,13 @@ This constructor does not store/modify `y` or `x`. x::X; tag::Union{Symbol,Nothing} = :default) where {F,X<:Real,Y<:Real} # @inline ensures that, e.g., DerivativeConfig(...; tag = :small) will be well-inferred - T = @inline maketag(tag, f, X) - return @noinline DerivativeConfig(f,y,x,T) + @static if VERSION ≥ v"1.8" + T = @inline maketag(tag, f, X) + return @noinline DerivativeConfig(f,y,x,T) + else + T = maketag(tag, f, X) + return DerivativeConfig(f,y,x,T) + end end function DerivativeConfig(f::F, @@ -190,8 +195,13 @@ This constructor does not store/modify `x`. c::Chunk{N} = Chunk(x); tag::Union{Symbol,Nothing} = :default) where {F,V,N} # @inline ensures that, e.g., GradientConfig(...; tag = :small) will be well-inferred - T = @inline maketag(tag, f, V) - return @noinline GradientConfig(f,x,c,T) + @static if VERSION ≥ v"1.8" + T = @inline maketag(tag, f, V) + return @noinline GradientConfig(f,x,c,T) + else + T = maketag(tag, f, V) + return GradientConfig(f,x,c,T) + end end function GradientConfig(f::F, @@ -239,8 +249,13 @@ This constructor does not store/modify `x`. c::Chunk{N} = Chunk(x); tag::Union{Symbol,Nothing} = :default) where {F,V,N} # @inline ensures that, e.g., JacobianConfig(...; tag = :small) will be well-inferred - T = @inline maketag(tag, f, V) - return @noinline JacobianConfig(f,x,c,T) + @static if VERSION ≥ v"1.8" + T = @inline maketag(tag, f, V) + return @noinline JacobianConfig(f,x,c,T) + else + T = maketag(tag, f, V) + return JacobianConfig(f,x,c,T) + end end function JacobianConfig(f::F, @@ -277,8 +292,13 @@ This constructor does not store/modify `y` or `x`. c::Chunk{N} = Chunk(x); tag::Union{Symbol,Nothing} = :default) where {F,Y,X,N} # @inline ensures that, e.g., JacobianConfig(...; tag = :small) will be well-inferred - T = @inline maketag(tag, f, X) - return @noinline JacobianConfig(f,y,x,c,T) + @static if VERSION ≥ v"1.8" + T = @inline maketag(tag, f, X) + return @noinline JacobianConfig(f,y,x,c,T) + else + T = maketag(tag, f, X) + return JacobianConfig(f,y,x,c,T) + end end function JacobianConfig(f::F, @@ -331,8 +351,13 @@ This constructor does not store/modify `x`. chunk::Chunk = Chunk(x); tag::Union{Symbol,Nothing} = :default) where {F,V} # @inline ensures that, e.g., HessianConfig(...; tag = :small) will be well-inferred - T = @inline maketag(tag, f, V) - return @noinline HessianConfig(f, x, chunk, T) + @static if VERSION ≥ v"1.8" + T = @inline maketag(tag, f, V) + return @noinline HessianConfig(f, x, chunk, T) + else + T = maketag(tag, f, V) + return HessianConfig(f, x, chunk, T) + end end function HessianConfig(f::F, @@ -368,8 +393,13 @@ This constructor does not store/modify `x`. chunk::Chunk = Chunk(x); tag::Union{Symbol,Nothing} = :default) where {F,V} # @inline ensures that, e.g., HessianConfig(...; tag = :small) will be well-inferred - T = @inline maketag(tag, f, V) - return @noinline HessianConfig(f, result, x, chunk, T) + @static if VERSION ≥ v"1.8" + T = @inline maketag(tag, f, V) + return @noinline HessianConfig(f, result, x, chunk, T) + else + T = maketag(tag, f, V) + return HessianConfig(f, result, x, chunk, T) + end end function HessianConfig(f::F, From 7e6ce13fc38c41f3e0a4353af214ca2b45680998 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sat, 26 Jul 2025 12:05:17 -0400 Subject: [PATCH 05/13] Update src/config.jl --- src/config.jl | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/config.jl b/src/config.jl index 4ec3c50f..11c9811f 100644 --- a/src/config.jl +++ b/src/config.jl @@ -24,9 +24,12 @@ Tag(::Nothing, ::Type{V}) where {V} = nothing tagcount(Tag{F1,V1}) < tagcount(Tag{F2,V2}) end -# SmallTag 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. +""" + SmallTag{Hash} + +SmallTag 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 SmallTag{H} end From 135b85f83a89d5e1004ef0a864655ee6c8aa2b4b Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sat, 26 Jul 2025 12:05:39 -0400 Subject: [PATCH 06/13] Update src/config.jl --- src/config.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/config.jl b/src/config.jl index 11c9811f..eb188b6d 100644 --- a/src/config.jl +++ b/src/config.jl @@ -32,7 +32,7 @@ instead of the full type, which makes stacktraces / types easier to read while still providing good resilience to perturbation confusion. struct SmallTag{H} end - +""" @generated function tagcount(::Type{SmallTag{H}}) where {H} :($(Threads.atomic_add!(TAGCOUNT, UInt(1)))) end From 50b2f06ca8b3aee6649fd1005fb9a9ed6d42dafc Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sat, 26 Jul 2025 12:10:06 -0400 Subject: [PATCH 07/13] Update config.jl --- src/config.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/config.jl b/src/config.jl index eb188b6d..d591321e 100644 --- a/src/config.jl +++ b/src/config.jl @@ -30,9 +30,10 @@ end SmallTag 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 SmallTag{H} end -""" + @generated function tagcount(::Type{SmallTag{H}}) where {H} :($(Threads.atomic_add!(TAGCOUNT, UInt(1)))) end From 81188889443e62af295d2c2f7f20b56eb36526bd Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Sat, 26 Jul 2025 12:16:24 -0400 Subject: [PATCH 08/13] Change `tag` kwarg to accept `:default`, `:type`, or `:hash` --- src/config.jl | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/src/config.jl b/src/config.jl index d591321e..a95ab1aa 100644 --- a/src/config.jl +++ b/src/config.jl @@ -106,12 +106,14 @@ Base.eltype(cfg::AbstractConfig) = eltype(typeof(cfg)) function maketag(kind::Union{Symbol,Nothing}, f, X) if kind === :default return Tag(f, X) - elseif kind === :small + elseif kind === :type + return Tag(f, X) + elseif kind === :hash return SmallTag(f, X) elseif kind === nothing return nothing else - throw(ArgumentError("tag may be :default, :small, or nothing")) + throw(ArgumentError("tag may be :default, :type, :hash, or nothing")) end end @@ -137,8 +139,8 @@ If `f!` is `nothing` instead of the actual target function, then the returned in 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 `tag` is `:small`, a small hash-based tag is provided. This tracks perturbation confusion -with similar accuracy, but is much smaller when printing types. +If `tag` is `:hash`, a small hash-based tag is provided. This tracks perturbation confusion +with similar accuracy, but is much smaller than `tag = :type`, which stores the full type. This constructor does not store/modify `y` or `x`. """ @@ -189,8 +191,8 @@ If `f` or `tag` is `nothing`, then the returned instance can be used with any ta However, this will reduce ForwardDiff's ability to catch and prevent perturbation confusion (see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). -If `tag` is `:small`, a small hash-based tag is provided. This tracks perturbation confusion -with similar accuracy, but is much smaller when printing types. +If `tag` is `:hash`, a small hash-based tag is provided. This tracks perturbation confusion +with similar accuracy, but is much smaller than `tag = :type`, which stores the full type. This constructor does not store/modify `x`. """ @@ -243,8 +245,8 @@ If `f` or `tag` is `nothing`, then the returned instance can be used with any ta However, this will reduce ForwardDiff's ability to catch and prevent perturbation confusion (see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). -If `tag` is `:small`, a small hash-based tag is provided. This tracks perturbation confusion -with similar accuracy, but is much smaller when printing types. +If `tag` is `:hash`, a small hash-based tag is provided. This tracks perturbation confusion +with similar accuracy, but is much smaller than `tag = :type`, which stores the full type. This constructor does not store/modify `x`. """ @@ -285,8 +287,8 @@ If `f!` or `tag` is `nothing`, then the returned instance can be used with any t However, this will reduce ForwardDiff's ability to catch and prevent perturbation confusion (see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). -If `tag` is `:small`, a small hash-based tag is provided. This tracks perturbation confusion -with similar accuracy, but is much smaller when printing types. +If `tag` is `:hash`, a small hash-based tag is provided. This tracks perturbation confusion +with similar accuracy, but is much smaller than `tag = :type`, which stores the full type. This constructor does not store/modify `y` or `x`. """ @@ -345,8 +347,8 @@ If `f` or `tag` is `nothing`, then the returned instance can be used with any ta However, this will reduce ForwardDiff's ability to catch and prevent perturbation confusion (see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). -If `tag` is `:small`, a small hash-based tag is provided. This tracks perturbation confusion -with similar accuracy, but is much smaller when printing types. +If `tag` is `:hash`, a small hash-based tag is provided. This tracks perturbation confusion +with similar accuracy, but is much smaller than `tag = :type`, which stores the full type. This constructor does not store/modify `x`. """ @@ -386,8 +388,8 @@ If `f` or `tag` is `nothing`, then the returned instance can be used with any ta However, this will reduce ForwardDiff's ability to catch and prevent perturbation confusion (see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). -If `tag` is `:small`, a small hash-based tag is provided. This tracks perturbation confusion -with similar accuracy, but is much smaller when printing types. +If `tag` is `:hash`, a small hash-based tag is provided. This tracks perturbation confusion +with similar accuracy, but is much smaller than `tag = :type`, which stores the full type. This constructor does not store/modify `x`. """ From 20a8adbbdbad2a0f0da185f3ac1ca1d80ec3a377 Mon Sep 17 00:00:00 2001 From: Cody Tapscott Date: Sat, 26 Jul 2025 12:18:27 -0400 Subject: [PATCH 09/13] Make `tag = :hash` the default on Julia 1.11+ --- src/config.jl | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/config.jl b/src/config.jl index a95ab1aa..a06adbc0 100644 --- a/src/config.jl +++ b/src/config.jl @@ -105,7 +105,14 @@ Base.eltype(cfg::AbstractConfig) = eltype(typeof(cfg)) function maketag(kind::Union{Symbol,Nothing}, f, X) if kind === :default - return Tag(f, X) + @static if VERSION ≥ v"1.11" + return SmallTag(f, X) + else + # On ≤1.10, the hash of a type cannot be computed at compile-time, + # making `SmallTag(...)` type-unstable, so `Tag(...)` is left as + # as the default. + return Tag(f, X) + end elseif kind === :type return Tag(f, X) elseif kind === :hash From 9f82dfa51a70cfc3ef8bc33ef5ee46f7cd92bc59 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sat, 26 Jul 2025 12:29:05 -0400 Subject: [PATCH 10/13] Recommended naming changes: HashTag and tagstyle --- src/config.jl | 110 +++++++++++++++++++++---------------------- test/JacobianTest.jl | 8 ++-- 2 files changed, 59 insertions(+), 59 deletions(-) diff --git a/src/config.jl b/src/config.jl index a06adbc0..ffac7751 100644 --- a/src/config.jl +++ b/src/config.jl @@ -25,20 +25,20 @@ Tag(::Nothing, ::Type{V}) where {V} = nothing end """ - SmallTag{Hash} + HashTag{Hash} -SmallTag is similar to a Tag, but carries just a small UInt64 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 SmallTag{H} +struct HashTag{H} end -@generated function tagcount(::Type{SmallTag{H}}) where {H} +@generated function tagcount(::Type{HashTag{H}}) where {H} :($(Threads.atomic_add!(TAGCOUNT, UInt(1)))) end -function SmallTag(f::F, ::Type{V}) where {F,V} +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} @@ -46,22 +46,22 @@ function SmallTag(f::F, ::Type{V}) where {F,V} else hash(F) ⊻ hash(V) end - tagcount(SmallTag{H}) # trigger generated function - SmallTag{H}() + tagcount(HashTag{H}) # trigger generated function + HashTag{H}() end -SmallTag(::Nothing, ::Type{V}) where {V} = nothing +HashTag(::Nothing, ::Type{V}) where {V} = nothing -@inline function ≺(::Type{SmallTag{H1}}, ::Type{Tag{F2,V2}}) where {H1,F2,V2} - tagcount(SmallTag{H1}) < tagcount(Tag{F2,V2}) +@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{SmallTag{H2}}) where {F1,V1,H2} - tagcount(Tag{F1,V1}) < tagcount(SmallTag{H2}) +@inline function ≺(::Type{Tag{F1,V1}}, ::Type{HashTag{H2}}) where {F1,V1,H2} + tagcount(Tag{F1,V1}) < tagcount(HashTag{H2}) end -@inline function ≺(::Type{SmallTag{H1}}, ::Type{SmallTag{H2}}) where {H1,H2} - tagcount(SmallTag{H1}) < tagcount(SmallTag{H2}) +@inline function ≺(::Type{HashTag{H1}}, ::Type{HashTag{H2}}) where {H1,H2} + tagcount(HashTag{H1}) < tagcount(HashTag{H2}) end struct InvalidTagException{E,O} <: Exception @@ -75,13 +75,13 @@ 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 -# SmallTag is a smaller tag, that only confirms the hash -function checktag(::Type{SmallTag{HT}}, f::F, x::AbstractArray{V}) where {HT,F,V} +# 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{SmallTag{H},SmallTag{HT}}()) + throw(InvalidTagException{HashTag{H},HashTag{HT}}()) end end @@ -103,21 +103,21 @@ Base.eltype(cfg::AbstractConfig) = eltype(typeof(cfg)) @inline (chunksize(::AbstractConfig{N})::Int) where {N} = N -function maketag(kind::Union{Symbol,Nothing}, f, X) - if kind === :default +function maketag(tagstyle::Union{Symbol,Nothing}, f, X) + if tagstyle === :default @static if VERSION ≥ v"1.11" - return SmallTag(f, X) + return HashTag(f, X) else # On ≤1.10, the hash of a type cannot be computed at compile-time, - # making `SmallTag(...)` type-unstable, so `Tag(...)` is left as + # making `HashTag(...)` type-unstable, so `Tag(...)` is left as # as the default. return Tag(f, X) end - elseif kind === :type + elseif tagstyle === :type return Tag(f, X) - elseif kind === :hash - return SmallTag(f, X) - elseif kind === nothing + elseif tagstyle === :hash + return HashTag(f, X) + elseif tagstyle === nothing return nothing else throw(ArgumentError("tag may be :default, :type, :hash, or nothing")) @@ -133,7 +133,7 @@ struct DerivativeConfig{T,D} <: AbstractConfig{1} end """ - ForwardDiff.DerivativeConfig(f!, y::AbstractArray, x::Real; tag::Union{Symbol,Nothing} = :default) + ForwardDiff.DerivativeConfig(f!, y::AbstractArray, x::Real; tagstyle::Union{Symbol,Nothing} = :default) Return a `DerivativeConfig` instance based on the type of `f!`, and the types/shapes of the output vector `y` and the input value `x`. @@ -154,13 +154,13 @@ This constructor does not store/modify `y` or `x`. @inline function DerivativeConfig(f::F, y::AbstractArray{Y}, x::X; - tag::Union{Symbol,Nothing} = :default) where {F,X<:Real,Y<:Real} - # @inline ensures that, e.g., DerivativeConfig(...; tag = :small) will be well-inferred + tagstyle::Union{Symbol,Nothing} = :default) where {F,X<:Real,Y<:Real} + # @inline ensures that, e.g., DerivativeConfig(...; tagstyle = :small) will be well-inferred @static if VERSION ≥ v"1.8" - T = @inline maketag(tag, f, X) + T = @inline maketag(tagstyle, f, X) return @noinline DerivativeConfig(f,y,x,T) else - T = maketag(tag, f, X) + T = maketag(tagstyle, f, X) return DerivativeConfig(f,y,x,T) end end @@ -186,7 +186,7 @@ struct GradientConfig{T,V,N,D} <: AbstractConfig{N} end """ - ForwardDiff.GradientConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x); tag::Union{Symbol,Nothing} = :default) + ForwardDiff.GradientConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x); tagstyle::Union{Symbol,Nothing} = :default) Return a `GradientConfig` instance based on the type of `f` and type/shape of the input vector `x`. @@ -206,13 +206,13 @@ This constructor does not store/modify `x`. @inline function GradientConfig(f::F, x::AbstractArray{V}, c::Chunk{N} = Chunk(x); - tag::Union{Symbol,Nothing} = :default) where {F,V,N} - # @inline ensures that, e.g., GradientConfig(...; tag = :small) will be well-inferred + tagstyle::Union{Symbol,Nothing} = :default) where {F,V,N} + # @inline ensures that, e.g., GradientConfig(...; tagstyle = :small) will be well-inferred @static if VERSION ≥ v"1.8" - T = @inline maketag(tag, f, V) + T = @inline maketag(tagstyle, f, V) return @noinline GradientConfig(f,x,c,T) else - T = maketag(tag, f, V) + T = maketag(tagstyle, f, V) return GradientConfig(f,x,c,T) end end @@ -239,7 +239,7 @@ struct JacobianConfig{T,V,N,D} <: AbstractConfig{N} end """ - ForwardDiff.JacobianConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x); tag::Union{Symbol,Nothing} = :default) + ForwardDiff.JacobianConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x); tagstyle::Union{Symbol,Nothing} = :default) Return a `JacobianConfig` instance based on the type of `f` and type/shape of the input vector `x`. @@ -260,13 +260,13 @@ This constructor does not store/modify `x`. @inline function JacobianConfig(f::F, x::AbstractArray{V}, c::Chunk{N} = Chunk(x); - tag::Union{Symbol,Nothing} = :default) where {F,V,N} - # @inline ensures that, e.g., JacobianConfig(...; tag = :small) will be well-inferred + tagstyle::Union{Symbol,Nothing} = :default) where {F,V,N} + # @inline ensures that, e.g., JacobianConfig(...; tagstyle = :small) will be well-inferred @static if VERSION ≥ v"1.8" - T = @inline maketag(tag, f, V) + T = @inline maketag(tagstyle, f, V) return @noinline JacobianConfig(f,x,c,T) else - T = maketag(tag, f, V) + T = maketag(tagstyle, f, V) return JacobianConfig(f,x,c,T) end end @@ -281,7 +281,7 @@ function JacobianConfig(f::F, end """ - ForwardDiff.JacobianConfig(f!, y::AbstractArray, x::AbstractArray, chunk::Chunk = Chunk(x); tag::Union{Symbol,Nothing} = :default) + ForwardDiff.JacobianConfig(f!, y::AbstractArray, x::AbstractArray, chunk::Chunk = Chunk(x); tagstyle::Union{Symbol,Nothing} = :default) Return a `JacobianConfig` instance based on the type of `f!`, and the types/shapes of the output vector `y` and the input vector `x`. @@ -303,13 +303,13 @@ This constructor does not store/modify `y` or `x`. y::AbstractArray{Y}, x::AbstractArray{X}, c::Chunk{N} = Chunk(x); - tag::Union{Symbol,Nothing} = :default) where {F,Y,X,N} - # @inline ensures that, e.g., JacobianConfig(...; tag = :small) will be well-inferred + tagstyle::Union{Symbol,Nothing} = :default) where {F,Y,X,N} + # @inline ensures that, e.g., JacobianConfig(...; tagstyle = :small) will be well-inferred @static if VERSION ≥ v"1.8" - T = @inline maketag(tag, f, X) + T = @inline maketag(tagstyle, f, X) return @noinline JacobianConfig(f,y,x,c,T) else - T = maketag(tag, f, X) + T = maketag(tagstyle, f, X) return JacobianConfig(f,y,x,c,T) end end @@ -339,7 +339,7 @@ struct HessianConfig{T,V,N,DG,DJ} <: AbstractConfig{N} end """ - ForwardDiff.HessianConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x); tag::Union{Symbol,Nothing} = :default) + ForwardDiff.HessianConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x); tagstyle::Union{Symbol,Nothing} = :default) Return a `HessianConfig` instance based on the type of `f` and type/shape of the input vector `x`. @@ -362,13 +362,13 @@ This constructor does not store/modify `x`. @inline function HessianConfig(f::F, x::AbstractArray{V}, chunk::Chunk = Chunk(x); - tag::Union{Symbol,Nothing} = :default) where {F,V} - # @inline ensures that, e.g., HessianConfig(...; tag = :small) will be well-inferred + tagstyle::Union{Symbol,Nothing} = :default) where {F,V} + # @inline ensures that, e.g., HessianConfig(...; tagstyle = :small) will be well-inferred @static if VERSION ≥ v"1.8" - T = @inline maketag(tag, f, V) + T = @inline maketag(tagstyle, f, V) return @noinline HessianConfig(f, x, chunk, T) else - T = maketag(tag, f, V) + T = maketag(tagstyle, f, V) return HessianConfig(f, x, chunk, T) end end @@ -383,7 +383,7 @@ function HessianConfig(f::F, end """ - ForwardDiff.HessianConfig(f, result::DiffResult, x::AbstractArray, chunk::Chunk = Chunk(x); tag::Union{Symbol,Nothing} = :default) + ForwardDiff.HessianConfig(f, result::DiffResult, x::AbstractArray, chunk::Chunk = Chunk(x); tagstyle::Union{Symbol,Nothing} = :default) Return a `HessianConfig` instance based on the type of `f`, types/storage in `result`, and type/shape of the input vector `x`. @@ -404,13 +404,13 @@ This constructor does not store/modify `x`. result::DiffResult, x::AbstractArray{V}, chunk::Chunk = Chunk(x); - tag::Union{Symbol,Nothing} = :default) where {F,V} - # @inline ensures that, e.g., HessianConfig(...; tag = :small) will be well-inferred + tagstyle::Union{Symbol,Nothing} = :default) where {F,V} + # @inline ensures that, e.g., HessianConfig(...; tagstyle = :small) will be well-inferred @static if VERSION ≥ v"1.8" - T = @inline maketag(tag, f, V) + T = @inline maketag(tagstyle, f, V) return @noinline HessianConfig(f, result, x, chunk, T) else - T = maketag(tag, f, V) + T = maketag(tagstyle, f, V) return HessianConfig(f, result, x, chunk, T) end end diff --git a/test/JacobianTest.jl b/test/JacobianTest.jl index 5b319af2..02abd8da 100644 --- a/test/JacobianTest.jl +++ b/test/JacobianTest.jl @@ -4,7 +4,7 @@ import Calculus using Test using ForwardDiff -using ForwardDiff: Dual, Tag, SmallTag, JacobianConfig, maketag +using ForwardDiff: Dual, Tag, HashTag, JacobianConfig, maketag using StaticArrays using DiffTests using LinearAlgebra @@ -103,11 +103,11 @@ for f in DiffTests.ARRAY_TO_ARRAY_FUNCS v = f(X) j = ForwardDiff.jacobian(f, X) @test isapprox(j, Calculus.jacobian(x -> vec(f(x)), X, :forward), atol=1.3FINITEDIFF_ERROR) - @testset "$f with chunk size = $c and tag = $(repr(tag))" for c in CHUNK_SIZES, tag in (nothing, Tag, SmallTag) + @testset "$f with chunk size = $c and tag = $(repr(tag))" for c in CHUNK_SIZES, tag in (nothing, Tag, HashTag) if tag == Tag tag = Tag(f, eltype(X)) - elseif tag == SmallTag - tag = SmallTag(f, eltype(X)) + elseif tag == HashTag + tag = HashTag(f, eltype(X)) end cfg = JacobianConfig(f, X, ForwardDiff.Chunk{c}(), tag) From 8a67a860ec2f257ccc341ae6b793def5840a69c6 Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sat, 26 Jul 2025 12:39:39 -0400 Subject: [PATCH 11/13] Remove option --- src/config.jl | 110 +++++++++++++------------------------------------- 1 file changed, 29 insertions(+), 81 deletions(-) diff --git a/src/config.jl b/src/config.jl index ffac7751..4efa1f12 100644 --- a/src/config.jl +++ b/src/config.jl @@ -104,23 +104,13 @@ Base.eltype(cfg::AbstractConfig) = eltype(typeof(cfg)) @inline (chunksize(::AbstractConfig{N})::Int) where {N} = N function maketag(tagstyle::Union{Symbol,Nothing}, f, X) - if tagstyle === :default - @static if VERSION ≥ v"1.11" - return HashTag(f, X) - else - # On ≤1.10, the hash of a type cannot be computed at compile-time, - # making `HashTag(...)` type-unstable, so `Tag(...)` is left as - # as the default. - return Tag(f, X) - end - elseif tagstyle === :type - return Tag(f, X) - elseif tagstyle === :hash + @static if VERSION ≥ v"1.11" return HashTag(f, X) - elseif tagstyle === nothing - return nothing else - throw(ArgumentError("tag may be :default, :type, :hash, or nothing")) + # On ≤1.10, the hash of a type cannot be computed at compile-time, + # making `HashTag(...)` type-unstable, so `Tag(...)` is left as + # as the default. + return Tag(f, X) end end @@ -133,7 +123,7 @@ struct DerivativeConfig{T,D} <: AbstractConfig{1} end """ - ForwardDiff.DerivativeConfig(f!, y::AbstractArray, x::Real; tagstyle::Union{Symbol,Nothing} = :default) + ForwardDiff.DerivativeConfig(f!, y::AbstractArray, x::Real) Return a `DerivativeConfig` instance based on the type of `f!`, and the types/shapes of the output vector `y` and the input value `x`. @@ -153,16 +143,9 @@ This constructor does not store/modify `y` or `x`. """ @inline function DerivativeConfig(f::F, y::AbstractArray{Y}, - x::X; - tagstyle::Union{Symbol,Nothing} = :default) where {F,X<:Real,Y<:Real} - # @inline ensures that, e.g., DerivativeConfig(...; tagstyle = :small) will be well-inferred - @static if VERSION ≥ v"1.8" - T = @inline maketag(tagstyle, f, X) - return @noinline DerivativeConfig(f,y,x,T) - else - T = maketag(tagstyle, f, X) - return DerivativeConfig(f,y,x,T) - end + x::X) where {F,X<:Real,Y<:Real} + T = maketag(f, X) + return DerivativeConfig(f,y,x,T) end function DerivativeConfig(f::F, @@ -186,7 +169,7 @@ struct GradientConfig{T,V,N,D} <: AbstractConfig{N} end """ - ForwardDiff.GradientConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x); tagstyle::Union{Symbol,Nothing} = :default) + ForwardDiff.GradientConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x)) Return a `GradientConfig` instance based on the type of `f` and type/shape of the input vector `x`. @@ -205,16 +188,9 @@ This constructor does not store/modify `x`. """ @inline function GradientConfig(f::F, x::AbstractArray{V}, - c::Chunk{N} = Chunk(x); - tagstyle::Union{Symbol,Nothing} = :default) where {F,V,N} - # @inline ensures that, e.g., GradientConfig(...; tagstyle = :small) will be well-inferred - @static if VERSION ≥ v"1.8" - T = @inline maketag(tagstyle, f, V) - return @noinline GradientConfig(f,x,c,T) - else - T = maketag(tagstyle, f, V) - return GradientConfig(f,x,c,T) - end + c::Chunk{N} = Chunk(x)) where {F,V,N} + T = maketag(f, V) + return GradientConfig(f,x,c,T) end function GradientConfig(f::F, @@ -239,7 +215,7 @@ struct JacobianConfig{T,V,N,D} <: AbstractConfig{N} end """ - ForwardDiff.JacobianConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x); tagstyle::Union{Symbol,Nothing} = :default) + ForwardDiff.JacobianConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x)) Return a `JacobianConfig` instance based on the type of `f` and type/shape of the input vector `x`. @@ -259,16 +235,9 @@ This constructor does not store/modify `x`. """ @inline function JacobianConfig(f::F, x::AbstractArray{V}, - c::Chunk{N} = Chunk(x); - tagstyle::Union{Symbol,Nothing} = :default) where {F,V,N} - # @inline ensures that, e.g., JacobianConfig(...; tagstyle = :small) will be well-inferred - @static if VERSION ≥ v"1.8" - T = @inline maketag(tagstyle, f, V) - return @noinline JacobianConfig(f,x,c,T) - else - T = maketag(tagstyle, f, V) - return JacobianConfig(f,x,c,T) - end + c::Chunk{N} = Chunk(x)) where {F,V,N} + T = maketag(f, V) + return JacobianConfig(f,x,c,T) end function JacobianConfig(f::F, @@ -281,7 +250,7 @@ function JacobianConfig(f::F, end """ - ForwardDiff.JacobianConfig(f!, y::AbstractArray, x::AbstractArray, chunk::Chunk = Chunk(x); tagstyle::Union{Symbol,Nothing} = :default) + ForwardDiff.JacobianConfig(f!, y::AbstractArray, x::AbstractArray, chunk::Chunk = Chunk(x)) Return a `JacobianConfig` instance based on the type of `f!`, and the types/shapes of the output vector `y` and the input vector `x`. @@ -302,16 +271,9 @@ This constructor does not store/modify `y` or `x`. @inline function JacobianConfig(f::F, y::AbstractArray{Y}, x::AbstractArray{X}, - c::Chunk{N} = Chunk(x); - tagstyle::Union{Symbol,Nothing} = :default) where {F,Y,X,N} - # @inline ensures that, e.g., JacobianConfig(...; tagstyle = :small) will be well-inferred - @static if VERSION ≥ v"1.8" - T = @inline maketag(tagstyle, f, X) - return @noinline JacobianConfig(f,y,x,c,T) - else - T = maketag(tagstyle, f, X) - return JacobianConfig(f,y,x,c,T) - end + c::Chunk{N} = Chunk(x)) where {F,Y,X,N} + T = maketag(f, X) + return JacobianConfig(f,y,x,c,T) end function JacobianConfig(f::F, @@ -339,7 +301,7 @@ struct HessianConfig{T,V,N,DG,DJ} <: AbstractConfig{N} end """ - ForwardDiff.HessianConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x); tagstyle::Union{Symbol,Nothing} = :default) + ForwardDiff.HessianConfig(f, x::AbstractArray, chunk::Chunk = Chunk(x)) Return a `HessianConfig` instance based on the type of `f` and type/shape of the input vector `x`. @@ -361,16 +323,9 @@ This constructor does not store/modify `x`. """ @inline function HessianConfig(f::F, x::AbstractArray{V}, - chunk::Chunk = Chunk(x); - tagstyle::Union{Symbol,Nothing} = :default) where {F,V} - # @inline ensures that, e.g., HessianConfig(...; tagstyle = :small) will be well-inferred - @static if VERSION ≥ v"1.8" - T = @inline maketag(tagstyle, f, V) - return @noinline HessianConfig(f, x, chunk, T) - else - T = maketag(tagstyle, f, V) - return HessianConfig(f, x, chunk, T) - end + chunk::Chunk = Chunk(x)) where {F,V} + T = maketag(f, V) + return HessianConfig(f, x, chunk, T) end function HessianConfig(f::F, @@ -383,7 +338,7 @@ function HessianConfig(f::F, end """ - ForwardDiff.HessianConfig(f, result::DiffResult, x::AbstractArray, chunk::Chunk = Chunk(x); tagstyle::Union{Symbol,Nothing} = :default) + ForwardDiff.HessianConfig(f, result::DiffResult, x::AbstractArray, chunk::Chunk = Chunk(x)) Return a `HessianConfig` instance based on the type of `f`, types/storage in `result`, and type/shape of the input vector `x`. @@ -403,16 +358,9 @@ This constructor does not store/modify `x`. @inline function HessianConfig(f::F, result::DiffResult, x::AbstractArray{V}, - chunk::Chunk = Chunk(x); - tagstyle::Union{Symbol,Nothing} = :default) where {F,V} - # @inline ensures that, e.g., HessianConfig(...; tagstyle = :small) will be well-inferred - @static if VERSION ≥ v"1.8" - T = @inline maketag(tagstyle, f, V) - return @noinline HessianConfig(f, result, x, chunk, T) - else - T = maketag(tagstyle, f, V) - return HessianConfig(f, result, x, chunk, T) - end + chunk::Chunk = Chunk(x)) where {F,V} + T = maketag(f, V) + return HessianConfig(f, result, x, chunk, T) end function HessianConfig(f::F, From dd606d7e8e8191e286966e1fca2252310db1344a Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sat, 26 Jul 2025 12:48:12 -0400 Subject: [PATCH 12/13] "Update tests" By removing the option, which is effectively reverting the test changes because now the option is gone and what the tests added was tests that the option worked. Because the tests already have inference testing, even the inference test is unnecessary to keep so we're good. --- test/GradientTest.jl | 14 +++----------- test/HessianTest.jl | 8 +++----- test/JacobianTest.jl | 19 +++++-------------- 3 files changed, 11 insertions(+), 30 deletions(-) diff --git a/test/GradientTest.jl b/test/GradientTest.jl index d9c67a46..4f46c167 100644 --- a/test/GradientTest.jl +++ b/test/GradientTest.jl @@ -6,7 +6,7 @@ import NaNMath using Test using LinearAlgebra using ForwardDiff -using ForwardDiff: Dual, maketag +using ForwardDiff: Dual, Tag using StaticArrays using DiffTests @@ -21,8 +21,7 @@ x = [0.1, 0.2, 0.3] v = f(x) g = [-9.4, 15.6, 52.0] -@testset "Rosenbrock, chunk size = $c and tag = $(repr(maketag(tag, f, eltype(x))))" for c in (1, 2, 3), tag in (nothing, :default, :small) - tag = maketag(tag, f, eltype(x)) +@testset "Rosenbrock, chunk size = $c and tag = $(repr(tag))" for c in (1, 2, 3), tag in (nothing, Tag(f, eltype(x))) cfg = ForwardDiff.GradientConfig(f, x, ForwardDiff.Chunk{c}(), tag) @test eltype(cfg) == Dual{typeof(tag), eltype(x), c} @@ -61,8 +60,7 @@ cfgx = ForwardDiff.GradientConfig(sin, x) v = f(X) g = ForwardDiff.gradient(f, X) @test isapprox(g, Calculus.gradient(f, X), atol=FINITEDIFF_ERROR) - @testset "... with chunk size = $c and tag = $(repr(maketag(tag, f, eltype(x))))" for c in CHUNK_SIZES, tag in (nothing, :default, :small) - tag = maketag(tag, f, eltype(x)) + @testset "... with chunk size = $c and tag = $(repr(tag))" for c in CHUNK_SIZES, tag in (nothing, Tag(f, eltype(x))) cfg = ForwardDiff.GradientConfig(f, X, ForwardDiff.Chunk{c}(), tag) out = ForwardDiff.gradient(f, X, cfg) @@ -142,12 +140,6 @@ end # make sure this is not a source of type instability @inferred ForwardDiff.GradientConfig(f, sx) - if VERSION ≥ v"1.11" - # make sure that `GradientConfig(...; tag = compile-time-constant)` also - # infers well (requires that Base.hash(::Type) is foldable, which is true - # in Julia ≥ 1.11) - @inferred ((f, sx)->ForwardDiff.GradientConfig(f, sx; tag=:small))(f, sx) - end end @testset "exponential function at base zero" begin diff --git a/test/HessianTest.jl b/test/HessianTest.jl index 68b6d103..4c667e5e 100644 --- a/test/HessianTest.jl +++ b/test/HessianTest.jl @@ -5,7 +5,7 @@ import Calculus using Test using LinearAlgebra using ForwardDiff -using ForwardDiff: Dual, maketag +using ForwardDiff: Dual, Tag using StaticArrays using DiffTests @@ -23,8 +23,7 @@ h = [-66.0 -40.0 0.0; -40.0 130.0 -80.0; 0.0 -80.0 200.0] -@testset "running hardcoded test with chunk size = $c and tag = $(repr(maketag(tag, (f, ForwardDiff.gradient), eltype(x))))" for c in HESSIAN_CHUNK_SIZES, tag in (nothing, :default, :small) - tag = maketag(tag, (f, ForwardDiff.gradient), eltype(x)) +@testset "running hardcoded test with chunk size = $c and tag = $(repr(tag))" for c in HESSIAN_CHUNK_SIZES, tag in (nothing, Tag((f,ForwardDiff.gradient), eltype(x))) cfg = ForwardDiff.HessianConfig(f, x, ForwardDiff.Chunk{c}(), tag) resultcfg = ForwardDiff.HessianConfig(f, DiffResults.HessianResult(x), x, ForwardDiff.Chunk{c}(), tag) @@ -69,8 +68,7 @@ for f in DiffTests.VECTOR_TO_NUMBER_FUNCS h = ForwardDiff.hessian(f, X) # finite difference approximation error is really bad for Hessians... @test isapprox(h, Calculus.hessian(f, X), atol=0.02) - @testset "$f with chunk size = $c and tag = $(repr(maketag(tag, (f, ForwardDiff.gradient), eltype(x))))" for c in HESSIAN_CHUNK_SIZES, tag in (nothing, :default, :small) - tag = maketag(tag, (f, ForwardDiff.gradient), eltype(x)) + @testset "$f with chunk size = $c and tag = $(repr(tag))" for c in HESSIAN_CHUNK_SIZES, tag in (nothing, Tag((f,ForwardDiff.gradient), eltype(x))) cfg = ForwardDiff.HessianConfig(f, X, ForwardDiff.Chunk{c}(), tag) resultcfg = ForwardDiff.HessianConfig(f, DiffResults.HessianResult(X), X, ForwardDiff.Chunk{c}(), tag) diff --git a/test/JacobianTest.jl b/test/JacobianTest.jl index 02abd8da..1e52f7fa 100644 --- a/test/JacobianTest.jl +++ b/test/JacobianTest.jl @@ -4,7 +4,7 @@ import Calculus using Test using ForwardDiff -using ForwardDiff: Dual, Tag, HashTag, JacobianConfig, maketag +using ForwardDiff: Dual, Tag, JacobianConfig using StaticArrays using DiffTests using LinearAlgebra @@ -31,8 +31,8 @@ j = [0.8242369704835132 0.4121184852417566 -10.933563142616123 0.169076696546684 0.084538348273342 -2.299173530851733 0.0 0.0 1.0] -for c in (1, 2, 3), tag in (nothing, :small, :default) - tags = (maketag(tag, f, eltype(x)), maketag(tag, f!, eltype(x))) +for c in (1, 2, 3), tags in ((nothing, nothing), + (Tag(f, eltype(x)), Tag(f!, eltype(x)))) println(" ...running hardcoded test with chunk size = $c and tag = $(repr(tags))") cfg = JacobianConfig(f, x, ForwardDiff.Chunk{c}(), tags[1]) ycfg = JacobianConfig(f!, fill(0.0, 4), x, ForwardDiff.Chunk{c}(), tags[2]) @@ -103,11 +103,9 @@ for f in DiffTests.ARRAY_TO_ARRAY_FUNCS v = f(X) j = ForwardDiff.jacobian(f, X) @test isapprox(j, Calculus.jacobian(x -> vec(f(x)), X, :forward), atol=1.3FINITEDIFF_ERROR) - @testset "$f with chunk size = $c and tag = $(repr(tag))" for c in CHUNK_SIZES, tag in (nothing, Tag, HashTag) + @testset "$f with chunk size = $c and tag = $(repr(tag))" for c in CHUNK_SIZES, tag in (nothing, Tag) if tag == Tag tag = Tag(f, eltype(X)) - elseif tag == HashTag - tag = HashTag(f, eltype(X)) end cfg = JacobianConfig(f, X, ForwardDiff.Chunk{c}(), tag) @@ -130,8 +128,7 @@ for f! in DiffTests.INPLACE_ARRAY_TO_ARRAY_FUNCS f!(v, X) j = ForwardDiff.jacobian(f!, fill!(similar(Y), 0.0), X) @test isapprox(j, Calculus.jacobian(x -> (y = fill!(similar(Y), 0.0); f!(y, x); vec(y)), X, :forward), atol=FINITEDIFF_ERROR) - @testset "$(f!) with chunk size = $c and tag = $(repr(maketag(tag, f!, eltype(X))))" for c in CHUNK_SIZES, tag in (nothing, :small, :default) - tag = maketag(tag, f!, eltype(X)) + @testset "$(f!) with chunk size = $c and tag = $(repr(tag))" for c in CHUNK_SIZES, tag in (nothing, Tag(f!, eltype(X))) ycfg = JacobianConfig(f!, fill!(similar(Y), 0.0), X, ForwardDiff.Chunk{c}(), tag) y = fill!(similar(Y), 0.0) @@ -228,12 +225,6 @@ for T in (StaticArrays.SArray, StaticArrays.MArray) # make sure this is not a source of type instability @inferred ForwardDiff.JacobianConfig(f, sx) - if VERSION ≥ v"1.11" - # make sure that `JacobianConfig(...; tag = compile-time-constant)` also - # infers well (requires that Base.hash(::Type) is foldable, which is true - # in Julia ≥ 1.11) - @inferred ((f, sx)->ForwardDiff.JacobianConfig(f, sx; tag=:small))(f, sx) - end end ######### From 5ef986044d00f14fb881893d3d1fc0bd712432bb Mon Sep 17 00:00:00 2001 From: Christopher Rackauckas Date: Sat, 26 Jul 2025 12:49:36 -0400 Subject: [PATCH 13/13] remove docstring changes clean new dispatches out Update src/config.jl Update src/config.jl Update src/config.jl Update src/config.jl Update src/config.jl Update src/config.jl Update src/config.jl Add a preferences version Update src/config.jl Update src/config.jl --- src/config.jl | 89 +++++++------------------------------------------- src/prelude.jl | 5 +++ 2 files changed, 17 insertions(+), 77 deletions(-) diff --git a/src/config.jl b/src/config.jl index 4efa1f12..f95b928d 100644 --- a/src/config.jl +++ b/src/config.jl @@ -104,12 +104,9 @@ Base.eltype(cfg::AbstractConfig) = eltype(typeof(cfg)) @inline (chunksize(::AbstractConfig{N})::Int) where {N} = N function maketag(tagstyle::Union{Symbol,Nothing}, f, X) - @static if VERSION ≥ v"1.11" + if HASHTAG_MODE_ENABLED return HashTag(f, X) else - # On ≤1.10, the hash of a type cannot be computed at compile-time, - # making `HashTag(...)` type-unstable, so `Tag(...)` is left as - # as the default. return Tag(f, X) end end @@ -136,22 +133,12 @@ If `f!` is `nothing` instead of the actual target function, then the returned in 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 `tag` is `:hash`, a small hash-based tag is provided. This tracks perturbation confusion -with similar accuracy, but is much smaller than `tag = :type`, which stores the full type. - This constructor does not store/modify `y` or `x`. """ -@inline function DerivativeConfig(f::F, - y::AbstractArray{Y}, - x::X) where {F,X<:Real,Y<:Real} - T = maketag(f, X) - return DerivativeConfig(f,y,x,T) -end - function DerivativeConfig(f::F, y::AbstractArray{Y}, x::X, - tag::T) where {F,X<:Real,Y<:Real,T} + tag::T = Tag(f, X)) where {F,X<:Real,Y<:Real,T} duals = similar(y, Dual{T,Y,1}) return DerivativeConfig{T,typeof(duals)}(duals) end @@ -181,22 +168,12 @@ If `f` or `tag` is `nothing`, then the returned instance can be used with any ta However, this will reduce ForwardDiff's ability to catch and prevent perturbation confusion (see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). -If `tag` is `:hash`, a small hash-based tag is provided. This tracks perturbation confusion -with similar accuracy, but is much smaller than `tag = :type`, which stores the full type. - This constructor does not store/modify `x`. """ -@inline function GradientConfig(f::F, - x::AbstractArray{V}, - c::Chunk{N} = Chunk(x)) where {F,V,N} - T = maketag(f, V) - return GradientConfig(f,x,c,T) -end - function GradientConfig(f::F, x::AbstractArray{V}, - ::Chunk{N}, - ::T) where {F,V,N,T} + ::Chunk{N} = Chunk(x), + ::T = Tag(f, V)) where {F,V,N,T} seeds = construct_seeds(Partials{N,V}) duals = similar(x, Dual{T,V,N}) return GradientConfig{T,V,N,typeof(duals)}(seeds, duals) @@ -228,22 +205,12 @@ If `f` or `tag` is `nothing`, then the returned instance can be used with any ta However, this will reduce ForwardDiff's ability to catch and prevent perturbation confusion (see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). -If `tag` is `:hash`, a small hash-based tag is provided. This tracks perturbation confusion -with similar accuracy, but is much smaller than `tag = :type`, which stores the full type. - This constructor does not store/modify `x`. """ -@inline function JacobianConfig(f::F, - x::AbstractArray{V}, - c::Chunk{N} = Chunk(x)) where {F,V,N} - T = maketag(f, V) - return JacobianConfig(f,x,c,T) -end - function JacobianConfig(f::F, x::AbstractArray{V}, - ::Chunk{N}, - ::T) where {F,V,N,T} + ::Chunk{N} = Chunk(x), + ::T = Tag(f, V)) where {F,V,N,T} seeds = construct_seeds(Partials{N,V}) duals = similar(x, Dual{T,V,N}) return JacobianConfig{T,V,N,typeof(duals)}(seeds, duals) @@ -263,24 +230,13 @@ If `f!` or `tag` is `nothing`, then the returned instance can be used with any t However, this will reduce ForwardDiff's ability to catch and prevent perturbation confusion (see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). -If `tag` is `:hash`, a small hash-based tag is provided. This tracks perturbation confusion -with similar accuracy, but is much smaller than `tag = :type`, which stores the full type. - This constructor does not store/modify `y` or `x`. """ -@inline function JacobianConfig(f::F, - y::AbstractArray{Y}, - x::AbstractArray{X}, - c::Chunk{N} = Chunk(x)) where {F,Y,X,N} - T = maketag(f, X) - return JacobianConfig(f,y,x,c,T) -end - function JacobianConfig(f::F, y::AbstractArray{Y}, x::AbstractArray{X}, - ::Chunk{N}, - ::T) where {F,Y,X,N,T} + ::Chunk{N} = Chunk(x), + ::T = Tag(f, X)) where {F,Y,X,N,T} seeds = construct_seeds(Partials{N,X}) yduals = similar(y, Dual{T,Y,N}) xduals = similar(x, Dual{T,X,N}) @@ -316,22 +272,12 @@ If `f` or `tag` is `nothing`, then the returned instance can be used with any ta However, this will reduce ForwardDiff's ability to catch and prevent perturbation confusion (see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). -If `tag` is `:hash`, a small hash-based tag is provided. This tracks perturbation confusion -with similar accuracy, but is much smaller than `tag = :type`, which stores the full type. - This constructor does not store/modify `x`. """ -@inline function HessianConfig(f::F, - x::AbstractArray{V}, - chunk::Chunk = Chunk(x)) where {F,V} - T = maketag(f, V) - return HessianConfig(f, x, chunk, T) -end - function HessianConfig(f::F, x::AbstractArray{V}, - chunk::Chunk, - tag) where {F,V} + chunk::Chunk = Chunk(x), + tag = Tag(f, V)) where {F,V} jacobian_config = JacobianConfig(f, x, chunk, tag) gradient_config = GradientConfig(f, jacobian_config.duals, chunk, tag) return HessianConfig(jacobian_config, gradient_config) @@ -350,24 +296,13 @@ If `f` or `tag` is `nothing`, then the returned instance can be used with any ta However, this will reduce ForwardDiff's ability to catch and prevent perturbation confusion (see https://github.com/JuliaDiff/ForwardDiff.jl/issues/83). -If `tag` is `:hash`, a small hash-based tag is provided. This tracks perturbation confusion -with similar accuracy, but is much smaller than `tag = :type`, which stores the full type. - This constructor does not store/modify `x`. """ -@inline function HessianConfig(f::F, - result::DiffResult, - x::AbstractArray{V}, - chunk::Chunk = Chunk(x)) where {F,V} - T = maketag(f, V) - return HessianConfig(f, result, x, chunk, T) -end - function HessianConfig(f::F, result::DiffResult, x::AbstractArray{V}, - chunk::Chunk, - tag) where {F,V} + chunk::Chunk = Chunk(x), + tag = Tag(f, V)) where {F,V} jacobian_config = JacobianConfig((f,gradient), DiffResults.gradient(result), x, chunk, tag) gradient_config = GradientConfig(f, jacobian_config.duals[2], chunk, tag) return HessianConfig(jacobian_config, gradient_config) diff --git a/src/prelude.jl b/src/prelude.jl index 9e037afa..81441f3a 100644 --- a/src/prelude.jl +++ b/src/prelude.jl @@ -1,6 +1,11 @@ const NANSAFE_MODE_ENABLED = @load_preference("nansafe_mode", false) const DEFAULT_CHUNK_THRESHOLD = @load_preference("default_chunk_threshold", 12) +# On ≤1.10, the hash of a type cannot be computed at compile-time, +# making `HashTag(...)` type-unstable, so `Tag(...)` is left as +# as the default. +const HASHTAG_MODE_ENABLED = @load_preference("hashtag_mode", VERSION ≥ v"1.11") + const AMBIGUOUS_TYPES = (AbstractFloat, Irrational, Integer, Rational, Real, RoundingMode) const UNARY_PREDICATES = Symbol[:isinf, :isnan, :isfinite, :iseven, :isodd, :isreal, :isinteger]