Daniel Varga commited on
Commit
e75e97c
·
1 Parent(s): fee109b

evolution strategies

Browse files
Files changed (2) hide show
  1. v2/architecture.py +39 -24
  2. v2/evolution_strategies.py +55 -0
v2/architecture.py CHANGED
@@ -8,7 +8,7 @@ import matplotlib.pyplot as plt
8
  # it's really just a network pricing model
9
  from supplier import Supplier, precalculate_supplier
10
  from data_processing import read_datasets, add_production_field, interpolate_and_join, SolarParameters
11
-
12
 
13
  DO_VIS = False
14
 
@@ -103,18 +103,13 @@ class Decider:
103
  self.random_seed = 0
104
  self.precalculated_supplier = precalculated_supplier
105
 
106
- # prod_cons_pred is a dataframe starting at now, containing
107
- # fields Production and Consumption.
108
- # this function does not mutate its inputs.
109
- # battery_model is just queried for capacity and current soc.
110
- # the method returns a pd.Series of Decisions as integers.
111
  def decide(self, prod_pred, cons_pred, fees, battery_model):
112
- return Decision.PASSIVE
113
- # 15 minutes demand charge window divided by 5 minute timestep, TODO make it more principled
114
- peak_shaving_window = 3
115
-
116
- # TODO is there a kWh kW confusion here?:
117
- step_in_hour = self.precalculated_supplier.time_index.freq.n / 60 # [hour], the length of a time step.
118
  deficit_kw = (cons_pred[:peak_shaving_window] - prod_pred[:peak_shaving_window]).clip(min=0)
119
  deficit_kwh = (step_in_hour * deficit_kw).sum()
120
  if deficit_kwh > self.precalculated_supplier.peak_demand:
@@ -123,6 +118,19 @@ class Decider:
123
  return Decision.PASSIVE
124
 
125
 
 
 
 
 
 
 
 
 
 
 
 
 
 
126
  # even mock-er class than usual.
127
  # knows the future in advance, so it predicts it very well.
128
  # it's also unrealistic in that it takes row index instead of date.
@@ -252,8 +260,8 @@ def simulator(battery_model, prod_cons, decider):
252
  fifteen_minute_demands_in_kwh = consumption_from_network_pandas_series.resample('15T').sum() * step_in_hour
253
  fifteen_minute_surdemands_in_kwh = (fifteen_minute_demands_in_kwh - decider.precalculated_supplier.peak_demand).clip(lower=0)
254
  demand_charges = fifteen_minute_surdemands_in_kwh * decider.precalculated_supplier.surcharge_per_kwh
255
- total_charge = consumption_charge_series.sum() + demand_charges.sum()
256
- print(f"All in all we have paid the network {total_charge / 10 ** 6} MHUF.")
257
 
258
  if DO_VIS:
259
  demand_charges.plot()
@@ -269,7 +277,20 @@ def simulator(battery_model, prod_cons, decider):
269
  'Production': prod_cons['Production']
270
  })
271
  results = results.set_index(prod_cons.index)
272
- return results
 
 
 
 
 
 
 
 
 
 
 
 
 
273
 
274
 
275
  def main():
@@ -292,15 +313,8 @@ def main():
292
  print("time_interval_min", time_interval_min)
293
  time_interval_h = time_interval_min / 60
294
 
295
- # we predict last week same time for consumption, and yesterday same time for production.
296
  all_data_with_predictions = all_data.copy()
297
- cons_shift = 60 * 168 // time_interval_min
298
- prod_shift = 60 * 24 // time_interval_min
299
- all_data_with_predictions['Consumption_prediction'] = all_data_with_predictions['Consumption'].shift(periods=cons_shift)
300
- all_data_with_predictions['Production_prediction'] = all_data_with_predictions['Production'].shift(periods=prod_shift)
301
- # we predict zero before we have data, no big deal:
302
- all_data_with_predictions['Consumption_prediction'][:cons_shift] = 0
303
- all_data_with_predictions['Production_prediction'][:prod_shift] = 0
304
 
305
  precalculated_supplier = precalculate_supplier(supplier, all_data.index)
306
  # we delete the supplier to avoid accidentally calling it instead of precalculated_supplier
@@ -316,7 +330,7 @@ def main():
316
  decider = Decider(precalculated_supplier)
317
 
318
  t = time.perf_counter()
319
- results = simulator(battery_model, all_data_with_predictions, decider)
320
  print("Simulation runtime", time.perf_counter() - t, "seconds.")
321
 
322
  if DO_VIS:
@@ -324,6 +338,7 @@ def main():
324
  plt.title('soc_series')
325
  plt.show()
326
 
 
327
 
328
  if __name__ == '__main__':
329
  main()
 
8
  # it's really just a network pricing model
9
  from supplier import Supplier, precalculate_supplier
10
  from data_processing import read_datasets, add_production_field, interpolate_and_join, SolarParameters
11
+ from evolution_strategies import evolution_strategies_optimizer
12
 
13
  DO_VIS = False
14
 
 
103
  self.random_seed = 0
104
  self.precalculated_supplier = precalculated_supplier
105
 
 
 
 
 
 
106
  def decide(self, prod_pred, cons_pred, fees, battery_model):
