Skip to main content

General physics engine for force-based particle simulations. Run `bengine -h` for CLI options.

Project description

Simulation Engine

Python Version License Oct 2024 - Present ย ย |ย  Ben Winstanley ๐Ÿ™‹โ€โ™‚๏ธ

Evac tile
Springs tile Nbody tile
Birds tile Pool tile

General physics engine for force-based particle simulations, built from scratch in Python.

I'm interested in numerical simulation and modelling. I noticed that a lot of my previous simulation projects tended to share the same workflow: initialisation, force-based timestepping, logging and rendering.

So to save myself 30 whole minutes of gruelling, predictable setup, I've spent the last year (on and off) working on this engine!

๐Ÿ“ Introduction

My simulation engine, or bengine, is a Python package featuring a commmand-line interface (CLI), where the user can choose from a number of preset simulation types to run - these pop up as little windows showing the final simulation on-loop:

Please have a quick scroll through the rest of this README if you'd like to learn more:


๐Ÿ“š Table of Contents

๐Ÿ› ๏ธ Get Started

Prerequisites:

  • Python 3.9 - Consider using pyenv for managing multiple python versions on your system.
  • FFmpeg - if not installed please see installation guide below.

Install with pip

  1. Create and activate a virtual environment
python -m venv venv

Windows

venv\Scripts\activate

Linux / macOS

source venv/bin/activate
  1. Install from PyPI
pip install --upgrade pip setuptools wheel
pip install bengine

Done!

Installation from source

  1. Clone the repository and move inside
git clone https://github.com/benw000/Simulation-Engine.git
cd Simulation-Engine
  1. Create and activate a virtual environment
python -m venv venv

Windows

venv\Scripts\activate

Linux / macOS

source venv/bin/activate
  1. Install as a package
python -m pip install --upgrade pip setuptools wheel
pip install -e .

Done!

Please see Troubleshooting if you have any issues.

(Back to contents)


๐Ÿง‘โ€๐Ÿ’ป Usage:

Below is a quick example showing most main command arguments.

Run a simulation

run_terminal_screenshot

  • Run a Predator-Prey simulation, with 10 Prey and 3 Predators
  • Set the timestep duration to 0.01 seconds, and use 200 timesteps in total.
  • Record a log of each particle's position, velocity etc. at each timestep in a readable .ndjson log, with a custom file path.
  • Save an .mp4 video of the simulation inside a custom folder path.

Load a simulation from logs

load_terminal_screenshot This reads the simulation you just logged and renders it again.

All arguments

Find help for all arguments with bengine -h:

help_terminal_screenshot

See the examples below for different simulation types available:

(Back to contents)


๐ŸŒŸ Examples:

8-ball pool breaking simulation ๐ŸŽฑ

pool-gif

Run this

bengine run pool -n 1 -t 500

Description

  • Pool balls are initialised in the normal setup, the cue ball starting off firing into the triangle with a slight random vertical deviance.
  • Balls repel off eachother, simulating elastic collision, and reflect off of the cushion walls, being removed if they hit the target of any pocket.

Forces

  • Repulsion force between contacting balls - very strong but active within a small range, scaling with $ \frac{1}{Distance}$.
  • Normal force from wall - this models each cushion as a spring, with any compression from incoming balls resulting in an outwards normal force on the ball, following Hooke's law.

Classroom Evacuation Model ๐ŸŽ“

evac-gif

Run this

bengine run evac -n 40 -t 200

Description

  • People are initialised at random points in the classroom, and make their way to the nearest exit, periodically re-evaluating which exit to use.
  • The graph on the right shows the number of people evacuated over time, which can be used to score different classroom layouts for fire safety.
  • Layouts are easily created by specifying pairs of vertices for each wall (seen in red), and specifying a number of target locations (green crosses).

Forces

  • Constant attraction force to an individual's chosen target exit.
  • Repulsion force between people - active within a personal space threshold, scales with $ \frac{1}{Distance}$
  • Repulsion force from walls - also active within a threshold, scales with $ \frac{1}{Distance^2}$
  • Deflection force from walls - force acting along length of wall towards an individual's target, prevents gridlock when a wall obscures the target.
  • Stochastic force - a small amount of noise is applied.
  • Note that additional bespoke forces would have to be specified in order to encode more intelligent, calculating behaviour.

