1 #ifndef OPENSIM_COMPONENT_OUTPUT_H_ 2 #define OPENSIM_COMPONENT_OUTPUT_H_ 3 /* -------------------------------------------------------------------------- * 4 * OpenSim: ComponentOutput.h * 5 * -------------------------------------------------------------------------- * 6 * The OpenSim API is a toolkit for musculoskeletal modeling and simulation. * 7 * See http://opensim.stanford.edu and the NOTICE file for more information. * 8 * OpenSim is developed at Stanford University and supported by the US * 9 * National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA * 10 * through the Warrior Web program. * 11 * * 12 * Copyright (c) 2005-2017 Stanford University and the Authors * 13 * Author(s): Ajay Seth * 14 * * 15 * Licensed under the Apache License, Version 2.0 (the "License"); you may * 16 * not use this file except in compliance with the License. You may obtain a * 17 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. * 18 * * 19 * Unless required by applicable law or agreed to in writing, software * 20 * distributed under the License is distributed on an "AS IS" BASIS, * 21 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 22 * See the License for the specific language governing permissions and * 23 * limitations under the License. * 24 * -------------------------------------------------------------------------- */ 25 26 /** @file 27 * This file defines the Output class, which formalizes an output (signal) 28 * that a Component produces. This can be the tension in a force element, the 29 * location of a body, metabolic energy consumption of a model, etc... 30 * It is the obligation of the Component to define its outputs. 31 */ 32 33 // INCLUDES 34 #include "Exception.h" 35 #include "Object.h" 36 37 #include <functional> 38 #include <map> 39 40 #include <SimTKcommon/internal/Stage.h> 41 #include <SimTKcommon/internal/State.h> 42 43 namespace OpenSim { 44 45 class Component; 46 class AbstractInput; 47 48 /** One of the values of an Output. */ 49 class AbstractChannel { 50 public: 51 virtual ~AbstractChannel() = default; 52 /** The name of this channel, or the name of the output that 53 contains this Channel if it's in a single-value Output. */ 54 virtual const std::string& getChannelName() const = 0; 55 /** The name of the value type (e.g., `double`) produced by this channel. */ 56 virtual std::string getTypeName() const = 0; 57 /** The name of this channel appended to the name of the output that 58 * contains this channel. The output name and channel name are separated by 59 * a colon (e.g., "markers:medial_knee"). If the output that contains 60 * this channel is a single-value Output, then this is just the Output's 61 * name. */ 62 virtual std::string getName() const = 0; 63 /** This returns the absolute path name of the component to which this channel 64 * belongs prepended to the channel's name. For example, this 65 * method might return something like "/model/metabolics|heat_rate:soleus_r". 66 */ 67 virtual std::string getPathName() const = 0; 68 }; 69 70 71 //============================================================================= 72 // OPENSIM COMPONENT OUTPUT 73 //============================================================================= 74 /** 75 * Output formalizes the access to a value of interest computed by the 76 * owning Component. The value is then exposed and easily accessible for use by 77 * other components (e.g. to satisfy an Input). 78 * The purpose of an Output is to bind a value of interest to a component's 79 * member function (generator), and provide a generic interface to the value, 80 * its type and label so it can be easily identified. It also specifies the 81 * realization (computational) stage at which the value is valid, so that a 82 * caller can provide adequate error handling. 83 * 84 * For example, a Body can have its position transformation with respect to 85 * ground as an Output, which is only accessible when the model has been 86 * realized to the Position stage or beyond, in which case it depends on the 87 * Position stage. The validity of data flow can be checked prior to 88 * initiating a simulation. 89 * 90 * An Output is intended to lightweight and adds no computational overhead 91 * if the output goes unused. When an Output's value is called upon, 92 * the overhead is a single redirect to the corresponding member function 93 * for the value. 94 * 95 * An Output can either be a single-value Output or a list Output. A list Output 96 * is one that can have multiple Channels. The Channels are what get connected 97 * to Inputs. 98 * @author Ajay Seth 99 */ 100 101 class OSIMCOMMON_API AbstractOutput { 102 public: AbstractOutput()103 AbstractOutput() : dependsOnStage(SimTK::Stage::Infinity) {} AbstractOutput(const std::string & name,SimTK::Stage dependsOnStage,bool isList)104 AbstractOutput(const std::string& name, SimTK::Stage dependsOnStage, 105 bool isList) : 106 name(name), dependsOnStage(dependsOnStage), _isList(isList) {} ~AbstractOutput()107 virtual ~AbstractOutput() { } 108 109 /** Output's name */ getName()110 const std::string& getName() const { return name; } 111 /** Output's dependence on System being realized to at least this System::Stage */ getDependsOnStage()112 const SimTK::Stage& getDependsOnStage() const { return dependsOnStage; } 113 /** Can this Output have more than one channel? */ isListOutput()114 bool isListOutput() const { return _isList; } 115 116 /** Output's owning Component */ getOwner()117 const Component& getOwner() const { return _owner.getRef(); } 118 119 /** This returns <absolute-path-to-component>|<output-name>. */ 120 std::string getPathName() const; 121 122 /** Output Interface */ 123 124 /** Remove all channels from this Output (for list Outputs). */ 125 virtual void clearChannels() = 0; 126 /** Add a channel to this Output. This should be called within the 127 * component's extendFinalizeFromProperties() .*/ 128 virtual void addChannel(const std::string& channelName) = 0; 129 virtual const AbstractChannel& getChannel(const std::string& name) const = 0; 130 131 /** The name of the value type (e.g., `double`) produced by this output. */ 132 virtual std::string getTypeName() const = 0; 133 virtual std::string getValueAsString(const SimTK::State& state) const = 0; 134 virtual bool isCompatible(const AbstractOutput&) const = 0; 135 virtual void compatibleAssign(const AbstractOutput&) = 0; 136 137 AbstractOutput& operator=(const AbstractOutput& o) 138 { compatibleAssign(o); return *this; } 139 140 virtual AbstractOutput* clone() const = 0; 141 142 /** Specification for number of significant figures in string value. */ getNumberOfSignificantDigits()143 unsigned int getNumberOfSignificantDigits() const { return _numSigFigs; } setNumberOfSignificantDigits(unsigned int numSigFigs)144 void setNumberOfSignificantDigits(unsigned int numSigFigs) 145 { _numSigFigs = numSigFigs; } 146 147 protected: 148 149 // Set the component that contains this Output. setOwner(const Component & owner)150 void setOwner(const Component& owner) { 151 _owner.reset(&owner); 152 } 153 154 SimTK::ReferencePtr<const Component> _owner; 155 156 private: 157 std::string name; 158 SimTK::Stage dependsOnStage; 159 unsigned int _numSigFigs = 8; 160 bool _isList = false; 161 162 // For calling setOwner(). 163 friend Component; 164 //============================================================================= 165 }; // END class AbstractOutput 166 167 template<class T> 168 class Output : public AbstractOutput { 169 public: 170 171 /// The concrete Channel type that corresponds to Output<T>. 172 class Channel; 173 174 /// The container type that holds onto all of Channels in an Output. 175 typedef std::map<std::string, Channel> ChannelMap; 176 177 //default construct output function pointer and result container Output()178 Output() {} 179 /** Convenience constructor 180 Create a Component::Output bound to a specific method of the Component and 181 valid at a given realization Stage. 182 @param name The name of the output. 183 @param outputFunction The output function to be invoked (returns Output T) 184 @param dependsOnStage Stage at which Output can be evaluated. 185 @param isList Can this Output have more than one channel? */ Output(const std::string & name,const std::function<void (const Component * comp,const SimTK::State &,const std::string & channel,T &)> & outputFunction,const SimTK::Stage & dependsOnStage,bool isList)186 explicit Output(const std::string& name, 187 const std::function<void (const Component* comp, 188 const SimTK::State&, 189 const std::string& channel, T&)>& outputFunction, 190 const SimTK::Stage& dependsOnStage, 191 bool isList) : 192 AbstractOutput(name, dependsOnStage, isList), 193 _outputFcn(outputFunction) { 194 if (!isList) { 195 // We want just one channel with an empty name. 196 _channels[""] = Channel(this, ""); 197 } 198 199 } 200 201 /** Custom copy constructor is for setting the Channel's pointer 202 * back to this Output. */ Output(const Output & source)203 Output(const Output& source) : AbstractOutput(source), 204 _outputFcn(source._outputFcn), _channels(source._channels) { 205 for (auto& it : _channels) { 206 it.second._output.reset(this); 207 } 208 } 209 210 /** Custom copy assignment operator is for setting the Channel's pointer 211 * back to this Output. */ 212 Output& operator=(const Output& source) { 213 if (&source == this) return *this; 214 AbstractOutput::operator=(source); 215 _outputFcn = source._outputFcn; 216 _channels = source._channels; 217 for (auto& it : _channels) { 218 it.second._output.reset(this); 219 } 220 return *this; 221 } 222 ~Output()223 virtual ~Output() {} 224 225 // TODO someone more knowledgeable could try to implement these. 226 Output(Output&&) = delete; 227 Output& operator=(Output&&) = delete; 228 isCompatible(const AbstractOutput & o)229 bool isCompatible(const AbstractOutput& o) const override { return isA(o); } compatibleAssign(const AbstractOutput & o)230 void compatibleAssign(const AbstractOutput& o) override { 231 if (!isA(o)) 232 SimTK_THROW2(SimTK::Exception::IncompatibleValues, 233 o.getTypeName(), getTypeName()); 234 *this = downcast(o); 235 } 236 clearChannels()237 void clearChannels() override { 238 if (!isListOutput()) 239 throw Exception("Cannot clear Channels of single-value Output."); 240 _channels.clear(); 241 } 242 addChannel(const std::string & channelName)243 void addChannel(const std::string& channelName) override { 244 if (!isListOutput()) 245 throw Exception("Cannot add Channels to single-value Output."); 246 if (channelName.empty()) 247 throw Exception("Channel name cannot be empty."); 248 _channels[channelName] = Channel(this, channelName); 249 } 250 251 /** For a single-value output, name must be empty or must be the output's 252 * name. */ getChannel(const std::string & name)253 const AbstractChannel& getChannel(const std::string& name) const override { 254 try { 255 if (!isListOutput() && name == getName()) return _channels.at(""); 256 return _channels.at(name); 257 } catch (const std::out_of_range&) { 258 OPENSIM_THROW(Exception, "Output '" + getName() + "' does not have " 259 "a channel named '" + name + "'."); 260 } 261 } 262 263 /** Use this to iterate through this Output's channels 264 (even for single-value Channels). 265 266 @code{.cpp} 267 for (const auto& chan : getChannels()) { 268 std::cout << chan.second->getName() << std::endl; 269 } 270 @endcode 271 */ getChannels()272 const ChannelMap& getChannels() const { return _channels; } 273 274 //-------------------------------------------------------------------------- 275 // OUTPUT VALUE 276 //-------------------------------------------------------------------------- 277 /** Return the Value of this output if the state is appropriately realized 278 to a stage at or beyond the dependsOnStage, otherwise expect an 279 Exception. */ getValue(const SimTK::State & state)280 const T& getValue(const SimTK::State& state) const { 281 if (isListOutput()) { 282 throw Exception("Cannot get value for list Output. " 283 "Ask a specific channel for its value."); 284 } 285 if (state.getSystemStage() < getDependsOnStage()) 286 { 287 throw SimTK::Exception::StageTooLow(__FILE__, __LINE__, 288 state.getSystemStage(), getDependsOnStage(), 289 "Output::getValue(state)"); 290 } 291 _outputFcn(_owner.get(), state, "", _result); 292 return _result; 293 } 294 getTypeName()295 std::string getTypeName() const override { 296 return OpenSim::Object_GetClassName<T>::name(); 297 } 298 getValueAsString(const SimTK::State & state)299 std::string getValueAsString(const SimTK::State& state) const override { 300 if (isListOutput()) { 301 throw Exception("Cannot get value for list Output. " 302 "Ask a specific channel for its value."); 303 } 304 unsigned int ns = getNumberOfSignificantDigits(); 305 std::stringstream s; 306 s << std::setprecision(ns) << getValue(state); 307 return s.str(); 308 } 309 clone()310 Output<T>* clone() const override { return new Output(*this); } 311 SimTK_DOWNCAST(Output, AbstractOutput); 312 313 /** For use in python/java/MATLAB bindings. */ 314 // This method exists for consistency with Object's safeDownCast. safeDownCast(AbstractOutput * parent)315 static Output<T>* safeDownCast(AbstractOutput* parent) { 316 return dynamic_cast<Output<T>*>(parent); 317 } 318 319 private: 320 mutable T _result; 321 std::function<void (const Component*, 322 const SimTK::State&, 323 const std::string& channel, 324 T& result)> _outputFcn { nullptr }; 325 // TODO consider using indices, and having a parallel data structure 326 // for names. 327 std::map<std::string, Channel> _channels; 328 329 //============================================================================= 330 }; // END class Output 331 332 333 template <typename T> 334 class Output<T>::Channel : public AbstractChannel { 335 public: 336 Channel() = default; Channel(const Output<T> * output,const std::string & channelName)337 Channel(const Output<T>* output, const std::string& channelName) 338 : _output(output), _channelName(channelName) {} getValue(const SimTK::State & state)339 const T& getValue(const SimTK::State& state) const { 340 // Must cache, since we're returning a reference. 341 _output->_outputFcn(_output->_owner.get(), state, _channelName, _result); 342 return _result; 343 } getOutput()344 const Output<T>& getOutput() const { return _output.getRef(); } getChannelName()345 const std::string& getChannelName() const override { 346 if (_channelName.empty()) return getOutput().getName(); 347 return _channelName; 348 } getTypeName()349 std::string getTypeName() const override { 350 return getOutput().getTypeName(); 351 } getName()352 std::string getName() const override { 353 if (_channelName.empty()) return getOutput().getName(); 354 return getOutput().getName() + ":" + _channelName; 355 } getPathName()356 std::string getPathName() const override { 357 return getOutput().getOwner().getAbsolutePathString() + "|" + getName(); 358 } 359 private: 360 mutable T _result; 361 SimTK::ReferencePtr<const Output<T>> _output; 362 std::string _channelName; 363 364 #ifndef SWIG // These declarations cause a warning in SWIG. 365 // To allow Output<T> to set the _output pointer upon copy. 366 friend Output<T>::Output(const Output&); 367 friend Output<T>& Output<T>::operator=(const Output&); 368 #endif 369 }; 370 371 // TODO consider using std::reference_wrapper<T> as type for _output_##oname, 372 // since it is copyable. 373 374 /// @name Creating Outputs for your Component 375 /// Use these macros at the top of your component class declaration, 376 /// near where you declare @ref Property properties. 377 /// @{ 378 /** Create an output for a member function of this component. 379 * The following must be true about componentMemberFunction, the function 380 * that returns the output: 381 * 382 * -# It is a member function of your component. 383 * -# The member function is const. 384 * -# It takes only one argument, which is `const SimTK::State&`. 385 * -# The function returns the computed quantity *by value* (e.g., 386 * `double computeQuantity(const SimTK::State&) const`). 387 * 388 * You must also provide the stage on which the output depends. 389 * 390 * Here's an example for using this macro: 391 * @code{.cpp} 392 * class MyComponent : public Component { 393 * public: 394 * OpenSim_DECLARE_OUTPUT(force, double, getForce, SimTK::Stage::Dynamics); 395 * ... 396 * }; 397 * @endcode 398 * 399 * @warning The fourth requirement above can be lifted if the function returns 400 * a quantity that is stored in the provided SimTK::State (as a state 401 * variable, cache variable, etc.); in this case, your function's return type 402 * should be `const T&` (e.g, `const double&`). If your function returns a 403 * `const T&` but the quantity is NOT stored in the provided SimTK::State, the 404 * output value will be invalid! 405 * 406 * @see Component::constructOutput() 407 * @relates OpenSim::Output 408 */ 409 #define OpenSim_DECLARE_OUTPUT(oname, T, func, ostage) \ 410 /** @name Outputs */ \ 411 /** @{ */ \ 412 /** Provides the value of func##() and is available at stage ostage. */ \ 413 /** This output was generated with the */ \ 414 /** #OpenSim_DECLARE_OUTPUT macro. */ \ 415 OpenSim_DOXYGEN_Q_PROPERTY(T, oname) \ 416 /** @} */ \ 417 /** @cond */ \ 418 bool _has_output_##oname { \ 419 this->template constructOutput<T>(#oname, &Self::func, ostage) \ 420 }; \ 421 /** @endcond */ 422 423 /** 424 * Create a list output for a member function of this component. A list output 425 * can have multiple values, or channels. The component must publish what channels 426 * its outputs have by calling AbstractOutput::addChannel() within 427 * Component::extendFinalizeFromProperties(). The provided member function must 428 * take the name of the channel whose value is requested. 429 * @code{.cpp} 430 * class MyComponent : public Component { 431 * public: 432 * double getData(const SimTK::State& s, const std::string& requestedChannel) const; 433 * OpenSim_DECLARE_LIST_OUTPUT(data, double, getData, SimTK::Stage::Dynamics); 434 * ... 435 * protected: 436 * void extendFinalizeFromProperties() { 437 * Super::extendFinalizeFromProperties(); 438 * for (const auto& name : getChannelsToAdd()) { 439 * updOutput("data").addChannel(name); 440 * } 441 * } 442 * }; 443 * @endcode 444 * In this example, `getChannelsToAdd()` is a placeholder for whatever way 445 * you determine your class' available channels. For example, TableSource_ 446 * uses the columns of its DataTable_. 447 * @relates OpenSim::Output 448 */ 449 #define OpenSim_DECLARE_LIST_OUTPUT(oname, T, func, ostage) \ 450 /** @name Outputs (list) */ \ 451 /** @{ */ \ 452 /** Provides the value of func##() and is available at stage ostage. */ \ 453 /** This output can have multiple channels. TODO */ \ 454 /** This output was generated with the */ \ 455 /** #OpenSim_DECLARE_LIST_OUTPUT macro. */ \ 456 OpenSim_DOXYGEN_Q_PROPERTY(T, oname) \ 457 /** @} */ \ 458 /** @cond */ \ 459 bool _has_output_##oname { \ 460 this->template constructListOutput<T>(#oname, &Self::func, ostage) \ 461 }; \ 462 /** @endcond */ 463 464 // Note: we could omit the T argument from the above macro by using the 465 // following code to deduce T from the provided func 466 // std::result_of<decltype(&Self::func)(Self, const SimTK::State&)>::type 467 // However, then we wouldn't be able to document the type for the output in 468 // doxygen. 469 470 /** Create an Output for a StateVariable in this component. The provided 471 * name is both the name of the output and of the state variable. 472 * 473 * While this macro is a convenient way to construct an Output for a 474 * StateVariable, it is inefficient because it uses a string lookup at runtime. 475 * To create a more efficient Output, create a member variable that returns the 476 * state variable directly (see Coordinate::getValue() or 477 * Muscle::getActivation()) and then use the #OpenSim_DECLARE_OUTPUT macro. 478 * 479 * @code{.cpp} 480 * class MyComponent : public Component { 481 * public: 482 * OpenSim_DECLARE_OUTPUT_FOR_STATE_VARIABLE(activation); 483 * ... 484 * }; 485 * @endcode 486 * @see Component::constructOutputForStateVariable() 487 * @relates OpenSim::Output 488 */ 489 #define OpenSim_DECLARE_OUTPUT_FOR_STATE_VARIABLE(oname) \ 490 /** @name Outputs */ \ 491 /** @{ */ \ 492 /** Provides the value of this class's oname state variable. */ \ 493 /** Available at stage SimTK::Stage::Model. */ \ 494 /** This output was generated with the */ \ 495 /** #OpenSim_DECLARE_OUTPUT_FOR_STATE_VARIABLE macro. */ \ 496 OpenSim_DOXYGEN_Q_PROPERTY(double, oname) \ 497 /** @} */ \ 498 /** @cond */ \ 499 bool _has_output_##oname { constructOutputForStateVariable(#oname) }; \ 500 /** @endcond */ 501 /// @} 502 //============================================================================= 503 //============================================================================= 504 505 } // end of namespace OpenSim 506 507 #endif // OPENSIM_COMPONENT_OUTPUT_H_ 508