Skip to content

Commit 029cf19

Browse files
authored
Merge pull request #7 from JuliaComputing/fbc/model-tests
Add stabilization and setpoint-tracking tests for all models
2 parents ef5a1f8 + e5c7383 commit 029cf19

2 files changed

Lines changed: 84 additions & 0 deletions

File tree

test/runtests.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,6 @@ end
3030
end
3131

3232
end
33+
34+
# Self-contained (defines its own `using`s and `@testset`), so include at top level.
35+
include("test_stabilization.jl")

test/test_stabilization.jl

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Closed-loop stabilization and setpoint-tracking tests for every DyadBot model.
2+
#
3+
# Each model is simulated through the same `DyadInterface.TransientAnalysis` path
4+
# the generated case tests use (which applies the SynchToolkit clock pass needed
5+
# by the discrete models). For every model we check that
6+
# * the solver reports success,
7+
# * the controller keeps the robot upright — the body tilt angle stays bounded
8+
# (never falls over) and settles back to ~0, and
9+
# * for the cascade models, the wheel position tracks the filtered position
10+
# reference by the end of the run (the outer loop reaches its setpoint).
11+
12+
using DyadBotComponents
13+
using DiscreteComponents # loads SynchToolkit so the clocked models get the Lustre pass
14+
using DyadInterface
15+
using ModelingToolkit
16+
using Test
17+
18+
# Simulate a model to `stop` and return (model, solution).
19+
function simulate_model(constructor; stop)
20+
model = constructor(; name = :model)
21+
result = TransientAnalysis(; model, alg = DyadInterface.ODEAlg.Auto(),
22+
start = 0.0, stop, abstol = 1e-8, reltol = 1e-8,
23+
automatic_discontinuity_detection = false)
24+
return model, DyadInterface.rebuild_sol(result)
25+
end
26+
27+
succeeded(sol) = Symbol(sol.retcode) == :Success
28+
29+
# Maximum absolute value of a signal over the whole trajectory.
30+
maxabs(sol, acc) = maximum(abs, sol[acc])
31+
32+
# Maximum absolute value of `acc` sampled over the time window [t0, t1].
33+
function maxabs_window(sol, acc, t0, t1)
34+
ts = range(t0, t1; length = 50)
35+
maximum(abs, sol(ts; idxs = acc).u)
36+
end
37+
38+
# Maximum absolute tracking error x - r sampled over the window [t0, t1].
39+
function maxabs_error_window(sol, x, r, t0, t1)
40+
ts = range(t0, t1; length = 50)
41+
maximum(abs, sol(ts; idxs = x).u .- sol(ts; idxs = r).u)
42+
end
43+
44+
# The body tilt angle must never exceed this — well below "fallen over".
45+
const THETA_BOUND = 0.2
46+
# Settled tolerances, evaluated over the final second of the run.
47+
const THETA_SETTLE = 0.02
48+
const POS_TRACK_TOL = 0.02
49+
50+
@testset "Stabilization and setpoint tracking" begin
51+
# Angle-only models: the sole setpoint is the upright pose (theta = 0). The
52+
# wheel position is intentionally uncontrolled and free to drift.
53+
@testset "$(nameof(ctor))" for ctor in (
54+
DyadBotComponents.AngleControlledDyadBot,
55+
DyadBotComponents.DiscreteAngleControlledDyadBot,
56+
)
57+
stop = 5.0
58+
m, sol = simulate_model(ctor; stop)
59+
@test succeeded(sol)
60+
@test maxabs(sol, m.plant.theta) < THETA_BOUND # never falls over
61+
@test maxabs_window(sol, m.plant.theta, stop - 1, stop) < THETA_SETTLE # settles upright
62+
end
63+
64+
# Cascade models: the inner loop holds the robot upright while the outer loop
65+
# drives the wheel position to the filtered square-wave reference.
66+
@testset "$(nameof(ctor))" for (ctor, reffun) in (
67+
(DyadBotComponents.CascadeControlledDyadBot, m -> m.firstorder1.y),
68+
(DyadBotComponents.DiscreteCascadeControlledDyadBot, m -> m.firstorder1.y),
69+
(DyadBotComponents.CascadeFFDyadBot, m -> m.pos_ref.y),
70+
(DyadBotComponents.DiscreteCascadeFFDyadBot, m -> m.pos_ref.y),
71+
)
72+
stop = 20.0
73+
m, sol = simulate_model(ctor; stop)
74+
@test succeeded(sol)
75+
@test maxabs(sol, m.plant.theta) < THETA_BOUND # inner loop keeps it upright
76+
@test maxabs_window(sol, m.plant.theta, stop - 1, stop) < THETA_SETTLE
77+
# Outer loop reaches its setpoint: position tracks the reference at the
78+
# end of the run (the reference has been constant for the last 5 s).
79+
@test maxabs_error_window(sol, m.plant.x, reffun(m), stop - 1, stop) < POS_TRACK_TOL
80+
end
81+
end

0 commit comments

Comments
 (0)