Skip to content

Commit a1d430e

Browse files
committed
impl inprogress
Signed-off-by: Sahas Subramanian <[email protected]>
1 parent 1d182bf commit a1d430e

File tree

3 files changed

+214
-256
lines changed

3 files changed

+214
-256
lines changed

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

Lines changed: 58 additions & 109 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,24 +39,26 @@
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:
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

Comments
 (0)