📚 The CoCalc Library - books, templates and other resources
License: OTHER
from pylab import *1from CellWorld import *2from cmath import *3from Fourier import fft4from Dist import *56class Forest(CellWorld):7"""Forest implements a CA model of a forest fire, as described8at http://en.wikipedia.org/wiki/Forest-fire_model9"""1011def __init__(self, size=500, csize=5, p=0.01, f=0.001):12CellWorld.__init__(self, size, csize)13self.p = p # probability of a new tree14self.f = f # probability of a spontaneous fire15self.series = []1617def setup(self):18# the left frame contains the canvas19self.fr(LEFT)20self.canvas = self.ca(width=self.size, height=self.size,21bg='white', scale = [self.csize, self.csize])22self.endfr()2324# the right frame contains various buttons25self.fr(LEFT, fill=BOTH, expand=1)2627self.fr()28self.bu(LEFT, text='Print canvas', command=self.canvas.dump)29self.bu(LEFT, text='Quit', command=self.quit)30self.endfr()3132self.fr()33self.bu(LEFT, text='Run', command=self.run)34self.bu(LEFT, text='Stop', command=self.stop)35self.bu(LEFT, text='Step', command=self.profile_step)36self.endfr()3738self.fr()39self.bu(LEFT, text='Show clusters', command=self.cluster_dist)40self.endfr()4142self.endfr()4344# make a grid of cells45xbound = self.size / self.csize / 2 -146ybound = xbound47low = [-xbound, -ybound]48high = [xbound, ybound]49self.make_cells([low, high])5051def make_cells(self, limits):52"""make a grid of cells with the specified limits.53limits is a list of pairs, [[lowx, lowy], [highx, highy]]"""54low, high = limits55xs = range(low[0], high[0])56ys = range(low[1], high[1])57for x in xs:58col = []59for y in ys:60indices = (x, y)61self.cells[indices] = Patch(self, indices)6263def get_cluster(self, patch):64"""find the set of cells that are connected to (patch) in65the sense that they are either neighbors, or neighbors of66neighbors, etc.67"""6869# queue is the set of cells waiting to be checked70queue = set([patch])7172# res it the resulting set of cells73res = set([patch])7475# do a breadth first search based on neighbors relationships76while queue:77patch = queue.pop()78neighbors = self.get_four_neighbors(patch, Patch.null)7980# look for green neighbors we haven't seen before81new = [n for n in neighbors82if n.state == 'green' and n not in res]8384# and add them to the cluster85queue.update(new)86res.update(new)8788return res8990def all_clusters(self):91"""find all the clusters in the current grid (returns a list92of sets).93"""9495# find all the green cells96greens = [p for p in self.cells.itervalues() if p.state == 'green']9798# keep track of cells we have already seen99marked = set()100101# initialize the list of cluster102clusters = []103104for patch in greens:105if patch in marked: continue106cluster = self.get_cluster(patch)107108# add the new cluster to the list109clusters.append(cluster)110111# and mark the cells in the cluster112marked.update(cluster)113114return clusters115116def cluster_dist(self):117"""compute and display the distribution of cluster sizes.118"""119clusters = self.all_clusters()120lengths = [len(cluster) for cluster in clusters]121d = Dist(lengths)122d.plot_ccdf(loglog)123show()124125def bind(self):126"""create bindings for the canvas127"""128self.canvas.bind('<ButtonPress-1>', self.click)129130def click(self, event):131"""this event handler is executed when the user clicks on132the canvas. It finds the cluster of the cell that was clicked133and displays it in red.134"""135x, y = self.canvas.invert([event.x, event.y])136i, j = int(floor(x)), int(floor(y))137patch = self.get_cell(i,j)138if patch and patch.state == 'green':139cluster = self.get_cluster(patch)140self.show_cluster(cluster)141142def show_cluster(self, cluster):143"""color all the cells in this cluster red144"""145for patch in cluster:146patch.config(fill='red')147148def profile_step(self):149"""run one step and profile it150"""151import profile152profile.run('world.step()')153154def step(self):155"""run one step: update the cells, counting the number156that are burning. Update the time series and display157the its FFT.158"""159burning = 0160for patch in self.cells.itervalues():161patch.step()162if patch.state == 'orange':163burning += 1164165self.series.append(burning)166self.display_fft(256)167168def display_fft(self, N=4096):169"""display the FFT of the last (N) values in self.series170"""171if len(self.series) % N != 0:172return173174h = self.series[-N:]175H = fft(h)176177# the squared magnitude of the fft is an estimate of the178# power spectral density179180# http://documents.wolfram.com/applications/timeseries/181# UsersGuidetoTimeSeries/1.8.3.html182# http://en.wikipedia.org/wiki/Power_spectral_density183freq = range(N/2 + 1)184sdf = [Hn * Hn.conjugate() for Hn in H]185sdf = [sdf[f].real for f in freq]186loglog(freq, sdf)187xlabel('frequency')188ylabel('power')189show()190191192class Patch(Cell):193"""a Patch is a part of a forest that may or may not have one tree194"""195def __init__(self, world, indices):196"""world is a Forest, indices is a tuple of integer coordinates197"""198self.world = world199self.indices = indices200self.bounds = self.world.cell_bounds(*indices)201self.state = 'white'202self.draw()203204def draw(self):205"""draw this patch206"""207coords = self.bounds[::2]208self.tag = self.world.canvas.rectangle(coords,209outline='gray80', fill=self.state)210211def set_state(self, state):212"""set the state of this patch and update the display.213(state) must be a color.214"""215self.state = state216self.config(fill=self.state)217218def step(self):219"""update this patch220"""221# invoke the appropriate function, according to self.state222Patch.dispatch[self.state](self)223224def step_empty(self):225"""update an empty patch226"""227if random.random() < self.world.p:228self.set_state('green')229230def step_tree(self):231"""update a patch with a tree232"""233if random.random() < self.world.f or self.any_neighbor_burning():234self.set_state('orange')235236def step_burning(self):237"""update a burning patch238"""239self.set_state('white')240241dispatch = dict(white=step_empty, green=step_tree, orange=step_burning)242243class NullPatch:244"""the NullPatch is a singleton that acts as a stand-in for245non-existent patches246"""247def __init__(self):248self.state = 'white'249250# instantiate the null patch251null = NullPatch()252253def any_neighbor_burning(self):254"""return True if any of this patch's 4 Von Neumann neighbors255are currently burning, False otherwise.256"""257neighbors = self.world.get_four_neighbors(self, Patch.null)258states = [patch.state for patch in neighbors]259return 'orange' in states260261262if __name__ == '__main__':263world = Forest()264world.bind()265world.mainloop()266267268