Пример #1
0
class EvacuationModel(Model):
    """
    This is a simulation of a crowd evacuation from a building.
    Several variables are taken into account: the knowledge of the emergency exits, the age and weight of the agents
    and the presence of stewards that can guide agents toward the emergency exits.
    Agents have different strategies to escape the building such as taking the shortest path to an exit or a random one.

    The goal is to study which combinations of agent types are more likely to escape the building and save themselves and
    how the amount of casualties varies with respect to the different variables.
    """
    def __init__(self,
                 N=10,
                 K=0,
                 width=50,
                 height=50,
                 fire_x=1,
                 fire_y=1,
                 civil_info_exchange=True):
        self.num_civilians = N
        self.num_stewards = K
        self.civil_info_exchange = civil_info_exchange
        self.fire_initial_pos = (fire_x, fire_y)
        self.warning_UI = ""
        self.agents_alive = N + K  # Agents alive and inside the building
        self.agents_saved = []  # Agents that managed to get out
        self.agents_killed = []  # Agents that perished during the evacuation
        self.grid = SingleGrid(height, width, False)
        self.graph = None  # General graph representing walkable terrain
        self.schedule = RandomActivation(
            self)  # Every tick, agents move in a different random order
        # Create exits
        self.pos_exits = [(0, 5), (0, 25), (0, 45)]
        for i in range(3):
            self.pos_exits.append((self.grid.width - 1, 14 + i))

        self.draw_environment(self.pos_exits)
        self.graph = path_finding.create_graph(self)
        # Define data collector
        model_collector = {
            "Agents killed": lambda killed: len(self.agents_killed),
            "Agents saved": lambda saved: len(self.agents_saved)
        }
        for exit_pos in self.pos_exits:
            title = "Exit {}".format(exit_pos)
            model_collector[title] = partial(count_agents_saved, exit_pos)
        self.datacollector = DataCollector(model_reporters=model_collector)
        # Create fire
        # for pos in self.fire_initial_pos:  # Only 1 source of fire since we are setting it from UI
        x, y = self.fire_initial_pos
        if not self.is_inside_square((x, y), (0, 29),
                                     (25, 39)) and not self.is_inside_square(
                                         (x, y), (0, 10), (25, 20)):
            pos = self.fire_initial_pos
        else:
            pos = (1, 1)
            self.warning_UI = "<b>WARNING:</b> Sorry but the position of the fire is outside of the building, " \
                              "change the setting and click reset simulation."
        fire_agent = FireAgent(pos, self)
        self.schedule.add(fire_agent)
        self.grid.place_agent(fire_agent, pos)
        # Create civilian agents
        for i in range(self.num_civilians):

            # a civilian agent will know at least the main entrance to the building
            known_exits = self.pos_exits[-3:]
            a = CivilianAgent(i, self, known_exits)

            self.schedule.add(a)
            # Add the agent to a random grid cell

            while True:
                # pick the random coordinate
                x = self.random.randrange(1, self.grid.width - 1)
                y = self.random.randrange(1, self.grid.height - 1)
                # check if the point is empty and inside of the building
                if self.grid.is_cell_empty((x, y)) and not self.is_inside_square((x, y), (0, 29), (25, 39)) \
                        and not self.is_inside_square((x, y), (0, 10), (25, 20)):
                    break

            self.grid.place_agent(a, (x, y))

        # Create steward agents
        for i in range(self.num_civilians,
                       self.num_civilians + self.num_stewards):

            # a steward agent will know all exits.
            known_exits = self.pos_exits
            a = StewardAgent(i, self, known_exits)

            self.schedule.add(a)
            # Add the agent to a random grid cell

            while True:
                # pick the random coordinate
                x = self.random.randrange(1, self.grid.width - 1)
                y = self.random.randrange(1, self.grid.height - 1)
                # check if the point is empty and inside of the building
                if self.grid.is_cell_empty((x, y)) and not self.is_inside_square((x, y), (0, 29), (25, 39)) \
                        and not self.is_inside_square((x, y), (0, 10), (25, 20)):
                    break

            self.grid.place_agent(a, (x, y))

        self.running = True  # Set this to false when we want to finish simulation (e.g. all agents are out of building)
        self.datacollector.collect(self)

    @staticmethod
    def is_inside_square(point, bottom_left, top_right):
        return bottom_left[0] <= point[0] <= top_right[0] and bottom_left[
            1] <= point[1] <= top_right[1]

    def step(self):
        self.schedule.step()
        # collect data
        self.datacollector.collect(self)

        # Halt if no more agents in the building
        if self.count_agents(self) == 0:
            self.running = False

    def remove_agent(self, agent, reason, **kwargs):
        """
        Removes an agent from the simulation. Depending on the reason it can be
        Args:
            agent (Agent):
            reason (Reasons):

        Returns:
            None
        """
        if reason == Reasons.SAVED:
            self.agents_saved.append(agent)
        elif reason == Reasons.KILLED_BY_FIRE:
            self.agents_killed.append(agent)

        self.agents_alive -= 1
        self.schedule.remove(agent)
        self.grid.remove_agent(agent)

    def draw_environment(self, exits=None):
        length_E = int(self.grid.height /
                       5)  # length of the vertical segments of the E
        depth_E = int(self.grid.width /
                      2)  # length of the horizontal segments of the E
        for i in range(3):
            start = max(0, 2 * i * length_E)
            self.draw_wall((0, start), (0, start + length_E - 1))
        for i in range(2):
            start = 2 * i * length_E + length_E
            self.draw_wall((depth_E, start), (depth_E, start + length_E - 1))
        # Horizontal lines of the E (BB)
        aux_y_coord = [
            length_E, 2 * length_E, 3 * length_E - 1, 4 * length_E - 1
        ]
        for y in aux_y_coord:
            self.draw_wall((0, y), (depth_E, y))
        top_left_corner = (0, self.grid.height - 1)
        top_right_corner = (self.grid.width - 1, self.grid.height - 1)
        bottom_right_corner = (self.grid.width - 1, 0)
        # Draw long contour lines E
        self.draw_wall((0, 0), bottom_right_corner)
        self.draw_wall(top_left_corner, top_right_corner)
        self.draw_wall(bottom_right_corner, top_right_corner)

        # Draw exits
        self.draw_exits(exits)

    def draw_wall(self, start, end):
        """
        Draws a line that goes from start point to end point.

        Args:
            start (List): Coordinates of line's starting point
            end (List): Coordinates of line's end point

        Returns:
            None
        """
        diff_x, diff_y = np.subtract(end, start)
        wall_coordinates = np.asarray(start)

        if self.grid.is_cell_empty(wall_coordinates.tolist()):
            w = WallAgent(wall_coordinates.tolist(), self)
            self.grid.place_agent(w, wall_coordinates.tolist())

        while diff_x != 0 or diff_y != 0:
            if abs(diff_x) == abs(diff_y):
                # diagonal wall
                wall_coordinates[0] += np.sign(diff_x)
                wall_coordinates[1] += np.sign(diff_y)
                diff_x -= 1
                diff_y -= 1
            elif abs(diff_x) < abs(diff_y):
                # wall built in y dimension
                wall_coordinates[1] += np.sign(diff_y)
                diff_y -= 1
            else:
                # wall built in x dimension
                wall_coordinates[0] += np.sign(diff_x)
                diff_x -= 1
            if self.grid.is_cell_empty(wall_coordinates.tolist()):
                w = WallAgent(wall_coordinates.tolist(), self)
                self.grid.place_agent(w, wall_coordinates.tolist())

    def draw_exits(self, exits_list):
        for ext in exits_list:
            e = ExitAgent(ext, self)
            if not self.grid.is_cell_empty(ext):
                # Only walls should exist in the grid at this time, so no need to remove it from scheduler
                agent = self.grid.get_cell_list_contents(ext)
                self.grid.remove_agent(agent[0])
            # Place exit
            self.schedule.add(e)
            self.grid.place_agent(e, ext)

    def spread_fire(self, fire_agent):
        fire_neighbors = self.grid.get_neighborhood(fire_agent.pos,
                                                    moore=True,
                                                    include_center=False)
        for grid_space in fire_neighbors:
            if self.grid.is_cell_empty(grid_space):
                # Create new fire agent and add it to grid and scheduler
                new_fire_agent = FireAgent(grid_space, self)
                self.schedule.add(new_fire_agent)
                self.grid.place_agent(new_fire_agent, grid_space)
            else:
                # If human agents, eliminate them and spread anyway
                agent = self.grid.get_cell_list_contents(grid_space)[0]
                if isinstance(agent, (CivilianAgent, StewardAgent)):
                    new_fire_agent = FireAgent(grid_space, self)
                    self.remove_agent(agent, Reasons.KILLED_BY_FIRE)
                    self.schedule.add(new_fire_agent)
                    self.grid.place_agent(new_fire_agent, grid_space)

    @staticmethod
    def count_agents(model):
        """
        Helper method to count agents alive and still in the building.
        """
        count = 0
        for agent in model.schedule.agents:
            agent_type = type(agent)
            if (agent_type == CivilianAgent) or (agent_type == StewardAgent):
                count += 1
        return count
