From 12d3dfe2b966394f91d68ddf417b8a181d8508a4 Mon Sep 17 00:00:00 2001 From: olegs Date: Tue, 29 Oct 2024 20:37:54 +0200 Subject: [PATCH 1/3] Add stubborns and random events --- examples/color_patches/color_patches/model.py | 87 ++++++++++++++----- .../color_patches/color_patches/server.py | 7 +- 2 files changed, 70 insertions(+), 24 deletions(-) diff --git a/examples/color_patches/color_patches/model.py b/examples/color_patches/color_patches/model.py index ca1aa263..ed4bf3ab 100644 --- a/examples/color_patches/color_patches/model.py +++ b/examples/color_patches/color_patches/model.py @@ -5,30 +5,37 @@ from collections import Counter import mesa +import random -class ColorCell(mesa.experimental.cell_space.CellAgent): +class ColorCell(mesa.Agent): """ Represents a cell's opinion (visualized by a color) """ - OPINIONS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] - def __init__(self, model, initial_state): + def __init__(self, pos, unique_id, model, initial_state): """ - Create a cell, in the given state, at the given row, col position. + Create a cell, in the given state, at the given row, col position and stubbornness. """ - super().__init__(model) - self.state = initial_state - self.next_state = None + super().__init__(unique_id, model) + self._row = pos[0] + self._col = pos[1] + self._state = initial_state + self.stubbornness = random.uniform(0, 0.99) + self._next_state = None def get_col(self): """Return the col location of this cell.""" - return self.cell.coordinate[0] + return self._col def get_row(self): """Return the row location of this cell.""" - return self.cell.coordinate[1] + return self._row + + def get_state(self): + """Return the current state (OPINION) of this cell.""" + return self._state def determine_opinion(self): """ @@ -36,9 +43,10 @@ def determine_opinion(self): The opinion is determined by the majority of the 8 neighbors' opinion A choice is made at random in case of a tie The next state is stored until all cells have been polled + The last opinion is determined by the stubbornness parameter of the agent """ - neighbors = self.cell.neighborhood.agents - neighbors_opinion = Counter(n.state for n in neighbors) + _neighbor_iter = self.model.grid.iter_neighbors((self._row, self._col), True) + neighbors_opinion = Counter(n.get_state() for n in _neighbor_iter) # Following is a a tuple (attribute, occurrences) polled_opinions = neighbors_opinion.most_common() tied_opinions = [] @@ -46,13 +54,22 @@ def determine_opinion(self): if neighbor[1] == polled_opinions[0][1]: tied_opinions.append(neighbor) - self.next_state = self.random.choice(tied_opinions)[0] + if self.random.random() > self.stubbornness: + self._next_state = self.random.choice(tied_opinions)[0] + else: + self._next_state = self._state def assume_opinion(self): """ Set the state of the agent to the next state """ - self.state = self.next_state + self._state = self._next_state + + def set_next_state(self, opinion): + """ + Set the next state for the agent. + """ + self._next_state = opinion class ColorPatches(mesa.Model): @@ -66,30 +83,60 @@ def __init__(self, width=20, height=20): The agents next state is first determined before updating the grid """ super().__init__() - self._grid = mesa.experimental.cell_space.OrthogonalMooreGrid( - (width, height), torus=False - ) + self._grid = mesa.space.SingleGrid(width, height, torus=False) # self._grid.coord_iter() # --> should really not return content + col + row # -->but only col & row # for (contents, col, row) in self._grid.coord_iter(): # replaced content with _ to appease linter - for cell in self._grid.all_cells: - agent = ColorCell(self, ColorCell.OPINIONS[self.random.randrange(0, 16)]) - agent.move_to(cell) + for _, (row, col) in self._grid.coord_iter(): + + cell = ColorCell( + (row, col), row+col*row, self, ColorCell.OPINIONS[self.random.randrange(0, 16)] + ) + self._grid.place_agent(cell, (row, col)) self.running = True + + def step(self): """ - Perform the model step in two stages: + Perform the model step in three stages: - First, all agents determine their next opinion based on their neighbors current opinions + - A random event is introduced to potentially influence agent opinions. - Then, all agents update their opinion to the next opinion """ self.agents.do("determine_opinion") + self.random_event() self.agents.do("assume_opinion") + def random_event(self, event_chance=0.001): + """ + Introduce a random event that may influence agent opinions. + If a randomly generated value is less than `event_chance`, a change in opinion will be triggered for agents by + calling `change_opinion`. + """ + if self.random.random() < event_chance: + self.change_opinion() + + def change_opinion(self, radius=2): + """ + Apply an opinion change to agents within a specified radius around a random grid location. + - Select random coordinates (x, y) within the grid boundaries. + - Retrieve agents located within a Moore neighborhood of (x, y) using the specified radius (default is 2), + which includes all agents within this distance as well as the center cell. + - Randomly choose an opinion from `ColorCell.OPINIONS`. + - Set each agent's `next_state` within this neighborhood to the selected opinion. + """ + x, y = self.random.randrange(self.grid.width), self.random.randrange(self.grid.height) + agents = list(self.grid.iter_neighbors((x, y), moore=True, include_center=True, radius=radius)) + opinion = ColorCell.OPINIONS[self.random.randrange(0, 16)] + + for agent in agents: + agent.set_next_state(opinion) + @property def grid(self): """ diff --git a/examples/color_patches/color_patches/server.py b/examples/color_patches/color_patches/server.py index 34d0d744..da17b099 100644 --- a/examples/color_patches/color_patches/server.py +++ b/examples/color_patches/color_patches/server.py @@ -2,8 +2,7 @@ handles the definition of the canvas parameters and the drawing of the model representation on the canvas """ - -# import webbrowser +import webbrowser import mesa @@ -30,7 +29,7 @@ grid_rows = 50 -grid_cols = 25 +grid_cols = 50 cell_size = 10 canvas_width = grid_rows * cell_size canvas_height = grid_cols * cell_size @@ -65,4 +64,4 @@ def color_patch_draw(cell): {"width": grid_rows, "height": grid_cols}, ) -# webbrowser.open('http://127.0.0.1:8521') # TODO: make this configurable +webbrowser.open('http://127.0.0.1:8521') # TODO: make this configurable From cbdd4442ceab4a15b04506777fa8b998d2aa1d75 Mon Sep 17 00:00:00 2001 From: olegs Date: Tue, 29 Oct 2024 20:38:43 +0200 Subject: [PATCH 2/3] Add stubborns and random events --- examples/color_patches/color_patches/server.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/color_patches/color_patches/server.py b/examples/color_patches/color_patches/server.py index da17b099..574bbd15 100644 --- a/examples/color_patches/color_patches/server.py +++ b/examples/color_patches/color_patches/server.py @@ -64,4 +64,4 @@ def color_patch_draw(cell): {"width": grid_rows, "height": grid_cols}, ) -webbrowser.open('http://127.0.0.1:8521') # TODO: make this configurable +webbrowser.open('http://127.0.0.1:8521') From 0585b999c4f9f2dee861b9fe2fbadb3773ec3491 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 29 Oct 2024 22:54:15 +0000 Subject: [PATCH 3/3] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- examples/color_patches/color_patches/model.py | 20 +++++++++++++------ .../color_patches/color_patches/server.py | 3 ++- 2 files changed, 16 insertions(+), 7 deletions(-) diff --git a/examples/color_patches/color_patches/model.py b/examples/color_patches/color_patches/model.py index ed4bf3ab..bcef6284 100644 --- a/examples/color_patches/color_patches/model.py +++ b/examples/color_patches/color_patches/model.py @@ -12,6 +12,7 @@ class ColorCell(mesa.Agent): """ Represents a cell's opinion (visualized by a color) """ + OPINIONS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15] def __init__(self, pos, unique_id, model, initial_state): @@ -91,16 +92,16 @@ def __init__(self, width=20, height=20): # for (contents, col, row) in self._grid.coord_iter(): # replaced content with _ to appease linter for _, (row, col) in self._grid.coord_iter(): - cell = ColorCell( - (row, col), row+col*row, self, ColorCell.OPINIONS[self.random.randrange(0, 16)] + (row, col), + row + col * row, + self, + ColorCell.OPINIONS[self.random.randrange(0, 16)], ) self._grid.place_agent(cell, (row, col)) self.running = True - - def step(self): """ Perform the model step in three stages: @@ -130,8 +131,15 @@ def change_opinion(self, radius=2): - Randomly choose an opinion from `ColorCell.OPINIONS`. - Set each agent's `next_state` within this neighborhood to the selected opinion. """ - x, y = self.random.randrange(self.grid.width), self.random.randrange(self.grid.height) - agents = list(self.grid.iter_neighbors((x, y), moore=True, include_center=True, radius=radius)) + x, y = ( + self.random.randrange(self.grid.width), + self.random.randrange(self.grid.height), + ) + agents = list( + self.grid.iter_neighbors( + (x, y), moore=True, include_center=True, radius=radius + ) + ) opinion = ColorCell.OPINIONS[self.random.randrange(0, 16)] for agent in agents: diff --git a/examples/color_patches/color_patches/server.py b/examples/color_patches/color_patches/server.py index 574bbd15..f7a7d45a 100644 --- a/examples/color_patches/color_patches/server.py +++ b/examples/color_patches/color_patches/server.py @@ -2,6 +2,7 @@ handles the definition of the canvas parameters and the drawing of the model representation on the canvas """ + import webbrowser import mesa @@ -64,4 +65,4 @@ def color_patch_draw(cell): {"width": grid_rows, "height": grid_cols}, ) -webbrowser.open('http://127.0.0.1:8521') +webbrowser.open("http://127.0.0.1:8521")