...
Here, we show the complete optimize.cpp. The user must provide one command-line argument, which is the name of an XML file that defines an a FlippinFelinesOptimizerTool object (we'll get to this in the next section). A serialization is a representation of an object or objects in data files (which, in our case, is a plain text file in XML format). See this page for more information about C++ command-line arguments.
Code Block | ||||
---|---|---|---|---|
| ||||
#include <iostream> #include <OpenSim/OpenSim.h> #include "FlippinFelinesOptimizerSystem.h" int main(int argc, char * argv[]) { // argc is the number of command line inputs, INCLUDING the name of the // exectuable. Thus, it'll always be greater than/equal to 1. // argv is an array of space-delimited command line inputs, the first one // necessarily being the name of the executable (e.g., "optimize // optimize_input_template.xml"). // Get the filename of the FlippinFelinesOptimizerTool serialization. if (argc == 2) { // Correct number of inputs. // Set-up // -------------------------------------------------------------------- // Parse inputs using our Tool class. std::string toolSetupFile = argv[1]; OpenSim::FlippinFelinesOptimizerTool tool(toolSetupFile); |
...
We create an optimizer that will optimize our FlippinFelinesOptimizerSystem, and tell the optimizer to figure out the best algorithm to use for the optimization. We played around with the settings a littlebit. We set the maximum number of iterations to be large just because we don't want so that the optimizer to does not quit early.
Code Block | ||||
---|---|---|---|---|
| ||||
// Create the optimizer with our system and the "Best Available" // algorithm. SimTK::Optimizer opt(sys, SimTK::BestAvailable); // Set optimizer settings. opt.setConvergenceTolerance(0.001); opt.useNumericalGradient(true); opt.setMaxIterations(100000); opt.setLimitedMemoryHistory(500); |
When we call optimize(), we need to give the optimizer a place to start in the parameter space of the parameters. Typically, one would define these initial parameters right here, in the same function where we call optimize(). However, we did not want to hard-code our initial parameters in the source code; we wanted to change this them between optimizations easily. So it's actually something we can specify , the initial parameters are actually specified in the toolSetupFile. The FlippinFelinesOptimizerSystem parses that input , and gives us the initial parameters in a Vector if we call FlippinFelinesOptimizerSystem::initialParameters(). We then pass this to the Optimizer.
The function optimize() returns when the optimization finishes. Once that happens, we print out the model (that should now be able to flip), as well as the actuation controls that produced the flip. In the case that anything goes wrong (an exception is thrown), we still want to see what the optimizer ended up finding (so as not to waste the failed optimization).
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; }; |
...
B: FlippinFelinesOptimizerTool: optimization settings and serialization
Although this part of the Below is a skeleton of the FlippinFelinesOptimizerTool definition. There are three main parts to the definition; we'll address each individually. Although this code is not vital for setting up optimizations, we discuss it now , since the rest of the our code depends on it. Below is a skeleton of the FlippinFelinesOptimizerTool definition. There are three main parts to the definition, which we'll get to shortly.This class
The FlippinFelinesOptimizerTool class operates similarly to OpenSim's tools (e.g., for the Forward Tool), which can be run via the command line by providing an XML setup file. The way weWe'd like to use our optimizer is via the command like via commands like in a similar way: for example, optimize optimize_setup_file.xml
. This Of course, this requires the ability to parse that the provided XML file. That's what this FlippinFelinesOptimizerTool the class is all about.
If we can define a class whose member variables are the settings for our optimization, and we can serialize this class into a text file, then we have a record of the settings we used for a given optimization. Furthermore, if we can deserialize a text file to create an instance of the class we originally serialized, then we have a really nice way to read that the optimize_setup_file.xml
input file to set the settings within for our optimization.
Fortunately, all OpenSim classes have the ability to serialize themselves, themselves and to deserialize a file into an instance of an OpenSim class. To create a class that has these features, all we need to do is subclass from OpenSim::Object, and and define our member variables with a specific syntax. OpenSim uses its ability to de/serialize all the time (all XML files produced by OpenSim or given to OpenSim are serializations done in this way), but we can use this feature for our own purposes as well. That's what we do here; we call our subclass FlippinFelinesOptimizerTool, since since the class functions similarly (but is different!) from OpenSim Tool and AbstractTool classes.
...
We create member variables using the OpenSim_DECLARE_PROPERTY and OpenSim_DECLARE_OPTIONAL_PROPERTY macros, which are described here. For simplicity, we've omitted many of the properties we have in the actual source code. For a member variable to be de/serializable, it must be delcared via this macro. Some of the properties help with general setup (e.g., results_directory, model_filename), while others modify the computation of the objective function value. These (e.g., desired configurations and weightings). The macros define getters/setters for us; we'll see the usage of the getters in other parts of the code.
...
Classes are serialized by calling their print() method. To be able to deserialize a file, the associated class must have a constructor that takes in a filename , and passes it to the constructor of Object. The properties in the class are overwritten by the values in the serialization by calling the method Object::updateFromXMLDocument().
...
Below is what the XML serialization of this class looks like. This is exactly what we meant by the optimizer_setup_file.xml
above. This is a simplified version of what's generated by the executable optimize_input_template, which is generated from optimize_input_template.cpp. We've given you this file, but we do not discuss it in this the tutorial.
Code Block | ||||
---|---|---|---|---|
| ||||
<?xml version="1.0" encoding="UTF-8" ?> <OpenSimDocument Version="30000"> <FlippinFelinesOptimizerTool> <!--Directory in which to save optimization log and results--> <results_directory>results</results_directory> <!--Specifies path to model file, WITH .osim extension--> <model_filename>flippinfelines_*FILL THIS IN*.osim</model_filename> <!--Number of points being optimized in each spline function. Constant across all splines. If an initial_parameters_filename is provided, the functions specified in that file must have the correct number of points. We do not error-check for this.--> <num_optim_spline_points>20</num_optim_spline_points> <!--Adds terms to the objective to minimize final value of (roll - Pi) and related speeds--> <anterior_legs_down_weight>1</anterior_legs_down_weight> <!--Adds terms to the objective to minimize final value of (twist - 0) and related speeds--> <posterior_legs_down_weight>1</posterior_legs_down_weight> <!--Adds a term to the objective to minimize final value of (hunch + 2 * pitch)--> <sagittal_symmetry_weight>1</sagittal_symmetry_weight> <!--TRUE: use coordinate limit forces, FALSE: ignore coordinate limit forces--> <use_coordinate_limit_forces>true</use_coordinate_limit_forces> <!--File containing FunctionSet of SimmSpline's used to initialize optimization parameters. If not provided, initial parameters are all 0.0, and this element must be DELETED from the XML file (cannot just leave it blank). The name of each function must be identical to that of the actuator it is for. x values are ignored. The time values that are actually used in the simulation are equally spaced from t = 0 to t = 1.0 s, and there should be as many points in each function as given by the num_optim_spline_points property. y values should be nondimensional and between -1 and 1 (negative values normalized by minControl if minControl is negative; otherwise the value is normalized by maxControl). NOTE that the output optimized splines are NOT NONDIMENSIONAL. Be careful, we do not do any error checking.--> <initial_parameters_filename>initial_parameters.xml</initial_parameters_filename> </FlippinFelinesOptimizerTool> </OpenSimDocument> |
...