...
Code Block | ||||
---|---|---|---|---|
| ||||
namespace OpenSim { // Manages inputs to the optimization via OpenSim's XML abilities. class FlippinFelinesOptimizerTool : public Object { // ********** PART B ********** }; } // end namespace OpenSim // Finds a control input that achieves flip, as determined by objective function. class FlippinFelinesOptimizerSystem : public SimTK::OptimizerSystem { // ... FlippinFelinesOptimizerSystem(OpenSim::FlippinFelinesOptimizerTool & tool) : _tool(tool), // ... { // ... } // ... // The meat of this class; this is called by the Optimizer that has this system. int objectiveFunc(const SimTK::Vector & parameters, bool new_parameters, SimTK::Real & f) const { // ... f = ...; // ... } // ... // Reference to the FlippinFelinesOptimizerTool. OpenSim::FlippinFelinesOptimizerTool & _tool; // ... }; |
As you can see, our optimizer system must be a subclass of SimTK::OptimizerSystem. We override its objectiveFunc() function, which is then called by the SimTK::Optimizer as part of its algorithm. It's in objectiveFunc() that we (1) convert the optimizer's tweaks to optimization parameters into changes in the actuation control of the model, (2) run a forward dynamic simulation of the cat's fall, and (3) evaluate the simulation by assigning a value for our objective function, f
, which is what we calledon the main Flippin' Felines page. See SimTK::OptimizerSystem reference and SimTK::Optimizer reference for more information.
...
Code Block | ||||
---|---|---|---|---|
| ||||
// Initialize parameters for the optimization as those determined
// by our OptimizerSystem.
SimTK::Vector initParameters = sys.initialParameters();
// And we're off! Running the optimization
// --------------------------------------------------------------------
try
{
double f = opt.optimize(initParameters);
// Print the optimized model so we can explore the resulting motion.
sys.printModel(name + "_optimized.osim");
// Print the control splines so we can explore the resulting actuation.
sys.printPrescribedControllerFunctionSet(
name + "_optimized_parameters.xml");
// Print out the final value of the objective function.
std::cout << "Done with " << name << "! f = " << f << std::endl;
}
catch (...)
{
// Print the last model/controls (not optimized) so we have something
// to look at.
sys.printModel(name + "_last.osim");
sys.printPrescribedControllerFunctionSet(
name + "_last_parameters.xml");
std::cout << "Exception thrown; optimization not achieved." << std::endl;
// Don't want to give the appearance of normal operation.
throw;
}
}
else
{ // Too few/many inputs, etc.
std::cout << "\nIncorrect input provided. "
"Must specify the name of a FlippinFelinesOptimizerTool "
"serialization (setup/input file).\n\nExamples:\n\t"
"optimize optimize_input_template.xml\n" << std::endl;
return 0;
}
return EXIT_SUCCESS;
}; |
...
Code Block | ||||
---|---|---|---|---|
| ||||
namespace OpenSim {
class FlippinFelinesOptimizerTool : public Object {
OpenSim_DECLARE_CONCRETE_OBJECT(FlippinFelinesOptimizerTool, Object);
public:
// Declare properties of the class.
// ------------------------------------------------------------------------
// ********** PART B.1 **********
// Constructors
// ------------------------------------------------------------------------
// ********** PART B.2 **********
private:
void setNull() { }
void constructProperties()
{
// ********** PART B.3 **********
}
}; |
...
Code Block | ||||
---|---|---|---|---|
| ||||
/** * Finds a control input history that achieves certain desired features * of a cat's flipping maneuver, as determined by the objective function. * The control of the system is performed via a PrescribedController that * uses a spline for all actuators. * */ class FlippinFelinesOptimizerSystem : public SimTK::OptimizerSystem { public: FlippinFelinesOptimizerSystem(OpenSim::FlippinFelinesOptimizerTool & tool) : _tool(tool), _duration(1.0), _objectiveCalls(0), _objectiveFcnValueBestYet(SimTK::Infinity), { // Parse inputs & prepare output // -------------------------------------------------------------------- // ********** PART C.1 ********** // Add a controller to the model to control its actuators // -------------------------------------------------------------------- // ********** PART C.2 ********** // Set parameter limits/bounds for the optimization. // -------------------------------------------------------------------- // ********** PART C.3 ********** } SimTK::Vector initialParameters() { // ********** PART C.4 ********** } // ... /** * Defines desired features for the cat model's flip. Each call to this * function updates the model's control, runs a forward dynamic simulation * with this control, computes the objective value resulting from the * simulation, and saves the results to output. * */ int objectiveFunc(const SimTK::Vector & parameters, bool new_parameters, SimTK::Real & f) const { // ... // Unpack parameters into the model (i.e., update spline points) // -------------------------------------------------------------------- // ********** PART C.5 ********** // Run a forward dynamic simulation // -------------------------------------------------------------------- // ********** PART C.6 ********** // Construct the objective function value, term by term // -------------------------------------------------------------------- // ********** PART C.7 ********** // Update the log and outputs with the objective function value // -------------------------------------------------------------------- // ********** PART C.8 ********** } // Miscellaneous functions (used, but uninteresting) // ------------------------------------------------------------------------ // ********** PART C.9 ********** private: // Objective function terms // ------------------------------------------------------------------------ // ********** PART C.7 ********** // Member variables // ------------------------------------------------------------------------ // ********** PART C.10 ********** }; |
Throughout, we'll be making use of member variables we've defined. To see their declarations, see part Cjump to part C.10.
Note that objectiveFunc() is necessarily a const
function. That's This is because we wouldndon't want the state of the system to change changing as we are optimizing optimize it. For example, we wouldn't want to change relative masses of the cat while trying to find the optimum control input; the solution would be a moving target.
C.1: Parse inputs
...
& prepare output
Here's where we load the model. If the setup file specifies that we turn off the coordinate limit forces, we turn them off. We do this in the constructor because it's not something we would need to do in during each call to the objective function.
...
We will continue to update a log file, and print out various other files (e.g., best-yet controls), as the optimization runs. Here's some the code that creates a folder for our outputs and opens the log file.
...
Right now, our models have CoordinateActuator's, but we have no way of controlling them. We must add a controller to the model to do this. In general, a controller can manage any number of the model's actuators. We choose to have one controller that controls all of the cat's actuators separately. We More specifically, we want a controller that will specify beforehand what the actuator signal (torque, in our case) is will be throughout the simulation . One (NOTE: one alternative would be to use some sort of a feedback law. So in our case, we want to use ). Such a controller is a PrescribedController. With a PrescribedController, we specify for each actuator a function that is the torque that each CoordinateActuator applies as a function of time in the simulationsimulation time. We choose to use splines (SimmSpline's ) as the functions.
Now, we We want our optimization to solve for the control input (prescribed control) that achieves flips the flipcat. So that means , our optimization parameters need to be related to our SimmSpline's. The simplest way to relate them is by choosing the optimization parameters to be the Y-values of the spline points. The As seen above, the number of spline points is specified in the setup file; the corresponding X-values (times) are preset as evenly-spaced , and the number of spline points is a settingover the simulation.
Parameters are ordered by actuator, then by spline point index for a given actuator. Parameters are nondimensionalized by the minimum or maximum control values for the associated actuator.
...
We tell the optimizer that there are bounds on the values of the parameters. We've chosen to nondimensionalize our parameters, so our bounds are simple. The OptimizerSystem::setParameterLimits() function is public, so sometimes you might see that this function is might be called in optimize.cpp instead.
...
We wanted to make it really easy for the user to specify the initial parameters for the optimization. Since they these parameters end up as Y-coordinates of splinesthe spline points, we thought this would that SimmSpline's would be the easiest way for a user to specify the initial parametersform of input. However, we need the initial parameters as a Vector. So, we wrote a method to convert the Y-coordinates from a FunctionSet of SimmSpline's into a Vector.
...
C.5: Unpack parameters into the model
The At this point, the Optimizer has given us new parameters to try outtry. We need to use these parameters to change the Y-values of the splinesplines. First, we We must first dimensionalize the parameters.
...
Now that we've updated the control functionscontrols, we can run a simulation and see how the cat ends up.
Code Block | ||||
---|---|---|---|---|
| ||||
SimTK::State& initState = _cat.initSystem(); // Construct an integrator. SimTK::RungeKuttaMersonIntegrator integrator(_cat.getMultibodySystem()); integrator.setAccuracy(1.0e-6); // Construct a manager to run the integration. OpenSim::Manager manager(_cat, integrator); _cat.getMultibodySystem().realize(initState, SimTK::Stage::Acceleration); // Integrate from initial time to final time manager.setInitialTime(0.0); manager.setFinalTime(_duration); manager.integrate(initState); |
C.7:
...
Construct the objective function value
Here's the brain of the optimization. We assign to f
the value of the objective function. Here, we have 3 terms in our objective function, which are described on the main Flippin' Felines page. The source code contains a few more terms; we've cut them out for simplicity.
...
We use some helper functions to keep this code clean. This first helper function is called by each of the "*Term()" functions above, and ; it provides a convenient way of, to (in one step, updating ) update the log and weighting weight a term in the function.
Code Block | ||||
---|---|---|---|---|
| ||||
/** * Helper function for writing data to the log. * */ double processObjTerm(std::string name, double weight, double term) const { if (_objectiveCalls % _outputPeriod == 0) _optLog << " " << name << " " << weight * term; return weight * term; } |
The following functions must be 'const
' because because objectiveFunc() is is const
; we cannot call member functions within a a const
member member function unless those functions are also also const
. These functions are also defined inline (i.e., definition is inside the class definition) for efficiency: the compiler will 'paste' the function body into the place where the function is called.
...
We now return to objectiveFunc(), right after the code where we've assembled f
. We update the log with the new objective function value , and might print an updated model file and updated control functions/controls.
Code Block | ||||||
---|---|---|---|---|---|---|
| ||||||
_lastCallWasBestYet = _thisCallIsBestYet; _thisCallIsBestYet = f <= _objectiveFcnValueBestYet; if (_thisCallIsBestYet) _objectiveFcnValueBestYet = f; if (_objectiveCalls % _outputPeriod == 0) _optLog << " objfcn " << f << " objfcn_best_yet " << _objectiveFcnValueBestYet << std::endl; // If this is the best yet (i.e., smallest objective function value), // save a copy of the splines. if (_thisCallIsBestYet) { for (unsigned int iFcn = 0; iFcn < _splinesBestYet.size(); iFcn++) _splinesBestYet[iFcn] = *_splines[iFcn]; } // If this is worse, print out the best-yet splines and current model // (NOTE: current model is NOT "best yet", but the idea is that it will // be close). Also print out nondimensionalized splines so that they can // be used directly as input for a subsequent optimization. if (_lastCallWasBestYet && !_thisCallIsBestYet) { printBestYetPrescribedControllerFunctionSet( _name + "_best_yet_parameters.xml"); printBestYetPrescribedControllerFunctionSet( _name + "_best_yet_parameters_nomdim.xml", true); printModel(_name + "_best_yet.osim"); } // Print out to the terminal/console every so often. if (_objectiveCalls % _outputPeriod == 0) std::cout << "Objective call # " << _objectiveCalls << std::endl; return 0; |
C.9: Miscellaneous functions
There Here are some functions that we use, but that aren't too important for learning how optimization works. They are used to print either the model or control functions.
...
We've given you some setup/input files to for the optimization to get you started and to reproduce reproducing our results. Enter a terminal or command prompt, navigate to a the directory containing the optimize executable and the counterrotation_setup.xml file. The following will start an optimization. It should take under 2 hours to complete.
...
Compare your results to those in the 'References' folder of the 'Tutorial code' codedirectory. You can do the same repeat this for the second optimization, if counterrotation_variableinertia_setup.xml and counterrotation_variableinertia_initial_parameters.xml are in the directory.
...
Remember that it's the setup file that refers to the initial parameters file. This second optimization should take about the same amount of time to complete.
Enjoy playing around with the code. We hope you've learned something useful!
...