Repository navigation
Conversation
find_steady_state!(s; trim=true) (KPS3 and KPS4) solves the vertical force balance of the kite instead of prescribing set.elevation, placing the kite at the elevation where its lift matches gravity. Deep stall trims (angle of attack beyond the angle of maximum lift) are rejected, and the solver is restarted at other elevations if no valid trim is found. KPS4: the wind is now set from the kite height of each solver iterate. Before, the kite was balanced for the ground wind (height 0 after clear!), leaving force residuals of about 130 N on the kite points. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
init!(s; trim=true) passes trim to find_steady_state!. KPS4: init sets the vertical accelerations in yd0 to -g, so the steady state solver balanced the forces without gravity. With trim=true the accelerations are now zero, so the kite is trimmed with its weight and the returned state is an equilibrium of the simulation: with init!(s; trim=true, stiffness_factor=1.0) the elevation stays within 0.1° over 10 s. trim=false is unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
For KPS3 and KPS4 at 4, 7 and 10 m/s: after init!(s; trim=true, stiffness_factor=1.0, delta=0.0) the wind at the kite matches the profile, the kite is not stalled, and the elevation stays within 0.01° over 10 s. Maasvlakte at 4 m/s is skipped, because there the kite cannot be carried with attached flow. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
The keyword argument delta of find_steady_state! and init! is deprecated and ignored. The steady state is solved without perturbation, with a perturbation of 0.001 only as an internal fallback, and it is always returned without perturbation, so it is an equilibrium of the simulation. KPS3 now converges in all 72 cases of a sweep over wind speed, elevation, tether length and stiffness factor. The examples, tests and precompile.jl no longer pass delta. With trim=false, find_steady_state! warns if the kite is in deep stall, which happens if the prescribed elevation is far below the natural elevation (with system.yaml below about 60°). The robustness test cases at 50° are moved to 60° and 65° for this reason. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The velocities were multiplied by the length of the tether segments, so with trim=true the kite was placed far below its natural elevation. Use trim=true in test_init_4p.jl and print v_reel_out. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The steady state is now always solved for the full tether stiffness, so the initial state is an equilibrium of the simulation. A reduced stiffness is only used internally as start value: if the direct solve fails, the schedules 0.1 -> 1, 0.1 -> 0.3 -> 1 and 0.3 -> 1 are tried, then the previous continuation. With trim=true, the KPS4 search starts at 80 degrees, from where the solver converges. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The particles were at rest and the initial reel-out speed was zero, so the initial elevation did not depend on v_reel_out. Now the particle velocities grow linearly along the tether to v_reel_out at the kite, and the initial state contains the reel-out speed, as for KPS4. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The first frame used xlim = (35, 55) and the later frames (35, 75), so the kite seemed to jump after the first frame. Both now use (40, 55). Also use MakieControlPlots from its main branch in the examples. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
🟡 Changes recommended
Both trim solvers can still return an invalid deep-stall or unconverged state after failing to find the promised attached-flow equilibrium.
6 open findings
Report failure when attached-flow restarts all fail · New Fail trim when no attached-flow restart succeeds · New Remove deprecated stiffness_factor from example · New Remove deprecated stiffness_factor from example · New Remove deprecated stiffness_factor from example · New Qualify equilibrium guarantee when steady state is disabled · New
What changed in this PR
Adds natural-elevation trimming to KPS3/KPS4 while correcting steady-state wind, reel-out velocity, and tether-stiffness initialization.
Changes:
- Adds
trim=true, attached-flow checks, and solver restart strategies. - Deprecates
deltaandstiffness_factor; initializes at full stiffness. - Updates tests, examples, documentation, and release notes.
| File | Description |
|---|---|
src/KPS3.jl |
Adds trimming and reel-out-aware initialization. |
src/KPS4.jl |
Adds trimming, wind updates, continuation, and velocity fixes. |
src/KiteModels.jl |
Adds shared options, deprecations, and init! integration. |
src/precompile.jl |
Updates precompile calls. |
test/test-trim.jl |
Tests trimming, profiles, deprecations, and reel-out. |
test/test-steady-state-robustness.jl |
Updates full-stiffness robustness cases. |
test/test-steady-state-kps4.jl |
Removes deprecated arguments. |
test/test-winch-state.jl |
Uses default initialization. |
test/test-update-sys-state.jl |
Removes deprecated options. |
test/test-simulate_4p.jl |
Updates initialization. |
test/test-kps4.jl |
Updates steady-state and breakage tests. |
test/test-kps3.jl |
Updates steady-state calls. |
test/test-interface.jl |
Updates interface tests. |
test/test_for_precompile.jl |
Updates precompile test initialization. |
test/plot_kps4.jl |
Updates plotting setup. |
examples/Project.toml |
Sources MakieControlPlots from main. |
examples/test_steady_state.jl |
Removes deprecated options. |
examples/test_init_4p.jl |
Demonstrates trimmed KPS4 initialization. |
examples/test_init_1p.jl |
Demonstrates reel-out-aware KPS3 trim. |
examples/steering_test_4p.jl |
Updates initialization. |
examples/steering_test_1p.jl |
Updates initialization. |
examples/simulate_steering.jl |
Updates initialization. |
examples/simulate_simple.jl |
Updates non-steady initialization. |
examples/reel_out_4p.jl |
Updates initialization. |
examples/reel_out_4p_torque_control.jl |
Updates initialization. |
examples/reel_out_1p.jl |
Updates initialization. |
examples/plot_side_cl.jl |
Updates initialization. |
examples/plot_pitch_stability.jl |
Updates initialization. |
examples/plot_parking_test.jl |
Updates initialization. |
examples/plot_cl_cd.jl |
Updates initialization. |
examples/plot_alpha2.jl |
Updates initialization. |
examples/compare_kps3_kps4.jl |
Stabilizes plot limits and updates initialization. |
examples/calc_spectrum.jl |
Updates initialization. |
examples/bench.jl |
Updates KPS3 benchmark setup. |
examples/bench_4p.jl |
Updates KPS4 benchmark setup. |
examples_3d/parking_wind_dir.jl |
Partially removes deprecated options. |
examples_3d/parking_4p.jl |
Partially removes deprecated options. |
examples_3d/auto_parking_4p.jl |
Partially removes deprecated options. |
docs/src/examples_4p.md |
Removes obsolete stiffness explanation. |
CHANGELOG.md |
Documents trim, deprecations, and fixes. |
🧠 Review effort: Balanced
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
With trim=true, find_steady_state! (KPS3 and KPS4) returned a deep stall or non-converged state if all restarts failed. Now it throws an error, so init! returns nothing instead of initializing from an invalid trim. The restarts use 10 degree steps and a cheaper solve, so that failing takes 7 s (KPS3) and 11 s (KPS4) instead of 25 s and 78 s. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The keyword is deprecated and ignored. Also update the paragraph in examples_4p.md, which still described the reduced stiffness. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
With steady_state=false, the initial state is not an equilibrium. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
find_steady_state!(s; trim=true)(KPS3 and KPS4). Instead of prescribingset.elevation, the vertical force balance of the kite is solved too, so the kite is placed at the elevation where its lift matches gravity. The defaulttrim=falsekeeps the previous behaviour.init!(s; trim=true)passestrimthrough tofind_steady_state!. The initial state is then an equilibrium of the simulation, so the elevation stays constant.initsets the vertical accelerations inyd0to-g, so the steady-state solver balances the forces without gravity (the existingsum(res2) ≈ -9.81*Ntests rely on this fortrim=false, which is unchanged). Withtrim=truethe accelerations are zero, so the kite is trimmed with its weight. KPS3 already balanced gravity.alpha_cl/cl_list) are rejected. KPS4 starts the trim search at 80° (or atset.elevation, if higher), because the solver converges from above the natural elevation but often not from far below it. If no valid trim is found, the solver is restarted at other elevations in steps of 10°, with a cheaper solve (no perturbation, no last-resort continuation). If no attached-flow trim is found at all,find_steady_state!throws an error instead of returning a deep-stall or non-converged state, andinit!(s; trim=true)prints the reason and returnsnothing. Withsystem.yamland the Maasvlakte profile at 4 m/s, where no such trim exists, failing takes 7 s (KPS3) and 11 s (KPS4).deltais deprecated and ignored (find_steady_state!andinit!). It perturbed the initial state, and the returned state was perturbed too, so it wasn't an equilibrium. Now the steady state is solved without perturbation, withdelta = 0.001only as an internal fallback, and always returned unperturbed.stiffness_factoris deprecated and ignored (find_steady_state!andinit!). Before, the KPS4 steady state was solved for the reduced tether stiffnessstiffness_factor, whichnext_step!then raised by 0.01 per step to 1.0, so the initial state was not an equilibrium of the simulation and depended on the chosen value. Now the steady state is always solved for the full stiffness (s.stiffness_factor == 1.0afterwards). A reduced stiffness is only used internally as start value: if the direct solve fails, the schedules 0.1 → 1, 0.1 → 0.3 → 1 and 0.3 → 1 are tried, then the previous continuation (see Solver strategy). For KPS3 the first solve uses a fixed 0.035; its result was already solved for the full stiffness.trim=false, a prescribed elevation far below the natural one can only be balanced in deep stall; this was returned silently.find_steady_state!now warns and suggeststrim=trueor a higher elevation. Withsystem.yamlthis happens below about 45° for KPS4 and 65° for KPS3.clear!, so the kite was balanced for 9.51 m/s instead of 12.6 m/s. The wind is now set from the kite height of every solver iterate.v_reel_out = 1 m/sthe KCU started at 20.9 m/s and the kite at 5.0 m/s. Withtrim=truethe kite was placed at 54.8° instead of 68.2° (examples/test_init_4p.jl) and then climbed for about 10 s, with a tether force peak of more than twice the steady value. Now each particle moves along its segment, growing linearly tov_reel_outat the KCU.v_reel_outwas ignored in the steady state. All particles were at rest and the reel-out speed in the initial state was zero, so the initial elevation didn't depend onv_reel_outand the winch first had to accelerate. Now the particle velocities grow linearly tov_reel_outat the kite, and the state starts with the reel-out speed. Withtrim=trueat 14.5 m/s: 73.5° at 0 m/s, 70.4° at 1 m/s, 60.9° at 4 m/s, constant during the simulation.deltaorstiffness_factor;test_init_1p.jlandtest_init_4p.jlusetrim=trueand printv_reel_out;compare_kps3_kps4.jluses the same x limits for all frames (the kite seemed to jump after the first frame). The examples use MakieControlPlots from itsmainbranch.Solver strategy
Benchmark of
nlsolvealone (without the elevation restarts) for KPS4 withsystem.yaml: 160 cases (wind 6–25 m/s, elevation 40–85°, tether 50–1000 m, with and without trim), of which 119 were solvable by at least one strategy. Failures among those 119:Starting directly at 1.0 is fastest and works for most fixed-elevation cases, starting at 0.1 is needed mainly for trim, and the schedule chain and the previous continuation fail in different cases. All 41 unsolved cases were trims started at 40° or 55°. The chain, then the previous continuation, with the trim search starting at 80° solved all 160 cases, in 0.14 s on average (max. 1.2 s). With 12 segments: 0.6 s on average; 4 trim cases at 6 m/s and 50 m tether failed in the benchmark (in production the elevation restarts follow).
With the production
find_steady_state!(including restarts) on the same 160 cases: KPS4 solves all, 24.9 s in total (max. 2.7 s); KPS3 solves 156, the 4 failures are fixed-elevation cases (trim=false) at 20–25 m/s, 40–55° and 400–1000 m tether, which the trim changes don't touch.Behaviour changes
trim=falsetoo (withsystem.yamlthe winch force afterfind_steady_state!goes from 308 N to 456 N), and the tether is now only stretched as much as the full stiffness allows.v_reel_out ≠ 0(velocity fixes), and slightly for runs that useddelta > 0(includinginit!'s former default of 0.005).deltaorstiffness_factorkeeps working, with aBase.depwarn. KiteControllers.jl still passes both.trim=true, a case without an attached-flow trim now raises an error infind_steady_state!(andinit!returnsnothing); before, a deep-stall state was returned with a warning.Performance
find_steady_state!(s; trim=true)for KPS4 takes 0.04–0.09 s, independent ofset.elevation(10°, 30°, 55°, 70.8°, 85° tested); before, KPS4 started far below the trim needed restarts that took 12–45 s. TheTrimtestset went from 21 s to 2–3 s.Tests
test/test-trim.jl: both models, starts at 30°, 55° and 85°, all forces balanced to < 1e-4, no deep stall, no warnings, result independent of the start value, and the elevation stays constant afterinit!(s; trim=true); wind profiles Maasvlakte (EXPLOG) and Cabauw (EXP) at 4, 7 and 10 m/s (Maasvlakte at 4 m/s skipped: no attached-flow trim exists);deltaandstiffness_factorare accepted and ignored; at a prescribed 40°,trim=falsewarns about deep stall andtrim=truefinds an attached-flow trim; withv_reel_out = 2 m/sthe initial state contains the reel-out speed and the trimmed elevation is lower than at 0 m/s; for the Maasvlakte profile at 4 m/s (no attached-flow trim)find_steady_state!throws andinit!returnsnothing.test/test-steady-state-robustness.jl: cases at 50° moved to 60° and 65°; the per-case stiffness factor is removed and the test checksstiffness_factor == 1.0afterwards.Rapturestest (test-kps4.jl): stiffness raised to 20 instead of 3 to break the bridle, because the initial state is now at full stiffness.runtests.jl) passes locally on Julia 1.13 (1 min 34 s);test-trim.jlalone: 112/112. Of the examples, onlycompare_kps3_kps4.jlwas run.🤖 Generated with Claude Code