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

same betts 10 1 simulation but new branch #308

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
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
Binary file added examples-gallery/betts_10_1_opty_solution.npy
Binary file not shown.
165 changes: 165 additions & 0 deletions examples-gallery/plot_betts_10_1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
"""
Alp Rider
=========

This is example 10.1 from Betts' book "Practical Methods for Optimal Control
Using NonlinearProgramming", 3rd edition, Chapter 10: Test Problems.
More details are in sectionn 4.11.7 of the book.
Copy link
Member

Choose a reason for hiding this comment

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

Can you cite Bett's book using RST citation notation, i.e. [Betts2010]_.


The main issue here is an inequality constraint for the state variables:
:math:`y_1^2 + y_2^2 + y_3^2 + y_4^2 \\geq function(time, parameters)`

As presently *opty* does not support inequality constraints, I introduced a new
state variable `fact` to take care of the inequality. The inequality is then
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
state variable `fact` to take care of the inequality. The inequality is then
state variable ``fact`` to take care of the inequality. The inequality is then

double back ticks give monspace font

reformulated as
:math:`y_1^2 + y_2^2 + y_3^2 + y_4^2 = fact \\cdot function(time, parameters)`.

and I restrict fact :math:`\\geq 1.0`.

**States**

- :math:`y_1, .., y_4` : state variables as per the book
- :math:`fact`: additional state variable to take care of the inequality

**Specifieds**

- :math:`u_1, u_2` : control variables

Note: I simply copied the equations of motion, the bounds and the constants
from the book. I do not know their meaning.
Copy link
Member

Choose a reason for hiding this comment

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

The why is the name of the problem "Alp Rider"? what does that mean? Please clarify.

Copy link
Contributor Author

@Peter230655 Peter230655 Feb 2, 2025

Choose a reason for hiding this comment

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

I simply took the name give to this problem by Dr. Betts.
Below it says:
Stiff ODE, terrain following [Betts2010] Sect. 4.7.6

But my 3rd edition does not seem to have this section.
I will send an email to Dr. Betts and will ask ihm.
In chapter 4.11.7 are more details about this problem.

I will correct the issues and push.

Copy link
Member

Choose a reason for hiding this comment

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

We should just name it something that has a meaning to the reader of opty documentation, as they may not have the book.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

In chapter 4.11.7 it is described what the system does:
It must follow steep peaks as closely as possible, see fig 4.6 there.
Maybe: System following uneven terrain ?


"""

import numpy as np
import sympy as sm
import sympy.physics.mechanics as me
from opty.direct_collocation import Problem
from opty.utils import create_objective_function

# %%
# Equations of Motion
# -------------------
#
t = me.dynamicsymbols._t
y1, y2, y3, y4 = me.dynamicsymbols('y1 y2 y3 y4')
fact = me.dynamicsymbols('fact')
u1, u2 = me.dynamicsymbols('u1 u2')

# %%
# As the time occurs explicitly in the equations of motion, I use a function
# to represent it symbolically. This will be a known_trajectory_map in the
# optimization problem.

T = sm.symbols('T', cls=sm.Function)

# %%
def p(t, a, b):
return sm.exp(-b*(t - a)**2)

rhs = (3.0*(p(T(t), 3, 12) + p(T(t), 6, 10) + p(T(t), 10, 6)) +
8.0*p(T(t), 15, 4) + 0.01)

eom = sm.Matrix([
-y1.diff(t) - 10*y1 + u1 + u2,
-y2.diff(t) - 2*y2 + u1 - 2*u2,
-y3.diff(t) - 3*y3 + 5*y4 + u1 - u2,
-y4.diff(t) + 5*y3 - 3*y4 + u1 + 3*u2,
y1**2 + y2**2 + y3**2 + y4**2 - fact*rhs,
])
sm.pprint(eom)

# %%
# Set up and Solve the Optimization Problem
# -----------------------------------------

t0, tf = 0.0, 20.0
num_nodes = 601
interval_value = (tf - t0)/(num_nodes - 1)
times = np.linspace(t0, tf, num_nodes)

state_symbols = (y1, y2, y3, y4, fact)
specified_symbols = (u1, u2)
integration_method = 'backward euler'

# Specify the objective function and form the gradient.
obj_func = sm.Integral(100*(y1**2 + y2**2 + y3**2 + y4**2)
+ 0.01*(u1**2 + u2**2), t)

obj, obj_grad = create_objective_function(
obj_func,
state_symbols,
specified_symbols,
tuple(),
num_nodes,
node_time_interval=interval_value,
integration_method=integration_method,
)

# Specify the symbolic instance constraints, as per the example.
instance_constraints = (
y1.func(t0) - 2.0,
y2.func(t0) - 1.0,
y3.func(t0) - 2.0,
y4.func(t0) - 1.0,
y1.func(tf) - 2.0,
y2.func(tf) - 3.0,
y3.func(tf) - 1.0,
y4.func(tf) + 2.0,
)

bounds = {fact: (1.0 , 10000 )}

# %%
# Create the optimization problem and set any options.
prob = Problem(
obj,
obj_grad,
eom,
state_symbols,
num_nodes,
interval_value,
instance_constraints=instance_constraints,
known_trajectory_map={T(t): times},
bounds=bounds,
integration_method=integration_method,
)

#prob.add_option('nlp_scaling_method', 'gradient-based')
Copy link
Member

Choose a reason for hiding this comment

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

remove commented lines

prob.add_option('max_iter', 7000)

# %%
# Give some rough estimates for the trajectories.
# In order to speed up the optimization, I used the solution from a previous
# run as the initial guess.
initial_guess = np.zeros(prob.num_free)
initial_guess = np.load('betts_10_1_opty_solution.npy')

# %%
# Find the optimal solution.
for _ in range(1):
solution, info = prob.solve(initial_guess)
initial_guess = solution
print(info['status_msg'])
print(f'Objective value achieved: {info['obj_val']:.4f}, as per the book ' +
f'it is {2030.85609}, so the difference is: '
f'{(info['obj_val'] - 2030.85609)/2030.85609*100:.3f} % ')

# %%
# Plot the optimal state and input trajectories.
prob.plot_trajectories(solution)

# %%
# Plot the constraint violations.
prob.plot_constraint_violations(solution)

# %%
# Plot the objective function as a function of optimizer iteration.
prob.plot_objective_value()

# %%
# Verify that the inequality is always kept, i.e. that fact >= 1.0 always.
print(f'minimum value of fact = {np.min(solution[4*num_nodes: 5*num_nodes]):.4e}')

# %%
# sphinx_gallery_thumbnail_number = 2
Copy link
Member

Choose a reason for hiding this comment

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

This is displaying in the rendered notebook, so I guess it not correctly specified. It should not show up in the rendered version.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This is displaying in the rendered notebook, so I guess it not correctly specified. It should not show up in the rendered version.

corrected.


Loading