Skip to content

Commit 38261ec

Browse files
authored
clean up clothoidal_paths.py (AtsushiSakai#638)
* clean up clothoidal_paths.py * add unit tests for clothoid_paths * add unit tests for clothoid_paths * add unit tests for clothoid_paths * code clean up * code clean up * code clean up * code clean up * code clean up * code clean up * code clean up * code clean up
1 parent 7ac2a17 commit 38261ec

File tree

6 files changed

+293
-149
lines changed

6 files changed

+293
-149
lines changed
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
"""
2+
Clothoid Path Planner
3+
Author: Daniel Ingram (daniel-s-ingram)
4+
Atsushi Sakai (AtsushiSakai)
5+
Reference paper: Fast and accurate G1 fitting of clothoid curves
6+
https://www.researchgate.net/publication/237062806
7+
"""
8+
9+
from collections import namedtuple
10+
import matplotlib.pyplot as plt
11+
import numpy as np
12+
import scipy.integrate as integrate
13+
from scipy.optimize import fsolve
14+
from math import atan2, cos, hypot, pi, sin
15+
from matplotlib import animation
16+
17+
Point = namedtuple("Point", ["x", "y"])
18+
19+
show_animation = True
20+
21+
22+
def generate_clothoid_paths(start_point, start_yaw_list,
23+
goal_point, goal_yaw_list,
24+
n_path_points):
25+
"""
26+
Generate clothoid path list. This function generate multiple clothoid paths
27+
from multiple orientations(yaw) at start points to multiple orientations
28+
(yaw) at goal point.
29+
30+
:param start_point: Start point of the path
31+
:param start_yaw_list: Orientation list at start point in radian
32+
:param goal_point: Goal point of the path
33+
:param goal_yaw_list: Orientation list at goal point in radian
34+
:param n_path_points: number of path points
35+
:return: clothoid path list
36+
"""
37+
clothoids = []
38+
for start_yaw in start_yaw_list:
39+
for goal_yaw in goal_yaw_list:
40+
clothoid = generate_clothoid_path(start_point, start_yaw,
41+
goal_point, goal_yaw,
42+
n_path_points)
43+
clothoids.append(clothoid)
44+
return clothoids
45+
46+
47+
def generate_clothoid_path(start_point, start_yaw,
48+
goal_point, goal_yaw, n_path_points):
49+
"""
50+
Generate a clothoid path list.
51+
52+
:param start_point: Start point of the path
53+
:param start_yaw: Orientation at start point in radian
54+
:param goal_point: Goal point of the path
55+
:param goal_yaw: Orientation at goal point in radian
56+
:param n_path_points: number of path points
57+
:return: a clothoid path
58+
"""
59+
dx = goal_point.x - start_point.x
60+
dy = goal_point.y - start_point.y
61+
r = hypot(dx, dy)
62+
63+
phi = atan2(dy, dx)
64+
phi1 = normalize_angle(start_yaw - phi)
65+
phi2 = normalize_angle(goal_yaw - phi)
66+
delta = phi2 - phi1
67+
68+
try:
69+
# Step1: Solve g function
70+
A = solve_g_for_root(phi1, phi2, delta)
71+
72+
# Step2: Calculate path parameters
73+
L = compute_path_length(r, phi1, delta, A)
74+
curvature = compute_curvature(delta, A, L)
75+
curvature_rate = compute_curvature_rate(A, L)
76+
except Exception as e:
77+
print(f"Failed to generate clothoid points: {e}")
78+
return None
79+
80+
# Step3: Construct a path with Fresnel integral
81+
points = []
82+
for s in np.linspace(0, L, n_path_points):
83+
try:
84+
x = start_point.x + s * X(curvature_rate * s ** 2, curvature * s,
85+
start_yaw)
86+
y = start_point.y + s * Y(curvature_rate * s ** 2, curvature * s,
87+
start_yaw)
88+
points.append(Point(x, y))
89+
except Exception as e:
90+
print(f"Skipping failed clothoid point: {e}")
91+
92+
return points
93+
94+
95+
def X(a, b, c):
96+
return integrate.quad(lambda t: cos((a/2)*t**2 + b*t + c), 0, 1)[0]
97+
98+
99+
def Y(a, b, c):
100+
return integrate.quad(lambda t: sin((a/2)*t**2 + b*t + c), 0, 1)[0]
101+
102+
103+
def solve_g_for_root(theta1, theta2, delta):
104+
initial_guess = 3*(theta1 + theta2)
105+
return fsolve(lambda A: Y(2*A, delta - A, theta1), [initial_guess])
106+
107+
108+
def compute_path_length(r, theta1, delta, A):
109+
return r / X(2*A, delta - A, theta1)
110+
111+
112+
def compute_curvature(delta, A, L):
113+
return (delta - A) / L
114+
115+
116+
def compute_curvature_rate(A, L):
117+
return 2 * A / (L**2)
118+
119+
120+
def normalize_angle(angle_rad):
121+
return (angle_rad + pi) % (2 * pi) - pi
122+
123+
124+
def get_axes_limits(clothoids):
125+
x_vals = [p.x for clothoid in clothoids for p in clothoid]
126+
y_vals = [p.y for clothoid in clothoids for p in clothoid]
127+
128+
x_min = min(x_vals)
129+
x_max = max(x_vals)
130+
y_min = min(y_vals)
131+
y_max = max(y_vals)
132+
133+
x_offset = 0.1*(x_max - x_min)
134+
y_offset = 0.1*(y_max - y_min)
135+
136+
x_min = x_min - x_offset
137+
x_max = x_max + x_offset
138+
y_min = y_min - y_offset
139+
y_max = y_max + y_offset
140+
141+
return x_min, x_max, y_min, y_max
142+
143+
144+
def draw_clothoids(start, goal, num_steps, clothoidal_paths,
145+
save_animation=False):
146+
147+
fig = plt.figure(figsize=(10, 10))
148+
x_min, x_max, y_min, y_max = get_axes_limits(clothoidal_paths)
149+
axes = plt.axes(xlim=(x_min, x_max), ylim=(y_min, y_max))
150+
151+
axes.plot(start.x, start.y, 'ro')
152+
axes.plot(goal.x, goal.y, 'ro')
153+
lines = [axes.plot([], [], 'b-')[0] for _ in range(len(clothoidal_paths))]
154+
155+
def animate(i):
156+
for line, clothoid_path in zip(lines, clothoidal_paths):
157+
x = [p.x for p in clothoid_path[:i]]
158+
y = [p.y for p in clothoid_path[:i]]
159+
line.set_data(x, y)
160+
161+
return lines
162+
163+
anim = animation.FuncAnimation(
164+
fig,
165+
animate,
166+
frames=num_steps,
167+
interval=25,
168+
blit=True
169+
)
170+
if save_animation:
171+
anim.save('clothoid.gif', fps=30, writer="imagemagick")
172+
plt.show()
173+
174+
175+
def main():
176+
start_point = Point(0, 0)
177+
start_orientation_list = [0.0]
178+
goal_point = Point(10, 0)
179+
goal_orientation_list = np.linspace(-pi, pi, 75)
180+
num_path_points = 100
181+
clothoid_paths = generate_clothoid_paths(
182+
start_point, start_orientation_list,
183+
goal_point, goal_orientation_list,
184+
num_path_points)
185+
if show_animation:
186+
draw_clothoids(start_point, goal_point,
187+
num_path_points, clothoid_paths,
188+
save_animation=False)
189+
190+
191+
if __name__ == "__main__":
192+
main()

PathPlanning/ClothoidalPaths/clothoidal_paths.py

Lines changed: 0 additions & 144 deletions
This file was deleted.

docs/modules/control/inverted_pendulum_control/inverted_pendulum_control.rst

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ So
3434
& \ddot{\theta} = \frac{g(M + m)sin{\theta} - \dot{\theta}^2lmsin{\theta}cos{\theta} + ucos{\theta}}{l(M + m - mcos^2{\theta})}
3535
3636
37-
Linearlied model when :math:`\theta` small, :math:`cos{\theta} \approx 1`, :math:`sin{\theta} \approx \theta`, :math:`\dot{\theta}^2 \approx 0`.
37+
Linearized model when :math:`\theta` small, :math:`cos{\theta} \approx 1`, :math:`sin{\theta} \approx \theta`, :math:`\dot{\theta}^2 \approx 0`.
3838

3939
.. math::
4040
& \ddot{x} = \frac{gm}{M}\theta + \frac{1}{M}u\\
@@ -68,30 +68,34 @@ If control x and \theta
6868
LQR control
6969
~~~~~~~~~~~~~~~~~~~~~~~~~~~
7070

71-
The LQR cotroller minimize this cost function defined as:
71+
The LQR controller minimize this cost function defined as:
7272

7373
.. math:: J = x^T Q x + u^T R u
74+
7475
the feedback control law that minimizes the value of the cost is:
7576

7677
.. math:: u = -K x
78+
7779
where:
7880

7981
.. 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):
82+
83+
and :math:`P` is the unique positive definite solution to the discrete time
84+
`algebraic Riccati equation <https://en.wikipedia.org/wiki/Inverted_pendulum#From_Lagrange's_equations>`__ (DARE):
8185

8286
.. math:: P = A^T P A - A^T P B ( R + B^T P B )^{-1} B^T P A + Q
8387

8488
.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Control/InvertedPendulumCart/animation_lqr.gif
8589

8690
MPC control
8791
~~~~~~~~~~~~~~~~~~~~~~~~~~~
88-
The MPC cotroller minimize this cost function defined as:
92+
The MPC controller minimize this cost function defined as:
8993

9094
.. math:: J = x^T Q x + u^T R u
9195

9296
subject to:
9397

94-
- Linearlied Inverted Pendulum model
98+
- Linearized Inverted Pendulum model
9599
- Initial state
96100

97101
.. image:: https://github.com/AtsushiSakai/PythonRoboticsGifs/raw/master/Control/InvertedPendulumCart/animation.gif

0 commit comments

Comments
 (0)