Skip to content

Take the initial azimuth into account in KPS3 and KPS4 (#243) - #338

Open
ufechner7 wants to merge 5 commits into
load_sys_statefrom
initial_azimuth
Open

ufechner7 wants to merge 5 commits into
load_sys_statefrom
initial_azimuth

Conversation

@ufechner7

@ufechner7 ufechner7 commented Oct 12, 2026 •

Copy link
Copy Markdown
Member

Addresses all items (1–5) of the scope proposed in #243 (comment). Stacked on #336.

Initial azimuth (KPS3 and KPS4)

find_steady_state! and init! (with or without trim, also with steady_state=false) place the kite at set.azimuths[1] in the wind reference frame. The default azimuth of zero keeps the previous behaviour: the extra unknowns are only added for an azimuth other than zero.

Method: a rotation about the vertical axis leaves gravity, the winch and a height-only wind profile unchanged. So the steady state is solved with the tether in the x-z plane and the wind turned by the azimuth, and the result is turned back.

  • Tether: the particles are also balanced laterally (new unknowns: y offsets for KPS4, lateral segment angles for KPS3), so the tether is pushed sideways by the wind.
  • Kite: it is kept at the prescribed azimuth instead of balancing its lateral forces, in the same way the elevation is prescribed with trim=false. For KPS4, the KCU and kite stay in the tether plane, and the kite is built symmetric to it (heading 0).

Behaviour: a kite at an azimuth is not in equilibrium; it has a side slip angle. With system.yaml at 30°:

  • KPS4: the winch force is 804 N instead of 502 N with trim=false, and 536 N instead of 461 N with trim=true, at a natural elevation of 60°. Without steering, the kite swings back through 0° to −27° within 10 s.
  • KPS3: the one-point kite is laterally unstable without a controller, even near 0° (2° grows to 34° in 10 s), so it drifts away and crashes. The docstrings say this for each model.

Initial heading and steering

  • Steering (KPS3 and KPS4): clear! sets the KCU steering from set.steerings[1] [%], like the depower, so the steady state is solved with it. Only its symmetric forces are balanced, so with steering ≠ 0 the kite turns: 20 % steering turns KPS4 from heading 0° to 84° in 3 s.
  • Heading (KPS4): after the steady state is found, init! rotates the kite points A, C and D about the line from the KCU to point B, solving for the angle so that calc_heading returns set.headings[1] exactly.
    • Unchanged: the kite position (so azimuth and elevation), the tether and all spring lengths.
    • Not an equilibrium: the kite flies towards its nose; with ±90° it moves symmetrically to ±18° azimuth in 3 s.
  • Heading (KPS3): ignored, because the one-point kite has no heading of its own.

Initial rates

init! adds the velocities of the initial rates to the initial state:

  • Elevation and azimuth rates (KPS3 and KPS4): the tether and the kite rotate as a rigid body about the winch, so the elevation and azimuth change exactly at set.elevation_rates[1] and set.azimuth_rates[1] (checked by finite differences).
  • Heading rate (KPS4): the kite also rotates about the line from the KCU to point B, scaled by the slope d(heading)/d(angle) so the heading changes exactly at set.heading_rates[1]. KPS3 ignores it.
  • Pitch rate (KPS4): with rates, it is calculated from the particle velocities instead of being zero.

The accelerations are not changed, so the simulation starts with a transient:

  • An azimuth rate of 10 °/s with heading 90° keeps the kite moving smoothly at about 10 °/s.
  • An elevation rate of −5 °/s makes the KPS4 tether force drop from 502 N to 48 N within 0.1 s.
  • Without steering, a heading rate decays within about 0.1 s.

Also in this PR

  • Fix: init!(s; steady_state=false) (KPS4) did not turn s.pos into the wind direction, so getters like calc_azimuth were wrong before the first time step if upwind_dir ≠ −90°.
  • Docs: the init! docstring lists which initial: settings are used (tether length, reel-out speed, depower, elevation, azimuth, steering, heading) and which is ignored (the kite distance), and describes the transient caused by the rates.

Tests

  • test/test-azimuth.jl (124 tests):
    • azimuths of 30° and −45° with two wind directions, for both models;
    • checks of the azimuth, the elevation, the in-plane and lateral balance of the tether particles, and the lateral deflection of the tether;
    • init! with and without the steady state;
    • azimuth 0 unchanged.
  • test/test-heading-steering.jl (67 tests): the heading for several headings and azimuths, with unchanged kite position, tether and spring lengths; sideways flight for ±90°; KPS3 ignoring the heading; the steering values in the KCU and model; turning with 20 % steering.
  • test/test-initial-rates.jl (43 tests): the rates by finite differences for several combinations with azimuth and heading, unchanged positions, KPS3 ignoring the heading rate, the pitch rate, and continued motion with an azimuth rate.
  • The full suite passes locally (1179 tests, without benchmarks).

🤖 Generated with Claude Code

ufechner7 and others added 2 commits October 12, 2026 23:46
find_steady_state! and init! place the kite at set.azimuths[1] in the wind
reference frame. The steady state is solved with the tether in the x-z plane
and the wind turned by the azimuth; for an azimuth other than zero, the
lateral forces on the tether particles are balanced too, while the kite is
kept at the prescribed azimuth. The default azimuth of zero keeps the
previous behaviour.

Also fix init!(s; steady_state=false) for KPS4, which did not turn s.pos
into the wind direction.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
ufechner7 and others added 2 commits October 13, 2026 00:50
The steering set.steerings[1] (KPS3 and KPS4) is set in clear!, like the
depower. The heading set.headings[1] (KPS4) is applied by init! after the
steady state was found, by rotating the kite points A, C and D about the line
from the KCU to the kite, which keeps the position of the kite and the lengths
of all springs. KPS3 ignores the heading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
)

