Daniel Varga commited on
Commit
f1abbf3
·
1 Parent(s): a13327d

offline prediction

Browse files
Files changed (2) hide show
  1. v2/architecture.py +14 -9
  2. v2/predictor.py +124 -0
v2/architecture.py CHANGED
@@ -127,11 +127,13 @@ class DummyPredictor:
127
 
128
  # this function does not mutate its inputs.
129
  # it makes a clone of battery_model and modifies that.
130
- def simulator(battery_model, supplier, prod_cons, prod_predictor, cons_predictor, decider):
131
  battery_model = copy.copy(battery_model)
132
 
133
  demand_np = prod_cons['Consumption'].to_numpy()
134
  production_np = prod_cons['Production'].to_numpy()
 
 
135
  assert len(demand_np) == len(production_np)
136
  step_in_minutes = prod_cons.index.freq.n
137
  assert step_in_minutes == 5
@@ -170,8 +172,12 @@ def simulator(battery_model, supplier, prod_cons, prod_predictor, cons_predictor
170
  unsatisfied_demand = demand
171
  remaining_production = production
172
 
173
- prod_prediction = prod_predictor.predict(i, decider.input_window_size)
174
- cons_prediction = cons_predictor.predict(i, decider.input_window_size)
 
 
 
 
175
  decision = decider.decide(prod_prediction, cons_prediction, battery_model)
176
 
177
  production_used_to_charge = 0
@@ -246,18 +252,17 @@ def main():
246
 
247
  met_2021_data, cons_2021_data = read_datasets()
248
  add_production_field(met_2021_data, parameters)
249
- all_2021_data = interpolate_and_join(met_2021_data, cons_2021_data)
250
 
251
- time_interval_min = all_2021_data.index.freq.n
 
 
252
  time_interval_h = time_interval_min / 60
253
  battery_model = BatteryModel(capacity_Ah=600, time_interval_h=time_interval_h)
254
 
255
- prod_predictor = DummyPredictor(pd.Series(all_2021_data['Production']))
256
- cons_predictor = DummyPredictor(pd.Series(all_2021_data['Consumption']))
257
-
258
  decider = Decider()
259
 
260
- results = simulator(battery_model, supplier, all_2021_data, prod_predictor, cons_predictor, decider)
261
 
262
  import matplotlib.pyplot as plt
263
  results['soc_series'].plot()
 
127
 
128
  # this function does not mutate its inputs.
129
  # it makes a clone of battery_model and modifies that.
130
+ def simulator(battery_model, supplier, prod_cons, decider):
131
  battery_model = copy.copy(battery_model)
132
 
133
  demand_np = prod_cons['Consumption'].to_numpy()
134
  production_np = prod_cons['Production'].to_numpy()
135
+ demand_prediction_np = prod_cons['Consumption_prediction'].to_numpy()
136
+ production_prediction_np = prod_cons['Production_prediction'].to_numpy()
137
  assert len(demand_np) == len(production_np)
138
  step_in_minutes = prod_cons.index.freq.n
139
  assert step_in_minutes == 5
 
172
  unsatisfied_demand = demand
173
  remaining_production = production
174
 
175
+ # TODO what to call it, demand or consumption?
176
+ # 1. sometimes demand is inappropriate, like consumption_from_solar vs demand_from_solar.
177
+ # 2. sometimes consumption is inappropriate, like unsatisfied_demand vs unsatisfied_consumption.
178
+ # 3. there should not be two of them.
179
+ prod_prediction = production_prediction_np[i: i + decider.input_window_size]
180
+ cons_prediction = demand_prediction_np[i: i + decider.input_window_size]
181
  decision = decider.decide(prod_prediction, cons_prediction, battery_model)
182
 
183
  production_used_to_charge = 0
 
252
 
253
  met_2021_data, cons_2021_data = read_datasets()
254
  add_production_field(met_2021_data, parameters)
255
+ all_data = interpolate_and_join(met_2021_data, cons_2021_data)
256
 
257
+ all_data_with_predictions = all_data.copy()
258
+
259
+ time_interval_min = all_data.index.freq.n
260
  time_interval_h = time_interval_min / 60
261
  battery_model = BatteryModel(capacity_Ah=600, time_interval_h=time_interval_h)
262
 
 
 
 
263
  decider = Decider()
264
 
265
+ results = simulator(battery_model, supplier, all_data_with_predictions, decider)
266
 
267
  import matplotlib.pyplot as plt
268
  results['soc_series'].plot()
v2/predictor.py ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import numpy as np
2
+ import pandas as pd
3
+ import matplotlib.pyplot as plt
4
+ from prophet import Prophet
5
+ import holidays
6
+ import logging
7
+ from sklearn.metrics import mean_absolute_error
8
+
9
+
10
+ # kW
11
+ PREDICTION_LOWER_BOUND = 0 # 15
12
+ print("do not forget about hardwired prediction lower bound", PREDICTION_LOWER_BOUND, "kW")
13
+
14
+ hungarian_holidays = holidays.Hungary(years=range(2019, 2031))
15
+ HOLIDAY_DF = pd.DataFrame(list(hungarian_holidays.items()), columns=['ds', 'holiday'])
16
+
17
+
18
+
19
+ def prophet_backend(train_data, forecast_horizon):
20
+ # Initialize and train the Prophet model using the training data
21
+ model = Prophet(seasonality_mode='multiplicative', growth='flat',
22
+ yearly_seasonality=False, weekly_seasonality=True, daily_seasonality=True,
23
+ holidays=HOLIDAY_DF)
24
+
25
+ # we can also play with setting daily_seasonality=False above, and then manually adding
26
+ # model.add_seasonality("daily", 1, fourier_order=10, prior_scale=100, mode="multiplicative")
27
+ # ...it didn't really work though. bumping the fourier_order helps, but makes the model slow.
28
+ # the rest didn't have much effect.
29
+
30
+ model.fit(train_data)
31
+
32
+ # Create a DataFrame with future timestamps for the evaluation period
33
+ future = model.make_future_dataframe(periods=forecast_horizon, freq='15T', include_history=False)
34
+
35
+ # Make predictions for the evaluation period
36
+ forecast = model.predict(future)
37
+ assert len(forecast) == forecast_horizon
38
+
39
+ for key in ('yhat', 'yhat_lower', 'yhat_upper'):
40
+ forecast[key] = np.maximum(forecast[key], PREDICTION_LOWER_BOUND)
41
+
42
+ return forecast, model
43
+
44
+
45
+ def prediction_task(backend, df, split_date, forecast_horizon):
46
+ # Split the data into training (past) and evaluation (future) sets
47
+ train_data = df[df['ds'] <= split_date]
48
+ eval_data = df[df['ds'] > split_date]
49
+ eval_data = eval_data.head(forecast_horizon)
50
+
51
+ forecast, model = backend(train_data, forecast_horizon)
52
+
53
+ mae = mean_absolute_error(eval_data['y'], forecast['yhat'])
54
+
55
+ do_vis = False
56
+ if do_vis:
57
+ future = model.make_future_dataframe(periods=forecast_horizon, freq='15T', include_history=True)
58
+ forecast = model.predict(future)
59
+
60
+ plt.figure(figsize=(12, 6))
61
+ plt.plot(eval_data['ds'], eval_data['y'], label='Actual', color='blue')
62
+ plt.plot(forecast['ds'], forecast['yhat'], label='Predicted', color='red')
63
+ plt.fill_between(forecast['ds'], forecast['yhat_lower'], forecast['yhat_upper'], color='pink', alpha=0.5, label='Uncertainty')
64
+ plt.xlabel('Timestamp')
65
+ plt.ylabel('Value')
66
+ plt.title('Actual vs. Predicted Values')
67
+ plt.legend()
68
+ plt.grid(True)
69
+ plt.show()
70
+
71
+ fig1 = model.plot(forecast)
72
+ plt.plot(eval_data['ds'], eval_data['y'], c='r')
73
+ plt.show()
74
+
75
+ fig2 = model.plot_components(forecast)
76
+ plt.show()
77
+ exit()
78
+
79
+ return mae, eval_data['y'].mean()
80
+
81
+
82
+ logger = logging.getLogger('cmdstanpy')
83
+ logger.addHandler(logging.NullHandler())
84
+ logger.propagate = False
85
+ logger.setLevel(logging.CRITICAL)
86
+
87
+
88
+
89
+ cons_filename = 'pq_terheles_2021_adatok.tsv'
90
+
91
+ df = pd.read_csv(cons_filename, sep='\t', skipinitialspace=True, na_values='n/a', decimal=',')
92
+ df['Time'] = pd.to_datetime(df['Korrigált időpont'], format='%m/%d/%y %H:%M')
93
+ df = df.set_index('Time')
94
+ df['Consumption'] = df['Hatásos teljesítmény [kW]']
95
+
96
+ df['ds'] = df.index
97
+ df['y'] = df['Consumption']
98
+
99
+
100
+ # TODO 15 minutes timestep hardwired!
101
+ forecast_horizon = 24 * 4
102
+ print("forecast horizon", forecast_horizon // 4, "hours")
103
+
104
+
105
+ start_date = '2021-06-01'
106
+ end_date = '2021-10-24'
107
+
108
+ weekly_date_range = pd.date_range(start=start_date, end=end_date, freq='8d')
109
+
110
+
111
+ maes = []
112
+ mean_values = []
113
+ for split_date in weekly_date_range:
114
+ # prophet_backend is the only backend currently
115
+ mae, mean_value = prediction_task(prophet_backend, df, split_date, forecast_horizon)
116
+ maes.append(mae)
117
+ mean_values.append(mean_value)
118
+ print(split_date, "Mean Absolute Error", mae, "MAE/true mean", mae / mean_value)
119
+
120
+ maes = np.array(maes)
121
+ mean_values = np.array(mean_values)
122
+ aggregate_mae = maes.mean()
123
+ print("Mean Absolute Error over whole date range", weekly_date_range[0], "-", weekly_date_range[-1], ":", aggregate_mae)
124
+ print("Mean Absolute Error / true mean over whole date range", aggregate_mae / mean_values.mean())