Skip to content

Commit 17e3628

Browse files
authored
Merge pull request #816 from pariterre/master
Final stretch of the reformating!
2 parents 1030ae7 + 0a6c34e commit 17e3628

37 files changed

+471
-731
lines changed

.github/workflows/run_tests_linux.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,9 @@ jobs:
4141
mamba list
4242
4343
- name: Install extra dependencies
44-
run: mamba install pytest-cov black pytest pytest-cov codecov packaging -cconda-forge
44+
run: |
45+
mamba install pytest-cov black pytest pytest-cov codecov packaging -cconda-forge
46+
sudo apt install -y librhash-dev
4547
4648
- name: Install ACADOS on Linux
4749
run: |

bioptim/dynamics/configure_problem.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -373,7 +373,6 @@ def stochastic_torque_driven(
373373
DynamicsFunctions.stochastic_torque_driven,
374374
with_contact=with_contact,
375375
with_friction=with_friction,
376-
allow_free_variables=True,
377376
)
378377

379378
@staticmethod
@@ -706,7 +705,7 @@ def holonomic_torque_driven(ocp, nlp):
706705
ConfigureProblem.configure_dynamics_function(ocp, nlp, DynamicsFunctions.holonomic_torque_driven)
707706

708707
@staticmethod
709-
def configure_dynamics_function(ocp, nlp, dyn_func, allow_free_variables: bool = False, **extra_params):
708+
def configure_dynamics_function(ocp, nlp, dyn_func, **extra_params):
710709
"""
711710
Configure the dynamics of the system
712711
@@ -718,12 +717,8 @@ def configure_dynamics_function(ocp, nlp, dyn_func, allow_free_variables: bool =
718717
A reference to the phase
719718
dyn_func: Callable[time, states, controls, param, algebraic_states] | tuple[Callable[time, states, controls, param, algebraic_states], ...]
720719
The function to get the derivative of the states
721-
allow_free_variables: bool
722-
If it is expected the dynamics depends on more than the variable provided by bioptim. It is therefore to the
723-
user prerogative to wrap the Function around another function to lift the free variable
724720
"""
725721

726-
nlp.parameters = ocp.parameters
727722
DynamicsFunctions.apply_parameters(nlp.parameters.mx, nlp)
728723

729724
if not isinstance(dyn_func, (tuple, list)):
@@ -757,7 +752,6 @@ def configure_dynamics_function(ocp, nlp, dyn_func, allow_free_variables: bool =
757752
[dynamics_dxdt],
758753
["t_span", "x", "u", "p", "a"],
759754
["xdot"],
760-
{"allow_free": allow_free_variables},
761755
),
762756
)
763757

@@ -790,7 +784,6 @@ def configure_dynamics_function(ocp, nlp, dyn_func, allow_free_variables: bool =
790784
[dynamics_eval.defects],
791785
["t_span", "x", "u", "p", "a", "xdot"],
792786
["defects"],
793-
{"allow_free": allow_free_variables},
794787
)
795788
)
796789
if nlp.dynamics_type.expand_dynamics:

