-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Generate formulas automatically from the component graph (#103)
- Loading branch information
Showing
16 changed files
with
816 additions
and
178 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
31 changes: 31 additions & 0 deletions
31
src/frequenz/sdk/timeseries/logical_meter/_formula_generators/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# License: MIT | ||
# Copyright © 2022 Frequenz Energy-as-a-Service GmbH | ||
|
||
"""Generators for formulas from component graphs.""" | ||
|
||
from ._battery_power_formula import BatteryPowerFormula | ||
from ._formula_generator import ( | ||
ComponentNotFound, | ||
FormulaGenerationError, | ||
FormulaGenerator, | ||
) | ||
from ._grid_power_formula import GridPowerFormula | ||
from ._pv_power_formula import PVPowerFormula | ||
|
||
__all__ = [ | ||
# | ||
# Base class | ||
# | ||
"FormulaGenerator", | ||
# | ||
# Formula generators | ||
# | ||
"GridPowerFormula", | ||
"BatteryPowerFormula", | ||
"PVPowerFormula", | ||
# | ||
# Exceptions | ||
# | ||
"ComponentNotFound", | ||
"FormulaGenerationError", | ||
] |
54 changes: 54 additions & 0 deletions
54
src/frequenz/sdk/timeseries/logical_meter/_formula_generators/_battery_power_formula.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# License: MIT | ||
# Copyright © 2022 Frequenz Energy-as-a-Service GmbH | ||
|
||
"""Formula generator from component graph for Grid Power.""" | ||
|
||
from .....sdk import microgrid | ||
from ....microgrid.component import ComponentCategory, ComponentMetricId, InverterType | ||
from .._formula_engine import FormulaEngine | ||
from ._formula_generator import ComponentNotFound, FormulaGenerator | ||
|
||
|
||
class BatteryPowerFormula(FormulaGenerator): | ||
"""Creates a formula engine from the component graph for calculating grid power.""" | ||
|
||
async def generate( | ||
self, | ||
) -> FormulaEngine: | ||
"""Make a formula for the cumulative AC battery power of a microgrid. | ||
The calculation is performed by adding the Active Powers of all the inverters | ||
that are attached to batteries. | ||
If there's no data coming from an inverter, that inverter's power will be | ||
treated as 0. | ||
Returns: | ||
A formula engine that will calculate cumulative battery power values. | ||
Raises: | ||
ComponentNotFound: if there are no batteries in the component graph, or if | ||
they don't have an inverter as a predecessor. | ||
FormulaGenerationError: If a battery has a non-inverter predecessor | ||
in the component graph. | ||
""" | ||
builder = self._get_builder(ComponentMetricId.ACTIVE_POWER) | ||
component_graph = microgrid.get().component_graph | ||
battery_inverters = list( | ||
comp | ||
for comp in component_graph.components() | ||
if comp.category == ComponentCategory.INVERTER | ||
and comp.type == InverterType.BATTERY | ||
) | ||
|
||
if not battery_inverters: | ||
raise ComponentNotFound( | ||
"Unable to find any battery inverters in the component graph." | ||
) | ||
|
||
for idx, comp in enumerate(battery_inverters): | ||
if idx > 0: | ||
builder.push_oper("+") | ||
await builder.push_component_metric(comp.component_id, nones_are_zeros=True) | ||
|
||
return builder.build() |
59 changes: 59 additions & 0 deletions
59
src/frequenz/sdk/timeseries/logical_meter/_formula_generators/_formula_generator.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
# License: MIT | ||
# Copyright © 2022 Frequenz Energy-as-a-Service GmbH | ||
|
||
"""Base class for formula generators that use the component graphs.""" | ||
|
||
from abc import ABC, abstractmethod | ||
|
||
from frequenz.channels import Sender | ||
|
||
from ....actor import ChannelRegistry, ComponentMetricRequest | ||
from ....microgrid.component import ComponentMetricId | ||
from .._formula_engine import FormulaEngine | ||
from .._resampled_formula_builder import ResampledFormulaBuilder | ||
|
||
|
||
class FormulaGenerationError(Exception): | ||
"""An error encountered during formula generation from the component graph.""" | ||
|
||
|
||
class ComponentNotFound(FormulaGenerationError): | ||
"""Indicates that a component required for generating a formula is not found.""" | ||
|
||
|
||
class FormulaGenerator(ABC): | ||
"""A class for generating formulas from the component graph.""" | ||
|
||
def __init__( | ||
self, | ||
namespace: str, | ||
channel_registry: ChannelRegistry, | ||
resampler_subscription_sender: Sender[ComponentMetricRequest], | ||
) -> None: | ||
"""Create a `FormulaGenerator` instance. | ||
Args: | ||
namespace: A namespace to use with the data-pipeline. | ||
channel_registry: A channel registry instance shared with the resampling | ||
actor. | ||
resampler_subscription_sender: A sender for sending metric requests to the | ||
resampling actor. | ||
""" | ||
self._channel_registry = channel_registry | ||
self._resampler_subscription_sender = resampler_subscription_sender | ||
self._namespace = namespace | ||
|
||
def _get_builder( | ||
self, component_metric_id: ComponentMetricId | ||
) -> ResampledFormulaBuilder: | ||
builder = ResampledFormulaBuilder( | ||
self._namespace, | ||
self._channel_registry, | ||
self._resampler_subscription_sender, | ||
component_metric_id, | ||
) | ||
return builder | ||
|
||
@abstractmethod | ||
async def generate(self) -> FormulaEngine: | ||
"""Generate a formula engine, based on the component graph.""" |
67 changes: 67 additions & 0 deletions
67
src/frequenz/sdk/timeseries/logical_meter/_formula_generators/_grid_power_formula.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,67 @@ | ||
# License: MIT | ||
# Copyright © 2022 Frequenz Energy-as-a-Service GmbH | ||
|
||
"""Formula generator from component graph for Grid Power.""" | ||
|
||
from .....sdk import microgrid | ||
from ....microgrid.component import ComponentCategory, ComponentMetricId | ||
from .._formula_engine import FormulaEngine | ||
from ._formula_generator import ComponentNotFound, FormulaGenerator | ||
|
||
|
||
class GridPowerFormula(FormulaGenerator): | ||
"""Creates a formula engine from the component graph for calculating grid power.""" | ||
|
||
async def generate( | ||
self, | ||
) -> FormulaEngine: | ||
"""Generate a formula for calculating grid power from the component graph. | ||
Returns: | ||
A formula engine that will calculate grid power values. | ||
Raises: | ||
ComponentNotFound: when the component graph doesn't have a `GRID` component. | ||
""" | ||
builder = self._get_builder(ComponentMetricId.ACTIVE_POWER) | ||
component_graph = microgrid.get().component_graph | ||
grid_component = next( | ||
( | ||
comp | ||
for comp in component_graph.components() | ||
if comp.category == ComponentCategory.GRID | ||
), | ||
None, | ||
) | ||
|
||
if grid_component is None: | ||
raise ComponentNotFound( | ||
"Unable to find a GRID component from the component graph." | ||
) | ||
|
||
grid_successors = component_graph.successors(grid_component.component_id) | ||
|
||
# generate a formula that just adds values from all commponents that are | ||
# directly connected to the grid. | ||
for idx, comp in enumerate(grid_successors): | ||
if idx > 0: | ||
builder.push_oper("+") | ||
|
||
# Ensure the device has an `ACTIVE_POWER` metric. When inverters | ||
# produce `None` samples, those inverters are excluded from the | ||
# calculation by treating their `None` values as `0`s. | ||
# | ||
# This is not possible for Meters, so when they produce `None` | ||
# values, those values get propagated as the output. | ||
if comp.category == ComponentCategory.INVERTER: | ||
nones_are_zeros = True | ||
elif comp.category == ComponentCategory.METER: | ||
nones_are_zeros = False | ||
else: | ||
continue | ||
|
||
await builder.push_component_metric( | ||
comp.component_id, nones_are_zeros=nones_are_zeros | ||
) | ||
|
||
return builder.build() |
45 changes: 45 additions & 0 deletions
45
src/frequenz/sdk/timeseries/logical_meter/_formula_generators/_pv_power_formula.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
# License: MIT | ||
# Copyright © 2022 Frequenz Energy-as-a-Service GmbH | ||
|
||
"""Formula generator for PV Power, from the component graph.""" | ||
|
||
from .....sdk import microgrid | ||
from ....microgrid.component import ComponentCategory, ComponentMetricId, InverterType | ||
from .._formula_engine import FormulaEngine | ||
from ._formula_generator import ComponentNotFound, FormulaGenerator | ||
|
||
|
||
class PVPowerFormula(FormulaGenerator): | ||
"""Creates a formula engine for calculating the PV power production.""" | ||
|
||
async def generate(self) -> FormulaEngine: | ||
"""Make a formula for the PV power production of a microgrid. | ||
Returns: | ||
A formula engine that will calculate PV power production values. | ||
Raises: | ||
ComponentNotFound: if there are no PV inverters in the component graph. | ||
""" | ||
builder = self._get_builder(ComponentMetricId.ACTIVE_POWER) | ||
|
||
component_graph = microgrid.get().component_graph | ||
pv_inverters = list( | ||
comp | ||
for comp in component_graph.components() | ||
if comp.category == ComponentCategory.INVERTER | ||
and comp.type == InverterType.SOLAR | ||
) | ||
|
||
if not pv_inverters: | ||
raise ComponentNotFound( | ||
"Unable to find any PV inverters in the component graph." | ||
) | ||
|
||
for idx, comp in enumerate(pv_inverters): | ||
if idx > 0: | ||
builder.push_oper("+") | ||
|
||
await builder.push_component_metric(comp.component_id, nones_are_zeros=True) | ||
|
||
return builder.build() |
Oops, something went wrong.