Skip to content

Commit 6258c76

Browse files
authored
Support exclusion bounds in power distributor (#562)
To be merged after #537 Closes #152
2 parents fd18523 + be9d239 commit 6258c76

File tree

6 files changed

+691
-283
lines changed

6 files changed

+691
-283
lines changed

RELEASE_NOTES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
- The `ConfigManagingActor` constructor now can accept a `pathlib.Path` as `config_path` too (before it accepted only a `str`).
1717

18+
- The `PowerDistributingActor` now considers exclusion bounds, when finding an optimal distribution for power between batteries.
19+
1820
## Bug Fixes
1921

2022
<!-- Here goes notable bug fixes that are worth a special mention or explanation -->

src/frequenz/sdk/actor/power_distributing/power_distributing.py

Lines changed: 73 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
from ...power import DistributionAlgorithm, DistributionResult, InvBatPair
4040
from ._battery_pool_status import BatteryPoolStatus, BatteryStatus
4141
from .request import Request
42-
from .result import Error, OutOfBound, PartialFailure, Result, Success
42+
from .result import Error, OutOfBound, PartialFailure, PowerBounds, Result, Success
4343

4444
_logger = logging.getLogger(__name__)
4545

@@ -226,6 +226,49 @@ def __init__(
226226
bat_id: None for bat_id, _ in self._bat_inv_map.items()
227227
}
228228

229+
def _get_bounds(
230+
self,
231+
pairs_data: list[InvBatPair],
232+
) -> PowerBounds:
233+
"""Get power bounds for given batteries.
234+
235+
Args:
236+
pairs_data: list of battery and adjacent inverter data pairs.
237+
238+
Returns:
239+
Power bounds for given batteries.
240+
"""
241+
return PowerBounds(
242+
inclusion_lower=sum(
243+
max(
244+
battery.power_inclusion_lower_bound,
245+
inverter.active_power_inclusion_lower_bound,
246+
)
247+
for battery, inverter in pairs_data
248+
),
249+
inclusion_upper=sum(
250+
min(
251+
battery.power_inclusion_upper_bound,
252+
inverter.active_power_inclusion_upper_bound,
253+
)
254+
for battery, inverter in pairs_data
255+
),
256+
exclusion_lower=sum(
257+
min(
258+
battery.power_exclusion_lower_bound,
259+
inverter.active_power_exclusion_lower_bound,
260+
)
261+
for battery, inverter in pairs_data
262+
),
263+
exclusion_upper=sum(
264+
max(
265+
battery.power_exclusion_upper_bound,
266+
inverter.active_power_exclusion_upper_bound,
267+
)
268+
for battery, inverter in pairs_data
269+
),
270+
)
271+
229272
def _get_upper_bound(self, batteries: abc.Set[int], include_broken: bool) -> float:
230273
"""Get total upper bound of power to be set for given batteries.
231274
@@ -307,11 +350,6 @@ async def run(self) -> None:
307350
await asyncio.sleep(self._wait_for_data_sec)
308351

309352
async for request in self._requests_receiver:
310-
error = self._check_request(request)
311-
if error:
312-
await self._send_result(request.namespace, error)
313-
continue
314-
315353
try:
316354
pairs_data: List[InvBatPair] = self._get_components_data(
317355
request.batteries, request.include_broken_batteries
@@ -329,6 +367,11 @@ async def run(self) -> None:
329367
)
330368
continue
331369

370+
error = self._check_request(request, pairs_data)
371+
if error:
372+
await self._send_result(request.namespace, error)
373+
continue
374+
332375
try:
333376
distribution = self._get_power_distribution(request, pairs_data)
334377
except ValueError as err:
@@ -452,11 +495,16 @@ def _get_power_distribution(
452495

453496
return result
454497

455-
def _check_request(self, request: Request) -> Optional[Result]:
498+
def _check_request(
499+
self,
500+
request: Request,
501+
pairs_data: List[InvBatPair],
502+
) -> Optional[Result]:
456503
"""Check whether the given request if correct.
457504
458505
Args:
459506
request: request to check
507+
pairs_data: list of battery and adjacent inverter data pairs.
460508
461509
Returns:
462510
Result for the user if the request is wrong, None otherwise.
@@ -472,19 +520,24 @@ def _check_request(self, request: Request) -> Optional[Result]:
472520
)
473521
return Error(request=request, msg=msg)
474522

475-
if not request.adjust_power:
476-
if request.power < 0:
477-
bound = self._get_lower_bound(
478-
request.batteries, request.include_broken_batteries
479-
)
480-
if request.power < bound:
481-
return OutOfBound(request=request, bound=bound)
482-
else:
483-
bound = self._get_upper_bound(
484-
request.batteries, request.include_broken_batteries
485-
)
486-
if request.power > bound:
487-
return OutOfBound(request=request, bound=bound)
523+
bounds = self._get_bounds(pairs_data)
524+
if request.adjust_power:
525+
# Automatic power adjustments can only bring down the requested power down
526+
# to the inclusion bounds.
527+
#
528+
# If the requested power is in the exclusion bounds, it is NOT possible to
529+
# increase it so that it is outside the exclusion bounds.
530+
if bounds.exclusion_lower < request.power < bounds.exclusion_upper:
531+
return OutOfBound(request=request, bound=bounds)
532+
else:
533+
in_lower_range = (
534+
bounds.inclusion_lower <= request.power <= bounds.exclusion_lower
535+
)
536+
in_upper_range = (
537+
bounds.exclusion_upper <= request.power <= bounds.inclusion_upper
538+
)
539+
if not (in_lower_range or in_upper_range):
540+
return OutOfBound(request=request, bound=bounds)
488541

489542
return None
490543

src/frequenz/sdk/actor/power_distributing/result.py

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,16 @@ class Error(Result):
7777
"""The error message explaining why error happened."""
7878

7979

80+
@dataclasses.dataclass
81+
class PowerBounds:
82+
"""Inclusion and exclusion power bounds for requested batteries."""
83+
84+
inclusion_lower: float
85+
exclusion_lower: float
86+
exclusion_upper: float
87+
inclusion_upper: float
88+
89+
8090
@dataclasses.dataclass
8191
class OutOfBound(Result):
8292
"""Result returned when the power was not set because it was out of bounds.
@@ -85,8 +95,8 @@ class OutOfBound(Result):
8595
`adjust_power = False` and the requested power is not within the batteries bounds.
8696
"""
8797

88-
bound: float
89-
"""The total power bound for the requested batteries.
98+
bound: PowerBounds
99+
"""The power bounds for the requested batteries.
90100
91101
If the requested power negative, then this value is the lower bound.
92102
Otherwise it is upper bound.

0 commit comments

Comments
 (0)