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

Implementing musculoskeletal model driven by FES #52

Merged
merged 64 commits into from
Feb 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
64 commits
Select commit Hold shift + click to select a range
3d87d28
implementing musculoskeletal dynamics to model
Jan 13, 2024
9f65e11
continuing the work on dynamical behaviour (bound issue)
Jan 15, 2024
83c97dc
bound and major error in forward dynamics input correction
Jan 16, 2024
f1dcaca
feat: enable the use of all fes model for dynamics ocp + examples
Jan 22, 2024
2de8a56
black
Jan 22, 2024
b4c72bc
feat: enabling 2dof model use + example
Jan 22, 2024
0d6b7fe
refactor: renaming ddl into dof
Jan 22, 2024
383ae8b
starting multi muscle incorporation
Jan 23, 2024
81c2b9f
for mayer
Jan 23, 2024
ce0ad2c
changed the way of defining bounds
Jan 23, 2024
b409945
feat: Enabling several FES muscle in one bio model optimization
Jan 24, 2024
a6da5bf
Enabled fourier track_q
Jan 25, 2024
5b4e7a8
adding hilldegroot model
Jan 25, 2024
8f07041
cycling
Jan 25, 2024
6e6a7d0
Comparation between with and without musce force-length relationship
Jan 26, 2024
d94afcf
muscle relationship activation simplification
Jan 26, 2024
dc51e27
Enabling muscle fatigue minimization
Jan 26, 2024
e84fde9
fix: Corrected a major error in fatigue calculation for pulse duratio…
Jan 26, 2024
2e82341
pulse duration optimization to minimize muscle fatigue
Jan 26, 2024
79275e4
Adding pulse apparition optimization to reduce muscle fatigue
Jan 26, 2024
abfeedf
black
Jan 26, 2024
d4139fa
corrected tests according to the pulse duration model fix
Jan 26, 2024
ecb3936
Fix : corrected the muscle fatigue calculation
Jan 29, 2024
06e2b6d
graph for joint angle
Jan 29, 2024
8d8d705
comparing with and without muscle fatigue minimization
Jan 30, 2024
78a69a2
readme
Jan 30, 2024
d864301
.
Jan 31, 2024
83365e7
adding a gif
Jan 31, 2024
23862ee
doc string
Jan 31, 2024
4131688
reaching example
Feb 1, 2024
5c9bfe0
condition modification
Kev1CO Feb 2, 2024
9705377
working on minimization strategies
Feb 2, 2024
ea43ae7
for computational tests
Feb 6, 2024
a53f35f
.
Kev1CO Feb 6, 2024
f7427ea
trying to remove residual torque
Kev1CO Feb 6, 2024
f17b1ac
changing initial guess
Feb 8, 2024
86d0ed5
reaching task refactor
Feb 8, 2024
d7a9f61
Merge branch 'dynamics_2' of https://github.com/Kev1CO/optistim into …
Feb 8, 2024
4d1c016
Auto stash before merge of "dynamics_2" and "origin/dynamics_2"
Feb 8, 2024
dec2a00
corrected path and cleaning
Kev1CO Feb 8, 2024
4596c39
black
Feb 8, 2024
c389101
Answering to assossiated pr commantes
Feb 8, 2024
fb2243b
new black version application
Feb 8, 2024
afbe2ce
adding test for dynamical problems
Feb 8, 2024
bcc3d14
enabling biomod file access in online tests
Feb 8, 2024
ce9a13f
to correspond to online tests
Feb 8, 2024
4756af1
adding one more test for multiple force tracking with intensity optim
Feb 9, 2024
bed9204
correction
Feb 9, 2024
e6e1bbd
testing new simulation conditions
Kev1CO Feb 10, 2024
232a669
graphic modification
Feb 12, 2024
417c920
Renaming class
Feb 15, 2024
486fbf1
moving logo in readme
Feb 15, 2024
633b1e9
same
Feb 15, 2024
51bbb29
removing read data and multistart file
Feb 15, 2024
62808e1
Removing last multistart examples
Feb 15, 2024
65d7c45
Readme update
Feb 15, 2024
cfbeeee
centering gif
Feb 15, 2024
48d3154
.
Feb 15, 2024
720fbf2
Finalizing graph and data for minimize fatigue
Feb 15, 2024
f24faf7
black
Feb 15, 2024
ada97bb
readme update
Feb 16, 2024
ff00cb5
cleanning
Feb 16, 2024
17fb9d0
black
Feb 16, 2024
bc81f3c
adding new
Feb 16, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions .github/ISSUE_TEMPLATE/enhancement_report.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
name: Enhancement report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''

---

### Subject of the enhancement
Give a title to your enhancement here.

### Proposed enhancement
Describe your enhancement here.

### Additional information
Any additional information, like libraries versions, etc.
77 changes: 49 additions & 28 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,17 +1,11 @@
<p align="center">
<img
src="docs/cocofest_logo.png"
alt="logo"
width="200"
/>
</p>
<img align="right" width="400" src="docs/cocofest_logo.png">

# COCOFEST

