Skip to content

Migrate cocofest to bioptim 3.4#27

Open
mickaelbegon wants to merge 14 commits into
pyomeca:mainfrom
mickaelbegon:pyomeca-main/bioptim-3-4-migration
Open

Migrate cocofest to bioptim 3.4#27
mickaelbegon wants to merge 14 commits into
pyomeca:mainfrom
mickaelbegon:pyomeca-main/bioptim-3-4-migration

Conversation

@mickaelbegon

@mickaelbegon mickaelbegon commented May 7, 2026

Copy link
Copy Markdown

Summary

  • replace legacy DynamicsList/DynamicsFcn usage with bioptim 3.4 DynamicsOptions and model-owned dynamics configuration
  • make FES and FES-MSK models implement the new StateDynamics contract
  • pin the conda environment to bioptim 3.4.0 and compatible biorbd/casadi versions

Note

Tests

  • PYTHONPATH=/private/tmp/pyomeca-bioptim-fdafe4d:/Users/mickaelbegon/Documents/cocofest_pedalage/cocofest /Users/mickaelbegon/miniconda3/envs/vitpose-ekf/bin/python -m pytest tests/shard1/test_models_dynamics_without_bioptim.py -q
  • PYTHONPATH=/private/tmp/pyomeca-bioptim-fdafe4d:/Users/mickaelbegon/Documents/cocofest_pedalage/cocofest /Users/mickaelbegon/miniconda3/envs/vitpose-ekf/bin/python -m pytest tests/shard1/test_ivp.py -q
  • PYTHONPATH=/private/tmp/pyomeca-bioptim-fdafe4d:/Users/mickaelbegon/Documents/cocofest_pedalage/cocofest /Users/mickaelbegon/miniconda3/envs/vitpose-ekf/bin/python -m pytest tests/shard1/test_fes_dynamics.py -q

This change is Reviewable

@mickaelbegon mickaelbegon force-pushed the pyomeca-main/bioptim-3-4-migration branch from 6b64b68 to bb5357f Compare May 8, 2026 12:39
@codecov

codecov Bot commented May 11, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 83.92435% with 68 lines in your changes missing coverage. Please review.
✅ Project coverage is 68.03%. Comparing base (6ea8096) to head (615daa9).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
cocofest/custom_objectives.py 41.66% 14 Missing ⚠️
cocofest/models/dynamical_model.py 86.02% 13 Missing ⚠️
cocofest/models/state_configure.py 30.76% 9 Missing ⚠️
cocofest/models/hmed2018/hmed2018_with_fatigue.py 80.00% 6 Missing ⚠️
cocofest/models/ding2007/ding2007.py 78.26% 5 Missing ⚠️
cocofest/models/hmed2018/hmed2018.py 79.16% 5 Missing ⚠️
cocofest/models/veltink1992/veltink1992.py 86.20% 4 Missing ⚠️
cocofest/models/ding2007/ding2007_with_fatigue.py 86.95% 3 Missing ⚠️
cocofest/dynamics/initial_guess_warm_start.py 0.00% 2 Missing ⚠️
cocofest/models/ding2003/ding2003.py 94.87% 2 Missing ⚠️
... and 3 more
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #27      +/-   ##
==========================================
+ Coverage   67.52%   68.03%   +0.51%     
==========================================
  Files          38       38              
  Lines        3002     3194     +192     
