diff --git a/docs/examples/conway.md b/docs/examples/conway.md new file mode 100644 index 0000000..8e764b7 --- /dev/null +++ b/docs/examples/conway.md @@ -0,0 +1,21 @@ +# Conway's Game of Life + +An implementation of Conway's cellular automata. + +![Conway](./img/conway.gif) + +{{ include_snippet("./docs/examples/src.md", show_filename=False) }} + +## Inputs + +The only inputs are the grid size (2d) and the proportion of cells that are alive initially. + +## Implementation + +This example uses the `StateGrid` class for efficient neighbour counting, requiring only a few lines of code to evolve a generation (see below). The initial alive cells are sampled randomly according to the proportion set. In each new generation, alive cells with less than 2 or more than 3 neighbours die; empty cells with 3 live neighbours come alive. Dead cells are coloured black, live cells are coloured according to how long they have been alive, from white (youngest) to brown (oldest). + +{{ include_snippet("examples/conway/conway.py", "step") }} + +## Outputs + +The sole output is an animation as shown above. diff --git a/docs/examples/img/conway.gif b/docs/examples/img/conway.gif new file mode 100644 index 0000000..78ad51c Binary files /dev/null and b/docs/examples/img/conway.gif differ diff --git a/docs/examples/img/schelling.gif b/docs/examples/img/schelling.gif index e15ec83..9c35543 100644 Binary files a/docs/examples/img/schelling.gif and b/docs/examples/img/schelling.gif differ diff --git a/docs/examples/neworder-1.0.1-examples-src.tgz b/docs/examples/neworder-1.0.1-examples-src.tgz index 97e3d45..62ad295 100644 Binary files a/docs/examples/neworder-1.0.1-examples-src.tgz and b/docs/examples/neworder-1.0.1-examples-src.tgz differ diff --git a/docs/examples/neworder-1.0.1-examples-src.zip b/docs/examples/neworder-1.0.1-examples-src.zip index ffe3a0e..9dda5a3 100644 Binary files a/docs/examples/neworder-1.0.1-examples-src.zip and b/docs/examples/neworder-1.0.1-examples-src.zip differ diff --git a/docs/examples/schelling.md b/docs/examples/schelling.md index ebc71fd..5f41457 100644 --- a/docs/examples/schelling.md +++ b/docs/examples/schelling.md @@ -8,11 +8,11 @@ An implementation of Schelling's segregation model [[7]](../references.md), whic ## Inputs -In the above example, the similarity threshold is 50% and the cells states are (approximately): 36% empty, 12% red, 12% blue and 39% green, on a 640 x 480 grid. The initial population is randomly constructed using the model's Monte-Carlo engine, the process of moving agents uses randomly swaps unsatisfied agents with empty cells. +In this example, the similarity threshold is 60% and the cells states are: 36% empty, 12% red, 12% blue and 40% green, on a 480 x 360 grid. The initial population is randomly constructed using the model's Monte-Carlo engine, the process of moving agents randomly swaps unsatisfied agents with empty cells. The boundaries are "sinks", i.e. there are no neighbouring cells ## Implementation -The key features that this example uses are the `StateGrid` class for efficient neighbour counting and the use of a conditional halting: an open-ended timeline and a call to the `Model.halt()` method when a certain state is achieved. +The key features used in this example are the `StateGrid` class for efficient neighbour counting and the use of a conditional halting: an open-ended timeline and a call to the `Model.halt()` method when a certain state is achieved. Since the key output for this model is graphical, the visualisation code sits within the model. The model reaches a steady state when there are no unsatisfied agents remaining and there is nothing to be gained by continuing, so when this happens the `neworder.Model.halt()` method is called, at the end of the `step()` implementation: @@ -29,15 +29,15 @@ The `StateGrid.count_neighbours` takes a function argument that filters the stat The output is an animation as shown above. Log messages also record the timestep and the proportion of the population that remains unsatisfied: ```text -[py 0/1] step 0 42.6660% unsatisfied -[py 0/1] step 1 39.5765% unsatisfied -[py 0/1] step 2 37.5599% unsatisfied -[py 0/1] step 3 36.2454% unsatisfied -[py 0/1] step 4 35.2279% unsatisfied +[py 0/1] step 0 43.1493% unsatisfied +[py 0/1] step 1 39.1400% unsatisfied +[py 0/1] step 2 36.9196% unsatisfied +[py 0/1] step 3 35.3113% unsatisfied +[py 0/1] step 4 33.9259% unsatisfied ... -[py 0/1] step 458 0.0003% unsatisfied -[py 0/1] step 459 0.0003% unsatisfied -[py 0/1] step 460 0.0003% unsatisfied -[py 0/1] step 461 0.0003% unsatisfied -[py 0/1] step 462 0.0000% unsatisfied +[py 0/1] step 133 0.0017% unsatisfied +[py 0/1] step 134 0.0012% unsatisfied +[py 0/1] step 135 0.0012% unsatisfied +[py 0/1] step 136 0.0006% unsatisfied +[py 0/1] step 137 0.0000% unsatisfied ``` diff --git a/examples/conway/conway.py b/examples/conway/conway.py index 0ef9f25..09b29b6 100644 --- a/examples/conway/conway.py +++ b/examples/conway/conway.py @@ -26,8 +26,8 @@ def __init__(self, nx, ny, n, edge=no.Domain.WRAP): self.fig, self.g = self.__init_visualisation() + # !step! def step(self): - n = self.domain.count_neighbours(lambda x: x > 0) deaths = np.logical_or(n < 2, n > 3) @@ -35,22 +35,21 @@ def step(self): self.domain.state = self.domain.state * ~deaths + births + self.__update_visualisation() + # !step! + + def check(self): # # randomly place a glider (not across edge) # if self.timeline.index() == 0: # x = self.mc.raw() % (self.domain.state.shape[0] - 2) # y = self.mc.raw() % (self.domain.state.shape[1] - 2) # self.domain.state[x:x+3, y:y+3] = np.rot90(Conway.__glider, self.mc.raw() % 4) - - self.__update_visualisation() - - def check(self): return True def __init_visualisation(self): - plt.ion() cmap = colors.ListedColormap(['black', 'white', 'purple', 'blue', 'green', 'yellow', 'orange', 'red', 'brown']) - fig = plt.figure(constrained_layout=True, figsize=(12,9)) + fig = plt.figure(constrained_layout=True, figsize=(8,6)) g = plt.imshow(self.domain.state, cmap=cmap, vmax=9) plt.axis("off") @@ -60,6 +59,9 @@ def __init_visualisation(self): return fig, g def __update_visualisation(self): - self.g.set_data(self.domain.state) + # plt.savefig("/tmp/conway%04d.png" % self.timeline.index(), dpi=80) + # if self.timeline.index() > 100: + # self.halt() + self.fig.canvas.flush_events() diff --git a/examples/schelling/model.py b/examples/schelling/model.py index 084fd7f..01e6b54 100644 --- a/examples/schelling/model.py +++ b/examples/schelling/model.py @@ -5,11 +5,11 @@ #neworder.verbose() # category 0 is empty -gridsize = [640,480] -categories = np.array([0.56, 0.19, 0.19, 0.6]) -# normalise -categories = categories / sum(categories) -similarity = 0.5 +gridsize = [480,360] +categories = np.array([0.36, 0.12, 0.12, 0.4]) +# normalise if necessary +# categories = categories / sum(categories) +similarity = 0.6 # open-ended timeline with arbitrary timestep # the model halts when all agents are satisfied, rather than at a specific time diff --git a/examples/schelling/schelling.py b/examples/schelling/schelling.py index c194767..91d5515 100644 --- a/examples/schelling/schelling.py +++ b/examples/schelling/schelling.py @@ -81,6 +81,5 @@ def __init_visualisation(self): def __update_visualisation(self): self.img.set_array(self.domain.state.T) - # if not self.timeline.index() % 5: - # plt.savefig("/tmp/schelling%04d.png" % (self.timeline.index()//5), dpi=80) + # plt.savefig("/tmp/schelling%04d.png" % self.timeline.index(), dpi=80) self.fig.canvas.flush_events() diff --git a/mkdocs.yml b/mkdocs.yml index 17e237a..68e80e1 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -48,6 +48,7 @@ nav: - RiskPaths: examples/riskpaths.md - People: ./examples/people.md - Option: examples/option.md + - Conway: examples/conway.md - Schelling: examples/schelling.md - Wolf-Sheep: examples/wolf-sheep.md - N-body: examples/n-body.md