Skip to content
This repository was archived by the owner on May 12, 2026. It is now read-only.
This repository was archived by the owner on May 12, 2026. It is now read-only.

RightRootFind and LeftRootFind have opposite behavior in some cases #1290

@DaniGlez

Description

@DaniGlez

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).

  • Output of versioninfo()
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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions