4. Boost: Usage Examples¶
This page provides some examples on how to use shared_ptr
, make_shared
, function
and bind
, which were introduced on the Boost page, at application level and in combination with Tudat. Note that the first two are crucial for any Tudat user. The documentation on function
/bind
should be consulted only when working on feature development. The example of bind
also includes the use of the RK4 numerical integrator from Tudat.
4.3. function¶
The boost function library contains a family of class templates that are function object wrappers. It is used in Tudat instead of a normal C++ function pointer because it is an easy way to pass a function as an input to another object or function. Specifically, it allows the passing of free functions and class member functions with a single interface. The boost function type defines both its input types (and order) and its output type. Any function that fits this profile is accepted when it is passed.
- Example description
For this example, we will show the use of
function
with a free function. The example with a member function is shown in thebind
example below. We use the Himmelblau function as an example:f( x, y ) = ( x 2 + y - 11 )^2 + ( x + y 2 - 7 )^2For which we have the following C++ free function:
double himmelblau( double x, double y ) { return ( ( x * x + y - 11.0 ) * ( x * x + y - 11.0 ) + ( x + y * y - 7.0 ) * ( x + y * y - 7.0 ) ); }- Include statements
For this example, the following include statement is required:
#include <boost/function.hpp>Generally, when using
function
in your code, this include statement is required.- Defining the boost function type
The
function
type that corresponds to the Himmelblau function defined above is:boost::function< double ( double, double ) >;where the parameters between the angular brackets are the template parameters. These parameters comply to the form:
< output ( input(s) ) >where
output
is the output type andinput(s)
is the type of the input, this can be a list of input arguments.- Creating a boost function pointer
In our main function, we want to create a function pointer that complies to this form, which we name
exampleFunction
. We can use the following code to create the function pointer:boost::function< double ( double, double ) > exampleFunction = &himmelblau;where:
boost::function< double ( double, double ) >
is the type of exampleFunction, which describes the form of the function (input: double, double; output: double).exampleFunction
is the name of the object.&himmelblau
is the function object that is assigned (note the use of the ampersand &).- Using the function pointer
Later-on in the code the input argument to the function object
exampleFunction
can be provided using:double initialPoint = exampleFunction ( 0.0, 0.0 ); // Computes f( 0.0, 0.0 ) double randomPoint = exampleFunction ( -5.0, 3.0 ); // Computes f( -5.0, 3.0 )Here, the input values (0.0, 0.0 and -5.0, 3.0 respectively) are routed to the function
himmelblau
, where the output is computed.- Results
Using the code that was discussed in this example, you should be able to reproduce the following results:
x y f(x,y) 3.0 2.0 0.0 -3.779310 -3.283189 4.24634e-010 -2.0 3.0 16.0 0.0 0.0 170.0- Creating a function returning a constant value
There are cases in which you will want to create a function that always returns the same value, without any reference to a specific implementation. This is achieved by using the
boost::lambda::constant
function. This function can be created as follows:boost::function< double( ) > testFunction = boost::lambda::constant( 3.0 );Now, if at any point in the code, the following is called:
double testValue = testFunction( );will result in testValue holding the value 3.0. The same trick can be used for any other type than a double. For instance:
boost::function< Eigen::Vector3d( ) > testVectorFunction = boost::lambda::constant( Eigen::Vector3d::UnitX( ) );will create a function that always returns the (1,0,0) vector. Also, this interface can be used for boost functions requiring inputs, so that:
boost::function< double( const double ) > testFunction = boost::lambda::constant( 3.0 ); double inputValue = 20.0; double testValue = testFunction( inputValue );will ignore the input to
testFunction
and again assign 3.0 to thetestValue
variable.
4.4. bind¶
The boost bind library implements a simple and versatile function argument binding mechanism. This boost feature is used in Tudat because it is a good way to pass function objects. Its use is required for a unified free function and member function interface when using a class method, due to the way C++ calls function methods1.
When using boost bind in your code, it can take many forms, varying where the class method is called (e.g. outside or inside the class), and varying the use of placeholders. This example considers the use of placeholders and calling from outside or inside a class.
- Example description
The example described here is that of a Skydiver who starts its fall from an altitude of 25 kilometers. We are interested in its position and velocity after 50 seconds of free fall. The following assumptions are made:
- The Earth and the Skydiver are modeled as point masses.
- Variations in Earth’s gravity with altitude are ignored.
- All perturbations are neglected.
- The following data is also needed for the calculations:
- The initial altitude is 25 kilometers.
- The initial velocity is 0 m/s.
- The gravitational acceleration is constant at 9.81 m/s^2.
- The Runge-Kutta 4 (RK4) integrator is used with a fixed step size of 1 second.
- Include statements
For this example, the following include statements are required:
#include <boost/bind.hpp> #include <Eigen/Core> #include <Tudat/Mathematics/NumericalIntegrators/rungeKutta4Integrator.h>When using
bind
in your code, the first include statement is required. To make use ofEigen
types, theCore
of Eigen is included as well. The last include statement is used for the RK4 integrator. For more information on Eigen, see Eigen. For more information on numerical integrators, see Tutorial Numerical Integrators- Calling from outside the class
First, an example is given when calling a class method from outside the class. For this we will define the
Skydiver
class, with a constructor and a function that computes the state derivative for its equations of motion:class Skydiver { public: //! Constructor of the Skydiver class. Skydiver( ){ } //! Compute the state derivative for given time and state. Eigen::Vector2d computeStateDerivative( const double time, const Eigen::Vector2d& state ); protected: private: };The function to compute the state derivative is:
Eigen::Vector2d Skydiver::computeStateDerivative( const double time, const Eigen::Vector2d& state ) { // Note that the time is not used in this function, but it is required as input for the integrator. Eigen::Vector2d stateDerivative = Eigen::Vector2d::Zero( ); // Declare and initialize to zero. stateDerivative( 0 ) = state( 1 ); // Velocity stateDerivative( 1 ) = -9.81; // Acceleration return stateDerivative; }The Skydiver class and function to compute the derivative can be placed above the main function (or alternatively in a separate header and source file). Inside the main function, first declare the constants that were given above. Declare these yourself:
initialTime initialState // (which is of the type Eigen::Vector2d) timeStep endTimeThen, our Skydiver testDiver is constructed:
Skydiver testDiver;Next, an intermediate step is taken, before defining the integrator itself. The constructor of the RK4 integrator takes a state-derivative function, an initial time and an initial state. The state-derivative function is a
function
type that corresponds to:boost::function< Eigen::Vector2d ( const double, const Eigen::Vector2d ) >The state derivative is the class method
computeStateDerivative
of the Skydiver class, because this is a class method,bind
is used to create a function pointer to the state-derivative function:boost::function< Eigen::Vector2d ( const double, const Eigen::Vector2d ) > stateDerivativeFunction = boost::bind( &Skydiver::computeStateDerivative, &testDiver, _1, _2 );where:
boost::function< Eigen::Vector2d ( const double, const Eigen::Vector2d ) >
is the type of stateDerivativeFunction, which describes the form of the function (input: double, Vector2d; output: Vector2d.stateDerivativeFunction
is the name of the object.boost::bind( ... )
is the call to boost::bind that binds the class method of the object testDiver to the object.&Skydiver::computeStateDerivative
is the member function of the Skydiver class that is bound (note the use of the ampersand &).&testDiver
is the object that the member function belongs to. Note the use of the ampersand &: this is used to pass a pointer to the object and not the object itself. Note that you can also pass a shared_ptr._1
and_2
specify placeholders. This is used to route the input arguments such that the input of the function can be provided later-on. In this case it specifies that the argument list for the computeStateDerivative function takes two arguments (time, state), and the order in which these arguments are taken.This
boost::function
can then be passed to the constructor of the integrator, the integrator is created as follows:tudat::mathematics::numerical_integrators::RungeKutta4IntegratorXd integrator( stateDerivativeFunction, initialTime, initialState );where:
RungeKuttaIntegratorXd
is the type of the object created, which is the defaultRungeKutta4Integrator
.integrator
is the name of the object.stateDerivativeFunction
,initialTime
andinitialState
are the input arguments of the constructor of the RK4 integrator.Note that the previous two code fragments can also be coded as a single statement, which is more complex:
tudat::mathematics::numerical_integrators::RungeKutta4IntegratorXd integrator( boost::bind( &Skydiver::computeStateDerivative, &testDiver, _1, _2 ), initialTime, initialState );The end state can now be computed using the class method integrateTo( ) of the integrator:
const Eigen::Vector2d endState = integrator.integrateTo( endTime, timeStep );- Calling from inside the class
Second, an example is given when calling a class method from inside the class. For this we will modify the Skydiver class by adding a second class method:
class Skydiver { public: //! Constructor of the Skydiver class. Skydiver( ){ } //! Compute the state derivative for given time and state. Eigen::Vector2d computeStateDerivative( const double time, const Eigen::Vector2d& state ); //! Compute the final state for given initial conditions, final time and time step-size. Eigen::Vector2d computeFinalState( const double initialTime, const Eigen::Vector2d& initialState, const double endTime, const double timeStep ); protected: private: };The function to compute the state derivative is the same as above, and the function to compute the final state is:
Eigen::Vector2d Skydiver::computeFinalState( const double initialTime, const Eigen::Vector2d& initialState, const double endTime, const double timeStep ) { tudat::mathematics::numerical_integrators::RungeKutta4IntegratorXd integrator( boost::bind( &Skydiver::computeStateDerivative, this, _1, _2 ), initialTime, initialState ); return integrator.integrateTo( endTime, timeStep ); }Now, compare this function to the code of the previous example. The integrator is now used inside the class method
computeFinalState
. This method calls the state derivative function from within the class. Do you notice the difference?Focus on the
boost::bind
call. In the previous example, the object that the method belonged to wastestDiver
. Now, the class method is called from within the class and the object that the method belongs to is this. Did you spot it? Great! That is the difference between calling from outside or inside a class.As in the previous example, declare the constants that were given in the main function. In the same manner as before, also the
testDiver
is declared. Now the end state can be computed using:const Eigen::Vector2d endState = testDiver.computeFinalState( initialTime, initialState, endTime, timeStep );- Results
Using the code that was discussed in this example, you should be able to reproduce the following results:
Time [s] Position [m] Velocity [m/s] 10 24509.5 -98.1 20 23038 -196.2 50 12737.5 -490.5Note that the following explanation about function calls in C++ is a simplification of reality, and for a rigorous explanation refer to your local computer scientist. When a new instance of a class is created, this does not mean that all function definitions etc. are copied. There is only one copy of a function in memory, regardless of the amount of class instances. When a class method is called, the function is called with a hidden pointer to the class instance; this way the method knows about the class variables. When a class method is called via a function pointer, then the pointer to the class instance needs to be passed explicitly by the called. This is where
bind
comes into the picture. Thebind
function wraps a function into another object, and stores the passed arguments (which in the examples are respectivelytestDiver
and this). When the function wrapper is eventually called, the function wrapper will call the wrapped function with both the arguments passed at its construction, as those passed to the call. The arguments passed to the function wrapper call are indicated with the_1
, respectively_2
in thebind
call.
-
class
TestBody
¶ //! The test body class. /*! * This class serves as an example of how a data repository can be constructed that stores state * and time information, which can be used in conjunction with acceleration models, the Cartesian * state derivative model, and the composite state derivative model. It should be noted that this * class should NOT be used "as is", without consideration for the application at hand. Classes * such as this are application-specific, hence unavailable through the Tudat libraries. */ class TestBody { public: // Constructor that takes an input state and time. // The default value of the input time is 0.0. TestBody( const Eigen::Vector6d& aState, const double aTime = 0.0 ) : currentState( aState ), currentPosition( aState.segment( 0, 3 ) ), currentTime( aTime ) { } // Public member function: set the current time and state. // This sets the current time, state and position, that are stored internally. void setCurrentTimeAndState( const double aTime, const Eigen::Vector6d& aState ) { currentTime = aTime; currentState = aState; currentPosition = aState.segment( 0, 3 ); } // Public member function: get the current position. // This returns the internally stored current position vector. Eigen::Vector3d getCurrentPosition( ) { return currentPosition; } protected: private: // Private member: the current state. Eigen::Vector6d currentState; // Private member: the current position. Eigen::Vector3d currentPosition; // Private member: the current time. double currentTime; };