Skip to content

Commit aef0e26

Browse files
committed
Improve linearize test
1 parent 5f0fa2e commit aef0e26

File tree

3 files changed

+149
-58
lines changed

3 files changed

+149
-58
lines changed

src/kite_geometry.jl

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -568,31 +568,40 @@ Distribute control angles across wing panels and apply smoothing using a moving
568568
# Returns
569569
- `nothing` (modifies wing in-place)
570570
"""
571-
function group_deform!(wing::RamAirWing, theta_angles::AbstractVector, delta_angles=nothing; smooth=false)
572-
!(wing.n_panels % length(theta_angles) == 0) && throw(ArgumentError("Number of angles has to be a multiple of number of panels"))
571+
function group_deform!(wing::RamAirWing, theta_angles=nothing, delta_angles=nothing; smooth=false)
572+
!isnothing(theta_angles) && !(wing.n_panels % length(theta_angles) == 0) &&
573+
throw(ArgumentError("Number of angles has to be a multiple of number of panels"))
574+
!isnothing(delta_angles) && !(wing.n_panels % length(delta_angles) == 0) &&
575+
throw(ArgumentError("Number of angles has to be a multiple of number of panels"))
576+
isnothing(theta_angles) && isnothing(delta_angles) && return nothing
577+
573578
n_panels = wing.n_panels
574579
theta_dist = wing.theta_dist
575580
delta_dist = wing.delta_dist
581+
n_angles = isnothing(theta_angles) ? length(delta_angles) : length(theta_angles)
576582

577583
dist_idx = 0
578-
for angle_idx in eachindex(theta_angles)
579-
for _ in 1:(wing.n_panels ÷ length(theta_angles))
584+
for angle_idx in 1:n_angles
585+
for _ in 1:(wing.n_panels ÷ n_angles)
580586
dist_idx += 1
581-
theta_dist[dist_idx] = theta_angles[angle_idx]
587+
!isnothing(theta_angles) && (theta_dist[dist_idx] = theta_angles[angle_idx])
582588
!isnothing(delta_angles) && (delta_dist[dist_idx] = delta_angles[angle_idx])
583589
end
584590
end
585591
@assert (dist_idx == wing.n_panels)
586592

587593
if smooth
588-
window_size = wing.n_panels ÷ length(theta_angles)
594+
window_size = wing.n_panels ÷ n_angles
589595
if n_panels > window_size
590596
smoothed = wing.cache[1][theta_dist]
591-
smoothed .= theta_dist
592-
for i in (window_size÷2 + 1):(n_panels - window_size÷2)
593-
@views smoothed[i] = mean(theta_dist[(i - window_size÷2):(i + window_size÷2)])
597+
598+
if !isnothing(theta_angles)
599+
smoothed .= theta_dist
600+
for i in (window_size÷2 + 1):(n_panels - window_size÷2)
601+
@views smoothed[i] = mean(theta_dist[(i - window_size÷2):(i + window_size÷2)])
602+
end
603+
theta_dist .= smoothed
594604
end
595-
theta_dist .= smoothed
596605

597606
if !isnothing(delta_angles)
598607
smoothed .= delta_dist

src/solver.jl

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -703,19 +703,25 @@ function linearize(solver::Solver, body_aero::BodyAerodynamics, y::Vector{T};
703703
@views theta_angles = isnothing(theta_idxs) ? nothing : y[theta_idxs]
704704
@views delta_angles = isnothing(delta_idxs) ? nothing : y[delta_idxs]
705705

706-
if !isnothing(theta_angles)
707-
if isnothing(delta_angles)
708-
if !all(theta_angles .== last_theta)
709-
VortexStepMethod.group_deform!(wing, theta_angles; smooth=false)
710-
VortexStepMethod.init!(body_aero; init_aero=false)
711-
last_theta .= theta_angles
712-
end
713-
elseif !all(delta_angles .== last_delta) || !all(theta_angles .== last_theta)
706+
if !isnothing(theta_angles) && isnothing(delta_angles)
707+
if !all(theta_angles .== last_theta)
708+
VortexStepMethod.group_deform!(wing, theta_angles, nothing; smooth=false)
709+
VortexStepMethod.init!(body_aero; init_aero=false)
710+
last_theta .= theta_angles
711+
end
712+
elseif !isnothing(theta_angles) && !isnothing(delta_angles)
713+
if !all(delta_angles .== last_delta) || !all(theta_angles .== last_theta)
714714
VortexStepMethod.group_deform!(wing, theta_angles, delta_angles; smooth=false)
715715
VortexStepMethod.init!(body_aero; init_aero=false)
716716
last_theta .= theta_angles
717717
last_delta .= delta_angles
718718
end
719+
elseif isnothing(theta_angles) && !isnothing(delta_angles)
720+
if !all(delta_angles .== last_delta)
721+
VortexStepMethod.group_deform!(wing, nothing, delta_angles; smooth=false)
722+
VortexStepMethod.init!(body_aero; init_aero=false)
723+
last_delta .= delta_angles
724+
end
719725
end
720726

721727
if !isnothing(va_idxs) && isnothing(omega_idxs)

test/test_results.jl

Lines changed: 116 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -132,49 +132,125 @@ end
132132

133133
# Test combinations of input variations
134134
@testset "Combined Input Variations" begin
135-
# Sample some key combinations
135+
# For combination testing, we'll create targeted test cases
136+
# that use only the specific indices we want to test together
136137
combination_tests = [
137-
("Theta + VA", [dtheta_magnitudes[1] * ones(4); dva_magnitudes[1] * ones(3); zeros(3); zeros(4)]),
138-
("Theta + Omega", [dtheta_magnitudes[1] * ones(4); zeros(3); domega_magnitudes[1] * ones(3); zeros(4)]),
139-
("VA + Omega", [zeros(4); dva_magnitudes[1] * ones(3); domega_magnitudes[1] * ones(3); zeros(4)]),
140-
("Delta + VA", [zeros(4); dva_magnitudes[1] * ones(3); zeros(3); ddelta_magnitudes[1] * ones(4)]),
141-
("All Inputs", [dtheta_magnitudes[1] * ones(4); dva_magnitudes[1] * ones(3);
142-
domega_magnitudes[1] * ones(3); ddelta_magnitudes[1] * ones(4)])
138+
# Name, active indices, perturbation vector, indices mappings
139+
(
140+
"Theta + VA",
141+
# Use only theta and va indices
142+
[1:4; 5:7],
143+
# Perturbation values for these indices
144+
[dtheta_magnitudes; dva_magnitudes],
145+
# Mappings for linearize function
146+
(theta_idxs=1:4, va_idxs=5:7, omega_idxs=nothing, delta_idxs=nothing)
147+
),
148+
(
149+
"Theta + Omega",
150+
[1:4; 8:10],
151+
[dtheta_magnitudes; domega_magnitudes],
152+
(theta_idxs=1:4, va_idxs=nothing, omega_idxs=5:7, delta_idxs=nothing)
153+
),
154+
(
155+
"VA + Omega",
156+
[5:7; 8:10],
157+
[dva_magnitudes; domega_magnitudes],
158+
(theta_idxs=nothing, va_idxs=1:3, omega_idxs=4:6, delta_idxs=nothing)
159+
),
160+
(
161+
"Delta + VA",
162+
[5:7; 11:14],
163+
[dva_magnitudes; ddelta_magnitudes],
164+
(theta_idxs=nothing, va_idxs=1:3, omega_idxs=nothing, delta_idxs=4:7)
165+
),
166+
(
167+
"All Inputs",
168+
[1:4; 5:7; 8:10; 11:14],
169+
[dtheta_magnitudes; dva_magnitudes;
170+
domega_magnitudes; ddelta_magnitudes],
171+
(theta_idxs=1:4, va_idxs=5:7, omega_idxs=8:10, delta_idxs=11:14)
172+
)
143173
]
144174

145-
for (combo_name, perturbation) in combination_tests
146-
# Reset to baseline
147-
VortexStepMethod.group_deform!(ram_wing, theta, delta; smooth=false)
148-
init!(body_aero; init_aero=false, va=va, omega=zeros(3))
149-
150-
# Extract components
151-
perturbed_theta = theta + perturbation[1:4]
152-
perturbed_va = va + perturbation[5:7]
153-
perturbed_omega = perturbation[8:10]
154-
perturbed_delta = delta + perturbation[11:14]
155-
156-
# Apply to nonlinear model
157-
VortexStepMethod.group_deform!(ram_wing, perturbed_theta, perturbed_delta; smooth=false)
158-
init!(body_aero; init_aero=false, va=perturbed_va, omega=perturbed_omega)
159-
160-
# Get nonlinear solution
161-
nonlin_res = VortexStepMethod.solve!(solver, body_aero; log=false)
162-
nonlin_res = [solver.sol.force; solver.sol.moment; solver.sol.group_moment_dist]
163-
164-
# Compute linearized prediction
165-
lin_prediction = lin_res + jac * perturbation
166-
167-
# Calculate error ratio
168-
prediction_error = norm(lin_prediction - nonlin_res)
169-
baseline_difference = norm(lin_res - nonlin_res)
170-
error_ratio = prediction_error / baseline_difference
171-
172-
@info "$combo_name error ratio: $error_ratio"
173-
174-
# Test combinations
175-
@test lin_res nonlin_res atol=1e-2
176-
@test lin_prediction nonlin_res rtol=0.2 atol=0.05
177-
@test error_ratio < 2e-3
175+
for (combo_name, active_indices, perturbation, idx_mappings) in combination_tests
176+
@testset "$combo_name" begin
177+
# Start with a fresh model for each combination test
178+
VortexStepMethod.group_deform!(ram_wing, theta, delta; smooth=false)
179+
init!(body_aero; init_aero=false, va, omega)
180+
181+
# Create the appropriate input vector for this combination
182+
input_vec = Vector{Float64}(undef, length(active_indices))
183+
184+
# Fill with base values
185+
if !isnothing(idx_mappings.theta_idxs)
186+
input_vec[idx_mappings.theta_idxs] .= theta
187+
end
188+
if !isnothing(idx_mappings.va_idxs)
189+
input_vec[idx_mappings.va_idxs] .= va
190+
end
191+
if !isnothing(idx_mappings.omega_idxs)
192+
input_vec[idx_mappings.omega_idxs] .= omega
193+
end
194+
if !isnothing(idx_mappings.delta_idxs)
195+
input_vec[idx_mappings.delta_idxs] .= delta
196+
end
197+
198+
# Get the Jacobian and linearization result for this specific combination
199+
jac_combo, lin_res_combo = VortexStepMethod.linearize(
200+
solver,
201+
body_aero,
202+
input_vec;
203+
idx_mappings...
204+
)
205+
206+
# Get baseline results
207+
baseline_res = VortexStepMethod.solve!(solver, body_aero; log=false)
208+
baseline_res = [solver.sol.force; solver.sol.moment; solver.sol.group_moment_dist]
209+
210+
# Should match the linearization result
211+
@test baseline_res lin_res_combo
212+
213+
# Apply perturbation using the appropriate indices
214+
perturbed_input = copy(input_vec) + perturbation
215+
216+
# Extract components based on the combination being tested
217+
perturbed_theta = !isnothing(idx_mappings.theta_idxs) ?
218+
perturbed_input[idx_mappings.theta_idxs] : theta
219+
220+
perturbed_va = !isnothing(idx_mappings.va_idxs) ?
221+
perturbed_input[idx_mappings.va_idxs] : va
222+
223+
perturbed_omega = !isnothing(idx_mappings.omega_idxs) ?
224+
perturbed_input[idx_mappings.omega_idxs] : omega
225+
226+
perturbed_delta = !isnothing(idx_mappings.delta_idxs) ?
227+
perturbed_input[idx_mappings.delta_idxs] : delta
228+
229+
# Apply to nonlinear model
230+
VortexStepMethod.group_deform!(ram_wing, perturbed_theta, perturbed_delta; smooth=false)
231+
init!(body_aero; init_aero=false, va=perturbed_va, omega=perturbed_omega)
232+
233+
# Get nonlinear solution with perturbation
234+
nonlin_res = VortexStepMethod.solve!(solver, body_aero; log=false)
235+
nonlin_res = [solver.sol.force; solver.sol.moment; solver.sol.group_moment_dist]
236+
237+
# Compute linearized prediction using our specialized Jacobian
238+
lin_prediction = lin_res_combo + jac_combo * perturbation
239+
240+
# Ensure perturbation had an effect
241+
@test baseline_res nonlin_res atol=1e-3
242+
243+
# Calculate error ratio
244+
prediction_error = norm(lin_prediction - nonlin_res)
245+
baseline_difference = norm(baseline_res - nonlin_res)
246+
error_ratio = prediction_error / baseline_difference
247+
248+
@info "$combo_name error metrics" prediction_error baseline_difference error_ratio
249+
250+
# Validate the prediction
251+
@test lin_prediction nonlin_res rtol=0.1 atol=1e-3
252+
@test error_ratio < 0.05
253+
end
178254
end
179255
end
180256
end

0 commit comments

Comments
 (0)