Skip to content

Commit cf048c3

Browse files
committed
2 parents cb089ea + af5430b commit cf048c3

File tree

6 files changed

+194
-21
lines changed

6 files changed

+194
-21
lines changed

src/body_aerodynamics.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ function init!(body_aero::BodyAerodynamics;
126126
panel_props.x_airf[i],
127127
panel_props.y_airf[i],
128128
panel_props.z_airf[i],
129-
beta
129+
beta;
130+
remove_nan=wing.remove_nan
130131
)
131132
idx += 1
132133
end

src/kite_geometry.jl

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,56 @@ function calculate_inertia_tensor(vertices, faces, mass, com)
239239
return (mass / total_area) * I / 3
240240
end
241241

242+
"""
243+
interpolate_matrix_nans!(matrix::Matrix{Float64})
244+
245+
Replace NaN values in a matrix by interpolating from nearest non-NaN neighbors.
246+
Uses an expanding search radius until valid neighbors are found.
247+
248+
# Arguments
249+
- `matrix`: Matrix containing NaN values to be interpolated
250+
"""
251+
function interpolate_matrix_nans!(matrix::Matrix{Float64})
252+
rows, cols = size(matrix)
253+
nans_found = 0
254+
while any(isnan, matrix)
255+
for i in 1:rows, j in 1:cols
256+
if isnan(matrix[i,j])
257+
# Search in expanding radius until we find valid neighbors
258+
radius = 1
259+
values = Float64[]
260+
weights = Float64[]
261+
262+
while isempty(values) && radius < max(rows, cols)
263+
# Check all points at current Manhattan distance
264+
for di in -radius:radius, dj in -radius:radius
265+
if abs(di) + abs(dj) == radius # Points exactly at distance 'radius'
266+
ni, nj = i + di, j + dj
267+
if 1 ni rows && 1 nj cols && !isnan(matrix[ni,nj])
268+
# Weight by inverse distance
269+
dist = sqrt(di^2 + dj^2)
270+
push!(values, matrix[ni,nj])
271+
push!(weights, 1/dist)
272+
end
273+
end
274+
end
275+
radius += 1
276+
end
277+
278+
if !isempty(values)
279+
# Calculate weighted average of found values
280+
matrix[i,j] = sum(values .* weights) / sum(weights)
281+
nans_found += 1
282+
else
283+
throw(ArgumentError("Could not remove NaN"))
284+
end
285+
end
286+
end
287+
end
288+
@info "Removed $nans_found NaNs from the matrix."
289+
return matrix
290+
end
291+
242292

