Skip to content
Draft
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
12 changes: 10 additions & 2 deletions Manifest-v1.12.toml.default
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

julia_version = "1.12.7"
manifest_format = "2.0"
project_hash = "02a519ef26de8d09c0837d7cff2d48fa2d64c8e7"
project_hash = "dd958dd897cd55999deead3b6d0c5ed2dc8a633b"

[[deps.ADTypes]]
deps = ["PrecompileTools"]
Expand Down Expand Up @@ -1352,8 +1352,16 @@ git-tree-sha1 = "9eda8292dd3268b3b7ec9df21bbfac24e177ec52"
uuid = "5ab0869b-81aa-558d-bb23-cbf5423bbe9b"
version = "0.6.12"

[[deps.KiteGeometry]]
deps = ["OrderedCollections", "StaticArrays", "YAML"]
git-tree-sha1 = "945cfc0586921e5c6a8773aaffc15d7a456426cf"
repo-rev = "1ca16d8ff26b98fff09e68696c6a830bf4cb2acf"
repo-url = "https://github.com/OpenSourceAWE/KiteGeometry.jl"
uuid = "941ca69c-4b42-4a27-9601-abc4d37bf99e"
version = "0.1.0"

[[deps.KiteModels]]
deps = ["AtmosphericModels", "Dierckx", "DiffEqBase", "DiscretePIDs", "DocStringExtensions", "Interpolations", "KitePodModels", "KiteUtils", "LinearAlgebra", "LinearSolve", "Logging", "NLSolversBase", "NLsolve", "NonlinearSolve", "OrdinaryDiffEqBDF", "OrdinaryDiffEqCore", "OrdinaryDiffEqNonlinearSolve", "Parameters", "Pkg", "PrecompileTools", "REPL", "Reexport", "Rotations", "StaticArrays", "Statistics", "Sundials", "Suppressor", "WinchModels"]
deps = ["AtmosphericModels", "Dierckx", "DiffEqBase", "DiscretePIDs", "DocStringExtensions", "Interpolations", "KiteGeometry", "KitePodModels", "KiteUtils", "LinearAlgebra", "LinearSolve", "Logging", "NLSolversBase", "NLsolve", "NonlinearSolve", "OrdinaryDiffEqBDF", "OrdinaryDiffEqCore", "OrdinaryDiffEqNonlinearSolve", "Parameters", "Pkg", "PrecompileTools", "REPL", "Reexport", "Rotations", "StaticArrays", "Statistics", "Sundials", "Suppressor", "WinchModels", "YAML"]
path = "."
uuid = "b94af626-7959-4878-9336-2adc27959007"
version = "0.12.0"
Expand Down
16 changes: 12 additions & 4 deletions Manifest-v1.13.toml.default
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
# This file is machine-generated - editing it directly is not advised

julia_version = "1.13.1"
julia_version = "1.13.0"
manifest_format = "2.1"
project_hash = "8ab9aee386001baa6732b7c487c6ae190f8d1f7f"
project_hash = "dd958dd897cd55999deead3b6d0c5ed2dc8a633b"

[[deps.ADTypes]]
deps = ["PrecompileTools"]
Expand Down Expand Up @@ -1509,8 +1509,16 @@ registries = "General"
uuid = "5ab0869b-81aa-558d-bb23-cbf5423bbe9b"
version = "0.6.12"

[[deps.KiteGeometry]]
deps = ["OrderedCollections", "StaticArrays", "YAML"]
git-tree-sha1 = "945cfc0586921e5c6a8773aaffc15d7a456426cf"
repo-rev = "1ca16d8ff26b98fff09e68696c6a830bf4cb2acf"
repo-url = "https://github.com/OpenSourceAWE/KiteGeometry.jl"
uuid = "941ca69c-4b42-4a27-9601-abc4d37bf99e"
version = "0.1.0"

[[deps.KiteModels]]
deps = ["AtmosphericModels", "Dierckx", "DiffEqBase", "DiscretePIDs", "DocStringExtensions", "Interpolations", "KitePodModels", "KiteUtils", "LinearAlgebra", "LinearSolve", "Logging", "NLSolversBase", "NLsolve", "NonlinearSolve", "OrdinaryDiffEqBDF", "OrdinaryDiffEqCore", "OrdinaryDiffEqNonlinearSolve", "Parameters", "Pkg", "PrecompileTools", "REPL", "Reexport", "Rotations", "StaticArrays", "Statistics", "Sundials", "Suppressor", "WinchModels"]
deps = ["AtmosphericModels", "Dierckx", "DiffEqBase", "DiscretePIDs", "DocStringExtensions", "Interpolations", "KiteGeometry", "KitePodModels", "KiteUtils", "LinearAlgebra", "LinearSolve", "Logging", "NLSolversBase", "NLsolve", "NonlinearSolve", "OrdinaryDiffEqBDF", "OrdinaryDiffEqCore", "OrdinaryDiffEqNonlinearSolve", "Parameters", "Pkg", "PrecompileTools", "REPL", "Reexport", "Rotations", "StaticArrays", "Statistics", "Sundials", "Suppressor", "WinchModels", "YAML"]
path = "."
uuid = "b94af626-7959-4878-9336-2adc27959007"
version = "0.12.0"
Expand Down Expand Up @@ -1618,7 +1626,7 @@ version = "1.9.1+0"
[[deps.LibSSH2_jll]]
deps = ["Artifacts", "CompilerSupportLibraries_jll", "Libdl", "OpenSSL_jll", "Zlib_jll"]
uuid = "29816b5a-b9ab-546f-933c-edad1886dfa8"
version = "1.11.104+0"
version = "1.11.103+0"

[[deps.Libdl]]
uuid = "8f399da3-3557-5675-b5ff-fb832c97cbdb"
Expand Down
7 changes: 7 additions & 0 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ DiffEqBase = "2b5f629d-d688-5b77-993f-72d75c75574e"
DiscretePIDs = "c1363496-6848-4723-8758-079b737f6baf"
DocStringExtensions = "ffbed154-4ef7-542d-bbb7-c09d3a79fcae"
Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59"
KiteGeometry = "941ca69c-4b42-4a27-9601-abc4d37bf99e"
KitePodModels = "9de5dc81-f971-414a-927b-652b2f41c539"
KiteUtils = "90980105-b163-44e5-ba9f-8b1c83bb0533"
LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e"
Expand All @@ -35,6 +36,10 @@ Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4"
Suppressor = "fd094767-a336-5f1f-9728-57cf17d0bbfb"
WinchModels = "7dcfa46b-7979-4771-bbf4-0aee0da42e1f"
YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6"

[sources]
KiteGeometry = {url = "https://github.com/OpenSourceAWE/KiteGeometry.jl", rev = "1ca16d8ff26b98fff09e68696c6a830bf4cb2acf"}

