Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 32 Next »

To get our models to flip, we want to add actuation control to them, and tweak the control until we obtain our desired flip motion. We have an optimization algorithm take care of the tweaking for us. The algorithm will tweak the control in some intelligent way, then we'll run a forward dynamic simulation with the given controls, and we'll see if the cat flips. The algorithm continues to tweak the control until we decide that the cat model has achieved a flip.

This procedure of algorithmically tweaking control inputs, running a forward dynamic simulation, then judging how well the model/simulation achieved some objective is called dynamic optimization. It is the way that biomechanists would prefer to study human movement (see Sky Higher and  Miller, 2012 for just two of many example), and is commonly viewed as the alternative to tracking simulations. The models used for studying such motions are complex, and so it takes a long time to run a dynamic optimization (days to weeks). For cat flipping, however, an optimization takes only an hour or two. Thus, it's an ideal problem for getting one's feet wet with dynamic optimization.

Organization of code

First, we present an overview of the 2 source files we use for the optimization. The file optimize.cpp creates an executable that mostly just creates the necessary objects, and runs the optimization. The code block below shows only 4 lines from this file, the most important lines, that show how we connect the various pieces of the optimization. Look at the next code block, a skeleton of the second file, FlippinFelinesOptimizerSystem.h, to see where we define the FlippinFelinesOptimizerTool and FlippinFelinesOptimizerSystem classes.

optimize.cpp (incomplete snippet)
int main(int argc, char * argv[])
{
    // ...

    // A class we made to read inputs to the optimization via an XML file.
    OpenSim::FlippinFelinesOptimizerTool tool(toolSetupFile);
    // ...

    // Our subclass of SimTK::OptimizerSystem; pass to it our inputs.
    FlippinFelinesOptimizerSystem sys(tool);
    // ...

    // The object that runs an optimization algorithm on the system we created.
    SimTK::Optimizer opt(sys, SimTK::BestAvailable);
    // ...

    // This function is what actually runs the optimizer.
    double f = opt.optimize(initParameters);

};

To run any optimization, it's necessary to use SimTK::Optimizer and a class like FlippinFelinesOptimizerSystem. However, a class like FlippinFelinesOptimizerTool is not necessary; it's just something we've done to make it easier to run these optimizations. (TODO so we'll focus on the first 2).

FlippinFelinesOptimizerSystem.h (incomplete snippet)
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 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.

A: Optimize.cpp: The executable that ties the pieces together

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 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.

optimize.cpp
#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);

One of the inputs provided in the FlippinFelinesOptimizerTool serialization is the name of a directory in which to save outputs of the optimization. We can grab it from the FlippinFelinesOptimizerTool as shown below. The only input our FlippinFelinesOptimizerSystem needs is this FlippinFelinesOptimizerTool object, which actually contains all our inputs.

optimize.cpp
        std::string name = tool.get_results_directory();

        // Use inputs to create the optimizer system.
        FlippinFelinesOptimizerSystem sys(tool);

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 little. We set the maximum number of iterations to be large just because we don't want the optimizer to quit early.

optimize.cpp
        // 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 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 between optimizations easily. So it's actually something we can specify 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).

optimize.cpp
        // 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 code is not vital for setting up optimizations, we discuss it now, since the rest of the 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 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 we'd like to use our optimizer is via the command like via commands like optimize optimize_setup_file.xml. This requires the ability to parse that XML file. That's what this FlippinFelinesOptimizerTool 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 optimize_setup_file.xml input file to set settings within our optimization.

Fortunately, all OpenSim classes have the ability to serialize 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 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 the class functions similarly (but is different!) from OpenSim Tool and AbstractTool classes.

Note that the (unattractive) alternative to using a setup file would be to recompile our code every time we wanted to change a setting. In that case, we don't really have a record of the settings we used for previous runs, since we've changed the source code and erased our previous settings.

Whenever subclassing from an OpenSim class, we must use a macro such as OpenSim_DECLARE_CONCRETE_OBJECT. See here for specifics.

FlippinFelinesOptimizerSystem.h
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 **********
    }
};

B.1: Declare properties of the class

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 macros define getters/setters for us; we'll see the usage of the getters in other parts of the code.

FlippinFelinesOptimizerSystem.h
// General properties.
OpenSim_DECLARE_PROPERTY(results_directory, std::string,
        "Directory in which to save optimization log and results");
OpenSim_DECLARE_PROPERTY(model_filename, std::string,
        "Specifies path to model file, WITH .osim extension");
