Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/meshes/structured_mesh.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
"""
9
StructuredMesh{NDIMS} <: AbstractMesh{NDIMS}
10
11
A structured curved mesh.
12
13
Different numbers of cells per dimension are possible and arbitrary functions
14
can be used as domain faces.
15
"""
16
mutable struct StructuredMesh{NDIMS, RealT <: Real} <: AbstractMesh{NDIMS}
17
cells_per_dimension::NTuple{NDIMS, Int}
18
mapping::Any # Not relevant for performance
19
mapping_as_string::String
20
periodicity::NTuple{NDIMS, Bool}
21
current_filename::String
22
unsaved_changes::Bool
23
end
24
25
"""
26
StructuredMesh(cells_per_dimension, mapping;
27
RealT = Float64,
28
periodicity = true,
29
unsaved_changes = true,
30
mapping_as_string = mapping2string(mapping, length(cells_per_dimension), RealT=RealT))
31
32
Create a StructuredMesh of the given size and shape that uses `RealT` as coordinate type.
33
34
# Arguments
35
- `cells_per_dimension::NTupleE{NDIMS, Int}`: the number of cells in each dimension.
36
- `mapping`: a function of `NDIMS` variables to describe the mapping, which transforms
37
the reference mesh to the physical domain.
38
If no `mapping_as_string` is defined, this function must be defined with the name `mapping`
39
to allow for restarts.
40
This will be changed in the future, see [https://github.com/trixi-framework/Trixi.jl/issues/541](https://github.com/trixi-framework/Trixi.jl/issues/541).
41
- `RealT::Type`: the type that should be used for coordinates.
42
- `periodicity`: either a `Bool` deciding if all of the boundaries are periodic or an `NTuple{NDIMS, Bool}`
43
deciding for each dimension if the boundaries in this dimension are periodic.
44
- `unsaved_changes::Bool`: if set to `true`, the mesh will be saved to a mesh file.
45
- `mapping_as_string::String`: the code that defines the `mapping`.
46
If `CodeTracking` can't find the function definition, it can be passed directly here.
47
The code string must define the mapping function with the name `mapping`.
48
This will be changed in the future, see [https://github.com/trixi-framework/Trixi.jl/issues/541](https://github.com/trixi-framework/Trixi.jl/issues/541).
49
"""
50
function StructuredMesh(cells_per_dimension, mapping;
51
RealT = Float64,
52
periodicity = true,
53
unsaved_changes = true,
54
mapping_as_string = mapping2string(mapping,
55
length(cells_per_dimension),
56
RealT))
57
NDIMS = length(cells_per_dimension)
58
59
# Convert periodicity to a Tuple of a Bool for every dimension
60
if all(periodicity)
61
# Also catches case where periodicity = true
62
periodicity = ntuple(_ -> true, NDIMS)
63
elseif !any(periodicity)
64
# Also catches case where periodicity = false
65
periodicity = ntuple(_ -> false, NDIMS)
66
else
67
# Default case if periodicity is an iterable
68
periodicity = Tuple(periodicity)
69
end
70
return StructuredMesh{NDIMS, RealT}(Tuple(cells_per_dimension), mapping,
71
mapping_as_string, periodicity, "",
72
unsaved_changes)
73
end
74
75
"""
76
StructuredMesh(cells_per_dimension, faces;
77
RealT = Float64,
78
periodicity = true)
79
80
Create a StructuredMesh of the given size and shape that uses `RealT` as coordinate type.
81
82
# Arguments
83
- `cells_per_dimension::NTupleE{NDIMS, Int}`: the number of cells in each dimension.
84
- `faces::NTuple{2*NDIMS}`: a tuple of `2 * NDIMS` functions that describe the faces of the domain.
85
Each function must take `NDIMS-1` arguments.
86
`faces[1]` describes the face onto which the face in negative x-direction
87
of the unit hypercube is mapped. The face in positive x-direction of
88
the unit hypercube will be mapped onto the face described by `faces[2]`.
89
`faces[3:4]` describe the faces in positive and negative y-direction respectively
90
(in 2D and 3D).
91
`faces[5:6]` describe the faces in positive and negative z-direction respectively (in 3D).
92
- `RealT::Type`: the type that should be used for coordinates.
93
- `periodicity`: either a `Bool` deciding if all of the boundaries are periodic or an `NTuple{NDIMS, Bool}` deciding for
94
each dimension if the boundaries in this dimension are periodic.
95
"""
96
function StructuredMesh(cells_per_dimension, faces::Tuple;
97
RealT = Float64,
98
periodicity = true)
99
NDIMS = length(cells_per_dimension)
100
101
validate_faces(faces)
102
103
# Use the transfinite mapping with the correct number of arguments
104
mapping = transfinite_mapping(faces)
105
106
# Collect definitions of face functions in one string (separated by semicolons)
107
face2substring(face) = code_string(face, ntuple(_ -> RealT, NDIMS - 1))
108
join_newline(strings) = join(strings, ";")
109
110
faces_definition = faces .|> face2substring .|> string |> join_newline
111
112
# Include faces definition in `mapping_as_string` to allow for evaluation
113
# without knowing the face functions
114
mapping_as_string = """$faces_definition;faces = $(string(faces));mapping = transfinite_mapping(faces)"""
115
116
return StructuredMesh(cells_per_dimension, mapping; RealT = RealT,
117
periodicity = periodicity,
118
mapping_as_string = mapping_as_string)
119
end
120
121
"""
122
StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max;
123
periodicity = true)
124
125
Create a StructuredMesh that represents a uncurved structured mesh with a rectangular domain.
126
127
# Arguments
128
- `cells_per_dimension::NTuple{NDIMS, Int}`: the number of cells in each dimension.
129
- `coordinates_min::NTuple{NDIMS, RealT}`: coordinate of the corner in the negative direction of each dimension.
130
- `coordinates_max::NTuple{NDIMS, RealT}`: coordinate of the corner in the positive direction of each dimension.
131
- `periodicity`: either a `Bool` deciding if all of the boundaries are periodic or an `NTuple{NDIMS, Bool}` deciding for
132
each dimension if the boundaries in this dimension are periodic.
133
"""
134
function StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max;
135
periodicity = true)
136
RealT = promote_type(eltype(coordinates_min), eltype(coordinates_max))
137
138
coordinates_min_max_check(coordinates_min, coordinates_max)
139
140
mapping = coordinates2mapping(coordinates_min, coordinates_max)
141
142
mapping_as_string = """coordinates_min = $coordinates_min;coordinates_max = $coordinates_max;mapping = coordinates2mapping(coordinates_min, coordinates_max)"""
143
return StructuredMesh(cells_per_dimension, mapping; RealT = RealT,
144
periodicity = periodicity,
145
mapping_as_string = mapping_as_string)
146
end
147
148
# Extract a string of the code that defines the mapping function
149
function mapping2string(mapping, ndims, RealT = Float64)
150
string(code_string(mapping, ntuple(_ -> RealT, ndims)))
151
end
152
153
# An internal function wrapping `CodeTracking.code_string` with additional
154
# error checking to avoid some problems when calling this function in
155
# Jupyter notebooks or Documenter.jl environments. See
156
# - https://github.com/trixi-framework/Trixi.jl/issues/931
157
# - https://github.com/trixi-framework/Trixi.jl/pull/1084
158
function code_string(f, t)
159
try
160
return CodeTracking.code_string(f, t)
161
catch e
162
return ""
163
end
164
end
165
166
# Interpolate linearly between left and right value where s should be between -1 and 1
167
function linear_interpolate(s, left_value, right_value)
168
0.5f0 * ((1 - s) * left_value + (1 + s) * right_value)
169
end
170
171
# Convert min and max coordinates of a rectangle to the corresponding transformation mapping
172
function coordinates2mapping(coordinates_min::NTuple{1}, coordinates_max::NTuple{1})
173
mapping(xi) = linear_interpolate(xi, coordinates_min[1], coordinates_max[1])
174
end
175
# Convenience function for 1D: Do not insist on tuples
176
function coordinates2mapping(coordinates_min::RealT,
177
coordinates_max::RealT) where {RealT <: Real}
178
mapping(xi) = linear_interpolate(xi, coordinates_min, coordinates_max)
179
end
180
181
function coordinates2mapping(coordinates_min::NTuple{2}, coordinates_max::NTuple{2})
182
function mapping(xi, eta)
183
SVector(linear_interpolate(xi, coordinates_min[1], coordinates_max[1]),
184
linear_interpolate(eta, coordinates_min[2], coordinates_max[2]))
185
end
186
end
187
188
function coordinates2mapping(coordinates_min::NTuple{3}, coordinates_max::NTuple{3})
189
function mapping(xi, eta, zeta)
190
SVector(linear_interpolate(xi, coordinates_min[1], coordinates_max[1]),
191
linear_interpolate(eta, coordinates_min[2], coordinates_max[2]),
192
linear_interpolate(zeta, coordinates_min[3], coordinates_max[3]))
193
end
194
end
195
196
# In 1D
197
# Linear mapping from the reference element to the domain described by the faces
198
function linear_mapping(x, faces)
199
return linear_interpolate(x, faces[1](), faces[2]())
200
end
201
202
# In 2D
203
# Bilinear mapping from the reference element to the domain described by the faces
204
function bilinear_mapping(x, y, faces)
205
x1 = faces[1](-1) # Bottom left
206
x2 = faces[2](-1) # Bottom right
207
x3 = faces[1](1) # Top left
208
x4 = faces[2](1) # Top right
209
210
return 0.25f0 * (x1 * (1 - x) * (1 - y) +
211
x2 * (1 + x) * (1 - y) +
212
x3 * (1 - x) * (1 + y) +
213
x4 * (1 + x) * (1 + y))
214
end
215
216
# In 3D
217
# Trilinear mapping from the reference element to the domain described by the faces
218
function trilinear_mapping(x, y, z, faces)
219
x1 = faces[1](-1, -1) # mapped from (-1,-1,-1)
220
x2 = faces[2](-1, -1) # mapped from ( 1,-1,-1)
221
x3 = faces[1](1, -1) # mapped from (-1, 1,-1)
222
x4 = faces[2](1, -1) # mapped from ( 1, 1,-1)
223
x5 = faces[1](-1, 1) # mapped from (-1,-1, 1)
224
x6 = faces[2](-1, 1) # mapped from ( 1,-1, 1)
225
x7 = faces[1](1, 1) # mapped from (-1, 1, 1)
226
x8 = faces[2](1, 1) # mapped from ( 1, 1, 1)
227
228
return 0.125f0 * (x1 * (1 - x) * (1 - y) * (1 - z) +
229
x2 * (1 + x) * (1 - y) * (1 - z) +
230
x3 * (1 - x) * (1 + y) * (1 - z) +
231
x4 * (1 + x) * (1 + y) * (1 - z) +
232
x5 * (1 - x) * (1 - y) * (1 + z) +
233
x6 * (1 + x) * (1 - y) * (1 + z) +
234
x7 * (1 - x) * (1 + y) * (1 + z) +
235
x8 * (1 + x) * (1 + y) * (1 + z))
236
end
237
238
# Use linear mapping in 1D
239
transfinite_mapping(faces::NTuple{2, Any}) = x -> linear_mapping(x, faces)
240
241
# In 2D
242
# Transfinite mapping from the reference element to the domain described by the faces
243
function transfinite_mapping(faces::NTuple{4, Any})
244
function mapping(x, y)
245
(linear_interpolate(x, faces[1](y), faces[2](y)) +
246
linear_interpolate(y, faces[3](x), faces[4](x)) -
247
bilinear_mapping(x, y, faces))
248
end
249
end
250
251
# In 3D
252
# Correction term for the Transfinite mapping
253
function correction_term_3d(x, y, z, faces)
254
# Correction for x-terms
255
c_x = linear_interpolate(x,
256
linear_interpolate(y, faces[3](-1, z), faces[4](-1, z)) +
257
linear_interpolate(z, faces[5](-1, y), faces[6](-1, y)),
258
linear_interpolate(y, faces[3](1, z), faces[4](1, z)) +
259
linear_interpolate(z, faces[5](1, y), faces[6](1, y)))
260
261
# Correction for y-terms
262
c_y = linear_interpolate(y,
263
linear_interpolate(x, faces[1](-1, z), faces[2](-1, z)) +
264
linear_interpolate(z, faces[5](x, -1), faces[6](x, -1)),
265
linear_interpolate(x, faces[1](1, z), faces[2](1, z)) +
266
linear_interpolate(z, faces[5](x, 1), faces[6](x, 1)))
267
268
# Correction for z-terms
269
c_z = linear_interpolate(z,
270
linear_interpolate(x, faces[1](y, -1), faces[2](y, -1)) +
271
linear_interpolate(y, faces[3](x, -1), faces[4](x, -1)),
272
linear_interpolate(x, faces[1](y, 1), faces[2](y, 1)) +
273
linear_interpolate(y, faces[3](x, 1), faces[4](x, 1)))
274
275
# Each of the 12 edges are counted twice above
276
# so we divide the correction term by two
277
return 0.5f0 * (c_x + c_y + c_z)
278
end
279
280
# In 3D
281
# Transfinite mapping from the reference element to the domain described by the faces
282
function transfinite_mapping(faces::NTuple{6, Any})
283
function mapping(x, y, z)
284
(linear_interpolate(x, faces[1](y, z), faces[2](y, z)) +
285
linear_interpolate(y, faces[3](x, z), faces[4](x, z)) +
286
linear_interpolate(z, faces[5](x, y), faces[6](x, y)) -
287
correction_term_3d(x, y, z, faces) +
288
trilinear_mapping(x, y, z, faces))
289
end
290
end
291
292
function validate_faces(faces::NTuple{2, Any}) end
293
294
function validate_faces(faces::NTuple{4, Any})
295
@assert faces[1](-1)≈faces[3](-1) "faces[1](-1) needs to match faces[3](-1) (bottom left corner)"
296
@assert faces[2](-1)≈faces[3](1) "faces[2](-1) needs to match faces[3](1) (bottom right corner)"
297
@assert faces[1](1)≈faces[4](-1) "faces[1](1) needs to match faces[4](-1) (top left corner)"
298
@assert faces[2](1)≈faces[4](1) "faces[2](1) needs to match faces[4](1) (top right corner)"
299
end
300
301
function validate_faces(faces::NTuple{6, Any})
302
@assert (faces[1](-1, -1)
303
faces[3](-1, -1)
304
faces[5](-1, -1)) "faces[1](-1, -1), faces[3](-1, -1) and faces[5](-1, -1) need to match at (-1, -1, -1) corner"
305
306
@assert (faces[2](-1, -1)
307
faces[3](1, -1)
308
faces[5](1, -1)) "faces[2](-1, -1), faces[3](1, -1) and faces[5](1, -1) need to match at (1, -1, -1) corner"
309
310
@assert (faces[1](1, -1)
311
faces[4](-1, -1)
312
faces[5](-1, 1)) "faces[1](1, -1), faces[4](-1, -1) and faces[5](-1, 1) need to match at (-1, 1, -1) corner"
313
314
@assert (faces[2](1, -1)
315
faces[4](1, -1)
316
faces[5](1, 1)) "faces[2](1, -1), faces[4](1, -1) and faces[5](1, 1) need to match at (1, 1, -1) corner"
317
318
@assert (faces[1](-1, 1)
319
faces[3](-1, 1)
320
faces[6](-1, -1)) "faces[1](-1, 1), faces[3](-1, 1) and faces[6](-1, -1) need to match at (-1, -1, 1) corner"
321
322
@assert (faces[2](-1, 1)
323
faces[3](1, 1)
324
faces[6](1, -1)) "faces[2](-1, 1), faces[3](1, 1) and faces[6](1, -1) need to match at (1, -1, 1) corner"
325
326
@assert (faces[1](1, 1)
327
faces[4](-1, 1)
328
faces[6](-1, 1)) "faces[1](1, 1), faces[4](-1, 1) and faces[6](-1, 1) need to match at (-1, 1, 1) corner"
329
330
@assert (faces[2](1, 1)
331
faces[4](1, 1)
332
faces[6](1, 1)) "faces[2](1, 1), faces[4](1, 1) and faces[6](1, 1) need to match at (1, 1, 1) corner"
333
end
334
335
# Check if mesh is periodic
336
isperiodic(mesh::StructuredMesh) = all(mesh.periodicity)
337
isperiodic(mesh::StructuredMesh, dimension) = mesh.periodicity[dimension]
338
339
@inline Base.ndims(::StructuredMesh{NDIMS}) where {NDIMS} = NDIMS
340
@inline Base.real(::StructuredMesh{NDIMS, RealT}) where {NDIMS, RealT} = RealT
341
Base.size(mesh::StructuredMesh) = mesh.cells_per_dimension
342
Base.size(mesh::StructuredMesh, i) = mesh.cells_per_dimension[i]
343
Base.axes(mesh::StructuredMesh) = map(Base.OneTo, mesh.cells_per_dimension)
344
Base.axes(mesh::StructuredMesh, i) = Base.OneTo(mesh.cells_per_dimension[i])
345
346
function Base.show(io::IO, mesh::StructuredMesh)
347
print(io, "StructuredMesh{", ndims(mesh), ", ", real(mesh), "}")
348
end
349
350
function Base.show(io::IO, ::MIME"text/plain", mesh::StructuredMesh)
351
if get(io, :compact, false)
352
show(io, mesh)
353
else
354
summary_header(io,
355
"StructuredMesh{" * string(ndims(mesh)) * ", " *
356
string(real(mesh)) * "}")
357
summary_line(io, "size", size(mesh))
358
# Print code lines of mapping_as_string
359
if occursin("\n", mesh.mapping_as_string)
360
mapping_lines = replace(mesh.mapping_as_string, '\n' => ';')
361
else
362
mapping_lines = mesh.mapping_as_string
363
end
364
365
mapping_lines = split(mapping_lines, ";")
366
367
if occursin("coordinates", mesh.mapping_as_string)
368
summary_line(io, "mapping", "linear")
369
coordinates_min = eval(Meta.parse(split(mapping_lines[1], "= ")[2]))
370
coordinates_max = eval(Meta.parse(split(mapping_lines[2], "= ")[2]))
371
dims = length(coordinates_max)
372
summary_line(increment_indent(io), "domain",
373
join(["[$(coordinates_min[i]), $(coordinates_max[i])]"
374
for i in 1:dims], "x"))
375
elseif occursin("mapping", mesh.mapping_as_string)
376
summary_line(io, "mapping", "custom mapping")
377
else
378
# At the moment this is not being used
379
summary_line(io, "mapping", "")
380
for i in eachindex(mapping_lines)
381
summary_line(increment_indent(io), "line $i", strip(mapping_lines[i]))
382
end
383
end
384
summary_footer(io)
385
end
386
end
387
end # @muladd
388
389