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:
6064 The new target power for the components.
@@ -78,64 +82,73 @@ 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
83- unshifted_power = Power .zero ()
8490 target_power = Power .zero ()
8591 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+
8696 if upper_bound < lower_bound :
8797 break
88- if next_proposal .preferred_power :
89- match _bounds .clamp_to_bounds (
90- 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 and proposal_power :
112+ proposal_power = upper_bound
113+ elif proposal_upper <= lower_bound and proposal_power :
114+ proposal_power = lower_bound
115+ else :
116+ lower_bound = max (lower_bound , proposal_lower )
117+ upper_bound = min (upper_bound , proposal_upper )
118+
119+ if proposal_power :
120+ clamped = _bounds .clamp_to_bounds (
121+ proposal_power ,
91122 lower_bound ,
92123 upper_bound ,
93124 exclusion_bounds ,
94- ):
125+ )
126+ match clamped :
95127 case (None , power ) | (power , None ) if power :
96128 unshifted_power = power
97129 case (power_low , power_high ) if power_low and power_high :
98- if (
99- power_high - next_proposal .preferred_power
100- < next_proposal .preferred_power - power_low
101- ):
130+ if power_high - proposal_power < proposal_power - power_low :
102131 unshifted_power = power_high
103132 else :
104133 unshifted_power = power_low
105134 case _:
106135 pass
107136
108- # Shift the bounds if no clamping bounds are specified.
109- if (
110- next_proposal .bounds .lower is None
111- and next_proposal .bounds .upper is None
112- and next_proposal .preferred_power is not None
113- ):
114- lower_bound = lower_bound - unshifted_power
115- upper_bound = upper_bound - unshifted_power
116- target_power += unshifted_power
117- unshifted_power = Power .zero ()
118- continue
119-
120- proposal_lower = next_proposal .bounds .lower or lower_bound
121- proposal_upper = next_proposal .bounds .upper or upper_bound
122- # If the bounds from the current proposal are fully within the exclusion
123- # bounds, then don't use them to narrow the bounds further. This allows
124- # subsequent proposals to not be blocked by the current proposal.
125- match _bounds .check_exclusion_bounds_overlap (
126- proposal_lower , proposal_upper , exclusion_bounds
127- ):
128- case (True , True ):
129- continue
130- lower_bound = max (lower_bound , proposal_lower )
131- upper_bound = min (upper_bound , proposal_upper )
132137 lower_bound , upper_bound = _bounds .adjust_exclusion_bounds (
133138 lower_bound , upper_bound , exclusion_bounds
134139 )
135140
136- target_power += unshifted_power
141+ lower_bound = lower_bound - unshifted_power
142+ upper_bound = upper_bound - unshifted_power
143+ target_power += unshifted_power
144+
145+ if exclusion_bounds is not None :
146+ exclusion_bounds = Bounds [Power ](
147+ exclusion_bounds .lower - unshifted_power ,
148+ exclusion_bounds .upper - unshifted_power ,
149+ )
137150
138- return target_power
151+ return target_power , Bounds [ Power ]( lower = lower_bound , upper = upper_bound )
139152
140153 def _validate_component_ids (
141154 self ,
@@ -217,7 +230,7 @@ def calculate_target_power(
217230 if proposals is None :
218231 return None
219232
220- target_power = self ._calc_target_power (proposals , system_bounds )
233+ target_power , _ = self ._calc_targets (proposals , system_bounds )
221234
222235 if (
223236 must_return_power
@@ -247,77 +260,13 @@ def get_status( # pylint: disable=too-many-locals
247260 the given priority.
248261 """
249262 target_power = self ._target_power .get (component_ids )
250- if system_bounds .inclusion_bounds is None :
251- return _Report (
252- target_power = target_power ,
253- _inclusion_bounds = None ,
254- _exclusion_bounds = system_bounds .exclusion_bounds ,
255- )
256-
257- lower_bound = system_bounds .inclusion_bounds .lower
258- upper_bound = system_bounds .inclusion_bounds .upper
259-
260- exclusion_bounds = None
261- if system_bounds .exclusion_bounds is not None and (
262- system_bounds .exclusion_bounds .lower != Power .zero ()
263- or system_bounds .exclusion_bounds .upper != Power .zero ()
264- ):
265- exclusion_bounds = system_bounds .exclusion_bounds
266-
267- limited_power = Power .zero ()
268- for next_proposal in sorted (
269- self ._component_buckets .get (component_ids , []), reverse = True
270- ):
271- if next_proposal .priority <= priority :
272- break
273- if next_proposal .preferred_power :
274- match _bounds .clamp_to_bounds (
275- next_proposal .preferred_power ,
276- lower_bound ,
277- upper_bound ,
278- exclusion_bounds ,
279- ):
280- case (None , power ) | (power , None ) if power :
281- limited_power = power
282- case (power_low , power_high ) if power_low and power_high :
283- if (
284- power_high - next_proposal .preferred_power
285- < next_proposal .preferred_power - power_low
286- ):
287- limited_power = power_high
288- else :
289- limited_power = power_low
290- case _:
291- pass
292- # Shift the bounds if no clamping bounds are specified.
293- if (
294- next_proposal .bounds .lower is None
295- and next_proposal .bounds .upper is None
296- and next_proposal .preferred_power is not None
297- ):
298- lower_bound = lower_bound - limited_power
299- upper_bound = upper_bound - limited_power
300- limited_power = Power .zero ()
301- continue
302- proposal_lower = next_proposal .bounds .lower or lower_bound
303- proposal_upper = next_proposal .bounds .upper or upper_bound
304- match _bounds .check_exclusion_bounds_overlap (
305- proposal_lower , proposal_upper , exclusion_bounds
306- ):
307- case (True , True ):
308- continue
309- calc_lower_bound = max (lower_bound , proposal_lower )
310- calc_upper_bound = min (upper_bound , proposal_upper )
311- if calc_lower_bound <= calc_upper_bound :
312- lower_bound , upper_bound = _bounds .adjust_exclusion_bounds (
313- calc_lower_bound , calc_upper_bound , exclusion_bounds
314- )
315- else :
316- break
263+ _ , bounds = self ._calc_targets (
264+ self ._component_buckets .get (component_ids , set ()), system_bounds , priority
265+ )
317266 return _Report (
318267 target_power = target_power ,
319268 _inclusion_bounds = timeseries .Bounds [Power ](
320- lower = lower_bound , upper = upper_bound
269+ lower = bounds . lower , upper = bounds . upper
321270 ),
322271 _exclusion_bounds = system_bounds .exclusion_bounds ,
323272 )
0 commit comments