Path: blob/main/docs/literate/src/files/custom_semidiscretization.jl
5591 views
#src # Custom semidiscretizations12# As described in the [overview section](@ref overview-semidiscretizations),3# semidiscretizations are high-level descriptions of spatial discretizations4# in Trixi.jl. Trixi.jl's main focus is on hyperbolic conservation5# laws represented in a [`SemidiscretizationHyperbolic`](@ref).6# Hyperbolic-parabolic problems based on the advection-diffusion equation or7# the compressible Navier-Stokes equations can be represented in a8# [`SemidiscretizationHyperbolicParabolic`](@ref). This is described in the9# [basic tutorial on parabolic terms](@ref parabolic_terms) and its extension to10# [custom parabolic terms](@ref adding_new_parabolic_terms).11# In this tutorial, we will describe how these semidiscretizations work and how12# they can be used to create custom semidiscretizations involving also other tasks.1314# ## Overview of the right-hand side evaluation1516# The semidiscretizations provided by Trixi.jl are set up to create `ODEProblem`s from the17# [SciML ecosystem for ordinary differential equations](https://diffeq.sciml.ai/latest/).18# In particular, a spatial semidiscretization can be wrapped in an ODE problem19# using [`semidiscretize`](@ref), which returns an `ODEProblem`. This `ODEProblem`20# bundles an initial condition, a right-hand side (RHS) function, the time span,21# and possible parameters. The `ODEProblem`s created by Trixi.jl use the semidiscretization22# passed to [`semidiscretize`](@ref) as a parameter.23# For a [`SemidiscretizationHyperbolic`](@ref), the `ODEProblem` wraps24# `Trixi.rhs!` as ODE RHS.25# For a [`SemidiscretizationHyperbolicParabolic`](@ref), Trixi.jl26# uses a `SplitODEProblem` combining `Trixi.rhs_parabolic!` for the27# (potentially) stiff part and `Trixi.rhs!` for the other part.2829# ## Standard Trixi.jl setup3031# In this tutorial, we will consider the linear advection equation32# with source term33# ```math34# \partial_t u(t,x) + \partial_x u(t,x) = -\exp(-t) \sin\bigl(\pi (x - t) \bigr)35# ```36# with periodic boundary conditions in the domain `[-1, 1]` as a37# model problem.38# The initial condition is39# ```math40# u(0,x) = \sin(\pi x).41# ```42# The source term results in some damping and the analytical solution43# ```math44# u(t,x) = \exp(-t) \sin\bigl(\pi (x - t) \bigr).45# ```46# First, we discretize this equation using the standard functionality47# of Trixi.jl.4849using OrdinaryDiffEqLowStorageRK50using Trixi, Plots5152# The linear scalar advection equation is already implemented in53# Trixi.jl as [`LinearScalarAdvectionEquation1D`](@ref). We construct54# it with an advection velocity `1.0`.5556equations = LinearScalarAdvectionEquation1D(1.0)5758# Next, we use a standard [`DGSEM`](@ref) solver.5960solver = DGSEM(polydeg = 3)6162# We create a simple [`TreeMesh`](@ref) in 1D.6364coordinates_min = (-1.0,)65coordinates_max = (+1.0,)66mesh = TreeMesh(coordinates_min, coordinates_max;67initial_refinement_level = 4,68n_cells_max = 10^4,69periodicity = true)7071# We wrap everything in in a semidiscretization and pass the source72# terms as a standard Julia function. Please note that Trixi.jl uses73# `SVector`s from74# [StaticArrays.jl](https://github.com/JuliaArrays/StaticArrays.jl)75# to store the conserved variables `u`. Thus, the return value of the76# source terms must be wrapped in an `SVector` - even if we consider77# just a scalar problem.7879function initial_condition(x, t, equations)80return SVector(exp(-t) * sinpi(x[1] - t))81end8283function source_terms_standard(u, x, t, equations)84return -initial_condition(x, t, equations)85end8687semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition,88solver;89boundary_conditions = boundary_condition_periodic,90source_terms = source_terms_standard)9192# Now, we can create the `ODEProblem`, solve the resulting ODE93# using a time integration method from94# [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl),95# and visualize the numerical solution at the final time using96# [Plots.jl](https://github.com/JuliaPlots/Plots.jl).9798tspan = (0.0, 3.0)99ode = semidiscretize(semi, tspan)100101sol = solve(ode, RDPK3SpFSAL49(); ode_default_options()...)102103plot(sol; label = "numerical sol.", legend = :topright)104105# We can also plot the analytical solution for comparison.106# Since Trixi.jl uses `SVector`s for the variables, we take their `first`107# (and only) component to get the scalar value for manual plotting.108109let110x = range(-1.0, 1.0; length = 200)111plot!(x, first.(initial_condition.(x, sol.t[end], equations)),112label = "analytical sol.", linestyle = :dash, legend = :topright)113end114115# We can also add the initial condition to the plot.116117plot!(sol.u[1], semi, label = "u0", linestyle = :dot, legend = :topleft)118119# You can of course also use some120# [callbacks](https://trixi-framework.github.io/TrixiDocumentation/stable/callbacks/)121# provided by Trixi.jl as usual.122123summary_callback = SummaryCallback()124analysis_interval = 100125analysis_callback = AnalysisCallback(semi; interval = analysis_interval)126alive_callback = AliveCallback(; analysis_interval)127callbacks = CallbackSet(summary_callback,128analysis_callback,129alive_callback)130131sol = solve(ode, RDPK3SpFSAL49();132ode_default_options()..., callback = callbacks)133134# ## Using a custom ODE right-hand side function135136# Next, we will solve the same problem but use our own ODE RHS function.137# To demonstrate this, we will artificially create a global variable138# containing the current time of the simulation.139140const GLOBAL_TIME = Ref(0.0)141142function source_terms_custom(u, x, t, equations)143t = GLOBAL_TIME[]144return -initial_condition(x, t, equations)145end146147# Next, we create our own RHS function to update the global time of148# the simulation before calling the RHS function from Trixi.jl.149150function rhs_source_custom!(du_ode, u_ode, semi, t)151GLOBAL_TIME[] = t152return Trixi.rhs!(du_ode, u_ode, semi, t)153end154155# Next, we create an `ODEProblem` manually copying over the data from156# the one we got from [`semidiscretize`](@ref) earlier.157158ode_source_custom = ODEProblem(rhs_source_custom!,159ode.u0,160ode.tspan,161ode.p) # semi162sol_source_custom = solve(ode_source_custom, RDPK3SpFSAL49();163ode_default_options()...)164165plot(sol_source_custom; label = "numerical sol.")166let167x = range(-1.0, 1.0; length = 200)168plot!(x, first.(initial_condition.(x, sol_source_custom.t[end], equations)),169label = "analytical sol.", linestyle = :dash, legend = :topleft)170end171plot!(sol_source_custom.u[1], semi, label = "u0", linestyle = :dot, legend = :topleft)172173# This also works with callbacks as usual.174175summary_callback = SummaryCallback()176analysis_interval = 100177analysis_callback = AnalysisCallback(semi; interval = analysis_interval)178alive_callback = AliveCallback(; analysis_interval)179callbacks = CallbackSet(summary_callback,180analysis_callback,181alive_callback)182183sol = solve(ode_source_custom, RDPK3SpFSAL49();184ode_default_options()..., callback = callbacks)185186# ## Setting up a custom semidiscretization187188# Using a global constant is of course not really nice from a software189# engineering point of view. Thus, it can often be useful to collect190# additional data in the parameters of the `ODEProblem`. Thus, it is191# time to create our own semidiscretization. Here, we create a small192# wrapper of a standard semidiscretization of Trixi.jl and the current193# global time of the simulation.194195struct CustomSemidiscretization{Semi, T} <: Trixi.AbstractSemidiscretization196semi::Semi197t::T198end199200semi_custom = CustomSemidiscretization(semi, Ref(0.0))201202# To get pretty printing in the REPL, you can consider specializing203#204# - `Base.show(io::IO, parameters::CustomSemidiscretization)`205# - `Base.show(io::IO, ::MIME"text/plain", parameters::CustomSemidiscretization)`206#207# for your custom semidiscretiation.208209# Next, we create our own source terms that use the global time stored210# in the custom semidiscretiation.211212source_terms_custom_semi = let semi_custom = semi_custom213function source_terms_custom_semi(u, x, t, equations)214t = semi_custom.t[]215return -initial_condition(x, t, equations)216end217end218219# We also create a custom ODE RHS to update the current global time220# stored in the custom semidiscretization. We unpack the standard221# semidiscretization created by Trixi.jl and pass it to `Trixi.rhs!`.222223function rhs_semi_custom!(du_ode, u_ode, semi_custom, t)224semi_custom.t[] = t225return Trixi.rhs!(du_ode, u_ode, semi_custom.semi, t)226end227228# Finally, we set up an `ODEProblem` and solve it numerically.229230ode_semi_custom = ODEProblem(rhs_semi_custom!,231ode.u0,232ode.tspan,233semi_custom)234sol_semi_custom = solve(ode_semi_custom, RDPK3SpFSAL49();235ode_default_options()...)236237# If we want to make use of additional functionality provided by238# Trixi.jl, e.g., for plotting, we need to implement a few additional239# specializations. In this case, we forward everything to the standard240# semidiscretization provided by Trixi.jl wrapped in our custom241# semidiscretization.242243Base.ndims(semi::CustomSemidiscretization) = ndims(semi.semi)244function Trixi.mesh_equations_solver_cache(semi::CustomSemidiscretization)245return Trixi.mesh_equations_solver_cache(semi.semi)246end247248# Now, we can plot the numerical solution as usual.249250plot(sol_semi_custom; label = "numerical sol.")251let252x = range(-1.0, 1.0; length = 200)253plot!(x, first.(initial_condition.(x, sol_semi_custom.t[end], equations)),254label = "analytical sol.", linestyle = :dash, legend = :topleft)255end256plot!(sol_semi_custom.u[1], semi, label = "u0", linestyle = :dot, legend = :topleft)257258# This also works with many callbacks as usual. However, the259# [`AnalysisCallback`](@ref) requires some special handling since it260# makes use of a performance counter contained in the standard261# semidiscretizations of Trixi.jl to report some262# [performance metrics](@ref performance-metrics).263# Here, we forward all accesses to the performance counter to the264# wrapped semidiscretization.265266function Base.getproperty(semi::CustomSemidiscretization, s::Symbol)267if s === :performance_counter268wrapped_semi = getfield(semi, :semi)269wrapped_semi.performance_counter270else271getfield(semi, s)272end273end274275# Moreover, the [`AnalysisCallback`](@ref) also performs some error276# calculations. We also need to forward them to the wrapped277# semidiscretization.278279function Trixi.calc_error_norms(func, u, t, analyzer,280semi::CustomSemidiscretization,281cache_analysis)282return Trixi.calc_error_norms(func, u, t, analyzer,283semi.semi,284cache_analysis)285end286287# Now, we can work with the callbacks used before as usual.288289summary_callback = SummaryCallback()290analysis_interval = 100291analysis_callback = AnalysisCallback(semi_custom;292interval = analysis_interval)293alive_callback = AliveCallback(; analysis_interval)294callbacks = CallbackSet(summary_callback,295analysis_callback,296alive_callback)297298sol = solve(ode_semi_custom, RDPK3SpFSAL49();299ode_default_options()..., callback = callbacks)300301# For even more advanced usage of custom semidiscretizations, you302# may look at the source code of the ones contained in Trixi.jl, e.g.,303# - [`SemidiscretizationHyperbolicParabolic`](@ref)304# - [`SemidiscretizationEulerGravity`](@ref)305# - [`SemidiscretizationEulerAcoustics`](@ref)306# - [`SemidiscretizationCoupled`](@ref)307308# ## Package versions309310# These results were obtained using the following versions.311312using InteractiveUtils313versioninfo()314315using Pkg316Pkg.status(["Trixi", "OrdinaryDiffEqLowStorageRK", "Plots"],317mode = PKGMODE_MANIFEST)318319320