Spaces:
Running
Running
| """Render the §5.3 de-tuned peak-solar prediction figure. | |
| Reproduces ``paper/figures/fig_validation_peak_solar.png`` from the committed | |
| ``reports/power_prediction/summary.csv`` artifact (written by | |
| ``scripts/run_power_prediction.py``) so the paper figure is regenerable rather | |
| than a hand-made PNG. | |
| For each flown rover the chart shows: | |
| - the published peak-solar band (grey span) with the published point value; | |
| - the de-tuned clean-array prediction (filled marker) with its | |
| literature-cell-efficiency sensitivity band (vertical bar) -- this uses a | |
| single fixed parameter set applied to every rover, no per-rover calibration; | |
| - the de-tuned beginning-of-life clean prediction (open marker) where it | |
| differs materially, to expose the aging/dust derate the published value of a | |
| multi-year rover bakes in. | |
| Usage | |
| ----- | |
| :: | |
| python scripts/make_peak_solar_figure.py | |
| """ | |
| from __future__ import annotations | |
| import argparse | |
| import sys | |
| from pathlib import Path | |
| import pandas as pd | |
| from roverdevkit.tradespace.visualize import set_paper_rcparams | |
| _PUBLISHED_COLOR = "#444444" | |
| _BAND_COLOR = "#cfcfcf" | |
| _PRED_COLOR = "#1f77b4" | |
| _BOL_COLOR = "#d62728" | |
| def _parse_args(argv: list[str] | None = None) -> argparse.Namespace: | |
| p = argparse.ArgumentParser( | |
| description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter | |
| ) | |
| p.add_argument( | |
| "--summary", | |
| type=Path, | |
| default=Path("reports/power_prediction/summary.csv"), | |
| ) | |
| p.add_argument( | |
| "--out", | |
| type=Path, | |
| default=Path("paper/figures/fig_validation_peak_solar.png"), | |
| ) | |
| return p.parse_args(argv) | |
| def main(argv: list[str] | None = None) -> int: | |
| args = _parse_args(argv) | |
| df = pd.read_csv(args.summary).sort_values("published_w").reset_index(drop=True) | |
| set_paper_rcparams() | |
| import matplotlib.pyplot as plt | |
| from matplotlib.lines import Line2D | |
| from matplotlib.patches import Patch | |
| fig, ax = plt.subplots(figsize=(7.2, 4.2)) | |
| x = list(range(len(df))) | |
| half_w = 0.30 | |
| for i, row in df.iterrows(): | |
| # Published band as a grey span behind everything. | |
| ax.add_patch( | |
| plt.Rectangle( | |
| (i - half_w, row["band_low_w"]), | |
| 2 * half_w, | |
| row["band_high_w"] - row["band_low_w"], | |
| facecolor=_BAND_COLOR, | |
| edgecolor="none", | |
| zorder=1, | |
| ) | |
| ) | |
| # Published point value. | |
| ax.hlines( | |
| row["published_w"], i - half_w, i + half_w, | |
| color=_PUBLISHED_COLOR, lw=2.0, zorder=3, | |
| ) | |
| # De-tuned clean prediction with its cell-efficiency sensitivity band. | |
| ax.errorbar( | |
| i, row["predicted_clean_w"], | |
| yerr=[ | |
| [row["predicted_clean_w"] - row["sensitivity_low_w"]], | |
| [row["sensitivity_high_w"] - row["predicted_clean_w"]], | |
| ], | |
| fmt="o", color=_PRED_COLOR, markersize=7, capsize=4, lw=1.6, zorder=5, | |
| ) | |
| # BOL clean prediction (open marker) only where it differs from the band. | |
| bol = row["predicted_bol_w"] | |
| if bol > row["band_high_w"]: | |
| ax.scatter( | |
| i, bol, marker="o", s=55, facecolor="none", | |
| edgecolor=_BOL_COLOR, linewidths=1.6, zorder=6, | |
| ) | |
| ax.annotate( | |
| f"BOL clean: {bol:.0f} W\nimplied derate {row['implied_total_derate']:.2f}", | |
| xy=(i, bol), xytext=(i + 0.12, bol), | |
| va="center", ha="left", fontsize=8, color=_BOL_COLOR, | |
| ) | |
| ax.set_xticks(x) | |
| ax.set_xticklabels(df["rover_name"]) | |
| ax.set_ylabel("peak solar power (W)") | |
| ax.set_xlim(-0.6, len(df) - 0.4 + 0.9) | |
| ax.set_ylim(0, float(df["predicted_bol_w"].max()) * 1.15) | |
| ax.set_title("Fixed-parameter peak-solar prediction vs published band (no per-rover tuning)") | |
| legend_handles = [ | |
| Patch(facecolor=_BAND_COLOR, label="published band"), | |
| Line2D([0], [0], color=_PUBLISHED_COLOR, lw=2.0, label="published value"), | |
| Line2D( | |
| [0], [0], marker="o", linestyle="none", color=_PRED_COLOR, markersize=7, | |
| label="fixed-parameter clean prediction (cell-eff. sensitivity bar)", | |
| ), | |
| Line2D( | |
| [0], [0], marker="o", linestyle="none", markerfacecolor="none", | |
| markeredgecolor=_BOL_COLOR, markersize=8, label="fixed-parameter BOL clean prediction", | |
| ), | |
| ] | |
| ax.legend( | |
| handles=legend_handles, | |
| loc="upper center", | |
| bbox_to_anchor=(0.5, -0.14), | |
| ncol=2, | |
| fontsize=8, | |
| frameon=False, | |
| ) | |
| args.out.parent.mkdir(parents=True, exist_ok=True) | |
| fig.savefig(args.out, bbox_inches="tight") | |
| plt.close(fig) | |
| print(f"Wrote {args.out}") | |
| return 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |