Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@
### Added

- `stability_derivatives` gives the force and moment coefficients and their derivatives
with respect to angle of attack and sideslip, and `trim_angle` the angles of attack at
which `CMy` changes sign, with the slope that says whether each trim is stable.
with respect to angle of attack, sideslip and the nondimensional roll, pitch and yaw
rates p̂ = pb/2V, q̂ = q c_ref/2V, r̂ = rb/2V, turning about `solver.reference_point`,
and `trim_angle` the angles of attack at which `CMy` changes sign, with the slope that
says whether each trim is stable.
- `apparent_wind(alpha, beta, va)` gives the body-frame inflow vector at an angle
of attack and sideslip, as `set_va!(body_aero, settings)` sets it.
- `Solver(settings)` and `Solver(n_panels, n_unrefined_sections)` build a solver without
Expand Down
35 changes: 23 additions & 12 deletions src/stability.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,33 @@
stability_derivatives(solver, body_aero, alpha, beta, va; kwargs...)

Aerodynamic coefficients `[CFx, CFy, CFz, CMx, CMy, CMz]` of `body_aero` at angle of attack
`alpha` [rad], sideslip `beta` [rad] and apparent wind speed `va` [m/s], and their
derivatives with respect to `alpha` and `beta` [1/rad], at the rotation rate
`body_aero.omega` and with moments about `solver.reference_point`. `kwargs` go to
[`linearize`](@ref), which leaves `body_aero` at this inflow.
`alpha` [rad], sideslip `beta` [rad], apparent wind speed `va` [m/s] and rotation rate
`body_aero.omega` [rad/s], and their derivatives: `dalpha` and `dbeta` [1/rad], and `dp`,
`dq`, `dr` with respect to p̂ = pb/2V, q̂ = q c_ref/2V and r̂ = rb/2V, where p, q, r are the
rates about body x, y, z, b the wing span, c_ref `body_aero.c_ref` and V is `va`, not the
area-weighted panel inflow speed the coefficients are normalised by. Moments are about,
and the body turns about, `solver.reference_point`. `kwargs` go to [`linearize`](@ref).
Leaves `body_aero` at this inflow with `body_aero.reference_point = solver.reference_point`.

