Skip to content

Consider abstracting component pool groups into their own class #963

@llucax

Description

@llucax

What's needed?

We have now 2 component pool groups: the default and the shifting group (which sort of move the baseline of the default group). The code could be restructured a bit to avoid some duplication.

Proposed solution

Something like:

class PoolGroup:
    algorithm: BaseAlgorithm
    subscriptions: dict[...]
    # Maybe more stuff could be stored directly in the group,
    # like the `component_ids` and `system_bounds`, so this
    # methods will need even fewer arguments, but I didn't
    # check if this is feasible or not

    def subscribe(...) -> None: ...
        if component_ids not in self.subscriptions:
            self.subscriptions[component_ids] = {
                priority: self._channel_registry.get_or_create(
                    _Report, subcription_request.get_channel_name()
                ).new_sender()
            }
        elif priority not in self.subscriptions[component_ids]:
            self.subscriptions[component_ids][priority] = (
                self._channel_registry.get_or_create(
                    _Report, subcription_request.get_channel_name()
                ).new_sender()
            )

    def calculate_target_power(
        self,
        component_ids: frozenset[int],
        system_bounds: ...,
        *,
        proposal: Proposal | None = None,
        shift: Power | None = None,
        must_send: bool = False,
     ) -> Power | None:
        bounds = self._calculate_shifted_bounds(system_bounds[component_ids], shift)
        return self.algorithm.calculate_target_power(
                    component_ids, proposal, bounds, must_send,
                )

    async def send_report(
        self, component_ids: frozenset[int], distribution_results: ..., bounds: ..., *,
        shift: Power | None = None
    ) -> None:
        for priority, sender in self.subscriptions.get(component_ids, {}).items():
            bounds = self._calculate_shifted_bounds(bounds, shift)
            result = distribution_results.get(component_ids)
            status = self.algorithm.get_status(component_ids, priority, bounds, result)
            await sender.send(status)

    def _calculate_shifted_bounds(
        self, bounds: SystemBounds, shift: Power | None
    ) -> SystemBounds:
        if shift is None:
            return bounds
        ...

class PowerManagingActor(Actor):
    def __init__(self, ...):
        self._default_group = PoolGroup(...)
        self._shifting_group = PoolGroup(...)

    async def _run(self) -> None:
        async for selected in select(...):
            if ...:
                ...
            elif selected_from(selected, self._bounds_subscription_receiver):
                sub = selected.message
                component_ids = sub.component_ids
                priority = sub.priority
                group = self._shifting_group if sub.in_shifting_group else self._default_group
                group.subscribe(...)
		        if component_ids not in self._bound_tracker_tasks:
		            self._add_system_bounds_tracker(component_ids)        

    def _calculate_target_power(self, ...) -> Power | None:
        tgt_power_shift: Power | None = None
        tgt_power_default: Power | None = None
        if proposal is not None:
            if proposal.in_shifting_group:
                bounds = self._system_bounds[component_ids]
                tgt_power_shift = self._shifting_group.calculate_target_power(
                    component_ids, bounds, proposal=proposal, must_send=must_send,
                )
                tgt_power_no_shift = self._default_group.calculate_target_power(
                    component_ids, bounds, shift=tgt_power_shift, must_send=must_send,
                )
        ...

    async def _send_reports(self, component_ids: frozenset[int]) -> None:
        if bounds is None:
            _logger.warning("PowerManagingActor: No bounds for %s", component_ids)
            return
        self._shifting_group.send_reports(component_ids, self._distribution_results, bounds)
        self._default_group.send_reports(
            component_ids,
            self._distribution_results,
            bounds,
            shift=self._shifting_group.algorithm.get_target_power(component_ids),
        )

Additional context

The original suggestion comes from the PR introducing the groups:

Metadata

Metadata

Assignees

No one assigned

    Labels

    part:actorAffects an actor ot the actors utilities (decorator, etc.)part:power-managementAffects the management of battery power and distributiontype:tech-debtImproves the project without visible changes for users

    Type

    No type

    Projects

    Status

    To do

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions