Spaces:
Runtime error
Runtime error
Update Down/plot_dual_down_antidown.py
Browse files- Down/plot_dual_down_antidown.py +91 -40
Down/plot_dual_down_antidown.py
CHANGED
|
@@ -1,10 +1,8 @@
|
|
| 1 |
"""
|
| 2 |
Overlay plot: Down vs AntiDown (tabular experiments).
|
| 3 |
|
| 4 |
-
Reads CSVs from
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
This is intentionally simple and dependency-light (pandas optional).
|
| 8 |
"""
|
| 9 |
|
| 10 |
from __future__ import annotations
|
|
@@ -13,6 +11,7 @@ from __future__ import annotations
|
|
| 13 |
import matplotlib
|
| 14 |
matplotlib.use("Agg")
|
| 15 |
|
|
|
|
| 16 |
import csv
|
| 17 |
import os
|
| 18 |
from collections import defaultdict
|
|
@@ -22,6 +21,10 @@ from typing import Dict, List, Tuple
|
|
| 22 |
import matplotlib.pyplot as plt
|
| 23 |
|
| 24 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
def _read_csv(path: Path) -> List[Dict[str, str]]:
|
| 26 |
with path.open("r", newline="") as f:
|
| 27 |
return list(csv.DictReader(f))
|
|
@@ -33,35 +36,34 @@ def _mean(xs: List[float]) -> float:
|
|
| 33 |
return sum(xs) / len(xs)
|
| 34 |
|
| 35 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 36 |
def _down_series(rows: List[Dict[str, str]]) -> Tuple[List[int], List[float]]:
|
| 37 |
"""
|
| 38 |
Down CSV fields (expected):
|
| 39 |
phase, episode, agent, episode_reward, [global_episode], ...
|
| 40 |
-
|
| 41 |
"""
|
| 42 |
bucket: Dict[Tuple[int, int], List[float]] = defaultdict(list)
|
|
|
|
| 43 |
for r in rows:
|
| 44 |
try:
|
| 45 |
-
phase = int(r
|
| 46 |
-
ep = int(r
|
| 47 |
-
rew = float(r
|
| 48 |
except Exception:
|
| 49 |
continue
|
| 50 |
bucket[(phase, ep)].append(rew)
|
| 51 |
|
| 52 |
-
# Sort by phase then episode, make a single x-axis that is "global episode"
|
| 53 |
-
keys = sorted(bucket.keys())
|
| 54 |
xs, ys = [], []
|
| 55 |
g = 0
|
| 56 |
-
|
| 57 |
-
for (phase, ep) in keys:
|
| 58 |
-
if last_phase is None:
|
| 59 |
-
last_phase = phase
|
| 60 |
-
if phase != last_phase:
|
| 61 |
-
last_phase = phase
|
| 62 |
xs.append(g)
|
| 63 |
-
ys.append(_mean(bucket[
|
| 64 |
g += 1
|
|
|
|
| 65 |
return xs, ys
|
| 66 |
|
| 67 |
|
|
@@ -69,55 +71,101 @@ def _antidown_series(rows: List[Dict[str, str]]) -> Tuple[List[int], List[float]
|
|
| 69 |
"""
|
| 70 |
AntiDown CSV fields (expected):
|
| 71 |
phase, episode, agent, total_reward, ...
|
| 72 |
-
|
| 73 |
"""
|
| 74 |
bucket: Dict[Tuple[int, int], List[float]] = defaultdict(list)
|
|
|
|
| 75 |
for r in rows:
|
| 76 |
try:
|
| 77 |
-
phase = int(r
|
| 78 |
-
ep = int(r
|
| 79 |
-
rew = float(r
|
| 80 |
except Exception:
|
| 81 |
continue
|
| 82 |
bucket[(phase, ep)].append(rew)
|
| 83 |
|
| 84 |
-
keys = sorted(bucket.keys())
|
| 85 |
xs, ys = [], []
|
| 86 |
g = 0
|
| 87 |
-
|
| 88 |
-
for (phase, ep) in keys:
|
| 89 |
-
if last_phase is None:
|
| 90 |
-
last_phase = phase
|
| 91 |
-
if phase != last_phase:
|
| 92 |
-
last_phase = phase
|
| 93 |
xs.append(g)
|
| 94 |
-
ys.append(_mean(bucket[
|
| 95 |
g += 1
|
|
|
|
| 96 |
return xs, ys
|
| 97 |
|
| 98 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 99 |
def main() -> None:
|
|
|
|
|
|
|
| 100 |
root = Path(__file__).resolve().parent
|
| 101 |
-
|
| 102 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 103 |
graphics_dir.mkdir(parents=True, exist_ok=True)
|
| 104 |
|
| 105 |
down_csv = results_dir / "down_paradox_tabular_results.csv"
|
| 106 |
anti_csv = results_dir / "antidown_corrupted_valley_tabular.csv"
|
| 107 |
|
| 108 |
-
if not down_csv.exists()
|
| 109 |
-
|
| 110 |
-
|
|
|
|
| 111 |
|
| 112 |
down_rows = _read_csv(down_csv)
|
| 113 |
anti_rows = _read_csv(anti_csv)
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
|
|
|
|
|
|
| 117 |
|
| 118 |
x1, y1 = _down_series(down_rows)
|
| 119 |
x2, y2 = _antidown_series(anti_rows)
|
| 120 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
plt.figure(figsize=(10, 5))
|
| 122 |
plt.plot(x1, y1, label="Down (mean across agents)")
|
| 123 |
plt.plot(x2, y2, label="AntiDown (mean across agents)")
|
|
@@ -125,10 +173,13 @@ def main() -> None:
|
|
| 125 |
plt.ylabel("Episode return")
|
| 126 |
plt.title("Down vs AntiDown — Tabular returns (mean across agents)")
|
| 127 |
plt.legend()
|
| 128 |
-
out = graphics_dir / "dual_down_antidown_episode_return.png"
|
| 129 |
plt.tight_layout()
|
| 130 |
-
|
| 131 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 132 |
|
| 133 |
|
| 134 |
if __name__ == "__main__":
|
|
|
|
| 1 |
"""
|
| 2 |
Overlay plot: Down vs AntiDown (tabular experiments).
|
| 3 |
|
| 4 |
+
Reads CSVs from a shared results directory and writes a PNG overlay plot.
|
| 5 |
+
This is the canonical comparative result for the Down vs AntiDown experiment.
|
|
|
|
|
|
|
| 6 |
"""
|
| 7 |
|
| 8 |
from __future__ import annotations
|
|
|
|
| 11 |
import matplotlib
|
| 12 |
matplotlib.use("Agg")
|
| 13 |
|
| 14 |
+
import argparse
|
| 15 |
import csv
|
| 16 |
import os
|
| 17 |
from collections import defaultdict
|
|
|
|
| 21 |
import matplotlib.pyplot as plt
|
| 22 |
|
| 23 |
|
| 24 |
+
# ----------------------------
|
| 25 |
+
# Utilities
|
| 26 |
+
# ----------------------------
|
| 27 |
+
|
| 28 |
def _read_csv(path: Path) -> List[Dict[str, str]]:
|
| 29 |
with path.open("r", newline="") as f:
|
| 30 |
return list(csv.DictReader(f))
|
|
|
|
| 36 |
return sum(xs) / len(xs)
|
| 37 |
|
| 38 |
|
| 39 |
+
# ----------------------------
|
| 40 |
+
# Series builders
|
| 41 |
+
# ----------------------------
|
| 42 |
+
|
| 43 |
def _down_series(rows: List[Dict[str, str]]) -> Tuple[List[int], List[float]]:
|
| 44 |
"""
|
| 45 |
Down CSV fields (expected):
|
| 46 |
phase, episode, agent, episode_reward, [global_episode], ...
|
| 47 |
+
Aggregate by (phase, episode), averaging across agents.
|
| 48 |
"""
|
| 49 |
bucket: Dict[Tuple[int, int], List[float]] = defaultdict(list)
|
| 50 |
+
|
| 51 |
for r in rows:
|
| 52 |
try:
|
| 53 |
+
phase = int(r["phase"])
|
| 54 |
+
ep = int(r["episode"])
|
| 55 |
+
rew = float(r["episode_reward"])
|
| 56 |
except Exception:
|
| 57 |
continue
|
| 58 |
bucket[(phase, ep)].append(rew)
|
| 59 |
|
|
|
|
|
|
|
| 60 |
xs, ys = [], []
|
| 61 |
g = 0
|
| 62 |
+
for key in sorted(bucket.keys()):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
xs.append(g)
|
| 64 |
+
ys.append(_mean(bucket[key]))
|
| 65 |
g += 1
|
| 66 |
+
|
| 67 |
return xs, ys
|
| 68 |
|
| 69 |
|
|
|
|
| 71 |
"""
|
| 72 |
AntiDown CSV fields (expected):
|
| 73 |
phase, episode, agent, total_reward, ...
|
| 74 |
+
Aggregate by (phase, episode), averaging across agents.
|
| 75 |
"""
|
| 76 |
bucket: Dict[Tuple[int, int], List[float]] = defaultdict(list)
|
| 77 |
+
|
| 78 |
for r in rows:
|
| 79 |
try:
|
| 80 |
+
phase = int(r["phase"])
|
| 81 |
+
ep = int(r["episode"])
|
| 82 |
+
rew = float(r["total_reward"])
|
| 83 |
except Exception:
|
| 84 |
continue
|
| 85 |
bucket[(phase, ep)].append(rew)
|
| 86 |
|
|
|
|
| 87 |
xs, ys = [], []
|
| 88 |
g = 0
|
| 89 |
+
for key in sorted(bucket.keys()):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
xs.append(g)
|
| 91 |
+
ys.append(_mean(bucket[key]))
|
| 92 |
g += 1
|
| 93 |
+
|
| 94 |
return xs, ys
|
| 95 |
|
| 96 |
|
| 97 |
+
# ----------------------------
|
| 98 |
+
# CLI
|
| 99 |
+
# ----------------------------
|
| 100 |
+
|
| 101 |
+
def _build_parser() -> argparse.ArgumentParser:
|
| 102 |
+
p = argparse.ArgumentParser(description="Plot Down vs AntiDown (dual overlay).")
|
| 103 |
+
p.add_argument(
|
| 104 |
+
"--results_dir",
|
| 105 |
+
type=str,
|
| 106 |
+
default="",
|
| 107 |
+
help="Directory containing both CSVs. Defaults to TWOQUARKS_RESULTS_DIR or ./results.",
|
| 108 |
+
)
|
| 109 |
+
p.add_argument(
|
| 110 |
+
"--out",
|
| 111 |
+
type=str,
|
| 112 |
+
default="",
|
| 113 |
+
help="Output directory for PNG. Defaults to TWOQUARKS_GRAPHICS_DIR or ./graphics.",
|
| 114 |
+
)
|
| 115 |
+
p.add_argument(
|
| 116 |
+
"--prefix",
|
| 117 |
+
type=str,
|
| 118 |
+
default="dual_down_antidown",
|
| 119 |
+
help="Filename prefix for output image.",
|
| 120 |
+
)
|
| 121 |
+
return p
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
# ----------------------------
|
| 125 |
+
# Main
|
| 126 |
+
# ----------------------------
|
| 127 |
+
|
| 128 |
def main() -> None:
|
| 129 |
+
args = _build_parser().parse_args()
|
| 130 |
+
|
| 131 |
root = Path(__file__).resolve().parent
|
| 132 |
+
|
| 133 |
+
results_dir = Path(
|
| 134 |
+
args.results_dir
|
| 135 |
+
or os.environ.get("TWOQUARKS_RESULTS_DIR", (root / "results").as_posix())
|
| 136 |
+
)
|
| 137 |
+
|
| 138 |
+
graphics_dir = Path(
|
| 139 |
+
args.out
|
| 140 |
+
or os.environ.get("TWOQUARKS_GRAPHICS_DIR", (root / "graphics").as_posix())
|
| 141 |
+
)
|
| 142 |
graphics_dir.mkdir(parents=True, exist_ok=True)
|
| 143 |
|
| 144 |
down_csv = results_dir / "down_paradox_tabular_results.csv"
|
| 145 |
anti_csv = results_dir / "antidown_corrupted_valley_tabular.csv"
|
| 146 |
|
| 147 |
+
if not down_csv.exists():
|
| 148 |
+
raise FileNotFoundError(f"Missing Down CSV: {down_csv}")
|
| 149 |
+
if not anti_csv.exists():
|
| 150 |
+
raise FileNotFoundError(f"Missing AntiDown CSV: {anti_csv}")
|
| 151 |
|
| 152 |
down_rows = _read_csv(down_csv)
|
| 153 |
anti_rows = _read_csv(anti_csv)
|
| 154 |
+
|
| 155 |
+
if not down_rows:
|
| 156 |
+
raise RuntimeError("Down CSV is empty.")
|
| 157 |
+
if not anti_rows:
|
| 158 |
+
raise RuntimeError("AntiDown CSV is empty.")
|
| 159 |
|
| 160 |
x1, y1 = _down_series(down_rows)
|
| 161 |
x2, y2 = _antidown_series(anti_rows)
|
| 162 |
|
| 163 |
+
if len(x1) != len(x2):
|
| 164 |
+
print(
|
| 165 |
+
f"[dual] WARNING: series length mismatch "
|
| 166 |
+
f"(Down={len(x1)}, AntiDown={len(x2)})"
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
plt.figure(figsize=(10, 5))
|
| 170 |
plt.plot(x1, y1, label="Down (mean across agents)")
|
| 171 |
plt.plot(x2, y2, label="AntiDown (mean across agents)")
|
|
|
|
| 173 |
plt.ylabel("Episode return")
|
| 174 |
plt.title("Down vs AntiDown — Tabular returns (mean across agents)")
|
| 175 |
plt.legend()
|
|
|
|
| 176 |
plt.tight_layout()
|
| 177 |
+
|
| 178 |
+
out_path = graphics_dir / f"{args.prefix}_episode_return.png"
|
| 179 |
+
plt.savefig(out_path, dpi=160)
|
| 180 |
+
plt.close()
|
| 181 |
+
|
| 182 |
+
print(f"[dual] saved {out_path}")
|
| 183 |
|
| 184 |
|
| 185 |
if __name__ == "__main__":
|