• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

trixi-framework / Trixi.jl / 21638916092

03 Feb 2026 04:42PM UTC coverage: 25.939% (-71.1%) from 97.034%
21638916092

Pull #2754

github

web-flow
Merge ee9b4b1c0 into ead0db32a
Pull Request #2754: `VolumeIntegralAdaptive` with `IndicatorEntropyChange`

0 of 83 new or added lines in 6 files covered. (0.0%)

31545 existing lines in 536 files now uncovered.

11563 of 44578 relevant lines covered (25.94%)

469675.74 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/visualization/types.jl
1
# Convenience type to allow dispatch on solution objects that were created by Trixi.jl
2
#
3
# This is a union of a Trixi.jl-specific SciMLBase.ODESolution and of Trixi.jl's own
4
# TimeIntegratorSolution.
5
#
6
#! format: off
7
const TrixiODESolution = Union{ODESolution{T, N, uType, uType2, DType, tType, rateType, discType, P} where
8
    {T, N, uType, uType2, DType, tType, rateType, discType, P<:ODEProblem{uType_, tType_, isinplace, P_, F_} where
9
     {uType_, tType_, isinplace, P_<:AbstractSemidiscretization, F_}}, TimeIntegratorSolution}
10
#! format: on
11

12
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).
13
# Since these FMAs can increase the performance of many numerical algorithms,
14
# we need to opt-in explicitly.
15
# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.
16
@muladd begin
17
#! format: noindent
18

19
# This file holds plotting types which can be used for both Plots.jl and Makie.jl.
20

21
# This abstract type is used to derive PlotData types of different dimensions; but still allows to share some functions for them.
22
abstract type AbstractPlotData{NDIMS} end
23

24
# Define additional methods for convenience.
25
# These are defined for AbstractPlotData, so they can be used for all types of plot data.
UNCOV
26
Base.firstindex(pd::AbstractPlotData) = first(pd.variable_names)
×
UNCOV
27
Base.lastindex(pd::AbstractPlotData) = last(pd.variable_names)
×
UNCOV
28
Base.length(pd::AbstractPlotData) = length(pd.variable_names)
×
UNCOV
29
Base.size(pd::AbstractPlotData) = (length(pd),)
×
UNCOV
30
Base.keys(pd::AbstractPlotData) = tuple(pd.variable_names...)
×
31

UNCOV
32
function Base.iterate(pd::AbstractPlotData, state = 1)
×
UNCOV
33
    if state > length(pd)
×
UNCOV
34
        return nothing
×
35
    else
UNCOV
36
        return (pd.variable_names[state] => pd[pd.variable_names[state]], state + 1)
×
37
    end
38
end
39

40
"""
41
    Base.getindex(pd::AbstractPlotData, variable_name)
42

43
Extract a single variable `variable_name` from `pd` for plotting with `Plots.plot`.
44
"""
UNCOV
45
function Base.getindex(pd::AbstractPlotData, variable_name)
×
UNCOV
46
    variable_id = findfirst(isequal(variable_name), pd.variable_names)
×
47

UNCOV
48
    if isnothing(variable_id)
×
UNCOV
49
        throw(KeyError(variable_name))
×
50
    end
51

UNCOV
52
    return PlotDataSeries(pd, variable_id)
×
53
end
54

UNCOV
55
Base.eltype(pd::AbstractPlotData) = Pair{String, PlotDataSeries{typeof(pd)}}
×
56

57
"""
58
    PlotData2D
59

60
Holds all relevant data for creating 2D plots of multiple solution variables and to visualize the
61
mesh.
62
"""
63
struct PlotData2DCartesian{Coordinates, Data, VariableNames, Vertices} <:
64
       AbstractPlotData{2}
65
    x::Coordinates
66
    y::Coordinates
67
    data::Data
68
    variable_names::VariableNames
69
    mesh_vertices_x::Vertices
70
    mesh_vertices_y::Vertices
71
    orientation_x::Int
72
    orientation_y::Int
73
end
74

75
# Show only a truncated output for convenience (the full data does not make sense)
UNCOV
76
function Base.show(io::IO, pd::PlotData2DCartesian)
×
UNCOV
77
    @nospecialize pd # reduce precompilation time
×
78

UNCOV
79
    print(io, "PlotData2DCartesian{",
×
80
          typeof(pd.x), ",",
81
          typeof(pd.data), ",",
82
          typeof(pd.variable_names), ",",
83
          typeof(pd.mesh_vertices_x),
84
          "}(<x>, <y>, <data>, <variable_names>, <mesh_vertices_x>, <mesh_vertices_y>)")
UNCOV
85
    return nothing
×
86
end
87

88
# holds plotting information for UnstructuredMesh2D and DGMulti-compatible meshes
89
struct PlotData2DTriangulated{DataType, NodeType, FaceNodeType, FaceDataType,
90
                              VariableNames, PlottingTriangulation} <:
91
       AbstractPlotData{2}
92
    x::NodeType # physical nodal coordinates, size (num_plotting_nodes x num_elements)
93
    y::NodeType
94
    data::DataType
95
    t::PlottingTriangulation
96
    x_face::FaceNodeType
97
    y_face::FaceNodeType
98
    face_data::FaceDataType
99
    variable_names::VariableNames
100
end
101

102
# Show only a truncated output for convenience (the full data does not make sense)
UNCOV
103
function Base.show(io::IO, pd::PlotData2DTriangulated)
×
UNCOV
104
    @nospecialize pd # reduce precompilation time
×
105

UNCOV
106
    print(io, "PlotData2DTriangulated{",
×
107
          typeof(pd.x), ", ",
108
          typeof(pd.data), ", ",
109
          typeof(pd.x_face), ", ",
110
          typeof(pd.face_data), ", ",
111
          typeof(pd.variable_names),
112
          "}(<x>, <y>, <data>, <plot_triangulation>, <x_face>, <y_face>, <face_data>, <variable_names>)")
UNCOV
113
    return nothing
×
114
end
115

116
"""
117
    PlotData1D
118

119
Holds all relevant data for creating 1D plots of multiple solution variables and to visualize the
120
mesh.
121
"""
122
struct PlotData1D{Coordinates, Data, VariableNames, Vertices} <: AbstractPlotData{1}
123
    x::Coordinates
124
    data::Data
125
    variable_names::VariableNames
126
    mesh_vertices_x::Vertices
127
    orientation_x::Integer
128
end
129

130
# Show only a truncated output for convenience (the full data does not make sense)
UNCOV
131
function Base.show(io::IO, pd::PlotData1D)
×
UNCOV
132
    print(io, "PlotData1D{",
×
133
          typeof(pd.x), ",",
134
          typeof(pd.data), ",",
135
          typeof(pd.variable_names), ",",
136
          typeof(pd.mesh_vertices_x),
137
          "}(<x>, <data>, <variable_names>, <mesh_vertices_x>)")
UNCOV
138
    return nothing
×
139
end
140

141
# Auxiliary data structure for visualizing a single variable
142
struct PlotDataSeries{PD <: AbstractPlotData}
143
    plot_data::PD
144
    variable_id::Int
145
end
146

147
# Show only a truncated output for convenience (the full data does not make sense)
UNCOV
148
function Base.show(io::IO, pds::PlotDataSeries)
×
UNCOV
149
    @nospecialize pds # reduce precompilation time
×
150

UNCOV
151
    print(io, "PlotDataSeries{", typeof(pds.plot_data), "}(<plot_data>, ",
×
152
          pds.variable_id, ")")
UNCOV
153
    return nothing
×
154
end
155

156
# Generic PlotMesh wrapper type.
157
struct PlotMesh{PD <: AbstractPlotData}
158
    plot_data::PD
159
end
160

161
# Show only a truncated output for convenience (the full data does not make sense)
UNCOV
162
function Base.show(io::IO, pm::PlotMesh)
×
UNCOV
163
    @nospecialize pm # reduce precompilation time
×
164

UNCOV
165
    print(io, "PlotMesh{", typeof(pm.plot_data), "}(<plot_data>)")
×
UNCOV
166
    return nothing
×
167
end
168

169
"""
170
    getmesh(pd::AbstractPlotData)
171

172
Extract grid lines from `pd` for plotting with `Plots.plot`.
173
"""
UNCOV
174
getmesh(pd::AbstractPlotData) = PlotMesh(pd)
×
175

176
"""
177
    PlotData2D(u, semi [or mesh, equations, solver, cache];
178
               solution_variables=nothing,
179
               grid_lines=true, max_supported_level=11, nvisnodes=nothing,
180
               slice=:xy, point=(0.0, 0.0, 0.0))
181

182
Create a new `PlotData2D` object that can be used for visualizing 2D/3D DGSEM solution data array
183
`u` with `Plots.jl`. All relevant geometrical information is extracted from the semidiscretization
184
`semi`. By default, the primitive variables (if existent) or the conservative variables (otherwise)
185
from the solution are used for plotting. This can be changed by passing an appropriate conversion
186
function to `solution_variables`.
187

188
For coupled semidiscretizations, i.e., `semi isa` [`SemidiscretizationCoupled`](@ref) a vector of
189
`PlotData2D` objects is returned, one for each semidiscretization which is part of the
190
coupled semidiscretization.
191

192
If `grid_lines` is `true`, also extract grid vertices for visualizing the mesh. The output
193
resolution is indirectly set via `max_supported_level`: all data is interpolated to
194
`2^max_supported_level` uniformly distributed points in each spatial direction, also setting the
195
maximum allowed refinement level in the solution. `nvisnodes` specifies the number of visualization
196
nodes to be used. If it is `nothing`, twice the number of solution DG nodes are used for
197
visualization, and if set to `0`, exactly the number of nodes in the DG elements are used.
198

199
When visualizing data from a three-dimensional simulation, a 2D slice is extracted for plotting.
200
`slice` specifies the plane that is being sliced and may be `:xy`, `:xz`, or `:yz`.
201
The slice position is specified by a `point` that lies on it, which defaults to `(0.0, 0.0, 0.0)`.
202
Both of these values are ignored when visualizing 2D data.
203

204
# Examples
205
```julia
206
julia> using Trixi, Plots
207

208
julia> trixi_include(default_example())
209
[...]
210

211
julia> pd = PlotData2D(sol)
212
PlotData2D(...)
213

214
julia> plot(pd) # To plot all available variables
215

216
julia> plot(pd["scalar"]) # To plot only a single variable
217

218
julia> plot!(getmesh(pd)) # To add grid lines to the plot
219
```
220
"""
UNCOV
221
function PlotData2D(u_ode, semi; kwargs...)
×
UNCOV
222
    return PlotData2D(wrap_array_native(u_ode, semi),
×
223
                      mesh_equations_solver_cache(semi)...;
224
                      kwargs...)
225
end
226

UNCOV
227
function PlotData2D(u_ode, semi::SemidiscretizationCoupled; kwargs...)
×
UNCOV
228
    plot_data_array = []
×
UNCOV
229
    @unpack semis = semi
×
230

UNCOV
231
    foreach_enumerate(semis) do (i, semi_)
×
UNCOV
232
        u_loc = get_system_u_ode(u_ode, i, semi)
×
UNCOV
233
        u_loc_wrapped = wrap_array_native(u_loc, semi_)
×
234

UNCOV
235
        return push!(plot_data_array,
×
236
                     PlotData2D(u_loc_wrapped,
237
                                mesh_equations_solver_cache(semi_)...;
238
                                kwargs...))
239
    end
240

UNCOV
241
    return plot_data_array
×
242
end
243

244
# Redirect `PlotDataTriangulated2D` constructor.
UNCOV
245
function PlotData2DTriangulated(u_ode, semi; kwargs...)
×
UNCOV
246
    return PlotData2DTriangulated(wrap_array_native(u_ode, semi),
×
247
                                  mesh_equations_solver_cache(semi)...;
248
                                  kwargs...)
249
end
250

251
# Create a PlotData2DCartesian object for TreeMeshes on default.
UNCOV
252
function PlotData2D(u, mesh::TreeMesh, equations, solver, cache; kwargs...)
×
UNCOV
253
    return PlotData2DCartesian(u, mesh::TreeMesh, equations, solver, cache; kwargs...)
×
254
end
255

256
# Create a PlotData2DTriangulated object for any type of mesh other than the TreeMesh.
UNCOV
257
function PlotData2D(u, mesh, equations, solver, cache; kwargs...)
×
UNCOV
258
    return PlotData2DTriangulated(u, mesh, equations, solver, cache; kwargs...)
×
259
end
260

261
# Create a PlotData2DCartesian for a TreeMesh.
UNCOV
262
function PlotData2DCartesian(u, mesh::TreeMesh, equations, solver, cache;
×
263
                             solution_variables = nothing,
264
                             grid_lines = true, max_supported_level = 11,
265
                             nvisnodes = nothing,
266
                             slice = :xy, point = (0.0, 0.0, 0.0))
UNCOV
267
    @assert ndims(mesh) in (2, 3) "unsupported number of dimensions $ndims (must be 2 or 3)"
×
UNCOV
268
    solution_variables_ = digest_solution_variables(equations, solution_variables)
×
269

270
    # Extract mesh info
UNCOV
271
    center_level_0 = mesh.tree.center_level_0
×
UNCOV
272
    length_level_0 = mesh.tree.length_level_0
×
UNCOV
273
    leaf_cell_ids = leaf_cells(mesh.tree)
×
UNCOV
274
    coordinates = mesh.tree.coordinates[:, leaf_cell_ids]
×
UNCOV
275
    levels = mesh.tree.levels[leaf_cell_ids]
×
276

UNCOV
277
    unstructured_data = get_unstructured_data(u, solution_variables_, mesh, equations,
×
278
                                              solver, cache)
UNCOV
279
    x, y, data, mesh_vertices_x, mesh_vertices_y = get_data_2d(center_level_0,
×
280
                                                               length_level_0,
281
                                                               leaf_cell_ids,
282
                                                               coordinates, levels,
283
                                                               ndims(mesh),
284
                                                               unstructured_data,
285
                                                               nnodes(solver),
286
                                                               grid_lines,
287
                                                               max_supported_level,
288
                                                               nvisnodes,
289
                                                               slice, point)
UNCOV
290
    variable_names = SVector(varnames(solution_variables_, equations))
×
291

UNCOV
292
    orientation_x, orientation_y = _get_orientations(mesh, slice)
×
293

UNCOV
294
    return PlotData2DCartesian(x, y, data, variable_names, mesh_vertices_x,
×
295
                               mesh_vertices_y,
296
                               orientation_x, orientation_y)
297
end
298

299
"""
300
    PlotData2D(sol; kwargs...)
301

302
Create a `PlotData2D` object from a solution object created by either `OrdinaryDiffEq.solve!` (which
303
returns a `SciMLBase.ODESolution`) or Trixi.jl's own `solve!` (which returns a
304
`TimeIntegratorSolution`).
305
"""
UNCOV
306
function PlotData2D(sol::TrixiODESolution; kwargs...)
×
UNCOV
307
    return PlotData2D(sol.u[end], sol.prob.p; kwargs...)
×
308
end
309

310
# Also redirect when using PlotData2DTriangulate.
UNCOV
311
function PlotData2DTriangulated(sol::TrixiODESolution; kwargs...)
×
UNCOV
312
    return PlotData2DTriangulated(sol.u[end], sol.prob.p; kwargs...)
×
313
end
314

315
# If `u` is an `Array{<:SVectors}` and not a `StructArray`, convert it to a `StructArray` first.
316
function PlotData2D(u::Array{<:SVector, 2}, mesh, equations, dg::DGMulti, cache;
×
317
                    solution_variables = nothing, nvisnodes = 2 * nnodes(dg))
318
    nvars = length(first(u))
×
319
    u_structarray = StructArray{eltype(u)}(ntuple(_ -> zeros(eltype(first(u)), size(u)),
×
320
                                                  nvars))
321
    for (i, u_i) in enumerate(u)
×
322
        u_structarray[i] = u_i
×
323
    end
×
324

325
    # re-dispatch to PlotData2D with mesh, equations, dg, cache arguments
326
    return PlotData2D(u_structarray, mesh, equations, dg, cache;
×
327
                      solution_variables = solution_variables, nvisnodes = nvisnodes)
328
end
329

330
# constructor which returns an `PlotData2DTriangulated` object.
UNCOV
331
function PlotData2D(u::StructArray, mesh, equations, dg::DGMulti, cache;
×
332
                    solution_variables = nothing, nvisnodes = 2 * nnodes(dg))
UNCOV
333
    rd = dg.basis
×
UNCOV
334
    md = mesh.md
×
335

336
    # Vp = the interpolation matrix from nodal points to plotting points
UNCOV
337
    @unpack Vp = rd
×
UNCOV
338
    interpolate_to_plotting_points!(out, x) = mul!(out, Vp, x)
×
339

UNCOV
340
    solution_variables_ = digest_solution_variables(equations, solution_variables)
×
UNCOV
341
    variable_names = SVector(varnames(solution_variables_, equations))
×
342

UNCOV
343
    if Vp isa UniformScaling
×
344
        num_plotting_points = size(u, 1)
×
345
    else
UNCOV
346
        num_plotting_points = size(Vp, 1)
×
347
    end
UNCOV
348
    nvars = nvariables(equations)
×
UNCOV
349
    uEltype = eltype(first(u))
×
UNCOV
350
    u_plot = StructArray{SVector{nvars, uEltype}}(ntuple(_ -> zeros(uEltype,
×
351
                                                                    num_plotting_points,
352
                                                                    md.num_elements),
353
                                                         nvars))
354

UNCOV
355
    for e in eachelement(mesh, dg, cache)
×
356
        # interpolate solution to plotting nodes element-by-element
UNCOV
357
        StructArrays.foreachfield(interpolate_to_plotting_points!, view(u_plot, :, e),
×
358
                                  view(u, :, e))
359

360
        # transform nodal values of the solution according to `solution_variables`
UNCOV
361
        transform_to_solution_variables!(view(u_plot, :, e), solution_variables_,
×
362
                                         equations)
UNCOV
363
    end
×
364

365
    # interpolate nodal coordinates to plotting points
UNCOV
366
    x_plot, y_plot = map(x -> Vp * x, md.xyz) # md.xyz is a tuple of arrays containing nodal coordinates
×
367

368
    # construct a triangulation of the reference plotting nodes
UNCOV
369
    t = reference_plotting_triangulation(rd.rstp) # rd.rstp = reference coordinates of plotting points
×
370

UNCOV
371
    x_face, y_face, face_data = mesh_plotting_wireframe(u, mesh, equations, dg, cache;
×
372
                                                        nvisnodes = nvisnodes)
373

UNCOV
374
    return PlotData2DTriangulated(x_plot, y_plot, u_plot, t, x_face, y_face, face_data,
×
375
                                  variable_names)
376
end
377

378
# specializes the PlotData2D constructor to return an PlotData2DTriangulated for any type of mesh.
UNCOV
379
function PlotData2DTriangulated(u, mesh, equations, dg::DGSEM, cache;
×
380
                                solution_variables = nothing,
381
                                nvisnodes = 2 * polydeg(dg))
UNCOV
382
    @assert ndims(mesh)==2 "Input must be two-dimensional."
×
383

UNCOV
384
    n_nodes_2d = nnodes(dg)^ndims(mesh)
×
UNCOV
385
    n_elements = nelements(dg, cache)
×
386

387
    # build nodes on reference element (seems to be the right ordering)
UNCOV
388
    r, s = reference_node_coordinates_2d(dg)
×
389

390
    # reference plotting nodes
UNCOV
391
    if nvisnodes == 0 || nvisnodes === nothing
×
UNCOV
392
        nvisnodes = polydeg(dg) + 1
×
393
    end
UNCOV
394
    plotting_interp_matrix = plotting_interpolation_matrix(dg; nvisnodes = nvisnodes)
×
395

396
    # create triangulation for plotting nodes
UNCOV
397
    r_plot, s_plot = (x -> plotting_interp_matrix * x).((r, s)) # interpolate dg nodes to plotting nodes
×
398

399
    # construct a triangulation of the plotting nodes
UNCOV
400
    t = reference_plotting_triangulation((r_plot, s_plot))
×
401

402
    # extract x,y coordinates and solutions on each element
UNCOV
403
    uEltype = eltype(u)
×
UNCOV
404
    nvars = nvariables(equations)
×
UNCOV
405
    x = reshape(view(cache.elements.node_coordinates, 1, :, :, :), n_nodes_2d,
×
406
                n_elements)
UNCOV
407
    y = reshape(view(cache.elements.node_coordinates, 2, :, :, :), n_nodes_2d,
×
408
                n_elements)
UNCOV
409
    u_extracted = StructArray{SVector{nvars, uEltype}}(ntuple(_ -> similar(x,
×
410
                                                                           (n_nodes_2d,
411
                                                                            n_elements)),
412
                                                              nvars))
UNCOV
413
    for element in eachelement(dg, cache)
×
UNCOV
414
        sk = 1
×
UNCOV
415
        for j in eachnode(dg), i in eachnode(dg)
×
UNCOV
416
            u_node = get_node_vars(u, equations, dg, i, j, element)
×
UNCOV
417
            u_extracted[sk, element] = u_node
×
UNCOV
418
            sk += 1
×
UNCOV
419
        end
×
UNCOV
420
    end
×
421

422
    # interpolate to volume plotting points
UNCOV
423
    xplot, yplot = plotting_interp_matrix * x, plotting_interp_matrix * y
×
UNCOV
424
    uplot = StructArray{SVector{nvars, uEltype}}(map(x -> plotting_interp_matrix * x,
×
425
                                                     StructArrays.components(u_extracted)))
426

UNCOV
427
    xfp, yfp, ufp = mesh_plotting_wireframe(u_extracted, mesh, equations, dg, cache;
×
428
                                            nvisnodes = nvisnodes)
429

430
    # convert variables based on solution_variables mapping
UNCOV
431
    solution_variables_ = digest_solution_variables(equations, solution_variables)
×
UNCOV
432
    variable_names = SVector(varnames(solution_variables_, equations))
×
433

UNCOV
434
    transform_to_solution_variables!(uplot, solution_variables_, equations)
×
UNCOV
435
    transform_to_solution_variables!(ufp, solution_variables_, equations)
×
436

UNCOV
437
    return PlotData2DTriangulated(xplot, yplot, uplot, t, xfp, yfp, ufp, variable_names)
×
438
end
439

440
# Wrapper struct to indicate that an array represents a scalar data field. Used only for dispatch.
441
struct ScalarData{T}
442
    data::T
443
end
444

445
"""
446
    ScalarPlotData2D(u, semi::AbstractSemidiscretization; kwargs...)
447

448
Returns an `PlotData2DTriangulated` object which is used to visualize a single scalar field.
449
`u` should be an array whose entries correspond to values of the scalar field at nodal points.
450
"""
UNCOV
451
function ScalarPlotData2D(u, semi::AbstractSemidiscretization; kwargs...)
×
UNCOV
452
    return ScalarPlotData2D(u, mesh_equations_solver_cache(semi)...; kwargs...)
×
453
end
454

455
# Returns an `PlotData2DTriangulated` which is used to visualize a single scalar field
UNCOV
456
function ScalarPlotData2D(u, mesh, equations, dg::DGMulti, cache;
×
457
                          variable_name = nothing, nvisnodes = 2 * nnodes(dg))
UNCOV
458
    rd = dg.basis
×
UNCOV
459
    md = mesh.md
×
460

461
    # Vp = the interpolation matrix from nodal points to plotting points
UNCOV
462
    @unpack Vp = rd
×
463

464
    # interpolate nodal coordinates and solution field to plotting points
UNCOV
465
    x_plot, y_plot = map(x -> Vp * x, md.xyz) # md.xyz is a tuple of arrays containing nodal coordinates
×
UNCOV
466
    u_plot = Vp * u
×
467

468
    # construct a triangulation of the reference plotting nodes
UNCOV
469
    t = reference_plotting_triangulation(rd.rstp) # rd.rstp = reference coordinates of plotting points
×
470

471
    # Ignore face data when plotting `ScalarPlotData2D`, since mesh lines can be plotted using
472
    # existing functionality based on `PlotData2D(sol)`.
UNCOV
473
    x_face, y_face, face_data = mesh_plotting_wireframe(ScalarData(u), mesh, equations,
×
474
                                                        dg, cache;
475
                                                        nvisnodes = 2 * nnodes(dg))
476

477
    # wrap solution in ScalarData struct for recipe dispatch
UNCOV
478
    return PlotData2DTriangulated(x_plot, y_plot, ScalarData(u_plot), t,
×
479
                                  x_face, y_face, face_data, variable_name)
480
end
481

UNCOV
482
function ScalarPlotData2D(u, mesh, equations, dg::DGSEM, cache; variable_name = nothing,
×
483
                          nvisnodes = 2 * nnodes(dg))
UNCOV
484
    n_nodes_2d = nnodes(dg)^ndims(mesh)
×
UNCOV
485
    n_elements = nelements(dg, cache)
×
486

487
    # build nodes on reference element (seems to be the right ordering)
UNCOV
488
    r, s = reference_node_coordinates_2d(dg)
×
489

490
    # reference plotting nodes
UNCOV
491
    if nvisnodes == 0 || nvisnodes === nothing
×
492
        nvisnodes = polydeg(dg) + 1
×
493
    end
UNCOV
494
    plotting_interp_matrix = plotting_interpolation_matrix(dg; nvisnodes = nvisnodes)
×
495

496
    # create triangulation for plotting nodes
UNCOV
497
    r_plot, s_plot = (x -> plotting_interp_matrix * x).((r, s)) # interpolate dg nodes to plotting nodes
×
498

499
    # construct a triangulation of the plotting nodes
UNCOV
500
    t = reference_plotting_triangulation((r_plot, s_plot))
×
501

502
    # extract x,y coordinates and reshape them into matrices of size (n_nodes_2d, n_elements)
UNCOV
503
    x = view(cache.elements.node_coordinates, 1, :, :, :)
×
UNCOV
504
    y = view(cache.elements.node_coordinates, 2, :, :, :)
×
UNCOV
505
    x, y = reshape.((x, y), n_nodes_2d, n_elements)
×
506

507
    # interpolate to volume plotting points by multiplying each column by `plotting_interp_matrix`
UNCOV
508
    x_plot, y_plot = plotting_interp_matrix * x, plotting_interp_matrix * y
×
UNCOV
509
    u_plot = plotting_interp_matrix * reshape(u, size(x))
×
510

511
    # Ignore face data when plotting `ScalarPlotData2D`, since mesh lines can be plotted using
512
    # existing functionality based on `PlotData2D(sol)`.
UNCOV
513
    x_face, y_face, face_data = mesh_plotting_wireframe(ScalarData(u), mesh, equations,
×
514
                                                        dg, cache;
515
                                                        nvisnodes = 2 * nnodes(dg))
516

517
    # wrap solution in ScalarData struct for recipe dispatch
UNCOV
518
    return PlotData2DTriangulated(x_plot, y_plot, ScalarData(u_plot), t,
×
519
                                  x_face, y_face, face_data, variable_name)
520
end
521

522
"""
523
    PlotData1D(u, semi [or mesh, equations, solver, cache];
524
               solution_variables=nothing, nvisnodes=nothing,
525
               reinterpolate=Trixi.default_reinterpolate(solver),
526
               slice=:x, point=(0.0, 0.0, 0.0), curve=nothing)
527

528
Create a new `PlotData1D` object that can be used for visualizing 1D DGSEM solution data array
529
`u` with `Plots.jl`. All relevant geometrical information is extracted from the
530
semidiscretization `semi`. By default, the primitive variables (if existent)
531
or the conservative variables (otherwise) from the solution are used for
532
plotting. This can be changed by passing an appropriate conversion function to
533
`solution_variables`, e.g., [`cons2cons`](@ref) or [`cons2prim`](@ref).
534

535
Alternatively, you can also pass a function `u` with signature `u(x, equations)`
536
returning a vector. In this case, the `solution_variables` are ignored. This is useful,
537
e.g., to visualize an analytical solution. The spatial variable `x` is a vector with one
538
element.
539

540
`nvisnodes` specifies the number of visualization nodes to be used. If it is `nothing`,
541
twice the number of solution DG nodes are used for visualization, and if set to `0`,
542
exactly the number of nodes as in the DG elements are used. The solution is interpolated to these
543
nodes for plotting. If `reinterpolate` is `false`, the solution is not interpolated to the `visnodes`,
544
i.e., the solution is plotted at the solution nodes. In this case `nvisnodes` is ignored. By default,
545
`reinterpolate` is set to `false` for `FDSBP` approximations and to `true` otherwise.
546

547
When visualizing data from a two-dimensional simulation, a 1D slice is extracted for plotting.
548
`slice` specifies the axis along which the slice is extracted and may be `:x`, or `:y`.
549
The slice position is specified by a `point` that lies on it, which defaults to `(0.0, 0.0)`.
550
Both of these values are ignored when visualizing 1D data.
551
This applies analogously to three-dimensional simulations, where `slice` may be `:xy`, `:xz`, or `:yz`.
552

553
Another way to visualize 2D/3D data is by creating a plot along a given curve.
554
This is done with the keyword argument `curve`. It can be set to a list of 2D/3D points
555
which define the curve. When using `curve` any other input from `slice` or `point` will be ignored.
556
"""
UNCOV
557
function PlotData1D(u_ode, semi; kwargs...)
×
UNCOV
558
    return PlotData1D(wrap_array_native(u_ode, semi),
×
559
                      mesh_equations_solver_cache(semi)...;
560
                      kwargs...)
561
end
562

UNCOV
563
function PlotData1D(func::Function, semi; kwargs...)
×
UNCOV
564
    return PlotData1D(func,
×
565
                      mesh_equations_solver_cache(semi)...;
566
                      kwargs...)
567
end
568

UNCOV
569
function PlotData1D(u, mesh::TreeMesh, equations, solver, cache;
×
570
                    solution_variables = nothing, nvisnodes = nothing,
571
                    reinterpolate = default_reinterpolate(solver),
572
                    slice = :x, point = (0.0, 0.0, 0.0), curve = nothing,
573
                    variable_names = nothing)
UNCOV
574
    solution_variables_ = digest_solution_variables(equations, solution_variables)
×
UNCOV
575
    variable_names_ = digest_variable_names(solution_variables_, equations,
×
576
                                            variable_names)
577

UNCOV
578
    original_nodes = cache.elements.node_coordinates
×
UNCOV
579
    unstructured_data = get_unstructured_data(u, solution_variables_, mesh, equations,
×
580
                                              solver, cache)
581

UNCOV
582
    orientation_x = 0 # Set 'orientation' to zero on default.
×
583

UNCOV
584
    if ndims(mesh) == 1
×
UNCOV
585
        x, data, mesh_vertices_x = get_data_1d(original_nodes, unstructured_data,
×
586
                                               nvisnodes, reinterpolate)
UNCOV
587
        orientation_x = 1
×
588

589
        # Special care is required for first-order FV approximations since the nodes are the
590
        # cell centers and do not contain the boundaries
UNCOV
591
        n_nodes = size(unstructured_data, 1)
×
UNCOV
592
        if n_nodes == 1
×
593
            n_visnodes = length(x) รท nelements(solver, cache)
×
594
            if n_visnodes != 2
×
595
                throw(ArgumentError("This number of visualization nodes is currently not supported for finite volume approximations."))
×
596
            end
597
            left_boundary = mesh.tree.center_level_0[1] - mesh.tree.length_level_0 / 2
×
598
            dx_2 = zero(left_boundary)
×
599
            for i in 1:div(length(x), 2)
×
600
                # Adjust plot nodes so that they are at the boundaries of each element
601
                dx_2 = x[2 * i - 1] - left_boundary
×
602
                x[2 * i - 1] -= dx_2
×
603
                x[2 * i] += dx_2
×
604
                left_boundary = left_boundary + 2 * dx_2
×
605

606
                # Adjust mesh plot nodes
607
                mesh_vertices_x[i] -= dx_2
×
608
            end
×
609
            mesh_vertices_x[end] += dx_2
×
610
        end
UNCOV
611
    elseif ndims(mesh) == 2
×
UNCOV
612
        if curve !== nothing
×
UNCOV
613
            x, data, mesh_vertices_x = unstructured_2d_to_1d_curve(original_nodes,
×
614
                                                                   unstructured_data,
615
                                                                   nvisnodes, curve,
616
                                                                   mesh, solver, cache)
617
        else
UNCOV
618
            x, data, mesh_vertices_x = unstructured_2d_to_1d(original_nodes,
×
619
                                                             unstructured_data,
620
                                                             nvisnodes, reinterpolate,
621
                                                             slice, point)
622
        end
623
    else # ndims(mesh) == 3
UNCOV
624
        if curve !== nothing
×
UNCOV
625
            x, data, mesh_vertices_x = unstructured_3d_to_1d_curve(original_nodes,
×
626
                                                                   unstructured_data,
627
                                                                   nvisnodes, curve,
628
                                                                   mesh, solver, cache)
629
        else
UNCOV
630
            x, data, mesh_vertices_x = unstructured_3d_to_1d(original_nodes,
×
631
                                                             unstructured_data,
632
                                                             nvisnodes, reinterpolate,
633
                                                             slice, point)
634
        end
635
    end
636

UNCOV
637
    return PlotData1D(x, data, variable_names_, mesh_vertices_x,
×
638
                      orientation_x)
639
end
640

641
# unwrap u if it is VectorOfArray
UNCOV
642
PlotData1D(u::VectorOfArray, mesh, equations, dg::DGMulti{1}, cache; kwargs...) = PlotData1D(parent(u),
×
643
                                                                                             mesh,
644
                                                                                             equations,
645
                                                                                             dg,
646
                                                                                             cache;
647
                                                                                             kwargs...)
UNCOV
648
PlotData2D(u::VectorOfArray, mesh, equations, dg::DGMulti{2}, cache; kwargs...) = PlotData2D(parent(u),
×
649
                                                                                             mesh,
650
                                                                                             equations,
651
                                                                                             dg,
652
                                                                                             cache;
653
                                                                                             kwargs...)
654

UNCOV
655
function PlotData1D(u, mesh, equations, solver, cache;
×
656
                    solution_variables = nothing, nvisnodes = nothing,
657
                    reinterpolate = default_reinterpolate(solver),
658
                    slice = :x, point = (0.0, 0.0, 0.0), curve = nothing,
659
                    variable_names = nothing)
UNCOV
660
    solution_variables_ = digest_solution_variables(equations, solution_variables)
×
UNCOV
661
    variable_names_ = digest_variable_names(solution_variables_, equations,
×
662
                                            variable_names)
663

UNCOV
664
    original_nodes = cache.elements.node_coordinates
×
665

UNCOV
666
    orientation_x = 0 # Set 'orientation' to zero on default.
×
667

UNCOV
668
    if ndims(mesh) == 1
×
UNCOV
669
        unstructured_data = get_unstructured_data(u, solution_variables_,
×
670
                                                  mesh, equations, solver, cache)
UNCOV
671
        x, data, mesh_vertices_x = get_data_1d(original_nodes, unstructured_data,
×
672
                                               nvisnodes, reinterpolate)
UNCOV
673
        orientation_x = 1
×
UNCOV
674
    elseif ndims(mesh) == 2
×
UNCOV
675
        x, data, mesh_vertices_x = unstructured_2d_to_1d_curve(u, mesh, equations,
×
676
                                                               solver, cache,
677
                                                               curve, slice,
678
                                                               point, nvisnodes,
679
                                                               solution_variables_)
680
    else # ndims(mesh) == 3
681
        # Extract the information required to create a PlotData1D object.
682
        # If no curve is defined, create an axis curve.
UNCOV
683
        if curve === nothing
×
UNCOV
684
            curve = axis_curve(view(original_nodes, 1, :, :, :, :),
×
685
                               view(original_nodes, 2, :, :, :, :),
686
                               view(original_nodes, 3, :, :, :, :),
687
                               slice, point, nvisnodes)
688
        end
689

690
        # We need to loop through all the points and check in which element they are
691
        # located. A general implementation working for all mesh types has to perform
692
        # a naive loop through all nodes. However, the P4estMesh and the T8codeMesh
693
        # can make use of the efficient search functionality of p4est/t8code
694
        # to speed up the process. Thus, we pass the mesh, too.