Пример #2
0
class DaisyModel(Model):
    """ "Daisys" grow, when the temperature is right. But they influence temperature themselves via their ability to block a certain amount of sunlight (albedo, indicated by color). They spread and they mutate (changing albedo) and thus adapt to different conditions."""
    def __init__(self, 
                 N, 
                 width, 
                 height, 
                 luminosity, 
                 heat_radius, 
                 mutation_range, 
                 surface_albedo, 
                 daisy_lifespan, 
                 daisy_tmin, 
                 daisy_tmax,
                 lum_model,
                 lum_increase):
        # Setup parameter
        self.dimensions = (width, height)
        self.running = True # never stop!
        self.num_agents = min([N, (width * height)]) # never more agents than cells
        self.grid = SingleGrid(width, height, torus=True)
        self.schedule = RandomActivation(self)
        # Model parameter
        self.mutation_range = mutation_range # default: 0.05
        self.luminosity = luminosity # default 1.35
        self.heat_radius = heat_radius
        self.surface_albedo = surface_albedo # default: 0.4
        self.lum_model = lum_model
        self.lum_increase = lum_increase # tried 0.001
        # Daisy parameter
        self.daisy_lifespan = daisy_lifespan
        self.daisy_tmin = daisy_tmin
        self.daisy_tmax = daisy_tmax

        # to inhibit using same postition twice: draw from urn
        position_list = []
        for i in range(width): # put positions in urn
            for j in range(height):
                position_list.append((i,j))
        for i in range(self.num_agents): # draw from urn
            a = DaisyAgent(i, self, 
                            random.uniform(0.1, 0.9), # random starting albedo
                            self.daisy_lifespan, self.daisy_tmin, self.daisy_tmax)
            self.schedule.add(a)
            pos = random.choice(position_list)
            self.grid.place_agent(a, pos)
            position_list.remove(pos)

        # Data collectors
        self.datacollector = DataCollector(
            model_reporters = {"Solar irradiance": get_irradiance, 
                               "Population": get_population,
                               "Mean albedo": get_mean_albedo,
                               "Population: North - South": get_north_south_population
                               }
        )

    def step(self):
        print(self.lum_model)
        if self.lum_model == 'linear increase':
            self.luminosity = linear_increase(self)


        self.datacollector.collect(self)
        self.schedule.step()
        

    def get_lat(self, pos):
        """ The grid is meant to be a sphere. This gets the latitude. Ranges from 0.0 (equator) to 1.0 (pole).  """
        return (pos[1] / self.dimensions[1])

    def get_GNI(self, pos):
        """ gives solar irradiance, depending on latitude"""
        return self.luminosity * math.sin(self.get_lat(pos)*math.pi)

    def expand_positionlist(self, pos_list):
        """ expands a list of positions, adding neighboring positions  """
        expanded_list = []
        for i in pos_list:
            expanded_list += self.grid.get_neighborhood(i, moore=True, include_center=False)
        return list(set(expanded_list))

    def get_local_heat(self, pos):
        """ Global Horizontal Irradiance (without diffusive irradiance) from pole (lower border) to pole (upper border). model is torus! """
        neighborhood = self.grid.get_neighborhood(pos, moore=True, include_center=True)
        
        if self.heat_radius > 1: # if radius of local temperature is >1, this expand the position list.
            for i in range(self.heat_radius):
                neighborhood = self.expand_positionlist(neighborhood)

        heat = []
        for i in neighborhood:
            if self.grid.is_cell_empty(i): # empty cell: surface albedo
                heat.append(self.get_GNI(pos) * (1 - self.surface_albedo) )
            else:
                inhabitant = self.grid.get_cell_list_contents(i)[0] 
                heat.append(self.get_GNI(pos) * (1 - inhabitant.albedo) ) # cell with daisy
        return sum(heat)/ len(neighborhood)
