Path: blob/main/src/equations/compressible_navier_stokes_1d.jl
2831 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"""8CompressibleNavierStokesDiffusion1D(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 [`CompressibleEulerEquations1D`](@ref).1415- `equations`: instance of the [`CompressibleEulerEquations1D`](@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 v_1 \\ \rho e_{\text{total}}33\end{pmatrix}34+35\frac{\partial}{\partial x}36\begin{pmatrix}37\rho v_1 \\ \rho v_1^2 + p \\ (\rho e_{\text{total}} + p) v_138\end{pmatrix}39=40\frac{\partial}{\partial x}41\begin{pmatrix}420 \\ \tau \\ \tau v_1 - q43\end{pmatrix}44```45where the system is closed with the ideal gas assumption giving46```math47p = (\gamma - 1) \left( \rho e_{\text{total}} - \frac{1}{2} \rho v_1^2 \right)48```49as the pressure. The value of the adiabatic constant `gamma` is taken from the [`CompressibleEulerEquations1D`](@ref).50The terms on the right hand side of the system above51are built from the viscous stress52```math53\tau = \mu \frac{\partial}{\partial x} v_154```55where the heat flux is56```math57q = -\kappa \frac{\partial}{\partial x} \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```math68q = -\kappa \frac{\partial}{\partial x} \left(T\right) = -\frac{\gamma \mu}{(\gamma - 1)\textrm{Pr}} \frac{\partial}{\partial x} \left(\frac{p}{\rho}\right)69```70which is the form implemented below in the [`flux`](@ref) function.7172In one spatial dimensions we require gradients for two quantities, e.g.,73primitive quantities74```math75\frac{\partial}{\partial x} v_1,\, \frac{\partial}{\partial x} T76```77or the entropy variables78```math79\frac{\partial}{\partial x} w_2,\, \frac{\partial}{\partial x} w_380```81where82```math83w_2 = \frac{\rho v_1}{p},\, w_3 = -\frac{\rho}{p}84```85"""86struct CompressibleNavierStokesDiffusion1D{GradientVariables, RealT <: Real, Mu,87E <: AbstractCompressibleEulerEquations{1}} <:88AbstractCompressibleNavierStokesDiffusion{1, 3, 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 law98max_1_kappa::RealT # max(1, kappa) used for diffusive CFL => `max_diffusivity`99100equations_hyperbolic::E # CompressibleEulerEquations1D101gradient_variables::GradientVariables # GradientVariablesPrimitive or GradientVariablesEntropy102end103104# default to primitive gradient variables105function CompressibleNavierStokesDiffusion1D(equations::CompressibleEulerEquations1D;106mu, Prandtl,107gradient_variables = GradientVariablesPrimitive())108gamma = equations.gamma109inv_gamma_minus_one = equations.inv_gamma_minus_one110111# Under the assumption of constant Prandtl number the thermal conductivity112# constant is kappa = gamma μ / ((gamma-1) Prandtl).113# Important note! Factor of μ is accounted for later in `flux`.114# This avoids recomputation of kappa for non-constant μ.115kappa = gamma * inv_gamma_minus_one / Prandtl116117return CompressibleNavierStokesDiffusion1D{typeof(gradient_variables),118typeof(gamma),119typeof(mu),120typeof(equations)}(gamma,121inv_gamma_minus_one,122mu, Prandtl, kappa,123max(one(kappa),124kappa),125equations,126gradient_variables)127end128129# TODO: parabolic130# This is the flexibility a user should have to select the different gradient variable types131# varnames(::typeof(cons2prim) , ::CompressibleNavierStokesDiffusion1D) = ("v1", "T")132# varnames(::typeof(cons2entropy), ::CompressibleNavierStokesDiffusion1D) = ("w2", "w3")133134function varnames(variable_mapping,135equations_parabolic::CompressibleNavierStokesDiffusion1D)136return varnames(variable_mapping, equations_parabolic.equations_hyperbolic)137end138139# we specialize this function to compute gradients of primitive variables instead of140# conservative variables.141function gradient_variable_transformation(::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive})142return cons2prim143end144function gradient_variable_transformation(::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy})145return cons2entropy146end147148# Explicit formulas for the diffusive Navier-Stokes fluxes are available, e.g., in Section 2149# of the paper by Rueda-Ramírez, Hennemann, Hindenlang, Winters, and Gassner150# "An Entropy Stable Nodal Discontinuous Galerkin Method for the resistive151# MHD Equations. Part II: Subcell Finite Volume Shock Capturing"152# where one sets the magnetic field components equal to 0.153function flux(u, gradients, orientation::Integer,154equations::CompressibleNavierStokesDiffusion1D)155# Here, `u` is assumed to be the "transformed" variables specified by `gradient_variable_transformation`.156_, v1, _ = convert_transformed_to_primitive(u, equations)157# Here `gradients` is assumed to contain the gradients of the primitive variables (rho, v1, T)158# either computed directly or reverse engineered from the gradient of the entropy variables159# by way of the `convert_gradient_variables` function.160_, dv1dx, dTdx = convert_derivative_to_primitive(u, gradients[1], equations)161162# Viscous stress (tensor)163tau_11 = dv1dx164165# Fick's law q = -kappa * grad(T) = -kappa * grad(p / (R rho))166# with thermal diffusivity constant kappa = gamma μ R / ((gamma-1) Pr)167# Note, the gas constant cancels under this formulation, so it is not present168# in the implementation169q1 = equations.kappa * dTdx170171# In the simplest cases, the user passed in `mu` or `mu()`172# (which returns just a constant) but173# more complex functions like Sutherland's law are possible.174# `dynamic_viscosity` is a helper function that handles both cases175# by dispatching on the type of `equations.mu`.176mu = dynamic_viscosity(u, equations)177178# viscous flux components in the x-direction179f1 = 0180f2 = tau_11 * mu181f3 = (v1 * tau_11 + q1) * mu182183return SVector(f1, f2, f3)184end185186@doc raw"""187max_diffusivity(u, equations_parabolic::CompressibleNavierStokesDiffusion1D)188189# Returns190- `dynamic_viscosity(u, equations_parabolic) / u[1] * equations_parabolic.max_1_kappa`191where `max_1_kappa = max(one(kappa), kappa)` is computed in the constructor.192193For the diffusive estimate we use the eigenvalues of the diffusivity matrix,194as suggested in Section 3.5 of195- Krais et. al (2021)196FLEXI: A high order discontinuous Galerkin framework for hyperbolic–parabolic conservation laws197[DOI: 10.1016/j.camwa.2020.05.004](https://doi.org/10.1016/j.camwa.2020.05.004)198199For details on the derivation of eigenvalues of the diffusivity matrix200for the compressible Navier-Stokes equations see for instance201- Richard P. Dwight (2006)202Efficiency improvements of RANS-based analysis and optimization using implicit and adjoint methods on unstructured grids203PhD Thesis, University of Manchester204https://elib.dlr.de/50794/1/rdwight-PhDThesis-ImplicitAndAdjoint.pdf205See especially equations (2.79), (3.24), and (3.25) from Chapter 3.2.3206207The eigenvalues of the diffusivity matrix in 1D are208``-\frac{\mu}{\rho} \{0, 1, \kappa\}``209and thus the largest absolute eigenvalue is210``\frac{\mu}{\rho} \max(1, \kappa)``.211"""212@inline function max_diffusivity(u,213equations_parabolic::CompressibleNavierStokesDiffusion1D)214# See for instance also the computation in FLUXO:215# https://github.com/project-fluxo/fluxo/blob/c7e0cc9b7fd4569dcab67bbb6e5a25c0a84859f1/src/equation/navierstokes/calctimestep.f90#L122-L128216#217# Accordingly, the spectral radius/largest absolute eigenvalue can be computed as:218return dynamic_viscosity(u, equations_parabolic) / u[1] *219equations_parabolic.max_1_kappa220end221222# Convert conservative variables to primitive223@inline function cons2prim(u, equations::CompressibleNavierStokesDiffusion1D)224rho, rho_v1, _ = u225226v1 = rho_v1 / rho227T = temperature(u, equations)228229return SVector(rho, v1, T)230end231232# Convert conservative variables to entropy233# TODO: parabolic. We can improve efficiency by not computing w_1, which involves logarithms234# This can be done by specializing `cons2entropy` and `entropy2cons` to `CompressibleNavierStokesDiffusion1D`,235# but this may be confusing to new users.236function cons2entropy(u, equations::CompressibleNavierStokesDiffusion1D)237return cons2entropy(u, equations.equations_hyperbolic)238end239function entropy2cons(w, equations::CompressibleNavierStokesDiffusion1D)240return entropy2cons(w, equations.equations_hyperbolic)241end242243# the `flux` function takes in transformed variables `u` which depend on the type of the gradient variables.244# For CNS, it is simplest to formulate the viscous terms in primitive variables, so we transform the transformed245# variables into primitive variables.246@inline function convert_transformed_to_primitive(u_transformed,247equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive})248return u_transformed249end250251# TODO: parabolic. Make this more efficient!252@inline function convert_transformed_to_primitive(u_transformed,253equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy})254# note: this uses CompressibleNavierStokesDiffusion1D versions of cons2prim and entropy2cons255return cons2prim(entropy2cons(u_transformed, equations), equations)256end257258# Takes the solution values `u` and gradient of the entropy variables (w_2, w_3) and259# reverse engineers the gradients to be terms of the primitive variables (v1, T).260# Helpful because then the diffusive fluxes have the same form as on paper.261# Note, the first component of `gradient_entropy_vars` contains gradient(rho) which is unused.262# TODO: parabolic; entropy stable viscous terms263@inline function convert_derivative_to_primitive(u, gradient,264::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive})265return gradient266end267268# the first argument is always the "transformed" variables.269@inline function convert_derivative_to_primitive(w, gradient_entropy_vars,270equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy})271272# TODO: parabolic. This is inefficient to pass in transformed variables but then transform them back.273# We can fix this if we directly compute v1, T from the entropy variables274u = entropy2cons(w, equations) # calls a "modified" entropy2cons defined for CompressibleNavierStokesDiffusion1D275rho, rho_v1, _ = u276277v1 = rho_v1 / rho278T = temperature(u, equations)279280return SVector(gradient_entropy_vars[1],281T * (gradient_entropy_vars[2] + v1 * gradient_entropy_vars[3]), # grad(u) = T*(grad(w_2)+v1*grad(w_3))282T * T * gradient_entropy_vars[3])283end284285# This routine is required because `prim2cons` is called in `initial_condition`, which286# is called with `equations::CompressibleEulerEquations1D`. This means it is inconsistent287# with `cons2prim(..., ::CompressibleNavierStokesDiffusion1D)` as defined above.288# TODO: parabolic. Is there a way to clean this up?289@inline function prim2cons(u, equations::CompressibleNavierStokesDiffusion1D)290return prim2cons(u, equations.equations_hyperbolic)291end292293"""294temperature(u, equations::CompressibleNavierStokesDiffusion1D)295296Compute the temperature from the conservative variables `u`.297In particular, this assumes a specific gas constant ``R = 1``:298```math299T = \\frac{p}{\\rho}300```301"""302@inline function temperature(u, equations::CompressibleNavierStokesDiffusion1D)303rho, rho_v1, rho_e_total = u304305p = (equations.gamma - 1) * (rho_e_total - 0.5f0 * rho_v1^2 / rho)306T = p / rho # Corresponds to a specific gas constant R = 1307return T308end309310@inline function velocity(u, equations::CompressibleNavierStokesDiffusion1D)311rho = u[1]312v1 = u[2] / rho313return v1314end315316@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,317<:Adiabatic})(flux_inner,318u_inner,319orientation::Integer,320direction,321x,322t,323operator_type::Gradient,324equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive})325v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t,326equations)327return SVector(u_inner[1], v1, u_inner[3])328end329330@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,331<:Adiabatic})(flux_inner,332u_inner,333orientation::Integer,334direction,335x,336t,337operator_type::Divergence,338equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive})339normal_heat_flux = boundary_condition.boundary_condition_heat_flux.boundary_value_normal_flux_function(x,340t,341equations)342v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t,343equations)344_, tau_1n, _ = flux_inner # extract fluxes for 2nd equation345normal_energy_flux = v1 * tau_1n + normal_heat_flux346return SVector(flux_inner[1], flux_inner[2], normal_energy_flux)347end348349@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,350<:Isothermal})(flux_inner,351u_inner,352orientation::Integer,353direction,354x,355t,356operator_type::Gradient,357equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive})358v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t,359equations)360T = boundary_condition.boundary_condition_heat_flux.boundary_value_function(x, t,361equations)362return SVector(u_inner[1], v1, T)363end364365@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,366<:Isothermal})(flux_inner,367u_inner,368orientation::Integer,369direction,370x,371t,372operator_type::Divergence,373equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive})374return flux_inner375end376377# specialized BC impositions for GradientVariablesEntropy.378379# This should return a SVector containing the boundary values of entropy variables.380# Here, `w_inner` are the transformed variables (e.g., entropy variables).381#382# Taken from "Entropy stable modal discontinuous Galerkin schemes and wall boundary conditions383# for the compressible Navier-Stokes equations" by Chan, Lin, Warburton 2022.384# DOI: 10.1016/j.jcp.2021.110723385@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,386<:Adiabatic})(flux_inner,387w_inner,388orientation::Integer,389direction,390x,391t,392operator_type::Gradient,393equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy})394v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t,395equations)396negative_rho_inv_p = w_inner[3] # w_3 = -rho / p397return SVector(w_inner[1], -v1 * negative_rho_inv_p, negative_rho_inv_p)398end399400# this is actually identical to the specialization for GradientVariablesPrimitive, but included for completeness.401@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,402<:Adiabatic})(flux_inner,403w_inner,404orientation::Integer,405direction,406x,407t,408operator_type::Divergence,409equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy})410normal_heat_flux = boundary_condition.boundary_condition_heat_flux.boundary_value_normal_flux_function(x,411t,412equations)413v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t,414equations)415_, tau_1n, _ = flux_inner # extract fluxes for 2nd equation416normal_energy_flux = v1 * tau_1n + normal_heat_flux417return SVector(flux_inner[1], flux_inner[2], normal_energy_flux)418end419420@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,421<:Isothermal})(flux_inner,422w_inner,423orientation::Integer,424direction,425x,426t,427operator_type::Gradient,428equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy})429v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t,430equations)431T = boundary_condition.boundary_condition_heat_flux.boundary_value_function(x, t,432equations)433434# the entropy variables w2 = rho * v1 / p = v1 / T = -v1 * w3.435w3 = -1 / T436return SVector(w_inner[1], -v1 * w3, w3)437end438439@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,440<:Isothermal})(flux_inner,441w_inner,442orientation::Integer,443direction,444x,445t,446operator_type::Divergence,447equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy})448return SVector(flux_inner[1], flux_inner[2], flux_inner[3])449end450end # @muladd451452453