Path: blob/main/src/semidiscretization/semidiscretization_hyperbolic_parabolic.jl
2055 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"""8SemidiscretizationHyperbolicParabolic910A struct containing everything needed to describe a spatial semidiscretization11of a mixed hyperbolic-parabolic conservation law.12"""13struct SemidiscretizationHyperbolicParabolic{Mesh, Equations, EquationsParabolic,14InitialCondition,15BoundaryConditions,16BoundaryConditionsParabolic,17SourceTerms, Solver, SolverParabolic,18Cache, CacheParabolic} <:19AbstractSemidiscretization20mesh::Mesh2122equations::Equations23equations_parabolic::EquationsParabolic2425# This guy is a bit messy since we abuse it as some kind of "exact solution"26# although this doesn't really exist...27initial_condition::InitialCondition2829boundary_conditions::BoundaryConditions30boundary_conditions_parabolic::BoundaryConditionsParabolic3132source_terms::SourceTerms3334solver::Solver35solver_parabolic::SolverParabolic3637cache::Cache38cache_parabolic::CacheParabolic3940performance_counter::PerformanceCounterList{2}4142function SemidiscretizationHyperbolicParabolic{Mesh, Equations, EquationsParabolic,43InitialCondition, BoundaryConditions,44BoundaryConditionsParabolic,45SourceTerms, Solver,46SolverParabolic, Cache,47CacheParabolic}(mesh::Mesh,48equations::Equations,49equations_parabolic::EquationsParabolic,50initial_condition::InitialCondition,51boundary_conditions::BoundaryConditions,52boundary_conditions_parabolic::BoundaryConditionsParabolic,53source_terms::SourceTerms,54solver::Solver,55solver_parabolic::SolverParabolic,56cache::Cache,57cache_parabolic::CacheParabolic) where {58Mesh,59Equations,60EquationsParabolic,61InitialCondition,62BoundaryConditions,63BoundaryConditionsParabolic,64SourceTerms,65Solver,66SolverParabolic,67Cache,68CacheParabolic69}70@assert ndims(mesh) == ndims(equations)7172# Todo: assert nvariables(equations)==nvariables(equations_parabolic)7374performance_counter = PerformanceCounterList{2}(false)7576new(mesh, equations, equations_parabolic, initial_condition,77boundary_conditions, boundary_conditions_parabolic,78source_terms, solver, solver_parabolic, cache, cache_parabolic,79performance_counter)80end81end8283"""84SemidiscretizationHyperbolicParabolic(mesh, both_equations, initial_condition, solver;85solver_parabolic=default_parabolic_solver(),86source_terms=nothing,87both_boundary_conditions=(boundary_condition_periodic, boundary_condition_periodic),88RealT=real(solver),89uEltype=RealT)9091Construct a semidiscretization of a hyperbolic-parabolic PDE.92"""93function SemidiscretizationHyperbolicParabolic(mesh, equations::Tuple,94initial_condition, solver;95solver_parabolic = default_parabolic_solver(),96source_terms = nothing,97boundary_conditions = (boundary_condition_periodic,98boundary_condition_periodic),99# `RealT` is used as real type for node locations etc.100# while `uEltype` is used as element type of solutions etc.101RealT = real(solver), uEltype = RealT)102equations_hyperbolic, equations_parabolic = equations103boundary_conditions_hyperbolic, boundary_conditions_parabolic = boundary_conditions104105return SemidiscretizationHyperbolicParabolic(mesh, equations_hyperbolic,106equations_parabolic,107initial_condition, solver;108solver_parabolic, source_terms,109boundary_conditions = boundary_conditions_hyperbolic,110boundary_conditions_parabolic = boundary_conditions_parabolic,111RealT, uEltype)112end113114function SemidiscretizationHyperbolicParabolic(mesh, equations, equations_parabolic,115initial_condition, solver;116solver_parabolic = default_parabolic_solver(),117source_terms = nothing,118boundary_conditions = boundary_condition_periodic,119boundary_conditions_parabolic = boundary_condition_periodic,120# `RealT` is used as real type for node locations etc.121# while `uEltype` is used as element type of solutions etc.122RealT = real(solver), uEltype = RealT)123cache = create_cache(mesh, equations, solver, RealT, uEltype)124_boundary_conditions = digest_boundary_conditions(boundary_conditions, mesh, solver,125cache)126_boundary_conditions_parabolic = digest_boundary_conditions(boundary_conditions_parabolic,127mesh, solver, cache)128129check_periodicity_mesh_boundary_conditions(mesh, _boundary_conditions)130131cache_parabolic = create_cache_parabolic(mesh, equations, equations_parabolic,132solver, solver_parabolic, RealT,133uEltype)134135SemidiscretizationHyperbolicParabolic{typeof(mesh), typeof(equations),136typeof(equations_parabolic),137typeof(initial_condition),138typeof(_boundary_conditions),139typeof(_boundary_conditions_parabolic),140typeof(source_terms), typeof(solver),141typeof(solver_parabolic), typeof(cache),142typeof(cache_parabolic)}(mesh, equations,143equations_parabolic,144initial_condition,145_boundary_conditions,146_boundary_conditions_parabolic,147source_terms,148solver,149solver_parabolic,150cache,151cache_parabolic)152end153154# Create a new semidiscretization but change some parameters compared to the input.155# `Base.similar` follows a related concept but would require us to `copy` the `mesh`,156# which would impact the performance. Instead, `SciMLBase.remake` has exactly the157# semantics we want to use here. In particular, it allows us to re-use mutable parts,158# e.g. `remake(semi).mesh === semi.mesh`.159function remake(semi::SemidiscretizationHyperbolicParabolic;160uEltype = real(semi.solver),161mesh = semi.mesh,162equations = semi.equations,163equations_parabolic = semi.equations_parabolic,164initial_condition = semi.initial_condition,165solver = semi.solver,166solver_parabolic = semi.solver_parabolic,167source_terms = semi.source_terms,168boundary_conditions = semi.boundary_conditions,169boundary_conditions_parabolic = semi.boundary_conditions_parabolic)170# TODO: Which parts do we want to `remake`? At least the solver needs some171# special care if shock-capturing volume integrals are used (because of172# the indicators and their own caches...).173SemidiscretizationHyperbolicParabolic(mesh, equations, equations_parabolic,174initial_condition, solver; solver_parabolic,175source_terms, boundary_conditions,176boundary_conditions_parabolic, uEltype)177end178179function Base.show(io::IO, semi::SemidiscretizationHyperbolicParabolic)180@nospecialize semi # reduce precompilation time181182print(io, "SemidiscretizationHyperbolicParabolic(")183print(io, semi.mesh)184print(io, ", ", semi.equations)185print(io, ", ", semi.equations_parabolic)186print(io, ", ", semi.initial_condition)187print(io, ", ", semi.boundary_conditions)188print(io, ", ", semi.boundary_conditions_parabolic)189print(io, ", ", semi.source_terms)190print(io, ", ", semi.solver)191print(io, ", ", semi.solver_parabolic)192print(io, ", cache(")193for (idx, key) in enumerate(keys(semi.cache))194idx > 1 && print(io, " ")195print(io, key)196end197print(io, "))")198end199200function Base.show(io::IO, ::MIME"text/plain",201semi::SemidiscretizationHyperbolicParabolic)202@nospecialize semi # reduce precompilation time203204if get(io, :compact, false)205show(io, semi)206else207summary_header(io, "SemidiscretizationHyperbolicParabolic")208summary_line(io, "#spatial dimensions", ndims(semi.equations))209summary_line(io, "mesh", semi.mesh)210summary_line(io, "hyperbolic equations", semi.equations |> typeof |> nameof)211summary_line(io, "parabolic equations",212semi.equations_parabolic |> typeof |> nameof)213summary_line(io, "initial condition", semi.initial_condition)214215# print_boundary_conditions(io, semi)216217summary_line(io, "source terms", semi.source_terms)218summary_line(io, "solver", semi.solver |> typeof |> nameof)219summary_line(io, "parabolic solver", semi.solver_parabolic |> typeof |> nameof)220summary_line(io, "total #DOFs per field", ndofsglobal(semi))221summary_footer(io)222end223end224225@inline Base.ndims(semi::SemidiscretizationHyperbolicParabolic) = ndims(semi.mesh)226227@inline function nvariables(semi::SemidiscretizationHyperbolicParabolic)228nvariables(semi.equations)229end230231@inline Base.real(semi::SemidiscretizationHyperbolicParabolic) = real(semi.solver)232233# retain dispatch on hyperbolic equations only234@inline function mesh_equations_solver_cache(semi::SemidiscretizationHyperbolicParabolic)235@unpack mesh, equations, solver, cache = semi236return mesh, equations, solver, cache237end238239function calc_error_norms(func, u_ode, t, analyzer,240semi::SemidiscretizationHyperbolicParabolic, cache_analysis)241@unpack mesh, equations, initial_condition, solver, cache = semi242u = wrap_array(u_ode, mesh, equations, solver, cache)243244calc_error_norms(func, u, t, analyzer, mesh, equations, initial_condition, solver,245cache, cache_analysis)246end247248function compute_coefficients(t, semi::SemidiscretizationHyperbolicParabolic)249# Call `compute_coefficients` in `src/semidiscretization/semidiscretization.jl`250compute_coefficients(semi.initial_condition, t, semi)251end252253function compute_coefficients!(u_ode, t, semi::SemidiscretizationHyperbolicParabolic)254compute_coefficients!(u_ode, semi.initial_condition, t, semi)255end256257# Required for storing `extra_node_variables` in the `SaveSolutionCallback`.258# Not to be confused with `get_node_vars` which returns the variables of the simulated equation.259function get_node_variables!(node_variables, u_ode,260semi::SemidiscretizationHyperbolicParabolic)261get_node_variables!(node_variables, u_ode, mesh_equations_solver_cache(semi)...,262semi.equations_parabolic, semi.cache_parabolic)263end264265"""266semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan)267268Wrap the semidiscretization `semi` as a split ODE problem in the time interval `tspan`269that can be passed to `solve` from the [SciML ecosystem](https://diffeq.sciml.ai/latest/).270The parabolic right-hand side is the first function of the split ODE problem and271will be used by default by the implicit part of IMEX methods from the272SciML ecosystem.273"""274function semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan;275reset_threads = true)276# Optionally reset Polyester.jl threads. See277# https://github.com/trixi-framework/Trixi.jl/issues/1583278# https://github.com/JuliaSIMD/Polyester.jl/issues/30279if reset_threads280Polyester.reset_threads!()281end282283u0_ode = compute_coefficients(first(tspan), semi)284# TODO: MPI, do we want to synchronize loading and print debug statements, e.g. using285# mpi_isparallel() && MPI.Barrier(mpi_comm())286# See https://github.com/trixi-framework/Trixi.jl/issues/328287iip = true # is-inplace, i.e., we modify a vector when calling rhs_parabolic!, rhs!288# Note that the IMEX time integration methods of OrdinaryDiffEq.jl treat the289# first function implicitly and the second one explicitly. Thus, we pass the290# stiffer parabolic function first.291return SplitODEProblem{iip}(rhs_parabolic!, rhs!, u0_ode, tspan, semi)292end293294"""295semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan,296restart_file::AbstractString)297298Wrap the semidiscretization `semi` as a split ODE problem in the time interval `tspan`299that can be passed to `solve` from the [SciML ecosystem](https://diffeq.sciml.ai/latest/).300The parabolic right-hand side is the first function of the split ODE problem and301will be used by default by the implicit part of IMEX methods from the302SciML ecosystem.303304The initial condition etc. is taken from the `restart_file`.305"""306function semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan,307restart_file::AbstractString;308reset_threads = true)309# Optionally reset Polyester.jl threads. See310# https://github.com/trixi-framework/Trixi.jl/issues/1583311# https://github.com/JuliaSIMD/Polyester.jl/issues/30312if reset_threads313Polyester.reset_threads!()314end315316u0_ode = load_restart_file(semi, restart_file)317# TODO: MPI, do we want to synchronize loading and print debug statements, e.g. using318# mpi_isparallel() && MPI.Barrier(mpi_comm())319# See https://github.com/trixi-framework/Trixi.jl/issues/328320iip = true # is-inplace, i.e., we modify a vector when calling rhs_parabolic!, rhs!321# Note that the IMEX time integration methods of OrdinaryDiffEq.jl treat the322# first function implicitly and the second one explicitly. Thus, we pass the323# stiffer parabolic function first.324return SplitODEProblem{iip}(rhs_parabolic!, rhs!, u0_ode, tspan, semi)325end326327function rhs!(du_ode, u_ode, semi::SemidiscretizationHyperbolicParabolic, t)328@unpack mesh, equations, boundary_conditions, source_terms, solver, cache = semi329330u = wrap_array(u_ode, mesh, equations, solver, cache)331du = wrap_array(du_ode, mesh, equations, solver, cache)332333# TODO: Taal decide, do we need to pass the mesh?334time_start = time_ns()335@trixi_timeit timer() "rhs!" rhs!(du, u, t, mesh, equations,336boundary_conditions, source_terms, solver, cache)337runtime = time_ns() - time_start338put!(semi.performance_counter.counters[1], runtime)339340return nothing341end342343function rhs_parabolic!(du_ode, u_ode, semi::SemidiscretizationHyperbolicParabolic, t)344@unpack mesh, equations_parabolic, boundary_conditions_parabolic, source_terms, solver, solver_parabolic, cache, cache_parabolic = semi345346u = wrap_array(u_ode, mesh, equations_parabolic, solver, cache_parabolic)347du = wrap_array(du_ode, mesh, equations_parabolic, solver, cache_parabolic)348349# TODO: Taal decide, do we need to pass the mesh?350time_start = time_ns()351@trixi_timeit timer() "parabolic rhs!" rhs_parabolic!(du, u, t, mesh,352equations_parabolic,353boundary_conditions_parabolic,354source_terms,355solver, solver_parabolic,356cache, cache_parabolic)357runtime = time_ns() - time_start358put!(semi.performance_counter.counters[2], runtime)359360return nothing361end362363function _jacobian_ad_forward(semi::SemidiscretizationHyperbolicParabolic, t0, u0_ode,364du_ode, config)365new_semi = remake(semi, uEltype = eltype(config))366367du_ode_hyp = Vector{eltype(config)}(undef, length(du_ode))368J = ForwardDiff.jacobian(du_ode, u0_ode, config) do du_ode, u_ode369# Implementation of split ODE problem in OrdinaryDiffEq370rhs!(du_ode_hyp, u_ode, new_semi, t0)371rhs_parabolic!(du_ode, u_ode, new_semi, t0)372du_ode .+= du_ode_hyp373end374375return J376end377378"""379jacobian_ad_forward_parabolic(semi::SemidiscretizationHyperbolicParabolic;380t0=zero(real(semi)),381u0_ode=compute_coefficients(t0, semi))382383Uses the *parabolic part* of the right-hand side operator of the [`SemidiscretizationHyperbolicParabolic`](@ref) `semi`384and forward mode automatic differentiation to compute the Jacobian `J` of the385parabolic/diffusive contribution only at time `t0` and state `u0_ode`.386387This might be useful for operator-splitting methods, e.g., the construction of optimized388time integrators which optimize different methods for the hyperbolic and parabolic part separately.389"""390function jacobian_ad_forward_parabolic(semi::SemidiscretizationHyperbolicParabolic;391t0 = zero(real(semi)),392u0_ode = compute_coefficients(t0, semi))393jacobian_ad_forward_parabolic(semi, t0, u0_ode)394end395396# The following version is for plain arrays397function jacobian_ad_forward_parabolic(semi::SemidiscretizationHyperbolicParabolic,398t0, u0_ode)399du_ode = similar(u0_ode)400config = ForwardDiff.JacobianConfig(nothing, du_ode, u0_ode)401402# Use a function barrier since the generation of the `config` we use above403# is not type-stable404_jacobian_ad_forward_parabolic(semi, t0, u0_ode, du_ode, config)405end406407function _jacobian_ad_forward_parabolic(semi, t0, u0_ode, du_ode, config)408new_semi = remake(semi, uEltype = eltype(config))409# Create anonymous function passed as first argument to `ForwardDiff.jacobian` to match410# `ForwardDiff.jacobian(f!, y::AbstractArray, x::AbstractArray,411# cfg::JacobianConfig = JacobianConfig(f!, y, x), check=Val{true}())`412J = ForwardDiff.jacobian(du_ode, u0_ode, config) do du_ode, u_ode413Trixi.rhs_parabolic!(du_ode, u_ode, new_semi, t0)414end415416return J417end418end # @muladd419420421