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 != ©Sub);
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