-
-
Notifications
You must be signed in to change notification settings - Fork 235
Refactoring and implementing unified tableau structure and generic perform_step! for SDIRK solver set #2779
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
- Implement SDIRKTableau struct consolidating all Butcher coefficients - Add generic_sdirk_perform_step! reducing duplication across 20+ methods - Migrate ImplicitEuler, SDIRK2/22, Cash4, all ESDIRK/SFSDIRK methods - Preserve TRBDF2 special handling with unified coefficients - Unify KenCarp/Kvaerno tableaus (separate perform_step! for additive splitting) - Reduce codebase by ~3000 lines while maintaining full compatibility - Pass all tests (JET, Aqua, integration) Part of SciML Small Grants: OrdinaryDiffEq.jl tableau refactoring Claimed by: Krish Gaur (July 4 - August 4, 2025) Ref: https://github.com/SciML/sciml.ai/blob/master/small_grants.md#refactor-ordinarydiffeqjl-solver-sets-to-reuse-perform_step-implementations-via-tableaus-100solver-set
…d Added missing tableau definitions for Hairer4, CFNLIRK3, KenCarp47, and KenCarp58
@ChrisRackauckas could you provide any suggestions or any changes I should make? |
The primary thing this PR seems to be missing is a bunch of deletions. If this PR unifies the SDIRK tableau structure and perform steps, why isn't it deleting the old tableaus and perform step methods? |
What's the unique structure? TRBDF2 and KenCarps should be able to join just fine. Any step predictor here is just a linear combination of previous values, which these match, so you just need an alpha array of step predictor coefficients. |
Sure, I'll just ensure that the TRBDF2 and KenCarp methods would now be unified under consistent structure, which will utilize shared tableau and step predictor coefficients for it. |
d = T(1 - sqrt(2) / 2) | ||
ω = T(sqrt(2) / 4) | ||
|
||
A = @SMatrix [0 0 0; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
are static arrays actually faster here than just normal Vector/Matrix?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I did found static arrays being significantly faster than normal Vector/Matrix for SDIRK tableau operations by almost 1.8 times faster.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's interesting (and somewhat unfortunate). I would have expected the nonlinear solve cost to dominate.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Atleast the test that I did, Static Array was faster but the construction was slower due to compilation overhead in this case.
@@ -7,7 +7,7 @@ function alg_cache(alg::Kvaerno3, u, rate_prototype, ::Type{uEltypeNoUnits}, | |||
::Type{uBottomEltypeNoUnits}, ::Type{tTypeNoUnits}, | |||
uprev, uprev2, f, t, dt, reltol, p, calck, | |||
::Val{false}) where {uEltypeNoUnits, uBottomEltypeNoUnits, tTypeNoUnits} | |||
tab = Kvaerno3Tableau(constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits)) | |||
tab = get_sdirk_tableau(:Kvaerno3, constvalue(uBottomEltypeNoUnits), constvalue(tTypeNoUnits)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reason for Symbol rather than alg dispatch here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Switched to get_sdirk_tableau(:Kvaerno3, ...)
for uniformity during the refactor. I can change it if you want?
A_explicit::Union{SMatrix{S, S, T}, Nothing} | ||
b_explicit::Union{SVector{S, T}, Nothing} | ||
c_explicit::Union{SVector{S, T2}, Nothing} | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the tableaus are missing a step predictor coefficients part?
z₁ = dt * integrator.fsalfirst | ||
end | ||
|
||
if s == 4 # 4-stage methods like KenCarp3, Kvaerno3 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why this instead of just stages?
end | ||
|
||
if s == 4 # 4-stage methods like KenCarp3, Kvaerno3 | ||
z₂ = z₁ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not generally a good prediction? It should be a linear combination from tableau coefficients?
@oscardssmith @ChrisRackauckas I would like to apologies for the delay from my side. I have made the necessary changes for the refactoring. Do let me know if there's anything else to be added/removed or if any change is required in this PR. |
γ, c = tab.γ, tab.γ | ||
nlsolver = build_nlsolver(alg, u, uprev, p, t, dt, f, rate_prototype, uEltypeNoUnits, | ||
uBottomEltypeNoUnits, tTypeNoUnits, γ, c, Val(false)) | ||
Hairer4ConstantCache(nlsolver, tab) | ||
end | ||
|
||
@cache mutable struct Hairer4Cache{uType, rateType, uNoUnitsType, Tab, N} <: | ||
@cache mutable struct Hairer4Cache{uType, rateType, uNoUnitsType, Tab, N, StepLimiter} <: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The point of the refactor is that these types don't need to exist. If there's a single SDIRKCache that just has different values for the tableau, then every SDIRK algorithm gives the same cache type, and so they compile all the same. If you have separate cache types, they will all compile different codes. So this doesn't meet the criteria. I recommend looking at the Rosenbrock methods, where a good chunk of them are all the same cache https://github.com/SciML/OrdinaryDiffEq.jl/blob/master/lib/OrdinaryDiffEqRosenbrock/src/rosenbrock_caches.jl#L748-L832, and all the same perform_step!, and it's just written based on the tableau size. This makes it so that compiling for one Rosenbrock compiles for all of them, effectively removing the per-algorithm compilation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you for the feedback. I have implemented the refactor following the Rosenbrock pattern you referenced for all algo specific cache types have been removed and replaced with unified SDIRKCache and SDIRKConstantCache types that work across all SDIRK methods.
Test failure looks real. |
γ = tab.γ | ||
|
||
# Check if this is an IMEX (split function) scheme | ||
is_imex = integrator.f isa SplitFunction && tab.has_additive_splitting |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
tab.has_additive_splitting
is not going to be inferred unless it's a value type, so is_imex
is not compile-time information, meaning k_explicit
is not going to be type-stable. I think the right answer is to make sure has_additive_splitting is type-level information.
θ = c[3] / c[2] | ||
α31 = ((1 + (-4θ + 3θ^2)) + (6θ * (1 - θ) / c[2]) * γ) | ||
α32 = ((-2θ + 3θ^2)) + (6θ * (1 - θ) / c[2]) * γ | ||
z₃ = α31 * z₁ + α32 * z₂ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this hardcoded here instead of being in the tableau?
generic_sdirk_perform_step!(integrator, cache, repeat_step) | ||
end | ||
|
||
@muladd function generic_additive_sdirk_perform_step!(integrator, cache::SDIRKConstantCache, repeat_step=false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why is this kept separate? And what is meant by "additive SDRIK"?
end | ||
|
||
# TRBDF2 special handling for constant cache | ||
@muladd function perform_step_trbdf2!(integrator, cache::SDIRKConstantCache, repeat_step=false) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's special about TRBDF2? It should just be a standard ESDIRK
end | ||
end | ||
|
||
# simple 1-stage SDIRK perform step (ImplicitEuler, ImplicitMidpoint) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why would 1 and 2 stages need to be treated differently?
Yes this is right on track. Looking fairly close. Left a bunch of comments. |
This PR refactors the SDIRK solver implementation to use a unified tableau structure (
SDIRKTableau
)and a single generic
perform_step!
function, consolidating 95% of SDIRK methods and reducing codeduplication. All methods now share consistent tableau coefficients while maintaining
full backward compatibility and performance. Special handling preserved for TRBDF2's unique structure.
KenCarp/Kvaerno methods retain separate implementations for additive splitting complexity.
Remaining Work:
Part of SciML Small Grants Program for OrdinaryDiffEq.jl solver set refactoring