{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "\n# PyGeNN implementation of local cortical microcircuit model\nThis example model is a reimplementation of the microcircuit model developed by Tobias C. Potjans and Markus Diesmann [Potjans2014]_.\nIt is a full-scale spiking network model of the local cortical microcircuit.\nThe simulated spontaneous activity is asynchronous irregular and cell-type specific firing rates are in agreement\nwith in vivo recordings in awake animals, including the low rate of layer 2/3 excitatory cells.\nThis example can be used as follows:\n\n.. argparse::\n :filename: ../userproject/potjans_microcircuit.py\n :func: get_parser\n :prog: potjans_microcircuit\n" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import numpy as np \n\nfrom argparse import ArgumentParser\nfrom pygenn import (GeNNModel, VarLocation, init_postsynaptic,\n init_sparse_connectivity, init_weight_update, init_var)\nfrom scipy.stats import norm\nfrom time import perf_counter\n\n# ----------------------------------------------------------------------------\n# Parameters\n# ----------------------------------------------------------------------------\n# Layer names\nLAYER_NAMES = [\"23\", \"4\", \"5\", \"6\"]\n\n# Population names\nPOPULATION_NAMES = [\"E\", \"I\"]\n\n# Simulation timestep [ms]\nDT_MS = 0.1\n\n# How many threads to use per spike for procedural connectivity?\nNUM_THREADS_PER_SPIKE = 8\n\n# Background rate per synapse\nBACKGROUND_RATE = 8.0 # spikes/s\n\n# Relative inhibitory synaptic weight\nG = -4.0\n\n# Mean synaptic weight for all excitatory projections except L4e->L2/3e\nMEAN_W = 87.8e-3 # nA\nEXTERNAL_W = 87.8e-3 # nA\n\n# Mean synaptic weight for L4e->L2/3e connections\n# See p. 801 of the paper, second paragraph under 'Model Parameterization',\n# and the caption to Supplementary Fig. 7\nLAYER_23_4_W = 2.0 * MEAN_W # nA\n\n# Standard deviation of weight distribution relative to mean for\n# all projections except L4e->L2/3e\nREL_W = 0.1\n\n# Standard deviation of weight distribution relative to mean for L4e->L2/3e\n# This value is not mentioned in the paper, but is chosen to match the\n# original code by Tobias Potjans\nLAYER_23_4_RELW = 0.05\n\n# Numbers of neurons in full-scale model\nNUM_NEURONS = {\n \"23\": {\"E\":20683, \"I\": 5834},\n \"4\": {\"E\":21915, \"I\": 5479},\n \"5\": {\"E\":4850, \"I\": 1065},\n \"6\": {\"E\":14395, \"I\": 2948}}\n\n# Probabilities for >=1 connection between neurons in the given populations.\n# The first index is for the target population; the second for the source population\nCONNECTION_PROBABILTIES = {\n \"23E\": {\"23E\": 0.1009, \"23I\": 0.1689, \"4E\": 0.0437, \"4I\": 0.0818, \"5E\": 0.0323, \"5I\": 0.0, \"6E\": 0.0076, \"6I\": 0.0},\n \"23I\": {\"23E\": 0.1346, \"23I\": 0.1371, \"4E\": 0.0316, \"4I\": 0.0515, \"5E\": 0.0755, \"5I\": 0.0, \"6E\": 0.0042, \"6I\": 0.0},\n \"4E\": {\"23E\": 0.0077, \"23I\": 0.0059, \"4E\": 0.0497, \"4I\": 0.135, \"5E\": 0.0067, \"5I\": 0.0003, \"6E\": 0.0453, \"6I\": 0.0},\n \"4I\": {\"23E\": 0.0691, \"23I\": 0.0029, \"4E\": 0.0794, \"4I\": 0.1597, \"5E\": 0.0033, \"5I\": 0.0, \"6E\": 0.1057, \"6I\": 0.0},\n \"5E\": {\"23E\": 0.1004, \"23I\": 0.0622, \"4E\": 0.0505, \"4I\": 0.0057, \"5E\": 0.0831, \"5I\": 0.3726, \"6E\": 0.0204, \"6I\": 0.0},\n \"5I\": {\"23E\": 0.0548, \"23I\": 0.0269, \"4E\": 0.0257, \"4I\": 0.0022, \"5E\": 0.06, \"5I\": 0.3158, \"6E\": 0.0086, \"6I\": 0.0},\n \"6E\": {\"23E\": 0.0156, \"23I\": 0.0066, \"4E\": 0.0211, \"4I\": 0.0166, \"5E\": 0.0572, \"5I\": 0.0197, \"6E\": 0.0396, \"6I\": 0.2252},\n \"6I\": {\"23E\": 0.0364, \"23I\": 0.001, \"4E\": 0.0034, \"4I\": 0.0005, \"5E\": 0.0277, \"5I\": 0.008, \"6E\": 0.0658, \"6I\": 0.1443}}\n \n\n# In-degrees for external inputs\nNUM_EXTERNAL_INPUTS = {\n \"23\": {\"E\": 1600, \"I\": 1500},\n \"4\": {\"E\": 2100, \"I\": 1900},\n \"5\": {\"E\": 2000, \"I\": 1900},\n \"6\": {\"E\": 2900, \"I\": 2100}}\n\n# Mean rates in the full-scale model, necessary for scaling\n# Precise values differ somewhat between network realizations\nMEAN_FIRING_RATES = {\n \"23\": {\"E\": 0.971, \"I\": 2.868},\n \"4\": {\"E\": 4.746, \"I\": 5.396},\n \"5\": {\"E\": 8.142, \"I\": 9.078},\n \"6\": {\"E\": 0.991, \"I\": 7.523}}\n\n# Means and standard deviations of delays from given source populations (ms)\nMEAN_DELAY = {\"E\": 1.5, \"I\": 0.75}\n\nDELAY_SD = {\"E\": 0.75, \"I\": 0.375}\n\n# ----------------------------------------------------------------------------\n# Helper functions\n# ----------------------------------------------------------------------------\ndef get_scaled_num_neurons(layer, pop, neuron_scale):\n return int(round(neuron_scale * NUM_NEURONS[layer][pop]))\n\ndef get_full_num_inputs(src_layer, src_pop, trg_layer, trg_pop):\n num_src = NUM_NEURONS[src_layer][src_pop]\n num_trg = NUM_NEURONS[trg_layer][trg_pop]\n connection_prob = CONNECTION_PROBABILTIES[trg_layer + trg_pop][src_layer + src_pop]\n\n return int(round(np.log(1.0 - connection_prob) / np.log(float(num_trg * num_src - 1) / float(num_trg * num_src))) / num_trg)\n\ndef get_mean_weight(src_layer, src_pop, trg_layer, trg_pop):\n # Determine mean weight\n if src_pop == \"E\":\n if src_layer == \"4\" and trg_layer == \"23\" and trg_pop == \"E\":\n return LAYER_23_4_W\n else:\n return MEAN_W\n else:\n return G * MEAN_W\n\ndef get_scaled_num_connections(src_layer, src_pop, trg_layer, trg_pop,\n neuron_scale, connectivity_scale):\n # Scale full number of inputs by scaling factor\n num_inputs = get_full_num_inputs(src_layer, src_pop, trg_layer, trg_pop) * connectivity_scale\n assert num_inputs >= 0.0\n\n # Multiply this by number of postsynaptic neurons\n num_neurons = get_scaled_num_neurons(trg_layer, trg_pop, neuron_scale)\n return int(round(num_inputs * float(num_neurons)))\n\ndef get_full_mean_input_current(layer, pop):\n # Loop through source populations\n mean_input_current = 0.0\n for src_layer in LAYER_NAMES:\n for src_pop in POPULATION_NAMES:\n mean_input_current += (get_mean_weight(src_layer, src_pop, layer, pop) *\n get_full_num_inputs(src_layer, src_pop, layer, pop) *\n MEAN_FIRING_RATES[src_layer][src_pop])\n\n # Add mean external input current\n mean_input_current += EXTERNAL_W * NUM_EXTERNAL_INPUTS[layer][pop] * BACKGROUND_RATE\n assert mean_input_current >= 0.0\n return mean_input_current\n\ndef get_parser():\n parser = ArgumentParser()\n parser.add_argument(\"--duration\", type=float, default=1000.0, help=\"Duration to simulate (ms)\")\n parser.add_argument(\"--neuron-scale\", type=float, default=1.0, help=\"Scaling factor to apply to number of neurons\")\n parser.add_argument(\"--connectivity-scale\", type=float, default=1.0, help=\"Scaling factor to apply to number of neurons\")\n parser.add_argument(\"--kernel-profiling\", action=\"store_true\", help=\"Output kernel profiling data\")\n parser.add_argument(\"--procedural-connectivity\", action=\"store_true\", help=\"Use procedural connectivity\")\n parser.add_argument(\"--save-data\", action=\"store_true\", help=\"Save spike data (rather than plotting it)\")\n return parser\n\n# ----------------------------------------------------------------------------\n# Entry point\n# ----------------------------------------------------------------------------\nif __name__ == \"__main__\":\n args = get_parser().parse_args()\n\n # ----------------------------------------------------------------------------\n # Network creation\n # ----------------------------------------------------------------------------\n model = GeNNModel(\"float\", \"potjans_microcircuit\")\n model.dt = DT_MS\n model.fuse_postsynaptic_models = True\n model.default_narrow_sparse_ind_enabled = True\n model.timing_enabled = args.kernel_profiling\n model.default_var_location = VarLocation.DEVICE\n model.default_sparse_connectivity_location = VarLocation.DEVICE\n\n lif_init = {\"V\": init_var(\"Normal\", {\"mean\": -58.0, \"sd\": 5.0}), \"RefracTime\": 0.0}\n poisson_init = {\"current\": 0.0}\n\n exp_curr_init = init_postsynaptic(\"ExpCurr\", {\"tau\": 0.5})\n\n quantile = 0.9999\n normal_quantile_cdf = norm.ppf(quantile)\n max_delay = {pop: MEAN_DELAY[pop] + (DELAY_SD[pop] * normal_quantile_cdf)\n for pop in POPULATION_NAMES}\n print(\"Max excitatory delay:%fms , max inhibitory delay:%fms\" % (max_delay[\"E\"], max_delay[\"I\"]))\n\n # Calculate maximum dendritic delay slots\n # **NOTE** it seems inefficient using maximum for all but this allows more aggressive merging of postsynaptic models\n max_dendritic_delay_slots = int(round(max(max_delay.values()) / DT_MS))\n print(\"Max dendritic delay slots:%d\" % max_dendritic_delay_slots)\n\n print(\"Creating neuron populations:\")\n total_neurons = 0\n neuron_populations = {}\n for layer in LAYER_NAMES:\n for pop in POPULATION_NAMES:\n pop_name = layer + pop\n\n # Calculate external input rate, weight and current\n ext_input_rate = NUM_EXTERNAL_INPUTS[layer][pop] * args.connectivity_scale * BACKGROUND_RATE\n ext_weight = EXTERNAL_W / np.sqrt(args.connectivity_scale)\n ext_input_current = 0.001 * 0.5 * (1.0 - np.sqrt(args.connectivity_scale)) * get_full_mean_input_current(layer, pop)\n assert ext_input_current >= 0.0\n\n lif_params = {\"C\": 0.25, \"TauM\": 10.0, \"Vrest\": -65.0, \"Vreset\": -65.0, \"Vthresh\" : -50.0,\n \"Ioffset\": ext_input_current, \"TauRefrac\": 2.0}\n poisson_params = {\"weight\": ext_weight, \"tauSyn\": 0.5, \"rate\": ext_input_rate}\n\n pop_size = get_scaled_num_neurons(layer, pop, args.neuron_scale)\n neuron_pop = model.add_neuron_population(pop_name, pop_size, \"LIF\", lif_params, lif_init)\n model.add_current_source(pop_name + \"_poisson\", \"PoissonExp\", neuron_pop, poisson_params, poisson_init)\n\n # Enable spike recording\n neuron_pop.spike_recording_enabled = True\n\n print(\"\\tPopulation %s: num neurons:%u, external input rate:%f, external weight:%f, external DC offset:%f\" % (pop_name, pop_size, ext_input_rate, ext_weight, ext_input_current))\n\n # Add number of neurons to total\n total_neurons += pop_size\n\n # Add neuron population to dictionary\n neuron_populations[pop_name] = neuron_pop\n\n # Loop through target populations and layers\n print(\"Creating synapse populations:\")\n total_synapses = 0\n num_sub_rows = NUM_THREADS_PER_SPIKE if args.procedural_connectivity else 1\n for trg_layer in LAYER_NAMES:\n for trg_pop in POPULATION_NAMES:\n trg_name = trg_layer + trg_pop\n\n # Loop through source populations and layers\n for src_layer in LAYER_NAMES:\n for src_pop in POPULATION_NAMES:\n src_name = src_layer + src_pop\n\n # Determine mean weight\n mean_weight = get_mean_weight(src_layer, src_pop, trg_layer, trg_pop) / np.sqrt(args.connectivity_scale)\n\n # Determine weight standard deviation\n if src_pop == \"E\" and src_layer == \"4\" and trg_layer == \"23\" and trg_pop == \"E\":\n weight_sd = mean_weight * LAYER_23_4_RELW\n else:\n weight_sd = abs(mean_weight * REL_W)\n\n # Calculate number of connections\n num_connections = get_scaled_num_connections(src_layer, src_pop,\n trg_layer, trg_pop,\n args.neuron_scale, args.connectivity_scale)\n\n if num_connections > 0:\n print(\"\\tConnection between '%s' and '%s': numConnections=%u, meanWeight=%f, weightSD=%f, meanDelay=%f, delaySD=%f\"\n % (src_name, trg_name, num_connections, mean_weight, weight_sd, MEAN_DELAY[src_pop], DELAY_SD[src_pop]))\n\n # Build parameters for fixed number total connector\n connect_params = {\"num\": num_connections}\n\n # Build distribution for delay parameters\n d_dist = {\"mean\": MEAN_DELAY[src_pop], \"sd\": DELAY_SD[src_pop], \"min\": 0.0, \"max\": max_delay[src_pop]}\n\n total_synapses += num_connections\n\n # Build unique synapse name\n synapse_name = src_name + \"_\" + trg_name\n\n matrix_type = \"PROCEDURAL\" if args.procedural_connectivity else \"SPARSE\"\n\n # Excitatory\n if src_pop == \"E\":\n # Build distribution for weight parameters\n # **HACK** np.float32 doesn't seem to automatically cast\n w_dist = {\"mean\": mean_weight, \"sd\": weight_sd, \"min\": 0.0, \"max\": float(np.finfo(np.float32).max)}\n\n # Create weight parameters\n static_synapse_init = init_weight_update(\"StaticPulseDendriticDelay\", {},\n {\"g\": init_var(\"NormalClipped\", w_dist),\n \"d\": init_var(\"NormalClippedDelay\", d_dist)})\n # Add synapse population\n syn_pop = model.add_synapse_population(synapse_name, matrix_type,\n neuron_populations[src_name], neuron_populations[trg_name],\n static_synapse_init, exp_curr_init,\n init_sparse_connectivity(\"FixedNumberTotalWithReplacement\", connect_params))\n\n # Set max dendritic delay and span type\n syn_pop.max_dendritic_delay_timesteps = max_dendritic_delay_slots\n\n if args.procedural_connectivity:\n syn_pop.num_threads_per_spike = NUM_THREADS_PER_SPIKE\n # Inhibitory\n else:\n # Build distribution for weight parameters\n # **HACK** np.float32 doesn't seem to automatically cast\n w_dist = {\"mean\": mean_weight, \"sd\": weight_sd, \"min\": float(-np.finfo(np.float32).max), \"max\": 0.0}\n\n # Create weight parameters\n static_synapse_init = init_weight_update(\"StaticPulseDendriticDelay\", {},\n {\"g\": init_var(\"NormalClipped\", w_dist),\n \"d\": init_var(\"NormalClippedDelay\", d_dist)})\n # Add synapse population\n syn_pop = model.add_synapse_population(synapse_name, matrix_type,\n neuron_populations[src_name], neuron_populations[trg_name],\n static_synapse_init, exp_curr_init,\n init_sparse_connectivity(\"FixedNumberTotalWithReplacement\", connect_params))\n\n # Set max dendritic delay and span type\n syn_pop.max_dendritic_delay_timesteps = max_dendritic_delay_slots\n\n if args.procedural_connectivity:\n syn_pop.num_threads_per_spike = NUM_THREADS_PER_SPIKE\n print(\"Total neurons=%u, total synapses=%u\" % (total_neurons, total_synapses))\n\n print(\"Building Model\")\n model.build()\n\n print(\"Loading Model\")\n duration_timesteps = int(round(args.duration / DT_MS))\n ten_percent_timestep = duration_timesteps // 10\n model.load(num_recording_timesteps=duration_timesteps)\n\n print(\"Simulating\")\n\n # Loop through timesteps\n sim_start_time = perf_counter()\n while model.t < args.duration:\n # Advance simulation\n model.step_time()\n\n # Indicate every 10%\n if (model.timestep % ten_percent_timestep) == 0:\n print(\"%u%%\" % (model.timestep / 100))\n\n sim_end_time = perf_counter()\n\n\n # Download recording data\n model.pull_recording_buffers_from_device()\n\n print(\"Timing:\")\n print(\"\\tSimulation:%f\" % ((sim_end_time - sim_start_time) * 1000.0))\n\n if args.kernel_profiling:\n print(\"\\tInit:%f\" % (1000.0 * model.init_time))\n print(\"\\tSparse init:%f\" % (1000.0 * model.init_sparse_time))\n print(\"\\tNeuron simulation:%f\" % (1000.0 * model.neuron_update_time))\n print(\"\\tSynapse simulation:%f\" % (1000.0 * model.presynaptic_update_time))\n\n if args.save_data:\n # Loop through populations and write spike data to CSV\n for n, pop in neuron_populations.items():\n np.savetxt(f\"{n}_spikes.csv\", np.column_stack(pop.spike_recording_data[0]),\n delimiter=\",\", fmt=(\"%f\", \"%d\"), header=\"Times [ms], Neuron ID\")\n\n else:\n import matplotlib.pyplot as plt\n\n # Create plot\n figure, axes = plt.subplots(1, 2)\n\n # **YUCK** re-order neuron populations for plotting\n ordered_neuron_populations = list(reversed(list(neuron_populations.values())))\n\n start_id = 0\n bar_y = 0.0\n for pop in ordered_neuron_populations:\n # Get recording data\n spike_times, spike_ids = pop.spike_recording_data[0]\n\n # Plot spikes\n actor = axes[0].scatter(spike_times, spike_ids + start_id, s=2, edgecolors=\"none\")\n\n # Plot bar showing rate in matching colour\n axes[1].barh(bar_y, len(spike_times) / (float(pop.num_neurons) * args.duration / 1000.0),\n align=\"center\", color=actor.get_facecolor(), ecolor=\"black\")\n\n # Update offset\n start_id += pop.num_neurons\n\n # Update bar pos\n bar_y += 1.0\n\n axes[0].set_xlabel(\"Time [ms]\")\n axes[0].set_ylabel(\"Neuron number\")\n\n axes[1].set_xlabel(\"Mean firingrate [Hz]\")\n axes[1].set_yticks(np.arange(0.0, len(neuron_populations), 1.0))\n axes[1].set_yticklabels([n.name for n in ordered_neuron_populations])\n\n # Show plot\n plt.show()" ] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.9.5" } }, "nbformat": 4, "nbformat_minor": 0 }