def test_slhd(): for i in range(10, 12): # To test even and odd slhd = SymmetricLatinHypercube(dim=3, num_pts=i) X = slhd.generate_points() assert isinstance(slhd, ExperimentalDesign) assert np.all(X.shape == (i, 3)) assert slhd.num_pts == i assert slhd.dim == 3
def test_slhd_round(): num_pts = 10 dim = 3 lb = np.array([1, 2, 3]) ub = np.array([3, 4, 5]) int_var = np.array([1]) np.random.seed(0) slhd = SymmetricLatinHypercube(dim=dim, num_pts=num_pts) X = slhd.generate_points(lb=lb, ub=ub, int_var=int_var) assert np.all(np.round(X[:, 1] == X[:, 1])) # Should be integers assert np.all(np.max(X, axis=0) == ub) assert np.all(np.min(X, axis=0) == lb)
def optimize(self): """Method used to run the Genetic algorithm :return: Returns the best individual and its function value :rtype: numpy.array, float """ # Initialize population if isinstance(self.start, np.ndarray): if self.start.shape[0] != self.nindividuals or \ self.start.shape[1] != self.nvariables: raise ValueError("Initial population has incorrect size") if any(np.min(self.start, axis=0) >= self.lower_boundary) or \ any(np.max(self.start, axis=0) <= self.upper_boundary): raise ValueError("Initial population is outside the domain") population = self.start elif self.start == "SLHD": exp_des = SymmetricLatinHypercube( self.nvariables, self.nindividuals) population = self.lower_boundary + exp_des.generate_points() * \ (self.upper_boundary - self.lower_boundary) elif self.start == "LHD": exp_des = LatinHypercube(self.nvariables, self.nindividuals) population = self.lower_boundary + exp_des.generate_points() * \ (self.upper_boundary - self.lower_boundary) elif self.start == "Random": population = self.lower_boundary + np.random.rand( self.nindividuals, self.nvariables) *\ (self.upper_boundary - self.lower_boundary) else: raise ValueError("Unknown argument for initial population") new_population = [] # Round positions if len(self.integer_variables) > 0: new_population = np.copy(population) population[:, self.integer_variables] = np.round( population[:, self.integer_variables]) for i in self.integer_variables: ind = np.where(population[:, i] < self.lower_boundary[i]) population[ind, i] += 1 ind = np.where(population[:, i] > self.upper_boundary[i]) population[ind, i] -= 1 # Evaluate all individuals function_values = self.function(population) if len(function_values.shape) == 2: function_values = np.squeeze(np.asarray(function_values)) # Save the best individual ind = np.argmin(function_values) best_individual = np.copy(population[ind, :]) best_value = function_values[ind] if len(self.integer_variables) > 0: population = new_population # Main loop for _ in range(self.ngenerations): # Do tournament selection to select the parents competitors = np.random.randint( 0, self.nindividuals, (self.nindividuals, self.tournament_size)) ind = np.argmin(function_values[competitors], axis=1) winner_indices = np.zeros(self.nindividuals, dtype=int) for i in range(self.tournament_size): # This loop is short winner_indices[np.where(ind == i)] = \ competitors[np.where(ind == i), i] parent1 = population[ winner_indices[0:self.nindividuals//2], :] parent2 = population[ winner_indices[self.nindividuals//2:self.nindividuals], :] # Averaging Crossover cross = np.where(np.random.rand( self.nindividuals//2) < self.p_cross)[0] nn = len(cross) # Number of crossovers alpha = np.random.rand(nn, 1) # Create the new chromosomes parent1_new = np.multiply(alpha, parent1[cross, :]) + \ np.multiply(1 - alpha, parent2[cross, :]) parent2_new = np.multiply(alpha, parent2[cross, :]) + \ np.multiply(1 - alpha, parent1[cross, :]) parent1[cross, :] = parent1_new parent2[cross, :] = parent2_new population = np.concatenate((parent1, parent2)) # Apply mutation scale_factors = self.sigma * ( self.upper_boundary - self.lower_boundary) # Scale perturbation = np.random.randn( self.nindividuals, self.nvariables) # Generate perturbations perturbation = np.multiply( perturbation, scale_factors) # Scale accordingly perturbation = np.multiply(perturbation, ( np.random.rand(self.nindividuals, self.nvariables) < self.p_mutation)) population += perturbation # Add perturbation population = np.maximum(np.reshape( self.lower_boundary, (1, self.nvariables)), population) population = np.minimum(np.reshape( self.upper_boundary, (1, self.nvariables)), population) # Round chromosomes new_population = [] if len(self.integer_variables) > 0: new_population = np.copy(population) population = round_vars(population, self.integer_variables, self.lower_boundary, self.upper_boundary) # Keep the best individual population[0, :] = best_individual # Evaluate all individuals function_values = self.function(population) if len(function_values.shape) == 2: function_values = np.squeeze(np.asarray(function_values)) # Save the best individual ind = np.argmin(function_values) best_individual = np.copy(population[ind, :]) best_value = function_values[ind] # Use the positions that are not rounded if len(self.integer_variables) > 0: population = new_population return best_individual, best_value
def optimize(self): """Method used to run the Genetic algorithm :return: Returns the best individual and its function value :rtype: numpy.array, float """ # Initialize population if isinstance(self.start, np.ndarray): if self.start.shape[0] != self.nindividuals or \ self.start.shape[1] != self.nvariables: raise ValueError("Initial population has incorrect size") if any(np.min(self.start, axis=0) >= self.lower_boundary) or \ any(np.max(self.start, axis=0) <= self.upper_boundary): raise ValueError("Initial population is outside the domain") population = self.start elif self.start == "SLHD": from pySOT.experimental_design import SymmetricLatinHypercube exp_des = SymmetricLatinHypercube( self.nvariables, self.nindividuals) population = self.lower_boundary + exp_des.generate_points() * \ (self.upper_boundary - self.lower_boundary) elif self.start == "LHD": from pySOT.experimental_design import LatinHypercube exp_des = LatinHypercube(self.nvariables, self.nindividuals) population = self.lower_boundary + exp_des.generate_points() * \ (self.upper_boundary - self.lower_boundary) elif self.start == "Random": population = self.lower_boundary + np.random.rand( self.nindividuals, self.nvariables) *\ (self.upper_boundary - self.lower_boundary) else: raise ValueError("Unknown argument for initial population") new_population = [] # Round positions if len(self.integer_variables) > 0: new_population = np.copy(population) population[:, self.integer_variables] = np.round( population[:, self.integer_variables]) for i in self.integer_variables: ind = np.where(population[:, i] < self.lower_boundary[i]) population[ind, i] += 1 ind = np.where(population[:, i] > self.upper_boundary[i]) population[ind, i] -= 1 # Evaluate all individuals function_values = self.function(population) if len(function_values.shape) == 2: function_values = np.squeeze(np.asarray(function_values)) # Save the best individual ind = np.argmin(function_values) best_individual = np.copy(population[ind, :]) best_value = function_values[ind] if len(self.integer_variables) > 0: population = new_population # Main loop for _ in range(self.ngenerations): # Do tournament selection to select the parents competitors = np.random.randint( 0, self.nindividuals, (self.nindividuals, self.tournament_size)) ind = np.argmin(function_values[competitors], axis=1) winner_indices = np.zeros(self.nindividuals, dtype=int) for i in range(self.tournament_size): # This loop is short winner_indices[np.where(ind == i)] = \ competitors[np.where(ind == i), i] parent1 = population[ winner_indices[0:self.nindividuals//2], :] parent2 = population[ winner_indices[self.nindividuals//2:self.nindividuals], :] # Averaging Crossover cross = np.where(np.random.rand( self.nindividuals//2) < self.p_cross)[0] nn = len(cross) # Number of crossovers alpha = np.random.rand(nn, 1) # Create the new chromosomes parent1_new = np.multiply(alpha, parent1[cross, :]) + \ np.multiply(1 - alpha, parent2[cross, :]) parent2_new = np.multiply(alpha, parent2[cross, :]) + \ np.multiply(1 - alpha, parent1[cross, :]) parent1[cross, :] = parent1_new parent2[cross, :] = parent2_new population = np.concatenate((parent1, parent2)) # Apply mutation scale_factors = self.sigma * ( self.upper_boundary - self.lower_boundary) # Scale perturbation = np.random.randn( self.nindividuals, self.nvariables) # Generate perturbations perturbation = np.multiply( perturbation, scale_factors) # Scale accordingly perturbation = np.multiply(perturbation, ( np.random.rand(self.nindividuals, self.nvariables) < self.p_mutation)) population += perturbation # Add perturbation population = np.maximum(np.reshape( self.lower_boundary, (1, self.nvariables)), population) population = np.minimum(np.reshape( self.upper_boundary, (1, self.nvariables)), population) # Round chromosomes new_population = [] if len(self.integer_variables) > 0: new_population = np.copy(population) population = round_vars(population, self.integer_variables, self.lower_boundary, self.upper_boundary) # Keep the best individual population[0, :] = best_individual # Evaluate all individuals function_values = self.function(population) if len(function_values.shape) == 2: function_values = np.squeeze(np.asarray(function_values)) # Save the best individual ind = np.argmin(function_values) best_individual = np.copy(population[ind, :]) best_value = function_values[ind] # Use the positions that are not rounded if len(self.integer_variables) > 0: population = new_population return best_individual, best_value
class MoSyncStrategyNoConstraints(BaseStrategy): """Parallel Multi-Objective synchronous optimization strategy without non-bound constraints. (GOMORS) This class implements the GOMORS Framework described by Akhtar and Shoemaker (2016). After the initial experimental design (which is embarrassingly parallel), the optimization proceeds in phases. During each phase, we allow nsamples simultaneous function evaluations. We insist that these evaluations run to completion -- if one fails for whatever reason, we will resubmit it. Samples are drawn randomly from a multi-rule selection strategy that includes i) Global Evolutionary / Candidate search with three selection rules a) Hypervolume, b) Max-min Decision Space Distance and c) Max-min Objective Space Distance, and, ii) Neighborhood Evolutionary / Candidate Search with hv selection. :param worker_id: Start ID in a multi-start setting :type worker_id: int :param data: Problem parameter data structure :type data: Object :param response_surface: Surrogate model object :type response_surface: Object :param maxeval: Stopping criterion. If positive, this is an evaluation budget. If negative, this is a time budget in seconds. :type maxeval: int :param nsamples: Number of simultaneous fevals allowed :type nsamples: int :param exp_design: Experimental design :type exp_design: Object :param sampling_method: Sampling method for finding points to evaluate :type sampling_method: Object :param extra: Points to be added to the experimental design :type extra: numpy.array :param extra_vals: Values of the points in extra (if known). Use nan for values that are not known. :type extra_vals: numpy.array """ def __init__(self, worker_id, data, response_surface, maxeval, nsamples, exp_design=None, sampling_method=None, archiving_method=None, extra=None, extra_vals=None, store_sim=False): # Check stopping criterion self.start_time = time.time() if maxeval < 0: # Time budget self.maxeval = np.inf self.time_budget = np.abs(maxeval) else: self.maxeval = maxeval self.time_budget = np.inf # Import problem information self.worker_id = worker_id self.data = data self.fhat = [] if response_surface is None: for i in range(self.data.nobj): self.fhat.append( RBFInterpolant(kernel=CubicKernel, tail=LinearTail, maxp=maxeval)) #MOPLS ONLY else: for i in range(self.data.nobj): response_surface.reset() # Just to be sure! self.fhat.append(deepcopy(response_surface)) #MOPLS ONLY self.ncenters = nsamples self.nsamples = 1 self.numinit = None self.extra = extra self.extra_vals = extra_vals self.store_sim = store_sim # Default to generate sampling points using Symmetric Latin Hypercube self.design = exp_design if self.design is None: if self.data.dim > 50: self.design = LatinHypercube(data.dim, data.dim + 1) else: self.design = SymmetricLatinHypercube(data.dim, 2 * (data.dim + 1)) self.xrange = np.asarray(data.xup - data.xlow) # algorithm parameters self.sigma_min = 0.005 self.sigma_max = 0.2 self.sigma_init = 0.2 self.failtol = max(5, data.dim) self.failcount = 0 self.contol = 5 self.numeval = 0 self.status = 0 self.sigma = 0 self.resubmitter = RetryStrategy() self.xbest = None self.fbest = None self.fbest_old = None self.improvement_prev = 1 # population of centers and long-term archive self.nd_archives = [] self.new_pop = [] self.sim_res = [] if archiving_method is None: self.memory_archive = NonDominatedArchive(200) else: self.memory_archive = archiving_method self.evals = [] self.maxfit = min(200, 20 * self.data.dim) self.d_thresh = 1.0 # Set up search procedures and initialize self.sampling = sampling_method if self.sampling is None: self.sampling = EvolutionaryAlgorithm(data) self.check_input() # Start with first experimental design self.sample_initial() def check_input(self): """Checks that the inputs are correct""" self.check_common() if hasattr(self.data, "eval_ineq_constraints"): raise ValueError( "Optimization problem has constraints,\n" "SyncStrategyNoConstraints can't handle constraints") if hasattr(self.data, "eval_eq_constraints"): raise ValueError( "Optimization problem has constraints,\n" "SyncStrategyNoConstraints can't handle constraints") def check_common(self): """Checks that the inputs are correct""" # Check evaluation budget if self.extra is None: if self.maxeval < self.design.npts: raise ValueError( "Experimental design is larger than the evaluation budget") else: # Check the number of unknown extra points if self.extra_vals is None: # All extra point are unknown nextra = self.extra.shape[0] else: # We know the values at some extra points so count how many we don't know nextra = np.sum(np.isinf(self.extra_vals[0])) + np.sum( np.isnan(self.extra_vals[0])) if self.maxeval < self.design.npts + nextra: raise ValueError("Experimental design + extra points " "exceeds the evaluation budget") # Check dimensionality if self.design.dim != self.data.dim: raise ValueError("Experimental design and optimization " "problem have different dimensions") if self.extra is not None: if self.data.dim != self.extra.shape[1]: raise ValueError("Extra point and optimization problem " "have different dimensions") if self.extra_vals is not None: if self.extra.shape[0] != len(self.extra_vals): raise ValueError("Extra point values has the wrong length") # Check that the optimization problem makes sense check_opt_prob(self.data) def proj_fun(self, x): """Projects a set of points onto the feasible region :param x: Points, of size npts x dim :type x: numpy.array :return: Projected points :rtype: numpy.array """ x = np.atleast_2d(x) return round_vars(self.data, x) def log_completion(self, record): """Record a completed evaluation to the log. :param record: Record of the function evaluation :type record: Object """ xstr = np.array_str(record.params[0], max_line_width=np.inf, precision=5, suppress_small=True) if self.store_sim is True: fstr = np.array_str(record.value[0], max_line_width=np.inf, precision=5, suppress_small=True) else: fstr = np.array_str(record.value, max_line_width=np.inf, precision=5, suppress_small=True) if record.feasible: logger.info("{} {} @ {}".format("True", fstr, xstr)) else: logger.info("{} {} @ {}".format("False", fstr, xstr)) def sample_initial(self): """Generate and queue an initial experimental design.""" for fhat in self.fhat: fhat.reset() #MOPLS Only self.sigma = self.sigma_init self.failcount = 0 self.xbest = None self.fbest_old = None self.fbest = None for fhat in self.fhat: fhat.reset() #MOPLS Only start_sample = self.design.generate_points() assert start_sample.shape[1] == self.data.dim, \ "Dimension mismatch between problem and experimental design" start_sample = from_unit_box(start_sample, self.data) if self.extra is not None: # We know the values if this is a restart, so add the points to the surrogate if self.numeval > 0: for i in range(len(self.extra_vals)): xx = self.proj_fun(np.copy(self.extra[i, :])) for j in range(self.data.nobj): self.fhat[j].add_point(np.ravel(xx), self.extra_vals[i, j]) else: # Check if we know the values of the points if self.extra_vals is None: self.extra_vals = np.nan * np.ones( (self.extra.shape[0], self.data.nobj)) for i in range(len(self.extra_vals)): xx = self.proj_fun(np.copy(self.extra[i, :])) if np.isnan(self.extra_vals[i, 0]) or np.isinf( self.extra_vals[i, 0]): # We don't know this value proposal = self.propose_eval(np.ravel(xx)) proposal.extra_point_id = i # Decorate the proposal self.resubmitter.rput(proposal) else: # We know this value for j in range(self.data.nobj): self.fhat[j].add_point(np.ravel(xx), self.extra_vals[i, j]) # 2 - Generate a Memory Record of the New Evaluation srec = MemoryRecord(np.copy(np.ravel(xx)), self.extra_vals[i, :], self.sigma_init) self.new_pop.append(srec) self.evals.append(srec) # Evaluate the experimental design for j in range(min(start_sample.shape[0], self.maxeval - self.numeval)): start_sample[j, :] = self.proj_fun( start_sample[j, :]) # Project onto feasible region proposal = self.propose_eval(np.copy(start_sample[j, :])) self.resubmitter.rput(proposal) if self.extra is not None: sample_init = np.vstack((start_sample, self.extra)) else: sample_init = start_sample sample_prev = np.copy(sample_init) if self.numeval == 0: logger.info("=== Start ===") elif self.status < self.contol: logger.info("=== Connected Start ===") print('Connected Restart # ' + str(self.status + 1) + ' initiated') # Step 1 - Update connected restart count self.status += 1 # Step 2 - Obtain xvals and fvals of ND points front = self.memory_archive.contents fvals = [rec.fx for rec in front] fvals = np.asarray(fvals) xvals = [rec.x for rec in front] xvals = np.asarray(xvals) # Step 3 - Add ND points to the surrogate npts, nobj = fvals.shape for i in range(npts): for j in range(nobj): self.fhat[j].add_point(xvals[i, :], fvals[i, j]) # Step 4 - Add points to the set of previously evaluated points for sampling strategy all_xvals = [rec.x for rec in self.evals] sample_prev = np.vstack((sample_prev, all_xvals)) else: # Step 4 - Store the Front in a separate archive front = self.memory_archive.contents self.nd_archives.append(front) self.status = 0 if len(self.nd_archives) == 2: logger.info("=== Global Connected Restart ===") print('GLOBAL Restart Initiated') prev_front = self.nd_archives[0] for rec in prev_front: self.memory_archive.add(rec) # Step 2 - Obtain xvals and fvals of ND points front = self.memory_archive.contents fvals = [rec.fx for rec in front] fvals = np.asarray(fvals) xvals = [rec.x for rec in front] xvals = np.asarray(xvals) # Step 3 - Add ND points to the surrogate npts, nobj = fvals.shape for i in range(npts): for j in range(nobj): self.fhat[j].add_point(xvals[i, :], fvals[i, j]) # Step 4 - Add points to the set of previously evaluated points for sampling strategy all_xvals = [rec.x for rec in self.evals] sample_prev = np.vstack((sample_prev, all_xvals)) self.nd_archives = [] self.failtol = self.failtol * 2 else: logger.info("=== Independent Restart ===") print('INDEPENDENT Restart Initiated') self.memory_archive.reset() #GOMORS only self.new_pop = [] #GOMORS Only all_xvals = [rec.x for rec in self.evals] sample_prev = np.vstack((sample_prev, all_xvals)) self.sampling.init(sample_init, self.fhat, self.maxeval - self.numeval, sample_prev) if self.numinit is None: self.numinit = start_sample.shape[0] print('Initialization completed successfully') def update_archives(self): """Update the Tabu list, Tabu Tenure, memory archive and non-dominated front. """ # Step 3 - Add newly Evaluated Points to Memory Archive and update ND_Archives list nimprovements = 0 for rec in self.new_pop: self.memory_archive.add(rec) nimprovements += self.memory_archive.improvement self.new_pop = [] self.memory_archive.compute_fitness() # Step 3b - Adjust failure_count if needed if nimprovements == 0: print('No Improvement Registered') if self.improvement_prev == 0: self.failcount += 1 print('No Improvement - Failure count is: ' + str(self.failcount)) self.improvement_prev = 0 else: print("Number of Improvements: " + str(nimprovements)) self.improvement_prev = 1 def sample_adapt(self): """Generate and queue samples from the search strategy""" # # Step 1 - Add Newly Evaluated Points to Memory Archive self.update_archives() front = self.memory_archive.contents fvals = [rec.fx for rec in front] fvals = np.asarray(fvals) xvals = [rec.x for rec in front] xvals = np.asarray(xvals) fitness = [rec.fitness for rec in front] if fitness[0] == POSITIVE_INFINITY: idx = random.randint(0, len(fitness) - 1) else: fitness = np.asarray(fitness) idx = np.argmax(fitness) self.xbest = xvals[idx, :] self.fbest = fvals[idx, :] #self.interactive_plotting(fvals) print('NUMBER OF EVALUATIONS COMPLETED: ' + str(self.numeval)) start = time.clock() new_points, new_fhvals, fhvals_nd = self.sampling.make_points( npts=1, xbest=self.xbest, xfront=xvals, front=fvals, proj_fun=self.proj_fun) #print(new_points) end = time.clock() totalTime = end - start print('CANDIDATE SELECTION TIME: ' + str(totalTime)) #self.interactive_plotting(fvals, new_fhvals, fhvals_nd) self.save_plot(self.numeval) nsamples = 4 for i in range(nsamples): proposal = self.propose_eval(np.copy(np.ravel(new_points[i, :]))) self.resubmitter.rput(proposal) def start_batch(self): """Generate and queue a new batch of points""" if self.failcount > self.failtol: self.sample_initial() else: self.sample_adapt() def propose_action(self): """Propose an action """ if self.numeval >= self.maxeval: # Save results to Array and Terminate X = np.zeros((self.maxeval, self.data.dim + self.data.nobj)) all_xvals = [rec.x for rec in self.evals] all_xvals = np.asarray(all_xvals) X[:, 0:self.data.dim] = all_xvals[0:self.maxeval, :] all_fvals = [rec.fx for rec in self.evals] all_fvals = np.asarray(all_fvals) X[:, self.data.dim:self.data.dim + self.data.nobj] = all_fvals[0:self.maxeval, :] np.savetxt('final.txt', X) return self.propose_terminate() elif self.resubmitter.num_eval_outstanding == 0: # UPDATE MEMORY ARCHIVE self.start_batch() return self.resubmitter.get() def on_complete(self, record): """Handle completed function evaluation. When a function evaluation is completed we need to ask the constraint handler if the function value should be modified which is the case for say a penalty method. We also need to print the information to the logfile, update the best value found so far and notify the GUI that an evaluation has completed. :param record: Evaluation record """ self.numeval += 1 record.worker_id = self.worker_id record.worker_numeval = self.numeval record.feasible = True self.log_completion(record) if self.store_sim is True: obj_val = np.copy(record.value[0]) self.sim_res.append(np.copy(record.value[1])) np.savetxt('final_simulations.txt', np.asarray(self.sim_res)) else: obj_val = np.copy(record.value) # 1 - Update Response Surface Model i = 0 for fhat in self.fhat: fhat.add_point(np.copy(record.params[0]), obj_val[i]) i += 1 # 2 - Generate a Memory Record of the New Evaluation srec = MemoryRecord(np.copy(record.params[0]), obj_val, self.sigma_init) self.new_pop.append(srec) self.evals.append(srec) def interactive_plotting(self, fvals, sel_fhvals, new_fhvals_nd): """"If interactive plotting is on, """ maxgen = (self.maxeval - self.numinit) / (self.nsamples * self.ncenters) curgen = (self.numeval - self.numinit) / (self.nsamples * self.ncenters) + 1 plt.show() #plt.plot(self.data.pf[:,0], self.data.pf[:,1], 'g') all_fvals = [rec.fx for rec in self.evals] all_fvals = np.asarray(all_fvals) plt.plot(all_fvals[:, 0], all_fvals[:, 1], 'k+') plt.plot(fvals[:, 0], fvals[:, 1], 'b*') plt.plot(self.fbest[0], self.fbest[1], 'y>') plt.plot(new_fhvals_nd[:, 0], new_fhvals_nd[:, 1], 'ro') plt.plot(sel_fhvals[:, 0], sel_fhvals[:, 1], 'cd') plt.draw() if curgen < maxgen: plt.pause(0.001) else: plt.show() def save_plot(self, i): """"If interactive plotting is on, """ #plt.figure(i) title = 'Number of Evals Completed: ' + str(i) front = self.memory_archive.contents fvals = [rec.fx for rec in front] fvals = np.asarray(fvals) maxgen = (self.maxeval - self.numinit) / (self.nsamples * self.ncenters) curgen = (self.numeval - self.numinit) / (self.nsamples * self.ncenters) + 1 if self.data.pf is not None: plt.plot(self.data.pf[:, 0], self.data.pf[:, 1], 'g') all_fvals = [rec.fx for rec in self.evals] all_fvals = np.asarray(all_fvals) plt.plot(all_fvals[:, 0], all_fvals[:, 1], 'k+') if fvals.ndim > 1: plt.plot(fvals[:, 0], fvals[:, 1], 'b*') plt.title(title) plt.draw() plt.savefig('Final') plt.clf() all_xvals = [rec.x for rec in self.evals] all_xvals = np.asarray(all_xvals) npts = all_xvals.shape[0] X = np.zeros((npts, self.data.dim + self.data.nobj)) X[:, 0:self.data.dim] = all_xvals X[:, self.data.dim:self.data.dim + self.data.nobj] = all_fvals np.savetxt('final.txt', X) if self.data.pf is not None: plt.plot(self.data.pf[:, 0], self.data.pf[:, 1], 'g') if fvals.ndim > 1: plt.plot(fvals[:, 0], fvals[:, 1], 'b*') plt.title(title) plt.draw() plt.savefig('Final_front') plt.clf()
class SyncStrategyNoConstraints(BaseStrategy): """Parallel synchronous optimization strategy without non-bound constraints. This class implements the parallel synchronous SRBF strategy described by Regis and Shoemaker. After the initial experimental design (which is embarrassingly parallel), the optimization proceeds in phases. During each phase, we allow nsamples simultaneous function evaluations. We insist that these evaluations run to completion -- if one fails for whatever reason, we will resubmit it. Samples are drawn randomly from around the current best point, and are sorted according to a merit function based on distance to other sample points and predicted function values according to the response surface. After several successive significant improvements, we increase the sampling radius; after several failures to improve the function value, we decrease the sampling radius. We restart once the sampling radius decreases below a threshold. :param worker_id: Start ID in a multi-start setting :type worker_id: int :param data: Problem parameter data structure :type data: Object :param response_surface: Surrogate model object :type response_surface: Object :param maxeval: Stopping criterion. If positive, this is an evaluation budget. If negative, this is a time budget in seconds. :type maxeval: int :param nsamples: Number of simultaneous fevals allowed :type nsamples: int :param exp_design: Experimental design :type exp_design: Object :param sampling_method: Sampling method for finding points to evaluate :type sampling_method: Object :param extra: Points to be added to the experimental design :type extra: numpy.array :param extra_vals: Values of the points in extra (if known). Use nan for values that are not known. :type extra_vals: numpy.array """ def __init__(self, worker_id, data, response_surface, maxeval, nsamples, exp_design=None, sampling_method=None, extra=None, extra_vals=None): # Check stopping criterion self.start_time = time.time() if maxeval < 0: # Time budget self.maxeval = np.inf self.time_budget = np.abs(maxeval) else: self.maxeval = maxeval self.time_budget = np.inf # Import problem information self.worker_id = worker_id self.data = data self.fhat = response_surface if self.fhat is None: self.fhat = RBFInterpolant(kernel=CubicKernel, tail=LinearTail, maxp=maxeval) self.fhat.reset() # Just to be sure! self.nsamples = nsamples self.extra = extra self.extra_vals = extra_vals # Default to generate sampling points using Symmetric Latin Hypercube self.design = exp_design if self.design is None: if self.data.dim > 50: self.design = LatinHypercube(data.dim, data.dim+1) else: self.design = SymmetricLatinHypercube(data.dim, 2*(data.dim+1)) self.xrange = np.asarray(data.xup - data.xlow) # algorithm parameters self.sigma_min = 0.005 self.sigma_max = 0.2 self.sigma_init = 0.2 self.failtol = max(5, data.dim) self.succtol = 3 self.numeval = 0 self.status = 0 self.sigma = 0 self.resubmitter = RetryStrategy() self.xbest = None self.fbest = np.inf self.fbest_old = None # Set up search procedures and initialize self.sampling = sampling_method if self.sampling is None: self.sampling = CandidateDYCORS(data) self.check_input() # Start with first experimental design self.sample_initial() def check_input(self): """Checks that the inputs are correct""" self.check_common() if hasattr(self.data, "eval_ineq_constraints"): raise ValueError("Optimization problem has constraints,\n" "SyncStrategyNoConstraints can't handle constraints") if hasattr(self.data, "eval_eq_constraints"): raise ValueError("Optimization problem has constraints,\n" "SyncStrategyNoConstraints can't handle constraints") def check_common(self): """Checks that the inputs are correct""" # Check evaluation budget if self.extra is None: if self.maxeval < self.design.npts: raise ValueError("Experimental design is larger than the evaluation budget") else: # Check the number of unknown extra points if self.extra_vals is None: # All extra point are unknown nextra = self.extra.shape[0] else: # We know the values at some extra points so count how many we don't know nextra = np.sum(np.isinf(self.extra_vals)) + np.sum(np.isnan(self.extra_vals)) if self.maxeval < self.design.npts + nextra: raise ValueError("Experimental design + extra points " "exceeds the evaluation budget") # Check dimensionality if self.design.dim != self.data.dim: raise ValueError("Experimental design and optimization " "problem have different dimensions") if self.extra is not None: if self.data.dim != self.extra.shape[1]: raise ValueError("Extra point and optimization problem " "have different dimensions") if self.extra_vals is not None: if self.extra.shape[0] != len(self.extra_vals): raise ValueError("Extra point values has the wrong length") # Check that the optimization problem makes sense check_opt_prob(self.data) def proj_fun(self, x): """Projects a set of points onto the feasible region :param x: Points, of size npts x dim :type x: numpy.array :return: Projected points :rtype: numpy.array """ x = np.atleast_2d(x) return round_vars(self.data, x) def log_completion(self, record): """Record a completed evaluation to the log. :param record: Record of the function evaluation :type record: Object """ xstr = np.array_str(record.params[0], max_line_width=np.inf, precision=5, suppress_small=True) if record.feasible: logger.info("{} {:.3e} @ {}".format("True", record.value, xstr)) else: logger.info("{} {:.3e} @ {}".format("False", record.value, xstr)) def adjust_step(self): """Adjust the sampling radius sigma. After succtol successful steps, we cut the sampling radius; after failtol failed steps, we double the sampling radius. """ # Initialize if this is the first adaptive step if self.fbest_old is None: self.fbest_old = self.fbest return # Check if we succeeded at significant improvement if self.fbest < self.fbest_old - 1e-3 * math.fabs(self.fbest_old): self.status = max(1, self.status + 1) else: self.status = min(-1, self.status - 1) self.fbest_old = self.fbest # Check if step needs adjusting if self.status <= -self.failtol: self.status = 0 self.sigma /= 2 logger.info("Reducing sigma") if self.status >= self.succtol: self.status = 0 self.sigma = min([2.0 * self.sigma, self.sigma_max]) logger.info("Increasing sigma") def sample_initial(self): """Generate and queue an initial experimental design.""" if self.numeval == 0: logger.info("=== Start ===") else: logger.info("=== Restart ===") self.fhat.reset() self.sigma = self.sigma_init self.status = 0 self.xbest = None self.fbest_old = None self.fbest = np.inf self.fhat.reset() start_sample = self.design.generate_points() assert start_sample.shape[1] == self.data.dim, \ "Dimension mismatch between problem and experimental design" start_sample = from_unit_box(start_sample, self.data) if self.extra is not None: # We know the values if this is a restart, so add the points to the surrogate if self.numeval > 0: for i in range(len(self.extra_vals)): xx = self.proj_fun(np.copy(self.extra[i, :])) self.fhat.add_point(np.ravel(xx), self.extra_vals[i]) else: # Check if we know the values of the points if self.extra_vals is None: self.extra_vals = np.nan * np.ones((self.extra.shape[0], 1)) for i in range(len(self.extra_vals)): xx = self.proj_fun(np.copy(self.extra[i, :])) if np.isnan(self.extra_vals[i]) or np.isinf(self.extra_vals[i]): # We don't know this value proposal = self.propose_eval(np.ravel(xx)) proposal.extra_point_id = i # Decorate the proposal self.resubmitter.rput(proposal) else: # We know this value self.fhat.add_point(np.ravel(xx), self.extra_vals[i]) # Evaluate the experimental design for j in range(min(start_sample.shape[0], self.maxeval - self.numeval)): start_sample[j, :] = self.proj_fun(start_sample[j, :]) # Project onto feasible region proposal = self.propose_eval(np.copy(start_sample[j, :])) self.resubmitter.rput(proposal) if self.extra is not None: self.sampling.init(np.vstack((start_sample, self.extra)), self.fhat, self.maxeval - self.numeval) else: self.sampling.init(start_sample, self.fhat, self.maxeval - self.numeval) def sample_adapt(self): """Generate and queue samples from the search strategy""" self.adjust_step() nsamples = min(self.nsamples, self.maxeval - self.numeval) new_points = self.sampling.make_points(npts=nsamples, xbest=np.copy(self.xbest), sigma=self.sigma, proj_fun=self.proj_fun) for i in range(nsamples): proposal = self.propose_eval(np.copy(np.ravel(new_points[i, :]))) self.resubmitter.rput(proposal) def start_batch(self): """Generate and queue a new batch of points""" if self.sigma < self.sigma_min: self.sample_initial() else: self.sample_adapt() def propose_action(self): """Propose an action""" current_time = time.time() if self.numeval >= self.maxeval or (current_time - self.start_time) >= self.time_budget: return self.propose_terminate() elif self.resubmitter.num_eval_outstanding == 0: self.start_batch() return self.resubmitter.get() def on_reply_accept(self, proposal): # Transfer the decorations if hasattr(proposal, 'extra_point_id'): proposal.record.extra_point_id = proposal.extra_point_id def on_complete(self, record): """Handle completed function evaluation. When a function evaluation is completed we need to ask the constraint handler if the function value should be modified which is the case for say a penalty method. We also need to print the information to the logfile, update the best value found so far and notify the GUI that an evaluation has completed. :param record: Evaluation record :type record: Object """ # Check for extra_point decorator if hasattr(record, 'extra_point_id'): self.extra_vals[record.extra_point_id] = record.value self.numeval += 1 record.worker_id = self.worker_id record.worker_numeval = self.numeval record.feasible = True self.log_completion(record) self.fhat.add_point(np.copy(record.params[0]), record.value) if record.value < self.fbest: self.xbest = np.copy(record.params[0]) self.fbest = record.value