Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Moco Feature Request] Expose functionality in MocoTrack::configureStateTracking() and MocoTrack::configureMarkerTracking() #3154

Open
nathantpickle opened this issue Mar 10, 2022 · 6 comments
Labels
Moco This label identifies bugs or desired features to aid Moco development

Comments

@nathantpickle
Copy link

It would be useful to be able to replicate a MocoTrack optimization using a generic MocoStudy. Pretty much everything in MocoTrack can be easily replicated, except for these two methods, which contain some fairly complicated processing of state and marker trajectories.

Could the functionality in these methods be made available somewhere else, for example in MocoUtilities, so it can be used with a generic MocoStudy?

@nickbianco
Copy link
Member

Hi @nathantpickle, we provide the initialize() method to return the internal MocoStudy that is pre-configured by MocoTrack. Is there a reason that this wouldn't be sufficient for your purposes?

@nathantpickle
Copy link
Author

Hi @nickbianco - I have a model that contains a ClutchedPathSpring, which I would like to use with MocoTrack. However, when I call MocoTrack::initialize() it complains that

No info available for state '/forceset/clutched_spring/stretch'

Because initialize() creates the MocoProblem, I can't call MocoProblem::setStateInfo() for the ClutchedPathSpring to provide the required state info.

My thought was to just replicate the setup that occurs inside MocoTrack::initialize() so that I can set the state info right after creating the MocoProblem. However, the state and marker processing that occurs inside MocoTrack would be fairly complicated to replicate (I'm working in Python), and there doesn't seem to be another (easy) way to perform the operations that occur inside configureStateTracking() and configureMarkerTracking().

Maybe there's another way around the issue I'm having?

@nickbianco
Copy link
Member

Ah, I see. Just checking, when you call initialize() it completely fails? Because if you get the MocoStudy back you could call problem = study.updProblem() and then set the state info, but I'm not sure if that would work off the top of my head.

If that doesn't work, I don't really have a better alternative at the moment. This is definitely a limitation of the current MocoTrack interface, one I would like to improve. Exposing configureStateTracking() and configureMarkerTracking() could be a nice intermediate option, but I'd have to think about if that would make sense.

In the meantime, it shouldn't be too hard to convert configureStateTracking to python. It's maybe a bit cumbersome, but all the OpenSim API stuff would stay the same (except for adding the osim prefix or however you've imported the opensim module) and then convert all the control logic and variable definitions to Python. And the stuff past adding the goal to the problem can be ignored.

@nathantpickle
Copy link
Author

Correct, it fails to return from initialize(), so I'm a bit stuck.

Thanks for the tip on converting to Python. I'll go that route as a workaround for now.

@nickbianco nickbianco added the Moco This label identifies bugs or desired features to aid Moco development label Mar 11, 2022
@nickbianco
Copy link
Member

No problem @nathantpickle. Let me know if you have any questions about getting your workaround going. I'll leave this issue open until we implement a proper fix.

@nathantpickle
Copy link
Author

Just to follow up, I was able to replicate the functionality I need in Python without too much trouble.

Also, I haven't tested this code, but here is roughly what I would expect a utility function to look like. This is the section of code I ended up replicating in Python:

OpenSim::MocoWeightSet updateSpeedsAndWeights(
    TimeSeriesTable& states,
    const MocoWeightSet& userWeights,
    const Model& model,
    const bool trackStateDerivatives
)
{
    auto stateSplines = GCVSplineSet(states, states.getColumnLabels());

    // Loop through all coordinates and compare labels in the reference data
    // to coordinate variable names.
    auto time = states.getIndependentColumn();
    auto labels = states.getColumnLabels();
    int numRefStates = (int)states.getNumColumns();
    MocoWeightSet weights;
    for (const auto& coord : model.getComponentList<Coordinate>()) {
        std::string coordPath = coord.getAbsolutePathString();
        std::string valueName = coordPath + "/value";
        std::string speedName = coordPath + "/speed";
        bool trackingValue = false;
        bool trackingSpeed = false;
        int valueIdx = -1;
        for (int i = 0; i < numRefStates; ++i) {
            if (labels[i] == valueName) {
                trackingValue = true;
                valueIdx = i;
            } else if (labels[i] == speedName) {
                trackingSpeed = true;
            }
        }

        // If a coordinate value was provided to track in the reference data,
        // but no corresponding speed, append the derivative of the coordinate
        // value to the tracking reference.
        if (trackingValue && !trackingSpeed &&
                trackStateDerivatives) {
            auto value = states.getDependentColumnAtIndex(valueIdx);
            auto* valueSpline = stateSplines.getGCVSpline(valueIdx);
            SimTK::Vector speed((int)time.size());
            for (int j = 0; j < (int)time.size(); ++j) {
                speed[j] = valueSpline->calcDerivative(
                        {0}, SimTK::Vector(1, time[j]));
            }
            states.appendColumn(speedName, speed);
            trackingSpeed = true;
        }

        // Unless the user already specified weights, don't track state
        // variables that are already constrained.
        bool isConstrained = coord.isConstrained(model.getWorkingState());
        // Value weights.
        if (userWeights.contains(valueName)) {
            auto uw = userWeights.get(valueName);
            weights.cloneAndAppend({valueName, uw.getWeight()});
        } else if (isConstrained) {
            weights.cloneAndAppend({valueName, 0});
        }
        // Speed weights.
        if (trackingSpeed) {
            if (userWeights.contains(speedName)) {
                auto uw = userWeights.get(speedName);
                weights.cloneAndAppend({speedName, uw.getWeight()});
            } else if (isConstrained) {
                weights.cloneAndAppend({speedName, 0});
            }
        }
    }

    return weights;
}

If updateSpeedsAndWeights() was publicly available somewhere, users could just call it to add the position derivatives and update weights accordingly. In that case, configureStateTracking() could be modified to:

TimeSeriesTable MocoTrack::configureStateTracking(
        MocoProblem& problem, Model& model) {

    // Read in the states reference data and spline.
    // TODO convert Degrees to Radians.
    TimeSeriesTable states = get_states_reference().processAndConvertToRadians(
            getDocumentDirectory(), model);

    MocoWeightSet weights = updateSpeedsAndWeights(states, 
            get_states_weight_set(), model, get_track_reference_position_derivatives());

   ...

Something similar could be done for configureMarkerTracking(), though after looking at it more closely it really isn't that complicated.

These are kind of nitpicky changes, but would save people a bit of time if they want to set up state tracking (including derivatives) with a generic MocoStudy.

@jenhicks jenhicks added this to the Future Release milestone Nov 23, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Moco This label identifies bugs or desired features to aid Moco development
Projects
None yet
Development

No branches or pull requests

3 participants