Files
Microtracking/plot_comparison.py

128 lines
4.7 KiB
Python

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
plot_comparison.py — Figura comparativa entre dos dias de campo
(14 Sep vs 21 Sep 2025, Aguascalientes, UTC-6).
Cada dia se evalua contra su PROPIO techo de referencia P_ref(t)
(alineacion focal ideal, Ecuacion del techo de potencia), de modo que la
comparativa muestra la reproducion entre sesiones y no depende de un
dataset externo de control.
Lee los salidas ya generadas por parse_log.py:
articulo/resultados_<tag>/samples.csv (t, G_POA, P_CPV, P_ref, ...)
articulo/resultados_<tag>/metrics.json (E, H, E/H, eta_tr, MAE_P, ...)
y produce:
articulo/figs/fig_comparison_14sep_21sep.{pdf,png}
Uso: py -3.12 plot_comparison.py
(Requiere haber corrido antes: py -3.12 parse_log.py gyuggyhoy.txt
y py -3.12 parse_log.py 21092025.txt )
"""
import io
import os
import json
from pathlib import Path
import numpy as np
import pandas as pd
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
import matplotlib.dates as mdates
BASE = Path(__file__).resolve().parent
FIGS = BASE / "articulo" / "figs"
# (tag del parser = stem del log, etiqueta para la figura)
# 14 Sep esta representado por su captura B (gyuggyhoy); 21 Sep por la
# sesion completa que es la sesion "cabecera" del articulo.
DAYS = [
("gyuggyhoy", "14 September 2025 (capture B)"),
("21092025", "21 September 2025 (headline session)"),
]
def load(tag):
d = BASE / "articulo" / f"resultados_{tag}"
csv = d / "samples.csv"
if not csv.exists():
raise SystemExit(
f"Falta {csv}. Ejecuta antes: py -3.12 parse_log.py {tag}.txt")
df = pd.read_csv(csv, parse_dates=["timestamp"])
with open(d / "metrics.json", encoding="utf-8") as f:
m = json.load(f)
return df, m
def savefig_safe(fig, stem):
"""Renderiza en memoria y escribe en disco (delete-then-create).
Evita el bloqueo de archivo que OneDrive causa con savefig directo."""
FIGS.mkdir(parents=True, exist_ok=True)
for ext, kw in (("pdf", {}), ("png", {"dpi": 200})):
path = FIGS / f"{stem}.{ext}"
if path.exists():
os.remove(str(path))
buf = io.BytesIO()
fig.savefig(buf, format=ext, **kw)
buf.seek(0)
with open(str(path), "wb") as f:
f.write(buf.read())
print(" saved", path)
def main():
fig, axes = plt.subplots(len(DAYS), 1, figsize=(9.2, 6.6), dpi=200)
axes = np.atleast_1d(axes)
for ax, (tag, label) in zip(axes, DAYS):
df, m = load(tag)
fs = m.get("full_session") or {}
t = df["timestamp"].values
lit = (df["G_POA"].values > 50.0)
ax.plot(t, df["P_CPV"].values * 1000, color="tab:red", lw=0.9,
label="$P_{\\mathrm{CPV}}$ (tracked)")
ax.plot(t, df["P_ref"].values * 1000, color="k", ls="--", lw=1.0,
label="$P_{\\mathrm{ref}}$ (reference ceiling)")
ax.fill_between(t, df["P_CPV"].values * 1000,
df["P_ref"].values * 1000,
where=lit, color="tab:red", alpha=0.10,
label="Loss vs. $P_{\\mathrm{ref}}$")
ax2 = ax.twinx()
ax2.plot(t, df["G_POA"].values, color="tab:orange", lw=0.6,
alpha=0.6, label="$G_{\\mathrm{POA}}$")
ax2.set_ylabel("Irradiance $G_{\\mathrm{POA}}$ (W/m$^2$)",
color="tab:orange")
ax2.tick_params(axis="y", labelcolor="tab:orange")
ax.set_ylabel("Power (mW)", color="tab:red")
ax.tick_params(axis="y", labelcolor="tab:red")
ax.set_ylim(bottom=0)
ax.xaxis.set_major_formatter(mdates.DateFormatter("%H:%M"))
ax.tick_params(axis="x", labelsize=8)
h1, l1 = ax.get_legend_handles_labels()
h2, l2 = ax2.get_legend_handles_labels()
ax.legend(h1 + h2, l1 + l2, loc="upper right", fontsize=7,
framealpha=0.6)
ax.grid(alpha=0.3)
ax.set_title(
f"{label} — $\\eta_{{tr}}$ = {fs.get('eta_tr_pct', 'n/a')}% "
f"MAE$_P$ = {fs.get('MAE_P_mW', 'n/a')} mW "
f"$E$ = {fs.get('E_Wh', 'n/a')} Wh "
f"$H$ = {fs.get('H_kWh_m2', 'n/a')} kWh/m$^2$",
fontsize=9.5)
# No global suptitle: it overflowed the figure width (clipped at both
# edges) and duplicated the LaTeX \caption. The per-panel titles below
# carry the per-day metrics; the article caption carries the description.
fig.tight_layout()
savefig_safe(fig, "fig_comparison_14sep_21sep")
plt.close(fig)
print("DONE ->", FIGS)
if __name__ == "__main__":
main()