Skip to content

Commit c47e37a

Browse files
committed
differences for PR #27
1 parent ea16629 commit c47e37a

6 files changed

Lines changed: 77 additions & 80 deletions

File tree

fig/predprey_out.png

21.2 KB
Loading

files/pred-prey/predprey.py

Lines changed: 49 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -6,33 +6,34 @@
66
import numpy as np
77

88
# Reproduction
9-
REPRODUCE_PREY_PROB = 0.05
10-
REPRODUCE_PRED_PROB = 0.03
9+
REPRODUCE_PREY_PROB = 0.012
10+
REPRODUCE_PRED_PROB = 0.015
1111

1212
# Cohesion/Avoidance
1313
SAME_SPECIES_AVOIDANCE_RADIUS = 0.035
14-
PREY_GROUP_COHESION_RADIUS = 0.2
14+
PREY_GROUP_COHESION_RADIUS = 0.25
1515

1616
# Predator/Prey/Grass interaction
17-
PRED_PREY_INTERACTION_RADIUS = 0.3
18-
PRED_SPEED_ADVANTAGE = 3.0
19-
PRED_KILL_DISTANCE = 0.03
17+
PRED_PREY_INTERACTION_RADIUS = 0.10
18+
PRED_SPEED_ADVANTAGE = 1.5
19+
PRED_KILL_DISTANCE = 0.06
2020
GRASS_EAT_DISTANCE = 0.05
21-
GAIN_FROM_FOOD_PREY = 80
22-
GAIN_FROM_FOOD_PREDATOR = 100
23-
GRASS_REGROW_CYCLES = 20
24-
PRED_HUNGER_THRESH = 100
25-
PREY_HUNGER_THRESH = 100
21+
GAIN_FROM_FOOD_PREY = 18
22+
GAIN_FROM_FOOD_PREDATOR = 70
23+
GRASS_REGROW_CYCLES = 24
24+
PRED_HUNGER_THRESH = 115
25+
PREY_HUNGER_THRESH = 140
2626

2727
# Simulation properties
2828
DELTA_TIME = 0.001
2929
BOUNDS_WIDTH = 2.0
3030
MIN_POSITION = -1.0
31-
MAX_POSITION = 1.0
31+
MAX_POSITION = 1.0
3232

3333
NEXT_PRED_ID = 1
3434
NEXT_PREY_ID = 1
3535

36+
3637
class Prey:
3738
def __init__(self):
3839
global NEXT_PREY_ID
@@ -53,11 +54,7 @@ def avoid_predators(self, predator_list):
5354

5455
# Add a steering factor away from each predator. Strength increases with closeness.
5556
for predator in predator_list:
56-
# Fetch location of predator
57-
predator_x = self.x;
58-
predator_y = self.y;
59-
60-
# Check if the two predators are within interaction radius
57+
# Check if predator and prey are within interaction radius
6158
dx = self.x - predator.x
6259
dy = self.y - predator.y
6360
distance = math.sqrt(dx * dx + dy * dy)
@@ -145,24 +142,25 @@ def eaten_or_starve(self, predator_list):
145142
if predator_index >= 0:
146143
predator_list[predator_index].life += GAIN_FROM_FOOD_PREDATOR
147144
return True
148-
149-
# If the life has reduced to 0 then the prey should die or starvation
145+
146+
# If the life has reduced to 0 then the prey should die of starvation
150147
if self.life < 1:
151148
return True
152149
return False
153-
150+
154151
def reproduce(self):
155152
if np.random.uniform() < REPRODUCE_PREY_PROB:
156153
self.life /= 2
157-
154+
158155
child = Prey()
159156
child.x = np.random.uniform() * BOUNDS_WIDTH - BOUNDS_WIDTH / 2.0
160157
child.y = np.random.uniform() * BOUNDS_WIDTH - BOUNDS_WIDTH / 2.0
161158
child.vx = np.random.uniform() * 2 - 1
162159
child.vy = np.random.uniform() * 2 - 1
163160
child.life = self.life
164-
165-
161+
return child
162+
163+
166164
class Predator:
167165
def __init__(self):
168166
global NEXT_PRED_ID
@@ -175,7 +173,7 @@ def __init__(self):
175173
self.steer_x = 0.0
176174
self.steer_y = 0.0
177175
self.life = 0
178-
176+
179177
def follow_prey(self, prey_list):
180178
# Find the closest prey by iterating the prey_location messages
181179
closest_prey_x = 0.0
@@ -200,7 +198,6 @@ def follow_prey(self, prey_list):
200198
self.steer_x = closest_prey_x - self.x
201199
self.steer_y = closest_prey_y - self.y
202200

