Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
trixi-framework
GitHub Repository: trixi-framework/Trixi.jl
Path: blob/main/src/callbacks_step/analysis_surface_integral_2d.jl
5586 views
1
# By default, Julia/LLVM does not use fused multiply-add operations (FMAs).
2
# Since these FMAs can increase the performance of many numerical algorithms,
3
# we need to opt-in explicitly.
4
# See https://ranocha.de/blog/Optimizing_EC_Trixi for further details.
5
@muladd begin
6
#! format: noindent
7
8
@doc raw"""
9
LiftCoefficientPressure2D(aoa, rho_inf, u_inf, l_inf)
10
11
Compute the lift coefficient
12
```math
13
C_{L,p} \coloneqq \frac{\oint_{\partial \Omega} p \boldsymbol n \cdot \psi_L \, \mathrm{d} S}
14
{0.5 \rho_{\infty} U_{\infty}^2 L_{\infty}}
15
```
16
based on the pressure distribution along a boundary.
17
In 2D, the freestream-normal unit vector ``\psi_L`` is given by
18
```math
19
\psi_L \coloneqq \begin{pmatrix} -\sin(\alpha) \\ \cos(\alpha) \end{pmatrix}
20
```
21
where ``\alpha`` is the angle of attack.
22
Supposed to be used in conjunction with [`AnalysisSurfaceIntegral`](@ref)
23
which stores the the to-be-computed variables (for instance `LiftCoefficientPressure2D`)
24
and boundary information.
25
26
- `aoa::Real`: Angle of attack in radians (for airfoils etc.)
27
- `rho_inf::Real`: Free-stream density
28
- `u_inf::Real`: Free-stream velocity
29
- `l_inf::Real`: Reference length of geometry (e.g. airfoil chord length)
30
"""
31
function LiftCoefficientPressure2D(aoa, rho_inf, u_inf, l_inf)
32
# `psi_lift` is the normal unit vector to the freestream direction.
33
# Note: The choice of the normal vector `psi_lift = (-sin(aoa), cos(aoa))`
34
# leads to positive lift coefficients for positive angles of attack for airfoils.
35
# One could also use `psi_lift = (sin(aoa), -cos(aoa))` which results in the same
36
# value, but with the opposite sign.
37
psi_lift = (-sin(aoa), cos(aoa))
38
return LiftCoefficientPressure(ForceState(psi_lift, rho_inf, u_inf, l_inf))
39
end
40
41
@doc raw"""
42
DragCoefficientPressure2D(aoa, rho_inf, u_inf, l_inf)
43
44
Compute the drag coefficient
45
```math
46
C_{D,p} \coloneqq \frac{\oint_{\partial \Omega} p \boldsymbol n \cdot \psi_D \, \mathrm{d} S}
47
{0.5 \rho_{\infty} U_{\infty}^2 L_{\infty}}
48
```
49
based on the pressure distribution along a boundary.
50
In 2D, the freestream-tangent unit vector ``\psi_D`` is given by
51
```math
52
\psi_D \coloneqq \begin{pmatrix} \cos(\alpha) \\ \sin(\alpha) \end{pmatrix}
53
```
54
where ``\alpha`` is the angle of attack.
55
56
Supposed to be used in conjunction with [`AnalysisSurfaceIntegral`](@ref)
57
which stores the the to-be-computed variables (for instance `DragCoefficientPressure2D`)
58
and boundary information.
59
60
- `aoa::Real`: Angle of attack in radians (for airfoils etc.)
61
- `rho_inf::Real`: Free-stream density
62
- `u_inf::Real`: Free-stream velocity
63
- `l_inf::Real`: Reference length of geometry (e.g. airfoil chord length)
64
"""
65
function DragCoefficientPressure2D(aoa, rho_inf, u_inf, l_inf)
66
# `psi_drag` is the unit vector tangent to the freestream direction
67
psi_drag = (cos(aoa), sin(aoa))
68
return DragCoefficientPressure(ForceState(psi_drag, rho_inf, u_inf, l_inf))
69
end
70
71
@doc raw"""
72
LiftCoefficientShearStress2D(aoa, rho_inf, u_inf, l_inf)
73
74
Compute the lift coefficient
75
```math
76
C_{L,f} \coloneqq \frac{\oint_{\partial \Omega} \boldsymbol \tau_w \cdot \psi_L \, \mathrm{d} S}
77
{0.5 \rho_{\infty} U_{\infty}^2 L_{\infty}}
78
```
79
based on the wall shear stress vector ``\tau_w`` along a boundary.
80
In 2D, the freestream-normal unit vector ``\psi_L`` is given by
81
```math
82
\psi_L \coloneqq \begin{pmatrix} -\sin(\alpha) \\ \cos(\alpha) \end{pmatrix}
83
```
84
where ``\alpha`` is the angle of attack.
85
Supposed to be used in conjunction with [`AnalysisSurfaceIntegral`](@ref)
86
which stores the the to-be-computed variables (for instance `LiftCoefficientShearStress2D`)
87
and boundary information.
88
89
- `aoa::Real`: Angle of attack in radians (for airfoils etc.)
90
- `rho_inf::Real`: Free-stream density
91
- `u_inf::Real`: Free-stream velocity
92
- `l_inf::Real`: Reference length of geometry (e.g. airfoil chord length)
93
"""
94
function LiftCoefficientShearStress2D(aoa, rho_inf, u_inf, l_inf)
95
# `psi_lift` is the normal unit vector to the freestream direction.
96
# Note: The choice of the normal vector `psi_lift = (-sin(aoa), cos(aoa))`
97
# leads to negative lift coefficients for airfoils.
98
# One could also use `psi_lift = (sin(aoa), -cos(aoa))` which results in the same
99
# value, but with the opposite sign.
100
psi_lift = (-sin(aoa), cos(aoa))
101
return LiftCoefficientShearStress(ForceState(psi_lift, rho_inf, u_inf, l_inf))
102
end
103
104
@doc raw"""
105
DragCoefficientShearStress2D(aoa, rho_inf, u_inf, l_inf)
106
107
Compute the drag coefficient
108
```math
109
C_{D,f} \coloneqq \frac{\oint_{\partial \Omega} \boldsymbol \tau_w \cdot \psi_D \, \mathrm{d} S}
110
{0.5 \rho_{\infty} U_{\infty}^2 L_{\infty}}
111
```
112
based on the wall shear stress vector ``\tau_w`` along a boundary.
113
In 2D, the freestream-tangent unit vector ``\psi_D`` is given by
114
```math
115
\psi_D \coloneqq \begin{pmatrix} \cos(\alpha) \\ \sin(\alpha) \end{pmatrix}
116
```
117
where ``\alpha`` is the angle of attack.
118
Supposed to be used in conjunction with [`AnalysisSurfaceIntegral`](@ref)
119
which stores the the to-be-computed variables (for instance `DragCoefficientShearStress2D`)
120
and boundary information.
121
122
- `aoa::Real`: Angle of attack in radians (for airfoils etc.)
123
- `rho_inf::Real`: Free-stream density
124
- `u_inf::Real`: Free-stream velocity
125
- `l_inf::Real`: Reference length of geometry (e.g. airfoil chord length)
126
"""
127
function DragCoefficientShearStress2D(aoa, rho_inf, u_inf, l_inf)
128
# `psi_drag` is the unit vector tangent to the freestream direction
129
psi_drag = (cos(aoa), sin(aoa))
130
return DragCoefficientShearStress(ForceState(psi_drag, rho_inf, u_inf, l_inf))
131
end
132
133
# Compute the three components of the 2D symmetric viscous stress tensor
134
# (tau_11, tau_12, tau_22) based on the gradients of the velocity field.
135
# This is required for drag and lift coefficients based on shear stress,
136
# as well as for the non-integrated quantities such as
137
# skin friction coefficient (to be added).
138
# NOTE: This function is only valid for the compressible Navier-Stokes diffusion operator with
139
# `gradient_variables = GradientVariablesPrimitive()`.
140
function viscous_stress_tensor(u, normal_direction, equations_parabolic,
141
gradients_1, gradients_2)
142
_, dv1dx, dv2dx, _ = convert_derivative_to_primitive(u, gradients_1,
143
equations_parabolic)
144
_, dv1dy, dv2dy, _ = convert_derivative_to_primitive(u, gradients_2,
145
equations_parabolic)
146
147
# Components of viscous stress tensor
148
# (4/3 * (v1)_x - 2/3 * (v2)_y)
149
tau_11 = 4.0 / 3.0 * dv1dx - 2.0 / 3.0 * dv2dy
150
# ((v1)_y + (v2)_x)
151
# stress tensor is symmetric
152
tau_12 = dv1dy + dv2dx # = tau_21
153
# (4/3 * (v2)_y - 2/3 * (v1)_x)
154
tau_22 = 4.0 / 3.0 * dv2dy - 2.0 / 3.0 * dv1dx
155
156
mu = dynamic_viscosity(u, equations_parabolic)
157
158
return mu .* (tau_11, tau_12, tau_22)
159
end
160
161
# 2D viscous stress vector based on contracting the viscous stress tensor
162
# with the normalized `normal_direction` vector.
163
function viscous_stress_vector(u, normal_direction, equations_parabolic,
164
gradients_1, gradients_2)
165
# Normalize normal direction, should point *into* the fluid => *(-1)
166
n_normal = -normal_direction / norm(normal_direction)
167
168
tau_11, tau_12, tau_22 = viscous_stress_tensor(u, normal_direction,
169
equations_parabolic,
170
gradients_1, gradients_2)
171
172
# Viscous stress vector: Stress tensor * normal vector
173
viscous_stress_vector_1 = tau_11 * n_normal[1] + tau_12 * n_normal[2]
174
viscous_stress_vector_2 = tau_12 * n_normal[1] + tau_22 * n_normal[2]
175
176
return (viscous_stress_vector_1, viscous_stress_vector_2)
177
end
178
179
function (lift_coefficient::LiftCoefficientShearStress{RealT, 2})(u, normal_direction,
180
x, t,
181
equations_parabolic,
182
gradients_1,
183
gradients_2) where {RealT <:
184
Real}
185
visc_stress_vector = viscous_stress_vector(u, normal_direction, equations_parabolic,
186
gradients_1, gradients_2)
187
@unpack psi, rho_inf, u_inf, l_inf = lift_coefficient.force_state
188
return (visc_stress_vector[1] * psi[1] + visc_stress_vector[2] * psi[2]) /
189
(0.5f0 * rho_inf * u_inf^2 * l_inf)
190
end
191
192
function (drag_coefficient::DragCoefficientShearStress{RealT, 2})(u, normal_direction,
193
x, t,
194
equations_parabolic,
195
gradients_1,
196
gradients_2) where {RealT <:
197
Real}
198
visc_stress_vector = viscous_stress_vector(u, normal_direction, equations_parabolic,
199
gradients_1, gradients_2)
200
@unpack psi, rho_inf, u_inf, l_inf = drag_coefficient.force_state
201
return (visc_stress_vector[1] * psi[1] + visc_stress_vector[2] * psi[2]) /
202
(0.5f0 * rho_inf * u_inf^2 * l_inf)
203
end
204
205
# 2D version of the `analyze` function for `AnalysisSurfaceIntegral`, i.e.,
206
# `LiftCoefficientPressure` and `DragCoefficientPressure`.
207
function analyze(surface_variable::AnalysisSurfaceIntegral, du, u, t,
208
mesh::P4estMesh{2},
209
equations, dg::DGSEM, cache, semi)
210
@unpack boundaries = cache
211
@unpack node_coordinates, contravariant_vectors = cache.elements
212
@unpack weights = dg.basis
213
214
@unpack variable, boundary_symbols = surface_variable
215
@unpack boundary_symbol_indices = semi.boundary_conditions
216
boundary_indices = get_boundary_indices(boundary_symbols, boundary_symbol_indices)
217
218
surface_integral = zero(eltype(u))
219
index_range = eachnode(dg)
220
for boundary in boundary_indices
221
element = boundaries.neighbor_ids[boundary]
222
node_indices = boundaries.node_indices[boundary]
223
direction = indices2direction(node_indices)
224
225
i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], index_range)
226
j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], index_range)
227
228
i_node = i_node_start
229
j_node = j_node_start
230
for node_index in index_range
231
u_node = Trixi.get_node_vars(cache.boundaries.u, equations, dg,
232
node_index, boundary)
233
# Extract normal direction at nodes which points from the elements outwards,
234
# i.e., *into* the structure.
235
normal_direction = get_normal_direction(direction, contravariant_vectors,
236
i_node, j_node, element)
237
238
# Coordinates at a boundary node
239
x = get_node_coords(node_coordinates, equations, dg,
240
i_node, j_node, element)
241
242
# L2 norm of normal direction (contravariant_vector) is the surface element
243
dS = weights[node_index] * norm(normal_direction)
244
245
# Integral over entire boundary surface. Note, it is assumed that the
246
# `normal_direction` is normalized to be a normal vector within the
247
# function `variable` and the division of the normal scaling factor
248
# `norm(normal_direction)` is then accounted for with the `dS` quantity.
249
surface_integral += variable(u_node, normal_direction, x, t, equations) * dS
250
251
i_node += i_node_step
252
j_node += j_node_step
253
end
254
end
255
if mpi_isparallel()
256
surface_integral = MPI.Allreduce!(Ref(surface_integral), +, mpi_comm())[]
257
end
258
return surface_integral
259
end
260
261
# 2D version of the `analyze` function for `AnalysisSurfaceIntegral` of viscous, i.e.,
262
# variables that require gradients of the solution variables.
263
# These are for parabolic equations readily available.
264
# Examples are `LiftCoefficientShearStress` and `DragCoefficientShearStress`.
265
function analyze(surface_variable::AnalysisSurfaceIntegral{Variable}, du, u, t,
266
mesh::P4estMesh{2},
267
equations, equations_parabolic,
268
dg::DGSEM, cache, semi,
269
cache_parabolic) where {Variable <: VariableParabolic}
270
@unpack boundaries = cache
271
@unpack node_coordinates, contravariant_vectors = cache.elements
272
@unpack weights = dg.basis
273
274
@unpack variable, boundary_symbols = surface_variable
275
@unpack boundary_symbol_indices = semi.boundary_conditions
276
boundary_indices = get_boundary_indices(boundary_symbols, boundary_symbol_indices)
277
278
# Additions for parabolic
279
@unpack parabolic_container = cache_parabolic
280
@unpack gradients = parabolic_container
281
282
gradients_x, gradients_y = gradients
283
284
surface_integral = zero(eltype(u))
285
index_range = eachnode(dg)
286
for boundary in boundary_indices
287
element = boundaries.neighbor_ids[boundary]
288
node_indices = boundaries.node_indices[boundary]
289
direction = indices2direction(node_indices)
290
291
i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], index_range)
292
j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], index_range)
293
294
i_node = i_node_start
295
j_node = j_node_start
296
for node_index in index_range
297
u_node = Trixi.get_node_vars(cache.boundaries.u, equations, dg,
298
node_index, boundary)
299
# Extract normal direction at nodes which points from the elements outwards,
300
# i.e., *into* the structure.
301
normal_direction = get_normal_direction(direction, contravariant_vectors,
302
i_node, j_node, element)
303
304
# Coordinates at a boundary node
305
x = get_node_coords(node_coordinates, equations, dg,
306
i_node, j_node, element)
307
308
# L2 norm of normal direction (contravariant_vector) is the surface element
309
dS = weights[node_index] * norm(normal_direction)
310
311
gradients_1 = get_node_vars(gradients_x, equations_parabolic, dg,
312
i_node, j_node, element)
313
gradients_2 = get_node_vars(gradients_y, equations_parabolic, dg,
314
i_node, j_node, element)
315
316
# Integral over whole boundary surface. Note, it is assumed that the
317
# `normal_direction` is normalized to be a normal vector within the
318
# function `variable` and the division of the normal scaling factor
319
# `norm(normal_direction)` is then accounted for with the `dS` quantity.
320
surface_integral += variable(u_node, normal_direction, x, t,
321
equations_parabolic,
322
gradients_1, gradients_2) * dS
323
324
i_node += i_node_step
325
j_node += j_node_step
326
end
327
end
328
if mpi_isparallel()
329
surface_integral = MPI.Allreduce!(Ref(surface_integral), +, mpi_comm())[]
330
end
331
return surface_integral
332
end
333
end # muladd
334
335