Create heatmap.py
Browse files- quread/heatmap.py +110 -0
quread/heatmap.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# quread/heatmap.py
|
| 2 |
+
from __future__ import annotations
|
| 3 |
+
|
| 4 |
+
import csv
|
| 5 |
+
import io
|
| 6 |
+
from dataclasses import dataclass
|
| 7 |
+
from typing import Dict, List, Optional, Tuple
|
| 8 |
+
|
| 9 |
+
import numpy as np
|
| 10 |
+
import matplotlib.pyplot as plt
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
@dataclass
|
| 14 |
+
class HeatmapConfig:
|
| 15 |
+
rows: int = 8 # chip rows
|
| 16 |
+
cols: int = 8 # chip cols
|
| 17 |
+
missing_value: float = 0.0
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _default_qubit_coords(n_qubits: int, rows: int, cols: int) -> Dict[int, Tuple[int, int]]:
|
| 21 |
+
"""
|
| 22 |
+
Simple default mapping: q0.. mapped row-major across the chip grid.
|
| 23 |
+
Override later with user-uploaded mapping if needed.
|
| 24 |
+
"""
|
| 25 |
+
m: Dict[int, Tuple[int, int]] = {}
|
| 26 |
+
for q in range(n_qubits):
|
| 27 |
+
r = q // cols
|
| 28 |
+
c = q % cols
|
| 29 |
+
if r >= rows:
|
| 30 |
+
break
|
| 31 |
+
m[q] = (r, c)
|
| 32 |
+
return m
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def _parse_history_rows(csv_text: str) -> List[dict]:
|
| 36 |
+
"""
|
| 37 |
+
Expects the CSV you generate in Task 2A:
|
| 38 |
+
step,gate,target,control,theta
|
| 39 |
+
"""
|
| 40 |
+
f = io.StringIO(csv_text or "")
|
| 41 |
+
reader = csv.DictReader(f)
|
| 42 |
+
rows = []
|
| 43 |
+
for r in reader:
|
| 44 |
+
rows.append({
|
| 45 |
+
"step": int(r.get("step", "0") or 0),
|
| 46 |
+
"gate": (r.get("gate") or "").strip(),
|
| 47 |
+
"target": r.get("target", "").strip(),
|
| 48 |
+
"control": r.get("control", "").strip(),
|
| 49 |
+
"theta": (r.get("theta") or "").strip(),
|
| 50 |
+
})
|
| 51 |
+
return rows
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def make_activity_heatmap(
|
| 55 |
+
csv_text: str,
|
| 56 |
+
n_qubits: int,
|
| 57 |
+
cfg: Optional[HeatmapConfig] = None,
|
| 58 |
+
qubit_coords: Optional[Dict[int, Tuple[int, int]]] = None,
|
| 59 |
+
) -> plt.Figure:
|
| 60 |
+
"""
|
| 61 |
+
Builds a heatmap counting how many times each qubit participates in an operation.
|
| 62 |
+
- Single-qubit op increments its target qubit.
|
| 63 |
+
- CNOT increments both control and target.
|
| 64 |
+
"""
|
| 65 |
+
cfg = cfg or HeatmapConfig()
|
| 66 |
+
coords = qubit_coords or _default_qubit_coords(n_qubits, cfg.rows, cfg.cols)
|
| 67 |
+
|
| 68 |
+
grid = np.full((cfg.rows, cfg.cols), cfg.missing_value, dtype=float)
|
| 69 |
+
|
| 70 |
+
# count per qubit
|
| 71 |
+
counts = np.zeros((n_qubits,), dtype=float)
|
| 72 |
+
|
| 73 |
+
rows = _parse_history_rows(csv_text)
|
| 74 |
+
for r in rows:
|
| 75 |
+
gate = r["gate"].upper()
|
| 76 |
+
|
| 77 |
+
# target
|
| 78 |
+
if r["target"] != "":
|
| 79 |
+
t = int(r["target"])
|
| 80 |
+
if 0 <= t < n_qubits:
|
| 81 |
+
counts[t] += 1.0
|
| 82 |
+
|
| 83 |
+
# control for CNOT
|
| 84 |
+
if gate == "CNOT" and r["control"] != "":
|
| 85 |
+
c = int(r["control"])
|
| 86 |
+
if 0 <= c < n_qubits:
|
| 87 |
+
counts[c] += 1.0
|
| 88 |
+
|
| 89 |
+
# place into chip grid
|
| 90 |
+
for q, (rr, cc) in coords.items():
|
| 91 |
+
if 0 <= q < n_qubits and 0 <= rr < cfg.rows and 0 <= cc < cfg.cols:
|
| 92 |
+
grid[rr, cc] = counts[q]
|
| 93 |
+
|
| 94 |
+
# plot
|
| 95 |
+
fig, ax = plt.subplots(figsize=(6, 5))
|
| 96 |
+
im = ax.imshow(grid, interpolation="nearest")
|
| 97 |
+
ax.set_title("Qubit activity heatmap (from circuit CSV)")
|
| 98 |
+
ax.set_xlabel("Chip column")
|
| 99 |
+
ax.set_ylabel("Chip row")
|
| 100 |
+
fig.colorbar(im, ax=ax, fraction=0.046, pad=0.04)
|
| 101 |
+
|
| 102 |
+
# annotate qubit ids on mapped cells
|
| 103 |
+
for q, (rr, cc) in coords.items():
|
| 104 |
+
if 0 <= rr < cfg.rows and 0 <= cc < cfg.cols:
|
| 105 |
+
ax.text(cc, rr, f"q{q}", ha="center", va="center", fontsize=9)
|
| 106 |
+
|
| 107 |
+
ax.set_xticks(range(cfg.cols))
|
| 108 |
+
ax.set_yticks(range(cfg.rows))
|
| 109 |
+
fig.tight_layout()
|
| 110 |
+
return fig
|