Skip to content

Commit 39953a9

Browse files
committed
Move the PV power formula from logical_meter to PVPool
Also update the PV formula builder to use the component IDs from the PV pool if specified. Signed-off-by: Sahas Subramanian <[email protected]>
1 parent 2ad0473 commit 39953a9

File tree

6 files changed

+78
-51
lines changed

6 files changed

+78
-51
lines changed

src/frequenz/sdk/timeseries/formula_engine/_formula_generators/_pv_power_formula.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,15 @@ def generate( # noqa: DOC502
3838
)
3939

4040
component_graph = connection_manager.get().component_graph
41-
pv_components = component_graph.dfs(
42-
self._get_grid_component(),
43-
set(),
44-
component_graph.is_pv_chain,
45-
)
41+
component_ids = self._config.component_ids
42+
if component_ids:
43+
pv_components = component_graph.components(set(component_ids))
44+
else:
45+
pv_components = component_graph.dfs(
46+
self._get_grid_component(),
47+
set(),
48+
component_graph.is_pv_chain,
49+
)
4650

4751
if not pv_components:
4852
_logger.warning(

src/frequenz/sdk/timeseries/logical_meter/_logical_meter.py

Lines changed: 4 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
from .._quantities import Power, Quantity
1414
from ..formula_engine import FormulaEngine
1515
from ..formula_engine._formula_engine_pool import FormulaEnginePool
16-
from ..formula_engine._formula_generators import CHPPowerFormula, PVPowerFormula
16+
from ..formula_engine._formula_generators import CHPPowerFormula
1717

1818

1919
class LogicalMeter:
@@ -42,17 +42,18 @@ class LogicalMeter:
4242
)
4343
4444
logical_meter = microgrid.logical_meter()
45+
pv_pool = microgrid.pv_pool()
4546
grid = microgrid.grid()
4647
4748
# Get a receiver for a builtin formula
48-
pv_power_recv = logical_meter.pv_power.new_receiver()
49+
pv_power_recv = pv_pool.power.new_receiver()
4950
async for pv_power_sample in pv_power_recv:
5051
print(pv_power_sample)
5152
5253
# or compose formulas to create a new formula
5354
net_power_recv = (
5455
(
55-
grid.power - logical_meter.pv_power
56+
grid.power - pv_pool.power
5657
)
5758
.build("net_power")
5859
.new_receiver()
@@ -123,28 +124,6 @@ def start_formula(
123124
formula, component_metric_id, nones_are_zeros=nones_are_zeros
124125
)
125126

126-
@property
127-
def pv_power(self) -> FormulaEngine[Power]:
128-
"""Fetch the PV power in the microgrid.
129-
130-
This formula produces values that are in the Passive Sign Convention (PSC).
131-
132-
If a formula engine to calculate PV power is not already running, it will be
133-
started.
134-
135-
A receiver from the formula engine can be created using the `new_receiver`
136-
method.
137-
138-
Returns:
139-
A FormulaEngine that will calculate and stream PV total power.
140-
"""
141-
engine = self._formula_pool.from_power_formula_generator(
142-
"pv_power",
143-
PVPowerFormula,
144-
)
145-
assert isinstance(engine, FormulaEngine)
146-
return engine
147-
148127
@property
149128
def chp_power(self) -> FormulaEngine[Power]:
150129
"""Fetch the CHP power production in the microgrid.

src/frequenz/sdk/timeseries/pv_pool/_pv_pool.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
from ...timeseries import Bounds
1515
from .._base_types import SystemBounds
1616
from .._quantities import Power
17+
from ..formula_engine import FormulaEngine
18+
from ..formula_engine._formula_generators import FormulaGeneratorConfig, PVPowerFormula
1719
from ._pv_pool_reference_store import PVPoolReferenceStore
1820
from ._result_types import PVPoolReport
1921

@@ -131,6 +133,32 @@ def component_ids(self) -> abc.Set[int]:
131133
"""
132134
return self._pv_pool_ref.component_ids
133135

136+
@property
137+
def power(self) -> FormulaEngine[Power]:
138+
"""Fetch the total power for the EV Chargers in the pool.
139+
140+
This formula produces values that are in the Passive Sign Convention (PSC).
141+
142+
If a formula engine to calculate EV Charger power is not already running, it
143+
will be started.
144+
145+
A receiver from the formula engine can be created using the `new_receiver`
146+
method.
147+
148+
Returns:
149+
A FormulaEngine that will calculate and stream the total power of all EV
150+
Chargers.
151+
"""
152+
engine = self._pv_pool_ref.formula_pool.from_power_formula_generator(
153+
"pv_power",
154+
PVPowerFormula,
155+
FormulaGeneratorConfig(
156+
component_ids=self._pv_pool_ref.component_ids,
157+
),
158+
)
159+
assert isinstance(engine, FormulaEngine)
160+
return engine
161+
134162
@property
135163
def power_status(self) -> ReceiverFetcher[PVPoolReport]:
136164
"""Get a receiver to receive new power status reports when they change.
@@ -163,6 +191,10 @@ def power_status(self) -> ReceiverFetcher[PVPoolReport]:
163191
# https://github.com/frequenz-floss/frequenz-sdk-python/issues/823
164192
return typing.cast(ReceiverFetcher[PVPoolReport], channel)
165193

194+
async def stop(self) -> None:
195+
"""Stop all tasks and channels owned by the PVPool."""
196+
await self._pv_pool_ref.stop()
197+
166198
@property
167199
def _system_power_bounds(self) -> ReceiverFetcher[SystemBounds]:
168200
"""Return a receiver fetcher for the system power bounds."""

tests/timeseries/_formula_engine/test_formula_composition.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ async def test_formula_composition( # pylint: disable=too-many-locals
4040
battery_pool._battery_pool.stop # pylint: disable=protected-access
4141
)
4242

43+
pv_pool = microgrid.pv_pool()
44+
stack.push_async_callback(pv_pool.stop)
45+
4346
grid = microgrid.grid()
4447
stack.push_async_callback(grid.stop)
4548

@@ -51,9 +54,9 @@ async def test_formula_composition( # pylint: disable=too-many-locals
5154
)
5255
grid_power_recv = grid.power.new_receiver()
5356
battery_power_recv = battery_pool.power.new_receiver()
54-
pv_power_recv = logical_meter.pv_power.new_receiver()
57+
pv_power_recv = pv_pool.power.new_receiver()
5558

56-
engine = (logical_meter.pv_power + battery_pool.power).build("inv_power")
59+
engine = (pv_pool.power + battery_pool.power).build("inv_power")
5760
stack.push_async_callback(engine._stop) # pylint: disable=protected-access
5861

5962
inv_calc_recv = engine.new_receiver()
@@ -62,6 +65,7 @@ async def test_formula_composition( # pylint: disable=too-many-locals
6265
await mockgrid.mock_resampler.send_meter_power(
6366
[100.0, 10.0, 12.0, 14.0, -100.0, -200.0]
6467
)
68+
await mockgrid.mock_resampler.send_pv_inverter_power([-100.0, -200.0])
6569

6670
grid_pow = await grid_power_recv.receive()
6771
pv_pow = await pv_power_recv.receive()
@@ -116,12 +120,15 @@ async def test_formula_composition_missing_pv(self, mocker: MockerFixture) -> No
116120
battery_pool._battery_pool.stop # pylint: disable=protected-access
117121
)
118122

123+
pv_pool = microgrid.pv_pool()
124+
stack.push_async_callback(pv_pool.stop)
125+
119126
logical_meter = microgrid.logical_meter()
120127
stack.push_async_callback(logical_meter.stop)
121128

122129
battery_power_recv = battery_pool.power.new_receiver()
123-
pv_power_recv = logical_meter.pv_power.new_receiver()
124-
engine = (logical_meter.pv_power + battery_pool.power).build("inv_power")
130+
pv_power_recv = pv_pool.power.new_receiver()
131+
engine = (pv_pool.power + battery_pool.power).build("inv_power")
125132
stack.push_async_callback(engine._stop) # pylint: disable=protected-access
126133

127134
inv_calc_recv = engine.new_receiver()
@@ -156,18 +163,22 @@ async def test_formula_composition_missing_bat(self, mocker: MockerFixture) -> N
156163
stack.push_async_callback(
157164
battery_pool._battery_pool.stop # pylint: disable=protected-access
158165
)
166+
167+
pv_pool = microgrid.pv_pool()
168+
stack.push_async_callback(pv_pool.stop)
169+
159170
logical_meter = microgrid.logical_meter()
160171
stack.push_async_callback(logical_meter.stop)
161172

162173
battery_power_recv = battery_pool.power.new_receiver()
163-
pv_power_recv = logical_meter.pv_power.new_receiver()
164-
engine = (logical_meter.pv_power + battery_pool.power).build("inv_power")
174+
pv_power_recv = pv_pool.power.new_receiver()
175+
engine = (pv_pool.power + battery_pool.power).build("inv_power")
165176
stack.push_async_callback(engine._stop) # pylint: disable=protected-access
166177

167178
inv_calc_recv = engine.new_receiver()
168179

169180
for _ in range(10):
170-
await mockgrid.mock_resampler.send_meter_power(
181+
await mockgrid.mock_resampler.send_pv_inverter_power(
171182
[12.0 + count, 14.0 + count]
172183
)
173184
await mockgrid.mock_resampler.send_non_existing_component_value()

tests/timeseries/test_formula_formatter.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -123,15 +123,16 @@ async def test_higher_order_formula(self, mocker: MockerFixture) -> None:
123123
logical_meter = microgrid.logical_meter()
124124
stack.push_async_callback(logical_meter.stop)
125125

126+
pv_pool = microgrid.pv_pool()
127+
stack.push_async_callback(pv_pool.stop)
128+
126129
grid = microgrid.grid()
127130
stack.push_async_callback(grid.stop)
128131

129132
assert str(grid.power) == "#36 + #7 + #47 + #17 + #57 + #27"
130133

131-
composed_formula = (grid.power - logical_meter.pv_power).build(
132-
"grid_minus_pv"
133-
)
134+
composed_formula = (grid.power - pv_pool.power).build("grid_minus_pv")
134135
assert (
135136
str(composed_formula)
136-
== "[grid-power](#36 + #7 + #47 + #17 + #57 + #27) - [pv-power](#57 + #47)"
137+
== "[grid-power](#36 + #7 + #47 + #17 + #57 + #27) - [pv-power](#48 + #58)"
137138
)

tests/timeseries/test_logical_meter.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,11 @@ async def test_pv_power(self, mocker: MockerFixture) -> None:
4444
mockgrid.add_solar_inverters(2)
4545

4646
async with mockgrid, AsyncExitStack() as stack:
47-
logical_meter = microgrid.logical_meter()
48-
stack.push_async_callback(logical_meter.stop)
49-
pv_power_receiver = logical_meter.pv_power.new_receiver()
47+
pv_pool = microgrid.pv_pool()
48+
stack.push_async_callback(pv_pool.stop)
49+
pv_power_receiver = pv_pool.power.new_receiver()
5050

51-
await mockgrid.mock_resampler.send_meter_power([-1.0, -2.0])
51+
await mockgrid.mock_resampler.send_pv_inverter_power([-1.0, -2.0])
5252
assert (await pv_power_receiver.receive()).value == Power.from_watts(-3.0)
5353

5454
async def test_pv_power_no_meter(self, mocker: MockerFixture) -> None:
@@ -57,9 +57,9 @@ async def test_pv_power_no_meter(self, mocker: MockerFixture) -> None:
5757
mockgrid.add_solar_inverters(2, no_meter=True)
5858

5959
async with mockgrid, AsyncExitStack() as stack:
60-
logical_meter = microgrid.logical_meter()
61-
stack.push_async_callback(logical_meter.stop)
62-
pv_power_receiver = logical_meter.pv_power.new_receiver()
60+
pv_pool = microgrid.pv_pool()
61+
stack.push_async_callback(pv_pool.stop)
62+
pv_power_receiver = pv_pool.power.new_receiver()
6363

6464
await mockgrid.mock_resampler.send_pv_inverter_power([-1.0, -2.0])
6565
assert (await pv_power_receiver.receive()).value == Power.from_watts(-3.0)
@@ -70,9 +70,9 @@ async def test_pv_power_no_pv_components(self, mocker: MockerFixture) -> None:
7070
MockMicrogrid(grid_meter=True, mocker=mocker) as mockgrid,
7171
AsyncExitStack() as stack,
7272
):
73-
logical_meter = microgrid.logical_meter()
74-
stack.push_async_callback(logical_meter.stop)
75-
pv_power_receiver = logical_meter.pv_power.new_receiver()
73+
pv_pool = microgrid.pv_pool()
74+
stack.push_async_callback(pv_pool.stop)
75+
pv_power_receiver = pv_pool.power.new_receiver()
7676

7777
await mockgrid.mock_resampler.send_non_existing_component_value()
7878
assert (await pv_power_receiver.receive()).value == Power.zero()

0 commit comments

Comments
 (0)