{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Event-Driven Synaptic Plasticity\n",
    "\n",
    "This tutorial shows how spike events select sparse synapses for weight updates. It implements a minimal pair-based STDP example; it does not claim that this rule captures the full biological diversity of synaptic plasticity.\n",
    "\n",
    "## Contents\n",
    "\n",
    "1. From Spike Events to Synaptic Updates\n",
    "2. Implement a Minimal STDP Rule\n",
    "3. Visualize the Learning Window\n",
    "4. Update CSR Weights from Pre- and Postsynaptic Events\n",
    "5. Apply Event-Driven Updates in a Network\n",
    "6. Summary and Next Steps\n",
    "\n",
    "## From Spike Events to Synaptic Updates\n",
    "\n",
    "### Hebb's Rule\n",
    "\n",
    "Hebbian ideas motivate activity-dependent weight changes, but a concrete implementation requires an explicit update rule, state variables, bounds, and a timing convention.\n",
    "\n",
    "### Spike-Timing-Dependent Plasticity\n",
    "\n",
    "Pair-based STDP uses decaying pre- and postsynaptic traces as summaries of recent events. A spike on one side triggers an update determined by the trace on the other side.\n",
    "\n",
    "### The Update Rule\n",
    "\n",
    "For an existing synapse from presynaptic neuron $i$ to postsynaptic neuron $j$, a pre-triggered update adds a scaled postsynaptic trace; a post-triggered update adds a scaled presynaptic trace. Signs determine potentiation or depression, and clipping enforces weight bounds.\n",
    "\n",
    "### BrainEvent Update Operations\n",
    "\n",
    "`update_csr_on_binary_pre` traverses outgoing CSR entries selected by presynaptic events. `update_csr_on_binary_post` uses a CSC index view plus a permutation back to CSR data order to traverse incoming entries selected by postsynaptic events."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import brainevent\n",
    "import jax\n",
    "import jax.numpy as jnp\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Implement a Minimal STDP Rule"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "tau_pre = 20.0\n",
    "tau_post = 20.0\n",
    "a_plus = 0.01\n",
    "a_minus = 0.012\n",
    "delta_t = jnp.linspace(-50.0, 50.0, 401)\n",
    "learning_window = jnp.where(\n",
    "    delta_t > 0,\n",
    "    a_plus * jnp.exp(-delta_t / tau_post),\n",
    "    -a_minus * jnp.exp(delta_t / tau_pre),\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Visualize the Learning Window"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig, ax = plt.subplots(figsize=(7, 3))\n",
    "ax.plot(np.asarray(delta_t), np.asarray(learning_window))\n",
    "ax.axhline(0.0, color=\"black\", linewidth=0.8)\n",
    "ax.axvline(0.0, color=\"black\", linewidth=0.8)\n",
    "ax.set(xlabel=\"relative event time\", ylabel=\"weight change\", title=\"Illustrative pair-based STDP window\")\n",
    "plt.tight_layout()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Update CSR Weights from Pre- and Postsynaptic Events"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dense_weights = jnp.array([\n",
    "    [0.20, 0.00, 0.40],\n",
    "    [0.00, 0.30, 0.00],\n",
    "], dtype=jnp.float32)\n",
    "csr = brainevent.CSR.fromdense(dense_weights)\n",
    "\n",
    "pre_spike = jnp.array([True, False])\n",
    "post_trace = jnp.array([0.01, 0.02, 0.03])\n",
    "after_pre = brainevent.update_csr_on_binary_pre(\n",
    "    csr.data, csr.indices, csr.indptr, pre_spike, post_trace,\n",
    "    0.0, 1.0, shape=csr.shape,\n",
    ")\n",
    "\n",
    "csc_indptr, csc_indices, weight_indices = brainevent.csr_to_csc_index(\n",
    "    csr.indptr, csr.indices, shape=csr.shape\n",
    ")\n",
    "post_spike = jnp.array([False, True, True])\n",
    "pre_trace = jnp.array([-0.01, -0.02])\n",
    "after_post = brainevent.update_csr_on_binary_post(\n",
    "    after_pre, csc_indices, csc_indptr, weight_indices, pre_trace, post_spike,\n",
    "    0.0, 1.0, shape=csr.shape,\n",
    ")\n",
    "after_post = jax.block_until_ready(after_post)\n",
    "print(\"initial CSR data:\", csr.data)\n",
    "print(\"after pre-triggered update:\", after_pre)\n",
    "print(\"after post-triggered update:\", after_post)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Apply Event-Driven Updates in a Network\n",
    "\n",
    "The sparsity pattern is unchanged; only its stored weights change. The updated data can therefore be placed back into the same CSR structure and used immediately by an event-driven forward pass."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "updated_csr = csr.with_data(after_post)\n",
    "input_events = brainevent.BinaryArray(jnp.array([True, False]))\n",
    "output = jax.block_until_ready(input_events @ updated_csr)\n",
    "print(\"updated dense weights:\\n\", updated_csr.todense())\n",
    "print(\"network output:\", output)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Summary and Next Steps\n",
    "\n",
    "BrainEvent separates the event trigger from the sparse connectivity data: pre- and postsynaptic events select which stored CSR weights receive trace-based updates. For storage details, continue with [CSR and CSC Sparse Matrices](../data-structures/02_sparse_matrices.ipynb); for task-oriented operator selection, see [Apply event-driven synaptic plasticity](../../how-to/data-structures/synaptic-plasticity.rst)."
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "name": "python",
   "version": "3.11"
  },
  "mystnb": {
   "execution_mode": "force",
   "execution_timeout": 120
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
