Skip to content

Commit 6af61cb

Browse files
committed
Add draft
1 parent 73370b1 commit 6af61cb

7 files changed

Lines changed: 271 additions & 1 deletion

File tree

highs/lp_data/HConst.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,8 @@ enum PresolveRuleType : int {
279279
kPresolveRuleSparsify,
280280
kPresolveRuleProbing,
281281
kPresolveRuleEnumeration,
282-
kPresolveRuleMax = kPresolveRuleEnumeration,
282+
kPresolveRulePrecedenceCycles,
283+
kPresolveRuleMax = kPresolveRulePrecedenceCycles,
283284
kPresolveRuleLastAllowOff = kPresolveRuleMax,
284285
kPresolveRuleCount
285286
};

highs/lp_data/HighsModelUtils.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1515,6 +1515,8 @@ std::string utilPresolveRuleTypeToString(const HighsInt rule_type) {
15151515
return "Probing";
15161516
} else if (rule_type == kPresolveRuleEnumeration) {
15171517
return "Enumeration";
1518+
} else if (rule_type == kPresolveRulePrecedenceCycles) {
1519+
return "Precedence cycles";
15181520
}
15191521
assert(1 == 0);
15201522
return "????";

highs/mip/HighsDomain.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2010,6 +2010,10 @@ void HighsDomain::changeBound(HighsDomainChange boundchg, Reason reason) {
20102010
*this, boundchg.column, col_lower_[boundchg.column] > 0.5);
20112011
}
20122012
}
2013+
2014+
if (!infeasible()) {
2015+
mipsolver->mipdata_->implications.applyPrecedenceGraph(*this, boundchg);
2016+
}
20132017
}
20142018

20152019
bool HighsDomain::checkChangeBound(HighsBoundType boundtype, HighsInt col,

highs/mip/HighsImplications.cpp

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -508,6 +508,8 @@ void HighsImplications::rebuild(HighsInt ncols,
508508
implications.resize(2 * ncols);
509509
colsubstituted.resize(ncols);
510510
substitutions.clear();
511+
precedenceLbs.clear();
512+
precedenceUbs.clear();
511513
vubs.clear();
512514
vubs.shrink_to_fit();
513515
vubs.resize(ncols);
@@ -913,3 +915,30 @@ void HighsImplications::applyImplications(HighsDomain& domain,
913915
}
914916
}
915917
}
918+
919+
void HighsImplications::applyPrecedenceGraph(
920+
HighsDomain& domain, const HighsDomainChange& boundchg) const {
921+
if (precedenceLbs.num_col_ == 0 || precedenceUbs.num_col_ == 0) return;
922+
const HighsSparseMatrix& precedence =
923+
boundchg.boundtype == HighsBoundType::kLower ? precedenceLbs
924+
: precedenceUbs;
925+
const HighsInt start = precedence.start_[boundchg.column];
926+
const HighsInt end = precedence.start_[boundchg.column + 1];
927+
928+
for (HighsInt i = start; i < end; ++i) {
929+
const HighsInt col = precedence.index_[i];
930+
const double shift = precedence.value_[i];
931+
if (boundchg.boundtype == HighsBoundType::kLower) {
932+
const double newLb = domain.col_lower_[boundchg.column] - shift;
933+
if (domain.col_lower_[col] < newLb - domain.feastol()) {
934+
domain.changeBound({newLb, col, HighsBoundType::kLower}, HighsDomain::Reason::unspecified());
935+
}
936+
} else if (boundchg.boundtype == HighsBoundType::kUpper) {
937+
const double newUb = domain.col_upper_[boundchg.column] + shift;
938+
if (domain.col_upper_[col] > newUb + domain.feastol()) {
939+
domain.changeBound({newUb, col, HighsBoundType::kUpper}, HighsDomain::Reason::unspecified());
940+
}
941+
}
942+
if (domain.infeasible()) return;
943+
}
944+
}

highs/mip/HighsImplications.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,8 @@ class HighsImplications {
5252
private:
5353
std::vector<HighsHashTree<HighsInt, VarBound>> vubs;
5454
std::vector<HighsHashTree<HighsInt, VarBound>> vlbs;
55+
HighsSparseMatrix precedenceLbs;
56+
HighsSparseMatrix precedenceUbs;
5557

5658
public:
5759
const HighsMipSolver& mipsolver;
@@ -88,6 +90,8 @@ class HighsImplications {
8890
vlbs.clear();
8991
vlbs.shrink_to_fit();
9092
vlbs.resize(numcol);
93+
precedenceLbs.clear();
94+
precedenceUbs.clear();
9195
numVarBounds = 0;
9296
maxVarBounds = calcMaxVarBounds(numcol);
9397

@@ -189,6 +193,12 @@ class HighsImplications {
189193
bool& infeasible, bool allowBoundChanges = true) const;
190194

191195
void applyImplications(HighsDomain& domain, HighsInt col, HighsInt val);
196+
197+
void applyPrecedenceGraph(HighsDomain& domain,
198+
const HighsDomainChange& boundchg) const;
199+
200+
HighsSparseMatrix& getPrecedenceDirectedGraphLb() { return precedenceLbs; }
201+
HighsSparseMatrix& getPrecedenceDirectedGraphUb() { return precedenceUbs; }
192202
};
193203

194204
#endif

highs/presolve/HPresolve.cpp

Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1443,6 +1443,200 @@ HPresolve::Result HPresolve::dominatedColumns(
14431443
return Result::kOk;
14441444
}
14451445

1446+
void HPresolve::createPrecedenceGraph(bool generateUb) const {
1447+
HighsImplications& implications = mipsolver->mipdata_->implications;
1448+
HighsSparseMatrix& precedenceLb = implications.getPrecedenceDirectedGraphLb();
1449+
1450+
std::vector<HighsInt> precedenceArcs;
1451+
precedenceArcs.reserve(mipsolver->numRow());
1452+
std::vector<HighsInt> numArcs(mipsolver->numCol(), 0);
1453+
1454+
// Want to store all rows of the form x <= y + c, c \in \R
1455+
for (HighsInt row = 0; row != model->num_row_; ++row) {
1456+
if (rowDeleted[row] || rowsize[row] != 2 || isEquation(row)) continue;
1457+
HighsInt x = -1;
1458+
double xCoef = 0;
1459+
HighsInt y = -1;
1460+
double yCoef = 0;
1461+
for (const HighsSliceNonzero& nonzero : getRowVector(row)) {
1462+
// get column index and coefficient
1463+
const HighsInt col = nonzero.index();
1464+
const double val = nonzero.value();
1465+
if (val > 0) {
1466+
x = col;
1467+
xCoef = val;
1468+
} else if (val < 0) {
1469+
y = col;
1470+
yCoef = val;
1471+
}
1472+
}
1473+
if (x == -1 || y == -1 || colDeleted[x] || colDeleted[y] ||
1474+
std::max(std::fabs(yCoef), std::fabs(xCoef)) <=
1475+
options->small_matrix_value ||
1476+
std::fabs(std::fabs(yCoef) - std::fabs(xCoef)) >
1477+
options->small_matrix_value ||
1478+
xCoef * yCoef >= 0)
1479+
continue;
1480+
if (model->row_upper_[row] != kHighsInf ||
1481+
model->row_lower_[row] != -kHighsInf) {
1482+
precedenceArcs.emplace_back(row);
1483+
}
1484+
if (model->row_upper_[row] != kHighsInf) {
1485+
numArcs[x]++;
1486+
}
1487+
if (model->row_lower_[row] != -kHighsInf) {
1488+
numArcs[y]++;
1489+
}
1490+
}
1491+
1492+
if (precedenceArcs.empty()) {
1493+
precedenceLb.clear();
1494+
implications.getPrecedenceDirectedGraphUb().clear();
1495+
return;
1496+
}
1497+
1498+
precedenceLb.num_col_ = mipsolver->numCol();
1499+
precedenceLb.num_row_ = mipsolver->numCol();
1500+
precedenceLb.format_ = MatrixFormat::kColwise;
1501+
precedenceLb.p_end_.clear();
1502+
std::vector<HighsInt>& start = precedenceLb.start_;
1503+
start.assign(mipsolver->numCol() + 1, 0);
1504+
std::vector<HighsInt>& index = precedenceLb.index_;
1505+
std::vector<double>& value = precedenceLb.value_;
1506+
for (HighsInt col = 0; col != mipsolver->numCol(); col++) {
1507+
start[col + 1] = start[col] + numArcs[col];
1508+
}
1509+
index.resize(start.back());
1510+
value.resize(start.back());
1511+
std::vector<HighsInt> pos = start;
1512+
for (const HighsInt row : precedenceArcs) {
1513+
HighsInt x = -1;
1514+
HighsInt y = -1;
1515+
double scale = 0;
1516+
for (const HighsSliceNonzero& nonzero : getRowVector(row)) {
1517+
// get column index and coefficient
1518+
const HighsInt col = nonzero.index();
1519+
const double val = nonzero.value();
1520+
if (val > 0) {
1521+
x = col;
1522+
scale = val;
1523+
} else if (val < 0) {
1524+
y = col;
1525+
}
1526+
}
1527+
if (model->row_upper_[row] != kHighsInf) {
1528+
const HighsInt p = pos[x]++;
1529+
index[p] = y;
1530+
value[p] = model->row_upper_[row] / scale;
1531+
}
1532+
if (model->row_lower_[row] != -kHighsInf) {
1533+
const HighsInt p = pos[y]++;
1534+
index[p] = x;
1535+
value[p] = -model->row_lower_[row] / scale;
1536+
}
1537+
}
1538+
1539+
if (generateUb) {
1540+
implications.getPrecedenceDirectedGraphUb().createRowwise(precedenceLb);
1541+
}
1542+
}
1543+
1544+
void HPresolve::strongConnect(
1545+
const std::vector<HighsInt>& start, const std::vector<HighsInt>& index,
1546+
const std::vector<double>& value, const double eps, HighsInt v,
1547+
HighsInt& time, std::vector<HighsInt>& dfsNum,
1548+
std::vector<HighsInt>& lowLink, std::vector<HighsInt>& stack,
1549+
std::vector<bool>& onStack,
1550+
std::vector<HighsInt>& stronglyConnectedComponents) {
1551+
dfsNum[v] = time;
1552+
lowLink[v] = time;
1553+
++time;
1554+
stack.push_back(v);
1555+
onStack[v] = true;
1556+
1557+
for (HighsInt i = start[v]; i != start[v + 1]; ++i) {
1558+
if (std::abs(value[i]) > eps) continue;
1559+
const HighsInt w = index[i];
1560+
if (dfsNum[w] == -1) {
1561+
strongConnect(start, index, value, eps, w, time, dfsNum, lowLink, stack,
1562+
onStack, stronglyConnectedComponents);
1563+
lowLink[v] = std::min(lowLink[v], lowLink[w]);
1564+
} else if (onStack[w]) {
1565+
lowLink[v] = std::min(lowLink[v], dfsNum[w]);
1566+
}
1567+
}
1568+
1569+
if (lowLink[v] != dfsNum[v]) return;
1570+
1571+
HighsInt u = v;
1572+
std::vector<HighsInt> stronglyConnectedComponent;
1573+
while (true) {
1574+
HighsInt w = stack.back();
1575+
stack.pop_back();
1576+
onStack[w] = false;
1577+
stronglyConnectedComponent.push_back(w);
1578+
u = std::min(u, w);
1579+
if (w == v) break;
1580+
}
1581+
1582+
for (const HighsInt w : stronglyConnectedComponent) {
1583+
stronglyConnectedComponents[w] = u;
1584+
}
1585+
}
1586+
1587+
void HPresolve::tarjan(const std::vector<HighsInt>& start,
1588+
const std::vector<HighsInt>& index,
1589+
const std::vector<double>& value, const double eps,
1590+
std::vector<HighsInt>& stronglyConnectedComponents) {
1591+
const HighsInt n = static_cast<HighsInt>(stronglyConnectedComponents.size());
1592+
std::vector<HighsInt> dfsNum(n, -1);
1593+
std::vector<HighsInt> lowLink(n, -1);
1594+
std::vector<HighsInt> stack;
1595+
stack.reserve(n);
1596+
std::vector<bool> onStack(n, false);
1597+
HighsInt time = 0;
1598+
1599+
for (HighsInt col = 0; col != n; ++col) {
1600+
if (colDeleted[col]) continue;
1601+
if (dfsNum[col] == -1)
1602+
strongConnect(start, index, value, eps, col, time, dfsNum, lowLink, stack,
1603+
onStack, stronglyConnectedComponents);
1604+
}
1605+
}
1606+
1607+
HPresolve::Result HPresolve::findPrecedenceCycles(
1608+
HighsPostsolveStack& postsolve_stack) {
1609+
HighsImplications& implications = mipsolver->mipdata_->implications;
1610+
HighsSparseMatrix& precedenceLb = implications.getPrecedenceDirectedGraphLb();
1611+
1612+
const HighsInt n = precedenceLb.num_col_;
1613+
if (n == 0) return Result::kOk;
1614+
1615+
// Run Tarjan's algorithm to find any strongly connected components
1616+
// If there is a cycle, e.g. x <= y, y <= z, z <= x, then we
1617+
// can conclude that x = y = z.
1618+
std::vector<HighsInt> stronglyConnectedComponents(n, -1);
1619+
tarjan(precedenceLb.start_, precedenceLb.index_, precedenceLb.value_,
1620+
options->small_matrix_value, stronglyConnectedComponents);
1621+
1622+
// Apply substitutions for all strongly connected components
1623+
for (HighsInt substCol = 0; substCol != n; ++substCol) {
1624+
if (colDeleted[substCol]) continue;
1625+
const HighsInt stayCol = stronglyConnectedComponents[substCol];
1626+
if (stayCol == -1 || stayCol == substCol) continue;
1627+
1628+
postsolve_stack.doubletonEquation(
1629+
-1, substCol, stayCol, 1.0, -1, 0, model->col_lower_[substCol],
1630+
model->col_upper_[substCol], 0.0, false, false,
1631+
HighsPostsolveStack::RowType::kEq, HighsEmptySlice());
1632+
1633+
markColDeleted(substCol);
1634+
substitute(substCol, stayCol, 0.0, 1.0);
1635+
}
1636+
1637+
return checkLimits(postsolve_stack);
1638+
}
1639+
14461640
HPresolve::Result HPresolve::prepareProbing(
14471641
HighsPostsolveStack& postsolve_stack, bool& firstCall) {
14481642
HighsDomain& domain = mipsolver->mipdata_->domain;
@@ -1474,6 +1668,9 @@ HPresolve::Result HPresolve::prepareProbing(
14741668
// prepare for domain propagation
14751669
mipsolver->mipdata_->setupDomainPropagation();
14761670

1671+
// Extract precedence constraints to be used for faster propagation
1672+
createPrecedenceGraph(true);
1673+
14771674
// first call?
14781675
firstCall = !mipsolver->mipdata_->cliquesExtracted;
14791676

@@ -5942,6 +6139,15 @@ HPresolve::Result HPresolve::presolve(HighsPostsolveStack& postsolve_stack) {
59426139
if (problemSizeReduction() > 0.05) continue;
59436140
}
59446141

6142+
// Find substitutions from the precedence graph
6143+
if (mipsolver != nullptr &&
6144+
analysis_.allow_rule_[kPresolveRulePrecedenceCycles]) {
6145+
storeCurrentProblemSize();
6146+
createPrecedenceGraph(false);
6147+
HPRESOLVE_CHECKED_CALL(findPrecedenceCycles(postsolve_stack));
6148+
if (problemSizeReduction() > 0.05) continue;
6149+
}
6150+
59456151
// enumerate solutions
59466152
if (mipsolver != nullptr &&
59476153
analysis_.allow_rule_[kPresolveRuleEnumeration]) {
@@ -6341,6 +6547,7 @@ HighsModelStatus HPresolve::run(HighsPostsolveStack& postsolve_stack) {
63416547
mipsolver->mipdata_->domain.addCutpool(mipsolver->mipdata_->cutpool);
63426548
mipsolver->mipdata_->domain.addConflictPool(
63436549
mipsolver->mipdata_->conflictPool);
6550+
createPrecedenceGraph(true);
63446551

63456552
if (mipsolver->mipdata_->numRestarts != 0) {
63466553
std::vector<HighsInt> cutinds;

highs/presolve/HPresolve.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -374,6 +374,23 @@ class HPresolve {
374374

375375
void addToMatrix(const HighsInt row, const HighsInt col, const double val);
376376

377+
void createPrecedenceGraph(bool generateUb) const;
378+
379+
void strongConnect(const std::vector<HighsInt>& start,
380+
const std::vector<HighsInt>& index,
381+
const std::vector<double>& value, double eps, HighsInt v,
382+
HighsInt& time, std::vector<HighsInt>& dfsNum,
383+
std::vector<HighsInt>& lowLink,
384+
std::vector<HighsInt>& stack, std::vector<bool>& onStack,
385+
std::vector<HighsInt>& stronglyConnectedComponents);
386+
387+
void tarjan(const std::vector<HighsInt>& start,
388+
const std::vector<HighsInt>& index,
389+
const std::vector<double>& value, double eps,
390+
std::vector<HighsInt>& stronglyConnectedComponents);
391+
392+
Result findPrecedenceCycles(HighsPostsolveStack& postsolve_stack);
393+
377394
Result prepareProbing(HighsPostsolveStack& postsolve_stack, bool& firstCall);
378395

379396
Result finaliseProbing(HighsPostsolveStack& postsolve_stack, bool firstCall,

0 commit comments

Comments
 (0)