Predator-Prey Model ๐Ÿฆ…

birds-gif

Run this

bengine run -n 50 5 -t 200

Description

  • Prey (white) and Predator (red) birds are initialised at random points.
  • The Predators act to hunt each Prey, always pursuing the closest bird and killing it within a certain distance threshold.
  • The Prey avoid the Predators, and flock together to increase chances of survival.
  • This all takes place on a torus topological domain, where opposite edges are connected with periodic boundary conditions.
  • The Predators aren't particularly intelligent, since their motion is governed by simple blind attraction at each timestep.

Forces

  • Prey repulsion from all Predators within a radius of detection, scaling with $ \frac{1}{Distance}$
  • Predator attraction to closest Pre with constant magnitude.
  • Constant attraction force on Prey towards the centre of mass of all Prey birds - this naรฏvely encodes flocking behaviour.
  • Repulsion force between birds - active within a personal space threshold, scales with $ \frac{1}{Distance}$.
  • Stochastic force - a fair amount of noise is applied to the prey, to simulate erratic movements to throw off predators. Less noise is applied to the predators, which are very direct.

Spring System Model ๐Ÿ”—

springs-gif

Run this

bengine run springs -n 50  -t 30

Description

  • Point particles are initialised at random positions on the plane; if a neighbour is within a spring length away, a spring is formed. Particles with no connections are culled before step 0.
  • We see larger molecules start to form as networks of connected particles reach an equillibrium.
  • Setting a larger spring length allows more particles to connect to eachother, increasing the complexity of the structures formed.

Forces

  • Elastic force following Hooke's law: $F = -k \cdot (Spring \ Extension)$
    This acts on both particles whenever the spring between them is in compression (red), or extension (yellow).
  • Damping force - directly opposes particle motion, scaling linearly with velocity.
  • Stochastic force - a small amount of random noise is applied to each particle.

N-body Gravitational Dynamics ๐Ÿ’ซ

nbody-gif

Run this

bengine run nbody -n 30

Description

  • Bodies are initialised with random positions and velocities, and masses of different magnitudes, chosen from a log-uniform distribution scale.
  • Each body feels a gravitational attraction towards every other body in the system. Larger bodies attract smaller ones, which accelerate towards them.
  • To a first order level of approximation, these smaller bodies then engage in elliptic orbits around the larger body, or are deflected, shooting off on a parabolic trajectory. As more bodies shoot off, their density in our viewing window decreases.

Forces

  • Gravitational attraction force - each body is attracted to every other body in the system, following Newton's law of universal gravitation:
    $F = G \frac{Mass_1 Mass_2}{Distance^2}$.

(Back to contents)


๐ŸŒณ Project Structure

Simulation-Engine
+--- ๐Ÿ“ฆ simulation_engine                 
โ”‚    +-- ๐Ÿ“ main
|    |   \-- entrypoint.py      # ๐Ÿ‘‹ CLI entrypoint into program
โ”‚    +-- ๐Ÿ“ core
|    |   +-- manager.py         # ๐Ÿง  Contains main Manager class
|    |   +-- particle.py          # ๐Ÿซง Contains Particle class
|    |   \-- environment.py     # ๐Ÿก Contains Environment, Wall, Target classes
โ”‚    +-- ๐Ÿ“ types
|    |   +-- birds.py           # ๐Ÿฆ† Predator-Prey
|    |   +-- evac.py            # ๐ŸŽ“ Classroom evacuation
|    |   +-- nbody.py           # ๐Ÿ’ซ N-body dynamics
|    |   +-- pool.py            # ๐ŸŽฑ Pool breaking
|    |   \-- springs.py         # ๐Ÿ”— Spring system
|    \-- ๐Ÿ“ utils
|        \-- errors.py          # โ—๏ธ Custom errors
|
+-- ๐Ÿ“ tests
โ”‚   +-- test_inputs_args.py     # โŒ Test handling bad inputs to CLI
โ”‚   \-- test_integration.py     # โœ… Test end-to-end integration of all modes
|
+-- ๐Ÿ“ data
|     +-- ๐Ÿ“ demo_videos        # โœจ Contains the shiny gifs you see in this README
|     +-- ๐Ÿ“ Simulation_Logs    # ๐Ÿ—„๏ธ Folder for user-generated logs
|     \-- ๐Ÿ“ Simulation_Videos  # โ–ถ๏ธ Folder for user-generated videos
|
+-- pyproject.toml             # ๐Ÿ“ฆ Project packaging for use with pip install
+-- README.md                  # ๐Ÿ“– What you're currently reading   
+-- LICENSE.md                 # โš–๏ธ License (MIT)
+-- .gitignore                 # ๐Ÿ”• Tells git to ignore certain local files

