The MWE code provided below produces the following output in 6.210:
Initial conditions: [-1.4674485752627353, -0.2688496895629546]
Condition value at crossing: [-2.6373123662081457, -1.0240241303143287e-16]
Since I'm using RightRootFind, my expectation is that the condition at the crossing time is either zero or small but positive (because the condition was originally negative). If the option is switched to LeftRootFinding instead of RightRootFinding, it inverts:
Initial conditions: [-1.4674485752627353, -0.2688496895629546]
Condition value at crossing: [-2.6373123662081457, 2.5053774899669434e-18]
Installing DiffEqBase@6.204.0 reverses the behavior back to the intended one (but see note at the bottom of my issue report!):
# RightRootFind
Initial conditions: [-1.4674485752627353, -0.2688496895629546]
Condition value at crossing: [-2.6373123662081457, 2.5053774899669434e-18]
# LeftRootFind
Initial conditions: [-1.4674485752627353, -0.2688496895629546]
Condition value at crossing: [-2.6373123662081457, -1.0240241303143287e-16]
Minimal Reproducible Example 👇
using OrdinaryDiffEqTsit5, OrdinaryDiffEqCore, SciMLBase, LinearAlgebra
# Lotka-Volterra equations
function lotka_volterra!(du, u, p, t)
α, β, δ, γ = 1.5, 1.0, 3.0, 1.0
x, y = u
du[1] = α * x - β * x * y
du[2] = δ * x * y - γ * y
return nothing
end
coeffs1 = [0.36736650867873943, -1.8348150839414747]
coeffs2 = [-0.7413123758633928, 0.47246268630043825]
u0 = [1.0, 1.0]
tspan = (0.0, 20.0)
# Record initial signs
initial_conditions = [dot(coeffs1, u0), dot(coeffs2, u0)]
initial_signs = sign.(initial_conditions)
# VCC condition: two linear functions of state
function vcc_condition!(out, u, t, integrator)
out[1] = dot(coeffs1, u)
out[2] = dot(coeffs2, u)
return nothing
end
function vcc_affect!(integrator, event_index)
u = integrator.u
if event_index == 2
println("Condition value at crossing: ", [dot(coeffs1, u), dot(coeffs2, u)])
terminate!(integrator)
end
return nothing
end
cb = VectorContinuousCallback(
vcc_condition!,
vcc_affect!,
2;
rootfind=SciMLBase.RightRootFind,
)
println("Initial conditions: ", initial_conditions)
prob = ODEProblem(lotka_volterra!, u0, tspan)
sol = solve(prob, Tsit5(); callback=cb, abstol=1e-10, reltol=1e-10)
nothing
Environment (please complete the following information):
Just ] add OrdinaryDiffEqTsit5, OrdinaryDiffEqCore, SciMLBase, LinearAlgebra, DiffEqBase@6.204 on a temporary env (remove the @6.204 pin for experimenting the buggy behavior).
Julia Version 1.11.9
Commit 53a02c0720c (2026-02-06 00:27 UTC)
Build Info:
Official https://julialang.org/ release
Platform Info:
OS: Linux (x86_64-linux-gnu)
CPU: 16 × AMD Ryzen 7 7800X3D 8-Core Processor
WORD_SIZE: 64
LLVM: libLLVM-16.0.6 (ORCJIT, znver4)
Threads: 8 default, 0 interactive, 4 GC (on 16 virtual cores)
Additional notes
- I think the bug might not necessarily be reproducible on a different machine due to arch-dependant FP behavior (I've seen an analogous manifestation of this happen in an EC2 instance while running correctly on my machine due to O(eps) variations). A different MWE can nevertheless be obtained (at least hopefully!) for your machine through the following stochastic MWE generator: https://gist.github.com/DaniGlez/488e0d7c6cdeb12e2f6d34b8c483e5f2
- While this specific instance is not present in 6.204.0, this older version might not be correct on a different set of instances (running the same script as in the other note produces some different instances).
The MWE code provided below produces the following output in 6.210:
Since I'm using RightRootFind, my expectation is that the condition at the crossing time is either zero or small but positive (because the condition was originally negative). If the option is switched to LeftRootFinding instead of RightRootFinding, it inverts:
Installing DiffEqBase@6.204.0 reverses the behavior back to the intended one (but see note at the bottom of my issue report!):
Minimal Reproducible Example 👇
Environment (please complete the following information):
Just
] add OrdinaryDiffEqTsit5, OrdinaryDiffEqCore, SciMLBase, LinearAlgebra, DiffEqBase@6.204on a temporary env (remove the@6.204pin for experimenting the buggy behavior).versioninfo()Additional notes