OpenSim_DECLARE_PROPERTY(num_optim_spline_points, int,
        "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.");
// Properties related to the objective function.
OpenSim_DECLARE_PROPERTY(anterior_legs_down_weight, double,
        "Adds terms to the objective to minimize final value of "
        "(roll - Pi) and related speeds");
OpenSim_DECLARE_PROPERTY(posterior_legs_down_weight, double,
        "Adds terms to the objective to minimize final value of "
        "(twist - 0) and related speeds");
OpenSim_DECLARE_PROPERTY(sagittal_symmetry_weight, double,
        "Adds a term to the objective to minimize final value of "
        "(hunch + 2 * pitch)");
// Modifying the model before optimizing.
OpenSim_DECLARE_PROPERTY(use_coordinate_limit_forces, bool,
        "TRUE: use coordinate limit forces, "
        "FALSE: ignore coordinate limit forces");
// Setting initial parameters for the optimization.
OpenSim_DECLARE_OPTIONAL_PROPERTY(initial_parameters_filename, std::string,
        "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.");

B.2: Constructors

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().

FlippinFelinesOptimizerSystem.h
FlippinFelinesOptimizerTool() : Object()
{
    setNull();
    constructProperties();
}
// NOTE: This constructor allows for the de/serialization.
FlippinFelinesOptimizerTool(const std::string &aFileName, bool
        aUpdateFromXMLNode=true) : Object(aFileName, aUpdateFromXMLNode)
{
    setNull();
    constructProperties();
    // Must be called after constructProperties():
    updateFromXMLDocument();
}

Before we can do that though, we need to construct the properties we've declared.

B.3: Constructing the properties we've declared

These constructProperty functions, generated by the macros in part B.1, require an initial/default value for the property.

FlippinFelinesOptimizerSystem.h
void constructProperties()
{
    constructProperty_results_directory("results");
    constructProperty_model_filename("flippinfelines_*FILL THIS IN*.osim");
    constructProperty_num_optim_spline_points(20);
    constructProperty_anterior_legs_down_weight(1.0);
    constructProperty_posterior_legs_down_weight(1.0);
    constructProperty_sagittal_symmetry_weight(1.0);
    constructProperty_use_coordinate_limit_forces(true);
    constructProperty_initial_parameters_filename("");
}

A sample input file

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 tutorial.

optimize_setup_file.xml
<?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> 

 

 

C: FlippinFelinesOptimizerSystem

Here's a skeleton of the FlippinFelinesOptimizerSystem class. The two functions that are the most important are the constructor and objectiveFunc(). We focus on these. The definition of an optimization problem consists primarily of (1) parameters/inputs, and (2) the objective function. The parts that deal with the parameters are C.2, C.3, C.4, and C.5

FlippinFelinesOptimizerSystem.h
 /**
 * 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.
 *
 * Parameters are ordered by actuator, then by spline point index for a
 * given actuator. Parameters are nondimensionalized by the min or max
 * control values for the associated actuator.
 * */
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, 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 C.10.

C.1: Parse inputs and prepare output

FlippinFelinesOptimizerSystem.h
// Parse inputs
// --------------------------------------------------------------------
_name = _tool.get_results_directory();
_cat = OpenSim::Model(_tool.get_model_filename());

// Disable coordinate limit forces?
if (!_tool.get_use_coordinate_limit_forces())
{
    SimTK::State& initState = _cat.initSystem();
    // Loop over all forces in model.
    using OpenSim::CoordinateLimitForce;
    for (int iFor = 0; iFor < _cat.getForceSet().getSize(); iFor++)
    {
        CoordinateLimitForce * LF = dynamic_cast<CoordinateLimitForce *> (&_cat.updForceSet().get(iFor));
        if (LF)
        { // If it is a coordinate limit force, disable it.
            _cat.updForceSet().get(iFor).setDisabled(initState, true);
        }
    }
}

_numOptimSplinePoints = _tool.get_num_optim_spline_points();

// Prepare output
// --------------------------------------------------------------------

// Create a results directory for all the output files.
#if defined(_WIN32)
    _mkdir(_name.c_str());
#else
    mkdir(_name.c_str(), 0777);
#endif

// Serialize what we just deserialized, in the results directory, to keep
// everything in one organized place.
_tool.print(_name + "/" + _name + "_setup.xml");

