Skip to content

Fix SDE/RODE algorithm ambiguity in OrdinaryDiffEqCore extension#567

Merged
isaacsas merged 2 commits intoSciML:masterfrom
ChrisRackauckas-Claude:fix-sde-algorithm-ambiguity
Mar 19, 2026
Merged

Fix SDE/RODE algorithm ambiguity in OrdinaryDiffEqCore extension#567
isaacsas merged 2 commits intoSciML:masterfrom
ChrisRackauckas-Claude:fix-sde-algorithm-ambiguity

Conversation

@ChrisRackauckas-Claude
Copy link
Copy Markdown
Contributor

Summary

OrdinaryDiffEqCore now defines __init with a Union that includes all four algorithm types:

  • OrdinaryDiffEqAlgorithm
  • DAEAlgorithm
  • StochasticDiffEqAlgorithm
  • StochasticDiffEqRODEAlgorithm

This creates an ambiguity with JumpProcesses' __init(::AbstractJumpProblem, ::DEAlgorithm) when calling with a JumpProblem and any of these algorithm types.

The Error

The error (seen in CI for PR #565):

MethodError: kwcall(..., ::typeof(SciMLBase.__init), ::JumpProblem{...}, ::Tsit5{...}) is ambiguous.

Candidates:
  kwcall(::NamedTuple, ::typeof(SciMLBase.__init), _jump_prob::SciMLBase.AbstractJumpProblem{P}, alg::SciMLBase.AbstractDEAlgorithm) where P
    @ JumpProcesses src/solve.jl:35
  kwcall(::NamedTuple, ::typeof(SciMLBase.__init), prob::Union{...AbstractJumpProblem...}, alg::Union{OrdinaryDiffEqAlgorithm, StochasticDiffEqAlgorithm, StochasticDiffEqRODEAlgorithm, DAEAlgorithm})
    @ OrdinaryDiffEqCore

Fix

Update the extension to include StochasticDiffEqAlgorithm and StochasticDiffEqRODEAlgorithm in the Union type, matching the exact signature from OrdinaryDiffEqCore. This makes the extension method more specific than both ambiguous candidates.


cc @isaacsas @ChrisRackauckas

ChrisRackauckas and others added 2 commits March 19, 2026 09:09
OrdinaryDiffEqCore now defines __init with:
  Union{OrdinaryDiffEqAlgorithm, DAEAlgorithm, StochasticDiffEqAlgorithm, StochasticDiffEqRODEAlgorithm}

This causes an ambiguity with JumpProcesses' __init(::AbstractJumpProblem, ::DEAlgorithm).

The fix adds StochasticDiffEqAlgorithm and StochasticDiffEqRODEAlgorithm to the
extension's Union type to match the exact signature from OrdinaryDiffEqCore.
@isaacsas
Copy link
Copy Markdown
Member

@ChrisRackauckas I thought StochasticDiffEq handled init for SDEs + jumps and JumpProcesses only handled ODEs + jumps or pure jumps?

@isaacsas
Copy link
Copy Markdown
Member

(Note I handled the ambiguity error with OrdinaryDiffEqCore previously.)

@ChrisRackauckas
Copy link
Copy Markdown
Member

No this is right. Since they are both the same init now it should just delegate to jump here, and then in the jump init it will delegate down to the problem. ODE's init is now involved even in SDEs, so it's not separate like that anymore. They are the same integrator.

@isaacsas
Copy link
Copy Markdown
Member

isaacsas commented Mar 19, 2026

But doesn't https://github.com/SciML/OrdinaryDiffEq.jl/blob/6359ecae94193ca2d9ee407ee5de9653aae1e708/lib/StochasticDiffEqCore/src/solve.jl#L92 already handle a JumpProblem with an SDE algorithm? Why add a dispatch for the AbstractJumpProblem case here. It won't ever actually get called for JumpProblem over SDEProblem would it (since the StochasticDiffEqCore version is more specific).

@isaacsas isaacsas merged commit 2d0ef5a into SciML:master Mar 19, 2026
11 checks passed
@isaacsas
Copy link
Copy Markdown
Member

isaacsas commented Mar 19, 2026

I think in practice this will never actually be called for jumps + SDEs, but I don't think it hurts either so I'll merge it.

@ChrisRackauckas
Copy link
Copy Markdown
Member

No this is the one that actually needs to get called so this gives it higher precedence. The SDE init handles regular jumps for some algorithms, like tau leaping, directly, but delegates here for the building of the irregular jump callbacks. The SDE init assumes that the irregular jump callbacks are already generated. So this needs to have higher precedence so that it does this to build the irregular jumps (ignores the regular jumps) and tag them to the SDE problem and then goes into the SDE init which handles the other jumps. If https://github.com/SciML/OrdinaryDiffEq.jl/blob/6359ecae94193ca2d9ee407ee5de9653aae1e708/lib/StochasticDiffEqCore/src/solve.jl#L92-L98 takes precedence, i.e. if the prob is split there into two dispatches, then it would go straight to the SDE part and ignore the irregular jumps. So this dispatch should be called given the other one is union SDE and jump and this is not, and this change makes it unambiguously a stricter dispatch.

@isaacsas
Copy link
Copy Markdown
Member

With the latest StochasticDiffEq (6.100) the following

using StochasticDiffEq, JumpProcesses

# SDE: dX = -X dt + 0.1 dW
f(u, p, t) = -u
g(u, p, t) = 0.1

# Constant rate jump
rate(u, p, t) = 1.0
affect!(integrator) = (integrator.u += 0.5)
crj = ConstantRateJump(rate, affect!)

sde_prob = SDEProblem(f, g, 1.0, (0.0, 1.0))
jprob = JumpProblem(sde_prob, Direct(), crj)

# Check which __init is called
m = which(DiffEqBase.__init, (typeof(jprob), typeof(EM())))
println("Dispatches to: ", m.module, " in ", m.file)

gives

Dispatches to: StochasticDiffEq in /Users/isaacsas/.julia/packages/StochasticDiffEq/NR3Sa/src/solve.jl

A print statement added to the __init in this PR is never displayed when calling init(jprob, EM(); dt = 1.0).

@ChrisRackauckas
Copy link
Copy Markdown
Member

Is that with this PR? And that version that is dispatching there, are the integrator.ops.callbacks missing the jump callbacks?

@isaacsas
Copy link
Copy Markdown
Member

No because the __sde_init adds the callbacks itself.

@ChrisRackauckas
Copy link
Copy Markdown
Member

I spawned an agent to step through it. I thought I had a call first to the jump library to add the callbacks, but if the StochasticDiffEq code is adding the callbacks then it should split the prob there.

@ChrisRackauckas
Copy link
Copy Markdown
Member

The right test is to look at the integrator object and see if the path has the jump callback made once: if SDE and this both do it, you'll double the jump callback so the length of integrator.opts.callbacks should be a good test for if this is done correctly.

@ChrisRackauckas-Claude
Copy link
Copy Markdown
Contributor Author

Callback duplication test results

Following Chris's suggestion to check integrator.opts.callback length, I added a test in test/callbacks.jl verifying that jump callbacks are added exactly once when using SDE algorithms with JumpProblems.

Test setup

  • ConstantRateJump + SDEProblem + EM(): checks length(integrator.opts.callback.discrete_callbacks) == 1
  • VariableRateJump + SDEProblem + EM(): checks length(integrator.opts.callback.continuous_callbacks) == 1

Results

Both tests pass — no duplication of jump callbacks. The full test suite (52 callback tests) passes cleanly.

Commit: 42e4102

Note: As @isaacsas observed, for SDE algorithms the dispatch currently goes through StochasticDiffEq's __init (not the JumpProcesses extension), but the callbacks are still added exactly once. The extension dispatch provides the ambiguity fix and would correctly route through JumpProcesses if StochasticDiffEq's more specific dispatch were removed.

ChrisRackauckas-Claude pushed a commit to ChrisRackauckas-Claude/JumpProcesses.jl that referenced this pull request Mar 20, 2026
Adds a regression test for PR SciML#567 that checks integrator.opts.callback
to ensure jump callbacks appear exactly once when using SDE algorithms
with JumpProblems. Tests both ConstantRateJump (discrete callback) and
VariableRateJump (continuous callback) with EM() solver.

Co-Authored-By: Chris Rackauckas <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
ChrisRackauckas added a commit that referenced this pull request Mar 20, 2026
…duplication-test

Add test for SDE+jump callback duplication (PR #567 follow-up)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants