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

trixi-framework / Trixi.jl / 25071984038

28 Apr 2026 06:59PM UTC coverage: 82.771% (-14.4%) from 97.129%
25071984038

push

github

ranocha
set development version to v0.16.7-DEV

40010 of 48338 relevant lines covered (82.77%)

34288212.65 hits per line

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

27.48
/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.
26
Base.firstindex(pd::AbstractPlotData) = first(pd.variable_names)
×
27
Base.lastindex(pd::AbstractPlotData) = last(pd.variable_names)
×
28
Base.length(pd::AbstractPlotData) = length(pd.variable_names)
10✔
29
Base.size(pd::AbstractPlotData) = (length(pd),)
×
30
Base.keys(pd::AbstractPlotData) = tuple(pd.variable_names...)
9✔
31

32
function Base.iterate(pd::AbstractPlotData, state = 1)
6✔
33
    if state > length(pd)
6✔
34
        return nothing
2✔
35
    else
36
        return (pd.variable_names[state] => pd[pd.variable_names[state]], state + 1)
2✔
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
"""
45
function Base.getindex(pd::AbstractPlotData, variable_name)
11✔
46
    variable_id = findfirst(isequal(variable_name), pd.variable_names)
11✔
47

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

52
    return PlotDataSeries(pd, variable_id)
11✔
53
end
54

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
11✔
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)
76
function Base.show(io::IO, pd::PlotData2DCartesian)
×
77
    @nospecialize pd # reduce precompilation time
×
78

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>)")
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)
4✔
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)
103
function Base.show(io::IO, pd::PlotData2DTriangulated)
×
104
    @nospecialize pd # reduce precompilation time
×
105

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>)")
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)
131
function Base.show(io::IO, pd::PlotData1D)
×
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>)")
138
    return nothing
×
139
end
140

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

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

151
    print(io, "PlotDataSeries{", typeof(pds.plot_data), "}(<plot_data>, ",
×
152
          pds.variable_id, ")")
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)
162
function Base.show(io::IO, pm::PlotMesh)
×
163
    @nospecialize pm # reduce precompilation time
×
164

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

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

172
Extract grid lines from `pd` for plotting with `Plots.plot`.
173
"""
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
"""
221
function PlotData2D(u_ode, semi; kwargs...)
22✔
222
    return PlotData2D(wrap_array_native(u_ode, semi),
11✔
223
                      mesh_equations_solver_cache(semi)...;
224
                      kwargs...)
225
end
226

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

231
    foreach_enumerate(semis) do (i, semi_)
1✔
232
        u_loc = get_system_u_ode(u_ode, i, semi)
4✔
233
        u_loc_wrapped = wrap_array_native(u_loc, semi_)
4✔
234

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

241
    return plot_data_array
1✔
242
end
243

244
# Redirect `PlotDataTriangulated2D` constructor.
245
function PlotData2DTriangulated(u_ode, semi; kwargs...)
×
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.
252
function PlotData2D(u, mesh::TreeMesh, equations, solver, cache; kwargs...)
22✔
253
    return PlotData2DCartesian(u, mesh::TreeMesh, equations, solver, cache; kwargs...)
11✔
254
end
255

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

261
# Create a PlotData2DCartesian for a TreeMesh.
262
function PlotData2DCartesian(u, mesh::TreeMesh, equations, solver, cache;
22✔
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))
267
    @assert ndims(mesh) in (2, 3) "unsupported number of dimensions $ndims (must be 2 or 3)"
11✔
268
    solution_variables_ = digest_solution_variables(equations, solution_variables)
11✔
269

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

277
    unstructured_data = get_unstructured_data(u, solution_variables_, mesh, equations,
11✔
278
                                              solver, cache)
279
    x, y, data, mesh_vertices_x, mesh_vertices_y = get_data_2d(center_level_0,
11✔
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)
290
    variable_names = SVector(varnames(solution_variables_, equations))
11✔
291

292
    orientation_x, orientation_y = _get_orientations(mesh, slice)
11✔
293

294
    return PlotData2DCartesian(x, y, data, variable_names, mesh_vertices_x,
11✔
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
"""
306
function PlotData2D(sol::TrixiODESolution; kwargs...)
2✔
307
    return PlotData2D(sol.u[end], sol.prob.p; kwargs...)
