Path: blob/main/src/visualization/utilities.jl
5586 views
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).1# Since these FMAs can increase the performance of many numerical algorithms,2# we need to opt-in explicitly.3# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.4@muladd begin5#! format: noindent67@inline num_faces(elem::Tri) = 38@inline num_faces(elem::Quad) = 4910# compute_triangle_area(tri)11#12# Computes the area of a triangle given `tri`, which is a tuple of three points (vectors),13# using the [Shoelace_formula](https://en.wikipedia.org/wiki/Shoelace_formula).14function compute_triangle_area(tri)15A, B, C = tri16return 0.5f0 * (A[1] * (B[2] - C[2]) + B[1] * (C[2] - A[2]) + C[1] * (A[2] - B[2]))17end1819# reference_plotting_triangulation(reference_plotting_coordinates)20#21# Computes a triangulation of the points in `reference_plotting_coordinates`, which is a tuple containing22# vectors of plotting points on the reference element (e.g., reference_plotting_coordinates = (r,s)).23# The reference element is assumed to be [-1,1]^d.24#25# This function returns `t` which is a `3 x N_tri` Matrix{Int} containing indices of triangles in the26# triangulation of the plotting points, with zero-volume triangles removed.27#28# For example, r[t[1, i]] returns the first reference coordinate of the 1st point on the ith triangle.29function reference_plotting_triangulation(reference_plotting_coordinates,30tol = 50 * eps())31# on-the-fly triangulation of plotting nodes on the reference element32tri_in = Triangulate.TriangulateIO()33tri_in.pointlist = permutedims(hcat(reference_plotting_coordinates...))34tri_out, _ = Triangulate.triangulate("Q", tri_in)35triangles = tri_out.trianglelist3637# filter out sliver triangles38has_volume = fill(true, size(triangles, 2))39for i in axes(triangles, 2)40ids = @view triangles[:, i]41x_points = @view tri_out.pointlist[1, ids]42y_points = @view tri_out.pointlist[2, ids]43area = compute_triangle_area(zip(x_points, y_points))44if abs(area) < tol45has_volume[i] = false46end47end48return permutedims(triangles[:, findall(has_volume)])49end5051# This function is used to avoid type instabilities when calling `digest_solution_variables`.52function transform_to_solution_variables!(u, solution_variables, equations)53for (i, u_i) in enumerate(u)54u[i] = solution_variables(u_i, equations)55end56end5758# global_plotting_triangulation_triplot(u_plot, rst_plot, xyz_plot)59#60# Returns (plotting_coordinates_x, plotting_coordinates_y, ..., plotting_values, plotting_triangulation).61# Output can be used with TriplotRecipes.DGTriPseudocolor(...).62#63# Inputs:64# - xyz_plot = plotting points (tuple of matrices of size (Nplot, K))65# - u_plot = matrix of size (Nplot, K) representing solution to plot.66# - t = triangulation of reference plotting points67function global_plotting_triangulation_triplot(xyz_plot, u_plot, t)68@assert size(first(xyz_plot), 1)==size(u_plot, 1) "Row dimension of u_plot does not match row dimension of xyz_plot"6970# build discontinuous data on plotting triangular mesh71num_plotting_points, num_elements = size(u_plot)72num_reference_plotting_triangles = size(t, 1)73num_plotting_elements_total = num_reference_plotting_triangles * num_elements7475# each column of `tp` corresponds to a vertex of a plotting triangle76tp = zeros(Int32, 3, num_plotting_elements_total)77zp = similar(tp, eltype(u_plot))78for e in 1:num_elements79for i in 1:num_reference_plotting_triangles80tp[:, i + (e - 1) * num_reference_plotting_triangles] .= @views t[i, :] .+81(e - 1) *82num_plotting_points83zp[:, i + (e - 1) * num_reference_plotting_triangles] .= @views u_plot[t[i,84:],85e]86end87end88return vec.(xyz_plot)..., zp, tp89end9091function get_face_node_indices(r, s, dg::Union{<:DGSEM, <:FDSBP}, tol = 100 * eps())92r_max, r_min = extrema(r)93s_max, s_min = extrema(s)94face_1 = findall(@. abs(s - s_min) < tol)95face_2 = findall(@. abs(r - r_max) < tol)96face_3 = findall(@. abs(s - s_max) < tol)97face_4 = findall(@. abs(r - r_min) < tol)98Fmask = hcat(face_1, face_2, face_3, face_4)99return Fmask100end101102# dispatch on semi103function mesh_plotting_wireframe(u, semi)104return mesh_plotting_wireframe(u, mesh_equations_solver_cache(semi)...)105end106107# mesh_plotting_wireframe(u, mesh, equations, dg::DGMulti, cache; num_plotting_pts=25)108#109# Generates data for plotting a mesh wireframe given StartUpDG data types.110# Returns (plotting_coordinates_x, plotting_coordinates_y, nothing) for a 2D mesh wireframe.111function mesh_plotting_wireframe(u::StructArray, mesh, equations, dg::DGMulti, cache;112nvisnodes = 2 * nnodes(dg))113@unpack md = mesh114rd = dg.basis115116# Construct 1D plotting interpolation matrix `Vp1D` for a single face117@unpack N, Fmask = rd118num_face_points = length(Fmask) ÷ num_faces(rd.element_type)119vandermonde_matrix_1D = StartUpDG.vandermonde(Line(), N,120StartUpDG.nodes(Line(),121num_face_points - 1))122rplot = LinRange(-1, 1, nvisnodes)123Vp1D = StartUpDG.vandermonde(Line(), N, rplot) / vandermonde_matrix_1D124125num_faces_total = num_faces(rd.element_type) * md.num_elements126xf, yf = map(x -> reshape(view(x, Fmask, :), num_face_points, num_faces_total),127md.xyz)128uf = similar(u, size(xf))129apply_to_each_field((out, x) -> out .= reshape(view(x, Fmask, :), num_face_points,130num_faces_total), uf, u)131132num_face_plotting_points = size(Vp1D, 1)133x_mesh, y_mesh = ntuple(_ -> zeros(num_face_plotting_points, num_faces_total), 2)134u_mesh = similar(u, (num_face_plotting_points, num_faces_total))135for f in 1:num_faces_total136mul!(view(x_mesh, :, f), Vp1D, view(xf, :, f))137mul!(view(y_mesh, :, f), Vp1D, view(yf, :, f))138apply_to_each_field(mul_by!(Vp1D), view(u_mesh, :, f), view(uf, :, f))139end140141return x_mesh, y_mesh, u_mesh142end143144function mesh_plotting_wireframe(u::StructArray, mesh, equations, dg::DGSEM, cache;145nvisnodes = 2 * nnodes(dg))146147# build nodes on reference element (seems to be the right ordering)148r, s = reference_node_coordinates_2d(dg)149150# extract node coordinates151uEltype = eltype(first(u))152nvars = nvariables(equations)153n_nodes_2d = nnodes(dg)^ndims(mesh)154n_elements = nelements(dg, cache)155x = reshape(view(cache.elements.node_coordinates, 1, :, :, :), n_nodes_2d,156n_elements)157y = reshape(view(cache.elements.node_coordinates, 2, :, :, :), n_nodes_2d,158n_elements)159160# extract indices of local face nodes for wireframe plotting161Fmask = get_face_node_indices(r, s, dg)162plotting_interp_matrix1D = face_plotting_interpolation_matrix(dg;163nvisnodes = nvisnodes)164165# These 5 lines extract the face values on each element from the arrays x,y,sol_to_plot.166# The resulting arrays are then reshaped so that xf, yf, sol_f are Matrix types of size167# (Number of face plotting nodes) x (Number of faces).168function face_first_reshape(x, num_nodes_1D, num_nodes, num_elements)169num_reference_faces = 2 * ndims(mesh)170xf = view(reshape(x, num_nodes, num_elements), vec(Fmask), :)171return reshape(xf, num_nodes_1D, num_elements * num_reference_faces)172end173function reshape_and_interpolate(x)174return plotting_interp_matrix1D *175face_first_reshape(x, nnodes(dg), n_nodes_2d, n_elements)176end177xfp, yfp = map(reshape_and_interpolate, (x, y))178ufp = StructArray{SVector{nvars, uEltype}}(map(reshape_and_interpolate,179StructArrays.components(u)))180181return xfp, yfp, ufp182end183184function mesh_plotting_wireframe(u::ScalarData, mesh, equations,185dg::Union{<:DGSEM, <:FDSBP}, cache;186nvisnodes = 2 * nnodes(dg))187188# build nodes on reference element (seems to be the right ordering)189r, s = reference_node_coordinates_2d(dg)190191# extract node coordinates192n_nodes_2d = nnodes(dg)^ndims(mesh)193n_elements = nelements(dg, cache)194x = reshape(view(cache.elements.node_coordinates, 1, :, :, :), n_nodes_2d,195n_elements)196y = reshape(view(cache.elements.node_coordinates, 2, :, :, :), n_nodes_2d,197n_elements)198199# extract indices of local face nodes for wireframe plotting200Fmask = get_face_node_indices(r, s, dg)201plotting_interp_matrix1D = face_plotting_interpolation_matrix(dg;202nvisnodes = nvisnodes)203204# These 5 lines extract the face values on each element from the arrays x,y,sol_to_plot.205# The resulting arrays are then reshaped so that xf, yf, sol_f are Matrix types of size206# (Number of face plotting nodes) x (Number of faces).207function face_first_reshape(x, num_nodes_1D, num_nodes, num_elements)208num_reference_faces = 2 * ndims(mesh)209xf = view(reshape(x, num_nodes, num_elements), vec(Fmask), :)210return reshape(xf, num_nodes_1D, num_elements * num_reference_faces)211end212function reshape_and_interpolate(x)213return plotting_interp_matrix1D *214face_first_reshape(x, nnodes(dg), n_nodes_2d, n_elements)215end216xfp, yfp, ufp = map(reshape_and_interpolate, (x, y, u.data))217218return xfp, yfp, ufp219end220221function extract_face_nodes_1D(basis::DGMultiBasis{<:Any, <:Tri})222# this assumes that the nodes of the first face on the reference element correspond to a face where223# s = constant, so that the `r` coordinates on this face can be used to construct a nodal basis.224@assert length(basis.Fmask) % num_faces(basis.element_type)==0 "The number of face nodes must be the same for all faces."225return reshape(basis.r[basis.Fmask[:, 1]], :, num_faces(basis.element_type))[:, 1]226end227228function extract_face_nodes_1D(basis::DGMultiBasis{<:Any, <:Quad})229# this assumes that the nodes of the first face on the reference element correspond to a face where230# r = constant, so that the `s` coordinates on this face can be used to construct a nodal basis.231# For quadrilateral elements, this is true since the faces are ordered r = ±1, s = ±1.232@assert length(basis.Fmask) % num_faces(basis.element_type)==0 "The number of face nodes must be the same for all faces."233return reshape(basis.s[basis.Fmask[:, 1]], :, num_faces(basis.element_type))[:, 1]234end235236function mesh_plotting_wireframe(u::ScalarData, mesh, equations, dg::DGMulti, cache;237nvisnodes = 2 * nnodes(dg))238@unpack md = mesh239rd = dg.basis240@unpack N, Fmask = rd241242# number of points on a single face, assuming all faces have the same number of points243# note that since `ScalarPlotData2D` is restricted to Tri and Quad types, this should always be true.244num_face_points = size(Fmask, 1) ÷ num_faces(rd.element_type)245246# extract a set of interpolation nodes for the face nodes. For Polynomial approximations,247# these are usually just (N+1) Gauss-Lobatto nodes. For SBP approximation types, these can248# be more general, with length(face_nodes_1D) ≥ N+1 for certain configurations.249face_nodes_1D = extract_face_nodes_1D(dg.basis)250251# Construct 1D plotting interpolation matrix `Vp1D` for a single face.252# Since num_face_points may be larger than N+1, this is doing a least squares projection253vandermonde_matrix_1D = StartUpDG.vandermonde(Line(), N, face_nodes_1D)254rplot = LinRange(-1, 1, nvisnodes)255Vp1D = StartUpDG.vandermonde(Line(), N, rplot) / vandermonde_matrix_1D256257num_faces_total = num_faces(rd.element_type) * md.num_elements258xf, yf, uf = map(x -> reshape(view(x, Fmask, :), num_face_points, num_faces_total),259(md.xyz..., u.data))260261num_face_plotting_points = size(Vp1D, 1)262x_mesh, y_mesh = ntuple(_ -> zeros(num_face_plotting_points, num_faces_total), 2)263u_mesh = similar(u.data, (num_face_plotting_points, num_faces_total))264for f in 1:num_faces_total265mul!(view(x_mesh, :, f), Vp1D, view(xf, :, f))266mul!(view(y_mesh, :, f), Vp1D, view(yf, :, f))267mul!(view(u_mesh, :, f), Vp1D, view(uf, :, f))268end269270return x_mesh, y_mesh, u_mesh271end272273# These methods are used internally to set the default value of the solution variables:274# - If a `cons2prim` for the given `equations` exists, use it275# - Otherwise, use `cons2cons`, which is defined for all systems of equations276digest_solution_variables(equations, solution_variables) = solution_variables277function digest_solution_variables(equations, solution_variables::Nothing)278if hasmethod(cons2prim, Tuple{AbstractVector, typeof(equations)})279return cons2prim280else281return cons2cons282end283end284285digest_variable_names(solution_variables_, equations, variable_names) = variable_names286digest_variable_names(solution_variables_, equations, ::Nothing) = SVector(varnames(solution_variables_,287equations))288289"""290adapt_to_mesh_level!(u_ode, semi, level)291adapt_to_mesh_level!(sol::Trixi.TrixiODESolution, level)292293Like [`adapt_to_mesh_level`](@ref), but modifies the solution and parts of the294semidiscretization (mesh and caches) in place.295"""296function adapt_to_mesh_level!(u_ode, semi, level)297# Create AMR callback with controller that refines everything towards a single level298amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first),299base_level = level)300amr_callback = AMRCallback(semi, amr_controller, interval = 0)301302# Adapt mesh until it does not change anymore303has_changed = amr_callback.affect!(u_ode, semi, 0.0, 0)304while has_changed305has_changed = amr_callback.affect!(u_ode, semi, 0.0, 0)306end307308return u_ode, semi309end310311function adapt_to_mesh_level!(sol::TrixiODESolution, level)312return adapt_to_mesh_level!(sol.u[end], sol.prob.p, level)313end314315"""316adapt_to_mesh_level(u_ode, semi, level)317adapt_to_mesh_level(sol::Trixi.TrixiODESolution, level)318319Use the regular adaptive mesh refinement routines to adaptively refine/coarsen the solution `u_ode`320with semidiscretization `semi` towards a uniformly refined grid with refinement level `level`. The321solution and semidiscretization are copied such that the original objects remain *unaltered*.322323A convenience method accepts an ODE solution object, from which solution and semidiscretization are324extracted as needed.325326See also: [`adapt_to_mesh_level!`](@ref)327"""328function adapt_to_mesh_level(u_ode, semi, level)329# Create new semidiscretization with copy of the current mesh330mesh, _, _, _ = mesh_equations_solver_cache(semi)331new_semi = remake(semi, mesh = deepcopy(mesh))332333return adapt_to_mesh_level!(deepcopy(u_ode), new_semi, level)334end335336function adapt_to_mesh_level(sol::TrixiODESolution, level)337return adapt_to_mesh_level(sol.u[end], sol.prob.p, level)338end339340# Extract data from a 2D/3D DG solution and prepare it for visualization as a heatmap/contour plot.341#342# Returns a tuple with343# - x coordinates344# - y coordinates345# - nodal 2D data346# - x vertices for mesh visualization347# - y vertices for mesh visualization348#349# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may350# thus be changed in future releases.351function get_data_2d(center_level_0, length_level_0, leaf_cells, coordinates, levels,352ndims, unstructured_data, n_nodes,353grid_lines = false, max_supported_level = 11, nvisnodes = nothing,354slice = :xy, point = (0.0, 0.0, 0.0))355# Determine resolution for data interpolation356max_level = maximum(levels)357if max_level > max_supported_level358error("Maximum refinement level $max_level is higher than " *359"maximum supported level $max_supported_level")360end361max_available_nodes_per_finest_element = 2^(max_supported_level - max_level)362if nvisnodes === nothing363max_nvisnodes = 2 * n_nodes364elseif nvisnodes == 0365max_nvisnodes = n_nodes366else367max_nvisnodes = nvisnodes368end369nvisnodes_at_max_level = min(max_available_nodes_per_finest_element, max_nvisnodes)370resolution = nvisnodes_at_max_level * 2^max_level371nvisnodes_per_level = [2^(max_level - level) * nvisnodes_at_max_level372for level in 0:max_level]373# nvisnodes_per_level is an array (accessed by "level + 1" to accommodate374# level-0-cell) that contains the number of visualization nodes for any375# refinement level to visualize on an equidistant grid376377if ndims == 3378(unstructured_data, coordinates, levels,379center_level_0) = unstructured_3d_to_2d(unstructured_data,380coordinates, levels, length_level_0,381center_level_0, slice,382point)383end384385# Normalize element coordinates: move center to (0, 0) and domain size to [-1, 1]²386n_elements = length(levels)387normalized_coordinates = similar(coordinates)388for element_id in 1:n_elements389@views normalized_coordinates[:, element_id] .= ((coordinates[:, element_id] .-390center_level_0) ./391(length_level_0 / 2))392end393394# Interpolate unstructured DG data to structured data395(structured_data = unstructured2structured(unstructured_data,396normalized_coordinates,397levels, resolution, nvisnodes_per_level))398399# Interpolate cell-centered values to node-centered values400node_centered_data = cell2node(structured_data)401402# Determine axis coordinates for contour plot403xs = collect(range(-1, 1, length = resolution + 1)) .* length_level_0 / 2 .+404center_level_0[1]405ys = collect(range(-1, 1, length = resolution + 1)) .* length_level_0 / 2 .+406center_level_0[2]407408# Determine element vertices to plot grid lines409if grid_lines410mesh_vertices_x, mesh_vertices_y = calc_vertices(coordinates, levels,411length_level_0)412else413mesh_vertices_x = Vector{Float64}(undef, 0)414mesh_vertices_y = Vector{Float64}(undef, 0)415end416417return xs, ys, node_centered_data, mesh_vertices_x, mesh_vertices_y418end419420# For finite difference methods (FDSBP), we do not want to reinterpolate the data, but use the same421# nodes as input nodes. For DG methods, we usually want to reinterpolate the data on an equidistant grid.422default_reinterpolate(solver) = solver isa FDSBP ? false : true423424# Extract data from a 1D DG solution and prepare it for visualization as a line plot.425# This returns a tuple with426# - x coordinates427# - nodal 1D data428#429# Note: This is a low-level function that is not considered as part of Trixi's interface and may430# thus be changed in future releases.431function get_data_1d(original_nodes, unstructured_data, nvisnodes, reinterpolate)432# 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.433n_nodes, n_elements, n_vars = size(unstructured_data)434435# If `reinterpolate` is `false`, we do nothing.436# If `reinterpolate` is `true`, the output nodes are equidistantly spaced.437if reinterpolate == false438interpolated_nodes = original_nodes439interpolated_data = unstructured_data440else441# Set the amount of nodes visualized according to nvisnodes.442if nvisnodes === nothing443max_nvisnodes = 2 * n_nodes444elseif nvisnodes == 0445max_nvisnodes = n_nodes446else447@assert nvisnodes>=2 "nvisnodes must be zero or >= 2"448max_nvisnodes = nvisnodes449end450451interpolated_nodes = Array{eltype(original_nodes), 2}(undef, max_nvisnodes,452n_elements)453interpolated_data = Array{eltype(unstructured_data), 3}(undef, max_nvisnodes,454n_elements, n_vars)455456for j in 1:n_elements457# Interpolate on an equidistant grid.458interpolated_nodes[:, j] .= range(original_nodes[1, 1, j],459original_nodes[1, end, j],460length = max_nvisnodes)461end462463nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes)464nodes_out = collect(range(-1, 1, length = max_nvisnodes))465466# Calculate vandermonde matrix for interpolation.467vandermonde = polynomial_interpolation_matrix(nodes_in, nodes_out)468469# Iterate over all variables.470for v in 1:n_vars471# Interpolate data for each element.472for element in 1:n_elements473multiply_scalar_dimensionwise!(@view(interpolated_data[:, element, v]),474vandermonde,475@view(unstructured_data[:, element, v]))476end477end478end479480# Return results after data is reshaped481return vec(interpolated_nodes), reshape(interpolated_data, :, n_vars),482vcat(original_nodes[1, 1, :], original_nodes[1, end, end])483end484485# Change order of dimensions (variables are now last) and convert data to `solution_variables`486#487# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may488# thus be changed in future releases.489function get_unstructured_data(u, solution_variables, mesh, equations, solver, cache)490if solution_variables === cons2cons491raw_data = u492n_vars = size(raw_data, 1)493else494# Similar to `save_solution_file` in `callbacks_step/save_solution_dg.jl`.495# However, we cannot use `reinterpret` here as `u` might have a non-bits type.496# See https://github.com/trixi-framework/Trixi.jl/pull/2388497n_vars_in = nvariables(equations)498n_vars = length(solution_variables(get_node_vars(u, equations, solver),499equations))500raw_data = Array{eltype(u)}(undef, n_vars, Base.tail(size(u))...)501reshaped_u = reshape(u, n_vars_in, :)502reshaped_r = reshape(raw_data, n_vars, :)503for idx in axes(reshaped_u, 2)504reshaped_r[:, idx] = solution_variables(get_node_vars(reshaped_u, equations,505solver, idx),506equations)507end508end509510unstructured_data = Array{eltype(raw_data)}(undef,511ntuple((d) -> nnodes(solver),512ndims(equations))...,513nelements(solver, cache), n_vars)514for variable in 1:n_vars515@views unstructured_data[.., :, variable] .= raw_data[variable, .., :]516end517518return unstructured_data519end520521# This method is only for plotting 1D functions522function get_unstructured_data(func::Function, solution_variables,523mesh::AbstractMesh{1}, equations, solver, cache)524original_nodes = cache.elements.node_coordinates525# original_nodes has size (1, nnodes, nelements)526# we want u to have size (nvars, nnodes, nelements)527# func.(original_nodes, equations) has size (1, nnodes, nelements), where each component has length n_vars528# Therefore, we drop the first (singleton) dimension and then stack the components529u = stack(func.(SVector.(dropdims(original_nodes; dims = 1)), equations))530return get_unstructured_data(u, solution_variables, mesh, equations, solver, cache)531end532533# Convert cell-centered values to node-centered values by averaging over all534# four neighbors. Solution values at the edges are padded with ghost values535# computed via linear extrapolation.536#537# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may538# thus be changed in future releases.539function cell2node(cell_centered_data)540# Create temporary data structure to make the averaging algorithm as simple541# as possible (by using a ghost layer)542tmp = similar(first(cell_centered_data), size(first(cell_centered_data)) .+ (2, 2))543544# Create output data structure545resolution_in, _ = size(first(cell_centered_data))546resolution_out = resolution_in + 1547node_centered_data = [Matrix{Float64}(undef, resolution_out, resolution_out)548for _ in eachindex(cell_centered_data)]549550for (cell_data, node_data) in zip(cell_centered_data, node_centered_data)551# Fill center with original data552tmp[2:(end - 1), 2:(end - 1)] .= cell_data553554# Linear extrapolation of top and bottom rows555tmp[1, 2:(end - 1)] .= cell_data[1, :] .+ (cell_data[1, :] .- cell_data[2, :])556tmp[end, 2:(end - 1)] .= (cell_data[end, :] .+557(cell_data[end, :] .- cell_data[end - 1, :]))558559# Linear extrapolatation of left and right columns560tmp[2:(end - 1), 1] .= cell_data[:, 1] .+ (cell_data[:, 1] .- cell_data[:, 2])561tmp[2:(end - 1), end] .= (cell_data[:, end] .+562(cell_data[:, end] .- cell_data[:, end - 1]))563564# Corners perform the linear extrapolatation along diagonals565tmp[1, 1] = tmp[2, 2] + (tmp[2, 2] - tmp[3, 3])566tmp[1, end] = tmp[2, end - 1] + (tmp[2, end - 1] - tmp[3, end - 2])567tmp[end, 1] = tmp[end - 1, 2] + (tmp[end - 1, 2] - tmp[end - 2, 3])568tmp[end, end] = (tmp[end - 1, end - 1] +569(tmp[end - 1, end - 1] - tmp[end - 2, end - 2]))570571# Obtain node-centered value by averaging over neighboring cell-centered values572for j in 1:resolution_out573for i in 1:resolution_out574node_data[i, j] = (tmp[i, j] +575tmp[i + 1, j] +576tmp[i, j + 1] +577tmp[i + 1, j + 1]) / 4578end579end580end581582# Transpose583for (index, data) in enumerate(node_centered_data)584node_centered_data[index] = permutedims(data)585end586587return node_centered_data588end589590# Convert 3d unstructured data to 2d data.591# Additional to the new unstructured data updated coordinates, levels and592# center coordinates are returned.593#594# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may595# thus be changed in future releases.596function unstructured_3d_to_2d(unstructured_data, coordinates, levels,597length_level_0, center_level_0, slice,598point)599if slice === :yz600slice_dimension = 1601other_dimensions = [2, 3]602elseif slice === :xz603slice_dimension = 2604other_dimensions = [1, 3]605elseif slice === :xy606slice_dimension = 3607other_dimensions = [1, 2]608else609error("illegal dimension '$slice', supported dimensions are :yz, :xz, and :xy")610end611612# Limits of domain in slice dimension613lower_limit = center_level_0[slice_dimension] - length_level_0 / 2614upper_limit = center_level_0[slice_dimension] + length_level_0 / 2615616@assert length(point)>=3 "Point must be three-dimensional."617if point[slice_dimension] < lower_limit || point[slice_dimension] > upper_limit618error(string("Slice plane is outside of domain.",619" point[$slice_dimension]=$(point[slice_dimension]) must be between $lower_limit and $upper_limit"))620end621622# Extract data shape information623n_nodes_in, _, _, n_elements, n_variables = size(unstructured_data)624625# Get node coordinates for DG locations on reference element626nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes_in)627628# New unstructured data has one dimension less.629# The redundant element ids are removed later.630@views new_unstructured_data = similar(unstructured_data[1, ..])631632# Declare new empty arrays to fill in new coordinates and levels633new_coordinates = Array{Float64}(undef, 2, n_elements)634new_levels = Array{eltype(levels)}(undef, n_elements)635636# Counter for new element ids637new_id = 0638639# Save vandermonde matrices in a Dict to prevent redundant generation640vandermonde_to_2d = Dict()641642# Permute dimensions such that the slice dimension is always the643# third dimension of the array. Below we can always interpolate in the644# third dimension.645if slice === :yz646unstructured_data = permutedims(unstructured_data, [2, 3, 1, 4, 5])647elseif slice === :xz648unstructured_data = permutedims(unstructured_data, [1, 3, 2, 4, 5])649end650651for element_id in 1:n_elements652# Distance from center to border of this element (half the length)653element_length = length_level_0 / 2^levels[element_id]654min_coordinate = coordinates[:, element_id] .- element_length / 2655max_coordinate = coordinates[:, element_id] .+ element_length / 2656657# Check if slice plane and current element intersect.658# The first check uses a "greater but not equal" to only match one cell if the659# slice plane lies between two cells.660# The second check is needed if the slice plane is at the upper border of661# the domain due to this.662if !((min_coordinate[slice_dimension] <= point[slice_dimension] &&663max_coordinate[slice_dimension] > point[slice_dimension]) ||664(point[slice_dimension] == upper_limit &&665max_coordinate[slice_dimension] == upper_limit))666# Continue for loop if they don't intersect667continue668end669670# This element is of interest671new_id += 1672673# Add element to new coordinates and levels674new_coordinates[:, new_id] = coordinates[other_dimensions, element_id]675new_levels[new_id] = levels[element_id]676677# Construct vandermonde matrix (or load from Dict if possible)678normalized_intercept = (point[slice_dimension] -679min_coordinate[slice_dimension]) /680element_length * 2 - 1681682if haskey(vandermonde_to_2d, normalized_intercept)683vandermonde = vandermonde_to_2d[normalized_intercept]684else685# Generate vandermonde matrix to interpolate values at nodes_in to one value686vandermonde = polynomial_interpolation_matrix(nodes_in,687[normalized_intercept])688vandermonde_to_2d[normalized_intercept] = vandermonde689end690691# 1D interpolation to specified slice plane692# We permuted the dimensions above such that now the dimension in which693# we will interpolate is always the third one.694for i in 1:n_nodes_in695for ii in 1:n_nodes_in696# Interpolate in the third dimension697data = unstructured_data[i, ii, :, element_id, :]698699value = multiply_dimensionwise(vandermonde, permutedims(data))700new_unstructured_data[i, ii, new_id, :] = value[:, 1]701end702end703end704705# Remove redundant element ids706unstructured_data = new_unstructured_data[:, :, 1:new_id, :]707new_coordinates = new_coordinates[:, 1:new_id]708new_levels = new_levels[1:new_id]709710center_level_0 = center_level_0[other_dimensions]711712return unstructured_data, new_coordinates, new_levels, center_level_0713end714715# Convert 2d unstructured data to 1d slice and interpolate them.716function unstructured_2d_to_1d(original_nodes, unstructured_data, nvisnodes,717reinterpolate, slice, point)718if slice === :x719slice_dimension = 2720other_dimension = 1721elseif slice === :y722slice_dimension = 1723other_dimension = 2724else725error("illegal dimension '$slice', supported dimensions are :x and :y")726end727728# Set up data structures to store new 1D data.729@views new_unstructured_data = similar(unstructured_data[1, ..])730@views new_nodes = similar(original_nodes[1, 1, ..])731732n_nodes_in, _, n_elements, n_variables = size(unstructured_data)733nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes_in)734735# Test if point lies in the domain.736lower_limit = original_nodes[1, 1, 1, 1]737upper_limit = original_nodes[1, n_nodes_in, n_nodes_in, n_elements]738739@assert length(point)>=2 "Point must be two-dimensional."740if point[slice_dimension] < lower_limit || point[slice_dimension] > upper_limit741error(string("Slice axis is outside of domain. ",742" point[$slice_dimension]=$(point[slice_dimension]) must be between $lower_limit and $upper_limit"))743end744745# Count the amount of new elements.746new_id = 0747748# Permute dimensions so that the slice dimension is always in the correct place for later use.749if slice === :y750original_nodes = permutedims(original_nodes, [1, 3, 2, 4])751unstructured_data = permutedims(unstructured_data, [2, 1, 3, 4])752end753754# Iterate over all elements to find the ones that lie on the slice axis.755for element_id in 1:n_elements756min_coordinate = original_nodes[:, 1, 1, element_id]757max_coordinate = original_nodes[:, n_nodes_in, n_nodes_in, element_id]758element_length = max_coordinate - min_coordinate759760# Test if the element is on the slice axis. If not just continue with the next element.761if !((min_coordinate[slice_dimension] <= point[slice_dimension] &&762max_coordinate[slice_dimension] > point[slice_dimension]) ||763(point[slice_dimension] == upper_limit &&764max_coordinate[slice_dimension] == upper_limit))765continue766end767768new_id += 1769770# Construct vandermonde matrix for interpolation of each 2D element to a 1D element.771normalized_intercept = (point[slice_dimension] -772min_coordinate[slice_dimension]) /773element_length[1] * 2 - 1774vandermonde = polynomial_interpolation_matrix(nodes_in, normalized_intercept)775776# Interpolate to each node of new 1D element.777for v in 1:n_variables778for node in 1:n_nodes_in779new_unstructured_data[node, new_id, v] = (vandermonde * unstructured_data[node,780:,781element_id,782v])[1]783end784end785786new_nodes[:, new_id] = original_nodes[other_dimension, :, 1, element_id]787end788789return get_data_1d(reshape(new_nodes[:, 1:new_id], 1, n_nodes_in, new_id),790new_unstructured_data[:, 1:new_id, :], nvisnodes, reinterpolate)791end792793# Calculate the arc length of a curve given by ndims x npoints point coordinates (piece-wise linear approximation)794function calc_arc_length(coordinates)795n_points = size(coordinates, 2)796arc_length = zeros(n_points)797for i in 1:(n_points - 1)798dist_squared = zero(eltype(arc_length))799for j in axes(coordinates, 1)800dist_squared += (coordinates[j, i + 1] - coordinates[j, i])^2801end802arc_length[i + 1] = arc_length[i] + sqrt(dist_squared)803end804return arc_length805end806807# Convert 2d unstructured data to 1d data at given curve for the TreeMesh.808function unstructured_2d_to_1d_curve(original_nodes, unstructured_data, nvisnodes,809curve, mesh::TreeMesh, solver, cache)810n_points_curve = size(curve)[2]811n_nodes, _, n_elements, n_variables = size(unstructured_data)812nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes)813baryweights_in = barycentric_weights(nodes_in)814815# Utility function to extract points as `SVector`s816get_point(data, idx...) = SVector(data[1, idx...], data[2, idx...])817818# Check if input is correct.819min = get_point(original_nodes, 1, 1, 1)820max = get_point(original_nodes, n_nodes, n_nodes, n_elements)821@assert size(curve)==(2, size(curve)[2]) "Coordinates along curve must be 2xn dimensional."822for element in 1:n_points_curve823p = get_point(curve, element)824if any(p .< min) || any(p .> max)825throw(DomainError("Some coordinates from `curve` are outside of the domain."))826end827end828829# Set nodes according to the length of the curve.830arc_length = calc_arc_length(curve)831832# Setup data structures.833data_on_curve = Array{Float64}(undef, n_points_curve, n_variables)834temp_data = Array{Float64}(undef, n_nodes, n_points_curve, n_variables)835836# For each coordinate find the corresponding element with its id.837element_ids = get_elements_by_coordinates(curve, mesh, solver, cache)838839# These Vandermonde matrices are really 1×n_nodes matrices, i.e.,840# row vectors. We allocate memory here to improve performance.841vandermonde_x = polynomial_interpolation_matrix(nodes_in, zero(eltype(curve)))842vandermonde_y = similar(vandermonde_x)843844# Iterate over all found elements.845for element in 1:n_points_curve846element_id = element_ids[element]847min_coordinate = get_point(original_nodes, 1, 1,848element_id)849max_coordinate = get_point(original_nodes, n_nodes, n_nodes,850element_id)851element_length = max_coordinate - min_coordinate852853normalized_coordinates = (get_point(curve, element) - min_coordinate) /854element_length[1] * 2 .- 1855856# Interpolate to a single point in each element.857# These Vandermonde matrices are really 1×n_nodes matrices, i.e.,858# row vectors.859polynomial_interpolation_matrix!(vandermonde_x, nodes_in,860normalized_coordinates[1], baryweights_in)861polynomial_interpolation_matrix!(vandermonde_y, nodes_in,862normalized_coordinates[2], baryweights_in)863for v in 1:n_variables864for i in 1:n_nodes865res_i = zero(eltype(temp_data))866for n in 1:n_nodes867res_i += vandermonde_y[n] * unstructured_data[i, n, element_id, v]868end869temp_data[i, element, v] = res_i870end871res_v = zero(eltype(data_on_curve))872for n in 1:n_nodes873res_v += vandermonde_x[n] * temp_data[n, element, v]874end875data_on_curve[element, v] = res_v876end877end878879return arc_length, data_on_curve, nothing880end881882# Convert 2d unstructured data from a general mesh to 1d data at given curve.883#884# We need to loop through all the points and check in which element they are885# located. A general implementation working for all mesh types has to perform886# a naive loop through all nodes. Thus, we use this entry point for dispatching887# on the `mesh` type.888function unstructured_2d_to_1d_curve(u, mesh, equations,889solver, cache,890curve, slice,891point, nvisnodes,892solution_variables)893return unstructured_2d_to_1d_curve_general(u, mesh, equations,894solver, cache,895curve, slice,896point, nvisnodes,897solution_variables)898end899900function unstructured_2d_to_1d_curve_general(u, mesh, equations,901solver, cache,902input_curve, slice,903point, nvisnodes,904solution_variables)905# Create a 'PlotData2DTriangulated' object so a triangulation906# can be used when extracting relevant data.907pd = PlotData2DTriangulated(u, mesh, equations, solver, cache;908solution_variables, nvisnodes)909910# If no curve is defined, create an axis curve.911if input_curve === nothing912input_curve = axis_curve(pd.x, pd.y, nothing, slice, point, nvisnodes)913end914915@assert size(input_curve, 1)==2 "Input 'curve' must be 2xn dimensional."916917# For each coordinate find the corresponding triangle with its ids.918# The default value if no element is found is 0.919ids_by_coordinates = get_ids_by_coordinates(input_curve, pd)920found_coordinates = view(ids_by_coordinates, :, 1) .!= 0921922@assert !iszero(found_coordinates) "No points of 'curve' are inside of the solutions domain."923924# These hold the ids of the elements and triangles the points of the curve sit in.925element_ids = @view ids_by_coordinates[found_coordinates, 1]926triangle_ids = @view ids_by_coordinates[found_coordinates, 2]927928# Shorten the curve, so that it contains only point that were found.929curve = @view input_curve[:, found_coordinates]930931n_variables = length(pd.data[1, 1])932n_points_curve = size(curve, 2)933934# Set nodes according to the length of the curve.935arc_length = calc_arc_length(curve)936937# Setup data structures.938data_on_curve = Array{Float64}(undef, n_points_curve, n_variables)939940# Iterate over all points on the curve.941for point in 1:n_points_curve942point_on_curve = SVector(curve[1, point], curve[2, point])943944element = element_ids[point]945triangle_id = triangle_ids[point]946triangle = (pd.t[triangle_id, 1], pd.t[triangle_id, 2], pd.t[triangle_id, 3])947948# Get the x and y coordinates of the corners of given triangle.949x_coordinates_triangle = SVector(pd.x[triangle[1], element],950pd.x[triangle[2], element],951pd.x[triangle[3], element])952y_coordinates_triangle = SVector(pd.y[triangle[1], element],953pd.y[triangle[2], element],954pd.y[triangle[3], element])955956# Extract solution values in corners of the triangle.957data_in_triangle = (pd.data[triangle[1], element],958pd.data[triangle[2], element],959pd.data[triangle[3], element])960961for v in 1:n_variables962# Extract solution values of variable `v` in corners of the triangle.963values_triangle = SVector(data_in_triangle[1][v],964data_in_triangle[2][v],965data_in_triangle[3][v])966967# Linear interpolation in each triangle to the points on the curve.968data_on_curve[point, v] = triangle_interpolation(x_coordinates_triangle,969y_coordinates_triangle,970values_triangle,971point_on_curve)972end973end974975return arc_length, data_on_curve, nothing976end977978# Convert 3d unstructured data to 1d data at given curve.979function unstructured_3d_to_1d_curve(original_nodes, unstructured_data, nvisnodes,980curve, mesh::TreeMesh, solver, cache)981n_points_curve = size(curve)[2]982n_nodes, _, _, n_elements, n_variables = size(unstructured_data)983nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes)984baryweights_in = barycentric_weights(nodes_in)985986# Utility function to extract points as `SVector`s987get_point(data, idx...) = SVector(data[1, idx...],988data[2, idx...],989data[3, idx...])990991# Check if input is correct.992min = get_point(original_nodes, 1, 1, 1, 1)993max = get_point(original_nodes, n_nodes, n_nodes, n_nodes, n_elements)994@assert size(curve)==(3, n_points_curve) "Coordinates along curve must be 3xn dimensional."995for element in 1:n_points_curve996p = get_point(curve, element)997if any(p .< min) || any(p .> max)998throw(DomainError("Some coordinates from `curve` are outside of the domain."))999end1000end10011002# Set nodes according to the length of the curve.1003arc_length = calc_arc_length(curve)10041005# Setup data structures.1006data_on_curve = Array{Float64}(undef, n_points_curve, n_variables)1007temp_data = Array{Float64}(undef, n_nodes, n_nodes + 1, n_points_curve, n_variables)10081009# For each coordinate find the corresponding element with its id.1010element_ids = get_elements_by_coordinates(curve, mesh, solver, cache)10111012# These Vandermonde matrices are really 1×n_nodes matrices, i.e.,1013# row vectors. We allocate memory here to improve performance.1014vandermonde_x = polynomial_interpolation_matrix(nodes_in, zero(eltype(curve)))1015vandermonde_y = similar(vandermonde_x)1016vandermonde_z = similar(vandermonde_x)10171018# Iterate over all found elements.1019for element in 1:n_points_curve1020element_id = element_ids[element]1021min_coordinate = get_point(original_nodes, 1, 1, 1,1022element_id)1023max_coordinate = get_point(original_nodes, n_nodes, n_nodes, n_nodes,1024element_id)1025element_length = max_coordinate - min_coordinate10261027normalized_coordinates = (get_point(curve, element) - min_coordinate) /1028element_length[1] * 2 .- 110291030# Interpolate to a single point in each element.1031# These Vandermonde matrices are really 1×n_nodes matrices, i.e.,1032# row vectors.1033polynomial_interpolation_matrix!(vandermonde_x, nodes_in,1034normalized_coordinates[1], baryweights_in)1035polynomial_interpolation_matrix!(vandermonde_y, nodes_in,1036normalized_coordinates[2], baryweights_in)1037polynomial_interpolation_matrix!(vandermonde_z, nodes_in,1038normalized_coordinates[3], baryweights_in)1039for v in 1:n_variables1040for i in 1:n_nodes1041for ii in 1:n_nodes1042res_ii = zero(eltype(temp_data))1043for n in 1:n_nodes1044res_ii += vandermonde_z[n] *1045unstructured_data[i, ii, n, element_id, v]1046end1047temp_data[i, ii, element, v] = res_ii1048end1049res_i = zero(eltype(temp_data))1050for n in 1:n_nodes1051res_i += vandermonde_y[n] * temp_data[i, n, element, v]1052end1053temp_data[i, n_nodes + 1, element, v] = res_i1054end1055res_v = zero(eltype(temp_data))1056for n in 1:n_nodes1057res_v += vandermonde_x[n] * temp_data[n, n_nodes + 1, element, v]1058end1059data_on_curve[element, v] = res_v1060end1061end10621063return arc_length, data_on_curve, nothing1064end10651066# Convert 3d unstructured data from a general mesh to 1d data at given curve.1067#1068# We need to loop through all the points and check in which element they are1069# located. A general implementation working for all mesh types has to perform1070# a naive loop through all nodes. Thus, we use this entry point for dispatching1071# on the `mesh` type.1072function unstructured_3d_to_1d_curve(u, mesh, equations, solver, cache,1073curve, solution_variables)1074return unstructured_3d_to_1d_curve_general(u, equations, solver, cache,1075curve, solution_variables)1076end10771078function unstructured_3d_to_1d_curve_general(u, equations, solver, cache,1079curve, solution_variables)1080# Set up data structure.1081@assert size(curve, 1) == 31082n_points_curve = size(curve, 2)10831084# Get the number of variables after applying the transformation to solution variables.1085u_node = get_node_vars(u, equations, solver, 1, 1, 1, 1)1086var_node = solution_variables(u_node, equations)1087n_variables = length(var_node)1088data_on_curve = Array{eltype(var_node)}(undef, n_points_curve, n_variables)10891090nodes = cache.elements.node_coordinates1091interpolation_cache = get_value_at_point_3d_cache(u)10921093# Iterate over every point on the curve and determine the solutions value at given point.1094for i in 1:n_points_curve1095point = SVector(curve[1, i], curve[2, i], curve[3, i])1096get_value_at_point_3d!(view(data_on_curve, i, :), point, solution_variables,1097nodes, u, equations, solver;1098cache = interpolation_cache)1099end11001101mesh_vertices_x = nothing11021103return calc_arc_length(curve), data_on_curve, mesh_vertices_x1104end11051106# Check if the first 'amount'-many points can still form a valid tetrahedron.1107function is_valid_tetrahedron(amount, coordinates; tol = 10^-4)1108a = SVector(coordinates[1, 1], coordinates[2, 1], coordinates[3, 1])1109b = SVector(coordinates[1, 2], coordinates[2, 2], coordinates[3, 2])1110c = SVector(coordinates[1, 3], coordinates[2, 3], coordinates[3, 3])1111d = SVector(coordinates[1, 4], coordinates[2, 4], coordinates[3, 4])11121113if amount == 2 # If two points are the same, then no tetrahedron can be formed.1114return !(isapprox(a, b; atol = tol))1115elseif amount == 3 # Check if three points are on the same line.1116return !on_the_same_line(a, b, c; tol = tol)1117elseif amount == 4 # Check if four points form a tetrahedron.1118# This is the same as1119# A = hcat(coordinates[1, :], coordinates[2, :], coordinates[3, :],1120# SVector(1, 1, 1, 1))1121# but more efficient.1122A = SMatrix{4, 4}(coordinates[1, 1], coordinates[2, 1], coordinates[3, 1], 1,1123coordinates[1, 2], coordinates[2, 2], coordinates[3, 2], 1,1124coordinates[1, 3], coordinates[2, 3], coordinates[3, 3], 1,1125coordinates[1, 4], coordinates[2, 4], coordinates[3, 4], 1)1126return !isapprox(det(A), 0; atol = tol)1127else # With one point a tetrahedron can always be formed.1128return true1129end1130end11311132# Check if three given 3D-points are on the same line.1133function on_the_same_line(a, b, c; tol = 10^-4)1134# Calculate the intersection of the a-b-axis at x=0.1135if b[1] == 01136intersect_a_b = b1137else1138intersect_a_b = a - b .* (a[1] / b[1])1139end1140# Calculate the intersection of the a-c-axis at x=0.1141if c[1] == 01142intersect_a_c = c1143else1144intersect_a_c = a - c .* (a[1] / c[1])1145end1146return isapprox(intersect_a_b, intersect_a_c; atol = tol)1147end11481149# Interpolate from four corners of a tetrahedron to a single point.1150function tetrahedron_interpolation(x_coordinates_in, y_coordinates_in, z_coordinates_in,1151values_in, coordinate_out)1152A = hcat(x_coordinates_in, y_coordinates_in, z_coordinates_in, SVector(1, 1, 1, 1))1153c = A \ values_in1154return c[1] * coordinate_out[1] + c[2] * coordinate_out[2] +1155c[3] * coordinate_out[3] + c[4]1156end11571158# Calculate the distances from every entry in `nodes` to the given `point` and return1159# the minimal/maximal squared distance as well as the index of the minimal squared distance.1160function squared_distances_from_single_point!(distances, nodes, point)1161_, n_nodes, _, _, n_elements = size(nodes)1162@assert size(nodes, 1) == 31163@assert size(nodes, 3) == size(nodes, 4) == n_nodes1164@assert size(distances, 1) == size(distances, 2) == size(distances, 3) == n_nodes1165@assert size(distances, 4) == n_elements11661167# Prepare for reduction1168dist_max = typemin(eltype(distances))1169dist_min = typemax(eltype(distances))1170index_min = CartesianIndex(1, 1, 1, 1)11711172# Iterate over every entry1173for element in 1:n_elements1174for z in 1:n_nodes, y in 1:n_nodes, x in 1:n_nodes1175dist = (nodes[1, x, y, z, element] - point[1])^2 +1176(nodes[2, x, y, z, element] - point[2])^2 +1177(nodes[3, x, y, z, element] - point[3])^21178distances[x, y, z, element] = dist11791180dist_max = max(dist_max, dist)1181if dist < dist_min1182dist_min = dist1183index_min = CartesianIndex(x, y, z, element)1184end1185end1186end11871188return dist_max, dist_min, index_min1189end11901191# Interpolate the data on given nodes to a single value at given point.1192function get_value_at_point_3d_cache(u)1193n_variables, n_x_nodes, n_y_nodes, n_z_nodes, n_elements = size(u)1194@assert n_x_nodes == n_y_nodes == n_z_nodes1195n_nodes = n_x_nodes11961197distances = zeros(n_nodes, n_nodes, n_nodes, n_elements)1198coordinates_tetrahedron = Array{Float64, 2}(undef, 3, 4)1199value_tetrahedron = Array{eltype(u)}(undef, n_variables, 4)12001201cache = (; distances, coordinates_tetrahedron, value_tetrahedron)1202return cache1203end12041205function get_value_at_point_3d!(data_on_curve_at_point, point, solution_variables,1206nodes, u, equations, solver;1207cache = get_value_at_point_3d_cache(u))1208# Set up data structures.1209n_variables = size(u, 1)1210(; distances, coordinates_tetrahedron, value_tetrahedron) = cache12111212maximum_distance, _, index = squared_distances_from_single_point!(distances, nodes,1213point)1214# We could also use the following code to find the maximum distance and index:1215# maximum_distance = maximum(distances)1216# index = argmin(distances)1217# However, it is more efficient if we do it in one pass through the memory.12181219# If the point sits exactly on a node, no interpolation is needed.1220nodes_at_index = SVector(nodes[1, index[1], index[2], index[3], index[4]],1221nodes[2, index[1], index[2], index[3], index[4]],1222nodes[3, index[1], index[2], index[3], index[4]])1223if nodes_at_index == point1224u_node = get_node_vars(u, equations, solver,1225index[1], index[2], index[3], index[4])1226data_on_curve_at_point .= solution_variables(u_node, equations)1227return data_on_curve_at_point1228end12291230# Restrict the interpolation to the closest element only.1231closest_element = index[4]1232@views element_distances = distances[:, :, :, closest_element]12331234# Find a tetrahedron, which is given by four corners, to interpolate from.1235for i in 1:41236# Iterate until a valid tetrahedron is found.1237while true1238index = argmin(element_distances)1239element_distances[index[1], index[2], index[3]] = maximum_distance12401241for k in 1:31242coordinates_tetrahedron[k, i] = nodes[k,1243index[1], index[2], index[3],1244closest_element]1245end1246for v in 1:n_variables1247value_tetrahedron[v, i] = u[v,1248index[1], index[2], index[3],1249closest_element]1250end12511252# Look for another point if current tetrahedron is not valid.1253if is_valid_tetrahedron(i, coordinates_tetrahedron)1254break1255end1256end1257end12581259# Interpolate from tetrahedron to given point.1260x_coordinates = SVector(coordinates_tetrahedron[1, 1],1261coordinates_tetrahedron[1, 2],1262coordinates_tetrahedron[1, 3],1263coordinates_tetrahedron[1, 4])1264y_coordinates = SVector(coordinates_tetrahedron[2, 1],1265coordinates_tetrahedron[2, 2],1266coordinates_tetrahedron[2, 3],1267coordinates_tetrahedron[2, 4])1268z_coordinates = SVector(coordinates_tetrahedron[3, 1],1269coordinates_tetrahedron[3, 2],1270coordinates_tetrahedron[3, 3],1271coordinates_tetrahedron[3, 4])1272# We compute the solution_variables first and interpolate them.1273u1 = get_node_vars(value_tetrahedron, equations, solver, 1)1274u2 = get_node_vars(value_tetrahedron, equations, solver, 2)1275u3 = get_node_vars(value_tetrahedron, equations, solver, 3)1276u4 = get_node_vars(value_tetrahedron, equations, solver, 4)1277val1 = solution_variables(u1, equations)1278val2 = solution_variables(u2, equations)1279val3 = solution_variables(u3, equations)1280val4 = solution_variables(u4, equations)1281for v in eachindex(val1)1282values = SVector(val1[v],1283val2[v],1284val3[v],1285val4[v])1286data_on_curve_at_point[v] = tetrahedron_interpolation(x_coordinates,1287y_coordinates,1288z_coordinates,1289values, point)1290end12911292return data_on_curve_at_point1293end12941295# Convert 3d unstructured data to 1d slice and interpolate them.1296function unstructured_3d_to_1d(original_nodes, unstructured_data, nvisnodes,1297reinterpolate, slice, point)1298if slice === :x1299slice_dimension = 11300other_dimensions = [2, 3]1301elseif slice === :y1302slice_dimension = 21303other_dimensions = [1, 3]1304elseif slice === :z1305slice_dimension = 31306other_dimensions = [1, 2]1307else1308error("illegal dimension '$slice', supported dimensions are :x, :y and :z")1309end13101311# Set up data structures to store new 1D data.1312@views new_unstructured_data = similar(unstructured_data[1, 1, ..])1313@views temp_unstructured_data = similar(unstructured_data[1, ..])1314@views new_nodes = similar(original_nodes[1, 1, 1, ..])13151316n_nodes_in, _, _, n_elements, n_variables = size(unstructured_data)1317nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes_in)13181319# Test if point lies in the domain.1320lower_limit = original_nodes[1, 1, 1, 1, 1]1321upper_limit = original_nodes[1, n_nodes_in, n_nodes_in, n_nodes_in, n_elements]13221323@assert length(point)>=3 "Point must be three-dimensional."1324if prod(point[other_dimensions] .< lower_limit) ||1325prod(point[other_dimensions] .> upper_limit)1326error(string("Slice axis is outside of domain. ",1327" point[$other_dimensions]=$(point[other_dimensions]) must be between $lower_limit and $upper_limit"))1328end13291330# Count the amount of new elements.1331new_id = 013321333# Permute dimensions so that the slice dimensions are always the in correct places for later use.1334if slice === :x1335original_nodes = permutedims(original_nodes, [1, 3, 4, 2, 5])1336unstructured_data = permutedims(unstructured_data, [2, 3, 1, 4, 5])1337elseif slice === :y1338original_nodes = permutedims(original_nodes, [1, 2, 4, 3, 5])1339unstructured_data = permutedims(unstructured_data, [1, 3, 2, 4, 5])1340end13411342# Iterate over all elements to find the ones that lie on the slice axis.1343for element_id in 1:n_elements1344min_coordinate = original_nodes[:, 1, 1, 1, element_id]1345max_coordinate = original_nodes[:, n_nodes_in, n_nodes_in, n_nodes_in,1346element_id]1347element_length = max_coordinate - min_coordinate13481349# Test if the element is on the slice axis. If not just continue with the next element.1350if !((prod(min_coordinate[other_dimensions] .<= point[other_dimensions]) &&1351prod(max_coordinate[other_dimensions] .> point[other_dimensions])) ||1352(point[other_dimensions] == upper_limit &&1353prod(max_coordinate[other_dimensions] .== upper_limit)))1354continue1355end13561357new_id += 113581359# Construct vandermonde matrix for interpolation of each 2D element to a 1D element.1360normalized_intercept = (point[other_dimensions] .-1361min_coordinate[other_dimensions]) /1362element_length[1] * 2 .- 11363vandermonde_i = polynomial_interpolation_matrix(nodes_in,1364normalized_intercept[1])1365vandermonde_ii = polynomial_interpolation_matrix(nodes_in,1366normalized_intercept[2])13671368# Interpolate to each node of new 1D element.1369for v in 1:n_variables1370for i in 1:n_nodes_in1371for ii in 1:n_nodes_in1372temp_unstructured_data[i, ii, new_id, v] = (vandermonde_ii * unstructured_data[ii,1373:,1374i,1375element_id,1376v])[1]1377end1378new_unstructured_data[i, new_id, v] = (vandermonde_i * temp_unstructured_data[i,1379:,1380new_id,1381v])[1]1382end1383end13841385new_nodes[:, new_id] = original_nodes[slice_dimension, 1, 1, :, element_id]1386end13871388return get_data_1d(reshape(new_nodes[:, 1:new_id], 1, n_nodes_in, new_id),1389new_unstructured_data[:, 1:new_id, :], nvisnodes, reinterpolate)1390end13911392# Interpolate unstructured DG data to structured data (cell-centered)1393#1394# This function takes DG data in an unstructured, Cartesian layout and converts it to a uniformly1395# distributed 2D layout.1396#1397# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may1398# thus be changed in future releases.1399function unstructured2structured(unstructured_data, normalized_coordinates,1400levels, resolution, nvisnodes_per_level)1401# Extract data shape information1402n_nodes_in, _, n_elements, n_variables = size(unstructured_data)14031404# Get node coordinates for DG locations on reference element1405nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes_in)14061407# Calculate interpolation vandermonde matrices for each level1408max_level = length(nvisnodes_per_level) - 11409vandermonde_per_level = []1410for l in 0:max_level1411n_nodes_out = nvisnodes_per_level[l + 1]1412dx = 2 / n_nodes_out1413nodes_out = collect(range(-1 + dx / 2, 1 - dx / 2, length = n_nodes_out))1414push!(vandermonde_per_level,1415polynomial_interpolation_matrix(nodes_in, nodes_out))1416end14171418# For each element, calculate index position at which to insert data in global data structure1419lower_left_index = element2index(normalized_coordinates, levels, resolution,1420nvisnodes_per_level)14211422# Create output data structure1423structured = [Matrix{Float64}(undef, resolution, resolution) for _ in 1:n_variables]14241425# For each variable, interpolate element data and store to global data structure1426for v in 1:n_variables1427# Reshape data array for use in multiply_dimensionwise function1428reshaped_data = reshape(unstructured_data[:, :, :, v], 1, n_nodes_in,1429n_nodes_in, n_elements)14301431for element_id in 1:n_elements1432# Extract level for convenience1433level = levels[element_id]14341435# Determine target indices1436n_nodes_out = nvisnodes_per_level[level + 1]1437first = lower_left_index[:, element_id]1438last = first .+ (n_nodes_out - 1)14391440# Interpolate data1441vandermonde = vandermonde_per_level[level + 1]1442structured[v][first[1]:last[1], first[2]:last[2]] .= (reshape(multiply_dimensionwise(vandermonde,1443reshaped_data[:,1444:,1445:,1446element_id]),1447n_nodes_out,1448n_nodes_out))1449end1450end14511452return structured1453end14541455# For a given normalized element coordinate, return the index of its lower left1456# contribution to the global data structure1457#1458# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may1459# thus be changed in future releases.1460function element2index(normalized_coordinates, levels, resolution, nvisnodes_per_level)1461@assert size(normalized_coordinates, 1)==2 "only works in 2D"14621463n_elements = length(levels)14641465# First, determine lower left coordinate for all cells1466dx = 2 / resolution1467ndim = 21468lower_left_coordinate = Array{Float64}(undef, ndim, n_elements)1469for element_id in 1:n_elements1470nvisnodes = nvisnodes_per_level[levels[element_id] + 1]1471lower_left_coordinate[1, element_id] = (normalized_coordinates[1, element_id] -1472(nvisnodes - 1) / 2 * dx)1473lower_left_coordinate[2, element_id] = (normalized_coordinates[2, element_id] -1474(nvisnodes - 1) / 2 * dx)1475end14761477# Then, convert coordinate to global index1478indices = coordinate2index(lower_left_coordinate, resolution)14791480return indices1481end14821483# Find 2D array index for a 2-tuple of normalized, cell-centered coordinates (i.e., in [-1,1])1484#1485# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may1486# thus be changed in future releases.1487function coordinate2index(coordinate, resolution::Integer)1488# Calculate 1D normalized coordinates1489dx = 2 / resolution1490mesh_coordinates = collect(range(-1 + dx / 2, 1 - dx / 2, length = resolution))14911492# Find index1493id_x = searchsortedfirst.(Ref(mesh_coordinates), coordinate[1, :],1494lt = (x, y) -> x .< y .- dx / 2)1495id_y = searchsortedfirst.(Ref(mesh_coordinates), coordinate[2, :],1496lt = (x, y) -> x .< y .- dx / 2)1497return transpose(hcat(id_x, id_y))1498end14991500# Calculate the vertices for each mesh cell such that it can be visualized as a closed box1501#1502# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may1503# thus be changed in future releases.1504function calc_vertices(coordinates, levels, length_level_0)1505ndim = size(coordinates, 1)1506@assert ndim==2 "only works in 2D"15071508# Initialize output arrays1509n_elements = length(levels)1510n_points_per_element = 2^ndim + 21511x = Vector{Float64}(undef, n_points_per_element * n_elements)1512y = Vector{Float64}(undef, n_points_per_element * n_elements)15131514# Calculate vertices for all coordinates at once1515for element_id in 1:n_elements1516length = length_level_0 / 2^levels[element_id]1517index = n_points_per_element * (element_id - 1)1518x[index + 1] = coordinates[1, element_id] - 1 / 2 * length1519x[index + 2] = coordinates[1, element_id] + 1 / 2 * length1520x[index + 3] = coordinates[1, element_id] + 1 / 2 * length1521x[index + 4] = coordinates[1, element_id] - 1 / 2 * length1522x[index + 5] = coordinates[1, element_id] - 1 / 2 * length1523x[index + 6] = NaN15241525y[index + 1] = coordinates[2, element_id] - 1 / 2 * length1526y[index + 2] = coordinates[2, element_id] - 1 / 2 * length1527y[index + 3] = coordinates[2, element_id] + 1 / 2 * length1528y[index + 4] = coordinates[2, element_id] + 1 / 2 * length1529y[index + 5] = coordinates[2, element_id] - 1 / 2 * length1530y[index + 6] = NaN1531end15321533return x, y1534end15351536# Calculate the vertices to plot each grid line for StructuredMesh1537#1538# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may1539# thus be changed in future releases.1540function calc_vertices(node_coordinates, mesh)1541@unpack cells_per_dimension = mesh1542@assert size(node_coordinates, 1)==2 "only works in 2D"15431544linear_indices = LinearIndices(size(mesh))15451546# Initialize output arrays1547n_lines = sum(cells_per_dimension) + 21548max_length = maximum(cells_per_dimension)1549n_nodes = size(node_coordinates, 2)15501551# Create output as two matrices `x` and `y`, each holding the node locations for each of the `n_lines` grid lines1552# The # of rows in the matrices must be sufficient to store the longest dimension (`max_length`),1553# and for each the node locations without doubling the corner nodes (`n_nodes-1`), plus the final node (`+1`)1554# Rely on Plots.jl to ignore `NaN`s (i.e., they are not plotted) to handle shorter lines1555x = fill(NaN, max_length * (n_nodes - 1) + 1, n_lines)1556y = fill(NaN, max_length * (n_nodes - 1) + 1, n_lines)15571558line_index = 11559# Lines in x-direction1560# Bottom boundary1561i = 11562for cell_x in axes(mesh, 1)1563for node in 1:(n_nodes - 1)1564x[i, line_index] = node_coordinates[1, node, 1, linear_indices[cell_x, 1]]1565y[i, line_index] = node_coordinates[2, node, 1, linear_indices[cell_x, 1]]15661567i += 11568end1569end1570# Last point on bottom boundary1571x[i, line_index] = node_coordinates[1, end, 1, linear_indices[end, 1]]1572y[i, line_index] = node_coordinates[2, end, 1, linear_indices[end, 1]]15731574# Other lines in x-direction1575line_index += 11576for cell_y in axes(mesh, 2)1577i = 11578for cell_x in axes(mesh, 1)1579for node in 1:(n_nodes - 1)1580x[i, line_index] = node_coordinates[1, node, end,1581linear_indices[cell_x, cell_y]]1582y[i, line_index] = node_coordinates[2, node, end,1583linear_indices[cell_x, cell_y]]15841585i += 11586end1587end1588# Last point on line1589x[i, line_index] = node_coordinates[1, end, end, linear_indices[end, cell_y]]1590y[i, line_index] = node_coordinates[2, end, end, linear_indices[end, cell_y]]15911592line_index += 11593end15941595# Lines in y-direction1596# Left boundary1597i = 11598for cell_y in axes(mesh, 2)1599for node in 1:(n_nodes - 1)1600x[i, line_index] = node_coordinates[1, 1, node, linear_indices[1, cell_y]]1601y[i, line_index] = node_coordinates[2, 1, node, linear_indices[1, cell_y]]16021603i += 11604end1605end1606# Last point on left boundary1607x[i, line_index] = node_coordinates[1, 1, end, linear_indices[1, end]]1608y[i, line_index] = node_coordinates[2, 1, end, linear_indices[1, end]]16091610# Other lines in y-direction1611line_index += 11612for cell_x in axes(mesh, 1)1613i = 11614for cell_y in axes(mesh, 2)1615for node in 1:(n_nodes - 1)1616x[i, line_index] = node_coordinates[1, end, node,1617linear_indices[cell_x, cell_y]]1618y[i, line_index] = node_coordinates[2, end, node,1619linear_indices[cell_x, cell_y]]16201621i += 11622end1623end1624# Last point on line1625x[i, line_index] = node_coordinates[1, end, end, linear_indices[cell_x, end]]1626y[i, line_index] = node_coordinates[2, end, end, linear_indices[cell_x, end]]16271628line_index += 11629end16301631return x, y1632end16331634# Convert `slice` to orientations (1 -> `x`, 2 -> `y`, 3 -> `z`) for the two axes in a 2D plot1635function _get_orientations(mesh, slice)1636if ndims(mesh) == 2 || (ndims(mesh) == 3 && slice === :xy)1637orientation_x = 11638orientation_y = 21639elseif ndims(mesh) == 3 && slice === :xz1640orientation_x = 11641orientation_y = 31642elseif ndims(mesh) == 3 && slice === :yz1643orientation_x = 21644orientation_y = 31645else1646orientation_x = 01647orientation_y = 01648end1649return orientation_x, orientation_y1650end16511652# Convert `orientation` into a guide label (see also `_get_orientations`)1653function _get_guide(orientation::Integer)1654if orientation == 11655return "\$x\$"1656elseif orientation == 21657return "\$y\$"1658elseif orientation == 31659return "\$z\$"1660else1661return ""1662end1663end16641665# plotting_interpolation_matrix(dg; kwargs...)1666#1667# Interpolation matrix which maps discretization nodes to a set of plotting nodes.1668# Defaults to the identity matrix of size `length(solver.basis.nodes)`, and interpolates1669# to equispaced nodes for DGSEM (set by kwarg `nvisnodes` in the plotting function).1670#1671# Example:1672# ```julia1673# A = plotting_interpolation_matrix(dg)1674# A * dg.basis.nodes # => vector of nodes at which to plot the solution1675# ```1676#1677# Note: we cannot use UniformScaling to define the interpolation matrix since we use it with `kron`1678# to define a multi-dimensional interpolation matrix later.1679plotting_interpolation_matrix(dg; kwargs...) = I(length(dg.basis.nodes))16801681function face_plotting_interpolation_matrix(dg::DGSEM;1682nvisnodes = 2 * length(dg.basis.nodes))1683return polynomial_interpolation_matrix(dg.basis.nodes, LinRange(-1, 1, nvisnodes))1684end16851686function plotting_interpolation_matrix(dg::DGSEM;1687nvisnodes = 2 * length(dg.basis.nodes))1688Vp1D = polynomial_interpolation_matrix(dg.basis.nodes, LinRange(-1, 1, nvisnodes))1689# For quadrilateral elements, interpolation to plotting nodes involves applying a 1D interpolation1690# operator to each line of nodes. This is equivalent to multiplying the vector containing all node1691# node coordinates on an element by a Kronecker product of the 1D interpolation operator (e.g., a1692# multi-dimensional interpolation operator).1693return kron(Vp1D, Vp1D)1694end16951696function reference_node_coordinates_2d(dg::Union{DGSEM, FDSBP})1697nodes = get_nodes(dg.basis)1698r = vec([nodes[i] for i in eachnode(dg), j in eachnode(dg)])1699s = vec([nodes[j] for i in eachnode(dg), j in eachnode(dg)])1700return r, s1701end17021703function plotting_interpolation_matrix(dg::FDSBP; kwargs...)1704# Typically, DGSEM interpolates LGL nodes to a finer set of uniformly spaced points.1705# However, since FDSBP already has equally spaced nodes, we skip this step1706return I1707end17081709function face_plotting_interpolation_matrix(dg::FDSBP; kwargs...)1710# Typically, DGSEM interpolates LGL nodes to a finer set of uniformly spaced points.1711# However, since FDSBP already has equally spaced nodes, we skip this step1712return I1713end17141715# Find element and triangle ids containing coordinates given as a matrix [ndims, npoints]1716function get_ids_by_coordinates!(ids, coordinates, pd)1717if length(ids) != 2 * size(coordinates, 2)1718throw(DimensionMismatch("storage length for element ids does not match the number of coordinates"))1719end17201721n_coordinates = size(coordinates, 2)17221723for index in 1:n_coordinates1724point = SVector(coordinates[1, index], coordinates[2, index])1725ids[index, :] .= find_element(point, pd)1726end17271728return ids1729end17301731# Find the ids of elements and triangles containing given coordinates by using the triangulation in 'pd'.1732function get_ids_by_coordinates(coordinates, pd)1733ids = Matrix{Int}(undef, size(coordinates, 2), 2)1734get_ids_by_coordinates!(ids, coordinates, pd)1735return ids1736end17371738# Check if given 'point' is inside the triangle with corners corresponding to the coordinates of x and y.1739function is_in_triangle(point, x, y)1740a = SVector(x[1], y[1])1741b = SVector(x[2], y[2])1742c = SVector(x[3], y[3])1743return is_on_same_side(point, a, b, c) && is_on_same_side(point, b, c, a) &&1744is_on_same_side(point, c, a, b)1745end17461747# Create an axis through x and y to then check if 'point' is on the same side of the axis as z.1748function is_on_same_side(point, x, y, z)1749if (y[1] - x[1]) == 01750return (point[1] - x[1]) * (z[1] - x[1]) >= 01751else1752a = (y[2] - x[2]) / (y[1] - x[1])1753b = x[2] - a * x[1]1754return (z[2] - a * z[1] - b) * (point[2] - a * point[1] - b) >= 01755end1756end17571758# For a given 'point', return the id of the element it is contained in in; if not found return 0.1759function find_element(point, pd)1760n_tri = size(pd.t, 1)1761n_elements = size(pd.x, 2)17621763# Iterate over all elements.1764for element in 1:n_elements1765# Iterate over all triangles in given element.1766for tri in 1:n_tri1767# The code below is equivalent to1768# x == pd.x[pd.t[tri, :], element]1769# y == pd.y[pd.t[tri, :], element]1770# but avoids allocations and is thus more efficient.1771tri_indices = (pd.t[tri, 1], pd.t[tri, 2], pd.t[tri, 3])1772x = SVector(pd.x[tri_indices[1], element],1773pd.x[tri_indices[2], element],1774pd.x[tri_indices[3], element])1775y = SVector(pd.y[tri_indices[1], element],1776pd.y[tri_indices[2], element],1777pd.y[tri_indices[3], element])1778if is_in_triangle(point, x, y)1779return (element, tri)1780end1781end1782end17831784return (0, 0)1785end17861787# Interpolate from three corners of a triangle to a single point.1788function triangle_interpolation(x_coordinates_in, y_coordinates_in, values_in,1789coordinate_out)1790A = hcat(x_coordinates_in, y_coordinates_in, SVector(1, 1, 1))1791c = A \ values_in1792return c[1] * coordinate_out[1] + c[2] * coordinate_out[2] + c[3]1793end17941795# Create an axis.1796function axis_curve(nodes_x, nodes_y, nodes_z, slice, point, n_points)1797if n_points === nothing1798n_points = 641799end1800dimensions = length(point)1801curve = zeros(dimensions, n_points)1802if slice == :x1803xmin, xmax = extrema(nodes_x)1804curve[1, :] .= range(xmin, xmax, length = n_points)1805curve[2, :] .= point[2]1806if dimensions === 31807curve[3, :] .= point[3]1808end1809elseif slice == :y1810ymin, ymax = extrema(nodes_y)1811curve[1, :] .= point[1]1812curve[2, :] .= range(ymin, ymax, length = n_points)1813if dimensions === 31814curve[3, :] .= point[3]1815end1816elseif slice == :z1817zmin, zmax = extrema(nodes_z)1818curve[1, :] .= point[1]1819curve[2, :] .= point[2]1820curve[3, :] .= range(zmin, zmax, length = n_points)1821else1822@assert false "Input for 'slice' is not supported here."1823end18241825return curve1826end1827end # @muladd182818291830