def test_branin_2D_mixed_parallel(self): n_parallel = 5 n_iter = 20 fun = Branin(ndim=2) xlimits = fun.xlimits criterion = "EI" #'EI' or 'SBO' or 'LCB' qEI = "KB" xtypes = [ORD, FLOAT] sm = KRG(print_global=False) mixint = MixedIntegerContext(xtypes, xlimits) sampling = mixint.build_sampling_method(FullFactorial) xdoe = sampling(10) ego = EGO( xdoe=xdoe, n_iter=n_iter, criterion=criterion, xtypes=[ORD, FLOAT], xlimits=xlimits, n_parallel=n_parallel, qEI=qEI, evaluator=ParallelEvaluator(), surrogate=sm, random_state=42, ) x_opt, y_opt, _, _, _ = ego.optimize(fun=fun) # 3 optimal points possible: [-pi, 12.275], [pi, 2.275], [9.42478, 2.475] self.assertTrue( np.allclose([[-3, 12.275]], x_opt, rtol=0.2) or np.allclose([[3, 2.275]], x_opt, rtol=0.2) or np.allclose([[9, 2.475]], x_opt, rtol=0.2)) self.assertAlmostEqual(0.494, float(y_opt), delta=1)
def run_mixed_integer_context_example(self): import numpy as np import matplotlib.pyplot as plt from matplotlib import colors from mpl_toolkits.mplot3d import Axes3D from smt.surrogate_models import KRG from smt.sampling_methods import LHS, Random from smt.applications.mixed_integer import MixedIntegerContext, FLOAT, INT, ENUM xtypes = [INT, FLOAT, (ENUM, 4)] xlimits = [[0, 5], [0.0, 4.0], ["blue", "red", "green", "yellow"]] def ftest(x): return (x[:, 0] * x[:, 0] + x[:, 1] * x[:, 1]) * (x[:, 2] + 1) # context to create consistent DOEs and surrogate mixint = MixedIntegerContext(xtypes, xlimits) # DOE for training lhs = mixint.build_sampling_method(LHS, criterion="ese") num = mixint.get_unfolded_dimension() * 5 print("DOE point nb = {}".format(num)) xt = lhs(num) yt = ftest(xt) # Surrogate sm = mixint.build_surrogate_model(KRG()) print(xt) sm.set_training_values(xt, yt) sm.train() # DOE for validation rand = mixint.build_sampling_method(Random) xv = rand(50) yv = ftest(xv) yp = sm.predict_values(xv) plt.plot(yv, yv) plt.plot(yv, yp, "o") plt.xlabel("actual") plt.ylabel("prediction") plt.show()
def test_qp_mixed_2D_INT(self): xtypes = [FLOAT, INT] xlimits = [[-10, 10], [-10, 10]] mixint = MixedIntegerContext(xtypes, xlimits) sm = mixint.build_surrogate_model(QP(print_prediction=False)) sampling = mixint.build_sampling_method(LHS, criterion="m") fun = Sphere(ndim=2) xt = sampling(10) yt = fun(xt) sm.set_training_values(xt, yt) sm.train() eq_check = True for i in range(xt.shape[0]): if abs(float(xt[i, :][1]) - int(float(xt[i, :][1]))) > 10e-8: eq_check = False self.assertTrue(eq_check)
def test_krg_mixed_3D(self): xtypes = [FLOAT, (ENUM, 3), INT] xlimits = [[-10, 10], ["blue", "red", "green"], [-10, 10]] mixint = MixedIntegerContext(xtypes, xlimits) sm = mixint.build_surrogate_model(KRG(print_prediction=False)) sampling = mixint.build_sampling_method(LHS, criterion="m") fun = Sphere(ndim=3) xt = sampling(20) yt = fun(xt) sm.set_training_values(xt, yt) sm.train() eq_check = True for i in range(xt.shape[0]): if abs(float(xt[i, :][2]) - int(float(xt[i, :][2]))) > 10e-8: eq_check = False if not (xt[i, :][1] == 0 or xt[i, :][1] == 1 or xt[i, :][1] == 2): eq_check = False self.assertTrue(eq_check)
class EGO(SurrogateBasedApplication): def _initialize(self): super(EGO, self)._initialize() declare = self.options.declare declare("fun", None, types=FunctionType, desc="Function to minimize") declare( "criterion", "EI", types=str, values=["EI", "SBO", "UCB"], desc= "criterion for next evaluation point determination: Expected Improvement, \ Surrogate-Based Optimization or Upper Confidence Bound", ) declare("n_iter", None, types=int, desc="Number of optimizer steps") declare( "n_max_optim", 20, types=int, desc="Maximum number of internal optimizations", ) declare("n_start", 20, types=int, desc="Number of optimization start points") declare( "n_parallel", 1, types=int, desc="Number of parallel samples to compute using qEI criterion", ) declare( "qEI", "KBLB", types=str, values=["KB", "KBLB", "KBUB", "KBRand", "CLmin"], desc="Approximated q-EI maximization strategy", ) declare( "evaluator", default=Evaluator(), types=Evaluator, desc= "Object used to run function fun to optimize at x points (nsamples, nxdim)", ) declare( "n_doe", None, types=int, desc= "Number of points of the initial LHS doe, only used if xdoe is not given", ) declare("xdoe", None, types=np.ndarray, desc="Initial doe inputs") declare("ydoe", None, types=np.ndarray, desc="Initial doe outputs") declare("xlimits", None, types=np.ndarray, desc="Bounds of function fun inputs") declare("verbose", False, types=bool, desc="Print computation information") declare( "enable_tunneling", False, types=bool, desc= "Enable the penalization of points that have been already evaluated in EI criterion", ) declare( "surrogate", KRG(print_global=False), types=(KRG, KPLS, KPLSK, MGP), desc="SMT kriging-based surrogate model used internaly", ) declare( "xtypes", None, types=list, desc= "x type specifications: either FLOAT for continuous, INT for integer " "or (ENUM n) for categorical doimension with n levels", ) self.options.declare( "random_state", types=(type(None), int, np.random.RandomState), desc= "Numpy RandomState object or seed number which controls random draws", ) def optimize(self, fun): """ Optimizes fun Parameters ---------- fun: function to optimize: ndarray[n, nx] or ndarray[n] -> ndarray[n, 1] Returns ------- [nx, 1]: x optimum [1, 1]: y optimum int: index of optimum in data arrays [ndoe + n_iter, nx]: coord-x data [ndoe + n_iter, 1]: coord-y data [ndoe, nx]: coord-x initial doe [ndoe, 1]: coord-y initial doe """ x_data, y_data = self._setup_optimizer(fun) n_iter = self.options["n_iter"] n_parallel = self.options["n_parallel"] for k in range(n_iter): # Virtual enrichement loop for p in range(n_parallel): # find next best x-coord point to evaluate x_et_k, success = self._find_best_point( x_data, y_data, self.options["enable_tunneling"]) if not success: self.log( "Internal optimization failed at EGO iter = {}.{}". format(k, p)) break elif success: self.log( "Internal optimization succeeded at EGO iter = {}.{}". format(k, p)) # Set temporaly the y-coord point based on the kriging prediction y_et_k = self._get_virtual_point(np.atleast_2d(x_et_k), y_data) # Update y_data with predicted value y_data = np.atleast_2d(np.append(y_data, y_et_k)).T x_data = np.atleast_2d(np.append(x_data, x_et_k, axis=0)) # Compute the real values of y_data x_to_compute = np.atleast_2d(x_data[-n_parallel:]) if self.mixint: x_to_compute = self.mixint.fold_with_enum_index(x_to_compute) y = self._evaluator.run(fun, x_to_compute) y_data[-n_parallel:] = y # Find the optimal point ind_best = np.argmin(y_data) x_opt = x_data[ind_best] y_opt = y_data[ind_best] if self.mixint: x_opt = self.mixint.fold_with_enum_index(x_opt)[0] return x_opt, y_opt, ind_best, x_data, y_data def log(self, msg): if self.options["verbose"]: print(msg) def EI(self, points, y_data, enable_tunneling=False, x_data=None): """ Expected improvement """ f_min = np.min(y_data) pred = self.gpr.predict_values(points) sig = np.sqrt(self.gpr.predict_variances(points)) args0 = (f_min - pred) / sig args1 = (f_min - pred) * norm.cdf(args0) args2 = sig * norm.pdf(args0) if sig.size == 1 and sig == 0.0: # can be use only if one point is computed return 0.0 ei = args1 + args2 # penalize the points already evaluated with tunneling if enable_tunneling: for i in range(len(points)): p = np.atleast_2d(points[i]) EIp = self.EI(p, y_data, enable_tunneling=False) for x in x_data: x = np.atleast_2d(x) # if np.abs(p-x)<1: # ei[i]=ei[i]*np.reciprocal(1+100*np.exp(-np.reciprocal(1-np.square(p-x)))) pena = (EIp - self.EI(x, y_data, enable_tunneling=False) ) / np.power(np.linalg.norm(p - x), 4) if pena > 0: ei[i] = ei[i] - pena ei[i] = max(ei[i], 0) return ei def SBO(self, point): """ Surrogate based optimization: min the surrogate model by suing the mean mu """ res = self.gpr.predict_values(point) return res def UCB(self, point): """ Upper confidence bound optimization: minimize by using mu - 3*sigma """ pred = self.gpr.predict_values(point) var = self.gpr.predict_variances(point) res = pred - 3.0 * np.sqrt(var) return res def _setup_optimizer(self, fun): """ Instanciate internal surrogate used for optimization and setup function evaluator wrt options Parameters ---------- fun: function to optimize: ndarray[n, nx] or ndarray[n] -> ndarray[n, 1] Returns ------- ndarray: initial coord-x doe ndarray: initial coord-y doe = fun(xdoe) """ # Set the model self.gpr = self.options["surrogate"] self.xlimits = self.options["xlimits"] # Handle mixed integer optimization xtypes = self.options["xtypes"] if xtypes: self.mixint = MixedIntegerContext(xtypes, self.xlimits, work_in_folded_space=False) self.gpr = self.mixint.build_surrogate_model(self.gpr) self._sampling = self.mixint.build_sampling_method( LHS, criterion="ese", random_state=self.options["random_state"]) else: self.mixint = None self._sampling = LHS( xlimits=self.xlimits, criterion="ese", random_state=self.options["random_state"], ) # Build DOE self._evaluator = self.options["evaluator"] xdoe = self.options["xdoe"] if xdoe is None: self.log("Build initial DOE with LHS") n_doe = self.options["n_doe"] x_doe = self._sampling(n_doe) else: self.log("Initial DOE given") x_doe = np.atleast_2d(xdoe) if self.mixint: x_doe = self.mixint.unfold_with_enum_mask(x_doe) ydoe = self.options["ydoe"] if ydoe is None: y_doe = self._evaluator.run(fun, x_doe) else: # to save time if y_doe is already given to EGO y_doe = ydoe return x_doe, y_doe def _find_best_point(self, x_data=None, y_data=None, enable_tunneling=False): """ Function that analyse a set of x_data and y_data and give back the more interesting point to evaluates according to the selected criterion Parameters ---------- x_data: ndarray(n_points, nx) y_data: ndarray(n_points, 1) Returns ------- ndarray(nx, 1): the next best point to evaluate boolean: success flag """ self.gpr.set_training_values(x_data, y_data) self.gpr.train() criterion = self.options["criterion"] n_start = self.options["n_start"] n_max_optim = self.options["n_max_optim"] if self.mixint: bounds = self.mixint.get_unfolded_xlimits() else: bounds = self.xlimits if criterion == "EI": self.obj_k = lambda x: -self.EI(np.atleast_2d(x), y_data, enable_tunneling, x_data) elif criterion == "SBO": self.obj_k = lambda x: self.SBO(np.atleast_2d(x)) elif criterion == "UCB": self.obj_k = lambda x: self.UCB(np.atleast_2d(x)) success = False n_optim = 1 # in order to have some success optimizations with SLSQP while not success and n_optim <= n_max_optim: opt_all = [] x_start = self._sampling(n_start) for ii in range(n_start): try: opt_all.append( minimize( lambda x: float(self.obj_k(x)), x_start[ii, :], method="SLSQP", bounds=bounds, options={"maxiter": 200}, )) except ValueError: # in case "x0 violates bound constraints" error print("warning: `x0` violates bound constraints") print("x0={}".format(x_start[ii, :])) print("bounds={}".format(bounds)) opt_all.append({"success": False}) opt_all = np.asarray(opt_all) opt_success = opt_all[[opt_i["success"] for opt_i in opt_all]] obj_success = np.array([opt_i["fun"] for opt_i in opt_success]) success = obj_success.size != 0 if not success: self.log("New start point for the internal optimization") n_optim += 1 if n_optim >= n_max_optim: # self.log("Internal optimization failed at EGO iter = {}".format(k)) return np.atleast_2d(0), False ind_min = np.argmin(obj_success) opt = opt_success[ind_min] x_et_k = np.atleast_2d(opt["x"]) return x_et_k, True def _get_virtual_point(self, x, y_data): """ Depending on the qEI attribute return a predicted value at given point x Parameters ---------- x: ndarray(1, 1) the x-coord point where to forecast the y-coord virtual point y_data: current y evaluation list only used when qEI is CLmin Returns ------- ndarray(1, 1): the so-called virtual y-coord point """ qEI = self.options["qEI"] if qEI == "CLmin": return np.min(y_data) if qEI == "KB": return self.gpr.predict_values(x) if qEI == "KBUB": conf = 3.0 if qEI == "KBLB": conf = -3.0 if qEI == "KBRand": conf = np.random.randn() pred = self.gpr.predict_values(x) var = self.gpr.predict_variances(x) return pred + conf * np.sqrt(var)
def run_ego_mixed_integer_example(): import numpy as np from smt.applications import EGO from smt.applications.mixed_integer import ( MixedIntegerContext, FLOAT, ENUM, ORD, ) import matplotlib.pyplot as plt from smt.surrogate_models import KRG from smt.sampling_methods import LHS # Regarding the interface, the function to be optimized should handle # categorical values as index values in the enumeration type specification. # For instance, here "blue" will be passed to the function as the index value 2. # This allows to keep the numpy ndarray X handling numerical values. def function_test_mixed_integer(X): # float x1 = X[:, 0] # enum 1 c1 = X[:, 1] x2 = c1 == 0 x3 = c1 == 1 x4 = c1 == 2 # enum 2 c2 = X[:, 2] x5 = c2 == 0 x6 = c2 == 1 # int i = X[:, 3] y = ((x2 + 2 * x3 + 3 * x4) * x5 * x1 + (x2 + 2 * x3 + 3 * x4) * x6 * 0.95 * x1 + i) return y n_iter = 15 xtypes = [FLOAT, (ENUM, 3), (ENUM, 2), ORD] xlimits = np.array([[-5, 5], ["red", "green", "blue"], ["square", "circle"], [0, 2]]) criterion = "EI" #'EI' or 'SBO' or 'LCB' qEI = "KB" sm = KRG(print_global=False) mixint = MixedIntegerContext(xtypes, xlimits) n_doe = 3 sampling = mixint.build_sampling_method(LHS, criterion="ese", random_state=42) xdoe = sampling(n_doe) ydoe = function_test_mixed_integer(xdoe) ego = EGO( n_iter=n_iter, criterion=criterion, xdoe=xdoe, ydoe=ydoe, xtypes=xtypes, xlimits=xlimits, surrogate=sm, qEI=qEI, random_state=42, ) x_opt, y_opt, _, _, y_data = ego.optimize( fun=function_test_mixed_integer) print("Minimum in x={} with f(x)={:.1f}".format(x_opt, float(y_opt))) print("Minimum in typed x={}".format( ego.mixint.cast_to_mixed_integer(x_opt))) min_ref = -15 mini = np.zeros(n_iter) for k in range(n_iter): mini[k] = np.log(np.abs(np.min(y_data[0:k + n_doe - 1]) - min_ref)) x_plot = np.linspace(1, n_iter + 0.5, n_iter) u = max(np.floor(max(mini)) + 1, -100) l = max(np.floor(min(mini)) - 0.2, -10) fig = plt.figure() axes = fig.add_axes([0.1, 0.1, 0.8, 0.8]) axes.plot(x_plot, mini, color="r") axes.set_ylim([l, u]) plt.title("minimum convergence plot", loc="center") plt.xlabel("number of iterations") plt.ylabel("log of the difference w.r.t the best") plt.show()