def mutation(self): """Mutation operator. For each chromosome in the solution pool, decide according to the mutation rate if we'll modify it or not. If its selected for mutation, we create a mutant as follows: We select two random hosts and swap two random VNFs. If none of the selected hosts has VNFs on it, we select two other hosts and so on. If the constraints are violated, the mutant is rejected. """ counter = 0 for s in self.solution_pool: if uniform(0, 1) <= self.mutation_rate: logging.debug("Mutating solution: " + str(s)) # create a copy of the chromosome scopy = copy.deepcopy(s) # Corner-case: There's just one gene in the chromosomes, so nothing to # mutate if len(scopy.genes) < 2: continue # pick two hosts while True: h1 = choice(scopy.genes) h2 = choice(scopy.genes) if h1 == h2: continue if h1.vnfs or h2.vnfs: break # pick one VNF from each host v1 = None v2 = None if h1.vnfs: v1 = choice(h1.vnfs) h1.vnfs = [ x for x in h1.vnfs if x["vnf_name"] != v1["vnf_name"] ] if h2.vnfs: v2 = choice(h2.vnfs) h2.vnfs = [ x for x in h2.vnfs if x["vnf_name"] != v2["vnf_name"] ] # swap the two VNFs if v2: h1.vnfs.append(v2) if v1: h2.vnfs.append(v1) # create a solution represented in the full format S = helpers.from_chromosome(self.scenario, scopy) reject = False # check constraints for v in S["vnfs"]: hname = v["place_at"][0] vname = v["vnf_name"] if not helpers.check_if_placement_allowed(S, hname, vname): # There's a VNF "illegally" placed reject = True break if not reject: if helpers.check_mec_constraints(S) is False: reject = True if not reject: if helpers.check_location_constraints(S) is False: reject = True if not reject: for h in S["hosts"]: if helpers.check_host_capacity_constraint(S, h) is False: reject = True break if not reject: if helpers.check_link_capacity_constraints(S) is False: reject = True if not reject: if helpers.check_delay_constraints(S) is False: reject = True mutant = helpers.to_chromosome(S) if not reject: # all constraints ok # delete old solution and replace with mutant self.solution_pool[counter] = mutant logging.debug("Mutant ACCEPTED") else: logging.debug("Mutant REJECTED") pass counter += 1
def execute(self): """Run the genetic algorithm. Returns the scenario JSON object with placement decisions plus some algorithm-specific information. """ # Fill in missing edges helpers.add_missing_links(self.scenario) # check if we will store data per generation in a file log_generation_data = False if self.generations_file is not None: try: gen_fp = open(self.generations_file, "w+") log_generation_data = True gen_fp.write("# Scenario: " + str(self.scenario_file) + "\n") gen_fp.write("# Seed: " + str(self.seed) + "\n") gen_fp.write( "#-----------------------------------------------\n") gen_fp.write("# Generation\tFitness\t\tTimestamp\n") except: logging.warn("Error opening/writing at " + self.generations_file) pass prev_obj_value = 100000000 #inf if self.convergence_check: remaining_generations = self.stop_after start_time = datetime.now() self.init_solution_pool() for i in range(0, self.generations): obj_value = self.generation() # get a timestamp for this generation dt = datetime.now() - start_time # convert to seconds. dt.days should really not matter... time_taken = dt.days * 24 * 3600 + dt.seconds + dt.microseconds / 1000000.0 logging.info("Generation/fitness (" + self.optimize_for + ")/timestamp: " + str(i) + "\t" + str(obj_value) + "\t" + str(time_taken)) if log_generation_data: gen_fp.write( str(i) + "\t\t" + str(obj_value) + "\t" + str(time_taken) + "\n") # if we're checking for convergence to finish execution faster # we have to do some checks if self.convergence_check: if abs(obj_value - prev_obj_value) < self.delta: # the solution fitness did not significantly changed remaining_generations -= 1 else: remaining_generations = self.stop_after # the algorithm converged if remaining_generations < 0: break prev_obj_value = obj_value final_solution = helpers.from_chromosome(self.scenario, self.solution_pool[0]) # add extra information about solution performance (cost, availability, latency, time taken, # generations) # and indications about constraint violations info = self.get_solution_info(final_solution) info["generations"] = i + 1 info["execution_time"] = time_taken info["link_capacity_constraints_ok"] = True info["delay_constraints_ok"] = True info["host_capacity_constraints_ok"] = True info["mec_constraints_ok"] = True info["legal_placement"] = True # some final checks if not helpers.check_mec_constraints(final_solution): logging.warn("Final solution violates MEC constraints") info["mec_constraints_ok"] = False if not helpers.check_location_constraints(final_solution): logging.warn("Final solution violates location constraints") info["location_constraints_ok"] = False if not helpers.check_link_capacity_constraints(final_solution): logging.warn("Final solution violates link capacity constraints") info["link_capacity_constraints_ok"] = False if not helpers.check_delay_constraints(final_solution): logging.warn("Final solution violates delay constraints") info["delay_constraints_ok"] = False for h in final_solution["hosts"]: if not helpers.check_host_capacity_constraint(final_solution, h): logging.warn("Final solution violates host " + h["host_name"] + " capacity constraints") info["host_capacity_constraints_ok"] = False for v in final_solution["vnfs"]: if not helpers.check_if_placement_allowed( final_solution, v["place_at"][0], v["vnf_name"]): logging.warn("Final solution includes illegal placement of " + v["place_at"][0] + " at " + v["vnf_name"]) info["legal_placement"] = False final_solution["solution_performance"] = info used_hosts = helpers.get_used_hosts(final_solution) logging.info("Used hosts:") for uh in used_hosts: logging.info(uh) used_hedges = helpers.get_used_host_links(final_solution) logging.info("Used host edges:") for ue in used_hedges: logging.info(ue["source"] + " -> " + ue["target"] + " (" + str(ue["delay"]) + ")") # Add host edge mapping info to VNF edges helpers.add_vnf_edge_mapping(final_solution) return final_solution
def init_solution_pool(self): """Initialize solution pool. Generate S feasible solutions/placements as follows: For each VNF, select a random host. If it has enough capacity, place VNF there. Otherwise look for another host. """ self.solution_pool = [] solutions_to_generate = self.solution_pool_size while solutions_to_generate > 0: # TODO: Remove the deepcopy, just clear "place_at" fields solution = copy.deepcopy(self.scenario) reset = False for v in solution['vnfs']: if reset is True: # Solution infeasible. Try another one... logging.debug("Infeasible solution. Resetting") break vnf_placed = False while not vnf_placed: if not helpers.check_if_there_is_space(solution, v): # reset solution and start from scratch reset = True break h = choice(solution["hosts"]) v["place_at"] = [h["host_name"]] # First check if it is allowed to place v at h # If not, try another one if not helpers.check_if_placement_allowed( solution, h["host_name"], v["vnf_name"]): logging.debug( "init_solution_pool: Not allowed, trying another host" ) v["place_at"] = None continue # check if h has the available resources to host v # If capacity will be exceeded, try another host if helpers.check_host_capacity_constraint(solution, h): vnf_placed = True logging.debug("init_solution_pool: VNF " + v["vnf_name"] + " placed at " + h["host_name"]) else: v["place_at"] = None # check if the solution violates any MEC constraints mec_constraints_ok = helpers.check_mec_constraints(solution) if not mec_constraints_ok: logging.debug("init_solution_pool: Mec constraint violated") continue # check if we're violating location constraints location_constraints_ok = helpers.check_location_constraints( solution) if not location_constraints_ok: logging.debug( "init_solution_pool: Location constraint violated") continue # Check if the solution violates any link capacities link_constraints_ok = helpers.check_link_capacity_constraints( solution) if not link_constraints_ok: logging.debug( "init_solution_pool: Link capacity constraint violated") continue delay_constraints_ok = helpers.check_delay_constraints(solution) if not delay_constraints_ok: logging.debug("init_solution_pool: Delay constraint violated") continue #reachability_ok = helpers.check_reachability(solution) #if not reachability_ok: # continue if reset is False: logging.debug(helpers.show_host_link_status(solution)) logging.debug( "Solution cost: " + str(helpers.get_solution_cost(solution)) + ", availability: " + str(helpers.get_solution_availability(solution)) + ", latency: " + str(helpers.get_solution_global_latency(solution))) # Store the simplified "chromosome" representation of the solution C = helpers.to_chromosome(solution) self.solution_pool.append(C) solutions_to_generate -= 1