Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/auxiliary/special_elixirs.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
convergence_test([mod::Module=Main,] elixir::AbstractString, iterations, RealT = Float64; kwargs...)
10
11
Run `iterations` Trixi.jl simulations using the setup given in `elixir` and compute
12
the experimental order of convergence (EOC) in the ``L^2`` and ``L^\\infty`` norm.
13
Use `RealT` as the data type to represent the errors.
14
In each iteration, the resolution of the respective mesh will be doubled.
15
Additional keyword arguments `kwargs...` and the optional module `mod` are passed directly
16
to [`trixi_include`](@ref).
17
18
This function assumes that the spatial resolution is set via the keywords
19
`initial_refinement_level` (an integer) or `cells_per_dimension` (a tuple of
20
integers, one per spatial dimension).
21
"""
22
function convergence_test(mod::Module, elixir::AbstractString, iterations,
23
RealT = Float64;
24
kwargs...)
25
@assert(iterations>1,
26
"Number of iterations must be bigger than 1 for a convergence analysis")
27
28
# Types of errors to be calculated
29
errors = Dict(:l2 => RealT[], :linf => RealT[])
30
31
initial_resolution = extract_initial_resolution(elixir, kwargs)
32
33
# run simulations and extract errors
34
for iter in 1:iterations
35
println("Running convtest iteration ", iter, "/", iterations)
36
37
include_refined(mod, elixir, initial_resolution, iter; kwargs)
38
39
# @invokelatest is required for interactive use
40
# due to world age issues on Julia 1.12 (and newer)
41
l2_error, linf_error = @invokelatest mod.analysis_callback(@invokelatest mod.sol)
42
43
# collect errors as one vector to reshape later
44
append!(errors[:l2], l2_error)
45
append!(errors[:linf], linf_error)
46
47
println("\n\n")
48
println("#"^100)
49
end
50
51
# Use raw error values to compute EOC
52
# @invokelatest is required for interactive use
53
# due to world age issues on Julia 1.12 (and newer)
54
return analyze_convergence(errors, iterations, (@invokelatest mod.semi))
55
end
56
57
"""
58
Trixi.calc_mean_convergence(eocs)
59
60
Calculate the mean convergence rates from the given experimental orders of convergence `eocs`.
61
The `eocs` are expected to be in the format returned by [`convergence_test`](@ref), i.e., a `Dict` where
62
the keys are the error types (e.g., `:l2`, `:linf`) and the values are matrices with the EOCs for each
63
variable in the columns and the iterations in the rows.
64
Returns a `Dict` with the same keys as `eocs` and the mean convergence rates for all variables as values.
65
"""
66
function calc_mean_convergence(eocs)
67
return Dict(kind => [sum(eocs[kind][:, v]) / length(eocs[kind][:, v])
68
for v in 1:size(eocs[kind], 2)]
69
for kind in keys(eocs))
70
end
71
72
# Analyze convergence for any semidiscretization
73
# Note: this intermediate method is to allow dispatching on the semidiscretization
74
function analyze_convergence(errors, iterations, semi::AbstractSemidiscretization)
75
_, equations, _, _ = mesh_equations_solver_cache(semi)
76
variablenames = varnames(cons2cons, equations)
77
return analyze_convergence(errors, iterations, variablenames)
78
end
79
80
# This method is called with the collected error values to actually compute and print the EOC
81
function analyze_convergence(errors, iterations,
82
variablenames::Union{Tuple, AbstractArray})
83
nvariables = length(variablenames)
84
85
# Reshape errors to get a matrix where the i-th row represents the i-th iteration
86
# and the j-th column represents the j-th variable
87
errorsmatrix = Dict(kind => transpose(reshape(error, (nvariables, iterations)))
88
for (kind, error) in errors)
89
90
# Calculate EOCs where the columns represent the variables
91
# As dx halves in every iteration the denominator needs to be log(1/2)
92
eocs = Dict(kind => log.(error[2:end, :] ./ error[1:(end - 1), :]) ./ log(1 / 2)
93
for (kind, error) in errorsmatrix)
94
95
eoc_mean_values = calc_mean_convergence(eocs)
96
97
for (kind, error) in errorsmatrix
98
println(kind)
99
100
for v in variablenames
101
@printf("%-20s", v)
102
end
103
println("")
104
105
for k in 1:nvariables
106
@printf("%-10s", "error")
107
@printf("%-10s", "EOC")
108
end
109
println("")
110
111
# Print errors for the first iteration
112
for k in 1:nvariables
113
@printf("%-10.2e", error[1, k])
114
@printf("%-10s", "-")
115
end
116
println("")
117
118
# For the following iterations print errors and EOCs
119
for j in 2:iterations
120
for k in 1:nvariables
121
@printf("%-10.2e", error[j, k])
122
@printf("%-10.2f", eocs[kind][j - 1, k])
123
end
124
println("")
125
end
126
println("")
127
128
# Print mean EOCs
129
for v in 1:nvariables
130
@printf("%-10s", "mean")
131
@printf("%-10.2f", eoc_mean_values[kind][v])
132
end
133
println("")
134
println("-"^100)
135
end
136
137
return eocs, errorsmatrix
138
end
139
140
function convergence_test(elixir::AbstractString, iterations, RealT = Float64;
141
kwargs...)
142
return convergence_test(Main, elixir::AbstractString, iterations, RealT; kwargs...)
143
end
144
145
# Helper methods used in the functions defined above
146
147
# Searches for the assignment that specifies the mesh resolution in the elixir
148
function extract_initial_resolution(elixir, kwargs)
149
code = read(elixir, String)
150
expr = Meta.parse("begin \n$code \nend")
151
152
try
153
# get the initial_refinement_level from the elixir
154
initial_refinement_level = TrixiBase.find_assignment(expr,
155
:initial_refinement_level)
156
157
if haskey(kwargs, :initial_refinement_level)
158
return kwargs[:initial_refinement_level]
159
else
160
return initial_refinement_level
161
end
162
catch e
163
# If `initial_refinement_level` is not found, we will get an `ArgumentError`
164
if isa(e, ArgumentError)
165
try
166
# get cells_per_dimension from the elixir
167
cells_per_dimension = eval(TrixiBase.find_assignment(expr,
168
:cells_per_dimension))
169
170
if haskey(kwargs, :cells_per_dimension)
171
return kwargs[:cells_per_dimension]
172
else
173
return cells_per_dimension
174
end
175
catch e2
176
# If `cells_per_dimension` is not found either
177
if isa(e2, ArgumentError)
178
throw(ArgumentError("`convergence_test` requires the elixir to define " *
179
"`initial_refinement_level` or `cells_per_dimension`"))
180
else
181
rethrow()
182
end
183
end
184
else
185
rethrow()
186
end
187
end
188
end
189
190
# runs the specified elixir with a doubled resolution each time iter is increased by 1
191
# works for TreeMesh
192
function include_refined(mod, elixir, initial_refinement_level::Int, iter; kwargs)
193
return trixi_include(mod, elixir; kwargs...,
194
initial_refinement_level = initial_refinement_level + iter - 1)
195
end
196
197
# runs the specified elixir with a doubled resolution each time iter is increased by 1
198
# works for StructuredMesh
199
function include_refined(mod, elixir, cells_per_dimension::NTuple{NDIMS, Int}, iter;
200
kwargs) where {NDIMS}
201
new_cells_per_dimension = cells_per_dimension .* 2^(iter - 1)
202
203
return trixi_include(mod, elixir; kwargs...,
204
cells_per_dimension = new_cells_per_dimension)
205
end
206
end # @muladd
207
208