Skip to content

Commit 29bcb00

Browse files
authored
Merge pull request #11 from JuliaComputing/prismatic
Prismatic joint
2 parents f4a489d + d02b9f6 commit 29bcb00

File tree

4 files changed

+101
-5
lines changed

4 files changed

+101
-5
lines changed

docs/src/examples/pendulum.md

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Pendulum---The "Hello World of multi-body dynamics"
1+
# Pendulum--The "Hello World of multi-body dynamics"
22
This beginners tutorial will model a pendulum pivoted around the origin in the world frame. The world frame is a constant that lives inside the Multibody module, all multibody models are "grounded" in the same world. To start, we load the required packages
33
```@example pendulum
44
using ModelingToolkit
@@ -72,12 +72,38 @@ connections = [connect(world.frame_b, joint.frame_a)
7272
@named model = ODESystem(connections, t, systems = [world, joint, body, damper])
7373
ssys = structural_simplify(model, allow_parameter = false)
7474
75-
7675
prob = ODEProblem(ssys, [damper.phi_rel => 1, D(joint.phi) => 0, D(D(joint.phi)) => 0],
7776
(0, 30))
7877
78+
sol = solve(prob, Rodas4())
79+
plot(sol, idxs = joint.phi)
80+
```
81+
This time we see that the pendulum loses energy and eventually comes to rest at the stable equilibrium point ``\pi / 2``.
82+
83+
## A linear pendulum?
84+
When we think of a pendulum, we typically think of a rotary pendulum that is rotating around a pivot point like in the examples above.
85+
A mass suspended in a spring can be though of as a linear pendulum (often referred to as a harmonic oscillator rather than a pendulum), and we show here how we can construct a model of such a device.
86+
87+
```@example pendulum
88+
import ModelingToolkitStandardLibrary.Mechanical.TranslationalModelica as T
89+
90+
@named damper = T.Damper(0.5)
91+
@named spring = T.Spring(1)
92+
@named joint = Prismatic(n = [0, 1, 0], isroot = true, useAxisFlange = true)
93+
94+
connections = [connect(world.frame_b, joint.frame_a)
95+
connect(damper.flange_b, spring.flange_b, joint.axis)
96+
connect(joint.support, damper.flange_a, spring.flange_a)
97+
connect(body.frame_a, joint.frame_b)]
98+
99+
@named model = ODESystem(connections, t, systems = [world, joint, body, damper, spring])
100+
ssys = structural_simplify(model, allow_parameter = false)
101+
102+
prob = ODEProblem(ssys, [damper.s_rel => 1, D(joint.s) => 0, D(D(joint.s)) => 0],
103+
(0, 30))
79104
80105
sol = solve(prob, Rodas4())
81-
plot(sol, idxs = collect(joint.phi))
106+
plot(sol, idxs = joint.s)
82107
```
83-
This time we see that the pendulum loses energy and eventually comes to rest at the stable equilibrium point ``\pi / 2``.
108+
109+
As is hopefully evident from the little code snippet above, this linear pendulum model has a lot in common with the rotary pendulum. In this example, we connected both the spring and a damper to the same axis flange in the joint. This time, the components came from the `TranslationalModelica` submodule of ModelingToolkitStandardLibrary rather than the `Rotational` submodule. Also here do we pass `useAxisFlange` when we create the joint to make sure that it is equipped with the flanges `support` and `axis` needed to connect the translational components.

src/Multibody.jl

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ module Multibody
33
using LinearAlgebra
44
using ModelingToolkit
55
import ModelingToolkitStandardLibrary.Mechanical.Rotational
6+
import ModelingToolkitStandardLibrary.Mechanical.TranslationalModelica as Translational
67

78
const t = let
89
(@variables t)[1]
@@ -31,7 +32,7 @@ include("frames.jl")
3132

3233
include("interfaces.jl")
3334

34-
export World, world, FixedTranslation, Revolute, Body
35+
export World, world, FixedTranslation, Revolute, Prismatic, Body
3536
include("components.jl")
3637

3738
export Spring

