| 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 |
| |
| |
| 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 |
| ) |
|
|
| |
| 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 |
|
|
| if market_choice < 0.6: |
| |
| soc = self.battery.step(p_fraction, dt_hours) |
| |
| revenue = p_fraction * inp[c_energy] * dt_hours |
| |
| 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: |
| |
| soc = self.battery.soc |
| |
| reward = (1.0 * inp[c_fcr] * dt_hours) * 0.5 |
|
|
| self.current_step += 1 |
| terminated = self.current_step >= len(self.input_data) |
| truncated = False |
|
|
| |
| |
| 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, {} |