Copying Actuator forces from one model to another leads to unreasonable results

Provide easy-to-use, extensible software for modeling, simulating, controlling, and analyzing the neuromusculoskeletal system.
POST REPLY
User avatar
Lukas Schmitz
Posts: 3
Joined: Thu Aug 10, 2023 2:01 am

Copying Actuator forces from one model to another leads to unreasonable results

Post by Lukas Schmitz » Mon Oct 23, 2023 7:44 am

Hello,

I want to analyze, how a certain model behaves with and without external forces. First, I generate a movement without external loads by using the TaskSpace planner https://simtk.org/projects/task-space. I use the TaskBasedTorqueController, which uses CoordinateActuators on each joint to generate the movement. The muscle forces are kept 0 for all muscles in this case. The outputs of this are written to a storage file using the OpenSim::ForceReporter.

To see how external forces change the movement, I load the same model in Matlab and copy all the the CoordinateActuators from the first model in the following way

Code: Select all

% Load the force data from .sto file
storageData = Storage("output/actuator_forces.sto");

% Get the number of states (time points) in the storageData
numStates = storageData.getSize();

% Extract the time data from the storageData
for i = 0:numStates-1
    stateVec = storageData.getStateVector(i);
    timeData(i+1) = stateVec.getTime();
end

% Get the column labels
labelsArray = storageData.getColumnLabels();
numLabels = labelsArray.getSize();
labels = cell(1, numLabels);
for i = 0:numLabels-1
    labels{i+1} = char(labelsArray.get(i));
end

% Create a CoordinateActuator for each force ending in "_control"
% (TaskBasedController) uses this naming for its CoordinateActuators.
% Add all of the actuators to a controller
controlled_actuator_names = []; % list of actuators used by the controller
for i = 1:length(labels)
    if endsWith(labels{i}, "_control")
        % find corresponding corrdinate
        coordName = erase(labels{i}, "_control");
        coordinate = model.getCoordinateSet().get(coordName);

        % Create a CoordinateActuator for this coordinate
        actuator = CoordinateActuator();
        actuator.setCoordinate(coordinate);
        actuator.setName(labels{i});
        actuator.setOptimalForce(1); % TODO: check if this is the correct value (seems like forces get multiplied by this factor)

        model.addForce(actuator);
        controller.addActuator(actuator)

        % add actuator to the list
        controlled_actuator_names = [controlled_actuator_names; string(labels{i})];

        % initialize actuator forces
        controller.prescribeControlForActuator(labels{i}, Constant(0));
    end
end
I then integrate over the simulation time using the Manager and, in each time step, copy the forces from my storage file and apply them to the corresponding actuator

Code: Select all

manager = Manager(model);
manager.initialize(state);

for tIndex = 1:length(timeData)
    % Set the time for the state
    state.setTime(timeData(tIndex));

    % Copy forces from file
    for i = 1:length(controlled_actuator_names)
        aName = controlled_actuator_names(i);
        storageIndexArray = storageData.getColumnIndicesForIdentifier(aName);
        if storageIndexArray.size() ~= 1
            disp("Warning: found multiple indices for given name. Results may be skewed");
        end

        % get the index of the column for this actuator in the storage
        storageIndex = storageIndexArray(1).getLast() - 1;

        forceValue = storageData.getStateVector(tIndex-1).getData().get(storageIndex);
        controller.prescribeControlForActuator(aName, Constant(forceValue));
    end

    % Integrate to the next time step
    manager.integrate(timeData(tIndex));

    % % Write forces to the storage file
    % forceTable = forceReporter.getForcesTable();
    % STOFileAdapter.write(forceTable, output_dir + "/actuator_forces_matlab.sto")

end
I have also added a PointActuator that is supposed to represent the external force, but for now it applies 0 force to the model. So far I should expect that the second model moves in the same way as the first one. However, the resulting motion in the second model looks very different and is completely unreasonable.

I have already verified, that the actuator forces are copied correctly from the storage file to the second model. The other model forces (PotentialEnergy and ligament/damping forces) show a big difference to the first model, which (as far as I understand) makes sense, as they result from the movement in the model.

Where is my mistake? Shouldn't the second model show the same movement pattern as the first, if all the Actuators have the same force?

Edit:
One thing I forgot to mention. When copying the actuators, I initialize the force to be 0 in the first time step. As the storage file contains in timesteps of 0.01 seconds, this means that the forces in the first and second model are differing for the first 0.01 seconds, but after this, the same actuator forces are applied to both models. Is this enough to change the movement drastically?

Tags:

User avatar
Carmichael Ong
Posts: 401
Joined: Fri Feb 24, 2012 11:50 am

Re: Copying Actuator forces from one model to another leads to unreasonable results

Post by Carmichael Ong » Fri Nov 17, 2023 4:55 pm

A few notes below:

Code: Select all

