1 /* -------------------------------------------------------------------------- *
2  *                       OpenSim:  testIterators.cpp                        *
3  * -------------------------------------------------------------------------- *
4  * The OpenSim API is a toolkit for musculoskeletal modeling and simulation.  *
5  * See http://opensim.stanford.edu and the NOTICE file for more information.  *
6  * OpenSim is developed at Stanford University and supported by the US        *
7  * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA    *
8  * through the Warrior Web program.                                           *
9  *                                                                            *
10  * Copyright (c) 2005-2017 Stanford University and the Authors                *
11  * Author(s): Ayman Habib                                                     *
12  * Contributer(s) :                                                           *
13  *                                                                            *
14  * Licensed under the Apache License, Version 2.0 (the "License"); you may    *
15  * not use this file except in compliance with the License. You may obtain a  *
16  * copy of the License at http://www.apache.org/licenses/LICENSE-2.0.         *
17  *                                                                            *
18  * Unless required by applicable law or agreed to in writing, software        *
19  * distributed under the License is distributed on an "AS IS" BASIS,          *
20  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
21  * See the License for the specific language governing permissions and        *
22  * limitations under the License.                                             *
23  * -------------------------------------------------------------------------- */
24 #include <iostream>
25 #include <OpenSim/Simulation/Model/Model.h>
26 #include "OpenSim/Simulation/SimbodyEngine/PinJoint.h"
27 #include <OpenSim/Common/LoadOpenSimLibrary.h>
28 #include <OpenSim/Auxiliary/auxiliaryTestFunctions.h>
29 
30 using namespace OpenSim;
31 using namespace std;
32 
33 // Example filter that allows for iterating only through Components that have
34 // state variables, used for demonstration purposes.
35 class ComponentWithStateVariables : public ComponentFilter {
36 public:
ComponentWithStateVariables()37     ComponentWithStateVariables() {
38         //std::cout << "ComponentWithStateVariables constructed" << std::endl;
39     };
isMatch(const Component & comp) const40     bool isMatch(const Component& comp) const override {
41         return (comp.getNumStateVariables()>0);
42     };
~ComponentWithStateVariables()43     ~ComponentWithStateVariables() {
44         //std::cout << "ComponentWithStateVariables destructed" << std::endl;
45     }
clone() const46     ComponentWithStateVariables* clone() const override {
47         return new ComponentWithStateVariables(*this);
48     }
49 };
50 
51 const std::string modelFilename = "arm26.osim";
52 // TODO: Hard-coding these numbers is not ideal. As we introduce more components
53 // to recompose existing components, this will need continual updating. For example,
54 // Joint's often add PhysicalOffsetFrames to handle what used to be baked in location
55 // and orientation offsets.
56 // 2018-09-05 updates to accommodate ModelComponentSets: BodySet, JointSet,
57 //    ConstraintSet, ForceSet, ProbeSet, WrapObjectSet, ...
58 const int expectedNumComponents = 200;
59 const int expectedNumJointsWithStateVariables = 2;
60 // 2018-08-22 added 2 for JointSet and ForceSet that contain Components with states
61 const int expectedNumModelComponentsWithStateVariables = 12;
62 // Below updated from 1 to 7 to account for offset frame and its geometry and
63 // wrapobjectset that are now part of the Joint
64 const int expectedNumJntComponents = 7;
65 // Test using the iterator to skip over every other Component (Frame in this case)
66 // nf = 1 ground + 2 bodies + 4 joint offsets = 7, skipping - 3 = 4
67 const int expectedNumCountSkipFrames = 4;
68 
69 namespace OpenSim {
70 
71 class Device : public ModelComponent {
72     OpenSim_DECLARE_CONCRETE_OBJECT(Device, ModelComponent);
73 }; // end of Device
74 
75 } // namespace OpenSim
76 
77 
testNestedComponentListConsistency()78 void testNestedComponentListConsistency() {
79     using SimTK::Vec3;
80     using SimTK::Inertia;
81 
82     Model model(modelFilename);
83 
84     auto device = new OpenSim::Device();
85     device->setName("device");
86 
87     auto humerus = new OpenSim::Body("device_humerus", 1, Vec3(0), Inertia(0));
88     auto radius  = new OpenSim::Body("device_radius",  1, Vec3(0), Inertia(0));
89 
90     auto shoulder = new OpenSim::PinJoint("device_shoulder",
91                                           model.getGround(), Vec3(0), Vec3(0),
92                                           *humerus, Vec3(0, 1, 0), Vec3(0));
93     auto elbow = new OpenSim::PinJoint("device_elbow",
94                                        *humerus, Vec3(0), Vec3(0),
95                                        *radius, Vec3(0, 1, 0), Vec3(0));
96 
97     device->addComponent(shoulder);
98     device->addComponent(elbow);
99 
100     model.addModelComponent(device);
101 
102     std::vector<const Joint*> joints1{}, joints2{};
103     std::set<const Coordinate*> coords{};
104 
105     std::cout << "Joints in the model: " << std::endl;
106     for(const auto& joint : model.getComponentList<Joint>()) {
107         std::cout << "    " << joint.getAbsolutePathString() << std::endl;
108         joints1.push_back(&joint);
109     }
110 
111     std::cout << "Joints and Coordinates: " << std::endl;
112     for(const auto& joint : model.getComponentList<Joint>()) {
113         joints2.push_back(&joint);
114         std::cout << "    Joint: " << joint.getAbsolutePathString() << std::endl;
115         for(const auto& coord : joint.getComponentList<Coordinate>()) {
116             std::cout << "        Coord: "
117                       << coord.getAbsolutePathString() << std::endl;
118             coords.insert(&coord);
119         }
120     }
121 
122     // Joints list should be a unique set.
123     ASSERT(std::set<const Joint*>{joints1.begin(), joints1.end()}.size() == 4);
124     // Joints1 and Joints2 must be identical.
125     ASSERT(joints1 == joints2);
126     // Expected number of unique coordinates.
127     ASSERT(coords.size() == 4);
128 }
129 
testComponentListConst()130 void testComponentListConst() {
131 
132     Model model(modelFilename);
133     model.printSubcomponentInfo();
134 
135     ComponentList<const Component> componentsList = model.getComponentList();
136     cout << "list begin: " << componentsList.begin()->getName() << endl;
137     int numComponents = 0;
138     for (ComponentList<const Component>::const_iterator
139             it = componentsList.begin(); it != componentsList.end();  ++it) {
140         cout << "Iterator is at: " << it->getAbsolutePathString() <<
141             " <" << it->getConcreteClassName() << ">" << endl;
142         numComponents++;
143         // it->setName("this line should not compile; using const_iterator.");
144     }
145 
146     ComponentList<const OpenSim::Body> bodiesList =
147         model.getComponentList<OpenSim::Body>();
148 
149     int numBodies = 0;
150     cout << "Bodies list begin: " << bodiesList.begin()->getName() << endl;
151     for (ComponentList<OpenSim::Body>::const_iterator
152             it = bodiesList.begin(); it != bodiesList.end(); ++it) {
153         cout << "Iterator is at Body: " << it->getName() << endl;
154         numBodies++;
155     }
156     // Now we try the post increment variant of the iterator
157     cout << "Bodies list begin, using post increment: "
158         << bodiesList.begin()->getName() << endl;
159     int numBodiesPost = 0;
160     for (ComponentList<OpenSim::Body>::const_iterator
161             itPost = bodiesList.begin(); itPost != bodiesList.end(); itPost++) {
162         cout << "Iterator is at Body: " << itPost->getName() << endl;
163         numBodiesPost++;
164     }
165 
166     int numMuscles = 0;
167     cout << "Using range-for loop over Muscles: " << endl;
168     ComponentList<const Muscle> musclesList = model.getComponentList<Muscle>();
169     for (const Muscle& muscle : musclesList) {
170         cout << "Iterator is at muscle: " << muscle.getName() << endl;
171         numMuscles++;
172     }
173 
174     int numGeomPaths = 0;
175     ComponentList<const GeometryPath> geomPathList =
176         model.getComponentList<GeometryPath>();
177     for (const GeometryPath& gpath : geomPathList) {
178         (void)gpath; // Suppress unused variable warning.
179         numGeomPaths++;
180     }
181     const OpenSim::Joint& shoulderJnt = model.getJointSet().get(0);
182     // cycle through components under shoulderJnt should return the Joint
183     // and the Coordinate
184     int numJntComponents = 0;
185     ComponentList<const Component> jComponentsList =
186         shoulderJnt.getComponentList();
187     cout << "Components/subComponents under Shoulder Joint:" << endl;
188     for (ComponentList<Component>::const_iterator
189             it = jComponentsList.begin(); it != jComponentsList.end(); ++it) {
190         cout << "Iterator is at: " << it->getConcreteClassName() << " "
191             << it->getAbsolutePathString() << endl;
192         numJntComponents++;
193     }
194     cout << "Num all components = " << numComponents << endl;
195     cout << "Num bodies = " << numBodies << endl;
196     cout << "Num Muscles = " << numMuscles << endl;
197     cout << "Num GeometryPath components = " << numGeomPaths << endl;
198     // Components = Model + 3Body + 3Marker + 2(Joint+Coordinate)
199     //              + 6(Muscle+GeometryPath)
200 
201     // To test states we must have added the components to the system
202     // which is done when the model creates and initializes the system
203     SimTK::State state = model.initSystem();
204 
205     unsigned numJoints{}, numCoords{};
206     for(const auto& joint : model.getComponentList<Joint>()) {
207         cout << "Joint: " << joint.getAbsolutePathString() << endl;
208         ++numJoints;
209         for(const auto& coord : joint.getComponentList<Coordinate>()) {
210             cout << "Coord: " << coord.getAbsolutePathString() << endl;
211             ++numCoords;
212         }
213     }
214     ASSERT(numJoints == 2);
215     ASSERT(numCoords == 2);
216 
217     int numJointsWithStateVariables = 0;
218     ComponentList<const Joint> jointsWithStates =
219         model.getComponentList<Joint>();
220     ComponentWithStateVariables myFilter;
221     jointsWithStates.setFilter(myFilter);
222     for (const Joint& comp : jointsWithStates) {
223         cout << comp.getConcreteClassName() << ":"
224             << comp.getAbsolutePathString() << endl;
225         numJointsWithStateVariables++;
226     }
227 
228     int numModelComponentsWithStateVariables = 0;
229     ComponentList<const ModelComponent> comps =
230         model.getComponentList<ModelComponent>();
231     comps.setFilter(myFilter);
232     for (const ModelComponent& comp : comps) {
233         cout << comp.getConcreteClassName() << ":"
234             << comp.getAbsolutePathString() << endl;
235         numModelComponentsWithStateVariables++;
236     }
237 
238     cout << "numModelComponentsWithStateVariables ="
239         << numModelComponentsWithStateVariables << endl;
240 
241     //Now test a std::iterator method
242     ComponentList<const Frame> allFrames = model.getComponentList<Frame>();
243     ComponentList<Frame>::const_iterator skipIter = allFrames.begin();
244     int countSkipFrames = 0;
245     while (skipIter != allFrames.end()){
246         cout << skipIter->getConcreteClassName() << ":"
247             << skipIter->getAbsolutePathString() << endl;
248         std::advance(skipIter, 2);
249         countSkipFrames++;
250     }
251 
252     ASSERT(numComponents == expectedNumComponents,
253             "", 0, "Number of Components mismatch");
254     ASSERT(numBodies == model.getNumBodies(), "", 0, "Number of Bodies mismatch");
255     ASSERT(numBodiesPost == numBodies, "", 0, "Number of Bodies post mismatch");
256     ASSERT(numMuscles == model.getMuscles().getSize(), "", 0,
257         "Number of Muscles mismatch");
258     ASSERT(numJointsWithStateVariables == expectedNumJointsWithStateVariables,
259             "", 0, "Number of Joints with StateVariables mismatch");
260     ASSERT(numModelComponentsWithStateVariables ==
261            expectedNumModelComponentsWithStateVariables, "", 0,
262         "Number of Components with StateVariables mismatch");
263     ASSERT(numJntComponents == expectedNumJntComponents, "", 0,
264         "Number of Components within Joints mismatch");
265     ASSERT(countSkipFrames == expectedNumCountSkipFrames, "", 0,
266         "Number of Frames skipping every other one, mismatch");
267 }
268 
269 // This test repeats the same tests as testComponentListConst(), but using
270 // updComponentList() instead, and focusing on using const iterators.
271 // That is, one still cannot
272 // modify the components, even though we are using updComponentList(). It's
273 // important that, even if you have a ComponentList() that does not allow
274 // modifying the components, it still can provide const access to the
275 // components.
testComponentListNonConstWithConstIterator()276 void testComponentListNonConstWithConstIterator() {
277     Model model(modelFilename);
278 
279     // Making this a const ComponentList causes us to use the const
280     // begin()/end() methods, which return const_iterators, and thus avoids
281     // the need for implicit conversion of non-const iterator to const_iterator.
282     const ComponentList<Component> componentsList = model.updComponentList();
283     std::cout << "list begin: " << componentsList.begin()->getName() << std::endl;
284     int numComponents = 0;
285     for (ComponentList<Component>::const_iterator it = componentsList.begin();
286             it != componentsList.end();
287             ++it) {
288         std::cout << "Iterator is at: " << it->getAbsolutePathString() << " <" << it->getConcreteClassName() << ">" << std::endl;
289         numComponents++;
290         // it->setName("this line should not compile; using const_iterator.");
291     }
292 
293     ComponentList<OpenSim::Body> bodiesList = model.updComponentList<OpenSim::Body>();
294     int numBodies = 0;
295     // Test the cbegin()/cend() variants, which avoid the implicit conversion
296     // from iterator to const_iterator.
297     for (ComponentList<OpenSim::Body>::const_iterator it = bodiesList.cbegin();
298             it != bodiesList.cend();
299             ++it) {
300         std::cout << "Iterator is at Body: " << it->getName() << std::endl;
301         numBodies++;
302     }
303     // Now we try the post increment variant of the iterator
304     std::cout << "Bodies list begin, using post increment: "
305               << bodiesList.begin()->getName() << std::endl;
306     int numBodiesPost = 0;
307     for (ComponentList<OpenSim::Body>::const_iterator itPost = bodiesList.begin();
308             itPost != bodiesList.end();
309             itPost++) {
310         std::cout << "Iterator is at Body: " << itPost->getName() << std::endl;
311         numBodiesPost++;
312     }
313 
314     int numMuscles = 0;
315     std::cout << "Using range-for loop over Muscles: " << std::endl;
316     // Make the ComponentList const to force the range for loop to use the
317     // const iterator.
318     const ComponentList<Muscle> musclesList = model.updComponentList<Muscle>();
319     for (const Muscle& muscle : musclesList) {
320         std::cout << "Iterator is at muscle: " << muscle.getName() << std::endl;
321         numMuscles++;
322     }
323 
324     int numGeomPaths = 0;
325     // Make the ComponentList const to force the range for loop to use the
326     // const iterator.
327     const ComponentList<GeometryPath> geomPathList = model.updComponentList<GeometryPath>();
328     for (const GeometryPath& gpath : geomPathList) {
329         (void)gpath; // Suppress unused variable warning.
330         numGeomPaths++;
331     }
332     OpenSim::Joint& shoulderJnt = model.getJointSet().get(0);
333     // cycle through components under shoulderJnt should return the Joint
334     // itself and the Coordinate
335     int numJntComponents = 0;
336     // This test relies on the implicit conversion from iterator to
337     // const_iterator.
338     ComponentList<Component> jComponentsList = shoulderJnt.updComponentList();
339     std::cout << "Components/subComponents under Shoulder Joint:" << std::endl;
340     for (ComponentList<Component>::const_iterator it = jComponentsList.begin();
341         it != jComponentsList.end();
342         ++it) {
343         std::cout << "Iterator is at: " << it->getConcreteClassName() << " " << it->getAbsolutePathString() << std::endl;
344         numJntComponents++;
345     }
346     cout << "Num all components = " << numComponents << std::endl;
347     cout << "Num bodies = " << numBodies << std::endl;
348     cout << "Num Muscles = " << numMuscles << std::endl;
349     cout << "Num GeometryPath components = " << numGeomPaths << std::endl;
350 
351     // To test states we must have added the components to the system
352     // which is done when the model creates and initializes the system
353     SimTK::State state = model.initSystem();
354 
355     int numJointsWithStateVariables = 0;
356     ComponentList<Joint> jointsWithStates = model.updComponentList<Joint>();
357     ComponentWithStateVariables myFilter;
358     jointsWithStates.setFilter(myFilter);
359     for (const Joint& comp : jointsWithStates) {
360         cout << comp.getConcreteClassName() << ":" << comp.getAbsolutePathString() << endl;
361         numJointsWithStateVariables++;
362     }
363     int numModelComponentsWithStateVariables = 0;
364     ComponentList<ModelComponent> comps = model.updComponentList<ModelComponent>();
365     comps.setFilter(myFilter);
366     for (const ModelComponent& comp : comps) {
367         cout << comp.getConcreteClassName() << ":" << comp.getAbsolutePathString() << endl;
368         numModelComponentsWithStateVariables++;
369     }
370     //Now test a std::iterator method
371     ComponentList<Frame> allFrames = model.updComponentList<Frame>();
372     // This line uses implicit conversion from iterator to const_iterator.
373     ComponentList<Frame>::const_iterator skipIter = allFrames.begin();
374     int countSkipFrames = 0;
375     while (skipIter != allFrames.end()){
376         cout << skipIter->getConcreteClassName() << ":" << skipIter->getAbsolutePathString() << endl;
377         std::advance(skipIter, 2);
378         countSkipFrames++;
379     }
380 
381     ASSERT(numComponents == expectedNumComponents);
382     ASSERT(numBodies == model.getNumBodies());
383     ASSERT(numBodiesPost == numBodies);
384     ASSERT(numMuscles == model.getMuscles().getSize());
385     ASSERT(numJointsWithStateVariables == expectedNumJointsWithStateVariables);
386     ASSERT(numModelComponentsWithStateVariables ==
387            expectedNumModelComponentsWithStateVariables);
388     ASSERT(numJntComponents == expectedNumJntComponents);
389     ASSERT(countSkipFrames == expectedNumCountSkipFrames);
390 
391 
392     // It is not possible to convert const_iterator to (non-const) iterator.
393     {
394         // Lines are commented out b/c they don't compile. I (Chris) uncommented
395         // them during development of the non-const iterators to check that
396         // these lines do not compile.
397         ComponentList<Body> mutBodyList = model.updComponentList<Body>();
398         // ComponentList<Body>::iterator itBody = mutBodyList.cbegin();
399         // Also does not work with an abstract type.
400         ComponentList<Component> mutCompList = model.updComponentList<Component>();
401         // ComponentList<Component>::iterator itComp = mutCompList.cbegin();
402     }
403 
404 }
405 
406 // Similar to testComponentListNonConstWithNonConstIterator, except that we
407 // allow modifying the elements of the list.
testComponentListNonConstWithNonConstIterator()408 void testComponentListNonConstWithNonConstIterator() {
409     Model model(modelFilename);
410 
411     ComponentList<Component> componentsList = model.updComponentList();
412     int numComponents = 0;
413     for (ComponentList<Component>::iterator it = componentsList.begin();
414             it != componentsList.end();
415             ++it) {
416         numComponents++;
417         it->setName(it->getName());
418     }
419 
420     ComponentList<OpenSim::Body> bodiesList = model.updComponentList<OpenSim::Body>();
421     int numBodies = 0;
422     for (ComponentList<OpenSim::Body>::iterator it = bodiesList.begin();
423             it != bodiesList.end();
424             ++it) {
425         numBodies++;
426         it->setName(it->getName());
427     }
428     // Now we try the post increment variant of the iterator
429     int numBodiesPost = 0;
430     for (ComponentList<OpenSim::Body>::iterator itPost = bodiesList.begin();
431             itPost != bodiesList.end();
432             itPost++) {
433         numBodiesPost++;
434         itPost->setName(itPost->getName());
435     }
436 
437     int numMuscles = 0;
438     ComponentList<Muscle> musclesList = model.updComponentList<Muscle>();
439     for (Muscle& muscle : musclesList) {
440         numMuscles++;
441         muscle.setName(muscle.getName());
442     }
443 
444     int numGeomPaths = 0;
445     ComponentList<GeometryPath> geomPathList = model.updComponentList<GeometryPath>();
446     for (GeometryPath& gpath : geomPathList) {
447         (void)gpath; // Suppress unused variable warning.
448         numGeomPaths++;
449         gpath.setName(gpath.getName());
450     }
451     OpenSim::Joint& shoulderJnt = model.getJointSet().get(0);
452     // cycle through components under shoulderJnt should return the Joint
453     // itself and the Coordinate
454     int numJntComponents = 0;
455     ComponentList<Component> jComponentsList = shoulderJnt.updComponentList();
456     for (ComponentList<Component>::iterator it = jComponentsList.begin();
457             it != jComponentsList.end();
458             ++it) {
459         numJntComponents++;
460         it->setName(it->getName());
461     }
462     cout << "Num all components = " << numComponents << std::endl;
463     cout << "Num bodies = " << numBodies << std::endl;
464     cout << "Num Muscles = " << numMuscles << std::endl;
465     cout << "Num GeometryPath components = " << numGeomPaths << std::endl;
466 
467     // To test states we must have added the components to the system
468     // which is done when the model creates and initializes the system
469     SimTK::State state = model.initSystem();
470 
471     int numJointsWithStateVariables = 0;
472     ComponentList<Joint> jointsWithStates = model.updComponentList<Joint>();
473     ComponentWithStateVariables myFilter;
474     jointsWithStates.setFilter(myFilter);
475     for (Joint& comp : jointsWithStates) {
476         numJointsWithStateVariables++;
477         comp.setName(comp.getName());
478     }
479     int numModelComponentsWithStateVariables = 0;
480     ComponentList<ModelComponent> comps = model.updComponentList<ModelComponent>();
481     comps.setFilter(myFilter);
482     for (ModelComponent& comp : comps) {
483         numModelComponentsWithStateVariables++;
484         comp.setName(comp.getName());
485     }
486     //Now test a std::iterator method
487     ComponentList<Frame> allFrames = model.updComponentList<Frame>();
488     ComponentList<Frame>::iterator skipIter = allFrames.begin();
489     int countSkipFrames = 0;
490     while (skipIter != allFrames.end()){
491         skipIter->setName(skipIter->getName());
492         std::advance(skipIter, 2);
493         countSkipFrames++;
494     }
495 
496     ASSERT(numComponents == expectedNumComponents);
497     ASSERT(numBodies == model.getNumBodies());
498     ASSERT(numBodiesPost == numBodies);
499     ASSERT(numMuscles == model.getMuscles().getSize());
500     ASSERT(numJointsWithStateVariables == expectedNumJointsWithStateVariables);
501 
502     cout << "numModelComponentsWithStateVariables ="
503         << numModelComponentsWithStateVariables << endl;
504 
505     ASSERT(numModelComponentsWithStateVariables ==
506            expectedNumModelComponentsWithStateVariables);
507     ASSERT(numJntComponents == expectedNumJntComponents);
508     ASSERT(countSkipFrames == expectedNumCountSkipFrames);
509 }
510 
511 // Ensure that we can compare const_iterator and (non-const) iterator.
testComponentListComparisonOperators()512 void testComponentListComparisonOperators() {
513     Model model(modelFilename);
514 
515     ComponentList<Body> list = model.updComponentList<Body>();
516     ComponentList<Body>::const_iterator constIt = list.cbegin();
517     ComponentList<Body>::iterator mutIt = list.begin();
518     SimTK_TEST(constIt == mutIt);
519     // It's important to switch the operands, since operator==() is invoked
520     // on the first operand, and we want to ensure both iterators can be
521     // used as the first operand.
522     SimTK_TEST(mutIt == constIt);
523     SimTK_TEST(constIt.equals(mutIt));
524     // No support for mutIt.equals(constIt); (no implicit conversion from
525     // const to non-const iterator.
526 
527     ++mutIt;
528     SimTK_TEST(constIt != mutIt);
529     SimTK_TEST(mutIt != constIt);
530 }
531 
main()532 int main() {
533     LoadOpenSimLibrary("osimActuators");
534     SimTK_START_TEST("testIterators");
535         SimTK_SUBTEST(testComponentListConst);
536         SimTK_SUBTEST(testComponentListNonConstWithConstIterator);
537         SimTK_SUBTEST(testComponentListNonConstWithNonConstIterator);
538         SimTK_SUBTEST(testComponentListComparisonOperators);
539         SimTK_SUBTEST(testNestedComponentListConsistency);
540     SimTK_END_TEST();
541 }
542 
543 
544 
545