Course-loop stability

The linear model behind examples/stability_*.jl. The transfer functions need using ControlSystemsBase, which loads the package extension.

Plant

SimpleKiteControllers.CourseLoopModel — Type

The identified parameters of the linear course-loop model, loaded from the file the system project names under course_loop_model (course_loop_model_file, data/course_loop_model.yaml in every project so far), where the provenance of each value is recorded. The turn-rate law itself (c1, c2, dead time and kite lag over depower) is not here: it is the turn-rate table, see turn_rate_coeffs, and neither is the steering tape's lag, which is 1/steering_gain of the KCU's P controller. Every field must be given by the file; the defaults are NaN so that a missing key cannot pass as a value.

The session's instance is course_loop_model.

Fields

  • kite_dead_time_exp::Float64: Exponent of the kite's dead time over v_a, τ ∝ v_a^-exp [-] Default: NaN

  • kite_lag_exp::Float64: Exponent of the kite's lag over v_a, T ∝ v_a^-exp [-] Default: NaN

  • pattern_delay_ref::Float64: Pattern law: the kite's response time τ + T [s] at pattern_v_ref Default: NaN

  • pattern_v_ref::Float64: Pattern law: reference airspeed [m/s] Default: NaN

  • pattern_delay_exp::Float64: Pattern law: exponent over v_a, τ + T ∝ v_a^-exp [-] Default: NaN

  • pattern_v_floor::Float64: Airspeed [m/s] below which the pattern law holds its value Default: NaN

  • pattern_law_depower::Float64: Depower [-] the pattern law was measured at Default: NaN

  • pattern_depower_exp::Float64: Growth of the pattern response time with depower, exp(exp·(depower - pattern_law_depower)) [-] Default: NaN

  • kite_corr_zero::Float64: Zero [Hz] of kite_correction Default: NaN

  • kite_corr_pole::Float64: Pole [Hz] of kite_correction Default: NaN

  • kite_corr_v_ref::Float64: Apparent wind speed [m/s] at which kite_corr_zero and kite_corr_pole hold; they scale with v_a Default: NaN

source
SimpleKiteControllers.kite_dead_time — Function
kite_dead_time(tc, v_app; clm = course_loop_model()) -> Float64
kite_lag(tc, v_app; clm = course_loop_model()) -> Float64

The kite's dead time and first-order lag [s] from the applied steering to the turn rate at v_app [m/s], for the turn-rate coefficients tc: the table's tc.dead_time and tc.kite_lag, scaled as (tc.v_app / v_app)^exp with the exponents kite_dead_time_exp and kite_lag_exp of CourseLoopModel, where tc.v_app is the airspeed of the flights they were identified at. Away from it they are extrapolated.

source
SimpleKiteControllers.pattern_dead_time_lag — Function
pattern_dead_time_lag(tc, v_app, depower; clm = course_loop_model()) -> (τ, T)

The kite's dead time and lag [s] in pattern flight. Their sum follows the pattern law of CourseLoopModel, pattern_delay_ref · (pattern_v_ref / v_a)^pattern_delay_exp, times the measured depower factor exp(pattern_depower_exp · (depower − pattern_law_depower)). It is split in the ratio tc.dead_time : tc.kite_lag of the turn-rate coefficients tc, which must be those at depower.

The pattern law was identified in the low crosswind pattern (elevation 15 – 26°), so this function holds for the pattern loop only, not for the entry, which flies at a much higher elevation. Below pattern_v_floor the law holds its value (the measured response time stops growing at about 0.28 s).

source
SimpleKiteControllers.turn_rate_plant — Function
turn_rate_plant(c1, c2, delay, v_app, gravity, Ts; lag, kite_lag = 0.0) -> StateSpace

rel_steering -> heading, ZOH-discretized: the steering tape's lag lag [s] (1/steering_gain of the settings, the lag of the KCU's P controller), then the turn-rate law with the kite's own first-order lag kite_lag [s] and its dead time delay [s] rounded to whole samples. gravity = cos(ψ0)·cos(β) in [-1, 1] selects the sign and size of the gravity pole. Needs using ControlSystemsBase, which loads the method.

source

Controller and guidance

SimpleKiteControllers.course_pid — Function
course_pid(K, Ti, Td, N, Ts) -> TransferFunction

Discrete transfer function from the regulated error to rel_steering of the DiscretePID built in CourseController: K + K·Ts/Ti/(z-1) + bd·(z-1)/(z-ad), with ad = Td/(Td+N·Ts) and bd = K·N·ad. Ti = false means no integral action. Needs using ControlSystemsBase, which loads the method.

