22import random
33import matplotlib .pyplot as plt
44import matplotlib .animation as animation
5+
6+ class Position :
7+ x : int
8+ y : int
9+
10+ def __init__ (self , x : int , y : int ):
11+ self .x = x
12+ self .y = y
13+
14+ def as_ndarray (self ) -> np .ndarray [int , int ]:
15+ return np .array ([self .x , self .y ])
16+
17+ def __add__ (self , other ):
18+ if isinstance (other , Position ):
19+ return Position (self .x + other .x , self .y + other .y )
20+ raise NotImplementedError (f"Addition not supported for Position and { type (other )} " )
21+
22+ def __sub__ (self , other ):
23+ if isinstance (other , Position ):
24+ return Position (self .x - other .x , self .y - other .y )
25+ raise NotImplementedError (f"Subtraction not supported for Position and { type (other )} " )
26+
27+ def __eq__ (self , other ):
28+ if isinstance (other , Position ):
29+ return self .x == other .x and self .y == other .y
30+ return False
31+
32+ def __repr__ (self ):
33+ return f"Position({ self .x } , { self .y } )"
34+
535class Grid ():
636
737 # Set in constructor
838 grid_size = None
939 grid = None
1040 obstacle_paths = []
41+ # Obstacles will never occupy these points. Useful to avoid impossible scenarios
42+ obstacle_avoid_points = []
1143
1244 # Problem definition
1345 time_limit = 100
14- num_obstacles = 2
46+ num_obstacles : int
1547
1648 # Logging control
1749 verbose = False
1850
19- def __init__ (self , grid_size : np .ndarray [int , int ]):
51+ def __init__ (self , grid_size : np .ndarray [int , int ], num_obstacles : int = 2 , obstacle_avoid_points : list [Position ] = []):
52+ self .num_obstacles = num_obstacles
53+ self .obstacle_avoid_points = obstacle_avoid_points
2054 self .grid_size = grid_size
2155 self .grid = np .zeros ((grid_size [0 ], grid_size [1 ], self .time_limit ))
2256
@@ -35,28 +69,32 @@ def __init__(self, grid_size: np.ndarray[int, int]):
3569 output:
3670 list[np.ndarray[int, int]]: list of positions of the obstacle at each time step
3771 """
38- def generate_dynamic_obstacle (self , obs_idx : int ) -> list [np . ndarray [ int , int ] ]:
72+ def generate_dynamic_obstacle (self , obs_idx : int ) -> list [Position ]:
3973
4074 # Sample until a free starting space is found
4175 initial_position = self .sample_random_position ()
42- while not self .valid_position (initial_position , 0 ):
76+ while not self .valid_obstacle_position (initial_position , 0 ):
4377 initial_position = self .sample_random_position ()
4478
4579 positions = [initial_position ]
4680 if self .verbose :
4781 print ("Obstacle initial position: " , initial_position )
48-
49- diffs = [np .array ([0 , 1 ]), np .array ([0 , - 1 ]), np .array ([1 , 0 ]), np .array ([- 1 , 0 ]), np .array ([0 , 0 ])]
50- weights = [0.125 , 0.125 , 0.125 , 0.125 , 0.5 ]
82+
83+ # Encourage obstacles to mostly stay in place - too much movement leads to chaotic planning scenarios
84+ # that are not fun to watch
85+ weights = [0.05 , 0.05 , 0.05 , 0.05 , 0.8 ]
86+ diffs = [Position (0 , 1 ), Position (0 , - 1 ), Position (1 , 0 ), Position (- 1 , 0 ), Position (0 , 0 )]
5187
5288 for t in range (1 , self .time_limit - 1 ):
53- rand_diffs = random .sample (diffs , k = 5 )
89+ sampled_indices = np .random .choice (len (diffs ), size = 5 , replace = False , p = weights )
90+ rand_diffs = [diffs [i ] for i in sampled_indices ]
91+ # rand_diffs = random.sample(diffs, k=len(diffs))
5492
5593 valid_position = None
5694 for diff in rand_diffs :
5795 new_position = positions [- 1 ] + diff
5896
59- if not self .valid_position (new_position , t ):
97+ if not self .valid_obstacle_position (new_position , t ):
6098 continue
6199
62100 valid_position = new_position
@@ -68,9 +106,9 @@ def generate_dynamic_obstacle(self, obs_idx: int) -> list[np.ndarray[int, int]]:
68106 valid_position = positions [- 1 ]
69107
70108 # Reserve old & new position at this time step
71- positions . append ( new_position )
72- self .grid [positions [ - 2 ][ 0 ], positions [ - 2 ][ 1 ] , t ] = obs_idx
73- self . grid [ new_position [ 0 ], new_position [ 1 ], t ] = obs_idx
109+ self . grid [ positions [ - 1 ]. x , positions [ - 1 ]. y , t ] = obs_idx
110+ self .grid [valid_position . x , valid_position . y , t ] = obs_idx
111+ positions . append ( valid_position )
74112
75113 return positions
76114
@@ -84,23 +122,35 @@ def generate_dynamic_obstacle(self, obs_idx: int) -> list[np.ndarray[int, int]]:
84122 output:
85123 bool: True if position/time combination is valid, False otherwise
86124 """
87- def valid_position (self , position , t ) -> bool :
125+ def valid_position (self , position : Position , t : int ) -> bool :
88126
89127 # Check if new position is in grid
90- if position [ 0 ] < 0 or position [ 0 ] >= self .grid_size [ 0 ] or position [ 1 ] < 0 or position [ 1 ] >= self . grid_size [ 1 ] :
128+ if not self .inside_grid_bounds ( position ) :
91129 return False
92130
93131 # Check if new position is not occupied at time t
94- return self .grid [position [0 ], position [1 ], t ] == 0
132+ return self .grid [position .x , position .y , t ] == 0
133+
134+ """
135+ Returns True if the given position is valid at time t and is not in the set of obstacle_avoid_points
136+ """
137+ def valid_obstacle_position (self , position : Position , t : int ) -> bool :
138+ return self .valid_position (position , t ) and position not in self .obstacle_avoid_points
139+
140+ """
141+ Returns True if the given position is within the grid's boundaries
142+ """
143+ def inside_grid_bounds (self , position : Position ) -> bool :
144+ return position .x >= 0 and position .x < self .grid_size [0 ] and position .y >= 0 and position .y < self .grid_size [1 ]
95145
96146 """
97147 Sample a random position that is within the grid's boundaries
98148
99149 output:
100150 np.ndarray[int, int]: (x, y) position
101151 """
102- def sample_random_position (self ) -> np . ndarray [ int , int ] :
103- return np . array ([ np .random .randint (0 , self .grid_size [0 ]), np .random .randint (0 , self .grid_size [1 ])] )
152+ def sample_random_position (self ) -> Position :
153+ return Position ( np .random .randint (0 , self .grid_size [0 ]), np .random .randint (0 , self .grid_size [1 ]))
104154
105155show_animation = True
106156
@@ -123,8 +173,8 @@ def get_frame(i):
123173 obs_y_points = []
124174 for obs_path in grid .obstacle_paths :
125175 obs_pos = obs_path [i ]
126- obs_x_points .append (obs_pos [ 0 ] )
127- obs_y_points .append (obs_pos [ 1 ] )
176+ obs_x_points .append (obs_pos . x )
177+ obs_y_points .append (obs_pos . y )
128178 points .set_data (obs_x_points , obs_y_points )
129179 return points ,
130180
0 commit comments