Skip to content

Commit 040e12d

Browse files
authored
Add inverted_pendulum_lqr_control (AtsushiSakai#635)
* Add inverted_pendulum_lqr_control * reorganize document of inverted pendulum control module * Refactor inverted_pendulum_lqr_control.py * Add doccument for inverted pendulum control * Corrected inverted pedulum LQR control doccument * Refactor inverted pendulum control by mpc and lqr * Add unit test for inverted_pendulum_lqr_control.py
1 parent 2c245d9 commit 040e12d

File tree

8 files changed

+328
-23
lines changed

8 files changed

+328
-23
lines changed
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
"""
2+
Inverted Pendulum LQR control
3+
author: Trung Kien - letrungkien.k53.hut@gmail.com
4+
"""
5+
6+
import math
7+
import time
8+
9+
import matplotlib.pyplot as plt
10+
import numpy as np
11+
from numpy.linalg import inv, eig
12+
13+
# Model parameters
14+
15+
l_bar = 2.0 # length of bar
16+
M = 1.0 # [kg]
17+
m = 0.3 # [kg]
18+
g = 9.8 # [m/s^2]
19+
20+
nx = 4 # number of state
21+
nu = 1 # number of input
22+
Q = np.diag([0.0, 1.0, 1.0, 0.0]) # state cost matrix
23+
R = np.diag([0.01]) # input cost matrix
24+
25+
delta_t = 0.1 # time tick [s]
26+
sim_time = 5.0 # simulation time [s]
27+
28+
show_animation = True
29+
30+
31+
def main():
32+
x0 = np.array([
33+
[0.0],
34+
[0.0],
35+
[0.3],
36+
[0.0]
37+
])
38+
39+
x = np.copy(x0)
40+
time = 0.0
41+
42+
while sim_time > time:
43+
time += delta_t
44+
45+
# calc control input
46+
u = lqr_control(x)
47+
48+
# simulate inverted pendulum cart
49+
x = simulation(x, u)
50+
51+
if show_animation:
52+
plt.clf()
53+
px = float(x[0])
54+
theta = float(x[2])
55+
plot_cart(px, theta)
56+
plt.xlim([-5.0, 2.0])
57+
plt.pause(0.001)
58+
59+
print("Finish")
60+
print(f"x={float(x[0]):.2f} [m] , theta={math.degrees(x[2]):.2f} [deg]")
61+
if show_animation:
62+
plt.show()
63+
64+
65+
def simulation(x, u):
66+
A, B = get_model_matrix()
67+
x = A @ x + B @ u
68+
69+
return x
70+
71+
72+
def solve_DARE(A, B, Q, R, maxiter=150, eps=0.01):
73+
"""
74+
Solve a discrete time_Algebraic Riccati equation (DARE)
75+
"""
76+
P = Q
77+
78+
for i in range(maxiter):
79+
Pn = A.T @ P @ A - A.T @ P @ B @ \
80+
inv(R + B.T @ P @ B) @ B.T @ P @ A + Q
81+
if (abs(Pn - P)).max() < eps:
82+
break
83+
P = Pn
84+
85+
return Pn
86+
87+
88+
def dlqr(A, B, Q, R):
89+
"""
90+
Solve the discrete time lqr controller.
91+
x[k+1] = A x[k] + B u[k]
92+
cost = sum x[k].T*Q*x[k] + u[k].T*R*u[k]
93+
# ref Bertsekas, p.151
94+
"""
95+
96+
# first, try to solve the ricatti equation
97+
P = solve_DARE(A, B, Q, R)
98+
99+
# compute the LQR gain
100+
K = inv(B.T @ P @ B + R) @ (B.T @ P @ A)
101+
102+
eigVals, eigVecs = eig(A - B @ K)
103+
return K, P, eigVals
104+
105+
106+
def lqr_control(x):
107+
A, B = get_model_matrix()
108+
start = time.time()
109+
K, _, _ = dlqr(A, B, Q, R)
110+
u = -K @ x
111+
elapsed_time = time.time() - start
112+
print(f"calc time:{elapsed_time:.6f} [sec]")
113+
return u
114+
115+
116+
def get_numpy_array_from_matrix(x):
117+
"""
118+
get build-in list from matrix
119+
"""
120+
return np.array(x).flatten()
121+
122+
123+
def get_model_matrix():
124+
A = np.array([
125+
[0.0, 1.0, 0.0, 0.0],
126+
[0.0, 0.0, m * g / M, 0.0],
127+
[0.0, 0.0, 0.0, 1.0],
128+
[0.0, 0.0, g * (M + m) / (l_bar * M), 0.0]
129+
])
130+
A = np.eye(nx) + delta_t * A
131+
132+
B = np.array([
133+
[0.0],
134+
[1.0 / M],
135+
[0.0],
136+
[1.0 / (l_bar * M)]
137+
])
138+
B = delta_t * B
139+
140+
return A, B
141+
142+
143+
def flatten(a):
144+
return np.array(a).flatten()
145+
146+
147+
def plot_cart(xt, theta):
148+
cart_w = 1.0
149+
cart_h = 0.5
150+
radius = 0.1
151+
152+
cx = np.array([-cart_w / 2.0, cart_w / 2.0, cart_w /
153+
2.0, -cart_w / 2.0, -cart_w / 2.0])
154+
cy = np.array([0.0, 0.0, cart_h, cart_h, 0.0])
155+
cy += radius * 2.0
156+
157+
cx = cx + xt
158+
159+
bx = np.array([0.0, l_bar * math.sin(-theta)])
160+
bx += xt
161+
by = np.array([cart_h, l_bar * math.cos(-theta) + cart_h])
162+
by += radius * 2.0
163+
164+
angles = np.arange(0.0, math.pi * 2.0, math.radians(3.0))
165+
ox = np.array([radius * math.cos(a) for a in angles])
166+
oy = np.array([radius * math.sin(a) for a in angles])
167+
168+
rwx = np.copy(ox) + cart_w / 4.0 + xt
169+
rwy = np.copy(oy) + radius
170+
lwx = np.copy(ox) - cart_w / 4.0 + xt
171+
lwy = np.copy(oy) + radius
172+
173+
wx = np.copy(ox) + bx[-1]
174+
wy = np.copy(oy) + by[-1]
175+
176+
plt.plot(flatten(cx), flatten(cy), "-b")
177+
plt.plot(flatten(bx), flatten(by), "-k")
178+
plt.plot(flatten(rwx), flatten(rwy), "-k")
179+
plt.plot(flatten(lwx), flatten(lwy), "-k")
180+
plt.plot(flatten(wx), flatten(wy), "-k")
181+
plt.title(f"x: {xt:.2f} , theta: {math.degrees(theta):.2f}")
182+
183+
# for stopping simulation with the esc key.
184+
plt.gcf().canvas.mpl_connect(
185+
'key_release_event',
186+
lambda event: [exit(0) if event.key == 'escape' else None])
187+
188+
plt.axis("equal")
189+
190+
191+
if __name__ == '__main__':
192+
main()

Control/InvertedPendulumMPCControl/inverted_pendulum_mpc_control.py renamed to Control/inverted_pendulum/inverted_pendulum_mpc_control.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,16 @@
1717
m = 0.3 # [kg]
1818
g = 9.8 # [m/s^2]
1919

20-
Q = np.diag([0.0, 1.0, 1.0, 0.0])
21-
R = np.diag([0.01])
2220
nx = 4 # number of state
2321
nu = 1 # number of input
22+
Q = np.diag([0.0, 1.0, 1.0, 0.0]) # state cost matrix
23+
R = np.diag([0.01]) # input cost matrix
24+
2425
T = 30 # Horizon length
2526
delta_t = 0.1 # time tick
27+
sim_time = 5.0 # simulation time [s]
2628

27-
animation = True
29+
show_animation = True
2830

2931

3032
def main():
@@ -36,8 +38,10 @@ def main():
3638
])
3739

