Skip to content

Use Jacobian-free Newton-Krylov for SDIRK time integrators #2505

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 35 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
c4cbb5a
example linear newton krylov
DanielDoehring Jul 29, 2025
8a4fdcd
Nonlinear example
DanielDoehring Jul 29, 2025
94f250a
comment
DanielDoehring Jul 29, 2025
2a51e91
Update test/Project.toml
DanielDoehring Jul 29, 2025
16dc218
Update test/Project.toml
DanielDoehring Jul 29, 2025
238afe3
check different sciml
DanielDoehring Jul 29, 2025
1221c94
v
DanielDoehring Jul 29, 2025
3453202
v
DanielDoehring Jul 29, 2025
7512dce
v
DanielDoehring Jul 29, 2025
57e5989
v
DanielDoehring Jul 29, 2025
1ae7504
v
DanielDoehring Jul 29, 2025
a1ffd0d
v
DanielDoehring Jul 29, 2025
0adb284
v
DanielDoehring Jul 29, 2025
b180056
v
DanielDoehring Jul 29, 2025
83dce58
v
DanielDoehring Jul 29, 2025
41f0b7c
v
DanielDoehring Jul 29, 2025
dda0feb
Merge branch 'main' into ExamplesNewtonKrylov
DanielDoehring Jul 29, 2025
9497f15
Update test/Project.toml
DanielDoehring Jul 29, 2025
da5500b
Update Project.toml
DanielDoehring Jul 29, 2025
73ee564
Merge branch 'main' into ExamplesNewtonKrylov
DanielDoehring Aug 1, 2025
9820f59
Merge branch 'main' into ExamplesNewtonKrylov
DanielDoehring Aug 2, 2025
8bbf603
Merge branch 'main' into ExamplesNewtonKrylov
DanielDoehring Aug 13, 2025
fcdfd39
ci
DanielDoehring Aug 14, 2025
2365682
compat
DanielDoehring Aug 15, 2025
6409d7c
v
DanielDoehring Aug 15, 2025
98012be
v
DanielDoehring Aug 15, 2025
178736e
v
DanielDoehring Aug 15, 2025
1e8ae90
v
DanielDoehring Aug 15, 2025
8a517fe
v
DanielDoehring Aug 15, 2025
bb9c185
v
DanielDoehring Aug 15, 2025
ae4d19d
Update test/Project.toml
DanielDoehring Aug 15, 2025
d7a4d77
Update test/Project.toml
DanielDoehring Aug 15, 2025
e04e649
Update test/Project.toml
JoshuaLampert Aug 15, 2025
add1a0c
Update test/Project.toml
JoshuaLampert Aug 15, 2025
e843d3b
Merge branch 'main' into ExamplesNewtonKrylov
DanielDoehring Aug 15, 2025
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
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
using Trixi
using OrdinaryDiffEqSDIRK
using LinearSolve # For Jacobian-free Newton-Krylov (GMRES) solver
using ADTypes # For automatic differentiation via finite differences

# This is the classic 1D viscous shock wave problem with analytical solution
# for a special value of the Prandtl number.
# The original references are:
#
# - R. Becker (1922)
# Stoßwelle und Detonation.
# [DOI: 10.1007/BF01329605](https://doi.org/10.1007/BF01329605)
#
# English translations:
# Impact waves and detonation. Part I.
# https://ntrs.nasa.gov/api/citations/19930090862/downloads/19930090862.pdf
# Impact waves and detonation. Part II.
# https://ntrs.nasa.gov/api/citations/19930090863/downloads/19930090863.pdf
#
# - M. Morduchow, P. A. Libby (1949)
# On a Complete Solution of the One-Dimensional Flow Equations
# of a Viscous, Head-Conducting, Compressible Gas
# [DOI: 10.2514/8.11882](https://doi.org/10.2514/8.11882)
#
#
# The particular problem considered here is described in
# - L. G. Margolin, J. M. Reisner, P. M. Jordan (2017)
# Entropy in self-similar shock profiles
# [DOI: 10.1016/j.ijnonlinmec.2017.07.003](https://doi.org/10.1016/j.ijnonlinmec.2017.07.003)

### Fixed parameters ###

# Special value for which nonlinear solver can be omitted
# Corresponds essentially to fixing the Mach number
alpha = 0.5
# We want kappa = cp * mu = mu_bar to ensure constant enthalpy
prandtl_number() = 3 / 4

### Free choices: ###
gamma() = 5 / 3

# In Margolin et al., the Navier-Stokes equations are given for an
# isotropic stress tensor τ, i.e., ∇ ⋅ τ = μ Δu
mu_isotropic() = 0.15
mu_bar() = mu_isotropic() / (gamma() - 1) # Re-scaled viscosity

rho_0() = 1
v() = 1 # Shock speed

domain_length = 4.0

### Derived quantities ###

Ma() = 2 / sqrt(3 - gamma()) # Mach number for alpha = 0.5
c_0() = v() / Ma() # Speed of sound ahead of the shock

