11using LinearAlgebra
22using VortexStepMethod
33using GLMakie
4+ using DelimitedFiles
45
56PLOT = true
67USE_TEX = false
@@ -57,7 +58,7 @@ USE_TEX = false
5758
5859# Solve and plot combined analysis
5960results = 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
78281nothing
0 commit comments