Skip to content
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

Update Documentation: Optimized Schemes, PERK Section, and PairedExplicitRK2 Tutorial #2146

Merged
merged 34 commits into from
Dec 3, 2024
Merged
Changes from 12 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
559f709
Some unfinished documentation progress
warisa-r Aug 17, 2024
4632d69
add final steps
warisa-r Aug 20, 2024
956847f
review docu
DanielDoehring Sep 16, 2024
589c0d1
typos
DanielDoehring Sep 16, 2024
aa5c413
add commas + change formulation of some sentences
warisa-r Sep 17, 2024
712faee
remove extra parentheses
warisa-r Sep 17, 2024
7e202bb
continue docu
DanielDoehring Nov 4, 2024
a632179
elaborate
DanielDoehring Nov 5, 2024
412c8e8
typo
DanielDoehring Nov 5, 2024
c5d7b06
Merge branch 'main' into PERK-documentation
warisa-r Nov 6, 2024
24c6746
try to fix refs
DanielDoehring Nov 6, 2024
a18efe8
Merge branch 'main' into PERK-documentation
ranocha Nov 7, 2024
0e42551
Merge branch 'main' into PERK-documentation
warisa-r Nov 13, 2024
83c08c2
use the example block instead
warisa-r Nov 25, 2024
a85fcf5
Merge branch 'main' into PERK-documentation
warisa-r Nov 25, 2024
c2fbc3e
Apply suggestions from code review
warisa-r Nov 26, 2024
051d377
edit the time_integration file and Project.toml to include Convex and…
warisa-r Nov 26, 2024
a303f5e
edit the Project.toml file to include Convex and ECOS
warisa-r Nov 26, 2024
23eba49
stick to PERK instead of P-ERK
DanielDoehring Nov 27, 2024
49ab1ac
Merge branch 'main' into PERK-documentation
warisa-r Nov 27, 2024
d74ff75
add a comment that s=e in the standalone methods
warisa-r Nov 27, 2024
f3dc713
the package import must be an example block
warisa-r Nov 27, 2024
8561d4b
Merge branch 'main' into PERK-documentation
warisa-r Nov 27, 2024
62e9bd5
Apply suggestions from code review
warisa-r Nov 27, 2024
ff2aae8
change example 1 to PERK-example-1
warisa-r Nov 27, 2024
f6ca186
attempt to fix the link to callbacks.md
warisa-r Nov 27, 2024
71b7f4c
change the way callbacks is ref
warisa-r Nov 28, 2024
a9c4541
get rid of the numbered list
warisa-r Nov 30, 2024
c0b7559
Merge branch 'main' into PERK-documentation
warisa-r Nov 30, 2024
086b3d1
Apply suggestions from code review
DanielDoehring Dec 3, 2024
ccc0c05
Merge branch 'main' into PERK-documentation
DanielDoehring Dec 3, 2024
07cf822
adapt
DanielDoehring Dec 3, 2024
d9b9265
Update docs/src/time_integration.md
DanielDoehring Dec 3, 2024
036a0d9
Merge branch 'main' into PERK-documentation
warisa-r Dec 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
188 changes: 183 additions & 5 deletions docs/src/time_integration.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# [Time integration methods](@id time-integration)

## Methods from OrdinaryDiffEq.jl

