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

trixi-framework / Trixi.jl / 20679838146

03 Jan 2026 04:24PM UTC coverage: 96.993%. Remained the same
20679838146

push

github

ranocha
set development version to v0.13.22-DEV

42292 of 43603 relevant lines covered (96.99%)

104628030.91 hits per line

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

91.16
/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)
6✔
27
Base.lastindex(pd::AbstractPlotData) = last(pd.variable_names)
6✔
28
Base.length(pd::AbstractPlotData) = length(pd.variable_names)
412✔
29
Base.size(pd::AbstractPlotData) = (length(pd),)
6✔
30
Base.keys(pd::AbstractPlotData) = tuple(pd.variable_names...)
36✔
31

32
function Base.iterate(pd::AbstractPlotData, state = 1)
281✔
33
    if state > length(pd)
290✔
34
        return nothing
55✔
35
    else
36
        return (pd.variable_names[state] => pd[pd.variable_names[state]], state + 1)
180✔
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)
259✔
46
    variable_id = findfirst(isequal(variable_name), pd.variable_names)
611✔
47

48
    if isnothing(variable_id)
512✔
49
        throw(KeyError(variable_name))
6✔
50
    end
51

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

55
Base.eltype(pd::AbstractPlotData) = Pair{String, PlotDataSeries{typeof(pd)}}
6✔
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
46✔
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)
1✔
77
    @nospecialize pd # reduce precompilation time
1✔
78

79
    print(io, "PlotData2DCartesian{",
1✔
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
1✔
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)
63✔
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)
4✔
104
    @nospecialize pd # reduce precompilation time
4✔
105

106
    print(io, "PlotData2DTriangulated{",
4✔
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
4✔
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
67✔
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)
1✔
132
    print(io, "PlotData1D{",
1✔
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
1✔
139
end
140

141
# Auxiliary data structure for visualizing a single variable
142
struct PlotDataSeries{PD <: AbstractPlotData}
143
    plot_data::PD
311✔
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)
6✔
149
    @nospecialize pds # reduce precompilation time
6✔
150

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

156
# Generic PlotMesh wrapper type.
157
struct PlotMesh{PD <: AbstractPlotData}
158
    plot_data::PD
30✔
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)
6✔
163
    @nospecialize pm # reduce precompilation time
6✔
164

165
    print(io, "PlotMesh{", typeof(pm.plot_data), "}(<plot_data>)")
6✔
166
    return nothing
6✔
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)
24✔
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...)
134✔
222
    return PlotData2D(wrap_array_native(u_ode, semi),
67✔
223
                      mesh_equations_solver_cache(semi)...;
224
                      kwargs...)
225
end
226

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

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

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

241
    return plot_data_array
3✔
242
end
243

244
# Redirect `PlotDataTriangulated2D` constructor.
245
function PlotData2DTriangulated(u_ode, semi; kwargs...)
6✔
246
    return PlotData2DTriangulated(wrap_array_native(u_ode, semi),
3✔
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...)
90✔
253
    return PlotData2DCartesian(u, mesh::TreeMesh, equations, solver, cache; kwargs...)
45✔
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...)
58✔
258
    return PlotData2DTriangulated(u, mesh, equations, solver, cache; kwargs...)
29✔
259
end
260

261
# Create a PlotData2DCartesian for a TreeMesh.
262
function PlotData2DCartesian(u, mesh::TreeMesh, equations, solver, cache;
90✔
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)"
45✔
268
    solution_variables_ = digest_solution_variables(equations, solution_variables)
45✔
269

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

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

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

294
    return PlotData2DCartesian(x, y, data, variable_names, mesh_vertices_x,
45✔
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...)
56✔
307
    return PlotData2D(sol.u[end], sol.prob.p; kwargs...)
28✔
308
end
309

310
# Also redirect when using PlotData2DTriangulate.
311
function PlotData2DTriangulated(sol::TrixiODESolution; kwargs...)
6✔
312
    return PlotData2DTriangulated(sol.u[end], sol.prob.p; kwargs...)
3✔
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.
331
function PlotData2D(u::StructArray, mesh, equations, dg::DGMulti, cache;
10✔
332
                    solution_variables = nothing, nvisnodes = 2 * nnodes(dg))
333
    rd = dg.basis
5✔
334
    md = mesh.md
5✔
335

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

340
    solution_variables_ = digest_solution_variables(equations, solution_variables)
5✔
341
    variable_names = SVector(varnames(solution_variables_, equations))
6✔
342

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

355
    for e in eachelement(mesh, dg, cache)
5✔
356
        # interpolate solution to plotting nodes element-by-element
357
        StructArrays.foreachfield(interpolate_to_plotting_points!, view(u_plot, :, e),
640✔
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_,
640✔
362
                                         equations)
363
    end
1,275✔
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
15✔
367

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

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

374
    return PlotData2DTriangulated(x_plot, y_plot, u_plot, t, x_face, y_face, face_data,
5✔
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;
104✔
380
                                solution_variables = nothing,
381
                                nvisnodes = 2 * polydeg(dg))
382
    @assert ndims(mesh)==2 "Input must be two-dimensional."
52✔
383

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

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

390
    # reference plotting nodes
391
    if nvisnodes == 0 || nvisnodes === nothing
83✔
392
        nvisnodes = polydeg(dg) + 1
21✔
393
    end
394
    plotting_interp_matrix = plotting_interpolation_matrix(dg; nvisnodes = nvisnodes)
52✔
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
312✔
398

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

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

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

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

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

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

437
    return PlotData2DTriangulated(xplot, yplot, uplot, t, xfp, yfp, ufp, variable_names)
52✔
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
12✔
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
"""
451
function ScalarPlotData2D(u, semi::AbstractSemidiscretization; kwargs...)
12✔
452
    return ScalarPlotData2D(u, mesh_equations_solver_cache(semi)...; kwargs...)
6✔
453
end
454

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

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

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

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

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

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

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

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

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

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

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

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

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

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

517
    # wrap solution in ScalarData struct for recipe dispatch
518
    return PlotData2DTriangulated(x_plot, y_plot, ScalarData(u_plot), t,
5✔
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
"""
557
function PlotData1D(u_ode, semi; kwargs...)
112✔
558
    return PlotData1D(wrap_array_native(u_ode, semi),
56✔
559
                      mesh_equations_solver_cache(semi)...;
560
                      kwargs...)
561
end
562

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

569
function PlotData1D(u, mesh::TreeMesh, equations, solver, cache;
44✔
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)
574
    solution_variables_ = digest_solution_variables(equations, solution_variables)
22✔
575
    variable_names_ = digest_variable_names(solution_variables_, equations,
22✔
576
                                            variable_names)
577

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

582
    orientation_x = 0 # Set 'orientation' to zero on default.
22✔
583

584
    if ndims(mesh) == 1
22✔
585
        x, data, mesh_vertices_x = get_data_1d(original_nodes, unstructured_data,
9✔
586
                                               nvisnodes, reinterpolate)
587
        orientation_x = 1
9✔
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
591
        n_nodes = size(unstructured_data, 1)
9✔
592
        if n_nodes == 1
9✔
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
611
    elseif ndims(mesh) == 2
13✔
612
        if curve !== nothing
6✔
613
            x, data, mesh_vertices_x = unstructured_2d_to_1d_curve(original_nodes,
3✔
614
                                                                   unstructured_data,
615
                                                                   nvisnodes, curve,
616
                                                                   mesh, solver, cache)
617
        else
618
            x, data, mesh_vertices_x = unstructured_2d_to_1d(original_nodes,
3✔
619
                                                             unstructured_data,
620
                                                             nvisnodes, reinterpolate,
621
                                                             slice, point)
622
        end
623
    else # ndims(mesh) == 3
624
        if curve !== nothing
7✔
625
            x, data, mesh_vertices_x = unstructured_3d_to_1d_curve(original_nodes,
3✔
626
                                                                   unstructured_data,
627
                                                                   nvisnodes, curve,
628
                                                                   mesh, solver, cache)