Returns `(coeffs, dalpha, dbeta, converged)`.
Returns `(coeffs, dalpha, dbeta, dp, dq, dr, converged)`.
"""
function stability_derivatives(solver::Solver, body_aero::BodyAerodynamics, alpha, beta,
va; kwargs...)
va_vec = apparent_wind(alpha, beta, va)
jac, results, converged = linearize(solver, body_aero, va_vec;
theta_idxs=nothing, va_vec_idxs=1:3, aero_coeffs=true, kwargs...)
set_va!(body_aero, va_vec, body_aero.omega; reference_point=solver.reference_point)
jac, results, converged = linearize(solver, body_aero, [va_vec; body_aero.omega];
theta_idxs=nothing, va_vec_idxs=1:3, omega_idxs=4:6, aero_coeffs=true, kwargs...)
dva_dalpha = ForwardDiff.derivative(
angle -> apparent_wind(angle, beta, va), alpha)
dva_dbeta = ForwardDiff.derivative(
angle -> apparent_wind(alpha, angle, va), beta)
coeff_jac = jac[1:6, :]
return (coeffs=results[1:6], dalpha=coeff_jac * dva_dalpha,
dbeta=coeff_jac * dva_dbeta, converged)
span = body_aero.wings[1].span
return (coeffs=results[1:6], dalpha=coeff_jac[:, 1:3] * dva_dalpha,
dbeta=coeff_jac[:, 1:3] * dva_dbeta,
dp=coeff_jac[:, 4] * 2va / span,
dq=coeff_jac[:, 5] * 2va / body_aero.c_ref,
dr=coeff_jac[:, 6] * 2va / span, converged)
end

"""
Expand All @@ -32,7 +40,8 @@ sign between neighbouring entries of `alpha_range`, bisected to `alpha_tol` [rad
sideslip `beta` [rad] and apparent wind speed `va` [m/s]. Returns one
`(alpha, dCMy_dalpha)` per trim, the slope [1/rad] from [`stability_derivatives`](@ref)
with `backend`; a trim is statically stable where `dCMy_dalpha < 0`. Throws a
[`SolveFailure`](@ref) if a solve misses the solver's tolerances.
[`SolveFailure`](@ref) if a solve misses the solver's tolerances. Leaves
`body_aero.reference_point = solver.reference_point`.
"""
function trim_angle(solver::Solver, body_aero::BodyAerodynamics, beta, va;
alpha_range=deg2rad.(-5:2:15), alpha_tol=1e-5, backend=AutoForwardDiff())
Expand All @@ -55,11 +64,13 @@ end

Aerodynamic coefficients `[CFx, CFy, CFz, CMx, CMy, CMz]` of `body_aero` solved at angle of
attack `alpha` [rad], sideslip `beta` [rad] and apparent wind speed `va` [m/s], at the
rotation rate `body_aero.omega`. Throws a [`SolveFailure`](@ref) if the solve misses the
rotation rate `body_aero.omega`, which it turns about and stores as
`body_aero.reference_point`. Throws a [`SolveFailure`](@ref) if the solve misses the
solver's tolerances.
"""
function coeffs_at_angles(solver, body_aero, alpha, beta, va)
set_va!(body_aero, apparent_wind(alpha, beta, va), body_aero.omega)
set_va!(body_aero, apparent_wind(alpha, beta, va), body_aero.omega;
reference_point=solver.reference_point)
sol = solve!(solver, body_aero; throw_on_fail=true)
return [sol.force_coeffs; sol.moment_coeffs]
end
Expand Down
36 changes: 36 additions & 0 deletions test/solver/test_stability.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,30 @@ end
@test derivatives.dbeta ≈ dbeta rtol = 1e-4 atol = 1e-6
end

@testset "rate derivatives match central differences of solve! about reference_point" begin
reference_point = [0.25, 0.5, 0.1]
body_aero, solver = trimmable_wing(0.05; reference_point, use_gamma_prev=false)
alpha, beta, va, step = deg2rad(4.0), deg2rad(3.0), 20.0, 1e-4
omega = [0.1, -0.05, 0.08]
body_aero.omega = omega

derivatives = stability_derivatives(solver, body_aero, alpha, beta, va)
@test derivatives.converged
@test body_aero.reference_point == reference_point

rate_scales = 2va ./ [body_aero.wings[1].span, body_aero.c_ref, body_aero.wings[1].span]
for (axis, derivative) in enumerate((derivatives.dp, derivatives.dq, derivatives.dr))
rate_step = step .* (1:3 .== axis)
body_aero.omega = omega + rate_step
coeffs_plus = coeffs_at_angles(solver, body_aero, alpha, beta, va)
body_aero.omega = omega - rate_step
coeffs_minus = coeffs_at_angles(solver, body_aero, alpha, beta, va)
central_difference = (coeffs_plus - coeffs_minus) / 2step * rate_scales[axis]
@test !iszero(central_difference)
@test derivative ≈ central_difference rtol = 1e-4 atol = 1e-6
end
end

@testset "trim_angle finds where CMy changes sign" begin
beta, va = 0.0, 20.0

Expand All @@ -75,6 +99,18 @@ end
@test trim.dCMy_dalpha > 0
end

@testset "pitching body: trim turning about the reference point" begin
reference_point = [1.0, 0.0, 0.0]
body_aero, solver = trimmable_wing(-0.05; reference_point)
omega = [0.0, 0.5, 0.0]
set_va!(body_aero, apparent_wind(0.0, beta, va), omega)
trim = only(trim_angle(solver, body_aero, beta, va))
set_va!(body_aero, apparent_wind(trim.alpha, beta, va), omega;
reference_point)
cmy = solve!(solver, body_aero).moment_coeffs[2]
@test abs(cmy) < 1e-5 * abs(trim.dCMy_dalpha)
end

@testset "a NONLIN solver with backend=nothing finds the same trim" begin
loop_body, loop_solver = trimmable_wing(0.05)
body_aero, solver = trimmable_wing(0.05; solver_type=NONLIN)
Expand Down
Loading