Moco goals for average speed in cycling model

OpenSim Moco is a software toolkit to solve optimal control problems with musculoskeletal models defined in OpenSim using the direct collocation method.
User avatar
Carlos Gonçalves
Posts: 127
Joined: Wed Jun 08, 2016 4:56 am

Re: Moco goals for average speed in cycling model

Post by Carlos Gonçalves » Mon Dec 04, 2023 10:44 am

Yes Ana, that was the idea of the test... But the behavior that you are observing is interesting.

If it helps, I generally start with the Pattern Info, and then I set a state differently, usually setting all the parameters (range, start, end).

I will start checking this in my simulations also :lol:

User avatar
Nicholas Bianco
Posts: 963
Joined: Thu Oct 04, 2012 8:09 pm

Re: Moco goals for average speed in cycling model

Post by Nicholas Bianco » Mon Dec 04, 2023 10:52 am

Hi Ana,

If you use "setStateInfoPattern()", these infos always get applied to the the states before the infos provided by "setStateInfo()", no matter what order you set them in the problem. So, in the first code snippet, you were overwriting the state info from the pattern with the normal "setStateInfo()" info.

If you didn't set initial/final bounds in the state info for the crank, you should get the expected behavior:

Code: Select all

problem.setStateInfo('/jointset/crank_angle/crank_angle/speed', [-100, 0])
problem.setStateInfoPattern('/jointset/.*/.*/speed', [], 0.0, [])
Hopefully that makes sense, sorry for the confusion!

Best,
Nick

User avatar
Ana de Sousa
Posts: 58
Joined: Thu Apr 07, 2016 4:21 pm

Re: Moco goals for average speed in cycling model

Post by Ana de Sousa » Tue Dec 05, 2023 2:23 pm

Hi!

Nick, thanks for clearing that up! I hadn't considered the impact of the list as well. It's good to know.

Feeling more confident about the ExpressionBasedCoordinateForce solution for crank resistance, I want to dive back into the "goals for average speed.", which was supposed to be the topic here. However, I've encountered some issues related to the forces... I'd appreciate any insights and any other suggestions you might have.

I've conducted simulations for two scenarios:

Crank Only Model
- One actuator (tau_crank) and one resistance force.
- Varying the resistance force from negative to positive values produced fine results in Moco, with the goals of minimising time (weight 10) and actuators' effort (weight 1). You can view one example of that here: here. Really easy and straightforward now.


Crank + Human Model
- A 2D human model attached to the crank with 4 actuators on the joints (hip_l, hip_r, knee_l, and knee_r) and the resistance force. Ankles are locked at 90deg here.
- While varying the resistance, the problem was converging. However... torque values for actuators remained oddly low (close to zero), even with high resistance.