==========================================
+ Hits         2027     2173     +146     
- Misses        975     1021      +46     
Flag Coverage Δ
unittests 68.03% <83.92%> (+0.51%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Kev1CO Kev1CO left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kev1CO reviewed 6 files and all commit messages, and made 8 comments.
Reviewable status: 6 of 34 files reviewed, 8 unresolved discussions (waiting on mickaelbegon).


environment.yml line 9 at r2 (raw file):

# - This branch targets bioptim 3.4.0 from conda-forge.
# - Keep `external/bioptim` out of PYTHONPATH so the conda package is used.
# - `ma57` is optional. If `libhsl.dylib` is available on your machine, point IPOPT to it.

remove

Code quote:

# Create the branch environment with:
#   conda env create -f environment.yml
# or update an existing one with:
#   conda env update -f environment.yml --prune
#
# Notes:
# - This branch targets bioptim 3.4.0 from conda-forge.
# - Keep `external/bioptim` out of PYTHONPATH so the conda package is used.
# - `ma57` is optional. If `libhsl.dylib` is available on your machine, point IPOPT to it.

cocofest/models/dynamical_model.py line 143 at r2 (raw file):

    def muscle_name_dof(self, index: int = 0) -> list[str]:
        muscle = self.muscles_dynamics_model[index]
        return [f"{name}_{muscle.muscle_name}" for name in muscle.name_dof]

was not used to print before, if this feature is kept, add print key in function (set to false default)

Code quote:

[f"{name}_{muscle.muscle_name}" for name in muscle.name_dof]

cocofest/models/dynamical_model.py line 201 at r2 (raw file):

                )

        return controls

To many nested for/if loops, create new functions to make the code lecture more understandable.

Code quote:

    @property
    def state_configuration_functions(self):
        state_configure = StateConfigure()
        muscle_state_configurations = []
        for muscle_model in self.muscles_dynamics_model:
            for state_key in muscle_model.name_dof:
                if state_key in state_configure.state_dictionary:
                    muscle_state_configurations.append(
                        lambda ocp, nlp, state_key=state_key, muscle_model=muscle_model: state_configure.state_dictionary[
                            state_key
                        ](
                            ocp=ocp,
                            nlp=nlp,
                            as_states=True,
                            as_controls=False,
                            muscle_name=muscle_model.muscle_name,
                        )
                    )

        return muscle_state_configurations + [
            lambda ocp, nlp: ConfigureVariables.configure_q(ocp, nlp, as_states=True),
            lambda ocp, nlp: ConfigureVariables.configure_qdot(ocp, nlp, as_states=True),
        ]

    @property
    def control_configuration_functions(self):
        controls = []
        if self.activate_residual_torque:
            controls.append(lambda ocp, nlp: ConfigureVariables.configure_tau(ocp, nlp, as_controls=True))

        for muscle_model in self.muscles_dynamics_model:
            if isinstance(muscle_model, DingModelPulseWidthFrequency):
                controls.append(
                    lambda ocp, nlp, muscle_model=muscle_model: StateConfigure().configure_last_pulse_width(
                        ocp, nlp, muscle_model.muscle_name
                    )
                )
            if isinstance(muscle_model, DingModelPulseIntensityFrequency):
                controls.append(
                    lambda ocp, nlp, muscle_model=muscle_model: StateConfigure().configure_pulse_intensity(
                        ocp, nlp, muscle_model.muscle_name, muscle_model.sum_stim_truncation
                    )
                )

        return controls

cocofest/models/state_configure.py line 491 at r2 (raw file):

                    state_name_list.append(state_key + "_" + muscle_dynamics_model.muscle_name)

        return state_name_list

Similarly, to many for/if loops, refactor

Code quote:

    def configure_all_muscle_states(self, muscles_dynamics_model, ocp, nlp):
        state_name_list = []
        for muscle_dynamics_model in muscles_dynamics_model:
            for state_key in muscle_dynamics_model.name_dof:
                if state_key in self.state_dictionary.keys():
                    self.state_dictionary[state_key](
                        ocp=ocp,
                        nlp=nlp,
                        as_states=True,
                        as_controls=False,
                        muscle_name=muscle_dynamics_model.muscle_name,
                    )
                    state_name_list.append(state_key + "_" + muscle_dynamics_model.muscle_name)

        return state_name_list

cocofest/models/ding2003/ding2003.py line 396 at r2 (raw file):

                controls=controls,
                numerical_timeseries=numerical_timeseries,
            )

Why try / except ?

Code quote:

        try:
            dxdt = dxdt_fun(
                cn=states[0],
                f=states[1],
                t=time,
                t_stim_prev=numerical_timeseries,
                force_length_relationship=force_length_relationship,
                force_velocity_relationship=force_velocity_relationship,
                passive_force_relationship=passive_force_relationship,
            )
        except TypeError:
            dxdt = dxdt_fun(
                time=time,
                states=states,
                controls=controls,
                numerical_timeseries=numerical_timeseries,
            )

