Skip to content

Commit 3bf6cf6

Browse files
authored
make it possible to change world.n (#166)
* make it possible to change `world.n` * add test file * tweak tutorial * fix some instances of double worlds
1 parent 8e70a4f commit 3bf6cf6

File tree

5 files changed

+104
-20
lines changed

5 files changed

+104
-20
lines changed

docs/src/examples/pendulum.md

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -326,11 +326,18 @@ We start by putting the model together and control it in open loop using a simpl
326326
import ModelingToolkitStandardLibrary.Mechanical.TranslationalModelica
327327
import ModelingToolkitStandardLibrary.Blocks
328328
using Plots
329-
W(args...; kwargs...) = Multibody.world
330329
gray = [0.5, 0.5, 0.5, 1]
331330
@mtkmodel Cartpole begin
331+
@structural_parameters begin
332+
use_world = false
333+
end
332334
@components begin
333-
world = W()
335+
if use_world
336+
fixed = World()
337+
else
338+
# In case we wrap this model in an outer model below, we place the world there instead
339+
fixed = Fixed()
340+
end
334341
cart = BodyShape(m = 1, r = [0.2, 0, 0], color=[0.2, 0.2, 0.2, 1], shape="box")
335342
mounting_point = FixedTranslation(r = [0.1, 0, 0])
336343
prismatic = Prismatic(n = [1, 0, 0], axisflange = true, color=gray, state_priority=100)
@@ -347,7 +354,7 @@ gray = [0.5, 0.5, 0.5, 1]
347354
w(t)
348355
end
349356
@equations begin
350-
connect(world.frame_b, prismatic.frame_a)
357+
connect(fixed.frame_b, prismatic.frame_a)
351358
connect(prismatic.frame_b, cart.frame_a, mounting_point.frame_a)
352359
connect(mounting_point.frame_b, revolute.frame_a)
353360
connect(revolute.frame_b, pendulum.frame_a)
@@ -363,7 +370,7 @@ gray = [0.5, 0.5, 0.5, 1]
363370
end
364371
@mtkmodel CartWithInput begin
365372
@components begin
366-
world = W()
373+
world = World()
367374
cartpole = Cartpole()
368375
input = Blocks.Cosine(frequency=1, amplitude=1)
369376
end
@@ -388,14 +395,14 @@ nothing # hide
388395

389396
### Adding feedback
390397

391-
We will attempt to stabilize the pendulum in the upright position by using feedback control. To design the contorller, we linearize the model in the upward equilibrium position and design an infinite-horizon LQR controller using ControlSystems.jl. We then connect the controller to the motor on the cart. See also [RobustAndOptimalControl.jl: Control design for a pendulum on a cart](https://juliacontrol.github.io/RobustAndOptimalControl.jl/dev/cartpole/) for a similar example with more detail on the control design.
398+
We will attempt to stabilize the pendulum in the upright position by using feedback control. To design the controller, we linearize the model in the upward equilibrium position and design an infinite-horizon LQR controller using ControlSystems.jl. We then connect the controller to the motor on the cart. See also [RobustAndOptimalControl.jl: Control design for a pendulum on a cart](https://juliacontrol.github.io/RobustAndOptimalControl.jl/dev/cartpole/) for a similar example with more detail on the control design.
392399

393400
### Linearization
394401
We start by linearizing the model in the upward equilibrium position using the function `ModelingToolkit.linearize`.
395402
```@example pendulum
396403
import ModelingToolkit: D_nounits as D
397404
using LinearAlgebra
398-
@named cp = Cartpole()
405+
@named cp = Cartpole(use_world = true)
399406
cp = complete(cp)
400407
inputs = [cp.u] # Input to the linearized system
401408
outputs = [cp.x, cp.phi, cp.v, cp.w] # These are the outputs of the linearized system
@@ -437,7 +444,7 @@ LQGSystem(args...; kwargs...) = ODESystem(observer_controller(lqg); kwargs...)
437444
438445
@mtkmodel CartWithFeedback begin
439446
@components begin
440-
world = W()
447+
world = World()
441448
cartpole = Cartpole()
442449
reference = Blocks.Step(start_time = 5, height=0.5)
443450
control_saturation = Blocks.Limiter(y_max = 10) # To limit the control signal magnitude
@@ -479,7 +486,7 @@ Below, we add also an energy-based swing-up controller. For more details this ki
479486
```@example pendulum
480487
"Compute total energy, kinetic + potential, for a body rotating around the z-axis of the world"
481488
function energy(body, w)
482-
g = world.g
489+
g = GlobalScope(world.g_inner)
483490
m = body.m
484491
d2 = body.r_cm[1]^2 + body.r_cm[2]^2 # Squared distance from
485492
I = body.I_33 + m*d2 # Parallel axis theorem
@@ -491,7 +498,7 @@ normalize_angle(x::Number) = mod(x+3.1415, 2pi)-3.1415
491498
492499
@mtkmodel CartWithSwingup begin
493500
@components begin
494-
world = W()
501+
world = World()
495502
cartpole = Cartpole()
496503
L = Blocks.MatrixGain(K = Lmat) # Here we use the LQR controller instead
497504
control_saturation = Blocks.Limiter(y_max = 12) # To limit the control signal magnitude

docs/src/examples/suspension.md

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ mirror = false
101101
dir = mirror ? -1 : 1
102102
end
103103
@components begin
104-
world = W()
104+
fixed = Fixed()
105105
chassis_frame = Frame()
106106
suspension = QuarterCarSuspension(; spring=true, mirror, rod_radius)
107107
@@ -115,7 +115,7 @@ mirror = false
115115
wheel_position.s_ref.u ~ amplitude*(sin(2pi*freq*t)) # Displacement of wheel
116116
connect(wheel_position.flange, wheel_prismatic.axis)
117117
118-
connect(world.frame_b, actuation_position.frame_a)
118+
connect(fixed.frame_b, actuation_position.frame_a)
119119
connect(actuation_position.frame_b, wheel_prismatic.frame_a)
120120
connect(wheel_prismatic.frame_b, actuation_rod.frame_a,)
121121
connect(actuation_rod.frame_b, suspension.r123.frame_ib)
@@ -200,7 +200,7 @@ In the example below, we extend the previous example to a half-car model with tw
200200
rod_radius = 0.02
201201
end
202202
@components begin
203-
world = W()
203+
world = World()
204204
mass = BodyShape(m=ms, r = [0,0,-wheel_base], radius=0.1, color=[0.4, 0.4, 0.4, 0.3])
205205
excited_suspension_r = SuspensionWithExcitation(; suspension.spring=true, mirror=false, rod_radius,
206206
actuation_position.r = [0, 0, (CD+wheel_base/2)],
@@ -329,7 +329,7 @@ end
329329
rod_radius = 0.02
330330
end
331331
@components begin
332-
world = W()
332+
world = World()
333333
mass = Body(m=ms, r_cm = 0.5DA*normalize([0, 0.2, 0.2*sin(t5)]))
334334
excited_suspension = ExcitedWheelAssembly(; rod_radius)
335335
body_upright = Prismatic(n = [0, 1, 0], render = false, state_priority=1000)
@@ -375,7 +375,7 @@ nothing # hide
375375
rod_radius = 0.02
376376
end
377377
@components begin
378-
world = W()
378+
world = World()
379379
mass = BodyShape(m=ms, r = [0,0,-wheel_base], radius=0.1, color=[0.4, 0.4, 0.4, 0.3])
380380
excited_suspension_r = ExcitedWheelAssembly(; mirror=false, rod_radius)
381381
excited_suspension_l = ExcitedWheelAssembly(; mirror=true, rod_radius)
@@ -451,9 +451,9 @@ transparent_gray = [0.4, 0.4, 0.4, 0.3]
451451
rod_radius = 0.02
452452
end
453453
@components begin
454-
world = W()
454+
world = World()
455455
front_axle = BodyShape(m=ms/4, r = [0,0,-wheel_base], radius=0.1, color=transparent_gray)
456-
back_front = BodyShape(m=ms/2, r = [2, 0, 0], radius=0.2, color=transparent_gray, isroot=true, state_priority=Inf, quat=false)
456+
back_front = BodyShape(m=ms/2, r = [-2, 0, 0], radius=0.2, color=transparent_gray, isroot=true, state_priority=Inf, quat=false)
457457
back_axle = BodyShape(m=ms/4, r = [0,0,-wheel_base], radius=0.1, color=transparent_gray)
458458
459459
excited_suspension_fr = ExcitedWheelAssembly(; mirror=false, rod_radius, freq = 10)
@@ -508,9 +508,13 @@ defs = [
508508
model.excited_suspension_fl.suspension.cs => 5*4000
509509
model.excited_suspension_fl.suspension.r2.phi => -0.6031
510510
511+
model.excited_suspension_fr.wheel.frame_a.render => true # To visualize one wheel rolling
512+
model.excited_suspension_fr.wheel.frame_a.radius => 0.01
513+
model.excited_suspension_fr.wheel.frame_a.length => 0.3
514+
511515
model.ms => 1500
512516
513-
model.back_front.body.r_0[1] => -2.0
517+
model.back_front.body.r_0[1] => 0
514518
model.back_front.body.r_0[2] => 0.193
515519
model.back_front.body.r_0[3] => 0.0
516520
model.back_front.body.v_0[1] => 1

src/components.jl

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,18 +47,37 @@ end
4747
g0 = g
4848
mu0 = mu
4949
@named frame_b = Frame()
50-
@parameters n[1:3]=n0 [description = "gravity direction of world"]
50+
51+
@parameters n[1:3] = n0 [description = "gravity direction"]
5152
@parameters g=g0 [description = "gravitational acceleration of world"]
5253
@parameters mu=mu0 [description = "Gravity field constant [m³/s²] (default = field constant of earth)"]
5354
@parameters render=render
5455
@parameters point_gravity = point_gravity
56+
57+
@variables n_inner(t)[1:3]
58+
@variables g_inner(t)
59+
@variables mu_inner(t)
60+
@variables render_inner(t)
61+
@variables point_gravity_inner(t)
62+
5563
n = Symbolics.scalarize(n)
64+
n_inner = GlobalScope.(Symbolics.scalarize(n_inner))
65+
g_inner = GlobalScope(g_inner)
66+
mu_inner = GlobalScope(mu_inner)
67+
render_inner = GlobalScope(render_inner)
68+
point_gravity_inner = GlobalScope(point_gravity_inner)
69+
5670
O = ori(frame_b)
5771
eqs = Equation[
5872
collect(frame_b.r_0) .~ 0;
5973
O ~ nullrotation()
74+
n_inner .~ n
75+
g_inner ~ g
76+
mu_inner ~ mu
77+
render_inner ~ render
78+
point_gravity_inner ~ point_gravity
6079
]
61-
ODESystem(eqs, t, [], [n; g; mu; point_gravity; render]; name, systems = [frame_b])#, defaults=[n => n0; g => g0; mu => mu0])
80+
ODESystem(eqs, t, [n_inner; g_inner; mu_inner; render_inner; point_gravity_inner], [n; g; mu; point_gravity; render]; name, systems = [frame_b])#, defaults=[n => n0; g => g0; mu => mu0])
6281
end
6382

6483
"""
@@ -68,7 +87,7 @@ const world = World(; name = :world)
6887

6988
"Compute the gravity acceleration, resolved in world frame"
7089
function gravity_acceleration(r)
71-
inner_gravity(GlobalScope(world.point_gravity), GlobalScope(world.mu), GlobalScope(world.g), GlobalScope.(collect(world.n)), collect(r))
90+
inner_gravity(GlobalScope(world.point_gravity), GlobalScope(world.mu), GlobalScope(world.g_inner), GlobalScope.(collect(world.n_inner)), collect(r))
7291
end
7392

7493
function inner_gravity(point_gravity, mu, g, n, r)

test/runtests.jl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ ssys = structural_simplify(model)
2626

2727
@test length(unknowns(ssys)) == 0 # This example is completely rigid and should simplify down to zero state variables
2828

29+
@testset "world" begin
30+
@info "Testing world"
31+
include("test_world.jl")
32+
end
33+
2934
@testset "urdf" begin
3035
@info "Testing urdf"
3136
include("test_urdf.jl")

test/test_world.jl

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using Multibody, ModelingToolkit, JuliaSimCompiler, Test
2+
@mtkmodel FallingBody begin
3+
@components begin
4+
my_world = World(g = 1, n = [0, 1, 0])
5+
body = Body(isroot=true)
6+
end
7+
end
8+
9+
@named model = FallingBody()
10+
model = complete(model)
11+
ssys = structural_simplify(IRSystem(model))
12+
prob = ODEProblem(ssys, [], (0, 1))
13+
sol = solve(prob, Tsit5())
14+
15+
tv = 0:0.1:1
16+
@test iszero(sol(tv, idxs=model.body.r_0[1]))
17+
@test sol(tv, idxs=model.body.r_0[2]) tv.^2 ./ 2 atol=1e-6
18+
@test iszero(sol(tv, idxs=model.body.r_0[3]))
19+
20+
21+
# Change g
22+
prob = ODEProblem(ssys, [model.my_world.g .=> 2], (0, 1))
23+
sol = solve(prob, Tsit5())
24+
@test iszero(sol(tv, idxs=model.body.r_0[1]))
25+
@test sol(tv, idxs=model.body.r_0[2]) 2*tv.^2 ./ 2 atol=1e-6
26+
@test iszero(sol(tv, idxs=model.body.r_0[3]))
27+
28+
# Change n
29+
prob = ODEProblem(ssys, collect(model.my_world.n) .=> [1, 0, 0], (0, 1))
30+
sol = solve(prob, Tsit5())
31+
@test sol(tv, idxs=model.body.r_0[1]) tv.^2 ./ 2 atol=1e-6
32+
@test iszero(sol(tv, idxs=model.body.r_0[2]))
33+
@test iszero(sol(tv, idxs=model.body.r_0[3]))
34+
35+
36+
## World in more than one place
37+
# This should result in too many equations
38+
@mtkmodel FallingBodyOuter begin
39+
@components begin
40+
inner_model = FallingBody()
41+
my_world_outer = World(g = 2, n = [0, 1, 0])
42+
body = Body(isroot=true)
43+
end
44+
end
45+
46+
@named model = FallingBodyOuter()
47+
model = complete(model)
48+
@test_throws ModelingToolkit.ExtraEquationsSystemException structural_simplify(IRSystem(model))
49+

0 commit comments

Comments
 (0)