Skip to content

Correct the d_discontinuities convention in the solve docstring - #4923

Open
singhharsh1708 wants to merge 6 commits into
SciML:masterfrom
singhharsh1708:d-discontinuities-convention-docs
Open

singhharsh1708 wants to merge 6 commits into
SciML:masterfrom
singhharsh1708:d-discontinuities-convention-docs

Conversation

@singhharsh1708

@singhharsh1708 singhharsh1708 commented Oct 27, 2026 •

Copy link
Copy Markdown
Member

The d_discontinuities entry of the solve docstring now calls the convention left-continuous (f at t_d is the old regime), gives the strict test t > t_d forward and t < t_d backward, and no longer says t >= t_d works.

With t >= t_d, on the driven mode from the issue (40 switches in 2 ns), Tsit5 rejects 914 steps and is still 5.0e-6 off at its steps where t > t_d gives 2.7e-11, and KenCarp4 takes 41 steps without a rejection and is 2.0e-2 off against 1.1e-8.

The strict form is not exact either: the clamped step evaluates its end stages at t + (t_d - t), which can round past t_d (t_d = 0.3 from t = -10.0 gives 0.3000000000000007), and a prevfloat/nextfloat shift has the same problem. Fixed-step RK4 and Tsit5 then end at 11.4167 and 33.64 against an exact 9.7 (dt = 15) with retcode Success, and with dt = 25 each is wrong for 127 of 500 evenly spaced t_d in [-9, 9]; adaptive solves (Tsit5, RK4, KenCarp4, TRBDF2) recover. The docstring says this and recommends flipping a parameter from a DiscreteCallback with tstops = [t_d] when the switch must be exact (0 of 500 wrong). It also fixes the starting-time sentence: the first step begins at nextfloat(tspan[1]) forward and prevfloat(tspan[1]) backward.

The same sentences are on the DiffEqDocs "Common Solver Options" page and SciMLBase's Common_Keywords page; this changes only the docstring. Making the clamped step land exactly on t_d would remove the rounding for every method and belongs in a separate src PR.

Fixes #4908.

AI Disclosure: Claude assisted with this change.

@singhharsh1708

Copy link
Copy Markdown
Member Author

🤖 Automated comment from an AI agent running as @singhharsh1708, not written or reviewed by the account holder.

Independent review (Codex CLI 0.163.0, model gpt-6.1-sol, reasoning effort high): MERGE, risk low (L1). Reviewed head: a2aebc81bbf55c14db2beec1d0351c4b5124a54a.

Full review

VERDICT: MERGE
PR: #4923
Round: 1
Head: a2aebc8
Base: 45ff46a
Slot: 1
Reviewer: Codex CLI 0.163.0 / gpt-6.1-sol
Session: 01a1975a-e5b6-7091-9d52-9f7a130785b1
Reviewed: 2026-11-01T13:49:22Z
Summary: Corrects the documented discontinuity convention and explains the mirrored workaround for backward integration. Analytic probes on head and base support the advice; no blocking defects found.

Risk

low (L1: Only the solve docstring changes; runtime behavior and tests remain unchanged.)

Risk assessment

  • Risk: low (L1: Documentation correction, rated before reading the PR description or conversation.)
  • Blast radius: Guidance for d_discontinuities in lib/DiffEqBase/src/solve.jl:410; no executable changes.
  • Evidence: Analytic Tsit5 probe passed 22/22 assertions on both head and merge-base. CI reports 266 passing checks, 7 failures, and 1 skipped check.
  • Verdict: MERGE
  • Round: 1
  • Blocking priorities: none
  • Merge: merge

Blocking findings

None.

Non-blocking findings

None.

What I ran

Read the supplied PR metadata first, verified the live PR remains open at the specified head, and inspected the linked issue after establishing the risk rating.

Git commands, all exit 0:

git rev-parse HEAD
git merge-base origin/master HEAD
git diff origin/master...HEAD
git diff --check origin/master...HEAD
git diff --name-status origin/master...HEAD
git log -1 --no-merges --format=%B
git status --short

Output confirmed the specified head and base, one modified docstring file, no whitespace errors, and a clean worktree. The newest non-merge commit contains no agent trailers.

Read-only GitHub commands, both exit 0:

gh pr view 4923 --repo SciML/OrdinaryDiffEq.jl --json state,headRefOid,isDraft
gh issue view 4908 --repo SciML/OrdinaryDiffEq.jl --json number,title,body,state

Ran this reviewer-written analytic probe from the head worktree:

../../jrun --project=../../tmp/SciML__OrdinaryDiffEq.jl__4923/head-env ../../tmp/SciML__OrdinaryDiffEq.jl__4923/probe.jl ../../tmp/SciML__OrdinaryDiffEq.jl__4923/head-env

Ran the same probe from the supplied base worktree:

../../jrun --project=../../tmp/SciML__OrdinaryDiffEq.jl__4923/base-env ../../tmp/SciML__OrdinaryDiffEq.jl__4923/probe.jl ../../tmp/SciML__OrdinaryDiffEq.jl__4923/base-env

Both exited 0, using Julia 1.13.1 through the required wrapper. Each isolated environment loaded its worktree’s DiffEqBase, OrdinaryDiffEqCore, and OrdinaryDiffEqTsit5.

The probe integrates a piecewise-constant derivative across t_d = 0.5, with analytic final values ±0.5, using fixed-step Tsit5 with abs(dt) = 0.25. Both runs produced identical results:

forward strict:             error=2.220446049250313e-16 rejects=0
forward nonstrict:          error=0.5811776310249432    rejects=0
forward nonstrict shifted:  error=1.1102230246251565e-16 rejects=0
backward strict:            error=1.6653345369377348e-16 rejects=0
backward nonstrict:         error=0.5811776310249435    rejects=0
backward nonstrict shifted: error=1.1102230246251565e-16 rejects=0
documented discontinuity conventions | 22  22

Starting-time shifts also passed in both directions. This confirms the documented endpoint hazard and the prevfloat/nextfloat workarounds. There were no new or changed test files to copy into the base worktree. Both probes pass because this PR corrects documentation of existing behavior; it introduces no runtime fix requiring fail-before/pass-after evidence. Both worktrees remained clean.

Inspected all supplied failed-job excerpts:

Failed head check Signature and master comparison
ModelingToolkit InterfaceI Equation-orientation isequal assertion; no matching failure in supplied master logs.
SciMLSensitivity Core2 Unexpected passes and finished ReverseDiff tape; matching master signatures.
SciMLSensitivity Core4 Enzyme mi2 !== nothing assertion; matching master signature.
Default GPU GPUCompiler deprecation warning fails @test_nowarn; matching master signature.
NonlinearSolve Core LineSearchException, step length 0.0; matching master signature.
AD LTS Finished ReverseDiff tape; matching master signature.
GPU Same GPUCompiler warning assertion as master.

Compared against master’s CI, integration, and sublibrary logs. The unmatched ModelingToolkit failure remains unexplained; the diff provides no evidence connecting it to this docstring edit. The head documentation build passed.

What I did not verify

Did not reproduce the issue’s full driven-mode experiment, adaptive rejection counts, KenCarp4, or AitkenNeville results. Did not rerun downstream, GPU, AD, MPI, or package-wide suites. No such executable paths change here, so G1–G3 introduce no additional verification requirement.

AUTHOR_GRADE: {"task":"docs","score":5,"difficulty":2,"rounds":1,"author_harness":"unknown","author_model":"unknown","note":"Focused documentation correction supported by implementation inspection and analytic probes passing on both head and base. Requires understanding endpoint stages and reversed integration direction, but changes no runtime behavior."}


🤖 Posted by an AI agent · harness: Claude Code · model: claude-opus-5-5 (coordinating gpt-6.1-sol reviewers)
Review: unreviewed
Session: 01a1975a-e5b6-7091-9d52-9f7a130785b1

@ChrisRackauckas

Copy link
Copy Markdown
Member

🤖 Automated comment from an AI agent running as @ChrisRackauckas — not written or reviewed by Chris.

PR: #4923

Reviewed head: a2aebc8
Raw SHA-256: 206b536fd8f089fce29365f69d6e68e70fa73a694397b00bf878a50869b9b671
Source SHA-256: ea82f28f9312a7c1be98a7618e79dd94e87d67b30ea8751e8bd9af6d612d3c49
Source: standard-review-v1 (harness-log)
Reviewer session: 01a1d256-d6e6-7001-9692-773ff5457018
Slot: 1
Confidence: unknown
Own risk fixed before author rating/discussion: yes

Risk assessment

  • Risk: low (L1)
  • Verdict: CHANGES
  • Reviewed head: a2aebc8

Review report

VERDICT: CHANGES
Round: 1
Head: a2aebc8
Slot: 1
CI: CI is not green (regress:SciMLSensitivity.jl/Core4/1 / Downstream Tests - Core4; ModelingToolkit.jl/InterfaceI/1 / Downstream Tests - InterfaceI)
Session: 01a1d256-d6e6-7001-9692-773ff5457018

Risk

low (L1: solver-keyword docstring only; executable code, tests, dependencies and public signatures are unchanged).

Blocking findings

  • Verdict: CHANGES
  • Round: 1
  • Blocking priorities: P2
  • Deferred P2 issues: None supplied.
  1. [P2] The one-ULP workaround still permits silent wrong answers — lib/DiffEqBase/src/solve.jl:427–429. Confirmed by running the documented forward and mirrored backward prescriptions:

    using OrdinaryDiffEq, OrdinaryDiffEqLowOrderRK
    
    forward = solve(
        ODEProblem((u, p, t) -> t >= 5.0 ? 1.0 : 0.0, 0.0, (-10.0, 10.0)),
        RK4(); dt = 15.0, adaptive = false,
        d_discontinuities = [prevfloat(5.0)])
    
    backward = solve(
        ODEProblem((u, p, t) -> t <= -5.0 ? 1.0 : 0.0, 0.0, (10.0, -10.0)),
        RK4(); dt = -15.0, adaptive = false,
        d_discontinuities = [nextfloat(-5.0)])

    Observed forward.u[end] == 7.5 and backward.u[end] == -7.5; the exact integrals are 5.0 and -5.0. Both return Success with zero rejected steps. Fixed-step Tsit5 gives 39.87065786149661 and -39.87065786149661 for the same cases. The corresponding strict-comparison controls, with discontinuities at the actual thresholds, pass for Euler, RK4 and Tsit5.

    The offset disappears in the step calculation: prevfloat(5.0) - (-10.0) == 15.0, hence RK4 evaluates its endpoint stage at -10.0 + 15.0 == 5.0, on the new side. The mirrored calculation has the same problem. The new recommendation needs qualification for this existing solver limitation. Recommend the strict inequalities, explain that shifted stops are not generally sufficient because stage times can round onto the threshold, and cover the stated workaround with analytic regression cases before presenting it as reliable.

Non-blocking findings

  • lib/DiffEqBase/src/solve.jl:431–432: the starting-time sentence still unconditionally promises nextfloat(tspan[1]), immediately after the new backward-solve advice. A backward starting-time probe observed init(...).t == prevfloat(10.0) == 9.999999999999998, and its exact-integral check passed. Update this existing sentence to specify nextfloat forward and prevfloat backward.

What I ran

  • Reviewer: codex-cli 0.164.0, model gpt-6.1-sol. Read only the checkout, diff, affected tests, latest commit trailers and supplied review/risk instructions. No root AGENTS.md or CLAUDE.md exists in this checkout. The newest commit has no Agent-Harness or Agent-Model trailers.
  • git diff origin/master...HEAD, git diff --check origin/master...HEAD, git rev-parse HEAD, and git status --porcelain: one docstring changed, whitespace check passed, requested head confirmed, checkout clean.
  • Set the requested TMPDIR and preserved the supplied layered JULIA_DEPOT_PATH. Exported the head using git archive HEAD into review scratch. Resolved a scratch test environment from repository dependency/compatibility declarations and local package sources, without modifying the checkout. Pkg.instantiate(; allow_autoprecomp=false) passed. Versions include Julia 1.13.2, OrdinaryDiffEq 7.8.1, DiffEqBase 7.21.4, OrdinaryDiffEqCore 4.19.0, OrdinaryDiffEqLowOrderRK 2.2.6, OrdinaryDiffEqTsit5 2.1.5 and SciMLBase 3.58.0. Scratch Manifest SHA-256: 4769e951ac486275dea3e71e252c60317235d5a745e841c58734ae084df1cf6f.
  • Ran the affected test/InterfaceI/ode_tstops_tests.jl and ode_backwards_test.jl through blind-affected.jl using julia --startup-file=no --compiled-modules=existing --compile=min -O0 --project=../scratch/blind-env ../scratch/blind-affected.jl: 65/65 passed, 18.7 seconds, exit 0. One warning reported that ODEProblemLibrary.prob_ode_linear was undeclared at import time.
  • Ran julia --startup-file=no --compiled-modules=existing --compile=min -O0 --project=../scratch/blind-env ../scratch/blind-probes.jl: backward starting-time checks 2/2 passed; 24 analytic-integral solves across Euler/RK4/Tsit5, both directions, strict/inclusive comparisons and positive/cross-zero intervals yielded 68 passed, 4 failed, 0 errored, 0 broken, exit 1. All four failures are the shifted-stop RK4/Tsit5 cross-zero cases above. Accuracy checks used analytic integrals with rtol=0 and a Float64 roundoff allowance atol=1024eps(20.0), specified before execution. Raw scripts and logs remain in the authorized review scratch directory.
  • Julia jobs ran serially in memory-capped scopes, with one Julia thread and automatic precompilation disabled. The test launcher waited until the supplied load, memory and test-worker limits admitted execution; both test commands have finished.
  • uv run --locked python --version: Python 3.15.0, with warning `--locked` has no effect when used outside of a project. Also used that interpreter for an independent floating-point endpoint calculation, which reproduced the lost offsets in both directions.

What I did not verify

  • No repository Julia Manifest, interpreter pin, uv.lock or Python project exists, so a repository-locked interpreter/dependency environment was unavailable. Julia testing used the repository's compatibility declarations and the generated scratch Manifest.
  • Did not run the entire InterfaceI group, QA, documentation build, or external downstream suites. The CI status above is supplied metadata; I did not inspect CI logs or establish the cause of those failures.
  • Did not read the PR body, comments, reviews, author risk rating, or any earlier same-head review. No GitHub writes, fixes, commits, pushes or checkout edits were made.

AUTHOR_GRADE: {"task":"docs","score":3,"difficulty":2,"rounds":1,"author_harness":"unknown","author_model":"unknown","note":"The strict-comparison explanation agrees with the tested solver behavior and all 65 affected-suite assertions pass. Four analytic probes disprove the unconditional shifted-stop workaround for RK4 and Tsit5 in both directions; the backward starting-time wording also remains inaccurate."}

Head link: a2aebc8


🤖 Posted by an AI agent — harness: codex-cli 0.164.0 · model: gpt-6.1-sol
Conversation: local session 01a1d256-d6e6-7001-9692-773ff5457018 (no session URL)

This branch has not been deployed

No deployments
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.

d_discontinuities docs: t >= t_d does not "also work", and the convention is left-continuous

2 participants