Ejemplo n.º 1
0
class SimulationModel(Model):
    def __init__(self, x_max, y_max, species, iterations):
        super(SimulationModel, self).__init__()

        self.starved = 0
        self.space = ContinuousSpace(x_max,
                                     y_max,
                                     grid_width=20,
                                     grid_height=20,
                                     torus=True)
        self.schedule = SimultaneousActivation(self)

        self.iterations = iterations

        self.species = []
        self.create_population(species)

    def create_population(self, species):
        for specie, lamarck, params in species:
            individuals = []
            for param in params:
                individuals.append(
                    self.create_individual(specie, lamarck, param))
                self.schedule.add(individuals[-1])
            self.species.append(individuals)

    def create_individual(self, specie, lamarck, param):
        x = random.random() * self.space.x_max
        y = random.random() * self.space.y_max
        ind = specie(self.space, x, y, lamarck, param)
        self.space.place_agent(ind, ind.pos)
        return ind

    def step(self):
        self.schedule.step()
        self.cleanup_corpses()
        self.update_iterations()

    def update_iterations(self):
        self.iterations -= 1
        if not self.iterations:
            self.running = False

    def cleanup_corpses(self):
        for agent in filter(t_matcher(AutonomicAgent), self.schedule.agents):
            if agent.energy <= 0:
                self.schedule.remove(agent)
                # noinspection PyProtectedMember
                self.space._remove_agent(agent.pos, agent)
                self.starved += 1

    def results(self):
        def get_energy(individual):
            return individual.eaten, individual.energy

        def get_energies(specie):
            return map(get_energy, specie)

        return map(get_energies, self.species)
Ejemplo n.º 2
0
class Company(Model):
    """
    Model of a company; pyramidal structure with n different levels;
    workers at different levels weigh differently on the efficiency of the
    company as a whole.

    In the paper, the default parameters are
        level_sizes = [1, 5, 11, 21, 41, 81] (for a total of 160 employees)
        level_weights = [1.0, 0.9, 0.8, 0.6, 0.4, 0.2]
    """
    def __init__(self,
                 level_sizes=(1, 5, 10, 30),
                 level_weights=(1.0, 0.8, 0.5, 0.2),
                 age_distribution=default_age_dist,
                 competency_distribution=default_competency_dist,
                 dismissal_threshold=4,
                 retirement_age=65,
                 timestep_years=1 / 12.,
                 initial_vacancy_fraction=0.2,
                 p_employee_leaves=1 / 24.,
                 competency_mechanism='common_sense',
                 promotion_strategy='best'):

        assert len(level_sizes) == len(level_weights), \
            "Incompatible dimensions (level sizes and weights)"

        assert hasattr(age_distribution, 'rvs'), \
            "age_distribution must have a rvs method returning random values"

        assert hasattr(competency_distribution, 'rvs'), \
            "competency_distribution must have a rvs method returning random values"

        assert promotion_strategy in ['best', 'worst', 'random'], \
            "Unrecognized promotion_strategy"

        assert competency_mechanism in ['common_sense', 'peter'], \
            "Unrecognized competency_mechanism"

        super().__init__()

        # Not the best way to pack all the constants I have
        self.level_sizes = level_sizes
        self.level_weights = level_weights
        self.age_distribution = age_distribution
        self.competency_distribution = competency_distribution
        self.dismissal_threshold = dismissal_threshold
        self.retirement_age = retirement_age
        self.timestep_years = timestep_years
        self.initial_vacancy_fraction = initial_vacancy_fraction
        self.dist_employees_leaving = bernoulli(p=p_employee_leaves)
        self.competency_mechanism = competency_mechanism
        self.promotion_strategy = promotion_strategy

        self.current_id = 0
        self.schedule = SimultaneousActivation(self)

        # Create agents
        self.levels = []
        for level_size in level_sizes:
            level = []
            n_initial_agents = binom(n=level_size,
                                     p=1 -
                                     self.initial_vacancy_fraction).rvs()
            n_initial_agents = max(1, n_initial_agents)
            for i in range(n_initial_agents):
                agent = Employee(self.next_id(), self)
                self.schedule.add(agent)
                level.append(agent)
            self.levels.append(level)

        # Initialize data collection
        self.data_collector = DataCollector(
            model_reporters={'efficiency': calculate_efficiency})

    def remove_employees(self):
        for level in self.levels:
            for employee in level:
                if employee.has_to_go:
                    level.remove(employee)
                    self.schedule.remove(employee)

    def pick_for_promotion_from(self, source_level):
        if self.promotion_strategy == 'best':
            return max(source_level, key=lambda e: e.competency)
        elif self.promotion_strategy == 'worst':
            return min(source_level, key=lambda e: e.competency)
        elif self.promotion_strategy == 'random':
            return self.random.choice(source_level)

    def recalculate_competency(self, employee):
        # Peter hypothesis: competency in new role not dependent on competency
        #                   in previous role
        if self.competency_mechanism == 'peter':
            employee.competency = self.competency_distribution.rvs()
        # Common-sense: competency mostly transferable from previous role
        elif self.competency_mechanism == 'common_sense':
            # TODO: abstract parameters used here
            random_variation = self.random.uniform(-1, 1)
            employee.competency += random_variation
            employee.competency = min(max(0, employee.competency), 10)  # Clip

    def promote_employees(self):
        for upper_level, lower_level, target_size in zip(
                self.levels, self.levels[1:], self.level_sizes):
            while len(upper_level) < target_size and len(lower_level):
                chosen_employee = self.pick_for_promotion_from(lower_level)
                lower_level.remove(chosen_employee)
                self.recalculate_competency(chosen_employee)
                upper_level.append(chosen_employee)

    def hire_employees(self):
        bottom_level = self.levels[-1]
        bottom_level_target_size = self.level_sizes[-1]
        vacant_bottom_positions = bottom_level_target_size - len(bottom_level)

        for i in range(vacant_bottom_positions):
            agent = Employee(self.next_id(), self)
            self.schedule.add(agent)
            bottom_level.append(agent)

    def step(self):
        self.data_collector.collect(self)
        self.schedule.step()
        self.remove_employees()
        self.promote_employees()
        self.hire_employees()
