Skip to content

Binder

Bifurcation diagram of the aln model

In this notebook, we will discover how easy it is to draw bifurcation diagrams in neurolib using its powerful BoxSearch class.

Bifurcation diagrams are an important tool to understand a dynamical system, may it be a single neuron model or a whole-brain network. They show how a system behaves when certain parameters of the model are changed: whether the system transitions into an oscillation for example, or whethter the system remains in a fixed point (of sustained constant activity).

We will use this to draw a map of the aln model: Since the aln model consists of two populations of Adex neurons, we will change its inputs to the excitatory and to the inhibitory population independently and do so for two different values of spike-frequency adaptation strength \(b\). We will measure the activity of the system and identify regions of oscillatory activity and discover bistable states, in which the system can be in two different stable states for the same set of parameters.

# change into the root directory of the project
import os
if os.getcwd().split("/")[-1] == "examples":
    os.chdir('..')
import logging
logger = logging.getLogger()
import warnings
warnings.filterwarnings("ignore")

#logger.setLevel(logging.DEBUG)
#logging.disable(logging.WARNING)
#logging.disable(logging.WARN)

%load_ext autoreload
%autoreload 2
import numpy as np
import matplotlib.pyplot as plt

from neurolib.models.aln import ALNModel
from neurolib.utils.parameterSpace import ParameterSpace
from neurolib.optimize.exploration import BoxSearch
import neurolib.utils.functions as func
import neurolib.utils.stimulus as stim
import neurolib.optimize.exploration.explorationUtils as eu
import neurolib.utils.devutils as du
from neurolib.utils.loadData import Dataset
plt.style.use("seaborn-white")
plt.rcParams['image.cmap'] = 'plasma'

Create the model

model = ALNModel()
model.params['dt'] = 0.1 # Integration time step, ms
model.params['duration'] = 20 * 1000 # Simulation time, ms

model.params['save_dt'] = 10.0 # 10 ms sampling steps for saving data, should be multiple of dt
model.params["tauA"] = 600.0 # Adaptation timescale, ms

Measuring bistability

The aln model has a region of bistability, in which two states are stable at the same time: the low-activity down-state, and the high-activity up-state. We can find these states by constructing a stimulus, which uncovers the bistable nature of the system: Initially, we apply a negative push to the system, to make sure that it is in the down-state. We then relax this stimulus slowly and wait for the system to settle. We then apply a sharp push in order to reach the up-state and release the stimulus slowly back again. The difference of the two states after the stimulus has relaxed back to zero is a sign for bistability.

# we place the system in the bistable region
model.params['mue_ext_mean'] = 2.5
model.params['mui_ext_mean'] = 2.5

# construct a stimulus
rect_stimulus = stim.RectifiedInput(amplitude=0.2).to_model(model)
model.params['ext_exc_current'] = rect_stimulus * 5.0    

model.run()
plt.figure(figsize=(5, 3), dpi=150)
plt.plot(model.t, model.output.T, lw = 3, c='k', label='rate')
plt.plot(model.t, (rect_stimulus * 100).squeeze(), lw = 3, c='r', label="stimulus")
plt.text(3000, 7, 'down-state', fontsize=16)
plt.text(15000, 35, 'up-state', fontsize=16)
plt.legend(fontsize=14)
plt.xlim(1, model.t[-1])
plt.xlabel("Time [ms]")
plt.ylabel("Activity [Hz]")
Text(0, 0.5, 'Activity [Hz]')

Define evaluation function

Let's construct a rather lengthy evaluation function which does exactly that, for every parameter configuration that we want to explore. We will also measure other things like the dominant frequency and amplitude of oscillations and the maximum rate of the excitatory population.

