feat(MLRsearch): MLRsearch v7
[csit.git] / resources / libraries / python / MLRsearch / config.py
diff --git a/resources/libraries/python/MLRsearch/config.py b/resources/libraries/python/MLRsearch/config.py
new file mode 100644 (file)
index 0000000..7aa8ed7
--- /dev/null
@@ -0,0 +1,179 @@
+# Copyright (c) 2023 Cisco and/or its affiliates.
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at:
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+"""Module defining Config class."""
+
+from collections.abc import Iterable
+from dataclasses import dataclass
+from typing import Optional
+
+from .dataclass import DataclassProperty
+from .search_goal import SearchGoal
+from .search_goal_tuple import SearchGoalTuple
+
+
+@dataclass
+class Config:
+    """Structure containing several static config items.
+
+    The main MLRsearch algorithm uses multiple customizable values.
+    Pylint complains if the values appear as long argument lists
+    or multiple local variables.
+
+    This class offers a storage for values which do not contain
+    internally mutable state and are set at an unknown time
+    before the search starts. This way users can override only some values,
+    and do it over multiple calls.
+    All "official" user inputs are contained here.
+
+    Properties are defined to enforce the requirements on allowed values.
+    All fields have default values, so instances can be created without any.
+    It is still recommended to set all values after instantiation,
+    as the defaults may change in the next version.
+
+    As some relations between values of different fields are required,
+    users must take care to set them in the correct order.
+
+    For example, min_load has to be set to a value smaller
+    than the current value of max_load.
+    """
+
+    # Externally visible "fields" (but in fact redefined as properties).
+    goals: SearchGoalTuple = SearchGoalTuple((SearchGoal(),))
+    """Container holding search goals."""
+    min_load: float = 9001.0
+    """Each trial measurement must have intended load at least this [tps]."""
+    max_load: float = 1e9
+    """Each trial measurement must have intended load at most this [tps]."""
+    search_duration_max: float = 1200.0
+    """The search will end as a failure this long [s] after it is started."""
+    warmup_duration: float = 1.0
+    """If specified, one trial at max load and this duration is performed
+    before the usual search starts. None converts to zero and means no warmup.
+    The results of that one trial are ignored."""
+
+    @DataclassProperty
+    def goals(self) -> SearchGoalTuple:
+        """Return the reference to the current container of goals.
+
+        :returns: The current container instance.
+        :rtype: SearchGoalTuple
+        """
+        return self._goals
+
+    @goals.setter
+    def goals(self, goals: Iterable[SearchGoal]) -> None:
+        """Create and store the goal container.
+
+        :param goals: Search goals to add to the container to store.
+        :type goals: Iterable[SearchGoal]
+        :raises ValueError: If there are no goals.
+        :raises TypeError: If any of the goals is not a SearchGoal.
+        """
+        self._goals = SearchGoalTuple(goals)
+
+    @DataclassProperty
+    def min_load(self) -> float:
+        """Getter for min load, no logic here.
+
+        :returns: Currently set minimal intended load [tps].
+        :rtype: float
+        """
+        return self._min_load
+
+    @min_load.setter
+    def min_load(self, load: float) -> None:
+        """Set min load after converting type and checking value.
+
+        :param load: Minimal intended load [tps] to set.
+        :type load: float
+        :raises ValueError: If the argument is found invalid.
+        """
+        load = float(load)
+        if load <= 0.0:
+            raise ValueError(f"Min load {load} must be positive.")
+        # At the time init is first called, _max_load is not set yet.
+        if hasattr(self, "_max_load") and load >= self.max_load:
+            raise ValueError(f"Min load {load} must be smaller.")
+        self._min_load = load
+
+    @DataclassProperty
+    def max_load(self) -> float:
+        """Getter for max load, no logic here.
+
+        :returns: Currently set maximal intended load [tps].
+        :rtype: float
+        """
+        return self._max_load
+
+    @max_load.setter
+    def max_load(self, load: float) -> None:
+        """Set max load after converting type and checking value.
+
+        :param load: Minimal intended load [tps] to set.
+        :type load: float
+        :raises ValueError: If the argument is found invalid.
+        """
+        load = float(load)
+        if load <= self.min_load:
+            raise ValueError(f"Max load {load} must be bigger.")
+        self._max_load = load
+
+    @DataclassProperty
+    def search_duration_max(self) -> float:
+        """Getter for max search duration, no logic here.
+
+        :returns: Currently set max search duration [s].
+        :rtype: float
+        """
+        return self._search_duration_max
+
+    @search_duration_max.setter
+    def search_duration_max(self, duration: float) -> None:
+        """Set max search duration after converting and checking value.
+
+        :param duration: Search duration maximum [s] to set.
+        :type duration: float
+        :raises ValueError: If the argument is found invalid.
+        """
+        duration = float(duration)
+        if duration <= 0.0:
+            raise ValueError(f"Search duration max too small: {duration}")
+        self._search_duration_max = duration
+
+    @DataclassProperty
+    def warmup_duration(self) -> float:
+        """Getter for warmup duration, no logic here.
+
+        :returns: Currently set max search duration [s].
+        :rtype: float
+        """
+        return self._warmup_duration
+
+    @warmup_duration.setter
+    def warmup_duration(self, duration: Optional[float]) -> None:
+        """Set warmup duration after converting and checking value.
+
+        Zero duration is treated as None, meaning no warmup trial.
+
+        :param duration: Warmup duration [s] to set.
+        :type duration: Optional(float)
+        :raises ValueError: If the argument is found invalid.
+        """
+        if duration:
+            duration = float(duration)
+            if duration < 0.0:
+                raise ValueError(f"Warmup duration too small: {duration}")
+        else:
+            duration = 0.0
+        self._warmup_duration = duration