diff --git a/docs/user-guide/formula-engine.md b/docs/user-guide/formula-engine.md index d1dab1798..5054544fc 100644 --- a/docs/user-guide/formula-engine.md +++ b/docs/user-guide/formula-engine.md @@ -1,15 +1,10 @@ # Formula Engine -::: frequenz.sdk.timeseries.formula_engine.FormulaEngine - options: - members: None - show_bases: false - show_root_full_path: false - show_source: false - -::: frequenz.sdk.timeseries.formula_engine.FormulaEngine3Phase +::: frequenz.sdk.timeseries.formula_engine options: members: None show_bases: false show_root_full_path: false + show_root_heading: false + show_root_toc_entry: false show_source: false diff --git a/mkdocs.yml b/mkdocs.yml index c046d238e..85245ad04 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -87,6 +87,8 @@ markdown_extensions: permalink: "¤" plugins: + - autorefs: + resolve_closest: true - gen-files: scripts: - docs/_scripts/mkdocstrings_autoapi.py diff --git a/pyproject.toml b/pyproject.toml index c5a9f2eec..901da5ba3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -54,7 +54,6 @@ dev-mkdocs = [ "black == 24.8.0", "Markdown==3.7", "mike == 2.1.3", - "mkdocs-autorefs >= 1.0.1, < 1.1", "mkdocs-gen-files == 0.5.0", "mkdocs-literate-nav == 0.6.1", "mkdocs-macros-plugin == 1.0.5", diff --git a/src/frequenz/sdk/timeseries/formula_engine/__init__.py b/src/frequenz/sdk/timeseries/formula_engine/__init__.py index 3eae5b758..761d6db91 100644 --- a/src/frequenz/sdk/timeseries/formula_engine/__init__.py +++ b/src/frequenz/sdk/timeseries/formula_engine/__init__.py @@ -1,12 +1,114 @@ # License: MIT # Copyright © 2023 Frequenz Energy-as-a-Service GmbH -"""The formula engine module. +"""Provides a way for the SDK to apply formulas on resampled data streams. -This module exposes the -[FormulaEngine][frequenz.sdk.timeseries.formula_engine.FormulaEngine] and -[FormulaEngine3Phase][frequenz.sdk.timeseries.formula_engine.FormulaEngine3Phase] -classes. +# Formula Engine + +[`FormulaEngine`][frequenz.sdk.timeseries.formula_engine.FormulaEngine]s are used in the +SDK to calculate and stream metrics like +[`grid_power`][frequenz.sdk.timeseries.grid.Grid.power], +[`consumer_power`][frequenz.sdk.timeseries.consumer.Consumer.power], etc., which are +building blocks of the [Frequenz SDK Microgrid +Model][frequenz.sdk.microgrid--frequenz-sdk-microgrid-model]. + +The SDK creates the formulas by analysing the configuration of components in the +{{glossary("Component Graph")}}. + +## Streaming Interface + +The +[`FormulaEngine.new_receiver()`][frequenz.sdk.timeseries.formula_engine.FormulaEngine.new_receiver] +method can be used to create a [Receiver][frequenz.channels.Receiver] that streams the +[Sample][frequenz.sdk.timeseries.Sample]s calculated by the formula engine. + +```python +from frequenz.sdk import microgrid + +battery_pool = microgrid.new_battery_pool(priority=5) + +async for power in battery_pool.power.new_receiver(): + print(f"{power=}") +``` + +## Composition + +Composite `FormulaEngine`s can be built using arithmetic operations on +`FormulaEngine`s streaming the same type of data. + +For example, if you're interested in a particular composite metric that can be +calculated by subtracting +[`new_battery_pool().power`][frequenz.sdk.timeseries.battery_pool.BatteryPool.power] and +[`new_ev_charger_pool().power`][frequenz.sdk.timeseries.ev_charger_pool.EVChargerPool] +from the +[`grid().power`][frequenz.sdk.timeseries.grid.Grid.power], +we can build a `FormulaEngine` that provides a stream of this calculated metric as +follows: + +```python +from frequenz.sdk import microgrid + +battery_pool = microgrid.new_battery_pool(priority=5) +ev_charger_pool = microgrid.new_ev_charger_pool(priority=5) +grid = microgrid.grid() + +# apply operations on formula engines to create a formula engine that would +# apply these operations on the corresponding data streams. +net_power = ( + grid.power - (battery_pool.power + ev_charger_pool.power) +).build("net_power") + +async for power in net_power.new_receiver(): + print(f"{power=}") +``` + +# Formula Engine 3-Phase + +A [`FormulaEngine3Phase`][frequenz.sdk.timeseries.formula_engine.FormulaEngine3Phase] +is similar to a +[`FormulaEngine`][frequenz.sdk.timeseries.formula_engine.FormulaEngine], except that +they stream [3-phase samples][frequenz.sdk.timeseries.Sample3Phase]. All the +current formulas (like +[`Grid.current_per_phase`][frequenz.sdk.timeseries.grid.Grid.current_per_phase], +[`EVChargerPool.current_per_phase`][frequenz.sdk.timeseries.ev_charger_pool.EVChargerPool.current_per_phase], +etc.) are implemented as per-phase formulas. + +## Streaming Interface + +The +[`FormulaEngine3Phase.new_receiver()`][frequenz.sdk.timeseries.formula_engine.FormulaEngine3Phase.new_receiver] +method can be used to create a [Receiver][frequenz.channels.Receiver] that streams the +[Sample3Phase][frequenz.sdk.timeseries.Sample3Phase] values +calculated by the formula engine. + +```python +from frequenz.sdk import microgrid + +ev_charger_pool = microgrid.new_ev_charger_pool(priority=5) + +async for sample in ev_charger_pool.current_per_phase.new_receiver(): + print(f"Current: {sample}") +``` + +## Composition + +`FormulaEngine3Phase` instances can be composed together, just like `FormulaEngine` +instances. + +```python +from frequenz.sdk import microgrid + +ev_charger_pool = microgrid.new_ev_charger_pool(priority=5) +grid = microgrid.grid() + +# Calculate grid consumption current that's not used by the EV chargers +other_current = (grid.current_per_phase - ev_charger_pool.current_per_phase).build( + "other_current" +) + +async for sample in other_current.new_receiver(): + print(f"Other current: {sample}") +``` """ from ._formula_engine import FormulaEngine, FormulaEngine3Phase diff --git a/src/frequenz/sdk/timeseries/formula_engine/_formula_engine.py b/src/frequenz/sdk/timeseries/formula_engine/_formula_engine.py index b1b3352c6..eabd83e77 100644 --- a/src/frequenz/sdk/timeseries/formula_engine/_formula_engine.py +++ b/src/frequenz/sdk/timeseries/formula_engine/_formula_engine.py @@ -57,68 +57,39 @@ class FormulaEngine(Generic[QuantityT]): - """[`FormulaEngine`][frequenz.sdk.timeseries.formula_engine.FormulaEngine]s are a - part of the SDK's data pipeline, and provide a way for the SDK to apply formulas on - resampled data streams. + """An engine to apply formulas on resampled data streams. - They are used in the SDK to calculate and stream metrics like - [`grid_power`][frequenz.sdk.timeseries.grid.Grid.power], - [`consumer_power`][frequenz.sdk.timeseries.consumer.Consumer.power], - etc., which are building blocks of the - [Frequenz SDK Microgrid Model][frequenz.sdk.microgrid--frequenz-sdk-microgrid-model]. + Please refer to the [module documentation][frequenz.sdk.timeseries.formula_engine] + for more information on how formula engines are used throughout the SDK. - The SDK creates the formulas by analysing the configuration of components in the - {{glossary("Component Graph")}}. - - ### Streaming Interface - - The - [`FormulaEngine.new_receiver()`][frequenz.sdk.timeseries.formula_engine.FormulaEngine.new_receiver] - method can be used to create a - [Receiver](https://frequenz-floss.github.io/frequenz-channels-python/latest/reference/frequenz/channels/#frequenz.channels.Receiver) - that streams the [Sample][frequenz.sdk.timeseries.Sample]s calculated by the formula - engine. - - ```python - from frequenz.sdk import microgrid - - battery_pool = microgrid.new_battery_pool(priority=5) - - async for power in battery_pool.power.new_receiver(): - print(f"{power=}") - ``` - - ### Composition + Example: Streaming the power of a battery pool. + ```python + from frequenz.sdk import microgrid - Composite `FormulaEngine`s can be built using arithmetic operations on - `FormulaEngine`s streaming the same type of data. + battery_pool = microgrid.new_battery_pool(priority=5) - For example, if you're interested in a particular composite metric that can be - calculated by subtracting - [`new_battery_pool().power`][frequenz.sdk.timeseries.battery_pool.BatteryPool.power] and - [`new_ev_charger_pool().power`][frequenz.sdk.timeseries.ev_charger_pool.EVChargerPool] - from the - [`grid().power`][frequenz.sdk.timeseries.grid.Grid.power], - we can build a `FormulaEngine` that provides a stream of this calculated metric as - follows: + async for power in battery_pool.power.new_receiver(): + print(f"{power=}") + ``` - ```python - from frequenz.sdk import microgrid + Example: Composition of formula engines. + ```python + from frequenz.sdk import microgrid - battery_pool = microgrid.new_battery_pool(priority=5) - ev_charger_pool = microgrid.new_ev_charger_pool(priority=5) - grid = microgrid.grid() + battery_pool = microgrid.new_battery_pool(priority=5) + ev_charger_pool = microgrid.new_ev_charger_pool(priority=5) + grid = microgrid.grid() - # apply operations on formula engines to create a formula engine that would - # apply these operations on the corresponding data streams. - net_power = ( - grid.power - (battery_pool.power + ev_charger_pool.power) - ).build("net_power") + # apply operations on formula engines to create a formula engine that would + # apply these operations on the corresponding data streams. + net_power = ( + grid.power - (battery_pool.power + ev_charger_pool.power) + ).build("net_power") - async for power in net_power.new_receiver(): - print(f"{power=}") - ``` - """ # noqa: D400, D205 + async for power in net_power.new_receiver(): + print(f"{power=}") + ``` + """ def __init__( self, @@ -392,54 +363,37 @@ def new_receiver( class FormulaEngine3Phase(Generic[QuantityT]): - """A - [`FormulaEngine3Phase`][frequenz.sdk.timeseries.formula_engine.FormulaEngine3Phase] - is similar to a - [`FormulaEngine`][frequenz.sdk.timeseries.formula_engine.FormulaEngine], except that - they stream [3-phase samples][frequenz.sdk.timeseries.Sample3Phase]. All the - current formulas (like - [`Grid.current_per_phase`][frequenz.sdk.timeseries.grid.Grid.current_per_phase], - [`EVChargerPool.current_per_phase`][frequenz.sdk.timeseries.ev_charger_pool.EVChargerPool.current_per_phase], - etc.) are implemented as per-phase formulas. - - ### Streaming Interface - - The - [`FormulaEngine3Phase.new_receiver()`][frequenz.sdk.timeseries.formula_engine.FormulaEngine3Phase.new_receiver] - method can be used to create a - [Receiver](https://frequenz-floss.github.io/frequenz-channels-python/latest/reference/frequenz/channels/#frequenz.channels.Receiver) - that streams the [Sample3Phase][frequenz.sdk.timeseries.Sample3Phase] values - calculated by the formula engine. - - ```python - from frequenz.sdk import microgrid - - ev_charger_pool = microgrid.new_ev_charger_pool(priority=5) - - async for sample in ev_charger_pool.current_per_phase.new_receiver(): - print(f"Current: {sample}") - ``` - - ### Composition - - `FormulaEngine3Phase` instances can be composed together, just like `FormulaEngine` - instances. - - ```python - from frequenz.sdk import microgrid - - ev_charger_pool = microgrid.new_ev_charger_pool(priority=5) - grid = microgrid.grid() - - # Calculate grid consumption current that's not used by the EV chargers - other_current = (grid.current_per_phase - ev_charger_pool.current_per_phase).build( - "other_current" - ) - - async for sample in other_current.new_receiver(): - print(f"Other current: {sample}") - ``` - """ # noqa: D205, D400 + """An engine to apply formulas on 3-phase resampled data streams. + + Please refer to the [module documentation][frequenz.sdk.timeseries.formula_engine] + for more information on how formula engines are used throughout the SDK. + + Example: Streaming the current of an EV charger pool. + ```python + from frequenz.sdk import microgrid + + ev_charger_pool = microgrid.new_ev_charger_pool(priority=5) + + async for sample in ev_charger_pool.current_per_phase.new_receiver(): + print(f"Current: {sample}") + ``` + + Example: Composition of formula engines. + ```python + from frequenz.sdk import microgrid + + ev_charger_pool = microgrid.new_ev_charger_pool(priority=5) + grid = microgrid.grid() + + # Calculate grid consumption current that's not used by the EV chargers + other_current = (grid.current_per_phase - ev_charger_pool.current_per_phase).build( + "other_current" + ) + + async for sample in other_current.new_receiver(): + print(f"Other current: {sample}") + ``` + """ def __init__( self,