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