Skip to content
LIBRARY
tests.UniversalConstraintTest.md

tests.UniversalConstraintTest

Universal Constraint (closed loop with a spring)

A body is connected to the world by the implicit UniversalConstraint at one end and pulled back to a fixed world point by a Spring at the other end, forming a closed loop.

The constraint locks all three translations (the origins of the two frames coincide at the world origin) and allows two rotational degrees of freedom: about n_a = [0, 0, 1] (resolved in frame_a) and about n_b = [1, 0, 0] (resolved in frame_b). The two axes are kept perpendicular by the joint. The body swings under gravity (along -y, acting on the offset center of mass) and the spring load.

The free body carries its own (absolute) Euler body coordinates as states, so it must be given a concrete initial orientation: the implicit constraint only keeps the two joint axes perpendicular (dot(n_b, n_a_in_b) = 0) and does not by itself pin the orientation. With the two rotational degrees of freedom left free, that single scalar constraint admits several solutions, and the initialization nonlinear solve converges to one of them depending on the (run-to-run nondeterministic) variable ordering of the assembled initialization system, which is what makes the test flaky.

Note that perpendicular joint axes is not a singularity of the universal joint (that would be collinear axes, which the constraint never permits — the axes are held at 90° throughout). phi = [0, 0, 0] is nonetheless a poor point to pin at: structural simplification keeps one Euler angle (phi[1]) as the constraint's dependent coordinate, and at the identity orientation the constraint cannot determine it (the derivative of dot(n_b, n_a_in_b) with respect to phi[1] vanishes), so initialization is ill-conditioned. The orientation is therefore pinned to phi = [0, -pi, 1], a point on the constraint manifold where initialization is well-conditioned and the run solves reliably.

Usage

MultibodyComponents.tests.UniversalConstraintTest()

Behavior

Source

dyad
"""
# Universal Constraint (closed loop with a spring)

A body is connected to the world by the implicit `UniversalConstraint` at one end
and pulled back to a fixed world point by a `Spring` at the other end, forming a
closed loop.

The constraint locks all three translations (the origins of the two frames
coincide at the world origin) and allows two rotational degrees of freedom: about
`n_a = [0, 0, 1]` (resolved in `frame_a`) and about `n_b = [1, 0, 0]` (resolved in
`frame_b`). The two axes are kept perpendicular by the joint. The body swings
under gravity (along `-y`, acting on the offset center of mass) and the spring
load.

The free body carries its own (absolute) Euler body coordinates as states, so it
must be given a concrete initial orientation: the implicit constraint only keeps
the two joint axes perpendicular (`dot(n_b, n_a_in_b) = 0`) and does not by itself
pin the orientation. With the two rotational degrees of freedom left free, that
single scalar constraint admits several solutions, and the initialization
nonlinear solve converges to one of them depending on the (run-to-run
nondeterministic) variable ordering of the assembled initialization system, which
is what makes the test flaky.

Note that perpendicular joint axes is *not* a singularity of the universal joint
(that would be collinear axes, which the constraint never permits — the axes are
held at 90° throughout). `phi = [0, 0, 0]` is nonetheless a poor point to pin at:
structural simplification keeps one Euler angle (`phi[1]`) as the constraint's
dependent coordinate, and at the identity orientation the constraint cannot
determine it (the derivative of `dot(n_b, n_a_in_b)` with respect to `phi[1]`
vanishes), so initialization is ill-conditioned. The orientation is therefore
pinned to `phi = [0, -pi, 1]`, a point on the constraint manifold where
initialization is well-conditioned and the run solves reliably.
"""
example component UniversalConstraintTest
  world = MultibodyComponents.World() {}
  constraint = MultibodyComponents.UniversalConstraint(n_a = [0, 0, 1], n_b = [1, 0, 0]) {}
  body = MultibodyComponents.Body(m = 1, r_cm = [0.2, 0, 0], I_11 = 0.1, I_22 = 0.1, I_33 = 0.1, orientation_state = OrientationState.Euler(), sequence = [1, 2, 3], phi(initial = [0, -pi, 1]), phid(initial = [0, 0, 0]), r_0(initial = missing), v_0(initial = missing)) {}
  rod = MultibodyComponents.FixedTranslation(r = [0.4, 0, 0], render = false) {}
  spring = MultibodyComponents.Spring(c = 20, s_unstretched = 0) {}
  mount = MultibodyComponents.FixedTranslation(r = [0.8, 0, 0.3], render = false) {}
