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

[RTM after push] Provide two examples of minmax OCP with extra parameters #835

Merged
merged 7 commits into from
Feb 9, 2024
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
"""
This example is inspired from the clear pike circle gymnastics skill. It is composed of two pendulums
This example is inspired from the giant circle gymnastics skill. It is composed of two pendulums
representing the trunk and legs segments (only the hip flexion is actuated). The objective is to minimize the
maximum torque (minmax) of the hip flexion while performing the clear pike circle motion. The maximum torque is included to the
maximum torque (minmax) of the hip flexion while performing the giant circle. The maximum torque is included to the
problem as a parameter, all torque interval re constrained to be smaller than this parameter, this parameter is the
minimized.
minimized. Two options are provided and compared to define initial and final states (0: bounds; 1: constraints)
"""

import numpy as np
Expand All @@ -15,6 +15,7 @@
DynamicsFcn,
ObjectiveList,
ConstraintList,
ConstraintFcn,
BoundsList,
InitialGuessList,
Node,
Expand All @@ -25,128 +26,113 @@
BiorbdModel,
PenaltyController,
ParameterObjectiveList,
ParameterConstraintList,
ConstraintFcn,
)
from matplotlib import pyplot as plt


def custom_constraint_max_tau(controller: PenaltyController) -> MX:
return controller.parameters["max_tau"].cx - controller.controls["tau"].cx

def custom_constraint_parameters(controller: PenaltyController) -> MX:
tau = controller.controls["tau"].cx_start
max_param = controller.parameters["max_tau"].cx
val = max_param - tau
return val

def custom_constraint_min_tau(controller: PenaltyController) -> MX:
return controller.parameters["min_tau"].cx - controller.controls["tau"].cx


def my_parameter_function(bio_model: biorbd.Model, value: MX):
return


def prepare_ocp(
method_bound_states: int = 0,
bio_model_path: str = "models/double_pendulum.bioMod",
) -> OptimalControlProgram:
bio_model = (BiorbdModel(bio_model_path), BiorbdModel(bio_model_path))
bio_model = BiorbdModel(bio_model_path)

# Problem parameters
n_shooting = (40, 40)
final_time = (1, 1)
tau_min, tau_max, tau_init = -300, 300, 0
n_shooting = 50
final_time = 1
tau_min, tau_max, tau_init = -10, 10, 0

# Mapping
tau_mappings = BiMappingList()
tau_mappings.add("tau", to_second=[None, 0], to_first=[1], phase=0)
tau_mappings.add("tau", to_second=[None, 0], to_first=[1], phase=1)
tau_mappings.add("tau", to_second=[None, 0], to_first=[1])

# Define the parameter to optimize
parameters = ParameterList()
parameter_init = InitialGuessList()
parameter_bounds = BoundsList()

parameters.add(
"max_tau", # The name of the parameter
my_parameter_function, # The function that modifies the biorbd model
"max_tau",
my_parameter_function,
size=1,
)
parameters.add(
"min_tau",
my_parameter_function,
size=1,
)

parameter_bounds = BoundsList()
parameter_init = InitialGuessList()
parameter_init["max_tau"] = 1
parameter_init["min_tau"] = -1

parameter_init["max_tau"] = 0
parameter_bounds.add("max_tau", min_bound=0, max_bound=tau_max, interpolation=InterpolationType.CONSTANT)
parameter_bounds.add("min_tau", min_bound=tau_min, max_bound=0, interpolation=InterpolationType.CONSTANT)

# Add phase independant objective functions
# Add phase independent objective functions
parameter_objectives = ParameterObjectiveList()
parameter_objectives.add(ObjectiveFcn.Parameter.MINIMIZE_PARAMETER, key="max_tau", weight=1000, quadratic=True)

# Add phase independant constraint functions
parameter_constraints = ParameterConstraintList()
parameter_constraints.add(ConstraintFcn.TRACK_PARAMETER, min_bound=-100, max_bound=100)
parameter_objectives.add(ObjectiveFcn.Parameter.MINIMIZE_PARAMETER, key="max_tau", weight=10, quadratic=True)
parameter_objectives.add(ObjectiveFcn.Parameter.MINIMIZE_PARAMETER, key="min_tau", weight=10, quadratic=True)

# Add objective functions
objective_functions = ObjectiveList()
objective_functions.add(ObjectiveFcn.Mayer.MINIMIZE_TIME, weight=1, phase=0, min_bound=0.1, max_bound=3)
objective_functions.add(ObjectiveFcn.Mayer.MINIMIZE_TIME, weight=1, phase=1, min_bound=0.1, max_bound=3)
objective_functions.add(ObjectiveFcn.Lagrange.MINIMIZE_CONTROL, key="tau", weight=10, phase=0)
objective_functions.add(ObjectiveFcn.Lagrange.MINIMIZE_CONTROL, key="tau", weight=10, phase=1)
objective_functions.add(ObjectiveFcn.Mayer.MINIMIZE_TIME, weight=1, phase=0, min_bound=1, max_bound=5)
objective_functions.add(ObjectiveFcn.Lagrange.MINIMIZE_CONTROL, key="tau", weight=1, phase=0)

# Constraints
constraints = ConstraintList()
constraints.add(custom_constraint_parameters, node=Node.ALL, min_bound=0, max_bound=tau_max)

# constraints.add(ConstraintFcn.TIME_CONSTRAINT, node=Node.END, target=3.8)

constraints.add(custom_constraint_max_tau, phase=0, node=Node.ALL_SHOOTING, min_bound=0, max_bound=tau_max)
constraints.add(custom_constraint_min_tau, phase=0, node=Node.ALL_SHOOTING, min_bound=tau_min, max_bound=0)

# Dynamics
dynamics = DynamicsList()
dynamics.add(DynamicsFcn.TORQUE_DRIVEN, with_contact=False)
dynamics.add(DynamicsFcn.TORQUE_DRIVEN, with_contact=False)

# Path constraint
x_bounds = BoundsList()
x_bounds.add(key="q", bounds=bio_model[0].bounds_from_ranges("q"), phase=0)
x_bounds.add(key="qdot", bounds=bio_model[0].bounds_from_ranges("qdot"), phase=0)
x_bounds.add(key="q", bounds=bio_model[1].bounds_from_ranges("q"), phase=1)
x_bounds.add(key="qdot", bounds=bio_model[1].bounds_from_ranges("qdot"), phase=1)

# change model bound for -pi, pi
for i in range(len(bio_model)):
x_bounds[i]["q"].min[1, :] = -np.pi
x_bounds[i]["q"].max[1, :] = np.pi

# Phase 0
x_bounds[0]["q"][0, 0] = np.pi
x_bounds[0]["q"][1, 0] = 0
x_bounds[0]["q"].min[1, -1] = 6 * np.pi / 8 - 0.1
x_bounds[0]["q"].max[1, -1] = 6 * np.pi / 8 + 0.1

# Phase 1
x_bounds[1]["q"][0, -1] = 3 * np.pi
x_bounds[1]["q"][1, -1] = 0

# Initial guess
x_init = InitialGuessList()
x_init.add(key="q", initial_guess=[0] * bio_model[0].nb_q, phase=0)
x_init.add(key="qdot", initial_guess=[0] * bio_model[0].nb_qdot, phase=0)
x_init.add(key="q", initial_guess=[1] * bio_model[1].nb_q, phase=1)
x_init.add(key="qdot", initial_guess=[1] * bio_model[1].nb_qdot, phase=1)

# Define control path constraint
n_tau = len(tau_mappings[0]["tau"].to_first)
u_bounds = BoundsList()
u_bounds.add(key="tau", min_bound=[tau_min] * n_tau, max_bound=[tau_max] * n_tau, phase=0)
u_bounds.add(key="tau", min_bound=[tau_min] * n_tau, max_bound=[tau_max] * n_tau, phase=1)

# Control initial guess
u_init = InitialGuessList()
u_init.add(key="tau", initial_guess=[tau_init] * n_tau, phase=0)
u_init.add(key="tau", initial_guess=[tau_init] * n_tau, phase=1)
x_bounds.add(key="q", bounds=bio_model.bounds_from_ranges("q"))
x_bounds.add(key="qdot", bounds=bio_model.bounds_from_ranges("qdot"))

x_bounds["q"].min[0, :] = 0
x_bounds["q"].max[0, :] = 3 * np.pi

x_bounds["q"].min[1, :] = -np.pi / 3
x_bounds["q"].max[1, :] = np.pi / 5

if method_bound_states == 0:
x_bounds["q"][0, 0] = np.pi + 0.01
x_bounds["q"][1, 0] = 0
x_bounds["qdot"][0, 0] = 0
x_bounds["qdot"][1, 0] = 0

x_bounds["q"][0, -1] = 3 * np.pi
x_bounds["q"][1, -1] = 0
else:
constraints.add(ConstraintFcn.TRACK_STATE, node=Node.START, key="q", target=[np.pi + 0.01, 0])
constraints.add(ConstraintFcn.TRACK_STATE, node=Node.START, key="qdot", target=[0, 0])
constraints.add(ConstraintFcn.TRACK_STATE, node=Node.END, key="q", target=[3 * np.pi, 0])

Check warning on line 126 in bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py

View check run for this annotation

Codecov / codecov/patch

bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py#L124-L126

Added lines #L124 - L126 were not covered by tests

