Skip to content

Commit 54ec2b7

Browse files
authored
Merge pull request #2470 from ERGO-Code/fix-2469
Now summing duplicate matrix entries resulting from repeated terms in .lp constraints
2 parents 768d44e + 6b226e8 commit 54ec2b7

File tree

4 files changed

+126
-6
lines changed

4 files changed

+126
-6
lines changed

check/TestFilereader.cpp

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,3 +477,20 @@ TEST_CASE("mps-silly-names", "[highs_filereader]") {
477477
HighsStatus return_status = h.readModel(model_file);
478478
REQUIRE(return_status == HighsStatus::kOk);
479479
}
480+
481+
TEST_CASE("lp-duplicate-variable", "[highs_filereader]") {
482+
const std::string test_name = Catch::getResultCapture().getCurrentTestName();
483+
std::string lp_file = test_name + ".lp";
484+
FILE* file = fopen(lp_file.c_str(), "w");
485+
std::string file_content =
486+
"Minimize\n obj: 2 x + y + z\nSubject To\nr0: 2 x + y - x + 0 z >= "
487+
"2\nr1: y + x - y >= 1\nEnd\n";
488+
if (dev_run) printf("Using .lp file\n%s", file_content.c_str());
489+
fprintf(file, "%s", file_content.c_str());
490+
fclose(file);
491+
Highs h;
492+
h.setOptionValue("output_flag", dev_run);
493+
REQUIRE(h.readModel(lp_file) == HighsStatus::kWarning);
494+
495+
std::remove(lp_file.c_str());
496+
}

