def update_rangefinder_sensors(self): """ The function to update the agent range finder sensors. """ for i, angle in enumerate(self.agent.range_finder_angles): rad = geometry.deg_to_rad(angle) # project a point from agent location outwards projection_point = geometry.Point( x = self.agent.location.x + math.cos(rad) * self.agent.range_finder_range, y = self.agent.location.y + math.sin(rad) * self.agent.range_finder_range ) # rotate the projection point by the agent's heading angle to # align it with heading direction projection_point.rotate(self.agent.heading, self.agent.location) # create the line segment from the agent location to the projected point projection_line = geometry.Line( a = self.agent.location, b = projection_point ) # set range to maximum detection range min_range = self.agent.range_finder_range # now test against maze walls to see if projection line hits any wall # and find the closest hit for wall in self.walls: found, intersection = wall.intersection(projection_line) if found: found_range = intersection.distance(self.agent.location) # we are interested in the closest hit if found_range < min_range: min_range = found_range # Update sensor value self.agent.range_finders[i] = min_range
def maze_simulation_evaluate(env, net, time_steps, mcns=0.0, n_item=None, path_points=None): """ The function to evaluate maze simulation for specific environment and controll ANN provided. The results will be saved into provided agent record holder. Arguments: env: The maze configuration environment. net: The maze solver agent's control ANN. time_steps: The number of time steps for maze simulation. mcns: The minimal criteria fitness value. n_item: The NoveltyItem to store evaluation results. path_points: The holder for path points collected during simulation. If provided None then nothing will be collected. Returns: The goal-oriented fitness value, i.e., how close is agent to the exit at the end of simulation. """ exit_found = False for i in range(time_steps): if maze_simulation_step(env, net): print("Maze solved in %d steps" % (i + 1)) exit_found = True break if path_points is not None: # collect current position path_points.append(geometry.Point(env.agent.location.x, env.agent.location.y)) # store agent path points at a given sample size rate if (time_steps - i) % env.location_sample_rate == 0 and n_item is not None: n_item.data.append(env.agent.location.x) n_item.data.append(env.agent.location.y) # store final agent coordinates as genome's novelty characteristics if n_item is not None: n_item.data.append(env.agent.location.x) n_item.data.append(env.agent.location.y) # Calculate the fitness score based on distance from exit fitness = 0.0 if exit_found: fitness = 1.0 else: # Normalize distance to range (0,1] distance = env.agent_distance_to_exit() fitness = (env.initial_distance - distance) / env.initial_distance if fitness <= 0: fitness = 0.01 # Use minimal criteria fitness value to signal if genome should be included into population if fitness < mcns: fitness = -1 # mark genome to be excluded if n_item is not None: n_item.fitness = fitness return fitness
def update(self, control_signals): """ The function to update solver agent position within maze. After agent position updated it will be checked to find out if maze exit was reached afetr that. Arguments: control_signals: The control signals received from the control ANN Returns: The True if maze exit was found after update or maze exit was already found in previous simulation cycles. """ if self.exit_found: # Maze exit already found return True # Apply control signals self.apply_control_signals(control_signals) # get X and Y velocity components vx = math.cos(geometry.deg_to_rad(self.agent.heading)) * self.agent.speed vy = math.sin(geometry.deg_to_rad(self.agent.heading)) * self.agent.speed # Update current Agent's heading (we consider the simulation time step size equal to 1s # and the angular velocity as degrees per second) self.agent.heading += self.agent.angular_vel # Enforce angular velocity bounds by wrapping if self.agent.heading > 360: self.agent.heading -= 360 elif self.agent.heading < 0: self.agent.heading += 360 # find the next location of the agent new_loc = geometry.Point( x = self.agent.location.x + vx, y = self.agent.location.y + vy ) if not self.test_wall_collision(new_loc): self.agent.location = new_loc # update agent's sensors self.update_rangefinder_sensors() self.update_radars() # check if agent reached exit point distance = self.agent_distance_to_exit() self.exit_found = (distance < self.exit_range) return self.exit_found
def update_radars(self): """ The function to update the agent radar sensors. """ target = geometry.Point(self.exit_point.x, self.exit_point.y) # rotate target with respect to the agent's heading to align it with heading direction target.rotate(self.agent.heading, self.agent.location) # translate with respect to the agent's location target.x -= self.agent.location.x target.y -= self.agent.location.y # the angle between maze exit point and the agent's heading direction angle = target.angle() # find the appropriate radar sensor to be fired for i, r_angles in enumerate(self.agent.radar_angles): self.agent.radar[i] = 0.0 # reset specific radar if (angle >= r_angles[0] and angle < r_angles[1]) or (angle + 360 >= r_angles[0] and angle + 360 < r_angles[1]): self.agent.radar[i] = 1.0 # fire the radar
def draw_maze_records(maze_env, records, best_threshold=0.8, filename=None, view=False, show_axes=False, width=400, height=400, fig_height=7): """ The function to draw maze with recorded agents positions. Arguments: maze_env: The maze environment configuration. records: The records of solver agents collected during NEAT execution. best_threshold: The minimal fitness of maze solving agent's species to be included into the best ones. filename: The name of file to store plot. view: The flag to indicate whether to view plot. width: The width of drawing in pixels height: The height of drawing in pixels fig_height: The plot figure height in inches """ # find the distance threshold for the best species dist_threshold = maze_env.agent_distance_to_exit() * (1.0 - best_threshold) # generate color palette and find the best species IDS max_sid = 0 for r in records: if r.species_id > max_sid: max_sid = r.species_id colors = [None] * (max_sid + 1) sp_idx = [False] * (max_sid + 1) best_sp_idx = [0] * (max_sid + 1) for r in records: if not sp_idx[r.species_id]: sp_idx[r.species_id] = True rgb = (random.random(), random.random(), random.random()) colors[r.species_id] = rgb if maze_env.exit_point.distance(geometry.Point(r.x, r.y)) <= dist_threshold: best_sp_idx[r.species_id] += 1 # initialize plotting fig = plt.figure() fig.set_dpi(100) fig_width = fig_height * (float(width) / float(2.0 * height)) - 0.2 print("Plot figure width: %.1f, height: %.1f" % (fig_width, fig_height)) fig.set_size_inches(fig_width, fig_height) ax1, ax2 = fig.subplots(2, 1, sharex=True) ax1.set_xlim(0, width) ax1.set_ylim(0, height) ax2.set_xlim(0, width) ax2.set_ylim(0, height) # draw species n_best_species = 0 for i, v in enumerate(best_sp_idx): if v > 0: n_best_species += 1 _draw_species_(records=records, sid=i, colors=colors, ax=ax1) else: _draw_species_(records=records, sid=i, colors=colors, ax=ax2) ax1.set_title('fitness >= %.1f, species: %d' % (best_threshold, n_best_species)) ax2.set_title('fitness < %.1f' % best_threshold) # draw maze _draw_maze_(maze_env, ax1) _draw_maze_(maze_env, ax2) # turn off axis rendering if not show_axes: ax1.axis('off') ax2.axis('off') # Invert Y axis to have coordinates origin at the top left ax1.invert_yaxis() ax2.invert_yaxis() # Save figure to file if filename is not None: plt.savefig(filename) if view: plt.show() plt.close()