Skip to content

Conversation

KrishGaur1354
Copy link

@KrishGaur1354 KrishGaur1354 commented Jul 27, 2025

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 code
duplication. 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:

  • KenCarp/Kvaerno Family (18 cache types):
    • Kvaerno3/4/5 (ConstantCache + Cache variants)
    • KenCarp3/4/5/47/58 (ConstantCache + Cache variants)
    • CFNLIRK3 (ConstantCache + Cache variants)
    • Kvaerno3/4/5 (ConstantCache + Cache variants)
    • KenCarp3/4/5/47/58 (ConstantCache + Cache variants)
    • CFNLIRK3 (ConstantCache + Cache variants)
  • Hairer Methods (2 cache types):
    • Hairer4ConstantCache, Hairer4Cache
  • Special Array Variant:
    • TRBDF2Cache{<:Array} (specialized version)
  • Duplication Removal of SDIRK related code
  • Re-run tests to check if working proper

Part of SciML Small Grants Program for OrdinaryDiffEq.jl solver set refactoring

- 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
@KrishGaur1354 KrishGaur1354 marked this pull request as ready for review July 28, 2025 12:55
KrishGaur1354 and others added 2 commits July 29, 2025 12:30
…d Added missing tableau definitions for Hairer4, CFNLIRK3, KenCarp47, and KenCarp58
@KrishGaur1354
Copy link
Author

@ChrisRackauckas could you provide any suggestions or any changes I should make?

@oscardssmith
Copy link
Member

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?

@ChrisRackauckas
Copy link
Member

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.

@KrishGaur1354
Copy link
Author

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;
Copy link
Member

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?

Copy link
Author

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.

Copy link
Member

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.

Copy link
Author

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.

@KrishGaur1354 KrishGaur1354 changed the title [WIP]: Refactoring and implementing unified tableau structure and generic perform_step! for SDIRK solver set Refactoring and implementing unified tableau structure and generic perform_step! for SDIRK solver set Aug 6, 2025
@@ -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))
Copy link
Member

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?

Copy link
Author

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
Copy link
Member

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
Copy link
Member

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₁
Copy link
Member

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?

@KrishGaur1354
Copy link
Author

@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} <:
Copy link
Member

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.

Copy link
Author

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.

@ChrisRackauckas
Copy link
Member

Test failure looks real.

γ = tab.γ

# Check if this is an IMEX (split function) scheme
is_imex = integrator.f isa SplitFunction && tab.has_additive_splitting
Copy link
Member

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.

Comment on lines +412 to +415
θ = 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₂
Copy link
Member

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)
Copy link
Member

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)
Copy link
Member

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)
Copy link
Member

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?

@ChrisRackauckas
Copy link
Member

Yes this is right on track. Looking fairly close. Left a bunch of comments.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants