Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/callbacks_step/summary.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
summary_callback(u, t, integrator) = false # when used as condition; never call the summary callback during the simulation
9
summary_callback(integrator) = u_modified!(integrator, false) # the summary callback does nothing when called accidentally
10
11
"""
12
SummaryCallback()
13
14
Create and return a callback that prints a human-readable summary of the simulation setup at the
15
beginning of a simulation and then resets the timer. At the end of the simulation the final timer
16
values are shown. When the returned callback is executed directly, the current timer values are shown.
17
"""
18
function SummaryCallback(reset_threads = true)
19
function initialize(cb, u, t, integrator)
20
return initialize_summary_callback(cb, u, t, integrator;
21
reset_threads)
22
end
23
# At the end of the simulation, the timer is printed
24
return DiscreteCallback(summary_callback, summary_callback,
25
save_positions = (false, false),
26
initialize = initialize,
27
finalize = finalize_summary_callback)
28
end
29
30
function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:typeof(summary_callback)})
31
@nospecialize cb # reduce precompilation time
32
33
print(io, "SummaryCallback")
34
return nothing
35
end
36
37
# Format a key/value pair for output from the SummaryCallback
38
function format_key_value_line(key::AbstractString, value::AbstractString, key_width,
39
total_width;
40
indentation_level = 0, guide = '…', filler = '…',
41
prefix = "│ ", suffix = " │")
42
@assert key_width < total_width
43
line = prefix
44
# Indent the key as requested (or not at all if `indentation_level == 0`)
45
indentation = prefix^indentation_level
46
reduced_key_width = key_width - length(indentation)
47
squeezed_key = indentation * squeeze(key, reduced_key_width, filler = filler)
48
line *= squeezed_key
49
line *= ": "
50
short = key_width - length(squeezed_key)
51
if short <= 1
52
line *= " "
53
else
54
line *= guide^(short - 1) * " "
55
end
56
value_width = total_width - length(prefix) - length(suffix) - key_width - 2
57
squeezed_value = squeeze(value, value_width, filler = filler)
58
line *= squeezed_value
59
short = value_width - length(squeezed_value)
60
line *= " "^short
61
line *= suffix
62
63
@assert length(line)==total_width "should not happen: algorithm error!"
64
65
return line
66
end
67
function format_key_value_line(key, value, args...; kwargs...)
68
return format_key_value_line(string(key), string(value), args...; kwargs...)
69
end
70
71
# Squeeze a string to fit into a maximum width by deleting characters from the center
72
function squeeze(message, max_width; filler::Char = '…')
73
@assert max_width>=3 "squeezing works only for a minimum `max_width` of 3"
74
75
length(message) <= max_width && return message
76
77
keep_front = div(max_width, 2)
78
keep_back = div(max_width, 2) - (isodd(max_width) ? 0 : 1)
79
remove_back = length(message) - keep_front
80
remove_front = length(message) - keep_back
81
squeezed = (chop(message, head = 0, tail = remove_back)
82
* filler *
83
chop(message, head = remove_front, tail = 0))
84
85
@assert length(squeezed)==max_width "`$(length(squeezed)) != $max_width` should not happen: algorithm error!"
86
87
return squeezed
88
end
89
90
# Print a summary with a box around it with a given heading and a setup of key=>value pairs
91
function summary_box(io::IO, heading, setup = [])
92
summary_header(io, heading)
93
for (key, value) in setup
94
summary_line(io, key, value)
95
end
96
summary_footer(io)
97
return nothing
98
end
99
100
function summary_header(io, heading; total_width = 100, indentation_level = 0)
101
total_width = get(io, :total_width, total_width)
102
indentation_level = get(io, :indentation_level, indentation_level)
103
104
@assert indentation_level>=0 "indentation level may not be negative"
105
106
# If indentation level is greater than zero, we assume the header has already been printed
107
indentation_level > 0 && return
108
109
# Print header
110
println(io, "┌" * "─"^(total_width - 2) * "┐")
111
println(io, "│ " * heading * " "^(total_width - length(heading) - 4) * " │")
112
println(io,
113
"│ " * "═"^length(heading) * " "^(total_width - length(heading) - 4) * " │")
114
return nothing
115
end
116
117
function summary_line(io, key, value; key_width = 35, total_width = 100,
118
indentation_level = 0)
119
# Printing is not performance-critical, so we can use `@nospecialize` to reduce latency
120
@nospecialize value # reduce precompilation time
121
122
key_width = get(io, :key_width, key_width)
123
total_width = get(io, :total_width, total_width)
124
indentation_level = get(io, :indentation_level, indentation_level)
125
126
s = format_key_value_line(key, value, key_width, total_width,
127
indentation_level = indentation_level)
128
129
println(io, s)
130
return nothing
131
end
132
133
function summary_footer(io; total_width = 100, indentation_level = 0)
134
total_width = get(io, :total_width, 100)
135
indentation_level = get(io, :indentation_level, 0)
136
137
if indentation_level == 0
138
s = "└" * "─"^(total_width - 2) * "┘"
139
else
140
s = ""
141
end
142
143
print(io, s)
144
return nothing
145
end
146
147
@inline function increment_indent(io)
148
return IOContext(io, :indentation_level => get(io, :indentation_level, 0) + 1)
149
end
150
151
# Print information about the current simulation setup
152
# Note: This is called *after* all initialization is done, but *before* the first time step
153
function initialize_summary_callback(cb::DiscreteCallback, u, t, integrator;
154
reset_threads = true)
155
# Optionally reset Polyester.jl threads. See
156
# https://github.com/trixi-framework/Trixi.jl/issues/1583
157
# https://github.com/JuliaSIMD/Polyester.jl/issues/30
158
if reset_threads
159
Polyester.reset_threads!()
160
end
161
162
# The summary callback should only print information on the root process.
163
# However, all other MPI processes should also reset the timer so that
164
# it can be used to diagnose performance.
165
if !mpi_isroot()
166
reset_timer!(timer())
167
return nothing
168
end
169
170
print_startup_message()
171
172
io = stdout
173
io_context = IOContext(io,
174
:compact => false,
175
:key_width => 35,
176
:total_width => 100,
177
:indentation_level => 0)
178
179
semi = integrator.p
180
print_summary_semidiscretization(io_context, semi)
181
182
callbacks = integrator.opts.callback
183
if callbacks isa CallbackSet
184
foreach(callbacks.continuous_callbacks) do cb
185
show(io_context, MIME"text/plain"(), cb)
186
println(io, "\n")
187
return nothing
188
end
189
foreach(callbacks.discrete_callbacks) do cb
190
# Do not show ourselves
191
cb.affect! === summary_callback && return nothing
192
193
show(io_context, MIME"text/plain"(), cb)
194
println(io, "\n")
195
return nothing
196
end
197
else
198
show(io_context, MIME"text/plain"(), callbacks)
199
println(io, "\n")
200
end
201
202
# time integration
203
setup = Pair{String, Any}["Start time" => first(integrator.sol.prob.tspan),
204
"Final time" => last(integrator.sol.prob.tspan),
205
"time integrator" => integrator.alg |> typeof |> nameof,
206
"adaptive" => integrator.opts.adaptive]
207
if integrator.opts.adaptive
208
push!(setup,
209
"abstol" => integrator.opts.abstol,
210
"reltol" => integrator.opts.reltol,
211
"controller" => integrator.opts.controller)
212
end
213
summary_box(io, "Time integration", setup)
214
println()
215
216
# technical details
217
setup = Pair{String, Any}["#threads" => Threads.nthreads()]
218
push!(setup, "threading backend" => string(_PREFERENCE_THREADING))
219
if !_PREFERENCE_LOOPVECTORIZATION
220
push!(setup, "LoopVectorization" => "disabled")
221
end
222
if mpi_isparallel()
223
push!(setup,
224
"#MPI ranks" => mpi_nranks())
225
end
226
summary_box(io, "Environment information", setup)
227
println()
228
229
reset_timer!(timer())
230
231
return nothing
232
end
233
234
finalize_summary_callback(cb, u, t, integrator) = cb()
235
236
function print_summary_semidiscretization(io::IO, semi::AbstractSemidiscretization)
237
show(io, MIME"text/plain"(), semi)
238
println(io, "\n")
239
mesh, equations, solver, _ = mesh_equations_solver_cache(semi)
240
show(io, MIME"text/plain"(), mesh)
241
println(io, "\n")
242
show(io, MIME"text/plain"(), equations)
243
println(io, "\n")
244
show(io, MIME"text/plain"(), solver)
245
println(io, "\n")
246
return nothing
247
end
248
249
function (cb::DiscreteCallback{Condition, Affect!})(io::IO = stdout) where {Condition,
250
Affect! <:
251
typeof(summary_callback)
252
}
253
mpi_isroot() || return nothing
254
255
TimerOutputs.complement!(timer())
256
print_timer(io, timer(), title = "Trixi.jl",
257
allocations = true, linechars = :unicode, compact = false)
258
println(io)
259
return nothing
260
end
261
end # @muladd
262
263