[compat]
AtmosphericModels = "0.3.8"
Expand All @@ -43,6 +48,7 @@ DiffEqBase = "6.161.0"
DiscretePIDs = "0.1.5, 1.0"
DocStringExtensions = "0.9.4"
Interpolations = "0.15.1, 0.16"
KiteGeometry = "0.1"
KitePodModels = "0.4.0"
KiteUtils = "0.13"
LinearAlgebra = "1.12, 1.13"
Expand All @@ -65,4 +71,5 @@ Statistics = "1"
Sundials = "4.24, 5.1"
Suppressor = "0.2.8"
WinchModels = "0.3.12"
YAML = "0.4.12"
julia = "1.12, 1.13"
8 changes: 8 additions & 0 deletions changelog.d/322-one-kite-system-definition-across-the-ki.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<!--
SPDX-FileCopyrightText: 2026 Bart van de Lint
SPDX-License-Identifier: MIT
-->
### Added
- `system_definition(s)` builds a KiteGeometry `SystemDefinition` of a KPS3 or KPS4 model: its points, the springs it integrates as segments, the tether and the winch.
- `topology_metadata(s)` carries that definition in a log: `save_log(logger, name; metadata)` writes it under the key `topology`.
- KiteGeometry is re-exported.
17 changes: 17 additions & 0 deletions docs/src/functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,22 @@ states
SysState
```

## System definition

A `SystemDefinition` from [KiteGeometry](https://github.com/OpenSourceAWE/KiteGeometry.jl) describes the points, segments, tether and winch of a model, so a log can be drawn by anything that reads a definition. Build it after `init!` and pass it to `save_log`:

```julia
integrator = init!(s)
metadata = topology_metadata(s)
# ... simulate and log! ...
save_log(logger, "sim_log"; metadata)
```

```@docs
system_definition
topology_metadata
```

## High level simulation interface

```@docs
Expand Down Expand Up @@ -91,4 +107,5 @@ calc_particle_forces!
inner_loop!
loop!
make_jac
assemble_system_definition
```
3 changes: 2 additions & 1 deletion examples/reel_out_4p_torque_control.jl
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ function simulate(integrator, steps, plot=false)
end

integrator = KiteModels.init!(kps4; delta=0.003, stiffness_factor=0.1, prn=STATISTIC)
metadata = topology_metadata(kps4)
kps4.sync_speed = 0.0

av_steps = if PLOT
Expand Down Expand Up @@ -124,7 +125,7 @@ println("Kite mass: $(set.mass) kg")
println("KCU mass: $(set.kcu_mass) kg")
println("Tether diameter: $(set.d_tether) mm")

save_log(logger, "reel_out_4p_torque_control")
save_log(logger, "reel_out_4p_torque_control"; metadata)
# savefig("docs/src/reelout_force_4p.png")

# Solver: DFBDF, reltol=0.000001
Expand Down
4 changes: 4 additions & 0 deletions src/KiteModels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ import KiteUtils: SysState, calc_course, calc_elevation, calc_heading
@reexport using KitePodModels
@reexport using WinchModels
@reexport using AtmosphericModels
@reexport using KiteGeometry
import YAML
using Rotations
import Base.zero
import OrdinaryDiffEqCore.init
Expand All @@ -38,6 +40,7 @@ export cl_cd, lift_drag, lift_over_drag, reel_out_speed, tether_length, unstretc
export calculate_rotational_inertia!
export copy_model_settings, install_examples_3d, kite_ref_frame, menu2, orient_euler, reactivate_host_app
export get_default_turbulence, set_default_turbulence, spring_forces, states, upwind_dir
export system_definition, topology_metadata
import LinearAlgebra: norm

set_zero_subnormals(true) # required to avoid drastic slow down on Intel CPUs when numbers become very small
Expand Down Expand Up @@ -85,6 +88,7 @@ end

include("KPS4.jl") # include code, specific for the four point kite model
include("KPS3.jl") # include code, specific for the one point kite model
include("system_definition.jl")
include("utils.jl") # functions to calculate the initial state vector, the initial masses and initial springs

function menu2()
Expand Down
73 changes: 73 additions & 0 deletions src/system_definition.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# SPDX-FileCopyrightText: 2026 Bart van de Lint
# SPDX-License-Identifier: MIT

"""
system_definition(s::KPS4)

The `SystemDefinition` of the four-point model: its points where they are now, and as
segments the tether and bridle springs it integrates, at full stiffness. The bridle
segments are massless. Call it after `init!` for the initial pose.
"""
function system_definition(s::KPS4)
tether_segments = s.set.segments
extra_masses = zeros(length(s.pos))
extra_masses[tether_segments + 1] = s.set.kcu_mass
extra_masses[(tether_segments + 2):end] .= s.masses[(tether_segments + 2):end]
bridle_segments = length(s.springs) - tether_segments
diameters = [fill(s.set.d_tether, tether_segments); fill(s.set.d_line, bridle_segments)]
Comment thread
1-Bort-1 marked this conversation as resolved.
densities = [fill(s.set.rho_tether, tether_segments); zeros(bridle_segments)]
segments = [Segment(; name=string(i), points=(Int(spring.p1), Int(spring.p2)),
l0=spring.length, diameter=diameters[i] / 1000,
density=densities[i],
unit_stiffness=spring.axial_stiffness * spring.length)
for (i, spring) in enumerate(s.springs)]
return assemble_system_definition(s, segments, extra_masses)
end

"""
system_definition(s::KPS3)

The `SystemDefinition` of the one-point model: its points where they are now, the tether
segments between them at full stiffness, and the kite and KCU mass on the last point. Call
it after `init!` for the initial pose.
"""
function system_definition(s::KPS3)
extra_masses = zeros(length(s.pos))
extra_masses[end] = s.set.mass + s.set.kcu_mass
segments = [Segment(; name=string(i), points=(i, i + 1), l0=s.segment_length,
diameter=s.set.d_tether / 1000, density=s.set.rho_tether,
unit_stiffness=s.axial_stiffness * s.segment_length)
for i in 1:s.set.segments]
return assemble_system_definition(s, segments, extra_masses)
end

"""
assemble_system_definition(s::AbstractKiteModel, segments, extra_masses)

The `SystemDefinition` of `s` with its `segments`, the first `s.set.segments` of which are
the tether from the winch at point 1 to point `s.set.segments + 1`.
"""
function assemble_system_definition(s::AbstractKiteModel, segments, extra_masses)
n_points = length(s.pos)
tether_segments = s.set.segments
metadata = Metadata(string(nameof(typeof(s))), "", "", "1.0.0", "structure_schema.yml",
Comment thread
1-Bort-1 marked this conversation as resolved.
n_points, "")
points = [Point(; name=string(i), type=i == 1 ? STATIC : DYNAMIC, pos_ENU=s.pos[i],
extra_mass=extra_masses[i])
for i in 1:n_points]
tether = Tether(; name="tether", start_point=1, end_point=tether_segments + 1,
segments=collect(1:tether_segments))
winch = Winch(; name="winch", tethers=[1], winch_point=1, gear_ratio=s.set.gear_ratio,
drum_radius=s.set.drum_radius)
return SystemDefinition(; metadata, points, segments, tethers=[tether], winches=[winch])
end

"""
topology_metadata(s::AbstractKiteModel)

The table metadata that carries the structure document of `system_definition(s)` in a
log, under the key `topology`: pass it as `save_log(logger, name; metadata)`.
"""
function topology_metadata(s::AbstractKiteModel)
return Dict("topology" => YAML.write(structure_document(system_definition(s))))
end
2 changes: 2 additions & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
WinchModels = "7dcfa46b-7979-4771-bbf4-0aee0da42e1f"
YAML = "ddb6d928-2868-570f-bddf-ab3f9cf99eb6"

[sources]
KiteModels = {path = ".."}
Expand All @@ -51,3 +52,4 @@ NonlinearSolve = "4.11"
OrdinaryDiffEqNonlinearSolve = "1.6.1"
StatsBase = "0.34"
Test = "1"
YAML = "0.4.12"
57 changes: 57 additions & 0 deletions test/test-system-definition.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# SPDX-FileCopyrightText: 2026 Bart van de Lint
# SPDX-License-Identifier: MIT

using Pkg
if dirname(Pkg.project().path) != @__DIR__
Pkg.activate(@__DIR__)
end
using KiteModels, KiteUtils, Test, YAML
using KiteModels: init!

set_data_path(joinpath(dirname(dirname(pathof(KiteModels)::String)), "data"))

"The pair of point indices each segment of `definition` joins."
segment_points(definition) = [segment.points for segment in definition.segments]

@testset "KPS4 definition holds the points and springs the model integrates" begin
kps4 = KPS4(load_settings("system.yaml"))
init!(kps4; delta=0.001, prn=false)
definition = system_definition(kps4)
segments = kps4.set.segments
@test definition.metadata.n_points == length(kps4.pos) == segments + 5
@test segment_points(definition) == [(spring.p1, spring.p2) for spring in kps4.springs]
@test [point.pos_ENU for point in definition.points] == kps4.pos
@test [segment.l0 for segment in definition.segments] ==
[spring.length for spring in kps4.springs]
@test only(definition.tethers).segments == 1:segments
@test only(definition.tethers).end_point == segments + 1
@test only(definition.winches).winch_point == 1
@test [segment.unit_stiffness for segment in definition.segments] ≈
[spring.axial_stiffness * spring.length for spring in kps4.springs]
@test sum(point.extra_mass for point in definition.points) ≈
kps4.set.mass + kps4.set.kcu_mass
@test all(segment.density == 0 for segment in definition.segments[(segments + 1):end])
end

@testset "KPS3 definition is one tether from the winch to the kite" begin
kps3 = KPS3(load_settings("system.yaml"))
init!(kps3; delta=0.001)
definition = system_definition(kps3)
segments = kps3.set.segments
@test definition.metadata.n_points == length(kps3.pos) == segments + 1
@test segment_points(definition) == [(i, i + 1) for i in 1:segments]
@test only(definition.tethers).segments == 1:segments
@test definition.points[end].extra_mass ≈ kps3.set.mass + kps3.set.kcu_mass
end

@testset "a saved log carries the KPS4 definition under topology" begin
kps4 = KPS4(load_settings("system.yaml"))
init!(kps4; delta=0.001, prn=false)
logger = Logger(length(kps4.pos), 1)
log!(logger, SysState(kps4))
path = mktempdir()
save_log(logger, "topology"; path, metadata=topology_metadata(kps4))
document = YAML.load(load_log("topology"; path).metadata["topology"])
@test structure_document(SystemDefinition(document)) ==
structure_document(system_definition(kps4))
end
Loading