File size: 6,114 Bytes
88bc772 | 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 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 | import logging
import json
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from stable_baselines3 import TD3
from stable_baselines3.common.noise import NormalActionNoise
from rl_env import CooptimEnv
from src.cooptim.orchestrator import Orchestrator
# Configuration du logging
logging.basicConfig(level=logging.INFO, format="%(asctime)s | %(levelname)s | %(message)s")
logger = logging.getLogger(__name__)
# --- RL INFERENCE ENGINE ---
def run_rl_inference(model, env, config):
"""Executes a full simulation day using the trained RL model with deterministic actions."""
obs, _ = env.reset()
done = False
history = []
input_data = env.input_data
energy_col = config["columns"]["energy"]
fcr_col = config["columns"]["fcr"]
step = 0
while not done and step < len(input_data):
# Use deterministic=True to see the learned strategy without exploration noise
action, _ = model.predict(obs, deterministic=True)
current_time = input_data.index[step]
prices = input_data.iloc[step]
# Environment Step
obs, reward, terminated, truncated, _ = env.step(action)
done = terminated or truncated
# Map Logic based on the 0.6 threshold defined in rl_env.py
market = "Ancillary" if action[0] >= 0.6 else "Arbitrage"
decision = "Idle"
if market == "Arbitrage":
if action[1] > 0.1: decision = "Sell"
elif action[1] < -0.1: decision = "Buy"
history.append({
"Timestamp": current_time,
"Market": market,
"Decision": decision,
"Power": action[1],
"Energy_Price": prices.get(energy_col, 0),
"FCR_Price": prices.get(fcr_col, 0),
"SoC": env.battery.soc,
"Revenue": reward
})
step += 1
return pd.DataFrame(history)
# --- REFINED DASHBOARD ---
def plot_rl_dashboard(df):
"""Generates the multi-panel strategy dashboard highlighting market switches and buy/sell timing."""
if df.empty:
logger.error("No data collected for dashboard.")
return
plt.style.use('seaborn-v0_8-muted')
fig, (ax1, ax2, ax3) = plt.subplots(3, 1, figsize=(15, 12), sharex=True)
# 1. SoC & Market Choice (Contextual Background)
ax1.plot(df["Timestamp"], df["SoC"], color='black', linewidth=2.5, label="Battery SoC")
for i in range(len(df)-1):
# Color background based on market choice
color = 'palegreen' if df["Market"].iloc[i] == "Ancillary" else 'lightskyblue'
ax1.axvspan(df["Timestamp"].iloc[i], df["Timestamp"].iloc[i+1], color=color, alpha=0.4)
ax1.set_ylabel("SoC (MWh)")
ax1.set_title("RL Strategy: Market Selection (Green=Ancillary, Blue=Arbitrage)")
ax1.legend(loc='upper left')
# 2. Trading Decisions vs Energy Price
ax2.plot(df["Timestamp"], df["Energy_Price"], color='darkorange', label="Energy Price (€/MWh)", alpha=0.8)
ax2_twin = ax2.twinx()
# Purple bars show the power flow (negative for charging/buying, positive for discharging/selling)
ax2_twin.bar(df["Timestamp"], df["Power"], width=0.008, color='purple', alpha=0.5, label="Action (+ Disch, - Ch)")
ax2_twin.set_ylim(-1.1, 1.1)
ax2.set_ylabel("Price")
ax2_twin.set_ylabel("Power Action")
ax2.set_title("Trading Decisions: Buy (Down) / Sell (Up) vs Price")
# 3. Financial Performance
df["CumRev"] = df["Revenue"].cumsum()
ax3.plot(df["Timestamp"], df["CumRev"], color='forestgreen', linewidth=2.5, label="Cumulative Profit")
ax3.fill_between(df["Timestamp"], df["CumRev"], color='forestgreen', alpha=0.15)
ax3.set_ylabel("Total Revenue (€)")
ax3.set_title("Real-Time Profit Accumulation")
plt.tight_layout()
plt.savefig("dashboard.png")
plt.show(block=True)
# --- MAIN BLOCK ---
if __name__ == "__main__":
# Load configuration
with open("config.json", "r") as f:
global_config = json.load(f)
START = global_config["run"]["start_date"]
END = global_config["run"]["end_date"]
# 1. Generate Baseline Data (Classical Co-optimization)
logger.info(f"--- Running Baseline Co-optimization: {START} to {END} ---")
orchestrator = Orchestrator(config=global_config)
sols_coopt = orchestrator.run()
# 2. RL Training Phase
logger.info("=== Initializing RL Multi-Market Agent Training ===")
try:
# Use first day's input as training data
training_df = sols_coopt[0].input
# Setup Custom Environment
env = CooptimEnv(training_df, global_config)
# AGGRESSIVE EXPLORATION: Sigma 0.3 forces the agent to try negative power (Buying)
n_actions = env.action_space.shape[0]
action_noise = NormalActionNoise(mean=np.zeros(n_actions), sigma=0.3 * np.ones(n_actions))
# Train TD3 Model with terminal SoC penalty awareness
model = TD3(
"MlpPolicy",
env,
action_noise=action_noise,
verbose=1,
learning_rate=0.0005,
batch_size=256
)
# Increased timesteps (20k) to allow agent to learn from terminal penalties
logger.info("Starting learning process...")
model.learn(total_timesteps=20000)
# 3. Decision Dashboard & Reporting
logger.info("=== Running Post-Training Deterministic Inference ===")
df_rl = run_rl_inference(model, env, global_config)
# Print Market Summary
print("\n" + "="*40)
print(f"RL MARKET ACTIVITY SUMMARY")
if not df_rl.empty:
print(df_rl["Market"].value_counts())
print("="*40)
# Generate the Final Dashboard
plot_rl_dashboard(df_rl)
# Save results to CSV
df_rl.to_csv("rl_trading_results.csv", index=False)
logger.info("Full decision report saved to rl_trading_results.csv")
except Exception as e:
logger.error(f"RL Pipeline failure: {e}")
logger.info("Simulation complete.") |