Skip to content

Commit 0b7b5af

Browse files
authored
Merge pull request #1048 from pariterre/master
Formalized parameters optimization (#1048)
2 parents 150c75c + 93f93d0 commit 0b7b5af

28 files changed

Lines changed: 307 additions & 221 deletions

.vscode/settings.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,7 @@
1010
"editor.rulers": [
1111
120
1212
]
13-
}
13+
},
14+
"python-envs.defaultEnvManager": "ms-python.python:conda",
15+
"python-envs.defaultPackageManager": "ms-python.python:conda"
1416
}

bioptim/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -239,7 +239,7 @@
239239
from .optimization.optimal_control_program import OptimalControlProgram
240240
from .optimization.optimization_variable import OptimizationVariableList
241241
from .optimization.vector_layout import OrderingStrategy
242-
from .optimization.parameters import ParameterList, ParameterContainer
242+
from .optimization.parameters import Parameter, ParameterList, ParameterContainer
243243
from .optimization.problem_type import SocpType
244244
from .optimization.receding_horizon_optimization import (
245245
CyclicNonlinearModelPredictiveControl,

bioptim/dynamics/dynamics_functions.py

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import TYPE_CHECKING
2+
13
from casadi import vertcat, MX, SX
24

35
from ..models.protocols.holonomic_constraints import HolonomicConstraintsFcn
@@ -6,6 +8,9 @@
68
from ..optimization.optimization_variable import OptimizationVariable
79
from ..misc.parameters_types import AnyListOptional, CX, CXOptional, Str, Tuple
810

11+
if TYPE_CHECKING:
12+
from ..optimization.non_linear_program import NonLinearProgram
13+
914

1015
class DynamicsFunctions:
1116
"""
@@ -26,7 +31,7 @@ class DynamicsFunctions:
2631
"""
2732

2833
@staticmethod
29-
def get_fatigable_tau(nlp, states: CX, controls: CX) -> CX:
34+
def get_fatigable_tau(nlp: "NonLinearProgram", states: CX, controls: CX) -> CX:
3035
"""
3136
Apply the forward dynamics including (or not) the torque fatigue
3237
@@ -82,7 +87,7 @@ def get_fatigable_tau(nlp, states: CX, controls: CX) -> CX:
8287
@staticmethod
8388
def get_fatigue_states(
8489
states,
85-
nlp,
90+
nlp: "NonLinearProgram",
8691
mus_activations,
8792
):
8893

@@ -141,7 +146,7 @@ def get(var: OptimizationVariable, cx: CX):
141146
return var.mapping.to_second.map(cx[var.index, :])
142147

143148
@staticmethod
144-
def compute_qdot(nlp, q: CX, qdot: CX):
149+
def compute_qdot(nlp: "NonLinearProgram", q: CX, qdot: CX):
145150
"""
146151
Easy accessor to derivative of q
147152
@@ -175,7 +180,7 @@ def compute_qdot(nlp, q: CX, qdot: CX):
175180
return mapping.to_first.map(nlp.model.reshape_qdot()(q, qdot, nlp.parameters.cx))
176181

177182
@staticmethod
178-
def compute_qddot(nlp, q: CX, qdot: CX, tau: CX, external_forces: CX):
183+
def compute_qddot(nlp: "NonLinearProgram", q: CX, qdot: CX, tau: CX, external_forces: CX):
179184
"""
180185
Easy accessor to derivative of qdot
181186
@@ -202,7 +207,7 @@ def compute_qddot(nlp, q: CX, qdot: CX, tau: CX, external_forces: CX):
202207

203208
@staticmethod
204209
def collect_tau(
205-
nlp,
210+
nlp: "NonLinearProgram",
206211
q: CX,
207212
qdot: CX,
208213
parameters: CX,
@@ -231,7 +236,7 @@ def collect_tau(
231236
return tau
232237

233238
@staticmethod
234-
def get_contact_defects(nlp, q: CX, qdot: CX, slope_qdot: CX):
239+
def get_contact_defects(nlp: "NonLinearProgram", q: CX, qdot: CX, slope_qdot: CX):
235240
"""
236241
Get the defects associated with implicit contacts.
237242
@@ -263,7 +268,9 @@ def get_contact_defects(nlp, q: CX, qdot: CX, slope_qdot: CX):
263268
return contact_defects
264269

265270
@staticmethod
266-
def get_fatigue_defects(key: Str, dxdt_defects: CX, slopes: CX, nlp, states: CX, controls: CX) -> Tuple[CX, CX]:
271+
def get_fatigue_defects(
272+
key: Str, dxdt_defects: CX, slopes: CX, nlp: "NonLinearProgram", states: CX, controls: CX
273+
) -> Tuple[CX, CX]:
267274
"""
268275
Get the dxdt and slopes associated with fatigue elements.
269276
These are added to compute the defects in the case where there is fatigue.
@@ -302,7 +309,7 @@ def get_fatigue_defects(key: Str, dxdt_defects: CX, slopes: CX, nlp, states: CX,
302309
return dxdt_defects, slopes
303310

304311
@staticmethod
305-
def get_external_forces_from_contacts(nlp, q, qdot, contact_types, external_forces: MX | SX):
312+
def get_external_forces_from_contacts(nlp: "NonLinearProgram", q, qdot, contact_types, external_forces: MX | SX):
306313
"""
307314
Get the external forces associated with the contacts defined in the model.
308315
@@ -353,7 +360,7 @@ def get_external_forces_from_contacts(nlp, q, qdot, contact_types, external_forc
353360

354361
@staticmethod
355362
def forward_dynamics(
356-
nlp,
363+
nlp: "NonLinearProgram",
357364
q: CX,
358365
qdot: CX,
359366
tau: CX,
@@ -412,7 +419,7 @@ def forward_dynamics(
412419

413420
@staticmethod
414421
def inverse_dynamics(
415-
nlp,
422+
nlp: "NonLinearProgram",
416423
q: CX,
417424
qdot: CX,
418425
qddot: CX,
@@ -472,7 +479,7 @@ def inverse_dynamics(
472479
return tau_var_mapping.map(tau)
473480

474481
@staticmethod
475-
def compute_muscle_dot(nlp, muscle_excitations: CX, muscle_activations: CX):
482+
def compute_muscle_dot(nlp: "NonLinearProgram", muscle_excitations: CX, muscle_activations: CX):
476483
"""
477484
Easy accessor to derivative of muscle activations
478485
@@ -494,7 +501,7 @@ def compute_muscle_dot(nlp, muscle_excitations: CX, muscle_activations: CX):
494501

495502
@staticmethod
496503
def compute_tau_from_muscle(
497-
nlp,
504+
nlp: "NonLinearProgram",
498505
q: CX,
499506
qdot: CX,
500507
muscle_activations: CX,
@@ -530,7 +537,7 @@ def compute_tau_from_muscle(
530537
return nlp.model.muscle_joint_torque()(activations, q, qdot, nlp.parameters.cx)
531538

532539
@staticmethod
533-
def no_states_mapping(nlp):
540+
def no_states_mapping(nlp: "NonLinearProgram"):
534541
for key in nlp.states.keys():
535542
if nlp.variable_mappings[key].actually_does_a_mapping():
536543
raise NotImplementedError(

bioptim/examples/getting_started/custom_parameters.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,12 @@
2828
ObjectiveList,
2929
PhaseDynamics,
3030
VariableScaling,
31+
Parameter,
3132
)
3233
from bioptim.examples.utils import ExampleUtils
3334

3435

35-
def my_parameter_function(bio_model: TorqueBiorbdModel, value: MX, extra_value: Any):
36+
def my_parameter_function(bio_model: TorqueBiorbdModel, parameter: Parameter, extra_value: Any):
3637
"""
3738
The pre dynamics function is called right before defining the dynamics of the system. If one wants to
3839
modify the dynamics (e.g. optimize the gravity in this case), then this function is the proper way to do it.
@@ -41,17 +42,17 @@ def my_parameter_function(bio_model: TorqueBiorbdModel, value: MX, extra_value:
4142
----------
4243
bio_model: TorqueBiorbdModel
4344
The model to modify by the parameters
44-
value: MX
45-
The CasADi variables to modify the model
45+
parameter: Parameter
46+
The optimization variables to modify the model
4647
extra_value: Any
4748
Any parameters required by the user. The name(s) of the extra_value must match those used in parameter.add
4849
"""
4950

50-
value[2] *= extra_value
51-
bio_model.set_gravity(value)
51+
parameter.mx[2] *= extra_value
52+
bio_model.set_gravity(parameter)
5253

5354

54-
def set_mass(bio_model: TorqueBiorbdModel, value: MX):
55+
def set_mass(bio_model: TorqueBiorbdModel, parameter: Parameter):
5556
"""
5657
The pre dynamics function is called right before defining the dynamics of the system. If one wants to
5758
modify the dynamics (e.g. optimize the gravity in this case), then this function is the proper way to do it.
@@ -60,11 +61,11 @@ def set_mass(bio_model: TorqueBiorbdModel, value: MX):
6061
----------
6162
bio_model: TorqueBiorbdModel
6263
The model to modify by the parameters
63-
value: MX
64-
The CasADi variables to modify the model
64+
parameter: Parameter
65+
The optimization variables to modify the model
6566
"""
66-
67-
bio_model.segments[0].characteristics().setMass(value)
67+
# Since Biorbd uses MX internally, we need to use the MX version of the variable, which is accessible through the .mx attribute
68+
bio_model.segments[0].characteristics().setMass(parameter.mx)
6869

6970

7071
def my_target_function(controller: PenaltyController, key: str) -> MX:

bioptim/examples/toy_examples/feature_examples/example_parameter_scaling.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
PhaseDynamics,
2626
VariableScaling,
2727
SolutionMerge,
28+
Parameter,
2829
)
2930
from bioptim.examples.utils import ExampleUtils
3031
import numpy as np
@@ -106,8 +107,8 @@ def generate_dat_to_track(
106107
)
107108

108109

109-
def my_parameter_function(bio_model: TorqueBiorbdModel, value: MX):
110-
bio_model.set_gravity(value)
110+
def my_parameter_function(bio_model: TorqueBiorbdModel, parameter: Parameter):
111+
bio_model.set_gravity(parameter)
111112

112113

113114
def my_target_function(controller: PenaltyController, key: str) -> MX:

bioptim/examples/toy_examples/holonomic_constraints/custom_dynamics.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -252,19 +252,18 @@ class HolonomicMusclesBiorbdModel(HolonomicBiorbdModel, HolonomicMusclesDynamics
252252
def __init__(
253253
self,
254254
bio_model: str | biorbd.Model,
255-
friction_coefficients: np.ndarray = None,
256255
parameters: ParameterList = None,
257256
holonomic_constraints: HolonomicConstraintsList | None = None,
258257
dependent_joint_index: list[int] | tuple[int, ...] = None,
259258
independent_joint_index: list[int] | tuple[int, ...] = None,
260259
):
261-
HolonomicBiorbdModel.__init__(self, bio_model, friction_coefficients, parameters)
260+
HolonomicBiorbdModel.__init__(self, bio_model, parameters)
262261
if holonomic_constraints is not None:
263262
self.set_holonomic_configuration(holonomic_constraints, dependent_joint_index, independent_joint_index)
264263
HolonomicMusclesDynamics.__init__(self)
265264

266265
def serialize(self) -> tuple[Callable, dict]:
267-
return HolonomicMusclesBiorbdModel, dict(bio_model=self.path, friction_coefficients=self.friction_coefficients)
266+
return HolonomicMusclesBiorbdModel, dict(bio_model=self.path)
268267

269268

270269
class AlgebraicHolonomicMusclesBiorbdModel(HolonomicMusclesBiorbdModel):

bioptim/examples/toy_examples/moving_horizon_estimation/multi_cyclic_nmpc_with_parameters.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
Solver,
3434
VariableScaling,
3535
OnlineOptim,
36+
Parameter,
3637
)
3738
from bioptim.examples.utils import ExampleUtils
3839
from casadi import MX, SX, vertcat
@@ -55,7 +56,7 @@ def advance_window_initial_guess_states(self, sol, n_cycles_simultaneous=None):
5556
return True
5657

5758

58-
def dummy_parameter_function(bio_model, value: MX):
59+
def dummy_parameter_function(bio_model, parameter: Parameter):
5960
return
6061

6162

bioptim/examples/toy_examples/stochastic_optimal_control/arm_reaching_torque_driven_collocations.py

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@
2525
Axis,
2626
SolutionMerge,
2727
DynamicsOptions,
28+
Parameter,
29+
ParameterList,
30+
ParameterObjectiveList,
2831
)
2932
from bioptim.examples.toy_examples.stochastic_optimal_control.arm_reaching_torque_driven_implicit import ExampleType
3033
from bioptim.examples.utils import ExampleUtils
@@ -52,6 +55,10 @@ def sensory_reference(
5255
return hand_pos_velo
5356

5457

58+
def set_friction_coefficients(bio_model, parameter: Parameter):
59+
bio_model.set_friction_coefficients(parameter)
60+
61+
5562
def prepare_socp(
5663
biorbd_model_path: str,
5764
final_time: float,
@@ -64,6 +71,7 @@ def prepare_socp(
6471
q_opt: np.ndarray = None,
6572
qdot_opt: np.ndarray = None,
6673
tau_opt: np.ndarray = None,
74+
optimize_friction_coefficients: bool = False,
6775
use_sx=False,
6876
) -> StochasticOptimalControlProgram:
6977
"""
@@ -86,6 +94,8 @@ def prepare_socp(
8694
The magnitude of the sensory noise
8795
example_type
8896
The type of problem to solve (CIRCLE or BAR)
97+
optimize_friction_coefficients: bool
98+
If the friction coefficients should be optimized or not
8999
use_sx: bool
90100
If SX should be used instead of MX
91101
@@ -103,6 +113,27 @@ def prepare_socp(
103113
initial_cov=initial_cov,
104114
)
105115

116+
if optimize_friction_coefficients:
117+
parameters = ParameterList(use_sx=use_sx)
118+
parameters.add("friction_coefficients", set_friction_coefficients, size=4)
119+
120+
target = np.array(2 * np.array([0.05, 0.025, 0.025, 0.05]))
121+
parameter_objectives = ParameterObjectiveList()
122+
parameter_objectives.add(
123+
ObjectiveFcn.Parameter.MINIMIZE_PARAMETER,
124+
key="friction_coefficients",
125+
target=target,
126+
quadratic=True,
127+
)
128+
129+
parameter_init = InitialGuessList()
130+
parameter_init.add("friction_coefficients", initial_guess=target)
131+
132+
else:
133+
parameters = None
134+
parameter_objectives = None
135+
parameter_init = None
136+
106137
bio_model = StochasticTorqueBiorbdModel(
107138
biorbd_model_path,
108139
problem_type=problem_type,
@@ -114,10 +145,14 @@ def prepare_socp(
114145
sensory_reference=sensory_reference,
115146
n_references=4, # This number must be in agreement with what is declared in sensory_reference
116147
n_feedbacks=4,
148+
parameters=parameters,
149+
parameter_init=parameter_init,
117150
use_sx=use_sx,
118-
friction_coefficients=np.array([[0.05, 0.025], [0.025, 0.05]]),
119151
)
120152

153+
if not optimize_friction_coefficients:
154+
bio_model.set_friction_coefficients(np.array([[0.05, 0.025], [0.025, 0.05]]))
155+
121156
n_tau = bio_model.nb_tau
122157
n_q = bio_model.nb_q
123158
n_qdot = bio_model.nb_qdot
@@ -255,10 +290,13 @@ def prepare_socp(
255290
x_init=x_init,
256291
u_init=u_init,
257292
a_init=a_init,
293+
parameters=parameters,
258294
x_bounds=x_bounds,
259295
u_bounds=u_bounds,
260296
a_bounds=a_bounds,
261297
objective_functions=objective_functions,
298+
parameter_objectives=parameter_objectives,
299+
parameter_init=parameter_init,
262300
constraints=constraints,
263301
control_type=ControlType.CONSTANT_WITH_LAST_NODE,
264302
n_threads=1,

bioptim/examples/toy_examples/stochastic_optimal_control/arm_reaching_torque_driven_explicit.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -396,11 +396,13 @@ def prepare_socp(
396396
n_noised_controls=2,
397397
sensory_noise_magnitude=sensory_noise_magnitude,
398398
motor_noise_magnitude=motor_noise_magnitude,
399-
friction_coefficients=np.array([[0.05, 0.025], [0.025, 0.05]]),
400399
sensory_reference=sensory_reference,
401400
)
402401
bio_model.force_field_magnitude = force_field_magnitude
403402

403+
# Please refer to the arm_reaching_torque_driven_collocations.py example for an example of optimizing these values
404+
bio_model.set_friction_coefficients(np.array([[0.05, 0.025], [0.025, 0.05]]))
405+
404406
n_tau = bio_model.nb_tau
405407
n_q = bio_model.nb_q
406408
n_qdot = bio_model.nb_qdot

bioptim/examples/toy_examples/stochastic_optimal_control/arm_reaching_torque_driven_implicit.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,8 @@ def prepare_socp(
9898
The type of problem to solve (CIRCLE or BAR)
9999
with_cholesky: bool
100100
If True, whether to use the Cholesky factorization of the covariance matrix or not
101+
with_scaling: bool
102+
If True, whether to use scaling for the controls or not
101103
102104
Returns
103105
-------
@@ -117,9 +119,11 @@ def prepare_socp(
117119
n_feedbacks=4,
118120
n_noised_states=4,
119121
n_noised_controls=2,
120-
friction_coefficients=np.array([[0.05, 0.025], [0.025, 0.05]]),
121122
)
122123

124+
# Please refer to the arm_reaching_torque_driven_collocations.py example for an example of optimizing these values
125+
bio_model.set_friction_coefficients(np.array([[0.05, 0.025], [0.025, 0.05]]))
126+
123127
n_tau = bio_model.nb_tau
124128
n_q = bio_model.nb_q
125129
n_qdot = bio_model.nb_qdot

0 commit comments

Comments
 (0)