def obtain_overall_costs(self, edge_selections):
        """
        ============================= [copy from offloading_common.py] =============================
        Calculate the overall costs, which is the sum of cost of each mobile device.

        :param edge_selections: the edge selection decisions for all mobile devices
        :return: overall costs
        """
        parameter = self.get_parameter()
        overall_costs = 0
        for i in range(parameter.get_user_num()):
            if parameter.get_task_requests()[i] == 1:
                division = int(sum(edge_selections[i]))
                if division:
                    # cost = self.obtain_time_consumption(
                    #     division, edge_selections[i], parameter.get_connectable_gains[i])
                    transmit_times = ToolFunction.obtain_transmit_times(
                        division, edge_selections[i], parameter,
                        parameter.get_connectable_gains()[i])
                    edge_exe_times = ToolFunction.obtain_edge_exe_times(
                        division, parameter)
                    edge_times = transmit_times + edge_exe_times
                    cost = max(edge_times) + parameter.get_local_exe_time(
                    ) + parameter.get_coordinate_cost() * division
                else:
                    cost = parameter.get_drop_penalty()
            else:
                cost = 0
            overall_costs += cost
        return overall_costs
    def generate_random_ins(self):
        """
        Generate random instance for those dimensions whose labels are True. In this version, we check whether the two
        constraints can be satisfied. (Actually, we first generate random edge_selections, and then convert it to an
        instance.)

        :return: a feasible instance
        """
        parameter = self.get_parameter()
        assignable_nums = np.repeat(parameter.get_max_assign(),
                                    parameter.get_server_num())
        edge_selections = self.greedy_sample(assignable_nums)
        ins_features = []
        for i in range(parameter.get_user_num()):
            if parameter.get_task_requests()[i] == 1:
                division = int(sum(edge_selections[i]))
                if division:
                    times = self.obtain_time_consumption(
                        division, edge_selections[i],
                        parameter.get_connectable_gains()[i])
                    energys = ToolFunction.obtain_transmit_energy(
                        division, edge_selections[i], parameter,
                        parameter.get_connectable_gains()[i])
                    # satisfy the constraint
                    if times >= parameter.get_ddl(
                    ) or energys > self.__battery_energy_levels[i]:
                        edge_selections[i] = np.zeros(len(edge_selections[i]))
                else:
                    pass
                for j in range(len(edge_selections[i])):
                    ins_features.append(edge_selections[i][j])
        dimension = Dimension(parameter.get_dim_size())
        instance = Instance(dimension)
        instance.set_features(ins_features)
        return instance
    def obtain_sub_problem_es(self, edge_selections):
        """
        Calculate the optimization goal of sub-problem $\mathcal{P}_2^{es}$.

        :param edge_selections: the edge selection decisions for all mobile devices
        :return: the optimization goal of $\mathcal{P}_2^{es}$
        """
        parameter = self.get_parameter()
        optimization_func_value = 0
        for i in range(parameter.get_user_num()):
            division = int(sum(edge_selections[i]))
            if division:
                energy_consumes = parameter.get_local_exe_energy() + \
                                  ToolFunction.obtain_transmit_energy(division, edge_selections[i], parameter,
                                                                      parameter.get_connectable_gains()[i])
                negative_part = self.__virtual_energy_levels[
                    i] * energy_consumes
            else:
                if self.__battery_energy_levels[
                        i] < parameter.get_local_exe_energy():
                    negative_part = 0
                else:
                    negative_part = self.__virtual_energy_levels[
                        i] * parameter.get_local_exe_energy()
            optimization_func_value -= negative_part
        optimization_func_value += parameter.get_v(
        ) * self.obtain_overall_costs(edge_selections)
        return optimization_func_value
    def update_energy_levels(self):
        """
        ============================= [copy from offloading_common.py] =============================
        Update the cost & virtual energy levels according to the involution expression \eqref{10}.

        :return: no return
        """
        parameter = self.get_parameter()
        for i in range(parameter.get_user_num()):
            division = int(sum(self.__edge_selections[i]))
            if division:
                self.__battery_energy_levels[i] = self.__battery_energy_levels[i] + \
                    self.__harvested_energys[i] - ToolFunction.obtain_transmit_energy(
                    division, self.__edge_selections[i], parameter, parameter.get_connectable_gains()[i]) - \
                    parameter.get_local_exe_energy()
            else:
                # check whether need to minus local_exe_energys
                # if self.__battery_energy_levels[i] < parameter.get_local_exe_energy():
                #     self.__battery_energy_levels[i] = self.__battery_energy_levels[i] + self.__harvested_energys[i]
                # else:
                #     self.__battery_energy_levels[i] = self.__battery_energy_levels[i] + \
                #         self.__harvested_energys[i] - parameter.get_local_exe_energy()
                self.__battery_energy_levels[i] = self.__battery_energy_levels[
                    i] + self.__harvested_energys[i]
            self.__virtual_energy_levels[i] = self.__battery_energy_levels[
                i] - parameter.get_perturbation_para()
    def generate_task_request(self):
        """
        Generate task request from Bernoulli Distribution.

        :return: a numpy array denoted task request, presented in [0, 1, ..., 1]
        """
        return ToolFunction.sample_from_bernoulli(self.__user_num,
                                                  self.__task_request_prob)