class SupermarketModel(Model):
    def __init__(self, type=QueueType.CLASSIC, seed=None):
        np.random.seed(seed)

        # Mesa internals
        self.running = True
        self.steps_in_day = 7200

        # World related
        self.queue_type = QueueType[type]
        self.terrain_map_name = 'map' if self.queue_type == QueueType.CLASSIC else 'map_snake'
        with open(os.path.join(os.getcwd(), '..', 'resources', '{}.txt'.format(self.terrain_map_name))) as f:
            self.width, self.height = map(int, f.readline().strip().split(' '))
            self.capacity = int(f.readline().strip())
            self.world = [list(c) for c in f.read().split('\n') if c]

        self.grid = SingleGrid(self.width, self.height, True)

        # Agent related
        self.generated_customers_count = 0
        self.schedule = BaseScheduler(self)

        self.entry_points = []
        self.queues = {}
        self.queue_length_limit = 5
        self.cashiers = {}
        # TODO: Merge position (cash_registers) and open
        # attribute (open_cashier) with cashiers dict
        self.cash_registers = {}
        self.open_cashier = set()

        # Pathfinding
        self.finder = IDAStarFinder()
        self.snake_entry = None
        self.snake_exit = None

        # Populate grid from world
        for col, line in enumerate(self.world):
            for row, cell in enumerate(line):
                if cell == 'X':
                    self.grid[row][col] = ObstacleAgent('{}:{}'.format(col, row), self)
                elif cell == 'S':
                    self.snake_entry = (row, col)
                elif cell == 'Z':
                    self.snake_exit = (row, col)
                elif cell in ['1', '2', '3', '4', '5']:
                    cash_register = CashRegisterAgent(cell, self, (row, col))
                    self.cashier_row = col
                    self.cashiers[cell] = self.grid[row][col] = cash_register
                    self.cash_registers[cell] = (row, col)
                    self.queues[cell] = set()
                    # TODO: Add (remove) only upon cashier opening (closing)
                    self.schedule.add(cash_register)

                    cashier = CashierAgent('Y{}'.format(cell), self, (row + 1, col), cash_register)
                    self.grid[row + 1][col] = cashier
                elif cell in ['A', 'B', 'C', 'D', 'E']:
                    self.entry_points.append((row, col, cell))
                    self.spawn_row = col

        self.lane_switch_boundary = math.ceil((self.cashier_row - self.spawn_row) * 3 / 4)

        self.heatmap = np.zeros((self.height, self.width))

        world_matrix = np.matrix(self.world)
        self.distance_matrix = np.zeros((self.height, self.width))
        self.distance_matrix[world_matrix == 'X'] = np.inf
        self.distance_matrix[world_matrix == '1'] = np.inf
        self.distance_matrix[world_matrix == '2'] = np.inf
        self.distance_matrix[world_matrix == '3'] = np.inf
        self.distance_matrix[world_matrix == '4'] = np.inf
        self.distance_matrix[world_matrix == '5'] = np.inf
        self.distance_matrix[world_matrix == 'Y'] = np.inf

        self.floor_fields = {}
        for dest_label, (dest_col, dest_row) in self.cash_registers.items():
            floor_field = self.calculate_floor_field((dest_row, dest_col - 1))

            self.floor_fields[dest_label] = floor_field.copy()

            # Save floor field heatmap into file
            # floor_field[floor_field == np.inf] = -np.inf
            # plt.figure(figsize=(14, 14))
            # sns.heatmap(floor_field, vmin=0, fmt='.1f', vmax=np.max(floor_field), annot=True, cbar=False, square=True, cmap='mako', xticklabels=False, yticklabels=False)
            # plt.tight_layout()
            # plt.savefig(os.path.join('..', 'output', 'ff-heatmap{}.png'.format(dest_label)))
            # plt.close()

        self.datacollector = DataCollector(
            model_reporters={"Total": get_total_agents,
                             "Shopping": get_shopping_agents,
                             "Queued": get_queued_agents,
                             "Queued (AVG)": get_avg_queued_agents,
                             "Queued Time (AVG)": get_avg_queued_steps,
                             "Total Time (AVG)": get_avg_total_steps,
                             "Paying": get_paying_agents})

        if self.queue_type == QueueType.SNAKE:
            self.distance_matrix[world_matrix == 'X'] = 1
            self.distance_matrix[world_matrix == '1'] = 1
            self.distance_matrix[world_matrix == '2'] = 1
            self.distance_matrix[world_matrix == '3'] = 1
            self.distance_matrix[world_matrix == '4'] = 1
            self.distance_matrix[world_matrix == '5'] = 1
            self.distance_matrix[world_matrix == 'Y'] = 1
            self.movement_grid = Grid(matrix=self.distance_matrix, inverse=True)

        coin = self.random.randint(1, len(self.cashiers))
        self.cashiers[str(coin)].set_life()

        coin = self.random.randint(1, len(self.cashiers))
        while self.cashiers[str(coin)].open:
            coin = self.random.randint(1, len(self.cashiers))

        self.cashiers[str(coin)].set_life()

    def step(self):
        self.current_agents = len(self.schedule.agents) - len(self.cashiers.items())

        if self.schedule.steps > self.steps_in_day and get_total_agents(self) == 0 and (self.schedule.steps - 3) % 250:
            self.store_heatmap()
            self.running = False
            return

        if self.schedule.steps < self.steps_in_day and self.steps_in_day and self.schedule.steps % 25 == 0 and self.current_agents < self.capacity and self.should_spawn_agent():
            self.schedule.add(self.create_agent())

        self.datacollector.collect(self)
        self.schedule.step()

        self.adjust_cashiers()
        if self.queue_type == QueueType.SNAKE:
            self.assign_cash_register_to_customer()

    def adjust_cashiers(self):
        # self.current_agents > (len(opened) + 1) * self.capacity / 7
        # (len(opened) + 1) * self.queue_length_limit / self.current_agents

        opened, closed = self.partition(self.cashiers.values(), lambda c: c.open)
        if len(closed) > 0:
            if len(opened) < self.ideal_number_of_cashier(self.schedule.steps) and self.current_agents > (len(opened) + 1) * self.queue_length_limit:
                coin = self.random.randint(0, len(closed) - 1)
                cashier = closed[coin]
                cashier.set_life()
                self.open_cashier.add(cashier.unique_id)
                print(Back.WHITE + Fore.GREEN + 'OPENING NEW CASH_REGISTER: {}'.format(cashier.unique_id))

        if len(opened) > 2:
            np.random.shuffle(opened)
            if self.queue_type == QueueType.CLASSIC:
                for cashier in opened:
                    in_queue = len(self.queues[cashier.unique_id])
                    if (in_queue > 1 or in_queue == 0) and len(opened) > self.ideal_number_of_cashier(self.schedule.steps) and self.current_agents < (len(opened) + 1) * self.queue_length_limit:
                        self.close_cashier(cashier)
                        opened.remove(cashier)
                        break

            elif self.queue_type == QueueType.SNAKE:
                if len(opened) > self.ideal_number_of_cashier(self.schedule.steps) and self.current_agents < (len(opened) + 1) * self.queue_length_limit:
                    to_close = opened
                    if len(to_close) > 0:
                        coin = self.random.randint(0, len(to_close) - 1)
                        cashier = to_close[coin]

                        self.close_cashier(cashier)
                        opened.remove(cashier)
                        [cashier.set_life(cashier.remaining_life + 25) for cashier in opened]

    def assign_cash_register_to_customer(self):
        available, busy = self.partition(self.cashiers.values(), lambda c: c.open and not c.is_busy)
        if (not self.grid.is_cell_empty(self.snake_exit)) and len(available) > 0:
            customer = self.grid.get_cell_list_contents(self.snake_exit)[0]

            coin = self.random.randint(0, len(available) - 1)
            cashier = available[coin]

            customer.objective = cashier.unique_id
            dest_col, dest_row = cashier.pos
            customer.destination = (dest_col - 1, dest_row)
            cashier.is_busy = True
            print(Back.WHITE + Fore.BLACK + 'ASSIGNING CASH_REGISTER {} TO CUSTOMER {}'.format(coin, customer.unique_id))

    def store_heatmap(self):
        self.heatmap /= np.max(self.heatmap)
        sns.heatmap(self.heatmap, vmin=0, vmax=1)
        plt.savefig(os.path.join('..', 'output', 'heatmap{}.png'.format('' if self.queue_type == QueueType.CLASSIC else '-snake')))
        plt.close()

    def close_cashier(self, cashier):
        cashier.open = False
        cashier.empty_since = 0
        self.open_cashier.remove(cashier.unique_id)
        print(Back.WHITE + Fore.RED + 'CLOSING CASH_REGISTER: {}'.format(cashier.unique_id))

    def partition(self, elements, predicate):
        left, right = [], []
        for e in elements:
            (left if predicate(e) else right).append(e)

        return left, right

    def create_agent(self):
        agent = CustomerAgent(self.generated_customers_count, self, self.random_sprite())
        self.generated_customers_count += 1
        return agent

    def should_spawn_agent(self):
        relative_time = self.schedule.steps % self.steps_in_day
        prob = (-math.cos(relative_time * np.pi / (self.steps_in_day / 2) + 1) + 1) / 2
        return self.random.random() <= 0.85 if self.random.random() <= prob else False

    def ideal_number_of_cashier(self, step):
        prob = (step % self.steps_in_day) / self.steps_in_day

        # if prob <= 0.125 or prob >= 0.875:
        #     return 2
        # if prob <= 0.25 or prob >= 0.75:
        #     return 3
        # if prob <= 0.375 or prob >= 0.625:
        #     return 4
        # if prob <= 0.75 or prob >= 0.25:
        #     return 5

        if prob <= 0.265 or prob >= 0.88:
            return 2
        if prob <= 0.36 or prob >= 0.765:
            return 3
        if prob <= 0.44 or prob >= 0.66:
            return 4

        return 5

    def random_sprite(self):
        sprites = [
            'images/characters/grandpa3',
            'images/characters/man5',
            'images/characters/man8',
            'images/characters/girl',
            'images/characters/girl3',
            'images/characters/girl9',
        ]

        return sprites[self.random.randint(0, len(sprites) - 1)]

    def calculate_floor_field(self, destination):
        field = self.distance_matrix.copy()

        for row in range(len(field)):
            for col in range(len(field[row])):
                if not np.isinf(field[row, col]):
                    field[row, col] = distance.euclidean([row, col], destination)

        return field
