Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.

...

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. Parts C.6 and C.7 deal with the objective function.

Code Block
languagecpp
titleFlippinFelinesOptimizerSystem.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 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 C.10.

C.1: Parse inputs and prepare output

...

Note that objectiveFunc() is necessarily a const function. That's because we wouldn't want the state of the system to change as we are optimizing 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 and prepare output

Code Block
languagecpp
titleFlippinFelinesOptimizerSystem.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();

_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;

We will continue to update a log file, and print out various other files, as the optimization runs. Here's some code that creates a folder for our outputs and opens the log file.

Code Block
languagecpp
titleFlippinFelinesOptimizerSystem.h
collapsetrue
// 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

 *
 * 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. TODO
Code Block
languagecpp
titleFlippinFelinesOptimizerSystem.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]);
} 

...

Code Block
languagecpp
titleFlippinFelinesOptimizerSystem.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 value

Code Block
languagecpp
titleFlippinFelinesOptimizerSystem.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);
// --------------------------------------------------------------------

...

Code Block
languagecpp
titleFlippinFelinesOptimizerSystem.h
collapsetrue
/**
 * @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

...

The mutable keyword lets us modify a member variable inside const member functions, such as objectiveFunc() above.

Code Block
languagecpp
titleFlippinFelinesOptimizerSystem.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; 

...