#include "presolve/HighsPostsolveStack.h"
#include <numeric>
#include "lp_data/HConst.h"
#include "lp_data/HighsModelUtils.h"
#include "lp_data/HighsOptions.h"
#include "util/HighsCDouble.h"
#include "util/HighsUtils.h"
namespace presolve {
void HighsPostsolveStack::initializeIndexMaps(HighsInt numRow,
HighsInt numCol) {
origNumRow = numRow;
origNumCol = numCol;
origRowIndex.resize(numRow);
std::iota(origRowIndex.begin(), origRowIndex.end(), 0);
origColIndex.resize(numCol);
std::iota(origColIndex.begin(), origColIndex.end(), 0);
linearlyTransformable.resize(numCol, true);
}
void HighsPostsolveStack::compressIndexMaps(
const std::vector<HighsInt>& newRowIndex,
const std::vector<HighsInt>& newColIndex) {
HighsInt numRow = origRowIndex.size();
for (size_t i = 0; i != newRowIndex.size(); ++i) {
if (newRowIndex[i] == -1)
--numRow;
else
origRowIndex[newRowIndex[i]] = origRowIndex[i];
}
origRowIndex.resize(numRow);
HighsInt numCol = origColIndex.size();
for (size_t i = 0; i != newColIndex.size(); ++i) {
if (newColIndex[i] == -1)
--numCol;
else
origColIndex[newColIndex[i]] = origColIndex[i];
}
origColIndex.resize(numCol);
}
void HighsPostsolveStack::LinearTransform::undo(const HighsOptions& options,
HighsSolution& solution) const {
solution.col_value[col] *= scale;
solution.col_value[col] += constant;
if (solution.dual_valid) solution.col_dual[col] /= scale;
}
void HighsPostsolveStack::LinearTransform::transformToPresolvedSpace(
std::vector<double>& primalSol) const {
primalSol[col] -= constant;
primalSol[col] /= scale;
}
static HighsBasisStatus computeRowStatus(double dual,
HighsPostsolveStack::RowType rowType) {
if (rowType == HighsPostsolveStack::RowType::kEq)
return dual < 0 ? HighsBasisStatus::kUpper : HighsBasisStatus::kLower;
else if (rowType == HighsPostsolveStack::RowType::kGeq)
return HighsBasisStatus::kLower;
else
return HighsBasisStatus::kUpper;
}
void HighsPostsolveStack::FreeColSubstitution::undo(
const HighsOptions& options, const std::vector<Nonzero>& rowValues,
const std::vector<Nonzero>& colValues, HighsSolution& solution,
HighsBasis& basis) {
double colCoef = 0;
HighsCDouble rowValue = 0;
for (const auto& rowVal : rowValues) {
if (rowVal.index == col)
colCoef = rowVal.value;
else
rowValue += rowVal.value * solution.col_value[rowVal.index];
}
assert(colCoef != 0);
if (solution.isModelRow(row))
solution.row_value[row] =
static_cast<double>(rowValue + colCoef * solution.col_value[col]);
solution.col_value[col] = static_cast<double>((rhs - rowValue) / colCoef);
if (!solution.dual_valid) return;
if (solution.isModelRow(row)) {
solution.row_dual[row] = 0;
HighsCDouble dualval = colCost;
for (const auto& colVal : colValues) {
if (solution.isModelRow(colVal.index))
dualval -= colVal.value * solution.row_dual[colVal.index];
}
solution.row_dual[row] = static_cast<double>(dualval / colCoef);
}
solution.col_dual[col] = 0;
if (!basis.valid) return;
basis.col_status[col] = HighsBasisStatus::kBasic;
if (solution.isModelRow(row))
basis.row_status[row] = computeRowStatus(solution.row_dual[row], rowType);
}
static HighsBasisStatus computeStatus(double dual, HighsBasisStatus& status,
double dual_feasibility_tolerance) {
if (dual > dual_feasibility_tolerance)
status = HighsBasisStatus::kLower;
else if (dual < -dual_feasibility_tolerance)
status = HighsBasisStatus::kUpper;
return status;
}
static HighsBasisStatus computeStatus(double dual,
double dual_feasibility_tolerance) {
if (dual > dual_feasibility_tolerance)
return HighsBasisStatus::kLower;
else if (dual < -dual_feasibility_tolerance)
return HighsBasisStatus::kUpper;
else
return HighsBasisStatus::kBasic;
}
void HighsPostsolveStack::DoubletonEquation::undo(
const HighsOptions& options, const std::vector<Nonzero>& colValues,
HighsSolution& solution, HighsBasis& basis) const {
solution.col_value[colSubst] = static_cast<double>(
(rhs - static_cast<HighsCDouble>(coef) * solution.col_value[col]) /
coefSubst);
if (row == -1 || !solution.dual_valid) return;
const HighsBasisStatus colStatus =
!basis.valid
? computeStatus(solution.col_dual[col],
options.dual_feasibility_tolerance)
: computeStatus(solution.col_dual[col], basis.col_status[col],
options.dual_feasibility_tolerance);
HighsCDouble rowDual = 0.0;
if (solution.isModelRow(row)) {
solution.row_dual[row] = 0;
for (const auto& colVal : colValues) {
if (solution.isModelRow(colVal.index))
rowDual -= colVal.value * solution.row_dual[colVal.index];
}
rowDual /= coefSubst;
solution.row_dual[row] = static_cast<double>(rowDual);
}
solution.col_dual[colSubst] = substCost;
solution.col_dual[col] += substCost * coef / coefSubst;
if ((upperTightened && colStatus == HighsBasisStatus::kUpper) ||
(lowerTightened && colStatus == HighsBasisStatus::kLower)) {
double rowDualDelta = solution.col_dual[col] / coef;
if (solution.isModelRow(row))
solution.row_dual[row] = static_cast<double>(rowDual + rowDualDelta);
solution.col_dual[col] = 0.0;
solution.col_dual[colSubst] = static_cast<double>(
static_cast<HighsCDouble>(solution.col_dual[colSubst]) -
rowDualDelta * coefSubst);
if (basis.valid) {
if ((std::signbit(coef) == std::signbit(coefSubst) &&
basis.col_status[col] == HighsBasisStatus::kUpper) ||
(std::signbit(coef) != std::signbit(coefSubst) &&
basis.col_status[col] == HighsBasisStatus::kLower))
basis.col_status[colSubst] = HighsBasisStatus::kLower;
else
basis.col_status[colSubst] = HighsBasisStatus::kUpper;
basis.col_status[col] = HighsBasisStatus::kBasic;
}
} else {
double rowDualDelta = solution.col_dual[colSubst] / coefSubst;
if (solution.isModelRow(row))
solution.row_dual[row] = static_cast<double>(rowDual + rowDualDelta);
solution.col_dual[colSubst] = 0.0;
solution.col_dual[col] =
static_cast<double>(static_cast<HighsCDouble>(solution.col_dual[col]) -
rowDualDelta * coef);
if (basis.valid) basis.col_status[colSubst] = HighsBasisStatus::kBasic;
}
if (!basis.valid) return;
if (solution.isModelRow(row))
basis.row_status[row] = computeRowStatus(solution.row_dual[row], rowType);
}
void HighsPostsolveStack::EqualityRowAddition::undo(
const HighsOptions& options, const std::vector<Nonzero>& eqRowValues,
HighsSolution& solution, HighsBasis& basis) const {
if (!solution.isModelRow(row) || !solution.isModelRow(addedEqRow)) return;
if (!solution.dual_valid || solution.row_dual[row] == 0.0) return;
solution.row_dual[addedEqRow] = static_cast<double>(
static_cast<HighsCDouble>(eqRowScale) * solution.row_dual[row] +
solution.row_dual[addedEqRow]);
assert(!basis.valid);
}
void HighsPostsolveStack::EqualityRowAdditions::undo(
const HighsOptions& options, const std::vector<Nonzero>& eqRowValues,
const std::vector<Nonzero>& targetRows, HighsSolution& solution,
HighsBasis& basis) const {
if (!solution.isModelRow(addedEqRow)) return;
if (!solution.dual_valid) return;
HighsCDouble eqRowDual = solution.row_dual[addedEqRow];
for (const auto& targetRow : targetRows) {
if (solution.isModelRow(targetRow.index))
eqRowDual += static_cast<HighsCDouble>(targetRow.value) *
solution.row_dual[targetRow.index];
}
solution.row_dual[addedEqRow] = static_cast<double>(eqRowDual);
assert(!basis.valid);
}
void HighsPostsolveStack::ForcingColumn::undo(
const HighsOptions& options, const std::vector<Nonzero>& colValues,
HighsSolution& solution, HighsBasis& basis) const {
HighsInt nonbasicRow = -1;
HighsBasisStatus nonbasicRowStatus = HighsBasisStatus::kNonbasic;
double colValFromNonbasicRow = colBound;
HighsInt debug_num_use_row_value = 0;
const bool debug_report = false;
auto computeColVal = [&](HighsInt direction) {
for (const auto& colVal : colValues) {
debug_num_use_row_value++;
if (solution.isModelRow(colVal.index)) {
double colValFromRow = solution.row_value[colVal.index] / colVal.value;
if (direction * colValFromRow > direction * colValFromNonbasicRow) {
nonbasicRow = colVal.index;
colValFromNonbasicRow = colValFromRow;
nonbasicRowStatus = direction * colVal.value > 0
? HighsBasisStatus::kLower
: HighsBasisStatus::kUpper;
}
}
}
if (nonbasicRow != -1 && colIntegral)
colValFromNonbasicRow =
direction * std::ceil(direction * colValFromNonbasicRow -
options.mip_feasibility_tolerance);
};
if (atInfiniteUpper) {
computeColVal(HighsInt{1});
} else {
computeColVal(HighsInt{-1});
}
if (debug_num_use_row_value && debug_report) {
printf(
"HighsPostsolveStack::ForcingColumn::undo Using %d unknown row "
"activit%s\n",
int(debug_num_use_row_value),
debug_num_use_row_value > 1 ? "ies" : "y");
}
solution.col_value[col] = colValFromNonbasicRow;
if (!solution.dual_valid) return;
solution.col_dual[col] = 0.0;
if (!basis.valid) return;
if (nonbasicRow == -1) {
basis.col_status[col] =
atInfiniteUpper ? HighsBasisStatus::kLower : HighsBasisStatus::kUpper;
} else {
basis.col_status[col] = HighsBasisStatus::kBasic;
basis.row_status[nonbasicRow] = nonbasicRowStatus;
}
}
void HighsPostsolveStack::ForcingColumnRemovedRow::undo(
const HighsOptions& options, const std::vector<Nonzero>& rowValues,
HighsSolution& solution, HighsBasis& basis) const {
if (!solution.isModelRow(row)) return;
HighsCDouble val = rhs;
for (const auto& rowVal : rowValues)
val -= rowVal.value * solution.col_value[rowVal.index];
solution.row_value[row] = static_cast<double>(val);
if (solution.dual_valid) solution.row_dual[row] = 0.0;
if (basis.valid) basis.row_status[row] = HighsBasisStatus::kBasic;
}
void HighsPostsolveStack::SingletonRow::undo(const HighsOptions& options,
HighsSolution& solution,
HighsBasis& basis) const {
if (!solution.dual_valid) return;
const HighsBasisStatus colStatus =
!basis.valid
? computeStatus(solution.col_dual[col],
options.dual_feasibility_tolerance)
: computeStatus(solution.col_dual[col], basis.col_status[col],
options.dual_feasibility_tolerance);
if ((!colLowerTightened || colStatus != HighsBasisStatus::kLower) &&
(!colUpperTightened || colStatus != HighsBasisStatus::kUpper)) {
if (solution.isModelRow(row)) {
if (basis.valid) basis.row_status[row] = HighsBasisStatus::kBasic;
solution.row_dual[row] = 0;
}
return;
}
if (solution.isModelRow(row))
solution.row_dual[row] = solution.col_dual[col] / coef;
solution.col_dual[col] = 0;
if (!basis.valid) return;
if (solution.isModelRow(row)) {
switch (colStatus) {
case HighsBasisStatus::kLower:
assert(colLowerTightened);
if (coef > 0)
basis.row_status[row] = HighsBasisStatus::kLower;
else
basis.row_status[row] = HighsBasisStatus::kUpper;
break;
case HighsBasisStatus::kUpper:
if (coef > 0)
basis.row_status[row] = HighsBasisStatus::kUpper;
else
basis.row_status[row] = HighsBasisStatus::kLower;
break;
default:
assert(false);
}
}
basis.col_status[col] = HighsBasisStatus::kBasic;
}
void HighsPostsolveStack::FixedCol::undo(const HighsOptions& options,
const std::vector<Nonzero>& colValues,
HighsSolution& solution,
HighsBasis& basis) const {
solution.col_value[col] = fixValue;
if (!solution.dual_valid) return;
HighsCDouble reducedCost = colCost;
for (const auto& colVal : colValues) {
if (solution.isModelRow(colVal.index))
reducedCost -= colVal.value * solution.row_dual[colVal.index];
}
solution.col_dual[col] = static_cast<double>(reducedCost);
if (basis.valid) {
basis.col_status[col] = fixType;
if (basis.col_status[col] == HighsBasisStatus::kNonbasic)
basis.col_status[col] = solution.col_dual[col] >= 0
? HighsBasisStatus::kLower
: HighsBasisStatus::kUpper;
}
}
void HighsPostsolveStack::RedundantRow::undo(const HighsOptions& options,
HighsSolution& solution,
HighsBasis& basis) const {
if (!solution.isModelRow(row)) return;
if (!solution.dual_valid) return;
solution.row_dual[row] = 0.0;
if (basis.valid) basis.row_status[row] = HighsBasisStatus::kBasic;
}
void HighsPostsolveStack::ForcingRow::undo(
const HighsOptions& options, const std::vector<Nonzero>& rowValues,
HighsSolution& solution, HighsBasis& basis) const {
if (!solution.dual_valid) return;
HighsInt basicCol = -1;
double dualDelta = 0;
HighsInt direction = rowType == RowType::kLeq ? 1 : -1;
for (const auto& rowVal : rowValues) {
double colDual = solution.col_dual[rowVal.index] - rowVal.value * dualDelta;
if (direction * colDual * rowVal.value < 0) {
dualDelta = solution.col_dual[rowVal.index] / rowVal.value;
basicCol = rowVal.index;
}
}
if (basicCol != -1) {
if (solution.isModelRow(row))
solution.row_dual[row] = solution.row_dual[row] + dualDelta;
for (const auto& rowVal : rowValues) {
solution.col_dual[rowVal.index] = static_cast<double>(
solution.col_dual[rowVal.index] -
static_cast<HighsCDouble>(dualDelta) * rowVal.value);
}
solution.col_dual[basicCol] = 0;
if (basis.valid) {
if (solution.isModelRow(row))
basis.row_status[row] =
(rowType == RowType::kGeq ? HighsBasisStatus::kLower
: HighsBasisStatus::kUpper);
basis.col_status[basicCol] = HighsBasisStatus::kBasic;
}
}
}
void HighsPostsolveStack::DuplicateRow::undo(const HighsOptions& options,
HighsSolution& solution,
HighsBasis& basis) const {
if (!solution.isModelRow(row)) return;
if (!solution.dual_valid) return;
if (!rowUpperTightened && !rowLowerTightened) {
if (solution.isModelRow(duplicateRow)) {
solution.row_dual[duplicateRow] = 0.0;
if (basis.valid)
basis.row_status[duplicateRow] = HighsBasisStatus::kBasic;
}
return;
}
const HighsBasisStatus rowStatus =
!basis.valid
? computeStatus(solution.row_dual[row],
options.dual_feasibility_tolerance)
: computeStatus(solution.row_dual[row], basis.row_status[row],
options.dual_feasibility_tolerance);
auto computeRowDualAndStatus = [&](bool tighened) {
if (tighened) {
if (solution.isModelRow(duplicateRow)) {
solution.row_dual[duplicateRow] =
solution.row_dual[row] / duplicateRowScale;
if (basis.valid) {
if (duplicateRowScale > 0)
basis.row_status[duplicateRow] = HighsBasisStatus::kUpper;
else
basis.row_status[duplicateRow] = HighsBasisStatus::kLower;
}
}
solution.row_dual[row] = 0.0;
if (basis.valid) basis.row_status[row] = HighsBasisStatus::kBasic;
} else if (solution.isModelRow(duplicateRow)) {
solution.row_dual[duplicateRow] = 0.0;
if (basis.valid)
basis.row_status[duplicateRow] = HighsBasisStatus::kBasic;
}
};
switch (rowStatus) {
case HighsBasisStatus::kBasic:
if (solution.isModelRow(duplicateRow)) {
solution.row_dual[duplicateRow] = 0.0;
if (basis.valid)
basis.row_status[duplicateRow] = HighsBasisStatus::kBasic;
}
break;
case HighsBasisStatus::kUpper:
computeRowDualAndStatus(rowUpperTightened);
break;
case HighsBasisStatus::kLower:
computeRowDualAndStatus(rowLowerTightened);
break;
default:
assert(false);
}
}
void HighsPostsolveStack::DuplicateColumn::undo(const HighsOptions& options,
HighsSolution& solution,
HighsBasis& basis) const {
const bool debug_report = false;
const double mergeVal = solution.col_value[col];
auto okResidual = [&](const double x, const double y) {
const double check_mergeVal = x + colScale * y;
const double residual = std::fabs(check_mergeVal - mergeVal);
const bool ok_residual = residual <= options.primal_feasibility_tolerance;
if (!ok_residual && debug_report) {
printf(
"HighsPostsolveStack::DuplicateColumn::undo %g + %g.%g = %g != %g: "
"residual = %g\n",
x, colScale, y, check_mergeVal, mergeVal, residual);
}
return ok_residual;
};
auto isAtBound = [&](const double value, const double bound) {
if (value < bound - options.primal_feasibility_tolerance) return false;
if (value <= bound + options.primal_feasibility_tolerance) return true;
return false;
};
if (solution.dual_valid)
solution.col_dual[duplicateCol] = solution.col_dual[col] * colScale;
if (basis.valid) {
switch (basis.col_status[col]) {
case HighsBasisStatus::kLower: {
solution.col_value[col] = colLower;
if (colScale > 0) {
basis.col_status[duplicateCol] = HighsBasisStatus::kLower;
solution.col_value[duplicateCol] = duplicateColLower;
} else {
basis.col_status[duplicateCol] = HighsBasisStatus::kUpper;
solution.col_value[duplicateCol] = duplicateColUpper;
}
assert(okResidual(solution.col_value[col],
solution.col_value[duplicateCol]));
return;
}
case HighsBasisStatus::kUpper: {
solution.col_value[col] = colUpper;
if (colScale > 0) {
basis.col_status[duplicateCol] = HighsBasisStatus::kUpper;
solution.col_value[duplicateCol] = duplicateColUpper;
} else {
basis.col_status[duplicateCol] = HighsBasisStatus::kLower;
solution.col_value[duplicateCol] = duplicateColLower;
}
assert(okResidual(solution.col_value[col],
solution.col_value[duplicateCol]));
return;
}
case HighsBasisStatus::kZero: {
solution.col_value[col] = 0.0;
basis.col_status[duplicateCol] = HighsBasisStatus::kZero;
solution.col_value[duplicateCol] = 0.0;
assert(okResidual(solution.col_value[col],
solution.col_value[duplicateCol]));
return;
}
case HighsBasisStatus::kBasic:
case HighsBasisStatus::kNonbasic:;
}
assert(basis.col_status[col] == HighsBasisStatus::kBasic);
}
if (colLower != -kHighsInf)
solution.col_value[col] = colLower;
else
solution.col_value[col] = std::min(0.0, colUpper);
solution.col_value[duplicateCol] = static_cast<double>(
(static_cast<HighsCDouble>(mergeVal) - solution.col_value[col]) /
colScale);
bool recomputeCol = false;
if (basis.valid) basis.col_status[duplicateCol] = HighsBasisStatus::kNonbasic;
if (solution.col_value[duplicateCol] > duplicateColUpper) {
solution.col_value[duplicateCol] = duplicateColUpper;
recomputeCol = true;
if (basis.valid) basis.col_status[duplicateCol] = HighsBasisStatus::kUpper;
} else if (solution.col_value[duplicateCol] < duplicateColLower) {
solution.col_value[duplicateCol] = duplicateColLower;
recomputeCol = true;
if (basis.valid) basis.col_status[duplicateCol] = HighsBasisStatus::kLower;
} else if (duplicateColIntegral) {
assert(!basis.valid);
if (fractionality(solution.col_value[duplicateCol]) >
options.mip_feasibility_tolerance) {
solution.col_value[duplicateCol] =
std::floor(solution.col_value[duplicateCol]);
recomputeCol = true;
}
}
if (recomputeCol) {
solution.col_value[col] =
mergeVal - colScale * solution.col_value[duplicateCol];
if (!duplicateColIntegral && colIntegral) {
assert(!basis.valid);
solution.col_value[col] = std::ceil(solution.col_value[col] -
options.mip_feasibility_tolerance);
solution.col_value[duplicateCol] = static_cast<double>(
(static_cast<HighsCDouble>(mergeVal) - solution.col_value[col]) /
colScale);
}
} else {
if (basis.valid) {
assert(basis.col_status[col] == HighsBasisStatus::kBasic);
basis.col_status[duplicateCol] = basis.col_status[col];
if (colLower != -kHighsInf) {
assert(solution.col_value[col] == colLower);
basis.col_status[col] = HighsBasisStatus::kLower;
} else if (colUpper <= 0.0) {
assert(solution.col_value[col] == colUpper);
basis.col_status[col] = HighsBasisStatus::kUpper;
} else {
assert(solution.col_value[col] == 0.0);
basis.col_status[col] = HighsBasisStatus::kZero;
if (colUpper < kHighsInf) {
printf(
"HighsPostsolveStack::DuplicateColumn::undo Col is nonbasic at "
"zero with upper bound of %g\n",
colUpper);
assert(111 == 679);
}
}
assert(basis.col_status[duplicateCol] == HighsBasisStatus::kBasic);
}
}
if (basis.valid)
assert(basis.col_status[duplicateCol] != HighsBasisStatus::kNonbasic);
auto hasError = [&]() {
bool illegal_duplicateCol_lower =
solution.col_value[duplicateCol] <
duplicateColLower - options.mip_feasibility_tolerance;
bool illegal_duplicateCol_upper =
solution.col_value[duplicateCol] >
duplicateColUpper + options.mip_feasibility_tolerance;
bool illegal_col_lower =
solution.col_value[col] < colLower - options.mip_feasibility_tolerance;
bool illegal_col_upper =
solution.col_value[col] > colUpper + options.mip_feasibility_tolerance;
bool illegal_residual =
!okResidual(solution.col_value[col], solution.col_value[duplicateCol]);
return (illegal_duplicateCol_lower || illegal_duplicateCol_upper ||
illegal_col_lower || illegal_col_upper || illegal_residual);
};
if (hasError()) {
if (debug_report)
printf(
"DuplicateColumn::undo error: col = %d(%g), duplicateCol = %d(%g)\n"
"%g\n%g\n%g %g %d\n%g %g %d\n",
int(col), solution.col_value[col], int(duplicateCol),
solution.col_value[duplicateCol], mergeVal, colScale, colLower,
colUpper, colIntegral, duplicateColLower, duplicateColUpper,
duplicateColIntegral);
undoFix(options, solution, mergeVal);
} else {
return;
}
const bool allow_assert = false;
if (allow_assert) assert(!hasError());
if (basis.valid) {
bool duplicateCol_basic = false;
if (duplicateColLower <= -kHighsInf && duplicateColUpper >= kHighsInf) {
if (solution.col_value[duplicateCol] == 0) {
basis.col_status[col] = HighsBasisStatus::kBasic;
basis.col_status[duplicateCol] = HighsBasisStatus::kZero;
} else {
duplicateCol_basic = true;
}
} else if (isAtBound(solution.col_value[duplicateCol], duplicateColLower)) {
basis.col_status[col] = HighsBasisStatus::kBasic;
basis.col_status[duplicateCol] = HighsBasisStatus::kLower;
} else if (isAtBound(solution.col_value[duplicateCol], duplicateColUpper)) {
basis.col_status[col] = HighsBasisStatus::kBasic;
basis.col_status[duplicateCol] = HighsBasisStatus::kUpper;
} else {
duplicateCol_basic = true;
}
if (duplicateCol_basic) {
basis.col_status[duplicateCol] = HighsBasisStatus::kBasic;
if (isAtBound(solution.col_value[col], colLower)) {
basis.col_status[col] = HighsBasisStatus::kLower;
} else if (isAtBound(solution.col_value[col], colUpper)) {
basis.col_status[col] = HighsBasisStatus::kUpper;
} else {
basis.col_status[col] = HighsBasisStatus::kNonbasic;
if (debug_report)
printf(
"When demerging, neither col nor duplicateCol can be "
"nonbasic\n");
if (kAllowDeveloperAssert) assert(666 == 999);
}
}
}
}
bool HighsPostsolveStack::DuplicateColumn::okMerge(
const double tolerance) const {
const double scale = colScale;
const bool x_int = colIntegral;
const bool y_int = duplicateColIntegral;
const double x_lo = x_int ? std::ceil(colLower - tolerance) : colLower;
const double x_up = x_int ? std::floor(colUpper + tolerance) : colUpper;
const double y_lo =
y_int ? std::ceil(duplicateColLower - tolerance) : duplicateColLower;
const double y_up =
y_int ? std::floor(duplicateColUpper + tolerance) : duplicateColUpper;
const double x_len = x_up - x_lo;
const double y_len = y_up - y_lo;
std::string newline = "\n";
bool ok_merge = true;
const bool debug_report = false;
if (scale == 0) {
if (debug_report)
printf("%sDuplicateColumn::checkMerge: Scale cannot be zero\n",
newline.c_str());
newline = "";
ok_merge = false;
}
const double abs_scale = std::fabs(scale);
if (x_int) {
if (y_int) {
if (fractionality(scale) > tolerance) {
if (debug_report)
printf(
"%sDuplicateColumn::checkMerge: scale must be integer, but is "
"%g\n",
newline.c_str(), scale);
newline = "";
ok_merge = false;
}
double scale_limit = x_len + 1 + tolerance;
if (abs_scale > scale_limit) {
if (debug_report)
printf(
"%sDuplicateColumn::checkMerge: scale = %g, but |scale| cannot "
"exceed %g since x is [%g, %g]\n",
newline.c_str(), scale, scale_limit, x_lo, x_up);
newline = "";
ok_merge = false;
}
} else {
if (debug_report)
printf("DuplicateColumn::checkMerge: x-integer; y-continuous\n");
if (y_len == 0) {
if (debug_report)
printf(
"%sDuplicateColumn::checkMerge: scale = %g is too small in "
"magnitude, as y is [%g, %g]\n",
newline.c_str(), scale, y_lo, y_up);
newline = "";
ok_merge = false;
} else {
double scale_limit = 1 / y_len;
if (abs_scale < scale_limit) {
if (debug_report)
printf(
"%sDuplicateColumn::checkMerge: scale = %g, but |scale| must "
"be "
"at least %g since y is [%g, %g]\n",
newline.c_str(), scale, scale_limit, y_lo, y_up);
newline = "";
ok_merge = false;
}
}
}
} else {
if (y_int) {
if (debug_report)
printf("DuplicateColumn::checkMerge: x-continuous; y-integer\n");
double scale_limit = x_len;
if (abs_scale > scale_limit) {
if (debug_report)
printf(
"%sDuplicateColumn::checkMerge: scale = %g, but |scale| must "
"be "
"at "
"most %g since x is [%g, %g]\n",
newline.c_str(), scale, scale_limit, x_lo, x_up);
newline = "";
ok_merge = false;
}
} else {
}
}
return ok_merge;
}
void HighsPostsolveStack::DuplicateColumn::undoFix(
const HighsOptions& options, HighsSolution& solution,
const double mergeValue) const {
const double mip_feasibility_tolerance = options.mip_feasibility_tolerance;
const double primal_feasibility_tolerance =
options.primal_feasibility_tolerance;
std::vector<double>& col_value = solution.col_value;
const bool allow_assert = false;
const bool debug_report = false;
auto isInteger = [&](const double v) {
return (fractionality(v) <= mip_feasibility_tolerance);
};
auto isFeasible = [&](const double l, const double v, const double u) {
return v >= l - primal_feasibility_tolerance &&
v <= u + primal_feasibility_tolerance;
};
const double scale = colScale;
const bool x_int = colIntegral;
const bool y_int = duplicateColIntegral;
const int x_ix = col;
const int y_ix = duplicateCol;
const double x_lo =
x_int ? std::ceil(colLower - mip_feasibility_tolerance) : colLower;
const double x_up =
x_int ? std::floor(colUpper + mip_feasibility_tolerance) : colUpper;
const double y_lo =
y_int ? std::ceil(duplicateColLower - mip_feasibility_tolerance)
: duplicateColLower;
const double y_up =
y_int ? std::floor(duplicateColUpper + mip_feasibility_tolerance)
: duplicateColUpper;
if (kAllowDeveloperAssert) assert(scale);
double x_v = mergeValue;
double y_v = -kHighsInf;
auto checkIntVar = [](double z_lo, double z_up, double& z_0, double& z_d,
double& z_1, bool& z_free) {
const double value_max = 1000;
z_0 = 0;
z_d = 0;
z_1 = 0;
z_free = false;
if (z_lo <= -kHighsInf) {
if (z_up >= kHighsInf) {
z_free = true;
z_0 = 0;
z_d = 1.0;
z_1 = value_max;
} else {
z_0 = z_up;
z_d = -1.0;
z_1 = -value_max;
}
} else {
z_0 = z_lo;
z_d = 1.0;
z_1 = z_up >= kHighsInf ? value_max : z_up;
}
};
auto computeValue = [mergeValue, scale](double value) {
return static_cast<double>((static_cast<HighsCDouble>(mergeValue) - value) /
scale);
};
auto computeInvValue = [mergeValue, scale](double value) {
return static_cast<double>(static_cast<HighsCDouble>(mergeValue) -
static_cast<HighsCDouble>(value) * scale);
};
auto findValue = [&](double& z_value, double z_0, double z_1, double z_delta,
double& other_value, double other_lower,
double other_upper, bool other_int) {
const double eps = 1e-8;
for (z_value = z_0;; z_value += z_delta) {
other_value = computeValue(z_value);
if (isFeasible(other_lower, other_value, other_upper) &&
(!other_int || isInteger(other_value)))
return true;
if (z_delta > 0 && z_value + z_delta >= z_1 + eps) return false;
if (z_delta < 0 && z_value + z_delta <= z_1 - eps) return false;
}
return false;
};
auto setXValue = [&](double value) {
x_v = value;
y_v = computeValue(value);
};
auto setYValue = [&](double value) {
y_v = value;
x_v = computeInvValue(value);
};
if (x_int) {
double x_0;
double x_d;
double x_1;
bool x_free;
checkIntVar(x_lo, x_up, x_0, x_d, x_1, x_free);
if (x_free && debug_report) printf("DuplicateColumn::undo x is free\n");
if (debug_report)
printf("DuplicateColumn::undo Using x (%g; %g; %g)\n", x_0, x_d, x_1);
bool found_y = findValue(x_v, x_0, x_1, x_d, y_v, y_lo, y_up, y_int);
if (allow_assert) assert(found_y);
} else if (y_int) {
double y_0;
double y_d;
double y_1;
bool y_free;
checkIntVar(y_lo, y_up, y_0, y_d, y_1, y_free);
if (y_free && debug_report) printf("DuplicateColumn::undo y is free\n");
if (debug_report)
printf("DuplicateColumn::undo Using y (%g; %g; %g)\n", y_0, y_d, y_1);
bool found_x = findValue(y_v, y_0, y_1, y_d, x_v, x_lo, x_up, x_int);
if (allow_assert) assert(found_x);
} else {
double v_m_a_ylo = 0;
double v_m_a_yup = 0;
if (y_lo <= -kHighsInf) {
v_m_a_ylo = scale > 0 ? kHighsInf : -kHighsInf;
} else {
v_m_a_ylo = computeInvValue(y_lo);
}
if (y_up >= kHighsInf) {
v_m_a_yup = scale > 0 ? -kHighsInf : kHighsInf;
} else {
v_m_a_yup = computeInvValue(y_up);
}
if (scale > 0) {
if (debug_report)
printf("DuplicateColumn::undo [V-a(y_u), V-a(y_l)] == [%g, %g]\n",
v_m_a_yup, v_m_a_ylo);
if (y_up < kHighsInf) {
if (kAllowDeveloperAssert)
assert(x_up + primal_feasibility_tolerance >= v_m_a_yup);
setYValue(y_up);
if (x_v < x_lo - primal_feasibility_tolerance) {
setXValue(x_lo);
if (y_v < y_lo - primal_feasibility_tolerance) {
setXValue(x_lo - primal_feasibility_tolerance);
}
}
} else if (y_lo > -kHighsInf) {
if (kAllowDeveloperAssert)
assert(x_lo - primal_feasibility_tolerance <= v_m_a_ylo);
setYValue(y_lo);
if (x_v > x_up + primal_feasibility_tolerance) {
setXValue(x_up);
if (y_v > y_up + primal_feasibility_tolerance) {
setXValue(x_up + primal_feasibility_tolerance);
}
}
} else {
setXValue(std::max(0.0, x_lo));
}
} else {
if (debug_report)
printf("DuplicateColumn::undo [V-a(y_l), V-a(y_u)] == [%g, %g]\n",
v_m_a_ylo, v_m_a_yup);
if (y_lo > -kHighsInf) {
if (kAllowDeveloperAssert)
assert(x_up + primal_feasibility_tolerance >= v_m_a_ylo);
setYValue(y_lo);
if (x_v < x_lo - primal_feasibility_tolerance) {
setXValue(x_lo);
if (y_v > y_up + primal_feasibility_tolerance) {
setXValue(x_lo - primal_feasibility_tolerance);
}
}
} else if (y_up < kHighsInf) {
if (kAllowDeveloperAssert)
assert(x_lo - primal_feasibility_tolerance <= v_m_a_yup);
setYValue(y_up);
if (x_v > x_up + primal_feasibility_tolerance) {
setXValue(x_up);
if (y_v < y_lo - primal_feasibility_tolerance) {
setXValue(x_up + primal_feasibility_tolerance);
}
}
} else {
setXValue(std::max(0.0, x_lo));
}
}
}
const double residual_tolerance = 1e-12;
double residual = std::fabs(static_cast<double>(
static_cast<HighsCDouble>(x_v) - computeInvValue(y_v)));
const bool x_y_ok =
isFeasible(x_lo, x_v, x_up) && isFeasible(y_lo, y_v, y_up) &&
(!x_int || isInteger(x_v)) && (!y_int || isInteger(y_v)) &&
(std::fabs(x_v) < kHighsInf) && (std::fabs(y_v) < kHighsInf) &&
(residual <= residual_tolerance);
bool check;
check = isFeasible(x_lo, x_v, x_up);
if (!check) {
if (debug_report)
printf(
"DuplicateColumn::undo error: isFeasible(x_lo, x_v, x_up) is "
"false\n");
if (allow_assert) assert(check);
}
check = isFeasible(y_lo, y_v, y_up);
if (!check) {
if (debug_report)
printf(
"DuplicateColumn::undo error: isFeasible(y_lo, y_v, y_up) is "
"false\n");
if (allow_assert) assert(check);
}
check = !x_int || isInteger(x_v);
if (!check) {
if (debug_report)
printf(
"DuplicateColumn::undo error: !x_int || isInteger(x_v) is false\n");
if (allow_assert) assert(check);
}
check = !y_int || isInteger(y_v);
if (!check) {
if (debug_report)
printf(
"DuplicateColumn::undo error: !y_int || isInteger(y_v) is false\n");
if (allow_assert) assert(check);
}
check = std::fabs(x_v) < kHighsInf;
if (!check) {
if (debug_report)
printf(
"DuplicateColumn::undo error: std::fabs(x_v) < kHighsInf is "
"false\n");
if (allow_assert) assert(check);
}
check = std::fabs(y_v) < kHighsInf;
if (!check) {
if (debug_report)
printf(
"DuplicateColumn::undo error: std::fabs(y_v) < kHighsInf is "
"false\n");
if (allow_assert) assert(check);
}
check = residual <= residual_tolerance;
if (!check) {
if (debug_report)
printf(
"DuplicateColumn::undo error: residual <= residual_tolerance is "
"false\n");
if (allow_assert) assert(check);
}
check = residual <= residual_tolerance;
if (debug_report)
printf("DuplicateColumn::undo%s x = %g; y = %g to give x + (%g)y = %g",
x_y_ok ? "" : " ERROR", x_v, y_v, scale, mergeValue);
if (x_y_ok) {
if (debug_report) printf(": FIXED\n");
} else if (check) {
if (debug_report) printf("\n");
} else {
if (debug_report) printf(": residual = %g\n", residual);
}
if (x_y_ok) {
col_value[x_ix] = x_v;
col_value[y_ix] = y_v;
}
}
void HighsPostsolveStack::DuplicateColumn::transformToPresolvedSpace(
std::vector<double>& primalSol) const {
primalSol[col] = primalSol[col] + colScale * primalSol[duplicateCol];
}
void HighsPostsolveStack::SlackColSubstitution::undo(
const HighsOptions& options, const std::vector<Nonzero>& rowValues,
HighsSolution& solution, HighsBasis& basis) {
bool debug_print = false;
double colCoef = 0;
HighsCDouble rowValue = 0;
for (const auto& rowVal : rowValues) {
if (rowVal.index == col)
colCoef = rowVal.value;
else
rowValue += rowVal.value * solution.col_value[rowVal.index];
}
assert(colCoef != 0);
if (solution.isModelRow(row))
solution.row_value[row] =
static_cast<double>(rowValue + colCoef * solution.col_value[col]);
solution.col_value[col] = static_cast<double>((rhs - rowValue) / colCoef);
if (!solution.dual_valid) return;
if (solution.isModelRow(row))
solution.col_dual[col] = -solution.row_dual[row] / colCoef;
if (!basis.valid) return;
if (solution.isModelRow(row)) {
HighsBasisStatus save_row_basis_status = basis.row_status[row];
if (basis.row_status[row] == HighsBasisStatus::kBasic) {
basis.col_status[col] = HighsBasisStatus::kBasic;
basis.row_status[row] =
computeRowStatus(solution.row_dual[row], RowType::kEq);
} else if (basis.row_status[row] == HighsBasisStatus::kLower) {
basis.col_status[col] =
colCoef > 0 ? HighsBasisStatus::kUpper : HighsBasisStatus::kLower;
} else {
basis.col_status[col] =
colCoef > 0 ? HighsBasisStatus::kLower : HighsBasisStatus::kUpper;
}
if (debug_print)
printf(
"HighsPostsolveStack::SlackColSubstitution::undo OgRowStatus = %s; "
"RowStatus = %s; ColStatus = %s\n",
utilBasisStatusToString(save_row_basis_status).c_str(),
utilBasisStatusToString(basis.row_status[row]).c_str(),
utilBasisStatusToString(basis.col_status[col]).c_str());
if (basis.col_status[col] == HighsBasisStatus::kLower) {
assert(solution.col_dual[col] > -options.dual_feasibility_tolerance);
} else if (basis.col_status[col] == HighsBasisStatus::kUpper) {
assert(solution.col_dual[col] < options.dual_feasibility_tolerance);
}
} else {
basis.col_status[col] = HighsBasisStatus::kNonbasic;
}
}
}