Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/equations/compressible_euler_quasi_1d.jl
2055 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
CompressibleEulerEquationsQuasi1D(gamma)
10
11
The quasi-1d compressible Euler equations (see Chan et al. [DOI: 10.48550/arXiv.2307.12089](https://doi.org/10.48550/arXiv.2307.12089) for details)
12
```math
13
\frac{\partial}{\partial t}
14
\begin{pmatrix}
15
a \rho \\ a \rho v_1 \\ a e
16
\end{pmatrix}
17
+
18
\frac{\partial}{\partial x}
19
\begin{pmatrix}
20
a \rho v_1 \\ a \rho v_1^2 \\ a v_1 (e +p)
21
\end{pmatrix}
22
+
23
a \frac{\partial}{\partial x}
24
\begin{pmatrix}
25
0 \\ p \\ 0
26
\end{pmatrix}
27
=
28
\begin{pmatrix}
29
0 \\ 0 \\ 0
30
\end{pmatrix}
31
```
32
for an ideal gas with ratio of specific heats `gamma` in one space dimension.
33
Here, ``\rho`` is the density, ``v_1`` the velocity, ``e`` the specific total energy **rather than** specific internal energy,
34
``a`` the (possibly) variable nozzle width, and
35
```math
36
p = (\gamma - 1) \left( e - \frac{1}{2} \rho v_1^2 \right)
37
```
38
the pressure.
39
40
The nozzle width function ``a(x)`` is set inside the initial condition routine
41
for a particular problem setup. To test the conservative form of the compressible Euler equations one can set the
42
nozzle width variable ``a`` to one.
43
44
In addition to the unknowns, Trixi.jl currently stores the nozzle width values at the approximation points
45
despite being fixed in time.
46
This affects the implementation and use of these equations in various ways:
47
* The flux values corresponding to the nozzle width must be zero.
48
* The nozzle width values must be included when defining initial conditions, boundary conditions or
49
source terms.
50
* [`AnalysisCallback`](@ref) analyzes this variable.
51
* Trixi.jl's visualization tools will visualize the nozzle width by default.
52
"""
53
struct CompressibleEulerEquationsQuasi1D{RealT <: Real} <:
54
AbstractCompressibleEulerEquations{1, 4}
55
gamma::RealT # ratio of specific heats
56
inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications
57
58
function CompressibleEulerEquationsQuasi1D(gamma)
59
γ, inv_gamma_minus_one = promote(gamma, inv(gamma - 1))
60
new{typeof(γ)}(γ, inv_gamma_minus_one)
61
end
62
end
63
64
have_nonconservative_terms(::CompressibleEulerEquationsQuasi1D) = True()
65
function varnames(::typeof(cons2cons), ::CompressibleEulerEquationsQuasi1D)
66
("a_rho", "a_rho_v1", "a_e", "a")
67
end
68
function varnames(::typeof(cons2prim), ::CompressibleEulerEquationsQuasi1D)
69
("rho", "v1", "p", "a")
70
end
71
72
"""
73
initial_condition_convergence_test(x, t, equations::CompressibleEulerEquationsQuasi1D)
74
75
A smooth initial condition used for convergence tests in combination with
76
[`source_terms_convergence_test`](@ref)
77
(and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains).
78
"""
79
function initial_condition_convergence_test(x, t,
80
equations::CompressibleEulerEquationsQuasi1D)
81
RealT = eltype(x)
82
c = 2
83
A = convert(RealT, 0.1)
84
L = 2
85
f = 1.0f0 / L
86
ω = 2 * convert(RealT, pi) * f
87
ini = c + A * sin(ω * (x[1] - t))
88
89
rho = ini
90
v1 = 1
91
e = ini^2 / rho
92
p = (equations.gamma - 1) * (e - 0.5f0 * rho * v1^2)
93
a = 1.5f0 - 0.5f0 * cospi(x[1])
94
95
return prim2cons(SVector(rho, v1, p, a), equations)
96
end
97
98
"""
99
source_terms_convergence_test(u, x, t, equations::CompressibleEulerEquationsQuasi1D)
100
101
Source terms used for convergence tests in combination with
102
[`initial_condition_convergence_test`](@ref)
103
(and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains).
104
105
This manufactured solution source term is specifically designed for the mozzle width 'a(x) = 1.5 - 0.5 * cos(x[1] * pi)'
106
as defined in [`initial_condition_convergence_test`](@ref).
107
"""
108
@inline function source_terms_convergence_test(u, x, t,
109
equations::CompressibleEulerEquationsQuasi1D)
110
# Same settings as in `initial_condition_convergence_test`.
111
# Derivatives calculated with ForwardDiff.jl
112
RealT = eltype(u)
113
c = 2
114
A = convert(RealT, 0.1)
115
L = 2
116
f = 1.0f0 / L
117
ω = 2 * convert(RealT, pi) * f
118
x1, = x
119
ini(x1, t) = c + A * sin(ω * (x1 - t))
120
121
rho(x1, t) = ini(x1, t)
122
v1(x1, t) = 1
123
e(x1, t) = ini(x1, t)^2 / rho(x1, t)
124
p1(x1, t) = (equations.gamma - 1) * (e(x1, t) - 0.5f0 * rho(x1, t) * v1(x1, t)^2)
125
a(x1, t) = 1.5f0 - 0.5f0 * cospi(x1)
126
127
arho(x1, t) = a(x1, t) * rho(x1, t)
128
arhou(x1, t) = arho(x1, t) * v1(x1, t)
129
aE(x1, t) = a(x1, t) * e(x1, t)
130
131
darho_dt(x1, t) = ForwardDiff.derivative(t -> arho(x1, t), t)
132
darhou_dx(x1, t) = ForwardDiff.derivative(x1 -> arhou(x1, t), x1)
133
134
arhouu(x1, t) = arhou(x1, t) * v1(x1, t)
135
darhou_dt(x1, t) = ForwardDiff.derivative(t -> arhou(x1, t), t)
136
darhouu_dx(x1, t) = ForwardDiff.derivative(x1 -> arhouu(x1, t), x1)
137
dp1_dx(x1, t) = ForwardDiff.derivative(x1 -> p1(x1, t), x1)
138
139
auEp(x1, t) = a(x1, t) * v1(x1, t) * (e(x1, t) + p1(x1, t))
140
daE_dt(x1, t) = ForwardDiff.derivative(t -> aE(x1, t), t)
141
dauEp_dx(x1, t) = ForwardDiff.derivative(x1 -> auEp(x1, t), x1)
142
143
du1 = darho_dt(x1, t) + darhou_dx(x1, t)
144
du2 = darhou_dt(x1, t) + darhouu_dx(x1, t) + a(x1, t) * dp1_dx(x1, t)
145
du3 = daE_dt(x1, t) + dauEp_dx(x1, t)
146
147
return SVector(du1, du2, du3, 0)
148
end
149
150
# Calculate 1D flux for a single point
151
@inline function flux(u, orientation::Integer,
152
equations::CompressibleEulerEquationsQuasi1D)
153
a_rho, a_rho_v1, a_e, a = u
154
rho, v1, p, a = cons2prim(u, equations)
155
e = a_e / a
156
157
# Ignore orientation since it is always "1" in 1D
158
f1 = a_rho_v1
159
f2 = a_rho_v1 * v1
160
f3 = a * v1 * (e + p)
161
162
return SVector(f1, f2, f3, 0)
163
end
164
165
"""
166
flux_nonconservative_chan_etal(u_ll, u_rr, orientation::Integer,
167
equations::CompressibleEulerEquationsQuasi1D)
168
flux_nonconservative_chan_etal(u_ll, u_rr, normal_direction,
169
equations::CompressibleEulerEquationsQuasi1D)
170
flux_nonconservative_chan_etal(u_ll, u_rr, normal_ll, normal_rr,
171
equations::CompressibleEulerEquationsQuasi1D)
172
173
Non-symmetric two-point volume flux discretizing the nonconservative (source) term
174
that contains the gradient of the pressure [`CompressibleEulerEquationsQuasi1D`](@ref)
175
and the nozzle width.
176
177
Further details are available in the paper:
178
- Jesse Chan, Khemraj Shukla, Xinhui Wu, Ruofeng Liu, Prani Nalluri (2023)
179
High order entropy stable schemes for the quasi-one-dimensional
180
shallow water and compressible Euler equations
181
[DOI: 10.48550/arXiv.2307.12089](https://doi.org/10.48550/arXiv.2307.12089)
182
"""
183
@inline function flux_nonconservative_chan_etal(u_ll, u_rr, orientation::Integer,
184
equations::CompressibleEulerEquationsQuasi1D)
185
#Variables
186
_, _, p_ll, a_ll = cons2prim(u_ll, equations)
187
_, _, p_rr, _ = cons2prim(u_rr, equations)
188
189
# For flux differencing using non-conservative terms, we return the
190
# non-conservative flux scaled by 2. This cancels with a factor of 0.5
191
# in the arithmetic average of {p}.
192
p_avg = p_ll + p_rr
193
194
return SVector(0, a_ll * p_avg, 0, 0)
195
end
196
197
# While `normal_direction` isn't strictly necessary in 1D, certain solvers assume that
198
# the normal component is incorporated into the numerical flux.
199
#
200
# See `flux(u, normal_direction::AbstractVector, equations::AbstractEquations{1})` for a
201
# similar implementation.
202
@inline function flux_nonconservative_chan_etal(u_ll, u_rr,
203
normal_direction::AbstractVector,
204
equations::CompressibleEulerEquationsQuasi1D)
205
return normal_direction[1] *
206
flux_nonconservative_chan_etal(u_ll, u_rr, 1, equations)
207
end
208
209
@inline function flux_nonconservative_chan_etal(u_ll, u_rr,
210
normal_ll::AbstractVector,
211
normal_rr::AbstractVector,
212
equations::CompressibleEulerEquationsQuasi1D)
213
# normal_ll should be equal to normal_rr in 1D
214
return flux_nonconservative_chan_etal(u_ll, u_rr, normal_ll, equations)
215
end
216
217
"""
218
@inline function flux_chan_etal(u_ll, u_rr, orientation::Integer,
219
equations::CompressibleEulerEquationsQuasi1D)
220
221
Conservative (symmetric) part of the entropy conservative flux for quasi 1D compressible Euler equations split form.
222
This flux is a generalization of [`flux_ranocha`](@ref) for [`CompressibleEulerEquations1D`](@ref).
223
Further details are available in the paper:
224
- Jesse Chan, Khemraj Shukla, Xinhui Wu, Ruofeng Liu, Prani Nalluri (2023)
225
High order entropy stable schemes for the quasi-one-dimensional
226
shallow water and compressible Euler equations
227
[DOI: 10.48550/arXiv.2307.12089](https://doi.org/10.48550/arXiv.2307.12089)
228
"""
229
@inline function flux_chan_etal(u_ll, u_rr, orientation::Integer,
230
equations::CompressibleEulerEquationsQuasi1D)
231
# Unpack left and right state
232
rho_ll, v1_ll, p_ll, a_ll = cons2prim(u_ll, equations)
233
rho_rr, v1_rr, p_rr, a_rr = cons2prim(u_rr, equations)
234
235
# Compute the necessary mean values
236
rho_mean = ln_mean(rho_ll, rho_rr)
237
# Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)`
238
# in exact arithmetic since
239
# log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ)
240
# = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ)
241
inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll)
242
v1_avg = 0.5f0 * (v1_ll + v1_rr)
243
a_v1_avg = 0.5f0 * (a_ll * v1_ll + a_rr * v1_rr)
244
p_avg = 0.5f0 * (p_ll + p_rr)
245
velocity_square_avg = 0.5f0 * (v1_ll * v1_rr)
246
247
# Calculate fluxes
248
# Ignore orientation since it is always "1" in 1D
249
f1 = rho_mean * a_v1_avg
250
f2 = rho_mean * a_v1_avg * v1_avg
251
f3 = f1 * (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) +
252
0.5f0 * (p_ll * a_rr * v1_rr + p_rr * a_ll * v1_ll)
253
254
return SVector(f1, f2, f3, 0)
255
end
256
257
# While `normal_direction` isn't strictly necessary in 1D, certain solvers assume that
258
# the normal component is incorporated into the numerical flux.
259
#
260
# See `flux(u, normal_direction::AbstractVector, equations::AbstractEquations{1})` for a
261
# similar implementation.
262
@inline function flux_chan_etal(u_ll, u_rr, normal_direction::AbstractVector,
263
equations::CompressibleEulerEquationsQuasi1D)
264
return normal_direction[1] * flux_chan_etal(u_ll, u_rr, 1, equations)
265
end
266
267
# Calculate estimates for maximum wave speed for local Lax-Friedrichs-type dissipation as the
268
# maximum velocity magnitude plus the maximum speed of sound
269
@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer,
270
equations::CompressibleEulerEquationsQuasi1D)
271
a_rho_ll, a_rho_v1_ll, a_e_ll, a_ll = u_ll
272
a_rho_rr, a_rho_v1_rr, a_e_rr, a_rr = u_rr
273
274
# Calculate primitive variables and speed of sound
275
rho_ll = a_rho_ll / a_ll
276
e_ll = a_e_ll / a_ll
277
v1_ll = a_rho_v1_ll / a_rho_ll
278
v_mag_ll = abs(v1_ll)
279
p_ll = (equations.gamma - 1) * (e_ll - 0.5f0 * rho_ll * v_mag_ll^2)
280
c_ll = sqrt(equations.gamma * p_ll / rho_ll)
281
rho_rr = a_rho_rr / a_rr
282
e_rr = a_e_rr / a_rr
283
v1_rr = a_rho_v1_rr / a_rho_rr
284
v_mag_rr = abs(v1_rr)
285
p_rr = (equations.gamma - 1) * (e_rr - 0.5f0 * rho_rr * v_mag_rr^2)
286
c_rr = sqrt(equations.gamma * p_rr / rho_rr)
287
288
return max(v_mag_ll, v_mag_rr) + max(c_ll, c_rr)
289
end
290
291
# Less "cautious", i.e., less overestimating `λ_max` compared to `max_abs_speed_naive`
292
@inline function max_abs_speed(u_ll, u_rr, orientation::Integer,
293
equations::CompressibleEulerEquationsQuasi1D)
294
a_rho_ll, a_rho_v1_ll, a_e_ll, a_ll = u_ll
295
a_rho_rr, a_rho_v1_rr, a_e_rr, a_rr = u_rr
296
297
# Calculate primitive variables and speed of sound
298
rho_ll = a_rho_ll / a_ll
299
e_ll = a_e_ll / a_ll
300
v1_ll = a_rho_v1_ll / a_rho_ll
301
v_mag_ll = abs(v1_ll)
302
p_ll = (equations.gamma - 1) * (e_ll - 0.5f0 * rho_ll * v_mag_ll^2)
303
c_ll = sqrt(equations.gamma * p_ll / rho_ll)
304
rho_rr = a_rho_rr / a_rr
305
e_rr = a_e_rr / a_rr
306
v1_rr = a_rho_v1_rr / a_rho_rr
307
v_mag_rr = abs(v1_rr)
308
p_rr = (equations.gamma - 1) * (e_rr - 0.5f0 * rho_rr * v_mag_rr^2)
309
c_rr = sqrt(equations.gamma * p_rr / rho_rr)
310
311
return max(v_mag_ll + c_ll, v_mag_rr + c_rr)
312
end
313
314
@inline function max_abs_speeds(u, equations::CompressibleEulerEquationsQuasi1D)
315
a_rho, a_rho_v1, a_e, a = u
316
rho = a_rho / a
317
v1 = a_rho_v1 / a_rho
318
e = a_e / a
319
p = (equations.gamma - 1) * (e - 0.5f0 * rho * v1^2)
320
c = sqrt(equations.gamma * p / rho)
321
322
return (abs(v1) + c,)
323
end
324
325
# Convert conservative variables to primitive. We use the convention that the primitive
326
# variables for the quasi-1D equations are `(rho, v1, p)` (i.e., the same as the primitive
327
# variables for `CompressibleEulerEquations1D`)
328
@inline function cons2prim(u, equations::CompressibleEulerEquationsQuasi1D)
329
a_rho, a_rho_v1, a_e, a = u
330
q = cons2prim(SVector(a_rho, a_rho_v1, a_e) / a,
331
CompressibleEulerEquations1D(equations.gamma))
332
333
return SVector(q[1], q[2], q[3], a)
334
end
335
336
# The entropy for the quasi-1D compressible Euler equations is the entropy for the
337
# 1D compressible Euler equations scaled by the channel width `a`.
338
@inline function entropy(u, equations::CompressibleEulerEquationsQuasi1D)
339
a_rho, a_rho_v1, a_e, a = u
340
return a * entropy(SVector(a_rho, a_rho_v1, a_e) / a,
341
CompressibleEulerEquations1D(equations.gamma))
342
end
343
344
# Convert conservative variables to entropy. The entropy variables for the
345
# quasi-1D compressible Euler equations are identical to the entropy variables
346
# for the standard Euler equations for an appropriate definition of `entropy`.
347
@inline function cons2entropy(u, equations::CompressibleEulerEquationsQuasi1D)
348
a_rho, a_rho_v1, a_e, a = u
349
w = cons2entropy(SVector(a_rho, a_rho_v1, a_e) / a,
350
CompressibleEulerEquations1D(equations.gamma))
351
352
# we follow the convention for other spatially-varying equations and return the spatially
353
# varying coefficient `a` as the final entropy variable.
354
return SVector(w[1], w[2], w[3], a)
355
end
356
357
@inline function entropy2cons(w, equations::CompressibleEulerEquationsQuasi1D)
358
w_rho, w_rho_v1, w_rho_e, a = w
359
u = entropy2cons(SVector(w_rho, w_rho_v1, w_rho_e),
360
CompressibleEulerEquations1D(equations.gamma))
361
return SVector(a * u[1], a * u[2], a * u[3], a)
362
end
363
364
# Convert primitive to conservative variables
365
@inline function prim2cons(u, equations::CompressibleEulerEquationsQuasi1D)
366
rho, v1, p, a = u
367
q = prim2cons(u, CompressibleEulerEquations1D(equations.gamma))
368
369
return SVector(a * q[1], a * q[2], a * q[3], a)
370
end
371
372
@inline function density(u, equations::CompressibleEulerEquationsQuasi1D)
373
a_rho, _, _, a = u
374
rho = a_rho / a
375
return rho
376
end
377
378
@inline function pressure(u, equations::CompressibleEulerEquationsQuasi1D)
379
a_rho, a_rho_v1, a_e, a = u
380
return pressure(SVector(a_rho, a_rho_v1, a_e) / a,
381
CompressibleEulerEquations1D(equations.gamma))
382
end
383
384
@inline function density_pressure(u, equations::CompressibleEulerEquationsQuasi1D)
385
a_rho, a_rho_v1, a_e, a = u
386
return density_pressure(SVector(a_rho, a_rho_v1, a_e) / a,
387
CompressibleEulerEquations1D(equations.gamma))
388
end
389
end # @muladd
390
391