def _expdes_dist(gen, iterations, lb, ub, int_var): """Helper method for picking the best experimental design. We generate iterations designs and picks the one the maximizes the minimum distance between points. This isn't a perfect criterion, but it will help avoid rank-defficient designs such as y=x. :param lb: Lower bounds :type lb: numpy.array :param ub: Upper bounds :type ub: numpy.array :param int_var: Indices of integer variables. :type int_var: numpy.array :return: Experimental design of size num_pts x dim :rtype: numpy.ndarray """ X = None best_score = 0 for _ in range(iterations): cand = gen() # Generate a new design if all([x is not None for x in [lb, ub]]): # Map and round cand = round_vars(from_unit_box(cand, lb, ub), int_var, lb, ub) dists = cdist(cand, cand) np.fill_diagonal(dists, np.inf) # Since these are zero score = dists.min().min() if score > best_score and rank(cand) == cand.shape[1]: best_score = score X = cand.copy() if X is None: raise ValueError("No valid design found, increase num_pts?") return X
def generate_points(self, lb=None, ub=None, int_var=None): """Generate a two factorial design in the unit hypercube. You can specify lb, ub, int_var to have the design mapped to a specific domain. These inputs are ignored if one of lb or ub is None. The design is generated in [0, 1]^d in this case. :param lb: Lower bounds :type lb: numpy.array :param ub: Upper bounds :type ub: numpy.array :param int_var: Indices of integer variables. If None, [], or np.array([]) we assume all variables are continuous. :type int_var: numpy.array :return: Two factorial design in unit hypercube of size num_pts x dim :rtype: numpy.array """ if int_var is None or len(int_var) == 0: int_var = np.array([]) X = np.array(list(itertools.product([0, 1], repeat=self.dim))) if all([x is not None for x in [lb, ub]]): # Map and round X = round_vars(from_unit_box(X, lb, ub), int_var, lb, ub) return X
def expected_improvement_uniform(num_pts, opt_prob, surrogate, X, fX, Xpend=None, dtol=1e-3, ei_tol=1e-6, num_cand=None): """Maximize EI from a uniform set of points. :param num_pts: Number of points to generate :type num_pts: int :param opt_prob: Optimization problem :type opt_prob: object :param surrogate: Surrogate model object :type surrogate: object :param X: Previously evaluated points, of size n x dim :type X: numpy.array :param fX: Values at previously evaluated points, of size n x 1 :type fX: numpy.array :param Xpend: Pending evaluations :type Xpend: numpy.array :param dtol: Minimum distance between evaluated and pending points :type dtol: float :param ei_tol: Return None if we can't reach this threshold :type ei_tol: float :param num_cand: Number of candidate points :type num_cand: int :return: num_pts new points to evaluate :rtype: numpy.array of size num_pts x dim """ if num_cand is None: num_cand = 100*opt_prob.dim if Xpend is None: # cdist can't handle None arguments Xpend = np.empty([0, opt_prob.dim]) XX = np.vstack((X, Xpend)) new_points = np.zeros((num_pts, opt_prob.dim)) for i in range(num_pts): # Fix default values if num_cand is None: num_cand = 100*opt_prob.dim # Generate uniformly random candidate points cand = np.random.uniform( opt_prob.lb, opt_prob.ub, (num_cand, opt_prob.dim)) # Round integer variables cand = round_vars(cand, opt_prob.int_var, opt_prob.lb, opt_prob.ub) # Compute EI and find maximizer ei = ei_merit(X=cand, surrogate=surrogate, fX=fX, XX=XX, dtol=dtol) jj = np.argmax(ei) ei_max = ei[jj] if ei_max < ei_tol: return None # Give up new_points[i, :] = cand[jj, :].copy() XX = np.vstack((XX, cand[jj, :].copy())) return new_points
def test_round_vars(): X = np.random.rand(5, 4) cont_var = np.array([1, 3]) int_var = np.array([0, 2]) lb = np.zeros((3, )) ub = np.ones((3, )) X1 = round_vars(X, int_var, lb, ub) np.testing.assert_equal(X.shape, X1.shape) np.testing.assert_almost_equal(X1[:, int_var], np.round(X[:, int_var])) np.testing.assert_almost_equal(X1[:, cont_var], X[:, cont_var])
def obj(Y): """Round integer variables and compute LCB.""" Y = round_vars(Y.copy(), opt_prob.int_var, opt_prob.lb, opt_prob.ub) return lcb_merit(X=Y, surrogate=surrogate, fX=fX, XX=XX, dtol=dtol, kappa=kappa)
def test_round_vars(): X = np.random.rand(5, 4) cont_var = np.array([1, 3]) int_var = np.array([0, 2]) lb = np.zeros((3,)) ub = np.ones((3,)) X1 = round_vars(X, int_var, lb, ub) np.testing.assert_equal(X.shape, X1.shape) np.testing.assert_almost_equal(X1[:, int_var], np.round(X[:, int_var])) np.testing.assert_almost_equal(X1[:, cont_var], X[:, cont_var])
def candidate_uniform(num_pts, opt_prob, surrogate, X, fX, weights, Xpend=None, subset=None, dtol=1e-3, num_cand=None): """Select new evaluations from uniform candidate points. :param num_pts: Number of points to generate :type num_pts: int :param opt_prob: Optimization problem :type opt_prob: object :param surrogate: Surrogate model object :type surrogate: object :param X: Previously evaluated points, of size n x dim :type X: numpy.array :param fX: Values at previously evaluated points, of size n x 1 :type fX: numpy.array :param weights: num_pts weights in [0, 1] for merit function :type weights: list or numpy.array :param Xpend: Pending evaluations :type Xpend: numpy.array :param subset: Coordinates that should be perturbed, use None for all :type subset: list or numpy.array :param dtol: Minimum distance between evaluated and pending points :type dtol: float :param num_cand: Number of candidate points :type num_cand: int :return: The num_pts new points to evaluate :rtype: numpy.array of size num_pts x dim """ # Find best solution xbest = np.copy(X[np.argmin(fX), :]).ravel() # Fix default values if num_cand is None: num_cand = 100*opt_prob.dim if subset is None: subset = np.arange(0, opt_prob.dim) # Generate uniformly random candidate points cand = np.multiply(np.ones((num_cand, opt_prob.dim)), xbest) cand[:, subset] = np.random.uniform( opt_prob.lb[subset], opt_prob.ub[subset], (num_cand, len(subset))) # Round integer variables cand = round_vars(cand, opt_prob.int_var, opt_prob.lb, opt_prob.ub) # Make selections return weighted_distance_merit(num_pts=num_pts, surrogate=surrogate, X=X, fX=fX, Xpend=Xpend, cand=cand, dtol=dtol, weights=weights)
def candidate_dycors(num_pts, opt_prob, surrogate, X, fX, weights, prob_perturb, Xpend=None, sampling_radius=0.2, subset=None, dtol=1e-3, num_cand=None, xbest=None): """Select new evaluations using DYCORS. :param num_pts: Number of points to generate :type num_pts: int :param opt_prob: Optimization problem :type opt_prob: object :param surrogate: Surrogate model object :type surrogate: object :param X: Previously evaluated points, of size n x dim :type X: numpy.array :param fX: Values at previously evaluated points, of size n x 1 :type fX: numpy.array :param weights: num_pts weights in [0, 1] for merit function :type weights: list or numpy.array :param prob_perturb: Probability to perturb a given coordinate :type prob_perturb: list or numpy.array :param Xpend: Pending evaluations :type Xpend: numpy.array :param sampling_radius: Perturbation radius :type sampling_radius: float :param subset: Coordinates that should be perturbed, use None for all :type subset: list or numpy.array :param dtol: Minimum distance between evaluated and pending points :type dtol: float :param num_cand: Number of candidate points :type num_cand: int :param xbest: The point around which candidates are generated :type xbest: numpy.array :return: The num_pts new points to evaluate :rtype: numpy.array of size num_pts x dim """ # Find best solution if xbest is None: xbest = np.copy(X[np.argmin(fX), :]).ravel() # Fix default values if num_cand is None: num_cand = 100*opt_prob.dim if subset is None: subset = np.arange(0, opt_prob.dim) # Compute scale factors for each dimension and make sure they # are correct for integer variables (at least 1) scalefactors = sampling_radius * (opt_prob.ub - opt_prob.lb) ind = np.intersect1d(opt_prob.int_var, subset) if len(ind) > 0: scalefactors[ind] = np.maximum(scalefactors[ind], 1.0) # Generate candidate points if len(subset) == 1: # Fix when nlen is 1 ar = np.ones((num_cand, 1)) else: ar = (np.random.rand(num_cand, len(subset)) < prob_perturb) ind = np.where(np.sum(ar, axis=1) == 0)[0] ar[ind, np.random.randint(0, len(subset) - 1, size=len(ind))] = 1 cand = np.multiply(np.ones((num_cand, opt_prob.dim)), xbest) for i in subset: lower, upper, sigma = opt_prob.lb[i], opt_prob.ub[i], scalefactors[i] ind = np.where(ar[:, i] == 1)[0] cand[ind, subset[i]] = stats.truncnorm.rvs( a=(lower - xbest[i]) / sigma, b=(upper - xbest[i]) / sigma, loc=xbest[i], scale=sigma, size=len(ind)) # Round integer variables cand = round_vars(cand, opt_prob.int_var, opt_prob.lb, opt_prob.ub) # Make selections return weighted_distance_merit( num_pts=num_pts, surrogate=surrogate, X=X, fX=fX, Xpend=Xpend, cand=cand, dtol=dtol, weights=weights)
def candidate_srbf(num_pts, opt_prob, surrogate, X, fX, weights, Xpend=None, sampling_radius=0.2, subset=None, dtol=1e-3, num_cand=None): """Select new evaluations using Stochastic RBF (SRBF). :param num_pts: Number of points to generate :type num_pts: int :param opt_prob: Optimization problem :type opt_prob: object :param surrogate: Surrogate model object :type surrogate: object :param X: Previously evaluated points, of size n x dim :type X: numpy.array :param fX: Values at previously evaluated points, of size n x 1 :type fX: numpy.array :param weights: num_pts weights in [0, 1] for merit function :type weights: list or numpy.array :param Xpend: Pending evaluation, of size k x dim :type Xpend: numpy.array :param sampling_radius: Perturbation radius :type sampling_radius: float :param subset: Coordinates that should be perturbed, use None for all :type subset: list or numpy.array :param dtol: Minimum distance between evaluated and pending points :type dtol: float :param num_cand: Number of candidate points :type num_cand: int :return: The num_pts new points to evaluate :rtype: numpy.array of size num_pts x dim """ # Find best solution xbest = np.copy(X[np.argmin(fX), :]).ravel() # Fix default values if num_cand is None: num_cand = 100 * opt_prob.dim if subset is None: subset = np.arange(0, opt_prob.dim) # Compute scale factors for each dimension and make sure they # are correct for integer variables (at least 1) scalefactors = sampling_radius * (opt_prob.ub - opt_prob.lb) ind = np.intersect1d(opt_prob.int_var, subset) if len(ind) > 0: scalefactors[ind] = np.maximum(scalefactors[ind], 1.0) # Generate candidate points cand = np.multiply(np.ones((num_cand, opt_prob.dim)), xbest) for i in subset: lower, upper, sigma = opt_prob.lb[i], opt_prob.ub[i], scalefactors[i] cand[:, i] = stats.truncnorm.rvs(a=(lower - xbest[i]) / sigma, b=(upper - xbest[i]) / sigma, loc=xbest[i], scale=sigma, size=num_cand) # Round integer variables cand = round_vars(cand, opt_prob.int_var, opt_prob.lb, opt_prob.ub) # Make selections return weighted_distance_merit(num_pts=num_pts, surrogate=surrogate, X=X, fX=fX, Xpend=Xpend, cand=cand, dtol=dtol, weights=weights)
def expected_improvement_uniform(num_pts, opt_prob, surrogate, X, fX, Xpend=None, dtol=1e-3, ei_tol=1e-6, num_cand=None): """Maximize EI from a uniform set of points. :param num_pts: Number of points to generate :type num_pts: int :param opt_prob: Optimization problem :type opt_prob: object :param surrogate: Surrogate model object :type surrogate: object :param X: Previously evaluated points, of size n x dim :type X: numpy.array :param fX: Values at previously evaluated points, of size n x 1 :type fX: numpy.array :param Xpend: Pending evaluations :type Xpend: numpy.array :param dtol: Minimum distance between evaluated and pending points :type dtol: float :param ei_tol: Return None if we can't reach this threshold :type ei_tol: float :param num_cand: Number of candidate points :type num_cand: int :return: num_pts new points to evaluate :rtype: numpy.array of size num_pts x dim """ if num_cand is None: num_cand = 100 * opt_prob.dim if Xpend is None: # cdist can't handle None arguments Xpend = np.empty([0, opt_prob.dim]) XX = np.vstack((X, Xpend)) new_points = np.zeros((num_pts, opt_prob.dim)) for i in range(num_pts): # Fix default values if num_cand is None: num_cand = 100 * opt_prob.dim # Generate uniformly random candidate points cand = np.random.uniform(opt_prob.lb, opt_prob.ub, (num_cand, opt_prob.dim)) # Round integer variables cand = round_vars(cand, opt_prob.int_var, opt_prob.lb, opt_prob.ub) # Compute EI and find maximizer ei = ei_merit(X=cand, surrogate=surrogate, fX=fX, XX=XX, dtol=dtol) jj = np.argmax(ei) ei_max = ei[jj] if ei_max < ei_tol: return None # Give up new_points[i, :] = cand[jj, :].copy() XX = np.vstack((XX, cand[jj, :].copy())) return new_points
def obj(Y): """Round integer variables and compute negative EI.""" Y = round_vars(Y.copy(), opt_prob.int_var, opt_prob.lb, opt_prob.ub) ei = ei_merit(X=Y, surrogate=surrogate, fX=fX, XX=XX, dtol=dtol) return -ei # Remember that we are minimizing!!!
def candidate_uniform(num_pts, opt_prob, surrogate, X, fX, weights, Xpend=None, subset=None, dtol=1e-3, num_cand=None): """Select new evaluations from uniform candidate points. :param num_pts: Number of points to generate :type num_pts: int :param opt_prob: Optimization problem :type opt_prob: object :param surrogate: Surrogate model object :type surrogate: object :param X: Previously evaluated points, of size n x dim :type X: numpy.array :param fX: Values at previously evaluated points, of size n x 1 :type fX: numpy.array :param weights: num_pts weights in [0, 1] for merit function :type weights: list or numpy.array :param Xpend: Pending evaluations :type Xpend: numpy.array :param subset: Coordinates that should be perturbed, use None for all :type subset: list or numpy.array :param dtol: Minimum distance between evaluated and pending points :type dtol: float :param num_cand: Number of candidate points :type num_cand: int :return: The num_pts new points to evaluate :rtype: numpy.array of size num_pts x dim """ # Find best solution xbest = np.copy(X[np.argmin(fX), :]).ravel() # Fix default values if num_cand is None: num_cand = 100 * opt_prob.dim if subset is None: subset = np.arange(0, opt_prob.dim) # Generate uniformly random candidate points cand = np.multiply(np.ones((num_cand, opt_prob.dim)), xbest) cand[:, subset] = np.random.uniform(opt_prob.lb[subset], opt_prob.ub[subset], (num_cand, len(subset))) # Round integer variables cand = round_vars(cand, opt_prob.int_var, opt_prob.lb, opt_prob.ub) # Make selections return weighted_distance_merit(num_pts=num_pts, surrogate=surrogate, X=X, fX=fX, Xpend=Xpend, cand=cand, dtol=dtol, weights=weights)
def candidate_dycors(num_pts, opt_prob, surrogate, X, fX, weights, prob_perturb, Xpend=None, sampling_radius=0.2, subset=None, dtol=1e-3, num_cand=None, xbest=None): """Select new evaluations using DYCORS. :param num_pts: Number of points to generate :type num_pts: int :param opt_prob: Optimization problem :type opt_prob: object :param surrogate: Surrogate model object :type surrogate: object :param X: Previously evaluated points, of size n x dim :type X: numpy.array :param fX: Values at previously evaluated points, of size n x 1 :type fX: numpy.array :param weights: num_pts weights in [0, 1] for merit function :type weights: list or numpy.array :param prob_perturb: Probability to perturb a given coordinate :type prob_perturb: list or numpy.array :param Xpend: Pending evaluations :type Xpend: numpy.array :param sampling_radius: Perturbation radius :type sampling_radius: float :param subset: Coordinates that should be perturbed, use None for all :type subset: list or numpy.array :param dtol: Minimum distance between evaluated and pending points :type dtol: float :param num_cand: Number of candidate points :type num_cand: int :param xbest: The point around which candidates are generated :type xbest: numpy.array :return: The num_pts new points to evaluate :rtype: numpy.array of size num_pts x dim """ # Find best solution if xbest is None: xbest = np.copy(X[np.argmin(fX), :]).ravel() # Fix default values if num_cand is None: num_cand = 100 * opt_prob.dim if subset is None: subset = np.arange(0, opt_prob.dim) # Compute scale factors for each dimension and make sure they # are correct for integer variables (at least 1) scalefactors = sampling_radius * (opt_prob.ub - opt_prob.lb) ind = np.intersect1d(opt_prob.int_var, subset) if len(ind) > 0: scalefactors[ind] = np.maximum(scalefactors[ind], 1.0) # Generate candidate points if len(subset) == 1: # Fix when nlen is 1 ar = np.ones((num_cand, 1)) else: ar = (np.random.rand(num_cand, len(subset)) < prob_perturb) ind = np.where(np.sum(ar, axis=1) == 0)[0] ar[ind, np.random.randint(0, len(subset) - 1, size=len(ind))] = 1 cand = np.multiply(np.ones((num_cand, opt_prob.dim)), xbest) for i in subset: lower, upper, sigma = opt_prob.lb[i], opt_prob.ub[i], scalefactors[i] ind = np.where(ar[:, i] == 1)[0] cand[ind, subset[i]] = stats.truncnorm.rvs(a=(lower - xbest[i]) / sigma, b=(upper - xbest[i]) / sigma, loc=xbest[i], scale=sigma, size=len(ind)) # Round integer variables cand = round_vars(cand, opt_prob.int_var, opt_prob.lb, opt_prob.ub) # Make selections return weighted_distance_merit(num_pts=num_pts, surrogate=surrogate, X=X, fX=fX, Xpend=Xpend, cand=cand, dtol=dtol, weights=weights)
def candidate_srbf(num_pts, opt_prob, surrogate, X, fX, weights, Xpend=None, sampling_radius=0.2, subset=None, dtol=1e-3, num_cand=None): """Select new evaluations using Stochastic RBF (SRBF). :param num_pts: Number of points to generate :type num_pts: int :param opt_prob: Optimization problem :type opt_prob: object :param surrogate: Surrogate model object :type surrogate: object :param X: Previously evaluated points, of size n x dim :type X: numpy.array :param fX: Values at previously evaluated points, of size n x 1 :type fX: numpy.array :param weights: num_pts weights in [0, 1] for merit function :type weights: list or numpy.array :param Xpend: Pending evaluation, of size k x dim :type Xpend: numpy.array :param sampling_radius: Perturbation radius :type sampling_radius: float :param subset: Coordinates that should be perturbed, use None for all :type subset: list or numpy.array :param dtol: Minimum distance between evaluated and pending points :type dtol: float :param num_cand: Number of candidate points :type num_cand: int :return: The num_pts new points to evaluate :rtype: numpy.array of size num_pts x dim """ # Find best solution xbest = np.copy(X[np.argmin(fX), :]).ravel() # Fix default values if num_cand is None: num_cand = 100*opt_prob.dim if subset is None: subset = np.arange(0, opt_prob.dim) # Compute scale factors for each dimension and make sure they # are correct for integer variables (at least 1) scalefactors = sampling_radius * (opt_prob.ub - opt_prob.lb) ind = np.intersect1d(opt_prob.int_var, subset) if len(ind) > 0: scalefactors[ind] = np.maximum(scalefactors[ind], 1.0) # Generate candidate points cand = np.multiply(np.ones((num_cand, opt_prob.dim)), xbest) for i in subset: lower, upper, sigma = opt_prob.lb[i], opt_prob.ub[i], scalefactors[i] cand[:, i] = stats.truncnorm.rvs( a=(lower - xbest[i]) / sigma, b=(upper - xbest[i]) / sigma, loc=xbest[i], scale=sigma, size=num_cand) # Round integer variables cand = round_vars(cand, opt_prob.int_var, opt_prob.lb, opt_prob.ub) # Make selections return weighted_distance_merit( num_pts=num_pts, surrogate=surrogate, X=X, fX=fX, Xpend=Xpend, cand=cand, dtol=dtol, weights=weights)