Daniel Varga commited on
Commit
c5bd69c
1 Parent(s): ffa5234

does something end-to-end

Browse files
data_processing.py CHANGED
@@ -43,7 +43,7 @@ def read_datasets(mini=False):
43
 
44
  @dataclass
45
  class Parameters:
46
- solar_cell_num: float = 114 # units
47
  solar_efficiency: float = 0.93 * 0.96 # [dimensionless]
48
  NOCT: float = 280 # [W]
49
  NOCT_irradiation: float = 800 # [W/m^2]
 
43
 
44
  @dataclass
45
  class Parameters:
46
+ solar_cell_num: float = 1140 # units
47
  solar_efficiency: float = 0.93 * 0.96 # [dimensionless]
48
  NOCT: float = 280 # [W]
49
  NOCT_irradiation: float = 800 # [W/m^2]
v2/architecture.py CHANGED
@@ -14,6 +14,7 @@ from evolution_strategies import evolution_strategies_optimizer
14
 
15
  DO_VIS = False
16
 
 
17
  # we predict last week same time for consumption, and yesterday same time for production.
18
  # adds fields in-place
19
  def add_dummy_predictions(all_data_with_predictions):
@@ -52,6 +53,29 @@ def simulator(battery_model, prod_cons, decider):
52
  step_in_minutes = prod_cons.index.freq.n
53
  assert step_in_minutes == 5
54
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
55
  consumption_fees_np = prod_cons['Consumption_fees'].to_numpy()
56
 
57
  print("Simulating for", len(demand_np), "time steps. Each step is", step_in_minutes, "minutes.")
@@ -157,7 +181,7 @@ def simulator(battery_model, prod_cons, decider):
157
  fifteen_minute_surdemands_in_kwh = (fifteen_minute_demands_in_kwh - decider.precalculated_supplier.peak_demand).clip(lower=0)
158
  demand_charges = fifteen_minute_surdemands_in_kwh * decider.precalculated_supplier.surcharge_per_kwh
159
  total_network_fee = consumption_charge_series.sum() + demand_charges.sum()
160
- print(f"All in all we have paid the network {total_network_fee / 10 ** 6} MHUF.")
161
 
162
  if DO_VIS:
163
  demand_charges.plot()
@@ -176,17 +200,21 @@ def simulator(battery_model, prod_cons, decider):
176
  return results, total_network_fee
177
 
178
 
 
179
  def optimizer(battery_model, all_data_with_predictions, precalculated_supplier):
180
  def objective_function(params):
 
181
  # TODO params completely ignored right now.
182
- decider = Decider(precalculated_supplier)
183
  t = time.perf_counter()
184
  results, total_network_fee = simulator(battery_model, all_data_with_predictions, decider)
185
  return total_network_fee
186
 
187
- init_mean = np.array([0.0, 0.0])
188
- init_scale = np.array([10.0, 10.0])
189
- best_params = evolution_strategies_optimizer(objective_function, init_mean=init_mean, init_scale=init_scale)
 
 
190
 
191
 
192
  def main():
@@ -199,12 +227,14 @@ def main():
199
  # during a 15 minute timestep.
200
  supplier.set_demand_charge(peak_demand=2.5, surcharge_per_kwh=500) # kWh in a 15 minutes interval, Ft/kWh
201
 
202
- parameters = SolarParameters()
203
 
204
  met_2021_data, cons_2021_data = read_datasets()
205
- add_production_field(met_2021_data, parameters)
206
  all_data = interpolate_and_join(met_2021_data, cons_2021_data)
207
 
 
 
208
  time_interval_min = all_data.index.freq.n
209
  time_interval_h = time_interval_min / 60
210
 
@@ -222,7 +252,8 @@ def main():
222
  # TODO this is super unfortunate:
223
  # Consumption_fees travels via all_data_with_predictions,
224
  # peak_demand and surcharge_per_kwh travels via precalculated_supplier of decider.
225
- decider = Decider(precalculated_supplier)
 
226
 
227
  t = time.perf_counter()
228
  results, total_network_fee = simulator(battery_model, all_data_with_predictions, decider)
 
14
 
15
  DO_VIS = False
16
 
17
+
18
  # we predict last week same time for consumption, and yesterday same time for production.
19
  # adds fields in-place
