{"id":5003,"date":"2026-09-23T13:26:58","date_gmt":"2026-09-23T17:26:58","guid":{"rendered":"https:\/\/faculty.fiu.edu\/~theobald\/?page_id=5003"},"modified":"2026-09-23T13:33:55","modified_gmt":"2026-09-23T17:33:55","slug":"synaptic_integration","status":"publish","type":"page","link":"https:\/\/faculty.fiu.edu\/~theobald\/fun\/neurobiology-fun\/synaptic_integration\/","title":{"rendered":"synaptic_integration"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"5003\" class=\"elementor elementor-5003\">\n\t\t\t\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-3d199cb elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"3d199cb\" data-element_type=\"section\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-70b4b97\" data-id=\"70b4b97\" data-element_type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t\t\t<div class=\"elementor-element elementor-element-e5c9af6 elementor-widget elementor-widget-html\" data-id=\"e5c9af6\" data-element_type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\" \/>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" \/>\n  <title>Synaptic summation (HH)<\/title>\n  <style>\n    :root{\n      --bg:#0f1115;\n      --panel:#141823;\n      --panel2:#10141d;\n      --fg:#e6e8ee;\n      --muted:#aeb4c2;\n      --grid:#2a2f3a;\n      --accent:#7aa2ff;\n    }\n    html,body{\n      height:100%;\n      margin:0;\n      background:var(--bg);\n      color:var(--fg);\n      font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial,sans-serif;\n    }\n    .wrap{\n      display:grid;\n      grid-template-columns:320px 1fr;\n      gap:12px;\n      padding:12px;\n      box-sizing:border-box;\n      height:100%;\n    }\n    .main{\n      display:grid;\n      grid-template-rows:1fr 260px;\n      gap:12px;\n      min-width:0;\n      min-height:0;\n    }\n    .panel{\n      background:var(--panel);\n      border:1px solid #1f2530;\n      border-radius:12px;\n      padding:12px;\n      box-sizing:border-box;\n    }\n    .scene{\n      background:var(--panel2);\n      border:1px solid #1f2530;\n      border-radius:12px;\n      overflow:hidden;\n      position:relative;\n    }\n    canvas{\n      display:block;\n      width:100%;\n      height:100%;\n    }\n    h2{\n      font-size:16px;\n      margin:0 0 10px 0;\n      font-weight:650;\n    }\n    .row{\n      display:flex;\n      align-items:center;\n      gap:10px;\n      margin:12px 0;\n    }\n    .row label{\n      flex:1 1 auto;\n      font-size:13px;\n      color:var(--muted);\n    }\n    .row input[type=\"range\"]{\n      flex:1 1 150px;\n      min-width:0;\n      accent-color:#d85b63;\n    }\n    .row .val{\n      width:54px;\n      text-align:right;\n      font-variant-numeric:tabular-nums;\n      color:var(--fg);\n      font-size:13px;\n    }\n    .small{\n      font-size:12px;\n      color:var(--muted);\n      line-height:1.4;\n    }\n    .hint{\n      margin-top:12px;\n      padding-top:10px;\n      border-top:1px solid #222836;\n    }\n    .fireRow{\n      display:grid;\n      grid-template-columns:1fr 1fr 1fr;\n      gap:8px;\n      margin-top:10px;\n    }\n    .scopes{\n      display:grid;\n      grid-template-columns:1fr;\n      gap:12px;\n      min-width:0;\n    }\n    .scopeBox{\n      background:var(--panel2);\n      border:1px solid #1f2530;\n      border-radius:12px;\n      overflow:hidden;\n    }\n    .scopeWrap{\n      position:relative;\n      height:124px;\n    }\n    .scopeTitle{\n      position:absolute;\n      left:10px;\n      top:8px;\n      font-size:12px;\n      color:var(--muted);\n      pointer-events:none;\n      z-index:2;\n    }\n    button{\n      background:#1b2230;\n      color:var(--fg);\n      border:1px solid #2a3242;\n      border-radius:10px;\n      padding:9px 10px;\n      cursor:pointer;\n      font-weight:600;\n    }\n    button:hover{border-color:#3a4761;}\n    .reset{\n      width:100%;\n      margin-top:14px;\n    }\n    .kbd{\n      display:inline-block;\n      padding:1px 6px;\n      border:1px solid #3a4150;\n      border-bottom-width:2px;\n      border-radius:6px;\n      background:#111522;\n      color:#cfd6e6;\n      font-size:12px;\n    }\n    .autoRow{\n      display:grid;\n      grid-template-columns: 88px 1fr 54px;\n      gap:8px;\n      align-items:center;\n      margin:5px 0 12px;\n    }\n    .autoCheck{\n      display:flex;\n      align-items:center;\n      gap:6px;\n      color:var(--muted);\n      font-size:12px;\n      white-space:nowrap;\n    }\n    .autoRow input[type=\"range\"]{\n      accent-color:#4f8fd8;\n    }\n    .autoRow input[type=\"range\"]:disabled{\n      opacity:0.35;\n    }\n    .autoRow + .row{\n      margin-top:24px;\n      padding-top:10px;\n      border-top:1px solid #202735;\n    }\n    .autoRow .val{\n      min-width:54px;\n    }\n\n    .moduleLinks{\n      display:flex;\n      flex-wrap:wrap;\n      gap:7px;\n      margin:0 0 14px 0;\n      padding-bottom:12px;\n      border-bottom:1px solid #222836;\n    }\n    .moduleLinks a{\n      color:#b9caff;\n      text-decoration:none;\n      font-size:12px;\n      border:1px solid #30394a;\n      border-radius:7px;\n      padding:4px 7px;\n      background:#111722;\n    }\n    .moduleLinks a:hover{\n      border-color:#526585;\n      color:#e2e9ff;\n    }\n\n    .status{\n      margin-top:12px;\n      font-size:11px;\n      color:#7f8998;\n    }\n    @media (max-width:800px){\n      html,body{height:auto;}\n      .wrap{\n        grid-template-columns:1fr;\n        height:auto;\n      }\n      .main{\n        grid-template-rows:500px 260px;\n      }\n    }\n  <\/style>\n<\/head>\n<body>\n  <div class=\"wrap\">\n    <div class=\"panel\">\n      <h2>Synaptic integration<\/h2>\n\n      <div class=\"moduleLinks\">\n        <a href=\"\/~theobald\/wp-content\/plugins\/neuro_demos\/demo_neuron.html\" target=\"_blank\" rel=\"noopener\">HH membrane<\/a>\n        <a href=\"\/~theobald\/wp-content\/plugins\/neuro_demos\/axon_conduction.html\" target=\"_blank\" rel=\"noopener\">Axon conduction<\/a>\n      <\/div>\n\n      <div class=\"row\">\n        <label style=\"justify-content:flex-start; gap:10px; color:var(--fg);\">\n          <input id=\"chkColor\" type=\"checkbox\" checked \/>\n          <span>Voltage coloration<\/span>\n        <\/label>\n      <\/div>\n\n      <div class=\"row\">\n        <label for=\"g1\">Connection 1<\/label>\n        <input id=\"g1\" type=\"range\" min=\"0\" max=\"2\" step=\"0.01\" value=\"1.00\" \/>\n        <div class=\"val\" id=\"g1Val\">1.00\u00d7<\/div>\n      <\/div>\n\n      <div class=\"autoRow\">\n        <label class=\"autoCheck\" for=\"auto1\">\n          <input id=\"auto1\" type=\"checkbox\" \/>\n          <span>Auto 1<\/span>\n        <\/label>\n        <input id=\"rate1\" type=\"range\" min=\"20\" max=\"60\" step=\"1\" value=\"40\" disabled \/>\n        <div class=\"val\" id=\"rate1Val\">40 Hz<\/div>\n      <\/div>\n\n      <div class=\"row\">\n        <label for=\"g2\">Connection 2<\/label>\n        <input id=\"g2\" type=\"range\" min=\"0\" max=\"2\" step=\"0.01\" value=\"1.00\" \/>\n        <div class=\"val\" id=\"g2Val\">1.00\u00d7<\/div>\n      <\/div>\n\n      <div class=\"autoRow\">\n        <label class=\"autoCheck\" for=\"auto2\">\n          <input id=\"auto2\" type=\"checkbox\" \/>\n          <span>Auto 2<\/span>\n        <\/label>\n        <input id=\"rate2\" type=\"range\" min=\"20\" max=\"60\" step=\"1\" value=\"40\" disabled \/>\n        <div class=\"val\" id=\"rate2Val\">40 Hz<\/div>\n      <\/div>\n\n      <div class=\"row\">\n        <label for=\"g3\">Connection 3<\/label>\n        <input id=\"g3\" type=\"range\" min=\"0\" max=\"2\" step=\"0.01\" value=\"1.00\" \/>\n        <div class=\"val\" id=\"g3Val\">1.00\u00d7<\/div>\n      <\/div>\n\n      <div class=\"autoRow\">\n        <label class=\"autoCheck\" for=\"auto3\">\n          <input id=\"auto3\" type=\"checkbox\" \/>\n          <span>Auto 3<\/span>\n        <\/label>\n        <input id=\"rate3\" type=\"range\" min=\"20\" max=\"60\" step=\"1\" value=\"40\" disabled \/>\n        <div class=\"val\" id=\"rate3Val\">40 Hz<\/div>\n      <\/div>\n\n      <div class=\"hint small\">\n        Send an action potential through an input with\n        <span class=\"kbd\">1<\/span>,\n        <span class=\"kbd\">2<\/span>, or\n        <span class=\"kbd\">3<\/span>.\n      <\/div>\n\n      <div class=\"fireRow\">\n        <button id=\"fire1\" type=\"button\">1<\/button>\n        <button id=\"fire2\" type=\"button\">2<\/button>\n        <button id=\"fire3\" type=\"button\">3<\/button>\n      <\/div>\n\n      <button class=\"reset\" id=\"btnReset\" type=\"button\">Reset<\/button>\n\n      <div class=\"status\" id=\"status\">Loading simulation\u2026<\/div>\n    <\/div>\n\n    <div class=\"main\">\n      <div class=\"scene\">\n        <canvas id=\"sceneCanvas\"><\/canvas>\n      <\/div>\n\n      <div class=\"scopes\">\n        <div class=\"scopeBox\">\n          <div class=\"scopeWrap\">\n            <div class=\"scopeTitle\">Soma<\/div>\n            <canvas id=\"scopeA\"><\/canvas>\n          <\/div>\n        <\/div>\n        <div class=\"scopeBox\">\n          <div class=\"scopeWrap\">\n            <div class=\"scopeTitle\">Downstream axon<\/div>\n            <canvas id=\"scopeB\"><\/canvas>\n          <\/div>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/div>\n\n  <script type=\"module\">\n\/\/ Synaptic summation demo\n\/\/ Uses the same Neuron and NeuronRenderer framework as axon_demo.\n\/\/\n\/\/ Production version for the FIU faculty site.\n\/\/ Shared HH, neuron, and rendering code live in the neuro_demos plugin.\n\nimport { Neuron } from \"\/~theobald\/wp-content\/plugins\/neuro_demos\/assets\/neuron.js\";\nimport { NeuronRenderer, RingBuffer, drawScope } from \"\/~theobald\/wp-content\/plugins\/neuro_demos\/assets\/render.js\";\n\nconst $ = (id) => document.getElementById(id);\n\nconst sceneCanvas = $(\"sceneCanvas\");\nconst sceneCtx = sceneCanvas.getContext(\"2d\", { alpha: false });\nconst scopeA = $(\"scopeA\");\nconst scopeB = $(\"scopeB\");\nconst scopeCtxA = scopeA.getContext(\"2d\", { alpha: false });\nconst scopeCtxB = scopeB.getContext(\"2d\", { alpha: false });\n\nconst renderer = new NeuronRenderer();\n\nfunction setVoltageColoration(on){\n  if (\"useVoltageColor\" in renderer) renderer.useVoltageColor = on;\n  if (\"colorByVoltage\" in renderer) renderer.colorByVoltage = on;\n  if (\"colorize\" in renderer) renderer.colorize = on;\n}\nsetVoltageColoration($(\"chkColor\").checked);\n$(\"chkColor\").addEventListener(\"change\", () => {\n  setVoltageColoration($(\"chkColor\").checked);\n});\n\nfunction resizeCanvasToCSS(canvas){\n  const dpr = window.devicePixelRatio || 1;\n  const rect = canvas.getBoundingClientRect();\n  const w = Math.max(2, Math.floor(rect.width * dpr));\n  const h = Math.max(2, Math.floor(rect.height * dpr));\n  if (canvas.width !== w || canvas.height !== h){\n    canvas.width = w;\n    canvas.height = h;\n  }\n}\n\nfunction resizeAll(){\n  resizeCanvasToCSS(sceneCanvas);\n  resizeCanvasToCSS(scopeA);\n  resizeCanvasToCSS(scopeB);\n}\nwindow.addEventListener(\"resize\", resizeAll);\n\nfunction makeFixedXform(canvas, world, paddingPx=30){\n  const W = canvas.width;\n  const H = canvas.height;\n  const spanX = world.xmax - world.xmin;\n  const spanY = world.ymax - world.ymin;\n\n  const sx = (W - 2*paddingPx) \/ spanX;\n  const sy = (H - 2*paddingPx) \/ spanY;\n  const scale = Math.min(sx, sy);\n\n  const cx = 0.5 * (world.xmin + world.xmax);\n  const cy = 0.5 * (world.ymin + world.ymax);\n\n  const map = (x, y) => {\n    const X = (x - cx) * scale + W\/2;\n    const Y = -(y - cy) * scale + H\/2;\n    return [X, Y];\n  };\n\n  return { map, scale };\n}\n\nlet xform = null;\nfunction updateXform(){\n  xform = makeFixedXform(\n    sceneCanvas,\n    { xmin:-7.4, xmax:9.2, ymin:-4.8, ymax:4.8 },\n    32\n  );\n}\n\nfunction addCurve(neuron, opts){\n  const before = neuron.sections.length;\n  neuron.addAxon(opts);\n  if (neuron.sections.length <= before){\n    throw new Error(\"Neuron.addAxon() did not add a section.\");\n  }\n  return neuron.sections[neuron.sections.length - 1];\n}\n\nfunction makePassive(section){\n  for (const c of section){\n    if (!c?.p) continue;\n    if (\"gNa_mS_cm2\" in c.p) c.p.gNa_mS_cm2 = 0.0;\n    if (\"gK_mS_cm2\"  in c.p) c.p.gK_mS_cm2  = 0.0;\n    if (\"gNaP_mS_cm2\" in c.p) c.p.gNaP_mS_cm2 = 0.0;\n    if (\"gKP_mS_cm2\"  in c.p) c.p.gKP_mS_cm2  = 0.0;\n    if (\"EL_mV\" in c.p) c.p.EL_mV = -65.0;\n    if (\"gL_mS_cm2\" in c.p) c.p.gL_mS_cm2 = 0.30;\n  }\n}\n\nfunction setSectionAxialG(section, g_S){\n  for (const c of section){\n    if (!c?.axial) continue;\n    for (const link of c.axial){\n      link.g = g_S;\n    }\n  }\n}\n\nfunction endComp(section){\n  return section[section.length - 1];\n}\n\nfunction buildPostsynapticNeuron(){\n  const n = new Neuron();\n  const soma = n.addSoma([1.45, 0.0], 0.52);\n\n  const dend1 = addCurve(n, {\n    num_pts: 8,\n    cpts: [[1.02,0.20],[0.25,0.75],[-0.20,1.95],[-1.35,2.20]],\n    connect: soma,\n    draw_rad: 0.075\n  });\n\n  const dend2 = addCurve(n, {\n    num_pts: 8,\n    cpts: [[0.95,0.00],[0.25,0.10],[-0.35,-0.10],[-1.35,0.00]],\n    connect: soma,\n    draw_rad: 0.075\n  });\n\n  const dend3 = addCurve(n, {\n    num_pts: 8,\n    cpts: [[1.02,-0.20],[0.25,-0.75],[-0.20,-1.95],[-1.35,-2.20]],\n    connect: soma,\n    draw_rad: 0.075\n  });\n\n  makePassive(dend1);\n  makePassive(dend2);\n  makePassive(dend3);\n\n  \/\/ The dendrites are electrically shorter and more strongly coupled than\n  \/\/ the thin axons. This keeps a distal PSP visible at the soma.\n  setSectionAxialG(dend1, 2.5e-7);\n  setSectionAxialG(dend2, 2.5e-7);\n  setSectionAxialG(dend3, 2.5e-7);\n\n  const axon = addCurve(n, {\n    num_pts: 140,\n    cpts: [[1.98,0.00],[5.15,0.35],[3.00,-3.25],[8.35,-3.10]],\n    connect: soma,\n    draw_rad: 0.05\n  });\n\n  return {\n    neuron:n,\n    soma,\n    dendrites:[dend1,dend2,dend3],\n    targets:[endComp(dend1),endComp(dend2),endComp(dend3)],\n    axon\n  };\n}\n\nfunction buildPresynapticNeuron(index){\n  const y = [2.55, 0.0, -2.55][index];\n\n  const n = new Neuron();\n  const soma = n.addSoma([-6.35, y], 0.30);\n\n  const endings = [[-1.49,2.20],[-1.49,0.00],[-1.49,-2.20]];\n  const c2 = [[-4.9,2.85],[-4.8,0.15],[-4.9,-2.85]][index];\n  const c3 = [[-3.0,1.90],[-3.0,-0.15],[-3.0,-1.90]][index];\n\n  const axon = addCurve(n, {\n    num_pts: 100,\n    cpts: [[-6.02,y],c2,c3,endings[index]],\n    connect:soma,\n    draw_rad:0.05\n  });\n\n  return {\n    neuron:n,\n    soma,\n    axon,\n    terminal:endComp(axon),\n    previousTerminalV:endComp(axon).V,\n    stimUntil:-1\n  };\n}\n\nclass RecordingElectrode {\n  constructor({ compName, scopeIndex, label, color }){\n    this.type = \"record\";\n    this.compName = compName;\n    this.scopeIndex = scopeIndex;\n    this.label = label;\n    this.color = color;\n    this.trace = new RingBuffer(2500);\n  }\n\n  primeWith(v){\n    this.trace.fill(v);\n  }\n\n  sample(neuron){\n    const c = neuron.getCompByName(this.compName);\n    if (c) this.trace.push(c.V);\n  }\n}\n\nclass Electrodes {\n  constructor(){\n    this.record = [];\n    this.stim = [];\n  }\n}\n\nlet post = null;\nlet pre = null;\nlet electrodes = null;\n\nconst syn = [\n  \/\/ User-facing strength 1.00 is the calibrated baseline.\n  \/\/ Excitatory events overlap enough for temporal summation, but decay\n  \/\/ fast enough to remain discrete PSPs during sustained firing.\n  { g:0, tau_s:40e-3, Erev_V:  0.000, basePeak_S:1.2903e-8 },\n  { g:0, tau_s:40e-3, Erev_V:  0.000, basePeak_S:1.2903e-8 },\n\n  \/\/ Inhibition remains in the already useful working regime.\n  { g:0, tau_s:12e-3, Erev_V: -0.080, basePeak_S:5.0e-8 }\n];\n\n\/\/ Diagnostic architecture: apply synaptic conductances directly to the soma.\n\/\/ The visual synapses remain on the three dendrites.\nconst SYNAPSE_TO_SOMA = true;\n\n\/\/ Hidden presynaptic trigger: deliberately suprathreshold so that\n\/\/ every keypress launches one HH action potential.\nconst stimAmp_uAcm2 = 30.0;\nconst stimDur_s = 1.00e-3;\n\n\/\/ Independent constant-rate firing for each presynaptic neuron.\n\/\/ Scheduling uses each neuron's model time, so rates are independent\n\/\/ of browser frame rate.\nconst nextAutoFire_s = [Infinity, Infinity, Infinity];\n\nfunction autoEnabled(i){\n  return $(`auto${i+1}`).checked;\n}\n\nfunction autoRateHz(i){\n  return Number($(`rate${i+1}`).value);\n}\n\nfunction scheduleNextAutoFire(i, fromNow=false){\n  if (!autoEnabled(i)){\n    nextAutoFire_s[i] = Infinity;\n    return;\n  }\n\n  const period_s = 1.0 \/ Math.max(0.1, autoRateHz(i));\n  if (fromNow || !Number.isFinite(nextAutoFire_s[i])){\n    nextAutoFire_s[i] = pre[i].neuron.t + period_s;\n  } else {\n    nextAutoFire_s[i] += period_s;\n  }\n}\n\nfunction updateAutoLabel(i){\n  const rate = autoRateHz(i);\n  $(`rate${i+1}Val`).textContent = rate.toFixed(0) + \" Hz\";\n  $(`rate${i+1}`).disabled = !autoEnabled(i);\n}\n\nfunction sliderStrength(i){\n  return Number($(`g${i+1}`).value);\n}\n\nfunction makeElectrodes(){\n  const e = new Electrodes();\n\n  const somaRec = new RecordingElectrode({\n    compName:post.soma.name,\n    scopeIndex:0,\n    label:\"Soma\",\n    color:\"rgba(120,170,255,0.95)\"\n  });\n  somaRec.primeWith(post.soma.V);\n\n  const distal = post.axon[Math.floor(post.axon.length * 0.82)];\n  const axonRec = new RecordingElectrode({\n    compName:distal.name,\n    scopeIndex:1,\n    label:\"Downstream axon\",\n    color:\"rgba(110,220,180,0.95)\"\n  });\n  axonRec.primeWith(distal.V);\n\n  e.record.push(somaRec, axonRec);\n  return e;\n}\n\nfunction buildAll(){\n  post = buildPostsynapticNeuron();\n  pre = [0,1,2].map(buildPresynapticNeuron);\n  electrodes = makeElectrodes();\n  for (const s of syn) s.g = 0;\n\n  console.log(\"postsynaptic soma:\", post.soma.name);\n  console.log(\"dendrite targets:\", post.targets.map(c => c.name));\n  console.log(\"all postsynaptic section names:\",\n    post.neuron.sections.map(section => section.map(c => c.name)));\n}\n\nfunction fireInput(index){\n  const p = pre[index];\n  p.stimUntil = p.neuron.t + stimDur_s;\n}\n\nfunction detectPresynapticArrival(index){\n  const p = pre[index];\n  const V = p.terminal.V;\n  const crossed = p.previousTerminalV < 0.0 && V >= 0.0;\n  p.previousTerminalV = V;\n  if (crossed){\n    syn[index].g += syn[index].basePeak_S * sliderStrength(index);\n    $(\"status\").textContent = `Input ${index + 1}: AP reached synapse`;\n  }\n}\n\nfunction presynapticCurrents(index){\n  const p = pre[index];\n  const currents = {};\n  if (p.neuron.t <= p.stimUntil){\n    const c = p.soma;\n    currents[c.name] = stimAmp_uAcm2 * 1e-6 * c.p.area_cm2;\n  }\n  return currents;\n}\n\nfunction postsynapticCurrents(){\n  const currents = {};\n  for (let i=0; i<3; i++){\n    const target = SYNAPSE_TO_SOMA ? post.soma : post.targets[i];\n    const I_A = syn[i].g * (syn[i].Erev_V - target.V);\n    if (currents[target.name] === undefined) currents[target.name] = 0;\n    currents[target.name] += I_A;\n  }\n  return currents;\n}\n\nfunction oneModelStep(){\n  for (let i=0; i<3; i++){\n    const p = pre[i];\n\n    if (autoEnabled(i) && p.neuron.t >= nextAutoFire_s[i]){\n      fireInput(i);\n      scheduleNextAutoFire(i, false);\n    }\n\n    p.neuron.step(p.neuron.dt, presynapticCurrents(i));\n    detectPresynapticArrival(i);\n  }\n\n  post.neuron.step(post.neuron.dt, postsynapticCurrents());\n\n  for (const s of syn){\n    s.g *= Math.exp(-post.neuron.dt \/ s.tau_s);\n  }\n}\n\nfunction drawLabels(){\n  sceneCtx.save();\n  const dpr = window.devicePixelRatio || 1;\n  sceneCtx.font = `${16*dpr}px system-ui`;\n  sceneCtx.fillStyle = \"rgba(230,232,238,0.90)\";\n  sceneCtx.textAlign = \"center\";\n  sceneCtx.textBaseline = \"middle\";\n\n  for (let i=0; i<3; i++){\n    const [x,y] = xform.map(pre[i].soma.pos[0], pre[i].soma.pos[1]);\n    sceneCtx.fillText(String(i+1), x, y - 0.60*xform.scale);\n  }\n  sceneCtx.restore();\n}\n\nfunction drawSynapseMarkers(){\n  sceneCtx.save();\n  sceneCtx.fillStyle = \"rgba(220,225,232,0.90)\";\n  sceneCtx.strokeStyle = \"rgba(220,225,232,0.80)\";\n\n  for (let i=0; i<3; i++){\n    const terminal = pre[i].terminal;\n    const target = post.targets[i];\n\n    const [x1,y1] = xform.map(terminal.pos[0], terminal.pos[1]);\n    const [x2,y2] = xform.map(target.pos[0], target.pos[1]);\n    const r = Math.max(2, 0.055*xform.scale);\n\n    sceneCtx.beginPath();\n    sceneCtx.arc(x1, y1, r, 0, Math.PI*2);\n    sceneCtx.fill();\n\n    const dx = x2 - x1;\n    const dy = y2 - y1;\n    const len = Math.hypot(dx,dy) || 1;\n    const nx = -dy\/len;\n    const ny = dx\/len;\n\n    sceneCtx.lineWidth = Math.max(1.5, 0.024*xform.scale);\n    sceneCtx.beginPath();\n    sceneCtx.moveTo(x2 - nx*r*0.65, y2 - ny*r*0.65);\n    sceneCtx.lineTo(x2 + nx*r*0.65, y2 + ny*r*0.65);\n    sceneCtx.stroke();\n  }\n\n  sceneCtx.restore();\n}\n\nfunction drawScene(){\n  sceneCtx.clearRect(0,0,sceneCanvas.width,sceneCanvas.height);\n  for (const p of pre){\n    renderer.drawNeuron(sceneCtx, p.neuron, xform);\n  }\n  renderer.drawNeuron(sceneCtx, post.neuron, xform);\n  drawSynapseMarkers();\n  drawLabels();\n  renderer.drawElectrodes(sceneCtx, post.neuron, electrodes, xform);\n}\n\nfunction drawScopes(){\n  const rec0 = electrodes.record[0];\n  const rec1 = electrodes.record[1];\n\n  drawScope(scopeCtxA, scopeA, rec0.trace.toArray(), \"\", rec0.color,\n            { autoscale:false, vmin:-0.100, vmax:0.070 });\n  drawScope(scopeCtxB, scopeB, rec1.trace.toArray(), \"\", rec1.color,\n            { autoscale:false, vmin:-0.100, vmax:0.070 });\n}\n\nfunction updateSliderLabel(i){\n  $(`g${i+1}Val`).textContent = Number($(`g${i+1}`).value).toFixed(2) + \"\u00d7\";\n}\n\nfor (let i=0; i<3; i++){\n  $(`g${i+1}`).addEventListener(\"input\", () => updateSliderLabel(i));\n  updateSliderLabel(i);\n\n  $(`fire${i+1}`).addEventListener(\"click\", () => fireInput(i));\n\n  $(`auto${i+1}`).addEventListener(\"change\", () => {\n    updateAutoLabel(i);\n    scheduleNextAutoFire(i, true);\n  });\n\n  $(`rate${i+1}`).addEventListener(\"input\", () => {\n    updateAutoLabel(i);\n    if (autoEnabled(i)) scheduleNextAutoFire(i, true);\n  });\n\n  updateAutoLabel(i);\n}\n\nwindow.addEventListener(\"keydown\", (ev) => {\n  const el = ev.target;\n  const tag = el?.tagName?.toLowerCase() || \"\";\n  const isTextEntry =\n    tag === \"textarea\" ||\n    tag === \"select\" ||\n    (tag === \"input\" && ![\"range\",\"checkbox\",\"button\"].includes((el.type || \"\").toLowerCase())) ||\n    el?.isContentEditable;\n\n  if (isTextEntry) return;\n\n  if (ev.key === \"1\" || ev.key === \"2\" || ev.key === \"3\"){\n    ev.preventDefault();\n    ev.stopPropagation();\n    if (ev.repeat) return;\n    fireInput(Number(ev.key) - 1);\n  }\n}, { capture:true });\n\n$(\"btnReset\").addEventListener(\"click\", () => {\n  for (let i=0; i<3; i++){\n    $(`g${i+1}`).value = \"1.00\";\n    $(`auto${i+1}`).checked = false;\n    $(`rate${i+1}`).value = \"40\";\n    nextAutoFire_s[i] = Infinity;\n    updateSliderLabel(i);\n    updateAutoLabel(i);\n  }\n  buildAll();\n  $(\"status\").textContent = \"Ready\";\n});\n\nresizeAll();\nupdateXform();\nbuildAll();\n\nconst stepsPerFrame = 50;\n\nfunction frame(){\n  resizeAll();\n  updateXform();\n\n  for (let k=0; k<stepsPerFrame; k++){\n    oneModelStep();\n  }\n\n  for (const e of electrodes.record){\n    e.sample(post.neuron);\n  }\n\n  drawScene();\n  drawScopes();\n  requestAnimationFrame(frame);\n}\n\n$(\"status\").textContent = \"Ready\";\nrequestAnimationFrame(frame);\n\n  <\/script>\n<\/body>\n<\/html>\n\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Synaptic summation (HH) Synaptic integration HH membrane Axon conduction Voltage coloration Connection 1 1.00\u00d7 Auto 1 40 Hz Connection 2 1.00\u00d7 Auto 2 40 Hz Connection 3 1.00\u00d7 Auto 3 40 Hz Send an action potential through an input with 1, 2, or 3. 1 2 3 Reset Loading simulation\u2026 Soma Downstream axon<\/p>\n","protected":false},"author":3,"featured_media":0,"parent":4438,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-5003","page","type-page","status-publish","hentry","entry"],"_links":{"self":[{"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/pages\/5003","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/users\/3"}],"replies":[{"embeddable":true,"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/comments?post=5003"}],"version-history":[{"count":4,"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/pages\/5003\/revisions"}],"predecessor-version":[{"id":5007,"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/pages\/5003\/revisions\/5007"}],"up":[{"embeddable":true,"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/pages\/4438"}],"wp:attachment":[{"href":"https:\/\/faculty.fiu.edu\/~theobald\/wp-json\/wp\/v2\/media?parent=5003"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}