diff --git a/performance.txt b/performance.txt index 3bf28aa..c9fec8b 100644 --- a/performance.txt +++ b/performance.txt @@ -8,4 +8,10 @@ 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 \ No newline at end of file +SIZE: 20.3338MB # removed grid + +TOTAL TIME: 0.1792s # probably wrong calculated (asizeof was in there) +SIZE: 20.2575MB # Factory instead of grid + +TOTAL TIME: 0.1152s # dict instead of list for cells +SIZE: 20.2575MB # process size 53 / 75,8 \ No newline at end of file diff --git a/scripts/main_ui.py b/scripts/main_ui.py index 80c85a3..a01e5cc 100644 --- a/scripts/main_ui.py +++ b/scripts/main_ui.py @@ -4,7 +4,7 @@ from cellular_automaton.ca_cell_state import CellState from cellular_automaton.ca_rule import Rule from cellular_automaton.cellular_automaton import CellularAutomaton, CellularAutomatonProcessor -from cellular_automaton.ca_factory import Factory +from cellular_automaton.ca_factory import CAFactory class TestRule(Rule): @@ -35,8 +35,7 @@ class MyState(CellState): def make_cellular_automaton(dimension, neighborhood, rule, state_class): - ca_factory = Factory() - cells = ca_factory.make_cellular_automaton(dimension=dimension, neighborhood=neighborhood, state_class=state_class) + cells = CAFactory.make_cellular_automaton(dimension=dimension, neighborhood=neighborhood, state_class=state_class) return CellularAutomaton(cells, dimension, rule) diff --git a/scripts/performance_test b/scripts/performance_test new file mode 100644 index 0000000..112495e Binary files /dev/null and b/scripts/performance_test differ diff --git a/src/cellular_automaton/__init__.py b/src/cellular_automaton/__init__.py index e69de29..0f1e175 100644 --- a/src/cellular_automaton/__init__.py +++ b/src/cellular_automaton/__init__.py @@ -0,0 +1,7 @@ +from .ca_cell import * +from .ca_cell_state import * +from .ca_display import * +from .ca_factory import * +from .ca_neighborhood import * +from .ca_rule import * +from .cellular_automaton import * diff --git a/src/cellular_automaton/ca_cell.py b/src/cellular_automaton/ca_cell.py index 53f1e82..b072e14 100644 --- a/src/cellular_automaton/ca_cell.py +++ b/src/cellular_automaton/ca_cell.py @@ -3,34 +3,34 @@ from typing import Type class Cell: - def __init__(self, state_class: Type[CellState], coordinate: list): + def __init__(self, state_class: Type[CellState], coordinate): self._coordinate = coordinate - self._state = state_class() - self._neighbours = [] + self.state = state_class() + self.neighbours = [] def set_neighbours(self, neighbours): - self._neighbours = neighbours + self.neighbours = neighbours def get_state(self): - return self._state + return self.state def get_coordinate(self): return self._coordinate 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)) + 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) def get_neighbour_states(self, index): - return [n.get_state_of_last_iteration(index) for n in self._neighbours] + return [n.get_state_of_last_iteration(index) for n in self.neighbours] def set_new_state_and_activate(self, new_state: CellState, iteration): - changed = self._state.set_state_of_iteration(new_state, iteration) + changed = self.state.set_state_of_iteration(new_state, iteration) if changed: self._set_active_for_next_iteration(iteration) def _set_active_for_next_iteration(self, iteration): - self._state.set_active_for_next_iteration(iteration) - for n in self._neighbours: + self.state.set_active_for_next_iteration(iteration) + for n in self.neighbours: n.set_active_for_next_iteration(iteration) diff --git a/src/cellular_automaton/ca_display.py b/src/cellular_automaton/ca_display.py index 77de284..84d01cb 100644 --- a/src/cellular_automaton/ca_display.py +++ b/src/cellular_automaton/ca_display.py @@ -60,11 +60,12 @@ class PyGameFor2D: 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) + self._evolve_with_performance(cellular_automaton_processor) first = False else: cellular_automaton_processor.evolve() @@ -73,9 +74,11 @@ class PyGameFor2D: time_ds_end = time.time() self._print_process_duration(time_ca_end, time_ca_start, time_ds_end) - def _evolve_with_performance(self, cap, time_ca_start): + def _evolve_with_performance(self, cap): size = asizeof.asizeof(self._cellular_automaton) + time_ca_start = time.time() cProfile.runctx("cap.evolve_x_times(10)", None, locals(), "performance_test") + time_ca_end = time.time() print("PERFORMANCE") p = pstats.Stats('performance_test') p.strip_dirs() @@ -83,7 +86,6 @@ class PyGameFor2D: 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") diff --git a/src/cellular_automaton/ca_factory.py b/src/cellular_automaton/ca_factory.py index cdec237..d70723d 100644 --- a/src/cellular_automaton/ca_factory.py +++ b/src/cellular_automaton/ca_factory.py @@ -1,61 +1,35 @@ from cellular_automaton.ca_cell import Cell, CellState from cellular_automaton.ca_neighborhood import Neighborhood from typing import Type +import itertools -class Factory: - def __init__(self): - self._dimension = None - self._state_class = None - self._cells = {} - - def make_cellular_automaton(self, - dimension, +class CAFactory: + @staticmethod + def make_cellular_automaton(dimension, neighborhood: Type[Neighborhood], state_class: Type[CellState]): - self._dimension = dimension - self._state_class = state_class - self.__create_cells() - self.__set_cell_neighbours(self._cells, neighborhood) - return tuple(self._cells.values()) + cells = CAFactory._make_cells(dimension, state_class) + CAFactory._apply_neighbourhood_to_cells(cells, neighborhood, dimension) + return tuple(cells.values()) - def __create_cells(self, dimension_index=0, coordinate=None): - """ Recursively steps down the dimensions to create cells in n dimensions and adds them to a dict. - :param dimension_index: The index indicating which dimension is currently traversed. - :param coordinate: The coordinate generated so far. - (each recursion adds one dimension to the coordinate. - """ - coordinate = _instantiate_coordinate_if_necessary(coordinate) + @staticmethod + def _make_cells(dimension, state_class): + cells = {} + for c in itertools.product(*[range(d) for d in dimension]): + coordinate_string = _join_coordinate(c) + cells[coordinate_string] = Cell(state_class, c) + return cells - try: - self.__recursive_step_down_dimensions(coordinate, dimension_index) - except IndexError: - coordinate_string = _join_coordinate(coordinate) - self._cells[coordinate_string] = Cell(self._state_class, coordinate) - - def __recursive_step_down_dimensions(self, coordinate, dimension_index): - """ For the range of the current dimension, recalls the recursion method. - :param coordinate: The coordinate so far. - :param dimension_index: The current dimension lvl. - """ - for cell_index in range(self._dimension[dimension_index]): - new_cod = coordinate + [cell_index] - self.__create_cells(dimension_index + 1, new_cod) - - def __set_cell_neighbours(self, cells, neighborhood): + @staticmethod + def _apply_neighbourhood_to_cells(cells, neighborhood, dimension): for cell in cells.values(): n_coordinates = neighborhood.calculate_cell_neighbor_coordinates(cell.get_coordinate(), - self._dimension) + dimension) cell.set_neighbours([cells[_join_coordinate(coordinate)].get_state() for coordinate in n_coordinates]) -def _instantiate_coordinate_if_necessary(coordinate): - if coordinate is None: - coordinate = [] - return coordinate - - def _join_coordinate(coordinate): return '-'.join(str(x) for x in coordinate) diff --git a/src/cellular_automaton/cellular_automaton.py b/src/cellular_automaton/cellular_automaton.py index 11eca46..f7bf9c7 100644 --- a/src/cellular_automaton/cellular_automaton.py +++ b/src/cellular_automaton/cellular_automaton.py @@ -14,10 +14,11 @@ class CellularAutomaton: class CellularAutomatonProcessor: def __init__(self, cellular_automaton, process_count: int = 1): self.ca = cellular_automaton + cells = {i: self.ca.cells[i] for i in range(len(self.ca.cells))} self.evolve_range = range(len(self.ca.cells)) self.pool = multiprocessing.Pool(processes=process_count, initializer=_init_process, - initargs=(self.ca.cells, + initargs=(cells, self.ca.evolution_rule, self.ca.evolution_iteration_index)) for cell in self.ca.cells: diff --git a/test/test_factory.py b/test/test_factory.py new file mode 100644 index 0000000..30b8083 --- /dev/null +++ b/test/test_factory.py @@ -0,0 +1,50 @@ +import sys +sys.path.append('../src') + +from cellular_automaton import * +import unittest +import mock + + +class TestFac(CAFactory): + @staticmethod + def make_cells(dimension, state_class): + return CAFactory._make_cells(dimension, state_class) + + @staticmethod + def apply_neighbourhood(cells, neighborhood, dimension): + return CAFactory._apply_neighbourhood_to_cells(cells, neighborhood, dimension) + + +class TestCAFactory(unittest.TestCase): + def test_make_ca_calls_correct_methods(self): + with mock.patch.object(CAFactory, '_make_cells', return_value={1: True}) as m1: + with mock.patch.object(CAFactory, '_apply_neighbourhood_to_cells') as m2: + CAFactory.make_cellular_automaton([10], Neighborhood, CellState) + m1.assert_called_once_with([10], CellState) + m2.assert_called_once_with({1: True}, Neighborhood, [10]) + + def test_make_ca_returns_correct_values(self): + with mock.patch.object(CAFactory, '_make_cells', return_value={1: True}): + with mock.patch.object(CAFactory, '_apply_neighbourhood_to_cells'): + cells = CAFactory.make_cellular_automaton([10], Neighborhood, CellState) + self.assertEqual(cells, (True, )) + + def test_1dimension_coordinates(self): + fac = TestFac() + c = fac.make_cells([3], CellState) + self.assertEqual(list(c.keys()), ['0', '1', '2']) + + def test_2dimension_coordinates(self): + fac = TestFac() + c = fac.make_cells([2, 2], CellState) + self.assertEqual(list(c.keys()), ['0-0', '0-1', '1-0', '1-1']) + + def test_3dimension_coordinates(self): + fac = TestFac() + c = fac.make_cells([2, 2, 2], CellState) + self.assertEqual(list(c.keys()), ['0-0-0', '0-0-1', '0-1-0', '0-1-1', '1-0-0', '1-0-1', '1-1-0', '1-1-1']) + + +if __name__ == '__main__': + unittest.main() diff --git a/test/test_neighborhood.py b/test/test_neighborhood.py index 06bcd9e..6a95dfc 100644 --- a/test/test_neighborhood.py +++ b/test/test_neighborhood.py @@ -5,8 +5,9 @@ import cellular_automaton.ca_neighborhood as csn import unittest -class TestCellState(unittest.TestCase): - def check_neighbors(self, neighborhood, neighborhood_sets): +class TestNeighborhood(unittest.TestCase): + @staticmethod + def check_neighbors(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: