From 29a2609b4efb65c72abc70a8c65c658c0ebc62f5 Mon Sep 17 00:00:00 2001 From: Damien Courteville Date: Mon, 15 Dec 2025 16:41:06 +0100 Subject: [PATCH 1/5] Fix left/right rootfinding for backward propagation --- src/callbacks.jl | 5 ++++- test/callbacks.jl | 24 ++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/src/callbacks.jl b/src/callbacks.jl index bd9c09978..d741248ae 100644 --- a/src/callbacks.jl +++ b/src/callbacks.jl @@ -362,7 +362,10 @@ end function bisection( f, tup, t_forward::Bool, rootfind::SciMLBase.RootfindOpt, abstol, reltol; maxiters = 1000) - if rootfind == SciMLBase.LeftRootFind + # ODE solver convention: right is toward integration direction + # ITP solver convention: right is toward increasing t + # Note: different non-linear solvers may have different convention + if xor(rootfind == SciMLBase.LeftRootFind, !t_forward) solve(IntervalNonlinearProblem{false}(f, tup), InternalITP(), abstol = abstol, reltol = reltol).left diff --git a/test/callbacks.jl b/test/callbacks.jl index 5f4f98e1a..b9f637ace 100644 --- a/test/callbacks.jl +++ b/test/callbacks.jl @@ -102,3 +102,27 @@ function test_find_first_callback(callbacks, int) end test_find_first_callback(callbacks, find_first_integrator); @test test_find_first_callback(callbacks, find_first_integrator).bytes == 0 + +# https://github.com/SciML/DiffEqBase.jl/issues/1233 +@testset "Inexact rootfinding" begin + # function with irrational root (sqrt(2)) + irrational_f(x, p=0.0) = x^2 - 2 + + # Forward integration + is_forward = true + tspan = (1.0, 2.0) + before = DiffEqBase.bisection(irrational_f, tspan, is_forward, SciMLBase.LeftRootFind, 0.0, 1e-14) + after = DiffEqBase.bisection(irrational_f, tspan, is_forward, SciMLBase.RightRootFind, 0.0, 1e-14) + @test irrational_f(before) < 0.0 + @test irrational_f(after) > 0.0 + @test nextfloat(before) == after + + # Backward integration + is_forward = false + tspan = (2.0, 1.0) + before = DiffEqBase.bisection(irrational_f, tspan, is_forward, SciMLBase.LeftRootFind, 0.0, 1e-14) + after = DiffEqBase.bisection(irrational_f, tspan, is_forward, SciMLBase.RightRootFind, 0.0, 1e-14) + @test irrational_f(before) > 0.0 + @test irrational_f(after) < 0.0 + @test nextfloat(after) == before +end From b80e236405ab1bc8d4c2f3a8d27dbaf1ed37dfb2 Mon Sep 17 00:00:00 2001 From: Damien Courteville Date: Mon, 22 Dec 2025 11:11:36 +0100 Subject: [PATCH 2/5] Replace internal ITP with NonlinearSolve ITP --- Project.toml | 2 + ext/DiffEqBaseForwardDiffExt.jl | 66 +------------------------ src/DiffEqBase.jl | 4 +- src/callbacks.jl | 25 +++++----- src/internal_itp.jl | 87 --------------------------------- test/callbacks.jl | 11 +++-- test/internal_rootfinder.jl | 49 ------------------- test/runtests.jl | 1 - 8 files changed, 24 insertions(+), 221 deletions(-) delete mode 100644 src/internal_itp.jl delete mode 100644 test/internal_rootfinder.jl diff --git a/Project.toml b/Project.toml index c4975168e..a118434dd 100644 --- a/Project.toml +++ b/Project.toml @@ -5,6 +5,7 @@ version = "6.192.0" [deps] ArrayInterface = "4fba245c-0d91-5ea0-9b3e-6abc04ee57a9" +BracketingNonlinearSolve = "70df07ce-3d50-431d-a3e7-ca6ddb60ac1e" ConcreteStructs = "2569d6c7-a4a2-43d3-a901-331e8e4be471" DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae" EnzymeCore = "f151be2c-9106-41f4-ab19-57ee4f262869" @@ -66,6 +67,7 @@ DiffEqBaseUnitfulExt = "Unitful" [compat] ArrayInterface = "7.8" +BracketingNonlinearSolve = "1.6.0" CUDA = "5" ChainRulesCore = "1" ConcreteStructs = "0.2.3" diff --git a/ext/DiffEqBaseForwardDiffExt.jl b/ext/DiffEqBaseForwardDiffExt.jl index c5c0b4e26..3006c6f48 100644 --- a/ext/DiffEqBaseForwardDiffExt.jl +++ b/ext/DiffEqBaseForwardDiffExt.jl @@ -6,8 +6,7 @@ using DiffEqBase: Void, FunctionWrappersWrappers, OrdinaryDiffEqTag, AbstractTimeseriesSolution, RecursiveArrayTools, reduce_tup, _promote_tspan, has_continuous_callback import DiffEqBase: hasdualpromote, wrapfun_oop, wrapfun_iip, prob2dtmin, - promote_tspan, ODE_DEFAULT_NORM, - InternalITP, nextfloat_tdir + promote_tspan, ODE_DEFAULT_NORM import SciMLBase: isdualtype, DualEltypeChecker, sse, __sum const dualT = ForwardDiff.Dual{ForwardDiff.Tag{OrdinaryDiffEqTag, Float64}, Float64, 1} @@ -149,67 +148,4 @@ if !hasmethod(nextfloat, Tuple{ForwardDiff.Dual}) end end -# bisection(f, tup::Tuple{T,T}, t_forward::Bool) where {T<:ForwardDiff.Dual} = find_zero(f, tup, Roots.AlefeldPotraShi()) - -# Differentiation of internal solver - -function scalar_nlsolve_ad(prob, alg::InternalITP, args...; kwargs...) - f = prob.f - p = value(prob.p) - - if prob isa IntervalNonlinearProblem - tspan = value(prob.tspan) - newprob = IntervalNonlinearProblem(f, tspan, p; prob.kwargs...) - else - u0 = value(prob.u0) - newprob = NonlinearProblem(f, u0, p; prob.kwargs...) - end - - sol = solve(newprob, alg, args...; kwargs...) - - uu = sol.u - if p isa Number - f_p = ForwardDiff.derivative(Base.Fix1(f, uu), p) - else - f_p = ForwardDiff.gradient(Base.Fix1(f, uu), p) - end - - f_x = ForwardDiff.derivative(Base.Fix2(f, p), uu) - pp = prob.p - sumfun = let f_x′ = -f_x - ((fp, p),) -> (fp / f_x′) * ForwardDiff.partials(p) - end - partials = sum(sumfun, zip(f_p, pp)) - return sol, partials -end - -function SciMLBase.solve( - prob::IntervalNonlinearProblem{uType, iip, - <:ForwardDiff.Dual{T, V, P}}, - alg::InternalITP, args...; - kwargs...) where {uType, iip, T, V, P} - sol, partials = scalar_nlsolve_ad(prob, alg, args...; kwargs...) - return SciMLBase.build_solution(prob, alg, ForwardDiff.Dual{T, V, P}(sol.u, partials), - sol.resid; retcode = sol.retcode, - left = ForwardDiff.Dual{T, V, P}(sol.left, partials), - right = ForwardDiff.Dual{T, V, P}(sol.right, partials)) -end - -function SciMLBase.solve( - prob::IntervalNonlinearProblem{uType, iip, - <:AbstractArray{ - <:ForwardDiff.Dual{T, - V, - P}, - }}, - alg::InternalITP, args...; - kwargs...) where {uType, iip, T, V, P} - sol, partials = scalar_nlsolve_ad(prob, alg, args...; kwargs...) - - return SciMLBase.build_solution(prob, alg, ForwardDiff.Dual{T, V, P}(sol.u, partials), - sol.resid; retcode = sol.retcode, - left = ForwardDiff.Dual{T, V, P}(sol.left, partials), - right = ForwardDiff.Dual{T, V, P}(sol.right, partials)) -end - end diff --git a/src/DiffEqBase.jl b/src/DiffEqBase.jl index b7a3024fb..d477df1da 100644 --- a/src/DiffEqBase.jl +++ b/src/DiffEqBase.jl @@ -113,6 +113,9 @@ Reexport.@reexport using SciMLBase SciMLBase.isfunctionwrapper(x::FunctionWrapper) = true +# Rootfinder for callbacks +using BracketingNonlinearSolve: ITP + import SymbolicIndexingInterface as SII ## Extension Functions @@ -140,7 +143,6 @@ include("utils.jl") include("stats.jl") include("calculate_residuals.jl") include("tableaus.jl") -include("internal_itp.jl") include("dae_initialization.jl") include("callbacks.jl") diff --git a/src/callbacks.jl b/src/callbacks.jl index d741248ae..d0d7184a8 100644 --- a/src/callbacks.jl +++ b/src/callbacks.jl @@ -52,6 +52,8 @@ has_continuous_callback(cb::VectorContinuousCallback) = true has_continuous_callback(cb::CallbackSet) = !isempty(cb.continuous_callbacks) has_continuous_callback(cb::Nothing) = false +isforward(integrator::DEIntegrator) = isone(integrator.tdir) + # Callback handling function get_tmp(integrator::DEIntegrator, callback) @@ -359,20 +361,17 @@ end # always ensures that if r = bisection(f, (x0, x1)) # then either f(nextfloat(r)) == 0 or f(nextfloat(r)) * f(r) < 0 # note: not really using bisection - uses the ITP method -function bisection( - f, tup, t_forward::Bool, rootfind::SciMLBase.RootfindOpt, abstol, reltol; - maxiters = 1000) +function find_root(f, tup, t_forward::Bool, + rootfind::SciMLBase.RootfindOpt, abstol, reltol) + sol = solve(IntervalNonlinearProblem{false}(f, tup), + ITP(), abstol = 0.0, reltol = 0.0) # ODE solver convention: right is toward integration direction # ITP solver convention: right is toward increasing t # Note: different non-linear solvers may have different convention if xor(rootfind == SciMLBase.LeftRootFind, !t_forward) - solve(IntervalNonlinearProblem{false}(f, tup), - InternalITP(), abstol = abstol, - reltol = reltol).left + sol.left else - solve(IntervalNonlinearProblem{false}(f, tup), - InternalITP(), abstol = abstol, - reltol = reltol).right + sol.right end end @@ -434,7 +433,7 @@ function find_callback_time(integrator, callback::ContinuousCallback, counter) sign(zero_func(bottom_t)) * sign_top >= zero(sign_top) && error("Double callback crossing floating pointer reducer errored. Report this issue.") end - Θ = bisection(zero_func, (bottom_t, top_t), isone(integrator.tdir), + Θ = find_root(zero_func, (bottom_t, top_t), isforward(integrator), callback.rootfind, callback.abstol, callback.reltol) integrator.last_event_error = DiffEqBase.value(ODE_DEFAULT_NORM( zero_func(Θ), Θ)) @@ -481,7 +480,7 @@ function find_callback_time(integrator, callback::VectorContinuousCallback, coun bottom_t = integrator.tprev end if callback.rootfind != SciMLBase.NoRootFind && !isdiscrete(integrator.alg) - min_t = isone(integrator.tdir) ? nextfloat(top_t) : prevfloat(top_t) + min_t = isforward(integrator) ? nextfloat(top_t) : prevfloat(top_t) min_event_idx = -1 for idx in 1:length(event_idx) if ArrayInterface.allowed_getindex(event_idx, idx) != 0 @@ -509,8 +508,8 @@ function find_callback_time(integrator, callback::VectorContinuousCallback, coun error("Double callback crossing floating pointer reducer errored. Report this issue.") end - Θ = bisection(zero_func, (bottom_t, top_t), - isone(integrator.tdir), callback.rootfind, + Θ = find_root(zero_func, (bottom_t, top_t), + isforward(integrator), callback.rootfind, callback.abstol, callback.reltol) if integrator.tdir * Θ < integrator.tdir * min_t integrator.last_event_error = DiffEqBase.value(ODE_DEFAULT_NORM( diff --git a/src/internal_itp.jl b/src/internal_itp.jl deleted file mode 100644 index 06f685045..000000000 --- a/src/internal_itp.jl +++ /dev/null @@ -1,87 +0,0 @@ -""" - prevfloat_tdir(x, x0, x1) - -Move `x` one floating point towards x0. -""" -function prevfloat_tdir(x, x0, x1) - x1 > x0 ? prevfloat(x) : nextfloat(x) -end - -function nextfloat_tdir(x, x0, x1) - x1 > x0 ? nextfloat(x) : prevfloat(x) -end - -function max_tdir(a, b, x0, x1) - x1 > x0 ? max(a, b) : min(a, b) -end - -""" -`InternalITP`: A non-allocating ITP method, internal to DiffEqBase for -simpler dependencies. -""" -struct InternalITP - scaled_k1::Float64 - n0::Int -end - -InternalITP() = InternalITP(0.2, 10) - -function SciMLBase.solve(prob::IntervalNonlinearProblem{IP, Tuple{T, T}}, alg::InternalITP, - args...; - maxiters = 1000, kwargs...) where {IP, T} - f = Base.Fix2(prob.f, prob.p) - left, right = minmax(prob.tspan...) # a and b - fl, fr = f(left), f(right) - ϵ = eps(T) - if iszero(fl) - return SciMLBase.build_solution(prob, alg, left, fl; - retcode = ReturnCode.ExactSolutionLeft, left, right) - elseif iszero(fr) - return SciMLBase.build_solution(prob, alg, right, fr; - retcode = ReturnCode.ExactSolutionRight, left, right) - end - span = right - left - k1 = T(alg.scaled_k1) / span - n0 = T(alg.n0) - n_h = exponent(span / (2 * ϵ)) - ϵ_s = ϵ * exp2(n_h + n0) - T0 = zero(fl) - - i = 1 - while i ≤ maxiters - span = right - left - mid = (left + right) / 2 - r = ϵ_s - (span / 2) - - x_f = left + span * (fl / (fl - fr)) # Interpolation Step - - δ = max(k1 * span^2, eps(x_f)) - diff = mid - x_f - - xt = ifelse(δ ≤ abs(diff), x_f + copysign(δ, diff), mid) # Truncation Step - - xp = ifelse(abs(xt - mid) ≤ r, xt, mid - copysign(r, diff)) # Projection Step - yp = f(xp) - yps = yp * sign(fr) - if yps > T0 - right, fr = xp, yp - elseif yps < T0 - left, fl = xp, yp - else - return SciMLBase.build_solution( - prob, alg, xp, yps; retcode = ReturnCode.Success, left = xp, right = xp - ) - end - - i += 1 - ϵ_s /= 2 - - if nextfloat_tdir(left, left, right) == right - return SciMLBase.build_solution( - prob, alg, right, fr; retcode = ReturnCode.FloatingPointLimit, left, right - ) - end - end - return SciMLBase.build_solution(prob, alg, left, fl; retcode = ReturnCode.MaxIters, - left = left, right = right) -end diff --git a/test/callbacks.jl b/test/callbacks.jl index b9f637ace..f8c98d738 100644 --- a/test/callbacks.jl +++ b/test/callbacks.jl @@ -56,6 +56,7 @@ cbs5 = CallbackSet(cbs1, cbs2) # the find callback time aspect, just the inference failure struct EmptyIntegrator u::Vector{Float64} + tdir::Int64 end function DiffEqBase.find_callback_time(integrator::EmptyIntegrator, callback::ContinuousCallback, counter) @@ -65,7 +66,7 @@ function DiffEqBase.find_callback_time(integrator::EmptyIntegrator, callback::VectorContinuousCallback, counter) 1.0 + counter, 0.9 + counter, true, counter end -find_first_integrator = EmptyIntegrator([1.0, 2.0]) +find_first_integrator = EmptyIntegrator([1.0, 2.0], 1) vector_affect! = function (integrator, idx) integrator.u = integrator.u + idx end @@ -111,8 +112,8 @@ test_find_first_callback(callbacks, find_first_integrator); # Forward integration is_forward = true tspan = (1.0, 2.0) - before = DiffEqBase.bisection(irrational_f, tspan, is_forward, SciMLBase.LeftRootFind, 0.0, 1e-14) - after = DiffEqBase.bisection(irrational_f, tspan, is_forward, SciMLBase.RightRootFind, 0.0, 1e-14) + before = DiffEqBase.find_root(irrational_f, tspan, is_forward, SciMLBase.LeftRootFind, 0.0, 1e-14) + after = DiffEqBase.find_root(irrational_f, tspan, is_forward, SciMLBase.RightRootFind, 0.0, 1e-14) @test irrational_f(before) < 0.0 @test irrational_f(after) > 0.0 @test nextfloat(before) == after @@ -120,8 +121,8 @@ test_find_first_callback(callbacks, find_first_integrator); # Backward integration is_forward = false tspan = (2.0, 1.0) - before = DiffEqBase.bisection(irrational_f, tspan, is_forward, SciMLBase.LeftRootFind, 0.0, 1e-14) - after = DiffEqBase.bisection(irrational_f, tspan, is_forward, SciMLBase.RightRootFind, 0.0, 1e-14) + before = DiffEqBase.find_root(irrational_f, tspan, is_forward, SciMLBase.LeftRootFind, 0.0, 1e-14) + after = DiffEqBase.find_root(irrational_f, tspan, is_forward, SciMLBase.RightRootFind, 0.0, 1e-14) @test irrational_f(before) > 0.0 @test irrational_f(after) < 0.0 @test nextfloat(after) == before diff --git a/test/internal_rootfinder.jl b/test/internal_rootfinder.jl deleted file mode 100644 index 82b0632e7..000000000 --- a/test/internal_rootfinder.jl +++ /dev/null @@ -1,49 +0,0 @@ -using DiffEqBase -using DiffEqBase: InternalITP, IntervalNonlinearProblem -using ForwardDiff - -for Rootfinder in (InternalITP,) - rf = Rootfinder() - # From SimpleNonlinearSolve - f = (u, p) -> u * u - p - tspan = (1.0, 20.0) - g = function (p) - probN = IntervalNonlinearProblem{false}(f, typeof(p).(tspan), p) - sol = solve(probN, rf) - return sol.u - end - - for p in (1.0,) #1.1:0.1:100.0 - @test g(p) ≈ sqrt(p) - #@test ForwardDiff.derivative(g, p) ≈ 1 / (2 * sqrt(p)) - end - - # https://github.com/SciML/DiffEqBase.jl/issues/916 - inp = IntervalNonlinearProblem((t, p) -> min(-1.0 + 0.001427344607477125 * t, 1e-9), - (699.0079267259368, 700.6176418816023)) - @test solve(inp, rf).u ≈ 700.6016590257979 - - # Flipped signs & reversed tspan test for bracketing algorithms - f1(u, p) = u * u - p - f2(u, p) = p - u * u - for p in 1:4 - inp1 = IntervalNonlinearProblem(f1, (1.0, 2.0), p) - inp2 = IntervalNonlinearProblem(f2, (1.0, 2.0), p) - inp3 = IntervalNonlinearProblem(f1, (2.0, 1.0), p) - inp4 = IntervalNonlinearProblem(f2, (2.0, 1.0), p) - @test abs.(solve(inp1, rf).u) ≈ sqrt.(p) - @test abs.(solve(inp2, rf).u) ≈ sqrt.(p) - @test abs.(solve(inp3, rf).u) ≈ sqrt.(p) - @test abs.(solve(inp4, rf).u) ≈ sqrt.(p) - end - - # https://github.com/SciML/DifferentialEquations.jl/issues/1087 - # Test with normal and reversed tspan. - # The expected zero is -acos(p), but there is another zero at acos(p) just outside tspan - f(u, p) = cos(u) - p - p = 0.9 - inp1 = IntervalNonlinearProblem(f, (-1.1 * acos(p), 0.9 * acos(p)), p) - inp2 = IntervalNonlinearProblem(f, (0.9 * acos(p), -1.1 * acos(p)), p) - @test solve(inp1, rf).u ≈ -acos(p) - @test solve(inp2, rf).u ≈ -acos(p) -end diff --git a/test/runtests.jl b/test/runtests.jl index 065201189..467f3e0d9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -26,7 +26,6 @@ end @time begin if GROUP == "All" || GROUP == "Core" @time @safetestset "Callbacks" include("callbacks.jl") - @time @safetestset "Internal Rootfinders" include("internal_rootfinder.jl") @time @safetestset "Plot Vars" include("plot_vars.jl") @time @safetestset "Problem Creation Tests" include("problem_creation_tests.jl") @time @safetestset "Export tests" include("export_tests.jl") From 040241b6870f19449f0335d9f45e94724d9cfc5b Mon Sep 17 00:00:00 2001 From: Damien Courteville <44789963+dcourteville@users.noreply.github.com> Date: Mon, 22 Dec 2025 21:34:25 +0100 Subject: [PATCH 3/5] Update Project.toml Co-authored-by: Oscar Smith --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 3c9cdb579..f357b28dd 100644 --- a/Project.toml +++ b/Project.toml @@ -67,7 +67,7 @@ DiffEqBaseUnitfulExt = "Unitful" [compat] ArrayInterface = "7.8" -BracketingNonlinearSolve = "1.6.0" +BracketingNonlinearSolve = "1.6.1" CUDA = "5" ChainRulesCore = "1" ConcreteStructs = "0.2.3" From e147b73e281e049f03fbcb66aed1aaccc2c1ea99 Mon Sep 17 00:00:00 2001 From: Damien Courteville Date: Wed, 31 Dec 2025 18:01:22 +0100 Subject: [PATCH 4/5] Update to BracketingNonlinearSolve 1.6.3 --- Project.toml | 2 +- src/callbacks.jl | 33 ++++++++++++++++----------------- test/callbacks.jl | 8 ++++---- 3 files changed, 21 insertions(+), 22 deletions(-) diff --git a/Project.toml b/Project.toml index f357b28dd..4b20a2828 100644 --- a/Project.toml +++ b/Project.toml @@ -67,7 +67,7 @@ DiffEqBaseUnitfulExt = "Unitful" [compat] ArrayInterface = "7.8" -BracketingNonlinearSolve = "1.6.1" +BracketingNonlinearSolve = "1.6.3" CUDA = "5" ChainRulesCore = "1" ConcreteStructs = "0.2.3" diff --git a/src/callbacks.jl b/src/callbacks.jl index d0d7184a8..efa4e691a 100644 --- a/src/callbacks.jl +++ b/src/callbacks.jl @@ -357,21 +357,23 @@ end event_occurred, interp_index, ts, prev_sign, prev_sign_index, event_idx end -# rough implementation, needs multiple type handling -# always ensures that if r = bisection(f, (x0, x1)) -# then either f(nextfloat(r)) == 0 or f(nextfloat(r)) * f(r) < 0 -# note: not really using bisection - uses the ITP method -function find_root(f, tup, t_forward::Bool, - rootfind::SciMLBase.RootfindOpt, abstol, reltol) + +""" +Find either exact or floating point precision root of `f`. +If the exact root cannot be represented, return closest floating point number depending on `rootfind` + +Assumes that: + - `tup[1] < tup[2]` for a forward integration + - `tup[1] > tup[2]` for a backward integration + - The nonlinear solver return left/right roots in the same order as `tup[1]`/`tup[2]` +""" +function find_root(f, tup, rootfind::SciMLBase.RootfindOpt) sol = solve(IntervalNonlinearProblem{false}(f, tup), ITP(), abstol = 0.0, reltol = 0.0) - # ODE solver convention: right is toward integration direction - # ITP solver convention: right is toward increasing t - # Note: different non-linear solvers may have different convention - if xor(rootfind == SciMLBase.LeftRootFind, !t_forward) - sol.left + if rootfind == SciMLBase.LeftRootFind + return sol.left else - sol.right + return sol.right end end @@ -433,8 +435,7 @@ function find_callback_time(integrator, callback::ContinuousCallback, counter) sign(zero_func(bottom_t)) * sign_top >= zero(sign_top) && error("Double callback crossing floating pointer reducer errored. Report this issue.") end - Θ = find_root(zero_func, (bottom_t, top_t), isforward(integrator), - callback.rootfind, callback.abstol, callback.reltol) + Θ = find_root(zero_func, (bottom_t, top_t), callback.rootfind) integrator.last_event_error = DiffEqBase.value(ODE_DEFAULT_NORM( zero_func(Θ), Θ)) end @@ -508,9 +509,7 @@ function find_callback_time(integrator, callback::VectorContinuousCallback, coun error("Double callback crossing floating pointer reducer errored. Report this issue.") end - Θ = find_root(zero_func, (bottom_t, top_t), - isforward(integrator), callback.rootfind, - callback.abstol, callback.reltol) + Θ = find_root(zero_func, (bottom_t, top_t), callback.rootfind) if integrator.tdir * Θ < integrator.tdir * min_t integrator.last_event_error = DiffEqBase.value(ODE_DEFAULT_NORM( zero_func(Θ), Θ)) diff --git a/test/callbacks.jl b/test/callbacks.jl index 26f38fdb8..8eba5d7f3 100644 --- a/test/callbacks.jl +++ b/test/callbacks.jl @@ -112,8 +112,8 @@ test_find_first_callback(callbacks, find_first_integrator); # Forward integration is_forward = true tspan = (1.0, 2.0) - before = DiffEqBase.find_root(irrational_f, tspan, is_forward, SciMLBase.LeftRootFind, 0.0, 1e-14) - after = DiffEqBase.find_root(irrational_f, tspan, is_forward, SciMLBase.RightRootFind, 0.0, 1e-14) + before = DiffEqBase.find_root(irrational_f, tspan, SciMLBase.LeftRootFind) + after = DiffEqBase.find_root(irrational_f, tspan, SciMLBase.RightRootFind) @test irrational_f(before) < 0.0 @test irrational_f(after) > 0.0 @test nextfloat(before) == after @@ -121,8 +121,8 @@ test_find_first_callback(callbacks, find_first_integrator); # Backward integration is_forward = false tspan = (2.0, 1.0) - before = DiffEqBase.find_root(irrational_f, tspan, is_forward, SciMLBase.LeftRootFind, 0.0, 1e-14) - after = DiffEqBase.find_root(irrational_f, tspan, is_forward, SciMLBase.RightRootFind, 0.0, 1e-14) + before = DiffEqBase.find_root(irrational_f, tspan, SciMLBase.LeftRootFind) + after = DiffEqBase.find_root(irrational_f, tspan, SciMLBase.RightRootFind) @test irrational_f(before) > 0.0 @test irrational_f(after) < 0.0 @test nextfloat(after) == before From 2c8003b82c9120ba72de1469fb8301194792833e Mon Sep 17 00:00:00 2001 From: Damien Courteville <44789963+dcourteville@users.noreply.github.com> Date: Fri, 2 Jan 2026 16:16:59 +0100 Subject: [PATCH 5/5] Downgrade BracketingNonlinearSolve to version 1.6.2 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 4b20a2828..274e83807 100644 --- a/Project.toml +++ b/Project.toml @@ -67,7 +67,7 @@ DiffEqBaseUnitfulExt = "Unitful" [compat] ArrayInterface = "7.8" -BracketingNonlinearSolve = "1.6.3" +BracketingNonlinearSolve = "1.6.2" CUDA = "5" ChainRulesCore = "1" ConcreteStructs = "0.2.3"