Skip to content

Commit 23ebc3a

Browse files
authored
Feat/xfoil (#43)
* add xfoil polar generation files * add dependencies * before testing * add interpolations model type * working example * add polar tests * remove comment * fix examples * remove unused file * remove unnecesary show * bigger angle range * print in deg * reduce range * fix angle range * make sure center of mass has zero y * define constants * add refs * extrapolate with NaN * remove name from 3d models * dont remove nan
1 parent f516568 commit 23ebc3a

File tree

13 files changed

+507
-132
lines changed

13 files changed

+507
-132
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
Manifest.toml
22
.vscode/settings.json
33
venv
4+
results/TUDELFT_V3_LEI_KITE/polars/$C_L$ vs $C_D$.pdf
5+
*.bin
46
results/TUDELFT_V3_LEI_KITE/polars/tutorial_testing_stall_model_n_panels_54_distribution_split_provided.pdf
57
docs/build/
8+

Project.toml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,19 @@ BenchmarkTools = "6e4b80f9-dd63-53aa-95a3-0cdb28fa8baf"
88
Colors = "5ae59095-9a9b-59fe-a467-6f913c188581"
99
ControlPlots = "23c2ee80-7a9e-4350-b264-8e670f12517c"
1010
DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab"
11+
Distributed = "8ba89e20-285c-5b6f-9357-94700520ee1b"
1112
Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
1213
LaTeXStrings = "b964fa9f-0449-5b57-a5c2-d3ea65f4040f"
1314
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
1415
Logging = "56ddb016-857b-54e1-b83d-db4d58db5568"
1516
Measures = "442fdcdd-2543-5da2-b0f3-8c86c306513e"
1617
NonlinearSolve = "8913a72c-1f9b-4ce2-8d82-65094dcecaec"
18+
Serialization = "9e88b42a-f829-5b0c-bbe9-9e923198166b"
19+
SharedArrays = "1a1011a3-84de-559e-8e89-a11a2f7dc383"
1720
StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
1821
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
22+
Timers = "21f18d07-b854-4dab-86f0-c15a3821819a"
23+
Xfoil = "19641d66-a62d-11e8-2441-8f57a969a9c4"
1924

2025
[compat]
2126
BenchmarkTools = "1"

data/ram_air_kite_foil.dat

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
A
2+
1.0000000 0.0000479
3+
0.9666341 0.0017363
4+
0.9038341 0.0066154
5+
0.8342935 0.0136262
6+
0.7624304 0.0214202
7+
0.6907649 0.0299758
8+
0.6203655 0.0397261
9+
0.5500472 0.0504400
10+
0.4788028 0.0614558
11+
0.4074159 0.0732084
12+
0.3368400 0.0848204
13+
0.2692757 0.0941998
14+
0.2083741 0.0988242
15+
0.1573868 0.0983570
16+
0.1169140 0.0924067
17+
0.0828168 0.0826568
18+
0.0560463 0.0704667
19+
0.0361758 0.0577955
20+
0.0215498 0.0452424
21+
0.0115826 0.0329061
22+
0.0055625 0.0219130
23+
0.0019945 0.0124293
24+
0.0002467 0.0035649
25+
0.0004755 -0.0045913
26+
0.0027675 -0.0130849
27+
0.0077900 -0.0220373
28+
0.0162007 -0.0306320
29+
0.0285895 -0.0396469
30+
0.0466671 -0.0494062
31+
0.0728758 -0.0593274
32+
0.1094090 -0.0681156
33+
0.1556860 -0.0747603
34+
0.2093904 -0.0783204
35+
0.2712652 -0.0798598
36+
0.3359510 -0.0788043
37+
0.4038697 -0.0747532
38+
0.4742506 -0.0687364
39+
0.5453696 -0.0615892
40+
0.6158254 -0.0528273
41+
0.6875958 -0.0427482
42+
0.7594140 -0.0320344
43+
0.8304601 -0.0220930
44+
0.9008044 -0.0130373
45+
0.9663063 -0.0044401
46+
1.0000000 0.0000479

examples/ram_air_kite.jl

Lines changed: 10 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,7 @@ using DataFrames
44
using LinearAlgebra
55

66
# Create wing geometry
7-
wing = KiteWing("data/HL5_ram_air_kite_body.obj")
8-
9-
alphas = deg2rad.(-10:1:25) # Range of angles from -10 to 25 degrees
10-
polars = zeros(length(alphas), 4) # Matrix for [alpha, CD, CL, CM]
11-
for (i, alpha) in enumerate(alphas)
12-
# Simplified aerodynamic coefficients
13-
cd = 0.015 + 0.015 * abs(alpha/10)^1.5 # Drag increases with angle
14-
cl = 5.0 * alpha + 0.01 * alpha^2 * exp(-alpha/20) # Lift with stall behavior
15-
cm = -0.02 * alpha # Linear pitching moment
16-
17-
polars[i, :] .= [alpha, cd, cl, cm]
18-
end
19-
20-
for gamma in range(wing.gamma_tip - wing.gamma_tip/10, -wing.gamma_tip + wing.gamma_tip/10, 20)
21-
add_section!(wing, gamma, ("polar_data", polars))
22-
end
7+
wing = KiteWing("data/ram_air_kite_body.obj", "data/ram_air_kite_foil.dat")
238
wing_aero = WingAerodynamics([wing])
249

2510
# Create solvers
@@ -59,33 +44,32 @@ plot_geometry(
5944

6045
# Solving and plotting distributions
6146
@time results = solve(VSM, wing_aero)
62-
@time results_with_stall = solve(VSM_with_stall_correction, wing_aero)
47+
@time results = solve(VSM, wing_aero)
6348

6449
CAD_y_coordinates = [panel.aerodynamic_center[2] for panel in wing_aero.panels]
6550

6651
plot_distribution(
67-
[CAD_y_coordinates, CAD_y_coordinates],
68-
[results, results_with_stall],
69-
["VSM", "VSM with stall correction"];
52+
[CAD_y_coordinates],
53+
[results],
54+
["VSM"];
7055
title="CAD_spanwise_distributions_alpha_$(round(aoa, digits=1))_beta_$(round(side_slip, digits=1))_yaw_$(round(yaw_rate, digits=1))_v_a_$(round(v_a, digits=1))",
7156
data_type=".pdf",
7257
is_save=false,
7358
is_show=true
7459
)
7560

7661
plot_polars(
77-
[VSM, VSM_with_stall_correction],
78-
[wing_aero, wing_aero],
62+
[VSM],
63+
[wing_aero],
7964
[
80-
"VSM CAD 19ribs",
81-
"VSM CAD 19ribs , with stall correction",
65+
"VSM from Ram Air Kite OBJ and DAT file",
8266
];
83-
angle_range=range(0, 25, length=25),
67+
angle_range=range(0, 20, length=20),
8468
angle_type="angle_of_attack",
8569
angle_of_attack=0,
8670
side_slip=0,
8771
v_a=10,
88-
title="tutorial_testing_stall_model_n_panels_$(wing.n_panels)_distribution_$(wing.spanwise_panel_distribution)",
72+
title="ram_kite_panels_$(wing.n_panels)_distribution_$(wing.spanwise_panel_distribution)",
8973
data_type=".pdf",
9074
is_save=false,
9175
is_show=true

scripts/polars.jl

Lines changed: 254 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,254 @@
1+
using Distributed, Timers, Serialization, SharedArrays
2+
using Interpolations
3+
using Xfoil
4+
using ControlPlots
5+
using Logging
6+
7+
const SPEED_OF_SOUND = 343 # https://en.wikipedia.org/wiki/Speed_of_sound
8+
const KINEMATIC_VISCOSITY = 1.460e-5 # https://en.wikipedia.org/wiki/Reynolds_number
9+
10+
@info "Creating polars. This can take several minutes."
11+
tic()
12+
13+
procs = addprocs()
14+
15+
try
16+
function normalize!(x, y)
17+
x_min = minimum(x)
18+
x_max = maximum(x)
19+
for i in eachindex(x)
20+
x[i] = (x[i] - x_min) / (x_max - x_min)
21+
y[i] = (y[i] - x_min) / (x_max - x_min)
22+
end
23+
end
24+
25+
@eval @everywhere using VortexStepMethod, Xfoil, Statistics, SharedArrays
26+
27+
alphas = -deg2rad(5):deg2rad(1.0):deg2rad(20)
28+
d_trailing_edge_angles = -deg2rad(5):deg2rad(1.0):deg2rad(20)
29+
30+
cl_matrix = SharedArray{Float64}((length(alphas), length(d_trailing_edge_angles)))
31+
cd_matrix = SharedArray{Float64}((length(alphas), length(d_trailing_edge_angles)))
32+
cm_matrix = SharedArray{Float64}((length(alphas), length(d_trailing_edge_angles)))
33+
34+
@everywhere begin
35+
function turn_trailing_edge!(angle, x, y, lower_turn, upper_turn, x_turn)
36+
turn_distance = upper_turn - lower_turn
37+
smooth_idx = []
38+
rm_idx = []
39+
40+
sign = angle > 0 ? 1 : -1
41+
y_turn = angle > 0 ? upper_turn : lower_turn
42+
for i in eachindex(x)
43+
if x_turn - turn_distance < x[i] < x_turn + turn_distance && sign * y[i] > 0
44+
append!(smooth_idx, i)
45+
elseif sign * y[i] < 0 && x_turn > x[i] > x_turn - turn_distance
46+
append!(rm_idx, i)
47+
end
48+
if x[i] > x_turn
49+
x_rel = x[i] - x_turn
50+
y_rel = y[i] - y_turn
51+
x[i] = x_turn + x_rel * cos(angle) + y_rel * sin(angle)
52+
y[i] = y_turn - x_rel * sin(angle) + y_rel * cos(angle)
53+
if angle > 0 && x[i] < x_turn - turn_distance/2 && y[i] > lower_turn
54+
append!(rm_idx, i)
55+
elseif angle < 0 && x[i] < x_turn - turn_distance/2 && y[i] < upper_turn
56+
append!(rm_idx, i)
57+
end
58+
end
59+
end
60+
61+
#TODO: lower and upper is slightly off because of smoothing
62+
lower_i, upper_i = minimum(smooth_idx), maximum(smooth_idx)
63+
for i in smooth_idx
64+
window = min(i - lower_i + 1, upper_i - i + 1)
65+
x[i] = mean(x[i-window:i+window])
66+
end
67+
deleteat!(x, rm_idx)
68+
deleteat!(y, rm_idx)
69+
nothing
70+
end
71+
72+
function solve_alpha!(cls, cds, cms, alphas, alpha_idxs, d_trailing_edge_angle, re, x_, y_, lower, upper, kite_speed, speed_of_sound, x_turn)
73+
x = deepcopy(x_)
74+
y = deepcopy(y_)
75+
turn_trailing_edge!(d_trailing_edge_angle, x, y, lower, upper, x_turn)
76+
Xfoil.set_coordinates(x, y)
77+
x, y = Xfoil.pane(npan=140)
78+
reinit = true
79+
for (alpha, alpha_idx) in zip(alphas, alpha_idxs)
80+
converged = false
81+
cl = 0.0
82+
cd = 0.0
83+
# Solve for the given angle of attack
84+
cl, cd, _, cm, converged = Xfoil.solve_alpha(rad2deg(alpha), re; iter=50, reinit=reinit, mach=kite_speed/speed_of_sound, xtrip=(0.05, 0.05))
85+
reinit = false
86+
if converged
87+
cls[alpha_idx] = cl
88+
cds[alpha_idx] = cd
89+
cms[alpha_idx] = cm
90+
end
91+
end
92+
return nothing
93+
end
94+
95+
function run_solve_alpha(alphas, d_trailing_edge_angle, re, x_, y_, lower, upper, kite_speed, speed_of_sound, x_turn)
96+
@info "solving alpha with trailing edge angle: $(rad2deg(d_trailing_edge_angle)) degrees"
97+
cls = Float64[NaN for _ in alphas]
98+
cds = Float64[NaN for _ in alphas]
99+
cms = Float64[NaN for _ in alphas]
100+
neg_idxs = sort(findall(alphas .< 0.0), rev=true)
101+
neg_alphas = alphas[neg_idxs]
102+
pos_idxs = sort(findall(alphas .>= 0.0))
103+
pos_alphas = alphas[pos_idxs]
104+
solve_alpha!(cls, cds, cms, neg_alphas, neg_idxs, d_trailing_edge_angle,
105+
re, x_, y_, lower, upper, kite_speed, speed_of_sound, x_turn)
106+
solve_alpha!(cls, cds, cms, pos_alphas, pos_idxs, d_trailing_edge_angle,
107+
re, x_, y_, lower, upper, kite_speed, speed_of_sound, x_turn)
108+
return cls, cds, cms
109+
end
110+
end
111+
112+
function get_lower_upper(x, y)
113+
lower_trailing_edge = 0.0
114+
upper_trailing_edge = 0.0
115+
min_lower_distance = Inf
116+
min_upper_distance = Inf
117+
for (xi, yi) in zip(x, y)
118+
if yi < 0
119+
lower_distance = abs(xi - x_turn)
120+
if lower_distance < min_lower_distance
121+
min_lower_distance = lower_distance
122+
lower_trailing_edge = yi
123+
end
124+
else
125+
upper_distance = abs(xi - x_turn)
126+
if upper_distance < min_upper_distance
127+
min_upper_distance = upper_distance
128+
upper_trailing_edge = yi
129+
end
130+
end
131+
end
132+
return lower_trailing_edge, upper_trailing_edge
133+
end
134+
135+
function replace_nan!(matrix)
136+
rows, cols = size(matrix)
137+
distance = max(rows, cols)
138+
for i in 1:rows
139+
for j in 1:cols
140+
if isnan(matrix[i, j])
141+
neighbors = []
142+
for d in 1:distance
143+
found = false
144+
if i-d >= 1 && !isnan(matrix[i-d, j]);
145+
push!(neighbors, matrix[i-d, j])
146+
found = true
147+
end
148+
if i+d <= rows && !isnan(matrix[i+d, j])
149+
push!(neighbors, matrix[i+d, j])
150+
found = true
151+
end
152+
if j-d >= 1 && !isnan(matrix[i, j-d])
153+
push!(neighbors, matrix[i, j-d])
154+
found = true
155+
end
156+
if j+d <= cols && !isnan(matrix[i, j+d])
157+
push!(neighbors, matrix[i, j+d])
158+
found = true
159+
end
160+
if found; break; end
161+
end
162+
if !isempty(neighbors)
163+
matrix[i, j] = sum(neighbors) / length(neighbors)
164+
end
165+
end
166+
end
167+
end
168+
return nothing
169+
end
170+
171+
kite_speed = v_wind
172+
chord_length = area / width
173+
local reynolds_number = kite_speed * chord_length / KINEMATIC_VISCOSITY # https://en.wikipedia.org/wiki/Reynolds_number
174+
175+
# Read airfoil coordinates from a file.
176+
local x, y = open(foil_path, "r") do f
177+
x = Float64[]
178+
y = Float64[]
179+
for line in eachline(f)
180+
entries = split(chomp(line))
181+
try
182+
push!(x, parse(Float64, entries[1]))
183+
push!(y, parse(Float64, entries[2]))
184+
catch ArgumentError
185+
println(entries)
186+
end
187+
end
188+
x, y
189+
end
190+
normalize!(x, y)
191+
Xfoil.set_coordinates(x, y)
192+
x, y = Xfoil.pane(npan=140)
193+
lower, upper = get_lower_upper(x, y)
194+
195+
@everywhere begin
196+
x = $x
197+
y = $y
198+
x_turn = $x_turn
199+
reynolds_number = $reynolds_number
200+
end
201+
202+
@sync @distributed for j in eachindex(d_trailing_edge_angles)
203+
cl_matrix[:, j], cd_matrix[:, j], cm_matrix[:, j] = run_solve_alpha(alphas, d_trailing_edge_angles[j],
204+
reynolds_number, x, y, lower, upper, kite_speed, SPEED_OF_SOUND, x_turn)
205+
end
206+
display(cl_matrix)
207+
208+
function plot_values(alphas, d_trailing_edge_angles, matrix, interp, name)
209+
fig = plt.figure()
210+
ax = fig.add_subplot(projection="3d")
211+
212+
X_data = collect(d_trailing_edge_angles) .+ zeros(length(alphas))'
213+
Y_data = collect(alphas)' .+ zeros(length(d_trailing_edge_angles))
214+
215+
interp_matrix = similar(matrix)
216+
int_alphas, int_d_trailing_edge_angles = alphas .+ deg2rad(0.5), d_trailing_edge_angles .+ deg2rad(0.5)
217+
interp_matrix .= [interp(alpha, d_trailing_edge_angle) for alpha in int_alphas, d_trailing_edge_angle in int_d_trailing_edge_angles]
218+
X_int = collect(int_d_trailing_edge_angles) .+ zeros(length(int_alphas))'
219+
Y_int = collect(int_alphas)' .+ zeros(length(int_d_trailing_edge_angles))
220+
221+
ax.plot_wireframe(X_data, Y_data, matrix, edgecolor="royalblue", lw=0.5, rstride=5, cstride=5, alpha=0.6)
222+
ax.plot_wireframe(X_int, Y_int, interp_matrix, edgecolor="orange", lw=0.5, rstride=5, cstride=5, alpha=0.6)
223+
plt.xlabel("Alpha")
224+
plt.ylabel("Flap angle")
225+
plt.zlabel("$name values")
226+
plt.title("$name for different d_flap and angle")
227+
plt.legend()
228+
plt.grid(true)
229+
plt.show()
230+
end
231+
232+
cl_interp = extrapolate(scale(interpolate(cl_matrix, BSpline(Linear())), alphas, d_trailing_edge_angles), NaN)
233+
cd_interp = extrapolate(scale(interpolate(cd_matrix, BSpline(Linear())), alphas, d_trailing_edge_angles), NaN)
234+
cm_interp = extrapolate(scale(interpolate(cm_matrix, BSpline(Linear())), alphas, d_trailing_edge_angles), NaN)
235+
236+
plot_values(alphas, d_trailing_edge_angles, cl_matrix, cl_interp, "Cl")
237+
plot_values(alphas, d_trailing_edge_angles, cd_matrix, cd_interp, "Cd")
238+
plot_values(alphas, d_trailing_edge_angles, cm_matrix, cm_interp, "Cm")
239+
240+
@info "Relative trailing_edge height: $(upper - lower)"
241+
@info "Reynolds number for flying speed of $kite_speed is $reynolds_number"
242+
243+
serialize(polar_path, (cl_interp, cd_interp, cm_interp))
244+
245+
catch e
246+
@info "Removing processes"
247+
rmprocs(procs)
248+
throw(e)
249+
finally
250+
@info "Removing processes"
251+
rmprocs(procs)
252+
end
253+
254+
toc()

0 commit comments

Comments
 (0)