That was not making sense. So, I performed a force analysis (ForceReport) in the Moco solution, including forces due to constraints. Conclusion: all forces came from the pedal constraints. Specifically, the ConstantDistanceConstraint, which I used to attach the feet and pedals. It was a bucket of cold water :( .

I've retaken a look at the example of kinematic constraints in Moco, but it doesn't address actuator-related issues, as far as I understood.

So my question is:
Should I reconsider the connection between feet and pedal :shock: , or is there a workaround within Moco? :?:

I was chatting with Carlos, and he threw the idea of using is_free_to_satisfy_constraints, but I'm not sure how that works and whether it's a viable solution for my problem.

Thanks again for your help.

Cheers!

User avatar
Nicholas Bianco
Posts: 963
Joined: Thu Oct 04, 2012 8:09 pm

Re: Moco goals for average speed in cycling model

Post by Nicholas Bianco » Wed Dec 06, 2023 11:08 am

Hi Ana,

The model with the crank + human will apply forces to enforce the kinematic constraints, but these forces alone shouldn't lead to the crank motion. If there is crank resistance, the human should need to apply a corresponding torque to move the crank.

Are the joint torques much smaller than the crank resistance torque?

-Nick

User avatar
Ana de Sousa
Posts: 58
Joined: Thu Apr 07, 2016 4:21 pm

Re: Moco goals for average speed in cycling model

Post by Ana de Sousa » Mon Dec 11, 2023 8:50 am

Hey Nick,
The model with the crank + human will apply forces to enforce the kinematic constraints, but these forces alone shouldn't lead to the crank motion.
Yeah, I thought so as well, but it is weird.
Are the joint torques much smaller than the crank resistance torque?
I set the joint torques to [-50;50] for the right and left knees and right and left hips. Ankle joints are locked.

I've been looking into the joint torques and resistance in my model:

Model + no resistance

Now, when I run the forward dynamics without any resistance (over 10s), the model oscillates within a range of -30 to 30 degrees (crank angle). The constraint forces and moments also oscillate, as shown in the left calcn constraints plot:

Image

Then, I tried predicting the movement using Moco. The model moves from 0 to 360 degrees, but the actuators are (almost) zero. The constraint forces reach 80Nm:

Image

Model + resistance

Now, when I introduce resistance (5Nm), the model cycles about 1/2 cycle backward in the forward dynamics. The constraint forces are:

Image

Running Moco with this resistance, the model cycles from 0 to 360 degrees again, and the constraint forces are different (actuators are still zero):

Image

I tried increasing the resistance to 10, 100, 200, but the predictions weren't converging.

Any thoughts on why this might be happening? Am I missing something crucial?
Appreciate any insights!


Other info

Code: Select all

num_mesh: 25
convergence_tol: 1e-1
constraint_tol: 1e-4
osim.MocoFinalTimeGoal('final_time', 10)
osim.MocoControlGoal('min_effort', 1)
I tried without MocoControlGoal, but it doesn't make such a difference. Constraint forces are slightly lower.

User avatar
Nicholas Bianco
Posts: 963
Joined: Thu Oct 04, 2012 8:09 pm

Re: Moco goals for average speed in cycling model

Post by Nicholas Bianco » Mon Dec 11, 2023 4:02 pm

Hi Ana,

Could you attach the console print out that describes the Moco problem you are trying solve and also attach the model? There's probably a reasonable explanation here, but dealing with kinematic constraints like these can be tricky.

My recommendation on the Moco side of things is to solve a simpler problem that you should expect to work. For example, try removing the crank resistance and solve an optimization that performs a full rotation of the crank, starting and ending from rest.

As for the kinematic constraint forces, I suppose that the forces between the foot and crank could be larger (in order to enforce the constraint), but the joint torques small in the case when the crank resistance is low (I'm doing some back-of-the-envelope dynamics in my head, so take it with a grain of salt).

As always, breaking down your simulation into simpler, solveable chunks is the best way to go.

Best,
Nick

User avatar
Aaron Fox
Posts: 271
Joined: Sun Aug 06, 2017 10:54 pm

Re: Moco goals for average speed in cycling model

Post by Aaron Fox » Mon Dec 11, 2023 5:35 pm

Hi Ana,

Would this project - https://simtk.org/projects/cycling_sim - and associated recently published paper be helpful in guiding your simulations?

Aaron

User avatar
Ana de Sousa
Posts: 58
Joined: Thu Apr 07, 2016 4:21 pm

Re: Moco goals for average speed in cycling model

Post by Ana de Sousa » Tue Dec 12, 2023 4:38 am

Hi everyone,

Thank you, Aaron, for sharing the paper. I've carefully reviewed it, and while the work is impressive, their prediction goals differ from mine. They utilise tracking data for foot and pedals, eliminating the need for kinematic constraints. In my case, I don't have markers, so I'm not focused on tracking but rather on predicting the torques with different goals.

Nick, I've attached a console print that outlines the Moco problem.

Moreover, when I tried optimising a full crank rotation without crank resistance, but the actuators were zero, and the constraint forces dominated.


Image

I suppose that the forces between the foot and crank could be larger (in order to enforce the constraint), but the joint torques small in the case when the crank resistance is low

I suspected the same. But I haven't been able to converge for larger resistances yet...

I'm also considering trying a force between the foot and pedal instead of relying on kinematic constraints. But I'm not sure if this would help or not.
Attachments
console_moco_description.txt
(5.22 KiB) Downloaded 70 times

User avatar
Ana de Sousa
Posts: 58
Joined: Thu Apr 07, 2016 4:21 pm

Re: Moco goals for average speed in cycling model

Post by Ana de Sousa » Tue Dec 12, 2023 7:28 am

Found my error!

I forgot to set the control info, resulting in torques being limited to -1 to 1 instead of the desired -50 to 50 range:

Code: Select all

problem.setControlInfo('/tau_hip_flexion_r', [-joint_torque, joint_torque])
problem.setControlInfo('/tau_hip_flexion_l', [-joint_torque, joint_torque])
problem.setControlInfo('/tau_knee_angle_r', [-joint_torque, joint_torque])
problem.setControlInfo('/tau_knee_angle_l', [-joint_torque, joint_torque])
With this correction, I successfully optimised for crank resistances of 0, 5, 20, and 100 Nm. The optimisation converged, and I saw an increase in the values of joint torques. P-e-r-f-e-c-t-i-o-n!

Now, I'll shift my focus to the main objective of this forum topic that I opened: implementing a goal for crank speed tracking.

Thanks a lot for all your patience, guys. I'll let you know if I succeeded or not.

User avatar
Ana de Sousa
Posts: 58
Joined: Thu Apr 07, 2016 4:21 pm

Re: Moco goals for average speed in cycling model

Post by Ana de Sousa » Mon Dec 18, 2023 7:44 am

Hey everyone!

Just wanted to share a quick update on my simulation progress and give a big shout-out for your fantastic suggestions! I solved the problem of the post using the MocoStateTrackingGoal.

First, I generate a sto file with time and angular speed data following a linear ramp pattern.

Code: Select all

def create_the_speed_sto_file(name_sto_file, max_speed_deg, duration):
    dt = 0.05
    max_speed_rad = np.deg2rad(max_speed_deg)#*2*np.pi/60
    stoLabels = ['time', '/jointset/crank_angle/crank_angle/speed']
    sto = osim.Storage()

    col_labels = osim.ArrayStr()
    for label in stoLabels:
        col_labels.append(label)
    sto.setColumnLabels(col_labels)

    time_vector = np.arange(0, duration + dt, dt)
    speed_vector = - np.minimum(time_vector * (2 * max_speed_rad / duration), max_speed_rad)
    data = np.transpose(np.array([time_vector, speed_vector]))

    nrow, ncol = data.shape

    for i in range(nrow):
        row = osim.ArrayDouble()
        for j in range(ncol):
            row.append(data[i, j])
        row_without_time = osim.ArrayDouble(row.getSize() - 1)
        for k in range(1, row.getSize()):
            row_without_time.setitem(k - 1, row.getitem(k))

        sto.append(data[i, 0], row_without_time)

    sto.setName(name_sto_file)
    sto.setInDegrees(False)
    sto.printResult(sto, name_sto_file, os.getcwd(), dt, '.sto')

    ref = osim.TableProcessor(name_sto_file + ".sto")

    return ref
    
Then, I added the speed tracking goal based on that STO file:

Code: Select all

def add_speed_goal_control(problem, max_speed, start_time, end_time, weight=None):
    # Create the sto file and get the reference
    ref = create_the_speed_sto_file("crank_speed", max_speed, end_time)

    # Add the goal
    tracking = osim.MocoStateTrackingGoal()
    tracking.setName('speed_tracking')
    tracking.setReference(ref)
    tracking.setAllowUnusedReferences(True)
    tracking.setWeightForState('/jointset/crank_angle/crank_angle/speed', weight)
    problem.addGoal(tracking)

    # Set time bonds
    problem.setTimeBounds(start_time, end_time)

    return problem
From that, Moco solved for the crank model (without a human) with different resistances - the actuation signal tuned with the resistance as expected.

I appreciate all the help I got in the month to make it work. I've learned a lot in the process. :D

But before closing this post, I wanted to ask a final question about torques and actuators. :geek:

So, I performed the simulations above with optimal force to 1.0:

Code: Select all

coord_set = model.updCoordinateSet()
actu = osim.CoordinateActuator()
actu.setName('tau_' + coord_name)
actu.setCoordinate(coord_set.get(coord_name))
actu.setOptimalForce(1.0)
model.addComponent(actu)
And then, I set the actuation of this torque:

Code: Select all

problem.setControlInfo('/tau_crank_angle',[-10., 10.])
Now, does this make sense? What are the consequences of setting up the torque like that? Is there a practical difference between setting the max force to 10.0 and letting the control signal roam from -1 to 1?

Thanks a bunch for all the help!

POST REPLY