(Back to contents)


๐Ÿ”— Dependencies

FFmpeg

We use the FFmpeg binary to save the rendered simulations as .mp4 videos.
This can be installed via your OS package manager:

๐ŸชŸ Windows: https://ffmpeg.org/download.html

๐ŸŽ macOS:

brew install ffmpeg

๐Ÿง Ubuntu/Debian:

sudo apt install ffmpeg

External Python libraries

The following external python libraries are found in the pyproject.toml file and will be installed automatically with pip install bengine.

Package Version Usage
numpy 2.0.2 Main computation
matplotlib 3.9.4 Rendering
opencv-python 4.11.0.86 Handling windows
rich 14.0.0 Pretty printing and tables
tqdm 4.67.1 Progress bars
pathvalidate 3.2.3 Checking user-supplied paths

(Back to contents)


๐Ÿฅธ Design

My primary aim with this package is to produce a clean, polished product of contained scope, which just works.

The design is oriented around two points of interaction with the user/developer - the CLI and the specific simulation module, which should both have as few hurdles as possible.

  • The CLI should allow for quick, visually pleasing simulation, with well-informed default options.
  • The simulation module, eg evac.py, need only describe features essential to that simulation; most shared features between simulation types should be obscured in Particle and Manager.

Architecture & Features

architecture

The following is an outline of the main classes and functions, and some of their standout features.

Particle

  • Base class for all particles, contains core geometric and numerical logic:
    • Verlet Integration timestepping method used for simplicity and numerical stability: $ x_{next} = 2 \cdot x_{current} - x_{last} + a \cdot dt^2 $
    • Supports simulation over non-euclidean toroidal domain, as seen in Predator/Prey.
  • Define simulation-specific particles with child classes, e.g. Human(Particle).
    These inherit the core logic and introduce specific force-based models to describe their dynamical system, along with plot functions for custom appearance when rendered.

Environment

  • Base class similar to Particle, contains unchangeable environment elements that the particles respond to:
    • Wall(Environment) subclass initialised between 2 vector endpoints, contains geometry for vector normals etc.
    • Target(Environment) subclass initialised at a point, particles can be attracted to the target.

Manager

  • Singleton class which oversees the pipeline of timestepping, writing/loading states and rendering.
  • Agnostic to specific simulation type, handled via dependency injection from entrypoint.py
    • Stores universal state as a nested Manager.state dictionary of Particle and Environment subclass objects.
  • Flexible options for memory management of simulation history:
    • By default saves history in memory as well as writing to log file.
    • Choose to disable either depending on system constraints on memory and/or storage.
    • If neither, synchronous mode is selected, where frames are displayed as soon as they are computed.

Logger

  • Logging class used by Manager to handle reading/writing simulation state from .ndjson logs.
  • Each entry to the ndjson corresponds to a Manager.state at a particular timestep -- serialised into json format using to_dict() and from_dict() methods from each particle/environment object.
  • Adjustable chunk size for memory-efficient reading of large logs.

matplotlib

  • At each timestep we iterate through the current Manager.state dictionary, calling the draw_plt method of each object to plot onto a shared matplotlib axis.
  • We keep track of plot elements in self.plt_artists lists for each object.
  • We use matplotlib's FuncAnimation to compile frames into a rendered pop-up window video.

Entrypoint

  • We alias the CLI command bengine to main/entrypoint.py, parsing the user arguments with argparse and a layer of custom validation functions.
  • The validated arguments are sent to the setup() function of the particular simulation type we're running (e.g. evac.py), which returns a Manager instance to run the pipeline with.

