multi processing fast and running with some tests

This commit is contained in:
Richard Feistenauer 2019-02-03 17:17:57 +01:00
parent d1820ebc58
commit 9c855c507a
10 changed files with 199 additions and 99 deletions

11
performance.txt Normal file
View File

@ -0,0 +1,11 @@
# With 100x100 10 times
Try One
TOTAL TIME: 0.1217s
SIZE: 21.7525MB
TOTAL TIME: 0.1171s # Only set on change
SIZE: 21.7525MB # process size 51,4 / main(75,9)
TOTAL TIME: 0.1161s
SIZE: 20.3338MB # removed grid

View File

@ -39,14 +39,16 @@ if __name__ == "__main__":
from cellular_automaton.ca_neighborhood import MooreNeighborhood, EdgeRule
from cellular_automaton.ca_display import PyGameFor2D
from cellular_automaton.ca_grid import Grid
freeze_support()
random.seed(1000)
dimension = [100, 100]
rule = TestRule()
grid = Grid(dimension=[200, 200], # best is 400/400 with 0,2 ca speed and 0,09 redraw
grid = Grid(dimension=dimension, # best is 400/400 with 0,2 ca speed and 0,09 redraw
neighborhood=MooreNeighborhood(EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS),
state_class=MyState)
ca = CellularAutomaton(grid, rule)
ca = CellularAutomaton(tuple(grid.get_cells().values()), dimension, rule)
ca_window = PyGameFor2D(window_size=[1000, 800], cellular_automaton=ca)
ca_processor = CellularAutomatonProcessor(process_count=2, cellular_automaton=ca)
ca_processor = CellularAutomatonProcessor(process_count=4, cellular_automaton=ca)
ca_window.main_loop(cellular_automaton_processor=ca_processor,
ca_iterations_per_draw=5)

View File

@ -1,8 +1,9 @@
from cellular_automaton.ca_cell_state import CellState
from typing import Type
class Cell:
def __init__(self, state_class: CellState.__class__, coordinate: list):
def __init__(self, state_class: Type[CellState], coordinate: list):
self._coordinate = coordinate
self._state = state_class()
self._neighbours = []
@ -16,29 +17,20 @@ class Cell:
def get_coordinate(self):
return self._coordinate
def evolve_if_ready(self, rule):
if self._neighbours_are_younger():
if self._state.is_active():
new_state = rule(self._state.get_current_state(), self.get_neighbour_states())
self.set_new_state_and_activate(new_state)
def evolve_if_ready(self, rule, iteration):
if self._state.is_active(iteration):
new_state = rule(self._state.get_state_of_last_iteration(iteration), self.get_neighbour_states(iteration))
self.set_new_state_and_activate(new_state, iteration)
self._state.increase_age()
def get_neighbour_states(self, index):
return [n.get_state_of_last_iteration(index) for n in self._neighbours]
def _neighbours_are_younger(self):
for n in self._neighbours:
if n.get_age() < self._state.get_age():
return False
return True
def get_neighbour_states(self):
return [n.get_state_of_iteration(self._state.get_age()) for n in self._neighbours]
def set_new_state_and_activate(self, new_state: CellState):
changed = self._state.set_current_state(new_state)
def set_new_state_and_activate(self, new_state: CellState, iteration):
changed = self._state.set_state_of_iteration(new_state, iteration)
if changed:
self._set_active()
self._set_active_for_next_iteration(iteration)
def _set_active(self):
self._state.set_active_for_next_iteration(self._state.get_age() + 1)
def _set_active_for_next_iteration(self, iteration):
self._state.set_active_for_next_iteration(iteration)
for n in self._neighbours:
n.set_active_for_next_iteration(self._state.get_age() + 1)
n.set_active_for_next_iteration(iteration)

View File

@ -1,4 +1,5 @@
from multiprocessing import Array, Value
from multiprocessing import RawArray, RawValue
from ctypes import c_float, c_bool
class CellState:
@ -8,45 +9,35 @@ class CellState:
When using the cellular automaton display, inherit this class and implement get_state_draw_color.
"""
def __init__(self, initial_state=(0., ), draw_first_state=True):
self._state_slots = [Array('d', initial_state) for i in range(self.__class__._state_save_slot_count)]
self._active = Value('i', 1)
self._age = Value('i', 0)
self._state_slots = [RawArray(c_float, initial_state) for i in range(self.__class__._state_save_slot_count)]
self._active = [RawValue(c_bool, False) for i in range(self.__class__._state_save_slot_count)]
self._active[0].value = True
if draw_first_state:
self._dirty = Value('i', 1)
self._dirty = RawValue(c_bool, True)
else:
self._dirty = Value('i', 0)
def get_age(self):
return self._age.value
def get_current_state(self):
return self.get_state_of_iteration(self.get_age())
self._dirty = RawValue(c_bool, False)
def get_state_of_iteration(self, iteration):
""" Will return the state for the iteration modulo number of saved states.
:param iteration: Uses the iteration index, to differ between concurrent states.
:return The state for this iteration.
"""
return self._state_slots[iteration % self.__class__._state_save_slot_count]
return self._state_slots[self.__calculate_slot(iteration)]
def is_active(self):
return self._active.value > self._age.value
def __calculate_slot(self, iteration):
return iteration % self.__class__._state_save_slot_count
def is_active(self, iteration):
return self._active[self.__calculate_slot(iteration)]
def set_active_for_next_iteration(self, iteration):
self._active.value = max(self._active.value, iteration + 1)
def increase_age(self):
with self._age.get_lock():
self._age.value += 1
self._active[self.__calculate_slot(iteration + 1)].value = True
def is_set_for_redraw(self):
return self._dirty != 1
return self._dirty.value
def was_redrawn(self):
self._dirty = 0
def set_current_state(self, new_state):
return self.set_state_of_iteration(new_state, self.get_age() + 1)
self._dirty.value = False
def get_state_of_last_iteration(self, current_iteration_index):
return self.get_state_of_iteration(current_iteration_index - 1)
@ -65,16 +56,13 @@ class CellState:
try:
if current_state[i] != new_state[i]:
changed = True
current_state[i] = new_state[i]
current_state[i] = new_state[i]
except IndexError:
raise IndexError("New State length or type is invalid!")
self._dirty.value |= changed
self._active[self.__calculate_slot(iteration)].value = False
return changed
def get_state_draw_color(self, iteration):
raise NotImplementedError
def __str__(self):
return str(self._state_slots)

View File

@ -2,6 +2,11 @@ import pygame
import time
import operator
import cProfile
import pstats
from pympler import asizeof
from cellular_automaton.cellular_automaton import CellularAutomaton, CellularAutomatonProcessor
@ -19,16 +24,13 @@ class DisplayFor2D:
cell_size = self._calculate_cell_display_size(grid_rect[-2:])
self._display_info = _DisplayInfo(grid_rect[-2:], grid_rect[:2], cell_size, screen)
def set_cellular_automaton(self, cellular_automaton):
self._cellular_automaton = cellular_automaton
def _redraw_cellular_automaton(self):
pygame.display.update(list(_cell_redraw_rectangles(self._cellular_automaton.grid.get_cells().values(),
self._cellular_automaton.evolution_iteration_index,
def redraw_cellular_automaton(self):
pygame.display.update(list(_cell_redraw_rectangles(self._cellular_automaton.cells,
0,
self._display_info)))
def _calculate_cell_display_size(self, grid_size):
grid_dimension = self._cellular_automaton.grid.get_dimension()
grid_dimension = self._cellular_automaton.dimension
return list(map(operator.truediv, grid_size, grid_dimension))
@ -36,7 +38,7 @@ class PyGameFor2D:
def __init__(self, window_size: list, cellular_automaton: CellularAutomaton):
self._window_size = window_size
self._cellular_automaton = cellular_automaton
self._cellular_automaton_proocessor = None
pygame.init()
pygame.display.set_caption("Cellular Automaton")
self._screen = pygame.display.set_mode(self._window_size)
@ -56,20 +58,34 @@ class PyGameFor2D:
def main_loop(self, cellular_automaton_processor: CellularAutomatonProcessor, ca_iterations_per_draw):
running = True
cellular_automaton_processor.evolve()
first = True
while running:
pygame.event.get()
time_ca_start = time.time()
if first:
self._evolve_with_performance(cellular_automaton_processor, time_ca_start)
first = False
else:
cellular_automaton_processor.evolve()
time_ca_end = time.time()
self.ca_display._redraw_cellular_automaton()
self.ca_display.redraw_cellular_automaton()
time_ds_end = time.time()
self._print_process_duration(time_ca_end, time_ca_start, time_ds_end)
time.sleep(0.5)
for event in pygame.event.get():
if event.type == pygame.QUIT:
cellular_automaton_processor.stop()
running = False
def _evolve_with_performance(self, cap, time_ca_start):
size = asizeof.asizeof(self._cellular_automaton)
cProfile.runctx("cap.evolve_x_times(10)", None, locals(), "performance_test")
print("PERFORMANCE")
p = pstats.Stats('performance_test2')
p.strip_dirs()
# sort by cumulative time in a function
p.sort_stats('cumulative').print_stats(10)
# sort by time spent in a function
p.sort_stats('time').print_stats(10)
time_ca_end = time.time()
print("TOTAL TIME: " + "{0:.4f}".format(time_ca_end - time_ca_start) + "s")
print("SIZE: " + "{0:.4f}".format(size / (1024 * 1024)) + "MB")
def _cell_redraw_rectangles(cells, evolution_index, display_info):

View File

@ -17,9 +17,6 @@ class Neighborhood:
self.edge_rule = edge_rule
self.grid_dimensions = []
def get_relative_neighbor_coordinates(self):
return self._neighbors
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

View File

@ -1,39 +1,49 @@
import multiprocessing
from cellular_automaton.ca_grid import Grid
from cellular_automaton.ca_rule import Rule
class CellularAutomaton:
def __init__(self, grid: Grid, evolution_rule: Rule):
self.grid = grid
def __init__(self, cells, dimension, evolution_rule: Rule):
self.cells = cells
self.dimension = dimension
self.evolution_rule = evolution_rule
self.evolution_iteration_index = 0
self.evolution_iteration_index = multiprocessing.RawValue('i', -1)
class CellularAutomatonProcessor:
def __init__(self, cellular_automaton, process_count: int = 1):
self.active = multiprocessing.Value('i', 1)
cells = list(cellular_automaton.grid.get_cells().values())
chunk_size = int(len(cells) / process_count)
self._processes = [multiprocessing.Process(target=_process_routine,
name=str(i),
args=(cells[i*chunk_size:i*chunk_size + chunk_size],
cellular_automaton.evolution_rule,
self.active))
for i in range(process_count)]
for p in self._processes:
p.start()
self.__cellular_automaton = None
self.ca = cellular_automaton
self.evolve_range = range(len(self.ca.cells))
self.pool = multiprocessing.Pool(processes=process_count,
initializer=_init_process,
initargs=(self.ca.cells,
self.ca.evolution_rule,
self.ca.evolution_iteration_index))
for cell in self.ca.cells:
cell.set_neighbours(None)
def stop(self):
self.active.value = 0
for p in self._processes:
p.join()
def evolve_x_times(self, x):
for x in range(x):
self.evolve()
def evolve(self):
self.ca.evolution_iteration_index.value += 1
self.pool.map(_process_routine, self.evolve_range)
def _process_routine(cells, rule, active):
while active.value == 1:
for cell in cells:
cell.evolve_if_ready(rule.evolve_cell)
global_cells = None
global_rule = None
global_iteration = None
def _init_process(cells, rule, index):
global global_rule, global_cells, global_iteration
global_cells = cells
global_rule = rule
global_iteration = index
def _process_routine(i):
global_cells[i].evolve_if_ready(global_rule.evolve_cell, global_iteration.value)

View File

@ -1,12 +1,41 @@
import sys
sys.path.append('../src')
import cellular_automaton.ca_cell_state as cas
import cellular_automaton.ca_cell as cac
import unittest
class TestState(cas.CellState):
def __init__(self):
super().__init__()
class TestCellState(unittest.TestCase):
pass
def setUp(self):
self.cell = cac.Cell(TestState, [])
self.neighbours = [TestState() for x in range(5)]
for neighbour in self.neighbours:
neighbour.set_state_of_iteration((0, ), 0)
self.cell.set_neighbours(self.neighbours)
def cell_and_neighbours_active(self, iteration):
self.neighbours.append(self.cell.get_state())
all_active = True
for state in self.neighbours:
if not state.is_active(iteration):
all_active = False
return all_active
def test_evolve_activation(self):
self.cell.evolve_if_ready((lambda a, b: (1,)), 0)
all_active = self.cell_and_neighbours_active(1)
self.assertTrue(all_active)
def test_evolve_activation_on_no_change(self):
self.cell.evolve_if_ready((lambda a, b: (0,)), 0)
all_active = self.cell_and_neighbours_active(1)
self.assertFalse(all_active)
if __name__ == '__main__':

View File

@ -22,6 +22,21 @@ class TestCellState(unittest.TestCase):
cell_state.set_state_of_iteration(new_state=(1,), iteration=0)
self.assertEqual(tuple(cell_state.get_state_of_iteration(1)), (0,))
def test_redraw_state_on_change(self):
cell_state = cs.CellState(initial_state=(0,), draw_first_state=False)
cell_state.set_state_of_iteration(new_state=(1,), iteration=0)
self.assertTrue(cell_state.is_set_for_redraw())
def test_redraw_state_on_nochange(self):
cell_state = cs.CellState(initial_state=(0,), draw_first_state=False)
cell_state.set_state_of_iteration(new_state=(0,), iteration=0)
self.assertFalse(cell_state.is_set_for_redraw())
def test_active_state_after_set(self):
cell_state = cs.CellState(initial_state=(0,), draw_first_state=False)
cell_state.set_state_of_iteration(new_state=(1,), iteration=0)
self.assertFalse(cell_state.is_active(1))
if __name__ == '__main__':
unittest.main()

40
test/test_neighborhood.py Normal file
View File

@ -0,0 +1,40 @@
import sys
sys.path.append('../src')
import cellular_automaton.ca_neighborhood as csn
import unittest
class TestCellState(unittest.TestCase):
def check_neighbors(self, neighborhood, neighborhood_sets):
for neighborhood_set in neighborhood_sets:
neighbors = neighborhood.calculate_cell_neighbor_coordinates(neighborhood_set[0], [3, 3])
if neighborhood_set[1] != neighbors:
print((neighborhood_set[1]), (neighbors))
return False
return True
def test_ignore_missing_neighbors(self):
neighborhood = csn.MooreNeighborhood(csn.EdgeRule.IGNORE_MISSING_NEIGHBORS_OF_EDGE_CELLS)
n00 = [[0, 0], [[1, 0], [0, 1], [1, 1]]]
n11 = [[1, 1], [[0, 0], [1, 0], [2, 0], [0, 1], [2, 1], [0, 2], [1, 2], [2, 2]]]
n22 = [[2, 2], [[1, 1], [2, 1], [1, 2]]]
self.assertTrue(self.check_neighbors(neighborhood, [n00, n11, n22]))
def test_ignore_edge_cells(self):
neighborhood = csn.MooreNeighborhood(csn.EdgeRule.IGNORE_EDGE_CELLS)
n00 = [[0, 0], []]
n11 = [[1, 1], [[0, 0], [1, 0], [2, 0], [0, 1], [2, 1], [0, 2], [1, 2], [2, 2]]]
n22 = [[2, 2], []]
self.assertTrue(self.check_neighbors(neighborhood, [n00, n11, n22]))
def test_cyclic_dimensions(self):
neighborhood = csn.MooreNeighborhood(csn.EdgeRule.FIRST_AND_LAST_CELL_OF_DIMENSION_ARE_NEIGHBORS)
n00 = [[0, 0], [[2, 2], [0, 2], [1, 2], [2, 0], [1, 0], [2, 1], [0, 1], [1, 1]]]
n11 = [[1, 1], [[0, 0], [1, 0], [2, 0], [0, 1], [2, 1], [0, 2], [1, 2], [2, 2]]]
n22 = [[2, 2], [[1, 1], [2, 1], [0, 1], [1, 2], [0, 2], [1, 0], [2, 0], [0, 0]]]
self.assertTrue(self.check_neighbors(neighborhood, [n00, n11, n22]))
if __name__ == '__main__':
unittest.main()