# From constant enthalpy condition
p_0() = c_0()^2 * rho_0() / gamma()

l() = mu_bar() / (rho_0() * v()) * 2 * gamma() / (gamma() + 1) # Appropriate length scale

"""
initial_condition_viscous_shock(x, t, equations)
Classic 1D viscous shock wave problem with analytical solution
for a special value of the Prandtl number.
The version implemented here is described in
- L. G. Margolin, J. M. Reisner, P. M. Jordan (2017)
Entropy in self-similar shock profiles
[DOI: 10.1016/j.ijnonlinmec.2017.07.003](https://doi.org/10.1016/j.ijnonlinmec.2017.07.003)
"""
function initial_condition_viscous_shock(x, t, equations)
y = x[1] - v() * t # Translated coordinate

# Coordinate transformation. See eq. (33) in Margolin et al. (2017)
chi = 2 * exp(y / (2 * l()))

w = 1 + 1 / (2 * chi^2) * (1 - sqrt(1 + 2 * chi^2))

rho = rho_0() / w
u = v() * (1 - w)
p = p_0() * 1 / w * (1 + (gamma() - 1) / 2 * Ma()^2 * (1 - w^2))

return prim2cons(SVector(rho, u, 0, p), equations)
end
initial_condition = initial_condition_viscous_shock

###############################################################################
# semidiscretization of the ideal compressible Navier-Stokes equations

equations = CompressibleEulerEquations2D(gamma())

# Trixi implements the stress tensor in deviatoric form, thus we need to
# convert the "isotropic viscosity" to the "deviatoric viscosity"
mu_deviatoric() = mu_bar() * 3 / 4
equations_parabolic = CompressibleNavierStokesDiffusion2D(equations, mu = mu_deviatoric(),
Prandtl = prandtl_number(),
gradient_variables = GradientVariablesEntropy())

solver = DGSEM(polydeg = 3, surface_flux = flux_hlle)

coordinates_min = (-domain_length / 2, -domain_length / 2)
coordinates_max = (domain_length / 2, domain_length / 2)

trees_per_dimension = (12, 3)
mesh = P4estMesh(trees_per_dimension,
polydeg = 3, initial_refinement_level = 0,
coordinates_min = coordinates_min, coordinates_max = coordinates_max,
periodicity = (false, true))

### Inviscid boundary conditions ###

# Prescribe pure influx based on initial conditions
function boundary_condition_inflow(u_inner, normal_direction::AbstractVector, x, t,
surface_flux_function,
equations::CompressibleEulerEquations2D)
u_cons = initial_condition_viscous_shock(x, t, equations)
flux = Trixi.flux(u_cons, normal_direction, equations)

return flux
end

boundary_conditions = Dict(:x_neg => boundary_condition_inflow,
:x_pos => boundary_condition_do_nothing)

### Viscous boundary conditions ###
# For the viscous BCs, we use the known analytical solution
velocity_bc = NoSlip() do x, t, equations_parabolic
Trixi.velocity(initial_condition_viscous_shock(x,
t,
equations_parabolic),
equations_parabolic)
end

heat_bc = Isothermal() do x, t, equations_parabolic
Trixi.temperature(initial_condition_viscous_shock(x,
t,
equations_parabolic),
equations_parabolic)
end

boundary_condition_parabolic = BoundaryConditionNavierStokesWall(velocity_bc, heat_bc)

boundary_conditions_parabolic = Dict(:x_neg => boundary_condition_parabolic,
:x_pos => boundary_condition_parabolic)

semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic),
initial_condition, solver;
boundary_conditions = (boundary_conditions,
boundary_conditions_parabolic))

###############################################################################
# ODE solvers, callbacks etc.

tspan = (0.0, 1.0)
ode = semidiscretize(semi, tspan)

summary_callback = SummaryCallback()
alive_callback = AliveCallback(alive_interval = 1)
analysis_callback = AnalysisCallback(semi, interval = 100)

callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback)

###############################################################################
# run the simulation

sol = solve(ode,
# Use (diagonally) implicit Runge-Kutta method with Jacobian-free (!) Newton-Krylov (GMRES) solver
# See https://docs.sciml.ai/DiffEqDocs/stable/tutorials/advanced_ode_example/#Using-Jacobian-Free-Newton-Krylov
KenCarp47(autodiff = AutoFiniteDiff(), linsolve = KrylovJL_GMRES());
ode_default_options()..., callback = callbacks)
5 changes: 2 additions & 3 deletions examples/tree_1d_dgsem/elixir_diffusion_ldg.jl
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,14 @@ boundary_conditions_parabolic = boundary_condition_periodic
solver_parabolic = ViscousFormulationLocalDG()
semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic),
initial_condition,
solver;
solver_parabolic,
solver; solver_parabolic,
boundary_conditions = (boundary_conditions,
boundary_conditions_parabolic))

###############################################################################
# ODE solvers, callbacks etc.

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

