Skip to content

Commit e63d999

Browse files
ufechner7claude
andcommitted
Add plots_extra.jl: attractor distance over time for the paper
Plots the attractor arc Δ·L [m] of Cabauw and Maasvlakte at 4, 7 and 10 m/s in one figure, recomputed from the logged apparent wind speed and tether length with each scenario's own fc_settings. Saves attractor_distance.pdf into ../LearningControl/figures. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
1 parent 16ec357 commit e63d999

1 file changed

Lines changed: 102 additions & 0 deletions

File tree

‎examples/plots_extra.jl‎

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Copyright (c) 2026 Uwe Fechner
2+
# SPDX-License-Identifier: MPL-2.0
3+
4+
"""
5+
Extra plots for the LearningControl paper that are not part of the pattern
6+
figures of `plot_patterns_paper.jl`.
7+
8+
- `plot_attractor_distance`: the arc length [m] from the closest path point Q to
9+
the attractor point over time, for the scenarios in
10+
[`ATTRACTOR_SCENARIOS`](@ref) (Cabauw and Maasvlakte at low, medium and high
11+
wind speed) in one plot. Saved as `attractor_distance.pdf` into
12+
`../LearningControl/figures`.
13+
14+
include("plots_extra.jl")
15+
"""
16+
17+
using Pkg
18+
if Base.active_project() != joinpath(@__DIR__, "Project.toml")
19+
Pkg.activate(joinpath(@__DIR__))
20+
end
21+
22+
using GLMakie
23+
using MakieControlPlots
24+
using V3Kite
25+
using SimpleKiteControllers
26+
27+
include(joinpath(@__DIR__, "plot_pattern_utils.jl"))
28+
29+
const PAPER_FIGURES_DIR = normpath(joinpath(@__DIR__, "..", "..", "LearningControl", "figures"))
30+
31+
"""
32+
ATTRACTOR_SCENARIOS
33+
34+
`(site, scenario)` pairs drawn by [`plot_attractor_distance`](@ref): both sites
35+
at 4, 7 and 10 m/s.
36+
"""
37+
const ATTRACTOR_SCENARIOS = [
38+
("cabauw", "v04"), ("cabauw", "v07"), ("cabauw", "v10"),
39+
("maasvlakte", "v04"), ("maasvlakte", "v07"), ("maasvlakte", "v10"),
40+
]
41+
42+
"""
43+
attractor_arc(site, scenario) -> (time, arc, flown_wind)
44+
45+
The attractor arc `Δ·L` [m] of the run in `output/scenarios/<site>/<scenario>`
46+
over time: [`attractor_distance`](@ref) evaluated with the scenario's own
47+
`fc_settings_reelout.yaml` at the logged apparent wind speed and tether length,
48+
the same value the guidance re-reads every step. `NaN` outside the phases
49+
transition, fig8 and final (3–5); in the entry phases before them the state
50+
machine replaces the guidance. `flown_wind` is the ground wind speed [m/s].
51+
"""
52+
function attractor_arc(site, scenario)
53+
scenario_dir = normpath(joinpath(@__DIR__, "..", "output", "scenarios", site, scenario))
54+
isdir(scenario_dir) || error("$scenario_dir does not exist.")
55+
fcs = FC_Settings(fc_settings(scenario_system_file(scenario_dir)); path = scenario_dir)
56+
57+
log_name = replace(only(filter(f -> endswith(f, ".arrow"), readdir(scenario_dir))), ".arrow" => "")
58+
sl = load_log(log_name; path = scenario_dir).syslog
59+
summary_sim = V3Kite.YAML.load_file(joinpath(scenario_dir, log_name * ".yaml"))["simulation"]
60+
61+
rng = 2:length(sl.time)
62+
time = Float64.(sl.time[rng])
63+
l_tether = getindex.(sl.l_tether[rng], 1)
64+
v_app = Float64.(sl.v_app[rng])
65+
guided = sl.sys_state[rng] .>= 3
66+
arc = [guided[i] ? deg2rad(attractor_distance(fcs, v_app[i], l_tether[i])) * l_tether[i] : NaN
67+
for i in eachindex(time)]
68+
return time, arc, summary_sim["wind_speed"]
69+
end
70+
71+
"""
72+
plot_attractor_distance(scenarios = ATTRACTOR_SCENARIOS; t_max = 180.0, disp = true, save = true)
73+
74+
Plot the attractor arc ([`attractor_arc`](@ref)) of each `(site, scenario)` pair
75+
in `scenarios` over time, one line per run. The runs share one time axis, that of
76+
the longest run; the others are sampled onto it, `NaN` beyond their own end.
77+
The time axis runs from 0 to `t_max` [s].
78+
"""
79+
function plot_attractor_distance(scenarios = ATTRACTOR_SCENARIOS; t_max = 180.0, disp = true, save = true)
80+
runs = [attractor_arc(site, scenario) for (site, scenario) in scenarios]
81+
time = argmax(r -> last(r[1]), runs)[1]
82+
arcs = map(runs) do (t_run, arc, _)
83+
map(time) do t
84+
k = searchsortedlast(t_run, t)
85+
k == 0 || t > last(t_run) ? NaN : arc[k]
86+
end
87+
end
88+
labels = ["$(uppercasefirst(site)) $(round(r[3]; digits = 1)) m/s"
89+
for ((site, _), r) in zip(scenarios, runs)]
90+
91+
p = MakieControlPlots.plot(time, arcs;
92+
xlabel = "time [s]", ylabel = "attractor distance [m]",
93+
labels = labels, xlims = (0.0, t_max), fig = "attractor distance", disp = disp)
94+
if save && disp
95+
pdf_file = joinpath(PAPER_FIGURES_DIR, "attractor_distance.pdf")
96+
savefig(pdf_file)
97+
@info "Saved attractor distance plot" pdf_file
98+
end
99+
return p
100+
end
101+
102+
plot_attractor_distance()

0 commit comments

Comments
 (0)