def _advance(self, **kwargs): # all the elements in the interval a, c, d, b = self.pop # the golden ratio (precomputed constant) R = self.R # if the left solution is better than the right if c.F[0] < d.F[0]: # make the right to be the new right bound and the left becomes the right a, b = a, d d = c # create a new left individual and evaluate c = Individual(X=b.X - R * (b.X - a.X)) self.evaluator.eval(self.problem, c, algorithm=self) self.infills = c # if the right solution is better than the left else: # make the left to be the new left bound and the right becomes the left a, b = c, b c = d # create a new right individual and evaluate d = Individual(X=a.X + R * (b.X - a.X)) self.evaluator.eval(self.problem, d, algorithm=self) self.infills = d # update the population with all the four individuals self.pop = Population.create(a, c, d, b)
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
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
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 _initialize_infill(self): super()._initialize_infill() a, b = self.a, self.b # the golden ratio (precomputed constant) R = self.R # create the left and right in the interval itself c, d = Individual(X=b.X - R * (b.X - a.X)), Individual(X=a.X + R * (b.X - a.X)) # create a population with all four individuals pop = Population.create(a, c, d, b) self.pop, self.infills = pop, pop return pop
def _pattern_move(self, _current, _direction): # calculate the new X and repair out of bounds if necessary X = _current.X + self.step_size * _direction set_to_bounds_if_outside_by_problem(self.problem, X) # create the new center individual and evaluate it trial = Individual(X=X) self.evaluator.eval(self.problem, trial, algorithm=self) return trial
def _initialize_infill(self): super()._initialize_infill() a, b = self.a, self.b # set c to be directly in the middle between the two brackets c = Individual(X=(b.X - a.X) / 2) # create a population with all three individuals pop = Population.create(a, b, c) return pop
def _infill(self): # the offspring population to finally evaluate and attach to the population infills = Population() # find the potential optimal solution in the current population potential_optimal = self._potential_optimal() # for each of those solutions execute the division move for current in potential_optimal: # find the largest dimension the solution has not been evaluated yet nxl, nxu = norm_bounds(current, self.problem) k = np.argmax(nxu - nxl) # the delta value to be used to get left and right - this is one sixth of the range xl, xu = current.get("xl"), current.get("xu") delta = (xu[k] - xl[k]) / 6 # create the left individual left_x = np.copy(current.X) left_x[k] = xl[k] + delta left = Individual(X=left_x) # create the right individual right_x = np.copy(current.X) right_x[k] = xu[k] - delta right = Individual(X=right_x) # update the boundaries for all the points accordingly for ind in [current, left, right]: update_bounds(ind, xl, xu, k, delta) # create the offspring population, evaluate and attach to current population _infill = Population.create(left, right) _infill.set("depth", current.get("depth") + 1) infills = Population.merge(infills, _infill) return infills
def _backward(self, X, inplace=False, **kwargs): assert isinstance(X, np.ndarray) # if it should be 2d we convert it to be a 1-d array if X.ndim == 2 and len(X) == 1: vals = X[0] assert vals.ndim == 1 if inplace: self.obj.set(self.attr, vals) return self.obj else: return Individual(**{self.attr: vals})
def _initialize_infill(self): # the boundaries of the problem for initialization xl, xu = self.problem.bounds() # take care of the lower bound - make sure it is an individual and make sure it is evaluated if self.a is None: assert xl is not None, "Either provide a left bound or set the xl attribute in the problem." self.a = Individual(X=xl) if self.a.F is None: self.evaluator.eval(self.problem, self.a, algorithm=self) # take care of the upper bound - make sure it is an individual and make sure it is evaluated if self.b is None: assert xl is not None, "Either provide a right bound or set the xu attribute in the problem." self.b = Individual(X=xu) if self.b.F is None: self.evaluator.eval(self.problem, self.b, algorithm=self) assert self.a.X[0] <= self.b.X[ 0], "The left bound must be smaller than the left bound!"
def step(self): alpha, max_alpha = self.alpha, self.problem.xu[0] if alpha > max_alpha: alpha = max_alpha infill = Individual(X=np.array([alpha])) self.evaluator.eval(self.problem, infill) self.pop = Population.merge(self.pop, infill)[-10:] if is_better(self.point, infill, eps=0.0) or alpha == max_alpha: self.termination.force_termination = True return self.point = infill self.alpha *= 2
def step(problem, x, delta, k): # copy and add delta to the new point X = np.copy(x) # if the problem has bounds normalize the delta # if problem.has_bounds(): # xl, xu = problem.bounds() # delta *= (xu[k] - xl[k]) # now add to the current solution X[k] = X[k] + delta[k] # repair if out of bounds if necessary X = set_to_bounds_if_outside_by_problem(problem, X) return Individual(X=X)
def _setup(self, problem, **kwargs): xl, xu = problem.bounds() X = denormalize(0.5 * np.ones(problem.n_var), xl, xu) x0 = Individual(X=X) x0.set("xl", xl) x0.set("xu", xu) x0.set("depth", 0) self.x0 = x0
def _advance(self, **kwargs): # all the elements in the interval a, b, c = self.pop # if this is the case then the function is not convex (which means U shaped) if c.F[0] >= a.F[0] or c.F[0] >= b.F[0]: # choose the left side if a smaller than b, or the right side otherwise if a.F[0] <= b.F[0]: a = c else: b = c c = Individual(X=(b.X - a.X) / 2) self.evaluator.eval(self.problem, c, algorithm=self) self.infills = c else: d = quadr_interp(a, b, c) self.evaluator.eval(self.problem, d, algorithm=self) self.infills = d # swap c and d -> make sure d is always on the right of c -> a, c, d, b if c.X[0] > d.X[0]: c, d = d, c # if c is better than d, then d becomes the new right bound if c.F[0] <= d.F[0]: b = d # if d is better than c, then c becomes the new left bound and d the new right bound else: a, c = c, d self.pop = Population.create(a, b, c)
def __new__(cls, n_individuals=0): obj = super(Population, cls).__new__(cls, n_individuals, dtype=cls).view(cls) for i in range(n_individuals): obj[i] = Individual() return obj
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
def _local_advance(self, **kwargs): # number of variables increased by one - matches equations in the paper xl, xu = self.problem.bounds() pop, n = self.pop, self.problem.n_var - 1 # calculate the centroid centroid = pop[:n + 1].get("X").mean(axis=0) # ------------------------------------------------------------------------------------------- # REFLECT # ------------------------------------------------------------------------------------------- # check the maximum alpha until the bounds are hit alphaU = max_alpha(centroid, (centroid - pop[n + 1].X), xl, xu) # reflect the point, consider factor if bounds are there, make sure in bounds (floating point) evaluate x_reflect = centroid + min(self.alpha, alphaU) * (centroid - pop[n + 1].X) x_reflect = set_to_bounds_if_outside_by_problem(self.problem, x_reflect) reflect = self.evaluator.eval(self.problem, Individual(X=x_reflect), algorithm=self) # whether a shrink is necessary or not - decided during this step shrink = False better_than_current_best = is_better(reflect, pop[0]) better_than_second_worst = is_better(reflect, pop[n]) better_than_worst = is_better(reflect, pop[n + 1]) # if better than the current best - check for expansion if better_than_current_best: # ------------------------------------------------------------------------------------------- # EXPAND # ------------------------------------------------------------------------------------------- # the maximum expansion until the bounds are hit betaU = max_alpha(centroid, (x_reflect - centroid), xl, xu) # expand using the factor, consider bounds, make sure in case of floating point issues x_expand = centroid + min(self.beta, betaU) * (x_reflect - centroid) x_expand = set_to_bounds_if_outside_by_problem(self.problem, x_expand) # if the expansion is almost equal to reflection (if boundaries were hit) - no evaluation if np.allclose(x_expand, x_reflect, atol=1e-16): expand = reflect else: expand = self.evaluator.eval(self.problem, Individual(X=x_expand), algorithm=self) # if the expansion further improved take it - otherwise use expansion if is_better(expand, reflect): pop[n + 1] = expand else: pop[n + 1] = reflect # if the new point is not better than the best, but better than second worst - just keep it elif not better_than_current_best and better_than_second_worst: pop[n + 1] = reflect # if not worse than the worst - outside contraction elif not better_than_second_worst and better_than_worst: # ------------------------------------------------------------------------------------------- # Outside Contraction # ------------------------------------------------------------------------------------------- x_contract_outside = centroid + self.gamma * (x_reflect - centroid) contract_outside = self.evaluator.eval(self.problem, Individual(X=x_contract_outside), algorithm=self) if is_better(contract_outside, reflect): pop[n + 1] = contract_outside else: shrink = True # if the reflection was worse than the worst - inside contraction else: # ------------------------------------------------------------------------------------------- # Inside Contraction # ------------------------------------------------------------------------------------------- x_contract_inside = centroid - self.gamma * (x_reflect - centroid) contract_inside = self.evaluator.eval(self.problem, Individual(X=x_contract_inside), algorithm=self) if is_better(contract_inside, pop[n + 1]): pop[n + 1] = contract_inside else: shrink = True # ------------------------------------------------------------------------------------------- # Shrink (only if necessary) # ------------------------------------------------------------------------------------------- if shrink: for i in range(1, len(pop)): pop[i].X = pop[0].X + self.delta * (pop[i].X - pop[0].X) pop[1:] = self.evaluator.eval(self.problem, pop[1:], algorithm=self) self.pop = FitnessSurvival().do(self.problem, pop, n_survive=len(pop))
def test_evaluate_individual(): evaluator = Evaluator() ind = evaluator.eval(problem, Individual(X=X[0])) np.testing.assert_allclose(F[0], ind.get("F")) assert evaluator.n_eval == 1
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 quadr_interp(a, b, c): return Individual(X=quadr_interp_equ(a.X, a.F, b.X, b.F, c.X, c.F))
def _advance(self, **kwargs): problem, evaluator = self.problem, self.evaluator # add the box constraints defined in the problem bounds = None if self.use_bounds: xl, xu = self.problem.bounds() if self.with_bounds: bounds = np.column_stack([xl, xu]) else: if xl is not None or xu is not None: raise Exception( f"Error: Boundary constraints can not be handled by {self.method}" ) # define the actual constraints if supported by the algorithm constr = [] if self.use_constr: constr = [LinearConstraint(np.eye(self.problem.n_var), xl, xu)] if problem.has_constraints(): if self.with_constr: def fun_constr(x): g, cv = problem.evaluate(x, return_values_of=["G", "CV"]) return cv[0] non_lin_constr = NonlinearConstraint( fun_constr, -float("inf"), 0) constr.append(non_lin_constr) else: raise Exception( f"Error: Constraint handling is not supported by {self.method}" ) # the objective function to be optimized and add gradients if available if self.estm_gradients: jac = None def fun_obj(x): f = problem.evaluate(x, return_values_of=["F"])[0] evaluator.n_eval += 1 return f else: jac = True def fun_obj(x): f, df = problem.evaluate(x, return_values_of=["F", "dF"]) if df is None: raise Exception( "If the gradient shall not be estimate, please set out['dF'] in _evaluate. " ) evaluator.n_eval += 1 return f[0], df[0] # the arguments to be used kwargs = dict(args=(), method=self.method, bounds=bounds, constraints=constr, jac=jac, options=self.options) # the starting solution found by sampling beforehand x0 = self.opt[0].X # actually run the optimization if not self.show_warnings: warnings.simplefilter("ignore") res = scipy_minimize(fun_obj, x0, **kwargs) opt = Population.create(Individual(X=res.x)) self.evaluator.eval(self.problem, opt, algorithm=self) self.pop, self.off = opt, opt self.termination.force_termination = True if hasattr("res", "nit"): self.n_gen = res.nit + 1