Testing (unittest)

  • We conduct end-to-end integration tests for a near-exhaustive list of CLI argument combinations.
  • We also test the CLI with bad arguments to ensure errors are correctly thrown.

Packaging

  • We package the project as a pip install-able module with pyproject.toml, which contains our small list of dependencies
  • Depending on future development this may be complemented by a Docker image, compiled binary or web app for easier distribution.

Reflections

This project has primarily served as a vehicle for learning and applying software best practices. During development I've focused on a few things:

  • ๐Ÿ—„๏ธ Modular, object-oriented design, with separation of concerns
  • ๐Ÿ‘“ Clean and readable code
  • ๐Ÿ“š Comprehensive docstrings and documentation
  • โœ… Automated testing and input validation
  • ๐Ÿ“ฆ Accessible packaging and end-to-end software design

Most of the work on this package has taken place in short bursts on my train journeys to and from work. It's been a large undertaking to improve my terrible, old code and restructure for a more modular, scaleable design - in some sense I've learnt a lot about refactoring existing work (whilst resisting the temptation to completely rewrite it!)

It's been rewarding to build this system from the ground up, and witness the consequences of different architectural decisions; some of which paid off nicely, while others induced a headache.

If you've got this far, thanks for reading! Feel free to contact me via GitHub or LinkedIn.


๐Ÿฆ† Next steps

  • Create comprehensive CLI with argument validation.
  • Create unittest test suite for automated testing of all modes.
  • Release as a PyPI package.
  • Introduce interactive mode for some simulation types (birds, pool) via PyGame backend.
  • Create Reinforcement Learning gym to train intelligent birds with PyTorch.
  • Computational speedups with Numba JIT, further vectorisation.
  • Repackage as simple web app.
  • Open up to open-source support by making clear contribution guidance.

(Back to contents)


๐Ÿ”ง Troubleshooting

Make sure your tools are up to date:

# (Linux / macOS)
sudo apt-get update
# Python (inside virtual environment)
python -m pip install --upgrade pip setuptools wheel

Matplotlib rendering issues

If no window appears after you see "Rendering Progress: 1%" in the command line, then Matplotlib may by default be using a non-interactive backend (Agg), which cannot display figures in a separate window.

This is a known (and nuanced) issue which depends on your operating system and Python installation, and it falls outside the scope of this project to fully resolve. However, you could try the following:

TkAgg GUI backend

Try using the TkAgg GUI rendering backend for matplotlib by setting the following environment variable in your shell before running the program:

# (Linux / macOS)
export MPLBACKEND=TkAgg
# (Windows)
set MPLBACKEND=TkAgg

If this doesn't work, you may need to install the tkinter toolkit

# (Linux)
sudo apt-get install python3-tk
# (macOS)
brew install python-tk

Useful links

(Back to contents)


โš–๏ธ License

MIT License. See LICENSE for more information.

(Back to contents)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bengine-1.0.0.tar.gz (70.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

bengine-1.0.0-py3-none-any.whl (76.2 kB view details)

Uploaded Python 3

File details

Details for the file bengine-1.0.0.tar.gz.

File metadata

  • Download URL: bengine-1.0.0.tar.gz
  • Upload date:
  • Size: 70.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.9.7

File hashes

Hashes for bengine-1.0.0.tar.gz
Algorithm Hash digest
SHA256 a6b89179c7414387bab5b8dfd13f9aff6300d2c0833c51ab20275a7638ec984c
MD5 9ed65c3a37c470041081c6c79223694b
BLAKE2b-256 250aac6fc609bc08ac5543f54decffe84d11cc4f9911b286b3c62820723153d0

See more details on using hashes here.

File details

Details for the file bengine-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: bengine-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 76.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.1.0 CPython/3.9.7

File hashes

Hashes for bengine-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 345fe4d4856741be0e694fc04114fa236b6aa0fc033ffd52a6efa591797bd992
MD5 a43af5ef71e84b30d8cd87e3e268c274
BLAKE2b-256 fd19d3ef422559283486856a91239c98cdad466e8265d93890c2dad7677c63b7

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page