Ejemplo n.º 3
0
class PandemicsModel(Model):
    def __init__(self, config=default_config, disease=dis.covid_disease):
        self.agents_count = config.citizens_count + config.policemen_count
        self.disease = disease
        self.deceased = []
        self.buried = []
        self.deceased_counter = 0
        self.infected_counter = 0
        self.grid = MultiGrid(config.width, config.height, True)
        self.safety_per_cell = np.ones((config.height, config.width))
        self.buildings_map = np.zeros((config.height, config.width))
        self.buildings_id_map = np.zeros((config.height, config.width))
        self.schedule = SimultaneousActivation(self)
        self.datacollector = DataCollector(
            model_reporters={
                "deceased": "deceased_counter",
                "infected": "infected_counter"},
            agent_reporters={
                "hp": lambda a: a.profile["hp"],
                "mask_protection": "mask_protection",
                "infection_day": lambda a: a.profile["infection_day"],
                "obedience": lambda a: a.profile["obedience"],
                "fear": lambda a: a.profile["fear"]}
        )
        self.config = config
        self.buildings = {b["id"] : b for b in self.config.buildings}
        self.houses = [x for x in self.buildings.values() if x['type'] == 'house']
        self.workplaces = [x for x in self.buildings.values() if x['type'] == 'workplace']
        self.shops = [x for x in self.buildings.values() if x['type'] == 'shop']

        self.add_buildings_to_map(self.buildings)

        self.street_positions = []
        for x in range(self.config.width):
            for y in range(self.config.height):
                if self.buildings_map[y][x] == 0:
                    self.street_positions.append((x, y))

        self.house_to_agents = defaultdict(list)
        self.workplace_to_agents = defaultdict(list)

        self.current_location_type = None

        # Create agents
        for i in range(self.agents_count):
            if i < config.policemen_count:
                a = agent.create_distribution_policeman_agent(
                        i, self, config.policemen_mental_features_distribution)
                a.assign_house(self, self.houses)
            elif i < config.policemen_count + config.citizens_count:
                a = agent.create_distribution_citizen_agent(
                        i, self, config.citizens_mental_features_distribution)
                a.assign_house(self, self.houses)
                a.assign_workplace(self, self.workplaces)
            self.add_agent(a)

        for i in self.random.choices(self.schedule.agents, k=config.infected_count):
            i.start_infection()
        self.running = True
        self.steps_count = 0
        self.datacollector.collect(self)

    # Returns (type, id) of the building where agent a is currently located
    def where_is_agent(self, a):
        (x, y) = a.pos
        return (self.buildings_map[y][x], self.buildings_id_map[y][x])

    def compute_time_of_day(self):
        return self.steps_count % self.config.steps_per_day / (self.config.steps_per_day / HOURS_PER_DAY)

    def compute_current_location_type(self):
        t = self.compute_time_of_day()
        return self.config.day_plan[t] if t in self.config.day_plan else \
                self.config.day_plan[min(self.config.day_plan.keys(), key=lambda k: k-t)]

    # Updates current location type based on time of day and the config schedule
    # Returns true if there is a change in current_location_type
    def update_current_location_type(self):
        t = self.compute_time_of_day()
        if t in self.config.day_plan:
            self.current_location_type = self.config.day_plan[t]
            return True
        return False

    def add_buildings_to_map(self, buildings):
        for b in buildings.values():
            (x, y) = b["bottom-left"]
            for i in range(x, x+b["width"]):
                for j in range(y, y+b["height"]):
                    self.buildings_map[j][i] = self.config.building_tags[b['type']]
                    self.buildings_id_map[j][i] = b['id']

    def add_agent(self, a):
        self.schedule.add(a)
        # Add the agent to a random grid cell
        x = self.random.randrange(self.grid.width)
        y = self.random.randrange(self.grid.height)
        self.grid.place_agent(a, (x, y))

    def bury_agent(self, a):
        self.schedule.remove(a)
        self.grid.remove_agent(a)
        self.deceased_counter += 1
        self.buried.append(a)

    def risk_from_agents(self, agents, weight):
        risk = 0
        for a in agents:
            risk += 1 - a.mask_protection
        return risk*weight

    def evaluate_safety_per_cell(self):
        """ 1.0 is a perfectly safe cell - empty, with all neighbours
        and neighbours-of-neighbours empty as well """
        for content, x, y in model.grid.coord_iter():
            self.safety_per_cell[y][x] = 1 # initial value
            # Compute risk from (x,y) cell
            ring0_risk = self.risk_from_agents(content, weight=0.5)
            considered_cells = {(x,y)}

            # Compute risk coming from the neighbours of (x,y) cell
            neighbours = self.grid.get_neighborhood(
                (x,y), moore=True, include_center=False)
            neighbours_content = self.grid.get_cell_list_contents(neighbours)
            ring1_risk = self.risk_from_agents(neighbours_content, 0.25)
            considered_cells | set(neighbours)

            # Compute risk coming from
            # the neighbours of the neighbours of (x,y) cell
            neighbours_of_neighbours = set()
            for c in neighbours:
                neighbours_of_neighbours | set(
                    self.grid.get_neighborhood(
                        (x,y),moore=True, include_center=False))
            neighbours_of_neighbours -= considered_cells
            ring2_risk = self.risk_from_agents(
                self.grid.get_cell_list_contents(neighbours_of_neighbours), 0.125)
            self.safety_per_cell[y][x] -= ring0_risk + ring1_risk + ring2_risk

    def step(self):
        if (self.update_current_location_type()):
            for a in self.schedule.agents:
                if (self.current_location_type is not None):
                    b = a.select_building(self.current_location_type)
                    a.teleport_to_building(b)
                else:
                    a.teleport_to_street()

        self.evaluate_safety_per_cell()
        self.schedule.step()
        self.steps_count += 1
        # collect data
        self.datacollector.collect(self)
        for d in self.deceased:
            self.bury_agent(d)
        self.deceased = []

    def run_model(self, n):
        for i in range(n):
            self.step()
