def test_get_rbf_matrix(self): """Test basic properties of the RBF matrix (e.g. symmetry, size). Verify that the RBF matrix is symmetric and it has the correct size for all types of RBF. """ settings = RbfoptSettings() for i in range(50): dim = random.randint(1, 20) num_points = random.randint(10, 50) node_pos = np.array( [[random.uniform(-100, 100) for j in range(dim)] for k in range(num_points)]) # Possible shapes of the matrix for rbf_type in self.rbf_types: settings.rbf = rbf_type mat = ru.get_rbf_matrix(settings, dim, num_points, node_pos) self.assertIsInstance(mat, np.matrix) self.assertAlmostEqual(np.max(mat - mat.transpose()), 0.0, msg='RBF matrix is not symmetric') size = num_points + 1 if (ru.get_degree_polynomial(settings) > 0): size += dim**ru.get_degree_polynomial(settings) self.assertEqual(mat.shape, (size, size)) # Check that exception is raised for unknown RBF types settings.rbf = 'unknown' self.assertRaises(ValueError, ru.get_rbf_matrix, settings, dim, num_points, node_pos)
def test_get_degree_polynomial(self): """Verify that the degree is always between 0 and 1.""" settings = RbfoptSettings() for rbf_type in self.rbf_types: settings.rbf = rbf_type degree = ru.get_degree_polynomial(settings) self.assertTrue(degree == 0 or degree == 1)
def initialize_h_k_aux_variables(settings, instance): """Initialize auxiliary variables for the h_k model. Initialize the rbfval and mu_k_inv variables of a problem instance, using the values for for x and u_pi already given. This helps the local search by starting at a feasible point. Parameters ---------- settings : rbfopt_settings.RbfSettings Global and algorithmic settings. instance : pyomo.ConcreteModel A concrete instance of mathematical optimization model. """ assert(isinstance(settings, RbfSettings)) instance.rbfval = math.fsum(instance.lambda_h[i] * instance.u_pi[i].value for i in instance.Q) instance.mu_k_inv = ((-1)**ru.get_degree_polynomial(settings) * math.fsum(instance.Ainv[i,j] * instance.u_pi[i].value * instance.u_pi[j].value for i in instance.Q for j in instance.Q) + instance.phi_0)
def initialize_instance_variables(settings, instance): """Initialize the variables of a problem instance. Initialize the x variables of a problem instance, and set the corresponding values for the vectors :math:`(u,\pi)`. This helps the local search by starting at a feasible point. Parameters ---------- settings : rbfopt_settings.RbfSettings Global and algorithmic settings. instance : pyomo.ConcreteModel A concrete instance of mathematical optimization model. """ assert(isinstance(settings, RbfSettings)) # Obtain radial basis function rbf = ru.get_rbf_function(settings) # Initialize variables for local search for i in instance.N: instance.x[i] = random.uniform(instance.var_lower[i], instance.var_upper[i]) for j in instance.K: instance.u_pi[j] = rbf(math.sqrt(math.fsum((instance.x[i].value - instance.node[j, i])**2 for i in instance.N))) if (ru.get_degree_polynomial(settings) == 1): for j in instance.N: instance.u_pi[instance.k + j] = instance.x[j].value instance.u_pi[instance.k + instance.n] = 1.0 elif (ru.get_degree_polynomial(settings) == 0): # We use "+ 0" here to convert the symbolic parameter # instance.k into a number that can be used for indexing. instance.u_pi[instance.k + 0] = 1.0
def create_max_h_k_model(settings, n, k, var_lower, var_upper, integer_vars, node_pos, rbf_lambda, rbf_h, mat, target_val): """Create the concrete model to maximize h_k. Create the concrete model to maximize h_k, also known as the Global Search Step of the RBF method. Parameters ---------- settings : :class:`rbfopt_settings.RbfoptSettings` Global and algorithmic settings. n : int The dimension of the problem, i.e. size of the space. k : int Number of nodes, i.e. interpolation points. var_lower : 1D numpy.ndarray[float] Vector of variable lower bounds. var_upper : 1D numpy.ndarray[float] Vector of variable upper bounds. integer_vars : 1D numpy.ndarray[int] List of indices of integer variables. node_pos : 2D numpy.ndarray[float] List of coordinates of the nodes (one on each row). rbf_lambda : 1D numpy.ndarray[float] The lambda coefficients of the RBF interpolant, corresponding to the radial basis functions. List of dimension k. rbf_h : 1D numpy.ndarray[float] The h coefficients of the RBF interpolant, corresponding to the polynomial. List of dimension n+1. mat: numpy.matrix The matrix necessary for the computation. This is the inverse of the matrix [Phi P; P^T 0], see paper as cited above. Must be a numpy.matrix of dimension ((k+1) x (k+1)) target_val : float Value f* that we want to find in the unknown objective function. Returns ------- pyomo.ConcreteModel The concrete model describing the problem. """ assert (isinstance(var_lower, np.ndarray)) assert (isinstance(var_upper, np.ndarray)) assert (isinstance(integer_vars, np.ndarray)) assert (isinstance(node_pos, np.ndarray)) assert (isinstance(rbf_lambda, np.ndarray)) assert (isinstance(rbf_h, np.ndarray)) assert (len(var_lower) == n) assert (len(var_upper) == n) assert (len(rbf_lambda) == k) assert (len(rbf_h) == (n + 1)) assert (len(node_pos) == k) assert (isinstance(mat, np.matrix)) assert (mat.shape == (n + k + 1, n + k + 1)) assert (isinstance(settings, RbfoptSettings)) assert (ru.get_degree_polynomial(settings) == 1) model = ConcreteModel() # Dimension of the space model.n = Param(initialize=n) model.N = RangeSet(0, model.n - 1) # Number of interpolation nodes model.k = Param(initialize=k) model.K = RangeSet(0, model.k - 1) # Dimension of the matrix model.q = Param(initialize=(n + k + 1)) model.Q = RangeSet(0, model.q - 1) model.Qlast = RangeSet(model.q - 1, model.q - 1) # Coefficients of the RBF lambda_h_param = {} for i in range(k): lambda_h_param[i] = float(rbf_lambda[i]) for i in range(n + 1): lambda_h_param[k + i] = float(rbf_h[i]) model.lambda_h = Param(model.Q, initialize=lambda_h_param) # Coordinates of the nodes node_param = {} for i in range(k): for j in range(n): node_param[i, j] = float(node_pos[i][j]) model.node = Param(model.K, model.N, initialize=node_param) # Variable bounds var_lower_param = {} var_upper_param = {} for i in range(n): var_lower_param[i] = float(var_lower[i]) var_upper_param[i] = float(var_upper[i]) model.var_lower = Param(model.N, initialize=var_lower_param) model.var_upper = Param(model.N, initialize=var_upper_param) # Inverse of the matrix [Phi P; P^T 0]. Because the matrix is # symmetric, we only save the upper right part, while doubling the # off-diagonal elements. Ainv_param = {} for i in range(n + k + 1): for j in range(i, n + k + 1): if (abs(mat[i, j]) != 0.0): if (i == j): Ainv_param[i, j] = float(mat[i, j]) else: Ainv_param[i, j] = float(2 * mat[i, j]) model.Ainv = Param(model.Q, model.Q, initialize=Ainv_param, default=0.0) # Target value model.fstar = Param(initialize=target_val) # Value of phi at zero, necessary for shift if (settings.rbf == 'cubic' or settings.rbf == 'thin_plate_spline'): model.phi_0 = Param(initialize=0.0) # Variable: the point in the space model.x = Var(model.N, domain=Reals, bounds=_x_bounds) # Auxiliary variables: the vectors (u, \pi), # see equations (6) and (7) in the paper by Costa and Nannicini model.u_pi = Var(model.Q, domain=Reals) # Auxiliary variable: value of the rbf at a given point model.rbfval = Var(domain=Reals) # Auxiliary variable: value of the inverse of \mu_k at a given point model.mu_k_inv = Var(domain=Reals) # Objective function. model.OBJ = Objective(rule=_max_h_k_obj_expression, sense=maximize) # Constraints. See definitions below. if (settings.rbf == 'cubic'): model.UdefConstraint = Constraint(model.K, rule=_udef_cubic_constraint_rule) elif (settings.rbf == 'thin_plate_spline'): model.UdefConstraint = Constraint(model.K, rule=_udef_thinplate_constraint_rule) model.PidefConstraint = Constraint(model.N, rule=_pidef_constraint_rule) model.NonhomoConstraint = Constraint(model.Qlast, rule=_nonhomo_constraint_rule) model.RbfdefConstraint = Constraint(rule=_rbfdef_constraint_rule) model.MukdefConstraint = Constraint(rule=_mukdef_constraint_rule) # Add integer variables if necessary if (len(integer_vars)): add_integrality_constraints(model, integer_vars) return model
def create_min_rbf_model(settings, n, k, var_lower, var_upper, integer_vars, node_pos, rbf_lambda, rbf_h): """Create the concrete model to minimize the RBF. Create the concrete model to minimize the RBF. Parameters ---------- settings : :class:`rbfopt_settings.RbfoptSettings` Global and algorithmic settings. n : int The dimension of the problem, i.e. size of the space. k : int Number of nodes, i.e. interpolation points. var_lower : 1D numpy.ndarray[float] Vector of variable lower bounds. var_upper : 1D numpy.ndarray[float] Vector of variable upper bounds. integer_vars : 1D numpy.ndarray[int] List of indices of integer variables. node_pos : 2D numpy.ndarray[float] List of coordinates of the nodes (one on each row). rbf_lambda : 1D numpy.ndarray[float] The lambda coefficients of the RBF interpolant, corresponding to the radial basis functions. List of dimension k. rbf_h : 1D numpy.ndarray[float] The h coefficients of the RBF interpolant, corresponding to the polynomial. List of dimension n+1. Returns ------- pyomo.ConcreteModel The concrete model describing the problem. """ assert (isinstance(var_lower, np.ndarray)) assert (isinstance(var_upper, np.ndarray)) assert (isinstance(integer_vars, np.ndarray)) assert (isinstance(node_pos, np.ndarray)) assert (isinstance(rbf_lambda, np.ndarray)) assert (isinstance(rbf_h, np.ndarray)) assert (len(var_lower) == n) assert (len(var_upper) == n) assert (len(rbf_lambda) == k) assert (len(rbf_h) == (n + 1)) assert (len(node_pos) == k) assert (isinstance(settings, RbfoptSettings)) assert (ru.get_degree_polynomial(settings) == 1) model = ConcreteModel() # Dimension of the space model.n = Param(initialize=n) model.N = RangeSet(0, model.n - 1) # Number of interpolation nodes model.k = Param(initialize=k) model.K = RangeSet(0, model.k - 1) # Dimension of u_pi model.q = Param(initialize=(n + k + 1)) model.Q = RangeSet(0, model.q - 1) model.Qlast = RangeSet(model.q - 1, model.q - 1) # Coefficients of the RBF lambda_h_param = {} for i in range(k): lambda_h_param[i] = float(rbf_lambda[i]) for i in range(n + 1): lambda_h_param[k + i] = float(rbf_h[i]) model.lambda_h = Param(model.Q, initialize=lambda_h_param) # Coordinates of the nodes node_param = {} for i in range(k): for j in range(n): node_param[i, j] = float(node_pos[i][j]) model.node = Param(model.K, model.N, initialize=node_param) # Variable bounds var_lower_param = {} var_upper_param = {} for i in range(n): var_lower_param[i] = float(var_lower[i]) var_upper_param[i] = float(var_upper[i]) model.var_lower = Param(model.N, initialize=var_lower_param) model.var_upper = Param(model.N, initialize=var_upper_param) # Variable: the point in the space model.x = Var(model.N, domain=Reals, bounds=_x_bounds) # Auxiliary variables: the vectors (u, \pi), # see equations (6) and (7) in the paper by Costa and Nannicini model.u_pi = Var(model.Q, domain=Reals) model.OBJ = Objective(rule=_min_rbf_obj_expression, sense=minimize) # Constraints. See definitions below. if (settings.rbf == 'cubic'): model.UdefConstraint = Constraint(model.K, rule=_udef_cubic_constraint_rule) elif (settings.rbf == 'thin_plate_spline'): model.UdefConstraint = Constraint(model.K, rule=_udef_thinplate_constraint_rule) model.PidefConstraint = Constraint(model.N, rule=_pidef_constraint_rule) model.NonhomoConstraint = Constraint(model.Qlast, rule=_nonhomo_constraint_rule) # Add integer variables if necessary if (len(integer_vars)): add_integrality_constraints(model, integer_vars) return model
def create_max_one_over_mu_model(settings, n, k, var_lower, var_upper, integer_vars, node_pos, mat): """Create the concrete model to maximize 1/\mu. Create the concrete model to maximize :math: `1/\mu`, also known as the InfStep of the RBF method. See paper by Costa and Nannicini, equation (7) pag 4, and the references therein. Parameters ---------- settings : :class:`rbfopt_settings.RbfoptSettings` Global and algorithmic settings. n : int The dimension of the problem, i.e. size of the space. k : int Number of nodes, i.e. interpolation points. var_lower : 1D numpy.ndarray[float] Vector of variable lower bounds. var_upper : 1D numpy.ndarray[float] Vector of variable upper bounds. integer_vars : 1D numpy.ndarray[int] List of indices of integer variables. node_pos : 2D numpy.ndarray[float] List of coordinates of the nodes (one on each row). mat: numpy.matrix The matrix necessary for the computation. This is the inverse of the matrix [Phi P; P^T 0], see paper as cited above. Must be a numpy.matrix of dimension ((k+1) x (k+1)) Returns ------- pyomo.ConcreteModel The concrete model describing the problem. """ assert (isinstance(var_lower, np.ndarray)) assert (isinstance(var_upper, np.ndarray)) assert (isinstance(integer_vars, np.ndarray)) assert (isinstance(node_pos, np.ndarray)) assert (len(var_lower) == n) assert (len(var_upper) == n) assert (len(node_pos) == k) assert (isinstance(mat, np.matrix)) assert (mat.shape == (n + k + 1, n + k + 1)) assert (isinstance(settings, RbfoptSettings)) assert (ru.get_degree_polynomial(settings) == 1) model = ConcreteModel() # Dimension of the space model.n = Param(initialize=n) model.N = RangeSet(0, model.n - 1) # Number of interpolation nodes model.k = Param(initialize=k) model.K = RangeSet(0, model.k - 1) # Dimension of the matrix model.q = Param(initialize=(n + k + 1)) model.Q = RangeSet(0, model.q - 1) model.Qlast = RangeSet(model.q - 1, model.q - 1) # Coordinates of the nodes node_param = {} for i in range(k): for j in range(n): node_param[i, j] = float(node_pos[i][j]) model.node = Param(model.K, model.N, initialize=node_param) # Variable bounds var_lower_param = {} var_upper_param = {} for i in range(n): var_lower_param[i] = float(var_lower[i]) var_upper_param[i] = float(var_upper[i]) model.var_lower = Param(model.N, initialize=var_lower_param) model.var_upper = Param(model.N, initialize=var_upper_param) # Inverse of the matrix [Phi P; P^T 0]. Because the matrix is # symmetric, we only save the upper right part, while doubling the # off-diagonal elements. Ainv_param = {} for i in range(n + k + 1): for j in range(i, n + k + 1): if (abs(mat[i, j]) != 0.0): if (i == j): Ainv_param[i, j] = float(mat[i, j]) else: Ainv_param[i, j] = float(2 * mat[i, j]) model.Ainv = Param(model.Q, model.Q, initialize=Ainv_param, default=0.0) # Value of phi at zero, necessary for shift if (settings.rbf == 'cubic' or settings.rbf == 'thin_plate_spline'): model.phi_0 = Param(initialize=0.0) # Variable: the point in the space model.x = Var(model.N, domain=Reals, bounds=_x_bounds) # Auxiliary variables: the vectors (u, \pi), # see equations (6) and (7) in the paper by Costa and Nannicini model.u_pi = Var(model.Q, domain=Reals) # Objective function. Remember that there should be a constant # term \phi(0) at the beginning, but because this is zero in this # case we take it out. model.OBJ = Objective(rule=_max_one_over_mu_obj_expression, sense=maximize) # Constraints. See definitions below. if (settings.rbf == 'cubic'): model.UdefConstraint = Constraint(model.K, rule=_udef_cubic_constraint_rule) elif (settings.rbf == 'thin_plate_spline'): model.UdefConstraint = Constraint(model.K, rule=_udef_thinplate_constraint_rule) model.PidefConstraint = Constraint(model.N, rule=_pidef_constraint_rule) model.NonhomoConstraint = Constraint(model.Qlast, rule=_nonhomo_constraint_rule) # Add integer variables if necessary if (len(integer_vars)): add_integrality_constraints(model, integer_vars) return model
def get_noisy_rbf_coefficients(settings, n, k, Phimat, Pmat, node_val, fast_node_index, fast_node_err_bounds, init_rbf_lambda = None, init_rbf_h = None): """Obtain coefficients for the noisy RBF interpolant. Solve a quadratic problem to compute the coefficients of the RBF interpolant that minimizes bumpiness and lets all points with index in fast_node_index deviate by a specified percentage from their value. Parameters ---------- settings : rbfopt_settings.RbfSettings Global and algorithmic settings. n : int The dimension of the problem, i.e. size of the space. k : int Number of nodes, i.e. interpolation points. Phimat : numpy.matrix Matrix Phi, i.e. top left part of the standard RBF matrix. Pmat : numpy.matrix Matrix P, i.e. top right part of the standard RBF matrix. node_val : List[float] List of values of the function at the nodes. fast_node_index : List[int] List of indices of nodes whose function value should be considered variable withing the allowed range. fast_node_err_bounds : List[(float, float)] Allowed deviation from node values for nodes affected by error. This is a list of pairs (lower, upper) of the same length as fast_node_index. init_rbf_lambda : List[float] or None Initial values that should be used for the lambda coefficients of the RBF. Can be None. init_rbf_h : List[float] or None Initial values that should be used for the h coefficients of the RBF. Can be None. Returns --- (List[float], List[float]) Two vectors: lambda coefficients (for the radial basis functions), and h coefficients (for the polynomial). If initialization information was provided and was valid, then some values will always be returned. Otherwise, it will be None. Raises ------ ValueError If the type of radial basis function is not supported. RuntimeError If the solver cannot be found. """ assert(isinstance(settings, RbfSettings)) assert(len(node_val)==k) assert(isinstance(Phimat, np.matrix)) assert(isinstance(Pmat, np.matrix)) assert(len(fast_node_index)==len(fast_node_err_bounds)) assert(init_rbf_lambda is None or len(init_rbf_lambda)==k) assert(init_rbf_h is None or len(init_rbf_h)==Pmat.shape[1]) # Instantiate model if (ru.get_degree_polynomial(settings) == 1): model = rbfopt_degree1_models elif (ru.get_degree_polynomial(settings) == 0): model = rbfopt_degree0_models else: raise ValueError('RBF type ' + settings.rbf + ' not supported') instance = model.create_min_bump_model(settings, n, k, Phimat, Pmat, node_val, fast_node_index, fast_node_err_bounds) # Instantiate optimizer opt = pyomo.opt.SolverFactory(config.NLP_SOLVER_EXEC, solver_io='nl') if opt is None: raise RuntimeError('Solver ' + config.NLP_SOLVER_EXEC + ' not found') set_nlp_solver_options(opt) # Initialize instance variables with the solution provided (if # available). This should avoid any infeasibility. if (init_rbf_lambda is not None and init_rbf_h is not None): for i in range(len(init_rbf_lambda)): instance.rbf_lambda[i] = init_rbf_lambda[i] instance.slack[i] = 0.0 for i in range(len(init_rbf_h)): instance.rbf_h[i] = init_rbf_h[i] # Solve and load results try: results = opt.solve(instance, keepfiles = False, tee = settings.print_solver_output) if ((results.solver.status == pyomo.opt.SolverStatus.ok) and (results.solver.termination_condition == TerminationCondition.optimal)): # this is feasible and optimal instance.solutions.load_from(results) rbf_lambda = [instance.rbf_lambda[i].value for i in instance.K] rbf_h = [instance.rbf_h[i].value for i in instance.P] else: # If we have initialization information, return it. It is # a feasible solution. Otherwise, this will be None. rbf_lambda = init_rbf_lambda rbf_h = init_rbf_h except: # If we have initialization information, return it. It is # a feasible solution. Otherwise, this will be None. rbf_lambda = init_rbf_lambda rbf_h = init_rbf_h return (rbf_lambda, rbf_h)
def get_model_quality_estimate_clp(settings, n, k, node_pos, node_val, num_iterations): """Compute an estimate of model quality using LPs with Clp. Computes an estimate of model quality, performing cross-validation. This version does not use all interpolation nodes, but rather only the best num_iterations ones, and it uses a sequence of LPs instead of a brute-force calculation. It should be much faster than the brute-force calculation, as each LP in the sequence requires only two pivots. The solver is COIN-OR Clp. Parameters ---------- settings : rbfopt_settings.RbfSettings Global and algorithmic settings. n : int Dimension of the problem, i.e. the space where the point lives. k : int Number of nodes, i.e. interpolation points. node_pos : List[List[float]] Location of current interpolation nodes. node_val : List[float] List of values of the function at the nodes. num_iterations : int Number of nodes on which quality should be tested. Returns ------- float An estimate of the leave-one-out cross-validation error, which can be interpreted as a measure of model quality. Raises ------ RuntimeError If the LPs cannot be solved. ValueError If some settings are not supported. """ assert(isinstance(settings, RbfSettings)) assert(len(node_val)==k) assert(len(node_pos)==k) assert(num_iterations <= k) assert(cpx_available) # We cannot find a leave-one-out interpolant if the following # condition is not met. assert(k > n + 1) # Get size of polynomial part of the matrix (p) and sign of obj # function (sign) if (ru.get_degree_polynomial(settings) == 1): p = n + 1 sign = 1 elif (ru.get_degree_polynomial(settings) == 0): p = 1 sign = -1 else: raise ValueError('RBF type ' + settings.rbf + ' not supported') # Sort interpolation nodes by increasing objective function value sorted_list = sorted([(node_val[i], node_pos[i]) for i in range(k)]) # Initialize the arrays used for the cross-validation cv_node_pos = [sorted_list[i][1] for i in range(k)] cv_node_val = [sorted_list[i][0] for i in range(k)] # Compute the RBF matrix, and the two submatrices of interest Amat = ru.get_rbf_matrix(settings, n, k, cv_node_pos) Phimat = Amat[:k, :k] Pmat = Amat[:k, k:] identity = np.matrix(np.identity(k)) # Instantiate model model = CyLPModel() # Add variables: lambda, h, slacks rbf_lambda = model.addVariable('rbf_lambda', k) rbf_h = model.addVariable('rbf_h', p) slack = model.addVariable('slack', k) # Add constraints: interpolation conditions, unisolvence F = CyLPArray(cv_node_val) zeros = CyLPArray([0] * p) ones = CyLPArray([1] * p) if (p > 1): # We can use matrix form model += (Phimat * rbf_lambda + Pmat * rbf_h + identity * slack == F) model += (Pmat.transpose() * rbf_lambda == zeros) else: # Workaround for one dimensional variables: one row at a time for i in range(k): model += Phimat[i, :] * rbf_lambda + rbf_h + slack[i] == F[i] model += (Pmat.transpose() * rbf_lambda == zeros) # Add objective obj = CyLPArray([sign*val for val in node_val]) model.objective = obj * rbf_lambda # Create LP, suppress output, and set bounds lp = clp.CyClpSimplex(model) lp.logLevel = 0 infinity = lp.getCoinInfinity() for i in range(k + p): lp.setColumnLower(i, -infinity) lp.setColumnUpper(i, infinity) for i in range(k): lp.setColumnLower(k + p + i, 0.0) lp.setColumnUpper(k + p + i, 0.0) # Estimate of the model error loo_error = 0.0 for i in range(num_iterations): # Fix lambda[i] to zero lp.setColumnLower(i, 0.0) lp.setColumnUpper(i, 0.0) # Set slack[i] to unconstrained lp.setColumnLower(k + p + i, -infinity) lp.setColumnUpper(k + p + i, infinity) lp.dual(startFinishOptions = 'sx') if (not lp.getStatusCode() == 0): raise RuntimeError('Could not solve LP with Clp, status ' + lp.getStatusString()) lambda_sol = lp.primalVariableSolution['rbf_lambda'].tolist() h_sol = lp.primalVariableSolution['rbf_h'].tolist() # Compute value of the interpolant at the removed node predicted_val = ru.evaluate_rbf(settings, cv_node_pos[i], n, k, cv_node_pos, lambda_sol, h_sol) # Update leave-one-out error loo_error += abs(predicted_val - cv_node_val[i]) # Fix lambda[i] to zero lp.setColumnLower(i, -infinity) lp.setColumnUpper(i, infinity) # Set slack[i] to unconstrained lp.setColumnLower(k + p + i, 0.0) lp.setColumnUpper(k + p + i, 0.0) return loo_error/num_iterations
def maximize_one_over_mu(settings, n, k, var_lower, var_upper, node_pos, mat, integer_vars): """Compute the maximum of :math: `1/\mu`. Construct a PyOmo model to maximize :math: `1/\mu` See paper by Costa and Nannicini, equation (7) pag 4, and the references therein. Parameters ---------- settings : rbfopt_settings.RbfSettings Global and algorithmic settings. n : int The dimension of the problem, i.e. size of the space. k : int Number of nodes, i.e. interpolation points. var_lower : List[float] Vector of variable lower bounds. var_upper : List[float] Vector of variable upper bounds. node_pos : List[List[float]] List of coordinates of the nodes mat : numpy.matrix The matrix necessary for the computation. This is the inverse of the matrix [Phi P; P^T 0], see paper as cited above. Must be a square numpy.matrix of appropriate dimension. integer_vars : List[int] or None A list containing the indices of the integrality constrained variables. If None or empty list, all variables are assumed to be continuous. Returns ------- float A maximizer. It is difficult to do global optimization so typically this method returns a local maximum. Raises ------ ValueError If the type of radial basis function is not supported. RuntimeError If the solver cannot be found. """ assert(len(var_lower)==n) assert(len(var_upper)==n) assert(len(node_pos)==k) assert(isinstance(mat, np.matrix)) assert(isinstance(settings, RbfSettings)) # Determine the size of the P matrix p = ru.get_size_P_matrix(settings, n) assert(mat.shape==(k + p, k + p)) # Instantiate model if (ru.get_degree_polynomial(settings) == 1): model = rbfopt_degree1_models elif (ru.get_degree_polynomial(settings) == 0): model = rbfopt_degree0_models else: raise ValueError('RBF type ' + settings.rbf + ' not supported') instance = model.create_max_one_over_mu_model(settings, n, k, var_lower, var_upper, node_pos, mat, integer_vars) # Initialize variables for local search initialize_instance_variables(settings, instance) # Instantiate optimizer opt = pyomo.opt.SolverFactory(config.MINLP_SOLVER_EXEC, solver_io='nl') if opt is None: raise RuntimeError('Solver ' + config.MINLP_SOLVER_EXEC + ' not found') set_minlp_solver_options(opt) # Solve and load results try: results = opt.solve(instance, keepfiles = False, tee = settings.print_solver_output) if ((results.solver.status == pyomo.opt.SolverStatus.ok) and (results.solver.termination_condition == TerminationCondition.optimal)): # this is feasible and optimal instance.solutions.load_from(results) point = [instance.x[i].value for i in instance.N] ru.round_integer_vars(point, integer_vars) else: point = None except: point = None return point
def minimize_rbf(settings, n, k, var_lower, var_upper, node_pos, rbf_lambda, rbf_h, integer_vars): """Compute the minimum of the RBF interpolant. Compute the minimum of the RBF interpolant with a PyOmo model. Parameters ---------- settings : rbfopt_settings.RbfSettings Global and algorithmic settings. n : int The dimension of the problem, i.e. size of the space. k : int Number of nodes, i.e. interpolation points. var_lower : List[float] Vector of variable lower bounds. var_upper : List[float] Vector of variable upper bounds. node_pos : List[List[float]] List of coordinates of the nodes. rbf_lambda : List[float] The lambda coefficients of the RBF interpolant, corresponding to the radial basis functions. List of dimension k. rbf_h : List[float] The h coefficients of the RBF interpolant, corresponding to the polynomial. List of dimension n+1. integer_vars: List[int] or None A list containing the indices of the integrality constrained variables. If None or empty list, all variables are assumed to be continuous. Returns ------- float A minimizer. It is difficult to do global optimization so typically this method returns a local minimum. Raises ------ ValueError If the type of radial basis function is not supported. RuntimeError If the solver cannot be found. """ assert(len(var_lower)==n) assert(len(var_upper)==n) assert(len(rbf_lambda)==k) assert(len(node_pos)==k) assert(isinstance(settings, RbfSettings)) # Determine the size of the P matrix p = ru.get_size_P_matrix(settings, n) assert(len(rbf_h)==(p)) # Instantiate model if (ru.get_degree_polynomial(settings) == 1): model = rbfopt_degree1_models elif (ru.get_degree_polynomial(settings) == 0): model = rbfopt_degree0_models else: raise ValueError('RBF type ' + settings.rbf + ' not supported') instance = model.create_min_rbf_model(settings, n, k, var_lower, var_upper, node_pos, rbf_lambda, rbf_h, integer_vars) # Initialize variables for local search initialize_instance_variables(settings, instance) # Instantiate optimizer opt = pyomo.opt.SolverFactory(config.MINLP_SOLVER_EXEC, solver_io='nl') if opt is None: raise RuntimeError('Solver ' + config.MINLP_SOLVER_EXEC + ' not found') set_minlp_solver_options(opt) # Solve and load results try: results = opt.solve(instance, keepfiles = False, tee = settings.print_solver_output) if ((results.solver.status == pyomo.opt.SolverStatus.ok) and (results.solver.termination_condition == TerminationCondition.optimal)): # this is feasible and optimal instance.solutions.load_from(results) point = [instance.x[i].value for i in instance.N] ru.round_integer_vars(point, integer_vars) else: point = None except: point = None return point
def create_min_bump_model(settings, n, k, Phimat, Pmat, node_val, fast_node_index, fast_node_err_bounds): """Create a model to find RBF coefficients with min bumpiness. Create a quadratic problem to compute the coefficients of the RBF interpolant that minimizes bumpiness and lets all points with index in fast_node_index deviate by a specified percentage from their value. Parameters ---------- settings : rbfopt_settings.RbfSettings Global and algorithmic settings. n : int The dimension of the problem, i.e. size of the space. k : int Number of nodes, i.e. interpolation points. Phimat : numpy.matrix Matrix Phi, i.e. top left part of the standard RBF matrix. Pmat : numpy.matrix Matrix P, i.e. top right part of the standard RBF matrix. node_val : List[float] List of values of the function at the nodes. fast_node_index : List[int] List of indices of nodes whose function value should be considered variable withing the allowed range. fast_node_err_bounds : List[(float, float)] Allowed deviation from node values for nodes affected by error. This is a list of pairs (lower, upper) of the same length as fast_node_index. Returns ------- pyomo.ConcreteModel The concrete model describing the problem. """ assert(isinstance(settings, RbfSettings)) assert(len(node_val)==k) assert(isinstance(Phimat, np.matrix)) assert(isinstance(Pmat, np.matrix)) assert(Phimat.shape==(k,k)) assert(Pmat.shape==(k,n+1)) assert(len(fast_node_index)==len(fast_node_err_bounds)) assert(ru.get_degree_polynomial(settings)==1) model = ConcreteModel() # Dimension of the space model.n = Param(initialize=n) model.N = RangeSet(0, model.n - 1) # Dimension of P matrix model.p = Param(initialize=n+1) model.P = RangeSet(0, model.n) # Number of interpolation nodes model.k = Param(initialize=k) model.K = RangeSet(0, model.k - 1) # Node values, i.e. right hand sides of the first set of equations # in the constraints node_val_param = {} for i in range(k): node_val_param[i] = float(node_val[i]) model.node_val = Param(model.K, initialize=node_val_param) # Slack variable bounds slack_lower_param = {} slack_upper_param = {} for (pos, var_index) in enumerate(fast_node_index): slack_lower_param[var_index] = float(fast_node_err_bounds[pos][0]) slack_upper_param[var_index] = float(fast_node_err_bounds[pos][1]) model.slack_lower = Param(model.K, initialize=slack_lower_param, default=0.0) model.slack_upper = Param(model.K, initialize=slack_upper_param, default=0.0) # Phi matrix. Phi_param = {} for i in range(k): for j in range(k): if (abs(Phimat[i, j]) != 0.0): Phi_param[i, j] = float(Phimat[i, j]) model.Phi = Param(model.K, model.K, initialize=Phi_param, default=0.0) # P matrix. Pm_param = {} for i in range(k): for j in range(n+1): if (abs(Pmat[i, j]) != 0.0): Pm_param[i, j] = float(Pmat[i, j]) model.Pm = Param(model.K, model.P, initialize=Pm_param, default=0.0) # Variable: the lambda coefficients of the RBF model.rbf_lambda = Var(model.K, domain=Reals) # Variable: the h coefficients of the RBF model.rbf_h = Var(model.P, domain=Reals) # Variable: the slacks for the equality constraints model.slack = Var(model.K, domain=Reals, bounds=_slack_bounds) # Objective function. model.OBJ = Objective(rule=_min_bump_obj_expression, sense=minimize) # Constraints. See definitions below. model.IntrConstraint = Constraint(model.K, rule=_intr_constraint_rule) model.UnisConstraint = Constraint(model.P, rule=_unis_constraint_rule) return model
def create_max_h_k_model(settings, n, k, var_lower, var_upper, node_pos, rbf_lambda, rbf_h, mat, target_val, integer_vars = None): """Create the abstract model to maximize h_k. Create the abstract model to maximize h_k, also known as the Global Search Step of the RBF method. Parameters ---------- settings : rbfopt_settings.RbfSettings Global and algorithmic settings. n : int The dimension of the problem, i.e. size of the space. k : int Number of nodes, i.e. interpolation points. var_lower : List[float] Vector of variable lower bounds. var_upper : List[float] Vector of variable upper bounds. node_pos : List[List[float]] List of coordinates of the nodes. rbf_lambda : List[float] The lambda coefficients of the RBF interpolant, corresponding to the radial basis functions. List of dimension k. rbf_h : List[float] The h coefficients of the RBF interpolant, corresponding to the polynomial. List of dimension n+1. mat: numpy.matrix The matrix necessary for the computation. This is the inverse of the matrix [Phi P; P^T 0], see paper as cited above. Must be a numpy.matrix of dimension ((k+1) x (k+1)) target_val : float Value f* that we want to find in the unknown objective function. integer_vars : List[int] List of indices of integer variables. Returns ------- pyomo.ConcreteModel The concrete model describing the problem. """ assert(len(var_lower)==n) assert(len(var_upper)==n) assert(len(rbf_lambda)==k) assert(len(rbf_h)==(n+1)) assert(len(node_pos)==k) assert(isinstance(mat, np.matrix)) assert(mat.shape==(n+k+1,n+k+1)) assert(isinstance(settings, RbfSettings)) assert(ru.get_degree_polynomial(settings)==1) model = ConcreteModel() # Dimension of the space model.n = Param(initialize=n) model.N = RangeSet(0, model.n - 1) # Number of interpolation nodes model.k = Param(initialize=k) model.K = RangeSet(0, model.k - 1) # Dimension of the matrix model.q = Param(initialize=(n+k+1)) model.Q = RangeSet(0, model.q - 1) model.Qlast = RangeSet(model.q - 1, model.q - 1) # Coefficients of the RBF lambda_h_param = {} for i in range(k): lambda_h_param[i] = float(rbf_lambda[i]) for i in range(n+1): lambda_h_param[k+i] = float(rbf_h[i]) model.lambda_h = Param(model.Q, initialize=lambda_h_param) # Coordinates of the nodes node_param = {} for i in range(k): for j in range(n): node_param[i, j] = float(node_pos[i][j]) model.node = Param(model.K, model.N, initialize=node_param) # Variable bounds var_lower_param = {} var_upper_param = {} for i in range(n): var_lower_param[i] = float(var_lower[i]) var_upper_param[i] = float(var_upper[i]) model.var_lower = Param(model.N, initialize=var_lower_param) model.var_upper = Param(model.N, initialize=var_upper_param) # Inverse of the matrix [Phi P; P^T 0]. Because the matrix is # symmetric, we only save the upper right part, while doubling the # off-diagonal elements. Ainv_param = {} for i in range(n+k+1): for j in range(i, n+k+1): if (abs(mat[i, j]) != 0.0): if (i == j): Ainv_param[i, j] = float(mat[i, j]) else: Ainv_param[i, j] = float(2*mat[i, j]) model.Ainv = Param(model.Q, model.Q, initialize=Ainv_param, default=0.0) # Target value model.fstar = Param(initialize=target_val) # Value of phi at zero, necessary for shift if (settings.rbf == 'cubic' or settings.rbf == 'thin_plate_spline'): model.phi_0 = Param(initialize=0.0) # Variable: the point in the space model.x = Var(model.N, domain=Reals, bounds=_x_bounds) # Auxiliary variables: the vectors (u, \pi), # see equations (6) and (7) in the paper by Costa and Nannicini model.u_pi = Var(model.Q, domain=Reals) # Auxiliary variable: value of the rbf at a given point model.rbfval = Var(domain=Reals) # Auxiliary variable: value of the inverse of \mu_k at a given point model.mu_k_inv = Var(domain=Reals) # Objective function. model.OBJ = Objective(rule=_max_h_k_obj_expression, sense=maximize) # Constraints. See definitions below. if (settings.rbf == 'cubic'): model.UdefConstraint = Constraint(model.K, rule=_udef_cubic_constraint_rule) elif (settings.rbf == 'thin_plate_spline'): model.UdefConstraint = Constraint(model.K, rule=_udef_thinplate_constraint_rule) model.PidefConstraint = Constraint(model.N, rule=_pidef_constraint_rule) model.NonhomoConstraint = Constraint(model.Qlast, rule=_nonhomo_constraint_rule) model.RbfdefConstraint = Constraint(rule=_rbfdef_constraint_rule) model.MukdefConstraint = Constraint(rule=_mukdef_constraint_rule) # Add integer variables if necessary if (integer_vars is not None and len(integer_vars) > 0): add_integrality_constraints(model, integer_vars) return model
def create_min_rbf_model(settings, n, k, var_lower, var_upper, node_pos, rbf_lambda, rbf_h, integer_vars = None): """Create the concrete model to minimize the RBF. Create the concrete model to minimize the RBF. Parameters ---------- settings : rbfopt_settings.RbfSettings Global and algorithmic settings. n : int The dimension of the problem, i.e. size of the space. k : int Number of nodes, i.e. interpolation points. var_lower : List[float] Vector of variable lower bounds. var_upper : List[float] Vector of variable upper bounds. node_pos : List[List[float]] List of coordinates of the nodes. rbf_lambda : List[float] The lambda coefficients of the RBF interpolant, corresponding to the radial basis functions. List of dimension k. rbf_h : List[float] The h coefficients of the RBF interpolant, corresponding to the polynomial. List of dimension n+1. integer_vars : List[int] List of indices of integer variables. Returns ------- pyomo.ConcreteModel The concrete model describing the problem. """ assert(len(var_lower)==n) assert(len(var_upper)==n) assert(len(rbf_lambda)==k) assert(len(rbf_h)==(n+1)) assert(len(node_pos)==k) assert(isinstance(settings, RbfSettings)) assert(ru.get_degree_polynomial(settings)==1) model = ConcreteModel() # Dimension of the space model.n = Param(initialize=n) model.N = RangeSet(0, model.n - 1) # Number of interpolation nodes model.k = Param(initialize=k) model.K = RangeSet(0, model.k - 1) # Dimension of u_pi model.q = Param(initialize=(n+k+1)) model.Q = RangeSet(0, model.q - 1) model.Qlast = RangeSet(model.q-1, model.q-1) # Coefficients of the RBF lambda_h_param = {} for i in range(k): lambda_h_param[i] = float(rbf_lambda[i]) for i in range(n+1): lambda_h_param[k+i] = float(rbf_h[i]) model.lambda_h = Param(model.Q, initialize=lambda_h_param) # Coordinates of the nodes node_param = {} for i in range(k): for j in range(n): node_param[i, j] = float(node_pos[i][j]) model.node = Param(model.K, model.N, initialize=node_param) # Variable bounds var_lower_param = {} var_upper_param = {} for i in range(n): var_lower_param[i] = float(var_lower[i]) var_upper_param[i] = float(var_upper[i]) model.var_lower = Param(model.N, initialize=var_lower_param) model.var_upper = Param(model.N, initialize=var_upper_param) # Variable: the point in the space model.x = Var(model.N, domain=Reals, bounds=_x_bounds) # Auxiliary variables: the vectors (u, \pi), # see equations (6) and (7) in the paper by Costa and Nannicini model.u_pi = Var(model.Q, domain=Reals) model.OBJ = Objective(rule=_min_rbf_obj_expression, sense=minimize) # Constraints. See definitions below. if (settings.rbf == 'cubic'): model.UdefConstraint = Constraint(model.K, rule=_udef_cubic_constraint_rule) elif (settings.rbf == 'thin_plate_spline'): model.UdefConstraint = Constraint(model.K, rule=_udef_thinplate_constraint_rule) model.PidefConstraint = Constraint(model.N, rule=_pidef_constraint_rule) model.NonhomoConstraint = Constraint(model.Qlast, rule=_nonhomo_constraint_rule) # Add integer variables if necessary if (integer_vars is not None and len(integer_vars) > 0): add_integrality_constraints(model, integer_vars) return model
def create_max_one_over_mu_model(settings, n, k, var_lower, var_upper, node_pos, mat, integer_vars = None): """Create the concrete model to maximize 1/\mu. Create the concrete model to maximize :math: `1/\mu`, also known as the InfStep of the RBF method. See paper by Costa and Nannicini, equation (7) pag 4, and the references therein. Parameters ---------- settings : rbfopt_settings.RbfSettings Global and algorithmic settings. n : int The dimension of the problem, i.e. size of the space. k : int Number of nodes, i.e. interpolation points. var_lower : List[float] Vector of variable lower bounds. var_upper : List[float] Vector of variable upper bounds. node_pos : List[List[float]] List of coordinates of the nodes. mat: numpy.matrix The matrix necessary for the computation. This is the inverse of the matrix [Phi P; P^T 0], see paper as cited above. Must be a numpy.matrix of dimension ((k+1) x (k+1)) integer_vars : List[int] List of indices of integer variables. Returns ------- pyomo.ConcreteModel The concrete model describing the problem. """ assert(len(var_lower)==n) assert(len(var_upper)==n) assert(len(node_pos)==k) assert(isinstance(mat, np.matrix)) assert(mat.shape==(n+k+1,n+k+1)) assert(isinstance(settings, RbfSettings)) assert(ru.get_degree_polynomial(settings)==1) model = ConcreteModel() # Dimension of the space model.n = Param(initialize=n) model.N = RangeSet(0, model.n - 1) # Number of interpolation nodes model.k = Param(initialize=k) model.K = RangeSet(0, model.k - 1) # Dimension of the matrix model.q = Param(initialize=(n+k+1)) model.Q = RangeSet(0, model.q - 1) model.Qlast = RangeSet(model.q - 1, model.q - 1) # Coordinates of the nodes node_param = {} for i in range(k): for j in range(n): node_param[i, j] = float(node_pos[i][j]) model.node = Param(model.K, model.N, initialize=node_param) # Variable bounds var_lower_param = {} var_upper_param = {} for i in range(n): var_lower_param[i] = float(var_lower[i]) var_upper_param[i] = float(var_upper[i]) model.var_lower = Param(model.N, initialize=var_lower_param) model.var_upper = Param(model.N, initialize=var_upper_param) # Inverse of the matrix [Phi P; P^T 0]. Because the matrix is # symmetric, we only save the upper right part, while doubling the # off-diagonal elements. Ainv_param = {} for i in range(n+k+1): for j in range(i, n+k+1): if (abs(mat[i, j]) != 0.0): if (i == j): Ainv_param[i, j] = float(mat[i, j]) else: Ainv_param[i, j] = float(2*mat[i, j]) model.Ainv = Param(model.Q, model.Q, initialize=Ainv_param, default=0.0) # Value of phi at zero, necessary for shift if (settings.rbf == 'cubic' or settings.rbf == 'thin_plate_spline'): model.phi_0 = Param(initialize=0.0) # Variable: the point in the space model.x = Var(model.N, domain=Reals, bounds=_x_bounds) # Auxiliary variables: the vectors (u, \pi), # see equations (6) and (7) in the paper by Costa and Nannicini model.u_pi = Var(model.Q, domain=Reals) # Objective function. Remember that there should be a constant # term \phi(0) at the beginning, but because this is zero in this # case we take it out. model.OBJ = Objective(rule=_max_one_over_mu_obj_expression, sense=maximize) # Constraints. See definitions below. if (settings.rbf == 'cubic'): model.UdefConstraint = Constraint(model.K, rule=_udef_cubic_constraint_rule) elif (settings.rbf == 'thin_plate_spline'): model.UdefConstraint = Constraint(model.K, rule=u_def_thinplate_constraint_rule) model.PidefConstraint = Constraint(model.N, rule=_pidef_constraint_rule) model.NonhomoConstraint = Constraint(model.Qlast, rule=_nonhomo_constraint_rule) # Add integer variables if necessary if (integer_vars is not None and len(integer_vars) > 0): add_integrality_constraints(model, integer_vars) return model
def create_min_bump_model(settings, n, k, Phimat, Pmat, node_val, fast_node_index, fast_node_err_bounds): """Create a model to find RBF coefficients with min bumpiness. Create a quadratic problem to compute the coefficients of the RBF interpolant that minimizes bumpiness and lets all points with index in fast_node_index deviate by a specified percentage from their value. Parameters ---------- settings : :class:`rbfopt_settings.RbfoptSettings` Global and algorithmic settings. n : int The dimension of the problem, i.e. size of the space. k : int Number of nodes, i.e. interpolation points. Phimat : numpy.matrix Matrix Phi, i.e. top left part of the standard RBF matrix. Pmat : numpy.matrix Matrix P, i.e. top right part of the standard RBF matrix. node_val : 1D numpy.ndarray[float] List of values of the function at the nodes. fast_node_index : 1D numpy.ndarray[int] List of indices of nodes whose function value should be considered variable withing the allowed range. fast_node_err_bounds : List[(float, float)] Allowed deviation from node values for nodes affected by error. This is a list of pairs (lower, upper) of the same length as fast_node_index. Returns ------- pyomo.ConcreteModel The concrete model describing the problem. """ assert (isinstance(node_val, np.ndarray)) assert (isinstance(fast_node_index, np.ndarray)) assert (isinstance(settings, RbfoptSettings)) assert (len(node_val) == k) assert (isinstance(Phimat, np.matrix)) assert (isinstance(Pmat, np.matrix)) assert (Phimat.shape == (k, k)) assert (Pmat.shape == (k, n + 1)) assert (len(fast_node_index) == len(fast_node_err_bounds)) assert (ru.get_degree_polynomial(settings) == 1) model = ConcreteModel() # Dimension of the space model.n = Param(initialize=n) model.N = RangeSet(0, model.n - 1) # Dimension of P matrix model.p = Param(initialize=n + 1) model.P = RangeSet(0, model.n) # Number of interpolation nodes model.k = Param(initialize=k) model.K = RangeSet(0, model.k - 1) # Node values, i.e. right hand sides of the first set of equations # in the constraints node_val_param = {} for i in range(k): node_val_param[i] = float(node_val[i]) model.node_val = Param(model.K, initialize=node_val_param) # Slack variable bounds slack_lower_param = {} slack_upper_param = {} for (pos, var_index) in enumerate(fast_node_index): slack_lower_param[var_index] = float(fast_node_err_bounds[pos][0]) slack_upper_param[var_index] = float(fast_node_err_bounds[pos][1]) model.slack_lower = Param(model.K, initialize=slack_lower_param, default=0.0) model.slack_upper = Param(model.K, initialize=slack_upper_param, default=0.0) # Phi matrix. Phi_param = {} for i in range(k): for j in range(k): if (abs(Phimat[i, j]) != 0.0): Phi_param[i, j] = float(Phimat[i, j]) model.Phi = Param(model.K, model.K, initialize=Phi_param, default=0.0) # P matrix. Pm_param = {} for i in range(k): for j in range(n + 1): if (abs(Pmat[i, j]) != 0.0): Pm_param[i, j] = float(Pmat[i, j]) model.Pm = Param(model.K, model.P, initialize=Pm_param, default=0.0) # Variable: the lambda coefficients of the RBF model.rbf_lambda = Var(model.K, domain=Reals) # Variable: the h coefficients of the RBF model.rbf_h = Var(model.P, domain=Reals) # Variable: the slacks for the equality constraints model.slack = Var(model.K, domain=Reals, bounds=_slack_bounds) # Objective function. model.OBJ = Objective(rule=_min_bump_obj_expression, sense=minimize) # Constraints. See definitions below. model.IntrConstraint = Constraint(model.K, rule=_intr_constraint_rule) model.UnisConstraint = Constraint(model.P, rule=_unis_constraint_rule) return model
def create_min_msrsm_model(settings, n, k, var_lower, var_upper, integer_vars, node_pos, rbf_lambda, rbf_h, dist_weight, dist_min, dist_max, fmin, fmax): """Create the concrete model to optimize the MSRSM objective. Create the concreate model to minimize a weighted combination of the value of the RBF interpolant and the (negative of the) distance from the closes interpolation node. This is the Global Search Step of the MSRSM method. Parameters ---------- settings : :class:`rbfopt_settings.RbfoptSettings` Global and algorithmic settings. n : int The dimension of the problem, i.e. size of the space. k : int Number of nodes, i.e. interpolation points. var_lower : 1D numpy.ndarray[float] Vector of variable lower bounds. var_upper : 1D numpy.ndarray[float] Vector of variable upper bounds. integer_vars : 1D numpy.ndarray[int] List of indices of integer variables. node_pos : 2D numpy.ndarray[float] List of coordinates of the nodes (one on each row). rbf_lambda : 1D numpy.ndarray[float] The lambda coefficients of the RBF interpolant, corresponding to the radial basis functions. List of dimension k. rbf_h : 1D numpy.ndarray[float] The h coefficients of the RBF interpolant, corresponding to the polynomial. List of dimension n+1. dist_weight : float The weight paramater for distance and RBF interpolant value. Must be between 0 and 1. A weight of 1.0 corresponds to using solely distance, 0.0 to objective function. dist_min : float The minimum distance between two interpolation nodes. dist_max : float The maximum distance between two interpolation nodes. fmin : float The minimum value of an interpolation node. fmax : float The maximum value of an interpolation node. Returns ------- pyomo.ConcreteModel The concrete model describing the problem. """ assert (isinstance(var_lower, np.ndarray)) assert (isinstance(var_upper, np.ndarray)) assert (isinstance(integer_vars, np.ndarray)) assert (isinstance(node_pos, np.ndarray)) assert (isinstance(rbf_lambda, np.ndarray)) assert (isinstance(rbf_h, np.ndarray)) assert (len(var_lower) == n) assert (len(var_upper) == n) assert (len(rbf_lambda) == k) assert (len(rbf_h) == (n + 1)) assert (len(node_pos) == k) assert (isinstance(settings, RbfoptSettings)) assert (ru.get_degree_polynomial(settings) == 1) assert (0 <= dist_weight <= 1) assert (dist_max >= dist_min >= 0) model = ConcreteModel() # Dimension of the space model.n = Param(initialize=n) model.N = RangeSet(0, model.n - 1) # Number of interpolation nodes model.k = Param(initialize=k) model.K = RangeSet(0, model.k - 1) # Dimension of u_pi model.q = Param(initialize=(n + k + 1)) model.Q = RangeSet(0, model.q - 1) model.Qlast = RangeSet(model.q - 1, model.q - 1) # Coefficients of the RBF lambda_h_param = {} for i in range(k): lambda_h_param[i] = float(rbf_lambda[i]) for i in range(n + 1): lambda_h_param[k + i] = float(rbf_h[i]) model.lambda_h = Param(model.Q, initialize=lambda_h_param) # Coordinates of the nodes node_param = {} for i in range(k): for j in range(n): node_param[i, j] = float(node_pos[i][j]) model.node = Param(model.K, model.N, initialize=node_param) # Variable bounds var_lower_param = {} var_upper_param = {} for i in range(n): var_lower_param[i] = float(var_lower[i]) var_upper_param[i] = float(var_upper[i]) model.var_lower = Param(model.N, initialize=var_lower_param) model.var_upper = Param(model.N, initialize=var_upper_param) # Adjust parameters to avoid zero denominators in expressions if (fmax <= fmin + settings.eps_zero): fmax = fmin + 1 if (dist_max <= dist_min + settings.eps_zero): dist_min = dist_max - 1 # Minimum and maximum distance model.dist_min = Param(initialize=dist_min) model.dist_max = Param(initialize=dist_max) # Minimum and maximum of function values interpolation nodes model.fmin = Param(initialize=fmin) model.fmax = Param(initialize=fmax) # Weight of the distance and objective criteria model.dist_weight = Param(initialize=dist_weight) model.obj_weight = Param( initialize=(1.0 if settings.modified_msrsm_score else 1 - dist_weight)) # Value of phi at zero, necessary for shift if (settings.rbf == 'cubic' or settings.rbf == 'thin_plate_spline'): model.phi_0 = Param(initialize=0.0) # Variable: the point in the space model.x = Var(model.N, domain=Reals, bounds=_x_bounds) # Auxiliary variables: the vectors (u, \pi), # see equations (6) and (7) in the paper by Costa and Nannicini model.u_pi = Var(model.Q, domain=Reals) # Auxiliary variable: value of the rbf at a given point model.rbfval = Var(domain=Reals) # Auxiliary variable: value of the inverse of \mu_k at a given point model.mindistsq = Var(domain=NonNegativeReals) # Objective function. model.OBJ = Objective(rule=_min_msrsm_obj_expression, sense=minimize) # Constraints. See definitions below. if (settings.rbf == 'cubic'): model.UdefConstraint = Constraint(model.K, rule=_udef_cubic_constraint_rule) elif (settings.rbf == 'thin_plate_spline'): model.UdefConstraint = Constraint(model.K, rule=_udef_thinplate_constraint_rule) model.PidefConstraint = Constraint(model.N, rule=_pidef_constraint_rule) model.NonhomoConstraint = Constraint(model.Qlast, rule=_nonhomo_constraint_rule) model.RbfdefConstraint = Constraint(rule=_rbfdef_constraint_rule) model.MdistdefConstraint = Constraint(model.K, rule=_mdistdef_constraint_rule) # Add integer variables if necessary if (len(integer_vars)): add_integrality_constraints(model, integer_vars) return model
def get_model_quality_estimate_cpx(settings, n, k, node_pos, node_val, num_iterations): """Compute an estimate of model quality using LPs with Cplex. Computes an estimate of model quality, performing cross-validation. This version does not use all interpolation nodes, but rather only the best num_iterations ones, and it uses a sequence of LPs instead of a brute-force calculation. It should be much faster than the brute-force calculation, as each LP in the sequence requires only two pivots. The solver is IBM-ILOG Cplex. Parameters ---------- settings : rbfopt_settings.RbfSettings Global and algorithmic settings. n : int Dimension of the problem, i.e. the space where the point lives. k : int Number of nodes, i.e. interpolation points. node_pos : List[List[float]] Location of current interpolation nodes. node_val : List[float] List of values of the function at the nodes. num_iterations : int Number of nodes on which quality should be tested. Returns ------- float An estimate of the leave-one-out cross-validation error, which can be interpreted as a measure of model quality. Raises ------ RuntimeError If the LPs cannot be solved. ValueError If some settings are not supported. """ assert(isinstance(settings, RbfSettings)) assert(len(node_val)==k) assert(len(node_pos)==k) assert(num_iterations <= k) assert(cpx_available) # We cannot find a leave-one-out interpolant if the following # condition is not met. assert(k > n + 1) # Get size of polynomial part of the matrix (p) and sign of obj # function (sign) if (ru.get_degree_polynomial(settings) == 1): p = n + 1 sign = 1 elif (ru.get_degree_polynomial(settings) == 0): p = 1 sign = -1 else: raise ValueError('RBF type ' + settings.rbf + ' not supported') # Sort interpolation nodes by increasing objective function value sorted_list = sorted([(node_val[i], node_pos[i]) for i in range(k)]) # Initialize the arrays used for the cross-validation cv_node_pos = [sorted_list[i][1] for i in range(k)] cv_node_val = [sorted_list[i][0] for i in range(k)] # Compute the RBF matrix Amat = ru.get_rbf_matrix(settings, n, k, cv_node_pos) # Instantiate model and suppress output lp = cpx.Cplex() lp.set_log_stream(None) lp.set_error_stream(None) lp.set_warning_stream(None) lp.set_results_stream(None) lp.cleanup(settings.eps_zero) lp.parameters.threads.set(1) # lp.parameters.simplex.display.set(2) # Add variables: lambda, h, slacks lp.variables.add(obj = [sign*val for val in node_val], lb = [-cpx.infinity] * k, ub = [cpx.infinity] * k) lp.variables.add(lb = [-cpx.infinity] * p, ub = [cpx.infinity] * p) lp.variables.add(lb = [0] * k, ub = [0] * k) # Add constraints: interpolation conditions, unisolvence expr = [cpx.SparsePair(ind = [j for j in range(k + p)] + [k + p + i], val = [Amat[i, j] for j in range(k + p)] + [1.0]) for i in range(k)] lp.linear_constraints.add(lin_expr = expr, senses = ['E'] * k, rhs = cv_node_val) expr = [cpx.SparsePair(ind = [j for j in range(k + p)], val = [Amat[i, j] for j in range(k + p)]) for i in range(k, k + p)] lp.linear_constraints.add(lin_expr = expr, senses = ['E'] * p, rhs = [0] * p) # Estimate of the model error loo_error = 0.0 for i in range(num_iterations): # Fix lambda[i] to zero lp.variables.set_lower_bounds(i, 0.0) lp.variables.set_upper_bounds(i, 0.0) # Set slack[i] to unconstrained lp.variables.set_lower_bounds(k + p + i, -cpx.infinity) lp.variables.set_upper_bounds(k + p + i, cpx.infinity) lp.solve() if (not lp.solution.is_primal_feasible()): raise RuntimeError('Could not solve LP with Cplex') rbf_l = lp.solution.get_values(0, k - 1) rbf_h = lp.solution.get_values(k, k + p - 1) # Compute value of the interpolant at the removed node predicted_val = ru.evaluate_rbf(settings, cv_node_pos[i], n, k, cv_node_pos, rbf_l, rbf_h) # Update leave-one-out error loo_error += abs(predicted_val - cv_node_val[i]) # Fix lambda[i] to zero lp.variables.set_lower_bounds(i, -cpx.infinity) lp.variables.set_upper_bounds(i, cpx.infinity) # Set slack[i] to unconstrained lp.variables.set_lower_bounds(k + p + i, 0.0) lp.variables.set_upper_bounds(k + p + i, 0.0) return loo_error/num_iterations