Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Validate docstring examples #364

Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 42 additions & 32 deletions src/frequenz/sdk/actor/_decorator.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,9 @@ def actor(cls: Type[Any]) -> Type[Any]:
TypeError: when the class doesn't have a `run` method as per spec.

Example (one actor receiving from two receivers):
``` python
```python
from frequenz.channels import Receiver, Sender, Broadcast
from frequenz.channels.util import Select
@actor
class EchoActor:
def __init__(
Expand All @@ -101,25 +103,30 @@ async def run(self) -> None:
await self._output.send(msg.inner)


input_chan_1: Broadcast[bool] = Broadcast("input_chan_1")
input_chan_2: Broadcast[bool] = Broadcast("input_chan_2")
async def main() -> None:
input_chan_1: Broadcast[bool] = Broadcast("input_chan_1")
input_chan_2: Broadcast[bool] = Broadcast("input_chan_2")

echo_chan: Broadcast[bool] = Broadcast("EchoChannel")
echo_chan: Broadcast[bool] = Broadcast("EchoChannel")

echo_actor = EchoActor(
"EchoActor",
recv1=input_chan_1.new_receiver(),
recv2=input_chan_2.new_receiver(),
output=echo_chan.new_sender(),
)
echo_rx = echo_chan.new_receiver()
echo_actor = EchoActor(
"EchoActor",
recv1=input_chan_1.new_receiver(),
recv2=input_chan_2.new_receiver(),
output=echo_chan.new_sender(),
)
echo_rx = echo_chan.new_receiver()

await input_chan_2.new_sender().send(True)
msg = await echo_rx.receive()

await input_chan_2.new_sender().send(True)
msg = await echo_rx.receive()
asyncio.run(main())
```

Example (two Actors composed):
``` python
```python
from frequenz.channels import Receiver, Sender, Broadcast
from frequenz.channels.util import Select
@actor
class Actor1:
def __init__(
Expand Down Expand Up @@ -154,24 +161,27 @@ async def run(self) -> None:
await self._output.send(msg)


input_chan: Broadcast[bool] = Broadcast("Input to A1")
a1_chan: Broadcast[bool] = Broadcast["A1 stream"]
a2_chan: Broadcast[bool] = Broadcast["A2 stream"]
a1 = Actor1(
name="ActorOne",
recv=input_chan.new_receiver(),
output=a1_chan.new_sender(),
)
a2 = Actor2(
name="ActorTwo",
recv=a1_chan.new_receiver(),
output=a2_chan.new_sender(),
)

a2_rx = a2_chan.new_receiver()

await input_chan.new_sender().send(True)
msg = await a2_rx.receive()
async def main() -> None:
input_chan: Broadcast[bool] = Broadcast("Input to A1")
a1_chan: Broadcast[bool] = Broadcast("A1 stream")
a2_chan: Broadcast[bool] = Broadcast("A2 stream")
a_1 = Actor1(
name="ActorOne",
recv=input_chan.new_receiver(),
output=a1_chan.new_sender(),
)
a_2 = Actor2(
name="ActorTwo",
recv=a1_chan.new_receiver(),
output=a2_chan.new_sender(),
)

a2_rx = a2_chan.new_receiver()

await input_chan.new_sender().send(True)
msg = await a2_rx.receive()

asyncio.run(main())
```

"""
Expand Down
84 changes: 52 additions & 32 deletions src/frequenz/sdk/actor/power_distributing/power_distributing.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,11 +89,13 @@ class PowerDistributingActor:
printed.

Example:
``` python
```python
import grpc.aio as grpcaio

from frequenz.sdk.microgrid.graph import _MicrogridComponentGraph
from frequenz.sdk.microgrid._graph import _MicrogridComponentGraph
from frequenz.sdk import microgrid
from frequenz.sdk.microgrid.component import ComponentCategory
from frequenz.sdk.actor import ResamplerConfig
from frequenz.sdk.actor.power_distributing import (
PowerDistributingActor,
Request,
Expand All @@ -103,42 +105,60 @@ class PowerDistributingActor:
PartialFailure,
Ignored,
)
from frequenz.channels import Bidirectional, Broadcast, Receiver, Sender
from datetime import timedelta
from frequenz.sdk import actor

HOST = "localhost"
PORT = 50051

async def main() -> None:
await microgrid.initialize(
HOST,
PORT,
ResamplerConfig(resampling_period=timedelta(seconds=1))
)

graph = microgrid.connection_manager.get().component_graph

batteries = graph.components(component_category={ComponentCategory.BATTERY})
batteries_ids = {c.component_id for c in batteries}

target = f"{host}:{port}"
grpc_channel = grpcaio.insecure_channel(target)
api = MicrogridGrpcClient(grpc_channel, target)
battery_status_channel = Broadcast[BatteryStatus]("battery-status")

graph = _MicrogridComponentGraph()
await graph.refresh_from_api(api)
channel = Bidirectional[Request, Result]("user1", "power_distributor")
power_distributor = PowerDistributingActor(
users_channels={"user1": channel.service_handle},
battery_status_sender=battery_status_channel.new_sender(),
)

batteries = graph.components(component_category={ComponentCategory.BATTERY})
batteries_ids = {c.component_id for c in batteries}
# Start the actor
await actor.run(power_distributor)

channel = Bidirectional[Request, Result]("user1", "power_distributor")
power_distributor = PowerDistributingActor(
mock_api, component_graph, {"user1": channel.service_handle}
)
client_handle = channel.client_handle

client_handle = channel.client_handle

# Set power 1200W to given batteries.
request = Request(power=1200.0, batteries=batteries_ids, request_timeout_sec=10.0)
await client_handle.send(request)

# It is recommended to use timeout when waiting for the response!
result: Result = await asyncio.wait_for(client_handle.receive(), timeout=10)

if isinstance(result, Success):
print("Command succeed")
elif isinstance(result, PartialFailure):
print(
f"Batteries {result.failed_batteries} failed, total failed power" \
f"{result.failed_power}")
elif isinstance(result, Ignored):
print(f"Request was ignored, because of newer request")
elif isinstance(result, Error):
print(f"Request failed with error: {result.msg}")
# Set power 1200W to given batteries.
request = Request(power=1200.0, batteries=batteries_ids, request_timeout_sec=10.0)
await client_handle.send(request)

# Set power 1200W to given batteries.
request = Request(power=1200, batteries=batteries_ids, request_timeout_sec=10.0)
await client_handle.send(request)

# It is recommended to use timeout when waiting for the response!
result: Result = await asyncio.wait_for(client_handle.receive(), timeout=10)

if isinstance(result, Success):
print("Command succeed")
elif isinstance(result, PartialFailure):
print(
f"Batteries {result.failed_batteries} failed, total failed power" \
f"{result.failed_power}"
)
elif isinstance(result, Ignored):
print("Request was ignored, because of newer request")
elif isinstance(result, Error):
print(f"Request failed with error: {result.msg}")
```
"""

Expand Down
92 changes: 51 additions & 41 deletions src/frequenz/sdk/power/_distribution_algorithm.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,26 +71,26 @@ class DistributionAlgorithm:

We would like our distribution to meet the equation:

``` python
```
distribution[i] = power_w * capacity_ratio[i] * x[i]
```

where:

``` python
```
sum(capacity_ratio[i] * x[i] for i in range(N)) == 1
```

Let `y` be our unknown, the proportion to discharge each battery would be
(1):

``` python
```
x[i] = available_soc[i]*y
```

We can compute `y` from equation above (2):

``` python
```
sum(capacity_ratio[i] * x[i] for i in range(N)) == 1
# =>
sum(capacity_ratio[i] * available_soc[i] * y for i in range(N)) == 1
Expand All @@ -100,7 +100,7 @@ class DistributionAlgorithm:

Now we know everything and we can compute distribution:

``` python
```
distribution[i] = power_w * capacity_ratio[i] * x[i] # from (1)
distribution[i] = \
power_w * capacity_ratio[i] * available_soc[i] * y # from (2)
Expand All @@ -110,13 +110,13 @@ class DistributionAlgorithm:

Let:

``` python
```
battery_availability_ratio[i] = capacity_ratio[i] * available_soc[i]
total_battery_availability_ratio = sum(battery_availability_ratio)
```

Then:
``` python
```
distribution[i] = power_w * battery_availability_ratio[i] \
/ total_battery_availability_ratio
```
Expand Down Expand Up @@ -151,29 +151,33 @@ def __init__(self, distributor_exponent: float = 1) -> None:
If `distribution_exponent` is:

* `0`: distribution for each battery will be the equal.
``` python
Bat1.distribution = 4000; Bat2.distribution = 4000
```python
BAT1_DISTRIBUTION = 4000
BAT2_DISTRIBUTION = 4000
```

* `1`: then `Bat2` will have 3x more power assigned then `Bat1`.
``` python
10 * x + 30 * x = 8000
x = 200
Bat1.distribution = 2000; Bat2.distribution = 6000
```python
# 10 * x + 30 * x = 8000
X = 200
BAT1_DISTRIBUTION = 2000
BAT2_DISTRIBUTION = 6000
```

* `2`: then `Bat2` will have 9x more power assigned then `Bat1`.
``` python
10^2 * x + 30^2 * x = 8000
x = 80
Bat1.distribution = 800; Bat2.distribution = 7200
```python
# 10^2 * x + 30^2 * x = 8000
X = 80
BAT1_DISTRIBUTION = 800
BAT2_DISTRIBUTION = 7200
```

* `3`: then `Bat2` will have 27x more power assigned then `Bat1`.
``` python
10^3 * x + 30^3 * x = 8000
x = 0,285714286
Bat1.distribution = 285; Bat2.distribution = 7715
```python
# 10^3 * x + 30^3 * x = 8000
X = 0.285714286
BAT1_DISTRIBUTION = 285
BAT2_DISTRIBUTION = 7715
```

# Example 2
Expand All @@ -189,29 +193,33 @@ def __init__(self, distributor_exponent: float = 1) -> None:
If `distribution_exponent` is:

* `0`: distribution for each battery will be the same.
``` python
Bat1.distribution = 4500; Bat2.distribution = 450
```python
BAT1_DISTRIBUTION = 4500
BAT2_DISTRIBUTION = 450
```

* `1`: then `Bat2` will have 2x more power assigned then `Bat1`.
``` python
30 * x + 60 * x = 900
x = 100
Bat1.distribution = 300; Bat2.distribution = 600
```python
# 30 * x + 60 * x = 900
X = 100
BAT1_DISTRIBUTION = 300
BAT2_DISTRIBUTION = 600
```

* `2`: then `Bat2` will have 4x more power assigned then `Bat1`.
``` python
30^2 * x + 60^2 * x = 900
x = 0.2
Bat1.distribution = 180; Bat2.distribution = 720
```python
# 30^2 * x + 60^2 * x = 900
X = 0.2
BAT1_DISTRIBUTION = 180
BAT2_DISTRIBUTION = 720
```

* `3`: then `Bat2` will have 8x more power assigned then `Bat1`.
``` python
30^3 * x + 60^3 * x = 900
x = 0,003703704
Bat1.distribution = 100; Bat2.distribution = 800
```python
# 30^3 * x + 60^3 * x = 900
X = 0.003703704
BAT1_DISTRIBUTION = 100
BAT2_DISTRIBUTION = 800
```

# Example 3
Expand All @@ -226,15 +234,17 @@ def __init__(self, distributor_exponent: float = 1) -> None:
If `distribution_exponent` is:

* `0`: distribution for each battery will be the equal.
``` python
Bat1.distribution = 450; Bat2.distribution = 450
```python
BAT1_DISTRIBUTION = 450
BAT2_DISTRIBUTION = 450
```

* `0.5`: then `Bat2` will have 6/4x more power assigned then `Bat1`.
``` python
sqrt(36) * x + sqrt(16) * x = 900
x = 100
Bat1.distribution = 600; Bat2.distribution = 400
```python
# sqrt(36) * x + sqrt(16) * x = 900
X = 100
BAT1_DISTRIBUTION = 600
BAT2_DISTRIBUTION = 400
```

Raises:
Expand Down
Loading