def execute_ga(self, vlb, vub, bits, pop_size, max_gen, random_state): """ Perform the genetic algorithm. Parameters ---------- vlb : ndarray Lower bounds array. vub : ndarray Upper bounds array. bits : ndarray Number of bits to encode the design space for each element of the design vector. pop_size : int Number of points in the population. max_gen : int Number of generations to run the GA. random_state : np.random.RandomState, int Random state (or seed-number) which controls the seed and random draws. Returns ------- ndarray Best design point float Objective value at best design point. int Number of successful function evaluations. """ comm = self.comm xopt = copy.deepcopy(vlb) fopt = np.inf self.lchrom = int(np.sum(bits)) if np.mod(pop_size, 2) == 1: pop_size += 1 self.npop = int(pop_size) fitness = np.zeros((self.npop, )) Pc = 0.5 Pm = (self.lchrom + 1.0) / (2.0 * pop_size * np.sum(bits)) elite = self.elite # TODO: from an user-supplied intial population # new_gen, lchrom = encode(x0, vlb, vub, bits) new_gen = np.round( lhs(self.lchrom, self.npop, criterion='center', random_state=random_state)) # Main Loop nfit = 0 for generation in range(max_gen + 1): old_gen = copy.deepcopy(new_gen) x_pop = self.decode(old_gen, vlb, vub, bits) # Evaluate points in this generation. if comm is not None: # Parallel # Since GA is random, ranks generate different new populations, so just take one # and use it on all. x_pop = comm.bcast(x_pop, root=0) cases = [((item, ii), None) for ii, item in enumerate(x_pop)] results = concurrent_eval(self.objfun, cases, comm, allgather=True, model_mpi=self.model_mpi) fitness[:] = np.inf for result in results: returns, traceback = result if returns: val, success, ii = returns if success: fitness[ii] = val nfit += 1 else: # Print the traceback if it fails print('A case failed:') print(traceback) else: # Serial for ii in range(self.npop): x = x_pop[ii] fitness[ii], success, _ = self.objfun(x, 0) if success: nfit += 1 else: fitness[ii] = np.inf # Elitism means replace worst performing point with best from previous generation. if elite and generation > 0: max_index = np.argmax(fitness) old_gen[max_index] = min_gen x_pop[max_index] = min_x fitness[max_index] = min_fit # Find best performing point in this generation. min_fit = np.min(fitness) min_index = np.argmin(fitness) min_gen = old_gen[min_index] min_x = x_pop[min_index] if min_fit < fopt: fopt = min_fit xopt = min_x # Evolve new generation. new_gen = self.tournament(old_gen, fitness) new_gen = self.crossover(new_gen, Pc) new_gen = self.mutate(new_gen, Pm) return xopt, fopt, nfit
def execute_ga(self, x0, vlb, vub, vob, bits, pop_size, max_gen, random_state, Pm=None, Pc=0.5): """ Perform the genetic algorithm. Parameters ---------- x0 : ndarray Initial design values vlb : ndarray Lower bounds array. vub : ndarray Upper bounds array. This includes over-allocation so that every point falls on an integer value. vob : ndarray Outer bounds array. This is purely for bounds check. bits : ndarray Number of bits to encode the design space for each element of the design vector. pop_size : int Number of points in the population. max_gen : int Number of generations to run the GA. random_state : np.random.RandomState, int Random state (or seed-number) which controls the seed and random draws. Pm : float or None Mutation rate Pc : float Crossover rate Returns ------- ndarray Best design point float Objective value at best design point. int Number of successful function evaluations. """ comm = self.comm xopt = copy.deepcopy(vlb) fopt = np.inf self.lchrom = int(np.sum(bits)) if np.mod(pop_size, 2) == 1: pop_size += 1 self.npop = int(pop_size) fitness = np.zeros((self.npop, )) # If mutation rate is not provided as input if Pm is None: Pm = (self.lchrom + 1.0) / (2.0 * pop_size * np.sum(bits)) elite = self.elite new_gen = np.round(lhs(self.lchrom, self.npop, criterion='center', random_state=random_state)) new_gen[0] = self.encode(x0, vlb, vub, bits) # Main Loop nfit = 0 for generation in range(max_gen + 1): old_gen = copy.deepcopy(new_gen) x_pop = self.decode(old_gen, vlb, vub, bits) # Evaluate points in this generation. if comm is not None: # Parallel # Since GA is random, ranks generate different new populations, so just take one # and use it on all. x_pop = comm.bcast(x_pop, root=0) cases = [((item, ii), None) for ii, item in enumerate(x_pop) if np.all(item - vob <= 0)] # Pad the cases with some dummy cases to make the cases divisible amongst the procs. # TODO: Add a load balancing option to this driver. extra = len(cases) % comm.size if extra > 0: for j in range(comm.size - extra): cases.append(cases[-1]) results = concurrent_eval(self.objfun, cases, comm, allgather=True, model_mpi=self.model_mpi) fitness[:] = np.inf for result in results: returns, traceback = result if returns: val, success, ii = returns if success: fitness[ii] = val nfit += 1 else: # Print the traceback if it fails print('A case failed:') print(traceback) else: # Serial for ii in range(self.npop): x = x_pop[ii] if np.any(x - vob > 0): # Exceeded bounds for integer variables that are over-allocated. success = False else: fitness[ii], success, _ = self.objfun(x, 0) if success: nfit += 1 else: fitness[ii] = np.inf # Elitism means replace worst performing point with best from previous generation. if elite and generation > 0: max_index = np.argmax(fitness) old_gen[max_index] = min_gen x_pop[max_index] = min_x fitness[max_index] = min_fit # Find best performing point in this generation. min_fit = np.min(fitness) min_index = np.argmin(fitness) min_gen = old_gen[min_index] min_x = x_pop[min_index] if min_fit < fopt: fopt = min_fit xopt = min_x # Evolve new generation. new_gen = self.tournament(old_gen, fitness) new_gen = self.crossover(new_gen, Pc) new_gen = self.mutate(new_gen, Pm) return xopt, fopt, nfit
def execute_ga(self, x0, vlb, vub, vob, bits, pop_size, max_gen, random_state, Pm=None, Pc=0.5): """ Perform the genetic algorithm. Parameters ---------- x0 : ndarray Initial design values. vlb : ndarray Lower bounds array. vub : ndarray Upper bounds array. This includes over-allocation so that every point falls on an integer value. vob : ndarray Outer bounds array. This is purely for bounds check. bits : ndarray Number of bits to encode the design space for each element of the design vector. pop_size : int Number of points in the population. max_gen : int Number of generations to run the GA. random_state : np.random.RandomState, int Random state (or seed-number) which controls the seed and random draws. Pm : float or None Mutation rate. Pc : float Crossover rate. Returns ------- ndarray Best design point. float Objective value at best design point. int Number of successful function evaluations. """ comm = self.comm nobj = self.nobj self.lchrom = int(np.sum(bits)) if nobj > 1: xopt = [] fopt = [] # Needs to be divisible by number of objectives because of tournament selection # strategy. if np.mod(pop_size, nobj) > 0: pop_size += nobj - np.mod(pop_size, nobj) else: xopt = copy.deepcopy(vlb) fopt = np.inf # Needs to be divisible by two because tournament selection pits one half of the # population against the other half. if np.mod(pop_size, 2) == 1: pop_size += 1 self.npop = int(pop_size) fitness = np.zeros((self.npop, nobj)) # If mutation rate is not provided as input if Pm is None: Pm = (self.lchrom + 1.0) / (2.0 * pop_size * np.sum(bits)) elite = self.elite new_gen = np.round( lhs(self.lchrom, self.npop, criterion='center', random_state=random_state)) new_gen[0] = self.encode(x0, vlb, vub, bits) # Main Loop nfit = 0 for generation in range(max_gen + 1): old_gen = copy.deepcopy(new_gen) x_pop = self.decode(old_gen, vlb, vub, bits) # Evaluate fitness of points in this generation. if comm is not None: # Parallel # Since GA is random, ranks generate different new populations, so just take one # and use it on all. x_pop = comm.bcast(x_pop, root=0) cases = [((item, ii), None) for ii, item in enumerate(x_pop) if np.all(item - vob <= 0)] # Pad the cases with some dummy cases to make the cases divisible amongst the procs. # TODO: Add a load balancing option to this driver. extra = len(cases) % comm.size if extra > 0: for j in range(comm.size - extra): cases.append(cases[-1]) results = concurrent_eval(self.objfun, cases, comm, allgather=True, model_mpi=self.model_mpi) fitness[:] = np.inf for result in results: returns, traceback = result if returns: val, success, ii = returns if success: fitness[ii, :] = val nfit += 1 else: # Print the traceback if it fails print('A case failed:') print(traceback) else: # Serial for ii in range(self.npop): x = x_pop[ii] if np.any(x - vob > 0): # Exceeded bounds for integer variables that are over-allocated. success = False else: fitness[ii, :], success, _ = self.objfun(x, 0) if success: nfit += 1 else: fitness[ii, :] = np.inf # Find Pareto front. if nobj > 1: xopt, fopt = self.eval_pareto(x_pop, fitness, xopt, fopt) # Find best objective. else: # Elitism means replace worst performing point with best from # previous generation. if elite and generation > 0: max_index = np.argmax(fitness[:, 0]) old_gen[max_index] = min_gen x_pop[max_index] = min_x fitness[max_index, 0] = min_fit # Find best performing point in this generation. min_fit = np.min(fitness) min_index = np.argmin(fitness) min_gen = old_gen[min_index] min_x = x_pop[min_index] if min_fit < fopt: fopt = min_fit xopt = min_x # Evolve new generation. if nobj > 1: new_gen, new_obj = self.tournament_multi_obj(old_gen, fitness) else: new_gen = self.tournament(old_gen, fitness[:, 0]) new_gen = self.crossover(new_gen, Pc) new_gen = self.mutate(new_gen, Pm) return xopt, fopt, nfit
def execute_ga(self, vlb, vub, bits, pop_size, max_gen): """ Perform the genetic algorithm. Parameters ---------- vlb : ndarray Lower bounds array. vub : ndarray Upper bounds array. bits : ndarray Number of bits to encode the design space for each element of the design vector. pop_size : int Number of points in the population. max_gen : int Number of generations to run the GA. Returns ------- ndarray Best design point float Objective value at best design point. int Number of successful function evaluations. """ xopt = copy.deepcopy(vlb) fopt = np.inf self.lchrom = int(np.sum(bits)) if np.mod(pop_size, 2) == 1: pop_size += 1 self.npop = int(pop_size) fitness = np.zeros((self.npop, )) Pc = 0.5 Pm = (self.lchrom + 1.0) / (2.0 * pop_size * np.sum(bits)) elite = self.elite # TODO: from an user-supplied intial population # new_gen, lchrom = encode(x0, vlb, vub, bits) new_gen = np.round(lhs(self.lchrom, self.npop, criterion='center')) # Main Loop nfit = 0 for generation in range(max_gen + 1): old_gen = copy.deepcopy(new_gen) x_pop = self.decode(old_gen, vlb, vub, bits) # Evaluate points in this generation. if self.comm is not None: # Parallel cases = [((item, ii), None) for ii, item in enumerate(x_pop)] results = concurrent_eval(self.objfun, cases, self.comm, allgather=True) fitness[:] = np.inf for result in results: returns, traceback = result if returns: val, success, ii = returns if success: fitness[ii] = val nfit += 1 else: # Print the traceback if it fails print('A case failed:') print(traceback) else: # Serial for ii in range(self.npop): x = x_pop[ii] fitness[ii], success, _ = self.objfun(x, 0) if success: nfit += 1 else: fitness[ii] = np.inf # Elitism means replace worst performing point with best from previous generation. if elite and generation > 0: max_index = np.argmax(fitness) old_gen[max_index] = min_gen x_pop[max_index] = min_x fitness[max_index] = min_fit # Find best performing point in this generation. min_fit = np.min(fitness) min_index = np.argmin(fitness) min_gen = old_gen[min_index] min_x = x_pop[min_index] if min_fit < fopt: fopt = min_fit xopt = min_x # Evolve new generation. new_gen = self.tournament(old_gen, fitness) new_gen = self.crossover(new_gen, Pc) new_gen = self.mutate(new_gen, Pm) return xopt, fopt, nfit
def execute(self, x0, vlb, vub, objfun, comm, model_mpi): """ Perform the genetic algorithm. Parameters ---------- x0 : ndarray Initial design values vlb : ndarray Lower bounds array. vub : ndarray Upper bounds array. objfun : function Objective callback function. Returns ------- ndarray Best design point float Objective value at best design point. """ xopt = copy.deepcopy(vlb) fopt = np.inf count = self.lchrom = len(x0) pop_size = self.pop_size if pop_size == 0: pop_size = 20 * len(x0) if np.mod(pop_size, 2) == 1: pop_size += 1 self.npop = int(pop_size) # use different seeds in different MPI processes seed = self.random_state + comm.Get_rank() if comm else 0 rng = np.random.default_rng(seed) # create random initial population, scaled to bounds population = rng.random([self.npop, self.lchrom ]) * (vub - vlb) + vlb # scale to bounds fitness = np.ones( self.npop) * np.inf # initialize fitness to infinitely bad # Main Loop nfit = 0 for generation in range(self.max_gen + 1): # Evaluate fitness of points in this generation if comm is not None: # Parallel # Since GA is random, ranks generate different new populations, so just take one # and use it on all. population = comm.bcast(population, root=0) cases = [((item, ii), None) for ii, item in enumerate(population)] # Pad the cases with some dummy cases to make the cases divisible amongst the procs. # TODO: Add a load balancing option to this driver. extra = len(cases) % comm.size if extra > 0: for j in range(comm.size - extra): cases.append(cases[-1]) results = concurrent_eval(objfun, cases, comm, allgather=True, model_mpi=model_mpi) fitness[:] = np.inf for result in results: returns, traceback = result if returns: # val, success = returns # if success: # fitness[ii] = val # nfit += 1 fitness[ii] = returns nfit += 1 else: # Print the traceback if it fails print('A case failed:') print(traceback) else: # Serial for ii in range(self.npop): # fitness[ii], success = objfun(population[ii]) fitness[ii] = objfun(population[ii]) nfit += 1 # Find best performing point in this generation. min_fit = np.min(fitness) min_index = np.argmin(fitness) min_gen = population[min_index] # Update overall best. if min_fit < fopt: fopt = min_fit xopt = min_gen if generation == self.max_gen: # finished break # Selection: new generation members replace parents, if better (implied elitism) if generation == 0: parentPop = copy.deepcopy(population) parentFitness = copy.deepcopy(fitness) else: for ii in range(self.npop): if fitness[ii] < parentFitness[ ii]: # if child is better, else parent unchanged parentPop[ii] = population[ii] parentFitness[ii] = fitness[ii] # Evolve new generation. population = np.zeros(np.shape(parentPop)) fitness = np.ones(self.npop) * np.inf for ii in range(self.npop): # randomly select 3 different population members other than the current choice a, b, c = ii, ii, ii while a == ii: a = rng.integers(0, self.npop) while b == ii or b == a: b = rng.integers(0, self.npop) while c == ii or c == a or c == b: c = rng.integers(0, self.npop) # randomly select chromosome index for forced crossover r = rng.integers(0, self.lchrom) # crossover and mutation population[ii] = parentPop[ii] # start the same as parent # clip mutant so that it cannot be outside the bounds mutant = np.clip( parentPop[a] + self.F * (parentPop[b] - parentPop[c]), vlb, vub) # sometimes replace parent's feature with mutant's rr = rng.random(self.lchrom) idx = np.where(rr < self.Pc) population[ii][idx] = mutant[idx] population[ii][r] = mutant[ r] # always replace at least one with mutant's return xopt, fopt
def execute(self, x0, vlb, vub, objfun, comm=None, model_mpi=None): """ Execute the CMA Evolution Strategy. Parameters ---------- x0 : ndarray Initial design values vlb : ndarray Lower bounds array. vub : ndarray Upper bounds array. objfun : function Objective callback function. comm : MPI communicator or None The MPI communicator that will be used objective evaluation. model_mpi : None or tuple If the model in objfun is also parallel, then this will contain a tuple with the the total number of population points to evaluate concurrently, and the color of the point to evaluate on this rank. Returns ------- ndarray Best design point float Objective value at best design point. """ self.options['bounds'] = [vlb, vub] if comm is None: # Running non-parallel, use functional interface res = cma.fmin(objfun, x0, self.sigma0, options=self.options) return res[0], res[1] else: # Running parallel, use OO interface # make sure all procs have the same seed seed = self.options['seed'] if comm.rank == 0 and (not isinstance(seed, int) or seed == 0): seed = int(time.time()) self.options['seed'] = comm.bcast(seed, root=0) optim = cma.CMAEvolutionStrategy(x0, self.sigma0, self.options) stop = False while not stop: # optim.stop(): # get candidate solutions X = optim.ask() # pad candidates to make them divisible into procs. cases = [((item, ), None) for ii, item in enumerate(X)] extra = len(cases) % comm.size if extra > 0: for j in range(comm.size - extra): cases.append(cases[-1]) # evaluate candidate solutions concurrently results = concurrent_eval(objfun, cases, comm, allgather=True, model_mpi=model_mpi) # assemble solutions corresponding to X f = [] for i in range(len(X)): returns, traceback = results[i] if returns: # fval, success = returns f.append(returns) else: # Print the traceback if it fails print('A case failed:') print(traceback) # do the "update", pass f-values and prepare for next iteration optim.tell(X, f) optim.disp(20) # display info every 20th iteration optim.logger.add() # log another "data line", non-standard # gather stop conditions, stop if any proc stops # (all procs should stop with same stop condition) stops = comm.allgather(optim.stop()) for proc_stop in stops: if len(proc_stop) > 0: stop = True # final output # print('termination by', stops) # print('best f-value =', optim.result[1]) # print('best solution =', optim.result[0]) # optim.logger.plot() # if matplotlib is available return optim.result[0], optim.result[1]
def execute(self, x0, sigma0, CMAOptions): """ Execute the CMA Evolution Strategy. Parameters ---------- x0 : ndarray Initial design values vlb : ndarray Lower bounds array. vub : ndarray Upper bounds array. sigma0 : float Initial standard deviation in each coordinate. CMAOptions : CMAOptions Options for CMAES execution. Returns ------- ndarray Best design point float Objective value at best design point. """ comm = self.comm if comm is None: # Running non-parallel, use functional interface res = cma.fmin(self.objfun, x0, sigma0, options=CMAOptions) return res[0], res[1] else: # Running parallel, use OO interface # make sure all procs have the same seed seed = CMAOptions['seed'] if comm.rank == 0 and (not isinstance(seed, int) or seed == 0): seed = int(time.time()) CMAOptions['seed'] = comm.bcast(seed, root=0) optim = cma.CMAEvolutionStrategy(x0, sigma0, CMAOptions) stop = False while not stop: # optim.stop(): # get candidate solutions X = optim.ask() # pad candidates to make them divisible into procs. cases = [((item, ), None) for ii, item in enumerate(X)] extra = len(cases) % comm.size if extra > 0: for j in range(comm.size - extra): cases.append(cases[-1]) # evaluate candidate solutions concurrently results = concurrent_eval(self.objfun, cases, comm, allgather=True, model_mpi=self.model_mpi) # assemble solutions corresponding to X f = [] for i in range(len(X)): returns, traceback = results[i] if returns: f.append(returns) else: # Print the traceback if it fails print('A case failed:') print(traceback) # do the "update", pass f-values and prepare for next iteration optim.tell(X, f) optim.disp(20) # display info every 20th iteration optim.logger.add() # log another "data line", non-standard # gather stop conditions, stop if any proc stops # (all procs should stop with same stop condition) stops = comm.allgather(optim.stop()) for proc_stop in stops: if len(proc_stop) > 0: stop = True # final output # print('termination by', stops) # print('best f-value =', optim.result[1]) # print('best solution =', optim.result[0]) # optim.logger.plot() # if matplotlib is available return optim.result[0], optim.result[1]
def execute_ga(self, x0, vlb, vub, pop_size, max_gen, random_state, F=0.5, Pc=0.5): """ Perform the genetic algorithm. Parameters ---------- x0 : ndarray Initial design values. vlb : ndarray Lower bounds array. vub : ndarray Upper bounds array. pop_size : int Number of points in the population. max_gen : int Number of generations to run the GA. random_state : int Seed-number which controls the random draws. F : float Differential rate. Pc : float Crossover rate. Returns ------- ndarray Best design point. float Objective value at best design point. int Number of successful function evaluations. """ comm = self.comm xopt = copy.deepcopy(vlb) fopt = np.inf self.lchrom = len(x0) if np.mod(pop_size, 2) == 1: pop_size += 1 self.npop = int(pop_size) if self.comm is not None and self.comm.size > 1: if random_state is None: # if no random_state is given, generate one on rank 0 and broadcast it to all # ranks. Because we add the rank to the starting random state, no ranks will # have the same seed. rng = np.random.default_rng() random_state = rng.integers(1e15) if self.comm.rank == 0: self.comm.bcast(random_state, root=0) else: random_state = self.comm.bcast(None, root=0) # add rank to ensure different seed in each MPI process seed = random_state + self.comm.rank else: seed = random_state rng = np.random.default_rng(seed) # create random initial population, scaled to bounds population = rng.random([self.npop, self.lchrom ]) * (vub - vlb) + vlb # scale to bounds fitness = np.ones( self.npop) * np.inf # initialize fitness to infinitely bad # Main Loop nfit = 0 for generation in range(max_gen + 1): # Evaluate fitness of points in this generation if comm is not None: # Parallel # Since GA is random, ranks generate different new populations, so just take one # and use it on all. population = comm.bcast(population, root=0) cases = [((item, ii), None) for ii, item in enumerate(population)] # Pad the cases with some dummy cases to make the cases divisible amongst the procs. # TODO: Add a load balancing option to this driver. extra = len(cases) % comm.size if extra > 0: for j in range(comm.size - extra): cases.append(cases[-1]) results = concurrent_eval(self.objfun, cases, comm, allgather=True, model_mpi=self.model_mpi) fitness[:] = np.inf for result in results: returns, traceback = result if returns: val, success, ii = returns if success: fitness[ii] = val nfit += 1 else: # Print the traceback if it fails print('A case failed:') print(traceback) else: # Serial for ii in range(self.npop): fitness[ii], success, _ = self.objfun(population[ii], 0) nfit += 1 # Find best performing point in this generation. min_fit = np.min(fitness) min_index = np.argmin(fitness) min_gen = population[min_index] # Update overall best. if min_fit < fopt: fopt = min_fit xopt = min_gen if generation == max_gen: # finished break # Selection: new generation members replace parents, if better (implied elitism) if generation == 0: parentPop = copy.deepcopy(population) parentFitness = copy.deepcopy(fitness) else: for ii in range(self.npop): if fitness[ii] < parentFitness[ ii]: # if child is better, else parent unchanged parentPop[ii] = population[ii] parentFitness[ii] = fitness[ii] # Evolve new generation. population = np.zeros(np.shape(parentPop)) fitness = np.ones(self.npop) * np.inf for ii in range(self.npop): # randomly select 3 different population members other than the current choice a, b, c = ii, ii, ii while a == ii: a = rng.integers(0, self.npop) while b == ii or b == a: b = rng.integers(0, self.npop) while c == ii or c == a or c == b: c = rng.integers(0, self.npop) # randomly select chromosome index for forced crossover r = rng.integers(0, self.lchrom) # crossover and mutation population[ii] = parentPop[ii] # start the same as parent # clip mutant so that it cannot be outside the bounds mutant = np.clip( parentPop[a] + F * (parentPop[b] - parentPop[c]), vlb, vub) # sometimes replace parent's feature with mutant's rr = rng.random(self.lchrom) idx = np.where(rr < Pc) population[ii][idx] = mutant[idx] population[ii][r] = mutant[ r] # always replace at least one with mutant's return xopt, fopt, nfit
def run(self): """ Excute the genetic algorithm. Returns ------- boolean Failure flag; True if failed to converge, False is successful. """ model = self._problem.model # Size design variables. desvars = self._designvars count = 0 for name, meta in iteritems(desvars): size = meta['size'] self._desvar_idx[name] = (count, count + size) count += size lower_bound = np.empty((count, )) upper_bound = np.empty((count, )) x0 = np.empty(count) desvar_vals = self.get_design_var_values() # Figure out bounds vectors and initial design vars for name, meta in iteritems(desvars): i, j = self._desvar_idx[name] lower_bound[i:j] = meta['lower'] upper_bound[i:j] = meta['upper'] x0[i:j] = desvar_vals[name] max_iter = self.options['max_iter'] or 1e20 max_time = self.options['max_time'] or 1e20 assert max_iter < 1e20 or max_time < 1e20, "max_iter or max_time must be set" disp = self.options['disp'] abs2prom = model._var_abs2prom['output'] # Initial Design Vars desvar_vals = self.get_design_var_values() i = 0 for name, meta in iteritems(self._designvars): size = meta['size'] x0[i:i + size] = desvar_vals[name] i += size obj_value_x0, success = self.objective_callback(x0, record=True) x1 = x0.copy() n_iter = 0 desvar_info = [(abs2prom[name], *self._desvar_idx[name], lower_bound, upper_bound) for name, meta in iteritems(desvars)] desvar_dict = { name: (x0[i:j].copy(), lbound[i:j], ubound[i:j]) for (name, i, j, lbound, ubound) in desvar_info } start = time.time() comm = self.comm while n_iter < max_iter and time.time() - start < max_time: for name, i, j, _, _ in desvar_info: desvar_dict[name][0][:] = x0[i:j].copy() if comm is not None: # We do it in parallel: One case per CPU available cases = [] if comm.rank == 0: for ii in range(comm.size): desvar_dict = self.randomize_func(desvar_dict) x1 = x0.copy() for name, i, j, _, _ in desvar_info: x1[i:j] = desvar_dict[name][0][:] cases.append(((x1, ii), None)) # Let's make sure we have the same cases everywhere cases = comm.bcast(cases, root=0) results = concurrent_eval(self.objective_callback, cases, comm, allgather=True) one_success = False for i, result in enumerate(results): returns, traceback = result obj_value_x1, success = returns if success and obj_value_x1 < obj_value_x0: one_success = True x0 = cases[i][0][0].copy() obj_value_x0 = obj_value_x1 # n_iter += 1 elif obj_value_x1 < 1e10: one_success = True # n_iter += 1 if one_success: n_iter += 1 obj_value_x0, success = self.objective_callback( x0, record=True) if disp and comm.rank == 0: # obj_value_x0, success = self.objective_callback(x0, record=False) print('rank:', comm.rank, n_iter, obj_value_x0) else: # We only use one CPU desvar_dict = self.randomize_func(desvar_dict) for name, i, j, _, _ in desvar_info: x1[i:j] = desvar_dict[name][0][:] obj_value_x1, success = self.objective_callback(x1, record=False) if success and obj_value_x1 < obj_value_x0: obj_value_x1, success = self.objective_callback( x1, record=True) x0 = x1.copy() obj_value_x0 = obj_value_x1 n_iter += 1 if disp: print(n_iter, obj_value_x1) else: if obj_value_x1 < 1e10: n_iter += 1 if not success or obj_value_x1 > obj_value_x0: obj_value_x1, success = self.objective_callback( x0, record=True) return False
def __call__(self, pop): """ Evaluate the fitness of the given population. Parameters ---------- pop : array_like List of chromosomes of the individuals in the population Returns ------- fit : np.array Fitness of the individuals in the given population con : np.array or None Constraint violations of the individuals in the given population if present. None otherwise. Notes ----- If this class has an MPI communicator the individuals will be evaluated in parallel. Otherwise function evaluation will be serial. """ if self.is_initialized: fit = np.empty((self.n_pop, self.n_obj)) con = None if self.n_con is None else np.empty( (self.n_pop, self.n_con)) else: fit = pop.shape[0] * [None] con = None def handle_result(_v, _i, _fit, _con): if isinstance(_v, tuple): _fit[_i] = np.asarray(_v[0]) c = np.asarray(_v[1]) if _con is None: _con = np.empty((pop.shape[0], c.size)) _con[_i] = c else: _fit[_i] = _v return _fit, _con # Evaluate generation if self._running_under_mpi: # Construct run cases cases = [((item, ii), None) for ii, item in enumerate(pop)] # Pad the cases with some dummy cases to make the cases divisible amongst the procs. extra = len(cases) % self.comm.size if extra > 0: for j in range(self.comm.size - extra): cases.append(cases[-1]) # Compute the fitness of all individuals in parallel using MPI results = concurrent_eval(self.fobj, cases, self.comm, allgather=True, model_mpi=self.model_mpi) # Gather the results for result in results: retval, err = result if err is not None or retval is None: raise Exception(err) else: fit, con = handle_result(*retval, fit, con) else: # Evaluate the population in serial for idx, ind in enumerate(pop): val = self.fobj(ind) fit, con = handle_result(val, idx, fit, con) # Turn all NaNs in the fitnesses into infs fit = np.reshape(np.where(np.isnan(fit), np.inf, fit), (pop.shape[0], -1)) if con is not None: con = np.reshape(np.where(np.isnan(con), np.inf, con), (pop.shape[0], -1)) return fit, con