heyoujue commited on
Commit
170ed3d
·
1 Parent(s): 458a605

add trajectory generation w. cubic splines

Browse files
interactive_3d_plot.html CHANGED
The diff for this file is too large to render. See raw diff
 
main.py CHANGED
@@ -1,5 +1,6 @@
1
  from flight_environment import FlightEnvironment
2
  from planners.rrt_planner import rrt_planner
 
3
  from plot_plotly import plot_cylinders
4
 
5
  env = FlightEnvironment(50)
@@ -20,9 +21,11 @@ path = rrt_planner(env, start, goal, step_size=1)
20
 
21
  # --------------------------------------------------------------------------------------------------- #
22
 
23
-
24
  plot_cylinders(env, path)
25
 
 
 
26
 
27
  # --------------------------------------------------------------------------------------------------- #
28
  # Call your trajectory planning algorithm here. The algorithm should
@@ -37,13 +40,12 @@ plot_cylinders(env, path)
37
  # points on the same figure to clearly show how the continuous trajectory
38
  # follows these path points.
39
 
40
-
41
-
42
 
43
  # --------------------------------------------------------------------------------------------------- #
44
 
45
 
46
-
47
  # You must manage this entire project using Git.
48
  # When submitting your assignment, upload the project to a code-hosting platform
49
  # such as GitHub or GitLab. The repository must be accessible and directly cloneable.
 
1
  from flight_environment import FlightEnvironment
2
  from planners.rrt_planner import rrt_planner
3
+ from planners.trajectory_generator import plan_trajectory_through_path, plot_trajectory
4
  from plot_plotly import plot_cylinders
5
 
6
  env = FlightEnvironment(50)
 
21
 
22
  # --------------------------------------------------------------------------------------------------- #
23
 
24
+ # Matplotlib (laggy)
25
  plot_cylinders(env, path)
26
 
27
+ # Plotly
28
+ # env.plot_cylinders(path)
29
 
30
  # --------------------------------------------------------------------------------------------------- #
31
  # Call your trajectory planning algorithm here. The algorithm should
 
40
  # points on the same figure to clearly show how the continuous trajectory
41
  # follows these path points.
42
 
43
+ t_traj, traj = plan_trajectory_through_path(path, desired_speed=1.0, samples_per_meter=8)
44
+ plot_trajectory(t_traj, traj, path)
45
 
46
  # --------------------------------------------------------------------------------------------------- #
47
 
48
 
 
49
  # You must manage this entire project using Git.
50
  # When submitting your assignment, upload the project to a code-hosting platform
51
  # such as GitHub or GitLab. The repository must be accessible and directly cloneable.
planners/__pycache__/rrt_planner.cpython-313.pyc CHANGED
Binary files a/planners/__pycache__/rrt_planner.cpython-313.pyc and b/planners/__pycache__/rrt_planner.cpython-313.pyc differ
 
planners/__pycache__/trajectory_generator.cpython-313.pyc ADDED
Binary file (8.61 kB). View file
 
planners/path_planner.py CHANGED
@@ -1,4 +1,3 @@
1
- import numpy as np
2
  """
3
  In this file, you should implement your own path planning class or function.
4
  Within your implementation, you may call `env.is_collide()` and `env.is_outside()`
@@ -10,100 +9,4 @@ any existing path planning algorithms from others is strictly
10
  prohibited. Please avoid using external packages beyond common Python libraries
11
  such as `numpy`, `math`, or `scipy`. If you must use additional packages, you
12
  must clearly explain the reason in your report.
13
- """
14
-
15
- def is_segment_collision_free(env, p1, p2, step=0.1):
16
- """Return True if straight segment p1->p2 is free.
17
- - env: FlightEnvironment instance
18
- - p1,p2: iterable (x,y,z)
19
- - step: distance between samples along the segment (meters)
20
- """
21
- dist = np.linalg.norm(p2 - p1)
22
- if dist == 0:
23
- return not env.is_collide(tuple(p1)) and not env.is_collide(tuple(p1))
24
- n = max(1, int(np.ceil(dist / step)))
25
- for i in range(n + 1):
26
- t = i / n
27
- p = p1 * (1 - t) + p2 * t
28
- if env.is_outside(tuple(p)) or env.is_collide(tuple(p)):
29
- return False
30
- return True
31
-
32
- def rrt_planner(env, start, goal, max_iters=10000, step_size=1.0, goal_tolerance=0.8, goal_sample_rate=0.1):
33
- """
34
- Basic RRT planner in continuous 3D space with cylinder obstacles provided by env.
35
- Returns a path as an (N x 3) numpy array from start to goal (inclusive).
36
- """
37
- start = np.array(start, dtype=float)
38
- goal = np.array(goal, dtype=float)
39
-
40
- # quick checks
41
- if env.is_outside(tuple(start)) or env.is_collide(tuple(start)):
42
- raise RuntimeError("Start is invalid (outside or in collision).")
43
- if env.is_outside(tuple(goal)) or env.is_collide(tuple(goal)):
44
- raise RuntimeError("Goal is invalid (outside or in collision).")
45
-
46
- # recorded statistics stored here
47
- stats = dict(collisions=0)
48
-
49
- @dataclass
50
- class Node:
51
- point: np.ndarray
52
- parent: np.ndarray = None
53
-
54
- # environment bounds
55
- Xmax, Ymax, Zmax = env.space_size
56
-
57
- nodes = [Node(start)]
58
-
59
- for it in range(max_iters):
60
- # sample point
61
- if np.random.rand() < goal_sample_rate:
62
- sample = goal.copy()
63
- else:
64
- sample = np.array([np.random.uniform(0, Xmax),
65
- np.random.uniform(0, Ymax),
66
- np.random.uniform(0, Zmax)])
67
- # find nearest node
68
- dists = [np.linalg.norm(node.point - sample) for node in nodes]
69
- nearest_idx = int(np.argmin(dists))
70
- nearest = nodes[nearest_idx]
71
-
72
- direction = sample - nearest.point
73
- norm = np.linalg.norm(direction)
74
- if norm == 0:
75
- print('norm0')
76
- continue
77
- direction = direction / norm
78
-
79
- new_point = nearest.point + direction * min(step_size, norm)
80
-
81
- # ensure within bounds
82
- if env.is_outside(tuple(new_point)):
83
- print('outside')
84
- continue
85
-
86
- # check collision along segment
87
- if not is_segment_collision_free(env, nearest.point, new_point, step=step_size / 5.0):
88
- stats["collisions"]+=1
89
- continue
90
-
91
- new_node = Node(new_point, nearest)
92
- nodes.append(new_node)
93
-
94
- # check if we reached goal
95
- if np.linalg.norm(new_point - goal) <= goal_tolerance:
96
- # try connect directly to goal
97
- if is_segment_collision_free(env, new_point, goal, step=step_size / 5.0):
98
- goal_node = Node(goal, new_node)
99
- nodes.append(goal_node)
100
- # reconstruct path
101
- path_nodes = []
102
- cur = goal_node
103
- while cur is not None:
104
- path_nodes.append(cur.point)
105
- cur = cur.parent
106
- path = np.array(path_nodes[::-1])
107
- return path
108
-
109
- raise RuntimeError(f"RRT failed to find a path within the given iterations. {stats=}")
 
 
1
  """
2
  In this file, you should implement your own path planning class or function.
3
  Within your implementation, you may call `env.is_collide()` and `env.is_outside()`
 
9
  prohibited. Please avoid using external packages beyond common Python libraries
10
  such as `numpy`, `math`, or `scipy`. If you must use additional packages, you
11
  must clearly explain the reason in your report.
12
+ """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
planners/rrt_planner.py CHANGED
@@ -1,5 +1,6 @@
1
  from dataclasses import dataclass
2
  import numpy as np
 
3
 
4
  def is_segment_collision_free(env, p1, p2, step=0.1):
5
  """Return True if straight segment p1->p2 is free.
@@ -20,7 +21,7 @@ def is_segment_collision_free(env, p1, p2, step=0.1):
20
 
21
  def rrt_planner(env, start, goal, max_iters=10000, step_size=1.0, goal_tolerance=0.8, goal_sample_rate=0.1):
22
  """
23
- Basic RRT planner in continuous 3D space with cylinder obstacles provided by env.
24
  Returns a path as an (N x 3) numpy array from start to goal (inclusive).
