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

refactor: split py

Browse files
Files changed (3) hide show
  1. v2/architecture.py +3 -108
  2. v2/bess.py +58 -0
  3. 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
+