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