def grid_search(self, grid_bounds: List[Tuple[float, float]], delta: float) -> float: """ Search optimal values along a grid in the parameter space. :param grid_bounds: list of tuples of lower and upper bounds :param delta: granularity of the grid search :return: optimized standard_bound """ if len(grid_bounds) != self.number_param: raise IllegalArgumentError( f"Number of parameters = {len(grid_bounds)} != {self.number_param}" ) list_slices = [slice(0)] * len(grid_bounds) for i in range(len(grid_bounds)): list_slices[i] = slice(grid_bounds[i][0], grid_bounds[i][1], delta) np.seterr("raise") try: grid_res = scipy.optimize.brute(func=self.eval_except, ranges=tuple(list_slices), full_output=True) # TODO: This exception is at the wrong spot! except FloatingPointError: return inf self.opt_x = grid_res[0].tolist() if self.print_x: print(f"grid search optimal x: {self.opt_x}") return grid_res[1]
def __init__(self, arr_list: List[Arrival], indep: bool, p_list: List[float]) -> None: self.arr_list = arr_list[:] if indep: self.p_list = [1.0] # self.p_n = 1.0 else: if len(p_list) != (len(self.arr_list) - 1): raise IllegalArgumentError( f"number of p={len(p_list)} and length of " f"arr_list={len(self.arr_list)} - 1 have to match") self.p_list = p_list[:] if isinstance(p_list, np.ndarray): np.concatenate((self.p_list, get_p_n(p_list=p_list))) else: self.p_list.append(get_p_n(p_list=p_list)) self.indep = indep
def delay(arr: Arrival, ser: Server, theta: float, prob_d: float, indep=True, p=1.0, geom_series=False) -> float: """Implements stationary standard_bound method""" if prob_d < 0.0 or prob_d > 1.0: raise IllegalArgumentError(f"prob_b={prob_d} must be in (0,1)") if indep: p = 1.0 q = 1.0 else: q = get_q(p=p) stability_check(arr=arr, ser=ser, theta=theta, indep=indep, p=p, q=q) sigma_sum, rho_diff = get_sigma_rho(arr=arr, ser=ser, theta=theta, indep=indep, p=p, q=q) if not geom_series: if arr.is_discrete(): log_part = log(prob_d * theta * (-rho_diff)) return (sigma_sum - log_part / theta) / ser.rho(theta=q * theta) else: tau_opt = 1 / (theta * ser.rho(theta=q * theta)) log_part = log(prob_d * theta * tau_opt * (-rho_diff)) return (tau_opt * ser.rho(theta=q * theta) + sigma_sum - log_part / theta) / ser.rho(theta=q * theta) if arr.is_discrete(): log_part = log(prob_d * (1 - exp(theta * rho_diff))) return (sigma_sum - log_part / theta) / ser.rho(theta=q * theta) else: tau_opt = log(arr.rho(theta=p * theta) / ser.rho(theta=q * theta)) / (theta * rho_diff) log_part = log(prob_d * (1 - exp(theta * tau_opt * rho_diff))) return (tau_opt * arr.rho(theta=p * theta) + sigma_sum - log_part / theta) / ser.rho(theta=q * theta)
def pattern_search(self, start_list: List[float], delta=3.0, delta_min=0.01) -> float: """ Optimization in Hooke and Jeeves. :param start_list: list of starting values :param delta: initial step length :param delta_min: final step length :return: optimized standard_bound """ if len(start_list) != self.number_param: raise IllegalArgumentError( f"Number of parameters {len(start_list)} is wrong") optimum_current = self.eval_except(param_list=start_list) optimum_new = optimum_current param_list = start_list[:] param_new = param_list[:] while delta > delta_min: for index, value in enumerate(param_list): param_new[index] = value + delta candidate_plus = self.eval_except(param_list=param_new) param_new[index] = value - delta candidate_minus = self.eval_except(param_list=param_new) if candidate_plus < optimum_new: param_new[index] = value + delta optimum_new = candidate_plus elif candidate_minus < optimum_new: param_new[index] = value - delta optimum_new = candidate_minus if optimum_new < optimum_current: # i.e., exploration step was successful param_old = param_list[:] param_list = param_new[:] optimum_current = optimum_new for index in range(len(param_list)): param_new[index] = 2 * param_list[index] - param_old[index] # try a pattern step candidate_new = self.eval_except(param_list=param_new) if candidate_new < optimum_current: param_list = param_new[:] optimum_current = optimum_new else: param_new = param_list[:] delta *= 0.5 self.opt_x = param_list if self.print_x: print(f"patten search optimal x: {self.opt_x}") return optimum_new
def nelder_mead_old(self, simplex: np.ndarray, nelder_mead_param: NelderMeadParameters, sd_min=10**(-2)): """ Nelder-Mead Optimization. :param simplex: initial parameter simplex :param nelder_mead_param: object that contains all the Nelder-Mead-parameters :param sd_min: abort criterion (detect when the changes become very small) :return: optimized standard_bound """ number_rows = simplex.shape[0] number_columns = simplex.shape[1] # number of rows is the number of points = number of columns + 1 # number of columns is the number of parameters if number_rows is not number_columns + 1: raise IllegalArgumentError( f"array argument is not a simplex, rows: {number_rows}," f" columns: {number_columns}") reflection_alpha = nelder_mead_param.reflection_alpha expansion_gamma = nelder_mead_param.expansion_gamma contraction_beta = nelder_mead_param.contraction_beta shrink_gamma = nelder_mead_param.shrink_gamma # print("simplex_start", simplex) # print("centroid", centroid_without_one_row(simplex=simplex, index=0)) y_value = np.empty(number_rows) index = 0 # print(simplex_start) for row in simplex: # print("row = ", row) y_value[index] = self.eval_except(param_list=row) index += 1 # print("y_value: ", y_value) best_index: int = np.nanargmin(y_value) # print("best index: ", best_index) while np.std(y_value, ddof=1) > sd_min: # print("simplex = ", simplex) # print("y_value = ", y_value) # print("standard_deviation of y = ", np.std(y_value, ddof=1)) worst_index: int = np.argmax(y_value) best_index: int = np.argmin(y_value) # compute centroid without worst row centroid = centroid_without_one_row(simplex=simplex, index=worst_index) # print("centroid: ", centroid) # print("worst_point: ", simplex_start[worst_index]) p_reflection = ( 1 + reflection_alpha ) * centroid - reflection_alpha * simplex[worst_index] # print("p_reflection", p_reflection) y_p_reflection = self.eval_except(param_list=p_reflection) # print("y_p_reflection", y_p_reflection) second_worst_index = np.argsort(y_value)[-2] # print("second_worst_index", second_worst_index) if y_p_reflection < y_value[best_index]: p_expansion = expansion_gamma * p_reflection + ( 1 - expansion_gamma) * centroid y_p_expansion = self.eval_except(param_list=p_expansion) if y_p_expansion < y_value[best_index]: simplex[worst_index] = p_expansion y_value[worst_index] = y_p_expansion else: simplex[worst_index] = p_reflection y_value[worst_index] = y_p_reflection elif y_p_reflection > y_value[second_worst_index]: if y_p_reflection < y_value[worst_index]: simplex[worst_index] = p_reflection y_value[worst_index] = y_p_reflection p_contraction = contraction_beta * simplex[worst_index] + ( 1 - contraction_beta) * centroid y_p_contraction = self.eval_except(param_list=p_contraction) if y_p_contraction < y_value[worst_index]: simplex[worst_index] = p_contraction y_value[worst_index] = y_p_contraction else: simplex = average_towards_best_row( simplex=simplex, best_index=best_index, shrink_factor=shrink_gamma) index = 0 for row in simplex: y_value[index] = self.eval_except(param_list=row) index += 1 else: simplex[worst_index] = p_reflection y_value[worst_index] = y_p_reflection self.opt_x = simplex[best_index] if self.print_x: print(f"NM old optimal x: {self.opt_x}") return y_value[best_index]
def pmoo_explicit(foi: Flow, cross_flows: List[Flow], ser_list: List[Server], theta: float, perform_param: PerformParameter, indep=True) -> float: """Explicit computation""" if foi.arr.is_discrete() is False: raise NotImplementedError( "Only implemented for discrete-time processes") if (perform_param.perform_metric is not PerformEnum.DELAY_PROB and perform_param.perform_metric is not PerformEnum.DELAY): raise IllegalArgumentError( "This function can only be used for the delay / delay probability") if indep is False: raise NotImplementedError("Only implemented for independent processes") residual_rate_list = [0.0] * len(ser_list) sigma_sum = 0.0 foi_rate = foi.arr.rho(theta=theta) # add all latencies for k, server in enumerate(ser_list): residual_rate_list[k] = server.rho(theta=theta) sigma_sum += server.sigma(theta=theta) # add all bursts and compute residual rates sigma_sum += foi.arr.sigma(theta=theta) for i, cross_flow in enumerate(cross_flows): sigma_sum += cross_flow.arr.sigma(theta=theta) for server_index in cross_flow.server_indices: cross_arr_rate = cross_flow.arr.rho(theta=theta) residual_rate_list[server_index] -= cross_arr_rate for res_rate in residual_rate_list: if res_rate - foi_rate <= 0: raise ParameterOutOfBounds("Stability condition is violated") if perform_param.perform_metric == PerformEnum.DELAY_PROB: sum_j = 0.0 for j, residual_rate_j in enumerate(residual_rate_list): prod_k = 1.0 for k, residual_rate_k in enumerate(residual_rate_list): if k is not j: rate_diff = residual_rate_j - residual_rate_k if rate_diff == 0.0: raise IllegalArgumentError("Multiplicity is not = 1") prod_k *= 1 / (1 - exp(theta * rate_diff)) prod_k *= exp( theta * sigma_sum) / (1 - exp(theta * (foi_rate - residual_rate_j))) prod_k *= exp(-theta * residual_rate_j * perform_param.value) sum_j += prod_k return sum_j elif perform_param.perform_metric == PerformEnum.DELAY: target_delay_prob = perform_param.value def helper_function(delay: float) -> float: perform_delay = PerformParameter( perform_metric=PerformEnum.DELAY_PROB, value=delay) current_delay_prob = pmoo_explicit(foi=foi, cross_flows=cross_flows, ser_list=ser_list, theta=theta, perform_param=perform_delay, indep=indep) return target_delay_prob - current_delay_prob res = scipy.optimize.bisect(helper_function, a=0.1, b=10000, full_output=True) return res[0] else: raise IllegalArgumentError( f"{perform_param.perform_metric} is an infeasible " f"performance metric")
def sfa_explicit(foi: ArrivalDistribution, leftover_service_list: List[Server], theta: float, perform_param: PerformParameter, indep=True) -> float: """Explicit computation""" if foi.is_discrete() is False: raise NotImplementedError( "Only implemented for discrete-time processes") if (perform_param.perform_metric is not PerformEnum.DELAY_PROB and perform_param.perform_metric is not PerformEnum.DELAY): raise IllegalArgumentError( "This function can only be used for the delay / delay probability") if indep is False: raise NotImplementedError("Only implemented for independent processes") residual_rate_list = [0.0] * len(leftover_service_list) sigma_sum = 0.0 foi_rate = foi.rho(theta=theta) # add all latencies for k, server in enumerate(leftover_service_list): stability_check(arr=foi, ser=server, theta=theta, indep=True) sigma_sum += server.sigma(theta=theta) residual_rate_list[k] = server.rho(theta=theta) # print(f"residual_rate_list[{k}] = {residual_rate_list[k]}") # add all bursts and compute residual rates sigma_sum += foi.sigma(theta=theta) if perform_param.perform_metric == PerformEnum.DELAY_PROB: sum_j = 0.0 for j, residual_rate_j in enumerate(residual_rate_list): prod_k = 1.0 for k, residual_rate_k in enumerate(residual_rate_list): if k is not j: rate_diff = residual_rate_j - residual_rate_k if rate_diff == 0.0: raise IllegalArgumentError("Multiplicity is not = 1") prod_k *= 1 / (1 - exp(theta * rate_diff)) prod_k *= 1 / (1 - exp(theta * (foi_rate - residual_rate_j))) prod_k *= exp(-theta * (len(leftover_service_list) - 1) * residual_rate_j * perform_param.value) sum_j += prod_k return sum_j * exp(theta * sigma_sum) elif perform_param.perform_metric == PerformEnum.DELAY: target_delay_prob = perform_param.value def helper_function(delay: float) -> float: perform_delay = PerformParameter( perform_metric=PerformEnum.DELAY_PROB, value=delay) current_delay_prob = sfa_explicit( foi=foi, leftover_service_list=leftover_service_list, theta=theta, perform_param=perform_delay, indep=indep) return target_delay_prob - current_delay_prob res = scipy.optimize.bisect(helper_function, a=0.1, b=10000, full_output=True) return res[0] else: raise IllegalArgumentError( f"{perform_param.perform_metric} is an infeasible " f"performance metric")
def msob_fp_array_to_results(title: str, arrival_enum: ArrivalEnum, perform_param: PerformParameter, opt_method: OptMethod, mc_dist: MonteCarloDist, param_array: np.array, res_array: np.array, number_flows: int, number_servers: int, compare_metric: ChangeEnum) -> dict: """Writes the array values into a dictionary""" if res_array.shape[1] != 3: raise IllegalArgumentError(f"Array must have 3 columns," f"not {res_array.shape[1]}") np.seterr(all='warn') res_array_no_full_nan = remove_full_nan_rows(full_array=res_array) valid_iterations = res_array_no_full_nan.shape[0] if compare_metric == ChangeEnum.RATIO_REF_NEW: change_vec_server_bound = np.divide(res_array[:, 0], res_array[:, 1]) change_vec_pmoo_fp = np.divide(res_array[:, 0], res_array[:, 2]) elif compare_metric == ChangeEnum.RATIO_NEW_REF: change_vec_server_bound = np.divide(res_array[:, 1], res_array[:, 0]) change_vec_pmoo_fp = np.divide(res_array[:, 2], res_array[:, 0]) elif compare_metric == ChangeEnum.RELATIVE_CHANGE: abs_vec_server_bound = np.subtract(res_array[:, 0], res_array[:, 1]) change_vec_server_bound = np.divide(abs_vec_server_bound, res_array[:, 0]) abs_vec_pmoo_fp = np.subtract(res_array[:, 0], res_array[:, 2]) change_vec_pmoo_fp = np.divide(abs_vec_pmoo_fp, res_array[:, 0]) else: raise NotImplementedError( f"Metric={compare_metric.name} is not implemented") only_improved_server_bound = change_vec_server_bound[ res_array[:, 0] > res_array[:, 1]] only_improved_pmoo_fp = change_vec_pmoo_fp[res_array[:, 0] > res_array[:, 2]] row_max_msob = np.nanargmax(change_vec_server_bound) opt_msob = change_vec_server_bound[row_max_msob] mean_msob = np.nanmean(change_vec_server_bound) median_improved_server_bound = np.nanmedian(only_improved_server_bound) row_max_pmoo_fp = np.nanargmax(change_vec_pmoo_fp) opt_pmoo_fp = change_vec_pmoo_fp[row_max_pmoo_fp] mean_pmoo_fp = np.nanmean(change_vec_pmoo_fp) median_improved_pmoo_fp = np.nanmedian(only_improved_pmoo_fp) if (perform_param.perform_metric == PerformEnum.DELAY_PROB or perform_param.perform_metric == PerformEnum.BACKLOG_PROB): number_standard_bound_valid = np.nansum( res_array_no_full_nan[:, 0] < 1) number_server_bound_valid = np.nansum(res_array_no_full_nan[:, 1] < 1) number_pmoo_fp_valid = np.nansum(res_array_no_full_nan[:, 2] < 1) else: number_standard_bound_valid = np.nansum( res_array_no_full_nan[:, 0] < inf) number_server_bound_valid = np.nansum( res_array_no_full_nan[:, 1] < inf) number_pmoo_fp_valid = np.nansum(res_array_no_full_nan[:, 2] < inf) number_improved_server_bound = np.sum( res_array_no_full_nan[:, 0] > res_array_no_full_nan[:, 1]) number_improved_pmoo_fp = np.sum( res_array_no_full_nan[:, 0] > res_array_no_full_nan[:, 2]) best_approach = np.nanargmin(res_array_no_full_nan, axis=1) standard_best = np.count_nonzero(best_approach == 0) msob_best = np.count_nonzero(best_approach == 1) fp_best = np.count_nonzero(best_approach == 2) res_dict = { "Name": "Value", "topology": title, "arrival_distribution": arrival_enum.name } opt_dict = { "Name": "Value", "topology": title, "arrival_distribution": arrival_enum.name } for j in range(number_flows): if arrival_enum == ArrivalEnum.DM1: opt_dict[f"pmoo_fp_lamb{j + 1}"] = format( param_array[row_max_pmoo_fp, j], '.3f') opt_dict[f"server_bound_lamb{j + 1}"] = format( param_array[row_max_msob, j], '.3f') elif arrival_enum == ArrivalEnum.MD1: opt_dict[f"pmoo_fp_lamb{j + 1}"] = format( param_array[row_max_pmoo_fp, j], '.3f') opt_dict[f"ser_bound_lamb{j + 1}"] = format( param_array[row_max_msob, j], '.3f') elif arrival_enum == ArrivalEnum.MMOODisc: opt_dict[f"pmoo_fp_stay_on{j + 1}"] = format( param_array[row_max_pmoo_fp, j], '.3f') opt_dict[f"pmoo_fp_stay_off{j + 1}"] = format( param_array[row_max_pmoo_fp, number_flows + j], '.3f') opt_dict[f"pmoo_fp_burst{j + 1}"] = format( param_array[row_max_pmoo_fp, 2 * number_flows + j], '.3f') opt_dict[f"ser_bound_stay_on{j + 1}"] = format( param_array[row_max_msob, j], '.3f') opt_dict[f"ser_bound_stay_off{j + 1}"] = format( param_array[row_max_msob, number_flows + j], '.3f') opt_dict[f"ser_bound_burst{j + 1}"] = format( param_array[row_max_msob, 2 * number_flows + j], '.3f') elif arrival_enum == ArrivalEnum.MMOOFluid: opt_dict[f"pmoo_fp_mu{j + 1}"] = format( param_array[row_max_pmoo_fp, j], '.3f') opt_dict[f"pmoo_fp_lamb{j + 1}"] = format( param_array[row_max_pmoo_fp, number_flows + j], '.3f') opt_dict[f"pmoo_fp_burst{j + 1}"] = format( param_array[row_max_pmoo_fp, 2 * number_flows + j], '.3f') opt_dict[f"ser_bound_mu{j + 1}"] = format( param_array[row_max_msob, j], '.3f') opt_dict[f"ser_bound_lamb{j + 1}"] = format( param_array[row_max_msob, number_flows + j], '.3f') opt_dict[f"ser_bound_burst{j + 1}"] = format( param_array[row_max_msob, 2 * number_flows + j], '.3f') else: raise NotImplementedError( f"Arrival parameter={arrival_enum.name} is not implemented") for j in range(number_servers): opt_dict[f"pmoo_fp_rate{j + 1}"] = format( param_array[row_max_pmoo_fp, arrival_enum.number_parameters() * number_flows + j], '.3f') opt_dict[f"server_bound_rate{j + 1}"] = format( param_array[row_max_msob, arrival_enum.number_parameters() * number_flows + j], '.3f') opt_dict.update({ "opt_pmoo_fp": format(opt_pmoo_fp, '.3f'), "opt_msob": format(opt_msob, '.3f'), "valid iterations": res_array.shape[0], "PerformParamValue": perform_param.value, "optimization": opt_method.name, "compare_metric": compare_metric.name, "MCDistribution": mc_dist.to_name(), "MCParam": mc_dist.param_to_string() }) res_dict.update({ "mean_pmoo_fp": mean_pmoo_fp, "mean_msob": mean_msob, "median_improved_pmoo_fp": median_improved_pmoo_fp, "median_improved_server_bound": median_improved_server_bound, "number standard bound is valid": number_standard_bound_valid, "number server bound is valid": number_server_bound_valid, "number PMOO_FP bound is valid": number_pmoo_fp_valid, "number server bound is improvement": number_improved_server_bound, "number PMOO_FP is improvement": number_improved_pmoo_fp, "valid iterations": valid_iterations, "number standard bound is best": standard_best, "number server bound is best": msob_best, "number PMOO_FP bound is best": fp_best, }) filename = title filename += f"_optimal_{perform_param.to_name()}_{arrival_enum.name}_" \ f"MC{mc_dist.to_name()}_{opt_method.name}_" \ f"{compare_metric.name}" with open(filename + ".csv", 'w') as csv_file: writer = csv.writer(csv_file) for key, value in opt_dict.items(): writer.writerow([key, value]) return res_dict
def sfa_tandem_bound(foi: ArrivalDistribution, leftover_service_list: List[Server], theta: float, perform_param: PerformParameter, p_list: [float], e2e_enum: E2EEnum, indep=True, geom_series=True) -> float: if foi.is_discrete() is False: raise NotImplementedError( "Only implemented for discrete-time processes") if indep: p_list = [1.0] else: if len(p_list) != (len(leftover_service_list) - 1): raise IllegalArgumentError( f"number of p={len(p_list)} and length of " f"ser_list={len(leftover_service_list)} - 1 have to match") if isinstance(p_list, np.ndarray): p_list = np.append(p_list, get_p_n(p_list=p_list)) else: p_list.append(get_p_n(p_list=p_list)) foi_rate = foi.rho(theta=theta) residual_rate_list = [0.0] * len(leftover_service_list) residual_rate_with_foi_list = [0.0] * len(leftover_service_list) sigma_sum = 0.0 if indep: for i, server in enumerate(leftover_service_list): stability_check(arr=foi, ser=server, theta=theta, indep=True) sigma_sum += server.sigma(theta=theta) residual_rate_list[i] = server.rho(theta=theta) residual_rate_with_foi_list[i] = residual_rate_list[i] - foi_rate else: for i, server in enumerate(leftover_service_list): stability_check(arr=foi, ser=server, theta=theta, indep=False, p=1.0, q=p_list[i]) # p = 1.0 is from the foi sigma_sum += server.sigma(theta=p_list[i] * theta) residual_rate_list[i] = server.rho(theta=p_list[i] * theta) residual_rate_with_foi_list[i] = residual_rate_list[i] - foi_rate sigma_sum += foi.sigma(theta=theta) if e2e_enum == E2EEnum.ARR_RATE: gamma = 1.0 for residual_rate in residual_rate_with_foi_list: if geom_series: gamma *= 1 / (1 - exp(-theta * residual_rate)) else: gamma *= 1 / residual_rate if perform_param.perform_metric == PerformEnum.BACKLOG_PROB: return exp(-theta * perform_param.value) * exp( theta * sigma_sum) * gamma elif perform_param.perform_metric == PerformEnum.BACKLOG: return (theta * sigma_sum + log(gamma / perform_param.value)) / theta elif perform_param.perform_metric == PerformEnum.DELAY_PROB: return exp(-theta * foi_rate * perform_param.value) * exp( theta * sigma_sum) * gamma elif perform_param.perform_metric == PerformEnum.DELAY: return (theta * sigma_sum + log(gamma / perform_param.value)) / (theta * foi_rate) elif perform_param.perform_metric == PerformEnum.OUTPUT: return exp(theta * foi_rate * perform_param.value) * exp( theta * sigma_sum) * gamma else: raise NotImplementedError( f"{perform_param.perform_metric} is an infeasible " f"performance metric") elif e2e_enum == E2EEnum.MIN_RATE: min_residual_rate = min(residual_rate_list) min_rate_with_foi = min_residual_rate - foi_rate q = exp(-theta * min_rate_with_foi) d_lower = len(leftover_service_list) * q / (1 - q) if perform_param.perform_metric == PerformEnum.DELAY_PROB: if perform_param.value >= d_lower: T_over_n = perform_param.value / len(leftover_service_list) zeta = (1 + T_over_n)**(1 + T_over_n) / (T_over_n**T_over_n) return exp( -theta * min_residual_rate * perform_param.value) * exp( theta * sigma_sum) * (zeta**len(leftover_service_list)) else: raise ParameterOutOfBounds("Zeta condition is violated") elif perform_param.perform_metric == PerformEnum.DELAY: T_over_n = d_lower / len(leftover_service_list) # print(f"T_over_n={T_over_n}") zeta = (1 + T_over_n)**(1 + T_over_n) / (T_over_n**T_over_n) # print(f"zeta={zeta}") delay_bound = (theta * sigma_sum + log(1 / perform_param.value) + len(leftover_service_list) * log(zeta)) / ( theta * min_residual_rate) return max(d_lower, delay_bound) # counter = 0 # # if delay_bound > d_lower + 1e-6: # while delay_bound > d_lower + 1e-6 and counter < 5: # d_lower = delay_bound # # T_over_n = delay_bound / len(leftover_service_list) # zeta = (1 + T_over_n)**(1 + T_over_n) / (T_over_n** # T_over_n) # # delay_bound = sigma_sum / min_residual_rate + ( # len(leftover_service_list) * log(zeta) - # log(perform_param.value)) / (theta * min_residual_rate) # # counter += 1 # # return delay_bound # # else: # return d_lower # # else: # raise NotImplementedError(f"{perform_param.perform_metric} is " # f"an infeasible performance metric") elif e2e_enum == E2EEnum.RATE_DIFF: min_residual_rate = min(residual_rate_list) dominating_pole_index = residual_rate_list.index( min(residual_rate_list)) gamma = 1.0 for index, residual_rate in enumerate(residual_rate_list): if index is not dominating_pole_index: rate_diff = residual_rate - min_residual_rate if rate_diff == 0.0: delta = 0.5 * (min_residual_rate - foi_rate) min_residual_rate -= delta rate_diff = residual_rate - min_residual_rate try: gamma *= 1 / (1 - exp(-theta * rate_diff)) except ZeroDivisionError: return inf min_residual_rate_with_foi = min_residual_rate - foi_rate gamma *= 1 / (1 - exp(-theta * min_residual_rate_with_foi)) if perform_param.perform_metric == PerformEnum.DELAY_PROB: return exp(-theta * min_residual_rate * perform_param.value) * exp( theta * sigma_sum) * gamma elif perform_param.perform_metric == PerformEnum.DELAY: return (theta * sigma_sum + log(gamma / perform_param.value)) / ( theta * min_residual_rate) else: raise NotImplementedError( f"{perform_param.perform_metric} is an infeasible " f"performance metric") elif e2e_enum == E2EEnum.ANALYTIC_COMBINATORICS: min_residual_rate = min(residual_rate_list) min_rate_with_foi = min_residual_rate - foi_rate gamma = 1.0 k = 0 for index, residual_rate in enumerate(residual_rate_list): rate_diff = residual_rate - min_residual_rate if rate_diff > 0: rate_diff = residual_rate - min_residual_rate gamma *= 1 / (1 - exp(-theta * rate_diff)) else: k += 1 factor = 0.0 for i in range(k): factor += scipy.special.binom( perform_param.value + k - i - 2, perform_param.value - 1) / ( (1 - exp(-theta * min_rate_with_foi))**(i + 1)) if k == 0: factor = 1.0 if perform_param.perform_metric == PerformEnum.DELAY_PROB: return exp(-theta * min_residual_rate * perform_param.value) * exp( theta * sigma_sum) * gamma * factor elif perform_param.perform_metric == PerformEnum.DELAY: target_delay_prob = perform_param.value def helper_function(delay: float) -> float: perform_delay = PerformParameter( perform_metric=PerformEnum.DELAY_PROB, value=delay) current_delay_prob = sfa_tandem_bound( foi=foi, leftover_service_list=leftover_service_list, theta=theta, perform_param=perform_delay, p_list=p_list, e2e_enum=e2e_enum, indep=indep, geom_series=True) return target_delay_prob - current_delay_prob res = scipy.optimize.bisect(helper_function, a=1e-3, b=1e5, full_output=True) return res[0] else: raise NotImplementedError( f"{perform_param.perform_metric} is an infeasible " f"performance metric") elif e2e_enum == E2EEnum.BINOM: min_residual_rate = min(residual_rate_list) min_rate_with_foi = min_residual_rate - foi_rate q = exp(-theta * min_rate_with_foi) # print(f"q = {q}") d_upper = len(leftover_service_list) * q / (1 - q) if perform_param.perform_metric == PerformEnum.DELAY_PROB: if perform_param.value >= d_upper: return exp(-(theta * foi_rate + q) * perform_param.value) * exp(theta * sigma_sum) / ( 1 - q)**len(leftover_service_list) else: raise ParameterOutOfBounds("Zeta condition is violated") elif perform_param.perform_metric == PerformEnum.DELAY: delay_bound = (theta * sigma_sum + log(1 / perform_param.value) + len(leftover_service_list) * log(1 / (1 - q))) / ( theta * foi_rate + q) if delay_bound <= d_upper: return delay_bound else: raise ParameterOutOfBounds(f"delay_bound={delay_bound} must " f"be <= d_upper={d_upper}") else: raise NotImplementedError(f"{perform_param.perform_metric} is an " f"infeasible performance metric") else: raise NotImplementedError("SFA Analysis is not implemented")