コード例 #1
0
    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]
コード例 #2
0
    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
コード例 #3
0
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)
コード例 #4
0
    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
コード例 #5
0
    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]
コード例 #6
0
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")
コード例 #7
0
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")
コード例 #8
0
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
コード例 #9
0
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")