Path: blob/main/docs/literate/src/files/adding_new_parabolic_terms.jl
5591 views
#src # Adding new parabolic terms.12# This demo illustrates the steps involved in adding new parabolic terms for the scalar3# advection equation. In particular, we will add an anisotropic diffusion. We begin by4# defining the hyperbolic (advection) part of the advection-diffusion equation.56using OrdinaryDiffEqLowStorageRK7using Trixi89advection_velocity = (1.0, 1.0)10equations_hyperbolic = LinearScalarAdvectionEquation2D(advection_velocity);1112# ## Define a new parabolic equation type13#14# Next, we define a 2D parabolic diffusion term type. This is similar to [`LaplaceDiffusion2D`](@ref)15# except that the `diffusivity` field refers to a spatially constant diffusivity matrix now. Note that16# `ConstantAnisotropicDiffusion2D` has a field for `equations_hyperbolic`. It is useful to have17# information about the hyperbolic system available to the parabolic part so that we can reuse18# functions defined for hyperbolic equations (such as `varnames`).19#20# The abstract type `Trixi.AbstractEquationsParabolic` has three parameters: `NDIMS` (the spatial dimension,21# e.g., 1D, 2D, or 3D), `NVARS` (the number of variables), and `GradientVariable`, which we set as22# `GradientVariablesConservative`. This indicates that the gradient should be taken with respect to the23# conservative variables (e.g., the same variables used in `equations_hyperbolic`). Users can also take24# the gradient with respect to a different set of variables; see, for example, the implementation of25# [`CompressibleNavierStokesDiffusion2D`](@ref), which can utilize either "primitive" or "entropy" variables.2627struct ConstantAnisotropicDiffusion2D{E, T} <:28Trixi.AbstractEquationsParabolic{2, 1, GradientVariablesConservative}29diffusivity::T30equations_hyperbolic::E31end3233function varnames(variable_mapping, equations_parabolic::ConstantAnisotropicDiffusion2D)34return varnames(variable_mapping, equations_parabolic.equations_hyperbolic)35end3637# Next, we define the parabolic flux function. We assume that the mixed hyperbolic-parabolic system38# is of the form39# ```math40# \partial_t u(t,x) + \partial_x (f_1(u) - g_1(u, \nabla u))41# + \partial_y (f_2(u) - g_2(u, \nabla u)) = 042# ```43# where ``f_1(u)``, ``f_2(u)`` are the hyperbolic fluxes and ``g_1(u, \nabla u)``, ``g_2(u, \nabla u)`` denote44# the parabolic fluxes. For anisotropic diffusion, the parabolic fluxes are the first and second components45# of the matrix-vector product involving `diffusivity` and the gradient vector.46#47# Here, we specialize the flux to our new parabolic equation type `ConstantAnisotropicDiffusion2D`.4849function Trixi.flux(u, gradients, orientation::Integer,50equations_parabolic::ConstantAnisotropicDiffusion2D)51@unpack diffusivity = equations_parabolic52dudx, dudy = gradients53if orientation == 154return SVector(diffusivity[1, 1] * dudx + diffusivity[1, 2] * dudy)55else # if orientation == 256return SVector(diffusivity[2, 1] * dudx + diffusivity[2, 2] * dudy)57end58end5960# ## Defining boundary conditions6162# Trixi.jl's implementation of parabolic terms discretizes both the gradient and divergence63# using weak formulation. In other words, we discretize the system64# ```math65# \begin{aligned}66# \bm{q} &= \nabla u \\67# \bm{\sigma} &= \begin{pmatrix} g_1(u, \bm{q}) \\ g_2(u, \bm{q}) \end{pmatrix} \\68# \text{parabolic contribution} &= \nabla \cdot \bm{\sigma}69# \end{aligned}70# ```71#72# Boundary data must be specified for all spatial derivatives, e.g., for both the gradient73# equation ``\bm{q} = \nabla u`` and the divergence of the parabolic flux74# ``\nabla \cdot \bm{\sigma}``. We account for this by introducing internal `Gradient`75# and `Divergence` types which are used to dispatch on each type of boundary condition.76#77# As an example, let us introduce a Dirichlet boundary condition with constant boundary data.7879struct BoundaryConditionConstantDirichlet{T <: Real}80boundary_value::T81end8283# This boundary condition contains only the field `boundary_value`, which we assume to be some84# real-valued constant which we will impose as the Dirichlet data on the boundary.85#86# Boundary conditions have generally been defined as "callable structs" (also known as "functors").87# For each boundary condition, we need to specify the appropriate boundary data to return for both88# the `Gradient` and `Divergence`. Since the gradient is operating on the solution `u`, the boundary89# data should be the value of `u`, and we can directly impose Dirichlet data.9091@inline function (boundary_condition::BoundaryConditionConstantDirichlet)(flux_inner,92u_inner,93normal::AbstractVector,94x, t,95operator_type::Trixi.Gradient,96equations_parabolic::ConstantAnisotropicDiffusion2D)97return boundary_condition.boundary_value98end99100# While the gradient acts on the solution `u`, the divergence acts on the parabolic flux ``\bm{\sigma}``.101# Thus, we have to supply boundary data for the `Divergence` operator that corresponds to ``\bm{\sigma}``.102# However, we've already imposed boundary data on `u` for a Dirichlet boundary condition, and imposing103# boundary data for ``\bm{\sigma}`` might overconstrain our problem.104#105# Thus, for the `Divergence` boundary data under a Dirichlet boundary condition, we simply return106# `flux_inner`, which is boundary data for ``\bm{\sigma}`` computed using the "inner" or interior solution.107# This way, we supply boundary data for the divergence operation without imposing any additional conditions.108109@inline function (boundary_condition::BoundaryConditionConstantDirichlet)(flux_inner,110u_inner,111normal::AbstractVector,112x, t,113operator_type::Trixi.Divergence,114equations_parabolic::ConstantAnisotropicDiffusion2D)115return flux_inner116end117118# ### A note on the choice of gradient variables119#120# It is often simpler to transform the solution variables (and solution gradients) to another set of121# variables prior to computing the parabolic fluxes (see [`CompressibleNavierStokesDiffusion2D`](@ref)122# for an example of this). If this is done, then the boundary condition for the `Gradient` operator123# should be modified accordingly as well.124#125# ## Putting things together126#127# Finally, we can instantiate our new parabolic equation type, define boundary conditions,128# and run a simulation. The specific anisotropic diffusion matrix we use produces more129# dissipation in the direction ``(1, -1)`` as an isotropic diffusion.130#131# For boundary conditions, we impose that ``u=1`` on the left wall, ``u=2`` on the bottom132# wall, and ``u = 0`` on the outflow walls. The initial condition is taken to be ``u = 0``.133# Note that we use `BoundaryConditionConstantDirichlet` only for the parabolic boundary134# conditions, since we have not defined its behavior for the hyperbolic part.135136using Trixi: SMatrix137diffusivity = 5.0e-2 * SMatrix{2, 2}([2 -1; -1 2])138equations_parabolic = ConstantAnisotropicDiffusion2D(diffusivity, equations_hyperbolic);139140boundary_conditions_hyperbolic = (;141x_neg = BoundaryConditionDirichlet((x, t, equations) -> SVector(1.0)),142y_neg = BoundaryConditionDirichlet((x, t, equations) -> SVector(2.0)),143y_pos = boundary_condition_do_nothing,144x_pos = boundary_condition_do_nothing)145146boundary_conditions_parabolic = (; x_neg = BoundaryConditionConstantDirichlet(1.0),147y_neg = BoundaryConditionConstantDirichlet(2.0),148y_pos = BoundaryConditionConstantDirichlet(0.0),149x_pos = BoundaryConditionConstantDirichlet(0.0));150151solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs)152coordinates_min = (-1.0, -1.0) # minimum coordinates (min(x), min(y))153coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y))154mesh = TreeMesh(coordinates_min, coordinates_max,155initial_refinement_level = 4,156periodicity = false, n_cells_max = 30_000) # set maximum capacity of tree data structure157158initial_condition = (x, t, equations) -> SVector(0.0)159160semi = SemidiscretizationHyperbolicParabolic(mesh,161(equations_hyperbolic, equations_parabolic),162initial_condition, solver;163boundary_conditions = (boundary_conditions_hyperbolic,164boundary_conditions_parabolic))165166tspan = (0.0, 2.0)167ode = semidiscretize(semi, tspan)168callbacks = CallbackSet(SummaryCallback())169time_int_tol = 1.0e-6170sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol,171ode_default_options()..., callback = callbacks);172println("Number of timesteps: ", sol.destats.naccept)173174using Plots175plot(sol)176177# ### Enabling CFL-based timestepping178179# In the example above, we used an adaptive timestep based on truncation error estimates.180# Alternatively, we can also use a CFL-based timestep control, cf. [`StepsizeCallback`](@ref).181# To be able to do so, we need to define [`max_diffusivity`](@ref) and182# [`have_constant_diffusivity`](@ref) for the new parabolic terms.183# In Trixi.jl, currently only the standard Laplace Diffusion and Compressible Navier-Stokes-Fourier184# parabolic terms are implemented.185# Since these equations have **isotropic** diffusivity, i.e., direction-independent coefficients,186# [`max_diffusivity`](@ref) is expected to return a scalar value.187#188# To comply with the existing code, we thus also return a scalar value for our anisotropic diffusion,189# estimated by the spectral radius (largest in magnitude eigenvalue) of the diffusivity matrix.190# Since diffusivity is constant, we do not need to repeatedly compute the spectral radius.191using LinearAlgebra: eigvals192lambda_max() = maximum(abs.(eigvals(diffusivity)))193194# This function indicates that the diffusivity is constant, i.e.,195# does not depend on the solution `u` and thus needs not to be recomputed at every node.196@inline function Trixi.have_constant_diffusivity(::ConstantAnisotropicDiffusion2D)197return Trixi.True()198end199200# Return the estimated maximum diffusivity for CFL calculations based on201# the spectral radius of the diffusivity matrix computed above202@inline function Trixi.max_diffusivity(equations_parabolic::ConstantAnisotropicDiffusion2D)203return lambda_max()204end205206# We now supply the hyperbolic and parabolic CFL numbers207cfl_hyperbolic = 2.0 # Not restrictive for this example208cfl_parabolic = 0.21 # Restricts the timestep209stepsize_callback = StepsizeCallback(cfl = cfl_hyperbolic,210cfl_parabolic = cfl_parabolic)211212# Add the stepsize callback to the existing callbacks213callbacks = CallbackSet(SummaryCallback(), stepsize_callback);214# Turn off adaptive time stepping based on error estimates215sol = solve(ode, RDPK3SpFSAL49();216adaptive = false, dt = stepsize_callback(ode),217ode_default_options()..., callback = callbacks);218println("Number of timesteps: ", sol.destats.naccept)219220plot(sol)221222# ## Package versions223224# These results were obtained using the following versions.225226using InteractiveUtils227versioninfo()228229using Pkg230Pkg.status(["Trixi", "OrdinaryDiffEqLowStorageRK", "Plots"],231mode = PKGMODE_MANIFEST)232233234