refactoring and comments

This commit is contained in:
Richard Feistenauer 2018-12-08 22:33:13 +01:00
parent 530072b1d5
commit 0960991e47
5 changed files with 98 additions and 105 deletions

View File

@ -31,8 +31,8 @@ class WorldGeneratorWindow:
cell_size = [x / y for x, y in zip(self.grid_size, grid_dimension)] cell_size = [x / y for x, y in zip(self.grid_size, grid_dimension)]
surfaces_to_update = [] surfaces_to_update = []
for cell in self._cellular_automaton.grid.get_active_cells().values(): for cell in self._cellular_automaton.grid.get_cells().values():
if not cell.is_dirty(): if not cell.is_set_for_redrawing():
continue continue
cell_coordinate = cell.coordinate cell_coordinate = cell.coordinate
status = cell.get_status_for_iteration(self._cellular_automaton.get_iteration_index()) status = cell.get_status_for_iteration(self._cellular_automaton.get_iteration_index())
@ -45,7 +45,7 @@ class WorldGeneratorWindow:
surface_pos = [x * y for x, y in zip(cell_size, cell_coordinate)] surface_pos = [x * y for x, y in zip(cell_size, cell_coordinate)]
surface_pos[1] += 20 surface_pos[1] += 20
surfaces_to_update.append(self.screen.fill(cell_color, (surface_pos, cell_size))) surfaces_to_update.append(self.screen.fill(cell_color, (surface_pos, cell_size)))
cell.release_dirty() cell.release_from_redraw()
pygame.display.update(surfaces_to_update) pygame.display.update(surfaces_to_update)
def main_loop(self): def main_loop(self):
@ -53,7 +53,7 @@ class WorldGeneratorWindow:
while running: while running:
time_ca_start = time.time() time_ca_start = time.time()
self._cellular_automaton.evolve() self._cellular_automaton.evolve_x_times(10)
time_ca_end = time.time() time_ca_end = time.time()
self._display_cellular_automaton() self._display_cellular_automaton()
time_ds_end = time.time() time_ds_end = time.time()
@ -86,17 +86,17 @@ class TestRule(Rule):
cell.set_status_for_iteration([rand], iteration_index + 1) cell.set_status_for_iteration([rand], iteration_index + 1)
if rand != 0: if rand != 0:
active = True active = True
cell.set_dirty() cell.set_for_redraw()
elif len(neighbors) == 8: elif len(neighbors) == 8:
left_neighbour_status = neighbors[3].get_status_for_iteration(last_iteration) left_neighbour_status = neighbors[3].get_status_for_iteration(last_iteration)
active = cell.set_status_for_iteration(left_neighbour_status, iteration_index) active = cell.set_status_for_iteration(left_neighbour_status, iteration_index)
if active: if active:
cell.set_dirty() cell.set_for_redraw()
return active return active
if __name__ == "__main__": if __name__ == "__main__":
rule = TestRule() rule = TestRule()
ca = CellularAutomaton([10, 10], MooreNeighborhood(EdgeRule.IGNORE_EDGE_CELLS), rule, thread_count=2) ca = CellularAutomaton([400, 400], MooreNeighborhood(EdgeRule.IGNORE_EDGE_CELLS), rule)
ca_window = WorldGeneratorWindow([1000, 730], ca) ca_window = WorldGeneratorWindow([1000, 730], ca)
ca_window.main_loop() ca_window.main_loop()

View File

@ -7,19 +7,22 @@ class Cell:
self._dirty = False self._dirty = False
def set_neighbours(self, neighbours: list): def set_neighbours(self, neighbours: list):
""" Set new cells as neighbour of this cell.
:param neighbours: A List of Cell names.
"""
self.neighbours = neighbours self.neighbours = neighbours
def is_dirty(self): def is_set_for_redrawing(self):
return self._dirty return self._dirty
def set_dirty(self): def set_for_redraw(self):
self._dirty = True self._dirty = True
def release_dirty(self): def release_from_redraw(self):
self._dirty = False self._dirty = False
def set_status_for_iteration(self, new_status, iteration): def set_status_of_iteration(self, new_status, iteration):
""" Will set the new status for Iteration. """ Will set the new status for the iteration modulo two.
:param new_status: The new status to set. :param new_status: The new status to set.
:param iteration: Uses the iteration index, to differ between current and next state. :param iteration: Uses the iteration index, to differ between current and next state.
:return True if status has changed. :return True if status has changed.
@ -28,8 +31,8 @@ class Cell:
return self._status[0] != self._status[1] return self._status[0] != self._status[1]
def get_status_for_iteration(self, iteration): def get_status_of_iteration(self, iteration):
""" Will return the status for the iteration. """ Will return the status for the iteration modulo two.
:param iteration: Uses the iteration index, to differ between current and next state. :param iteration: Uses the iteration index, to differ between current and next state.
:return The status for this iteration. :return The status for this iteration.
""" """