cocofest/models/ding2003/ding2003_with_fatigue.py line 187 at r2 (raw file):

        The value of the derivative of each state dx/dt at the current time t
        """
        if states is not None:

Same ? should'nt it be always not none ?


cocofest/models/ding2003/ding2003_with_fatigue.py line 194 at r2 (raw file):

            km = states[4]
        if time is not None:
            t = time

Why ? shouldn't time alway be not None ?

Code quote:

        if time is not None:
            t = time

cocofest/models/ding2007/ding2007.py line 160 at r2 (raw file):

            f = states[1]
        if time is not None:
            t = time

Similar

Code quote:

        if states is not None:
            cn = states[0]
            f = states[1]
        if time is not None:
            t = time

@Kev1CO

Kev1CO commented May 12, 2026

Copy link
Copy Markdown
Collaborator

cocofest/models/hmed2018/hmed2018_with_fatigue.py line 70 at r2 (raw file):

    @property
    def name_dofs(self) -> list[str]:
        return self.name_dof

?

Code quote:

    @property
    def name_dofs(self) -> list[str]:
        return self.name_dof

@Kev1CO Kev1CO left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kev1CO made 13 comments.
Reviewable status: 6 of 34 files reviewed, 21 unresolved discussions (waiting on mickaelbegon).


cocofest/models/hmed2018/hmed2018_with_fatigue.py line 70 at r2 (raw file):

Previously, Kev1CO (Kevin Co) wrote…

?

what happen here


cocofest/models/ding2003/ding2003_with_fatigue.py line 345 at r2 (raw file):

        """
        StateConfigure().configure_all_fes_model_states(ocp, nlp, fes_model=self)
        ConfigureProblem.configure_dynamics_function(ocp, nlp, dyn_func=self.dynamics)

Why does it show as new lines, when this already exist in file

Code quote:

    @staticmethod
    def dynamics(
        time: MX,
        states: MX,
        controls: MX,
        parameters: MX,
        algebraic_states: MX,
        numerical_timeseries: MX,
        nlp: NonLinearProgram,
        fes_model=None,
        force_length_relationship: MX | float = 1,
        force_velocity_relationship: MX | float = 1,
        passive_force_relationship: MX | float = 0,
    ) -> DynamicsEvaluation:
        """
        Functional electrical stimulation dynamic

        Parameters
        ----------
        time: MX
            The system's current node time
        states: MX
            The state of the system CN, F, A, Tau1, Km
        controls: MX
            The controls of the system, none
        parameters: MX
            The parameters acting on the system, final time of each phase
        algebraic_states: MX
            The stochastic variables of the system, none
        numerical_timeseries: MX
            The numerical timeseries of the system
        nlp: NonLinearProgram
            A reference to the phase
        fes_model: DingModelFrequencyWithFatigue
            The current phase fes model
        force_length_relationship: MX | float
            The force length relationship value (unitless)
        force_velocity_relationship: MX | float
            The force velocity relationship value (unitless)
        passive_force_relationship: MX | float
            The passive force relationship value (unitless)
        Returns
        -------
        The derivative of the states in the tuple[MX] format
        """
        model = fes_model if fes_model else nlp.model
        dxdt_fun = model.system_dynamics

        return DynamicsEvaluation(
            dxdt=dxdt_fun(
                cn=states[0],
                f=states[1],
                a=states[2],
                tau1=states[3],
                km=states[4],
                t=time,
                t_stim_prev=numerical_timeseries,
                force_length_relationship=force_length_relationship,
                force_velocity_relationship=force_velocity_relationship,
                passive_force_relationship=passive_force_relationship,
            ),
            defects=None,
        )

    def declare_ding_variables(
        self,
        ocp: OptimalControlProgram,
        nlp: NonLinearProgram,
        numerical_data_timeseries: dict[str, np.ndarray] = None,
        contact_type: list = (),
    ):
        """
        Tell the program which variables are states and controls.
        The user is expected to use the ConfigureProblem.configure_xxx functions.
        Parameters
        ----------
        ocp: OptimalControlProgram
            A reference to the ocp
        nlp: NonLinearProgram
            A reference to the phase
        numerical_data_timeseries: dict[str, np.ndarray]
            A list of values to pass to the dynamics at each node. Experimental external forces should be included here.
        contact_type: list
            A list of contact types. This is used to define the contact forces in the dynamics. Not used in this model.
        """
        StateConfigure().configure_all_fes_model_states(ocp, nlp, fes_model=self)
        ConfigureProblem.configure_dynamics_function(ocp, nlp, dyn_func=self.dynamics)

