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)]
surfaces_to_update = []
for cell in self._cellular_automaton.grid.get_active_cells().values():
if not cell.is_dirty():
for cell in self._cellular_automaton.grid.get_cells().values():
if not cell.is_set_for_redrawing():
continue
cell_coordinate = cell.coordinate
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[1] += 20
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)
def main_loop(self):
@ -53,7 +53,7 @@ class WorldGeneratorWindow:
while running:
time_ca_start = time.time()
self._cellular_automaton.evolve()
self._cellular_automaton.evolve_x_times(10)
time_ca_end = time.time()
self._display_cellular_automaton()
time_ds_end = time.time()
@ -86,17 +86,17 @@ class TestRule(Rule):
cell.set_status_for_iteration([rand], iteration_index + 1)
if rand != 0:
active = True
cell.set_dirty()
cell.set_for_redraw()
elif len(neighbors) == 8:
left_neighbour_status = neighbors[3].get_status_for_iteration(last_iteration)
active = cell.set_status_for_iteration(left_neighbour_status, iteration_index)
if active:
cell.set_dirty()
cell.set_for_redraw()
return active
if __name__ == "__main__":
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.main_loop()

View File

@ -7,19 +7,22 @@ class Cell:
self._dirty = False
def set_neighbours(self, neighbours: list):
""" Set new cells as neighbour of this cell.
:param neighbours: A List of Cell names.
"""
self.neighbours = neighbours
def is_dirty(self):
def is_set_for_redrawing(self):
return self._dirty
def set_dirty(self):
def set_for_redraw(self):
self._dirty = True
def release_dirty(self):
def release_from_redraw(self):
self._dirty = False
def set_status_for_iteration(self, new_status, iteration):
""" Will set the new status for Iteration.
def set_status_of_iteration(self, new_status, iteration):
""" Will set the new status for the iteration modulo two.
:param new_status: The new status to set.
:param iteration: Uses the iteration index, to differ between current and next state.
:return True if status has changed.
@ -28,8 +31,8 @@ class Cell:
return self._status[0] != self._status[1]
def get_status_for_iteration(self, iteration):
""" Will return the status for the iteration.
def get_status_of_iteration(self, iteration):
""" Will return the status for the iteration modulo two.
:param iteration: Uses the iteration index, to differ between current and next state.
: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_neighborhood import Neighborhood
@ -16,19 +14,24 @@ class Grid:
self._active_cells = self._cells.copy()
self._set_all_cells_active()
def get_names_of_active_cells(self):
def get_active_cell_names(self):
return list(self._active_cells.keys())
def get_active_cells(self):
return self._active_cells
def clear_active_cells(self):
self._active_cells.clear()
def get_cells(self):
return self._cells
def set_cell_and_neighbours_active(self, cell_info: list):
self._active_cells[cell_info[0].name] = cell_info[0]
for neighbour in cell_info[1]:
self._active_cells[neighbour.name] = neighbour
def clear_active_cells(self):
self._active_cells = {}
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):
cell = self._cells[cell_name]
@ -84,18 +87,10 @@ class Grid:
try:
self._recursive_step_down_dimensions(coordinate.copy(), dimension_index, self._set_cell_neighbours)
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]
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):
if coordinate is None:

View File

@ -9,19 +9,28 @@ class EdgeRule(Enum):
class Neighborhood:
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.edge_rule = edge_rule
self.dimensions = []
self.grid_dimensions = []
def get_relative_neighbor_coordinates(self):
return self._neighbors
def get_neighbor_coordinates(self, cell_coordinate, dimensions):
self.dimensions = dimensions
def calculate_cell_neighbor_coordinates(self, cell_coordinate, grid_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):
return []
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):
if self.edge_rule == EdgeRule.IGNORE_EDGE_CELLS and self._is_coordinate_on_an_edge(coordinate):
@ -29,37 +38,42 @@ class Neighborhood:
return False
def _is_coordinate_on_an_edge(self, coordinate):
for nd, d in zip(coordinate, self.dimensions):
if nd == 0 or nd == d - 1:
for neighbor_dimension, dimension in zip(coordinate, self.grid_dimensions):
if neighbor_dimension == 0 or neighbor_dimension == dimension - 1:
return True
return False
def _apply_edge_rule_to_neighbours_of(self, cell_coordinate):
def _apply_edge_rule_to_neighbours(self, coordinate):
remaining_neighbours = []
for neighbour in self._neighbors:
if not self._does_ignore_edge_cell_neighbours_rule_apply(neighbour, cell_coordinate):
remaining_neighbours.append(self._calculate_neighbour_coordinate(neighbour, cell_coordinate))
if not self._does_ignore_edge_cell_neighbours_rule_apply(neighbour, coordinate):
remaining_neighbours.append(self._calculate_neighbour_coordinate(neighbour, coordinate))
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:
for rel_nd, cd, d in zip(neighbour, cell_coordinate, self.dimensions):
nd = cd + rel_nd
if nd < 0 or nd >= d:
for rel_neighbour_dim, cell_dim, dim in zip(neighbour, coordinate, self.grid_dimensions):
neighbor_dimension = cell_dim + rel_neighbour_dim
if neighbor_dimension < 0 or neighbor_dimension >= dim:
return True
return False
def _calculate_neighbour_coordinate(self, neighbour, cell_coordinate):
new_coordinate = []
for rel_nd, cd, d in zip(neighbour, cell_coordinate, self.dimensions):
nd = cd + rel_nd
if nd < 0:
nd = d - 1
elif nd >= d:
nd = 0
new_coordinate.append(nd)
for rel_neighbour_dim, cell_dim, dim in zip(neighbour, cell_coordinate, self.grid_dimensions):
neighbor_dim = cell_dim + rel_neighbour_dim
neighbor_dim = self._calculate_neighbour_dimension_of_edge_cells(dim, neighbor_dim)
new_coordinate.append(neighbor_dim)
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):
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_rule import Rule
from cellular_automaton.ca_neighborhood import Neighborhood
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.rule = rule_
self._thread_count = thread_count
self.evolution_rule = evolution_rule
self.iteration = 0
self.test_number = 0
def set_rule(self, rule: Rule):
self.rule = rule
def set_thread_count(self, thread_count: int):
self._thread_count = thread_count
""" Set new evolution rule.
:param rule:
:return:
"""
self.evolution_rule = rule
def get_iteration_index(self):
""" Get the count of evolution cycles done.
:return: Evolution steps done.
"""
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):
""" Evolves all active cells for one time step.
:return: True if all cells are inactive.
"""
if self._all_cells_are_inactive():
if self._is_evolution_finished():
return True
else:
self.iteration += 1
self._start_multi_process_cell_evolution()
self._evolve_all_active_cells()
return False
def _start_multi_process_cell_evolution(self):
""" Evolves the cells in separate threads """
lists_of_cell_names = self._create_list_of_cell_names_lists_for_all_threads()
def _evolve_all_active_cells(self):
active_cells = self.grid.get_active_cell_names()
self.grid.clear_active_cells()
jobs = self._start_treads_to_evolve_grid(lists_of_cell_names)
self._wait_for_all_threads_to_finish(jobs)
print(self.test_number)
self._evolve_cells(active_cells)
def _all_cells_are_inactive(self):
return len(self.grid.get_names_of_active_cells()) == 0
def _is_evolution_finished(self):
return len(self.grid.get_active_cell_names()) == 0
def _create_list_of_cell_names_lists_for_all_threads(self):
active_cells = self.grid.get_names_of_active_cells()
cell_count_per_thread = int(len(active_cells) / self._thread_count)
return self._divide_list_in_parts_of_size(active_cells, cell_count_per_thread)
@staticmethod
def _divide_list_in_parts_of_size(active_cells: list, cell_count_per_thread: int):
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)
def _evolve_cells(self, cell_names: list):
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)
cell_info = self.grid.get_cell_and_neighbors(cell_name)
active = self.evolution_rule.evolve_cell(cell_info[0], cell_info[1], self.iteration)
if active:
cellular_automaton.grid.set_cell_and_neighbours_active(cell_info)
self.grid.set_cells_active(cell_info[0] + cell_info[1])