Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion REUSE.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ SPDX-FileCopyrightText = "2025 Uwe Fechner, Bart van de Lint"
SPDX-License-Identifier = "MIT"

[[annotations]]
path = ["docs/src/*.png", "docs/src/*.svg", "docs/src/*.md", "docs/src/kite_power_tools.drawio"]
path = ["docs/src/*.png", "docs/src/*.svg", "docs/src/*.md",
"docs/src/symbolic_awe_model/*", "docs/src/kite_power_tools.drawio"]
SPDX-FileCopyrightText = "2025 Uwe Fechner, Bart van de Lint"
SPDX-License-Identifier = "CC-BY-4.0"

Expand Down
Binary file modified data/model_1.10_ram_dynamic_3_seg.bin.default.xz
Binary file not shown.
Binary file modified data/model_1.11_ram_dynamic_3_seg.bin.default.xz
Binary file not shown.
8 changes: 5 additions & 3 deletions docs/make.jl
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,14 @@ makedocs(;
"Home" => "index.md",
"Types" => "types.md",
"Functions" => "functions.md",
"SymbolicAWEModel" => "ram_air_kite.md",
"SymbolicAWEModel" => [
"Overview" => "symbolic_awe_model/ram_air_kite.md",
"Examples" => "symbolic_awe_model/examples_ram_air.md",
"Tutorial" => "symbolic_awe_model/tutorial_system_structure.md"
],
"Parameters" => "parameters.md",
"Examples 1p" => "examples.md",
"Examples 4p" => "examples_4p.md",
"Examples SymbolicAWEModel" => "examples_ram_air.md",
"SystemStructure for custom models" => "tutorial_system_structure.md",
"Quickstart" => "quickstart.md",
"Advanced usage" => "advanced.md",
],
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Segment
Pulley(idx, segment_idxs, type)
Pulley
Tether
Winch(idx, model, tether_idxs, tether_length; tether_vel=0.0)
Winch(idx, model, tether_idxs; tether_length=0.0, tether_vel=0.0)
Winch
Wing(idx, group_idxs, R_b_c, pos_cad)
Wing
Expand All @@ -50,4 +50,4 @@ reinit!
scalar_eqs!
linear_vsm_eqs!
force_eqs!
```
```
175 changes: 175 additions & 0 deletions docs/src/symbolic_awe_model/tutorial_system_structure.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
```@meta
CurrentModule = KiteModels
```
# Custom SystemStructure and SymbolicAWESystem

A custom `SystemStructure` can be used to create models of kite power systems of almost any configuration.
- custom amount of tethers
- custom bridle configurations
- quasi-static or dynamic point masses
- different amounts of stiffness, damping and diameter on different tether segments

## Precondition
First, following the [Quickstart](@ref) section up to the installation of the examples. Make sure that
at least `KiteModels` version 0.8 is installed by typing `using Pkg; Pkg.status()`. To start Julia,
either use `julia --project`, or `./bin/run_julia`.

## Creating a simple tether

We start by loading the necessary packages and defining settings and parameters.

```julia
using KiteModels, VortexStepMethod, ControlPlots

set = Settings("system_ram.yaml")
set.segments = 20
set.l_tether = 50.0
dynamics_type = DYNAMIC
```

Then, we define vectors of the system structure types we are going to use. For this simple example we only need points and segments.

```julia
points = Point[]
segments = Segment[]

points = push!(points, Point(1, zeros(3), STATIC; wing_idx=0))
```

The first point we add is a static point. There are four different [`DynamicsType`](@ref)s to choose from: `STATIC`, `QUASI_STATIC`, `DYNAMIC` and `WING`. `STATIC` just means that the point doesn't move. `DYNAMIC` is a point modeled with acceleration, while `QUASI_STATIC` constrains this acceleration to be zero at all times. A `WING` point is connected to a wing body.

Now we can add `DYNAMIC` points and connect them to each other with segments. `BRIDLE` segments don't need to have a tether, because they have a constant unstretched length.
```julia
segment_idxs = Int[]
for i in 1:set.segments
global points, segments
point_idx = i+1
pos = [0.0, 0.0, i * set.l_tether / set.segments]
if i < set.segments
push!(points, Point(point_idx, pos, dynamics_type; wing_idx=0))
else
push!(points, Point(point_idx, pos, dynamics_type; mass=1.0, wing_idx=0))
end
segment_idx = i
push!(segments, Segment(segment_idx, (point_idx-1, point_idx), BRIDLE))
push!(segment_idxs, segment_idx)
end
```