20
  def add_dummy_predictions(all_data_with_predictions):
 
53
  step_in_minutes = prod_cons.index.freq.n
54
  assert step_in_minutes == 5
55
 
56
+ '''
57
+ surdemand_np = demand_np - production_np
58
+ plt.plot(demand_np, c="r")
59
+ plt.plot(production_np, c="g")
60
+ plt.plot(surdemand_np, c="b")
61
+ plt.show()
62
+ print("max prod", production_np.max())
63
+ exit()
64
+ '''
65
+
66
+ '''
67
+ surdemand_window = 24 * 60 // 5 # one day
68
+ mean_surdemands_kw = []
69
+ for i in range(len(demand_prediction_np) - surdemand_window):
70
+ mean_surdemand_kw = (demand_prediction_np[i: i+surdemand_window] - production_prediction_np[i: i+surdemand_window]).mean()
71
+ mean_surdemands_kw.append(mean_surdemand_kw)
72
+ mean_surdemands_kw = pd.Series(mean_surdemands_kw, prod_cons.index[:len(mean_surdemands_kw)])
73
+ mean_surdemands_kw.plot()
74
+ plt.title("mean_surdemands_kw")
75
+ plt.show()
76
+ exit()
77
+ '''
78
+
79
  consumption_fees_np = prod_cons['Consumption_fees'].to_numpy()
80
 
81
  print("Simulating for", len(demand_np), "time steps. Each step is", step_in_minutes, "minutes.")
 
181
  fifteen_minute_surdemands_in_kwh = (fifteen_minute_demands_in_kwh - decider.precalculated_supplier.peak_demand).clip(lower=0)
182
  demand_charges = fifteen_minute_surdemands_in_kwh * decider.precalculated_supplier.surcharge_per_kwh
183
  total_network_fee = consumption_charge_series.sum() + demand_charges.sum()
184
+ print(f"Total network fee {total_network_fee / 10 ** 6} MHUF.")
185
 
186
  if DO_VIS:
187
  demand_charges.plot()
 
200
  return results, total_network_fee
201
 
202
 
203
+
204
  def optimizer(battery_model, all_data_with_predictions, precalculated_supplier):
205
  def objective_function(params):
206
+ print("Simulating with parameters", params)
207
  # TODO params completely ignored right now.
208
+ decider = Decider(params, precalculated_supplier)
209
  t = time.perf_counter()
210
  results, total_network_fee = simulator(battery_model, all_data_with_predictions, decider)
211
  return total_network_fee
212
 
213
+ def clipper_function(params):
214
+ return Decider.clip_params(params)
215
+
216
+ init_mean, init_scale = Decider.initial_params()
217
+ best_params = evolution_strategies_optimizer(objective_function, clipper_function, init_mean=init_mean, init_scale=init_scale)
218
 
219
 
220
  def main():
 
227
  # during a 15 minute timestep.
228
  supplier.set_demand_charge(peak_demand=2.5, surcharge_per_kwh=500) # kWh in a 15 minutes interval, Ft/kWh
229
 
230
+ solar_parameters = SolarParameters()
231
 
232
  met_2021_data, cons_2021_data = read_datasets()
233
+ add_production_field(met_2021_data, solar_parameters)
234
  all_data = interpolate_and_join(met_2021_data, cons_2021_data)
235
 
236
+ print("Working with", solar_parameters.solar_cell_num, "solar cells, that's a maximum production of", all_data['Production'].max(), "kW.")
237
+
238
  time_interval_min = all_data.index.freq.n
239
  time_interval_h = time_interval_min / 60
240
 
 
252
  # TODO this is super unfortunate:
253
  # Consumption_fees travels via all_data_with_predictions,
254
  # peak_demand and surcharge_per_kwh travels via precalculated_supplier of decider.
255
+ decider_init_mean, decider_init_scale = Decider.initial_params()
256
+ decider = Decider(decider_init_mean, precalculated_supplier)
257
 
258
  t = time.perf_counter()
259
  results, total_network_fee = simulator(battery_model, all_data_with_predictions, decider)
v2/data_processing.py CHANGED
@@ -44,7 +44,7 @@ def read_datasets(mini=False):
44
  # BESS parameters are now in BatteryModel
45
  @dataclass
46
  class SolarParameters:
