PowwerUp / rl_env.py
pvsai's picture
Upload folder using huggingface_hub
88bc772 verified
Raw
History Blame Contribute Delete
3 kB
import gymnasium as gym
from gymnasium import spaces
import numpy as np
from battery import Battery
class CooptimEnv(gym.Env):
def __init__(self, input_data, config):
super().__init__()
self.input_data = input_data.copy()
self.config = config
self.current_step = 0
# Initialize Battery from Config
bat_cfg = config["battery"]
self.battery = Battery(
e_max_mwh=bat_cfg["e_max_mwh"],
p_ch_max=bat_cfg["p_ch_max_mw"],
p_dis_max=bat_cfg["p_dis_max_mw"],
eta_ch=bat_cfg["eta_ch"],
eta_dis=bat_cfg["eta_dis"]
)
self.observation_space = spaces.Box(
low=-np.inf, high=np.inf, shape=(input_data.shape[1] + 1,), dtype=np.float32
)
# Action: [Market Choice, Power Fraction]
self.action_space = spaces.Box(
low=np.array([0.0, -1.0]), high=np.array([1.0, 1.0]), dtype=np.float32
)
def reset(self, seed=None, options=None):
super().reset(seed=seed)
self.current_step = 0
soc = self.battery.reset()
obs = np.append(self.input_data.iloc[self.current_step].values, soc)
return obs.astype(np.float32), {}
def step(self, action):
market_choice, p_fraction = action
inp = self.input_data.iloc[self.current_step]
c_energy = self.config["columns"]["energy"]
c_fcr = self.config["columns"]["fcr"]
reward = 0.0
dt_hours = 0.25 # 15-min intervals
if market_choice < 0.6:
# --- ARBITRAGE MODE ---
soc = self.battery.step(p_fraction, dt_hours)
# Revenue = flow * price (Positive for sell, Negative for buy)
revenue = p_fraction * inp[c_energy] * dt_hours
# Throughput penalty to prevent excessive micro-trading
t_penalty = self.config["throughput_penalty"]["c_eur_per_mwh"]
costs = abs(p_fraction * self.battery.p_dis_max) * dt_hours * t_penalty
reward = revenue - costs
else:
# --- ANCILLARY MODE ---
soc = self.battery.soc
# Derate Ancillary during training to encourage exploration of Arb peaks
reward = (1.0 * inp[c_fcr] * dt_hours) * 0.5
self.current_step += 1
terminated = self.current_step >= len(self.input_data)
truncated = False
# --- TERMINAL SOC PENALTY ---
# If the battery is empty at end-of-day, apply a massive penalty
if terminated:
target_soc = self.config["end_of_day"]["min_soc_mwh"]
soc_error = abs(self.battery.soc - target_soc)
reward -= (soc_error * 100.0)
if not terminated:
next_obs = np.append(self.input_data.iloc[self.current_step].values, soc)
else:
next_obs = np.zeros(self.observation_space.shape)
return next_obs.astype(np.float32), float(reward), terminated, truncated, {}