Skip to content

Commit 3611e4a

Browse files
Make error check more compatible with SciML interface
1 parent 25073a5 commit 3611e4a

File tree

1 file changed

+47
-29
lines changed

1 file changed

+47
-29
lines changed

src/integrator_interface.jl

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,13 @@ function set_proposed_dt!(i::DEIntegrator, dt)
168168
error("set_proposed_dt!: method has not been implemented for the integrator")
169169
end
170170

171+
"""
172+
get_tdir(i::DEIntegrator)
173+
174+
Get the time direction of the integrator. Should return 1 or -1 with the same type as the time type of the integrator.
175+
"""
176+
get_tdir(i::DEIntegrator) = i.tdir
177+
171178
"""
172179
savevalues!(integrator::DEIntegrator,
173180
force_save=false) -> Tuple{Bool, Bool}
@@ -406,6 +413,15 @@ function get_sol(integrator::DEIntegrator)
406413
return integrator.sol
407414
end
408415

416+
"""
417+
get_retcode(integrator::DEIntegrator)
418+
419+
Get the return code of the integrator.
420+
"""
421+
function get_retcode(integrator::DEIntegrator)
422+
return integrator.sol.retcode
423+
end
424+
409425
### Addat isn't a real thing. Let's make it a real thing Gretchen
410426

411427
function addat!(a::AbstractArray, idxs, val = nothing)
@@ -564,18 +580,27 @@ end
564580

565581
last_step_failed(integrator::DEIntegrator) = false
566582

583+
# Standard error message for classical PID type controllers
584+
function controller_message_on_dtmin_error(integrator::DEIntegrator)
585+
if isdefined(integrator, :EEst)
586+
return ", and step error estimate = $(integrator.EEst)"
587+
else
588+
return ""
589+
end
590+
end
591+
567592
"""
568593
check_error(integrator)
569594
570595
Check state of `integrator` and return one of the
571596
[Return Codes](https://docs.sciml.ai/DiffEqDocs/stable/basics/solution/#retcodes)
572597
"""
573598
function check_error(integrator::DEIntegrator)
574-
if integrator.sol.retcode (ReturnCode.Success, ReturnCode.Default)
575-
return integrator.sol.retcode
599+
if get_retcode(integrator) (ReturnCode.Success, ReturnCode.Default)
600+
return get_retcode(integrator)
576601
end
577602
opts = integrator.opts
578-
verbose = opts.verbose
603+
verbose = hasproperty(opts, :verbose) && opts.verbose
579604
# This implementation is intended to be used for ODEIntegrator and
580605
# SDEIntegrator.
581606
if isnan(integrator.dt)
@@ -584,7 +609,7 @@ function check_error(integrator::DEIntegrator)
584609
end
585610
return ReturnCode.DtNaN
586611
end
587-
if integrator.iter > opts.maxiters
612+
if hasproperty(opts, :maxiters) && integrator.iter > opts.maxiters
588613
if verbose
589614
@warn("Interrupted. Larger maxiters is needed. If you are using an integrator for non-stiff ODEs or an automatic switching algorithm (the default), you may want to consider using a method for stiff equations. See the solver pages for more details (e.g. https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/#Stiff-Problems).")
590615
end
@@ -597,42 +622,35 @@ function check_error(integrator::DEIntegrator)
597622
# We also exit if the ODE is unstable according to a user chosen callback
598623
# but only if we accepted the step to prevent from bailing out as unstable
599624
# when we just took way too big a step)
600-
step_accepted = !hasproperty(integrator, :accept_step) || integrator.accept_step
601-
if !opts.force_dtmin && opts.adaptive
602-
if abs(integrator.dt) <= abs(opts.dtmin) &&
603-
(!step_accepted || (hasproperty(opts, :tstops) ?
604-
integrator.t + integrator.dt < integrator.tdir * first(opts.tstops) :
605-
true))
625+
step_rejected = last_step_failed(integrator)
626+
step_accepted = !step_rejected # For better readability
627+
force_dtmin = hasproperty(integrator, :force_dtmin) && integrator.force_dtmin
628+
if !force_dtmin && isadaptive(integrator)
629+
dt_below_min = abs(integrator.dt) abs(opts.dtmin)
630+
before_next_tstop = has_tstop(integrator) ? integrator.t + integrator.dt < get_tdir(integrator) * first_tstop(integrator) : true
631+
if dt_below_min && (step_rejected || before_next_tstop)
606632
if verbose
607-
if isdefined(integrator, :EEst)
608-
EEst = ", and step error estimate = $(integrator.EEst)"
609-
else
610-
EEst = ""
611-
end
612-
@warn("dt($(integrator.dt)) <= dtmin($(opts.dtmin)) at t=$(integrator.t)$EEst. Aborting. There is either an error in your model specification or the true solution is unstable.")
633+
controller_string = controller_message_on_dtmin_error(integrator)
634+
@warn("dt($(integrator.dt)) <= dtmin($(opts.dtmin)) at t=$(integrator.t)$(controller_string). Aborting. There is either an error in your model specification or the true solution is unstable.")
613635
end
614636
return ReturnCode.DtLessThanMin
615-
elseif !step_accepted && integrator.t isa AbstractFloat &&
616-
abs(integrator.dt) <= abs(eps(integrator.t))
637+
elseif step_rejected && integrator.t isa AbstractFloat &&
638+
abs(integrator.dt) <= abs(eps(integrator.t)) # = DiffEqBase.timedepentdtmin(integrator)
617639
if verbose
618-
if isdefined(integrator, :EEst)
619-
EEst = ", and step error estimate = $(integrator.EEst)"
620-
else
621-
EEst = ""
622-
end
623-
@warn("At t=$(integrator.t), dt was forced below floating point epsilon $(integrator.dt)$EEst. Aborting. There is either an error in your model specification or the true solution is unstable (or the true solution can not be represented in the precision of $(eltype(integrator.u))).")
640+
controller_string = controller_message_on_dtmin_error(integrator)
641+
@warn("At t=$(integrator.t), dt was forced below floating point epsilon $(integrator.dt)$(controller_string). Aborting. There is either an error in your model specification or the true solution is unstable (or the true solution can not be represented in the precision of $(eltype(integrator.u))).")
624642
end
625643
return ReturnCode.Unstable
626644
end
627645
end
628-
if step_accepted &&
646+
if step_accepted && hasproperty(opts, :unstable_check) &&
629647
opts.unstable_check(integrator.dt, integrator.u, integrator.p, integrator.t)
630648
if verbose
631649
@warn("Instability detected. Aborting")
632650
end
633651
return ReturnCode.Unstable
634652
end
635-
if last_step_failed(integrator)
653+
if last_step_failed(integrator) && !isadaptive(integrator)
636654
if verbose
637655
@warn("Newton steps could not converge and algorithm is not adaptive. Use a lower dt.")
638656
end
@@ -873,7 +891,7 @@ Base.length(iter::TimeChoiceIterator) = length(iter.ts)
873891
end
874892
end
875893

876-
xflip --> integrator.tdir < 0
894+
xflip --> get_tdir(integrator) < 0
877895

878896
if denseplot
879897
seriestype --> :path
@@ -904,11 +922,11 @@ Base.length(iter::TimeChoiceIterator) = length(iter.ts)
904922
end
905923

906924
function step!(integ::DEIntegrator, dt, stop_at_tdt = false)
907-
(dt * integ.tdir) < 0 * oneunit(dt) && error("Cannot step backward.")
925+
(dt * get_tdir(integ)) < 0 * oneunit(dt) && error("Cannot step backward.")
908926
t = integ.t
909927
next_t = t + dt
910928
stop_at_tdt && add_tstop!(integ, next_t)
911-
while integ.t * integ.tdir < next_t * integ.tdir
929+
while integ.t * get_tdir(integ) < next_t * get_tdir(integ)
912930
step!(integ)
913931
integ.sol.retcode in (ReturnCode.Default, ReturnCode.Success) || break
914932
end

0 commit comments

Comments
 (0)