Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/visualization/utilities.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
@inline num_faces(elem::Tri) = 3
9
@inline num_faces(elem::Quad) = 4
10
11
# compute_triangle_area(tri)
12
#
13
# Computes the area of a triangle given `tri`, which is a tuple of three points (vectors),
14
# using the [Shoelace_formula](https://en.wikipedia.org/wiki/Shoelace_formula).
15
function compute_triangle_area(tri)
16
A, B, C = tri
17
return 0.5f0 * (A[1] * (B[2] - C[2]) + B[1] * (C[2] - A[2]) + C[1] * (A[2] - B[2]))
18
end
19
20
# reference_plotting_triangulation(reference_plotting_coordinates)
21
#
22
# Computes a triangulation of the points in `reference_plotting_coordinates`, which is a tuple containing
23
# vectors of plotting points on the reference element (e.g., reference_plotting_coordinates = (r,s)).
24
# The reference element is assumed to be [-1,1]^d.
25
#
26
# This function returns `t` which is a `3 x N_tri` Matrix{Int} containing indices of triangles in the
27
# triangulation of the plotting points, with zero-volume triangles removed.
28
#
29
# For example, r[t[1, i]] returns the first reference coordinate of the 1st point on the ith triangle.
30
function reference_plotting_triangulation(reference_plotting_coordinates,
31
tol = 50 * eps())
32
# on-the-fly triangulation of plotting nodes on the reference element
33
tri_in = Triangulate.TriangulateIO()
34
tri_in.pointlist = permutedims(hcat(reference_plotting_coordinates...))
35
tri_out, _ = Triangulate.triangulate("Q", tri_in)
36
triangles = tri_out.trianglelist
37
38
# filter out sliver triangles
39
has_volume = fill(true, size(triangles, 2))
40
for i in axes(triangles, 2)
41
ids = @view triangles[:, i]
42
x_points = @view tri_out.pointlist[1, ids]
43
y_points = @view tri_out.pointlist[2, ids]
44
area = compute_triangle_area(zip(x_points, y_points))
45
if abs(area) < tol
46
has_volume[i] = false
47
end
48
end
49
return permutedims(triangles[:, findall(has_volume)])
50
end
51
52
# This function is used to avoid type instabilities when calling `digest_solution_variables`.
53
function transform_to_solution_variables!(u, solution_variables, equations)
54
for (i, u_i) in enumerate(u)
55
u[i] = solution_variables(u_i, equations)
56
end
57
end
58
59
# global_plotting_triangulation_triplot(u_plot, rst_plot, xyz_plot)
60
#
61
# Returns (plotting_coordinates_x, plotting_coordinates_y, ..., plotting_values, plotting_triangulation).
62
# Output can be used with TriplotRecipes.DGTriPseudocolor(...).
63
#
64
# Inputs:
65
# - xyz_plot = plotting points (tuple of matrices of size (Nplot, K))
66
# - u_plot = matrix of size (Nplot, K) representing solution to plot.
67
# - t = triangulation of reference plotting points
68
function global_plotting_triangulation_triplot(xyz_plot, u_plot, t)
69
@assert size(first(xyz_plot), 1)==size(u_plot, 1) "Row dimension of u_plot does not match row dimension of xyz_plot"
70
71
# build discontinuous data on plotting triangular mesh
72
num_plotting_points, num_elements = size(u_plot)
73
num_reference_plotting_triangles = size(t, 1)
74
num_plotting_elements_total = num_reference_plotting_triangles * num_elements
75
76
# each column of `tp` corresponds to a vertex of a plotting triangle
77
tp = zeros(Int32, 3, num_plotting_elements_total)
78
zp = similar(tp, eltype(u_plot))
79
for e in 1:num_elements
80
for i in 1:num_reference_plotting_triangles
81
tp[:, i + (e - 1) * num_reference_plotting_triangles] .= @views t[i, :] .+
82
(e - 1) *
83
num_plotting_points
84
zp[:, i + (e - 1) * num_reference_plotting_triangles] .= @views u_plot[t[i,
85
:],
86
e]
87
end
88
end
89
return vec.(xyz_plot)..., zp, tp
90
end
91
92
function get_face_node_indices(r, s, dg::Union{<:DGSEM, <:FDSBP}, tol = 100 * eps())
93
r_max, r_min = extrema(r)
94
s_max, s_min = extrema(s)
95
face_1 = findall(@. abs(s - s_min) < tol)
96
face_2 = findall(@. abs(r - r_max) < tol)
97
face_3 = findall(@. abs(s - s_max) < tol)
98
face_4 = findall(@. abs(r - r_min) < tol)
99
Fmask = hcat(face_1, face_2, face_3, face_4)
100
return Fmask
101
end
102
103
# dispatch on semi
104
function mesh_plotting_wireframe(u, semi)
105
return mesh_plotting_wireframe(u, mesh_equations_solver_cache(semi)...)
106
end
107
108
# mesh_plotting_wireframe(u, mesh, equations, dg::DGMulti, cache; num_plotting_pts=25)
109
#
110
# Generates data for plotting a mesh wireframe given StartUpDG data types.
111
# Returns (plotting_coordinates_x, plotting_coordinates_y, nothing) for a 2D mesh wireframe.
112
function mesh_plotting_wireframe(u::StructArray, mesh, equations, dg::DGMulti, cache;
113
nvisnodes = 2 * nnodes(dg))
114
@unpack md = mesh
115
rd = dg.basis
116
117
# Construct 1D plotting interpolation matrix `Vp1D` for a single face
118
@unpack N, Fmask = rd
119
num_face_points = length(Fmask) ÷ num_faces(rd.element_type)
120
vandermonde_matrix_1D = StartUpDG.vandermonde(Line(), N,
121
StartUpDG.nodes(Line(),
122
num_face_points - 1))
123
rplot = LinRange(-1, 1, nvisnodes)
124
Vp1D = StartUpDG.vandermonde(Line(), N, rplot) / vandermonde_matrix_1D
125
126
num_faces_total = num_faces(rd.element_type) * md.num_elements
127
xf, yf = map(x -> reshape(view(x, Fmask, :), num_face_points, num_faces_total),
128
md.xyz)
129
uf = similar(u, size(xf))
130
apply_to_each_field((out, x) -> out .= reshape(view(x, Fmask, :), num_face_points,
131
num_faces_total), uf, u)
132
133
num_face_plotting_points = size(Vp1D, 1)
134
x_mesh, y_mesh = ntuple(_ -> zeros(num_face_plotting_points, num_faces_total), 2)
135
u_mesh = similar(u, (num_face_plotting_points, num_faces_total))
136
for f in 1:num_faces_total
137
mul!(view(x_mesh, :, f), Vp1D, view(xf, :, f))
138
mul!(view(y_mesh, :, f), Vp1D, view(yf, :, f))
139
apply_to_each_field(mul_by!(Vp1D), view(u_mesh, :, f), view(uf, :, f))
140
end
141
142
return x_mesh, y_mesh, u_mesh
143
end
144
145
function mesh_plotting_wireframe(u::StructArray, mesh, equations, dg::DGSEM, cache;
146
nvisnodes = 2 * nnodes(dg))
147
148
# build nodes on reference element (seems to be the right ordering)
149
r, s = reference_node_coordinates_2d(dg)
150
151
# extract node coordinates
152
uEltype = eltype(first(u))
153
nvars = nvariables(equations)
154
n_nodes_2d = nnodes(dg)^ndims(mesh)
155
n_elements = nelements(dg, cache)
156
x = reshape(view(cache.elements.node_coordinates, 1, :, :, :), n_nodes_2d,
157
n_elements)
158
y = reshape(view(cache.elements.node_coordinates, 2, :, :, :), n_nodes_2d,
159
n_elements)
160
161
# extract indices of local face nodes for wireframe plotting
162
Fmask = get_face_node_indices(r, s, dg)
163
plotting_interp_matrix1D = face_plotting_interpolation_matrix(dg;
164
nvisnodes = nvisnodes)
165
166
# These 5 lines extract the face values on each element from the arrays x,y,sol_to_plot.
167
# The resulting arrays are then reshaped so that xf, yf, sol_f are Matrix types of size
168
# (Number of face plotting nodes) x (Number of faces).
169
function face_first_reshape(x, num_nodes_1D, num_nodes, num_elements)
170
num_reference_faces = 2 * ndims(mesh)
171
xf = view(reshape(x, num_nodes, num_elements), vec(Fmask), :)
172
return reshape(xf, num_nodes_1D, num_elements * num_reference_faces)
173
end
174
function reshape_and_interpolate(x)
175
return plotting_interp_matrix1D *
176
face_first_reshape(x, nnodes(dg), n_nodes_2d, n_elements)
177
end
178
xfp, yfp = map(reshape_and_interpolate, (x, y))
179
ufp = StructArray{SVector{nvars, uEltype}}(map(reshape_and_interpolate,
180
StructArrays.components(u)))
181
182
return xfp, yfp, ufp
183
end
184
185
function mesh_plotting_wireframe(u::ScalarData, mesh, equations,
186
dg::Union{<:DGSEM, <:FDSBP}, cache;
187
nvisnodes = 2 * nnodes(dg))
188
189
# build nodes on reference element (seems to be the right ordering)
190
r, s = reference_node_coordinates_2d(dg)
191
192
# extract node coordinates
193
n_nodes_2d = nnodes(dg)^ndims(mesh)
194
n_elements = nelements(dg, cache)
195
x = reshape(view(cache.elements.node_coordinates, 1, :, :, :), n_nodes_2d,
196
n_elements)
197
y = reshape(view(cache.elements.node_coordinates, 2, :, :, :), n_nodes_2d,
198
n_elements)
199
200
# extract indices of local face nodes for wireframe plotting
201
Fmask = get_face_node_indices(r, s, dg)
202
plotting_interp_matrix1D = face_plotting_interpolation_matrix(dg;
203
nvisnodes = nvisnodes)
204
205
# These 5 lines extract the face values on each element from the arrays x,y,sol_to_plot.
206
# The resulting arrays are then reshaped so that xf, yf, sol_f are Matrix types of size
207
# (Number of face plotting nodes) x (Number of faces).
208
function face_first_reshape(x, num_nodes_1D, num_nodes, num_elements)
209
num_reference_faces = 2 * ndims(mesh)
210
xf = view(reshape(x, num_nodes, num_elements), vec(Fmask), :)
211
return reshape(xf, num_nodes_1D, num_elements * num_reference_faces)
212
end
213
function reshape_and_interpolate(x)
214
return plotting_interp_matrix1D *
215
face_first_reshape(x, nnodes(dg), n_nodes_2d, n_elements)
216
end
217
xfp, yfp, ufp = map(reshape_and_interpolate, (x, y, u.data))
218
219
return xfp, yfp, ufp
220
end
221
222
function extract_face_nodes_1D(basis::DGMultiBasis{<:Any, <:Tri})
223
# this assumes that the nodes of the first face on the reference element correspond to a face where
224
# s = constant, so that the `r` coordinates on this face can be used to construct a nodal basis.
225
@assert length(basis.Fmask) % num_faces(basis.element_type)==0 "The number of face nodes must be the same for all faces."
226
return reshape(basis.r[basis.Fmask[:, 1]], :, num_faces(basis.element_type))[:, 1]
227
end
228
229
function extract_face_nodes_1D(basis::DGMultiBasis{<:Any, <:Quad})
230
# this assumes that the nodes of the first face on the reference element correspond to a face where
231
# r = constant, so that the `s` coordinates on this face can be used to construct a nodal basis.
232
# For quadrilateral elements, this is true since the faces are ordered r = ±1, s = ±1.
233
@assert length(basis.Fmask) % num_faces(basis.element_type)==0 "The number of face nodes must be the same for all faces."
234
return reshape(basis.s[basis.Fmask[:, 1]], :, num_faces(basis.element_type))[:, 1]
235
end
236
237
function mesh_plotting_wireframe(u::ScalarData, mesh, equations, dg::DGMulti, cache;
238
nvisnodes = 2 * nnodes(dg))
239
@unpack md = mesh
240
rd = dg.basis
241
@unpack N, Fmask = rd
242
243
# number of points on a single face, assuming all faces have the same number of points
244
# note that since `ScalarPlotData2D` is restricted to Tri and Quad types, this should always be true.
245
num_face_points = size(Fmask, 1) ÷ num_faces(rd.element_type)
246
247
# extract a set of interpolation nodes for the face nodes. For Polynomial approximations,
248
# these are usually just (N+1) Gauss-Lobatto nodes. For SBP approximation types, these can
249
# be more general, with length(face_nodes_1D) ≥ N+1 for certain configurations.
250
face_nodes_1D = extract_face_nodes_1D(dg.basis)
251
252
# Construct 1D plotting interpolation matrix `Vp1D` for a single face.
253
# Since num_face_points may be larger than N+1, this is doing a least squares projection
254
vandermonde_matrix_1D = StartUpDG.vandermonde(Line(), N, face_nodes_1D)
255
rplot = LinRange(-1, 1, nvisnodes)
256
Vp1D = StartUpDG.vandermonde(Line(), N, rplot) / vandermonde_matrix_1D
257
258
num_faces_total = num_faces(rd.element_type) * md.num_elements
259
xf, yf, uf = map(x -> reshape(view(x, Fmask, :), num_face_points, num_faces_total),
260
(md.xyz..., u.data))
261
262
num_face_plotting_points = size(Vp1D, 1)
263
x_mesh, y_mesh = ntuple(_ -> zeros(num_face_plotting_points, num_faces_total), 2)
264
u_mesh = similar(u.data, (num_face_plotting_points, num_faces_total))
265
for f in 1:num_faces_total
266
mul!(view(x_mesh, :, f), Vp1D, view(xf, :, f))
267
mul!(view(y_mesh, :, f), Vp1D, view(yf, :, f))
268
mul!(view(u_mesh, :, f), Vp1D, view(uf, :, f))
269
end
270
271
return x_mesh, y_mesh, u_mesh
272
end
273
274
# These methods are used internally to set the default value of the solution variables:
275
# - If a `cons2prim` for the given `equations` exists, use it
276
# - Otherwise, use `cons2cons`, which is defined for all systems of equations
277
digest_solution_variables(equations, solution_variables) = solution_variables
278
function digest_solution_variables(equations, solution_variables::Nothing)
279
if hasmethod(cons2prim, Tuple{AbstractVector, typeof(equations)})
280
return cons2prim
281
else
282
return cons2cons
283
end
284
end
285
286
digest_variable_names(solution_variables_, equations, variable_names) = variable_names
287
digest_variable_names(solution_variables_, equations, ::Nothing) = SVector(varnames(solution_variables_,
288
equations))
289
290
"""
291
adapt_to_mesh_level!(u_ode, semi, level)
292
adapt_to_mesh_level!(sol::Trixi.TrixiODESolution, level)
293
294
Like [`adapt_to_mesh_level`](@ref), but modifies the solution and parts of the
295
semidiscretization (mesh and caches) in place.
296
"""
297
function adapt_to_mesh_level!(u_ode, semi, level)
298
# Create AMR callback with controller that refines everything towards a single level
299
amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first),
300
base_level = level)
301
amr_callback = AMRCallback(semi, amr_controller, interval = 0)
302
303
# Adapt mesh until it does not change anymore
304
has_changed = amr_callback.affect!(u_ode, semi, 0.0, 0)
305
while has_changed
306
has_changed = amr_callback.affect!(u_ode, semi, 0.0, 0)
307
end
308
309
return u_ode, semi
310
end
311
312
function adapt_to_mesh_level!(sol::TrixiODESolution, level)
313
return adapt_to_mesh_level!(sol.u[end], sol.prob.p, level)
314
end
315
316
"""
317
adapt_to_mesh_level(u_ode, semi, level)
318
adapt_to_mesh_level(sol::Trixi.TrixiODESolution, level)
319
320
Use the regular adaptive mesh refinement routines to adaptively refine/coarsen the solution `u_ode`
321
with semidiscretization `semi` towards a uniformly refined grid with refinement level `level`. The
322
solution and semidiscretization are copied such that the original objects remain *unaltered*.
323
324
A convenience method accepts an ODE solution object, from which solution and semidiscretization are
325
extracted as needed.
326
327
See also: [`adapt_to_mesh_level!`](@ref)
328
"""
329
function adapt_to_mesh_level(u_ode, semi, level)
330
# Create new semidiscretization with copy of the current mesh
331
mesh, _, _, _ = mesh_equations_solver_cache(semi)
332
new_semi = remake(semi, mesh = deepcopy(mesh))
333
334
return adapt_to_mesh_level!(deepcopy(u_ode), new_semi, level)
335
end
336
337
function adapt_to_mesh_level(sol::TrixiODESolution, level)
338
return adapt_to_mesh_level(sol.u[end], sol.prob.p, level)
339
end
340
341
# Extract data from a 2D/3D DG solution and prepare it for visualization as a heatmap/contour plot.
342
#
343
# Returns a tuple with
344
# - x coordinates
345
# - y coordinates
346
# - nodal 2D data
347
# - x vertices for mesh visualization
348
# - y vertices for mesh visualization
349
#
350
# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may
351
# thus be changed in future releases.
352
function get_data_2d(center_level_0, length_level_0, leaf_cells, coordinates, levels,
353
ndims, unstructured_data, n_nodes,
354
grid_lines = false, max_supported_level = 11, nvisnodes = nothing,
355
slice = :xy, point = (0.0, 0.0, 0.0))
356
# Determine resolution for data interpolation
357
max_level = maximum(levels)
358
if max_level > max_supported_level
359
error("Maximum refinement level $max_level is higher than " *
360
"maximum supported level $max_supported_level")
361
end
362
max_available_nodes_per_finest_element = 2^(max_supported_level - max_level)
363
if nvisnodes === nothing
364
max_nvisnodes = 2 * n_nodes
365
elseif nvisnodes == 0
366
max_nvisnodes = n_nodes
367
else
368
max_nvisnodes = nvisnodes
369
end
370
nvisnodes_at_max_level = min(max_available_nodes_per_finest_element, max_nvisnodes)
371
resolution = nvisnodes_at_max_level * 2^max_level
372
nvisnodes_per_level = [2^(max_level - level) * nvisnodes_at_max_level
373
for level in 0:max_level]
374
# nvisnodes_per_level is an array (accessed by "level + 1" to accommodate
375
# level-0-cell) that contains the number of visualization nodes for any
376
# refinement level to visualize on an equidistant grid
377
378
if ndims == 3
379
(unstructured_data, coordinates, levels,
380
center_level_0) = unstructured_3d_to_2d(unstructured_data,
381
coordinates, levels, length_level_0,
382
center_level_0, slice,
383
point)
384
end
385
386
# Normalize element coordinates: move center to (0, 0) and domain size to [-1, 1]²
387
n_elements = length(levels)
388
normalized_coordinates = similar(coordinates)
389
for element_id in 1:n_elements
390
@views normalized_coordinates[:, element_id] .= ((coordinates[:, element_id] .-
391
center_level_0) ./
392
(length_level_0 / 2))
393
end
394
395
# Interpolate unstructured DG data to structured data
396
(structured_data = unstructured2structured(unstructured_data,
397
normalized_coordinates,
398
levels, resolution, nvisnodes_per_level))
399
400
# Interpolate cell-centered values to node-centered values
401
node_centered_data = cell2node(structured_data)
402
403
# Determine axis coordinates for contour plot
404
xs = collect(range(-1, 1, length = resolution + 1)) .* length_level_0 / 2 .+
405
center_level_0[1]
406
ys = collect(range(-1, 1, length = resolution + 1)) .* length_level_0 / 2 .+
407
center_level_0[2]
408
409
# Determine element vertices to plot grid lines
410
if grid_lines
411
mesh_vertices_x, mesh_vertices_y = calc_vertices(coordinates, levels,
412
length_level_0)
413
else
414
mesh_vertices_x = Vector{Float64}(undef, 0)
415
mesh_vertices_y = Vector{Float64}(undef, 0)
416
end
417
418
return xs, ys, node_centered_data, mesh_vertices_x, mesh_vertices_y
419
end
420
421
# For finite difference methods (FDSBP), we do not want to reinterpolate the data, but use the same
422
# nodes as input nodes. For DG methods, we usually want to reinterpolate the data on an equidistant grid.
423
default_reinterpolate(solver) = solver isa FDSBP ? false : true
424
425
# Extract data from a 1D DG solution and prepare it for visualization as a line plot.
426
# This returns a tuple with
427
# - x coordinates
428
# - nodal 1D data
429
#
430
# Note: This is a low-level function that is not considered as part of Trixi's interface and may
431
# thus be changed in future releases.
432
function get_data_1d(original_nodes, unstructured_data, nvisnodes, reinterpolate)
433
# Get the dimensions of u; where n_vars is the number of variables, n_nodes the number of nodal values per element and n_elements the total number of elements.
434
n_nodes, n_elements, n_vars = size(unstructured_data)
435
436
# If `reinterpolate` is `false`, we do nothing.
437
# If `reinterpolate` is `true`, the output nodes are equidistantly spaced.
438
if reinterpolate == false
439
interpolated_nodes = original_nodes
440
interpolated_data = unstructured_data
441
else
442
# Set the amount of nodes visualized according to nvisnodes.
443
if nvisnodes === nothing
444
max_nvisnodes = 2 * n_nodes
445
elseif nvisnodes == 0
446
max_nvisnodes = n_nodes
447
else
448
@assert nvisnodes>=2 "nvisnodes must be zero or >= 2"
449
max_nvisnodes = nvisnodes
450
end
451
452
interpolated_nodes = Array{eltype(original_nodes), 2}(undef, max_nvisnodes,
453
n_elements)
454
interpolated_data = Array{eltype(unstructured_data), 3}(undef, max_nvisnodes,
455
n_elements, n_vars)
456
457
for j in 1:n_elements
458
# Interpolate on an equidistant grid.
459
interpolated_nodes[:, j] .= range(original_nodes[1, 1, j],
460
original_nodes[1, end, j],
461
length = max_nvisnodes)
462
end
463
464
nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes)
465
nodes_out = collect(range(-1, 1, length = max_nvisnodes))
466
467
# Calculate vandermonde matrix for interpolation.
468
vandermonde = polynomial_interpolation_matrix(nodes_in, nodes_out)
469
470
# Iterate over all variables.
471
for v in 1:n_vars
472
# Interpolate data for each element.
473
for element in 1:n_elements
474
multiply_scalar_dimensionwise!(@view(interpolated_data[:, element, v]),
475
vandermonde,
476
@view(unstructured_data[:, element, v]))
477
end
478
end
479
end
480
481
# Return results after data is reshaped
482
return vec(interpolated_nodes), reshape(interpolated_data, :, n_vars),
483
vcat(original_nodes[1, 1, :], original_nodes[1, end, end])
484
end
485
486
# Change order of dimensions (variables are now last) and convert data to `solution_variables`
487
#
488
# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may
489
# thus be changed in future releases.
490
function get_unstructured_data(u, solution_variables, mesh, equations, solver, cache)
491
if solution_variables === cons2cons
492
raw_data = u
493
n_vars = size(raw_data, 1)
494
else
495
# Similar to `save_solution_file` in `callbacks_step/save_solution_dg.jl`.
496
# However, we cannot use `reinterpret` here as `u` might have a non-bits type.
497
# See https://github.com/trixi-framework/Trixi.jl/pull/2388
498
n_vars_in = nvariables(equations)
499
n_vars = length(solution_variables(get_node_vars(u, equations, solver),
500
equations))
501
raw_data = Array{eltype(u)}(undef, n_vars, Base.tail(size(u))...)
502
reshaped_u = reshape(u, n_vars_in, :)
503
reshaped_r = reshape(raw_data, n_vars, :)
504
for idx in axes(reshaped_u, 2)
505
reshaped_r[:, idx] = solution_variables(get_node_vars(reshaped_u, equations,
506
solver, idx),
507
equations)
508
end
509
end
510
511
unstructured_data = Array{eltype(raw_data)}(undef,
512
ntuple((d) -> nnodes(solver),
513
ndims(equations))...,
514
nelements(solver, cache), n_vars)
515
for variable in 1:n_vars
516
@views unstructured_data[.., :, variable] .= raw_data[variable, .., :]
517
end
518
519
return unstructured_data
520
end
521
522
# This method is only for plotting 1D functions
523
function get_unstructured_data(func::Function, solution_variables,
524
mesh::AbstractMesh{1}, equations, solver, cache)
525
original_nodes = cache.elements.node_coordinates
526
# original_nodes has size (1, nnodes, nelements)
527
# we want u to have size (nvars, nnodes, nelements)
528
# func.(original_nodes, equations) has size (1, nnodes, nelements), where each component has length n_vars
529
# Therefore, we drop the first (singleton) dimension and then stack the components
530
u = stack(func.(SVector.(dropdims(original_nodes; dims = 1)), equations))
531
return get_unstructured_data(u, solution_variables, mesh, equations, solver, cache)
532
end
533
534
# Convert cell-centered values to node-centered values by averaging over all
535
# four neighbors. Solution values at the edges are padded with ghost values
536
# computed via linear extrapolation.
537
#
538
# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may
539
# thus be changed in future releases.
540
function cell2node(cell_centered_data)
541
# Create temporary data structure to make the averaging algorithm as simple
542
# as possible (by using a ghost layer)
543
tmp = similar(first(cell_centered_data), size(first(cell_centered_data)) .+ (2, 2))
544
545
# Create output data structure
546
resolution_in, _ = size(first(cell_centered_data))
547
resolution_out = resolution_in + 1
548
node_centered_data = [Matrix{Float64}(undef, resolution_out, resolution_out)
549
for _ in eachindex(cell_centered_data)]
550
551
for (cell_data, node_data) in zip(cell_centered_data, node_centered_data)
552
# Fill center with original data
553
tmp[2:(end - 1), 2:(end - 1)] .= cell_data
554
555
# Linear extrapolation of top and bottom rows
556
tmp[1, 2:(end - 1)] .= cell_data[1, :] .+ (cell_data[1, :] .- cell_data[2, :])
557
tmp[end, 2:(end - 1)] .= (cell_data[end, :] .+
558
(cell_data[end, :] .- cell_data[end - 1, :]))
559
560
# Linear extrapolatation of left and right columns
561
tmp[2:(end - 1), 1] .= cell_data[:, 1] .+ (cell_data[:, 1] .- cell_data[:, 2])
562
tmp[2:(end - 1), end] .= (cell_data[:, end] .+
563
(cell_data[:, end] .- cell_data[:, end - 1]))
564
565
# Corners perform the linear extrapolatation along diagonals
566
tmp[1, 1] = tmp[2, 2] + (tmp[2, 2] - tmp[3, 3])
567
tmp[1, end] = tmp[2, end - 1] + (tmp[2, end - 1] - tmp[3, end - 2])
568
tmp[end, 1] = tmp[end - 1, 2] + (tmp[end - 1, 2] - tmp[end - 2, 3])
569
tmp[end, end] = (tmp[end - 1, end - 1] +
570
(tmp[end - 1, end - 1] - tmp[end - 2, end - 2]))
571
572
# Obtain node-centered value by averaging over neighboring cell-centered values
573
for j in 1:resolution_out
574
for i in 1:resolution_out
575
node_data[i, j] = (tmp[i, j] +
576
tmp[i + 1, j] +
577
tmp[i, j + 1] +
578
tmp[i + 1, j + 1]) / 4
579
end
580
end
581
end
582
583
# Transpose
584
for (index, data) in enumerate(node_centered_data)
585
node_centered_data[index] = permutedims(data)
586
end
587
588
return node_centered_data
589
end
590
591
# Convert 3d unstructured data to 2d data.
592
# Additional to the new unstructured data updated coordinates, levels and
593
# center coordinates are returned.
594
#
595
# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may
596
# thus be changed in future releases.
597
function unstructured_3d_to_2d(unstructured_data, coordinates, levels,
598
length_level_0, center_level_0, slice,
599
point)
600
if slice === :yz
601
slice_dimension = 1
602
other_dimensions = [2, 3]
603
elseif slice === :xz
604
slice_dimension = 2
605
other_dimensions = [1, 3]
606
elseif slice === :xy
607
slice_dimension = 3
608
other_dimensions = [1, 2]
609
else
610
error("illegal dimension '$slice', supported dimensions are :yz, :xz, and :xy")
611
end
612
613
# Limits of domain in slice dimension
614
lower_limit = center_level_0[slice_dimension] - length_level_0 / 2
615
upper_limit = center_level_0[slice_dimension] + length_level_0 / 2
616
617
@assert length(point)>=3 "Point must be three-dimensional."
618
if point[slice_dimension] < lower_limit || point[slice_dimension] > upper_limit
619
error(string("Slice plane is outside of domain.",
620
" point[$slice_dimension]=$(point[slice_dimension]) must be between $lower_limit and $upper_limit"))
621
end
622
623
# Extract data shape information
624
n_nodes_in, _, _, n_elements, n_variables = size(unstructured_data)
625
626
# Get node coordinates for DG locations on reference element
627
nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes_in)
628
629
# New unstructured data has one dimension less.
630
# The redundant element ids are removed later.
631
@views new_unstructured_data = similar(unstructured_data[1, ..])
632
633
# Declare new empty arrays to fill in new coordinates and levels
634
new_coordinates = Array{Float64}(undef, 2, n_elements)
635
new_levels = Array{eltype(levels)}(undef, n_elements)
636
637
# Counter for new element ids
638
new_id = 0
639
640
# Save vandermonde matrices in a Dict to prevent redundant generation
641
vandermonde_to_2d = Dict()
642
643
# Permute dimensions such that the slice dimension is always the
644
# third dimension of the array. Below we can always interpolate in the
645
# third dimension.
646
if slice === :yz
647
unstructured_data = permutedims(unstructured_data, [2, 3, 1, 4, 5])
648
elseif slice === :xz
649
unstructured_data = permutedims(unstructured_data, [1, 3, 2, 4, 5])
650
end
651
652
for element_id in 1:n_elements
653
# Distance from center to border of this element (half the length)
654
element_length = length_level_0 / 2^levels[element_id]
655
min_coordinate = coordinates[:, element_id] .- element_length / 2
656
max_coordinate = coordinates[:, element_id] .+ element_length / 2
657
658
# Check if slice plane and current element intersect.
659
# The first check uses a "greater but not equal" to only match one cell if the
660
# slice plane lies between two cells.
661
# The second check is needed if the slice plane is at the upper border of
662
# the domain due to this.
663
if !((min_coordinate[slice_dimension] <= point[slice_dimension] &&
664
max_coordinate[slice_dimension] > point[slice_dimension]) ||
665
(point[slice_dimension] == upper_limit &&
666
max_coordinate[slice_dimension] == upper_limit))
667
# Continue for loop if they don't intersect
668
continue
669
end
670
671
# This element is of interest
672
new_id += 1
673
674
# Add element to new coordinates and levels
675
new_coordinates[:, new_id] = coordinates[other_dimensions, element_id]
676
new_levels[new_id] = levels[element_id]
677
678
# Construct vandermonde matrix (or load from Dict if possible)
679
normalized_intercept = (point[slice_dimension] -
680
min_coordinate[slice_dimension]) /
681
element_length * 2 - 1
682
683
if haskey(vandermonde_to_2d, normalized_intercept)
684
vandermonde = vandermonde_to_2d[normalized_intercept]
685
else
686
# Generate vandermonde matrix to interpolate values at nodes_in to one value
687
vandermonde = polynomial_interpolation_matrix(nodes_in,
688
[normalized_intercept])
689
vandermonde_to_2d[normalized_intercept] = vandermonde
690
end
691
692
# 1D interpolation to specified slice plane
693
# We permuted the dimensions above such that now the dimension in which
694
# we will interpolate is always the third one.
695
for i in 1:n_nodes_in
696
for ii in 1:n_nodes_in
697
# Interpolate in the third dimension
698
data = unstructured_data[i, ii, :, element_id, :]
699
700
value = multiply_dimensionwise(vandermonde, permutedims(data))
701
new_unstructured_data[i, ii, new_id, :] = value[:, 1]
702
end
703
end
704
end
705
706
# Remove redundant element ids
707
unstructured_data = new_unstructured_data[:, :, 1:new_id, :]
708
new_coordinates = new_coordinates[:, 1:new_id]
709
new_levels = new_levels[1:new_id]
710
711
center_level_0 = center_level_0[other_dimensions]
712
713
return unstructured_data, new_coordinates, new_levels, center_level_0
714
end
715
716
# Convert 2d unstructured data to 1d slice and interpolate them.
717
function unstructured_2d_to_1d(original_nodes, unstructured_data, nvisnodes,
718
reinterpolate, slice, point)
719
if slice === :x
720
slice_dimension = 2
721
other_dimension = 1
722
elseif slice === :y
723
slice_dimension = 1
724
other_dimension = 2
725
else
726
error("illegal dimension '$slice', supported dimensions are :x and :y")
727
end
728
729
# Set up data structures to store new 1D data.
730
@views new_unstructured_data = similar(unstructured_data[1, ..])
731
@views new_nodes = similar(original_nodes[1, 1, ..])
732
733
n_nodes_in, _, n_elements, n_variables = size(unstructured_data)
734
nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes_in)
735
736
# Test if point lies in the domain.
737
lower_limit = original_nodes[1, 1, 1, 1]
738
upper_limit = original_nodes[1, n_nodes_in, n_nodes_in, n_elements]
739
740
@assert length(point)>=2 "Point must be two-dimensional."
741
if point[slice_dimension] < lower_limit || point[slice_dimension] > upper_limit
742
error(string("Slice axis is outside of domain. ",
743
" point[$slice_dimension]=$(point[slice_dimension]) must be between $lower_limit and $upper_limit"))
744
end
745
746
# Count the amount of new elements.
747
new_id = 0
748
749
# Permute dimensions so that the slice dimension is always in the correct place for later use.
750
if slice === :y
751
original_nodes = permutedims(original_nodes, [1, 3, 2, 4])
752
unstructured_data = permutedims(unstructured_data, [2, 1, 3, 4])
753
end
754
755
# Iterate over all elements to find the ones that lie on the slice axis.
756
for element_id in 1:n_elements
757
min_coordinate = original_nodes[:, 1, 1, element_id]
758
max_coordinate = original_nodes[:, n_nodes_in, n_nodes_in, element_id]
759
element_length = max_coordinate - min_coordinate
760
761
# Test if the element is on the slice axis. If not just continue with the next element.
762
if !((min_coordinate[slice_dimension] <= point[slice_dimension] &&
763
max_coordinate[slice_dimension] > point[slice_dimension]) ||
764
(point[slice_dimension] == upper_limit &&
765
max_coordinate[slice_dimension] == upper_limit))
766
continue
767
end
768
769
new_id += 1
770
771
# Construct vandermonde matrix for interpolation of each 2D element to a 1D element.
772
normalized_intercept = (point[slice_dimension] -
773
min_coordinate[slice_dimension]) /
774
element_length[1] * 2 - 1
775
vandermonde = polynomial_interpolation_matrix(nodes_in, normalized_intercept)
776
777
# Interpolate to each node of new 1D element.
778
for v in 1:n_variables
779
for node in 1:n_nodes_in
780
new_unstructured_data[node, new_id, v] = (vandermonde * unstructured_data[node,
781
:,
782
element_id,
783
v])[1]
784
end
785
end
786
787
new_nodes[:, new_id] = original_nodes[other_dimension, :, 1, element_id]
788
end
789
790
return get_data_1d(reshape(new_nodes[:, 1:new_id], 1, n_nodes_in, new_id),
791
new_unstructured_data[:, 1:new_id, :], nvisnodes, reinterpolate)
792
end
793
794
# Calculate the arc length of a curve given by ndims x npoints point coordinates (piece-wise linear approximation)
795
function calc_arc_length(coordinates)
796
n_points = size(coordinates, 2)
797
arc_length = zeros(n_points)
798
for i in 1:(n_points - 1)
799
dist_squared = zero(eltype(arc_length))
800
for j in axes(coordinates, 1)
801
dist_squared += (coordinates[j, i + 1] - coordinates[j, i])^2
802
end
803
arc_length[i + 1] = arc_length[i] + sqrt(dist_squared)
804
end
805
return arc_length
806
end
807
808
# Convert 2d unstructured data to 1d data at given curve for the TreeMesh.
809
function unstructured_2d_to_1d_curve(original_nodes, unstructured_data, nvisnodes,
810
curve, mesh::TreeMesh, solver, cache)
811
n_points_curve = size(curve)[2]
812
n_nodes, _, n_elements, n_variables = size(unstructured_data)
813
nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes)
814
baryweights_in = barycentric_weights(nodes_in)
815
816
# Utility function to extract points as `SVector`s
817
get_point(data, idx...) = SVector(data[1, idx...], data[2, idx...])
818
819
# Check if input is correct.
820
min = get_point(original_nodes, 1, 1, 1)
821
max = get_point(original_nodes, n_nodes, n_nodes, n_elements)
822
@assert size(curve)==(2, size(curve)[2]) "Coordinates along curve must be 2xn dimensional."
823
for element in 1:n_points_curve
824
p = get_point(curve, element)
825
if any(p .< min) || any(p .> max)
826
throw(DomainError("Some coordinates from `curve` are outside of the domain."))
827
end
828
end
829
830
# Set nodes according to the length of the curve.
831
arc_length = calc_arc_length(curve)
832
833
# Setup data structures.
834
data_on_curve = Array{Float64}(undef, n_points_curve, n_variables)
835
temp_data = Array{Float64}(undef, n_nodes, n_points_curve, n_variables)
836
837
# For each coordinate find the corresponding element with its id.
838
element_ids = get_elements_by_coordinates(curve, mesh, solver, cache)
839
840
# These Vandermonde matrices are really 1×n_nodes matrices, i.e.,
841
# row vectors. We allocate memory here to improve performance.
842
vandermonde_x = polynomial_interpolation_matrix(nodes_in, zero(eltype(curve)))
843
vandermonde_y = similar(vandermonde_x)
844
845
# Iterate over all found elements.
846
for element in 1:n_points_curve
847
element_id = element_ids[element]
848
min_coordinate = get_point(original_nodes, 1, 1,
849
element_id)
850
max_coordinate = get_point(original_nodes, n_nodes, n_nodes,
851
element_id)
852
element_length = max_coordinate - min_coordinate
853
854
normalized_coordinates = (get_point(curve, element) - min_coordinate) /
855
element_length[1] * 2 .- 1
856
857
# Interpolate to a single point in each element.
858
# These Vandermonde matrices are really 1×n_nodes matrices, i.e.,
859
# row vectors.
860
polynomial_interpolation_matrix!(vandermonde_x, nodes_in,
861
normalized_coordinates[1], baryweights_in)
862
polynomial_interpolation_matrix!(vandermonde_y, nodes_in,
863
normalized_coordinates[2], baryweights_in)
864
for v in 1:n_variables
865
for i in 1:n_nodes
866
res_i = zero(eltype(temp_data))
867
for n in 1:n_nodes
868
res_i += vandermonde_y[n] * unstructured_data[i, n, element_id, v]
869
end
870
temp_data[i, element, v] = res_i
871
end
872
res_v = zero(eltype(data_on_curve))
873
for n in 1:n_nodes
874
res_v += vandermonde_x[n] * temp_data[n, element, v]
875
end
876
data_on_curve[element, v] = res_v
877
end
878
end
879
880
return arc_length, data_on_curve, nothing
881
end
882
883
# Convert 2d unstructured data from a general mesh to 1d data at given curve.
884
#
885
# We need to loop through all the points and check in which element they are
886
# located. A general implementation working for all mesh types has to perform
887
# a naive loop through all nodes. Thus, we use this entry point for dispatching
888
# on the `mesh` type.
889
function unstructured_2d_to_1d_curve(u, mesh, equations,
890
solver, cache,
891
curve, slice,
892
point, nvisnodes,
893
solution_variables)
894
return unstructured_2d_to_1d_curve_general(u, mesh, equations,
895
solver, cache,
896
curve, slice,
897
point, nvisnodes,
898
solution_variables)
899
end
900
901
function unstructured_2d_to_1d_curve_general(u, mesh, equations,
902
solver, cache,
903
input_curve, slice,
904
point, nvisnodes,
905
solution_variables)
906
# Create a 'PlotData2DTriangulated' object so a triangulation
907
# can be used when extracting relevant data.
908
pd = PlotData2DTriangulated(u, mesh, equations, solver, cache;
909
solution_variables, nvisnodes)
910
911
# If no curve is defined, create an axis curve.
912
if input_curve === nothing
913
input_curve = axis_curve(pd.x, pd.y, nothing, slice, point, nvisnodes)
914
end
915
916
@assert size(input_curve, 1)==2 "Input 'curve' must be 2xn dimensional."
917
918
# For each coordinate find the corresponding triangle with its ids.
919
# The default value if no element is found is 0.
920
ids_by_coordinates = get_ids_by_coordinates(input_curve, pd)
921
found_coordinates = view(ids_by_coordinates, :, 1) .!= 0
922
923
@assert !iszero(found_coordinates) "No points of 'curve' are inside of the solutions domain."
924
925
# These hold the ids of the elements and triangles the points of the curve sit in.
926
element_ids = @view ids_by_coordinates[found_coordinates, 1]
927
triangle_ids = @view ids_by_coordinates[found_coordinates, 2]
928
929
# Shorten the curve, so that it contains only point that were found.
930
curve = @view input_curve[:, found_coordinates]
931
932
n_variables = length(pd.data[1, 1])
933
n_points_curve = size(curve, 2)
934
935
# Set nodes according to the length of the curve.
936
arc_length = calc_arc_length(curve)
937
938
# Setup data structures.
939
data_on_curve = Array{Float64}(undef, n_points_curve, n_variables)
940
941
# Iterate over all points on the curve.
942
for point in 1:n_points_curve
943
point_on_curve = SVector(curve[1, point], curve[2, point])
944
945
element = element_ids[point]
946
triangle_id = triangle_ids[point]
947
triangle = (pd.t[triangle_id, 1], pd.t[triangle_id, 2], pd.t[triangle_id, 3])
948
949
# Get the x and y coordinates of the corners of given triangle.
950
x_coordinates_triangle = SVector(pd.x[triangle[1], element],
951
pd.x[triangle[2], element],
952
pd.x[triangle[3], element])
953
y_coordinates_triangle = SVector(pd.y[triangle[1], element],
954
pd.y[triangle[2], element],
955
pd.y[triangle[3], element])
956
957
# Extract solution values in corners of the triangle.
958
data_in_triangle = (pd.data[triangle[1], element],
959
pd.data[triangle[2], element],
960
pd.data[triangle[3], element])
961
962
for v in 1:n_variables
963
# Extract solution values of variable `v` in corners of the triangle.
964
values_triangle = SVector(data_in_triangle[1][v],
965
data_in_triangle[2][v],
966
data_in_triangle[3][v])
967
968
# Linear interpolation in each triangle to the points on the curve.
969
data_on_curve[point, v] = triangle_interpolation(x_coordinates_triangle,
970
y_coordinates_triangle,
971
values_triangle,
972
point_on_curve)
973
end
974
end
975
976
return arc_length, data_on_curve, nothing
977
end
978
979
# Convert 3d unstructured data to 1d data at given curve.
980
function unstructured_3d_to_1d_curve(original_nodes, unstructured_data, nvisnodes,
981
curve, mesh::TreeMesh, solver, cache)
982
n_points_curve = size(curve)[2]
983
n_nodes, _, _, n_elements, n_variables = size(unstructured_data)
984
nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes)
985
baryweights_in = barycentric_weights(nodes_in)
986
987
# Utility function to extract points as `SVector`s
988
get_point(data, idx...) = SVector(data[1, idx...],
989
data[2, idx...],
990
data[3, idx...])
991
992
# Check if input is correct.
993
min = get_point(original_nodes, 1, 1, 1, 1)
994
max = get_point(original_nodes, n_nodes, n_nodes, n_nodes, n_elements)
995
@assert size(curve)==(3, n_points_curve) "Coordinates along curve must be 3xn dimensional."
996
for element in 1:n_points_curve
997
p = get_point(curve, element)
998
if any(p .< min) || any(p .> max)
999
throw(DomainError("Some coordinates from `curve` are outside of the domain."))
1000
end
1001
end
1002
1003
# Set nodes according to the length of the curve.
1004
arc_length = calc_arc_length(curve)
1005
1006
# Setup data structures.
1007
data_on_curve = Array{Float64}(undef, n_points_curve, n_variables)
1008
temp_data = Array{Float64}(undef, n_nodes, n_nodes + 1, n_points_curve, n_variables)
1009
1010
# For each coordinate find the corresponding element with its id.
1011
element_ids = get_elements_by_coordinates(curve, mesh, solver, cache)
1012
1013
# These Vandermonde matrices are really 1×n_nodes matrices, i.e.,
1014
# row vectors. We allocate memory here to improve performance.
1015
vandermonde_x = polynomial_interpolation_matrix(nodes_in, zero(eltype(curve)))
1016
vandermonde_y = similar(vandermonde_x)
1017
vandermonde_z = similar(vandermonde_x)
1018
1019
# Iterate over all found elements.
1020
for element in 1:n_points_curve
1021
element_id = element_ids[element]
1022
min_coordinate = get_point(original_nodes, 1, 1, 1,
1023
element_id)
1024
max_coordinate = get_point(original_nodes, n_nodes, n_nodes, n_nodes,
1025
element_id)
1026
element_length = max_coordinate - min_coordinate
1027
1028
normalized_coordinates = (get_point(curve, element) - min_coordinate) /
1029
element_length[1] * 2 .- 1
1030
1031
# Interpolate to a single point in each element.
1032
# These Vandermonde matrices are really 1×n_nodes matrices, i.e.,
1033
# row vectors.
1034
polynomial_interpolation_matrix!(vandermonde_x, nodes_in,
1035
normalized_coordinates[1], baryweights_in)
1036
polynomial_interpolation_matrix!(vandermonde_y, nodes_in,
1037
normalized_coordinates[2], baryweights_in)
1038
polynomial_interpolation_matrix!(vandermonde_z, nodes_in,
1039
normalized_coordinates[3], baryweights_in)
1040
for v in 1:n_variables
1041
for i in 1:n_nodes
1042
for ii in 1:n_nodes
1043
res_ii = zero(eltype(temp_data))
1044
for n in 1:n_nodes
1045
res_ii += vandermonde_z[n] *
1046
unstructured_data[i, ii, n, element_id, v]
1047
end
1048
temp_data[i, ii, element, v] = res_ii
1049
end
1050
res_i = zero(eltype(temp_data))
1051
for n in 1:n_nodes
1052
res_i += vandermonde_y[n] * temp_data[i, n, element, v]
1053
end
1054
temp_data[i, n_nodes + 1, element, v] = res_i
1055
end
1056
res_v = zero(eltype(temp_data))
1057
for n in 1:n_nodes
1058
res_v += vandermonde_x[n] * temp_data[n, n_nodes + 1, element, v]
1059
end
1060
data_on_curve[element, v] = res_v
1061
end
1062
end
1063
1064
return arc_length, data_on_curve, nothing
1065
end
1066
1067
# Convert 3d unstructured data from a general mesh to 1d data at given curve.
1068
#
1069
# We need to loop through all the points and check in which element they are
1070
# located. A general implementation working for all mesh types has to perform
1071
# a naive loop through all nodes. Thus, we use this entry point for dispatching
1072
# on the `mesh` type.
1073
function unstructured_3d_to_1d_curve(u, mesh, equations, solver, cache,
1074
curve, solution_variables)
1075
return unstructured_3d_to_1d_curve_general(u, equations, solver, cache,
1076
curve, solution_variables)
1077
end
1078
1079
function unstructured_3d_to_1d_curve_general(u, equations, solver, cache,
1080
curve, solution_variables)
1081
# Set up data structure.
1082
@assert size(curve, 1) == 3
1083
n_points_curve = size(curve, 2)
1084
1085
# Get the number of variables after applying the transformation to solution variables.
1086
u_node = get_node_vars(u, equations, solver, 1, 1, 1, 1)
1087
var_node = solution_variables(u_node, equations)
1088
n_variables = length(var_node)
1089
data_on_curve = Array{eltype(var_node)}(undef, n_points_curve, n_variables)
1090
1091
nodes = cache.elements.node_coordinates
1092
interpolation_cache = get_value_at_point_3d_cache(u)
1093
1094
# Iterate over every point on the curve and determine the solutions value at given point.
1095
for i in 1:n_points_curve
1096
point = SVector(curve[1, i], curve[2, i], curve[3, i])
1097
get_value_at_point_3d!(view(data_on_curve, i, :), point, solution_variables,
1098
nodes, u, equations, solver;
1099
cache = interpolation_cache)
1100
end
1101
1102
mesh_vertices_x = nothing
1103
1104
return calc_arc_length(curve), data_on_curve, mesh_vertices_x
1105
end
1106
1107
# Check if the first 'amount'-many points can still form a valid tetrahedron.
1108
function is_valid_tetrahedron(amount, coordinates; tol = 10^-4)
1109
a = SVector(coordinates[1, 1], coordinates[2, 1], coordinates[3, 1])
1110
b = SVector(coordinates[1, 2], coordinates[2, 2], coordinates[3, 2])
1111
c = SVector(coordinates[1, 3], coordinates[2, 3], coordinates[3, 3])
1112
d = SVector(coordinates[1, 4], coordinates[2, 4], coordinates[3, 4])
1113
1114
if amount == 2 # If two points are the same, then no tetrahedron can be formed.
1115
return !(isapprox(a, b; atol = tol))
1116
elseif amount == 3 # Check if three points are on the same line.
1117
return !on_the_same_line(a, b, c; tol = tol)
1118
elseif amount == 4 # Check if four points form a tetrahedron.
1119
# This is the same as
1120
# A = hcat(coordinates[1, :], coordinates[2, :], coordinates[3, :],
1121
# SVector(1, 1, 1, 1))
1122
# but more efficient.
1123
A = SMatrix{4, 4}(coordinates[1, 1], coordinates[2, 1], coordinates[3, 1], 1,
1124
coordinates[1, 2], coordinates[2, 2], coordinates[3, 2], 1,
1125
coordinates[1, 3], coordinates[2, 3], coordinates[3, 3], 1,
1126
coordinates[1, 4], coordinates[2, 4], coordinates[3, 4], 1)
1127
return !isapprox(det(A), 0; atol = tol)
1128
else # With one point a tetrahedron can always be formed.
1129
return true
1130
end
1131
end
1132
1133
# Check if three given 3D-points are on the same line.
1134
function on_the_same_line(a, b, c; tol = 10^-4)
1135
# Calculate the intersection of the a-b-axis at x=0.
1136
if b[1] == 0
1137
intersect_a_b = b
1138
else
1139
intersect_a_b = a - b .* (a[1] / b[1])
1140
end
1141
# Calculate the intersection of the a-c-axis at x=0.
1142
if c[1] == 0
1143
intersect_a_c = c
1144
else
1145
intersect_a_c = a - c .* (a[1] / c[1])
1146
end
1147
return isapprox(intersect_a_b, intersect_a_c; atol = tol)
1148
end
1149
1150
# Interpolate from four corners of a tetrahedron to a single point.
1151
function tetrahedron_interpolation(x_coordinates_in, y_coordinates_in, z_coordinates_in,
1152
values_in, coordinate_out)
1153
A = hcat(x_coordinates_in, y_coordinates_in, z_coordinates_in, SVector(1, 1, 1, 1))
1154
c = A \ values_in
1155
return c[1] * coordinate_out[1] + c[2] * coordinate_out[2] +
1156
c[3] * coordinate_out[3] + c[4]
1157
end
1158
1159
# Calculate the distances from every entry in `nodes` to the given `point` and return
1160
# the minimal/maximal squared distance as well as the index of the minimal squared distance.
1161
function squared_distances_from_single_point!(distances, nodes, point)
1162
_, n_nodes, _, _, n_elements = size(nodes)
1163
@assert size(nodes, 1) == 3
1164
@assert size(nodes, 3) == size(nodes, 4) == n_nodes
1165
@assert size(distances, 1) == size(distances, 2) == size(distances, 3) == n_nodes
1166
@assert size(distances, 4) == n_elements
1167
1168
# Prepare for reduction
1169
dist_max = typemin(eltype(distances))
1170
dist_min = typemax(eltype(distances))
1171
index_min = CartesianIndex(1, 1, 1, 1)
1172
1173
# Iterate over every entry
1174
for element in 1:n_elements
1175
for z in 1:n_nodes, y in 1:n_nodes, x in 1:n_nodes
1176
dist = (nodes[1, x, y, z, element] - point[1])^2 +
1177
(nodes[2, x, y, z, element] - point[2])^2 +
1178
(nodes[3, x, y, z, element] - point[3])^2
1179
distances[x, y, z, element] = dist
1180
1181
dist_max = max(dist_max, dist)
1182
if dist < dist_min
1183
dist_min = dist
1184
index_min = CartesianIndex(x, y, z, element)
1185
end
1186
end
1187
end
1188
1189
return dist_max, dist_min, index_min
1190
end
1191
1192
# Interpolate the data on given nodes to a single value at given point.
1193
function get_value_at_point_3d_cache(u)
1194
n_variables, n_x_nodes, n_y_nodes, n_z_nodes, n_elements = size(u)
1195
@assert n_x_nodes == n_y_nodes == n_z_nodes
1196
n_nodes = n_x_nodes
1197
1198
distances = zeros(n_nodes, n_nodes, n_nodes, n_elements)
1199
coordinates_tetrahedron = Array{Float64, 2}(undef, 3, 4)
1200
value_tetrahedron = Array{eltype(u)}(undef, n_variables, 4)
1201
1202
cache = (; distances, coordinates_tetrahedron, value_tetrahedron)
1203
return cache
1204
end
1205
1206
function get_value_at_point_3d!(data_on_curve_at_point, point, solution_variables,
1207
nodes, u, equations, solver;
1208
cache = get_value_at_point_3d_cache(u))
1209
# Set up data structures.
1210
n_variables = size(u, 1)
1211
(; distances, coordinates_tetrahedron, value_tetrahedron) = cache
1212
1213
maximum_distance, _, index = squared_distances_from_single_point!(distances, nodes,
1214
point)
1215
# We could also use the following code to find the maximum distance and index:
1216
# maximum_distance = maximum(distances)
1217
# index = argmin(distances)
1218
# However, it is more efficient if we do it in one pass through the memory.
1219
1220
# If the point sits exactly on a node, no interpolation is needed.
1221
nodes_at_index = SVector(nodes[1, index[1], index[2], index[3], index[4]],
1222
nodes[2, index[1], index[2], index[3], index[4]],
1223
nodes[3, index[1], index[2], index[3], index[4]])
1224
if nodes_at_index == point
1225
u_node = get_node_vars(u, equations, solver,
1226
index[1], index[2], index[3], index[4])
1227
data_on_curve_at_point .= solution_variables(u_node, equations)
1228
return data_on_curve_at_point
1229
end
1230
1231
# Restrict the interpolation to the closest element only.
1232
closest_element = index[4]
1233
@views element_distances = distances[:, :, :, closest_element]
1234
1235
# Find a tetrahedron, which is given by four corners, to interpolate from.
1236
for i in 1:4
1237
# Iterate until a valid tetrahedron is found.
1238
while true
1239
index = argmin(element_distances)
1240
element_distances[index[1], index[2], index[3]] = maximum_distance
1241
1242
for k in 1:3
1243
coordinates_tetrahedron[k, i] = nodes[k,
1244
index[1], index[2], index[3],
1245
closest_element]
1246
end
1247
for v in 1:n_variables
1248
value_tetrahedron[v, i] = u[v,
1249
index[1], index[2], index[3],
1250
closest_element]
1251
end
1252
1253
# Look for another point if current tetrahedron is not valid.
1254
if is_valid_tetrahedron(i, coordinates_tetrahedron)
1255
break
1256
end
1257
end
1258
end
1259
1260
# Interpolate from tetrahedron to given point.
1261
x_coordinates = SVector(coordinates_tetrahedron[1, 1],
1262
coordinates_tetrahedron[1, 2],
1263
coordinates_tetrahedron[1, 3],
1264
coordinates_tetrahedron[1, 4])
1265
y_coordinates = SVector(coordinates_tetrahedron[2, 1],
1266
coordinates_tetrahedron[2, 2],
1267
coordinates_tetrahedron[2, 3],
1268
coordinates_tetrahedron[2, 4])
1269
z_coordinates = SVector(coordinates_tetrahedron[3, 1],
1270
coordinates_tetrahedron[3, 2],
1271
coordinates_tetrahedron[3, 3],
1272
coordinates_tetrahedron[3, 4])
1273
# We compute the solution_variables first and interpolate them.
1274
u1 = get_node_vars(value_tetrahedron, equations, solver, 1)
1275
u2 = get_node_vars(value_tetrahedron, equations, solver, 2)
1276
u3 = get_node_vars(value_tetrahedron, equations, solver, 3)
1277
u4 = get_node_vars(value_tetrahedron, equations, solver, 4)
1278
val1 = solution_variables(u1, equations)
1279
val2 = solution_variables(u2, equations)
1280
val3 = solution_variables(u3, equations)
1281
val4 = solution_variables(u4, equations)
1282
for v in eachindex(val1)
1283
values = SVector(val1[v],
1284
val2[v],
1285
val3[v],
1286
val4[v])
1287
data_on_curve_at_point[v] = tetrahedron_interpolation(x_coordinates,
1288
y_coordinates,
1289
z_coordinates,
1290
values, point)
1291
end
1292
1293
return data_on_curve_at_point
1294
end
1295
1296
# Convert 3d unstructured data to 1d slice and interpolate them.
1297
function unstructured_3d_to_1d(original_nodes, unstructured_data, nvisnodes,
1298
reinterpolate, slice, point)
1299
if slice === :x
1300
slice_dimension = 1
1301
other_dimensions = [2, 3]
1302
elseif slice === :y
1303
slice_dimension = 2
1304
other_dimensions = [1, 3]
1305
elseif slice === :z
1306
slice_dimension = 3
1307
other_dimensions = [1, 2]
1308
else
1309
error("illegal dimension '$slice', supported dimensions are :x, :y and :z")
1310
end
1311
1312
# Set up data structures to store new 1D data.
1313
@views new_unstructured_data = similar(unstructured_data[1, 1, ..])
1314
@views temp_unstructured_data = similar(unstructured_data[1, ..])
1315
@views new_nodes = similar(original_nodes[1, 1, 1, ..])
1316
1317
n_nodes_in, _, _, n_elements, n_variables = size(unstructured_data)
1318
nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes_in)
1319
1320
# Test if point lies in the domain.
1321
lower_limit = original_nodes[1, 1, 1, 1, 1]
1322
upper_limit = original_nodes[1, n_nodes_in, n_nodes_in, n_nodes_in, n_elements]
1323
1324
@assert length(point)>=3 "Point must be three-dimensional."
1325
if prod(point[other_dimensions] .< lower_limit) ||
1326
prod(point[other_dimensions] .> upper_limit)
1327
error(string("Slice axis is outside of domain. ",
1328
" point[$other_dimensions]=$(point[other_dimensions]) must be between $lower_limit and $upper_limit"))
1329
end
1330
1331
# Count the amount of new elements.
1332
new_id = 0
1333
1334
# Permute dimensions so that the slice dimensions are always the in correct places for later use.
1335
if slice === :x
1336
original_nodes = permutedims(original_nodes, [1, 3, 4, 2, 5])
1337
unstructured_data = permutedims(unstructured_data, [2, 3, 1, 4, 5])
1338
elseif slice === :y
1339
original_nodes = permutedims(original_nodes, [1, 2, 4, 3, 5])
1340
unstructured_data = permutedims(unstructured_data, [1, 3, 2, 4, 5])
1341
end
1342
1343
# Iterate over all elements to find the ones that lie on the slice axis.
1344
for element_id in 1:n_elements
1345
min_coordinate = original_nodes[:, 1, 1, 1, element_id]
1346
max_coordinate = original_nodes[:, n_nodes_in, n_nodes_in, n_nodes_in,
1347
element_id]
1348
element_length = max_coordinate - min_coordinate
1349
1350
# Test if the element is on the slice axis. If not just continue with the next element.
1351
if !((prod(min_coordinate[other_dimensions] .<= point[other_dimensions]) &&
1352
prod(max_coordinate[other_dimensions] .> point[other_dimensions])) ||
1353
(point[other_dimensions] == upper_limit &&
1354
prod(max_coordinate[other_dimensions] .== upper_limit)))
1355
continue
1356
end
1357
1358
new_id += 1
1359
1360
# Construct vandermonde matrix for interpolation of each 2D element to a 1D element.
1361
normalized_intercept = (point[other_dimensions] .-
1362
min_coordinate[other_dimensions]) /
1363
element_length[1] * 2 .- 1
1364
vandermonde_i = polynomial_interpolation_matrix(nodes_in,
1365
normalized_intercept[1])
1366
vandermonde_ii = polynomial_interpolation_matrix(nodes_in,
1367
normalized_intercept[2])
1368
1369
# Interpolate to each node of new 1D element.
1370
for v in 1:n_variables
1371
for i in 1:n_nodes_in
1372
for ii in 1:n_nodes_in
1373
temp_unstructured_data[i, ii, new_id, v] = (vandermonde_ii * unstructured_data[ii,
1374
:,
1375
i,
1376
element_id,
1377
v])[1]
1378
end
1379
new_unstructured_data[i, new_id, v] = (vandermonde_i * temp_unstructured_data[i,
1380
:,
1381
new_id,
1382
v])[1]
1383
end
1384
end
1385
1386
new_nodes[:, new_id] = original_nodes[slice_dimension, 1, 1, :, element_id]
1387
end
1388
1389
return get_data_1d(reshape(new_nodes[:, 1:new_id], 1, n_nodes_in, new_id),
1390
new_unstructured_data[:, 1:new_id, :], nvisnodes, reinterpolate)
1391
end
1392
1393
# Interpolate unstructured DG data to structured data (cell-centered)
1394
#
1395
# This function takes DG data in an unstructured, Cartesian layout and converts it to a uniformly
1396
# distributed 2D layout.
1397
#
1398
# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may
1399
# thus be changed in future releases.
1400
function unstructured2structured(unstructured_data, normalized_coordinates,
1401
levels, resolution, nvisnodes_per_level)
1402
# Extract data shape information
1403
n_nodes_in, _, n_elements, n_variables = size(unstructured_data)
1404
1405
# Get node coordinates for DG locations on reference element
1406
nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes_in)
1407
1408
# Calculate interpolation vandermonde matrices for each level
1409
max_level = length(nvisnodes_per_level) - 1
1410
vandermonde_per_level = []
1411
for l in 0:max_level
1412
n_nodes_out = nvisnodes_per_level[l + 1]
1413
dx = 2 / n_nodes_out
1414
nodes_out = collect(range(-1 + dx / 2, 1 - dx / 2, length = n_nodes_out))
1415
push!(vandermonde_per_level,
1416
polynomial_interpolation_matrix(nodes_in, nodes_out))
1417
end
1418
1419
# For each element, calculate index position at which to insert data in global data structure
1420
lower_left_index = element2index(normalized_coordinates, levels, resolution,
1421
nvisnodes_per_level)
1422
1423
# Create output data structure
1424
structured = [Matrix{Float64}(undef, resolution, resolution) for _ in 1:n_variables]
1425
1426
# For each variable, interpolate element data and store to global data structure
1427
for v in 1:n_variables
1428
# Reshape data array for use in multiply_dimensionwise function
1429
reshaped_data = reshape(unstructured_data[:, :, :, v], 1, n_nodes_in,
1430
n_nodes_in, n_elements)
1431
1432
for element_id in 1:n_elements
1433
# Extract level for convenience
1434
level = levels[element_id]
1435
1436
# Determine target indices
1437
n_nodes_out = nvisnodes_per_level[level + 1]
1438
first = lower_left_index[:, element_id]
1439
last = first .+ (n_nodes_out - 1)
1440
1441
# Interpolate data
1442
vandermonde = vandermonde_per_level[level + 1]
1443
structured[v][first[1]:last[1], first[2]:last[2]] .= (reshape(multiply_dimensionwise(vandermonde,
1444
reshaped_data[:,
1445
:,
1446
:,
1447
element_id]),
1448
n_nodes_out,
1449
n_nodes_out))
1450
end
1451
end
1452
1453
return structured
1454
end
1455
1456
# For a given normalized element coordinate, return the index of its lower left
1457
# contribution to the global data structure
1458
#
1459
# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may
1460
# thus be changed in future releases.
1461
function element2index(normalized_coordinates, levels, resolution, nvisnodes_per_level)
1462
@assert size(normalized_coordinates, 1)==2 "only works in 2D"
1463
1464
n_elements = length(levels)
1465
1466
# First, determine lower left coordinate for all cells
1467
dx = 2 / resolution
1468
ndim = 2
1469
lower_left_coordinate = Array{Float64}(undef, ndim, n_elements)
1470
for element_id in 1:n_elements
1471
nvisnodes = nvisnodes_per_level[levels[element_id] + 1]
1472
lower_left_coordinate[1, element_id] = (normalized_coordinates[1, element_id] -
1473
(nvisnodes - 1) / 2 * dx)
1474
lower_left_coordinate[2, element_id] = (normalized_coordinates[2, element_id] -
1475
(nvisnodes - 1) / 2 * dx)
1476
end
1477
1478
# Then, convert coordinate to global index
1479
indices = coordinate2index(lower_left_coordinate, resolution)
1480
1481
return indices
1482
end
1483
1484
# Find 2D array index for a 2-tuple of normalized, cell-centered coordinates (i.e., in [-1,1])
1485
#
1486
# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may
1487
# thus be changed in future releases.
1488
function coordinate2index(coordinate, resolution::Integer)
1489
# Calculate 1D normalized coordinates
1490
dx = 2 / resolution
1491
mesh_coordinates = collect(range(-1 + dx / 2, 1 - dx / 2, length = resolution))
1492
1493
# Find index
1494
id_x = searchsortedfirst.(Ref(mesh_coordinates), coordinate[1, :],
1495
lt = (x, y) -> x .< y .- dx / 2)
1496
id_y = searchsortedfirst.(Ref(mesh_coordinates), coordinate[2, :],
1497
lt = (x, y) -> x .< y .- dx / 2)
1498
return transpose(hcat(id_x, id_y))
1499
end
1500
1501
# Calculate the vertices for each mesh cell such that it can be visualized as a closed box
1502
#
1503
# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may
1504
# thus be changed in future releases.
1505
function calc_vertices(coordinates, levels, length_level_0)
1506
ndim = size(coordinates, 1)
1507
@assert ndim==2 "only works in 2D"
1508
1509
# Initialize output arrays
1510
n_elements = length(levels)
1511
n_points_per_element = 2^ndim + 2
1512
x = Vector{Float64}(undef, n_points_per_element * n_elements)
1513
y = Vector{Float64}(undef, n_points_per_element * n_elements)
1514
1515
# Calculate vertices for all coordinates at once
1516
for element_id in 1:n_elements
1517
length = length_level_0 / 2^levels[element_id]
1518
index = n_points_per_element * (element_id - 1)
1519
x[index + 1] = coordinates[1, element_id] - 1 / 2 * length
1520
x[index + 2] = coordinates[1, element_id] + 1 / 2 * length
1521
x[index + 3] = coordinates[1, element_id] + 1 / 2 * length
1522
x[index + 4] = coordinates[1, element_id] - 1 / 2 * length
1523
x[index + 5] = coordinates[1, element_id] - 1 / 2 * length
1524
x[index + 6] = NaN
1525
1526
y[index + 1] = coordinates[2, element_id] - 1 / 2 * length
1527
y[index + 2] = coordinates[2, element_id] - 1 / 2 * length
1528
y[index + 3] = coordinates[2, element_id] + 1 / 2 * length
1529
y[index + 4] = coordinates[2, element_id] + 1 / 2 * length
1530
y[index + 5] = coordinates[2, element_id] - 1 / 2 * length
1531
y[index + 6] = NaN
1532
end
1533
1534
return x, y
1535
end
1536
1537
# Calculate the vertices to plot each grid line for StructuredMesh
1538
#
1539
# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may
1540
# thus be changed in future releases.
1541
function calc_vertices(node_coordinates, mesh)
1542
@unpack cells_per_dimension = mesh
1543
@assert size(node_coordinates, 1)==2 "only works in 2D"
1544
1545
linear_indices = LinearIndices(size(mesh))
1546
1547
# Initialize output arrays
1548
n_lines = sum(cells_per_dimension) + 2
1549
max_length = maximum(cells_per_dimension)
1550
n_nodes = size(node_coordinates, 2)
1551
1552
# Create output as two matrices `x` and `y`, each holding the node locations for each of the `n_lines` grid lines
1553
# The # of rows in the matrices must be sufficient to store the longest dimension (`max_length`),
1554
# and for each the node locations without doubling the corner nodes (`n_nodes-1`), plus the final node (`+1`)
1555
# Rely on Plots.jl to ignore `NaN`s (i.e., they are not plotted) to handle shorter lines
1556
x = fill(NaN, max_length * (n_nodes - 1) + 1, n_lines)
1557
y = fill(NaN, max_length * (n_nodes - 1) + 1, n_lines)
1558
1559
line_index = 1
1560
# Lines in x-direction
1561
# Bottom boundary
1562
i = 1
1563
for cell_x in axes(mesh, 1)
1564
for node in 1:(n_nodes - 1)
1565
x[i, line_index] = node_coordinates[1, node, 1, linear_indices[cell_x, 1]]
1566
y[i, line_index] = node_coordinates[2, node, 1, linear_indices[cell_x, 1]]
1567
1568
i += 1
1569
end
1570
end
1571
# Last point on bottom boundary
1572
x[i, line_index] = node_coordinates[1, end, 1, linear_indices[end, 1]]
1573
y[i, line_index] = node_coordinates[2, end, 1, linear_indices[end, 1]]
1574
1575
# Other lines in x-direction
1576
line_index += 1
1577
for cell_y in axes(mesh, 2)
1578
i = 1
1579
for cell_x in axes(mesh, 1)
1580
for node in 1:(n_nodes - 1)
1581
x[i, line_index] = node_coordinates[1, node, end,
1582
linear_indices[cell_x, cell_y]]
1583
y[i, line_index] = node_coordinates[2, node, end,
1584
linear_indices[cell_x, cell_y]]
1585
1586
i += 1
1587
end
1588
end
1589
# Last point on line
1590
x[i, line_index] = node_coordinates[1, end, end, linear_indices[end, cell_y]]
1591
y[i, line_index] = node_coordinates[2, end, end, linear_indices[end, cell_y]]
1592
1593
line_index += 1
1594
end
1595
1596
# Lines in y-direction
1597
# Left boundary
1598
i = 1
1599
for cell_y in axes(mesh, 2)
1600
for node in 1:(n_nodes - 1)
1601
x[i, line_index] = node_coordinates[1, 1, node, linear_indices[1, cell_y]]
1602
y[i, line_index] = node_coordinates[2, 1, node, linear_indices[1, cell_y]]
1603
1604
i += 1
1605
end
1606
end
1607
# Last point on left boundary
1608
x[i, line_index] = node_coordinates[1, 1, end, linear_indices[1, end]]
1609
y[i, line_index] = node_coordinates[2, 1, end, linear_indices[1, end]]
1610
1611
# Other lines in y-direction
1612
line_index += 1
1613
for cell_x in axes(mesh, 1)
1614
i = 1
1615
for cell_y in axes(mesh, 2)
1616
for node in 1:(n_nodes - 1)
1617
x[i, line_index] = node_coordinates[1, end, node,
1618
linear_indices[cell_x, cell_y]]
1619
y[i, line_index] = node_coordinates[2, end, node,
1620
linear_indices[cell_x, cell_y]]
1621
1622
i += 1
1623
end
1624
end
1625
# Last point on line
1626
x[i, line_index] = node_coordinates[1, end, end, linear_indices[cell_x, end]]
1627
y[i, line_index] = node_coordinates[2, end, end, linear_indices[cell_x, end]]
1628
1629
line_index += 1
1630
end
1631
1632
return x, y
1633
end
1634
1635
# Convert `slice` to orientations (1 -> `x`, 2 -> `y`, 3 -> `z`) for the two axes in a 2D plot
1636
function _get_orientations(mesh, slice)
1637
if ndims(mesh) == 2 || (ndims(mesh) == 3 && slice === :xy)
1638
orientation_x = 1
1639
orientation_y = 2
1640
elseif ndims(mesh) == 3 && slice === :xz
1641
orientation_x = 1
1642
orientation_y = 3
1643
elseif ndims(mesh) == 3 && slice === :yz
1644
orientation_x = 2
1645
orientation_y = 3
1646
else
1647
orientation_x = 0
1648
orientation_y = 0
1649
end
1650
return orientation_x, orientation_y
1651
end
1652
1653
# Convert `orientation` into a guide label (see also `_get_orientations`)
1654
function _get_guide(orientation::Integer)
1655
if orientation == 1
1656
return "\$x\$"
1657
elseif orientation == 2
1658
return "\$y\$"
1659
elseif orientation == 3
1660
return "\$z\$"
1661
else
1662
return ""
1663
end
1664
end
1665
1666
# plotting_interpolation_matrix(dg; kwargs...)
1667
#
1668
# Interpolation matrix which maps discretization nodes to a set of plotting nodes.
1669
# Defaults to the identity matrix of size `length(solver.basis.nodes)`, and interpolates
1670
# to equispaced nodes for DGSEM (set by kwarg `nvisnodes` in the plotting function).
1671
#
1672
# Example:
1673
# ```julia
1674
# A = plotting_interpolation_matrix(dg)
1675
# A * dg.basis.nodes # => vector of nodes at which to plot the solution
1676
# ```
1677
#
1678
# Note: we cannot use UniformScaling to define the interpolation matrix since we use it with `kron`
1679
# to define a multi-dimensional interpolation matrix later.
1680
plotting_interpolation_matrix(dg; kwargs...) = I(length(dg.basis.nodes))
1681
1682
function face_plotting_interpolation_matrix(dg::DGSEM;
1683
nvisnodes = 2 * length(dg.basis.nodes))
1684
return polynomial_interpolation_matrix(dg.basis.nodes, LinRange(-1, 1, nvisnodes))
1685
end
1686
1687
function plotting_interpolation_matrix(dg::DGSEM;
1688
nvisnodes = 2 * length(dg.basis.nodes))
1689
Vp1D = polynomial_interpolation_matrix(dg.basis.nodes, LinRange(-1, 1, nvisnodes))
1690
# For quadrilateral elements, interpolation to plotting nodes involves applying a 1D interpolation
1691
# operator to each line of nodes. This is equivalent to multiplying the vector containing all node
1692
# node coordinates on an element by a Kronecker product of the 1D interpolation operator (e.g., a
1693
# multi-dimensional interpolation operator).
1694
return kron(Vp1D, Vp1D)
1695
end
1696
1697
function reference_node_coordinates_2d(dg::Union{DGSEM, FDSBP})
1698
nodes = get_nodes(dg.basis)
1699
r = vec([nodes[i] for i in eachnode(dg), j in eachnode(dg)])
1700
s = vec([nodes[j] for i in eachnode(dg), j in eachnode(dg)])
1701
return r, s
1702
end
1703
1704
function plotting_interpolation_matrix(dg::FDSBP; kwargs...)
1705
# Typically, DGSEM interpolates LGL nodes to a finer set of uniformly spaced points.
1706
# However, since FDSBP already has equally spaced nodes, we skip this step
1707
return I
1708
end
1709
1710
function face_plotting_interpolation_matrix(dg::FDSBP; kwargs...)
1711
# Typically, DGSEM interpolates LGL nodes to a finer set of uniformly spaced points.
1712
# However, since FDSBP already has equally spaced nodes, we skip this step
1713
return I
1714
end
1715
1716
# Find element and triangle ids containing coordinates given as a matrix [ndims, npoints]
1717
function get_ids_by_coordinates!(ids, coordinates, pd)
1718
if length(ids) != 2 * size(coordinates, 2)
1719
throw(DimensionMismatch("storage length for element ids does not match the number of coordinates"))
1720
end
1721
1722
n_coordinates = size(coordinates, 2)
1723
1724
for index in 1:n_coordinates
1725
point = SVector(coordinates[1, index], coordinates[2, index])
1726
ids[index, :] .= find_element(point, pd)
1727
end
1728
1729
return ids
1730
end
1731
1732
# Find the ids of elements and triangles containing given coordinates by using the triangulation in 'pd'.
1733
function get_ids_by_coordinates(coordinates, pd)
1734
ids = Matrix{Int}(undef, size(coordinates, 2), 2)
1735
get_ids_by_coordinates!(ids, coordinates, pd)
1736
return ids
1737
end
1738
1739
# Check if given 'point' is inside the triangle with corners corresponding to the coordinates of x and y.
1740
function is_in_triangle(point, x, y)
1741
a = SVector(x[1], y[1])
1742
b = SVector(x[2], y[2])
1743
c = SVector(x[3], y[3])
1744
return is_on_same_side(point, a, b, c) && is_on_same_side(point, b, c, a) &&
1745
is_on_same_side(point, c, a, b)
1746
end
1747
1748
# Create an axis through x and y to then check if 'point' is on the same side of the axis as z.
1749
function is_on_same_side(point, x, y, z)
1750
if (y[1] - x[1]) == 0
1751
return (point[1] - x[1]) * (z[1] - x[1]) >= 0
1752
else
1753
a = (y[2] - x[2]) / (y[1] - x[1])
1754
b = x[2] - a * x[1]
1755
return (z[2] - a * z[1] - b) * (point[2] - a * point[1] - b) >= 0
1756
end
1757
end
1758
1759
# For a given 'point', return the id of the element it is contained in in; if not found return 0.
1760
function find_element(point, pd)
1761
n_tri = size(pd.t, 1)
1762
n_elements = size(pd.x, 2)
1763
1764
# Iterate over all elements.
1765
for element in 1:n_elements
1766
# Iterate over all triangles in given element.
1767
for tri in 1:n_tri
1768
# The code below is equivalent to
1769
# x == pd.x[pd.t[tri, :], element]
1770
# y == pd.y[pd.t[tri, :], element]
1771
# but avoids allocations and is thus more efficient.
1772
tri_indices = (pd.t[tri, 1], pd.t[tri, 2], pd.t[tri, 3])
1773
x = SVector(pd.x[tri_indices[1], element],
1774
pd.x[tri_indices[2], element],
1775
pd.x[tri_indices[3], element])
1776
y = SVector(pd.y[tri_indices[1], element],
1777
pd.y[tri_indices[2], element],
1778
pd.y[tri_indices[3], element])
1779
if is_in_triangle(point, x, y)
1780
return (element, tri)
1781
end
1782
end
1783
end
1784
1785
return (0, 0)
1786
end
1787
1788
# Interpolate from three corners of a triangle to a single point.
1789
function triangle_interpolation(x_coordinates_in, y_coordinates_in, values_in,
1790
coordinate_out)
1791
A = hcat(x_coordinates_in, y_coordinates_in, SVector(1, 1, 1))
1792
c = A \ values_in
1793
return c[1] * coordinate_out[1] + c[2] * coordinate_out[2] + c[3]
1794
end
1795
1796
# Create an axis.
1797
function axis_curve(nodes_x, nodes_y, nodes_z, slice, point, n_points)
1798
if n_points === nothing
1799
n_points = 64
1800
end
1801
dimensions = length(point)
1802
curve = zeros(dimensions, n_points)
1803
if slice == :x
1804
xmin, xmax = extrema(nodes_x)
1805
curve[1, :] .= range(xmin, xmax, length = n_points)
1806
curve[2, :] .= point[2]
1807
if dimensions === 3
1808
curve[3, :] .= point[3]
1809
end
1810
elseif slice == :y
1811
ymin, ymax = extrema(nodes_y)
1812
curve[1, :] .= point[1]
1813
curve[2, :] .= range(ymin, ymax, length = n_points)
1814
if dimensions === 3
1815
curve[3, :] .= point[3]
1816
end
1817
elseif slice == :z
1818
zmin, zmax = extrema(nodes_z)
1819
curve[1, :] .= point[1]
1820
curve[2, :] .= point[2]
1821
curve[3, :] .= range(zmin, zmax, length = n_points)
1822
else
1823
@assert false "Input for 'slice' is not supported here."
1824
end
1825
1826
return curve
1827
end
1828
end # @muladd
1829
1830