示例#6
0
    def generate_task_request(self, parameter):
        """
        Generate task request from Bernoulli Distribution.

        :param parameter: the instance of class Parameter
        :return: a numpy array denoted task request, presented in [0, 1, ..., 1]
        """
        return ToolFunction.sample_from_bernoulli(self.get_user_num(),
                                                  parameter)
示例#7
0
    def obtain_harvested_energy(self, parameter):
        """
        Randomly choose energy between $[0, E_i^H]$.

        :param parameter: the instance of class Parameter
        :return: actually harvested energy, $\alpha_i$
        """
        return random.uniform(
            0, ToolFunction.generate_harvestable_energy(parameter))
    def obtain_time_consumption(self, division, edge_selection,
                                channel_power_gains):
        """
        Calculate the time consumption on transmission and edge execution for one mobile device.

        :param division: the number of chosen edge sites (not zero)
        :param edge_selection: the edge selection decision of one mobile devices
        :param channel_power_gains: the channel power gains of one mobile devices to every connectable servers
        :return: the time consumption on transmission and edge execution
        """
        parameter = self.get_parameter()
        transmit_times = ToolFunction.obtain_transmit_times(
            division, edge_selection, parameter, channel_power_gains)
        edge_exe_times = ToolFunction.obtain_edge_exe_times(
            division, parameter)
        edge_times = transmit_times + edge_exe_times
        time_consumption = max(edge_times) + parameter.get_local_exe_time(
        ) + parameter.get_coordinate_cost() * division
        return time_consumption
示例#9
0
    def racos(self, optimization_func):
        """
        The optimization algorithm, RACOS.

        :return: no return
        """
        self.clear()
        self.generate_solutions(optimization_func)
        # if sequential is not used
        if not self.__online:
            # begin iteration
            for it in range(self.__iterations_num):
                print('RACOS iteration #', it)
                self.__next_population = []
                for i in range(self.__sample_size):
                    while True:
                        self.reset()
                        chosen_pos_idx = ToolFunction.sample_uniform_integer(
                            0, self.__pos_num - 1)
                        eps = ToolFunction.sample_uniform_double(0, 1)
                        if eps <= self.__rand_prob:
                            non_chosen_dim = self.shrink(
                                self.__pos_population[chosen_pos_idx])
                            for j in range(len(non_chosen_dim)):
                                self.__labels[non_chosen_dim[j]] = False
                        ins = self.generate_from_pos_ins(
                            self.__pos_population[chosen_pos_idx])
                        if (not Racos.is_ins_in_list(ins, self.__pos_population, self.__pos_num)) \
                                and (not Racos.is_ins_in_list(ins, self.__next_population, i)):
                            ins.set_fitness(optimization_func(ins))
                            break
                    self.__next_population.append(ins)
                self.__population = []
                for i in range(self.__sample_size):
                    self.__population.append(self.__next_population[i])
                self.update_pos_population()
                self.update_optimal()
                # print the best-so-far fitness for test
                print('Best-so-far fitness:',
                      self.get_optimal_solution().get_fitness())
        else:
            # sequential mode is not interested in this paper
            pass
