The logistic map

A first dive into complexity and chaos.
April 14, 2018

I’m currently reading Complexity: A Guided Tour, a wonderful book written by the American scientist Melanie Mitchell. I created the animation shown above using an equation I learned about in the book, the logistic map, which is the most famous equation in the field of chaos theory. Here it is:

xt+1=Rxt(1xt)

At its core, the logistic map is used to model the growth of a population from one generation to the next. In order to take it apart and understand it, let’s first consider the birth rate (or natality) of a population, which we will designate TN and calculate this way:

TN=vp×1000

v represents the amount of births (over a full year, for example) and p represents the size of the total population. That being said, I wonder if p represents the size of the population at the beginning or at the end of the year. Does the population p include the new births v?The quotient of v and p is then multiplied by 1000 because the birth rate is usually expressed in per thousand. The death rate (TM) is calculated in the same way but by replacing births by deaths. Another fundamental notion is the carrying capacity (K) of an environment: the maximum amount of individuals of a given population that the environment can sustain. Here is the logistic model that uses these values and allows us to calculate the size of the population (n) at the generation t:

nt+1=(TNTM)(ntn2tK)

The amount of individuals who perish from overcrowding is n2tK (which is something I do not yet understand and that I will need to investigate). Now, to get to the simpler equation of the logistic map, we first need to consider the variable R, whose value is the birth rate minus the death rate (TNTM). We now obtain this equation:

nt+1=R(ntn2tK)

We then need to divide each side of the equation by the carrying capacity (K):

nt+1K=R(ntKn2tK2)

Now, we will do something surprising and interesting: we will represent ntK (which is the size of the population divided by the carrying capacity, or the fraction of the carrying capacity that is currently used) by the value xt. By replacing this first expression by the second one, we finally obtain the logistic map I find it amusing to realize that the term “logistic” is here somewhat arbitrary. It comes from a book by the French mathematician Pierre François Verhulst and, according to Wikipedia, “the author doesn’t explain his choice but ‘logistic’ has the same root as logarithm and logistikos means ‘calculation’ in Greek.” It would be difficult to pick a more imprecise term.

xt+1=R(xtx2t)

We can also reduce the equation:

xt+1=Rxt(1xt)

So when xt=0, the population is not occupying any part of the carrying capacity, and when xt=1, it occupies all of it. The value of xt is always between 0 and 1 inclusively, which can be written xt[0;1].

An equation with an astounding behaviour

To make the animation shown at the top of this article, I wrote a short program allowing me to explore the surprising properties of the logistic map. It’s important to mention that, while doing these experiments, I’m no longer concerned about the “real” or “physical” origins of this equation. In this spirit, I really loved this quote from Mitchell’s book: “Given this simplified model [the logistic map], scientists and mathematicians promptly forget all about population growth, carrying capacity, and anything else connected to the real world, and simply get lost in the astounding behavior of the equation itself. We will do the same.”

After several experiments, I had the idea of creating a graph where the positions of points are defined by the variables x and y, to which I give slightly different initial values:

x0=0.0001y0=0.0002

These variables are then iterated 1000 times in the logistic map (whose R value is set to 3.58). The result is displayed on a single frame of animation, which consequently contains 1000 dots. Then, for the next frame of animation, everything is erased and the variables x and y are initialized again to their starting value, but that starting value has changed. For each new frame, both starting values are incremented by 0.000001. For example, for the second frame of animation, the system is initialized in this way:

x0=0.000101y0=0.000201

Thus, I think that if we let a be a frame of animation, and t still represents the iteration index of the logistic map inside that frame of animation, we can say that each animation frame is initialized like this:

x0a=0.0001+(a×0.000001)y0a=0.0002+(a×0.000001)

It’s interesting to realize how the logistic map creates such fluctuations in the positions of all dots, even with very similar starting values. This is an important notion and one that is superbly explained by Melanie Mitchell, the “sensitive dependence on initial conditions”.

The code

Here is the code written to create this animation. It contains many elements that are part of a basic template I use for all my animations; these elements are not essential to this specific experiment but I nonetheless find it preferable to leave them in place. The full code can be read and downloaded on GitHub. It is released freely under a Gnu Gpl 3.0 Licence.

let looping = true;
let socket, cnvs, ctx, canvasDOM;
let fileName = "./frames/logistic-map";
let maxFrames = 20;
let x, y;
let startX = 0.0001;
let startY = 0.0002;
let r = 3.58;
// 3.569946
let increment = 0;

function setup() {
    socket = io.connect('http://localhost:8080');
    cnvs = createCanvas(windowWidth / 16 * 9, windowWidth / 16 * 9);
    ctx = cnvs.drawingContext;
    canvasDOM = document.getElementById('defaultCanvas0');
    frameRate(30);
    background(0);
    fill(255, 255);
    noStroke();
    if (!looping) {
        noLoop();
    }
}

function draw() {
    x = startX + increment;
    y = startY + increment;
    background(0, 50);
    translate(-320, -320);
    for (var i = 0; i < 1000; i++) {
        ellipse(x * 1100, y * 1100, 2);
        x = logisticMap(x);
        y = logisticMap(y);
    }
    increment += 0.000001;
    if (exporting && frameCount < maxFrames) {
        frameExport();
    }
}

function logisticMap(n) {
    return r * n * (1 - n);
}

function keyPressed() {
    if (keyCode === 32) {
        if (looping) {
            noLoop();
            looping = false;
        } else {
            loop();
            looping = true;
        }
    }
    if (key == 'p' || key == 'P') {
        frameExport(p);
    }
    if (key == 'r' || key == 'R') {
        window.location.reload();
    }
}

Scribbled notes

It will be very interesting to see if I can use the logistic map to model the growth of a population of plants whose designs would be algorithmically defined. An environment could be created where a certain maximum population of plants could survive—and I would use the logistic map to determine how many plants are growing. The definition of the carrying capacity could be done in numerous ways; for example, a terrain could be genereated with Perlin noise and its natural resources could determine the carrying capacity of various regions.

Also, thinking about a population and its growth allows us to think about the genetic information that could determine the physical appearance of an algorithmic plant: the “gene pool” of a given plant can be thought of as a kind of “population”, and the way in which these genes reproduce to a new generation of the plant could perhaps be calculated by something akin to the logistic map. The “population” that represents a single plant could also be part of a “metapopulation” (a group of plants), and we can imagine all sorts of interaction between those different levels of complexity. We’ll see if these ideas can generate interesting shapes and movements.

Context

This blog post is part of my research project Towards an algorithmic cinema, started in April 2018. I invite you to read the first blog post of the project to learn more about it.