cocofest/models/ding2007/ding2007.py line 84 at r2 (raw file):

    @property
    def control_configuration_functions(self):
        return [lambda ocp, nlp: StateConfigure().configure_last_pulse_width(ocp, nlp, self.muscle_name)]

Why was it moved underneath

Code quote:

    @property
    def control_configuration_functions(self):
        return [lambda ocp, nlp: StateConfigure().configure_last_pulse_width(ocp, nlp, self.muscle_name)]

cocofest/models/ding2007/ding2007.py line 331 at r2 (raw file):

                states=states,
                controls=controls,
                numerical_timeseries=numerical_timeseries,

Okay, I might understand, this is for the non fatigue and fatigue version (parent-child). But if its the case that why is there still the dynamics function in the child class?
Is it because bioptim requires the function dynamics in each models ? If yes than remove the try except

Code quote:

        try:
            dxdt = dxdt_fun(
                cn=states[0],
                f=states[1],
                t=time,
                t_stim_prev=numerical_timeseries,
                pulse_width=controls[0],
                force_length_relationship=force_length_relationship,
                force_velocity_relationship=force_velocity_relationship,
                passive_force_relationship=passive_force_relationship,
            )
        except TypeError:
            dxdt = dxdt_fun(
                time=time,
                states=states,
                controls=controls,
                numerical_timeseries=numerical_timeseries,

cocofest/models/ding2007/ding2007_with_fatigue.py line 184 at r2 (raw file):

            km = states[4]
        if time is not None:
            t = time

same

Code quote:

        if states is not None:
            cn = states[0]
            f = states[1]
            a = states[2]
            tau1 = states[3]
            km = states[4]
        if time is not None:
            t = time

cocofest/models/hmed2018/hmed2018.py line 89 at r2 (raw file):

                ocp, nlp, self.muscle_name, self.sum_stim_truncation
            )
        ]

same, why did it move downwards?

Code quote:

    @property
    def control_configuration_functions(self):
        return [
            lambda ocp, nlp: StateConfigure().configure_pulse_intensity(
                ocp, nlp, self.muscle_name, self.sum_stim_truncation
            )
        ]

examples/fes_multibody/cycling/cost_functions.py line 1 at r2 (raw file):

from casadi import MX, vertcat, sum1, fabs, sign, tanh, if_else, log, exp, DM, dot, mmax, mmin, cos, sin, sqrt

Remove this file

Code quote:

from casadi import M

examples/fes_multibody/cycling/cycling_bayesian_mhe.py line 2 at r2 (raw file):

