Skip to content

Commit 9d9fdf6

Browse files
committed
Merged latest into this branch
2 parents 3a95f64 + 54ec2b7 commit 9d9fdf6

File tree

2 files changed

+122
-5
lines changed

2 files changed

+122
-5
lines changed

check/TestFilereader.cpp

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,4 +575,20 @@ TEST_CASE("read-highs-lp-file1", "[highs_filereader]") {
575575
std::remove(model_file_name.c_str());
576576

577577
h.resetGlobalScheduler(true);
578+
579+
TEST_CASE("lp-duplicate-variable", "[highs_filereader]") {
580+
const std::string test_name = Catch::getResultCapture().getCurrentTestName();
581+
std::string lp_file = test_name + ".lp";
582+
FILE* file = fopen(lp_file.c_str(), "w");
583+
std::string file_content =
584+
"Minimize\n obj: 2 x + y + z\nSubject To\nr0: 2 x + y - x + 0 z >= "
585+
"2\nr1: y + x - y >= 1\nEnd\n";
586+
if (dev_run) printf("Using .lp file\n%s", file_content.c_str());
587+
fprintf(file, "%s", file_content.c_str());
588+
fclose(file);
589+
Highs h;
590+
h.setOptionValue("output_flag", dev_run);
591+
REQUIRE(h.readModel(lp_file) == HighsStatus::kWarning);
592+
593+
std::remove(lp_file.c_str());
578594
}

highs/io/FilereaderLp.cpp

Lines changed: 106 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
FilereaderRetcode FilereaderLp::readModelFromFile(const HighsOptions& options,
2323
const std::string filename,
2424
HighsModel& model) {
25+
bool warning_issued = false;
2526
HighsLp& lp = model.lp_;
2627
HighsHessian& hessian = model.hessian_;
2728
try {
@@ -168,26 +169,126 @@ FilereaderRetcode FilereaderLp::readModelFromFile(const HighsOptions& options,
168169
"with same prefix: row names cleared\n");
169170
}
170171

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

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

0 commit comments

Comments
 (0)