Ejemplo n.º 4
0
class World(Model):
    """
    The class World which inherits from Model and is responsible for the
    intarations for the experiment.

    Attributs:
      gridsize: dimentions of the world grid
      cop_density: density of the cops placed in world
      citizen_density: density of the citizens in world
      agent_type: the alignment of agent either as cop or citizen
      l_state: the legitimacy state in world
      reduction_constant: the constant attribute which decide by what rate
      the state of l_state will reduce
    """
    def __init__(
        self,
        gridsize,
        cop_density,
        citizen_density,
        agent_type,
        legitimacy,
        l_state,
        reduction_constant,
        active_threshold,
        include_wealth,
        rich_threshold,
    ):

        # Create a new World instance.

        # Args:
        #    gridsize: the size of grid
        #    cop_density: density of cops to be placed
        #    citizen_density: density of citizens to be placed
        #    agent_type: the alignment of agent either as cop or citizen
        #    l_state: the legitimacy state
        #    reduction_constant: the constant attribute which decide by what rate
        #        the state of l_state will reduce

        self.cop_density = cop_density
        self.citizen_density = citizen_density
        self.agent_type = agent_type

        self.legitimacy = legitimacy
        self.l_state = l_state

        self.reduction_constant = reduction_constant
        self.active_threshold = active_threshold
        self.include_wealth = include_wealth
        self.rich_threshold = rich_threshold

        self.ap_constant = 2.3

        # Agent count r_c: rich_count, r_a_c: rich_active_count, m_c: middle_count, m_a_c: middle_active_count, p_c: poor_count, p_a_c: poor_active_count,.
        self.r_c = 0
        self.r_a_c = 0
        self.m_c = 0
        self.m_a_c = 0
        self.p_c = 0
        self.p_a_c = 0

        self.mean = 0
        self.kill_agents = []
        self.agents_killed = 0
        self.grid = MultiGrid(gridsize, gridsize, False)
        self.schedule = SimultaneousActivation(self)
        self.placement(gridsize)
        self.running = True

    def placement(self, gridsize):

        # Placement of agents inside the Grid

        # Arguments:
        # gridsize: Dimensions of grid

        unique_id = 1

        if self.cop_density + self.citizen_density > 1:
            print("Density ratios must not exceed 1", file=sys.stderr)

        self.bank = Bank(1, self)

        for (_, x, y) in self.grid.coord_iter():

            if self.random.random() < self.cop_density:
                a = Cop(unique_id, self)
                self.schedule.add(a)
                self.grid.place_agent(a, (x, y))
                unique_id += 1

            elif self.random.random() < (self.cop_density +
                                         self.citizen_density):
                a = Citizen(
                    unique_id,
                    self,
                    hardship=self.random.random(),
                    risk_aversion=self.random.random(),
                    bank=self.bank,
                    rich_threshold=self.rich_threshold,
                )
                self.schedule.add(a)
                self.grid.place_agent(a, (x, y))
                unique_id += 1

        self.datacollector = DataCollector(
            model_reporters={
                "Poor Grievance": lambda m: self.measure_poor_grievance(m),
                "Middle Grievance": lambda m: self.measure_middle_grievance(m),
                "Rich Grievance": lambda m: self.measure_rich_grievance(m),
                "Calm": lambda m: self.count_calm(m),
                "Revolt": lambda m: self.count_revolt(m),
                "Jail": lambda m: self.count_jailed(m),
                "Cops": lambda m: self.count_cops(m),
                "Rich": lambda m: self.count_rich(m),
                "Middle": lambda m: self.count_middle(m),
                "Poor": lambda m: self.count_poor(m),
                "Rich Wealth": lambda m: self.measure_rich_wealth(m),
                "Middle Wealth": lambda m: self.measure_middle_wealth(m),
                "Poor Wealth": lambda m: self.measure_poor_wealth(m),
                "Rich Confidence": lambda m: self.measure_rich_confidence(m),
                "Middle Confidence":
                lambda m: self.measure_middle_confidence(m),
                "Poor Confidence": lambda m: self.measure_poor_confidence(m),
                "Rich Hardship": lambda m: self.measure_rich_hardship(m),
                "Middle Hardship": lambda m: self.measure_middle_hardship(m),
                "Poor Hardship": lambda m: self.measure_poor_hardship(m),
                "Legitimacy": lambda m: self.measure_legitimacy(m),
                "WO Revolt": lambda m: self.wo_wealth_active(m),
                "WO Calm": lambda m: self.wo_wealth_calm(m),
                "WO Jail": lambda m: self.wo_wealth_jail(m),
            })

    def update_agent_count(self):

        # Updates the number of current and active agents

        self.r_c = len([
            a for a in self.schedule.agents
            if a.alignment == "Citizen" and a.status == "Rich"
        ])
        self.r_a_c = len([
            a for a in self.schedule.agents if a.alignment == "Citizen"
            and a.status == "Rich" and a.state == "Revolt"
        ])

        self.m_c = len([
            a for a in self.schedule.agents
            if a.alignment == "Citizen" and a.status == "Middle"
        ])
        self.m_a_c = len([
            a for a in self.schedule.agents if a.alignment == "Citizen"
            and a.status == "Middle" and a.state == "Revolt"
        ])

        self.p_c = len([
            a for a in self.schedule.agents
            if a.alignment == "Citizen" and a.status == "Poor"
        ])
        self.p_a_c = len([
            a for a in self.schedule.agents if a.alignment == "Citizen"
            and a.status == "Poor" and a.state == "Revolt"
        ])

    def mean_wealth(self):

        # Calculate the mean wealth of all the citizen agents

        self.agents = [
            agent.savings for agent in self.schedule.agents
            if agent.alignment == "Citizen"
        ]
        self.mean = s.mean(self.agents)

    def update_core(self):
        if self.l_state:
            if self.legitimacy > 0.0:
                self.legitimacy -= self.reduction_constant
            else:
                self.legitimacy = 0.0

    def step(self):

        # Calculation of world attributes in one step(iteration) of execution

        self.update_agent_count()
        self.datacollector.collect(self)
        self.mean_wealth()
        self.schedule.step()
        self.update_core()

        if self.kill_agents:
            self.kill_agents = list(dict.fromkeys(self.kill_agents))
            for i in self.kill_agents:
                self.grid.remove_agent(i)
                self.schedule.remove(i)
            self.kill_agents = []

        total_agents = len(
            [a for a in self.schedule.agents if a.alignment == "Citizen"])
        if total_agents < 2:
            self.running = False

    @staticmethod
    def count_calm(model):
        a = len([
            a for a in model.schedule.agents
            if a.alignment == "Citizen" and a.state == "Calm"
        ])
        return a

    @staticmethod
    def count_revolt(model):
        a = len([
            a for a in model.schedule.agents
            if a.alignment == "Citizen" and a.state == "Revolt"
        ])
        return a

    @staticmethod
    def count_jailed(model):
        a = len([
            a for a in model.schedule.agents
            if a.alignment == "Citizen" and a.state == "Jail"
        ])
        return a

    @staticmethod
    def count_cops(model):
        a = len([a for a in model.schedule.agents if a.alignment == "Cop"])
        return a

    @staticmethod
    def count_rich(model):
        a = len([
            a for a in model.schedule.agents
            if a.alignment == "Citizen" and a.status == "Rich"
        ])
        return a

    @staticmethod
    def count_middle(model):
        a = len([
            a for a in model.schedule.agents
            if a.alignment == "Citizen" and a.status == "Middle"
        ])
        return a

    @staticmethod
    def count_poor(model):
        a = len([
            a for a in model.schedule.agents
            if a.alignment == "Citizen" and a.status == "Poor"
        ])
        return a

    @staticmethod
    def measure_poor_grievance(model):
        if model.include_wealth:
            confidence = [
                a.grievance for a in model.schedule.agents
                if a.alignment == "Citizen" and a.status == "Poor"
            ]
            if confidence:
                total = sum(confidence)
                return total
            else:
                return 0
        else:
            confidence = [
                a.grievance for a in model.schedule.agents
                if a.alignment == "Citizen"
            ]
            return sum(confidence)

    @staticmethod
    def measure_middle_grievance(model):
        confidence = [
            a.grievance for a in model.schedule.agents
            if a.alignment == "Citizen" and a.status == "Middle"
        ]
        if confidence:
            total = sum(confidence)
            return total
        else:
            return 0

    @staticmethod
    def measure_rich_grievance(model):
        confidence = [
            a.grievance for a in model.schedule.agents
            if a.alignment == "Citizen" and a.status == "Rich"
        ]
        if confidence:
            total = sum(confidence)
            return total
        else:
            return 0

    @staticmethod
    def measure_rich_wealth(model):
        wealth = [
            a.wealth for a in model.schedule.agents
            if a.alignment == "Citizen" and a.status == "Rich"
        ]
        return s.mean(wealth) if wealth else 0

    @staticmethod
    def measure_middle_wealth(model):
        wealth = [
            a.wealth for a in model.schedule.agents
            if a.alignment == "Citizen" and a.status == "Middle"
        ]
        return s.mean(wealth) if wealth else 0

    @staticmethod
    def measure_poor_wealth(model):
        wealth = [
            a.wealth for a in model.schedule.agents
            if a.alignment == "Citizen" and a.status == "Poor"
        ]
        return s.mean(wealth) if wealth else 0

    @staticmethod
    def measure_poor_confidence(model):
        confidence = [
            a.confidence for a in model.schedule.agents
            if a.alignment == "Citizen" and a.status == "Poor"
        ]
        if confidence:
            total = s.mean(confidence)
            return total
        else:
            return 0

    @staticmethod
    def measure_middle_confidence(model):
        confidence = [
            a.confidence for a in model.schedule.agents
            if a.alignment == "Citizen" and a.status == "Middle"
        ]
        if confidence:
            total = s.mean(confidence)
            return total
        else:
            return 0

    @staticmethod
    def measure_rich_confidence(model):
        confidence = [
            a.confidence for a in model.schedule.agents
            if a.alignment == "Citizen" and a.status == "Rich"
        ]
        if confidence:
            total = s.mean(confidence)
            return total
        else:
            return 0

    @staticmethod
    def measure_total_reserves(model):
        return s.mean(model.bank.total_reserves)

    @staticmethod
    def measure_poor_hardship(model):

        confidence = [
            a.hardship for a in model.schedule.agents
            if a.alignment == "Citizen" and a.status == "Poor"
        ]
        if confidence:
            total = sum(confidence)
            return total
        else:
            return 0

    @staticmethod
    def measure_middle_hardship(model):
        confidence = [
            a.hardship for a in model.schedule.agents
            if a.alignment == "Citizen" and a.status == "Middle"
        ]
        if confidence:
            total = sum(confidence)
            return total
        else:
            return 0

    @staticmethod
    def measure_rich_hardship(model):
        confidence = [
            a.hardship for a in model.schedule.agents
            if a.alignment == "Citizen" and a.status == "Rich"
        ]
        if confidence:
            total = sum(confidence)
            return total
        else:
            return 0

    @staticmethod
    def measure_legitimacy(model):
        return model.legitimacy * 100

    @staticmethod
    def wo_wealth_active(model):
        if model.include_wealth == False:
            active = [
                a for a in model.schedule.agents
                if a.alignment == "Citizen" and a.state == "Revolt"
            ]
            return len(active)
        else:
            return 0

    @staticmethod
    def wo_wealth_calm(model):
        if model.include_wealth == False:
            active = [
                a for a in model.schedule.agents
                if a.alignment == "Citizen" and a.state == "Calm"
            ]
            return len(active)
        else:
            return 0

    @staticmethod
    def wo_wealth_jail(model):
        if model.include_wealth == False:
            active = [
                a for a in model.schedule.agents
                if a.alignment == "Citizen" and a.state == "Jail"
            ]
            return len(active)
        else:
            return 0
