Path: blob/main/src/auxiliary/special_elixirs.jl
5586 views
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).1# Since these FMAs can increase the performance of many numerical algorithms,2# we need to opt-in explicitly.3# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.4@muladd begin5#! format: noindent67"""8convergence_test([mod::Module=Main,] elixir::AbstractString, iterations, RealT = Float64; kwargs...)910Run `iterations` Trixi.jl simulations using the setup given in `elixir` and compute11the experimental order of convergence (EOC) in the ``L^2`` and ``L^\\infty`` norm.12Use `RealT` as the data type to represent the errors.13In each iteration, the resolution of the respective mesh will be doubled.14Additional keyword arguments `kwargs...` and the optional module `mod` are passed directly15to [`trixi_include`](@ref).1617This function assumes that the spatial resolution is set via the keywords18`initial_refinement_level` (an integer) or `cells_per_dimension` (a tuple of19integers, one per spatial dimension).20"""21function convergence_test(mod::Module, elixir::AbstractString, iterations,22RealT = Float64;23kwargs...)24@assert(iterations>1,25"Number of iterations must be bigger than 1 for a convergence analysis")2627# Types of errors to be calculated28errors = Dict(:l2 => RealT[], :linf => RealT[])2930initial_resolution = extract_initial_resolution(elixir, kwargs)3132# run simulations and extract errors33for iter in 1:iterations34println("Running convtest iteration ", iter, "/", iterations)3536include_refined(mod, elixir, initial_resolution, iter; kwargs)3738# @invokelatest is required for interactive use39# due to world age issues on Julia 1.12 (and newer)40l2_error, linf_error = @invokelatest mod.analysis_callback(@invokelatest mod.sol)4142# collect errors as one vector to reshape later43append!(errors[:l2], l2_error)44append!(errors[:linf], linf_error)4546println("\n\n")47println("#"^100)48end4950# Use raw error values to compute EOC51# @invokelatest is required for interactive use52# due to world age issues on Julia 1.12 (and newer)53return analyze_convergence(errors, iterations, (@invokelatest mod.semi))54end5556"""57Trixi.calc_mean_convergence(eocs)5859Calculate the mean convergence rates from the given experimental orders of convergence `eocs`.60The `eocs` are expected to be in the format returned by [`convergence_test`](@ref), i.e., a `Dict` where61the keys are the error types (e.g., `:l2`, `:linf`) and the values are matrices with the EOCs for each62variable in the columns and the iterations in the rows.63Returns a `Dict` with the same keys as `eocs` and the mean convergence rates for all variables as values.64"""65function calc_mean_convergence(eocs)66return Dict(kind => [sum(eocs[kind][:, v]) / length(eocs[kind][:, v])67for v in 1:size(eocs[kind], 2)]68for kind in keys(eocs))69end7071# Analyze convergence for any semidiscretization72# Note: this intermediate method is to allow dispatching on the semidiscretization73function analyze_convergence(errors, iterations, semi::AbstractSemidiscretization)74_, equations, _, _ = mesh_equations_solver_cache(semi)75variablenames = varnames(cons2cons, equations)76return analyze_convergence(errors, iterations, variablenames)77end7879# This method is called with the collected error values to actually compute and print the EOC80function analyze_convergence(errors, iterations,81variablenames::Union{Tuple, AbstractArray})82nvariables = length(variablenames)8384# Reshape errors to get a matrix where the i-th row represents the i-th iteration85# and the j-th column represents the j-th variable86errorsmatrix = Dict(kind => transpose(reshape(error, (nvariables, iterations)))87for (kind, error) in errors)8889# Calculate EOCs where the columns represent the variables90# As dx halves in every iteration the denominator needs to be log(1/2)91eocs = Dict(kind => log.(error[2:end, :] ./ error[1:(end - 1), :]) ./ log(1 / 2)92for (kind, error) in errorsmatrix)9394eoc_mean_values = calc_mean_convergence(eocs)9596for (kind, error) in errorsmatrix97println(kind)9899for v in variablenames100@printf("%-20s", v)101end102println("")103104for k in 1:nvariables105@printf("%-10s", "error")106@printf("%-10s", "EOC")107end108println("")109110# Print errors for the first iteration111for k in 1:nvariables112@printf("%-10.2e", error[1, k])113@printf("%-10s", "-")114end115println("")116117# For the following iterations print errors and EOCs118for j in 2:iterations119for k in 1:nvariables120@printf("%-10.2e", error[j, k])121@printf("%-10.2f", eocs[kind][j - 1, k])122end123println("")124end125println("")126127# Print mean EOCs128for v in 1:nvariables129@printf("%-10s", "mean")130@printf("%-10.2f", eoc_mean_values[kind][v])131end132println("")133println("-"^100)134end135136return eocs, errorsmatrix137end138139function convergence_test(elixir::AbstractString, iterations, RealT = Float64;140kwargs...)141return convergence_test(Main, elixir::AbstractString, iterations, RealT; kwargs...)142end143144# Helper methods used in the functions defined above145146# Searches for the assignment that specifies the mesh resolution in the elixir147function extract_initial_resolution(elixir, kwargs)148code = read(elixir, String)149expr = Meta.parse("begin \n$code \nend")150151try152# get the initial_refinement_level from the elixir153initial_refinement_level = TrixiBase.find_assignment(expr,154:initial_refinement_level)155156if haskey(kwargs, :initial_refinement_level)157return kwargs[:initial_refinement_level]158else159return initial_refinement_level160end161catch e162# If `initial_refinement_level` is not found, we will get an `ArgumentError`163if isa(e, ArgumentError)164try165# get cells_per_dimension from the elixir166cells_per_dimension = eval(TrixiBase.find_assignment(expr,167:cells_per_dimension))168169if haskey(kwargs, :cells_per_dimension)170return kwargs[:cells_per_dimension]171else172return cells_per_dimension173end174catch e2175# If `cells_per_dimension` is not found either176if isa(e2, ArgumentError)177throw(ArgumentError("`convergence_test` requires the elixir to define " *178"`initial_refinement_level` or `cells_per_dimension`"))179else180rethrow()181end182end183else184rethrow()185end186end187end188189# runs the specified elixir with a doubled resolution each time iter is increased by 1190# works for TreeMesh191function include_refined(mod, elixir, initial_refinement_level::Int, iter; kwargs)192return trixi_include(mod, elixir; kwargs...,193initial_refinement_level = initial_refinement_level + iter - 1)194end195196# runs the specified elixir with a doubled resolution each time iter is increased by 1197# works for StructuredMesh198function include_refined(mod, elixir, cells_per_dimension::NTuple{NDIMS, Int}, iter;199kwargs) where {NDIMS}200new_cells_per_dimension = cells_per_dimension .* 2^(iter - 1)201202return trixi_include(mod, elixir; kwargs...,203cells_per_dimension = new_cells_per_dimension)204end205end # @muladd206207208