1 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
2 /*                                                                       */
3 /*    This file is part of the HiGHS linear optimization suite           */
4 /*                                                                       */
5 /*    Written and engineered 2008-2021 at the University of Edinburgh    */
6 /*                                                                       */
7 /*    Available as open-source under the MIT License                     */
8 /*                                                                       */
9 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
10 #include "mip/HighsSeparation.h"
11 
12 #include <algorithm>
13 #include <cassert>
14 #include <queue>
15 
16 #include "mip/HighsCliqueTable.h"
17 #include "mip/HighsDomain.h"
18 #include "mip/HighsImplications.h"
19 #include "mip/HighsLpRelaxation.h"
20 #include "mip/HighsMipSolverData.h"
21 
22 enum class RowType : int8_t {
23   Unusuable = -2,
24   Geq = -1,
25   Eq = 0,
26   Leq = 1,
27 };
28 
complementWithUpper(double coef,double ub,HighsCDouble & rhs)29 static double complementWithUpper(double coef, double ub, HighsCDouble& rhs) {
30   rhs -= coef * ub;
31   return -coef;
32 }
33 
complementWithLower(double coef,double lb,HighsCDouble & rhs)34 static double complementWithLower(double coef, double lb, HighsCDouble& rhs) {
35   rhs -= coef * lb;
36   return coef;
37 }
38 
separatePureBinaryKnapsackCover(const std::vector<double> & solvals,HighsCDouble coverweight,HighsCDouble lambda,std::vector<int> & cover,std::vector<int> & inds,std::vector<double> & vals,HighsCDouble & rhs,double feastol)39 static bool separatePureBinaryKnapsackCover(
40     const std::vector<double>& solvals, HighsCDouble coverweight,
41     HighsCDouble lambda, std::vector<int>& cover, std::vector<int>& inds,
42     std::vector<double>& vals, HighsCDouble& rhs, double feastol) {
43   int coversize = cover.size();
44   int rowlen = inds.size();
45   std::vector<double> S;
46   S.resize(coversize);
47   std::vector<int8_t> coverflag;
48   coverflag.resize(rowlen);
49   std::sort(cover.begin(), cover.end(),
50             [&](int a, int b) { return vals[a] > vals[b]; });
51 
52   HighsCDouble abartmp = vals[cover[0]];
53   HighsCDouble sigma = lambda;
54   for (int i = 1; i != coversize; ++i) {
55     HighsCDouble delta = abartmp - vals[cover[i]];
56     HighsCDouble kdelta = double(i) * delta;
57     if (double(kdelta) < double(sigma)) {
58       abartmp = vals[cover[i]];
59       sigma -= kdelta;
60     } else {
61       abartmp -= sigma * (1.0 / i);
62       sigma = 0.0;
63       break;
64     }
65   }
66 
67   if (double(sigma) > 0) abartmp = rhs / double(coversize);
68 
69   double abar = double(abartmp);
70 
71   HighsCDouble sum = 0.0;
72   int cplussize = 0;
73   for (int i = 0; i != coversize; ++i) {
74     sum += std::min(abar, vals[cover[i]]);
75     S[i] = double(sum);
76 
77     if (vals[cover[i]] > abar + feastol) {
78       ++cplussize;
79       coverflag[cover[i]] = 1;
80     } else
81       coverflag[cover[i]] = -1;
82   }
83   assert(std::abs(double(sum - rhs) / double(rhs)) <= 1e-14);
84   bool halfintegral = false;
85 
86   /* define the lifting function */
87   auto g = [&](double z) {
88     double hfrac = z / abar;
89     double coef = 0.0;
90 
91     int h = std::floor(hfrac + 0.5);
92     if (std::abs(hfrac - h) <= 1e-10 && h <= cplussize - 1) {
93       halfintegral = true;
94       coef = 0.5;
95     }
96 
97     h = std::max(h - 1, 0);
98     for (; h < coversize; ++h) {
99       if (z <= S[h] + feastol) break;
100     }
101 
102     return coef + h;
103   };
104 
105   rhs = coversize - 1;
106 
107   for (int i = 0; i != rowlen; ++i) {
108     if (coverflag[i] == -1) {
109       vals[i] = 1;
110     } else {
111       vals[i] = g(vals[i]);
112     }
113   }
114 
115   if (halfintegral) {
116     rhs *= 2;
117     for (int i = 0; i != rowlen; ++i) vals[i] *= 2;
118   }
119 
120   HighsCDouble viol = -rhs;
121   for (int i = 0; i != rowlen; ++i) {
122     viol += vals[i] * solvals[i];
123   }
124 
125   if (double(viol) > 1e-5) {
126     // printf("found pure 0-1 cover cut with violation %g\n", double(viol));
127     return true;
128   }
129 
130   return false;
131 }
132 
separateMixedBinaryKnapsackCover(const HighsMipSolver & mip,const std::vector<double> & solvals,HighsCDouble coverweight,HighsCDouble lambda,std::vector<int> & cover,std::vector<int> & inds,std::vector<double> & vals,HighsCDouble & rhs)133 static bool separateMixedBinaryKnapsackCover(
134     const HighsMipSolver& mip, const std::vector<double>& solvals,
135     HighsCDouble coverweight, HighsCDouble lambda, std::vector<int>& cover,
136     std::vector<int>& inds, std::vector<double>& vals, HighsCDouble& rhs) {
137   int coversize = cover.size();
138   int rowlen = inds.size();
139   std::vector<double> S;
140   S.resize(coversize);
141   std::vector<uint8_t> coverflag;
142   coverflag.resize(rowlen);
143 
144   if (coversize == 0) return false;
145 
146   for (int i = 0; i != coversize; ++i) coverflag[cover[i]] = 1;
147 
148   std::sort(cover.begin(), cover.end(),
149             [&](int a, int b) { return vals[a] > vals[b]; });
150   HighsCDouble sum = 0;
151 
152   int p = coversize;
153   for (int i = 0; i != coversize; ++i) {
154     if (vals[cover[i]] - lambda < 0) {
155       p = i;
156       break;
157     }
158     sum += vals[cover[i]];
159     S[i] = double(sum);
160   }
161   if (p == 0) return false;
162   /* define the lifting function */
163   auto phi = [&](double a) {
164     for (int i = 0; i < p; ++i) {
165       if (a <= S[i] - lambda) return double(i * lambda);
166 
167       if (a <= S[i]) return double((i + 1) * lambda + (HighsCDouble(a) - S[i]));
168     }
169 
170     return double(p * lambda + (HighsCDouble(a) - S[p - 1]));
171   };
172 
173   rhs = -lambda;
174 
175   for (int i = 0; i != rowlen; ++i) {
176     if (inds[i] >= mip.numCol() ||
177         mip.variableType(inds[i]) == HighsVarType::CONTINUOUS)
178       continue;
179 
180     if (coverflag[i]) {
181       vals[i] = std::min(vals[i], double(lambda));
182       rhs += vals[i];
183     } else {
184       vals[i] = phi(vals[i]);
185     }
186   }
187 
188   HighsCDouble viol = -rhs;
189   for (int i = 0; i != rowlen; ++i) {
190     viol += vals[i] * solvals[i];
191   }
192 
193   if (double(viol) > 1e-5) {
194     // printf("found mixed 0-1 cover cut with violation %g\n", double(viol));
195     return true;
196   }
197 
198   return false;
199 }
200 
separateMixedIntegerKnapsackCover(const HighsMipSolver & mip,const std::vector<double> & solvals,const std::vector<double> & upper,HighsCDouble coverweight,HighsCDouble lambda,std::vector<int> & cover,std::vector<int> & inds,std::vector<double> & vals,HighsCDouble & rhs)201 static bool separateMixedIntegerKnapsackCover(
202     const HighsMipSolver& mip, const std::vector<double>& solvals,
203     const std::vector<double>& upper, HighsCDouble coverweight,
204     HighsCDouble lambda, std::vector<int>& cover, std::vector<int>& inds,
205     std::vector<double>& vals, HighsCDouble& rhs) {
206   int coversize = cover.size();
207   int rowlen = inds.size();
208 
209   int l = -1;
210 
211   HighsCDouble mu;
212   for (int i = coversize - 1; i >= 0; --i) {
213     int j = cover[i];
214     mu = upper[j] * vals[j] - lambda;
215 
216     if (mu > 1e-5) {
217       l = j;
218       break;
219     }
220   }
221 
222   if (l == -1) return false;
223 
224   assert(mu > 1e-5);
225 
226   double al = vals[l];
227   double mudival = double(mu / al);
228   double eta = ceil(mudival);
229   HighsCDouble r = mu - floor(mudival) * al;
230 
231   int kmin = floor(eta - upper[l] - 0.5);
232 
233   auto phi_l = [&](double a) {
234     assert(a < 0);
235 
236     for (int k = -1; k >= kmin; --k) {
237       if (a >= k * al + r) {
238         assert(a < (k + 1) * al);
239         return double(a - (k + 1) * r);
240       }
241 
242       if (a >= k * al) {
243         assert(a < k * al + r);
244         return double(k * (al - r));
245       }
246     }
247 
248     assert(a < -lambda);
249     return double(kmin * (al - r));
250   };
251 
252   std::sort(cover.begin(), cover.end(),
253             [&](int a, int b) { return vals[a] > vals[b]; });
254 
255   HighsCDouble ulminusetaplusone = HighsCDouble(upper[l]) - eta + 1.0;
256   HighsCDouble cplusthreshold = ulminusetaplusone * al;
257 
258   std::vector<HighsCDouble> a;
259   std::vector<HighsCDouble> u;
260   std::vector<HighsCDouble> m;
261 
262   a.resize(coversize);
263   u.resize(coversize + 1);
264   m.resize(coversize + 1);
265 
266   HighsCDouble usum = 0.0;
267   HighsCDouble msum = 0.0;
268 
269   int cplussize;
270   for (cplussize = 0; cplussize != coversize; ++cplussize) {
271     int i = cover[cplussize];
272     if (vals[i] < cplusthreshold) break;
273 
274     u[cplussize] = usum;
275     m[cplussize] = msum;
276     a[cplussize] = vals[i];
277     usum += upper[i];
278     msum += upper[i] * a[cplussize];
279   }
280 
281   u[cplussize] = usum;
282   m[cplussize] = msum;
283   int kmax = floor(upper[l] - eta + 0.5);
284 
285   auto gamma_l = [&](double z) {
286     assert(z > 0);
287     for (int i = 0; i < cplussize; ++i) {
288       int upperi = upper[cover[i]];
289 
290       for (int h = 0; h <= upperi; ++h) {
291         HighsCDouble mih = m[i] + h * a[i];
292         HighsCDouble uih = u[i] + h;
293         HighsCDouble mihplusdeltai = mih + a[i] - cplusthreshold;
294         if (z <= mihplusdeltai) {
295           assert(mih <= z);
296           return double(uih * ulminusetaplusone * (al - r));
297         }
298 
299         for (int k = 0; k <= kmax; ++k) {
300           if (z <= mihplusdeltai + k * al + r) {
301             assert(mihplusdeltai + k * al <= z);
302             return double((uih * ulminusetaplusone + k) * (al - r));
303           }
304 
305           if (z <= mihplusdeltai + (k + 1) * al) {
306             assert(mihplusdeltai + k * al + r <= z);
307             return double((uih * ulminusetaplusone) * (al - r) + z - mih -
308                           a[i] + cplusthreshold - (k + 1) * r);
309           }
310         }
311       }
312     }
313 
314     for (int p = 0;; ++p) {
315       if (z <= m[cplussize] + p * al + r) {
316         assert(m[cplussize] + p * al <= z);
317         return double((u[cplussize] * ulminusetaplusone + p) * (al - r));
318       }
319 
320       if (z <= m[cplussize] + (p + 1) * al) {
321         assert(m[cplussize] + p * al + r <= z);
322         return double((u[cplussize] * ulminusetaplusone) * (al - r) + z -
323                       m[cplussize] - (p + 1) * r);
324       }
325     }
326   };
327 
328   std::vector<uint8_t> coverflag;
329   coverflag.resize(rowlen);
330   for (int i : cover) coverflag[i] = 1;
331 
332   rhs = (HighsCDouble(upper[l]) - eta) * r - lambda;
333 
334   for (int i = 0; i != rowlen; ++i) {
335     int col = inds[i];
336     if (col >= mip.numCol() ||
337         mip.variableType(col) == HighsVarType::CONTINUOUS)
338       continue;
339 
340     if (coverflag[i]) {
341       vals[i] = -phi_l(-vals[i]);
342       rhs += vals[i] * upper[i];
343     } else {
344       vals[i] = gamma_l(vals[i]);
345     }
346   }
347 
348   HighsCDouble viol = -rhs;
349   for (int i = 0; i != rowlen; ++i) {
350     viol += vals[i] * solvals[i];
351   }
352 
353   if (double(viol) > 1e-5) {
354     // printf("found mixed integer cover cut with violation %g\n",
355     // double(viol));
356     return true;
357   }
358 
359   return false;
360 }
361 
transformBaseEquation(const HighsMipSolver & mip,const HighsDomain & domain,const HighsSolution & lpsol,const int * Rindex,const double * Rvalue,int Rlen,double scale,HighsCDouble & rhs,std::vector<int8_t> & complementation,std::vector<int> & inds,std::vector<double> & vals,std::vector<double> & upper,std::vector<double> & solvals,int & nbin,int & nint,int & ncont,int & nunbndint)362 static bool transformBaseEquation(
363     const HighsMipSolver& mip, const HighsDomain& domain,
364     const HighsSolution& lpsol, const int* Rindex, const double* Rvalue,
365     int Rlen, double scale, HighsCDouble& rhs,
366     std::vector<int8_t>& complementation, std::vector<int>& inds,
367     std::vector<double>& vals, std::vector<double>& upper,
368     std::vector<double>& solvals, int& nbin, int& nint, int& ncont,
369     int& nunbndint) {
370   complementation.clear();
371   inds.clear();
372   vals.clear();
373   upper.clear();
374   solvals.clear();
375 
376   nbin = 0;
377   nint = 0;
378   ncont = 0;
379   nunbndint = 0;
380 
381   rhs *= scale;
382 
383   double minabsval = 1.0;
384   for (int j = 0; j != Rlen; ++j) {
385     double absval = std::abs(Rvalue[j]);
386     if (absval > minabsval) minabsval = absval;
387   }
388 
389   minabsval *= mip.mipdata_->feastol;
390 
391   for (int j = 0; j != Rlen; ++j) {
392     int col = Rindex[j];
393     if (col >= mip.numCol()) {
394       int row = col - mip.numCol();
395       bool rowintegral;
396       if (row < mip.numRow())
397         rowintegral = mip.mipdata_->rowintegral[row];
398       else
399         rowintegral = mip.mipdata_->cutpool.cutIsIntegral(
400             mip.mipdata_->lp.getCutIndex(row));
401       double val = Rvalue[j] * scale;
402       if (false && rowintegral) {
403         // todo
404       } else {
405         if (val > 0.0) continue;
406 
407         inds.push_back(col);
408         upper.push_back(HIGHS_CONST_INF);
409         vals.push_back(val);
410         complementation.push_back(0);
411         solvals.push_back(0);
412 
413         ++ncont;
414       }
415 
416       continue;
417     } else if (domain.colLower_[col] == -HIGHS_CONST_INF &&
418                domain.colUpper_[col] == HIGHS_CONST_INF) {
419       return false;
420     }
421 
422     double val = Rvalue[j] * scale;
423     if (domain.colLower_[col] == domain.colUpper_[col]) {
424       rhs -= val * domain.colLower_[col];
425       continue;
426     }
427 
428     if (std::abs(val) < minabsval) {
429       if (val > 0) {
430         if (domain.colLower_[col] == -HIGHS_CONST_INF) return false;
431 
432         rhs -= val * domain.colLower_[col];
433       } else {
434         if (domain.colUpper_[col] == HIGHS_CONST_INF) return false;
435 
436         rhs -= val * domain.colUpper_[col];
437       }
438       continue;
439     }
440 
441     int8_t c = 0;
442     double ub = HIGHS_CONST_INF;
443     double solval = lpsol.col_value[col];
444 
445     if (mip.variableType(col) != HighsVarType::CONTINUOUS) {
446       if (domain.colLower_[col] != -HIGHS_CONST_INF &&
447           domain.colUpper_[col] != HIGHS_CONST_INF)
448         ub = floor(domain.colUpper_[col] - domain.colLower_[col] + 0.5);
449       // complement integer variables to have a positive coefficient
450       if (domain.colUpper_[col] != HIGHS_CONST_INF && val < 0) {
451         rhs -= val * domain.colUpper_[col];
452         c = -1;
453         val = -val;
454         solval = domain.colUpper_[col] - solval;
455       } else if (domain.colLower_[col] != 0.0 &&
456                  domain.colLower_[col] != -HIGHS_CONST_INF) {
457         c = 1;
458         rhs -= val * domain.colLower_[col];
459         solval = solval - domain.colLower_[col];
460       }
461 
462       if (ub == 1.0)
463         ++nbin;
464       else if (ub == HIGHS_CONST_INF)
465         ++nunbndint;
466       else
467         ++nint;
468     } else {
469       if (domain.colLower_[col] == -HIGHS_CONST_INF) {
470         rhs -= val * domain.colUpper_[col];
471         c = -1;
472         val = -val;
473         solval = domain.colUpper_[col] - solval;
474       } else if (domain.colUpper_[col] == HIGHS_CONST_INF) {
475         if (domain.colLower_[col] != 0.0) {
476           c = 1;
477           rhs -= val * domain.colLower_[col];
478           solval = solval - domain.colLower_[col];
479         }
480       } else {
481         ub = domain.colUpper_[col] - domain.colLower_[col];
482         double bounddist = (lpsol.col_value[col] - domain.colLower_[col]) / ub;
483         if (bounddist < 0.5) {
484           if (domain.colLower_[col] != 0.0) {
485             c = 1;
486             rhs -= val * domain.colLower_[col];
487             solval = solval - domain.colLower_[col];
488           }
489         } else {
490           rhs -= val * domain.colUpper_[col];
491           c = -1;
492           val = -val;
493           solval = domain.colUpper_[col] - solval;
494         }
495       }
496 
497       if (val > 0.0) continue;
498 
499       ++ncont;
500     }
501 
502     inds.push_back(col);
503     upper.push_back(ub);
504     vals.push_back(val);
505     complementation.push_back(c);
506     solvals.push_back(solval);
507   }
508 
509   // on the inequality relaxed from the base equality we perform coefficient
510   // tightening before the cut generation
511   int len = inds.size();
512   HighsCDouble maxact = 0.0;
513   bool unbnd = false;
514   for (int i = 0; i != len; ++i) {
515     if (upper[i] == HIGHS_CONST_INF) {
516       unbnd = true;
517       break;
518     }
519     if (vals[i] > 0) maxact += vals[i] * upper[i];
520   }
521 
522   HighsCDouble maxabscoef = maxact - rhs;
523   if (!unbnd && maxabscoef > mip.mipdata_->feastol) {
524     int ntightened = 0;
525     for (int i = 0; i != len; ++i) {
526       if (inds[i] >= mip.numCol() ||
527           mip.variableType(inds[i]) == HighsVarType::CONTINUOUS)
528         continue;
529 
530       if (vals[i] > maxabscoef) {
531         HighsCDouble delta = vals[i] - maxabscoef;
532         rhs -= delta * upper[i];
533         vals[i] = double(maxabscoef);
534         ++ntightened;
535       }
536     }
537   }
538 
539   /* relax the right hand side a little to ensure numerical safety */
540   rhs += HIGHS_CONST_TINY;
541   rhs *= (1.0 + std::copysign(HIGHS_CONST_TINY, double(rhs)));
542 
543   rhs.renormalize();
544   return true;
545 }
546 
determineCover(const HighsMipSolver & mip,std::vector<int> & inds,const std::vector<double> & vals,const std::vector<double> & upper,const std::vector<double> & solvals,const HighsCDouble & rhs,std::vector<int> & cover,HighsCDouble & coverweight,HighsCDouble & lambda)547 static bool determineCover(const HighsMipSolver& mip, std::vector<int>& inds,
548                            const std::vector<double>& vals,
549                            const std::vector<double>& upper,
550                            const std::vector<double>& solvals,
551                            const HighsCDouble& rhs, std::vector<int>& cover,
552                            HighsCDouble& coverweight, HighsCDouble& lambda) {
553   cover.clear();
554   int len = inds.size();
555   for (int j = 0; j != len; ++j) {
556     if (inds[j] >= mip.numCol()) continue;
557 
558     if (solvals[j] > mip.mipdata_->feastol &&
559         mip.variableType(inds[j]) != HighsVarType::CONTINUOUS) {
560       cover.push_back(j);
561     }
562   }
563 
564   if (rhs <= 1e-5) return false;
565 
566   std::sort(cover.begin(), cover.end(), [&](int a, int b) {
567     if (solvals[a] * upper[b] > solvals[b] * upper[a]) return true;
568 
569     if (solvals[a] * upper[b] < solvals[b] * upper[a]) return false;
570 
571     if (vals[a] * upper[a] > vals[b] * upper[b]) return true;
572 
573     return false;
574   });
575 
576   coverweight = 0.0;
577   int coversize = 0;
578   double maxcontribution = 0;
579   for (int j : cover) {
580     double lambda = double(coverweight - rhs);
581     if (lambda > 1e-5) break;
582 
583     double contribution = vals[j] * upper[j];
584     coverweight += contribution;
585     ++coversize;
586     maxcontribution = std::max(contribution, maxcontribution);
587   }
588 
589   if (coversize == 0) return false;
590 
591   cover.resize(coversize);
592 
593   coverweight.renormalize();
594   lambda = coverweight - rhs;
595 
596   if (lambda <= 1e-5) return false;
597 
598   assert(lambda > 1e-5);
599 
600   if (maxcontribution <= lambda + 1e-5) {
601     for (int j = coversize - 2; j >= 0; --j) {
602       int i = cover[j];
603       double contribution = vals[i] * upper[i];
604       if (contribution < lambda - 1e-5) {
605         coverweight -= contribution;
606         lambda -= contribution;
607 
608         assert(lambda > 1e-5);
609         assert(coverweight > rhs);
610 
611         cover.erase(cover.begin() + j);
612         return true;
613       }
614     }
615   }
616 
617   return true;
618 }
619 
cmirCutGenerationHeuristic(const HighsMipSolver & mip,const std::vector<double> & upper,HighsCDouble & efficacy,std::vector<double> & solvals,std::vector<int8_t> & complementation,std::vector<int> & inds,std::vector<double> & vals,HighsCDouble & rhs)620 bool cmirCutGenerationHeuristic(const HighsMipSolver& mip,
621                                 const std::vector<double>& upper,
622                                 HighsCDouble& efficacy,
623                                 std::vector<double>& solvals,
624                                 std::vector<int8_t>& complementation,
625                                 std::vector<int>& inds,
626                                 std::vector<double>& vals, HighsCDouble& rhs) {
627   int len = inds.size();
628 
629   /* first complement every variable to its closest bound */
630   for (int i = 0; i != len; ++i) {
631     if (upper[i] == HIGHS_CONST_INF) continue;
632     if (upper[i] - solvals[i] < solvals[i]) {
633       complementation[i] = -complementation[i];
634       rhs -= upper[i] * vals[i];
635       vals[i] = -vals[i];
636       solvals[i] = upper[i] - solvals[i];
637     }
638   }
639 
640   std::vector<double> deltas;
641 
642   HighsCDouble continuouscontribution = 0.0;
643   HighsCDouble continuoussqrnorm = 0.0;
644   std::vector<int> integerinds;
645   integerinds.reserve(inds.size());
646   double maxabsdelta = 0.0;
647 
648   for (int i = 0; i != len; ++i) {
649     if (inds[i] < mip.numCol() &&
650         mip.variableType(inds[i]) != HighsVarType::CONTINUOUS) {
651       integerinds.push_back(i);
652       if (solvals[i] > mip.mipdata_->feastol) {
653         double delta = std::abs(vals[i]);
654         if (delta <= 1e-4 || delta >= 1e4) continue;
655         maxabsdelta = std::max(maxabsdelta, delta);
656         deltas.push_back(delta);
657       }
658     } else {
659       continuouscontribution += vals[i] * solvals[i];
660       continuoussqrnorm += vals[i] * vals[i];
661     }
662   }
663 
664   if (maxabsdelta + 1.0 > 1e-4 && maxabsdelta + 1.0 < 1e4)
665     deltas.push_back(maxabsdelta + 1.0);
666   deltas.push_back(1.0);
667 
668   if (deltas.empty()) return false;
669 
670   std::sort(deltas.begin(), deltas.end());
671   double curdelta = deltas[0];
672   for (size_t i = 1; i < deltas.size(); ++i) {
673     if (deltas[i] - curdelta <= mip.mipdata_->feastol)
674       deltas[i] = 0.0;
675     else
676       curdelta = deltas[i];
677   }
678 
679   deltas.erase(std::remove(deltas.begin(), deltas.end(), 0.0), deltas.end());
680   double bestdelta = -1;
681   double bestefficacy = 0.0;
682 
683   for (double delta : deltas) {
684     HighsCDouble scale = 1.0 / HighsCDouble(delta);
685     HighsCDouble scalrhs = rhs * scale;
686     double downrhs = std::floor(double(scalrhs));
687 
688     HighsCDouble f0 = scalrhs - downrhs;
689     if (f0 < 0.01 || f0 > 0.99) continue;
690     HighsCDouble oneoveroneminusf0 = 1.0 / (1.0 - f0);
691     if (double(oneoveroneminusf0) * double(scale) > 1e4) continue;
692 
693     HighsCDouble sqrnorm = scale * scale * continuoussqrnorm;
694     HighsCDouble viol = continuouscontribution * oneoveroneminusf0 - scalrhs;
695 
696     for (int j : integerinds) {
697       HighsCDouble scalaj = vals[j] * scale;
698       double downaj = std::floor(double(scalaj));
699       HighsCDouble fj = scalaj - downaj;
700       double aj;
701       if (fj > f0)
702         aj = double(downaj + fj - f0);
703       else
704         aj = downaj;
705 
706       viol += aj * solvals[j];
707       sqrnorm += aj * aj;
708     }
709 
710     double efficacy = double(viol / sqrt(sqrnorm));
711     if (efficacy > bestefficacy) {
712       bestdelta = delta;
713       bestefficacy = efficacy;
714     }
715   }
716 
717   if (bestdelta == -1) return false;
718 
719   /* try if multiplying best delta by 2 4 or 8 gives a better efficacy */
720   for (int k = 1; k <= 3; ++k) {
721     double delta = bestdelta * (1 << k);
722     if (delta <= 1e-4 || delta >= 1e4) continue;
723     HighsCDouble scale = 1.0 / HighsCDouble(delta);
724     HighsCDouble scalrhs = rhs * scale;
725     double downrhs = std::floor(double(scalrhs));
726     HighsCDouble f0 = scalrhs - downrhs;
727     if (f0 < 0.01 || f0 > 0.99) continue;
728 
729     HighsCDouble oneoveroneminusf0 = 1.0 / (1.0 - f0);
730     if (double(oneoveroneminusf0) * double(scale) > 1e4) continue;
731 
732     HighsCDouble sqrnorm = scale * scale * continuoussqrnorm;
733     HighsCDouble viol = continuouscontribution * oneoveroneminusf0 - scalrhs;
734 
735     for (int j : integerinds) {
736       HighsCDouble scalaj = vals[j] * scale;
737       double downaj = std::floor(double(scalaj));
738       HighsCDouble fj = scalaj - downaj;
739       double aj;
740       if (fj > f0)
741         aj = double(downaj + fj - f0);
742       else
743         aj = downaj;
744 
745       viol += aj * solvals[j];
746       sqrnorm += aj * aj;
747     }
748 
749     double efficacy = double(viol / sqrt(sqrnorm));
750     if (efficacy > bestefficacy) {
751       bestdelta = delta;
752       bestefficacy = efficacy;
753     }
754   }
755 
756   if (bestdelta == -1) return false;
757 
758   // try to flip complementation of integers to increase efficacy
759 
760   for (int k : integerinds) {
761     if (upper[k] == HIGHS_CONST_INF) continue;
762 
763     complementation[k] = -complementation[k];
764     rhs -= upper[k] * vals[k];
765     vals[k] = -vals[k];
766     solvals[k] = upper[k] - solvals[k];
767 
768     double delta = bestdelta;
769     HighsCDouble scale = 1.0 / HighsCDouble(delta);
770     HighsCDouble scalrhs = rhs * scale;
771     double downrhs = std::floor(double(scalrhs));
772 
773     HighsCDouble f0 = scalrhs - downrhs;
774     if (f0 < 0.01 || f0 > 0.99) {
775       complementation[k] = -complementation[k];
776       rhs -= upper[k] * vals[k];
777       vals[k] = -vals[k];
778       solvals[k] = upper[k] - solvals[k];
779       continue;
780     }
781 
782     HighsCDouble oneoveroneminusf0 = 1.0 / (1.0 - f0);
783     if (double(oneoveroneminusf0) * double(scale) > 1e4) {
784       complementation[k] = -complementation[k];
785       rhs -= upper[k] * vals[k];
786       vals[k] = -vals[k];
787       solvals[k] = upper[k] - solvals[k];
788       continue;
789     }
790 
791     HighsCDouble sqrnorm = scale * scale * continuoussqrnorm;
792     HighsCDouble viol = continuouscontribution * oneoveroneminusf0 - scalrhs;
793 
794     for (int j : integerinds) {
795       HighsCDouble scalaj = vals[j] * scale;
796       double downaj = std::floor(double(scalaj));
797       HighsCDouble fj = scalaj - downaj;
798       double aj;
799       if (fj > f0)
800         aj = double(downaj + fj - f0);
801       else
802         aj = downaj;
803 
804       viol += aj * solvals[j];
805       sqrnorm += aj * aj;
806     }
807 
808     double efficacy = double(viol / sqrt(sqrnorm));
809     if (efficacy > bestefficacy) {
810       bestefficacy = efficacy;
811     } else {
812       complementation[k] = -complementation[k];
813       rhs -= upper[k] * vals[k];
814       vals[k] = -vals[k];
815       solvals[k] = upper[k] - solvals[k];
816     }
817   }
818 
819   if (bestefficacy <= efficacy) return false;
820   efficacy = bestefficacy;
821   HighsCDouble scale = 1.0 / HighsCDouble(bestdelta);
822   HighsCDouble scalrhs = rhs * scale;
823   double downrhs = std::floor(double(scalrhs));
824 
825   HighsCDouble f0 = scalrhs - downrhs;
826   HighsCDouble oneoveroneminusf0 = 1.0 / (1.0 - f0);
827 
828   rhs = downrhs * bestdelta;
829 
830   for (int j = 0; j != len; ++j) {
831     if (inds[j] >= mip.numCol() ||
832         mip.variableType(inds[j]) == HighsVarType::CONTINUOUS) {
833       vals[j] = double(vals[j] * oneoveroneminusf0);
834     } else {
835       HighsCDouble scalaj = scale * vals[j];
836       double downaj = std::floor(double(scalaj));
837       HighsCDouble fj = scalaj - downaj;
838       HighsCDouble aj;
839       if (fj > f0)
840         aj = downaj + fj - f0;
841       else
842         aj = downaj;
843       vals[j] = double(aj * bestdelta);
844     }
845   }
846 
847   return true;
848 }
849 
generateCut(const HighsMipSolver & mip,const std::vector<double> & upper,int nbin,int nint,int ncont,int nunbndint,std::vector<double> & solvals,std::vector<int8_t> & complementation,std::vector<int> & inds,std::vector<double> & vals,HighsCDouble & rhs,bool & integral)850 bool generateCut(const HighsMipSolver& mip, const std::vector<double>& upper,
851                  int nbin, int nint, int ncont, int nunbndint,
852                  std::vector<double>& solvals,
853                  std::vector<int8_t>& complementation, std::vector<int>& inds,
854                  std::vector<double>& vals, HighsCDouble& rhs, bool& integral) {
855   std::vector<int> cover;
856 
857   bool success;
858   integral = false;
859 #if 0
860   auto tmpinds = inds;
861   auto tmpvals = vals;
862   auto tmpcompl = complementation;
863   HighsCDouble tmprhs = rhs;
864   HighsCDouble efficacy = feastol;
865 #endif
866   if (nunbndint != 0) {
867     HighsCDouble efficacy = 0;
868     success = cmirCutGenerationHeuristic(mip, upper, efficacy, solvals,
869                                          complementation, inds, vals, rhs);
870   } else {
871     HighsCDouble coverweight;
872     HighsCDouble lambda;
873 
874     success = determineCover(mip, inds, vals, upper, solvals, rhs, cover,
875                              coverweight, lambda);
876 
877     if (success) {
878       if (nint == 0 && ncont == 0) {
879         integral = true;
880         success = separatePureBinaryKnapsackCover(solvals, coverweight, lambda,
881                                                   cover, inds, vals, rhs,
882                                                   mip.mipdata_->feastol);
883       } else if (nint == 0)
884         success = separateMixedBinaryKnapsackCover(
885             mip, solvals, coverweight, lambda, cover, inds, vals, rhs);
886       else
887         success = separateMixedIntegerKnapsackCover(
888             mip, solvals, upper, coverweight, lambda, cover, inds, vals, rhs);
889     }
890   }
891 
892 #if 0
893   if (success) {
894     efficacy = -rhs;
895     HighsCDouble sqrnorm = 0.0;
896     for (size_t i = 0; i != inds.size(); ++i) {
897       sqrnorm += vals[i] * vals[i];
898       efficacy += vals[i] * solvals[i];
899     }
900     efficacy = efficacy / sqrt(sqrnorm);
901   }
902 
903   if (cmirCutGenerationHeuristic(mip, upper, efficacy, solvals, tmpcompl,
904                                  tmpinds, tmpvals, tmprhs)) {
905     inds = tmpinds;
906     vals = tmpvals;
907     complementation = tmpcompl;
908     rhs = tmprhs;
909     return true;
910   }
911 #endif
912   return success;
913 }
914 
915 class ImpliedBounds {
916   HighsImplications& implications;
917 
918  public:
ImpliedBounds(HighsImplications & implications)919   ImpliedBounds(HighsImplications& implications) : implications(implications) {}
920 
separateImplBounds(const HighsLpRelaxation & lp,HighsCutPool & cutpool,HighsDomain & propdomain)921   void separateImplBounds(const HighsLpRelaxation& lp, HighsCutPool& cutpool,
922                           HighsDomain& propdomain) {
923     const double feastol = lp.getMipSolver().mipdata_->feastol;
924     int inds[2];
925     double vals[2];
926     double rhs;
927     const auto& sol = lp.getLpSolver().getSolution().col_value;
928     implications.cliquetable.cleanupFixed(implications.globaldomain);
929     if (implications.globaldomain.infeasible()) return;
930     int numboundchgs = 0;
931 
932     // first do probing on all candidates that have not been probed yet
933     for (std::pair<int, double> fracint : lp.getFractionalIntegers()) {
934       int col = fracint.first;
935       if (implications.globaldomain.colLower_[col] != 0.0 ||
936           implications.globaldomain.colUpper_[col] != 1.0)
937         continue;
938 
939       if (implications.runProbing(col, numboundchgs)) {
940         ++numboundchgs;
941         if (implications.globaldomain.infeasible()) return;
942       }
943     }
944 
945     for (std::pair<int, double> fracint : lp.getFractionalIntegers()) {
946       int col = fracint.first;
947       // skip non binary variables
948       if (implications.globaldomain.colLower_[col] != 0.0 ||
949           implications.globaldomain.colUpper_[col] != 1.0)
950         continue;
951 
952       bool infeas;
953       const HighsDomainChange* implics = nullptr;
954       int nimplics = implications.getImplications(col, 1, implics, infeas);
955       if (implications.globaldomain.infeasible()) return;
956       if (infeas) {
957 #ifdef HIGHS_DEBUGSOL
958         assert(highsDebugSolution[col] < 0.5);
959 #endif
960         vals[0] = 1.0;
961         inds[0] = col;
962         int cut = cutpool.addCut(inds, vals, 1, 0.0);
963         propdomain.cutAdded(cut);
964         continue;
965       }
966 
967       for (int i = 0; i != nimplics; ++i) {
968         if (implics[i].boundtype == HighsBoundType::Upper) {
969           if (implics[i].boundval + feastol >=
970               implications.globaldomain.colUpper_[implics[i].column])
971             continue;
972 
973           vals[0] = 1.0;
974           inds[0] = implics[i].column;
975           vals[1] = implications.globaldomain.colUpper_[implics[i].column] -
976                     implics[i].boundval;
977           inds[1] = col;
978           rhs = implications.globaldomain.colUpper_[implics[i].column];
979 
980         } else {
981           if (implics[i].boundval - feastol <=
982               implications.globaldomain.colLower_[implics[i].column])
983             continue;
984 
985           vals[0] = -1.0;
986           inds[0] = implics[i].column;
987           vals[1] = implications.globaldomain.colLower_[implics[i].column] -
988                     implics[i].boundval;
989           inds[1] = col;
990           rhs = -implications.globaldomain.colLower_[implics[i].column];
991         }
992 
993         double viol = sol[inds[0]] * vals[0] + sol[inds[1]] * vals[1] - rhs;
994 
995         if (viol > feastol) {
996           // printf("added implied bound cut to pool\n");
997 #ifdef HIGHS_DEBUGSOL
998           HighsCDouble debugactivity = 0;
999           for (size_t i = 0; i != 2; ++i)
1000             debugactivity += highsDebugSolution[inds[i]] * vals[i];
1001 
1002           assert(debugactivity <= rhs + 1e-9);
1003 #endif
1004           int cut = cutpool.addCut(inds, vals, 2, rhs);
1005           propdomain.cutAdded(cut);
1006         }
1007       }
1008 
1009       nimplics = implications.getImplications(col, 0, implics, infeas);
1010       if (implications.globaldomain.infeasible()) return;
1011       if (infeas) {
1012 #ifdef HIGHS_DEBUGSOL
1013         assert(highsDebugSolution[col] > 0.5);
1014 #endif
1015         vals[0] = -1.0;
1016         inds[0] = col;
1017         int cut = cutpool.addCut(inds, vals, 1, -1.0);
1018         propdomain.cutAdded(cut);
1019         continue;
1020       }
1021 
1022       for (int i = 0; i != nimplics; ++i) {
1023         if (implics[i].boundtype == HighsBoundType::Upper) {
1024           if (implics[i].boundval + feastol >=
1025               implications.globaldomain.colUpper_[implics[i].column])
1026             continue;
1027 
1028           vals[0] = 1.0;
1029           inds[0] = implics[i].column;
1030           vals[1] = implics[i].boundval -
1031                     implications.globaldomain.colUpper_[implics[i].column];
1032           inds[1] = col;
1033           rhs = implics[i].boundval;
1034         } else {
1035           if (implics[i].boundval - feastol <=
1036               implications.globaldomain.colLower_[implics[i].column])
1037             continue;
1038 
1039           vals[0] = -1.0;
1040           inds[0] = implics[i].column;
1041           vals[1] = implications.globaldomain.colLower_[implics[i].column] -
1042                     implics[i].boundval;
1043           inds[1] = col;
1044           rhs = -implics[i].boundval;
1045         }
1046 
1047         double viol = sol[inds[0]] * vals[0] + sol[inds[1]] * vals[1] - rhs;
1048 
1049         if (viol > feastol) {
1050           // printf("added implied bound cut to pool\n");
1051 #ifdef HIGHS_DEBUGSOL
1052           HighsCDouble debugactivity = 0;
1053           for (size_t i = 0; i != 2; ++i)
1054             debugactivity += highsDebugSolution[inds[i]] * vals[i];
1055 
1056           assert(debugactivity <= rhs + feastol);
1057 #endif
1058           int cut = cutpool.addCut(inds, vals, 2, rhs);
1059           propdomain.cutAdded(cut);
1060         }
1061       }
1062     }
1063   }
1064 };
1065 
1066 class AggregationHeuristic {
1067   const HighsLpRelaxation& lprelaxation;
1068   const HighsDomain& domain;
1069   HighsCutPool& cutpool;
1070   HighsDomain& propdomain;
1071 
1072   const HighsMipSolver& mip;
1073   const HighsSolution& lpsol;
1074   const HighsLp& lp;
1075 
1076   std::vector<RowType> rowtype;
1077   std::vector<uint8_t> rowusable;
1078   std::vector<int> numcontinuous;
1079 
1080   std::vector<int> colubtype;
1081   std::vector<double> colubscale;
1082   std::vector<int> collbtype;
1083   std::vector<double> collbscale;
1084   std::vector<double> bounddistance;
1085 
1086   // vector sum object for bound substitution and retransformation
1087   HighsSparseVectorSum vectorsum;
1088 
1089   // current indices and values and rhs for the aggregated row we are looking
1090   // at
1091   std::vector<int> baseinds;
1092   std::vector<double> basevals;
1093   HighsCDouble baserhs;
1094 
1095   // position of continuous variables with positive bound distance
1096   std::vector<int> bounddistpos;
1097 
1098   // temporary indices and values for the row aggregating the next row
1099   std::vector<int> tmpinds;
1100   std::vector<double> tmpvals;
1101 
1102   // current transformed row we are looking at
1103   HighsCDouble rhs;
1104   std::vector<int> inds;
1105   std::vector<double> vals;
1106   std::vector<double> upper;
1107   std::vector<double> solvals;
1108   std::vector<int8_t> complementation;
1109   int ncont;
1110   int nbin;
1111   int nint;
1112   int nunbndint;
1113   bool freevar;
1114 
1115   // maximal number of aggregations we allow
1116   static constexpr int maxAggr = 6;
1117   // array for the current path and its length
1118   int currpath[maxAggr];
1119   double currpathweights[maxAggr];
1120   int currpathlen;
1121 
1122   int numcuts;
1123 
1124  public:
AggregationHeuristic(const HighsLpRelaxation & lprelaxation,const HighsDomain & domain,HighsCutPool & cutpool,HighsDomain & propdomain)1125   AggregationHeuristic(const HighsLpRelaxation& lprelaxation,
1126                        const HighsDomain& domain, HighsCutPool& cutpool,
1127                        HighsDomain& propdomain)
1128       : lprelaxation(lprelaxation),
1129         domain(domain),
1130         cutpool(cutpool),
1131         propdomain(propdomain),
1132         mip(lprelaxation.getMipSolver()),
1133         lpsol(lprelaxation.getLpSolver().getSolution()),
1134         lp(lprelaxation.getLpSolver().getLp()) {
1135     numcuts = 0;
1136   }
1137 
determineRowTypes()1138   void determineRowTypes() {
1139     rowtype.resize(lp.numRow_);
1140     rowusable.resize(lp.numRow_);
1141     for (int i = 0; i != lp.numRow_; ++i) {
1142       if (lp.rowLower_[i] == lp.rowUpper_[i]) {
1143         rowtype[i] = RowType::Eq;
1144         rowusable[i] = true;
1145         continue;
1146       }
1147 
1148       double lowerslack = HIGHS_CONST_INF;
1149       double upperslack = HIGHS_CONST_INF;
1150 
1151       if (lp.rowLower_[i] != -HIGHS_CONST_INF)
1152         lowerslack = lpsol.row_value[i] - lp.rowLower_[i];
1153 
1154       if (lp.rowUpper_[i] != HIGHS_CONST_INF)
1155         upperslack = lp.rowUpper_[i] - lpsol.row_value[i];
1156 
1157       if (lowerslack > mip.mipdata_->feastol &&
1158           upperslack > mip.mipdata_->feastol)
1159         rowtype[i] = RowType::Unusuable;
1160       else if (lowerslack < upperslack)
1161         rowtype[i] = RowType::Geq;
1162       else
1163         rowtype[i] = RowType::Leq;
1164 
1165       rowusable[i] = rowtype[i] != RowType::Unusuable;
1166     }
1167   }
1168 
detectContinuousBounds()1169   void detectContinuousBounds() {
1170     // count number of continuous variables
1171     numcontinuous.assign(lp.numRow_, 0);
1172     for (int i = 0; i != lp.numCol_; ++i) {
1173       if (mip.variableType(i) != HighsVarType::CONTINUOUS) continue;
1174 
1175       int start = lp.Astart_[i];
1176       int end = lp.Astart_[i + 1];
1177 
1178       for (int j = start; j != end; ++j) ++numcontinuous[lp.Aindex_[j]];
1179     }
1180 
1181     // now detect variable bound constraints with one or multiple integral
1182     // variables and store the sparsest one for each constinuous column Also
1183     // compute the bound distance to the best simple or variable bound
1184     colubtype.assign(lp.numCol_, -1);
1185     colubscale.resize(lp.numCol_);
1186     collbtype.assign(lp.numCol_, -1);
1187     collbscale.resize(lp.numCol_);
1188     bounddistance.assign(lp.numCol_, 0.0);
1189 
1190     for (int i = 0; i != lp.numCol_; ++i) {
1191       if (mip.variableType(i) != HighsVarType::CONTINUOUS) continue;
1192 
1193       int start = lp.Astart_[i];
1194       int end = lp.Astart_[i + 1];
1195 
1196       int lblen = HIGHS_CONST_I_INF;
1197       int ublen = HIGHS_CONST_I_INF;
1198 
1199       for (int j = start; j != end; ++j) {
1200         int row = lp.Aindex_[j];
1201         if (numcontinuous[row] != 1) continue;
1202 
1203         int rowlen =
1204             row < mip.model_->numRow_
1205                 ? mip.mipdata_->ARstart_[row + 1] - mip.mipdata_->ARstart_[row]
1206                 : cutpool.getRowLength(lprelaxation.getCutIndex(row));
1207         if (rowlen != 2) continue;
1208         switch (rowtype[row]) {
1209           case RowType::Unusuable:
1210             break;
1211           case RowType::Leq:
1212             if (lp.Avalue_[j] < 0 && rowlen < lblen) {
1213               lblen = rowlen;
1214               collbtype[i] = row;
1215               collbscale[i] = lp.Avalue_[j];
1216             } else if (lp.Avalue_[j] > 0 && rowlen < ublen) {
1217               ublen = rowlen;
1218               colubtype[i] = row;
1219               colubscale[i] = lp.Avalue_[j];
1220             }
1221             break;
1222           case RowType::Geq:
1223             if (lp.Avalue_[j] > 0 && rowlen < lblen) {
1224               lblen = rowlen;
1225               collbtype[i] = row;
1226               collbscale[i] = lp.Avalue_[j];
1227             } else if (lp.Avalue_[j] < 0 && rowlen < ublen) {
1228               ublen = rowlen;
1229               colubtype[i] = row;
1230               colubscale[i] = lp.Avalue_[j];
1231             }
1232             break;
1233           case RowType::Eq:
1234             if (rowlen < ublen) {
1235               ublen = rowlen;
1236               colubtype[i] = row;
1237               colubscale[i] = lp.Avalue_[j];
1238             }
1239             if (rowlen < lblen) {
1240               lblen = rowlen;
1241               collbtype[i] = row;
1242               collbscale[i] = lp.Avalue_[j];
1243             }
1244         }
1245       }
1246 
1247       double lbdist;
1248       double ubdist;
1249 
1250       assert(collbtype[i] == -1 || rowusable[collbtype[i]]);
1251       assert(colubtype[i] == -1 || rowusable[colubtype[i]]);
1252       if (collbtype[i] != -1) {
1253         lbdist = 0.0;
1254         rowusable[collbtype[i]] = false;
1255       } else
1256         lbdist = domain.colLower_[i] != -HIGHS_CONST_INF
1257                      ? lpsol.col_value[i] - domain.colLower_[i]
1258                      : HIGHS_CONST_INF;
1259 
1260       if (colubtype[i] != -1) {
1261         ubdist = 0.0;
1262         rowusable[colubtype[i]] = false;
1263       } else
1264         ubdist = domain.colUpper_[i] != HIGHS_CONST_INF
1265                      ? domain.colUpper_[i] - lpsol.col_value[i]
1266                      : HIGHS_CONST_INF;
1267 
1268       bounddistance[i] = std::min(lbdist, ubdist);
1269       if (bounddistance[i] < mip.mipdata_->feastol) bounddistance[i] = 0;
1270     }
1271 
1272     // todo :mark all continuous variables that have fractional integer
1273     // variables in their variable bound constraints then mark all rows that
1274     // contain fractional integer variables or marked continuous variables
1275 
1276     // now only count the number of continuous variables
1277     // not at their bound
1278     numcontinuous.assign(lp.numRow_, 0);
1279     for (int i = 0; i != lp.numCol_; ++i) {
1280       if (bounddistance[i] == 0.0) continue;
1281 
1282       int start = lp.Astart_[i];
1283       int end = lp.Astart_[i + 1];
1284 
1285       for (int j = start; j != end; ++j) ++numcontinuous[lp.Aindex_[j]];
1286     }
1287 
1288     // mark each of the variables that has an equation row where it is the
1289     // only continuous variable by flipping its bound distance such variables
1290     // will be substituted in the aggregation heuristic first
1291   }
1292 
setupStartRow(int row)1293   void setupStartRow(int row) {
1294     baseinds.clear();
1295     basevals.clear();
1296 
1297     currpath[0] = row;
1298     currpathlen = 1;
1299 
1300     const int* Rindex;
1301     const double* Rvalue;
1302     int Rlen;
1303     if (row < mip.model_->numRow_)
1304       mip.mipdata_->getRow(row, Rlen, Rindex, Rvalue);
1305     else
1306       cutpool.getCut(lprelaxation.getCutIndex(row), Rlen, Rindex, Rvalue);
1307 
1308     baseinds.insert(baseinds.end(), Rindex, Rindex + Rlen);
1309 
1310     if (rowtype[row] == RowType::Leq) {
1311       baserhs = lp.rowUpper_[row];
1312       basevals.insert(basevals.end(), Rvalue, Rvalue + Rlen);
1313       currpathweights[0] = 1.0;
1314     } else {
1315       baserhs = -lp.rowLower_[row];
1316       basevals.resize(Rlen);
1317       currpathweights[0] = -1.0;
1318       for (int i = 0; i != Rlen; ++i) basevals[i] = -Rvalue[i];
1319     }
1320   }
1321 
computeTransformedRow(bool negate)1322   void computeTransformedRow(bool negate) {
1323     vectorsum.clear();
1324     bounddistpos.clear();
1325     inds.clear();
1326     vals.clear();
1327     upper.clear();
1328     solvals.clear();
1329     complementation.clear();
1330 
1331     freevar = false;
1332 
1333     vectorsum.setDimension(lp.numCol_);
1334 
1335     double basescale;
1336 
1337     if (negate) {
1338       for (int i = 0; i < currpathlen; ++i) {
1339         if (rowtype[currpath[i]] == RowType::Eq) continue;
1340         // add a slack variable for the rows in the path if they are not
1341         // equations
1342         inds.push_back(lp.numCol_ + currpath[i]);
1343         vals.push_back(-std::abs(currpathweights[i]));
1344         upper.push_back(HIGHS_CONST_INF);
1345         complementation.push_back(0);
1346         solvals.push_back(0);
1347       }
1348       basescale = -1.0;
1349     } else
1350       basescale = 1.0;
1351 
1352     rhs = basescale * baserhs;
1353 
1354     for (size_t j = 0; j != baseinds.size(); ++j) {
1355       int col = baseinds[j];
1356       double baseval = basescale * basevals[j];
1357 
1358       // add things to the vectorsum
1359       // store solution values and upper bounds
1360       // the coefficient values area added up with the sparse vector sum
1361       // as variable bound usage can alter things
1362 
1363       // we first need to complement the continuous variables
1364       if (mip.variableType(col) != HighsVarType::CONTINUOUS) {
1365         vectorsum.add(col, baseval);
1366         continue;
1367       }
1368 
1369       // complement positive if possible without slack using a simple or
1370       // variable bound otherwise use the tightest bound and prefer simple
1371       // bounds adjust the right hand side accordingly
1372       if (bounddistance[col] > mip.mipdata_->feastol) bounddistpos.push_back(j);
1373 
1374       if (bounddistance[col] == HIGHS_CONST_INF) freevar = true;
1375 
1376       if (freevar) continue;
1377 
1378       double simplelbdist = domain.colLower_[col] != -HIGHS_CONST_INF
1379                                 ? lpsol.col_value[col] - domain.colLower_[col]
1380                                 : HIGHS_CONST_INF;
1381       double simpleubdist = domain.colUpper_[col] != HIGHS_CONST_INF
1382                                 ? domain.colUpper_[col] - lpsol.col_value[col]
1383                                 : HIGHS_CONST_INF;
1384 
1385       if (baseval < 0) {
1386         if (colubtype[col] != -1) {
1387           // use variable upper bound
1388           int UBrow = colubtype[col];
1389           double UBscale = colubscale[col];
1390           int UBlen;
1391           const int* UBindex;
1392           const double* UBvalue;
1393           double UBconst;
1394 
1395           if (UBscale < 0)
1396             UBconst = lp.rowLower_[UBrow];
1397           else
1398             UBconst = lp.rowUpper_[UBrow];
1399 
1400           if (UBrow < mip.numRow()) {
1401             mip.mipdata_->getRow(UBrow, UBlen, UBindex, UBvalue);
1402           } else {
1403             int cut = lprelaxation.getCutIndex(UBrow);
1404             cutpool.getCut(cut, UBlen, UBindex, UBvalue);
1405           }
1406 
1407           HighsCDouble scale = HighsCDouble(-baseval) / UBscale;
1408 
1409           // the upper bound constraint adds a conmtinuous slack variable
1410           // with positive coefficient that we relax out
1411 
1412           rhs += scale * UBconst;
1413 
1414           for (int k = 0; k != UBlen; ++k) {
1415             if (UBindex[k] == col)  // the column cancels out
1416             {
1417               assert(std::abs(double(baseval + scale * UBvalue[k])) < 1e-10);
1418               continue;
1419             }
1420             assert(mip.variableType(UBindex[k]) != HighsVarType::CONTINUOUS);
1421             vectorsum.add(UBindex[k], scale * UBvalue[k]);
1422           }
1423         } else if (simpleubdist - mip.mipdata_->feastol <= bounddistance[col]) {
1424           // use  the simple upper bound for complementation and then relax
1425           // the positive continuous variable as it has a positive
1426           // coefficient
1427           complementWithUpper(baseval, domain.colUpper_[col], rhs);
1428         } else if (simplelbdist - mip.mipdata_->feastol <= bounddistance[col]) {
1429           inds.push_back(col);
1430           complementation.push_back(1);
1431           if (domain.colUpper_[col] == HIGHS_CONST_INF)
1432             upper.push_back(HIGHS_CONST_INF);
1433           else
1434             upper.push_back(domain.colUpper_[col] - domain.colLower_[col]);
1435           vals.push_back(
1436               complementWithLower(baseval, domain.colLower_[col], rhs));
1437           solvals.push_back(lpsol.col_value[col] - domain.colLower_[col]);
1438         } else {
1439           assert(collbtype[col] != -1);
1440 
1441           // use variable lower bound
1442           int LBrow = collbtype[col];
1443           double LBscale = collbscale[col];
1444           int LBlen;
1445           const int* LBindex;
1446           const double* LBvalue;
1447           double LBconst;
1448 
1449           if (LBscale < 0)
1450             LBconst = lp.rowUpper_[LBrow];
1451           else
1452             LBconst = lp.rowLower_[LBrow];
1453 
1454           if (LBrow < mip.numRow()) {
1455             mip.mipdata_->getRow(LBrow, LBlen, LBindex, LBvalue);
1456           } else {
1457             int cut = lprelaxation.getCutIndex(LBrow);
1458             cutpool.getCut(cut, LBlen, LBindex, LBvalue);
1459           }
1460 
1461           HighsCDouble scale = HighsCDouble(-baseval) / LBscale;
1462 
1463           rhs += scale * LBconst;
1464 
1465           // the lb constraint does add a slack variable with negative sign
1466           // as the inequality orientations do not match
1467           if (LBscale > 0)
1468             vals.push_back(-double(scale));
1469           else
1470             vals.push_back(double(scale));
1471           assert(vals.back() < 0);
1472 
1473           inds.push_back(lp.numCol_ + LBrow);
1474           complementation.push_back(0);
1475           upper.push_back(HIGHS_CONST_INF);
1476           solvals.push_back(0);
1477 
1478           for (int k = 0; k != LBlen; ++k) {
1479             if (LBindex[k] == col)  // the column cancels out
1480             {
1481               assert(std::abs(double(baseval + scale * LBvalue[k])) < 1e-10);
1482               continue;
1483             }
1484 
1485             assert(mip.variableType(LBindex[k]) != HighsVarType::CONTINUOUS);
1486             vectorsum.add(LBindex[k], scale * LBvalue[k]);
1487           }
1488         }
1489       } else {
1490         assert(baseval > 0);
1491         if (collbtype[col] != -1) {
1492           int LBrow = collbtype[col];
1493           double LBscale = collbscale[col];
1494           int LBlen;
1495           const int* LBindex;
1496           const double* LBvalue;
1497           double LBconst;
1498 
1499           if (LBscale < 0)
1500             LBconst = lp.rowUpper_[LBrow];
1501           else
1502             LBconst = lp.rowLower_[LBrow];
1503 
1504           if (LBrow < mip.numRow()) {
1505             mip.mipdata_->getRow(LBrow, LBlen, LBindex, LBvalue);
1506           } else {
1507             int cut = lprelaxation.getCutIndex(LBrow);
1508             cutpool.getCut(cut, LBlen, LBindex, LBvalue);
1509           }
1510 
1511           HighsCDouble scale = HighsCDouble(-baseval) / LBscale;
1512 
1513           rhs += scale * LBconst;
1514 
1515           assert(int(rowtype[LBrow]) * double(scale) >= 0);
1516 
1517           // the lower bound constraint does not add a slack variable we
1518           // need to remember as the inequalities match in orientation and
1519           // the positive slack variable is relaxed out
1520 
1521           for (int k = 0; k != LBlen; ++k) {
1522             if (LBindex[k] == col)  // the column cancels out
1523             {
1524               assert(std::abs(double(baseval + scale * LBvalue[k])) < 1e-10);
1525               continue;
1526             }
1527             assert(mip.variableType(LBindex[k]) != HighsVarType::CONTINUOUS);
1528             vectorsum.add(LBindex[k], scale * LBvalue[k]);
1529           }
1530         } else if (simplelbdist - mip.mipdata_->feastol <= bounddistance[col]) {
1531           // use  the simple lower bound for complementation and then relax
1532           // the positive continuous variable as it has a positive
1533           // coefficient
1534           complementWithLower(baseval, domain.colLower_[col], rhs);
1535         } else if (simpleubdist - mip.mipdata_->feastol <= bounddistance[col]) {
1536           // use the simple upper bound and add the negative complemented
1537           // variable
1538           inds.push_back(col);
1539           complementation.push_back(-1);
1540           if (domain.colLower_[col] == -HIGHS_CONST_INF)
1541             upper.push_back(HIGHS_CONST_INF);
1542           else
1543             upper.push_back(domain.colUpper_[col] - domain.colLower_[col]);
1544           solvals.push_back(domain.colUpper_[col] - lpsol.col_value[col]);
1545 
1546           vals.push_back(
1547               complementWithUpper(baseval, domain.colUpper_[col], rhs));
1548         } else {
1549           assert(colubtype[col] != -1);
1550           // use variable upper bound
1551           int UBrow = colubtype[col];
1552           double UBscale = colubscale[col];
1553           int UBlen;
1554           const int* UBindex;
1555           const double* UBvalue;
1556           double UBconst;
1557 
1558           if (UBscale < 0)
1559             UBconst = lp.rowLower_[UBrow];
1560           else
1561             UBconst = lp.rowUpper_[UBrow];
1562 
1563           if (UBrow < mip.numRow()) {
1564             mip.mipdata_->getRow(UBrow, UBlen, UBindex, UBvalue);
1565           } else {
1566             int cut = lprelaxation.getCutIndex(UBrow);
1567             cutpool.getCut(cut, UBlen, UBindex, UBvalue);
1568           }
1569 
1570           HighsCDouble scale = HighsCDouble(-baseval) / UBscale;
1571           // the ub constraint does add a slack variable as the inequality
1572           // orientations do not match
1573 
1574           if (UBscale > 0)
1575             vals.push_back(double(scale));
1576           else
1577             vals.push_back(-double(scale));
1578           assert(vals.back() < 0);
1579 
1580           inds.push_back(lp.numCol_ + UBrow);
1581           complementation.push_back(0);
1582           upper.push_back(HIGHS_CONST_INF);
1583           solvals.push_back(0);
1584           rhs += scale * UBconst;
1585 
1586           for (int k = 0; k != UBlen; ++k) {
1587             if (UBindex[k] == col)  // the column cancels out
1588             {
1589               assert(std::abs(double(baseval + scale * UBvalue[k])) < 1e-10);
1590               continue;
1591             }
1592             assert(mip.variableType(UBindex[k]) != HighsVarType::CONTINUOUS);
1593             vectorsum.add(UBindex[k], scale * UBvalue[k]);
1594           }
1595         }
1596       }
1597     }
1598 
1599     // so far we only added continuous variables as the values of integer
1600     // variables could still change due to variable bound usage
1601     ncont = inds.size();
1602     nbin = 0;
1603     nint = 0;
1604     nunbndint = 0;
1605 
1606     // now we add the integer variables and complement them to be positive
1607     for (int col : vectorsum.getNonzeros()) {
1608       double val = vectorsum.getValue(col);
1609 
1610       if (std::abs(val) <= 1e-10) continue;
1611 
1612       if (std::abs(val) <= mip.mipdata_->feastol) {
1613         if (val < 0)
1614           rhs -= domain.colUpper_[col] * val;
1615         else
1616           rhs -= domain.colLower_[col] * val;
1617       }
1618 
1619       assert(mip.variableType(col) != HighsVarType::CONTINUOUS);
1620       assert(domain.colLower_[col] != -HIGHS_CONST_INF ||
1621              domain.colUpper_[col] != HIGHS_CONST_INF);
1622 
1623       if (domain.colLower_[col] == -HIGHS_CONST_INF ||
1624           domain.colUpper_[col] == HIGHS_CONST_INF)
1625         upper.push_back(HIGHS_CONST_INF);
1626       else
1627         upper.push_back(
1628             std::floor(domain.colUpper_[col] - domain.colLower_[col] + 0.5));
1629 
1630       inds.push_back(col);
1631 
1632       if (upper.back() == 1.0)
1633         ++nbin;
1634       else if (upper.back() == HIGHS_CONST_INF)
1635         ++nunbndint;
1636       else
1637         ++nint;
1638       if (val < 0 && domain.colUpper_[col] != HIGHS_CONST_INF) {
1639         solvals.push_back(domain.colUpper_[col] - lpsol.col_value[col]);
1640         complementation.push_back(-1);
1641         vals.push_back(complementWithUpper(val, domain.colUpper_[col], rhs));
1642       } else {
1643         solvals.push_back(lpsol.col_value[col] - domain.colLower_[col]);
1644         complementation.push_back(1);
1645         vals.push_back(complementWithLower(val, domain.colLower_[col], rhs));
1646       }
1647     }
1648 
1649     // perform coefficient tightening on the transformed row
1650     int len = inds.size();
1651     HighsCDouble maxact = 0.0;
1652     bool unbnd = false;
1653     for (int i = 0; i != len; ++i) {
1654       if (upper[i] == HIGHS_CONST_INF) {
1655         unbnd = true;
1656         break;
1657       }
1658       if (vals[i] > 0) maxact += vals[i] * upper[i];
1659     }
1660 
1661     HighsCDouble maxabscoef = maxact - rhs;
1662     if (!unbnd && maxabscoef > mip.mipdata_->feastol) {
1663       int ntightened = 0;
1664       for (int i = 0; i != len; ++i) {
1665         if (inds[i] >= mip.numCol() ||
1666             mip.variableType(inds[i]) == HighsVarType::CONTINUOUS)
1667           continue;
1668 
1669         if (vals[i] > maxabscoef) {
1670           HighsCDouble delta = vals[i] - maxabscoef;
1671           rhs -= delta * upper[i];
1672           vals[i] = double(maxabscoef);
1673           ++ntightened;
1674         }
1675       }
1676     }
1677   }
1678 
computeCut()1679   void computeCut() {
1680     bool cutintegral;
1681     bool foundcut =
1682         generateCut(mip, upper, nbin, nint, ncont, nunbndint, solvals,
1683                     complementation, inds, vals, rhs, cutintegral);
1684 
1685     if (foundcut) {
1686       vectorsum.clear();
1687       vectorsum.setDimension(mip.numCol());
1688 
1689       int cutlen = inds.size();
1690 
1691       for (int i = 0; i != cutlen; ++i) {
1692         if (vals[i] == 0.0) continue;
1693 
1694         if (inds[i] >= mip.numCol()) {
1695           int row = inds[i] - mip.numCol();
1696 
1697           const int* Rindex;
1698           const double* Rvalue;
1699           int Rlen;
1700           double Rside;
1701 
1702           assert(rowtype[row] == RowType::Leq || rowtype[row] == RowType::Geq);
1703 
1704           if (row < mip.numRow()) {
1705             if (rowtype[row] == RowType::Leq)
1706               Rside = lp.rowUpper_[row];
1707             else
1708               Rside = -lp.rowLower_[row];
1709             mip.mipdata_->getRow(row, Rlen, Rindex, Rvalue);
1710           } else {
1711             assert(rowtype[row] == RowType::Leq);
1712             int cut = lprelaxation.getCutIndex(row);
1713             cutpool.getCut(cut, Rlen, Rindex, Rvalue);
1714             Rside = lp.rowUpper_[row];
1715           }
1716 
1717           rhs -= vals[i] * Rside;
1718 
1719           double slackval = -int(rowtype[row]) * vals[i];
1720 
1721           for (int j = 0; j != Rlen; ++j)
1722             vectorsum.add(Rindex[j], slackval * Rvalue[j]);
1723 
1724           continue;
1725         }
1726 
1727         if (complementation[i] == 1)
1728           rhs += vals[i] * domain.colLower_[inds[i]];
1729         else if (complementation[i] == -1) {
1730           vals[i] = -vals[i];
1731           rhs += vals[i] * domain.colUpper_[inds[i]];
1732         }
1733 
1734         vectorsum.add(inds[i], vals[i]);
1735       }
1736 
1737       inds.clear();
1738       vals.clear();
1739 
1740       vectorsum.sort();
1741 
1742       for (int col : vectorsum.getNonzeros()) {
1743         double val = vectorsum.getValue(col);
1744 
1745         if (std::abs(val) > 1e-10) {
1746           vals.push_back(val);
1747           inds.push_back(col);
1748         }
1749       }
1750 
1751       double upper = double(rhs);
1752 
1753 #ifdef HIGHS_DEBUGSOL
1754       HighsCDouble debugactivity = 0;
1755       for (size_t i = 0; i != inds.size(); ++i)
1756         debugactivity += highsDebugSolution[inds[i]] * vals[i];
1757 
1758       assert(debugactivity <= upper + feastol);
1759 #endif
1760 
1761       int cutindex = cutpool.addCut(inds.data(), vals.data(), inds.size(),
1762                                     upper, cutintegral);
1763       propdomain.cutAdded(cutindex);
1764 
1765       ++numcuts;
1766     }
1767   }
1768 
aggregateNextRow()1769   bool aggregateNextRow() {
1770     if (currpathlen == maxAggr || bounddistpos.empty()) return false;
1771 
1772     // sort by decreasing bound distance
1773     std::sort(bounddistpos.begin(), bounddistpos.end(), [&](int a, int b) {
1774       int col1 = baseinds[a];
1775       int col2 = baseinds[b];
1776       return bounddistance[col1] > bounddistance[col2];
1777     });
1778 
1779     int nextaggrow = -1;
1780     HighsCDouble nextaggscale;
1781     int naggbounddist = HIGHS_CONST_I_INF;
1782 
1783     for (int pos : bounddistpos) {
1784       int col = baseinds[pos];
1785 
1786       int start = lp.Astart_[col];
1787       int end = lp.Astart_[col + 1];
1788 
1789       for (int j = start; j != end; ++j) {
1790         int row = lp.Aindex_[j];
1791         // if the row is marked as unusable or the inequality orientation does
1792         // not match we skip it
1793         if (!rowusable[row] ||
1794             basevals[pos] * lp.Avalue_[j] * int(rowtype[row]) > 0.0)
1795           continue;
1796 
1797         bool incurrentpath = false;
1798         for (int k = 0; k < currpathlen; ++k) {
1799           if (currpath[k] == row) {
1800             incurrentpath = true;
1801             break;
1802           }
1803         }
1804 
1805         if (incurrentpath) continue;
1806 
1807         bool nextaggusedbefore = currpath[0] > nextaggrow;
1808         bool rowusedbefore = currpath[0] > row;
1809 
1810         if ((nextaggusedbefore && !rowusedbefore) ||
1811             (nextaggusedbefore == rowusedbefore &&
1812              numcontinuous[row] < naggbounddist)) {
1813           nextaggscale = HighsCDouble(-basevals[pos]) / lp.Avalue_[j];
1814           nextaggrow = row;
1815           naggbounddist = numcontinuous[row];
1816           if (numcontinuous[row] == 1 && rowtype[row] == RowType::Eq) break;
1817         }
1818       }
1819 
1820       if (nextaggrow != -1) break;
1821     }
1822 
1823     if (nextaggrow == -1) return false;
1824 
1825     const int* nextRindex;
1826     const double* nextRvalue;
1827     int nextRlen;
1828 
1829     if (rowtype[nextaggrow] == RowType::Geq) {
1830       baserhs += nextaggscale * lp.rowLower_[nextaggrow];
1831       assert(nextaggscale < 0);
1832     } else {
1833       assert(rowtype[nextaggrow] == RowType::Eq || nextaggscale > 0);
1834       baserhs += nextaggscale * lp.rowUpper_[nextaggrow];
1835     }
1836     if (nextaggrow < mip.numRow())
1837       mip.mipdata_->getRow(nextaggrow, nextRlen, nextRindex, nextRvalue);
1838     else
1839       cutpool.getCut(lprelaxation.getCutIndex(nextaggrow), nextRlen, nextRindex,
1840                      nextRvalue);
1841 
1842     tmpinds.clear();
1843     tmpvals.clear();
1844 
1845     int a = 0;
1846     int b = 0;
1847 
1848     int baselen = baseinds.size();
1849 
1850     while (a != nextRlen && b != baselen) {
1851       if (nextRindex[a] < baseinds[b]) {
1852         tmpinds.push_back(nextRindex[a]);
1853         tmpvals.push_back(double(nextaggscale * nextRvalue[a]));
1854         ++a;
1855 
1856       } else if (baseinds[b] < nextRindex[a]) {
1857         tmpinds.push_back(baseinds[b]);
1858         tmpvals.push_back(basevals[b]);
1859         ++b;
1860       } else {
1861         assert(nextRindex[a] == baseinds[b]);
1862         double val = double(basevals[b] + nextaggscale * nextRvalue[a]);
1863         if (std::abs(val) > 1e-10) {
1864           tmpinds.push_back(baseinds[b]);
1865           tmpvals.push_back(val);
1866         }
1867         ++a;
1868         ++b;
1869       }
1870     }
1871 
1872     if (a != nextRlen) {
1873       tmpinds.insert(tmpinds.end(), nextRindex + a, nextRindex + nextRlen);
1874       do {
1875         tmpvals.push_back(double(nextaggscale * nextRvalue[a]));
1876         ++a;
1877       } while (a != nextRlen);
1878     }
1879 
1880     if (b != baselen) {
1881       tmpinds.insert(tmpinds.end(), baseinds.begin() + b, baseinds.end());
1882       tmpvals.insert(tmpvals.end(), basevals.begin() + b, basevals.end());
1883     }
1884 
1885     tmpinds.swap(baseinds);
1886     tmpvals.swap(basevals);
1887     currpath[currpathlen] = nextaggrow;
1888     currpathweights[currpathlen] = double(nextaggscale);
1889     ++currpathlen;
1890     return true;
1891   }
1892 
run()1893   void run() {
1894     determineRowTypes();
1895     detectContinuousBounds();
1896 
1897     for (int row = 0; row != lp.numRow_; ++row) {
1898       if (!rowusable[row]) continue;
1899 
1900       int currnumcuts = numcuts;
1901       setupStartRow(row);
1902 
1903       do {
1904 #ifdef HIGHS_DEBUGSOL
1905         HighsCDouble debugactivity = 0;
1906         for (size_t i = 0; i != baseinds.size(); ++i)
1907           debugactivity += highsDebugSolution[baseinds[i]] * basevals[i];
1908 
1909         assert(debugactivity <= baserhs + feastol);
1910 #endif
1911         computeTransformedRow(false);
1912 
1913         if (!freevar) {
1914           computeCut();
1915           computeTransformedRow(true);
1916           computeCut();
1917 
1918           if (currnumcuts < numcuts) {
1919             break;
1920           }
1921         }
1922 
1923         if (!aggregateNextRow()) break;
1924       } while (true);
1925     }
1926   }
1927 };
1928 
doSeparate(const HighsDomain & domain,const HighsLpRelaxation & lp,const HighsSolution & lpsol,const HighsSeparation::BaseRows & baserows,HighsCutPool & cutpool,HighsDomain & propdomain)1929 static void doSeparate(const HighsDomain& domain, const HighsLpRelaxation& lp,
1930                        const HighsSolution& lpsol,
1931                        const HighsSeparation::BaseRows& baserows,
1932                        HighsCutPool& cutpool, HighsDomain& propdomain) {
1933   std::vector<int8_t> complementation;
1934   std::vector<int> inds;
1935   std::vector<double> vals;
1936   std::vector<double> upper;
1937   std::vector<double> solvals;
1938   HighsCDouble rhs;
1939 
1940   const HighsMipSolver& mip = lp.getMipSolver();
1941   int numbaserows = baserows.rhs_.size();
1942 
1943   for (int i = 0; i != numbaserows; ++i) {
1944     bool success;
1945     bool cutintegral;
1946 
1947     int start = baserows.ARstart_[i];
1948     int end = baserows.ARstart_[i + 1];
1949 
1950     int nbin;
1951     int nint;
1952     int ncont;
1953     int nunbndint;
1954 
1955     rhs = baserows.rhs_[i];
1956     success = transformBaseEquation(
1957         mip, domain, lpsol, &baserows.ARindex_[start],
1958         &baserows.ARvalue_[start], end - start, 1.0, rhs, complementation, inds,
1959         vals, upper, solvals, nbin, nint, ncont, nunbndint);
1960     if (success)
1961       success = generateCut(mip, upper, nbin, nint, ncont, nunbndint, solvals,
1962                             complementation, inds, vals, rhs, cutintegral);
1963 
1964     if (success) {
1965       baserows.retransformAndAddCut(domain, lp, inds, vals, complementation,
1966                                     rhs, cutpool, propdomain, cutintegral);
1967     }
1968 
1969     rhs = baserows.rhs_[i];
1970     success = transformBaseEquation(
1971         mip, domain, lpsol, &baserows.ARindex_[start],
1972         &baserows.ARvalue_[start], end - start, -1.0, rhs, complementation,
1973         inds, vals, upper, solvals, nbin, nint, ncont, nunbndint);
1974 
1975     if (success)
1976       success = generateCut(mip, upper, nbin, nint, ncont, nunbndint, solvals,
1977                             complementation, inds, vals, rhs, cutintegral);
1978 
1979     if (success) {
1980       baserows.retransformAndAddCut(domain, lp, inds, vals, complementation,
1981                                     rhs, cutpool, propdomain, cutintegral);
1982     }
1983   }
1984 }
1985 
tableauaggregator(HighsLpRelaxation & lp,const HighsCutPool & cutpool,HighsSeparation::BaseRows & baserows)1986 static void tableauaggregator(HighsLpRelaxation& lp,
1987                               const HighsCutPool& cutpool,
1988                               HighsSeparation::BaseRows& baserows) {
1989   std::vector<int> basisinds;
1990   int numrow = lp.getNumLpRows();
1991   basisinds.resize(numrow);
1992   lp.getLpSolver().getBasicVariables(basisinds.data());
1993 
1994   std::vector<int> aggrinds;
1995   std::vector<double> aggrvals;
1996   int naggrinds;
1997 
1998   const HighsMipSolver& mip = lp.getMipSolver();
1999 
2000   aggrinds.resize(numrow);
2001   aggrvals.resize(numrow);
2002   baserows.slacktype_.resize(numrow);
2003 
2004   for (int i = 0; i != numrow; ++i) {
2005     if (lp.rowLower(i) == lp.rowUpper(i)) {
2006       baserows.slacktype_[i] = 0;
2007       continue;
2008     }
2009 
2010     switch (lp.getLpSolver().getBasis().row_status[i]) {
2011       case HighsBasisStatus::LOWER:
2012         baserows.slacktype_[i] = -1;
2013         break;
2014       case HighsBasisStatus::UPPER:
2015         baserows.slacktype_[i] = 1;
2016         break;
2017       default:
2018         continue;
2019     }
2020   }
2021 
2022   baserows.ARstart_.push_back(0);
2023 
2024   auto& lpsol = lp.getLpSolver().getSolution();
2025 
2026   for (int i = 0; i != int(basisinds.size()); ++i) {
2027     if (basisinds[i] < 0) {
2028       continue;
2029       // todo, cuts off solutions
2030       int row = -basisinds[i] - 1;
2031 
2032       bool rowintegral;
2033       if (row < mip.numRow())
2034         rowintegral = mip.mipdata_->rowintegral[row];
2035       else
2036         rowintegral = cutpool.cutIsIntegral(lp.getCutIndex(row));
2037 
2038       if (!rowintegral) continue;
2039 
2040       double solval = lpsol.row_value[row];
2041       double frac = std::abs(floor(solval + 0.5) - solval);
2042       if (frac < 1e-1) continue;
2043     } else {
2044       int col = basisinds[i];
2045       if (mip.variableType(col) == HighsVarType::CONTINUOUS) continue;
2046       double solval = lpsol.col_value[col];
2047 
2048       double frac = std::abs(std::floor(solval + 0.5) - solval);
2049       if (frac < 1e-4) continue;
2050     }
2051 
2052     if (lp.getLpSolver().getBasisInverseRow(i, aggrvals.data(), &naggrinds,
2053                                             aggrinds.data()) != HighsStatus::OK)
2054       continue;
2055 
2056     baserows.addAggregation(lp, cutpool, aggrvals.data(), aggrinds.data(),
2057                             naggrinds);
2058   }
2059 }
2060 
addAggregation(const HighsLpRelaxation & lp,const HighsCutPool & cutpool,const double * aggrvals,const int * aggrinds,int naggrinds)2061 void HighsSeparation::BaseRows::addAggregation(const HighsLpRelaxation& lp,
2062                                                const HighsCutPool& cutpool,
2063                                                const double* aggrvals,
2064                                                const int* aggrinds,
2065                                                int naggrinds) {
2066   HighsCDouble rhs = 0.0;
2067 
2068   const HighsMipSolver& mip = lp.getMipSolver();
2069 
2070   vectorsum.setDimension(mip.numCol() + lp.getNumLpRows());
2071 
2072   assert(std::all_of(vectorsum.nonzeroflag.begin(), vectorsum.nonzeroflag.end(),
2073                      [](uint8_t f) { return f == 0; }));
2074 
2075   for (int k = 0; k != naggrinds; ++k) {
2076     int j = aggrinds[k];
2077     if (std::abs(aggrvals[j]) <= mip.mipdata_->feastol) continue;
2078 
2079     int rowlen;
2080     const int* rowinds;
2081     const double* rowvals;
2082     if (j < mip.numRow()) {
2083       mip.mipdata_->getRow(j, rowlen, rowinds, rowvals);
2084     } else {
2085       int cut = lp.getCutIndex(j);
2086       cutpool.getCut(cut, rowlen, rowinds, rowvals);
2087     }
2088 
2089     HighsCDouble scale = aggrvals[j];
2090 
2091     assert(slacktype_[j] != 0 || lp.rowLower(j) == lp.rowUpper(j));
2092 
2093     if (slacktype_[j] != 0) {
2094       vectorsum.set(mip.numCol() + j, slacktype_[j] * scale);
2095     }
2096 
2097     if (slacktype_[j] == -1)
2098       rhs += scale * lp.rowLower(j);
2099     else
2100       rhs += scale * lp.rowUpper(j);
2101 
2102     for (int k = 0; k != rowlen; ++k)
2103       vectorsum.add(rowinds[k], scale * rowvals[k]);
2104   }
2105 
2106   double minval = HIGHS_CONST_INF;
2107   double maxval = 0;
2108   int len = 0;
2109   for (int j : vectorsum.getNonzeros()) {
2110     double val = std::abs(vectorsum.getValue(j));
2111 
2112     if (val <= 1e-12) {
2113       vectorsum.chgValue(j, 0.0);
2114       continue;
2115     }
2116 
2117     ++len;
2118     if (val < minval) minval = val;
2119 
2120     if (val > maxval) maxval = val;
2121   }
2122 
2123   /* reject baserows that have a too large dynamism */
2124   if (maxval <= 1e6 * minval) {
2125     rhs_.push_back(double(rhs));
2126     for (int j : vectorsum.getNonzeros()) {
2127       double val = vectorsum.getValue(j);
2128 
2129       if (val == 0.0) continue;
2130 
2131       ARindex_.push_back(j);
2132       ARvalue_.push_back(val);
2133     }
2134 
2135     ARstart_.push_back(ARindex_.size());
2136   }
2137 
2138   vectorsum.clear();
2139 }
2140 
retransformAndAddCut(const HighsDomain & domain,const HighsLpRelaxation & lp,std::vector<int> & inds,std::vector<double> & vals,std::vector<int8_t> & complementation,HighsCDouble rhs,HighsCutPool & cutpool,HighsDomain & propdomain,bool cutintegral) const2141 void HighsSeparation::BaseRows::retransformAndAddCut(
2142     const HighsDomain& domain, const HighsLpRelaxation& lp,
2143     std::vector<int>& inds, std::vector<double>& vals,
2144     std::vector<int8_t>& complementation, HighsCDouble rhs,
2145     HighsCutPool& cutpool, HighsDomain& propdomain, bool cutintegral) const {
2146   const HighsMipSolver& mip = lp.getMipSolver();
2147   vectorsum.setDimension(mip.numCol() + lp.getNumLpRows());
2148 
2149   assert(std::all_of(vectorsum.nonzeroflag.begin(), vectorsum.nonzeroflag.end(),
2150                      [](uint8_t f) { return f == 0; }));
2151   int cutlen = inds.size();
2152 
2153   for (int i = 0; i != cutlen; ++i) {
2154     if (vals[i] == 0.0) continue;
2155 
2156     if (inds[i] >= mip.numCol()) {
2157       int row = inds[i] - mip.numCol();
2158 
2159       const int* Rindex;
2160       const double* Rvalue;
2161       int Rlen;
2162       double Rside;
2163 
2164       assert(slacktype_[row] == 1 || slacktype_[row] == -1);
2165 
2166       if (row < mip.numRow()) {
2167         if (slacktype_[row] == 1)
2168           Rside = lp.rowUpper(row);
2169         else
2170           Rside = -lp.rowLower(row);
2171         mip.mipdata_->getRow(row, Rlen, Rindex, Rvalue);
2172       } else {
2173         int cut = lp.getCutIndex(row);
2174         assert(slacktype_[row] == 1);
2175         cutpool.getCut(cut, Rlen, Rindex, Rvalue);
2176         Rside = lp.rowUpper(row);
2177       }
2178 
2179       rhs -= vals[i] * Rside;
2180 
2181       double slackval = -slacktype_[row] * vals[i];
2182 
2183       for (int j = 0; j != Rlen; ++j)
2184         vectorsum.add(Rindex[j], slackval * Rvalue[j]);
2185 
2186       continue;
2187     }
2188 
2189     if (complementation[i] == 1)
2190       rhs += vals[i] * domain.colLower_[inds[i]];
2191     else if (complementation[i] == -1) {
2192       vals[i] = -vals[i];
2193       rhs += vals[i] * domain.colUpper_[inds[i]];
2194     }
2195 
2196     vectorsum.add(inds[i], vals[i]);
2197   }
2198 
2199   inds.clear();
2200   vals.clear();
2201 
2202   vectorsum.sort();
2203 
2204   for (int col : vectorsum.getNonzeros()) {
2205     double val = vectorsum.getValue(col);
2206 
2207     if (std::abs(val) > 1e-10) {
2208       vals.push_back(val);
2209       inds.push_back(col);
2210     }
2211   }
2212 
2213   vectorsum.clear();
2214 
2215 #ifdef HIGHSDEBUGSOL
2216   int len = inds.size();
2217 
2218   HighsCDouble debugactivity = 0;
2219   for (int i = 0; i != len; ++i) {
2220     debugactivity += highsDebugSolution[inds[i]] * HighsCDouble(vals[i]);
2221   }
2222 
2223   if (debugactivity - lp.getMipSolver().mipdata_->epsilon > rhs) {
2224     printf("debug solution violated by %g for this cut:\n",
2225            double(debugactivity - rhs));
2226     printCut(inds.data(), vals.data(), len, double(rhs));
2227     assert(false);
2228   }
2229 #endif
2230   double upper = double(rhs);
2231   // domain.tightenCoefficients(inds.data(), vals.data(), inds.size(),
2232   // upper);
2233   int cutindex =
2234       cutpool.addCut(inds.data(), vals.data(), inds.size(), upper, cutintegral);
2235   propdomain.cutAdded(cutindex);
2236 }
2237 
separationRound(HighsDomain & propdomain,HighsLpRelaxation::Status & status)2238 int HighsSeparation::separationRound(HighsDomain& propdomain,
2239                                      HighsLpRelaxation::Status& status) {
2240   const HighsSolution& sol = lp->getLpSolver().getSolution();
2241 
2242   HighsMipSolverData& mipdata = *lp->getMipSolver().mipdata_;
2243   ImpliedBounds implbound(mipdata.implications);
2244   implbound.separateImplBounds(*lp, mipdata.cutpool, propdomain);
2245   propdomain.propagate();
2246 
2247   if (propdomain.infeasible() || mipdata.domain.infeasible()) {
2248     status = HighsLpRelaxation::Status::Infeasible;
2249     propdomain.clearChangedCols();
2250     return 0;
2251   }
2252 
2253   if (!propdomain.getChangedCols().empty()) {
2254     lp->flushDomain(propdomain);
2255     status = lp->resolveLp();
2256 
2257     if (!lp->scaledOptimal(status)) return 0;
2258   }
2259 
2260   mipdata.cliquetable.separateCliques(sol.col_value, mipdata.domain, propdomain,
2261                                       mipdata.cutpool, mipdata.feastol);
2262 
2263   AggregationHeuristic aggheur(*lp, mipdata.domain, mipdata.cutpool,
2264                                propdomain);
2265   aggheur.run();
2266 
2267   baserows.clear();
2268   tableauaggregator(*lp, mipdata.cutpool, baserows);
2269   doSeparate(mipdata.domain, *lp, sol, baserows, mipdata.cutpool, propdomain);
2270   baserows.clear();
2271 
2272   propdomain.propagate();
2273 
2274   if (propdomain.infeasible()) {
2275     status = HighsLpRelaxation::Status::Infeasible;
2276     propdomain.clearChangedCols();
2277     return 0;
2278   }
2279 
2280   if (!propdomain.getChangedCols().empty()) {
2281     lp->flushDomain(propdomain);
2282     status = lp->resolveLp();
2283   }
2284 
2285   if (lp->scaledOptimal(status)) {
2286     mipdata.cutpool.separate(sol.col_value, propdomain, cutset,
2287                              mipdata.feastol);
2288 
2289     int ncuts = cutset.numCuts();
2290 
2291     if (ncuts > 0) {
2292       lp->addCuts(cutset);
2293       status = lp->resolveLp();
2294       mipdata.cutpool.ageLPRows(*lp);
2295     }
2296     return ncuts;
2297   }
2298 
2299   return 0;
2300 }
2301 
computeAndAddConflictCut(HighsMipSolver & mipsolver,HighsDomain & localdomain,std::vector<int> & inds,std::vector<double> & vals,double rowupper)2302 void HighsSeparation::computeAndAddConflictCut(HighsMipSolver& mipsolver,
2303                                                HighsDomain& localdomain,
2304                                                std::vector<int>& inds,
2305                                                std::vector<double>& vals,
2306                                                double rowupper) {
2307   int len = inds.size();
2308   std::vector<double> solvals(len);
2309   std::vector<int8_t> complementation(len);
2310   std::vector<double> upper(len);
2311   HighsCDouble rhs = rowupper;
2312 
2313 #ifdef HIGHS_DEBUGSOL
2314   HighsCDouble debugactivity = 0;
2315   for (size_t i = 0; i != inds.size(); ++i)
2316     debugactivity += highsDebugSolution[inds[i]] * vals[i];
2317 
2318   assert(debugactivity <= rhs + mipsolver.mipdata_->epsilon);
2319 #endif
2320 
2321   int nbin = 0;
2322   int nint = 0;
2323   int ncont = 0;
2324   int nunbndint = 0;
2325 
2326   const HighsDomain& globaldomain = mipsolver.mipdata_->domain;
2327 
2328   double minact = 0.0;
2329 
2330   for (int i = 0; i != len; ++i) {
2331     int col = inds[i];
2332     assert(globaldomain.colUpper_[col] != HIGHS_CONST_INF ||
2333            globaldomain.colLower_[col] != -HIGHS_CONST_INF);
2334 
2335     if (globaldomain.colUpper_[col] == HIGHS_CONST_INF ||
2336         globaldomain.colLower_[col] == -HIGHS_CONST_INF) {
2337       upper[i] = HIGHS_CONST_INF;
2338     } else {
2339       upper[i] = globaldomain.colUpper_[col] - globaldomain.colLower_[col];
2340     }
2341 
2342     if (mipsolver.variableType(col) != HighsVarType::CONTINUOUS) {
2343       if (upper[i] < 1.5) {
2344         upper[i] = 1.0;
2345         ++nbin;
2346       } else if (upper[i] != HIGHS_CONST_INF) {
2347         upper[i] = std::floor(upper[i] + 0.5);
2348         ++nint;
2349       } else {
2350         ++nunbndint;
2351       }
2352     } else {
2353       ++ncont;
2354     }
2355 
2356     if (vals[i] < 0 && globaldomain.colUpper_[col] != HIGHS_CONST_INF) {
2357       vals[i] = complementWithUpper(vals[i], globaldomain.colUpper_[col], rhs);
2358       complementation[i] = -1;
2359       solvals[i] = globaldomain.colUpper_[col] - localdomain.colUpper_[col];
2360     } else {
2361       vals[i] = complementWithLower(vals[i], globaldomain.colLower_[col], rhs);
2362       complementation[i] = 1;
2363       solvals[i] = localdomain.colLower_[col] - globaldomain.colLower_[col];
2364     }
2365 
2366     minact += solvals[i] * vals[i];
2367   }
2368 
2369   bool cutintegral;
2370   bool success =
2371       generateCut(mipsolver, upper, nbin, nint, ncont, nunbndint, solvals,
2372                   complementation, inds, vals, rhs, cutintegral);
2373 
2374   if (success) {
2375     int offset = 0;
2376 
2377     for (int i = 0; i != len; ++i) {
2378       // skip zeros
2379       if (vals[i] == 0) continue;
2380 
2381       // undo complementation
2382       if (complementation[i] == 1)
2383         rhs += vals[i] * globaldomain.colLower_[inds[i]];
2384       else {
2385         assert(complementation[i] == -1);
2386         vals[i] = -vals[i];
2387         rhs += vals[i] * globaldomain.colUpper_[inds[i]];
2388       }
2389 
2390       // store back
2391       if (offset < i) {
2392         vals[offset] = vals[i];
2393         inds[offset] = inds[i];
2394       }
2395       ++offset;
2396     }
2397 
2398 #ifdef HIGHS_DEBUGSOL
2399     HighsCDouble debugactivity = 0;
2400     for (int i = 0; i != offset; ++i)
2401       debugactivity += highsDebugSolution[inds[i]] * vals[i];
2402 
2403     assert(debugactivity <= rhs + mipsolver.mipdata_->epsilon);
2404 #endif
2405 
2406     int cutind = mipsolver.mipdata_->cutpool.addCut(
2407         inds.data(), vals.data(), offset, double(rhs), cutintegral);
2408     localdomain.cutAdded(cutind);
2409   }
2410 }
2411 
separate(HighsDomain & propdomain)2412 void HighsSeparation::separate(HighsDomain& propdomain) {
2413   HighsLpRelaxation::Status status = lp->getStatus();
2414   const HighsMipSolver& mipsolver = lp->getMipSolver();
2415 
2416   if (lp->scaledOptimal(status) && !lp->getFractionalIntegers().empty()) {
2417     double firstobj = mipsolver.mipdata_->rootlpsolobj;
2418 
2419     while (lp->getObjective() < mipsolver.mipdata_->upper_limit) {
2420       double lastobj = lp->getObjective();
2421 
2422       size_t nlpiters = -lp->getNumLpIterations();
2423       int ncuts = separationRound(propdomain, status);
2424       nlpiters += lp->getNumLpIterations();
2425       mipsolver.mipdata_->sepa_lp_iterations += nlpiters;
2426       // printf("separated %d cuts\n", ncuts);
2427 
2428       // printf(
2429       //     "separation round %d at node %d added %d cuts objective changed "
2430       //     "from %g to %g, first obj is %g\n",
2431       //     nrounds, (int)nnodes, ncuts, lastobj, lp->getObjective(),
2432       //     firstobj);
2433       if (ncuts == 0 || !lp->scaledOptimal(status) ||
2434           lp->getFractionalIntegers().empty())
2435         break;
2436 
2437       // if the objective improved considerably we continue
2438       if ((lp->getObjective() - firstobj) <=
2439           std::max((lastobj - firstobj), 0.0) * 1.01)
2440         break;
2441     }
2442 
2443     // printf("done separating\n");
2444   } else {
2445     // printf("no separation, just aging. status: %d\n", (int)status);
2446     mipsolver.mipdata_->cutpool.ageLPRows(*lp);
2447     mipsolver.mipdata_->cutpool.ageNonLPRows();
2448   }
2449 }