示例#10
0
    def obtain_edge_selections(self):
        """
        Obtain the feasible solution with greedy policy on computation.

        :return: edge_selections, every row denotes a mobile device who has task request
        """
        parameter = self.get_parameter()
        # first initialize with zero
        edge_selections = []
        for i in range(parameter.get_user_num()):
            edge_selection = np.repeat(
                0, len(parameter.get_connectable_servers()[i]))
            edge_selections.append(edge_selection)

        # for every edge site, generate a random integer with [0, max_assign], and distribute connections to
        # connectable mobile devices
        for j in range(parameter.get_server_num()):
            assign_num = parameter.get_max_assign()
            connectable_user_num = len(parameter.get_connectable_users()[j])
            if assign_num >= connectable_user_num:
                # every mobile device in it can be chosen
                for i in range(connectable_user_num):
                    user_index = parameter.get_connectable_users()[j][i]
                    edge_index = list.index(
                        parameter.get_connectable_servers()[user_index], j)
                    edge_selections[user_index][edge_index] = 1
            else:
                # randomly choose assign_num users to distribute j's computation capacity
                user_indices = random.sample(
                    parameter.get_connectable_users()[j], assign_num)
                for i in range(len(user_indices)):
                    user_index = user_indices[i]
                    edge_index = list.index(
                        parameter.get_connectable_servers()[user_index], j)
                    edge_selections[user_index][edge_index] = 1

        # set those mobile devices who do not have task request to [0, 0, ..., 0]
        # we can not delete them from the list because every row is the index of the corresponding mobile device
        for i in range(parameter.get_user_num()):
            if parameter.get_task_requests()[i] == 0:
                edge_selections[i] = np.zeros(len(edge_selections[i]))
            else:
                division = int(sum(edge_selections[i]))
                if division:
                    times = self.obtain_time_consumption(
                        division, edge_selections[i],
                        parameter.get_connectable_gains()[i])
                    energys = ToolFunction.obtain_transmit_energy(
                        division, edge_selections[i], parameter,
                        parameter.get_connectable_gains()[i])
                    # satisfy the constraint
                    if times >= parameter.get_ddl(
                    ) or energys > self.get_battery_energy_levels()[i]:
                        edge_selections[i] = np.zeros(len(edge_selections[i]))
        return edge_selections
    def obtain_harvested_energy(self, parameter):
        """
        Obtain the optimal harvested energy by solving the 'optimal energy harvesting' sub-problem
        according to \eqref{18}.

        :param parameter: the instance of class Parameter
        :return: the optimal harvested energy
        """
        if self.get_virtual_energy_levels() <= 0:
            return ToolFunction.generate_harvestable_energy(parameter)
        else:
            return 0
示例#12
0
    def obtain_overall_costs(self, parameter):
        """
        Calculate the overall costs, which is the sum of cost of each mobile device.

        :param parameter: the instance of class Parameter
        :return: overall costs
        """
        overall_costs = 0
        for i in range(self.__user_num):
            transmit_times = ToolFunction.obtain_transmit_times(
                self.edge_selections[i], parameter,
                self.__connectable_distances[i])
            edge_exe_times = ToolFunction.obtain_edge_exe_times(
                self.edge_selections[i], parameter)
            edge_times = transmit_times + edge_exe_times
            division = sum(self.edge_selections[i])
            is_dropped = False if division else True
            overall_cost = max(edge_times) + parameter.get_local_exe_time() + parameter.get_coordinate_cost() * \
                           sum(self.edge_selections[i]) + is_dropped * parameter.get_drop_penalty()
            overall_costs += overall_cost
        return overall_costs
示例#13
0
    def generate_random_ins(self):
        """
        Generate a random instance for those dimensions whose labels are True.

        :return: the generated instance
        """
        instance = Instance(self.__dimension)
        for i in range(self.__dimension.get_dim_size()):
            instance.set_feature(
                i,
                ToolFunction.sample_uniform_integer(self.__regions[i][0],
                                                    self.__regions[i][1]))
        return instance