Пример #4
0
class AgentKnowledgeMap():
    '''
    *** Constructor:
        Inputs:
               - height and width of the grid used by the AgSimulator

       Actions:
               - Construct navigationGrid
               - Construct planGrid
               - Create agent dictionaries
    '''
    def __init__(self, height, width, model):
        self.navigationGrid = SingleGrid(height, width, False)
        self.planGrid = MultiGrid(height, width, False)
        self.planAgents = defaultdict(list)
        self.perceptionAgents = {}
        self.model = model
        agent = FarmAgent(0, self.model.farmPos, self)
        self.navigationGrid.place_agent(agent, self.model.farmPos)
        self.attendancePoints = list()

    '''
    *** update function is used by each ActiveAgent to update ActiveAgentKnowledgeMap
        Input:
              - ActiveAgentPlanning objects are placed on planGrid
              - PassiveAgentPerception objects are placed on navigationGrid
    '''

    def update(self, agent):
        if (isinstance(agent, ActiveAgentPlanning)):
            self.planGrid.place_agent(agent, agent.pos)
            self.planAgents.setdefault(agent.unique_id, [])
            self.planAgents[agent.unique_id].append(agent)
        elif (isinstance(agent, PassiveAgentPerception)):
            if self.navigationGrid.is_cell_empty(agent.pos):
                self.navigationGrid.place_agent(agent, agent.pos)
                self.perceptionAgents[agent.unique_id] = agent
            else:
                existing_agent = self.navigationGrid.get_cell_list_contents(
                    agent.pos)[0]
                existing_agent.update(agent.state, agent.time_at_current_state)

    # This function is used for removing a step from the KnowledgeMap
    def removeOneStep(self, agentID):
        if self.planAgents[agentID]:
            self.planGrid.remove_agent(self.planAgents[agentID].pop(0))

    # This function is used for canceling the entire plan in case a collision is detected
    def cancelPlan(self, agentID):
        while len(self.planAgents[agentID]) > 0:
            self.planGrid.remove_agent(self.planAgents[agentID].pop(0))

    '''
    *** getGridStateAtStep returns a SingleGrid object with anticipated state of the grid at specified steps
        Input:
              - step for which the SingleGrid should be generated
        Output:
              - SingleGrid object with PassiveAgentPerception objects and ActiveAgentPlanning objects corresponding to chosen step
    '''

    def getGridStateAtStep(self, step=0):
        plan_agent_keys = [uid for uid, a in self.planAgents.items()]
        perception_agent_keys = [
            uid for uid, a in self.perceptionAgents.items()
        ]
        navGridAtStep = SingleGrid(self.navigationGrid.height,
                                   self.navigationGrid.width, False)
        for key in perception_agent_keys:
            navGridAtStep.place_agent(self.perceptionAgents[key],
                                      self.perceptionAgents[key].pos)
        for key in plan_agent_keys:
            for agent in self.planAgents[key]:
                if agent.steps_left == step and navGridAtStep.is_cell_empty(
                        agent.pos):
                    navGridAtStep.place_agent(agent, agent.pos)
        return navGridAtStep

    # This function is used to get a numpy array containing 0 and 1;
    # 0 for empty blocks at step X
    # 1 for any kind of agent at step X
    def getGridAtStepAsNumpyArray(self, step=0):
        plan_agent_keys = [uid for uid, a in self.planAgents.items()]
        perception_agent_keys = [
            uid for uid, a in self.perceptionAgents.items()
        ]
        return_numpy_array = numpy.zeros(
            (self.navigationGrid.width, self.navigationGrid.height),
            dtype='int8')
        for key in perception_agent_keys:
            return_numpy_array[self.perceptionAgents[key].pos[1],
                               self.perceptionAgents[key].pos[0]] = 1
        for agent_key in self.planAgents:
            agent_plans = self.planAgents[agent_key]
            if len(agent_plans) > 0 and len(agent_plans) >= step:
                for plan in agent_plans:
                    if plan.steps_left == step:
                        return_numpy_array[plan.pos[1], plan.pos[0]] = 1
            elif len(agent_plans) == 0:
                active_agent = self.model.schedule.getPassiveAgent(agent_key)
                return_numpy_array[active_agent.pos[1],
                                   active_agent.pos[0]] = 1
            else:
                return_numpy_array[agent_plans[-1].pos[1],
                                   agent_plans[-1].pos[0]] = 1
        return_numpy_array[self.model.farmPos[1], self.model.farmPos[0]] = 1
        return return_numpy_array
class modelSim(Model):
    """ 
    details of the world 
    
    introduce time is when animal agents first get introduced into the wrold
    disp_rate is the dispersal rate for experiment 3
    dist is perceptual strength for animals if fixed
    det is decision determinacy of animals if fixed
    cog_fixed determines if cognition of animals is fixed to particular values or is allowed to evolve
    if skip_300 is True, patchiness values are not calculated for the first 300 steps-- this makes the model run faster
    collect_cog_dist creates a seperate dataframe for all cognition values for agents at every timestep
    if evolve_disp is true, dispersion rate of plants is free to evolve
    """

    def __init__(self, introduce_time, disp_rate, dist, det, cog_fixed = False, \
                 skip_300 = True, collect_cog_dist = False, evolve_disp = False):

        self.skip_300 = skip_300
        self.cog_fixed = cog_fixed
        self.evolve_disp = evolve_disp
        self.collect_cog_dist = collect_cog_dist
        self.dist = dist
        self.det = det
        self.disp_rate = disp_rate
        self.intro_time = introduce_time
        (self.a1num, self.a2num) = (20, 20)
        self.schedule = RandomActivation(
            self)  # agents take a step in random order
        self.grid = SingleGrid(
            200, 200,
            True)  # the world is a grid with specified height and width

        self.initialize_perception()

        disp = np.power(self.disp_rate, range(0, 100))
        self.disp = disp / sum(disp)
        self.grid_ind = np.indices((200, 200))
        positions = np.maximum(abs(100 - self.grid_ind[0]),
                               abs(100 - self.grid_ind[1]))
        self.positions = np.minimum(positions, 200 - positions)

        self.agentgrid = np.zeros(
            (self.grid.width, self.grid.height
             ))  # allows for calculation of patchiness of both agents
        self.coggrid = np.full(
            (self.nCogPar, self.grid.width, self.grid.height), 101.0)
        self.dispgrid = np.full((2, self.grid.width, self.grid.height), 101.0)
        self.age = []
        (self.nstep, self.unique_id, self.reprod, self.food, self.death,
         self.combat) = (0, 0, 0, 0, 0, 0)

        self.cmap = colors.ListedColormap([
            'midnightblue', 'mediumseagreen', 'white', 'white', 'white',
            'white', 'white'
        ])  #'yellow', 'orange', 'red', 'brown'])
        bounds = [0, 1, 2, 3, 4, 5, 6, 7]
        self.norm = colors.BoundaryNorm(bounds, self.cmap.N)

        self.expect_NN = []
        self.NN = [5, 10]
        for i in self.NN:
            self.expect_NN.append(
                (math.factorial(2 * i) * i) / (2**i * math.factorial(i))**2)

        grid_ind_food = np.indices((21, 21))
        positions_food = np.maximum(abs(10 - grid_ind_food[0]),
                                    abs(10 - grid_ind_food[1]))
        self.positions_food = np.minimum(positions_food, 21 - positions_food)
        if self.collect_cog_dist:
            self.cog_dist_dist = pd.DataFrame(columns=[])
            self.cog_dist_det = pd.DataFrame(columns=[])

        for i in range(self.a1num):  # initiate a1 agents at random locations
            self.introduce_agents("A1")
        self.nA1 = self.a1num
        self.nA2 = 0
