Skip to content

Commit 7be3518

Browse files
committed
Expose power distribution results through PowerManager reporting streams
Failure messages need to trigger a resending of target power to the PowerDistributor. This is done in a subsequent commit. Signed-off-by: Sahas Subramanian <[email protected]>
1 parent a4d6268 commit 7be3518

File tree

6 files changed

+48
-6
lines changed

6 files changed

+48
-6
lines changed

src/frequenz/sdk/actor/_power_managing/_base_classes.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
if typing.TYPE_CHECKING:
1818
from ...timeseries.battery_pool import PowerMetrics
19+
from .. import power_distributing
1920

2021

2122
@dataclasses.dataclass(frozen=True)
@@ -65,6 +66,12 @@ class Report:
6566
priorities.
6667
"""
6768

69+
distribution_result: power_distributing.Result | None
70+
"""The result of the last power distribution.
71+
72+
This is `None` if no power distribution has been performed yet.
73+
"""
74+
6875

6976
@dataclasses.dataclass(frozen=True)
7077
class Proposal:
@@ -141,14 +148,19 @@ def get_target_power(
141148
# It can be loosened up when more algorithms are added.
142149
@abc.abstractmethod
143150
def get_status(
144-
self, battery_ids: frozenset[int], priority: int, system_bounds: PowerMetrics
151+
self,
152+
battery_ids: frozenset[int],
153+
priority: int,
154+
system_bounds: PowerMetrics,
155+
distribution_result: power_distributing.Result | None,
145156
) -> Report:
146157
"""Get the bounds for a set of batteries, for the given priority.
147158
148159
Args:
149160
battery_ids: The IDs of the batteries to get the bounds for.
150161
priority: The priority of the actor for which the bounds are requested.
151162
system_bounds: The system bounds for the batteries.
163+
distribution_result: The result of the last power distribution.
152164
153165
Returns:
154166
The bounds for the batteries.

src/frequenz/sdk/actor/_power_managing/_matryoshka.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
if typing.TYPE_CHECKING:
1919
from ...timeseries.battery_pool import PowerMetrics
20+
from .. import power_distributing
2021

2122
_logger = logging.getLogger(__name__)
2223

@@ -140,22 +141,29 @@ def get_target_power(
140141

141142
@override
142143
def get_status(
143-
self, battery_ids: frozenset[int], priority: int, system_bounds: PowerMetrics
144+
self,
145+
battery_ids: frozenset[int],
146+
priority: int,
147+
system_bounds: PowerMetrics,
148+
distribution_result: power_distributing.Result | None,
144149
) -> Report:
145150
"""Get the bounds for the algorithm.
146151
147152
Args:
148153
battery_ids: The IDs of the batteries to get the bounds for.
149154
priority: The priority of the actor for which the bounds are requested.
150155
system_bounds: The system bounds for the batteries.
156+
distribution_result: The result of the last power distribution.
151157
152158
Returns:
153159
The target power and the available bounds for the given batteries, for
154160
the given priority.
155161
"""
156162
target_power = self._target_power.get(battery_ids)
157163
if system_bounds.inclusion_bounds is None:
158-
return Report(target_power, None, system_bounds.exclusion_bounds)
164+
return Report(
165+
target_power, None, system_bounds.exclusion_bounds, distribution_result
166+
)
159167

160168
lower_bound = system_bounds.inclusion_bounds.lower
161169
upper_bound = system_bounds.inclusion_bounds.upper
@@ -178,4 +186,5 @@ def get_status(
178186
lower=lower_bound, upper=upper_bound
179187
),
180188
exclusion_bounds=system_bounds.exclusion_bounds,
189+
distribution_result=distribution_result,
181190
)

src/frequenz/sdk/actor/_power_managing/_power_managing_actor.py

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ def __init__( # pylint: disable=too-many-arguments
3434
proposals_receiver: Receiver[Proposal],
3535
bounds_subscription_receiver: Receiver[ReportRequest],
3636
power_distributing_requests_sender: Sender[power_distributing.Request],
37+
power_distributing_results_receiver: Receiver[power_distributing.Result],
3738
channel_registry: ChannelRegistry,
3839
algorithm: Algorithm = Algorithm.MATRYOSHKA,
3940
):
@@ -44,6 +45,8 @@ def __init__( # pylint: disable=too-many-arguments
4445
bounds_subscription_receiver: The receiver for bounds subscriptions.
4546
power_distributing_requests_sender: The sender for power distribution
4647
requests.
48+
power_distributing_results_receiver: The receiver for power distribution
49+
results.
4750
channel_registry: The channel registry.
4851
algorithm: The power management algorithm to use.
4952
@@ -58,12 +61,14 @@ def __init__( # pylint: disable=too-many-arguments
5861

5962
self._bounds_subscription_receiver = bounds_subscription_receiver
6063
self._power_distributing_requests_sender = power_distributing_requests_sender
64+
self._power_distributing_results_receiver = power_distributing_results_receiver
6165
self._channel_registry = channel_registry
6266
self._proposals_receiver = proposals_receiver
6367

6468
self._system_bounds: dict[frozenset[int], PowerMetrics] = {}
6569
self._bound_tracker_tasks: dict[frozenset[int], asyncio.Task[None]] = {}
6670
self._subscriptions: dict[frozenset[int], dict[int, Sender[Report]]] = {}
71+
self._distribution_results: dict[frozenset[int], power_distributing.Result] = {}
6772

6873
self._algorithm: BaseAlgorithm = Matryoshka()
6974

@@ -80,7 +85,13 @@ async def _send_reports(self, battery_ids: frozenset[int]) -> None:
8085
_logger.warning("PowerManagingActor: No bounds for %s", battery_ids)
8186
return
8287
for priority, sender in self._subscriptions.get(battery_ids, {}).items():
83-
await sender.send(self._algorithm.get_status(battery_ids, priority, bounds))
88+
status = self._algorithm.get_status(
89+
battery_ids,
90+
priority,
91+
bounds,
92+
self._distribution_results.get(battery_ids),
93+
)
94+
await sender.send(status)
8495

8596
async def _bounds_tracker(
8697
self,
@@ -161,7 +172,9 @@ async def _send_updated_target_power(
161172
async def _run(self) -> None:
162173
"""Run the power managing actor."""
163174
async for selected in select(
164-
self._proposals_receiver, self._bounds_subscription_receiver
175+
self._proposals_receiver,
176+
self._bounds_subscription_receiver,
177+
self._power_distributing_results_receiver,
165178
):
166179
if selected_from(selected, self._proposals_receiver):
167180
proposal = selected.value
@@ -188,3 +201,7 @@ async def _run(self) -> None:
188201

189202
if sub.battery_ids not in self._bound_tracker_tasks:
190203
self._add_bounds_tracker(sub.battery_ids)
204+
205+
elif selected_from(selected, self._power_distributing_results_receiver):
206+
result = selected.value
207+
self._distribution_results[frozenset(result.request.batteries)] = result

src/frequenz/sdk/microgrid/_data_pipeline.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -257,6 +257,9 @@ def _start_power_managing_actor(self) -> None:
257257
power_distributing_requests_sender=(
258258
self._power_distribution_requests_channel.new_sender()
259259
),
260+
power_distributing_results_receiver=(
261+
self._power_distribution_results_channel.new_receiver()
262+
),
260263
channel_registry=self._channel_registry,
261264
)
262265
self._power_managing_actor.start()

tests/actor/_power_managing/test_matryoshka.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def test_tgt_power(
5454
def test_bounds(
5555
priority: int, expected_power: float, expected_bounds: tuple[float, float]
5656
) -> None:
57-
report = algorithm.get_status(batteries, priority, system_bounds)
57+
report = algorithm.get_status(batteries, priority, system_bounds, None)
5858
assert report.target_power is not None and report.inclusion_bounds is not None
5959
assert report.target_power.as_watts() == expected_power
6060
assert report.inclusion_bounds.lower.as_watts() == expected_bounds[0]

tests/timeseries/_battery_pool/test_battery_pool_control_methods.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ def _make_report(
149149
lower=Power.from_watts(0.0),
150150
upper=Power.from_watts(0.0),
151151
),
152+
distribution_result=None,
152153
)
153154

154155
async def test_case_1(

0 commit comments

Comments
 (0)