diff --git a/src/PETScDiffEq.jl b/src/PETScDiffEq.jl index 4f7aec36..0c80a681 100644 --- a/src/PETScDiffEq.jl +++ b/src/PETScDiffEq.jl @@ -900,6 +900,7 @@ mutable struct TSContext{R, S, A, F, F2, JAC, JBUF, P, L, V} inner::Any mass_slope::Bool tend::R + attempt_margin::Int end _distributed(alg::AnyPETScTS) = alg.comm != MPI.COMM_SELF @@ -2759,6 +2760,19 @@ function _check_attempts!( return LibPETSc.PetscErrorCode(0) end +const _MAX_REJECT_OPTIONS = ("ts_max_reject", "ts_max_step_rejections") + +function _arm_attempts!(ctx, ts_ptr) + ctx.attempt_margin < 0 && return nothing + pl = ctx.petsclib + code, taken = _taken(pl, ts_ptr) + _check_code(code) + ctx.maxiters - (taken + ctx.nreject + ctx.nfail) > ctx.attempt_margin && return nothing + ctx.attempt_margin = -1 + _set_stage_check!(pl, LibPETSc.TS(ts_ptr, pl), ctx, _callbacks(ctx).attempt_check) + return nothing +end + function _set_stage_check!(pl, ts, ctx, ptr) POST_STEP_CTX[ts.ptr] = (ctx, ts) _check_code( @@ -4436,6 +4450,7 @@ end function _monitor_at!(ctx, ts_ptr, step, t, x) try + _arm_attempts!(ctx, ts_ptr) (ctx.retry_fp || ctx.comm !== nothing) && ctx.workvec == C_NULL && _hold_work_vec!(ctx, ts_ptr) tol = _near(t, ctx.tbound) @@ -6159,7 +6174,7 @@ function _setup( force_dtmin && dtmin !== nothing && dtmin != 0, nothing, 0, 0, bound, false, false, step_limit, false, nothing, nothing, 0, 0, false, shape, nothing, - nothing, mass_slope, tf, + nothing, mass_slope, tf, -1, )::TSContext h = Base.inferencebarrier(TSHandles)( ctx, petsclib, LibPETSc.TS(petsclib), uvec, nothing, nothing, ad_calls, nothing, @@ -6519,8 +6534,19 @@ function _setup( !fixed && running == "bdf" && !any(o -> _names_option(o, "snes_lag_jacobian"), effective_options) && _set_jacobian_lag!(petsclib, _snes(petsclib, ts.ptr), 5) - fixed || _uses_ifunction(alg) || - _set_stage_check!(petsclib, ts, ctx, ptrs.attempt_check) + if !fixed && !_uses_ifunction(alg) + # PETSc 3.25 renames -ts_max_reject; the pushed options are the only source. + raised = any(effective_options) do o + any(n -> _names_option(o, n), _MAX_REJECT_OPTIONS) + end + if raised + _set_stage_check!(petsclib, ts, ctx, ptrs.attempt_check) + else + # A TSStep makes at most PETSc's default 11 attempts, so the check, which + # runs at every stage, is installed only once maxiters is that close. + ctx.attempt_margin = 13 + end + end ctx.linear = _uses_ifunction(alg) && _snes_type(petsclib, ts.ptr) in ("ksponly", "ksptransposeonly") if running != ctx.alg_name @@ -8221,6 +8247,7 @@ function _reuse!(h::TSHandles{<:Any, <:Any, R, S}, prob, tstops) where {R, S} for i in eachindex(fresh) setfield!(ctx, i, fresh[i]) end + ctx.attempt_margin < 0 || _set_stage_check!(pl, ts, ctx, C_NULL) # PETSc's counts run on over the solves of one TS; these offsets take back the earlier ones. fails = Int(LibPETSc.TSGetSNESFailures(pl, ts)) ctx.nreject = fails - Int(LibPETSc.TSGetStepRejections(pl, ts)) @@ -8663,6 +8690,7 @@ function _step_unlocked(integ::PETScIntegrator, outer = nothing) h.stopped = 0 (ctx.retry_fp || ctx.comm !== nothing) && ctx.workvec == C_NULL && _hold_work_vec!(ctx, h.ts) + _arm_attempts!(ctx, h.ts.ptr) failure = _run!(h) do LibPETSc.TSStep(pl, h.ts) _linear_bdf_slope!(h) diff --git a/test/runtests.jl b/test/runtests.jl index f88dc069..c2c09d83 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1666,6 +1666,24 @@ const ALL_TESTS = Test.DefaultTestSet("PETScDiffEq.jl") @test counts(integ.sol) == counts(sol) @test integ.sol.t == sol.t && integ.sol.u == sol.u end + # A raised reject limit lets one step make more attempts than maxiters has left. + stiff = SciMLBase.ODEProblem( + (du, u, p, t) -> (du[1] = -2.0e3 * (u[1] - cos(t)); du[2] = u[1] - u[2]), + [0.0, 1.0], (0.0, 2.0), + ) + # PETSc 3.25 renames -ts_max_reject; 3.22 knows only the old name. + for name in ("-ts_max_reject", "-ts_max_step_rejections") + opts = ["-ts_adapt_clip", "0.95,10", name, "50"] + push!(opts, "-options_suppress_deprecated_warnings") + alg = PETScDiffEq.TSRK("5dp", opts) + sol = SciMLBase.solve(stiff, alg; dt = 1.0, maxiters = 20) + @test sum(counts(sol)) <= 20 + name == "-ts_max_reject" && @test sol.retcode == SciMLBase.ReturnCode.MaxIters + integ = SciMLBase.init(stiff, alg; dt = 1.0, maxiters = 20) + SciMLBase.solve!(integ) + @test counts(integ.sol) == counts(sol) + @test integ.sol.retcode == sol.retcode + end end @testset "the nonlinear counters come from the solver, not the stepper" begin