Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/auxiliary/auxiliary.jl
5586 views
1
# The following statements below outside the `@muladd begin ... end` block, as otherwise
2
# Revise.jl might be broken
3
4
include("containers.jl")
5
include("math.jl")
6
7
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).
8
# Since these FMAs can increase the performance of many numerical algorithms,
9
# we need to opt-in explicitly.
10
# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.
11
@muladd begin
12
#! format: noindent
13
14
"""
15
PerformanceCounter()
16
17
A `PerformanceCounter` can be used to track the runtime performance of some calls.
18
Add a new runtime measurement via `put!(counter, runtime)` and get the averaged
19
runtime of all measurements added so far via `take!(counter)`, resetting the
20
`counter`.
21
"""
22
mutable struct PerformanceCounter
23
ncalls_since_readout::Int
24
runtime::Float64
25
end
26
27
PerformanceCounter() = PerformanceCounter(0, 0.0)
28
29
@inline function Base.take!(counter::PerformanceCounter)
30
time_per_call = counter.runtime / counter.ncalls_since_readout
31
counter.ncalls_since_readout = 0
32
counter.runtime = 0.0
33
return time_per_call
34
end
35
36
@inline function Base.put!(counter::PerformanceCounter, runtime::Real)
37
counter.ncalls_since_readout += 1
38
return counter.runtime += runtime
39
end
40
41
@inline ncalls(counter::PerformanceCounter) = counter.ncalls_since_readout
42
43
"""
44
PerformanceCounterList{N}()
45
46
A `PerformanceCounterList{N}` can be used to track the runtime performance of
47
calls to multiple functions, adding them up.
48
Add a new runtime measurement via `put!(counter.counters[i], runtime)` and get
49
the averaged runtime of all measurements added so far via `take!(counter)`,
50
resetting the `counter`.
51
"""
52
struct PerformanceCounterList{N}
53
counters::NTuple{N, PerformanceCounter}
54
check_ncalls_consistency::Bool
55
end
56
57
function PerformanceCounterList{N}(check_ncalls_consistency) where {N}
58
counters = ntuple(_ -> PerformanceCounter(), Val{N}())
59
return PerformanceCounterList{N}(counters, check_ncalls_consistency)
60
end
61
PerformanceCounterList{N}() where {N} = PerformanceCounterList{N}(true)
62
63
@inline function Base.take!(counter_list::PerformanceCounterList)
64
time_per_call = 0.0
65
for c in counter_list.counters
66
time_per_call += take!(c)
67
end
68
return time_per_call
69
end
70
71
@inline function ncalls(counter_list::PerformanceCounterList)
72
ncalls_first = ncalls(first(counter_list.counters))
73
74
if counter_list.check_ncalls_consistency
75
for c in counter_list.counters
76
if ncalls_first != ncalls(c)
77
error("Some counters have a different number of calls. Using `ncalls` on the counter list is undefined behavior.")
78
end
79
end
80
end
81
82
return ncalls_first
83
end
84
85
"""
86
@trixi_timeit_ext backend timer() "some label" expression
87
88
This macro is an extension of [`@trixi_timeit`](@ref) that also synchronizes the given `backend` after executing the given `expression`.
89
This is useful to get accurate timing measurements for GPU backends, where the execution of kernels is asynchronous.
90
The synchronization ensures that all GPU operations are completed before the timer is stopped.
91
92
See also [`@trixi_timeit`](@ref).
93
"""
94
macro trixi_timeit_ext(backend, timer_output, label, expr)
95
expr = quote
96
local val = $(esc(expr))
97
if $(esc(backend)) !== nothing && $(TrixiBase).timeit_debug_enabled()
98
$(KernelAbstractions.synchronize)($(esc(backend)))
99
end
100
val
101
end
102
return :(@trixi_timeit($(esc(timer_output)), $(esc(label)), $(expr)))
103
end
104
105
"""
106
examples_dir()
107
108
Return the directory where the example files provided with Trixi.jl are located. If Trixi.jl is
109
installed as a regular package (with `]add Trixi`), these files are read-only and should *not* be
110
modified. To find out which files are available, use, e.g., `readdir`:
111
112
# Examples
113
```@example
114
readdir(examples_dir())
115
```
116
"""
117
examples_dir() = pkgdir(Trixi, "examples")
118
119
"""
120
get_examples()
121
122
Return a list of all example elixirs that are provided by Trixi.jl. See also
123
[`examples_dir`](@ref) and [`default_example`](@ref).
124
"""
125
function get_examples()
126
examples = String[]
127
for (root, dirs, files) in walkdir(examples_dir())
128
for f in files
129
if startswith(f, "elixir_") && endswith(f, ".jl")
130
push!(examples, joinpath(root, f))
131
end
132
end
133
end
134
135
return examples
136
end
137
138
"""
139
default_example()
140
141
Return the path to an example elixir that can be used to quickly see Trixi.jl in action on a
142
[`TreeMesh`](@ref). See also [`examples_dir`](@ref) and [`get_examples`](@ref).
143
"""
144
function default_example()
145
return joinpath(examples_dir(), "tree_2d_dgsem", "elixir_advection_basic.jl")
146
end
147
148
"""
149
default_example_unstructured()
150
151
Return the path to an example elixir that can be used to quickly see Trixi.jl in action on an
152
[`UnstructuredMesh2D`](@ref). This simulation is run on the example curved, unstructured mesh
153
given in the Trixi.jl documentation regarding unstructured meshes.
154
"""
155
function default_example_unstructured()
156
return joinpath(examples_dir(), "unstructured_2d_dgsem", "elixir_euler_basic.jl")
157
end
158
159
"""
160
ode_default_options()
161
162
Return the default options for OrdinaryDiffEq's `solve`. Pass `ode_default_options()...` to `solve`
163
to only return the solution at the final time and enable **MPI aware** error-based step size control,
164
whenever MPI is used.
165
For example, use `solve(ode, alg; ode_default_options()...)`.
166
"""
167
function ode_default_options()
168
if mpi_isparallel()
169
return (; save_everystep = false, internalnorm = ode_norm,
170
unstable_check = ode_unstable_check)
171
else
172
return (; save_everystep = false)
173
end
174
end
175
176
# Print informative message at startup
177
function print_startup_message()
178
s = """
179
180
████████╗██████╗ ██╗██╗ ██╗██╗
181
╚══██╔══╝██╔══██╗██║╚██╗██╔╝██║
182
██║ ██████╔╝██║ ╚███╔╝ ██║
183
██║ ██╔══██╗██║ ██╔██╗ ██║
184
██║ ██║ ██║██║██╔╝ ██╗██║
185
╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═╝╚═╝
186
"""
187
mpi_println(s)
188
return nothing
189
end
190
191
"""
192
get_name(x)
193
194
Returns a name of `x` ready for pretty printing.
195
By default, return `string(y)` if `x isa Val{y}` and return `string(x)` otherwise.
196
197
# Examples
198
199
```jldoctest
200
julia> Trixi.get_name("test")
201
"test"
202
203
julia> Trixi.get_name(Val(:test))
204
"test"
205
```
206
"""
207
get_name(x) = string(x)
208
get_name(::Val{x}) where {x} = string(x)
209
210
"""
211
@threaded for ... end
212
213
Semantically the same as `Threads.@threads` when iterating over a `AbstractUnitRange`
214
but without guarantee that the underlying implementation uses `Threads.@threads`
215
or works for more general `for` loops.
216
In particular, there may be an additional check whether only one thread is used
217
to reduce the overhead of serial execution or the underlying threading capabilities
218
might be provided by other packages such as [Polyester.jl](https://github.com/JuliaSIMD/Polyester.jl).
219
220
!!! warn
221
This macro does not necessarily work for general `for` loops. For example,
222
it does not necessarily support general iterables such as `eachline(filename)`.
223
224
Some discussion can be found at [https://discourse.julialang.org/t/overhead-of-threads-threads/53964](https://discourse.julialang.org/t/overhead-of-threads-threads/53964)
225
and [https://discourse.julialang.org/t/threads-threads-with-one-thread-how-to-remove-the-overhead/58435](https://discourse.julialang.org/t/threads-threads-with-one-thread-how-to-remove-the-overhead/58435).
226
"""
227
macro threaded(expr)
228
# !!! danger "Heisenbug"
229
# Look at the comments for `wrap_array` when considering to change this macro.
230
expr = @static if _PREFERENCE_THREADING === :polyester
231
# Currently using `@batch` from Polyester.jl is more efficient,
232
# bypasses the Julia task scheduler and provides parallelization with less overhead.
233
quote
234
$Trixi.@batch $(expr)
235
end
236
elseif _PREFERENCE_THREADING === :static ||
237
_PREFERENCE_THREADING === :kernelabstractions
238
# The following code is a simple version using only `Threads.@threads` from the
239
# standard library with an additional check whether only a single thread is used
240
# to reduce some overhead (and allocations) for serial execution.
241
# If we want to execute on KernelAbstractions, we use the static backend here to fallback on,
242
# for loops that do not yet support GPU execution.
243
quote
244
let
245
if $Threads.nthreads() == 1
246
$(expr)
247
else
248
$Threads.@threads :static $(expr)
249
end
250
end
251
end
252
elseif _PREFERENCE_THREADING === :serial
253
quote
254
$(expr)
255
end
256
end
257
# Use `esc(quote ... end)` for nested macro calls as suggested in
258
# https://github.com/JuliaLang/julia/issues/23221
259
return esc(expr)
260
end
261
262
"""
263
@autoinfiltrate
264
@autoinfiltrate condition::Bool
265
266
Invoke the `@infiltrate` macro of the package Infiltrator.jl to create a breakpoint for ad-hoc
267
interactive debugging in the REPL. If the optional argument `condition` is given, the breakpoint is
268
only enabled if `condition` evaluates to `true`.
269
270
As opposed to using `Infiltrator.@infiltrate` directly, this macro does not require Infiltrator.jl
271
to be added as a dependency to Trixi.jl. As a bonus, the macro will also attempt to load the
272
Infiltrator module if it has not yet been loaded manually.
273
274
Note: For this macro to work, the Infiltrator.jl package needs to be installed in your current Julia
275
environment stack.
276
277
See also: [Infiltrator.jl](https://github.com/JuliaDebug/Infiltrator.jl)
278
279
!!! warning "Internal use only"
280
Please note that this macro is intended for internal use only. It is *not* part of the public
281
API of Trixi.jl, and it thus can altered (or be removed) at any time without it being considered
282
a breaking change.
283
"""
284
macro autoinfiltrate(condition = true)
285
pkgid = Base.PkgId(Base.UUID("5903a43b-9cc3-4c30-8d17-598619ec4e9b"), "Infiltrator")
286
if !haskey(Base.loaded_modules, pkgid)
287
try
288
Base.eval(Main, :(using Infiltrator))
289
catch err
290
@error "Cannot load Infiltrator.jl. Make sure it is included in your environment stack."
291
end
292
end
293
i = get(Base.loaded_modules, pkgid, nothing)
294
lnn = LineNumberNode(__source__.line, __source__.file)
295
296
if i === nothing
297
return Expr(:macrocall,
298
Symbol("@warn"),
299
lnn,
300
"Could not load Infiltrator.")
301
end
302
303
return Expr(:macrocall,
304
Expr(:., i, QuoteNode(Symbol("@infiltrate"))),
305
lnn,
306
esc(condition))
307
end
308
309
# Use the *experimental* feature in `Base` to add error hints for specific errors. We use it to
310
# warn users in case they try to execute functions that are extended in package extensions which
311
# have not yet been loaded.
312
#
313
# Reference: https://docs.julialang.org/en/v1/base/base/#Base.Experimental.register_error_hint
314
function register_error_hints()
315
# We follow the advice in the docs and gracefully exit without doing anything if the experimental
316
# features gets silently removed.
317
if !isdefined(Base.Experimental, :register_error_hint)
318
return nothing
319
end
320
321
Base.Experimental.register_error_hint(MethodError) do io, exc, argtypes, kwargs
322
if exc.f in [iplot, iplot!] && isempty(methods(exc.f))
323
print(io,
324
"\n$(exc.f) has no methods yet. It is part of a plotting extension of Trixi.jl " *
325
"that relies on Makie being loaded.\n" *
326
"To activate the extension, execute `using Makie`, `using CairoMakie`, " *
327
"`using GLMakie`, or load any other package that also uses Makie.")
328
end
329
end
330
331
return nothing
332
end
333
334
"""
335
Trixi.download(src_url, file_path)
336
337
Download a file from given `src_url` to given `file_path` if
338
`file_path` is not already a file. This function just returns
339
`file_path`.
340
This is a small wrapper of `Downloads.download(src_url, file_path)`
341
that avoids race conditions when multiple MPI ranks are used.
342
Furthermore, when run as part of a GitHub Action, it uses
343
token-authenticated downloads to avoid GitHub's rate limiting
344
for unauthenticated HTTP request. To use this feature, provide
345
the environment variable `GITHUB_TOKEN`.
346
"""
347
function download(src_url, file_path)
348
# Note that `mpi_isroot()` is also `true` if running
349
# in serial (without MPI).
350
if mpi_isroot()
351
if !isfile(file_path)
352
headers = Pair{String, String}[]
353
# Pass the GITHUB_TOKEN through to prevent rate-limiting
354
token = get(ENV, "GITHUB_TOKEN", nothing)
355
if token !== nothing
356
push!(headers, "authorization" => "Bearer $token")
357
end
358
Downloads.download(src_url, file_path; headers)
359
end
360
end
361
362
if mpi_isparallel()
363
MPI.Barrier(mpi_comm())
364
end
365
366
return file_path
367
end
368
end # @muladd
369
370