init! adds the velocities of the initial rates to the initial state: for the
elevation and azimuth rates (KPS3 and KPS4), the tether and the kite rotate
as a rigid body about the winch; for the heading rate (KPS4), the kite also
rotates about the line from the KCU to the kite, scaled so that calc_heading
changes with the prescribed rate. The accelerations are not changed, so the
simulation starts with a transient. The rotation of the kite is factored out
of set_initial_heading! into heading_after_rotation!.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

KPS3 samples turbulent wind at the unrotated position, and the nonzero-azimuth trim path lacks coverage.

2 open findings
What changed in this PR

Adds initial azimuth, heading, steering, and angular-rate handling to KPS3/KPS4 initialization.

Changes:

  • Balances tether geometry for nonzero initial azimuths.
  • Applies initial steering, KPS4 heading, and configured rates.
  • Adds documentation, changelog entries, and regression tests.
File Description
src/​KPS3.jl Adds azimuth balancing, steering, and lateral offsets.
src/​KPS4.jl Adds azimuth balancing and heading rotation.
src/​KiteModels.jl Applies initial headings/rates during initialization.
test/​test-azimuth.jl Tests initial azimuth behavior.
test/​test-heading-steering.jl Tests heading and steering initialization.
test/​test-initial-rates.jl Tests configured angular rates.
CHANGELOG.md Documents the new initialization behavior.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/KPS3.jl
Comment on lines +601 to +603
turn_dir = upwind_dir - deg2rad(s.set.azimuth)
res1 = vcat(turn(res1[1:n], turn_dir), res1[n+1:end])
res2 = vcat(turn(res2[1:n], turn_dir), res2[n+1:end])
Comment thread test/test-azimuth.jl
@testset "$(nameof(Model)), azimuth $(azimuth)°, upwind_dir $(round(rad2deg(upwind_dir)))°" for
Model in (KPS3, KPS4), azimuth in (30.0, -45.0), upwind_dir in (-π/2, π/4)
s = new_model(Model, azimuth)
res1, res2 = @test_logs min_level=Base.CoreLogging.Warn find_steady_state!(s; upwind_dir)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants