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

[DO NOT MERGE] Stochastic plots, noisy_integrate, and other plot shits #853

Closed
wants to merge 80 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
90f7415
calcule numerical cov
mickaelbegon Jan 28, 2024
bd5e6a0
numerical cov is still to small
mickaelbegon Jan 28, 2024
89b02a3
start working on rockit example
mickaelbegon Jan 28, 2024
56b3919
Fix: constaints wrt time in rockit example
mickaelbegon Jan 28, 2024
c2c0e70
continue fixing rockit example
mickaelbegon Jan 28, 2024
6ffc4ed
VALID: sOCP pour mass_point_model - rockit still not working
mickaelbegon Jan 29, 2024
9fdd043
tried to understand
EveCharbie Jan 29, 2024
101488b
back to original code from MICK
EveCharbie Jan 29, 2024
b6c8546
Merge remote-tracking branch 'pyomeca/master' into gillis_noise_eve
EveCharbie Feb 23, 2024
da37821
naninf_option
EveCharbie Feb 23, 2024
5fbb947
added stochastic plots
EveCharbie Feb 24, 2024
7210c25
first draft of noisy_integrate
EveCharbie Feb 26, 2024
81a3301
removed double noise on controls
EveCharbie Feb 26, 2024
6bae186
Think noisy_integrate is ok
EveCharbie Feb 26, 2024
6250b4e
readded an example of how to save bioptim version
EveCharbie Feb 26, 2024
c36f3ed
Merge remote-tracking branch 'origin/gillis_noise_eve' into gillis_no…
EveCharbie Feb 26, 2024
fb403b3
noisy integrate plot bad looking
EveCharbie Feb 26, 2024
48833ef
penalty shape throwing error
EveCharbie Feb 26, 2024
6d34b65
save bioptim plots automatically
EveCharbie Feb 26, 2024
8094c62
cannot get inf_du and inf_pr from nlpsol_out :(
EveCharbie Feb 27, 2024
ade1afe
blacked
EveCharbie Feb 27, 2024
9beae22
woupsi checked at the wrong place
EveCharbie Feb 27, 2024
4a8741b
fixed the motor noise to be gaussian
EveCharbie Feb 27, 2024
9d1380f
blacked
EveCharbie Feb 27, 2024
ae7be94
I think this solution is ugly, but would work
EveCharbie Feb 28, 2024
c2d4094
Think this solution could work, but gets blocked by matplotlib
EveCharbie Feb 28, 2024
5f93f22
blacked before re diving into it
EveCharbie Mar 1, 2024
7e023ff
Merge remote-tracking branch 'pyomeca/master' into gillis_noise_eve
EveCharbie Mar 1, 2024
311d6f9
made the requested changes to the examples
EveCharbie Mar 1, 2024
9cafb7a
made small changes requested
EveCharbie Mar 4, 2024
a5fdae5
This bug was fixed weeks ago!!!!! will change test values :@
EveCharbie Mar 4, 2024
1baba96
extra_dynamics_func + nan_inf_test
EveCharbie Mar 4, 2024
58ca129
refactored noisy integrate as requested
EveCharbie Mar 4, 2024
dc3f1c4
test noisy_integrate + removed confusion dynamics_func list
EveCharbie Mar 4, 2024
1346eda
fixed the output graphs, but bug in casadi/ipopt?
EveCharbie Mar 5, 2024
1113fae
blacked
EveCharbie Mar 5, 2024
d4690f0
aesthetics on check_conditionning
EveCharbie Mar 5, 2024
170a641
refactored check_conditioning
EveCharbie Mar 5, 2024
9c9f4e8
refactored to remove code duplicates (no more phases though)
EveCharbie Mar 6, 2024
5646eb5
starts to be clean
EveCharbie Mar 7, 2024
963807a
stochastic min efforts quadratic
EveCharbie Mar 7, 2024
71aed66
moved ipopt plots to its own file
EveCharbie Mar 7, 2024
ef00670
live check conditionning seems to work (without constraints)
EveCharbie Mar 7, 2024
21277f4
socp constraints in g_internal
EveCharbie Mar 8, 2024
fe0c133
fixed some tests
EveCharbie Mar 11, 2024
15772ac
blacked
EveCharbie Mar 11, 2024
f193328
internal constraints multi-node mess up?
EveCharbie Mar 11, 2024
68e6a0e
fixed tests variationnal ocp
EveCharbie Mar 11, 2024
898dfae
blacked
EveCharbie Mar 11, 2024
d1cb5fa
Merge remote-tracking branch 'pyomeca/master' into gillis_noise_eve
EveCharbie Mar 11, 2024
afb127d
fixed some tests
EveCharbie Mar 12, 2024
82ef208
fixed some tests
EveCharbie Mar 12, 2024
c228acc
backed
EveCharbie Mar 12, 2024
c4fa5ab
woupsi
EveCharbie Mar 12, 2024
46ad363
temporary, will be reverted
EveCharbie Mar 12, 2024
02175b1
Temporarily restored the constraint pool
EveCharbie Mar 12, 2024
e5ffcec
blacked
EveCharbie Mar 12, 2024
73042bb
Merge remote-tracking branch 'pyomeca/master' into gillis_noise_eve
EveCharbie Mar 12, 2024
8b44d7f
fixed tests ?
EveCharbie Mar 13, 2024
f761aca
casadi update
EveCharbie Mar 13, 2024
84d753f
woups
EveCharbie Mar 13, 2024
ef1ec15
ipopt output plots work in real time, but are really slow!
EveCharbie Mar 15, 2024
470e76e
blacked
EveCharbie Mar 15, 2024
9622fce
more details to ipopt plots
EveCharbie Mar 15, 2024
aa1a299
IPOPT output works live on SOCP
EveCharbie Mar 15, 2024
a047e9d
removed max_g
EveCharbie Mar 15, 2024
ce3369a
Merge remote-tracking branch 'pyomeca/master' into gillis_noise_eve
EveCharbie Mar 16, 2024
18dacc1
ipopt out put small fix
EveCharbie Mar 17, 2024
3d4acbf
Merge remote-tracking branch 'origin/gillis_noise_eve' into gillis_no…
EveCharbie Mar 17, 2024
d9df0f5
other small fix ipoot_output_plot
EveCharbie Mar 18, 2024
57a7d13
blacked
EveCharbie Mar 19, 2024
11f669d
fix Kevin's merge
EveCharbie Mar 19, 2024
cb91543
made requested changes
EveCharbie Mar 20, 2024
ea89ef3
added minimal testing of the live plots
EveCharbie Mar 20, 2024
0c28767
blacked
EveCharbie Mar 20, 2024
9cbe782
just to relaunch tests
EveCharbie Mar 21, 2024
010bcd0
just to relaunch tests
EveCharbie Mar 21, 2024
b19a907
trying to fix test on Ubuntu
EveCharbie Mar 24, 2024
5ed4f6b
to restart tests
EveCharbie Mar 26, 2024
ff8c300
Merge remote-tracking branch 'pyomeca/master' into gillis_noise_eve
EveCharbie Mar 26, 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
14 changes: 14 additions & 0 deletions bioptim/dynamics/configure_new_variable.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,20 @@
self.nlp.variable_mappings[self.name],
node_index,
)
if not self.skip_plot:
all_variables_in_one_subplot = True if self.name in ["m", "cov", "k"] else False
self.nlp.plot[f"{self.name}_algebraic"] = CustomPlot(

Check warning on line 523 in bioptim/dynamics/configure_new_variable.py

View check run for this annotation

Codecov / codecov/patch

bioptim/dynamics/configure_new_variable.py#L521-L523

Added lines #L521 - L523 were not covered by tests
lambda t0, phases_dt, node_idx, x, u, p, a: (
a[self.nlp.algebraic_states.key_index(self.name), :]
if a.any()
else np.ndarray((cx[0][0].shape[0], 1)) * np.nan
),
plot_type=PlotType.STEP,
axes_idx=self.axes_idx,
legend=self.legend,
combine_to=self.combine_name,
all_variables_in_one_subplot=all_variables_in_one_subplot,
)


def _manage_fatigue_to_new_variable(
Expand Down
125 changes: 72 additions & 53 deletions bioptim/dynamics/configure_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -908,31 +908,85 @@
A reference to the ocp
nlp: NonLinearProgram
A reference to the phase
dyn_func: Callable[time, states, controls, param, algebraic_states] | tuple[Callable[time, states, controls, param, algebraic_states], ...]
dyn_func: Callable[time, states, controls, param, algebraic_states]
The function to get the derivative of the states
"""

DynamicsFunctions.apply_parameters(nlp)

if not isinstance(dyn_func, (tuple, list)):
dyn_func = (dyn_func,)
dynamics_eval = dyn_func(
nlp.time_mx,
nlp.states.scaled.mx_reduced,
nlp.controls.scaled.mx_reduced,
nlp.parameters.scaled.mx_reduced,
nlp.algebraic_states.scaled.mx_reduced,
nlp,
**extra_params,
)
dynamics_dxdt = dynamics_eval.dxdt
if isinstance(dynamics_dxdt, (list, tuple)):
dynamics_dxdt = vertcat(*dynamics_dxdt)

Check warning on line 928 in bioptim/dynamics/configure_problem.py

View check run for this annotation

Codecov / codecov/patch

bioptim/dynamics/configure_problem.py#L928

Added line #L928 was not covered by tests

for func in dyn_func:
dynamics_eval = func(
nlp.time_mx,
nlp.states.scaled.mx_reduced,
nlp.controls.scaled.mx_reduced,
nlp.parameters.scaled.mx_reduced,
nlp.algebraic_states.scaled.mx_reduced,
nlp,
**extra_params,
time_span_sym = vertcat(nlp.time_mx, nlp.dt_mx)
if nlp.dynamics_func is None:
nlp.dynamics_func = Function(
"ForwardDyn",
[
time_span_sym,
nlp.states.scaled.mx_reduced,
nlp.controls.scaled.mx_reduced,
nlp.parameters.scaled.mx_reduced,
nlp.algebraic_states.scaled.mx_reduced,
],
[dynamics_dxdt],
["t_span", "x", "u", "p", "a"],
["xdot"],
)
dynamics_dxdt = dynamics_eval.dxdt
if isinstance(dynamics_dxdt, (list, tuple)):
dynamics_dxdt = vertcat(*dynamics_dxdt)

time_span_sym = vertcat(nlp.time_mx, nlp.dt_mx)
nlp.dynamics_func.append(
# TODO: allow expand for each dynamics independently
if nlp.dynamics_type.expand_dynamics:
try:
nlp.dynamics_func = nlp.dynamics_func.expand()
except Exception as me:
RuntimeError(

Check warning on line 951 in bioptim/dynamics/configure_problem.py

View check run for this annotation

Codecov / codecov/patch

bioptim/dynamics/configure_problem.py#L950-L951

Added lines #L950 - L951 were not covered by tests
f"An error occurred while executing the 'expand()' function for the dynamic function. "
f"Please review the following casadi error message for more details.\n"
"Several factors could be causing this issue. One of the most likely is the inability to "
"use expand=True at all. In that case, try adding expand=False to the dynamics.\n"
"Original casadi error message:\n"
f"{me}"
)

# Only possible for regular dynamics, not for extra_dynamics
if dynamics_eval.defects is not None:
nlp.implicit_dynamics_func = Function(
"DynamicsDefects",
[
time_span_sym,
nlp.states.scaled.mx_reduced,
nlp.controls.scaled.mx_reduced,
nlp.parameters.scaled.mx_reduced,
nlp.algebraic_states.scaled.mx_reduced,
nlp.states_dot.scaled.mx_reduced,
],
[dynamics_eval.defects],
["t_span", "x", "u", "p", "a", "xdot"],
["defects"],
)
if nlp.dynamics_type.expand_dynamics:
try:
nlp.implicit_dynamics_func = nlp.implicit_dynamics_func.expand()
except Exception as me:
RuntimeError(

Check warning on line 980 in bioptim/dynamics/configure_problem.py

View check run for this annotation

Codecov / codecov/patch

bioptim/dynamics/configure_problem.py#L979-L980

Added lines #L979 - L980 were not covered by tests
f"An error occurred while executing the 'expand()' function for the dynamic function. "
f"Please review the following casadi error message for more details.\n"
"Several factors could be causing this issue. One of the most likely is the inability to "
"use expand=True at all. In that case, try adding expand=False to the dynamics.\n"
"Original casadi error message:\n"
f"{me}"
)
else:
nlp.extra_dynamics_func.append(

Check warning on line 989 in bioptim/dynamics/configure_problem.py

View check run for this annotation

Codecov / codecov/patch

bioptim/dynamics/configure_problem.py#L989

Added line #L989 was not covered by tests
Function(
"ForwardDyn",
[
Expand All @@ -951,7 +1005,7 @@
# TODO: allow expand for each dynamics independently
if nlp.dynamics_type.expand_dynamics:
try:
nlp.dynamics_func[-1] = nlp.dynamics_func[-1].expand()
nlp.extra_dynamics_func[-1] = nlp.dynamics_funcextra_dynamics_func[-1].expand()

Check warning on line 1008 in bioptim/dynamics/configure_problem.py

View check run for this annotation

Codecov / codecov/patch

bioptim/dynamics/configure_problem.py#L1008

Added line #L1008 was not covered by tests
except Exception as me:
RuntimeError(
f"An error occurred while executing the 'expand()' function for the dynamic function. "
Expand All @@ -962,36 +1016,6 @@
f"{me}"
)

if dynamics_eval.defects is not None:
nlp.implicit_dynamics_func.append(
Function(
"DynamicsDefects",
[
time_span_sym,
nlp.states.scaled.mx_reduced,
nlp.controls.scaled.mx_reduced,
nlp.parameters.scaled.mx_reduced,
nlp.algebraic_states.scaled.mx_reduced,
nlp.states_dot.scaled.mx_reduced,
],
[dynamics_eval.defects],
["t_span", "x", "u", "p", "a", "xdot"],
["defects"],
)
)
if nlp.dynamics_type.expand_dynamics:
try:
nlp.implicit_dynamics_func[-1] = nlp.implicit_dynamics_func[-1].expand()
except Exception as me:
RuntimeError(
f"An error occurred while executing the 'expand()' function for the dynamic function. "
f"Please review the following casadi error message for more details.\n"
"Several factors could be causing this issue. One of the most likely is the inability to "
"use expand=True at all. In that case, try adding expand=False to the dynamics.\n"
"Original casadi error message:\n"
f"{me}"
)

@staticmethod
def configure_contact_function(ocp, nlp, dyn_func: Callable, **extra_params):
"""
Expand Down Expand Up @@ -1369,7 +1393,6 @@
as_controls=False,
as_states_dot=False,
as_algebraic_states=True,
skip_plot=True,
)

@staticmethod
Expand Down Expand Up @@ -1493,7 +1516,6 @@
as_controls=False,
as_states_dot=False,
as_algebraic_states=True,
skip_plot=True,
)

@staticmethod
Expand Down Expand Up @@ -1525,7 +1547,6 @@
as_controls=False,
as_states_dot=False,
as_algebraic_states=True,
skip_plot=True,
)

@staticmethod
Expand Down Expand Up @@ -1554,7 +1575,6 @@
as_controls=False,
as_states_dot=False,
as_algebraic_states=True,
skip_plot=True,
)

@staticmethod
Expand Down Expand Up @@ -1589,7 +1609,6 @@
as_controls=False,
as_states_dot=False,
as_algebraic_states=True,
skip_plot=True,
)

@staticmethod
Expand Down
45 changes: 28 additions & 17 deletions bioptim/dynamics/ode_solver.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,9 @@
"""
return nlp.parameters.scaled.cx_start

def initialize_integrator(self, ocp, nlp, dynamics_index: int, node_index: int, **extra_opt) -> Callable:
def initialize_integrator(
self, ocp, nlp, node_index: int, dynamics_index: int = 0, is_extra_dynamics: bool = False, **extra_opt
) -> Callable:
"""
Initialize the integrator

Expand All @@ -169,10 +171,12 @@
The Optimal control program handler
nlp
The NonLinearProgram handler
dynamics_index
The current dynamics to resolve (that can be referred to nlp.dynamics_func[index])
node_index
The index of the node currently initialized
dynamics_index
The current extra dynamics to resolve (that can be referred to nlp.extra_dynamics_func[index])
is_extra_dynamics
If the dynamics is an extra dynamics
extra_opt
Any extra options to pass to the integrator

