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