Fix tstop overshoot error and super dense time event triggers#2869
Conversation
Updated Implementation with Simplified LogicRefined the approach based on feedback to make it much cleaner: Simplified Flag DetectionClean Separation of Concerns
Key InsightWhen This eliminates all the floating-point precision issues while maintaining the existing code structure and flow. |
✅ Comprehensive Test Suite AddedAdded extensive tests to Test Coverage
Key Assertions
These tests specifically target the scenarios mentioned in the issue and ensure robust handling of all edge cases. |
✅ Test Dependencies FixedAdded proper imports to Dependencies Added:
Test Status:
The comprehensive test suite will run as part of the "Tstops Tests" in the Interface test group. |
|
I really don't think this is a good AI issue. This is very tricky code and I would appreciate it remaining correct. |
|
This is actually a rather simple solution to it. It didn't come up with the solution though, it went down some weird floating point rabbit hole. This solution of just using a flag is rather simple |
9484879 to
c168d78
Compare
917b03f to
9daadcd
Compare
9daadcd to
1f5cd7c
Compare
99b1e5b to
43567d0
Compare
43567d0 to
982989f
Compare
Fix for Failing TestsThe loop condition in
The FixChanged line 612 in This restores the original loop behavior while preserving the tstop flag mechanism. Tests Verified Locally
🤖 Generated with Claude Code |
f2c4a26 to
cddbea0
Compare
9b8a7ac to
6dc77d3
Compare
Fixes SciML#2752 where StaticArrays trigger "Integrator stepped past tstops" errors due to floating-point precision differences in tstop distance calculations between StaticArrays and regular Arrays. Instead of relying solely on floating-point epsilon correction to snap to tstops, this adds a flag-based mechanism: - Add `next_step_tstop` flag and `tstop_target` to ODEIntegrator - `modify_dt_for_tstops!` sets the flag when dt is reduced for a tstop - `handle_tstop_step!` skips perform_step! for tiny dt (< eps(t)) - `fixed_t_for_tstop_error!` snaps exactly to tstop_target when flagged, with the original 100*eps correction retained as fallback - `solve!` loop uses the flag to exit the inner loop after tstop snap - Prevent duplicate tstops at tspan end by using strict `<` in filtering - Restore original dt before step controller when snapping to tstop Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com>
6dc77d3 to
95dacaa
Compare
Rebased and cleaned upRebased the branch onto current master (was 283 commits behind). The 14 iterative commits have been squashed into a single clean commit. Changes from the previous version:
Test results (local, Julia 1.12):
|
…ibility DDEIntegrator in DelayDiffEq.jl doesn't have the new next_step_tstop and tstop_target fields. Replace direct field access with accessor functions that fall back to safe defaults for non-ODEIntegrator types. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
CI Fix: DDEIntegrator compatibility (commit 937dbdf)CI showed 8 failures caused by our changes — all with the same root cause: Fix: Added accessor functions with fallback defaults:
This way DDEIntegrator gracefully falls back to the old behavior (no flag-based snapping) without requiring a companion PR to DelayDiffEq.jl. Remaining CI failures (all pre-existing on master):
|
The flag-based tstop snapping covers all cases where we're stepping towards a tstop. The old 100*eps correction is dead code since modify_dt_for_tstops! always sets the flag when dt is shortened. Co-Authored-By: Chris Rackauckas <accounts@chrisrackauckas.com> Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Cool. Got it to now just all be a state machine instead of any floating point handling. 🤞 that passes all tests, but everything I checked worked. |
Summary
Fixes issue #2752 where
StaticArraystrigger "Something went wrong. Integrator stepped past tstops but the algorithm was dtchangeable" errors due to tiny floating-point precision differences in tstop distance calculations.Root Cause Analysis
The bug occurs because:
abs(tdir_tstop - tdir_t)produce slightly different results (differences ~1e-15 to 1e-21)100*eps(t)floating-point correction thresholdhandle_tstop!detects overshoot whendtchangeable=trueand throws errorSolution: Flag-Based Exact Tstop Handling
Instead of relying on floating-point precision for tstop stepping, this implementation uses a flag-based approach:
Key Changes:
Add Flag Mechanism
next_step_tstop::Bool- flag when next step should land exactly on tstoptstop_target::tType- exact target time for tstop landingEnhanced
modify_dt_for_tstops!next_step_tstop = trueand stores exact target intstop_targeteps(t)), sets minimal non-zero dtNew
handle_tstop_step!Functionperform_step!whennext_step_tstop = trueModified Stepping Loop
next_step_tstop ? handle_tstop_step!() : perform_step!()Algorithm Flow Comparison
Before (Problematic):
After (Robust):
Advantages
✅ Eliminates Root Cause: No floating-point precision dependence
✅ Exact Tstop Landing: Guaranteed precision regardless of array type
✅ Performance: Avoids wasted computation on tiny steps
✅ Backward Compatible: No changes to existing API
✅ Robust: Works identically for StaticArrays and regular Arrays
✅ Clean Architecture: Clear separation of tstop vs normal stepping logic
Files Modified
lib/OrdinaryDiffEqCore/src/integrators/type.jl- Add integrator fieldslib/OrdinaryDiffEqCore/src/solve.jl- Initialize fields and modify stepping looplib/OrdinaryDiffEqCore/src/integrators/integrator_utils.jl- Enhanced tstop logictest/tstop_flag_tests.jl- Test framework for new functionalityTest Plan
Breaking Changes
None. This is a pure enhancement that maintains existing API compatibility.
🤖 Generated with Claude Code