1 /* -------------------------------------------------------------------------- *
2  *                OpenSim:  testComponentInterface.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): Ajay Seth, Ayman Habib                                          *
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 #include <OpenSim/Auxiliary/auxiliaryTestFunctions.h>
24 #include <OpenSim/Common/Component.h>
25 #include <OpenSim/Common/Reporter.h>
26 #include <OpenSim/Common/TableSource.h>
27 #include <OpenSim/Common/STOFileAdapter.h>
28 #include <OpenSim/Common/CommonUtilities.h>
29 #include <simbody/internal/SimbodyMatterSubsystem.h>
30 #include <simbody/internal/GeneralForceSubsystem.h>
31 #include <simbody/internal/Force.h>
32 #include <simbody/internal/MobilizedBody_Pin.h>
33 #include <simbody/internal/MobilizedBody_Ground.h>
34 
35 using namespace OpenSim;
36 using namespace std;
37 using namespace SimTK;
38 
39 
40 class Foo;
41 class Bar;
42 
43 class Sub : public Component {
44     OpenSim_DECLARE_CONCRETE_OBJECT(Sub, Component);
45 public:
46     OpenSim_DECLARE_OUTPUT_FOR_STATE_VARIABLE(subState);
47     Sub() = default;
48     virtual ~Sub() = default;
49 private:
extendAddToSystem(MultibodySystem & system) const50     void extendAddToSystem(MultibodySystem &system) const override {
51         Super::extendAddToSystem(system);
52         addStateVariable("subState", Stage::Dynamics);
53     }
computeStateVariableDerivatives(const SimTK::State & s) const54     void computeStateVariableDerivatives(const SimTK::State& s) const override {
55         double deriv = exp(-2.0*s.getTime());
56         setStateVariableDerivativeValue(s, "subState", deriv);
57     }
58 }; //end class Sub
59 
60 class TheWorld : public Component {
61     OpenSim_DECLARE_CONCRETE_OBJECT(TheWorld, Component);
62 public:
TheWorld()63     TheWorld() : Component() { }
64 
TheWorld(const std::string & fileName,bool updFromXMLNode=false)65     TheWorld(const std::string& fileName, bool updFromXMLNode = false)
66         : Component(fileName, updFromXMLNode) {
67         // Propagate XML file values to properties
68         updateFromXMLDocument();
69         // add components listed as properties as sub components.
70         finalizeFromProperties();
71     }
72 
add(Component * comp)73     void add(Component* comp) {
74         addComponent(comp);
75         // Edit Sub
76         /*Sub& subc = */updMemberSubcomponent<Sub>(intSubix);
77     }
78 
79     // Top level connection method for this all encompassing component, TheWorld
connect()80     void connect() {
81         Super::finalizeConnections(*this);
82     }
buildUpSystem(MultibodySystem & system)83     void buildUpSystem(MultibodySystem& system) {
84         connect();
85         addToSystem(system);
86     }
87 
getMatterSubsystem() const88     const SimbodyMatterSubsystem& getMatterSubsystem() const { return *matter; }
updMatterSubsystem() const89     SimbodyMatterSubsystem& updMatterSubsystem() const { return *matter; }
90 
getForceSubsystem() const91     const GeneralForceSubsystem& getForceSubsystem() const { return *forces; }
updForceSubsystem() const92     GeneralForceSubsystem& updForceSubsystem() const { return *forces; }
93 
94 protected:
95     // Component interface implementation
extendAddToSystem(MultibodySystem & system) const96     void extendAddToSystem(MultibodySystem& system) const override {
97         if (system.hasMatterSubsystem()){
98             matter = system.updMatterSubsystem();
99         }
100         else{
101             // const Sub& subc = getMemberSubcomponent<Sub>(intSubix);
102 
103             SimbodyMatterSubsystem* old_matter = matter.release();
104             delete old_matter;
105             matter = new SimbodyMatterSubsystem(system);
106 
107             GeneralForceSubsystem* old_forces = forces.release();
108             delete old_forces;
109             forces = new GeneralForceSubsystem(system);
110 
111             SimTK::Force::UniformGravity gravity(*forces, *matter, Vec3(0, -9.816, 0));
112             fix = gravity.getForceIndex();
113 
114             system.updMatterSubsystem().setShowDefaultGeometry(true);
115         }
116     }
117 
118 private:
119     // Keep track of pointers to the underlying computational subsystems.
120     mutable ReferencePtr<SimbodyMatterSubsystem> matter;
121     mutable ReferencePtr<GeneralForceSubsystem> forces;
122 
123     // keep track of the force added by the component
124     mutable ForceIndex fix;
125 
126     MemberSubcomponentIndex intSubix{ constructSubcomponent<Sub>("internalSub") };
127 
128 }; // end of TheWorld
129 
130 
131 class Foo : public Component {
132     OpenSim_DECLARE_CONCRETE_OBJECT(Foo, Component);
133 public:
134 //=============================================================================
135 // PROPERTIES
136 //=============================================================================
137     OpenSim_DECLARE_PROPERTY(mass, double, "mass (kg)");
138     OpenSim_DECLARE_LIST_PROPERTY_SIZE(inertia, double, 6,
139         "inertia {Ixx, Iyy, Izz, Ixy, Ixz, Iyz}");
140 
141     OpenSim_DECLARE_OUTPUT(Output1, double, getSomething, SimTK::Stage::Time);
142     OpenSim_DECLARE_OUTPUT(Output2, SimTK::Vec3, calcSomething,
143             SimTK::Stage::Time);
144 
145     OpenSim_DECLARE_OUTPUT(Output3, double, getSomethingElse, SimTK::Stage::Time);
146 
147     OpenSim_DECLARE_OUTPUT(Qs, Vector, getQ, SimTK::Stage::Position);
148 
149     OpenSim_DECLARE_OUTPUT(BodyAcc, SpatialVec, calcSpatialAcc,
150             SimTK::Stage::Velocity);
151 
152     OpenSim_DECLARE_OUTPUT(return_by_ref, double, getReturnByRef,
153             SimTK::Stage::Time);
154 
155     OpenSim_DECLARE_INPUT(input1, double, SimTK::Stage::Model, "");
156     OpenSim_DECLARE_INPUT(AnglesIn, Vector, SimTK::Stage::Model, "");
157     OpenSim_DECLARE_INPUT(fiberLength, double, SimTK::Stage::Model, "");
158     OpenSim_DECLARE_INPUT(activation, double, SimTK::Stage::Model, "");
159     OpenSim_DECLARE_LIST_INPUT(listInput1, double, SimTK::Stage::Model, "");
160 
Foo()161     Foo() : Component() {
162         constructProperties();
163         m_ctr = 0;
164         m_mutableCtr = 0;
165     }
166 
getSomething(const SimTK::State & state) const167     double getSomething(const SimTK::State& state) const {
168         const_cast<Foo *>(this)->m_ctr++;
169         m_mutableCtr++;
170 
171         return state.getTime();
172     }
173 
calcSomething(const SimTK::State & state) const174     SimTK::Vec3 calcSomething(const SimTK::State& state) const {
175         const_cast<Foo *>(this)->m_ctr++;
176         m_mutableCtr++;
177 
178         double t = state.getTime();
179         return SimTK::Vec3(t, t*t, sqrt(t));
180     }
181 
getSomethingElse(const SimTK::State & state) const182     double getSomethingElse(const SimTK::State& state) const {
183         return 1.618;
184     }
185 
getQ(const SimTK::State & state) const186     SimTK::Vector getQ(const SimTK::State& state) const {
187         return state.getQ();
188     }
189 
calcSpatialAcc(const SimTK::State & state) const190     SimTK::SpatialVec calcSpatialAcc(const SimTK::State& state) const {
191         const_cast<Foo *>(this)->m_ctr++;
192         m_mutableCtr++;
193 
194         return getSystem().getMatterSubsystem().getMobilizedBody(bindex)
195             .getBodyAcceleration(state);
196     }
197 
getReturnByRef(const SimTK::State & s) const198     const double& getReturnByRef(const SimTK::State& s) const {
199         // Must return something that is stored in the state!
200         return s.getTime();
201     }
202 
203 protected:
204     /** Component Interface */
extendFinalizeConnections(Component & root)205     void extendFinalizeConnections(Component& root) override {
206         Super::extendFinalizeConnections(root);
207         // do any internal wiring
208         world = dynamic_cast<TheWorld*>(&root);
209     }
210 
extendAddToSystem(MultibodySystem & system) const211     void extendAddToSystem(MultibodySystem &system) const override {
212         Super::extendAddToSystem(system);
213 
214         SimbodyMatterSubsystem& matter = system.updMatterSubsystem();
215 
216         Vec3 mInB(0.0, 1.0, 0);
217         Vec3 mInP(0, 0, 0);
218 
219         SimTK::Body::Rigid bone(
220             MassProperties(1, Vec3(0), Inertia::brick(0.5, 1, 0.5)));
221 
222         // Thigh connected by hip
223         MobilizedBody::Pin b1ToGround(matter.updGround(), SimTK::Transform(mInP),
224             bone, SimTK::Transform(mInB));
225 
226         //Pin knee connects shank
227         MobilizedBody::Pin b1ToB2(b1ToGround, SimTK::Transform(mInP),
228             bone, SimTK::Transform(mInB));
229 
230         bindex = b1ToB2.getMobilizedBodyIndex();
231     }
232 
233 private:
234     int m_ctr;
235     mutable int m_mutableCtr;
236 
237 
constructProperties()238     void constructProperties() {
239         constructProperty_mass(1.0);
240         Array<double> inertia(0.001, 6);
241         inertia[0] = inertia[1] = inertia[2] = 0.1;
242         constructProperty_inertia(inertia);
243     }
244 
245     // Keep indices and reference to the world
246     mutable MobilizedBodyIndex bindex;
247     ReferencePtr<TheWorld> world;
248 
249 }; // End of class Foo
250 
251 class Bar : public Component {
252     OpenSim_DECLARE_CONCRETE_OBJECT(Bar, Component);
253 public:
254 
255     OpenSim_DECLARE_SOCKET(parentFoo, Foo, "");
256     OpenSim_DECLARE_SOCKET(childFoo, Foo, "");
257 
258     // This is used to test output copying and returns the address of the
259     // component.
260     OpenSim_DECLARE_OUTPUT(copytesting, size_t, myself, SimTK::Stage::Model);
261     // Use this member variable to ensure that output functions get copied
262     // correctly.
263     double copytestingViaMemberVariable = 5;
264     OpenSim_DECLARE_OUTPUT(copytestingMemVar, double, getCopytestingMemVar,
265                            SimTK::Stage::Model);
266 
267     OpenSim_DECLARE_OUTPUT(PotentialEnergy, double, getPotentialEnergy,
268             SimTK::Stage::Velocity);
269 
270     OpenSim_DECLARE_OUTPUT_FOR_STATE_VARIABLE(fiberLength);
271     OpenSim_DECLARE_OUTPUT_FOR_STATE_VARIABLE(activation);
272 
getPotentialEnergy(const SimTK::State & state) const273     double getPotentialEnergy(const SimTK::State& state) const {
274         const GeneralForceSubsystem& forces = world->getForceSubsystem();
275         const SimTK::Force& force = forces.getForce(fix);
276         const auto& spring = SimTK::Force::TwoPointLinearSpring::downcast(force);
277 
278         return spring.calcPotentialEnergyContribution(state);
279     }
280 
281     /** Returns the `this` pointer. Used to ensure that the std::function
282      within Outputs is properly copied when copying components. */
myself(const SimTK::State & s) const283     size_t myself(const SimTK::State& s) const { return size_t(this); }
284 
getCopytestingMemVar(const SimTK::State & s) const285     double getCopytestingMemVar(const SimTK::State& s) const
286     { return copytestingViaMemberVariable; }
287 
288     class ParentAndFooAreSame : OpenSim::Exception {
289     public:
290         using OpenSim::Exception::Exception;
291     };
292 protected:
293     /** Component Interface */
extendFinalizeConnections(Component & root)294     void extendFinalizeConnections(Component& root) override{
295         Super::extendFinalizeConnections(root);
296         // do any internal wiring
297         world = dynamic_cast<TheWorld*>(&root);
298         // perform custom checking
299         if (&updSocket<Foo>("parentFoo").getConnectee()
300                 == &updSocket<Foo>("childFoo").getConnectee()){
301             string msg = "ERROR - Bar::extendFinalizeConnections()\n";
302             msg += " parentFoo and childFoo cannot be the same component.";
303             throw ParentAndFooAreSame(msg);
304         }
305     }
306 
307     // Copied here from Component for testing purposes.
308 
309 
extendAddToSystem(MultibodySystem & system) const310     void extendAddToSystem(MultibodySystem& system) const override{
311 
312         GeneralForceSubsystem& forces = world->updForceSubsystem();
313         SimbodyMatterSubsystem& matter = world->updMatterSubsystem();
314 
315         int nb = matter.getNumBodies();
316         if (nb > 2) {
317             const MobilizedBody& b1 = matter.getMobilizedBody(MobilizedBodyIndex(1));
318             const MobilizedBody& b2 = matter.getMobilizedBody(MobilizedBodyIndex(2));
319 
320             SimTK::Force::TwoPointLinearSpring
321                 spring(forces, b1, Vec3(0.5,0,0), b2, Vec3(0.5,0,0), 10.0, 0.1);
322             fix = spring.getForceIndex();
323         }
324 
325         // We use these to test the Output's that are generated when we
326         // add a StateVariable.
327         addStateVariable("fiberLength", SimTK::Stage::Velocity);
328         addStateVariable("activation", SimTK::Stage::Dynamics);
329 
330         // Create a hidden state variable, so we can ensure that hidden state
331         // variables do not have a corresponding Output.
332         bool hidden = true;
333         addStateVariable("hiddenStateVar", SimTK::Stage::Dynamics, hidden);
334     }
335 
computeStateVariableDerivatives(const SimTK::State & state) const336     void computeStateVariableDerivatives(const SimTK::State& state) const override {
337         setStateVariableDerivativeValue(state, "fiberLength", 2.0);
338         setStateVariableDerivativeValue(state, "activation", 3.0 * state.getTime());
339         setStateVariableDerivativeValue(state, "hiddenStateVar",
340                                           exp(-0.5 * state.getTime()));
341     }
342 
343 private:
344 
345     // keep track of the force added by the component
346     mutable ForceIndex fix;
347     ReferencePtr<TheWorld> world;
348 
349 }; // End of class Bar
350 
351 // Create 2nd level derived class to verify that Component interface
352 // holds up.
353 class CompoundFoo : public Foo {
354     OpenSim_DECLARE_CONCRETE_OBJECT(CompoundFoo, Foo);
355 public:
356     //=============================================================================
357     // PROPERTIES
358     //=============================================================================
359     OpenSim_DECLARE_PROPERTY(Foo1, Foo, "1st Foo of CompoundFoo");
360     OpenSim_DECLARE_PROPERTY(Foo2, Foo, "2nd Foo of CompoundFoo");
361     OpenSim_DECLARE_PROPERTY(scale1, double, "Scale factor for 1st Foo");
362     OpenSim_DECLARE_PROPERTY(scale2, double, "Scale factor for 2nd Foo");
363 
CompoundFoo()364     CompoundFoo() : Foo() {
365         constructProperties();
366     }
367 
368 protected:
369     // Component implementation interface
extendFinalizeFromProperties()370     void extendFinalizeFromProperties() override {
371         // Allow Foo to do its finalize from properties
372         Super::extendFinalizeFromProperties();
373 
374         // Mark components listed in properties as subcomponents
375         Foo& foo1 = upd_Foo1();
376         Foo& foo2 = upd_Foo2();
377 
378         // update CompoundFoo's properties based on it sub Foos
379         double orig_mass = get_mass();
380         upd_mass() = get_scale1()*foo1.get_mass() + get_scale2()*foo2.get_mass();
381 
382         double inertiaScale = (get_mass() / orig_mass);
383 
384         for (int i = 0; i < updProperty_inertia().size(); ++i) {
385             upd_inertia(i) = inertiaScale*get_inertia(i);
386         }
387     }
388 
389 private:
constructProperties()390     void constructProperties() {
391         constructProperty_Foo1(Foo());
392         constructProperty_Foo2(Foo());
393         constructProperty_scale1(1.0);
394         constructProperty_scale2(2.0);
395     }
396 }; // End of Class CompoundFoo
397 
398 SimTK_NICETYPENAME_LITERAL(Foo);
399 SimTK_NICETYPENAME_LITERAL(Bar);
400 
testMisc()401 void testMisc() {
402     // Define the Simbody system
403     MultibodySystem system;
404 
405     TheWorld theWorld;
406     theWorld.setName("World");
407     theWorld.finalizeFromProperties();
408 
409     // ComponentHasNoSystem exception should be thrown if user attempts to read
410     // or write state, discrete, or cache variables before Component has an
411     // underlying MultibodySystem.
412     {
413         SimTK::State sBlank;
414         const std::string varName = "waldo"; //dummy name
415 
416         ASSERT_THROW(ComponentHasNoSystem,
417                 theWorld.traverseToStateVariable(varName));
418         ASSERT_THROW(ComponentHasNoSystem, theWorld.getNumStateVariables());
419         ASSERT_THROW(ComponentHasNoSystem, theWorld.getStateVariableNames());
420         ASSERT_THROW(ComponentHasNoSystem,
421             theWorld.getStateVariableValue(sBlank, varName));
422         ASSERT_THROW(ComponentHasNoSystem,
423             theWorld.setStateVariableValue(sBlank, varName, 0.));
424         ASSERT_THROW(ComponentHasNoSystem,
425             theWorld.getStateVariableValues(sBlank));
426         ASSERT_THROW(ComponentHasNoSystem,
427             theWorld.setStateVariableValues(sBlank, SimTK::Vector(1, 0.)));
428         ASSERT_THROW(ComponentHasNoSystem,
429             theWorld.getStateVariableDerivativeValue(sBlank, varName));
430         ASSERT_THROW(ComponentHasNoSystem,
431             theWorld.getDiscreteVariableValue(sBlank, varName));
432         ASSERT_THROW(ComponentHasNoSystem,
433             theWorld.setDiscreteVariableValue(sBlank, varName, 0.));
434         ASSERT_THROW(ComponentHasNoSystem,
435             theWorld.getCacheVariableValue<double>(sBlank, varName));
436         ASSERT_THROW(ComponentHasNoSystem,
437             theWorld.setCacheVariableValue(sBlank, varName, 0.));
438         ASSERT_THROW(ComponentHasNoSystem,
439             theWorld.updCacheVariableValue<double>(sBlank, varName));
440         ASSERT_THROW(ComponentHasNoSystem,
441             theWorld.markCacheVariableValid(sBlank, varName));
442         ASSERT_THROW(ComponentHasNoSystem,
443             theWorld.markCacheVariableInvalid(sBlank, varName));
444         ASSERT_THROW(ComponentHasNoSystem,
445             theWorld.isCacheVariableValid(sBlank, varName));
446     }
447 
448     TheWorld* cloneWorld = theWorld.clone();
449     cloneWorld->setName("ClonedWorld");
450     cloneWorld->finalizeFromProperties();
451 
452     TheWorld copyWorld(theWorld);
453     copyWorld.setName("CopiedWorld");
454     copyWorld.finalizeFromProperties();
455 
456     const Sub& theSub = theWorld.getComponent<Sub>("internalSub");
457     const Sub& cloneSub = cloneWorld->getComponent<Sub>("internalSub");
458     const Sub& copySub = copyWorld.getComponent<Sub>("internalSub");
459 
460     // The clone and copy intern Sub components should be different
461     // allocation (address) from original internal Sub
462     ASSERT(&theSub != &cloneSub);
463     ASSERT(&theSub != &copySub);
464     // But their contents/values should be identical
465     ASSERT(theSub == cloneSub);
466     ASSERT(theSub == copySub);
467 
468     // let component add its stuff to the system
469     Foo& foo = *new Foo();
470     foo.setName("Foo");
471     theWorld.add(&foo);
472     foo.set_mass(2.0);
473 
474     // Foo* footTest = foo.clone();
475 
476     // bar0 is to test copying of the function within a component's outputs.
477     std::unique_ptr<Bar> bar0(new Bar());
478     Bar& bar = *bar0->clone();
479     bar.copytestingViaMemberVariable = 6;
480     bar.setName("Bar");
481     theWorld.add(&bar);
482 
483     Bar barEqual(bar);
484     ASSERT(barEqual == bar);
485 
486     //Configure the socket to look for its dependency by this name
487     //Will get resolved and connected automatically at Component connect
488     bar.updSocket<Foo>("parentFoo").setConnecteePath(
489             foo.getAbsolutePathString());
490     bar.connectSocket_childFoo(foo);
491 
492     // add a subcomponent
493     // connect internals
494     ASSERT_THROW( Bar::ParentAndFooAreSame,
495                   theWorld.connect() );
496 
497 
498     auto worldTreeAsList = theWorld.getComponentList();
499     std::cout << "list begin: " << worldTreeAsList.begin()->getName() << std::endl;
500     for (auto it = worldTreeAsList.begin();
501               it != worldTreeAsList.end(); ++it) {
502         std::cout << "Iterator is at: " << it->getAbsolutePathString() << std::endl;
503     }
504 
505 
506     std::cout << "Using range-for loop: " << std::endl;
507     for (const Component& component : worldTreeAsList) {
508         std::cout << "Iterator is at: " << component.getAbsolutePathString() << std::endl;
509     }
510 
511 
512     std::cout << "Iterate over only Foo's." << std::endl;
513     for (auto& component : theWorld.getComponentList<Foo>()) {
514         std::cout << "Iterator is at: " << component.getAbsolutePathString() << std::endl;
515     }
516 
517     Foo& foo2 = *new Foo();
518     foo2.setName("Foo2");
519     foo2.set_mass(3.0);
520 
521     theWorld.add(&foo2);
522 
523     std::cout << "Iterate over Foo's after adding Foo2." << std::endl;
524     for (auto& component : theWorld.getComponentList<Foo>()) {
525         std::cout << "Iter at: " << component.getAbsolutePathString() << std::endl;
526     }
527 
528     // Query existing components.
529     theWorld.printComponentsMatching("");
530     SimTK_TEST(theWorld.hasComponent("Foo"));
531     SimTK_TEST(!theWorld.hasComponent("Nonexistent"));
532     SimTK_TEST(theWorld.hasComponent<Foo>("Foo"));
533     SimTK_TEST(!theWorld.hasComponent<Bar>("Foo"));
534     SimTK_TEST(!theWorld.hasComponent<Foo>("Nonexistent"));
535 
536 
537     bar.connectSocket_childFoo(foo2);
538     string socketName = bar.updSocket<Foo>("childFoo").getName();
539 
540     // do any other input/output connections
541     foo.connectInput_input1(bar.getOutput("PotentialEnergy"));
542 
543     // Bar should connect now
544     theWorld.connect();
545     theWorld.buildUpSystem(system);
546 
547     const Foo& foo2found = theWorld.getComponent<Foo>("Foo2");
548     ASSERT(foo2 == foo2found);
549 
550     // check how this model serializes
551     string modelFile("testComponentInterfaceModel.osim");
552     theWorld.print(modelFile);
553 
554     // Simbody model state setup
555     State s = system.realizeTopology();
556 
557     // int nu = system.getMatterSubsystem().getNumMobilities();
558 
559     //SimTK::Visualizer viz(system);
560     //viz.drawFrameNow(s);
561     const Vector q = Vector(s.getNQ(), SimTK::Pi/2);
562     const Vector u = Vector(s.getNU(), 1.0);
563 
564     // Ensure the "this" pointer inside the output function is for the
565     // correct Bar.
566     system.realize(s, Stage::Model);
567     // Since bar0 is not part of any "world", we must call
568     // finalizeFromProperties() on it ourselves in order to set the
569     // "owner" of its outputs.
570     bar0->finalizeFromProperties();
571     // If bar's copytesting output is 0, then the following tests will pass
572     // accidentally.
573     SimTK_TEST(bar.getOutputValue<size_t>(s, "copytesting") != 0);
574     // Make sure bar's outputs don't point to bar0.
575     SimTK_TEST(bar.getOutputValue<size_t>(s, "copytesting") != size_t(bar0.get()));
576     // Make sure bar's outputs are using bar underneath.
577     SimTK_TEST(bar.getOutputValue<size_t>(s, "copytesting") == size_t(&bar));
578     SimTK_TEST(bar0->getOutputValue<double>(s, "copytestingMemVar") == 5);
579     SimTK_TEST(bar.getOutputValue<double>(s, "copytestingMemVar") == 6);
580 
581     // By deleting bar0 then calling getOutputValue on bar without a
582     // segfault (throughout the remaining code), we ensure that bar
583     // does not depend on bar0.
584     bar0.reset(nullptr);
585 
586 
587     for (int i = 0; i < 10; ++i){
588         s.updTime() = i*0.01234;
589         s.updQ() = (i+1)*q/10.0;
590         system.realize(s, Stage::Velocity);
591 
592         const AbstractOutput& out1 = foo.getOutput("Output1");
593         const AbstractOutput& out2 = foo.getOutput("Output2");
594         const AbstractOutput& out3 = foo.getOutput("Qs");
595         const AbstractOutput& out4 = foo.getOutput("BodyAcc");
596         const AbstractOutput& out5 = bar.getOutput("PotentialEnergy");
597 
598         cout << "=========================[Time " << s.getTime() << "s]======================="<<endl;
599         cout << out1.getName() <<"|"<< out1.getTypeName() <<"|"<< out1.getValueAsString(s) << endl;
600         cout << out2.getName() <<"|"<< out2.getTypeName() <<"|"<< out2.getValueAsString(s) << endl;
601         cout << out3.getName() <<"|"<< out3.getTypeName() <<"|"<< out3.getValueAsString(s) << endl;
602 
603         system.realize(s, Stage::Acceleration);
604         cout << out4.getName() <<"|"<< out4.getTypeName() <<"|"<< out4.getValueAsString(s) << endl;
605         cout << out5.getName() <<"|"<< out5.getTypeName() <<"|"<< out5.getValueAsString(s) << endl;
606 
607         //viz.report(s);
608         system.realize(s, Stage::Report);
609 
610         cout << "foo.input1 = " << foo.getInputValue<double>(s, "input1") << endl;
611     }
612 
613     // Test the output that returns by const T&.
614     SimTK_TEST(foo.getOutputValue<double>(s, "return_by_ref") == s.getTime());
615 
616     MultibodySystem system2;
617     TheWorld *world2 = new TheWorld(modelFile, true);
618 
619     world2->updComponent("Bar").getSocket<Foo>("childFoo");
620     // We haven't called connect yet, so this connection isn't made yet.
621     SimTK_TEST_MUST_THROW_EXC(
622             world2->updComponent("Bar").getConnectee<Foo>("childFoo"),
623             OpenSim::Exception
624              );
625 
626     ASSERT(theWorld == *world2, __FILE__, __LINE__,
627         "Model serialization->deserialization FAILED");
628 
629     world2->setName("InternalWorld");
630     world2->connect();
631 
632     world2->updComponent("Bar").getSocket<Foo>("childFoo");
633     ASSERT("Foo2" ==
634             world2->updComponent("Bar").getConnectee<Foo>("childFoo").getName());
635 
636     world2->buildUpSystem(system2);
637     s = system2.realizeTopology();
638 
639     world2->print("clone_" + modelFile);
640 
641     // Test copy assignment
642     TheWorld world3;
643     world3 = *world2;
644 
645     ASSERT(&world3 != world2, __FILE__, __LINE__,
646         "Model copy assignment FAILED: A copy was not made.");
647 
648     world3.finalizeFromProperties();
649 
650     ASSERT(world3 == *world2, __FILE__, __LINE__,
651         "Model copy assignment FAILED: Property values are not identical.");
652 
653     world3.getComponent("Bar").getSocket<Foo>("parentFoo");
654 
655     auto& barInWorld3 = world3.getComponent<Bar>("Bar");
656     auto& barInWorld2 = world2->getComponent<Bar>("Bar");
657     ASSERT(&barInWorld3 != &barInWorld2, __FILE__, __LINE__,
658         "Model copy assignment FAILED: property was not copied but "
659         "assigned the same memory");
660 
661     world3.setName("World3");
662 
663     // Add second world as the internal model of the first
664     theWorld.add(world2);
665     theWorld.connect();
666 
667     Bar& bar2 = *new Bar();
668     bar2.setName("bar2");
669     CompoundFoo& compFoo = *new CompoundFoo();
670     compFoo.setName("BigFoo");
671 
672     // setting Foo's creates copies that are now part of CompoundFoo
673     compFoo.set_Foo1(foo);
674     compFoo.set_Foo2(foo2);
675     compFoo.finalizeFromProperties();
676 
677     world3.add(&compFoo);
678     world3.add(&bar2);
679 
680     //Configure the socket to look for its dependency by this name
681     //Will get resolved and connected automatically at Component connect
682     bar2.updSocket<Foo>("parentFoo").setConnecteePath(
683             compFoo.getRelativePathString(bar2));
684 
685     bar2.connectSocket_childFoo(compFoo.get_Foo1());
686     compFoo.upd_Foo1().updInput("input1")
687         .connect(bar2.getOutput("PotentialEnergy"));
688 
689     world3.connect();
690     world3.print("Compound_" + modelFile);
691 
692     cout << "Adding world3 to theWorld" << endl;
693     theWorld.add(world3.clone());
694 
695     // Should not be able to add the same Component twice within the same tree
696     ASSERT_THROW( ComponentAlreadyPartOfOwnershipTree,
697                   world3.add(&bar2));
698 
699     cout << "Connecting theWorld:" << endl;
700     //theWorld.dumpSubcomponents();
701     theWorld.printSubcomponentInfo();
702     theWorld.printOutputInfo();
703     theWorld.finalizeFromProperties();
704     theWorld.connect();
705 
706     auto* reporter = new TableReporterVector();
707     reporter->set_report_time_interval(0.1);
708     reporter->connectInput_inputs(foo.getOutput("Qs"));
709     theWorld.add(reporter);
710 
711     // Connect our state variables.
712     foo.connectInput_fiberLength(bar.getOutput("fiberLength"));
713     foo.connectInput_activation(bar.getOutput("activation"));
714 
715     MultibodySystem system3;
716     cout << "Building theWorld's system:" << endl;
717     theWorld.buildUpSystem(system3);
718 
719     // Since hiddenStateVar is a hidden state variable, it has no
720     // corresponding output.
721     ASSERT_THROW( OpenSim::Exception,
722           /*const AbstractOutput& out = */bar.getOutput("hiddenStateVar") );
723 
724     s = system3.realizeTopology();
725 
726     bar.setStateVariableValue(s, "fiberLength", 1.5);
727     bar.setStateVariableValue(s, "activation", 0);
728 
729     // int nu3 = system3.getMatterSubsystem().getNumMobilities();
730 
731     // realize simbody system to velocity stage
732     system3.realize(s, Stage::Velocity);
733 
734     RungeKuttaFeldbergIntegrator integ(system3);
735     integ.setAccuracy(1.0e-3);
736 
737     TimeStepper ts(system3, integ);
738     ts.initialize(s);
739     ts.stepTo(1.0);
740     s = ts.getState();
741 
742     // realize simbody system to velocity stage
743     system3.realize(s, Stage::Velocity);
744 
745     // Get the results of integrating the system forward
746     const TimeSeriesTable_<Real>& results = reporter->getTable();
747     ASSERT(results.getNumRows() == 11, __FILE__, __LINE__,
748         "Number of rows in Reporter results not equal to number of time intervals.");
749     cout << "************** Contents of Table of Results ****************" << endl;
750     cout << results << endl;
751     cout << "***************** Qs Output at Final state *****************" << endl;
752     auto& finalVal = foo.getOutputValue<Vector>(s, "Qs");
753     (~finalVal).dump();
754     size_t ncols = results.getNumColumns();
755     ASSERT(ncols == static_cast<size_t>(finalVal.size()), __FILE__, __LINE__,
756         "Number of cols in Reporter results not equal to size of Output'Qs' size.");
757 
758     // Check the result of the integration on our state variables.
759     ASSERT_EQUAL(3.5, bar.getOutputValue<double>(s, "fiberLength"), 1e-10);
760     ASSERT_EQUAL(1.5, bar.getOutputValue<double>(s, "activation"), 1e-10);
761 
762     // Ensure the connection works.
763     ASSERT_EQUAL(3.5, foo.getInputValue<double>(s, "fiberLength"), 1e-10);
764     ASSERT_EQUAL(1.5, foo.getInputValue<double>(s, "activation"), 1e-10);
765 
766     theWorld.printSubcomponentInfo();
767     theWorld.printOutputInfo();
768 
769     std::cout << "Iterate over all Components in the world." << std::endl;
770     for (auto& component : theWorld.getComponentList<Component>()) {
771         std::cout << "Iterator is at: " << component.getAbsolutePathString() << std::endl;
772     }
773 
774     // Should fail to get Component when path is not specified
775     ASSERT_THROW(OpenSim::Exception,
776         theWorld.getComponent<CompoundFoo>("BigFoo") );
777 
778     // With path to the component it should work
779     auto& bigFoo = theWorld.getComponent<CompoundFoo>("World3/BigFoo");
780     // const Sub& topSub = theWorld.getComponent<Sub>("InternalWorld/internalSub");
781 
782     // Should also be able to get top-level
783     auto& topFoo = theWorld.getComponent<Foo>("Foo2");
784     cout << "Top level Foo2 path name: " << topFoo.getAbsolutePathString() << endl;
785 
786     // And the leaf Foo2 from BigFoo
787     auto& leafFoo = bigFoo.getComponent<Foo>("Foo2");
788     cout << "Leaf level Foo2 path name: " << leafFoo.getAbsolutePathString() << endl;
789 
790     theWorld.print("Nested_" + modelFile);
791 }
792 
testThrowOnDuplicateNames()793 void testThrowOnDuplicateNames() {
794     TheWorld theWorld;
795     theWorld.setName("World");
796     theWorld.finalizeFromProperties();
797 
798     Foo* a = new Foo();
799     a->setName("A");
800 
801     Foo* b = new Foo();
802     b->setName("B");
803 
804     theWorld.addComponent(a);
805     theWorld.addComponent(b);
806 
807     b->setName("A");
808 
809     SimTK_TEST_MUST_THROW_EXC(
810         theWorld.finalizeFromProperties(),
811         OpenSim::SubcomponentsWithDuplicateName );
812 
813 }
814 
815 // In order to access subcomponents in a copy, One must invoke
816 // finalizeFromProperties() after copying. This test makes sure that you get an
817 // exception if you did not call finalizeFromProperties() before calling a
818 // method like getComponentList().
testExceptionsFinalizeFromPropertiesAfterCopy()819 void testExceptionsFinalizeFromPropertiesAfterCopy() {
820     TheWorld theWorld;
821     {
822         MultibodySystem system;
823         Foo* foo = new Foo();
824         theWorld.add(foo);
825     }
826     {
827         TheWorld copy = theWorld;
828         SimTK_TEST_MUST_THROW_EXC(copy.getComponentList(), ComponentIsAnOrphan);
829     }
830 }
831 
testListInputs()832 void testListInputs() {
833     MultibodySystem system;
834     TheWorld theWorld;
835     theWorld.setName("World");
836 
837     Foo& foo = *new Foo();
838     foo.setName("Foo");
839     theWorld.add(&foo);
840     foo.set_mass(2.0);
841 
842     Foo& foo2 = *new Foo();
843     foo2.setName("Foo2");
844     foo2.set_mass(3.0);
845     theWorld.add(&foo2);
846 
847     Bar& bar = *new Bar();
848     bar.setName("Bar");
849     theWorld.add(&bar);
850 
851     bar.connectSocket_parentFoo(foo);
852     bar.connectSocket_childFoo(foo2);
853 
854     auto* reporter = new ConsoleReporter();
855     reporter->setName("rep0");
856     theWorld.add(reporter);
857 
858     // wire up console reporter inputs to desired model outputs
859     reporter->connectInput_inputs(foo.getOutput("Output1"));
860     reporter->connectInput_inputs(bar.getOutput("PotentialEnergy"));
861     reporter->connectInput_inputs(bar.getOutput("fiberLength"));
862     reporter->connectInput_inputs(bar.getOutput("activation"));
863 
864     auto* tabReporter = new TableReporter();
865     tabReporter->setName("TableReporterMixedOutputs");
866     theWorld.add(tabReporter);
867 
868     // wire up table reporter inputs (using convenience method) to desired
869     // model outputs
870     tabReporter->addToReport(bar.getOutput("fiberLength"));
871     tabReporter->addToReport(bar.getOutput("activation"));
872     tabReporter->addToReport(foo.getOutput("Output1"));
873     tabReporter->addToReport(bar.getOutput("PotentialEnergy"));
874 
875     theWorld.connect();
876     theWorld.buildUpSystem(system);
877 
878     State s = system.realizeTopology();
879 
880     const Vector q = Vector(s.getNQ(), SimTK::Pi/2);
881     for (int i = 0; i < 10; ++i){
882         s.updTime() = i*0.01234;
883         s.updQ() = (i+1)*q/10.0;
884         system.realize(s, Stage::Report);
885     }
886 
887     cout << "  TableReporterMixedOutputs (contents)" << endl;
888     cout << tabReporter->getTable() << endl;
889 
890     tabReporter->clearTable();
891     ASSERT(tabReporter->getTable().getNumRows() == 0);
892 }
893 
testListSockets()894 void testListSockets() {
895     MultibodySystem system;
896     TheWorld theWorld;
897     theWorld.setName("world");
898 
899     Foo& foo = *new Foo(); foo.setName("foo"); foo.set_mass(2.0);
900     theWorld.add(&foo);
901 
902     Foo& foo2 = *new Foo(); foo2.setName("foo2"); foo2.set_mass(3.0);
903     theWorld.add(&foo2);
904 
905     Bar& bar = *new Bar(); bar.setName("bar");
906     theWorld.add(&bar);
907 
908     // Non-list sockets.
909     bar.connectSocket_parentFoo(foo);
910     bar.connectSocket_childFoo(foo2);
911 
912     // Ensure that calling connect() on bar's "parentFoo" doesn't increase
913     // its number of connectees.
914     bar.connectSocket_parentFoo(foo);
915     // TODO The "Already connected to 'foo'" is caught by `connect()`.
916     SimTK_TEST(bar.getSocket<Foo>("parentFoo").getNumConnectees() == 1);
917 
918     theWorld.connect();
919     theWorld.buildUpSystem(system);
920 
921     State s = system.realizeTopology();
922 
923     std::cout << bar.getConnectee<Foo>("parentFoo").get_mass() << std::endl;
924 
925     // TODO redo with the property list / the reference connect().
926 }
927 
testComponentPathNames()928 void testComponentPathNames()
929 {
930     Foo foo;
931     foo.setName("LegWithConstrainedFoot/foot");
932     // verify that this illegal name throws when we try to finalize
933     // the component from its property values
934     ASSERT_THROW(InvalidComponentName, foo.finalizeFromProperties());
935 
936     // Build using real components and assemble them
937     // into a tree and test the path names that are
938     // generated on the fly.
939     TheWorld top;
940     TheWorld otherTop;
941 
942     top.setName("Top");
943     otherTop.setName("OtherTop");
944 
945     TheWorld* A = new TheWorld();
946     TheWorld* B = new TheWorld();
947     TheWorld* C = new TheWorld();
948     TheWorld* D = new TheWorld();
949     TheWorld* E = new TheWorld();
950     A->setName("A");
951     B->setName("B");
952     C->setName("C");
953     D->setName("D");
954     E->setName("E");
955 
956     top.add(A);
957     A->add(B);
958     B->add(C);
959     A->add(D);
960     D->add(E);
961 
962     top.printSubcomponentInfo();
963     top.printOutputInfo();
964 
965     std::string absPathC = C->getAbsolutePathString();
966     ASSERT(absPathC == "/A/B/C");
967 
968     std::string absPathE = E->getAbsolutePathString();
969     ASSERT(absPathE == "/A/D/E");
970 
971     // Specific tests to relative path name facilities
972     std::string EWrtB = E->getRelativePathString(*B);
973     ASSERT(EWrtB == "../D/E"); // "/A/B/" as common
974 
975     std::string BWrtE = B->getRelativePathString(*E);
976     ASSERT(BWrtE == "../../B"); // "/A/" as common
977 
978     // null case component wrt itself
979     std::string fooWrtFoo = D->getRelativePathString(*D);
980     ASSERT(fooWrtFoo == "");
981 
982     std::string CWrtOtherTop = C->getRelativePathString(otherTop);
983     ASSERT(CWrtOtherTop == "A/B/C");
984 
985     std::string OtherTopWrtC = otherTop.getRelativePathString(*C);
986     ASSERT(OtherTopWrtC == "../../../");
987 
988     // Must specify a unique path to E
989     ASSERT_THROW(OpenSim::ComponentNotFoundOnSpecifiedPath,
990                  /*auto& eref = */top.getComponent("E") );
991 
992     auto& cref = top.getComponent(absPathC);
993     auto& eref = top.getComponent(absPathE);
994 
995     auto cFromE = cref.getRelativePathString(eref);
996     ASSERT(cFromE == "../../B/C");
997 
998     auto eFromC = eref.getRelativePathString(cref);
999     ASSERT(eFromC == "../../D/E");
1000 
1001     // verify that we can also navigate relative paths properly
1002     auto& eref2 = cref.getComponent(eFromC);
1003     ASSERT(eref2 == eref);
1004 
1005     Foo* foo1 = new Foo();
1006     foo1->setName("Foo1");
1007     Foo* foo2 = new Foo();
1008     foo2->setName("Foo2");
1009     Bar* bar2 = new Bar();
1010     bar2->setName("Bar2");
1011 
1012     A->add(foo1);
1013     A->add(foo2);
1014     A->add(bar2);
1015 
1016     TheWorld* F = A->clone();
1017     F->setName("F");
1018     top.add(F);
1019 
1020     top.printSubcomponentInfo();
1021     top.printOutputInfo();
1022 
1023     std::string fFoo1AbsPath =
1024         F->getComponent<Foo>("Foo1").getAbsolutePathString();
1025     std::string aBar2AbsPath =
1026         A->getComponent<Bar>("Bar2").getAbsolutePathString();
1027     auto bar2FromBarFoo =
1028         bar2->getRelativePathString(F->getComponent<Foo>("Foo1"));
1029 
1030     // Verify deep copy of subcomponents
1031     const Foo& foo1inA = top.getComponent<Foo>("/A/Foo1");
1032     const Foo& foo1inF = top.getComponent<Foo>("/F/Foo1");
1033     ASSERT(&foo1inA != &foo1inF);
1034 
1035     // double check that we have the original Foo foo1 in A
1036     ASSERT(&foo1inA == foo1);
1037 
1038     // This bar2 that belongs to A and connects the two foo2s
1039     bar2->connectSocket_parentFoo(*foo2);
1040     bar2->connectSocket_childFoo(F->getComponent<Foo>("Foo2"));
1041 
1042     // auto& foo2inF = bar2->getComponent<Foo>("../../F/Foo2");
1043 
1044     // now wire up bar2 that belongs to F and connect the
1045     // two foo1s one in A and other F
1046     auto& fbar2 = F->updComponent<Bar>("Bar2");
1047     ASSERT(&fbar2 != bar2);
1048 
1049     fbar2.connectSocket_parentFoo(*foo1);
1050     fbar2.updSocket<Foo>("childFoo")
1051             .setConnecteePath("../Foo1");
1052 
1053     top.printSubcomponentInfo();
1054     top.printOutputInfo();
1055     top.connect();
1056 }
1057 
testFindComponent()1058 void testFindComponent() {
1059     class A : public Component {
1060         OpenSim_DECLARE_CONCRETE_OBJECT(A, Component);
1061     public:
1062         A(const std::string& name) { setName(name); }
1063     };
1064     class B : public Component {
1065         OpenSim_DECLARE_CONCRETE_OBJECT(B, Component);
1066     public:
1067         OpenSim_DECLARE_SOCKET(socket_a, A, "");
1068         B(const std::string& name) { setName(name); }
1069     };
1070 
1071     // Build a model.
1072     A top("top");
1073     B* b1 = new B("b1");
1074     b1->connectSocket_socket_a(top);
1075     top.addComponent(b1);
1076     B* b2 = new B("b2");
1077     b1->addComponent(b2);
1078     B* b3 = new B("b3");
1079     b2->addComponent(b3);
1080 
1081     // Test findComponent().
1082     B* duplicate1 = new B("duplicate");
1083     b1->addComponent(duplicate1);
1084     B* duplicate2 = new B("duplicate");
1085     b2->addComponent(duplicate2);
1086     SimTK_TEST(top.findComponent("nonexistant") == nullptr);
1087     SimTK_TEST(top.findComponent("b1") == b1);
1088     SimTK_TEST(top.findComponent<B>("b1") == b1);
1089     SimTK_TEST(b3->findComponent("b1") == nullptr);
1090     SimTK_TEST(top.findComponent<A>("b1") == nullptr);
1091 
1092     SimTK_TEST_MUST_THROW_EXC(top.findComponent("duplicate"),
1093             OpenSim::Exception);
1094 
1095     // Test AbstractSocket::findAndConnect().
1096     b3->updSocket("socket_a").findAndConnect("top");
1097     SimTK_TEST(&b3->getConnectee<A>("socket_a") == &top);
1098     // Connectee has the wrong type.
1099     SimTK_TEST_MUST_THROW_EXC(b3->updSocket("socket_a").findAndConnect("b1"),
1100             OpenSim::Exception);
1101 
1102     // Test partial paths.
1103     A* a3 = new A("a3");
1104     b2->addComponent(a3);
1105     b3->updSocket("socket_a").findAndConnect("b2/a3");
1106     SimTK_TEST(&b3->getConnectee<A>("socket_a") == a3);
1107 
1108     // This works, even though a3 is not in b3.
1109     b3->updSocket("socket_a").findAndConnect("b3/a3");
1110     SimTK_TEST(&b3->getConnectee<A>("socket_a") == a3);
1111 }
1112 
testTraversePathToComponent()1113 void testTraversePathToComponent() {
1114     class A : public Component {
1115         OpenSim_DECLARE_CONCRETE_OBJECT(A, Component);
1116     public:
1117         A(const std::string& name) { setName(name); }
1118     };
1119     class B : public Component {
1120         OpenSim_DECLARE_CONCRETE_OBJECT(B, Component);
1121     public:
1122         B(const std::string& name) { setName(name); }
1123     };
1124 
1125     // Add lots of subcomponents to check the performance of
1126     // traversePathToSubcomponent().
1127     auto addLotsOfSubcomponents = [](Component& c) {
1128         for (int i = 0; i < 100; ++i) {
1129             c.addComponent(new A("unuseda" + std::to_string(i)));
1130             c.addComponent(new B("unusedb" + std::to_string(i)));
1131         }
1132     };
1133 
1134     A top("top");
1135     addLotsOfSubcomponents(top);
1136 
1137     A* a1 = new A("a1");
1138     addLotsOfSubcomponents(*a1);
1139     top.addComponent(a1);
1140     B* b1 = new B("b1");
1141     addLotsOfSubcomponents(*b1);
1142     top.addComponent(b1);
1143 
1144     A* a2 = new A("a2");
1145     addLotsOfSubcomponents(*a2);
1146     a1->addComponent(a2);
1147     B* b2 = new B("b2");
1148     addLotsOfSubcomponents(*b2);
1149     a1->addComponent(b2);
1150 
1151     // top.printSubcomponentInfo();
1152 
1153     // Self.
1154     SimTK_TEST(&top.getComponent<A>("") == &top);
1155     SimTK_TEST(&top.getComponent<A>(".") == &top);
1156     SimTK_TEST(&a1->getComponent<A>("") == a1);
1157     SimTK_TEST(&b2->getComponent<B>("") == b2);
1158 
1159     SimTK_TEST(&top.getComponent<A>("a1") == a1);
1160     SimTK_TEST(&top.getComponent<A>("a1/") == a1);
1161     SimTK_TEST(&top.getComponent<B>("b1") == b1);
1162     SimTK_TEST(&top.getComponent<A>("a1/a2") == a2);
1163     SimTK_TEST(&top.getComponent<B>("a1/b2") == b2);
1164     // Going up.
1165     SimTK_TEST(&a1->getComponent<A>("..") == &top);
1166     SimTK_TEST(&a2->getComponent<A>("../../") == &top);
1167     SimTK_TEST(&b2->getComponent<A>("../../") == &top);
1168     SimTK_TEST(&a2->getComponent<A>("..") == a1);
1169     SimTK_TEST(&b2->getComponent<A>("..") == a1);
1170     // Going up and then back down.
1171     SimTK_TEST(&a1->getComponent<A>("../a1") == a1);
1172     SimTK_TEST(&a1->getComponent<B>("../b1") == b1);
1173     SimTK_TEST(&a1->getComponent<B>("../a1/b2") == b2);
1174     // Absolute paths.
1175     SimTK_TEST(&top.getComponent<A>("/a1") == a1);
1176     SimTK_TEST(&b1->getComponent<B>("/a1/b2") == b2);
1177     SimTK_TEST(&b2->getComponent<A>("/a1/a2") == a2);
1178     SimTK_TEST(&top.getComponent<A>("/") == &top);
1179 
1180 
1181     // No component.
1182     // -------------
1183     // Incorrect path.
1184     SimTK_TEST_MUST_THROW(top.getComponent<A>("oops/a2"));
1185     SimTK_TEST_MUST_THROW(b1->getComponent("/nonexistent"));
1186     // Wrong type.
1187     SimTK_TEST_MUST_THROW(top.getComponent<B>("a1/a2"));
1188     // Going too high up.
1189     SimTK_TEST_MUST_THROW(top.getComponent<A>(".."));
1190     SimTK_TEST_MUST_THROW(top.getComponent<A>("../"));
1191     SimTK_TEST_MUST_THROW(top.getComponent<A>("../.."));
1192     SimTK_TEST_MUST_THROW(top.getComponent<A>("../../"));
1193     SimTK_TEST_MUST_THROW(a1->getComponent<A>("../../"));
1194     SimTK_TEST_MUST_THROW(b2->getComponent<A>("../../../"));
1195 
1196     // Repeated name bug.
1197     // ------------------
1198     // There used to be a bug where calling traversePathToComponent({"tx/tx"})
1199     // would return the component at "tx" rather than the one at "tx/tx".
1200     A* atx = new A("tx");
1201     top.addComponent(atx);
1202     B* btx = new B("tx");
1203     atx->addComponent(btx);
1204     SimTK_TEST(&top.getComponent<Component>("tx/tx") == btx);
1205 }
1206 
testGetStateVariableValue()1207 void testGetStateVariableValue() {
1208 
1209     TheWorld top;
1210     top.setName("top");
1211     Sub* a = new Sub();
1212     a->setName("a");
1213     Sub* b = new Sub();
1214     b->setName("b");
1215 
1216     top.add(a);
1217     a->addComponent(b);
1218 
1219     MultibodySystem system;
1220     top.buildUpSystem(system);
1221     State s = system.realizeTopology();
1222 
1223     SimTK_TEST(s.getNY() == 3);
1224     s.updY()[0] = 10; // "top/internalSub/subState"
1225     s.updY()[1] = 20; // "top/a/subState"
1226     s.updY()[2] = 30; // "top/a/b/subState"
1227 
1228     SimTK_TEST(top.getStateVariableValue(s, "internalSub/subState") == 10);
1229     SimTK_TEST(top.getStateVariableValue(s, "a/subState") == 20);
1230     SimTK_TEST(top.getStateVariableValue(s, "a/b/subState") == 30);
1231     SimTK_TEST(a->getStateVariableValue(s, "subState") == 20);
1232     SimTK_TEST(a->getStateVariableValue(s, "b/subState") == 30);
1233     SimTK_TEST(b->getStateVariableValue(s, "subState") == 30);
1234     SimTK_TEST(b->getStateVariableValue(s, "../subState") == 20);
1235     SimTK_TEST(b->getStateVariableValue(s, "../../internalSub/subState") == 10);
1236 
1237     SimTK_TEST_MUST_THROW_EXC(
1238             top.getStateVariableValue(s, "typo/b/subState"),
1239             OpenSim::Exception);
1240 }
1241 
testInputOutputConnections()1242 void testInputOutputConnections()
1243 {
1244     {
1245         TheWorld world;
1246         Foo* foo1 = new Foo();
1247         Foo* foo2 = new Foo();
1248         Bar* bar = new Bar();
1249 
1250         foo1->setName("foo1");
1251         foo2->setName("foo2");
1252         bar->setName("bar");
1253         bar->connectSocket_parentFoo(*foo1);
1254         bar->connectSocket_childFoo(*foo2);
1255 
1256         world.add(foo1);
1257         world.add(foo2);
1258         world.add(bar);
1259 
1260         MultibodySystem mbs;
1261 
1262         world.connect();
1263 
1264         // do any other input/output connections
1265         foo1->connectInput_input1(bar->getOutput("PotentialEnergy"));
1266 
1267         // Test various exceptions for inputs, outputs, sockets
1268         ASSERT_THROW(InputNotFound, foo1->getInput("input0"));
1269         ASSERT_THROW(SocketNotFound, bar->updSocket<Foo>("parentFoo0"));
1270         ASSERT_THROW(OutputNotFound,
1271             world.getComponent("./internalSub").getOutput("subState0"));
1272         // Ensure that getOutput does not perform a "find"
1273         ASSERT_THROW(OutputNotFound,
1274             world.getOutput("./internalSub/subState"));
1275 
1276         foo2->connectInput_input1(world.getComponent("./internalSub").getOutput("subState"));
1277 
1278         foo1->connectInput_AnglesIn(foo2->getOutput("Qs"));
1279         foo2->connectInput_AnglesIn(foo1->getOutput("Qs"));
1280 
1281         foo1->connectInput_activation(bar->getOutput("activation"));
1282         foo1->connectInput_fiberLength(bar->getOutput("fiberLength"));
1283 
1284         foo2->connectInput_activation(bar->getOutput("activation"));
1285         foo2->connectInput_fiberLength(bar->getOutput("fiberLength"));
1286 
1287         world.connect();
1288         world.buildUpSystem(mbs);
1289     }
1290     // Test exception message when asking for the value of an input that is
1291     // not wired up.
1292     class A : public Component { // Test single-value input.
1293         OpenSim_DECLARE_CONCRETE_OBJECT(A, Component);
1294     public:
1295         OpenSim_DECLARE_INPUT(in1, double, SimTK::Stage::Model, "");
1296     };
1297     class B : public Component { // Test list inputs.
1298         OpenSim_DECLARE_CONCRETE_OBJECT(B, Component);
1299     public:
1300         OpenSim_DECLARE_LIST_INPUT(in1, double, SimTK::Stage::Model, "");
1301     };
1302     class C : public Component {
1303         OpenSim_DECLARE_CONCRETE_OBJECT(C, Component);
1304     public:
1305         OpenSim_DECLARE_OUTPUT(out1, double, calcOut1, SimTK::Stage::Time);
1306         double calcOut1(const SimTK::State& state) const { return 0; }
1307     };
1308     {
1309         // Single-value input.
1310         TheWorld world;
1311         A* a = new A(); a->setName("a");
1312         world.add(a);
1313         MultibodySystem system;
1314         world.connect();
1315         world.buildUpSystem(system);
1316         State s = system.realizeTopology();
1317         system.realize(s, Stage::Model);
1318         SimTK_TEST_MUST_THROW_EXC(a->getInput<double>("in1").getValue(s),
1319                 InputNotConnected);
1320     }
1321     {
1322         // List input.
1323         // We must wire up an output to the input, as a list input with no
1324         // connectees is always "connected."
1325         TheWorld world;
1326         B* b = new B(); b->setName("b");
1327         C* c = new C(); c->setName("c");
1328         world.add(b);
1329         world.add(c);
1330         b->updInput("in1").connect(c->getOutput("out1"));
1331         MultibodySystem system;
1332         world.connect();
1333         world.buildUpSystem(system);
1334         State s = system.realizeTopology();
1335         system.realize(s, Stage::Model);
1336         // The following will work, now that the connection is satisfied.
1337         b->getInput<double>("in1").getValue(s, 0);
1338         // Disconnect to get the "not connected"exception.
1339         b->clearConnections();
1340         SimTK_TEST_MUST_THROW_EXC(b->getInput<double>("in1").getValue(s, 0),
1341                 InputNotConnected);
1342     }
1343 }
1344 
testInputConnecteePaths()1345 void testInputConnecteePaths() {
1346     {
1347         std::string componentPath, outputName, channelName, alias;
1348         AbstractInput::parseConnecteePath("/foo/bar|output",
1349                                           componentPath, outputName,
1350                                           channelName, alias);
1351         SimTK_TEST(componentPath == "/foo/bar");
1352         SimTK_TEST(outputName == "output");
1353         SimTK_TEST(channelName == "");
1354         SimTK_TEST(alias == "");
1355     }
1356     {
1357         std::string componentPath, outputName, channelName, alias;
1358         AbstractInput::parseConnecteePath("/foo/bar|output:channel",
1359                                           componentPath, outputName,
1360                                           channelName, alias);
1361         SimTK_TEST(componentPath == "/foo/bar");
1362         SimTK_TEST(outputName == "output");
1363         SimTK_TEST(channelName == "channel");
1364         SimTK_TEST(alias == "");
1365     }
1366     {
1367         std::string componentPath, outputName, channelName, alias;
1368         AbstractInput::parseConnecteePath("/foo/bar|output(baz)",
1369                                           componentPath, outputName,
1370                                           channelName, alias);
1371         SimTK_TEST(componentPath == "/foo/bar");
1372         SimTK_TEST(outputName == "output");
1373         SimTK_TEST(channelName == "");
1374         SimTK_TEST(alias == "baz");
1375     }
1376     {
1377         std::string componentPath, outputName, channelName, alias;
1378         AbstractInput::parseConnecteePath("/foo/bar|output:channel(baz)",
1379                                           componentPath, outputName,
1380                                           channelName, alias);
1381         SimTK_TEST(componentPath == "/foo/bar");
1382         SimTK_TEST(outputName == "output");
1383         SimTK_TEST(channelName == "channel");
1384         SimTK_TEST(alias == "baz");
1385     }
1386 
1387     // TODO test invalid names as well.
1388 }
1389 
testExceptionsForConnecteeTypeMismatch()1390 void testExceptionsForConnecteeTypeMismatch() {
1391     // Create Component classes for use in the following tests.
1392     // --------------------------------------------------------
1393     // This class has Outputs.
1394     class A : public Component {
1395         OpenSim_DECLARE_CONCRETE_OBJECT(A, Component);
1396     public:
1397         OpenSim_DECLARE_OUTPUT(out1, double, calcOut1, SimTK::Stage::Time);
1398         OpenSim_DECLARE_LIST_OUTPUT(outL, double, calcOutL, SimTK::Stage::Time);
1399         double calcOut1(const SimTK::State& state) const { return 0; }
1400         double calcOutL(const SimTK::State& state,
1401                         const std::string& channel) const { return 0; }
1402     private:
1403         void extendFinalizeFromProperties() override {
1404             // Register Channels for list output 'outL'.
1405             auto& outL = updOutput("outL");
1406             outL.addChannel("0"); outL.addChannel("1"); outL.addChannel("2");
1407         }
1408     };
1409     // This class has Inputs.
1410     class B : public Component {
1411         OpenSim_DECLARE_CONCRETE_OBJECT(B, Component);
1412     public:
1413         OpenSim_DECLARE_INPUT(in1, Vec3, SimTK::Stage::Model, "");
1414         OpenSim_DECLARE_LIST_INPUT(inL, Vec3, SimTK::Stage::Model, "");
1415     };
1416     // This class has a socket.
1417     class C : public Component {
1418         OpenSim_DECLARE_CONCRETE_OBJECT(C, Component);
1419     public:
1420         OpenSim_DECLARE_SOCKET(socket1, A, "");
1421     };
1422 
1423     // Test various type mismatches.
1424     // -----------------------------
1425     // First, check for exceptions when directly connecting inputs to outputs
1426     // (or sockets to connectees).
1427     { // Socket.
1428         TheWorld model;
1429         B* b = new B(); b->setName("b");
1430         C* c = new C(); c->setName("c");
1431         model.add(b); model.add(c);
1432         SimTK_TEST_MUST_THROW_EXC(c->updSocket("socket1").connect(*b),
1433                                   OpenSim::Exception);
1434     }
1435     { // single-value output -> single-value input.
1436         TheWorld model;
1437         A* a = new A(); a->setName("a");
1438         B* b = new B(); b->setName("b");
1439         model.add(a); model.add(b);
1440         SimTK_TEST_MUST_THROW_EXC(b->updInput("in1").connect(a->getOutput("out1")),
1441                                   OpenSim::Exception);
1442     }
1443     { // single-value output -> list input.
1444         TheWorld model;
1445         A* a = new A(); a->setName("a");
1446         B* b = new B(); b->setName("b");
1447         model.add(a); model.add(b);
1448         SimTK_TEST_MUST_THROW_EXC(b->updInput("inL").connect(a->getOutput("out1")),
1449                                   OpenSim::Exception);
1450     }
1451     { // list output -> single-value input.
1452         TheWorld model;
1453         A* a = new A(); a->setName("a");
1454         B* b = new B(); b->setName("b");
1455         model.add(a); model.add(b);
1456         SimTK_TEST_MUST_THROW_EXC(
1457             b->updInput("in1").connect(a->getOutput("outL").getChannel("0")),
1458             OpenSim::Exception);
1459     }
1460     { // list output -> list input.
1461         TheWorld model;
1462         A* a = new A(); a->setName("a");
1463         B* b = new B(); b->setName("b");
1464         model.add(a); model.add(b);
1465         SimTK_TEST_MUST_THROW_EXC(
1466             b->updInput("inL").connect(a->getOutput("outL").getChannel("1")),
1467             OpenSim::Exception);
1468     }
1469 
1470     // Now check for exceptions when setting the connectee_name property, then
1471     // connecting on the model (similar to deserializing an XML model file).
1472     { // Socket.
1473         TheWorld model;
1474         B* b = new B(); b->setName("b");
1475         C* c = new C(); c->setName("c");
1476         model.add(b); model.add(c);
1477         c->updSocket("socket1").setConnecteePath("../b");
1478         SimTK_TEST_MUST_THROW_EXC(model.connect(), OpenSim::Exception);
1479     }
1480     { // single-value output -> single-value input.
1481         TheWorld model;
1482         A* a = new A(); a->setName("a");
1483         B* b = new B(); b->setName("b");
1484         model.add(a); model.add(b);
1485         b->updInput("in1").setConnecteePath("../a/out1");
1486         SimTK_TEST_MUST_THROW_EXC(model.connect(), OpenSim::Exception);
1487     }
1488     { // single-value output -> list input.
1489         TheWorld model;
1490         A* a = new A(); a->setName("a");
1491         B* b = new B(); b->setName("b");
1492         model.add(a); model.add(b);
1493         b->updInput("inL").appendConnecteePath("../a/out1");
1494         SimTK_TEST_MUST_THROW_EXC(model.connect(), OpenSim::Exception);
1495     }
1496     { // list output -> single-value input.
1497         TheWorld model;
1498         A* a = new A(); a->setName("a");
1499         B* b = new B(); b->setName("b");
1500         model.add(a); model.add(b);
1501         b->updInput("in1").setConnecteePath("../a/outL:2");
1502         SimTK_TEST_MUST_THROW_EXC(model.connect(), OpenSim::Exception);
1503     }
1504     { // list output -> list input.
1505         TheWorld model;
1506         A* a = new A(); a->setName("a");
1507         B* b = new B(); b->setName("b");
1508         model.add(a); model.add(b);
1509         b->updInput("inL").appendConnecteePath("../a/outL:0");
1510         SimTK_TEST_MUST_THROW_EXC(model.connect(), OpenSim::Exception);
1511     }
1512 }
1513 
testExceptionsSocketNameExistsAlready()1514 void testExceptionsSocketNameExistsAlready() {
1515     // Make sure that it is not possible for a class to have more than one
1516     // socket with a given name, even if the connectee types are different.
1517 
1518     // We will use Z and Y as the connectee types.
1519     class Z : public Component
1520     {   OpenSim_DECLARE_CONCRETE_OBJECT(Z, Component); };
1521     class Y : public Component
1522     {   OpenSim_DECLARE_CONCRETE_OBJECT(Y, Component); };
1523 
1524     // A is the base class that has a socket named 'socket1', of type Z.
1525     class A : public Component {
1526         OpenSim_DECLARE_CONCRETE_OBJECT(A, Component);
1527     public:
1528         OpenSim_DECLARE_SOCKET(socket1, Z, "");
1529     };
1530 
1531     // BSame tries to reuse the name 'socket1', and also connect to type Z.
1532     class BSame : public A {
1533         OpenSim_DECLARE_CONCRETE_OBJECT(BSame, A);
1534     public:
1535         OpenSim_DECLARE_SOCKET(socket1, Z, "");
1536     };
1537 
1538     // BDifferent uses the same name 'socket1' but connects to a different type.
1539     class BDifferent : public A {
1540         OpenSim_DECLARE_CONCRETE_OBJECT(BDifferent, A);
1541     public:
1542         OpenSim_DECLARE_SOCKET(socket1, Y, "");
1543     };
1544 
1545     ASSERT_THROW_MSG(OpenSim::Exception,
1546             "BSame already has a socket named 'socket1'",
1547             BSame b;);
1548     ASSERT_THROW_MSG(OpenSim::Exception,
1549             "BDifferent already has a socket named 'socket1'",
1550             BDifferent b;);
1551 
1552     // The API user may try to create two sockets with the
1553     // same name in the same exact class (that is, not separated across the
1554     // inheritance hierarchy). We do not need to test this case, because it
1555     // leads to a compiling error (duplicate member variable).
1556 }
1557 
testExceptionsInputNameExistsAlready()1558 void testExceptionsInputNameExistsAlready() {
1559     // Make sure that it is not possible for a class to have more than one
1560     // input with a given name, even if the connectee types are different.
1561 
1562     { // Single-value input.
1563         class A : public Component {
1564             OpenSim_DECLARE_CONCRETE_OBJECT(A, Component);
1565         public:
1566             OpenSim_DECLARE_INPUT(in1, Vec3, SimTK::Stage::Model, "");
1567         };
1568 
1569         // BSame tries to reuse the name 'in1', and also connect to Vec3.
1570         class BSame : public A {
1571             OpenSim_DECLARE_CONCRETE_OBJECT(BSame, A);
1572         public:
1573             OpenSim_DECLARE_INPUT(in1, Vec3, SimTK::Stage::Model, "");
1574         };
1575 
1576         // BDifferent uses the same name 'in1' but connects to a different type.
1577         class BDifferent : public A {
1578             OpenSim_DECLARE_CONCRETE_OBJECT(BDifferent, A);
1579         public:
1580             OpenSim_DECLARE_INPUT(in1, double, SimTK::Stage::Model, "");
1581         };
1582 
1583         ASSERT_THROW_MSG(OpenSim::Exception,
1584                 "BSame already has an input named 'in1'",
1585                 BSame b;);
1586         ASSERT_THROW_MSG(OpenSim::Exception,
1587                 "BDifferent already has an input named 'in1'",
1588                 BDifferent b;);
1589     }
1590 
1591     { // List input.
1592         class A : public Component {
1593             OpenSim_DECLARE_CONCRETE_OBJECT(A, Component);
1594         public:
1595             OpenSim_DECLARE_LIST_INPUT(in1, Vec3, SimTK::Stage::Model, "");
1596         };
1597 
1598         // BSame tries to reuse the name 'in1', and also connect to Vec3.
1599         class BSame : public A {
1600             OpenSim_DECLARE_CONCRETE_OBJECT(BSame, A);
1601         public:
1602             OpenSim_DECLARE_LIST_INPUT(in1, Vec3, SimTK::Stage::Model, "");
1603         };
1604 
1605         // BDifferent uses the same name 'in1' but connects to a different type.
1606         class BDifferent : public A {
1607             OpenSim_DECLARE_CONCRETE_OBJECT(BDifferent, A);
1608         public:
1609             OpenSim_DECLARE_LIST_INPUT(in1, double, SimTK::Stage::Model, "");
1610         };
1611 
1612         ASSERT_THROW_MSG(OpenSim::Exception,
1613                 "BSame already has an input named 'in1'",
1614                 BSame b;);
1615         ASSERT_THROW_MSG(OpenSim::Exception,
1616                 "BDifferent already has an input named 'in1'",
1617                 BDifferent b;);
1618     }
1619 }
1620 
testExceptionsOutputNameExistsAlready()1621 void testExceptionsOutputNameExistsAlready() {
1622     // Make sure that it is not possible for a class to have more than one
1623     // output with a given name, even if the types are different.
1624 
1625     { // Single-value output.
1626         class A : public Component {
1627             OpenSim_DECLARE_CONCRETE_OBJECT(A, Component);
1628         public:
1629             OpenSim_DECLARE_OUTPUT(out1, double, calcOut1, SimTK::Stage::Time);
1630             double calcOut1(const SimTK::State& state) const { return 0; }
1631         };
1632 
1633         // BSame tries to reuse the name 'out1', and also uses type double.
1634         class BSame : public A {
1635             OpenSim_DECLARE_CONCRETE_OBJECT(BSame, A);
1636         public:
1637             OpenSim_DECLARE_OUTPUT(out1, double, calcOut1, SimTK::Stage::Time);
1638             double calcOut1(const SimTK::State& state) const { return 0; }
1639         };
1640 
1641         // BDifferent uses the same name 'out1' but uses a different type.
1642         class BDifferent : public A {
1643             OpenSim_DECLARE_CONCRETE_OBJECT(BDifferent, A);
1644         public:
1645             OpenSim_DECLARE_OUTPUT(out1, Vec3, calcOut1, SimTK::Stage::Time);
1646             Vec3 calcOut1(const SimTK::State& state) const { return Vec3(0); }
1647         };
1648 
1649         ASSERT_THROW_MSG(OpenSim::Exception,
1650                 "BSame already has an output named 'out1'",
1651                 BSame b;);
1652         ASSERT_THROW_MSG(OpenSim::Exception,
1653                 "BDifferent already has an output named 'out1'",
1654                 BDifferent b;);
1655     }
1656 
1657     { // List output.
1658         class A : public Component {
1659             OpenSim_DECLARE_CONCRETE_OBJECT(A, Component);
1660         public:
1661             OpenSim_DECLARE_LIST_OUTPUT(out1, double, calcOut1,
1662                                         SimTK::Stage::Time);
1663             double calcOut1(const SimTK::State& state,
1664                             const std::string&) const { return 0; }
1665         };
1666 
1667         // BSame tries to reuse the name 'out1', and also uses type double.
1668         class BSame : public A {
1669             OpenSim_DECLARE_CONCRETE_OBJECT(BSame, A);
1670         public:
1671             OpenSim_DECLARE_LIST_OUTPUT(out1, double, calcOut1,
1672                                         SimTK::Stage::Time);
1673             double calcOut1(const SimTK::State& state,
1674                             const std::string&) const { return 0; }
1675         };
1676 
1677         // BDifferent uses the same name 'out1' but uses a different type.
1678         class BDifferent : public A {
1679             OpenSim_DECLARE_CONCRETE_OBJECT(BDifferent, A);
1680         public:
1681             OpenSim_DECLARE_LIST_OUTPUT(out1, Vec3, calcOut1,
1682                                         SimTK::Stage::Time);
1683             Vec3 calcOut1(const SimTK::State& state,
1684                           const std::string&) const { return Vec3(0); }
1685         };
1686 
1687         ASSERT_THROW_MSG(OpenSim::Exception,
1688                 "BSame already has an output named 'out1'",
1689                 BSame b;);
1690         ASSERT_THROW_MSG(OpenSim::Exception,
1691                 "BDifferent already has an output named 'out1'",
1692                 BDifferent b;);
1693     }
1694 }
1695 
1696 template<typename RowVec>
assertEqual(const RowVec & a,const RowVec & b)1697 void assertEqual(const RowVec& a, const RowVec& b) {
1698     ASSERT(a.nrow() == b.nrow());
1699     ASSERT(a.ncol() == b.ncol());
1700     for(int i = 0; i < a.ncol(); ++i)
1701         ASSERT_EQUAL(a[i], b[i], 1e-10);
1702 }
1703 
testTableSource()1704 void testTableSource() {
1705     using namespace OpenSim;
1706     using namespace SimTK;
1707 
1708     {
1709         TheWorld model{};
1710         auto tablesource = new TableSourceVec3{};
1711         tablesource->setName("tablesource");
1712         tablesource->set_filename("dataWithEformat.trc");
1713         tablesource->set_tablename("markers");
1714         model.addComponent(tablesource);
1715 
1716         model.print("TestTableSource.osim");
1717     }
1718 
1719     {
1720     const std::string src_file{"TestTableSource.osim"};
1721     TheWorld model{src_file};
1722     const auto& tablesource =
1723         model.getComponent<TableSourceVec3>("tablesource");
1724     model.print("TestTableSourceResult.osim");
1725     // Read the model file again to verify serialization.
1726     TheWorld model_copy{"TestTableSourceResult.osim"};
1727     const auto& tablesource_copy =
1728         model_copy.getComponent<TableSourceVec3>("tablesource");
1729     OPENSIM_THROW_IF(tablesource_copy.get_filename() !=
1730                      tablesource.get_filename(),
1731                      OpenSim::Exception);
1732     }
1733 
1734     TimeSeriesTable table{};
1735     table.setColumnLabels({"0", "1", "2", "3"});
1736     SimTK::RowVector_<double> row{4, double{0}};
1737     for(unsigned i = 0; i < 4; ++i)
1738         table.appendRow(0.00 + 0.25 * i, row + i);
1739 
1740     std::cout << "Contents of the table :" << std::endl;
1741     std::cout << table << std::endl;
1742 
1743     auto tableSource = new TableSource{table};
1744 
1745     auto tableReporter = new TableReporter_<double, double>{};
1746 
1747     // Define the Simbody system
1748     MultibodySystem system;
1749 
1750     TheWorld theWorld;
1751     theWorld.setName("World");
1752 
1753     theWorld.add(tableSource);
1754     theWorld.add(tableReporter);
1755 
1756     tableReporter->addToReport(tableSource->getOutput("column"));
1757 
1758     theWorld.finalizeFromProperties();
1759 
1760     theWorld.connect();
1761     theWorld.buildUpSystem(system);
1762 
1763     const auto& report = tableReporter->getTable();
1764 
1765     State s = system.realizeTopology();
1766 
1767     s.setTime(0);
1768     tableReporter->report(s);
1769     assertEqual(table.getRowAtIndex(0)  , report.getRowAtIndex(0));
1770 
1771     s.setTime(0.1);
1772     tableReporter->report(s);
1773     row = RowVector_<double>{4, 0.4};
1774     assertEqual(row.getAsRowVectorView(), report.getRowAtIndex(1));
1775 
1776     s.setTime(0.25);
1777     tableReporter->report(s);
1778     assertEqual(table.getRowAtIndex(1)  , report.getRowAtIndex(2));
1779 
1780     s.setTime(0.4);
1781     tableReporter->report(s);
1782     row = RowVector_<double>{4, 1.6};
1783     assertEqual(row.getAsRowVectorView(), report.getRowAtIndex(3));
1784 
1785     s.setTime(0.5);
1786     tableReporter->report(s);
1787     assertEqual(table.getRowAtIndex(2)  , report.getRowAtIndex(4));
1788 
1789     s.setTime(0.6);
1790     tableReporter->report(s);
1791     row = RowVector_<double>{4, 2.4};
1792     assertEqual(row.getAsRowVectorView(), report.getRowAtIndex(5));
1793 
1794     s.setTime(0.75);
1795     tableReporter->report(s);
1796     assertEqual(table.getRowAtIndex(3)  , report.getRowAtIndex(6));
1797 
1798     std::cout << "Report: " << std::endl;
1799     std::cout << report << std::endl;
1800 }
1801 
testTableReporter()1802 void testTableReporter() {
1803     // TableReporter works fine even if its input has no connectees.
1804     {
1805         TheWorld model;
1806         auto* reporter = new TableReporter();
1807         reporter->set_report_time_interval(0.1);
1808         model.addComponent(reporter);
1809 
1810         MultibodySystem system;
1811         model.buildUpSystem(system);
1812 
1813         {
1814             SimTK::State s = system.realizeTopology();
1815             RungeKuttaFeldbergIntegrator integ(system);
1816             integ.setAccuracy(1.0e-3);
1817             TimeStepper ts(system, integ);
1818             ts.initialize(s);
1819             ts.stepTo(1.0);
1820             const auto& table = reporter->getTable();
1821             std::cout << "TableReporter table after simulating:\n"
1822                       << table.toString() << std::endl;
1823             SimTK_TEST_MUST_THROW_EXC(table.getDependentColumnAtIndex(0),
1824                                       EmptyTable);
1825         }
1826 
1827         // Ensure that clearing the table and performing a new simulation works
1828         // even if the reporter's input has no connectees.
1829         reporter->clearTable();
1830         std::cout << "TableReporter table after clearing:\n"
1831                   << reporter->getTable().toString() << std::endl;
1832         SimTK_TEST_MUST_THROW_EXC(
1833             reporter->getTable().getDependentColumnAtIndex(0),
1834             EmptyTable);
1835 
1836         {
1837             SimTK::State s = system.realizeTopology();
1838             RungeKuttaFeldbergIntegrator integ(system);
1839             integ.setAccuracy(1.0e-3);
1840             TimeStepper ts(system, integ);
1841             ts.initialize(s);
1842             ts.stepTo(1.0);
1843             const auto& table = reporter->getTable();
1844             std::cout << "TableReporter table after simulating again:\n"
1845                       << table.toString() << std::endl;
1846             SimTK_TEST_MUST_THROW_EXC(table.getDependentColumnAtIndex(0),
1847                                       EmptyTable);
1848         }
1849     }
1850 }
1851 
1852 const std::string dataFileNameForInputConnecteeSerialization =
1853         "testComponentInterface_testInputConnecteeSerialization_data.sto";
1854 
writeTimeSeriesTableForInputConnecteeSerialization()1855 void writeTimeSeriesTableForInputConnecteeSerialization() {
1856     TimeSeriesTable table{};
1857     table.setColumnLabels({"a", "b", "c", "d"});
1858     SimTK::RowVector row{4, 0.0}; row(1)=0.5; row(2)= 0.7; row(3)=0.8;
1859     for(unsigned i = 0; i < 4; ++i) table.appendRow(0.25 * i, row + i);
1860     STOFileAdapter_<double>::write(table,
1861                                    dataFileNameForInputConnecteeSerialization);
1862 }
1863 
testListInputConnecteeSerialization()1864 void testListInputConnecteeSerialization() {
1865     // We build a model, store the input connectee paths, then
1866     // recreate the same model from a serialization, and make sure the
1867     // connectee paths are the same.
1868 
1869     // Helper function.
1870     auto getConnecteePaths = [](const AbstractInput& in) {
1871         const auto numConnectees = in.getNumConnectees();
1872         std::vector<std::string> connecteePaths(numConnectees);
1873         for (unsigned ic = 0u; ic < numConnectees; ++ic) {
1874             connecteePaths[ic] = in.getConnecteePath(ic);
1875         }
1876         return connecteePaths;
1877     };
1878 
1879     // Build a model and serialize it.
1880     std::string modelFileName = "testComponentInterface_"
1881                                 "testListInputConnecteeSerialization_world.xml";
1882     std::vector<std::string> expectedConnecteePaths{
1883             "/producer|column:a",
1884             "/producer|column:c",
1885             "/producer|column:b(berry)"};
1886     SimTK::Vector expectedInputValues;
1887     {
1888         // Create the "model," which just contains a reporter.
1889         TheWorld world;
1890         world.setName("World");
1891 
1892         // TableSource.
1893         auto* source = new TableSource();
1894         source->setName("producer");
1895         source->set_filename(dataFileNameForInputConnecteeSerialization);
1896 
1897         // TableReporter.
1898         auto* reporter = new TableReporter();
1899         reporter->setName("consumer");
1900 
1901         // Add to world.
1902         world.add(source);
1903         world.add(reporter);
1904 
1905         // Connect, finalize, etc.
1906         const auto& output = source->getOutput("column");
1907         // See if we preserve the ordering of the channels.
1908         reporter->addToReport(output.getChannel("a"));
1909         reporter->addToReport(output.getChannel("c"));
1910         // We want to make sure aliases are preserved.
1911         reporter->addToReport(output.getChannel("b"), "berry");
1912         // Ensure that re-finalizing from properties does not cause Inputs
1913         // to hold onto stale references to the outputs' channels.
1914         world.finalizeFromProperties();
1915         world.connect();
1916         MultibodySystem system;
1917         world.buildUpSystem(system);
1918 
1919         // Grab the connectee paths.
1920         const auto& input = reporter->getInput("inputs");
1921         SimTK_TEST(getConnecteePaths(input) == expectedConnecteePaths);
1922 
1923         // Get the value of the input at some given time.
1924         State s = system.realizeTopology();
1925         system.realize(s, Stage::Model);
1926         s.setTime(0.3);
1927         expectedInputValues = Input<double>::downcast(input).getVector(s);
1928         SimTK_TEST(expectedInputValues.size() == 3);
1929 
1930         // Serialize.
1931         world.print(modelFileName);
1932     }
1933 
1934     // Deserialize and test.
1935     {
1936         TheWorld world(modelFileName);
1937         const auto& reporter = world.getComponent("consumer");
1938         const auto& input = reporter.getInput("inputs");
1939         SimTK_TEST(input.isListSocket());
1940         // Check connectee paths before *and* after connecting, since
1941         // the connecting process edits the connectee_name property.
1942         SimTK_TEST(getConnecteePaths(input) == expectedConnecteePaths);
1943         world.connect();
1944         SimTK_TEST(getConnecteePaths(input) == expectedConnecteePaths);
1945         // Check aliases.
1946         SimTK_TEST(input.getAlias(0) == ""); // default.
1947         SimTK_TEST(input.getAlias(1) == ""); // default.
1948         SimTK_TEST(input.getAlias(2) == "berry"); // specified.
1949 
1950         // Check that the value of the input is the same as before.
1951         MultibodySystem system;
1952         world.buildUpSystem(system);
1953         State s = system.realizeTopology();
1954         system.realize(s, Stage::Model);
1955         s.setTime(0.3);
1956         auto actualInputValues = Input<double>::downcast(input).getVector(s);
1957 
1958         SimTK_TEST_EQ(expectedInputValues, actualInputValues);
1959     }
1960 }
1961 
testSingleValueInputConnecteeSerialization()1962 void testSingleValueInputConnecteeSerialization() {
1963 
1964     // Test normal behavior of single-value input (de)serialization.
1965     // -------------------------------------------------------------
1966 
1967     // Build a model and serialize it.
1968     std::string modelFileName = "testComponentInterface_"
1969             "testSingleValueInputConnecteeSerialization_world.xml";
1970     double expectedInput1Value = SimTK::NaN;
1971     {
1972         // Create the "model," which just contains a reporter.
1973         TheWorld world;
1974         world.setName("World");
1975 
1976         // TableSource.
1977         auto* source = new TableSource();
1978         source->setName("producer");
1979         source->set_filename(dataFileNameForInputConnecteeSerialization);
1980 
1981         // TableReporter.
1982         auto* foo = new Foo();
1983         foo->setName("consumer");
1984         // Make sure we are dealing with single-value inputs
1985         // (future-proofing this test).
1986         SimTK_TEST(!foo->updInput("input1").isListSocket());
1987         SimTK_TEST(!foo->updInput("fiberLength").isListSocket());
1988 
1989         // Add to world.
1990         world.add(source);
1991         world.add(foo);
1992 
1993         // Connect, finalize, etc.
1994         const auto& output = source->getOutput("column");
1995         // See if we preserve the ordering of the channels.
1996         foo->connectInput_input1(output.getChannel("b"));
1997         // We want to make sure aliases are preserved.
1998         foo->connectInput_fiberLength(output.getChannel("d"), "desert");
1999         // Ensure that re-finalizing from properties does not cause Inputs
2000         // to hold onto stale references to the outputs' channels.
2001         world.finalizeFromProperties();
2002         world.connect();
2003         MultibodySystem system;
2004         world.buildUpSystem(system);
2005 
2006         // Get the value of the input at some given time.
2007         State s = system.realizeTopology();
2008         system.realize(s, Stage::Model);
2009         s.setTime(0.3);
2010         const auto& input1 = foo->getInput("input1");
2011         expectedInput1Value = Input<double>::downcast(input1).getValue(s);
2012 
2013         // We won't wire up this input, but its connectee path should still
2014         // (de)serialize.
2015         foo->updInput("activation").setConnecteePath("non/existent");
2016 
2017         // Serialize.
2018         world.print(modelFileName);
2019     }
2020 
2021     // Deserialize and test.
2022     {
2023         TheWorld world(modelFileName);
2024         auto& foo = world.updComponent("consumer");
2025         const auto& input1 = foo.getInput("input1");
2026         const auto& fiberLength = foo.getInput("fiberLength");
2027         auto& activation = foo.updInput("activation");
2028 
2029         // Make sure these inputs are single-value after deserialization,
2030         // even before connecting.
2031         SimTK_TEST(!input1.isListSocket());
2032         SimTK_TEST(!fiberLength.isListSocket());
2033         SimTK_TEST(!activation.isListSocket());
2034 
2035         // Check connectee paths before *and* after connecting, since
2036         // the connecting process edits the connectee_name property.
2037         SimTK_TEST(input1.getConnecteePath() == "/producer|column:b");
2038         SimTK_TEST(fiberLength.getConnecteePath() ==
2039                    "/producer|column:d(desert)");
2040         // Even if we hadn't wired this up, its name still deserializes:
2041         SimTK_TEST(activation.getConnecteePath() == "non/existent");
2042         // Now we must clear this before trying to connect, since the connectee
2043         // doesn't exist.
2044         activation.setConnecteePath("");
2045 
2046         // Connect.
2047         world.connect();
2048 
2049         // Make sure these inputs are single-value even after connecting.
2050         SimTK_TEST(!input1.isListSocket());
2051         SimTK_TEST(!fiberLength.isListSocket());
2052         SimTK_TEST(!activation.isListSocket());
2053 
2054         SimTK_TEST(input1.getConnecteePath() == "/producer|column:b");
2055         SimTK_TEST(fiberLength.getConnecteePath() ==
2056                    "/producer|column:d(desert)");
2057 
2058         // Check aliases.
2059         SimTK_TEST(input1.getAlias(0) == "");
2060         SimTK_TEST(fiberLength.getAlias(0) == "desert");
2061 
2062         // Check that the value of the input is the same as before.
2063         MultibodySystem system;
2064         world.buildUpSystem(system);
2065         State s = system.realizeTopology();
2066         system.realize(s, Stage::Model);
2067         s.setTime(0.3);
2068 
2069         SimTK_TEST_EQ(Input<double>::downcast(input1).getValue(s),
2070                       expectedInput1Value);
2071     }
2072 
2073     // Test error case: single-value input connectee_name has multiple values.
2074     // -----------------------------------------------------------------------
2075     // We'll first create an Input with multiple connectee_names (as is possible
2076     // in an XML file), then deserialize it and see what errors we get.
2077     std::string modelFileNameMultipleValues = "testComponentInterface_"
2078         "testSingleValueInputConnecteeSerializationMultipleValues_world.xml";
2079     {
2080         TheWorld world;
2081         auto* foo = new Foo();
2082         world.add(foo);
2083 
2084         // Hack into the Foo and modify its properties! The typical interface
2085         // for editing the input's connectee_name does not allow multiple
2086         // connectee paths for a single-value input.
2087         auto& connectee_name = Property<std::string>::updAs(
2088                         foo->updPropertyByName("input_input1"));
2089         connectee_name.setAllowableListSize(0, 10);
2090         connectee_name.appendValue("apple");
2091         connectee_name.appendValue("banana");
2092         connectee_name.appendValue("lemon");
2093 
2094         world.print(modelFileNameMultipleValues);
2095     }
2096     // Deserialize.
2097     {
2098         // Single-value connectee cannot have multiple connectee_names.
2099         SimTK_TEST_MUST_THROW_EXC(
2100              TheWorld world(modelFileNameMultipleValues),
2101              OpenSim::Exception);
2102     }
2103 
2104     // Test error case: connectee_name has invalid characters.
2105     // -------------------------------------------------------
2106     // This test is structured similarly to the one above.
2107     std::string modelFileNameInvalidChar = "testComponentInterface_"
2108         "testSingleValueInputConnecteeSerializationInvalidChar_world.xml";
2109     {
2110         TheWorld world;
2111         auto* foo = new Foo();
2112         world.add(foo);
2113         auto& input1 = foo->updInput("input1");
2114         input1.setConnecteePath("abc+def"); // '+' is invalid for ComponentPath.
2115         // The check for invalid names occurs in
2116         // AbstractSocket::checkConnecteePathProperty(), which is invoked
2117         // by the following function:
2118         SimTK_TEST_MUST_THROW_EXC(foo->finalizeFromProperties(),
2119                                   OpenSim::Exception);
2120         world.print(modelFileNameInvalidChar);
2121     }
2122     // Deserialize.
2123     {
2124         // Make sure that deserializing a Component with an invalid
2125         // connectee_name throws an exception.
2126         SimTK_TEST_MUST_THROW_EXC(TheWorld world(modelFileNameInvalidChar),
2127                                   OpenSim::Exception);
2128     }
2129 }
2130 
testAliasesAndLabels()2131 void testAliasesAndLabels() {
2132     auto theWorld = std::unique_ptr<TheWorld>(new TheWorld());
2133     theWorld->setName("world");
2134 
2135     Foo* foo = new Foo();  foo->setName("foo");
2136     Foo* bar = new Foo();  bar->setName("bar");
2137 
2138     theWorld->addComponent(foo);
2139     theWorld->addComponent(bar);
2140 
2141     ASSERT_THROW(InputNotConnected, foo->getInput("input1").getAlias());
2142     ASSERT_THROW(InputNotConnected, foo->getInput("input1").getAlias(0));
2143     ASSERT_THROW(InputNotConnected, foo->updInput("input1").setAlias("qux"));
2144     ASSERT_THROW(InputNotConnected, foo->updInput("input1").setAlias(0, "qux"));
2145     ASSERT_THROW(InputNotConnected, foo->getInput("input1").getLabel());
2146     ASSERT_THROW(InputNotConnected, foo->getInput("input1").getLabel(0));
2147 
2148     // Non-list Input, no alias.
2149     foo->connectInput_input1( bar->getOutput("Output1") );
2150     theWorld->connect();
2151     SimTK_TEST(foo->getInput("input1").getAlias().empty());
2152     SimTK_TEST(foo->getInput("input1").getLabel() == "/bar|Output1");
2153 
2154     // Set alias.
2155     foo->updInput("input1").setAlias("waldo");
2156     SimTK_TEST(foo->getInput("input1").getAlias() == "waldo");
2157     SimTK_TEST(foo->getInput("input1").getLabel() == "waldo");
2158 
2159     foo->updInput("input1").setAlias(0, "fred");
2160     SimTK_TEST(foo->getInput("input1").getAlias() == "fred");
2161     SimTK_TEST(foo->getInput("input1").getLabel() == "fred");
2162 
2163     using SimTKIndexOutOfRange = SimTK::Exception::IndexOutOfRange;
2164     ASSERT_THROW(SimTKIndexOutOfRange, foo->getInput("input1").getAlias(1));
2165     ASSERT_THROW(SimTKIndexOutOfRange, foo->updInput("input1").setAlias(1, "fred"));
2166     ASSERT_THROW(SimTKIndexOutOfRange, foo->getInput("input1").getLabel(1));
2167 
2168     foo->updInput("input1").disconnect();
2169 
2170     // Non-list Input, with alias.
2171     foo->connectInput_input1( bar->getOutput("Output1"), "baz" );
2172     theWorld->connect();
2173     SimTK_TEST(foo->getInput("input1").getAlias() == "baz");
2174     SimTK_TEST(foo->getInput("input1").getLabel() == "baz");
2175 
2176     // List Input, no aliases.
2177     foo->connectInput_listInput1( bar->getOutput("Output1") );
2178     foo->connectInput_listInput1( bar->getOutput("Output3") );
2179     theWorld->connect();
2180 
2181     ASSERT_THROW(OpenSim::Exception, foo->getInput("listInput1").getAlias());
2182     ASSERT_THROW(OpenSim::Exception, foo->getInput("listInput1").getLabel());
2183 
2184     SimTK_TEST(foo->getInput("listInput1").getAlias(0).empty());
2185     SimTK_TEST(foo->getInput("listInput1").getLabel(0) == "/bar|Output1");
2186 
2187     SimTK_TEST(foo->getInput("listInput1").getAlias(1).empty());
2188     SimTK_TEST(foo->getInput("listInput1").getLabel(1) == "/bar|Output3");
2189 
2190     foo->updInput("listInput1").disconnect();
2191 
2192     // List Input, with aliases.
2193     foo->connectInput_listInput1( bar->getOutput("Output1"), "plugh" );
2194     foo->connectInput_listInput1( bar->getOutput("Output3"), "thud" );
2195     theWorld->connect();
2196 
2197     SimTK_TEST(foo->getInput("listInput1").getAlias(0) == "plugh");
2198     SimTK_TEST(foo->getInput("listInput1").getLabel(0) == "plugh");
2199 
2200     SimTK_TEST(foo->getInput("listInput1").getAlias(1) == "thud");
2201     SimTK_TEST(foo->getInput("listInput1").getLabel(1) == "thud");
2202 }
2203 
testGetAbsolutePathStringSpeed()2204 void testGetAbsolutePathStringSpeed() {
2205 
2206     std::clock_t constructStartTime = std::clock();
2207 
2208     TheWorld* A = new TheWorld();
2209     TheWorld* B = new TheWorld();
2210     TheWorld* C = new TheWorld();
2211     TheWorld* D = new TheWorld();
2212     TheWorld* E = new TheWorld();
2213     // Use longer names to avoid short string optimization
2214     A->setName("a2345678901234567890");
2215     B->setName("b2345678901234567890");
2216     C->setName("c2345678901234567890");
2217     D->setName("d2345678901234567890");
2218     E->setName("e2345678901234567890");
2219 
2220     A->add(B);
2221     B->add(C);
2222     C->add(D);
2223     D->add(E);
2224 
2225     double avgTime = 0;
2226     int numTrials = 10;
2227     int numLoops = 1000000;
2228     for (int trial = 0; trial < numTrials; ++trial) {
2229         std::clock_t loopStartTime = std::clock();
2230         for (int i = 0; i < numLoops; ++i) {
2231             A->getAbsolutePathString();
2232             B->getAbsolutePathString();
2233             C->getAbsolutePathString();
2234             D->getAbsolutePathString();
2235             E->getAbsolutePathString();
2236         }
2237         std::clock_t loopEndTime = std::clock();
2238         double loopClocks = loopEndTime - loopStartTime;
2239         avgTime += loopClocks / CLOCKS_PER_SEC;
2240     }
2241 
2242     cout << "getAbsolutePathString avgTime = " << avgTime / numTrials << "s" << endl;
2243 
2244     avgTime = 0;
2245     for (int trial = 0; trial < numTrials; ++trial) {
2246         std::clock_t loopStartTime = std::clock();
2247         for (int i = 0; i < numLoops; ++i) {
2248             A->getName();
2249             B->getName();
2250             C->getName();
2251             D->getName();
2252             E->getName();
2253         }
2254         std::clock_t loopEndTime = std::clock();
2255         double loopClocks = loopEndTime - loopStartTime;
2256         avgTime += loopClocks / CLOCKS_PER_SEC;
2257     }
2258 
2259     cout << "getName avgTime = " << avgTime / numTrials << "s" << endl;
2260 }
2261 
testFormattedDateTime()2262 void testFormattedDateTime() {
2263     std::string withMicroseconds = getFormattedDateTime(true, "%Y");
2264     std::string withoutMicroseconds = getFormattedDateTime(false, "%Y");
2265     SimTK_TEST(withMicroseconds.find(withoutMicroseconds) == 0);
2266 }
2267 
main()2268 int main() {
2269 
2270     //Register new types for testing deserialization
2271     Object::registerType(Foo());
2272     Object::registerType(Bar());
2273     Object::registerType(TheWorld());
2274 
2275     SimTK_START_TEST("testComponentInterface");
2276         SimTK_SUBTEST(testMisc);
2277         // Uncomment test for duplicate names when we re-enable the exception
2278         //SimTK_SUBTEST(testThrowOnDuplicateNames);
2279         SimTK_SUBTEST(testExceptionsFinalizeFromPropertiesAfterCopy);
2280         SimTK_SUBTEST(testListInputs);
2281         SimTK_SUBTEST(testListSockets);
2282         SimTK_SUBTEST(testComponentPathNames);
2283         SimTK_SUBTEST(testFindComponent);
2284         SimTK_SUBTEST(testTraversePathToComponent);
2285         SimTK_SUBTEST(testGetStateVariableValue);
2286         SimTK_SUBTEST(testInputOutputConnections);
2287         SimTK_SUBTEST(testInputConnecteePaths);
2288         SimTK_SUBTEST(testExceptionsForConnecteeTypeMismatch);
2289         SimTK_SUBTEST(testExceptionsSocketNameExistsAlready);
2290         SimTK_SUBTEST(testExceptionsInputNameExistsAlready);
2291         SimTK_SUBTEST(testExceptionsOutputNameExistsAlready);
2292         SimTK_SUBTEST(testTableSource);
2293         SimTK_SUBTEST(testTableReporter);
2294         SimTK_SUBTEST(testAliasesAndLabels);
2295 
2296         writeTimeSeriesTableForInputConnecteeSerialization();
2297         SimTK_SUBTEST(testListInputConnecteeSerialization);
2298         SimTK_SUBTEST(testSingleValueInputConnecteeSerialization);
2299 
2300         // This is commented out since it adds ~20-30sec without testing
2301         // any new functionality. Make sure to uncomment to use (and
2302         // consider commenting other subtests for more stable benchmark).
2303         //SimTK_SUBTEST(testGetAbsolutePathStringSpeed);
2304 
2305         SimTK_SUBTEST(testFormattedDateTime);
2306 
2307     SimTK_END_TEST();
2308 }
2309