Skip to content

SDIRK scheme implementation #1598

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

Open
wants to merge 16 commits into
base: master
Choose a base branch
from
Open

SDIRK scheme implementation #1598

wants to merge 16 commits into from

Conversation

etiennemarin
Copy link
Collaborator

@etiennemarin etiennemarin commented Jul 29, 2025

Description

This pull request implements the SDIRK (Singly Diagonally Implicit Runge-Kutta) time integration method for the unsteady Navier–Stokes equations.

The current implementation is quite brute-force and would surely benefit from a proper refactoring to improve efficiency and readability. The idea is to also use the SDIRK scheme for other applications beyond just lethe-fluid.

The code is structured to match as closely as possible the existing BDF structure. Here's how it works:

  • At each time iteration, we loop over the stages. For "traditional" schemes (mainly BDF), the loop is straightforward: there is only one stage.
  • At each stage, for the SDIRK scheme, we compute the vectors kᵢ.
  • However, the structure of Lethe is designed to solve the nonlinear system on the velocity. So instead of directly solving kᵢ = F(tᵢ, uᵢ), we solve the equivalent system:

(uᵢ* - uₙ) / (h aᵢᵢ) - ∑_{j=0}^{i-1} (aᵢⱼ / aᵢᵢ) kⱼ = F(tᵢ, uᵢ)

  • Once we have computed the $\mathbf{u}_i^*$ vector, we store the associated $\mathbf{k}_i$ vector in order to compute the next stages and the final sum.
  • At the end of the loop, the solution is computed as:

uₙ₊₁ = uₙ + h ∑ bᵢ kᵢ

The main changes are implemented in the solvers/navier_stokes_base.cc file, within the iterate() function.
The Butcher table is stored in scratch-data because it's required during matrix assembly.


Testing

The code has been tested on the lethe-fluid/application-test Taylor–Green vortex (GLS formulation) and yields results close to the BDF2 scheme.
The test outputs, at each time step, the enstrophy, the kinetic energy, and the L2 error.

The goal is now to test this new scheme on multiple examples and with different Butcher tables.


Documentation

  • The Butcher table is set in the scratch-data.
  • The variable sum_over_previous_stages is now required for matrix assembly.

Miscellaneous (will be removed when merged)

Checklist (will be removed when merged)

See this page for more information about the pull request process.

Code related list:

  • All in-code documentation related to this PR is up to date (Doxygen format)
  • Copyright headers are present and up to date
  • Lethe documentation is up to date
  • New feature has unit test(s) (preferred) or application test(s), and restart files are in the generator folder
  • The branch is rebased onto master
  • Changelog (CHANGELOG.md) is up to date
  • Code is indented with indent-all and .prm files (examples and tests) with prm-indent

Pull request related list:

  • Labels are applied
  • There are at least 2 reviewers (or 1 if small feature) excluding the responsible for the merge
  • If this PR closes an issue or is related to a project, it is linked in the "Projects" or "Development" section
  • If the fix is temporary, an issue is opened
  • The PR description is cleaned and ready for merge

@blaisb
Copy link
Contributor

blaisb commented Jul 29, 2025

Can you add the documentation? Furthermore, have you tested if you recoved the 2nd and 3rd order convergence rate for both SDIRK implementations?

Copy link
Contributor

@blaisb blaisb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First round of comment.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So you added a new bdf2 test case? Is it to have some sort of stored comparison between bdf2,sdirk22 and sdirk33?
In that case, I would suggest you do a case with a larger time-step so that the accuracy in the case be limited by the time-step and not the mesh resolution. Do. like a time-step of 0.2 maybe

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes that was my idea since the BDF2 is broadly used in Lethe. I found it more relevant than BDF1. I will do the simulation again with the larger time step

Comment on lines 496 to 500
unsigned int
get_stage_i_number()
{
return stage_i;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs a brief and a description of the return. PLease be also a bit more verbose. What does i mean here?

Copy link
Collaborator Author

@etiennemarin etiennemarin Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, since we are calculating the sum of a_ij * k_j in the loop in the iterate() function, I think we don't need these two functions. In the assembler, we only need to have the diagonal term, so we don't need to know at which stage we are. In the loop, we already know the stage. These two functions can maybe be usefull for more general implicit schemes, so I don't know if you want me to keep them. But for SDIRK, it is not necessary

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you don't use them, then you can remove them for now i presume.

Comment on lines 502 to 507
void
set_stage_i_number(unsigned int s)
{
stage_i = s;
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above comment.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See the comment just above about the need (or not) to keep these two functions in the code

Comment on lines 927 to 944
VectorType present_k_i_solution;
VectorType temp_present_k_i_solution;

VectorType system_rhs;
VectorType tmp;

// Previous solutions vectors
std::vector<VectorType> previous_solutions;
VectorType previous_solutions_0;

std::vector<VectorType> previous_k_j_solutions;
VectorType previous_k_j_solutions_p;

VectorType sum_bi_ki;
VectorType temp_sum_bi_ki;

VectorType sum_over_previous_stages;
VectorType temp_sum_over_previous_stages;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok that's a lot of vectors... You really don't need that many! Please try to clean this to have as little as possible.

@@ -990,6 +1006,8 @@ class NavierStokesBase : public PhysicsSolver<VectorType>
/// Dynamic homogeneous constraints used for temperature-dependent solid
/// domain constraints
AffineConstraints<double> dynamic_zero_constraints;

std::unique_ptr<NavierStokesScratchData<dim>> scratch_data;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

??? You don't need this

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I need the Butcher's table in the iterate() function to build the sums. The table is in the scratch data. But I also need this table for the matrix assembly. Should I store the table in an other location ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can recalculate the Butcher table when. you need it there. Just remake it and not store it as part of the class. It will be simpler. There,s no point in having a scratch data live just for a table. Just reget the table. Otherwise if you want to store it, store it in simulation control where the time related stuff is stored.

tmp = previous_k_j_solutions[0];
tmp.equ(stage_data.a_ij[p] / a_ii,
previous_k_j_solutions_p);
temp_sum_over_previous_stages.add(1.0, tmp);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why don't you just do instead

Suggested change
temp_sum_over_previous_stages.add(1.0, tmp);
temp_sum_over_previous_stages.add(stage_data.a_ij[p] / a_ii, previous_k_j_solutions[p]);

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I don't need to have all these lines, your solution works. But we still need to have a non-locally relevant vector in the .add operation, and previous_k_j_solutions[p] is locally-relevant

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand, but why create a new locally_owned vector? You can use local evaluation point to store your temporary solution, this will remove one more vector.
Try to minimize the amount of local vectors you generate.

previous_k_j_solutions_p = previous_k_j_solutions[p];
tmp = previous_k_j_solutions[0];
tmp.equ(stage_data.a_ij[p] / a_ii,
previous_k_j_solutions_p);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
previous_k_j_solutions_p);
previous_k_j_solutions[p]);

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the operations (addition, multiplication, etc.) we need a non-locally relevant dofs vector, I think

@blaisb
Copy link
Contributor

blaisb commented Jul 30, 2025

@etiennemarin Once you have done the convergence study, can you ask for two other reviewer outside of me? You can use the lethe channel in the slack to ask people. Don't forget to check the "check list" and ensure that you can really check all tasks of the checklist. It is a reminder of what you need to do when you make a PR.

@blaisb blaisb force-pushed the sdirk_implementation branch from 1fe889f to b4108a0 Compare July 31, 2025 12:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants