Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/callbacks_step/time_series.jl
2055 views
1
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).
2
# Since these FMAs can increase the performance of many numerical algorithms,
3
# we need to opt-in explicitly.
4
# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.
5
@muladd begin
6
#! format: noindent
7
8
"""
9
TimeSeriesCallback(semi, point_coordinates;
10
interval=1, solution_variables=cons2cons,
11
output_directory="out", filename="time_series.h5",
12
RealT=real(solver), uEltype=eltype(cache.elements))
13
14
Create a callback that records point-wise data at points given in `point_coordinates` every
15
`interval` time steps. The point coordinates are to be specified either as a vector of coordinate
16
tuples or as a two-dimensional array where the first dimension is the point number and the second
17
dimension is the coordinate dimension. By default, the conservative variables are recorded, but this
18
can be controlled by passing a different conversion function to `solution_variables`.
19
20
After the last time step, the results are stored in an HDF5 file `filename` in directory
21
`output_directory`.
22
23
The real data type `RealT` and data type for solution variables `uEltype` default to the respective
24
types used in the solver and the cache.
25
26
Currently this callback is only implemented for [`TreeMesh`](@ref) and [`UnstructuredMesh2D`](@ref).
27
"""
28
mutable struct TimeSeriesCallback{RealT <: Real, uEltype <: Real, SolutionVariables,
29
VariableNames, Cache}
30
interval::Int
31
solution_variables::SolutionVariables
32
variable_names::VariableNames
33
output_directory::String
34
filename::String
35
point_coordinates::Array{RealT, 2}
36
# Point data is stored as a vector of vectors of the solution data type:
37
# * the "outer" `Vector` contains one vector for each point at which a time_series is recorded
38
# * the "inner" `Vector` contains the actual time series for a single point,
39
# with each record adding "n_vars" entries
40
# The reason for using this data structure is that the length of the inner vectors needs to be
41
# increased for each record, which can only be realized in Julia using ordinary `Vector`s.
42
point_data::Vector{Vector{uEltype}}
43
time::Vector{RealT}
44
step::Vector{Int}
45
time_series_cache::Cache
46
end
47
48
function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:TimeSeriesCallback})
49
@nospecialize cb # reduce precompilation time
50
51
time_series_callback = cb.affect!
52
@unpack interval, solution_variables, output_directory, filename = time_series_callback
53
print(io, "TimeSeriesCallback(",
54
"interval=", interval, ", ",
55
"solution_variables=", interval, ", ",
56
"output_directory=", "\"output_directory\"", ", ",
57
"filename=", "\"filename\"",
58
")")
59
end
60
61
function Base.show(io::IO, ::MIME"text/plain",
62
cb::DiscreteCallback{<:Any, <:TimeSeriesCallback})
63
@nospecialize cb # reduce precompilation time
64
65
if get(io, :compact, false)
66
show(io, cb)
67
else
68
time_series_callback = cb.affect!
69
70
setup = [
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.filename
76
]
77
summary_box(io, "TimeSeriesCallback", setup)
78
end
79
end
80
81
# Main constructor
82
function TimeSeriesCallback(mesh, equations, solver, cache, point_coordinates;
83
interval::Integer = 1,
84
solution_variables = cons2cons,
85
output_directory = "out",
86
filename = "time_series.h5",
87
RealT = real(solver),
88
uEltype = eltype(cache.elements))
89
# check arguments
90
if !(interval isa Integer && interval >= 0)
91
throw(ArgumentError("`interval` must be a non-negative integer (provided `interval = $interval`)"))
92
end
93
94
if ndims(point_coordinates) != 2 || size(point_coordinates, 2) != ndims(mesh)
95
throw(ArgumentError("`point_coordinates` must be a matrix of size n_points × ndims"))
96
end
97
98
# create the output folder if it does not exist already
99
if mpi_isroot() && !isdir(output_directory)
100
mkpath(output_directory)
101
end
102
103
# 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`
105
point_coordinates_ = permutedims(point_coordinates)
106
107
# Invoke callback every `interval` time steps or after final step (for storing the data on disk)
108
if interval > 0
109
# 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 not
113
# activated after a rejected step.
114
condition = (u, t, integrator) -> ((integrator.stats.naccept % interval == 0 &&
115
!(integrator.stats.naccept == 0 &&
116
integrator.iter > 0)) ||
117
isfinished(integrator))
118
else # disable the callback for interval == 0
119
condition = (u, t, integrator) -> false
120
end
121
122
# Create data structures that are to be filled by the callback
123
variable_names = varnames(solution_variables, equations)
124
n_points = size(point_coordinates_, 2)
125
point_data = Vector{uEltype}[Vector{uEltype}() for _ in 1:n_points]
126
time = Vector{RealT}()
127
step = Vector{Int}()
128
time_series_cache = create_cache_time_series(point_coordinates_, mesh, solver,
129
cache)
130
131
time_series_callback = TimeSeriesCallback(interval,
132
solution_variables,
133
variable_names,
134
output_directory,
135
filename,
136
point_coordinates_,
137
point_data,
138
time,
139
step,
140
time_series_cache)
141
142
return DiscreteCallback(condition, time_series_callback,
143
save_positions = (false, false))
144
end
145
146
# Convenience constructor that unpacks the semidiscretization into mesh, equations, solver, cache
147
function TimeSeriesCallback(semi, point_coordinates; kwargs...)
148
mesh, equations, solver, cache = mesh_equations_solver_cache(semi)
149
150
return TimeSeriesCallback(mesh, equations, solver, cache, point_coordinates;
151
kwargs...)
152
end
153
154
# Convenience constructor that converts a vector of points into a Trixi.jl-style coordinate array
155
function TimeSeriesCallback(mesh, equations, solver, cache,
156
point_coordinates::AbstractVector;
157
kwargs...)
158
# Coordinates are usually stored in [ndims, n_points], but here as [n_points, ndims]
159
n_points = length(point_coordinates)
160
point_coordinates_ = Matrix{eltype(eltype(point_coordinates))}(undef, n_points,
161
ndims(mesh))
162
163
for p in 1:n_points
164
for d in 1:ndims(mesh)
165
point_coordinates_[p, d] = point_coordinates[p][d]
166
end
167
end
168
169
return TimeSeriesCallback(mesh, equations, solver, cache, point_coordinates_;
170
kwargs...)
171
end
172
173
# This method is called as callback during the time integration.
174
function (time_series_callback::TimeSeriesCallback)(integrator)
175
# Ensure this is not accidentally used with AMR enabled
176
if uses_amr(integrator.opts.callback)
177
error("the TimeSeriesCallback does not work with AMR enabled")
178
end
179
180
@unpack interval = time_series_callback
181
182
# Create record if in correct interval (needs to be checked since the callback is also called
183
# after the final step for storing the data on disk, independent of the current interval)
184
if integrator.stats.naccept % interval == 0
185
@trixi_timeit timer() "time series" begin
186
# Store time and step
187
push!(time_series_callback.time, integrator.t)
188
push!(time_series_callback.step, integrator.stats.naccept)
189
190
# Unpack data
191
u_ode = integrator.u
192
semi = integrator.p
193
mesh, equations, solver, cache = mesh_equations_solver_cache(semi)
194
u = wrap_array(u_ode, mesh, equations, solver, cache)
195
196
@unpack (point_data, solution_variables,
197
variable_names, time_series_cache) = time_series_callback
198
199
# Record state at points (solver/mesh-dependent implementation)
200
record_state_at_points!(point_data, u, solution_variables,
201
length(variable_names), mesh,
202
equations, solver, time_series_cache)
203
end
204
end
205
206
# Store time_series if this is the last time step
207
if isfinished(integrator)
208
semi = integrator.p
209
mesh, equations, solver, _ = mesh_equations_solver_cache(semi)
210
save_time_series_file(time_series_callback, mesh, equations, solver)
211
end
212
213
# avoid re-evaluating possible FSAL stages
214
u_modified!(integrator, false)
215
216
return nothing
217
end
218
219
include("time_series_dg.jl")
220
include("time_series_dg_tree.jl")
221
include("time_series_dg_unstructured.jl")
222
end # @muladd
223
224