def tree_search(self, max_expand=float('inf')): """initialize state tree using the initial state of problem""" expand_count = 0 while True: # if there are no candidates for expansion, return fail if self.fringe.is_empty(): raise Exception("Tree search failed!") # choose which node to expand based on strategy: use heuristic to determine the best option to expand option = self.fringe.extract_min() self.hist.append(option) # for debug # if the node contains a goal state, return the solution if option.state.is_goal(): # check if the chosen node is a goal node debug("goal reached:") option.state.describe() return expand_count, self.backtrack(option) elif expand_count < max_expand: # otherwise, expand the node self.expand_node(option) expand_count += 1 else: print( 'Maximum number of expansions reached. Returning best strategy so far' ) return expand_count, self.backtrack(option)
def act(self, env: Environment): if not self.is_available(env): return debug("# NOOPS left: {}".format(self.noop_counter)) if self.noop_counter != 0: self.noop_counter -= 1 self.no_op(env) return u = self.loc if self.last_action_type() == ActionType.BLOCK: targets = self.get_possible_steps(env) else: targets = env.G.neighbours(self.loc) edges = [env.G.get_edge(u, v) for v in targets] if not edges: self.terminate(env) return def comp(e: Edge): dest = e.v1 if u != e.v1 else e.v2 return e.w, dest.label # sort by weight, tie-breaker is destination node's name e_min = min(edges, key=comp) if self.last_action_type() == ActionType.BLOCK: # traverse road with lowest weight, i.e move to second vertex of respective edge v = e_min.v1 if u != e_min.v1 else e_min.v2 self.goto2(env, v) else: # block the accessible road with the lowest weight, i.e remove it from graph self.block2(env, e_min)
def heuristic_helper(self, agent, state: State): """given a state for an max_player, returns how many people can (!) be saved by the max_player""" self.env.apply_state(state, active_agent=agent) if agent.terminated: return agent.n_saved def num_rescuable_carrying(): can_reach_a_shelter = any([ self.env.can_reach_before_deadline(v) for v in self.env.get_shelters() ]) return agent.n_carrying if can_reach_a_shelter else 0 def get_evac_candidates(): """find nodes that can be reached before hurricane hits them. :returns: a list of (node, pickup_time, pickup_path) tuples""" src = agent.loc self.env.G.dijkstra(src) evacuation_candidates = [] require_evac_nodes = self.env.get_require_evac_nodes() for v in require_evac_nodes: if self.env.can_reach_before_deadline( v) and self.env.can_reach_before_other_agent(agent, v): # nodes we can reach in time time_after_pickup = self.env.time + v.d pickup_shortest_path = list( self.env.G.get_shortest_path(src, v)) evacuation_candidates.append( (v, time_after_pickup, pickup_shortest_path)) return evacuation_candidates def can_reach_shelter(evacuation_candidates): can_save = [] V = self.env.G.get_vertices() for u, time_after_pickup, pickup_shortest_path in evacuation_candidates: self.env.G.dijkstra( u) # calculate minimum distance from node after pickup shelter_candidates = [ (v, time_after_pickup + v.d, list(self.env.G.get_shortest_path(u, v))) for v in V if v.is_shelter() and time_after_pickup + v.d <= v.deadline ] if len(shelter_candidates) != 0: can_save.append(u) return can_save evac_candidates = get_evac_candidates() can_save_nodes = can_reach_shelter(evac_candidates) n_already_saved = agent.n_saved self.env.G.dijkstra(agent.loc) # TODO: remove if all goes to shit n_can_save_carrying = num_rescuable_carrying() n_can_save_new = sum([v.n_people for v in can_save_nodes]) total_can_save = n_can_save_carrying + n_can_save_new + n_already_saved debug( '[#{0}]: {1.name} can save {2} (nodes:{3} (={4}) + rescuable carrying ={5} + saved before={1.n_saved})' .format(state.ID, agent, total_can_save, can_save_nodes, n_can_save_new, n_can_save_carrying)) return total_can_save
def try_evacuate(self, env: Environment, v: EvacuateNode): if self.terminated: return if v.is_shelter(): if self.n_carrying > 0: debug('Dropped off {.n_carrying} people'.format(self)) self.n_saved += self.n_carrying self.n_carrying = 0 elif not v.evacuated: debug('Picked up {} people'.format(v.n_people)) self.n_carrying += v.n_people v.evacuated = True v.n_people = 0 env.require_evac_nodes.remove(v)
def expand_node(self, plan: Plan): """Expands fringe, adding (path, state) pair of all possible moves.""" self.env.apply_state(plan.state) agent = plan.state.agent debug("Expanding node ID={0.ID} (cost = {0.cost}):".format(plan)) plan.state.describe() neighbours = agent.get_possible_steps( self.env, verbose=True) # options to proceed for dest in neighbours + [ActionType.TERMINATE]: action, result_state = self.successor(plan.state, dest) debug("\ncreated state:") result_state.describe() cost = self.total_cost(result_state) new_plan = Plan(cost=cost, state=result_state, action=action, parent=plan) debug("plan ID={}".format(new_plan.ID)) self.fringe.insert(new_plan)
def heuristic(self, state: State = None): """given a state for an agent, returns how many people cannot be saved by the agent""" self.env.apply_state(state) agent = state.agent src = agent.loc self.env.G.dijkstra(src) V = self.env.G.get_vertices() require_evac_nodes = list(self.env.require_evac_nodes) # find nodes that can be reached before hurricane hits them. create (node, required_pickup_time) pairs evac_candidates, doomed_nodes = [], [] for v in require_evac_nodes: if self.env.time + v.d > v.deadline: doomed_nodes.append( v) # nodes we cannot save from the imminent hurricane else: evac_candidates.append( (v, self.env.time + v.d, list(self.env.G.get_shortest_path(src, v)))) for u, time_after_pickup, pickup_shortest_path in evac_candidates: self.env.G.dijkstra( u) # calculate minimum distance from node after pickup shelter_candidates = [ (v, time_after_pickup + v.d, list(self.env.G.get_shortest_path(u, v))) for v in V if v.is_shelter() and time_after_pickup + v.d <= v.deadline ] if not shelter_candidates: doomed_nodes.append(u) debug('\npossible routes for evacuating {}:'.format(u)) for shelter, total_time, dropoff_shortest_path in shelter_candidates: debug('pickup:(T{}){}(T{}) | drop-off:{}(T{}): Shelter(D{})'. format(self.env.time, pickup_shortest_path, time_after_pickup, dropoff_shortest_path, total_time, shelter.deadline)) n_doomed_people = sum([v.n_people for v in doomed_nodes]) debug('h(x) = {} = # of doomed people (doomed_nodes = {})'.format( n_doomed_people, doomed_nodes)) return n_doomed_people
def tie_breaker(agent, option, current_best_option): debug('Tie: %s VS. %s' % (current_best_option.state.ID, option.state.ID)) agent_state = agent is option.state.agent and option.state.agent_state or option.state.agent2_state return (Configurator.tie_breaker is 'goal' and option.state.is_goal()) or \ (Configurator.tie_breaker is 'shelter' and agent_state.loc.is_shelter())
def total_cost(self, state): # assumes environment's state was updated before calling this function h = 0 if state.is_goal() else self.heuristic(state) g = state.agent.penalty debug('cost = g + h = {} + {} = {}'.format(g, h, g + h)) return g + h