203-
204201
def avoid_predators(self, predator_list):
205202
# Fetch this predator's position
206203
avoid_velocity_x = 0.0
@@ -219,7 +216,7 @@ def avoid_predators(self, predator_list):
219216

220217
self.steer_x += avoid_velocity_x
221218
self.steer_y += avoid_velocity_y
222-
219+
223220
def move(self):
224221
# Integrate steering forces and cap velocity
225222
self.vx += self.steer_x
@@ -234,21 +231,21 @@ def move(self):
234231
self.x += self.vx * DELTA_TIME * PRED_SPEED_ADVANTAGE
235232
self.y += self.vy * DELTA_TIME * PRED_SPEED_ADVANTAGE
236233

237-
# Bound the position within the environment
234+
# Bound the position within the environment
238235
self.x = max(self.x, MIN_POSITION)
239236
self.x = min(self.x, MAX_POSITION)
240237
self.y = max(self.y, MIN_POSITION)
241238
self.y = min(self.y, MAX_POSITION)
242239

243240
# Reduce life by one unit of energy
244241
self.life -= 1
245-
242+
246243
def starve(self):
247244
# Did the predator starve?
248245
if self.life < 1:
249246
return True
250247
return False
251-
248+
252249
def reproduce(self):
253250
if np.random.uniform() < REPRODUCE_PRED_PROB:
254251
self.life /= 2
@@ -260,14 +257,14 @@ def reproduce(self):
260257
child.vy = np.random.uniform() * 2 - 1
261258
child.life = self.life
262259
return child
263-
260+
264261
class Grass:
265262
def __init__(self):
266263
self.x = 0.0
267264
self.y = 0.0
268265
self.dead_cycles = 0
269266
self.available = 1
270-
267+
271268
def grow(self):
272269
new_dead_cycles = self.dead_cycles + 1
273270
if self.dead_cycles == GRASS_REGROW_CYCLES:
@@ -277,7 +274,7 @@ def grow(self):
277274
if self.available == 0:
278275
self.dead_cycles = new_dead_cycles
279276

280-
277+
281278
def eaten(self, prey_list):
282279
if self.available:
283280
prey_index = -1
@@ -299,14 +296,14 @@ def eaten(self, prey_list):
299296
if prey_index >= 0:
300297
# Add grass eaten message
301298
prey_list[prey_index].life += GAIN_FROM_FOOD_PREY
302-
299+
303300
# Update grass agent variables
304301
self.dead_cycles = 0
305302
self.available = 0
306-
303+
307304
class Model:
308305

