11using VortexStepMethod
2- using VortexStepMethod: calculate_cl, calculate_cd_cm, calculate_projected_area, calculate_AIC_matrices!
2+ using VortexStepMethod: calculate_cl, calculate_cd_cm, calculate_projected_area, calculate_AIC_matrices!, init!
33using LinearAlgebra
44using Test
55using Logging
66
7- # if !@isdefined ram_wing
7+ if ! @isdefined ram_wing
88 cp (" data/ram_air_kite_body.obj" , " /tmp/ram_air_kite_body.obj" ; force= true )
99 cp (" data/ram_air_kite_foil.dat" , " /tmp/ram_air_kite_foil.dat" ; force= true )
10- ram_wing = RamAirWing (" /tmp/ram_air_kite_body.obj" , " /tmp/ram_air_kite_foil.dat" )
11- # end
10+ ram_wing = RamAirWing (" /tmp/ram_air_kite_body.obj" , " /tmp/ram_air_kite_foil.dat" ; alpha_range= deg2rad .(- 1 : 1 ), delta_range= deg2rad .(- 1 : 1 ))
11+ end
12+
13+ # @testset "Nonlinear vs Linear" begin
14+ # va = [15.0, 0.0, 0.0]
15+ # theta = zeros(4)
16+ # delta = zeros(4)
17+ # omega = zeros(3)
18+
19+ # body_aero = BodyAerodynamics([ram_wing]; va)
20+ # solver = Solver(body_aero;
21+ # aerodynamic_model_type=VSM,
22+ # is_with_artificial_damping=false,
23+ # solver_type=NONLIN
24+ # )
1225
13- # ram_wing = RamAirWing("data/ram_air_kite_body.obj", "data/ram_air_kite_foil.dat")
26+ # jac, lin_res = VortexStepMethod.linearize(
27+ # solver,
28+ # body_aero,
29+ # ram_wing,
30+ # [theta; va; omega; delta];
31+ # theta_idxs=1:4,
32+ # va_idxs=5:7,
33+ # omega_idxs=8:10,
34+ # delta_idxs=11:14,
35+ # moment_frac=0.1
36+ # )
37+ # nonlin_res = VortexStepMethod.solve!(solver, body_aero; log=true)
38+ # nonlin_res = [solver.sol.force; solver.sol.moment; solver.sol.group_moment_dist]
1439
15- @testset " Nonlinear vs Linear" begin
40+ # @test nonlin_res ≈ lin_res
41+
42+ # dva = [0.01, 0.01, 0.01]
43+ # init!(body_aero; init_aero=false, va=va+dva)
44+ # nonlin_res = VortexStepMethod.solve!(solver, body_aero; log=true)
45+ # nonlin_res = [solver.sol.force; solver.sol.moment; solver.sol.group_moment_dist]
46+ # @test lin_res ≉ nonlin_res atol=1e-2
47+ # @test lin_res + jac * [zeros(4); dva; zeros(3); zeros(4)] ≈ nonlin_res atol=1e-2
48+ # # Test accuracy
49+ # @test norm(lin_res + jac * [zeros(4); dva; zeros(3); zeros(4)] - nonlin_res) /
50+ # norm(lin_res - nonlin_res) < 2e-3
51+ # end
52+
53+ @testset " Nonlinear vs Linear - Comprehensive Input Testing" begin
54+ # Initialize with base parameters
1655 va = [15.0 , 0.0 , 0.0 ]
1756 theta = zeros (4 )
1857 delta = zeros (4 )
1958 omega = zeros (3 )
20-
21- body_aero = BodyAerodynamics ([ram_wing]; va)
59+
60+ # Define perturbation magnitudes
61+ dva_magnitudes = [0.01 , 0.01 , 0.01 ] # Velocity perturbations (m/s)
62+ dtheta_magnitudes = [deg2rad (0.1 ), deg2rad (0.5 ), deg2rad (1.0 ), deg2rad (2.0 )] # Angle perturbations (rad)
63+ ddelta_magnitudes = [deg2rad (0.1 ), deg2rad (0.5 ), deg2rad (1.0 ), deg2rad (2.0 )] # Trailing edge perturbations (rad)
64+ domega_magnitudes = [deg2rad (0.1 ), deg2rad (0.5 ), deg2rad (1.0 )] # Angular rate perturbations (rad/s)
65+
66+ # Create body aerodynamics and solver
67+ VortexStepMethod. group_deform! (ram_wing, theta, delta; smooth= false )
68+ body_aero = BodyAerodynamics ([ram_wing]; va, omega)
2269 solver = Solver (body_aero;
2370 aerodynamic_model_type= VSM,
2471 is_with_artificial_damping= false ,
72+ atol= 1e-8 ,
73+ rtol= 1e-8 ,
2574 solver_type= NONLIN
2675 )
27-
28- jac, res = VortexStepMethod. linearize (
76+
77+ # Get initial Jacobian and linearization result
78+ base_inputs = [theta; va; omega; delta]
79+ jac, lin_res = VortexStepMethod. linearize (
2980 solver,
3081 body_aero,
31- ram_wing,
32- [theta; va; omega; delta];
82+ base_inputs;
3383 theta_idxs= 1 : 4 ,
3484 va_idxs= 5 : 7 ,
3585 omega_idxs= 8 : 10 ,
3686 delta_idxs= 11 : 14 ,
3787 moment_frac= 0.1
3888 )
39- results = VortexStepMethod. solve! (solver, body_aero; log= true )
40-
41- @show jac res
89+
90+ # Verify that linearization results match nonlinear results at operating point
91+ baseline_res = VortexStepMethod. solve! (solver, body_aero; log= false )
92+ baseline_res = [solver. sol. force; solver. sol. moment; solver. sol. group_moment_dist]
93+ @test baseline_res ≈ lin_res
94+
95+ # Define test cases
96+ test_cases = [
97+ (" va" , 5 : 7 , dva_magnitudes),
98+ (" theta" , 1 : 4 , dtheta_magnitudes),
99+ (" omega" , 8 : 10 , domega_magnitudes),
100+ (" delta" , 11 : 14 , ddelta_magnitudes)
101+ ]
102+
103+ # For each test case
104+ for (input_name, indices, magnitudes) in test_cases
105+ @testset " Input: $input_name " begin
106+ max_error_ratio = 0.0
107+
108+ # Select one index at a time for focused testing
109+ for idx in indices
110+ # Test each magnitude
111+ for mag in magnitudes
112+ # Prepare perturbation vector
113+ perturbation = zeros (length (base_inputs))
114+
115+ if length (indices) > 1 && input_name != " theta" && input_name != " delta"
116+ # For vector quantities (va, omega), perturb all components together
117+ perturbation[indices] .= mag
118+ else
119+ # For control angles, perturb one at a time
120+ perturbation[idx] = mag
121+ end
122+
123+ # Reset to baseline
124+ reset_va = copy (va)
125+ reset_theta = copy (theta)
126+ reset_delta = copy (delta)
127+ reset_omega = zeros (3 )
128+
129+ # Apply the perturbation to the nonlinear model
130+ if input_name == " va"
131+ reset_va = va + perturbation[5 : 7 ]
132+ elseif input_name == " theta"
133+ reset_theta = theta + perturbation[1 : 4 ]
134+ elseif input_name == " delta"
135+ reset_delta = delta + perturbation[11 : 14 ]
136+ elseif input_name == " omega"
137+ reset_omega = perturbation[8 : 10 ]
138+ else
139+ throw (ArgumentError ())
140+ end
141+ VortexStepMethod. group_deform! (ram_wing, reset_theta, reset_delta; smooth= false )
142+ init! (body_aero; init_aero= false , va= reset_va, omega= reset_omega)
143+
144+ # Get nonlinear solution
145+ nonlin_res = VortexStepMethod. solve! (solver, body_aero, nothing ; log= false )
146+ nonlin_res = [solver. sol. force; solver. sol. moment; solver. sol. group_moment_dist]
147+ @test nonlin_res ≉ baseline_res
148+
149+ # Compute linearized prediction
150+ lin_prediction = lin_res + jac * perturbation
151+
152+ # Calculate error ratio for this case
153+ prediction_error = norm (lin_prediction - nonlin_res)
154+ baseline_difference = norm (baseline_res - nonlin_res)
155+ error_ratio = prediction_error / baseline_difference
42156
157+ # Update max error ratio
158+ max_error_ratio = max (max_error_ratio, error_ratio)
159+
160+ # For small perturbations, test that error ratio is small
161+ if idx == first (indices) && mag == first (magnitudes)
162+ @test error_ratio < 2e-3
163+ end
164+ end
165+ end
166+
167+ @info " Max error ratio for $input_name : $max_error_ratio "
168+ end
169+ end
170+
171+ # Test combinations of input variations
172+ @testset " Combined Input Variations" begin
173+ # Sample some key combinations
174+ combination_tests = [
175+ (" Theta + VA" , [dtheta_magnitudes[1 ] * ones (4 ); dva_magnitudes[1 ] * ones (3 ); zeros (3 ); zeros (4 )]),
176+ (" Theta + Omega" , [dtheta_magnitudes[1 ] * ones (4 ); zeros (3 ); domega_magnitudes[1 ] * ones (3 ); zeros (4 )]),
177+ (" VA + Omega" , [zeros (4 ); dva_magnitudes[1 ] * ones (3 ); domega_magnitudes[1 ] * ones (3 ); zeros (4 )]),
178+ (" Delta + VA" , [zeros (4 ); dva_magnitudes[1 ] * ones (3 ); zeros (3 ); ddelta_magnitudes[1 ] * ones (4 )]),
179+ (" All Inputs" , [dtheta_magnitudes[1 ] * ones (4 ); dva_magnitudes[1 ] * ones (3 );
180+ domega_magnitudes[1 ] * ones (3 ); ddelta_magnitudes[1 ] * ones (4 )])
181+ ]
182+
183+ for (combo_name, perturbation) in combination_tests
184+ # Reset to baseline
185+ VortexStepMethod. group_deform! (ram_wing, theta, delta; smooth= false )
186+ init! (body_aero; init_aero= false , va= va, omega= zeros (3 ))
187+
188+ # Extract components
189+ perturbed_theta = theta + perturbation[1 : 4 ]
190+ perturbed_va = va + perturbation[5 : 7 ]
191+ perturbed_omega = perturbation[8 : 10 ]
192+ perturbed_delta = delta + perturbation[11 : 14 ]
193+
194+ # Apply to nonlinear model
195+ VortexStepMethod. group_deform! (ram_wing, perturbed_theta, perturbed_delta; smooth= false )
196+ init! (body_aero; init_aero= false , va= perturbed_va, omega= perturbed_omega)
197+
198+ # Get nonlinear solution
199+ nonlin_res = VortexStepMethod. solve! (solver, body_aero; log= false )
200+ nonlin_res = [solver. sol. force; solver. sol. moment; solver. sol. group_moment_dist]
201+
202+ # Compute linearized prediction
203+ lin_prediction = lin_res + jac * perturbation
204+
205+ # Calculate error ratio
206+ prediction_error = norm (lin_prediction - nonlin_res)
207+ baseline_difference = norm (lin_res - nonlin_res)
208+ error_ratio = prediction_error / baseline_difference
209+
210+ @info " $combo_name error ratio: $error_ratio "
211+
212+ # Test combinations
213+ @test lin_res ≉ nonlin_res atol= 1e-2
214+ @test lin_prediction ≈ nonlin_res rtol= 0.2 atol= 0.05
215+ @test error_ratio < 2e-3
216+ end
217+ end
43218end
0 commit comments