Path: blob/main/src/callbacks_step/analysis_surface_integral_2d.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"""8LiftCoefficientPressure2D(aoa, rho_inf, u_inf, l_inf)910Compute the lift coefficient11```math12C_{L,p} \coloneqq \frac{\oint_{\partial \Omega} p \boldsymbol n \cdot \psi_L \, \mathrm{d} S}13{0.5 \rho_{\infty} U_{\infty}^2 L_{\infty}}14```15based on the pressure distribution along a boundary.16In 2D, the freestream-normal unit vector ``\psi_L`` is given by17```math18\psi_L \coloneqq \begin{pmatrix} -\sin(\alpha) \\ \cos(\alpha) \end{pmatrix}19```20where ``\alpha`` is the angle of attack.21Supposed to be used in conjunction with [`AnalysisSurfaceIntegral`](@ref)22which stores the the to-be-computed variables (for instance `LiftCoefficientPressure2D`)23and boundary information.2425- `aoa::Real`: Angle of attack in radians (for airfoils etc.)26- `rho_inf::Real`: Free-stream density27- `u_inf::Real`: Free-stream velocity28- `l_inf::Real`: Reference length of geometry (e.g. airfoil chord length)29"""30function LiftCoefficientPressure2D(aoa, rho_inf, u_inf, l_inf)31# `psi_lift` is the normal unit vector to the freestream direction.32# Note: The choice of the normal vector `psi_lift = (-sin(aoa), cos(aoa))`33# leads to positive lift coefficients for positive angles of attack for airfoils.34# One could also use `psi_lift = (sin(aoa), -cos(aoa))` which results in the same35# value, but with the opposite sign.36psi_lift = (-sin(aoa), cos(aoa))37return LiftCoefficientPressure(ForceState(psi_lift, rho_inf, u_inf, l_inf))38end3940@doc raw"""41DragCoefficientPressure2D(aoa, rho_inf, u_inf, l_inf)4243Compute the drag coefficient44```math45C_{D,p} \coloneqq \frac{\oint_{\partial \Omega} p \boldsymbol n \cdot \psi_D \, \mathrm{d} S}46{0.5 \rho_{\infty} U_{\infty}^2 L_{\infty}}47```48based on the pressure distribution along a boundary.49In 2D, the freestream-tangent unit vector ``\psi_D`` is given by50```math51\psi_D \coloneqq \begin{pmatrix} \cos(\alpha) \\ \sin(\alpha) \end{pmatrix}52```53where ``\alpha`` is the angle of attack.5455Supposed to be used in conjunction with [`AnalysisSurfaceIntegral`](@ref)56which stores the the to-be-computed variables (for instance `DragCoefficientPressure2D`)57and boundary information.5859- `aoa::Real`: Angle of attack in radians (for airfoils etc.)60- `rho_inf::Real`: Free-stream density61- `u_inf::Real`: Free-stream velocity62- `l_inf::Real`: Reference length of geometry (e.g. airfoil chord length)63"""64function DragCoefficientPressure2D(aoa, rho_inf, u_inf, l_inf)65# `psi_drag` is the unit vector tangent to the freestream direction66psi_drag = (cos(aoa), sin(aoa))67return DragCoefficientPressure(ForceState(psi_drag, rho_inf, u_inf, l_inf))68end6970@doc raw"""71LiftCoefficientShearStress2D(aoa, rho_inf, u_inf, l_inf)7273Compute the lift coefficient74```math75C_{L,f} \coloneqq \frac{\oint_{\partial \Omega} \boldsymbol \tau_w \cdot \psi_L \, \mathrm{d} S}76{0.5 \rho_{\infty} U_{\infty}^2 L_{\infty}}77```78based on the wall shear stress vector ``\tau_w`` along a boundary.79In 2D, the freestream-normal unit vector ``\psi_L`` is given by80```math81\psi_L \coloneqq \begin{pmatrix} -\sin(\alpha) \\ \cos(\alpha) \end{pmatrix}82```83where ``\alpha`` is the angle of attack.84Supposed to be used in conjunction with [`AnalysisSurfaceIntegral`](@ref)85which stores the the to-be-computed variables (for instance `LiftCoefficientShearStress2D`)86and boundary information.8788- `aoa::Real`: Angle of attack in radians (for airfoils etc.)89- `rho_inf::Real`: Free-stream density90- `u_inf::Real`: Free-stream velocity91- `l_inf::Real`: Reference length of geometry (e.g. airfoil chord length)92"""93function LiftCoefficientShearStress2D(aoa, rho_inf, u_inf, l_inf)94# `psi_lift` is the normal unit vector to the freestream direction.95# Note: The choice of the normal vector `psi_lift = (-sin(aoa), cos(aoa))`96# leads to negative lift coefficients for airfoils.97# One could also use `psi_lift = (sin(aoa), -cos(aoa))` which results in the same98# value, but with the opposite sign.99psi_lift = (-sin(aoa), cos(aoa))100return LiftCoefficientShearStress(ForceState(psi_lift, rho_inf, u_inf, l_inf))101end102103@doc raw"""104DragCoefficientShearStress2D(aoa, rho_inf, u_inf, l_inf)105106Compute the drag coefficient107```math108C_{D,f} \coloneqq \frac{\oint_{\partial \Omega} \boldsymbol \tau_w \cdot \psi_D \, \mathrm{d} S}109{0.5 \rho_{\infty} U_{\infty}^2 L_{\infty}}110```111based on the wall shear stress vector ``\tau_w`` along a boundary.112In 2D, the freestream-tangent unit vector ``\psi_D`` is given by113```math114\psi_D \coloneqq \begin{pmatrix} \cos(\alpha) \\ \sin(\alpha) \end{pmatrix}115```116where ``\alpha`` is the angle of attack.117Supposed to be used in conjunction with [`AnalysisSurfaceIntegral`](@ref)118which stores the the to-be-computed variables (for instance `DragCoefficientShearStress2D`)119and boundary information.120121- `aoa::Real`: Angle of attack in radians (for airfoils etc.)122- `rho_inf::Real`: Free-stream density123- `u_inf::Real`: Free-stream velocity124- `l_inf::Real`: Reference length of geometry (e.g. airfoil chord length)125"""126function DragCoefficientShearStress2D(aoa, rho_inf, u_inf, l_inf)127# `psi_drag` is the unit vector tangent to the freestream direction128psi_drag = (cos(aoa), sin(aoa))129return DragCoefficientShearStress(ForceState(psi_drag, rho_inf, u_inf, l_inf))130end131132# Compute the three components of the 2D symmetric viscous stress tensor133# (tau_11, tau_12, tau_22) based on the gradients of the velocity field.134# This is required for drag and lift coefficients based on shear stress,135# as well as for the non-integrated quantities such as136# skin friction coefficient (to be added).137# NOTE: This function is only valid for the compressible Navier-Stokes diffusion operator with138# `gradient_variables = GradientVariablesPrimitive()`.139function viscous_stress_tensor(u, normal_direction, equations_parabolic,140gradients_1, gradients_2)141_, dv1dx, dv2dx, _ = convert_derivative_to_primitive(u, gradients_1,142equations_parabolic)143_, dv1dy, dv2dy, _ = convert_derivative_to_primitive(u, gradients_2,144equations_parabolic)145146# Components of viscous stress tensor147# (4/3 * (v1)_x - 2/3 * (v2)_y)148tau_11 = 4.0 / 3.0 * dv1dx - 2.0 / 3.0 * dv2dy149# ((v1)_y + (v2)_x)150# stress tensor is symmetric151tau_12 = dv1dy + dv2dx # = tau_21152# (4/3 * (v2)_y - 2/3 * (v1)_x)153tau_22 = 4.0 / 3.0 * dv2dy - 2.0 / 3.0 * dv1dx154155mu = dynamic_viscosity(u, equations_parabolic)156157return mu .* (tau_11, tau_12, tau_22)158end159160# 2D viscous stress vector based on contracting the viscous stress tensor161# with the normalized `normal_direction` vector.162function viscous_stress_vector(u, normal_direction, equations_parabolic,163gradients_1, gradients_2)164# Normalize normal direction, should point *into* the fluid => *(-1)165n_normal = -normal_direction / norm(normal_direction)166167tau_11, tau_12, tau_22 = viscous_stress_tensor(u, normal_direction,168equations_parabolic,169gradients_1, gradients_2)170171# Viscous stress vector: Stress tensor * normal vector172viscous_stress_vector_1 = tau_11 * n_normal[1] + tau_12 * n_normal[2]173viscous_stress_vector_2 = tau_12 * n_normal[1] + tau_22 * n_normal[2]174175return (viscous_stress_vector_1, viscous_stress_vector_2)176end177178function (lift_coefficient::LiftCoefficientShearStress{RealT, 2})(u, normal_direction,179x, t,180equations_parabolic,181gradients_1,182gradients_2) where {RealT <:183Real}184visc_stress_vector = viscous_stress_vector(u, normal_direction, equations_parabolic,185gradients_1, gradients_2)186@unpack psi, rho_inf, u_inf, l_inf = lift_coefficient.force_state187return (visc_stress_vector[1] * psi[1] + visc_stress_vector[2] * psi[2]) /188(0.5f0 * rho_inf * u_inf^2 * l_inf)189end190191function (drag_coefficient::DragCoefficientShearStress{RealT, 2})(u, normal_direction,192x, t,193equations_parabolic,194gradients_1,195gradients_2) where {RealT <:196Real}197visc_stress_vector = viscous_stress_vector(u, normal_direction, equations_parabolic,198gradients_1, gradients_2)199@unpack psi, rho_inf, u_inf, l_inf = drag_coefficient.force_state200return (visc_stress_vector[1] * psi[1] + visc_stress_vector[2] * psi[2]) /201(0.5f0 * rho_inf * u_inf^2 * l_inf)202end203204# 2D version of the `analyze` function for `AnalysisSurfaceIntegral`, i.e.,205# `LiftCoefficientPressure` and `DragCoefficientPressure`.206function analyze(surface_variable::AnalysisSurfaceIntegral, du, u, t,207mesh::P4estMesh{2},208equations, dg::DGSEM, cache, semi)209@unpack boundaries = cache210@unpack node_coordinates, contravariant_vectors = cache.elements211@unpack weights = dg.basis212213@unpack variable, boundary_symbols = surface_variable214@unpack boundary_symbol_indices = semi.boundary_conditions215boundary_indices = get_boundary_indices(boundary_symbols, boundary_symbol_indices)216217surface_integral = zero(eltype(u))218index_range = eachnode(dg)219for boundary in boundary_indices220element = boundaries.neighbor_ids[boundary]221node_indices = boundaries.node_indices[boundary]222direction = indices2direction(node_indices)223224i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], index_range)225j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], index_range)226227i_node = i_node_start228j_node = j_node_start229for node_index in index_range230u_node = Trixi.get_node_vars(cache.boundaries.u, equations, dg,231node_index, boundary)232# Extract normal direction at nodes which points from the elements outwards,233# i.e., *into* the structure.234normal_direction = get_normal_direction(direction, contravariant_vectors,235i_node, j_node, element)236237# Coordinates at a boundary node238x = get_node_coords(node_coordinates, equations, dg,239i_node, j_node, element)240241# L2 norm of normal direction (contravariant_vector) is the surface element242dS = weights[node_index] * norm(normal_direction)243244# Integral over entire boundary surface. Note, it is assumed that the245# `normal_direction` is normalized to be a normal vector within the246# function `variable` and the division of the normal scaling factor247# `norm(normal_direction)` is then accounted for with the `dS` quantity.248surface_integral += variable(u_node, normal_direction, x, t, equations) * dS249250i_node += i_node_step251j_node += j_node_step252end253end254if mpi_isparallel()255surface_integral = MPI.Allreduce!(Ref(surface_integral), +, mpi_comm())[]256end257return surface_integral258end259260# 2D version of the `analyze` function for `AnalysisSurfaceIntegral` of viscous, i.e.,261# variables that require gradients of the solution variables.262# These are for parabolic equations readily available.263# Examples are `LiftCoefficientShearStress` and `DragCoefficientShearStress`.264function analyze(surface_variable::AnalysisSurfaceIntegral{Variable}, du, u, t,265mesh::P4estMesh{2},266equations, equations_parabolic,267dg::DGSEM, cache, semi,268cache_parabolic) where {Variable <: VariableParabolic}269@unpack boundaries = cache270@unpack node_coordinates, contravariant_vectors = cache.elements271@unpack weights = dg.basis272273@unpack variable, boundary_symbols = surface_variable274@unpack boundary_symbol_indices = semi.boundary_conditions275boundary_indices = get_boundary_indices(boundary_symbols, boundary_symbol_indices)276277# Additions for parabolic278@unpack parabolic_container = cache_parabolic279@unpack gradients = parabolic_container280281gradients_x, gradients_y = gradients282283surface_integral = zero(eltype(u))284index_range = eachnode(dg)285for boundary in boundary_indices286element = boundaries.neighbor_ids[boundary]287node_indices = boundaries.node_indices[boundary]288direction = indices2direction(node_indices)289290i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], index_range)291j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], index_range)292293i_node = i_node_start294j_node = j_node_start295for node_index in index_range296u_node = Trixi.get_node_vars(cache.boundaries.u, equations, dg,297node_index, boundary)298# Extract normal direction at nodes which points from the elements outwards,299# i.e., *into* the structure.300normal_direction = get_normal_direction(direction, contravariant_vectors,301i_node, j_node, element)302303# Coordinates at a boundary node304x = get_node_coords(node_coordinates, equations, dg,305i_node, j_node, element)306307# L2 norm of normal direction (contravariant_vector) is the surface element308dS = weights[node_index] * norm(normal_direction)309310gradients_1 = get_node_vars(gradients_x, equations_parabolic, dg,311i_node, j_node, element)312gradients_2 = get_node_vars(gradients_y, equations_parabolic, dg,313i_node, j_node, element)314315# Integral over whole boundary surface. Note, it is assumed that the316# `normal_direction` is normalized to be a normal vector within the317# function `variable` and the division of the normal scaling factor318# `norm(normal_direction)` is then accounted for with the `dS` quantity.319surface_integral += variable(u_node, normal_direction, x, t,320equations_parabolic,321gradients_1, gradients_2) * dS322323i_node += i_node_step324j_node += j_node_step325end326end327if mpi_isparallel()328surface_integral = MPI.Allreduce!(Ref(surface_integral), +, mpi_comm())[]329end330return surface_integral331end332end # muladd333334335