Skip to content

Commit 0e2ea48

Browse files
author
Steve
committed
Hyperbolic-Parabolic elixir using SCT
1 parent ae35f82 commit 0e2ea48

File tree

2 files changed

+206
-10
lines changed

2 files changed

+206
-10
lines changed
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
using Trixi
2+
using SparseConnectivityTracer # For obtaining the Jacobian sparsity pattern
3+
using SparseMatrixColorings # For obtaining the coloring vector
4+
using OrdinaryDiffEqSDIRK, ADTypes
5+
6+
###############################################################################
7+
# semidiscretization of the linear advection-diffusion equation
8+
9+
advection_velocity = (1.5, 1.0)
10+
equations = LinearScalarAdvectionEquation2D(advection_velocity)
11+
diffusivity() = 5.0e-2
12+
equations_parabolic = LaplaceDiffusion2D(diffusivity(), equations)
13+
14+
# Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux
15+
solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs)
16+
17+
coordinates_min = (-1.0, -1.0) # minimum coordinates (min(x), min(y))
18+
coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y))
19+
20+
# Create a uniformly refined mesh with periodic boundaries
21+
mesh = TreeMesh(coordinates_min, coordinates_max,
22+
initial_refinement_level = 4,
23+
periodicity = true,
24+
n_cells_max = 30_000) # set maximum capacity of tree data structure
25+
26+
# Define initial condition
27+
function initial_condition_diffusive_convergence_test(x, t,
28+
equation::LinearScalarAdvectionEquation2D)
29+
# Store translated coordinate for easy use of exact solution
30+
RealT = eltype(x)
31+
x_trans = x - equation.advection_velocity * t
32+
33+
nu = diffusivity()
34+
c = 1
35+
A = 0.5f0
36+
L = 2
37+
f = 1.0f0 / L
38+
omega = 2 * convert(RealT, pi) * f
39+
scalar = c + A * sin(omega * sum(x_trans)) * exp(-2 * nu * omega^2 * t)
40+
return SVector(scalar)
41+
end
42+
initial_condition = initial_condition_diffusive_convergence_test
43+
44+
# define periodic boundary conditions everywhere
45+
boundary_conditions = boundary_condition_periodic
46+
boundary_conditions_parabolic = boundary_condition_periodic
47+
48+
###############################################################################
49+
### semidiscretization for sparsity detection ###
50+
51+
jac_detector = TracerSparsityDetector()
52+
# We need to construct the semidiscretization with the correct
53+
# sparsity-detection ready datatype, which is retrieved here
54+
jac_eltype = jacobian_eltype(real(solver), jac_detector)
55+
56+
# A semidiscretization collects data structures and functions for the spatial discretization
57+
semi_jac_type = SemidiscretizationHyperbolicParabolic(mesh,
58+
(equations, equations_parabolic),
59+
initial_condition, solver,
60+
uEltype = jac_eltype;
61+
solver_parabolic = ViscousFormulationBassiRebay1(),
62+
boundary_conditions = (boundary_conditions,
63+
boundary_conditions_parabolic))
64+
65+
tspan = (0.0, 1.5) # Re-used for wrapping `rhs_parabolic!` below
66+
67+
# Call `semidiscretize` to create the ODE problem to have access to the
68+
# initial condition based on which the sparsity pattern is computed
69+
ode_jac_type = semidiscretize(semi_jac_type, tspan)
70+
u0_ode = ode_jac_type.u0
71+
du_ode = similar(u0_ode)
72+
73+
###############################################################################
74+
### Compute the Jacobian sparsity pattern ###
75+
76+
# Only the parabolic part of the `SplitODEProblem` is treated implicitly so we only need the parabolic Jacobian
77+
# https://docs.sciml.ai/DiffEqDocs/stable/types/split_ode_types/#SciMLBase.SplitFunction
78+
79+
# Wrap the `Trixi.rhs_parabolic!` function to match the signature `f!(du, u)`, see
80+
# https://adrianhill.de/SparseConnectivityTracer.jl/stable/user/api/#ADTypes.jacobian_sparsity
81+
rhs_parabolic_wrapped! = (du_ode, u0_ode) -> Trixi.rhs_parabolic!(du_ode, u0_ode, semi_jac_type, tspan[1])
82+
83+
jac_prototype_parabolic = jacobian_sparsity(rhs_parabolic_wrapped!, du_ode, u0_ode, jac_detector)
84+
85+
# For most efficient solving we also want the coloring vector
86+
87+
coloring_prob = ColoringProblem(; structure = :nonsymmetric, partition = :column)
88+
coloring_alg = GreedyColoringAlgorithm(; decompression = :direct)
89+
coloring_result = coloring(jac_prototype_parabolic, coloring_prob, coloring_alg)
90+
coloring_vec_parabolic = column_colors(coloring_result)
91+
92+
###############################################################################
93+
### sparsity-aware semidiscretization and ode ###
94+
95+
# Semidiscretization for actual simulation. `eEltype` is here retrieved from `solver`
96+
semi_float_type = SemidiscretizationHyperbolicParabolic(mesh,
97+
(equations, equations_parabolic),
98+
initial_condition, solver;
99+
solver_parabolic = ViscousFormulationBassiRebay1(),
100+
boundary_conditions = (boundary_conditions,
101+
boundary_conditions_parabolic))
102+
103+
# Supply Jacobian prototype and coloring vector to the semidiscretization
104+
ode_jac_sparse = semidiscretize(semi_float_type, tspan,
105+
jac_prototype_parabolic = jac_prototype_parabolic,
106+
colorvec_parabolic = coloring_vec_parabolic)
107+
# using "dense" `ode = semidiscretize(semi_float_type, tspan)` is 4-6 times slower!
108+
109+
###############################################################################
110+
### callbacks ###
111+
112+
# At the beginning of the main loop, the SummaryCallback prints a summary of the simulation setup
113+
# and resets the timers
114+
summary_callback = SummaryCallback()
115+
116+
# The AnalysisCallback allows to analyse the solution in regular intervals and prints the results
117+
analysis_interval = 100
118+
analysis_callback = AnalysisCallback(semi_float_type, interval = analysis_interval)
119+
120+
# The AliveCallback prints short status information in regular intervals
121+
alive_callback = AliveCallback(analysis_interval = analysis_interval)
122+
123+
save_restart = SaveRestartCallback(interval = 100,
124+
save_final_restart = true)
125+
126+
# Note: No `stepsize_callback` due to (implicit) solver with adaptive timestep control
127+
callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_restart)
128+
129+
###############################################################################
130+
### solve the ODE problem ###
131+
132+
time_int_tol = 1.0e-9
133+
time_abs_tol = 1.0e-9
134+
135+
sol = solve(ode_jac_sparse, TRBDF2(; autodiff = AutoFiniteDiff());
136+
dt = 0.1, save_everystep = false,
137+
abstol = time_abs_tol, reltol = time_int_tol,
138+
ode_default_options()..., callback = callbacks)

