tests.GyroscopicEffectsTest
Gyroscopic Effects Test
Validates Rotor1D's gyroscopic reaction against an explicit 3D model of the same rotor.
Both branches are a spherically-supported arm carrying a rotor at 45 degrees to the arm, released with the rotor spinning at 10 rad/s:
Branch A builds the rotor explicitly: a
Revolutejoint lets aBodyCylinderspin relative to the arm.Branch B attaches the very same
BodyCylinderrigidly and adds aRotor1Don the same frame to supply the spin momentum.
The two must move identically. That equivalence is exactly what the -e*dot(nJ, z_a) term in Rotor1D buys: it hands the rotor's axial-inertia demand back to the flange path, so the momenta add up without double counting the axial inertia that the rigid BodyCylinder already contributes.
Usage
MultibodyComponents.tests.GyroscopicEffectsTest(d_rotor=0.1, l_rotor=0.2, density=7700, m_rotor=density * 3.141592653589793 * l_rotor * (d_rotor / 2) ^ 2, J_rotor=m_rotor * (d_rotor / 2) ^ 2 / 2)
Parameters:
| Name | Description | Units | Default value |
|---|---|---|---|
exact | – | true | |
d_rotor | m | 0.1 | |
l_rotor | m | 0.2 | |
density | – | 7700 |
Behavior
Source
"""
# Gyroscopic Effects Test
Validates `Rotor1D`'s gyroscopic reaction against an explicit 3D model of the
same rotor.
Both branches are a spherically-supported arm carrying a rotor at 45 degrees to
the arm, released with the rotor spinning at 10 rad/s:
- Branch A builds the rotor explicitly: a `Revolute` joint lets a `BodyCylinder`
spin relative to the arm.
- Branch B attaches the very same `BodyCylinder` *rigidly* and adds a `Rotor1D`
on the same frame to supply the spin momentum.
The two must move identically. That equivalence is exactly what the
`-e*dot(nJ, z_a)` term in `Rotor1D` buys: it hands the rotor's axial-inertia
demand back to the flange path, so the momenta add up without double counting
the axial inertia that the rigid `BodyCylinder` already contributes.
"""
example component GyroscopicEffectsTest
world = MultibodyComponents.World() {
"Dyad": {"placement": {"diagram": {"x1": 20, "x2": 120, "y1": 120, "y2": 220}}}
}
# Branch A: explicit 3D rotor on a revolute joint
spherical1 = MultibodyComponents.Spherical() {
"Dyad": {"placement": {"diagram": {"x1": 145, "x2": 245, "y1": 120, "y2": 220}}}
}
arm1 = MultibodyComponents.BodyCylinder(r = [0.25, 0, 0], diameter = 0.05) {
"Dyad": {"placement": {"diagram": {"x1": 290, "x2": 390, "y1": 120, "y2": 220}}}
}
rot1 = MultibodyComponents.FixedRotation(n = [0, 1, 0], angle = 45) {
"Dyad": {"placement": {"diagram": {"x1": 410, "x2": 510, "y1": 120, "y2": 220}}}
}
revolute = MultibodyComponents.Revolute(n = [1, 0, 0], phi(initial = 0), w(initial = 10)) {
"Dyad": {"placement": {"diagram": {"x1": 540, "x2": 640, "y1": 120, "y2": 220}}}
}
rotorbody1 = MultibodyComponents.BodyCylinder(r = [l_rotor, 0, 0], diameter = d_rotor) {
"Dyad": {"placement": {"diagram": {"x1": 660, "x2": 760, "y1": 120, "y2": 220}}}
}
# Branch B: rigid rotor body plus Rotor1D for the spin momentum
anchor = MultibodyComponents.Fixed(r = [0, -0.6, 0]) {
"Dyad": {
"placement": {"diagram": {"x1": -10, "x2": 90, "y1": 490, "y2": 590, "rot": 270}}
}
}
spherical2 = MultibodyComponents.Spherical() {
"Dyad": {
"placement": {"diagram": {"x1": -10, "x2": 90, "y1": 380, "y2": 480, "rot": 270}}
}
}
arm2 = MultibodyComponents.BodyCylinder(r = [0.25, 0, 0], diameter = 0.05) {
"Dyad": {"placement": {"diagram": {"x1": 30, "x2": 130, "y1": 255, "y2": 355}}}
}
rot2 = MultibodyComponents.FixedRotation(n = [0, 1, 0], angle = 45) {
"Dyad": {"placement": {"diagram": {"x1": 150, "x2": 250, "y1": 255, "y2": 355}}}
}
rotorbody2 = MultibodyComponents.BodyCylinder(r = [l_rotor, 0, 0], diameter = d_rotor) {
"Dyad": {"placement": {"diagram": {"x1": 270, "x2": 370, "y1": 255, "y2": 355}}}
}
rotor1D = MultibodyComponents.Rotor1D(J = J_rotor, n = [1, 0, 0], exact = exact, phi(initial = 0), w(initial = 10)) {
"Dyad": {"placement": {"diagram": {"x1": 395, "x2": 495, "y1": 240, "y2": 340}}}
}
structural parameter exact::Boolean = true
parameter d_rotor::Length = 0.1
parameter l_rotor::Length = 0.2
parameter density::Real = 7700
# Must match BodyCylinder's own axial inertia for the two branches to agree:
# m = density*pi*length*radius^2, I_axial = m*radius^2/2. A float literal is
# used for pi because a symbolic pi cannot be evaluated in a parameter binding.
final parameter m_rotor::Mass = density * 3.141592653589793 * l_rotor * (d_rotor / 2) ^ 2
final parameter J_rotor::Inertia = m_rotor * (d_rotor / 2) ^ 2 / 2
relations
# Branch A
connect(world.frame_b, spherical1.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
connect(spherical1.frame_b, arm1.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
connect(arm1.frame_b, rot1.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
connect(rot1.frame_b, revolute.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
connect(revolute.frame_b, rotorbody1.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
# Branch B. `anchor` is its own ground -- it must NOT also be connected to
# `world.frame_b`, or the orientation would be rooted twice.
connect(anchor.frame_b, spherical2.frame_a) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 40.00000000000001, "y": 540}], "E": 2}],
"path": {}
}
}
connect(spherical2.frame_b, arm2.frame_a) {
"Dyad": {
"edges": [
{"S": 1, "M": [{"x": 39.99999999999999, "y": 360}, {"x": 40, "y": 360}], "E": 2}
],
"path": {}
}
}
connect(arm2.frame_b, rot2.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
connect(rot2.frame_b, rotorbody2.frame_a, rotor1D.frame_a) {
"Dyad": {
"edges": [
{"S": 1, "M": [], "E": -1},
{"S": -1, "M": [], "E": 2},
{"S": -1, "M": [{"x": 260, "y": 395}, {"x": 445, "y": 395}], "E": 3}
],
"junctions": [{"x": 260, "y": 305}],
"path": {}
}
}
endFlattened Source
"""
# Gyroscopic Effects Test
Validates `Rotor1D`'s gyroscopic reaction against an explicit 3D model of the
same rotor.
Both branches are a spherically-supported arm carrying a rotor at 45 degrees to
the arm, released with the rotor spinning at 10 rad/s:
- Branch A builds the rotor explicitly: a `Revolute` joint lets a `BodyCylinder`
spin relative to the arm.
- Branch B attaches the very same `BodyCylinder` *rigidly* and adds a `Rotor1D`
on the same frame to supply the spin momentum.
The two must move identically. That equivalence is exactly what the
`-e*dot(nJ, z_a)` term in `Rotor1D` buys: it hands the rotor's axial-inertia
demand back to the flange path, so the momenta add up without double counting
the axial inertia that the rigid `BodyCylinder` already contributes.
"""
example component GyroscopicEffectsTest
world = MultibodyComponents.World() {
"Dyad": {"placement": {"diagram": {"x1": 20, "x2": 120, "y1": 120, "y2": 220}}}
}
# Branch A: explicit 3D rotor on a revolute joint
spherical1 = MultibodyComponents.Spherical() {
"Dyad": {"placement": {"diagram": {"x1": 145, "x2": 245, "y1": 120, "y2": 220}}}
}
arm1 = MultibodyComponents.BodyCylinder(r = [0.25, 0, 0], diameter = 0.05) {
"Dyad": {"placement": {"diagram": {"x1": 290, "x2": 390, "y1": 120, "y2": 220}}}
}
rot1 = MultibodyComponents.FixedRotation(n = [0, 1, 0], angle = 45) {
"Dyad": {"placement": {"diagram": {"x1": 410, "x2": 510, "y1": 120, "y2": 220}}}
}
revolute = MultibodyComponents.Revolute(n = [1, 0, 0], phi(initial = 0), w(initial = 10)) {
"Dyad": {"placement": {"diagram": {"x1": 540, "x2": 640, "y1": 120, "y2": 220}}}
}
rotorbody1 = MultibodyComponents.BodyCylinder(r = [l_rotor, 0, 0], diameter = d_rotor) {
"Dyad": {"placement": {"diagram": {"x1": 660, "x2": 760, "y1": 120, "y2": 220}}}
}
# Branch B: rigid rotor body plus Rotor1D for the spin momentum
anchor = MultibodyComponents.Fixed(r = [0, -0.6, 0]) {
"Dyad": {
"placement": {"diagram": {"x1": -10, "x2": 90, "y1": 490, "y2": 590, "rot": 270}}
}
}
spherical2 = MultibodyComponents.Spherical() {
"Dyad": {
"placement": {"diagram": {"x1": -10, "x2": 90, "y1": 380, "y2": 480, "rot": 270}}
}
}
arm2 = MultibodyComponents.BodyCylinder(r = [0.25, 0, 0], diameter = 0.05) {
"Dyad": {"placement": {"diagram": {"x1": 30, "x2": 130, "y1": 255, "y2": 355}}}
}
rot2 = MultibodyComponents.FixedRotation(n = [0, 1, 0], angle = 45) {
"Dyad": {"placement": {"diagram": {"x1": 150, "x2": 250, "y1": 255, "y2": 355}}}
}
rotorbody2 = MultibodyComponents.BodyCylinder(r = [l_rotor, 0, 0], diameter = d_rotor) {
"Dyad": {"placement": {"diagram": {"x1": 270, "x2": 370, "y1": 255, "y2": 355}}}
}
rotor1D = MultibodyComponents.Rotor1D(J = J_rotor, n = [1, 0, 0], exact = exact, phi(initial = 0), w(initial = 10)) {
"Dyad": {"placement": {"diagram": {"x1": 395, "x2": 495, "y1": 240, "y2": 340}}}
}
structural parameter exact::Boolean = true
parameter d_rotor::Length = 0.1
parameter l_rotor::Length = 0.2
parameter density::Real = 7700
# Must match BodyCylinder's own axial inertia for the two branches to agree:
# m = density*pi*length*radius^2, I_axial = m*radius^2/2. A float literal is
# used for pi because a symbolic pi cannot be evaluated in a parameter binding.
final parameter m_rotor::Mass = density * 3.141592653589793 * l_rotor * (d_rotor / 2) ^ 2
final parameter J_rotor::Inertia = m_rotor * (d_rotor / 2) ^ 2 / 2
relations
# Branch A
connect(world.frame_b, spherical1.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
connect(spherical1.frame_b, arm1.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
connect(arm1.frame_b, rot1.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
connect(rot1.frame_b, revolute.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
connect(revolute.frame_b, rotorbody1.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
# Branch B. `anchor` is its own ground -- it must NOT also be connected to
# `world.frame_b`, or the orientation would be rooted twice.
connect(anchor.frame_b, spherical2.frame_a) {
"Dyad": {
"edges": [{"S": 1, "M": [{"x": 40.00000000000001, "y": 540}], "E": 2}],
"path": {}
}
}
connect(spherical2.frame_b, arm2.frame_a) {
"Dyad": {
"edges": [
{"S": 1, "M": [{"x": 39.99999999999999, "y": 360}, {"x": 40, "y": 360}], "E": 2}
],
"path": {}
}
}
connect(arm2.frame_b, rot2.frame_a) {"Dyad": {"edges": [{"S": 1, "M": [], "E": 2}], "path": {}}}
connect(rot2.frame_b, rotorbody2.frame_a, rotor1D.frame_a) {
"Dyad": {
"edges": [
{"S": 1, "M": [], "E": -1},
{"S": -1, "M": [], "E": 2},
{"S": -1, "M": [{"x": 260, "y": 395}, {"x": 445, "y": 395}], "E": 3}
],
"junctions": [{"x": 260, "y": 305}],
"path": {}
}
}
metadata {}
endTest Cases
No test cases defined.
Related
Examples
Experiments
Analyses