Skip to content

Commit b25272d

Browse files
committed
Make all examples validate correctly
Signed-off-by: Mathias L. Baumann <[email protected]>
1 parent 31684ed commit b25272d

File tree

8 files changed

+146
-87
lines changed

8 files changed

+146
-87
lines changed

src/frequenz/sdk/actor/_decorator.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,9 @@ def actor(cls: Type[Any]) -> Type[Any]:
7676
TypeError: when the class doesn't have a `run` method as per spec.
7777
7878
Example (one actor receiving from two receivers):
79-
``` python
79+
```python
80+
from frequenz.channels import Broadcast, Receiver, Sender
81+
from frequenz.channels.util import Select
8082
@actor
8183
class EchoActor:
8284
def __init__(
@@ -115,11 +117,12 @@ async def run(self) -> None:
115117
echo_rx = echo_chan.new_receiver()
116118
117119
await input_chan_2.new_sender().send(True)
118-
msg = await echo_rx.receive()
120+
received_msg = await echo_rx.receive()
119121
```
120122
121123
Example (two Actors composed):
122-
``` python
124+
```python
125+
from frequenz.channels import Broadcast, Receiver, Sender
123126
@actor
124127
class Actor1:
125128
def __init__(
@@ -153,16 +156,15 @@ async def run(self) -> None:
153156
async for msg in self._recv:
154157
await self._output.send(msg)
155158
156-
157159
input_chan: Broadcast[bool] = Broadcast("Input to A1")
158-
a1_chan: Broadcast[bool] = Broadcast["A1 stream"]
159-
a2_chan: Broadcast[bool] = Broadcast["A2 stream"]
160-
a1 = Actor1(
160+
a1_chan: Broadcast[bool] = Broadcast("A1 stream")
161+
a2_chan: Broadcast[bool] = Broadcast("A2 stream")
162+
a_1 = Actor1(
161163
name="ActorOne",
162164
recv=input_chan.new_receiver(),
163165
output=a1_chan.new_sender(),
164166
)
165-
a2 = Actor2(
167+
a_2 = Actor2(
166168
name="ActorTwo",
167169
recv=a1_chan.new_receiver(),
168170
output=a2_chan.new_sender(),
@@ -171,7 +173,7 @@ async def run(self) -> None:
171173
a2_rx = a2_chan.new_receiver()
172174
173175
await input_chan.new_sender().send(True)
174-
msg = await a2_rx.receive()
176+
received_msg = await a2_rx.receive()
175177
```
176178
177179
"""

src/frequenz/sdk/actor/power_distributing/power_distributing.py

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -89,11 +89,10 @@ class PowerDistributingActor:
8989
printed.
9090
9191
Example:
92-
``` python
93-
import grpc.aio as grpcaio
94-
95-
from frequenz.sdk.microgrid.graph import _MicrogridComponentGraph
92+
```python
93+
from frequenz.sdk import microgrid
9694
from frequenz.sdk.microgrid.component import ComponentCategory
95+
from frequenz.sdk.actor import ResamplerConfig
9796
from frequenz.sdk.actor.power_distributing import (
9897
PowerDistributingActor,
9998
Request,
@@ -103,29 +102,45 @@ class PowerDistributingActor:
103102
PartialFailure,
104103
Ignored,
105104
)
105+
from frequenz.channels import Bidirectional, Broadcast, Receiver, Sender
106+
from datetime import timedelta
107+
from frequenz.sdk import actor
106108
109+
HOST = "localhost"
110+
PORT = 50051
107111
108-
target = f"{host}:{port}"
109-
grpc_channel = grpcaio.insecure_channel(target)
110-
api = MicrogridGrpcClient(grpc_channel, target)
112+
await microgrid.initialize(
113+
HOST,
114+
PORT,
115+
ResamplerConfig(resampling_period=timedelta(seconds=1))
116+
)
111117
112-
graph = _MicrogridComponentGraph()
113-
await graph.refresh_from_api(api)
118+
graph = microgrid.connection_manager.get().component_graph
114119
115120
batteries = graph.components(component_category={ComponentCategory.BATTERY})
116121
batteries_ids = {c.component_id for c in batteries}
117122
123+
battery_status_channel = Broadcast[BatteryStatus]("battery-status")
124+
118125
channel = Bidirectional[Request, Result]("user1", "power_distributor")
119126
power_distributor = PowerDistributingActor(
120-
mock_api, component_graph, {"user1": channel.service_handle}
127+
users_channels={"user1": channel.service_handle},
128+
battery_status_sender=battery_status_channel.new_sender(),
121129
)
122130
131+
# Start the actor
132+
await actor.run(power_distributor)
133+
123134
client_handle = channel.client_handle
124135
125136
# Set power 1200W to given batteries.
126137
request = Request(power=1200.0, batteries=batteries_ids, request_timeout_sec=10.0)
127138
await client_handle.send(request)
128139
140+
# Set power 1200W to given batteries.
141+
request = Request(power=1200, batteries=batteries_ids, request_timeout_sec=10.0)
142+
await client_handle.send(request)
143+
129144
# It is recommended to use timeout when waiting for the response!
130145
result: Result = await asyncio.wait_for(client_handle.receive(), timeout=10)
131146
@@ -134,9 +149,10 @@ class PowerDistributingActor:
134149
elif isinstance(result, PartialFailure):
135150
print(
136151
f"Batteries {result.failed_batteries} failed, total failed power" \
137-
f"{result.failed_power}")
152+
f"{result.failed_power}"
153+
)
138154
elif isinstance(result, Ignored):
139-
print(f"Request was ignored, because of newer request")
155+
print("Request was ignored, because of newer request")
140156
elif isinstance(result, Error):
141157
print(f"Request failed with error: {result.msg}")
142158
```

src/frequenz/sdk/power/_distribution_algorithm.py

Lines changed: 51 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -71,26 +71,26 @@ class DistributionAlgorithm:
7171
7272
We would like our distribution to meet the equation:
7373
74-
``` python
74+
```
7575
distribution[i] = power_w * capacity_ratio[i] * x[i]
7676
```
7777
7878
where:
7979
80-
``` python
80+
```
8181
sum(capacity_ratio[i] * x[i] for i in range(N)) == 1
8282
```
8383
8484
Let `y` be our unknown, the proportion to discharge each battery would be
8585
(1):
8686
87-
``` python
87+
```
8888
x[i] = available_soc[i]*y
8989
```
9090
9191
We can compute `y` from equation above (2):
9292
93-
``` python
93+
```
9494
sum(capacity_ratio[i] * x[i] for i in range(N)) == 1
9595
# =>
9696
sum(capacity_ratio[i] * available_soc[i] * y for i in range(N)) == 1
@@ -100,7 +100,7 @@ class DistributionAlgorithm:
100100
101101
Now we know everything and we can compute distribution:
102102
103-
``` python
103+
```
104104
distribution[i] = power_w * capacity_ratio[i] * x[i] # from (1)
105105
distribution[i] = \
106106
power_w * capacity_ratio[i] * available_soc[i] * y # from (2)
@@ -110,13 +110,13 @@ class DistributionAlgorithm:
110110
111111
Let:
112112
113-
``` python
113+
```
114114
battery_availability_ratio[i] = capacity_ratio[i] * available_soc[i]
115115
total_battery_availability_ratio = sum(battery_availability_ratio)
116116
```
117117
118118
Then:
119-
``` python
119+
```
120120
distribution[i] = power_w * battery_availability_ratio[i] \
121121
/ total_battery_availability_ratio
122122
```
@@ -151,29 +151,33 @@ def __init__(self, distributor_exponent: float = 1) -> None:
151151
If `distribution_exponent` is:
152152
153153
* `0`: distribution for each battery will be the equal.
154-
``` python
155-
Bat1.distribution = 4000; Bat2.distribution = 4000
154+
```python
155+
BAT1_DISTRIBUTION = 4000
156+
BAT2_DISTRIBUTION = 4000
156157
```
157158
158159
* `1`: then `Bat2` will have 3x more power assigned then `Bat1`.
159-
``` python
160-
10 * x + 30 * x = 8000
161-
x = 200
162-
Bat1.distribution = 2000; Bat2.distribution = 6000
160+
```python
161+
# 10 * x + 30 * x = 8000
162+
X = 200
163+
BAT1_DISTRIBUTION = 2000
164+
BAT2_DISTRIBUTION = 6000
163165
```
164166
165167
* `2`: then `Bat2` will have 9x more power assigned then `Bat1`.
166-
``` python
167-
10^2 * x + 30^2 * x = 8000
168-
x = 80
169-
Bat1.distribution = 800; Bat2.distribution = 7200
168+
```python
169+
# 10^2 * x + 30^2 * x = 8000
170+
X = 80
171+
BAT1_DISTRIBUTION = 800
172+
BAT2_DISTRIBUTION = 7200
170173
```
171174
172175
* `3`: then `Bat2` will have 27x more power assigned then `Bat1`.
173-
``` python
174-
10^3 * x + 30^3 * x = 8000
175-
x = 0,285714286
176-
Bat1.distribution = 285; Bat2.distribution = 7715
176+
```python
177+
# 10^3 * x + 30^3 * x = 8000
178+
X = 0.285714286
179+
BAT1_DISTRIBUTION = 285
180+
BAT2_DISTRIBUTION = 7715
177181
```
178182
179183
# Example 2
@@ -189,29 +193,33 @@ def __init__(self, distributor_exponent: float = 1) -> None:
189193
If `distribution_exponent` is:
190194
191195
* `0`: distribution for each battery will be the same.
192-
``` python
193-
Bat1.distribution = 4500; Bat2.distribution = 450
196+
```python
197+
BAT1_DISTRIBUTION = 4500
198+
BAT2_DISTRIBUTION = 450
194199
```
195200
196201
* `1`: then `Bat2` will have 2x more power assigned then `Bat1`.
197-
``` python
198-
30 * x + 60 * x = 900
199-
x = 100
200-
Bat1.distribution = 300; Bat2.distribution = 600
202+
```python
203+
# 30 * x + 60 * x = 900
204+
X = 100
205+
BAT1_DISTRIBUTION = 300
206+
BAT2_DISTRIBUTION = 600
201207
```
202208
203209
* `2`: then `Bat2` will have 4x more power assigned then `Bat1`.
204-
``` python
205-
30^2 * x + 60^2 * x = 900
206-
x = 0.2
207-
Bat1.distribution = 180; Bat2.distribution = 720
210+
```python
211+
# 30^2 * x + 60^2 * x = 900
212+
X = 0.2
213+
BAT1_DISTRIBUTION = 180
214+
BAT2_DISTRIBUTION = 720
208215
```
209216
210217
* `3`: then `Bat2` will have 8x more power assigned then `Bat1`.
211-
``` python
212-
30^3 * x + 60^3 * x = 900
213-
x = 0,003703704
214-
Bat1.distribution = 100; Bat2.distribution = 800
218+
```python
219+
# 30^3 * x + 60^3 * x = 900
220+
X = 0.003703704
221+
BAT1_DISTRIBUTION = 100
222+
BAT2_DISTRIBUTION = 800
215223
```
216224
217225
# Example 3
@@ -226,15 +234,17 @@ def __init__(self, distributor_exponent: float = 1) -> None:
226234
If `distribution_exponent` is:
227235
228236
* `0`: distribution for each battery will be the equal.
229-
``` python
230-
Bat1.distribution = 450; Bat2.distribution = 450
237+
```python
238+
BAT1_DISTRIBUTION = 450
239+
BAT2_DISTRIBUTION = 450
231240
```
232241
233242
* `0.5`: then `Bat2` will have 6/4x more power assigned then `Bat1`.
234-
``` python
235-
sqrt(36) * x + sqrt(16) * x = 900
236-
x = 100
237-
Bat1.distribution = 600; Bat2.distribution = 400
243+
```python
244+
# sqrt(36) * x + sqrt(16) * x = 900
245+
X = 100
246+
BAT1_DISTRIBUTION = 600
247+
BAT2_DISTRIBUTION = 400
238248
```
239249
240250
Raises:

src/frequenz/sdk/timeseries/_formula_engine/_formula_engine.py

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -411,10 +411,13 @@ class FormulaBuilder:
411411
following calls need to be made:
412412
413413
```python
414-
builder = FormulaBuilder()
415-
builder.push_metric("metric_1", receiver_1)
414+
channel = Broadcast[Sample]("channel")
415+
receiver_1 = channel.new_receiver("receiver_1")
416+
receiver_2 = channel.new_receiver("receiver_2")
417+
builder = FormulaBuilder("addition")
418+
builder.push_metric("metric_1", receiver_1, nones_are_zeros=True)
416419
builder.push_oper("+")
417-
builder.push_metric("metric_2", receiver_2)
420+
builder.push_metric("metric_2", receiver_2, nones_are_zeros=True)
418421
engine = builder.build()
419422
```
420423
@@ -503,20 +506,30 @@ def push_clipper(self, min_value: float | None, max_value: float | None) -> None
503506
For example, this clips the output of the entire expression:
504507
505508
```python
509+
builder = FormulaBuilder("example")
510+
channel = Broadcast[Sample]("channel")
511+
receiver_1 = channel.new_receiver("receiver_1")
512+
receiver_2 = channel.new_receiver("receiver_2")
513+
506514
builder.push_oper("(")
507-
builder.push_metric("metric_1", receiver_1)
515+
builder.push_metric("metric_1", receiver_1, nones_are_zeros=True)
508516
builder.push_oper("+")
509-
builder.push_metric("metric_2", receiver_2)
517+
builder.push_metric("metric_2", receiver_2, nones_are_zeros=True)
510518
builder.push_oper(")")
511519
builder.push_clipper(min_value=0.0, max_value=None)
512520
```
513521
514522
And this clips the output of metric_2 only, and not the final result:
515523
516524
```python
517-
builder.push_metric("metric_1", receiver_1)
525+
builder = FormulaBuilder("example")
526+
channel = Broadcast[Sample]("channel")
527+
receiver_1 = channel.new_receiver("receiver_1")
528+
receiver_2 = channel.new_receiver("receiver_2")
529+
530+
builder.push_metric("metric_1", receiver_1, nones_are_zeros=True)
518531
builder.push_oper("+")
519-
builder.push_metric("metric_2", receiver_2)
532+
builder.push_metric("metric_2", receiver_2, nones_are_zeros=True)
520533
builder.push_clipper(min_value=0.0, max_value=None)
521534
```
522535

src/frequenz/sdk/timeseries/_moving_window.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ class MovingWindow:
5757
Example: Calculate the mean of a time interval
5858
5959
```python
60+
from datetime import datetime, timedelta, timezone
61+
resampled_data_recv = Broadcast[Sample]("sample-data").new_receiver()
62+
6063
window = MovingWindow(
6164
size=timedelta(minutes=5),
6265
resampled_data_recv=resampled_data_recv,
@@ -78,7 +81,11 @@ class MovingWindow:
7881
Example: Create a polars data frame from a `MovingWindow`
7982
8083
```python
84+
# pylint: disable=import-error
8185
import polars as pl
86+
from datetime import datetime, timedelta, timezone
87+
88+
sample_receiver = Broadcast[Sample]("sample-data").new_receiver()
8289
8390
# create a window that stores two days of data
8491
# starting at 1.1.23 with samplerate=1
@@ -94,7 +101,7 @@ class MovingWindow:
94101
# create a polars series with one full day of data
95102
time_start = datetime(2023, 1, 1, tzinfo=timezone.utc)
96103
time_end = datetime(2023, 1, 2, tzinfo=timezone.utc)
97-
s = pl.Series("Jan_1", mv[time_start:time_end])
104+
s = pl.Series("Jan_1", window[time_start:time_end])
98105
```
99106
"""
100107

0 commit comments

Comments
 (0)