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:
6 # http://www.apache.org/licenses/LICENSE-2.0
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.
14 """Module defining SearchGoal class."""
16 from dataclasses import dataclass
19 @dataclass(frozen=True, eq=True)
21 """This is the part of controller inputs that can be repeated
22 with different values. MLRsearch saves time by searching
23 for conditional throughput for each goal at the same time,
24 compared to repeated calls with separate goals.
26 Most fields (called attributes) of this composite
27 are relevant to the definition of conditional throughput.
28 The rest does not, but can affect the overal search time.
31 loss_ratio: float = 0.0
32 """The goal loss ratio.
33 A trial can satisfy the goal only when its trial loss ratio is not higher
34 than this. See MeasurementResult.loss_ratio for details.
35 A trial that does not satisfy this goal is called a bad trial."""
36 exceed_ratio: float = 0.5
37 """What portion of the duration sum can consist of bad trial seconds
38 while still being classified as lower bound (assuming no short trials)."""
39 relative_width: float = 0.005
40 """Target is achieved when the relevant lower bound
41 is no more than this (in units of the tightest upper bound) far
42 from the relevant upper bound."""
43 initial_trial_duration: float = 1.0
44 """Shortest trial duration employed when searching for this goal."""
45 final_trial_duration: float = 1.0
46 """Longest trial duration employed when searching for this goal."""
47 duration_sum: float = 20.0
48 """Minimal sum of durations of relevant trials sufficient to declare a load
49 to be upper or lower bound for this goal."""
50 preceding_targets: int = 2
51 """Number of increasingly coarser search targets to insert,
52 hoping to speed up searching for the final target of this goal."""
53 expansion_coefficient: int = 2
54 """External search multiplies width (in logarithmic space) by this."""
55 fail_fast: bool = True
56 """If true and min load is not an upper bound, raise.
57 If false, search will return None instead of lower bound."""
59 def __post_init__(self) -> None:
60 """Convert fields to correct types and call validate."""
61 super().__setattr__("loss_ratio", float(self.loss_ratio))
62 super().__setattr__("exceed_ratio", float(self.exceed_ratio))
63 super().__setattr__("relative_width", float(self.relative_width))
65 "final_trial_duration", float(self.final_trial_duration)
68 "initial_trial_duration", float(self.initial_trial_duration)
70 super().__setattr__("duration_sum", float(self.duration_sum))
71 super().__setattr__("preceding_targets", int(self.preceding_targets))
73 "expansion_coefficient", int(self.expansion_coefficient)
75 super().__setattr__("fail_fast", bool(self.fail_fast))
78 def validate(self) -> None:
79 """Make sure the initialized values conform to requirements.
81 :raises ValueError: If a field value is outside allowed bounds.
83 if self.loss_ratio < 0.0:
84 raise ValueError(f"Loss ratio cannot be negative: {self}")
85 if self.loss_ratio >= 1.0:
86 raise ValueError(f"Loss ratio must be lower than 1: {self}")
87 if self.exceed_ratio < 0.0:
88 raise ValueError(f"Exceed ratio cannot be negative: {self}")
89 if self.exceed_ratio >= 1.0:
90 raise ValueError(f"Exceed ratio must be lower than 1: {self}")
91 if self.relative_width <= 0.0:
92 raise ValueError(f"Relative width must be positive: {self}")
93 if self.relative_width >= 1.0:
94 raise ValueError(f"Relative width must be less than 1: {self}")
95 if self.initial_trial_duration <= 0.0:
96 raise ValueError(f"Initial trial duration must be positive: {self}")
97 if self.final_trial_duration < self.initial_trial_duration:
99 f"Single duration max must be at least initial: {self}"
101 if self.duration_sum < self.final_trial_duration:
103 "Min duration sum cannot be smaller"
104 f" than final trial duration: {self}"
106 if self.expansion_coefficient <= 1:
107 raise ValueError(f"Expansion coefficient is too small: {self}")
109 if self.preceding_targets < 0:
111 elif self.preceding_targets < 1:
112 if self.initial_trial_duration < self.duration_sum:
116 f"Number of preceding targets is too small: {self}"