`Cocofest` : Custom Optimal Control Optimization for Functional Electrical Stimulation, is an optimal control program (OCP) package for functional electrical stimulation (FES).
It is based on the [bioptim](https://github.com/pyomeca/bioptim) framework for the optimal control construction.
Bioptim uses [biorbd](https://github.com/pyomeca/biorbd) a biomechanics library and benefits from the powerful algorithmic diff provided by [CasADi](https://web.casadi.org/).
To solve the OCP, the robust solver [Ipopt](https://github.com/coin-or/Ipopt) has been implemented.
To solve the OCP, the robust solver [Ipopt](https://github.com/coin-or/Ipopt) has been implemented.

## Status

Expand All @@ -37,11 +31,11 @@ To solve the OCP, the robust solver [Ipopt](https://github.com/coin-or/Ipopt) ha
[Create your own FES OCP](#create-your-own-fes-ocp)

[Examples](#examples)
- [Musculoskeletal model driven by FES models](#musculoskeletal-model-driven-by-FES-models)

<details>
<summary><a href="#other-functionalities">Other functionalities</a></summary>

- [With fatigue](#with-fatigue)
- [Initial value problem](#initital-value-problem)
- [Summation truncation](#summation-truncation)

Expand All @@ -56,11 +50,36 @@ Cloning the repository is the first step to be able to use the package.

## Dependencies
`Cocofest` relies on several libraries.
Based on `bioptim`, the user is invited to directly download the framework from anaconda or from the [sources](https://github.com/pyomeca/bioptim) by cloning the repository
Follows the steps to install everything you need to use `Cocofest`.
</br>
First, you need to create a new conda environment
```bash
conda create -n YOUR_ENV_NAME python=3.10
```

Then, activate the environment
```bash
conda activate YOUR_ENV_NAME
```

This step will allow you to install the dependencies in the environment much quicker
```bash:
conda install -cconda-forge conda-libmamba-solver
```

After, install the dependencies
```bash
conda install -c conda-forge bioptim
conda install numpy matplotlib pytest casadi biorbd -cconda-forge --solver=libmamba
```
The other [bioptim dependencies](https://github.com/pyomeca/bioptim#dependencies) must be installed as well.

Finally, install the bioptim setup.py file located in your cocofest/external/bioptim folder
```bash
cd <path_up_to_cocofest_file>/external/bioptim
python setup.py install
```

You are now ready to use `Cocofest`!


# Available FES models
The available FES models are likely to increase so stay tune.
Expand Down Expand Up @@ -96,7 +115,7 @@ the minimum and maximum time between two stimulation pulse, the time bimapping
(If True, will act like a frequency at constant pulse interval).

```python
ocp = OcpFes.prepare_ocp(...,
ocp = OcpFes().prepare_ocp(...,
n_stim=10,
n_shooting=20,
...,)
Expand All @@ -110,39 +129,41 @@ result = ocp.solve()

# Examples
You can find all the available examples in the [examples](https://github.com/Kev1CO/cocofest/tree/main/examples) file.
## Musculoskeletal model driven by FES models
The following example is a musculoskeletal model driven by the Ding2007 FES model.
The objective function is to reach a 90° forearm position and 0° arm position at the movement end.
The stimulation last 1s and the stimulation frequency is 10Hz.
The optimized parameter are each stimulation pulse width.

# Other functionalities
<p align="center">
<img width="500" src=docs/arm_flexion.gif>
</p>

## With fatigue
It is possible to compute the models with their fatigue equations or not.
For example, the "ding2003" model can be use with fatigue DingModelFrequencyWithFatigue or without DingModelFrequency.
If no fatigue is applied, the fatigue equation will not be added to the model and the muscle force will remain
constant during the simulation regardless of the previous stimulation appearance.

```python
ocp = OcpFes.prepare_ocp(model=DingModelFrequencyWithFatigue())
```

# Other functionalities

## Initital value problem
You can also compute the models form initial value problem.
For that, use the IvpFes class to build the computed problem.

```python
ocp = IvpFes(
ding_model=DingModelFrequency(),
)
ocp = IvpFes(model=DingModelFrequency(), ...)
```

## Summation truncation
The summation truncation is an integer parameter that can be added to the model.
It will truncate the stimulation apparition list used for the calcium summation.
The integer number defines the stimulation number to keep for this summation.
The integer number defines the stimulation number to keep prior this summation calculation (e.g only the 5 past stimulation will be included).

```python
ocp = OcpFes.prepare_ocp()
ocp = OcpFes().prepare_ocp(model=DingModelFrequency(sum_stim_truncation=5))
```


# Citing
`Cocofest` is not yet published in a journal.
But if you use `Cocofest` in your research, please kindly cite this package by giving the repository link.
But if you use `Cocofest` in your research, please kindly cite this zenodo link [10.5281/zenodo.10427934](https://doi.org/10.5281/zenodo.10427934).

# Acknowledgements
The Cocofest [logo](docs/cocofest_logo.png) has been design by [MaxMV](https://www.instagram.com/max_mv3/)
4 changes: 2 additions & 2 deletions cocofest/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@
from .models.ding2007_with_fatigue import DingModelPulseDurationFrequencyWithFatigue
from .models.hmed2018 import DingModelIntensityFrequency
from .models.hmed2018_with_fatigue import DingModelIntensityFrequencyWithFatigue
from .optimization.fes_multi_start import FunctionalElectricStimulationMultiStart
from .models.dynamical_model import FesMskModel
from .optimization.fes_ocp import OcpFes
from .optimization.fes_identification_ocp import OcpFesId
from .optimization.fes_ocp_dynamics import OcpFesMsk
from .integration.ivp_fes import IvpFes
from .fourier_approx import FourierSeries
from .read_data import ExtractData
from .identification.ding2003_force_parameter_identification import DingModelFrequencyForceParameterIdentification
from .identification.ding2007_force_parameter_identification import (
DingModelPulseDurationFrequencyForceParameterIdentification,
Expand Down
37 changes: 37 additions & 0 deletions cocofest/custom_objectives.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
This custom objective is to enable the tracking of a curve by a state at all node. Used for sample data control problems
such as functional electro stimulation
"""

import numpy as np
from casadi import MX, SX

Expand Down Expand Up @@ -68,3 +69,39 @@
return 1 - (force / controller.states[key].cx)
else:
raise RuntimeError(f"Minimization type {minimization_type} not implemented")

@staticmethod
def minimize_overall_muscle_fatigue(controller: PenaltyController) -> MX:
"""
Minimize the overall muscle fatigue.

Parameters
----------
controller: PenaltyController
The penalty node elements

Returns
-------
The sum of each force scaling factor
"""
muscle_name_list = controller.model.bio_model.muscle_names
muscle_fatigue = [controller.states["A_" + muscle_name_list[x]].cx for x in range(len(muscle_name_list))]
return sum(muscle_fatigue)

Check warning on line 89 in cocofest/custom_objectives.py

View check run for this annotation

Codecov / codecov/patch

cocofest/custom_objectives.py#L87-L89

Added lines #L87 - L89 were not covered by tests

@staticmethod
def minimize_overall_muscle_force_production(controller: PenaltyController) -> MX:
"""
Minimize the overall muscle force production.

Parameters
----------
controller: PenaltyController
The penalty node elements

Returns
-------
The sum of each force
"""
muscle_name_list = controller.model.bio_model.muscle_names
muscle_force = [controller.states["F_" + muscle_name_list[x]].cx ** 3 for x in range(len(muscle_name_list))]
return sum(muscle_force)

Check warning on line 107 in cocofest/custom_objectives.py

View check run for this annotation

Codecov / codecov/patch

cocofest/custom_objectives.py#L105-L107

Added lines #L105 - L107 were not covered by tests
42 changes: 24 additions & 18 deletions cocofest/integration/ivp_fes.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,14 @@ class IvpFes(OptimalControlProgram):

def __init__(
self,
model: DingModelFrequency
| DingModelFrequencyWithFatigue
| DingModelPulseDurationFrequency
| DingModelPulseDurationFrequencyWithFatigue
| DingModelIntensityFrequency
| DingModelIntensityFrequencyWithFatigue,
model: (
DingModelFrequency
| DingModelFrequencyWithFatigue
| DingModelPulseDurationFrequency
| DingModelPulseDurationFrequencyWithFatigue
| DingModelIntensityFrequency
| DingModelIntensityFrequencyWithFatigue
),
n_stim: int = None,
n_shooting: int = None,
final_time: float = None,
Expand Down Expand Up @@ -257,12 +259,14 @@ def build_initial_guess_from_ocp(self, ocp):
@classmethod
def from_frequency_and_final_time(
cls,
model: DingModelFrequency
| DingModelFrequencyWithFatigue
| DingModelPulseDurationFrequency
| DingModelPulseDurationFrequencyWithFatigue
| DingModelIntensityFrequency
| DingModelIntensityFrequencyWithFatigue,
model: (
DingModelFrequency
| DingModelFrequencyWithFatigue
| DingModelPulseDurationFrequency
| DingModelPulseDurationFrequencyWithFatigue
| DingModelIntensityFrequency
| DingModelIntensityFrequencyWithFatigue
),
n_shooting: int,
final_time: float,
frequency: int | float = None,
Expand Down Expand Up @@ -298,12 +302,14 @@ def from_frequency_and_final_time(
@classmethod
def from_frequency_and_n_stim(
cls,
model: DingModelFrequency
| DingModelFrequencyWithFatigue
| DingModelPulseDurationFrequency
| DingModelPulseDurationFrequencyWithFatigue
| DingModelIntensityFrequency
| DingModelIntensityFrequencyWithFatigue,
model: (
DingModelFrequency
| DingModelFrequencyWithFatigue
| DingModelPulseDurationFrequency
| DingModelPulseDurationFrequencyWithFatigue
| DingModelIntensityFrequency
| DingModelIntensityFrequencyWithFatigue
),
n_stim: int,
n_shooting: int,
frequency: int | float = None,
Expand Down
Loading
Loading