import sys import os import numpy as np import torch import matplotlib.pyplot as plt # Add root directory to sys.path for absolute imports sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) from openenv.grid_twin import GridTwinEnv from openenv.models import Action import glob from agent.loader import load_trained_model # ========================= # SETUP # ========================= device = torch.device("cuda" if torch.cuda.is_available() else "cpu") PLOT_DIR = "visualization" os.makedirs(PLOT_DIR, exist_ok=True) # 🧹 Clear existing plots to prevent accumulation for f in glob.glob(f"{PLOT_DIR}/*.png"): try: os.remove(f) except OSError: pass # ========================= # LOAD SAC MODEL # ========================= actor = load_trained_model() # ========================= # EVALUATION (7 DAYS) # ========================= print("\n=== SAC RL Evaluation (Grid-Aware) ===") total_rewards = [] total_profits = [] total_EA_all = 0 total_FR_all = 0 total_PS_all = 0 total_deg_all = 0 total_v_viols = 0 # Store Day 0 Trajectory soc_list, power_list, demand_list, lmp_list = [], [], [], [] regd_list, voltage_list, line_load_list = [], [], [] num_days = 7 for day in range(num_days): env = GridTwinEnv(seed=day) obs = env.reset(episode=999).to_array() # Ensure High-Fidelity done = False total_reward = 0 while not done: state = torch.tensor(obs, dtype=torch.float32).to(device).unsqueeze(0) with torch.no_grad(): action = actor.get_action(state, deterministic=True) next_obs, reward, done, info = env.step(Action(power=action)) total_reward += reward obs = next_obs.to_array() if day == 0: soc_list.append(env.soc) power_list.append(action) demand_list.append(env.demand_series[env.t-1]) lmp_list.append(env.lmp_series[env.t-1]) regd_list.append(env.regd_series[env.t-1]) voltage_list.append(env.v_pu) line_load_list.append(env.line_loading) if env.v_pu < 0.95 or env.v_pu > 1.05: total_v_viols += 1 day_profit = env.total_EA + env.total_FR print(f"Day {day}: Reward={total_reward:7.2f} | Profit=${day_profit:7.2f} | EA=${env.total_EA:7.2f}") total_rewards.append(total_reward) total_profits.append(day_profit) total_EA_all += env.total_EA total_FR_all += env.total_FR total_PS_all += env.total_PS total_deg_all += env.total_deg # ========================= # PERFORMANCE SUMMARY # ========================= print("\n=== AGGREGATE STATS ===") print(f"Avg Reward: {np.mean(total_rewards):.3f}") print(f"Avg Daily Profit: ${np.mean(total_profits):.2f}") print(f"Total Voltage Violations: {total_v_viols} (over {num_days*288} steps)") print(f"Avg Battery Deg: {total_deg_all / num_days:.3f} MWh/day") print("\n=== REVENUE BREAKDOWN (Avg/Day) ===") print(f"Energy Arbitrage (EA): ${total_EA_all / num_days:7.2f}") print(f"Freq Regulation (FR): ${total_FR_all / num_days:7.2f}") print(f"Peak Stress Level: {total_PS_all / num_days:7.2f}") # ========================= # PLOTS (ONLY DAY 0) # ========================= t = np.arange(len(soc_list)) plt.rcParams['figure.figsize'] = [10, 5] # Grid Physics (Consolidated) plt.figure() ax1 = plt.gca() ax1.plot(t, voltage_list, color='teal', label="Voltage (pu)") ax1.axhline(1.05, color='r', ls='--') ax1.axhline(0.95, color='r', ls='--') ax1.set_ylabel("Voltage (pu)") ax2 = ax1.twinx() ax2.plot(t, line_load_list, color='orange', alpha=0.5, label="Line Loading (%)") ax2.axhline(80, color='brown', ls=':') ax2.set_ylabel("Loading (%)") plt.title("Day 0: Grid Constraints Compliance") plt.savefig(f"{PLOT_DIR}/grid_compliance.png") plt.close() # Revenue Breakdown Pie labels = ["Energy Arbitrage", "Freq Regulation"] values = [max(0.1, total_EA_all), max(0.1, total_FR_all)] plt.figure() plt.pie(values, labels=labels, autopct='%1.1f%%', colors=['#4CAF50', '#2196F3']) plt.title("Revenue Mix (7 Days)") plt.savefig(f"{PLOT_DIR}/breakdown.png") plt.close() # SOC & Price fig, ax1 = plt.subplots() ax1.plot(t, lmp_list, color='gray', alpha=0.3, label="LMP") ax1.set_ylabel("Price") ax2 = ax1.twinx() ax2.plot(t, soc_list, color='blue', label="SOC") ax2.set_ylabel("SOC [0-1]") plt.title("Day 0: Cycling Behavior vs Price") plt.savefig(f"{PLOT_DIR}/soc_price.png") plt.close() print(f"\nEvaluation complete. Trajectories saved to {PLOT_DIR}/")