def evaluateSimulation(traj):
    # get the model from the trajectory using `search.getModelFromTraj(traj)`
    model = search.getModelFromTraj(traj)
    # initiate the model with random initial contitions
    model.randomICs()
    defaultDuration = model.params['duration']

    # -------- stage wise simulation --------

    # Stage 3: full and final simulation
    # ---------------------------------------    
    model.params['duration'] = defaultDuration

    rect_stimulus = stim.RectifiedInput(amplitude=0.2).to_model(model)
    model.params['ext_exc_current'] = rect_stimulus * 5.0    

    model.run()

    # up down difference    
    state_length = 2000
    last_state = (model.t > defaultDuration - state_length)
    down_window = (defaultDuration/2-state_length<model.t) & (model.t<defaultDuration/2) # time period in ms where we expect the down-state
    up_window = (defaultDuration-state_length<model.t) & (model.t<defaultDuration) # and up state
    up_state_rate = np.mean(model.output[:, up_window], axis=1)
    down_state_rate = np.mean(model.output[:, down_window], axis=1)
    up_down_difference = np.max(up_state_rate - down_state_rate)

    # check rates!
    max_amp_output = np.max(
          np.max(model.output[:, up_window], axis=1) 
        - np.min(model.output[:, up_window], axis=1)
    )
    max_output = np.max(model.output[:, up_window])

    model_frs, model_pwrs = func.getMeanPowerSpectrum(model.output, 
                                                      dt=model.params.dt, 
                                                      maxfr=40, 
                                                      spectrum_windowsize=10)
    max_power = np.max(model_pwrs)     

    model_frs, model_pwrs = func.getMeanPowerSpectrum(model.output[:, up_window], dt=model.params.dt, maxfr=40, spectrum_windowsize=5)
    domfr = model_frs[np.argmax(model_pwrs)]    

    result = {
        "end" : 3,
        "max_output": max_output, 
        "max_amp_output" : max_amp_output,
        "max_power" : max_power,
        #"model_pwrs" : model_pwrs,
        #"output": model.output[:, ::int(model.params['save_dt']/model.params['dt'])],
        "domfr" : domfr,
        "up_down_difference" : up_down_difference
    }

    search.saveToPypet(result, traj)
    return    

Let's now define the parameter space over which we want to serach. We apply a grid search over the mean external input parameters to the excitatory and the inhibitory population mue_ext_mean/mui_ext_mean and do this for two values of spike-frequency adapation strength \(b\), once without and once with adaptation.

Exploration parameters

# low number of parameters for testing:
parameters = ParameterSpace({"mue_ext_mean": np.linspace(0.0, 4, 2), 
                             "mui_ext_mean": np.linspace(0.0, 4, 2),
                              "b": [0.0, 20.0]
                             }, kind="grid")
# real: 
# parameters = ParameterSpace({"mue_ext_mean": np.linspace(0.0, 4, 41), 
#                              "mui_ext_mean": np.linspace(0.0, 4, 41),
#                               "b": [0.0, 20.0]
#                              })
search = BoxSearch(evalFunction = evaluateSimulation, model=model, parameterSpace=parameters, filename='example-1.3-aln-bifurcation-diagram.hdf')
MainProcess pypet.storageservice.HDF5StorageService INFO     I will use the hdf5 file `./data/hdf/example-1.3-aln-bifurcation-diagram.hdf`.
MainProcess pypet.environment.Environment INFO     Environment initialized.
MainProcess root INFO     Number of parameter configurations: 3362
MainProcess root INFO     BoxSearch: Environment initialized.

Run

search.run()
MainProcess pypet.environment.Environment INFO     I am preparing the Trajectory for the experiment and initialise the store.
MainProcess pypet.environment.Environment INFO     Initialising the storage for the trajectory.
MainProcess pypet.storageservice.HDF5StorageService INFO     Initialising storage or updating meta data of Trajectory `results-2021-06-19-01H-23M-48S`.
MainProcess pypet.storageservice.HDF5StorageService INFO     Finished init or meta data update for `results-2021-06-19-01H-23M-48S`.
MainProcess pypet.environment.Environment INFO     
************************************************************
STARTING runs of trajectory
`results-2021-06-19-01H-23M-48S`.
************************************************************

MainProcess pypet.storageservice.HDF5StorageService INFO     Initialising storage or updating meta data of Trajectory `results-2021-06-19-01H-23M-48S`.
MainProcess pypet.storageservice.HDF5StorageService INFO     Finished init or meta data update for `results-2021-06-19-01H-23M-48S`.
MainProcess pypet.environment.Environment INFO     Starting multiprocessing with at most 8 processes running at the same time.
MainProcess pypet INFO     PROGRESS: Finished    0/3362 runs [                    ]  0.0%
MainProcess pypet INFO     PROGRESS: Finished  169/3362 runs [=                   ]  5.0%, remaining: 0:01:27
MainProcess pypet INFO     PROGRESS: Finished  337/3362 runs [==                  ] 10.0%, remaining: 0:01:24
MainProcess pypet INFO     PROGRESS: Finished  505/3362 runs [===                 ] 15.0%, remaining: 0:01:27
MainProcess pypet INFO     PROGRESS: Finished  673/3362 runs [====                ] 20.0%, remaining: 0:01:26
MainProcess pypet INFO     PROGRESS: Finished  841/3362 runs [=====               ] 25.0%, remaining: 0:01:26
MainProcess pypet INFO     PROGRESS: Finished 1009/3362 runs [======              ] 30.0%, remaining: 0:01:24
MainProcess pypet INFO     PROGRESS: Finished 1177/3362 runs [=======             ] 35.0%, remaining: 0:01:19
MainProcess pypet INFO     PROGRESS: Finished 1345/3362 runs [========            ] 40.0%, remaining: 0:01:15
MainProcess pypet INFO     PROGRESS: Finished 1513/3362 runs [=========           ] 45.0%, remaining: 0:01:10
MainProcess pypet INFO     PROGRESS: Finished 1681/3362 runs [==========          ] 50.0%, remaining: 0:01:05
MainProcess pypet INFO     PROGRESS: Finished 1850/3362 runs [===========         ] 55.0%, remaining: 0:00:59
MainProcess pypet INFO     PROGRESS: Finished 2018/3362 runs [============        ] 60.0%, remaining: 0:00:55
MainProcess pypet INFO     PROGRESS: Finished 2186/3362 runs [=============       ] 65.0%, remaining: 0:00:49
MainProcess pypet INFO     PROGRESS: Finished 2354/3362 runs [==============      ] 70.0%, remaining: 0:00:42
MainProcess pypet INFO     PROGRESS: Finished 2522/3362 runs [===============     ] 75.0%, remaining: 0:00:36
MainProcess pypet INFO     PROGRESS: Finished 2690/3362 runs [================    ] 80.0%, remaining: 0:00:29
MainProcess pypet INFO     PROGRESS: Finished 2858/3362 runs [=================   ] 85.0%, remaining: 0:00:22
MainProcess pypet INFO     PROGRESS: Finished 3026/3362 runs [==================  ] 90.0%, remaining: 0:00:15
MainProcess pypet INFO     PROGRESS: Finished 3194/3362 runs [=================== ] 95.0%, remaining: 0:00:07
MainProcess pypet INFO     PROGRESS: Finished 3362/3362 runs [====================]100.0%
MainProcess pypet.storageservice.HDF5StorageService INFO     Initialising storage or updating meta data of Trajectory `results-2021-06-19-01H-23M-48S`.
MainProcess pypet.storageservice.HDF5StorageService INFO     Finished init or meta data update for `results-2021-06-19-01H-23M-48S`.
MainProcess pypet.environment.Environment INFO     
************************************************************
FINISHED all runs of trajectory
`results-2021-06-19-01H-23M-48S`.
************************************************************

MainProcess pypet.environment.Environment INFO     
************************************************************
STARTING FINAL STORING of trajectory
`results-2021-06-19-01H-23M-48S`
************************************************************

MainProcess pypet.storageservice.HDF5StorageService INFO     Start storing Trajectory `results-2021-06-19-01H-23M-48S`.
MainProcess pypet.storageservice.HDF5StorageService INFO     Storing branch `config`.
MainProcess pypet.storageservice.HDF5StorageService INFO     Storing branch `parameters`.
MainProcess pypet.storageservice.HDF5StorageService INFO     Finished storing Trajectory `results-2021-06-19-01H-23M-48S`.
MainProcess pypet.environment.Environment INFO     
************************************************************
FINISHED FINAL STORING of trajectory
`results-2021-06-19-01H-23M-48S`.
************************************************************

