{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Binary Events and Event-Driven Operations\n",
    "\n",
    "This tutorial develops the event side of BrainEvent: create binary event arrays, inspect them, multiply them by data, and process a complete time series without a Python time-step loop.\n",
    "\n",
    "## Creating Binary Events\n",
    "\n",
    "### Creating Events from Array-Like Inputs"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import brainevent\n",
    "import brainstate\n",
    "import jax\n",
    "import jax.numpy as jnp\n",
    "import numpy as np\n",
    "\n",
    "from_list = brainevent.BinaryArray([1, 0, 1, 0])\n",
    "from_numpy = brainevent.BinaryArray(np.array([True, False, True]))\n",
    "from_jax = brainevent.BinaryArray(jnp.array([False, True, True]))\n",
    "print(from_list)\n",
    "print(from_numpy)\n",
    "print(from_jax)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Representing Simulated Spikes"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "brainstate.random.seed(11)\n",
    "spike_values = brainstate.random.bernoulli(0.25, size=(12,))\n",
    "spikes = brainevent.BinaryArray(spike_values)\n",
    "print(spikes)\n",
    "print(\"active events:\", int(spike_values.sum()))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Inspecting and Transforming Binary Events\n",
    "\n",
    "### Indexing"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "events_2d = brainevent.BinaryArray([[1, 0, 1], [0, 1, 0]])\n",
    "print(\"first row:\", events_2d[0])\n",
    "print(\"last two columns:\", events_2d[:, 1:])"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Reductions and Logical Operations"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "event_a = brainevent.BinaryArray([1, 0, 1, 0])\n",
    "event_b = brainevent.BinaryArray([1, 1, 0, 0])\n",
    "print(\"events per row:\", jnp.sum(events_2d.value, axis=1))\n",
    "print(\"A AND B:\", jnp.logical_and(event_a.value, event_b.value))\n",
    "print(\"A OR B:\", jnp.logical_or(event_a.value, event_b.value))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Event-Driven Matrix Multiplication\n",
    "\n",
    "### Binary Events with Dense Data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "pre_spikes = brainevent.BinaryArray([1, 0, 1, 0, 1])\n",
    "weights = jnp.array([\n",
    "    [0.5, 0.2, 0.1],\n",
    "    [0.3, 0.4, 0.2],\n",
    "    [0.1, 0.5, 0.3],\n",
    "    [0.2, 0.1, 0.4],\n",
    "    [0.4, 0.3, 0.5],\n",
    "])\n",
    "post_input = jax.block_until_ready(pre_spikes @ weights)\n",
    "print(post_input)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Correctness and Performance Comparison\n",
    "\n",
    "The event-driven result must first match ordinary dense multiplication. Timing is a separate question: warm up compiled work, synchronize every measured result, and report the backend, shapes, event density, and repetition count before interpreting a speed difference."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "dense_spikes = jnp.array([1, 0, 1, 0, 1], dtype=weights.dtype)\n",
    "dense_result = jax.block_until_ready(dense_spikes @ weights)\n",
    "event_result = jax.block_until_ready(pre_spikes @ weights)\n",
    "print(\"results match:\", bool(jnp.allclose(event_result, dense_result)))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## A Small Event-Driven Feedforward Network"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "brainstate.random.seed(19)\n",
    "w1 = brainstate.random.normal(size=(5, 4)) * 0.2\n",
    "w2 = brainstate.random.normal(size=(4, 2)) * 0.2\n",
    "hidden_drive = pre_spikes @ w1\n",
    "hidden_events = brainevent.BinaryArray(hidden_drive > 0.15)\n",
    "network_output = jax.block_until_ready(hidden_events @ w2)\n",
    "print(\"hidden events:\", hidden_events)\n",
    "print(\"network output:\", network_output)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Processing Time-Series Events\n",
    "\n",
    "`BinaryArray` accepts a two-dimensional event matrix, so the time axis can be processed in one compiled matrix operation rather than a Python loop."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "brainstate.random.seed(23)\n",
    "spike_trains = brainstate.random.bernoulli(0.1, size=(40, 12))\n",
    "brainstate.random.seed(29)\n",
    "readout_weights = brainstate.random.normal(size=(12, 3)) * 0.1\n",
    "time_series_output = jax.block_until_ready(\n",
    "    brainevent.BinaryArray(spike_trains) @ readout_weights\n",
    ")\n",
    "print(\"input shape:\", spike_trains.shape)\n",
    "print(\"output shape:\", time_series_output.shape)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Summary and Next Steps\n",
    "\n",
    "`BinaryArray` represents vector or batched binary events and composes with dense data and JAX synchronization. Continue with [Event-Driven Synaptic Plasticity](synaptic-plasticity.ipynb) for event-triggered weight updates, or move to [Data](../data-structures/index.rst) to choose a connectivity representation."
   ]
  }
 ],
 "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
}
