Skip to content

Commit 943e862

Browse files
nabbelbabbelJan Unsleber
authored andcommitted
Add orbital entanglement diagram widget (#2974)
**Note:** This PR is a lot of AI generated infra to achieve an outcome for testing purposes. While the features work, the way they are done needs some serious work and rethinking. Adds: - orbital entanglement diagram widget - option to generate static SVG from orbital entanglement diagram widget for publications/presentations with python code - option to generate static SVG from histogram - option to generate static SVG from circuit Widget with active space selection (gold): <img width="638" height="842" alt="image" src="https://github.com/user-attachments/assets/2a468f7e-111d-4409-87f2-87bf90e8891f" /> Widget while hovering individual orbital/entangled node: <img width="856" height="851" alt="image" src="https://github.com/user-attachments/assets/8b9b05da-ac89-45cb-9782-5f7355a9d62b" /> --------- Co-authored-by: Jan Unsleber <janunsleber@microsoft.com>
1 parent 4a05464 commit 943e862

6 files changed

Lines changed: 1452 additions & 2 deletions

File tree

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "4ce65e98",
6+
"metadata": {},
7+
"source": [
8+
"# Orbital Entanglement Chord Diagram — 250 Synthetic Orbitals\n",
9+
"\n",
10+
"Demonstrates the `Entanglement` JS widget for an orbital-entanglement use case on a large system with\n",
11+
"synthetic single-orbital entropies and mutual information. Five highly entangled clusters are\n",
12+
"highlighted with distinct outline colours and grouped together on the ring."
13+
]
14+
},
15+
{
16+
"cell_type": "markdown",
17+
"id": "d2428548",
18+
"metadata": {},
19+
"source": [
20+
"## 1 — Build synthetic data"
21+
]
22+
},
23+
{
24+
"cell_type": "code",
25+
"execution_count": null,
26+
"id": "c033bd86",
27+
"metadata": {},
28+
"outputs": [],
29+
"source": [
30+
"import numpy as np\n",
31+
"\n",
32+
"rng = np.random.default_rng(42)\n",
33+
"\n",
34+
"N = 250 # total orbitals\n",
35+
"N_CORE = 50 # strongly-coupled subset to highlight\n",
36+
"\n",
37+
"# ── Single-orbital entropies ─────────────────────────────────────────\n",
38+
"# Three regimes interleaved across the orbital indices:\n",
39+
"# • ~50 \"core\" orbitals with high entropy (near ln 4)\n",
40+
"# • ~60 \"medium\" orbitals with moderate entropy\n",
41+
"# • ~140 \"spectator\" orbitals with a long decaying tail\n",
42+
"s1 = np.zeros(N)\n",
43+
"\n",
44+
"all_indices = rng.permutation(N)\n",
45+
"core_idx = np.sort(all_indices[:N_CORE])\n",
46+
"medium_idx = np.sort(all_indices[N_CORE : N_CORE + 60])\n",
47+
"tail_idx = np.sort(all_indices[N_CORE + 60 :])\n",
48+
"\n",
49+
"s1[core_idx] = rng.beta(2.5, 1.2, len(core_idx)) * np.log(4.0)\n",
50+
"s1[medium_idx] = rng.beta(1.8, 3.0, len(medium_idx)) * np.log(4.0) * 0.5\n",
51+
"s1[tail_idx] = np.sort(rng.exponential(0.03, len(tail_idx)))[::-1]\n",
52+
"\n",
53+
"# ── Mutual information matrix ──────────────────────────────────────\n",
54+
"mi = np.zeros((N, N))\n",
55+
"\n",
56+
"# 1) Five intra-core clusters of ~10 orbitals each with strong MI\n",
57+
"cluster_size = len(core_idx) // 5\n",
58+
"clusters = []\n",
59+
"for k in range(5):\n",
60+
" cl = core_idx[k * cluster_size : (k + 1) * cluster_size]\n",
61+
" clusters.append(cl)\n",
62+
" for ii, i in enumerate(cl):\n",
63+
" for j in cl[ii + 1 :]:\n",
64+
" val = rng.beta(3, 1.5) * np.log(16.0) * 0.55\n",
65+
" mi[i, j] = mi[j, i] = val\n",
66+
"\n",
67+
"# 2) Sparse inter-cluster core links\n",
68+
"for ii, i in enumerate(core_idx):\n",
69+
" for j in core_idx[ii + 1 :]:\n",
70+
" if mi[i, j] == 0 and rng.random() < 0.15:\n",
71+
" val = rng.exponential(0.08)\n",
72+
" mi[i, j] = mi[j, i] = val\n",
73+
"\n",
74+
"# 3) Medium orbitals: moderate MI to a few core and medium neighbours\n",
75+
"for i in medium_idx:\n",
76+
" n_core_links = rng.integers(1, 4)\n",
77+
" targets = rng.choice(core_idx, size=n_core_links, replace=False)\n",
78+
" for j in targets:\n",
79+
" val = rng.exponential(0.12)\n",
80+
" mi[i, j] = mi[j, i] = val\n",
81+
" n_med_links = rng.integers(0, 3)\n",
82+
" others = rng.choice(\n",
83+
" medium_idx[medium_idx != i],\n",
84+
" size=min(n_med_links, len(medium_idx) - 1),\n",
85+
" replace=False,\n",
86+
" )\n",
87+
" for j in others:\n",
88+
" if mi[i, j] == 0:\n",
89+
" val = rng.exponential(0.06)\n",
90+
" mi[i, j] = mi[j, i] = val\n",
91+
"\n",
92+
"# 4) Tail orbitals: very sparse, weak links to core or medium\n",
93+
"for i in tail_idx:\n",
94+
" if rng.random() < 0.12:\n",
95+
" pool = np.concatenate([core_idx, medium_idx])\n",
96+
" j = rng.choice(pool)\n",
97+
" val = rng.exponential(0.02)\n",
98+
" mi[i, j] = mi[j, i] = val\n",
99+
"\n",
100+
"np.fill_diagonal(mi, 0.0)\n",
101+
"\n",
102+
"# All five highly-entangled clusters\n",
103+
"region_a, region_b, region_c, region_d, region_e = clusters\n",
104+
"\n",
105+
"print(f\"{N} orbitals, 5 entangled clusters built ({cluster_size} orbitals each)\")\n",
106+
"for name, region in zip(\"ABCDE\", clusters):\n",
107+
" print(f\" Cluster {name}: {region.tolist()}\")\n",
108+
"print(f\"s1 range: {s1.min():.4f} – {s1.max():.4f}\")\n",
109+
"print(f\"MI range: {mi[mi > 0].min():.4f} – {mi.max():.4f}\")\n",
110+
"print(f\"Non-zero MI pairs: {(mi > 0).sum() // 2}\")"
111+
]
112+
},
113+
{
114+
"cell_type": "markdown",
115+
"id": "b35327c0",
116+
"metadata": {},
117+
"source": [
118+
"## 2 — Display the interactive widget\n",
119+
"\n",
120+
"The `Entanglement` widget renders an orbital-entanglement chord diagram\n",
121+
"directly in the notebook output. Arc length encodes single-orbital\n",
122+
"entropy; chord thickness encodes mutual information. Five highly\n",
123+
"entangled regions (clusters of strongly-coupled orbitals) are each\n",
124+
"outlined in a different colour and, when the grouping toggle is active,\n",
125+
"placed adjacent on the ring.\n",
126+
"\n",
127+
"### Visual options\n",
128+
"\n",
129+
"The widget constructor accepts keyword arguments that tune the diagram\n",
130+
"appearance. This sample uses:\n",
131+
"\n",
132+
"| Option | Value | Effect |\n",
133+
"|---|---|---|\n",
134+
"| `gap_deg` | `0.6` | Narrow gap (degrees) between arcs — tight packing for 250 orbitals |\n",
135+
"| `arc_width` | `0.05` | Thinner arcs (fraction of radius; default `0.08`) |\n",
136+
"| `mi_threshold` | `0.01` | Hide chords with mutual information below 0.01 to reduce clutter |\n",
137+
"| `group_selected` | `True` | Reorder arcs so each group's members sit adjacent on the ring |\n",
138+
"| `width` / `height` | `800` / `880` | Larger viewport (default 600 × 660) |\n",
139+
"\n",
140+
"Other options: `radius`, `line_scale`, `s1_vmax`, `mi_vmax`,\n",
141+
"`selection_color`, `selection_linewidth`, `group_colors`, `title`.\n",
142+
"See `help(Entanglement)` for the full list."
143+
]
144+
},
145+
{
146+
"cell_type": "code",
147+
"execution_count": null,
148+
"id": "7469867d",
149+
"metadata": {},
150+
"outputs": [],
151+
"source": [
152+
"from qsharp_widgets import Entanglement\n",
153+
"\n",
154+
"widget = Entanglement(\n",
155+
" s1_entropies=s1.tolist(),\n",
156+
" mutual_information=mi.tolist(),\n",
157+
" labels=[str(i) for i in range(N)],\n",
158+
" groups={\n",
159+
" \"Region A\": region_a.tolist(),\n",
160+
" \"Region B\": region_b.tolist(),\n",
161+
" \"Region C\": region_c.tolist(),\n",
162+
" \"Region D\": region_d.tolist(),\n",
163+
" \"Region E\": region_e.tolist(),\n",
164+
" },\n",
165+
" title=f\"Synthetic Orbital Entanglement — {N} orbitals (5 entangled regions)\",\n",
166+
" group_selected=True,\n",
167+
" gap_deg=0.6,\n",
168+
" arc_width=0.05,\n",
169+
" mi_threshold=0.01,\n",
170+
" width=800,\n",
171+
" height=880,\n",
172+
")\n",
173+
"widget"
174+
]
175+
}
176+
],
177+
"metadata": {
178+
"kernelspec": {
179+
"display_name": ".venv",
180+
"language": "python",
181+
"name": "python3"
182+
},
183+
"language_info": {
184+
"codemirror_mode": {
185+
"name": "ipython",
186+
"version": 3
187+
},
188+
"file_extension": ".py",
189+
"mimetype": "text/x-python",
190+
"name": "python",
191+
"nbconvert_exporter": "python",
192+
"pygments_lexer": "ipython3",
193+
"version": "3.12.3"
194+
}
195+
},
196+
"nbformat": 4,
197+
"nbformat_minor": 5
198+
}

0 commit comments

Comments
 (0)