Difference between revisions of "Simple ecosystem simulation (NetLogo)"

From Simulace.info
Jump to: navigation, search
(Eating)
(Splitting (making an offspring))
Line 283: Line 283:
 
       ]
 
       ]
 
</nowiki>
 
</nowiki>
 +
 +
Reproduction function is guaranteed to not create any new collisions. This is because it produces two creatures (the original and a hatched one) with half the mass who are positioned that they are touching (not colliding!) and heading in opposite directions. Their point of touch is where center of original creature used to be before it split.

Revision as of 16:21, 27 May 2016

Simple ecosystem simulation

This problem is partly inspired by theory of "Primordial soup", partly by modern things such as agar.io game and partly by Swimbots simulation.

The primordial soup was a term created by Alexander Oparin in his theory of origin of the life on Earth. This theory states that there were just chemical elements which, under pressure and exposed to various energies (electrical discharges, high temperature), formed monomers, polymers and, in the end, live organisms. ( https://en.wikipedia.org/wiki/Primordial_soup )

Agar.io is a web-browser based game where the player controls his own cell. The cell can move, eject some of its matter and split itself in two. It can also absorb smaller cells which belong to other players. There is no particular goal in the game - just to, well, live long and prosper.

Swimbots is a simulation of the "gene pool" and originally was created as an answer to the statement of Richard Dawkins that:

"The very idea of a gene pool has no meaning if there is no sex. 'Gene Pool' is a persuasive metaphor because the genes of a sexual population are being continually mixed and diffused, as if in a liquid. Bring in the time dimension, and the pool becomes a river, flowing through geological time..." -Richard Dawkins, The Ancestor's Tale, page 432

Therefore swimbots are simulation where agents can move around in a "pool" and have two basic goals - 1) to eat and 2) to reproduce. Everything else emerges on its own during the simulation. Official web of the Swimbots is www.swimbots.com (TODO link) where the complete simulation app can be downloaded.

For purpose of our simulation we blended those three topics together and the result was a small artificial ecosystem which could have existed on the primeval Earth.

The blend is as follows:

Oparin's primordial soup already evolved into single-cell organisms - algae and protozoa. Some of protozoa are herbivorous (can eat single-cell algae), some of them "carnivorous" (can eat other protozoa). More detailed information is given in the problem statement but main idea of our work is to simulate what happens in the pool as the time goes on. We are insterested in this, because we want to determine if such a closed ecosystem could actually exist - and if it could, what is its equilibrium (here we consider the pool as an actual small puddle of water with all those happy little creatures in it, separated from the "outside" world).

Problem definition

Name of simulation: Simple ecosystem simulation

Class: 4IT495 Simulace systémů (LS 2015/2016)

Author: Petr Fišer

Type of model: Multi-agent simulation

Modelling software: NetLogo


We have three types of objects, algae (AL), herbivorous protozoa (HP) and carnivorous protozoa (CP).

Basic properties:

  • Algae do not move, protozoa, on the other hand, do.
    • They are not intelligent enough so they move randomly.
    • Each protozoan has its movement speed (determined upon its creation) and a direction it is moving in.
    • There is a 5% chance the direction changes.
  • Algae emerge on their own.
    • Frequency of algae regrowth is adjustable.
  • HP can eat algae.
  • CP can eat HP and CP. If CP eats AL, it does not count as consuming food (the AL disappears though).
  • If protozoan consumes food (algae or other protozoa) it grows by a size of food it has just consumed.
  • Protozoa can eat only smaller food than they are.
  • "Eating" happens when two objects touch.
  • Each object has a size.
    • Size of the AL is always 1.
    • Initial size (mass) of the HP is 3.
    • Initial size (mass) of the CP is 5.

Life of a protozoa:

  • When moving around, protozoa consume energy.
    • The bigger they are, the more energy they need. Therefore, the mass of a protozoa continually decreases by 0,25% (HP) and 0,5% (CP), respectively
    • Mass of protozoa decreases by at least by 0.001.
    • When two objects collide (touch), "eating" happens.
    • Two objects cannot overlay.
    • When the cannot eat it, they stop moving in the given direction.
    • If protozoan has mass equal to zero, it dies.
  • If protozoan grows beyond two times of its initial size, it can reproduce by spliting itself.
    • Beyond two times of initial size, the chance of splitting is 25%.
    • Beyond three times of inital size, the chance is 50%.
    • New protozoan does inherit speed atribute from its parent.

