From 131f8805d7ea32dacc82a695125bc32f74233d30 Mon Sep 17 00:00:00 2001 From: singhharsh1708 Date: Sat, 24 Oct 2026 20:38:57 +0530 Subject: [PATCH 1/3] Install the explicit attempt check only near maxiters --- src/PETScDiffEq.jl | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/src/PETScDiffEq.jl b/src/PETScDiffEq.jl index 9da7f7bc..69829916 100644 --- a/src/PETScDiffEq.jl +++ b/src/PETScDiffEq.jl @@ -899,6 +899,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 @@ -2731,6 +2732,17 @@ function _check_attempts!( return LibPETSc.PetscErrorCode(0) end +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( @@ -4356,6 +4368,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) @@ -6080,7 +6093,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, @@ -6417,8 +6430,15 @@ 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) + if any(o -> _names_option(o, "ts_max_reject"), effective_options) + _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 @@ -8087,6 +8107,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)) @@ -8529,6 +8550,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) From c95d322e0fde088b9bba57f857e410aa5b42ba9c Mon Sep 17 00:00:00 2001 From: singhharsh1708 Date: Sat, 24 Oct 2026 22:05:52 +0530 Subject: [PATCH 2/3] Bump to 0.12.3 --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 1179efa4..15e2bcf4 100644 --- a/Project.toml +++ b/Project.toml @@ -1,7 +1,7 @@ name = "PETScDiffEq" uuid = "0a65194f-a16f-47d0-900e-080c22068669" authors = ["Harsh Singh"] -version = "0.12.2" +version = "0.12.3" [deps] ADTypes = "47edcb42-4c32-4615-8424-f2b9edc5f35b" From f0659a620187ffab2a6f40736a50c7fa53ba8c5f Mon Sep 17 00:00:00 2001 From: singhharsh1708 Date: Sat, 24 Oct 2026 23:51:53 +0530 Subject: [PATCH 3/3] Recognise -ts_max_step_rejections before arming the attempt check --- src/PETScDiffEq.jl | 8 +++++++- test/runtests.jl | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/PETScDiffEq.jl b/src/PETScDiffEq.jl index 69829916..252757b0 100644 --- a/src/PETScDiffEq.jl +++ b/src/PETScDiffEq.jl @@ -2732,6 +2732,8 @@ 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 @@ -6431,7 +6433,11 @@ function _setup( !any(o -> _names_option(o, "snes_lag_jacobian"), effective_options) && _set_jacobian_lag!(petsclib, _snes(petsclib, ts.ptr), 5) if !fixed && !_uses_ifunction(alg) - if any(o -> _names_option(o, "ts_max_reject"), effective_options) + # 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 diff --git a/test/runtests.jl b/test/runtests.jl index 75c9fce2..6a3d6cd6 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1633,6 +1633,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