Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download

📚 The CoCalc Library - books, templates and other resources

132922 views
License: OTHER
1
from pylab import *
2
from CellWorld import *
3
from cmath import *
4
from Fourier import fft
5
from Dist import *
6
7
class Forest(CellWorld):
8
"""Forest implements a CA model of a forest fire, as described
9
at http://en.wikipedia.org/wiki/Forest-fire_model
10
"""
11
12
def __init__(self, size=500, csize=5, p=0.01, f=0.001):
13
CellWorld.__init__(self, size, csize)
14
self.p = p # probability of a new tree
15
self.f = f # probability of a spontaneous fire
16
self.series = []
17
18
def setup(self):
19
# the left frame contains the canvas
20
self.fr(LEFT)
21
self.canvas = self.ca(width=self.size, height=self.size,
22
bg='white', scale = [self.csize, self.csize])
23
self.endfr()
24
25
# the right frame contains various buttons
26
self.fr(LEFT, fill=BOTH, expand=1)
27
28
self.fr()
29
self.bu(LEFT, text='Print canvas', command=self.canvas.dump)
30
self.bu(LEFT, text='Quit', command=self.quit)
31
self.endfr()
32
33
self.fr()
34
self.bu(LEFT, text='Run', command=self.run)
35
self.bu(LEFT, text='Stop', command=self.stop)
36
self.bu(LEFT, text='Step', command=self.profile_step)
37
self.endfr()
38
39
self.fr()
40
self.bu(LEFT, text='Show clusters', command=self.cluster_dist)
41
self.endfr()
42
43
self.endfr()
44
45
# make a grid of cells
46
xbound = self.size / self.csize / 2 -1
47
ybound = xbound
48
low = [-xbound, -ybound]
49
high = [xbound, ybound]
50
self.make_cells([low, high])
51
52
def make_cells(self, limits):
53
"""make a grid of cells with the specified limits.
54
limits is a list of pairs, [[lowx, lowy], [highx, highy]]"""
55
low, high = limits
56
xs = range(low[0], high[0])
57
ys = range(low[1], high[1])
58
for x in xs:
59
col = []
60
for y in ys:
61
indices = (x, y)
62
self.cells[indices] = Patch(self, indices)
63
64
def get_cluster(self, patch):
65
"""find the set of cells that are connected to (patch) in
66
the sense that they are either neighbors, or neighbors of
67
neighbors, etc.
68
"""
69
70
# queue is the set of cells waiting to be checked
71
queue = set([patch])
72
73
# res it the resulting set of cells
74
res = set([patch])
75
76
# do a breadth first search based on neighbors relationships
77
while queue:
78
patch = queue.pop()
79
neighbors = self.get_four_neighbors(patch, Patch.null)
80
81
# look for green neighbors we haven't seen before
82
new = [n for n in neighbors
83
if n.state == 'green' and n not in res]
84
85
# and add them to the cluster
86
queue.update(new)
87
res.update(new)
88
89
return res
90
91
def all_clusters(self):
92
"""find all the clusters in the current grid (returns a list
93
of sets).
94
"""
95
96
# find all the green cells
97
greens = [p for p in self.cells.itervalues() if p.state == 'green']
98
99
# keep track of cells we have already seen
100
marked = set()
101
102
# initialize the list of cluster
103
clusters = []
104
105
for patch in greens:
106
if patch in marked: continue
107
cluster = self.get_cluster(patch)
108
109
# add the new cluster to the list
110
clusters.append(cluster)
111
112
# and mark the cells in the cluster
113
marked.update(cluster)
114
115
return clusters
116
117
def cluster_dist(self):
118
"""compute and display the distribution of cluster sizes.
119
"""
120
clusters = self.all_clusters()
121
lengths = [len(cluster) for cluster in clusters]
122
d = Dist(lengths)
123
d.plot_ccdf(loglog)
124
show()
125
126
def bind(self):
127
"""create bindings for the canvas
128
"""
129
self.canvas.bind('<ButtonPress-1>', self.click)
130
131
def click(self, event):
132
"""this event handler is executed when the user clicks on
133
the canvas. It finds the cluster of the cell that was clicked
134
and displays it in red.
135
"""
136
x, y = self.canvas.invert([event.x, event.y])
137
i, j = int(floor(x)), int(floor(y))
138
patch = self.get_cell(i,j)
139
if patch and patch.state == 'green':
140
cluster = self.get_cluster(patch)
141
self.show_cluster(cluster)
142
143
def show_cluster(self, cluster):
144
"""color all the cells in this cluster red
145
"""
146
for patch in cluster:
147
patch.config(fill='red')
148
149
def profile_step(self):
150
"""run one step and profile it
151
"""
152
import profile
153
profile.run('world.step()')
154
155
def step(self):
156
"""run one step: update the cells, counting the number
157
that are burning. Update the time series and display
158
the its FFT.
159
"""
160
burning = 0
161
for patch in self.cells.itervalues():
162
patch.step()
163
if patch.state == 'orange':
164
burning += 1
165
166
self.series.append(burning)
167
self.display_fft(256)
168
169
def display_fft(self, N=4096):
170
"""display the FFT of the last (N) values in self.series
171
"""
172
if len(self.series) % N != 0:
173
return
174
175
h = self.series[-N:]
176
H = fft(h)
177
178
# the squared magnitude of the fft is an estimate of the
179
# power spectral density
180
181
# http://documents.wolfram.com/applications/timeseries/
182
# UsersGuidetoTimeSeries/1.8.3.html
183
# http://en.wikipedia.org/wiki/Power_spectral_density
184
freq = range(N/2 + 1)
185
sdf = [Hn * Hn.conjugate() for Hn in H]
186
sdf = [sdf[f].real for f in freq]
187
loglog(freq, sdf)
188
xlabel('frequency')
189
ylabel('power')
190
show()
191
192
193
class Patch(Cell):
194
"""a Patch is a part of a forest that may or may not have one tree
195
"""
196
def __init__(self, world, indices):
197
"""world is a Forest, indices is a tuple of integer coordinates
198
"""
199
self.world = world
200
self.indices = indices
201
self.bounds = self.world.cell_bounds(*indices)
202
self.state = 'white'
203
self.draw()
204
205
def draw(self):
206
"""draw this patch
207
"""
208
coords = self.bounds[::2]
209
self.tag = self.world.canvas.rectangle(coords,
210
outline='gray80', fill=self.state)
211
212
def set_state(self, state):
213
"""set the state of this patch and update the display.
214
(state) must be a color.
215
"""
216
self.state = state
217
self.config(fill=self.state)
218
219
def step(self):
220
"""update this patch
221
"""
222
# invoke the appropriate function, according to self.state
223
Patch.dispatch[self.state](self)
224
225
def step_empty(self):
226
"""update an empty patch
227
"""
228
if random.random() < self.world.p:
229
self.set_state('green')
230
231
def step_tree(self):
232
"""update a patch with a tree
233
"""
234
if random.random() < self.world.f or self.any_neighbor_burning():
235
self.set_state('orange')
236
237
def step_burning(self):
238
"""update a burning patch
239
"""
240
self.set_state('white')
241
242
dispatch = dict(white=step_empty, green=step_tree, orange=step_burning)
243
244
class NullPatch:
245
"""the NullPatch is a singleton that acts as a stand-in for
246
non-existent patches
247
"""
248
def __init__(self):
249
self.state = 'white'
250
251
# instantiate the null patch
252
null = NullPatch()
253
254
def any_neighbor_burning(self):
255
"""return True if any of this patch's 4 Von Neumann neighbors
256
are currently burning, False otherwise.
257
"""
258
neighbors = self.world.get_four_neighbors(self, Patch.null)
259
states = [patch.state for patch in neighbors]
260
return 'orange' in states
261
262
263
if __name__ == '__main__':
264
world = Forest()
265
world.bind()
266
world.mainloop()
267
268