Starting conditions:

  • A puddle of water with starting amount of AL, HP and CP scattered around. Those amounts need to be adjustable.
  • Algae regrows (adjustable frequency).

Goal

Observe evolution of the ecosystem as a whole in order to determine if it is able to survive in the long run. If it is, then determine for which values of starting parameters there is an equilibrium formed in the ecosystem and determine ratios between count of algae,HP and CP respectively.

Used method and software

Multi-agent simulation using NetLogo.

Model

This section describes model implementation. First we show user interface and means for changing default inputs. Later, we will describe implementation on the level of functions called throughout the model. For complete programmer-level documentation, please, see the nlogo code itself.

User interface

Relativne stabilni 2.png

On the left side, we see simulation inputs and internal model variables. Variables which are meant to be set have their boxes above the "setup" button. Those are: initial number of algae, herbivores and carnivores and algae regrowth rate and amount. Model is run by hitting "go" button. Other inputs are internal model variables which are, generally, not meant to be fiddled with. They are there to make model more customizable for different purposes. During our simulation, those settings are left to default (because that is how the original problem was specified). Buttons not fully shown on the screenshot are used to reset internal model settings and to reset all settings to default, respectively.

On the right side, we see plots and counters. We observe counts of our creatures and also their overall mass. This is important because, as we see later, in some circumstances a supercreature can emerge - such a creature can fill the whole simulation window with its mass but in absolute counts, there is nothing else than this one creature. Other monitors give us a peek on the biggest/smallest and fastest/slowest creatures and also on the average values of speed and mass. In the center there is a simulation window. One important fact is that this is not only a place to show something, its size actually matters. As we see later, when simulation window (=puddle of water) is too small, the ecosystem is not able to survive for long.

Implementation

This section describes actual implementation of a simulation.

Agents (creatures)

There are three types of agents in the model - alga, herbivore and carnivore. Model consists only of agents. This is because we have to simulate collisions between them and modelling algae (algae do not move) as patches would cause troubles in computing collisions, not mentioning that a patch cannot have circular shape. Each agent - or "creature" as model internally calls it - has six attributes:

  • creature-type - alga / herbivore / carnivore
  • mass - mass of a creature, also used as a radius of circular body
  • speed - a speed of a creature, as "distance per tick"
  • already-existing - if it does exist or was freshly created (useful when creating new creatures)
  • weight-consumption-pct - on every turn, creature consumes some percent of energy contained in its own weight
  • initial-mass - initial (default) mass, 1 for AL, 3 for HP, 5 for CP

Most attributes are given to the creature upon its creation. This allows readjusting model parameters during the simulation without affecting already existing creatures. Type of creature determines its color, initial-mass and weight-onsumption-pct. Shape is always circle and size attribute is set to twice the mass.

Mass of a creature continually decreases because a creature needs to consume energy in order to move (and live at all). This is governed by weight-consumption-pct (percent of current creature mass) but is always at least weight-consumption-min.

Each creature has a circular body. This is main difference from other NetLogo agent simulations where agent is treated more or less as a point. Agents (creatures) are created by three functions: breed-algae, breed-herbivores and breed-carnivores.

Each creature can split itsef when getting more than (splitting-threshold-1 * mass). There are two such thresholds because when creature has more than (splitting-threshold-2 * mass) it can have differrent splitting probability. Probabilities are given by splitting-probability-1 and splitting-probability-2, respectively, and are adjustable. Unlike other internal variables, splitting-* variables influence whole model including already existing creatures. Splitting is implemented by cell-split.

When created, each creature has direction it is facing. There is 5% (adjustable) probability that the direction changes. This is implemented by new-direction function.

Algae regrowth

Algae regrows during the simulation run. Its behavior is governed by algae-sprout-ticks (number of ticks between regrowth) and algae-sprout-amount (amount of alga in one regrowth). Regrowth is implemented by sprout-algae function. When sprouting, alga is placed in the simulation window in such a way that it is not overlapping any existing creature (including other alga). This is checked by collission detection routine.

Global parameters and variables

  • max-creature-speed - max speed of a creature; generated as ((random-float max-creature-speed) + 0.000000000001)
  • alga-initial-mass - initial mass of an alga
  • herbivore-initial-mass - initial mass of a herbivore
  • carnivore-initial-mass - initial mass of a carnivore
  • probability-direction-change - probability that creature changes direction while moving
  • weight-consumption-min - minimal (absolute) weight consumption
  • weight-consupmtion-pct-herbivore - percentual weight consumption of herbivores
  • weight-consupmtion-pct-carnivore - percentual weight consumption of carnivores
  • algae-sprout-ticks - number of ticks, after which new algae will grow
  • algae-sprout-amount - number of individual alga in one grow
  • splitting-threshold-1 - creatures split when big enough, this is first threshold (multiple of mass)
  • splitting-probability-1 - probability creature will split itself when bigger than threshold
  • splitting-threshold-2 - the second splitting threshold
  • splitting-probability-2 - splitting probability after second threshold can be different
  • algae-setup-count - initial count of alga
  • herbivores-setup-count - initial count of herbivores
  • carnivores-setup-count - initial count of carnivores
  • last-algae-sprouting - global counter how many go loops passed since last algae regrowth

Program functions

This is a printout of function headers with only a brief explanation what they do. For more detailed information, please see the code.

  • reset-all-settings - resets all settings to defaults
  • reset-model-internal - settings - resets internal model settings to defaults
  • setup - sets up the simulation according to the parameters
  • go - main simulation loop
  • check-collisions - checks collisions between agents
  • process-collision [creature1-id creature2-id distance-delta] - if collission of agents is detected, this function is called to resolve it
  • resolve-back-off [creature-who creature-who-away-from distance-delta] - when two creatures collide and none can eat the other, the smaller one has to correct its position
  • process-eating [eater-id food-id] - when two creatures collid and one can eat the other, the bigger eats the smaller one
  • process-nonweight-eating [eater-id food-id] - this function is called when carnivore collides with alga; carnivore eats it but does not gain weight
  • place-nonconflicting [creature-id] - ensures agent placement such that it does not collide with those already placed, gives up after 500 (hardwired) tries to prevent inifinite loop
  • cell-split - asks carnivores and herbivores to split themselves according to the splitting thresholds and probabilities
  • consume-weight - asks carnivores and herbivores to lowe their weight according to the weight-consumption-pct
  • do-weight-decrease [decrease-pct] - this function realizes actual weight decrease of an creature
  • sprout-algae - this method regrows algae
  • breed-creatures [amount] - creates non-specialized creatures, is called from breed-algae, breed-herbivores and breed-carnivores
  • breed-algae [amount] - creates algae
  • breed-herbivores [amount] - creates herbivores
  • breed-carnivores [amount] - creates carnivores
  • (reporter) new-direction [previous-direction] - determines if creature should change its direction

Simulation setup

Before starting a simulation, we need to set it up. Because many functions are called internally from breed-* functions, the setup is very simple.

  1. clear-all
  2. set default creature shape as "circle"
  3. set patch color as "sky - 2"
  4. set last-algae-sprouting to zero
  5. create desired count of algae, herbivores and carnivores
  6. reset-ticks

Simulation go (main loop)

When simulation is set up, we can run it by calling go function in loop. go function looks as folows:

  1. try to sprout algae
  2. ask all creatures to move by forward speed command; alga has speed set to 0 so it does not move
  3. check for collisions and resolve them
  4. ask cells to split if they can and wish; the splitting algorithm ensures no new collissions are created during splitting
  5. decrease creatures' weight
  6. refresh creatures' directions
  7. tick

Movement and collision detection

Herbivores and carnivores move, algae, on the other hand, don't. Our problem statement is sort-of based on a fact that there is a limited space in a puddle and that creatures occupy significant space. They also cannot overlap (not even mentioning move one through the other). When two creatures touch, they can stop moving or eat each other. Each tick (each loop of go function), creatures move forward in the direction they are currently facing by a speed distance. The speed attribute of a creature can be understood as a "distance per tick".

Creatures have circular shape of size twice the creature's mass - mass is therefore a radius of a creature in the visualization window. For detecting collisions we had to create special routine. This routine, apart from working correctly, has to respect that if two creatures collide in a model, it would be good for them to collide optically. For this purpose, we stated mass as a radius of a creature.

Relying on the fact that creatures are circles, we can then check for collisions very simply: if (mass of agent1) + (mass of agent2) > (distance of agent1 to agent2), then there must have been a collision. Distance between agents is measured as a distance of centers of circle icons because that is how NetLogo draws them (size attribute of an icon is the same as diameter when icon is a circle).

We implement collision detection in check-collisions function which just takes every tuple of agents possible and checks for collision. If there was any, the process-collision is called on particular agents. The main decision block of process-collision looks as follows and decides possible combinations of colliding agents. Please note that we do not need reverted conditions like (creature1 alga and creature2 herbivore) or (creature1 herbivore and creature2 alga). This is because check-collisions generates all combinations of tuples (creature1,creature2).