25
  """
26
  start = np.array(start, dtype=float)
@@ -32,8 +33,9 @@ def rrt_planner(env, start, goal, max_iters=10000, step_size=1.0, goal_tolerance
32
  if env.is_outside(tuple(goal)) or env.is_collide(tuple(goal)):
33
  raise RuntimeError("Goal is invalid (outside or in collision).")
34
 
35
- # recorded statistics stored here
36
  stats = dict(collisions=0)
 
37
 
38
  @dataclass
39
  class Node:
@@ -46,7 +48,7 @@ def rrt_planner(env, start, goal, max_iters=10000, step_size=1.0, goal_tolerance
46
  nodes = [Node(start)]
47
 
48
  for it in range(max_iters):
49
- # sample point
50
  if np.random.rand() < goal_sample_rate:
51
  sample = goal.copy()
52
  else:
@@ -61,7 +63,6 @@ def rrt_planner(env, start, goal, max_iters=10000, step_size=1.0, goal_tolerance
61
  direction = sample - nearest.point
62
  norm = np.linalg.norm(direction)
63
  if norm == 0:
64
- print('norm0')
65
  continue
66
  direction = direction / norm
67
 
@@ -69,7 +70,6 @@ def rrt_planner(env, start, goal, max_iters=10000, step_size=1.0, goal_tolerance
69
 
70
  # ensure within bounds
71
  if env.is_outside(tuple(new_point)):
72
- print('outside')
73
  continue
74
 
75
  # check collision along segment
@@ -93,6 +93,7 @@ def rrt_planner(env, start, goal, max_iters=10000, step_size=1.0, goal_tolerance
93
  path_nodes.append(cur.point)
94
  cur = cur.parent
95
  path = np.array(path_nodes[::-1])
 
96
  return path
97
 
98
  raise RuntimeError(f"RRT failed to find a path within the given iterations. {stats=}")
 
1
  from dataclasses import dataclass
2
  import numpy as np
3
+ import time
4
 
5
  def is_segment_collision_free(env, p1, p2, step=0.1):
6
  """Return True if straight segment p1->p2 is free.
 
21
 
22
  def rrt_planner(env, start, goal, max_iters=10000, step_size=1.0, goal_tolerance=0.8, goal_sample_rate=0.1):
23
  """
24
+ Basic RRT planner in continuous 3D space.
25
  Returns a path as an (N x 3) numpy array from start to goal (inclusive).
26
  """
27
  start = np.array(start, dtype=float)
 
33
  if env.is_outside(tuple(goal)) or env.is_collide(tuple(goal)):
34
  raise RuntimeError("Goal is invalid (outside or in collision).")
35
 
36
+ # recorded statistics stored here, can be inspected later
37
  stats = dict(collisions=0)
38
+ tstart = time.time()
39
 
40
  @dataclass
41
  class Node:
 
48
  nodes = [Node(start)]
49
 
50
  for it in range(max_iters):
51
+ # sample random point sometimes, otherwise take goal (->bias)
52
  if np.random.rand() < goal_sample_rate:
53
  sample = goal.copy()
54
  else:
 
63
  direction = sample - nearest.point
64
  norm = np.linalg.norm(direction)
65
  if norm == 0:
 
66
  continue
67
  direction = direction / norm
68
 
 
70
 
71
  # ensure within bounds
72
  if env.is_outside(tuple(new_point)):
 
73
  continue
74
 
75
  # check collision along segment
 
93
  path_nodes.append(cur.point)
94
  cur = cur.parent
95
  path = np.array(path_nodes[::-1])
96
+ print("reached goal", len(nodes), "nodes", len(path), "waypoints", f'{time.time()-tstart:.2f}', "seconds")
97
  return path
98
 
99
  raise RuntimeError(f"RRT failed to find a path within the given iterations. {stats=}")
planners/trajectory_generator.py ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ In this file, you should implement your trajectory generation class or function.
3
+ Your method must generate a smooth 3-axis trajectory (x(t), y(t), z(t)) that
4
+ passes through all the previously computed path points. A positional deviation
5
+ up to 0.1 m from each path point is allowed.
6
+
7
+ You should output the generated trajectory and visualize it. The figure must
8
+ contain three subplots showing x, y, and z, respectively, with time t (in seconds)
9
+ as the horizontal axis. Additionally, you must plot the original discrete path
10
+ points on the same figure for comparison.
11
+
12
+ You are expected to write the implementation yourself. Do NOT copy or reuse any
13
+ existing trajectory generation code from others. Avoid using external packages
14
+ beyond general scientific libraries such as numpy, math, or scipy. If you decide
15
+ to use additional packages, you must clearly explain the reason in your report.
16
+ """
17
+
18
+ import numpy as np
19
+ import matplotlib.pyplot as plt
20
+
21
+ # --------------------------------------------------------------------------------------------------- #
22
+ # Trajectory planning: Natural cubic spline through the discrete path points
23
+ # The trajectory is continuous and smooth and passes through all path points.
24
+ # We parameterize the spline by cumulative chord length (time ~ distance / speed).
25
+ # Then we plot x(t), y(t), z(t) with the discrete path points overlayed.
26
+ # --------------------------------------------------------------------------------------------------- #
27
+
28
+
29
+ def natural_cubic_spline_coeffs(t, y):
30
+ """
31
+ Compute natural cubic spline second derivatives (m values) for points (t, y).
32
+ Returns array m of length n (m[0] = m[-1] = 0).
33
+ """
34
+ n = len(t)
35
+ if n < 2:
36
+ raise ValueError("Need at least two points for spline.")
37
+ h = np.diff(t)
38
+ # handle degenerate h
39
+ if np.any(h <= 0):
40
+ raise ValueError("t must be strictly increasing.", t)
41
+ # set up tridiagonal system for m[1..n-2]
42
+ A = np.zeros((n, n))
43
+ rhs = np.zeros(n)
44
+ A[0, 0] = 1.0
45
+ A[-1, -1] = 1.0
46
+ for i in range(1, n - 1):
47
+ A[i, i - 1] = h[i - 1]
48
+ A[i, i] = 2 * (h[i - 1] + h[i])
49
+ A[i, i + 1] = h[i]
50
+ rhs[i] = 6 * ((y[i + 1] - y[i]) / h[i] - (y[i] - y[i - 1]) / h[i - 1])
51
+ m = np.linalg.solve(A, rhs)
52
+ return m
53
+
54
+
55
+ def build_spline_segments(t, y):
56
+ """
57
+ Given t and y (arrays of length n), compute spline coefficients for each segment.
58
+ Returns list of dicts with keys a,b,c,d and interval [t_i, t_{i+1}).
59
+ Spline form on segment i: S_i(s) = a + b*s + c*s^2 + d*s^3, where s = (t - t_i)
60
+ """
61
+ n = len(t)
62
+ h = np.diff(t)
63
+ m = natural_cubic_spline_coeffs(t, y)
64
+ segments = []
65
+ for i in range(n - 1):
66
+ a = y[i]
67
+ c = m[i] / 2.0
68
+ d = (m[i + 1] - m[i]) / (6.0 * h[i])
69
+ b = (y[i + 1] - y[i]) / h[i] - (h[i] * (2 * m[i] + m[i + 1])) / 6.0
70
+ segments.append({'t0': t[i], 't1': t[i + 1], 'a': a, 'b': b, 'c': c, 'd': d, 'h': h[i]})
71
+ return segments
72
+
73
+
74
+ def evaluate_spline(segments, t_query):
75
+ """
76
+ Evaluate spline (list of segments) at times t_query (array).
77
+ """
78
+ t_query = np.array(t_query)
79
+ y_out = np.zeros_like(t_query, dtype=float)
80
+ for i, ti in enumerate(t_query):
81
+ # find segment
82
+ # segments are ordered, so we can do a simple search
83
+ if ti <= segments[0]['t0']:
84
+ seg = segments[0]
85
+ elif ti >= segments[-1]['t1']:
86
+ seg = segments[-1]
87
+ else:
88
+ # binary search could be used; linear search is fine for modest sizes
89
+ for seg in segments:
90
+ if seg['t0'] <= ti <= seg['t1']:
91
+ break
92
+ s = ti - seg['t0']
93
+ y_out[i] = seg['a'] + seg['b'] * s + seg['c'] * s**2 + seg['d'] * s**3
94
+ return y_out
95
+
96
+
97
+ def plan_trajectory_through_path(path, desired_speed=1.0, samples_per_meter=10):
98
+ """
99
+ Given path (N x 3), compute a smooth trajectory (x(t), y(t), z(t)).
100
+ desired_speed sets time scaling (seconds per meter ~ 1/desired_speed).
101
+ samples_per_meter controls temporal resolution.
102
+ Returns time array t_out and trajectory array traj_out (len(t_out) x 3)
103
+ """
104
+ path = np.array(path, dtype=float)
105
+ n = path.shape[0]
106
+ # parameterize by cumulative chord length
107
+ dists = np.linalg.norm(np.diff(path, axis=0), axis=1)
108
+ chord = np.insert(np.cumsum(dists), 0, 0.0)
109
+ total_length = chord[-1]
110
+ if total_length == 0:
111
+ t = np.array([0.0])
112
+ traj = path.copy()
113
+ return t, traj
114
+
115
+ # time vector proportional to distance, with constant desired_speed
116
+ T_total = total_length / desired_speed
117
+ t_pts = chord / desired_speed # times at which we must pass through path points
118
+
119
+ # build spline for each axis
120
+ seg_x = build_spline_segments(t_pts, path[:, 0])
121
+ seg_y = build_spline_segments(t_pts, path[:, 1])
122
+ seg_z = build_spline_segments(t_pts, path[:, 2])
123
+
124
+ # sample times
125
+ n_samples = max(200, int(np.ceil(total_length * samples_per_meter)))
126
+ t_out = np.linspace(0.0, t_pts[-1], n_samples)
127
+
128
+ x_out = evaluate_spline(seg_x, t_out)
129
+ y_out = evaluate_spline(seg_y, t_out)
130
+ z_out = evaluate_spline(seg_z, t_out)
131
+
132
+ traj_out = np.vstack((x_out, y_out, z_out)).T
133
+ return t_out, traj_out
134
+
135
+ def plot_trajectory(t_traj, traj, path):
136
+ # Plot x(t), y(t), z(t) in separate subplots and overlay discrete path points at their times
137
+ fig = plt.figure(figsize=(8, 8))
138
+ axs = []
139
+ axs.append(plt.subplot2grid((7, 1), (0, 0), rowspan=3, colspan=1))
140
+ axs.append(plt.subplot2grid((7, 1), (3, 0), rowspan=3, colspan=1))
141
+ axs.append(plt.subplot2grid((7, 1), (6, 0), rowspan=1, colspan=1))
142
+ axs[0].plot(t_traj, traj[:, 0], label='x(t)')
143
+ axs[1].plot(t_traj, traj[:, 1], label='y(t)', color='orange')
144
+ axs[2].plot(t_traj, traj[:, 2], label='z(t)', color='green')
145
+
146
+ # overlay discrete path points
147
+ # compute discrete path times (same parameterization used for spline)
148
+ path = np.array(path)
149
+ dists = np.linalg.norm(np.diff(path, axis=0), axis=1)
150
+ chord = np.insert(np.cumsum(dists), 0, 0.0)
151
+ path_times = chord / 1.0 # desired_speed = 1.0 used above
152
+
153
+ axs[0].scatter(path_times, path[:, 0], color='k', zorder=10)
154
+ axs[1].scatter(path_times, path[:, 1], color='k', zorder=10)
155
+ axs[2].scatter(path_times, path[:, 2], color='k', zorder=10)
156
+
157
+ axs[2].set_xlabel('time (s)')
158
+ axs[0].set_ylabel('x (m)')
159
+ axs[1].set_ylabel('y (m)')
160
+ axs[2].set_ylabel('z (m)')
161
+
162
+ axs[0].grid(True)
163
+ axs[1].grid(True)
164
+ axs[2].grid(True)
165
+
166
+ axs[0].set_title('Trajectory')
167
+
168
+ plt.tight_layout()
169
+ plt.show()
trajectory_generator.py DELETED
@@ -1,17 +0,0 @@
1
- """
2
- In this file, you should implement your trajectory generation class or function.
3
- Your method must generate a smooth 3-axis trajectory (x(t), y(t), z(t)) that
4
- passes through all the previously computed path points. A positional deviation
5
- up to 0.1 m from each path point is allowed.
6
-
7
- You should output the generated trajectory and visualize it. The figure must
8
- contain three subplots showing x, y, and z, respectively, with time t (in seconds)
9
- as the horizontal axis. Additionally, you must plot the original discrete path
10
- points on the same figure for comparison.
11
-
12
- You are expected to write the implementation yourself. Do NOT copy or reuse any
13
- existing trajectory generation code from others. Avoid using external packages
14
- beyond general scientific libraries such as numpy, math, or scipy. If you decide
15
- to use additional packages, you must clearly explain the reason in your report.
16
- """
17
-