multi processing fast and running with some tests
This commit is contained in:
parent
d1820ebc58
commit
9c855c507a
11
performance.txt
Normal file
11
performance.txt
Normal 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
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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__':
|
||||
|
@ -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
40
test/test_neighborhood.py
Normal 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()
|
Loading…
Reference in New Issue
Block a user