// Create a log for the objective function value and the separate terms
// that go into it.
_optLog.open((_name + "/" + _name + "_log.txt").c_str(), std::ofstream::out);
_optLog << "Flippin Felines optimization log: " << _name << std::endl;
time_t rawtime; time(&rawtime);
_optLog << ctime(&rawtime);
_optLog << "Model file name: " << _tool.get_model_filename() << std::endl;

C.2: Add a controller to the model to control its actuators

FlippinFelinesOptimizerSystem.h
// Compute the number of optimization parameters for the model.
_numActuators = _cat.getActuators().getSize();
int numParameters = _numActuators * _numOptimSplinePoints;
setNumParameters(numParameters);

// Size the vector of best-yet splines.
_splinesBestYet.resize(_numActuators);

// Add a PrescribedController to the model.
using OpenSim::PrescribedController;
PrescribedController * flipController = new PrescribedController();
flipController->setName("flip_controller");

// Set the PrescribedController to control all actuators.
flipController->setActuators(_cat.getActuators());

// Add the PrescribedController to the model.
_cat.addController(flipController);

// Create SimmSpline's for each actuator and push into vector.
double indexToTimeInSeconds =
    _duration / (double)(_numOptimSplinePoints - 1);
for (int i = 0; i < _numActuators; i++)
{
    // Create a spline function for the current actuator.
    _splines.push_back(new OpenSim::SimmSpline());
    // Name this spline with the name of the current actuator.
    _splines[i]->setName(_cat.getActuators().get(i).getName());
    // Add the correct number of points to this spline.
    for (int j = 0; j < _numOptimSplinePoints; j++)
    {
        // Scale the time points appropriately.
        double thisTime = (double)j * indexToTimeInSeconds;
        _splines[i]->addPoint(thisTime, 0.0);
    }
    // Tell the controller about this function.
    flipController->prescribeControlForActuator(i, _splines[i]);
} 

C.3: Set parameter limits/bounds for the optimization

FlippinFelinesOptimizerSystem.h
// Set (nondimensional) parameter limits/bounds for the actuators.
SimTK::Vector lowerLimits(getNumParameters(), -1.0);
SimTK::Vector upperLimits(getNumParameters(), 1.0);
setParameterLimits(lowerLimits, upperLimits);

C.4: Initial parameters

FlippinFelinesOptimizerSystem.h
SimTK::Vector initialParameters()
{
    SimTK::Vector initParams(getNumParameters(), 0.0);
    if (_tool.get_initial_parameters_filename() != "")
    { // A file is specified.
        // Deserialize the initial parameters XML file.
        OpenSim::FunctionSet initFcns(_tool.get_initial_parameters_filename());
        // Write a copy of the FunctionSet to the results directory.
        initFcns.print(_name + "/" + _name + "_initial_parameters.xml");
        OpenSim::Array<std::string> initNames;
        initFcns.getNames(initNames);
        
        // This loop is set up so that the initFcns do not need to be in the same
        // order as the optimization parameters. Also, the number of initFcns
        // specified by the initial parameters file can be greater than the
        // number of actuators in the model (assuming that the initial parameters
        // file AT LEAST contains the model's actuators).
        using OpenSim::SimmSpline;
        for (int iAct = 0; iAct < _numActuators; iAct++)
        {
            // Get index of the initFcn corresponding to the actuator.
            int iFcn = initFcns.getIndex(_cat.getActuators().get(iAct).getName());
            // Get access to the spline's methods.
            SimmSpline * fcn =
                dynamic_cast<SimmSpline *>(&initFcns.get(iFcn));
            for (int iPts = 0; iPts < _numOptimSplinePoints; iPts++)
            {
                // Find the right index in the optimization parameters.
                int paramIndex = iAct * _numOptimSplinePoints + iPts;
                // Transfer y value from input to initial parameters.
                initParams[paramIndex] = fcn->getY(iPts);
            }
        }
    }
    return initParams;
}

C.5: Unpack parameters into the model

FlippinFelinesOptimizerSystem.h
// Increment the count of calls to this function.
_objectiveCalls++;

