Skip to content

Commit 81fcd9d

Browse files
committed
Need to be able to generate the IIS LP internally for checking IIS
1 parent f287093 commit 81fcd9d

File tree

6 files changed

+334
-2
lines changed

6 files changed

+334
-2
lines changed

check/TestIis.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,63 @@ TEST_CASE("lp-empty-infeasible-row", "[iis]") {
120120
highs.resetGlobalScheduler(true);
121121
}
122122

123+
TEST_CASE("lp-get-iis-light", "[iis]") {
124+
HighsLp lp;
125+
lp.num_col_ = 4;
126+
lp.num_row_ = 3;
127+
lp.col_cost_ = {0, 0, 0, 0};
128+
lp.col_lower_ = {10, 10, 10, 10};
129+
lp.col_upper_ = {20, 20, 20, 20};
130+
lp.row_lower_ = {-inf, -10, -34};
131+
lp.row_upper_ = { 30, 15, inf};
132+
lp.a_matrix_.format_ = MatrixFormat::kRowwise;
133+
lp.a_matrix_.num_col_ = lp.num_col_;
134+
lp.a_matrix_.num_row_ = lp.num_row_;
135+
lp.a_matrix_.start_ = {0, 3, 7, 10};
136+
lp.a_matrix_.index_ = { 0, 1, 2, 0, 1, 2, 3, 1, 2, 3};
137+
lp.a_matrix_.value_ = {1.5, 2, 1, 4, -2, 1, 2, -2, -1.5, -1};
138+
Highs highs;
139+
highs.setOptionValue("output_flag", dev_run);
140+
highs.passModel(lp);
141+
highs.setOptionValue("iis_strategy", kIisStrategyLight);
142+
HighsIis iis;
143+
HighsLp iis_lp;
144+
145+
for (int l = 0; l < 3; l++) {
146+
for (int k = 0; k < 2; k++) {
147+
REQUIRE(highs.getIis(iis) == HighsStatus::kOk);
148+
REQUIRE(highs.getModelStatus() == HighsModelStatus::kInfeasible);
149+
REQUIRE(highs.getIisLp(iis_lp) == HighsStatus::kOk);
150+
const bool write_model = true;
151+
if (dev_run && write_model) {
152+
highs.writeModel("");
153+
printf("\nNow pass IIS LP to write it out\n");
154+
highs.passModel(iis_lp);
155+
highs.writeModel("");
156+
}
157+
checkIisLp(lp, iis, iis_lp);
158+
159+
highs.passModel(lp);
160+
highs.getIis(iis);
161+
REQUIRE(highs.checkIis() == HighsStatus::kOk);
162+
163+
if (k == 0) {
164+
// Now flip to column-wise for code coverage
165+
lp.a_matrix_.ensureColwise();
166+
highs.passModel(lp);
167+
}
168+
}
169+
if (l == 0) {
170+
lp.row_upper_[0] = inf;
171+
} else if (l == 1) {
172+
lp.row_upper_[1] = inf;
173+
}
174+
highs.passModel(lp);
175+
}
176+
highs.resetGlobalScheduler(true);
177+
178+
}
179+
123180
TEST_CASE("lp-get-iis", "[iis]") {
124181
HighsLp lp;
125182
lp.model_name_ = "lp-get-iis";

highs/Highs.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,11 @@ class Highs {
573573
*/
574574
HighsStatus getIis(HighsIis& iis);
575575

576+
/**
577+
* @brief Checks the IIS information for the incumbent model
578+
*/
579+
HighsStatus checkIis() const;
580+
576581
/**
577582
* @brief Get an LP corresponding to (any) irreducible infeasible subsystem
578583
* (IIS) information for the incumbent model

highs/lp_data/Highs.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1938,6 +1938,21 @@ HighsStatus Highs::getIis(HighsIis& iis) {
19381938
return return_status;
19391939
}
19401940

1941+
HighsStatus Highs::checkIis() const {
1942+
if (this->iis_.valid_) {
1943+
if (this->iis_.ok(this->model_.lp_, this->options_)) {
1944+
highsLogUser(options_.log_options, HighsLogType::kInfo,
1945+
"IIS is correct\n");
1946+
return HighsStatus::kOk;
1947+
} else {
1948+
return HighsStatus::kError;
1949+
}
1950+
}
1951+
highsLogUser(options_.log_options, HighsLogType::kInfo,
1952+
"IIS is not known\n");
1953+
return HighsStatus::kOk;
1954+
}
1955+
19411956
HighsStatus Highs::getIisLp(HighsLp& iis_lp) {
19421957
HighsStatus return_status = HighsStatus::kOk;
19431958

highs/lp_data/HighsIis.cpp

Lines changed: 242 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
*/
1111

1212
#include "Highs.h"
13+
#include <tuple>
1314

1415
void HighsIis::invalidate() {
1516
this->valid_ = false;
@@ -533,3 +534,244 @@ HighsStatus HighsIis::compute(const HighsLp& lp, const HighsOptions& options,
533534
this->strategy_ = options.iis_strategy;
534535
return HighsStatus::kOk;
535536
}
537+
538+
bool HighsIis::rowValueBounds(const HighsLp& lp, const HighsOptions& options) {
539+
// Look for infeasible rows based on row value bounds
540+
this->invalidate();
541+
std::vector<double> lower_value;
542+
std::vector<double> upper_value;
543+
if (lp.a_matrix_.isColwise()) {
544+
lower_value.assign(lp.num_row_, 0);
545+
upper_value.assign(lp.num_row_, 0);
546+
for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) {
547+
const double lower = lp.col_lower_[iCol];
548+
const double upper = lp.col_upper_[iCol];
549+
for (HighsInt iEl = lp.a_matrix_.start_[iCol]; iEl < lp.a_matrix_.start_[iCol+1]; iEl++) {
550+
HighsInt iRow = lp.a_matrix_.index_[iEl];
551+
double value = lp.a_matrix_.value_[iEl];
552+
if (value > 0) {
553+
lower_value[iRow] += value * lower;
554+
upper_value[iRow] += value * upper;
555+
} else {
556+
lower_value[iRow] += value * upper;
557+
upper_value[iRow] += value * lower;
558+
}
559+
}
560+
}
561+
} else {
562+
for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) {
563+
double lower_row_value = 0;
564+
double upper_row_value = 0;
565+
for (HighsInt iEl = lp.a_matrix_.start_[iRow]; iEl < lp.a_matrix_.start_[iRow+1]; iEl++) {
566+
HighsInt iCol = lp.a_matrix_.index_[iEl];
567+
const double lower = lp.col_lower_[iCol];
568+
const double upper = lp.col_upper_[iCol];
569+
double value = lp.a_matrix_.value_[iEl];
570+
if (value > 0) {
571+
lower_row_value += value * lower;
572+
upper_row_value += value * upper;
573+
} else {
574+
lower_row_value += value * upper;
575+
upper_row_value += value * lower;
576+
}
577+
}
578+
lower_value[iRow] = lower_row_value;
579+
upper_value[iRow] = upper_row_value;
580+
}
581+
}
582+
bool below_lower;
583+
bool above_upper;
584+
for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) {
585+
below_lower = upper_value[iRow] < lp.row_lower_[iRow] - options.primal_feasibility_tolerance;
586+
above_upper = lower_value[iRow] > lp.row_upper_[iRow] + options.primal_feasibility_tolerance;
587+
if (below_lower || above_upper) {
588+
this->row_index_.push_back(iRow);
589+
if (below_lower) {
590+
this->row_bound_.push_back(kIisBoundStatusLower);
591+
} else {
592+
this->row_bound_.push_back(kIisBoundStatusUpper);
593+
}
594+
break;
595+
}
596+
}
597+
if (this->row_index_.size() == 0) return false;
598+
assert(below_lower || above_upper);
599+
assert(!(below_lower && above_upper));
600+
// Found an infeasible row
601+
HighsInt iRow = this->row_index_[0];
602+
if (below_lower) {
603+
printf("Row %d has maximum row value of %g, below lower bound of %g\n",
604+
int(iRow), upper_value[iRow], lp.row_lower_[iRow]);
605+
} else {
606+
printf("Row %d has minimum row value of %g, above upper bound of %g\n",
607+
int(iRow), lower_value[iRow], lp.row_upper_[iRow]);
608+
}
609+
std::vector<std::tuple<double, HighsInt, double>> row_data;
610+
double activity = 0;
611+
// Lambda for adding row data tuples
612+
auto addRowData = [&](HighsInt iCol, double value) {
613+
double bound;
614+
if (below_lower) {
615+
if (value > 0) {
616+
bound = lp.col_upper_[iCol];
617+
} else {
618+
bound = lp.col_lower_[iCol];
619+
}
620+
} else {
621+
if (value > 0) {
622+
bound = lp.col_lower_[iCol];
623+
} else {
624+
bound = lp.col_upper_[iCol];
625+
}
626+
}
627+
double term = bound * value;
628+
activity += term;
629+
printf("addRowData: tuple(%g, %d, %g)\n", term, int(iCol), value);
630+
row_data.push_back(std::make_tuple(term, iCol, value));
631+
};
632+
if (lp.a_matrix_.isColwise()) {
633+
for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) {
634+
for (HighsInt iEl = lp.a_matrix_.start_[iCol]; iEl < lp.a_matrix_.start_[iCol+1]; iEl++) {
635+
if (lp.a_matrix_.index_[iEl] == iRow)
636+
addRowData(iCol, lp.a_matrix_.value_[iEl]);
637+
}
638+
}
639+
} else {
640+
for (HighsInt iEl = lp.a_matrix_.start_[iRow]; iEl < lp.a_matrix_.start_[iRow+1]; iEl++)
641+
addRowData(lp.a_matrix_.index_[iEl], lp.a_matrix_.value_[iEl]);
642+
}
643+
if (below_lower) {
644+
assert(std::fabs(activity-upper_value[iRow]) < 1e-5);
645+
} else {
646+
assert(std::fabs(activity-lower_value[iRow]) < 1e-5);
647+
}
648+
std::sort(row_data.begin(), row_data.end());
649+
HighsInt row_data_size = row_data.size();
650+
for (HighsInt iisCol = 0; iisCol < row_data_size; iisCol++) {
651+
652+
printf("row_data: tuple(%g, %d, %g)\n",
653+
std::get<0>(row_data[iisCol]),
654+
int(std::get<1>(row_data[iisCol])),
655+
std::get<2>(row_data[iisCol]));
656+
}
657+
658+
// Determine whether any entries can be removed
659+
if (below_lower) {
660+
const double lower = lp.row_lower_[iRow] - options.primal_feasibility_tolerance;
661+
// Loop through the terms from most positive to most negative
662+
// removing them from the activity, until it is within the bound,
663+
// then use that column and any others in the IIS
664+
for (HighsInt iisCol = row_data_size-1; iisCol >= 0; iisCol--) {
665+
const double term = std::get<0>(row_data[iisCol]);
666+
const HighsInt iCol = std::get<1>(row_data[iisCol]);
667+
const double value = std::get<2>(row_data[iisCol]);
668+
double activity_m_term = activity-term;
669+
printf("Deletion loop: iisCol = %2d; iCol = %3d; term = %11.4g; value = %11.4g; lower = %11.4g; activity = %11.4g; activity - term = %11.4g\n",
670+
int(iisCol), int(iCol), term, value, lower, activity, activity_m_term);
671+
assert(activity < lower);
672+
if (activity - term <= lower) {
673+
// Column not in IIS
674+
activity -= term;
675+
} else {
676+
// Column in IIS
677+
this->col_index_.push_back(iCol);
678+
double bound;
679+
if (value > 0) {
680+
this->col_bound_.push_back(kIisBoundStatusUpper);
681+
bound = lp.col_upper_[iCol];
682+
} else {
683+
this->col_bound_.push_back(kIisBoundStatusLower);
684+
bound = lp.col_lower_[iCol];
685+
}
686+
if (bound * value != term) {
687+
printf("Deletion loop %d: bound(%g) * value(%g) = %g != %g = term\n",
688+
int(iisCol), bound, value, bound * value, term);
689+
}
690+
assert(bound * value == term);
691+
}
692+
}
693+
} else {
694+
const double upper = lp.row_upper_[iRow] + options.primal_feasibility_tolerance;
695+
// Loop through the terms from most negative to most positive,
696+
// removing them from the activity, until it is within the bound,
697+
// then use that column and any others in the IIS
698+
for (HighsInt iisCol = 0; iisCol < row_data_size; iisCol++) {
699+
const double term = std::get<0>(row_data[iisCol]);
700+
const HighsInt iCol = std::get<1>(row_data[iisCol]);
701+
const double value = std::get<2>(row_data[iisCol]);
702+
printf("Deletion loop: iisCol = %2d; iCol = %3d; term = %11.4g; value = %11.4g; upper = %11.4g; activity = %11.4g; activity - term = %11.4g\n",
703+
int(iisCol), int(iCol), term, value, upper, activity, activity-term);
704+
assert(activity > upper);
705+
if (activity - term >= upper) {
706+
// Column not in IIS
707+
activity -= term;
708+
} else {
709+
// Column in IIS
710+
this->col_index_.push_back(iCol);
711+
double bound;
712+
if (value > 0) {
713+
this->col_bound_.push_back(kIisBoundStatusLower);
714+
bound = lp.col_lower_[iCol];
715+
} else {
716+
this->col_bound_.push_back(kIisBoundStatusUpper);
717+
bound = lp.col_upper_[iCol];
718+
}
719+
assert(bound * value == term);
720+
}
721+
}
722+
}
723+
// There must be at least one column in the IIS
724+
assert(this->col_index_.size() > 0);
725+
this->valid_ = true;
726+
return this->valid_;
727+
}
728+
729+
bool HighsIis::ok(const HighsLp& lp, const HighsOptions& options) const {
730+
if (!this->valid_) return true;
731+
const HighsLogOptions& log_options = options.log_options;
732+
Highs h;
733+
h.passOptions(options);
734+
h.passModel(lp);
735+
h.run();
736+
if (h.getModelStatus() != HighsModelStatus::kInfeasible) {
737+
highsLogUser(log_options, HighsLogType::kError, "HighsIis: Given LP is not infeasible\n");
738+
return false;
739+
}
740+
auto optimalOrUnbounded = [&]() -> bool {
741+
h.writeModel("");
742+
h.run();
743+
return
744+
h.getModelStatus() == HighsModelStatus::kOptimal ||
745+
h.getModelStatus() == HighsModelStatus::kUnbounded;
746+
};
747+
HighsInt num_iis_col = this->col_index_.size();
748+
HighsInt num_iis_row = this->row_index_.size();
749+
for (HighsInt iisCol = 0; iisCol < num_iis_col; iisCol++) {
750+
HighsInt iCol = this->col_index_[iisCol];
751+
if (this->col_bound_[iisCol] == kIisBoundStatusLower ||
752+
this->col_bound_[iisCol] == kIisBoundStatusBoxed) {
753+
h.changeColBounds(iCol, -kHighsInf, lp.col_upper_[iCol]);
754+
if (!optimalOrUnbounded()) {
755+
highsLogUser(log_options, HighsLogType::kError,
756+
"HighsIis: IIS column %d (LP column %d): relaxing lower bound of %g does not yield LP with optimal or unbounded status\n",
757+
int(iisCol), int(iCol), lp.col_lower_[iCol]);
758+
return false;
759+
}
760+
h.changeColBounds(iCol, lp.col_lower_[iCol], lp.col_upper_[iCol]);
761+
}
762+
if (this->col_bound_[iisCol] == kIisBoundStatusUpper ||
763+
this->col_bound_[iisCol] == kIisBoundStatusBoxed) {
764+
h.changeColBounds(iCol, lp.col_lower_[iCol], kHighsInf);
765+
if (!optimalOrUnbounded()) {
766+
highsLogUser(log_options, HighsLogType::kError,
767+
"HighsIis: IIS column %d (LP column %d): relaxing upper bound of %g does not yield LP with optimal or unbounded status\n",
768+
int(iisCol), int(iCol), lp.col_upper_[iCol]);
769+
return false;
770+
}
771+
h.changeColBounds(iCol, lp.col_lower_[iCol], lp.col_upper_[iCol]);
772+
}
773+
}
774+
return true;
775+
776+
777+
}

