{
 "cells": [
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2026-08-27T11:40:04.919607800Z",
     "start_time": "2026-08-27T11:40:04.906089300Z"
    }
   },
   "source": "from IPython.display import HTML, display\n",
   "outputs": [],
   "execution_count": 1
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": [
    "## 交互式 group-major PID 映射\n",
    "\n",
    "拖动滑块或点击按钮，观察一维 PID 如何经过分组计算映射到二维输出块。该示例特意令 M 方向包含 5 个块，以展示最后一组的 `group_size_adj` 调整。\n"
   ],
   "id": "6aec3b6f3fa933b3"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2026-08-27T11:40:04.997005500Z",
     "start_time": "2026-08-27T11:40:04.928469800Z"
    }
   },
   "cell_type": "code",
   "source": [
    "group_major_html = r'''\n",
    "<div id=\"group-major-pid-map\" style=\"width:100%;box-sizing:border-box;padding:16px;border:1px solid #cbd5e1;border-radius:8px;background:#f8fafc;color:#172033;color-scheme:light;font-family:'Noto Sans CJK SC','Microsoft YaHei',ui-sans-serif,system-ui,sans-serif;--foreground:#172033;--muted-foreground:#5b6475;--border:#c9d2df;--primary:#2563eb;--primary-foreground:#ffffff;--viz-series-1:#2563eb;--viz-series-2:#0891b2;--viz-series-3:#d97706;\">\n",
    "  <div style=\"display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:12px;\">\n",
    "    <button class=\"btn btn-secondary\" type=\"button\" data-action=\"previous\" aria-label=\"上一个 PID\" title=\"上一个 PID\">&#8592;</button>\n",
    "    <label style=\"display:flex;align-items:center;gap:10px;flex:1;min-width:220px;\">\n",
    "      <span>PID</span>\n",
    "      <input class=\"form-control\" data-role=\"pid-slider\" type=\"range\" min=\"0\" max=\"19\" value=\"0\" step=\"1\" style=\"flex:1;\" />\n",
    "      <output data-role=\"pid-output\" style=\"min-width:2ch;\">0</output>\n",
    "    </label>\n",
    "    <button class=\"btn btn-secondary\" type=\"button\" data-action=\"next\" aria-label=\"下一个 PID\" title=\"下一个 PID\">&#8594;</button>\n",
    "  </div>\n",
    "\n",
    "  <div data-role=\"summary\" aria-live=\"polite\" style=\"margin-bottom:10px;color:var(--muted-foreground);\"></div>\n",
    "\n",
    "  <svg data-role=\"diagram\" viewBox=\"0 0 920 510\" role=\"img\" aria-labelledby=\"gm-title gm-desc\" style=\"display:block;width:100%;height:auto;overflow:visible;\">\n",
    "    <title id=\"gm-title\">group-major PID 映射</title>\n",
    "    <desc id=\"gm-desc\">一维 PID 分组后映射到输出矩阵的二维块坐标。</desc>\n",
    "  </svg>\n",
    "\n",
    "  <div style=\"display:flex;gap:18px;flex-wrap:wrap;margin-top:8px;color:var(--muted-foreground);\">\n",
    "    <span style=\"display:inline-flex;align-items:center;gap:6px;\"><span style=\"width:12px;height:12px;background:color-mix(in srgb,var(--viz-series-2) 22%,transparent);border:1px solid var(--border);\"></span>偶数组</span>\n",
    "    <span style=\"display:inline-flex;align-items:center;gap:6px;\"><span style=\"width:12px;height:12px;background:color-mix(in srgb,var(--viz-series-3) 22%,transparent);border:1px solid var(--border);\"></span>奇数组</span>\n",
    "    <span style=\"display:inline-flex;align-items:center;gap:6px;\"><span style=\"width:12px;height:12px;background:var(--primary);border:1px solid var(--primary);\"></span>当前 PID</span>\n",
    "  </div>\n",
    "</div>\n",
    "\n",
    "<script>\n",
    "(() => {\n",
    "  const root = document.getElementById('group-major-pid-map');\n",
    "  const svg = root.querySelector('[data-role=\"diagram\"]');\n",
    "  const slider = root.querySelector('[data-role=\"pid-slider\"]');\n",
    "  const output = root.querySelector('[data-role=\"pid-output\"]');\n",
    "  const summary = root.querySelector('[data-role=\"summary\"]');\n",
    "  const previous = root.querySelector('[data-action=\"previous\"]');\n",
    "  const next = root.querySelector('[data-action=\"next\"]');\n",
    "  const ns = 'http://www.w3.org/2000/svg';\n",
    "\n",
    "  const M = 80;\n",
    "  const N = 80;\n",
    "  const BLOCK_SIZE_M = 16;\n",
    "  const BLOCK_SIZE_N = 20;\n",
    "  const GROUP_SIZE = 2;\n",
    "  const numM = Math.ceil(M / BLOCK_SIZE_M);\n",
    "  const numN = Math.ceil(N / BLOCK_SIZE_N);\n",
    "  const totalPids = numM * numN;\n",
    "  slider.max = String(totalPids - 1);\n",
    "\n",
    "  function node(name, attrs = {}, text = '') {\n",
    "    const element = document.createElementNS(ns, name);\n",
    "    Object.entries(attrs).forEach(([key, value]) => element.setAttribute(key, String(value)));\n",
    "    if (text) element.textContent = text;\n",
    "    return element;\n",
    "  }\n",
    "\n",
    "  function text(x, y, value, attrs = {}) {\n",
    "    return node('text', {\n",
    "      x, y,\n",
    "      fill: 'var(--foreground)',\n",
    "      'font-size': '14',\n",
    "      'text-anchor': 'middle',\n",
    "      'dominant-baseline': 'middle',\n",
    "      ...attrs,\n",
    "    }, value);\n",
    "  }\n",
    "\n",
    "  function mapping(pid) {\n",
    "    const numPidInGroup = GROUP_SIZE * numN;\n",
    "    const groupId = Math.floor(pid / numPidInGroup);\n",
    "    const firstM = groupId * GROUP_SIZE;\n",
    "    const groupSizeAdj = Math.min(numM - firstM, GROUP_SIZE);\n",
    "    const localPid = pid % numPidInGroup;\n",
    "    const pidM = firstM + (localPid % groupSizeAdj);\n",
    "    const pidN = Math.floor(localPid / groupSizeAdj);\n",
    "    return {pid, numPidInGroup, groupId, firstM, groupSizeAdj, localPid, pidM, pidN};\n",
    "  }\n",
    "\n",
    "  const allMappings = Array.from({length: totalPids}, (_, pid) => mapping(pid));\n",
    "\n",
    "  function render() {\n",
    "    const selectedPid = Number(slider.value);\n",
    "    const selected = mapping(selectedPid);\n",
    "    output.value = String(selectedPid);\n",
    "    output.textContent = String(selectedPid);\n",
    "    summary.textContent = `PID ${selectedPid} → group ${selected.groupId} → 组内 PID ${selected.localPid} → 输出块 (PID_M=${selected.pidM}, PID_N=${selected.pidN})`;\n",
    "    svg.replaceChildren();\n",
    "\n",
    "    svg.appendChild(text(20, 22, `示例：M=${M}, N=${N}, BLOCK_SIZE_M=${BLOCK_SIZE_M}, BLOCK_SIZE_N=${BLOCK_SIZE_N}, GROUP_SIZE=${GROUP_SIZE}`, {\n",
    "      'text-anchor': 'start', fill: 'var(--muted-foreground)'\n",
    "    }));\n",
    "\n",
    "    svg.appendChild(text(20, 60, '一维 PID 启动序列', {'text-anchor': 'start', 'font-weight': '500'}));\n",
    "    const stripX = 20;\n",
    "    const stripY = 80;\n",
    "    const stripCell = 40;\n",
    "    allMappings.forEach((item) => {\n",
    "      const x = stripX + item.pid * stripCell;\n",
    "      const isSelected = item.pid === selectedPid;\n",
    "      const fill = isSelected\n",
    "        ? 'var(--primary)'\n",
    "        : `color-mix(in srgb, var(${item.groupId % 2 === 0 ? '--viz-series-2' : '--viz-series-3'}) 22%, transparent)`;\n",
    "      svg.appendChild(node('rect', {\n",
    "        x, y: stripY, width: stripCell - 2, height: 36,\n",
    "        fill, stroke: isSelected ? 'var(--primary)' : 'var(--border)', rx: 2\n",
    "      }));\n",
    "      svg.appendChild(text(x + (stripCell - 2) / 2, stripY + 18, String(item.pid), {\n",
    "        fill: isSelected ? 'var(--primary-foreground)' : 'var(--foreground)',\n",
    "        'font-weight': isSelected ? '500' : '400'\n",
    "      }));\n",
    "    });\n",
    "\n",
    "    for (let groupId = 0; groupId <= Math.floor((totalPids - 1) / (GROUP_SIZE * numN)); groupId += 1) {\n",
    "      const firstPid = groupId * GROUP_SIZE * numN;\n",
    "      const count = Math.min(GROUP_SIZE, numM - groupId * GROUP_SIZE) * numN;\n",
    "      const startX = stripX + firstPid * stripCell;\n",
    "      const endX = startX + count * stripCell - 2;\n",
    "      svg.appendChild(node('path', {\n",
    "        d: `M ${startX} ${stripY + 42} V ${stripY + 48} H ${endX} V ${stripY + 42}`,\n",
    "        fill: 'none', stroke: 'var(--muted-foreground)', 'stroke-width': 1\n",
    "      }));\n",
    "      svg.appendChild(text((startX + endX) / 2, stripY + 62, `group ${groupId}`, {\n",
    "        fill: 'var(--muted-foreground)', 'font-size': '12'\n",
    "      }));\n",
    "    }\n",
    "\n",
    "    const arrowStartX = stripX + selectedPid * stripCell + 19;\n",
    "    svg.appendChild(node('path', {\n",
    "      d: `M ${arrowStartX} ${stripY + 72} C ${arrowStartX} 190, 450 165, 450 215`,\n",
    "      fill: 'none', stroke: 'var(--viz-series-1)', 'stroke-width': 2\n",
    "    }));\n",
    "    svg.appendChild(node('path', {\n",
    "      d: 'M 444 205 L 450 215 L 456 205',\n",
    "      fill: 'none', stroke: 'var(--viz-series-1)', 'stroke-width': 2\n",
    "    }));\n",
    "\n",
    "    const calcX = 20;\n",
    "    const calcY = 190;\n",
    "    svg.appendChild(text(calcX, calcY, '映射计算', {'text-anchor': 'start', 'font-weight': '500'}));\n",
    "    const calculations = [\n",
    "      `num_PID_along_M = ceil(${M}/${BLOCK_SIZE_M}) = ${numM}`,\n",
    "      `num_PID_along_N = ceil(${N}/${BLOCK_SIZE_N}) = ${numN}`,\n",
    "      `num_PID_in_group = ${GROUP_SIZE} × ${numN} = ${selected.numPidInGroup}`,\n",
    "      `group_id = ${selectedPid} // ${selected.numPidInGroup} = ${selected.groupId}`,\n",
    "      `first_PID_in_group_along_M = ${selected.groupId} × ${GROUP_SIZE} = ${selected.firstM}`,\n",
    "      `group_size_adj = min(${numM} - ${selected.firstM}, ${GROUP_SIZE}) = ${selected.groupSizeAdj}`,\n",
    "      `PID_M = ${selected.firstM} + ((${selectedPid} % ${selected.numPidInGroup}) % ${selected.groupSizeAdj}) = ${selected.pidM}`,\n",
    "      `PID_N = (${selectedPid} % ${selected.numPidInGroup}) // ${selected.groupSizeAdj} = ${selected.pidN}`,\n",
    "    ];\n",
    "    calculations.forEach((line, index) => {\n",
    "      const active = index >= 6;\n",
    "      svg.appendChild(text(calcX, calcY + 30 + index * 27, line, {\n",
    "        'text-anchor': 'start',\n",
    "        fill: active ? 'var(--foreground)' : 'var(--muted-foreground)',\n",
    "        'font-size': active ? '14' : '12',\n",
    "        'font-weight': active ? '500' : '400'\n",
    "      }));\n",
    "    });\n",
    "\n",
    "    const gridX = 525;\n",
    "    const gridY = 205;\n",
    "    const cellW = 86;\n",
    "    const cellH = 52;\n",
    "    svg.appendChild(text(gridX, gridY - 28, '输出矩阵 C 的块网格', {'text-anchor': 'start', 'font-weight': '500'}));\n",
    "    for (let n = 0; n < numN; n += 1) {\n",
    "      svg.appendChild(text(gridX + n * cellW + cellW / 2, gridY - 8, `N块 ${n}`, {\n",
    "        'font-size': '12', fill: 'var(--muted-foreground)'\n",
    "      }));\n",
    "    }\n",
    "    for (let m = 0; m < numM; m += 1) {\n",
    "      svg.appendChild(text(gridX - 10, gridY + m * cellH + cellH / 2, `M块 ${m}`, {\n",
    "        'text-anchor': 'end', 'font-size': '12', fill: 'var(--muted-foreground)'\n",
    "      }));\n",
    "      const rowGroup = Math.floor(m / GROUP_SIZE);\n",
    "      if (m % GROUP_SIZE === 0) {\n",
    "        svg.appendChild(node('rect', {\n",
    "          x: gridX - 4, y: gridY + m * cellH - 4,\n",
    "          width: numN * cellW + 8,\n",
    "          height: Math.min(GROUP_SIZE, numM - m) * cellH + 8,\n",
    "          fill: 'none',\n",
    "          stroke: rowGroup % 2 === 0 ? 'var(--viz-series-2)' : 'var(--viz-series-3)',\n",
    "          'stroke-width': 2,\n",
    "          rx: 3\n",
    "        }));\n",
    "      }\n",
    "      for (let n = 0; n < numN; n += 1) {\n",
    "        const item = allMappings.find((entry) => entry.pidM === m && entry.pidN === n);\n",
    "        const isSelected = item.pid === selectedPid;\n",
    "        const fill = isSelected\n",
    "          ? 'var(--primary)'\n",
    "          : `color-mix(in srgb, var(${rowGroup % 2 === 0 ? '--viz-series-2' : '--viz-series-3'}) 18%, transparent)`;\n",
    "        svg.appendChild(node('rect', {\n",
    "          x: gridX + n * cellW,\n",
    "          y: gridY + m * cellH,\n",
    "          width: cellW - 5,\n",
    "          height: cellH - 5,\n",
    "          fill,\n",
    "          stroke: isSelected ? 'var(--primary)' : 'var(--border)',\n",
    "          rx: 2\n",
    "        }));\n",
    "        svg.appendChild(text(gridX + n * cellW + (cellW - 5) / 2, gridY + m * cellH + (cellH - 5) / 2, `PID ${item.pid}`, {\n",
    "          fill: isSelected ? 'var(--primary-foreground)' : 'var(--foreground)',\n",
    "          'font-weight': isSelected ? '500' : '400'\n",
    "        }));\n",
    "      }\n",
    "    }\n",
    "\n",
    "    const lastGroupY = gridY + (numM - 1) * cellH;\n",
    "    svg.appendChild(text(gridX + numN * cellW / 2, lastGroupY + cellH + 18, `最后一组只有 ${numM % GROUP_SIZE || GROUP_SIZE} 行，因此 group_size_adj=${numM % GROUP_SIZE || GROUP_SIZE}`, {\n",
    "      'font-size': '12', fill: 'var(--muted-foreground)'\n",
    "    }));\n",
    "  }\n",
    "\n",
    "  slider.addEventListener('input', render);\n",
    "  previous.addEventListener('click', () => {\n",
    "    slider.value = String((Number(slider.value) - 1 + totalPids) % totalPids);\n",
    "    render();\n",
    "  });\n",
    "  next.addEventListener('click', () => {\n",
    "    slider.value = String((Number(slider.value) + 1) % totalPids);\n",
    "    render();\n",
    "  });\n",
    "  render();\n",
    "})();\n",
    "</script>\n",
    "'''\n",
    "display(HTML(group_major_html))\n"
   ],
   "id": "eb8c955f1e2a38e9",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ],
      "text/html": [
       "\n",
       "<div id=\"group-major-pid-map\" style=\"width:100%;box-sizing:border-box;padding:16px;border:1px solid #cbd5e1;border-radius:8px;background:#f8fafc;color:#172033;color-scheme:light;font-family:'Noto Sans CJK SC','Microsoft YaHei',ui-sans-serif,system-ui,sans-serif;--foreground:#172033;--muted-foreground:#5b6475;--border:#c9d2df;--primary:#2563eb;--primary-foreground:#ffffff;--viz-series-1:#2563eb;--viz-series-2:#0891b2;--viz-series-3:#d97706;\">\n",
       "  <div style=\"display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:12px;\">\n",
       "    <button class=\"btn btn-secondary\" type=\"button\" data-action=\"previous\" aria-label=\"上一个 PID\" title=\"上一个 PID\">&#8592;</button>\n",
       "    <label style=\"display:flex;align-items:center;gap:10px;flex:1;min-width:220px;\">\n",
       "      <span>PID</span>\n",
       "      <input class=\"form-control\" data-role=\"pid-slider\" type=\"range\" min=\"0\" max=\"19\" value=\"0\" step=\"1\" style=\"flex:1;\" />\n",
       "      <output data-role=\"pid-output\" style=\"min-width:2ch;\">0</output>\n",
       "    </label>\n",
       "    <button class=\"btn btn-secondary\" type=\"button\" data-action=\"next\" aria-label=\"下一个 PID\" title=\"下一个 PID\">&#8594;</button>\n",
       "  </div>\n",
       "\n",
       "  <div data-role=\"summary\" aria-live=\"polite\" style=\"margin-bottom:10px;color:var(--muted-foreground);\"></div>\n",
       "\n",
       "  <svg data-role=\"diagram\" viewBox=\"0 0 920 510\" role=\"img\" aria-labelledby=\"gm-title gm-desc\" style=\"display:block;width:100%;height:auto;overflow:visible;\">\n",
       "    <title id=\"gm-title\">group-major PID 映射</title>\n",
       "    <desc id=\"gm-desc\">一维 PID 分组后映射到输出矩阵的二维块坐标。</desc>\n",
       "  </svg>\n",
       "\n",
       "  <div style=\"display:flex;gap:18px;flex-wrap:wrap;margin-top:8px;color:var(--muted-foreground);\">\n",
       "    <span style=\"display:inline-flex;align-items:center;gap:6px;\"><span style=\"width:12px;height:12px;background:color-mix(in srgb,var(--viz-series-2) 22%,transparent);border:1px solid var(--border);\"></span>偶数组</span>\n",
       "    <span style=\"display:inline-flex;align-items:center;gap:6px;\"><span style=\"width:12px;height:12px;background:color-mix(in srgb,var(--viz-series-3) 22%,transparent);border:1px solid var(--border);\"></span>奇数组</span>\n",
       "    <span style=\"display:inline-flex;align-items:center;gap:6px;\"><span style=\"width:12px;height:12px;background:var(--primary);border:1px solid var(--primary);\"></span>当前 PID</span>\n",
       "  </div>\n",
       "</div>\n",
       "\n",
       "<script>\n",
       "(() => {\n",
       "  const root = document.getElementById('group-major-pid-map');\n",
       "  const svg = root.querySelector('[data-role=\"diagram\"]');\n",
       "  const slider = root.querySelector('[data-role=\"pid-slider\"]');\n",
       "  const output = root.querySelector('[data-role=\"pid-output\"]');\n",
       "  const summary = root.querySelector('[data-role=\"summary\"]');\n",
       "  const previous = root.querySelector('[data-action=\"previous\"]');\n",
       "  const next = root.querySelector('[data-action=\"next\"]');\n",
       "  const ns = 'http://www.w3.org/2000/svg';\n",
       "\n",
       "  const M = 80;\n",
       "  const N = 80;\n",
       "  const BLOCK_SIZE_M = 16;\n",
       "  const BLOCK_SIZE_N = 20;\n",
       "  const GROUP_SIZE = 2;\n",
       "  const numM = Math.ceil(M / BLOCK_SIZE_M);\n",
       "  const numN = Math.ceil(N / BLOCK_SIZE_N);\n",
       "  const totalPids = numM * numN;\n",
       "  slider.max = String(totalPids - 1);\n",
       "\n",
       "  function node(name, attrs = {}, text = '') {\n",
       "    const element = document.createElementNS(ns, name);\n",
       "    Object.entries(attrs).forEach(([key, value]) => element.setAttribute(key, String(value)));\n",
       "    if (text) element.textContent = text;\n",
       "    return element;\n",
       "  }\n",
       "\n",
       "  function text(x, y, value, attrs = {}) {\n",
       "    return node('text', {\n",
       "      x, y,\n",
       "      fill: 'var(--foreground)',\n",
       "      'font-size': '14',\n",
       "      'text-anchor': 'middle',\n",
       "      'dominant-baseline': 'middle',\n",
       "      ...attrs,\n",
       "    }, value);\n",
       "  }\n",
       "\n",
       "  function mapping(pid) {\n",
       "    const numPidInGroup = GROUP_SIZE * numN;\n",
       "    const groupId = Math.floor(pid / numPidInGroup);\n",
       "    const firstM = groupId * GROUP_SIZE;\n",
       "    const groupSizeAdj = Math.min(numM - firstM, GROUP_SIZE);\n",
       "    const localPid = pid % numPidInGroup;\n",
       "    const pidM = firstM + (localPid % groupSizeAdj);\n",
       "    const pidN = Math.floor(localPid / groupSizeAdj);\n",
       "    return {pid, numPidInGroup, groupId, firstM, groupSizeAdj, localPid, pidM, pidN};\n",
       "  }\n",
       "\n",
       "  const allMappings = Array.from({length: totalPids}, (_, pid) => mapping(pid));\n",
       "\n",
       "  function render() {\n",
       "    const selectedPid = Number(slider.value);\n",
       "    const selected = mapping(selectedPid);\n",
       "    output.value = String(selectedPid);\n",
       "    output.textContent = String(selectedPid);\n",
       "    summary.textContent = `PID ${selectedPid} → group ${selected.groupId} → 组内 PID ${selected.localPid} → 输出块 (PID_M=${selected.pidM}, PID_N=${selected.pidN})`;\n",
       "    svg.replaceChildren();\n",
       "\n",
       "    svg.appendChild(text(20, 22, `示例：M=${M}, N=${N}, BLOCK_SIZE_M=${BLOCK_SIZE_M}, BLOCK_SIZE_N=${BLOCK_SIZE_N}, GROUP_SIZE=${GROUP_SIZE}`, {\n",
       "      'text-anchor': 'start', fill: 'var(--muted-foreground)'\n",
       "    }));\n",
       "\n",
       "    svg.appendChild(text(20, 60, '一维 PID 启动序列', {'text-anchor': 'start', 'font-weight': '500'}));\n",
       "    const stripX = 20;\n",
       "    const stripY = 80;\n",
       "    const stripCell = 40;\n",
       "    allMappings.forEach((item) => {\n",
       "      const x = stripX + item.pid * stripCell;\n",
       "      const isSelected = item.pid === selectedPid;\n",
       "      const fill = isSelected\n",
       "        ? 'var(--primary)'\n",
       "        : `color-mix(in srgb, var(${item.groupId % 2 === 0 ? '--viz-series-2' : '--viz-series-3'}) 22%, transparent)`;\n",
       "      svg.appendChild(node('rect', {\n",
       "        x, y: stripY, width: stripCell - 2, height: 36,\n",
       "        fill, stroke: isSelected ? 'var(--primary)' : 'var(--border)', rx: 2\n",
       "      }));\n",
       "      svg.appendChild(text(x + (stripCell - 2) / 2, stripY + 18, String(item.pid), {\n",
       "        fill: isSelected ? 'var(--primary-foreground)' : 'var(--foreground)',\n",
       "        'font-weight': isSelected ? '500' : '400'\n",
       "      }));\n",
       "    });\n",
       "\n",
       "    for (let groupId = 0; groupId <= Math.floor((totalPids - 1) / (GROUP_SIZE * numN)); groupId += 1) {\n",
       "      const firstPid = groupId * GROUP_SIZE * numN;\n",
       "      const count = Math.min(GROUP_SIZE, numM - groupId * GROUP_SIZE) * numN;\n",
       "      const startX = stripX + firstPid * stripCell;\n",
       "      const endX = startX + count * stripCell - 2;\n",
       "      svg.appendChild(node('path', {\n",
       "        d: `M ${startX} ${stripY + 42} V ${stripY + 48} H ${endX} V ${stripY + 42}`,\n",
       "        fill: 'none', stroke: 'var(--muted-foreground)', 'stroke-width': 1\n",
       "      }));\n",
       "      svg.appendChild(text((startX + endX) / 2, stripY + 62, `group ${groupId}`, {\n",
       "        fill: 'var(--muted-foreground)', 'font-size': '12'\n",
       "      }));\n",
       "    }\n",
       "\n",
       "    const arrowStartX = stripX + selectedPid * stripCell + 19;\n",
       "    svg.appendChild(node('path', {\n",
       "      d: `M ${arrowStartX} ${stripY + 72} C ${arrowStartX} 190, 450 165, 450 215`,\n",
       "      fill: 'none', stroke: 'var(--viz-series-1)', 'stroke-width': 2\n",
       "    }));\n",
       "    svg.appendChild(node('path', {\n",
       "      d: 'M 444 205 L 450 215 L 456 205',\n",
       "      fill: 'none', stroke: 'var(--viz-series-1)', 'stroke-width': 2\n",
       "    }));\n",
       "\n",
       "    const calcX = 20;\n",
       "    const calcY = 190;\n",
       "    svg.appendChild(text(calcX, calcY, '映射计算', {'text-anchor': 'start', 'font-weight': '500'}));\n",
       "    const calculations = [\n",
       "      `num_PID_along_M = ceil(${M}/${BLOCK_SIZE_M}) = ${numM}`,\n",
       "      `num_PID_along_N = ceil(${N}/${BLOCK_SIZE_N}) = ${numN}`,\n",
       "      `num_PID_in_group = ${GROUP_SIZE} × ${numN} = ${selected.numPidInGroup}`,\n",
       "      `group_id = ${selectedPid} // ${selected.numPidInGroup} = ${selected.groupId}`,\n",
       "      `first_PID_in_group_along_M = ${selected.groupId} × ${GROUP_SIZE} = ${selected.firstM}`,\n",
       "      `group_size_adj = min(${numM} - ${selected.firstM}, ${GROUP_SIZE}) = ${selected.groupSizeAdj}`,\n",
       "      `PID_M = ${selected.firstM} + ((${selectedPid} % ${selected.numPidInGroup}) % ${selected.groupSizeAdj}) = ${selected.pidM}`,\n",
       "      `PID_N = (${selectedPid} % ${selected.numPidInGroup}) // ${selected.groupSizeAdj} = ${selected.pidN}`,\n",
       "    ];\n",
       "    calculations.forEach((line, index) => {\n",
       "      const active = index >= 6;\n",
       "      svg.appendChild(text(calcX, calcY + 30 + index * 27, line, {\n",
       "        'text-anchor': 'start',\n",
       "        fill: active ? 'var(--foreground)' : 'var(--muted-foreground)',\n",
       "        'font-size': active ? '14' : '12',\n",
       "        'font-weight': active ? '500' : '400'\n",
       "      }));\n",
       "    });\n",
       "\n",
       "    const gridX = 525;\n",
       "    const gridY = 205;\n",
       "    const cellW = 86;\n",
       "    const cellH = 52;\n",
       "    svg.appendChild(text(gridX, gridY - 28, '输出矩阵 C 的块网格', {'text-anchor': 'start', 'font-weight': '500'}));\n",
       "    for (let n = 0; n < numN; n += 1) {\n",
       "      svg.appendChild(text(gridX + n * cellW + cellW / 2, gridY - 8, `N块 ${n}`, {\n",
       "        'font-size': '12', fill: 'var(--muted-foreground)'\n",
       "      }));\n",
       "    }\n",
       "    for (let m = 0; m < numM; m += 1) {\n",
       "      svg.appendChild(text(gridX - 10, gridY + m * cellH + cellH / 2, `M块 ${m}`, {\n",
       "        'text-anchor': 'end', 'font-size': '12', fill: 'var(--muted-foreground)'\n",
       "      }));\n",
       "      const rowGroup = Math.floor(m / GROUP_SIZE);\n",
       "      if (m % GROUP_SIZE === 0) {\n",
       "        svg.appendChild(node('rect', {\n",
       "          x: gridX - 4, y: gridY + m * cellH - 4,\n",
       "          width: numN * cellW + 8,\n",
       "          height: Math.min(GROUP_SIZE, numM - m) * cellH + 8,\n",
       "          fill: 'none',\n",
       "          stroke: rowGroup % 2 === 0 ? 'var(--viz-series-2)' : 'var(--viz-series-3)',\n",
       "          'stroke-width': 2,\n",
       "          rx: 3\n",
       "        }));\n",
       "      }\n",
       "      for (let n = 0; n < numN; n += 1) {\n",
       "        const item = allMappings.find((entry) => entry.pidM === m && entry.pidN === n);\n",
       "        const isSelected = item.pid === selectedPid;\n",
       "        const fill = isSelected\n",
       "          ? 'var(--primary)'\n",
       "          : `color-mix(in srgb, var(${rowGroup % 2 === 0 ? '--viz-series-2' : '--viz-series-3'}) 18%, transparent)`;\n",
       "        svg.appendChild(node('rect', {\n",
       "          x: gridX + n * cellW,\n",
       "          y: gridY + m * cellH,\n",
       "          width: cellW - 5,\n",
       "          height: cellH - 5,\n",
       "          fill,\n",
       "          stroke: isSelected ? 'var(--primary)' : 'var(--border)',\n",
       "          rx: 2\n",
       "        }));\n",
       "        svg.appendChild(text(gridX + n * cellW + (cellW - 5) / 2, gridY + m * cellH + (cellH - 5) / 2, `PID ${item.pid}`, {\n",
       "          fill: isSelected ? 'var(--primary-foreground)' : 'var(--foreground)',\n",
       "          'font-weight': isSelected ? '500' : '400'\n",
       "        }));\n",
       "      }\n",
       "    }\n",
       "\n",
       "    const lastGroupY = gridY + (numM - 1) * cellH;\n",
       "    svg.appendChild(text(gridX + numN * cellW / 2, lastGroupY + cellH + 18, `最后一组只有 ${numM % GROUP_SIZE || GROUP_SIZE} 行，因此 group_size_adj=${numM % GROUP_SIZE || GROUP_SIZE}`, {\n",
       "      'font-size': '12', fill: 'var(--muted-foreground)'\n",
       "    }));\n",
       "  }\n",
       "\n",
       "  slider.addEventListener('input', render);\n",
       "  previous.addEventListener('click', () => {\n",
       "    slider.value = String((Number(slider.value) - 1 + totalPids) % totalPids);\n",
       "    render();\n",
       "  });\n",
       "  next.addEventListener('click', () => {\n",
       "    slider.value = String((Number(slider.value) + 1) % totalPids);\n",
       "    render();\n",
       "  });\n",
       "  render();\n",
       "})();\n",
       "</script>\n"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 2
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": "",
   "id": "fadfee19ddf922e9"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