source
SimpleKiteControllers.guidance_tf — Function
guidance_tf(ω_g, Ts) -> TransferFunction

The attractor guidance as seen by the course loop, 1 + ω_g/s discretized with the pole at z = 1: the commanded course follows the cross-track error, which integrates the course, with the corner ω_g = v_k/(L·D) [rad/s] (D the attractor's arc distance [rad]). Multiply the inner loop C·P by it for pattern flight, as stability_opt_reelout.jl does. Validated at the 300 m fig8 point (oldplans/Plan_model_validation.md, V1 step 1): it predicts the measured course → regulated-error link at 0.5 Hz to within 5 % and 1°. Needs using ControlSystemsBase, which loads the method.

source
SimpleKiteControllers.kite_correction — Function
kite_correction(Ts, v_app; clm = course_loop_model()) -> StateSpace

Lag-lead (1 + s/ω_z)/(1 + s/ω_p) at the apparent wind speed v_app [m/s], with the zero and pole kite_corr_zero, kite_corr_pole of CourseLoopModel scaled by v_app / kite_corr_v_ref: like the measured kite correction (course_correction), its features sit at a fixed distance flown, so they move in frequency with v_a. It brings the turn-rate law's steering → heading response to what an injected multisine measures in the simulation: from ~0.9 Hz up the kite turns less than the relay-identified law says (0.8 at 1.1 Hz, 0.6 – 0.7 above 1.4 Hz) with ~10° more lag. Multiply the plant by it, together with the pattern law's dead time and lag (pattern_dead_time_lag), against which it is chosen. It is the causal stand-in for the measured kite correction (kite_correction_file), which loses gain without the matching phase lag and so has no low-order causal form: the zero and pole are chosen conservative against it (examples/identify_kite_correction.jl). Needs using ControlSystemsBase, which loads the method.

source
SimpleKiteControllers.load_course_correction — Function
load_course_correction(path = COURSE_CORRECTION_FILE) -> Vector{NamedTuple}

The measured course correction M(f, v_a) [-]: what the simulation's steering → fed-back course response is, divided by this model's tape lag × turn-rate law (without kite_correction, which it contains). Measured with injected multisines on the fig8 pattern at v_a 23.7, 34 (200 and 300 m, pooled) and 40.1 m/s (oldplans/Plan_model_validation.md, V1). One table per airspeed, sorted by v_a, each with the frequencies [Hz], log|M| and the unwrapped phase [rad], for course_correction. The measured kite correction (kite_correction_file) has the same format and is read with this function too.

source
SimpleKiteControllers.course_correction — Function
course_correction(tabs, f, v_a) -> ComplexF64

M at f [Hz] and v_a [m/s] from the tables of load_course_correction: each table is scaled in frequency to v_a (its features sit at a fixed distance flown, so they move as f ∝ v_a) and log|M| and the phase are interpolated linearly in v_a between the two nearest airspeeds. Outside the measured airspeeds the nearest table is only scaled. At the measured airspeeds it reproduces the data; between them it predicted the 34 m/s margins from the 23.7 and 40.1 m/s tables within 23 %, on the safe side. Keep f inside the measured band (0.2 – 4 Hz at 34 m/s, scaled with v_a).

source

Margins

SimpleKiteControllers.delay_margin — Function
delay_margin(L) -> Float64

Smallest extra dead time [s] that destabilizes L, over all its gain crossovers; 0 if the closed loop is already unstable. ControlSystemsBase.delaymargin takes the phase margin unwrapped and so reports e.g. 374° instead of 14° for a loop with a long dead time. Needs using ControlSystemsBase, which loads the method.

source
SimpleKiteControllers.frd_margins — Function
frd_margins(f, L) -> NamedTuple

Margins of a loop given only as frequency-response points L [complex] at the frequencies f [Hz], e.g. a measured plant times the controller: the first gain crossover (f_gc, phase margin pm [deg], delay margin dm [s]) and the first phase crossover (f_pc, gain margin gm), found by interpolating log|L| and the unwrapped phase between the points. Needed where the plant has no good low-order model, like the fed-back course of pattern flight (oldplans/Plan_model_validation.md, V1 step 1). NaN where there is no crossing.

source
SimpleKiteControllers.frd_diskmargin — Function
frd_diskmargin(L) -> Float64

Balanced disk margin α (skew 0) of a loop given as frequency-response points L: 2 / max |(1 − L)/(1 + L)|, i.e. 1/‖S − 1/2‖∞ over the points. Only as good as the points cover the frequencies where the maximum sits, the crossover region for the course loop.

source