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
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
"""
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
l2_error, linf_error = mod.analysis_callback(mod.sol)
40
41
# collect errors as one vector to reshape later
42
append!(errors[:l2], l2_error)
43
append!(errors[:linf], linf_error)
44
45
println("\n\n")
46
println("#"^100)
47
end
48
49
# Use raw error values to compute EOC
50
analyze_convergence(errors, iterations, mod.semi)
51
end
52
53
# Analyze convergence for any semidiscretization
54
# Note: this intermediate method is to allow dispatching on the semidiscretization
55
function analyze_convergence(errors, iterations, semi::AbstractSemidiscretization)
56
_, equations, _, _ = mesh_equations_solver_cache(semi)
57
variablenames = varnames(cons2cons, equations)
58
analyze_convergence(errors, iterations, variablenames)
59
end
60
61
# This method is called with the collected error values to actually compute and print the EOC
62
function analyze_convergence(errors, iterations,
63
variablenames::Union{Tuple, AbstractArray})
64
nvariables = length(variablenames)
65
66
# Reshape errors to get a matrix where the i-th row represents the i-th iteration
67
# and the j-th column represents the j-th variable
68
errorsmatrix = Dict(kind => transpose(reshape(error, (nvariables, iterations)))
69
for (kind, error) in errors)
70
71
# Calculate EOCs where the columns represent the variables
72
# As dx halves in every iteration the denominator needs to be log(1/2)
73
eocs = Dict(kind => log.(error[2:end, :] ./ error[1:(end - 1), :]) ./ log(1 / 2)
74
for (kind, error) in errorsmatrix)
75
76
eoc_mean_values = Dict{Symbol, Any}()
77
eoc_mean_values[:variables] = variablenames
78
79
for (kind, error) in errorsmatrix
80
println(kind)
81
82
for v in variablenames
83
@printf("%-20s", v)
84
end
85
println("")
86
87
for k in 1:nvariables
88
@printf("%-10s", "error")
89
@printf("%-10s", "EOC")
90
end
91
println("")
92
93
# Print errors for the first iteration
94
for k in 1:nvariables
95
@printf("%-10.2e", error[1, k])
96
@printf("%-10s", "-")
97
end
98
println("")
99
100
# For the following iterations print errors and EOCs
101
for j in 2:iterations
102
for k in 1:nvariables
103
@printf("%-10.2e", error[j, k])
104
@printf("%-10.2f", eocs[kind][j - 1, k])
105
end
106
println("")
107
end
108
println("")
109
110
# Print mean EOCs
111
mean_values = zeros(eltype(errors[:l2]), nvariables)
112
for v in 1:nvariables
113
mean_values[v] = sum(eocs[kind][:, v]) ./ length(eocs[kind][:, v])
114
@printf("%-10s", "mean")
115
@printf("%-10.2f", mean_values[v])
116
end
117
eoc_mean_values[kind] = mean_values
118
println("")
119
println("-"^100)
120
end
121
122
return eoc_mean_values
123
end
124
125
function convergence_test(elixir::AbstractString, iterations, RealT = Float64;
126
kwargs...)
127
convergence_test(Main, elixir::AbstractString, iterations, RealT; kwargs...)
128
end
129
130
# Helper methods used in the functions defined above
131
132
# Searches for the assignment that specifies the mesh resolution in the elixir
133
function extract_initial_resolution(elixir, kwargs)
134
code = read(elixir, String)
135
expr = Meta.parse("begin \n$code \nend")
136
137
try
138
# get the initial_refinement_level from the elixir
139
initial_refinement_level = TrixiBase.find_assignment(expr,
140
:initial_refinement_level)
141
142
if haskey(kwargs, :initial_refinement_level)
143
return kwargs[:initial_refinement_level]
144
else
145
return initial_refinement_level
146
end
147
catch e
148
# If `initial_refinement_level` is not found, we will get an `ArgumentError`
149
if isa(e, ArgumentError)
150
try
151
# get cells_per_dimension from the elixir
152
cells_per_dimension = eval(TrixiBase.find_assignment(expr,
153
:cells_per_dimension))
154
155
if haskey(kwargs, :cells_per_dimension)
156
return kwargs[:cells_per_dimension]
157
else
158
return cells_per_dimension
159
end
160
catch e2
161
# If `cells_per_dimension` is not found either
162
if isa(e2, ArgumentError)
163
throw(ArgumentError("`convergence_test` requires the elixir to define " *
164
"`initial_refinement_level` or `cells_per_dimension`"))
165
else
166
rethrow()
167
end
168
end
169
else
170
rethrow()
171
end
172
end
173
end
174
175
# runs the specified elixir with a doubled resolution each time iter is increased by 1
176
# works for TreeMesh
177
function include_refined(mod, elixir, initial_refinement_level::Int, iter; kwargs)
178
trixi_include(mod, elixir; kwargs...,
179
initial_refinement_level = initial_refinement_level + iter - 1)
180
end
181
182
# runs the specified elixir with a doubled resolution each time iter is increased by 1
183
# works for StructuredMesh
184
function include_refined(mod, elixir, cells_per_dimension::NTuple{NDIMS, Int}, iter;
185
kwargs) where {NDIMS}
186
new_cells_per_dimension = cells_per_dimension .* 2^(iter - 1)
187
188
trixi_include(mod, elixir; kwargs..., cells_per_dimension = new_cells_per_dimension)
189
end
190
end # @muladd
191
192