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