Skip to content

Commit 8a47f2d

Browse files
committed
fixup-WIP
1 parent 6244353 commit 8a47f2d

File tree

2 files changed

+170
-65
lines changed

2 files changed

+170
-65
lines changed

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ dependencies = [
3131
# (plugins.mkdocstrings.handlers.python.import)
3232
#"frequenz-client-microgrid >= 0.18.0, < 0.19.0",
3333
"frequenz-client-microgrid @ git+https://github.com/frequenz-floss/frequenz-client-microgrid-python@refs/heads/v0.18.x",
34-
"frequenz-client-common >= 0.3.2, < 0.4.0",
34+
"frequenz-client-common >= 0.3.6, < 0.4.0",
3535
"frequenz-channels >= 1.6.1, < 2.0.0",
3636
"frequenz-quantities[marshmallow] >= 1.0.0, < 2.0.0",
3737
"networkx >= 2.8, < 4",

tests/timeseries/_pv_pool/test_pv_pool_control_methods.py

Lines changed: 169 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
from pytest_mock import MockerFixture
1818

1919
from frequenz.sdk import microgrid
20+
from frequenz.sdk.microgrid import _power_distributing
2021
from frequenz.sdk.microgrid._data_pipeline import _DataPipeline
2122
from frequenz.sdk.timeseries import ResamplerConfig2
2223
from frequenz.sdk.timeseries.pv_pool import PVPoolReport
@@ -75,8 +76,6 @@ async def _init_pv_inverters(self, mocks: _Mocks) -> None:
7576
active_power=0.0,
7677
active_power_inclusion_lower_bound=-10000.0 * (idx + 1),
7778
active_power_inclusion_upper_bound=0.0,
78-
active_power_exclusion_lower_bound=0.0,
79-
active_power_exclusion_upper_bound=0.0,
8079
),
8180
0.05,
8281
)
@@ -98,25 +97,30 @@ async def _fail_pv_inverters(
9897
active_power=0.0,
9998
active_power_inclusion_lower_bound=-10000.0 * (idx + 1),
10099
active_power_inclusion_upper_bound=0.0,
101-
active_power_exclusion_lower_bound=0.0,
102-
active_power_exclusion_upper_bound=0.0,
103100
),
104101
)
105102

