Skip to content

Commit d877386

Browse files
committed
Fix succeeded_power calculation in PV power distribution
This was reported incorrectly earlier using a variable that was never set. That variable and related (unused) channels have been removed. This also updates the PV pool tests to ensure that the distribution results are accurate. Signed-off-by: Sahas Subramanian <[email protected]>
1 parent 255fd11 commit d877386

File tree

2 files changed

+103
-10
lines changed

2 files changed

+103
-10
lines changed

src/frequenz/sdk/microgrid/_power_distributing/_component_managers/_pv_inverter_manager/_pv_inverter_manager.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
import logging
99
from datetime import timedelta
1010

11-
from frequenz.channels import Broadcast, LatestValueCache, Sender
11+
from frequenz.channels import LatestValueCache, Sender
1212
from frequenz.client.microgrid import (
1313
ApiClientError,
1414
ComponentCategory,
@@ -67,9 +67,6 @@ def __init__(
6767
self._component_data_caches: dict[
6868
ComponentId, LatestValueCache[InverterData]
6969
] = {}
70-
self._target_power = Power.zero()
71-
self._target_power_channel = Broadcast[Request](name="target_power")
72-
self._target_power_tx = self._target_power_channel.new_sender()
7370
self._task: asyncio.Task[None] | None = None
7471

7572
@override
@@ -241,7 +238,7 @@ async def _set_api_power( # pylint: disable=too-many-locals
241238
failed_components=failed_components,
242239
succeeded_components=succeeded_components,
243240
failed_power=failed_power,
244-
succeeded_power=self._target_power - failed_power,
241+
succeeded_power=request.power - failed_power - remaining_power,
245242
excess_power=remaining_power,
246243
request=request,
247244
)
@@ -250,7 +247,7 @@ async def _set_api_power( # pylint: disable=too-many-locals
250247
await self._results_sender.send(
251248
Success(
252249
succeeded_components=succeeded_components,
253-
succeeded_power=self._target_power,
250+
succeeded_power=request.power - remaining_power,
254251
excess_power=remaining_power,
255252
request=request,
256253
)

tests/timeseries/_pv_pool/test_pv_pool_control_methods.py

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ async def test_setting_power( # pylint: disable=too-many-statements
151151
bounds_rx,
152152
lambda x: x.bounds is not None and x.bounds.lower.as_watts() == -100000.0,
153153
)
154+
dist_results_rx = pv_pool.power_distribution_results.new_receiver()
155+
154156
self._assert_report(latest_report, power=None, lower=-100000.0, upper=0.0)
155157
await pv_pool.propose_power(Power.from_watts(-80000.0))
156158
await self._recv_reports_until(
@@ -172,6 +174,18 @@ async def test_setting_power( # pylint: disable=too-many-statements
172174
mocker.call(inv_ids[2], -25000.0),
173175
mocker.call(inv_ids[3], -25000.0),
174176
]
177+
dist_results = await dist_results_rx.receive()
178+
assert isinstance(
179+
dist_results, _power_distributing.Success
180+
), f"Expected a success, got {dist_results}"
181+
assert dist_results.succeeded_power == Power.from_watts(-80000.0)
182+
assert dist_results.excess_power == Power.zero()
183+
assert dist_results.succeeded_components == {
184+
ComponentId(8),
185+
ComponentId(18),
186+
ComponentId(28),
187+
ComponentId(38),
188+
}
175189

176190
set_power.reset_mock()
177191
await pv_pool.propose_power(Power.from_watts(-4000.0))
@@ -194,6 +208,18 @@ async def test_setting_power( # pylint: disable=too-many-statements
194208
mocker.call(inv_ids[2], -1000.0),
195209
mocker.call(inv_ids[3], -1000.0),
196210
]
211+
dist_results = await dist_results_rx.receive()
212+
assert isinstance(
213+
dist_results, _power_distributing.Success
214+
), f"Expected a success, got {dist_results}"
215+
assert dist_results.succeeded_power == Power.from_watts(-4000.0)
216+
assert dist_results.excess_power == Power.zero()
217+
assert dist_results.succeeded_components == {
218+
ComponentId(8),
219+
ComponentId(18),
220+
ComponentId(28),
221+
ComponentId(38),
222+
}
197223

198224
# After failing 1 inverter, bounds should go down and power shouldn't be
199225
# distributed to that inverter.
@@ -205,6 +231,17 @@ async def test_setting_power( # pylint: disable=too-many-statements
205231
self._assert_report(
206232
await bounds_rx.receive(), power=-4000.0, lower=-80000.0, upper=0.0
207233
)
234+
dist_results = await dist_results_rx.receive()
235+
assert isinstance(
236+
dist_results, _power_distributing.Success
237+
), f"Expected a success, got {dist_results}"
238+
assert dist_results.succeeded_power == Power.from_watts(-4000.0)
239+
assert dist_results.excess_power == Power.zero()
240+
assert dist_results.succeeded_components == {
241+
ComponentId(8),
242+
ComponentId(28),
243+
ComponentId(38),
244+
}
208245

209246
set_power.reset_mock()
210247
await pv_pool.propose_power(Power.from_watts(-70000.0))
@@ -227,6 +264,17 @@ async def test_setting_power( # pylint: disable=too-many-statements
227264
mocker.call(inv_ids[2], -30000.0),
228265
mocker.call(inv_ids[3], -30000.0),
229266
]
267+
dist_results = await dist_results_rx.receive()
268+
assert isinstance(
269+
dist_results, _power_distributing.Success
270+
), f"Expected a success, got {dist_results}"
271+
assert dist_results.succeeded_power == Power.from_watts(-70000.0)
272+
assert dist_results.excess_power == Power.zero()
273+
assert dist_results.succeeded_components == {
274+
ComponentId(8),
275+
ComponentId(28),
276+
ComponentId(38),
277+
}
230278

231279
# After the failed inverter recovers, bounds should go back up and power
232280
# should be distributed to all inverters
@@ -238,17 +286,29 @@ async def test_setting_power( # pylint: disable=too-many-statements
238286
self._assert_report(
239287
await bounds_rx.receive(), power=-70000.0, lower=-100000.0, upper=0.0
240288
)
289+
dist_results = await dist_results_rx.receive()
290+
assert isinstance(
291+
dist_results, _power_distributing.Success
292+
), f"Expected a success, got {dist_results}"
293+
assert dist_results.succeeded_power == Power.from_watts(-70000.0)
294+
assert dist_results.excess_power == Power.zero()
295+
assert dist_results.succeeded_components == {
296+
ComponentId(8),
297+
ComponentId(18),
298+
ComponentId(28),
299+
ComponentId(38),
300+
}
241301

242302
set_power.reset_mock()
243-
await pv_pool.propose_power(Power.from_watts(-90000.0))
303+
await pv_pool.propose_power(Power.from_watts(-200000.0))
244304
await self._recv_reports_until(
245305
bounds_rx,
246306
lambda x: x.target_power is not None
247-
and x.target_power.as_watts() == -90000.0,
307+
and x.target_power.as_watts() == -100000.0,
248308
)
249309

250310
self._assert_report(
251-
await bounds_rx.receive(), power=-90000.0, lower=-100000.0, upper=0.0
311+
await bounds_rx.receive(), power=-100000.0, lower=-100000.0, upper=0.0
252312
)
253313
await asyncio.sleep(0.0)
254314

@@ -258,8 +318,20 @@ async def test_setting_power( # pylint: disable=too-many-statements
258318
mocker.call(inv_ids[0], -10000.0),
259319
mocker.call(inv_ids[1], -20000.0),
260320
mocker.call(inv_ids[2], -30000.0),
261-
mocker.call(inv_ids[3], -30000.0),
321+
mocker.call(inv_ids[3], -40000.0),
262322
]
323+
dist_results = await dist_results_rx.receive()
324+
assert isinstance(
325+
dist_results, _power_distributing.Success
326+
), f"Expected a success, got {dist_results}"
327+
assert dist_results.succeeded_power == Power.from_watts(-100000.0)
328+
assert dist_results.excess_power == Power.zero()
329+
assert dist_results.succeeded_components == {
330+
ComponentId(8),
331+
ComponentId(18),
332+
ComponentId(28),
333+
ComponentId(38),
334+
}
263335

264336
# Setting 0 power should set all inverters to 0
265337
set_power.reset_mock()
@@ -281,6 +353,18 @@ async def test_setting_power( # pylint: disable=too-many-statements
281353
mocker.call(inv_ids[2], 0.0),
282354
mocker.call(inv_ids[3], 0.0),
283355
]
356+
dist_results = await dist_results_rx.receive()
357+
assert isinstance(
358+
dist_results, _power_distributing.Success
359+
), f"Expected a success, got {dist_results}"
360+
assert dist_results.succeeded_power == Power.zero()
361+
assert dist_results.excess_power == Power.zero()
362+
assert dist_results.succeeded_components == {
363+
ComponentId(8),
364+
ComponentId(18),
365+
ComponentId(28),
366+
ComponentId(38),
367+
}
284368

285369
# Resetting the power should lead to default (full) power getting set for all
286370
# inverters.
@@ -301,3 +385,15 @@ async def test_setting_power( # pylint: disable=too-many-statements
301385
mocker.call(inv_ids[2], -30_000.0),
302386
mocker.call(inv_ids[3], -40_000.0),
303387
]
388+
dist_results = await dist_results_rx.receive()
389+
assert isinstance(
390+
dist_results, _power_distributing.Success
391+
), f"Expected a success, got {dist_results}"
392+
assert dist_results.succeeded_power == Power.from_watts(-100000.0)
393+
assert dist_results.excess_power == Power.zero()
394+
assert dist_results.succeeded_components == {
395+
ComponentId(8),
396+
ComponentId(18),
397+
ComponentId(28),
398+
ComponentId(38),
399+
}

0 commit comments

Comments
 (0)