Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 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
5 changes: 3 additions & 2 deletions Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ StaticArrays = "90137ffa-7385-5640-81b9-e52037218182"
Statistics = "10745b16-79ce-11e8-11f9-7d13ad32a3b2"
StatsBase = "2913bbd2-ae8a-5f71-8c99-4fb6c76f3a91"
Sundials = "c3572dad-4567-51f8-b174-8c6c989267f4"
SymbolicAWEModels = "9c9a347c-5289-41db-a9b9-25ccc76c3360"
SymbolicIndexingInterface = "2efcf032-c050-4f8e-a9bb-153293bab1f5"
SymbolicUtils = "d1185830-fcd6-423d-90d6-eec64667417b"
Timers = "21f18d07-b854-4dab-86f0-c15a3821819a"
Expand Down Expand Up @@ -71,7 +72,6 @@ KiteUtils = "0.10.15"
LaTeXStrings = "1.4.0"
LinearAlgebra = "1.10, 1.11"
LinearSolve = "~2.39.0, 3"
ModelingToolkit = "~9.78.0"
NLSolversBase = "~7.8.3"
NLsolve = "4.5"
NonlinearSolve = "4.8.0"
Expand All @@ -93,11 +93,12 @@ StaticArrays = "1.9.7"
Statistics = "1"
StatsBase = "0.34"
Sundials = "4.24"
SymbolicAWEModels = "0.1.2"
SymbolicIndexingInterface = "0.3"
SymbolicUtils = "3.25"
Test = "1"
Timers = "0.1.5"
VortexStepMethod = "1.2.6"
VortexStepMethod = "2.0.0"
WinchModels = "0.3.6"
julia = "1.10, 1.11"

Expand Down
6 changes: 3 additions & 3 deletions data/settings_ram.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ system:
# use / as path delimiter, even on Windows
time_lapse: 1.0 # relative replay speed
sim_time: 100.0 # simulation time [sim only]
segments: 6 # number of tether segments
segments: 3 # number of tether segments
sample_freq: 20 # sample frequency in Hz
zoom: 0.03 # zoom factor for the system view
kite_scale: 3.0 # relative zoom factor for the 4 point kite
fixed_font: "" # name or filepath+filename of alternative fixed pitch font

initial:
l_tethers: [50.0, 50.0, 50.0] # initial tether length [m]
l_tethers: [50.0, 50.4, 50.4] # initial tether length [m]
elevation: 70.8 # initial elevation angle [deg]
v_reel_outs: [0.0, 0.0, 0.0] # initial reel out speed [m/s]

Expand Down Expand Up @@ -69,7 +69,7 @@ environment:
rho_0: 1.225 # air density at zero height and 15 °C [kg/m³]
alpha: 0.08163 # exponent of the wind profile law
z0: 0.0002 # surface roughness [m]
profile_law: 3 # 1=EXP, 2=LOG, 3=EXPLOG, 4=F
profile_law: 3 # 1=EXP, 2=LOG, 3=EXPLOG, 4=FAST_EXP, 5=FAST_LOG, 6=FAST_EXPLOG
# the following parameters are for calculating the turbulent wind field using the Mann model
use_turbulence: 0.0 # turbulence intensity relative to Cabau, NL
v_wind_gnds: [3.483, 5.324, 8.163] # wind speeds at ref height for calculating the turbulent wind field [m/s]
Expand Down
81 changes: 25 additions & 56 deletions examples/lin_ram_model.jl
Original file line number Diff line number Diff line change
@@ -1,93 +1,62 @@
# Copyright (c) 2025 Bart van de Lint
# SPDX-License-Identifier: MPL-2.0

#=
This example demonstrates linearized model accuracy by comparing:
1. Nonlinear SymbolicAWEModel model simulation
2. Linearized state-space model simulation

Both models start from the same operating point and are subjected
to identical steering inputs. The resulting state trajectories are
plotted together to visualize how well the linearized model
approximates the nonlinear dynamics.
=#
# This example demonstrates how to:
# - Load a system configuration from a YAML file,
# - Initialize a SymbolicAWEModel with specified winch torques,
# - Stabilize the system at its operating point,
# - Linearize the system at that point,
# - And plot the Bode plots of the resulting linearized system.

using Timers
tic()
@info "Loading packages "

PLOT = true
if PLOT
using Pkg
if ! ("LaTeXStrings" ∈ keys(Pkg.project().dependencies))
using TestEnv; TestEnv.activate()
end
using ControlPlots, LaTeXStrings, ControlSystemsBase
using Pkg
if ! ("LaTeXStrings" ∈ keys(Pkg.project().dependencies))
using TestEnv; TestEnv.activate()
end
using ControlPlots, LaTeXStrings, ControlSystemsBase

using KiteModels, LinearAlgebra, Statistics, OrdinaryDiffEqCore
using SymbolicAWEModels, KiteUtils, LinearAlgebra, Statistics
using ModelingToolkit
using ModelingToolkit: t_nounits
toc()

# TODO: use sparse autodiff

# Simulation parameters
dt = 0.001
total_time = 1.0 # Increased from 0.1s to 1.0s for better dynamics observation
vsm_interval = 3
steps = Int(round(total_time / dt))

# Steering parameters
steering_freq = 1/2 # Hz - full left-right cycle frequency
steering_magnitude = 5.0 # Magnitude of steering input [Nm]

# Initialize model
set = load_settings("system_ram.yaml")
set.segments = 3
set = Settings("system_ram.yaml")
set_values = [-50.0, 0.0, 0.0] # Set values of the torques of the three winches. [Nm]
set.quasi_static = false
set.physical_model = "simple_ram"

@info "Creating SymbolicAWEModel model..."
@info "Creating SymbolicAWEModel..."
s = SymbolicAWEModel(set)
s.set.abs_tol = 1e-2
s.set.rel_tol = 1e-2
toc()

# Define outputs for linearization - heading
lin_outputs = @variables heading(t_nounits)[1]
@variables begin
heading(t_nounits)[1]
angle_of_attack(t_nounits)[1]
tether_len(t_nounits)[1:3]
winch_force(t_nounits)[1:3]
end
lin_outputs = [heading[1], angle_of_attack[1], tether_len[1], winch_force[1]]
@info "Linear outputs: $lin_outputs"

# Initialize at elevation with linearization outputs
s.sys_struct.winches[2].tether_length += 0.2
s.sys_struct.winches[3].tether_length += 0.2
KiteModels.init!(s;
remake=false,
reload=true,
lin_outputs # Specify which outputs to track in linear model
)
SymbolicAWEModels.init!(s; lin_outputs)
sys = s.sys

@show rad2deg(s.integrator[sys.elevation[1]])


@info "System initialized at:"
toc()

# --- Stabilize system at operating point ---
@info "Stabilizing system at operating point..."
s.integrator.ps[sys.stabilize] = true
stabilization_steps = Int(10 ÷ dt)
for i in 1:stabilization_steps
next_step!(s; dt, vsm_interval=0.05÷dt)
end
s.integrator.ps[sys.stabilize] = false
SymbolicAWEModels.find_steady_state!(s)

# --- Linearize at operating point ---
@info "Linearizing system at operating point..."
@time (; A, B, C, D) = KiteModels.linearize(s)
@time (; A, B, C, D) = KiteModels.linearize(s)
@show norm(A)
(; A, B, C, D) = SymbolicAWEModels.linearize!(s)
@time (; A, B, C, D) = SymbolicAWEModels.linearize!(s)
@info "System linearized with matrix dimensions:" A=size(A) B=size(B) C=size(C) D=size(D)

sys = ss(A,B,C,D)
Expand Down
28 changes: 10 additions & 18 deletions examples/ram_air_kite.jl
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ using Timers
tic()
@info "Loading packages "

PLOT = true
PLOT = false
using Pkg
if ! ("LaTeXStrings" ∈ keys(Pkg.project().dependencies))
using TestEnv; TestEnv.activate()
end
using ControlPlots, LaTeXStrings
using KiteModels, LinearAlgebra, Statistics
using SymbolicAWEModels, KiteUtils, LinearAlgebra, Statistics

if ! @isdefined SIMPLE
SIMPLE = false
Expand All @@ -30,40 +30,31 @@ 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.segments = 3
set = Settings("system_ram.yaml")
set_values = [-50, 0.0, 0.0] # Set values of the torques of the three winches. [Nm]
set.quasi_static = false
set.physical_model = SIMPLE ? "simple_ram" : "ram"

@info "Creating wing, aero, vsm_solver, sys_struct and symbolic_awe_model:"
@info "Creating SymbolicAWEModel:"
sam = SymbolicAWEModel(set)
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
init!(sam; remake=false, reload=false)
SymbolicAWEModels.init!(sam; remake=false, reload=false)
sys = sam.sys

@info "System initialized at:"
toc()

# Stabilize system
find_steady_state!(sam)
SymbolicAWEModels.find_steady_state!(sam)

logger = Logger(length(sam.sys_struct.points), steps)
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 All @@ -73,14 +64,13 @@ try
PLOT && plot(sam, t; zoom=false, front=false)

# Calculate steering inputs based on cosine wave
steering = steering_magnitude * cos(2π * steering_freq * t + bias)-0.001*t*t
steering = steering_magnitude * cos(2π * steering_freq * t + bias)
set_values = -sam.set.drum_radius .* sam.integrator[sys.winch_force]
_vsm_interval = 1
if t > 1.0
set_values .+= [0.0, steering, -steering] # Opposite steering for left/right
_vsm_interval = vsm_interval
end

# Step simulation
steptime = @elapsed next_step!(sam; set_values, dt, vsm_interval=vsm_interval)
t_new = sam.integrator.t
Expand Down Expand Up @@ -149,3 +139,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