Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/auxiliary/auxiliary.jl
2055 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
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
examples_dir()
87
88
Return the directory where the example files provided with Trixi.jl are located. If Trixi.jl is
89
installed as a regular package (with `]add Trixi`), these files are read-only and should *not* be
90
modified. To find out which files are available, use, e.g., `readdir`:
91
92
# Examples
93
```@example
94
readdir(examples_dir())
95
```
96
"""
97
examples_dir() = pkgdir(Trixi, "examples")
98
99
"""
100
get_examples()
101
102
Return a list of all example elixirs that are provided by Trixi.jl. See also
103
[`examples_dir`](@ref) and [`default_example`](@ref).
104
"""
105
function get_examples()
106
examples = String[]
107
for (root, dirs, files) in walkdir(examples_dir())
108
for f in files
109
if startswith(f, "elixir_") && endswith(f, ".jl")
110
push!(examples, joinpath(root, f))
111
end
112
end
113
end
114
115
return examples
116
end
117
118
"""
119
default_example()
120
121
Return the path to an example elixir that can be used to quickly see Trixi.jl in action on a
122
[`TreeMesh`](@ref). See also [`examples_dir`](@ref) and [`get_examples`](@ref).
123
"""
124
function default_example()
125
joinpath(examples_dir(), "tree_2d_dgsem", "elixir_advection_basic.jl")
126
end
127
128
"""
129
default_example_unstructured()
130
131
Return the path to an example elixir that can be used to quickly see Trixi.jl in action on an
132
[`UnstructuredMesh2D`](@ref). This simulation is run on the example curved, unstructured mesh
133
given in the Trixi.jl documentation regarding unstructured meshes.
134
"""
135
function default_example_unstructured()
136
joinpath(examples_dir(), "unstructured_2d_dgsem", "elixir_euler_basic.jl")
137
end
138
139
"""
140
ode_default_options()
141
142
Return the default options for OrdinaryDiffEq's `solve`. Pass `ode_default_options()...` to `solve`
143
to only return the solution at the final time and enable **MPI aware** error-based step size control,
144
whenever MPI is used.
145
For example, use `solve(ode, alg; ode_default_options()...)`.
146
"""
147
function ode_default_options()
148
if mpi_isparallel()
149
return (; save_everystep = false, internalnorm = ode_norm,
150
unstable_check = ode_unstable_check)
151
else
152
return (; save_everystep = false)
153
end
154
end
155
156
# Print informative message at startup
157
function print_startup_message()
158
s = """
159
160
████████╗██████╗ ██╗██╗ ██╗██╗
161
╚══██╔══╝██╔══██╗██║╚██╗██╔╝██║
162
██║ ██████╔╝██║ ╚███╔╝ ██║
163
██║ ██╔══██╗██║ ██╔██╗ ██║
164
██║ ██║ ██║██║██╔╝ ██╗██║
165
╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═╝╚═╝
166
"""
167
mpi_println(s)
168
end
169
170
"""
171
get_name(x)
172
173
Returns a name of `x` ready for pretty printing.
174
By default, return `string(y)` if `x isa Val{y}` and return `string(x)` otherwise.
175
176
# Examples
177
178
```jldoctest
179
julia> Trixi.get_name("test")
180
"test"
181
182
julia> Trixi.get_name(Val(:test))
183
"test"
184
```
185
"""
186
get_name(x) = string(x)
187
get_name(::Val{x}) where {x} = string(x)
188
189
"""
190
@threaded for ... end
191
192
Semantically the same as `Threads.@threads` when iterating over a `AbstractUnitRange`
193
but without guarantee that the underlying implementation uses `Threads.@threads`
194
or works for more general `for` loops.
195
In particular, there may be an additional check whether only one thread is used
196
to reduce the overhead of serial execution or the underlying threading capabilities
197
might be provided by other packages such as [Polyester.jl](https://github.com/JuliaSIMD/Polyester.jl).
198
199
!!! warn
200
This macro does not necessarily work for general `for` loops. For example,
201
it does not necessarily support general iterables such as `eachline(filename)`.
202
203
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)
204
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).
205
"""
206
macro threaded(expr)
207
# !!! danger "Heisenbug"
208
# Look at the comments for `wrap_array` when considering to change this macro.
209
expr = @static if _PREFERENCE_THREADING === :polyester
210
# Currently using `@batch` from Polyester.jl is more efficient,
211
# bypasses the Julia task scheduler and provides parallelization with less overhead.
212
quote
213
$Trixi.@batch $(expr)
214
end
215
elseif _PREFERENCE_THREADING === :static ||
216
_PREFERENCE_THREADING === :kernelabstractions
217
# The following code is a simple version using only `Threads.@threads` from the
218
# standard library with an additional check whether only a single thread is used
219
# to reduce some overhead (and allocations) for serial execution.
220
# If we want to execute on KernelAbstractions, we use the static backend here to fallback on,
221
# for loops that do not yet support GPU execution.
222
quote
223
let
224
if $Threads.nthreads() == 1
225
$(expr)
226
else
227
$Threads.@threads :static $(expr)
228
end
229
end
230
end
231
elseif _PREFERENCE_THREADING === :serial
232
quote
233
$(expr)
234
end
235
end
236
# Use `esc(quote ... end)` for nested macro calls as suggested in
237
# https://github.com/JuliaLang/julia/issues/23221
238
return esc(expr)
239
end
240
241
"""
242
@autoinfiltrate
243
@autoinfiltrate condition::Bool
244
245
Invoke the `@infiltrate` macro of the package Infiltrator.jl to create a breakpoint for ad-hoc
246
interactive debugging in the REPL. If the optional argument `condition` is given, the breakpoint is
247
only enabled if `condition` evaluates to `true`.
248
249
As opposed to using `Infiltrator.@infiltrate` directly, this macro does not require Infiltrator.jl
250
to be added as a dependency to Trixi.jl. As a bonus, the macro will also attempt to load the
251
Infiltrator module if it has not yet been loaded manually.
252
253
Note: For this macro to work, the Infiltrator.jl package needs to be installed in your current Julia
254
environment stack.
255
256
See also: [Infiltrator.jl](https://github.com/JuliaDebug/Infiltrator.jl)
257
258
!!! warning "Internal use only"
259
Please note that this macro is intended for internal use only. It is *not* part of the public
260
API of Trixi.jl, and it thus can altered (or be removed) at any time without it being considered
261
a breaking change.
262
"""
263
macro autoinfiltrate(condition = true)
264
pkgid = Base.PkgId(Base.UUID("5903a43b-9cc3-4c30-8d17-598619ec4e9b"), "Infiltrator")
265
if !haskey(Base.loaded_modules, pkgid)
266
try
267
Base.eval(Main, :(using Infiltrator))
268
catch err
269
@error "Cannot load Infiltrator.jl. Make sure it is included in your environment stack."
270
end
271
end
272
i = get(Base.loaded_modules, pkgid, nothing)
273
lnn = LineNumberNode(__source__.line, __source__.file)
274
275
if i === nothing
276
return Expr(:macrocall,
277
Symbol("@warn"),
278
lnn,
279
"Could not load Infiltrator.")
280
end
281
282
return Expr(:macrocall,
283
Expr(:., i, QuoteNode(Symbol("@infiltrate"))),
284
lnn,
285
esc(condition))
286
end
287
288
# Use the *experimental* feature in `Base` to add error hints for specific errors. We use it to
289
# warn users in case they try to execute functions that are extended in package extensions which
290
# have not yet been loaded.
291
#
292
# Reference: https://docs.julialang.org/en/v1/base/base/#Base.Experimental.register_error_hint
293
function register_error_hints()
294
# We follow the advice in the docs and gracefully exit without doing anything if the experimental
295
# features gets silently removed.
296
if !isdefined(Base.Experimental, :register_error_hint)
297
return nothing
298
end
299
300
Base.Experimental.register_error_hint(MethodError) do io, exc, argtypes, kwargs
301
if exc.f in [iplot, iplot!] && isempty(methods(exc.f))
302
print(io,
303
"\n$(exc.f) has no methods yet. It is part of a plotting extension of Trixi.jl " *
304
"that relies on Makie being loaded.\n" *
305
"To activate the extension, execute `using Makie`, `using CairoMakie`, " *
306
"`using GLMakie`, or load any other package that also uses Makie.")
307
end
308
end
309
310
return nothing
311
end
312
313
"""
314
Trixi.download(src_url, file_path)
315
316
Download a file from given `src_url` to given `file_path` if
317
`file_path` is not already a file. This function just returns
318
`file_path`.
319
This is a small wrapper of `Downloads.download(src_url, file_path)`
320
that avoids race conditions when multiple MPI ranks are used.
321
Furthermore, when run as part of a GitHub Action, it uses
322
token-authenticated downloads to avoid GitHub's rate limiting
323
for unauthenticated HTTP request. To use this feature, provide
324
the environment variable `GITHUB_TOKEN`.
325
"""
326
function download(src_url, file_path)
327
# Note that `mpi_isroot()` is also `true` if running
328
# in serial (without MPI).
329
if mpi_isroot()
330
if !isfile(file_path)
331
headers = Pair{String, String}[]
332
# Pass the GITHUB_TOKEN through to prevent rate-limiting
333
token = get(ENV, "GITHUB_TOKEN", nothing)
334
if token !== nothing
335
push!(headers, "authorization" => "Bearer $token")
336
end
337
Downloads.download(src_url, file_path; headers)
338
end
339
end
340
341
if mpi_isparallel()
342
MPI.Barrier(mpi_comm())
343
end
344
345
return file_path
346
end
347
end # @muladd
348
349