def __init__(self, bounds, func_eval, noise_eval, truth_eval, options, A=None, b=None): """ Alpha DOGS is an efficient optimization algorithm for time-averaged statistics. :param bounds : The physical bounds of the input for the test problem, e.g. lower bound = [0, 0, 0] while upper bound = [1, 1, 1] then bnds = np.hstack((np.zeros((3,1)), np.ones((3,1)))) :param func_eval : The objective function :param noise_eval : The function for noise evaluation :param truth_eval : The function to evaluate the truth values :param A : The linear constraints of parameter space :param b : The linear constraints of parameter space :param options : The options for alphaDOGS """ # n: The dimension of input parameter space self.n = bounds.shape[0] # physical lb & ub: Normalize the physical bounds self.physical_lb = bounds[:, 0].reshape(-1, 1) self.physical_ub = bounds[:, 1].reshape(-1, 1) # lb & ub: The normalized search bounds self.lb = np.zeros((self.n, 1)) self.ub = np.ones((self.n, 1)) # ms: The mesh size for each iteration. This is the initial definition. self.initial_mesh_size = options.get_option('Initial mesh size') self.ms = 2**self.initial_mesh_size self.num_mesh_refine = options.get_option('Number of mesh refinement') self.max_mesh_size = 2**(self.initial_mesh_size + self.num_mesh_refine) self.iter = 0 # Define the surrogate model if options.get_option('Constant surrogate') and options.get_option( 'Adaptive surrogate'): raise ValueError( 'Constant and Adaptive surrogate both activated. Set one to False then rerun code.' ) elif not options.get_option( 'Constant surrogate') and not options.get_option( 'Adaptive surrogate'): raise ValueError( 'Constant and Adaptive surrogate both inactivated. Set one to True then rerun code.' ) elif options.get_option('Constant surrogate'): self.surrogate_type = 'c' elif options.get_option('Adaptive surrogate'): self.surrogate_type = 'a' else: pass if self.surrogate_type == 'c': # Define the parameters for discrete and continuous constant search function self.L = options.get_option('Constant L') self.L0 = self.L self.K = options.get_option('Constant K') elif self.surrogate_type == 'a': self.y0 = options.get_option('Target value') # Define the linear constraints, Ax <= b. if A and b are None type, set them to be the box domain constraints. if (A and b) is None: self.Ain = np.concatenate( (np.identity(self.n), -np.identity(self.n)), axis=0) self.Bin = np.concatenate((np.ones( (self.n, 1)), np.zeros((self.n, 1))), axis=0) else: pass # Define the statistics for time length self.T0 = options.get_option('Initial time length') self.dt = options.get_option('Incremental time step') self.eval_times = options.get_option('Maximum evaluation times') self.Tmax = self.T0 + self.eval_times * self.dt self.Tmax_reached = None if options.get_option('Scipy solver') and options.get_option( 'Snopt solver'): raise ValueError('More than one optimization solver specified!') elif not options.get_option('Scipy solver') and not options.get_option( 'Snopt solver'): raise ValueError('No optimization solver specified!') elif options.get_option('Scipy solver'): self.solver_type = 'scipy' elif options.get_option('Snopt solver'): self.solver_type = 'snopy' else: pass # Initialize the function evaluation, noise sigma evaluation and truth function evaluation. # capsulate those three functions with physical bounds self.func_eval = partial(Utils.fun_eval, func_eval, self.physical_lb, self.physical_ub) self.sigma_eval = partial(Utils.fun_eval, noise_eval, self.physical_lb, self.physical_ub) self.truth_eval = partial(Utils.fun_eval, truth_eval, self.physical_lb, self.physical_ub) # Define the global optimum and its values if options.get_option('Global minimizer known'): self.xmin = Utils.normalize_bounds( options.get_option('Global minimizer'), self.physical_lb, self.physical_ub) self.y0 = options.get_option('Target value') else: self.xmin = None self.y0 = None # Define the iteration type for each sampling iteration. self.iter_type = None # Define the initial sites and their function evaluations if options.get_option('Initial sites known'): physical_initial_sites = options.get_option('Initial sites') # Normalize the bound self.xE = Utils.normalize_bounds(physical_initial_sites, self.physical_lb, self.physical_ub) else: self.xE = Utils.random_initial(self.n, 2 * self.n, self.ms, self.Ain, self.Bin, self.xU) if options.get_option('Initial function values') is not None: self.yE = options.get_option('Initial function values') self.T = self.T0 * np.ones(self.xE.shape[1], dtype=float) self.sigma = options.get_option('Initial funtion noise') else: # Compute the function values, time length, and noise level at initial sites self.yE = np.zeros(self.xE.shape[1]) self.T = self.T0 * np.ones(self.xE.shape[1], dtype=float) self.sigma = np.zeros(self.xE.shape[1]) for i in range(2 * self.n): self.yE[i] = self.func_eval(self.xE[:, i], self.T[i]) self.sigma[i] = self.sigma_eval(self.xE[:, i], self.T0) self.iteration_summary_matrix = {} # Define the initial support points self.xU = Utils.bounds(self.lb, self.ub, self.n) self.xU = Utils.unique_support_points(self.xU, self.xE) self.yu = None self.K0 = np.ptp(self.yE, axis=0) # Define the interpolation self.inter_par = None self.yp = None # Define the discrete search function self.sd = None # Define the minimizer of continuous search function, parameter to be evaluated, xc & yc. self.xc = None self.yc = None # Define the minimizer of discrete search function self.xd = None self.yd = None self.index_min_yd = None # Although it is stochastic, just display the behavior instead of the actual data to show the trend. # Define the name of directory to store the figures self.algorithm_name = options.solverName # ==== Plot section here ==== self.plot = plot.PlotClass() # Define the parameter to save image or no, and what format to save for images self.save_fig = options.get_option('Plot saver') self.fig_format = options.get_option('Figure format') # Generate the folder path self.func_path = options.get_option( 'Objective function name') # Function folder, e.g. Lorenz self.current_path = None # Directory path self.plot_folder = None # Plot storage folder self.folder_path_generator() # Determine the folder paths above self.func_name = options.get_option( 'Objective function name' ) # Define the name of the function called. # All the prior function values should be provided by user, instead of solver calculating those values. # Generate the plot of test function self.func_initial_prior = True # The objective function values are stored in func_prior_yE # for the future iteration, just change the point of func_prior_sigma that is close to the evaluated point self.func_prior_xE_2DX = None self.func_prior_xE_2DY = None self.func_prior_xE_2DZ = None if options.get_option('Function prior file path') is not None: self.func_prior_file_name = options.get_option( 'Function prior file path') data = io.loadmat(self.func_prior_file_name) # The prior data must have the following keyword self.func_prior_xE = data['x'] self.func_prior_yE = data['y'][0] self.func_prior_sigma = data['sigma'][0] * 2 if self.n == 1: # Define the range of plot in y-axis self.plot_ylow = np.min(self.func_prior_yE) * 2 self.plot_yupp = np.max(self.func_prior_yE) * 2 else: self.func_prior_file_name = None if options.get_option('Function evaluation cheap') and self.n < 3: if self.n == 1: self.plot.initial_calc1D(self) elif self.n == 2: self.plot.initial_calc2D(self) else: pass if self.n == 1: # Define the range of plot in y-axis self.plot_ylow = np.min(self.func_prior_yE) * 2 self.plot_yupp = np.max(self.func_prior_yE) * 2 else: self.func_initial_prior = False self.func_prior_xE = None self.func_prior_yE = None # Define the range of plot in y-axis self.plot_ylow = np.min(self.yE) * 2 self.plot_yupp = np.max(self.yE) * 2 print( 'Function evaluation is expensive and no file that contains prior function values information ' 'is found.') if self.n >= 3: print( 'Parameter space dimenion > 3, the plot for each iteration is unavailable.' ) if self.save_fig and self.func_initial_prior: if self.n == 1: self.initial_plot = self.plot.initial_plot1D self.iter_plot = self.plot.plot1D elif self.n == 2: self.initial_plot = self.plot.initial_plot2D self.iter_plot = self.plot.plot2D else: self.initial_plot = None print( "Parameter space higher than 2, set 'Plot saver' to be False." ) self.initial_plot(self) self.iter_summary = options.get_option('Iteration summary') self.optm_summary = options.get_option('Optimization summary')
def vertex_find(A, b, lb, ub): ''' Find the vertices of a simplex; Ax<b: Linear constraints; lb<x<ub: actual constraints; Attension: Do not try to include lb&ub inside A&b matrix. Example: A = np.array([[-1, 1],[1, -1]]) b = np.array([[0.5], [0.5]]) lb = np.zeros((2, 1)) ub = np.ones((2, 1)) :param A: :param b: :param lb: :param ub: :return: ''' if A.shape[0] == 0 and b.shape[0] == 0: if len(lb) != 0 and len(ub) != 0: Vertex = Utils.bounds(lb, ub, len(lb)) else: Vertex = [] raise ValueError('All inputs A, b, lb and ub have dimension 0.') else: if len(lb) != 0: Vertex = np.matrix([[], []]) m = A.shape[0] n = A.shape[1] if m == 0: Vertex = Utils.bounds(lb, ub, len(lb)) else: for r in range(min(n, m) + 1): from itertools import combinations nlist = np.arange(1, m + 1) C = np.array([list(c) for c in combinations(nlist, r)]) nlist2 = np.arange(1, n + 1) D = np.array([list(d) for d in combinations(nlist2, n - r)]) if r == 0: F = Utils.bounds(lb, ub, n) for kk in range(F.shape[1]): x = np.copy(F[:, kk]).reshape(-1, 1) if np.all(np.dot(A, x) - b < 0): Vertex = np.column_stack((Vertex, x)) else: for ii in range(len(C)): index_A = np.copy(C[ii]) v1 = np.arange(1, m + 1) index_A_C = np.setdiff1d(v1, index_A) A1 = np.copy(A[index_A - 1, :]) b1 = np.copy(b[index_A - 1]) for jj in range(len(D)): index_B = np.copy(D[jj]) v2 = np.arange(1, n + 1) index_B_C = np.setdiff1d(v2, index_B) if len(index_B) != 0 and len(index_B_C) != 0: F = Utils.bounds(lb[index_B - 1], ub[index_B - 1], n - r) A11 = np.copy(A1[:, index_B - 1]) A12 = np.copy(A1[:, index_B_C - 1]) for kk in range(F.shape[1]): A11 = np.copy(A1[:, index_B - 1]) A12 = np.copy(A1[:, index_B_C - 1]) xd = np.linalg.lstsq(A12, b1 - np.dot(A11, F[:, kk].reshape(-1, 1)), rcond=None)[0] x = np.zeros((n, 1)) x[index_B - 1] = np.copy(F[:, kk]) x[index_B_C - 1] = np.copy(xd) if r == m or (np.dot(A[index_A_C - 1, :], x) - b[index_A_C - 1]).min() < 0: if (x - ub).max() < 1e-6 and (x - lb).min() > -1e-6 and Utils.mindis(x, Vertex)[0] > 1e-6: Vertex = np.column_stack((Vertex, x)) else: m = A.shape[0] n = A.shape[1] from itertools import combinations nlist = np.arange(1, m + 1) C = np.array([list(c) for c in combinations(nlist, n)]) Vertex = np.empty(shape=[n, 0]) for ii in range(len(C)): index_A = np.copy(C[ii]) v1 = np.arange(1, m + 1) index_A_C = np.setdiff1d(v1, index_A) A1 = np.copy(A[index_A - 1, :]) b1 = np.copy(b[index_A - 1]) A2 = np.copy(A[index_A_C - 1]) b2 = np.copy(b[index_A_C - 1]) x = np.linalg.lstsq(A1, b1, rcond=None)[0] if (np.dot(A2, x) - b2).max() < 1e-6: Vertex = np.column_stack((Vertex, x)) # cant plot Vertex directly. must transform it into np.array. return np.array(Vertex)
def continuous_search_plot1D(self, adogs): ''' Plot the continuous search function. :return: ''' xU = Utils.bounds(adogs.lb, adogs.ub, adogs.n) if adogs.iter_type == 'scmin' and Utils.mindis(adogs.xc, xU)[0] > 1e-6: xi = np.hstack((adogs.xE[:, :-1], adogs.xU)) else: xi = np.hstack((adogs.xE, adogs.xU)) sx = sorted(range(xi.shape[1]), key=lambda x: xi[:, x]) tri = np.zeros((xi.shape[1] - 1, 2)) tri[:, 0] = sx[:xi.shape[1] - 1] tri[:, 1] = sx[1:] tri = tri.astype(np.int32) num_plot_points = 2000 xe_plot = np.zeros((tri.shape[0], num_plot_points)) e_plot = np.zeros((tri.shape[0], num_plot_points)) sc_plot = np.zeros((tri.shape[0], num_plot_points)) for ii in range(len(tri)): simplex_range = np.copy(xi[:, tri[ii, :]]) # Discretized mesh grid on x direction in one simplex x = np.linspace(simplex_range[0, 0], simplex_range[0, 1], num_plot_points) # Circumradius and circumcenter for current simplex R2, xc = Utils.circhyp(xi[:, tri[ii, :]], adogs.n) for jj in range(len(x)): # Interpolation p(x) p = adogs.inter_par.inter_val(x[jj]) # Uncertainty function e(x) e_plot[ii, jj] = (R2 - np.linalg.norm(x[jj] - xc)**2) # Continuous search function s(x) sc_plot[ii, jj] = p - adogs.K * adogs.K0 * e_plot[ii, jj] xe_plot[ii, :] = np.copy(x) for i in range(len(tri)): # Plot the uncertainty function e(x) # plt.plot(xe_plot[i, :], e_plot[i, :] - 5.5, c='g', label=r'$e(x)$') # Plot the continuous search function sc(x) plt.plot(xe_plot[i, :], sc_plot[i, :], 'r--', zorder=20, label=r'$S_c(x)$') yc_min = sc_plot.flat[np.abs(xe_plot - adogs.xc).argmin()] plt.scatter(adogs.xc, yc_min, c=(1, 0.769, 0.122), marker='D', zorder=15, label=r'min $S_c(x)$')
sc = "AdaptiveK" # The type of continuous search function alg_name = 'DDOGS/' # Calculate the Initial trinagulation points num_iter = 0 # Represents how many iteration the algorithm goes Nm = 8 # Initial mesh grid size L_refine = 0 # Initial refinement sign # Truth function fun, lb, ub, y0, xmin, fname = Utils.test_fun(fun_arg, n) func_eval = partial(Utils.fun_eval, fun, lb, ub) # safe constraints safe_fun, lb, ub, safe_name, L_safe = Utils.test_safe_fun(safe_fun_arg, n) safe_eval = partial(Utils.fun_eval, safe_fun, lb, ub) xU = Utils.bounds(np.zeros([n, 1]), np.ones([n, 1]), n) Ain = np.concatenate((np.identity(n), -np.identity(n)), axis=0) Bin = np.concatenate((np.ones((n, 1)), np.zeros((n, 1))), axis=0) regret = np.zeros((nff, iter_max)) estimate = np.zeros((nff, iter_max)) datalength = np.zeros((nff, iter_max)) mesh = np.zeros((nff, iter_max)) for ff in range(nff): xE = np.array([[0.2]]) num_ini = xE.shape[1] yE = np.zeros(xE.shape[1]) # yE stores the objective function value y_safe = np.zeros( xE.shape[1]) # y_safe stores the value of safe constraints.