|  | 
|  | 1 | +# License: MIT | 
|  | 2 | +# Copyright © 2023 Frequenz Energy-as-a-Service GmbH | 
|  | 3 | + | 
|  | 4 | +"""Provides a way for the SDK to apply formulas on resampled data streams. | 
|  | 5 | +
 | 
|  | 6 | +# Formula Engine | 
|  | 7 | +
 | 
|  | 8 | +[`FormulaEngine`][frequenz.sdk.timeseries.formula_engine.FormulaEngine]s are used in the | 
|  | 9 | +SDK to calculate and stream metrics like | 
|  | 10 | +[`grid_power`][frequenz.sdk.timeseries.grid.Grid.power], | 
|  | 11 | +[`consumer_power`][frequenz.sdk.timeseries.consumer.Consumer.power], etc., which are | 
|  | 12 | +building blocks of the [Frequenz SDK Microgrid | 
|  | 13 | +Model][frequenz.sdk.microgrid--frequenz-sdk-microgrid-model]. | 
|  | 14 | +
 | 
|  | 15 | +The SDK creates the formulas by analysing the configuration of components in the | 
|  | 16 | +{{glossary("Component Graph")}}. | 
|  | 17 | +
 | 
|  | 18 | +## Streaming Interface | 
|  | 19 | +
 | 
|  | 20 | +The | 
|  | 21 | +[`FormulaEngine.new_receiver()`][frequenz.sdk.timeseries.formula_engine.FormulaEngine.new_receiver] | 
|  | 22 | +method can be used to create a [Receiver][frequenz.channels.Receiver] that streams the | 
|  | 23 | +[Sample][frequenz.sdk.timeseries.Sample]s calculated by the formula engine. | 
|  | 24 | +
 | 
|  | 25 | +```python | 
|  | 26 | +from frequenz.sdk import microgrid | 
|  | 27 | +
 | 
|  | 28 | +battery_pool = microgrid.new_battery_pool(priority=5) | 
|  | 29 | +
 | 
|  | 30 | +async for power in battery_pool.power.new_receiver(): | 
|  | 31 | +    print(f"{power=}") | 
|  | 32 | +``` | 
|  | 33 | +
 | 
|  | 34 | +## Composition | 
|  | 35 | +
 | 
|  | 36 | +Composite `FormulaEngine`s can be built using arithmetic operations on | 
|  | 37 | +`FormulaEngine`s streaming the same type of data. | 
|  | 38 | +
 | 
|  | 39 | +For example, if you're interested in a particular composite metric that can be | 
|  | 40 | +calculated by subtracting | 
|  | 41 | +[`new_battery_pool().power`][frequenz.sdk.timeseries.battery_pool.BatteryPool.power] and | 
|  | 42 | +[`new_ev_charger_pool().power`][frequenz.sdk.timeseries.ev_charger_pool.EVChargerPool] | 
|  | 43 | +from the | 
|  | 44 | +[`grid().power`][frequenz.sdk.timeseries.grid.Grid.power], | 
|  | 45 | +we can build a `FormulaEngine` that provides a stream of this calculated metric as | 
|  | 46 | +follows: | 
|  | 47 | +
 | 
|  | 48 | +```python | 
|  | 49 | +from frequenz.sdk import microgrid | 
|  | 50 | +
 | 
|  | 51 | +battery_pool = microgrid.new_battery_pool(priority=5) | 
|  | 52 | +ev_charger_pool = microgrid.new_ev_charger_pool(priority=5) | 
|  | 53 | +grid = microgrid.grid() | 
|  | 54 | +
 | 
|  | 55 | +# apply operations on formula engines to create a formula engine that would | 
|  | 56 | +# apply these operations on the corresponding data streams. | 
|  | 57 | +net_power = ( | 
|  | 58 | +    grid.power - (battery_pool.power + ev_charger_pool.power) | 
|  | 59 | +).build("net_power") | 
|  | 60 | +
 | 
|  | 61 | +async for power in net_power.new_receiver(): | 
|  | 62 | +    print(f"{power=}") | 
|  | 63 | +``` | 
|  | 64 | +
 | 
|  | 65 | +# Formula Engine 3-Phase | 
|  | 66 | +
 | 
|  | 67 | +A [`FormulaEngine3Phase`][frequenz.sdk.timeseries.formula_engine.FormulaEngine3Phase] | 
|  | 68 | +is similar to a | 
|  | 69 | +[`FormulaEngine`][frequenz.sdk.timeseries.formula_engine.FormulaEngine], except that | 
|  | 70 | +they stream [3-phase samples][frequenz.sdk.timeseries.Sample3Phase].  All the | 
|  | 71 | +current formulas (like | 
|  | 72 | +[`Grid.current_per_phase`][frequenz.sdk.timeseries.grid.Grid.current_per_phase], | 
|  | 73 | +[`EVChargerPool.current_per_phase`][frequenz.sdk.timeseries.ev_charger_pool.EVChargerPool.current_per_phase], | 
|  | 74 | +etc.) are implemented as per-phase formulas. | 
|  | 75 | +
 | 
|  | 76 | +## Streaming Interface | 
|  | 77 | +
 | 
|  | 78 | +The | 
|  | 79 | +[`FormulaEngine3Phase.new_receiver()`][frequenz.sdk.timeseries.formula_engine.FormulaEngine3Phase.new_receiver] | 
|  | 80 | +method can be used to create a [Receiver][frequenz.channels.Receiver] that streams the | 
|  | 81 | +[Sample3Phase][frequenz.sdk.timeseries.Sample3Phase] values | 
|  | 82 | +calculated by the formula engine. | 
|  | 83 | +
 | 
|  | 84 | +```python | 
|  | 85 | +from frequenz.sdk import microgrid | 
|  | 86 | +
 | 
|  | 87 | +ev_charger_pool = microgrid.new_ev_charger_pool(priority=5) | 
|  | 88 | +
 | 
|  | 89 | +async for sample in ev_charger_pool.current_per_phase.new_receiver(): | 
|  | 90 | +    print(f"Current: {sample}") | 
|  | 91 | +``` | 
|  | 92 | +
 | 
|  | 93 | +## Composition | 
|  | 94 | +
 | 
|  | 95 | +`FormulaEngine3Phase` instances can be composed together, just like `FormulaEngine` | 
|  | 96 | +instances. | 
|  | 97 | +
 | 
|  | 98 | +```python | 
|  | 99 | +from frequenz.sdk import microgrid | 
|  | 100 | +
 | 
|  | 101 | +ev_charger_pool = microgrid.new_ev_charger_pool(priority=5) | 
|  | 102 | +grid = microgrid.grid() | 
|  | 103 | +
 | 
|  | 104 | +# Calculate grid consumption current that's not used by the EV chargers | 
|  | 105 | +other_current = (grid.current_per_phase - ev_charger_pool.current_per_phase).build( | 
|  | 106 | +    "other_current" | 
|  | 107 | +) | 
|  | 108 | +
 | 
|  | 109 | +async for sample in other_current.new_receiver(): | 
|  | 110 | +    print(f"Other current: {sample}") | 
|  | 111 | +``` | 
|  | 112 | +""" | 
|  | 113 | + | 
|  | 114 | +from ._formula_engine import FormulaEngine, FormulaEngine3Phase | 
|  | 115 | + | 
|  | 116 | +__all__ = [ | 
|  | 117 | +    "FormulaEngine", | 
|  | 118 | +    "FormulaEngine3Phase", | 
|  | 119 | +] | 
0 commit comments