diff --git a/README.md b/README.md index 524e06c..ef26578 100644 --- a/README.md +++ b/README.md @@ -1 +1,25 @@ -# sheep-flock \ No newline at end of file +# sheep-flock + +### About + +An agent-based model implementation for simulating a sheep flock and herd dogs. + +In the .env file, you can set parameters for a simulation. We recommend focussing on these parameters: +- N_SHEEP +- N_DOG +- SPAWN_CONTROLLABLE_DOG (Set to True for a controllable dog that can be controlled using the arrow keys) +- SHEEP_SPAWN_DISTRIBUTION + +### Setup + +Use Python 3.12 or a similar version. Create a virtual environment using ``python -m venv venv``, activate it (Windows: ``venv\Scripts\activate ``, Linux/macOS: ``source venv/bin/activate``) and use ``pip install -r requirements.txt`` to install the necessary packages into the venv. + +To start a simulation, run the main.py. + +from the root directory of the project run: +``` +python -m src.main +``` + + + diff --git a/Report.pdf b/Report.pdf new file mode 100644 index 0000000..641fa44 Binary files /dev/null and b/Report.pdf differ diff --git a/requirements.txt b/requirements.txt index d120b8c..80f4f82 100644 Binary files a/requirements.txt and b/requirements.txt differ diff --git a/src/.env b/src/.env index 3fc247b..2cde881 100644 --- a/src/.env +++ b/src/.env @@ -1,8 +1,12 @@ # Environment basics N_SHEEP=70 -N_DOG=4 +N_DOG=3 SHEEP_SPAWN_DISTRIBUTION=normal # Options: "uniform" or "normal" +# Controllable dog +SPAWN_CONTROLLABLE_DOG=False +CONTROLLABLE_DOG_MAX_SPEED = 0.02 + # Sheep parameters ANIMAL_OBSERVATION_RADIUS=0.55 SHEEP_MAX_SPEED=0.001 @@ -33,8 +37,3 @@ COLOR_SHEEP_DEFAULT=255,255,255 COLOR_SHEEP_EXCITED=0,0,0 COLOR_DOG=153,102,51 COLOR_CONTROLLABLE_DOG=0,0,255 - - -# Controllable DOG -SPAWN_CONTROLLABLE_DOG=True -CONTROLLABLE_DOG_MAX_SPEED = 0.02 \ No newline at end of file diff --git a/src/animals/sheep.py b/src/animals/sheep.py index f2970a8..7c9af66 100644 --- a/src/animals/sheep.py +++ b/src/animals/sheep.py @@ -1,7 +1,7 @@ import os from pygame import Color, Vector2 -from src.animals.animals import Animal from src.utils import timed +from src.animals.animals import Animal import random as rand @@ -164,6 +164,10 @@ def _calculate_separation_velocity(self, sheep): @timed def _calculate_dog_avoidance(self, dogs): + + if not dogs: + return Vector2(0, 0) # No dogs at all + close_dogs = [ dog for dog in dogs if (self.position - dog.position).magnitude() <= self.dog_avoidance_radius diff --git a/src/environment.py b/src/environment.py index 7cb526e..63c014d 100644 --- a/src/environment.py +++ b/src/environment.py @@ -1,12 +1,10 @@ -from distutils.util import strtobool - import numpy as np import pygame import os from pygame import Vector2, DOUBLEBUF -from animals.sheep import Sheep -from animals.dog import Dog, ControllableDog -from utils import timed +from src.animals.sheep import Sheep +from src.animals.dog import Dog, ControllableDog +from src.utils import timed, strtobool import random as rand @@ -60,11 +58,12 @@ def _translate_to_canvas(self, vector: Vector2): return translated def _init_herd(self) -> list[Sheep]: + sheep = [] spawn_distribution = os.getenv("SHEEP_SPAWN_DISTRIBUTION") - print(spawn_distribution) + print(f"spawning sheep with distribution: {spawn_distribution}") mean = [0, 0] cov = np.array([[0.1, 0], # Variance for x and y (diagonal values) @@ -119,7 +118,7 @@ def update_animals(self): herd_copy = self.herd.copy() dogs_copy = self.dogs.copy() - p_excited = 0.002 + p_excited = 0.001 self._choose_sheep_to_excite(p_excited) for sheep in self.herd: diff --git a/src/main.py b/src/main.py index 97fa9a3..58243d9 100644 --- a/src/main.py +++ b/src/main.py @@ -3,9 +3,14 @@ from dotenv import load_dotenv load_dotenv() -from environment import Environment +from src.environment import Environment import os +import random as rand +import numpy as np +seed = 2 +rand.seed(seed) +np.random.seed(seed) if __name__ == "__main__": n_sheep = int(os.getenv("N_SHEEP", 0)) diff --git a/src/utils.py b/src/utils.py index 9dc2519..c8702bd 100644 --- a/src/utils.py +++ b/src/utils.py @@ -4,6 +4,7 @@ SHOULD_TIME = bool(os.getenv("TIME_EXECUTION", 0)) + def timed(func): def timer_wrapper(*args, **kwargs): start = time.time() @@ -11,8 +12,19 @@ def timer_wrapper(*args, **kwargs): duration = time.time() - start open(LOG_OUTPUTFILE, "a").write(f"{func.__name__},{duration}\n") return res + def default(*args, **kwargs): return func(*args, **kwargs) - return timer_wrapper if SHOULD_TIME else default + + +def strtobool(val: str) -> int: + val = val.lower() + if val in ("y", "yes", "t", "true", "on", "1"): + return 1 + elif val in ("n", "no", "f", "false", "off", "0"): + return 0 + else: + raise ValueError(f"Invalid truth value: {val}") +