Source browser

scripts/plot_results.py

Read the complete file below or download its original bytes. Line numbers provide stable links to a declaration or processing step.

File fingerprint
1#!/usr/bin/env python3
2"""Render SVG figures from the visible result tables and supplied fixtures.
3
4NumPy and Matplotlib render the figures. The visible HTML tables provide the
5recorded measurements, and prepared data files provide the example imagery.
6"""
7import argparse  # Select a published result page or an alternate output directory.
8from html.parser import HTMLParser  # Read visible table cells with the standard-library HTML parser.
9from pathlib import Path  # Locate the versioned reports, fixtures and SVG outputs.
10import re  # Add accessible descriptions to the standalone vector figures.
11
12
13class ReportTables(HTMLParser):  # Collect the explicit publication tables and exact corpus scalar fields.
14    def __init__(self):  # Initialize the active table and captured field state.
15        super().__init__(convert_charrefs=True)  # Decode visible HTML entities back into their recorded text.
16        self.tables, self.values = {}, {}  # Retain table rows and path-addressed corpus values.
17        self.active, self.row, self.cell = None, None, None  # Track only the table currently being parsed.
18        self.row_attrs, self.capture = {}, None  # Retain optional row identity and one scalar capture.
19
20    def handle_starttag(self, tag, attrs):  # Recognize semantic table boundaries and report-value annotations.
21        attrs = dict(attrs)  # Access attributes by their names.
22        if tag == "table" and attrs.get("id"):  # Select tables carrying an authoritative identifier.
23            self.active = attrs["id"]  # Remember the current publication table.
24            self.tables[self.active] = []  # Collect its rows in displayed order.
25        if self.active and tag == "tr":  # Begin a row only within an identified table.
26            self.row, self.row_attrs = [], attrs  # Preserve visible cells and any stable row identity.
27        if self.row is not None and tag in ("td", "th"):  # Read both headings and ordinary data cells.
28            self.cell = []  # Begin accumulating this cell's visible text.
29        if attrs.get("data-report-path"):  # Read exact scalar provenance already visible in the corpus page.
30            self.capture = (tag, attrs["data-report-path"], [])  # Keep the closing tag and original dictionary path.
31
32    def handle_data(self, data):  # Preserve human-readable table text and recorded scalar values.
33        if self.cell is not None:  # Include nested code and link text in the current table cell.
34            self.cell.append(data)  # Keep text until the cell closes.
35        if self.capture is not None:  # Preserve the exact corpus value separately from surrounding table headings.
36            self.capture[2].append(data)  # Retain line breaks and scalar spelling.
37
38    def handle_endtag(self, tag):  # Finish each collected value at its semantic boundary.
39        if self.capture is not None and tag == self.capture[0]:  # Store a complete path-addressed report field.
40            self.values[self.capture[1]] = "".join(self.capture[2])  # Avoid changing diagnostics or hash strings.
41            self.capture = None  # Stop collecting after that scalar's closing tag.
42        if tag in ("td", "th") and self.cell is not None:  # Finish one displayed table cell.
43            self.row.append(" ".join("".join(self.cell).split()))  # Normalize only table-layout whitespace.
44            self.cell = None  # Wait for the next cell.
45        if tag == "tr" and self.row is not None:  # Finish one complete publication row.
46            self.tables[self.active].append((self.row_attrs, self.row))  # Retain attributes and cells together.
47            self.row = None  # Stop collecting cells until another row begins.
48        if tag == "table":  # Leave the current identified table.
49            self.active = None  # Collect text inside the selected table cells.
50
51
52def read_tables(path):  # Parse one local standalone HTML report.
53    reader = ReportTables()  # Use only the standard HTML parser to recover the visible record.
54    reader.feed(path.read_text(encoding="utf-8"))  # Read the complete published page.
55    return reader  # Return both visible tables and exact corpus metadata.
56
57
58def numeric_rows(report, identifier, start):  # Skip headings and return only rows with numeric measured fields.
59    result = []  # Keep the original visible row order.
60    for attrs, cells in report.tables[identifier]:  # Visit each heading or data row.
61        try:  # Parse measurements from numeric cells.
62            values = [float(value) for value in cells[start:]]  # Read the exact rounded numbers published in the table.
63        except ValueError:  # Keep headings out of the numerical record.
64            continue  # Continue with the next displayed row.
65        result.append((attrs, cells[:start], values))  # Retain labels and row identities with their numerical values.
66    return result  # Use the recorded measurement values and timing ratios.
67
68
69def save_svg(figure, output, title, description):  # Export a self-contained scientific vector figure.
70    figure.savefig(output, format="svg", bbox_inches="tight", metadata={"Date": "2026-10-15", "Creator": "shdr publication plots", "Title": title})  # Preserve the recorded measurement date.
71    text = output.read_text(encoding="utf-8")  # Add screen-reader text to the exported SVG root.
72    from html import escape  # Keep plot descriptions safe inside XML.
73    description = f'<title id="plot-title">{escape(title)}</title><desc id="plot-description">{escape(description)}</desc>'  # Explain units and scope inside the downloadable artifact.
74    text = re.sub(r"(<svg\b[^>]*)(>)", r'\1 role="img" aria-labelledby="plot-title plot-description"\2' + description, text, count=1)  # Make the SVG usable independently of its HTML figure caption.
75    text = "\n".join(line.rstrip() for line in text.splitlines()) + "\n"  # Remove exporter padding while preserving the SVG's paths and text.
76    output.write_text(text, encoding="utf-8")  # Keep every plot as a portable standalone file.
77    print(output.name)  # Report the artifact generated by this invocation.
78
79
80def benchmark_plot(report, host, title, graphics, output, np, plt):  # Compare all 25 measured pipelines on one configuration.
81    rows = numeric_rows(report, "benchmark-" + host, 2)  # Read the exact visible CPU, graphics and reported ratio columns.
82    labels = [label[0] for _, label, _ in rows]  # Preserve names in the same order as the benchmark executable.
83    values = np.asarray([value for _, _, value in rows])  # Keep recorded rounded means and unrounded-derived reported ratios.
84    y = np.arange(len(rows))  # Assign one shared position to each pipeline.
85    figure, axes = plt.subplots(1, 2, figsize=(14.5, 12.2), gridspec_kw={"width_ratios": [2.6, 1]}, sharey=True)  # Align time and relative-completion views for direct reading.
86    axes[0].barh(y - .17, values[:, 0], height=.32, color="#225d9b", label="C++11 CPU")  # Plot the measured serial CPU means.
87    axes[0].barh(y + .17, values[:, 1], height=.32, color="#0c8a79", label=graphics)  # Distinguish llvmpipe software from actual hardware in the legend.
88    axes[0].set_xscale("log")  # Keep microsecond-scale and large neural workloads visible together.
89    axes[0].set_yticks(y, labels, fontsize=9)  # Make every pipeline label readable in the vector export.
90    axes[0].invert_yaxis()  # Match the published table's top-to-bottom ordering.
91    axes[0].set_xlabel("Mean completed workload time (ms, logarithmic axis)")  # State the measured unit and nonlinear axis clearly.
92    axes[0].grid(axis="x", which="both", alpha=.2)  # Help compare bars across the broad timing range.
93    axes[0].legend(loc="upper right", frameon=False)  # Place the execution-path legend beside the row labels.
94    axes[1].axvline(1, color="#566071", linewidth=1, linestyle="--")  # Mark equal completion time explicitly.
95    axes[1].scatter(values[:, 2], y, s=34, color="#bd5a2d", zorder=3)  # Use the benchmark's recorded CPU/GPU ratio.
96    axes[1].set_xscale("log")  # Show advantages in either direction on a symmetric ratio scale.
97    axes[1].set_xlim(.02, 40)  # Use the same ratio range for all three configurations.
98    axes[1].set_xticks([.02, .1, 1, 10, 40], ["0.02×", "0.1×", "1×", "10×", "40×"])  # Avoid opaque scientific notation for the relative-completion view.
99    axes[1].set_xlabel("CPU time / graphics time")  # Label the plotted value as the CPU/GPU time ratio.
100    axes[1].grid(axis="x", which="both", alpha=.2)  # Retain readable ratio comparisons at small values.
101    axes[1].tick_params(axis="y", left=False, labelleft=False)  # Keep a single shared pipeline-label column.
102    figure.suptitle(title, fontsize=18, x=.02, ha="left")  # Name the tested CPU and graphics implementation.
103    figure.text(.02, .025, "20 measured repetitions · seed 12345 · scale 1 · ES 3.1 profile\nResident means exclude compilation, first upload and final readback. Hybrid transfers remain included.", fontsize=10, color="#46546a")  # Keep timing scope attached to downloaded figures.
104    figure.tight_layout(rect=(0, .06, 1, .97))  # Reserve room for the title and measurement note.
105    save_svg(figure, output, title, "All 25 recorded pipeline timings and CPU-to-graphics ratios. Timing means are in milliseconds. Ratios greater than one favor the graphics path.")  # Export the scientific plot with accessible units and interpretation.
106    plt.close(figure)  # Release figure memory before generating the next configuration.
107
108
109def evaluation_plot(report, output, np, plt):  # Compare the six recorded complete held-out evaluations.
110    rows = numeric_rows(report, "mnist-metrics", 2)  # Read correct counts, image counts, accuracy, seconds and maximum error.
111    labels = [text[0] + " / " + text[1] for _, text, _ in rows]  # Keep each CPU and graphics configuration explicit.
112    accuracy = np.array([values[2] for _, _, values in rows])  # Preserve the logged accuracy percentages.
113    seconds = np.array([values[3] for _, _, values in rows])  # Keep full-evaluation scope separate from resident benchmark time.
114    figure, axes = plt.subplots(1, 2, figsize=(14.5, 5.3))  # Show accuracy and completed duration side by side.
115    y = np.arange(len(rows))  # Assign the same row to each path in both panels.
116    axes[0].barh(y, accuracy, color="#0c8a79", label="Correct")  # Show measured successful classifications on a full percentage scale.
117    axes[0].barh(y, 100 - accuracy, left=accuracy, color="#d9a06f", label="Incorrect")  # Show the complete observed error fraction.
118    axes[0].set_xlim(0, 100)  # Avoid exaggerating equal accuracies with a truncated axis.
119    axes[0].set_yticks(y, labels, fontsize=9)  # Retain the identity of all six measured paths.
120    axes[0].invert_yaxis()  # Match the accessible result table order.
121    axes[0].set_xlabel("Official held-out labels (%)")  # State the denominator and unit.
122    axes[0].legend(frameon=False, loc="lower left", bbox_to_anchor=(0, 1.0), ncol=2)  # Explain both correct and incorrect portions of every bar.
123    axes[1].barh(y, seconds, color=["#225d9b" if index % 2 == 0 else "#0c8a79" for index in y])  # Distinguish CPU and graphics durations consistently.
124    axes[1].set_yticks(y, labels, fontsize=9)  # Keep the duration panel usable on its own.
125    axes[1].invert_yaxis()  # Align paths with the accuracy panel.
126    axes[1].set_xlabel("Completed 10,000-image evaluation (seconds)")  # Include preprocessing and comparison in the stated scope.
127    for index, value in enumerate(seconds):  # Label actual recorded durations directly on the figure.
128        axes[1].text(value + .4, index, f"{value:g}", va="center", fontsize=9)  # Avoid estimating values from the axis.
129    axes[1].set_xlim(0, max(seconds) * 1.18)  # Leave room for the duration annotations.
130    axes[1].grid(axis="x", alpha=.2)  # Help compare completed evaluation times.
131    figure.suptitle("Complete MNIST evaluation · 9,778 / 10,000 correct on every path", fontsize=16)  # State the exact observed classification result.
132    figure.tight_layout(rect=(0, 0, 1, .94))  # Keep labels and title clear of each other.
133    save_svg(figure, output, "Complete held-out MNIST evaluations", "Every recorded path classified 9778 of 10000 labels correctly, or 97.78 percent. Duration includes batch construction, input preparation, native execution, result reads and reference comparison. llvmpipe uses the CPU.")  # Keep model-evaluation scope with the plot.
134    plt.close(figure)  # Release plotting resources.
135
136
137def coverage_plot(report, output, np, plt):  # Show passed tests and skipped profiles as separate outcomes.
138    rows = numeric_rows(report, "runtime-tests", 1)  # Read passed, skipped, failed and total counts.
139    values = np.array([value for _, _, value in rows], dtype=int)  # Keep test outcomes as exact integer counts.
140    labels = [text[0] for _, text, _ in rows]  # Label configurations by their public hardware names.
141    figure, axis = plt.subplots(figsize=(11.5, 4.2))  # Give each registered configuration a clearly separated bar.
142    y = np.arange(len(rows))  # Position the three platform totals.
143    axis.barh(y, values[:, 0], color="#0c8a79", label="Passed")  # Count only tests that actually completed successfully.
144    axis.barh(y, values[:, 1], left=values[:, 0], color="#c5cedb", label="Unavailable context")  # Show profile-availability skips explicitly.
145    axis.barh(y, values[:, 2], left=values[:, 0] + values[:, 1], color="#c3443b", label="Failed")  # Keep a zero-valued failure category explicit in the legend.
146    for index, row in enumerate(values):  # Label the plotted bars with exact counts.
147        axis.text(row[3] + 1, index, f"{row[0]} passed · {row[1]} skipped · {row[2]} failed", va="center", fontsize=10)  # Include the absence of failures and the scope of skipped contexts.
148    axis.set_yticks(y, labels)  # Identify the tested renderer configuration.
149    axis.invert_yaxis()  # Preserve the order of the source table.
150    axis.set_xlim(0, max(values[:, 3]) + 47)  # Reserve room for complete count labels.
151    axis.set_xlabel("Registered CTest cases")  # Distinguish registered cases from individual shader/profile compilations.
152    axis.legend(frameon=False, loc="lower right")  # Identify skips as profile-availability outcomes.
153    axis.set_title("Runtime and integration validation · no failed registered tests", loc="left", fontsize=15)  # Describe observed outcomes with their unit of coverage.
154    figure.tight_layout()  # Keep count labels inside the exported bounds.
155    save_svg(figure, output, "Registered test outcomes", "Passed, skipped and failed registered CTest cases for the three configurations. Some registered cases contain many numerical checks. The separate 250 shader/profile checks and eight frontend tests are described in the result tables.")  # Prevent confusion between suite totals and inner assertion counts.
156    plt.close(figure)  # Release the figure after export.
157
158
159def loss_plot(report, output, np, plt):  # Plot the measured initial and final mini-batch losses.
160    rows = numeric_rows(report, "mini-batch-loss", 1)  # Read step count, starting loss and final loss.
161    labels = [text[0] for _, text, _ in rows]  # Keep the three small model names visible.
162    values = np.array([value for _, _, value in rows])  # Use the identical logged values observed across all six paths.
163    x = np.arange(len(rows))  # Separate the three architectures.
164    figure, axis = plt.subplots(figsize=(9, 4.8))  # Use bars because only the start and finish were recorded.
165    axis.bar(x - .18, values[:, 1], width=.35, color="#8ca7c5", label="Before updates")  # Show the observed starting cross entropy.
166    axis.bar(x + .18, values[:, 2], width=.35, color="#0c8a79", label="After updates")  # Show the observed final mini-batch cross entropy.
167    axis.set_xticks(x, [name + f"\n{int(value[0])} updates" for name, value in zip(labels, values)])  # Make differing update counts explicit.
168    axis.set_ylim(0, .82)  # Show a true zero baseline for the loss comparison.
169    axis.set_ylabel("Mean binary cross entropy")  # Use the models' actual scalar loss definition.
170    axis.legend(frameon=False)  # Distinguish the two recorded observations.
171    axis.set_title("Real digit mini-batch training · 32 selected zero/one images", loc="left", fontsize=14)  # Avoid calling training-set loss a held-out accuracy result.
172    axis.grid(axis="y", alpha=.2)  # Help compare measured loss changes.
173    figure.tight_layout()  # Keep model and update-count labels readable.
174    save_svg(figure, output, "Observed real-data mini-batch loss changes", "Recorded loss before and after training for ANN, CNN and transformer models on one 32-image training mini-batch. No intermediate learning curve or generalization metric was recorded.")  # State the limited scope of these observations.
175    plt.close(figure)  # Release plotting resources.
176
177
178def confusion_plot(report, output, np, plt):  # Plot the complete independent-framework reference confusion table.
179    rows = numeric_rows(report, "reference-confusion", 1)  # Read the ten visible ground-truth rows and predicted-class counts.
180    matrix = np.array([value for _, _, value in rows], dtype=int)  # Preserve all 100 actual confusion counts.
181    from matplotlib.colors import LogNorm  # Make rare confusions readable beside large correct-class totals.
182    figure, axis = plt.subplots(figsize=(8.5, 7.3))  # Keep each digit pair legible in the standalone SVG.
183    image = axis.imshow(np.ma.masked_equal(matrix, 0), cmap="Blues", norm=LogNorm(vmin=1, vmax=matrix.max()))  # Leave zero-count cells white and display positive counts logarithmically.
184    for row in range(10):  # Annotate every original digit class.
185        for column in range(10):  # Annotate every predicted digit class.
186            value = matrix[row, column]  # Use the exact displayed reference count.
187            axis.text(column, row, str(value), ha="center", va="center", fontsize=8, color="white" if value > 50 else "#17243a")  # Keep large and small counts readable against the color scale.
188    axis.set_xticks(range(10))  # Label predictions with their original digit values.
189    axis.set_yticks(range(10))  # Label true classes with the official MNIST labels.
190    axis.set_xlabel("Predicted digit · independent framework reference")  # Avoid claiming that native per-class predictions were archived.
191    axis.set_ylabel("Official test digit")  # State the ground-truth source.
192    axis.set_title("Reference confusion · 10,000 official test images", loc="left", fontsize=14)  # Define the complete evaluated population.
193    figure.colorbar(image, ax=axis, label="Reference predictions per cell (log scale)")  # State the nonlinear color unit.
194    figure.tight_layout()  # Keep axis labels and colorbar visible.
195    save_svg(figure, output, "Independent reference model confusion", "Exact confusion counts from the shipped independent framework logits and all 10000 official labels. Native per-class prediction matrices were not retained. Native complete scores and total accuracy were checked separately.")  # Keep the distinction between native and reference predictions explicit.
196    plt.close(figure)  # Release plotting resources.
197
198
199def fixture_plot(fixtures, output, np, plt):  # Illustrate actual prepared inputs and an explicitly independent signal reference.
200    earth = np.frombuffer((fixtures / "earth_rgb.bin").read_bytes(), dtype=np.uint8).reshape(64, 64, 3)  # Read the exact prepared NASA RGB image.
201    ecg = np.frombuffer((fixtures / "signal_ecg.bin").read_bytes(), dtype="<f4").reshape(2, 2048)[0].astype(float)  # Read the channel-major MLII signal in millivolts.
202    digits = np.frombuffer((fixtures / "mnist_test.bin").read_bytes(), dtype=np.uint8).reshape(-1, 28, 28)  # Read the shipped official test-image prefix.
203    labels = np.frombuffer((fixtures / "mnist_labels.bin").read_bytes(), dtype=np.uint8)  # Retain the original digit labels.
204    indices = [int(np.flatnonzero(labels == value)[0]) for value in range(10)]  # Show the first supplied instance of each class.
205    offsets = np.arange(65) - 32  # Center the documented odd-length FIR design.
206    coefficients = 2 * (40 / 360) * np.sinc(2 * (40 / 360) * offsets) * np.hamming(65)  # Independently calculate the Hamming-sinc 40 Hz low-pass reference.
207    coefficients = (coefficients / coefficients.sum()).astype(np.float32).astype(float)  # Match stored coefficient precision while keeping the reference accumulation independent.
208    filtered = np.convolve(ecg, coefficients)[:len(ecg)]  # Apply the documented causal zero-prefix FIR convention.
209    time = np.arange(len(ecg)) / 360  # Express sample positions in seconds at the public recording's sample rate.
210    frequencies = np.fft.rfftfreq(len(ecg), 1 / 360)  # Express the real-spectrum bins in hertz.
211    figure = plt.figure(figsize=(14.5, 8.5))  # Combine image, waveform, spectrum and digit examples into one clear fixture plate.
212    grid = figure.add_gridspec(3, 3, height_ratios=[1, 1, .65], width_ratios=[1, 1.1, 1.1])  # Reserve a full-width final row for the ten digit classes.
213    axis = figure.add_subplot(grid[:2, 0])  # Display the supplied low-resolution image.
214    axis.imshow(earth, interpolation="nearest")  # Preserve the prepared RGB8 pixels.
215    axis.set_title("NASA Blue Marble · prepared 64 × 64 RGB", fontsize=11)  # Identify the source and prepared image size.
216    axis.axis("off")  # Avoid implying a geographic coordinate system for the RGB preview.
217    axis = figure.add_subplot(grid[0, 1:])  # Show the original ECG and independent filter reference together.
218    axis.plot(time, ecg, linewidth=.8, color="#7a8fa8", label="Prepared MLII")  # Plot the actual float32 waveform from the fixture.
219    axis.plot(time, filtered, linewidth=1, color="#0c8a79", label="Independent 65-tap, 40 Hz FIR reference")  # Clearly distinguish a computed reference from archived runtime output.
220    axis.set_xlim(0, 2.5)  # Show the original samples from an initial waveform interval.
221    axis.set_xlabel("Time (s)")  # State waveform time units.
222    axis.set_ylabel("Potential (mV)")  # Preserve the published ADC scaling.
223    axis.legend(frameon=False, fontsize=9, ncol=2)  # Identify source samples and reference filtering.
224    axis.grid(alpha=.2)  # Make waveform amplitude comparisons readable.
225    axis = figure.add_subplot(grid[1, 1:])  # Show the independent full-fixture real Fourier spectrum.
226    for signal, color, name in [(ecg, "#7a8fa8", "Prepared MLII"), (filtered, "#0c8a79", "Independent FIR reference")]:  # Use the same sources and colors as the waveform panel.
227        amplitude = np.abs(np.fft.rfft(signal)) / len(signal)  # Compute a correctly normalized real-signal amplitude spectrum.
228        amplitude[1:-1] *= 2  # Double only the interior positive-frequency bins.
229        axis.semilogy(frequencies, np.maximum(amplitude, 1e-9), color=color, linewidth=.9, label=name)  # Use an explicit small display floor on the logarithmic amplitude axis.
230    axis.set_xlabel("Frequency (Hz)")  # Use physical frequency units on the axis.
231    axis.set_ylabel("Single-sided amplitude (mV)")  # State the spectrum normalization and signal unit.
232    axis.set_xlim(0, 180)  # Show the original 360 Hz recording's complete Nyquist range.
233    axis.grid(alpha=.2)  # Help locate the low-pass attenuation region.
234    digit_grid = grid[2, :].subgridspec(1, 10)  # Keep one original labeled sample per digit class.
235    for digit, index in enumerate(indices):  # Preserve an explicit reproducible sample-selection rule.
236        axis = figure.add_subplot(digit_grid[0, digit])  # Give each test digit its own clean preview.
237        axis.imshow(digits[index], cmap="gray", vmin=0, vmax=255, interpolation="nearest")  # Display the original unsigned-byte image values.
238        axis.set_title(f"Label {digit}\nTest index {index}", fontsize=9)  # Distinguish ground-truth labels from model predictions.
239        axis.axis("off")  # Keep pixel previews uncluttered.
240    figure.suptitle("Recorded public inputs · images, ECG and official handwritten digits", fontsize=16)  # Describe the figure as actual input data and reference signal analysis.
241    figure.tight_layout(rect=(0, 0, 1, .95))  # Separate the title from the specimen panels.
242    save_svg(figure, output, "Actual prepared public fixtures", "Actual NASA image pixels, PhysioNet MLII samples at 360 Hz, independently calculated 40 Hz FIR and Fourier references, and first shipped MNIST instance of each class. This is an input illustration, not an invented runtime measurement.")  # Keep source and reference distinctions with the standalone artifact.
243    plt.close(figure)  # Release plotting resources.
244
245
246def main():  # Reproduce all figures from the published HTML and bundled data.
247    root = Path(__file__).resolve().parent.parent  # Resolve paths relative to the project.
248    parser = argparse.ArgumentParser(description=__doc__)  # Explain that plotting is an optional publication step.
249    parser.add_argument("--report", type=Path, default=root / "docs/results.html")  # Read the authoritative visible measurement tables.
250    parser.add_argument("--output", type=Path, default=root / "docs/plots")  # Choose where standalone SVGs are written.
251    parser.add_argument("--fixtures", type=Path, default=root / "examples/data")  # Read the prepared public input bytes.
252    args = parser.parse_args()  # Apply explicit caller paths before importing optional plotting packages.
253    import numpy as np  # Calculate only reference illustrations and array-based plot coordinates.
254    import matplotlib  # Keep scientific rendering outside normal library builds.
255    matplotlib.use("Agg")  # Render figures with the headless plotting backend.
256    import matplotlib.pyplot as plt  # Render standard scientific vector plots.
257    plt.rcParams.update({"font.family": "DejaVu Sans", "svg.fonttype": "none", "svg.hashsalt": "shdr-publication-2026-10-15", "axes.spines.top": False, "axes.spines.right": False, "axes.titlepad": 12})  # Keep selectable text, clear axes and consistent publication typography.
258    report = read_tables(args.report)  # Recover all plotted measurements from their accessible tables.
259    args.output.mkdir(parents=True, exist_ok=True)  # Create only the requested publication artifact directory.
260    for host, title, graphics in [("amd", "Ryzen 9 5900X · C++11 and llvmpipe software", "llvmpipe software"), ("intel", "Core i7-1260P · C++11 and Iris Xe", "Iris Xe hardware"), ("pi", "Raspberry Pi 5 · C++11 and V3D", "V3D hardware")]:  # Generate the same benchmark views for all measured configurations.
261        benchmark_plot(report, host, title, graphics, args.output / ("benchmark-" + host + ".svg"), np, plt)  # Preserve platform identity and exact recorded numbers.
262    evaluation_plot(report, args.output / "mnist-evaluation.svg", np, plt)  # Show all six complete held-out measurements.
263    coverage_plot(report, args.output / "test-coverage.svg", np, plt)  # Separate successful tests from unavailable-context skips.
264    loss_plot(report, args.output / "mini-batch-loss.svg", np, plt)  # Plot only the before-and-after training observations actually recorded.
265    confusion_plot(report, args.output / "mnist-reference-confusion.svg", np, plt)  # Reproduce the full visible reference confusion matrix.
266    fixture_plot(args.fixtures, args.output / "public-fixtures.svg", np, plt)  # Illustrate the real shipped input bytes with clearly labeled reference analysis.
267
268
269if __name__ == "__main__":  # Run plotting only when requested explicitly.
270    main()  # Generate the standalone publication figures.