Skip to content

Commit 03ca876

Browse files
committed
Merges latest into branch
2 parents aa291fd + cfa1614 commit 03ca876

File tree

6 files changed

+235
-35
lines changed

6 files changed

+235
-35
lines changed

check/TestMipSolver.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -916,3 +916,16 @@ TEST_CASE("ZI Round and Shifting Heuristics", "[highs_test_mip_solver]") {
916916
const double optimal_objective = 82.19999924;
917917
solve(highs, kHighsOnString, require_model_status, optimal_objective);
918918
}
919+
920+
TEST_CASE("issue-2290", "[highs_test_mip_solver]") {
921+
std::string filename =
922+
std::string(HIGHS_DIR) + "/check/instances/issue-2290.mps";
923+
Highs highs;
924+
highs.setOptionValue("output_flag", dev_run);
925+
highs.setOptionValue("mip_rel_gap", 0);
926+
highs.setOptionValue("mip_abs_gap", 0);
927+
highs.readModel(filename);
928+
const HighsModelStatus require_model_status = HighsModelStatus::kOptimal;
929+
const double optimal_objective = -1.6666666666;
930+
solve(highs, kHighsOnString, require_model_status, optimal_objective);
931+
}

check/instances/issue-2290.mps

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
NAME
2+
ROWS
3+
N Obj
4+
E r0
5+
E r1
6+
L r2
7+
L r3
8+
L r4
9+
L r5
10+
L r6
11+
L r7
12+
E r8
13+
E r9
14+
E r10
15+
G r11
16+
E r12
17+
E r13
18+
E r14
19+
L r15
20+
L r16
21+
L r17
22+
L r18
23+
L r19
24+
L r20
25+
L r21
26+
E r22
27+
E r23
28+
E r24
29+
G r25
30+
E r26
31+
E r27
32+
E r28
33+
L r29
34+
L r30
35+
L r31
36+
L r32
37+
L r33
38+
L r34
39+
COLUMNS
40+
c0 r11 1
41+
c0 r13 -0.08
42+
c0 r14 -1
43+
c0 r15 1
44+
c0 r18 1
45+
c1 r25 1
46+
c1 r27 -0.08
47+
c1 r28 -1
48+
c1 r29 1
49+
c1 r32 1
50+
MARK0000 'MARKER' 'INTORG'
51+
c2 r17 3
52+
c2 r18 -3
53+
c3 r31 3
54+
c3 r32 -3
55+
MARK0001 'MARKER' 'INTEND'
56+
c4 r10 1
57+
c5 r24 1
58+
c6 r8 1
59+
c6 r13 -0.08333333333
60+
c7 r22 1
61+
c7 r27 -0.08333333333
62+
c8 r9 1
63+
c8 r11 1
64+
c8 r14 1
65+
c9 r23 1
66+
c9 r25 1
67+
c9 r28 1
68+
c10 r2 0.08680555556
69+
c10 r11 1
70+
c10 r13 0.08680555556
71+
c10 r14 1
72+
c10 r16 1
73+
c10 r17 1
74+
c11 r3 0.08680555556
75+
c11 r25 1
76+
c11 r27 0.08680555556
77+
c11 r28 1
78+
c11 r30 1
79+
c11 r31 1
80+
c12 r12 1
81+
c12 r13 0.08333333333
82+
c13 r26 1
83+
c13 r27 0.08333333333
84+
c14 Obj -0.8333333333
85+
c14 r14 -1
86+
c14 r20 1
87+
c15 Obj -0.8333333333
88+
c15 r28 -1
89+
c15 r34 1
90+
c16 r15 1
91+
c16 r19 1
92+
c17 r25 0.01
93+
c17 r27 -0.0008
94+
c17 r29 1
95+
c17 r33 1
96+
c18 Obj 2.5
97+
c18 r14 1
98+
c18 r19 1
99+
c19 Obj 2.5
100+
c19 r28 1
101+
c19 r33 1
102+
c20 r2 0.5208333333
103+
c20 r16 1
104+
c20 r20 1
105+
c21 r3 0.5208333333
106+
c21 r25 0.011
107+
c21 r27 0.0009548611111
108+
c21 r30 1
109+
c21 r34 1
110+
c22 r11 1
111+
c23 r25 1
112+
c24 r0 1
113+
c24 r2 -1
114+
c24 r4 1
115+
c24 r13 -1
116+
c25 r3 -1
117+
c25 r5 1
118+
c25 r13 1
119+
c25 r27 -1
120+
c26 r6 1
121+
c26 r27 1
122+
c27 r7 1
123+
c27 r14 -1
124+
c28 r21 1
125+
c28 r28 -1
126+
c29 r1 1
127+
RHS
128+
RHS_V r0 4.5
129+
RHS_V r1 21
130+
RHS_V r2 -0.45
131+
RHS_V r3 -0.45
132+
RHS_V r4 4.5
133+
RHS_V r5 4.5
134+
RHS_V r6 4.5
135+
RHS_V r7 0.1
136+
RHS_V r11 1
137+
RHS_V r14 -1.1
138+
RHS_V r15 3
139+
RHS_V r16 3
140+
RHS_V r17 3
141+
RHS_V r19 1000
142+
RHS_V r20 1
143+
RHS_V r21 0.1
144+
RHS_V r25 1
145+
RHS_V r28 -1.1
146+
RHS_V r29 3
147+
RHS_V r30 3
148+
RHS_V r31 3
149+
RHS_V r33 1000
150+
RHS_V r34 1
151+
BOUNDS
152+
BV BOUND c2
153+
BV BOUND c3
154+
FR BOUND c24
155+
FR BOUND c25
156+
FR BOUND c26
157+
FR BOUND c29
158+
ENDATA