Trixi.jl is compatible with the [SciML ecosystem for ordinary differential equations](https://diffeq.sciml.ai/latest/).
In particular, explicit Runge-Kutta methods from [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl)
In particular, [explicit Runge-Kutta methods](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/#Explicit-Runge-Kutta-Methods) from [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl)
are tested extensively.
Interesting classes of time integration schemes are
- [Explicit low-storage Runge-Kutta methods](https://diffeq.sciml.ai/latest/solvers/ode_solve/#Low-Storage-Methods)
- [Strong stability preserving methods](https://diffeq.sciml.ai/latest/solvers/ode_solve/#Explicit-Strong-Stability-Preserving-Runge-Kutta-Methods-for-Hyperbolic-PDEs-(Conservation-Laws))
- [Explicit low-storage Runge-Kutta methods](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/#Low-Storage-Methods)
- [Strong stability preserving (SSP) methods](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/#Explicit-Strong-Stability-Preserving-Runge-Kutta-Methods-for-Hyperbolic-PDEs-(Conservation-Laws))

Some common options for `solve` from [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl)
are the following. Further documentation can be found in the
Expand All @@ -21,7 +23,7 @@ are the following. Further documentation can be found in the
out of memory or start to swap).
- You can set the maximal number of time steps via `maxiters=...`.
- SSP methods and many low-storage methods from OrdinaryDiffEq.jl support
`stage_limiter!`s and `step_limiter!`s, e.g., [`PositivityPreservingLimiterZhangShu`](@ref)
`stage_limiter!`s and `step_limiter!`s, e.g., [`PositivityPreservingLimiterZhangShu`](@ref) and [`EntropyBoundedLimiter`](@ref)
from Trixi.jl.
- If you start Julia with multiple threads and want to use them also in the time
integration method from OrdinaryDiffEq.jl, you need to pass the keyword argument
Expand All @@ -31,7 +33,7 @@ are the following. Further documentation can be found in the
For more information on using thread-based parallelism in Trixi.jl, please refer to
[Shared-memory parallelization with threads](@ref).
- If you use error-based step size control (see also the section on
[error-based adaptive step sizes](@ref adaptive_step_sizes) together with MPI, you need to
[error-based adaptive step sizes](@ref adaptive_step_sizes)) together with MPI, you need to
pass `internalnorm=ode_norm` and you should pass `unstable_check=ode_unstable_check` to
OrdinaryDiffEq's [`solve`](https://docs.sciml.ai/DiffEqDocs/latest/basics/common_solver_opts/), which are both
included in [`ode_default_options`](@ref).
Expand All @@ -42,3 +44,179 @@ are the following. Further documentation can be found in the
of stages, e.g. to allow for interpolation (dense output), root-finding for continuous callbacks,
and error-based time step control. In general, you often should not need to worry about this if you
use Trixi.jl.

## Custom Optimized Schemes

### Stabilized Explicit Runge-Kutta Methods

Optimized explicit schemes aim to maximize the timestep $\Delta t$ for a given simulation setup.
Formally, this boils down to an optimization problem of the form
```math
\underset{P_{p;S} \, \in \, \mathcal{P}_{p;S}}{\max} \Delta t \text{ such that } \big \vert P_{p;S}(\Delta t \lambda_m) \big \vert \leq 1, \quad m = 1 , \dots , M \tag{1}
```
where $p$ denotes the order of consistency of the scheme, $S$ is the number of stage evaluations and $M$ denotes the number of eigenvalues $\lambda_m$ of the Jacobian matrix $J \coloneqq \frac{\partial \boldsymbol F}{\partial \boldsymbol U}$ of the right-hand side of the [semidiscretized PDE](https://trixi-framework.github.io/Trixi.jl/stable/overview/#overview-semidiscretizations):
```math
\dot{\boldsymbol U} = \boldsymbol F(\boldsymbol U) \tag{2} \: .
```
In particular, for $S > p$ the Runge-Kutta method includes some free coefficients which may be used to adapt the domain of absolute stability to the problem at hand.
Since Trixi.jl [supports exact computation of the Jacobian $J$ by means of automatic differentiation](https://trixi-framework.github.io/Trixi.jl/stable/tutorials/differentiable_programming/), we have access to the Jacobian of a given simulation setup.
For small (up to approximately $10^4$ DoF) systems, the spectrum $\boldsymbol \sigma = \left \{ \lambda_m \right \}_{m=1, \dots, M}$ can be computed directly using [`LinearAlgebra.eigvals(J)`](https://docs.julialang.org/en/v1/stdlib/LinearAlgebra/#LinearAlgebra.eigvals).
DanielDoehring marked this conversation as resolved.
Show resolved Hide resolved
For larger systems, we recommend the procedure outlined in section 4.1 of [Doehring et al. (2024)](https://doi.org/10.1016/j.jcp.2024.113223). This approach computes a reduced set of (estimated) eigenvalues $\widetilde{\boldsymbol \sigma}$ around the convex hull of the spectrum by means of the [Arnoldi method](https://github.com/JuliaLinearAlgebra/Arpack.jl).

The optimization problem (1) can be solved using the algorithms described in [Ketcheson, Ahmadia (2012)](http://dx.doi.org/10.2140/camcos.2012.7.247) for a moderate number of stages $S$ or [Doehring, Gassner, Torrilhon (2024)](https://doi.org/10.1007/s10915-024-02478-5) for a large number of stages $S$.
In Trixi.jl, the former approach is implemented by means of convex optimization using the [Convex.jl](https://github.com/jump-dev/Convex.jl) package.

The resulting stability polynomial $P_{p;S}$ is then used to construct an optimized Runge-Kutta method.
Trixi.jl implements the [Paired-Explicit Runge-Kutta (P-ERK)](https://doi.org/10.1016/j.jcp.2019.05.014) method, a low-storage, multirate-ready method with optimized domain of absolute stability.

### Paired-Explicit Runge-Kutta (P-ERK) Schemes

Paired-Explicit Runge-Kutta (P-ERK) or `PairedExplicitRK` schemes are an advanced class of numerical methods designed to efficiently solve ODEs.
In the [original publication](https://doi.org/10.1016/j.jcp.2019.05.014), second-order schemes were introduced, which have been extended to [third](https://doi.org/10.1016/j.jcp.2022.111470)- and [fourth](https://doi.org/10.48550/arXiv.2408.05470)-order in subsequent works.
DanielDoehring marked this conversation as resolved.
Show resolved Hide resolved

By construction, P-ERK schemes are suited for integrating multirate systems, i.e., systems with varying characteristics speeds throughout the domain.
Nevertheless, due to their optimized stability properties and low-storage nature, the P-ERK schemes are also highly efficient when applied as standalone methods.

#### Tutorial: Using `PairedExplicitRK2`

In this tutorial, we will demonstrate how you can use the second-order P-ERK time integrator.

1. First, ensure you have the necessary packages installed. For the optimization of the stability polynomial $P_{2; S}$, you need the `Convex.jl` and `ECOS.jl` packages.
You can install them using Julia's package manager:

```julia
using Pkg
Pkg.add("Trixi")
Pkg.add("Convex")
Pkg.add("ECOS")
```

2. Now you can load the necessary packages:
warisa-r marked this conversation as resolved.
Show resolved Hide resolved

```julia
using Convex, ECOS
using Trixi
```
warisa-r marked this conversation as resolved.
Show resolved Hide resolved

3. Define the ODE problem and the semidiscretization setup. For this example, we will use a simple advection problem.
warisa-r marked this conversation as resolved.
Show resolved Hide resolved

```julia
# Define the mesh
cells_per_dimension = 100
coordinates_min = 0.0
coordinates_max = 1.0
mesh = StructuredMesh(cells_per_dimension,
coordinates_min, coordinates_max)

# Define the equation and initial condition
advection_velocity = 1.0
equations = LinearScalarAdvectionEquation1D(advection_velocity)

initial_condition = initial_condition_convergence_test

# Define the solver
solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs)

# Define the semidiscretization
semi = SemidiscretizationHyperbolic(mesh,
equations, initial_condition,
solver)
```

4. Define the necessary callbacks for the simulation. Callbacks are used to perform actions at specific points during the integration process.
warisa-r marked this conversation as resolved.
Show resolved Hide resolved

```julia
# Define some standard callbacks
summary_callback = SummaryCallback()
alive_callback = AliveCallback()
analysis_callback = AnalysisCallback(semi, interval = 200)
# For this optimized method we can use a relatively large CFL number
stepsize_callback = StepsizeCallback(cfl = 2.5)

# Create a CallbackSet to collect all callbacks
callbacks = CallbackSet(summary_callback,
alive_callback,
analysis_callback,
stepsize_callback)
```

5. Define the ODE problem by specifying the time span over which the ODE will be solved.
warisa-r marked this conversation as resolved.
Show resolved Hide resolved
The `tspan` parameter is a tuple `(t_start, t_end)` that defines the start and end times for the simulation.
The `semidiscretize` function is used to create the ODE problem from the simulation setup.

```julia
# Define the time span
tspan = (0.0, 1.0)

# Create ODE problem with time span from 0.0 to 1.0
ode = semidiscretize(semi, tspan)
```

6. In this step we will construct the time integrator. In order to do this, you need the following components:
warisa-r marked this conversation as resolved.
Show resolved Hide resolved

- Number of Stages: The number of stages $S$ in the Runge-Kutta method.
warisa-r marked this conversation as resolved.
Show resolved Hide resolved
In this example, we use `6` stages.
- Time Span (`tspan`): A tuple `(t_start, t_end)` that defines the time span over which the ODE will be solved.
warisa-r marked this conversation as resolved.
Show resolved Hide resolved
This defines the bounds for the bisection routine for the optimal timestep $\Delta t$ used in calculating the polynomial coefficients at optimization stage.
This variable is already defined in step 5.
- Semidiscretization (`semi`): The semidiscretization setup that includes the mesh, equations, initial condition, and solver. In this example, this variable is already defined in step 3.
In the background, we compute from `semi` the Jacobian $J$ evaluated at the initial condition using [`jacobian_ad_forward`](https://trixi-framework.github.io/Trixi.jl/stable/reference-trixi/#Trixi.jacobian_ad_forward-Tuple{Trixi.AbstractSemidiscretization}).
This is then followed by the computation of the spectrum $\boldsymbol \sigma(J)$ using `LinearAlgebra.eigvals`.
Equipped with the spectrum, the optimal stability polynomial is computed, from which the corresponding Runge-Kutta method is derived. Other constructors (if the coefficients $\boldsymbol{\alpha}$ of the stability polynomial are already available, or if a reduced spectrum $\widetilde{\boldsymbol{\sigma}}$ should be used) are discussed below.

```julia
# Construct second order-explicit Runge-Kutta method with 6 stages for given simulation setup (`semi`)
# `tspan` provides the bounds for the bisection routine that is used to calculate the maximum timestep.
ode_algorithm = Trixi.PairedExplicitRK2(6, tspan, semi)
```

7. With everything now set up, you can now use `Trixi.solve` to solve the ODE problem. The `solve` function takes the ODE problem, the time integrator, and some options such as the time step (`dt`), whether to save every step (`save_everystep`), and the callbacks.
warisa-r marked this conversation as resolved.
Show resolved Hide resolved

```julia
# Solve the ODE problem using PERK2
sol = Trixi.solve(ode, ode_algorithm,
dt = 1.0, # overwritten by `stepsize_callback`
save_everystep = false, callback = callbacks)
```

8. Advanced constructors:
warisa-r marked this conversation as resolved.
Show resolved Hide resolved
There are two additional constructors for the `PairedExplicitRK2` method besides the one taking in a semidiscretization `semi`:
- `PairedExplicitRK2(num_stages, base_path_monomial_coeffs::AbstractString)` constructs a `num_stages`-stage method from the given optimal monomial_coeffs $\boldsymbol \alpha$.
These are expected to be present in the provided directory in the form of a `gamma_<S>.txt` file, where `<S>` is the number of stages `num_stages`.
This constructor is useful when the optimal coefficients cannot be obtained using the optimization routine by Ketcheson and Ahmadia, possibly due to a large number of stages $S$.
- `PairedExplicitRK2(num_stages, tspan, eig_vals::Vector{ComplexF64})` constructs a `num_stages`-stage using the optimization approach by Ketcheson and Ahmadia for the (reduced) spectrum `eig_vals`.
The use-case for this constructor would be a large system, for which the computation of all eigenvalues is infeasible.

#### Automatic computation of stable CFL Number
DanielDoehring marked this conversation as resolved.
Show resolved Hide resolved

In the previous tutorial the CFL number was set manually to $2.5$.
To avoid this trial-and error process, instantiations of `AbstractPairedExplicitRK` methods can automatically compute the stable CFL number for a given simulation setup using the [`Trixi.calculate_cfl`](@ref) function.
DanielDoehring marked this conversation as resolved.
Show resolved Hide resolved
When constructing the time integrator from a semidiscretization `semi`,
```julia
# Construct third-order paired-explicit Runge-Kutta method with 8 stages for given simulation setup.
ode_algorithm = Trixi.PairedExplicitRK3(8, tspan, semi)
```
the maximum timestep `dt` is stored by the `ode_algorithm`.
This can then be used to compute the stable CFL number for the given simulation setup:
```julia
cfl_number = Trixi.calculate_cfl(ode_algorithm, ode)
```
For nonlinear problems, the spectrum will in general change over the course of the simulation.
Thus, it is often necessary to reduce the optimal `cfl_number` by a safety factor:
```julia
# For non-linear problems, the CFL number should be reduced by a safety factor
# as the spectrum changes (in general) over the course of a simulation
stepsize_callback = StepsizeCallback(cfl = 0.85 * cfl_number)
```
If the optimal monomial coefficients are precomputed, the user needs to set the obtained maximum timestep from the optimization manually via
```julia
ode_algorithm.dt_opt = 42.0 # The timestep obtained from the optimization
```
Then, the stable CFL number can be computed as described above.

#### Currently implemented P-ERK methods

##### Single/Standalone methods

- [`Trixi.PairedExplicitRK2`](@ref): Second-order P-ERK method with at least two stages.
- [`Trixi.PairedExplicitRK3`](@ref): Third-order P-ERK method with at least three stages.
Loading