Expand All @@ -181,16 +185,20 @@
The initialized integrator function
"""

if dynamics_index > 0 and not is_extra_dynamics:
raise RuntimeError("dynamics_index should be 0 if is_extra_dynamics is False")

Check warning on line 189 in bioptim/dynamics/ode_solver.py

View check run for this annotation

Codecov / codecov/patch

bioptim/dynamics/ode_solver.py#L189

Added line #L189 was not covered by tests

nlp.states.node_index = node_index
nlp.states_dot.node_index = node_index
nlp.controls.node_index = node_index
nlp.algebraic_states.node_index = node_index
dynamics_func = nlp.dynamics_func if not is_extra_dynamics else nlp.extra_dynamics_func[dynamics_index]
ode_opt = {
"model": nlp.model,
"cx": nlp.cx,
"control_type": nlp.control_type,
"defects_type": self.defects_type,
"ode_index": node_index if nlp.dynamics_func[dynamics_index].size2_out("xdot") > 1 else 0,
"ode_index": node_index if dynamics_func.size2_out("xdot") > 1 else 0,
"duplicate_starting_point": self.duplicate_starting_point,
**extra_opt,
}
Expand All @@ -201,13 +209,8 @@
"u": self.p_ode(nlp),
"a": self.a_ode(nlp),
"param": self.param_ode(nlp),
"ode": nlp.dynamics_func[dynamics_index],
# TODO this actually checks "not nlp.implicit_dynamics_func" (or that nlp.implicit_dynamics_func == [])
"implicit_ode": (
nlp.implicit_dynamics_func[dynamics_index]
if len(nlp.implicit_dynamics_func) > 0
else nlp.implicit_dynamics_func
),
"ode": dynamics_func,
"implicit_ode": nlp.implicit_dynamics_func,
}

return nlp.ode_solver.integrator(ode, ode_opt)
Expand Down Expand Up @@ -235,14 +238,19 @@

# Extra dynamics
extra_dynamics = []
for i in range(1, len(nlp.dynamics_func)):
extra_dynamics += [nlp.ode_solver.initialize_integrator(ocp, nlp, dynamics_index=i, node_index=0)]
for i in range(len(nlp.extra_dynamics_func)):
extra_dynamics += [

Check warning on line 242 in bioptim/dynamics/ode_solver.py

View check run for this annotation

Codecov / codecov/patch

bioptim/dynamics/ode_solver.py#L242

Added line #L242 was not covered by tests
nlp.ode_solver.initialize_integrator(ocp, nlp, dynamics_index=i, node_index=0, is_extra_dynamics=True)
]
if nlp.phase_dynamics == PhaseDynamics.SHARED_DURING_THE_PHASE:
extra_dynamics = extra_dynamics * nlp.ns
else:
for node_index in range(1, nlp.ns):
extra_dynamics += [nlp.ode_solver.initialize_integrator(ocp, nlp, dynamics_index=i, node_index=0)]
# TODO include this in nlp.dynamics so the index of nlp.dynamics_func and nlp.dynamics match
extra_dynamics += [

Check warning on line 249 in bioptim/dynamics/ode_solver.py

View check run for this annotation

Codecov / codecov/patch

bioptim/dynamics/ode_solver.py#L249

Added line #L249 was not covered by tests
nlp.ode_solver.initialize_integrator(
ocp, nlp, dynamics_index=i, node_index=node_index, is_extra_dynamics=True
)
]
nlp.extra_dynamics.append(extra_dynamics)


Expand Down Expand Up @@ -538,7 +546,9 @@
def a_ode(self, nlp):
return nlp.algebraic_states.scaled.cx

def initialize_integrator(self, ocp, nlp, dynamics_index: int, node_index: int, **extra_opt):
def initialize_integrator(
self, ocp, nlp, node_index: int, dynamics_index: int = 0, is_extra_dynamics: bool = False, **extra_opt
):
raise NotImplementedError("CVODES is not yet implemented")

if extra_opt:
Expand All @@ -560,10 +570,11 @@
raise RuntimeError("CVODES cannot be used with algebraic_states variables")

t = [self.t_ode(nlp)[0], self.t_ode(nlp)[1] - self.t_ode(nlp)[0]]
dynamics_func = nlp.dynamics_func if not is_extra_dynamics else nlp.extra_dynamics_func[dynamics_index]
ode = {
"x": nlp.states.scaled.cx_start,
"u": nlp.controls.scaled.cx_start, # todo: add p=parameters
"ode": nlp.dynamics_func[dynamics_index](
"ode": dynamics_func(
vertcat(*t), self.x_ode(nlp), self.p_ode(nlp), self.param_ode(nlp), self.a_ode(nlp)
),
}
Expand Down
54 changes: 52 additions & 2 deletions bioptim/examples/getting_started/pendulum.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,9 @@
ocp = prepare_ocp(biorbd_model_path="models/pendulum.bioMod", final_time=1, n_shooting=400, n_threads=2)

# Custom plots
ocp.add_plot_penalty(CostType.ALL)
ocp.add_plot_penalty(CostType.ALL) # This will display the objectives and constraints at the current iteration

Check warning on line 139 in bioptim/examples/getting_started/pendulum.py

View check run for this annotation

Codecov / codecov/patch

bioptim/examples/getting_started/pendulum.py#L139

Added line #L139 was not covered by tests
# ocp.add_plot_ipopt_outputs() # This will display the solver's output at the current iteration
# ocp.add_plot_check_conditioning() # This will display the conditioning of the problem at the current iteration

# --- If one is interested in checking the conditioning of the problem, they can uncomment the following line --- #
# ocp.check_conditioning()
Expand All @@ -146,12 +148,60 @@

# --- Solve the ocp. Please note that online graphics only works with the Linux operating system --- #
sol = ocp.solve(Solver.IPOPT(show_online_optim=platform.system() == "Linux"))

sol.print_cost()

# --- Show the results (graph or animation) --- #
# sol.graphs(show_bounds=True)
# sol.graphs(show_bounds=True, save_name="results.png")
sol.animate(n_frames=100)

# # --- Save the solution --- #
# Here is an example of how we recommend to save the solution. Please note that sol.ocp is not picklable and that sol will be loaded using the current bioptim version, not the version at the time of the generation of the results.
# import pickle
# import git
# from datetime import date
#
# # Save the version of bioptim and the date of the optimization for future reference
# repo = git.Repo(search_parent_directories=True)
# commit_id = str(repo.commit())
# branch = str(repo.active_branch)
# tag = repo.git.describe("--tags")
# bioptim_version = repo.git.version_info
# git_date = repo.git.log("-1", "--format=%cd")
# version_dic = {
# "commit_id": commit_id,
# "git_date": git_date,
# "branch": branch,
# "tag": tag,
# "bioptim_version": bioptim_version,
# "date_of_the_optimization": date.today().strftime("%b-%d-%Y-%H-%M-%S"),
# }
#
# q = sol.decision_states(to_merge=SolutionMerge.NODES)["q"]
# qdot = sol.decision_states(to_merge=SolutionMerge.NODES)["qdot"]
# tau = sol.decision_controls(to_merge=SolutionMerge.NODES)["tau"]
#
# # Do everything you need with the solution here before we delete ocp
# integrated_sol = sol.integrate(to_merge=SolutionMerge.NODES)
# q_integrated = integrated_sol["q"]
# qdot_integrated = integrated_sol["qdot"]
#
# # Save the output of the optimization
# with open("pendulum_data.pkl", "wb") as file:
# data = {"q": q,
# "qdot": qdot,
# "tau": tau,
# "real_time_to_optimize": sol.real_time_to_optimize,
# "version": version_dic,
# "q_integrated": q_integrated,
# "qdot_integrated": qdot_integrated}
# pickle.dump(data, file)
#
# # Save the solution for future use, you will only need to do sol.ocp = prepare_ocp() to get the same solution object as above.
# with open("pendulum_sol.pkl", "wb") as file:
# del sol.ocp
# pickle.dump(sol, file)


if __name__ == "__main__":
main()
Loading
Loading