relations
  connect(world.frame_b, constraint.frame_a) {}
  connect(constraint.frame_b, body.frame_a, rod.frame_a) {}
  connect(rod.frame_b, spring.frame_b) {}
  connect(world.frame_b, mount.frame_a) {}
  connect(mount.frame_b, spring.frame_a) {}
end
Flattened Source
dyad
"""
# Universal Constraint (closed loop with a spring)

A body is connected to the world by the implicit `UniversalConstraint` at one end
and pulled back to a fixed world point by a `Spring` at the other end, forming a
closed loop.

The constraint locks all three translations (the origins of the two frames
coincide at the world origin) and allows two rotational degrees of freedom: about
`n_a = [0, 0, 1]` (resolved in `frame_a`) and about `n_b = [1, 0, 0]` (resolved in
`frame_b`). The two axes are kept perpendicular by the joint. The body swings
under gravity (along `-y`, acting on the offset center of mass) and the spring
load.

The free body carries its own (absolute) Euler body coordinates as states, so it
must be given a concrete initial orientation: the implicit constraint only keeps
the two joint axes perpendicular (`dot(n_b, n_a_in_b) = 0`) and does not by itself
pin the orientation. With the two rotational degrees of freedom left free, that
single scalar constraint admits several solutions, and the initialization
nonlinear solve converges to one of them depending on the (run-to-run
nondeterministic) variable ordering of the assembled initialization system, which
is what makes the test flaky.

Note that perpendicular joint axes is *not* a singularity of the universal joint
(that would be collinear axes, which the constraint never permits — the axes are
held at 90° throughout). `phi = [0, 0, 0]` is nonetheless a poor point to pin at:
structural simplification keeps one Euler angle (`phi[1]`) as the constraint's
dependent coordinate, and at the identity orientation the constraint cannot
determine it (the derivative of `dot(n_b, n_a_in_b)` with respect to `phi[1]`
vanishes), so initialization is ill-conditioned. The orientation is therefore
pinned to `phi = [0, -pi, 1]`, a point on the constraint manifold where
initialization is well-conditioned and the run solves reliably.
"""
example component UniversalConstraintTest
  world = MultibodyComponents.World() {}
  constraint = MultibodyComponents.UniversalConstraint(n_a = [0, 0, 1], n_b = [1, 0, 0]) {}
  body = MultibodyComponents.Body(m = 1, r_cm = [0.2, 0, 0], I_11 = 0.1, I_22 = 0.1, I_33 = 0.1, orientation_state = OrientationState.Euler(), sequence = [1, 2, 3], phi(initial = [0, -pi, 1]), phid(initial = [0, 0, 0]), r_0(initial = missing), v_0(initial = missing)) {}
  rod = MultibodyComponents.FixedTranslation(r = [0.4, 0, 0], render = false) {}
  spring = MultibodyComponents.Spring(c = 20, s_unstretched = 0) {}
  mount = MultibodyComponents.FixedTranslation(r = [0.8, 0, 0.3], render = false) {}
relations
  connect(world.frame_b, constraint.frame_a) {}
  connect(constraint.frame_b, body.frame_a, rod.frame_a) {}
  connect(rod.frame_b, spring.frame_b) {}
  connect(world.frame_b, mount.frame_a) {}
  connect(mount.frame_b, spring.frame_a) {}
metadata {}
end


Test Cases

No test cases defined.

  • Examples

  • Experiments

  • Analyses