check/TestMipSolver.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -946,6 +946,7 @@ TEST_CASE("issue-2409", "[highs_test_mip_solver]") {
946946
const HighsModelStatus require_model_status = HighsModelStatus::kOptimal;
947947
const double optimal_objective = 0.1;
948948
Highs highs;
949+
highs.setOptionValue("output_flag", dev_run);
949950
REQUIRE(highs.passModel(lp) == HighsStatus::kOk);
950951
if (dev_run) printf("Testing that presolve reduces the problem to empty\n");
951952
REQUIRE(highs.presolve() == HighsStatus::kOk);
@@ -982,6 +983,7 @@ TEST_CASE("issue-2432", "[highs_test_mip_solver]") {
982983
const HighsModelStatus require_model_status = HighsModelStatus::kOptimal;
983984
const double optimal_objective = -3777.57124352;
984985
Highs highs;
986+
highs.setOptionValue("output_flag", dev_run);
985987
REQUIRE(highs.passModel(lp) == HighsStatus::kOk);
986988
if (dev_run) printf("Testing that presolve reduces the problem\n");
987989
REQUIRE(highs.presolve() == HighsStatus::kOk);

check/TestRays.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#include "catch.hpp"
55
#include "lp_data/HConst.h"
66

7-
const bool dev_run = true; // false;
7+
const bool dev_run = false; // true; //
88
const double zero_ray_value_tolerance = 1e-14;
99

1010
void reportRay(std::string message, HighsInt dim, double* computed,

highs/io/FilereaderLp.cpp

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const bool allow_model_names = true;
2525
FilereaderRetcode FilereaderLp::readModelFromFile(const HighsOptions& options,
2626
const std::string filename,
2727
HighsModel& model) {
28+
bool warning_issued = false;
2829
HighsLp& lp = model.lp_;
2930
HighsHessian& hessian = model.hessian_;
3031
try {
@@ -171,26 +172,126 @@ FilereaderRetcode FilereaderLp::readModelFromFile(const HighsOptions& options,
171172
"with same prefix: row names cleared\n");
172173
}
173174

174-
HighsInt nz = 0;
175+
HighsInt num_nz = 0;
175176
// lp.a_matrix_ is initialised with start_[0] for fictitious
176177
// column 0, so have to clear this before pushing back start
177178
lp.a_matrix_.start_.clear();
178179
assert((int)lp.a_matrix_.start_.size() == 0);
179180
for (const auto& var : m.variables) {
180-
lp.a_matrix_.start_.push_back(nz);
181+
lp.a_matrix_.start_.push_back(num_nz);
181182
for (size_t j = 0; j < consofvarmap_index[var].size(); j++) {
182183
double value = consofvarmap_value[var][j];
183184
if (value) {
184185
lp.a_matrix_.index_.push_back(consofvarmap_index[var][j]);
185186
lp.a_matrix_.value_.push_back(value);
186-
nz++;
187+
num_nz++;
187188
}
188189
}
189190
}
190-
lp.a_matrix_.start_.push_back(nz);
191+
lp.a_matrix_.start_.push_back(num_nz);
191192
lp.a_matrix_.format_ = MatrixFormat::kColwise;
192193
lp.sense_ = m.sense == ObjectiveSense::MIN ? ObjSense::kMinimize
193194
: ObjSense::kMaximize;
195+
// In a .lp file, more than one term involving the same variable
196+
// can appear in a constraint, resulting in repeated row indices
197+
// in a column
198+
HighsSparseMatrix& matrix = lp.a_matrix_;
199+
assert(matrix.isColwise());
200+
num_nz = 0;
201+
std::vector<double> column(lp.num_row_, 0);
202+
std::vector<HighsInt> nz_count(lp.num_row_, 0);
203+
std::vector<HighsInt> zero_count(lp.num_row_, 0);
204+
HighsInt sum_num_duplicate = 0;
205+
HighsInt sum_num_zero = 0;
206+
HighsInt sum_cancellation = 0;
207+
HighsInt num_report = 0;
208+
HighsInt max_num_report = 10;
209+
for (HighsInt iCol = 0; iCol < lp.num_col_; iCol++) {
210+
// Save the start, since this will be reduced if there are
211+
// repeated row indices in a column
212+
const HighsInt from_el = matrix.start_[iCol];
213+
for (HighsInt iEl = from_el; iEl < matrix.start_[iCol + 1]; iEl++) {
214+
// Add in the value to zero or any previous nonzero in this
215+
// row
216+
HighsInt iRow = matrix.index_[iEl];
217+
double value = matrix.value_[iEl];
218+
if (value) {
219+
column[iRow] += value;
220+
nz_count[iRow]++;
221+
} else {
222+
zero_count[iRow]++;
223+
}
224+
}
225+
// Pass through the column again, storing and then zeroing the
226+
// entries in column - both to eliminate the duplicate and
227+
// ensure that column is zeroed for the next matrix column.
228+
matrix.start_[iCol] = num_nz;
229+
for (HighsInt iEl = from_el; iEl < matrix.start_[iCol + 1]; iEl++) {
230+
HighsInt iRow = matrix.index_[iEl];
231+
if (column[iRow]) {
232+
assert(num_nz <= iEl);
233+
matrix.index_[num_nz] = iRow;
234+
matrix.value_[num_nz] = column[iRow];
235+
num_nz++;
236+
}
237+
// Report explicit zeros and/or sum to a nonzero value
238+
HighsInt num_ocurrence = zero_count[iRow] + nz_count[iRow];
239+
if (num_ocurrence > 1) {
240+
if (nz_count[iRow] > 1) {
241+
if (num_report < max_num_report)
242+
highsLogUser(options.log_options, HighsLogType::kWarning,
243+
"Column %d (name \"%s\") occurs %d times in row %d "
244+
"(name \"%s\"): values summed to %g\n",
245+
int(iCol), lp.col_names_[iCol].c_str(),
246+
int(num_ocurrence), int(iRow),
247+
lp.row_names_[iRow].c_str(), column[iRow]);
248+
num_report++;
249+
}
250+
if (zero_count[iRow] > 0) {
251+
if (num_report < max_num_report)
252+
highsLogUser(options.log_options, HighsLogType::kWarning,
253+
"Column %d (name \"%s\") contains %d explicit zero "
254+
"coefficient%s in row %d (name \"%s\")\n",
255+
int(iCol), lp.col_names_[iCol].c_str(),
256+
int(zero_count[iRow]),
257+
zero_count[iRow] > 1 ? "s" : "", int(iRow),
258+
lp.row_names_[iRow].c_str());
259+
num_report++;
260+
}
261+
sum_num_duplicate += (num_ocurrence - 1);
262+
sum_num_zero += zero_count[iRow];
263+
if (column[iRow] == 0 && nz_count[iRow] > 0) sum_cancellation++;
264+
}
265+
zero_count[iRow] = 0;
266+
nz_count[iRow] = 0;
267+
column[iRow] = 0;
268+
}
269+
for (HighsInt iRow = 0; iRow < lp.num_row_; iRow++) {
270+
assert(zero_count[iRow] == 0);
271+
assert(nz_count[iRow] == 0);
272+
assert(column[iRow] == 0);
273+
}
274+
}
275+
matrix.start_[lp.num_col_] = num_nz;
276+
warning_issued = sum_num_duplicate > 0 || sum_num_zero > 0;
277+
HighsInt num_report_skipped = num_report - max_num_report;
278+
if (num_report_skipped > 0)
279+
highsLogUser(options.log_options, HighsLogType::kInfo,
280+
"Skipped %d further warning%s of this kind\n",
281+
int(num_report_skipped), num_report_skipped > 1 ? "s" : "");
282+
283+
if (sum_num_duplicate > 0)
284+
highsLogUser(options.log_options, HighsLogType::kWarning,
285+
"lp file contains %d repeated variable%s in constraints: "
286+
"summing them yielded %d cancellation%s\n",
287+
int(sum_num_duplicate), sum_num_duplicate > 1 ? "s" : "",
288+
int(sum_cancellation),
289+
(sum_cancellation == 0 || sum_cancellation > 1) ? "s" : "");
290+
if (sum_num_zero > 0)
291+
highsLogUser(options.log_options, HighsLogType::kWarning,
292+
"lp file contains %d explicit zero%s\n", int(sum_num_zero),
293+
sum_num_zero > 1 ? "s" : "");
294+
194295
} catch (std::invalid_argument& ex) {
195296
// lpassert in extern/filereaderlp/def.hpp throws
196297
// std::invalid_argument whatever the error. Hence, unless
@@ -205,7 +306,7 @@ FilereaderRetcode FilereaderLp::readModelFromFile(const HighsOptions& options,
205306
return FilereaderRetcode::kParserError;
206307
}
207308
lp.ensureColwise();
208-
return FilereaderRetcode::kOk;
309+
return warning_issued ? FilereaderRetcode::kWarning : FilereaderRetcode::kOk;
209310
}
210311

211312
void FilereaderLp::writeToFile(FILE* file, const char* format, ...) {

0 commit comments

Comments
 (0)