class SegregationModelModel(Model):

    def __init__(self, datacollector=None):
        super().__init__()
        # work from directory this file is in
        os.chdir(os.path.dirname(os.path.realpath(__file__)))
        self.schedule = SimultaneousActivation(self)
        self.G = nx.Graph()
        self.time = 0  # simple iteration counter
        self._generate_sites()
        self.grid = NetworkGrid(self.G)
        # make a dictionary of {hash: site} values for easy relation lookups in agent generation
        self.site_hashes = {h: s for s, h in dict(
            self.G.nodes.data('hash')).items()}
        self._generate_agents()
        self.vita_groups = []
        self.datacollector = datacollector

    def step(self):
        if self.datacollector:
            self.datacollector.collect(self)
        else:
            warnings.warn('This Model has no DataCollector! You may want to add one in the `datacollector` attribute '
                          'before running the model')

        self.schedule.step()

        while self.vita_groups:
            a = self.vita_groups.pop()
            a.unique_id = self.next_id()
            a.model = self
            self.schedule.add(a)

        for a in self.schedule.agents:
            if a.get('__void__', False):
                self.grid._remove_agent(a, a.pos)
                self.schedule.remove(a)

        self.time += 1

    # ------------------------- INITIALIZATION HELPERS -------------------------

    def _generate_agents(self):
        """
        Called once during __init__ to create appropriate groups from the original simulation's model and add them to
        the model grid.
        Loads group data from the JSON file created during translation.
        """
        with open("SegregationModelGroups.json", 'r') as file:
            j = json.load(file)
            for group in j:
                for _ in range(group['m']):
                    a = SegregationModelAgent(
                        self.next_id(), self, group['attr'], group['rel'])
                    self.schedule.add(a)

    def _generate_sites(self):
        """
        Called once during __init__ to load the original simulation's sites into the networkx graph.
        Loads site data from a JSON file created during translation.
        """
        with open("SegregationModelSites.json", 'r') as file:
            j = json.load(file)
            for site in j:
                self.G.add_node(
                    str(site['name']), hash=site['hash'], rel_name=site['rel_name'])
                for k, v in site['attr'].items():
                    self.G.nodes[str(site['name'])][k] = v

    # ------------------------- RUNTIME FUNCTIONS -------------------------

    def get_attr(self, agent_or_node, name=None):
        """
        Retrieves an attribute of a Mesa Agent or NetworkGrid node.
        :param name: A string containing the attribute to retrieve, or None
        :param agent_or_node: A Mesa Agent or a string corresponding to a node in the NetworkGrid
        :return: If agent_or_node is a string, returns the named attribute represented by it, or the node's entire
                     attribute dictionary if name is None (note: this includes the special 'agent' attribute)
                 If agent_or_node is an Agent, returns the named attribute of that Agent
        """
        name = mpi(name) if name is not None else name
        if isinstance(agent_or_node, str):
            node_dict = self.grid.G.nodes[agent_or_node]
            return node_dict.get(name) if name is not None else node_dict
        elif isinstance(agent_or_node, Agent):
            # return getattr(agent_or_node, name, agent_or_node.namespace[name])
            return agent_or_node.get(name)
        else:
            raise TypeError(
                f"get_attr expected a str or Agent for agent_or_node, but received {type(agent_or_node)}")

    def get_groups(self, node_or_model, qry=None):
        """
        Returns a list of agents at the node or the entire model that satisfy the qry. 
        :param node_or_model: A string corresponding to a node in the NetworkGrid, or a Mesa Model
        :param qry: a GroupQry namedtuple
        :return: a list of agents at the node satisfying the qry. 
        """
        if isinstance(node_or_model, Model):
            agents = node_or_model.schedule.agents
        elif isinstance(node_or_model, str):
            agents = self.grid.get_cell_list_contents([node_or_model])
        else:
            raise TypeError(
                f"get_groups expects a str or Model for node_or_model, but received {type(node_or_model)}")

        return [a for a in agents if a.matches_qry(qry)]
        # if not qry:
        #     return agents
        # # the code below is REALLY PAINFUL... replacing it with 'return agents` makes the code run like 20x faster
        # elif qry.full:
        #     return [a for a in agents
        #             if qry.attr.items() == {k: getattr(a, k) for k in a._attr}.items()
        #             and qry.rel.items() == {k: getattr(a, k) for k in a._rel}.items()
        #             and all([fn(a) for fn in qry.cond])]
        # else:
        #     return [a for a in agents
        #             if qry.attr.items() <= {k: getattr(a, k) for k in a._attr}.items()
        #             and qry.rel.items() <= {k: getattr(a, k) for k in a._rel}.items()
        #             and all([fn(a) for fn in qry.cond])]

    def get_mass(self, agent_node_model, qry=None):
        """
        If agent_node_model is an agent, returns the number of agents with the same attributes as it, including itself.
        This ignores unique_id (and source_name).
        This is probably very unoptimized.
        If agent_node_model is a string corresponding to a node in the NetworkGrid, returns the number of agents at that
        node with the attributes specified in qry, or all agents at that node if qry is None.
        If agent_node_model is a Model, returns the total number of agents in the model.
        """
        if isinstance(agent_node_model, str):
            return len(self.get_groups(agent_node_model, qry))
        elif isinstance(agent_node_model, Agent):
            mod_dict = {k: v for k, v in agent_node_model.__dict__.items()
                        if k not in ('unique_id', 'source_name')}  # toss unique identifiers
            return sum([mod_dict == {k: v for k, v in a.__dict__.items() if k not in ('unique_id', 'source_name')}
                        for a in self.schedule.agents])
        elif isinstance(agent_node_model, Model):
            return len(agent_node_model.schedule.agents)
        else:
            raise TypeError(f"get_mass expects a str, Agent, or Model for agent_node_model, but received "
                            f"{type(agent_node_model)}")
