Quasi-static segmented tether model

I want to make a quasi-static tether model of a segmented tether. This means that I want to use a nonlinear solver to find positions, such that the acceleration of the tether points is zero. For this I need to run a nonlinear solver inside the model, that finds these positions. I still want to keep some differential equations (for the last tether point or for an object connected to the last tether point).

I know I can @register_symbolic a function to do this, but then I have to move all tether force equations outside of the equation set and inside the function. Are there any better ways to add a nonlinear solver inside the ODE?

The code example below doesn’t work, but it does show the set of equations I want to solve.

# Example simulating a 3D mass-spring system with a nonlinear spring (no spring forces
# for l < l_0) and n tether segments. 
using ModelingToolkit, OrdinaryDiffEq, LinearAlgebra, Timers, ControlPlots
using ModelingToolkit: t_nounits as t, D_nounits as D

G_EARTH::Vector{Float64} = [0.0, 0.0, -9.81]    # gravitational acceleration     [m/s²]
L0::Float64 = 5.0                               # initial segment length            [m]
V0::Float64 = 2                                 # initial velocity of lowest mass [m/s]
M0::Float64 = 0.5                               # mass per particle                [kg]
C_SPRING::Float64 = 50                          # spring constant
segments::Int64 = 5                             # number of tether segments         [-]
α0 = π/10                                       # initial tether angle            [rad]
duration = 10.0                                 # duration of the simulation        [s]
POS0 = zeros(3, segments+1)
VEL0 = zeros(3, segments+1)
for i in 1:segments+1
    local l0
    l0 = -(i-1) * L0
    v0 = (i-1) * V0/segments
    POS0[:, i] .= [sin(α0) * l0, 0, cos(α0) * l0]
    VEL0[:, i] .= [sin(α0) * v0, 0, cos(α0) * v0]
end

# defining the model, Z component upwards
@parameters mass=M0 c_spring0=C_SPRING damping=0.5 l_seg=L0
@variables pos(t)[1:3, 1:segments+1]  = POS0
@variables vel(t)[1:3, 1:segments+1]  = VEL0
@variables acc(t)[1:3, 1:segments+1]
@variables segment(t)[1:3, 1:segments]
@variables unit_vector(t)[1:3, 1:segments]
@variables norm1(t)[1:segments]
@variables rel_vel(t)[1:3, 1:segments]
@variables spring_vel(t)[1:segments]
@variables c_spring(t)[1:segments]
@variables spring_force(t)[1:3, 1:segments]
@variables total_force(t)[1:3, 1:segments+1]

function model()
    eqs = []
    # loop over all segments to calculate the spring forces
    for i in segments:-1:1
        eqs = [
            eqs
            segment[:, i]      ~ pos[:, i+1] - pos[:, i]
            norm1[i]           ~ norm(segment[:, i])
            unit_vector[:, i]  ~ -segment[:, i]/norm1[i]
            rel_vel[:, i]      ~ vel[:, i+1] - vel[:, i]
            spring_vel[i]      ~ -unit_vector[:, i] â‹… rel_vel[:, i]
            c_spring[i]           ~ c_spring0 * (norm1[i] > l_seg)
            spring_force[:, i] ~ (c_spring[i] * (norm1[i] - l_seg) + damping * spring_vel[i]) * unit_vector[:, i]
        ]
    end
    # loop over all tether particles to apply the forces and calculate the accelerations
    for i in 1:(segments+1)
        if i == segments+1
            eqs = [
                eqs
                total_force[:, i] ~ spring_force[:, i-1]
                acc[:, i]         ~ G_EARTH + total_force[:, i] / (10 * mass)
                D(pos[:, i])      ~ vel[:, i]
                D(vel[:, i])      ~ acc[:, i]
            ]
        elseif i == 1
            eqs = [
                eqs
                total_force[:, i] ~ spring_force[:, i]
                acc[:, i]         ~ zeros(3)
                pos[:, i]         ~ zeros(3)
                vel[:, i]         ~ zeros(3)
            ]
        else
            eqs = [
                eqs
                total_force[:, i] ~ spring_force[:, i-1] - spring_force[:, i]
                acc[:, i]         ~ G_EARTH + total_force[:, i] / mass
                acc[:, i]         ~ zeros(3) # acceleration is constrained to zero
                vel[:, i]         ~ zeros(3)
            ]
        end
    end
    return eqs
end
     
@named sys = ODESystem(reduce(vcat, Symbolics.scalarize.(model())), t)
simple_sys = structural_simplify(sys)

# running the simulation
dt = 0.02
tol = 1e-6
tspan = (0.0, duration)
ts    = 0:dt:duration

prob = ODEProblem(simple_sys, nothing, tspan)
elapsed_time = @elapsed sol = solve(prob, Rodas4(autodiff=ModelingToolkit.AutoFiniteDiff()); dt, abstol=tol, reltol=tol, saveat=ts)
elapsed_time = @elapsed sol = solve(prob, Rodas4(autodiff=ModelingToolkit.AutoFiniteDiff()); dt, abstol=tol, reltol=tol, saveat=ts)
println("Elapsed time: $(elapsed_time) s, speed: $(round(duration/elapsed_time)) times real-time")

function play()
    dt = 0.15
    ylim = (-1.2 * segments * L0, 0.5)
    xlim = (-segments * L0/2, segments * L0/2)
    z_max = 0.0
    # text position
    xy = (segments * L0/4.2, z_max-3.0*segments/5)
    start = time_ns()
    i = 1
    for time in 0:dt:duration
        # while we run the simulation in steps of 20ms, we update the plot only every 150ms
        # therefore we have to skip some steps of the result
        while sol.t[i] < time
            i += 1
        end
        plot2d(sol[pos][i], time; segments, xlim, ylim, xy)
        wait_until(start + 0.5 * time * 1e9)
    end
    nothing
end
play()

This is the model we want to implement: https://arc.aiaa.org/doi/10.2514/1.G002354

Main goal is not so much speed (the fully dynamic model is fast enough using Julia), but more to be able to get a linearized model with less states to make it easier to design a controller.

The full MTK model consists of:

  • one or more of these tether models
  • the kite model
  • the winch model

Currently we have 31 degrees of freedom (DOF) for my model, with this tether model we hope to be able to reduce that to 16 DOF. Barts model has four tethers, so even more DOF.

1 Like

Is model-order reduction after linearization an option? We have plenty of tools for that

Yes! But I don’t have experience with that. Where do I start?

This page

lists several tools, most of which are from the open-source RobustAndOptimalControl.jl

The main thing that is added in JSControl is the interactive model-reduction GUI

2 Likes

Does model reduction work well for systems where different states have different amounts of stiffness? The tethers are very stiff while the kite is not.

Model-reduction by means of balanced truncation considers the transfer from input to outputs, the order is reduced such that the Hankel norm of the error between the original and the reduced-order model is approximately minimized. You can thus manipulate how the model is reduced by scaling the outputs, such that important outputs that you want to represent with high fidelity are scaled up.

1 Like

See some more details in the docstring (including the “extended help” secont)

Also see if you’re interested in the theory:
Chapter 5 of “Introduction to Model Order Reduction Lecture Notes”, Henrik Sandberg

1 Like