highs/mip/HighsCliqueTable.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1105,6 +1105,10 @@ void HighsCliqueTable::extractCliquesFromCut(const HighsMipSolver& mipsolver,
11051105
}
11061106
}
11071107

1108+
if (rhs - minact < 0.0) {
1109+
minact = rhs;
1110+
}
1111+
11081112
for (HighsInt i = 0; i != len; ++i) {
11091113
if (mipsolver.variableType(inds[i]) == HighsVarType::kContinuous) continue;
11101114

highs/mip/HighsCutGeneration.cpp

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -589,14 +589,15 @@ bool HighsCutGeneration::cmirCutGenerationHeuristic(double minEfficacy,
589589
double oneoveroneminusf0 = 1.0 / (1.0 - f0);
590590
if (oneoveroneminusf0 > maxCMirScale) continue;
591591

592-
double sqrnorm = scale * scale * continuoussqrnorm;
593-
double viol = scale * continuouscontribution * oneoveroneminusf0 - downrhs;
592+
double contscale = scale * oneoveroneminusf0;
593+
double sqrnorm = contscale * contscale * continuoussqrnorm;
594+
double viol = contscale * continuouscontribution - downrhs;
594595

595596
for (HighsInt j : integerinds) {
596597
double scalaj = vals[j] * scale;
597598
double downaj = fast_floor(scalaj + kHighsTiny);
598599
double fj = scalaj - downaj;
599-
double aj = downaj + std::max(0.0, fj - f0);
600+
double aj = downaj + std::max(0.0, (fj - f0) * oneoveroneminusf0);
600601
updateViolationAndNorm(j, aj, viol, sqrnorm);
601602
}
602603

@@ -621,14 +622,15 @@ bool HighsCutGeneration::cmirCutGenerationHeuristic(double minEfficacy,
621622
double oneoveroneminusf0 = 1.0 / (1.0 - f0);
622623
if (oneoveroneminusf0 > maxCMirScale) continue;
623624

624-
double sqrnorm = scale * scale * continuoussqrnorm;
625-
double viol = scale * continuouscontribution * oneoveroneminusf0 - downrhs;
625+
double contscale = scale * oneoveroneminusf0;
626+
double sqrnorm = contscale * contscale * continuoussqrnorm;
627+
double viol = contscale * continuouscontribution - downrhs;
626628

627629
for (HighsInt j : integerinds) {
628630
double scalaj = vals[j] * scale;
629631
double downaj = fast_floor(scalaj + kHighsTiny);
630632
double fj = scalaj - downaj;
631-
double aj = downaj + std::max(0.0, fj - f0);
633+
double aj = downaj + std::max(0.0, (fj - f0) * oneoveroneminusf0);
632634
updateViolationAndNorm(j, aj, viol, sqrnorm);
633635
}
634636

@@ -665,14 +667,15 @@ bool HighsCutGeneration::cmirCutGenerationHeuristic(double minEfficacy,
665667
continue;
666668
}
667669

668-
double sqrnorm = scale * scale * continuoussqrnorm;
669-
double viol = scale * continuouscontribution * oneoveroneminusf0 - downrhs;
670+
double contscale = scale * oneoveroneminusf0;
671+
double sqrnorm = contscale * contscale * continuoussqrnorm;
672+
double viol = contscale * continuouscontribution - downrhs;
670673

671674
for (HighsInt j : integerinds) {
672675
double scalaj = vals[j] * scale;
673676
double downaj = fast_floor(scalaj + kHighsTiny);
674677
double fj = scalaj - downaj;
675-
double aj = downaj + std::max(0.0, fj - f0);
678+
double aj = downaj + std::max(0.0, (fj - f0) * oneoveroneminusf0);
676679
updateViolationAndNorm(j, aj, viol, sqrnorm);
677680
}
678681

@@ -708,12 +711,28 @@ bool HighsCutGeneration::cmirCutGenerationHeuristic(double minEfficacy,
708711
double downaj = floor(double(scalaj + kHighsTiny));
709712
HighsCDouble fj = scalaj - downaj;
710713
HighsCDouble aj = downaj;
711-
if (fj > f0) aj += fj - f0;
714+
if (fj > f0) aj += (fj - f0) * oneoveroneminusf0;
712715

713716
vals[j] = double(aj * bestdelta);
714717
}
715718
}
716719

720+
#ifndef NDEBUG
721+
// Check if the computed cut has the correct efficacy
722+
{
723+
double checkviol = -downrhs * bestdelta;
724+
double checknorm = 0.0;
725+
for (HighsInt j = 0; j != rowlen; ++j) {
726+
if (vals[j] == 0.0) continue;
727+
updateViolationAndNorm(j, vals[j], checkviol, checknorm);
728+
}
729+
double checkefficacy = checkviol / sqrt(checknorm);
730+
// the efficacy can become infinite if the cut 0 <= -1 is derived
731+
assert(fabs(checkefficacy - bestefficacy) < 0.001 ||
732+
fabs(checkefficacy) >= 1e30);
733+
}
734+
#endif
735+
717736
return true;
718737
}
719738

highs/mip/HighsImplications.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -635,7 +635,7 @@ void HighsImplications::separateImpliedBounds(
635635
vals[0] = -1.0;
636636
inds[0] = implics[i].column;
637637
vals[1] =
638-
globaldomain.col_lower_[implics[i].column] - implics[i].boundval;
638+
implics[i].boundval - globaldomain.col_lower_[implics[i].column];
639639
inds[1] = col;
640640
rhs = -globaldomain.col_lower_[implics[i].column];
641641
}

highs/presolve/HPresolve.cpp

Lines changed: 30 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3842,9 +3842,19 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack,
38423842
// baseiRUpper);
38433843

38443844
auto checkForcingRow = [&](HighsInt row, HighsInt direction, double rowSide,
3845+
double impliedRowBound, double minAbsCoef,
38453846
HighsPostsolveStack::RowType rowType) {
3846-
// store row
3847-
storeRow(row);
3847+
// 1. direction = 1 (>=): forcing row if upper bound on constraint activity
3848+
// is equal to row's lower bound
3849+
// 2. direction = -1 (<=): forcing row if lower bound on constraint activity
3850+
// is equal to row's upper bound
3851+
// scale tolerance (equivalent to scaling row to have minimum absolute
3852+
// coefficient of 1)
3853+
if (direction * impliedRowBound >
3854+
direction * rowSide + primal_feastol * std::min(1.0, minAbsCoef))
3855+
return Result::kOk;
3856+
3857+
// get stored row
38483858
auto rowVector = getStoredRow();
38493859

38503860
HighsInt nfixings = 0;
@@ -3920,29 +3930,25 @@ HPresolve::Result HPresolve::rowPresolve(HighsPostsolveStack& postsolve_stack,
39203930

39213931
if (analysis_.allow_rule_[kPresolveRuleForcingRow]) {
39223932
// Allow rule to consider forcing rows
3923-
if (impliedRowUpper <= // check for forcing row on the row lower bound
3924-
model->row_lower_[row] + primal_feastol) {
3925-
// the row upper bound that is implied by the column bounds is equal to
3926-
// the row lower bound there for we can fix all columns at their bound
3927-
// as this is the only feasible assignment for this row and then find a
3928-
// suitable dual multiplier in postsolve. First we store the row on the
3929-
// postsolve stack (forcingRow() call) afterwards we store each column
3930-
// fixing on the postsolve stack. As the postsolve goes over the stack
3931-
// in reverse, it will first restore the column primal and dual values
3932-
// as the dual values are required to find the proper dual multiplier for
3933-
// the row and the column that we put in the basis.
3934-
HPRESOLVE_CHECKED_CALL(
3935-
checkForcingRow(row, HighsInt{1}, model->row_lower_[row],
3936-
HighsPostsolveStack::RowType::kGeq));
3937-
if (rowDeleted[row]) return Result::kOk;
3938-
3939-
} else if (impliedRowLower >= model->row_upper_[row] - primal_feastol) {
3940-
// forcing row in the other direction
3941-
HPRESOLVE_CHECKED_CALL(
3942-
checkForcingRow(row, HighsInt{-1}, model->row_upper_[row],
3943-
HighsPostsolveStack::RowType::kLeq));
3944-
if (rowDeleted[row]) return Result::kOk;
3933+
3934+
// store row and compute minimum absolute coefficient
3935+
storeRow(row);
3936+
double minAbsCoef = kHighsInf;
3937+
for (const HighsSliceNonzero& nonzero : getStoredRow()) {
3938+
minAbsCoef = std::min(minAbsCoef, std::abs(nonzero.value()));
39453939
}
3940+
3941+
// >= inequality
3942+
HPRESOLVE_CHECKED_CALL(checkForcingRow(
3943+
row, HighsInt{1}, model->row_lower_[row], impliedRowUpper, minAbsCoef,
3944+
HighsPostsolveStack::RowType::kGeq));
3945+
if (rowDeleted[row]) return Result::kOk;
3946+
3947+
// <= inequality
3948+
HPRESOLVE_CHECKED_CALL(checkForcingRow(
3949+
row, HighsInt{-1}, model->row_upper_[row], impliedRowLower, minAbsCoef,
3950+
HighsPostsolveStack::RowType::kLeq));
3951+
if (rowDeleted[row]) return Result::kOk;
39463952
}
39473953

39483954
// implied bounds can only be computed when row bounds are available and

0 commit comments

Comments
 (0)