You've already forked OrthoRoute
mirror of
https://github.com/bbenchoff/OrthoRoute.git
synced 2026-08-22 06:09:32 +00:00
2053 lines
74 KiB
Python
2053 lines
74 KiB
Python
"""Build CSV, Markdown, and SVG comparisons from monster progress journals."""
|
|
|
|
import argparse
|
|
import csv
|
|
import html
|
|
import json
|
|
import math
|
|
import re
|
|
import subprocess
|
|
from functools import lru_cache
|
|
from pathlib import Path
|
|
from typing import Any, Dict, Iterable, List, Optional, Sequence, Tuple
|
|
|
|
|
|
COLORS = (
|
|
"#2563eb",
|
|
"#dc2626",
|
|
"#16a34a",
|
|
"#9333ea",
|
|
"#ea580c",
|
|
"#0891b2",
|
|
"#be123c",
|
|
"#4d7c0f",
|
|
"#7c3aed",
|
|
"#0f766e",
|
|
)
|
|
REPO_ROOT = Path(__file__).resolve().parents[1]
|
|
UNIQUE_EDGE_ACCOUNTING_COMMIT = (
|
|
"43e34e0239cab4b7e0ad29e5ed1ac7e8e8baed51"
|
|
)
|
|
BIDIRECTIONAL_EDGE_RESERVATION_COMMIT = (
|
|
"76eaefd"
|
|
)
|
|
STALL_WINDOW = 8
|
|
STALL_COLUMNS = (
|
|
"layers",
|
|
"run",
|
|
"git_sha",
|
|
"status",
|
|
"edge_accounting",
|
|
"iterations",
|
|
"observation",
|
|
"stall_window",
|
|
"stall_iteration",
|
|
"overuse_at_stall",
|
|
"best_iteration",
|
|
"best_overuse",
|
|
"current_overuse",
|
|
"eligible_for_fit",
|
|
"progress_file",
|
|
)
|
|
|
|
|
|
@lru_cache(maxsize=None)
|
|
def _uses_unique_edge_accounting(git_sha: str) -> bool:
|
|
if not git_sha:
|
|
return False
|
|
result = subprocess.run(
|
|
[
|
|
"git",
|
|
"merge-base",
|
|
"--is-ancestor",
|
|
UNIQUE_EDGE_ACCOUNTING_COMMIT,
|
|
git_sha,
|
|
],
|
|
cwd=REPO_ROOT,
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
check=False,
|
|
)
|
|
return result.returncode == 0
|
|
|
|
|
|
@lru_cache(maxsize=None)
|
|
def _edge_accounting_mode(git_sha: str) -> str:
|
|
if _uses_unique_edge_accounting(git_sha):
|
|
return "unique physical"
|
|
if not git_sha:
|
|
return "legacy directed"
|
|
result = subprocess.run(
|
|
[
|
|
"git",
|
|
"merge-base",
|
|
"--is-ancestor",
|
|
BIDIRECTIONAL_EDGE_RESERVATION_COMMIT,
|
|
git_sha,
|
|
],
|
|
cwd=REPO_ROOT,
|
|
stdout=subprocess.DEVNULL,
|
|
stderr=subprocess.DEVNULL,
|
|
check=False,
|
|
)
|
|
return (
|
|
"paired arcs normalized"
|
|
if result.returncode == 0 else
|
|
"legacy directed"
|
|
)
|
|
|
|
|
|
def _physical_edge_overuse(
|
|
item: Dict[str, Any],
|
|
*,
|
|
edge_accounting_mode: str,
|
|
) -> int:
|
|
edge = int(item.get("edge_overuse", item.get("overuse_total", 0)))
|
|
if edge_accounting_mode == "paired arcs normalized":
|
|
if edge % 2:
|
|
raise ValueError(
|
|
f"directed-arc edge overuse must be paired, got {edge}"
|
|
)
|
|
edge //= 2
|
|
return (
|
|
edge
|
|
+ int(item.get("via_column_overuse", 0))
|
|
+ int(item.get("via_segment_overuse", 0))
|
|
)
|
|
|
|
|
|
def _normalize_iteration(
|
|
item: Dict[str, Any],
|
|
*,
|
|
edge_accounting_mode: str,
|
|
) -> Dict[str, Any]:
|
|
normalized = dict(item)
|
|
physical_edge = _physical_edge_overuse(
|
|
item,
|
|
edge_accounting_mode=edge_accounting_mode,
|
|
)
|
|
normalized["_physical_edge_overuse"] = physical_edge
|
|
if "path_node_overuse_total" in item:
|
|
normalized["_physical_negotiated_overuse"] = (
|
|
physical_edge + int(item["path_node_overuse_total"])
|
|
)
|
|
elif "negotiated_overuse_total" in item:
|
|
node_component = (
|
|
int(item["negotiated_overuse_total"])
|
|
- int(item.get("overuse_total", 0))
|
|
)
|
|
normalized["_physical_negotiated_overuse"] = (
|
|
physical_edge + node_component
|
|
)
|
|
return normalized
|
|
|
|
|
|
def _is_candidate_name(name: str) -> bool:
|
|
"""Include reduced-layer gates and every full-board layer candidate."""
|
|
if "HDI-pcbway_" not in name:
|
|
return False
|
|
return bool(
|
|
re.search(r"Backplane-(?:8L-1024N|14L-1024N|(\d+)L-\1L)", name)
|
|
)
|
|
|
|
|
|
def _label(path: Path, journal: Dict[str, Any]) -> str:
|
|
name = journal.get("run_name", path.stem)
|
|
layers = re.search(r"Backplane-(\d+)L", name)
|
|
nets = re.search(r"-(\d+)N-", name)
|
|
stamp = re.search(r"-(\d{8})_(\d{6})$", name)
|
|
process = (
|
|
"mechanical" if "pcbway_mechanical" in name else
|
|
"ELIC" if "pcbway_elic" in name else "unspecified"
|
|
)
|
|
scope = f"{nets.group(1)} nets" if nets else "8,192 nets"
|
|
identity = str(journal.get("git_sha", ""))[:7]
|
|
if stamp:
|
|
clock = stamp.group(2)
|
|
identity = " ".join(filter(None, (
|
|
identity,
|
|
f"{clock[:2]}:{clock[2:4]}",
|
|
)))
|
|
suffix = f" {identity}" if identity else ""
|
|
return (
|
|
f"{layers.group(1) if layers else '?'}L {scope} "
|
|
f"{process}{suffix}"
|
|
)
|
|
|
|
|
|
def _load(paths: Iterable[Path]) -> List[Dict[str, Any]]:
|
|
runs = []
|
|
for path in sorted(paths, key=lambda item: item.stat().st_mtime):
|
|
journal = json.loads(path.read_text(encoding="utf-8"))
|
|
edge_accounting_mode = _edge_accounting_mode(
|
|
str(journal.get("git_sha", ""))
|
|
)
|
|
iterations = [
|
|
_normalize_iteration(
|
|
item,
|
|
edge_accounting_mode=edge_accounting_mode,
|
|
)
|
|
for item in journal.get("iterations", [])
|
|
]
|
|
if not iterations and journal.get("status") != "starting":
|
|
continue
|
|
runs.append({
|
|
"path": path,
|
|
"journal": journal,
|
|
"iterations": iterations,
|
|
"edge_accounting_mode": edge_accounting_mode,
|
|
"label": _label(path, journal),
|
|
})
|
|
return runs
|
|
|
|
|
|
def _row(run: Dict[str, Any]) -> Dict[str, Any]:
|
|
journal = run["journal"]
|
|
edge_accounting_mode = run.get(
|
|
"edge_accounting_mode", "unique physical"
|
|
)
|
|
iterations = [
|
|
(
|
|
item
|
|
if "_physical_edge_overuse" in item else
|
|
_normalize_iteration(
|
|
item,
|
|
edge_accounting_mode=edge_accounting_mode,
|
|
)
|
|
)
|
|
for item in run["iterations"]
|
|
]
|
|
final = iterations[-1] if iterations else {}
|
|
overuse = [
|
|
int(item.get(
|
|
"_physical_edge_overuse",
|
|
_physical_edge_overuse(
|
|
item,
|
|
edge_accounting_mode=edge_accounting_mode,
|
|
),
|
|
))
|
|
for item in iterations
|
|
]
|
|
negotiated = [
|
|
int(item["_physical_negotiated_overuse"])
|
|
for item in iterations
|
|
if "_physical_negotiated_overuse" in item
|
|
]
|
|
barrels = [
|
|
int(item.get("barrel_conflicts", 0)) for item in iterations
|
|
]
|
|
path_nodes = [
|
|
int(item.get("path_node_conflicts", 0))
|
|
for item in iterations
|
|
]
|
|
best_overuse = (
|
|
min(
|
|
iterations,
|
|
key=lambda item: int(item.get(
|
|
"_physical_edge_overuse",
|
|
_physical_edge_overuse(
|
|
item,
|
|
edge_accounting_mode=edge_accounting_mode,
|
|
),
|
|
)),
|
|
)
|
|
if iterations else {}
|
|
)
|
|
negotiated_iterations = [
|
|
item for item in iterations
|
|
if "_physical_negotiated_overuse" in item
|
|
]
|
|
best_negotiated = (
|
|
min(
|
|
negotiated_iterations,
|
|
key=lambda item: int(item["_physical_negotiated_overuse"]),
|
|
)
|
|
if negotiated_iterations else {}
|
|
)
|
|
best_physical = (
|
|
min(iterations, key=lambda item: int(item.get("barrel_conflicts", 0)))
|
|
if iterations else {}
|
|
)
|
|
best_path_nodes = (
|
|
min(
|
|
iterations,
|
|
key=lambda item: int(
|
|
item.get("path_node_conflicts", 0)
|
|
),
|
|
)
|
|
if iterations else {}
|
|
)
|
|
completion = journal.get("completion", {})
|
|
routed = final.get("routed_nets", journal.get("routed_nets", 0))
|
|
scope_match = re.search(r"(\d[\d,]*) nets", run["label"])
|
|
target = int(scope_match.group(1).replace(",", "")) if scope_match else 0
|
|
return {
|
|
"run": run["label"],
|
|
"status": journal.get("status", "unknown"),
|
|
"iterations": len(iterations),
|
|
"routed_nets": routed or 0,
|
|
"target_nets": completion.get("total_nets", target),
|
|
"complete": bool(completion.get("complete", False)),
|
|
"edge_accounting": edge_accounting_mode,
|
|
"initial_overuse": overuse[0] if overuse else "",
|
|
"best_overuse": min(overuse) if overuse else "",
|
|
"best_overuse_iteration": best_overuse.get("iteration", ""),
|
|
"final_overuse": overuse[-1] if overuse else "",
|
|
"initial_negotiated_overuse": (
|
|
negotiated[0] if negotiated else ""
|
|
),
|
|
"best_negotiated_overuse": (
|
|
min(negotiated) if negotiated else ""
|
|
),
|
|
"best_negotiated_overuse_iteration": best_negotiated.get(
|
|
"iteration", ""
|
|
),
|
|
"final_negotiated_overuse": (
|
|
negotiated[-1] if negotiated else ""
|
|
),
|
|
"initial_physical": barrels[0] if barrels else "",
|
|
"best_physical": min(barrels) if barrels else "",
|
|
"best_physical_iteration": best_physical.get("iteration", ""),
|
|
"final_physical": barrels[-1] if barrels else "",
|
|
"initial_path_nodes": path_nodes[0] if path_nodes else "",
|
|
"best_path_nodes": min(path_nodes) if path_nodes else "",
|
|
"best_path_nodes_iteration": best_path_nodes.get(
|
|
"iteration", ""
|
|
),
|
|
"final_path_nodes": path_nodes[-1] if path_nodes else "",
|
|
"final_pres_fac": final.get("pres_fac", ""),
|
|
"elapsed_seconds": final.get(
|
|
"elapsed_seconds", journal.get("elapsed_seconds", "")
|
|
),
|
|
"progress_file": str(run["path"]),
|
|
}
|
|
|
|
|
|
def _stall_row(
|
|
run: Dict[str, Any],
|
|
plateau_window: int = STALL_WINDOW,
|
|
) -> Optional[Dict[str, Any]]:
|
|
"""Summarize a comparable full-board negotiated-overuse plateau."""
|
|
if "8,192 nets" not in run["label"]:
|
|
return None
|
|
values = [
|
|
(
|
|
int(item["iteration"]),
|
|
int(item["_physical_negotiated_overuse"]),
|
|
)
|
|
for item in run["iterations"]
|
|
if "_physical_negotiated_overuse" in item
|
|
]
|
|
if not values:
|
|
return None
|
|
layer_match = re.search(r"Backplane-(\d+)L", str(
|
|
run["journal"].get("run_name", "")
|
|
))
|
|
if not layer_match:
|
|
return None
|
|
best_position = min(
|
|
range(len(values)),
|
|
key=lambda index: values[index][1],
|
|
)
|
|
best_iteration, best_overuse = values[best_position]
|
|
status = str(run["journal"].get("status", "unknown"))
|
|
completion = run["journal"].get("completion", {})
|
|
converged = (
|
|
best_overuse == 0
|
|
and (
|
|
status == "complete"
|
|
or bool(completion.get("complete", False))
|
|
)
|
|
)
|
|
stalled = (
|
|
len(values) - 1 - best_position >= max(1, plateau_window)
|
|
)
|
|
if converged:
|
|
observation = "converged"
|
|
stall_iteration = best_iteration
|
|
overuse_at_stall = 0
|
|
elif stalled:
|
|
observation = "stalled"
|
|
stall_iteration = values[best_position + plateau_window][0]
|
|
overuse_at_stall = best_overuse
|
|
elif status == "routing":
|
|
observation = "live_not_stalled"
|
|
stall_iteration = ""
|
|
overuse_at_stall = ""
|
|
else:
|
|
observation = "terminal_without_plateau"
|
|
stall_iteration = ""
|
|
overuse_at_stall = ""
|
|
return {
|
|
"layers": int(layer_match.group(1)),
|
|
"run": run["label"],
|
|
"git_sha": str(run["journal"].get("git_sha", "")),
|
|
"status": status,
|
|
"edge_accounting": run["edge_accounting_mode"],
|
|
"iterations": len(values),
|
|
"observation": observation,
|
|
"stall_window": plateau_window,
|
|
"stall_iteration": stall_iteration,
|
|
"overuse_at_stall": overuse_at_stall,
|
|
"best_iteration": best_iteration,
|
|
"best_overuse": best_overuse,
|
|
"current_overuse": values[-1][1],
|
|
"eligible_for_fit": converged or stalled,
|
|
"progress_file": str(run["path"]),
|
|
}
|
|
|
|
|
|
def _accounting_rank(row: Dict[str, Any]) -> int:
|
|
return {
|
|
"unique physical": 2,
|
|
"paired arcs normalized": 1,
|
|
"legacy directed": 0,
|
|
}.get(str(row.get("edge_accounting", "")), -1)
|
|
|
|
|
|
def _selected_stalls(
|
|
rows: Sequence[Dict[str, Any]],
|
|
) -> List[Dict[str, Any]]:
|
|
"""Choose the newest best-accounted stalled run per layer."""
|
|
selected: Dict[int, Tuple[int, int, Dict[str, Any]]] = {}
|
|
for order, row in enumerate(rows):
|
|
if not row.get("eligible_for_fit"):
|
|
continue
|
|
layers = int(row["layers"])
|
|
candidate = (_accounting_rank(row), order, row)
|
|
previous = selected.get(layers)
|
|
if previous is None or candidate[:2] > previous[:2]:
|
|
selected[layers] = candidate
|
|
return [
|
|
selected[layers][2] for layers in sorted(selected)
|
|
]
|
|
|
|
|
|
def _linear_layer_fit(
|
|
rows: Sequence[Dict[str, Any]],
|
|
) -> Optional[Dict[str, float]]:
|
|
"""Fit stalled negotiated overuse versus total copper layer count."""
|
|
selected = _selected_stalls(rows)
|
|
if len(selected) < 2:
|
|
return None
|
|
xs = [float(row["layers"]) for row in selected]
|
|
ys = [float(row["overuse_at_stall"]) for row in selected]
|
|
mean_x = sum(xs) / len(xs)
|
|
mean_y = sum(ys) / len(ys)
|
|
denominator = sum((value - mean_x) ** 2 for value in xs)
|
|
if denominator <= 0:
|
|
return None
|
|
slope = sum(
|
|
(x - mean_x) * (y - mean_y) for x, y in zip(xs, ys)
|
|
) / denominator
|
|
intercept = mean_y - slope * mean_x
|
|
residual = sum(
|
|
(y - (slope * x + intercept)) ** 2
|
|
for x, y in zip(xs, ys)
|
|
)
|
|
total = sum((y - mean_y) ** 2 for y in ys)
|
|
r_squared = 1.0 if total == 0 else 1.0 - residual / total
|
|
zero_layer = -intercept / slope if slope < 0 else math.nan
|
|
return {
|
|
"points": float(len(selected)),
|
|
"slope": slope,
|
|
"intercept": intercept,
|
|
"r_squared": r_squared,
|
|
"zero_layer": zero_layer,
|
|
}
|
|
|
|
|
|
def _write_stall_csv(
|
|
rows: Sequence[Dict[str, Any]],
|
|
path: Path,
|
|
) -> None:
|
|
with path.open("w", newline="", encoding="utf-8") as stream:
|
|
writer = csv.DictWriter(stream, fieldnames=STALL_COLUMNS)
|
|
writer.writeheader()
|
|
writer.writerows(rows)
|
|
|
|
|
|
def _write_stall_markdown(
|
|
rows: Sequence[Dict[str, Any]],
|
|
path: Path,
|
|
) -> None:
|
|
columns = (
|
|
"layers",
|
|
"run",
|
|
"status",
|
|
"edge_accounting",
|
|
"iterations",
|
|
"observation",
|
|
"stall_iteration",
|
|
"overuse_at_stall",
|
|
"best_iteration",
|
|
"best_overuse",
|
|
"current_overuse",
|
|
"eligible_for_fit",
|
|
)
|
|
lines = [
|
|
"# Full-board layer/stall congestion",
|
|
"",
|
|
(
|
|
f"A stall means no new minimum in the complete normalized "
|
|
f"PathFinder objective for {STALL_WINDOW} consecutive "
|
|
"iterations. The objective is physical edge/via-pool excess "
|
|
"plus capacity-one graph-node excess; it is not a KiCad DRC "
|
|
"count. Zero-congestion convergence is also a fit-eligible "
|
|
"observation."
|
|
),
|
|
"",
|
|
"| " + " | ".join(columns) + " |",
|
|
"|" + "|".join("---" for _ in columns) + "|",
|
|
]
|
|
for row in rows:
|
|
lines.append(
|
|
"| " + " | ".join(str(row[column]) for column in columns)
|
|
+ " |"
|
|
)
|
|
fit = _linear_layer_fit(rows)
|
|
lines.extend(["", "## Extrapolation", ""])
|
|
if fit is None:
|
|
lines.append(
|
|
"Withheld: at least two distinct total-layer counts with "
|
|
"comparable full-board stalls or convergences are required. "
|
|
"A single-layer history cannot identify a layer-capacity slope."
|
|
)
|
|
else:
|
|
zero = fit["zero_layer"]
|
|
zero_text = (
|
|
f"{zero:.2f} layers"
|
|
if math.isfinite(zero) else
|
|
"not projected because the fitted slope is non-negative"
|
|
)
|
|
lines.append(
|
|
f"Ordinary least-squares fit over {int(fit['points'])} "
|
|
f"selected layer counts: slope {fit['slope']:.1f} excess "
|
|
f"uses/layer, R²={fit['r_squared']:.3f}, zero intercept "
|
|
f"{zero_text}. This is a planning extrapolation, not a "
|
|
"routability proof."
|
|
)
|
|
lines.extend([
|
|
"",
|
|
"For each layer count, the fit prefers unique-physical accounting, "
|
|
"then paired-arc-normalized accounting, and uses the newest "
|
|
"available stalled or converged run at that accounting quality.",
|
|
"",
|
|
])
|
|
path.write_text("\n".join(lines), encoding="utf-8", newline="\n")
|
|
|
|
|
|
def _write_stall_svg(
|
|
rows: Sequence[Dict[str, Any]],
|
|
path: Path,
|
|
) -> None:
|
|
width, height = 1200, 820
|
|
plot_x, plot_y, plot_width, plot_height = 110, 120, 980, 540
|
|
layer_values = [14, 16, 18, 20, 24, 28, 32]
|
|
if rows:
|
|
layer_values.extend(int(row["layers"]) for row in rows)
|
|
min_layer = min(layer_values)
|
|
max_layer = max(layer_values)
|
|
y_values = [
|
|
int(row["overuse_at_stall"] or row["best_overuse"])
|
|
for row in rows
|
|
]
|
|
y_max = max(y_values, default=1)
|
|
y_max = max(1, int(math.ceil(y_max * 1.10 / 10_000) * 10_000))
|
|
|
|
def x_at(layers: float) -> float:
|
|
return plot_x + plot_width * (
|
|
(layers - min_layer) / max(1, max_layer - min_layer)
|
|
)
|
|
|
|
def y_at(value: float) -> float:
|
|
return plot_y + plot_height * (1.0 - value / y_max)
|
|
|
|
svg = [
|
|
f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" '
|
|
f'height="{height}" viewBox="0 0 {width} {height}">',
|
|
'<rect width="100%" height="100%" fill="#ffffff"/>',
|
|
'<text x="60" y="42" font-family="sans-serif" font-size="25" '
|
|
'font-weight="bold">Full-board plateau congestion vs copper '
|
|
'layers</text>',
|
|
'<text x="60" y="72" font-family="sans-serif" font-size="14" '
|
|
'fill="#475569">8,192 nets · 0.4 mm grid · physical edge/via '
|
|
'+ exact node excess · linear scale to zero</text>',
|
|
f'<rect x="{plot_x}" y="{plot_y}" width="{plot_width}" '
|
|
f'height="{plot_height}" fill="#fafafa" stroke="#94a3b8"/>',
|
|
]
|
|
for tick in range(6):
|
|
value = y_max * (1.0 - tick / 5)
|
|
y = plot_y + plot_height * tick / 5
|
|
svg.extend([
|
|
f'<line x1="{plot_x}" y1="{y:.1f}" '
|
|
f'x2="{plot_x + plot_width}" y2="{y:.1f}" '
|
|
'stroke="#e2e8f0"/>',
|
|
f'<text x="{plot_x - 10}" y="{y + 4:.1f}" '
|
|
'text-anchor="end" font-family="monospace" font-size="12">'
|
|
f'{int(value):,}</text>',
|
|
])
|
|
for layers in range(min_layer, max_layer + 1, 2):
|
|
x = x_at(layers)
|
|
svg.extend([
|
|
f'<line x1="{x:.1f}" y1="{plot_y}" x2="{x:.1f}" '
|
|
f'y2="{plot_y + plot_height}" stroke="#f1f5f9"/>',
|
|
f'<text x="{x:.1f}" y="{plot_y + plot_height + 24}" '
|
|
'text-anchor="middle" font-family="sans-serif" font-size="13">'
|
|
f'{layers}</text>',
|
|
])
|
|
svg.extend([
|
|
f'<text x="{plot_x + plot_width / 2:.1f}" '
|
|
f'y="{plot_y + plot_height + 56}" text-anchor="middle" '
|
|
'font-family="sans-serif" font-size="14">Total copper layers</text>',
|
|
f'<text x="25" y="{plot_y + plot_height / 2:.1f}" '
|
|
'transform="rotate(-90 25 '
|
|
f'{plot_y + plot_height / 2:.1f})" text-anchor="middle" '
|
|
'font-family="sans-serif" font-size="14">'
|
|
'Negotiated excess uses at plateau</text>',
|
|
])
|
|
|
|
selected = _selected_stalls(rows)
|
|
fit = _linear_layer_fit(rows)
|
|
if fit is not None:
|
|
x1, x2 = float(min_layer), float(max_layer)
|
|
y1 = max(0.0, fit["slope"] * x1 + fit["intercept"])
|
|
y2 = max(0.0, fit["slope"] * x2 + fit["intercept"])
|
|
svg.append(
|
|
f'<line x1="{x_at(x1):.1f}" y1="{y_at(y1):.1f}" '
|
|
f'x2="{x_at(x2):.1f}" y2="{y_at(y2):.1f}" '
|
|
'stroke="#0f172a" stroke-width="2" stroke-dasharray="8 6"/>'
|
|
)
|
|
|
|
for index, row in enumerate(rows):
|
|
value = int(row["overuse_at_stall"] or row["best_overuse"])
|
|
x = x_at(float(row["layers"]))
|
|
x += ((index % 5) - 2) * 5
|
|
y = y_at(value)
|
|
selected_point = row in selected
|
|
color = (
|
|
"#16a34a"
|
|
if row["observation"] == "converged" else
|
|
"#dc2626"
|
|
if row["eligible_for_fit"] else
|
|
"#2563eb"
|
|
)
|
|
if row["eligible_for_fit"]:
|
|
svg.append(
|
|
f'<circle cx="{x:.1f}" cy="{y:.1f}" r="7" '
|
|
f'fill="{color}" stroke="#ffffff" stroke-width="2"/>'
|
|
)
|
|
else:
|
|
svg.append(
|
|
f'<rect x="{x - 6:.1f}" y="{y - 6:.1f}" width="12" '
|
|
f'height="12" fill="#ffffff" stroke="{color}" '
|
|
'stroke-width="3"/>'
|
|
)
|
|
weight = "bold" if selected_point else "normal"
|
|
state = str(row["observation"]).replace("_", " ")
|
|
short_label = (
|
|
f"{row['layers']}L {str(row['git_sha'])[:7]} "
|
|
f"{state}: {value:,}"
|
|
)
|
|
label_y = y - 10 - (index % 4) * 18
|
|
svg.append(
|
|
f'<text x="{x + 10:.1f}" y="{label_y:.1f}" '
|
|
f'font-family="sans-serif" font-size="11" '
|
|
f'font-weight="{weight}">{html.escape(short_label)}</text>'
|
|
)
|
|
if fit is None:
|
|
fit_text = (
|
|
"Trend withheld: need comparable stall/convergence points at "
|
|
"≥2 distinct layer counts"
|
|
)
|
|
else:
|
|
zero = fit["zero_layer"]
|
|
zero_text = (
|
|
f"zero intercept ≈ {zero:.2f} layers"
|
|
if math.isfinite(zero) else
|
|
"no finite zero intercept"
|
|
)
|
|
fit_text = (
|
|
f"OLS slope {fit['slope']:.1f} excess/layer; "
|
|
f"R² {fit['r_squared']:.3f}; {zero_text}"
|
|
)
|
|
svg.extend([
|
|
f'<text x="{plot_x}" y="735" font-family="sans-serif" '
|
|
f'font-size="14" font-weight="bold">{html.escape(fit_text)}</text>',
|
|
f'<circle cx="{plot_x}" cy="775" r="7" fill="#dc2626"/>',
|
|
f'<text x="{plot_x + 14}" y="779" font-family="sans-serif" '
|
|
'font-size="12">observed ≥8-iteration stall</text>',
|
|
f'<rect x="{plot_x + 310}" y="769" width="12" height="12" '
|
|
'fill="#ffffff" stroke="#2563eb" stroke-width="3"/>',
|
|
f'<text x="{plot_x + 330}" y="779" font-family="sans-serif" '
|
|
'font-size="12">live/terminal point without observed stall</text>',
|
|
f'<circle cx="{plot_x + 720}" cy="775" r="7" fill="#16a34a"/>',
|
|
f'<text x="{plot_x + 734}" y="779" font-family="sans-serif" '
|
|
'font-size="12">zero-congestion convergence</text>',
|
|
"</svg>",
|
|
])
|
|
path.write_text("\n".join(svg), encoding="utf-8", newline="\n")
|
|
|
|
|
|
def _write_csv(rows: Sequence[Dict[str, Any]], path: Path) -> None:
|
|
with path.open("w", newline="", encoding="utf-8") as stream:
|
|
writer = csv.DictWriter(stream, fieldnames=list(rows[0]))
|
|
writer.writeheader()
|
|
writer.writerows(rows)
|
|
|
|
|
|
def _write_markdown(rows: Sequence[Dict[str, Any]], path: Path) -> None:
|
|
columns = (
|
|
"run", "status", "iterations", "routed_nets", "target_nets",
|
|
"complete", "edge_accounting",
|
|
"initial_overuse", "best_overuse", "final_overuse",
|
|
"best_overuse_iteration", "initial_physical", "best_physical",
|
|
"best_physical_iteration", "final_physical",
|
|
"initial_path_nodes", "best_path_nodes",
|
|
"best_path_nodes_iteration", "final_path_nodes",
|
|
"initial_negotiated_overuse", "best_negotiated_overuse",
|
|
"best_negotiated_overuse_iteration",
|
|
"final_negotiated_overuse",
|
|
"final_pres_fac",
|
|
"elapsed_seconds",
|
|
)
|
|
lines = [
|
|
"# Reduced-layer monster route comparison",
|
|
"",
|
|
"| " + " | ".join(columns) + " |",
|
|
"|" + "|".join("---" for _ in columns) + "|",
|
|
]
|
|
for row in rows:
|
|
lines.append(
|
|
"| " + " | ".join(str(row[column]) for column in columns) + " |"
|
|
)
|
|
lines.extend([
|
|
"",
|
|
"Generated from the per-iteration progress JSON journals. "
|
|
"A `routing` status can mean either an active process or a preserved "
|
|
"stopped checkpoint; consult `CODEX_SUMMARY.md` for lifecycle notes.",
|
|
"",
|
|
])
|
|
path.write_text("\n".join(lines), encoding="utf-8", newline="\n")
|
|
|
|
|
|
def _points(
|
|
values: Sequence[float],
|
|
x0: float,
|
|
y0: float,
|
|
width: float,
|
|
height: float,
|
|
max_iterations: int,
|
|
max_value: float,
|
|
*,
|
|
log_scale: bool = True,
|
|
) -> str:
|
|
result = []
|
|
log_max = math.log10(max(1.0, max_value))
|
|
for index, value in enumerate(values, start=1):
|
|
x = x0 + width * (index - 1) / max(1, max_iterations - 1)
|
|
if log_scale:
|
|
normalized = (
|
|
math.log10(max(1.0, value)) / max(1.0, log_max)
|
|
)
|
|
else:
|
|
normalized = max(0.0, value) / max(1.0, max_value)
|
|
y = y0 + height * (1.0 - normalized)
|
|
result.append(f"{x:.1f},{y:.1f}")
|
|
return " ".join(result)
|
|
|
|
|
|
def _write_svg(runs: Sequence[Dict[str, Any]], path: Path) -> None:
|
|
width, height = 1200, 1660
|
|
plot_x, plot_width = 90, 1040
|
|
panel_height = 200
|
|
panels = [
|
|
(
|
|
"Unique physical edge/via overuse (log scale)",
|
|
"_physical_edge_overuse",
|
|
90,
|
|
True,
|
|
),
|
|
(
|
|
"Physical conflict reports (log scale)",
|
|
"barrel_conflicts",
|
|
390,
|
|
True,
|
|
),
|
|
(
|
|
"Shared capacity-one path nodes (log scale)",
|
|
"path_node_conflicts",
|
|
690,
|
|
True,
|
|
),
|
|
(
|
|
"Negotiated edge + exact node excess (log scale)",
|
|
"_physical_negotiated_overuse",
|
|
990,
|
|
True,
|
|
),
|
|
("Present congestion pressure", "pres_fac", 1290, False),
|
|
]
|
|
max_iterations = max(
|
|
(len(run["iterations"]) for run in runs), default=1
|
|
)
|
|
svg = [
|
|
f'<svg xmlns="http://www.w3.org/2000/svg" '
|
|
f'width="{width}" height="{height}" viewBox="0 0 {width} {height}">',
|
|
'<rect width="100%" height="100%" fill="#ffffff"/>',
|
|
'<text x="60" y="38" font-family="sans-serif" font-size="24" '
|
|
'font-weight="bold">Reduced-layer monster routing progress</text>',
|
|
]
|
|
for title, key, y0, log_scale in panels:
|
|
maximum = max(
|
|
(
|
|
float(item[key])
|
|
for run in runs
|
|
for item in run["iterations"]
|
|
if key in item
|
|
),
|
|
default=1.0,
|
|
)
|
|
svg.extend([
|
|
f'<text x="{plot_x}" y="{y0 - 18}" font-family="sans-serif" '
|
|
f'font-size="16">{html.escape(title)}</text>',
|
|
f'<rect x="{plot_x}" y="{y0}" width="{plot_width}" '
|
|
f'height="{panel_height}" fill="#fafafa" stroke="#cbd5e1"/>',
|
|
])
|
|
for tick in range(5):
|
|
y = y0 + panel_height * tick / 4
|
|
if log_scale:
|
|
exponent = math.log10(maximum) * (1.0 - tick / 4)
|
|
value = f"{int(10 ** exponent):,}"
|
|
else:
|
|
value = f"{maximum * (1.0 - tick / 4):.1f}"
|
|
svg.extend([
|
|
f'<line x1="{plot_x}" y1="{y:.1f}" '
|
|
f'x2="{plot_x + plot_width}" y2="{y:.1f}" '
|
|
'stroke="#e2e8f0"/>',
|
|
f'<text x="{plot_x - 8}" y="{y + 4:.1f}" '
|
|
'text-anchor="end" font-family="monospace" font-size="11">'
|
|
f'{value}</text>',
|
|
])
|
|
for run_index, run in enumerate(runs):
|
|
values = [
|
|
float(item[key]) for item in run["iterations"]
|
|
if key in item
|
|
]
|
|
if not values:
|
|
continue
|
|
points = _points(
|
|
values, plot_x, y0, plot_width, panel_height,
|
|
max_iterations, maximum, log_scale=log_scale,
|
|
)
|
|
color = COLORS[run_index % len(COLORS)]
|
|
svg.append(
|
|
f'<polyline points="{points}" fill="none" '
|
|
f'stroke="{color}" stroke-width="2"/>'
|
|
)
|
|
for tick in range(0, max_iterations + 1, 10):
|
|
x = plot_x + plot_width * tick / max(1, max_iterations)
|
|
svg.append(
|
|
f'<text x="{x:.1f}" y="{y0 + panel_height + 18}" '
|
|
'text-anchor="middle" font-family="sans-serif" '
|
|
f'font-size="11">{tick}</text>'
|
|
)
|
|
legend_y = 1570
|
|
for index, run in enumerate(runs):
|
|
color = COLORS[index % len(COLORS)]
|
|
x = 80 + (index % 3) * 370
|
|
y = legend_y + (index // 3) * 24
|
|
svg.extend([
|
|
f'<line x1="{x}" y1="{y}" x2="{x + 24}" y2="{y}" '
|
|
f'stroke="{color}" stroke-width="4"/>',
|
|
f'<text x="{x + 32}" y="{y + 4}" font-family="sans-serif" '
|
|
f'font-size="12">{html.escape(run["label"])}</text>',
|
|
])
|
|
svg.append("</svg>")
|
|
path.write_text("\n".join(svg), encoding="utf-8", newline="\n")
|
|
|
|
|
|
def _latest_layer_node_snapshot(
|
|
runs: Sequence[Dict[str, Any]],
|
|
) -> Optional[Dict[str, Any]]:
|
|
"""Return the newest iteration with layer-local path-node telemetry."""
|
|
for run in reversed(runs):
|
|
for iteration in reversed(run["iterations"]):
|
|
layers = iteration.get("path_node_layers")
|
|
if not layers:
|
|
continue
|
|
rows = []
|
|
for item in layers:
|
|
capacity = int(item["capacity_nodes"])
|
|
occupied = int(item["occupied_nodes"])
|
|
conflicts = int(item["conflict_nodes"])
|
|
layer = int(item["layer"])
|
|
rows.append({
|
|
"run": run["label"],
|
|
"iteration": int(iteration["iteration"]),
|
|
"layer": layer,
|
|
"role": (
|
|
"outer"
|
|
if layer in {0, len(layers) - 1}
|
|
else "internal"
|
|
),
|
|
"capacity_nodes": capacity,
|
|
"occupied_nodes": occupied,
|
|
"occupied_pct": round(
|
|
100.0 * occupied / max(1, capacity), 3
|
|
),
|
|
"conflict_nodes": conflicts,
|
|
"conflict_pct": round(
|
|
100.0 * conflicts / max(1, capacity), 3
|
|
),
|
|
"excess_uses": int(item["excess_uses"]),
|
|
"max_use": int(item["max_use"]),
|
|
})
|
|
return {
|
|
"run": run["label"],
|
|
"iteration": int(iteration["iteration"]),
|
|
"rows": rows,
|
|
}
|
|
return None
|
|
|
|
|
|
def _write_layer_node_csv(
|
|
snapshot: Dict[str, Any],
|
|
path: Path,
|
|
) -> None:
|
|
rows = snapshot["rows"]
|
|
with path.open("w", newline="", encoding="utf-8") as stream:
|
|
writer = csv.DictWriter(stream, fieldnames=list(rows[0]))
|
|
writer.writeheader()
|
|
writer.writerows(rows)
|
|
|
|
|
|
def _write_layer_node_markdown(
|
|
snapshot: Dict[str, Any],
|
|
path: Path,
|
|
) -> None:
|
|
columns = (
|
|
"layer", "role", "capacity_nodes", "occupied_nodes",
|
|
"occupied_pct", "conflict_nodes", "conflict_pct",
|
|
"excess_uses", "max_use",
|
|
)
|
|
lines = [
|
|
"# Latest layer-local path-node congestion",
|
|
"",
|
|
f"Run: `{snapshot['run']}` ",
|
|
f"Iteration: {snapshot['iteration']}",
|
|
"",
|
|
"| " + " | ".join(columns) + " |",
|
|
"|" + "|".join("---" for _ in columns) + "|",
|
|
]
|
|
for row in snapshot["rows"]:
|
|
lines.append(
|
|
"| " + " | ".join(str(row[column]) for column in columns)
|
|
+ " |"
|
|
)
|
|
lines.extend([
|
|
"",
|
|
"`occupied_pct` is distinct path-node occupancy divided by the "
|
|
"layer's lattice-node capacity. `conflict_pct` counts capacity-one "
|
|
"nodes used by multiple nets; `excess_uses` includes every use above "
|
|
"one. Outer layers are shown for completeness but are not routing "
|
|
"fabric in the same sense as the internal layers.",
|
|
"",
|
|
])
|
|
path.write_text("\n".join(lines), encoding="utf-8", newline="\n")
|
|
|
|
|
|
def _write_layer_node_svg(
|
|
snapshot: Dict[str, Any],
|
|
path: Path,
|
|
) -> None:
|
|
width, height = 1200, 820
|
|
plot_x, plot_width = 90, 1040
|
|
rows = snapshot["rows"]
|
|
bar_step = plot_width / max(1, len(rows))
|
|
bar_width = max(4.0, bar_step * 0.64)
|
|
occupied_y, occupied_height = 105, 245
|
|
excess_y, excess_height = 465, 245
|
|
max_excess = max(
|
|
(int(row["excess_uses"]) for row in rows),
|
|
default=1,
|
|
)
|
|
svg = [
|
|
f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" '
|
|
f'height="{height}" viewBox="0 0 {width} {height}">',
|
|
'<rect width="100%" height="100%" fill="#ffffff"/>',
|
|
'<text x="60" y="38" font-family="sans-serif" font-size="24" '
|
|
'font-weight="bold">Layer-local path-node congestion</text>',
|
|
f'<text x="60" y="66" font-family="sans-serif" font-size="14">'
|
|
f'{html.escape(snapshot["run"])} — iteration '
|
|
f'{snapshot["iteration"]}</text>',
|
|
f'<text x="{plot_x}" y="90" font-family="sans-serif" '
|
|
'font-size="16">Distinct occupied nodes (% of layer capacity)</text>',
|
|
f'<rect x="{plot_x}" y="{occupied_y}" width="{plot_width}" '
|
|
f'height="{occupied_height}" fill="#fafafa" stroke="#cbd5e1"/>',
|
|
f'<text x="{plot_x}" y="450" font-family="sans-serif" '
|
|
'font-size="16">Excess uses on capacity-one nodes</text>',
|
|
f'<rect x="{plot_x}" y="{excess_y}" width="{plot_width}" '
|
|
f'height="{excess_height}" fill="#fafafa" stroke="#cbd5e1"/>',
|
|
]
|
|
for tick in range(5):
|
|
occupied_value = 100 - tick * 25
|
|
occupied_line_y = occupied_y + occupied_height * tick / 4
|
|
excess_value = max_excess * (1.0 - tick / 4)
|
|
excess_line_y = excess_y + excess_height * tick / 4
|
|
svg.extend([
|
|
f'<line x1="{plot_x}" y1="{occupied_line_y:.1f}" '
|
|
f'x2="{plot_x + plot_width}" y2="{occupied_line_y:.1f}" '
|
|
'stroke="#e2e8f0"/>',
|
|
f'<text x="{plot_x - 8}" y="{occupied_line_y + 4:.1f}" '
|
|
'text-anchor="end" font-family="monospace" font-size="11">'
|
|
f'{occupied_value}%</text>',
|
|
f'<line x1="{plot_x}" y1="{excess_line_y:.1f}" '
|
|
f'x2="{plot_x + plot_width}" y2="{excess_line_y:.1f}" '
|
|
'stroke="#e2e8f0"/>',
|
|
f'<text x="{plot_x - 8}" y="{excess_line_y + 4:.1f}" '
|
|
'text-anchor="end" font-family="monospace" font-size="11">'
|
|
f'{int(excess_value):,}</text>',
|
|
])
|
|
for index, row in enumerate(rows):
|
|
center = plot_x + (index + 0.5) * bar_step
|
|
occupied_height_value = (
|
|
occupied_height * float(row["occupied_pct"]) / 100.0
|
|
)
|
|
excess_height_value = (
|
|
excess_height * int(row["excess_uses"])
|
|
/ max(1, max_excess)
|
|
)
|
|
fill = "#94a3b8" if row["role"] == "outer" else "#2563eb"
|
|
svg.extend([
|
|
f'<rect x="{center - bar_width / 2:.1f}" '
|
|
f'y="{occupied_y + occupied_height - occupied_height_value:.1f}" '
|
|
f'width="{bar_width:.1f}" height="{occupied_height_value:.1f}" '
|
|
f'fill="{fill}"><title>L{row["layer"]}: '
|
|
f'{row["occupied_pct"]}% occupied, '
|
|
f'{row["conflict_nodes"]:,} conflict nodes</title></rect>',
|
|
f'<rect x="{center - bar_width / 2:.1f}" '
|
|
f'y="{excess_y + excess_height - excess_height_value:.1f}" '
|
|
f'width="{bar_width:.1f}" height="{excess_height_value:.1f}" '
|
|
'fill="#dc2626"><title>'
|
|
f'L{row["layer"]}: {row["excess_uses"]:,} excess uses, '
|
|
f'max use {row["max_use"]}</title></rect>',
|
|
f'<text x="{center:.1f}" y="733" text-anchor="middle" '
|
|
'font-family="sans-serif" font-size="11">'
|
|
f'L{row["layer"]}</text>',
|
|
])
|
|
svg.extend([
|
|
'<rect x="90" y="770" width="14" height="14" fill="#2563eb"/>',
|
|
'<text x="112" y="782" font-family="sans-serif" font-size="12">'
|
|
'internal layer occupancy</text>',
|
|
'<rect x="300" y="770" width="14" height="14" fill="#94a3b8"/>',
|
|
'<text x="322" y="782" font-family="sans-serif" font-size="12">'
|
|
'outer layer occupancy</text>',
|
|
'<rect x="500" y="770" width="14" height="14" fill="#dc2626"/>',
|
|
'<text x="522" y="782" font-family="sans-serif" font-size="12">'
|
|
'capacity-one excess uses</text>',
|
|
"</svg>",
|
|
])
|
|
path.write_text("\n".join(svg), encoding="utf-8", newline="\n")
|
|
|
|
|
|
def _latest_layer_balance_history(
|
|
runs: Sequence[Dict[str, Any]],
|
|
) -> Optional[Dict[str, Any]]:
|
|
"""Summarize shallow/middle/deep node excess across one live run."""
|
|
for run in reversed(runs):
|
|
rows = []
|
|
ranges = None
|
|
for iteration in run.get("iterations", []):
|
|
layers = iteration.get("path_node_layers")
|
|
if not layers or len(layers) < 5:
|
|
continue
|
|
internal = list(layers[1:-1])
|
|
groups = [[], [], []]
|
|
for index, item in enumerate(internal):
|
|
group = min(2, index * 3 // len(internal))
|
|
groups[group].append(item)
|
|
if any(not group for group in groups):
|
|
continue
|
|
if ranges is None:
|
|
ranges = [
|
|
f"L{int(group[0]['layer'])}-L{int(group[-1]['layer'])}"
|
|
for group in groups
|
|
]
|
|
excess = [
|
|
sum(int(item["excess_uses"]) for item in group)
|
|
for group in groups
|
|
]
|
|
total = sum(excess)
|
|
negotiated = iteration.get(
|
|
"_physical_negotiated_overuse",
|
|
iteration.get("negotiated_overuse_total"),
|
|
)
|
|
rows.append({
|
|
"run": run["label"],
|
|
"iteration": int(iteration["iteration"]),
|
|
"negotiated_overuse": (
|
|
None if negotiated is None else int(negotiated)
|
|
),
|
|
"internal_node_excess": total,
|
|
"shallow_excess": excess[0],
|
|
"shallow_pct": round(
|
|
100.0 * excess[0] / max(1, total), 3
|
|
),
|
|
"middle_excess": excess[1],
|
|
"middle_pct": round(
|
|
100.0 * excess[1] / max(1, total), 3
|
|
),
|
|
"deep_excess": excess[2],
|
|
"deep_pct": round(
|
|
100.0 * excess[2] / max(1, total), 3
|
|
),
|
|
})
|
|
if rows:
|
|
return {
|
|
"run": run["label"],
|
|
"ranges": ranges,
|
|
"rows": rows,
|
|
}
|
|
return None
|
|
|
|
|
|
def _write_layer_balance_csv(
|
|
snapshot: Dict[str, Any],
|
|
path: Path,
|
|
) -> None:
|
|
rows = snapshot["rows"]
|
|
with path.open("w", newline="", encoding="utf-8") as stream:
|
|
writer = csv.DictWriter(stream, fieldnames=list(rows[0]))
|
|
writer.writeheader()
|
|
writer.writerows(rows)
|
|
|
|
|
|
def _write_layer_balance_markdown(
|
|
snapshot: Dict[str, Any],
|
|
path: Path,
|
|
) -> None:
|
|
shallow, middle, deep = snapshot["ranges"]
|
|
columns = (
|
|
"iteration", "negotiated_overuse", "internal_node_excess",
|
|
"shallow_excess", "shallow_pct", "middle_excess", "middle_pct",
|
|
"deep_excess", "deep_pct",
|
|
)
|
|
lines = [
|
|
"# Layer-balance history",
|
|
"",
|
|
f"Run: `{snapshot['run']}` ",
|
|
f"Relative fabric thirds: shallow `{shallow}`, middle `{middle}`, "
|
|
f"deep `{deep}`.",
|
|
"",
|
|
"| " + " | ".join(columns) + " |",
|
|
"|" + "|".join("---" for _ in columns) + "|",
|
|
]
|
|
for row in snapshot["rows"]:
|
|
lines.append(
|
|
"| " + " | ".join(str(row[column]) for column in columns)
|
|
+ " |"
|
|
)
|
|
lines.extend([
|
|
"",
|
|
"Each band contains one third of the internal routing layers. "
|
|
"Percentages divide that band's path-node excess by total internal "
|
|
"path-node excess for the same iteration. The graph objective also "
|
|
"includes edge and via-pool excess, so `negotiated_overuse` can "
|
|
"differ from `internal_node_excess`.",
|
|
"",
|
|
])
|
|
path.write_text("\n".join(lines), encoding="utf-8", newline="\n")
|
|
|
|
|
|
def _write_layer_balance_svg(
|
|
snapshot: Dict[str, Any],
|
|
path: Path,
|
|
) -> None:
|
|
width, height = 1200, 820
|
|
plot_x, plot_width = 100, 1040
|
|
top_y, panel_height = 115, 245
|
|
bottom_y = 465
|
|
rows = snapshot["rows"]
|
|
iterations = [int(row["iteration"]) for row in rows]
|
|
min_iteration, max_iteration = min(iterations), max(iterations)
|
|
internal_values = [
|
|
int(row["internal_node_excess"]) for row in rows
|
|
]
|
|
value_min, value_max = min(internal_values), max(internal_values)
|
|
value_padding = max(1.0, (value_max - value_min) * 0.08)
|
|
y_min = max(0.0, value_min - value_padding)
|
|
y_max = value_max + value_padding
|
|
share_max = max(
|
|
float(row[key])
|
|
for row in rows
|
|
for key in ("shallow_pct", "middle_pct", "deep_pct")
|
|
)
|
|
share_ceiling = max(5.0, 5.0 * int((share_max * 1.15 + 4.999) / 5.0))
|
|
|
|
def x_position(iteration: int) -> float:
|
|
return (
|
|
plot_x
|
|
+ plot_width
|
|
* (iteration - min_iteration)
|
|
/ max(1, max_iteration - min_iteration)
|
|
)
|
|
|
|
def top_position(value: float) -> float:
|
|
return (
|
|
top_y + panel_height
|
|
- panel_height * (value - y_min) / max(1.0, y_max - y_min)
|
|
)
|
|
|
|
def share_position(value: float) -> float:
|
|
return (
|
|
bottom_y + panel_height
|
|
- panel_height * value / share_ceiling
|
|
)
|
|
|
|
svg = [
|
|
f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" '
|
|
f'height="{height}" viewBox="0 0 {width} {height}">',
|
|
'<rect width="100%" height="100%" fill="#ffffff"/>',
|
|
'<text x="60" y="38" font-family="sans-serif" font-size="24" '
|
|
'font-weight="bold">Layer-balance history</text>',
|
|
f'<text x="60" y="66" font-family="sans-serif" font-size="14">'
|
|
f'{html.escape(snapshot["run"])}; shallow '
|
|
f'{snapshot["ranges"][0]}, middle {snapshot["ranges"][1]}, deep '
|
|
f'{snapshot["ranges"][2]}</text>',
|
|
f'<text x="{plot_x}" y="98" font-family="sans-serif" '
|
|
'font-size="16">Internal path-node excess (zoomed scale)</text>',
|
|
f'<rect x="{plot_x}" y="{top_y}" width="{plot_width}" '
|
|
f'height="{panel_height}" fill="#fafafa" stroke="#cbd5e1"/>',
|
|
f'<text x="{plot_x}" y="448" font-family="sans-serif" '
|
|
'font-size="16">Share of internal node excess by fabric third</text>',
|
|
f'<rect x="{plot_x}" y="{bottom_y}" width="{plot_width}" '
|
|
f'height="{panel_height}" fill="#fafafa" stroke="#cbd5e1"/>',
|
|
]
|
|
for tick in range(5):
|
|
fraction = tick / 4
|
|
top_value = y_max - fraction * (y_max - y_min)
|
|
top_line_y = top_y + fraction * panel_height
|
|
share_value = share_ceiling * (1.0 - fraction)
|
|
share_line_y = bottom_y + fraction * panel_height
|
|
svg.extend([
|
|
f'<line x1="{plot_x}" y1="{top_line_y:.1f}" '
|
|
f'x2="{plot_x + plot_width}" y2="{top_line_y:.1f}" '
|
|
'stroke="#e2e8f0"/>',
|
|
f'<text x="{plot_x - 8}" y="{top_line_y + 4:.1f}" '
|
|
'text-anchor="end" font-family="monospace" font-size="11">'
|
|
f'{int(top_value):,}</text>',
|
|
f'<line x1="{plot_x}" y1="{share_line_y:.1f}" '
|
|
f'x2="{plot_x + plot_width}" y2="{share_line_y:.1f}" '
|
|
'stroke="#e2e8f0"/>',
|
|
f'<text x="{plot_x - 8}" y="{share_line_y + 4:.1f}" '
|
|
'text-anchor="end" font-family="monospace" font-size="11">'
|
|
f'{share_value:.1f}%</text>',
|
|
])
|
|
top_points = " ".join(
|
|
f'{x_position(int(row["iteration"])):.1f},'
|
|
f'{top_position(int(row["internal_node_excess"])):.1f}'
|
|
for row in rows
|
|
)
|
|
svg.append(
|
|
f'<polyline points="{top_points}" fill="none" stroke="#dc2626" '
|
|
'stroke-width="3"/>'
|
|
)
|
|
colors = {
|
|
"shallow_pct": "#d97706",
|
|
"middle_pct": "#2563eb",
|
|
"deep_pct": "#16a34a",
|
|
}
|
|
for key, color in colors.items():
|
|
points = " ".join(
|
|
f'{x_position(int(row["iteration"])):.1f},'
|
|
f'{share_position(float(row[key])):.1f}'
|
|
for row in rows
|
|
)
|
|
svg.append(
|
|
f'<polyline points="{points}" fill="none" stroke="{color}" '
|
|
'stroke-width="3"/>'
|
|
)
|
|
for tick in range(6):
|
|
iteration = round(
|
|
min_iteration
|
|
+ (max_iteration - min_iteration) * tick / 5
|
|
)
|
|
x = x_position(iteration)
|
|
svg.extend([
|
|
f'<line x1="{x:.1f}" y1="{bottom_y + panel_height}" '
|
|
f'x2="{x:.1f}" y2="{bottom_y + panel_height + 6}" '
|
|
'stroke="#64748b"/>',
|
|
f'<text x="{x:.1f}" y="{bottom_y + panel_height + 22}" '
|
|
'text-anchor="middle" font-family="sans-serif" font-size="11">'
|
|
f'{iteration}</text>',
|
|
])
|
|
legend = [
|
|
("#dc2626", "total internal excess"),
|
|
("#d97706", f"shallow {snapshot['ranges'][0]}"),
|
|
("#2563eb", f"middle {snapshot['ranges'][1]}"),
|
|
("#16a34a", f"deep {snapshot['ranges'][2]}"),
|
|
]
|
|
for index, (color, label) in enumerate(legend):
|
|
x = 100 + index * 255
|
|
svg.extend([
|
|
f'<line x1="{x}" y1="780" x2="{x + 28}" y2="780" '
|
|
f'stroke="{color}" stroke-width="4"/>',
|
|
f'<text x="{x + 36}" y="784" font-family="sans-serif" '
|
|
f'font-size="12">{html.escape(label)}</text>',
|
|
])
|
|
svg.append("</svg>")
|
|
path.write_text("\n".join(svg), encoding="utf-8", newline="\n")
|
|
|
|
|
|
def _latest_hotset_policy_snapshot(
|
|
runs: Sequence[Dict[str, Any]],
|
|
) -> Optional[Dict[str, Any]]:
|
|
"""Return pass- and phase-level evidence for the newest hotset run."""
|
|
for run in reversed(runs):
|
|
iterations = run.get("iterations", [])
|
|
max_iterations = max(
|
|
max(
|
|
(int(item.get("iteration", 0)) for item in iterations),
|
|
default=0,
|
|
),
|
|
int(run.get("journal", {}).get("max_iterations", 200)),
|
|
)
|
|
rows = []
|
|
phase = 0
|
|
previous_policy = None
|
|
for previous, current in zip(iterations, iterations[1:]):
|
|
if (
|
|
"hotset_size" not in current
|
|
or "elapsed_seconds" not in previous
|
|
or "elapsed_seconds" not in current
|
|
or "_physical_negotiated_overuse" not in previous
|
|
or "_physical_negotiated_overuse" not in current
|
|
):
|
|
continue
|
|
hotset = int(current["hotset_size"])
|
|
pres_fac = float(current.get("pres_fac", 0.0))
|
|
pressure_tier = 64
|
|
while pressure_tier < pres_fac:
|
|
pressure_tier *= 2
|
|
policy = (hotset, pressure_tier)
|
|
if policy != previous_policy:
|
|
phase += 1
|
|
previous_policy = policy
|
|
elapsed = max(
|
|
0.0,
|
|
float(current["elapsed_seconds"])
|
|
- float(previous["elapsed_seconds"]),
|
|
)
|
|
before = int(previous["_physical_negotiated_overuse"])
|
|
after = int(current["_physical_negotiated_overuse"])
|
|
drop = before - after
|
|
remaining_passes = max(
|
|
1,
|
|
max_iterations - int(current["iteration"]),
|
|
)
|
|
previous_edge = int(previous["_physical_edge_overuse"])
|
|
current_edge = int(current["_physical_edge_overuse"])
|
|
previous_node = int(previous.get("path_node_overuse_total", 0))
|
|
current_node = int(current.get("path_node_overuse_total", 0))
|
|
previous_escape = int(previous.get("escape_conflicts", 0))
|
|
current_escape = int(current.get("escape_conflicts", 0))
|
|
previous_portal = int(previous.get("portal_grid_conflicts", 0))
|
|
current_portal = int(current.get("portal_grid_conflicts", 0))
|
|
previous_barrel = int(
|
|
previous.get("exact_barrel_conflicts", 0)
|
|
)
|
|
current_barrel = int(
|
|
current.get("exact_barrel_conflicts", 0)
|
|
)
|
|
rows.append({
|
|
"run": run["label"],
|
|
"phase": phase,
|
|
"iteration": int(current["iteration"]),
|
|
"hotset_size": hotset,
|
|
"hotset_cap": int(current.get("hotset_cap", hotset)),
|
|
"pressure_tier": pressure_tier,
|
|
"elapsed_seconds": round(elapsed, 3),
|
|
"overuse_before": before,
|
|
"overuse_after": after,
|
|
"overuse_drop": drop,
|
|
"drop_pct": round(
|
|
100.0 * drop / max(1, before),
|
|
4,
|
|
),
|
|
"drop_per_second": round(
|
|
drop / max(0.001, elapsed),
|
|
4,
|
|
),
|
|
"required_drop_per_remaining_pass": round(
|
|
after / remaining_passes,
|
|
4,
|
|
),
|
|
"edge_via_drop": previous_edge - current_edge,
|
|
"node_drop": previous_node - current_node,
|
|
"pres_fac": round(float(current.get("pres_fac", 0.0)), 6),
|
|
"pres_fac_max": round(
|
|
float(current.get("pres_fac_max", 0.0)),
|
|
6,
|
|
),
|
|
"slow_progress_events": int(
|
|
current.get("slow_progress_events", 0)
|
|
),
|
|
"slow_progress_fraction": (
|
|
""
|
|
if current.get("slow_progress_fraction") is None else
|
|
round(
|
|
100.0
|
|
* float(current["slow_progress_fraction"]),
|
|
4,
|
|
)
|
|
),
|
|
"adaptive_pressure_limit": (
|
|
""
|
|
if "adaptive_pressure_limit" not in current else
|
|
round(
|
|
float(current["adaptive_pressure_limit"]),
|
|
6,
|
|
)
|
|
),
|
|
"pressure_trial_reference_ceiling": (
|
|
""
|
|
if current.get(
|
|
"pressure_trial_reference_ceiling"
|
|
) is None else
|
|
round(
|
|
float(current[
|
|
"pressure_trial_reference_ceiling"
|
|
]),
|
|
6,
|
|
)
|
|
),
|
|
"pressure_trial_reference_fraction": (
|
|
""
|
|
if current.get(
|
|
"pressure_trial_reference_fraction"
|
|
) is None else
|
|
round(
|
|
100.0 * float(current[
|
|
"pressure_trial_reference_fraction"
|
|
]),
|
|
4,
|
|
)
|
|
),
|
|
"pressure_trial_underperform_count": int(
|
|
current.get(
|
|
"pressure_trial_underperform_count",
|
|
0,
|
|
)
|
|
),
|
|
"pressure_backoff_count": int(
|
|
current.get("pressure_backoff_count", 0)
|
|
),
|
|
"pressure_rejected_ceiling": (
|
|
""
|
|
if current.get("pressure_rejected_ceiling") is None else
|
|
round(
|
|
float(current["pressure_rejected_ceiling"]),
|
|
6,
|
|
)
|
|
),
|
|
"rate_boost_until": int(
|
|
current.get("hotset_rate_boost_until", 0)
|
|
),
|
|
"conflict_pair_count": (
|
|
""
|
|
if "hotset_conflict_pair_count" not in current else
|
|
int(current["hotset_conflict_pair_count"])
|
|
),
|
|
"conflict_pairs_covered": (
|
|
""
|
|
if "hotset_conflict_pairs_covered" not in current else
|
|
int(current["hotset_conflict_pairs_covered"])
|
|
),
|
|
"conflict_pair_coverage_pct": (
|
|
""
|
|
if (
|
|
"hotset_conflict_pair_coverage_fraction"
|
|
not in current
|
|
) else
|
|
round(
|
|
100.0 * float(current[
|
|
"hotset_conflict_pair_coverage_fraction"
|
|
]),
|
|
4,
|
|
)
|
|
),
|
|
"escape_conflicts": current_escape,
|
|
"escape_delta": current_escape - previous_escape,
|
|
"portal_grid_conflicts": current_portal,
|
|
"portal_delta": current_portal - previous_portal,
|
|
"exact_barrel_conflicts": current_barrel,
|
|
"exact_barrel_delta": current_barrel - previous_barrel,
|
|
})
|
|
if not rows:
|
|
continue
|
|
|
|
phases = []
|
|
for phase_number in sorted({row["phase"] for row in rows}):
|
|
phase_rows = [
|
|
row for row in rows
|
|
if row["phase"] == phase_number
|
|
]
|
|
first = phase_rows[0]
|
|
last = phase_rows[-1]
|
|
# Compare a widened wave only with the tail of the immediately
|
|
# preceding contiguous policy phase. Slicing an arbitrary number
|
|
# of prior rows can cross a 512 -> 256 boundary and present a
|
|
# mixed policy as the baseline for 1024. Use equal lengths when
|
|
# the prior phase is long enough, otherwise use that complete
|
|
# shorter phase and compare normalized per-pass/per-second rates.
|
|
previous_phase_rows = [
|
|
row for row in rows
|
|
if row["phase"] == phase_number - 1
|
|
]
|
|
comparison_passes = min(
|
|
len(phase_rows),
|
|
len(previous_phase_rows),
|
|
)
|
|
prior_rows = (
|
|
previous_phase_rows[-comparison_passes:]
|
|
if comparison_passes else
|
|
[]
|
|
)
|
|
elapsed = sum(
|
|
float(row["elapsed_seconds"]) for row in phase_rows
|
|
)
|
|
drop = first["overuse_before"] - last["overuse_after"]
|
|
rate = drop / max(0.001, elapsed)
|
|
prior_elapsed = sum(
|
|
float(row["elapsed_seconds"]) for row in prior_rows
|
|
)
|
|
prior_drop = sum(
|
|
int(row["overuse_drop"]) for row in prior_rows
|
|
)
|
|
drop_per_pass = drop / len(phase_rows)
|
|
prior_drop_per_pass = (
|
|
None
|
|
if not prior_rows else
|
|
prior_drop / len(prior_rows)
|
|
)
|
|
prior_rate = (
|
|
None
|
|
if not prior_rows else
|
|
prior_drop / max(0.001, prior_elapsed)
|
|
)
|
|
remaining_passes = max(
|
|
1,
|
|
max_iterations - int(last["iteration"]),
|
|
)
|
|
required_drop_per_remaining_pass = (
|
|
last["overuse_after"] / remaining_passes
|
|
)
|
|
coverage_values = [
|
|
float(row["conflict_pair_coverage_pct"])
|
|
for row in phase_rows
|
|
if row["conflict_pair_coverage_pct"] != ""
|
|
]
|
|
phases.append({
|
|
"phase": phase_number,
|
|
"iterations": (
|
|
f"{first['iteration']}-{last['iteration']}"
|
|
),
|
|
"passes": len(phase_rows),
|
|
"hotset_size": first["hotset_size"],
|
|
"pressure_tier": first["pressure_tier"],
|
|
"elapsed_seconds": round(elapsed, 3),
|
|
"overuse_before": first["overuse_before"],
|
|
"overuse_after": last["overuse_after"],
|
|
"overuse_drop": drop,
|
|
"drop_per_pass": round(drop_per_pass, 4),
|
|
"required_drop_per_remaining_pass": round(
|
|
required_drop_per_remaining_pass,
|
|
4,
|
|
),
|
|
"linear_pace_ratio": round(
|
|
drop_per_pass
|
|
/ max(0.0001, required_drop_per_remaining_pass),
|
|
4,
|
|
),
|
|
"projected_zero_iteration": (
|
|
""
|
|
if drop_per_pass <= 0 else
|
|
round(
|
|
int(last["iteration"])
|
|
+ last["overuse_after"] / drop_per_pass,
|
|
2,
|
|
)
|
|
),
|
|
"matched_prior_drop_per_pass": (
|
|
""
|
|
if prior_drop_per_pass is None else
|
|
round(prior_drop_per_pass, 4)
|
|
),
|
|
"iteration_efficiency_ratio": (
|
|
""
|
|
if prior_drop_per_pass in {None, 0.0} else
|
|
round(drop_per_pass / prior_drop_per_pass, 4)
|
|
),
|
|
"drop_per_second": round(rate, 4),
|
|
"matched_prior_iterations": (
|
|
""
|
|
if prior_rate is None else
|
|
f"{prior_rows[0]['iteration']}-"
|
|
f"{prior_rows[-1]['iteration']}"
|
|
),
|
|
"matched_prior_drop_per_second": (
|
|
""
|
|
if prior_rate is None else
|
|
round(prior_rate, 4)
|
|
),
|
|
"efficiency_ratio": (
|
|
""
|
|
if prior_rate in {None, 0.0} else
|
|
round(rate / prior_rate, 4)
|
|
),
|
|
"edge_via_drop": sum(
|
|
int(row["edge_via_drop"]) for row in phase_rows
|
|
),
|
|
"node_drop": sum(
|
|
int(row["node_drop"]) for row in phase_rows
|
|
),
|
|
"pressure_start": phase_rows[0]["pres_fac"],
|
|
"pressure_end": phase_rows[-1]["pres_fac"],
|
|
"conflict_pair_coverage_pct_avg": (
|
|
""
|
|
if not coverage_values else
|
|
round(
|
|
sum(coverage_values) / len(coverage_values),
|
|
4,
|
|
)
|
|
),
|
|
"escape_start": (
|
|
first["escape_conflicts"] - first["escape_delta"]
|
|
),
|
|
"escape_end": last["escape_conflicts"],
|
|
"escape_delta": (
|
|
last["escape_conflicts"]
|
|
- first["escape_conflicts"]
|
|
+ first["escape_delta"]
|
|
),
|
|
"portal_start": (
|
|
first["portal_grid_conflicts"] - first["portal_delta"]
|
|
),
|
|
"portal_end": last["portal_grid_conflicts"],
|
|
"portal_delta": (
|
|
last["portal_grid_conflicts"]
|
|
- first["portal_grid_conflicts"]
|
|
+ first["portal_delta"]
|
|
),
|
|
"exact_barrel_start": (
|
|
first["exact_barrel_conflicts"]
|
|
- first["exact_barrel_delta"]
|
|
),
|
|
"exact_barrel_end": last["exact_barrel_conflicts"],
|
|
"exact_barrel_delta": (
|
|
last["exact_barrel_conflicts"]
|
|
- first["exact_barrel_conflicts"]
|
|
+ first["exact_barrel_delta"]
|
|
),
|
|
})
|
|
return {
|
|
"run": run["label"],
|
|
"max_iterations": max_iterations,
|
|
"rows": rows,
|
|
"phases": phases,
|
|
}
|
|
return None
|
|
|
|
|
|
def _write_hotset_policy_csv(
|
|
snapshot: Dict[str, Any],
|
|
path: Path,
|
|
) -> None:
|
|
rows = snapshot["rows"]
|
|
with path.open("w", newline="", encoding="utf-8") as stream:
|
|
writer = csv.DictWriter(stream, fieldnames=list(rows[0]))
|
|
writer.writeheader()
|
|
writer.writerows(rows)
|
|
|
|
|
|
def _write_hotset_policy_markdown(
|
|
snapshot: Dict[str, Any],
|
|
path: Path,
|
|
) -> None:
|
|
phase_columns = (
|
|
"phase", "iterations", "passes", "hotset_size",
|
|
"pressure_tier",
|
|
"elapsed_seconds", "overuse_before", "overuse_after",
|
|
"overuse_drop", "drop_per_pass",
|
|
"required_drop_per_remaining_pass", "linear_pace_ratio",
|
|
"projected_zero_iteration",
|
|
"matched_prior_drop_per_pass", "iteration_efficiency_ratio",
|
|
"drop_per_second", "matched_prior_iterations",
|
|
"matched_prior_drop_per_second", "efficiency_ratio",
|
|
"edge_via_drop", "node_drop", "pressure_start", "pressure_end",
|
|
"conflict_pair_coverage_pct_avg",
|
|
"escape_start", "escape_end", "escape_delta",
|
|
"portal_start", "portal_end", "portal_delta",
|
|
"exact_barrel_start", "exact_barrel_end", "exact_barrel_delta",
|
|
)
|
|
pass_columns = (
|
|
"iteration", "hotset_size", "elapsed_seconds",
|
|
"overuse_before", "overuse_after", "overuse_drop",
|
|
"drop_per_second", "required_drop_per_remaining_pass",
|
|
"edge_via_drop", "node_drop",
|
|
"pres_fac", "slow_progress_fraction",
|
|
"adaptive_pressure_limit",
|
|
"pressure_trial_reference_ceiling",
|
|
"pressure_trial_reference_fraction",
|
|
"pressure_trial_underperform_count",
|
|
"pressure_backoff_count", "pressure_rejected_ceiling",
|
|
"conflict_pair_count", "conflict_pairs_covered",
|
|
"conflict_pair_coverage_pct",
|
|
"escape_conflicts", "portal_grid_conflicts",
|
|
"exact_barrel_conflicts",
|
|
)
|
|
lines = [
|
|
"# Hotset policy experiment",
|
|
"",
|
|
f"Run: `{snapshot['run']}`",
|
|
"",
|
|
"## Contiguous policy phases",
|
|
"",
|
|
"| " + " | ".join(phase_columns) + " |",
|
|
"|" + "|".join("---" for _ in phase_columns) + "|",
|
|
]
|
|
for row in snapshot["phases"]:
|
|
lines.append(
|
|
"| " + " | ".join(
|
|
str(row[column]) for column in phase_columns
|
|
) + " |"
|
|
)
|
|
lines.extend([
|
|
"",
|
|
"## Per-pass measurements",
|
|
"",
|
|
"| " + " | ".join(pass_columns) + " |",
|
|
"|" + "|".join("---" for _ in pass_columns) + "|",
|
|
])
|
|
for row in snapshot["rows"]:
|
|
lines.append(
|
|
"| " + " | ".join(
|
|
str(row[column]) for column in pass_columns
|
|
) + " |"
|
|
)
|
|
lines.extend([
|
|
"",
|
|
"`hotset_size` is the number of selected nets rerouted in the pass. "
|
|
"`drop_per_second` is complete normalized edge/via plus path-node "
|
|
"excess removed per wall-clock second; negative values are regressions. "
|
|
"`required_drop_per_remaining_pass` is the linear pace needed to "
|
|
f"reach graph zero by this run's iteration {snapshot['max_iterations']} "
|
|
"limit. `linear_pace_ratio` and `projected_zero_iteration` extrapolate "
|
|
"the complete phase's average; they are diagnostics, not convergence "
|
|
"proofs. "
|
|
"`conflict_pair_coverage_pct` is the share of distinct live node-"
|
|
"conflict pairs with at least one endpoint in the selected wave. "
|
|
"Escape and portal counts are retained because faster graph descent "
|
|
"is not useful if it accumulates unpriced physical cleanup debt.",
|
|
"",
|
|
])
|
|
path.write_text("\n".join(lines), encoding="utf-8", newline="\n")
|
|
|
|
|
|
def _hotset_color(hotset: int) -> str:
|
|
if hotset <= 100:
|
|
return "#64748b"
|
|
if hotset <= 256:
|
|
return "#2563eb"
|
|
if hotset <= 512:
|
|
return "#ea580c"
|
|
if hotset <= 1024:
|
|
return "#9333ea"
|
|
return "#dc2626"
|
|
|
|
|
|
def _write_hotset_policy_svg(
|
|
snapshot: Dict[str, Any],
|
|
path: Path,
|
|
) -> None:
|
|
width, height = 1200, 820
|
|
plot_x, plot_width = 95, 1030
|
|
overuse_y, overuse_height = 110, 260
|
|
rate_y, rate_height = 485, 220
|
|
rows = snapshot["rows"]
|
|
iterations = [rows[0]["iteration"] - 1] + [
|
|
row["iteration"] for row in rows
|
|
]
|
|
totals = [rows[0]["overuse_before"]] + [
|
|
row["overuse_after"] for row in rows
|
|
]
|
|
minimum_iteration = min(iterations)
|
|
maximum_iteration = max(iterations)
|
|
iteration_span = max(1, maximum_iteration - minimum_iteration)
|
|
minimum_total = min(totals)
|
|
maximum_total = max(totals)
|
|
total_padding = max(1.0, 0.08 * (maximum_total - minimum_total))
|
|
total_low = max(0.0, minimum_total - total_padding)
|
|
total_high = maximum_total + total_padding
|
|
total_span = max(1.0, total_high - total_low)
|
|
rate_rows = rows[-min(12, len(rows)):]
|
|
rate_iterations = [int(row["iteration"]) for row in rate_rows]
|
|
rate_iteration_index = {
|
|
iteration: index
|
|
for index, iteration in enumerate(rate_iterations)
|
|
}
|
|
rates = [float(row["drop_per_second"]) for row in rate_rows]
|
|
rate_low = min(0.0, min(rates))
|
|
rate_high = max(0.0, max(rates))
|
|
rate_padding = max(0.25, 0.08 * (rate_high - rate_low))
|
|
rate_low -= rate_padding
|
|
rate_high += rate_padding
|
|
rate_span = max(0.001, rate_high - rate_low)
|
|
|
|
def x_position(iteration: int) -> float:
|
|
return (
|
|
plot_x
|
|
+ plot_width
|
|
* (iteration - minimum_iteration)
|
|
/ iteration_span
|
|
)
|
|
|
|
def total_y(value: float) -> float:
|
|
return (
|
|
overuse_y + overuse_height
|
|
- overuse_height * (value - total_low) / total_span
|
|
)
|
|
|
|
def rate_x_position(iteration: int) -> float:
|
|
return (
|
|
plot_x
|
|
+ plot_width
|
|
* (rate_iteration_index[iteration] + 0.5)
|
|
/ len(rate_rows)
|
|
)
|
|
|
|
def rate_value_y(value: float) -> float:
|
|
return (
|
|
rate_y + rate_height
|
|
- rate_height * (value - rate_low) / rate_span
|
|
)
|
|
|
|
points = " ".join(
|
|
f"{x_position(iteration):.1f},{total_y(total):.1f}"
|
|
for iteration, total in zip(iterations, totals)
|
|
)
|
|
svg = [
|
|
f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" '
|
|
f'height="{height}" viewBox="0 0 {width} {height}">',
|
|
'<rect width="100%" height="100%" fill="#ffffff"/>',
|
|
'<text x="60" y="38" font-family="sans-serif" font-size="24" '
|
|
'font-weight="bold">Hotset policy experiment</text>',
|
|
f'<text x="60" y="66" font-family="sans-serif" font-size="14">'
|
|
f'{html.escape(snapshot["run"])}</text>',
|
|
f'<text x="{plot_x}" y="95" font-family="sans-serif" '
|
|
'font-size="16">Complete negotiated excess</text>',
|
|
f'<rect x="{plot_x}" y="{overuse_y}" width="{plot_width}" '
|
|
f'height="{overuse_height}" fill="#fafafa" stroke="#cbd5e1"/>',
|
|
f'<polyline points="{points}" fill="none" stroke="#0f172a" '
|
|
'stroke-width="2"/>',
|
|
f'<text x="{plot_x}" y="468" font-family="sans-serif" '
|
|
'font-size="16">Recent excess removed per wall-clock second '
|
|
f'(last {len(rate_rows)} passes)</text>',
|
|
f'<rect x="{plot_x}" y="{rate_y}" width="{plot_width}" '
|
|
f'height="{rate_height}" fill="#fafafa" stroke="#cbd5e1"/>',
|
|
f'<line x1="{plot_x}" y1="{rate_value_y(0):.1f}" '
|
|
f'x2="{plot_x + plot_width}" y2="{rate_value_y(0):.1f}" '
|
|
'stroke="#475569" stroke-width="1"/>',
|
|
]
|
|
for tick in range(5):
|
|
fraction = tick / 4
|
|
total_value = total_high - fraction * total_span
|
|
total_line_y = overuse_y + fraction * overuse_height
|
|
rate_value = rate_high - fraction * rate_span
|
|
rate_line_y = rate_y + fraction * rate_height
|
|
svg.extend([
|
|
f'<line x1="{plot_x}" y1="{total_line_y:.1f}" '
|
|
f'x2="{plot_x + plot_width}" y2="{total_line_y:.1f}" '
|
|
'stroke="#e2e8f0"/>',
|
|
f'<text x="{plot_x - 8}" y="{total_line_y + 4:.1f}" '
|
|
'text-anchor="end" font-family="monospace" font-size="11">'
|
|
f'{int(total_value):,}</text>',
|
|
f'<line x1="{plot_x}" y1="{rate_line_y:.1f}" '
|
|
f'x2="{plot_x + plot_width}" y2="{rate_line_y:.1f}" '
|
|
'stroke="#e2e8f0"/>',
|
|
f'<text x="{plot_x - 8}" y="{rate_line_y + 4:.1f}" '
|
|
'text-anchor="end" font-family="monospace" font-size="11">'
|
|
f'{rate_value:.1f}</text>',
|
|
])
|
|
bar_width = max(
|
|
5.0,
|
|
min(52.0, plot_width / max(1, len(rate_rows)) * 0.58),
|
|
)
|
|
zero_y = rate_value_y(0.0)
|
|
previous_event_count = 0
|
|
for row in rows:
|
|
event_count = int(row["slow_progress_events"])
|
|
if event_count <= previous_event_count:
|
|
continue
|
|
previous_event_count = event_count
|
|
iteration = int(row["iteration"])
|
|
top_x = x_position(iteration)
|
|
label_on_left = top_x > plot_x + 0.82 * plot_width
|
|
label_x = top_x - 4 if label_on_left else top_x + 4
|
|
label_anchor = "end" if label_on_left else "start"
|
|
svg.extend([
|
|
f'<line x1="{top_x:.1f}" y1="{overuse_y}" '
|
|
f'x2="{top_x:.1f}" y2="{overuse_y + overuse_height}" '
|
|
'stroke="#64748b" stroke-dasharray="5 4"/>',
|
|
f'<text x="{label_x:.1f}" y="{overuse_y + 15}" '
|
|
f'text-anchor="{label_anchor}" '
|
|
'font-family="sans-serif" font-size="10" fill="#475569">'
|
|
f'event {event_count} · p≤{row["pres_fac_max"]:g}</text>',
|
|
])
|
|
if iteration in rate_iteration_index:
|
|
bottom_x = rate_x_position(iteration)
|
|
svg.append(
|
|
f'<line x1="{bottom_x:.1f}" y1="{rate_y}" '
|
|
f'x2="{bottom_x:.1f}" y2="{rate_y + rate_height}" '
|
|
'stroke="#64748b" stroke-dasharray="5 4"/>'
|
|
)
|
|
for row in rows:
|
|
x = x_position(int(row["iteration"]))
|
|
color = _hotset_color(int(row["hotset_size"]))
|
|
svg.append(
|
|
f'<circle cx="{x:.1f}" cy="{total_y(row["overuse_after"]):.1f}" '
|
|
f'r="4.5" fill="{color}"><title>iteration '
|
|
f'{row["iteration"]}: {row["overuse_after"]:,} excess; '
|
|
f'hotset {row["hotset_size"]}</title></circle>'
|
|
)
|
|
for row in rate_rows:
|
|
x = rate_x_position(int(row["iteration"]))
|
|
rate = float(row["drop_per_second"])
|
|
y = rate_value_y(rate)
|
|
color = _hotset_color(int(row["hotset_size"]))
|
|
svg.extend([
|
|
f'<rect x="{x - bar_width / 2:.1f}" '
|
|
f'y="{min(y, zero_y):.1f}" width="{bar_width:.1f}" '
|
|
f'height="{max(1.0, abs(zero_y - y)):.1f}" fill="{color}">'
|
|
f'<title>iteration {row["iteration"]}: '
|
|
f'{row["drop_per_second"]} excess/s; '
|
|
f'{row["elapsed_seconds"]} s; hotset '
|
|
f'{row["hotset_size"]}</title></rect>',
|
|
f'<text x="{x:.1f}" y="731" text-anchor="middle" '
|
|
'font-family="sans-serif" font-size="10">'
|
|
f'{row["iteration"]}</text>',
|
|
])
|
|
legend_hotsets = list(dict.fromkeys(
|
|
int(row["hotset_size"]) for row in rows
|
|
))
|
|
for index, hotset in enumerate(legend_hotsets):
|
|
x = 95 + index * 145
|
|
svg.extend([
|
|
f'<rect x="{x}" y="770" width="14" height="14" '
|
|
f'fill="{_hotset_color(hotset)}"/>',
|
|
f'<text x="{x + 22}" y="782" font-family="sans-serif" '
|
|
f'font-size="12">{hotset}-net hotset</text>',
|
|
])
|
|
svg.extend([
|
|
'<text x="1125" y="747" text-anchor="end" '
|
|
'font-family="sans-serif" font-size="11">iteration</text>',
|
|
"</svg>",
|
|
])
|
|
path.write_text("\n".join(svg), encoding="utf-8", newline="\n")
|
|
|
|
|
|
def main() -> None:
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("results_dir", type=Path)
|
|
parser.add_argument("output_dir", type=Path)
|
|
args = parser.parse_args()
|
|
candidates = [
|
|
path for path in args.results_dir.glob("*progress.json")
|
|
if _is_candidate_name(path.name)
|
|
]
|
|
runs = _load(candidates)
|
|
if not runs:
|
|
raise SystemExit("no matching progress journals found")
|
|
rows = [_row(run) for run in runs]
|
|
stall_rows = [
|
|
row for row in (_stall_row(run) for run in runs)
|
|
if row is not None
|
|
]
|
|
args.output_dir.mkdir(parents=True, exist_ok=True)
|
|
_write_csv(rows, args.output_dir / "reduced-layer-comparison.csv")
|
|
_write_markdown(rows, args.output_dir / "reduced-layer-comparison.md")
|
|
_write_svg(runs, args.output_dir / "reduced-layer-progress.svg")
|
|
_write_stall_csv(
|
|
stall_rows,
|
|
args.output_dir / "layer-stall-overuse.csv",
|
|
)
|
|
_write_stall_markdown(
|
|
stall_rows,
|
|
args.output_dir / "layer-stall-overuse.md",
|
|
)
|
|
_write_stall_svg(
|
|
stall_rows,
|
|
args.output_dir / "layer-stall-overuse.svg",
|
|
)
|
|
layer_snapshot = _latest_layer_node_snapshot(runs)
|
|
layer_artifacts = {}
|
|
if layer_snapshot is not None:
|
|
layer_csv = args.output_dir / "layer-node-congestion.csv"
|
|
layer_markdown = args.output_dir / "layer-node-congestion.md"
|
|
layer_svg = args.output_dir / "layer-node-congestion.svg"
|
|
_write_layer_node_csv(layer_snapshot, layer_csv)
|
|
_write_layer_node_markdown(layer_snapshot, layer_markdown)
|
|
_write_layer_node_svg(layer_snapshot, layer_svg)
|
|
layer_artifacts = {
|
|
"layer_node_csv": str(layer_csv),
|
|
"layer_node_markdown": str(layer_markdown),
|
|
"layer_node_svg": str(layer_svg),
|
|
}
|
|
layer_balance = _latest_layer_balance_history(runs)
|
|
layer_balance_artifacts = {}
|
|
if layer_balance is not None:
|
|
balance_csv = args.output_dir / "layer-balance-history.csv"
|
|
balance_markdown = args.output_dir / "layer-balance-history.md"
|
|
balance_svg = args.output_dir / "layer-balance-history.svg"
|
|
_write_layer_balance_csv(layer_balance, balance_csv)
|
|
_write_layer_balance_markdown(layer_balance, balance_markdown)
|
|
_write_layer_balance_svg(layer_balance, balance_svg)
|
|
layer_balance_artifacts = {
|
|
"layer_balance_csv": str(balance_csv),
|
|
"layer_balance_markdown": str(balance_markdown),
|
|
"layer_balance_svg": str(balance_svg),
|
|
}
|
|
hotset_snapshot = _latest_hotset_policy_snapshot(runs)
|
|
hotset_artifacts = {}
|
|
if hotset_snapshot is not None:
|
|
hotset_csv = args.output_dir / "hotset-policy.csv"
|
|
hotset_markdown = args.output_dir / "hotset-policy.md"
|
|
hotset_svg = args.output_dir / "hotset-policy.svg"
|
|
_write_hotset_policy_csv(hotset_snapshot, hotset_csv)
|
|
_write_hotset_policy_markdown(hotset_snapshot, hotset_markdown)
|
|
_write_hotset_policy_svg(hotset_snapshot, hotset_svg)
|
|
hotset_artifacts = {
|
|
"hotset_csv": str(hotset_csv),
|
|
"hotset_markdown": str(hotset_markdown),
|
|
"hotset_svg": str(hotset_svg),
|
|
}
|
|
print(json.dumps({
|
|
"runs": len(runs),
|
|
"csv": str(args.output_dir / "reduced-layer-comparison.csv"),
|
|
"markdown": str(args.output_dir / "reduced-layer-comparison.md"),
|
|
"svg": str(args.output_dir / "reduced-layer-progress.svg"),
|
|
"stall_csv": str(args.output_dir / "layer-stall-overuse.csv"),
|
|
"stall_markdown": str(
|
|
args.output_dir / "layer-stall-overuse.md"
|
|
),
|
|
"stall_svg": str(args.output_dir / "layer-stall-overuse.svg"),
|
|
**layer_artifacts,
|
|
**layer_balance_artifacts,
|
|
**hotset_artifacts,
|
|
}, indent=2))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|