View File

@ -1,5 +1,3 @@
import sys
from cellular_automaton.ca_cell import Cell from cellular_automaton.ca_cell import Cell
from cellular_automaton.ca_neighborhood import Neighborhood from cellular_automaton.ca_neighborhood import Neighborhood
@ -16,19 +14,24 @@ class Grid:
self._active_cells = self._cells.copy() self._active_cells = self._cells.copy()
self._set_all_cells_active() self._set_all_cells_active()
def get_names_of_active_cells(self): def get_active_cell_names(self):
return list(self._active_cells.keys()) return list(self._active_cells.keys())
def get_active_cells(self): def get_active_cells(self):
return self._active_cells return self._active_cells
def clear_active_cells(self): def get_cells(self):
self._active_cells.clear() return self._cells
def set_cell_and_neighbours_active(self, cell_info: list): def clear_active_cells(self):
self._active_cells[cell_info[0].name] = cell_info[0] self._active_cells = {}
for neighbour in cell_info[1]:
self._active_cells[neighbour.name] = neighbour def set_cells_active(self, cells: list):
""" Consider the cells in the next evolution cycle.
:param cells: A list of Cell objects, that shall be considered in the next evolution cycle.
"""
for cell in cells:
self._active_cells[cell.name] = cells
def get_cell_and_neighbors(self, cell_name): def get_cell_and_neighbors(self, cell_name):
cell = self._cells[cell_name] cell = self._cells[cell_name]
@ -84,18 +87,10 @@ class Grid:
try: try:
self._recursive_step_down_dimensions(coordinate.copy(), dimension_index, self._set_cell_neighbours) self._recursive_step_down_dimensions(coordinate.copy(), dimension_index, self._set_cell_neighbours)
except IndexError: except IndexError:
neighbours_coordinates = self._neighborhood.get_neighbor_coordinates(coordinate, self._dimension) neighbours_coordinates = self._neighborhood.calculate_cell_neighbor_coordinates(coordinate, self._dimension)
neighbour_names = [self._cells[_join_coordinate(nc)].name for nc in neighbours_coordinates] neighbour_names = [self._cells[_join_coordinate(nc)].name for nc in neighbours_coordinates]
self._cells[_join_coordinate(coordinate)].set_neighbours(neighbour_names) self._cells[_join_coordinate(coordinate)].set_neighbours(neighbour_names)
# def __sizeof__(self):
# size = 0
# for cell in self._cells.values():
# size += sys.getsizeof(cell)
# size += sys.getsizeof(self._dimension) + sys.getsizeof(self._cells) + sys.getsizeof(self._active_cells)
# return size
def _instantiate_coordinate_if_necessary(coordinate): def _instantiate_coordinate_if_necessary(coordinate):
if coordinate is None: if coordinate is None:

View File