MainProcess pypet.environment.Environment INFO     All runs of trajectory `results-2021-06-19-01H-23M-48S` were completed successfully.

Analysis

search.loadResults(all=False)
MainProcess root INFO     Loading results from ./data/hdf/example-1.3-aln-bifurcation-diagram.hdf
MainProcess root INFO     Analyzing trajectory results-2021-06-19-01H-23M-48S
MainProcess pypet.storageservice.HDF5StorageService INFO     I will use the hdf5 file `./data/hdf/example-1.3-aln-bifurcation-diagram.hdf`.
MainProcess pypet.storageservice.HDF5StorageService INFO     Loading trajectory `results-2021-06-19-01H-23M-48S`.
MainProcess pypet.storageservice.HDF5StorageService INFO     Loading branch `config` in mode `2`.
MainProcess pypet.storageservice.HDF5StorageService INFO     Loading branch `parameters` in mode `2`.
MainProcess pypet.storageservice.HDF5StorageService INFO     Loading branch `results` in mode `1`.
MainProcess root INFO     Creating `dfResults` dataframe ...
MainProcess root INFO     Aggregating results to `dfResults` ...
100%|██████████| 3362/3362 [00:22<00:00, 152.47it/s]
MainProcess root INFO     All results loaded.

The results dataframe

search.dfResults
mue_ext_mean mui_ext_mean b up_down_difference max_power max_output max_amp_output end domfr
0 0.0 0.0 0.0 0.000019 17.665778 0.038741 3.747003e-16 3.0 0.500025
1 0.0 0.0 20.0 0.000009 5.151548 0.037037 2.042323e-05 3.0 0.500025
2 0.0 0.1 0.0 0.000010 15.441657 0.024629 1.110223e-16 3.0 0.500025
3 0.0 0.1 20.0 0.000006 4.140089 0.024062 8.806806e-06 3.0 0.500025
4 0.0 0.2 0.0 0.000006 12.466037 0.012277 7.112366e-17 3.0 0.500025
... ... ... ... ... ... ... ... ... ...
3357 4.0 3.8 20.0 0.000038 14.426393 29.965853 1.263433e-06 3.0 0.000000
3358 4.0 3.9 0.0 0.000674 58.447442 93.451476 2.842171e-14 3.0 0.000000
3359 4.0 3.9 20.0 0.000038 14.482713 29.928097 1.243205e-06 3.0 0.000000
3360 4.0 4.0 0.0 0.000674 58.494615 93.380582 1.421085e-14 3.0 0.000000
3361 4.0 4.0 20.0 0.000038 14.534853 29.891972 1.224562e-06 3.0 0.000000

3362 rows × 9 columns

Plotting 2D bifurcation diagrams

Let's draw the bifurcation diagrams. We will use a white contour for oscillatory areas (measured by max_amp_output) and a green dashed lined for the bistable region (measured by up_down_difference). We can use the function explorationUtils.plotExplorationResults() for this.

plot_key_label = "Max. $r_E$"
eu.plotExplorationResults(search.dfResults, 
                          par1=['mue_ext_mean', '$\mu_e$'], 
                          par2=['mui_ext_mean', '$\mu_i$'], 
                          by=['b'], 
                          plot_key='max_output',
                          plot_clim=[0.0, 80.0],
                          nan_to_zero=False,
                          plot_key_label=plot_key_label, 
                          one_figure=False,
                          contour=["max_amp_output", "up_down_difference"],
                          contour_color=[['white'], ['springgreen']],
                          contour_levels=[[10], [10]],
                          contour_alpha=[1.0, 1.0],
                          contour_kwargs={0 : {"linewidths" : (5,)}, 1 : {"linestyles" : "--", "linewidths" : (5,)}},
                          #alpha_mask="relative_amplitude_BOLD",
                          mask_threshold=0.1,
                          mask_alpha=0.2)