UNCOV
695
        x, data, mesh_vertices_x = unstructured_3d_to_1d_curve(u, mesh, equations,
×
696
                                                               solver, cache,
697
                                                               curve,
698
                                                               solution_variables_)
699
    end
700

UNCOV
701
    return PlotData1D(x, data, variable_names_, mesh_vertices_x,
×
702
                      orientation_x)
703
end
704

UNCOV
705
function PlotData1D(func::Function, mesh, equations, dg::DGMulti{1}, cache;
×
706
                    solution_variables = nothing, variable_names = nothing)
UNCOV
707
    x = mesh.md.x
×
UNCOV
708
    u = func.(x, equations)
×
709

UNCOV
710
    return PlotData1D(u, mesh, equations, dg, cache;
×
711
                      solution_variables, variable_names)
712
end
713

714
# Specializes the `PlotData1D` constructor for one-dimensional `DGMulti` solvers.
UNCOV
715
function PlotData1D(u, mesh, equations, dg::DGMulti{1}, cache;
×
716
                    solution_variables = nothing, variable_names = nothing)
UNCOV
717
    solution_variables_ = digest_solution_variables(equations, solution_variables)
×
UNCOV
718
    variable_names_ = digest_variable_names(solution_variables_, equations,
×
719
                                            variable_names)
720

UNCOV
721
    orientation_x = 0 # Set 'orientation' to zero on default.
×
722

UNCOV
723
    if u isa StructArray
×
724
        # Convert conserved variables to the given `solution_variables` and set up
725
        # plotting coordinates
726
        # This uses a "structure of arrays"
UNCOV
727
        data = map(x -> vcat(dg.basis.Vp * x, fill(NaN, 1, size(u, 2))),
×
728
                   StructArrays.components(solution_variables_.(u, equations)))
UNCOV
729
        x = vcat(dg.basis.Vp * mesh.md.x, fill(NaN, 1, size(u, 2)))
×
730

731
        # Here, we ensure that `DGMulti` visualization uses the same data layout and format
732
        # as `TreeMesh`. This enables us to reuse existing plot recipes. In particular,
733
        # `hcat(data...)` creates a matrix of size `num_plotting_points` by `nvariables(equations)`,
734
        # with data on different elements separated by `NaNs`.
UNCOV
735
        x_plot = vec(x)
×
UNCOV
736
        data_plot = hcat(vec.(data)...)
×
737
    else
738
        # Convert conserved variables to the given `solution_variables` and set up
739
        # plotting coordinates
740
        # This uses an "array of structures"
UNCOV
741
        data_tmp = dg.basis.Vp * solution_variables_.(u, equations)
×
UNCOV
742
        data = vcat(data_tmp, fill(NaN * zero(eltype(data_tmp)), 1, size(u, 2)))
×
UNCOV
743
        x = vcat(dg.basis.Vp * mesh.md.x, fill(NaN, 1, size(u, 2)))
×
744

745
        # Same as above - we create `data_plot` as array of size `num_plotting_points`
746
        # by "number of plotting variables".
UNCOV
747
        x_plot = vec(x)
×
UNCOV
748
        data_ = reinterpret(reshape, eltype(eltype(data)), vec(data))
×
749
        # If there is only one solution variable, we need to add a singleton dimension
UNCOV
750
        if ndims(data_) == 1
×
UNCOV
751
            data_plot = reshape(data_, :, 1)
×
752
        else
UNCOV
753
            data_plot = permutedims(data_, (2, 1))
×
754
        end
755
    end
756

UNCOV
757
    return PlotData1D(x_plot, data_plot, variable_names_, mesh.md.VX, orientation_x)
×
758
end
759

760
"""
761
    PlotData1D(sol; kwargs...)
762

763
Create a `PlotData1D` object from a solution object created by either `OrdinaryDiffEq.solve!`
764
(which returns a `SciMLBase.ODESolution`) or Trixi.jl's own `solve!` (which returns a
765
`TimeIntegratorSolution`).
766
"""
UNCOV
767
function PlotData1D(sol::TrixiODESolution; kwargs...)
×
UNCOV
768
    return PlotData1D(sol.u[end], sol.prob.p; kwargs...)
×
769
end
770

UNCOV
771
function PlotData1D(time_series_callback::TimeSeriesCallback, point_id::Integer)
×
UNCOV
772
    @unpack time, variable_names, point_data = time_series_callback
×
773

UNCOV
774
    n_solution_variables = length(variable_names)
×
UNCOV
775
    data = Matrix{Float64}(undef, length(time), n_solution_variables)
×
UNCOV
776
    reshaped = reshape(point_data[point_id], n_solution_variables, length(time))
×
UNCOV
777
    for v in 1:n_solution_variables
×
UNCOV
778
        @views data[:, v] = reshaped[v, :]
×
UNCOV
779
    end
×
780

UNCOV
781
    mesh_vertices_x = Vector{Float64}(undef, 0)
×
782

UNCOV
783
    return PlotData1D(time, data, SVector(variable_names), mesh_vertices_x, 0)
×
784
end
785

UNCOV
786
function PlotData1D(cb::DiscreteCallback{<:Any, <:TimeSeriesCallback},
×
787
                    point_id::Integer)
UNCOV
788
    return PlotData1D(cb.affect!, point_id)
×
789
end
790
end # @muladd
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc