File size: 4,494 Bytes
d7adedb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
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}/")