<reponame>mahetoodang/abm<filename>functionality/agent.py
from mesa import Agent
import random
import numpy as np

from .path_finder import find_path
from .cell import Cell


class Human(Agent):
    '''
    Human class that keeps track of the agent's: id, home location,
    current position, maximum travel time, travel speed, character (score),
    current path, possible trip destinations and whether the agent is
    currently interacting.
    '''

    def __init__(self, unique_id, model, pos, character, speed):
        super().__init__(unique_id, model)
        self.pos = pos
        self.home = pos
        self.max_travel_time = np.random.randint(5, 10)
        self.speed = speed
        self.character = character
        self.interaction = False
        self.path = []
        self.destinations = self.find_possible_destionations()

    def step(self):
        '''
        Execute step (move/interact/create new trip/go home).
        '''

        # create new trip if home
        if self.is_home():
            self.create_trip()

        # move if interacting and trip not completed
        if self.interaction and len(self.path):
            self.move()
        # return home if trip completed
        else:
            if not len(self.path):
                self.go_home()

        # interact with neighbors if not yet interacting
        if not self.interaction:
            self.interact_with_neighbors()

        # move if still not interacting and trip not completed
        if not self.interaction and len(self.path):
            self.move()

    def get_friends(self):
        '''
        Returns list of friends.
        '''
        others = self.model.friends_score[self.unique_id]
        ids = others[others > 0].index
        friends = []
        for agent in self.model.schedule.agents:
            if agent.unique_id in ids:
                friends.append(agent)
        return friends

    def get_distance(self, point):
        '''
        Returns manhattan distance from current position to point on grid.
        '''

        return np.abs(self.pos[0] - point[0]) + np.abs(self.pos[1] - point[1])

    def get_avg(self):
        '''
        Returns average friends score, spatial distance, social distance.
        '''

        friends = self.get_friends()

        count = len(friends)
        score_sum = 0
        social_sum = 0
        spatial_sum = 0

        if not count:
            avg_score = score_sum
            avg_social = social_sum
            avg_spatial = spatial_sum
        else:
            for friend in friends:
                score_sum += self.model.friends_score.at[self.unique_id, friend.unique_id]
                social_sum += abs(self.character - friend.character)
                spatial_sum += self.get_distance(friend.pos)
            # Average
            avg_score = score_sum/count
            avg_social = social_sum/count
            avg_spatial = spatial_sum/count

        return avg_score, avg_social, avg_spatial, count

    def interact_with_neighbors(self):
        '''
        Interact with other Human agents on same cell.
        '''

        # get agents on current position
        neighbors = self.model.grid.get_cell_list_contents([self.pos])
        for neighbor in neighbors:

            # if other human agent on current position
            if self.unique_id != neighbor.unique_id and type(neighbor) is Human:

                # matrix indexing
                i = np.max([self.unique_id, neighbor.unique_id])
                j = np.min([self.unique_id, neighbor.unique_id])

                # social distance & suitability
                character_dist = abs(self.character - neighbor.character)
                suitability = 1 - np.abs(character_dist)
                social_introversion = 1 - self.model.social_extroversion

                # interact
                if random.uniform(social_introversion, 1) < suitability:
                    # reset 'last interaction' count (decay)
                    self.model.last_interaction.values[i-1,j-1] = 0
                    self.model.last_interaction.values[j-1, i-1] = 0

                    # update friends score
                    rand_suit = random.random() * suitability
                    self.model.friends_score.values[i-1, j-1] += rand_suit
                    self.model.friends_score.values[j-1, i-1] += rand_suit

                    # update cell values if running with social hubs
                    if self.model.hubs:
                        cell = self.get_cell()
                        cell.update(self.character, neighbor.character)

                self.model.interactions[i][j] += 1
                break

    def get_cell(self):
        '''
        Returns cell for current position
        '''
        this_cell = self.model.grid.get_cell_list_contents([self.pos])
        for agent in this_cell:
            if type(agent) is Cell:
                return agent

    def create_trip(self):
        '''
        Generates path/trip to random destination.
        '''
        bounds = self.get_relative_bounds()

        path = False
        while not path:
            # get all possible destination cells
            cells = []
            for pos in self.destinations:
                this_cell = self.model.grid.get_cell_list_contents([pos])
                for agent in this_cell:
                    if type(agent) is Cell:
                        cells.append(agent)

            # weighted random choice based on cell value if running with social hubs
            if self.model.hubs:
                value_sum = sum((1 - abs(self.character - cell.value)) for cell in cells)
                w = [(1 - abs(self.character - cell.value)) / value_sum for cell in cells]
                selected_cell = random.choices(population=cells, weights=w, k=1)
                chosen_trip = selected_cell[0].pos
            else:
                selected_cell = random.choice(cells)
                chosen_trip = selected_cell.pos

            # find path to chosen destination
            destination = [
                chosen_trip[0] - self.pos[0],
                chosen_trip[1] - self.pos[1]
            ]
            trip_length = np.abs(destination[0]) + np.abs(destination[1])
            path = find_path([0, 0], destination, trip_length + 2, bounds)

        self.path = path

    def go_home(self):
        '''
        Generate path/trip from current location to home.
        '''

        bounds = self.get_relative_bounds()
        destination = self.relative_home_location()
        trip_length = np.abs(destination[0]) + np.abs(destination[1])
        path = find_path([0, 0], destination, trip_length + 2, bounds)
        self.path = path

    def has_friends(self):
        '''
        Returns True if agent has friends.
        '''

        friend_count = self.nr_friends()
        return friend_count > 0

    def nr_friends(self):
        '''
        Returns number of friends.
        '''

        friends_df = self.model.friends
        uid = self.unique_id
        friend_count = np.sum(friends_df[uid].values) + np.sum(friends_df.loc[uid].values)
        return friend_count

    def is_home(self):
        '''
        Returns True if agent is home.
        '''
        return self.pos[0] == self.home[0] and self.pos[1] == self.home[1]

    def relative_home_location(self):
        '''
        Returns location relative to home location.
        '''
        x = self.home[0] - self.pos[0]
        y = self.home[1] - self.pos[1]
        return [x, y]

    def move(self):
        '''
        Perform next move/stap in path.
        '''
        i = 0
        while i < self.speed:
            if len(self.path):
                move = self.path.pop(0)
                new_pos = (
                    self.pos[0] + move[0],
                    self.pos[1] + move[1]
                )
                self.model.grid.move_agent(self, new_pos)
                i += 1
            else:
                break

    def get_relative_bounds(self):
        '''
        Returns grid bounds relative to current position.
        '''
        # returns [ [min_x, min_y], [max_x, max_y] ]
        # showing the moving limits of the agents
        [min_x, min_y] = [0, 0]
        [max_x, max_y] = [self.model.grid.width-1, self.model.grid.height-1]
        pos = self.pos
        bounds = [
            [min_x - pos[0], min_y - pos[1]],
            [max_x - pos[0], max_y - pos[1]]
        ]
        return bounds

    def is_inside(self, p, max_x, max_y):
        '''
        Checks if coordinate p is within grid.
        '''

        x_in = 0 <= p[0] <= max_x
        y_in = 0 <= p[1] <= max_y
        return x_in and y_in

    def find_manhattan_neighbors(self, radius):
        '''
        Returns coordinates of neighboring cells.
        '''

        neighbors = []
        pos = self.pos
        [max_x, max_y] = [self.model.grid.width - 1, self.model.grid.height - 1]
        for i in range(radius+1):
            if i != 0:
                p = [i + pos[0], radius - i + pos[1]]
                if self.is_inside(p, max_x, max_y):
                    neighbors.append(p)
                p = [-i + pos[0], radius - i + pos[1]]
                if self.is_inside(p, max_x, max_y):
                    neighbors.append(p)
                if (radius - i) != 0:
                    p = [i + pos[0], -(radius - i) + pos[1]]
                    if self.is_inside(p, max_x, max_y):
                        neighbors.append(p)
                    p = [-i + pos[0], -(radius - i) + pos[1]]
                    if self.is_inside(p, max_x, max_y):
                        neighbors.append(p)
            else:
                p = [i + pos[0], radius - i + pos[1]]
                if self.is_inside(p, max_x, max_y):
                    neighbors.append(p)
                p = [i + pos[0], -(radius - i) + pos[1]]
                if self.is_inside(p, max_x, max_y):
                    neighbors.append(p)
        return neighbors

    def find_possible_destionations(self):
        '''
        Returns coordinates of all possible destinations within range (based on
        speed and maximum travel time).
        '''

        min_trip_length = self.speed
        max_trip_length = self.speed * self.max_travel_time
        destinations = []
        for i in range(min_trip_length, max_trip_length):
            destinations.extend(self.find_manhattan_neighbors(i))
        return destinations

    def random_move(self):
        '''
        Execute random move.
        '''
        grid = self.model.grid
        neighborhood = grid.get_neighborhood(self.pos, True, radius=1)
        grid.move_agent(self, random.choice(neighborhood))

