Spaces:
Sleeping
Sleeping
Daniel Varga
commited on
Commit
•
ffa5234
1
Parent(s):
e75e97c
refactor: split py
Browse files- v2/architecture.py +3 -108
- v2/bess.py +58 -0
- v2/decider.py +50 -0
v2/architecture.py
CHANGED
@@ -2,121 +2,17 @@ import numpy as np
|
|
2 |
import pandas as pd
|
3 |
import copy
|
4 |
import time
|
5 |
-
from enum import IntEnum
|
6 |
import matplotlib.pyplot as plt
|
7 |
|
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 |
-
|
15 |
-
STEPS_PER_HOUR = 12
|
16 |
-
|
17 |
-
|
18 |
-
# SOC is normalized so that minimal_depth_of_discharge = 0 and maximal_depth_of_discharge = 1.
|
19 |
-
# please set capacity_Ah = nominal_capacity_Ah * (max_dod - min_dod)
|
20 |
-
#
|
21 |
-
# TODO efficiency multiplier is not currently used, where best to put it?
|
22 |
-
class BatteryModel:
|
23 |
-
def __init__(self, capacity_Ah, time_interval_h):
|
24 |
-
self.capacity_Ah = capacity_Ah
|
25 |
-
self.efficiency = 0.9 # [dimensionless]
|
26 |
-
self.voltage_V = 600
|
27 |
-
self.charge_kW = 50
|
28 |
-
self.discharge_kW = 60
|
29 |
-
self.time_interval_h = time_interval_h
|
30 |
-
|
31 |
-
# the only non-constant member variable!
|
32 |
-
# ratio of self.current_capacity_kWh and self.maximal_capacity_kWh
|
33 |
-
self.soc = 0.0
|
34 |
-
|
35 |
-
@property
|
36 |
-
def maximal_capacity_kWh(self):
|
37 |
-
return self.capacity_Ah * self.voltage_V / 1000
|
38 |
-
|
39 |
-
@property
|
40 |
-
def current_capacity_kWh(self):
|
41 |
-
return self.soc * self.maximal_capacity_kWh
|
42 |
-
|
43 |
-
def satisfy_demand(self, demand_kW):
|
44 |
-
assert 0 <= self.soc <= 1
|
45 |
-
assert demand_kW >= 0
|
46 |
-
# rate limited:
|
47 |
-
possible_discharge_in_timestep_kWh = self.discharge_kW * self.time_interval_h
|
48 |
-
# limited by current capacity:
|
49 |
-
possible_discharge_in_timestep_kWh = min((possible_discharge_in_timestep_kWh, self.current_capacity_kWh))
|
50 |
-
# limited by need:
|
51 |
-
discharge_in_timestep_kWh = min((possible_discharge_in_timestep_kWh, demand_kW * self.time_interval_h))
|
52 |
-
consumption_from_bess_kW = discharge_in_timestep_kWh / self.time_interval_h
|
53 |
-
unsatisfied_demand_kW = demand_kW - consumption_from_bess_kW
|
54 |
-
cap_of_battery_kWh = self.current_capacity_kWh - discharge_in_timestep_kWh
|
55 |
-
soc = cap_of_battery_kWh / self.maximal_capacity_kWh
|
56 |
-
assert 0 <= soc <= self.soc <= 1
|
57 |
-
self.soc = soc
|
58 |
-
return unsatisfied_demand_kW
|
59 |
-
|
60 |
-
def charge(self, charge_kW):
|
61 |
-
assert 0 <= self.soc <= 1
|
62 |
-
assert charge_kW >= 0
|
63 |
-
# rate limited:
|
64 |
-
possible_charge_in_timestep_kWh = self.charge_kW * self.time_interval_h
|
65 |
-
# limited by current capacity:
|
66 |
-
possible_charge_in_timestep_kWh = min((possible_charge_in_timestep_kWh, self.maximal_capacity_kWh - self.current_capacity_kWh))
|
67 |
-
# limited by supply:
|
68 |
-
charge_in_timestep_kWh = min((possible_charge_in_timestep_kWh, charge_kW * self.time_interval_h))
|
69 |
-
actual_charge_kW = charge_in_timestep_kWh / self.time_interval_h
|
70 |
-
unused_charge_kW = charge_kW - actual_charge_kW
|
71 |
-
cap_of_battery_kWh = self.current_capacity_kWh + charge_in_timestep_kWh
|
72 |
-
soc = cap_of_battery_kWh / self.maximal_capacity_kWh
|
73 |
-
assert 0 <= self.soc <= soc <= 1
|
74 |
-
self.soc = soc
|
75 |
-
return unused_charge_kW
|
76 |
-
|
77 |
-
|
78 |
-
class Decision(IntEnum):
|
79 |
-
# use solar to satisfy consumption,
|
80 |
-
# and if it is not enough, use network.
|
81 |
-
# BESS is not discharged in this mode,
|
82 |
-
# but might be charged if solar has surplus.
|
83 |
-
PASSIVE = 0
|
84 |
-
|
85 |
-
# use the battery if possible and necessary.
|
86 |
-
# the possible part means that there's charge in it,
|
87 |
-
# and the necessary part means that the consumption
|
88 |
-
# is not already covered by solar.
|
89 |
-
DISCHARGE = 1
|
90 |
-
|
91 |
-
# use the network to charge the battery
|
92 |
-
# this is similar to PASSIVE, but forces the
|
93 |
-
# BESS to be charged, even if solar does not cover
|
94 |
-
# the whole of consumption plus BESS.
|
95 |
-
NETWORK_CHARGE = 2
|
96 |
-
|
97 |
-
|
98 |
-
# mock class as usual
|
99 |
-
# output_window_size is not yet used, always decides one timestep.
|
100 |
-
class Decider:
|
101 |
-
def __init__(self, precalculated_supplier):
|
102 |
-
self.input_window_size = STEPS_PER_HOUR * 24 # day long window.
|
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:
|
116 |
-
return Decision.DISCHARGE
|
117 |
-
else:
|
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
|
@@ -310,7 +206,6 @@ def main():
|
|
310 |
all_data = interpolate_and_join(met_2021_data, cons_2021_data)
|
311 |
|
312 |
time_interval_min = all_data.index.freq.n
|
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()
|
|
|
2 |
import pandas as pd
|
3 |
import copy
|
4 |
import time
|
|
|
5 |
import matplotlib.pyplot as plt
|
6 |
|
7 |
# it's really just a network pricing model
|
8 |
+
from decider import Decider, Decision
|
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
|
12 |
from evolution_strategies import evolution_strategies_optimizer
|
13 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
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
|
|
|
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 |
|
211 |
all_data_with_predictions = all_data.copy()
|
v2/bess.py
ADDED
@@ -0,0 +1,58 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# SOC is normalized so that minimal_depth_of_discharge = 0 and maximal_depth_of_discharge = 1.
|
2 |
+
# please set capacity_Ah = nominal_capacity_Ah * (max_dod - min_dod)
|
3 |
+
#
|
4 |
+
# TODO efficiency multiplier is not currently used, where best to put it?
|
5 |
+
class BatteryModel:
|
6 |
+
def __init__(self, capacity_Ah, time_interval_h):
|
7 |
+
self.capacity_Ah = capacity_Ah
|
8 |
+
self.efficiency = 0.9 # [dimensionless]
|
9 |
+
self.voltage_V = 600
|
10 |
+
self.charge_kW = 50
|
11 |
+
self.discharge_kW = 60
|
12 |
+
self.time_interval_h = time_interval_h
|
13 |
+
|
14 |
+
# the only non-constant member variable!
|
15 |
+
# ratio of self.current_capacity_kWh and self.maximal_capacity_kWh
|
16 |
+
self.soc = 0.0
|
17 |
+
|
18 |
+
@property
|
19 |
+
def maximal_capacity_kWh(self):
|
20 |
+
return self.capacity_Ah * self.voltage_V / 1000
|
21 |
+
|
22 |
+
@property
|
23 |
+
def current_capacity_kWh(self):
|
24 |
+
return self.soc * self.maximal_capacity_kWh
|
25 |
+
|
26 |
+
def satisfy_demand(self, demand_kW):
|
27 |
+
assert 0 <= self.soc <= 1
|
28 |
+
assert demand_kW >= 0
|
29 |
+
# rate limited:
|
30 |
+
possible_discharge_in_timestep_kWh = self.discharge_kW * self.time_interval_h
|
31 |
+
# limited by current capacity:
|
32 |
+
possible_discharge_in_timestep_kWh = min((possible_discharge_in_timestep_kWh, self.current_capacity_kWh))
|
33 |
+
# limited by need:
|
34 |
+
discharge_in_timestep_kWh = min((possible_discharge_in_timestep_kWh, demand_kW * self.time_interval_h))
|
35 |
+
consumption_from_bess_kW = discharge_in_timestep_kWh / self.time_interval_h
|
36 |
+
unsatisfied_demand_kW = demand_kW - consumption_from_bess_kW
|
37 |
+
cap_of_battery_kWh = self.current_capacity_kWh - discharge_in_timestep_kWh
|
38 |
+
soc = cap_of_battery_kWh / self.maximal_capacity_kWh
|
39 |
+
assert 0 <= soc <= self.soc <= 1
|
40 |
+
self.soc = soc
|
41 |
+
return unsatisfied_demand_kW
|
42 |
+
|
43 |
+
def charge(self, charge_kW):
|
44 |
+
assert 0 <= self.soc <= 1
|
45 |
+
assert charge_kW >= 0
|
46 |
+
# rate limited:
|
47 |
+
possible_charge_in_timestep_kWh = self.charge_kW * self.time_interval_h
|
48 |
+
# limited by current capacity:
|
49 |
+
possible_charge_in_timestep_kWh = min((possible_charge_in_timestep_kWh, self.maximal_capacity_kWh - self.current_capacity_kWh))
|
50 |
+
# limited by supply:
|
51 |
+
charge_in_timestep_kWh = min((possible_charge_in_timestep_kWh, charge_kW * self.time_interval_h))
|
52 |
+
actual_charge_kW = charge_in_timestep_kWh / self.time_interval_h
|
53 |
+
unused_charge_kW = charge_kW - actual_charge_kW
|
54 |
+
cap_of_battery_kWh = self.current_capacity_kWh + charge_in_timestep_kWh
|
55 |
+
soc = cap_of_battery_kWh / self.maximal_capacity_kWh
|
56 |
+
assert 0 <= self.soc <= soc <= 1
|
57 |
+
self.soc = soc
|
58 |
+
return unused_charge_kW
|
v2/decider.py
ADDED
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from enum import IntEnum
|
2 |
+
|
3 |
+
|
4 |
+
|
5 |
+
STEPS_PER_HOUR = 12
|
6 |
+
|
7 |
+
|
8 |
+
class Decision(IntEnum):
|
9 |
+
# use solar to satisfy consumption,
|
10 |
+
# and if it is not enough, use network.
|
11 |
+
# BESS is not discharged in this mode,
|
12 |
+
# but might be charged if solar has surplus.
|
13 |
+
PASSIVE = 0
|
14 |
+
|
15 |
+
# use the battery if possible and necessary.
|
16 |
+
# the possible part means that there's charge in it,
|
17 |
+
# and the necessary part means that the consumption
|
18 |
+
# is not already covered by solar.
|
19 |
+
DISCHARGE = 1
|
20 |
+
|
21 |
+
# use the network to charge the battery
|
22 |
+
# this is similar to PASSIVE, but forces the
|
23 |
+
# BESS to be charged, even if solar does not cover
|
24 |
+
# the whole of consumption plus BESS.
|
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, 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
|
38 |
+
DEMAND_CHARGE_WINDOW_MIN = 15
|
39 |
+
time_interval_min = self.precalculated_supplier.time_index.freq.n
|
40 |
+
assert DEMAND_CHARGE_WINDOW_MIN % time_interval_min == 0
|
41 |
+
peak_shaving_window = DEMAND_CHARGE_WINDOW_MIN // time_interval_min
|
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 |
+
|