AT2 – Neuromodelling (Teacher: Manuel Beiran)

Younesse Kaddar (http://younesse.net/ipynb/neuromodeling/Intro_Tutorial.html)

Problem set #1 - Tutorial

This is the first problem of the AT2, it goes through the very basics of what you will have to learn during this class. We will create a script, with an array, a loop and some basic plotting. Try and get used to writing down answers documented by figures in any of your favorite word processor. This is the most important part of the project reports grading you will have to hand in.

In [1]:
# /!\ I'm using Python 3

import plotly
import numpy as np
import plotly.plotly as py
import plotly.graph_objs as go

import matplotlib.pyplot as plt
import scipy.io as sio
from scipy import stats
%matplotlib inline

plotly.offline.init_notebook_mode(connected=True)
from IPython.core.display import display, HTML, Markdown
# The polling here is to ensure that plotly.js has already been loaded before
# setting display alignment in order to avoid a race condition.
display(HTML(
    '<script>'
        'var waitForPlotly = setInterval( function() {'
            'if( typeof(window.Plotly) !== "undefined" ){'
                'MathJax.Hub.Config({ SVG: { font: "STIX-Web" }, displayAlign: "center" });'
                'MathJax.Hub.Queue(["setRenderer", MathJax.Hub, "SVG"]);'
                'clearInterval(waitForPlotly);'
            '}}, 250 );'
    '</script>'
))

PROBLEM 1 - A simple population growth model (Level 1)

We will try to simulate a population of animals growing. We start with a fixed number of animals $N_0 = 2$, and we will model the growth yearly over a hundred years period. First, create a script.

Now for the growth model, let's name $p_n$ the population at year $n$. It is safe to assume that the population will grow by a factor of itself, that is, the bigger the population, the bigger the growth. So the number of new individuals born any given year should look something like $αp$, if $p$ is the population that year. Let us set $α$ to $0.1$ for now. Noting the current year $n - 1$, this means will try to model the following map:

$$p_n = p_{n-1} + 0.1 p_{n-1}$$

1. Create an array named p, meant to hold on to the population values every year.

In [2]:
N_0 = 2
alpha = 0.1

# to be consistent with what follows, 
# let's set the array size to 100
array_size = 101


p = np.zeros(array_size)

2. Set the first value of the array, p[0] to the initial population value.

In [3]:
p[0] = N_0

3. Given the map above, set the second value of the array by computing p[1].

In [4]:
def compute_value(n, p, alpha=alpha, N_0=N_0):
    if n==0:
        p[0] = N_0
    else:
        p[n] = 1.1*p[n-1]
In [5]:
compute_value(1, p)
print(p[1])
2.2

4. Write down a loop that automates the computation of p[1] that you did before, to compute the remaining p[2], . . . p[100] values

In [6]:
for i in range(2, 101):
    compute_value(i, p)

5. Plot the result and show it.

In [7]:
data = go.Scatter(
    x = list(range(1, len(p)+1)), 
    y = p,
    mode = 'lines', 
    name = '$p_n$'
)

layout= go.Layout(
    title= 'Evolution of the population',
    hovermode= 'closest',
    xaxis= dict(
        title= '$\\text{Year } \, n$',
        ticklen= 5,
        zeroline= False,
        gridwidth= 2,
    ),
    yaxis=dict(
        title= '$\\text{Number of animals } \; p_n$',
        ticklen= 5,
        gridwidth= 2,
    ),
    showlegend= True
) 


plotly.offline.iplot(go.Figure(data=[data], layout=layout))

3. What is happening? Could we have predicted it?

It's diverging (the number of animals goes up to infinity). It was predictable, since the sequence $(p_n)_{n∈ℕ}$ is divergent:

$$p_n ≝ N_0 × 1.1^n \underset{n → +∞}{⟶} +∞$$

Play around by changing the growth parameter $α$: how does it affect the result? Does changing the initial population affect the result sensibly?

In [8]:
def compute_array(alpha=alpha, N_0=N_0, n_max=array_size):
    p = [N_0]
    
    for _ in range(array_size-1):
        p.append(p[-1]*(1+alpha))
    return np.array(p)

# Colorscales
def colorscale_list(cmap, number_colors, return_rgb_only=False):
    cm = plt.get_cmap(cmap)
    colors = [np.array(cm(i/number_colors)) for i in range(1, number_colors+1)]
    rgb_colors_plotly = []
    rgb_colors_only = []
    for i, c in enumerate(colors):
        col = 'rgb{}'.format(tuple(255*c[:-1]))
        rgb_colors_only.append(col)
        rgb_colors_plotly.append([i/number_colors, col])
        rgb_colors_plotly.append([(i+1)/number_colors, col])
    return rgb_colors_only if return_rgb_only else rgb_colors_plotly
In [9]:
display(Markdown("### For different values of `alpha`"))

number_lines = 10
legend_every = 1

value_min, value_max = .01, .1

colors = colorscale_list('Reds', number_lines+3, return_rgb_only=True)


# Plotting the evolution of the population
traces_alpha = []

for i, alpha in enumerate(np.linspace(value_min, value_max, number_lines)):
    traces_alpha.append(
        go.Scatter(
            x = list(range(1, array_size+1)), 
            y = compute_array(alpha=alpha),
            mode = 'lines',
            name = 'alpha = {}'.format(alpha),
            line = dict(
                width = 3,
                color = colors[i+2],
                shape = 'spline',
                dash = 'solid'
            ),
            hoverlabel = dict(
                namelength = -1
            ),
            showlegend = (i % legend_every == 0)
        )
    )

plotly.offline.iplot(go.Figure(data=traces_alpha, layout=layout))

For different values of alpha

The population number diverges very quickly when alpha increases. Nothing abnormal: the sequence $(p_n)_{n∈ℕ}$ is an exponential one, of ratio $1 + α$, so that:

$$p_n = N_0 × (1+α)^n$$

In [13]:
display(Markdown("### For different values of `N_0`, for `alpha = {}`".format(alpha)))

number_lines = 5
legend_every = 1

value_min, value_max = 1, 5

colors = colorscale_list('Greens', number_lines+3, return_rgb_only=True)


# Plotting the evolution of the population
traces_N_0 = []

for i, N_0 in enumerate(np.linspace(value_min, value_max, number_lines)):
    traces_N_0.append(
        go.Scatter(
            x = list(range(1, array_size+1)), 
            y = compute_array(N_0=N_0),
            mode = 'lines',
            name = 'N_0 = {}'.format(N_0),
            line = dict(
                width = 3,
                color = colors[i+2],
                shape = 'spline',
                dash = 'solid'
            ),
            hoverlabel = dict(
                namelength = -1
            ),
            showlegend = (i % legend_every == 0)
        )
    )

plotly.offline.iplot(go.Figure(data=traces_N_0, layout=layout))

For different values of N_0, for alpha = 0.1

$p_n = N_0 × (1+α)^n$ increases at such an enormous rate with respect to $α$ that $N_0$ hardly has any impact, as compared to the exponential growth.

Modulated growth factor

Of course our model is very naive, and it seems that we could refine it a little bit. Let's consider the general problem of resources, our population now lives in an environment where resources are limited. A good way of modeling that is simply to modulate the growth factor. This means that we will replace our $α$ by some function of the population. We choose to write $α = 200 − p$.

1. Plot this function for population rates between 0 and 500 individuals.

In [14]:
alpha_0 = 200

data_modulated = go.Scatter(
    x = np.arange(501), 
    y = 200-np.arange(501),
    mode = 'lines', 
    name = '$α$'
)

layout_modulated = go.Layout(
    title= 'Evolution of $α ≝ 200 - p$',
    hovermode= 'closest',
    xaxis= dict(
        title= 'Population number',
        ticklen= 5,
        zeroline= False,
        gridwidth= 2,
    ),
    yaxis=dict(
        title= '$α$',
        ticklen= 5,
        gridwidth= 2,
    ),
    showlegend= True
) 


plotly.offline.iplot(go.Figure(data=[data_modulated], layout=layout_modulated))

2. Interpret the curve, what does it imply for the growth rate of our population?

As soon as $α < 0$, the population number will be decreasing, since the growth rate will be stricly lower than $1$.

Now we have some intuition of the behavior of the population, but we might want to check it by simulating it again. Every next year the population grows by $δ_n = 0.001 p_{n-1}(200 - p_{n-1})$. This means the population follows the map:

$$p_n = p_{n-1} + \underbrace{0.001}_{≝ \, β} \, p_{n-1} (\underbrace{200}_{≝ \, γ} - p_{n-1})$$

Simulate the system, and plot the variation of the population values over time. What happens?

In [15]:
def compute_new_behavior(beta=0.001, gamma=200, N_0=N_0, n_max=array_size):
    p = [N_0]
    
    for _ in range(array_size-1):
        p.append(p[-1]*(1 + beta*(gamma - p[-1])))
        
    return np.array(p)

data = go.Scatter(
    x = list(range(1, array_size+1)), 
    y = compute_new_behavior(),
    mode = 'lines', 
    name = '$p_n$'
)

plotly.offline.iplot(go.Figure(data=[data], layout=layout))

There is a plateau: the population number converges toward a maximum value, which is more realistic.

Play around with the additional parameter that we have introduced, how does it affect the simulation result?

In [16]:
display(Markdown("## Parameter values leading to a monotonous evolution"))


number_lines = 10
legend_every = 1

value_min, value_max = .01, .1

parameters = {'beta': {'args': {'beta':0.001, 'gamma':200, 'N_0':2}, 
                       'nb_lines': 10, 'val_min':0.001, 'val_max':0.005, 
                       'legend_every': 1, 'color': 'Reds'},
              'gamma': {'args': {'beta':0.001, 'gamma':200, 'N_0':2}, 
                        'nb_lines': 10, 'val_min':200, 'val_max':1000, 
                        'legend_every': 1, 'color': 'Blues'},
              'N_0': {'args': {'beta':0.001, 'gamma':200, 'N_0':2}, 
                      'nb_lines': 10, 'val_min':1, 'val_max':10, 
                      'legend_every': 1, 'color': 'Greens'}
             }

traces = {}

for param in ['beta', 'gamma', 'N_0']:
    display(Markdown("### For different values of {}".format(param)))

    number_lines = parameters[param]['nb_lines']
    legend_every = parameters[param]['legend_every']

    value_min, value_max = parameters[param]['val_min'], parameters[param]['val_max'] 

    colors = colorscale_list(parameters[param]['color'], number_lines+3, return_rgb_only=True)
    
    args = parameters[param]['args']

    # Plotting the evolution of the population
    traces[param] = []

    for i, par in enumerate(np.linspace(value_min, value_max, number_lines)):
        args[param] = par
        
        traces[param].append(
            go.Scatter(
                x = list(range(1, array_size+1)), 
                y = compute_new_behavior(**args),
                mode = 'lines',
                name = '{} = {}'.format(param, par),
                line = dict(
                    width = 3,
                    color = colors[i+2],
                    shape = 'spline',
                    dash = 'solid'
                ),
                hoverlabel = dict(
                    namelength = -1
                ),
                showlegend = (i % legend_every == 0)
            )
        )

    plotly.offline.iplot(go.Figure(data=traces[param], layout=layout))

Parameter values leading to a monotonous evolution

For different values of beta

For different values of gamma

For different values of N_0

In [18]:
display(Markdown("## Parameter values, some of which asymptotically lead to a sinusoidal evolution"))


number_lines = 10
legend_every = 1

value_min, value_max = .01, .1

parameters = {'beta': {'args': {'beta':0.001, 'gamma':200, 'N_0':2}, 
                       'nb_lines': 22, 'val_min':0.001, 'val_max':0.011, 
                       'legend_every': 2, 'color': 'Reds'},
              'gamma': {'args': {'beta':0.001, 'gamma':200, 'N_0':2}, 
                        'nb_lines': 20, 'val_min':200, 'val_max':2000, 
                        'legend_every': 2, 'color': 'Blues'},
              'N_0': {'args': {'beta':0.001, 'gamma':200, 'N_0':2}, 
                      'nb_lines': 20, 'val_min':1, 'val_max':20, 
                      'legend_every': 2, 'color': 'Greens'}
             }

traces = {}

for param in ['beta', 'gamma', 'N_0']:
    display(Markdown("### For different values of {}".format(param)))

    number_lines = parameters[param]['nb_lines']
    legend_every = parameters[param]['legend_every']

    value_min, value_max = parameters[param]['val_min'], parameters[param]['val_max'] 

    colors = colorscale_list(parameters[param]['color'], number_lines+3, return_rgb_only=True)
    
    args = parameters[param]['args']

    # Plotting the evolution of the population
    traces[param] = []

    for i, par in enumerate(np.linspace(value_min, value_max, number_lines)):
        args[param] = par
        
        traces[param].append(
            go.Scatter(
                x = list(range(1, array_size+1)), 
                y = compute_new_behavior(**args),
                mode = 'lines',
                name = '{} = {}'.format(param, par),
                line = dict(
                    width = 3,
                    color = colors[i+2],
                    shape = 'spline',
                    dash = 'solid'
                ),
                hoverlabel = dict(
                    namelength = -1
                ),
                showlegend = (i % legend_every == 0)
            )
        )

    plotly.offline.iplot(go.Figure(data=traces[param], layout=layout))

Parameter values, some of which asymptotically lead to a sinusoidal evolution

For different values of beta

For different values of gamma

For different values of N_0

It appears that:

  • for $β ≤ 0.005, γ ≤ 1000$ and for any $N_0 ∈ ℕ$: the population number functions $n \mapsto p_n$ increase when the parameter ($β, α$ or $N_0$) does so, and each of these functions is increasing and monotonous.

  • for $β > 0.005, γ >> 1000$: the geater the parameter, the bigger the oscillations of the population number functions, to such a point that it ends up being asymptotically sinusoidal (with ever-increasing oscillations, as $β$ and $γ$ increase).