Skip to content

Commit c1bc917

Browse files
authored
Improve resampler structure and performance (#1242)
This pull request splits the `resampling` module into many submodules as it was getting very large If also uses tuples internally to store and calculate samples, which improves performance and simplifies the logic, as `Sample`s can have `None` values, but the values in the internal buffer can never be `None`, and we don't need to use a `Quantity` as we know we are always working with the same units. It also exposes the `ResamplingFunction` and `SourceProperties` types, converts `ResamplingFunction` to a `Protocol`, and replaces `average` as the default resampling function with Python's `statistics.fmean`, among other small improvements and fixes.
2 parents f0bca75 + da45456 commit c1bc917

File tree

25 files changed

+558
-415
lines changed

25 files changed

+558
-415
lines changed

RELEASE_NOTES.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
## Upgrading
88

99
* The microgrid client dependency has been updated to version 0.9.0
10+
* The resampling function now takes plain `float`s as values instead of `Quantity` objects.
11+
* `frequenz.sdk.timeseries.UNIX_EPOCH` was removed, use [`frequenz.core.datetime.UNIX_EPOCH`](https://frequenz-floss.github.io/frequenz-core-python/latest/reference/frequenz/core/datetime/#frequenz.core.datetime.UNIX_EPOCH) instead.
1012

1113
## New Features
1214

benchmarks/timeseries/resampling.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,13 @@
77
from datetime import datetime, timedelta, timezone
88
from timeit import timeit
99

10-
from frequenz.quantities import Quantity
11-
12-
from frequenz.sdk.timeseries import Sample
13-
from frequenz.sdk.timeseries._resampling import (
14-
ResamplerConfig,
15-
SourceProperties,
16-
_ResamplingHelper,
17-
)
10+
from frequenz.sdk.timeseries import ResamplerConfig
11+
from frequenz.sdk.timeseries._resampling._base_types import SourceProperties
12+
from frequenz.sdk.timeseries._resampling._resampler import _ResamplingHelper
1813

1914

2015
def nop( # pylint: disable=unused-argument
21-
samples: Sequence[Sample[Quantity]],
16+
samples: Sequence[tuple[datetime, float]],
2217
resampler_config: ResamplerConfig,
2318
source_properties: SourceProperties,
2419
) -> float:
@@ -43,10 +38,11 @@ def _benchmark_resampling_helper(resamples: int, samples: int) -> None:
4338

4439
def _do_work() -> None:
4540
nonlocal now
41+
delta = timedelta(seconds=1 / samples)
4642
for _n_resample in range(resamples):
4743
for _n_sample in range(samples):
48-
now = now + timedelta(seconds=1 / samples)
49-
helper.add_sample(Sample(now, Quantity(0.0)))
44+
now = now + delta
45+
helper.add_sample((now, 0.0))
5046
helper.resample(now)
5147

5248
print(timeit(_do_work, number=5))

examples/battery_pool.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,9 +33,8 @@ async def main() -> None:
3333
receivers = [
3434
battery_pool.soc.new_receiver(limit=1),
3535
battery_pool.capacity.new_receiver(limit=1),
36-
# pylint: disable=protected-access
36+
# pylint: disable-next=protected-access
3737
battery_pool._system_power_bounds.new_receiver(limit=1),
38-
# pylint: enable=protected-access
3938
]
4039

4140
async for metric in merge(*receivers):

mkdocs.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -117,8 +117,10 @@ plugins:
117117
import:
118118
# See https://mkdocstrings.github.io/python/usage/#import for details
119119
- https://docs.python.org/3/objects.inv
120-
- https://frequenz-floss.github.io/frequenz-channels-python/v1.1/objects.inv
121-
- https://frequenz-floss.github.io/frequenz-client-microgrid-python/v0.7/objects.inv
120+
- https://frequenz-floss.github.io/frequenz-channels-python/v1/objects.inv
121+
- https://frequenz-floss.github.io/frequenz-client-common-python/v0.3/objects.inv
122+
- https://frequenz-floss.github.io/frequenz-client-microgrid-python/v0.9/objects.inv
123+
- https://frequenz-floss.github.io/frequenz-core-python/v1/objects.inv
122124
- https://frequenz-floss.github.io/frequenz-quantities-python/v1/objects.inv
123125
- https://lovasoa.github.io/marshmallow_dataclass/html/objects.inv
124126
- https://marshmallow.readthedocs.io/en/stable/objects.inv

src/frequenz/sdk/actor/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -597,7 +597,7 @@ async def main() -> None: # (6)!
597597
[_run]: #the-_run-method
598598
"""
599599

600-
from ..timeseries._resampling import ResamplerConfig
600+
from ..timeseries._resampling._config import ResamplerConfig
601601
from ._actor import Actor
602602
from ._background_service import BackgroundService
603603
from ._run_utils import run

src/frequenz/sdk/microgrid/_power_distributing/_component_status/_battery_status_tracker.py

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -394,8 +394,8 @@ def _is_inverter_state_correct(self, msg: InverterData) -> bool:
394394
True if inverter is in correct state. False otherwise.
395395
"""
396396
# Component state is not exposed to the user.
397-
# pylint: disable=protected-access
398397
state = msg.component_state
398+
# pylint: disable-next=protected-access
399399
if state not in BatteryStatusTracker._inverter_valid_state:
400400
if self._last_status == ComponentStatusEnum.WORKING:
401401
_logger.warning(
@@ -404,7 +404,6 @@ def _is_inverter_state_correct(self, msg: InverterData) -> bool:
404404
state.name,
405405
)
406406
return False
407-
# pylint: enable=protected-access
408407
return True
409408

410409
def _is_battery_state_correct(self, msg: BatteryData) -> bool:
@@ -417,8 +416,8 @@ def _is_battery_state_correct(self, msg: BatteryData) -> bool:
417416
True if battery is in correct state. False otherwise.
418417
"""
419418
# Component state is not exposed to the user.
420-
# pylint: disable=protected-access
421419
state = msg.component_state
420+
# pylint: disable-next=protected-access
422421
if state not in BatteryStatusTracker._battery_valid_state:
423422
if self._last_status == ComponentStatusEnum.WORKING:
424423
_logger.warning(
@@ -439,7 +438,6 @@ def _is_battery_state_correct(self, msg: BatteryData) -> bool:
439438
)
440439
return False
441440
return True
442-
# pylint: enable=protected-access
443441

444442
def _is_timestamp_outdated(self, timestamp: datetime) -> bool:
445443
"""Return if timestamp is to old.

src/frequenz/sdk/microgrid/_power_distributing/_distribution_algorithm/_battery_distribution_algorithm.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,8 @@ def _compute_battery_availability_ratio(
461461

462462
return battery_availability_ratio, total_battery_availability_ratio
463463

464-
def _distribute_power( # pylint: disable=too-many-arguments
464+
# pylint: disable-next=too-many-arguments,too-many-locals,too-many-branches,too-many-statements
465+
def _distribute_power(
465466
self,
466467
*,
467468
components: list[InvBatPair],
@@ -470,7 +471,6 @@ def _distribute_power( # pylint: disable=too-many-arguments
470471
incl_bounds: dict[ComponentId, Power],
471472
excl_bounds: dict[ComponentId, Power],
472473
) -> DistributionResult:
473-
# pylint: disable=too-many-locals,too-many-branches,too-many-statements
474474
"""Distribute power between given components.
475475
476476
After this method power should be distributed between batteries

src/frequenz/sdk/microgrid/_power_distributing/power_distributing.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,7 @@
3333
_logger = logging.getLogger(__name__)
3434

3535

36-
class PowerDistributingActor(Actor):
37-
# pylint: disable=too-many-instance-attributes
36+
class PowerDistributingActor(Actor): # pylint: disable=too-many-instance-attributes
3837
"""Actor to distribute the power between components in a microgrid.
3938
4039
One instance of the actor can handle only one component category and type,

src/frequenz/sdk/microgrid/_power_managing/_power_managing_actor.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -158,16 +158,17 @@ def _add_system_bounds_tracker(self, component_ids: frozenset[ComponentId]) -> N
158158
NotImplementedError: When the pool type is not supported.
159159
"""
160160
bounds_receiver: Receiver[SystemBounds]
161-
# pylint: disable=protected-access
162161
if self._component_category is ComponentCategory.BATTERY:
163162
battery_pool = _data_pipeline.new_battery_pool(
164163
priority=-sys.maxsize - 1, component_ids=component_ids
165164
)
165+
# pylint: disable-next=protected-access
166166
bounds_receiver = battery_pool._system_power_bounds.new_receiver()
167167
elif self._component_category is ComponentCategory.EV_CHARGER:
168168
ev_charger_pool = _data_pipeline.new_ev_charger_pool(
169169
priority=-sys.maxsize - 1, component_ids=component_ids
170170
)
171+
# pylint: disable-next=protected-access
171172
bounds_receiver = ev_charger_pool._system_power_bounds.new_receiver()
172173
elif (
173174
self._component_category is ComponentCategory.INVERTER
@@ -176,8 +177,8 @@ def _add_system_bounds_tracker(self, component_ids: frozenset[ComponentId]) -> N
176177
pv_pool = _data_pipeline.new_pv_pool(
177178
priority=-sys.maxsize - 1, component_ids=component_ids
178179
)
180+
# pylint: disable-next=protected-access
179181
bounds_receiver = pv_pool._system_power_bounds.new_receiver()
180-
# pylint: enable=protected-access
181182
else:
182183
err = (
183184
"PowerManagingActor: Unsupported component category: "

src/frequenz/sdk/microgrid/_power_wrapper.py

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,16 @@
99
from datetime import timedelta
1010

1111
from frequenz.channels import Broadcast
12-
13-
# pylint seems to think this is a cyclic import, but it is not.
14-
#
15-
# pylint: disable=cyclic-import
1612
from frequenz.client.microgrid import ComponentCategory, ComponentType
1713

1814
from .._internal._channels import ChannelRegistry, ReceiverFetcher
15+
16+
# pylint seems to think this is a cyclic import, but it is not.
17+
#
18+
# pylint: disable-next=cyclic-import
1919
from . import _power_managing, connection_manager
20+
21+
# pylint: disable-next=cyclic-import
2022
from ._power_distributing import (
2123
ComponentPoolStatus,
2224
PowerDistributingActor,

0 commit comments

Comments
 (0)