309-
def __init__(self, steps = 250):
306+
def __init__(self, steps = 50):
310307
self.steps = steps
311308
self.num_prey = 200
312309
self.num_predators = 50
@@ -323,7 +320,7 @@ def _init_population(self):
323320
p.vy = np.random.uniform(-1.0, 1.0)
324321
p.life = np.random.randint(10, 50)
325322
self.prey.append(p)
326-
323+
327324
# Initialise predator agents
328325
self.predators = []
329326
for i in range(self.num_predators):
@@ -334,48 +331,48 @@ def _init_population(self):
334331
p.vy = np.random.uniform(-1.0, 1.0)
335332
p.life = np.random.randint(10, 15)
336333
self.predators.append(p)
337-
334+
338335
# Initialise grass agents
339336
self.grass = []
340337
for i in range(self.num_grass):
341338
g = Grass()
342339
g.x = np.random.uniform(-1.0, 1.0)
343340
g.y = np.random.uniform(-1.0, 1.0)
344341
self.grass.append(g)
345-
342+
346343
def _step(self):
347344
## Shuffle agent list order to avoid bias
348345
np.random.shuffle(self.predators) # todo, this probably doesn't like Python lists
349346
np.random.shuffle(self.prey)
350-
347+
351348
for p in self.predators:
352349
p.follow_prey(self.prey)
353350
for p in self.prey:
354351
p.avoid_predators(self.predators)
355-
352+
356353
for p in self.prey:
357354
p.flock(self.prey)
358355
for p in self.predators:
359356
p.avoid_predators(self.predators)
360-
357+
361358
for p in self.prey:
362359
p.move()
363360
for p in self.predators:
364361
p.move()
365-
366-
362+
363+
367364
for g in self.grass:
368365
g.eaten(self.prey)
369-
366+
370367
self.prey = [p for p in self.prey if not p.eaten_or_starve(self.predators)]
371368
self.predators = [p for p in self.predators if not p.starve()]
372-
369+
373370
children = []
374371
for p in self.prey:
375372
c = p.reproduce()
376373
if c:
377374
children.append(c)
378-
self.predators.extend(children)
375+
self.prey.extend(children)
379376
children = []
380377
for p in self.predators:
381378
c = p.reproduce()
@@ -384,17 +381,17 @@ def _step(self):
384381
self.predators.extend(children)
385382
for g in self.grass:
386383
g.grow()
387-
384+
388385
def _init_log(self):
389386
self.prey_log = [len(self.prey)]
390387
self.predator_log = [len(self.predators)]
391388
self.grass_log = [sum(g.available for g in self.grass)/20]
392-
389+
393390
def _log(self):
394391
self.prey_log.append(len(self.prey))
395392
self.predator_log.append(len(self.predators))
396393
self.grass_log.append(sum(g.available for g in self.grass)/20)
397-
394+
398395
def _plot(self):
399396
plt.figure(figsize=(16,10))
400397
plt.rcParams.update({'font.size': 18})
@@ -405,7 +402,7 @@ def _plot(self):
405402
plt.plot(range(0, len(self.grass_log)), self.grass_log, 'g', label="Grass/20")
406403
plt.legend()
407404
plt.savefig('predprey_out.png')
408-
405+
409406
def run(self, random_seed=12):
410407
np.random.seed(random_seed)
411408
# init
@@ -417,7 +414,7 @@ def run(self, random_seed=12):
417414
self._log()
418415
# plot graph of results
419416
self._plot()
420-
417+
421418
# Argument parsing
422419
if len(sys.argv) != 2:
423420
print("Script expects 1 positive integer argument (number of steps), %u found."%(len(sys.argv) - 1))
@@ -431,4 +428,4 @@ def run(self, random_seed=12):
431428
model = Model(steps=steps)
432429
model.run()
433430
end_time = time.monotonic()
434-
print(f"Execution time: {end_time - start_time:.3f} s")
431+
print(f"Execution time: {end_time - start_time:.3f} s")

files/pred-prey/predprey.py.lprof

24 Bytes
Binary file not shown.

md5sum.txt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@
55
"index.md" "3dedac9faa381aab1590c73efbf0910e" "site/built/index.md" "2026-02-19"
66
"links.md" "8184cf4149eafbf03ce8da8ff0778c14" "site/built/links.md" "2024-03-20"
77
"episodes/profiling-introduction.md" "462eb186dc911e801313092feaf914ab" "site/built/profiling-introduction.md" "2026-01-30"
8-
"episodes/profiling-functions.md" "5d514c45ad9b7bc3cbdc4986db2bb60d" "site/built/profiling-functions.md" "2026-01-30"
8+
"episodes/profiling-functions.md" "eb7867977ed66a9b17bbe495dc6e1583" "site/built/profiling-functions.md" "2026-02-21"
99
"episodes/short-break1.md" "c7d9988cade5cc12dfbbf6c2a29ff2e9" "site/built/short-break1.md" "2024-03-28"
10-
"episodes/profiling-lines.md" "e2dbc755326fa00ffeb1c1ad9d775689" "site/built/profiling-lines.md" "2026-01-30"
10+
"episodes/profiling-lines.md" "a7e78b6dbf5b47ab9c6222edd930ad73" "site/built/profiling-lines.md" "2026-02-21"
1111
"episodes/profiling-conclusion.md" "b5687e26387b353ef23c6292f295ca02" "site/built/profiling-conclusion.md" "2024-03-28"
1212
"episodes/optimisation-introduction.md" "aacb6eaab453c48a49727f28ca7620bd" "site/built/optimisation-introduction.md" "2025-07-06"
1313
"episodes/optimisation-using-python.md" "76b19d19ccea20b9f1a5b6f69c504073" "site/built/optimisation-using-python.md" "2025-11-24"