actuator.setOptimalForce(1); % TODO: check if this is the correct value (seems like forces get multiplied by this fact
I saw this the following comment in your code, and you are correct that the optimal force will scale with this property
the forces in the first and second model are differing for the first 0.01 seconds, but after this, the same actuator forces are applied to both models. Is this enough to change the movement drastically?
Even small differences in forces (especially in the start) can lead to large differences later, so this could definitely be a problem.

Code: Select all

controller.prescribeControlForActuator(aName, Constant(forceValue));
I'm not familiar with the project on SimTK you used for task based control. Do you know if that also applies constant forces between each time step, or if it's just printing out the forces at each time step whether or not they were constant? This can also accumulate errors.

Also, perhaps one last thing to check is that the actuators you have added don't have their max and min controls bounded too tightly. For instance, if you were passing in a control value of 100 but their control maximum value was 1, then it could be clipping the torques that you were trying to apply.

User avatar
Lukas Schmitz
Posts: 3
Joined: Thu Aug 10, 2023 2:01 am

Re: Copying Actuator forces from one model to another leads to unreasonable results

Post by Lukas Schmitz » Sat Nov 18, 2023 2:58 am

Thank you for your reply!
I'm not familiar with the project on SimTK you used for task based control. Do you know if that also applies constant forces between each time step, or if it's just printing out the forces at each time step whether or not they were constant? This can also accumulate errors.
I am using this project by Dimitar Stanev for task based control https://simtk.org/projects/task-space.
I not too familiar with the OpenSim API, but it seems like the forces are applied using the addInControls() method for each actuator.
The relevant code can be found here: https://github.com/mitkof6/task-space/b ... roller.cpp

Code: Select all

void TaskBasedTorqueController::computeControls(const State& s,
                                                Vector& controls) const {
    // evaluate control strategy
    auto tau = controlStrategy(s);
    appliedForces.append(s.getTime(), tau.size(), &tau[0], true);
    // apply forces as controls to the CoordinateActuators
    for (int i = 0; i < getActuatorSet().getSize(); i++) {
        getActuatorSet()[i].addInControls(Vector(1, tau[i]), controls);
    }
}
I guess I don't need the PrescribedController anymore if I try doing it like this, is this correct?
Also, perhaps one last thing to check is that the actuators you have added don't have their max and min controls bounded too tightly. For instance, if you were passing in a control value of 100 but their control maximum value was 1, then it could be clipping the torques that you were trying to apply.
I checked and the actuators are unbounded.


In the meantime I had some success by changing the actuation function. Instead of Constant, I use PiecewiseLinearFunction and create a linear function between the last time step and the current time step. I read somwhere that the Constant is not supposed to be changed during the simulation so I guess that could also lead to errors (there is also another function that can be used: PiecewiseConstantFunction ). The integration now looks like this:

Code: Select all

%% Simulate
% Create the manager for simulation
manager = Manager(model);
manager.initialize(state);

for tIndex = 1:length(timeData)
    % Set the time for the state
    state.setTime(timeData(tIndex));

    % Copy forces from file
    for i = 1:length(controlled_actuator_names)
        aName = controlled_actuator_names(i);
        storageIndexArray = storageData.getColumnIndicesForIdentifier(aName);
        if storageIndexArray.size() ~= 1
            disp("Warning: found multiple indices for given name. Results may be skewed");
        end

        % get the index of the column for this actuator in the storage
        storageIndex = storageIndexArray(1).getLast() - 1;
        
        forceValue = storageData.getStateVector(tIndex-1).getData().get(storageIndex);
        if tIndex == 1
            controller.prescribeControlForActuator(aName, Constant(forceValue));
        else
            forceValuePrev = storageData.getStateVector(tIndex-2).getData().get(storageIndex);
            fnc = PiecewiseLinearFunction();
            fnc.addPoint(timeData(tIndex-1), forceValuePrev);
            fnc.addPoint(timeData(tIndex), forceValue);
            controller.prescribeControlForActuator(aName, fnc);
        end
    end

    % Integrate to the next time step
    manager.integrate(timeData(tIndex));

    % % Write forces to the storage file
    % forceTable = forceReporter.getForcesTable();
    % STOFileAdapter.write(forceTable, output_dir + "/actuator_forces_matlab.sto")

end
Additionally, I now initialized the actuators with their correct value

Code: Select all

controlled_actuator_names = []; % list of actuators used by the controller
for i = 1:length(labels)
    if endsWith(labels{i}, "_control")
        % find corresponding corrdinate
        coordName = erase(labels{i}, "_control");
        coordinate = model.getCoordinateSet().get(coordName);

        % Create a CoordinateActuator for this coordinate
        actuator = CoordinateActuator();
        actuator.setCoordinate(coordinate);
        actuator.setName(labels{i});
        actuator.setOptimalForce(1); % TODO: check if this is the correct value (seems like forces get multiplied by this factor)

        model.addForce(actuator);
        controller.addActuator(actuator)

        % add actuator to the list
        controlled_actuator_names = [controlled_actuator_names; string(labels{i})];

        % initialize forces
        storageIndexArray = storageData.getColumnIndicesForIdentifier(labels{i});
        if storageIndexArray.size() ~= 1
            disp("Warning: found multiple indices for given name. Results may be skewed");
        end

        % get the index of the column for this actuator in the storage
        storageIndex = storageIndexArray(1).getLast() - 1;

        forceValue = storageData.getStateVector(0).getData().get(storageIndex);
        controller.prescribeControlForActuator(labels{i}, Constant(forceValue));
    end
end
Overall I achieved a much better result with this. The movement is now pretty much the same (visually speaking), though it does drift over time.


I will also try using the addInControls() as seen above and report back.
Edit: I tried this, unfortunately it leads to the same unreasonable and unstable movement pattern, as was the case in my initial approach

POST REPLY