feat(MLRsearch): MLRsearch v7
[csit.git] / resources / libraries / python / MLRsearch / strategy / halve.py
1 # Copyright (c) 2023 Cisco and/or its affiliates.
2 # Licensed under the Apache License, Version 2.0 (the "License");
3 # you may not use this file except in compliance with the License.
4 # You may obtain a copy of the License at:
5 #
6 #     http://www.apache.org/licenses/LICENSE-2.0
7 #
8 # Unless required by applicable law or agreed to in writing, software
9 # distributed under the License is distributed on an "AS IS" BASIS,
10 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11 # See the License for the specific language governing permissions and
12 # limitations under the License.
13
14 """Module defining HalveStrategy class."""
15
16
17 from dataclasses import dataclass
18 from typing import Optional, Tuple
19
20 from ..discrete_interval import DiscreteInterval
21 from ..discrete_load import DiscreteLoad
22 from ..discrete_width import DiscreteWidth
23 from ..relevant_bounds import RelevantBounds
24 from .base import StrategyBase
25
26
27 @dataclass
28 class HalveStrategy(StrategyBase):
29     """First strategy to apply for a new current target.
30
31     Pick a load between initial lower bound and initial upper bound,
32     nominate it if it is (still) worth it.
33
34     In a sense, this can be viewed as an extension of preceding target's
35     bisect strategy. But as the current target may require a different
36     trial duration, it is better to do it for the new target.
37
38     Alternatively, this is a way to save one application
39     of subsequent refine strategy, thus avoiding reducing risk of triggering
40     an external search (slight time saver for highly unstable SUTs).
41     Either way, minor time save is achieved by preceding target
42     only needing to reach double of current target width.
43
44     If the distance between initial bounds is already at or below
45     current target width, the middle point is not nominated.
46     The reasoning is that in this case external search is likely
47     to get triggered by the subsequent refine strategies,
48     so attaining a relevant bound here is not as likely to help.
49     """
50
51     def nominate(
52         self, bounds: RelevantBounds
53     ) -> Tuple[Optional[DiscreteLoad], Optional[DiscreteWidth]]:
54         """Nominate the middle between initial lower and upper bound.
55
56         The returned width is the target width, even if initial bounds
57         happened to be closer together.
58
59         :param bounds: Freshly updated bounds relevant for current target.
60         :type bounds: RelevantBounds
61         :returns: Two nones or candidate intended load and duration.
62         :rtype: Tuple[Optional[DiscreteLoad], Optional[DiscreteWidth]]
63         """
64         if not self.initial_lower_load or not self.initial_upper_load:
65             return None, None
66         interval = DiscreteInterval(
67             lower_bound=self.initial_lower_load,
68             upper_bound=self.initial_upper_load,
69         )
70         wig = interval.width_in_goals(self.target.discrete_width)
71         if wig > 2.0:
72             # Can happen for initial target.
73             return None, None
74         if wig <= 1.0:
75             # Already was narrow enough, refinements shall be sufficient.
76             return None, None
77         load = interval.middle(self.target.discrete_width)
78         if self.not_worth(bounds, load):
79             return None, None
80         self.debug(f"Halving available: {load}")
81         # TODO: Report possibly smaller width?
82         self.expander.limit(self.target.discrete_width)
83         return load, self.target.discrete_width