Ejemplo n.º 6
0
class BurglaryModel(Model):

    def __init__(self, N, width, height, b_rate, delta, omega, theta, mu, gamma, space):
        self.num_agents = N
        self.grid = MultiGrid(width, height, True)
        self.width = width
        self.height = height
        self.houses = self.width * self.height
        self.schedule = SimultaneousActivation(self)
        self.house_schedule = SimultaneousActivation(self)
        self.b_rate = b_rate
        self.delta = delta
        self.omega = omega
        self.theta = theta
        self.mu = mu
        self.kill_agents = []
        self.gamma = gamma
        self.gen_agent = 1 - math.exp(-self.gamma*self.delta)
        self.total_agents = self.num_agents
        self.space = space

        a_0 = 0.2
        # place houses on grid, 1 house per grid location
        for i in range(self.width):
            for j in range(self.height):
                num = str(i) + str(j)
                num = int(num)
                a = House(num, self, a_0, i, j, self.delta, self.omega, self.theta, self.mu, self.space)
                self.grid.place_agent(a, (a.x_point, a.y_point))
                self.house_schedule.add(a)

        # place the criminals
        for k in range(self.num_agents):
            unique_id = "criminal" + str(k)
            criminal = Criminal(unique_id, self, self.width, self.height)
            self.grid.place_agent(criminal, (criminal.x_point, criminal.y_point))
            self.schedule.add(criminal)

        # set up data collection
        self.datacollector = DataCollector(
            model_reporters={"Mean_Attractiveness": get_mean_att,
                             "Max_Attractiveness": get_max_att,
                             "Min_Attractiveness": get_min_att,
                             "CrimeEvents": get_num_burgles,
                             "Criminals": get_num_criminals,
                             "MaxPos": get_max_att_pos},
            agent_reporters={"Att": lambda x: x.att_t if x.unique_id[:1]!="c" else None})


    def add_criminals(self):
        start_count = self.total_agents + 1
        for i in range(self.houses):
            y = random.random()
            if y < self.gen_agent:
                unique_id = "criminal" + str(start_count)
                criminal = Criminal(unique_id, self, self.width, self.height)
                self.grid.place_agent(criminal, (criminal.x_point, criminal.y_point))
                self.schedule.add(criminal)
                start_count = start_count + 1
                self.total_agents = start_count
                self.num_agents = self.num_agents + 1


    def step(self):
        self.datacollector.collect(self)
        # cycle through all houses and calculate updates on their attractiveness
        self.house_schedule.step()
        self.schedule.step()
        for row in self.kill_agents:
            try:
                self.grid.remove_agent(row)
                self.schedule.remove(row)
                self.kill_agents.remove(row)
                self.num_agents = self.num_agents - 1

            except:
                self.kill_agents.remove(row)

        # add new criminals

        self.add_criminals()
Ejemplo n.º 7
0
class SIRSModelModel(Model):
    def __init__(self, datacollector=None):
        super().__init__()
        # work from directory this file is in
        os.chdir(os.path.dirname(os.path.realpath(__file__)))
        self.schedule = SimultaneousActivation(self)
        self.G = nx.Graph()
        self.time = 0  # simple iteration counter
        self._generate_sites()
        self.grid = NetworkGrid(self.G)
        # make a dictionary of {hash: site} values for easy relation lookups in agent generation
        self.site_hashes = {
            h: s
            for s, h in dict(self.G.nodes.data('hash')).items()
        }
        self._generate_agents()
        self.vita_groups = []
        self.datacollector = datacollector

    def step(self):
        if self.datacollector:
            self.datacollector.collect(self)
        else:
            warnings.warn(
                'This Model has no DataCollector! You may want to add one in the `datacollector` attribute '
                'before running the model')

        self.schedule.step()

        while self.vita_groups:
            a = self.vita_groups.pop()
            a.unique_id = self.next_id()
            a.model = self
            self.schedule.add(a)

        for a in self.schedule.agents:
            if a.get('__void__', False):
                self.grid._remove_agent(a, a.pos)
                self.schedule.remove(a)

        self.time += 1

    # ------------------------- INITIALIZATION HELPERS -------------------------

    def _generate_agents(self):
        """
        Called once during __init__ to create appropriate groups from the original simulation's model and add them to
        the model grid.
        Loads group data from the JSON file created during translation.
        """
        with open("SIRSModelGroups.json", 'r') as file:
            j = json.load(file)
            for group in j:
                for _ in range(group['m']):
                    a = SIRSModelAgent(self.next_id(), self, group['attr'],
                                       group['rel'])
                    self.schedule.add(a)

    def _generate_sites(self):
        """
        Called once during __init__ to load the original simulation's sites into the networkx graph.
        Loads site data from a JSON file created during translation.
        """
        with open("SIRSModelSites.json", 'r') as file:
            j = json.load(file)
            for site in j:
                self.G.add_node(str(site['name']),
                                hash=site['hash'],
                                rel_name=site['rel_name'])
                for k, v in site['attr'].items():
                    self.G.nodes[str(site['name'])][k] = v

    # ------------------------- RUNTIME FUNCTIONS -------------------------

    def get_attr(self, agent_or_node, name=None):
        """
        Retrieves an attribute of a Mesa Agent or NetworkGrid node.
        :param name: A string containing the attribute to retrieve, or None
        :param agent_or_node: A Mesa Agent or a string corresponding to a node in the NetworkGrid
        :return: If agent_or_node is a string, returns the named attribute represented by it, or the node's entire
                     attribute dictionary if name is None (note: this includes the special 'agent' attribute)
                 If agent_or_node is an Agent, returns the named attribute of that Agent
        """
        name = mpi(name) if name is not None else name
        if isinstance(agent_or_node, str):
            node_dict = self.grid.G.nodes[agent_or_node]
            return node_dict.get(name) if name is not None else node_dict
        elif isinstance(agent_or_node, Agent):
            # return getattr(agent_or_node, name, agent_or_node.namespace[name])
            return agent_or_node.get(name)
        else:
            raise TypeError(
                f"get_attr expected a str or Agent for agent_or_node, but received {type(agent_or_node)}"
            )
