Repository: jeshraghian/snntorch (language: python)

Issue:
updated if else conditions for spikegen.py
Updated the if else conditions in spikegen.py to be more Pythonic.
instead of "if variable is True:", "if variable:" will evaluate to the same boolean logic.
instead of "if variable is False:", "if not variable:" will evaluate to the same boolean logic.


Discussion / hints:

Fix patch:
diff --git a/.vscode/settings.json b/.vscode/settings.json
index 114761c7..7a73a41b 100644
--- a/.vscode/settings.json
+++ b/.vscode/settings.json
@@ -1,3 +1,2 @@
 {
-    "python.pythonPath": "/home/xinxin/anaconda/envs/dlcourse/bin/python"
 }
\ No newline at end of file
diff --git a/examples/.ipynb_checkpoints/tutorial_1-checkpoint.ipynb b/examples/.ipynb_checkpoints/tutorial_1-checkpoint.ipynb
deleted file mode 100644
index 1ec60795..00000000
--- a/examples/.ipynb_checkpoints/tutorial_1-checkpoint.ipynb
+++ /dev/null
@@ -1,441 +0,0 @@
-{
- "cells": [
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "# Spike Train Generation\n",
-    "## 1. MNIST Conversion to Spike Train\n",
-    "### 1.1. Import packages and setup environment"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 1,
-   "metadata": {
-    "pycharm": {
-     "name": "#%%\n"
-    }
-   },
-   "outputs": [],
-   "source": [
-    "# Note to self: from snntorch import utils is not needed as that is in __init__\n",
-    "\n",
-    "import torch\n",
-    "import os\n",
-    "\n",
-    "# Note to self: when running from a notebook, I need to change directory to the following path\n",
-    "os.chdir(\"C:\\\\Users\\\\Jason\\\\Dropbox\\\\repos\\\\snntorch\")\n",
-    "\n",
-    "import snntorch as snn\n",
-    "from snntorch.spikevision import datamod, spikegen\n",
-    "\n",
-    "device = torch.device(\"cuda\") if torch.cuda.is_available() else torch.device(\"cpu\")"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "Initialize the configuration file which contains information about the dataset.\n",
-    "\n",
-    "The first input refers to the first two dataset dimensions. `channels` is the third dimension (i.e., depth).\n",
-    "\n",
-    "`split` can be used to assign data from the training to the the validation set.\n",
-    "*E.g., for a split of 0.2, the validation set will be made up of 20% of the train set.*\n",
-    "\n",
-    "`subset` is used to partition the training and test sets down by the given factor.\n",
-    "*E.g., for a subset of 100, a training set of 60,000 will be reduced to 600.*\n",
-    "\n",
-    "`num_classes` is the number of output classes (10 for MNIST).\n",
-    "\n",
-    " `T` is the number of time steps to be simulated.\n",
-    "\n",
-    " Finally, `data_path` is the directory to which your training set will be downloaded to, if using torchvision."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 2,
-   "metadata": {
-    "pycharm": {
-     "name": "#%%\n"
-    }
-   },
-   "outputs": [],
-   "source": [
-    "config = snn.utils.Configuration([28,28], channels=1, batch_size=100, split=0.1, subset=100, num_classes=10, T=1000,\n",
-    "                           data_path='/data/mnist')"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {},
-   "source": [
-    "### 1.2 Download Dataset\n",
-    "\n",
-    "Note that we are creating a dataset `mnist_val` which is equivalent to `mnist_train`.\n",
-    "This allows us to retain the `data` and `target` attributes which would otherwise be lost had we used `random_split`."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 3,
-   "metadata": {
-    "pycharm": {
-     "name": "#%%\n"
-    }
-   },
-   "outputs": [],
-   "source": [
-    "from torchvision import datasets, transforms\n",
-    "\n",
-    "# Define a transform\n",
-    "transform = transforms.Compose([\n",
-    "            transforms.Resize(config.input_size),\n",
-    "            transforms.Grayscale(),\n",
-    "            transforms.ToTensor(),\n",
-    "            transforms.Normalize((0,), (1,))])\n",
-    "\n",
-    "mnist_train = datasets.MNIST(config.data_path, train=True, download=True, transform=transform)\n",
-    "mnist_val = datasets.MNIST(config.data_path, train=True, download=True, transform=transform)\n",
-    "mnist_test = datasets.MNIST(config.data_path, train=False, download=True, transform=transform)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {
-    "pycharm": {
-     "name": "#%% md\n"
-    }
-   },
-   "source": [
-    "Create train/validation split using the value in `config.split`.\n",
-    "\n",
-    "`spikevision` is a subpackage within `snntorch` containing useful modules for modifying the data set.\n",
-    "Many such functions are called from the package `datamod`."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 4,
-   "metadata": {
-    "pycharm": {
-     "name": "#%%\n"
-    }
-   },
-   "outputs": [],
-   "source": [
-    "from snntorch.spikevision import datamod\n",
-    "\n",
-    "mnist_train, mnist_val = datamod.valid_split(mnist_train, mnist_val, config)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {
-    "pycharm": {
-     "name": "#%% md\n"
-    }
-   },
-   "source": [
-    "Reduce training, validation and test sets to smaller subsets as defined in `config.subset`."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 5,
-   "metadata": {
-    "pycharm": {
-     "name": "#%%\n"
-    }
-   },
-   "outputs": [],
-   "source": [
-    "mnist_train = datamod.data_subset(mnist_train, config)\n",
-    "mnist_val = datamod.data_subset(mnist_val, config)\n",
-    "mnist_test = datamod.data_subset(mnist_test, config)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {
-    "pycharm": {
-     "name": "#%% md\n"
-    }
-   },
-   "source": [
-    "As a quick sanity check, let's take a look at the length of each of our datasets:"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 6,
-   "metadata": {
-    "pycharm": {
-     "name": "#%%\n"
-    }
-   },
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "The size of mnist_train is 540\n",
-      "The size of mnist_val is 60\n",
-      "The size of mnist_test is 100\n"
-     ]
-    }
-   ],
-   "source": [
-    "print(f\"The size of mnist_train is {len(mnist_train)}\")\n",
-    "print(f\"The size of mnist_val is {len(mnist_val)}\")\n",
-    "print(f\"The size of mnist_test is {len(mnist_test)}\")"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {
-    "pycharm": {
-     "name": "#%% md\n"
-    }
-   },
-   "source": [
-    "### 1.3 Create Dataloaders"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 7,
-   "metadata": {
-    "pycharm": {
-     "name": "#%%\n"
-    }
-   },
-   "outputs": [],
-   "source": [
-    "from torch.utils.data import DataLoader\n",
-    "\n",
-    "# Note to self: after each dataloader object, append \".to(device)\"\n",
-    "train_loader = DataLoader(mnist_train, batch_size=config.batch_size, shuffle=True)\n",
-    "val_loader = DataLoader(mnist_val, batch_size=config.batch_size, shuffle=True)\n",
-    "test_loader = DataLoader(mnist_test, batch_size=config.batch_size, shuffle=True)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {
-    "pycharm": {
-     "name": "#%% md\n"
-    }
-   },
-   "source": [
-    "### 1.4 Spike Train Generation\n",
-    "We now need to convert static images from MNIST into a spike train. Ideally, this would be done in one single batch.\n",
-    "But this will often exceed memory capacity as the size of a dataset would be multiplied by the number of time steps.\n",
-    "*E.g., a 45 MB MNIST dataset simulated for T=1,000 time steps would be 45GB.*\n",
-    "\n",
-    "To avoid this issue, minibatches of spikes must be re-calculated every time they are called."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 8,
-   "metadata": {
-    "pycharm": {
-     "name": "#%%\n"
-    }
-   },
-   "outputs": [],
-   "source": [
-    "data = iter(train_loader)\n",
-    "data_it, targets_it = next(data)\n",
-    "\n",
-    "# Spiking Data --------- should calculate this on cuda, test on colab.\n",
-    "spike_data, spike_targets = spikegen.spike_conversion(data_it, targets_it, config)"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {
-    "pycharm": {
-     "name": "#%% md\n"
-    }
-   },
-   "source": [
-    "#### 1.4.1 Visualising Data\n",
-    "The `spikeplot` module contains useful functions for visualising spiking data. Let's index into one sample from\n",
-    "`spike_data`, where it is of dimensions [T x B x C x W x H], and must be reduced to [T x W x H]."
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 13,
-   "metadata": {
-    "pycharm": {
-     "name": "#%%\n"
-    }
-   },
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "tensor([0., 0., 1., 0., 0., 0., 0., 0., 0., 0.])\n"
-     ]
-    },
-    {
-     "ename": "RuntimeError",
-     "evalue": "Requested MovieWriter (ffmpeg) not available",
-     "output_type": "error",
-     "traceback": [
-      "\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
-      "\u001b[1;31mRuntimeError\u001b[0m                              Traceback (most recent call last)",
-      "\u001b[1;32m<ipython-input-13-66c77d57edb9>\u001b[0m in \u001b[0;36m<module>\u001b[1;34m()\u001b[0m\n\u001b[0;32m     13\u001b[0m \u001b[1;31m#plt.close(fig)\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     14\u001b[0m \u001b[0mprint\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mspike_targets\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;36m0\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m---> 15\u001b[1;33m \u001b[0mHTML\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mdata_sample\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mto_html5_video\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m     16\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m     17\u001b[0m \u001b[1;31m#however, HTML required installation of ffmpeg package. Not ideal.\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
-      "\u001b[1;32m~\\Anaconda3\\lib\\site-packages\\matplotlib\\animation.py\u001b[0m in \u001b[0;36mto_html5_video\u001b[1;34m(self, embed_limit)\u001b[0m\n\u001b[0;32m   1314\u001b[0m                 \u001b[1;31m# We create a writer manually so that we can get the\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m   1315\u001b[0m                 \u001b[1;31m# appropriate size for the tag\u001b[0m\u001b[1;33m\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m-> 1316\u001b[1;33m                 \u001b[0mWriter\u001b[0m \u001b[1;33m=\u001b[0m \u001b[0mwriters\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mmpl\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mrcParams\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'animation.writer'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m   1317\u001b[0m                 writer = Writer(codec='h264',\n\u001b[0;32m   1318\u001b[0m                                 \u001b[0mbitrate\u001b[0m\u001b[1;33m=\u001b[0m\u001b[0mmpl\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mrcParams\u001b[0m\u001b[1;33m[\u001b[0m\u001b[1;34m'animation.bitrate'\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m,\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n",
-      "\u001b[1;32m~\\Anaconda3\\lib\\site-packages\\matplotlib\\animation.py\u001b[0m in \u001b[0;36m__getitem__\u001b[1;34m(self, name)\u001b[0m\n\u001b[0;32m    164\u001b[0m         \u001b[1;32mif\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0mis_available\u001b[0m\u001b[1;33m(\u001b[0m\u001b[0mname\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m:\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    165\u001b[0m             \u001b[1;32mreturn\u001b[0m \u001b[0mself\u001b[0m\u001b[1;33m.\u001b[0m\u001b[0m_registered\u001b[0m\u001b[1;33m[\u001b[0m\u001b[0mname\u001b[0m\u001b[1;33m]\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[1;32m--> 166\u001b[1;33m         \u001b[1;32mraise\u001b[0m \u001b[0mRuntimeError\u001b[0m\u001b[1;33m(\u001b[0m\u001b[1;34mf\"Requested MovieWriter ({name}) not available\"\u001b[0m\u001b[1;33m)\u001b[0m\u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0m\u001b[0;32m    167\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n\u001b[0;32m    168\u001b[0m \u001b[1;33m\u001b[0m\u001b[0m\n",
-      "\u001b[1;31mRuntimeError\u001b[0m: Requested MovieWriter (ffmpeg) not available"
-     ]
-    }
-   ],
-   "source": [
-    "from snntorch import spikeplot\n",
-    "# Note to Self: I only needed matplotlib.use(\"TkAgg\") for use in PyCharm.\n",
-    "import matplotlib; matplotlib.use(\"TkAgg\")\n",
-    "import matplotlib.pyplot as plt\n",
-    "\n",
-    "spike_data_visualizer = spike_data[:,0,0]\n",
-    "data_sample = spikeplot.spike_animator(spike_data_visualizer, x=28, y=28, T=100)\n",
-    "#plt.show()\n",
-    "\n",
-    "\n",
-    "# If the above code doesn't work in Colab, then modify spikeplot to the following. Need to test:\n",
-    "#from IPython.display import HTML\n",
-    "#plt.close(fig)\n",
-    "#print(spike_targets[0][0])\n",
-    "#HTML(data_sample.to_html5_video())\n",
-    "\n",
-    "#however, HTML required installation of ffmpeg package. Not ideal.\n",
-    "#### I should come up with a more general solution."
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {
-    "pycharm": {
-     "name": "#%% md\n"
-    }
-   },
-   "source": [
-    "And the associated label can be indexed as follows:"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 10,
-   "metadata": {
-    "pycharm": {
-     "name": "#%%\n"
-    }
-   },
-   "outputs": [
-    {
-     "name": "stdout",
-     "output_type": "stream",
-     "text": [
-      "The target is: 8\n"
-     ]
-    }
-   ],
-   "source": [
-    "import numpy as np\n",
-    "\n",
-    "print(f\"The target is: {np.argmax(spike_targets[0][:][0], axis=0)}\")"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {
-    "pycharm": {
-     "name": "#%% md\n"
-    }
-   },
-   "source": [
-    "As a matter of interest, let's do that again but with 25% of the gain for increased sparsity:"
-   ]
-  },
-  {
-   "cell_type": "code",
-   "execution_count": null,
-   "metadata": {
-    "pycharm": {
-     "name": "#%%\n"
-    }
-   },
-   "outputs": [],
-   "source": [
-    "spike_data, spike_targets = spikegen.spike_conversion(data_it, targets_it, config, gain=0.25)\n",
-    "spike_data_visualizer = spike_data[:,0,0]\n",
-    "data_sample = spikeplot.spike_animator(spike_data_visualizer, x=28, y=28, T=100)\n",
-    "plt.show()"
-   ]
-  },
-  {
-   "cell_type": "markdown",
-   "metadata": {
-    "pycharm": {
-     "name": "#%% md\n"
-    }
-   },
-   "source": [
-    "#### To-do: 1) append \".to(device)\" to dataloaders\n",
-    "#### To-do: 2) include functionality for the following datasets, as we will need to benchmark:\n",
-    "##### a) CIFAR-10 converted to spikes\n",
-    "##### b) NMNIST\n",
-    "##### c) N-CIFAR-10?\n",
-    "##### d) DVS Gesture\n",
-    "##### d) EEG from Enea's Frontiers paper\n",
-    "##### e)\n",
-    "\n",
-    "### 2.0 LIF Neuron & Architecture"
-   ]
-  }
- ],
- "metadata": {
-  "accelerator": "GPU",
-  "colab": {
-   "collapsed_sections": [
-    "9QXsrr6Mp5e_",
-    "1EWDw3bip8Ie",
-    "vFM8UV9CreIX",
-    "xXkTAJ9ws1Y6",
-    "OgkWg605tE1y",
-    "OBt0WDzyujnk",
-    "xC96eesMqYo-",
-    "mszPTrYOluym",
-    "VTHK-wAWV57B"
-   ],
-   "name": "Copy of quantAwareTrain.ipynb",
-   "provenance": []
-  },
-  "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.6.1"
-  }
- },
- "nbformat": 4,
- "nbformat_minor": 1
-}
diff --git a/examples/tutorial_1_spikegen.ipynb b/examples/tutorial_1_spikegen.ipynb
index a9d89bb4..39c482d2 100644
--- a/examples/tutorial_1_spikegen.ipynb
+++ b/examples/tutorial_1_spikegen.ipynb
@@ -54,10 +54,11 @@
   },
   {
    "cell_type": "code",
-   "execution_count": null,
+   "execution_count": 1,
    "outputs": [],
    "source": [
-    "import snntorch as snn"
+    "import snntorch as snn\n",
+    "import torch"
    ],
    "metadata": {
     "collapsed": false,
@@ -84,7 +85,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 90,
+   "execution_count": 2,
    "metadata": {
     "pycharm": {
      "name": "#%%\n"
@@ -113,13 +114,13 @@
    "source": [
     "### 1.2 Download Dataset\n",
     "\n",
-    "MNIST does not have a specified validation set by default. To start with, we just make a copy of the training set in `mnist_val`.\n",
-    "We won't use `mnist_val` or `mnist_test` here - they're only here for demo purposes."
+    "MNIST does not have a specified validation set by default. So we can make a copy of the training set in `mnist_val`.\n",
+    "We won't use `mnist_val` or `mnist_test` here - they're only to demonstrate creating a train-validation split."
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 3,
+   "execution_count": 4,
    "metadata": {
     "pycharm": {
      "name": "#%%\n"
@@ -149,13 +150,13 @@
     }
    },
    "source": [
-    "`utils` contains a few useful utility functions for modifying the dataset.\n",
+    "`utils` contains a few useful functions for modifying the dataset.\n",
     "A train-validation split can be created by calling `valid_split`:"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 4,
+   "execution_count": 5,
    "metadata": {
     "pycharm": {
      "name": "#%%\n"
@@ -176,14 +177,14 @@
     }
    },
    "source": [
-    "For testing and development, we don't need large datasets.\n",
-    "So let's make life simpler, and training times faster, by reducing the size of the MNIST dataset.\n",
-    "We can apply `data_subset` to divide down the dataset by the factor given in the argument `subset`.\n"
+    "Until we actually start doing some training, we won't need large datasets.\n",
+    "So let's make life simpler by reducing the size of the MNIST dataset.\n",
+    "We can apply `data_subset` to reduce the dataset by the factor given in the argument `subset`.\n"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 5,
+   "execution_count": 6,
    "metadata": {
     "pycharm": {
      "name": "#%%\n"
@@ -204,12 +205,12 @@
     }
    },
    "source": [
-    "To verify, let's take a look at the length of each of our datasets:"
+    "To verify, we can take a look at the length of each of our datasets:"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 6,
+   "execution_count": 7,
    "metadata": {
     "pycharm": {
      "name": "#%%\n"
@@ -246,7 +247,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 7,
+   "execution_count": 8,
    "metadata": {
     "pycharm": {
      "name": "#%%\n"
@@ -270,45 +271,33 @@
    },
    "source": [
     "## 2. Spike Encoding\n",
-    "MNIST is not a time-varying dataset. SNNs are made to exploit time-varying data.\n",
+    "MNIST is not a time-varying dataset. Spiking Neural Networks (SNNs) are made to exploit time-varying data.\n",
     "This means that we have one of two options for passing input data into an SNN:\n",
     "\n",
-    "(1) Directly feed the same static input features $x_i^{m}$ at each time step, where $x_i^{m}$ takes on an analog value $x_i^{m} \\in$  [0, 1].\n",
-    " This is akin to converting MNIST into a static video.\n",
+    "1. Directly feed the same static input features $x_i^{m}$ at each time step, where $x_i^{m}$ takes on an analog value $x_i^{m} \\in$  [0, 1].\n",
+    " This is like converting MNIST into a static, unchanging video.\n",
     "\n",
-    "(2) Convert the input into a spike train of sequence length `num_steps`, where $x_i^{m}$ takes on a discrete value $x_i^{m} \\in$ {0, 1}.\n",
-    "In this case, MNIST would become a time-varying sequence of spikes that are somehow related to the original image.\n",
+    "2. Convert the input into a spike train of sequence length `num_steps`, where $x_i^{m}$ takes on a discrete value $x_i^{m} \\in$ {0, 1}.\n",
+    "In this case, MNIST would become a time-varying sequence of spikes that are related to the original image.\n",
     "\n",
-    "Both methods are valid options, but let's consider (2) in more detail as the first is quite straightforward.\n",
+    "Both methods are valid options, but let's consider (2) in more detail as the first is quite trivial.\n",
     "There are currently two options available for spike generation in `snntorch`:\n",
     "\n",
-    "* Rate coding: `spikegen.rate`\n",
-    "* Latency coding: `spikegen.latency`\n",
+    "1. Rate coding: `spikegen.rate`\n",
+    "2. Latency coding: `spikegen.latency`\n",
     "\n",
     "*Rate coding* uses input features to determine spiking frequency. *Latency coding* uses input features to determine spike timing.\n",
     "\n",
     "### 2.1 Spike rate coding of MNIST\n",
     "\n",
-    "Each input feature is used as the probability that an event occurs in a binomial distribution. Formally, $X$~$B(n=1, p=x_i^{m})$ where the\n",
+    "Each input feature is used as the probability an event occurs, sampled from a binomial distribution. Formally, $X$~$B(n=1, p=x_i^{m})$ where the\n",
     "**expected value** $\\mathbb E[X]=x_i^m$ is simply the probability that a spike is generated at any given time step.\n",
-    "For an MNIST image, this probability corresponds to the pixel value. A white pixel corresponds to a 100% probability of spiking, and a black pixel will never generate a spike.\n",
-    "\n",
-    "`spikegen.rate` takes a few arguments that can modify spiking probability:\n",
-    "\n",
-    "* `gain` multiplies the input by the given factor, and\n",
-    "* `offset` applies a level-shift to the input.\n",
-    "\n",
-    "If the result falls outside of [0,1], then it will automatically be clipped such that the feature represents a probability.\n",
-    "\n",
-    "Below, we perform this one minibatch at a time.\n",
-    "\n",
-    "Note: there are options to convert targets to one hot encodings using `convert_targets`, and to extend the encodings along the time-axis using `temporal targets`.\n",
-    "Both are set to `False`, so `targets_it` is passed directly to `spike_targets`."
+    "For an MNIST image, this probability corresponds to the pixel value. A white pixel corresponds to a 100% probability of spiking, and a black pixel will never generate a spike.\n"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 17,
+   "execution_count": 9,
    "metadata": {
     "pycharm": {
      "name": "#%%\n"
@@ -332,6 +321,16 @@
   {
    "cell_type": "markdown",
    "source": [
+    "As you can see, `spikegen.rate` takes a few arguments that can modify spiking probability:\n",
+    "\n",
+    "* `gain` multiplies the input by the given factor, and\n",
+    "* `offset` applies a level-shift to the input.\n",
+    "\n",
+    "If the result falls outside of [0,1], then it will automatically be clipped such that the feature represents a probability.\n",
+    "\n",
+    "Note: there are also options to convert targets to one hot encodings using `convert_targets`, and to extend the encodings along the time-axis using `temporal targets`.\n",
+    "Both are set to `False`, so `targets_it` is passed directly to `spike_targets`.\n",
+    "\n",
     "The structure of the input data is [num_steps x batch_size x input dimensions]:"
    ],
    "metadata": {
@@ -343,7 +342,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 18,
+   "execution_count": 10,
    "outputs": [
     {
      "name": "stdout",
@@ -378,7 +377,7 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 19,
+   "execution_count": 11,
    "metadata": {
     "pycharm": {
      "name": "#%%\n"
@@ -400,12 +399,12 @@
     }
    },
    "source": [
-    "To plot one sample of data, we have to index into the B dimension of `spike_data`, [T x B x 1 x 28 x 28]:"
+    "To plot one sample of data, we have to index into the batch (B) dimension of `spike_data`, [T x B x 1 x 28 x 28]:"
    ]
   },
   {
    "cell_type": "code",
-   "execution_count": 20,
+   "execution_count": 12,
    "outputs": [
     {
      "name": "stdout",
@@ -428,12 +427,12 @@
   },
   {
    "cell_type": "code",
-   "execution_count": 21,
+   "execution_count": 13,
    "outputs": [
     {
      "data": {
       "text/plain": "<Figure size 432x288 with 1 Axes>",
-      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAOcAAADnCAYAAADl9EEgAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8vihELAAAACXBIWXMAAAsTAAALEwEAmpwYAAADaklEQVR4nO3dMW7iUBRA0fEoBQ3KCrK8LIIVsIgsLyuIaOj+VFOMZOKRiOEaziltyaK5ehJP357GGL+Ant/3/gHAPHFClDghSpwQJU6Ievnu5n539FcurOx0Pkxz101OiBInRIkTosQJUeKEKHFClDghSpwQJU6IEidEiROixAlR4oQocUKUOCFKnBAlTogSJ0SJE6LECVHihChxQpQ4IUqcECVOiBInRIkTosQJUeKEKHFC1LefAOT2Pr8+Vn3+2+v7qs/n55icECVOiBInRIkTosQJUeKEKHFClDghSpwQJU6IEidEiROixAlR4oQocUKU85xPZum8qPOeHSYnRIkTosQJUeKEKHFClDghyirlyViVbIfJCVHihChxQpQ4IUqcECVOiBInRIkTosQJUeKEKHFClDghSpwQJU6IEidEiROixAlR4oQocUKUOCFKnBAlTogSJ0RNY4yLN/e74+Wb3MXSJ/yu5b22t3c6H6a56yYnRIkTosQJUeKEKHFClDghSpwQ5fucMfaY/GVyQpQ4IUqcECVOiBInRIkTohwZ2xirlsfjyBhsjDghSpwQJU6IEidEiROixAlR4oQocUKUOCFKnBAlTogSJ0SJE6LECVFejflknNfcDpMTosQJUeKEKHFClDghSpwQJU6Isud8Mte899aO9LZMTogSJ0SJE6LECVHihChxQpQ4Icqek3/YZXaYnBAlTogSJ0SJE6LECVHihKhpjHHx5n53vHyTpGuOhC2xZlnH6XyY5q6bnBAlTogSJ0SJE6LECVHihChxQpQjYxuz5h6TFpMTosQJUeKEKHFClDghSpwQJU6IsufcmKUzlfagj8PkhChxQpQ4IUqcECVOiBInRIkTosQJUeKEKHFClDghSpwQJU6IEidEOTLGf1s6juYTgT/L5IQocUKUOCFKnBAlTogSJ0SJE6LECVHihChxQpQ4IUqcECVOiBInRIkTosQJUeKEKHFClDghSpwQJU6IEidEiROivLf2wXh37OMwOSFKnBAlTogSJ0SJE6LECVHihChxQpQ4IUqcECVOiBInRIkTosQJUeKEKHFClDghSpwQJU6IEidEiROixAlR4oQocUKUOCFKnBAlTogSJ0SJE6LECVHTGOPevwGYYXJClDghSpwQJU6IEidEiROi/gAanUCGNcH6QgAAAABJRU5ErkJggg==\n"
+      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAOcAAADnCAYAAADl9EEgAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMiwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8vihELAAAACXBIWXMAAAsTAAALEwEAmpwYAAADx0lEQVR4nO3dQWrbUABF0ap0kEnoCrq8LiIryCK6vK6gZJKZOgo04MiNP7KupHOGNgbbcPmQx3emeZ6/AD1ft34DwGXihChxQpQ4IUqcEPVt6cnHh2d/yoWVvbw+TZced3JClDghSpwQJU6IEidEiROixAlR4oQocUKUOCFKnBAlTogSJ0SJE6LECVGL9znhX7///Fp8/sf3n3d6J+fg5IQocUKUOCFKnBAlTogSJ0SZUnjn2lwy8lpTy+c4OSFKnBAlTogSJ0SJE6LECVHihCg7586M7JD/Y2mLtGPel5MTosQJUeKEKHFClDghSpwQJU6IsnOezMgWace8LycnRIkTosQJUeKEKHFClDghSpwQZeeMWfu+pjuZ++HkhChxQpQ4IUqcECVOiBInRJlSVrD2HLLEFHIcTk6IEidEiROixAlR4oQocUKUOCHKzrkzdszzcHJClDghSpwQJU6IEidEiROixAlRds4bbHlfk/NwckKUOCFKnBAlTogSJ0SJE6LECVF2zhj3NXnj5IQocUKUOCFKnBAlTogSJ0SJE6LECVHihChxQpQ4IUqcECVOiBInRLkytoE1r4WVf7bTdbjPcXJClDghSpwQJU6IEidEiROixAlRds4LRrfCkT2vvFOOuvbZ7KDvOTkhSpwQJU6IEidEiROixAlR4oSoU+6ca++Y9rzbLH1vZ/zOnJwQJU6IEidEiROixAlR4oQocULUNM/zh08+Pjx//OSBle9U7nnvG/le9/y5r3l5fZouPe7khChxQpQ4IUqcECVOiBInRIkTok55n3Nto/c9R1571D3wjJ/byQlR4oQocUKUOCFKnBAlTogypaxgzStn5clgy6t2R5xanJwQJU6IEidEiROixAlR4oQocUKUnTNmj3tcwRG/NycnRIkTosQJUeKEKHFClDghSpwQ5V8A3mDLe4uje97Ivcc9f+4y/wIQdkacECVOiBInRIkTosQJUeKEKDvnBrbcC8uOvGUusXPCzogTosQJUeKEKHFClDghyk9jbqB6LWvUWaeQtTg5IUqcECVOiBInRIkTosQJUeKEKDtnjK2QN05OiBInRIkTosQJUeKEKHFClDghSpwQJU6IEidEiROixAlR4oQocUKUOCFKnBAlTogSJ0SJE6LECVHihChxQpQ4IUqcECVOiBInRIkTosQJUeKEKHFClDghSpwQJU6IEidEiROixAlR4oQocUKUOCFKnBAlTogSJ0SJE6LECVHihChxQtQ0z/PW7wG4wMkJUeKEKHFClDghSpwQJU6I+gt6p3ojO3D9tAAAAABJRU5ErkJggg==\n"
      },
      "metadata": {
       "needs_background": "light"
@@ -454,7 +453,9 @@
     "    camera.snap()\n",
     "\n",
     "# interval=40 specifies 40ms delay between frames\n",
-    "a = camera.animate(interval=40)"
+    "anim = camera.animate(interval=40)\n",
+    "\n",
+    "HTML(anim.to_html5_video())"
    ],
    "metadata": {
     "collapsed": false,
@@ -463,37 +464,13 @@
     }
    }
   },
-  {
-   "cell_type": "code",
-   "execution_count": 22,
-   "metadata": {
-    "pycharm": {
-     "name": "#%%\n"
-    }
-   },
-   "outputs": [
-    {
-     "data": {
-      "text/plain": "<IPython.core.display.HTML object>",
-      "text/html": "<video width=\"432\" height=\"288\" controls autoplay loop>\n  <source type=\"video/mp4\" src=\"data:video/mp4;base64,AAAAIGZ0eXBNNFYgAAACAE00ViBpc29taXNvMmF2YzEAAAAIZnJlZQAASBJtZGF0AAACoAYF//+c\n3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE2MSAtIEguMjY0L01QRUctNCBBVkMgY29kZWMg\nLSBDb3B5bGVmdCAyMDAzLTIwMjAgLSBodHRwOi8vd3d3LnZpZGVvbGFuLm9yZy94MjY0Lmh0bWwg\nLSBvcHRpb25zOiBjYWJhYz0xIHJlZj0zIGRlYmxvY2s9MTowOjAgYW5hbHlzZT0weDM6MHgxMTMg\nbWU9aGV4IHN1Ym1lPTcgcHN5PTEgcHN5X3JkPTEuMDA6MC4wMCBtaXhlZF9yZWY9MSBtZV9yYW5n\nZT0xNiBjaHJvbWFfbWU9MSB0cmVsbGlzPTEgOHg4ZGN0PTEgY3FtPTAgZGVhZHpvbmU9MjEsMTEg\nZmFzdF9wc2tpcD0xIGNocm9tYV9xcF9vZmZzZXQ9LTIgdGhyZWFkcz02IGxvb2thaGVhZF90aHJl\nYWRzPTEgc2xpY2VkX3RocmVhZHM9MCBucj0wIGRlY2ltYXRlPTEgaW50ZXJsYWNlZD0wIGJsdXJh\neV9jb21wYXQ9MCBjb25zdHJhaW5lZF9pbnRyYT0wIGJmcmFtZXM9MyBiX3B5cmFtaWQ9MiBiX2Fk\nYXB0PTEgYl9iaWFzPTAgZGlyZWN0PTEgd2VpZ2h0Yj0xIG9wZW5fZ29wPTAgd2VpZ2h0cD0yIGtl\neWludD0yNTAga2V5aW50X21pbj0yNSBzY2VuZWN1dD00MCBpbnRyYV9yZWZyZXNoPTAgcmNfbG9v\na2FoZWFkPTQwIHJjPWNyZiBtYnRyZWU9MSBjcmY9MjMuMCBxY29tcD0wLjYwIHFwbWluPTAgcXBt\nYXg9NjkgcXBzdGVwPTQgaXBfcmF0aW89MS40MCBhcT0xOjEuMDAAgAAABB1liIQAN//+9vD+BTY7\nmNCXEc3onTMfvxW4ujQ3vc4AAAMAADfc5kT+ypMG5sAAAuCYLsvhIVQAB+ECxyblslmdlMHk1qnV\n+Ua3+luSWVdfCpvgsZ4l/n3sJMlYswp/0VG2tskxNpn/+Q7meVhbVvPNIPzc2YaenbkMb1lfm6x6\n7Y5g0RZgdZno6iLj0lY+lR5a3hxWdwuQbXN7lp8mtxmuwcNbcV7qabUtbgwHIEtb9TWQwEYscBds\n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\nAAEAAAYAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAoAAAAA\nAQAABAAAAAABAAAAAAAAAAEAAAIAAAAAAQAACgAAAAABAAAEAAAAAAEAAAAAAAAAAQAAAgAAAAAB\nAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAoAAAAAAQAABAAAAAABAAAAAAAAAAEA\nAAIAAAAAAQAACgAAAAABAAAEAAAAAAEAAAAAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAA\nAAAAAAABAAACAAAAAAEAAAoAAAAAAQAABAAAAAABAAAAAAAAAAEAAAIAAAAAAQAACgAAAAABAAAE\nAAAAAAEAAAAAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAoA\nAAAAAQAABAAAAAABAAAAAAAAAAEAAAIAAAAAAQAACgAAAAABAAAEAAAAAAEAAAAAAAAAAQAAAgAA\nAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAQAAAAAAQAACgAAAAABAAAEAAAA\nAAEAAAAAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAoAAAAA\nAQAABAAAAAABAAAAAAAAAAEAAAIAAAAAAgAABAAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAAB\nAAACAAAAAAEAAAYAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEA\nAAYAAAAAAQAAAgAAAAABAAAKAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAoAAAAAAQAA\nBAAAAAABAAAAAAAAAAEAAAIAAAAAAQAACgAAAAABAAAEAAAAAAEAAAAAAAAAAQAAAgAAAAABAAAK\nAAAAAAEAAAQAAAAAAQAAAAAAAAABAAACAAAAAAEAAAQAAAAAHHN0c2MAAAAAAAAAAQAAAAEAAABk\nAAAAAQAAAaRzdHN6AAAAAAAAAAAAAABkAAAGxQAAAekAAADYAAAAhAAAAIEAAAClAAAAnAAAAP8A\nAAEHAAAAqgAAAOYAAADFAAAAvgAAAJIAAACmAAAASAAAAOAAAACZAAAAmQAAAJ0AAABuAAAAjAAA\nAOMAAACDAAAAfwAAAFYAAADpAAAAQgAAANAAAACLAAAAxAAAALEAAAEEAAAATQAAAE4AAACwAAAA\nxwAAAJwAAACWAAAAWgAAAFcAAACoAAAAvAAAAFAAAADkAAAAegAAAMUAAABRAAAArQAAALoAAABc\nAAAA8AAAAI4AAACLAAAAtQAAAIsAAAB4AAAArAAAAM4AAACxAAAAQgAAAQAAAACmAAAAuAAAAIIA\nAAEOAAABHgAAAG4AAACLAAAAewAAARYAAAEoAAAApwAAAKcAAAC4AAABEQAAAEsAAAD7AAAAhAAA\nAKsAAABQAAAAoQAAAOEAAABiAAAAmAAAAIoAAAB9AAABKAAAAGAAAACKAAAAYwAAAUMAAADIAAAA\nwgAAAJwAAABVAAAAkAAAAIQAAAC/AAAAkQAAABRzdGNvAAAAAAAAAAEAAAAwAAAAYnVkdGEAAABa\nbWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAAACWpdG9v\nAAAAHWRhdGEAAAABAAAAAExhdmY1OC40NS4xMDA=\n\">\n  Your browser does not support the video tag.\n</video>"
-     },
-     "execution_count": 22,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "# Doesn't work locally in pycharm.\n",
-    "HTML(a.to_html5_video())"
-   ]
-  },
   {
    "cell_type": "code",
    "execution_count": null,
    "outputs": [],
    "source": [
-    "# Save output: .gif, .mp4 etc.\n",
-    "a.save(\"spike_mnist_test.gif\")"
+    "# If you're feeling sentimental, you can save the animation: .gif, .mp4 etc.\n",
+    "anim.save(\"spike_mnist_test.gif\")"
    ],
    "metadata": {
     "collapsed": false,
@@ -531,7 +508,7 @@
     }
    ],
    "source": [
-    "print(f\"The target is: {spike_targets[0]}\")"
+    "print(f\"The corresponding target is: {spike_targets[0]}\")"
    ]
   },
   {
@@ -577,11 +554,12 @@
     "plt.axis('off')\n",
     "\n",
     "for i in range(num_steps):\n",
-    "    im = ax.imshow(spike_data_sample[i, :, :], cmap='plasma')\n",
+    "    im = ax.imshow(spike_data_sample2[i, :, :], cmap='plasma')\n",
     "    camera.snap()\n",
     "\n",
     "# interval=40 specifies 40ms delay between frames\n",
-    "a = camera.animate(interval=40)"
+    "anim = camera.animate(interval=40)\n",
+    "HTML(anim.to_html5_video())"
    ]
   },
   {
@@ -589,7 +567,8 @@
    "execution_count": 20,
    "outputs": [],
    "source": [
-    "a.save(\"spike_mnist_test2.gif\")"
+    "# Optional save\n",
+    "anim.save(\"spike_mnist_test2.gif\")"
    ],
    "metadata": {
     "collapsed": false,
@@ -676,13 +655,13 @@
     }
    ],
    "source": [
-    "spike_data_sample = spike_data_sample.reshape((num_steps, -1))\n",
+    "spike_data_sample2 = spike_data_sample2.reshape((num_steps, -1))\n",
     "\n",
     "# raster plot\n",
     "fig = plt.figure(facecolor=\"w\", figsize=(10, 5))\n",
     "ax = fig.add_subplot(111)\n",
     "\n",
-    "ax.scatter(*torch.where(spike_data_sample.cpu()), s=1.5, c=\"black\")\n",
+    "ax.scatter(*torch.where(spike_data_sample2.cpu()), s=1.5, c=\"black\")\n",
     "\n",
     "plt.title(\"Input Layer\")\n",
     "plt.xlabel(\"Time step\")\n",
@@ -723,7 +702,7 @@
    ],
    "source": [
     "plt.figure(facecolor=\"w\", figsize=(8,1))\n",
-    "plt.scatter(*torch.where(spike_data_sample[:,210].unsqueeze(1).cpu()), s=100, c=\"black\", marker=\"|\")\n",
+    "plt.scatter(*torch.where(spike_data_sample.reshape(num_steps, -1)[:,210].unsqueeze(1).cpu()), s=100, c=\"black\", marker=\"|\")\n",
     "plt.title(\"Input Neuron\")\n",
     "plt.xlabel(\"Time step\")\n",
     "plt.yticks([])\n",
@@ -758,16 +737,10 @@
    "source": [
     "### 2.2 Latency Coding of MNIST\n",
     "Temporal codes capture information about the precise firing time of neurons; a single spike carries much more meaning than in rate codes which rely on firing frequency.\n",
-    "While this opens up higher susceptibility to noise, it also enables lower power consumption as the need for multiple spikes is removed.\n",
+    "While this opens up more susceptibility to noise, it can also decrease the power consumed by the hardware running SNN algorithms because the need for multiple spikes is removed.\n",
     "\n",
     "`spikegen.latency` is a function that allows each input to fire at most **once** during the full time sweep.\n",
-    "Features closer to one will fire earlier and features closer to zero will fire later. I.e., in our MNIST case, bright pixels will fire earlier and dark pixels will fire later.\n",
-    "\n",
-    "* `tau`: by default, the input features are treated as a constant current source to an RC circuit. `tau` is the RC time constant of the circuit. A higher `tau` will induce slower firing.\n",
-    "* `threshold`: the membrane potential the RC circuit must charge up to before firing. All features below the threshold are saturated.\n",
-    "* `clip`: if `True`, spikes below the `threshold` are removed.\n",
-    "* `normalize`: if `True`, the full range of firing is extended or squashed to fit `num_steps`.\n",
-    "* `linear`: if True, the logarithmic firing latency of the RC circuit is replaced with a linear firing latency."
+    "Features closer to one will fire earlier and features closer to zero will fire later. I.e., in our MNIST case, bright pixels will fire earlier and dark pixels will fire later."
    ]
   },
   {
@@ -788,8 +761,11 @@
   {
    "cell_type": "markdown",
    "source": [
-    "### 2.2.1 Raster plot\n",
-    "We'll start with a raster this time."
+    "* `tau`: by default, the input features are treated as a constant current injected into an RC circuit. `tau` is the RC time constant of the circuit. A higher `tau` will induce slower firing.\n",
+    "* `threshold`: the membrane potential the RC circuit must charge to before it can fire. All features below the threshold are saturated.\n",
+    "* `clip`: if `True`, spikes below the `threshold` are removed.\n",
+    "* `normalize`: if `True`, the full range of firing is extended or squashed to fit `num_steps`.\n",
+    "* `linear`: if True, the logarithmic firing latency of the RC circuit is replaced with a linear firing latency."
    ],
    "metadata": {
     "collapsed": false,
@@ -799,14 +775,15 @@
    }
   },
   {
-   "cell_type": "code",
-   "execution_count": null,
-   "outputs": [],
-   "source": [],
+   "cell_type": "markdown",
+   "source": [
+    "### 2.2.1 Raster plot\n",
+    "We'll start with a raster this time."
+   ],
    "metadata": {
     "collapsed": false,
     "pycharm": {
-     "name": "#%%\n"
+     "name": "#%% md\n"
     }
    }
   },
@@ -842,7 +819,8 @@
   {
    "cell_type": "markdown",
    "source": [
-    "The logarithmic code coupled with the lack of diverse input values (beyond close to 1 and 0) causes significant clustering in two areas of the plot.\n",
+    "The logarithmic code coupled with the lack of diverse input values (i.e., the lack of midtone/grayscale features) causes significant clustering in two areas of the plot.\n",
+    "The bright pixels induce firing at the start of the run, and the dark pixels at the end.\n",
     "We can increase `tau` to slow this down, or we can linearize the data by setting `linear=True`."
    ],
    "metadata": {
@@ -887,8 +865,8 @@
   {
    "cell_type": "markdown",
    "source": [
-    "This is a more even range of firing times now. But notice it all happens within the first 5 time steps, whereas we're simulating 100 time steps.\n",
-    "So we have a lot of redundant time steps doing nothing. This can be solved by either increasing `tau`, or setting `normalize=True` to span the full range of `num_steps`."
+    "The spread of firing times is much more evenly distributed now. But notice all firing occurs within the first ~5 time steps, whereas the simulation range is 100 time steps.\n",
+    "This indicates that we have a lot of redundant time steps doing nothing. This can be solved by either increasing `tau` to slow down the time constant, or setting `normalize=True` to span the full range of `num_steps`."
    ],
    "metadata": {
     "collapsed": false,
@@ -978,7 +956,7 @@
   {
    "cell_type": "markdown",
    "source": [
-    "That looks better!"
+    "That looks much better!"
    ],
    "metadata": {
     "collapsed": false,
@@ -990,7 +968,8 @@
   {
    "cell_type": "markdown",
    "source": [
-    "### 2.2.2 Animation"
+    "### 2.2.2 Animation\n",
+    "We will run the exact same code block as before to create an animation."
    ],
    "metadata": {
     "collapsed": false,
@@ -1050,32 +1029,8 @@
     "    camera.snap()\n",
     "\n",
     "# interval=100 specifies 100ms delay between frames\n",
-    "a = camera.animate(interval=100)"
-   ],
-   "metadata": {
-    "collapsed": false,
-    "pycharm": {
-     "name": "#%%\n"
-    }
-   }
-  },
-  {
-   "cell_type": "code",
-   "execution_count": 82,
-   "outputs": [
-    {
-     "data": {
-      "text/plain": "<IPython.core.display.HTML object>",
-      "text/html": "<video width=\"432\" height=\"288\" controls autoplay loop>\n  <source type=\"video/mp4\" src=\"data:video/mp4;base64,AAAAIGZ0eXBNNFYgAAACAE00ViBpc29taXNvMmF2YzEAAAAIZnJlZQAAGqxtZGF0AAACoAYF//+c\n3EXpvebZSLeWLNgg2SPu73gyNjQgLSBjb3JlIDE2MSAtIEguMjY0L01QRUctNCBBVkMgY29kZWMg\nLSBDb3B5bGVmdCAyMDAzLTIwMjAgLSBodHRwOi8vd3d3LnZpZGVvbGFuLm9yZy94MjY0Lmh0bWwg\nLSBvcHRpb25zOiBjYWJhYz0xIHJlZj0zIGRlYmxvY2s9MTowOjAgYW5hbHlzZT0weDM6MHgxMTMg\nbWU9aGV4IHN1Ym1lPTcgcHN5PTEgcHN5X3JkPTEuMDA6MC4wMCBtaXhlZF9yZWY9MSBtZV9yYW5n\nZT0xNiBjaHJvbWFfbWU9MSB0cmVsbGlzPTEgOHg4ZGN0PTEgY3FtPTAgZGVhZHpvbmU9MjEsMTEg\nZmFzdF9wc2tpcD0xIGNocm9tYV9xcF9vZmZzZXQ9LTIgdGhyZWFkcz02IGxvb2thaGVhZF90aHJl\nYWRzPTEgc2xpY2VkX3RocmVhZHM9MCBucj0wIGRlY2ltYXRlPTEgaW50ZXJsYWNlZD0wIGJsdXJh\neV9jb21wYXQ9MCBjb25zdHJhaW5lZF9pbnRyYT0wIGJmcmFtZXM9MyBiX3B5cmFtaWQ9MiBiX2Fk\nYXB0PTEgYl9iaWFzPTAgZGlyZWN0PTEgd2VpZ2h0Yj0xIG9wZW5fZ29wPTAgd2VpZ2h0cD0yIGtl\neWludD0yNTAga2V5aW50X21pbj0xMCBzY2VuZWN1dD00MCBpbnRyYV9yZWZyZXNoPTAgcmNfbG9v\na2FoZWFkPTQwIHJjPWNyZiBtYnRyZWU9MSBjcmY9MjMuMCBxY29tcD0wLjYwIHFwbWluPTAgcXBt\nYXg9NjkgcXBzdGVwPTQgaXBfcmF0aW89MS40MCBhcT0xOjEuMDAAgAAAArFliIQAEf/+94gfMstp\n+TrXchHnrS6tH1DuRnFepL3+IAAAAwAWnJlD+2HHTlrDQABCibOAP4osWAA/EWEThITw1evfZk+Y\n/69ohdDPlwRzHTwBXWyatM8KhLg352qNVj3l3H7vwA448O3zbMy2Cmxt3bc1lri+G/zDax4P0paY\nK1WFtZLX6hIIqZrfXpICjq2rM8s0epAYIXWauXNhDQGu/Pm+4bJ6MZ35YfS40eotcjwEt4fFDcaC\n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\np2F2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAABsAEgAEgAAABIAAAAAAAAAAEAAAAAAAAAAAAA\nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY//8AAAA1YXZjQwFkABX/4QAYZ2QAFazZQbCWhAAAAwAE\nAAADAFA8WLZYAQAGaOvjyyLA/fj4AAAAABx1dWlka2hA8l8kT8W6OaUbzwMj8wAAAAAAAAAYc3R0\ncwAAAAAAAAABAAAAZAAABAAAAAAUc3RzcwAAAAAAAAABAAAAAQAAAyBjdHRzAAAAAAAAAGIAAAAB\nAAAIAAAAAAEAABQAAAAAAQAACAAAAAABAAAAAAAAAAEAAAQAAAAAAQAAFAAAAAABAAAIAAAAAAEA\nAAAAAAAAAQAABAAAAAABAAAQAAAAAAIAAAQAAAAAAQAAFAAAAAABAAAIAAAAAAEAAAAAAAAAAQAA\nBAAAAAABAAAMAAAAAAEAAAQAAAAAAQAAFAAAAAABAAAIAAAAAAEAAAAAAAAAAQAABAAAAAABAAAU\nAAAAAAEAAAgAAAAAAQAAAAAAAAABAAAEAAAAAAEAAAwAAAAAAQAABAAAAAABAAAUAAAAAAEAAAgA\nAAAAAQAAAAAAAAABAAAEAAAAAAEAABQAAAAAAQAACAAAAAABAAAAAAAAAAEAAAQAAAAAAQAAFAAA\nAAABAAAIAAAAAAEAAAAAAAAAAQAABAAAAAABAAAUAAAAAAEAAAgAAAAAAQAAAAAAAAABAAAEAAAA\nAAEAABQAAAAAAQAACAAAAAABAAAAAAAAAAEAAAQAAAAAAQAAFAAAAAABAAAIAAAAAAEAAAAAAAAA\nAQAABAAAAAABAAAUAAAAAAEAAAgAAAAAAQAAAAAAAAABAAAEAAAAAAEAABQAAAAAAQAACAAAAAAB\nAAAAAAAAAAEAAAQAAAAAAQAAFAAAAAABAAAIAAAAAAEAAAAAAAAAAQAABAAAAAABAAAUAAAAAAEA\nAAgAAAAAAQAAAAAAAAABAAAEAAAAAAEAABQAAAAAAQAACAAAAAABAAAAAAAAAAEAAAQAAAAAAQAA\nCAAAAAABAAAUAAAAAAEAAAgAAAAAAQAAAAAAAAABAAAEAAAAAAEAAAwAAAAAAQAABAAAAAABAAAM\nAAAAAAEAAAQAAAAAAQAAFAAAAAABAAAIAAAAAAEAAAAAAAAAAQAABAAAAAABAAAUAAAAAAEAAAgA\nAAAAAQAAAAAAAAABAAAEAAAAAAEAABQAAAAAAQAACAAAAAABAAAAAAAAAAEAAAQAAAAAAQAAFAAA\nAAABAAAIAAAAAAEAAAAAAAAAAQAABAAAAAABAAAQAAAAAAIAAAQAAAAAHHN0c2MAAAAAAAAAAQAA\nAAEAAABkAAAAAQAAAaRzdHN6AAAAAAAAAAAAAABkAAAFWQAAACwAAAApAAAAJwAAABEAAAAYAAAA\ncQAAABcAAAAXAAAAkwAAAH4AAABUAAAAIQAAABoAAADBAAAAQgAAAIUAAAAZAAAAIQAAABoAAAAX\nAAAAEQAAABgAAAAVAAAAEQAAABEAAACUAAAAGQAAAIcAAACQAAAAGwAAAB0AAACIAAAAIAAAABgA\nAADHAAAAIgAAABoAAAAXAAAAEQAAABgAAAAVAAAAEQAAABEAAAAYAAAAFQAAABEAAAARAAAAGAAA\nABUAAABqAAAAEQAAAGQAAAAZAAAAEQAAAHgAAAAfAAAAFwAAABQAAAAQAAAAGAAAABMAAAAQAAAA\nQAAAABgAAAATAAAAEAAAABAAAAAZAAAAEwAAADkAAAAQAAAAigAAACEAAAAWAAAAFQAAAE8AAACg\nAAAAewAAAPQAAAAsAAAA1wAAAHYAAAA4AAAAJgAAACQAAACAAAAAigAAAMAAAAAZAAAAEgAAADkA\nAABqAAAAGQAAABIAAAAQAAAAEAAAABgAAAASAAAAEAAAABRzdGNvAAAAAAAAAAEAAAAwAAAAYnVk\ndGEAAABabWV0YQAAAAAAAAAhaGRscgAAAAAAAAAAbWRpcmFwcGwAAAAAAAAAAAAAAAAtaWxzdAAA\nACWpdG9vAAAAHWRhdGEAAAABAAAAAExhdmY1OC40NS4xMDA=\n\">\n  Your browser does not support the video tag.\n</video>"
-     },
-     "execution_count": 82,
-     "metadata": {},
-     "output_type": "execute_result"
-    }
-   ],
-   "source": [
-    "# Doesn't work locally in pycharm.\n",
-    "HTML(a.to_html5_video())"
+    "anim = camera.animate(interval=100)\n",
+    "HTML(anim.to_html5_video())"
    ],
    "metadata": {
     "collapsed": false,
@@ -1090,7 +1045,7 @@
    "outputs": [],
    "source": [
     "# Save output: .gif, .mp4 etc.\n",
-    "a.save(\"mnist_latency.gif\")"
+    "anim.save(\"mnist_latency.gif\")"
    ],
    "metadata": {
     "collapsed": false,
@@ -1102,7 +1057,7 @@
   {
    "cell_type": "markdown",
    "source": [
-    "This animation is obviously much tougher to make out in video form, but if you watch carefully you'll catch a glimpse of the initial frame where most of the spikes take place.\n",
+    "This animation is obviously much tougher to make out in video form, but a keen eye will be able to catch a glimpse of the initial frame where most of the spikes occur.\n",
     "We can index into the corresponding target value to check what value it is."
    ],
    "metadata": {
@@ -1144,8 +1099,8 @@
    "source": [
     "## 3. Spike Generation\n",
     "Now say we just want a randomly generated spike train from scratch.\n",
-    "`spikegen.rate` has a nested function, `rate_conv` which takes care of the feature to spike conversion process.\n",
-    "All we have to do is initialize a randomly generated torchTensor to pass in."
+    "`spikegen.rate` has a nested function, `rate_conv` which takes care of the feature $\\rightarrow$ spike conversion process.\n",
+    "All we have to do is initialize a randomly generated `torchTensor` to pass in."
    ]
   },
   {
@@ -1159,8 +1114,8 @@
    "outputs": [],
    "source": [
     "# Create a random spike train\n",
-    "spike_prob = torch.rand((num_steps, 28, 28), device=device, dtype=dtype)\n",
-    "spike_data = spikegen.rate_conv(spike_prob)"
+    "spike_prob = torch.rand((num_steps, 28, 28), device=device, dtype=dtype) * 0.5\n",
+    "spike_rand = spikegen.rate_conv(spike_prob)"
    ]
   },
   {
@@ -1188,14 +1143,12 @@
     "\n",
     "# iterate over time and take a snapshot with celluloid\n",
     "for time in range(num_steps):\n",
-    "    im = ax.imshow(spike_data[time, :, :], cmap='plasma')\n",
+    "    im = ax.imshow(spike_rand[time, :, :], cmap='plasma')\n",
     "    camera.snap()\n",
     "\n",
     "# interval=40 specifies 40ms delay between frames\n",
-    "a = camera.animate(interval=100)\n",
-    "\n",
-    "# Doesn't work locally in pycharm.\n",
-    "HTML(a.to_html5_video())"
+    "anim = camera.animate(interval=40)\n",
+    "HTML(anim.to_html5_video())"
    ],
    "metadata": {
     "collapsed": false,
@@ -1210,7 +1163,7 @@
    "outputs": [],
    "source": [
     "# Save output: .gif, .mp4 etc.\n",
-    "a.save(\"random_spikes.gif\")"
+    "anim.save(\"random_spikes.gif\")"
    ],
    "metadata": {
     "collapsed": false,
@@ -1247,7 +1200,7 @@
    "source": [
     "fig = plt.figure(facecolor=\"w\", figsize=(10, 5))\n",
     "ax = fig.add_subplot(111)\n",
-    "ax.scatter(*torch.where(spike_data[:, 0].view(100, -1).cpu()), s=25, c=\"black\")\n",
+    "ax.scatter(*torch.where(spike_rand[:, 0].view(100, -1).cpu()), s=25, c=\"black\")\n",
     "plt.title(\"Input Layer\")\n",
     "plt.xlabel(\"Time step\")\n",
     "plt.ylabel(\"Neuron Number\")\n",
@@ -1293,9 +1246,9 @@
    "provenance": []
   },
   "kernelspec": {
-   "name": "pycharm-78d010ff",
+   "name": "python3",
    "language": "python",
-   "display_name": "PyCharm (snntorch)"
+   "display_name": "Python 3"
   },
   "language_info": {
    "codemirror_mode": {
diff --git a/examples/tutorial_2_FCN.ipynb b/examples/tutorial_2_FCN.ipynb
index 18fac852..b66375f5 100644
--- a/examples/tutorial_2_FCN.ipynb
+++ b/examples/tutorial_2_FCN.ipynb
@@ -16,9 +16,9 @@
   {
    "cell_type": "markdown",
    "source": [
-    "# # Gradient-based Learning in Spiking Neural Networks\n",
+    "## Gradient-based Learning in Spiking Neural Networks\n",
     "In this tutorial, we'll create a simple 2-layer fully-connected network (FCN) to classify the MNIST dataset.\n",
-    "We will use the backpropagation through time (BPTT) algorithm to do so.\n",
+    "We will use the backpropagation through time (BPTT) algorithm to train the network.\n",
     "\n",
     "If running in Google Colab:\n",
     "* Ensure you are connected to GPU by checking Runtime > Change runtime type > Hardware accelerator: GPU\n",
@@ -107,8 +107,8 @@
     "# Temporal Dynamics\n",
     "num_steps = 25\n",
     "time_step = 1e-3\n",
-    "tau_mem = 6.5e-4\n",
-    "tau_syn = 5.5e-4\n",
+    "tau_mem = 3e-3\n",
+    "tau_syn = 2.2e-3\n",
     "alpha = float(np.exp(-time_step/tau_syn))\n",
     "beta = float(np.exp(-time_step/tau_mem))\n",
     "\n",
@@ -188,16 +188,15 @@
    "cell_type": "markdown",
    "source": [
     "## 2. Define Network\n",
-    "To define our network, we will import two functions from the `snntorch.neuron` module, which contains a series of neuron models and related functions.\n",
-    "snnTorch treats neurons as activations with recurrent connections, so that it integrates smoothly with PyTorch's pre-existing layer functions.\n",
-    "* `snntorch.neuron.LIF` is a simple Leaky Integrate and Fire (LIF) neuron. Specifically, it uses Stein's model which assumes instantaneous rise times for synaptic current and membrane potential.\n",
-    "* `snntorch.neuron.FastSigmoidSurrogate` defines separate forward and backward functions. The forward function is a Heaviside step function for spike generation. The backward function is the derivative of a fast sigmoid function, to ensure continuous differentiability.\n",
-    "FSS is mostly derived from:\n",
+    "snnTorch treats neurons as activations with recurrent connections. This allows for smooth integration with PyTorch.\n",
+    "There are a few useful neuron models and surrogate gradient functions which approximate the gradient of spikes.\n",
     "\n",
-    ">Neftci, E. O., Mostafa, H., and Zenke, F. (2019) Surrogate Gradient Learning in Spiking Neural Networks. https://arxiv.org/abs/1901/09948\n",
+    "Our network will use one type of neuron model and one surrogate gradient:\n",
+    "1. `snntorch.Stein` is a basic leaky integrate and fire (LIF) neuron. Specifically, it assumes instantaneous rise times for synaptic current and membrane potential.\n",
+    "2. `snntorch.FastSigmoidSurrogate` defines separate forward and backward functions. The forward function is a Heaviside step function for spike generation. The backward function is the derivative of a fast sigmoid function, to ensure continuous differentiability.\n",
+    "The `FastSigmoidSurrogate` function has been adapted from:\n",
     "\n",
-    "`snn.neuron.slope` is a variable that defines the slope of the backward surrogate.\n",
-    "TO-DO: Include visualisation."
+    ">Neftci, E. O., Mostafa, H., and Zenke, F. (2019) Surrogate Gradient Learning in Spiking Neural Networks. https://arxiv.org/abs/1901/09948"
    ],
    "metadata": {
     "collapsed": false,
@@ -211,11 +210,8 @@
    "execution_count": null,
    "outputs": [],
    "source": [
-    "from snntorch.neuron import LIF\n",
-    "from snntorch.neuron import FastSimgoidSurrogate as FSS\n",
-    "\n",
-    "spike_fn = FSS.apply\n",
-    "snn.neuron.slope = 50"
+    "spike_grad = snn.FastSigmoidSurrogate.apply\n",
+    "snn.slope = 50"
    ],
    "metadata": {
     "collapsed": false,
@@ -227,11 +223,36 @@
   {
    "cell_type": "markdown",
    "source": [
-    "Now we can define our SNN. Defining an instance of `LIF` requires three arguments: 1) the surrogate spiking function, 2) $I_{syn}$ decay rate, $\\alpha$, and 3) $V_{mem}$ decay rate, $\\beta$.\n",
+    "The surrogate is passed to `spike_grad` and overrides the default gradient of the Heaviside step function.\n",
+    "If we did not override the default gradient, (zero everywhere, except for $x=1$ where it is technically infinite but clipped to 1 here), then learning would not take place for as long as the neuron was not emitting post-synaptic spikes.\n",
+    "\n",
+    "`snn.slope` defines the slope of the backward surrogate.\n",
+    "\n",
+    "TO-DO: Include visualisation."
+   ],
+   "metadata": {
+    "collapsed": false,
+    "pycharm": {
+     "name": "#%% md\n"
+    }
+   }
+  },
+  {
+   "cell_type": "markdown",
+   "source": [
+    "Now we can define our spiking neural network (SNN).\n",
+    "Creating an instance of the `Stein` neuron requires two arguments and two optional arguments:\n",
+    "1. $I_{syn}$ decay rate, $\\alpha$,\n",
+    "2. $V_{mem}$ decay rate, $\\beta$,\n",
+    "3. the surrogate spiking function, `spike_grad` (*default*: the gradient of the Heaviside function), and\n",
+    "4. the threshold for spiking, (*default*: 1.0).\n",
     "\n",
-    "The LIF neuron is simply treated as a recurrent activation. It requires initialization of the post-synaptic spikes `spk1` and `spk2`, the synaptic current `syn1` and `syn2`, and the membrane potential `mem1` and `mem2`.\n",
+    "snnTorch treats the LIF neuron as a recurrent activation. Therefore, it requires initialization of its internal states.\n",
+    "For each layer, we initialize the synaptic current `syn1` and `syn2`, the membrane potential `mem1` and `mem2`, and the post-synaptic spikes `spk1` and `spk2` to zero.\n",
+    "A class method `init_stein` will take care of this.\n",
     "\n",
-    "We will use the final layer spikes and membrane for determining loss and accuracy, so we will record and return their histories in `spk2_rec` and `mem2_rec`.\n",
+    "For rate coding, the final layer of spikes and membrane potential are used to determine accuracy and loss, respectively.\n",
+    "So their historical values are recorded in `spk2_rec` and `mem2_rec`.\n",
     "\n",
     "Keep in mind, the dataset we are using is just static MNIST. I.e., it is *not* time-varying.\n",
     "Therefore, we pass the same MNIST sample to the input at each time step.\n",
@@ -256,13 +277,13 @@
     "\n",
     "    # initialize layers\n",
     "        self.fc1 = nn.Linear(num_inputs, num_hidden)\n",
-    "        self.lif1 = LIF(spike_fn=spike_fn, alpha=alpha, beta=beta)\n",
+    "        self.lif1 = snn.Stein(alpha=alpha, beta=beta, spike_grad=spike_grad)\n",
     "        self.fc2 = nn.Linear(num_hidden, num_outputs)\n",
-    "        self.lif2 = LIF(spike_fn=spike_fn, alpha=alpha, beta=beta)\n",
+    "        self.lif2 = snn.Stein(alpha=alpha, beta=beta, spike_grad=spike_grad)\n",
     "\n",
     "    def forward(self, x):\n",
-    "        spk1, syn1, mem1 = self.lif1.init_hidden(batch_size, num_hidden)\n",
-    "        spk2, syn2, mem2 = self.lif2.init_hidden(batch_size, num_outputs)\n",
+    "        spk1, syn1, mem1 = self.lif1.init_stein(batch_size, num_hidden)\n",
+    "        spk2, syn2, mem2 = self.lif2.init_stein(batch_size, num_outputs)\n",
     "\n",
     "        spk2_rec = []\n",
     "        mem2_rec = []\n",
@@ -291,7 +312,7 @@
    "cell_type": "markdown",
    "source": [
     "## 3. Training\n",
-    "Time for training! Let's first define a couple of functions to print out test/train accuracy for each minibatch."
+    "Time for training! Let's first define a couple of functions to print out test/train accuracy."
    ],
    "metadata": {
     "collapsed": false,
@@ -307,10 +328,10 @@
    "source": [
     "def print_batch_accuracy(data, targets, train=False):\n",
     "    output, _ = net(data.view(batch_size, -1))\n",
-    "    _, am = output.sum(dim=0).max(1)\n",
-    "    acc = np.mean((targets == am).detach().cpu().numpy())\n",
+    "    _, idx = output.sum(dim=0).max(1)\n",
+    "    acc = np.mean((targets == idx).detach().cpu().numpy())\n",
     "\n",
-    "    if train is True:\n",
+    "    if train:\n",
     "        print(f\"Train Set Accuracy: {acc}\")\n",
     "    else:\n",
     "        print(f\"Test Set Accuracy: {acc}\")\n",
@@ -334,10 +355,12 @@
    "cell_type": "markdown",
    "source": [
     "### 3.1 Optimizer & Loss\n",
-    "We'll apply the softmax function to the membrane potentials of the output layer in calculating a negative log-likelihood loss.\n",
-    "The Adam optimizer is used for weight updates.\n",
-    "\n",
-    "Accuracy is measured by counting the spikes of the output neurons. The neuron that fires the most frequently will be our predicted class."
+    "* *Output Activation*: We'll apply the softmax function to the membrane potentials of the output layer, rather than the spikes.\n",
+    "* *Loss*: This will then be used to calculate the negative log-likelihood loss.\n",
+    "By encouraging the membrane of the correct neuron class to reach the threshold, we expect that neuron will fire more frequently.\n",
+    "The loss could be applied to the spike count as well, but the membrane is  continuous whereas spike count is discrete.\n",
+    "* *Optimizer*: The Adam optimizer is used for weight updates.\n",
+    "* *Accuracy*: Accuracy is measured by counting the spikes of the output neurons. The neuron that fires the most frequently will be our predicted class."
    ],
    "metadata": {
     "collapsed": false,
@@ -481,7 +504,8 @@
   {
    "cell_type": "markdown",
    "source": [
-    "### 4.2 Test Set Accuracy"
+    "### 4.2 Test Set Accuracy\n",
+    "This function just iterates over all minibatches to obtain a measure of accuracy over the full 10,000 samples in the test set."
    ],
    "metadata": {
     "collapsed": false,
@@ -565,8 +589,8 @@
     "from snntorch import spikegen\n",
     "\n",
     "# MNIST to spiking-MNIST\n",
-    "spike_data, spike_targets = spikegen.rate(data_it, targets_it, num_outputs=num_outputs, num_steps=num_steps,\n",
-    "                                                      gain=1, offset=0, convert_targets=False, temporal_targets=False)"
+    "spike_data, spike_targets = spikegen.rate(data_it, targets_it, num_outputs=num_outputs, num_steps=num_steps, gain=1,\n",
+    "                                          offset=0, convert_targets=False, temporal_targets=False)"
    ],
    "metadata": {
     "collapsed": false,
@@ -591,7 +615,7 @@
   {
    "cell_type": "code",
    "source": [
-    "!pip install celluloid # animating matplotlib plots made easy"
+    "!pip install celluloid # matplotlib animations made easy"
    ],
    "metadata": {
     "collapsed": false,
@@ -623,7 +647,20 @@
     "\n",
     "# interval=40 specifies 40ms delay between frames\n",
     "a = camera.animate(interval=40)\n",
-    "HTML(a.to_html5_video())\n",
+    "HTML(a.to_html5_video())"
+   ],
+   "metadata": {
+    "collapsed": false,
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "outputs": [],
+   "source": [
     "print(spike_targets[0])"
    ],
    "metadata": {
@@ -652,23 +689,24 @@
    "execution_count": null,
    "outputs": [],
    "source": [
-    "spike_fn = FSS.apply\n",
-    "snn.neuron.slope = 50\n",
+    "spike_grad = snn.FastSigmoidSurrogate.apply\n",
+    "snn.slope = 50 # The lower the slope, the smoother the gradient\n",
     "\n",
     "# Define Network\n",
     "class Net(nn.Module):\n",
     "    def __init__(self):\n",
     "        super().__init__()\n",
     "\n",
-    "    # initialize layers\n",
+    "        # Initialize layers\n",
     "        self.fc1 = nn.Linear(num_inputs, num_hidden)\n",
-    "        self.lif1 = LIF(spike_fn=spike_fn, alpha=alpha, beta=beta)\n",
+    "        self.lif1 = snn.Stein(alpha=alpha, beta=beta, spike_grad=spike_grad)\n",
     "        self.fc2 = nn.Linear(num_hidden, num_outputs)\n",
-    "        self.lif2 = LIF(spike_fn=spike_fn, alpha=alpha, beta=beta)\n",
+    "        self.lif2 = snn.Stein(alpha=alpha, beta=beta, spike_grad=spike_grad)\n",
     "\n",
     "    def forward(self, x):\n",
-    "        spk1, syn1, mem1 = self.lif1.init_hidden(batch_size, num_hidden)\n",
-    "        spk2, syn2, mem2 = self.lif2.init_hidden(batch_size, num_outputs)\n",
+    "        # Initialize hidden states + output spike at t=0\n",
+    "        spk1, syn1, mem1 = self.lif1.init_stein(batch_size, num_hidden)\n",
+    "        spk2, syn2, mem2 = self.lif2.init_stein(batch_size, num_outputs)\n",
     "\n",
     "        spk2_rec = []\n",
     "        mem2_rec = []\n",
@@ -711,14 +749,12 @@
    "execution_count": null,
    "outputs": [],
    "source": [
-    "# Print batch accuracy function\n",
-    "\n",
     "def print_batch_accuracy(data, targets, train=False):\n",
     "    output, _ = net(data.view(num_steps, batch_size, -1))\n",
-    "    _, am = output.sum(dim=0).max(1)\n",
-    "    acc = np.mean((targets == am). detach().cpu().numpy())\n",
+    "    _, idx = output.sum(dim=0).max(1)\n",
+    "    acc = np.mean((targets == idx).detach().cpu().numpy())\n",
     "\n",
-    "    if train is True:\n",
+    "    if train:\n",
     "        print(f\"Train Set Accuracy: {acc}\")\n",
     "    else:\n",
     "        print(f\"Test Set Accuracy: {acc}\")\n",
@@ -770,7 +806,8 @@
   {
    "cell_type": "markdown",
    "source": [
-    "### 7.2 Training Loop"
+    "### 7.2 Training Loop\n",
+    "The training loop is identical to the static MNIST case, but we pass each minibatch through `spikegen.rate` before running it through the feedforward network."
    ],
    "metadata": {
     "collapsed": false,
@@ -948,7 +985,8 @@
   {
    "cell_type": "markdown",
    "source": [
-    "That's all for now! Next time, we'll make a few tiny modifications to show how we can crank accuracy up even further by using spiking convolutional layers."
+    "That's all for now!\n",
+    "Next time, we'll introduce how to use spiking convolutional layers to improve accuracy."
    ],
    "metadata": {
     "collapsed": false
@@ -957,9 +995,9 @@
  ],
  "metadata": {
   "kernelspec": {
-   "name": "pycharm-78d010ff",
+   "name": "python3",
    "language": "python",
-   "display_name": "PyCharm (snntorch)"
+   "display_name": "Python 3"
   },
   "language_info": {
    "codemirror_mode": {
diff --git a/examples/tutorial_3_CNN.ipynb b/examples/tutorial_3_CNN.ipynb
index b1de651c..e007b7e8 100644
--- a/examples/tutorial_3_CNN.ipynb
+++ b/examples/tutorial_3_CNN.ipynb
@@ -188,15 +188,16 @@
    "cell_type": "markdown",
    "source": [
     "## 2. Define Network\n",
-    "To define our network, we will import two functions from the `snntorch.neuron` module, which contains a series of neuron models and related functions.\n",
-    "snnTorch treats neurons as activations with recurrent connections, so that it integrates smoothly with PyTorch's pre-existing layer functions.\n",
-    "* `snntorch.neuron.LIF` is a simple Leaky Integrate and Fire (LIF) neuron. Specifically, it uses Stein's model which assumes instantaneous rise times for synaptic current and membrane potential.\n",
-    "* `snntorch.neuron.FastSigmoidSurrogate` defines separate forward and backward functions. The forward function is a Heaviside step function for spike generation. The backward function is the derivative of a fast sigmoid function, to ensure continuous differentiability.\n",
+    "snnTorch contains a series of neuron models and related functions to ease the training process.\n",
+    "Neurons are treated as activations with recurrent connections, and integrate smoothly with PyTorch's pre-existing layer functions.\n",
+    "* `snntorch.Stein` is a simple Leaky Integrate and Fire (LIF) neuron. Specifically, it uses Stein's model which assumes instantaneous rise times for synaptic current and membrane potential.\n",
+    "* `snntorch.FastSigmoidSurrogate` defines separate forward and backward functions. The forward function is a Heaviside step function for spike generation. The backward function is the derivative of a fast sigmoid function, to ensure continuous differentiability.\n",
     "FSS is mostly derived from:\n",
     "\n",
     ">Neftci, E. O., Mostafa, H., and Zenke, F. (2019) Surrogate Gradient Learning in Spiking Neural Networks. https://arxiv.org/abs/1901/09948\n",
     "\n",
-    "`snn.neuron.slope` is a variable that defines the slope of the backward surrogate.\n",
+    "There are a few other surrogate gradient functions included.\n",
+    "`snn.slope` is a variable that defines the slope of the backward surrogate.\n",
     "TO-DO: Include visualisation."
    ],
    "metadata": {
@@ -211,11 +212,8 @@
    "execution_count": null,
    "outputs": [],
    "source": [
-    "from snntorch.neuron import LIF\n",
-    "from snntorch.neuron import FastSimgoidSurrogate as FSS\n",
-    "\n",
-    "spike_fn = FSS.apply\n",
-    "snn.neuron.slope = 50"
+    "spike_grad = snn.FastSigmoidSurrogate.apply\n",
+    "snn.slope = 50"
    ],
    "metadata": {
     "collapsed": false,
@@ -227,15 +225,25 @@
   {
    "cell_type": "markdown",
    "source": [
-    "Now we can define our SNN. Defining an instance of `LIF` requires three arguments: 1) the surrogate spiking function, 2) $I_{syn}$ decay rate, $\\alpha$, and 3) $V_{mem}$ decay rate, $\\beta$.\n",
+    "Now we can define our spiking neural network (SNN).\n",
+    "If you have already worked through Tutorial 2, you may wish to skip ahead.\n",
+    "\n",
+    "Creating an instance of the `Stein` neuron requires two compulsory arguments and two optional arguments:\n",
+    "1. $I_{syn}$ decay rate, $\\alpha$,\n",
+    "2. $V_{mem}$ decay rate, $\\beta$,\n",
+    "3. *optional*: the surrogate spiking function, `spike_grad` (*default*: the gradient of the Heaviside function), and\n",
+    "4. *optional*: the threshold for spiking, (*default*: 1.0).\n",
     "\n",
-    "The LIF neuron is simply treated as a recurrent activation. It requires initialization of the post-synaptic spikes `spk1` and `spk2`, the synaptic current `syn1` and `syn2`, and the membrane potential `mem1` and `mem2`.\n",
+    "snnTorch treats the LIF neuron as a recurrent activation. Therefore, it requires initialization of its internal states.\n",
+    "For each layer, we initialize the synaptic current `syn1` and `syn2`, the membrane potential `mem1` and `mem2`, and the post-synaptic spikes `spk1` and `spk2` to zero.\n",
+    "A class method `init_stein` will take care of this.\n",
     "\n",
-    "We will use the final layer spikes and membrane for determining loss and accuracy, so we will record and return their histories in `spk3_rec` and `mem3_rec`.\n",
+    "For rate coding, the final layer of spikes and membrane potential are used to determine accuracy and loss, respectively.\n",
+    "So their historical values are recorded in `spk3_rec` and `mem3_rec`.\n",
     "\n",
     "Keep in mind, the dataset we are using is just static MNIST. I.e., it is *not* time-varying.\n",
     "Therefore, we pass the same MNIST sample to the input at each time step.\n",
-    "This is handled in the line `cur1 = F.max_pool2d(self.conv1(x), 2)`, where `x` is the same input over the whole for-loop."
+    "This is handled with `cur1 = F.max_pool2d(self.conv1(x), 2)`, where `x` is the same input over the whole for-loop."
    ],
    "metadata": {
     "collapsed": false,
@@ -255,11 +263,11 @@
     "\n",
     "    # initialize layers\n",
     "        self.conv1 = nn.Conv2d(in_channels=1, out_channels=12, kernel_size=5, stride=1, padding=1)\n",
-    "        self.lif1 = LIF(spike_fn=spike_fn, alpha=alpha, beta=beta)\n",
+    "        self.lif1 = snn.Stein(alpha=alpha, beta=beta, spike_grad=spike_grad)\n",
     "        self.conv2 = nn.Conv2d(in_channels=12, out_channels=64, kernel_size=5, stride=1, padding=1)\n",
-    "        self.lif2 = LIF(spike_fn=spike_fn, alpha=alpha, beta=beta)\n",
+    "        self.lif2 = snn.Stein(alpha=alpha, beta=beta, spike_grad=spike_grad)\n",
     "        self.fc2 = nn.Linear(64*5*5, 10)\n",
-    "        self.lif3 = LIF(spike_fn=spike_fn, alpha=alpha, beta=beta)\n",
+    "        self.lif3 = snn.Stein(alpha=alpha, beta=beta, spike_grad=spike_grad)\n",
     "\n",
     "    def forward(self, x):\n",
     "        # Initialize LIF state variables and spike output tensors\n",
@@ -296,7 +304,7 @@
    "cell_type": "markdown",
    "source": [
     "## 3. Training\n",
-    "Time for training! Let's first define a couple of functions to print out test/train accuracy for each minibatch."
+    "Time for training! Let's first define a couple of functions to print out test/train accuracy."
    ],
    "metadata": {
     "collapsed": false,
@@ -312,10 +320,10 @@
    "source": [
     "def print_batch_accuracy(data, targets, train=False):\n",
     "    output, _ = net(data.view(batch_size, 1, 28, 28))\n",
-    "    _, am = output.sum(dim=0).max(1)\n",
-    "    acc = np.mean((targets == am). detach().cpu().numpy())\n",
+    "    _, idx = output.sum(dim=0).max(1)\n",
+    "    acc = np.mean((targets == idx).detach().cpu().numpy())\n",
     "\n",
-    "    if train is True:\n",
+    "    if train:\n",
     "        print(f\"Train Set Accuracy: {acc}\")\n",
     "    else:\n",
     "        print(f\"Test Set Accuracy: {acc}\")\n",
@@ -339,10 +347,12 @@
    "cell_type": "markdown",
    "source": [
     "### 3.1 Optimizer & Loss\n",
-    "We'll apply the softmax function to the membrane potentials of the output layer in calculating a negative log-likelihood loss.\n",
-    "The Adam optimizer is used for weight updates.\n",
-    "\n",
-    "Accuracy is measured by counting the spikes of the output neurons. The neuron that fires the most frequently will be our predicted class."
+    "* *Output Activation*: We'll apply the softmax function to the membrane potentials of the output layer, rather than the spikes.\n",
+    "* *Loss*: This will then be used to calculate the negative log-likelihood loss.\n",
+    "By encouraging the membrane of the correct neuron class to reach the threshold, we expect that neuron will fire more frequently.\n",
+    "The loss could be applied to the spike count as well, but the membrane is  continuous whereas spike count is discrete.\n",
+    "* *Optimizer*: The Adam optimizer is used for weight updates.\n",
+    "* *Accuracy*: Accuracy is measured by counting the spikes of the output neurons. The neuron that fires the most frequently will be our predicted class."
    ],
    "metadata": {
     "collapsed": false,
@@ -487,7 +497,8 @@
   {
    "cell_type": "markdown",
    "source": [
-    "### 4.2 Test Set Accuracy"
+    "### 4.2 Test Set Accuracy\n",
+    "This function just iterates over all minibatches to obtain a measure of accuracy over the full 10,000 samples in the test set."
    ],
    "metadata": {
     "collapsed": false,
@@ -541,7 +552,7 @@
   {
    "cell_type": "markdown",
    "source": [
-    "Voila! That's it for static MNIST."
+    "That's it for static MNIST!"
    ],
    "metadata": {
     "collapsed": false,
@@ -554,7 +565,8 @@
    "cell_type": "markdown",
    "source": [
     "## 5. Spiking MNIST\n",
-    "Part of the appeal of SNNs is their ability to handle time-varying spiking data. So let's use rate-coding to convert MNIST into spiking MNIST using the `spikegen` module in Tutorial 1, and train our network with that instead."
+    "As before, there isn't anything all that impressive about training a network on the static MNIST dataset.\n",
+    "So let's apply rate-coding to convert it into a time-varying stream of spikes."
    ],
    "metadata": {
     "collapsed": false,
@@ -597,7 +609,7 @@
   {
    "cell_type": "code",
    "source": [
-    "!pip install celluloid # animating matplotlib plots made easy"
+    "!pip install celluloid # matplotlib animations made easy"
    ],
    "metadata": {
     "collapsed": false,
@@ -629,7 +641,20 @@
     "\n",
     "# interval=40 specifies 40ms delay between frames\n",
     "a = camera.animate(interval=40)\n",
-    "HTML(a.to_html5_video())\n",
+    "HTML(a.to_html5_video())"
+   ],
+   "metadata": {
+    "collapsed": false,
+    "pycharm": {
+     "name": "#%%\n"
+    }
+   }
+  },
+  {
+   "cell_type": "code",
+   "execution_count": null,
+   "outputs": [],
+   "source": [
     "print(spike_targets[0])"
    ],
    "metadata": {
@@ -643,7 +668,7 @@
    "cell_type": "markdown",
    "source": [
     "## 6. Define Network\n",
-    "The network is the same as before. The one difference is that the for-loop iterates through the first dimension of the input:\n",
+    "The convolutional network is the same as before. The one difference is that the for-loop iterates through the first dimension of the input:\n",
     "`cur1 = F.max_pool2d(self.conv1(x[step]), 2)`"
    ],
    "metadata": {
@@ -658,8 +683,8 @@
    "execution_count": null,
    "outputs": [],
    "source": [
-    "spike_fn = FSS.apply\n",
-    "snn.neuron.slope = 50\n",
+    "spike_grad = snn.FastSigmoidSurrogate.apply\n",
+    "snn.slope = 50\n",
     "\n",
     "# Define a different network\n",
     "class Net(nn.Module):\n",
@@ -668,11 +693,11 @@
     "\n",
     "        # initialize layers\n",
     "        self.conv1 = nn.Conv2d(in_channels=1, out_channels=12, kernel_size=5, stride=1, padding=1)\n",
-    "        self.lif1 = LIF(spike_fn=spike_fn, alpha=alpha, beta=beta)\n",
+    "        self.lif1 = snn.Stein(alpha=alpha, beta=beta, spike_grad=spike_grad)\n",
     "        self.conv2 = nn.Conv2d(in_channels=12, out_channels=64, kernel_size=5, stride=1, padding=1)\n",
-    "        self.lif2 = LIF(spike_fn=spike_fn, alpha=alpha, beta=beta)\n",
+    "        self.lif1 = snn.Stein(alpha=alpha, beta=beta, spike_grad=spike_grad)\n",
     "        self.fc2 = nn.Linear(64*5*5, 10)\n",
-    "        self.lif3 = LIF(spike_fn=spike_fn, alpha=alpha, beta=beta)\n",
+    "        self.lif1 = snn.Stein(alpha=alpha, beta=beta, spike_grad=spike_grad)\n",
     "\n",
     "        # self.conv1 = nn.Conv2d(in_channels=1, out_channels=3, kernel_size=3, stride=1, padding=0)\n",
     "        # self.lif1 = LIF(spike_fn=spike_fn, alpha=alpha, beta=beta)\n",
@@ -681,9 +706,9 @@
     "\n",
     "    def forward(self, x):\n",
     "        # Initialize LIF state variables and spike output tensors\n",
-    "        spk1, syn1, mem1 = self.lif1.init_hidden(batch_size, 12, 13, 13)\n",
-    "        spk2, syn2, mem2 = self.lif1.init_hidden(batch_size, 64, 5, 5)\n",
-    "        spk3, syn3, mem3 = self.lif2.init_hidden(batch_size, 10)\n",
+    "        spk1, syn1, mem1 = self.lif1.init_stein(batch_size, 12, 13, 13)\n",
+    "        spk2, syn2, mem2 = self.lif1.init_stein(batch_size, 64, 5, 5)\n",
+    "        spk3, syn3, mem3 = self.lif2.init_stein(batch_size, 10)\n",
     "\n",
     "        spk3_rec = []\n",
     "        mem3_rec = []\n",
@@ -728,14 +753,12 @@
    "execution_count": null,
    "outputs": [],
    "source": [
-    "# Print batch accuracy function\n",
-    "\n",
     "def print_batch_accuracy(data, targets, train=False):\n",
     "    output, _ = net(data.view(num_steps, batch_size, 1, 28, 28))\n",
-    "    _, am = output.sum(dim=0).max(1)\n",
-    "    acc = np.mean((targets == am). detach().cpu().numpy())\n",
+    "    _, idx = output.sum(dim=0).max(1)\n",
+    "    acc = np.mean((targets == idx).detach().cpu().numpy())\n",
     "\n",
-    "    if train is True:\n",
+    "    if train:\n",
     "        print(f\"Train Set Accuracy: {acc}\")\n",
     "    else:\n",
     "        print(f\"Test Set Accuracy: {acc}\")\n",
@@ -787,7 +810,8 @@
   {
    "cell_type": "markdown",
    "source": [
-    "### 7.2 Training Loop"
+    "### 7.2 Training Loop\n",
+    "The training loop is identical to the static MNIST case, but we pass each minibatch through `spikegen.rate` before running it through the feedforward network."
    ],
    "metadata": {
     "collapsed": false,
@@ -822,7 +846,7 @@
     "        # Forward pass\n",
     "        output, mem_rec = net(spike_data.view(num_steps, batch_size, 1, 28, 28))\n",
     "        log_p_y = log_softmax_fn(mem_rec)\n",
-    "        loss_val = torch.zeros((1), dtype=dtype, device=device)\n",
+    "        loss_val = torch.zeros(1, dtype=dtype, device=device)\n",
     "\n",
     "        # Sum loss over time steps to perform BPTT\n",
     "        for step in range(num_steps):\n",
@@ -965,8 +989,9 @@
   {
    "cell_type": "markdown",
    "source": [
-    "Professor Lu has kidnapped my daughter and won't return her until I hit 99.99% accuracy, please help\n",
-    "-JE"
+    "To-do:\n",
+    "* Add figures to explain in better detail\n",
+    "* See if SRM0 model can reach acceptable acc within reasonable num_steps\n"
    ],
    "metadata": {
     "collapsed": false
@@ -975,9 +1000,9 @@
  ],
  "metadata": {
   "kernelspec": {
-   "name": "pycharm-78d010ff",
+   "name": "python3",
    "language": "python",
-   "display_name": "PyCharm (snntorch)"
+   "display_name": "Python 3"
   },
   "language_info": {
    "codemirror_mode": {
diff --git a/setup.py b/setup.py
index 994ea8d3..1ecc4bb0 100644
--- a/setup.py
+++ b/setup.py
@@ -5,11 +5,11 @@
 
 setuptools.setup(
     name="snntorch",
-    version="0.0.4",
+    version="0.0.7",
     author="Jason K. Eshraghian",
     author_email="jasonesh@umich.edu",
     description="Deep learning with spiking neural networks",
-    long_description=long_description, # loaded from README.md
+    long_description=long_description,  # loaded from README.md
     long_description_content_type="text/markdown",
     url="https://github.com/jeshraghian/snntorch",
     packages=setuptools.find_packages(),
diff --git a/snntorch/__init__.py b/snntorch/__init__.py
index 2bc5416c..de5e5368 100644
--- a/snntorch/__init__.py
+++ b/snntorch/__init__.py
@@ -1,1 +1,199 @@
-# from snntorch import utils
\ No newline at end of file
+import torch
+import torch.nn as nn
+import numpy as np
+
+
+device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
+dtype = torch.float
+slope = 25
+
+
+class LIF(nn.Module):
+    """Parent class for leaky integrate and fire neuron models."""
+    def __init__(self, alpha, beta, threshold=1.0, spike_grad=None):
+        super(LIF, self).__init__()
+
+        self.alpha = alpha
+        self.beta = beta
+        self.threshold = threshold
+
+        if spike_grad is None:
+            self.spike_grad = self.Heaviside.apply
+        else:
+            self.spike_grad = spike_grad
+
+    def fire(self, mem):
+        mem_shift = mem - self.threshold
+        spk = self.spike_grad(mem_shift).to(device)
+        reset = torch.zeros_like(mem)
+        spk_idx = (mem_shift > 0)
+        reset[spk_idx] = torch.ones_like(mem)[spk_idx]
+        return spk, reset
+
+
+    @staticmethod
+    def init_stein(batch_size, *args):
+        """Used to initialize syn, mem and spk.
+        *args are the input feature dimensions.
+        E.g., batch_size=128 and input feature of size=1x28x28 would require init_hidden(128, 1, 28, 28)."""
+        syn = torch.zeros((batch_size, *args), device=device, dtype=dtype)
+        mem = torch.zeros((batch_size, *args), device=device, dtype=dtype)
+        spk = torch.zeros((batch_size, *args), device=device, dtype=dtype)
+
+        return spk, syn, mem
+
+    @staticmethod
+    def init_srm0(batch_size, *args):
+        """Used to initialize syn_pre, syn_post, mem and spk.
+        *args are the input feature dimensions.
+        E.g., batch_size=128 and input feature of size=1x28x28 would require init_hidden(128, 1, 28, 28)."""
+        syn_pre = torch.zeros((batch_size, *args), device=device, dtype=dtype)
+        syn_post = torch.zeros((batch_size, *args), device=device, dtype=dtype)
+        mem = torch.zeros((batch_size, *args), device=device, dtype=dtype)
+        spk = torch.zeros((batch_size, *args), device=device, dtype=dtype)
+
+        return spk, syn_pre, syn_post, mem
+
+    @staticmethod
+    class Heaviside(torch.autograd.Function):
+        """Default and non-approximate spiking function for neuron.
+        Forward pass: Heaviside step function.
+        Backward pass: Dirac Delta clipped to 1 at x>0 instead of inf at x=1.
+        This assumption holds true on the basis that a spike occurs as long as x>0 and the following time step incurs a reset."""
+
+        @staticmethod
+        def forward(ctx, input_):
+            ctx.save_for_backward(input_)
+            out = torch.zeros_like(input_)
+            out[input_ > 0] = 1.0
+            return out
+
+        @staticmethod
+        def backward(ctx, grad_output):
+            input_, = ctx.saved_tensors
+            grad_input = grad_output.clone()
+            grad_input[input_ < 0] = 0.0
+            grad = grad_input
+            return grad
+
+
+
+# Neuron Models
+
+class Stein(LIF):
+    """
+    Stein's model of the leaky integrate and fire neuron.
+    The synaptic current jumps upon spike arrival, which causes a jump in membrane potential.
+    Synaptic current and membrane potential decay exponentially with rates of alpha and beta, respectively.
+
+    For further reading, see:
+    R. B. Stein (1965) A theoretical analysis of neuron variability. Biophys. J. 5, pp. 173-194.
+    R. B. Stein (1967) Some models of neuronal variability. Biophys. J. 7. pp. 37-68."""
+
+    def __init__(self, alpha, beta, threshold=1.0, spike_grad=None):
+        super(Stein, self).__init__(alpha, beta, threshold, spike_grad)
+
+    def forward(self, input_, syn, mem):
+        spk, reset = self.fire(mem)
+        syn = self.alpha * syn + input_
+        mem = self.beta * mem + syn - reset
+
+        return spk, syn, mem
+
+
+class SRM0(LIF):
+    """
+    Simplified Spike Response Model of the leaky integrate and fire neuron.
+    The time course of the membrane potential response depends on a combination of exponentials.
+    In this case, the change in membrane potential experiences a delay.
+    This can be interpreted as the input current taking on its own exponential shape as a result of an input spike train.
+    For excitatory spiking, ensure alpha > beta.
+
+    For further reading, see:
+    R. Jovilet, J. Timothy, W. Gerstner (2003) The spike response model: A framework to predict neuronal spike trains. Artificial Neural Networks and Neural Information Processing, pp. 846-853.
+    """
+    def __init__(self, alpha, beta, threshold=1.0, spike_grad=None):
+        super(SRM0, self).__init__(alpha, beta, threshold, spike_grad)
+        self.tau_srm = np.log(self.alpha) / (np.log(self.beta) - np.log(self.alpha)) + 1
+        if self.alpha <= self.beta:
+            raise ValueError("alpha must be greater than beta.")
+
+    def forward(self, input_, syn_pre, syn_post, mem):
+        spk, reset = self.fire(mem)
+        syn_pre = (self.alpha * syn_pre + input_) * (1 - reset)
+        syn_post = (self.beta * syn_post - input_) * (1 - reset)
+        mem = self.tau_srm * (syn_pre + syn_post)*(1-reset) + (mem*reset - reset)
+
+    # cool forward function that resulted in burst firing - worth exploring
+
+    # def forward(self, input_, syn_pre, syn_post, mem):
+    #     mem_shift = mem - self.threshold
+    #     spk = self.spike_grad(mem_shift).to(device)
+    #     reset = torch.zeros_like(mem)
+    #     spk_idx = (mem_shift > 0)
+    #     reset[spk_idx] = torch.ones_like(mem)[spk_idx]
+    #
+    #     syn_pre = self.alpha * syn_pre + input_
+    #     syn_post = self.beta * syn_post - input_
+    #     mem = self.tau_srm * (syn_pre + syn_post) - reset
+
+        return spk, syn_pre, syn_post, mem
+
+
+# Spike-gradient functions
+
+
+class FastSigmoidSurrogate(torch.autograd.Function):
+    """
+    Surrogate gradient of the Heaviside step function.
+    Forward pass: Heaviside step function.
+    Backward pass: Gradient of fast sigmoid function.
+
+    f(x) = x / (1 + abs(x))
+    f'(x) = 1 / ([1+slope*abs(x)]^2)
+
+    Adapted from Zenke & Ganguli (2018).
+    """
+
+    @staticmethod
+    def forward(ctx, input_):
+        ctx.save_for_backward(input_)
+        out = torch.zeros_like(input_)
+        out[input_ > 0] = 1.0
+        return out
+
+    @staticmethod
+    def backward(ctx, grad_output):
+        input_, = ctx.saved_tensors
+        grad_input = grad_output.clone()
+        grad = grad_input / (slope * torch.abs(input_) + 1.0) ** 2
+        return grad
+
+
+class SigmoidSurrogate(torch.autograd.Function):
+    """
+    Surrogate gradient of the Heaviside step function.
+    Forward pass: Heaviside step function.
+    Backward pass: Gradient of sigmoid function.
+
+    f(x) = 1 / (1 + exp(-slope * x)
+    f'(x) = slope*exp(-slope*x) / ((exp(-A*x)+1)^2)
+    """
+
+    @staticmethod
+    def forward(ctx, input_):
+        ctx.save_for_backward(input_)
+        out = torch.zeros_like(input_)
+        out[input_ > 0] = 1.0
+        return out
+
+    @staticmethod
+    def backward(ctx, grad_output):
+        input_, = ctx.saved_tensors
+        grad_input = grad_output.clone()
+        grad = grad_input * slope * torch.exp(-slope*input_) / ((torch.exp(-slope*input_)+1) ** 2)
+        return grad
+
+# boltzmann func
+# piecewise linear func
+# tanh surrogate func
\ No newline at end of file
diff --git a/snntorch/neuron.py b/snntorch/neuron.py
deleted file mode 100644
index eba115e9..00000000
--- a/snntorch/neuron.py
+++ /dev/null
@@ -1,55 +0,0 @@
-import torch
-import torch.nn as nn
-
-device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
-dtype = torch.float
-slope = 25
-
-class LIF(nn.Module):
-    """Based on F. Zenke's code (2020)"""
-    def __init__(self, spike_fn, alpha, beta):
-        super(LIF, self).__init__()
-        self.spike_fn = spike_fn
-        self.alpha = alpha
-        self.beta = beta
-
-    def forward(self, input, syn, mem):
-        mthr = mem - 1.0
-        spk = self.spike_fn(mthr).to(device)
-        rst = torch.zeros_like(mem)
-        c = (mthr > 0)
-        rst[c] = torch.ones_like(mem)[c]
-
-        syn = self.alpha*syn + input
-        mem = self.beta*mem + syn - rst
-        return spk, syn, mem
-
-    @staticmethod
-    def init_hidden(batch_size, *args):
-        """Used to initialize syn, mem and spk. *args are the input feature dimensions.
-        E.g., batch_size=128 and input feature of size=1x28x28 would call init_hidden(128, 1, 28, 28)."""
-        syn = torch.zeros((batch_size, *args), device=device, dtype=dtype)
-        mem = torch.zeros((batch_size, *args), device=device, dtype=dtype)
-        spk = torch.zeros((batch_size, *args), device=device, dtype=dtype)
-
-        return spk, syn, mem
-
-
-class FastSimgoidSurrogate(torch.autograd.Function):
-    """
-    Adapted from Zenke & Ganguli (2018).
-    """
-
-    @staticmethod
-    def forward(ctx, input):
-        ctx.save_for_backward(input)
-        out = torch.zeros_like(input)
-        out[input > 0] = 1.0
-        return out
-
-    @staticmethod
-    def backward(ctx, grad_output):
-        input, = ctx.saved_tensors
-        grad_input = grad_output.clone()
-        grad = grad_input / (slope * torch.abs(input) + 1.0) ** 2
-        return grad
diff --git a/snntorch/spikegen.py b/snntorch/spikegen.py
index 5fce40fa..7611e08d 100644
--- a/snntorch/spikegen.py
+++ b/snntorch/spikegen.py
@@ -1,6 +1,6 @@
 import torch
 device = torch.device("cuda") if torch.cuda.is_available() else torch.device("cpu")
-
+dtype = torch.float
 
 def rate(data, targets=False, num_outputs=None, num_steps=1, gain=1, offset=0, convert_targets=False,
          temporal_targets=False):
@@ -35,11 +35,11 @@ def rate(data, targets=False, num_outputs=None, num_steps=1, gain=1, offset=0, c
                     one-hot encoding of targets with time optionally in the first dimension.
                """
 
-    if convert_targets is True:
+    if convert_targets:
         # One-hot encoding of targets and repeat it along the first dimension, i.e., time first.
         spike_targets = targets_to_spikes(targets, num_outputs, num_steps, temporal_targets)
 
-    elif convert_targets is False and temporal_targets is True:
+    elif not convert_targets and temporal_targets:
         # Repeat tensor in the first dimension without converting targets to one-hot.
         spike_targets = targets.repeat(tuple([num_steps] + torch.ones(len(targets.size()), dtype=int).tolist()))
 
@@ -98,11 +98,11 @@ def latency(data, targets=False, num_outputs=None, convert_targets=False, tempor
                     one-hot encoding of targets with time optionally in the first dimension.
                """
 
-    if convert_targets is True:
+    if convert_targets:
         # One-hot encoding of targets and repeat it along the first dimension, i.e., time first.
         spike_targets = targets_to_spikes(targets, num_outputs, num_steps, temporal_targets)
 
-    elif convert_targets is False and temporal_targets is True:
+    elif not convert_targets and temporal_targets:
         # Repeat tensor in the first dimension without converting targets to one-hot.
         spike_targets = targets.repeat(tuple([num_steps] + torch.ones(len(targets.size()), dtype=int).tolist()))
 
@@ -172,7 +172,7 @@ def latency_conv(data, num_steps=False, threshold=0, epsilon=1e-7, tau=1, clip=F
     spike_time, idx = latency_code(data, num_steps=num_steps, threshold=threshold, epsilon=epsilon, tau=tau,
                                    normalize=normalize, linear=linear)
 
-    spike_data = torch.zeros((tuple([num_steps] + list(spike_time.size()))))
+    spike_data = torch.zeros((tuple([num_steps] + list(spike_time.size()))), dtype=dtype, device=device)
     clamp_flag = 0
     print_flag = True
 
@@ -181,7 +181,7 @@ def latency_conv(data, num_steps=False, threshold=0, epsilon=1e-7, tau=1, clip=F
             spike_data = spike_data.scatter(0, torch.round(spike_time).long().unsqueeze(0), 1)
             break
         except RuntimeError: # this block runs if indexes in spike_time exceed range in num_steps.
-            if print_flag is True:
+            if print_flag:
                 print("Warning: the spikes outside of the range of num_steps have been clipped.\n "
                       "Setting ``normalize`` to ``True`` or increasing ``num_steps`` can fix this.")
                 print_flag = False
@@ -193,7 +193,7 @@ def latency_conv(data, num_steps=False, threshold=0, epsilon=1e-7, tau=1, clip=F
         spike_data[-1] = 0
 
     # Use idx to remove spikes below the threshold
-    if clip is True:
+    if clip:
         spike_data = spike_data * ~idx  # idx is broadcast in T direction
 
     return spike_data
@@ -236,20 +236,20 @@ def latency_code(data, num_steps=False, threshold=0.01, epsilon=1e-7, tau=1, nor
     if tau <= 0:  # double check if this can just be threshold < 0 instead.
         raise Exception("``tau`` must be greater than 0.")
 
-    if normalize is True and num_steps is False:
+    if normalize and not num_steps:
         raise Exception("`num_steps` should not be empty if normalize is set to True.")
 
     idx = data < threshold
 
-    if linear is False:
+    if not linear:
         data = torch.clamp(data, threshold + epsilon)  # saturates all values below threshold.
         spike_time = tau * torch.log(data / (data - threshold))
-        if normalize is True:
+        if normalize:
             tmax = torch.Tensor([(threshold + epsilon) / epsilon])
             spike_time = spike_time * (num_steps-1) / (tau * torch.log(tmax))
 
-    elif linear is True:
-        if normalize is True:
+    elif linear:
+        if normalize:
             tau = num_steps-1
         spike_time = -tau * (data - 1)
         spike_time = torch.clamp_max(spike_time, (-tau * (threshold - 1)))
@@ -287,7 +287,7 @@ def targets_to_spikes(targets, num_outputs=None, num_steps=1, temporal_targets=F
 
     targets_1h = to_one_hot(targets, num_outputs)
 
-    if temporal_targets is not False:
+    if temporal_targets:
         # Extend one-hot targets in time dimension. Create a new axis in the second dimension.
         # Allocate first dim to batch size, and subtract it off len(targets_1h.size())
         spike_targets = targets_1h.repeat(tuple([num_steps] + torch.ones(len(targets_1h.size()), dtype=int).tolist()))


Test patch:
diff --git a/test_1.py b/test_1.py
index 145c3f56..022d0b2e 100644
--- a/test_1.py
+++ b/test_1.py
@@ -1,4 +1,4 @@
-from snntorch import spikegen
+from snntorch import spikegen, neuron
 import torch
 from torch.utils.data import DataLoader
 from torchvision import datasets, transforms
@@ -48,17 +48,12 @@
 data = iter(train_loader)
 data_it, targets_it = next(data)
 
-spike_train, spike_targets = spikegen.latency(data_it, targets_it, num_steps=50, tau=10, threshold=0.1, clip=True,
-                                              normalize=False, linear=False)
+spike_train, spike_targets = spikegen.rate(data_it, targets_it, num_steps=50, num_outputs=10)
 
-fig = plt.figure(facecolor="w", figsize=(10, 5))
-ax = fig.add_subplot(111)
+# test heaviside auto
+lif = neuron.Stein(alpha=0.5, beta=0.5)
+spk, syn, mem = neuron.LIF.init_hidden(1)
 
-ax.scatter(*torch.where(spike_train[:, 0].view(50, -1).cpu()), s=25, c="black")
-
-# print(torch.where(spike_train[:, 0].view(25, -1)))
-
-plt.title("Input Layer")
-plt.xlabel("Time step")
-plt.ylabel("Neuron Number")
-plt.show()
+# generate input
+spk_in = torch.zeros(num_steps, device=device, dtype=dtype)
+spk_in[5:7] = 0.51
\ No newline at end of file