47
- solar_cell_num: float = 114 # units
48
  solar_efficiency: float = 0.93 * 0.96 # [dimensionless]
49
  NOCT: float = 280 # [W]
50
  NOCT_irradiation: float = 800 # [W/m^2]
 
44
  # BESS parameters are now in BatteryModel
45
  @dataclass
46
  class SolarParameters:
47
+ solar_cell_num: float = 1140 # units
48
  solar_efficiency: float = 0.93 * 0.96 # [dimensionless]
49
  NOCT: float = 280 # [W]
50
  NOCT_irradiation: float = 800 # [W/m^2]
v2/decider.py CHANGED
@@ -1,5 +1,5 @@
1
  from enum import IntEnum
2
-
3
 
4
 
5
  STEPS_PER_HOUR = 12
@@ -28,10 +28,16 @@ class Decision(IntEnum):
28
  # mock class as usual
29
  # output_window_size is not yet used, always decides one timestep.
30
  class Decider:
31
- def __init__(self, precalculated_supplier):
32
  self.input_window_size = STEPS_PER_HOUR * 24 # day long window.
33
  self.random_seed = 0
34
  self.precalculated_supplier = precalculated_supplier
 
 
 
 
 
 
35
 
36
  def decide(self, prod_pred, cons_pred, fees, battery_model):
37
  # TODO 15 minutes demand charge window hardwired at this weird place
@@ -42,9 +48,38 @@ class Decider:
42
  step_in_hour = time_interval_min / 60 # [hour], the length of a time step.
43
  deficit_kw = (cons_pred[:peak_shaving_window] - prod_pred[:peak_shaving_window]).clip(min=0)
44
  deficit_kwh = (step_in_hour * deficit_kw).sum()
 
 
 
 
 
 
 
 
 
 
 
45
  if deficit_kwh > self.precalculated_supplier.peak_demand:
46
  return Decision.DISCHARGE
47
  else:
48
  return Decision.PASSIVE
49
 
 
 
 
 
 
 
 
 
 
 
50
 
 
 
 
 
 
 
 
 
 
1
  from enum import IntEnum
2
+ import numpy as np
3
 
4
 
5
  STEPS_PER_HOUR = 12
 
28
  # mock class as usual
29
  # output_window_size is not yet used, always decides one timestep.
30
  class Decider:
31
+ def __init__(self, params, precalculated_supplier):
32
  self.input_window_size = STEPS_PER_HOUR * 24 # day long window.
33
  self.random_seed = 0
34
  self.precalculated_supplier = precalculated_supplier
35
+ assert params.shape == (2, )
36
+ # param_1 is how many minutes do we look ahead to decide if there's
37
+ # an upcoming shortage.
38
+ # param_2 is the threshhold for shortage, in kW not kWh,
39
+ # that is, parametrized as an average over the window.
40
+ self.surdemand_lookahead_window_min, self.lookahead_surdemand_kw = params
41
 
42
  def decide(self, prod_pred, cons_pred, fees, battery_model):
43
  # TODO 15 minutes demand charge window hardwired at this weird place
 
48
  step_in_hour = time_interval_min / 60 # [hour], the length of a time step.
49
  deficit_kw = (cons_pred[:peak_shaving_window] - prod_pred[:peak_shaving_window]).clip(min=0)
50
  deficit_kwh = (step_in_hour * deficit_kw).sum()