107
+ # TODO 15 minutes demand charge window hardwired at this weird place
108
+ DEMAND_CHARGE_WINDOW_MIN = 15
109
+ time_interval_min = self.precalculated_supplier.time_index.freq.n
110
+ assert DEMAND_CHARGE_WINDOW_MIN % time_interval_min == 0
111
+ peak_shaving_window = DEMAND_CHARGE_WINDOW_MIN // time_interval_min
112
+ step_in_hour = time_interval_min / 60 # [hour], the length of a time step.
113
  deficit_kw = (cons_pred[:peak_shaving_window] - prod_pred[:peak_shaving_window]).clip(min=0)
114
  deficit_kwh = (step_in_hour * deficit_kw).sum()
115
  if deficit_kwh > self.precalculated_supplier.peak_demand:
 
118
  return Decision.PASSIVE
119
 
120
 
121
+ # we predict last week same time for consumption, and yesterday same time for production.
122
+ # adds fields in-place
123
+ def add_dummy_predictions(all_data_with_predictions):
124
+ time_interval_min = all_data_with_predictions.index.freq.n
125
+ cons_shift = 60 * 168 // time_interval_min
126
+ prod_shift = 60 * 24 // time_interval_min
127
+ all_data_with_predictions['Consumption_prediction'] = all_data_with_predictions['Consumption'].shift(periods=cons_shift)
128
+ all_data_with_predictions['Production_prediction'] = all_data_with_predictions['Production'].shift(periods=prod_shift)
129
+ # we predict zero before we have data, no big deal:
130
+ all_data_with_predictions['Consumption_prediction'][:cons_shift] = 0
131
+ all_data_with_predictions['Production_prediction'][:prod_shift] = 0
132
+
133
+
134
  # even mock-er class than usual.
135
  # knows the future in advance, so it predicts it very well.
136
  # it's also unrealistic in that it takes row index instead of date.
 
260
  fifteen_minute_demands_in_kwh = consumption_from_network_pandas_series.resample('15T').sum() * step_in_hour
261
  fifteen_minute_surdemands_in_kwh = (fifteen_minute_demands_in_kwh - decider.precalculated_supplier.peak_demand).clip(lower=0)
262
  demand_charges = fifteen_minute_surdemands_in_kwh * decider.precalculated_supplier.surcharge_per_kwh
263
+ total_network_fee = consumption_charge_series.sum() + demand_charges.sum()
264
+ print(f"All in all we have paid the network {total_network_fee / 10 ** 6} MHUF.")
265
 
266
  if DO_VIS:
267
  demand_charges.plot()
 
277
  'Production': prod_cons['Production']
278
  })
279
  results = results.set_index(prod_cons.index)
280
+ return results, total_network_fee
281
+
282
+
283
+ def optimizer(battery_model, all_data_with_predictions, precalculated_supplier):
284
+ def objective_function(params):
285
+ # TODO params completely ignored right now.
286
+ decider = Decider(precalculated_supplier)
287
+ t = time.perf_counter()
288
+ results, total_network_fee = simulator(battery_model, all_data_with_predictions, decider)
289
+ return total_network_fee
290
+
291
+ init_mean = np.array([0.0, 0.0])
292
+ init_scale = np.array([10.0, 10.0])
293
+ best_params = evolution_strategies_optimizer(objective_function, init_mean=init_mean, init_scale=init_scale)
294
 
295
 
296
  def main():
 
313
  print("time_interval_min", time_interval_min)
314
  time_interval_h = time_interval_min / 60
315
 
 
316
  all_data_with_predictions = all_data.copy()
317
+ add_dummy_predictions(all_data_with_predictions)
 
 
 
 
 
 
318
 
319
  precalculated_supplier = precalculate_supplier(supplier, all_data.index)
320
  # we delete the supplier to avoid accidentally calling it instead of precalculated_supplier
 
330
  decider = Decider(precalculated_supplier)
331
 
332
  t = time.perf_counter()
333
+ results, total_network_fee = simulator(battery_model, all_data_with_predictions, decider)
334
  print("Simulation runtime", time.perf_counter() - t, "seconds.")
335
 
336
  if DO_VIS:
 
338
  plt.title('soc_series')
339
  plt.show()
340
 
341
+ optimizer(battery_model, all_data_with_predictions, precalculated_supplier)
342
 
343
  if __name__ == '__main__':
344
  main()
v2/evolution_strategies.py ADDED
@@ -0,0 +1,55 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
8
+ mutation_scale = 0.1
9
+ selection_ratio = 0.5
10
+ selected_size = int(population_size * selection_ratio)
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
17
+ fitness = np.array([objective_function(individual) for individual in population])
18
+
19
+ # Select the best individuals
20
+ selected_indices = np.argsort(fitness)[:selected_size]
21
+ selected = population[selected_indices]
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
28
+ population[selected_size:] = offspring
29
+
30
+ # Logging
31
+ best_fitness = fitness[selected_indices[0]]
32
+ best_index = np.argmin(fitness)
33
+ best_solution = population[best_index]
34
+ print(f"Generation {generation + 1}: Best Fitness = {best_fitness}", f"Best solution so far: {best_solution}")
35
+
36
+
37
+ # Best solution
38
+ best_index = np.argmin(fitness)
39
+ best_solution = population[best_index]
40
+ print(f"Best solution found: {best_solution}")
41
+ return best_solution
42
+
43
+
44
+ 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__':
55
+ main()