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
5586 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
const interval::Int
31
const solution_variables::SolutionVariables
32
const variable_names::VariableNames
33
const output_directory::String
34
const filename::String
35
const 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
const 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
return nothing
60
end
61
62
function Base.show(io::IO, ::MIME"text/plain",
63
cb::DiscreteCallback{<:Any, <:TimeSeriesCallback})
64
@nospecialize cb # reduce precompilation time
65
66
if get(io, :compact, false)
67
show(io, cb)
68
else
69
time_series_callback = cb.affect!
70
71
setup = [
72
"#points" => size(time_series_callback.point_coordinates, 2),
73
"interval" => time_series_callback.interval,
74
"solution_variables" => time_series_callback.solution_variables,
75
"output_directory" => time_series_callback.output_directory,
76
"filename" => time_series_callback.filename
77
]
78
summary_box(io, "TimeSeriesCallback", setup)
79
end
80
end
81
82
# Main constructor
83
function TimeSeriesCallback(mesh, equations, solver, cache, point_coordinates;
84
interval::Integer = 1,
85
solution_variables = cons2cons,
86
output_directory = "out",
87
filename = "time_series.h5",
88
RealT = real(solver),
89
uEltype = eltype(cache.elements))
90
# check arguments
91
if !(interval isa Integer && interval >= 0)
92
throw(ArgumentError("`interval` must be a non-negative integer (provided `interval = $interval`)"))
93
end
94
95
if ndims(point_coordinates) != 2 || size(point_coordinates, 2) != ndims(mesh)
96
throw(ArgumentError("`point_coordinates` must be a matrix of size n_points × ndims"))
97
end
98
99
# create the output folder if it does not exist already
100
if mpi_isroot() && !isdir(output_directory)
101
mkpath(output_directory)
102
end
103
104
# Transpose point_coordinates to our usual format [ndims, n_points]
105
# Note: They are accepted in a different format to allow direct input from `readdlm`
106
point_coordinates_ = permutedims(point_coordinates)
107
108
# Invoke callback every `interval` time steps or after final step (for storing the data on disk)
109
if interval > 0
110
# With error-based step size control, some steps can be rejected. Thus,
111
# `integrator.iter >= integrator.stats.naccept`
112
# (total #steps) (#accepted steps)
113
# We need to check the number of accepted steps since callbacks are not
114
# activated after a rejected step.
115
condition = (u, t, integrator) -> ((integrator.stats.naccept % interval == 0 &&
116
!(integrator.stats.naccept == 0 &&
117
integrator.iter > 0)) ||
118
isfinished(integrator))
119
else # disable the callback for interval == 0
120
condition = (u, t, integrator) -> false
121
end
122
123
# Create data structures that are to be filled by the callback
124
variable_names = varnames(solution_variables, equations)
125
n_points = size(point_coordinates_, 2)
126
point_data = Vector{uEltype}[Vector{uEltype}() for _ in 1:n_points]
127
time = Vector{RealT}()
128
step = Vector{Int}()
129
time_series_cache = create_cache_time_series(point_coordinates_, mesh, solver,
130
cache)
131
132
time_series_callback = TimeSeriesCallback(interval,
133
solution_variables,
134
variable_names,
135
output_directory,
136
filename,
137
point_coordinates_,
138
point_data,
139
time,
140
step,
141
time_series_cache)
142
143
return DiscreteCallback(condition, time_series_callback,
144
save_positions = (false, false))
145
end
146
147
# Convenience constructor that unpacks the semidiscretization into mesh, equations, solver, cache
148
function TimeSeriesCallback(semi, point_coordinates; kwargs...)
149
mesh, equations, solver, cache = mesh_equations_solver_cache(semi)
150
151
return TimeSeriesCallback(mesh, equations, solver, cache, point_coordinates;
152
kwargs...)
153
end
154
155
# Convenience constructor that converts a vector of points into a Trixi.jl-style coordinate array
156
function TimeSeriesCallback(mesh, equations, solver, cache,
157
point_coordinates::AbstractVector;
158
kwargs...)
159
# Coordinates are usually stored in [ndims, n_points], but here as [n_points, ndims]
160
n_points = length(point_coordinates)
161
point_coordinates_ = Matrix{eltype(eltype(point_coordinates))}(undef, n_points,
162
ndims(mesh))
163
164
for p in 1:n_points
165
for d in 1:ndims(mesh)
166
point_coordinates_[p, d] = point_coordinates[p][d]
167
end
168
end
169
170
return TimeSeriesCallback(mesh, equations, solver, cache, point_coordinates_;
171
kwargs...)
172
end
173
174
# This method is called as callback during the time integration.
175
function (time_series_callback::TimeSeriesCallback)(integrator)
176
# Ensure this is not accidentally used with AMR enabled
177
if uses_amr(integrator.opts.callback)
178
error("the TimeSeriesCallback does not work with AMR enabled")
179
end
180
181
@unpack interval = time_series_callback
182
183
# Create record if in correct interval (needs to be checked since the callback is also called
184
# after the final step for storing the data on disk, independent of the current interval)
185
if integrator.stats.naccept % interval == 0
186
@trixi_timeit timer() "time series" begin
187
# Store time and step
188
push!(time_series_callback.time, integrator.t)
189
push!(time_series_callback.step, integrator.stats.naccept)
190
191
# Unpack data
192
u_ode = integrator.u
193
semi = integrator.p
194
mesh, equations, solver, cache = mesh_equations_solver_cache(semi)
195
u = wrap_array(u_ode, mesh, equations, solver, cache)
196
197
@unpack (point_data, solution_variables,
198
variable_names, time_series_cache) = time_series_callback
199
200
# Record state at points (solver/mesh-dependent implementation)
201
record_state_at_points!(point_data, u, solution_variables,
202
length(variable_names), mesh,
203
equations, solver, time_series_cache)
204
end
205
end
206
207
# Store time_series if this is the last time step
208
if isfinished(integrator)
209
semi = integrator.p
210
mesh, equations, solver, _ = mesh_equations_solver_cache(semi)
211
save_time_series_file(time_series_callback, mesh, equations, solver)
212
end
213
214
# avoid re-evaluating possible FSAL stages
215
u_modified!(integrator, false)
216
217
return nothing
218
end
219
220
include("time_series_dg.jl")
221
include("time_series_dg_tree.jl")
222
include("time_series_dg_unstructured.jl")
223
end # @muladd
224
225