Path: blob/main/src/equations/compressible_navier_stokes_2d.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@doc raw"""8CompressibleNavierStokesDiffusion2D(equations; mu, Pr,9gradient_variables=GradientVariablesPrimitive())1011Contains the diffusion (i.e. parabolic) terms applied12to mass, momenta, and total energy together with the advective terms from13the [`CompressibleEulerEquations2D`](@ref).1415- `equations`: instance of the [`CompressibleEulerEquations2D`](@ref)16- `mu`: dynamic viscosity,17- `Pr`: Prandtl number,18- `gradient_variables`: which variables the gradients are taken with respect to.19Defaults to [`GradientVariablesPrimitive()`](@ref).20For an entropy stable formulation, use [`GradientVariablesEntropy()`](@ref).2122Fluid properties such as the dynamic viscosity ``\mu`` can be provided in any consistent unit system, e.g.,23[``\mu``] = kg m⁻¹ s⁻¹.24The viscosity ``\mu`` may be a constant or a function of the current state, e.g.,25depending on temperature (Sutherland's law): ``\mu = \mu(T)``.26In the latter case, the function `mu` needs to have the signature `mu(u, equations)`.2728The particular form of the compressible Navier-Stokes implemented is29```math30\frac{\partial}{\partial t}31\begin{pmatrix}32\rho \\ \rho \mathbf{v} \\ \rho e33\end{pmatrix}34+35\nabla \cdot36\begin{pmatrix}37\rho \mathbf{v} \\ \rho \mathbf{v}\mathbf{v}^T + p \underline{I} \\ (\rho e + p) \mathbf{v}38\end{pmatrix}39=40\nabla \cdot41\begin{pmatrix}420 \\ \underline{\tau} \\ \underline{\tau}\mathbf{v} - \mathbf{q}43\end{pmatrix}44```45where the system is closed with the ideal gas assumption giving46```math47p = (\gamma - 1) \left( \rho e - \frac{1}{2} \rho (v_1^2+v_2^2) \right)48```49as the pressure. The value of the adiabatic constant `gamma` is taken from the [`CompressibleEulerEquations2D`](@ref).50The terms on the right hand side of the system above51are built from the viscous stress tensor52```math53\underline{\tau} = \mu \left(\nabla\mathbf{v} + \left(\nabla\mathbf{v}\right)^T\right) - \frac{2}{3} \mu \left(\nabla\cdot\mathbf{v}\right)\underline{I}54```55where ``\underline{I}`` is the ``2\times 2`` identity matrix and the heat flux is56```math57\mathbf{q} = -\kappa\nabla\left(T\right),\quad T = \frac{p}{R\rho}58```59where ``T`` is the temperature and ``\kappa`` is the thermal conductivity for Fick's law.60Under the assumption that the gas has a constant Prandtl number,61the thermal conductivity is62```math63\kappa = \frac{\gamma \mu R}{(\gamma - 1)\textrm{Pr}}.64```65From this combination of temperature ``T`` and thermal conductivity ``\kappa`` we see66that the gas constant `R` cancels and the heat flux becomes67```math68\mathbf{q} = -\kappa\nabla\left(T\right) = -\frac{\gamma \mu}{(\gamma - 1)\textrm{Pr}}\nabla\left(\frac{p}{\rho}\right)69```70which is the form implemented below in the [`flux`](@ref) function.7172In two spatial dimensions we require gradients for three quantities, e.g.,73primitive quantities74```math75\nabla v_1,\, \nabla v_2,\, \nabla T76```77or the entropy variables78```math79\nabla w_2,\, \nabla w_3,\, \nabla w_480```81where82```math83w_2 = \frac{\rho v_1}{p},\, w_3 = \frac{\rho v_2}{p},\, w_4 = -\frac{\rho}{p}84```85"""86struct CompressibleNavierStokesDiffusion2D{GradientVariables, RealT <: Real, Mu,87E <: AbstractCompressibleEulerEquations{2}} <:88AbstractCompressibleNavierStokesDiffusion{2, 4, GradientVariables}89# TODO: parabolic90# 1) For now save gamma and inv(gamma-1) again, but could potentially reuse them from the Euler equations91# 2) Add NGRADS as a type parameter here and in AbstractEquationsParabolic, add `ngradients(...)` accessor function92gamma::RealT # ratio of specific heats93inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications9495mu::Mu # viscosity96Pr::RealT # Prandtl number97kappa::RealT # thermal diffusivity for Fick's law9899equations_hyperbolic::E # CompressibleEulerEquations2D100gradient_variables::GradientVariables # GradientVariablesPrimitive or GradientVariablesEntropy101end102103# default to primitive gradient variables104function CompressibleNavierStokesDiffusion2D(equations::CompressibleEulerEquations2D;105mu, Prandtl,106gradient_variables = GradientVariablesPrimitive())107gamma = equations.gamma108inv_gamma_minus_one = equations.inv_gamma_minus_one109110# Under the assumption of constant Prandtl number the thermal conductivity111# constant is kappa = gamma μ / ((gamma-1) Prandtl).112# Important note! Factor of μ is accounted for later in `flux`.113# This avoids recomputation of kappa for non-constant μ.114kappa = gamma * inv_gamma_minus_one / Prandtl115116CompressibleNavierStokesDiffusion2D{typeof(gradient_variables), typeof(gamma),117typeof(mu),118typeof(equations)}(gamma, inv_gamma_minus_one,119mu, Prandtl, kappa,120equations,121gradient_variables)122end123124# TODO: parabolic125# This is the flexibility a user should have to select the different gradient variable types126# varnames(::typeof(cons2prim) , ::CompressibleNavierStokesDiffusion2D) = ("v1", "v2", "T")127# varnames(::typeof(cons2entropy), ::CompressibleNavierStokesDiffusion2D) = ("w2", "w3", "w4")128129function varnames(variable_mapping,130equations_parabolic::CompressibleNavierStokesDiffusion2D)131varnames(variable_mapping, equations_parabolic.equations_hyperbolic)132end133134# we specialize this function to compute gradients of primitive variables instead of135# conservative variables.136function gradient_variable_transformation(::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive})137cons2prim138end139function gradient_variable_transformation(::CompressibleNavierStokesDiffusion2D{GradientVariablesEntropy})140cons2entropy141end142143# Explicit formulas for the diffusive Navier-Stokes fluxes are available, e.g., in Section 2144# of the paper by Rueda-Ramírez, Hennemann, Hindenlang, Winters, and Gassner145# "An Entropy Stable Nodal Discontinuous Galerkin Method for the resistive146# MHD Equations. Part II: Subcell Finite Volume Shock Capturing"147# where one sets the magnetic field components equal to 0.148function flux(u, gradients, orientation::Integer,149equations::CompressibleNavierStokesDiffusion2D)150# Here, `u` is assumed to be the "transformed" variables specified by `gradient_variable_transformation`.151_, v1, v2, _ = convert_transformed_to_primitive(u, equations)152# Here `gradients` is assumed to contain the gradients of the primitive variables (rho, v1, v2, T)153# either computed directly or reverse engineered from the gradient of the entropy variables154# by way of the `convert_gradient_variables` function.155_, dv1dx, dv2dx, dTdx = convert_derivative_to_primitive(u, gradients[1], equations)156_, dv1dy, dv2dy, dTdy = convert_derivative_to_primitive(u, gradients[2], equations)157158# Components of viscous stress tensor159160# (4 * (v1)_x / 3 - 2 * (v2)_y / 3)161tau_11 = 4 * dv1dx / 3 - 2 * dv2dy / 3162# ((v1)_y + (v2)_x)163# stress tensor is symmetric164tau_12 = dv1dy + dv2dx # = tau_21165# (4/3 * (v2)_y - 2/3 * (v1)_x)166tau_22 = 4 * dv2dy / 3 - 2 * dv1dx / 3167168# Fick's law q = -kappa * grad(T) = -kappa * grad(p / (R rho))169# with thermal diffusivity constant kappa = gamma μ R / ((gamma-1) Pr)170# Note, the gas constant cancels under this formulation, so it is not present171# in the implementation172q1 = equations.kappa * dTdx173q2 = equations.kappa * dTdy174175# In the simplest cases, the user passed in `mu` or `mu()`176# (which returns just a constant) but177# more complex functions like Sutherland's law are possible.178# `dynamic_viscosity` is a helper function that handles both cases179# by dispatching on the type of `equations.mu`.180mu = dynamic_viscosity(u, equations)181182if orientation == 1183# viscous flux components in the x-direction184f1 = 0185f2 = tau_11 * mu186f3 = tau_12 * mu187f4 = (v1 * tau_11 + v2 * tau_12 + q1) * mu188189return SVector(f1, f2, f3, f4)190else # if orientation == 2191# viscous flux components in the y-direction192# Note, symmetry is exploited for tau_12 = tau_21193g1 = 0194g2 = tau_12 * mu # tau_21 * mu195g3 = tau_22 * mu196g4 = (v1 * tau_12 + v2 * tau_22 + q2) * mu197198return SVector(g1, g2, g3, g4)199end200end201202# Convert conservative variables to primitive203@inline function cons2prim(u, equations::CompressibleNavierStokesDiffusion2D)204rho, rho_v1, rho_v2, _ = u205206v1 = rho_v1 / rho207v2 = rho_v2 / rho208T = temperature(u, equations)209210return SVector(rho, v1, v2, T)211end212213# Convert conservative variables to entropy214# TODO: parabolic. We can improve efficiency by not computing w_1, which involves logarithms215# This can be done by specializing `cons2entropy` and `entropy2cons` to `CompressibleNavierStokesDiffusion2D`,216# but this may be confusing to new users.217function cons2entropy(u, equations::CompressibleNavierStokesDiffusion2D)218cons2entropy(u, equations.equations_hyperbolic)219end220function entropy2cons(w, equations::CompressibleNavierStokesDiffusion2D)221entropy2cons(w, equations.equations_hyperbolic)222end223224# the `flux` function takes in transformed variables `u` which depend on the type of the gradient variables.225# For CNS, it is simplest to formulate the viscous terms in primitive variables, so we transform the transformed226# variables into primitive variables.227@inline function convert_transformed_to_primitive(u_transformed,228equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive})229return u_transformed230end231232# TODO: parabolic. Make this more efficient!233@inline function convert_transformed_to_primitive(u_transformed,234equations::CompressibleNavierStokesDiffusion2D{GradientVariablesEntropy})235# note: this uses CompressibleNavierStokesDiffusion2D versions of cons2prim and entropy2cons236return cons2prim(entropy2cons(u_transformed, equations), equations)237end238239# Takes the solution values `u` and gradient of the entropy variables (w_2, w_3, w_4) and240# reverse engineers the gradients to be terms of the primitive variables (v1, v2, T).241# Helpful because then the diffusive fluxes have the same form as on paper.242# Note, the first component of `gradient_entropy_vars` contains gradient(rho) which is unused.243# TODO: parabolic; entropy stable viscous terms244@inline function convert_derivative_to_primitive(u, gradient,245::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive})246return gradient247end248249# the first argument is always the "transformed" variables.250@inline function convert_derivative_to_primitive(w, gradient_entropy_vars,251equations::CompressibleNavierStokesDiffusion2D{GradientVariablesEntropy})252253# TODO: parabolic. This is inefficient to pass in transformed variables but then transform them back.254# We can fix this if we directly compute v1, v2, T from the entropy variables255u = entropy2cons(w, equations) # calls a "modified" entropy2cons defined for CompressibleNavierStokesDiffusion2D256rho, rho_v1, rho_v2, _ = u257258v1 = rho_v1 / rho259v2 = rho_v2 / rho260T = temperature(u, equations)261262return SVector(gradient_entropy_vars[1],263T * (gradient_entropy_vars[2] + v1 * gradient_entropy_vars[4]), # grad(u) = T*(grad(w_2)+v1*grad(w_4))264T * (gradient_entropy_vars[3] + v2 * gradient_entropy_vars[4]), # grad(v) = T*(grad(w_3)+v2*grad(w_4))265T * T * gradient_entropy_vars[4])266end267268# This routine is required because `prim2cons` is called in `initial_condition`, which269# is called with `equations::CompressibleEulerEquations2D`. This means it is inconsistent270# with `cons2prim(..., ::CompressibleNavierStokesDiffusion2D)` as defined above.271# TODO: parabolic. Is there a way to clean this up?272@inline function prim2cons(u, equations::CompressibleNavierStokesDiffusion2D)273prim2cons(u, equations.equations_hyperbolic)274end275276@inline function temperature(u, equations::CompressibleNavierStokesDiffusion2D)277rho, rho_v1, rho_v2, rho_e = u278279p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2) / rho)280T = p / rho281return T282end283284@inline function velocity(u, equations::CompressibleNavierStokesDiffusion2D)285rho = u[1]286v1 = u[2] / rho287v2 = u[3] / rho288return SVector(v1, v2)289end290291@doc raw"""292enstrophy(u, gradients, equations::CompressibleNavierStokesDiffusion2D)293294Computes the (node-wise) enstrophy, defined as295```math296\mathcal{E} = \frac{1}{2} \rho \omega \cdot \omega297```298where ``\omega = \nabla \times \boldsymbol{v}`` is the [`vorticity`](@ref).299In 2D, ``\omega`` is just a scalar.300"""301@inline function enstrophy(u, gradients, equations::CompressibleNavierStokesDiffusion2D)302# Enstrophy is 0.5 rho ω⋅ω where ω = ∇ × v303304omega = vorticity(u, gradients, equations)305return 0.5f0 * u[1] * omega^2306end307308@doc raw"""309vorticity(u, gradients, equations::CompressibleNavierStokesDiffusion2D)310311Computes the (node-wise) vorticity, defined in 2D as312```math313\omega = \nabla \times \boldsymbol{v} = \frac{\partial v_2}{\partial x_1} - \frac{\partial v_1}{\partial x_2}314```315"""316@inline function vorticity(u, gradients, equations::CompressibleNavierStokesDiffusion2D)317# Ensure that we have velocity `gradients` by way of the `convert_gradient_variables` function.318_, _, dv2dx, _ = convert_derivative_to_primitive(u, gradients[1], equations)319_, dv1dy, _, _ = convert_derivative_to_primitive(u, gradients[2], equations)320321return dv2dx - dv1dy322end323324@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,325<:Adiabatic})(flux_inner,326u_inner,327normal::AbstractVector,328x,329t,330operator_type::Gradient,331equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive})332v1, v2 = boundary_condition.boundary_condition_velocity.boundary_value_function(x,333t,334equations)335return SVector(u_inner[1], v1, v2, u_inner[4])336end337338@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,339<:Adiabatic})(flux_inner,340u_inner,341normal::AbstractVector,342x,343t,344operator_type::Divergence,345equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive})346normal_heat_flux = boundary_condition.boundary_condition_heat_flux.boundary_value_normal_flux_function(x,347t,348equations)349v1, v2 = boundary_condition.boundary_condition_velocity.boundary_value_function(x,350t,351equations)352_, tau_1n, tau_2n, _ = flux_inner # extract fluxes for 2nd and 3rd equations353normal_energy_flux = v1 * tau_1n + v2 * tau_2n + normal_heat_flux354return SVector(flux_inner[1], flux_inner[2], flux_inner[3], normal_energy_flux)355end356357@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,358<:Isothermal})(flux_inner,359u_inner,360normal::AbstractVector,361x,362t,363operator_type::Gradient,364equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive})365v1, v2 = boundary_condition.boundary_condition_velocity.boundary_value_function(x,366t,367equations)368T = boundary_condition.boundary_condition_heat_flux.boundary_value_function(x, t,369equations)370return SVector(u_inner[1], v1, v2, T)371end372373@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,374<:Isothermal})(flux_inner,375u_inner,376normal::AbstractVector,377x,378t,379operator_type::Divergence,380equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive})381return flux_inner382end383384# specialized BC impositions for GradientVariablesEntropy.385386# This should return a SVector containing the boundary values of entropy variables.387# Here, `w_inner` are the transformed variables (e.g., entropy variables).388#389# Taken from "Entropy stable modal discontinuous Galerkin schemes and wall boundary conditions390# for the compressible Navier-Stokes equations" by Chan, Lin, Warburton 2022.391# DOI: 10.1016/j.jcp.2021.110723392@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,393<:Adiabatic})(flux_inner,394w_inner,395normal::AbstractVector,396x,397t,398operator_type::Gradient,399equations::CompressibleNavierStokesDiffusion2D{GradientVariablesEntropy})400v1, v2 = boundary_condition.boundary_condition_velocity.boundary_value_function(x,401t,402equations)403negative_rho_inv_p = w_inner[4] # w_4 = -rho / p404return SVector(w_inner[1], -v1 * negative_rho_inv_p, -v2 * negative_rho_inv_p,405negative_rho_inv_p)406end407408# this is actually identical to the specialization for GradientVariablesPrimitive, but included for completeness.409@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,410<:Adiabatic})(flux_inner,411w_inner,412normal::AbstractVector,413x,414t,415operator_type::Divergence,416equations::CompressibleNavierStokesDiffusion2D{GradientVariablesEntropy})417normal_heat_flux = boundary_condition.boundary_condition_heat_flux.boundary_value_normal_flux_function(x,418t,419equations)420v1, v2 = boundary_condition.boundary_condition_velocity.boundary_value_function(x,421t,422equations)423_, tau_1n, tau_2n, _ = flux_inner # extract fluxes for 2nd and 3rd equations424normal_energy_flux = v1 * tau_1n + v2 * tau_2n + normal_heat_flux425return SVector(flux_inner[1], flux_inner[2], flux_inner[3], normal_energy_flux)426end427428@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,429<:Isothermal})(flux_inner,430w_inner,431normal::AbstractVector,432x,433t,434operator_type::Gradient,435equations::CompressibleNavierStokesDiffusion2D{GradientVariablesEntropy})436v1, v2 = boundary_condition.boundary_condition_velocity.boundary_value_function(x,437t,438equations)439T = boundary_condition.boundary_condition_heat_flux.boundary_value_function(x, t,440equations)441442# the entropy variables w2 = rho * v1 / p = v1 / T = -v1 * w4. Similarly for w3443w4 = -1 / T444return SVector(w_inner[1], -v1 * w4, -v2 * w4, w4)445end446447@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,448<:Isothermal})(flux_inner,449w_inner,450normal::AbstractVector,451x,452t,453operator_type::Divergence,454equations::CompressibleNavierStokesDiffusion2D{GradientVariablesEntropy})455return SVector(flux_inner[1], flux_inner[2], flux_inner[3], flux_inner[4])456end457458# Computes the mirror velocity across a symmetry plane which enforces459# a tangential velocity that is aligned with the symmetry plane, i.e.,460# which is normal to the `normal_direction`.461# See also `boundary_condition_slip_wall` and `rotate_to_x`.462@inline function velocity_symmetry_plane(normal_direction::AbstractVector, v1, v2)463norm_ = norm(normal_direction)464normal = normal_direction / norm_465466# Compute alignment of velocity with normal direction467v_normal = v1 * normal[1] + v2 * normal[2]468469v1_outer = v1 - 2 * v_normal * normal[1]470v2_outer = v2 - 2 * v_normal * normal[2]471472return v1_outer, v2_outer473end474475# Note: This should be used with `boundary_condition_slip_wall` for the hyperbolic (Euler) part.476@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:Slip,477<:Adiabatic})(flux_inner,478u_inner,479normal::AbstractVector,480x,481t,482operator_type::Gradient,483equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive})484v1_outer, v2_outer = velocity_symmetry_plane(normal,485u_inner[2],486u_inner[3])487488return SVector(u_inner[1], v1_outer, v2_outer, u_inner[4])489end490491# Note: This should be used with `boundary_condition_slip_wall` for the hyperbolic (Euler) part.492@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:Slip,493<:Adiabatic})(flux_inner,494u_inner,495normal::AbstractVector,496x,497t,498operator_type::Divergence,499equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive})500normal_heat_flux = boundary_condition.boundary_condition_heat_flux.boundary_value_normal_flux_function(x,501t,502equations)503504# Normal stresses should be 0. This implies also that `normal_energy_flux = normal_heat_flux`.505# For details, see Section 4.2 of506# "Entropy stable modal discontinuous Galerkin schemes and wall boundary conditions507# for the compressible Navier-Stokes equations" by Chan, Lin, Warburton 2022.508# DOI: 10.1016/j.jcp.2021.110723509return SVector(flux_inner[1], 0, 0, normal_heat_flux)510end511512# Dirichlet Boundary Condition for e.g. P4est mesh513@inline function (boundary_condition::BoundaryConditionDirichlet)(flux_inner,514u_inner,515normal::AbstractVector,516x, t,517operator_type::Gradient,518equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive})519# BCs are usually specified as conservative variables so we convert them to primitive variables520# because the gradients are assumed to be with respect to the primitive variables521u_boundary = boundary_condition.boundary_value_function(x, t, equations)522523return cons2prim(u_boundary, equations)524end525526@inline function (boundary_condition::BoundaryConditionDirichlet)(flux_inner,527u_inner,528normal::AbstractVector,529x, t,530operator_type::Divergence,531equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive})532# for Dirichlet boundary conditions, we do not impose any conditions on the viscous fluxes533return flux_inner534end535end # @muladd536537538