highs/lp_data/HighsIis.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ class HighsIis {
4848
const HighsBasis* basis = nullptr);
4949

5050
bool trivial(const HighsLp& lp, const HighsOptions& options);
51+
bool rowValueBounds(const HighsLp& lp, const HighsOptions& options);
52+
bool ok(const HighsLp& lp, const HighsOptions& options) const;
5153

5254
// Data members
5355
bool valid_ = false;

highs/lp_data/HighsInterface.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1884,9 +1884,12 @@ HighsStatus Highs::getIisInterface() {
18841884
}
18851885
if (this->iis_.valid_) return HighsStatus::kOk;
18861886
this->iis_.invalidate();
1887-
HighsLp& lp = model_.lp_;
1887+
const HighsLp& lp = model_.lp_;
18881888
// Check for trivial IIS: empty infeasible row or inconsistent bounds
1889-
if (this->iis_.trivial(lp, options_)) return HighsStatus::kOk;
1889+
if (this->iis_.trivial(lp, options_)) {
1890+
this->model_status_ = HighsModelStatus::kInfeasible;
1891+
return HighsStatus::kOk;
1892+
}
18901893
HighsInt num_row = lp.num_row_;
18911894
if (num_row == 0) {
18921895
// For an LP with no rows, the only scope for infeasibility is
@@ -1895,6 +1898,14 @@ HighsStatus Highs::getIisInterface() {
18951898
this->iis_.valid_ = true;
18961899
return HighsStatus::kOk;
18971900
}
1901+
// Look for infeasible rows based on row value bounds
1902+
if (this->iis_.rowValueBounds(lp, options_)) {
1903+
this->model_status_ = HighsModelStatus::kInfeasible;
1904+
return HighsStatus::kOk;
1905+
}
1906+
// Don't continue with more expensive techniques if using the IIS
1907+
// light strategy
1908+
if (options_.iis_strategy == kIisStrategyLight) return HighsStatus::kOk;
18981909
const bool ray_option = false;
18991910
// options_.iis_strategy == kIisStrategyFromRayRowPriority ||
19001911
// options_.iis_strategy == kIisStrategyFromRayColPriority;

0 commit comments

Comments
 (0)