106-
def _assert_report(
103+
def _assert_report( # pylint: disable=too-many-arguments
107104
self,
108105
report: PVPoolReport | None,
109106
*,
110107
power: float | None,
111108
lower: float,
112109
upper: float,
110+
dist_result: _power_distributing.Result | None = None,
111+
expected_result_pred: (
112+
typing.Callable[[_power_distributing.Result], bool] | None
113+
) = None,
113114
) -> None:
114115
assert report is not None and report.target_power == (
115116
Power.from_watts(power) if power is not None else None
116117
)
117118
assert report.bounds is not None
118119
assert report.bounds.lower == Power.from_watts(lower)
119120
assert report.bounds.upper == Power.from_watts(upper)
121+
if expected_result_pred is not None:
122+
assert dist_result is not None
123+
assert expected_result_pred(dist_result)
120124

121125
async def _recv_reports_until(
122126
self,
@@ -126,13 +130,16 @@ async def _recv_reports_until(
126130
"""Receive reports until the given condition is met."""
127131
max_reports = 10
128132
ctr = 0
133+
latest_report: PVPoolReport | None = None
129134
while ctr < max_reports:
130135
ctr += 1
131-
async with asyncio.timeout(10.0):
132-
report = await bounds_rx.receive()
133-
if check(report):
134-
return report
135-
return None
136+
print(f"[TEST] Waiting for report #{ctr}...")
137+
latest_report = await bounds_rx.receive()
138+
print(f"[TEST] Received report #{ctr}: {latest_report}")
139+
if check(latest_report):
140+
break
141+
142+
return latest_report
136143

137144
async def test_setting_power( # pylint: disable=too-many-statements
138145
self,
@@ -148,81 +155,117 @@ async def test_setting_power( # pylint: disable=too-many-statements
148155
await self._init_pv_inverters(mocks)
149156
pv_pool = microgrid.new_pv_pool(priority=5)
150157
bounds_rx = pv_pool.power_status.new_receiver()
151-
report = await self._recv_reports_until(
158+
latest_report = await self._recv_reports_until(
152159
bounds_rx,
153-
lambda x: x.bounds is not None and x.bounds.lower.as_watts() == -100_000.0,
160+
lambda x: x.bounds is not None and x.bounds.lower.as_watts() == -100000.0,
154161
)
155-
assert report is not None, "No report meeting the condition was received"
156-
self._assert_report(report, power=None, lower=-100_000.0, upper=0.0)
157-
await pv_pool.propose_power(Power.from_watts(-80_000.0))
158-
report = await self._recv_reports_until(
162+
dist_results_rx = pv_pool.power_distribution_results.new_receiver()
163+
164+
self._assert_report(latest_report, power=None, lower=-100000.0, upper=0.0)
165+
await pv_pool.propose_power(Power.from_watts(-80000.0))
166+
await self._recv_reports_until(
159167
bounds_rx,
160168
lambda x: x.target_power is not None
161-
and x.target_power.as_watts() == -80_000.0,
169+
and x.target_power.as_watts() == -80000.0,
170+
)
171+
self._assert_report(
172+
await bounds_rx.receive(), power=-80000.0, lower=-100000.0, upper=0.0
162173
)
163-
assert report is not None, "No report meeting the condition was received"
164-
self._assert_report(report, power=-80_000.0, lower=-100_000.0, upper=0.0)
165174
await asyncio.sleep(0.0)
166175

167176
# Components are set initial power
168177
assert set_power.call_count == 4
169178
inv_ids = mocks.microgrid.pv_inverter_ids
170179
assert sorted(set_power.call_args_list, key=lambda x: x.args[0]) == [
171-
mocker.call(inv_ids[0], -10_000.0),
172-
mocker.call(inv_ids[1], -20_000.0),
173-
mocker.call(inv_ids[2], -25_000.0),
174-
mocker.call(inv_ids[3], -25_000.0),
180+
mocker.call(inv_ids[0], -10000.0),
181+
mocker.call(inv_ids[1], -20000.0),
182+
mocker.call(inv_ids[2], -25000.0),
183+
mocker.call(inv_ids[3], -25000.0),
175184
]
185+
dist_results = await dist_results_rx.receive()
186+
assert isinstance(
187+
dist_results, _power_distributing.Success
188+
), f"Expected a success, got {dist_results}"
189+
assert dist_results.succeeded_power == Power.from_watts(-80000.0)
190+
assert dist_results.excess_power == Power.zero()
191+
assert dist_results.succeeded_components == {
192+
ComponentId(8),
193+
ComponentId(18),
194+
ComponentId(28),
195+
ComponentId(38),
196+
}
176197

177198
set_power.reset_mock()
178-
await pv_pool.propose_power(Power.from_watts(-4_000.0))
179-
report = await self._recv_reports_until(
199+
await pv_pool.propose_power(Power.from_watts(-4000.0))
200+
await self._recv_reports_until(
180201
bounds_rx,
181202
lambda x: x.target_power is not None
182-
and x.target_power.as_watts() == -4_000.0,
203+
and x.target_power.as_watts() == -4000.0,
204+
)
205+
self._assert_report(
206+
await bounds_rx.receive(), power=-4000.0, lower=-100000.0, upper=0.0
183207
)
184-
assert report is not None, "No report meeting the condition was received"
185-
self._assert_report(report, power=-4_000.0, lower=-100_000.0, upper=0.0)
186208
await asyncio.sleep(0.0)
187209

188210
# Components are set initial power
189211
assert set_power.call_count == 4
190212
inv_ids = mocks.microgrid.pv_inverter_ids
191213
assert sorted(set_power.call_args_list, key=lambda x: x.args[0]) == [
192-
mocker.call(inv_ids[0], -1_000.0),
193-
mocker.call(inv_ids[1], -1_000.0),
194-
mocker.call(inv_ids[2], -1_000.0),
195-
mocker.call(inv_ids[3], -1_000.0),
214+
mocker.call(inv_ids[0], -1000.0),
215+
mocker.call(inv_ids[1], -1000.0),
216+
mocker.call(inv_ids[2], -1000.0),
217+
mocker.call(inv_ids[3], -1000.0),
196218
]
219+
dist_results = await dist_results_rx.receive()
220+
assert isinstance(
221+
dist_results, _power_distributing.Success
222+
), f"Expected a success, got {dist_results}"
223+
assert dist_results.succeeded_power == Power.from_watts(-4000.0)
224+
assert dist_results.excess_power == Power.zero()
225+
assert dist_results.succeeded_components == {
226+
ComponentId(8),
227+
ComponentId(18),
228+
ComponentId(28),
229+
ComponentId(38),
230+
}
197231

198232
# After failing 1 inverter, bounds should go down and power shouldn't be
199233
# distributed to that inverter.
200234
print(f"\n[TEST] ========== FAILING INVERTER {inv_ids[1]} ==========")
201235
await self._fail_pv_inverters([inv_ids[1]], mocks)
202236
print("[TEST] Waiting for bounds to update to -80_000.0...")
203-
report = await self._recv_reports_until(
237+
await self._recv_reports_until(
204238
bounds_rx,
205-
lambda x: x.bounds is not None and x.bounds.lower.as_watts() == -80_000.0,
239+
lambda x: x.bounds is not None and x.bounds.lower.as_watts() == -80000.0,
240+
)
241+
self._assert_report(
242+
await bounds_rx.receive(), power=-4000.0, lower=-80000.0, upper=0.0
206243
)
207-
assert report is not None, "No report meeting the condition was received"
208-
print(f"[TEST] Got bounds report: {report}")
209-
self._assert_report(report, power=-4_000.0, lower=-80_000.0, upper=0.0)
244+
dist_results = await dist_results_rx.receive()
245+
assert isinstance(
246+
dist_results, _power_distributing.Success
247+
), f"Expected a success, got {dist_results}"
248+
assert dist_results.succeeded_power == Power.from_watts(-4000.0)
249+
assert dist_results.excess_power == Power.zero()
250+
assert dist_results.succeeded_components == {
251+
ComponentId(8),
252+
ComponentId(28),
253+
ComponentId(38),
254+
}
210255

211256
set_power.reset_mock()
212257
print(f"\n[TEST] ========== PROPOSING NEW POWER -70_000.0 ==========")
213-
print(f"[TEST] set_power call count before proposal: {set_power.call_count}")
214-
await pv_pool.propose_power(Power.from_watts(-70_000.0))
258+
await pv_pool.propose_power(Power.from_watts(-70000.0))
215259
print("[TEST] Waiting for target power to be -70_000.0...")
216-
report = await self._recv_reports_until(
260+
await self._recv_reports_until(
217261
bounds_rx,
218262
lambda x: x.target_power is not None
219-
and x.target_power.as_watts() == -70_000.0,
263+
and x.target_power.as_watts() == -70000.0,
220264
)
221265

222-
assert report is not None, "No report meeting the condition was received"
223-
print(f"[TEST] Got target power report: {report}")
224-
self._assert_report(report, power=-70_000.0, lower=-80_000.0, upper=0.0)
225-
print(repr(report))
266+
self._assert_report(
267+
await bounds_rx.receive(), power=-70000.0, lower=-80000.0, upper=0.0
268+
)
226269
await asyncio.sleep(0.0)
227270

228271
# Components are set initial power
@@ -232,51 +275,89 @@ async def test_setting_power( # pylint: disable=too-many-statements
232275
assert set_power.call_count == 3
233276
inv_ids = mocks.microgrid.pv_inverter_ids
234277
assert sorted(set_power.call_args_list, key=lambda x: x.args[0]) == [
235-
mocker.call(inv_ids[0], -10_000.0),
236-
mocker.call(inv_ids[2], -30_000.0),
237-
mocker.call(inv_ids[3], -30_000.0),
278+
mocker.call(inv_ids[0], -10000.0),
279+
mocker.call(inv_ids[2], -30000.0),
280+
mocker.call(inv_ids[3], -30000.0),
238281
]
282+
dist_results = await dist_results_rx.receive()
283+
assert isinstance(
284+
dist_results, _power_distributing.Success
285+
), f"Expected a success, got {dist_results}"
286+
assert dist_results.succeeded_power == Power.from_watts(-70000.0)
287+
assert dist_results.excess_power == Power.zero()
288+
assert dist_results.succeeded_components == {
289+
ComponentId(8),
290+
ComponentId(28),
291+
ComponentId(38),
292+
}
239293

240294
# After the failed inverter recovers, bounds should go back up and power
241295
# should be distributed to all inverters
242296
await self._fail_pv_inverters([], mocks)
243-
report = await self._recv_reports_until(
297+
await self._recv_reports_until(
244298
bounds_rx,
245-
lambda x: x.bounds is not None and x.bounds.lower.as_watts() == -100_000.0,
299+
lambda x: x.bounds is not None and x.bounds.lower.as_watts() == -100000.0,
300+
)
301+
self._assert_report(
302+
await bounds_rx.receive(), power=-70000.0, lower=-100000.0, upper=0.0
246303
)
247-
assert report is not None, "No report meeting the condition was received"
248-
self._assert_report(report, power=-70_000.0, lower=-100_000.0, upper=0.0)
304+
dist_results = await dist_results_rx.receive()
305+
assert isinstance(
306+
dist_results, _power_distributing.Success
307+
), f"Expected a success, got {dist_results}"
308+
assert dist_results.succeeded_power == Power.from_watts(-70000.0)
309+
assert dist_results.excess_power == Power.zero()
310+
assert dist_results.succeeded_components == {
311+
ComponentId(8),
312+
ComponentId(18),
313+
ComponentId(28),
314+
ComponentId(38),
315+
}
249316

250317
set_power.reset_mock()
251-
await pv_pool.propose_power(Power.from_watts(-90_000.0))
252-
report = await self._recv_reports_until(
318+
await pv_pool.propose_power(Power.from_watts(-200000.0))
319+
await self._recv_reports_until(
253320
bounds_rx,
254321
lambda x: x.target_power is not None
255-
and x.target_power.as_watts() == -90_000.0,
322+
and x.target_power.as_watts() == -100000.0,
256323
)
257324

258-
assert report is not None, "No report meeting the condition was received"
259-
self._assert_report(report, power=-90_000.0, lower=-100_000.0, upper=0.0)
325+
self._assert_report(
326+
await bounds_rx.receive(), power=-100000.0, lower=-100000.0, upper=0.0
327+
)
260328
await asyncio.sleep(0.0)
261329

262330
assert set_power.call_count == 4
263331
inv_ids = mocks.microgrid.pv_inverter_ids
264332
assert sorted(set_power.call_args_list, key=lambda x: x.args[0]) == [
265-
mocker.call(inv_ids[0], -10_000.0),
266-
mocker.call(inv_ids[1], -20_000.0),
267-
mocker.call(inv_ids[2], -30_000.0),
268-
mocker.call(inv_ids[3], -30_000.0),
333+
mocker.call(inv_ids[0], -10000.0),
334+
mocker.call(inv_ids[1], -20000.0),
335+
mocker.call(inv_ids[2], -30000.0),
336+
mocker.call(inv_ids[3], -40000.0),
269337
]
338+
dist_results = await dist_results_rx.receive()
339+
assert isinstance(
340+
dist_results, _power_distributing.Success
341+
), f"Expected a success, got {dist_results}"
342+
assert dist_results.succeeded_power == Power.from_watts(-100000.0)
343+
assert dist_results.excess_power == Power.zero()
344+
assert dist_results.succeeded_components == {
345+
ComponentId(8),
346+
ComponentId(18),
347+
ComponentId(28),
348+
ComponentId(38),
349+
}
270350

271351
# Setting 0 power should set all inverters to 0
272352
set_power.reset_mock()
273353
await pv_pool.propose_power(Power.zero())
274-
report = await self._recv_reports_until(
354+
await self._recv_reports_until(
275355
bounds_rx,
276356
lambda x: x.target_power is not None and x.target_power.as_watts() == 0.0,
277357
)
278-
assert report is not None, "No report meeting the condition was received"
279-
self._assert_report(report, power=0.0, lower=-100_000.0, upper=0.0)
358+
self._assert_report(
359+
await bounds_rx.receive(), power=0.0, lower=-100000.0, upper=0.0
360+
)
280361
await asyncio.sleep(0.0)
281362

282363
assert set_power.call_count == 4
@@ -287,6 +368,18 @@ async def test_setting_power( # pylint: disable=too-many-statements
287368
mocker.call(inv_ids[2], 0.0),
288369
mocker.call(inv_ids[3], 0.0),
289370
]
371+
dist_results = await dist_results_rx.receive()
372+
assert isinstance(
373+
dist_results, _power_distributing.Success
374+
), f"Expected a success, got {dist_results}"
375+
assert dist_results.succeeded_power == Power.zero()
376+
assert dist_results.excess_power == Power.zero()
377+
assert dist_results.succeeded_components == {
378+
ComponentId(8),
379+
ComponentId(18),
380+
ComponentId(28),
381+
ComponentId(38),
382+
}
290383

291384
# Resetting the power should lead to default (full) power getting set for all
292385
# inverters.
@@ -307,3 +400,15 @@ async def test_setting_power( # pylint: disable=too-many-statements
307400
mocker.call(inv_ids[2], -30_000.0),
308401
mocker.call(inv_ids[3], -40_000.0),
309402
]
403+
dist_results = await dist_results_rx.receive()
404+
assert isinstance(
405+
dist_results, _power_distributing.Success
406+
), f"Expected a success, got {dist_results}"
407+
assert dist_results.succeeded_power == Power.from_watts(-100000.0)
408+
assert dist_results.excess_power == Power.zero()
409+
assert dist_results.succeeded_components == {
410+
ComponentId(8),
411+
ComponentId(18),
412+
ComponentId(28),
413+
ComponentId(38),
414+
}

0 commit comments

Comments
 (0)