Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/docs/literate/src/files/adding_new_parabolic_terms.jl
5591 views
1
#src # Adding new parabolic terms.
2
3
# This demo illustrates the steps involved in adding new parabolic terms for the scalar
4
# advection equation. In particular, we will add an anisotropic diffusion. We begin by
5
# defining the hyperbolic (advection) part of the advection-diffusion equation.
6
7
using OrdinaryDiffEqLowStorageRK
8
using Trixi
9
10
advection_velocity = (1.0, 1.0)
11
equations_hyperbolic = LinearScalarAdvectionEquation2D(advection_velocity);
12
13
# ## Define a new parabolic equation type
14
#
15
# Next, we define a 2D parabolic diffusion term type. This is similar to [`LaplaceDiffusion2D`](@ref)
16
# except that the `diffusivity` field refers to a spatially constant diffusivity matrix now. Note that
17
# `ConstantAnisotropicDiffusion2D` has a field for `equations_hyperbolic`. It is useful to have
18
# information about the hyperbolic system available to the parabolic part so that we can reuse
19
# functions defined for hyperbolic equations (such as `varnames`).
20
#
21
# The abstract type `Trixi.AbstractEquationsParabolic` has three parameters: `NDIMS` (the spatial dimension,
22
# e.g., 1D, 2D, or 3D), `NVARS` (the number of variables), and `GradientVariable`, which we set as
23
# `GradientVariablesConservative`. This indicates that the gradient should be taken with respect to the
24
# conservative variables (e.g., the same variables used in `equations_hyperbolic`). Users can also take
25
# the gradient with respect to a different set of variables; see, for example, the implementation of
26
# [`CompressibleNavierStokesDiffusion2D`](@ref), which can utilize either "primitive" or "entropy" variables.
27
28
struct ConstantAnisotropicDiffusion2D{E, T} <:
29
Trixi.AbstractEquationsParabolic{2, 1, GradientVariablesConservative}
30
diffusivity::T
31
equations_hyperbolic::E
32
end
33
34
function varnames(variable_mapping, equations_parabolic::ConstantAnisotropicDiffusion2D)
35
return varnames(variable_mapping, equations_parabolic.equations_hyperbolic)
36
end
37
38
# Next, we define the parabolic flux function. We assume that the mixed hyperbolic-parabolic system
39
# is of the form
40
# ```math
41
# \partial_t u(t,x) + \partial_x (f_1(u) - g_1(u, \nabla u))
42
# + \partial_y (f_2(u) - g_2(u, \nabla u)) = 0
43
# ```
44
# where ``f_1(u)``, ``f_2(u)`` are the hyperbolic fluxes and ``g_1(u, \nabla u)``, ``g_2(u, \nabla u)`` denote
45
# the parabolic fluxes. For anisotropic diffusion, the parabolic fluxes are the first and second components
46
# of the matrix-vector product involving `diffusivity` and the gradient vector.
47
#
48
# Here, we specialize the flux to our new parabolic equation type `ConstantAnisotropicDiffusion2D`.
49
50
function Trixi.flux(u, gradients, orientation::Integer,
51
equations_parabolic::ConstantAnisotropicDiffusion2D)
52
@unpack diffusivity = equations_parabolic
53
dudx, dudy = gradients
54
if orientation == 1
55
return SVector(diffusivity[1, 1] * dudx + diffusivity[1, 2] * dudy)
56
else # if orientation == 2
57
return SVector(diffusivity[2, 1] * dudx + diffusivity[2, 2] * dudy)
58
end
59
end
60
61
# ## Defining boundary conditions
62
63
# Trixi.jl's implementation of parabolic terms discretizes both the gradient and divergence
64
# using weak formulation. In other words, we discretize the system
65
# ```math
66
# \begin{aligned}
67
# \bm{q} &= \nabla u \\
68
# \bm{\sigma} &= \begin{pmatrix} g_1(u, \bm{q}) \\ g_2(u, \bm{q}) \end{pmatrix} \\
69
# \text{parabolic contribution} &= \nabla \cdot \bm{\sigma}
70
# \end{aligned}
71
# ```
72
#
73
# Boundary data must be specified for all spatial derivatives, e.g., for both the gradient
74
# equation ``\bm{q} = \nabla u`` and the divergence of the parabolic flux
75
# ``\nabla \cdot \bm{\sigma}``. We account for this by introducing internal `Gradient`
76
# and `Divergence` types which are used to dispatch on each type of boundary condition.
77
#
78
# As an example, let us introduce a Dirichlet boundary condition with constant boundary data.
79
80
struct BoundaryConditionConstantDirichlet{T <: Real}
81
boundary_value::T
82
end
83
84
# This boundary condition contains only the field `boundary_value`, which we assume to be some
85
# real-valued constant which we will impose as the Dirichlet data on the boundary.
86
#
87
# Boundary conditions have generally been defined as "callable structs" (also known as "functors").
88
# For each boundary condition, we need to specify the appropriate boundary data to return for both
89
# the `Gradient` and `Divergence`. Since the gradient is operating on the solution `u`, the boundary
90
# data should be the value of `u`, and we can directly impose Dirichlet data.
91
92
@inline function (boundary_condition::BoundaryConditionConstantDirichlet)(flux_inner,
93
u_inner,
94
normal::AbstractVector,
95
x, t,
96
operator_type::Trixi.Gradient,
97
equations_parabolic::ConstantAnisotropicDiffusion2D)
98
return boundary_condition.boundary_value
99
end
100
101
# While the gradient acts on the solution `u`, the divergence acts on the parabolic flux ``\bm{\sigma}``.
102
# Thus, we have to supply boundary data for the `Divergence` operator that corresponds to ``\bm{\sigma}``.
103
# However, we've already imposed boundary data on `u` for a Dirichlet boundary condition, and imposing
104
# boundary data for ``\bm{\sigma}`` might overconstrain our problem.
105
#
106
# Thus, for the `Divergence` boundary data under a Dirichlet boundary condition, we simply return
107
# `flux_inner`, which is boundary data for ``\bm{\sigma}`` computed using the "inner" or interior solution.
108
# This way, we supply boundary data for the divergence operation without imposing any additional conditions.
109
110
@inline function (boundary_condition::BoundaryConditionConstantDirichlet)(flux_inner,
111
u_inner,
112
normal::AbstractVector,
113
x, t,
114
operator_type::Trixi.Divergence,
115
equations_parabolic::ConstantAnisotropicDiffusion2D)
116
return flux_inner
117
end
118
119
# ### A note on the choice of gradient variables
120
#
121
# It is often simpler to transform the solution variables (and solution gradients) to another set of
122
# variables prior to computing the parabolic fluxes (see [`CompressibleNavierStokesDiffusion2D`](@ref)
123
# for an example of this). If this is done, then the boundary condition for the `Gradient` operator
124
# should be modified accordingly as well.
125
#
126
# ## Putting things together
127
#
128
# Finally, we can instantiate our new parabolic equation type, define boundary conditions,
129
# and run a simulation. The specific anisotropic diffusion matrix we use produces more
130
# dissipation in the direction ``(1, -1)`` as an isotropic diffusion.
131
#
132
# For boundary conditions, we impose that ``u=1`` on the left wall, ``u=2`` on the bottom
133
# wall, and ``u = 0`` on the outflow walls. The initial condition is taken to be ``u = 0``.
134
# Note that we use `BoundaryConditionConstantDirichlet` only for the parabolic boundary
135
# conditions, since we have not defined its behavior for the hyperbolic part.
136
137
using Trixi: SMatrix
138
diffusivity = 5.0e-2 * SMatrix{2, 2}([2 -1; -1 2])
139
equations_parabolic = ConstantAnisotropicDiffusion2D(diffusivity, equations_hyperbolic);
140
141
boundary_conditions_hyperbolic = (;
142
x_neg = BoundaryConditionDirichlet((x, t, equations) -> SVector(1.0)),
143
y_neg = BoundaryConditionDirichlet((x, t, equations) -> SVector(2.0)),
144
y_pos = boundary_condition_do_nothing,
145
x_pos = boundary_condition_do_nothing)
146
147
boundary_conditions_parabolic = (; x_neg = BoundaryConditionConstantDirichlet(1.0),
148
y_neg = BoundaryConditionConstantDirichlet(2.0),
149
y_pos = BoundaryConditionConstantDirichlet(0.0),
150
x_pos = BoundaryConditionConstantDirichlet(0.0));
151
152
solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs)
153
coordinates_min = (-1.0, -1.0) # minimum coordinates (min(x), min(y))
154
coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y))
155
mesh = TreeMesh(coordinates_min, coordinates_max,
156
initial_refinement_level = 4,
157
periodicity = false, n_cells_max = 30_000) # set maximum capacity of tree data structure
158
159
initial_condition = (x, t, equations) -> SVector(0.0)
160
161
semi = SemidiscretizationHyperbolicParabolic(mesh,
162
(equations_hyperbolic, equations_parabolic),
163
initial_condition, solver;
164
boundary_conditions = (boundary_conditions_hyperbolic,
165
boundary_conditions_parabolic))
166
167
tspan = (0.0, 2.0)
168
ode = semidiscretize(semi, tspan)
169
callbacks = CallbackSet(SummaryCallback())
170
time_int_tol = 1.0e-6
171
sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol,
172
ode_default_options()..., callback = callbacks);
173
println("Number of timesteps: ", sol.destats.naccept)
174
175
using Plots
176
plot(sol)
177
178
# ### Enabling CFL-based timestepping
179
180
# In the example above, we used an adaptive timestep based on truncation error estimates.
181
# Alternatively, we can also use a CFL-based timestep control, cf. [`StepsizeCallback`](@ref).
182
# To be able to do so, we need to define [`max_diffusivity`](@ref) and
183
# [`have_constant_diffusivity`](@ref) for the new parabolic terms.
184
# In Trixi.jl, currently only the standard Laplace Diffusion and Compressible Navier-Stokes-Fourier
185
# parabolic terms are implemented.
186
# Since these equations have **isotropic** diffusivity, i.e., direction-independent coefficients,
187
# [`max_diffusivity`](@ref) is expected to return a scalar value.
188
#
189
# To comply with the existing code, we thus also return a scalar value for our anisotropic diffusion,
190
# estimated by the spectral radius (largest in magnitude eigenvalue) of the diffusivity matrix.
191
# Since diffusivity is constant, we do not need to repeatedly compute the spectral radius.
192
using LinearAlgebra: eigvals
193
lambda_max() = maximum(abs.(eigvals(diffusivity)))
194
195
# This function indicates that the diffusivity is constant, i.e.,
196
# does not depend on the solution `u` and thus needs not to be recomputed at every node.
197
@inline function Trixi.have_constant_diffusivity(::ConstantAnisotropicDiffusion2D)
198
return Trixi.True()
199
end
200
201
# Return the estimated maximum diffusivity for CFL calculations based on
202
# the spectral radius of the diffusivity matrix computed above
203
@inline function Trixi.max_diffusivity(equations_parabolic::ConstantAnisotropicDiffusion2D)
204
return lambda_max()
205
end
206
207
# We now supply the hyperbolic and parabolic CFL numbers
208
cfl_hyperbolic = 2.0 # Not restrictive for this example
209
cfl_parabolic = 0.21 # Restricts the timestep
210
stepsize_callback = StepsizeCallback(cfl = cfl_hyperbolic,
211
cfl_parabolic = cfl_parabolic)
212
213
# Add the stepsize callback to the existing callbacks
214
callbacks = CallbackSet(SummaryCallback(), stepsize_callback);
215
# Turn off adaptive time stepping based on error estimates
216
sol = solve(ode, RDPK3SpFSAL49();
217
adaptive = false, dt = stepsize_callback(ode),
218
ode_default_options()..., callback = callbacks);
219
println("Number of timesteps: ", sol.destats.naccept)
220
221
plot(sol)
222
223
# ## Package versions
224
225
# These results were obtained using the following versions.
226
227
using InteractiveUtils
228
versioninfo()
229
230
using Pkg
231
Pkg.status(["Trixi", "OrdinaryDiffEqLowStorageRK", "Plots"],
232
mode = PKGMODE_MANIFEST)
233
234