Buckets:
| #!/usr/bin/env python3 | |
| """Create auditable, self-contained interactive figures from canonical CSVs. | |
| Every generated HTML file embeds Plotly itself and has a sibling CSV containing | |
| the exact derived rows shown in the figure. A deterministic MANIFEST.json lists | |
| both generated and unavailable panels, so absent evidence cannot be mistaken for | |
| an empty or successful visualization. | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import csv | |
| import hashlib | |
| import json | |
| import math | |
| import os | |
| import shutil | |
| import statistics | |
| import sys | |
| import tempfile | |
| from collections import Counter, defaultdict | |
| from dataclasses import dataclass | |
| from pathlib import Path | |
| from typing import Any, Callable, Mapping, Sequence | |
| import plotly.graph_objects as go | |
| import plotly.io as pio | |
| from plotly.subplots import make_subplots | |
| FIGURE_BUILDER_VERSION = "1.2.0" | |
| SUPPORTED_TABLE_SCHEMA = "1.0.0" | |
| REQUIRED_TABLES = ( | |
| "episodes.csv", | |
| "training_runs.csv", | |
| "training_metrics.csv", | |
| "proof_certificates.csv", | |
| "parameter_audit.csv", | |
| "summaries.csv", | |
| "artifacts.csv", | |
| "selections.csv", | |
| "environments.csv", | |
| "claim_summaries.csv", | |
| "errors.csv", | |
| "exclusions.csv", | |
| ) | |
| MOUNTAINCAR_ENVIRONMENT = "MountainCarContinuous-v0" | |
| PENDULUM_ENVIRONMENT = "DeterministicPendulum-v1" | |
| COLORS = ( | |
| "#0072B2", | |
| "#D55E00", | |
| "#009E73", | |
| "#CC79A7", | |
| "#E69F00", | |
| "#56B4E9", | |
| "#F0E442", | |
| "#000000", | |
| ) | |
| METHOD_LABELS = { | |
| "analytic_two_phase": "Analytical", | |
| "ars": "ARS", | |
| "ppo": "PPO", | |
| "sac": "SAC", | |
| "ch3_ars": "CH-3-ARS", | |
| "ch3_ppo": "CH-3-PPO", | |
| "ch3_reinforce_adamw_seeded": "CH-3-REINFORCE", | |
| "ch3_reinforce_adamw_seeded_selected": "CH-3-REINFORCE · selected policy", | |
| "ch6_ars": "CH-6-ARS", | |
| "ch6_ars_pendulum": "CH-6-ARS candidates", | |
| "ch6_ars_pendulum_selected": "CH-6-ARS", | |
| "ars_baseline_pendulum_released": "ARS (released)", | |
| "ars_baseline_pendulum_released_corrected_reset": "ARS (corrected reset)", | |
| } | |
| class ValidationError(RuntimeError): | |
| """Raised when canonical evidence is invalid or internally inconsistent.""" | |
| class FigureResult: | |
| key: str | |
| status: str | |
| reason: str | None = None | |
| html_path: str | None = None | |
| data_path: str | None = None | |
| row_count: int = 0 | |
| details: Mapping[str, Any] | None = None | |
| def sha256_file(path: Path) -> str: | |
| digest = hashlib.sha256() | |
| with path.open("rb") as handle: | |
| for chunk in iter(lambda: handle.read(1024 * 1024), b""): | |
| digest.update(chunk) | |
| return digest.hexdigest() | |
| def canonical_json(value: Any) -> str: | |
| return json.dumps( | |
| value, | |
| ensure_ascii=False, | |
| allow_nan=False, | |
| separators=(",", ":"), | |
| sort_keys=True, | |
| ) | |
| def _reject_json_constant(value: str) -> None: | |
| raise ValidationError(f"Non-standard JSON constant is forbidden: {value}") | |
| def _unique_object(pairs: Sequence[tuple[str, Any]]) -> dict[str, Any]: | |
| output: dict[str, Any] = {} | |
| for key, value in pairs: | |
| if key in output: | |
| raise ValidationError(f"Duplicate JSON object key: {key!r}") | |
| output[key] = value | |
| return output | |
| def load_json_object(path: Path) -> dict[str, Any]: | |
| try: | |
| value = json.loads( | |
| path.read_text(encoding="utf-8"), | |
| object_pairs_hook=_unique_object, | |
| parse_constant=_reject_json_constant, | |
| ) | |
| except (OSError, json.JSONDecodeError, ValidationError) as exc: | |
| raise ValidationError(f"Invalid JSON in {path}: {exc}") from exc | |
| if not isinstance(value, dict): | |
| raise ValidationError(f"Expected JSON object in {path}") | |
| return value | |
| def require_nonempty(value: Any, label: str) -> str: | |
| if not isinstance(value, str) or not value.strip(): | |
| raise ValidationError(f"{label} must be a non-empty string") | |
| return value | |
| def parse_finite(value: str, label: str) -> float: | |
| if value == "": | |
| raise ValidationError(f"Missing required numeric value: {label}") | |
| try: | |
| number = float(value) | |
| except ValueError as exc: | |
| raise ValidationError(f"Invalid numeric value for {label}: {value!r}") from exc | |
| if not math.isfinite(number): | |
| raise ValidationError(f"Non-finite numeric value for {label}: {value!r}") | |
| return number | |
| def parse_optional_int(value: str, label: str) -> int | None: | |
| if value == "": | |
| return None | |
| try: | |
| number = int(value) | |
| except ValueError as exc: | |
| raise ValidationError(f"Invalid integer value for {label}: {value!r}") from exc | |
| return number | |
| def read_verified_tables( | |
| tables_root: Path, | |
| ) -> tuple[dict[str, Any], dict[str, list[dict[str, str]]]]: | |
| tables_root = tables_root.resolve() | |
| manifest_path = tables_root / "MANIFEST.json" | |
| if not manifest_path.is_file() or manifest_path.is_symlink(): | |
| raise ValidationError(f"Missing real canonical manifest: {manifest_path}") | |
| manifest = load_json_object(manifest_path) | |
| if manifest.get("schema_version") != SUPPORTED_TABLE_SCHEMA: | |
| raise ValidationError( | |
| f"Unsupported canonical schema: {manifest.get('schema_version')!r}" | |
| ) | |
| if manifest.get("status") != "success": | |
| raise ValidationError("Canonical table manifest is not successful") | |
| declared_tables = manifest.get("tables") | |
| if not isinstance(declared_tables, dict): | |
| raise ValidationError("Canonical manifest.tables must be an object") | |
| missing = sorted(set(REQUIRED_TABLES) - set(declared_tables)) | |
| unexpected = sorted(set(declared_tables) - set(REQUIRED_TABLES)) | |
| if missing or unexpected: | |
| raise ValidationError( | |
| f"Canonical table inventory mismatch; missing={missing}, unexpected={unexpected}" | |
| ) | |
| tables: dict[str, list[dict[str, str]]] = {} | |
| for table_name in REQUIRED_TABLES: | |
| declaration = declared_tables[table_name] | |
| if not isinstance(declaration, dict): | |
| raise ValidationError(f"Invalid declaration for {table_name}") | |
| if declaration.get("path") != table_name: | |
| raise ValidationError(f"Non-canonical table path for {table_name}") | |
| expected_hash = require_nonempty( | |
| declaration.get("sha256"), f"{table_name}.sha256" | |
| ) | |
| expected_rows = declaration.get("rows") | |
| expected_columns = declaration.get("columns") | |
| if ( | |
| isinstance(expected_rows, bool) | |
| or not isinstance(expected_rows, int) | |
| or expected_rows < 0 | |
| ): | |
| raise ValidationError(f"Invalid row count for {table_name}") | |
| if not isinstance(expected_columns, list) or not all( | |
| isinstance(column, str) and column for column in expected_columns | |
| ): | |
| raise ValidationError(f"Invalid column declaration for {table_name}") | |
| if len(expected_columns) != len(set(expected_columns)): | |
| raise ValidationError(f"Duplicate declared columns for {table_name}") | |
| path = tables_root / table_name | |
| if not path.is_file() or path.is_symlink(): | |
| raise ValidationError(f"Missing real canonical table: {path}") | |
| if sha256_file(path) != expected_hash: | |
| raise ValidationError(f"Canonical table hash mismatch: {table_name}") | |
| with path.open("r", encoding="utf-8", newline="") as handle: | |
| reader = csv.DictReader(handle) | |
| if reader.fieldnames != expected_columns: | |
| raise ValidationError( | |
| f"Canonical CSV header mismatch for {table_name}: " | |
| f"{reader.fieldnames} != {expected_columns}" | |
| ) | |
| rows = [] | |
| for line_number, row in enumerate(reader, start=2): | |
| if None in row: | |
| raise ValidationError( | |
| f"Extra CSV fields in {table_name}:{line_number}" | |
| ) | |
| if any(value is None for value in row.values()): | |
| raise ValidationError( | |
| f"Short CSV row in {table_name}:{line_number}" | |
| ) | |
| rows.append(dict(row)) | |
| if len(rows) != expected_rows: | |
| raise ValidationError( | |
| f"Canonical row-count mismatch for {table_name}: {len(rows)} != {expected_rows}" | |
| ) | |
| tables[table_name] = rows | |
| return manifest, tables | |
| def csv_scalar(value: Any) -> str | int: | |
| if value is None: | |
| return "" | |
| if isinstance(value, bool): | |
| return "true" if value else "false" | |
| if isinstance(value, int): | |
| return value | |
| if isinstance(value, float): | |
| if not math.isfinite(value): | |
| raise ValidationError("Cannot write a non-finite figure value") | |
| return format(value, ".17g") | |
| if isinstance(value, (dict, list)): | |
| return canonical_json(value) | |
| return str(value) | |
| def write_rows( | |
| path: Path, rows: Sequence[Mapping[str, Any]], columns: Sequence[str] | |
| ) -> None: | |
| if not rows: | |
| raise ValidationError( | |
| f"Refusing to create an empty figure dataset: {path.name}" | |
| ) | |
| unknown = sorted({key for row in rows for key in row} - set(columns)) | |
| if unknown: | |
| raise ValidationError(f"Figure data has undeclared columns: {unknown}") | |
| with path.open("w", encoding="utf-8", newline="") as handle: | |
| writer = csv.DictWriter(handle, fieldnames=list(columns), lineterminator="\n") | |
| writer.writeheader() | |
| for row in rows: | |
| writer.writerow({column: csv_scalar(row.get(column)) for column in columns}) | |
| def style_figure( | |
| fig: go.Figure, title: str, height: int = 650, width: int | None = None | |
| ) -> None: | |
| fig.update_layout( | |
| template="plotly_white", | |
| title={ | |
| "text": title, | |
| "x": 0.01, | |
| "xanchor": "left", | |
| "y": 0.985, | |
| "yanchor": "top", | |
| }, | |
| font={"family": "Inter, ui-sans-serif, system-ui, sans-serif", "size": 14}, | |
| hoverlabel={"font_size": 13}, | |
| legend={ | |
| "orientation": "h", | |
| "yanchor": "bottom", | |
| "y": 1.02, | |
| "x": 0, | |
| "font": {"size": 12}, | |
| }, | |
| margin={"l": 75, "r": 35, "t": 155, "b": 70}, | |
| height=height, | |
| width=width, | |
| ) | |
| def display_method(method: str) -> str: | |
| """Return a compact stable label while preserving unknown method names.""" | |
| return METHOD_LABELS.get(method, method.replace("_", "-")) | |
| def rgba(hex_color: str, alpha: float) -> str: | |
| value = hex_color.lstrip("#") | |
| red, green, blue = (int(value[index : index + 2], 16) for index in (0, 2, 4)) | |
| return f"rgba({red},{green},{blue},{alpha})" | |
| def write_self_contained_html(path: Path, fig: go.Figure, div_id: str) -> None: | |
| html = pio.to_html( | |
| fig, | |
| include_plotlyjs=True, | |
| full_html=True, | |
| config={"displaylogo": False, "responsive": True}, | |
| div_id=div_id, | |
| auto_play=False, | |
| ) | |
| if "<script src=" in html.lower(): | |
| raise ValidationError( | |
| f"Generated HTML unexpectedly references external JavaScript: {path}" | |
| ) | |
| path.write_text(html, encoding="utf-8") | |
| def generated_result( | |
| staging: Path, | |
| key: str, | |
| fig: go.Figure, | |
| rows: Sequence[Mapping[str, Any]], | |
| columns: Sequence[str], | |
| details: Mapping[str, Any], | |
| ) -> FigureResult: | |
| html_name = f"{key}.html" | |
| data_name = f"{key}.csv" | |
| write_rows(staging / data_name, rows, columns) | |
| write_self_contained_html(staging / html_name, fig, f"figure-{key}") | |
| return FigureResult( | |
| key=key, | |
| status="generated", | |
| html_path=html_name, | |
| data_path=data_name, | |
| row_count=len(rows), | |
| details=dict(details), | |
| ) | |
| def successful_environment_rows( | |
| episodes: Sequence[Mapping[str, str]], environment: str | |
| ) -> list[Mapping[str, str]]: | |
| return [ | |
| row | |
| for row in episodes | |
| if row.get("status") == "success" and row.get("environment") == environment | |
| ] | |
| def selected_seed_index( | |
| selections: Sequence[Mapping[str, str]], | |
| context: str, | |
| *, | |
| claim_ids: set[str] | None = None, | |
| ) -> dict[tuple[str, str], int]: | |
| """Return the unique declared selected seed for each batch and method.""" | |
| output: dict[tuple[str, str], int] = {} | |
| for row in selections: | |
| if row.get("status") != "success": | |
| continue | |
| if claim_ids is not None and row.get("claim_id") not in claim_ids: | |
| continue | |
| batch_id = require_nonempty( | |
| row.get("batch_id"), f"{context}.selection.batch_id" | |
| ) | |
| method = require_nonempty(row.get("method"), f"{context}.selection.method") | |
| selected_seed = parse_optional_int( | |
| row.get("selected_seed", ""), f"{context}.selection.selected_seed" | |
| ) | |
| if selected_seed is None: | |
| raise ValidationError( | |
| f"{context}: successful selection has no selected_seed" | |
| ) | |
| identity = (batch_id, method) | |
| if identity in output: | |
| raise ValidationError( | |
| f"{context}: multiple successful selection records for {identity}" | |
| ) | |
| output[identity] = selected_seed | |
| return output | |
| def build_mountaincar_return_figure( | |
| staging: Path, tables: Mapping[str, Sequence[Mapping[str, str]]] | |
| ) -> FigureResult: | |
| key = "mountaincar_return_vs_start" | |
| environment_rows = successful_environment_rows( | |
| tables["episodes.csv"], MOUNTAINCAR_ENVIRONMENT | |
| ) | |
| source_rows = [ | |
| row for row in environment_rows if row.get("start_position", "") != "" | |
| ] | |
| if not source_rows: | |
| return FigureResult( | |
| key, | |
| "missing", | |
| f"No successful {MOUNTAINCAR_ENVIRONMENT} fixed-start episode rows", | |
| ) | |
| selections = selected_seed_index( | |
| tables["selections.csv"], key, claim_ids={"claim3", "claim4"} | |
| ) | |
| groups: dict[tuple[str, str, float], list[tuple[float, str, str, int | None]]] = ( | |
| defaultdict(list) | |
| ) | |
| for row in source_rows: | |
| start = parse_finite(row.get("start_position", ""), f"{key}.start_position") | |
| value = parse_finite(row.get("return", ""), f"{key}.return") | |
| batch_id = require_nonempty(row.get("batch_id"), f"{key}.batch_id") | |
| method = require_nonempty(row.get("method"), f"{key}.method") | |
| seed = parse_optional_int(row.get("seed", ""), f"{key}.seed") | |
| groups[(batch_id, method, start)].append( | |
| (value, row.get("claim_id", ""), row.get("panel_id", ""), seed) | |
| ) | |
| derived: list[dict[str, Any]] = [] | |
| for (batch_id, method, start), values in sorted(groups.items()): | |
| returns = [item[0] for item in values] | |
| derived.append( | |
| { | |
| "batch_id": batch_id, | |
| "method": method, | |
| "performance_scope": "all_recorded_fixed_grid", | |
| "selected_seed": None, | |
| "start_position": start, | |
| "episode_count": len(returns), | |
| "mean_return": statistics.fmean(returns), | |
| "min_return": min(returns), | |
| "max_return": max(returns), | |
| "claim_ids": sorted({item[1] for item in values if item[1]}), | |
| "panel_ids": sorted({item[2] for item in values if item[2]}), | |
| } | |
| ) | |
| selected_trace_status: list[dict[str, Any]] = [] | |
| available_methods = { | |
| (str(row.get("batch_id")), str(row.get("method"))) for row in source_rows | |
| } | |
| for identity, selected_seed in sorted(selections.items()): | |
| if identity not in available_methods: | |
| alias_method = { | |
| "ch3_reinforce_adamw_seeded": "ch3_reinforce_adamw_seeded_selected" | |
| }.get(identity[1]) | |
| alias_identity = (identity[0], alias_method) if alias_method else None | |
| if alias_identity in available_methods: | |
| selected_trace_status.append( | |
| { | |
| "batch_id": identity[0], | |
| "method": identity[1], | |
| "selected_policy_method": alias_method, | |
| "selected_seed": selected_seed, | |
| "status": "represented_by_explicit_selected_policy_method", | |
| } | |
| ) | |
| continue | |
| selected_trace_status.append( | |
| { | |
| "batch_id": identity[0], | |
| "method": identity[1], | |
| "selected_seed": selected_seed, | |
| "status": "missing", | |
| "reason": "selection method has no fixed-start episodes under the same method name", | |
| } | |
| ) | |
| continue | |
| selected_source = [ | |
| row | |
| for row in source_rows | |
| if (row.get("batch_id"), row.get("method")) == identity | |
| and parse_optional_int(row.get("seed", ""), f"{key}.selected.seed") | |
| == selected_seed | |
| ] | |
| if not selected_source: | |
| raise ValidationError( | |
| f"{key}: selection {identity} names seed {selected_seed}, but that seed has no " | |
| "fixed-grid episode rows" | |
| ) | |
| selected_by_start: dict[float, list[Mapping[str, str]]] = defaultdict(list) | |
| for row in selected_source: | |
| selected_by_start[ | |
| parse_finite(row["start_position"], f"{key}.selected.start_position") | |
| ].append(row) | |
| expected_starts = { | |
| start | |
| for batch_id, method, start in groups | |
| if (batch_id, method) == identity | |
| } | |
| if set(selected_by_start) != expected_starts: | |
| raise ValidationError( | |
| f"{key}: selected seed {selected_seed} does not cover the method's fixed-start grid" | |
| ) | |
| for start, rows in sorted(selected_by_start.items()): | |
| if len(rows) != 1: | |
| raise ValidationError( | |
| f"{key}: selected seed {selected_seed} has {len(rows)} rows at start {start}; " | |
| "expected exactly one" | |
| ) | |
| row = rows[0] | |
| value = parse_finite(row["return"], f"{key}.selected.return") | |
| derived.append( | |
| { | |
| "batch_id": identity[0], | |
| "method": identity[1], | |
| "performance_scope": "selected_policy_fixed_grid", | |
| "selected_seed": selected_seed, | |
| "start_position": start, | |
| "episode_count": 1, | |
| "mean_return": value, | |
| "min_return": value, | |
| "max_return": value, | |
| "claim_ids": [row["claim_id"]] if row.get("claim_id") else [], | |
| "panel_ids": [row["panel_id"]] if row.get("panel_id") else [], | |
| } | |
| ) | |
| selected_trace_status.append( | |
| { | |
| "batch_id": identity[0], | |
| "method": identity[1], | |
| "selected_seed": selected_seed, | |
| "status": "generated", | |
| "start_count": len(selected_by_start), | |
| } | |
| ) | |
| series_keys = sorted( | |
| { | |
| ( | |
| str(row["batch_id"]), | |
| str(row["method"]), | |
| str(row["performance_scope"]), | |
| row["selected_seed"], | |
| ) | |
| for row in derived | |
| }, | |
| key=lambda item: ( | |
| item[0], | |
| item[1], | |
| item[2], | |
| -1 if item[3] is None else int(item[3]), | |
| ), | |
| ) | |
| method_colors = { | |
| identity: COLORS[index % len(COLORS)] | |
| for index, identity in enumerate( | |
| sorted({(str(row["batch_id"]), str(row["method"])) for row in derived}) | |
| ) | |
| } | |
| base_labels: dict[tuple[str, str, str, Any], str] = {} | |
| for series_key in series_keys: | |
| batch_id, method, performance_scope, selected_seed = series_key | |
| series_rows = [ | |
| row | |
| for row in derived | |
| if ( | |
| row["batch_id"], | |
| row["method"], | |
| row["performance_scope"], | |
| row["selected_seed"], | |
| ) | |
| == series_key | |
| ] | |
| if performance_scope == "selected_policy_fixed_grid": | |
| base_labels[series_key] = ( | |
| f"{display_method(method)} · selected seed {selected_seed}" | |
| ) | |
| else: | |
| recorded_count = max(int(row["episode_count"]) for row in series_rows) | |
| base_labels[series_key] = ( | |
| f"{display_method(method)} · {recorded_count}-seed mean" | |
| if recorded_count > 1 | |
| else display_method(method) | |
| ) | |
| label_counts = Counter(base_labels.values()) | |
| series_labels = { | |
| series_key: (f"{label} · {series_key[0]}" if label_counts[label] > 1 else label) | |
| for series_key, label in base_labels.items() | |
| } | |
| fig = make_subplots( | |
| rows=2, | |
| cols=1, | |
| shared_xaxes=True, | |
| vertical_spacing=0.12, | |
| row_heights=[0.48, 0.52], | |
| ) | |
| for series_key in series_keys: | |
| batch_id, method, performance_scope, selected_seed = series_key | |
| rows = sorted( | |
| [ | |
| row | |
| for row in derived | |
| if ( | |
| row["batch_id"], | |
| row["method"], | |
| row["performance_scope"], | |
| row["selected_seed"], | |
| ) | |
| == series_key | |
| ], | |
| key=lambda row: float(row["start_position"]), | |
| ) | |
| color = method_colors[(batch_id, method)] | |
| is_selected = performance_scope == "selected_policy_fixed_grid" | |
| label = series_labels[series_key] | |
| x = [row["start_position"] for row in rows] | |
| custom = [ | |
| [ | |
| row["episode_count"], | |
| batch_id, | |
| performance_scope, | |
| selected_seed, | |
| row["min_return"], | |
| row["max_return"], | |
| ] | |
| for row in rows | |
| ] | |
| if not is_selected: | |
| fig.add_trace( | |
| go.Scatter( | |
| x=x, | |
| y=[row["max_return"] for row in rows], | |
| mode="lines", | |
| line={"width": 0, "color": color}, | |
| hoverinfo="skip", | |
| showlegend=False, | |
| legendgroup=label, | |
| ), | |
| row=1, | |
| col=1, | |
| ) | |
| fig.add_trace( | |
| go.Scatter( | |
| x=x, | |
| y=[row["min_return"] for row in rows], | |
| mode="lines", | |
| line={"width": 0, "color": color}, | |
| fill="tonexty", | |
| fillcolor=rgba(color, 0.13), | |
| hoverinfo="skip", | |
| showlegend=False, | |
| legendgroup=label, | |
| ), | |
| row=1, | |
| col=1, | |
| ) | |
| for row_index in (1, 2): | |
| fig.add_trace( | |
| go.Scatter( | |
| x=x, | |
| y=[row["mean_return"] for row in rows], | |
| mode="lines+markers", | |
| name=label, | |
| line={ | |
| "color": color, | |
| "width": 3.5 if is_selected else 2.2, | |
| "dash": "dash" if is_selected else "solid", | |
| }, | |
| marker={ | |
| "size": (7 if is_selected else 5) if row_index == 2 else 3, | |
| "symbol": "diamond" if is_selected else "circle", | |
| }, | |
| customdata=custom, | |
| hovertemplate=( | |
| "start=%{x:.5f}<br>mean return=%{y:.4f}" | |
| "<br>min–max=%{customdata[4]:.4f}–%{customdata[5]:.4f}" | |
| "<br>episodes=%{customdata[0]}<br>batch=%{customdata[1]}" | |
| "<br>scope=%{customdata[2]}<br>selected seed=%{customdata[3]}" | |
| "<extra>%{fullData.name}</extra>" | |
| ), | |
| legendgroup=label, | |
| showlegend=row_index == 2, | |
| ), | |
| row=row_index, | |
| col=1, | |
| ) | |
| mean_values = [float(row["mean_return"]) for row in derived] | |
| mean_span = max(mean_values) - min(mean_values) | |
| zoom_pad = max(0.25, 0.06 * mean_span) | |
| fig.update_xaxes(showgrid=True, row=1, col=1) | |
| fig.update_xaxes(title="Initial position", showgrid=True, row=2, col=1) | |
| fig.update_yaxes(title="Return · full range", showgrid=True, row=1, col=1) | |
| fig.update_yaxes( | |
| title="Mean return · zoom", | |
| showgrid=True, | |
| range=[min(mean_values) - zoom_pad, max(mean_values) + zoom_pad], | |
| row=2, | |
| col=1, | |
| ) | |
| style_figure( | |
| fig, "Mountain Car performance across deterministic starts", height=860 | |
| ) | |
| fig.update_layout( | |
| legend={ | |
| "orientation": "h", | |
| "yanchor": "top", | |
| "y": -0.10, | |
| "x": 0, | |
| "font": {"size": 12}, | |
| }, | |
| margin={"l": 80, "r": 35, "t": 120, "b": 145}, | |
| ) | |
| return generated_result( | |
| staging, | |
| key, | |
| fig, | |
| derived, | |
| ( | |
| "batch_id", | |
| "method", | |
| "performance_scope", | |
| "selected_seed", | |
| "start_position", | |
| "episode_count", | |
| "mean_return", | |
| "min_return", | |
| "max_return", | |
| "claim_ids", | |
| "panel_ids", | |
| ), | |
| { | |
| "aggregation": ( | |
| "all-recorded-policy mean/min/max at each batch-method-start, plus an exact " | |
| "selected-seed trace declared by selections.csv" | |
| ), | |
| "selected_policy_traces": selected_trace_status, | |
| "source_rows": len(source_rows), | |
| "excluded_random_start_rows": len(environment_rows) - len(source_rows), | |
| "exclusion_rule": "rows without start_position are not part of a return-vs-start curve", | |
| "series": len(series_keys), | |
| }, | |
| ) | |
| def build_seed_distribution_figure( | |
| staging: Path, tables: Mapping[str, Sequence[Mapping[str, str]]] | |
| ) -> FigureResult: | |
| key = "mountaincar_seed_distribution" | |
| source_rows = successful_environment_rows( | |
| tables["episodes.csv"], MOUNTAINCAR_ENVIRONMENT | |
| ) | |
| seeded = [row for row in source_rows if row.get("seed", "") != ""] | |
| if not seeded: | |
| return FigureResult( | |
| key, | |
| "missing", | |
| "No successful Mountain Car episode rows carry an explicit seed", | |
| ) | |
| selections = selected_seed_index( | |
| tables["selections.csv"], key, claim_ids={"claim3", "claim4"} | |
| ) | |
| groups: dict[tuple[str, str, str, str, int], list[float]] = defaultdict(list) | |
| for row in seeded: | |
| seed = parse_optional_int(row.get("seed", ""), f"{key}.seed") | |
| assert seed is not None | |
| group = ( | |
| require_nonempty(row.get("batch_id"), f"{key}.batch_id"), | |
| row.get("claim_id", ""), | |
| row.get("panel_id", ""), | |
| require_nonempty(row.get("method"), f"{key}.method"), | |
| seed, | |
| ) | |
| groups[group].append(parse_finite(row.get("return", ""), f"{key}.return")) | |
| derived = [ | |
| { | |
| "batch_id": group[0], | |
| "claim_id": group[1], | |
| "panel_id": group[2], | |
| "method": group[3], | |
| "seed": group[4], | |
| "episode_count": len(values), | |
| "mean_return": statistics.fmean(values), | |
| "std_population": statistics.pstdev(values), | |
| "min_return": min(values), | |
| "max_return": max(values), | |
| } | |
| for group, values in sorted(groups.items()) | |
| ] | |
| for row in derived: | |
| row["selected"] = ( | |
| selections.get((str(row["batch_id"]), str(row["method"]))) == row["seed"] | |
| ) | |
| fig = go.Figure() | |
| series_keys = sorted( | |
| { | |
| (row["batch_id"], row["claim_id"], row["panel_id"], row["method"]) | |
| for row in derived | |
| } | |
| ) | |
| method_counts = Counter(display_method(series_key[3]) for series_key in series_keys) | |
| selected_legend_added = False | |
| for index, series_key in enumerate(series_keys): | |
| rows = [ | |
| row | |
| for row in derived | |
| if (row["batch_id"], row["claim_id"], row["panel_id"], row["method"]) | |
| == series_key | |
| ] | |
| batch_id, claim_id, panel_id, method = series_key | |
| method_label = display_method(method) | |
| panel_context = panel_id or claim_id | |
| if "selection" in panel_context or "random" in panel_context: | |
| context_label = "selection panel" | |
| elif "primary" in panel_context or "fixed" in panel_context: | |
| context_label = "fixed grid" | |
| else: | |
| context_label = claim_id or panel_context | |
| label = ( | |
| f"{method_label} · {context_label}" | |
| if method_counts[method_label] > 1 | |
| else method_label | |
| ) | |
| color = COLORS[index % len(COLORS)] | |
| fig.add_trace( | |
| go.Violin( | |
| x=[label] * len(rows), | |
| y=[row["mean_return"] for row in rows], | |
| name=label, | |
| customdata=[[row["seed"], row["episode_count"]] for row in rows], | |
| box_visible=True, | |
| meanline_visible=True, | |
| points="all", | |
| jitter=0.18, | |
| pointpos=0, | |
| marker={"color": color, "size": 7}, | |
| line={"color": color}, | |
| showlegend=False, | |
| hovertemplate=( | |
| "seed=%{customdata[0]}<br>grid mean=%{y:.4f}" | |
| "<br>episodes=%{customdata[1]}<br>batch=" | |
| + batch_id | |
| + "<br>panel=" | |
| + panel_context | |
| + "<extra>%{fullData.name}</extra>" | |
| ), | |
| ) | |
| ) | |
| selected_rows = [row for row in rows if row["selected"]] | |
| if selected_rows: | |
| if len(selected_rows) != 1: | |
| raise ValidationError( | |
| f"{key}: {series_key} has {len(selected_rows)} selected seed rows" | |
| ) | |
| selected_row = selected_rows[0] | |
| fig.add_trace( | |
| go.Scatter( | |
| x=[label], | |
| y=[selected_row["mean_return"]], | |
| mode="markers", | |
| name="Selected seed", | |
| marker={ | |
| "size": 15, | |
| "symbol": "diamond", | |
| "color": "#E69F00", | |
| "line": {"color": "#000000", "width": 1.5}, | |
| }, | |
| customdata=[[selected_row["seed"], selected_row["episode_count"]]], | |
| hovertemplate=( | |
| "selected seed=%{customdata[0]}<br>grid mean=%{y:.4f}" | |
| "<br>episodes=%{customdata[1]}<extra>%{fullData.name}</extra>" | |
| ), | |
| showlegend=not selected_legend_added, | |
| ) | |
| ) | |
| selected_legend_added = True | |
| fig.update_xaxes(title="Method / evaluation panel", tickangle=0) | |
| fig.update_yaxes(title="Per-seed mean episode return") | |
| style_figure( | |
| fig, "Mountain Car robustness across released-protocol seeds", height=700 | |
| ) | |
| return generated_result( | |
| staging, | |
| key, | |
| fig, | |
| derived, | |
| ( | |
| "batch_id", | |
| "claim_id", | |
| "panel_id", | |
| "method", | |
| "seed", | |
| "episode_count", | |
| "mean_return", | |
| "std_population", | |
| "min_return", | |
| "max_return", | |
| "selected", | |
| ), | |
| { | |
| "experimental_unit": "training seed", | |
| "seed_statistic": "mean return over that seed's recorded evaluation episodes", | |
| "source_rows": len(seeded), | |
| "seed_rows": len(derived), | |
| "selected_seed_rows": sum(bool(row["selected"]) for row in derived), | |
| }, | |
| ) | |
| def build_parameter_performance_figure( | |
| staging: Path, tables: Mapping[str, Sequence[Mapping[str, str]]] | |
| ) -> FigureResult: | |
| key = "parameter_performance_comparison" | |
| all_episodes = successful_environment_rows( | |
| tables["episodes.csv"], MOUNTAINCAR_ENVIRONMENT | |
| ) | |
| episodes = [row for row in all_episodes if row.get("start_position", "") != ""] | |
| parameters = [ | |
| row for row in tables["parameter_audit.csv"] if row.get("status") == "success" | |
| ] | |
| if not episodes: | |
| return FigureResult( | |
| key, "missing", "No successful Mountain Car performance rows" | |
| ) | |
| if not parameters: | |
| return FigureResult(key, "missing", "No successful parameter-audit rows") | |
| selections = selected_seed_index( | |
| tables["selections.csv"], key, claim_ids={"claim3"} | |
| ) | |
| performance_rows: dict[tuple[str, str], list[Mapping[str, str]]] = defaultdict(list) | |
| for row in episodes: | |
| identity = ( | |
| require_nonempty(row.get("batch_id"), f"{key}.batch_id"), | |
| require_nonempty(row.get("method"), f"{key}.method"), | |
| ) | |
| parse_finite(row.get("return", ""), f"{key}.return") | |
| performance_rows[identity].append(row) | |
| def scoped_performance( | |
| identity: tuple[str, str], | |
| ) -> tuple[list[float], set[str], str, int | None]: | |
| rows = performance_rows[identity] | |
| selected_seed = selections.get(identity) | |
| if selected_seed is not None: | |
| rows = [ | |
| row | |
| for row in rows | |
| if parse_optional_int(row.get("seed", ""), f"{key}.selected_seed") | |
| == selected_seed | |
| ] | |
| if not rows: | |
| raise ValidationError( | |
| f"{key}: selection {identity} names seed {selected_seed}, but no matching " | |
| "fixed-grid episodes exist" | |
| ) | |
| starts = [ | |
| parse_finite(row["start_position"], f"{key}.selected.start_position") | |
| for row in rows | |
| ] | |
| if len(starts) != len(set(starts)): | |
| raise ValidationError( | |
| f"{key}: selected policy {identity}, seed {selected_seed} has duplicate " | |
| "fixed-grid start positions" | |
| ) | |
| performance_scope = "selected_policy_fixed_grid" | |
| else: | |
| performance_scope = "method_fixed_grid" | |
| values = [parse_finite(row["return"], f"{key}.return") for row in rows] | |
| claim_ids = {row["claim_id"] for row in rows if row.get("claim_id")} | |
| return values, claim_ids, performance_scope, selected_seed | |
| derived: list[dict[str, Any]] = [] | |
| for row in parameters: | |
| batch_id = require_nonempty(row.get("batch_id"), f"{key}.parameter.batch_id") | |
| parameter_method = require_nonempty( | |
| row.get("method"), f"{key}.parameter.method" | |
| ) | |
| if row.get("chebyshev_parameter_count", "") != "": | |
| # The raw arithmetic audit is intentionally wide because it keeps | |
| # conflicting paper and implementation counts separate. Only the | |
| # Chebyshev count has an exact matching evaluated policy. | |
| identity = (batch_id, "ch3_ars") | |
| if identity in performance_rows: | |
| count = parse_finite( | |
| row["chebyshev_parameter_count"], | |
| f"{key}.chebyshev_parameter_count", | |
| ) | |
| if not count.is_integer() or count <= 0: | |
| raise ValidationError( | |
| f"{key}: chebyshev_parameter_count must be a positive integer" | |
| ) | |
| values, claim_ids, performance_scope, selected_seed = ( | |
| scoped_performance(identity) | |
| ) | |
| derived.append( | |
| { | |
| "batch_id": batch_id, | |
| "method": "ch3_ars", | |
| "parameter_method": parameter_method, | |
| "count_source": "degree_3_bivariate_chebyshev_formula", | |
| "parameter_count": int(count), | |
| "mean_return": statistics.fmean(values), | |
| "episode_count": len(values), | |
| "performance_scope": performance_scope, | |
| "selected_seed": selected_seed, | |
| "claim_ids": sorted(claim_ids), | |
| } | |
| ) | |
| if row.get("parameter_count", "") == "": | |
| continue | |
| performance_method = row.get("performance_method") or parameter_method | |
| identity = (batch_id, performance_method) | |
| if identity not in performance_rows: | |
| continue | |
| count = parse_finite(row["parameter_count"], f"{key}.parameter_count") | |
| if not count.is_integer() or count <= 0: | |
| raise ValidationError(f"{key}: parameter_count must be a positive integer") | |
| values, claim_ids, performance_scope, selected_seed = scoped_performance( | |
| identity | |
| ) | |
| derived.append( | |
| { | |
| "batch_id": batch_id, | |
| "method": performance_method, | |
| "parameter_method": parameter_method, | |
| "count_source": require_nonempty( | |
| row.get("count_source"), f"{key}.count_source" | |
| ), | |
| "parameter_count": int(count), | |
| "mean_return": statistics.fmean(values), | |
| "episode_count": len(values), | |
| "performance_scope": performance_scope, | |
| "selected_seed": selected_seed, | |
| "claim_ids": sorted(claim_ids), | |
| } | |
| ) | |
| derived.sort( | |
| key=lambda row: ( | |
| row["batch_id"], | |
| row["method"], | |
| row["parameter_count"], | |
| row["count_source"], | |
| ) | |
| ) | |
| if not derived: | |
| return FigureResult( | |
| key, | |
| "missing", | |
| "Parameter methods do not match any Mountain Car performance method in the same batch", | |
| ) | |
| fig = go.Figure() | |
| method_colors = { | |
| method: COLORS[index % len(COLORS)] | |
| for index, method in enumerate(sorted({str(row["method"]) for row in derived})) | |
| } | |
| for row in derived: | |
| method_label = display_method(str(row["method"])) | |
| label = f"{method_label} · {row['count_source']}" | |
| fig.add_trace( | |
| go.Scatter( | |
| x=[row["parameter_count"]], | |
| y=[row["mean_return"]], | |
| mode="markers+text", | |
| text=[f"{method_label}<br>{int(row['parameter_count']):,} parameters"], | |
| textposition="top center", | |
| name=label, | |
| marker={ | |
| "size": 14, | |
| "color": method_colors[str(row["method"])], | |
| "line": {"color": "white", "width": 1.5}, | |
| }, | |
| customdata=[ | |
| [ | |
| row["count_source"], | |
| row["episode_count"], | |
| row["batch_id"], | |
| row["performance_scope"], | |
| row["selected_seed"], | |
| ] | |
| ], | |
| hovertemplate=( | |
| "parameters=%{x:,}<br>mean return=%{y:.4f}" | |
| "<br>source=%{customdata[0]}<br>episodes=%{customdata[1]}" | |
| "<br>batch=%{customdata[2]}<br>scope=%{customdata[3]}" | |
| "<br>selected seed=%{customdata[4]}<extra>%{fullData.name}</extra>" | |
| ), | |
| showlegend=False, | |
| ) | |
| ) | |
| fig.update_xaxes(title="Trainable scalar count (log scale)", type="log") | |
| performance_values = [float(row["mean_return"]) for row in derived] | |
| performance_pad = max( | |
| 0.5, 0.08 * (max(performance_values) - min(performance_values)) | |
| ) | |
| fig.update_yaxes( | |
| title="Mean Mountain Car return", | |
| range=[ | |
| min(performance_values) - performance_pad, | |
| max(performance_values) + performance_pad, | |
| ], | |
| ) | |
| style_figure(fig, "Parameter efficiency versus reproduced Mountain Car performance") | |
| fig.update_layout(margin={"l": 75, "r": 35, "t": 100, "b": 70}) | |
| return generated_result( | |
| staging, | |
| key, | |
| fig, | |
| derived, | |
| ( | |
| "batch_id", | |
| "method", | |
| "parameter_method", | |
| "count_source", | |
| "parameter_count", | |
| "mean_return", | |
| "episode_count", | |
| "performance_scope", | |
| "selected_seed", | |
| "claim_ids", | |
| ), | |
| { | |
| "join": "exact batch_id + (performance_method if declared, otherwise method)", | |
| "performance_statistic": ( | |
| "if selections.csv declares a selected seed, mean only that seed's fixed-grid " | |
| "episodes; otherwise mean the method's fixed-grid episodes" | |
| ), | |
| "selection_precedence": ( | |
| "exact batch_id + method selection is authoritative for the headline Pareto " | |
| "point; all-seed robustness remains in mountaincar_seed_distribution" | |
| ), | |
| "performance_filter": "fixed-start rows only; random-start selection episodes are excluded", | |
| "excluded_random_start_rows": len(all_episodes) - len(episodes), | |
| "points": len(derived), | |
| }, | |
| ) | |
| def build_pendulum_figure( | |
| staging: Path, tables: Mapping[str, Sequence[Mapping[str, str]]] | |
| ) -> FigureResult: | |
| key = "pendulum_heatmap_difference" | |
| source_rows = successful_environment_rows( | |
| tables["episodes.csv"], PENDULUM_ENVIRONMENT | |
| ) | |
| if not source_rows: | |
| return FigureResult( | |
| key, "missing", f"No successful {PENDULUM_ENVIRONMENT} episode rows" | |
| ) | |
| unit_rows: dict[tuple[str, str, str, str, int | None], list[Mapping[str, str]]] = ( | |
| defaultdict(list) | |
| ) | |
| for row in source_rows: | |
| identity = ( | |
| require_nonempty(row.get("batch_id"), f"{key}.batch_id"), | |
| row.get("claim_id", ""), | |
| row.get("panel_id", ""), | |
| require_nonempty(row.get("method"), f"{key}.method"), | |
| parse_optional_int(row.get("seed", ""), f"{key}.seed"), | |
| ) | |
| parse_finite(row.get("initial_angle", ""), f"{key}.initial_angle") | |
| parse_finite( | |
| row.get("initial_angular_velocity", ""), | |
| f"{key}.initial_angular_velocity", | |
| ) | |
| parse_finite(row.get("return", ""), f"{key}.return") | |
| unit_rows[identity].append(row) | |
| selection_rows = [ | |
| row | |
| for row in tables["selections.csv"] | |
| if row.get("status") == "success" | |
| and row.get("claim_id") == "claim5" | |
| and row.get("method") == "ch6_ars_pendulum" | |
| ] | |
| if len(selection_rows) != 1: | |
| raise ValidationError( | |
| f"{key}: expected one authoritative Claim 5 selection, found " | |
| f"{len(selection_rows)}" | |
| ) | |
| selection = selection_rows[0] | |
| batch_id = require_nonempty(selection.get("batch_id"), f"{key}.selection.batch_id") | |
| declared_seed = parse_optional_int( | |
| selection.get("selected_seed", ""), f"{key}.selection.selected_seed" | |
| ) | |
| candidate_count = parse_optional_int( | |
| selection.get("candidate_count", ""), f"{key}.selection.candidate_count" | |
| ) | |
| if declared_seed is None or candidate_count is None or candidate_count <= 0: | |
| raise ValidationError( | |
| f"{key}: selection seed/count must be present and positive" | |
| ) | |
| if selection.get("selection_and_reporting_grid_reused") != "true": | |
| raise ValidationError( | |
| f"{key}: selection must declare reuse of the fixed reporting grid" | |
| ) | |
| candidate_identities = sorted( | |
| identity | |
| for identity in unit_rows | |
| if identity[0] == batch_id | |
| and identity[1] == "claim5" | |
| and identity[2] == "pendulum_ch6_ars_seed_grid_v1" | |
| and identity[3] == "ch6_ars_pendulum" | |
| and identity[4] is not None | |
| ) | |
| candidate_seeds = [int(identity[4]) for identity in candidate_identities] | |
| if len(candidate_identities) != candidate_count: | |
| raise ValidationError( | |
| f"{key}: selection declares {candidate_count} candidates, found " | |
| f"{len(candidate_identities)} unique candidate grids" | |
| ) | |
| if len(candidate_seeds) != len(set(candidate_seeds)): | |
| raise ValidationError(f"{key}: duplicate candidate seed surfaces") | |
| selected_identity = ( | |
| batch_id, | |
| "claim5", | |
| "pendulum_primary_comparison_v1", | |
| "ch6_ars_pendulum_selected", | |
| declared_seed, | |
| ) | |
| author_identity = ( | |
| batch_id, | |
| "claim5", | |
| "pendulum_primary_comparison_v1", | |
| "ars_baseline_pendulum_released", | |
| 0, | |
| ) | |
| corrected_identity = ( | |
| batch_id, | |
| "claim5", | |
| "pendulum_baseline_reset_sensitivity_v1", | |
| "ars_baseline_pendulum_released_corrected_reset", | |
| 0, | |
| ) | |
| expected_identities = { | |
| *candidate_identities, | |
| selected_identity, | |
| author_identity, | |
| corrected_identity, | |
| } | |
| def materialize_surface( | |
| identity: tuple[str, str, str, str, int | None], | |
| *, | |
| expected_protocol: str | None = None, | |
| ) -> tuple[dict[tuple[float, float], float], str, tuple[int, int]]: | |
| rows = unit_rows.get(identity) | |
| if not rows: | |
| raise ValidationError( | |
| f"{key}: missing required Pendulum surface {identity}" | |
| ) | |
| values: dict[tuple[float, float], float] = {} | |
| for row in rows: | |
| angle = parse_finite(row["initial_angle"], f"{key}.initial_angle") | |
| velocity = parse_finite( | |
| row["initial_angular_velocity"], f"{key}.initial_angular_velocity" | |
| ) | |
| coordinate = (angle, velocity) | |
| if coordinate in values: | |
| raise ValidationError( | |
| f"{key}: duplicate Pendulum coordinate for surface {identity}" | |
| ) | |
| values[coordinate] = parse_finite(row["return"], f"{key}.return") | |
| protocols = {row.get("evaluation_protocol", "") for row in rows} | |
| if len(protocols) != 1: | |
| raise ValidationError( | |
| f"{key}: mixed protocols in surface {identity}: {protocols}" | |
| ) | |
| protocol = next(iter(protocols)) | |
| if expected_protocol is not None and protocol != expected_protocol: | |
| raise ValidationError( | |
| f"{key}: protocol mismatch for {identity}: {protocol!r} != " | |
| f"{expected_protocol!r}" | |
| ) | |
| angles = sorted({coordinate[0] for coordinate in values}) | |
| velocities = sorted({coordinate[1] for coordinate in values}) | |
| expected = {(angle, velocity) for angle in angles for velocity in velocities} | |
| if set(values) != expected: | |
| missing = sorted(expected - set(values)) | |
| raise ValidationError( | |
| f"{key}: incomplete rectangular grid for {identity}; " | |
| f"missing {len(missing)} coordinates" | |
| ) | |
| return values, protocol, (len(angles), len(velocities)) | |
| candidate_surfaces: dict[int, dict[tuple[float, float], float]] = {} | |
| candidate_shapes: set[tuple[int, int]] = set() | |
| for identity in candidate_identities: | |
| seed = int(identity[4]) | |
| values, _, shape = materialize_surface(identity) | |
| candidate_surfaces[seed] = values | |
| candidate_shapes.add(shape) | |
| if len(candidate_shapes) != 1: | |
| raise ValidationError( | |
| f"{key}: candidate grid shapes disagree: {candidate_shapes}" | |
| ) | |
| recomputed_seed = max( | |
| candidate_seeds, | |
| key=lambda seed: (statistics.fmean(candidate_surfaces[seed].values()), -seed), | |
| ) | |
| if recomputed_seed != declared_seed: | |
| raise ValidationError( | |
| f"{key}: declared selected seed {declared_seed} disagrees with complete-grid " | |
| f"argmax {recomputed_seed}" | |
| ) | |
| unexpected_identities = sorted(set(unit_rows) - expected_identities, key=repr) | |
| if unexpected_identities: | |
| raise ValidationError( | |
| f"{key}: unexpected Pendulum method/panel/seed surfaces: " | |
| f"{unexpected_identities}" | |
| ) | |
| selected_values, selected_protocol, selected_shape = materialize_surface( | |
| selected_identity, | |
| expected_protocol="author_exact_polynomial_helper", | |
| ) | |
| author_values, author_protocol, author_shape = materialize_surface( | |
| author_identity, | |
| expected_protocol="author_exact_first_observation_bypasses_wrappers", | |
| ) | |
| corrected_values, corrected_protocol, corrected_shape = materialize_surface( | |
| corrected_identity, | |
| expected_protocol="corrected_first_observation_through_vecnormalize", | |
| ) | |
| if candidate_surfaces[declared_seed] != selected_values: | |
| raise ValidationError( | |
| f"{key}: explicit selected-policy grid differs from candidate seed " | |
| f"{declared_seed}" | |
| ) | |
| coordinate_set = set(selected_values) | |
| if set(author_values) != coordinate_set or set(corrected_values) != coordinate_set: | |
| raise ValidationError( | |
| f"{key}: selected, author, and corrected grids do not align" | |
| ) | |
| if len({selected_shape, author_shape, corrected_shape, *candidate_shapes}) != 1: | |
| raise ValidationError(f"{key}: displayed and candidate grid shapes disagree") | |
| selected_mean = statistics.fmean(selected_values.values()) | |
| author_mean = statistics.fmean(author_values.values()) | |
| corrected_mean = statistics.fmean(corrected_values.values()) | |
| corrected_delta = corrected_mean - author_mean | |
| performance_difference = { | |
| coordinate: selected_values[coordinate] - author_values[coordinate] | |
| for coordinate in coordinate_set | |
| } | |
| surface_rows: list[dict[str, Any]] = [] | |
| raw_surfaces = ( | |
| (selected_identity, selected_values, selected_protocol), | |
| (author_identity, author_values, author_protocol), | |
| (corrected_identity, corrected_values, corrected_protocol), | |
| ) | |
| for identity, values, protocol in raw_surfaces: | |
| for (angle, velocity), value in sorted(values.items()): | |
| surface_rows.append( | |
| { | |
| "panel_kind": "surface", | |
| "batch_id": identity[0], | |
| "claim_id": identity[1], | |
| "panel_id": identity[2], | |
| "method": identity[3], | |
| "comparison": "", | |
| "selected_seed": identity[4], | |
| "evaluation_protocol": protocol, | |
| "left_method": "", | |
| "left_seed": "", | |
| "left_protocol": "", | |
| "left_value": "", | |
| "right_method": "", | |
| "right_seed": "", | |
| "right_protocol": "", | |
| "right_value": "", | |
| "initial_angle": angle, | |
| "initial_angular_velocity": velocity, | |
| "metric": "episode_return", | |
| "value": value, | |
| } | |
| ) | |
| difference_label = "CH-6-ARS − ARS · author-exact" | |
| for (angle, velocity), value in sorted(performance_difference.items()): | |
| surface_rows.append( | |
| { | |
| "panel_kind": "difference", | |
| "batch_id": batch_id, | |
| "claim_id": "claim5", | |
| "panel_id": "pendulum_primary_comparison_v1", | |
| "method": "", | |
| "comparison": difference_label, | |
| "selected_seed": declared_seed, | |
| "evaluation_protocol": "paired_coordinate_difference", | |
| "left_method": selected_identity[3], | |
| "left_seed": declared_seed, | |
| "left_protocol": selected_protocol, | |
| "left_value": selected_values[(angle, velocity)], | |
| "right_method": author_identity[3], | |
| "right_seed": 0, | |
| "right_protocol": author_protocol, | |
| "right_value": author_values[(angle, velocity)], | |
| "initial_angle": angle, | |
| "initial_angular_velocity": velocity, | |
| "metric": "return_difference", | |
| "value": value, | |
| } | |
| ) | |
| performance_delta = statistics.fmean(performance_difference.values()) | |
| panels: tuple[tuple[str, dict[tuple[float, float], float], bool], ...] = ( | |
| ( | |
| f"CH-6-ARS · selected seed {declared_seed}" | |
| f"<br><sup>mean {selected_mean:.2f}</sup>", | |
| selected_values, | |
| False, | |
| ), | |
| ( | |
| f"ARS · author-exact reset<br><sup>mean {author_mean:.2f}</sup>", | |
| author_values, | |
| False, | |
| ), | |
| ( | |
| f"{difference_label}<br><sup>mean Δ {performance_delta:+.2f}</sup>", | |
| performance_difference, | |
| True, | |
| ), | |
| ( | |
| f"ARS · corrected reset<br><sup>mean {corrected_mean:.2f}; " | |
| f"Δ vs exact {corrected_delta:+.2f}</sup>", | |
| corrected_values, | |
| False, | |
| ), | |
| ) | |
| fig = make_subplots( | |
| rows=2, | |
| cols=2, | |
| subplot_titles=[panel[0] for panel in panels], | |
| horizontal_spacing=0.14, | |
| vertical_spacing=0.16, | |
| ) | |
| surface_values = [ | |
| value | |
| for _, values, is_difference in panels | |
| if not is_difference | |
| for value in values.values() | |
| ] | |
| difference_values = [ | |
| value | |
| for _, values, is_difference in panels | |
| if is_difference | |
| for value in values.values() | |
| ] | |
| surface_min, surface_max = min(surface_values), max(surface_values) | |
| if surface_min == surface_max: | |
| surface_min -= 1e-12 | |
| surface_max += 1e-12 | |
| surface_bounds = (surface_min, surface_max) | |
| difference_extent = max( | |
| max((abs(value) for value in difference_values), default=0.0), | |
| 1e-12, | |
| ) | |
| for index, (label, values, is_difference) in enumerate(panels): | |
| row = index // 2 + 1 | |
| column = index % 2 + 1 | |
| angles = sorted({coordinate[0] for coordinate in values}) | |
| velocities = sorted({coordinate[1] for coordinate in values}) | |
| z = [[values[(angle, velocity)] for angle in angles] for velocity in velocities] | |
| protocol = ( | |
| "paired coordinate difference" | |
| if is_difference | |
| else { | |
| 0: selected_protocol, | |
| 1: author_protocol, | |
| 3: corrected_protocol, | |
| }[index] | |
| ) | |
| customdata = [[[protocol] for _ in angles] for _ in velocities] | |
| trace = go.Heatmap( | |
| x=angles, | |
| y=velocities, | |
| z=z, | |
| coloraxis="coloraxis2" if is_difference else "coloraxis", | |
| customdata=customdata, | |
| hovertemplate=( | |
| "angle=%{x:.4f}<br>angular velocity=%{y:.4f}<br>" | |
| + ("return difference=%{z:.4f}" if is_difference else "return=%{z:.4f}") | |
| + "<br>protocol=%{customdata[0]}" | |
| + f"<extra>{label}</extra>" | |
| ), | |
| ) | |
| fig.add_trace(trace, row=row, col=column) | |
| fig.update_xaxes(title="Initial angle", row=row, col=column) | |
| fig.update_yaxes(title="Initial angular velocity", row=row, col=column) | |
| style_figure( | |
| fig, | |
| "Pendulum initial-state returns, improvement, and reset sensitivity", | |
| 920, | |
| ) | |
| fig.update_layout( | |
| coloraxis={ | |
| "colorscale": "Viridis", | |
| "cmin": surface_bounds[0], | |
| "cmax": surface_bounds[1], | |
| "colorbar": { | |
| "title": "return", | |
| "x": 1.02, | |
| "y": 0.76, | |
| "len": 0.34, | |
| }, | |
| }, | |
| coloraxis2={ | |
| "colorscale": "RdBu", | |
| "cmin": -difference_extent, | |
| "cmid": 0, | |
| "cmax": difference_extent, | |
| "colorbar": { | |
| "title": "Δ return", | |
| "x": 1.02, | |
| "y": 0.24, | |
| "len": 0.34, | |
| }, | |
| }, | |
| margin={"l": 80, "r": 110, "t": 155, "b": 75}, | |
| ) | |
| details = { | |
| "surface_selection": ( | |
| "authoritative selections.csv record; complete-grid argmax " | |
| "recomputed only as an integrity check" | |
| ), | |
| "declared_selected_seed": declared_seed, | |
| "recomputed_selected_seed": recomputed_seed, | |
| "candidate_count": candidate_count, | |
| "candidate_seeds": candidate_seeds, | |
| "selection_matches_recomputed_argmax": True, | |
| "selected_copy_matches_candidate_grid": True, | |
| "selection_and_reporting_grid_reused": True, | |
| "grid_shape": list(selected_shape), | |
| "selected_ch6_mean": selected_mean, | |
| "author_exact_ars_mean": author_mean, | |
| "ch6_minus_author_exact_ars_mean": performance_delta, | |
| "corrected_reset_ars_mean": corrected_mean, | |
| "corrected_minus_author_exact_mean": corrected_delta, | |
| "protocols": { | |
| "selected_ch6": selected_protocol, | |
| "author_exact_ars": author_protocol, | |
| "corrected_reset_ars": corrected_protocol, | |
| }, | |
| "surface_count": 3, | |
| "difference_count": 1, | |
| "displayed_panels": [panel[0] for panel in panels], | |
| "candidate_surfaces_excluded_from_render": candidate_count, | |
| "candidate_surface_reason": ( | |
| "all candidates remain in canonical tables; the selected candidate is " | |
| "identical to the explicitly named selected-policy grid" | |
| ), | |
| "source_rows": len(source_rows), | |
| } | |
| surface_rows.sort( | |
| key=lambda row: ( | |
| row["panel_kind"], | |
| row["batch_id"], | |
| row["claim_id"], | |
| row["panel_id"], | |
| row["method"], | |
| row["comparison"], | |
| float(row["initial_angle"]), | |
| float(row["initial_angular_velocity"]), | |
| ) | |
| ) | |
| return generated_result( | |
| staging, | |
| key, | |
| fig, | |
| surface_rows, | |
| ( | |
| "panel_kind", | |
| "batch_id", | |
| "claim_id", | |
| "panel_id", | |
| "method", | |
| "comparison", | |
| "selected_seed", | |
| "evaluation_protocol", | |
| "left_method", | |
| "left_seed", | |
| "left_protocol", | |
| "left_value", | |
| "right_method", | |
| "right_seed", | |
| "right_protocol", | |
| "right_value", | |
| "initial_angle", | |
| "initial_angular_velocity", | |
| "metric", | |
| "value", | |
| ), | |
| details, | |
| ) | |
| def publish_directory(staging: Path, destination: Path, replace: bool) -> None: | |
| if destination.is_symlink(): | |
| raise ValidationError(f"Refusing to publish through a symlink: {destination}") | |
| destination = destination.resolve() | |
| if destination.exists() and not destination.is_dir(): | |
| raise ValidationError( | |
| f"Output path exists and is not a directory: {destination}" | |
| ) | |
| if destination.exists() and not replace: | |
| raise ValidationError( | |
| f"Output directory already exists: {destination}; use --replace for derived outputs" | |
| ) | |
| destination.parent.mkdir(parents=True, exist_ok=True) | |
| if not destination.exists(): | |
| os.replace(staging, destination) | |
| return | |
| backup = destination.parent / f".{destination.name}.old-{os.getpid()}" | |
| if backup.exists(): | |
| raise ValidationError(f"Refusing to overwrite stale backup directory: {backup}") | |
| os.replace(destination, backup) | |
| try: | |
| os.replace(staging, destination) | |
| except BaseException: | |
| os.replace(backup, destination) | |
| raise | |
| shutil.rmtree(backup) | |
| def result_manifest_entry(staging: Path, result: FigureResult) -> dict[str, Any]: | |
| entry: dict[str, Any] = { | |
| "status": result.status, | |
| "reason": result.reason, | |
| "details": dict(result.details or {}), | |
| } | |
| if result.status == "generated": | |
| assert result.html_path is not None and result.data_path is not None | |
| html_path = staging / result.html_path | |
| data_path = staging / result.data_path | |
| entry.update( | |
| { | |
| "html": { | |
| "path": result.html_path, | |
| "sha256": sha256_file(html_path), | |
| "bytes": html_path.stat().st_size, | |
| "plotly_javascript": "embedded", | |
| }, | |
| "data": { | |
| "path": result.data_path, | |
| "sha256": sha256_file(data_path), | |
| "bytes": data_path.stat().st_size, | |
| "rows": result.row_count, | |
| }, | |
| } | |
| ) | |
| return entry | |
| def make_figures(tables_root: Path, output_root: Path, replace: bool) -> dict[str, Any]: | |
| table_manifest, tables = read_verified_tables(tables_root) | |
| tables_resolved = tables_root.resolve() | |
| output_resolved = output_root.resolve() | |
| if output_resolved == tables_resolved or tables_resolved in output_resolved.parents: | |
| raise ValidationError( | |
| "Figure output must not overwrite or be nested in canonical tables" | |
| ) | |
| output_root.parent.mkdir(parents=True, exist_ok=True) | |
| staging = Path( | |
| tempfile.mkdtemp( | |
| prefix=f".{output_root.name}.tmp-", dir=output_root.parent.resolve() | |
| ) | |
| ) | |
| builders: tuple[ | |
| Callable[[Path, Mapping[str, Sequence[Mapping[str, str]]]], FigureResult], ... | |
| ] = ( | |
| build_mountaincar_return_figure, | |
| build_seed_distribution_figure, | |
| build_parameter_performance_figure, | |
| build_pendulum_figure, | |
| ) | |
| try: | |
| results = [builder(staging, tables) for builder in builders] | |
| manifest = { | |
| "schema_version": SUPPORTED_TABLE_SCHEMA, | |
| "builder": "make_figures.py", | |
| "builder_version": FIGURE_BUILDER_VERSION, | |
| "builder_sha256": sha256_file(Path(__file__).resolve()), | |
| "command": build_command(tables_root, output_root, replace), | |
| "status": "success", | |
| "experiment_id": table_manifest["experiment_id"], | |
| "spec_version": table_manifest["spec_version"], | |
| "paper_version": table_manifest["paper_version"], | |
| "openreview_id": table_manifest["openreview_id"], | |
| "source": { | |
| "tables_manifest_sha256": sha256_file( | |
| tables_root.resolve() / "MANIFEST.json" | |
| ), | |
| "table_hashes": { | |
| name: table_manifest["tables"][name]["sha256"] | |
| for name in REQUIRED_TABLES | |
| }, | |
| }, | |
| "availability_contract": ( | |
| "A missing figure is recorded explicitly and emits no placeholder HTML or data CSV. " | |
| "Malformed or incomplete data for a present panel is a build error." | |
| ), | |
| "figures": { | |
| result.key: result_manifest_entry(staging, result) for result in results | |
| }, | |
| } | |
| (staging / "MANIFEST.json").write_text( | |
| json.dumps( | |
| manifest, ensure_ascii=False, allow_nan=False, indent=2, sort_keys=True | |
| ) | |
| + "\n", | |
| encoding="utf-8", | |
| ) | |
| publish_directory(staging, output_root, replace) | |
| return manifest | |
| except BaseException: | |
| if staging.exists(): | |
| shutil.rmtree(staging) | |
| raise | |
| def build_command(tables_root: Path, output_root: Path, replace: bool) -> list[str]: | |
| """Return the canonical, interpreter-independent command for this build.""" | |
| command = [ | |
| str(Path(__file__).resolve()), | |
| "--tables-root", | |
| str(tables_root.resolve()), | |
| "--output-root", | |
| str(output_root.resolve()), | |
| ] | |
| if replace: | |
| command.append("--replace") | |
| return command | |
| def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace: | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument("--tables-root", type=Path, required=True) | |
| parser.add_argument("--output-root", type=Path, required=True) | |
| parser.add_argument( | |
| "--replace", | |
| action="store_true", | |
| help="Atomically replace an existing derived figure directory.", | |
| ) | |
| return parser.parse_args(argv) | |
| def main(argv: Sequence[str] | None = None) -> int: | |
| args = parse_args(argv) | |
| try: | |
| manifest = make_figures(args.tables_root, args.output_root, args.replace) | |
| except (OSError, ValidationError, ValueError) as exc: | |
| print(f"make_figures: ERROR: {exc}", file=sys.stderr) | |
| return 2 | |
| generated = sum( | |
| entry["status"] == "generated" for entry in manifest["figures"].values() | |
| ) | |
| missing = len(manifest["figures"]) - generated | |
| print( | |
| f"Generated {generated} interactive figure(s); {missing} explicitly unavailable." | |
| ) | |
| print(args.output_root.resolve() / "MANIFEST.json") | |
| return 0 | |
| if __name__ == "__main__": | |
| raise SystemExit(main()) | |
Xet Storage Details
- Size:
- 65.7 kB
- Xet hash:
- 0865323d64f0c1a14023efd31fe4827e0b2cb0e1fb6cfb28d2dabbac7102d5a3
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.