aboutsummaryrefslogtreecommitdiffstats
path: root/resources/libraries/python/MLRsearch/PerDurationDatabase.py
diff options
context:
space:
mode:
Diffstat (limited to 'resources/libraries/python/MLRsearch/PerDurationDatabase.py')
-rw-r--r--resources/libraries/python/MLRsearch/PerDurationDatabase.py121
1 files changed, 121 insertions, 0 deletions
diff --git a/resources/libraries/python/MLRsearch/PerDurationDatabase.py b/resources/libraries/python/MLRsearch/PerDurationDatabase.py
new file mode 100644
index 0000000000..b069dd921a
--- /dev/null
+++ b/resources/libraries/python/MLRsearch/PerDurationDatabase.py
@@ -0,0 +1,121 @@
+# Copyright (c) 2021 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 PerDurationDatabase class."""
+
+import copy
+
+
+class PerDurationDatabase:
+ """List-like structure holding measurement results for one duration.
+
+ This is a building block for MeasurementDatabase.
+
+ This class hold measurements for single target duration value only,
+ so the logic is quite simple.
+
+ Several utility methods are added, accomplishing tasks useful for MLRsearch
+ (to be called by MeasurementDatabade).
+ """
+
+ def __init__(self, duration, measurements):
+ """Store (deep copy of) measurement results and normalize them.
+
+ The results have to have the corresponding target duration,
+ and there should be no duplicate target_tr values.
+ Empty iterable (zero measurements) is an acceptable input.
+
+ :param duration: All measurements have to have this target duration [s].
+ :param measurements: The measurement results to store.
+ :type duration: float
+ :type measurements: Iterable[ReceiveRateMeasurement]
+ :raises ValueError: If duration does not match or if TR duplicity.
+ """
+ self.duration = duration
+ self.measurements = [copy.deepcopy(meas) for meas in measurements]
+ self._normalize()
+
+ def __repr__(self):
+ """Return string executable to get equivalent instance.
+
+ :returns: Code to construct equivalent instance.
+ :rtype: str
+ """
+ return (
+ u"PerDurationDatabase("
+ f"duration={self.duration!r},"
+ f"measurements={self.measurements!r})"
+ )
+
+ def _normalize(self):
+ """Sort by target_tr, fail on detecting duplicate target_tr.
+
+ Also set effective loss ratios.
+ """
+ measurements = self.measurements
+ measurements.sort(key=lambda measurement: measurement.target_tr)
+ # Detect duplicated TRs.
+ previous_tr = None
+ for measurement in measurements:
+ current_tr = measurement.target_tr
+ if current_tr == previous_tr:
+ raise ValueError(
+ u"Transmit rate conflict:"
+ f" {measurement!r} {previous_tr!r}"
+ )
+ previous_tr = current_tr
+ # Update effective ratios.
+ ratio_previous = None
+ for measurement in measurements:
+ if ratio_previous is None:
+ ratio_previous = measurement.loss_ratio
+ measurement.effective_loss_ratio = ratio_previous
+ continue
+ ratio_previous = max(ratio_previous, measurement.loss_ratio)
+ measurement.effective_loss_ratio = ratio_previous
+
+ def add(self, measurement):
+ """Add measurement and normalize.
+
+ :param measurement: Measurement result to add to the database.
+ :type measurement: ReceiveRateMeasurement
+ """
+ # TODO: We should deepcopy either everywhere or nowhere.
+ self.measurements.append(measurement)
+ self._normalize()
+
+ def get_valid_bounds(self, ratio):
+ """Return None or a valid measurement for two tightest bounds.
+
+ The validity of a measurement to act as a bound is determined
+ by comparing the argument ratio with measurement's effective loss ratio.
+
+ Both lower and upper bounds are returned, both tightest and second
+ tightest. If some value is not available, None is returned instead.
+
+ :param ratio: Target ratio, valid has to be lower or equal.
+ :type ratio: float
+ :returns: Tightest lower bound, tightest upper bound,
+ second tightest lower bound, second tightest upper bound.
+ :rtype: 4-tuple of Optional[ReceiveRateMeasurement]
+ """
+ lower_1, upper_1, lower_2, upper_2 = None, None, None, None
+ for measurement in self.measurements:
+ if measurement.effective_loss_ratio > ratio:
+ if upper_1 is None:
+ upper_1 = measurement
+ continue
+ upper_2 = measurement
+ break
+ lower_1, lower_2 = measurement, lower_1
+ return lower_1, upper_1, lower_2, upper_2