51
+
52
+ surdemand_window = int(self.surdemand_lookahead_window_min // time_interval_min)
53
+ mean_surdemand_kw = (cons_pred[:surdemand_window] - prod_pred[:surdemand_window]).mean()
54
+
55
+ current_fee = fees[0]
56
+ # TODO this should not be a hard threashold, and more importantly,
57
+ # it should not be hardwired.
58
+ HARDWIRED_THRESHOLD_FOR_CHEAP_POWER_HUF_PER_KWH = 20 # [HUF/kWh]
59
+ if mean_surdemand_kw > self.lookahead_surdemand_kw and current_fee <= HARDWIRED_THRESHOLD_FOR_CHEAP_POWER_HUF_PER_KWH:
60
+ return Decision.NETWORK_CHARGE
61
+
62
  if deficit_kwh > self.precalculated_supplier.peak_demand:
63
  return Decision.DISCHARGE
64
  else:
65
  return Decision.PASSIVE
66
 
67
+ # this is called by the optimizer so that meaningless parameter settings are not attempted
68
+ # we could vectorize this easily, but it's not a bottleneck, the simulation is.
69
+ @staticmethod
70
+ def clip_params(params):
71
+ assert params.shape == (2, )
72
+ surdemand_lookahead_window_min, lookahead_surdemand_kw = params
73
+ surdemand_lookahead_window_min = np.clip(surdemand_lookahead_window_min, 5, 60 * 24 * 3)
74
+ # no-op right now:
75
+ lookahead_surdemand_kw = np.clip(lookahead_surdemand_kw, -np.inf, np.inf)
76
+ return np.array([surdemand_lookahead_window_min, lookahead_surdemand_kw])
77
 
78
+ @staticmethod
79
+ def initial_params():
80
+ # surdemand_lookahead_window_min, lookahead_surdemand_kw
81
+ # param1 [minutes]. one day mean, half day scale for lookahead horizon.
82
+ # param2 [kWh], averaged over the horizon. negative values are meaningful.
83
+ init_mean = np.array([1440.0, 0.0])
84
+ init_scale = np.array([720.0, 100.0])
85
+ return init_mean, init_scale
v2/evolution_strategies.py CHANGED
@@ -1,7 +1,13 @@
1
  import numpy as np
2
 
3
 
4
- def evolution_strategies_optimizer(objective_function, init_mean, init_scale):
 
 
 
 
 
 
5
  # Initialize parameters
6
  population_size = 100
7
  number_of_generations = 30
@@ -11,6 +17,7 @@ def evolution_strategies_optimizer(objective_function, init_mean, init_scale):
11
 
12
  # Initialize population (randomly)
13
  population = np.random.normal(loc=init_mean, scale=init_scale, size=(population_size, len(init_mean)))
 
14
 
15
  for generation in range(number_of_generations):
16
  # Evaluate fitness
@@ -22,6 +29,7 @@ def evolution_strategies_optimizer(objective_function, init_mean, init_scale):
22
 
23
  # Reproduce (mutate)
24
  offspring = selected + np.random.randn(selected_size, 2) * mutation_scale
 
25
 
26
  # Replacement: Here we simply generate new candidates around the selected ones
27
  population[:selected_size] = selected
@@ -45,10 +53,14 @@ def toy_objective_function(x):
45
  return (x[0] - 3)**2 + (x[1] + 2)**2
46
 
47
 
 
 
 
 
48
  def main():
49
  init_mean = np.array([0.0, 0.0])
50
  init_scale = np.array([10.0, 10.0])
51
- best_solution = evolution_strategies_optimizer(toy_objective_function, init_mean, init_scale)
52
 
53
 
54
  if __name__ == '__main__':
 
1
  import numpy as np
2
 
3
 
4
+ # in-place
5
+ def clip_params(population, clipper_function):
6
+ for i, individual in enumerate(population):
7
+ population[i] = clipper_function(individual)
8
+
9
+
10
+ def evolution_strategies_optimizer(objective_function, clipper_function, init_mean, init_scale):
11
  # Initialize parameters
12
  population_size = 100
13
  number_of_generations = 30
 
17
 
18
  # Initialize population (randomly)
19
  population = np.random.normal(loc=init_mean, scale=init_scale, size=(population_size, len(init_mean)))
20
+ clip_params(population, clipper_function)
21
 
22
  for generation in range(number_of_generations):
23
  # Evaluate fitness
 
29
 
30
  # Reproduce (mutate)
31
  offspring = selected + np.random.randn(selected_size, 2) * mutation_scale
32
+ clip_params(offspring, clipper_function) # in-place
33
 
34
  # Replacement: Here we simply generate new candidates around the selected ones
35
  population[:selected_size] = selected
 
53
  return (x[0] - 3)**2 + (x[1] + 2)**2
54
 
55
 
56
+ def toy_clipper_function(x):
57
+ return x
58
+
59
+
60
  def main():
61
  init_mean = np.array([0.0, 0.0])
62
  init_scale = np.array([10.0, 10.0])
63
+ best_solution = evolution_strategies_optimizer(toy_objective_function, toy_clipper_function, init_mean, init_scale)
64
 
65
 
66
  if __name__ == '__main__':