src/semidiscretization/semidiscretization_hyperbolic_parabolic.jl

Lines changed: 68 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -264,15 +264,25 @@ function get_node_variables!(node_variables, u_ode,
264264
end
265265

266266
"""
267-
semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan)
267+
semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan;
268+
jac_prototype_parabolic::Union{AbstractMatrix, Nothing} = nothing,
269+
colorvec_parabolic::Union{AbstractVector, Nothing} = nothing)
268270
269271
Wrap the semidiscretization `semi` as a split ODE problem in the time interval `tspan`
270272
that can be passed to `solve` from the [SciML ecosystem](https://diffeq.sciml.ai/latest/).
271273
The parabolic right-hand side is the first function of the split ODE problem and
272274
will be used by default by the implicit part of IMEX methods from the
273275
SciML ecosystem.
276+
277+
Optional keyword arguments:
278+
- `jac_prototype_parabolic`: Expected to come from [SparseConnectivityTracer.jl](https://github.com/adrhill/SparseConnectivityTracer.jl).
279+
Specifies the sparsity structure of the parabolic function's Jacobian to enable e.g. efficient implicit time stepping.
280+
- `colorvec_parabolic`: Expected to come from [SparseMatrixColorings.jl](https://github.com/gdalle/SparseMatrixColorings.jl).
281+
Allows for even faster Jacobian computation. Not necessarily required when `jac_prototype_parabolic` is given.
274282
"""
275283
function semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan;
284+
jac_prototype_parabolic::Union{AbstractMatrix, Nothing} = nothing,
285+
colorvec_parabolic::Union{AbstractVector, Nothing} = nothing,
276286
reset_threads = true)
277287
# Optionally reset Polyester.jl threads. See
278288
# https://github.com/trixi-framework/Trixi.jl/issues/1583
@@ -286,15 +296,36 @@ function semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan;
286296
# mpi_isparallel() && MPI.Barrier(mpi_comm())
287297
# See https://github.com/trixi-framework/Trixi.jl/issues/328
288298
iip = true # is-inplace, i.e., we modify a vector when calling rhs_parabolic!, rhs!
289-
# Note that the IMEX time integration methods of OrdinaryDiffEq.jl treat the
290-
# first function implicitly and the second one explicitly. Thus, we pass the
291-
# stiffer parabolic function first.
292-
return SplitODEProblem{iip}(rhs_parabolic!, rhs!, u0_ode, tspan, semi)
299+
300+
# Check if Jacobian prototype is provided for sparse Jacobian
301+
if jac_prototype_parabolic !== nothing
302+
# Convert `jac_prototype_parabolic` to real type, as seen here:
303+
# https://docs.sciml.ai/DiffEqDocs/stable/tutorials/advanced_ode_example/#Declaring-a-Sparse-Jacobian-with-Automatic-Sparsity-Detection
304+
parabolic_ode = SciMLBase.ODEFunction(rhs_parabolic!,
305+
jac_prototype = convert.(eltype(u0_ode),
306+
jac_prototype_parabolic),
307+
colorvec = colorvec_parabolic) # coloring vector is optional
308+
309+
# Note that the IMEX time integration methods of OrdinaryDiffEq.jl treat the
310+
# first function implicitly and the second one explicitly. Thus, we pass the
311+
# stiffer parabolic function first.
312+
return SplitODEProblem{iip}(parabolic_ode, rhs!, u0_ode, tspan, semi)
313+
else
314+
# We could also construct an `ODEFunction` without the Jacobian here,
315+
# but we stick to the more light-weight direct in-place function `rhs_parabolic!`.
316+
317+
# Note that the IMEX time integration methods of OrdinaryDiffEq.jl treat the
318+
# first function implicitly and the second one explicitly. Thus, we pass the
319+
# stiffer parabolic function first.
320+
return SplitODEProblem{iip}(rhs_parabolic!, rhs!, u0_ode, tspan, semi)
321+
end
293322
end
294323

295324
"""
296325
semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan,
297-
restart_file::AbstractString)
326+
restart_file::AbstractString;
327+
jac_prototype_parabolic::Union{AbstractMatrix, Nothing} = nothing,
328+
colorvec_parabolic::Union{AbstractVector, Nothing} = nothing)
298329
299330
Wrap the semidiscretization `semi` as a split ODE problem in the time interval `tspan`
300331
that can be passed to `solve` from the [SciML ecosystem](https://diffeq.sciml.ai/latest/).
@@ -303,9 +334,17 @@ will be used by default by the implicit part of IMEX methods from the
303334
SciML ecosystem.
304335
305336
The initial condition etc. is taken from the `restart_file`.
337+
338+
Optional keyword arguments:
339+
- `jac_prototype_parabolic`: Expected to come from [SparseConnectivityTracer.jl](https://github.com/adrhill/SparseConnectivityTracer.jl).
340+
Specifies the sparsity structure of the parabolic function's Jacobian to enable e.g. efficient implicit time stepping.
341+
- `colorvec_parabolic`: Expected to come from [SparseMatrixColorings.jl](https://github.com/gdalle/SparseMatrixColorings.jl).
342+
Allows for even faster Jacobian computation. Not necessarily required when `jac_prototype_parabolic` is given.
306343
"""
307344
function semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan,
308345
restart_file::AbstractString;
346+
jac_prototype_parabolic::Union{AbstractMatrix, Nothing} = nothing,
347+
colorvec_parabolic::Union{AbstractVector, Nothing} = nothing,
309348
reset_threads = true)
310349
# Optionally reset Polyester.jl threads. See
311350
# https://github.com/trixi-framework/Trixi.jl/issues/1583
@@ -319,10 +358,29 @@ function semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan,
319358
# mpi_isparallel() && MPI.Barrier(mpi_comm())
320359
# See https://github.com/trixi-framework/Trixi.jl/issues/328
321360
iip = true # is-inplace, i.e., we modify a vector when calling rhs_parabolic!, rhs!
322-
# Note that the IMEX time integration methods of OrdinaryDiffEq.jl treat the
323-
# first function implicitly and the second one explicitly. Thus, we pass the
324-
# stiffer parabolic function first.
325-
return SplitODEProblem{iip}(rhs_parabolic!, rhs!, u0_ode, tspan, semi)
361+
362+
# Check if Jacobian prototype is provided for sparse Jacobian
363+
if jac_prototype_parabolic !== nothing
364+
# Convert `jac_prototype_parabolic` to real type, as seen here:
365+
# https://docs.sciml.ai/DiffEqDocs/stable/tutorials/advanced_ode_example/#Declaring-a-Sparse-Jacobian-with-Automatic-Sparsity-Detection
366+
parabolic_ode = SciMLBase.ODEFunction(rhs_parabolic!,
367+
jac_prototype = convert.(eltype(u0_ode),
368+
jac_prototype_parabolic),
369+
colorvec = colorvec_parabolic) # coloring vector is optional
370+
371+
# Note that the IMEX time integration methods of OrdinaryDiffEq.jl treat the
372+
# first function implicitly and the second one explicitly. Thus, we pass the
373+
# stiffer parabolic function first.
374+
return SplitODEProblem{iip}(parabolic_ode, rhs!, u0_ode, tspan, semi)
375+
else
376+
# We could also construct an `ODEFunction` without the Jacobian here,
377+
# but we stick to the more light-weight direct in-place function `rhs_parabolic!`.
378+
379+
# Note that the IMEX time integration methods of OrdinaryDiffEq.jl treat the
380+
# first function implicitly and the second one explicitly. Thus, we pass the
381+
# stiffer parabolic function first.
382+
return SplitODEProblem{iip}(rhs_parabolic!, rhs!, u0_ode, tspan, semi)
383+
end
326384
end
327385

328386
function rhs!(du_ode, u_ode, semi::SemidiscretizationHyperbolicParabolic, t)

0 commit comments

Comments
 (0)