profiling-functions.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -429,7 +429,7 @@ python -m snakeviz predprey_out.prof
429429

430430
## Exercise 2: Predator Prey
431431

432-
Download and profile <a href="files/pred-prey/predprey.py" download>the Python predator prey model</a>, try to locate the function call(s) where the majority of execution time is being spent
432+
Download and profile <a href="files/pred-prey/predprey.py" download>the Python predator prey model</a>, try to locate the function call(s) where the majority of execution time is being spent.
433433

434434
*This exercise uses the packages `numpy` and `matplotlib`, they can be installed via `pip install numpy matplotlib`.*
435435

@@ -438,7 +438,7 @@ Download and profile <a href="files/pred-prey/predprey.py" download>the Python p
438438
> The three agents; predators, prey and grass exist in a two dimensional grid. Predators eat prey, prey eat grass. The size of each population changes over time. Depending on the parameters of the model, the populations may oscillate, grow or collapse due to the availability of their food source.
439439
440440
The program can be executed via `python predprey.py <steps>`.
441-
The value of `steps` for a full run is 250, however a full run may not be necessary to find the bottlenecks.
441+
The value of `steps` for a full run is 400, which may take a few minutes. However, using 100–200 steps should be sufficient to find the bottlenecks.
442442

443443
When the model finishes it outputs a graph of the three populations `predprey_out.png`.
444444

@@ -454,13 +454,13 @@ If the table is ordered by `ncalls`, it can be identified as the joint 4th most
454454

455455
If you checked `predprey_out.png` (shown below), you should notice that there are significantly more `Grass` agents than `Predators` or `Prey`.
456456

457-
![`predprey_out.png` as produced by the default configuration of `predprey.py`.](episodes/fig/predprey_out.png){alt="A line graph plotting population over time through 250 steps of the pred prey model. Grass/20, shown in green, has a brief dip in the first 30 steps, but recovers holding steady at approximately 240 (4800 agents). Prey, shown in blue, starts at 200, quickly drops to around 185, before levelling off for steps and then slowly declining to a final value of 50. The data for predators, shown in red, has significantly more noise. There are 50 predators to begin, this rises briefly before falling to around 10, from here it noisily grows to around 70 by step 250 with several larger declines during the growth."}
457+
![`predprey_out.png` as produced by the default configuration of `predprey.py`.](episodes/fig/predprey_out.png){alt="A line graph plotting population over time through 400 time steps of the pred prey model. The amount of grass, shown in green, is scaled down by a factor of 20 to fit onto the graph. It has a brief dip in the first 25 steps, then slowly declines from approximately 220 to 150 over the next 200 steps, before steadily returning to 250. The number of prey, shown in blue, starts at 200, then grows to around 600 after 200 steps, before declining quickly and reaching zero at 350 to 400 steps. The number of predators, shown in red, falls from 50 to around 30 after 15 time steps, then grows to almost 700 by step 330 before declining quickly."}
458458

459459
Similarly, the `Grass::eaten()` has a `percall` time is inline with other agent functions such as `Prey::flock()` (from `predprey.py:67`).
460460

461461
Maybe we could investigate this further with line profiling!
462462

463-
*You may have noticed many iciles on the right hand of the diagram, these primarily correspond to the `import` of `matplotlib` which is relatively expensive!*
463+
*You may have noticed many icicles on the right hand of the diagram, these primarily correspond to the `import` of `matplotlib` which is relatively expensive!*
464464

465465
:::::::::::::::::::::::::::::::::
466466
:::::::::::::::::::::::::::::::::::::::::::::::

0 commit comments

Comments
 (0)