Ejemplo n.º 8
0
class InfectionModel(Model):
    """
    Mesa model class that simulates infection spread
    """

    def __init__(self, params: dict):
        """
        Parameters
        ----------
        params: dict
            Simulation parameters
        """
        self.current_id = 0     # inherited variable, for id generation
        self.params = params    # parameters
        self.statistics = {     # statistics for data collector
            "infected": 0,
            "recovered": 0,
            "susceptible": 0,
            "vaccinated": 0,
            "deaths": 0,
            "alive": 0,
            "total_infections": 0,
            "total_recoveries": 0,
        }

        self.grid = MultiGrid(self.params['grid_width'], self.params['grid_height'], True)  # grid that agents move on
        self.schedule = SimultaneousActivation(self)    # scheduler for iterations of the simulation
        self.dataCollector = DataCollector(model_reporters={    # to collect data for the graph
            "infected": lambda m: m.statistics["infected"],
            "recovered": lambda m: m.statistics["recovered"],
            "susceptible": lambda m: m.statistics["susceptible"],
            "vaccinated": lambda m: m.statistics["vaccinated"],
            "deaths": lambda m: m.statistics["deaths"],
            "alive": lambda m: m.statistics["alive"],
            "total_infections": lambda m: m.statistics["total_infections"],
            "total_recoveries": lambda m: m.statistics["total_recoveries"],
        })

        self.running = True                # required for visualization, tells if simulation is done
        self.dead_agents = []   # when agents die, they are added to this list to be removed
        self.step_count = 0     # number of steps completed, required for vaccination
        self.vaccination_started = False    # has vaccination started?

        # creating agents
        for _ in range(self.params['num_agents']):
            # initial state of this agent
            initial_state = InfectionState.INF if \
                self.random.uniform(0, 1) < self.params['initial_infected_chance'] else InfectionState.SUS

            # by default, this won't add to total_infections which leads to incorrect results
            if initial_state == InfectionState.INF:
                self.statistics["total_infections"] += 1
            # randomise position
            pos = self.random.randrange(self.grid.width), self.random.randrange(self.grid.height)
            self.add_agent(self.create_agent(initial_state), pos)

    def check_running(self):
        """
        Checks and returns if the simulation is still running (there are infected people)
        """
        for agent in self.schedule.agent_buffer():
            if agent.state == InfectionState.INF:
                return True
        return False

    def step(self):
        """
        Called every step
        """
        # just to show the progress while running in console
        if self.step_count % 100 == 0:
            print(self.step_count)
        self.per_agent_actions()  # simulate actions to be taken globally on all agents
        self.schedule.step()    # run step for all agents

        # collect data at a particular frequency
        if self.step_count % self.params['data_collection_frequency'] == 0:
            self.calculate_statistics()  # calculate statistics for data collector
            self.dataCollector.collect(self)    # collect data

        self.step_count += 1
        # if vaccination is enabled and enough time has passed
        if not self.vaccination_started and self.params['vaccination_start'] != -1 and \
                self.step_count > self.params['vaccination_start']:
            self.vaccination_started = True  # start vaccination

        for x in self.dead_agents:  # remove dead agents
            self.remove_agent(x)
            self.statistics["deaths"] += 1   # add to death count
        self.dead_agents = []
        self.running = self.check_running()  # is the simulation still running?

    def calculate_statistics(self):
        """
        Calculates statistics each iteration, for more efficient data collection
        """
        # reset all iteration specific parameters
        self.statistics["infected"] = 0
        self.statistics["recovered"] = 0
        self.statistics["susceptible"] = 0
        self.statistics["vaccinated"] = 0
        self.statistics["alive"] = 0

        for agent in self.schedule.agent_buffer():
            self.statistics["alive"] += 1
            if agent.state == InfectionState.INF:
                self.statistics["infected"] += 1
            elif agent.state == InfectionState.SUS:
                self.statistics["susceptible"] += 1
            elif agent.state == InfectionState.REC:
                self.statistics["recovered"] += 1
            elif agent.state == InfectionState.VAC:
                self.statistics["vaccinated"] += 1

    def per_agent_actions(self):
        """
        Simulates actions to be taken on a global scale per agent
        """
        # this should only occur once a day
        if self.step_count % 24 != 0:
            return
        for agent in self.schedule.agent_buffer():
            if self.random.uniform(0, 1) < self.params['external_infection_chance']:
                agent.state = InfectionState.INF
                self.statistics["total_infections"] += 1

    def create_agent(self, initial_state: InfectionState) -> PersonAgent:
        """
        Creates an agent, and returns it

        Parameters
        ----------
        initial_state : InfectionState
            Initial infection state of this agent

        Returns
        -------
        PersonAgent
            The agent created
        """
        return PersonAgent(self.next_id(), self, initial_state)

    def add_agent(self, agent: Agent, pos: Tuple[int, int]):
        """
        Adds an agent to the simulation

        Parameters
        ----------
        agent : agent
            The agent to be added

        pos : Tuple[int, int]
            The position where this agent should be on the grid
        """
        # add to scheduler
        self.schedule.add(agent)
        # assign position
        self.grid.place_agent(agent, pos)

    def remove_agent(self, agent: Agent):
        """
        Removes an agent from the simulation

        Parameters
        ----------
        agent : Agent
            The agent to be removed from the simulation
        """
        self.grid.remove_agent(agent)
        self.schedule.remove(agent)
Ejemplo n.º 9
0
class trafficSimulation(Model):
    def __init__(self, spawn_speed):
        self.spawn_speed = spawn_speed
        self.counter = 0
        self.traffic_lights_schedule = TrafficScheduler(self)
        self.cars_schedule = SimultaneousActivation(self)
        self.grid = MultiGrid(10, 10, False)
        self.id = 0
        self.spawnpoints = [(9, 5), (0, 4), (6, 0), (5, 9)]
        self.kill_agents = []

        self.datacollector = DataCollector(model_reporters={"Grid": get_grid})

        traffic_light_coords = [(7, 5), (4, 4), (6, 3), (5, 6)]

        for coord in traffic_light_coords:
            light = trafficLight(self.id, coord, self)
            self.id = self.id + 1
            self.grid.place_agent(light, coord)
            self.traffic_lights_schedule.add(light)
            self.datacollector.collect(self)

    def get_data(self):
        traffic_lights = [{
            "coords": {
                "x": agent.coords[0],
                "y": agent.coords[1]
            },
            "state": stateToString(agent.state)
        } for agent in get_other_lights(-1, self)]
        cars = [{
            "direction": {
                "x": agent.direction[0],
                "y": agent.direction[1]
            },
            "coords": {
                "x": agent.coords[0],
                "y": agent.coords[1]
            }
        } for agent in self.cars_schedule.agents]
        return traffic_lights, cars

    def step(self):

        self.traffic_lights_schedule.step()
        self.cars_schedule.step()

        if self.counter == self.spawn_speed - 1 and random.random() > 0.5:
            orientation, coords = random.choice(
                [x for x in zip(direction.lst, self.spawnpoints)])
            anyCar = any([
                isinstance(agent, carAgent)
                for agent in self.grid.iter_neighbors(coords, False, True, 0)
            ])
            if not anyCar:
                car = carAgent(self.id, coords, orientation, self)
                self.grid.place_agent(car, coords)
                self.cars_schedule.add(car)
                self.id = self.id + 1
        self.counter = (self.counter + 1) % self.spawn_speed

        for x in self.kill_agents:
            self.grid.remove_agent(x)
            self.cars_schedule.remove(x)
            self.kill_agents.remove(x)
class ClimateMigrationModel(Model):
    def __init__(   self, num_counties, preferences, network_type, \
                    climate_threshold, limited_radius=True, init_time=0):
        super().__init__()
        global TICK
        TICK = init_time
        self.num_agents = 0
        self.agent_index = 0
        self.preferences = preferences
        self.limited_radius = limited_radius
        self.upper_network_size = 3
        self.network_type = network_type
        self.climate_threshold = climate_threshold
        self.schedule = SimultaneousActivation(self)
        self.G = create_graph()
        self.num_counties = num_counties
        self.nodes = self.G.nodes()
        self.grid = NetworkGrid(self.G)
        self.county_climate_ranking = []
        self.county_population_list = [0] * self.num_counties
        self.county_flux = [0] * self.num_counties
        self.deaths = []
        self.births = []
        self.county_income = {}
        self.datacollector = DataCollector(model_reporters={"County Population": lambda m1: list(m1.county_population_list),
                                                            "County Influx": lambda m2: list(m2.county_flux),
                                                            "Deaths": lambda m3: m3.deaths,
                                                            "Births": lambda m4: m4.births,
                                                            "Total Population": lambda m5: m5.num_agents})

    def add_agents(self):
        """
        Adds agents based on 2013 ACS population data.
        """
        cumulative_population_list = get_cumulative_population_list()
        self.county_population_list = get_population_list()

        county = 0   # keeps track of which county each agent should be placed in
        index = 0   # keeps track of each agent's unique_id

        # keep creating agents until county population is reached
        while index < cumulative_population_list[county]:
            # create agent
            agent = Household(index, self)
            # place agent in appropriate county
            self.grid.place_agent(agent, list(self.nodes)[county])
            # add agent to schedule
            self.schedule.add(agent)
            # set agent's original_pos attribute
            agent.original_pos = agent.pos
            # initialize all other agent attributes
            agent.initialize_agent()
            # if running model with heterogeneous preferences, set agent preference
            if self.preferences:
                agent.initialize_preference()

            # update index
            index += 1

            # if done with county and not at last county, increase county
            if index == cumulative_population_list[county] and county < self.num_counties - 1:
                county += 1

        # after all agents are added, set model attributes
        self.num_agents = cumulative_population_list[self.num_counties-1]
        self.agent_index = cumulative_population_list[self.num_counties-1]

    def initialize_all_random_networks(self):
        """
        Initializes random networks for all agents in model.
        """
        for a in self.schedule.agents:
            a.initialize_random_network()

    def initialize_all_income_networks(self):
        """
        Initializes income-based networks for all agents in model.
        """
        for a in self.schedule.agents:
            a.initialize_income_network()

    def initialize_all_age_networks(self):
        """
        Initializes age-based networks for all agents in model.
        """
        for a in self.schedule.agents:
            a.initialize_age_network()

    def initialize_all_income_age_networks(self):
        """
        Initializes income and age-based networks for all agents in model.
        """
        for a in self.schedule.agents:
            a.initialize_income_age_network()

    def initialize_all_families(self):
        """
        Initializes families for all agents in model.
        """
        for a in self.schedule.agents:
            a.initialize_family()

    def update_population(self):
        """
        Updates population by adding and removing agents.
        """
        # keep track of number of deaths and births per county
        self.deaths = [0]*self.num_counties
        self.births = [0]*self.num_counties

        # remove agents (death)
        # loop through all agents
        for agent in self.schedule.agents:
            # source: https://www.ssa.gov/oact/STATS/table4c6.html#ss
            # calculate death probability by age in the united states
            if random.random() < 0.0001*(math.e**(0.075*agent.age)):
                # keep track of deaths by county
                self.deaths[agent.pos] += 1
                # remove agent from model
                self.grid._remove_agent(agent, agent.pos)
                # remove agent from schedule
                self.schedule.remove(agent)
                # update number of agents
                self.num_agents -= 1

        # add agents (birth)
        # loop through all counties
        for county in range(self.num_counties):
            # source: https://www.cdc.gov/nchs/fastats/births.htm
            # access current population
            current_population = self.county_population_list[county]
            # calculate how many agents should be added
            to_add = current_population//100 # birth rate
            # update number of agents
            self.num_agents += to_add

            # add specified number of agents
            for count in range(to_add):
                # update agent index
                self.agent_index += 1
                # create new agent
                agent = Household(self.agent_index, self)
                # place agent in current county
                self.grid.place_agent(agent, county)
                # add agent to schedule
                self.schedule.add(agent)

                # initialize agent attributes and networks
                
                # agents are assumed to be 18 as that is the lower bound of a householder's age
                agent.age = 18
                # based on age, income is assigned
                agent.initialize_income(random.random())
                # based on income, tenure is assigned
                agent.initialize_tenure(random.random())
                # input-specified network is initialized
                agent.initialize_network()
                # family is initialized
                agent.initialize_family()
                if self.preferences:
                    agent.initialize_preference()
                # original position is set
                agent.original_pos = agent.pos
                # keep track of births by county
                self.births[agent.pos] += 1

        # loop through counties, update population counts
        for county in self.nodes:
            self.county_population_list[county] = len(self.G.node[county]['agent'])

    def update_climate(self):
        """
        Update climate variables based on NOAA's predictions.

        Index 1 represents number of days above 90 degrees Fahrenheit.
        Index 4 represents number of days with < 1 inch of rain.
        Index 7 represents number of days without rain.
        Indexes 3, 6, and 9 are the yearly increases/decreases for these estimates.
        The update function is a simple linear function.
        
        Note: More accurate climate data could be integrated by importing
        more climate explorer data.
        """
        for n in self.nodes:
            self.G.node[n]['climate'][1] += self.G.node[n]['climate'][3]
            self.G.node[n]['climate'][4] += self.G.node[n]['climate'][6]
            self.G.node[n]['climate'][7] += self.G.node[n]['climate'][9]

    def rank_by_climate(self):
        """
        Create an ordered list of counties, from least hot/dry climate to
        most hot/dry climate.
        """
        # initialize lists to store data
        heat_data = []
        dry_data = []
        heat_dry_data = []

        # loop through counties in order
        for county in self.nodes:
            # access and store all heat/dry data
            heat_data.append(self.G.node[county]['climate'][1])
            dry_data.append(self.G.node[county]['climate'][7])

        # find max heat/dry data
        max_heat = max(heat_data)
        max_dry = max(dry_data)

        # normalize data based on max value
        heat_data = [(e/max_heat) for e in heat_data]
        dry_data = [(e/max_dry) for e in dry_data]

        # add normalized data
        for county in range(self.num_counties):
            heat_dry_data.append(heat_data[county] + dry_data[county])

        # convert to numpy array
        heat_dry_data = np.array(heat_dry_data)
        # returns indices that would sort an array (in this case,
        # returns county id's from best to worst climate)
        county_climate_rank = np.argsort(heat_dry_data)
        # convert to list, update model attribute
        self.county_climate_ranking = list(county_climate_rank)

    def update_income_counts(self):
        """
        Update income distribution by county.
        """
        # loop through counties in order
        for county in range(self.num_counties):
            # initialize list
            self.county_income[county] = [0]*10
        # loop through agents
        for agent in self.schedule.agents:
            # update dictionary based on agent data
            self.county_income[agent.pos][agent.income-1] += 1
        # income counts are printed at the beginning and end of run
        print(self.county_income)

    def get_preference_distribution(self):
        """
        TODO: docstring
        """
        preference_list = [0]*5
        for agent in self.schedule.agents:
            preference_list[agent.preference] += 1
        print(preference_list)


    def step(self):
        """
        Advance the model by one step.
        """
        global TICK
        # update climate ranking
        self.rank_by_climate()
        # advance all agents by one step
        self.schedule.step()
        # update population
        self.update_population()
        # update climate
        self.update_climate()
        # collect data
        self.datacollector.collect(self)
        # update step counter
        TICK += 1