Expand Down
59 changes: 59 additions & 0 deletions examples/tree_1d_dgsem/elixir_diffusion_ldg_newton_krylov.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
using Trixi
using OrdinaryDiffEqSDIRK
using LinearSolve # For Jacobian-free Newton-Krylov (GMRES) solver
using ADTypes # For automatic differentiation via finite differences

###############################################################################
# semidiscretization of the linear (advection) diffusion equation

advection_velocity = 0.0 # Note: This renders the equation mathematically purely parabolic
equations = LinearScalarAdvectionEquation1D(advection_velocity)
diffusivity() = 0.5
equations_parabolic = LaplaceDiffusion1D(diffusivity(), equations)

# surface flux does not matter for pure diffusion problem
solver = DGSEM(polydeg = 3, surface_flux = flux_central)

coordinates_min = -convert(Float64, pi)
coordinates_max = convert(Float64, pi)

mesh = TreeMesh(coordinates_min, coordinates_max,
initial_refinement_level = 4,
n_cells_max = 30_000)

function initial_condition_pure_diffusion_1d_convergence_test(x, t,
equation)
nu = diffusivity()
c = 0
A = 1
omega = 1
scalar = c + A * sin(omega * sum(x)) * exp(-nu * omega^2 * t)
return SVector(scalar)
end
initial_condition = initial_condition_pure_diffusion_1d_convergence_test

solver_parabolic = ViscousFormulationLocalDG()
semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic),
initial_condition,
solver; solver_parabolic)

###############################################################################
# ODE solvers, callbacks etc.

tspan = (0.0, 2.0)
ode = semidiscretize(semi, tspan)

summary_callback = SummaryCallback()
analysis_callback = AnalysisCallback(semi, interval = 10)
alive_callback = AliveCallback(alive_interval = 1)

callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback)

###############################################################################
# run the simulation

sol = solve(ode,
# Use (diagonally) implicit Runge-Kutta method with Jacobian-free (!) Newton-Krylov (GMRES) solver
# See https://docs.sciml.ai/DiffEqDocs/stable/tutorials/advanced_ode_example/#Using-Jacobian-Free-Newton-Krylov
KenCarp47(autodiff = AutoFiniteDiff(), linsolve = KrylovJL_GMRES());
ode_default_options()..., callback = callbacks)
1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ ECOS = "e2685f51-7e38-5353-a97d-a921fd2c8199"
ExplicitImports = "7d51a73a-1435-4ff3-83d9-f097790105c7"
ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
LinearSolve = "7ed4a6bd-45f5-4d41-b270-4a48e9bafcae"
MPI = "da04e1cc-30fd-572f-bb4f-1f8673147195"
NLsolve = "2774e3e8-f4cf-5e23-947b-6d7e65073b56"
OrdinaryDiffEqFeagin = "101fe9f7-ebb6-4678-b671-3a81e7194747"
Expand Down
14 changes: 14 additions & 0 deletions test/test_parabolic_1d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,20 @@ end
end
end

@trixi_testset "TreeMesh1D: elixir_diffusion_ldg_newton_krylov.jl" begin
@test_trixi_include(joinpath(examples_dir(), "tree_1d_dgsem",
"elixir_diffusion_ldg_newton_krylov.jl"),
l2=[6.542981787119615e-6], linf=[1.712011459781282e-5])
# Ensure that we do not have excessive memory allocations
# (e.g., from type instabilities)
let
t = sol.t[end]
u_ode = sol.u[end]
du_ode = similar(u_ode)
@test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000
end
end

@trixi_testset "TreeMesh1D: elixir_advection_diffusion_restart.jl" begin
@test_trixi_include(joinpath(examples_dir(), "tree_1d_dgsem",
"elixir_advection_diffusion_restart.jl"),
Expand Down
25 changes: 25 additions & 0 deletions test/test_parabolic_2d.jl
Original file line number Diff line number Diff line change
Expand Up @@ -866,6 +866,31 @@ end
end
end

@trixi_testset "P4estMesh2D: elixir_navierstokes_viscous_shock_newton_krylov.jl" begin
@test_trixi_include(joinpath(examples_dir(), "p4est_2d_dgsem",
"elixir_navierstokes_viscous_shock_newton_krylov.jl"),
l2=[
0.0050454344200822595,
0.004688806400845471,
2.636511509998046e-5,
0.0047873461533362765
],
linf=[
0.02208819271861917,
0.024209434405893293,
0.0007989190783110008,
0.026948544666356877
])
# Ensure that we do not have excessive memory allocations
# (e.g., from type instabilities)
let
t = sol.t[end]
u_ode = sol.u[end]
du_ode = similar(u_ode)
@test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000
end
end

@trixi_testset "elixir_navierstokes_SD7003airfoil.jl" begin
@test_trixi_include(joinpath(examples_dir(), "p4est_2d_dgsem",
"elixir_navierstokes_SD7003airfoil.jl"),
Expand Down
Loading