diff --git a/Result-Reports/December.md b/Result-Reports/December.md index 7df419b..8e4bad7 100644 --- a/Result-Reports/December.md +++ b/Result-Reports/December.md @@ -4,7 +4,7 @@ Eerst literatuur bekijken ofdat probleem al voorkomt - [x] compare reconstructed prices with real imbalance prices (on figure and metrics on whole test set) -- [ ] Baseline policy (Eche imbalanceprijzen): +- [x] Baseline policy (Eche imbalanceprijzen): - Batterij: - 2 MWh - 1 MW power (charging / discharging) @@ -13,9 +13,7 @@ Eerst literatuur bekijken ofdat probleem al voorkomt - 1000 generaties voor 1 dag -> 1000 prijzen reconstrueren -> tresholds bepalen (logische tresholds) -> 2 tresholds voor 1 dag en toepassen op volledige test set --> resultaten: # charge cycle - -- [ ] Learning rate non autoregressive +- [x] Learning rate non autoregressive - [x] !!! Historgram quantile plot volledige dag - [x] CRPS en MAE, over 96 wanneer wordt het slecht @@ -199,4 +197,4 @@ Estimated Total Size (MB): 219.17 | Model | Experiment | test_L1Loss | test_CRPSLoss | |---|---|---| ---| | Linear Model | [Link](https://clearml.victormylle.be/projects/2e46d4af6f1e4c399cf9f5aa30bc8795/experiments/8f00fa7713f94d2ca376ce0eabc2742f/info-output/metrics/scalar?columns=selected&columns=type&columns=name&columns=tags&columns=status&columns=project.name&columns=users&columns=started&columns=last_update&columns=last_iteration&columns=parent.name&columns=m.1a899a19b54957e02a21c3a1d82577ad.0970ca62a85af2722008c5220e9d8a9e.value.Summary%2Ftest_CRPSLoss.lastreported&columns=m.293da6b015ca6a65992dcf7a53fa0237.098f6bcd4621d373cade4e832627b4f6.min_value.PinballLoss.test&order=-last_update&filter=) | 104.89022124436754 | 69.04472427024562 | -| Non Linear Model | [Link](https://clearml.victormylle.be/projects/2e46d4af6f1e4c399cf9f5aa30bc8795/experiments/f2ed5c3b54cd4d859d72bc55e53ff646/info-output/metrics/scalar?columns=selected&columns=type&columns=name&columns=tags&columns=status&columns=project.name&columns=users&columns=started&columns=last_update&columns=last_iteration&columns=parent.name&columns=m.1a899a19b54957e02a21c3a1d82577ad.0970ca62a85af2722008c5220e9d8a9e.value.Summary%2Ftest_CRPSLoss.lastreported&columns=m.293da6b015ca6a65992dcf7a53fa0237.098f6bcd4621d373cade4e832627b4f6.min_value.PinballLoss.test&order=-last_update&filter=) | 103.250159162178 | 74.21675055952228 | +| Non Linear Model | [Link](https://clearml.victormylle.be/projects/2e46d4af6f1e4c399cf9f5aa30bc8795/experiments/d19c767120a24f97b3231f0e8ac9f2b5/info-output/metrics/scalar?columns=selected&columns=type&columns=name&columns=tags&columns=status&columns=project.name&columns=users&columns=started&columns=last_update&columns=last_iteration&columns=parent.name&columns=m.1a899a19b54957e02a21c3a1d82577ad.0970ca62a85af2722008c5220e9d8a9e.value.Summary%2Ftest_CRPSLoss.lastreported&columns=m.293da6b015ca6a65992dcf7a53fa0237.098f6bcd4621d373cade4e832627b4f6.min_value.PinballLoss.test&order=-last_update&filter=) | 103.3332725941881 | 69.50204645931149 | diff --git a/Result-Reports/Policies.md b/Result-Reports/Policies.md new file mode 100644 index 0000000..d8a387b --- /dev/null +++ b/Result-Reports/Policies.md @@ -0,0 +1,85 @@ +# Policies for battery charging +## Battery +Capacity: 2MWh \ +Charging Power: 1MW \ +Discharging Power: 1MW + +## Baseline policy +Determining 2 thresholds for battery charging and discharging. Charging when the imbalance price is below the lower threshold and discharging when the imbalance price is above the upper threshold. + +Training data: 01-01-2020 - 31-12-2022 +Test data: 01-01-2023 - 12-12-2023 + +| Charging threshold (€/MWh) | Discharge threshold (€/MWh) | Profit (€) | Charge Cycles | +------------------:|---------------------:|---------------:|----------------:| +| 150 | 100 | 366406 | 2658 | +| 150 | 50 | 366406 | 2658 | +| 150 | 0 | 366406 | 2658 | +| 150 | 150 | 365999 | 2639 | +| 100 | 150 | 364905 | 2306 | +| 100 | 100 | 364483 | 2886 | +| 100 | 0 | 364376 | 2896 | +| 100 | 50 | 364376 | 2896 | +| 200 | 150 | 352953 | 2416 | +| 200 | 100 | 352953 | 2416 | +| 200 | 50 | 352953 | 2416 | +| 200 | 0 | 352953 | 2416 | +| 200 | 200 | 352811 | 2413 | +| 150 | 200 | 350975 | 2243 | +| 100 | 200 | 345855 | 1929 | +| 50 | 50 | 341569 | 3864 | +| 50 | 100 | 341421 | 2357 | +| 50 | 0 | 341202 | 3865 | +| 200 | 250 | 337567 | 1996 | +| 50 | 150 | 330189 | 1815 | +| 150 | 250 | 329684 | 1835 | +| 200 | 300 | 329343 | 1610 | +| 250 | 250 | 323456 | 2089 | +| 250 | 0 | 322692 | 2103 | +| 250 | 150 | 322692 | 2103 | +| 250 | 50 | 322692 | 2103 | +| 250 | 100 | 322692 | 2103 | +| 250 | 200 | 322692 | 2103 | +| 250 | 300 | 321378 | 1685 | +| 100 | 250 | 319651 | 1554 | +| 150 | 300 | 319089 | 1462 | +| 300 | 250 | 314776 | 1752 | +| 300 | 50 | 314776 | 1752 | +| 300 | 100 | 314776 | 1752 | +| 300 | 150 | 314776 | 1752 | +| 300 | 200 | 314776 | 1752 | +| 300 | 0 | 314776 | 1752 | +| 50 | 200 | 313685 | 1476 | +| 300 | 300 | 313469 | 1749 | +| 100 | 300 | 309773 | 1236 | +| 200 | 350 | 306832 | 1341 | +| 250 | 350 | 302349 | 1411 | +| 150 | 350 | 295917 | 1202 | +| 300 | 350 | 290570 | 1466 | +| 50 | 250 | 290097 | 1195 | +| 350 | 300 | 284856 | 1507 | +| 350 | 250 | 284856 | 1507 | +| 350 | 150 | 284856 | 1507 | +| 350 | 100 | 284856 | 1507 | +| 350 | 50 | 284856 | 1507 | +| 350 | 0 | 284856 | 1507 | +| 350 | 200 | 284856 | 1507 | +| 100 | 350 | 284323 | 996 | +| 50 | 300 | 280922 | 954 | +| 350 | 350 | 279834 | 1496 | +| 0 | 150 | 277863 | 1243 | +| 0 | 100 | 276435 | 1477 | +| 0 | 200 | 270056 | 1080 | +| 0 | 50 | 269265 | 1914 | +| 50 | 350 | 259569 | 787 | +| 0 | 250 | 258282 | 908 | +| 0 | 300 | 250679 | 741 | +| 0 | 0 | 248357 | 2065 | +| 0 | 350 | 233775 | 624 | + +## Optimal policy +Charge threshold: 150 €/MWh \ +Discharge threshold: 100 €/MWh + +Profit on test data: € 169846.84 \ +Charge cycles: 1403 \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index d375463..ccec0b4 100644 --- a/requirements.txt +++ b/requirements.txt @@ -10,4 +10,5 @@ prettytable clearml properscoring nbconvert -torchinfo \ No newline at end of file +torchinfo +tabulate \ No newline at end of file diff --git a/src/policies/simple_baseline.py b/src/policies/simple_baseline.py new file mode 100644 index 0000000..bff4991 --- /dev/null +++ b/src/policies/simple_baseline.py @@ -0,0 +1,139 @@ +import pandas as pd +import numpy as np +import itertools +from tqdm import tqdm + +imbalance_prices = "data/imbalance_prices.csv" + +class Battery: + def __init__(self, capacity: float, power: float): + """ + :param capacity: Battery capacity in MWh + :param current_charge: Current charge in MWh + :param power: Power in MW + """ + self.capacity = capacity + self.current_charge = 0 + self.power = power + self.charge_cycles = 0 + + self.charging = False + self.discharging = False + + def simulate(self): + """ + Simulate the battery for one time step (one quarter of an hour) + """ + if self.charging: + return self.charge() + elif self.discharging: + return self.discharge() + return 0 + + def discharge(self): + """ + Discharge the battery by one time step (one quarter of an hour) + """ + if self.current_charge == 0: + return 0 + self.discharging = True + self.current_charge -= self.power / 4 + + if self.current_charge <= 0: + self.current_charge = 0 + self.discharging = False + self.charge_cycles += 1 + + return self.power / 4 + + def charge(self): + """ + Charge the battery by one time step (one quarter of an hour) + """ + if self.current_charge == self.capacity: + return 0 + self.charging = True + self.current_charge += self.power / 4 + + if self.current_charge >= self.capacity: + self.current_charge = self.capacity + self.charging = False + + return self.power / 4 + + def reset(self): + """ + Reset the battery to its initial state + """ + self.current_charge = 0 + self.charging = False + self.discharging = False + self.charge_cycles = 0 + +class BaselinePolicy(): + def __init__(self, battery: Battery): + self.battery = battery + self.train_data = self.load_imbalance_prices(train=True) + self.test_data = self.load_imbalance_prices(train=False) + + # print first datetime of train and test data + print(f"Training range: {self.train_data.iloc[0]['DateTime'].strftime('%d-%m-%Y')} - {self.train_data.iloc[-1]['DateTime'].strftime('%d-%m-%Y')}") + print(f"Test range: {self.test_data.iloc[0]['DateTime'].strftime('%d-%m-%Y')} - {self.test_data.iloc[-1]['DateTime'].strftime('%d-%m-%Y')}") + + def load_imbalance_prices(self, train: bool = True): + imbalance_prices = pd.read_csv('data/imbalance_prices.csv', parse_dates=True, sep=";") + imbalance_prices = imbalance_prices[['DateTime', 'Positive imbalance price']] + imbalance_prices['DateTime'] = pd.to_datetime(imbalance_prices['DateTime'], utc=True) + if train: + imbalance_prices = imbalance_prices.loc[imbalance_prices['DateTime'].dt.year < 2023] + imbalance_prices = imbalance_prices.loc[imbalance_prices['DateTime'].dt.year >= 2020] + else: + imbalance_prices = imbalance_prices.loc[imbalance_prices['DateTime'].dt.year == 2023] + imbalance_prices = imbalance_prices.sort_values(by=['DateTime']) + return imbalance_prices + + def get_train_score(self, charge_treshold, discharge_treshold): + return self.get_score(self.train_data, charge_treshold, discharge_treshold) + + def get_test_score(self, charge_treshold, discharge_treshold): + return self.get_score(self.test_data, charge_treshold, discharge_treshold) + + # if price is below treshold -> charge battery: total_profit -= charge * price + # if price is above treshold -> discharge battery: total_profit += discharge * price + def get_score(self, df, charge_treshold, discharge_treshold): + self.battery.reset() + total_profit = 0 + + for index, row in df.iterrows(): + if self.battery.charging: + total_profit -= self.battery.simulate() * row['Positive imbalance price'] + elif self.battery.discharging: + total_profit += self.battery.simulate() * row['Positive imbalance price'] + else: + if row['Positive imbalance price'] < charge_treshold: + total_profit -= self.battery.charge() * row['Positive imbalance price'] + elif row['Positive imbalance price'] > discharge_treshold: + total_profit += self.battery.discharge() * row['Positive imbalance price'] + + return total_profit, self.battery.charge_cycles + + def treshold_scores(self, charge_tresholds, discharge_tresholds): + df = pd.DataFrame(columns=["Charge treshold", "Discharge treshold", "Total Profit", "Charge cycles"]) + + for charge_treshold, discharge_treshold in tqdm(itertools.product(charge_tresholds, discharge_tresholds)): + total_profit, charge_cycles = self.get_train_score(charge_treshold, discharge_treshold) + df = pd.concat([df, pd.DataFrame([[charge_treshold, discharge_treshold, total_profit, charge_cycles]], columns=["Charge treshold", "Discharge treshold", "Total Profit", "Charge cycles"])]) + + df = df.sort_values(by=['Total Profit'], ascending=False) + return df + + +battery = Battery(2, 1) +policy = BaselinePolicy(battery) + +# charge_tresholds = [0, 50, 100, 150, 200, 250, 300, 350] +# discharge_tresholds = [0, 50, 100, 150, 200, 250, 300, 350] +# df = policy.treshold_scores(charge_tresholds, discharge_tresholds) +# print(df.to_markdown()) + +print(policy.get_test_score(150, 100)) \ No newline at end of file