Skip to content

Commit 15cd4bd

Browse files
committed
added alpha_geometric_dist to the solver output, and updated example V3_kite to also test solve! output
1 parent 01a418c commit 15cd4bd

2 files changed

Lines changed: 233 additions & 3 deletions

File tree

examples/V3_kite.jl

Lines changed: 205 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using LinearAlgebra
22
using VortexStepMethod
33
using GLMakie
4+
using DelimitedFiles
45

56
PLOT = true
67
USE_TEX = false
@@ -57,7 +58,7 @@ USE_TEX = false
5758

5859
# Solve and plot combined analysis
5960
results = VortexStepMethod.solve(solver, body_aero; log=true)
60-
PLOT && plot_combined_analysis(
61+
fig1 = plot_combined_analysis(
6162
solver,
6263
body_aero,
6364
results;
@@ -69,10 +70,212 @@ PLOT && plot_combined_analysis(
6970
side_slip=sideslip_deg,
7071
v_a=wind_speed,
7172
title="TU Delft V3 Kite",
72-
is_show=true,
73+
is_show=false,
7374
use_tex=USE_TEX,
7475
angle_of_attack_for_spanwise_distribution=10.0,
7576
)
77+
scr1 = display(fig1)
78+
wait(scr1)
79+
80+
81+
# Polar sweep including CMy, using solve! and calculate_results
82+
function compute_polar_with_cm(
83+
solver,
84+
body_aero,
85+
angle_range;
86+
angle_type::String="angle_of_attack",
87+
angle_of_attack::Float64=0.0,
88+
side_slip::Float64=0.0,
89+
v_a::Float64=10.0
90+
)
91+
n_angles = length(angle_range)
92+
cl = zeros(n_angles)
93+
cd = zeros(n_angles)
94+
cs = zeros(n_angles)
95+
cmy = fill(NaN, n_angles)
96+
reynolds_number = zeros(n_angles)
97+
98+
gamma_prev = solver.sol.gamma_distribution
99+
for (i, angle_i) in enumerate(angle_range)
100+
if angle_type == "angle_of_attack"
101+
α = deg2rad(angle_i)
102+
β = deg2rad(side_slip)
103+
elseif angle_type == "side_slip"
104+
α = deg2rad(angle_of_attack)
105+
β = deg2rad(angle_i)
106+
else
107+
throw(ArgumentError("angle_type must be 'angle_of_attack' or 'side_slip'"))
108+
end
109+
110+
set_va!(body_aero, [cos(α) * cos(β), sin(β), sin(α)] * v_a)
111+
solve!(solver, body_aero, gamma_prev; log=false)
112+
gamma_prev = solver.sol.gamma_distribution
113+
114+
results_local = calculate_results(
115+
body_aero,
116+
solver.lr.gamma_new,
117+
zeros(MVec3),
118+
solver.density,
119+
solver.aerodynamic_model_type,
120+
solver.core_radius_fraction,
121+
solver.mu,
122+
solver.lr.alpha_dist,
123+
solver.lr.v_a_dist,
124+
solver.sol._chord_dist,
125+
solver.sol._x_airf_dist,
126+
solver.sol._y_airf_dist,
127+
solver.sol._z_airf_dist,
128+
solver.sol._va_dist,
129+
solver.br.va_norm_dist,
130+
solver.br.va_unit_dist,
131+
body_aero.panels,
132+
solver.is_only_f_and_gamma_output;
133+
correct_aoa=solver.correct_aoa
134+
)
135+
136+
cl[i] = results_local["cl"]
137+
cd[i] = results_local["cd"]
138+
cs[i] = results_local["cs"]
139+
cmy[i] = get(results_local, "cmy", NaN)
140+
reynolds_number[i] = results_local["Rey"]
141+
end
142+
143+
return (angle=angle_range, cl=cl, cd=cd, cs=cs, cmy=cmy, rey=reynolds_number)
144+
end
145+
146+
function plot_polars_with_cmy(
147+
solver_list,
148+
body_aero_list,
149+
label_list;
150+
literature_path_list::Vector{String}=String[],
151+
angle_range=range(-10, 40, step=1),
152+
angle_type::String="angle_of_attack",
153+
angle_of_attack::Float64=0.0,
154+
side_slip::Float64=0.0,
155+
v_a::Float64=10.0,
156+
title::String="polar_with_cm",
157+
fig_size::Tuple{Int,Int}=(1200, 800),
158+
angle_xlim::Tuple{Real,Real}=(-10, 40)
159+
)
160+
total_cases = length(body_aero_list) + length(literature_path_list)
161+
length(label_list) == total_cases || throw(ArgumentError("labels length ($(length(label_list))) must match number of cases ($total_cases)"))
162+
length(solver_list) == length(body_aero_list) || throw(ArgumentError("solver_list length must match body_aero_list length"))
163+
164+
polar_data_list = Vector{Any}()
165+
labels_full = String[]
166+
167+
# Computational cases
168+
for (solver_i, body, lbl) in zip(solver_list, body_aero_list, label_list[1:length(solver_list)])
169+
pd = compute_polar_with_cm(
170+
solver_i,
171+
body,
172+
angle_range;
173+
angle_type=angle_type,
174+
angle_of_attack=angle_of_attack,
175+
side_slip=side_slip,
176+
v_a=v_a
177+
)
178+
@info "polar sample (solver)" label=lbl first_cl=pd.cl[1] first_cd=pd.cd[1] first_cs=pd.cs[1] first_cmy=pd.cmy[1]
179+
push!(polar_data_list, pd)
180+
re_tag = round(Int, first(pd.rey) * 1e-5)
181+
push!(labels_full, "$(lbl) Re=$(re_tag)e5")
182+
end
183+
184+
# Literature cases
185+
for (path, lbl) in zip(literature_path_list, label_list[length(solver_list)+1:end])
186+
data = readdlm(path, ',')
187+
header_raw = string.(data[1, :])
188+
header = lowercase.(strip.(header_raw))
189+
alpha_idx = findfirst(x -> occursin("alpha", x) || occursin("aoa", x), header)
190+
cl_idx = findfirst(x -> occursin("cl", x), header)
191+
cd_idx = findfirst(x -> occursin("cd", x), header)
192+
cs_idx = findfirst(x -> occursin("cs", x), header)
193+
cmy_idx = findfirst(x -> occursin("cmy", x), header)
194+
195+
parse_col(col) = begin
196+
vals = Float64[]
197+
for v in col
198+
if v isa Real
199+
push!(vals, Float64(v))
200+
else
201+
s = strip(String(v))
202+
y = tryparse(Float64, s)
203+
push!(vals, isnothing(y) ? NaN : y)
204+
end
205+
end
206+
vals
207+
end
208+
209+
alpha_col = parse_col(data[2:end, alpha_idx])
210+
cl_col = parse_col(data[2:end, cl_idx])
211+
cd_col = parse_col(data[2:end, cd_idx])
212+
cs_col = cs_idx === nothing ? zeros(size(data, 1)-1) : parse_col(data[2:end, cs_idx])
213+
cmy_col = cmy_idx === nothing ? nothing : parse_col(data[2:end, cmy_idx])
214+
215+
push!(polar_data_list, (angle=alpha_col, cl=cl_col, cd=cd_col, cs=cs_col, cmy=cmy_col, rey=fill(NaN, length(alpha_col))))
216+
push!(labels_full, lbl)
217+
end
218+
219+
fig = Figure(size=fig_size)
220+
Label(fig[0, :], title; fontsize=20, font=:bold)
221+
222+
ax_cl = Axis(fig[1, 1], title="CL vs $angle_type [deg]", xlabel="$angle_type [deg]", ylabel="CL [-]")
223+
ax_cd = Axis(fig[1, 2], title="CD vs $angle_type [deg]", xlabel="$angle_type [deg]", ylabel="CD [-]")
224+
ax_cmy = Axis(fig[1, 3], title="CMy vs $angle_type [deg]", xlabel="$angle_type [deg]", ylabel="CMy [-]")
225+
ax_cs = Axis(fig[2, 1], title="CS vs $angle_type [deg]", xlabel="$angle_type [deg]", ylabel="CS [-]")
226+
ax_polar = Axis(fig[2, 2], title="CL vs CD", xlabel="CD [-]", ylabel="CL [-]")
227+
228+
xlims!(ax_cl, angle_xlim...)
229+
xlims!(ax_cd, angle_xlim...)
230+
xlims!(ax_cmy, angle_xlim...)
231+
xlims!(ax_cs, angle_xlim...)
232+
233+
colors = Makie.wong_colors()
234+
235+
for (idx, (pd, lbl)) in enumerate(zip(polar_data_list, labels_full))
236+
color = colors[mod1(idx, length(colors))]
237+
238+
lines!(ax_cl, pd.angle, pd.cl; color, label=lbl)
239+
scatter!(ax_cl, pd.angle, pd.cl; color, markersize=6)
240+
241+
lines!(ax_cd, pd.angle, pd.cd; color, label=lbl)
242+
scatter!(ax_cd, pd.angle, pd.cd; color, markersize=6)
243+
244+
lines!(ax_cs, pd.angle, pd.cs; color, label=lbl)
245+
scatter!(ax_cs, pd.angle, pd.cs; color, markersize=6)
246+
247+
lines!(ax_polar, pd.cd, pd.cl; color, label=lbl)
248+
scatter!(ax_polar, pd.cd, pd.cl; color, markersize=6)
249+
250+
if pd.cmy !== nothing
251+
cmy_vals = Float64.(pd.cmy)
252+
if !all(isnan, cmy_vals)
253+
lines!(ax_cmy, pd.angle, cmy_vals; color, label=lbl)
254+
scatter!(ax_cmy, pd.angle, cmy_vals; color, markersize=6)
255+
end
256+
end
257+
end
258+
259+
Legend(fig[2, 3], ax_cl)
260+
return fig
261+
end
262+
263+
264+
fig2 = plot_polars_with_cmy(
265+
[solver],
266+
[body_aero],
267+
labels;
268+
literature_path_list=literature_paths,
269+
angle_range=range(-5, 40, step=1),
270+
angle_type="angle_of_attack",
271+
angle_of_attack=angle_of_attack_deg,
272+
side_slip=sideslip_deg,
273+
v_a=wind_speed,
274+
title="Testing solve! on V3 kite",
275+
angle_xlim=(-5, 40)
276+
)
277+
scr2 = display(fig2)
278+
wait(scr2)
76279

77280

78281
nothing

src/solver.jl

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ Struct for storing the solution of the [solve!](@ref) function. Must contain all
4343
### end of private vectors
4444
width_dist::Vector{Float64} = zeros(P)
4545
alpha_dist::Vector{Float64} = zeros(P)
46+
alpha_geometric_dist::Vector{Float64} = zeros(P)
4647
cl_dist::Vector{Float64} = zeros(P)
4748
cd_dist::Vector{Float64} = zeros(P)
4849
cm_dist::Vector{Float64} = zeros(P)
@@ -208,6 +209,7 @@ function solve!(solver::Solver, body_aero::BodyAerodynamics, gamma_distribution=
208209
converged = solver.lr.converged
209210
alpha_dist = solver.lr.alpha_dist
210211
alpha_corrected = solver.sol.alpha_dist
212+
alpha_geometric_dist = solver.sol.alpha_geometric_dist
211213
v_a_dist = solver.lr.v_a_dist
212214
panels = body_aero.panels
213215

@@ -224,6 +226,28 @@ function solve!(solver::Solver, body_aero::BodyAerodynamics, gamma_distribution=
224226
cl_dist[i] = calculate_cl(panel, alpha_dist[i])
225227
cd_dist[i], cm_dist[i] = calculate_cd_cm(panel, alpha_dist[i])
226228
width_dist[i] = panel.width
229+
230+
# Geometric AoA using panel-local axes and prescribed freestream
231+
# @views makes slices like solver.sol._va_dist[i, :] return views instead of copies.
232+
# Without it, each [...] would allocate a new array; with it, you reuse a lightweight
233+
# window into the original array, which cuts allocations in this tight loop.
234+
@views begin
235+
va_panel = solver.sol._va_dist[i, :]
236+
x_airf = solver.sol._x_airf_dist[i, :]
237+
z_airf = solver.sol._z_airf_dist[i, :]
238+
va_norm = norm(va_panel)
239+
x_norm = norm(x_airf)
240+
z_norm = norm(z_airf)
241+
if va_norm == 0 || x_norm == 0 || z_norm == 0
242+
alpha_geometric_dist[i] = NaN
243+
else
244+
v_unit = va_panel / va_norm
245+
v_tangential = dot(x_airf, -v_unit) / x_norm
246+
v_normal = dot(z_airf, -v_unit) / z_norm
247+
alpha_geometric_dist[i] = pi + atan(v_normal, v_tangential)
248+
end
249+
end
250+
227251
end
228252

229253
# create an alias for the three vertical output vectors
@@ -465,6 +489,10 @@ function solve(solver::Solver, body_aero::BodyAerodynamics, gamma_distribution=n
465489
solver.is_only_f_and_gamma_output;
466490
correct_aoa=solver.correct_aoa
467491
)
492+
# Attach geometric AoA (already computed in calculate_results) to solver.sol
493+
if haskey(results, "alpha_geometric")
494+
solver.sol.alpha_geometric_dist .= results["alpha_geometric"]
495+
end
468496
return results
469497
end
470498

@@ -885,4 +913,3 @@ function linearize(solver::Solver, body_aero::BodyAerodynamics, y::Vector{T};
885913
calc_results!(results, y)
886914
return jac, results
887915
end
888-

0 commit comments

Comments
 (0)