src/components.jl

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,49 @@ function Revolute(; name, phi0 = 0, w0 = 0, n = Float64[0, 0, 1], useAxisFlange
130130
end
131131
end
132132

133+
function Prismatic(; name, n = Float64[0, 0, 1], useAxisFlange = false,
134+
isroot = false)
135+
norm(n) 1 || error("Axis of motion must be a unit vector")
136+
@named frame_a = Frame()
137+
@named frame_b = Frame()
138+
@parameters n[1:3]=n [description = "axis of motion"]
139+
n = collect(n)
140+
141+
@variables s(t)=0 [state_priority = 10, description = "Relative distance between frame_a and frame_b"]
142+
@variables v(t)=0 [state_priority = 10, description = "Relative velocity between frame_a and frame_b"]
143+
@variables a(t)=0 [state_priority = 10, description = "Relative acceleration between frame_a and frame_b"]
144+
@variables f(t)=0 [connect = Flow, description = "Actuation force in direction of joint axis"]
145+
146+
eqs = [
147+
v ~ D(s)
148+
a ~ D(v)
149+
150+
# relationships between kinematic quantities of frame_a and of frame_b
151+
collect(frame_b.r_0) .~ collect(frame_a.r_0) + resolve1(ori(frame_a), n*s)
152+
ori(frame_b) ~ ori(frame_a)
153+
154+
# Force and torque balance
155+
zeros(3) .~ collect(frame_a.f + frame_b.f)
156+
zeros(3) .~ collect(frame_a.tau + frame_b.tau + cross(n*s, frame_b.f))
157+
158+
# d'Alemberts principle
159+
f .~ -n'collect(frame_b.f)
160+
]
161+
162+
if useAxisFlange
163+
@named fixed = Translational.Fixed()
164+
@named axis = Translational.Flange()
165+
@named support = Translational.Flange()
166+
push!(eqs, connect(fixed.flange, support))
167+
push!(eqs, axis.s ~ s)
168+
push!(eqs, axis.f ~ f)
169+
compose(ODESystem(eqs, t; name), frame_a, frame_b, axis, support, fixed)
170+
else
171+
push!(eqs, f ~ 0)
172+
compose(ODESystem(eqs, t; name), frame_a, frame_b)
173+
end
174+
end
175+
133176
"""
134177
Body(; name, m = 1, r_cm, I = collect(0.001 * LinearAlgebra.I(3)), isroot = false, phi0 = zeros(3), phid0 = zeros(3))
135178

test/runtests.jl

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,3 +151,29 @@ sol = solve(prob, Rodas4())
151151
@test maximum(sol[rev.phi]) < π
152152
@test sol[rev.phi][end]π / 2 rtol=0.01 # pendulum settles at 90 degrees stable equilibrium
153153
isinteractive() && plot(sol, idxs = collect(rev.phi))
154+
155+
156+
# ==============================================================================
157+
## Linear mass-spring-damper ===================================================
158+
# ==============================================================================
159+
import ModelingToolkitStandardLibrary.Mechanical.TranslationalModelica as T
160+
161+
@named damper = T.Damper(0.5)
162+
@named spring = T.Spring(1)
163+
@named joint = Prismatic(n = [0, 1, 0], isroot = true, useAxisFlange = true)
164+
165+
connections = [connect(world.frame_b, joint.frame_a)
166+
connect(damper.flange_b, spring.flange_b, joint.axis)
167+
connect(joint.support, damper.flange_a, spring.flange_a)
168+
connect(body.frame_a, joint.frame_b)]
169+
170+
@named model = ODESystem(connections, t, systems = [world, joint, body, damper, spring])
171+
ssys = structural_simplify(model, allow_parameter = false)
172+
173+
prob = ODEProblem(ssys, [damper.s_rel => 1, D(joint.s) => 0, D(D(joint.s)) => 0],
174+
(0, 30))
175+
176+
sol = solve(prob, Rodas4())
177+
@test SciMLBase.successful_retcode(sol)
178+
@test sol[joint.s][end]9.81 rtol=0.01 # gravitational acceleration since spring stiffness is 1
179+
isinteractive() && plot(sol, idxs = joint.s)

0 commit comments

Comments
 (0)