File size: 6,486 Bytes
ca74b23
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
from __future__ import annotations

import base64
import html
import math


COLORS = ("#2563eb", "#059669", "#d97706", "#dc2626")


def line_svg(title: str, x_label: str, y_label: str, series: list[dict], log_x: bool = False, log_y: bool = False) -> str:
    width, height = 760, 430
    left, right, top, bottom = 90, 30, 55, 70
    plot_w, plot_h = width - left - right, height - top - bottom
    all_x = [value for item in series for value in item["x"]]
    all_y = [value for item in series for value in item["y"]]
    tx = (lambda value: math.log10(value)) if log_x else (lambda value: value)
    ty = (lambda value: math.log10(value)) if log_y else (lambda value: value)
    x_values = [tx(value) for value in all_x]
    y_values = [ty(value) for value in all_y]
    x_min, x_max = min(x_values), max(x_values)
    y_min, y_max = min(y_values), max(y_values)
    x_pad = (x_max - x_min or 1.0) * 0.04
    y_pad = (y_max - y_min or 1.0) * 0.08
    x_min, x_max = x_min - x_pad, x_max + x_pad
    y_min, y_max = y_min - y_pad, y_max + y_pad

    def px(value: float) -> float:
        return left + (tx(value) - x_min) / (x_max - x_min) * plot_w

    def py(value: float) -> float:
        return top + (y_max - ty(value)) / (y_max - y_min) * plot_h

    parts = [
        f'<svg xmlns="http://www.w3.org/2000/svg" width="{width}" height="{height}" viewBox="0 0 {width} {height}" role="img">',
        f"<title>{html.escape(title)}</title>",
        '<rect width="100%" height="100%" fill="white"/>',
        f'<text x="{width/2}" y="28" text-anchor="middle" font-family="sans-serif" font-size="19" font-weight="700">{html.escape(title)}</text>',
    ]
    for tick in range(6):
        fraction = tick / 5
        x = left + fraction * plot_w
        y = top + fraction * plot_h
        x_value = x_min + fraction * (x_max - x_min)
        y_value = y_max - fraction * (y_max - y_min)
        x_text = f"10^{x_value:.1f}" if log_x else f"{x_value:.3g}"
        y_text = f"10^{y_value:.1f}" if log_y else f"{y_value:.3g}"
        parts.extend([
            f'<line x1="{x:.2f}" y1="{top}" x2="{x:.2f}" y2="{top+plot_h}" stroke="#e5e7eb"/>',
            f'<line x1="{left}" y1="{y:.2f}" x2="{left+plot_w}" y2="{y:.2f}" stroke="#e5e7eb"/>',
            f'<text x="{x:.2f}" y="{top+plot_h+22}" text-anchor="middle" font-family="sans-serif" font-size="11">{x_text}</text>',
            f'<text x="{left-10}" y="{y+4:.2f}" text-anchor="end" font-family="sans-serif" font-size="11">{y_text}</text>',
        ])
    parts.extend([
        f'<line x1="{left}" y1="{top+plot_h}" x2="{left+plot_w}" y2="{top+plot_h}" stroke="#111827"/>',
        f'<line x1="{left}" y1="{top}" x2="{left}" y2="{top+plot_h}" stroke="#111827"/>',
        f'<text x="{left+plot_w/2}" y="{height-18}" text-anchor="middle" font-family="sans-serif" font-size="13">{html.escape(x_label)}</text>',
        f'<text x="20" y="{top+plot_h/2}" text-anchor="middle" transform="rotate(-90 20 {top+plot_h/2})" font-family="sans-serif" font-size="13">{html.escape(y_label)}</text>',
    ])
    for index, item in enumerate(series):
        color = COLORS[index % len(COLORS)]
        points = " ".join(f"{px(x):.2f},{py(y):.2f}" for x, y in zip(item["x"], item["y"]))
        parts.append(f'<polyline points="{points}" fill="none" stroke="{color}" stroke-width="2.5"/>')
        for x, y in zip(item["x"], item["y"]):
            parts.append(f'<circle cx="{px(x):.2f}" cy="{py(y):.2f}" r="4" fill="{color}"/>')
        legend_x = left + 12 + (index % 2) * 280
        legend_y = top + 18 + (index // 2) * 20
        parts.extend([
            f'<line x1="{legend_x}" y1="{legend_y}" x2="{legend_x+24}" y2="{legend_y}" stroke="{color}" stroke-width="3"/>',
            f'<text x="{legend_x+31}" y="{legend_y+4}" font-family="sans-serif" font-size="12">{html.escape(item["label"])}</text>',
        ])
    parts.append("</svg>")
    return "\n".join(parts) + "\n"


def make_figures(claims: list[dict]) -> dict[str, str]:
    by_id = {claim["claim_id"]: claim for claim in claims}
    claim1 = by_id["claim1"]
    primary = [
        {"label": f"omega={item['omega']:.4g}", "x": [row["T"] for row in item["rows"]], "y": [row["mean_squared_parameter_error"] for row in item["rows"]]}
        for item in claim1["problems"]
    ]
    primary.append({"label": "constant-step control", "x": [row["T"] for row in claim1["negative_control"]["rows"]], "y": [row["mean_squared_parameter_error"] for row in claim1["negative_control"]["rows"]]})
    base = claim1["problems"][0]
    claim2 = by_id["claim2"]
    claim3 = by_id["claim3"]
    claim4 = by_id["claim4"]
    rho = [0.6, 0.9, 0.99]
    figures = {
        "headline_td_scaling.svg": line_svg("Exact exponential schedule converges; constant step stalls", "horizon T (log scale)", "last-iterate MSE (log scale)", primary, True, True),
        "bound_calibration.svg": line_svg("Observed error remains below Theorem 3.4's explicit bound", "horizon T (log scale)", "MSE / upper bound (log scale)", [{"label": "observed MSE", "x": [row["T"] for row in base["rows"]], "y": [row["mean_squared_parameter_error"] for row in base["rows"]]}, {"label": "explicit bound", "x": [row["T"] for row in base["rows"]], "y": [row["theorem_upper_bound"] for row in base["rows"]]}], True, True),
        "omega_dependency.svg": line_svg("Standard eta0 needs omega; regularized eta0 does not", "omega", "eta0 at T=1e6 (log scale)", [{"label": "Theorem 4.10 standard", "x": list(claim2["omega_values"]), "y": claim2["eta0_values_at_T_1e6"]}, {"label": "Theorem 4.12 regularized", "x": list(claim3["omega_values"]), "y": claim3["eta0_at_T_1e6_for_each_omega"]}], False, True),
        "mixing_factor.svg": line_svg("The displayed mixing factor spans 86 orders of magnitude", "tau_0.05", "log10 exp(m/log(1/rho))", [{"label": "Theorem 4.12 factor", "x": [row["tau_0.05"] for row in claim4["mixing_sweep"]], "y": [row["log10_bound_factor"] for row in claim4["mixing_sweep"]]}]),
        "feasible_horizons.svg": line_svg("Exact Markovian theorem regimes begin at extreme horizons", "rho", "first feasible log10(T)", [{"label": "standard", "x": rho, "y": [claim2["calibrated_first_feasible_horizons"][str(value)]["log10_T_first_feasible"] for value in rho]}, {"label": "regularized", "x": rho, "y": [claim3["calibrated_first_feasible_horizons"][str(value)]["log10_T_first_feasible"] for value in rho]}]),
    }
    return {name: base64.b64encode(svg.encode()).decode() for name, svg in figures.items()}