"""
This example will perform a bayesian optimization on an optimal control program moving time horizon for a hand cycling

This version of cycling is outdated
At the end of all comment in the PR, add the new one


examples/fes_multibody/cycling/cycling_pulse_width_mhe.py line 1482 at r2 (raw file):

                # ["minimize_average_fatigue_and_recovery_2"],
                # ["minimize_balanced_fatigue_by_contribution"],
            ]

Similar to bayesian example

Code quote:

                # --- Recovery --- #
                ["minimize_average_fatigue_and_recovery"],
                # ["minimize_average_fatigue_and_recovery_2"],
                # ["minimize_balanced_fatigue_by_contribution"],
            ]

examples/fes_multibody/cycling/cycling_standard_clinics.py line 10 at r2 (raw file):

from sys import platform
import pickle

similar


examples/fes_multibody/cycling/cycling_with_different_driven_methods.py line 618 at r2 (raw file):

        solver.set_option_unsafe(2.0, "ma57_pre_alloc")
    solver.set_option_unsafe(1e-6, "acceptable_tol")
    solver.set_option_unsafe(12, "acceptable_iter")

why

Code quote:

    solver.set_option_unsafe(1e-6, "acceptable_tol")
    solver.set_option_unsafe(12, "acceptable_iter")

examples/fes_multibody/cycling/cycling_with_different_driven_methods.py line 643 at r2 (raw file):

    )
    parser.add_argument("--max-iter", type=int, default=6000, help="Maximum IPOPT iterations.")
    args = parser.parse_args()

dont use parser

Code quote:

    parser = argparse.ArgumentParser(description="Run the cycling OCP with different driven methods.")
    parser.add_argument("--plot", action="store_true", help="Display animation and graphs after solving.")
    parser.add_argument(
        "--objective-mode",
        type=str,
        default="endurance_1500_weighted_fatigue",
        choices=["force_production", "endurance_1500_weighted_fatigue"],
        help="Objective used for the FES-driven OCP.",
    )
    parser.add_argument(
        "--linear-solver",
        type=str,
        default="ma57",
        help="IPOPT linear solver to use, e.g. ma57 or mumps.",
    )
    parser.add_argument("--max-iter", type=int, default=6000, help="Maximum IPOPT iterations.")
    args = parser.parse_args()

tests/shard1/test_bioptim_3_4_compat.py line 29 at r2 (raw file):

    assert ding2007._with_fatigue is True
    assert ding2003.name_dofs == ["Cn", "F", "A", "Tau1", "Km"]
    assert ding2007.name_dofs == ["Cn", "F", "A", "Tau1", "Km"]

Overlap with test_models_dynamics_without_bioptim

Code quote:

def test_model_maker_instantiates_ding_models_on_bioptim_3_4():
    ding2003 = ModelMaker.create_model("ding2003_with_fatigue", stim_time=[0, 0.1], sum_stim_truncation=10)
    ding2007 = ModelMaker.create_model("ding2007_with_fatigue", stim_time=[0, 0.1], sum_stim_truncation=10)

    assert ding2003._with_fatigue is True
    assert ding2007._with_fatigue is True
    assert ding2003.name_dofs == ["Cn", "F", "A", "Tau1", "Km"]
    assert ding2007.name_dofs == ["Cn", "F", "A", "Tau1", "Km"]

@Kev1CO Kev1CO left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kev1CO reviewed 1 file and all commit messages, made 13 comments, and resolved 3 discussions.
Reviewable status: 7 of 34 files reviewed, 29 unresolved discussions (waiting on mickaelbegon).


cocofest/models/ding2003/ding2003_with_fatigue.py line 187 at r2 (raw file):

Previously, Kev1CO (Kevin Co) wrote…

Same ? should'nt it be always not none ?

Follow up


cocofest/models/ding2003/ding2003_with_fatigue.py line 194 at r2 (raw file):

Previously, Kev1CO (Kevin Co) wrote…

Why ? shouldn't time alway be not None ?

Same, want an answer


cocofest/models/dynamical_model.py line 216 at r3 (raw file):

            for state_key in muscle_model.name_dof
            if state_key in state_dictionary
        ]

This list comprehension is way to packed.
Can you separate the muscle state key configuration from the muscle models in the msk model ?

Or can you use the configure_all_muscle_states from state_configure.py

Code quote:

    def _muscle_state_configuration_functions(self):
        state_configure = StateConfigure()
        state_dictionary = state_configure.state_dictionary
        return [
            (
                lambda ocp, nlp, state_key=state_key, muscle_model=muscle_model: state_dictionary[state_key](
                    ocp=ocp,
                    nlp=nlp,
                    as_states=True,
                    as_controls=False,
                    muscle_name=muscle_model.muscle_name,
                )
            )
            for muscle_model in self.muscles_dynamics_model
            for state_key in muscle_model.name_dof
            if state_key in state_dictionary
        ]

cocofest/models/ding2003/ding2003.py line 211 at r3 (raw file):

    @staticmethod
    def _legacy_state_inputs(cn, f, t, t_stim_prev, states, time, numerical_timeseries):

Can you explain to me the purpose of this addition?


cocofest/models/ding2003/ding2003.py line 399 at r3 (raw file):

                "tau1": states[3],
                "km": states[4],
            }

Why are you adding this here? The fatigue model (child of this class) has its own dynamics function so model.fatigue can't be true here.

Code quote:

        if model.with_fatigue:
            dynamics_kwargs |= {
                "a": states[2],
                "tau1": states[3],
                "km": states[4],
            }

cocofest/models/ding2003/ding2003.py line 401 at r3 (raw file):

            }
        if "cn" in inspect.signature(dxdt_fun).parameters:
            dxdt = dxdt_fun(**dynamics_kwargs)

Can you explain the meaning of this to me.

Code quote:

        if "cn" in inspect.signature(dxdt_fun).parameters:
            dxdt = dxdt_fun(**dynamics_kwargs)

cocofest/models/ding2003/ding2003_with_fatigue.py line 104 at r3 (raw file):

    # ---- Needed for the example ---- #
    @property
    def name_dof(self, with_muscle_name: bool = False) -> list[str]:

why removed the s?

Code quote:

of(

cocofest/models/ding2003/ding2003_with_fatigue.py line 167 at r3 (raw file):

            The value of the force (N)
        a: MX
            The value of the scaling factor (unitless)

not unitless: s^-2


cocofest/models/ding2007/ding2007.py line 324 at r3 (raw file):

                "tau1": states[3],
                "km": states[4],
            }

Similar comment than for the ding2003 model
Fatigue can not be here + what is the inspect.signature thing

Code quote:

        if model.with_fatigue:
            dynamics_kwargs |= {
                "a": states[2],
                "tau1": states[3],
                "km": states[4],
            }

cocofest/models/ding2007/ding2007_with_fatigue.py line 155 at r3 (raw file):

            The value of the force (N)
        a: MX
            The value of the scaling factor (unitless)

not unitless


cocofest/models/ding2007/ding2007_with_fatigue.py line 317 at r3 (raw file):

            state_names = DingModelPulseWidthFrequency._get_state_dot_names(nlp, model)
            states_dot = DingModelPulseWidthFrequency._get_states_dot_scaled_cx(nlp, state_names)
            defects = states_dot * nlp.dt - dxdt * nlp.dt

This is needed for collocation, I used it for this model specifically but can you make it available for the other model (the ding2003 and hmed2018) otherwise they are not collocation ready

Code quote:

        defects = None
        if isinstance(nlp.dynamics_type.ode_solver, OdeSolver.COLLOCATION) and nlp.model is model:
            state_names = DingModelPulseWidthFrequency._get_state_dot_names(nlp, model)
            states_dot = DingModelPulseWidthFrequency._get_states_dot_scaled_cx(nlp, state_names)
            defects = states_dot * nlp.dt - dxdt * nlp.dt

cocofest/models/hmed2018/hmed2018.py line 178 at r3 (raw file):

            t_stim_prev = numerical_timeseries
        if controls is not None:
            pulse_intensity = controls

Similar, why these?

Code quote:

        if states is not None:
            cn = states[0]
            f = states[1]
        if time is not None:
            t = time
        if numerical_timeseries is not None:
            t_stim_prev = numerical_timeseries
        if controls is not None:
            pulse_intensity = controls

cocofest/models/hmed2018/hmed2018_with_fatigue.py line 172 at r3 (raw file):

            tau1 = self.tau1_rest
        if km is None:
            km = self.km_rest

similar

Code quote:

        cn, f, t, t_stim_prev = self._legacy_state_inputs(cn, f, t, t_stim_prev, states, time, numerical_timeseries)
        if states is not None:
            a = states[2]
            tau1 = states[3]
            km = states[4]
        if controls is not None:
            pulse_intensity = controls
        if a is None:
            a = self.a_rest
        if tau1 is None:
            tau1 = self.tau1_rest
        if km is None:
            km = self.km_rest

@mickaelbegon

Copy link
Copy Markdown
Author

Thanks, I went through the latest review pass and pushed a follow-up in 615daa9.

Addressed in code:

  • simplified FesMskModel._muscle_state_configuration_functions so the per-muscle state configuration is split into its own helper instead of one packed comprehension
  • simplified the compatibility path in the Ding 2003 / Ding 2007 parent dynamics: the parent dynamics() now only passes the non-fatigue state set, while fatigue-specific states stay in the child dynamics() implementations
  • kept the explicit/legacy dispatch, but made it intentional via _uses_explicit_state_signature(...) + _resolve_legacy_inputs(...) instead of a broad runtime try/except
  • added collocation defects support to Ding 2003 and Hmed 2018 as well, and also wired the fatigue variants through the same defect helper
  • fixed Hmed 2018 pulse-intensity handling so scalar and vector inputs are both accepted cleanly
  • corrected the A / scaling-factor units in the fatigue model docstrings
  • added targeted tests for Ding 2003 and Hmed 2018 collocation defects

A few direct answers to the review questions:

  • _resolve_legacy_inputs(...) is there to support both call conventions that currently coexist in cocofest: the newer explicit bioptim-style calls (cn=..., f=..., t=...) and the older internal calls (states=..., time=..., numerical_timeseries=...). That lets the same model implementation work for the migrated bioptim path and the historical example/model code without a generic try/except around the whole dynamics call.
  • the inspect.signature(...) check is only used to distinguish those two system_dynamics(...) signatures deterministically. In practice, it separates the Ding/Hmed explicit signature from the Marion-style legacy signature.
  • the fatigue state kwargs were removed from the parent Ding 2003 / Ding 2007 dynamics() functions; the child fatigue models now keep ownership of the fatigue-specific dynamics path.
  • on name_dof / name_dofs: I kept name_dof as the canonical raw state-name list used by the model internals, and name_dofs remains the compatibility accessor exposed elsewhere in cocofest / tests.

Validation on my side after this push:

  • python -m pytest tests/shard1/test_bioptim_3_4_compat.py tests/shard1/test_models_dynamics_without_bioptim.py -q -> 14 passed
  • python -m pytest tests/shard2/test_run_examples.py -q -> 28 passed

I’ll keep an eye on the next review round and fold in any follow-up comments.

@mickaelbegon

Copy link
Copy Markdown
Author

Small follow-up on the remaining review questions on this PR: the latest push on this branch is still 615daa9.

What changed relative to the older review snapshots:

  • the parent Ding 2003 / Ding 2007 dynamics() functions no longer carry fatigue-only kwargs; those stay in the child fatigue model implementations now
  • the packed list comprehension in FesMskModel was split into a per-muscle helper so the state configuration path is easier to read
  • collocation defects support is now shared across Ding 2003, Ding 2007, and Hmed 2018, including the fatigue variants
  • the Hmed 2018 scalar / vector pulse-intensity path was cleaned up so both historical calls and the explicit bioptim 3.4 path behave consistently

And to answer the “why is this here?” questions directly:

  • _resolve_legacy_inputs(...) is only a bridge between the two call styles that currently coexist in cocofest (cn=..., f=..., t=... versus states=..., time=..., numerical_timeseries=...). It lets the migrated bioptim path and the older model/example path share the same model code without a broad try/except around the whole dynamics call.
  • _uses_explicit_state_signature(...) only decides which of those two system_dynamics(...) signatures a given model exposes. This is needed because Marion models still use the historical signature while the Ding/Hmed migrated path now uses the explicit one.
  • name_dof remains the raw canonical state-name list used internally by the models, while name_dofs is kept as the compatibility accessor expected elsewhere in cocofest.

I re-ran after that cleanup:

  • python -m pytest tests/shard1/test_bioptim_3_4_compat.py tests/shard1/test_models_dynamics_without_bioptim.py -q -> 14 passed
  • python -m pytest tests/shard2/test_run_examples.py -q -> 28 passed

If you want, I can also go thread-by-thread in Reviewable wording and answer each point more literally there, but from the code side the follow-up concerns above are now reflected in the branch.

@Kev1CO Kev1CO left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Kev1CO reviewed 27 files and all commit messages, and resolved 21 discussions.
Reviewable status: all files reviewed, 8 unresolved discussions (waiting on mickaelbegon).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants