def do(self, problem, pop, off, return_indices=False, inplace=False, **kwargs): # this makes it usable as a traditional survival if isinstance(off, int): k = off off = pop[k:] pop = pop[:k] # if the offsprings are simply empty don't do anything if len(off) == 0: return pop assert len(pop) == len(off), "For the replacement pop and off must have the same number of individuals." pop = Population.create(pop) if isinstance(pop, Individual) else pop off = Population.create(off) if isinstance(off, Individual) else off I = self._do(problem, pop, off, **kwargs) if return_indices: return I else: if not inplace: pop = pop.copy() pop[I] = off[I] return pop
def do(self, problem, n_samples, pop=Population(), **kwargs): """ Sample new points with problem information if necessary. Parameters ---------- problem : :class:`~pymoo.core.problem.Problem` The problem to which points should be sampled. (lower and upper bounds, discrete, binary, ...) n_samples : int Number of samples pop : :class:`~pymoo.core.population.Population` The sampling results are stored in a population. The template of the population can be changed. If 'none' simply a numpy array is returned. Returns ------- X : numpy.array Samples points in a two dimensional array """ val = self._do(problem, n_samples, **kwargs) if pop is None: return val return Population.new("X", val)
def calc(self, problem, evaluator, solution, values, eps, hessian): jacobian_estm, hessian_estm = self.jacobian, self.hessian jac_approx = Population.new(X=jacobian_estm.points(solution.X, eps)) evaluator.eval(problem, jac_approx) for value in values: f, F = solution.get(value), jac_approx.get(value) dF = np.array([ jacobian_estm.calc(f[m], F[:, m], eps) for m in range(F.shape[1]) ]) solution.set("d" + value, dF) # if the hessian should be calculated as well if hessian: hess_approx = Population.new( X=self.hessian.points(solution.X, eps)) evaluator.eval(problem, hess_approx) for value in values: f, F, FF = solution.get(value), jac_approx.get( value), hess_approx.get(value) ddF = np.array([ hessian_estm.calc(f[m], F[:, m], FF[:, m], eps) for m in range(FF.shape[1]) ]) solution.set("dd" + value, ddF)
def _advance(self, infills=None, **kwargs): self.norm.update(infills) pop = Population.merge(Population.merge(self.pop, infills), self.archive) F = pop.get("F") if self.problem.has_constraints(): G = pop.get("G") C = np.maximum(0, G) C = C / np.maximum(1, C.max(axis=0)) CV = C.sum(axis=1) else: CV = np.zeros(len(pop)) F = F - self.norm.ideal(only_feas=False) + 1e-6 indicator = np.full((len(pop), len(pop)), np.inf) for i in range(len(pop)): Ci = CV[i] Fi = F[i] if Ci == 0: Ir = np.log(Fi / F) MaxIr = np.max(Ir, axis=1) MinIr = np.min(Ir, axis=1) CVA = MaxIr J = MaxIr <= 0 CVA[J] = MinIr[J] val = CVA else: IC = (Ci + 1e-6) / (CV + 1e-6) CVF = np.max(np.maximum(Fi, F) / np.minimum(Fi, F), axis=1) val = np.log(np.maximum(CVF, IC)) indicator[:, i] = val indicator[i, i] = np.inf feas = np.where(CV <= 0)[0] if len(feas) <= self.pop_size: self.archive = pop[np.argsort(CV)[:self.pop_size]] else: feas_F = pop[feas].get("F") - self.norm.ideal( only_feas=True) + 1e-6 I = Selection_Operator_of_PREA(feas_F, indicator[feas][:, feas], self.pop_size) self.archive = pop[feas[I]] I = Indicator_based_CHT(F, indicator, self.ref_dirs, self.pop_size) self.pop = pop[I] self.Ra = 1 - self.n_gen / self.termination.n_max_gen
def test_restricted_mating_selection(ref_dirs, evaluator): np.random.seed(200) selection = RestrictedMating(func_comp=comp_by_cv_dom_then_random) problem = C3DTLZ4(n_var=12, n_obj=3) ca_x = np.loadtxt(path_to_test_resource('ctaea', 'c3dtlz4', 'case2', 'preCA.x')) CA = Population.create(ca_x) evaluator.eval(problem, CA) da_x = np.loadtxt(path_to_test_resource('ctaea', 'c3dtlz4', 'case2', 'preDA.x')) DA = Population.create(da_x) evaluator.eval(problem, DA) Hm = Population.merge(CA, DA) n_pop = len(CA) _, rank = NonDominatedSorting().do(Hm.get('F'), return_rank=True) Pc = (rank[:n_pop] == 0).sum() / len(Hm) Pd = (rank[n_pop:] == 0).sum() / len(Hm) P = selection.do(Hm, len(CA)) assert P.shape == (91, 2) if Pc > Pd: assert (P[:, 0] < n_pop).all() else: assert (P[:, 0] >= n_pop).all() assert (P[:, 1] >= n_pop).any() assert (P[:, 1] < n_pop).any()
def test_association(ref_dirs, evaluator): problem = C1DTLZ3(n_var=12, n_obj=3) ca_x = np.loadtxt(path_to_test_resource('ctaea', 'c1dtlz3', 'case3', 'preCA.x')) CA = Population.create(ca_x) evaluator.eval(problem, CA) da_x = np.loadtxt(path_to_test_resource('ctaea', 'c1dtlz3', 'case3', 'preDA.x')) DA = Population.create(da_x) evaluator.eval(problem, DA) off_x = np.loadtxt(path_to_test_resource('ctaea', 'c1dtlz3', 'case3', 'offspring.x')) off = Population.create(off_x) evaluator.eval(problem, off) true_assoc = np.loadtxt(path_to_test_resource('ctaea', 'c1dtlz3', 'case3', 'feasible_rank0.txt')) true_niche = true_assoc[:, 1] true_id = true_assoc[:, 0] sorted_id = np.argsort(true_id) survival = CADASurvival(ref_dirs) mixed = Population.merge(CA, off) survival.ideal_point = np.min(np.vstack((DA.get("F"), mixed.get("F"))), axis=0) fronts = NonDominatedSorting().do(mixed.get("F"), n_stop_if_ranked=len(ref_dirs)) I = np.concatenate(fronts) niche, _ = survival._associate(mixed[I]) sorted_I = np.argsort(I) assert (niche[sorted_I] == true_niche[sorted_id]).all()
def _next(self): sol = self.opt[0] x = sol.get("X") jac, hess = self.gradient_and_hessian(sol) method = "cholesky" func = direction_cholesky if method == "cholesky" else direction_inv direction, decrement, adapted, success = func(jac, hess) # in case we can not calculate the newton direction fall back to the gradient approach if not success: direction = -jac decrement = np.linalg.norm(direction) ** 2 if self.damped: line = LineSearchProblem(self.problem, sol, direction) _next = WolfeSearch().setup(line, evaluator=self.evaluator).run() # _next = wolfe_line_search(self.problem, sol, direction, evaluator=self.evaluator) # if the newton step was not successful, then try the gradient if adapted and (sol.F[0] - _next.F[0]) < 1e-16: _next = inexact_line_search(self.problem, sol, -jac, evaluator=self.evaluator) else: _x = x + direction _next = Solution(X=_x) if isinstance(_next, Individual): _next = Population.create(_next) self.pop = Population.merge(self.opt, _next) if decrement / 2 <= self.eps: self.termination.force_termination = True
def _initialize_infill(self): self.step_size = self.alpha if self.point is None: return Population.new(X=np.copy(self.problem.xl[None, :])) else: return Population.create(self.point)
def do(self, problem, pop, *args, n_survive=None, return_indices=False, **kwargs): # make sure the population has at least one individual if len(pop) == 0: return pop if n_survive is None: n_survive = len(pop) n_survive = min(n_survive, len(pop)) # if the split should be done beforehand if self.filter_infeasible and problem.n_constr > 0: # split feasible and infeasible solutions feas, infeas = split_by_feasibility(pop, eps=0.0, sort_infeasbible_by_cv=True) if len(feas) == 0: survivors = Population() else: survivors = self._do(problem, pop[feas], *args, n_survive=min(len(feas), n_survive), **kwargs) # calculate how many individuals are still remaining to be filled up with infeasible ones n_remaining = n_survive - len(survivors) # if infeasible solutions needs to be added if n_remaining > 0: survivors = Population.merge(survivors, pop[infeas[:n_remaining]]) else: survivors = self._do(problem, pop, *args, n_survive=n_survive, **kwargs) if return_indices: H = {} for k, ind in enumerate(pop): H[ind] = k return [H[survivor] for survivor in survivors] else: return survivors
def _initialize_advance(self, infills=None, **kwargs): super()._initialize_advance(infills, **kwargs) self.pop, self.da = self.survival.do(self.problem, self.pop, Population(), n_survive=len(self.pop), algorithm=self)
def _infill(self): pop, mu, _lambda = self.pop, self.pop_size, self.n_offsprings xl, xu = self.problem.bounds() X, sigma = pop.get("X", "sigma") # cycle through the elites individuals for create the solutions I = np.arange(_lambda) % mu # transform X and sigma to the shape of number of offsprings X, sigma = X[I], sigma[I] # get the sigma only of the elites to be used sigmap = es_intermediate_recomb(sigma) # calculate the new sigma based on tau and tau prime sigmap = np.minimum(self.sigma_max, es_sigma(sigmap, self.tau, self.taup)) # execute the evolutionary strategy to calculate the offspring solutions Xp = X + sigmap * np.random.normal(size=sigmap.shape) # if gamma is not none do the differential variation overwrite Xp and sigmap for the first mu-1 individuals if self.gamma is not None: Xp[:mu - 1] = X[:mu - 1] + self.gamma * (X[0] - X[1:mu]) sigmap[:mu - 1] = sigma[:mu - 1] # if we have bounds to consider -> repair the individuals which are out of bounds if self.problem.has_bounds(): Xp = es_mut_repair(Xp, X, sigmap, xl, xu, 10) # create the population to proceed further off = Population.new(X=Xp, sigma=sigmap) return off
def _infill(self): pop, mu, _lambda = self.pop, self.pop_size, self.n_offsprings xl, xu = self.problem.bounds() X, sigma = pop.get("X", "sigma") # cycle through the elites individuals for create the solutions I = np.arange(_lambda) % mu # transform X and sigma to the shape of number of offsprings X, sigma = X[I], sigma[I] # copy the original sigma to sigma prime to be modified Xp, sigmap = np.copy(X), np.copy(sigma) # for the best individuals do differential variation to provide a direction to search in Xp[:mu - 1] = X[:mu - 1] + self.gamma * (X[0] - X[1:mu]) # update the sigma values for elite and non-elite individuals sigmap[mu - 1:] = np.minimum( self.sigma_max, es_sigma(sigma[mu - 1:], self.tau, self.taup)) # execute the evolutionary strategy to calculate the offspring solutions Xp[mu - 1:] = X[mu - 1:] + sigmap[mu - 1:] * np.random.normal( size=sigmap[mu - 1:].shape) # repair the individuals which are not feasible by sampling from sigma again Xp = es_mut_repair(Xp, X, sigmap, xl, xu, 10) # now update the sigma values of the non-elites only sigmap[mu:] = sigma[mu:] + self.alpha * (sigmap[mu:] - sigma[mu:]) # create the population to proceed further off = Population.new(X=Xp, sigma=sigmap) return off
def test_survival(): problem = DTLZ2(n_obj=3) for k in range(1, 11): print("TEST RVEA GEN", k) ref_dirs = np.loadtxt( path_to_test_resource('rvea', f"ref_dirs_{k}.txt")) F = np.loadtxt(path_to_test_resource('rvea', f"F_{k}.txt")) pop = Population.new(F=F) algorithm = RVEA(ref_dirs) algorithm.setup(problem, termination=('n_gen', 500)) algorithm.n_gen = k algorithm.pop = pop survival = APDSurvival(ref_dirs) survivors = survival.do(problem, algorithm.pop, n_survive=len(pop), algorithm=algorithm, return_indices=True) apd = pop[survivors].get("apd") correct_apd = np.loadtxt(path_to_test_resource('rvea', f"apd_{k}.txt")) np.testing.assert_allclose(apd, correct_apd)
def update_PCpop(pc_pop, off): pc_objs = pc_pop.get("F") off_objs = off.get("F") n = pc_objs.shape[0] del_ind = [] for i in range(n): flag = Dominator.get_relation(off_objs[0, :], pc_objs[i, :]) if flag == 1: # off dominates pc_pop[i] del_ind.append(i) break elif flag == -1: # pc_pop[i] dominates off return pc_pop else: # flag == 0 # off and pc_pop[i] are nondominated break if len(del_ind) > 0: pc_index = np.arange(n) # Delete element at index positions given by the list 'del_ind' pc_index = np.delete(pc_index, del_ind) pc_pop = pc_pop[pc_index.tolist()] pc_pop = Population.merge(pc_pop, off) return pc_pop
def filter_optimum(pop, least_infeasible=False): # if the population is none to optimum can be found if pop is None or len(pop) == 0: return None # first only choose feasible solutions ret = pop[pop.get("feasible")[:, 0]] # if at least one feasible solution was found if len(ret) > 0: # then check the objective values F = ret.get("F") if F.shape[1] > 1: I = NonDominatedSorting().do(F, only_non_dominated_front=True) ret = ret[I] else: ret = ret[np.argmin(F)] # no feasible solution was found else: # if flag enable report the least infeasible if least_infeasible: ret = pop[np.argmin(pop.get("CV"))] # otherwise just return none else: ret = None if isinstance(ret, Individual): ret = Population().create(ret) return ret
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 update_ep(EP, Offsprings, nEP, nds): """ Update the external population archive """ # merge population and keep only the first non-dominated front EP = Population.merge(EP, Offsprings) EP = EP[nds.do(EP.get("F"), only_non_dominated_front=True)] N, M = EP.get("F").shape # Delete the overcrowded solutions D = cdist(EP.get("F"), EP.get("F")) # prevent selection of a point with itself D[np.eye(len(D), dtype=np.bool)] = np.inf removed = np.zeros(N, dtype=np.bool) while sum(removed) < N - nEP: remain = np.flatnonzero(~removed) subDis = np.sort(D[np.ix_(remain, remain)], axis=1) # compute viscinity distance among the closest neighbors prodDist = np.prod(subDis[:, 0:min(M, len(remain))], axis=1) # select the point with the smallest viscinity distance to its neighbors and set it as removed worst = np.argmin(prodDist) removed[remain[worst]] = True return EP[~removed]
def _backward(self, X, inplace=False, **kwargs): assert isinstance(X, np.ndarray) and X.ndim == 2 if inplace: self.obj.set(self.attr, X) return self.obj else: return Population.new(**{self.attr: X})
def _advance(self, infills=None, **kwargs): assert infills is not None, "This algorithms uses the AskAndTell interface thus infills must to be provided." pop = Population.merge(self.pop, infills) self.pop, self.da = self.survival.do(self.problem, pop, self.da, self.pop_size, algorithm=self)
def __init__(self, ref_dirs, n_neighbors=20, decomposition=Tchebicheff2(), prob_neighbor_mating=0.9, sampling=FloatRandomSampling(), crossover=SimulatedBinaryCrossover(prob=1.0, eta=20), mutation=PolynomialMutation(prob=None, eta=20), display=MultiObjectiveDisplay(), **kwargs): """ Parameters ---------- ref_dirs n_neighbors decomposition prob_neighbor_mating display kwargs """ self.ref_dirs = ref_dirs self.pc_capacity = len(ref_dirs) self.pc_pop = Population.new() self.npc_pop = Population.new() self.n_neighbors = min(len(ref_dirs), n_neighbors) self.prob_neighbor_mating = prob_neighbor_mating self.decomp = decomposition # initialise the neighborhood of subproblems based on the distances of weight vectors self.neighbors = np.argsort(cdist(self.ref_dirs, self.ref_dirs), axis=1, kind='quicksort')[:, :self.n_neighbors] self.selection = NeighborhoodSelection(self.neighbors, prob=prob_neighbor_mating) super().__init__(pop_size=len(ref_dirs), sampling=sampling, crossover=crossover, mutation=mutation, eliminate_duplicates=DefaultDuplicateElimination(), display=display, advance_after_initialization=False, **kwargs)
def _local_infill(self): X = np.array(self.next_X) self.send_array_to_yield = X.ndim > 1 X = np.atleast_2d(X) # evaluate the population self.pop = Population.new("X", self.norm.backward(X)) return self.pop
def crossover(crossover, a, b, c=None, xl=0, xu=1, type_var=np.double, **kwargs): n = a.shape[0] _pop = Population.merge(Population.new("X", a), Population.new("X", b)) _P = np.column_stack([np.arange(n), np.arange(n) + n]) if c is not None: _pop = Population.merge(_pop, Population.new("X", c)) _P = np.column_stack([_P, np.arange(n) + 2 * n]) problem = get_problem_func(a.shape[1], xl, xu, type_var)(**kwargs) return crossover.do(problem, _pop, _P, **kwargs).get("X")
def _advance(self, infills=None, **kwargs): # if not all solutions suggested by infill() are evaluated we create a more semi (mu+lambda) algorithm if len(infills) < self.pop_size: infills = Population.merge(infills, self.pop) self.pop = self.survival.do(self.problem, infills, n_survive=self.pop_size)
def _do(self, problem, pop, off, algorithm=None, **kwargs): if self.cnt is None: self.cnt = np.zeros(len(pop), dtype=int) cnt = self.cnt cv = pop.get("CV")[:, 0] # cnt = self.cnt cnt = algorithm.n_gen - pop.get("n_gen") - 1 # make sure we never replace the best solution if we would consider feasibility first best = FitnessSurvival().do(problem, Population.merge(pop, off), n_survive=1)[0] eps = np.zeros(len(pop)) for k, t in enumerate(cnt): # cycle = (t // (4 * self.t)) # max_eps = (2 ** cycle) * self.eps max_eps = self.eps t = t % (4 * self.t) if t < self.t: eps[k] = cv[k] + (max_eps - cv[k]) * (t / self.t) elif t < 2 * self.t: eps[k] = max_eps elif t < 3 * self.t: eps[k] = max_eps * (1 - ((t % self.t) / self.t)) else: eps[k] = 0.0 eps_is_zero = np.where(eps <= 0)[0] # print(len(eps_is_zero)) repl = np.full(len(pop), False) for k in range(len(pop)): if pop[k] == best: repl[k] = False elif off[k] == best: repl[k] = True else: if rel_eps_constr(pop[k], off[k], eps[k]) <= 0: repl[k] = True # self.cnt[repl] = 0 # self.cnt[~repl] += 1 return repl
def do(self, problem, pop, parents, **kwargs): X = pop.get("X")[parents.T].copy() _, n_matings, n_var = X.shape M = mut_binomial(n_matings, n_var, self.bias, at_least_once=True) Xp = X[0] Xp[~M] = X[1][~M] return Population.new(X=Xp)
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 _advance(self, infills=None): pop = self.pop # get all the elites from the current population elites = np.where(pop.get("type") == "elite")[0] # finally merge everything together and sort by fitness pop = Population.merge(pop[elites], infills) # the do survival selection - set the elites for the next round self.pop = self.survival.do(self.problem, pop, n_survive=len(pop), algorithm=self)
def _infill(self): pop = self.pop # actually do the mating given the elite selection and biased crossover off = self.mating.do(self.problem, pop, n_offsprings=self.n_offsprings, algorithm=self) # create the mutants randomly to fill the population with mutants = FloatRandomSampling().do(self.problem, self.n_mutants, algorithm=self) # evaluate all the new solutions return Population.merge(off, mutants)
def to_solution_set(X, F=None, G=None): if isinstance(X, np.ndarray): if X.ndim == 1: X = Solution(X=X, F=F, G=G) else: X = Population.new(X=X, F=F, G=G) if isinstance(X, Individual): X = SolutionSet.create(X) return X
def _advance(self, infills=None, **kwargs): # merge the offsprings with the current population if infills is not None: self.pop = Population.merge(self.pop, infills) # execute the survival to find the fittest solutions self.pop = self.survival.do(self.problem, self.pop, n_survive=self.pop_size, algorithm=self)