Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/examples/structured_2d_dgsem/elixir_lbm_eulerpolytropic_coupled.jl
2055 views
1
using OrdinaryDiffEqSSPRK
2
using Trixi
3
using LinearAlgebra: norm
4
5
###############################################################################
6
# Semidiscretizations of the polytropic Euler equations and Lattice-Boltzmann method (LBM)
7
# coupled using converter functions across their respective domains to generate a periodic system.
8
#
9
# In this elixir, we have a rectangular domain that is divided into a left and right half.
10
# On each half of the domain, an independent SemidiscretizationHyperbolic is created for each set of equations.
11
# The two systems are coupled in the x-direction and are periodic in the y-direction.
12
# For a high-level overview, see also the figure below:
13
#
14
# (-2, 1) ( 2, 1)
15
# ┌────────────────────┬────────────────────┐
16
# │ ↑ periodic ↑ │ ↑ periodic ↑ │
17
# │ │ │
18
# │ ========= │ ========= │
19
# │ system #1 │ system #2 │
20
# | Euler | LBM |
21
# │ ========= │ ========= │
22
# │ │ │
23
# │<-- coupled │<-- coupled │
24
# │ coupled -->│ coupled -->│
25
# │ │ │
26
# │ ↓ periodic ↓ │ ↓ periodic ↓ │
27
# └────────────────────┴────────────────────┘
28
# (-2, -1) ( 2, -1)
29
30
polydeg = 2
31
cells_per_dim_per_section = (16, 8)
32
33
###########
34
# system #1
35
# Euler
36
###########
37
38
### Setup taken from "elixir_eulerpolytropic_isothermal_wave.jl" ###
39
40
gamma = 1.0 # Isothermal gas
41
kappa = 1.0 # Scaling factor for the pressure, must fit to LBM `c_s`
42
eqs_euler = PolytropicEulerEquations2D(gamma, kappa)
43
44
volume_flux = flux_winters_etal
45
solver_euler = DGSEM(polydeg = polydeg, surface_flux = flux_hll,
46
volume_integral = VolumeIntegralFluxDifferencing(volume_flux))
47
48
# Linear pressure wave/Gaussian bump moving in the positive x-direction.
49
function initial_condition_pressure_bump(x, t, equations::PolytropicEulerEquations2D)
50
rho = ((1.0 + 0.01 * exp(-(x[1] - 1)^2 / 0.1)) / equations.kappa)^(1 / equations.gamma)
51
v1 = ((0.01 * exp(-(x[1] - 1)^2 / 0.1)) / equations.kappa)
52
v2 = 0.0
53
54
return prim2cons(SVector(rho, v1, v2), equations)
55
end
56
initial_condition_euler = initial_condition_pressure_bump
57
58
coords_min_euler = (-2.0, -1.0)
59
coords_max_euler = (0.0, 1.0)
60
mesh_euler = StructuredMesh(cells_per_dim_per_section,
61
coords_min_euler, coords_max_euler,
62
periodicity = (false, true))
63
64
# Use macroscopic variables derived from LBM populations
65
# as boundary values for the Euler equations
66
function coupling_function_LBM2Euler(x, u, equations_other, equations_own)
67
rho, v1, v2, _ = cons2macroscopic(u, equations_other)
68
return prim2cons(SVector(rho, v1, v2), equations_own)
69
end
70
71
boundary_conditions_euler = (x_neg = BoundaryConditionCoupled(2, (:end, :i_forward),
72
Float64,
73
coupling_function_LBM2Euler),
74
x_pos = BoundaryConditionCoupled(2, (:begin, :i_forward),
75
Float64,
76
coupling_function_LBM2Euler),
77
y_neg = boundary_condition_periodic,
78
y_pos = boundary_condition_periodic)
79
80
semi_euler = SemidiscretizationHyperbolic(mesh_euler, eqs_euler, initial_condition_euler,
81
solver_euler;
82
boundary_conditions = boundary_conditions_euler)
83
84
###########
85
# system #2
86
# LBM
87
###########
88
89
# Results in c_s = c/sqrt(3) = 1.
90
# This in turn implies that also in the LBM, p = c_s^2 * rho = 1 * rho = kappa * rho holds
91
# This is absolutely essential for the correct coupling between the two systems.
92
c = sqrt(3)
93
94
# Reference values `rho0, u0` correspond to the initial condition of the Euler equations.
95
# The gas should be inviscid (Re = Inf) to be consistent with the inviscid Euler equations.
96
# The Mach number `Ma` is computed internally from the speed of sound `c_s = c / sqrt(3)` and `u0`.
97
eqs_lbm = LatticeBoltzmannEquations2D(c = c, Re = Inf, rho0 = 1.0, u0 = 0.0, Ma = nothing)
98
99
# Quick & dirty implementation of the `flux_godunov` for Cartesian, yet structured meshes.
100
@inline function Trixi.flux_godunov(u_ll, u_rr, normal_direction::AbstractVector,
101
equations::LatticeBoltzmannEquations2D)
102
RealT = eltype(normal_direction)
103
if isapprox(normal_direction[2], zero(RealT), atol = 2 * eps(RealT))
104
v_alpha = equations.v_alpha1 * abs(normal_direction[1])
105
elseif isapprox(normal_direction[1], zero(RealT), atol = 2 * eps(RealT))
106
v_alpha = equations.v_alpha2 * abs(normal_direction[2])
107
else
108
error("Invalid normal direction for flux_godunov: $normal_direction")
109
end
110
return 0.5f0 * (v_alpha .* (u_ll + u_rr) - abs.(v_alpha) .* (u_rr - u_ll))
111
end
112
113
solver_lbm = DGSEM(polydeg = 2, surface_flux = flux_godunov)
114
115
function initial_condition_lbm(x, t, equations::LatticeBoltzmannEquations2D)
116
rho = (1.0 + 0.01 * exp(-(x[1] - 1)^2 / 0.1))
117
v1 = 0.01 * exp(-(x[1] - 1)^2 / 0.1)
118
119
v2 = 0.0
120
121
return equilibrium_distribution(rho, v1, v2, equations)
122
end
123
124
coords_min_lbm = (0.0, -1.0)
125
coords_max_lbm = (2.0, 1.0)
126
mesh_lbm = StructuredMesh(cells_per_dim_per_section,
127
coords_min_lbm, coords_max_lbm,
128
periodicity = (false, true))
129
130
# Supply equilibrium (Maxwellian) distribution function computed
131
# from the Euler-variables as boundary values for the LBM equations
132
function coupling_function_Euler2LBM(x, u, equations_other, equations_own)
133
u_prim_euler = cons2prim(u, equations_other)
134
rho = u_prim_euler[1]
135
v1 = u_prim_euler[2]
136
v2 = u_prim_euler[3]
137
138
return equilibrium_distribution(rho, v1, v2, equations_own)
139
end
140
141
boundary_conditions_lbm = (x_neg = BoundaryConditionCoupled(1, (:end, :i_forward),
142
Float64,
143
coupling_function_Euler2LBM),
144
x_pos = BoundaryConditionCoupled(1, (:begin, :i_forward),
145
Float64,
146
coupling_function_Euler2LBM),
147
y_neg = boundary_condition_periodic,
148
y_pos = boundary_condition_periodic)
149
150
semi_lbm = SemidiscretizationHyperbolic(mesh_lbm, eqs_lbm, initial_condition_lbm,
151
solver_lbm;
152
boundary_conditions = boundary_conditions_lbm)
153
154
# Create a semidiscretization that bundles the two semidiscretizations
155
semi = SemidiscretizationCoupled(semi_euler, semi_lbm)
156
157
###############################################################################
158
# ODE solvers, callbacks etc.
159
160
tspan = (0.0, 10.0)
161
ode = semidiscretize(semi, tspan)
162
163
summary_callback = SummaryCallback()
164
165
analysis_interval = 100
166
analysis_callback_euler = AnalysisCallback(semi_euler, interval = analysis_interval)
167
analysis_callback_lbm = AnalysisCallback(semi_lbm, interval = analysis_interval)
168
analysis_callback = AnalysisCallbackCoupled(semi,
169
analysis_callback_euler,
170
analysis_callback_lbm)
171
172
alive_callback = AliveCallback(analysis_interval = analysis_interval)
173
174
# Need to implement `cons2macroscopic` for `PolytropicEulerEquations2D`
175
# in order to be able to use this in the `SaveSolutionCallback` below
176
@inline function Trixi.cons2macroscopic(u, equations::PolytropicEulerEquations2D)
177
u_prim = cons2prim(u, equations)
178
p = pressure(u, equations)
179
return SVector(u_prim[1], u_prim[2], u_prim[3], p)
180
end
181
function Trixi.varnames(::typeof(cons2macroscopic), ::PolytropicEulerEquations2D)
182
("rho", "v1", "v2", "p")
183
end
184
185
save_solution = SaveSolutionCallback(interval = 50,
186
save_initial_solution = true,
187
save_final_solution = true,
188
solution_variables = cons2macroscopic)
189
190
cfl = 2.0
191
stepsize_callback = StepsizeCallback(cfl = cfl)
192
193
# Need special version of the LBM collision callback for a `SemidiscretizationCoupled`
194
@inline function Trixi.lbm_collision_callback(integrator)
195
dt = get_proposed_dt(integrator)
196
semi_coupled = integrator.p # Here `p` is the `SemidiscretizationCoupled`
197
u_ode_full = integrator.u # ODE Vector for the entire coupled system
198
for (semi_index, semi_i) in enumerate(semi_coupled.semis)
199
mesh, equations, solver, cache = Trixi.mesh_equations_solver_cache(semi_i)
200
if equations isa LatticeBoltzmannEquations2D
201
@unpack collision_op = equations
202
203
u_ode_i = Trixi.get_system_u_ode(u_ode_full, semi_index, semi_coupled)
204
u = Trixi.wrap_array(u_ode_i, mesh, equations, solver, cache)
205
206
Trixi.@trixi_timeit Trixi.timer() "LBM collision" Trixi.apply_collision!(u, dt,
207
collision_op,
208
mesh,
209
equations,
210
solver,
211
cache)
212
end
213
end
214
215
return nothing
216
end
217
218
collision_callback = LBMCollisionCallback()
219
220
callbacks = CallbackSet(summary_callback,
221
analysis_callback, alive_callback,
222
save_solution,
223
stepsize_callback,
224
collision_callback)
225
226
###############################################################################
227
# run the simulation
228
229
sol = solve(ode, SSPRK83();
230
dt = 0.01, # solve needs some value here but it will be overwritten by the stepsize_callback
231
ode_default_options()..., callback = callbacks);
232
233