In order to describe the initial orientation of the structure, we define a [`Transform(idx, elevation, azimuth, heading)`](@ref) with an elevation (-80 degrees), azimuth and heading, and a base position `[0.0, 0.0, 50.0]`.
```julia
transforms = [Transform(1, deg2rad(-80), 0.0, 0.0;
base_pos = [0.0, 0.0, 50.0], base_point_idx=points[1].idx,
rot_point_idx=points[end].idx)]
```

From the points, segments and transform we create a [`SystemStructure(name, set)`](@ref), which can be plotted in 2d to quickly investigate if the model is correct.
```julia
sys_struct = SystemStructure("tether", set; points, segments, transforms)
plot(sys_struct, 0.0)
```
![SystemStructure visualization](tether_sys_struct.png)

If the system looks good, we can easily model it, by first creating a [`SymbolicAWEModel`](@ref), initializing it and stepping through time.
```julia
sam = SymbolicAWEModel(set, sys_struct)

init_sim!(sam; remake=false)
for i in 1:80
plot(sam, i/set.sample_freq)
next_step!(sam)
end
```
![Tether during simulation](tether_during_sim.png)

# Using a winch and a tether

Let's try to adjust the length of the tether in the last example. To do this we first need to create a set of segments with a common changing `l0`, called a [`Tether`](@ref).
```julia
set.v_wind = 0.0
tethers = [Tether(1,[segment.idx for segment in segments])]
```
As you can see, we just add all of the segments from the simple tether to our [`Tether`](@ref) struct.
The next step is to create a winch. Each winch can be connected to one or more tethers, so it is possible to connect multiple tethers to the same winch. We have to specify which kind of winch we want to use. For now, only the `TorqueControlledMachine` from `WinchModels.jl` is supported.

```julia
using WinchModels
wm = TorqueControlledMachine(set)
winches = [Winch(1, wm, [1])]
```

The 2d plot of the [`SystemStructure`](@ref) should still look the same, so we don't have to plot that. We can just create the system, and simulate it. We just need to be sure that we call plot with `t=0.0` to reset the plot.

```julia
sys_struct = SystemStructure("winch", set; points, segments, tethers, winches, transforms)
@show set.v_wind
sam = SymbolicAWEModel(set, sys_struct)
init_sim!(sam; remake=false)
ss = SysState(sam)

for i in 1:80
plot(sam, (i-1)/set.sample_freq)
next_step!(sam; set_values=[-20.0])
update_sys_state!(ss, sam)
end
@show ss.l_tether[1]
```

# Using a pulley

First, we need to update some settings. `l_tether` is specified such that the plot window is zoomed in correctly.

```julia
using KiteModels, VortexStepMethod, ControlPlots

set = se("system_ram.yaml")
set.v_wind = 10.0
set.l_tether = 5.0
set.abs_tol = 1e-4
set.rel_tol = 1e-4
dynamics_type = DYNAMIC
```

Now we create points and segments in a similar manner as in the last example. The `mass` keyword can be used to specify the mass of the point itself. When `mass=0.0` the mass of the point just consists of the tether/segment mass.

```julia
points = Point[]
segments = Segment[]
pulleys = Pulley[]

push!(points, Point(1, [0.0, 0.0, 2.0], STATIC))
push!(points, Point(2, [2.0, 0.0, 2.0], STATIC))
push!(points, Point(3, [0.1, 0.0, 1.0], DYNAMIC))
push!(points, Point(4, [0.1, 0.0, 0.0], DYNAMIC; mass=0.1))

push!(segments, Segment(1, (3,1), BRIDLE))
push!(segments, Segment(2, (3,2), BRIDLE))
push!(segments, Segment(3, (3,4), BRIDLE))
```

Pulleys can be modeled when three or more [`Segment`](@ref)s are connected to a common [`Point`](@ref). When creating a pulley, only two segments are specified: these are the segments of the tether moving through the pulley.

```julia
push!(pulleys, Pulley(1, (1,2), DYNAMIC))
```

We can then use a [`Transform`](@ref) to describe the orientation of the initial system.

```julia
transforms = [Transform(1, -deg2rad(0.0), 0.0, 0.0; base_pos=[1.0, 0.0, 4.0], base_point_idx=1, rot_point_idx=2)]
sys_struct = KiteModels.SystemStructure("pulley", set; points, segments, pulleys, transforms)
plot(sys_struct, 0.0; zoom=false, l_tether=set.l_tether)
```

If the plot of the [`SystemStructure`](@ref) looks good, we can continue by creating a [`SymbolicAWEModel`](@ref) and simulating through time.

```julia
sam = SymbolicAWEModel(set, sys_struct)
init_sim!(sam; remake=false)
for i in 1:100
plot(sam, i/set.sample_freq; zoom=false)
next_step!(sam)
end
```


78 changes: 0 additions & 78 deletions docs/src/tutorial_system_structure.md

This file was deleted.

39 changes: 39 additions & 0 deletions examples/pulley.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# SPDX-FileCopyrightText: 2025 Bart van de Lint
#
# SPDX-License-Identifier: MPL-2.0

using KiteModels, VortexStepMethod, ControlPlots

set = se("system_ram.yaml")
set.v_wind = 10.0
set.l_tether = 5.0
set.abs_tol = 1e-4
set.rel_tol = 1e-4
dynamics_type = DYNAMIC

points = Point[]
segments = Segment[]
pulleys = Pulley[]

push!(points, Point(1, [0.0, 0.0, 2.0], STATIC))
push!(points, Point(2, [2.0, 0.0, 2.0], STATIC))
push!(points, Point(3, [0.1, 0.0, 1.0], DYNAMIC))
push!(points, Point(4, [0.1, 0.0, 0.0], DYNAMIC; mass=0.1))

push!(segments, Segment(1, (3,1), BRIDLE))
push!(segments, Segment(2, (3,2), BRIDLE))
push!(segments, Segment(3, (3,4), BRIDLE))

push!(pulleys, Pulley(1, (1,2), DYNAMIC))

transforms = [Transform(1, -deg2rad(0.0), 0.0, 0.0; base_pos=[1.0, 0.0, 4.0], base_point_idx=1, rot_point_idx=2)]
sys_struct = KiteModels.SystemStructure("pulley", set; points, segments, pulleys, transforms)
plot(sys_struct, 0.0; zoom=false, l_tether=set.l_tether)

sam = SymbolicAWEModel(set, sys_struct)

init_sim!(sam; remake=false)
for i in 1:100
plot(sam, i/set.sample_freq; zoom=false)
next_step!(sam)
end
16 changes: 7 additions & 9 deletions examples/ram_air_kite.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ using Timers
tic()
@info "Loading packages "

PLOT = true
PLOT = false
using Pkg
if ! ("LaTeXStrings" ∈ keys(Pkg.project().dependencies))
using TestEnv; TestEnv.activate()
Expand All @@ -30,7 +30,7 @@ steering_freq = 1/2 # Hz - full left-right cycle frequency
steering_magnitude = 10.0 # Magnitude of steering input [Nm]

# Initialize model
set = load_settings("system_ram.yaml")
set = Settings("system_ram.yaml")
set.segments = 3
set_values = [-50, 0.0, 0.0] # Set values of the torques of the three winches. [Nm]
set.quasi_static = false
Expand All @@ -42,13 +42,9 @@ sam.set.abs_tol = 1e-2
sam.set.rel_tol = 1e-2
toc()

# init_Q_b_w, R_b_w = KiteModelsam.initial_orient(sam.set)
# init_kite_pos = init!(sam.sys_struct, sam.set, R_b_w, init_Q_b_w)
# plot(sam.sys_struct, 0.0; zoom=false, front=false)

# Initialize at elevation
set.l_tethers[2] += 0.2
set.l_tethers[3] += 0.2
set.l_tethers[2] += 0.4
set.l_tethers[3] += 0.4
init_sim!(sam; remake=false, reload=false)
sys = sam.sys

Expand All @@ -63,7 +59,7 @@ sys_state = SysState(sam)
t = 0.0
runtime = 0.0
integ_runtime = 0.0
bias = set.quasi_static ? 0.45 : 0.40
bias = set.quasi_static ? 0.45 : 0.35
t0 = sam.integrator.t

try
Expand Down Expand Up @@ -149,3 +145,5 @@ display(p)
@info "Performance:" times_realtime=(total_time/2)/runtime integrator_times_realtime=(total_time/2)/integ_runtime

# 55x realtime (PLOT=false, CPU: Intel i9-9980HK (16) @ 5.000GHz)
# 40-65x realtime (PLOT=false, CPU: Intel i9-9980HK (16) @ 5.000GHz) - commit 6620ed5d0a38e96930615aad9a66e4cd666955f2
# 40x realtime (PLOT=false, CPU: Intel i9-9980HK (16) @ 5.000GHz) - commit 88a78894038d3cbd50fbff83dfbe5c26266b0637
Loading
Loading