-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi_extension.py
More file actions
246 lines (204 loc) · 10.5 KB
/
api_extension.py
File metadata and controls
246 lines (204 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
import inspect
from typing import Collection, Tuple
import fleafapi
from fastapi import HTTPException
from flessim import ForecastingFlessimAPI
from vessim.actor import ComputingSystem, Generator
from vessim.controller import Controller
from vessim.cosim import Microgrid
__all__ = ["ENERGY_SHARING_METHOD_MAPPING", "FleafPowerManagementExtension", "FleafEnergySharingExtension",
"EnergySharingController", "EnergySharingFlessimAPI"]
ENERGY_SHARING_METHOD_MAPPING = {
# TODO if this extension is included in the standard, take care of create_accessor_type's `interface_mixin`,
# so __doc__, annotations and properties are inherited.
"current_power_limit": "power_limit?consumer_id={0}",
"power_limit_range": "power_limit_range?consumer_id={0}",
"limit_power_to": ("PUT", "power_limit?consumer_id={0}&watt={1}"),
"start_energy_sharing": ("PUT", "energy_sharing?consumer_id={0}"),
"end_energy_sharing": ("DELETE", "energy_sharing?consumer_id={0}"),
}
class FleafPowerManagementExtension(fleafapi.FleafSemanticsDescriptor):
SEMANTICS = "with power management"
@classmethod
def _get_semantics_type(cls):
return __class__.SEMANTICS
def current_power_limit(self, consumer_id: str) -> float:
"""Return the current power limit set for the consumer."""
return 0.0
def power_limit_range(self, consumer_id: str) -> Tuple[float, float]:
"""Return the minimum and maximum power limits that can be configured
for the consumer."""
return (0.0, 0.0)
def limit_power_to(self, consumer_id: str, watt: float):
"""Set the consumer's power limit to the given value in watt."""
pass
class FleafEnergySharingExtension(FleafPowerManagementExtension):
SEMANTICS = "with energy sharing"
@classmethod
def _get_semantics_type(cls):
return __class__.SEMANTICS
def start_energy_sharing(self, consumer_id: str):
"""Instruct the interface to try to share an energy budget between this
consumer, and other consumers for which the energy sharing has been
activated."""
pass
def end_energy_sharing(self, consumer_id: str):
"""Instruct the interface to remove this consumer from the energy
sharing mechanism."""
pass
class ExcessPowerController(Controller):
def __init__(self, charge_ratio: float = 1.0, **kwargs):
super().__init__(**kwargs)
if 0 >= charge_ratio or charge_ratio > 1:
raise ValueError("charge_ratio must be above 0 but not above 1.")
self.charge_ratio = charge_ratio
def start(self, microgrid: Microgrid):
self.policy = microgrid.policy
assert hasattr(self.policy, "charge_power")
def step(self, time: int, p_delta: float, e_delta: float, actor_infos: dict):
generation = 0
consumption = 0
for actor_name, info in actor_infos.items():
if info["p"] > 0:
generation += info["p"]
else:
consumption += -info["p"]
budget = max(0.0, generation - consumption)
# TODO replaced by "set_parameters" in newer vessim versions.
self.policy.charge_power = budget * self.charge_ratio
class EnergySharingController(Controller, FleafEnergySharingExtension):
# TODO if this extension is included in the standard,
# some syntactic sugar to add this controller automatically would be nice.
def __init__(self, known_consumers: Collection[ComputingSystem], adaptation_rate: float = 1.0, **kwargs):
super().__init__(**kwargs)
self.known_consumers = {consumer.name: (consumer, consumer.power_meters[0]) for consumer in known_consumers}
self._sharing_energy = set() # type: ignore
self._original_limits = {pm: pm.full_power for (_, pm) in self.known_consumers.values()}
if 0 >= adaptation_rate or adaptation_rate > 1:
raise ValueError("adaptation_rate must be above 0 but not above 1.")
self.adaptation_rate = adaptation_rate
def step(self, time: int, p_delta: float, e_delta: float, actor_infos: dict):
# TODO this assumes that all local generation is renewable (which is the case in our example scenarios)
# Since the FlessimAPI has methods to extract traits from the information provided in the step method,
# they could be utilized here to implement a generalized solution.
generation = 0
consumption = 0
shared_consumption = 0
for cid in self._sharing_energy:
shared_consumption += -actor_infos[cid]["p"]
for actor_name, info in actor_infos.items():
if info["p"] > 0:
generation += info["p"]
else:
consumption += -info["p"]
budget = max(0.0, generation - consumption + shared_consumption)
new_limits = {cid: 0.0 for cid in self._sharing_energy}
limit_ranges = {cid: self.power_limit_range(cid) for cid in self._sharing_energy}
steps = 20
for i in range(steps, -1, -1):
threshold = i / steps
reserved = 0.0
for cid in self._sharing_energy:
maxW = limit_ranges[cid][1]
if new_limits[cid] > maxW:
continue
curpow = -actor_infos[cid]["p"]
curlim = self.current_power_limit(cid)
if curpow < (curlim + new_limits[cid]) * threshold:
continue
take = (budget / len(self._sharing_energy)) * (1 - threshold)
take = min(maxW - new_limits[cid], take)
reserved += take
new_limits[cid] += take
budget -= reserved
if budget <= 0.0001:
break
for cid, limit in new_limits.items():
minW, maxW = limit_ranges[cid]
target = max(min(limit, maxW), minW)
current = self.current_power_limit(cid)
newlimit = current + (target - current) * self.adaptation_rate # slowly adjust limits
self.limit_power_to(cid, newlimit)
def finalize(self):
# Restore power limits at the end.
for power_meter, limit in self._original_limits.items():
power_meter.set_power_limit(limit)
@fleafapi.inherit_annotations(from_class=FleafEnergySharingExtension)
def current_power_limit(self, consumer_id):
if consumer_id not in self.known_consumers:
return 0.0
consumer, pm = self.known_consumers[consumer_id]
return pm.full_power * consumer.pue
@fleafapi.inherit_annotations(from_class=FleafEnergySharingExtension)
def power_limit_range(self, consumer_id):
if consumer_id not in self.known_consumers:
return (0.0, 0.0)
consumer, pm = self.known_consumers[consumer_id]
minW, maxW = pm.power_limit_range()
return minW * consumer.pue, maxW * consumer.pue
@fleafapi.inherit_annotations(from_class=FleafEnergySharingExtension)
def limit_power_to(self, consumer_id, watt):
if consumer_id not in self.known_consumers:
return
consumer, pm = self.known_consumers[consumer_id]
pm.set_power_limit(watt / consumer.pue)
@fleafapi.inherit_annotations(from_class=FleafEnergySharingExtension)
def start_energy_sharing(self, consumer_id):
if consumer_id not in self.known_consumers:
return
self._sharing_energy.add(consumer_id)
@fleafapi.inherit_annotations(from_class=FleafEnergySharingExtension)
def end_energy_sharing(self, consumer_id):
if consumer_id in self._sharing_energy:
self._sharing_energy.remove(consumer_id)
pm = self.known_consumers[consumer_id][1]
pm.set_power_limit(self._original_limits[pm])
class EnergySharingFlessimAPI(ForecastingFlessimAPI, FleafEnergySharingExtension):
@classmethod
def _process_energy_sharing_query(cls, events, microgrid):
for time, (method, *args) in events.items():
for controller in microgrid.controllers:
if isinstance(controller, EnergySharingController):
getattr(controller, method)(*args)
@classmethod
def prepare_request_collectors(cls):
return {
"energy_sharing_query": cls._process_energy_sharing_query,
}
def _register_api_routes(self):
super()._register_api_routes()
self._register_method(self.current_power_limit, "/power_limit", "GET")
self._register_method(self.power_limit_range, "/power_limit_range", "GET")
self._register_method(self.limit_power_to, "/power_limit", "PUT", status_code=202)
self._register_method(self.start_energy_sharing, "/energy_sharing", "PUT", status_code=202)
self._register_method(self.end_energy_sharing, "/energy_sharing", "DELETE", status_code=202)
def _verify_known_consumer(self, consumer_id):
if consumer_id not in self.broker._actor_infos:
raise HTTPException(status_code=404, detail="Consumer with ID '{}' is unknown".format(consumer_id))
@fleafapi.inherit_annotations(from_class=FleafEnergySharingExtension)
def current_power_limit(self, consumer_id):
return sum(self.broker.get_actor_infos(consumer_id).get("power_meters_max_p", ()))
@fleafapi.inherit_annotations(from_class=FleafEnergySharingExtension)
def power_limit_range(self, consumer_id):
# TODO This information is not included in the actor state (yet), so it cannot be queried from the broker.
return (125.0, 230.0)
@fleafapi.inherit_annotations(from_class=FleafEnergySharingExtension)
def limit_power_to(self, consumer_id, watt):
self._verify_known_consumer(consumer_id)
minW, maxW = self.power_limit_range(consumer_id)
if minW <= watt <= maxW:
self.broker.set_event("energy_sharing_query", ("limit_power_to", consumer_id, watt))
else:
raise HTTPException(
status_code=422,
detail="Provided limit lies outside the supported range ({:.3f}, {:.3f}) for that consumer.".
format(minW, maxW),
)
@fleafapi.inherit_annotations(from_class=FleafEnergySharingExtension)
def start_energy_sharing(self, consumer_id):
self._verify_known_consumer(consumer_id)
self.broker.set_event("energy_sharing_query", ("start_energy_sharing", consumer_id))
@fleafapi.inherit_annotations(from_class=FleafEnergySharingExtension)
def end_energy_sharing(self, consumer_id):
self._verify_known_consumer(consumer_id)
self.broker.set_event("energy_sharing_query", ("end_energy_sharing", consumer_id))