{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "4ce65e98",
   "metadata": {},
   "source": [
    "# Orbital Entanglement Chord Diagram — 250 Synthetic Orbitals\n",
    "\n",
    "Demonstrates the `Entanglement` JS widget for an orbital-entanglement use case on a large system with\n",
    "synthetic single-orbital entropies and mutual information.  Five highly entangled clusters are\n",
    "highlighted with distinct outline colours and grouped together on the ring."
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d2428548",
   "metadata": {},
   "source": [
    "## 1 — Build synthetic data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c033bd86",
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "\n",
    "rng = np.random.default_rng(42)\n",
    "\n",
    "N = 250        # total orbitals\n",
    "N_CORE = 50    # strongly-coupled subset to highlight\n",
    "\n",
    "# ── Single-orbital entropies ─────────────────────────────────────────\n",
    "# Three regimes interleaved across the orbital indices:\n",
    "#   • ~50 \"core\" orbitals with high entropy (near ln 4)\n",
    "#   • ~60 \"medium\" orbitals with moderate entropy\n",
    "#   • ~140 \"spectator\" orbitals with a long decaying tail\n",
    "s1 = np.zeros(N)\n",
    "\n",
    "all_indices = rng.permutation(N)\n",
    "core_idx = np.sort(all_indices[:N_CORE])\n",
    "medium_idx = np.sort(all_indices[N_CORE : N_CORE + 60])\n",
    "tail_idx = np.sort(all_indices[N_CORE + 60 :])\n",
    "\n",
    "s1[core_idx] = rng.beta(2.5, 1.2, len(core_idx)) * np.log(4.0)\n",
    "s1[medium_idx] = rng.beta(1.8, 3.0, len(medium_idx)) * np.log(4.0) * 0.5\n",
    "s1[tail_idx] = np.sort(rng.exponential(0.03, len(tail_idx)))[::-1]\n",
    "\n",
    "# ── Mutual information matrix ──────────────────────────────────────\n",
    "mi = np.zeros((N, N))\n",
    "\n",
    "# 1) Five intra-core clusters of ~10 orbitals each with strong MI\n",
    "cluster_size = len(core_idx) // 5\n",
    "clusters = []\n",
    "for k in range(5):\n",
    "    cl = core_idx[k * cluster_size : (k + 1) * cluster_size]\n",
    "    clusters.append(cl)\n",
    "    for ii, i in enumerate(cl):\n",
    "        for j in cl[ii + 1 :]:\n",
    "            val = rng.beta(3, 1.5) * np.log(16.0) * 0.55\n",
    "            mi[i, j] = mi[j, i] = val\n",
    "\n",
    "# 2) Sparse inter-cluster core links\n",
    "for ii, i in enumerate(core_idx):\n",
    "    for j in core_idx[ii + 1 :]:\n",
    "        if mi[i, j] == 0 and rng.random() < 0.15:\n",
    "            val = rng.exponential(0.08)\n",
    "            mi[i, j] = mi[j, i] = val\n",
    "\n",
    "# 3) Medium orbitals: moderate MI to a few core and medium neighbours\n",
    "for i in medium_idx:\n",
    "    n_core_links = rng.integers(1, 4)\n",
    "    targets = rng.choice(core_idx, size=n_core_links, replace=False)\n",
    "    for j in targets:\n",
    "        val = rng.exponential(0.12)\n",
    "        mi[i, j] = mi[j, i] = val\n",
    "    n_med_links = rng.integers(0, 3)\n",
    "    others = rng.choice(\n",
    "        medium_idx[medium_idx != i],\n",
    "        size=min(n_med_links, len(medium_idx) - 1),\n",
    "        replace=False,\n",
    "    )\n",
    "    for j in others:\n",
    "        if mi[i, j] == 0:\n",
    "            val = rng.exponential(0.06)\n",
    "            mi[i, j] = mi[j, i] = val\n",
    "\n",
    "# 4) Tail orbitals: very sparse, weak links to core or medium\n",
    "for i in tail_idx:\n",
    "    if rng.random() < 0.12:\n",
    "        pool = np.concatenate([core_idx, medium_idx])\n",
    "        j = rng.choice(pool)\n",
    "        val = rng.exponential(0.02)\n",
    "        mi[i, j] = mi[j, i] = val\n",
    "\n",
    "np.fill_diagonal(mi, 0.0)\n",
    "\n",
    "# All five highly-entangled clusters\n",
    "region_a, region_b, region_c, region_d, region_e = clusters\n",
    "\n",
    "print(f\"{N} orbitals, 5 entangled clusters built ({cluster_size} orbitals each)\")\n",
    "for name, region in zip(\"ABCDE\", clusters):\n",
    "    print(f\"  Cluster {name}: {region.tolist()}\")\n",
    "print(f\"s1 range: {s1.min():.4f} – {s1.max():.4f}\")\n",
    "print(f\"MI range: {mi[mi > 0].min():.4f} – {mi.max():.4f}\")\n",
    "print(f\"Non-zero MI pairs: {(mi > 0).sum() // 2}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "b35327c0",
   "metadata": {},
   "source": [
    "## 2 — Display the interactive widget\n",
    "\n",
    "The `Entanglement` widget renders an orbital-entanglement chord diagram\n",
    "directly in the notebook output. Arc length encodes single-orbital\n",
    "entropy; chord thickness encodes mutual information. Five highly\n",
    "entangled regions (clusters of strongly-coupled orbitals) are each\n",
    "outlined in a different colour and, when the grouping toggle is active,\n",
    "placed adjacent on the ring.\n",
    "\n",
    "### Visual options\n",
    "\n",
    "The widget constructor accepts keyword arguments that tune the diagram\n",
    "appearance.  This sample uses:\n",
    "\n",
    "| Option | Value | Effect |\n",
    "|---|---|---|\n",
    "| `gap_deg` | `0.6` | Narrow gap (degrees) between arcs — tight packing for 250 orbitals |\n",
    "| `arc_width` | `0.05` | Thinner arcs (fraction of radius; default `0.08`) |\n",
    "| `mi_threshold` | `0.01` | Hide chords with mutual information below 0.01 to reduce clutter |\n",
    "| `group_selected` | `True` | Reorder arcs so each group's members sit adjacent on the ring |\n",
    "| `width` / `height` | `800` / `880` | Larger viewport (default 600 × 660) |\n",
    "\n",
    "Other options: `radius`, `line_scale`, `s1_vmax`, `mi_vmax`,\n",
    "`selection_color`, `selection_linewidth`, `group_colors`, `title`.\n",
    "See `help(Entanglement)` for the full list."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7469867d",
   "metadata": {},
   "outputs": [],
   "source": [
    "from qsharp_widgets import Entanglement\n",
    "\n",
    "widget = Entanglement(\n",
    "    s1_entropies=s1.tolist(),\n",
    "    mutual_information=mi.tolist(),\n",
    "    labels=[str(i) for i in range(N)],\n",
    "    groups={\n",
    "        \"Region A\": region_a.tolist(),\n",
    "        \"Region B\": region_b.tolist(),\n",
    "        \"Region C\": region_c.tolist(),\n",
    "        \"Region D\": region_d.tolist(),\n",
    "        \"Region E\": region_e.tolist(),\n",
    "    },\n",
    "    title=f\"Synthetic Orbital Entanglement — {N} orbitals (5 entangled regions)\",\n",
    "    group_selected=True,\n",
    "    gap_deg=0.6,\n",
    "    arc_width=0.05,\n",
    "    mi_threshold=0.01,\n",
    "    width=800,\n",
    "    height=880,\n",
    ")\n",
    "widget"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": ".venv",
   "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.12.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
