1 /* -------------------------------------------------------------------------- *
2  *                               Simbody(tm)                                  *
3  * -------------------------------------------------------------------------- *
4  * This is part of the SimTK biosimulation toolkit originating from           *
5  * Simbios, the NIH National Center for Physics-Based Simulation of           *
6  * Biological Structures at Stanford, funded under the NIH Roadmap for        *
7  * Medical Research, grant U54 GM072970. See https://simtk.org/home/simbody.  *
8  *                                                                            *
9  * Portions copyright (c) 2008-12 Stanford University and the Authors.        *
10  * Authors: Peter Eastman                                                     *
11  * Contributors: Michael Sherman                                              *
12  *                                                                            *
13  * Licensed under the Apache License, Version 2.0 (the "License"); you may    *
14  * not use this file except in compliance with the License. You may obtain a  *
15  * copy of the License at http://www.apache.org/licenses/LICENSE-2.0.         *
16  *                                                                            *
17  * Unless required by applicable law or agreed to in writing, software        *
18  * distributed under the License is distributed on an "AS IS" BASIS,          *
19  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
20  * See the License for the specific language governing permissions and        *
21  * limitations under the License.                                             *
22  * -------------------------------------------------------------------------- */
23 
24 #include "SimTKsimbody.h"
25 #include "SimTKcommon/Testing.h"
26 
27 using namespace SimTK;
28 using namespace std;
29 
30 const Real TOL = 1e-10;
31 const Real BOND_LENGTH = 0.5;
32 
33 #define ASSERT(cond) {SimTK_ASSERT_ALWAYS(cond, "Assertion failed");}
34 
35 template <class T>
assertEqual(T val1,T val2,double tol=TOL)36 void assertEqual(T val1, T val2, double tol=TOL) {
37     ASSERT(abs(val1-val2) < tol);
38 }
39 
40 template <int N>
assertEqual(Vec<N> val1,Vec<N> val2,double tol)41 void assertEqual(Vec<N> val1, Vec<N> val2, double tol) {
42     double norm = max(val1.norm(), 1.0);
43     for (int i = 0; i < N; ++i)
44         ASSERT(abs(val1[i]-val2[i]) < tol*norm);
45 }
46 
47 template<>
assertEqual(Vector val1,Vector val2,double tol)48 void assertEqual(Vector val1, Vector val2, double tol) {
49     ASSERT(val1.size() == val2.size());
50     for (int i = 0; i < val1.size(); ++i)
51         assertEqual(val1[i], val2[i], tol);
52 }
53 
54 template<>
assertEqual(SpatialVec val1,SpatialVec val2,double tol)55 void assertEqual(SpatialVec val1, SpatialVec val2, double tol) {
56     assertEqual(val1[0], val2[0], tol);
57     assertEqual(val1[1], val2[1], tol);
58 }
59 
60 template<>
assertEqual(Transform val1,Transform val2,double tol)61 void assertEqual(Transform val1, Transform val2, double tol) {
62     assertEqual(val1.p(), val2.p(), tol);
63     ASSERT(val1.R().isSameRotationToWithinAngle(val2.R(), tol));
64 }
65 
compareReactionToConstraint(SpatialVec reactionForce,const Constraint & constraint,const State & state)66 void compareReactionToConstraint(SpatialVec reactionForce, const Constraint& constraint, const State& state) {
67     Vector_<SpatialVec> constraintForce(constraint.getNumConstrainedBodies());
68     Vector mobilityForce(constraint.getNumConstrainedU(state));
69     constraint.calcConstraintForcesFromMultipliers(state, constraint.getMultipliersAsVector(state), constraintForce, mobilityForce);
70 
71     // Transform the reaction force from the joint location to the body location.
72 
73     const MobilizedBody& body = constraint.getMobilizedBodyFromConstrainedBody(ConstrainedBodyIndex(1));
74     Vec3 localForce = ~body.getBodyTransform(state).R()*reactionForce[1];
75     reactionForce[0] += body.getBodyTransform(state).R()*(body.getOutboardFrame(state).p()%localForce);
76     assertEqual(reactionForce, -1*constraint.getAncestorMobilizedBody().getBodyRotation(state)*constraintForce[1]);
77 }
78 
79 /**
80  * Compare the forces generated by equivalent mobilizers and constraints.
81  */
82 
testByComparingToConstraints()83 void testByComparingToConstraints() {
84     MultibodySystem system;
85     SimbodyMatterSubsystem matter(system);
86     GeneralForceSubsystem forces(system);
87     Force::UniformGravity(forces, matter, Vec3(0, -9.8, 0));
88 
89     // Create two free joints (which should produce no reaction forces).
90 
91     Body::Rigid body = Body::Rigid(MassProperties(1.3, Vec3(0), Inertia(1.3)));
92     MobilizedBody::Free f1(matter.updGround(), Transform(Vec3(0)), body, Transform(Vec3(BOND_LENGTH, 0, 0)));
93     MobilizedBody::Free f2(f1, Transform(Vec3(0)), body, Transform(Vec3(BOND_LENGTH, 0, 0)));
94 
95     // Two ball joints, and two free joints constrained to act like ball joints.
96 
97     MobilizedBody::Free fb1(matter.updGround(), Transform(Vec3(0)), body, Transform(Vec3(BOND_LENGTH, 0, 0)));
98     MobilizedBody::Free fb2(fb1, Transform(Vec3(0, 0, BOND_LENGTH)), body, Transform(Vec3(BOND_LENGTH, 0, 0)));
99     Constraint::Ball fb1constraint(matter.updGround(), Vec3(0, 0, 0), fb1, Vec3(BOND_LENGTH, 0, 0));
100     Constraint::Ball fb2constraint(fb1, Vec3(0, 0, BOND_LENGTH), fb2, Vec3(BOND_LENGTH, 0, 0));
101     MobilizedBody::Ball b1(matter.updGround(), Transform(Vec3(0)), body, Transform(Vec3(BOND_LENGTH, 0, 0)));
102     MobilizedBody::Ball b2(b1, Transform(Vec3(0, 0, BOND_LENGTH)), body, Transform(Vec3(BOND_LENGTH, 0, 0)));
103     Force::ConstantTorque(forces, fb2, Vec3(0.1, 0.1, 1.0));
104     Force::ConstantTorque(forces, b2, Vec3(0.1, 0.1, 1.0));
105 
106     // Two translation joints, and two free joints constrained to act like translation joints.
107 
108     MobilizedBody::Free ft1(matter.updGround(), Transform(Vec3(0)), body, Transform(Vec3(BOND_LENGTH, 0, 0)));
109     MobilizedBody::Free ft2(ft1, Transform(Vec3(0)), body, Transform(Vec3(0, BOND_LENGTH, 0)));
110     Constraint::ConstantOrientation ft1constraint(matter.updGround(), Rotation(0, Vec3(1)), ft1, Rotation(0, Vec3(1)));
111     Constraint::ConstantOrientation ft2constraint(ft1, Rotation(0, Vec3(1)), ft2, Rotation(0, Vec3(1)));
112     MobilizedBody::Translation t1(matter.updGround(), Transform(Vec3(0)), body, Transform(Vec3(BOND_LENGTH, 0, 0)));
113     MobilizedBody::Translation t2(t1, Transform(Vec3(0)), body, Transform(Vec3(0, BOND_LENGTH, 0)));
114     Force::ConstantTorque(forces, ft2, Vec3(0.1, 0.1, 1.0));
115     Force::ConstantTorque(forces, t2, Vec3(0.1, 0.1, 1.0));
116 
117     // Create the state.
118 
119     system.realizeTopology();
120     State state = system.getDefaultState();
121     Random::Gaussian random;
122     int nq = state.getNQ()/2;
123     for (int i = 0; i < state.getNY(); ++i)
124         state.updY()[i] = random.getValue();
125     system.realize(state, Stage::Velocity);
126     Transform b1transform = b1.getMobilizerTransform(state);
127     Transform b2transform = b2.getMobilizerTransform(state);
128     SpatialVec b1velocity = b1.getMobilizerVelocity(state);
129     SpatialVec b2velocity = b2.getMobilizerVelocity(state);
130     Transform t1transform = t1.getMobilizerTransform(state);
131     Transform t2transform = t2.getMobilizerTransform(state);
132     SpatialVec t1velocity = t1.MobilizedBody::getMobilizerVelocity(state);
133     SpatialVec t2velocity = t2.MobilizedBody::getMobilizerVelocity(state);
134     fb1.setQToFitTransform(state, b1transform);
135     fb2.setQToFitTransform(state, b2transform);
136     fb1.setUToFitVelocity(state, b1velocity);
137     fb2.setUToFitVelocity(state, b2velocity);
138     ft1.setQToFitTransform(state, t1transform);
139     ft2.setQToFitTransform(state, t2transform);
140     ft1.setUToFitVelocity(state, t1velocity);
141     ft2.setUToFitVelocity(state, t2velocity);
142 
143     system.project(state, TOL);
144     system.realize(state, Stage::Acceleration);
145 
146     // Make sure the free and constrained bodies really are identical.
147 
148     assertEqual(b1.getBodyTransform(state), fb1.getBodyTransform(state));
149     assertEqual(b2.getBodyTransform(state), fb2.getBodyTransform(state));
150     assertEqual(b1.getBodyVelocity(state), fb1.getBodyVelocity(state));
151     assertEqual(b2.getBodyVelocity(state), fb2.getBodyVelocity(state));
152     assertEqual(t1.getBodyTransform(state), ft1.getBodyTransform(state));
153     assertEqual(t2.getBodyTransform(state), ft2.getBodyTransform(state));
154     assertEqual(t1.getBodyVelocity(state), ft1.getBodyVelocity(state));
155     assertEqual(t2.getBodyVelocity(state), ft2.getBodyVelocity(state));
156 
157     // Calculate the mobility reaction forces.
158 
159     Vector_<SpatialVec> forcesAtMInG(matter.getNumBodies());
160     matter.calcMobilizerReactionForces(state, forcesAtMInG);
161 
162 
163 
164     // Check that the bulk calculation matches the body-by-body calculation.
165     for (MobilizedBodyIndex bx(0); bx < matter.getNumBodies(); ++bx) {
166         assertEqual(forcesAtMInG[bx],
167             matter.getMobilizedBody(bx)
168                .findMobilizerReactionOnBodyAtMInGround(state));
169     }
170 
171     // Make sure all free bodies have no reaction force on them.
172 
173     assertEqual((forcesAtMInG[f1.getMobilizedBodyIndex()]), SpatialVec(Vec3(0), Vec3(0)));
174     assertEqual((forcesAtMInG[f2.getMobilizedBodyIndex()]), SpatialVec(Vec3(0), Vec3(0)));
175     assertEqual((forcesAtMInG[fb1.getMobilizedBodyIndex()]), SpatialVec(Vec3(0), Vec3(0)));
176     assertEqual((forcesAtMInG[fb2.getMobilizedBodyIndex()]), SpatialVec(Vec3(0), Vec3(0)));
177     assertEqual((forcesAtMInG[ft1.getMobilizedBodyIndex()]), SpatialVec(Vec3(0), Vec3(0)));
178     assertEqual((forcesAtMInG[ft2.getMobilizedBodyIndex()]), SpatialVec(Vec3(0), Vec3(0)));
179 
180     // The reaction forces should match the corresponding constraint forces.
181 
182     compareReactionToConstraint(forcesAtMInG[b1.getMobilizedBodyIndex()], fb1constraint, state);
183     compareReactionToConstraint(forcesAtMInG[b2.getMobilizedBodyIndex()], fb2constraint, state);
184     compareReactionToConstraint(forcesAtMInG[t1.getMobilizedBodyIndex()], ft1constraint, state);
185     compareReactionToConstraint(forcesAtMInG[t2.getMobilizedBodyIndex()], ft2constraint, state);
186 }
187 
188 /*
189  * (sherm 110919) None of the existing tests caught the problem reported
190  * in bug #1535 -- incorrect reaction torques sometimes.
191  * This is a pair of identical two-body pendulums, one done with pin joints
192  * and one done with equivalent constraints.
193  */
testByComparingToConstraints2()194 void testByComparingToConstraints2() {
195     MultibodySystem system;
196     SimbodyMatterSubsystem matter(system);
197     GeneralForceSubsystem forces(system);
198     Force::UniformGravity gravity(forces, matter, Vec3(10, -9.8, 3));
199 
200     Body::Rigid pendulumBody(MassProperties(1.0, Vec3(0), Inertia(1)));
201     pendulumBody.addDecoration(Transform(), DecorativeSphere(0.1).setColor(Red));
202 
203     // First double pendulum, using Pin joints.
204     Rotation x45(Pi/4, XAxis);
205     MobilizedBody::Pin pendulum1(matter.updGround(),
206                                 Transform(x45,Vec3(0,-1,0)),
207                                 pendulumBody,
208                                 Transform(Vec3(0, 1, 0)));
209     MobilizedBody::Pin pendulum1b(pendulum1,
210                                 Transform(x45,Vec3(0,-1,0)),
211                                 pendulumBody,
212                                 Transform(Vec3(0, 1, 0)));
213 
214     // Second double pendulum, using Free joints plus 5 constraints.
215     MobilizedBody::Free pendulum2(matter.updGround(),
216                                   Transform(x45,Vec3(2,-1,0)),
217                                   pendulumBody,
218                                   Transform(Vec3(0,1,0)));
219     Constraint::Ball ballcons2(matter.updGround(), Vec3(2,-1,0),
220                                pendulum2, Vec3(0,1,0));
221     const Transform& X_GF2 = pendulum2.getDefaultInboardFrame();
222     const Transform& X_P2M = pendulum2.getDefaultOutboardFrame();
223     Constraint::ConstantAngle angx2(matter.Ground(), X_GF2.x(),
224                               pendulum2, X_P2M.z());
225     Constraint::ConstantAngle angy2(matter.Ground(), X_GF2.y(),
226                               pendulum2, X_P2M.z());
227 
228     MobilizedBody::Free pendulum2b(pendulum2,
229                                    Transform(x45,Vec3(0,-1,0)),
230                                    pendulumBody,
231                                    Transform(Vec3(0,1,0)));
232     Constraint::Ball ballcons2b(pendulum2, Vec3(0,-1,0),
233                                 pendulum2b, Vec3(0,1,0));
234     const Transform& X_GF2b = pendulum2b.getDefaultInboardFrame();
235     const Transform& X_P2Mb = pendulum2b.getDefaultOutboardFrame();
236     Constraint::ConstantAngle angx2b(pendulum2, X_GF2b.x(),
237                               pendulum2b, X_P2Mb.z());
238     Constraint::ConstantAngle angy2b(pendulum2, X_GF2b.y(),
239                               pendulum2b, X_P2Mb.z());
240 
241     // Uncomment if you want to see this.
242     //Visualizer viz(system);
243 
244     // Initialize the system and state.
245 
246     system.realizeTopology();
247     State state = system.getDefaultState();
248     pendulum1.setOneQ(state, 0, Pi/4);
249     pendulum1.setOneU(state, 0, 1.0); // initial velocity 1 rad/sec
250 
251     pendulum1b.setOneU(state, 0, 1.0); // initial velocity 1 rad/sec
252     pendulum1b.setOneQ(state, 0, Pi/4);
253 
254     pendulum2.setQToFitRotation(state, Rotation(Pi/4, ZAxis));
255     pendulum2.setUToFitAngularVelocity(state, Vec3(0,0,1));
256     pendulum2b.setQToFitRotation(state, Rotation(Pi/4, ZAxis));
257     pendulum2b.setUToFitAngularVelocity(state, Vec3(0,0,1));
258 
259     system.realize(state);
260     //viz.report(state);
261 
262     const MobodIndex p2x = pendulum2.getMobilizedBodyIndex();
263     const MobodIndex p2bx = pendulum2b.getMobilizedBodyIndex();
264 
265 
266     Vector_<SpatialVec> forcesAtMInG, forcesAtBInG, forcesAtFInG;
267     matter.calcMobilizerReactionForces(state, forcesAtMInG);
268 
269     // Check that the bulk results match the individual ones, and fill
270     // up the Vector of reaction on the parent bodies.
271     forcesAtFInG.resize(forcesAtMInG.size());
272     for (MobilizedBodyIndex mbx(0); mbx < matter.getNumBodies(); ++mbx) {
273         SimTK_TEST_EQ(forcesAtMInG[mbx], matter.getMobilizedBody(mbx)
274             .findMobilizerReactionOnBodyAtMInGround(state));
275 
276         forcesAtFInG[mbx] = matter.getMobilizedBody(mbx)
277             .findMobilizerReactionOnParentAtFInGround(state);
278     }
279 
280     // Now we'll convert forces on B at M to forces on P at F manually, and
281     // compare with the ones we got by asking the mobilized body.
282     Vector_<SpatialVec> forcesAtFInG_byhand(forcesAtMInG.size());
283     forcesAtFInG_byhand[0] = -forcesAtMInG[0]; // Ground is "welded" at origin
284     for (MobilizedBodyIndex i(1); i < matter.getNumBodies(); ++i) {
285         const MobilizedBody& body   = matter.getMobilizedBody(i);
286         const MobilizedBody& parent = body.getParentMobilizedBody();
287         // Want to shift reaction by p_MF, the vector from M to F across the
288         // mobilizer, and negate. Can get p_FM; must reexpress in G.
289         const Vec3& p_FM = body.getMobilizerTransform(state).p();
290         const Rotation& R_PF = body.getInboardFrame(state).R(); // In parent.
291         const Rotation& R_GP = parent.getBodyTransform(state).R();
292         Rotation R_GF   =   R_GP*R_PF;  // F frame orientation in Ground.
293         Vec3     p_MF_G = -(R_GF*p_FM); // Re-express and negate shift vector.
294         forcesAtFInG_byhand[i] = -shiftForceBy(forcesAtMInG[i], p_MF_G);
295     }
296 
297     SimTK_TEST_EQ(forcesAtFInG, forcesAtFInG_byhand);
298 
299     // Shift the reaction forces to body origins for easy comparison with
300     // the reported constraint forces.
301     forcesAtBInG.resize(forcesAtMInG.size());
302     const MobodIndex p1x = pendulum1.getMobilizedBodyIndex();
303     const MobodIndex p1bx = pendulum1b.getMobilizedBodyIndex();
304     const Rotation& R_G1 = pendulum1.getBodyTransform(state).R();
305     const Rotation& R_G1b = pendulum1b.getBodyTransform(state).R();
306     forcesAtBInG[p1x] = shiftForceFromTo(forcesAtMInG[p1x],
307                                          R_G1*Vec3(0,1,0), Vec3(0));
308     forcesAtBInG[p1bx] = shiftForceFromTo(forcesAtMInG[p1bx],
309                                          R_G1b*Vec3(0,1,0), Vec3(0));
310 
311     // Compare those manually-shifted quantities to the ones we can get
312     // direction from the MobilizedBody.
313     SpatialVec forcesAtBInG_p1 =
314         pendulum1.findMobilizerReactionOnBodyAtOriginInGround(state);
315     SpatialVec forcesAtBInG_p1b =
316         pendulum1b.findMobilizerReactionOnBodyAtOriginInGround(state);
317 
318     SimTK_TEST_EQ(forcesAtBInG[p1x], forcesAtBInG_p1);
319     SimTK_TEST_EQ(forcesAtBInG[p1bx], forcesAtBInG_p1b);
320 
321     // The constraints apply forces to parent and body; we want to compare
322     // forces on the body, which will be the second entry here. We're assuming
323     // the ball and constant angle constraints are ordered the same way; if
324     // that ever changes the constraints can be queried to find the mobilized
325     // body index corresponding to the constrained body index.
326     Vector_<SpatialVec> cons2Forces =
327         -(ballcons2.getConstrainedBodyForcesAsVector(state)
328           + angx2.getConstrainedBodyForcesAsVector(state)
329           + angy2.getConstrainedBodyForcesAsVector(state));
330     Vector_<SpatialVec> cons2bForces =
331         -(ballcons2b.getConstrainedBodyForcesAsVector(state)
332           + angx2b.getConstrainedBodyForcesAsVector(state)
333           + angy2b.getConstrainedBodyForcesAsVector(state));
334 
335     // Couldn't quite make default tolerance on some platforms. This uses
336     // 10X default.
337     SimTK_TEST_EQ_SIZE(cons2Forces[1], forcesAtBInG[p1x], 10);
338     SimTK_TEST_EQ_SIZE(cons2bForces[1], forcesAtBInG[p1bx], 10);
339 }
340 
341 /**
342  * Construct a system of several bodies, and compare the reaction forces to those calculated by SD/FAST.
343  */
344 
testByComparingToSDFAST()345 void testByComparingToSDFAST() {
346     MultibodySystem system;
347     SimbodyMatterSubsystem matter(system);
348     GeneralForceSubsystem forces(system);
349     Force::UniformGravity(forces, matter, Vec3(0, -9.8, 0));
350 
351     // Construct the set of bodies.
352 
353     Inertia inertia = Inertia(Mat33(0.1, 0.01, 0.01,
354                                     0.01, 0.1, 0.01,
355                                     0.01, 0.01, 0.1));
356     MobilizedBody::Slider body1(matter.updGround(), MassProperties(10.0, Vec3(0), inertia));
357     MobilizedBody::Pin body2(body1, Vec3(0.1, 0.1, 0), MassProperties(20.0, Vec3(0), inertia), Vec3(0, -0.2, 0));
358     MobilizedBody::Gimbal body3(body2, Vec3(0, 0.2, 0), MassProperties(20.0, Vec3(0), inertia), Vec3(0, -0.2, 0));
359     MobilizedBody::Pin body4(body3, Vec3(0, 0.2, 0), MassProperties(30.0, Vec3(0), inertia), Vec3(0, -0.2, 0));
360     State state = system.realizeTopology();
361     system.realize(state, Stage::Acceleration);
362 
363     // Calculate reaction forces, and compare to the values that were generated by SD/FAST.
364 
365     Vector_<SpatialVec> reaction(matter.getNumBodies());
366     matter.calcMobilizerReactionForces(state, reaction);
367     assertEqual(~body1.getBodyTransform(state).R()*reaction[body1.getMobilizedBodyIndex()], SpatialVec(Vec3(0, 0, 68.6), Vec3(0, 784.0, 0)));
368     assertEqual(~body2.getBodyTransform(state).R()*reaction[body2.getMobilizedBodyIndex()], SpatialVec(Vec3(0, 0, 0), Vec3(0, 686.0, 0)));
369     assertEqual(~body3.getBodyTransform(state).R()*reaction[body3.getMobilizedBodyIndex()], SpatialVec(Vec3(0, 0, 0), Vec3(0, 490.0, 0)));
370     assertEqual(~body4.getBodyTransform(state).R()*reaction[body4.getMobilizedBodyIndex()], SpatialVec(Vec3(0, 0, 0), Vec3(0, 294.0, 0)));
371 
372     // Now set it to a different configuration and try again.
373 
374     body1.setLength(state, 1.0);
375     body2.setAngle(state, 0.5);
376     Rotation r;
377     r.setRotationFromThreeAnglesThreeAxes(BodyRotationSequence, 0.2, ZAxis, -0.1, XAxis, 2.0, YAxis);
378     body3.setQToFitRotation(state, r);
379     body4.setAngle(state, -0.5);
380     system.realize(state, Stage::Acceleration);
381     matter.calcMobilizerReactionForces(state, reaction);
382     assertEqual(~body1.getBodyTransform(state).R()*reaction[body1.getMobilizedBodyIndex()], SpatialVec(Vec3(1.647327, 0.783211, 34.088183), Vec3(0, 359.274099, 3.342380)), 1e-5);
383     assertEqual(~body2.getBodyTransform(state).R()*reaction[body2.getMobilizedBodyIndex()], SpatialVec(Vec3(1.688077, 0.351125, 0), Vec3(55.399123, 267.455570, 3.342380)), 1e-5);
384     assertEqual(~body3.getBodyTransform(state).R()*reaction[body3.getMobilizedBodyIndex()], SpatialVec(Vec3(0, 0, 0), Vec3(-17.757553, 174.663042, -11.383057)), 1e-5);
385     assertEqual(~body4.getBodyTransform(state).R()*reaction[body4.getMobilizedBodyIndex()], SpatialVec(Vec3(0.910890, 0.082353, 0), Vec3(-13.977214, 74.444715, 4.943682)), 1e-5);
386 
387     // Try giving it momentum.
388 
389     state.updQ() = 0.0;
390     body2.setOneU(state, 0, 1);
391     body3.setUToFitAngularVelocity(state, Vec3(3, 4, 2));
392     body4.setOneU(state, 0, 5);
393     system.realize(state, Stage::Acceleration);
394     matter.calcMobilizerReactionForces(state, reaction);
395     assertEqual(~body1.getBodyTransform(state).R()*reaction[body1.getMobilizedBodyIndex()], SpatialVec(Vec3(-13.549253, 2.723897, -6.355912), Vec3(0, 34.0, -27.088584)), 1e-5);
396     assertEqual(~body2.getBodyTransform(state).R()*reaction[body2.getMobilizedBodyIndex()], SpatialVec(Vec3(-10.840395, 0.015039, 0), Vec3(-0.440882, -64.0, -27.088584)), 1e-5);
397     assertEqual(~body3.getBodyTransform(state).R()*reaction[body3.getMobilizedBodyIndex()], SpatialVec(Vec3(0, 0, 0), Vec3(0.692814, -256.000000, -27.088584)), 1e-5);
398     assertEqual(~body4.getBodyTransform(state).R()*reaction[body4.getMobilizedBodyIndex()], SpatialVec(Vec3(3.276930, -0.281928, 0), Vec3(3.796164, -372.0, 21.472977)), 1e-5);
399 }
400 
401 /**
402  * Construct a system of several bodies, and compare the reaction forces to those calculated by SD/FAST.
403  */
404 
testByComparingToSDFAST2()405 void testByComparingToSDFAST2() {
406     MultibodySystem system;
407     SimbodyMatterSubsystem matter(system);
408     GeneralForceSubsystem forces(system);
409     Force::UniformGravity(forces, matter, Vec3(0, -9.8065, 0));
410 
411     // Construct the set of bodies.
412 
413     Body::Rigid femur(MassProperties(8.806, Vec3(0), Inertia(Vec3(0.1268, 0.0332, 0.1337))));
414     Body::Rigid tibia(MassProperties(3.510, Vec3(0), Inertia(Vec3(0.0477, 0.0048, 0.0484))));
415     MobilizedBody::Pin p1(matter.Ground(), Transform(Vec3(0.0000, -0.0700, 0.0935)), femur, Transform(Vec3(0.0020, 0.1715, 0)));
416     MobilizedBody::Slider p2(p1, Transform(Vec3(0.0033, -0.2294, 0)), tibia, Transform(Vec3(0.0, 0.1862, 0.0)));
417     State state = system.realizeTopology();
418     system.realize(state, Stage::Acceleration);
419 
420     // Calculate reaction forces, and compare to the values that were generated by SD/FAST.
421 
422     Vector_<SpatialVec> reaction(matter.getNumBodies());
423     matter.calcMobilizerReactionForces(state, reaction);
424     assertEqual(~p1.getBodyTransform(state).R()*reaction[p1.getMobilizedBodyIndex()], SpatialVec(Vec3(0, 0, 0), Vec3(0.438079, 120.773069, 0)), 1e-5);
425     assertEqual(~p2.getBodyTransform(state).R()*reaction[p2.getMobilizedBodyIndex()], SpatialVec(Vec3(0, 0, 0.014040), Vec3(0, 34.422139, 0)), 1e-5);
426 
427     // Now set it to a different configuration and try again.
428 
429     p1.setOneQ(state, 0, -90*NTraits<Real>::getPi()/180);
430     p2.setOneQ(state, 0, 0.1);
431     system.realize(state, Stage::Acceleration);
432     matter.calcMobilizerReactionForces(state, reaction);
433     assertEqual(~p1.getBodyTransform(state).R()*reaction[p1.getMobilizedBodyIndex()], SpatialVec(Vec3(0, 0, 0), Vec3(-39.481457, 10.489344, 0)), 1e-5);
434     assertEqual(~p2.getBodyTransform(state).R()*reaction[p2.getMobilizedBodyIndex()], SpatialVec(Vec3(0, 0, 1.502242), Vec3(0, 11.035987, 0)), 1e-5);
435 }
436 
437 /**
438  * Construct a system of several bodies and a constraint, and compare the reaction forces to those calculated by SD/FAST.
439  */
440 
testByComparingToSDFASTWithConstraint()441 void testByComparingToSDFASTWithConstraint() {
442     MultibodySystem system;
443     SimbodyMatterSubsystem matter(system);
444     GeneralForceSubsystem forces(system);
445     Force::UniformGravity(forces, matter, Vec3(0, -9.8, 0));
446 
447     // Construct the set of bodies.
448 
449     Inertia inertia = Inertia(Mat33(0.1, 0.01, 0.01,
450                                     0.01, 0.1, 0.01,
451                                     0.01, 0.01, 0.1));
452     MobilizedBody::Gimbal body1(matter.updGround(),
453         MassProperties(10.0, Vec3(0), inertia));
454     MobilizedBody::Gimbal body2(body1, Vec3(0, -0.1, 0.2),
455         MassProperties(20.0, Vec3(0), inertia), Vec3(0, 0.2, 0));
456     MobilizedBody::Gimbal body3(body1, Vec3(0, -0.1, -0.2),
457         MassProperties(20.0, Vec3(0), inertia), Vec3(0, 0.2, 0));
458     MobilizedBody::Gimbal body4(body2, Vec3(0, -0.2, 0),
459         MassProperties(30.0, Vec3(0), inertia), Vec3(0, 0.2, 0));
460     MobilizedBody::Gimbal body5(body3, Vec3(0, -0.2, 0),
461         MassProperties(30.0, Vec3(0), inertia), Vec3(0, 0.2, 0));
462     Constraint::Rod constraint(body4, body5, 0.15);
463     State state = system.realizeTopology();
464     system.realize(state, Stage::Velocity);
465     // After I changed the Rod constraint to use distance rather than distance^2
466     // it assembled slightly differently and then wouldn't pass this test to
467     // this precision. So I replaced the project() call with the result from
468     // the older squared equations. The result I was getting from project() with
469     // the distance equations was:
470     //          0 0 0 0.188972205696439 0 0 -0.188972205696439 0 0
471     //          0.062986198663389 0 0 -0.062986198663389 0 0
472     // which is nearly identical to those below, but different enough to cause
473     // a failure here. With the same set of q's, the reactions should be the
474     // same regardless of the equations being used for Rod. (sherm 140506)
475     //system.project(state, 1e-10);
476     Real q[15]={0,0,0,0.189000969332574,0,0,-0.189000969332574,0,0,
477                 0.0628990902570866,0,0,-0.0628990902570866,0,0};
478     state.updQ() = Vector(15, q);
479     system.realize(state, Stage::Acceleration);
480 
481     // Calculate reaction forces, and compare to the values that were generated by SD/FAST.
482 
483     Vector_<SpatialVec> reaction(matter.getNumBodies());
484     matter.calcMobilizerReactionForces(state, reaction);
485     assertEqual(~body1.getBodyTransform(state).R()*reaction[body1.getMobilizedBodyIndex()],
486                 SpatialVec(Vec3(0, 0, 0), Vec3(-0.000626, 1077.988912, 0.000030)), 1e-5);
487     assertEqual(~body2.getBodyTransform(state).R()*reaction[body2.getMobilizedBodyIndex()],
488                 SpatialVec(Vec3(0, 0, 0), Vec3(-0.005038, 495.288692, -18.767467)), 1e-5);
489     assertEqual(~body3.getBodyTransform(state).R()*reaction[body3.getMobilizedBodyIndex()],
490                 SpatialVec(Vec3(0, 0, 0), Vec3(0.004236, 495.287857, 18.767535)), 1e-5);
491     assertEqual(~body4.getBodyTransform(state).R()*reaction[body4.getMobilizedBodyIndex()],
492                 SpatialVec(Vec3(0, 0, 0), Vec3(0.006251, 303.365940, -0.202330)), 1e-5);
493     assertEqual(~body5.getBodyTransform(state).R()*reaction[body5.getMobilizedBodyIndex()],
494                 SpatialVec(Vec3(0, 0, 0), Vec3(-0.005933, 303.365472, 0.202301)), 1e-5);
495 
496     // Now set it to a different configuration and try again.
497 
498     Rotation r;
499     r.setRotationFromThreeAnglesThreeAxes(BodyRotationSequence, 1.0, ZAxis, 1.0, XAxis, 1.0, YAxis);
500     body1.setQToFitRotation(state, r);
501     r.setRotationFromThreeAnglesThreeAxes(BodyRotationSequence, 0.433843, ZAxis, 0.647441, XAxis, 0.500057, YAxis);
502     body2.setQToFitRotation(state, r);
503     r.setRotationFromThreeAnglesThreeAxes(BodyRotationSequence, 0.066156, ZAxis, -0.117266, XAxis, -0.047605, YAxis);
504     body3.setQToFitRotation(state, r);
505     r.setRotationFromThreeAnglesThreeAxes(BodyRotationSequence, 0.000997, ZAxis, 0.055206, XAxis, 0.0, YAxis);
506     body4.setQToFitRotation(state, r);
507     r.setRotationFromThreeAnglesThreeAxes(BodyRotationSequence, 1.008746, ZAxis, 0.951972, XAxis, 1.0, YAxis);
508     body5.setQToFitRotation(state, r);
509     system.realize(state, Stage::Acceleration);
510     matter.calcMobilizerReactionForces(state, reaction);
511     assertEqual(~body1.getBodyTransform(state).R()*reaction[body1.getMobilizedBodyIndex()], SpatialVec(Vec3(0, 0, 0), Vec3(99.121319, 139.500095, 95.065409)), 1e-5);
512     assertEqual(~body2.getBodyTransform(state).R()*reaction[body2.getMobilizedBodyIndex()], SpatialVec(Vec3(0, 0, 0), Vec3(15.359115, 55.876994, 22.508078)), 1e-5);
513     assertEqual(~body3.getBodyTransform(state).R()*reaction[body3.getMobilizedBodyIndex()], SpatialVec(Vec3(0, 0, 0), Vec3(15.696393, 65.002065, 13.133021)), 1e-5);
514     assertEqual(~body4.getBodyTransform(state).R()*reaction[body4.getMobilizedBodyIndex()], SpatialVec(Vec3(0, 0, 0), Vec3(-6.262023, 32.714510, -9.770708)), 1e-5);
515     assertEqual(~body5.getBodyTransform(state).R()*reaction[body5.getMobilizedBodyIndex()], SpatialVec(Vec3(0, 0, 0), Vec3(10.471620, 0.963822, -4.640161)), 1e-5);
516 }
517 
518 // Create a free body in space and apply some forces to it.
519 // As long as we don't apply mobility forces, the reaction force
520 // in the mobilizer should be zero.
521 // It is important to do this with a full inertia, offset com,
522 // non-unit mass, twisted frames, non-zero velocities, etc.
523 
524 const Real d = 1.5; // length (m)
525 const Real mass = 2; // kg
526 const Transform X_GF(Rotation(Pi/3, Vec3(.1,-.3,.3)), Vec3(-4,-5,-1));
527 const Transform X_BM(Rotation(-Pi/10, Vec3(7,5,3)), Vec3(0,d,0));
528 
testFreeMobilizer()529 void testFreeMobilizer() {
530     MultibodySystem forward;
531     SimbodyMatterSubsystem fwdMatter(forward);
532     GeneralForceSubsystem fwdForces(forward);
533     Force::UniformGravity(fwdForces, fwdMatter, Vec3(0, -1, 0));
534 
535     const Vec3 com(1,2,3);
536     const UnitInertia centralGyration(1, 1.5, 2, .1, .2, .3);
537     Body::Rigid body(MassProperties(mass, com, mass*centralGyration.shiftFromMassCenter(com, 1)));
538 
539     MobilizedBody::Free fwdA (fwdMatter.Ground(),  X_GF, body, X_BM);
540 
541     Force::ConstantForce(fwdForces, fwdA, Vec3(-1,.27,4), Vec3(5,.6,-1));
542     Force::ConstantTorque(fwdForces, fwdA, Vec3(-5.5,1.6,-1.1));
543 
544     State fwdState  = forward.realizeTopology();
545     fwdA.setQToFitTransform(fwdState, Transform(Rotation(Pi/9,Vec3(-1.8,4,2.2)), Vec3(.1,.2,.7)));
546 
547     forward.realize (fwdState,  Stage::Position);
548 
549     fwdA.setUToFitVelocity(fwdState, SpatialVec(Vec3(.99,2,4), Vec3(-1.2,4,.000333)));
550     forward.realize (fwdState,  Stage::Velocity);
551     forward.realize (fwdState,  Stage::Acceleration);
552 
553     Vector_<SpatialVec> fwdReac;
554     fwdMatter.calcMobilizerReactionForces(fwdState, fwdReac);
555 
556     // We expect no reaction from a Free joint.
557     assertEqual(fwdReac[0], SpatialVec(Vec3(0)));
558     assertEqual(fwdReac[1], SpatialVec(Vec3(0)));
559 }
560 
main()561 int main() {
562     SimTK_START_TEST("TestMobilizerReactionForces");
563         SimTK_SUBTEST(testByComparingToConstraints);
564         SimTK_SUBTEST(testByComparingToConstraints2);
565         SimTK_SUBTEST(testByComparingToSDFAST);
566         SimTK_SUBTEST(testByComparingToSDFAST2);
567         SimTK_SUBTEST(testByComparingToSDFASTWithConstraint);
568         SimTK_SUBTEST(testFreeMobilizer);
569     SimTK_END_TEST();
570 }
571