bioptim/dynamics/dynamics_functions.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -238,10 +238,14 @@ def stochastic_torque_driven(
238238

239239
sensory_input = nlp.model.sensory_reference(states, controls, parameters, algebraic_states, nlp)
240240

241-
mapped_motor_noise = nlp.model.motor_noise_sym_mx
242-
mapped_sensory_feedback_torque = nlp.model.compute_torques_from_noise_and_feedback(k_matrix, sensory_input, ref)
241+
mapped_motor_noise = parameters[nlp.parameters["motor_noise"].index]
242+
mapped_sensory_noise = parameters[nlp.parameters["sensory_noise"].index]
243+
mapped_sensory_feedback_torque = nlp.model.compute_torques_from_noise_and_feedback(
244+
mapped_sensory_noise, k_matrix, sensory_input, ref
245+
)
246+
243247
if "tau" in nlp.model.motor_noise_mapping.keys():
244-
mapped_motor_noise = nlp.model.motor_noise_mapping["tau"].to_second.map(nlp.model.motor_noise_sym_mx)
248+
mapped_motor_noise = nlp.model.motor_noise_mapping["tau"].to_second.map(nlp.parameters["motor_noise"].mx)
245249
mapped_sensory_feedback_torque = nlp.model.motor_noise_mapping["tau"].to_second.map(
246250
mapped_sensory_feedback_torque
247251
)

bioptim/dynamics/integrator.py

Lines changed: 3 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ def __init__(self, ode: dict, ode_opt: dict):
7171
self.defects_type = ode_opt["defects_type"]
7272
self.control_type = ode_opt["control_type"]
7373
self.function = None
74-
self.allow_free_variables = ode_opt["allow_free_variables"]
7574
self.duplicate_starting_point = ode_opt["duplicate_starting_point"]
7675

7776
# Initialize is expected to set step_time
@@ -80,13 +79,7 @@ def __init__(self, ode: dict, ode_opt: dict):
8079
self.step_times_from_dt = self._time_xall_from_dt_func
8180
self.function = Function(
8281
"integrator",
83-
[
84-
self.t_span_sym,
85-
self._x_sym_modified,
86-
self.u_sym,
87-
self.param_sym,
88-
self.a_sym,
89-
],
82+
[self.t_span_sym, self._x_sym_modified, self.u_sym, self.param_sym, self.a_sym],
9083
self.dxdt(
9184
states=self.x_sym,
9285
controls=self.u_sym,
@@ -95,7 +88,6 @@ def __init__(self, ode: dict, ode_opt: dict):
9588
),
9689
self._input_names,
9790
self._output_names,
98-
{"allow_free": self.allow_free_variables},
9991
)
10092

10193
@property
@@ -505,7 +497,6 @@ def _initialize(self, ode: dict, ode_opt: dict):
505497
"""
506498
self.method = ode_opt["method"]
507499
self.degree = ode_opt["irk_polynomial_interpolation_degree"]
508-
self.allow_free_variables = ode_opt["allow_free_variables"]
509500

510501
# Coefficients of the collocation equation
511502
self._c = self.cx.zeros((self.degree + 1, self.degree + 1))
@@ -621,13 +612,9 @@ def dxdt(
621612

622613
if self.defects_type == DefectType.EXPLICIT:
623614
f_j = self.fun(
624-
t,
625-
states[j + 1],
626-
self.get_u(controls, self._integration_time[j]),
627-
params,
628-
algebraic_states,
615+
t, states[j + 1], self.get_u(controls, self._integration_time[j]), params, algebraic_states
629616
)[:, self.ode_idx]
630-
defects.append(xp_j - self.h * f_j)
617+
defects.append(xp_j - f_j * self.h)
631618
elif self.defects_type == DefectType.IMPLICIT:
632619
defects.append(
633620
self.implicit_fun(

bioptim/dynamics/ode_solver.py

Lines changed: 8 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,14 @@ class OdeSolverBase:
1818
Properly set the integration in an nlp
1919
"""
2020

21-
def __init__(self, allow_free_variables: bool = False, duplicate_starting_point: bool = False):
21+
def __init__(self, duplicate_starting_point: bool = False):
2222
"""
2323
Parameters
2424
----------
25-
allow_free_variables: bool
26-
If the free variables are allowed in the integrator's casadi function
2725
duplicate_starting_point: bool
2826
If the starting point should be duplicated in the integrator's casadi function
2927
"""
30-
self.allow_free_variables = allow_free_variables
28+
3129
self.duplicate_starting_point = duplicate_starting_point
3230

3331
@property
@@ -161,9 +159,7 @@ def param_ode(self, nlp) -> MX:
161159
"""
162160
return nlp.parameters.cx
163161

164-
def initialize_integrator(
165-
self, ocp, nlp, dynamics_index: int, node_index: int, allow_free_variables: bool = False, **extra_opt
166-
) -> Callable:
162+
def initialize_integrator(self, ocp, nlp, dynamics_index: int, node_index: int, **extra_opt) -> Callable:
167163
"""
168164
Initialize the integrator
169165
@@ -177,8 +173,6 @@ def initialize_integrator(
177173
The current dynamics to resolve (that can be referred to nlp.dynamics_func[index])
178174
node_index
179175
The index of the node currently initialized
180-
allow_free_variables
181-
If the free variables are allowed in the integrator's casadi function
182176
extra_opt
183177
Any extra options to pass to the integrator
184178
@@ -196,7 +190,6 @@ def initialize_integrator(
196190
"cx": nlp.cx,
197191
"control_type": nlp.control_type,
198192
"defects_type": self.defects_type,
199-
"allow_free_variables": allow_free_variables,
200193
"param_scaling": vertcat(*[nlp.parameters[key].scaling.scaling for key in nlp.parameters.keys()]),
201194
"ode_index": node_index if nlp.dynamics_func[dynamics_index].size2_out("xdot") > 1 else 0,
202195
"duplicate_starting_point": self.duplicate_starting_point,
@@ -231,43 +224,23 @@ def prepare_dynamic_integrator(self, ocp, nlp):
231224
"""
232225

233226
# Primary dynamics
234-
dynamics = [
235-
nlp.ode_solver.initialize_integrator(
236-
ocp, nlp, dynamics_index=0, node_index=0, allow_free_variables=self.allow_free_variables
237-
)
238-
]
227+
dynamics = [nlp.ode_solver.initialize_integrator(ocp, nlp, dynamics_index=0, node_index=0)]
239228
if nlp.phase_dynamics == PhaseDynamics.SHARED_DURING_THE_PHASE:
240229
dynamics = dynamics * nlp.ns
241230
else:
242231
for node_index in range(1, nlp.ns):
243-
dynamics.append(
244-
nlp.ode_solver.initialize_integrator(
245-
ocp,
246-
nlp,
247-
dynamics_index=0,
248-
node_index=node_index,
249-
allow_free_variables=self.allow_free_variables,
250-
)
251-
)
232+
dynamics.append(nlp.ode_solver.initialize_integrator(ocp, nlp, dynamics_index=0, node_index=node_index))
252233
nlp.dynamics = dynamics
253234

254235
# Extra dynamics
255236
extra_dynamics = []
256237
for i in range(1, len(nlp.dynamics_func)):
257-
extra_dynamics += [
258-
nlp.ode_solver.initialize_integrator(
259-
ocp, nlp, dynamics_index=i, node_index=0, allow_free_variables=True
260-
)
261-
]
238+
extra_dynamics += [nlp.ode_solver.initialize_integrator(ocp, nlp, dynamics_index=i, node_index=0)]
262239
if nlp.phase_dynamics == PhaseDynamics.SHARED_DURING_THE_PHASE:
263240
extra_dynamics = extra_dynamics * nlp.ns
264241
else:
265242
for node_index in range(1, nlp.ns):
266-
extra_dynamics += [
267-
nlp.ode_solver.initialize_integrator(
268-
ocp, nlp, dynamics_index=i, node_index=0, allow_free_variables=True
269-
)
270-
]
243+
extra_dynamics += [nlp.ode_solver.initialize_integrator(ocp, nlp, dynamics_index=i, node_index=0)]
271244
# TODO include this in nlp.dynamics so the index of nlp.dynamics_func and nlp.dynamics match
272245
nlp.extra_dynamics.append(extra_dynamics)
273246

@@ -564,9 +537,7 @@ def p_ode(self, nlp):
564537
def a_ode(self, nlp):
565538
return nlp.algebraic_states.scaled.cx
566539

567-
def initialize_integrator(
568-
self, ocp, nlp, dynamics_index: int, node_index: int, allow_free_variables: bool = False, **extra_opt
569-
):
540+
def initialize_integrator(self, ocp, nlp, dynamics_index: int, node_index: int, **extra_opt):
570541
raise NotImplementedError("CVODES is not yet implemented")
571542

572543
if extra_opt:
@@ -610,7 +581,6 @@ def initialize_integrator(
610581
),
611582
["t_span", "x0", "u", "p", "a"],
612583
["xf", "xall"],
613-
{"allow_free": allow_free_variables},
614584
)
615585
]
616586

bioptim/examples/stochastic_optimal_control/arm_reaching_muscle_driven.py

Lines changed: 20 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,8 @@ def stochastic_forward_dynamics(
8080
motor_noise = 0
8181
sensory_noise = 0
8282
if with_noise:
83-
motor_noise = nlp.model.motor_noise_sym_mx
84-
sensory_noise = nlp.model.sensory_noise_sym_mx
83+
motor_noise = nlp.parameters["motor_noise"].mx
84+
sensory_noise = nlp.parameters["sensory_noise"].mx
8585

8686
mus_excitations_fb = mus_excitations
8787
noise_torque = np.zeros(nlp.model.motor_noise_magnitude.shape)
@@ -156,20 +156,20 @@ def configure_stochastic_optimal_control_problem(ocp: OptimalControlProgram, nlp
156156
dyn_func=lambda time, states, controls, parameters, algebraic_states, nlp: nlp.dynamics_type.dynamic_function(
157157
time, states, controls, parameters, algebraic_states, nlp, with_noise=True
158158
),
159-
allow_free_variables=True,
160159
)
161160

162161

163162
def minimize_uncertainty(controllers: list[PenaltyController], key: str) -> cas.MX:
164163
"""
165164
Minimize the uncertainty (covariance matrix) of the states.
166165
"""
167-
dt = controllers[0].dt
166+
dt = controllers[0].dt.cx
168167
out = 0
169168
for i, ctrl in enumerate(controllers):
170169
cov_matrix = StochasticBioModel.reshape_to_matrix(ctrl.integrated_values["cov"].cx, ctrl.model.matrix_shape_cov)
171170
p_partial = cov_matrix[ctrl.states[key].index, ctrl.states[key].index]
172171
out += cas.trace(p_partial) * dt
172+
173173
return out
174174

175175

@@ -184,68 +184,48 @@ def get_cov_mat(nlp, node_index):
184184
m_matrix = StochasticBioModel.reshape_to_matrix(nlp.algebraic_states["m"].cx, nlp.model.matrix_shape_m)
185185

186186
CX_eye = cas.SX_eye if nlp.cx == cas.SX else cas.MX_eye
187-
sigma_w = cas.vertcat(nlp.model.sensory_noise_sym, nlp.model.motor_noise_sym) * CX_eye(6)
187+
sensory_noise = nlp.parameters["sensory_noise"].cx
188+
motor_noise = nlp.parameters["motor_noise"].cx
189+
sigma_w = cas.vertcat(sensory_noise, motor_noise) * CX_eye(6)
188190
cov_sym = cas.MX.sym("cov", nlp.integrated_values.cx.shape[0])
189191
cov_matrix = StochasticBioModel.reshape_to_matrix(cov_sym, nlp.model.matrix_shape_cov)
190192

191193
dx = stochastic_forward_dynamics(
192194
nlp.states.mx,
193195
nlp.controls.mx,
194-
nlp.parameters,
196+
nlp.parameters.mx,
195197
nlp.algebraic_states.mx,
196198
nlp,
197199
force_field_magnitude=nlp.model.force_field_magnitude,
198200
with_noise=True,
199201
)
200202

201203
dx.dxdt = cas.Function(
202-
"tp",
203-
[
204-
nlp.states.mx,
205-
nlp.controls.mx,
206-
nlp.parameters,
207-
nlp.algebraic_states.mx,
208-
nlp.model.sensory_noise_sym_mx,
209-
nlp.model.motor_noise_sym_mx,
210-
],
211-
[dx.dxdt],
212-
)(
213-
nlp.states.cx,
214-
nlp.controls.cx,
215-
nlp.parameters,
216-
nlp.algebraic_states.cx,
217-
nlp.model.sensory_noise_sym,
218-
nlp.model.motor_noise_sym,
219-
)
204+
"tp", [nlp.states.mx, nlp.controls.mx, nlp.parameters.mx, nlp.algebraic_states.mx], [dx.dxdt]
205+
)(nlp.states.cx, nlp.controls.cx, nlp.parameters.cx, nlp.algebraic_states.cx)
220206

221-
ddx_dwm = cas.jacobian(dx.dxdt, cas.vertcat(nlp.model.sensory_noise_sym, nlp.model.motor_noise_sym))
207+
ddx_dwm = cas.jacobian(dx.dxdt, cas.vertcat(sensory_noise, motor_noise))
222208
dg_dw = -ddx_dwm * dt
223209
ddx_dx = cas.jacobian(dx.dxdt, nlp.states.cx)
224210
dg_dx = -(ddx_dx * dt / 2 + CX_eye(ddx_dx.shape[0]))
225211

226212
p_next = m_matrix @ (dg_dx @ cov_matrix @ dg_dx.T + dg_dw @ sigma_w @ dg_dw.T) @ m_matrix.T
213+
214+
parameters = nlp.parameters.cx
215+
parameters[nlp.parameters["sensory_noise"].index] = nlp.model.sensory_noise_magnitude
216+
parameters[nlp.parameters["motor_noise"].index] = nlp.model.motor_noise_magnitude
217+
227218
func_eval = cas.Function(
228219
"p_next",
229-
[
230-
dt,
231-
nlp.states.cx,
232-
nlp.controls.cx,
233-
nlp.parameters,
234-
nlp.algebraic_states.cx,
235-
cov_sym,
236-
nlp.model.motor_noise_sym,
237-
nlp.model.sensory_noise_sym,
238-
],
220+
[dt, nlp.states.cx, nlp.controls.cx, nlp.parameters.cx, nlp.algebraic_states.cx, cov_sym],
239221
[p_next],
240222
)(
241223
nlp.dt,
242224
nlp.states.cx,
243225
nlp.controls.cx,
244-
nlp.parameters,
226+
parameters,
245227
nlp.algebraic_states.cx,
246228
nlp.integrated_values["cov"].cx,
247-
nlp.model.motor_noise_magnitude,
248-
nlp.model.sensory_noise_magnitude,
249229
)
250230
p_vector = StochasticBioModel.reshape_to_vector(func_eval)
251231
return p_vector
@@ -404,7 +384,6 @@ def prepare_socp(
404384
sensory_noise_magnitude=sensory_noise_magnitude,
405385
motor_noise_magnitude=motor_noise_magnitude,
406386
sensory_reference=sensory_reference,
407-
use_sx=use_sx,
408387
)
409388
bio_model.force_field_magnitude = force_field_magnitude
410389

@@ -587,7 +566,7 @@ def prepare_socp(
587566
max_bound=stochastic_max[curent_index : curent_index + n_states * n_states, :],
588567
)
589568

590-
integrated_value_functions = {"cov": lambda nlp, node_index: get_cov_mat(nlp, node_index)}
569+
integrated_value_functions = {"cov": get_cov_mat}
591570

592571
return StochasticOptimalControlProgram(
593572
bio_model,
@@ -722,8 +701,6 @@ def main():
722701
parameters = socp.nlp[0].parameters.cx
723702
algebraic_states = socp.nlp[0].algebraic_states.cx
724703
nlp = socp.nlp[0]
725-
motor_noise_sym = cas.MX.sym("motor_noise", 2, 1)
726-
sensory_noise_sym = cas.MX.sym("sensory_noise", 4, 1)
727704
out = stochastic_forward_dynamics(
728705
states,
729706
controls,
@@ -733,11 +710,7 @@ def main():
733710
force_field_magnitude=force_field_magnitude,
734711
with_noise=True,
735712
)
736-
dyn_fun = cas.Function(
737-
"dyn_fun",
738-
[states, controls, parameters, algebraic_states, motor_noise_sym, sensory_noise_sym],
739-
[out.dxdt],
740-
)
713+
dyn_fun = cas.Function("dyn_fun", [states, controls, parameters, algebraic_states], [out.dxdt])
741714

742715
fig, axs = plt.subplots(3, 2)
743716
n_simulations = 30

0 commit comments

Comments
 (0)