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)
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)
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
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
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
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
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
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()
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
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
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
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
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