...

if (creature1-type = "alga" and creature2-type = "herbivore") [
     ifelse (creature1-mass >= creature2-mass) [
       ;; alga is bigger, herbivore backs off
        resolve-back-off creature2-id creature1-id distance-delta
      ] [
        ;; alga is smaller, herbivore eats it
        process-eating creature2-id creature1-id
      ]
  ]
  if (creature1-type = "alga" and creature2-type = "carnivore") [
      ifelse (creature1-mass >= creature2-mass) [
        ;; alga is bigger, carnivore backs off
        resolve-back-off creature2-id creature1-id distance-delta
      ] [
        ;; alga is smaller, carnivore destroys it
        process-nonweight-eating creature2-id creature1-id
      ]
  ]
  if (creature1-type = "herbivore" and creature2-type = "herbivore") [
      ifelse (creature1-mass >= creature2-mass) [
        ;; herbivore1 is bigger, herbivore2 backs off
        resolve-back-off creature2-id creature1-id distance-delta
      ] [
        ;; herbivore2 is bigger, herbivore1 backs off
        resolve-back-off creature1-id creature2-id distance-delta
      ]
  ]
  if (creature1-type = "herbivore" and creature2-type = "carnivore") [
     ifelse (creature1-mass >= creature2-mass) [
        ;; herbivore is bigger, carnivore backs off
        resolve-back-off creature2-id creature1-id distance-delta
      ] [
        ;; herbivore is smaller, carnivore eats it
        process-eating creature2-id creature1-id
      ]
  ]

  if (creature1-type = "carnivore" and creature2-type = "carnivore") [
      ifelse (creature1-mass = creature2-mass) [
        ;; both carnivores are of equal size, one of them backs off
        resolve-back-off creature2-id creature1-id distance-delta
      ] [
        if (creature1-mass < creature2-mass) [
          ;; carnivore1 is bigger, eats carnivore2
          process-eating creature2-id creature1-id
        ]
      ]
  ]

Our collision system therefore works similarly to collision detection systems used in e.g. computer games - we forward the time by a small amount and then we check if collision happened. If it did, we react and either let one creature eat other or correct their positions in such a way that the are touching but not colliding.

Back off (position correction)

When two creatures collide and none can eat the other, one of them must correct the position so they are not colliding any more. We respect a "rule of the heavier" where the creature with higher mass stays on its position and the one with smaller mass moves in order to resolve the conflict. It is not necessarily moved backwards, it can bounce-off of the heavier creature. This is done by calculating how much creatures are overlapping and then setting heading property of smaller creature straight away from the heavier one. Direction is set to be parallel to line connecting centers of colliding creatures. Colliding creature is then moved by a distance of which they were overlapping.

Eating

When two creatures collide and one can eat another, eating happens. This is processed by process-nonweight-eating (when carnivore collides with smaller alga) and process-eating (for everything else) functions. Nonweight eating simply removet the eaten agent from the agentset. Regular eating transfers mass of creature that is being eaten to its devourer and repaints his icon.

Some new collision may occur after the creature enlarges, but in practice we observed them being with no significant effect on the simulation. With bigger creatures, the speed they move is small enough that such collision will get resolved in another go loop.

Splitting (making an offspring)

When creature gains enough mass, it can reproduce by splitting itself in two. The probability of splitting is governed by splitting-probability-{1,2} and the threshold after which it occurs is set by (splitting-threshold-{1,2} * initial-mass). Decision routine is this (algae do not reproduce):

                        ;;mass over 2*initial-mass
      if (((mass >= (splitting-threshold-1 * initial-mass))
        and             ;;but below 3*initial-mass
        (mass < (splitting-threshold-2 * initial-mass))
        and             ;;generate random [0;1) and when lower than 0.25
        ((random-float 1) <= splitting-probability-1))
      or                 ;;mass over 3*initial-mass
      ((mass >= (splitting-threshold-2 * initial-mass))
        and              ;;generate random [0;1) and when lower than 0.5
        ((random-float 1) <= splitting-probability-2)))
      [
        set size mass     ;;make original creature half the size and move it
        forward (mass / 2)
        set mass (mass / 2)
        hatch 1 [         ;;hatch a clone and move it in opposite direction
          set heading (heading + 180)
          forward (mass * 2)
        ]
      ]

Reproduction function is guaranteed to not create any new collisions. This is because it produces two creatures (the original and a hatched one) with half the mass who are positioned that they are touching (not colliding!) and heading in opposite directions. Their point of touch is where center of original creature used to be before it split.