1 // Copyright (C) 2007 International Business Machines and others. 2 // All Rights Reserved. 3 // This code is published under the Eclipse Public License. 4 // 5 // $Id$ 6 // 7 // Author: Andreas Waechter IBM 2007-03-30 8 9 #include "BonCutStrengthener.hpp" 10 #include "IpBlas.hpp" 11 12 namespace Bonmin 13 { 14 using namespace Ipopt; 15 CutStrengthener(SmartPtr<TNLPSolver> tnlp_solver,SmartPtr<OptionsList> options)16 CutStrengthener::CutStrengthener(SmartPtr<TNLPSolver> tnlp_solver, 17 SmartPtr<OptionsList> options) 18 : 19 tnlp_solver_(tnlp_solver) 20 { 21 options->GetIntegerValue("oa_log_level", oa_log_level_, tnlp_solver->prefix()); 22 options->GetEnumValue("cut_strengthening_type", cut_strengthening_type_, 23 tnlp_solver->prefix()); 24 options->GetEnumValue("disjunctive_cut_type", disjunctive_cut_type_, 25 tnlp_solver->prefix()); 26 27 tnlp_solver_->options()->clear(); 28 if (!tnlp_solver_->Initialize("strength.opt")) { 29 throw CoinError("CutStrengthener","CutStrengthener","Error during initialization of tnlp_solver_"); 30 } 31 tnlp_solver_->options()->SetStringValue("hessian_approximation","limited-memory"); 32 tnlp_solver_->options()->SetStringValue("mu_strategy", "adaptive"); 33 } 34 ~CutStrengthener()35 CutStrengthener::~CutStrengthener() 36 { 37 } 38 HandleOneCut(bool is_tight,TMINLP * tminlp,TMINLP2TNLP * problem,const double * minlp_lb,const double * minlp_ub,const int gindex,CoinPackedVector & cut,double & cut_lb,double & cut_ub,int n,const double * x,double infty)39 bool CutStrengthener::HandleOneCut(bool is_tight, TMINLP* tminlp, 40 TMINLP2TNLP* problem, 41 const double* minlp_lb, 42 const double* minlp_ub, 43 const int gindex, CoinPackedVector& cut, 44 double& cut_lb, double& cut_ub, 45 int n, const double* x, 46 double infty) 47 { 48 bool retval = true; 49 const int cut_nele = cut.getNumElements(); 50 const int* cut_indices = cut.getIndices(); 51 const TMINLP::VariableType* vartypes = problem->var_types(); 52 const double* cut_elements = cut.getElements(); 53 switch (disjunctive_cut_type_) { 54 case DC_None: 55 if (!is_tight) { 56 retval = StrengthenCut(tminlp, gindex, cut, n, x, minlp_lb, minlp_ub, 57 cut_lb, cut_ub); 58 } 59 break; 60 case DC_MostFractional: { 61 // First check if there is a discrete variable to do disjunction on 62 int imostfra = -1; 63 double viol = 1e-6; 64 for (int i=0; i<cut_nele; i++) { 65 const int& idx = cut_indices[i]; 66 if (idx < n && (vartypes[idx] == TMINLP::BINARY || 67 vartypes[idx] == TMINLP::INTEGER)) { 68 const double& xi = x[idx]; 69 const double this_viol = CoinMin(xi-floor(xi),ceil(xi)-xi); 70 if (this_viol > viol) { 71 imostfra = i; 72 viol = this_viol; 73 } 74 } 75 } 76 if (imostfra == -1) { 77 // No disjunction to be done 78 if (!is_tight) { 79 retval = StrengthenCut(tminlp, gindex, cut, n, x, minlp_lb, minlp_ub, 80 cut_lb, cut_ub); 81 } 82 } 83 else { 84 //Do the disjunction for imostfra 85 const int& idx = cut_indices[imostfra]; 86 const double& xi = x[idx]; 87 if (oa_log_level_>=2) { 88 printf("Doing disjunction for constr %d on x[%d] = %e\n", gindex, idx, xi); 89 } 90 const double down_xi = floor(xi); 91 double* changed_bnds = new double[n]; 92 // First the down disjunction: 93 CoinCopyN(minlp_ub, n, changed_bnds); 94 changed_bnds[idx] = down_xi; 95 double cut_lb_down = cut_lb; 96 double cut_ub_down = cut_ub; 97 retval = StrengthenCut(tminlp, gindex, cut, n, x, minlp_lb, 98 changed_bnds, cut_lb_down, cut_ub_down); 99 double cut_lb_up = cut_lb; 100 double cut_ub_up = cut_ub; 101 if (retval) { 102 CoinCopyN(minlp_lb, n, changed_bnds); 103 changed_bnds[idx] = down_xi + 1.; 104 retval = StrengthenCut(tminlp, gindex, cut, n, x, changed_bnds, 105 minlp_ub, cut_lb_up, cut_ub_up); 106 } 107 delete [] changed_bnds; 108 if (retval) { 109 // change the cut 110 double coeff_xi = cut_elements[imostfra]; 111 const double old_coeff = coeff_xi; 112 if (cut_lb <= -infty) { 113 // cut has upper bound 114 coeff_xi += cut_ub_down - cut_ub_up; 115 cut_ub = cut_ub_up + (down_xi+1.)*(cut_ub_down - cut_ub_up); 116 } 117 else { 118 // cut has lower bound 119 coeff_xi += cut_lb_down - cut_lb_up; 120 cut_lb = cut_lb_up + (down_xi+1.)*(cut_lb_down - cut_lb_up); 121 } 122 cut.setElement(imostfra, coeff_xi); 123 printf("old coeff = %e new = %e\n", old_coeff, coeff_xi); 124 } 125 } 126 } 127 break; 128 default: 129 std::cerr << "Invalid case for disjunctive_cut_type_ in CutStrengthener HandleOneCut\n"; 130 exit(-2); 131 break; 132 } 133 134 return retval; 135 } 136 ComputeCuts(OsiCuts & cs,TMINLP * tminlp,TMINLP2TNLP * problem,const int gindex,CoinPackedVector & cut,double & cut_lb,double & cut_ub,const double g_val,const double g_lb,const double g_ub,int n,const double * x,double infty)137 bool CutStrengthener::ComputeCuts(OsiCuts &cs, 138 TMINLP* tminlp, 139 TMINLP2TNLP* problem, 140 const int gindex, CoinPackedVector& cut, 141 double& cut_lb, double& cut_ub, 142 const double g_val, const double g_lb, 143 const double g_ub, 144 int n, const double* x, 145 double infty) 146 { 147 //printf("before: lb = %e ub = %e rl = %e ru = %e g = %e\n", lb[i], ub[i], rowLower[bindi], rowUpper[bindi], g[bindi]); 148 // First check if the cut is indeed away from the constraint 149 bool is_tight = false; 150 if (gindex==-1) { 151 is_tight = true; 152 } 153 else { 154 const Number tight_tol = 1e-8; 155 if (cut_lb <= -infty && g_ub - g_val <= tight_tol) { 156 is_tight = true; 157 } 158 else if (cut_ub >= infty && g_val - g_lb <= tight_tol) { 159 is_tight = true; 160 } 161 } 162 if (cut_strengthening_type_ == CS_StrengthenedGlobal || 163 cut_strengthening_type_ == CS_StrengthenedGlobal_StrengthenedLocal) { 164 const double orig_lb = cut_lb; 165 const double orig_ub = cut_ub; 166 bool retval = HandleOneCut(is_tight, tminlp, problem, 167 problem->orig_x_l(), 168 problem->orig_x_u(), gindex, cut, 169 cut_lb, cut_ub, n, x, infty); 170 if (!retval) { 171 if (oa_log_level_ >= 1) { 172 printf(" Error during strengthening of global cut for constraint %d\n", gindex); 173 } 174 } 175 else { 176 if (oa_log_level_ >=2 && (fabs(orig_lb-cut_lb)>1e-4 || 177 fabs(orig_ub-cut_ub)>1e-4)) { 178 if (orig_ub < infty) { 179 printf(" Strengthening ub of global cut for constraint %d from %e to %e\n", gindex, orig_ub, cut_ub); 180 } 181 else { 182 printf(" Strengthening lb of global cut for constraint %d from %e to %e\n", gindex, orig_lb, cut_lb); 183 } 184 } 185 } 186 } 187 if (cut_strengthening_type_ == CS_UnstrengthenedGlobal_StrengthenedLocal || 188 cut_strengthening_type_ == CS_StrengthenedGlobal_StrengthenedLocal) { 189 Number lb2 = cut_lb; 190 Number ub2 = cut_ub; 191 CoinPackedVector cut2(cut); 192 bool retval = HandleOneCut(is_tight, tminlp, problem, problem->x_l(), 193 problem->x_u(), gindex, cut2, 194 lb2, ub2, n, x, infty); 195 if (!retval) { 196 if (oa_log_level_ >= 1) { 197 printf(" Error during strengthening of local cut for constraint %d\n", gindex); 198 } 199 } 200 else { 201 const Number localCutTol = 1e-4; 202 if (fabs(lb2-cut_lb) >= localCutTol || fabs(cut_ub-ub2) >= localCutTol) { 203 if (ub2 < infty) { 204 printf(" Strengthening ub of local cut for constraint %d from %e to %e\n", gindex, cut_ub, ub2); 205 } 206 else { 207 printf(" Strengthening ub of local cut for constraint %d from %e to %e\n", gindex, cut_lb, lb2); 208 } 209 // Now we generate a new cut 210 OsiRowCut newCut2; 211 newCut2.setEffectiveness(99.99e99); 212 newCut2.setLb(lb2); 213 newCut2.setUb(ub2); 214 newCut2.setRow(cut2); 215 cs.insert(newCut2); 216 } 217 } 218 } 219 return true; 220 } 221 StrengthenCut(SmartPtr<TMINLP> tminlp,int constr_index,const CoinPackedVector & row,int n,const double * x,const double * x_l,const double * x_u,double & lb,double & ub)222 bool CutStrengthener::StrengthenCut(SmartPtr<TMINLP> tminlp, 223 int constr_index, 224 const CoinPackedVector& row, 225 int n, 226 const double* x, 227 const double* x_l, 228 const double* x_u, 229 double& lb, 230 double& ub) 231 { 232 // Get data to set up the NLP to be solved 233 Index nele_grad_gi; 234 Index* jCol = new Index[n+1]; 235 bool new_x = true; 236 if (constr_index == -1) { 237 // Objective function 238 // Compute random perturbation of point 239 double* x_rand = new double[n]; 240 for (int i=0; i<n; i++) { 241 const double radius = CoinMin(1., x_u[i]-x_l[i]); 242 const double p = CoinMax(CoinMin(x[i]-0.5*radius, x_u[i]-radius), 243 x_l[i]); 244 x_rand[i] = p + radius*CoinDrand48(); 245 } 246 Number* grad_f = new Number[n]; 247 bool retval = tminlp->eval_grad_f(n, x_rand, new_x, grad_f); 248 delete [] x_rand; 249 if (!retval) { 250 delete [] grad_f; 251 delete [] jCol; 252 return false; 253 } 254 nele_grad_gi = 0; 255 for (int i=0; i<n; i++) { 256 if (grad_f[i] != 0.) { 257 jCol[nele_grad_gi++] = i; 258 } 259 } 260 delete [] grad_f; 261 jCol[nele_grad_gi++] = n; // for the z variable 262 } 263 else { 264 if (!tminlp->eval_grad_gi(n, x, new_x, constr_index, nele_grad_gi, 265 jCol, NULL)) { 266 delete [] jCol; 267 return false; 268 } 269 } 270 271 bool lower_bound; 272 if (lb <= -COIN_DBL_MAX) { 273 assert(ub < COIN_DBL_MAX); 274 lower_bound = false; 275 } 276 else { 277 assert(ub >= COIN_DBL_MAX); 278 lower_bound = true; 279 } 280 SmartPtr<StrengtheningTNLP> stnlp = 281 new StrengtheningTNLP(tminlp, row, lower_bound, n, x, x_l, x_u, 282 constr_index, nele_grad_gi, jCol); 283 284 delete [] jCol; 285 286 TNLPSolver::ReturnStatus status = 287 tnlp_solver_->OptimizeTNLP(GetRawPtr(stnlp)); 288 289 if (status == TNLPSolver::solvedOptimal || 290 status == TNLPSolver::solvedOptimalTol) { 291 const Number tiny_move = 0e-8; 292 const Number final_bound = stnlp->StrengthenedBound(); 293 if (lower_bound) { 294 lb = final_bound - tiny_move; 295 } 296 else { 297 ub = final_bound + tiny_move; 298 } 299 } 300 else { 301 return false; 302 } 303 304 return true; 305 } 306 307 CutStrengthener::StrengtheningTNLP:: StrengtheningTNLP(SmartPtr<TMINLP> tminlp,const CoinPackedVector & cut,bool lower_bound,Index n,const Number * starting_point,const double * x_l_orig,const double * x_u_orig,Index constr_index,Index nvar_constr,const Index * jCol)308 StrengtheningTNLP(SmartPtr<TMINLP> tminlp, 309 const CoinPackedVector& cut, 310 bool lower_bound, 311 Index n, 312 const Number* starting_point, 313 const double* x_l_orig, 314 const double* x_u_orig, 315 Index constr_index, 316 Index nvar_constr /** Number of variables in constraint */, 317 const Index* jCol) 318 : 319 tminlp_(tminlp), 320 n_orig_(n), 321 constr_index_(constr_index), 322 nvar_constr_(nvar_constr), 323 lower_bound_(lower_bound), 324 have_final_bound_(false), 325 grad_f_(NULL) 326 { 327 starting_point_ = new Number[n_orig_]; 328 x_full_ = new Number[n_orig_]; 329 IpBlasDcopy(n_orig_, starting_point, 1, starting_point_, 1); 330 IpBlasDcopy(n_orig_, starting_point, 1, x_full_, 1); 331 332 obj_grad_ = new Number[nvar_constr_]; 333 x_l_ = new Number[nvar_constr_]; 334 x_u_ = new Number[nvar_constr_]; 335 const Number zero = 0.; 336 IpBlasDcopy(nvar_constr_, &zero, 0, obj_grad_, 1); 337 338 const int cut_nele = cut.getNumElements(); 339 const int* cut_indices = cut.getIndices(); 340 const double* cut_elements = cut.getElements(); 341 342 for (int i = 0; i<cut_nele; i++) { 343 const int& idx = cut_indices[i]; 344 // ToDo: This could be done more efficiently 345 Index jidx = -1; 346 for (int j=0; j<nvar_constr_; j++) { 347 if (idx == jCol[j]) { 348 jidx = j; 349 break; 350 } 351 } 352 if (jidx < 0) { 353 printf("There is an index (%d) in the cut that does not appear in the constraint.\n",idx); 354 exit(-99); 355 } 356 357 if (lower_bound) { 358 obj_grad_[jidx] = cut_elements[i]; 359 } 360 else { 361 obj_grad_[jidx] = -cut_elements[i]; 362 } 363 364 } 365 366 var_indices_ = new Index[nvar_constr_]; 367 for (int i=0; i<nvar_constr_; i++) { 368 const Index& j = jCol[i]; 369 var_indices_[i] = j; 370 if (j < n) { 371 x_l_[i] = x_l_orig[j]; 372 x_u_[i] = x_u_orig[j]; 373 } 374 else { 375 x_l_[i] = -1e100; 376 x_u_[i] = 1e100; 377 } 378 } 379 380 if (constr_index_ == -1) { 381 grad_f_ = new Number[n_orig_]; 382 } 383 } 384 ~StrengtheningTNLP()385 CutStrengthener::StrengtheningTNLP::~StrengtheningTNLP() 386 { 387 delete [] obj_grad_; 388 delete [] x_l_; 389 delete [] x_u_; 390 delete [] var_indices_; 391 delete [] starting_point_; 392 delete [] x_full_; 393 delete [] grad_f_; 394 } 395 396 bool CutStrengthener::StrengtheningTNLP:: get_nlp_info(Index & n,Index & m,Index & nnz_jac_g,Index & nnz_h_lag,IndexStyleEnum & index_style)397 get_nlp_info(Index& n, Index& m, Index& nnz_jac_g, 398 Index& nnz_h_lag, IndexStyleEnum& index_style) 399 { 400 n = nvar_constr_; 401 m = 1; 402 nnz_jac_g = nvar_constr_; 403 nnz_h_lag = 0; 404 index_style = C_STYLE; 405 406 Index n_orig; 407 Index nnz_jac_g_orig; 408 Index nnz_h_lag_orig; 409 TNLP::IndexStyleEnum index_style_orig; 410 if(!tminlp_->get_nlp_info(n_orig, m_orig_, nnz_jac_g_orig, nnz_h_lag_orig, 411 index_style_orig)) { 412 return false; 413 } 414 if (n_orig_ != n_orig) { 415 std::cerr << "Number of variables inconsistent in StrengtheningTNLP::get_nlp_info\n"; 416 return false; 417 } 418 419 return true; 420 } 421 422 bool CutStrengthener::StrengtheningTNLP:: get_bounds_info(Index n,Number * x_l,Number * x_u,Index m,Number * g_l,Number * g_u)423 get_bounds_info(Index n, Number* x_l, Number* x_u, 424 Index m, Number* g_l, Number* g_u) 425 { 426 if (constr_index_ == -1) { 427 g_l[0] = -1e100; 428 g_u[0] = 0.; 429 } 430 else { 431 Number* x_l_orig = new Number[n_orig_]; 432 Number* x_u_orig = new Number[n_orig_]; 433 Number* g_l_orig = new Number[m_orig_]; 434 Number* g_u_orig = new Number[m_orig_]; 435 436 if (!tminlp_->get_bounds_info(n_orig_, x_l_orig, x_u_orig, 437 m_orig_, g_l_orig, g_u_orig)) { 438 delete [] x_l_orig; 439 delete [] x_u_orig; 440 delete [] g_l_orig; 441 delete [] g_u_orig; 442 return false; 443 } 444 445 g_l[0] = g_l_orig[constr_index_]; 446 g_u[0] = g_u_orig[constr_index_]; 447 448 delete [] x_l_orig; 449 delete [] x_u_orig; 450 delete [] g_l_orig; 451 delete [] g_u_orig; 452 } 453 454 for (Index i=0; i<nvar_constr_; i++) { 455 x_l[i] = x_l_[i]; 456 x_u[i] = x_u_[i]; 457 } 458 459 return true; 460 } 461 462 bool CutStrengthener::StrengtheningTNLP:: get_starting_point(Index n,bool init_x,Number * x,bool init_z,Number * z_L,Number * z_U,Index m,bool init_lambda,Number * lambda)463 get_starting_point(Index n, bool init_x, Number* x, 464 bool init_z, Number* z_L, Number* z_U, 465 Index m, bool init_lambda, 466 Number* lambda) 467 { 468 assert(!init_z && !init_lambda); 469 assert(n = nvar_constr_); 470 if (init_x) { 471 if (constr_index_ == -1) { 472 for (Index i=0; i<n-1; i++) { 473 x[i] = starting_point_[var_indices_[i]]; 474 } 475 x[n-1] = 0.; 476 } 477 else { 478 for (Index i=0; i<n; i++) { 479 x[i] = starting_point_[var_indices_[i]]; 480 } 481 } 482 } 483 484 return true; 485 } 486 487 bool CutStrengthener::StrengtheningTNLP:: eval_f(Index n,const Number * x,bool new_x,Number & obj_value)488 eval_f(Index n, const Number* x, bool new_x, Number& obj_value) 489 { 490 obj_value = 0.; 491 for (Index i=0; i<n; i++) { 492 obj_value += obj_grad_[i]*x[i]; 493 } 494 return true; 495 } 496 497 bool CutStrengthener::StrengtheningTNLP:: eval_grad_f(Index n,const Number * x,bool new_x,Number * grad_f)498 eval_grad_f(Index n, const Number* x, bool new_x, Number* grad_f) 499 { 500 IpBlasDcopy(n, obj_grad_, 1, grad_f, 1); 501 return true; 502 } 503 504 bool CutStrengthener::StrengtheningTNLP:: eval_g(Index n,const Number * x,bool new_x,Index m,Number * g)505 eval_g(Index n, const Number* x, bool new_x, Index m, Number* g) 506 { 507 update_x_full(x); 508 bool retval; 509 if (constr_index_ == -1) { 510 retval = tminlp_->eval_f(n_orig_, x_full_, new_x, g[0]); 511 g[0] -= x[n-1]; 512 } 513 else { 514 retval = tminlp_->eval_gi(n_orig_, x_full_, new_x, constr_index_, g[0]); 515 } 516 return retval; 517 } 518 519 bool CutStrengthener::StrengtheningTNLP:: eval_jac_g(Index n,const Number * x,bool new_x,Index m,Index nele_jac,Index * iRow,Index * jCol,Number * values)520 eval_jac_g(Index n, const Number* x, bool new_x, 521 Index m, Index nele_jac, Index* iRow, Index *jCol, 522 Number* values) 523 { 524 bool retval = true; 525 if (iRow) { 526 DBG_ASSERT(jCol && !values); 527 for (Index i=0; i<nele_jac; i++) { 528 iRow[i] = 0; 529 jCol[i] = i; 530 } 531 } 532 else { 533 DBG_ASSERT(!iRow && values); 534 update_x_full(x); 535 if (constr_index_ == -1) { 536 retval = tminlp_->eval_grad_f(n_orig_, x_full_, new_x, grad_f_); 537 if (retval) { 538 for (Index i=0; i<n-1; i++) { 539 values[i] = grad_f_[var_indices_[i]]; 540 } 541 values[n-1] = -1.; 542 } 543 } 544 else { 545 retval = tminlp_->eval_grad_gi(n_orig_, x_full_, new_x, constr_index_, 546 nele_jac, NULL, values); 547 } 548 } 549 return retval; 550 } 551 552 bool CutStrengthener::StrengtheningTNLP:: eval_h(Index n,const Number * x,bool new_x,Number obj_factor,Index m,const Number * lambda,bool new_lambda,Index nele_hess,Index * iRow,Index * jCol,Number * values)553 eval_h(Index n, const Number* x, bool new_x, 554 Number obj_factor, Index m, const Number* lambda, 555 bool new_lambda, Index nele_hess, 556 Index* iRow, Index* jCol, Number* values) 557 { 558 std::cerr << "At the moment, StrengtheningTNLP::eval_h is not yet implemented\n"; 559 return false; 560 } 561 562 void CutStrengthener::StrengtheningTNLP:: update_x_full(const Number * x)563 update_x_full(const Number *x) 564 { 565 if (constr_index_ == -1) { 566 for (Index i=0; i<nvar_constr_-1; i++) { 567 x_full_[var_indices_[i]] = x[i]; 568 } 569 } 570 else { 571 for (Index i=0; i<nvar_constr_; i++) { 572 x_full_[var_indices_[i]] = x[i]; 573 } 574 } 575 } 576 577 void CutStrengthener::StrengtheningTNLP:: finalize_solution(SolverReturn status,Index n,const Number * x,const Number * z_L,const Number * z_U,Index m,const Number * g,const Number * lambda,Number obj_value,const IpoptData * ip_data,IpoptCalculatedQuantities * ip_cq)578 finalize_solution(SolverReturn status, 579 Index n, const Number* x, const Number* z_L, const Number* z_U, 580 Index m, const Number* g, const Number* lambda, 581 Number obj_value, 582 const IpoptData* ip_data, 583 IpoptCalculatedQuantities* ip_cq) 584 { 585 if (status == SUCCESS || status == STOP_AT_ACCEPTABLE_POINT) { 586 have_final_bound_ = true; 587 strengthened_bound_ = obj_value; 588 } 589 else { 590 have_final_bound_ = false; 591 } 592 } 593 StrengthenedBound() const594 Number CutStrengthener::StrengtheningTNLP::StrengthenedBound() const 595 { 596 DBG_ASSERT(have_final_bound_); 597 if (lower_bound_) { 598 return strengthened_bound_; 599 } 600 else { 601 return -strengthened_bound_; 602 } 603 } 604 605 606 } // namespace Bonmin 607