Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/equations/compressible_navier_stokes_1d.jl
5586 views
1
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).
2
# Since these FMAs can increase the performance of many numerical algorithms,
3
# we need to opt-in explicitly.
4
# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.
5
@muladd begin
6
#! format: noindent
7
8
@doc raw"""
9
CompressibleNavierStokesDiffusion1D(equations; mu, Pr,
10
gradient_variables=GradientVariablesPrimitive())
11
12
Contains the diffusion (i.e. parabolic) terms applied
13
to mass, momenta, and total energy together with the advective terms from
14
the [`CompressibleEulerEquations1D`](@ref).
15
16
- `equations`: instance of the [`CompressibleEulerEquations1D`](@ref)
17
- `mu`: dynamic viscosity,
18
- `Pr`: Prandtl number,
19
- `gradient_variables`: which variables the gradients are taken with respect to.
20
Defaults to [`GradientVariablesPrimitive()`](@ref).
21
For an entropy stable formulation, use [`GradientVariablesEntropy()`](@ref).
22
23
Fluid properties such as the dynamic viscosity ``\mu`` can be provided in any consistent unit system, e.g.,
24
[``\mu``] = kg m⁻¹ s⁻¹.
25
The viscosity ``\mu`` may be a constant or a function of the current state, e.g.,
26
depending on temperature (Sutherland's law): ``\mu = \mu(T)``.
27
In the latter case, the function `mu` needs to have the signature `mu(u, equations)`.
28
29
The particular form of the compressible Navier-Stokes implemented is
30
```math
31
\frac{\partial}{\partial t}
32
\begin{pmatrix}
33
\rho \\ \rho v_1 \\ \rho e_{\text{total}}
34
\end{pmatrix}
35
+
36
\frac{\partial}{\partial x}
37
\begin{pmatrix}
38
\rho v_1 \\ \rho v_1^2 + p \\ (\rho e_{\text{total}} + p) v_1
39
\end{pmatrix}
40
=
41
\frac{\partial}{\partial x}
42
\begin{pmatrix}
43
0 \\ \tau \\ \tau v_1 - q
44
\end{pmatrix}
45
```
46
where the system is closed with the ideal gas assumption giving
47
```math
48
p = (\gamma - 1) \left( \rho e_{\text{total}} - \frac{1}{2} \rho v_1^2 \right)
49
```
50
as the pressure. The value of the adiabatic constant `gamma` is taken from the [`CompressibleEulerEquations1D`](@ref).
51
The terms on the right hand side of the system above
52
are built from the viscous stress
53
```math
54
\tau = \mu \frac{\partial}{\partial x} v_1
55
```
56
where the heat flux is
57
```math
58
q = -\kappa \frac{\partial}{\partial x} \left(T\right),\quad T = \frac{p}{R\rho}
59
```
60
where ``T`` is the temperature and ``\kappa`` is the thermal conductivity for Fourier's law.
61
Under the assumption that the gas has a constant Prandtl number,
62
the thermal conductivity is
63
```math
64
\kappa = \frac{\gamma \mu R}{(\gamma - 1)\textrm{Pr}}.
65
```
66
From this combination of temperature ``T`` and thermal conductivity ``\kappa`` we see
67
that the gas constant `R` cancels and the heat flux becomes
68
```math
69
q = -\kappa \frac{\partial}{\partial x} \left(T\right) = -\frac{\gamma \mu}{(\gamma - 1)\textrm{Pr}} \frac{\partial}{\partial x} \left(\frac{p}{\rho}\right)
70
```
71
which is the form implemented below in the [`flux`](@ref) function.
72
73
In one spatial dimensions we require gradients for two quantities, e.g.,
74
primitive quantities
75
```math
76
\frac{\partial}{\partial x} v_1,\, \frac{\partial}{\partial x} T
77
```
78
or the entropy variables
79
```math
80
\frac{\partial}{\partial x} w_2,\, \frac{\partial}{\partial x} w_3
81
```
82
where
83
```math
84
w_2 = \frac{\rho v_1}{p},\, w_3 = -\frac{\rho}{p}
85
```
86
"""
87
struct CompressibleNavierStokesDiffusion1D{GradientVariables, RealT <: Real, Mu,
88
E <: AbstractCompressibleEulerEquations{1}} <:
89
AbstractCompressibleNavierStokesDiffusion{1, 3, GradientVariables}
90
# TODO: parabolic
91
# Add NGRADS as a type parameter here and in AbstractEquationsParabolic, add `ngradients(...)` accessor function
92
93
mu::Mu # viscosity
94
Pr::RealT # Prandtl number
95
kappa::RealT # thermal diffusivity for Fourier's law
96
max_1_kappa::RealT # max(1, kappa) used for parabolic cfl => `max_diffusivity`
97
98
equations_hyperbolic::E # CompressibleEulerEquations1D
99
gradient_variables::GradientVariables # GradientVariablesPrimitive or GradientVariablesEntropy
100
end
101
102
# default to primitive gradient variables
103
function CompressibleNavierStokesDiffusion1D(equations::CompressibleEulerEquations1D;
104
mu, Prandtl,
105
gradient_variables = GradientVariablesPrimitive())
106
@unpack gamma, inv_gamma_minus_one = equations
107
108
Pr = promote_type(typeof(gamma), typeof(Prandtl))(Prandtl)
109
# Under the assumption of constant Prandtl number the thermal conductivity
110
# constant is kappa = gamma μ / ((gamma-1) Prandtl).
111
# Important note! Factor of μ is accounted for later in `flux`.
112
# This avoids recomputation of kappa for non-constant μ.
113
kappa = gamma * inv_gamma_minus_one / Pr
114
115
return CompressibleNavierStokesDiffusion1D{typeof(gradient_variables),
116
typeof(Pr), typeof(mu),
117
typeof(equations)}(mu, Pr, kappa,
118
max(one(kappa),
119
kappa),
120
equations,
121
gradient_variables)
122
end
123
124
# TODO: parabolic
125
# This is the flexibility a user should have to select the different gradient variable types
126
# varnames(::typeof(cons2prim) , ::CompressibleNavierStokesDiffusion1D) = ("v1", "T")
127
# varnames(::typeof(cons2entropy), ::CompressibleNavierStokesDiffusion1D) = ("w2", "w3")
128
129
function varnames(variable_mapping,
130
equations_parabolic::CompressibleNavierStokesDiffusion1D)
131
return varnames(variable_mapping, equations_parabolic.equations_hyperbolic)
132
end
133
134
# we specialize this function to compute gradients of primitive variables instead of
135
# conservative variables.
136
function gradient_variable_transformation(::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive})
137
return cons2prim
138
end
139
function gradient_variable_transformation(::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy})
140
return cons2entropy
141
end
142
143
# Explicit formulas for the diffusive Navier-Stokes fluxes are available, e.g., in Section 2
144
# of the paper by Rueda-Ramírez, Hennemann, Hindenlang, Winters, and Gassner
145
# "An Entropy Stable Nodal Discontinuous Galerkin Method for the resistive
146
# MHD Equations. Part II: Subcell Finite Volume Shock Capturing"
147
# where one sets the magnetic field components equal to 0.
148
function flux(u, gradients, orientation::Integer,
149
equations::CompressibleNavierStokesDiffusion1D)
150
# Here, `u` is assumed to be the "transformed" variables specified by `gradient_variable_transformation`.
151
_, v1, _ = convert_transformed_to_primitive(u, equations)
152
# Here `gradients` is assumed to contain the gradients of the primitive variables (rho, v1, T)
153
# either computed directly or reverse engineered from the gradient of the entropy variables
154
# by way of the `convert_gradient_variables` function.
155
_, dv1dx, dTdx = convert_derivative_to_primitive(u, gradients[1], equations)
156
157
# Viscous stress (tensor)
158
tau_11 = dv1dx
159
160
# Fourier's law q = -kappa * grad(T) = -kappa * grad(p / (R rho))
161
# with thermal diffusivity constant kappa = gamma μ R / ((gamma-1) Pr)
162
# Note, the gas constant cancels under this formulation, so it is not present
163
# in the implementation
164
q1 = equations.kappa * dTdx
165
166
# In the simplest cases, the user passed in `mu` or `mu()`
167
# (which returns just a constant) but
168
# more complex functions like Sutherland's law are possible.
169
# `dynamic_viscosity` is a helper function that handles both cases
170
# by dispatching on the type of `equations.mu`.
171
mu = dynamic_viscosity(u, equations)
172
173
# parabolic flux components in the x-direction
174
f1 = 0
175
f2 = tau_11 * mu
176
f3 = (v1 * tau_11 + q1) * mu
177
178
return SVector(f1, f2, f3)
179
end
180
181
@doc raw"""
182
max_diffusivity(u, equations_parabolic::CompressibleNavierStokesDiffusion1D)
183
184
# Returns
185
- `dynamic_viscosity(u, equations_parabolic) / u[1] * equations_parabolic.max_1_kappa`
186
where `max_1_kappa = max(one(kappa), kappa)` is computed in the constructor.
187
188
For the diffusive estimate we use the eigenvalues of the diffusivity matrix,
189
as suggested in Section 3.5 of
190
- Krais et. al (2021)
191
FLEXI: A high order discontinuous Galerkin framework for hyperbolic–parabolic conservation laws
192
[DOI: 10.1016/j.camwa.2020.05.004](https://doi.org/10.1016/j.camwa.2020.05.004)
193
194
For details on the derivation of eigenvalues of the diffusivity matrix
195
for the compressible Navier-Stokes equations see for instance
196
- Richard P. Dwight (2006)
197
Efficiency improvements of RANS-based analysis and optimization using implicit and adjoint methods on unstructured grids
198
PhD Thesis, University of Manchester
199
https://elib.dlr.de/50794/1/rdwight-PhDThesis-ImplicitAndAdjoint.pdf
200
See especially equations (2.79), (3.24), and (3.25) from Chapter 3.2.3
201
202
The eigenvalues of the diffusivity matrix in 1D are
203
``-\frac{\mu}{\rho} \{0, 1, \kappa\}``
204
and thus the largest absolute eigenvalue is
205
``\frac{\mu}{\rho} \max(1, \kappa)``.
206
"""
207
@inline function max_diffusivity(u,
208
equations_parabolic::CompressibleNavierStokesDiffusion1D)
209
# See for instance also the computation in FLUXO:
210
# https://github.com/project-fluxo/fluxo/blob/c7e0cc9b7fd4569dcab67bbb6e5a25c0a84859f1/src/equation/navierstokes/calctimestep.f90#L122-L128
211
#
212
# Accordingly, the spectral radius/largest absolute eigenvalue can be computed as:
213
return dynamic_viscosity(u, equations_parabolic) / u[1] *
214
equations_parabolic.max_1_kappa
215
end
216
217
# Convert conservative variables to primitive
218
@inline function cons2prim(u, equations::CompressibleNavierStokesDiffusion1D)
219
rho, rho_v1, _ = u
220
221
v1 = rho_v1 / rho
222
T = temperature(u, equations)
223
224
return SVector(rho, v1, T)
225
end
226
227
# Convert conservative variables to entropy
228
# TODO: parabolic. We can improve efficiency by not computing w_1, which involves logarithms
229
# This can be done by specializing `cons2entropy` and `entropy2cons` to `CompressibleNavierStokesDiffusion1D`,
230
# but this may be confusing to new users.
231
function cons2entropy(u, equations::CompressibleNavierStokesDiffusion1D)
232
return cons2entropy(u, equations.equations_hyperbolic)
233
end
234
function entropy2cons(w, equations::CompressibleNavierStokesDiffusion1D)
235
return entropy2cons(w, equations.equations_hyperbolic)
236
end
237
238
# the `flux` function takes in transformed variables `u` which depend on the type of the gradient variables.
239
# For CNS, it is simplest to formulate the parabolic terms in primitive variables, so we transform the transformed
240
# variables into primitive variables.
241
@inline function convert_transformed_to_primitive(u_transformed,
242
equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive})
243
return u_transformed
244
end
245
246
# TODO: parabolic. Make this more efficient!
247
@inline function convert_transformed_to_primitive(u_transformed,
248
equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy})
249
# note: this uses CompressibleNavierStokesDiffusion1D versions of cons2prim and entropy2cons
250
return cons2prim(entropy2cons(u_transformed, equations), equations)
251
end
252
253
# Takes the solution values `u` and gradient of the entropy variables (w_2, w_3) and
254
# reverse engineers the gradients to be terms of the primitive variables (v1, T).
255
# Helpful because then the diffusive fluxes have the same form as on paper.
256
# Note, the first component of `gradient_entropy_vars` contains gradient(rho) which is unused.
257
# TODO: parabolic; entropy stable parabolic terms
258
@inline function convert_derivative_to_primitive(u, gradient,
259
::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive})
260
return gradient
261
end
262
263
# the first argument is always the "transformed" variables.
264
@inline function convert_derivative_to_primitive(w, gradient_entropy_vars,
265
equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy})
266
267
# TODO: parabolic. This is inefficient to pass in transformed variables but then transform them back.
268
# We can fix this if we directly compute v1, T from the entropy variables
269
u = entropy2cons(w, equations) # calls a "modified" entropy2cons defined for CompressibleNavierStokesDiffusion1D
270
rho, rho_v1, _ = u
271
272
v1 = rho_v1 / rho
273
T = temperature(u, equations)
274
275
return SVector(gradient_entropy_vars[1],
276
T * (gradient_entropy_vars[2] + v1 * gradient_entropy_vars[3]), # grad(u) = T*(grad(w_2)+v1*grad(w_3))
277
T * T * gradient_entropy_vars[3])
278
end
279
280
# This routine is required because `prim2cons` is called in `initial_condition`, which
281
# is called with `equations::CompressibleEulerEquations1D`. This means it is inconsistent
282
# with `cons2prim(..., ::CompressibleNavierStokesDiffusion1D)` as defined above.
283
# TODO: parabolic. Is there a way to clean this up?
284
@inline function prim2cons(u, equations::CompressibleNavierStokesDiffusion1D)
285
return prim2cons(u, equations.equations_hyperbolic)
286
end
287
288
"""
289
temperature(u, equations::CompressibleNavierStokesDiffusion1D)
290
291
Compute the temperature from the conservative variables `u`.
292
In particular, this assumes a specific gas constant ``R = 1``:
293
```math
294
T = \\frac{p}{\\rho}
295
```
296
"""
297
@inline function temperature(u, equations::CompressibleNavierStokesDiffusion1D)
298
rho, rho_v1, rho_e_total = u
299
@unpack gamma = equations
300
301
p = (gamma - 1) * (rho_e_total - 0.5f0 * rho_v1^2 / rho)
302
T = p / rho # Corresponds to a specific gas constant R = 1
303
return T
304
end
305
306
@inline function velocity(u, equations::CompressibleNavierStokesDiffusion1D)
307
rho = u[1]
308
v1 = u[2] / rho
309
return v1
310
end
311
312
@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,
313
<:Adiabatic})(flux_inner,
314
u_inner,
315
orientation::Integer,
316
direction,
317
x,
318
t,
319
operator_type::Gradient,
320
equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive})
321
v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t,
322
equations)
323
return SVector(u_inner[1], v1, u_inner[3])
324
end
325
326
@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,
327
<:Adiabatic})(flux_inner,
328
u_inner,
329
orientation::Integer,
330
direction,
331
x,
332
t,
333
operator_type::Divergence,
334
equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive})
335
normal_heat_flux = boundary_condition.boundary_condition_heat_flux.boundary_value_normal_flux_function(x,
336
t,
337
equations)
338
v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t,
339
equations)
340
_, tau_1n, _ = flux_inner # extract fluxes for 2nd equation
341
normal_energy_flux = v1 * tau_1n + normal_heat_flux
342
return SVector(flux_inner[1], flux_inner[2], normal_energy_flux)
343
end
344
345
@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,
346
<:Isothermal})(flux_inner,
347
u_inner,
348
orientation::Integer,
349
direction,
350
x,
351
t,
352
operator_type::Gradient,
353
equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive})
354
v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t,
355
equations)
356
T = boundary_condition.boundary_condition_heat_flux.boundary_value_function(x, t,
357
equations)
358
return SVector(u_inner[1], v1, T)
359
end
360
361
@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,
362
<:Isothermal})(flux_inner,
363
u_inner,
364
orientation::Integer,
365
direction,
366
x,
367
t,
368
operator_type::Divergence,
369
equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive})
370
return flux_inner
371
end
372
373
# specialized BC impositions for GradientVariablesEntropy.
374
375
# This should return a SVector containing the boundary values of entropy variables.
376
# Here, `w_inner` are the transformed variables (e.g., entropy variables).
377
#
378
# Taken from "Entropy stable modal discontinuous Galerkin schemes and wall boundary conditions
379
# for the compressible Navier-Stokes equations" by Chan, Lin, Warburton 2022.
380
# DOI: 10.1016/j.jcp.2021.110723
381
@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,
382
<:Adiabatic})(flux_inner,
383
w_inner,
384
orientation::Integer,
385
direction,
386
x,
387
t,
388
operator_type::Gradient,
389
equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy})
390
v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t,
391
equations)
392
negative_rho_inv_p = w_inner[3] # w_3 = -rho / p
393
return SVector(w_inner[1], -v1 * negative_rho_inv_p, negative_rho_inv_p)
394
end
395
396
# this is actually identical to the specialization for GradientVariablesPrimitive, but included for completeness.
397
@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,
398
<:Adiabatic})(flux_inner,
399
w_inner,
400
orientation::Integer,
401
direction,
402
x,
403
t,
404
operator_type::Divergence,
405
equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy})
406
normal_heat_flux = boundary_condition.boundary_condition_heat_flux.boundary_value_normal_flux_function(x,
407
t,
408
equations)
409
v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t,
410
equations)
411
_, tau_1n, _ = flux_inner # extract fluxes for 2nd equation
412
normal_energy_flux = v1 * tau_1n + normal_heat_flux
413
return SVector(flux_inner[1], flux_inner[2], normal_energy_flux)
414
end
415
416
@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,
417
<:Isothermal})(flux_inner,
418
w_inner,
419
orientation::Integer,
420
direction,
421
x,
422
t,
423
operator_type::Gradient,
424
equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy})
425
v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t,
426
equations)
427
T = boundary_condition.boundary_condition_heat_flux.boundary_value_function(x, t,
428
equations)
429
430
# the entropy variables w2 = rho * v1 / p = v1 / T = -v1 * w3.
431
w3 = -1 / T
432
return SVector(w_inner[1], -v1 * w3, w3)
433
end
434
435
@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,
436
<:Isothermal})(flux_inner,
437
w_inner,
438
orientation::Integer,
439
direction,
440
x,
441
t,
442
operator_type::Divergence,
443
equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy})
444
return SVector(flux_inner[1], flux_inner[2], flux_inner[3])
445
end
446
end # @muladd
447
448