diff --git a/examples/p4est_2d_dgsem/elixir_advection_restart.jl b/examples/p4est_2d_dgsem/elixir_advection_restart.jl index 4f43e122ab3..0f573714c1f 100644 --- a/examples/p4est_2d_dgsem/elixir_advection_restart.jl +++ b/examples/p4est_2d_dgsem/elixir_advection_restart.jl @@ -31,7 +31,7 @@ save_solution.condition.save_initial_solution = false integrator = init(ode, CarpenterKennedy2N54(williamson_condition = false), dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); + save_everystep = false, callback = callbacks, maxiters = 100_000); # Get the last time index and work with that. load_timestep!(integrator, restart_filename) diff --git a/examples/p4est_3d_dgsem/elixir_advection_restart.jl b/examples/p4est_3d_dgsem/elixir_advection_restart.jl index cd97d69d692..b3dead42399 100644 --- a/examples/p4est_3d_dgsem/elixir_advection_restart.jl +++ b/examples/p4est_3d_dgsem/elixir_advection_restart.jl @@ -29,7 +29,7 @@ save_solution.condition.save_initial_solution = false integrator = init(ode, CarpenterKennedy2N54(williamson_condition = false), dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); + save_everystep = false, callback = callbacks, maxiters = 100_000); # Get the last time index and work with that. load_timestep!(integrator, restart_filename) diff --git a/examples/structured_2d_dgsem/elixir_advection_restart.jl b/examples/structured_2d_dgsem/elixir_advection_restart.jl index 19863faae8d..0accbdba702 100644 --- a/examples/structured_2d_dgsem/elixir_advection_restart.jl +++ b/examples/structured_2d_dgsem/elixir_advection_restart.jl @@ -30,7 +30,7 @@ save_solution.condition.save_initial_solution = false integrator = init(ode, CarpenterKennedy2N54(williamson_condition = false), dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); + save_everystep = false, callback = callbacks, maxiters = 100_000); # Get the last time index and work with that. load_timestep!(integrator, restart_filename) diff --git a/examples/structured_3d_dgsem/elixir_advection_restart.jl b/examples/structured_3d_dgsem/elixir_advection_restart.jl index e81ad5d6430..e516d794df8 100644 --- a/examples/structured_3d_dgsem/elixir_advection_restart.jl +++ b/examples/structured_3d_dgsem/elixir_advection_restart.jl @@ -29,7 +29,7 @@ save_solution.condition.save_initial_solution = false integrator = init(ode, CarpenterKennedy2N54(williamson_condition = false), dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); + save_everystep = false, callback = callbacks, maxiters = 100_000); # Get the last time index and work with that. load_timestep!(integrator, restart_filename) diff --git a/examples/tree_2d_dgsem/elixir_advection_restart.jl b/examples/tree_2d_dgsem/elixir_advection_restart.jl index 770629bb15e..e0d1003f524 100644 --- a/examples/tree_2d_dgsem/elixir_advection_restart.jl +++ b/examples/tree_2d_dgsem/elixir_advection_restart.jl @@ -29,7 +29,7 @@ save_solution.condition.save_initial_solution = false integrator = init(ode, alg, dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks; ode_default_options()...) + callback = callbacks, maxiters = 100_000; ode_default_options()...) # Load saved context for adaptive time integrator if integrator.opts.adaptive diff --git a/examples/tree_3d_dgsem/elixir_advection_restart.jl b/examples/tree_3d_dgsem/elixir_advection_restart.jl index f81e013fc0a..a53f4ebf5b1 100644 --- a/examples/tree_3d_dgsem/elixir_advection_restart.jl +++ b/examples/tree_3d_dgsem/elixir_advection_restart.jl @@ -27,7 +27,7 @@ save_solution.condition.save_initial_solution = false integrator = init(ode, CarpenterKennedy2N54(williamson_condition = false), dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); + save_everystep = false, callback = callbacks, maxiters = 100_000); # Get the last time index and work with that. load_timestep!(integrator, restart_filename) diff --git a/examples/unstructured_2d_dgsem/elixir_euler_restart.jl b/examples/unstructured_2d_dgsem/elixir_euler_restart.jl index 4d6af65b8a4..c1908691902 100644 --- a/examples/unstructured_2d_dgsem/elixir_euler_restart.jl +++ b/examples/unstructured_2d_dgsem/elixir_euler_restart.jl @@ -29,7 +29,7 @@ save_solution.condition.save_initial_solution = false integrator = init(ode, CarpenterKennedy2N54(williamson_condition = false), dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); + save_everystep = false, callback = callbacks, maxiters = 100_000); # Get the last time index and work with that. load_timestep!(integrator, restart_filename) diff --git a/src/auxiliary/special_elixirs.jl b/src/auxiliary/special_elixirs.jl index 25bca8939ce..5fdd9aea0c5 100644 --- a/src/auxiliary/special_elixirs.jl +++ b/src/auxiliary/special_elixirs.jl @@ -20,7 +20,7 @@ providing examples with sensible default values for users. Before replacing assignments in `elixir`, the keyword argument `maxiters` is inserted into calls to `solve` and `Trixi.solve` with it's default value used in the SciML ecosystem -for ODEs, see the "Miscellaneous" section of the +for ODEs, see the "Miscellaneous" section of the [documentation](https://docs.sciml.ai/DiffEqDocs/stable/basics/common_solver_opts/). # Examples @@ -36,6 +36,16 @@ julia> redirect_stdout(devnull) do ``` """ function trixi_include(mod::Module, elixir::AbstractString; kwargs...) + # Check that all kwargs exist as assignments + code = read(elixir, String) + expr = Meta.parse("begin \n$code \nend") + expr = insert_maxiters(expr) + + for (key, val) in kwargs + # This will throw an error when `key` is not found + find_assignment(expr, key) + end + # Print information on potential wait time only in non-parallel case if !mpi_isparallel() @info "You just called `trixi_include`. Julia may now compile the code, please be patient." @@ -243,6 +253,7 @@ end function find_assignment(expr, destination) # declare result to be able to assign to it in the closure local result + found = false # find explicit and keyword assignments walkexpr(expr) do x @@ -250,12 +261,17 @@ function find_assignment(expr, destination) if (x.head === Symbol("=") || x.head === :kw) && x.args[1] === Symbol(destination) result = x.args[2] + found = true # dump(x) end end return x end + if !found + throw(ArgumentError("assignment `$destination` not found in expression")) + end + result end @@ -274,17 +290,28 @@ function extract_initial_resolution(elixir, kwargs) return initial_refinement_level end catch e - if isa(e, UndefVarError) - # get cells_per_dimension from the elixir - cells_per_dimension = eval(find_assignment(expr, :cells_per_dimension)) - - if haskey(kwargs, :cells_per_dimension) - return kwargs[:cells_per_dimension] - else - return cells_per_dimension + # If `initial_refinement_level` is not found, we will get an `ArgumentError` + if isa(e, ArgumentError) + try + # get cells_per_dimension from the elixir + cells_per_dimension = eval(find_assignment(expr, :cells_per_dimension)) + + if haskey(kwargs, :cells_per_dimension) + return kwargs[:cells_per_dimension] + else + return cells_per_dimension + end + catch e2 + # If `cells_per_dimension` is not found either + if isa(e2, ArgumentError) + throw(ArgumentError("`convergence_test` requires the elixir to define " * + "`initial_refinement_level` or `cells_per_dimension`")) + else + rethrow() + end end else - throw(e) + rethrow() end end end diff --git a/test/test_mpi_p4est_2d.jl b/test/test_mpi_p4est_2d.jl index 1edbce8f6c8..da90537fcfd 100644 --- a/test/test_mpi_p4est_2d.jl +++ b/test/test_mpi_p4est_2d.jl @@ -69,7 +69,10 @@ const EXAMPLES_DIR = pkgdir(Trixi, "examples", "p4est_2d_dgsem") @trixi_testset "elixir_advection_restart.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), l2=[4.507575525876275e-6], - linf=[6.21489667023134e-5]) + linf=[6.21489667023134e-5], + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override=(maxiters = 100_000,)) end @trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" begin diff --git a/test/test_mpi_p4est_3d.jl b/test/test_mpi_p4est_3d.jl index 8082930b3b4..75f43650082 100644 --- a/test/test_mpi_p4est_3d.jl +++ b/test/test_mpi_p4est_3d.jl @@ -63,7 +63,10 @@ const EXAMPLES_DIR = pkgdir(Trixi, "examples", "p4est_3d_dgsem") @trixi_testset "elixir_advection_restart.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), l2=[0.002590388934758452], - linf=[0.01840757696885409]) + linf=[0.01840757696885409], + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override=(maxiters = 100_000,)) end @trixi_testset "elixir_advection_cubed_sphere.jl" begin diff --git a/test/test_p4est_2d.jl b/test/test_p4est_2d.jl index 07c6d02bbcd..db34aecc168 100644 --- a/test/test_p4est_2d.jl +++ b/test/test_p4est_2d.jl @@ -94,7 +94,10 @@ end @trixi_testset "elixir_advection_restart.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), l2=[4.507575525876275e-6], - linf=[6.21489667023134e-5]) + linf=[6.21489667023134e-5], + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override=(maxiters = 100_000,)) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -216,8 +219,8 @@ end ], surface_flux=flux_hlle, tspan=(0.0, 0.3)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) let t = sol.t[end] u_ode = sol.u[end] diff --git a/test/test_p4est_3d.jl b/test/test_p4est_3d.jl index 346c61c7448..f2467f30204 100644 --- a/test/test_p4est_3d.jl +++ b/test/test_p4est_3d.jl @@ -110,7 +110,10 @@ end @trixi_testset "elixir_advection_restart.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), l2=[0.002590388934758452], - linf=[0.01840757696885409]) + linf=[0.01840757696885409], + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override=(maxiters = 100_000,)) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -305,8 +308,8 @@ end ], tspan=(0.0, 0.3), surface_flux=flux_hlle) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) let t = sol.t[end] u_ode = sol.u[end] diff --git a/test/test_special_elixirs.jl b/test/test_special_elixirs.jl index 85671002ba6..ba670a6025e 100644 --- a/test/test_special_elixirs.jl +++ b/test/test_special_elixirs.jl @@ -92,8 +92,7 @@ coverage = occursin("--code-coverage", cmd) && !occursin("--code-coverage=none", # the convergence test logic @test_nowarn_mod convergence_test(@__MODULE__, joinpath(EXAMPLES_DIR, "tree_2d_dgsem", - "elixir_advection_basic.jl"), 2, - tspan = (0.0, 0.01)) + "elixir_advection_basic.jl"), 2) @test_nowarn_mod convergence_test(@__MODULE__, joinpath(EXAMPLES_DIR, "tree_2d_dgsem", "elixir_advection_extended.jl"), 2, @@ -101,12 +100,10 @@ coverage = occursin("--code-coverage", cmd) && !occursin("--code-coverage=none", tspan = (0.0, 0.1)) @test_nowarn_mod convergence_test(@__MODULE__, joinpath(EXAMPLES_DIR, "structured_2d_dgsem", - "elixir_advection_basic.jl"), 2, - tspan = (0.0, 0.01)) + "elixir_advection_basic.jl"), 2) @test_nowarn_mod convergence_test(@__MODULE__, joinpath(EXAMPLES_DIR, "structured_2d_dgsem", - "elixir_advection_coupled.jl"), 2, - tspan = (0.0, 0.01)) + "elixir_advection_coupled.jl"), 2) @test_nowarn_mod convergence_test(@__MODULE__, joinpath(EXAMPLES_DIR, "structured_2d_dgsem", "elixir_advection_extended.jl"), 2, @@ -345,8 +342,8 @@ end end @timed_testset "elixir_euler_ad.jl" begin - @test_trixi_include(joinpath(examples_dir(), "special_elixirs", - "elixir_euler_ad.jl")) + @test_nowarn_mod trixi_include(joinpath(examples_dir(), "special_elixirs", + "elixir_euler_ad.jl")) end end end diff --git a/test/test_structured_2d.jl b/test/test_structured_2d.jl index 17ba001d5ec..96202e00f58 100644 --- a/test/test_structured_2d.jl +++ b/test/test_structured_2d.jl @@ -194,7 +194,10 @@ end @trixi_testset "elixir_advection_restart.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), l2=[4.219208035582454e-6], - linf=[3.438434404412494e-5]) + linf=[3.438434404412494e-5], + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override=(maxiters = 100_000,)) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -211,7 +214,10 @@ end linf=[0.0015194252169410394], rtol=5.0e-5, # Higher tolerance to make tests pass in CI (in particular with macOS) elixir_file="elixir_advection_waving_flag.jl", - restart_file="restart_000021.h5") + restart_file="restart_000021.h5", + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override=(maxiters = 100_000,)) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -227,7 +233,10 @@ end l2=[7.841217436552029e-15], linf=[1.0857981180834031e-13], elixir_file="elixir_advection_free_stream.jl", - restart_file="restart_000036.h5") + restart_file="restart_000036.h5", + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override=(maxiters = 100_000,)) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let diff --git a/test/test_structured_3d.jl b/test/test_structured_3d.jl index 0213e1a9813..a52c459d6be 100644 --- a/test/test_structured_3d.jl +++ b/test/test_structured_3d.jl @@ -62,7 +62,10 @@ end @trixi_testset "elixir_advection_restart.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), l2=[0.0025903889347585777], - linf=[0.018407576968841655]) + linf=[0.018407576968841655], + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override=(maxiters = 100_000,)) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let diff --git a/test/test_threaded.jl b/test/test_threaded.jl index 478c90b476a..dbbcbf4c7ce 100644 --- a/test/test_threaded.jl +++ b/test/test_threaded.jl @@ -208,7 +208,10 @@ end linf=[0.0015194252169410394], rtol=5.0e-5, # Higher tolerance to make tests pass in CI (in particular with macOS) elixir_file="elixir_advection_waving_flag.jl", - restart_file="restart_000021.h5") + restart_file="restart_000021.h5", + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override=(maxiters = 100_000,)) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) diff --git a/test/test_tree_3d_advection.jl b/test/test_tree_3d_advection.jl index 56278629417..ae53a2df52f 100644 --- a/test/test_tree_3d_advection.jl +++ b/test/test_tree_3d_advection.jl @@ -27,7 +27,10 @@ end @trixi_testset "elixir_advection_restart.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), l2=[0.00016017848135651983], - linf=[0.0014175368788298393]) + linf=[0.0014175368788298393], + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override=(maxiters = 100_000,)) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let diff --git a/test/test_trixi.jl b/test/test_trixi.jl index 245efbc0175..cebe2164ae6 100644 --- a/test/test_trixi.jl +++ b/test/test_trixi.jl @@ -33,7 +33,7 @@ macro test_trixi_include(elixir, args...) local kwargs = Pair{Symbol, Any}[] for arg in args if (arg.head == :(=) && - !(arg.args[1] in (:l2, :linf, :atol, :rtol, :coverage_override)) + !(arg.args[1] in (:l2, :linf, :atol, :rtol, :coverage_override, :skip_coverage)) && !(coverage && arg.args[1] in keys(coverage_override))) push!(kwargs, Pair(arg.args...)) end diff --git a/test/test_unit.jl b/test/test_unit.jl index 20368481607..7f774e11d3b 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -1374,6 +1374,103 @@ end @test mesh.boundary_faces[:entire_boundary] == [1, 2] end + +@testset "trixi_include" begin + @trixi_testset "Basic" begin + example = """ + x = 4 + """ + + filename = tempname() + try + open(filename, "w") do file + write(file, example) + end + + # Use `@trixi_testset`, which wraps code in a temporary module, and call + # `trixi_include` with `@__MODULE__` in order to isolate this test. + @test_warn "You just called" trixi_include(@__MODULE__, filename) + @test @isdefined x + @test x == 4 + + @test_warn "You just called" trixi_include(@__MODULE__, filename, x = 7) + @test x == 7 + + @test_throws "assignment `y` not found in expression" trixi_include(@__MODULE__, + filename, + y = 3) + finally + rm(filename, force = true) + end + end + + @trixi_testset "With `solve` Without `maxiters`" begin + # `trixi_include` assumes this to be the `solve` function of OrdinaryDiffEq, + # and therefore tries to insert the kwarg `maxiters`, which will fail here. + example = """ + solve() = 0 + x = solve() + """ + + filename = tempname() + try + open(filename, "w") do file + write(file, example) + end + + # Use `@trixi_testset`, which wraps code in a temporary module, and call + # `trixi_include` with `@__MODULE__` in order to isolate this test. + @test_throws "no method matching solve(; maxiters::Int64)" trixi_include(@__MODULE__, + filename) + + @test_throws "no method matching solve(; maxiters::Int64)" trixi_include(@__MODULE__, + filename, + maxiters = 3) + finally + rm(filename, force = true) + end + end + + @trixi_testset "With `solve` with `maxiters`" begin + # We need another example file that we include with `Base.include` first, in order to + # define the `solve` method without `trixi_include` trying to insert `maxiters` kwargs. + # Then, we can test that `trixi_include` inserts the kwarg in the `solve()` call. + example1 = """ + solve(; maxiters=0) = maxiters + """ + + example2 = """ + x = solve() + """ + + filename1 = tempname() + filename2 = tempname() + try + open(filename1, "w") do file + write(file, example1) + end + open(filename2, "w") do file + write(file, example2) + end + + # Use `@trixi_testset`, which wraps code in a temporary module, and call + # `Base.include` and `trixi_include` with `@__MODULE__` in order to isolate this test. + Base.include(@__MODULE__, filename1) + @test_warn "You just called" trixi_include(@__MODULE__, filename2) + @test @isdefined x + # This is the default `maxiters` inserted by `trixi_include` + @test x == 10^5 + + @test_warn "You just called" trixi_include(@__MODULE__, filename2, + maxiters = 7) + # Test that `maxiters` got overwritten + @test x == 7 + finally + rm(filename1, force = true) + rm(filename2, force = true) + end + end +end end end #module diff --git a/test/test_unstructured_2d.jl b/test/test_unstructured_2d.jl index d4416ac5b6a..5341d86a7d1 100644 --- a/test/test_unstructured_2d.jl +++ b/test/test_unstructured_2d.jl @@ -129,7 +129,10 @@ end 0.005243995459478956, 0.004685630332338153, 0.01750217718347713, - ]) + ], + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override=(maxiters = 100_000,)) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let diff --git a/test/test_visualization.jl b/test/test_visualization.jl index 48164a70fb3..6444dc91d5d 100644 --- a/test/test_visualization.jl +++ b/test/test_visualization.jl @@ -243,7 +243,6 @@ end @test_nowarn_mod trixi_include(@__MODULE__, joinpath(examples_dir(), "tree_2d_dgsem", "elixir_advection_basic.jl"), - tspan = (0, 0.1), analysis_callback = Trixi.TrivialCallback()) @test adapt_to_mesh_level(sol, 5) isa Tuple @@ -259,7 +258,6 @@ end @test_nowarn_mod trixi_include(@__MODULE__, joinpath(examples_dir(), "tree_3d_dgsem", "elixir_advection_basic.jl"), - tspan = (0, 0.1), analysis_callback = Trixi.TrivialCallback(), initial_refinement_level = 1) @test PlotData2D(sol) isa Trixi.PlotData2DCartesian @@ -288,8 +286,7 @@ end @test_nowarn_mod trixi_include(@__MODULE__, joinpath(examples_dir(), "structured_3d_dgsem", - "elixir_advection_basic.jl"), - tspan = (0, 0.1)) + "elixir_advection_basic.jl")) @testset "1D plot from 3D solution and general mesh" begin @testset "Create 1D plot as slice" begin