@ -9,19 +9,28 @@ class EdgeRule(Enum):
class Neighborhood: class Neighborhood:
def __init__(self, neighbors: list, edge_rule: EdgeRule): def __init__(self, neighbors: list, edge_rule: EdgeRule):
""" Defines a neighborhood for cells.
:param neighbors: List of relative coordinates for the neighbors.
:param edge_rule: A EdgeRule to define, how Cells on the edge of the grid will be handled.
"""
self._neighbors = neighbors self._neighbors = neighbors
self.edge_rule = edge_rule self.edge_rule = edge_rule
self.dimensions = [] self.grid_dimensions = []
def get_relative_neighbor_coordinates(self): def get_relative_neighbor_coordinates(self):
return self._neighbors return self._neighbors
def get_neighbor_coordinates(self, cell_coordinate, dimensions): def calculate_cell_neighbor_coordinates(self, cell_coordinate, grid_dimensions):
self.dimensions = dimensions """ Get a list of coordinates for the cell neighbors. The EdgeRule can reduce the returned neighbor count.
:param cell_coordinate: The coordinate of the cell to get the neighbors
:param grid_dimensions: The dimensions of the grid, to apply edge the rule.
:return:
"""
self.grid_dimensions = grid_dimensions
if self._does_ignore_edge_cell_rule_apply(cell_coordinate): if self._does_ignore_edge_cell_rule_apply(cell_coordinate):
return [] return []
else: else:
return self._apply_edge_rule_to_neighbours_of(cell_coordinate) return self._apply_edge_rule_to_neighbours(cell_coordinate)
def _does_ignore_edge_cell_rule_apply(self, coordinate): def _does_ignore_edge_cell_rule_apply(self, coordinate):
if self.edge_rule == EdgeRule.IGNORE_EDGE_CELLS and self._is_coordinate_on_an_edge(coordinate): if self.edge_rule == EdgeRule.IGNORE_EDGE_CELLS and self._is_coordinate_on_an_edge(coordinate):
@ -29,37 +38,42 @@ class Neighborhood:
return False return False
def _is_coordinate_on_an_edge(self, coordinate): def _is_coordinate_on_an_edge(self, coordinate):
for nd, d in zip(coordinate, self.dimensions): for neighbor_dimension, dimension in zip(coordinate, self.grid_dimensions):
if nd == 0 or nd == d - 1: if neighbor_dimension == 0 or neighbor_dimension == dimension - 1:
return True return True
return False return False
def _apply_edge_rule_to_neighbours_of(self, cell_coordinate): def _apply_edge_rule_to_neighbours(self, coordinate):
remaining_neighbours = [] remaining_neighbours = []
for neighbour in self._neighbors: for neighbour in self._neighbors:
if not self._does_ignore_edge_cell_neighbours_rule_apply(neighbour, cell_coordinate): if not self._does_ignore_edge_cell_neighbours_rule_apply(neighbour, coordinate):
remaining_neighbours.append(self._calculate_neighbour_coordinate(neighbour, cell_coordinate)) remaining_neighbours.append(self._calculate_neighbour_coordinate(neighbour, coordinate))
return remaining_neighbours return remaining_neighbours
def _does_ignore_edge_cell_neighbours_rule_apply(self, neighbour, cell_coordinate): def _does_ignore_edge_cell_neighbours_rule_apply(self, neighbour, coordinate):
if self.edge_rule == EdgeRule.IGNORE_MISSING_NEIGHBORS_OF_EDGE_CELLS: if self.edge_rule == EdgeRule.IGNORE_MISSING_NEIGHBORS_OF_EDGE_CELLS:
for rel_nd, cd, d in zip(neighbour, cell_coordinate, self.dimensions): for rel_neighbour_dim, cell_dim, dim in zip(neighbour, coordinate, self.grid_dimensions):
nd = cd + rel_nd neighbor_dimension = cell_dim + rel_neighbour_dim
if nd < 0 or nd >= d: if neighbor_dimension < 0 or neighbor_dimension >= dim:
return True return True
return False return False
def _calculate_neighbour_coordinate(self, neighbour, cell_coordinate): def _calculate_neighbour_coordinate(self, neighbour, cell_coordinate):
new_coordinate = [] new_coordinate = []
for rel_nd, cd, d in zip(neighbour, cell_coordinate, self.dimensions): for rel_neighbour_dim, cell_dim, dim in zip(neighbour, cell_coordinate, self.grid_dimensions):
nd = cd + rel_nd neighbor_dim = cell_dim + rel_neighbour_dim
if nd < 0: neighbor_dim = self._calculate_neighbour_dimension_of_edge_cells(dim, neighbor_dim)
nd = d - 1 new_coordinate.append(neighbor_dim)
elif nd >= d:
nd = 0
new_coordinate.append(nd)
return new_coordinate return new_coordinate
@staticmethod
def _calculate_neighbour_dimension_of_edge_cells(dim, neighbor_dim):
if neighbor_dim < 0:
neighbor_dim = dim - 1
elif neighbor_dim >= dim:
neighbor_dim = 0
return neighbor_dim
class MooreNeighborhood(Neighborhood): class MooreNeighborhood(Neighborhood):
def __init__(self, edge_rule: EdgeRule): def __init__(self, edge_rule: EdgeRule):

View File

@ -1,81 +1,62 @@
import multiprocessing
import time
from cellular_automaton.ca_grid import Grid from cellular_automaton.ca_grid import Grid
from cellular_automaton.ca_rule import Rule from cellular_automaton.ca_rule import Rule
from cellular_automaton.ca_neighborhood import Neighborhood from cellular_automaton.ca_neighborhood import Neighborhood
class CellularAutomaton: class CellularAutomaton:
def __init__(self, dimension: list, neighborhood: Neighborhood, rule_: Rule=None, thread_count: int=4): def __init__(self, dimension: list, neighborhood: Neighborhood, evolution_rule: Rule=None):
self.grid = Grid(dimension, neighborhood) self.grid = Grid(dimension, neighborhood)
self.rule = rule_ self.evolution_rule = evolution_rule
self._thread_count = thread_count
self.iteration = 0 self.iteration = 0
self.test_number = 0 self.test_number = 0
def set_rule(self, rule: Rule): def set_rule(self, rule: Rule):
self.rule = rule """ Set new evolution rule.
:param rule:
def set_thread_count(self, thread_count: int): :return:
self._thread_count = thread_count """
self.evolution_rule = rule
def get_iteration_index(self): def get_iteration_index(self):
""" Get the count of evolution cycles done.
:return: Evolution steps done.
"""
return self.iteration return self.iteration
def evolve_x_times(self, evolutions: int):
""" Evolve all cells for x time steps.
:param evolutions: the count of evolutions done.
:return: True if all cells are inactive
"""
for evo in range(evolutions):
finished = self.evolve()
if finished:
return True
return False
def evolve(self): def evolve(self):
""" Evolves all active cells for one time step. """ Evolves all active cells for one time step.
:return: True if all cells are inactive. :return: True if all cells are inactive.
""" """
if self._all_cells_are_inactive(): if self._is_evolution_finished():
return True return True
else: else:
self.iteration += 1 self.iteration += 1
self._start_multi_process_cell_evolution() self._evolve_all_active_cells()
return False return False
def _start_multi_process_cell_evolution(self): def _evolve_all_active_cells(self):
""" Evolves the cells in separate threads """ active_cells = self.grid.get_active_cell_names()
lists_of_cell_names = self._create_list_of_cell_names_lists_for_all_threads()
self.grid.clear_active_cells() self.grid.clear_active_cells()
jobs = self._start_treads_to_evolve_grid(lists_of_cell_names) self._evolve_cells(active_cells)
self._wait_for_all_threads_to_finish(jobs)
print(self.test_number)
def _all_cells_are_inactive(self): def _is_evolution_finished(self):
return len(self.grid.get_names_of_active_cells()) == 0 return len(self.grid.get_active_cell_names()) == 0
def _create_list_of_cell_names_lists_for_all_threads(self): def _evolve_cells(self, cell_names: list):
active_cells = self.grid.get_names_of_active_cells() for cell_name in cell_names:
cell_count_per_thread = int(len(active_cells) / self._thread_count) cell_info = self.grid.get_cell_and_neighbors(cell_name)
return self._divide_list_in_parts_of_size(active_cells, cell_count_per_thread) active = self.evolution_rule.evolve_cell(cell_info[0], cell_info[1], self.iteration)
@staticmethod if active:
def _divide_list_in_parts_of_size(active_cells: list, cell_count_per_thread: int): self.grid.set_cells_active(cell_info[0] + cell_info[1])
return [active_cells[i:i + cell_count_per_thread]
for i in range(0, len(active_cells), cell_count_per_thread)]
def _start_treads_to_evolve_grid(self, lists_of_cell_names):
jobs = []
for t in range(self._thread_count):
process = multiprocessing.Process(target=_evolve_cells, args=(self, lists_of_cell_names[t]))
jobs.append(process)
process.start()
return jobs
@staticmethod
def _wait_for_all_threads_to_finish(jobs):
for process in jobs:
process.join()
def _evolve_cells(cellular_automaton, cell_names: list):
time.sleep(2)
cellular_automaton.test_number += 1
print(cellular_automaton.grid)
for cell_name in cell_names:
cell_info = cellular_automaton.grid.get_cell_and_neighbors(cell_name)
active = cellular_automaton.rule.evolve_cell(cell_info[0], cell_info[1], cellular_automaton.iteration)
if active:
cellular_automaton.grid.set_cell_and_neighbours_active(cell_info)