+ def binary_search(self, b_min, b_max, traffic_type, skip_max_rate=False):
+ """Binary search of rate with loss below acceptance criteria.
+
+ :param b_min: Min range rate.
+ :param b_max: Max range rate.
+ :param traffic_type: Traffic profile.
+ :param skip_max_rate: Start with max rate first
+ :type b_min: float
+ :type b_max: float
+ :type traffic_type: str
+ :type skip_max_rate: bool
+ :return: nothing
+ """
+
+ if not self._rate_min <= float(b_min) <= self._rate_max:
+ raise ValueError("Min rate is not in min,max range")
+ if not self._rate_min <= float(b_max) <= self._rate_max:
+ raise ValueError("Max rate is not in min,max range")
+ if float(b_max) < float(b_min):
+ raise ValueError("Min rate is greater than max rate")
+
+ # binary search
+ if skip_max_rate:
+ # rate is half of interval + start of interval
+ rate = ((float(b_max) - float(b_min)) / 2) + float(b_min)
+ else:
+ # rate is max of interval
+ rate = float(b_max)
+ # rate diff with previous run
+ rate_diff = abs(self._last_binary_rate - rate)
+
+ # convergence criterium
+ if float(rate_diff) < float(self._binary_convergence_threshold):
+ if not self._search_result_rate:
+ self._search_result = SearchResults.FAILURE
+ else:
+ self._search_result = SearchResults.SUCCESS
+ return
+
+ self._last_binary_rate = rate
+
+ res = []
+ for dummy in range(self._max_attempts):
+ res.append(self.measure_loss(rate, self._frame_size,
+ self._loss_acceptance,
+ self._loss_acceptance_type,
+ traffic_type))
+
+ res = self._get_res_based_on_search_type(res)
+
+ # loss occurred and it was above acceptance criteria
+ if not res:
+ self.binary_search(b_min, rate, traffic_type, True)
+ # there was no loss / loss below acceptance criteria
+ else:
+ self._search_result_rate = rate
+ self.binary_search(rate, b_max, traffic_type, True)
+
+ def combined_search(self, start_rate, traffic_type):
+ """Combined search of rate with loss below acceptance criteria.
+
+ :param start_rate: Initial rate.
+ :param traffic_type: Traffic profile.
+ :type start_rate: float
+ :type traffic_type: str
+ :return: nothing
+ """
+
+ self.linear_search(start_rate, traffic_type)
+
+ if self._search_result in [SearchResults.SUCCESS,
+ SearchResults.SUSPICIOUS]:
+ b_min = self._search_result_rate
+ b_max = self._search_result_rate + self._rate_linear_step
+
+ # we found max rate by linear search
+ if self.floats_are_close_equal(float(b_min), self._rate_max):
+ return
+
+ # limiting binary range max value into max range
+ if float(b_max) > self._rate_max:
+ b_max = self._rate_max
+
+ # reset result rate
+ temp_rate = self._search_result_rate
+ self._search_result_rate = None
+
+ # we will use binary search to refine search in one linear step
+ self.binary_search(b_min, b_max, traffic_type, True)
+
+ # linear and binary search succeed
+ if self._search_result == SearchResults.SUCCESS:
+ return
+ # linear search succeed but binary failed or suspicious
+ else:
+ self._search_result = SearchResults.SUSPICIOUS
+ self._search_result_rate = temp_rate
+ else:
+ raise RuntimeError("Linear search FAILED")
+
+ @staticmethod
+ def floats_are_close_equal(num_a, num_b, rel_tol=1e-9, abs_tol=0.0):
+ """Compares two float numbers for close equality.
+
+ :param num_a: First number to compare.
+ :param num_b: Second number to compare.
+ :param rel_tol=1e-9: The relative tolerance.
+ :param abs_tol=0.0: The minimum absolute tolerance level.
+ :type num_a: float
+ :type num_b: float
+ :type rel_tol: float
+ :type abs_tol: float
+ :return: Returns True if num_a is close in value to num_b or equal.
+ False otherwise.
+ :rtype: boolean
+ """
+
+ if num_a == num_b:
+ return True
+
+ if rel_tol < 0.0 or abs_tol < 0.0:
+ raise ValueError('Error tolerances must be non-negative')
+
+ return abs(num_b - num_a) <= max(rel_tol * max(abs(num_a), abs(num_b)),
+ abs_tol)