|
| 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