Daniel Varga commited on
Commit
dd725fc
1 Parent(s): 0c694ac

simulator with dummy decider and dummy predictors does something

Browse files
Files changed (1) hide show
  1. v2/architecture.py +68 -33
v2/architecture.py CHANGED
@@ -64,40 +64,45 @@ class Decision(IntEnum):
64
 
65
 
66
  # mock class as usual
 
67
  class Decider:
68
  def __init__(self):
69
  self.parameters = None
70
  self.input_window_size = STEPS_PER_HOUR * 24 # day long window.
71
  self.output_window_size = STEPS_PER_HOUR # only output decisions for the next hour
 
72
 
73
  # prod_cons_pred is a dataframe starting at now, containing
74
  # fields Production and Consumption.
75
  # this function does not mutate its inputs.
76
  # battery_model is just queried for capacity and current soc.
77
  # the method returns a pd.Series of Decisions as integers.
78
- def decide(self, prod_cons_pred, battery_model):
79
- assert len(prod_cons_pred) == self.input_window_size
 
 
 
80
  # dummy decider always says DISCHARGE:
81
- return pd.Series([Decision.DISCHARGE] * STEPS_PER_HOUR, dtype=int)
82
 
83
 
84
- # this function does not mutate its inputs.
85
- # it makes a clone of battery_model and modifies that.
86
- # it returns
87
- def simulator(battery_model, supplier, prod_cons, decider):
88
- battery_model = copy.copy(battery_model)
89
- for indx, date in enumerate(prod_cons.index):
90
- if indx + decider.input_window_size > len(prod_cons):
91
- break
92
- # not really a prediction obviously
93
- prod_cons_pred = prod_cons.iloc[indx: indx + decider.input_window_size]
94
- decisions = decider.decide(prod_cons_pred, battery_model)
95
- return None
96
 
97
 
 
 
98
  # TODO even in a first mockup version, parameters should come from a single
99
  # place, not some from the parameters dataclass and some from the battery_model.
100
- def simulator(battery_model, supplier, prod_cons, decider, parameters):
101
  battery_model = copy.copy(battery_model)
102
 
103
  demand_np = prod_cons['Consumption'].to_numpy()
@@ -114,8 +119,9 @@ def simulator(battery_model, supplier, prod_cons, decider, parameters):
114
  # in our simple model, demand is always satisfied, hence demand=consumption.
115
  # BESS demand is called charge.
116
  consumption_from_solar_series = [] # demand satisfied by solar production
117
- consumption_from_network_series = [] # demand satisfied by network
118
  consumption_from_bess_series = [] # demand satisfied by BESS
 
119
  # the previous three must sum to demand_series.
120
 
121
  # power taken from solar by BESS.
@@ -132,37 +138,39 @@ def simulator(battery_model, supplier, prod_cons, decider, parameters):
132
  # and will use only maximal_depth_of_discharge percent of the real battery capacity
133
 
134
  max_cap_of_battery = parameters.bess_capacity * parameters.maximal_depth_of_discharge
135
- cap_of_battery = soc * max_cap_of_battery
136
 
137
  time_interval = step_in_minutes / 60 # amount of time step in hours
138
  for i, (demand, production) in enumerate(zip(demand_np, production_np)):
139
-
140
  # these five are modified on the appropriate codepaths:
141
  consumption_from_solar = 0
142
  consumption_from_bess = 0
143
  consumption_from_network = 0
144
  discarded_production = 0
 
145
 
146
  unsatisfied_demand = demand
147
  remaining_production = production
148
 
149
- if parameters.bess_present:
150
- is_battery_charged_enough = battery_model.soc > 0
151
- is_battery_chargeable = battery_model.soc < 1.0
152
- else:
153
- is_battery_charged_enough = battery_model.soc <= 0
154
- is_battery_chargeable = battery_model.soc >= 1.0
 
155
 
 
156
  if unsatisfied_demand >= remaining_production:
157
  # all goes to demand
158
  consumption_from_solar = remaining_production
159
  unsatisfied_demand -= consumption_from_solar
160
  remaining_production = 0
161
  # we try to cover the rest from BESS
162
- if (unsatisfied_demand > 0 ) and parameters.bess_present:
163
- if is_battery_charged_enough:
164
  # battery capacity is limited!
165
- if cap_of_battery >= unsatisfied_demand * time_interval :
166
  consumption_from_bess = unsatisfied_demand
167
  unsatisfied_demand = 0
168
  cap_of_battery -= consumption_from_bess * time_interval
@@ -185,14 +193,16 @@ def simulator(battery_model, supplier, prod_cons, decider, parameters):
185
  consumption_from_solar = unsatisfied_demand
186
  remaining_production -= unsatisfied_demand
187
  unsatisfied_demand = 0
188
- if (remaining_production > 0) and parameters.bess_present:
189
  # exploitable production still remains:
190
  if is_battery_chargeable:
191
  # we try to specify the BESS modell
192
  if parameters.bess_charge <= remaining_production :
193
  energy = parameters.bess_charge * time_interval
194
  remaining_production = remaining_production - parameters.bess_charge
 
195
  else :
 
196
  energy = remaining_production * time_interval
197
  remaining_production = 0
198
  cap_of_battery += energy
@@ -200,9 +210,30 @@ def simulator(battery_model, supplier, prod_cons, decider, parameters):
200
 
201
  discarded_production = remaining_production
202
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
203
  soc_series.append(battery_model.soc)
204
  consumption_from_solar_series.append(consumption_from_solar)
205
  consumption_from_network_series.append(consumption_from_network)
 
206
  consumption_from_bess_series.append(consumption_from_bess)
207
  discarded_production_series.append(discarded_production)
208
 
@@ -215,11 +246,12 @@ def simulator(battery_model, supplier, prod_cons, decider, parameters):
215
  results = pd.DataFrame({'soc_series': soc_series, 'consumption_from_solar': consumption_from_solar_series,
216
  'consumption_from_network': consumption_from_network_series,
217
  'consumption_from_bess': consumption_from_bess_series,
 
218
  'discarded_production': discarded_production_series,
219
- 'Consumption': all_data['Consumption'],
220
- 'Production': all_data['Production']
221
  })
222
- results = results.set_index(all_data.index)
223
  return results
224
 
225
 
@@ -235,9 +267,12 @@ def main():
235
  add_production_field(met_2021_data, parameters)
236
  all_2021_data = interpolate_and_join(met_2021_data, cons_2021_data)
237
 
 
 
 
238
  decider = Decider()
239
 
240
- results = simulator(battery_model, supplier, all_2021_data, decider, parameters)
241
 
242
 
243
  if __name__ == '__main__':
 
64
 
65
 
66
  # mock class as usual
67
+ # output_window_size is not yet used, always decides one timestep.
68
  class Decider:
69
  def __init__(self):
70
  self.parameters = None
71
  self.input_window_size = STEPS_PER_HOUR * 24 # day long window.
72
  self.output_window_size = STEPS_PER_HOUR # only output decisions for the next hour
73
+ self.random_seed = 0
74
 
75
  # prod_cons_pred is a dataframe starting at now, containing
76
  # fields Production and Consumption.
77
  # this function does not mutate its inputs.
78
  # battery_model is just queried for capacity and current soc.
79
  # the method returns a pd.Series of Decisions as integers.
80
+ def decide(self, prod_pred, cons_pred, battery_model):
81
+ # assert len(prod_pred) == len(cons_pred) == self.input_window_size
82
+ self.random_seed += 1
83
+ self.random_seed %= 3 # dummy rotates between 3 Decisions
84
+ return self.random_seed
85
  # dummy decider always says DISCHARGE:
86
+ # return pd.Series([Decision.DISCHARGE] * self.output_window_size, dtype=int)
87
 
88
 
89
+ # even mock-er class than usual.
90
+ # knows the future in advance, so it predicts it very well.
91
+ # it's also unrealistic in that it takes row index instead of date.
92
+ class DummyPredictor:
93
+ def __init__(self, series):
94
+ self.series = series
95
+
96
+ def predict(self, indx, window_size):
97
+ prediction = self.series.iloc[indx: indx + window_size]
98
+ return prediction
 
 
99
 
100
 
101
+ # this function does not mutate its inputs.
102
+ # it makes a clone of battery_model and modifies that.
103
  # TODO even in a first mockup version, parameters should come from a single
104
  # place, not some from the parameters dataclass and some from the battery_model.
105
+ def simulator(battery_model, supplier, prod_cons, prod_predictor, cons_predictor, decider, parameters):
106
  battery_model = copy.copy(battery_model)
107
 
108
  demand_np = prod_cons['Consumption'].to_numpy()
 
119
  # in our simple model, demand is always satisfied, hence demand=consumption.
120
  # BESS demand is called charge.
121
  consumption_from_solar_series = [] # demand satisfied by solar production
122
+ consumption_from_network_series = [] # full demand satisfied by network, also includes consumption_from_network_to_bess.
123
  consumption_from_bess_series = [] # demand satisfied by BESS
124
+ consumption_from_network_to_bess_series = [] # network used to charge BESS, also included in consumption_from_network.
125
  # the previous three must sum to demand_series.
126
 
127
  # power taken from solar by BESS.
 
138
  # and will use only maximal_depth_of_discharge percent of the real battery capacity
139
 
140
  max_cap_of_battery = parameters.bess_capacity * parameters.maximal_depth_of_discharge
 
141
 
142
  time_interval = step_in_minutes / 60 # amount of time step in hours
143
  for i, (demand, production) in enumerate(zip(demand_np, production_np)):
