def zoom(alpha_low, alpha_high, max_iter=100): while True: _alpha = (alpha_high.get("alpha") + alpha_low.get("alpha")) / 2 _point = Individual(X=_alpha) evaluator.eval(problem, _point, evaluate_values_of=["F", "CV"]) if _point.F[ 0] > sol_F + self.c1 * _alpha * sol_dF @ direction or _point.F[ 0] > alpha_low.F[0]: alpha_high = _point else: evaluator.eval(problem, _point, evaluate_values_of=["dF"]) point_dF = _point.get("dF")[0] if np.abs(point_dF @ direction) <= -self.c2 * sol_dF @ direction: return _point if (point_dF @ direction) * (alpha_high.get("alpha") - alpha_low.get("alpha")) >= 0: alpha_high = alpha_low alpha_low = _point
class LineSearch(Algorithm): def __init__(self, **kwargs): super().__init__(**kwargs) self.point, self.direction = None, None def setup(self, problem, point=None, direction=None, **kwargs): super().setup(problem, **kwargs) msg = "Only problems with one objective and no constraints can be solved using a line search!" assert not problem.has_constraints() and problem.n_obj == 1, msg assert point is not None, "You have to define a starting point for the algorithm" self.point = point assert direction is not None, "You have to define a direction point for the algorithm" self.direction = direction return self def _initialize_infill(self): # x could be a vector or an individual if isinstance(self.point, np.ndarray): self.point = Individual(X=self.point) # make sure it is evaluated - if not yet also get the gradient if self.point.get("F") is None: self.evaluator.eval(self.problem, self.point, algorithm=self) self.infill = self.point
def _advance(self, **kwargs): repair, crossover, mutation = self.repair, self.mating.crossover, self.mating.mutation pc_pop = self.pc_pop.copy(deep=True) npc_pop = self.npc_pop.copy(deep=True) ############################################################## # PC evolving ############################################################## # Normalise both poulations according to the PC individuals pc_pop, npc_pop = normalize_bothpop(pc_pop, npc_pop) PCObj = pc_pop.get("F") NPCObj = npc_pop.get("F") pc_size = PCObj.shape[0] npc_size = NPCObj.shape[0] ###################################################### # Calculate the Euclidean distance among individuals ###################################################### d = np.zeros((pc_size, pc_size)) d = cdist(PCObj, PCObj, 'euclidean') d[d == 0] = np.inf # Determine the size of the niche if pc_size == 1: radius = 0 else: radius = determine_radius(d, pc_size, self.pc_capacity) # calculate the radius for individual exploration r = pc_size / self.pc_capacity * radius ######################################################## # find the promising individuals in PC for exploration ######################################################## # promising_num: record how many promising individuals in PC promising_num = 0 # count: record how many NPC individuals are located in each PC individual's niche count = np.array([]) d2 = np.zeros((pc_size, npc_size)) d2 = cdist(PCObj, NPCObj, 'euclidean') # Count of True elements in each row (each individual in PC) of 2D Numpy Array count = np.count_nonzero(d2 <= r, axis=1) # Check if the niche has no NPC individual or has only one NPC individual # Record the indices of promising individuals. # Since np.nonzero() returns a tuple of arrays, we change the type of promising_index to a numpy.ndarray. promising_index = np.nonzero(count <= 1) promising_index = np.asarray(promising_index).flatten() # Record total number of promising individuals in PC for exploration promising_num = len(promising_index) ######################################## # explore these promising individuals ######################################## original_size = pc_size off = Individual() if promising_num > 0: for i in range(promising_num): if original_size > 1: parents = Population.new(2) # The explored individual is considered as one parent parents[0] = pc_pop[promising_index[i]] # The remaining parent will be selected randomly from the PC population rnd = np.random.permutation(pc_size) for j in rnd: if j != promising_index[i]: parents[1] = pc_pop[j] break index = np.array([0, 1]) parents_shape = index[None, :] # do recombination and create an offspring off = crossover.do(self.problem, parents, parents_shape)[0] else: off = pc_pop[0] # mutation off = Population.create(off) off = mutation.do(self.problem, off) # evaluate the offspring self.evaluator.eval(self.problem, off) # update the PC population by the offspring self.pc_pop = update_PCpop(self.pc_pop, off) # update the ideal point self.ideal = np.min(np.vstack([self.ideal, off.get("F")]), axis=0) # update at most one solution in NPC population self.npc_pop = update_NPCpop(self.npc_pop, off, self.ideal, self.ref_dirs, self.decomp) ######################################################## # NPC evolution based on MOEA/D ######################################################## # iterate for each member of the population in random order for i in np.random.permutation(len(self.npc_pop)): # get the parents using the neighborhood selection P = self.selection.do(self.npc_pop, 1, self.mating.crossover.n_parents, k=[i]) # perform a mating using the default operators (recombination & mutation) - if more than one offspring just pick the first off = self.mating.do(self.problem, self.npc_pop, 1, parents=P)[0] off = Population.create(off) # evaluate the offspring self.evaluator.eval(self.problem, off, algorithm=self) # update the PC population by the offspring self.pc_pop = update_PCpop(self.pc_pop, off) # update the ideal point self.ideal = np.min(np.vstack([self.ideal, off.get("F")]), axis=0) # now actually do the replacement of the individual is better self.npc_pop = self._replace(i, off) ######################################################## # population maintenance operation in the PC evolution ######################################################## current_pop = Population.merge(self.pc_pop, self.npc_pop) current_pop = Population.merge(current_pop, self.pop) # filter duplicate in the population pc_pop = self.eliminate_duplicates.do(current_pop) pc_size = len(pc_pop) if (pc_size > self.pc_capacity): # get the objective space values and objects pc_objs = pc_pop.get("F") fronts, rank = NonDominatedSorting().do(pc_objs, return_rank=True) front_0_index = fronts[0] # put the nondominated individuals of the NPC population into the PC population self.pc_pop = pc_pop[front_0_index] if len(self.pc_pop) > self.pc_capacity: self.pc_pop = maintain_PCpop(self.pc_pop, self.pc_capacity) self.pop = self.pc_pop.copy(deep=True)
def _infill(self): problem, evaluator = self.problem, self.evaluator evaluator.skip_already_evaluated = False sol, direction = self.problem.point, self.problem.direction # the function value and gradient of the initial solution sol.set("alpha", 0.0) sol_F, sol_dF = sol.F[0], sol.get("dF")[0] def zoom(alpha_low, alpha_high, max_iter=100): while True: _alpha = (alpha_high.get("alpha") + alpha_low.get("alpha")) / 2 _point = Individual(X=_alpha) evaluator.eval(problem, _point, evaluate_values_of=["F", "CV"]) if _point.F[ 0] > sol_F + self.c1 * _alpha * sol_dF @ direction or _point.F[ 0] > alpha_low.F[0]: alpha_high = _point else: evaluator.eval(problem, _point, evaluate_values_of=["dF"]) point_dF = _point.get("dF")[0] if np.abs(point_dF @ direction) <= -self.c2 * sol_dF @ direction: return _point if (point_dF @ direction) * (alpha_high.get("alpha") - alpha_low.get("alpha")) >= 0: alpha_high = alpha_low alpha_low = _point last = sol alpha = 1.0 current = Individual(X=alpha) for i in range(1, self.max_iter + 1): # evaluate the solutions evaluator.eval(problem, current, evaluate_values_of=["F", "CV"]) # get the values from the solution to be used to evaluate the conditions F, dF, _F = last.F[0], last.get("dF")[0], current.F[0] # if the wolfe condition is violate we have found our upper bound if _F > sol_F + self.c1 * sol_dF @ direction or (i > 1 and F >= _F): return zoom(last, current) # for the other condition we need the gradient information evaluator.eval(problem, current, evaluate_values_of=["dF"]) _dF = current.get("dF")[0] if np.abs(_dF @ direction) <= -self.c2 * sol_dF @ direction: return current if _dF @ direction >= 0: return zoom(current, last) alpha = 2 * alpha last = current current = Individual(X=alpha) return current
def wolfe_line_search(problem, sol, direction, c1=1e-4, c2=0.9, max_iter=10, evaluator=None): # initialize the evaluator to be used (this will make sure evaluations are counted) evaluator = evaluator if evaluator is not None else Evaluator() evaluator.skip_already_evaluated = False # the function value and gradient of the initial solution sol.set("alpha", 0.0) sol_F, sol_dF = sol.F[0], sol.get("dF")[0] def zoom(alpha_low, alpha_high, max_iter=100): while True: _alpha = (alpha_high.get("alpha") + alpha_low.get("alpha")) / 2 _point = Individual(X=sol.X + _alpha * direction, alpha=_alpha) evaluator.eval(problem, _point, evaluate_values_of=["F", "CV"]) if _point.F[ 0] > sol_F + c1 * _alpha * sol_dF @ direction or _point.F[ 0] > alpha_low.F[0]: alpha_high = _point else: evaluator.eval(problem, _point, evaluate_values_of=["dF"]) point_dF = _point.get("dF")[0] if np.abs(point_dF @ direction) <= -c2 * sol_dF @ direction: return _point if (point_dF @ direction) * (alpha_high.get("alpha") - alpha_low.get("alpha")) >= 0: alpha_high = alpha_low alpha_low = _point last = sol alpha = 1.0 current = Individual(X=sol.X + alpha * direction, alpha=alpha) for i in range(1, max_iter + 1): # evaluate the solutions evaluator.eval(problem, current, evaluate_values_of=["F", "CV"]) # get the values from the solution to be used to evaluate the conditions F, dF, _F = last.F[0], last.get("dF")[0], current.F[0] # if the wolfe condition is violate we have found our upper bound if _F > sol_F + c1 * sol_dF @ direction or (i > 1 and F >= _F): return zoom(last, current) # for the other condition we need the gradient information evaluator.eval(problem, current, evaluate_values_of=["dF"]) _dF = current.get("dF")[0] if np.abs(_dF @ direction) <= -c2 * sol_dF @ direction: return current if _dF @ direction >= 0: return zoom(current, last) alpha = 2 * alpha last = current current = Individual(X=sol.X + alpha * direction, alpha=alpha) return current