Path: blob/main/src/equations/compressible_navier_stokes_1d.jl
5586 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 Fourier'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# Add NGRADS as a type parameter here and in AbstractEquationsParabolic, add `ngradients(...)` accessor function9192mu::Mu # viscosity93Pr::RealT # Prandtl number94kappa::RealT # thermal diffusivity for Fourier's law95max_1_kappa::RealT # max(1, kappa) used for parabolic cfl => `max_diffusivity`9697equations_hyperbolic::E # CompressibleEulerEquations1D98gradient_variables::GradientVariables # GradientVariablesPrimitive or GradientVariablesEntropy99end100101# default to primitive gradient variables102function CompressibleNavierStokesDiffusion1D(equations::CompressibleEulerEquations1D;103mu, Prandtl,104gradient_variables = GradientVariablesPrimitive())105@unpack gamma, inv_gamma_minus_one = equations106107Pr = promote_type(typeof(gamma), typeof(Prandtl))(Prandtl)108# Under the assumption of constant Prandtl number the thermal conductivity109# constant is kappa = gamma μ / ((gamma-1) Prandtl).110# Important note! Factor of μ is accounted for later in `flux`.111# This avoids recomputation of kappa for non-constant μ.112kappa = gamma * inv_gamma_minus_one / Pr113114return CompressibleNavierStokesDiffusion1D{typeof(gradient_variables),115typeof(Pr), typeof(mu),116typeof(equations)}(mu, Pr, kappa,117max(one(kappa),118kappa),119equations,120gradient_variables)121end122123# TODO: parabolic124# This is the flexibility a user should have to select the different gradient variable types125# varnames(::typeof(cons2prim) , ::CompressibleNavierStokesDiffusion1D) = ("v1", "T")126# varnames(::typeof(cons2entropy), ::CompressibleNavierStokesDiffusion1D) = ("w2", "w3")127128function varnames(variable_mapping,129equations_parabolic::CompressibleNavierStokesDiffusion1D)130return varnames(variable_mapping, equations_parabolic.equations_hyperbolic)131end132133# we specialize this function to compute gradients of primitive variables instead of134# conservative variables.135function gradient_variable_transformation(::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive})136return cons2prim137end138function gradient_variable_transformation(::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy})139return cons2entropy140end141142# Explicit formulas for the diffusive Navier-Stokes fluxes are available, e.g., in Section 2143# of the paper by Rueda-Ramírez, Hennemann, Hindenlang, Winters, and Gassner144# "An Entropy Stable Nodal Discontinuous Galerkin Method for the resistive145# MHD Equations. Part II: Subcell Finite Volume Shock Capturing"146# where one sets the magnetic field components equal to 0.147function flux(u, gradients, orientation::Integer,148equations::CompressibleNavierStokesDiffusion1D)149# Here, `u` is assumed to be the "transformed" variables specified by `gradient_variable_transformation`.150_, v1, _ = convert_transformed_to_primitive(u, equations)151# Here `gradients` is assumed to contain the gradients of the primitive variables (rho, v1, T)152# either computed directly or reverse engineered from the gradient of the entropy variables153# by way of the `convert_gradient_variables` function.154_, dv1dx, dTdx = convert_derivative_to_primitive(u, gradients[1], equations)155156# Viscous stress (tensor)157tau_11 = dv1dx158159# Fourier's law q = -kappa * grad(T) = -kappa * grad(p / (R rho))160# with thermal diffusivity constant kappa = gamma μ R / ((gamma-1) Pr)161# Note, the gas constant cancels under this formulation, so it is not present162# in the implementation163q1 = equations.kappa * dTdx164165# In the simplest cases, the user passed in `mu` or `mu()`166# (which returns just a constant) but167# more complex functions like Sutherland's law are possible.168# `dynamic_viscosity` is a helper function that handles both cases169# by dispatching on the type of `equations.mu`.170mu = dynamic_viscosity(u, equations)171172# parabolic flux components in the x-direction173f1 = 0174f2 = tau_11 * mu175f3 = (v1 * tau_11 + q1) * mu176177return SVector(f1, f2, f3)178end179180@doc raw"""181max_diffusivity(u, equations_parabolic::CompressibleNavierStokesDiffusion1D)182183# Returns184- `dynamic_viscosity(u, equations_parabolic) / u[1] * equations_parabolic.max_1_kappa`185where `max_1_kappa = max(one(kappa), kappa)` is computed in the constructor.186187For the diffusive estimate we use the eigenvalues of the diffusivity matrix,188as suggested in Section 3.5 of189- Krais et. al (2021)190FLEXI: A high order discontinuous Galerkin framework for hyperbolic–parabolic conservation laws191[DOI: 10.1016/j.camwa.2020.05.004](https://doi.org/10.1016/j.camwa.2020.05.004)192193For details on the derivation of eigenvalues of the diffusivity matrix194for the compressible Navier-Stokes equations see for instance195- Richard P. Dwight (2006)196Efficiency improvements of RANS-based analysis and optimization using implicit and adjoint methods on unstructured grids197PhD Thesis, University of Manchester198https://elib.dlr.de/50794/1/rdwight-PhDThesis-ImplicitAndAdjoint.pdf199See especially equations (2.79), (3.24), and (3.25) from Chapter 3.2.3200201The eigenvalues of the diffusivity matrix in 1D are202``-\frac{\mu}{\rho} \{0, 1, \kappa\}``203and thus the largest absolute eigenvalue is204``\frac{\mu}{\rho} \max(1, \kappa)``.205"""206@inline function max_diffusivity(u,207equations_parabolic::CompressibleNavierStokesDiffusion1D)208# See for instance also the computation in FLUXO:209# https://github.com/project-fluxo/fluxo/blob/c7e0cc9b7fd4569dcab67bbb6e5a25c0a84859f1/src/equation/navierstokes/calctimestep.f90#L122-L128210#211# Accordingly, the spectral radius/largest absolute eigenvalue can be computed as:212return dynamic_viscosity(u, equations_parabolic) / u[1] *213equations_parabolic.max_1_kappa214end215216# Convert conservative variables to primitive217@inline function cons2prim(u, equations::CompressibleNavierStokesDiffusion1D)218rho, rho_v1, _ = u219220v1 = rho_v1 / rho221T = temperature(u, equations)222223return SVector(rho, v1, T)224end225226# Convert conservative variables to entropy227# TODO: parabolic. We can improve efficiency by not computing w_1, which involves logarithms228# This can be done by specializing `cons2entropy` and `entropy2cons` to `CompressibleNavierStokesDiffusion1D`,229# but this may be confusing to new users.230function cons2entropy(u, equations::CompressibleNavierStokesDiffusion1D)231return cons2entropy(u, equations.equations_hyperbolic)232end233function entropy2cons(w, equations::CompressibleNavierStokesDiffusion1D)234return entropy2cons(w, equations.equations_hyperbolic)235end236237# the `flux` function takes in transformed variables `u` which depend on the type of the gradient variables.238# For CNS, it is simplest to formulate the parabolic terms in primitive variables, so we transform the transformed239# variables into primitive variables.240@inline function convert_transformed_to_primitive(u_transformed,241equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive})242return u_transformed243end244245# TODO: parabolic. Make this more efficient!246@inline function convert_transformed_to_primitive(u_transformed,247equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy})248# note: this uses CompressibleNavierStokesDiffusion1D versions of cons2prim and entropy2cons249return cons2prim(entropy2cons(u_transformed, equations), equations)250end251252# Takes the solution values `u` and gradient of the entropy variables (w_2, w_3) and253# reverse engineers the gradients to be terms of the primitive variables (v1, T).254# Helpful because then the diffusive fluxes have the same form as on paper.255# Note, the first component of `gradient_entropy_vars` contains gradient(rho) which is unused.256# TODO: parabolic; entropy stable parabolic terms257@inline function convert_derivative_to_primitive(u, gradient,258::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive})259return gradient260end261262# the first argument is always the "transformed" variables.263@inline function convert_derivative_to_primitive(w, gradient_entropy_vars,264equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy})265266# TODO: parabolic. This is inefficient to pass in transformed variables but then transform them back.267# We can fix this if we directly compute v1, T from the entropy variables268u = entropy2cons(w, equations) # calls a "modified" entropy2cons defined for CompressibleNavierStokesDiffusion1D269rho, rho_v1, _ = u270271v1 = rho_v1 / rho272T = temperature(u, equations)273274return SVector(gradient_entropy_vars[1],275T * (gradient_entropy_vars[2] + v1 * gradient_entropy_vars[3]), # grad(u) = T*(grad(w_2)+v1*grad(w_3))276T * T * gradient_entropy_vars[3])277end278279# This routine is required because `prim2cons` is called in `initial_condition`, which280# is called with `equations::CompressibleEulerEquations1D`. This means it is inconsistent281# with `cons2prim(..., ::CompressibleNavierStokesDiffusion1D)` as defined above.282# TODO: parabolic. Is there a way to clean this up?283@inline function prim2cons(u, equations::CompressibleNavierStokesDiffusion1D)284return prim2cons(u, equations.equations_hyperbolic)285end286287"""288temperature(u, equations::CompressibleNavierStokesDiffusion1D)289290Compute the temperature from the conservative variables `u`.291In particular, this assumes a specific gas constant ``R = 1``:292```math293T = \\frac{p}{\\rho}294```295"""296@inline function temperature(u, equations::CompressibleNavierStokesDiffusion1D)297rho, rho_v1, rho_e_total = u298@unpack gamma = equations299300p = (gamma - 1) * (rho_e_total - 0.5f0 * rho_v1^2 / rho)301T = p / rho # Corresponds to a specific gas constant R = 1302return T303end304305@inline function velocity(u, equations::CompressibleNavierStokesDiffusion1D)306rho = u[1]307v1 = u[2] / rho308return v1309end310311@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,312<:Adiabatic})(flux_inner,313u_inner,314orientation::Integer,315direction,316x,317t,318operator_type::Gradient,319equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive})320v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t,321equations)322return SVector(u_inner[1], v1, u_inner[3])323end324325@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,326<:Adiabatic})(flux_inner,327u_inner,328orientation::Integer,329direction,330x,331t,332operator_type::Divergence,333equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive})334normal_heat_flux = boundary_condition.boundary_condition_heat_flux.boundary_value_normal_flux_function(x,335t,336equations)337v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t,338equations)339_, tau_1n, _ = flux_inner # extract fluxes for 2nd equation340normal_energy_flux = v1 * tau_1n + normal_heat_flux341return SVector(flux_inner[1], flux_inner[2], normal_energy_flux)342end343344@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,345<:Isothermal})(flux_inner,346u_inner,347orientation::Integer,348direction,349x,350t,351operator_type::Gradient,352equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive})353v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t,354equations)355T = boundary_condition.boundary_condition_heat_flux.boundary_value_function(x, t,356equations)357return SVector(u_inner[1], v1, T)358end359360@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,361<:Isothermal})(flux_inner,362u_inner,363orientation::Integer,364direction,365x,366t,367operator_type::Divergence,368equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive})369return flux_inner370end371372# specialized BC impositions for GradientVariablesEntropy.373374# This should return a SVector containing the boundary values of entropy variables.375# Here, `w_inner` are the transformed variables (e.g., entropy variables).376#377# Taken from "Entropy stable modal discontinuous Galerkin schemes and wall boundary conditions378# for the compressible Navier-Stokes equations" by Chan, Lin, Warburton 2022.379# DOI: 10.1016/j.jcp.2021.110723380@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,381<:Adiabatic})(flux_inner,382w_inner,383orientation::Integer,384direction,385x,386t,387operator_type::Gradient,388equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy})389v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t,390equations)391negative_rho_inv_p = w_inner[3] # w_3 = -rho / p392return SVector(w_inner[1], -v1 * negative_rho_inv_p, negative_rho_inv_p)393end394395# this is actually identical to the specialization for GradientVariablesPrimitive, but included for completeness.396@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,397<:Adiabatic})(flux_inner,398w_inner,399orientation::Integer,400direction,401x,402t,403operator_type::Divergence,404equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy})405normal_heat_flux = boundary_condition.boundary_condition_heat_flux.boundary_value_normal_flux_function(x,406t,407equations)408v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t,409equations)410_, tau_1n, _ = flux_inner # extract fluxes for 2nd equation411normal_energy_flux = v1 * tau_1n + normal_heat_flux412return SVector(flux_inner[1], flux_inner[2], normal_energy_flux)413end414415@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,416<:Isothermal})(flux_inner,417w_inner,418orientation::Integer,419direction,420x,421t,422operator_type::Gradient,423equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy})424v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t,425equations)426T = boundary_condition.boundary_condition_heat_flux.boundary_value_function(x, t,427equations)428429# the entropy variables w2 = rho * v1 / p = v1 / T = -v1 * w3.430w3 = -1 / T431return SVector(w_inner[1], -v1 * w3, w3)432end433434@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip,435<:Isothermal})(flux_inner,436w_inner,437orientation::Integer,438direction,439x,440t,441operator_type::Divergence,442equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy})443return SVector(flux_inner[1], flux_inner[2], flux_inner[3])444end445end # @muladd446447448