// Unpack parameters into the model (i.e., update spline points)
// --------------------------------------------------------------------
for (int iAct = 0; iAct < _numActuators; iAct++)
{
    // Max/min for all spline points for the i-th actuator.
    double minControl = _cat.getActuators().get(iAct).getMinControl();
    double maxControl = _cat.getActuators().get(iAct).getMaxControl();

    // Visit each point in this spline.
    for (int iPts = 0; iPts < _numOptimSplinePoints; iPts++)
    {
        int paramIndex = iAct * _numOptimSplinePoints + iPts;

        // Dimensionalize the control value.
        double dimControlValue;
        if  (parameters[paramIndex] < 0 && minControl < 0)
        { // parameter and minControl are both neg; must negate again.
            dimControlValue = -minControl * parameters[paramIndex];
        }
        else
        {
            dimControlValue = maxControl * parameters[paramIndex];
        }

        // Edit the spline with the dimensional control value.
        _splines[iAct]->setY(iPts, dimControlValue);
    }
}

C.6: Run a forward dynamic simulation

FlippinFelinesOptimizerSystem.h
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: The objective function

FlippinFelinesOptimizerSystem.h
// Will be writing to a log while constructing the objective function.
if (_objectiveCalls % _outputPeriod == 0)
    _optLog << _objectiveCalls;

// Copy the model's state. We need a consistent state to access the
// model's configuration and related speeds.
SimTK::State aState = initState;
_cat.getMultibodySystem().realize(aState, SimTK::Stage::Acceleration);
const OpenSim::CoordinateSet& coordinates = _cat.getCoordinateSet();
double roll = coordinates.get("roll").getValue(aState);
double rollRate = coordinates.get("roll").getSpeedValue(aState);
double rollAccel = coordinates.get("roll").getAccelerationValue(aState);
double twist = coordinates.get("twist").getValue(aState);
double twistRate = coordinates.get("twist").getSpeedValue(aState);
double twistAccel = coordinates.get("twist").getAccelerationValue(aState);
double hunch = coordinates.get("hunch").getValue(aState);
double pitch = coordinates.get("pitch").getValue(aState);
double pitchRate = coordinates.get("pitch").getSpeedValue(aState);
double pitchAccel = coordinates.get("pitch").getAccelerationValue(aState);

// --------------------------------------------------------------------
f = 0;
f += anteriorLegsDownTerm(roll, rollRate, rollAccel);
f += posteriorLegsDownTerm(twist, twistRate, twistAccel);
f += sagittalSymmetryTerm(hunch, pitch, pitchRate, pitchAccel);
// --------------------------------------------------------------------
FlippinFelinesOptimizerSystem.h
/**
 * 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 objectiveFunc() is const; we cannot call member functions within a const member function unless those functions are also const. These functions are also defined inline (definition is inside the class definition) for efficiency: the compiler will 'paste' the function body into the place where the function is called.

FlippinFelinesOptimizerSystem.h
double anteriorLegsDownTerm(
        double roll, double rollRate, double rollAccel) const
{
    if (_tool.get_anterior_legs_down_weight() != 0.0)
    {
        return processObjTerm("anterior_legs_down", _tool.get_anterior_legs_down_weight(),
                pow(roll - SimTK::Pi, 2) + _relw * pow(rollRate, 2) + _relw * pow(rollAccel, 2));
    }
    return 0.0;
}
double posteriorLegsDownTerm(
        double twist, double twistRate, double twistAccel) const
{
    if (_tool.get_posterior_legs_down_weight() != 0.0)
    {
        return processObjTerm("posterior_legs_down", _tool.get_posterior_legs_down_weight(),
                pow(twist - 0.0, 2) + _relw * pow(twistRate, 2) + _relw * pow(twistAccel, 2));
    }
    return 0.0;
} 

double sagittalSymmetryTerm(
        double hunch, double pitch, double pitchRate, double pitchAccel) const
{
    if (_tool.get_sagittal_symmetry_weight() != 0.0)
    {
        return processObjTerm("sagittal_symmetry", _tool.get_sagittal_symmetry_weight(),
                pow(hunch + 2 * pitch, 2) + _relw * pow(pitchRate, 2) + _relw * pow(pitchAccel, 2));
    }
    return 0.0;
}

C.8: Update the log and outputs

We now return to objectiveFunc(), after we've finished assembling f.

FlippinFelinesOptimizerSystem.h
_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 are some functions that we use, but that aren't that important for learning how optimization works. They are used to print either the model or control functions.

FlippinFelinesOptimizerSystem.h
/**
 * @brief Prints the current model to the given filename.  The file will
 * be located in the directory containing the log file for this
 * optimization run.
 * */
void printModel(std::string filename) const
{
    _cat.print(_name + "/" + filename);
}
/**
 * @brief Serializes the current set of functions used for the actuators.
 * The file will be located in the directory containing the log file
 * for this optimization run.
 * */
void printPrescribedControllerFunctionSet(std::string filename) const
{
    // Create the FunctionSet that we'll then serialize.
    OpenSim::FunctionSet fset;
    fset.setSize(_splines.size());
    for (unsigned int iFcn = 0; iFcn < _splines.size(); iFcn++)
    {
        fset.insert(iFcn, *_splines[iFcn]);
    }
    fset.print(_name + "/" + filename);
}
/**
 * @brief Serializes the set of functions associated with the best-yet
 * value of the objective function. The file will be located in the
 * directory containing the log file for this optimization run.
 *
 * @param (nondimensionalize) Divide spline values by minControl for the
 * actuator, if value and minControl are negative, and by maxControl, if
 * value is positive. Once nondimensionalized, this FunctionSet can be used
 * as initial parameters.
 * */
void printBestYetPrescribedControllerFunctionSet(std::string filename,
        bool nondimensionalize=false) const
{
    using OpenSim::SimmSpline;
    // Create the FunctionSet that we will then serialize.
    OpenSim::FunctionSet fset;
    fset.setSize(_splinesBestYet.size());
    for (unsigned int iFcn = 0; iFcn < _splinesBestYet.size(); iFcn++)
    {
        fset.insert(iFcn, _splinesBestYet[iFcn]);
        if (nondimensionalize)
        { // Go through each y-value and nondimensionalize based on its sign.
            // Max/min for all spline points for the i-th actuator.
            double minControl = _cat.getActuators().get(iFcn).getMinControl();
            double maxControl = _cat.getActuators().get(iFcn).getMaxControl();
            for (int iPts = 0; iPts < _numOptimSplinePoints; iPts++)
            {
                SimmSpline * fcn = dynamic_cast<SimmSpline *>(&fset.get(iFcn));
                double dimValue = fcn->getY(iPts);
                double nonDimValue = 0;
                if (dimValue < 0 && minControl < 0)
                {
                    nonDimValue = -dimValue / minControl;
                }
                else
                {
                    nonDimValue = dimValue / maxControl;
                }
                fcn->setY(iPts, nonDimValue);
            }
        }
    }
    fset.print(_name + "/" + filename);
}

C.10: Member variables

// (NOTE: 'mutable' lets us modify the member variable inside const member
// functions, such as objectiveFunc() above). TODO
FlippinFelinesOptimizerSystem.h
// Reference to the FlippinFelinesOptimizerTool.
OpenSim::FlippinFelinesOptimizerTool & _tool;

// Simulation runtime (seconds).
double _duration;

// Model containing the attributes described in this class' description
mutable OpenSim::Model _cat;

// Number of actuators in the model.
int _numActuators;

// Number of points used to define splines for actuator control.
int _numOptimSplinePoints;

// Vector of splines used in the PrescribedController.
std::vector<OpenSim::SimmSpline *> _splines;

// Vector of splines that gave the best objective value yet.
mutable std::vector<OpenSim::SimmSpline> _splinesBestYet;

// Number of calls to the objective function.
mutable int _objectiveCalls;

// Name of results directory.
std::string _name;

// Log for recording data during optimization
mutable std::ofstream _optLog;

// Period for how often objective terms are printed to the log.
static const int _outputPeriod = 100;

// Best (lowest) value of the objective function, for logging.
mutable double _objectiveFcnValueBestYet;

// To aid with conservative printing of best yet actuation:
mutable bool _lastCallWasBestYet;
mutable bool _thisCallIsBestYet; 

 

 

TODO MUTABLES

 

TODO talk about controls

TODO talk about how the parameters work.

 

C.2 is the part that would be custom between different pplz

 

CAN BE ANYTHING

iterations is not number of objective function calls.

 

 

 

 

 

There's an additional file, optimizer_tool_template.cpp, whose code we do not show here. It generates a template XML file TODO .... TODO paste a version of it into this page.

 

 

 

Getting started with optimization and reproducing our results

We've given you some setup/input files to the optimization to get you started and to reproduce our results. Enter a terminal or command prompt, navigate to a 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.

 optimize counterrotation_setup.xml

Compare your results to those in the References folder of the 'Tutorial' code. TODO. You can do the same for the second optimization, if counterrotation_variableinertia_setup.xml and counterrotation_variableinertia_initial_parameters.xml are in the directory.

 optimize counterrotation_variableinertia_setup.xml

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.

  • No labels