243293
"""
244294
KiteWing
@@ -252,6 +302,7 @@ Represents a curved wing that inherits from Wing with additional geometric prope
252302
- `spanwise_direction::MVec3`: Wing span direction vector
253303
- `sections::Vector{Section}`: List of wing sections, see: [Section](@ref)
254304
- refined_sections::Vector{Section}
305+
- `remove_nan::Bool`: Wether to remove the NaNs from interpolations or not
255306
- Additional fields:
256307
- `center_of_mass::Vector{Float64}`: Center of mass coordinates
257308
- `circle_center_z::Vector{Float64}`: Center of circle coordinates
@@ -269,6 +320,7 @@ mutable struct KiteWing <: AbstractWing
269320
spanwise_direction::MVec3
270321
sections::Vector{Section}
271322
refined_sections::Vector{Section}
323+
remove_nan::Bool
272324

273325
# Additional fields for KiteWing
274326
non_deformed_sections::Vector{Section}
@@ -288,7 +340,7 @@ end
288340
"""
289341
KiteWing(obj_path, dat_path; alpha=0.0, crease_frac=0.75, wind_vel=10., mass=1.0,
290342
n_panels=54, n_sections=n_panels+1, spanwise_panel_distribution=UNCHANGED,
291-
spanwise_direction=[0.0, 1.0, 0.0])
343+
spanwise_direction=[0.0, 1.0, 0.0], remove_nan::Bool=true)
292344
293345
Constructor for a [KiteWing](@ref) that allows to use an `.obj` and a `.dat` file as input.
294346
@@ -306,10 +358,11 @@ Constructor for a [KiteWing](@ref) that allows to use an `.obj` and a `.dat` fil
306358
- `n_sections`=n_panels+1: Number of sections (there is a section on each side of each panel.)
307359
- `spanwise_panel_distribution`=UNCHANGED: see: [PanelDistribution](@ref)
308360
- `spanwise_direction`=[0.0, 1.0, 0.0]
361+
- `remove_nan::Bool`: Wether to remove the NaNs from interpolations or not
309362
"""
310363
function KiteWing(obj_path, dat_path; alpha=0.0, crease_frac=0.75, wind_vel=10., mass=1.0,
311364
n_panels=54, n_sections=n_panels+1, spanwise_panel_distribution=UNCHANGED,
312-
spanwise_direction=[0.0, 1.0, 0.0])
365+
spanwise_direction=[0.0, 1.0, 0.0], remove_nan=true)
313366

314367
!isapprox(spanwise_direction, [0.0, 1.0, 0.0]) && throw(ArgumentError("Spanwise direction has to be [0.0, 1.0, 0.0], not $spanwise_direction"))
315368

@@ -346,6 +399,11 @@ function KiteWing(obj_path, dat_path; alpha=0.0, crease_frac=0.75, wind_vel=10.,
346399

347400
@info "Loading polars and kite info from $polar_path and $info_path"
348401
(alpha_range, beta_range, cl_matrix::Matrix, cd_matrix::Matrix, cm_matrix::Matrix) = deserialize(polar_path)
402+
if remove_nan
403+
interpolate_matrix_nans!(cl_matrix)
404+
interpolate_matrix_nans!(cd_matrix)
405+
interpolate_matrix_nans!(cm_matrix)
406+
end
349407

350408
(center_of_mass, inertia_tensor, circle_center_z, radius, gamma_tip,
351409
le_interp, te_interp, area_interp) = deserialize(info_path)
@@ -359,7 +417,7 @@ function KiteWing(obj_path, dat_path; alpha=0.0, crease_frac=0.75, wind_vel=10.,
359417
push!(sections, Section(LE_point, TE_point, POLAR_MATRICES, aero_data))
360418
end
361419

362-
KiteWing(n_panels, spanwise_panel_distribution, spanwise_direction, sections, sections, sections,
420+
KiteWing(n_panels, spanwise_panel_distribution, spanwise_direction, sections, sections, remove_nan, sections,
363421
mass, center_of_mass, circle_center_z, gamma_tip, inertia_tensor, radius,
364422
le_interp, te_interp, area_interp, zeros(n_panels), zeros(n_panels))
365423
end

src/panel.jl

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# static types for interpolations
22
const I1 = Interpolations.FilledExtrapolation{Float64, 1, Interpolations.GriddedInterpolation{Float64, 1, Vector{Float64}, Interpolations.Gridded{Interpolations.Linear{Interpolations.Throw{Interpolations.OnGrid}}}, Tuple{Vector{Float64}}}, Interpolations.Gridded{Interpolations.Linear{Interpolations.Throw{Interpolations.OnGrid}}}, Float64}
3-
const I2 = Interpolations.FilledExtrapolation{Float64, 2, Interpolations.GriddedInterpolation{Float64, 2, Matrix{Float64}, Interpolations.Gridded{Interpolations.Linear{Interpolations.Throw{Interpolations.OnGrid}}}, Tuple{Vector{Float64}, Vector{Float64}}}, Interpolations.Gridded{Interpolations.Linear{Interpolations.Throw{Interpolations.OnGrid}}}, Float64}
3+
const I2 = Interpolations.Extrapolation{Float64, 1, Interpolations.GriddedInterpolation{Float64, 1, Vector{Float64}, Interpolations.Gridded{Interpolations.Linear{Interpolations.Throw{Interpolations.OnGrid}}}, Tuple{Vector{Float64}}}, Interpolations.Gridded{Interpolations.Linear{Interpolations.Throw{Interpolations.OnGrid}}}, Interpolations.Line{Nothing}}
4+
const I3 = Interpolations.FilledExtrapolation{Float64, 2, Interpolations.GriddedInterpolation{Float64, 2, Matrix{Float64}, Interpolations.Gridded{Interpolations.Linear{Interpolations.Throw{Interpolations.OnGrid}}}, Tuple{Vector{Float64}, Vector{Float64}}}, Interpolations.Gridded{Interpolations.Linear{Interpolations.Throw{Interpolations.OnGrid}}}, Float64}
5+
const I4 = Interpolations.Extrapolation{Float64, 2, Interpolations.GriddedInterpolation{Float64, 2, Matrix{Float64}, Interpolations.Gridded{Interpolations.Linear{Interpolations.Throw{Interpolations.OnGrid}}}, Tuple{Vector{Float64}, Vector{Float64}}}, Interpolations.Gridded{Interpolations.Linear{Interpolations.Throw{Interpolations.OnGrid}}}, Interpolations.Line{Nothing}}
46

57
"""
68
@with_kw mutable struct Panel
@@ -50,9 +52,9 @@ Represents a panel in a vortex step method simulation. All points and vectors ar
5052
cl_coeffs::Vector{Float64} = zeros(Float64, 3)
5153
cd_coeffs::Vector{Float64} = zeros(Float64, 3)
5254
cm_coeffs::Vector{Float64} = zeros(Float64, 3)
53-
cl_interp::Union{Nothing, I1, I2} = nothing
54-
cd_interp::Union{Nothing, I1, I2} = nothing
55-
cm_interp::Union{Nothing, I1, I2} = nothing
55+
cl_interp::Union{Nothing, I1, I2, I3, I4} = nothing
56+
cd_interp::Union{Nothing, I1, I2, I3, I4} = nothing
57+
cm_interp::Union{Nothing, I1, I2, I3, I4} = nothing
5658
aero_center::MVec3 = zeros(MVec3)
5759
control_point::MVec3 = zeros(MVec3)
5860
bound_point_1::MVec3 = zeros(MVec3)
@@ -113,7 +115,8 @@ end
113115
function init_aero!(
114116
panel::Panel,
115117
section_1::Section,
116-
section_2::Section,
118+
section_2::Section;
119+
remove_nan = true
117120
)
118121
# Validate aero model consistency
119122
panel.aero_model = section_1.aero_model
@@ -132,6 +135,12 @@ function init_aero!(
132135
throw(ArgumentError("Polar data must have same shape"))
133136
end
134137

138+
if remove_nan
139+
extrapolation_bc = Line()
140+
else
141+
extrapolation_bc = NaN
142+
end
143+
135144
if panel.aero_model == POLAR_VECTORS
136145
!all(isapprox.(aero_1[1], aero_2[1])) && @error "Make sure you use the same alpha range for all your interpolations."
137146

@@ -142,9 +151,9 @@ function init_aero!(
142151
)
143152
alphas = Vector{Float64}(aero_1[1])
144153

145-
panel.cl_interp = linear_interpolation(alphas, polar_data[1]; extrapolation_bc=NaN)
146-
panel.cd_interp = linear_interpolation(alphas, polar_data[2]; extrapolation_bc=NaN)
147-
panel.cm_interp = linear_interpolation(alphas, polar_data[3]; extrapolation_bc=NaN)
154+
panel.cl_interp = linear_interpolation(alphas, polar_data[1]; extrapolation_bc)
155+
panel.cd_interp = linear_interpolation(alphas, polar_data[2]; extrapolation_bc)
156+
panel.cm_interp = linear_interpolation(alphas, polar_data[3]; extrapolation_bc)
148157

149158
elseif panel.aero_model == POLAR_MATRICES
150159
!all(isapprox.(aero_1[1], aero_2[1])) && @error "Make sure you use the same alpha range for all your interpolations."
@@ -158,9 +167,9 @@ function init_aero!(
158167
alphas = Vector{Float64}(aero_1[1])
159168
betas = Vector{Float64}(aero_1[2])
160169

161-
panel.cl_interp = linear_interpolation((alphas, betas), polar_data[1]; extrapolation_bc=NaN)
162-
panel.cd_interp = linear_interpolation((alphas, betas), polar_data[2]; extrapolation_bc=NaN)
163-
panel.cm_interp = linear_interpolation((alphas, betas), polar_data[3]; extrapolation_bc=NaN)
170+
panel.cl_interp = linear_interpolation((alphas, betas), polar_data[1]; extrapolation_bc)
171+
panel.cd_interp = linear_interpolation((alphas, betas), polar_data[2]; extrapolation_bc)
172+
panel.cm_interp = linear_interpolation((alphas, betas), polar_data[3]; extrapolation_bc)
164173
else
165174
throw(ArgumentError("Polar data in wrong format: $aero_1"))
166175
end
@@ -182,11 +191,12 @@ function init!(
182191
y_airf::PosVector,
183192
z_airf::PosVector,
184193
beta;
185-
init_aero = true
194+
init_aero = true,
195+
remove_nan = true
186196
)
187197
init_pos!(panel, section_1, section_2, aero_center, control_point, bound_point_1, bound_point_2,
188198
x_airf, y_airf, z_airf, beta)
189-
init_aero && init_aero!(panel, section_1, section_2)
199+
init_aero && init_aero!(panel, section_1, section_2; remove_nan)
190200
return nothing
191201
end
192202

src/wing_geometry.jl

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ Represents a wing composed of multiple sections with aerodynamic properties.
6969
- `spanwise_direction::MVec3`: Wing span direction vector
7070
- `sections::Vector{Section}`: Vector of wing sections, see: [Section](@ref)
7171
- `refined_sections::Vector{Section}`: Vector of refined wing sections, see: [Section](@ref)
72+
- `remove_nan::Bool`: Wether to remove the NaNs from interpolations or not
7273
7374
"""
7475
mutable struct Wing <: AbstractWing
@@ -77,12 +78,14 @@ mutable struct Wing <: AbstractWing
7778
spanwise_direction::MVec3
7879
sections::Vector{Section}
7980
refined_sections::Vector{Section}
81+
remove_nan::Bool
8082
end
8183

8284
"""
8385
Wing(n_panels::Int;
8486
spanwise_panel_distribution::PanelDistribution=LINEAR,
85-
spanwise_direction::PosVector=MVec3([0.0, 1.0, 0.0]))
87+
spanwise_direction::PosVector=MVec3([0.0, 1.0, 0.0]),
88+
remove_nan::Bool=true)
8689
8790
Constructor for a [Wing](@ref) struct with default values that initializes the sections
8891
and refined sections as empty arrays.
@@ -91,11 +94,13 @@ and refined sections as empty arrays.
9194
- `n_panels::Int64`: Number of panels in aerodynamic mesh
9295
- `spanwise_panel_distribution`::PanelDistribution = LINEAR: [PanelDistribution](@ref)
9396
- `spanwise_direction::MVec3` = MVec3([0.0, 1.0, 0.0]): Wing span direction vector
97+
- `remove_nan::Bool`: Wether to remove the NaNs from interpolations or not
9498
"""
9599
function Wing(n_panels::Int;
96100
spanwise_panel_distribution::PanelDistribution=LINEAR,
97-
spanwise_direction::PosVector=MVec3([0.0, 1.0, 0.0]))
98-
Wing(n_panels, spanwise_panel_distribution, spanwise_direction, Section[], Section[])
101+
spanwise_direction::PosVector=MVec3([0.0, 1.0, 0.0]),
102+
remove_nan=true)
103+
Wing(n_panels, spanwise_panel_distribution, spanwise_direction, Section[], Section[], remove_nan)
99104
end
100105

101106
function init!(wing::AbstractWing; aero_center_location::Float64=0.25, control_point_location::Float64=0.75)
@@ -111,6 +116,36 @@ function init!(wing::AbstractWing; aero_center_location::Float64=0.25, control_p
111116
return panel_props
112117
end
113118

119+
120+
"""
121+
remove_vector_nans(aero_data)
122+
123+
Remove the indices from aero_data where a NaN is found.
124+
"""
125+
function remove_vector_nans(aero_data)
126+
alpha_range, cl_vector, cd_vector, cm_vector = aero_data
127+
alpha_range = collect(alpha_range)
128+
nan_indices = Set{Int}()
129+
for vec in (cl_vector, cd_vector, cm_vector)
130+
union!(nan_indices, findall(isnan, vec))
131+
end
132+
if isempty(nan_indices)
133+
return aero_data
134+
end
135+
# Convert to sorted array for consistent removal
136+
nan_indices = sort(collect(nan_indices))
137+
# Create mask for valid indices
138+
valid_mask = trues(length(alpha_range))
139+
valid_mask[nan_indices] .= false
140+
# Remove NaN values from all vectors
141+
clean_alpha = alpha_range[valid_mask]
142+
clean_cl = cl_vector[valid_mask]
143+
clean_cd = cd_vector[valid_mask]
144+
clean_cm = cm_vector[valid_mask]
145+
@info "Removed $(length(nan_indices)) indices containing NaNs from aero_data."
146+
return (clean_alpha, clean_cl, clean_cd, clean_cm)
147+
end
148+
114149
"""
115150
add_section!(wing::Wing, LE_point::PosVector, TE_point::PosVector,
116151
aero_model, aero_data::AeroData=nothing)
@@ -126,6 +161,11 @@ Add a new section to the wing.
126161
"""
127162
function add_section!(wing::Wing, LE_point::Vector{Float64},
128163
TE_point::Vector{Float64}, aero_model::AeroModel, aero_data::AeroData=nothing)
164+
if aero_model === POLAR_VECTORS && wing.remove_nan
165+
aero_data = remove_vector_nans(aero_data)
166+
elseif aero_model === POLAR_MATRICES && wing.remove_nan
167+
interpolate_matrix_nans!.(aero_data[3:5])
168+
end
129169
push!(wing.sections, Section(LE_point, TE_point, aero_model, aero_data))
130170
return nothing
131171
end

test/test_kite_geometry.jl

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ using Serialization
9999
cm_matrix[i,j] = -0.1*sin(deg2rad(alphas[i]))
100100
end
101101
end
102+
cl_matrix[end] = NaN
103+
cd_matrix[end] = NaN
104+
cm_matrix[end] = NaN
102105

103106
# Serialize polar data
104107
polar_path = test_dat_path[1:end-4] * "_polar.bin"
@@ -140,7 +143,7 @@ using Serialization
140143
end
141144

142145
@testset "KiteWing Construction" begin
143-
wing = KiteWing(test_obj_path, test_dat_path)
146+
wing = KiteWing(test_obj_path, test_dat_path; remove_nan=true)
144147

145148
@test wing.n_panels == 54 # Default value
146149
@test wing.spanwise_panel_distribution == UNCHANGED
@@ -150,6 +153,14 @@ using Serialization
150153
@test length(wing.center_of_mass) == 3
151154
@test wing.radius r rtol=1e-2
152155
@test wing.gamma_tip π/4 rtol=1e-2
156+
@test !isnan(wing.sections[1].aero_data[3][end])
157+
@test !isnan(wing.sections[1].aero_data[4][end])
158+
@test !isnan(wing.sections[1].aero_data[5][end])
159+
160+
wing = KiteWing(test_obj_path, test_dat_path; remove_nan=false)
161+
@test isnan(wing.sections[1].aero_data[3][end])
162+
@test isnan(wing.sections[1].aero_data[4][end])
163+
@test isnan(wing.sections[1].aero_data[5][end])
153164
end
154165

155166
rm(test_obj_path)

test/test_wing_geometry.jl

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,59 @@ end
3939
@test section.aero_model === INVISCID
4040
end
4141

42+
@testset "Vector polar data" begin
43+
wing = Wing(2; remove_nan=true)
44+
45+
# Create vector data with NaNs
46+
alpha = range(-10, 10, 21)
47+
cl = cos.(alpha)
48+
cd = sin.(alpha)
49+
cm = zeros(21)
50+
51+
# Insert NaNs
52+
cl[5] = NaN
53+
cd[10] = NaN
54+
cm[15] = NaN
55+
56+
aero_data = (collect(alpha), cl, cd, cm)
57+
add_section!(wing, [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], POLAR_VECTORS, aero_data)
58+
59+
# Check if NaNs were removed consistently
60+
cleaned_data = wing.sections[1].aero_data
61+
@test length(cleaned_data[1]) == 18 # 21 - 3 NaN positions
62+
@test !any(isnan, cleaned_data[2]) # cl
63+
@test !any(isnan, cleaned_data[3]) # cd
64+
@test !any(isnan, cleaned_data[4]) # cm
65+
@test all(diff(cleaned_data[1]) .> 0) # alpha still monotonic
66+
end
67+
68+
@testset "Matrix polar data" begin
69+
wing = Wing(2)
70+
71+
# Create matrix data with NaNs
72+
alpha = collect(range(-10, 10, 21))
73+
beta = collect(range(-5, 5, 11))
74+
cl = [cos(a) * cos(b) for a in alpha, b in beta]
75+
cd = [sin(a) * sin(b) for a in alpha, b in beta]
76+
cm = zeros(21, 11)
77+
78+
# Insert NaNs at various positions
79+
cl[5,3] = NaN
80+
cd[10,5] = NaN
81+
cm[15,7] = NaN
82+
83+
aero_data = (alpha, beta, cl, cd, cm)
84+
add_section!(wing, [0.0, 0.0, 0.0], [1.0, 0.0, 0.0], POLAR_MATRICES, aero_data)
85+
86+
# Check if NaNs were removed consistently
87+
cleaned_data = wing.sections[1].aero_data
88+
@test !any(isnan, cleaned_data[3]) # cl
89+
@test !any(isnan, cleaned_data[4]) # cd
90+
@test !any(isnan, cleaned_data[5]) # cm
91+
@test all(diff(cleaned_data[1]) .> 0) # alpha still monotonic
92+
@test all(diff(cleaned_data[2]) .> 0) # beta still monotonic
93+
end
94+
4295
@testset "Robustness left to right" begin
4396
example_wing = Wing(10)
4497
# Test correct order

0 commit comments

Comments
 (0)