示例#14
0
    def update_energy_levels(self, parameter):
        """
        Update the battery & virtual energy level according to the involution expression \eqref{10}.

        :param parameter: the instance of class Parameter
        :return: no return
        """
        for i in range(self.__user_num):
            self.__battery_energy_levels[i] = self.__battery_energy_levels[i] - ToolFunction.obtain_transmit_energys(
                self.edge_selections[i], parameter, self.__connectable_distances[i]) - \
                                              parameter.get_local_exe_energy() + self.obtain_harvested_energy(parameter)
            self.__virtual_energy_levels[i] = self.__battery_energy_levels[
                i] - parameter.get_perturbation_para()
示例#15
0
    def update_labels(self):
        """
        Update self.__labels according to the number of uncertain bits.

        :return: no return
        """
        dims = [n for n in range(self.__dimension.get_dim_size())]
        for i in range(self.__uncertain_bits_num):
            index = ToolFunction.sample_uniform_integer(
                0,
                self.__dimension.get_dim_size() - i - 1)
            self.__labels[dims[index]] = False
            dims.remove(dims[index])
    def obtain_wireless_signal_coverage(self):
        """
        According to the position of mobile devices and edge sites, judge whether mobile devices are covered by edge
        sites' wireless signal.

        :return: lists stored indices of connectable edge sites, power gains, and mobile devices, respectively, and
        distances from mobile devices to connectable edge sites
        """
        connectable_servers, connectable_distances, connectable_gains = [], [], []
        global_distances = [
        ]  # a 'N * M' matrix stored distances between each user and each edge site
        for i in range(len(self.__user_info)):
            tmp_s, tmp_d, tmp_g = [], [], []
            tmp_gains = []
            for j in range(len(self.__edge_info)):
                distance = ToolFunction.obtain_geo_distance(
                    self.__user_info[i], self.__edge_info[j])
                if distance <= self.__edge_info[j][2]:
                    # under signal coverage
                    tmp_s.append(j)
                    tmp_d.append(distance)
                    tmp_gains.append(
                        ToolFunction.obtain_channel_power_gain(
                            self.__path_loss_const, distance))
                tmp_g.append(distance)
            connectable_servers.append(tmp_s)
            connectable_distances.append(tmp_d)
            connectable_gains.append(tmp_gains)
            global_distances.append(tmp_g)

        connectable_users = []
        for j in range(len(self.__edge_info)):
            tmp_u = []
            for i in range(len(self.__user_info)):
                if global_distances[i][j] <= self.__edge_info[j][2]:
                    tmp_u.append(i)
            connectable_users.append(tmp_u)

        return connectable_servers, connectable_users, connectable_distances, connectable_gains, global_distances
示例#17
0
    def shrink(self, instance):
        """
        Shrink the dimensions.

        :param instance: the chosen instance
        :return: the shrunken dimensions with size only being self.__uncertain_bits_num
        """
        non_chosen_dimension = [
            n for n in range(self.__dimension.get_dim_size())
        ]
        chosen_dimension = []
        while not self.is_distinguish(instance, chosen_dimension):
            tmp = non_chosen_dimension[ToolFunction.sample_uniform_integer(
                0,
                len(non_chosen_dimension) - 1)]
            chosen_dimension.append(tmp)
            non_chosen_dimension.remove(tmp)
        while len(non_chosen_dimension) > self.__uncertain_bits_num:
            tmp = non_chosen_dimension[ToolFunction.sample_uniform_integer(
                0,
                len(non_chosen_dimension) - 1)]
            chosen_dimension.append(tmp)
            non_chosen_dimension.remove(tmp)
        return non_chosen_dimension
    def obtain_overall_costs(self, parameter):
        """
        Calculate 'V * overall_costs + Lyapunov_drift', which is the sum of it of each mobile device.

        :param parameter: the instance of class Parameter
        :return: V * overall costs + \Delta(\Theta)
        """
        overall_costs = 0
        for i in range(len(self.edge_selections)):
            transmit_times = ToolFunction.obtain_transmit_times(
                self.edge_selections[i], parameter,
                self.__connectable_distances[i])
            edge_exe_times = ToolFunction.obtain_edge_exe_times(
                self.edge_selections[i], parameter)
            edge_times = transmit_times + edge_exe_times
            division = sum(self.edge_selections[i])
            is_dropped = False if division else True
            overall_cost = max(edge_times) + parameter.get_local_exe_time() + parameter.get_coordinate_cost() * \
                           sum(self.edge_selections[i]) + is_dropped * parameter.get_drop_penalty()
            neg_lyapunov_drift = (parameter.get_local_exe_energy() + ToolFunction.obtain_transmit_energys(
                self.edge_selections[i], parameter, self.get_connectable_distances()[i])) * \
                             self.get_virtual_energy_levels()[i]
            overall_costs += overall_cost - neg_lyapunov_drift
        return overall_costs
示例#19
0
    def obtain_perturbation_para(self):
        """
        Calculate the lower bound of perturbation parameter used in Lyapunov Optimization.

        :return: the lower bound of perturbation parameter
        """
        max_achievable_rate = ToolFunction.obtain_achieve_rate(
            self.__bandwidth, self.__max_channel_power_gain,
            self.__transmit_power, self.__noise)
        part1 = self.__v * max_achievable_rate / (self.__transmit_power *
                                                  self.__edge_input_size)
        part2 = self.__server_num * self.__drop_penalty - self.__edge_input_size / max_achievable_rate - \
                self.__edge_input_size * self.__unit_cpu_num / self.__edge_cpu_freq
        max_energy_all = self.__local_exe_energy + self.__server_num * \
                         self.__transmit_power * (self.__ddl - self.__local_exe_time)
        return part1 * part2 + max_energy_all
示例#20
0
    def generate_from_pos_ins(self, pos_instance):
        """
        Generate an instance from an exist positive instance.

        :param pos_instance: the exist positive instance
        :return: the generated instance
        """
        instance = Instance(self.__dimension)
        for i in range(self.__dimension.get_dim_size()):
            if not self.__labels[i]:
                instance.set_feature(
                    i,
                    ToolFunction.sample_uniform_integer(
                        self.__regions[i][0], self.__regions[i][1]))
            else:
                instance.set_feature(i, pos_instance.get_feature(i))
        return instance
    def sub_problem_es(self, parameter):
        """
        Calculate the optimization goal of sub-problem $\mathcal{P}_2^{es}$.

        :param parameter: the instance of class Parameter
        :return: the optimization goal of $\mathcal{P}_2^{es}$
        """
        distances = self.get_connectable_distances()
        optimization_goals = 0
        for i in range(len(self.edge_selections)):
            edge_selection = self.edge_selections[i]
            distance = distances[i]
            optimization_goal = parameter.get_v() * self.obtain_overall_costs(parameter) - \
                                parameter.get_local_exe_energy() - ToolFunction.obtain_transmit_energys(
                                edge_selection, parameter, distance)
            optimization_goals += optimization_goal
        return optimization_goals