#     self.agent_steps = {}

    def initialize_perception(self):
        self.history = pd.DataFrame(columns=[
            "nA1", "nA2", "age", "LIP5", "LIP10", "LIPanim5", "LIPanim10",
            "Morsita5", "Morsita10", "Morsitaanim5", "Morsitaanim10", "NN5",
            "NN10", "NNanim5", "NNanim10", "reprod", "food", "death", "combat",
            "dist", "det", "dist_lower", "det_lower", "dist_upper",
            "det_upper", "dist_ci", "det_ci"
        ])
        self.nCogPar = 2
        (self.start_energy, self.eat_energy, self.tire_energy, self.reproduction_energy, self.cognition_energy) \
        = (10, 5, 3, 20, 1)

    def introduce_agents(self, which_agent):
        x = random.randrange(self.grid.width)
        y = random.randrange(self.grid.height)

        if which_agent == "A1":
            if self.grid.is_cell_empty((x, y)):
                a = A1(self.unique_id, self, self.start_energy, disp_rate=0)
                self.unique_id += 1
                self.grid.position_agent(a, x, y)
                self.schedule.add(a)
                self.agentgrid[x][y] = 1
            else:
                self.introduce_agents(which_agent)
        elif which_agent == "A2":
            if self.cog_fixed:
                c = (self.dist, self.det)
            else:
                c = tuple([0] * self.nCogPar)
            a = A2(self.unique_id,
                   self,
                   self.start_energy,
                   cognition=c,
                   disp_rate=0)
            self.unique_id += 1
            if self.agentgrid[x][y] == 1:
                die = self.grid.get_cell_list_contents([(x, y)])[0]
                die.dead = True
                self.grid.remove_agent(die)
                self.schedule.remove(die)
                self.grid.place_agent(a, (x, y))
                self.schedule.add(a)
                self.agentgrid[x][y] = 2
                self.coggrid[:, x, y] = c
            elif self.agentgrid[x][y] == 0:
                self.grid.place_agent(a, (x, y))
                self.schedule.add(a)
                self.agentgrid[x][y] = 2
                self.coggrid[:, x, y] = c

    def flatten_(self, n, grid, full_grid=False, mean=True, range_=False):
        if full_grid:
            return (grid[n].flatten())
        i = grid[n].flatten()
        if mean:
            i = np.delete(i, np.where(i == 101))
            if len(i) == 0:
                # if range_:
                return ([0] * 4)
            #else:
            #    return(0)
            if range_:
                if self.cog_fixed:
                    return ([np.mean(i)] * 4)
                return (np.concatenate(
                    ([np.mean(i)], np.percentile(i, [2.5, 97.5]),
                     self.calculate_ci(i))))
            return ([np.mean(i), 0, 0, 0])
        else:
            return (i)

    def calculate_ci(self, data):
        if np.min(data) == np.max(data):
            return ([0.0])
        return ([
            np.mean(data) - st.t.interval(
                0.95, len(data) - 1, loc=np.mean(data), scale=st.sem(data))[0]
        ])

    def return_zero(self, num, denom):
        if self.nstep == 1:
            #     print("whaaat")
            return (0)
        if denom == "old_nA2":
            denom = self.history["nA2"][self.nstep - 2]
        if denom == 0.0:
            return 0
        return (num / denom)

    def nearest_neighbor(self, agent):  # fix this later
        if agent == "a1":
            x = np.argwhere(self.agentgrid == 1)
            if len(x) <= 10:
                return ([-1] * len(self.NN))
            elif len(x) > 39990:
                return ([0.97, 0.99])
        #  if self.nstep<300 and self.skip_300:
        #      return([-1,-1] )
        else:
            x = np.argwhere(self.agentgrid == 2)
            if len(x) <= 10:
                return ([-1] * len(self.NN))
        density = len(x) / (self.grid.width)**2
        expect_NN_ = self.expect_NN
        expect_dist = np.array(expect_NN_) / (density**0.5)
        distances = [0, 0]
        for i in x:
            distx = abs(x[:, 0] - i[0])
            distx[distx > 100] = 200 - distx[distx > 100]
            disty = abs(x[:, 1] - i[1])
            disty[disty > 100] = 200 - disty[disty > 100]
            dist = (distx**2 + disty**2)**0.5
            distances[0] += (np.partition(dist, 5)[5])
            distances[1] += (np.partition(dist, 10)[10])
        mean_dist = np.array(distances) / len(x)
        out = mean_dist / expect_dist
        return (out)

    def quadrant_patch(
        self, agent
    ):  # function to calculate the patchiness index of agents at every step
        if agent == "a1":
            x = self.agentgrid == 1
        else:
            x = self.agentgrid == 2
        gsize = np.array([5, 10])
        gnum = 200 / gsize
        qcs = []
        for i in range(2):
            x_ = x.reshape(int(gnum[i]), gsize[i], int(gnum[i]),
                           gsize[i]).sum(1).sum(2)
            mean = np.mean(x_)
            var = np.var(x_)
            if mean == 0.0:
                return ([-1] * 4)
            lip = 1 + (var - mean) / (mean**2)
            morsita = np.sum(x) * ((np.sum(np.power(x_, 2)) - np.sum(x_)) /
                                   (np.sum(x_)**2 - np.sum(x_)))
            qcs += [lip, morsita]
        return (qcs)

    def l_function(self, agent):
        if agent == "a1":
            x = np.argwhere(self.agentgrid == 1)
        else:
            x = np.argwhere(self.agentgrid == 2)
            if len(x) == 0:
                return (-1)
        distances = np.array([])
        for i in x:
            distx = abs(x[:, 0] - i[0])
            distx[distx > 100] = 200 - distx[distx > 100]
            disty = abs(x[:, 1] - i[1])
            disty[disty > 100] = 200 - disty[disty > 100]
            dist = (distx**2 + disty**2)**0.5
            distances = np.concatenate((distances, dist[dist != 0]))
        l = np.array([])
        for i in np.arange(5, 51, 5):
            l = np.append(l, sum(distances < i))
        k = (l * 200**2) / (len(x)**2)
        l = (k / math.pi)**0.5
        return (abs(l - np.arange(5, 51, 5)))

    def collect_hist(self):
        if self.nstep < 300 and self.skip_300:
            NNcalc = [-1, -1]  #self.nearest_neighbor("a1")
            NNanimcalc = [-1, -1]  #self.nearest_neighbor("a2")
        else:
            NNcalc = self.nearest_neighbor("a1")
            NNanimcalc = self.nearest_neighbor("a2")
        quadrantcalc = self.quadrant_patch("a1")
        quadrantanimcalc = self.quadrant_patch("a2")
        dist_values = self.flatten_(0,
                                    grid=self.coggrid,
                                    mean=True,
                                    range_=False)
        det_values = self.flatten_(1,
                                   grid=self.coggrid,
                                   mean=True,
                                   range_=False)
        # l_f = 0#self.l_function("a1")
        dat = {
            "nA1": self.nA1,
            "nA2": self.nA2,
            "age": self.return_zero(sum(self.age), self.nA2),
            "LIP5": quadrantcalc[0],
            "LIP10": quadrantcalc[2],
            "LIPanim5": quadrantanimcalc[0],
            "LIPanim10": quadrantanimcalc[2],
            "Morsita5": quadrantcalc[1],
            "Morsita10": quadrantcalc[3],
            "Morsitaanim5": quadrantanimcalc[1],
            "Morsitaanim10": quadrantanimcalc[3],
            "NN5": NNcalc[0],
            "NN10": NNcalc[1],
            "NNanim5": NNanimcalc[0],
            "NNanim10":
            NNanimcalc[1],  #"l_ripley" : l_f,# self.nearest_neighbor("a2"),  
            "reprod": self.return_zero(self.reprod, "old_nA2"),
            "food": self.return_zero(self.food, self.nA2),
            "death": self.return_zero(self.death, "old_nA2"),
            "combat": self.return_zero(self.combat, "old_nA2"),
            "dist": dist_values[0],
            "det": det_values[0],
            "dist_lower": dist_values[1],
            "det_lower": det_values[1],
            "dist_upper": dist_values[2],
            "det_upper": det_values[2],
            "dist_ci": dist_values[3],
            "det_ci": det_values[3],
            "disp_a1": self.flatten_(0, grid=self.dispgrid)[0],
            "disp_a2": self.flatten_(1, grid=self.dispgrid)[0]
        }
        self.history = self.history.append(dat, ignore_index=True)
        self.age = []
        (self.reprod, self.food, self.death, self.combat) = (0, 0, 0, 0)
        if self.collect_cog_dist:
            if (self.nstep % 10) == 0:
                self.cog_dist_dist[str(self.nstep - 1)] = self.flatten_(
                    0, grid=self.coggrid, full_grid=True, mean=False)
                self.cog_dist_det[str(self.nstep - 1)] = self.flatten_(
                    1, grid=self.coggrid, full_grid=True, mean=False)

    def step(self):
        self.nstep += 1  # step counter
        if self.nstep == self.intro_time:
            for i in range(self.a2num):
                self.introduce_agents("A2")
        self.schedule.step()
        self.nA1 = np.sum(self.agentgrid == 1)
        self.nA2 = np.sum(self.agentgrid == 2)
        self.collect_hist()
        if self.nstep % 10 == 0:
            sys.stdout.write((str(self.nstep) + " " + str(self.nA1) + " " +
                              str(self.nA2) + "\n"))

    def visualize(self):
        f, ax = plt.subplots(1)
        self.agentgrid = self.agentgrid.astype(int)
        ax.imshow(self.agentgrid,
                  interpolation='nearest',
                  cmap=self.cmap,
                  norm=self.norm)
        # plt.axis("off")
        return (f)