Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/docs/literate/src/files/structured_mesh_mapping.jl
5591 views
1
#src # Structured mesh with curvilinear mapping
2
3
# Here, we want to introduce another mesh type of [Trixi.jl](https://github.com/trixi-framework/Trixi.jl).
4
# More precisely, this tutorial is about the curved mesh type [`StructuredMesh`](@ref) supporting
5
# curved meshes.
6
7
# # Creating a curved mesh
8
# There are two basic options to define a curved [`StructuredMesh`](@ref) in Trixi.jl. You can
9
# implement curves for the domain boundaries, or alternatively, set up directly the complete
10
# transformation mapping. We now present one short example each.
11
12
# ## Mesh defined by domain boundary curves
13
# Both examples are based on a semdiscretization of the 2D compressible Euler equations.
14
15
using OrdinaryDiffEqLowStorageRK
16
using Trixi
17
18
equations = CompressibleEulerEquations2D(1.4)
19
20
# We start with a pressure perturbation at `(xs, 0.0)` as initial condition.
21
function initial_condition_pressure_perturbation(x, t,
22
equations::CompressibleEulerEquations2D)
23
xs = 1.5 # location of the initial disturbance on the x axis
24
w = 1 / 8 # half width
25
p = exp(-log(2) * ((x[1] - xs)^2 + x[2]^2) / w^2) + 1.0
26
v1 = 0.0
27
v2 = 0.0
28
rho = 1.0
29
30
return prim2cons(SVector(rho, v1, v2, p), equations)
31
end
32
initial_condition = initial_condition_pressure_perturbation
33
34
# Initialize every boundary as a [`boundary_condition_slip_wall`](@ref).
35
boundary_conditions = boundary_condition_slip_wall
36
37
# The approximation setup is an entropy-stable split-form DG method with `polydeg=4`. We are using
38
# the two fluxes [`flux_ranocha`](@ref) and [`flux_lax_friedrichs`](@ref).
39
solver = DGSEM(polydeg = 4, surface_flux = flux_lax_friedrichs,
40
volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha))
41
42
# We want to define a circular cylinder as physical domain. It contains an inner semicircle with
43
# radius `r0` and an outer semicircle of radius `r1`.
44
45
# ![](https://user-images.githubusercontent.com/74359358/159492083-1709510f-8ba4-4416-9fb1-e2ed2a11c62c.png)
46
47
#src # \documentclass[border=0.2cm]{standalone}
48
#src # \usepackage{tikz}
49
#src # \begin{document}
50
#src # \begin{tikzpicture}
51
#src # % Circular cylinder
52
#src # \draw[<-] (-5,0) -- node[above] {$\textbf{f}_2$} (-0.5,0);
53
#src # \draw[->] (0.5,0) -- node[above] {$\textbf{f}_1$} (5,0);
54
#src # \draw[->] (0.5,0) arc (0:180:0.5); \node at (0, 0.8) {$\textbf{f}_3$};
55
#src # \draw[->] (5,0) arc (0:180:5); \node at (0, 4.7) {$\textbf{f}_4$};
56
#src # \draw[dashed] (-5, -0.5) -- node[below] {$\textbf{r}_1$} (0, -0.5);
57
#src # \draw[dashed] (-0.5, -0.7) -- node[below] {$\textbf{r}_0$} (0, -0.7);
58
#src # % Arrow
59
#src # \draw[line width=0.1cm, ->, bend angle=30, bend left] (4, 2) to (8,3);
60
#src # % Right Square
61
#src # \draw[->] (7,0) -- node[below] {$\textbf{f}_1$} (12,0);
62
#src # \draw[->] (7,5) -- node[above] {$\textbf{f}_2$} (12,5);
63
#src # \draw[->] (7,0) -- node[left] {$\textbf{f}_3$} (7,5);
64
#src # \draw[->] (12,0) -- node[right] {$\textbf{f}_4$} (12,5);
65
#src # % Pressure perturbation
66
#src # \draw[fill] (1.6, 0) arc (0:180:0.1);
67
#src # \draw (1.7, 0) arc (0:180:0.2);
68
#src # \draw (1.8, 0) arc (0:180:0.3);
69
#src # \end{tikzpicture}
70
#src # \end{document}
71
72
# The domain boundary curves with curve parameter in $[-1,1]$ are sorted as shown in the sketch.
73
# They always are orientated from negative to positive coordinate, such that the corners have to
74
# fit like this $f_1(+1) = f_4(-1)$, $f_3(+1) = f_2(-1)$, etc.
75
76
# In our case we can define the domain boundary curves as follows:
77
r0 = 0.5 # inner radius
78
r1 = 5.0 # outer radius
79
f1(xi) = SVector(r0 + 0.5 * (r1 - r0) * (xi + 1), 0.0) # right line
80
f2(xi) = SVector(-r0 - 0.5 * (r1 - r0) * (xi + 1), 0.0) # left line
81
f3(eta) = SVector(r0 * cos(0.5 * pi * (eta + 1)), r0 * sin(0.5 * pi * (eta + 1))) # inner circle
82
f4(eta) = SVector(r1 * cos(0.5 * pi * (eta + 1)), r1 * sin(0.5 * pi * (eta + 1))) # outer circle
83
84
# We create a curved mesh with 16 x 16 elements. The defined domain boundary curves are passed as a tuple.
85
cells_per_dimension = (16, 16)
86
mesh = StructuredMesh(cells_per_dimension, (f1, f2, f3, f4), periodicity = false)
87
88
# Then, we define the simulation with endtime `T=3` with `semi`, `ode` and `callbacks`.
89
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver,
90
boundary_conditions = boundary_conditions)
91
92
tspan = (0.0, 3.0)
93
ode = semidiscretize(semi, tspan)
94
95
analysis_interval = 100
96
analysis_callback = AnalysisCallback(semi, interval = analysis_interval)
97
98
alive_callback = AliveCallback(analysis_interval = analysis_interval)
99
100
stepsize_callback = StepsizeCallback(cfl = 0.9)
101
102
callbacks = CallbackSet(analysis_callback,
103
alive_callback,
104
stepsize_callback);
105
106
# Running the simulation
107
sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false);
108
dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback
109
ode_default_options()..., callback = callbacks);
110
111
using Plots
112
plot(sol)
113
#-
114
pd = PlotData2D(sol)
115
plot(pd["p"])
116
plot!(getmesh(pd))
117
118
# ## Mesh directly defined by the transformation mapping
119
# As mentioned before, you can also define the domain for a `StructuredMesh` by directly setting up
120
# a transformation mapping. Here, we want to present a nice mapping, which is often used to test
121
# free-stream preservation. Exact free-stream preservation is a crucial property of any numerical
122
# method on curvilinear grids. The mapping is a reduced 2D version of the mapping described in
123
# [Rueda-Ramírez et al. (2021), p.18](https://arxiv.org/abs/2012.12040).
124
125
using OrdinaryDiffEqLowStorageRK
126
using Trixi
127
128
equations = CompressibleEulerEquations2D(1.4)
129
130
# As mentioned, this mapping is used for testing free-stream preservation. So, we use a constant
131
# initial condition.
132
initial_condition = initial_condition_constant
133
134
solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs)
135
136
# We define the transformation mapping with variables in $[-1, 1]$ as described in
137
# Rueda-Ramírez et al. (2021), p.18 (reduced to 2D):
138
function mapping(xi_, eta_)
139
## Transform input variables between -1 and 1 onto [0,3]
140
xi = 1.5 * xi_ + 1.5
141
eta = 1.5 * eta_ + 1.5
142
143
y = eta + 3 / 8 * (cos(1.5 * pi * (2 * xi - 3) / 3) *
144
cos(0.5 * pi * (2 * eta - 3) / 3))
145
146
x = xi + 3 / 8 * (cos(0.5 * pi * (2 * xi - 3) / 3) *
147
cos(2 * pi * (2 * y - 3) / 3))
148
149
return SVector(x, y)
150
end
151
152
# Instead of a tuple of boundary functions, the `mesh` now has the mapping as its parameter.
153
cells_per_dimension = (16, 16)
154
mesh = StructuredMesh(cells_per_dimension, mapping, periodicity = true)
155
156
semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver;
157
boundary_conditions = boundary_condition_periodic)
158
159
tspan = (0.0, 1.0)
160
ode = semidiscretize(semi, tspan)
161
162
analysis_callback = AnalysisCallback(semi, interval = 250)
163
164
stepsize_callback = StepsizeCallback(cfl = 0.8)
165
166
callbacks = CallbackSet(analysis_callback,
167
stepsize_callback)
168
169
sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false);
170
dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback
171
ode_default_options()..., callback = callbacks);
172
173
# Now, we want to verify the free-stream preservation property and plot the mesh. For the verification,
174
# we calculate the absolute difference of the first conservation variable density `u[1]` and `1.0`.
175
# To plot this error and the mesh, we are using the visualization feature `ScalarPlotData2D`,
176
# explained in [visualization](@ref visualization).
177
error_density = let u = Trixi.wrap_array(sol.u[end], semi)
178
abs.(u[1, :, :, :] .- 1.0) # density, x, y, elements
179
end
180
pd = ScalarPlotData2D(error_density, semi)
181
182
using Plots
183
plot(pd, title = "Error in density")
184
plot!(getmesh(pd))
185
186
# We observe that the errors in the variable `density` are at the level of machine accuracy.
187
# Moreover, the plot shows the mesh structure resulting from our transformation mapping.
188
189
# Of course, you can also use other mappings as for instance shifts by $(x, y)$
190
# ````julia
191
# mapping(xi, eta) = SVector(xi + x, eta + y)
192
# ````
193
# or rotations with a rotation matrix $T$
194
# ````julia
195
# mapping(xi, eta) = T * SVector(xi, eta).
196
# ````
197
198
# For more curved mesh mappings, please have a look at some
199
# [elixirs for `StructuredMesh`](https://github.com/trixi-framework/Trixi.jl/tree/main/examples).
200
# For another curved mesh type, there is a [tutorial](@ref hohqmesh_tutorial) about Trixi.jl's
201
# unstructured mesh type [`UnstructuredMesh2D`] and its use of the
202
# [High-Order Hex-Quad Mesh (HOHQMesh) generator](https://github.com/trixi-framework/HOHQMesh),
203
# created and developed by David Kopriva.
204
205
# ## Package versions
206
207
# These results were obtained using the following versions.
208
209
using InteractiveUtils
210
versioninfo()
211
212
using Pkg
213
Pkg.status(["Trixi", "OrdinaryDiffEqLowStorageRK", "Plots"],
214
mode = PKGMODE_MANIFEST)
215
216