/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /* */ /* This file is part of the HiGHS linear optimization suite */ /* */ /* Written and engineered 2008-2022 at the University of Edinburgh */ /* */ /* Available as open-source under the MIT License */ /* */ /* Authors: Julian Hall, Ivet Galabova, Leona Gottwald and Michael */ /* Feldmeier */ /* */ /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */ /**@file io/HMPSIO.cpp * @brief */ #include "io/HMPSIO.h" #include #include #include "lp_data/HConst.h" #include "lp_data/HighsLp.h" #include "lp_data/HighsModelUtils.h" #include "lp_data/HighsOptions.h" #include "util/HighsUtils.h" #include "util/stringutil.h" #ifdef ZLIB_FOUND #include "zstr.hpp" #endif using std::map; // // Read file called filename. Returns 0 if OK and 1 if file can't be opened // FilereaderRetcode readMps( const HighsLogOptions& log_options, const std::string filename, HighsInt mxNumRow, HighsInt mxNumCol, HighsInt& numRow, HighsInt& numCol, ObjSense& objSense, double& objOffset, vector& Astart, vector& Aindex, vector& Avalue, vector& colCost, vector& colLower, vector& colUpper, vector& rowLower, vector& rowUpper, vector& integerColumn, std::string& objective_name, vector& col_names, vector& row_names, HighsInt& Qdim, vector& Qstart, vector& Qindex, vector& Qvalue, HighsInt& cost_row_location, const HighsInt keep_n_rows) { // MPS file buffer numRow = 0; numCol = 0; cost_row_location = -1; objOffset = 0; objSense = ObjSense::kMinimize; objective_name = ""; // Astart.clear() added since setting Astart.push_back(0) in // setup_clearModel() messes up the MPS read Astart.clear(); highsLogDev(log_options, HighsLogType::kInfo, "readMPS: Trying to open file %s\n", filename.c_str()); #ifdef ZLIB_FOUND zstr::ifstream file; try { file.open(filename, std::ios::in); } catch (const strict_fstream::Exception& e) { highsLogDev(log_options, HighsLogType::kInfo, e.what()); return FilereaderRetcode::kFileNotFound; } #else std::ifstream file; file.open(filename, std::ios::in); #endif if (!file.is_open()) { highsLogDev(log_options, HighsLogType::kInfo, "readMPS: Not opened file OK\n"); return FilereaderRetcode::kFileNotFound; } highsLogDev(log_options, HighsLogType::kInfo, "readMPS: Opened file OK\n"); // Input buffer const HighsInt lmax = 128; char line[lmax]; char flag[2] = {0, 0}; double data[3]; HighsInt num_alien_entries = 0; HighsVarType integerCol = HighsVarType::kContinuous; // Load NAME load_mpsLine(file, integerCol, lmax, line, flag, data); highsLogDev(log_options, HighsLogType::kInfo, "readMPS: Read NAME OK\n"); // Load OBJSENSE or ROWS load_mpsLine(file, integerCol, lmax, line, flag, data); if (flag[0] == 'O') { // Found OBJSENSE load_mpsLine(file, integerCol, lmax, line, flag, data); std::string sense(&line[2], &line[2] + 3); // the sense must be "MAX" or "MIN" if (sense.compare("MAX") == 0) { objSense = ObjSense::kMaximize; } else if (sense.compare("MIN") == 0) { objSense = ObjSense::kMinimize; } else { return FilereaderRetcode::kParserError; } highsLogDev(log_options, HighsLogType::kInfo, "readMPS: Read OBJSENSE OK\n"); // Load ROWS load_mpsLine(file, integerCol, lmax, line, flag, data); } row_names.clear(); col_names.clear(); vector rowType; map rowIndex; double objName = 0; while (load_mpsLine(file, integerCol, lmax, line, flag, data)) { if (flag[0] == 'N' && (objName == 0 || keep_n_rows == kKeepNRowsDeleteRows)) { // N-row: take the first as the objective and possibly ignore any others if (objName == 0) { objName = data[1]; std::string name(&line[4], &line[4] + 8); objective_name = trim(name); cost_row_location = numRow; } } else { if (mxNumRow > 0 && numRow >= mxNumRow) return FilereaderRetcode::kParserError; rowType.push_back(flag[0]); // rowIndex is used to get the row index from a row name in the // COLUMNS, RHS and RANGES section. However, if this contains a // reference to a row that isn't in the ROWS section the value // of rowIndex is zero. Unless the value associated with the // name in rowIndex is one more than the index of the row, this // return of zero leads to data relating to row 0 being // over-written and (generally) corrupted. rowIndex[data[1]] = ++numRow; std::string name(&line[4], &line[4] + 8); name = trim(name); row_names.push_back(name); } } highsLogDev(log_options, HighsLogType::kInfo, "readMPS: Read ROWS OK\n"); // Load COLUMNS map colIndex; double lastName = 0; // flag[1] is used to indicate whether there is more to read on the // line - field 5 non-empty. save_flag1 is used to deduce whether // the row name and value are from fields 5 and 6, or 3 and 4 HighsInt save_flag1 = 0; while (load_mpsLine(file, integerCol, lmax, line, flag, data)) { HighsInt iRow = rowIndex[data[2]] - 1; std::string name = ""; if (iRow >= 0) name = row_names[iRow]; if (lastName != data[1]) { // New column if (mxNumCol > 0 && numCol >= mxNumCol) return FilereaderRetcode::kParserError; lastName = data[1]; // colIndex is used to get the column index from a column name // in the BOUNDS section. However, if this contains a reference // to a column that isn't in the COLUMNS section the value of // colIndex is zero. Unless the value associated with the name // in colIndex is one more than the index of the column, this // return of zero leads to the bounds on column 0 being // over-written and (generally) corrupted. colIndex[data[1]] = ++numCol; colCost.push_back(0); Astart.push_back(Aindex.size()); integerColumn.push_back(integerCol); std::string name(&line[field_2_start], &line[field_2_start] + field_2_width); name = trim(name); col_names.push_back(name); } if (data[2] == objName) // Cost colCost.back() = data[0]; else if (data[0] != 0) { HighsInt iRow = rowIndex[data[2]] - 1; if (iRow >= 0) { if (rowType[iRow] != 'N' || keep_n_rows != kKeepNRowsDeleteEntries) { Aindex.push_back(iRow); Avalue.push_back(data[0]); } } else { // Spurious row name std::string name; if (!save_flag1) { std::string field_3(&line[field_3_start], &line[field_3_start] + field_3_width); name = field_3; } else { std::string field_5(&line[field_5_start], &line[field_5_start] + field_5_width); name = field_5; } num_alien_entries++; highsLogDev( log_options, HighsLogType::kInfo, "COLUMNS section contains row %-8s not in ROWS section, line: " "%s\n", name.c_str(), line); } } save_flag1 = flag[1]; } Astart.push_back(Aindex.size()); if (num_alien_entries) highsLogUser(log_options, HighsLogType::kWarning, "COLUMNS section entries contain %8" HIGHSINT_FORMAT " with row not in ROWS " " section: ignored\n", num_alien_entries); highsLogDev(log_options, HighsLogType::kInfo, "readMPS: Read COLUMNS OK\n"); // Load RHS num_alien_entries = 0; vector RHS(numRow, 0); save_flag1 = 0; while (load_mpsLine(file, integerCol, lmax, line, flag, data)) { if (data[2] != objName) { HighsInt iRow = rowIndex[data[2]] - 1; if (iRow >= 0) { RHS[iRow] = data[0]; } else { // Spurious row name std::string name; if (!save_flag1) { std::string field_3(&line[field_3_start], &line[field_3_start] + field_3_width); name = field_3; } else { std::string field_5(&line[field_5_start], &line[field_5_start] + field_5_width); name = field_5; } num_alien_entries++; highsLogUser(log_options, HighsLogType::kInfo, "RHS section contains row %-8s not in ROWS " "section, line: %s\n", name.c_str(), line); } } else { // Treat negation of a RHS entry for the N row as an objective // offset. Not all MPS readers do this, so give different // reported objective values for problems (eg e226) highsLogDev( log_options, HighsLogType::kInfo, "Using RHS value of %g for N-row in MPS file as negated objective " "offset\n", data[0]); objOffset = -data[0]; // Objective offset } save_flag1 = flag[1]; } if (num_alien_entries) highsLogUser(log_options, HighsLogType::kWarning, "RHS section entries contain %8" HIGHSINT_FORMAT " with row not in ROWS " " section: ignored\n", num_alien_entries); highsLogDev(log_options, HighsLogType::kInfo, "readMPS: Read RHS OK\n"); // Load RANGES num_alien_entries = 0; rowLower.resize(numRow); rowUpper.resize(numRow); if (flag[0] == 'R') { save_flag1 = 0; while (load_mpsLine(file, integerCol, lmax, line, flag, data)) { HighsInt iRow = rowIndex[data[2]] - 1; if (iRow >= 0) { if (rowType[iRow] == 'L' || (rowType[iRow] == 'E' && data[0] < 0)) { rowLower[iRow] = RHS[iRow] - fabs(data[0]); rowUpper[iRow] = RHS[iRow]; } else { rowUpper[iRow] = RHS[iRow] + fabs(data[0]); rowLower[iRow] = RHS[iRow]; } rowType[iRow] = 'X'; } else { // Spurious row name std::string name; if (!save_flag1) { std::string field_3(&line[field_3_start], &line[field_3_start] + field_3_width); name = field_3; } else { std::string field_5(&line[field_5_start], &line[field_5_start] + field_5_width); name = field_5; } num_alien_entries++; highsLogDev( log_options, HighsLogType::kInfo, "RANGES section contains row %-8s not in ROWS section, line: " "%s\n", name.c_str(), line); } save_flag1 = flag[1]; } } // Setup bounds for row without 'RANGE' for (HighsInt iRow = 0; iRow < numRow; iRow++) { switch (rowType[iRow]) { case 'L': rowLower[iRow] = -kHighsInf; rowUpper[iRow] = RHS[iRow]; break; case 'G': rowLower[iRow] = RHS[iRow]; rowUpper[iRow] = +kHighsInf; break; case 'E': rowLower[iRow] = RHS[iRow]; rowUpper[iRow] = RHS[iRow]; break; case 'N': rowLower[iRow] = -kHighsInf; rowUpper[iRow] = +kHighsInf; break; case 'X': break; } } if (num_alien_entries) highsLogUser(log_options, HighsLogType::kWarning, "RANGES section entries contain %8" HIGHSINT_FORMAT " with row not in ROWS " " section: ignored\n", num_alien_entries); highsLogDev(log_options, HighsLogType::kInfo, "readMPS: Read RANGES OK\n"); // Load BOUNDS num_alien_entries = 0; colLower.assign(numCol, 0); colUpper.assign(numCol, kHighsInf); if (flag[0] == 'B') { while (load_mpsLine(file, integerCol, lmax, line, flag, data)) { // Find the column index associated woith the name "data[2]". If // the name is in colIndex then the value stored is the true // column index plus one. Otherwise 0 will be returned. HighsInt iCol = colIndex[data[2]] - 1; if (iCol >= 0) { switch (flag[0]) { case 'O': /*LO*/ colLower[iCol] = data[0]; break; case 'I': /*MI*/ colLower[iCol] = -kHighsInf; break; case 'L': /*PL*/ colUpper[iCol] = kHighsInf; break; case 'X': /*FX*/ colLower[iCol] = data[0]; colUpper[iCol] = data[0]; break; case 'R': /*FR*/ colLower[iCol] = -kHighsInf; colUpper[iCol] = kHighsInf; break; case 'P': /*UP*/ colUpper[iCol] = data[0]; if (colLower[iCol] == 0 && data[0] < 0) colLower[iCol] = -kHighsInf; break; } } else { std::string name(&line[field_3_start], &line[field_3_start] + field_3_width); num_alien_entries++; highsLogDev( log_options, HighsLogType::kInfo, "BOUNDS section contains col %-8s not in COLUMNS section, line: " "%s\n", name.c_str(), line); } } } // Load Hessian if (flag[0] == 'Q') { highsLogUser( log_options, HighsLogType::kWarning, "Quadratic section: under development. Assumes QUADOBJ section\n"); Qdim = numCol; HighsInt hessian_nz = 0; HighsInt previous_col = -1; bool has_diagonal = false; Qstart.clear(); while (load_mpsLine(file, integerCol, lmax, line, flag, data)) { HighsInt iCol0 = colIndex[data[1]] - 1; std::string name0 = ""; if (iCol0 >= 0) name0 = col_names[iCol0]; HighsInt iCol1 = colIndex[data[2]] - 1; std::string name1 = ""; if (iCol1 >= 0) name1 = col_names[iCol1]; double value = data[0]; if (iCol0 != previous_col) { // Can't handle columns out of order if (iCol0 < previous_col) { return FilereaderRetcode::kParserError; } else if (iCol0 > previous_col) { // A previous column has entries, but not on the diagonal if (previous_col >= 0 && !has_diagonal) return FilereaderRetcode::kParserError; // New column, so set up starts of any intermediate (empty) columns for (HighsInt iCol = previous_col + 1; iCol < iCol0; iCol++) Qstart.push_back(hessian_nz); Qstart.push_back(hessian_nz); previous_col = iCol0; has_diagonal = false; } } // Assumes QUADOBJ, so iCol1 cannot be less than iCol0 if (iCol1 < iCol0) return FilereaderRetcode::kParserError; if (iCol1 == iCol0) has_diagonal = true; if (value) { Qindex.push_back(iCol1); Qvalue.push_back(value); hessian_nz++; } } // Hessian entries have been read, so set up starts of any remaining (empty) // columns for (HighsInt iCol = previous_col + 1; iCol < numCol; iCol++) Qstart.push_back(hessian_nz); Qstart.push_back(hessian_nz); assert((HighsInt)Qstart.size() == Qdim + 1); assert((HighsInt)Qindex.size() == hessian_nz); assert((HighsInt)Qvalue.size() == hessian_nz); } // Determine the number of integer variables and set bounds of [0,1] // for integer variables without bounds HighsInt num_int = 0; for (HighsInt iCol = 0; iCol < numCol; iCol++) { if (integerColumn[iCol] == HighsVarType::kInteger) { num_int++; if (colUpper[iCol] >= kHighsInf) colUpper[iCol] = 1; } } if (num_alien_entries) highsLogUser(log_options, HighsLogType::kWarning, "BOUNDS section entries contain %8" HIGHSINT_FORMAT " with col not in " "COLUMNS section: ignored\n", num_alien_entries); highsLogDev(log_options, HighsLogType::kInfo, "readMPS: Read BOUNDS OK\n"); highsLogDev(log_options, HighsLogType::kInfo, "readMPS: Read ENDATA OK\n"); highsLogDev(log_options, HighsLogType::kInfo, "readMPS: Model has %" HIGHSINT_FORMAT " rows and %" HIGHSINT_FORMAT " columns with %" HIGHSINT_FORMAT " integer\n", numRow, numCol, num_int); // Load ENDATA and close file file.close(); // If there are no integer variables then clear the integrality vector if (!num_int) integerColumn.clear(); return FilereaderRetcode::kOk; } bool load_mpsLine(std::istream& file, HighsVarType& integerVar, HighsInt lmax, char* line, char* flag, double* data) { HighsInt F1 = 1, F2 = 4, F3 = 14, F4 = 24, F5 = 39, F6 = 49; // check the buffer if (flag[1]) { flag[1] = 0; memcpy(&data[2], &line[F5], 8); data[0] = atof(&line[F6]); return true; } // try to read some to the line for (;;) { // Line input *line = '\0'; file.get(line, lmax); if (*line == '\0' && file.eof()) // nothing read and EOF return false; // Line trim -- to delete tailing white spaces HighsInt lcnt = strlen(line) - 1; // if file.get() did not stop because it reached the lmax-1 limit, // then because it reached a newline char (or eof); lets consume this // newline (or do nothing if eof) if (lcnt + 1 < lmax - 1) file.get(); while (isspace(line[lcnt]) && lcnt >= 0) lcnt--; if (lcnt <= 0 || line[0] == '*') continue; // Line expand -- to get data easier lcnt++; while (lcnt < F4) line[lcnt++] = ' '; // For row and bound row name if (lcnt == F4) line[lcnt++] = '0'; // For bound value line[lcnt] = '\0'; // Done with section symbol if (line[0] != ' ') { flag[0] = line[0]; return false; } if (line[F3] == '\'') { if (line[F3 + 1] == 'M' && line[F3 + 2] == 'A' && line[F3 + 3] == 'R' && line[F3 + 4] == 'K' && line[F3 + 5] == 'E' && line[F3 + 6] == 'R') { HighsInt cnter = line[F3 + 8]; while (line[cnter] != '\'') ++cnter; if (line[cnter + 1] == 'I' && line[cnter + 2] == 'N' && line[cnter + 3] == 'T' && line[cnter + 4] == 'O' && line[cnter + 5] == 'R' && line[cnter + 6] == 'G') integerVar = HighsVarType::kInteger; else if (line[cnter + 1] == 'I' && line[cnter + 2] == 'N' && line[cnter + 3] == 'T' && line[cnter + 4] == 'E' && line[cnter + 5] == 'N' && line[cnter + 6] == 'D') integerVar = HighsVarType::kContinuous; continue; } } // Read major symbol & name flag[0] = line[F1 + 1] == ' ' ? line[F1] : line[F1 + 1]; memcpy(&data[1], &line[F2], 8); // Read 1st minor name & value to output memcpy(&data[2], &line[F3], 8); data[0] = atof(&line[F4]); // Keep 2nd minor name & value for future if (lcnt > F5) flag[1] = 1; break; } return true; } HighsStatus writeModelAsMps(const HighsOptions& options, const std::string filename, const HighsModel& model, const bool free_format) { bool warning_found = false; const HighsLp& lp = model.lp_; const HighsHessian& hessian = model.hessian_; bool have_col_names = lp.col_names_.size(); bool have_row_names = lp.row_names_.size(); std::vector local_col_names; std::vector local_row_names; local_col_names.resize(lp.num_col_); local_row_names.resize(lp.num_row_); // Initialise the local names to any existing names if (have_col_names) local_col_names = lp.col_names_; if (have_row_names) local_row_names = lp.row_names_; // // Normalise the column names HighsInt max_col_name_length = kHighsIInf; if (!free_format) max_col_name_length = 8; HighsStatus col_name_status = normaliseNames(options.log_options, "column", lp.num_col_, local_col_names, max_col_name_length); if (col_name_status == HighsStatus::kError) return col_name_status; warning_found = col_name_status == HighsStatus::kWarning || warning_found; // // Normalise the row names HighsInt max_row_name_length = kHighsIInf; if (!free_format) max_row_name_length = 8; HighsStatus row_name_status = normaliseNames(options.log_options, "row", lp.num_row_, local_row_names, max_row_name_length); if (row_name_status == HighsStatus::kError) return col_name_status; warning_found = row_name_status == HighsStatus::kWarning || warning_found; HighsInt max_name_length = std::max(max_col_name_length, max_row_name_length); bool use_free_format = free_format; if (!free_format) { if (max_name_length > 8) { highsLogUser(options.log_options, HighsLogType::kWarning, "Maximum name length is %" HIGHSINT_FORMAT " so using free format rather " "than fixed format\n", max_name_length); use_free_format = true; warning_found = true; } } // Set a local objective name, creating one if necessary const std::string local_objective_name = findModelObjectiveName(&lp, &hessian); // If there is Hessian data to write out, writeMps assumes that hessian is // triangular if (hessian.dim_) assert(hessian.format_ == HessianFormat::kTriangular); HighsStatus write_status = writeMps( options.log_options, filename, lp.model_name_, lp.num_row_, lp.num_col_, hessian.dim_, lp.sense_, lp.offset_, lp.col_cost_, lp.col_lower_, lp.col_upper_, lp.row_lower_, lp.row_upper_, lp.a_matrix_.start_, lp.a_matrix_.index_, lp.a_matrix_.value_, hessian.start_, hessian.index_, hessian.value_, lp.integrality_, local_objective_name, local_col_names, local_row_names, use_free_format); if (write_status == HighsStatus::kOk && warning_found) return HighsStatus::kWarning; return write_status; } HighsStatus writeMps( const HighsLogOptions& log_options, const std::string filename, const std::string model_name, const HighsInt& num_row, const HighsInt& num_col, const HighsInt& q_dim, const ObjSense& sense, const double& offset, const vector& col_cost, const vector& col_lower, const vector& col_upper, const vector& row_lower, const vector& row_upper, const vector& a_start, const vector& a_index, const vector& a_value, const vector& q_start, const vector& q_index, const vector& q_value, const vector& integrality, const std::string objective_name, const vector& col_names, const vector& row_names, const bool use_free_format) { const bool write_zero_no_cost_columns = true; HighsInt num_zero_no_cost_columns = 0; HighsInt num_zero_no_cost_columns_in_bounds_section = 0; highsLogDev(log_options, HighsLogType::kInfo, "writeMPS: Trying to open file %s\n", filename.c_str()); FILE* file = fopen(filename.c_str(), "w"); if (file == 0) { highsLogUser(log_options, HighsLogType::kError, "Cannot open file %s\n", filename.c_str()); return HighsStatus::kError; } highsLogDev(log_options, HighsLogType::kInfo, "writeMPS: Opened file OK\n"); // Check that the names are no longer than 8 characters for fixed format write HighsInt max_col_name_length = maxNameLength(num_col, col_names); HighsInt max_row_name_length = maxNameLength(num_row, row_names); HighsInt max_name_length = std::max(max_col_name_length, max_row_name_length); if (!use_free_format && max_name_length > 8) { highsLogUser( log_options, HighsLogType::kError, "Cannot write fixed MPS with names of length (up to) %" HIGHSINT_FORMAT "\n", max_name_length); return HighsStatus::kError; } assert(objective_name != ""); vector r_ty; vector rhs, ranges; bool have_rhs = false; bool have_ranges = false; bool have_bounds = false; bool have_int = false; r_ty.resize(num_row); rhs.assign(num_row, 0); ranges.assign(num_row, 0); for (HighsInt r_n = 0; r_n < num_row; r_n++) { if (row_lower[r_n] == row_upper[r_n]) { // Equality constraint - Type E - range = 0 r_ty[r_n] = MPS_ROW_TY_E; rhs[r_n] = row_lower[r_n]; } else if (!highs_isInfinity(row_upper[r_n])) { // Upper bounded constraint - Type L r_ty[r_n] = MPS_ROW_TY_L; rhs[r_n] = row_upper[r_n]; if (!highs_isInfinity(-row_lower[r_n])) { // Boxed constraint - range = u-l ranges[r_n] = row_upper[r_n] - row_lower[r_n]; } } else if (!highs_isInfinity(-row_lower[r_n])) { // Lower bounded constraint - Type G r_ty[r_n] = MPS_ROW_TY_G; rhs[r_n] = row_lower[r_n]; } else { // Free constraint - Type N r_ty[r_n] = MPS_ROW_TY_N; rhs[r_n] = 0; } } for (HighsInt r_n = 0; r_n < num_row; r_n++) { if (rhs[r_n]) { have_rhs = true; break; } } // Check whether there is an objective offset - which will be defines as a RHS // on the cost row if (offset) have_rhs = true; for (HighsInt r_n = 0; r_n < num_row; r_n++) { if (ranges[r_n]) { have_ranges = true; break; } } have_int = false; if (integrality.size()) { for (HighsInt c_n = 0; c_n < num_col; c_n++) { if (integrality[c_n] == HighsVarType::kInteger || integrality[c_n] == HighsVarType::kSemiContinuous || integrality[c_n] == HighsVarType::kSemiInteger) { have_int = true; break; } } } for (HighsInt c_n = 0; c_n < num_col; c_n++) { if (col_lower[c_n]) { have_bounds = true; break; } bool discrete = false; if (have_int) discrete = integrality[c_n] == HighsVarType::kInteger || integrality[c_n] == HighsVarType::kSemiContinuous || integrality[c_n] == HighsVarType::kSemiInteger; if (!highs_isInfinity(col_upper[c_n]) || discrete) { // If the upper bound is finite, or the variable is integer or a // semi-variable then there is a BOUNDS section. Integer // variables with infinite upper bound are indicated as LI. All // semi-variables appear in the BOUNDS section. have_bounds = true; break; } } highsLogDev(log_options, HighsLogType::kInfo, "Model: RHS = %s\n RANGES = %s\n BOUNDS = %s\n", highsBoolToString(have_rhs).c_str(), highsBoolToString(have_ranges).c_str(), highsBoolToString(have_bounds).c_str()); // Field: 1 2 3 4 5 6 // Columns: 2-3 5-12 15-22 25-36 40-47 50-61 Indexed // from 1 Columns: 1-2 4-11 14-21 24-35 39-46 49-60 // Indexed from 0 // 1 2 3 4 5 6 // 0123456789012345678901234567890123456789012345678901234567890 // x11x22222222xx33333333xx444444444444xxx55555555xx666666666666 // ROWS // N ENDCAP // COLUMNS // CFOOD01 BAGR01 .00756 BFTT01 .150768 // RHS // RHSIDE HCAP01 -20. CBCAP01 -8. // RANGES // RANGE1 VILLKOR2 7. VILLKOR3 7. // BOUNDS // LO BOUND CFOOD01 850. // // NB d6cube has (eg) // COLUMNS // 1 1 1. 4 -1. // 1 5 -1. 1151 1. // Indexed from 0 // 1 2 3 4 5 6 // 0123456789012345678901234567890123456789012345678901234567890 // x11x22222222xx33333333xx444444444444xxx55555555xx666666666666 // // In fixed format the first column name is " 1 ", and its first entry is // in row "1 ". // // The free format reader thought that it had a name of "1 1" containing // spaces. fprintf(file, "NAME %s\n", model_name.c_str()); fprintf(file, "ROWS\n"); fprintf(file, " N %-8s\n", objective_name.c_str()); for (HighsInt r_n = 0; r_n < num_row; r_n++) { if (r_ty[r_n] == MPS_ROW_TY_E) { fprintf(file, " E %-8s\n", row_names[r_n].c_str()); } else if (r_ty[r_n] == MPS_ROW_TY_G) { fprintf(file, " G %-8s\n", row_names[r_n].c_str()); } else if (r_ty[r_n] == MPS_ROW_TY_L) { fprintf(file, " L %-8s\n", row_names[r_n].c_str()); } else { fprintf(file, " N %-8s\n", row_names[r_n].c_str()); } } bool integerFg = false; HighsInt nIntegerMk = 0; fprintf(file, "COLUMNS\n"); for (HighsInt c_n = 0; c_n < num_col; c_n++) { if (a_start[c_n] == a_start[c_n + 1] && col_cost[c_n] == 0) { // Possibly skip this column as it's zero and has no cost num_zero_no_cost_columns++; if (write_zero_no_cost_columns) { // Give the column a presence by writing out a zero cost double v = 0; fprintf(file, " %-8s %-8s %.15g\n", col_names[c_n].c_str(), objective_name.c_str(), v); } continue; } if (have_int) { if (integrality[c_n] == HighsVarType::kInteger && !integerFg) { // Start an integer section fprintf(file, " MARK%04" HIGHSINT_FORMAT " 'MARKER' 'INTORG'\n", nIntegerMk++); integerFg = true; } else if (integrality[c_n] != HighsVarType::kInteger && integerFg) { // End an integer section fprintf(file, " MARK%04" HIGHSINT_FORMAT " 'MARKER' 'INTEND'\n", nIntegerMk++); integerFg = false; } } if (col_cost[c_n] != 0) { double v = (HighsInt)sense * col_cost[c_n]; fprintf(file, " %-8s %-8s %.15g\n", col_names[c_n].c_str(), objective_name.c_str(), v); } for (HighsInt el_n = a_start[c_n]; el_n < a_start[c_n + 1]; el_n++) { double v = a_value[el_n]; HighsInt r_n = a_index[el_n]; fprintf(file, " %-8s %-8s %.15g\n", col_names[c_n].c_str(), row_names[r_n].c_str(), v); } } // End any integer section if (integerFg) fprintf(file, " MARK%04" HIGHSINT_FORMAT " 'MARKER' 'INTEND'\n", nIntegerMk++); have_rhs = true; if (have_rhs) { fprintf(file, "RHS\n"); if (offset) { // Handle the objective offset as a RHS entry for the cost row double v = -(HighsInt)sense * offset; fprintf(file, " RHS_V %-8s %.15g\n", objective_name.c_str(), v); } for (HighsInt r_n = 0; r_n < num_row; r_n++) { double v = rhs[r_n]; if (v) { fprintf(file, " RHS_V %-8s %.15g\n", row_names[r_n].c_str(), v); } } } if (have_ranges) { fprintf(file, "RANGES\n"); for (HighsInt r_n = 0; r_n < num_row; r_n++) { double v = ranges[r_n]; if (v) { fprintf(file, " RANGE %-8s %.15g\n", row_names[r_n].c_str(), v); } } } if (have_bounds) { fprintf(file, "BOUNDS\n"); for (HighsInt c_n = 0; c_n < num_col; c_n++) { double lb = col_lower[c_n]; double ub = col_upper[c_n]; bool discrete = false; if (have_int) discrete = integrality[c_n] == HighsVarType::kInteger || integrality[c_n] == HighsVarType::kSemiContinuous || integrality[c_n] == HighsVarType::kSemiInteger; if (a_start[c_n] == a_start[c_n + 1] && col_cost[c_n] == 0) { // Possibly skip this column if it's zero and has no cost if (!highs_isInfinity(ub) || lb) { // Column would have a bound to report num_zero_no_cost_columns_in_bounds_section++; } if (!write_zero_no_cost_columns) continue; } if (lb == ub) { // Equal lower and upper bounds: Fixed fprintf(file, " FX BOUND %-8s %.15g\n", col_names[c_n].c_str(), lb); } else if (highs_isInfinity(-lb) && highs_isInfinity(ub)) { // Infinite lower and upper bounds: Free fprintf(file, " FR BOUND %-8s\n", col_names[c_n].c_str()); } else { if (discrete) { if (integrality[c_n] == HighsVarType::kInteger) { if (lb == 0 && ub == 1) { // Binary fprintf(file, " BV BOUND %-8s\n", col_names[c_n].c_str()); } else { if (!highs_isInfinity(-lb)) { // Finite lower bound. No need to state this if LB is // zero unless UB is infinte if (lb || highs_isInfinity(ub)) fprintf(file, " LI BOUND %-8s %.15g\n", col_names[c_n].c_str(), lb); } if (!highs_isInfinity(ub)) { // Finite upper bound fprintf(file, " UI BOUND %-8s %.15g\n", col_names[c_n].c_str(), ub); } } } else if (integrality[c_n] == HighsVarType::kSemiInteger) { fprintf(file, " SI BOUND %-8s %.15g\n", col_names[c_n].c_str(), ub); fprintf(file, " LO BOUND %-8s %.15g\n", col_names[c_n].c_str(), lb); } else if (integrality[c_n] == HighsVarType::kSemiContinuous) { fprintf(file, " SC BOUND %-8s %.15g\n", col_names[c_n].c_str(), ub); fprintf(file, " LO BOUND %-8s %.15g\n", col_names[c_n].c_str(), lb); } } else { if (!highs_isInfinity(-lb)) { // Lower bounded variable - default is 0 if (lb) { fprintf(file, " LO BOUND %-8s %.15g\n", col_names[c_n].c_str(), lb); } } else { // Infinite lower bound fprintf(file, " MI BOUND %-8s\n", col_names[c_n].c_str()); } if (!highs_isInfinity(ub)) { // Upper bounded variable fprintf(file, " UP BOUND %-8s %.15g\n", col_names[c_n].c_str(), ub); } } } } } if (q_dim) { // Write out Hessian info assert((HighsInt)q_start.size() >= q_dim + 1); assert((HighsInt)q_index.size() >= q_start[q_dim]); assert((HighsInt)q_value.size() >= q_start[q_dim]); // Assumes that Hessian entries are the lower triangle column-wise fprintf(file, "QUADOBJ\n"); for (HighsInt col = 0; col < q_dim; col++) { for (HighsInt el = q_start[col]; el < q_start[col + 1]; el++) { HighsInt row = q_index[el]; assert(row >= col); // May have explicit zeroes on the diagonal if (q_value[el]) fprintf(file, " %-8s %-8s %.15g\n", col_names[col].c_str(), col_names[row].c_str(), (HighsInt)sense * q_value[el]); } } } fprintf(file, "ENDATA\n"); if (num_zero_no_cost_columns) highsLogUser(log_options, HighsLogType::kInfo, "Model has %" HIGHSINT_FORMAT " zero columns with no costs: %" HIGHSINT_FORMAT " have finite upper bounds " "or nonzero lower bounds and are %swritten in MPS file\n", num_zero_no_cost_columns, num_zero_no_cost_columns_in_bounds_section, write_zero_no_cost_columns ? "" : "not "); fclose(file); return HighsStatus::kOk; }