Path: blob/main/src/callbacks_step/visualization.jl
2055 views
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).1# Since these FMAs can increase the performance of many numerical algorithms,2# we need to opt-in explicitly.3# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.4@muladd begin5#! format: noindent67mutable struct VisualizationCallback{PlotDataCreator, SolutionVariables, VariableNames,8PlotCreator}9plot_data_creator::PlotDataCreator10interval::Int11solution_variables::SolutionVariables12variable_names::VariableNames13show_mesh::Bool14plot_creator::PlotCreator15plot_arguments::Dict{Symbol, Any}16end1718function Base.show(io::IO,19cb::DiscreteCallback{Condition, Affect!}) where {Condition,20Affect! <:21VisualizationCallback22}23visualization_callback = cb.affect!24@unpack plot_data_creator, interval, plot_arguments, solution_variables, variable_names, show_mesh, plot_creator = visualization_callback25print(io, "VisualizationCallback(",26"plot_data_creator=", plot_data_creator, ", ",27"interval=", interval, ", ",28"solution_variables=", solution_variables, ", ",29"variable_names=", variable_names, ", ",30"show_mesh=", show_mesh, ", ",31"plot_creator=", plot_creator, ", ",32"plot_arguments=", plot_arguments, ")")33end3435function Base.show(io::IO, ::MIME"text/plain",36cb::DiscreteCallback{Condition, Affect!}) where {Condition,37Affect! <:38VisualizationCallback39}40if get(io, :compact, false)41show(io, cb)42else43visualization_callback = cb.affect!4445setup = [46"plot data creator" => visualization_callback.plot_data_creator,47"interval" => visualization_callback.interval,48"plot arguments" => visualization_callback.plot_arguments,49"solution variables" => visualization_callback.solution_variables,50"variable names" => visualization_callback.variable_names,51"show mesh" => visualization_callback.show_mesh,52"plot creator" => visualization_callback.plot_creator53]54summary_box(io, "VisualizationCallback", setup)55end56end5758"""59VisualizationCallback(semi, plot_data_creator = nothing;60interval=0,61solution_variables=cons2prim,62variable_names=[],63show_mesh=false,64plot_creator=show_plot,65plot_arguments...)6667Create a callback that visualizes results during a simulation, also known as *in-situ68visualization*.6970To customize the generated figure, `plot_data_creator` allows to use different plot data types.71Currently provided are [`PlotData1D`](@ref) and [`PlotData2D`](@ref), while the latter is used for both 2D and 3D.7273The `interval` specifies the number of time step iterations after which a new plot is generated. The74available variables to plot are configured with the `solution_variables` parameter, which acts the75same way as for the [`SaveSolutionCallback`](@ref). The variables to be actually plotted can be76selected by providing a single string or a list of strings to `variable_names`, and if `show_mesh`77is `true`, an additional plot with the mesh will be generated.7879With `plot_creator` you can further specify an own function to visualize results, which must support the80same interface as the default implementation [`show_plot`](@ref). All remaining81keyword arguments are collected and passed as additional arguments to the plotting command.82"""83function VisualizationCallback(semi, plot_data_creator = nothing;84interval = 0,85solution_variables = cons2prim,86variable_names = [],87show_mesh = false,88plot_creator = show_plot,89plot_arguments...)90mpi_isparallel() && error("this callback does not work in parallel yet")9192if variable_names isa String93variable_names = String[variable_names]94end9596if plot_data_creator === nothing # No custom plot data type provided97if ndims(semi) == 198plot_data_creator = PlotData1D99else # 2D or 3D100plot_data_creator = PlotData2D101end102end103104visualization_callback = VisualizationCallback(plot_data_creator,105interval,106solution_variables, variable_names,107show_mesh,108plot_creator,109Dict{Symbol, Any}(plot_arguments))110111# Warn users if they create a visualization callback without having loaded the Plots package112#113# Note: This warning is added for convenience, as Plots is the only "officially" supported114# visualization package right now. However, in general nothing prevents anyone from using115# other packages such as Makie, Gadfly etc., given that appropriate `plot_creator`s are116# passed. This is also the reason why the visualization callback is not included via117# Requires.jl only when Plots is present.118# In the future, we should update/remove this warning if other plotting packages are119# starting to be used.120if !(:Plots in names(@__MODULE__, all = true))121@warn "Package `Plots` not loaded but required by `VisualizationCallback` to visualize results"122end123124DiscreteCallback(visualization_callback, visualization_callback, # the first one is the condition, the second the affect!125save_positions = (false, false),126initialize = initialize!)127end128129function initialize!(cb::DiscreteCallback{Condition, Affect!}, u, t,130integrator) where {Condition, Affect! <: VisualizationCallback}131visualization_callback = cb.affect!132133visualization_callback(integrator)134135return nothing136end137138# this method is called to determine whether the callback should be activated139function (visualization_callback::VisualizationCallback)(u, t, integrator)140@unpack interval = visualization_callback141142# With error-based step size control, some steps can be rejected. Thus,143# `integrator.iter >= integrator.stats.naccept`144# (total #steps) (#accepted steps)145# We need to check the number of accepted steps since callbacks are not146# activated after a rejected step.147return interval > 0 && (integrator.stats.naccept % interval == 0 ||148isfinished(integrator))149end150151# this method is called when the callback is activated152function (visualization_callback::VisualizationCallback)(integrator)153u_ode = integrator.u154semi = integrator.p155@unpack plot_data_creator, plot_arguments, solution_variables, variable_names, show_mesh, plot_creator = visualization_callback156157# Extract plot data158plot_data = plot_data_creator(u_ode, semi, solution_variables = solution_variables)159160# If variable names were not specified, plot everything161if isempty(variable_names)162variable_names = String[keys(plot_data)...]163end164165# Create plot166plot_creator(plot_data, variable_names;167show_mesh = show_mesh, plot_arguments = plot_arguments,168time = integrator.t, timestep = integrator.stats.naccept)169170# avoid re-evaluating possible FSAL stages171u_modified!(integrator, false)172return nothing173end174175"""176show_plot(plot_data, variable_names;177show_mesh=true, plot_arguments=Dict{Symbol,Any}(),178time=nothing, timestep=nothing)179180Visualize the plot data object provided in `plot_data` and display result, plotting only the181variables in `variable_names` and, optionally, the mesh (if `show_mesh` is `true`). Additionally,182`plot_arguments` will be unpacked and passed as keyword arguments to the `Plots.plot` command.183184This function is the default `plot_creator` argument for the [`VisualizationCallback`](@ref).185`time` and `timestep` are currently unused by this function.186187See also: [`VisualizationCallback`](@ref), [`save_plot`](@ref)188"""189function show_plot(plot_data, variable_names;190show_mesh = true, plot_arguments = Dict{Symbol, Any}(),191time = nothing, timestep = nothing)192# Gather subplots193plots = []194for v in variable_names195push!(plots, Plots.plot(plot_data[v]; plot_arguments...))196end197if show_mesh198push!(plots, Plots.plot(getmesh(plot_data); plot_arguments...))199end200201# Note, for the visualization callback to work for general equation systems202# this layout construction would need to use the if-logic below.203# Currently, there is no use case for this so it is left here as a note.204#205# Determine layout206# if length(plots) <= 3207# cols = length(plots)208# rows = 1209# else210# cols = ceil(Int, sqrt(length(plots)))211# rows = div(length(plots), cols, RoundUp)212# end213# layout = (rows, cols)214215# Determine layout216cols = ceil(Int, sqrt(length(plots)))217rows = div(length(plots), cols, RoundUp)218layout = (rows, cols)219220# Show plot221display(Plots.plot(plots..., layout = layout))222end223224"""225save_plot(plot_data, variable_names;226show_mesh=true, plot_arguments=Dict{Symbol,Any}(),227time=nothing, timestep=nothing)228229Visualize the plot data object provided in `plot_data` and save result as a PNG file in the `out`230directory, plotting only the variables in `variable_names` and, optionally, the mesh (if `show_mesh`231is `true`). Additionally, `plot_arguments` will be unpacked and passed as keyword arguments to the232`Plots.plot` command.233234The `timestep` is used in the filename. `time` is currently unused by this function.235236See also: [`VisualizationCallback`](@ref), [`show_plot`](@ref)237"""238function save_plot(plot_data, variable_names;239show_mesh = true, plot_arguments = Dict{Symbol, Any}(),240time = nothing, timestep = nothing)241# Gather subplots242plots = []243for v in variable_names244push!(plots, Plots.plot(plot_data[v]; plot_arguments...))245end246if show_mesh247push!(plots, Plots.plot(getmesh(plot_data); plot_arguments...))248end249250# Determine layout251cols = ceil(Int, sqrt(length(plots)))252rows = div(length(plots), cols, RoundUp)253layout = (rows, cols)254255# Create plot256Plots.plot(plots..., layout = layout)257258# Determine filename and save plot259filename = joinpath("out", @sprintf("solution_%09d.png", timestep))260Plots.savefig(filename)261end262end # @muladd263264265