return OptimalControlProgram(
bio_model,
dynamics,
n_shooting,
final_time,
x_init=x_init,
u_init=u_init,
x_bounds=x_bounds,
u_bounds=u_bounds,
objective_functions=objective_functions,
parameter_objectives=parameter_objectives,
parameter_constraints=parameter_constraints,
parameter_bounds=parameter_bounds,
parameter_init=parameter_init,
constraints=constraints,
Expand All @@ -157,14 +143,73 @@

def main():
# --- Prepare the ocp --- #
ocp = prepare_ocp()

# --- Solve the ocp --- #
sol = ocp.solve()

# --- Show results --- #
sol.animate()
sol.graphs(show_bounds=True)
fig, axs = plt.subplots(1, 3)
axs[0].set_title("Joint coordinates")
axs[0].set_ylabel("q [°]")
axs[1].set_title("Joint velocities")
axs[1].set_ylabel("qdot [°/s]")
axs[2].set_title("Generalized forces")
axs[2].set_ylabel("tau [Nm]")
for ax in [0, 1, 2]:
axs[ax].set_xlabel("Time [s]")
axs[ax].grid(True)

Check warning on line 155 in bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py

View check run for this annotation

Codecov / codecov/patch

bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py#L146-L155

Added lines #L146 - L155 were not covered by tests

linestyles = ["solid", "dashed"]

Check warning on line 157 in bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py

View check run for this annotation

Codecov / codecov/patch

bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py#L157

Added line #L157 was not covered by tests

for i, linestyle in enumerate(linestyles):

Check warning on line 159 in bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py

View check run for this annotation

Codecov / codecov/patch

bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py#L159

Added line #L159 was not covered by tests
# --- Prepare the ocp --- #
ocp = prepare_ocp(method_bound_states=i)

Check warning on line 161 in bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py

View check run for this annotation

Codecov / codecov/patch

bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py#L161

Added line #L161 was not covered by tests

# --- Solve the ocp --- #
sol = ocp.solve()

Check warning on line 164 in bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py

View check run for this annotation

Codecov / codecov/patch

bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py#L164

Added line #L164 was not covered by tests
# sol.print_cost()

# --- Show results --- #
# sol.animate()
# sol.graphs(show_bounds=True)

states = sol.decision_states()
controls = sol.decision_controls()

Check warning on line 172 in bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py

View check run for this annotation

Codecov / codecov/patch

bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py#L171-L172

Added lines #L171 - L172 were not covered by tests

q = np.array([item.flatten() for item in states["q"]])
qdot = np.array([item.flatten() for item in states["qdot"]])
tau = np.vstack([np.array([item.flatten() for item in controls["tau"]]), np.array([[np.nan]])])
time = np.array([item.full().flatten()[0] for item in sol.stepwise_time()])

Check warning on line 177 in bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py

View check run for this annotation

Codecov / codecov/patch

bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py#L174-L177

Added lines #L174 - L177 were not covered by tests

print("Duration: ", time[-1])
print("sum tau**2 dt = ", np.nansum(tau**2 * time[1]))
print("min-max tau: ", np.nanmin(tau), np.nanmax(tau))

Check warning on line 181 in bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py

View check run for this annotation

Codecov / codecov/patch

bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py#L179-L181

Added lines #L179 - L181 were not covered by tests

# Plotting q solutions for both DOFs
axs[0].plot(

Check warning on line 184 in bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py

View check run for this annotation

Codecov / codecov/patch

bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py#L184

Added line #L184 was not covered by tests
time,
q * 180 / np.pi,
linestyle=linestyle,
label=[f"q_0 (method {i})", f"q_1 (method {i})"],
)
axs[1].plot(

Check warning on line 190 in bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py

View check run for this annotation

Codecov / codecov/patch

bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py#L190

Added line #L190 was not covered by tests
time,
qdot * 180 / np.pi,
linestyle=linestyle,
label=[f"qdot_0 (method {i})", f"qdot_1 (method {i})"],
)
axs[2].step(time, tau, linestyle=linestyle, label=f"tau (method {i})", where="post")
xlim = np.asarray(axs[2].get_xlim())
xlim = np.hstack([xlim, np.nan, xlim])
min_max = np.hstack(

Check warning on line 199 in bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py

View check run for this annotation

Codecov / codecov/patch

bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py#L196-L199

Added lines #L196 - L199 were not covered by tests
[np.ones([2]) * sol.parameters["min_tau"], np.nan, np.ones([2]) * sol.parameters["max_tau"]]
)
axs[2].plot(xlim, min_max, linestyle=(0, (5, 10)), label=f"params - bounds (method {i})")

Check warning on line 202 in bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py

View check run for this annotation

Codecov / codecov/patch

bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py#L202

Added line #L202 was not covered by tests

plt.tight_layout()
plt.subplots_adjust(left=None, bottom=None, right=None, top=None, wspace=None, hspace=0.3)

Check warning on line 205 in bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py

View check run for this annotation

Codecov / codecov/patch

bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py#L204-L205

Added lines #L204 - L205 were not covered by tests

# Display the plot
axs[0].legend()
axs[1].legend()
axs[2].legend()

Check warning on line 210 in bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py

View check run for this annotation

Codecov / codecov/patch

bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py#L208-L210

Added lines #L208 - L210 were not covered by tests

plt.show()

Check warning on line 212 in bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py

View check run for this annotation

Codecov / codecov/patch

bioptim/examples/torque_driven_ocp/minimize_maximum_torque_by_extra_parameter.py#L212

Added line #L212 was not covered by tests


if __name__ == "__main__":
Expand Down
Loading
Loading