.. -*- mode: rst -*- .. _chap-particle-life: *************** Particle life *************** .. sectionauthor:: Ian Bishop [status: content-mostly-written] Motivation, prerequisites, plan ================================= Today, we will be exploring how complex behavior can emerge from simple rules. We will make particles attract and repel one another, then look for unexpected patterns that we didn't explicitly program. Prerequisites: * The 10-Hour "serious programming" course Awesome, now that that's out of the way, let's take a look at what we'll be doing today: 1. calculate one interaction 2. draw some particles 3. run the simulation 4. and experiment with its rules. Getting started ================= On Linux, prepare a Python virtual environment: .. code-block:: console sudo apt install python3-venv python3-pip python3 -m venv particle-venv source particle-venv/bin/activate python -m pip install pygame If the first command fails, install your specific distribution's Python venv install command. .. note:: Pygame is what's commonly known as a "game engine." While game engines are built mainly to aid in game development, we can also use them for programs of smaller scope, such as a simple particle simulation. We won't do anything too fancy, but pygame allows us to do things such as drawing shapes and reading keyboard inputs, which we will use in this chapter. Our particles will come in three colors: red, green, and blue. Their rules are simple: * When particles are too close, they repel * When particles are too far, there is no force * And when particles are near each other, they follow a preprogrammed rule. Rules are cool and all, but wouldn't seeing it in action be way cooler!? Let's first test calculating force. Try the following: .. code-block:: python distance = 40 attraction = 0.6 push = attraction * (80 - distance) / (80 - 12) print("Push Magnitude:", push) This should give about 0.353. Change the sign of ``attraction`` and try again. The model is very simple: just one formula for calculating force. Basic Drawing With Pygame ========================= But what does this actually look like? Save this as :file:`particle_life.py` and run ``python particle_life.py``: .. _listing-particle-life-two-dots: .. code-block:: python :caption: Draw two particles import pygame pygame.init() screen = pygame.display.set_mode((400, 300)) screen.fill((15, 18, 25)) pygame.draw.circle(screen, (240, 90, 90), (100, 100), 6) pygame.draw.circle(screen, (90, 220, 130), (140, 100), 6) pygame.display.flip() print("Initialized two particles.") running = True while running: event = pygame.event.wait() if event.type == pygame.QUIT: running = False pygame.quit() The dots are given positions based on coordinates in the code. Try moving the green dot below the red one. Remember: y increases downward on the screen. The simulation ================ Replace the file with :numref:`listing-particle-life-py` and run it again. Press :kbd:`Space` to pause, :kbd:`R` for new positions, and :kbd:`Escape` to quit. Watch for clusters and moving groups. .. _listing-particle-life-py: .. literalinclude:: particle-simulation.py :caption: particle-simulation.py :language: python There are a couple things to note here: * ``make_particles()`` creates all the particles that we see on screen * ``wrapped_difference()`` corrects the value for distance between particles to account for the particles being on opposite sides of the window, since we're simulating these particles as if one end of the window connects to the other. * ``force()`` calculates the amount of force applied to the particle based on distance and hard-coded attraction values * ``step()`` uses the calculated force and applies it to each particle in the simulation * ``draw()`` clears the entire window, and then draws each particle using its position You may also notice that some of the variable names here are capitalized, and that's because these are variables we intend to keep as constants. Constants are variables that never change state. But ask yourself: Where does a cluster's shape appear in the code? It doesn't! You can search for as long as you'd like, but you will never find these behaviors intentionally programmed. Each particle responds to its neighbors, and those movements change the neighborhood. The resulting group behavior is **emergence**. Though the simulation may make it seem so, these particles are not alive, and the rules are invented. Some arrangements settle down instead of continuing to move. Have some fun with it! ---------------------- Try changing one setting at a time. Predict the result, run the program, and observe what happens. Restart with the same seed for comparisons, then try other seeds like 8 and 9. .. exercise:: Try changing the values in ``RULES``. What changes about the behavior of the particles? .. exercise:: Set every table entry to zero. Theoretically, the particles shouldn't move, right? So then... why can nearby particles still move? .. exercise:: Change the rules back to their original state. Compare ``DAMPING = 0.5`` and ``0.9``. Which value of ``DAMPING`` allows the particles to glide longer? How come? .. exercise:: Try changing ``REACH`` to 50. Try 100. What happens to the groups? Keep ``0 < CLOSE < REACH`` and ``REACH`` below half the smaller window dimension for the most interesting results. .. exercise:: Double the value of ``COUNT``. The nested loops check ``COUNT * COUNT`` pairs. How much more work is that? Reduce the count if the simulation runs slow. Conway's Game of Life ======================= Conway's Game of Life follows a few simple guidelines. It uses a grid of live and dead cells, on or off. Every step, each cell's eight neighbors (the cell directly adjacent), including diagonals, are used to evaluate the following two rules: * A live cell survives with two or three live neighbors; otherwise it dies. * A dead cell becomes alive with exactly three live neighbors. All cells update simultaneously. Here, ``#`` is alive and ``.`` is dead. Also, let's assume cells outside the drawing are dead: .. code-block:: text :caption: A blinker repeats after two steps ..... ..... ..... ..... ..#.. ..... .###. --> ..#.. --> .###. ..... ..#.. ..... ..... ..... ..... Check the neighbor counts yourself. Other arrangements also have names, such as gliders, which move across the grid. As with particle life, local rules produce a pattern that no single cell is programmed to create. Conway's Game of Life is known as a cellular automaton. Connections to real life ========================== In flocking models, individuals avoid crowding while aligning their direction and stay near their neighbors. These rules are simple alone, but together they produce coordinated movement. While our particles have attraction and repulsion, they have no alignment rule. Also think economics. Imagine buyers choosing affordable goods and sellers adjusting prices when stock sells out or remains unsold. Repeated decisions produce market-wide price patterns that we can predict. What rules might you give each buyer and seller? What could you measure? The connection here is that local decisions can produce complex, collective behavior. Further reading =============== * Firstly, there is the chapter on :ref:`chap-emergent-behavior`, which talks a bit more about Conway's Game of Life, so I would recommend taking a look at that. * https://www.youtube.com/watch?v=p4YirERTVF0&t=379s - This video gives another high-level explanation of particle life and has some absolutely wicked visuals, so if you're interested in particle life I would highly recommend it. * https://www.red3d.com/cwr/boids - This explains the flocking model from earlier. It isn't super pretty, but the explanation is clear and concise. * https://www.youtube.com/watch?v=bqtqltqcQhw - This explains the flocking model in a high-level video format. This same channel also has some other phenomenal videos that I highly recommend checking out to get your head-gears turning. * https://math.mit.edu/traffic/ - This is a bit of a fun one. It describes how traffic jams can happen even when there is no real obstruction.