Skip to content

Commit 431411f

Browse files
committed
Implement the adding of powers and shifting/limiting of bounds
Signed-off-by: Sahas Subramanian <[email protected]>
1 parent 2d91d3b commit 431411f

File tree

2 files changed

+185
-185
lines changed

2 files changed

+185
-185
lines changed

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

Lines changed: 65 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
from frequenz.quantities import Power
2727
from typing_extensions import override
2828

29+
from frequenz.sdk.timeseries._base_types import Bounds
30+
2931
from ... import timeseries
3032
from . import _bounds
3133
from ._base_classes import BaseAlgorithm, Proposal, _Report
@@ -37,27 +39,29 @@
3739

3840

3941
class ShiftingMatryoshka(BaseAlgorithm):
40-
"""The matryoshka algorithm."""
42+
"""The ShiftingMatryoshka algorithm."""
4143

4244
def __init__(self, max_proposal_age: timedelta) -> None:
4345
"""Create a new instance of the matryoshka algorithm."""
4446
self._max_proposal_age_sec = max_proposal_age.total_seconds()
4547
self._component_buckets: dict[frozenset[int], set[Proposal]] = {}
4648
self._target_power: dict[frozenset[int], Power] = {}
4749

48-
def _calc_target_power(
50+
def _calc_targets(
4951
self,
5052
proposals: set[Proposal],
5153
system_bounds: SystemBounds,
52-
) -> Power:
53-
"""Calculate the target power for the given components.
54+
priority: int | None = None,
55+
) -> tuple[Power, Bounds[Power]]:
56+
"""Calculate the target power and bounds for the given components.
5457
5558
Args:
5659
proposals: The proposals for the given components.
5760
system_bounds: The system bounds for the components in the proposal.
61+
priority: The priority of the actor for which the target power is calculated.
5862
5963
Returns:
60-
The new target power for the components.
64+
The new target power and bounds for the components.
6165
"""
6266
lower_bound = (
6367
system_bounds.inclusion_bounds.lower
@@ -78,47 +82,74 @@ def _calc_target_power(
7882
system_bounds.exclusion_bounds.lower != Power.zero()
7983
or system_bounds.exclusion_bounds.upper != Power.zero()
8084
):
81-
exclusion_bounds = system_bounds.exclusion_bounds
85+
exclusion_bounds = Bounds(
86+
system_bounds.exclusion_bounds.lower,
87+
system_bounds.exclusion_bounds.upper,
88+
)
8289

8390
target_power = Power.zero()
8491
for next_proposal in sorted(proposals, reverse=True):
92+
unshifted_power = Power.zero()
93+
if priority is not None and next_proposal.priority <= priority:
94+
break
95+
8596
if upper_bound < lower_bound:
8697
break
87-
if next_proposal.preferred_power:
98+
99+
proposal_lower = next_proposal.bounds.lower or lower_bound
100+
proposal_upper = next_proposal.bounds.upper or upper_bound
101+
proposal_power = next_proposal.preferred_power
102+
103+
if proposal_upper < proposal_lower:
104+
continue
105+
106+
if proposal_power and (
107+
proposal_power < proposal_lower or proposal_power > proposal_upper
108+
):
109+
continue
110+
111+
if proposal_lower >= upper_bound:
112+
if proposal_power:
113+
proposal_power = upper_bound
114+
elif proposal_upper <= lower_bound:
115+
if proposal_power:
116+
proposal_power = lower_bound
117+
else:
118+
lower_bound = max(lower_bound, proposal_lower)
119+
upper_bound = min(upper_bound, proposal_upper)
120+
121+
if proposal_power:
88122
match _bounds.clamp_to_bounds(
89-
next_proposal.preferred_power,
123+
proposal_power,
90124
lower_bound,
91125
upper_bound,
92126
exclusion_bounds,
93127
):
94128
case (None, power) | (power, None) if power:
95-
target_power = power
129+
unshifted_power = power
96130
case (power_low, power_high) if power_low and power_high:
97-
if (
98-
power_high - next_proposal.preferred_power
99-
< next_proposal.preferred_power - power_low
100-
):
101-
target_power = power_high
131+
if power_high - proposal_power < proposal_power - power_low:
132+
unshifted_power = power_high
102133
else:
103-
target_power = power_low
134+
unshifted_power = power_low
135+
case _:
136+
pass
104137

105-
proposal_lower = next_proposal.bounds.lower or lower_bound
106-
proposal_upper = next_proposal.bounds.upper or upper_bound
107-
# If the bounds from the current proposal are fully within the exclusion
108-
# bounds, then don't use them to narrow the bounds further. This allows
109-
# subsequent proposals to not be blocked by the current proposal.
110-
match _bounds.check_exclusion_bounds_overlap(
111-
proposal_lower, proposal_upper, exclusion_bounds
112-
):
113-
case (True, True):
114-
continue
115-
lower_bound = max(lower_bound, proposal_lower)
116-
upper_bound = min(upper_bound, proposal_upper)
117138
lower_bound, upper_bound = _bounds.adjust_exclusion_bounds(
118139
lower_bound, upper_bound, exclusion_bounds
119140
)
120141

121-
return target_power
142+
lower_bound = lower_bound - unshifted_power
143+
upper_bound = upper_bound - unshifted_power
144+
target_power += unshifted_power
145+
146+
if exclusion_bounds is not None:
147+
exclusion_bounds = Bounds[Power](
148+
exclusion_bounds.lower - unshifted_power,
149+
exclusion_bounds.upper - unshifted_power,
150+
)
151+
152+
return target_power, Bounds[Power](lower=lower_bound, upper=upper_bound)
122153

123154
def _validate_component_ids(
124155
self,
@@ -200,7 +231,7 @@ def calculate_target_power(
200231
if proposals is None:
201232
return None
202233

203-
target_power = self._calc_target_power(proposals, system_bounds)
234+
target_power, _ = self._calc_targets(proposals, system_bounds)
204235

205236
if (
206237
must_return_power
@@ -212,7 +243,7 @@ def calculate_target_power(
212243
return None
213244

214245
@override
215-
def get_status(
246+
def get_status( # pylint: disable=too-many-locals
216247
self,
217248
component_ids: frozenset[int],
218249
priority: int,
@@ -230,47 +261,13 @@ def get_status(
230261
the given priority.
231262
"""
232263
target_power = self._target_power.get(component_ids)
233-
if system_bounds.inclusion_bounds is None:
234-
return _Report(
235-
target_power=target_power,
236-
_inclusion_bounds=None,
237-
_exclusion_bounds=system_bounds.exclusion_bounds,
238-
)
239-
240-
lower_bound = system_bounds.inclusion_bounds.lower
241-
upper_bound = system_bounds.inclusion_bounds.upper
242-
243-
exclusion_bounds = None
244-
if system_bounds.exclusion_bounds is not None and (
245-
system_bounds.exclusion_bounds.lower != Power.zero()
246-
or system_bounds.exclusion_bounds.upper != Power.zero()
247-
):
248-
exclusion_bounds = system_bounds.exclusion_bounds
249-
250-
for next_proposal in sorted(
251-
self._component_buckets.get(component_ids, []), reverse=True
252-
):
253-
if next_proposal.priority <= priority:
254-
break
255-
proposal_lower = next_proposal.bounds.lower or lower_bound
256-
proposal_upper = next_proposal.bounds.upper or upper_bound
257-
match _bounds.check_exclusion_bounds_overlap(
258-
proposal_lower, proposal_upper, exclusion_bounds
259-
):
260-
case (True, True):
261-
continue
262-
calc_lower_bound = max(lower_bound, proposal_lower)
263-
calc_upper_bound = min(upper_bound, proposal_upper)
264-
if calc_lower_bound <= calc_upper_bound:
265-
lower_bound, upper_bound = _bounds.adjust_exclusion_bounds(
266-
calc_lower_bound, calc_upper_bound, exclusion_bounds
267-
)
268-
else:
269-
break
264+
_, bounds = self._calc_targets(
265+
self._component_buckets.get(component_ids, set()), system_bounds, priority
266+
)
270267
return _Report(
271268
target_power=target_power,
272269
_inclusion_bounds=timeseries.Bounds[Power](
273-
lower=lower_bound, upper=upper_bound
270+
lower=bounds.lower, upper=bounds.upper
274271
),
275272
_exclusion_bounds=system_bounds.exclusion_bounds,
276273
)

0 commit comments

Comments
 (0)