1 /* -------------------------------------------------------------------------- *
2  *                            OpenSim: Component.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, Michael Sherman                                      *
12  * Contributor(s): Ayman Habib                                                *
13  *                                                                            *
14  * Licensed under the Apache License, Version 2.0 (the "License"); you may    *
15  * not use this file except in compliance with the License. You may obtain a  *
16  * copy of the License at http://www.apache.org/licenses/LICENSE-2.0.         *
17  *                                                                            *
18  * Unless required by applicable law or agreed to in writing, software        *
19  * distributed under the License is distributed on an "AS IS" BASIS,          *
20  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.   *
21  * See the License for the specific language governing permissions and        *
22  * limitations under the License.                                             *
23  * -------------------------------------------------------------------------- */
24 
25 // INCLUDES
26 #include "Component.h"
27 #include "OpenSim/Common/IO.h"
28 #include "XMLDocument.h"
29 #include <unordered_map>
30 #include <set>
31 #include <regex>
32 
33 using namespace SimTK;
34 
35 namespace OpenSim {
36 
37 //==============================================================================
38 //                            COMPONENT MEASURE
39 //==============================================================================
40 // Every OpenSim::Component is associated with a Simbody Measure of type
41 // ComponentMeasure defined here. This provides a full set of realize()
42 // methods for performing computations with System resources that are maintained
43 // at the Component base level, such as calculating state derivatives.
44 
45 template <class T>
46 class ComponentMeasure : public SimTK::Measure_<T> {
47 public:
48     SimTK_MEASURE_HANDLE_PREAMBLE(ComponentMeasure, SimTK::Measure_<T>);
49 
ComponentMeasure(SimTK::Subsystem & sub,const OpenSim::Component & mc)50     ComponentMeasure(SimTK::Subsystem& sub,
51                           const OpenSim::Component& mc)
52     :   SimTK::Measure_<T>(sub, new Implementation(mc),
53                     SimTK::AbstractMeasure::SetHandle()) {}
54 
55     SimTK_MEASURE_HANDLE_POSTSCRIPT(ComponentMeasure, SimTK::Measure_<T>);
56 };
57 
58 
59 template <class T>
60 class ComponentMeasure<T>::Implementation
61 :   public SimTK::Measure_<T>::Implementation {
62 public:
63     // Don't allocate a value cache entry since this measure's value is
64     // just a dummy.
Implementation(const Component & c)65     explicit Implementation(const Component& c)
66     :   SimTK::Measure_<T>::Implementation(0), _Component(c) {}
67 
68     // Implementations of Measure_<T>::Implementation virtual methods.
69 
cloneVirtual() const70     Implementation* cloneVirtual() const override final
71     {   return new Implementation(*this); }
72 
getNumTimeDerivativesVirtual() const73     int getNumTimeDerivativesVirtual() const override final {return 0;}
getDependsOnStageVirtual(int order) const74     SimTK::Stage getDependsOnStageVirtual(int order) const override final
75     {   return SimTK::Stage::Empty; }
76 
getUncachedValueVirtual(const SimTK::State & s,int derivOrder) const77     const T& getUncachedValueVirtual
78        (const SimTK::State& s, int derivOrder) const override final
79     {   return this->getValueZero(); }
80 
realizeMeasureTopologyVirtual(SimTK::State & s) const81     void realizeMeasureTopologyVirtual(SimTK::State& s) const override final
82     {   _Component.extendRealizeTopology(s); }
realizeMeasureModelVirtual(SimTK::State & s) const83     void realizeMeasureModelVirtual(SimTK::State& s) const override final
84     {   _Component.extendRealizeModel(s); }
realizeMeasureInstanceVirtual(const SimTK::State & s) const85     void realizeMeasureInstanceVirtual(const SimTK::State& s)
86         const override final
87     {   _Component.extendRealizeInstance(s); }
realizeMeasureTimeVirtual(const SimTK::State & s) const88     void realizeMeasureTimeVirtual(const SimTK::State& s) const override final
89     {   _Component.extendRealizeTime(s); }
realizeMeasurePositionVirtual(const SimTK::State & s) const90     void realizeMeasurePositionVirtual(const SimTK::State& s)
91         const override final
92     {   _Component.extendRealizePosition(s); }
realizeMeasureVelocityVirtual(const SimTK::State & s) const93     void realizeMeasureVelocityVirtual(const SimTK::State& s)
94         const override final
95     {   _Component.extendRealizeVelocity(s); }
realizeMeasureDynamicsVirtual(const SimTK::State & s) const96     void realizeMeasureDynamicsVirtual(const SimTK::State& s)
97         const override final
98     {   _Component.extendRealizeDynamics(s); }
realizeMeasureAccelerationVirtual(const SimTK::State & s) const99     void realizeMeasureAccelerationVirtual(const SimTK::State& s)
100         const override final
101     {   _Component.extendRealizeAcceleration(s); }
realizeMeasureReportVirtual(const SimTK::State & s) const102     void realizeMeasureReportVirtual(const SimTK::State& s)
103         const override final
104     {   _Component.extendRealizeReport(s); }
105 
106 private:
107     const Component& _Component;
108 };
109 
110 
111 //==============================================================================
112 //                              COMPONENT
113 //==============================================================================
Component()114 Component::Component() : Object()
115 {
116     constructProperty_components();
117 }
118 
Component(const std::string & fileName,bool updFromXMLNode)119 Component::Component(const std::string& fileName, bool updFromXMLNode)
120 :   Object(fileName, updFromXMLNode)
121 {
122     constructProperty_components();
123 }
124 
Component(SimTK::Xml::Element & element)125 Component::Component(SimTK::Xml::Element& element) : Object(element)
126 {
127     constructProperty_components();
128 }
129 
isComponentInOwnershipTree(const Component * subcomponent) const130 bool Component::isComponentInOwnershipTree(const Component* subcomponent) const {
131     //get to the root Component
132     const Component* root = this;
133     while (root->hasOwner()) {
134         root = &(root->getOwner());
135     }
136     // if the root has no immediate subcomponents do not bother
137     // checking if the subcomponent is in the ownership tree
138     if ((root->getNumImmediateSubcomponents() > 0)) {
139         auto components = root->getComponentList<Component>();
140         for (auto& c : components) {
141             if (subcomponent == &c) return true;
142         }
143     }
144     return false;
145 }
146 
addComponent(Component * subcomponent)147 void Component::addComponent(Component* subcomponent)
148 {
149     OPENSIM_THROW_IF(isComponentInOwnershipTree(subcomponent),
150                      ComponentAlreadyPartOfOwnershipTree,
151                       subcomponent->getName(), getName());
152 
153     updProperty_components().adoptAndAppendValue(subcomponent);
154     finalizeFromProperties();
155 
156     prependComponentPathToConnecteePath(*subcomponent);
157 
158     // allow the derived Component to perform secondary operations
159     // in response to the inclusion of the subcomponent
160     extendAddComponent(subcomponent);
161 }
162 
prependComponentPathToConnecteePath(Component & subcomponent)163 void Component::prependComponentPathToConnecteePath(
164         Component& subcomponent) {
165     const std::string compPath = subcomponent.getAbsolutePathString();
166     const Component& root = subcomponent.getRoot();
167     for (auto& comp : subcomponent.updComponentList()) {
168         for (auto& it : comp._socketsTable) {
169             if (!root.hasComponent(it.second->getConnecteePath()))
170                 it.second->prependComponentPathToConnecteePath(compPath);
171         }
172         for (auto& it : comp._inputsTable) {
173             it.second->prependComponentPathToConnecteePath(compPath);
174         }
175     }
176 }
177 
finalizeFromProperties()178 void Component::finalizeFromProperties()
179 {
180     reset();
181 
182     // last opportunity to modify Object names based on properties
183     if (!hasOwner()) {
184         // only call when Component is root since method is recursive
185         makeObjectNamesConsistentWithProperties();
186     }
187 
188     // TODO use a flag to set whether we are lenient on having nameless
189     // Components. For backward compatibility we need to be able to
190     // handle nameless components so assign them their class name
191     // - aseth
192     if (getName().empty()) {
193         setName(IO::Lowercase(getConcreteClassName()));
194     }
195 
196     OPENSIM_THROW_IF( getName().empty(), ComponentHasNoName,
197                       getConcreteClassName() );
198 
199     ComponentPath cp;
200     OPENSIM_THROW_IF( !cp.isLegalPathElement(getName()), InvalidComponentName,
201         getName(), cp.getInvalidChars(), getConcreteClassName());
202 
203     for (auto& comp : _memberSubcomponents) {
204         comp->setOwner(*this);
205     }
206     for (auto& comp : _adoptedSubcomponents) {
207         comp->setOwner(*this);
208     }
209 
210     // Provide sockets, inputs, and outputs with a pointer to its component
211     // (this) so that they can invoke the component's methods.
212     for (auto& it : _socketsTable) {
213         it.second->setOwner(*this);
214         // Let the Socket handle any errors in the connectee_name property.
215         it.second->checkConnecteePathProperty();
216     }
217     for (auto& it : _inputsTable) {
218         it.second->setOwner(*this);
219         // Let the Socket handle any errors in the connectee_name property.
220         it.second->checkConnecteePathProperty();
221     }
222     for (auto& it : _outputsTable) {
223         it.second->setOwner(*this);
224     }
225 
226     markPropertiesAsSubcomponents();
227     componentsFinalizeFromProperties();
228 
229     // The following block is used to ensure that deserialized names of
230     // Components are unique so they can be used to unambiguously locate
231     // and connect all loaded Components. If a duplicate is encountered,
232     // it is assigned a unique name.
233     auto subs = getImmediateSubcomponents();
234     std::set<std::string> names{};
235 
236     // increment the count of duplicates to use as a unique suffix
237     int count{ 0 };
238     // temp variable to hold the unique name used to rename a duplicate
239     std::string uniqueName{};
240 
241     for (auto& sub : subs) {
242         const std::string& name = sub->getName();
243 
244         // reset duplicate count and search name
245         count = 0;
246         uniqueName = name;
247 
248         // while the name is still not unique keep incrementing the count
249         while (names.find(uniqueName) != names.cend()) {
250             // In the future this should become an Exception
251             //OPENSIM_THROW(SubcomponentsWithDuplicateName, getName(), searchName);
252             // for now, rename the duplicately named subcomponent
253             // but first test the uniqueness of the name (the while condition)
254             uniqueName = name + "_" + std::to_string(count++);
255         }
256 
257         if (count > 0) { // if a duplicate
258             // Warn of the problem
259             std::string msg = getConcreteClassName() + " '" + getName() +
260                 "' has subcomponents with duplicate name '" + name +  "'.\n" +
261                 "The duplicate is being renamed to '" + uniqueName + "'.";
262             std::cout << msg << std::endl;
263 
264             // Now rename the subcomponent with its verified unique name
265             Component* mutableSub = const_cast<Component *>(sub.get());
266             mutableSub->setName(uniqueName);
267         }
268 
269         // keep track of unique names
270         names.insert(uniqueName);
271     }
272     // End of duplicate finding and renaming.
273 
274     extendFinalizeFromProperties();
275     setObjectIsUpToDateWithProperties();
276 }
277 
278 // Base class implementation of virtual method.
279 // Call finalizeFromProperties on all subcomponents
componentsFinalizeFromProperties() const280 void Component::componentsFinalizeFromProperties() const
281 {
282     for (auto& comp : _memberSubcomponents) {
283         const_cast<Component*>(comp.get())
284             ->finalizeFromProperties();
285     }
286     for (auto& comp : _propertySubcomponents) {
287         const_cast<Component*>(comp.get())
288             ->finalizeFromProperties();
289     }
290     for (auto& comp : _adoptedSubcomponents) {
291         const_cast<Component*>(comp.get())
292             ->finalizeFromProperties();
293     }
294 }
295 
296 // Base class implementation of non-virtual finalizeConnections method.
finalizeConnections(Component & root)297 void Component::finalizeConnections(Component& root)
298 {
299     if (!isObjectUpToDateWithProperties()){
300         // if edits occur between construction and connect() this is
301         // the last chance to finalize before addToSystem.
302         finalizeFromProperties();
303     }
304 
305     for (auto& it : _socketsTable) {
306         auto& socket = it.second;
307         try {
308             socket->finalizeConnection(root);
309         }
310         catch (const std::exception& x) {
311             OPENSIM_THROW_FRMOBJ(Exception, "Failed to connect Socket '" +
312                 socket->getName() + "' of type " +
313                 socket->getConnecteeTypeName() +
314                 " (details: " + x.what() + ").");
315         }
316     }
317 
318     for (auto& it : _inputsTable) {
319         auto& input = it.second;
320         try {
321             input->finalizeConnection(root);
322         }
323         catch (const std::exception& x) {
324             OPENSIM_THROW_FRMOBJ(Exception, "Failed to connect Input '" +
325                 input->getName() + "' of type " + input->getConnecteeTypeName()
326                 + " (details: " + x.what() + ").");
327         }
328     }
329 
330     // Allow derived Components to handle/check their connections and also
331     // override the order in which its subcomponents are ordered when
332     // adding subcomponents to the System
333     extendFinalizeConnections(root);
334 
335     // Allow subcomponents to form their connections
336     componentsFinalizeConnections(root);
337 
338     // Forming connections changes the Socket which is a property
339     // Remark as upToDate.
340     setObjectIsUpToDateWithProperties();
341 }
342 
343 // invoke connect on all (sub)components of this component
componentsFinalizeConnections(Component & root)344 void Component::componentsFinalizeConnections(Component& root)
345 {
346     // enable the subcomponents the opportunity to connect themselves
347     for (unsigned int i = 0; i<_memberSubcomponents.size(); ++i) {
348         _memberSubcomponents[i].upd()->finalizeConnections(root);
349     }
350     for(unsigned int i=0; i<_propertySubcomponents.size(); ++i){
351         _propertySubcomponents[i].get()->finalizeConnections(root);
352     }
353     for (unsigned int i = 0; i<_adoptedSubcomponents.size(); ++i) {
354         _adoptedSubcomponents[i].upd()->finalizeConnections(root);
355     }
356 }
357 
clearConnections()358 void Component::clearConnections()
359 {
360     // First give the subcomponents the opportunity to disconnect themselves
361     for (unsigned int i = 0; i<_memberSubcomponents.size(); i++) {
362         _memberSubcomponents[i]->clearConnections();
363     }
364     for (unsigned int i = 0; i<_propertySubcomponents.size(); i++){
365         _propertySubcomponents[i]->clearConnections();
366     }
367     for (unsigned int i = 0; i<_adoptedSubcomponents.size(); i++) {
368         _adoptedSubcomponents[i]->clearConnections();
369     }
370 
371     //Now cycle through and disconnect all sockets for this component
372     for (auto& it : _socketsTable) {
373         it.second->disconnect();
374     }
375 
376     // Must also clear the input's connections.
377     for (auto& it : _inputsTable) {
378         it.second->disconnect();
379     }
380 
381     //now clear all the stored system indices from this component
382     reset();
383 }
384 
addToSystem(SimTK::MultibodySystem & system) const385 void Component::addToSystem(SimTK::MultibodySystem& system) const
386 {
387     // If being asked to be added to the same System that it is already
388     // a part, there is nothing to be done.
389     if (hasSystem() && (&getSystem() == &system)) {
390         return;
391     }
392     baseAddToSystem(system);
393     extendAddToSystem(system);
394     componentsAddToSystem(system);
395     extendAddToSystemAfterSubcomponents(system);
396 }
397 
398 // Base class implementation of virtual method.
399 // Every Component owns an underlying SimTK::Measure
400 // which is a ComponentMeasure<T> and is added to the System's default
401 // subsystem. That measure is used only for the side effect of its realize()
402 // methods being called; its value is not used.
baseAddToSystem(SimTK::MultibodySystem & system) const403 void Component::baseAddToSystem(SimTK::MultibodySystem& system) const
404 {
405     if (!isObjectUpToDateWithProperties()) {
406         std::string msg = "Component " + getConcreteClassName() + "::" + getName();
407         msg += " cannot extendAddToSystem until it is up-to-date with its properties.";
408 
409         throw Exception(msg);
410     }
411 
412     // Clear cached list of all related StateVariables if any from a previous
413     // System.
414     _allStateVariables.clear();
415 
416     // Briefly get write access to the Component to record some
417     // information associated with the System; that info is const after this.
418     Component* mutableThis = const_cast<Component *>(this);
419     mutableThis->_system = system;
420 
421     // Allocate the ComponentMeasure, point it to this Component for
422     // making realize() calls, and add it to the system's default subsystem.
423     ComponentMeasure<double> mcMeasure(system.updDefaultSubsystem(), *this);
424     mutableThis->_simTKcomponentIndex = mcMeasure.getSubsystemMeasureIndex();
425 }
426 
componentsAddToSystem(SimTK::MultibodySystem & system) const427 void Component::componentsAddToSystem(SimTK::MultibodySystem& system) const
428 {
429     // If _orderedSubcomponents is specified, then use this Component's
430     // specification for the order in which subcomponents are added. At a
431     // minimum the order for all immediate subcomponents must be specified.
432     if (_orderedSubcomponents.size() >= getNumImmediateSubcomponents()) {
433         for (const auto& compRef : _orderedSubcomponents) {
434             compRef->addToSystem(system);
435         }
436     }
437     else if (_orderedSubcomponents.size() == 0) {
438         // Otherwise, invoke on all immediate subcomponents in tree order
439         auto mySubcomponents = getImmediateSubcomponents();
440         for (const auto& compRef : mySubcomponents) {
441             compRef->addToSystem(system);
442         }
443     }
444     else {
445         OPENSIM_THROW_FRMOBJ(Exception,
446             "_orderedSubcomponents specified, but its size does not reflect the "
447             "the number of immediate subcomponents. Verify that you have included "
448             "all immediate subcomponents in the ordered list."
449         )
450     }
451 }
452 
initStateFromProperties(SimTK::State & state) const453 void Component::initStateFromProperties(SimTK::State& state) const
454 {
455     extendInitStateFromProperties(state);
456     componentsInitStateFromProperties(state);
457 }
458 
componentsInitStateFromProperties(SimTK::State & state) const459 void Component::componentsInitStateFromProperties(SimTK::State& state) const
460 {
461     for (unsigned int i = 0; i<_memberSubcomponents.size(); ++i)
462         _memberSubcomponents[i]->initStateFromProperties(state);
463     for (unsigned int i = 0; i<_propertySubcomponents.size(); ++i)
464         _propertySubcomponents[i]->initStateFromProperties(state);
465     for (unsigned int i = 0; i<_adoptedSubcomponents.size(); ++i)
466         _adoptedSubcomponents[i]->initStateFromProperties(state);
467 }
468 
setPropertiesFromState(const SimTK::State & state)469 void Component::setPropertiesFromState(const SimTK::State& state)
470 {
471     extendSetPropertiesFromState(state);
472     componentsSetPropertiesFromState(state);
473 }
474 
componentsSetPropertiesFromState(const SimTK::State & state)475 void Component::componentsSetPropertiesFromState(const SimTK::State& state)
476 {
477     for (unsigned int i = 0; i<_memberSubcomponents.size(); ++i)
478         _memberSubcomponents[i]->setPropertiesFromState(state);
479     for (unsigned int i = 0; i<_propertySubcomponents.size(); ++i)
480         _propertySubcomponents[i]->setPropertiesFromState(state);
481     for (unsigned int i = 0; i<_adoptedSubcomponents.size(); ++i)
482         _adoptedSubcomponents[i]->setPropertiesFromState(state);
483 }
484 
485 // Base class implementation of virtual method. Note that we're not handling
486 // subcomponents here; this method gets called from extendRealizeAcceleration()
487 // which will be invoked for each (sub) component by its own ComponentMeasure.
computeStateVariableDerivatives(const SimTK::State & s) const488 void Component::computeStateVariableDerivatives(const SimTK::State& s) const
489 {
490     int nsv = getNumStateVariablesAddedByComponent();
491     if(nsv > 0){
492         int nasv = 0;
493         std::map<std::string, StateVariableInfo>::const_iterator it;
494         for(it = _namedStateVariableInfo.begin();
495             it != _namedStateVariableInfo.end(); ++it){
496                 const StateVariable& sv = *it->second.stateVariable;
497                 const AddedStateVariable *asv =
498                     dynamic_cast<const AddedStateVariable *>(&sv);
499                 if(asv) nasv++;
500         }
501         if(nasv > 0){
502             std::stringstream msg;
503             msg << "Component " + getConcreteClassName()+"::"+getName();
504             msg << " added " << nasv << " state variables and ";
505             msg << " must specify their derivatives." << std::endl;
506 
507             throw Exception(msg.str());
508         }
509     }
510 }
511 
512 
513 void Component::
addModelingOption(const std::string & optionName,int maxFlagValue) const514 addModelingOption(const std::string& optionName, int maxFlagValue) const
515 {
516     // don't add modeling option if there is another state with the same
517     // name for this component
518     std::map<std::string, ModelingOptionInfo>::const_iterator it;
519     it = _namedModelingOptionInfo.find(optionName);
520     if(it != _namedModelingOptionInfo.end())
521         throw Exception("Component::addModelingOption: Modeling option '"
522               + optionName + "' already exists.");
523     // assign a "slot" for a modeling option by name
524     // modeling option index will be invalid by default
525     // upon allocation during realizeTopology the index will be set
526     _namedModelingOptionInfo[optionName] = ModelingOptionInfo(maxFlagValue);
527 }
528 
addStateVariable(const std::string & stateVariableName,const SimTK::Stage & invalidatesStage,bool isHidden) const529 void Component::addStateVariable(const std::string&  stateVariableName,
530                                  const SimTK::Stage& invalidatesStage,
531                                  bool isHidden) const
532 {
533     if( (invalidatesStage < Stage::Position) ||
534         (invalidatesStage > Stage::Dynamics)) {
535         throw Exception("Component::addStateVariable: invalidatesStage "
536                         "must be Position, Velocity or Dynamics.");
537     }
538     // Allocate space for a new state variable
539     AddedStateVariable* asv =
540         new AddedStateVariable(stateVariableName, *this, invalidatesStage, isHidden);
541     // Add it to the Component and let it take ownership
542     addStateVariable(asv);
543 }
544 
545 
addStateVariable(Component::StateVariable * stateVariable) const546 void Component::addStateVariable(Component::StateVariable*  stateVariable) const
547 {
548     const std::string& stateVariableName = stateVariable->getName();
549     // don't add state if there is another state variable with the same name
550     // for this component
551     std::map<std::string, StateVariableInfo>::const_iterator it;
552     it = _namedStateVariableInfo.find(stateVariableName);
553     if(it != _namedStateVariableInfo.end()){
554         throw Exception("Component::addStateVariable: State variable '" +
555             stateVariableName + "' already exists.");
556     }
557 
558     int order = (int)_namedStateVariableInfo.size();
559 
560     // assign a "slot" for a state variable by name
561     // state variable index will be invalid by default
562     // upon allocation during realizeTopology the index will be set
563     _namedStateVariableInfo[stateVariableName] =
564         StateVariableInfo(stateVariable, order);
565 
566     const AddedStateVariable* asv =
567         dynamic_cast<const Component::AddedStateVariable *>(stateVariable);
568     // Now automatically add a cache variable to hold the derivative
569     // to enable a similar interface for setting and getting the derivatives
570     // based on the creator specified state name
571     if(asv){
572         addCacheVariable(stateVariableName+"_deriv", 0.0, Stage::Dynamics);
573     }
574 
575 }
576 
577 
addDiscreteVariable(const std::string & discreteVariableName,SimTK::Stage invalidatesStage) const578 void Component::addDiscreteVariable(const std::string&  discreteVariableName,
579                                     SimTK::Stage        invalidatesStage) const
580 {
581     // don't add discrete var if there is another discrete variable with the
582     // same name for this component
583     std::map<std::string, DiscreteVariableInfo>::const_iterator it;
584     it = _namedDiscreteVariableInfo.find(discreteVariableName);
585     if(it != _namedDiscreteVariableInfo.end()){
586         throw Exception("Component::addDiscreteVariable: discrete variable '" +
587             discreteVariableName + "' already exists.");
588     }
589     // assign "slots" for the discrete variables by name
590     // discrete variable indices will be invalid by default
591     // upon allocation during realizeTopology the indices will be set
592     _namedDiscreteVariableInfo[discreteVariableName] =
593         DiscreteVariableInfo(invalidatesStage);
594 }
595 
596 // Get the value of a ModelingOption flag for this Component.
597 int Component::
getModelingOption(const SimTK::State & s,const std::string & name) const598 getModelingOption(const SimTK::State& s, const std::string& name) const
599 {
600     std::map<std::string, ModelingOptionInfo>::const_iterator it;
601     it = _namedModelingOptionInfo.find(name);
602 
603     if(it != _namedModelingOptionInfo.end()) {
604         SimTK::DiscreteVariableIndex dvIndex = it->second.index;
605         return SimTK::Value<int>::downcast(
606             getDefaultSubsystem().getDiscreteVariable(s, dvIndex)).get();
607     } else {
608         std::stringstream msg;
609         msg << "Component::getModelingOption: ERR- name '" << name
610             << "' not found.\n "
611             << "for component '"<< getName() << "' of type "
612             << getConcreteClassName();
613         throw Exception(msg.str(),__FILE__,__LINE__);
614         return -1;
615     }
616 }
617 
618 // Set the value of a discrete variable allocated by this Component by name.
619 void Component::
setModelingOption(SimTK::State & s,const std::string & name,int flag) const620 setModelingOption(SimTK::State& s, const std::string& name, int flag) const
621 {
622     std::map<std::string, ModelingOptionInfo>::const_iterator it;
623     it = _namedModelingOptionInfo.find(name);
624 
625     if(it != _namedModelingOptionInfo.end()) {
626         SimTK::DiscreteVariableIndex dvIndex = it->second.index;
627         if(flag > it->second.maxOptionValue){
628             std::stringstream msg;
629             msg << "Component::setModelingOption: "<< name
630                 << " flag cannot exceed "<< it->second.maxOptionValue <<".\n ";
631         throw Exception(msg.str(),__FILE__,__LINE__);
632         }
633 
634         SimTK::Value<int>::downcast(
635             getDefaultSubsystem().updDiscreteVariable(s, dvIndex)).upd() = flag;
636     } else {
637         std::stringstream msg;
638         msg << "Component::setModelingOption: modeling option " << name
639             << " not found.\n ";
640         throw Exception(msg.str(),__FILE__,__LINE__);
641     }
642 }
643 
printComponentsMatching(const std::string & substring) const644 unsigned Component::printComponentsMatching(const std::string& substring) const
645 {
646     auto components = getComponentList();
647     components.setFilter(ComponentFilterAbsolutePathNameContainsString(substring));
648     unsigned count = 0;
649     for (const auto& comp : components) {
650         std::cout << comp.getAbsolutePathString() << std::endl;
651         ++count;
652     }
653     return count;
654 }
655 
getNumStateVariables() const656 int Component::getNumStateVariables() const
657 {
658     // Must have already called initSystem.
659     OPENSIM_THROW_IF_FRMOBJ(!hasSystem(), ComponentHasNoSystem);
660 
661     //Get the number of state variables added (or exposed) by this Component
662     int ns = getNumStateVariablesAddedByComponent();
663     // And then include the states of its subcomponents
664     for (unsigned int i = 0; i<_memberSubcomponents.size(); i++)
665         ns += _memberSubcomponents[i]->getNumStateVariables();
666 
667     for(unsigned int i=0; i<_propertySubcomponents.size(); i++)
668         ns += _propertySubcomponents[i]->getNumStateVariables();
669 
670     for (unsigned int i = 0; i<_adoptedSubcomponents.size(); i++)
671         ns += _adoptedSubcomponents[i]->getNumStateVariables();
672 
673     return ns;
674 }
675 
676 
getOwner() const677 const Component& Component::getOwner() const
678 {
679     if (!hasOwner()) {
680         std::string msg = "Component '" + getName() + "'::getOwner(). " +
681             "Has no owner assigned.\n" +
682             "Make sure the component was added to the Model " +
683             "(or another component).";
684         throw Exception(msg);
685     }
686     return _owner.getRef();
687 }
688 
hasOwner() const689 bool Component::hasOwner() const
690 {
691     return !_owner.empty();
692 }
693 
getRoot() const694 const Component& Component::getRoot() const {
695     const Component* root = this;
696     while (root->hasOwner()) {
697         root = &root->getOwner();
698     }
699     return *root;
700 }
701 
setOwner(const Component & owner)702 void Component::setOwner(const Component& owner)
703 {
704     if (&owner == this) {
705         std::string msg = "Component '" + getName() + "'::setOwner(). " +
706             "Attempted to set itself as its owner.";
707         throw Exception(msg);
708     }
709     else if (_owner.get() == &owner) {
710         return;
711     }
712 
713     _owner.reset(&owner);
714 }
715 
getAbsolutePathString() const716 std::string Component::getAbsolutePathString() const
717 {
718     if (!hasOwner()) return "/";
719     std::string absPathName("/" + getName());
720 
721     const Component* up = this;
722 
723     while (up && up->hasOwner()) {
724         up = &up->getOwner();
725         if (up->hasOwner())
726             absPathName.insert(0, "/" + up->getName());
727     }
728 
729     return absPathName;
730 
731 }
732 
getAbsolutePath() const733 ComponentPath Component::getAbsolutePath() const
734 {
735     if (!hasOwner()) return ComponentPath({}, true);
736 
737     std::vector<std::string> pathVec;
738     pathVec.push_back(getName());
739 
740     const Component* up = this;
741 
742     while (up && up->hasOwner()) {
743         up = &up->getOwner();
744         if (up->hasOwner())
745             pathVec.insert(pathVec.begin(), up->getName());
746     }
747 
748     return ComponentPath(pathVec, true);
749 }
750 
getRelativePathString(const Component & wrt) const751 std::string Component::getRelativePathString(const Component& wrt) const
752 {
753     return getRelativePath(wrt).toString();
754 }
755 
getRelativePath(const Component & wrt) const756 ComponentPath Component::getRelativePath(const Component& wrt) const
757 {
758     ComponentPath thisP = getAbsolutePath();
759     ComponentPath wrtP = wrt.getAbsolutePath();
760 
761     return thisP.formRelativePath(wrtP);
762 }
763 
764 const Component::StateVariable* Component::
traverseToStateVariable(const std::string & pathName) const765     traverseToStateVariable(const std::string& pathName) const
766 {
767     // Must have already called initSystem.
768     OPENSIM_THROW_IF_FRMOBJ(!hasSystem(), ComponentHasNoSystem);
769 
770     ComponentPath svPath(pathName);
771 
772     const StateVariable* found = nullptr;
773     if (svPath.getNumPathLevels() == 1) {
774         // There was no slash. The state variable should be in this component.
775         auto it = _namedStateVariableInfo.find(pathName);
776         if (it != _namedStateVariableInfo.end()) {
777             return it->second.stateVariable.get();
778         }
779     } else if (svPath.getNumPathLevels() > 1) {
780         const auto& compPath = svPath.getParentPath();
781         const Component* comp = traversePathToComponent<Component>(compPath);
782         if (comp) {
783             // This is the leaf of the path:
784             const auto& varName = svPath.getComponentName();
785             found = comp->traverseToStateVariable(varName);
786         }
787     }
788     return found;
789 }
790 
791 // Get the names of "continuous" state variables maintained by the Component and
792 // its subcomponents.
getStateVariableNames() const793 Array<std::string> Component::getStateVariableNames() const
794 {
795     // Must have already called initSystem.
796     OPENSIM_THROW_IF_FRMOBJ(!hasSystem(), ComponentHasNoSystem);
797 
798     Array<std::string> stateNames = getStateVariableNamesAddedByComponent();
799 
800     for (int i = 0; i < stateNames.size(); ++i) {
801         stateNames[i] = (getAbsolutePathString() + "/" + stateNames[i]);
802     }
803 
804     for (auto& comp : getComponentList<Component>()) {
805         const std::string& pathName = comp.getAbsolutePathString();// *this);
806         Array<std::string> subStateNames =
807             comp.getStateVariableNamesAddedByComponent();
808         for (int i = 0; i < subStateNames.size(); ++i) {
809             stateNames.append(pathName + "/" + subStateNames[i]);
810         }
811     }
812 
813     return stateNames;
814 }
815 
816 // Get the value of a state variable allocated by this Component.
817 double Component::
getStateVariableValue(const SimTK::State & s,const std::string & name) const818     getStateVariableValue(const SimTK::State& s, const std::string& name) const
819 {
820     // Must have already called initSystem.
821     OPENSIM_THROW_IF_FRMOBJ(!hasSystem(), ComponentHasNoSystem);
822 
823     // find the state variable with this component or its subcomponents
824     const StateVariable* rsv = traverseToStateVariable(name);
825     if (rsv) {
826         return rsv->getValue(s);
827     }
828 
829     std::stringstream msg;
830     msg << "Component::getStateVariableValue: ERR- state named '" << name
831         << "' not found in " << getName() << " of type " << getConcreteClassName();
832     throw Exception(msg.str(),__FILE__,__LINE__);
833 
834     return SimTK::NaN;
835 }
836 
837 // Get the value of a state variable derivative computed by this Component.
838 double Component::
getStateVariableDerivativeValue(const SimTK::State & state,const std::string & name) const839     getStateVariableDerivativeValue(const SimTK::State& state,
840                                 const std::string& name) const
841 {
842     // Must have already called initSystem.
843     OPENSIM_THROW_IF_FRMOBJ(!hasSystem(), ComponentHasNoSystem);
844 
845     computeStateVariableDerivatives(state);
846 
847     std::map<std::string, StateVariableInfo>::const_iterator it;
848     it = _namedStateVariableInfo.find(name);
849 
850     if(it != _namedStateVariableInfo.end()) {
851         return it->second.stateVariable->getDerivative(state);
852     }
853     else{
854         // otherwise find the component that variable belongs to
855         const StateVariable* rsv = traverseToStateVariable(name);
856         if (rsv) {
857             return rsv->getDerivative(state);
858         }
859     }
860 
861     std::stringstream msg;
862     msg << "Component::getStateVariableDerivative: ERR- variable name '" << name
863         << "' not found.\n "
864         << getName() << " of type " << getConcreteClassName()
865         << " has " << getNumStateVariables() << " states.";
866     throw Exception(msg.str(),__FILE__,__LINE__);
867     return SimTK::NaN;
868 }
869 
870 // Set the value of a state variable allocated by this Component given its name
871 // for this component.
872 void Component::
setStateVariableValue(State & s,const std::string & name,double value) const873     setStateVariableValue(State& s, const std::string& name, double value) const
874 {
875     // Must have already called initSystem.
876     OPENSIM_THROW_IF_FRMOBJ(!hasSystem(), ComponentHasNoSystem);
877 
878     // find the state variable
879     const StateVariable* rsv = traverseToStateVariable(name);
880 
881     if(rsv){ // find required rummaging through the state variable names
882             return rsv->setValue(s, value);
883     }
884 
885     std::stringstream msg;
886     msg << "Component::setStateVariable: ERR- state named '" << name
887         << "' not found in " << getName() << " of type "
888         << getConcreteClassName() << ".\n";
889     throw Exception(msg.str(),__FILE__,__LINE__);
890 }
891 
isAllStatesVariablesListValid() const892 bool Component::isAllStatesVariablesListValid() const
893 {
894     int nsv = getNumStateVariables();
895     // Consider the list of all StateVariables to be valid if all of
896     // the following conditions are true:
897     // 1. Component is up-to-date with its Properties
898     // 2. a System has been associated with the list of StateVariables
899     // 3. The list of all StateVariables is correctly sized (initialized)
900     // 4. The System associated with the StateVariables is the current System
901     // TODO: Enable the isObjectUpToDateWithProperties() check when computing
902     // the path of the GeomtryPath does not involve updating its PathPointSet.
903     // This change dirties the GeometryPath which is a property of a Muscle which
904     // is property of the Model. Therefore, during integration the Model is not
905     // up-to-date and this causes a rebuilding of the cached StateVariables list.
906     // See GeometryPath::computePath() for the corresponding TODO that must be
907     // addressed before we can re-enable the isObjectUpToDateWithProperties
908     // check.
909     // It has been verified that the adding Components will invalidate the state
910     // variables associated with the Model and force the list to be rebuilt.
911     bool valid = //isObjectUpToDateWithProperties() &&                  // 1.
912         !_statesAssociatedSystem.empty() &&                             // 2.
913         _allStateVariables.size() == nsv &&                             // 3.
914         getSystem().isSameSystem(_statesAssociatedSystem.getRef());     // 4.
915 
916     return valid;
917 }
918 
919 
920 // Get all values of the state variables allocated by this Component. Includes
921 // state variables allocated by its subcomponents.
922 SimTK::Vector Component::
getStateVariableValues(const SimTK::State & state) const923     getStateVariableValues(const SimTK::State& state) const
924 {
925     // Must have already called initSystem.
926     OPENSIM_THROW_IF_FRMOBJ(!hasSystem(), ComponentHasNoSystem);
927 
928     int nsv = getNumStateVariables();
929     // if the StateVariables are invalid (see above) rebuild the list
930     if (!isAllStatesVariablesListValid()) {
931         _statesAssociatedSystem.reset(&getSystem());
932         _allStateVariables.clear();
933         _allStateVariables.resize(nsv);
934         Array<std::string> names = getStateVariableNames();
935         for (int i = 0; i < nsv; ++i)
936             _allStateVariables[i].reset(traverseToStateVariable(names[i]));
937     }
938 
939     Vector stateVariableValues(nsv, SimTK::NaN);
940     for(int i=0; i<nsv; ++i){
941         stateVariableValues[i]= _allStateVariables[i]->getValue(state);
942     }
943 
944     return stateVariableValues;
945 }
946 
947 // Set all values of the state variables allocated by this Component. Includes
948 // state variables allocated by its subcomponents.
949 void Component::
setStateVariableValues(SimTK::State & state,const SimTK::Vector & values) const950     setStateVariableValues(SimTK::State& state,
951                            const SimTK::Vector& values) const
952 {
953     // Must have already called initSystem.
954     OPENSIM_THROW_IF_FRMOBJ(!hasSystem(), ComponentHasNoSystem);
955 
956     int nsv = getNumStateVariables();
957 
958     SimTK_ASSERT(values.size() == nsv,
959         "Component::setStateVariableValues() number values does not match the "
960         "number of state variables.");
961 
962     // if the StateVariables are invalid (see above) rebuild the list
963     if (!isAllStatesVariablesListValid()) {
964         _statesAssociatedSystem.reset(&getSystem());
965         _allStateVariables.clear();
966         _allStateVariables.resize(nsv);
967         Array<std::string> names = getStateVariableNames();
968         for (int i = 0; i < nsv; ++i)
969             _allStateVariables[i].reset(traverseToStateVariable(names[i]));
970     }
971 
972     for(int i=0; i<nsv; ++i){
973         _allStateVariables[i]->setValue(state, values[i]);
974     }
975 }
976 
977 // Set the derivative of a state variable computed by this Component by name.
978 void Component::
setStateVariableDerivativeValue(const State & state,const std::string & name,double value) const979     setStateVariableDerivativeValue(const State& state,
980                                const std::string& name, double value) const
981 {
982     std::map<std::string, StateVariableInfo>::const_iterator it;
983     it = _namedStateVariableInfo.find(name);
984 
985     if(it != _namedStateVariableInfo.end()) {
986         const StateVariable& sv = *it->second.stateVariable;
987         sv.setDerivative(state, value);
988     }
989     else{
990         std::stringstream msg;
991         msg << "Component::setStateVariableDerivative: ERR- name '" << name
992             << "' not found.\n "
993             << getName() << " of type " << getConcreteClassName()
994             << " has " << getNumStateVariables() << " states.";
995         throw Exception(msg.str(),__FILE__,__LINE__);
996     }
997 }
998 
999 // Get the value of a discrete variable allocated by this Component by name.
1000 double Component::
getDiscreteVariableValue(const SimTK::State & s,const std::string & name) const1001 getDiscreteVariableValue(const SimTK::State& s, const std::string& name) const
1002 {
1003     // Must have already called initSystem.
1004     OPENSIM_THROW_IF_FRMOBJ(!hasSystem(), ComponentHasNoSystem);
1005 
1006     std::map<std::string, DiscreteVariableInfo>::const_iterator it;
1007     it = _namedDiscreteVariableInfo.find(name);
1008 
1009     if(it != _namedDiscreteVariableInfo.end()) {
1010         SimTK::DiscreteVariableIndex dvIndex = it->second.index;
1011         return SimTK::Value<double>::downcast(
1012             getDefaultSubsystem().getDiscreteVariable(s, dvIndex)).get();
1013     } else {
1014         std::stringstream msg;
1015         msg << "Component::getDiscreteVariable: ERR- name '" << name
1016             << "' not found.\n "
1017             << "for component '"<< getName() << "' of type "
1018             << getConcreteClassName();
1019         throw Exception(msg.str(),__FILE__,__LINE__);
1020         return SimTK::NaN;
1021     }
1022 }
1023 
1024 // Set the value of a discrete variable allocated by this Component by name.
1025 void Component::
setDiscreteVariableValue(SimTK::State & s,const std::string & name,double value) const1026 setDiscreteVariableValue(SimTK::State& s, const std::string& name, double value) const
1027 {
1028     // Must have already called initSystem.
1029     OPENSIM_THROW_IF_FRMOBJ(!hasSystem(), ComponentHasNoSystem);
1030 
1031     std::map<std::string, DiscreteVariableInfo>::const_iterator it;
1032     it = _namedDiscreteVariableInfo.find(name);
1033 
1034     if(it != _namedDiscreteVariableInfo.end()) {
1035         SimTK::DiscreteVariableIndex dvIndex = it->second.index;
1036         SimTK::Value<double>::downcast(
1037             getDefaultSubsystem().updDiscreteVariable(s, dvIndex)).upd() = value;
1038     } else {
1039         std::stringstream msg;
1040         msg << "Component::setDiscreteVariable: ERR- name '" << name
1041             << "' not found.\n "
1042             << "for component '"<< getName() << "' of type "
1043             << getConcreteClassName();
1044         throw Exception(msg.str(),__FILE__,__LINE__);
1045     }
1046 }
1047 
constructOutputForStateVariable(const std::string & name)1048 bool Component::constructOutputForStateVariable(const std::string& name)
1049 {
1050     auto func = [name](const Component* comp,
1051                        const SimTK::State& s, const std::string&,
1052                        double& result) -> void {
1053         result = comp->getStateVariableValue(s, name);
1054     };
1055     return constructOutput<double>(name, func, SimTK::Stage::Model);
1056 }
1057 
1058 // helper method to specify the order of subcomponents.
setNextSubcomponentInSystem(const Component & sub) const1059 void Component::setNextSubcomponentInSystem(const Component& sub) const
1060 {
1061     auto it =
1062         std::find(_orderedSubcomponents.begin(), _orderedSubcomponents.end(),
1063             SimTK::ReferencePtr<const Component>(&sub));
1064     if (it == _orderedSubcomponents.end()) {
1065         _orderedSubcomponents.push_back(SimTK::ReferencePtr<const Component>(&sub));
1066     }
1067 }
1068 
updateFromXMLNode(SimTK::Xml::Element & node,int versionNumber)1069 void Component::updateFromXMLNode(SimTK::Xml::Element& node, int versionNumber)
1070 {
1071     // During deserialization, some components' clone() may
1072     // finalizeFromProperties(). Upating from XML can then cause stale pointers.
1073     // We must make sure to clear any pointers to properties that may exist
1074     // in this component.
1075     reset();
1076     if (versionNumber < XMLDocument::getLatestVersion()) {
1077         if (versionNumber < 30500) {
1078             // In 3.3 and earlier, spaces in names were tolerated. Spaces are
1079             // no longer acceptable in Component names.
1080             if (node.hasAttribute("name")) {
1081                 auto name = node.getRequiredAttribute("name").getValue();
1082                 if (name.find_first_of("\n\t ") < std::string::npos) {
1083                     std::cout << getConcreteClassName() << " name '" << name
1084                         << "' contains whitespace. ";
1085                     name.erase(
1086                         std::remove_if(name.begin(), name.end(), ::isspace),
1087                         name.end() );
1088                     node.setAttributeValue("name", name);
1089                     std::cout << "It was renamed '" << name << "'." << std::endl;
1090                 }
1091             }
1092             else { // As 4.0 all Components must have a name. If none, assign one.
1093                 // Note: in finalizeFromProperties(), the Component will ensure
1094                 // that names are unique by travesing its list of subcomponents
1095                 // and renaming any duplicates.
1096                 node.setAttributeValue("name",
1097                     IO::Lowercase(getConcreteClassName()));
1098             }
1099         }
1100         if (versionNumber < 30508) {
1101             // Here's an example of the change this function might make:
1102             // Previous: <connectors>
1103             //               <Connector_PhysicalFrame_ name="parent">
1104             //                   <connectee_name>...</connectee_name>
1105             //               </Connector_PhysicalFrame_>
1106             //           </connectors>
1107             // New:      <connector_parent_connectee_name>...
1108             //           </connector_parent_connectee_name>
1109             //
1110             XMLDocument::updateConnectors30508(node);
1111         }
1112 
1113         if(versionNumber < 30510) {
1114             // Before -- <connector_....> ... </connector_...>
1115             // After  -- <socket_...> ... </socket_...>
1116             for(auto iter = node.element_begin();
1117                 iter != node.element_end();
1118                 ++iter) {
1119                 std::string oldName{"connector"};
1120                 std::string newName{"socket"};
1121                 auto tagname = iter->getElementTag();
1122                 auto pos = tagname.find(oldName);
1123                 if(pos != std::string::npos) {
1124                     tagname.replace(pos, oldName.length(), newName);
1125                     iter->setElementTag(tagname);
1126                 }
1127             }
1128 
1129         }
1130         if (versionNumber <= 30516) {
1131             // Rename xml tags for socket_*_connectee_name to socket_*
1132             std::string connecteeNameString = "_connectee_name";
1133             for (auto iter = node.element_begin();
1134                 iter != node.element_end();
1135                 ++iter) {
1136                 auto tagname = iter->getElementTag();
1137                 if (std::regex_match(tagname, std::regex("(socket_|input_)(.*)(_connectee_name)"))) {
1138                     auto pos = tagname.find(connecteeNameString);
1139                     if (pos != std::string::npos) {
1140                         tagname.replace(pos, connecteeNameString.length(), "");
1141                         iter->setElementTag(tagname);
1142                     }
1143                 }
1144                 else if (std::regex_match(tagname, std::regex("(input_)(.*)(_connectee_names)"))) {
1145                     auto pos = tagname.find(connecteeNameString);
1146                     if (pos != std::string::npos) {
1147                         tagname.replace(pos, connecteeNameString.length()+1, "");
1148                         iter->setElementTag(tagname);
1149                     }
1150                 }
1151             }
1152         }
1153     }
1154     Super::updateFromXMLNode(node, versionNumber);
1155 }
1156 
1157 // mark components owned as properties as subcomponents
markPropertiesAsSubcomponents()1158 void Component::markPropertiesAsSubcomponents()
1159 {
1160     // Method can be invoked for either constructing a new Component
1161     // or the properties have been modified. In the latter case
1162     // we must make sure that pointers to old properties are cleared
1163     _propertySubcomponents.clear();
1164 
1165     // Now mark properties that are Components as subcomponents
1166     //loop over all its properties
1167     for (int i = 0; i < getNumProperties(); ++i) {
1168         auto& prop = getPropertyByIndex(i);
1169         // check if property is of type Object
1170         if (prop.isObjectProperty()) {
1171             // a property is a list so cycle through its contents
1172             for (int j = 0; j < prop.size(); ++j) {
1173                 const Object& obj = prop.getValueAsObject(j);
1174                 // if the object is a Component mark it
1175                 if (const Component* comp = dynamic_cast<const Component*>(&obj) ) {
1176                     markAsPropertySubcomponent(comp);
1177                 }
1178                 else {
1179                     // otherwise it may be a Set (of objects), and
1180                     // would prefer to do something like this to test:
1181                     //  Set<Component>* objects = dynamic_cast<Set<Component>*>(obj)
1182                     // Instead we can see if the object has a property called
1183                     // "objects" which is a PropertyObjArray used by Set<T>.
1184                     // knowing the object Type is useful for debugging
1185                     // and it could be used to strengthen the test (e.g. scan
1186                     // for "Set" in the type name).
1187                     std::string objType = obj.getConcreteClassName();
1188                     if (obj.hasProperty("objects")) {
1189                         // get the PropertyObjArray if the object has one
1190                         auto& objectsProp = obj.getPropertyByName("objects");
1191                         // loop over the objects in the PropertyObjArray
1192                         for (int k = 0; k < objectsProp.size(); ++k) {
1193                             const Object& obj = objectsProp.getValueAsObject(k);
1194                             // if the object is a Component mark it
1195                             if (const Component* comp = dynamic_cast<const Component*>(&obj) )
1196                                 markAsPropertySubcomponent(comp);
1197                         } // loop over objects and mark it if it is a component
1198                     } // end if property is a Set with "objects" inside
1199                 } // end of if/else property value is an Object or something else
1200             } // loop over the property list
1201         } // end if property is an Object
1202     } // loop over properties
1203 }
1204 
1205 // mark a Component as a subcomponent of this one. If already a
1206 // subcomponent, it is not added to the list again.
markAsPropertySubcomponent(const Component * component)1207 void Component::markAsPropertySubcomponent(const Component* component)
1208 {
1209     // Only add if the component is not already a part of this Component
1210     SimTK::ReferencePtr<Component> compRef(const_cast<Component*>(component));
1211     auto it =
1212         std::find(_propertySubcomponents.begin(), _propertySubcomponents.end(), compRef);
1213     if ( it == _propertySubcomponents.end() ){
1214         // Must reconstruct the reference pointer in place in order
1215         // to invoke move constructor from SimTK::Array::push_back
1216         // otherwise it will copy and reset the Component pointer to null.
1217         _propertySubcomponents.push_back(
1218             SimTK::ReferencePtr<Component>(const_cast<Component*>(component)));
1219     }
1220     else{
1221         auto compPath = component->getAbsolutePathString();
1222         auto foundPath = it->get()->getAbsolutePathString();
1223         OPENSIM_THROW( ComponentAlreadyPartOfOwnershipTree,
1224                        component->getName(), getName());
1225     }
1226 
1227     compRef->setOwner(*this);
1228 }
1229 
1230 // Include another Component as a subcomponent of this one. If already a
1231 // subcomponent, it is not added to the list again.
adoptSubcomponent(Component * subcomponent)1232 void Component::adoptSubcomponent(Component* subcomponent)
1233 {
1234     OPENSIM_THROW_IF(subcomponent->hasOwner(),
1235         ComponentAlreadyPartOfOwnershipTree,
1236         subcomponent->getName(), this->getName());
1237 
1238     //get the top-level component
1239     const Component* top = this;
1240     while (top->hasOwner())
1241         top = &top->getOwner();
1242 
1243     // cycle through all components from the top level component
1244     // down to verify the component is not already in the tree
1245     for (auto& comp : top->getComponentList<Component>()) {
1246         OPENSIM_THROW_IF(subcomponent->hasOwner(),
1247             ComponentAlreadyPartOfOwnershipTree,
1248             subcomponent->getName(), comp.getName());
1249     }
1250 
1251     subcomponent->setOwner(*this);
1252     _adoptedSubcomponents.push_back(SimTK::ClonePtr<Component>(subcomponent));
1253 }
1254 
1255 std::vector<SimTK::ReferencePtr<const Component>>
getImmediateSubcomponents() const1256     Component::getImmediateSubcomponents() const
1257 {
1258     std::vector<SimTK::ReferencePtr<const Component>> mySubcomponents;
1259     for (auto& compRef : _memberSubcomponents) {
1260         mySubcomponents.push_back(
1261             SimTK::ReferencePtr<const Component>(compRef.get()) );
1262     }
1263     for (auto& compRef : _propertySubcomponents) {
1264         mySubcomponents.push_back(
1265             SimTK::ReferencePtr<const Component>(compRef.get()) );
1266     }
1267     for (auto& compRef : _adoptedSubcomponents) {
1268         mySubcomponents.push_back(
1269             SimTK::ReferencePtr<const Component>(compRef.get()) );
1270     }
1271     return mySubcomponents;
1272 }
1273 
1274 
getNumMemberSubcomponents() const1275 size_t Component::getNumMemberSubcomponents() const
1276 {
1277     return _memberSubcomponents.size();
1278 }
1279 
getNumPropertySubcomponents() const1280 size_t Component::getNumPropertySubcomponents() const
1281 {
1282     return _propertySubcomponents.size();
1283 }
1284 
getNumAdoptedSubcomponents() const1285 size_t Component::getNumAdoptedSubcomponents() const
1286 {
1287     return _adoptedSubcomponents.size();
1288 }
1289 
1290 
1291 
getStateIndex(const std::string & name) const1292 const int Component::getStateIndex(const std::string& name) const
1293 {
1294     std::map<std::string, StateVariableInfo>::const_iterator it;
1295     it = _namedStateVariableInfo.find(name);
1296 
1297     if(it != _namedStateVariableInfo.end()) {
1298         return it->second.stateVariable->getVarIndex();
1299     } else {
1300         std::stringstream msg;
1301         msg << "Component::getStateVariableSystemIndex: ERR- name '"
1302             << name << "' not found.\n "
1303             << "for component '"<< getName() << "' of type "
1304             << getConcreteClassName();
1305         throw Exception(msg.str(),__FILE__,__LINE__);
1306         return SimTK::InvalidIndex;
1307     }
1308 }
1309 
1310 SimTK::SystemYIndex Component::
getStateVariableSystemIndex(const std::string & stateVariableName) const1311 getStateVariableSystemIndex(const std::string& stateVariableName) const
1312 {
1313     //const SimTK::State& s = getSystem().getDefaultState();
1314 
1315     std::map<std::string, StateVariableInfo>::const_iterator it;
1316     it = _namedStateVariableInfo.find(stateVariableName);
1317 
1318     if(it != _namedStateVariableInfo.end()){
1319         return it->second.stateVariable->getSystemYIndex();
1320     }
1321 
1322     // Otherwise we have to search through subcomponents
1323     SimTK::SystemYIndex yix;
1324 
1325     for(unsigned int i = 0; i < _propertySubcomponents.size(); ++i) {
1326         yix = _propertySubcomponents[i]->getStateVariableSystemIndex(stateVariableName);
1327         if(yix.isValid()){
1328             return yix;
1329         }
1330     }
1331 
1332     if(!(yix.isValid())){
1333         throw Exception(getConcreteClassName()
1334             + "::getStateVariableSystemIndex : state variable "
1335             + stateVariableName+" has an invalid index.");
1336     }
1337 
1338     return yix;
1339 }
1340 
1341 const SimTK::DiscreteVariableIndex Component::
getDiscreteVariableIndex(const std::string & name) const1342 getDiscreteVariableIndex(const std::string& name) const
1343 {
1344     std::map<std::string, DiscreteVariableInfo>::const_iterator it;
1345     it = _namedDiscreteVariableInfo.find(name);
1346 
1347     return it->second.index;
1348 }
1349 
1350 const SimTK::CacheEntryIndex Component::
getCacheVariableIndex(const std::string & name) const1351 getCacheVariableIndex(const std::string& name) const
1352 {
1353     std::map<std::string, CacheInfo>::const_iterator it;
1354     it = _namedCacheVariableInfo.find(name);
1355 
1356     return it->second.index;
1357 }
1358 
1359 Array<std::string> Component::
getStateVariableNamesAddedByComponent() const1360 getStateVariableNamesAddedByComponent() const
1361 {
1362     std::map<std::string, StateVariableInfo>::const_iterator it;
1363     it = _namedStateVariableInfo.begin();
1364 
1365     Array<std::string> names("",(int)_namedStateVariableInfo.size());
1366 
1367     while(it != _namedStateVariableInfo.end()){
1368         names[it->second.order] = it->first;
1369         it++;
1370     }
1371     return names;
1372 }
1373 
1374 //------------------------------------------------------------------------------
1375 //                            REALIZE TOPOLOGY
1376 //------------------------------------------------------------------------------
1377 // This is the base class implementation of a virtual method that can be
1378 // overridden by derived model components, but they *must* invoke
1379 // Super::extendRealizeTopology() as the first line in the overriding method so
1380 // that this code is executed before theirs.
1381 // This method is invoked from the ComponentMeasure associated with this
1382 // Component.
1383 // Note that subcomponent realize() methods will be invoked by their own
1384 // ComponentMeasures, so we do not need to forward to subcomponents here.
extendRealizeTopology(SimTK::State & s) const1385 void Component::extendRealizeTopology(SimTK::State& s) const
1386 {
1387     const SimTK::Subsystem& subSys = getSystem().getDefaultSubsystem();
1388 
1389     Component *mutableThis = const_cast<Component*>(this);
1390 
1391     // Allocate Modeling Option
1392     if(_namedModelingOptionInfo.size()>0){
1393         std::map<std::string, ModelingOptionInfo>::iterator it;
1394         for (it = (mutableThis->_namedModelingOptionInfo).begin();
1395              it !=_namedModelingOptionInfo.end(); ++it)
1396         {
1397             ModelingOptionInfo& moi = it->second;
1398             moi.index = subSys.allocateDiscreteVariable
1399                (s, SimTK::Stage::Instance, new SimTK::Value<int>(0));
1400         }
1401     }
1402 
1403     // Allocate Continuous State Variables
1404     if(_namedStateVariableInfo.size()>0){
1405         SimTK::Vector zInit(1, 0.0);
1406         std::map<std::string, StateVariableInfo>::iterator it;
1407         for (it = (mutableThis->_namedStateVariableInfo).begin();
1408              it != _namedStateVariableInfo.end(); ++it)
1409         {
1410             const StateVariable& sv = *it->second.stateVariable;
1411             const AddedStateVariable* asv
1412                 = dynamic_cast<const AddedStateVariable *>(&sv);
1413 
1414             if(asv){// add index information for added state variables
1415                 // make mutable just to update system allocated index ONLY!
1416                 AddedStateVariable* masv = const_cast<AddedStateVariable*>(asv);
1417                 masv->setVarIndex(subSys.allocateZ(s, zInit));
1418                 masv->setSubsystemIndex(getDefaultSubsystem().getMySubsystemIndex());
1419             }
1420         }
1421     }
1422 
1423     // Allocate Discrete State Variables
1424     if(_namedDiscreteVariableInfo.size()>0){
1425         std::map<std::string, DiscreteVariableInfo>::iterator it;
1426         for (it = (mutableThis->_namedDiscreteVariableInfo).begin();
1427              it != _namedDiscreteVariableInfo.end(); ++it)
1428         {
1429             DiscreteVariableInfo& dvi = it->second;
1430             dvi.index = subSys.allocateDiscreteVariable
1431                (s, dvi.invalidatesStage, new SimTK::Value<double>(0.0));
1432         }
1433     }
1434 
1435     // Allocate Cache Entry in the State
1436     if(_namedCacheVariableInfo.size()>0){
1437         std::map<std::string, CacheInfo>::iterator it;
1438         for (it = (mutableThis->_namedCacheVariableInfo).begin();
1439              it != _namedCacheVariableInfo.end(); ++it){
1440             CacheInfo& ci = it->second;
1441             ci.index = subSys.allocateLazyCacheEntry
1442                (s, ci.dependsOnStage, ci.prototype->clone());
1443         }
1444     }
1445 }
1446 
1447 
1448 //------------------------------------------------------------------------------
1449 //                         REALIZE ACCELERATION
1450 //------------------------------------------------------------------------------
1451 // Base class implementation of virtual method.
1452 // Collect this component's state variable derivatives.
extendRealizeAcceleration(const SimTK::State & s) const1453 void Component::extendRealizeAcceleration(const SimTK::State& s) const
1454 {
1455     // don't bother computing derivatives if the component has no state variables
1456     if(getNumStateVariablesAddedByComponent() > 0) {
1457         const SimTK::Subsystem& subSys = getDefaultSubsystem();
1458 
1459         // evaluate and set component state derivative values (in cache)
1460         computeStateVariableDerivatives(s);
1461 
1462         std::map<std::string, StateVariableInfo>::const_iterator it;
1463 
1464         for (it = _namedStateVariableInfo.begin();
1465              it != _namedStateVariableInfo.end(); ++it)
1466         {
1467             const StateVariable& sv = *it->second.stateVariable;
1468             const AddedStateVariable* asv =
1469                 dynamic_cast<const AddedStateVariable*>(&sv);
1470             if(asv)
1471                 // set corresponding system derivative value from
1472                 // cached value
1473                 subSys.updZDot(s)[ZIndex(asv->getVarIndex())] =
1474                     asv->getDerivative(s);
1475         }
1476     }
1477 }
1478 
getSystem() const1479 const SimTK::MultibodySystem& Component::getSystem() const
1480 {
1481     OPENSIM_THROW_IF_FRMOBJ(!hasSystem(), ComponentHasNoSystem);
1482     return _system.getRef();
1483 }
1484 
updSystem() const1485 SimTK::MultibodySystem& Component::updSystem() const
1486 {
1487     OPENSIM_THROW_IF_FRMOBJ(!hasSystem(), ComponentHasNoSystem);
1488     return _system.getRef();
1489 }
1490 
1491 //------------------------------------------------------------------------------
1492 //                         OTHER REALIZE METHODS
1493 //------------------------------------------------------------------------------
1494 // Base class implementations of these virtual methods do nothing now but
1495 // could do something in the future. Users must still invoke Super::realizeXXX()
1496 // as the first line in their overrides to ensure future compatibility.
extendRealizeModel(SimTK::State & state) const1497 void Component::extendRealizeModel(SimTK::State& state) const {}
extendRealizeInstance(const SimTK::State & state) const1498 void Component::extendRealizeInstance(const SimTK::State& state) const {}
extendRealizeTime(const SimTK::State & state) const1499 void Component::extendRealizeTime(const SimTK::State& state) const {}
extendRealizePosition(const SimTK::State & state) const1500 void Component::extendRealizePosition(const SimTK::State& state) const {}
extendRealizeVelocity(const SimTK::State & state) const1501 void Component::extendRealizeVelocity(const SimTK::State& state) const {}
extendRealizeDynamics(const SimTK::State & state) const1502 void Component::extendRealizeDynamics(const SimTK::State& state) const {}
extendRealizeReport(const SimTK::State & state) const1503 void Component::extendRealizeReport(const SimTK::State& state) const {}
1504 
1505 
1506 //override virtual methods
getValue(const SimTK::State & state) const1507 double Component::AddedStateVariable::getValue(const SimTK::State& state) const
1508 {
1509     ZIndex zix(getVarIndex());
1510     if(getSubsysIndex().isValid() && zix.isValid()){
1511         const SimTK::Vector& z = getOwner().getDefaultSubsystem().getZ(state);
1512         return z[ZIndex(zix)];
1513     }
1514 
1515     std::stringstream msg;
1516     msg << "Component::AddedStateVariable::getValue: ERR- variable '"
1517         << getName() << "' is invalid for component " << getOwner().getName()
1518         << " of type " << getOwner().getConcreteClassName() <<".";
1519     throw Exception(msg.str(),__FILE__,__LINE__);
1520     return SimTK::NaN;
1521 }
1522 
setValue(SimTK::State & state,double value) const1523 void Component::AddedStateVariable::setValue(SimTK::State& state, double value) const
1524 {
1525     ZIndex zix(getVarIndex());
1526     if(getSubsysIndex().isValid() && zix.isValid()){
1527         SimTK::Vector& z = getOwner().getDefaultSubsystem().updZ(state);
1528         z[ZIndex(zix)] = value;
1529         return;
1530     }
1531 
1532     std::stringstream msg;
1533     msg << "Component::AddedStateVariable::setValue: ERR- variable '"
1534         << getName() << "' is invalid for component " << getOwner().getName()
1535         << " of type " << getOwner().getConcreteClassName() <<".";
1536     throw Exception(msg.str(),__FILE__,__LINE__);
1537 }
1538 
1539 double Component::AddedStateVariable::
getDerivative(const SimTK::State & state) const1540     getDerivative(const SimTK::State& state) const
1541 {
1542     return getOwner().getCacheVariableValue<double>(state, getName()+"_deriv");
1543 }
1544 
1545 void Component::AddedStateVariable::
setDerivative(const SimTK::State & state,double deriv) const1546     setDerivative(const SimTK::State& state, double deriv) const
1547 {
1548     return getOwner().setCacheVariableValue<double>(state, getName()+"_deriv", deriv);
1549 }
1550 
1551 
printSocketInfo() const1552 void Component::printSocketInfo() const {
1553     std::cout << "Sockets for component " << getName()
1554               << " of type [" << getConcreteClassName()
1555               << "] along with connectee paths:";
1556     if (getNumSockets() == 0)
1557         std::cout << " none";
1558     std::cout << std::endl;
1559 
1560     size_t maxlenTypeName{}, maxlenSockName{};
1561     for(const auto& sock : _socketsTable) {
1562         maxlenTypeName = std::max(maxlenTypeName,
1563                                   sock.second->getConnecteeTypeName().length());
1564         maxlenSockName = std::max(maxlenSockName,
1565                                   sock.second->getName().length());
1566     }
1567     maxlenTypeName += 4;
1568     maxlenSockName += 1;
1569 
1570     for (const auto& it : _socketsTable) {
1571         const auto& socket = it.second;
1572         std::cout << std::string(maxlenTypeName -
1573                                  socket->getConnecteeTypeName().length(), ' ')
1574                   << "[" << socket->getConnecteeTypeName() << "]"
1575                   << std::string(maxlenSockName -
1576                                  socket->getName().length(), ' ')
1577                   << socket->getName() << " : ";
1578         if (socket->getNumConnectees() == 0) {
1579             std::cout << "no connectees" << std::endl;
1580         } else {
1581             for (unsigned i = 0; i < socket->getNumConnectees(); ++i) {
1582                 std::cout << socket->getConnecteePath(i) << " ";
1583             }
1584             std::cout << std::endl;
1585         }
1586     }
1587     std::cout << std::endl;
1588 }
1589 
printInputInfo() const1590 void Component::printInputInfo() const {
1591     std::cout << "Inputs for component " << getName() << " of type ["
1592               << getConcreteClassName() << "] along with connectee paths:";
1593     if (getNumInputs() == 0)
1594         std::cout << " none";
1595     std::cout << std::endl;
1596 
1597     size_t maxlenTypeName{}, maxlenInputName{};
1598     for(const auto& input : _inputsTable) {
1599         maxlenTypeName = std::max(maxlenTypeName,
1600                                 input.second->getConnecteeTypeName().length());
1601         maxlenInputName = std::max(maxlenInputName,
1602                                 input.second->getName().length());
1603     }
1604     maxlenTypeName += 4;
1605     maxlenInputName += 1;
1606 
1607     for (const auto& it : _inputsTable) {
1608         const auto& input = it.second;
1609         std::cout << std::string(maxlenTypeName -
1610                                  input->getConnecteeTypeName().length(), ' ')
1611                   << "[" << input->getConnecteeTypeName() << "]"
1612                   << std::string(maxlenInputName -
1613                                  input->getName().length(), ' ')
1614                   << input->getName() << " : ";
1615         if (input->getNumConnectees() == 0 ||
1616             (input->getNumConnectees() == 1 && input->getConnecteePath().empty())) {
1617             std::cout << "no connectees" << std::endl;
1618         } else {
1619             for (unsigned i = 0; i < input->getNumConnectees(); ++i) {
1620                 std::cout << input->getConnecteePath(i) << " ";
1621                 // TODO as is, requires the input connections to be satisfied.
1622                 // std::cout << " (alias: " << input.getAlias(i) << ") ";
1623             }
1624             std::cout << std::endl;
1625         }
1626     }
1627     std::cout << std::endl;
1628 }
1629 
printSubcomponentInfo() const1630 void Component::printSubcomponentInfo() const {
1631     printSubcomponentInfo<Component>();
1632 }
1633 
printOutputInfo(const bool includeDescendants) const1634 void Component::printOutputInfo(const bool includeDescendants) const {
1635 
1636     // Do not display header for Components with no outputs.
1637     if (getNumOutputs() > 0) {
1638         const std::string msg = "Outputs from " + getAbsolutePathString() +
1639             " [" + getConcreteClassName() + "]";
1640         std::cout << msg << "\n" << std::string(msg.size(), '=') << std::endl;
1641 
1642         const auto& outputs = getOutputs();
1643         size_t maxlen{};
1644         for(const auto& output : outputs)
1645             maxlen = std::max(maxlen, output.second->getTypeName().length());
1646         maxlen += 2;
1647 
1648         for(const auto& output : outputs) {
1649             const auto& name = output.second->getTypeName();
1650             std::cout << std::string(maxlen - name.length(), ' ');
1651             std::cout << "[" << name  << "]  " << output.first << std::endl;
1652         }
1653         std::cout << std::endl;
1654     }
1655 
1656     if (includeDescendants) {
1657         for (const Component& thisComp : getComponentList<Component>()) {
1658             // getComponentList() returns all descendants (i.e.,
1659             // children, grandchildren, etc.) so set includeDescendants=false
1660             // when calling on thisComp.
1661             thisComp.printOutputInfo(false);
1662         }
1663     }
1664 }
1665 
initComponentTreeTraversal(const Component & root) const1666 void Component::initComponentTreeTraversal(const Component &root) const {
1667     // Going down the tree, this node is followed by all its children.
1668     // The last child's successor (next) is the parent's successor.
1669 
1670     const size_t nmsc = _memberSubcomponents.size();
1671     const size_t npsc = _propertySubcomponents.size();
1672     const size_t nasc = _adoptedSubcomponents.size();
1673 
1674     if (!hasOwner()) {
1675         // If this isn't the root component and it has no owner, then
1676         // this is an orphan component and we likely failed to call
1677         // finalizeFromProperties() on the root OR this is a clone that
1678         // has not been added to the root (in which case would have an owner).
1679         if (this != &root) {
1680             OPENSIM_THROW(ComponentIsAnOrphan, getName(),
1681                 getConcreteClassName());
1682         }
1683         // if the root (have no owner) and have no components
1684         else if (!(nmsc + npsc + nasc)) {
1685             OPENSIM_THROW(ComponentIsRootWithNoSubcomponents,
1686                 getName(), getConcreteClassName());
1687         }
1688     }
1689 
1690     const Component* last = nullptr;
1691     for (unsigned int i = 0; i < nmsc; i++) {
1692         if (i == nmsc - 1) {
1693             _memberSubcomponents[i]->_nextComponent = _nextComponent.get();
1694             last = _memberSubcomponents[i].get();
1695         }
1696         else {
1697             _memberSubcomponents[i]->_nextComponent =
1698                 _memberSubcomponents[i + 1].get();
1699             last = _memberSubcomponents[i + 1].get();
1700         }
1701     }
1702     if (npsc) {
1703         if (last)
1704             last->_nextComponent = _propertySubcomponents[0].get();
1705 
1706         for (unsigned int i = 0; i < npsc; i++) {
1707             if (i == npsc - 1) {
1708                 _propertySubcomponents[i]->_nextComponent =
1709                     _nextComponent.get();
1710                 last = _propertySubcomponents[i].get();
1711             }
1712             else {
1713                 _propertySubcomponents[i]->_nextComponent =
1714                     _propertySubcomponents[i + 1].get();
1715                 last = _propertySubcomponents[i + 1].get();
1716             }
1717         }
1718     }
1719     if (nasc) {
1720         if (last)
1721             last->_nextComponent = _adoptedSubcomponents[0].get();
1722 
1723         for (unsigned int i = 0; i <nasc; i++) {
1724             if (i == nasc - 1) {
1725                 _adoptedSubcomponents[i]->_nextComponent = _nextComponent.get();
1726             }
1727             else {
1728                 _adoptedSubcomponents[i]->_nextComponent
1729                     = _adoptedSubcomponents[i + 1].get();
1730             }
1731         }
1732     }
1733 
1734     // recurse to handle children of subcomponents
1735     for (unsigned int i = 0; i < nmsc; ++i) {
1736         _memberSubcomponents[i]->initComponentTreeTraversal(root);
1737     }
1738     for (unsigned int i = 0; i < npsc; ++i) {
1739         _propertySubcomponents[i]->initComponentTreeTraversal(root);
1740     }
1741     for (unsigned int i = 0; i < nasc; ++i) {
1742         _adoptedSubcomponents[i]->initComponentTreeTraversal(root);
1743     }
1744 }
1745 
1746 
clearStateAllocations()1747 void Component::clearStateAllocations()
1748 {
1749     _namedModelingOptionInfo.clear();
1750     _namedStateVariableInfo.clear();
1751     _namedDiscreteVariableInfo.clear();
1752     _namedCacheVariableInfo.clear();
1753 }
1754 
reset()1755 void Component::reset()
1756 {
1757     _system.reset();
1758     _simTKcomponentIndex.invalidate();
1759     clearStateAllocations();
1760 
1761     _propertySubcomponents.clear();
1762     _adoptedSubcomponents.clear();
1763     resetSubcomponentOrder();
1764 }
1765 
warnBeforePrint() const1766 void Component::warnBeforePrint() const {
1767     if (!isObjectUpToDateWithProperties()) return;
1768     std::string message;
1769     auto checkIfConnecteePathIsSet =
1770             [](const Component& comp, std::string& message) {
1771         for (const auto& it : comp._socketsTable) {
1772             const auto& socket = it.second;
1773             if (socket->isConnected() &&
1774                 ((socket->isListSocket() &&
1775                   socket->getNumConnectees() == 0) ||
1776                  (!socket->isListSocket() &&
1777                          socket->getConnecteePath().empty()))) {
1778                 // TODO: Improve this condition by making sure the connectee
1779                 // name is correct.
1780                 message += "  Socket '" + socket->getName() + "' in " +
1781                            comp.getConcreteClassName() + " at " +
1782                            comp.getAbsolutePathString() + "\n";
1783             }
1784         }
1785     };
1786     if (getNumImmediateSubcomponents() == 0) {
1787         checkIfConnecteePathIsSet(*this, message);
1788     } else {
1789         for (const auto& comp : getComponentList()) {
1790             checkIfConnecteePathIsSet(comp, message);
1791         }
1792     }
1793     if (!message.empty()) {
1794         std::stringstream buffer;
1795         buffer << "Warning in " << getConcreteClassName()
1796                 << "::print(): The following connections are not finalized "
1797                    "and will not appear in the resulting XML file. "
1798                    "Call finalizeConnections() before print().\n"
1799                    "To ignore, set the debug level to at least 1 "
1800                    "(e.g, by calling Object::setDebugLevel(1)) first.\n"
1801                 << message << std::endl;
1802         OPENSIM_THROW_FRMOBJ(Exception, buffer.str());
1803     }
1804 }
1805 
1806 } // end of namespace OpenSim
1807