629
        else
630
            x, data, mesh_vertices_x = unstructured_3d_to_1d(original_nodes,
4✔
631
                                                             unstructured_data,
632
                                                             nvisnodes, reinterpolate,
633
                                                             slice, point)
634
        end
635
    end
636

637
    return PlotData1D(x, data, variable_names_, mesh_vertices_x,
22✔
638
                      orientation_x)
639
end
640

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

655
function PlotData1D(u, mesh, equations, solver, cache;
72✔
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)
660
    solution_variables_ = digest_solution_variables(equations, solution_variables)
36✔
661
    variable_names_ = digest_variable_names(solution_variables_, equations,
36✔
662
                                            variable_names)
663

664
    original_nodes = cache.elements.node_coordinates
36✔
665

666
    orientation_x = 0 # Set 'orientation' to zero on default.
36✔
667

668
    if ndims(mesh) == 1
36✔
669
        unstructured_data = get_unstructured_data(u, solution_variables_,
5✔
670
                                                  mesh, equations, solver, cache)
671
        x, data, mesh_vertices_x = get_data_1d(original_nodes, unstructured_data,
5✔
672
                                               nvisnodes, reinterpolate)
673
        orientation_x = 1
5✔
674
    elseif ndims(mesh) == 2
31✔
675
        x, data, mesh_vertices_x = unstructured_2d_to_1d_curve(u, mesh, equations,
20✔
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.
683
        if curve === nothing
11✔
684
            curve = axis_curve(view(original_nodes, 1, :, :, :, :),
4✔
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.
695
        x, data, mesh_vertices_x = unstructured_3d_to_1d_curve(u, mesh, equations,
11✔
696
                                                               solver, cache,
697
                                                               curve,
698
                                                               solution_variables_)
699
    end
700

701
    return PlotData1D(x, data, variable_names_, mesh_vertices_x,
36✔
702
                      orientation_x)
703
end
704

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

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

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

721
    orientation_x = 0 # Set 'orientation' to zero on default.
6✔
722

723
    if u isa StructArray
6✔
724
        # Convert conserved variables to the given `solution_variables` and set up
725
        # plotting coordinates
726
        # This uses a "structure of arrays"
727
        data = map(x -> vcat(dg.basis.Vp * x, fill(NaN, 1, size(u, 2))),
5✔
728
                   StructArrays.components(solution_variables_.(u, equations)))
729
        x = vcat(dg.basis.Vp * mesh.md.x, fill(NaN, 1, size(u, 2)))
8✔
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`.
735
        x_plot = vec(x)
1✔
736
        data_plot = hcat(vec.(data)...)
2✔
737
    else
738
        # Convert conserved variables to the given `solution_variables` and set up
739
        # plotting coordinates
740
        # This uses an "array of structures"
741
        data_tmp = dg.basis.Vp * solution_variables_.(u, equations)
10✔
742
        data = vcat(data_tmp, fill(NaN * zero(eltype(data_tmp)), 1, size(u, 2)))
12✔
743
        x = vcat(dg.basis.Vp * mesh.md.x, fill(NaN, 1, size(u, 2)))
40✔
744

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

757
    return PlotData1D(x_plot, data_plot, variable_names_, mesh.md.VX, orientation_x)
6✔
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
"""
767
function PlotData1D(sol::TrixiODESolution; kwargs...)
88✔
768
    return PlotData1D(sol.u[end], sol.prob.p; kwargs...)
44✔
769
end
770

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

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

781
    mesh_vertices_x = Vector{Float64}(undef, 0)
2✔
782

783
    return PlotData1D(time, data, SVector(variable_names), mesh_vertices_x, 0)
2✔
784
end
785

786
function PlotData1D(cb::DiscreteCallback{<:Any, <:TimeSeriesCallback},
1✔
787
                    point_id::Integer)
788
    return PlotData1D(cb.affect!, point_id)
1✔
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