Skip to content

Commit 332fe84

Browse files
authored
Improved state priority for quaternions (#92)
* turn of quaternion normalization * eliminate Q variable * eliminate `n` * bump state priority Quat * turn of quaternion normalization * eliminate Q variable * eliminate `n` * bump state priority Quat * clean up * tweak test tol * broek test fixed * up test
1 parent e49a48c commit 332fe84

File tree

7 files changed

+85
-13
lines changed

7 files changed

+85
-13
lines changed

docs/src/examples/gyroscopic_effects.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ world = Multibody.world
2020
2121
systems = @named begin
2222
spherical = Spherical(state=true, radius=0.02, color=[1,1,0,1])
23-
body1 = BodyCylinder(r = [0.25, 0, 0], diameter = 0.05, isroot=false, quat=false)
23+
body1 = BodyCylinder(r = [0.25, 0, 0], diameter = 0.05)
2424
rot = FixedRotation(; n = [0,1,0], angle=deg2rad(45))
2525
revolute = Revolute(n = [1,0,0], radius=0.06, color=[1,0,0,1])
2626
trans = FixedTranslation(r = [-0.1, 0, 0])

src/components.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -255,7 +255,7 @@ Representing a body with 3 translational and 3 rotational degrees-of-freedom.
255255
]
256256
@variables g_0(t)[1:3] [guess = 0, description = "gravity acceleration"]
257257
@variables w_a(t)[1:3]=w_a [guess = 0,
258-
state_priority = 2,
258+
state_priority = 2+2quat*state,
259259
description = "Absolute angular velocity of frame_a resolved in frame_a",
260260
]
261261
@variables z_a(t)[1:3] [guess = 0,

src/frames.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
r_0, f, tau = collect.((r_0, f, tau))
1414
# R: Orientation object to rotate the world frame into the connector frame
1515

16-
R = NumRotationMatrix(; name, varw, state_priority=0)
16+
R = NumRotationMatrix(; name, varw, state_priority=-1)
1717

1818
ODESystem(Equation[], t, [r_0; f; tau], []; name,
1919
metadata = Dict(:orientation => R, :frame => true))

src/orientation.jl

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ end
257257
Base.:/(q::Rotations.Quaternions.Quaternion, x::Num) = Rotations.Quaternions.Quaternion(q.s / x, q.v1 / x, q.v2 / x, q.v3 / x)
258258
function from_Q(Q2, w)
259259
# Q2 = to_q(Q) # Due to different conventions
260-
q = Rotations.QuatRotation(Q2)
260+
q = Rotations.QuatRotation(Q2, false)
261261
R = RotMatrix(q)
262262
RotationMatrix(R, w)
263263
end
@@ -404,11 +404,14 @@ function get_frame(sol, frame, t)
404404
end
405405

406406
function nonunit_quaternion_equations(R, w)
407-
@variables Q(t)[1:4]=[1,0,0,0], [description="Unit quaternion with [w,i,j,k]"] # normalized
408-
@variables (t)[1:4]=[1,0,0,0], [description="Non-unit quaternion with [w,i,j,k]"] # Non-normalized
407+
@variables Q(t)[1:4]=[1,0,0,0], [state_priority=-1, description="Unit quaternion with [w,i,j,k]"] # normalized
408+
@variables (t)[1:4]=[1,0,0,0], [state_priority=1000, description="Non-unit quaternion with [w,i,j,k]"] # Non-normalized
409+
@variables Q̂d(t)[1:4]=[0,0,0,0], [state_priority=1000]
410+
# NOTE:
409411
@variables n(t)=1 c(t)=0
410412
@parameters k = 0.1
411413
= collect(Q̂)
414+
Q̂d = collect(Q̂d)
412415
Q = collect(Q)
413416
# w is used in Ω, and Ω determines D(Q̂)
414417
# This corresponds to modelica's
@@ -417,12 +420,13 @@ function nonunit_quaternion_equations(R, w)
417420
# They also have w_a = angularVelocity2(frame_a.R) even for quaternions, so w_a = angularVelocity2(Q, der(Q)), this is their link between w_a and D(Q), while ours is D(Q̂) .~ (Ω * Q̂)
418421
Ω = [0 -w[1] -w[2] -w[3]; w[1] 0 w[3] -w[2]; w[2] -w[3] 0 w[1]; w[3] w[2] -w[1] 0]
419422
# QR = from_Q(Q, angular_velocity2(Q, D.(Q)))
420-
QR = from_Q(Q, w)
423+
QR = from_Q(./ sqrt(n), w)
421424
[
422425
n ~'
423426
c ~ k * (1 - n)
424-
D.(Q̂) .~' * Q̂) ./ 2 + c *# We use Ω' which is the same as using -w to handle the fact that w coming in here is typically described frame_a rather than in frame_b, the paper is formulated with w being expressed in the rotating body frame (frame_b)
427+
D.(Q̂) .~ Q̂d
428+
Q̂d .~' * Q̂) ./ 2 + c *# We use Ω' which is the same as using -w to handle the fact that w coming in here is typically described frame_a rather than in frame_b, the paper is formulated with w being expressed in the rotating body frame (frame_b)
425429
Q .~./ sqrt(n)
426-
R ~ QR
430+
R ~ from_Q(Q, w)
427431
]
428432
end

test/runtests.jl

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1036,3 +1036,71 @@ using LinearAlgebra
10361036
end
10371037

10381038
##
1039+
1040+
using LinearAlgebra, ModelingToolkit, Multibody, JuliaSimCompiler, OrdinaryDiffEq
1041+
using Multibody.Rotations: RotXYZ
1042+
t = Multibody.t
1043+
D = Multibody.D
1044+
world = Multibody.world
1045+
1046+
@named joint = Multibody.Spherical(isroot=false, state=false, quat=false)
1047+
@named rod = FixedTranslation(; r = [1, 0, 0])
1048+
@named body = Body(; m = 1, isroot=true, quat=true)
1049+
1050+
connections = [connect(world.frame_b, joint.frame_a)
1051+
connect(joint.frame_b, rod.frame_a)
1052+
connect(rod.frame_b, body.frame_a)]
1053+
1054+
@named model = ODESystem(connections, t,
1055+
systems = [world, joint, body, rod])
1056+
irsys = IRSystem(model)
1057+
ssys = structural_simplify(irsys)
1058+
prob = ODEProblem(ssys, [
1059+
# vec(ori(rod.frame_a).R) .=> vec(RotXYZ(0,0,0));
1060+
# D.(body.Q̂) .=> 0;
1061+
1062+
], (0, 1))
1063+
sol1 = solve(prob, Rodas4())
1064+
1065+
## quat in joint
1066+
@named joint = Multibody.Spherical(isroot=true, state=true, quat=true)
1067+
@named rod = FixedTranslation(; r = [1, 0, 0])
1068+
@named body = Body(; m = 1, isroot=false, quat=false)
1069+
1070+
connections = [connect(world.frame_b, joint.frame_a)
1071+
connect(joint.frame_b, rod.frame_a)
1072+
connect(rod.frame_b, body.frame_a)]
1073+
1074+
@named model = ODESystem(connections, t,
1075+
systems = [world, joint, body, rod])
1076+
irsys = IRSystem(model)
1077+
ssys = structural_simplify(irsys)
1078+
prob = ODEProblem(ssys, [
1079+
# vec(ori(rod.frame_a).R) .=> vec(RotXYZ(0,0,0));
1080+
# D.(joint.Q̂) .=> 0;
1081+
1082+
], (0, 1))
1083+
sol2 = solve(prob, Rodas4())
1084+
1085+
## euler
1086+
@named joint = Multibody.Spherical(isroot=true, state=true, quat=false)
1087+
@named rod = FixedTranslation(; r = [1, 0, 0])
1088+
@named body = Body(; m = 1, isroot=false, quat=false)
1089+
1090+
connections = [connect(world.frame_b, joint.frame_a)
1091+
connect(joint.frame_b, rod.frame_a)
1092+
connect(rod.frame_b, body.frame_a)]
1093+
1094+
@named model = ODESystem(connections, t,
1095+
systems = [world, joint, body, rod])
1096+
irsys = IRSystem(model)
1097+
ssys = structural_simplify(irsys)
1098+
prob = ODEProblem(ssys, [
1099+
# vec(ori(rod.frame_a).R) .=> vec(RotXYZ(0,0,0));
1100+
# D.(joint.Q̂) .=> 0;
1101+
1102+
], (0, 1))
1103+
sol3 = solve(prob, Rodas4())
1104+
1105+
@test sol1(0:0.1:1, idxs=collect(body.r_0)) sol2(0:0.1:1, idxs=collect(body.r_0)) atol=1e-5
1106+
@test sol1(0:0.1:1, idxs=collect(body.r_0)) sol3(0:0.1:1, idxs=collect(body.r_0)) atol=1e-3

test/test_orientation_getters.jl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ arm_r = sol(1, idxs=collect(model.upper_arm.r))
7171

7272
# The lower arm is finally having an extent along the $y$-axis. At the final time when the pendulum motion has been fully damped, we see that the second frame of this body ends up with an $y$-coordinate of `-0.6`:
7373
t1 = get_trans(sol, model.lower_arm.frame_b, 12)
74-
@test t1 [-0.009040487302666853, -0.59999996727278, 0.599931920189277]
74+
@test t1 [-0.009040487302666853, -0.59999996727278, 0.599931920189277] rtol=1e-6
7575

7676

7777
# If we rotate the vector of extent of the lower arm to the world frame, we indeed see that the only coordinate that is nonzero is the $y$ coordinate:

test/test_quaternions.jl

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ end
184184
@test norm(mapslices(norm, Q, dims=1) .- 1) < 1e-2
185185

186186
@test get_R(sol, joint.frame_b, 0pi) I
187-
@test_broken get_R(sol, joint.frame_b, 1pi) diagm([1, -1, -1]) atol=1e-3
187+
@test get_R(sol, joint.frame_b, 1pi) diagm([1, -1, -1]) atol=1e-3
188188
@test get_R(sol, joint.frame_b, 2pi) I atol=1e-3
189189

190190
Matrix(sol(ts, idxs = [joint.w_rel_b...]))
@@ -301,8 +301,8 @@ using Multibody.Rotations: params
301301
collect(body.body.w_a .=> 0);
302302
collect(body.body.v_0 .=> 0);
303303
# collect(body.body.phi .=> deg2rad(10));
304-
collect(body.body.Q) .=> params(QuatRotation(RotXYZ(deg2rad.((10,10,10))...)));
305-
collect(body.body.Q̂) .=> params(QuatRotation(RotXYZ(deg2rad.((10,10,10))...)));
304+
collect(body.body.Q) .=> params(Quat(RotXYZ(deg2rad.((10,10,10))...)));
305+
collect(body.body.Q̂) .=> params(Quat(RotXYZ(deg2rad.((10,10,10))...)));
306306
], (0, 10))
307307

308308
# @test_skip begin # The modelica example uses angles_fixed = true, which causes the body component to run special code for variable initialization. This is not yet supported by MTK

0 commit comments

Comments
 (0)