Ejemplo n.º 11
0
class SpeedModel(Model):
    """
    Model of the game "Spe_ed". This class controls the execution of the simulation.
    """
    def __init__(self,
                 width,
                 height,
                 nb_agents,
                 agent_classes,
                 initial_agents_params=None,
                 cells=None,
                 data_collector=None,
                 save=False):
        """
        Model-Initialization.
        :param width: Width of the field
        :param height: Height of the field
        :param nb_agents: Number of Agents
        :param agent_classes: List of classes of the agents that should be players in the game. The length has to be
        equal or greater than nb_agents
        :param initial_agents_params: A list of dictionaries containing initialization parameters for agents that should
        be initialized at the start of the simulation
        :param cells: A Spe_ed-cells like 2D-Array that initializes the field
        :param data_collector: Mesa data collector function
        :param save: whether or not track the games history
        """
        super().__init__()
        self.data_collector = data_collector
        self.width = width
        self.height = height
        self.nb_agents = nb_agents
        self.save = save
        if self.save:
            self.history = []
        if initial_agents_params is None:
            initial_agents_params = [{} for i in range(nb_agents)]
        else:
            initial_agents_params = copy.deepcopy(initial_agents_params)

        self.schedule = SimultaneousActivation(self)

        self.grid = MultiGrid(width, height, True)
        # width and height are swapped since height is rows and width is columns
        # an alternative to this representation would be to transpose cells everytime it is exposed
        # but that could be inefficient
        self.cells = np.zeros((height, width), dtype="int")

        # Init initial agents
        self.speed_agents = []
        self.active_speed_agents = []
        for i in range(nb_agents):
            agent_params = initial_agents_params[i]
            agent_params["model"] = self
            # set to random position/direction if no position/direction is given
            if "pos" not in agent_params:
                agent_params["pos"] = self.random.choice(
                    list(self.grid.empties))
            if "direction" not in agent_params:
                agent_params["direction"] = self.random.choice(list(Direction))

            agent = agent_classes[i](**agent_params)

            # don't add agent to grid/cells if its out of bounds. But add it to the scheduler.
            if self.grid.out_of_bounds(agent_params["pos"]):
                self.schedule.add(agent)
                self.speed_agents.append(agent)
            else:
                self.add_agent(agent)
                self.speed_agents.append(agent)
                self.active_speed_agents.append(agent)

        if cells is not None:
            self.init_cells_and_grid(cells)

    def init_cells_and_grid(self, cells):
        """
        Initializes Mesas Grid and the Model-cells the field with the information given in cells.
        :param cells: A Spe_ed-cells like 2D-Array that initializes the field
        :return: None
        """
        self.cells = np.array(cells)
        # add traces to grid
        for y in range(self.cells.shape[0]):
            for x in range(self.cells.shape[1]):
                # cell is occupied by a collision
                if self.cells[y, x] == -1:
                    agent = AgentTraceCollision(self, (x, y))
                    self.add_agent(agent)
                # cell is occupied by head or trace
                elif self.cells[y, x] != 0:
                    # head of the agent is not already a entry in self.grid
                    if len(self.grid.get_cell_list_contents((x, y))) == 0:
                        # add trace
                        agent = AgentTrace(
                            self, (x, y),
                            self.speed_agents[self.cells[y, x] -
                                              1])  # get agent based on id
                        self.add_agent(agent)

    def step(self):
        """
        Computes one iteration of the model.
        :return: None
        """
        if self.data_collector:
            self.data_collector.collect(self)
        if self.save:
            self.history.append(copy.deepcopy(model_to_json(self)))
        self.schedule.step()
        self.check_collisions()
        self.check_game_finished()

    def step_specific_agent(self, agent):
        """
        Only steps one specific agent. This is only for specific applications (e.g. Multi-Minimax).
        Don't use this method if not necessary since it doesn't increment all model parts (e.g. time).
        :param cells: The agent to step
        :return: None
        """
        agent.step()
        agent.advance()
        self.check_collisions()
        self.check_game_finished()

    def check_collisions(self):
        """
        Checks every active agent for collisions with traces or other agents. Colliding agents are eliminated.
        :return: None
        """
        agents_to_set_inactive = []
        for agent in self.speed_agents:
            for t in agent.trace:
                cell_contents = self.grid.get_cell_list_contents(t)
                if len(cell_contents) > 1:
                    if agent not in agents_to_set_inactive:
                        agents_to_set_inactive.append(agent)
                    self.add_agent(AgentTraceCollision(self, t))

        for agent in agents_to_set_inactive:
            agent.set_inactive()

    def check_game_finished(self):
        """
        Checks whether or not the game has finished (every agent is eliminated) and prints the result if finished.
        :return: None
        """
        if len(self.active_speed_agents) <= 1:
            self.running = False
            if self.save:
                self.history.append(copy.deepcopy(model_to_json(self)))
                path = os.path.abspath("") + "/res/simulatedGames/"
                for entry in self.history:
                    entry["cells"] = entry["cells"].tolist()
                with open(
                        path + datetime.datetime.now().strftime(
                            "%d-%m-%y__%H-%M-%S-%f") + ".json", "w") as f:
                    json.dump(self.history, f, indent=4)

    def add_agent(self, agent):
        """
        Adds an agent to the model.
        :param agent: The agent to add to the model
        :return: None
        """
        self.schedule.add(agent)
        self.grid.place_agent(agent, agent.pos)
        # swapped position args since cells has the format (height, width)
        pos = (agent.pos[1], agent.pos[0])
        if isinstance(agent, SpeedAgent):
            self.cells[pos] = agent.unique_id
        elif type(agent) is AgentTraceCollision:
            self.cells[pos] = -1
        elif type(agent) is AgentTrace:
            self.cells[pos] = agent.origin.unique_id

    def remove_agent(self, agent):
        """
        Removes an agent from the model.
        :param agent: The agent to remove from the model
        :return: None
        """
        if agent in self.schedule.agents:
            self.schedule.remove(agent)
            self.grid.remove_agent(agent)

    def get_agent_by_id(self, unique_id):
        """
        Returns an agent-object by its unique_id.
        :param unique_id: The agent id to search for
        :return: Agent or None if no match
        """
        for agent in self.speed_agents:
            if agent.unique_id == unique_id:
                return agent
        return None