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 }