diff --git a/Project.toml b/Project.toml index acc1fb4..1f59660 100644 --- a/Project.toml +++ b/Project.toml @@ -7,6 +7,7 @@ authors = ["Chris Elrod and contributors"] PrecompileTools = "aea7be01-6a6a-4083-8856-8a6e6704d82a" [compat] +ForwardDiff = "0.10, 1" PrecompileTools = "1.0.1" SafeTestsets = "0.1" SciMLTesting = "2.2" @@ -15,10 +16,11 @@ Test = "1.10" julia = "1.10" [extras] +ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" SafeTestsets = "1bc83da4-3b8d-516f-aca4-4fe02f6d838f" SciMLTesting = "09d9d899-5365-40a9-917a-5f67fddea283" StableRNGs = "860ef19b-820b-49d6-a774-d7a799459cd3" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" [targets] -test = ["SafeTestsets", "SciMLTesting", "StableRNGs", "Test"] +test = ["ForwardDiff", "SafeTestsets", "SciMLTesting", "StableRNGs", "Test"] diff --git a/src/search_properties.jl b/src/search_properties.jl index d554045..c01d84b 100644 --- a/src/search_properties.jl +++ b/src/search_properties.jl @@ -47,7 +47,7 @@ const _AUTO_LINEAR_REL_TOLERANCE = 1.0e-3 for k in 1:9 kk = 1 + (k * nm1) ÷ 10 expected = v1 + (kk - 1) / nm1 * span - rel_err = Float64(abs(v[kk] - expected) / abs_span) + rel_err = abs(v[kk] - expected) / abs_span rel_err > max_err && (max_err = rel_err) end return max_err @@ -83,12 +83,12 @@ function _validated_uniform(v::AbstractVector{<:Real}) n < 2 && return false @inbounds begin v1, vn = v[1], v[n] - span = Float64(vn - v1) + span = vn - v1 (iszero(span) || !isfinite(span)) && return false nm1 = n - 1 for i in 2:nm1 expected = v1 + (i - 1) / nm1 * span - abs(Float64(v[i] - expected)) > _UNIFORM_REL_TOLERANCE * abs(span) && + abs(v[i] - expected) > _UNIFORM_REL_TOLERANCE * abs(span) && return false end end @@ -110,8 +110,8 @@ end @inbounds begin v1, vn = v[1], v[n] (v1 <= 0 || vn <= 0 || !isfinite(v1) || !isfinite(vn)) && return false - log_v1 = log(Float64(v1)) - log_vn = log(Float64(vn)) + log_v1 = log(v1) + log_vn = log(vn) span = log_vn - log_v1 (iszero(span) || !isfinite(span)) && return false abs_span = abs(span) @@ -121,7 +121,7 @@ end vk = v[kk] (vk <= 0 || !isfinite(vk)) && return false expected = log_v1 + (kk - 1) / nm1 * span - rel_err = abs(log(Float64(vk)) - expected) / abs_span + rel_err = abs(log(vk) - expected) / abs_span rel_err > tol && return false end end diff --git a/test/core_tests.jl b/test/core_tests.jl index 3366964..ee149f8 100644 --- a/test/core_tests.jl +++ b/test/core_tests.jl @@ -645,6 +645,53 @@ end @test p_nonuniform.inv_step == 0.0 end +@safetestset "SearchProperties with ForwardDiff.Dual knots" begin + using FindFirstFunctions + using FindFirstFunctions: SearchProperties, Auto, searchsorted_last, searchsorted_first + using ForwardDiff + + # `ForwardDiff.Dual <: Real`, so a Dual-valued knot vector — as produced + # when an interpolation is reconstructed inside `ForwardDiff` and the + # knots are promoted to Duals — dispatches to the `<:Real` probe. The + # probes must not force a `Float64(::Dual)` conversion (which errors); + # they must classify from the primal values and keep partials intact. + ForwardDiff.derivative(2.0) do p + t = collect(range(0.0, 1.0; length = 15)) .* p # linear Dual knots + props = SearchProperties(t) + @test props.has_props + @test props.is_linear # linear in index + @test props.is_uniform # evenly spaced + # first_val / inv_step carry the derivative w.r.t. p. + @test ForwardDiff.value(props.first_val) == 0.0 + # sum(t) = 7.5p, so d/dp = 7.5; exercise a value that depends on the + # branch chosen from the probe result. + props.is_linear ? sum(t) : 2 * sum(t) + end == 7.5 + + # Non-uniform Dual knots must probe without error and report not-uniform. + ForwardDiff.derivative(3.0) do p + t = (Float64[0, 1, 2, 4, 8, 16, 20, 21, 22, 40, 41, 100]) .* p + props = SearchProperties(t) + @test props.has_props + @test !props.is_uniform + sum(t) + end + + # Full search path on uniform Dual knots exercises the closed-form + # UniformStep kernel (`Auto` resolves to `KIND_UNIFORM_STEP`). It must + # return the same index as `Base` on the primal values. + ForwardDiff.derivative(2.0) do p + t = collect(range(0.0, 10.0; length = 40)) .* p + tv = map(ForwardDiff.value, t) + auto = Auto(t) + for q in (t[1], t[7], t[end], 5.0 * p, -1.0 * p, 11.0 * p) + @test searchsorted_last(auto, t, q) == searchsortedlast(tv, ForwardDiff.value(q)) + @test searchsorted_first(auto, t, q) == searchsortedfirst(tv, ForwardDiff.value(q)) + end + sum(t) + end +end + @safetestset "Auto{T} parametric + props-aware UniformStep kernel" begin using FindFirstFunctions using FindFirstFunctions: