Skip to content

Commit 3c8e7ef

Browse files
committed
Make LiftingLine and StripwiseELement inherit from AbstractBody and AbstractElement; postprocess linear solution
1 parent 07b2799 commit 3c8e7ef

File tree

4 files changed

+224
-21
lines changed

4 files changed

+224
-21
lines changed

src/FLOWPanel.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ for header_name in ["elements", "linearsolver",
6464
"abstractbody", "nonliftingbody",
6565
"abstractliftingbody", "liftingbody",
6666
"multibody",
67-
"liftingline_stripwise", "liftingline",
67+
"liftingline_stripwise", "liftingline", "liftingline_postprocess",
6868
"utils", "postprocess",
6969
# "fmm"
7070
]

src/FLOWPanel_liftingline.jl

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -15,16 +15,17 @@ import LaTeXStrings: @L_str
1515
# LIFTING LINE STRUCT
1616
################################################################################
1717
struct LiftingLine{ R<:Number,
18-
S<:StripwiseElement,
18+
S<:StripwiseElement, N,
1919
VectorType<:AbstractVector{R},
2020
MatrixType<:AbstractMatrix{R},
2121
TensorType<:AbstractArray{R, 3},
22-
LI<:LinearIndices}
22+
LI<:LinearIndices} <: AbstractBody{S, N}
2323

2424
# Internal properties
2525
grid::gt.Grid # Flat-geometry grid
2626
linearindices::LI # Linear indices of grid.nodes where linearindices[i, j]
2727
# is TE (j==1) or LE (j==2) of the i-th row of nodes
28+
fields::Vector{String} # Available fields (solutions)
2829

2930
ypositions::Vector{Float64} # Non-dimensional y-position of nodes, 2*y/b
3031

@@ -44,12 +45,13 @@ struct LiftingLine{ R<:Number,
4445
# j-th semi-infinite filament (1==a, 2==b) of the
4546
# n-th horeseshoe
4647

47-
midpoints::MatrixType # Midpoint along bound vortex for probing the velocity
48+
midpoints::MatrixType # Midpoint along lifting line for probing the velocity
4849
controlpoints::MatrixType # Control point of each horseshoe
4950
normals::MatrixType # Normal of each horseshoe
5051

5152
aoas::VectorType # (deg) angle of attack seen by each stripwise element
5253
Gammas::VectorType # Circulation of each horseshoe
54+
Us::MatrixType # Velocity at each midpoint
5355

5456
G::MatrixType # Geometry matrix in linear system of equations
5557
RHS::VectorType # Right-hand-side vector in linear system of equations
@@ -116,11 +118,13 @@ struct LiftingLine{ R<:Number,
116118
normals = MatrixType(undef, 3, nelements)
117119
aoas = VectorType(undef, nelements)
118120
Gammas = VectorType(undef, nelements)
121+
Us = MatrixType(undef, 3, nelements)
119122
G = MatrixType(undef, nelements, nelements)
120123
RHS = VectorType(undef, nelements)
121124

122125
aoas .= 0
123126
Gammas .= 0
127+
Us .= 0
124128
G .= 0
125129
RHS .= 0
126130

@@ -139,17 +143,17 @@ struct LiftingLine{ R<:Number,
139143

140144
calc_Dinfs!(Dinfs, initial_Uinf, nelements)
141145

142-
146+
S = eltype(elements)
143147
new{R,
144-
eltype(elements),
148+
S, _count(S),
145149
VectorType, MatrixType, TensorType,
146150
typeof(linearindices)}(
147-
grid, linearindices,
151+
grid, linearindices, String[],
148152
ypositions,
149153
nelements, elements,
150154
aerocenters, horseshoes, Dinfs,
151155
midpoints, controlpoints, normals,
152-
aoas, Gammas,
156+
aoas, Gammas, Us,
153157
G, RHS,
154158
kerneloffset, kernelcutoff)
155159

@@ -189,10 +193,16 @@ function remorph!(self::LiftingLine, args...;
189193

190194
calc_Dinfs!(self, Uinf)
191195

196+
# Reset solution
197+
self.aoas .= 0
198+
self.Gammas .= 0
199+
self.Us .= 0
200+
192201
return nothing
193202
end
194203

195204

205+
196206
function save(self::LiftingLine, filename::AbstractString;
197207
format="vtk",
198208
horseshoe_suffix="_horseshoe",
@@ -265,7 +275,11 @@ function save(self::LiftingLine, filename::AbstractString;
265275
midpoints_data = [
266276
Dict( "field_name" => "angleofattack",
267277
"field_type" => "scalar",
268-
"field_data" => self.aoas)
278+
"field_data" => self.aoas),
279+
280+
Dict( "field_name" => "U",
281+
"field_type" => "vector",
282+
"field_data" => eachcol(self.Us))
269283
]
270284

271285
str *= gt.generateVTK(filename*midpoint_suffix, midpoints;
@@ -288,23 +302,41 @@ function save(self::LiftingLine, filename::AbstractString;
288302
point_data=controlpoints_data, optargs...)
289303

290304
# ------------- OUTPUT PLANAR GEOMETRY ------------------------------------
291-
planar_data = [
292-
Dict( "field_name" => "Gamma",
293-
"field_type" => "scalar",
294-
"field_data" => self.Gammas)
295-
296-
Dict( "field_name" => "angleofattack",
297-
"field_type" => "scalar",
298-
"field_data" => self.aoas)
299-
]
300305

301-
str *= gt.save(self.grid, filename*planar_suffix;
302-
cell_data=planar_data, format, optargs...)
306+
str *= gt.save(self.grid, filename*planar_suffix; format, optargs...)
303307

304308
return str
305309
end
306310

307311

312+
function add_field(self::LiftingLine, field_name::String, field_type::String,
313+
field_data, entry_type::String;
314+
raise_warn=false, collectfield=true)
315+
316+
# Add field to grid
317+
gt.add_field(self.grid, field_name, field_type,
318+
collectfield ? collect(field_data) : field_data, entry_type;
319+
raise_warn=raise_warn)
320+
321+
# Register the field
322+
if !(field_name in self.fields)
323+
push!(self.fields, field_name)
324+
end
325+
326+
nothing
327+
end
328+
329+
check_field(self::LiftingLine, field_name::String) = field_name in self.fields
330+
331+
function get_field(self::LiftingLine, field_name::String)
332+
333+
@assert check_field(self, field_name) ""*
334+
"Field $field_name not found! Available fields: $(self.fields)"
335+
336+
return self.grid.field[field_name]
337+
end
338+
339+
308340
function calc_horseshoes!(self::LiftingLine, args...; optargs...)
309341
return calc_horseshoes!(self.horseshoes,
310342
self.grid.nodes, self.linearindices,
@@ -552,9 +584,14 @@ function solve_linear(self::LiftingLine, Uinfs::AbstractMatrix;
552584
# Solve system of equations
553585
solver(self.Gammas, self.G, self.RHS; solver_optargs...)
554586

587+
# Calculate velocity at lifting-line midpoints
588+
self.Us .= Uinfs
589+
Uind!(self, self.midpoints, self.Us)
590+
555591
if addfields
556592
gt.add_field(self.grid, "Uinf", "vector", collect(eachcol(Uinfs)), "cell"; raise_warn)
557593
gt.add_field(self.grid, "Gamma", "scalar", self.Gammas, "cell"; raise_warn)
594+
gt.add_field(self.grid, "angleofattack", "scalar", self.aoas, "cell"; raise_warn)
558595
end
559596

560597
return nothing
Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
#=##############################################################################
2+
# DESCRIPTION
3+
Methods for postprocessing solver results of non-linear lifting line method
4+
5+
# AUTHORSHIP
6+
* Created by : Eduardo J. Alvarez
7+
* Email : Edo.AlvarezR@gmail.com
8+
* Date : Nov 2025
9+
* License : MIT License
10+
=###############################################################################
11+
12+
13+
################################################################################
14+
# FORCE FIELDS
15+
################################################################################
16+
"""
17+
calcfield_Fkj!(out::Vector, body::AbstractBody,
18+
areas::Vector, normals::Matrix, Cps::Vector,
19+
Uinf::Number, rho::Number;
20+
fieldname="F")
21+
22+
Calculate the force of each element using the Kutta-Joukowski theorem,
23+
``F = \\rho \\mathbf{u} \\times \\mathbf{l} \\Gamma``.
24+
``F`` is saved as a field named `fieldname`.
25+
26+
The field is calculated in-place and added to `out` (hence, make sure that `out`
27+
starts with all zeroes).
28+
"""
29+
function calcfield_Fkj!(out::AbstractMatrix, ll::LiftingLine, rho::Number;
30+
addfield=true, fieldname="Fkj")
31+
32+
# Error cases
33+
@assert size(out, 1)==3 && size(out, 2)==ll.nelements ""*
34+
"Invalid `out` matrix."*
35+
" Expected size $((3, ll.nelements)); got $(size(out))."
36+
37+
for ei in 1:ll.nelements # Iterate over horseshoes
38+
for (i, ip1) in ( # Iterate over bound vortices
39+
# (1, 2), # A - Ap
40+
(2, 3), # B - A
41+
# (3, 4) # Bp - B
42+
)
43+
44+
dl1 = ll.horseshoes[1, ip1, ei] - ll.horseshoes[1, i, ei]
45+
dl2 = ll.horseshoes[2, ip1, ei] - ll.horseshoes[2, i, ei]
46+
dl3 = ll.horseshoes[3, ip1, ei] - ll.horseshoes[3, i, ei]
47+
48+
out[1, ei] += rho * ll.Gammas[ei] * (ll.Us[2, ei]*dl3 - ll.Us[3, ei]*dl2)
49+
out[2, ei] += rho * ll.Gammas[ei] * (ll.Us[3, ei]*dl1 - ll.Us[1, ei]*dl3)
50+
out[3, ei] += rho * ll.Gammas[ei] * (ll.Us[1, ei]*dl2 - ll.Us[2, ei]*dl1)
51+
52+
end
53+
end
54+
55+
# Save field in lifting line
56+
if addfield
57+
add_field(ll, fieldname, "vector", eachcol(out), "cell")
58+
end
59+
60+
return out
61+
end
62+
63+
64+
"""
65+
calcfield_Fkj(args...; optargs...)
66+
67+
Similar to [`calcfield_Fkj!`](@ref) but automatically pre-allocating `out` if it
68+
hasn't been pre-allocated yet
69+
"""
70+
function calcfield_Fkj(ll::LiftingLine{R}, args...; fieldname="Fkj", optargs...) where {R}
71+
72+
if fieldname in ll.fields
73+
out = get_field(ll, fieldname)["field_data"]
74+
out .= 0
75+
else
76+
out = zeros(R, 3, ll.nelements)
77+
end
78+
79+
return calcfield_Fkj!(out, ll, args...; optargs...)
80+
81+
end
82+
83+
84+
85+
86+
"""
87+
calcfield_fkj!(out::Vector, body::AbstractBody,
88+
areas::Vector, normals::Matrix, Cps::Vector,
89+
Uinf::Number, rho::Number;
90+
fieldname="F")
91+
92+
Calculate loading distribution (force per unit span) using the Kutta-Joukowski
93+
theorem,
94+
``f = \\rho \\mathbf{u} \\times \\hat{\\mathbf{y}} \\Gamma``.
95+
``f`` is saved as a field named `fieldname`.
96+
97+
The field is calculated in-place and added to `out` (hence, make sure that `out`
98+
starts with all zeroes).
99+
"""
100+
function calcfield_fkj!(out::AbstractMatrix, ll::LiftingLine, rho::Number;
101+
addfield=true, fieldname="fkj")
102+
103+
# Error cases
104+
@assert size(out, 1)==3 && size(out, 2)==ll.nelements ""*
105+
"Invalid `out` matrix."*
106+
" Expected size $((3, ll.nelements)); got $(size(out))."
107+
108+
for ei in 1:ll.nelements # Iterate over horseshoes
109+
for (i, ip1) in ( # Iterate over bound vortices
110+
# (1, 2), # A - Ap
111+
(2, 3), # B - A
112+
# (3, 4) # Bp - B
113+
)
114+
115+
# Filament length
116+
dl1 = ll.horseshoes[1, ip1, ei] - ll.horseshoes[1, i, ei]
117+
dl2 = ll.horseshoes[2, ip1, ei] - ll.horseshoes[2, i, ei]
118+
dl3 = ll.horseshoes[3, ip1, ei] - ll.horseshoes[3, i, ei]
119+
120+
# Velocity
121+
U1 = ll.Us[1, ei]
122+
U2 = ll.Us[2, ei]
123+
U3 = ll.Us[3, ei]
124+
magU = sqrt(U1^2 + U2^2 + U3^2)
125+
126+
# Calculate tranversal length (counter-projection of U): dy = dl - (dl⋅hatU)hatU
127+
dldotU = dl1*U1 + dl2*U2 + dl3*U3
128+
dy1 = dl1 - dldotU*U1/magU^2
129+
dy2 = dl2 - dldotU*U2/magU^2
130+
dy3 = dl3 - dldotU*U3/magU^2
131+
magdy = sqrt(dy1^2 + dy2^2 + dy3^2)
132+
133+
out[1, ei] += rho * ll.Gammas[ei] * (U2*dy3 - U3*dy2)/magdy
134+
out[2, ei] += rho * ll.Gammas[ei] * (U3*dy1 - U1*dy3)/magdy
135+
out[3, ei] += rho * ll.Gammas[ei] * (U1*dy2 - U2*dy1)/magdy
136+
137+
end
138+
end
139+
140+
# Save field in lifting line
141+
if addfield
142+
add_field(ll, fieldname, "vector", eachcol(out), "cell")
143+
end
144+
145+
return out
146+
end
147+
148+
149+
"""
150+
calcfield_fkj(args...; optargs...)
151+
152+
Similar to [`calcfield_fkj!`](@ref) but automatically pre-allocating `out` if it
153+
hasn't been pre-allocated yet
154+
"""
155+
function calcfield_fkj(ll::LiftingLine{R}, args...; fieldname="fkj", optargs...) where {R}
156+
157+
if fieldname in ll.fields
158+
out = get_field(ll, fieldname)["field_data"]
159+
out .= 0
160+
else
161+
out = zeros(R, 3, ll.nelements)
162+
end
163+
164+
return calcfield_fkj!(out, ll, args...; optargs...)
165+
166+
end

src/FLOWPanel_liftingline_stripwise.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import CSV
1313
import DataFrames: DataFrame
1414
import AirfoilPrep
1515

16-
abstract type StripwiseElement end
16+
abstract type StripwiseElement <: AbstractElement end
1717

1818
################################################################################
1919
# JETFOIL ELEMENT STRUCT

0 commit comments

Comments
 (0)