from mesa import Agent
import random
import numpy as np

from .path_finder import find_path
from .cell import Cell


class Human(Agent):
    '''
    Human class that keeps track of the agent's: id, home location,
    current position, maximum travel time, travel speed, character (score),
    current path, possible trip destinations and whether the agent is
    currently interacting.
    '''

    def __init__(self, unique_id, model, pos, character, speed):
        super().__init__(unique_id, model)
        self.pos = pos
        self.home = pos
        self.max_travel_time = np.random.randint(5, 10)
        self.speed = speed
        self.character = character
        self.interaction = False
        self.path = []
        self.destinations = self.find_possible_destionations()

    def step(self):
        '''
        Execute step (move/interact/create new trip/go home).
        '''

        # create new trip if home
        if self.is_home():
            self.create_trip()

        # move if interacting and trip not completed
        if self.interaction and len(self.path):
            self.move()
        # return home if trip completed
        else:
            if not len(self.path):
                self.go_home()

        # interact with neighbors if not yet interacting
        if not self.interaction:
            self.interact_with_neighbors()

        # move if still not interacting and trip not completed
        if not self.interaction and len(self.path):
            self.move()

    def get_friends(self):
        '''
        Returns list of friends.
        '''
        others = self.model.friends_score[self.unique_id]
        ids = others[others > 0].index
        friends = []
        for agent in self.model.schedule.agents:
            if agent.unique_id in ids:
                friends.append(agent)
        return friends

    def get_distance(self, point):
        '''
        Returns manhattan distance from current position to point on grid.
        '''

        return np.abs(self.pos[0] - point[0]) + np.abs(self.pos[1] - point[1])

    def get_avg(self):
        '''
        Returns average friends score, spatial distance, social distance.
        '''

        friends = self.get_friends()

        count = len(friends)
        score_sum = 0
        social_sum = 0
        spatial_sum = 0

        if not count:
            avg_score = score_sum
            avg_social = social_sum
            avg_spatial = spatial_sum
        else:
            for friend in friends:
                score_sum += self.model.friends_score.at[self.unique_id, friend.unique_id]
                social_sum += abs(self.character - friend.character)
                spatial_sum += self.get_distance(friend.pos)
            # Average
            avg_score = score_sum/count
            avg_social = social_sum/count
            avg_spatial = spatial_sum/count

        return avg_score, avg_social, avg_spatial, count

    def interact_with_neighbors(self):
        '''
        Interact with other Human agents on same cell.
        '''

        # get agents on current position
        neighbors = self.model.grid.get_cell_list_contents([self.pos])
        for neighbor in neighbors:

            # if other human agent on current position
            if self.unique_id != neighbor.unique_id and type(neighbor) is Human:

                # matrix indexing
                i = np.max([self.unique_id, neighbor.unique_id])
                j = np.min([self.unique_id, neighbor.unique_id])

                # social distance & suitability
                character_dist = abs(self.character - neighbor.character)
                suitability = 1 - np.abs(character_dist)
                social_introversion = 1 - self.model.social_extroversion

                # interact
                if random.uniform(social_introversion, 1) < suitability:
                    # reset 'last interaction' count (decay)
                    self.model.last_interaction.values[i-1,j-1] = 0
                    self.model.last_interaction.values[j-1, i-1] = 0

                    # update friends score
                    rand_suit = random.random() * suitability
                    self.model.friends_score.values[i-1, j-1] += rand_suit
                    self.model.friends_score.values[j-1, i-1] += rand_suit

                    # update cell values if running with social hubs
                    if self.model.hubs:
                        cell = self.get_cell()
                        cell.update(self.character, neighbor.character)

                self.model.interactions[i][j] += 1
                break

    def get_cell(self):
        '''
        Returns cell for current position
        '''
        this_cell = self.model.grid.get_cell_list_contents([self.pos])
        for agent in this_cell:
            if type(agent) is Cell:
                return agent

    def create_trip(self):
        '''
        Generates path/trip to random destination.
        '''
        bounds = self.get_relative_bounds()

        path = False
        while not path:
            # get all possible destination cells
            cells = []
            for pos in self.destinations:
                this_cell = self.model.grid.get_cell_list_contents([pos])
                for agent in this_cell:
                    if type(agent) is Cell:
                        cells.append(agent)

            # weighted random choice based on cell value if running with social hubs
            if self.model.hubs:
                value_sum = sum((1 - abs(self.character - cell.value)) for cell in cells)
                w = [(1 - abs(self.character - cell.value)) / value_sum for cell in cells]
                selected_cell = random.choices(population=cells, weights=w, k=1)
                chosen_trip = selected_cell[0].pos
            else:
                selected_cell = random.choice(cells)
                chosen_trip = selected_cell.pos

            # find path to chosen destination
            destination = [
                chosen_trip[0] - self.pos[0],
                chosen_trip[1] - self.pos[1]
            ]
            trip_length = np.abs(destination[0]) + np.abs(destination[1])
            path = find_path([0, 0], destination, trip_length + 2, bounds)

        self.path = path

    def go_home(self):
        '''
        Generate path/trip from current location to home.
        '''

        bounds = self.get_relative_bounds()
        destination = self.relative_home_location()
        trip_length = np.abs(destination[0]) + np.abs(destination[1])
        path = find_path([0, 0], destination, trip_length + 2, bounds)
        self.path = path

    def has_friends(self):
        '''
        Returns True if agent has friends.
        '''

        friend_count = self.nr_friends()
        return friend_count > 0

    def nr_friends(self):
        '''
        Returns number of friends.
        '''

        friends_df = self.model.friends
        uid = self.unique_id
        friend_count = np.sum(friends_df[uid].values) + np.sum(friends_df.loc[uid].values)
        return friend_count

    def is_home(self):
        '''
        Returns True if agent is home.
        '''
        return self.pos[0] == self.home[0] and self.pos[1] == self.home[1]

    def relative_home_location(self):
        '''
        Returns location relative to home location.
        '''
        x = self.home[0] - self.pos[0]
        y = self.home[1] - self.pos[1]
        return [x, y]

    def move(self):
        '''
        Perform next move/stap in path.
        '''
        i = 0
        while i < self.speed:
            if len(self.path):
                move = self.path.pop(0)
                new_pos = (
                    self.pos[0] + move[0],
                    self.pos[1] + move[1]
                )
                self.model.grid.move_agent(self, new_pos)
                i += 1
            else:
                break

    def get_relative_bounds(self):
        '''
        Returns grid bounds relative to current position.
        '''
        # returns [ [min_x, min_y], [max_x, max_y] ]
        # showing the moving limits of the agents
        [min_x, min_y] = [0, 0]
        [max_x, max_y] = [self.model.grid.width-1, self.model.grid.height-1]
        pos = self.pos
        bounds = [
            [min_x - pos[0], min_y - pos[1]],
            [max_x - pos[0], max_y - pos[1]]
        ]
        return bounds

    def is_inside(self, p, max_x, max_y):
        '''
        Checks if coordinate p is within grid.
        '''

        x_in = 0 <= p[0] <= max_x
        y_in = 0 <= p[1] <= max_y
        return x_in and y_in

    def find_manhattan_neighbors(self, radius):
        '''
        Returns coordinates of neighboring cells.
        '''

        neighbors = []
        pos = self.pos
        [max_x, max_y] = [self.model.grid.width - 1, self.model.grid.height - 1]
        for i in range(radius+1):
            if i != 0:
                p = [i + pos[0], radius - i + pos[1]]
                if self.is_inside(p, max_x, max_y):
                    neighbors.append(p)
                p = [-i + pos[0], radius - i + pos[1]]
                if self.is_inside(p, max_x, max_y):
                    neighbors.append(p)
                if (radius - i) != 0:
                    p = [i + pos[0], -(radius - i) + pos[1]]
                    if self.is_inside(p, max_x, max_y):
                        neighbors.append(p)
                    p = [-i + pos[0], -(radius - i) + pos[1]]
                    if self.is_inside(p, max_x, max_y):
                        neighbors.append(p)
            else:
                p = [i + pos[0], radius - i + pos[1]]
                if self.is_inside(p, max_x, max_y):
                    neighbors.append(p)
                p = [i + pos[0], -(radius - i) + pos[1]]
                if self.is_inside(p, max_x, max_y):
                    neighbors.append(p)
        return neighbors

    def find_possible_destionations(self):
        '''
        Returns coordinates of all possible destinations within range (based on
        speed and maximum travel time).
        '''

        min_trip_length = self.speed
        max_trip_length = self.speed * self.max_travel_time
        destinations = []
        for i in range(min_trip_length, max_trip_length):
            destinations.extend(self.find_manhattan_neighbors(i))
        return destinations

    def random_move(self):
        '''
        Execute random move.
        '''
        grid = self.model.grid
        neighborhood = grid.get_neighborhood(self.pos, True, radius=1)
        grid.move_agent(self, random.choice(neighborhood))
Human class that keeps track of the agent's: id, home location,     current position, maximum travel time, travel speed, character (score),     current path, possible trip destinations and whether the agent is     currently interacting. Execute step (move/interact/create new trip/go home). # create new trip if home # move if interacting and trip not completed # return home if trip completed # interact with neighbors if not yet interacting # move if still not interacting and trip not completed Returns list of friends. Returns manhattan distance from current position to point on grid. Returns average friends score, spatial distance, social distance. # Average Interact with other Human agents on same cell. # get agents on current position # if other human agent on current position # matrix indexing # social distance & suitability # interact # reset 'last interaction' count (decay) # update friends score # update cell values if running with social hubs Returns cell for current position Generates path/trip to random destination. # get all possible destination cells # weighted random choice based on cell value if running with social hubs # find path to chosen destination Generate path/trip from current location to home. Returns True if agent has friends. Returns number of friends. Returns True if agent is home. Returns location relative to home location. Perform next move/stap in path. Returns grid bounds relative to current position. # returns [ [min_x, min_y], [max_x, max_y] ] # showing the moving limits of the agents Checks if coordinate p is within grid. Returns coordinates of neighboring cells. Returns coordinates of all possible destinations within range (based on         speed and maximum travel time). Execute random move.