Path: blob/main/src/callbacks_step/time_series.jl
5586 views
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).1# Since these FMAs can increase the performance of many numerical algorithms,2# we need to opt-in explicitly.3# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.4@muladd begin5#! format: noindent67"""8TimeSeriesCallback(semi, point_coordinates;9interval=1, solution_variables=cons2cons,10output_directory="out", filename="time_series.h5",11RealT=real(solver), uEltype=eltype(cache.elements))1213Create a callback that records point-wise data at points given in `point_coordinates` every14`interval` time steps. The point coordinates are to be specified either as a vector of coordinate15tuples or as a two-dimensional array where the first dimension is the point number and the second16dimension is the coordinate dimension. By default, the conservative variables are recorded, but this17can be controlled by passing a different conversion function to `solution_variables`.1819After the last time step, the results are stored in an HDF5 file `filename` in directory20`output_directory`.2122The real data type `RealT` and data type for solution variables `uEltype` default to the respective23types used in the solver and the cache.2425Currently this callback is only implemented for [`TreeMesh`](@ref) and [`UnstructuredMesh2D`](@ref).26"""27mutable struct TimeSeriesCallback{RealT <: Real, uEltype <: Real, SolutionVariables,28VariableNames, Cache}29const interval::Int30const solution_variables::SolutionVariables31const variable_names::VariableNames32const output_directory::String33const filename::String34const point_coordinates::Array{RealT, 2}35# Point data is stored as a vector of vectors of the solution data type:36# * the "outer" `Vector` contains one vector for each point at which a time_series is recorded37# * the "inner" `Vector` contains the actual time series for a single point,38# with each record adding "n_vars" entries39# The reason for using this data structure is that the length of the inner vectors needs to be40# increased for each record, which can only be realized in Julia using ordinary `Vector`s.41point_data::Vector{Vector{uEltype}}42time::Vector{RealT}43step::Vector{Int}44const time_series_cache::Cache45end4647function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:TimeSeriesCallback})48@nospecialize cb # reduce precompilation time4950time_series_callback = cb.affect!51@unpack interval, solution_variables, output_directory, filename = time_series_callback52print(io, "TimeSeriesCallback(",53"interval=", interval, ", ",54"solution_variables=", interval, ", ",55"output_directory=", "\"output_directory\"", ", ",56"filename=", "\"filename\"",57")")58return nothing59end6061function Base.show(io::IO, ::MIME"text/plain",62cb::DiscreteCallback{<:Any, <:TimeSeriesCallback})63@nospecialize cb # reduce precompilation time6465if get(io, :compact, false)66show(io, cb)67else68time_series_callback = cb.affect!6970setup = [71"#points" => size(time_series_callback.point_coordinates, 2),72"interval" => time_series_callback.interval,73"solution_variables" => time_series_callback.solution_variables,74"output_directory" => time_series_callback.output_directory,75"filename" => time_series_callback.filename76]77summary_box(io, "TimeSeriesCallback", setup)78end79end8081# Main constructor82function TimeSeriesCallback(mesh, equations, solver, cache, point_coordinates;83interval::Integer = 1,84solution_variables = cons2cons,85output_directory = "out",86filename = "time_series.h5",87RealT = real(solver),88uEltype = eltype(cache.elements))89# check arguments90if !(interval isa Integer && interval >= 0)91throw(ArgumentError("`interval` must be a non-negative integer (provided `interval = $interval`)"))92end9394if ndims(point_coordinates) != 2 || size(point_coordinates, 2) != ndims(mesh)95throw(ArgumentError("`point_coordinates` must be a matrix of size n_points × ndims"))96end9798# create the output folder if it does not exist already99if mpi_isroot() && !isdir(output_directory)100mkpath(output_directory)101end102103# Transpose point_coordinates to our usual format [ndims, n_points]104# Note: They are accepted in a different format to allow direct input from `readdlm`105point_coordinates_ = permutedims(point_coordinates)106107# Invoke callback every `interval` time steps or after final step (for storing the data on disk)108if interval > 0109# With error-based step size control, some steps can be rejected. Thus,110# `integrator.iter >= integrator.stats.naccept`111# (total #steps) (#accepted steps)112# We need to check the number of accepted steps since callbacks are not113# activated after a rejected step.114condition = (u, t, integrator) -> ((integrator.stats.naccept % interval == 0 &&115!(integrator.stats.naccept == 0 &&116integrator.iter > 0)) ||117isfinished(integrator))118else # disable the callback for interval == 0119condition = (u, t, integrator) -> false120end121122# Create data structures that are to be filled by the callback123variable_names = varnames(solution_variables, equations)124n_points = size(point_coordinates_, 2)125point_data = Vector{uEltype}[Vector{uEltype}() for _ in 1:n_points]126time = Vector{RealT}()127step = Vector{Int}()128time_series_cache = create_cache_time_series(point_coordinates_, mesh, solver,129cache)130131time_series_callback = TimeSeriesCallback(interval,132solution_variables,133variable_names,134output_directory,135filename,136point_coordinates_,137point_data,138time,139step,140time_series_cache)141142return DiscreteCallback(condition, time_series_callback,143save_positions = (false, false))144end145146# Convenience constructor that unpacks the semidiscretization into mesh, equations, solver, cache147function TimeSeriesCallback(semi, point_coordinates; kwargs...)148mesh, equations, solver, cache = mesh_equations_solver_cache(semi)149150return TimeSeriesCallback(mesh, equations, solver, cache, point_coordinates;151kwargs...)152end153154# Convenience constructor that converts a vector of points into a Trixi.jl-style coordinate array155function TimeSeriesCallback(mesh, equations, solver, cache,156point_coordinates::AbstractVector;157kwargs...)158# Coordinates are usually stored in [ndims, n_points], but here as [n_points, ndims]159n_points = length(point_coordinates)160point_coordinates_ = Matrix{eltype(eltype(point_coordinates))}(undef, n_points,161ndims(mesh))162163for p in 1:n_points164for d in 1:ndims(mesh)165point_coordinates_[p, d] = point_coordinates[p][d]166end167end168169return TimeSeriesCallback(mesh, equations, solver, cache, point_coordinates_;170kwargs...)171end172173# This method is called as callback during the time integration.174function (time_series_callback::TimeSeriesCallback)(integrator)175# Ensure this is not accidentally used with AMR enabled176if uses_amr(integrator.opts.callback)177error("the TimeSeriesCallback does not work with AMR enabled")178end179180@unpack interval = time_series_callback181182# Create record if in correct interval (needs to be checked since the callback is also called183# after the final step for storing the data on disk, independent of the current interval)184if integrator.stats.naccept % interval == 0185@trixi_timeit timer() "time series" begin186# Store time and step187push!(time_series_callback.time, integrator.t)188push!(time_series_callback.step, integrator.stats.naccept)189190# Unpack data191u_ode = integrator.u192semi = integrator.p193mesh, equations, solver, cache = mesh_equations_solver_cache(semi)194u = wrap_array(u_ode, mesh, equations, solver, cache)195196@unpack (point_data, solution_variables,197variable_names, time_series_cache) = time_series_callback198199# Record state at points (solver/mesh-dependent implementation)200record_state_at_points!(point_data, u, solution_variables,201length(variable_names), mesh,202equations, solver, time_series_cache)203end204end205206# Store time_series if this is the last time step207if isfinished(integrator)208semi = integrator.p209mesh, equations, solver, _ = mesh_equations_solver_cache(semi)210save_time_series_file(time_series_callback, mesh, equations, solver)211end212213# avoid re-evaluating possible FSAL stages214u_modified!(integrator, false)215216return nothing217end218219include("time_series_dg.jl")220include("time_series_dg_tree.jl")221include("time_series_dg_unstructured.jl")222end # @muladd223224225