Py.Cafe

ib_sample_projects/

mesa-prey-predator-simulation

Prey-Predator Simulation with Solara and Mesa

DocsPricing
  • app.py
  • model.py
  • predator.py
  • prey.py
  • requirements.txt
model.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import mesa
import random
import math
import numpy as np
from mesa.experimental.continuous_space import ContinuousSpaceAgent
from mesa import Model
from mesa.experimental.continuous_space import ContinuousSpace
from mesa.datacollection import DataCollector
from prey import PreyAgent
from predator import PredatorAgent

def space_distance(self, pos1, pos2):
    dx = abs(pos1[0] - pos2[0])
    dy = abs(pos1[1] - pos2[1])
    if self.torus:
        dx = min(dx, self.width - dx)
        dy = min(dy, self.height - dy)
    return math.sqrt(dx**2 + dy**2)

# Attach the custom distance function to ContinuousSpace.
ContinuousSpace.get_distance = space_distance


def generate_terrain(width, height, border_thickness=22, mountain_fraction=0.1):
    """
    Generates terrain for a grid with the following layout:
    
    - Outer border cells (with thickness border_thickness) are forest.
    - The interior is divided vertically:
      * The left portion (mountain_fraction of the interior width) is mountains.
      * The remaining interior cells are grassland.
    
    Args:
        width (int): The grid's total width.
        height (int): The grid's total height.
        border_thickness (int): Thickness of the forest border.
        mountain_fraction (float): Fraction (0 to 1) of the interior allocated to mountains.
    
    Returns:
        dict: A mapping from (x, y) tuple to terrain type ("forest", "mountains", "grassland").
    """
    terrain = {}
    
    # Initialize the entire grid as forest.
    for x in range(width):
        for y in range(height):
            terrain[(x, y)] = "forest"
    
    # Define the interior region boundaries.
    interior_min_x = border_thickness
    interior_max_x = width - border_thickness
    interior_min_y = border_thickness
    interior_max_y = height - border_thickness
    
    interior_width = interior_max_x - interior_min_x
    # Determine the column index dividing mountains and grassland.
    mountain_division = interior_min_x + int(interior_width * mountain_fraction)
    
    # Fill the interior region with mountains on the left and grassland on the right.
    for x in range(interior_min_x, interior_max_x):
        for y in range(interior_min_y, interior_max_y):
            if x < mountain_division:
                terrain[(x, y)] = "mountains"
            else:
                terrain[(x, y)] = "grassland"
                
    return terrain




class PreyPredatorModel(Model):
    def __init__(self, 
                 width=50, 
                 height=50, 
                 num_predators=5, 
                 num_prey=20,
                 sensor_noise=2.0, 
                 use_kalman_filter=True,
                 seed=None):
        super().__init__(seed=seed)
        
        self.num_predators = num_predators
        self.num_prey = num_prey
        self.sensor_noise = sensor_noise
        self.use_kalman_filter = use_kalman_filter
        self.kalman_Q = 0.1  # Process noise for Kalman filter
        self.kalman_R = 0.1  # Measurement noise for Kalman filter

        # Set up the continuous space.
        self.space = ContinuousSpace([[0, width], [0, height]], torus=False, random=self.random, n_agents=num_predators + num_prey)
        
        # Generate structured terrain: a forest border, a mountain range on the left, and grassland on the right.
        self.terrain = generate_terrain(width, height, border_thickness=10, mountain_fraction=0.1)
        
        # Create Prey Agents 
        positions = self.rng.random(size=(num_prey, 2)) * self.space.size
        self.prey_agents = PreyAgent.create_agents(model=self, n=self.num_prey, space=self.space, position=positions)

        # Create Predator Agents
        positions = self.rng.random(size=(num_predators, 2)) * self.space.size
        self.predator_agents = PredatorAgent.create_agents(model=self, n=self.num_predators, space=self.space, position=positions)

        # Initialize DataCollector
        self.datacollector = DataCollector(
            model_reporters={
                "Prey_Count": lambda m: len(m.prey_agents),
                "Predator_Count": lambda m: len(m.predator_agents),
                "Average_Energy": lambda m: np.mean([agent.energy for agent in m.prey_agents]) if m.prey_agents else 0,
                "Average_Hunger": lambda m: np.mean([agent.hunger for agent in m.predator_agents]) if m.predator_agents else 0,
            }
        )


    def step(self):
        self.agents.shuffle_do("step")
        self.datacollector.collect(self)