2626from frequenz .quantities import Power
2727from typing_extensions import override
2828
29+ from frequenz .sdk .timeseries ._base_types import Bounds
30+
2931from ... import timeseries
3032from . import _bounds
3133from ._base_classes import BaseAlgorithm , Proposal , _Report
3739
3840
3941class 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