1✔
308
end
309

310
# Also redirect when using PlotData2DTriangulate.
311
function PlotData2DTriangulated(sol::TrixiODESolution; kwargs...)
×
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}, 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.
331
function PlotData2D(u::StructArray, mesh, equations, dg::DGMulti, cache;
×
332
                    solution_variables = nothing, nvisnodes = 2 * nnodes(dg))
333
    rd = dg.basis
×
334
    md = mesh.md
×
335

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

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

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

355
    for e in eachelement(mesh, dg, cache)
×
356
        # interpolate solution to plotting nodes element-by-element
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`
361
        transform_to_solution_variables!(view(u_plot, :, e), solution_variables_,
×
362
                                         equations)
363
    end
×
364

365
    # interpolate nodal coordinates to plotting points
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
369
    t = reference_plotting_triangulation(rd.rstp) # rd.rstp = reference coordinates of plotting points
×
370

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

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.
379
function PlotData2DTriangulated(u, mesh, equations, dg::DGSEM, cache;
8✔
380
                                solution_variables = nothing,
381
                                nvisnodes = 2 * polydeg(dg))
382
    @assert ndims(mesh)==2 "Input must be two-dimensional."
4✔
383

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

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

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

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

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

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

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

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

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

434
    transform_to_solution_variables!(uplot, solution_variables_, equations)
4✔
435
    transform_to_solution_variables!(ufp, solution_variables_, equations)
4✔
436

437
    return PlotData2DTriangulated(xplot, yplot, uplot, t, xfp, yfp, ufp, variable_names)
4✔
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
    ScalarPlotData2D(function_to_visualize, u, semi::AbstractSemidiscretization; kwargs...)
448

449
Returns a `PlotData2DTriangulated` object which is used to visualize a single scalar field.
450
`u` should be an array whose entries correspond to values of the scalar field at nodal points.
451

452
The optional argument `function_to_visualize(u, equations)` should be a function which takes 
453
in the conservative variables and equations as input and outputs a scalar variable to be visualized,
454
e.g., [`pressure`](@ref) or [`density`](@ref) for the compressible Euler equations.
455
"""
456
function ScalarPlotData2D(function_to_visualize::Func, u_ode,
×
457
                          semi::AbstractSemidiscretization;
458
                          kwargs...) where {Func}
459
    u = wrap_array(u_ode, semi)
×
460
    scalar_data = evaluate_scalar_function_at_nodes(function_to_visualize,
×
461
                                                    u,
462
                                                    mesh_equations_solver_cache(semi)...)
463
    return ScalarPlotData2D(scalar_data,
×
464
                            mesh_equations_solver_cache(semi)...; kwargs...)
465
end
466

467
function ScalarPlotData2D(scalar_data, semi::AbstractSemidiscretization; kwargs...)
×
468
    return ScalarPlotData2D(scalar_data, mesh_equations_solver_cache(semi)...;
×
469
                            kwargs...)
470
end
471

472
function evaluate_scalar_function_at_nodes(function_to_visualize, u, mesh, equations,
×
473
                                           dg::DGMulti, cache)
474
    # for DGMulti solvers, eltype(u) should be SVector{nvariables(equations)}, so 
475
    # broadcasting `func_to_visualize` over the solution array will work. 
476
    return function_to_visualize.(u, equations)
×
477
end
478

479
function evaluate_scalar_function_at_nodes(function_to_visualize, u,
×
480
                                           mesh::AbstractMesh{2}, equations,
481
                                           dg::Union{<:DGSEM, <:FDSBP}, cache)
482

483
    # `u` should be an array of size (nvariables, nnodes, nnodes, nelements)
484
    f = zeros(eltype(u), nnodes(dg), nnodes(dg), nelements(dg, cache))
×
485
    @threaded for element in eachelement(dg, cache)
×
486
        for j in eachnode(dg), i in eachnode(dg)
×
487
            u_node = get_node_vars(u, equations, dg, i, j, element)
×
488
            f[i, j, element] = function_to_visualize(u_node, equations)
×
489
        end
×
490
    end
×
491
    return f
×
492
end
493

494
# Returns an `PlotData2DTriangulated` which is used to visualize a single scalar field
495
function ScalarPlotData2D(u, mesh, equations, dg::DGMulti, cache;
×
496
                          variable_name = nothing, nvisnodes = 2 * nnodes(dg))
497
    rd = dg.basis
×
498
    md = mesh.md
×
499

500
    # Vp = the interpolation matrix from nodal points to plotting points
501
    @unpack Vp = rd
×
502

503
    # interpolate nodal coordinates and solution field to plotting points
504
    x_plot, y_plot = map(x -> Vp * x, md.xyz) # md.xyz is a tuple of arrays containing nodal coordinates
×
505
    u_plot = Vp * u
×
506

507
    # construct a triangulation of the reference plotting nodes
508
    t = reference_plotting_triangulation(rd.rstp) # rd.rstp = reference coordinates of plotting points
×
509

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

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

521
function ScalarPlotData2D(u, mesh, equations, dg::Union{<:DGSEM, <:FDSBP}, cache;
×
522
                          variable_name = nothing,
523
                          nvisnodes = 2 * nnodes(dg))
524
    n_nodes_2d = nnodes(dg)^ndims(mesh)
×
525
    n_elements = nelements(dg, cache)
×
526

527
    # build nodes on reference element (seems to be the right ordering)
528
    r, s = reference_node_coordinates_2d(dg)
×
529

530
    # reference plotting nodes
531
    if nvisnodes == 0 || nvisnodes === nothing
×
532
        nvisnodes = polydeg(dg) + 1
×
533
    end
534
    plotting_interp_matrix = plotting_interpolation_matrix(dg; nvisnodes = nvisnodes)
×
535

536
    # create triangulation for plotting nodes
537
    r_plot, s_plot = (x -> plotting_interp_matrix * x).((r, s)) # interpolate dg nodes to plotting nodes
×
538

539
    # construct a triangulation of the plotting nodes
540
    t = reference_plotting_triangulation((r_plot, s_plot))
×
541

542
    # extract x,y coordinates and reshape them into matrices of size (n_nodes_2d, n_elements)
543
    x = view(cache.elements.node_coordinates, 1, :, :, :)
×
544
    y = view(cache.elements.node_coordinates, 2, :, :, :)
×
545
    x, y = reshape.((x, y), n_nodes_2d, n_elements)
×
546

547
    # interpolate to volume plotting points by multiplying each column by `plotting_interp_matrix`
548
    x_plot, y_plot = plotting_interp_matrix * x, plotting_interp_matrix * y
×
549
    u_plot = plotting_interp_matrix * reshape(u, size(x))
×
550

551
    # Ignore face data when plotting `ScalarPlotData2D`, since mesh lines can be plotted using
552
    # existing functionality based on `PlotData2D(sol)`.
553
    x_face, y_face, face_data = mesh_plotting_wireframe(ScalarData(u), mesh, equations,
×
554
                                                        dg, cache;
555
                                                        nvisnodes = 2 * nnodes(dg))
556

557
    # wrap solution in ScalarData struct for recipe dispatch
558
    return PlotData2DTriangulated(x_plot, y_plot, ScalarData(u_plot), t,
×
559
                                  x_face, y_face, face_data, variable_name)
560
end
561

562
"""
563
    PlotData1D(u, semi [or mesh, equations, solver, cache];
564
               solution_variables=nothing, nvisnodes=nothing,
565
               reinterpolate=Trixi.default_reinterpolate(solver),
566
               slice=:x, point=(0.0, 0.0, 0.0), curve=nothing)
567

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

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

580
`nvisnodes` specifies the number of visualization nodes to be used. If it is `nothing`,
581
twice the number of solution DG nodes are used for visualization, and if set to `0`,
582
exactly the number of nodes as in the DG elements are used. The solution is interpolated to these
583
nodes for plotting. If `reinterpolate` is `false`, the solution is not interpolated to the `visnodes`,
584
i.e., the solution is plotted at the solution nodes. In this case `nvisnodes` is ignored. By default,
585
`reinterpolate` is set to `false` for `FDSBP` approximations and to `true` otherwise.
586

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

593
Another way to visualize 2D/3D data is by creating a plot along a given curve.
594
This is done with the keyword argument `curve`. It can be set to a list of 2D/3D points
595
which define the curve. When using `curve` any other input from `slice` or `point` will be ignored.
596
"""
597
function PlotData1D(u_ode, semi; kwargs...)
×
598
    return PlotData1D(wrap_array_native(u_ode, semi),
×
599
                      mesh_equations_solver_cache(semi)...;
600
                      kwargs...)
601
end
602

603
function PlotData1D(func::Function, semi; kwargs...)
×
604
    return PlotData1D(func,
×
605
                      mesh_equations_solver_cache(semi)...;
606
                      kwargs...)
607
end
608

609
function PlotData1D(u, mesh::TreeMesh, equations, solver, cache;
×
610
                    solution_variables = nothing, nvisnodes = nothing,
611
                    reinterpolate = default_reinterpolate(solver),
612
                    slice = :x, point = (0.0, 0.0, 0.0), curve = nothing,
613
                    variable_names = nothing)
614
    solution_variables_ = digest_solution_variables(equations, solution_variables)
×
615
    variable_names_ = digest_variable_names(solution_variables_, equations,
×
616
                                            variable_names)
617

618
    original_nodes = cache.elements.node_coordinates
×
619
    unstructured_data = get_unstructured_data(u, solution_variables_, mesh, equations,
×
620
                                              solver, cache)
621

622
    orientation_x = 0 # Set 'orientation' to zero on default.
×
623

624
    if ndims(mesh) == 1
×
625
        x, data, mesh_vertices_x = get_data_1d(original_nodes, unstructured_data,
×
626
                                               nvisnodes, reinterpolate)
627
        orientation_x = 1
×
628

629
        # Special care is required for first-order FV approximations since the nodes are the
630
        # cell centers and do not contain the boundaries
631
        n_nodes = size(unstructured_data, 1)
×
632
        if n_nodes == 1
×
633
            n_visnodes = length(x) รท nelements(solver, cache)
×
634
            if n_visnodes != 2
×
635
                throw(ArgumentError("This number of visualization nodes is currently not supported for finite volume approximations."))
×
636
            end
637
            left_boundary = mesh.tree.center_level_0[1] - mesh.tree.length_level_0 / 2
×
638
            dx_2 = zero(left_boundary)
×
639
            for i in 1:div(length(x), 2)
×
640
                # Adjust plot nodes so that they are at the boundaries of each element
641
                dx_2 = x[2 * i - 1] - left_boundary
×
642
                x[2 * i - 1] -= dx_2
×
643
                x[2 * i] += dx_2
×
644
                left_boundary = left_boundary + 2 * dx_2
×
645

646
                # Adjust mesh plot nodes
647
                mesh_vertices_x[i] -= dx_2
×
648
            end
×
649
            mesh_vertices_x[end] += dx_2
×
650
        end
651
    elseif ndims(mesh) == 2
×
652
        if curve !== nothing
×
653
            x, data, mesh_vertices_x = unstructured_2d_to_1d_curve(original_nodes,
×
654
                                                                   unstructured_data,
655
                                                                   nvisnodes, curve,
656
                                                                   mesh, solver, cache)
657
        else
658
            x, data, mesh_vertices_x = unstructured_2d_to_1d(original_nodes,
×
659
                                                             unstructured_data,
660
                                                             nvisnodes, reinterpolate,
661
                                                             slice, point)
662
        end
663
    else # ndims(mesh) == 3
664
        if curve !== nothing
×
665
            x, data, mesh_vertices_x = unstructured_3d_to_1d_curve(original_nodes,
×
666
                                                                   unstructured_data,
667
                                                                   nvisnodes, curve,
668
                                                                   mesh, solver, cache)
669
        else
670
            x, data, mesh_vertices_x = unstructured_3d_to_1d(original_nodes,
×
671
                                                             unstructured_data,
672
                                                             nvisnodes, reinterpolate,
673
                                                             slice, point)
674
        end
675
    end
676

677
    return PlotData1D(x, data, variable_names_, mesh_vertices_x,
×
678
                      orientation_x)
679
end
680

681
# unwrap u if it is VectorOfArray
682
PlotData1D(u::VectorOfArray, mesh, equations, dg::DGMulti{1}, cache; kwargs...) = PlotData1D(parent(u),
×
683
                                                                                             mesh,
684
                                                                                             equations,
685
                                                                                             dg,
686
                                                                                             cache;
687
                                                                                             kwargs...)
688
PlotData2D(u::VectorOfArray, mesh, equations, dg::DGMulti{2}, cache; kwargs...) = PlotData2D(parent(u),
×
689
                                                                                             mesh,
690
                                                                                             equations,
691
                                                                                             dg,
692
                                                                                             cache;
693
                                                                                             kwargs...)
694

695
function PlotData1D(u, mesh, equations, solver, cache;
×
696
                    solution_variables = nothing, nvisnodes = nothing,
697
                    reinterpolate = default_reinterpolate(solver),
698
                    slice = :x, point = (0.0, 0.0, 0.0), curve = nothing,
699
                    variable_names = nothing)
700
    solution_variables_ = digest_solution_variables(equations, solution_variables)
×
701
    variable_names_ = digest_variable_names(solution_variables_, equations,
×
702
                                            variable_names)
703

704
    original_nodes = cache.elements.node_coordinates
×
705

706
    orientation_x = 0 # Set 'orientation' to zero on default.
×
707

708
    if ndims(mesh) == 1
×
709
        unstructured_data = get_unstructured_data(u, solution_variables_,
×
710
                                                  mesh, equations, solver, cache)
711
        x, data, mesh_vertices_x = get_data_1d(original_nodes, unstructured_data,
×
712
                                               nvisnodes, reinterpolate)
713
        orientation_x = 1
×
714
    elseif ndims(mesh) == 2
×
715
        x, data, mesh_vertices_x = unstructured_2d_to_1d_curve(u, mesh, equations,
×
716
                                                               solver, cache,
717
                                                               curve, slice,
718
                                                               point, nvisnodes,
719
                                                               solution_variables_)
720
    else # ndims(mesh) == 3
721
        # Extract the information required to create a PlotData1D object.
722
        # If no curve is defined, create an axis curve.
723
        if curve === nothing
×
724
            curve = axis_curve(view(original_nodes, 1, :, :, :, :),
×
725
                               view(original_nodes, 2, :, :, :, :),
726
                               view(original_nodes, 3, :, :, :, :),
727
                               slice, point, nvisnodes)
728
        end
729

730
        # We need to loop through all the points and check in which element they are
731
        # located. A general implementation working for all mesh types has to perform
732
        # a naive loop through all nodes. However, the P4estMesh and the T8codeMesh
733
        # can make use of the efficient search functionality of p4est/t8code
734
        # to speed up the process. Thus, we pass the mesh, too.
735
        x, data, mesh_vertices_x = unstructured_3d_to_1d_curve(u, mesh, equations,
×
736
                                                               solver, cache,
737
                                                               curve,
738
                                                               solution_variables_)
739
    end
740

741
    return PlotData1D(x, data, variable_names_, mesh_vertices_x,
×
742
                      orientation_x)
743
end
744

745
function PlotData1D(func::Function, mesh, equations, dg::DGMulti{1}, cache;
×
746
                    solution_variables = nothing, variable_names = nothing)
747
    x = mesh.md.x
×
748
    u = func.(x, equations)
×
749

750
    return PlotData1D(u, mesh, equations, dg, cache;
×
751
                      solution_variables, variable_names)
752
end
753

754
# Specializes the `PlotData1D` constructor for one-dimensional `DGMulti` solvers.
755
function PlotData1D(u, mesh, equations, dg::DGMulti{1}, cache;
×
756
                    solution_variables = nothing, variable_names = nothing)
757
    solution_variables_ = digest_solution_variables(equations, solution_variables)
×
758
    variable_names_ = digest_variable_names(solution_variables_, equations,
×
759
                                            variable_names)
760

761
    orientation_x = 0 # Set 'orientation' to zero on default.
×
762

763
    if u isa StructArray
×
764
        # Convert conserved variables to the given `solution_variables` and set up
765
        # plotting coordinates
766
        # This uses a "structure of arrays"
767
        data = map(x -> vcat(dg.basis.Vp * x, fill(NaN, 1, size(u, 2))),
×
768
                   StructArrays.components(solution_variables_.(u, equations)))
769
        x = vcat(dg.basis.Vp * mesh.md.x, fill(NaN, 1, size(u, 2)))
×
770

771
        # Here, we ensure that `DGMulti` visualization uses the same data layout and format
772
        # as `TreeMesh`. This enables us to reuse existing plot recipes. In particular,
773
        # `hcat(data...)` creates a matrix of size `num_plotting_points` by `nvariables(equations)`,
774
        # with data on different elements separated by `NaNs`.
775
        x_plot = vec(x)
×
776
        data_plot = hcat(vec.(data)...)
×
777
    else
778
        # Convert conserved variables to the given `solution_variables` and set up
779
        # plotting coordinates
780
        # This uses an "array of structures"
781
        data_tmp = dg.basis.Vp * solution_variables_.(u, equations)
×
782
        data = vcat(data_tmp, fill(NaN * zero(eltype(data_tmp)), 1, size(u, 2)))
×
783
        x = vcat(dg.basis.Vp * mesh.md.x, fill(NaN, 1, size(u, 2)))
×
784

785
        # Same as above - we create `data_plot` as array of size `num_plotting_points`
786
        # by "number of plotting variables".
787
        x_plot = vec(x)
×
788
        data_ = reinterpret(reshape, eltype(eltype(data)), vec(data))
×
789
        # If there is only one solution variable, we need to add a singleton dimension
790
        if ndims(data_) == 1
×
791
            data_plot = reshape(data_, :, 1)
×
792
        else
793
            data_plot = permutedims(data_, (2, 1))
×
794
        end
795
    end
796

797
    return PlotData1D(x_plot, data_plot, variable_names_, mesh.md.VX, orientation_x)
×
798
end
799

800
"""
801
    PlotData1D(sol; kwargs...)
802

803
Create a `PlotData1D` object from a solution object created by either `OrdinaryDiffEq.solve!`
804
(which returns a `SciMLBase.ODESolution`) or Trixi.jl's own `solve!` (which returns a
805
`TimeIntegratorSolution`).
806
"""
807
function PlotData1D(sol::TrixiODESolution; kwargs...)
×
808
    return PlotData1D(sol.u[end], sol.prob.p; kwargs...)
×
809
end
810

811
function PlotData1D(time_series_callback::TimeSeriesCallback, point_id::Integer)
×
812
    @unpack time, variable_names, point_data = time_series_callback
×
813

814
    n_solution_variables = length(variable_names)
×
815
    data = Matrix{Float64}(undef, length(time), n_solution_variables)
×
816
    reshaped = reshape(point_data[point_id], n_solution_variables, length(time))
×
817
    for v in 1:n_solution_variables
×
818
        @views data[:, v] = reshaped[v, :]
×
819
    end
×
820

821
    mesh_vertices_x = Vector{Float64}(undef, 0)
×
822

823
    return PlotData1D(time, data, SVector(variable_names), mesh_vertices_x, 0)
×
824
end
825

826
function PlotData1D(cb::DiscreteCallback{<:Any, <:TimeSeriesCallback},
×
827
                    point_id::Integer)
828
    return PlotData1D(cb.affect!, point_id)
×
829
end
830
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