示例#22
0
    def obtain_wireless_signal_coverage(user_info, edge_info):
        """
        According to the position of mobile devices and edge sites, judge whether mobile devices are covered by edge
        sites' wireless signal.

        :param user_info: a numpy array stored latitude and longitude of all mobile devices
        :param edge_info: a numpy array stored latitude, longitude and signal coverage radius of all edge sites
        :return: lists stored indices of connectable edge sites and mobile devices, respectively, and distances from
        mobile devices to connectable edge sites
        """
        connectable_servers, connectable_distances = [], []
        global_distances = [
        ]  # a N*M matrix stored distances between each user and each edge site
        for i in range(len(user_info)):
            tmp_s = [], tmp_d = [], tmp_g = []
            for j in range(len(edge_info)):
                distance = ToolFunction.obtain_geo_distance(
                    user_info[i], edge_info[j])
                if distance <= edge_info[j][2]:
                    # under signal coverage
                    tmp_s.append(j)
                    tmp_d.append(distance)
                tmp_g.append(distance)
            connectable_servers.append(tmp_s)
            connectable_distances.append(tmp_d)
            global_distances.append(tmp_g)

        connectable_users = []
        for j in range(len(edge_info)):
            tmp_u = []
            for i in range(len(user_info)):
                if global_distances[i][j] <= edge_info[j][2]:
                    tmp_u.append(i)
            connectable_users.append(tmp_u)

        return connectable_servers, connectable_users, connectable_distances, global_distances
    def obtain_edge_selections(self):
        """
        Obtain the feasible solution with greedy policy on communication.

        :return: edge_selections, every row denotes a mobile device who has task request
        """
        parameter = self.get_parameter()
        # deep copy the connectable servers and gains
        shadow_servers, shadow_gains, edge_selections = [], [], []
        for i in range(parameter.get_user_num()):
            tmp_s, tmp_g = [], []
            for j in range(len(parameter.get_connectable_servers()[i])):
                tmp_s.append(parameter.get_connectable_servers()[i][j])
                tmp_g.append(parameter.get_connectable_gains()[i][j])
            shadow_servers.append(tmp_s)
            shadow_gains.append(tmp_g)
            edge_selections.append(np.zeros(len(parameter.get_connectable_servers()[i])))

        best_gains, best_servers = [], []
        for i in range(parameter.get_user_num()):
            best_gain = max(shadow_gains[i])
            best_gains.append(best_gain)

            best_server_idx = shadow_gains[i].index(best_gain)
            best_servers.append(shadow_servers[i][best_server_idx])

        checked = [False] * parameter.get_user_num()
        while False in checked:
            # satisfy the maximum assignment constraint
            for j in range(parameter.get_server_num()):
                connected_users = [idx for idx, server in enumerate(best_servers) if server == j]
                if len(connected_users):
                    # at least one mobile device choose this server
                    connected_gains = [best_gains[i] for i in connected_users]

                    # only the user with the best channel power gains can be chosen
                    lucky_user = connected_users[connected_gains.index(max(connected_gains))]
                    checked[lucky_user] = True

                    # update best connectable information (remove j)
                    for i in range(parameter.get_user_num()):
                        if not checked[i]:
                            if shadow_servers[i].count(j) != 0:
                                server_idx = shadow_servers[i].index(j)
                                shadow_servers[i].pop(server_idx)
                                shadow_gains[i].pop(server_idx)
                else:
                    # this server is not chosen by any mobile device
                    continue
                # re-calculate the best server and gains for each mobile device
                for i in range(parameter.get_user_num()):
                    if len(shadow_gains[i]) != 0:
                        best_gains[i] = max(shadow_gains[i])
                        best_server_index = shadow_gains[i].index(best_gains[i])
                        best_servers[i] = shadow_servers[i][best_server_index]
                    else:
                        checked[i] = True

        # obtain edge_selections from best_servers
        for i in range(parameter.get_user_num()):
            if parameter.get_task_requests()[i] == 1:
                if best_servers[i]:
                    edge_idx = parameter.get_connectable_servers()[i].index(best_servers[i])
                    edge_selections[i][edge_idx] = 1

                # check whether the constraints can be satisfied
                division = int(sum(edge_selections[i]))
                if division:
                    times = self.obtain_time_consumption(division, edge_selections[i],
                                                         parameter.get_connectable_gains()[i])
                    energys = ToolFunction.obtain_transmit_energy(division, edge_selections[i], parameter,
                                                                  parameter.get_connectable_gains()[i])
                    # satisfy the constraint
                    if times >= parameter.get_ddl() or energys > self.get_battery_energy_levels()[i]:
                        edge_selections[i] = np.zeros(len(edge_selections[i]))
        return edge_selections
    def __init__(self,
                 time_slot_length=2e-3,
                 time_horizon=50,
                 ddl=2e-3,
                 drop_penalty=2e-3,
                 coordinate_cost=2e-5,
                 task_request_prob=0.7,
                 unit_cpu_num=737.5,
                 local_input_size=50,
                 local_cpu_freq=1.5e9,
                 switched_cap=1e-28,
                 max_transient_discharge=0.04,
                 edge_input_size=3000,
                 max_assign=3,
                 edge_cpu_freq=4.5e9,
                 bandwidth=1e6,
                 noise=1e-13,
                 transmit_power=1,
                 path_loss_const=1e-4,
                 min_distance=150,
                 max_distance=400,
                 max_channel_power_gain=1.02e-13,
                 max_harvest_energy=4.8e-4,
                 v=1e-6):
        """
        Initialization.
        Without loss of generality, we set properties of every mobile device and every edge site the same, respectively.

        :param time_slot_length: the length of time slot in second
        :param time_horizon: the length of time horizon, i.e., the number of time slots
        :param ddl: the deadline of computation task (unit: second)
        :param drop_penalty: the penalty for dropping the computation task (unit: second)
        :param coordinate_cost: the cost for coordination/collaboration of edge sites (unit: second) [--tunable--]
        :param task_request_prob: the task request probability of all users under Bernoulli process
        :param unit_cpu_num: number of CPU-cycles required to process one bit computation task (num/bit)

        :param local_input_size: the input data size of the computation task for local execution (in bits)
        :param local_cpu_freq: the CPU-cycle frequency of all mobile devices
        :param switched_cap: the effective switched capacitance of all mobile devices
        :param max_transient_discharge: the maximum allowable transient discharge of mobile devices [--danger--]

        :param edge_input_size: the input data size of the computation task for edge/remote execution (in bits)
        :param max_assign: maximum number of assignments (acceptable mobile devices) of all edge sites [--tunable--]
        :param edge_cpu_freq: the CPU-cycle frequency of all edge servers

        :param bandwidth: the bandwidth of all edge sites (unit: Hz)
        :param noise: the background noise power of edge sites (unit: W)
        :param transmit_power: the transmitting power of mobile devices (unit: W)
        :param path_loss_const: the pass-loss constant for transmission
        :param min_distance: the minimum distance from mobile device to edge site under wireless signal coverage
        :param max_distance: the maximum distance from mobile device to edge site under wireless signal coverage
        :param max_channel_power_gain: the 'empirical' maximum channel power gain under exponential distribution
        with 4e8 trials
        :param max_harvest_energy: the maximum harvestable energy at each time slot (unit: J)
        :param v: the tuning parameter of Lyapunov Optimization (unit: J^2/sec) [--tunable--]
        """
        # =============================== basic parameters ===============================
        # parameters for scenario construction
        self.__time_slot_length = time_slot_length
        self.__time_horizon = time_horizon
        self.__ddl = ddl
        self.__drop_penalty = drop_penalty
        self.__coordinate_cost = coordinate_cost
        self.__task_request_prob = task_request_prob
        self.__unit_cpu_num = unit_cpu_num

        # parameters for local execution
        self.__local_input_size = local_input_size
        self.__local_cpu_freq = local_cpu_freq
        self.__switched_cap = switched_cap
        self.__max_transient_discharge = max_transient_discharge

        # parameter for edge execution
        self.__edge_input_size = edge_input_size
        self.__max_assign = max_assign
        self.__edge_cpu_freq = edge_cpu_freq

        # parameter for communication, energy harvesting and V
        self.__bandwidth = bandwidth
        self.__noise = noise
        self.__transmit_power = transmit_power
        self.__path_loss_const = path_loss_const
        self.__min_distance = min_distance
        self.__max_distance = max_distance
        self.__max_channel_power_gain = max_channel_power_gain
        self.__max_harvest_energy = max_harvest_energy
        self.__v = v

        # =============================== position information ===============================
        # read scenario data from dataset: initial users and servers' position
        self.__user_info, self.__edge_info = self.import_scenario()
        # get number of edge servers and users
        self.__user_num = len(self.__user_info)
        self.__server_num = len(self.__edge_info)
        # user position information
        self.__connectable_servers, self.__connectable_users, self.__connectable_distances, self.__connectable_gains, \
            self.__global_distances = self.obtain_wireless_signal_coverage()
        # mobility management
        self.__latitude_drv, self.__longitude_drv = self.obtain_derivation()

        # =============================== random events ===============================
        self.__harvestable_energys = ToolFunction.generate_harvestable_energys(
            self.__max_harvest_energy, self.__user_num)
        self.__task_requests = self.generate_task_request()

        # =============================== execution & cost information ===============================
        # calculate local execution time
        self.__local_exe_time = self.__local_input_size * self.__unit_cpu_num / self.__local_cpu_freq
        # calculate local execution energy consumption
        self.__local_exe_energy = self.__switched_cap * self.__local_input_size * self.__unit_cpu_num * \
            (self.__local_cpu_freq ** 2)
        # calculate the lower bound of perturbation parameter
        self.__perturbation_para = self.obtain_perturbation_para()
        # calculate the dimension size
        self.__dim_size = self.calculate_dim_size()
    def generate_from_pos_ins(self, pos_instance):
        """
        Generate an instance from an exist positive instance.

        :param pos_instance: the exist positive instance
        :return: a feasible instance
        """
        parameter = self.get_parameter()
        assignable_nums = np.repeat(parameter.get_max_assign(),
                                    parameter.get_server_num())
        edge_selections = self.greedy_sample(assignable_nums)
        ins_features = []
        for i in range(parameter.get_user_num()):
            if parameter.get_task_requests()[i] == 1:
                division = int(sum(edge_selections[i]))
                if division:
                    times = self.obtain_time_consumption(
                        division, edge_selections[i],
                        parameter.get_connectable_gains()[i])
                    energys = ToolFunction.obtain_transmit_energy(
                        division, edge_selections[i], parameter,
                        parameter.get_connectable_gains()[i])
                    # satisfy the constraint
                    if times >= parameter.get_ddl(
                    ) or energys > self.__battery_energy_levels[i]:
                        edge_selections[i] = np.zeros(len(edge_selections[i]))
                else:
                    pass
                for j in range(len(edge_selections[i])):
                    ins_features.append(edge_selections[i][j])
        dimension = Dimension(parameter.get_dim_size())
        instance = Instance(dimension)
        instance.set_features(ins_features)

        # update from the chosen positive instance
        for i in range(len(instance.get_features())):
            if self.get_labels()[i]:
                instance.set_feature(i, pos_instance.get_feature(i))

        # re-check whether the maximum assignable nums is satisfied
        edge_selections = self.instance_to_edge_selections(instance)
        for j in range(parameter.get_server_num()):
            if assignable_nums[j] >= len(parameter.get_connectable_users()[j]):
                continue
            connect_users = []
            for i in range(len(parameter.get_connectable_users()[j])):
                user_idx = parameter.get_connectable_users()[j][i]
                edge_idx = list.index(
                    parameter.get_connectable_servers()[user_idx], j)
                if edge_selections[user_idx][edge_idx] == 1:
                    connect_users.append(user_idx)
            if len(connect_users) <= assignable_nums[j]:
                continue
            permitted_connect_users = random.sample(connect_users,
                                                    assignable_nums[j])
            non_permitted = [
                u for u in connect_users if u not in permitted_connect_users
            ]
            for i in range(len(non_permitted)):
                user_idx = non_permitted[i]
                edge_idx = list.index(
                    parameter.get_connectable_servers()[user_idx], j)
                edge_selections[user_idx][edge_idx] = 0

        # re-check whether the ddl and energy consumption are satisfied
        for i in range(parameter.get_user_num()):
            if parameter.get_task_requests()[i] == 1:
                division = int(sum(edge_selections[i]))
                if division:
                    # times = self.obtain_time_consumption(division, edge_selections[i],
                    #                                      parameter.get_connectable_gains()[i])
                    transmit_times = ToolFunction.obtain_transmit_times(
                        division, edge_selections[i], parameter,
                        parameter.get_connectable_gains()[i])
                    edge_exe_times = ToolFunction.obtain_edge_exe_times(
                        division, parameter)
                    edge_times = transmit_times + edge_exe_times
                    times = max(edge_times) + parameter.get_local_exe_time() + \
                        parameter.get_coordinate_cost() * division
                    energys = ToolFunction.obtain_transmit_energy(
                        division, edge_selections[i], parameter,
                        parameter.get_connectable_gains()[i])
                    # satisfy the constraint
                    if times >= parameter.get_ddl(
                    ) or energys > self.__battery_energy_levels[i]:
                        edge_selections[i] = np.zeros(len(edge_selections[i]))
                else:
                    pass

        # get final instance from the new edge_selections
        ins_features = []
        for i in range(parameter.get_user_num()):
            if parameter.get_task_requests()[i] == 1:
                for j in range(len(edge_selections[i])):
                    ins_features.append(edge_selections[i][j])
        instance.set_features(ins_features)
        return instance