Spaces:
Sleeping
Sleeping
Daniel Varga
commited on
Commit
•
dd725fc
1
Parent(s):
0c694ac
simulator with dummy decider and dummy predictors does something
Browse files- 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,
|
79 |
-
assert len(
|
|
|
|
|
|
|
80 |
# dummy decider always says DISCHARGE:
|
81 |
-
return pd.Series([Decision.DISCHARGE] *
|
82 |
|
83 |
|
84 |
-
#
|
85 |
-
#
|
86 |
-
# it
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
92 |
-
|
93 |
-
|
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 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
|
|
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
|
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
|
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':
|
220 |
-
'Production':
|
221 |
})
|
222 |
-
results = results.set_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__':
|