From 9f05c59fe8884b1900a36c80a652117414d9c0a4 Mon Sep 17 00:00:00 2001 From: YurenHao0426 Date: Sat, 29 Aug 2026 13:23:23 -0500 Subject: fig: plot physical grid bias crossover --- experiments/plot_physical_grid_p5.py | 156 +++++ results/figs/physical_grid_p5.pdf | Bin 0 -> 30357 bytes results/figs/physical_grid_p5.png | Bin 0 -> 144310 bytes results/figs/physical_grid_p5.svg | 876 +++++++++++++++++++++++++++ visual-composer/physical-grid-p5-contract.md | 15 + 5 files changed, 1047 insertions(+) create mode 100644 experiments/plot_physical_grid_p5.py create mode 100644 results/figs/physical_grid_p5.pdf create mode 100644 results/figs/physical_grid_p5.png create mode 100644 results/figs/physical_grid_p5.svg create mode 100644 visual-composer/physical-grid-p5-contract.md diff --git a/experiments/plot_physical_grid_p5.py b/experiments/plot_physical_grid_p5.py new file mode 100644 index 0000000..bc86647 --- /dev/null +++ b/experiments/plot_physical_grid_p5.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +"""Plot the P5 physical-grid classification crossover from committed JSON.""" + +from __future__ import annotations + +import argparse +import json +from pathlib import Path + +import matplotlib.pyplot as plt +import numpy as np + + +METHOD_STYLE = { + "raw": { + "label": "Raw local update", + "color": "#666666", + "marker": "x", + "linestyle": ":", + }, + "constant": { + "label": "Constant calibration", + "color": "#E69F00", + "marker": "^", + "linestyle": "--", + }, + "sdil": { + "label": "SDIL", + "color": "#0072B2", + "marker": "o", + "linestyle": "-", + }, + "overclamp": { + "label": "Overclamping", + "color": "#222222", + "marker": "D", + "linestyle": "--", + }, + "overclamp_sdil": { + "label": "Overclamping + SDIL", + "color": "#009E73", + "marker": "s", + "linestyle": "-", + }, +} + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser() + parser.add_argument( + "--input", type=Path, + default=Path("results/physical_bias/p5_grid_bias_crossover.json")) + parser.add_argument( + "--output-prefix", type=Path, + default=Path("results/figs/physical_grid_p5")) + return parser.parse_args() + + +def values_by_diameter(records: list[dict], method: str) -> tuple[np.ndarray, list[np.ndarray]]: + diameters = np.asarray(sorted({ + record["input_diameter_v"] for record in records + })) + values = [] + for diameter in diameters: + values.append(np.asarray([ + 100.0 * record["methods"][method]["classification_error"] + for record in records + if record["input_diameter_v"] == diameter + ])) + return 1_000.0 * diameters, values + + +def draw_panel( + axis: plt.Axes, + records: list[dict], + methods: tuple[str, ...], + title: str, +) -> None: + offsets = np.linspace(-3.0, 3.0, len(methods)) + for method_index, method in enumerate(methods): + diameters, values = values_by_diameter(records, method) + style = METHOD_STYLE[method] + for diameter, trial_values in zip(diameters, values): + trial_jitter = np.linspace(-1.5, 1.5, len(trial_values)) + axis.scatter( + diameter + offsets[method_index] + trial_jitter, + trial_values, + color=style["color"], + marker=style["marker"], + s=18, + alpha=0.28, + linewidths=0.8, + zorder=2, + ) + means = np.asarray([np.mean(trials) for trials in values]) + axis.plot( + diameters, + means, + label=style["label"], + color=style["color"], + marker=style["marker"], + linestyle=style["linestyle"], + linewidth=1.8, + markersize=5.5, + markerfacecolor=( + "white" if method in {"sdil", "overclamp_sdil"} + else style["color"]), + markeredgewidth=1.2, + zorder=3, + ) + axis.set_title(title, loc="left", fontsize=10, fontweight="bold") + axis.set_xlabel("Input diameter (mV)") + axis.set_xticks(diameters) + axis.set_ylim(-2.0, 80.0) + axis.set_yticks((0, 20, 40, 60, 80)) + axis.grid(axis="y", color="#D9D9D9", linewidth=0.7, zorder=0) + axis.spines[["top", "right"]].set_visible(False) + axis.legend(frameon=False, fontsize=8.5, loc="upper right") + + +def main() -> None: + args = parse_args() + report = json.loads(args.input.read_text()) + records = report["records"] + plt.rcParams.update({ + "font.family": "DejaVu Sans", + "font.size": 9, + "axes.labelsize": 9, + "xtick.labelsize": 8, + "ytick.labelsize": 8, + "svg.fonttype": "none", + "pdf.fonttype": 42, + }) + figure, axes = plt.subplots( + 1, 2, figsize=(7.1, 2.75), sharey=True, constrained_layout=True) + draw_panel( + axes[0], records, ("raw", "constant", "sdil"), + "(a) Standard local learning") + draw_panel( + axes[1], records, ("overclamp", "overclamp_sdil"), + "(b) Composition with overclamping") + axes[0].set_ylabel("Classification error (%)") + axes[1].text( + 0.98, 0.82, "8 trials per input diameter", + transform=axes[1].transAxes, ha="right", va="top", + fontsize=8, color="#666666") + args.output_prefix.parent.mkdir(parents=True, exist_ok=True) + figure.savefig(args.output_prefix.with_suffix(".svg")) + figure.savefig(args.output_prefix.with_suffix(".pdf")) + figure.savefig(args.output_prefix.with_suffix(".png"), dpi=300) + plt.close(figure) + print(f"wrote {args.output_prefix}.{{svg,pdf,png}}") + + +if __name__ == "__main__": + main() diff --git a/results/figs/physical_grid_p5.pdf b/results/figs/physical_grid_p5.pdf new file mode 100644 index 0000000..2d77906 Binary files /dev/null and b/results/figs/physical_grid_p5.pdf differ diff --git a/results/figs/physical_grid_p5.png b/results/figs/physical_grid_p5.png new file mode 100644 index 0000000..84d69ce Binary files /dev/null and b/results/figs/physical_grid_p5.png differ diff --git a/results/figs/physical_grid_p5.svg b/results/figs/physical_grid_p5.svg new file mode 100644 index 0000000..3e1df50 --- /dev/null +++ b/results/figs/physical_grid_p5.svg @@ -0,0 +1,876 @@ + + + + + + + + 2026-08-29T13:23:14.218343 + image/svg+xml + + + Matplotlib v3.10.8, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + 83.4 + + + + + + + + + + 166.8 + + + + + + + + + + 250.2 + + + + + + + + + + 333.5 + + + + + + + + + + 416.9 + + + + Input diameter (mV) + + + + + + + + + + + + + + + + + 0 + + + + + + + + + + + + + 20 + + + + + + + + + + + + + 40 + + + + + + + + + + + + + 60 + + + + + + + + + + + + + 80 + + + + Classification error (%) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + (a) Standard local learning + + + + + + + + + + Raw local update + + + + + + + + + Constant calibration + + + + + + + + + SDIL + + + + + + + + + + + + + + + + 83.4 + + + + + + + + + + 166.8 + + + + + + + + + + 250.2 + + + + + + + + + + 333.5 + + + + + + + + + + 416.9 + + + + Input diameter (mV) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 8 trials per input diameter + + + (b) Composition with overclamping + + + + + + + + + + Overclamping + + + + + + + + + Overclamping + SDIL + + + + + + + + + + + + + diff --git a/visual-composer/physical-grid-p5-contract.md b/visual-composer/physical-grid-p5-contract.md new file mode 100644 index 0000000..1c7b448 --- /dev/null +++ b/visual-composer/physical-grid-p5-contract.md @@ -0,0 +1,15 @@ +# Physical-grid P5 visual contract + +- Artifact: two-panel classification-error figure. +- Target format: full-width paper figure; editable SVG/PDF plus PNG preview. +- Core claim: local state-dependent residualization restores physical local learning and composes with overclamping. +- Reviewer question: does SDIL improve downstream classification over raw learning, constant calibration, and overclamping under the same component imperfections? +- Evidence layer: main result. +- Source data: `results/physical_bias/p5_grid_bias_crossover.json`. +- Statistics: eight trials per input diameter (two label rotations by four device draws); show every trial and the arithmetic mean, without inferred uncertainty intervals. +- Panel map: (a) raw, per-edge constant calibration, and SDIL under standard clamping; (b) overclamping and overclamping plus SDIL. +- Labels: classification error (%), input diameter (mV), method names, eight trials per diameter. +- Caption role: state the physical-grid setting, trial count, component-error model, and perfect-result counts without extending the evidence to hardware measurements. +- Output: `results/figs/physical_grid_p5.{svg,pdf,png}`. +- Traceability: plotting code reads the committed JSON directly; no values are copied by hand. + -- cgit v1.2.3