144
+ cap_of_battery = battery_model.soc * max_cap_of_battery
145
  # these five are modified on the appropriate codepaths:
146
  consumption_from_solar = 0
147
  consumption_from_bess = 0
148
  consumption_from_network = 0
149
  discarded_production = 0
150
+ network_used_to_charge = 0
151
 
152
  unsatisfied_demand = demand
153
  remaining_production = production
154
 
155
+ assert parameters.bess_present
156
+ is_battery_charged_enough = battery_model.soc > 0
157
+ is_battery_chargeable = battery_model.soc < 1.0
158
+
159
+ prod_prediction = prod_predictor.predict(i, decider.input_window_size)
160
+ cons_prediction = cons_predictor.predict(i, decider.input_window_size)
161
+ decision = decider.decide(prod_prediction, cons_prediction, battery_model)
162
 
163
+ production_used_to_charge = 0
164
  if unsatisfied_demand >= remaining_production:
165
  # all goes to demand
166
  consumption_from_solar = remaining_production
167
  unsatisfied_demand -= consumption_from_solar
168
  remaining_production = 0
169
  # we try to cover the rest from BESS
170
+ if unsatisfied_demand > 0:
171
+ if is_battery_charged_enough and decision == Decision.DISCHARGE:
172
  # battery capacity is limited!
173
+ if cap_of_battery >= unsatisfied_demand * time_interval:
174
  consumption_from_bess = unsatisfied_demand
175
  unsatisfied_demand = 0
176
  cap_of_battery -= consumption_from_bess * time_interval
 
193
  consumption_from_solar = unsatisfied_demand
194
  remaining_production -= unsatisfied_demand
195
  unsatisfied_demand = 0
196
+ if remaining_production > 0:
197
  # exploitable production still remains:
198
  if is_battery_chargeable:
199
  # we try to specify the BESS modell
200
  if parameters.bess_charge <= remaining_production :
201
  energy = parameters.bess_charge * time_interval
202
  remaining_production = remaining_production - parameters.bess_charge
203
+ production_used_to_charge = parameters.bess_charge
204
  else :
205
+ production_used_to_charge = remaining_production
206
  energy = remaining_production * time_interval
207
  remaining_production = 0
208
  cap_of_battery += energy
 
210
 
211
  discarded_production = remaining_production
212
 
213
+ if decision == Decision.NETWORK_CHARGE:
214
+ # there are two things that can limit charging at this point,
215
+ # one is a distance-like, the other is a velocity-like quantity.
216
+ # 1. the battery is fully charged
217
+ # 2. the battery is being charged from solar, no bandwidth left.
218
+ is_battery_chargeable = battery_model.soc < 1.0
219
+ remaining_network_charge = parameters.bess_charge - production_used_to_charge
220
+ if is_battery_chargeable:
221
+ # we try to specify the BESS modell
222
+ if parameters.bess_charge <= remaining_network_charge :
223
+ energy = parameters.bess_charge * time_interval
224
+ remaining_network_charge = remaining_network_charge - parameters.bess_charge
225
+ network_used_to_charge = parameters.bess_charge
226
+ else :
227
+ network_used_to_charge = remaining_production
228
+ energy = remaining_production * time_interval
229
+ cap_of_battery += energy
230
+ soc = cap_of_battery / max_cap_of_battery
231
+ consumption_from_network += network_used_to_charge
232
+
233
  soc_series.append(battery_model.soc)
234
  consumption_from_solar_series.append(consumption_from_solar)
235
  consumption_from_network_series.append(consumption_from_network)
236
+ consumption_from_network_to_bess_series.append(network_used_to_charge)
237
  consumption_from_bess_series.append(consumption_from_bess)
238
  discarded_production_series.append(discarded_production)
239
 
 
246
  results = pd.DataFrame({'soc_series': soc_series, 'consumption_from_solar': consumption_from_solar_series,
247
  'consumption_from_network': consumption_from_network_series,
248
  'consumption_from_bess': consumption_from_bess_series,
249
+ 'consumption_from_network_to_bess_series': consumption_from_network_to_bess_series,
250
  'discarded_production': discarded_production_series,
251
+ 'Consumption': prod_cons['Consumption'],
252
+ 'Production': prod_cons['Production']
253
  })
254
+ results = results.set_index(prod_cons.index)
255
  return results
256
 
257
 
 
267
  add_production_field(met_2021_data, parameters)
268
  all_2021_data = interpolate_and_join(met_2021_data, cons_2021_data)
269
 
270
+ prod_predictor = DummyPredictor(pd.Series(all_2021_data['Production']))
271
+ cons_predictor = DummyPredictor(pd.Series(all_2021_data['Consumption']))
272
+
273
  decider = Decider()
274
 
275
+ results = simulator(battery_model, supplier, all_2021_data, prod_predictor, cons_predictor, decider, parameters)
276
 
277
 
278
  if __name__ == '__main__':