Spaces:
Sleeping
Sleeping
Daniel Varga
commited on
Commit
•
7f927c0
1
Parent(s):
1fee7d7
random decider for debugging
Browse files- v2/architecture.py +66 -15
- v2/decider.py +39 -1
- v2/evolution_strategies.py +4 -16
- v2/supplier.py +1 -1
v2/architecture.py
CHANGED
@@ -5,7 +5,7 @@ import time
|
|
5 |
import matplotlib.pyplot as plt
|
6 |
|
7 |
# it's really just a network pricing model
|
8 |
-
from decider import Decider,
|
9 |
from supplier import Supplier, precalculate_supplier
|
10 |
from data_processing import read_datasets, add_production_field, interpolate_and_join, SolarParameters
|
11 |
from bess import BatteryModel
|
@@ -27,8 +27,8 @@ def add_dummy_predictions(all_data_with_predictions):
|
|
27 |
all_data_with_predictions['Consumption_prediction'] = all_data_with_predictions['Consumption'].shift(periods=cons_shift)
|
28 |
all_data_with_predictions['Production_prediction'] = all_data_with_predictions['Production'].shift(periods=prod_shift)
|
29 |
# we predict zero before we have data, no big deal:
|
30 |
-
all_data_with_predictions[
|
31 |
-
all_data_with_predictions[
|
32 |
|
33 |
|
34 |
# even mock-er class than usual.
|
@@ -185,7 +185,7 @@ def simulator(battery_model, prod_cons, decider):
|
|
185 |
'''
|
186 |
consumption_charge_series = consumption_fees_np * consumption_from_network_pandas_series.to_numpy()
|
187 |
step_in_hour = consumption_from_network_pandas_series.index.freq.n / 60 # [hour], the length of a time step.
|
188 |
-
fifteen_minute_demands_in_kwh = consumption_from_network_pandas_series.resample('
|
189 |
fifteen_minute_surdemands_in_kwh = (fifteen_minute_demands_in_kwh - decider.precalculated_supplier.peak_demand).clip(lower=0)
|
190 |
demand_charges = fifteen_minute_surdemands_in_kwh * decider.precalculated_supplier.surcharge_per_kwh
|
191 |
total_network_fee = consumption_charge_series.sum() + demand_charges.sum()
|
@@ -210,23 +210,62 @@ def simulator(battery_model, prod_cons, decider):
|
|
210 |
|
211 |
|
212 |
|
213 |
-
def optimizer(battery_model, all_data_with_predictions, precalculated_supplier):
|
|
|
|
|
|
|
214 |
def objective_function(params):
|
215 |
print("Simulating with parameters", params)
|
216 |
-
|
217 |
-
decider = Decider(params, precalculated_supplier)
|
218 |
t = time.perf_counter()
|
219 |
results, total_network_fee = simulator(battery_model, all_data_with_predictions, decider)
|
|
|
220 |
return total_network_fee
|
221 |
|
222 |
def clipper_function(params):
|
223 |
-
return
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
224 |
|
225 |
-
init_mean, init_scale = Decider.initial_params()
|
226 |
-
best_params = evolution_strategies_optimizer(objective_function, clipper_function, init_mean=init_mean, init_scale=init_scale)
|
227 |
|
228 |
|
229 |
def main():
|
|
|
|
|
230 |
supplier = Supplier(price=100) # Ft/kWh
|
231 |
# nine-to-five increased price.
|
232 |
supplier.set_price_for_daily_interval(9, 17, 150)
|
@@ -242,11 +281,17 @@ def main():
|
|
242 |
add_production_field(met_2021_data, solar_parameters)
|
243 |
all_data = interpolate_and_join(met_2021_data, cons_2021_data)
|
244 |
|
245 |
-
print("Working with", solar_parameters.solar_cell_num, "solar cells, that's a maximum production of", all_data['Production'].max(), "kW.")
|
246 |
-
|
247 |
time_interval_min = all_data.index.freq.n
|
248 |
time_interval_h = time_interval_min / 60
|
249 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
250 |
all_data_with_predictions = all_data.copy()
|
251 |
add_dummy_predictions(all_data_with_predictions)
|
252 |
|
@@ -258,11 +303,13 @@ def main():
|
|
258 |
|
259 |
battery_model = BatteryModel(capacity_Ah=600, time_interval_h=time_interval_h)
|
260 |
|
|
|
|
|
261 |
# TODO this is super unfortunate:
|
262 |
# Consumption_fees travels via all_data_with_predictions,
|
263 |
# peak_demand and surcharge_per_kwh travels via precalculated_supplier of decider.
|
264 |
-
decider_init_mean, decider_init_scale =
|
265 |
-
decider =
|
266 |
|
267 |
t = time.perf_counter()
|
268 |
results, total_network_fee = simulator(battery_model, all_data_with_predictions, decider)
|
@@ -273,7 +320,11 @@ def main():
|
|
273 |
plt.title('soc_series')
|
274 |
plt.show()
|
275 |
|
276 |
-
optimizer(battery_model, all_data_with_predictions, precalculated_supplier)
|
|
|
|
|
|
|
|
|
277 |
|
278 |
if __name__ == '__main__':
|
279 |
main()
|
|
|
5 |
import matplotlib.pyplot as plt
|
6 |
|
7 |
# it's really just a network pricing model
|
8 |
+
from decider import Decision, Decider, RandomDecider
|
9 |
from supplier import Supplier, precalculate_supplier
|
10 |
from data_processing import read_datasets, add_production_field, interpolate_and_join, SolarParameters
|
11 |
from bess import BatteryModel
|
|
|
27 |
all_data_with_predictions['Consumption_prediction'] = all_data_with_predictions['Consumption'].shift(periods=cons_shift)
|
28 |
all_data_with_predictions['Production_prediction'] = all_data_with_predictions['Production'].shift(periods=prod_shift)
|
29 |
# we predict zero before we have data, no big deal:
|
30 |
+
all_data_with_predictions.loc[all_data_with_predictions.index[:cons_shift], 'Consumption_prediction'] = 0
|
31 |
+
all_data_with_predictions.loc[all_data_with_predictions.index[:prod_shift], 'Production_prediction'] = 0
|
32 |
|
33 |
|
34 |
# even mock-er class than usual.
|
|
|
185 |
'''
|
186 |
consumption_charge_series = consumption_fees_np * consumption_from_network_pandas_series.to_numpy()
|
187 |
step_in_hour = consumption_from_network_pandas_series.index.freq.n / 60 # [hour], the length of a time step.
|
188 |
+
fifteen_minute_demands_in_kwh = consumption_from_network_pandas_series.resample('15min').sum() * step_in_hour
|
189 |
fifteen_minute_surdemands_in_kwh = (fifteen_minute_demands_in_kwh - decider.precalculated_supplier.peak_demand).clip(lower=0)
|
190 |
demand_charges = fifteen_minute_surdemands_in_kwh * decider.precalculated_supplier.surcharge_per_kwh
|
191 |
total_network_fee = consumption_charge_series.sum() + demand_charges.sum()
|
|
|
210 |
|
211 |
|
212 |
|
213 |
+
def optimizer(decider_class, battery_model, all_data_with_predictions, precalculated_supplier):
|
214 |
+
number_of_generations = 3
|
215 |
+
population_size = 100
|
216 |
+
collected_loss_values = []
|
217 |
def objective_function(params):
|
218 |
print("Simulating with parameters", params)
|
219 |
+
decider = decider_class(params, precalculated_supplier)
|
|
|
220 |
t = time.perf_counter()
|
221 |
results, total_network_fee = simulator(battery_model, all_data_with_predictions, decider)
|
222 |
+
collected_loss_values.append((params, total_network_fee))
|
223 |
return total_network_fee
|
224 |
|
225 |
def clipper_function(params):
|
226 |
+
return decider_class.clip_params(params)
|
227 |
+
|
228 |
+
init_mean, init_scale = decider_class.initial_params()
|
229 |
+
best_params = evolution_strategies_optimizer(objective_function, clipper_function,
|
230 |
+
init_mean=init_mean, init_scale=init_scale,
|
231 |
+
number_of_generations=number_of_generations,
|
232 |
+
population_size=population_size)
|
233 |
+
return best_params, collected_loss_values
|
234 |
+
|
235 |
+
|
236 |
+
def visualize_collected_loss_values(collected_loss_values):
|
237 |
+
all_params = []
|
238 |
+
losses = []
|
239 |
+
for row in collected_loss_values:
|
240 |
+
params, loss = row
|
241 |
+
all_params.append(params)
|
242 |
+
losses.append(loss)
|
243 |
+
|
244 |
+
all_params = np.array(all_params)
|
245 |
+
losses = np.array(losses)
|
246 |
+
|
247 |
+
# losses -= losses.min() ; losses /= losses.max()
|
248 |
+
|
249 |
+
plt.scatter(all_params[:, 0], all_params[:, 1], c=range(len(all_params)))
|
250 |
+
plt.show()
|
251 |
+
|
252 |
+
|
253 |
+
from mpl_toolkits.mplot3d import Axes3D
|
254 |
+
|
255 |
+
fig = plt.figure()
|
256 |
+
|
257 |
+
# Add a 3D subplot
|
258 |
+
ax = fig.add_subplot(111, projection='3d')
|
259 |
+
|
260 |
+
# Scatter plot
|
261 |
+
ax.scatter(all_params[:, 0], all_params[:, 1], losses)
|
262 |
+
plt.show()
|
263 |
|
|
|
|
|
264 |
|
265 |
|
266 |
def main():
|
267 |
+
np.random.seed(1)
|
268 |
+
|
269 |
supplier = Supplier(price=100) # Ft/kWh
|
270 |
# nine-to-five increased price.
|
271 |
supplier.set_price_for_daily_interval(9, 17, 150)
|
|
|
281 |
add_production_field(met_2021_data, solar_parameters)
|
282 |
all_data = interpolate_and_join(met_2021_data, cons_2021_data)
|
283 |
|
|
|
|
|
284 |
time_interval_min = all_data.index.freq.n
|
285 |
time_interval_h = time_interval_min / 60
|
286 |
|
287 |
+
# for faster testing:
|
288 |
+
DATASET_TRUNCATED_SIZE = 10000
|
289 |
+
if DATASET_TRUNCATED_SIZE is not None:
|
290 |
+
print("Truncating dataset to", DATASET_TRUNCATED_SIZE, "datapoints, that is", DATASET_TRUNCATED_SIZE * time_interval_h / 24, "days")
|
291 |
+
all_data = all_data.iloc[:DATASET_TRUNCATED_SIZE]
|
292 |
+
|
293 |
+
print("Working with", solar_parameters.solar_cell_num, "solar cells, that's a maximum production of", all_data['Production'].max(), "kW.")
|
294 |
+
|
295 |
all_data_with_predictions = all_data.copy()
|
296 |
add_dummy_predictions(all_data_with_predictions)
|
297 |
|
|
|
303 |
|
304 |
battery_model = BatteryModel(capacity_Ah=600, time_interval_h=time_interval_h)
|
305 |
|
306 |
+
decider_class = RandomDecider
|
307 |
+
|
308 |
# TODO this is super unfortunate:
|
309 |
# Consumption_fees travels via all_data_with_predictions,
|
310 |
# peak_demand and surcharge_per_kwh travels via precalculated_supplier of decider.
|
311 |
+
decider_init_mean, decider_init_scale = decider_class.initial_params()
|
312 |
+
decider = decider_class(decider_init_mean, precalculated_supplier)
|
313 |
|
314 |
t = time.perf_counter()
|
315 |
results, total_network_fee = simulator(battery_model, all_data_with_predictions, decider)
|
|
|
320 |
plt.title('soc_series')
|
321 |
plt.show()
|
322 |
|
323 |
+
best_params, collected_loss_values = optimizer(decider_class, battery_model, all_data_with_predictions, precalculated_supplier)
|
324 |
+
visualize_collected_loss_values(collected_loss_values)
|
325 |
+
|
326 |
+
decider = decider_class(best_params, precalculated_supplier)
|
327 |
+
results, total_network_fee = simulator(battery_model, all_data_with_predictions, decider)
|
328 |
|
329 |
if __name__ == '__main__':
|
330 |
main()
|
v2/decider.py
CHANGED
@@ -25,12 +25,50 @@ class Decision(IntEnum):
|
|
25 |
NETWORK_CHARGE = 2
|
26 |
|
27 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
25 |
NETWORK_CHARGE = 2
|
26 |
|
27 |
|
28 |
+
class RandomDecider:
|
29 |
+
def __init__(self, params, precalculated_supplier):
|
30 |
+
self.input_window_size = STEPS_PER_HOUR * 24 # day long window.
|
31 |
+
self.precalculated_supplier = precalculated_supplier
|
32 |
+
assert params.shape == (2, )
|
33 |
+
# param_1 is prob of choosing PASSIVE
|
34 |
+
# param_2 is prob of choosing NETWORK_CHARGE
|
35 |
+
self.passive_prob, self.network_prob = params
|
36 |
+
|
37 |
+
def decide(self, prod_pred, cons_pred, fees, battery_model):
|
38 |
+
r = np.random.rand()
|
39 |
+
if r < self.passive_prob:
|
40 |
+
return Decision.PASSIVE
|
41 |
+
elif r < self.passive_prob + self.network_prob:
|
42 |
+
return Decision.NETWORK_CHARGE
|
43 |
+
else:
|
44 |
+
return Decision.DISCHARGE
|
45 |
+
|
46 |
+
@staticmethod
|
47 |
+
def clip_params(params):
|
48 |
+
assert params.shape == (2, )
|
49 |
+
p1, p2 = params
|
50 |
+
p1 = max((0, p1))
|
51 |
+
p2 = max((0, p2))
|
52 |
+
s = p1 + p2
|
53 |
+
if s > 1:
|
54 |
+
p1 /= s
|
55 |
+
p2 /= s
|
56 |
+
return np.array([p1, p2])
|
57 |
+
|
58 |
+
|
59 |
+
@staticmethod
|
60 |
+
def initial_params():
|
61 |
+
init_mean = np.array([1/3, 1/3])
|
62 |
+
init_scale = np.array([1.0, 1.0])
|
63 |
+
return init_mean, init_scale
|
64 |
+
|
65 |
+
|
66 |
+
|
67 |
# mock class as usual
|
68 |
# output_window_size is not yet used, always decides one timestep.
|
69 |
class Decider:
|
70 |
def __init__(self, params, precalculated_supplier):
|
71 |
self.input_window_size = STEPS_PER_HOUR * 24 # day long window.
|
|
|
72 |
self.precalculated_supplier = precalculated_supplier
|
73 |
assert params.shape == (2, )
|
74 |
# param_1 is how many minutes do we look ahead to decide if there's
|
v2/evolution_strategies.py
CHANGED
@@ -11,10 +11,11 @@ def clip_params(population, clipper_function):
|
|
11 |
population[i] = clipper_function(individual)
|
12 |
|
13 |
|
14 |
-
def evolution_strategies_optimizer(objective_function, clipper_function,
|
|
|
|
|
|
|
15 |
# Initialize parameters
|
16 |
-
population_size = 100
|
17 |
-
number_of_generations = 30
|
18 |
mutation_scale = 0.05
|
19 |
selection_ratio = 0.5
|
20 |
selected_size = int(population_size * selection_ratio)
|
@@ -44,19 +45,6 @@ def evolution_strategies_optimizer(objective_function, clipper_function, init_me
|
|
44 |
best_index = np.argmin(fitness)
|
45 |
best_solution = population[best_index]
|
46 |
print(f"Generation {generation + 1}: Best Fitness = {best_fitness}", f"Best solution so far: {best_solution}")
|
47 |
-
if DO_VIS:
|
48 |
-
plt.scatter(population[:, 0], population[:, 1])
|
49 |
-
plt.xlim(-5, 5)
|
50 |
-
plt.ylim(-5, 5)
|
51 |
-
x = np.linspace(-5, 5, 100)
|
52 |
-
y = np.linspace(-5, 5, 100)
|
53 |
-
XY = np.stack(np.meshgrid(x, y), axis=-1)
|
54 |
-
print(XY.shape)
|
55 |
-
|
56 |
-
# Defining the function (x - 3)**2 + (y + 2)**2
|
57 |
-
Z = toy_objective_function(XY)
|
58 |
-
contour = plt.contour(XY[..., 0], XY[..., 1], Z, levels=50)
|
59 |
-
plt.show()
|
60 |
|
61 |
|
62 |
# Best solution
|
|
|
11 |
population[i] = clipper_function(individual)
|
12 |
|
13 |
|
14 |
+
def evolution_strategies_optimizer(objective_function, clipper_function,
|
15 |
+
init_mean, init_scale,
|
16 |
+
number_of_generations,
|
17 |
+
population_size):
|
18 |
# Initialize parameters
|
|
|
|
|
19 |
mutation_scale = 0.05
|
20 |
selection_ratio = 0.5
|
21 |
selected_size = int(population_size * selection_ratio)
|
|
|
45 |
best_index = np.argmin(fitness)
|
46 |
best_solution = population[best_index]
|
47 |
print(f"Generation {generation + 1}: Best Fitness = {best_fitness}", f"Best solution so far: {best_solution}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
|
49 |
|
50 |
# Best solution
|
v2/supplier.py
CHANGED
@@ -71,7 +71,7 @@ class Supplier:
|
|
71 |
assert 15 % demand_series.index.freq.n == 0
|
72 |
time_steps_per_demand_charge_evaluation = 15 // demand_series.index.freq.n
|
73 |
# fifteen_minute_peaks [kW] tells the maximum demand in a 15 minutes timeframe:
|
74 |
-
fifteen_minute_demands_in_kwh = demand_series.resample('
|
75 |
demand_charges = pd.Series([self.demand_charge(demand_in_kwh) for demand_in_kwh in fifteen_minute_demands_in_kwh], index=fifteen_minute_demands_in_kwh.index)
|
76 |
total_demand_charge = sum(demand_charges)
|
77 |
total_charge = consumption_charge + total_demand_charge
|
|
|
71 |
assert 15 % demand_series.index.freq.n == 0
|
72 |
time_steps_per_demand_charge_evaluation = 15 // demand_series.index.freq.n
|
73 |
# fifteen_minute_peaks [kW] tells the maximum demand in a 15 minutes timeframe:
|
74 |
+
fifteen_minute_demands_in_kwh = demand_series.resample('15min').sum() * step_in_hour
|
75 |
demand_charges = pd.Series([self.demand_charge(demand_in_kwh) for demand_in_kwh in fifteen_minute_demands_in_kwh], index=fifteen_minute_demands_in_kwh.index)
|
76 |
total_demand_charge = sum(demand_charges)
|
77 |
total_charge = consumption_charge + total_demand_charge
|