3840
x = np.copy(x0)
41+
time = 0.0
3942

40-
for i in range(50):
43+
while sim_time > time:
44+
time += delta_t
4145

4246
# calc control input
4347
opt_x, opt_delta_x, opt_theta, opt_delta_theta, opt_input = \
@@ -49,18 +53,22 @@ def main():
4953
# simulate inverted pendulum cart
5054
x = simulation(x, u)
5155

52-
if animation:
56+
if show_animation:
5357
plt.clf()
5458
px = float(x[0])
5559
theta = float(x[2])
5660
plot_cart(px, theta)
5761
plt.xlim([-5.0, 2.0])
5862
plt.pause(0.001)
5963

64+
print("Finish")
65+
print(f"x={float(x[0]):.2f} [m] , theta={math.degrees(x[2]):.2f} [deg]")
66+
if show_animation:
67+
plt.show()
68+
6069

6170
def simulation(x, u):
6271
A, B = get_model_matrix()
63-
6472
x = np.dot(A, x) + np.dot(B, u)
6573

6674
return x
@@ -85,7 +93,7 @@ def mpc_control(x0):
8593
start = time.time()
8694
prob.solve(verbose=False)
8795
elapsed_time = time.time() - start
88-
print("calc time:{0} [sec]".format(elapsed_time))
96+
print(f"calc time:{elapsed_time:.6f} [sec]")
8997

9098
if prob.status == cvxpy.OPTIMAL:
9199
ox = get_numpy_array_from_matrix(x.value[0, :])
@@ -165,8 +173,12 @@ def plot_cart(xt, theta):
165173
plt.plot(flatten(rwx), flatten(rwy), "-k")
166174
plt.plot(flatten(lwx), flatten(lwy), "-k")
167175
plt.plot(flatten(wx), flatten(wy), "-k")
168-
plt.title("x:" + str(round(xt, 2)) + ",theta:" +
169-
str(round(math.degrees(theta), 2)))
176+
plt.title(f"x: {xt:.2f} , theta: {math.degrees(theta):.2f}")
177+
178+
# for stopping simulation with the esc key.
179+
plt.gcf().canvas.mpl_connect(
180+
'key_release_event',
181+
lambda event: [exit(0) if event.key == 'escape' else None])
170182

171183
plt.axis("equal")
172184

docs/modules/control/control_main.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
Control
44
=================
55

6-
.. include:: inverted_pendulum_mpc_control/inverted_pendulum_mpc_control.rst
6+
.. include:: inverted_pendulum_control/inverted_pendulum_control.rst
7+
78
.. include:: move_to_a_pose_control/move_to_a_pose_control.rst
89

Loading
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
Inverted Pendulum Control
2+
-----------------------------
3+
4+
An inverted pendulum on a cart consists of a mass :math:`m` at the top of a pole of length :math:`l` pivoted on a
5+
horizontally moving base as shown in the adjacent.
6+
7+
The objective of the control system is to balance the inverted pendulum by applying a force to the cart that the pendulum is attached to.
8+
9+
Modeling
10+
~~~~~~~~~~~~
11+
12+
.. image:: inverted_pendulum_control/inverted-pendulum.png
13+
:align: center
14+
15+
- :math:`M`: mass of the cart
16+
- :math:`m`: mass of the load on the top of the rod
17+
- :math:`l`: length of the rod
18+
- :math:`u`: force applied to the cart
19+
- :math:`x`: cart position coordinate
20+
- :math:`\theta`: pendulum angle from vertical
21+
22+
Using Lagrange's equations:
23+
24+
.. math::
25+
& (M + m)\ddot{x} - ml\ddot{\theta}cos{\theta} + ml\dot{\theta^2}\sin{\theta} = u \\
26+
& l\ddot{\theta} - g\sin{\theta} = \ddot{x}\cos{\theta}
27+
28+
See this `link <https://en.wikipedia.org/wiki/Inverted_pendulum#From_Lagrange's_equations>`__ for more details.
29+
30+
So
31+
32+
.. math::
33+
& \ddot{x} = \frac{m(gcos{\theta} - \dot{\theta}^2l)sin{\theta} + u}{M + m - mcos^2{\theta}} \\
34+
& \ddot{\theta} = \frac{g(M + m)sin{\theta} - \dot{\theta}^2lmsin{\theta}cos{\theta} + ucos{\theta}}{l(M + m - mcos^2{\theta})}
35+
36+
37+
Linearlied model when :math:`\theta` small, :math:`cos{\theta} \approx 1`, :math:`sin{\theta} \approx \theta`, :math:`\dot{\theta}^2 \approx 0`.
38+
39+
.. math::
40+
& \ddot{x} = \frac{gm}{M}\theta + \frac{1}{M}u\\
41+
& \ddot{\theta} = \frac{g(M + m)}{Ml}\theta + \frac{1}{Ml}u
42+
43+
State space:
44+
45+
.. math::
46+
& \dot{x} = Ax + Bu \\
47+
& y = Cx + Du
48+
49+
where
50+
51+
.. math::
52+
& x = [x, \dot{x}, \theta,\dot{\theta}]\\
53+
& A = \begin{bmatrix} 0 & 1 & 0 & 0\\0 & 0 & \frac{gm}{M} & 0\\0 & 0 & 0 & 1\\0 & 0 & \frac{g(M + m)}{Ml} & 0 \end{bmatrix}\\
54+
& B = \begin{bmatrix} 0 \\ \frac{1}{M} \\ 0 \\ \frac{1}{Ml} \end{bmatrix}
55+
56+
If control only \theta
57+
58+
.. math::
59+
& C = \begin{bmatrix} 0 & 0 & 1 & 0 \end{bmatrix}\\
60+
& D = [0]
61+
62+
If control x and \theta
63+
64+
.. math::
65+
& C = \begin{bmatrix} 1 & 0 & 0 & 0\\0 & 0 & 1 & 0 \end{bmatrix}\\
66+
& D = \begin{bmatrix} 0 \\ 0 \end{bmatrix}
67+
68+
LQR control
69+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
70+
71+
The LQR cotroller minimize this cost function defined as:
72+
73+
.. math:: J = x^T Q x + u^T R u
74+
the feedback control law that minimizes the value of the cost is:
75+
76+
.. math:: u = -K x
77+
where:
78+
79+
.. math:: K = (B^T P B + R)^{-1} B^T P A
80+
and :math:`P` is the unique positive definite solution to the discrete time `algebraic Riccati equation <https://en.wikipedia.org/wiki/Inverted_pendulum#From_Lagrange's_equations>`__ (DARE):
81+
82+
.. math:: P = A^T P A - A^T P B ( R + B^T P B )^{-1} B^T P A + Q
83+
84+
.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Control/InvertedPendulumCart/animation_lqr.gif
85+
86+
MPC control
87+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
88+
The MPC cotroller minimize this cost function defined as:
89+
90+
.. math:: J = x^T Q x + u^T R u
91+
92+
subject to:
93+
94+
- Linearlied Inverted Pendulum model
95+
- Initial state
96+
97+
.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Control/InvertedPendulumCart/animation.gif

docs/modules/control/inverted_pendulum_mpc_control/inverted_pendulum_mpc_control.rst

Lines changed: 0 additions & 6 deletions
This file was deleted.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import conftest
2+
from Control.inverted_pendulum import inverted_pendulum_lqr_control as m
3+
4+
5+
def test_1():
6+
m.show_animation = False
7+
m.main()
8+
9+
10+
if __name__ == '__main__':
11+
conftest.run_this_test(__file__)

0 commit comments

Comments
 (0)